More work on standalone version, fix plugin
This commit is contained in:
parent
5ace15e912
commit
a275d5d819
|
@ -54,3 +54,4 @@ List of changes since the fork of Apprentice Harper's repository:
|
||||||
- ineptpdf: Support for V=5, R=5 and R=6 PDF files, and for AES256-encrypted PDFs.
|
- ineptpdf: Support for V=5, R=5 and R=6 PDF files, and for AES256-encrypted PDFs.
|
||||||
- ineptpdf: Disable cross-reference streams in the output file. This may make PDFs slightly larger, but the current code for cross-reference streams seems to be buggy and sometimes creates corrupted PDFs.
|
- ineptpdf: Disable cross-reference streams in the output file. This may make PDFs slightly larger, but the current code for cross-reference streams seems to be buggy and sometimes creates corrupted PDFs.
|
||||||
- Drop support for importing key data from the ancient, pre "DeDRM" Calibre plugins ("Ignoble Epub DeDRM", "eReader PDB 2 PML" and "K4MobiDeDRM"). These are from 2011, I doubt anyone still has these installed, I can't even find a working link for these to test them. If you still have encryption keys in one of these plugins, you will need to update to DeDRM v10.0.2 or older (to convert the keys) before updating to DeDRM v10.0.3 or newer.
|
- Drop support for importing key data from the ancient, pre "DeDRM" Calibre plugins ("Ignoble Epub DeDRM", "eReader PDB 2 PML" and "K4MobiDeDRM"). These are from 2011, I doubt anyone still has these installed, I can't even find a working link for these to test them. If you still have encryption keys in one of these plugins, you will need to update to DeDRM v10.0.2 or older (to convert the keys) before updating to DeDRM v10.0.3 or newer.
|
||||||
|
- Some Python3 bugfixes for Amazon books (merged #10 by ableeker).
|
|
@ -17,7 +17,7 @@ p {margin-top: 0}
|
||||||
|
|
||||||
<body>
|
<body>
|
||||||
|
|
||||||
<h1>DeDRM Plugin <span class="version">(v10.0.0)</span></h1>
|
<h1>DeDRM Plugin <span class="version">(v10.0.2)</span></h1>
|
||||||
|
|
||||||
<p>This plugin removes DRM from ebooks when they are imported into calibre. If you already have DRMed ebooks in your calibre library, you will need to remove them and import them again.</p>
|
<p>This plugin removes DRM from ebooks when they are imported into calibre. If you already have DRMed ebooks in your calibre library, you will need to remove them and import them again.</p>
|
||||||
|
|
||||||
|
|
|
@ -2,13 +2,19 @@
|
||||||
#@@CALIBRE_COMPAT_CODE_START@@
|
#@@CALIBRE_COMPAT_CODE_START@@
|
||||||
import sys, os
|
import sys, os
|
||||||
|
|
||||||
# Explicitly allow importing the parent folder
|
# Explicitly allow importing everything ...
|
||||||
if os.path.dirname(os.path.dirname(os.path.abspath(__file__))) not in sys.path:
|
if os.path.dirname(os.path.dirname(os.path.abspath(__file__))) not in sys.path:
|
||||||
sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
|
sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
|
||||||
|
if os.path.dirname(os.path.abspath(__file__)) not in sys.path:
|
||||||
|
sys.path.insert(0, os.path.dirname(os.path.abspath(__file__)))
|
||||||
|
|
||||||
# Bugfix for Calibre < 5:
|
# Bugfix for Calibre < 5:
|
||||||
if "calibre" in sys.modules and sys.version_info[0] == 2:
|
if "calibre" in sys.modules and sys.version_info[0] == 2:
|
||||||
from calibre.utils.config import config_dir
|
from calibre.utils.config import config_dir
|
||||||
if os.path.join(config_dir, "plugins", "DeDRM.zip") not in sys.path:
|
if os.path.join(config_dir, "plugins", "DeDRM.zip") not in sys.path:
|
||||||
sys.path.insert(0, os.path.join(config_dir, "plugins", "DeDRM.zip"))
|
sys.path.insert(0, os.path.join(config_dir, "plugins", "DeDRM.zip"))
|
||||||
|
|
||||||
|
# Explicitly set the package identifier so we are allowed to import stuff ...
|
||||||
|
#__package__ = "DeDRM_plugin"
|
||||||
|
|
||||||
#@@CALIBRE_COMPAT_CODE_END@@
|
#@@CALIBRE_COMPAT_CODE_END@@
|
||||||
|
|
|
@ -8,7 +8,6 @@ from __future__ import print_function
|
||||||
# Copyright © 2021 NoDRM
|
# Copyright © 2021 NoDRM
|
||||||
|
|
||||||
__license__ = 'GPL v3'
|
__license__ = 'GPL v3'
|
||||||
__version__ = '10.0.2'
|
|
||||||
__docformat__ = 'restructuredtext en'
|
__docformat__ = 'restructuredtext en'
|
||||||
|
|
||||||
|
|
||||||
|
@ -88,12 +87,6 @@ __docformat__ = 'restructuredtext en'
|
||||||
Decrypt DRMed ebooks.
|
Decrypt DRMed ebooks.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
PLUGIN_NAME = "DeDRM"
|
|
||||||
PLUGIN_VERSION_TUPLE = tuple([int(x) for x in __version__.split(".")])
|
|
||||||
PLUGIN_VERSION = ".".join([str(x)for x in PLUGIN_VERSION_TUPLE])
|
|
||||||
# Include an html helpfile in the plugin's zipfile with the following name.
|
|
||||||
RESOURCE_NAME = PLUGIN_NAME + '_Help.htm'
|
|
||||||
|
|
||||||
import codecs
|
import codecs
|
||||||
import sys, os
|
import sys, os
|
||||||
import time
|
import time
|
||||||
|
@ -101,6 +94,8 @@ import traceback
|
||||||
|
|
||||||
#@@CALIBRE_COMPAT_CODE@@
|
#@@CALIBRE_COMPAT_CODE@@
|
||||||
|
|
||||||
|
import __version
|
||||||
|
|
||||||
class DeDRMError(Exception):
|
class DeDRMError(Exception):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
@ -147,6 +142,10 @@ class SafeUnbuffered:
|
||||||
def __getattr__(self, attr):
|
def __getattr__(self, attr):
|
||||||
return getattr(self.stream, attr)
|
return getattr(self.stream, attr)
|
||||||
|
|
||||||
|
PLUGIN_NAME = __version.PLUGIN_NAME
|
||||||
|
PLUGIN_VERSION = __version.PLUGIN_VERSION
|
||||||
|
PLUGIN_VERSION_TUPLE = __version.PLUGIN_VERSION_TUPLE
|
||||||
|
|
||||||
class DeDRM(FileTypePlugin):
|
class DeDRM(FileTypePlugin):
|
||||||
name = PLUGIN_NAME
|
name = PLUGIN_NAME
|
||||||
description = "Removes DRM from Amazon Kindle, Adobe Adept (including Kobo), Readium LCP, Barnes & Noble, Mobipocket and eReader ebooks. Credit given to i♥cabbages and The Dark Reverser for the original stand-alone scripts."
|
description = "Removes DRM from Amazon Kindle, Adobe Adept (including Kobo), Readium LCP, Barnes & Noble, Mobipocket and eReader ebooks. Credit given to i♥cabbages and The Dark Reverser for the original stand-alone scripts."
|
||||||
|
|
|
@ -0,0 +1,12 @@
|
||||||
|
#!/usr/bin/env python3
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
|
||||||
|
#@@CALIBRE_COMPAT_CODE@@
|
||||||
|
|
||||||
|
PLUGIN_NAME = "DeDRM"
|
||||||
|
__version__ = '10.0.2'
|
||||||
|
|
||||||
|
PLUGIN_VERSION_TUPLE = tuple([int(x) for x in __version__.split(".")])
|
||||||
|
PLUGIN_VERSION = ".".join([str(x)for x in PLUGIN_VERSION_TUPLE])
|
||||||
|
# Include an html helpfile in the plugin's zipfile with the following name.
|
||||||
|
RESOURCE_NAME = PLUGIN_NAME + '_Help.htm'
|
|
@ -28,7 +28,7 @@ from calibre.constants import iswindows, isosx
|
||||||
|
|
||||||
|
|
||||||
from __init__ import PLUGIN_NAME, PLUGIN_VERSION
|
from __init__ import PLUGIN_NAME, PLUGIN_VERSION
|
||||||
from __init__ import RESOURCE_NAME as help_file_name
|
from __version import RESOURCE_NAME as help_file_name
|
||||||
from utilities import uStrCmp
|
from utilities import uStrCmp
|
||||||
|
|
||||||
import prefs
|
import prefs
|
||||||
|
|
|
@ -12,12 +12,20 @@ import traceback
|
||||||
#@@CALIBRE_COMPAT_CODE@@
|
#@@CALIBRE_COMPAT_CODE@@
|
||||||
|
|
||||||
|
|
||||||
|
try:
|
||||||
from calibre.utils.config import JSONConfig
|
from calibre.utils.config import JSONConfig
|
||||||
|
except:
|
||||||
|
from standalone.jsonconfig import JSONConfig
|
||||||
|
|
||||||
from __init__ import PLUGIN_NAME
|
from __init__ import PLUGIN_NAME
|
||||||
|
|
||||||
class DeDRM_Prefs():
|
class DeDRM_Prefs():
|
||||||
def __init__(self):
|
def __init__(self, json_path=None):
|
||||||
|
if json_path is None:
|
||||||
JSON_PATH = os.path.join("plugins", PLUGIN_NAME.strip().lower().replace(' ', '_') + '.json')
|
JSON_PATH = os.path.join("plugins", PLUGIN_NAME.strip().lower().replace(' ', '_') + '.json')
|
||||||
|
else:
|
||||||
|
JSON_PATH = json_path
|
||||||
|
|
||||||
self.dedrmprefs = JSONConfig(JSON_PATH)
|
self.dedrmprefs = JSONConfig(JSON_PATH)
|
||||||
|
|
||||||
self.dedrmprefs.defaults['configured'] = False
|
self.dedrmprefs.defaults['configured'] = False
|
||||||
|
|
|
@ -9,10 +9,11 @@ from __future__ import absolute_import, print_function
|
||||||
|
|
||||||
OPT_SHORT_TO_LONG = [
|
OPT_SHORT_TO_LONG = [
|
||||||
["c", "config"],
|
["c", "config"],
|
||||||
["d", "dest"],
|
|
||||||
["e", "extract"],
|
["e", "extract"],
|
||||||
["f", "force"],
|
["f", "force"],
|
||||||
["h", "help"],
|
["h", "help"],
|
||||||
|
["i", "import"],
|
||||||
|
["o", "output"],
|
||||||
["p", "password"],
|
["p", "password"],
|
||||||
["q", "quiet"],
|
["q", "quiet"],
|
||||||
["t", "test"],
|
["t", "test"],
|
||||||
|
@ -22,8 +23,6 @@ OPT_SHORT_TO_LONG = [
|
||||||
|
|
||||||
#@@CALIBRE_COMPAT_CODE@@
|
#@@CALIBRE_COMPAT_CODE@@
|
||||||
|
|
||||||
# Explicitly set the package identifier so we are allowed to import stuff ...
|
|
||||||
__package__ = "DeDRM_plugin"
|
|
||||||
import os, sys
|
import os, sys
|
||||||
|
|
||||||
|
|
||||||
|
@ -34,6 +33,9 @@ _additional_data = []
|
||||||
_additional_params = []
|
_additional_params = []
|
||||||
_function = None
|
_function = None
|
||||||
|
|
||||||
|
global config_file_path
|
||||||
|
config_file_path = "dedrm.json"
|
||||||
|
|
||||||
def print_fname(f, info):
|
def print_fname(f, info):
|
||||||
print(" " + f.ljust(15) + " " + info)
|
print(" " + f.ljust(15) + " " + info)
|
||||||
|
|
||||||
|
@ -64,7 +66,7 @@ def print_err_header():
|
||||||
print()
|
print()
|
||||||
|
|
||||||
def print_help():
|
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(PLUGIN_NAME + " v" + PLUGIN_VERSION + " - DRM removal plugin by noDRM")
|
||||||
print("Based on DeDRM Calibre plugin by Apprentice Harper, Apprentice Alf and others.")
|
print("Based on DeDRM Calibre plugin by Apprentice Harper, Apprentice Alf and others.")
|
||||||
print("See https://github.com/noDRM/DeDRM_tools for more information.")
|
print("See https://github.com/noDRM/DeDRM_tools for more information.")
|
||||||
|
@ -78,12 +80,13 @@ def print_help():
|
||||||
print()
|
print()
|
||||||
print("Available functions:")
|
print("Available functions:")
|
||||||
print_fname("passhash", "Manage Adobe PassHashes")
|
print_fname("passhash", "Manage Adobe PassHashes")
|
||||||
|
print_fname("remove_drm", "Remove DRM from one or multiple books")
|
||||||
print()
|
print()
|
||||||
|
|
||||||
# TODO: All parameters that are global should be listed here.
|
# TODO: All parameters that are global should be listed here.
|
||||||
|
|
||||||
def print_credits():
|
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(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("Based on DeDRM Calibre plugin by Apprentice Harper, Apprentice Alf and others.")
|
||||||
print("See https://github.com/noDRM/DeDRM_tools for more information.")
|
print("See https://github.com/noDRM/DeDRM_tools for more information.")
|
||||||
|
@ -105,18 +108,28 @@ def print_credits():
|
||||||
def handle_single_argument(arg, next):
|
def handle_single_argument(arg, next):
|
||||||
used_up = 0
|
used_up = 0
|
||||||
global _additional_params
|
global _additional_params
|
||||||
|
global config_file_path
|
||||||
|
|
||||||
if arg in ["--username", "--password"]:
|
if arg in ["--username", "--password", "--output", "--outputdir"]:
|
||||||
used_up = 1
|
used_up = 1
|
||||||
_additional_params.append(arg)
|
_additional_params.append(arg)
|
||||||
if next is None:
|
if next is None or len(next) == 0:
|
||||||
print_err_header()
|
print_err_header()
|
||||||
print("Missing parameter for argument " + arg, file=sys.stderr)
|
print("Missing parameter for argument " + arg, file=sys.stderr)
|
||||||
sys.exit(1)
|
sys.exit(1)
|
||||||
else:
|
else:
|
||||||
_additional_params.append(next[0])
|
_additional_params.append(next[0])
|
||||||
|
|
||||||
elif arg in ["--help", "--credits", "--verbose", "--quiet", "--extract"]:
|
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)
|
||||||
|
|
||||||
|
config_file_path = next[0]
|
||||||
|
used_up = 1
|
||||||
|
|
||||||
|
elif arg in ["--help", "--credits", "--verbose", "--quiet", "--extract", "--import", "--overwrite", "--force"]:
|
||||||
_additional_params.append(arg)
|
_additional_params.append(arg)
|
||||||
|
|
||||||
|
|
||||||
|
@ -143,12 +156,28 @@ def handle_data(data):
|
||||||
def execute_action(action, filenames, params):
|
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)
|
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
|
from standalone.passhash import perform_action
|
||||||
perform_action(params, filenames)
|
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:
|
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):
|
def main(argv):
|
||||||
|
@ -236,7 +265,7 @@ def main(argv):
|
||||||
# This function gets told what to do and gets additional data (filenames).
|
# This function gets told what to do and gets additional data (filenames).
|
||||||
# It also receives additional parameters.
|
# It also receives additional parameters.
|
||||||
# The rest of the code will be in different Python files.
|
# 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)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -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')
|
isosx = sys.platform.startswith('darwin')
|
||||||
|
|
||||||
def print_passhash_help():
|
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(PLUGIN_NAME + " v" + PLUGIN_VERSION + " - Calibre DRM removal plugin by noDRM")
|
||||||
print()
|
print()
|
||||||
print("passhash: Manage Adobe PassHashes")
|
print("passhash: Manage Adobe PassHashes")
|
||||||
print()
|
print()
|
||||||
print_std_usage("passhash", "[ -u username -p password | -e ]")
|
print_std_usage("passhash", "[ -u username -p password | -b base64str ] [ -i ] ")
|
||||||
|
|
||||||
print()
|
print()
|
||||||
print("Options: ")
|
print("Options: ")
|
||||||
print_opt("u", "username", "Generate a PassHash with the given username")
|
print_opt("u", "username", "Generate a PassHash with the given username")
|
||||||
print_opt("p", "password", "Generate a PassHash with the given username")
|
print_opt("p", "password", "Generate a PassHash with the given password")
|
||||||
print_opt("e", "extract", "Extract PassHashes found on this machine")
|
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):
|
def perform_action(params, files):
|
||||||
user = None
|
user = None
|
||||||
|
@ -40,6 +41,7 @@ def perform_action(params, files):
|
||||||
return 0
|
return 0
|
||||||
|
|
||||||
extract = False
|
extract = False
|
||||||
|
import_to_json = True
|
||||||
|
|
||||||
while len(params) > 0:
|
while len(params) > 0:
|
||||||
p = params.pop(0)
|
p = params.pop(0)
|
||||||
|
@ -52,8 +54,10 @@ def perform_action(params, files):
|
||||||
elif p == "--help":
|
elif p == "--help":
|
||||||
print_passhash_help()
|
print_passhash_help()
|
||||||
return 0
|
return 0
|
||||||
|
elif p == "--import":
|
||||||
|
import_to_json = True
|
||||||
|
|
||||||
if not extract:
|
if not extract and not import_to_json:
|
||||||
if user is None:
|
if user is None:
|
||||||
print("Missing parameter: --username", file=sys.stderr)
|
print("Missing parameter: --username", file=sys.stderr)
|
||||||
if pwd is None:
|
if pwd is None:
|
||||||
|
@ -61,12 +65,23 @@ def perform_action(params, files):
|
||||||
if user is None or pwd is None:
|
if user is None or pwd is None:
|
||||||
return 1
|
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:
|
if user is not None and pwd is not None:
|
||||||
from ignoblekeyGenPassHash import generate_key
|
from ignoblekeyGenPassHash import generate_key
|
||||||
key = generate_key(user, pwd)
|
key = generate_key(user, pwd)
|
||||||
|
if import_to_json:
|
||||||
|
# TODO: Import the key to the JSON
|
||||||
|
pass
|
||||||
|
|
||||||
print(key.decode("utf-8"))
|
print(key.decode("utf-8"))
|
||||||
|
|
||||||
if extract:
|
if extract or import_to_json:
|
||||||
if not iswindows and not isosx:
|
if not iswindows and not isosx:
|
||||||
print("Extracting PassHash keys not supported on Linux.", file=sys.stderr)
|
print("Extracting PassHash keys not supported on Linux.", file=sys.stderr)
|
||||||
return 1
|
return 1
|
||||||
|
@ -92,6 +107,11 @@ def perform_action(params, files):
|
||||||
|
|
||||||
# Print all found keys
|
# Print all found keys
|
||||||
for k in newkeys:
|
for k in newkeys:
|
||||||
|
if import_to_json:
|
||||||
|
# TODO: Add keys to json
|
||||||
|
pass
|
||||||
|
|
||||||
|
if extract:
|
||||||
print(k)
|
print(k)
|
||||||
|
|
||||||
|
|
||||||
|
@ -99,4 +119,4 @@ def perform_action(params, files):
|
||||||
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
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)
|
|
@ -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)
|
Loading…
Reference in New Issue