]> xmof Git - DeDRM.git/commitdiff
More work on standalone version, fix plugin
authorNoDRM <no_drm123@protonmail.com>
Sat, 1 Jan 2022 13:09:56 +0000 (14:09 +0100)
committerNoDRM <no_drm123@protonmail.com>
Sat, 1 Jan 2022 13:11:39 +0000 (14:11 +0100)
CHANGELOG.md
DeDRM_plugin/DeDRM_Help.htm
DeDRM_plugin/__calibre_compat_code.py
DeDRM_plugin/__init__.py
DeDRM_plugin/__version.py [new file with mode: 0644]
DeDRM_plugin/config.py
DeDRM_plugin/prefs.py
DeDRM_plugin/standalone/__init__.py
DeDRM_plugin/standalone/jsonconfig.py [new file with mode: 0644]
DeDRM_plugin/standalone/passhash.py
DeDRM_plugin/standalone/remove_drm.py [new file with mode: 0644]

index 2f3a65d59c5a0e4c409d35ec49ea783fb7e50d5c..a800725024fbda1cee14fc65270dd91e993932ae 100644 (file)
@@ -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: 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.
+- Some Python3 bugfixes for Amazon books (merged #10 by ableeker).
\ No newline at end of file
index c5a92ac6eb7ae74d0673cfdf1c60525e0a5bf01f..c94d074baf5f107f6ff4620173839a700ae35321 100644 (file)
@@ -17,7 +17,7 @@ p {margin-top: 0}
 
 <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>
 
index 4896dd6761b6232842a10716b55369b1a5c30278..a535a424f5840cd98150132b2c2a8ac7a08a6b59 100644 (file)
@@ -2,13 +2,19 @@
 #@@CALIBRE_COMPAT_CODE_START@@
 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:
     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:
 if "calibre" in sys.modules and sys.version_info[0] == 2:
     from calibre.utils.config import config_dir
     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"))
+
+# Explicitly set the package identifier so we are allowed to import stuff ...
+#__package__ = "DeDRM_plugin"
+
 #@@CALIBRE_COMPAT_CODE_END@@
index ac11d0fc5956c1a8de48eeb9b7e7dd5cf1be99ac..651647e8c12234ac518df7829f475bb4f602fa62 100644 (file)
@@ -8,7 +8,6 @@ from __future__ import print_function
 # Copyright © 2021 NoDRM
 
 __license__   = 'GPL v3'
-__version__ = '10.0.2'
 __docformat__ = 'restructuredtext en'
 
 
@@ -88,12 +87,6 @@ __docformat__ = 'restructuredtext en'
 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 sys, os
 import time
@@ -101,6 +94,8 @@ import traceback
 
 #@@CALIBRE_COMPAT_CODE@@
 
+import __version
+
 class DeDRMError(Exception):
     pass
 
@@ -147,6 +142,10 @@ class SafeUnbuffered:
     def __getattr__(self, 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):
     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."
diff --git a/DeDRM_plugin/__version.py b/DeDRM_plugin/__version.py
new file mode 100644 (file)
index 0000000..db5eaa1
--- /dev/null
@@ -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'
\ No newline at end of file
index 79e7c4d5f17dc66d9bc0f97caddf0598b905d0bd..fa92fa268849551e30dde98f784b7d32c81e89f4 100755 (executable)
@@ -28,7 +28,7 @@ from calibre.constants import iswindows, isosx
 
 
 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
 
 import prefs
index 4db9618e85add05cf488aa9354c1778dc88b4425..0ae39434c030c8d92fc27f69830a52ed16bc640b 100755 (executable)
@@ -12,12 +12,20 @@ import traceback
 #@@CALIBRE_COMPAT_CODE@@
 
 
-from calibre.utils.config import JSONConfig
+try: 
+    from calibre.utils.config import JSONConfig
+except:
+    from standalone.jsonconfig import JSONConfig
+
 from __init__ import PLUGIN_NAME
 
 class DeDRM_Prefs():
-    def __init__(self):
-        JSON_PATH = os.path.join("plugins", PLUGIN_NAME.strip().lower().replace(' ', '_') + '.json')
+    def __init__(self, json_path=None):
+        if json_path is None:
+            JSON_PATH = os.path.join("plugins", PLUGIN_NAME.strip().lower().replace(' ', '_') + '.json')
+        else:
+            JSON_PATH = json_path
+
         self.dedrmprefs = JSONConfig(JSON_PATH)
 
         self.dedrmprefs.defaults['configured'] = False
index fe74bb31137ecb6dfaa9585d9b4d5a326fed17ca..e5149bc2d0af743eef346fff94f9464d9dafcafa 100644 (file)
@@ -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)
+
+        config_file_path = next[0]
+        used_up = 1
 
-    elif arg in ["--help", "--credits", "--verbose", "--quiet", "--extract"]:
+    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)
         
 
     
diff --git a/DeDRM_plugin/standalone/jsonconfig.py b/DeDRM_plugin/standalone/jsonconfig.py
new file mode 100644 (file)
index 0000000..a4149bf
--- /dev/null
@@ -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()
\ No newline at end of file
index 215f283146f30678eddd32e10884969f732e71d6..f7bf565fa5e91596825e285686545fb04c330823 100644 (file)
@@ -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!")
\ No newline at end of file
+    print("This code is not intended to be executed directly!", file=sys.stderr)
\ No newline at end of file
diff --git a/DeDRM_plugin/standalone/remove_drm.py b/DeDRM_plugin/standalone/remove_drm.py
new file mode 100644 (file)
index 0000000..5ab5f33
--- /dev/null
@@ -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)
\ No newline at end of file