import os, sys
+# When executed as a module ("python -m DeDRM_plugin.standalone.__init__"),
+# this file becomes '__main__'. Ensure the package name also maps to this
+# module so that sibling modules can reliably import it.
+if __name__ == "__main__":
+ sys.modules.setdefault('DeDRM_plugin.standalone.__init__', sys.modules[__name__])
+
global _additional_data
global _additional_params
global config_file_path
config_file_path = "dedrm.json"
+# Path to a Kindle voucher key file passed via --keyfile
+kfx_skeyfile = None
+
def print_fname(f, info):
print(" " + f.ljust(15) + " " + info)
print(" python3 DeDRM_plugin.zip "+name+" "+param_string, file=sys.stderr)
def print_err_header():
- from __init__ import PLUGIN_NAME, PLUGIN_VERSION # type: ignore
+ from ..__version import PLUGIN_NAME, PLUGIN_VERSION
print(PLUGIN_NAME + " v" + PLUGIN_VERSION + " - DRM removal plugin by noDRM")
print()
def print_help():
- from __version import PLUGIN_NAME, PLUGIN_VERSION
+ from ..__version import PLUGIN_NAME, PLUGIN_VERSION
print(PLUGIN_NAME + " v" + PLUGIN_VERSION + " - DRM removal plugin by noDRM")
print("Based on DeDRM Calibre plugin by Apprentice Harper, Apprentice Alf and others.")
print("See https://github.com/noDRM/DeDRM_tools for more information.")
# TODO: All parameters that are global should be listed here.
def print_credits():
- from __version import PLUGIN_NAME, PLUGIN_VERSION
+ from ..__version import PLUGIN_NAME, PLUGIN_VERSION
print(PLUGIN_NAME + " v" + PLUGIN_VERSION + " - Calibre DRM removal plugin by noDRM")
print("Based on DeDRM Calibre plugin by Apprentice Harper, Apprentice Alf and others.")
print("See https://github.com/noDRM/DeDRM_tools for more information.")
global _additional_params
global config_file_path
- if arg in ["--username", "--password", "--output", "--outputdir"]:
+ global kfx_skeyfile
+
+ if arg in ["--username", "--password", "--output", "--outputdir", "--keyfile"]:
used_up = 1
_additional_params.append(arg)
- if next is None or len(next) == 0:
+ if next is None or len(next) == 0:
print_err_header()
print("Missing parameter for argument " + arg, file=sys.stderr)
sys.exit(1)
else:
- _additional_params.append(next[0])
+ val = next[0]
+ if arg == "--keyfile":
+ kfx_skeyfile = val
+ else:
+ _additional_params.append(val)
elif arg == "--config":
if next is None or len(next) == 0:
sys.exit(0)
elif action == "passhash":
- from standalone.passhash import perform_action
+ from .passhash import perform_action
perform_action(params, filenames)
elif action == "remove_drm":
if not os.path.isfile(os.path.abspath(config_file_path)):
print("Config file missing ...")
- from standalone.remove_drm import perform_action
+ from .remove_drm import perform_action
perform_action(params, filenames)
elif action == "config":
from zipfile import ZipInfo, ZipFile, ZIP_STORED, ZIP_DEFLATED
from contextlib import closing
-from standalone.__init__ import print_opt, print_std_usage
+# Ensure absolute imports within the main plugin work when this module is run
+# as part of the standalone package
+package_root = os.path.abspath(os.path.join(os.path.dirname(__file__), '..'))
+if package_root not in sys.path:
+ sys.path.insert(0, package_root)
+
+from .__init__ import print_opt, print_std_usage
iswindows = sys.platform.startswith('win')
isosx = sys.platform.startswith('darwin')
+# Path to a Kindle voucher key file used for KFX books
+kfx_skeyfile = None
+
def print_removedrm_help():
- from __init__ import PLUGIN_NAME, PLUGIN_VERSION
+ from ..__version import PLUGIN_NAME, PLUGIN_VERSION
print(PLUGIN_NAME + " v" + PLUGIN_VERSION + " - Calibre DRM removal plugin by noDRM")
print()
print("remove_drm: Remove DRM from one or multiple files")
print()
- print_std_usage("remove_drm", "<filename> ... [ -o <filename> ] [ -f ]")
+ print_std_usage("remove_drm", "<filename> ... [ -o <filename> ] [ -f ] [ --keyfile <file> ]")
print()
print("Options: ")
print_opt("o", "output", "File name to export the file to")
print_opt("f", "force", "Overwrite output file if it already exists")
print_opt(None, "overwrite", "Replace DRMed file with DRM-free file (implies --force)")
+ print_opt(None, "keyfile", "Path to a Kindle voucher key file")
def determine_file_type(file):
print("File " + input_file + " to " + output_file)
# Okay, first check the file type and don't rely on the extension.
- try:
+ try:
ftype = determine_file_type(input_file)
- except:
+ except Exception as e:
print("Can't determine file type for this file.")
ftype = None
+ try:
+ with open(input_file, 'rb') as fh:
+ hdr = fh.read(4)
+ if hdr == b'PK\x03\x04':
+ # check if ZIP contains a DRMION file
+ from zipfile import ZipFile
+ with ZipFile(input_file, 'r') as zf:
+ for name in zf.namelist():
+ with zf.open(name) as sf:
+ if sf.read(8) == b'\xeaDRMION\xee':
+ ftype = "KFX-ZIP"
+ break
+ if ftype is None:
+ ftype = "ZIP"
+ except Exception:
+ pass
- if ftype is None:
+ if ftype is None:
+ return
+
+ if ftype == "KFX-ZIP":
+ from ..kfxdedrm import KFXZipBook
+ from .__init__ import kfx_skeyfile
+ keyfile = kfx_skeyfile
+ temp_keyfile = None
+ # Some key files omit the trailing '.voucher' on the voucher ID.
+ # Add a duplicate entry with the suffix so the ion parser can match.
+ if keyfile and os.path.isfile(keyfile):
+ with open(keyfile, "r", encoding="utf8") as fh:
+ lines = fh.read().splitlines()
+ amended = []
+ changed = False
+ for line in lines:
+ amended.append(line)
+ parts = line.split("$", 1)
+ if len(parts) > 1:
+ vid, rest = parts[0], parts[1]
+ if not vid.endswith(".voucher"):
+ amended.append(f"{vid}.voucher${rest}")
+ changed = True
+ if changed:
+ import tempfile
+ tf = tempfile.NamedTemporaryFile("w", delete=False)
+ tf.write("\n".join(amended) + "\n")
+ tf.close()
+ temp_keyfile = tf.name
+ keyfile = temp_keyfile
+ try:
+ book = KFXZipBook(input_file, keyfile)
+ book.processBook([''])
+ # Ensure output directory exists
+ outdir = os.path.dirname(output_file)
+ if outdir and not os.path.isdir(outdir):
+ os.makedirs(outdir, exist_ok=True)
+ book.getFile(output_file)
+ print("Saved decrypted KFX book to " + output_file)
+ except Exception as e:
+ print("Failed to remove DRM: " + str(e), file=sys.stderr)
+ finally:
+ if temp_keyfile:
+ try:
+ os.unlink(temp_keyfile)
+ except Exception:
+ pass
return