diff --git a/.gitignore b/.gitignore index e2f5dd2..f4d6c00 100644 --- a/.gitignore +++ b/.gitignore @@ -1 +1,3 @@ -result \ No newline at end of file +result +.vscode +todo.md \ No newline at end of file diff --git a/demo.png b/demo.png deleted file mode 100644 index 217e924..0000000 Binary files a/demo.png and /dev/null differ diff --git a/flake.lock b/flake.lock index 52ed2b5..39dfaa9 100644 --- a/flake.lock +++ b/flake.lock @@ -1,32 +1,45 @@ { "nodes": { - "benpkgs": { - "inputs": { - "nixpkgs": [ - "nixpkgs" - ] - }, + "base64-src": { + "flake": false, "locked": { - "lastModified": 1638237664, - "narHash": "sha256-9SkZPl1EZ1ezWZoMdhYOzPm2HK0Ca5hkfphjlPNk/FY=", - "owner": "BentonEdmondson", - "repo": "benpkgs", - "rev": "da71a3a488de3ae7ab8daff2c2a9354d2f0464bf", - "type": "github" + "lastModified": 1468170709, + "narHash": "sha256-dt6i1j0rqH7lA+2XXp9KTEhj2GvYueyGrHh9VXBEsbw=", + "ref": "master", + "rev": "7d5a89229a525452e37504976a73c35fbaf2fe4d", + "revCount": 1, + "type": "git", + "url": "https://gist.github.com/f0fd86b6c73063283afe550bc5d77594.git" }, "original": { - "owner": "BentonEdmondson", - "repo": "benpkgs", - "type": "github" + "type": "git", + "url": "https://gist.github.com/f0fd86b6c73063283afe550bc5d77594.git" + } + }, + "gourou-src": { + "flake": false, + "locked": { + "lastModified": 1650729079, + "narHash": "sha256-2ZnuO/fIjSzTK0srFzDAhGNh1hA4lZf3lK4VuFxWXmo=", + "ref": "master", + "rev": "7b6b1471fefb27e79e06e5d686cb8842c539cd0c", + "revCount": 79, + "type": "git", + "url": "git://soutade.fr/libgourou.git" + }, + "original": { + "ref": "master", + "type": "git", + "url": "git://soutade.fr/libgourou.git" } }, "nixpkgs": { "locked": { - "lastModified": 1638198142, - "narHash": "sha256-plU9b8r4St6q4U7VHtG9V7oF8k9fIpfXl/KDaZLuY9k=", + "lastModified": 1654230545, + "narHash": "sha256-8Vlwf0x8ow6pPOK2a04bT+pxIeRnM1+O0Xv9/CuDzRs=", "owner": "NixOS", "repo": "nixpkgs", - "rev": "8a308775674e178495767df90c419425474582a1", + "rev": "236cc2971ac72acd90f0ae3a797f9f83098b17ec", "type": "github" }, "original": { @@ -36,31 +49,47 @@ "type": "github" } }, - "rmdrm": { - "inputs": { - "nixpkgs": [ - "nixpkgs" - ] - }, + "pugixml-src": { + "flake": false, "locked": { - "lastModified": 1638240151, - "narHash": "sha256-2FHEb4xaskyLdS5M/eILue74Idplve9g8FRxg9D1Z/4=", - "owner": "BentonEdmondson", - "repo": "rmdrm", - "rev": "c60eaa0c4338be5ee6165f59f2a1816566aba8a0", + "lastModified": 1644379750, + "narHash": "sha256-FLemG9T17n6l7vgb01OmO22BK59jv5uozVHeUnILEEQ=", + "owner": "zeux", + "repo": "pugixml", + "rev": "314baf6605143f1e837209008f490e8559529e1c", "type": "github" }, "original": { - "owner": "BentonEdmondson", - "repo": "rmdrm", + "owner": "zeux", + "ref": "latest", + "repo": "pugixml", "type": "github" } }, "root": { "inputs": { - "benpkgs": "benpkgs", + "base64-src": "base64-src", + "gourou-src": "gourou-src", "nixpkgs": "nixpkgs", - "rmdrm": "rmdrm" + "pugixml-src": "pugixml-src", + "updfparser-src": "updfparser-src" + } + }, + "updfparser-src": { + "flake": false, + "locked": { + "lastModified": 1647424063, + "narHash": "sha256-9dvibKiUbbI4CrmuAaJzlpntT0XdLvdGeC2/WzjlA5U=", + "ref": "master", + "rev": "9d56c1d0b1ce81aae4c8db9d99a8b5d1f7967bcf", + "revCount": 25, + "type": "git", + "url": "git://soutade.fr/updfparser.git" + }, + "original": { + "ref": "master", + "type": "git", + "url": "git://soutade.fr/updfparser.git" } } }, diff --git a/flake.nix b/flake.nix index f499354..519aa3e 100644 --- a/flake.nix +++ b/flake.nix @@ -1,60 +1,150 @@ { inputs = { nixpkgs.url = "github:NixOS/nixpkgs/nixos-unstable"; - - rmdrm.url = "github:BentonEdmondson/rmdrm"; - rmdrm.inputs.nixpkgs.follows = "nixpkgs"; - - benpkgs.url = "github:BentonEdmondson/benpkgs"; - benpkgs.inputs.nixpkgs.follows = "nixpkgs"; + gourou-src = { + url = "git://soutade.fr/libgourou.git"; + type = "git"; + ref = "master"; + flake = false; + }; + updfparser-src = { + url = "git://soutade.fr/updfparser.git"; + type = "git"; + ref = "master"; + flake = false; + }; + base64-src = { + url = "git+https://gist.github.com/f0fd86b6c73063283afe550bc5d77594.git"; + flake = false; + }; + pugixml-src = { + url = "github:zeux/pugixml/latest"; + flake = false; + }; }; outputs = flakes: let - nixpkgs = flakes.nixpkgs.legacyPackages.x86_64-linux; - libgourou-utils = flakes.libgourou-utils.defaultPackage.x86_64-linux; - rmdrm = flakes.rmdrm.defaultPackage.x86_64-linux; - benpkgs = flakes.benpkgs.packages.x86_64-linux; - in { - defaultPackage.x86_64-linux = nixpkgs.python3Packages.buildPythonApplication rec { - pname = "knock"; - version = "1.1.0-alpha"; - src = ./.; - - nativeBuildInputs = [ nixpkgs.makeWrapper ]; - - buildInputs = [ - rmdrm - benpkgs.libgourou - nixpkgs.ffmpeg + self = flakes.self.packages.x86_64-linux; + nixpkgs = flakes.nixpkgs.legacyPackages.x86_64-linux.pkgsStatic; + gourou-src = flakes.gourou-src; + updfparser-src = flakes.updfparser-src; + base64-src = flakes.base64-src; + pugixml-src = flakes.pugixml-src; + cxx = "${nixpkgs.stdenv.cc}/bin/x86_64-unknown-linux-musl-g++"; + ar = "${nixpkgs.stdenv.cc.bintools.bintools_bin}/bin/x86_64-unknown-linux-musl-ar"; + obj-flags = "-O2 -static"; + in rec { + packages.x86_64-linux.libzip-static = nixpkgs.libzip.overrideAttrs (prev: { + cmakeFlags = (prev.cmakeFlags or []) ++ [ + "-DBUILD_SHARED_LIBS=OFF" + "-DBUILD_EXAMPLES=OFF" + "-DBUILD_DOC=OFF" + "-DBUILD_TOOLS=OFF" + "-DBUILD_REGRESS=OFF" ]; - - propagatedBuildInputs = [ - benpkgs.Audible - nixpkgs.python3Packages.python_magic - nixpkgs.python3Packages.xdg - nixpkgs.python3Packages.click - ]; - - format = "other"; - - installPhase = '' - mkdir -p $out/bin $out/${nixpkgs.python3.sitePackages} - cp lib/*.py $out/${nixpkgs.python3.sitePackages} - cp src/knock.py $out/bin/knock - wrapProgram $out/bin/knock --prefix PATH : ${nixpkgs.lib.makeBinPath buildInputs} - #''; - - meta = { - description = "A CLI tool to convert ACSM files to DRM-free EPUB/PDF files"; - homepage = "https://github.com/BentonEdmondson/knock"; - license = [ nixpkgs.lib.licenses.gpl3Only ]; - maintainers = [{ - name = "Benton Edmondson"; - email = "bentonedmondson@gmail.com"; - }]; - # potentially others, but I'm only listed those tested - platforms = [ "x86_64-linux" ]; - }; + outputs = ["out"]; + }); + packages.x86_64-linux.base64 = derivation { + name = "updfparser"; + system = "x86_64-linux"; + builder = "${nixpkgs.bash}/bin/bash"; + PATH = "${nixpkgs.coreutils}/bin"; + args = ["-c" '' + mkdir -p $out/include/base64 + cp ${base64-src}/Base64.h $out/include/base64/Base64.h + '']; }; + packages.x86_64-linux.updfparser = derivation { + name = "updfparser"; + system = "x86_64-linux"; + builder = "${nixpkgs.bash}/bin/bash"; + PATH = "${nixpkgs.coreutils}/bin"; + args = [ "-c" '' + ${cxx} \ + -c ${updfparser-src}/src/*.cpp \ + -I ${updfparser-src}/include \ + ${obj-flags} + mkdir -p $out/lib + ${ar} crs $out/lib/libupdfparser.a *.o + '' ]; + }; + packages.x86_64-linux.gourou = derivation { + name = "gourou"; + system = "x86_64-linux"; + builder = "${nixpkgs.bash}/bin/bash"; + PATH = "${nixpkgs.coreutils}/bin"; + args = [ "-c" '' + ${cxx} \ + -c \ + ${gourou-src}/src/*.cpp \ + ${pugixml-src}/src/pugixml.cpp \ + -I ${self.base64}/include \ + -I ${gourou-src}/include \ + -I ${pugixml-src}/src \ + -I ${updfparser-src}/include \ + ${obj-flags} + mkdir -p $out/lib $out/debug + ${ar} crs $out/lib/libgourou.a *.o + cp *.o $out/debug + '' ]; + }; + packages.x86_64-linux.utils-common = derivation { + name = "utils-common"; + system = "x86_64-linux"; + builder = "${nixpkgs.bash}/bin/bash"; + PATH = "${nixpkgs.coreutils}/bin"; + args = [ "-c" '' + ${cxx} \ + -c ${gourou-src}/utils/drmprocessorclientimpl.cpp \ + ${gourou-src}/utils/utils_common.cpp \ + -I ${gourou-src}/utils \ + -I ${gourou-src}/include \ + -I ${pugixml-src}/src \ + -I ${nixpkgs.openssl.dev}/include \ + -I ${nixpkgs.curl.dev}/include \ + -I ${nixpkgs.zlib.dev}/include \ + -I ${self.libzip-static}/include \ + ${obj-flags} + mkdir -p $out/lib + ${ar} crs $out/lib/libutils-common.a *.o + '' ]; + }; + packages.x86_64-linux.knock = derivation { + name = "knock"; + system = "x86_64-linux"; + builder = "${nixpkgs.bash}/bin/bash"; + PATH = "${nixpkgs.coreutils}/bin"; + args = [ "-c" '' + mkdir -p $out/bin + ${cxx} \ + -o $out/bin/knock \ + ${./src/knock.cpp} \ + -Wl,--as-needed -static \ + ${self.utils-common}/lib/libutils-common.a \ + ${self.gourou}/lib/libgourou.a \ + ${self.updfparser}/lib/libupdfparser.a \ + -Wl,--start-group \ + ${self.libzip-static}/lib/libzip.a \ + ${nixpkgs.libnghttp2}/lib/libnghttp2.a \ + ${nixpkgs.libidn2.out}/lib/libidn2.a \ + ${nixpkgs.libunistring}/lib/libunistring.a \ + ${nixpkgs.libssh2}/lib/libssh2.a \ + ${nixpkgs.zstd.out}/lib/libzstd.a \ + ${nixpkgs.zlib}/lib/libz.a \ + ${nixpkgs.openssl.out}/lib/libcrypto.a \ + ${nixpkgs.curl.out}/lib/libcurl.a \ + ${nixpkgs.openssl.out}/lib/libssl.a \ + -static-libgcc -static-libstdc++ \ + -Wl,--end-group \ + -I ${gourou-src}/utils \ + -I ${gourou-src}/include \ + -I ${pugixml-src}/src \ + -I ${nixpkgs.openssl.dev}/include \ + -I ${nixpkgs.curl.dev}/include \ + -I ${nixpkgs.zlib.dev}/include \ + -I ${self.libzip-static}/include + '' ]; + }; + defaultPackage.x86_64-linux = self.knock; }; } \ No newline at end of file diff --git a/lib/handle_aax.py b/lib/handle_aax.py deleted file mode 100644 index fe91e5f..0000000 --- a/lib/handle_aax.py +++ /dev/null @@ -1,51 +0,0 @@ -from xdg import xdg_config_home -from utils import open_fake_terminal, close_fake_terminal, run -import audible, click, sys - -def handle_aax(aax_path): - authcode_path = xdg_config_home().joinpath('knock', 'aax', 'authcode') - - # make the config dir if it doesn't exist - authcode_path.parent.mkdir(parents=True, exist_ok=True) - - m4b_path = aax_path.with_suffix('.m4b') - - if m4b_path.exists(): - click.echo(f"Error: {m4b_path} must be moved out of the way or deleted.", err=True) - sys.exit(1) - - if not authcode_path.exists(): - click.echo('This device does not have an Audible decryption key.') - - email = click.prompt("Enter your Audible account's email address") - password = click.prompt("Enter your Audible account's password", hide_input=True) - locale = click.prompt("Enter your locale (e.g. 'us', 'ca', 'jp', etc)") - - open_fake_terminal(f'audible.auth.Authenticator.from_login("{email}", "{locale}").get_activation_bytes()') - - try: - authcode = audible.auth.Authenticator.from_login( - username=email, - password=password, - locale=locale - ).get_activation_bytes() - authcode_path.write_text(authcode) - except Exception as error: - click.echo(error, err=True) - close_fake_terminal(1) - - close_fake_terminal(0) - - click.echo('Decrypting the file...') - - authcode = authcode_path.read_text() - - run([ - 'ffmpeg', - '-activation_bytes', authcode, - '-i', str(aax_path), - '-c', 'copy', str(m4b_path), - '-loglevel', 'error' - ]) - - click.secho(f'DRM-free M4B file created:\n{m4b_path}', fg='green') diff --git a/lib/handle_acsm.py b/lib/handle_acsm.py deleted file mode 100644 index defc68b..0000000 --- a/lib/handle_acsm.py +++ /dev/null @@ -1,77 +0,0 @@ -from xdg import xdg_config_home -import click, sys, shutil, subprocess, magic -from utils import run - -def handle_acsm(acsm_path): - drm_path = acsm_path.with_suffix('.drm') - adobe_dir = xdg_config_home().joinpath('knock', 'acsm') - adobe_dir.mkdir(parents=True, exist_ok=True) - - if drm_path.exists(): - click.echo(f"Error: {drm_path} must be moved out of the way or deleted.", err=True) - sys.exit(1) - - if ( - not adobe_dir.joinpath('device.xml').exists() - or not adobe_dir.joinpath('activation.xml').exists() - or not adobe_dir.joinpath('devicesalt').exists() - ): - shutil.rmtree(str(adobe_dir)) - click.echo('This device is not registered with Adobe.') - email = click.prompt("Enter your Adobe account's email address") - password = click.prompt("Enter your Adobe account's password", hide_input=True) - click.echo('Registering this device with Adobe...') - - run( - [ - 'adept_activate', - '-u', email, - '-O', str(adobe_dir) - ], - stdin=password+'\n', - cleanser=lambda:shutil.rmtree(str(adobe_dir)) - ) - - click.echo('Downloading the book from Adobe...') - - run([ - 'acsmdownloader', - '-d', str(adobe_dir.joinpath('device.xml')), - '-a', str(adobe_dir.joinpath('activation.xml')), - '-k', str(adobe_dir.joinpath('devicesalt')), - '-o', str(drm_path), - '-f', str(acsm_path) - ]) - - drm_path_type = magic.from_file(str(drm_path), mime=True) - if drm_path_type == 'application/epub+zip': - file_type = 'epub' - elif drm_path_type == 'application/pdf': - file_type = 'pdf' - else: - click.echo(f'Error: Received file of media type {drm_path_type} from Adobe\' servers.', err=True) - click.echo('Only the following ACSM conversions are currently supported:', err=True) - click.echo(' * ACSM -> EPUB', err=True) - click.echo(' * ACSM -> PDF', err=True) - click.echo('Please open a feature request at:', err=True) - click.echo(f' https://github.com/BentonEdmondson/knock/issues/new?title=Support%20{drm_path_type}%20Files&labels=enhancement', err=True) - sys.exit(1) - - output_path = acsm_path.with_suffix('.' + file_type) - if output_path.exists(): - drm_path.unlink() - click.echo(f"Error: {output_path} must be moved out of the way or deleted.", err=True) - sys.exit(1) - - click.echo('Decrypting the file...') - - run([ - 'rmdrm-' + file_type, - str(adobe_dir.joinpath('activation.xml')), - str(drm_path), - str(output_path) - ]) - - drm_path.unlink() - - click.secho(f'DRM-free {file_type.upper()} file created:\n{output_path}', fg='green') \ No newline at end of file diff --git a/lib/utils.py b/lib/utils.py deleted file mode 100644 index 9551d43..0000000 --- a/lib/utils.py +++ /dev/null @@ -1,45 +0,0 @@ -import click, subprocess, sys - -# run a command and display output in a styled terminal -# cleanser is called if the command returns a >0 exit code -def run(command: [str], stdin: str = '', cleanser = lambda: None) -> int: - - open_fake_terminal(' '.join(command)) - - result = subprocess.run( - command, - stderr=subprocess.STDOUT, - input=stdin.encode(), - check=False # don't throw Python error if returncode isn't 0 - ) - - close_fake_terminal(result.returncode, cleanser) - - return result.returncode - - -def open_fake_terminal(command: str): - click.secho('', fg='white', bg='black', bold=True, reset=False) - - # show command - click.echo(f'knock> {command}') - - # remove bold - click.secho('', fg='white', bg='black', bold=False, reset=False) - - -def close_fake_terminal(exit_code: int, cleanser = lambda: None): - click.secho(f'\nknock[{exit_code}]>', bold=True) - - # newline - click.echo('') - - if exit_code > 0: - cleanser() - click.echo(f'Error: Command returned error code {exit_code}.', err=True) - sys.exit(1) - -def verify_absence_of(file_path): - if m4b_path.exists(): - click.echo(f"Error: {file_path} must be moved out of the way or deleted.", err=True) - sys.exit(1) \ No newline at end of file diff --git a/readme.md b/readme.md index e1a2073..8f1b8b1 100644 --- a/readme.md +++ b/readme.md @@ -1,54 +1,20 @@ # Knock -Perform the following conversions with one command: -* ACSM → EPUB -* ACSM → PDF -* (Soon: AAX → M4B) - -![CLI demonstration](demo.png) +Convert ACSM files to PDF/EPUBs with one command on Linux ([and MacOS very soon](https://github.com/BentonEdmondson/knock/issues/58)). *This software does not utilize Adobe Digital Editions nor Wine. It is completely free and open-source software written natively for Linux.* -## Setup and Installation +## Installation -* For NixOS users, include this flake in your system `flake.nix`. Then run `knock ~/path/to/my-book.acsm` to use. - ```nix - { - inputs.knock.url = "github:BentonEdmondson/knock"; - outputs = { self, knock }: { /* knock.defaultPackage.x86_64-linux is the package */ }; - } - ``` -* For non-NixOS, use the latest [release](https://github.com/BentonEdmondson/knock/releases). It is large because it includes all dependencies, allowing it to run on any system with an x86_64 Linux kernel. It was built using [`nix bundle`](https://nixos.org/manual/nix/unstable/command-ref/new-cli/nix3-bundle.html). Use it by doing the following: - 1. Download `knock-version-x86_64-linux` and open a terminal - 1. Navigate to the folder within which `knock-version-x86_64-linux` resides (e.g. `cd ~/Downloads`) - 1. Run `mv knock-version-x86_64-linux knock` to rename it to `knock` - 1. Run `chmod +x knock` to make it executable - 1. Run `./knock ~/path/to/my-book.acsm` to convert the ebook - - If you receive an error that says something like `./nix/store/...: not found` or `./nix/store/...: No such file or directory` then you might not have user namespaces enabled. Try running the following to fix it: - - ``` - echo "kernel.unprivileged_userns_clone=1" >> /etc/sysctl.conf - sudo reboot - ``` - If you receive an error that says something like `E_AUTH_FAILED http://adeactivate.adobe.com/adept/SignInDirect xxxx@xxxxxxxx.com CUS05051` then you might have over (at least) 10 digit password for Adobe. Try changing it to 10 digit password and try the command again. - - 1. Optionally move the executable to `~/bin` (for your user) or `/usr/local/bin/` (for all users) to allow it to run from anywhere (might not work on some distributions) - -## Recommended Workflows - -Before buying your ebook/audiobook, check if it is available for free on [Project Gutenberg](https://gutenberg.org/) (ebooks) or [LibriVox](https://librivox.org/) (audiobooks). - -If you're looking for an ebook reader or audiobook player, I recommend [Foliate](https://johnfactotum.github.io/foliate/) for the former and [Cozy](https://cozy.sh/) for the latter. +* Download the latest [release](https://github.com/BentonEdmondson/knock/releases). Make sure it is the correct version for your architecture (run `uname -m` to check). +* Rename the binary and make it executable. +* Run `knock /path/to/book.acsm` to perform the conversion. ## Verified Book Sources -Knock should work on any ACSM file, but it has been specifically verified to work on ACSM files from the following: +Knock should work on any ACSM file, but it has been specifically verified to work on ACSM files purchased [eBooks.com](https://www.ebooks.com/en-us/) and [Kobo](https://www.kobo.com/us/en), among others. -* [eBooks.com](https://www.ebooks.com/en-us/) -* [Rakuten Kobo](https://www.kobo.com/us/en) -* [Google Books](https://books.google.com/) -* [Hugendubel.de](https://www.hugendubel.de/de/) (German) +Before buying your ebook, check if it is available for free on [Project Gutenberg](https://gutenberg.org/). ## The Name @@ -65,13 +31,26 @@ The name comes from the [D&D 5e spell](https://roll20.net/compendium/dnd5e/Knock ## Dependencies -* [`libgourou`](http://indefero.soutade.fr/p/libgourou/) for using the ACSM file to download the corresponding encrypted EPUB/PDF file from Adobe's servers -* [`rmdrm`](https://github.com/BentonEdmondson/rmdrm/) for decrypting the Adobe ADEPT-encrypted EPUB/PDF files -* [`Audible`](https://github.com/mkb79/Audible) for fetching the Audible decryption key used to decrypt AAX files -* [`ffmpeg`](https://www.ffmpeg.org/) for converting AAX files to M4B files using the Audible decryption key +There are no userspace runtime dependencies. -These are already included in all releases and in the Nix flake of course. +## Building & Contributing + +Install [Nix](https://github.com/NixOS/nix) if you don't have it. [Enable flakes](https://nixos.wiki/wiki/Flakes) if you haven't. Run + +``` +nix build +``` + +to build and + +``` +nix flake update +``` + +to update libraries. + +Test books can be found [here](https://www.adobe.com/solutions/ebook/digital-editions/sample-ebook-library.html). ## License -This software is licensed under GPLv3. +This software is licensed under GPLv3. The linked libraries have various licenses. diff --git a/src/knock.cpp b/src/knock.cpp new file mode 100644 index 0000000..5017c9e --- /dev/null +++ b/src/knock.cpp @@ -0,0 +1,104 @@ +#include +#include "drmprocessorclientimpl.h" +#include "libgourou_common.h" +#include "libgourou.h" + +std::string get_data_dir(); +void verify_absence(std::string file); +void verify_presence(std::string file); + +int main(int argc, char** argv) try { + + if (argc != 2) { + throw std::invalid_argument("the ACSM file must be passed as an argument"); + } + + const std::string acsm_file = argv[1]; + verify_presence(acsm_file); + const std::string acsm_stem = acsm_file.substr(0, acsm_file.find_last_of(".")); + const std::string drm_file = acsm_stem + ".drm"; + const std::string out_file = acsm_stem + ".out"; + verify_absence(drm_file); + verify_absence(out_file); + const std::string knock_data = get_data_dir(); + + DRMProcessorClientImpl client; + gourou::DRMProcessor* processor = gourou::DRMProcessor::createDRMProcessor( + &client, + false, // don't "always generate a new device" (default) + knock_data + ); + + processor->signIn("anonymous", ""); + processor->activateDevice(); + + std::cout << "downloading the file from Adobe..." << std::endl; + gourou::FulfillmentItem* item = processor->fulfill(acsm_file); + gourou::DRMProcessor::ITEM_TYPE type = processor->download(item, drm_file); + + std::cout << "removing DRM from the file..." << std::endl; + std::string ext_file; + std::string file_type; + switch (type) { + case gourou::DRMProcessor::ITEM_TYPE::PDF: { + // for pdfs the function moves the pdf while removing drm + processor->removeDRM(drm_file, out_file, type, nullptr, 0); + std::filesystem::remove(drm_file); + ext_file = acsm_stem + ".pdf"; + file_type = "PDF"; + break; + } + case gourou::DRMProcessor::ITEM_TYPE::EPUB: { + // for epubs the drm is removed in-place so in == out + processor->removeDRM(drm_file, drm_file, type, nullptr, 0); + std::filesystem::rename(drm_file, out_file); + ext_file = acsm_stem + ".epub"; + file_type = "EPUB"; + break; + } + } + + if (std::filesystem::exists(ext_file)) { + std::cerr + << "warning: failed to update file extension; " + ext_file + " already exists" + << std::endl; + ext_file = out_file; + } else { + std::filesystem::rename(out_file, ext_file); + } + + std::cout << "DRM-free " + file_type + " file generated at " + ext_file << std::endl; + + return 0; + +} catch (const gourou::Exception& e) { + std::cerr << "error:\n" << e.what(); + return EXIT_FAILURE; +} catch (const std::exception& e) { + std::cerr << "error: " << e.what() << std::endl; + return EXIT_FAILURE; +} + +std::string get_data_dir() { + char* xdg_data_home = std::getenv("XDG_DATA_HOME"); + std::string knock_data; + if (xdg_data_home != nullptr) { + knock_data = xdg_data_home; + } else { + knock_data = std::string(std::getenv("HOME")) + "/.local/share"; + } + knock_data += "/knock/acsm"; + return knock_data; +} + +void verify_absence(std::string file) { + if (std::filesystem::exists(file)) { + throw std::runtime_error("file " + file + " must be moved out of the way or deleted"); + } +} + +void verify_presence(std::string file) { + if (!std::filesystem::exists(file)) { + throw std::runtime_error("file " + file + " does not exist"); + } +} \ No newline at end of file diff --git a/src/knock.py b/src/knock.py deleted file mode 100755 index 274149a..0000000 --- a/src/knock.py +++ /dev/null @@ -1,46 +0,0 @@ -#!/usr/bin/env python3 - -import subprocess, shutil, click -from pathlib import Path -from getpass import getpass -from xdg import xdg_config_home - -from handle_acsm import handle_acsm -from handle_aax import handle_aax - -@click.command() -@click.argument( - "path", - type=click.Path( - exists=True, - file_okay=True, - dir_okay=False, - readable=True, - resolve_path=True - ) -) -def main(path): - path = Path(path) - - # make the config dir if it doesn't exist - xdg_config_home().joinpath('knock').mkdir(parents=True, exist_ok=True) - - path_type = path.suffix[1:].upper() - - if path_type == 'ACSM': - click.echo('Received an ACSM (Adobe) file...') - handle_acsm(path) - #elif path_type == 'AAX': - # click.echo('Received an AAX (Audible) file...') - # handle_aax(path) - else: - click.echo(f'Error: Files of type {path_type} are not supported.\n', err=True) - click.echo('Only the following file types are currently supported:', err=True) - click.echo(' * ACSM (Adobe)\n', err=True) - #click.echo(' * AAX (Audible)\n', err=True) - click.echo('Please open a feature request at:', err=True) - click.echo(f' https://github.com/BentonEdmondson/knock/issues/new?title=Support%20{path_type}%20Files&labels=enhancement', err=True) - sys.exit(1) - -if __name__ == "__main__": - main() \ No newline at end of file