mirror of
https://github.com/noDRM/DeDRM_tools.git
synced 2026-03-20 21:08:57 +00:00
More work on standalone version, fix plugin
This commit is contained in:
@@ -9,10 +9,11 @@ from __future__ import absolute_import, print_function
|
||||
|
||||
OPT_SHORT_TO_LONG = [
|
||||
["c", "config"],
|
||||
["d", "dest"],
|
||||
["e", "extract"],
|
||||
["f", "force"],
|
||||
["h", "help"],
|
||||
["i", "import"],
|
||||
["o", "output"],
|
||||
["p", "password"],
|
||||
["q", "quiet"],
|
||||
["t", "test"],
|
||||
@@ -22,8 +23,6 @@ OPT_SHORT_TO_LONG = [
|
||||
|
||||
#@@CALIBRE_COMPAT_CODE@@
|
||||
|
||||
# Explicitly set the package identifier so we are allowed to import stuff ...
|
||||
__package__ = "DeDRM_plugin"
|
||||
import os, sys
|
||||
|
||||
|
||||
@@ -34,6 +33,9 @@ _additional_data = []
|
||||
_additional_params = []
|
||||
_function = None
|
||||
|
||||
global config_file_path
|
||||
config_file_path = "dedrm.json"
|
||||
|
||||
def print_fname(f, info):
|
||||
print(" " + f.ljust(15) + " " + info)
|
||||
|
||||
@@ -64,7 +66,7 @@ def print_err_header():
|
||||
print()
|
||||
|
||||
def print_help():
|
||||
from __init__ 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.")
|
||||
@@ -78,12 +80,13 @@ def print_help():
|
||||
print()
|
||||
print("Available functions:")
|
||||
print_fname("passhash", "Manage Adobe PassHashes")
|
||||
print_fname("remove_drm", "Remove DRM from one or multiple books")
|
||||
print()
|
||||
|
||||
# TODO: All parameters that are global should be listed here.
|
||||
|
||||
def print_credits():
|
||||
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("Based on DeDRM Calibre plugin by Apprentice Harper, Apprentice Alf and others.")
|
||||
print("See https://github.com/noDRM/DeDRM_tools for more information.")
|
||||
@@ -105,18 +108,28 @@ def print_credits():
|
||||
def handle_single_argument(arg, next):
|
||||
used_up = 0
|
||||
global _additional_params
|
||||
global config_file_path
|
||||
|
||||
if arg in ["--username", "--password"]:
|
||||
if arg in ["--username", "--password", "--output", "--outputdir"]:
|
||||
used_up = 1
|
||||
_additional_params.append(arg)
|
||||
if next is None:
|
||||
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])
|
||||
|
||||
elif arg == "--config":
|
||||
if next is None or len(next) == 0:
|
||||
print_err_header()
|
||||
print("Missing parameter for argument " + arg, file=sys.stderr)
|
||||
sys.exit(1)
|
||||
|
||||
elif arg in ["--help", "--credits", "--verbose", "--quiet", "--extract"]:
|
||||
config_file_path = next[0]
|
||||
used_up = 1
|
||||
|
||||
elif arg in ["--help", "--credits", "--verbose", "--quiet", "--extract", "--import", "--overwrite", "--force"]:
|
||||
_additional_params.append(arg)
|
||||
|
||||
|
||||
@@ -143,12 +156,28 @@ def handle_data(data):
|
||||
def execute_action(action, filenames, params):
|
||||
print("Executing '{0}' on file(s) {1} with parameters {2}".format(action, str(filenames), str(params)), file=sys.stderr)
|
||||
|
||||
if action == "passhash":
|
||||
if action == "help":
|
||||
print_help()
|
||||
sys.exit(0)
|
||||
|
||||
elif action == "passhash":
|
||||
from standalone.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
|
||||
perform_action(params, filenames)
|
||||
|
||||
elif action == "config":
|
||||
import prefs
|
||||
config = prefs.DeDRM_Prefs(os.path.abspath(config_file_path))
|
||||
print(config["adeptkeys"])
|
||||
|
||||
else:
|
||||
print("ERROR: This feature is still in development. Right now it can't be used yet.", file=sys.stderr)
|
||||
print("Command '"+action+"' is unknown.", file=sys.stderr)
|
||||
|
||||
|
||||
def main(argv):
|
||||
@@ -236,7 +265,7 @@ def main(argv):
|
||||
# This function gets told what to do and gets additional data (filenames).
|
||||
# It also receives additional parameters.
|
||||
# The rest of the code will be in different Python files.
|
||||
execute_action(_function, _additional_data, _additional_params)
|
||||
execute_action(_function.lower(), _additional_data, _additional_params)
|
||||
|
||||
|
||||
|
||||
|
||||
140
DeDRM_plugin/standalone/jsonconfig.py
Normal file
140
DeDRM_plugin/standalone/jsonconfig.py
Normal file
@@ -0,0 +1,140 @@
|
||||
#!/usr/bin/env python3
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
# CLI interface for the DeDRM plugin (useable without Calibre, too)
|
||||
# Config implementation
|
||||
|
||||
from __future__ import absolute_import, print_function
|
||||
|
||||
# Taken from Calibre code - Copyright © 2008, Kovid Goyal kovid@kovidgoyal.net, GPLv3
|
||||
|
||||
#@@CALIBRE_COMPAT_CODE@@
|
||||
|
||||
import sys, os, codecs, json
|
||||
|
||||
config_dir = "/"
|
||||
CONFIG_DIR_MODE = 0o700
|
||||
iswindows = sys.platform.startswith('win')
|
||||
|
||||
|
||||
filesystem_encoding = sys.getfilesystemencoding()
|
||||
if filesystem_encoding is None:
|
||||
filesystem_encoding = 'utf-8'
|
||||
else:
|
||||
try:
|
||||
if codecs.lookup(filesystem_encoding).name == 'ascii':
|
||||
filesystem_encoding = 'utf-8'
|
||||
# On linux, unicode arguments to os file functions are coerced to an ascii
|
||||
# bytestring if sys.getfilesystemencoding() == 'ascii', which is
|
||||
# just plain dumb. This is fixed by the icu.py module which, when
|
||||
# imported changes ascii to utf-8
|
||||
except Exception:
|
||||
filesystem_encoding = 'utf-8'
|
||||
|
||||
|
||||
class JSONConfig(dict):
|
||||
|
||||
EXTENSION = '.json'
|
||||
|
||||
|
||||
def __init__(self, rel_path_to_cf_file, base_path=config_dir):
|
||||
dict.__init__(self)
|
||||
self.no_commit = False
|
||||
self.defaults = {}
|
||||
self.file_path = os.path.join(base_path,
|
||||
*(rel_path_to_cf_file.split('/')))
|
||||
self.file_path = os.path.abspath(self.file_path)
|
||||
if not self.file_path.endswith(self.EXTENSION):
|
||||
self.file_path += self.EXTENSION
|
||||
|
||||
self.refresh()
|
||||
|
||||
def mtime(self):
|
||||
try:
|
||||
return os.path.getmtime(self.file_path)
|
||||
except OSError:
|
||||
return 0
|
||||
|
||||
def touch(self):
|
||||
try:
|
||||
os.utime(self.file_path, None)
|
||||
except OSError:
|
||||
pass
|
||||
|
||||
|
||||
def decouple(self, prefix):
|
||||
self.file_path = os.path.join(os.path.dirname(self.file_path), prefix + os.path.basename(self.file_path))
|
||||
self.refresh()
|
||||
|
||||
def refresh(self, clear_current=True):
|
||||
d = {}
|
||||
if os.path.exists(self.file_path):
|
||||
with open(self.file_path, "rb") as f:
|
||||
raw = f.read()
|
||||
try:
|
||||
d = self.raw_to_object(raw) if raw.strip() else {}
|
||||
except SystemError:
|
||||
pass
|
||||
except:
|
||||
import traceback
|
||||
traceback.print_exc()
|
||||
d = {}
|
||||
if clear_current:
|
||||
self.clear()
|
||||
self.update(d)
|
||||
|
||||
def has_key(self, key):
|
||||
return dict.__contains__(self, key)
|
||||
|
||||
def set(self, key, val):
|
||||
self.__setitem__(key, val)
|
||||
|
||||
def __delitem__(self, key):
|
||||
try:
|
||||
dict.__delitem__(self, key)
|
||||
except KeyError:
|
||||
pass # ignore missing keys
|
||||
else:
|
||||
self.commit()
|
||||
|
||||
def commit(self):
|
||||
if self.no_commit:
|
||||
return
|
||||
if hasattr(self, 'file_path') and self.file_path:
|
||||
dpath = os.path.dirname(self.file_path)
|
||||
if not os.path.exists(dpath):
|
||||
os.makedirs(dpath, mode=CONFIG_DIR_MODE)
|
||||
with open(self.file_path, "w") as f:
|
||||
raw = self.to_raw()
|
||||
f.seek(0)
|
||||
f.truncate()
|
||||
f.write(raw)
|
||||
|
||||
def __enter__(self):
|
||||
self.no_commit = True
|
||||
|
||||
def __exit__(self, *args):
|
||||
self.no_commit = False
|
||||
self.commit()
|
||||
|
||||
def raw_to_object(self, raw):
|
||||
return json.loads(raw)
|
||||
|
||||
def to_raw(self):
|
||||
return json.dumps(self, ensure_ascii=False)
|
||||
|
||||
def __getitem__(self, key):
|
||||
try:
|
||||
return dict.__getitem__(self, key)
|
||||
except KeyError:
|
||||
return self.defaults[key]
|
||||
|
||||
def get(self, key, default=None):
|
||||
try:
|
||||
return dict.__getitem__(self, key)
|
||||
except KeyError:
|
||||
return self.defaults.get(key, default)
|
||||
|
||||
def __setitem__(self, key, val):
|
||||
dict.__setitem__(self, key, val)
|
||||
self.commit()
|
||||
@@ -18,18 +18,19 @@ iswindows = sys.platform.startswith('win')
|
||||
isosx = sys.platform.startswith('darwin')
|
||||
|
||||
def print_passhash_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("passhash: Manage Adobe PassHashes")
|
||||
print()
|
||||
print_std_usage("passhash", "[ -u username -p password | -e ]")
|
||||
print_std_usage("passhash", "[ -u username -p password | -b base64str ] [ -i ] ")
|
||||
|
||||
print()
|
||||
print("Options: ")
|
||||
print_opt("u", "username", "Generate a PassHash with the given username")
|
||||
print_opt("p", "password", "Generate a PassHash with the given username")
|
||||
print_opt("e", "extract", "Extract PassHashes found on this machine")
|
||||
print_opt("p", "password", "Generate a PassHash with the given password")
|
||||
print_opt("e", "extract", "Display PassHashes found on this machine")
|
||||
print_opt("i", "import", "Import hashes into the JSON config file")
|
||||
|
||||
def perform_action(params, files):
|
||||
user = None
|
||||
@@ -40,6 +41,7 @@ def perform_action(params, files):
|
||||
return 0
|
||||
|
||||
extract = False
|
||||
import_to_json = True
|
||||
|
||||
while len(params) > 0:
|
||||
p = params.pop(0)
|
||||
@@ -52,21 +54,34 @@ def perform_action(params, files):
|
||||
elif p == "--help":
|
||||
print_passhash_help()
|
||||
return 0
|
||||
elif p == "--import":
|
||||
import_to_json = True
|
||||
|
||||
if not extract:
|
||||
if not extract and not import_to_json:
|
||||
if user is None:
|
||||
print("Missing parameter: --username", file=sys.stderr)
|
||||
if pwd is None:
|
||||
print("Missing parameter: --password", file=sys.stderr)
|
||||
if user is None or pwd is None:
|
||||
return 1
|
||||
|
||||
if user is None and pwd is not None:
|
||||
print("Parameter --password also requires --username", file=sys.stderr)
|
||||
return 1
|
||||
if user is not None and pwd is None:
|
||||
print("Parameter --username also requires --password", file=sys.stderr)
|
||||
return 1
|
||||
|
||||
if user is not None and pwd is not None:
|
||||
from ignoblekeyGenPassHash import generate_key
|
||||
key = generate_key(user, pwd)
|
||||
if import_to_json:
|
||||
# TODO: Import the key to the JSON
|
||||
pass
|
||||
|
||||
print(key.decode("utf-8"))
|
||||
|
||||
if extract:
|
||||
if extract or import_to_json:
|
||||
if not iswindows and not isosx:
|
||||
print("Extracting PassHash keys not supported on Linux.", file=sys.stderr)
|
||||
return 1
|
||||
@@ -92,11 +107,16 @@ def perform_action(params, files):
|
||||
|
||||
# Print all found keys
|
||||
for k in newkeys:
|
||||
print(k)
|
||||
if import_to_json:
|
||||
# TODO: Add keys to json
|
||||
pass
|
||||
|
||||
if extract:
|
||||
print(k)
|
||||
|
||||
|
||||
return 0
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
print("This code is not intended to be executed directly!")
|
||||
print("This code is not intended to be executed directly!", file=sys.stderr)
|
||||
209
DeDRM_plugin/standalone/remove_drm.py
Normal file
209
DeDRM_plugin/standalone/remove_drm.py
Normal file
@@ -0,0 +1,209 @@
|
||||
#!/usr/bin/env python3
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
# CLI interface for the DeDRM plugin (useable without Calibre, too)
|
||||
# DRM removal
|
||||
|
||||
from __future__ import absolute_import, print_function
|
||||
|
||||
# Copyright © 2021 NoDRM
|
||||
|
||||
#@@CALIBRE_COMPAT_CODE@@
|
||||
|
||||
import os, sys
|
||||
|
||||
from zipfile import ZipInfo, ZipFile, ZIP_STORED, ZIP_DEFLATED
|
||||
from contextlib import closing
|
||||
|
||||
from standalone.__init__ import print_opt, print_std_usage
|
||||
|
||||
iswindows = sys.platform.startswith('win')
|
||||
isosx = sys.platform.startswith('darwin')
|
||||
|
||||
def print_removedrm_help():
|
||||
from __init__ 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()
|
||||
print("Options: ")
|
||||
print_opt(None, "outputdir", "Folder to export the file(s) to")
|
||||
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)")
|
||||
|
||||
|
||||
def determine_file_type(file):
|
||||
# Returns a file type:
|
||||
# "PDF", "PDB", "MOBI", "TPZ", "LCP", "ADEPT", "ADEPT-PassHash", "KFX-ZIP", "ZIP" or None
|
||||
|
||||
f = open(file, "rb")
|
||||
fdata = f.read(100)
|
||||
f.close()
|
||||
|
||||
if fdata.startswith(b"PK\x03\x04"):
|
||||
pass
|
||||
# Either LCP, Adobe, or Amazon
|
||||
elif fdata.startswith(b"%PDF"):
|
||||
return "PDF"
|
||||
elif fdata[0x3c:0x3c+8] == b"PNRdPPrs" or fdata[0x3c:0x3c+8] == b"PDctPPrs":
|
||||
return "PDB"
|
||||
elif fdata[0x3c:0x3c+8] == b"BOOKMOBI" or fdata[0x3c:0x3c+8] == b"TEXtREAd":
|
||||
return "MOBI"
|
||||
elif fdata.startswith(b"TPZ"):
|
||||
return "TPZ"
|
||||
else:
|
||||
return None
|
||||
# Unknown file type
|
||||
|
||||
|
||||
# If it's a ZIP, determine the type.
|
||||
|
||||
from lcpdedrm import isLCPbook
|
||||
if isLCPbook(file):
|
||||
return "LCP"
|
||||
|
||||
from ineptepub import adeptBook, isPassHashBook
|
||||
if adeptBook(file):
|
||||
if isPassHashBook(file):
|
||||
return "ADEPT-PassHash"
|
||||
else:
|
||||
return "ADEPT"
|
||||
|
||||
try:
|
||||
# Amazon / KFX-ZIP has a file that starts with b'\xeaDRMION\xee' in the ZIP.
|
||||
with closing(ZipFile(open(file, "rb"))) as book:
|
||||
for subfilename in book.namelist():
|
||||
with book.open(subfilename) as subfile:
|
||||
data = subfile.read(8)
|
||||
if data == b'\xeaDRMION\xee':
|
||||
return "KFX-ZIP"
|
||||
except:
|
||||
pass
|
||||
|
||||
return "ZIP"
|
||||
|
||||
|
||||
|
||||
|
||||
def dedrm_single_file(input_file, output_file):
|
||||
# When this runs, all the stupid file handling is done.
|
||||
# Just take the file at the absolute path "input_file"
|
||||
# and export it, DRM-free, to "output_file".
|
||||
|
||||
# Use a temp file as input_file and output_file
|
||||
# might be identical.
|
||||
|
||||
# The output directory might not exist yet.
|
||||
|
||||
print("File " + input_file + " to " + output_file)
|
||||
|
||||
# Okay, first check the file type and don't rely on the extension.
|
||||
try:
|
||||
ftype = determine_file_type(input_file)
|
||||
except:
|
||||
print("Can't determine file type for this file.")
|
||||
ftype = None
|
||||
|
||||
if ftype is None:
|
||||
return
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
def perform_action(params, files):
|
||||
output = None
|
||||
outputdir = None
|
||||
force = False
|
||||
overwrite_original = False
|
||||
|
||||
|
||||
if len(files) == 0:
|
||||
print_removedrm_help()
|
||||
return 0
|
||||
|
||||
while len(params) > 0:
|
||||
p = params.pop(0)
|
||||
if p == "--output":
|
||||
output = params.pop(0)
|
||||
elif p == "--outputdir":
|
||||
outputdir = params.pop(0)
|
||||
elif p == "--force":
|
||||
force = True
|
||||
elif p == "--overwrite":
|
||||
overwrite_original = True
|
||||
force = True
|
||||
elif p == "--help":
|
||||
print_removedrm_help()
|
||||
return 0
|
||||
|
||||
if overwrite_original and (output is not None or outputdir is not None):
|
||||
print("Can't use --overwrite together with --output or --outputdir.")
|
||||
return 1
|
||||
|
||||
if output is not None and os.path.isfile(output) and not force:
|
||||
print("Output file already exists. Use --force to overwrite.", file=sys.stderr)
|
||||
return 1
|
||||
|
||||
|
||||
if output is not None and len(files) > 1:
|
||||
print("Cannot set output file name if there's multiple input files.", file=sys.stderr)
|
||||
return 1
|
||||
|
||||
if outputdir is not None and output is not None and os.path.isabs(output):
|
||||
print("--output parameter is absolute path despite --outputdir being set.")
|
||||
print("Remove --outputdir, or give a relative path to --output.")
|
||||
return 1
|
||||
|
||||
|
||||
|
||||
for file in files:
|
||||
|
||||
file = os.path.abspath(file)
|
||||
|
||||
if not os.path.isfile(file):
|
||||
print("Skipping file " + file + " - not found.")
|
||||
continue
|
||||
|
||||
if overwrite_original:
|
||||
output_filename = file
|
||||
else:
|
||||
if output is not None:
|
||||
# Due to the check above, we DO only have one file here.
|
||||
if outputdir is not None and not os.path.isabs(output):
|
||||
output_filename = os.path.join(outputdir, output)
|
||||
else:
|
||||
output_filename = os.path.abspath(output)
|
||||
else:
|
||||
if outputdir is None:
|
||||
outputdir = os.getcwd()
|
||||
output_filename = os.path.join(outputdir, os.path.basename(file))
|
||||
output_filename = os.path.abspath(output_filename)
|
||||
|
||||
if output_filename == file:
|
||||
# If we export to the import folder, add a suffix to the file name.
|
||||
fn, f_ext = os.path.splitext(output_filename)
|
||||
output_filename = fn + "_nodrm" + f_ext
|
||||
|
||||
|
||||
|
||||
if os.path.isfile(output_filename) and not force:
|
||||
print("Skipping file " + file + " because output file already exists (use --force).", file=sys.stderr)
|
||||
continue
|
||||
|
||||
|
||||
|
||||
dedrm_single_file(file, output_filename)
|
||||
|
||||
|
||||
|
||||
|
||||
return 0
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
print("This code is not intended to be executed directly!", file=sys.stderr)
|
||||
Reference in New Issue
Block a user