Add fully functional standalone CLI (no Calibre required)#977
Open
davidxue1989 wants to merge 4 commits intonoDRM:masterfrom
Open
Add fully functional standalone CLI (no Calibre required)#977davidxue1989 wants to merge 4 commits intonoDRM:masterfrom
davidxue1989 wants to merge 4 commits intonoDRM:masterfrom
Conversation
Implements a fully functional standalone command-line interface that removes DRM from ebooks (Adobe ADEPT, Kindle, B&N, eReader, PDF) without requiring Calibre to be installed. ## Entry point `dedrm.py` is the single command for agents and users: python3 dedrm.py remove_drm book.epub -o book_nodrm.epub It auto-installs Python dependencies (lxml, pycryptodome), builds DeDRM_plugin.zip from source on first run, detects source staleness and rebuilds when any source file changes, then delegates all arguments to the plugin CLI. Exit codes: 0 = success, 1 = decryption failure. ## Build pipeline `build_plugin.py` produces DeDRM_plugin.zip by copying DeDRM_plugin/ source and inlining the calibre compat shim (replacing #@@CALIBRE_COMPAT_CODE@@ placeholders). The zip is a local build cache — gitignored, never committed, rebuilt automatically by dedrm.py when stale. ## Design decisions **Source is the truth, not the zip.** Committing a binary zip would be opaque to security audits and could drift from source. Agents that use this as a skill can read all .py files before trusting and running the tool. The build step is a transparent, deterministic transformation of that same source. **Compat code extended for Python 3.12+ standalone use.** The existing __calibre_compat_code.py shim already handled the Calibre case by setting __package__ = "calibre_plugins.dedrm". An else-branch adds a _DeDRMFinder meta_path hook (using find_spec, required in Python 3.12+) that maps `import dedrm.X` to `import X`, enabling relative imports in ineptepub.py and other modules when running from a flat zip without Calibre's package context. Calibre behavior is completely unchanged. **prefs.py import fix.** `from __init__ import PLUGIN_NAME` is ambiguous in the flat-zip context because the calibre compat code adds `DeDRM_plugin.zip/standalone` to sys.path (to support Calibre < 5), which shadows the root __init__.py. Fixed with a try/except that prefers `from __version__ import PLUGIN_NAME` and falls back for compatibility. **Bug fix: PassHash key loop.** The original Calibre ePubDecrypt() returned the still-encrypted file inside the auto-discovered-key loop after the first attempt regardless of success. The standalone implementation tries all keys before reporting failure. **Plain KFX detection.** Added `\xeaDRMION\xee` magic-byte detection for KFX files not wrapped in a ZIP container (plain .kfx), routing them to the same k4mobidedrm branch as KFX-ZIP. **No DeACSM integration.** The Calibre plugin's checkForDeACSMkeys() imports from calibre_plugins.deacsm — impossible outside Calibre. Omitted with a documented workaround: export the key from DeACSM manually and add to dedrm.json. **Watermark removal omitted.** epubwatermark.py uses the Calibre plugin object (self.temporary_file etc.) throughout. Out of scope for standalone. **Failure semantics.** Calibre returns the encrypted file on failure so Calibre can handle it gracefully. The standalone CLI returns exit code 1 and writes no output file — correct for a command-line tool. ## Key storage Decryption keys are auto-discovered from installed ADE / Kindle / NOOK apps on Windows and macOS. Successfully used keys are saved to dedrm.json in the working directory (gitignored — contains private cryptographic material). Use --config to specify a stable path. On Linux, populate dedrm.json manually by transferring keys from a Windows/Mac machine. ## Files changed vs upstream Modified: - DeDRM_plugin/__calibre_compat_code.py (standalone import hook, else-branch only) - DeDRM_plugin/prefs.py (PLUGIN_NAME import fix) - DeDRM_plugin/standalone/remove_drm.py (full implementation, was a stub) - .gitignore (zip artifacts, dedrm.json, .claude/) Added: - dedrm.py Entry point: dep check + stale-zip rebuild + delegation - build_plugin.py Fast zip builder for development - requirements.txt Python dependencies for standalone use - STANDALONE_CLI.md Usage, architecture, and design decisions - dedrm_revision_plan.md Implementation plan (dev reference) Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Adds a dedicated "Using as an Agent Skill" section to STANDALONE_CLI.md covering the full trust chain an AI agent follows when pulling this repo as a skill: clone → read source → audit → call dedrm.py → auto-build → run Key design decisions documented: - DeDRM_plugin.zip is never committed — it is a local build cache derived from the audited source, rebuilt on demand and on source staleness - dedrm.py is the single entry point (~80 lines): dep install, stale-zip detection, and delegation via subprocess — no magic, fully readable - Includes a JSON skill descriptor suitable for agent skill registries Also updates dedrm_revision_plan.md to mark the plan as implemented and records the extended agent-skill goals that emerged during design review, explaining why the zip is gitignored rather than committed. Verified: python3 dedrm.py correctly decrypts an Adobe ADEPT ePub (Unwanted.epub) in 0.8 s via UUID-matched ADE key auto-discovery. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
dedrm.py already auto-installs dependencies on first run. Telling users to run pip install manually contradicted the single-command design and was confusing. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Bug fix: standalone/__init__.py execute_action was silently dropping the return value of perform_action(), so the process always exited 0 even on decryption failure. Fixed with sys.exit(perform_action(...)). Verified: missing-file → exit 1, successful decrypt → exit 0. Doc corrections in STANDALONE_CLI.md: - Key lookup order was reversed: stored keys are tried FIRST, auto-discovery is the fallback (not the other way around) - Progress messages go to stdout, not stderr (only arg errors → stderr) - Module count corrected: ~48 .py files (was "~15") - Added Windows glob caveat (*.epub does not expand in cmd/PowerShell) - Removed duplicate transparency content between Agent Skill and Architecture sections - Config-missing behaviour documented (warns and continues with defaults) - Line count estimates updated to match actual file sizes - Files Changed table updated to include the __init__.py exit-code fix Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Implements a complete standalone CLI so users can remove DRM without installing Calibre. The DRM-removal logic already existed in the individual format modules — this PR adds the missing glue, a self-bootstrapping entry point, and documentation.
What was missing / broken
standalone/remove_drm.pywas a non-functional stub (upstream comment: "there's only a rough code structure and no working code yet")standalone/__init__.pysilently droppedperform_action's return value, so the process always exited0even on decryption failureChanges
Bug fixes
standalone/__init__.py—execute_actionnow propagatesperform_action's exit code viasys.exit(). Failure now correctly returns exit code1.Core implementation
standalone/remove_drm.py— full implementation ofdedrm_single_file()and helpers, covering all supported formats:Compat shim
__calibre_compat_code.py— added_DeDRMFindermeta path hook for standalone relative-import support (Python 3.12+ compatible). The Calibre branch is unchanged.Minor fix
prefs.py—from __init__ import→from __version__ importto avoid import ambiguity when running as a zipapp.New files
dedrm.py— single entry point for agents and users: auto-installs deps (lxml,pycryptodome), buildsDeDRM_plugin.zipfrom source if missing or stale, delegates all args to the plugin CLI unchangedbuild_plugin.py— fast zip builder for development iteration (~65 lines)requirements.txt— explicit dependency list with floor versionsSTANDALONE_CLI.md— user and agent documentationdedrm_revision_plan.md— developer design referenceDesign: agent-skill transparency
DeDRM_plugin.zipis not committed. It is.gitignoredand rebuilt on demand from source. This ensures any AI agent pulling this repo as a skill can audit every line before execution:No binary blobs. No network calls at runtime. No eval/exec of downloaded code.
Calibre plugin compatibility
All changes are additive or in standalone-only branches. The Calibre plugin path is unaffected.
make_release.pystill produces a validDeDRM_plugin.zipfor Calibre users.Tested
Unwanted.epubvia Adobe Digital Editions): decrypted in ~0.8s via UUID-matched ADE key auto-discovery on Windows0on success,1on failure🤖 Generated with Claude Code