Compare commits
74 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
464788a3f1 | ||
|
|
218539f131 | ||
|
|
cdab22e59c | ||
|
|
4868a7460e | ||
|
|
0859f197fc | ||
|
|
da85d4ffac | ||
|
|
6fd5535072 | ||
|
|
885ef5e890 | ||
|
|
22d2b37e04 | ||
|
|
837562db66 | ||
|
|
3dcf3a5483 | ||
|
|
f7b4efc3e1 | ||
|
|
2fbf2c1c5f | ||
|
|
3166273622 | ||
|
|
ea916d85fc | ||
|
|
2bb73584f2 | ||
|
|
8495ebe36d | ||
|
|
92bf51bc8f | ||
|
|
e15ff385ca | ||
|
|
d48f4b86cf | ||
|
|
2ef5c59ebe | ||
|
|
d2995539f0 | ||
|
|
ef3c7f261c | ||
|
|
778ce4782e | ||
|
|
69ac9b7399 | ||
|
|
423dec0309 | ||
|
|
582479c1f4 | ||
|
|
c1ece2f288 | ||
|
|
f5dd758b1b | ||
|
|
a107742191 | ||
|
|
ce8538a2ca | ||
|
|
2cf5960511 | ||
|
|
ef687eb057 | ||
|
|
7d5352fdf3 | ||
|
|
795f413ecb | ||
|
|
b35f777580 | ||
|
|
0895aeb323 | ||
|
|
eddbefcf91 | ||
|
|
0955713cd6 | ||
|
|
4e26b9d4e7 | ||
|
|
8c08c67aa8 | ||
|
|
90335bb925 | ||
|
|
a10d9a617f | ||
|
|
7edebeef0d | ||
|
|
e35b37c4f4 | ||
|
|
1fd972ee17 | ||
|
|
616548a9a8 | ||
|
|
e4c1a09d45 | ||
|
|
89cf29cb78 | ||
|
|
c74f4b20d3 | ||
|
|
ae703e523c | ||
|
|
48dac14218 | ||
|
|
798a7f9c8e | ||
|
|
43f80b767a | ||
|
|
e07bb6523b | ||
|
|
5d8dc595ce | ||
|
|
fc6f830088 | ||
|
|
ff51ee8227 | ||
|
|
952b7fa7c0 | ||
|
|
0e9e3cf7ca | ||
|
|
57702b7d17 | ||
|
|
666af55404 | ||
|
|
60f1865b53 | ||
|
|
488cc540cd | ||
|
|
5bb6b58bc1 | ||
|
|
3f591ce66f | ||
|
|
8bd53cd998 | ||
|
|
4bd89fa4aa | ||
|
|
b71ed3887e | ||
|
|
d152586edc | ||
|
|
aca8043174 | ||
|
|
8165ad3ebb | ||
|
|
3d0aa17b2e | ||
|
|
b17b913839 |
17
.gitattributes
vendored
17
.gitattributes
vendored
@@ -1,17 +0,0 @@
|
|||||||
# Auto detect text files and perform LF normalization
|
|
||||||
* text=auto
|
|
||||||
|
|
||||||
# Custom for Visual Studio
|
|
||||||
*.cs diff=csharp
|
|
||||||
|
|
||||||
# Standard to msysgit
|
|
||||||
*.doc diff=astextplain
|
|
||||||
*.DOC diff=astextplain
|
|
||||||
*.docx diff=astextplain
|
|
||||||
*.DOCX diff=astextplain
|
|
||||||
*.dot diff=astextplain
|
|
||||||
*.DOT diff=astextplain
|
|
||||||
*.pdf diff=astextplain
|
|
||||||
*.PDF diff=astextplain
|
|
||||||
*.rtf diff=astextplain
|
|
||||||
*.RTF diff=astextplain
|
|
||||||
35
.github/workflows/Python_tests.yml
vendored
Normal file
35
.github/workflows/Python_tests.yml
vendored
Normal file
@@ -0,0 +1,35 @@
|
|||||||
|
name: Python_tests
|
||||||
|
on: [push, pull_request]
|
||||||
|
jobs:
|
||||||
|
Python_tests:
|
||||||
|
runs-on: ${{ matrix.os }}
|
||||||
|
strategy:
|
||||||
|
fail-fast: false
|
||||||
|
matrix:
|
||||||
|
os: [macos-latest]
|
||||||
|
python-version: [2.7] # , 3.8]
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v2
|
||||||
|
- name: Set up Python ${{ matrix.python-version }}
|
||||||
|
uses: actions/setup-python@v1
|
||||||
|
with:
|
||||||
|
python-version: ${{ matrix.python-version }}
|
||||||
|
- name: Install dependencies
|
||||||
|
run: |
|
||||||
|
python -m pip install --upgrade pip
|
||||||
|
pip install flake8 pytest # -r requirements.txt
|
||||||
|
#- name: Check formatting with black
|
||||||
|
# if: matrix.python-version == '3.8'
|
||||||
|
# run: |
|
||||||
|
# pip install black
|
||||||
|
# black --check .
|
||||||
|
- name: Lint with flake8
|
||||||
|
run: |
|
||||||
|
# stop the build if there are Python syntax errors or undefined names
|
||||||
|
flake8 . --builtins=_,I --count --select=E9,F63,F7,F82 --show-source --statistics
|
||||||
|
# exit-zero treats all errors as warnings. The GitHub editor is 127 chars wide
|
||||||
|
flake8 . --builtins=_,I --count --exit-zero --max-complexity=10 --max-line-length=127 --statistics
|
||||||
|
#- name: Test with pytest
|
||||||
|
# run: pytest
|
||||||
|
#- name: Run doctests with pytest
|
||||||
|
# run: pytest --doctest-modules
|
||||||
96
.gitignore
vendored
96
.gitignore
vendored
@@ -1,96 +0,0 @@
|
|||||||
# Byte-compiled / optimized / DLL files
|
|
||||||
__pycache__/
|
|
||||||
*.pyc
|
|
||||||
|
|
||||||
# Distribution / packaging
|
|
||||||
.Python
|
|
||||||
env/
|
|
||||||
build/
|
|
||||||
develop-eggs/
|
|
||||||
dist/
|
|
||||||
downloads/
|
|
||||||
eggs/
|
|
||||||
lib64/
|
|
||||||
parts/
|
|
||||||
sdist/
|
|
||||||
var/
|
|
||||||
*.egg-info/
|
|
||||||
.installed.cfg
|
|
||||||
*.egg
|
|
||||||
|
|
||||||
# PyInstaller
|
|
||||||
# Usually these files are written by a python script from a template
|
|
||||||
# before PyInstaller builds the exe, so as to inject date/other infos into it.
|
|
||||||
*.manifest
|
|
||||||
*.spec
|
|
||||||
|
|
||||||
# Installer logs
|
|
||||||
pip-log.txt
|
|
||||||
pip-delete-this-directory.txt
|
|
||||||
|
|
||||||
# Unit test / coverage reports
|
|
||||||
htmlcov/
|
|
||||||
.tox/
|
|
||||||
.coverage
|
|
||||||
.cache
|
|
||||||
nosetests.xml
|
|
||||||
coverage.xml
|
|
||||||
|
|
||||||
# Translations
|
|
||||||
*.pot
|
|
||||||
|
|
||||||
# Django stuff:
|
|
||||||
*.log
|
|
||||||
|
|
||||||
# Sphinx documentation
|
|
||||||
docs/_build/
|
|
||||||
|
|
||||||
# PyBuilder
|
|
||||||
target/
|
|
||||||
|
|
||||||
# =========================
|
|
||||||
# Operating System Files
|
|
||||||
# =========================
|
|
||||||
|
|
||||||
# OSX
|
|
||||||
# =========================
|
|
||||||
|
|
||||||
.DS_Store
|
|
||||||
.AppleDouble
|
|
||||||
.LSOverride
|
|
||||||
|
|
||||||
# Thumbnails
|
|
||||||
._*
|
|
||||||
|
|
||||||
# Files that might appear on external disk
|
|
||||||
.Spotlight-V100
|
|
||||||
.Trashes
|
|
||||||
|
|
||||||
# Directories potentially created on remote AFP share
|
|
||||||
.AppleDB
|
|
||||||
.AppleDesktop
|
|
||||||
Network Trash Folder
|
|
||||||
Temporary Items
|
|
||||||
.apdisk
|
|
||||||
|
|
||||||
# Windows
|
|
||||||
# =========================
|
|
||||||
|
|
||||||
# Windows image file caches
|
|
||||||
Thumbs.db
|
|
||||||
ehthumbs.db
|
|
||||||
|
|
||||||
# Folder config file
|
|
||||||
Desktop.ini
|
|
||||||
|
|
||||||
# Recycle Bin used on file shares
|
|
||||||
$RECYCLE.BIN/
|
|
||||||
|
|
||||||
# Windows Installer files
|
|
||||||
*.cab
|
|
||||||
*.msi
|
|
||||||
*.msm
|
|
||||||
*.msp
|
|
||||||
|
|
||||||
# Windows shortcuts
|
|
||||||
*.lnk
|
|
||||||
@@ -27,7 +27,7 @@ platforms.
|
|||||||
|
|
||||||
#### Enter your keys
|
#### Enter your keys
|
||||||
- Figure out what format DeDRM wants your key in by looking in
|
- Figure out what format DeDRM wants your key in by looking in
|
||||||
[the code that handles that](src/prefs.py).
|
[the code that handles that](dedrm_src/prefs.py).
|
||||||
- For Kindle eInk devices, DeDRM expects you to put a list of serial
|
- For Kindle eInk devices, DeDRM expects you to put a list of serial
|
||||||
numbers in the `serials` field: `"serials": ["012345689abcdef"]` or
|
numbers in the `serials` field: `"serials": ["012345689abcdef"]` or
|
||||||
`"serials": ["1111111111111111", "2222222222222222"]`.
|
`"serials": ["1111111111111111", "2222222222222222"]`.
|
||||||
|
|||||||
@@ -17,12 +17,12 @@ p {margin-top: 0}
|
|||||||
|
|
||||||
<body>
|
<body>
|
||||||
|
|
||||||
<h1>DeDRM Plugin <span class="version">(v6.3.0)</span></h1>
|
<h1>DeDRM Plugin <span class="version">(v6.7.0)</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>
|
||||||
|
|
||||||
<h3>Installation</h3>
|
<h3>Installation</h3>
|
||||||
<p>You have obviously managed to install the plugin, as otherwise you wouldn’t be reading this help file. However, you should also delete any older DeDRM plugins, as this DeDRM plugin replaces the five older plugins: Kindle and Mobipocket DeDRM (K4MobiDeDRM), Ignoble Epub DeDRM (ignobleepub), Inept Epub DeDRM (ineptepub), Inept PDF DeDRM (ineptepub) and eReader PDB 2 PML (eReaderPDB2PML).</p>
|
<p>You have obviously managed to install the plugin, as otherwise you wouldn’t be reading this help file. However, you should also delete any older DRM removal plugins, as this DeDRM plugin replaces the five older plugins: Kindle and Mobipocket DeDRM (K4MobiDeDRM), Ignoble Epub DeDRM (ignobleepub), Inept Epub DeDRM (ineptepub), Inept PDF DeDRM (ineptepub) and eReader PDB 2 PML (eReaderPDB2PML).</p>
|
||||||
|
|
||||||
<h3>Configuration</h3>
|
<h3>Configuration</h3>
|
||||||
<p>On Windows and Mac, the keys for ebooks downloaded for Kindle for Mac/PC and Adobe Digital Editions are automatically generated. If all your DRMed ebooks can be opened and read in Kindle for Mac/PC and/or Adobe Digital Editions on the same computer on which you are running calibre, you do not need to do any configuration of this plugin. On Linux, keys for Kindle for PC and Adobe Digital Editions need to be generated separately (see the Linux section below)</p>
|
<p>On Windows and Mac, the keys for ebooks downloaded for Kindle for Mac/PC and Adobe Digital Editions are automatically generated. If all your DRMed ebooks can be opened and read in Kindle for Mac/PC and/or Adobe Digital Editions on the same computer on which you are running calibre, you do not need to do any configuration of this plugin. On Linux, keys for Kindle for PC and Adobe Digital Editions need to be generated separately (see the Linux section below)</p>
|
||||||
@@ -4,9 +4,10 @@
|
|||||||
from __future__ import with_statement
|
from __future__ import with_statement
|
||||||
|
|
||||||
# __init__.py for DeDRM_plugin
|
# __init__.py for DeDRM_plugin
|
||||||
# Copyright © 2008-2018 Apprentice Harper et al.
|
# Copyright © 2008-2020 Apprentice Harper et al.
|
||||||
|
|
||||||
__license__ = 'GPL v3'
|
__license__ = 'GPL v3'
|
||||||
|
__version__ = '6.8.1'
|
||||||
__docformat__ = 'restructuredtext en'
|
__docformat__ = 'restructuredtext en'
|
||||||
|
|
||||||
|
|
||||||
@@ -17,8 +18,8 @@ __docformat__ = 'restructuredtext en'
|
|||||||
# We had the much easier job of converting them to a calibre plugin.
|
# We had the much easier job of converting them to a calibre plugin.
|
||||||
#
|
#
|
||||||
# This plugin is meant to decrypt eReader PDBs, Adobe Adept ePubs, Barnes & Noble ePubs,
|
# This plugin is meant to decrypt eReader PDBs, Adobe Adept ePubs, Barnes & Noble ePubs,
|
||||||
# Adobe Adept PDFs, Amazon Kindle and Mobipocket files without having to
|
# Adobe Adept PDFs, Amazon Kindle and Mobipocket files without having
|
||||||
# install any dependencies... other than having calibre installed, of course.
|
# to install any dependencies... other than having calibre installed, of course.
|
||||||
#
|
#
|
||||||
# Configuration:
|
# Configuration:
|
||||||
# Check out the plugin's configuration settings by clicking the "Customize plugin"
|
# Check out the plugin's configuration settings by clicking the "Customize plugin"
|
||||||
@@ -67,14 +68,17 @@ __docformat__ = 'restructuredtext en'
|
|||||||
# imported format was azw8 since that may be converted to kfx)
|
# imported format was azw8 since that may be converted to kfx)
|
||||||
# 6.6.1 - Thanks to wzyboy for a fix for stand-alone tools, and the new folder structure.
|
# 6.6.1 - Thanks to wzyboy for a fix for stand-alone tools, and the new folder structure.
|
||||||
# 6.6.2 - revamp of folders to get Mac OS X app working. Updated to 64-bit app. Various fixes.
|
# 6.6.2 - revamp of folders to get Mac OS X app working. Updated to 64-bit app. Various fixes.
|
||||||
|
# 6.6.3 - More cleanup of kindle book names and start of support for .kinf2018
|
||||||
|
# 6.7.0 - Handle new library in calibre.
|
||||||
|
# 6.8.0 - Full support for .kinf2018 and new KFX encryption (Kindle for PC/Mac 2.5+)
|
||||||
|
# 6.8.1 - Kindle key fix for Mac OS X Big Syr
|
||||||
|
|
||||||
"""
|
"""
|
||||||
Decrypt DRMed ebooks.
|
Decrypt DRMed ebooks.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
PLUGIN_NAME = u"DeDRM"
|
PLUGIN_NAME = u"DeDRM"
|
||||||
PLUGIN_VERSION_TUPLE = (6, 6, 2)
|
PLUGIN_VERSION_TUPLE = (6, 8, 0)
|
||||||
PLUGIN_VERSION = u".".join([unicode(str(x)) for x in PLUGIN_VERSION_TUPLE])
|
PLUGIN_VERSION = u".".join([unicode(str(x)) for x in PLUGIN_VERSION_TUPLE])
|
||||||
# Include an html helpfile in the plugin's zipfile with the following name.
|
# Include an html helpfile in the plugin's zipfile with the following name.
|
||||||
RESOURCE_NAME = PLUGIN_NAME + '_Help.htm'
|
RESOURCE_NAME = PLUGIN_NAME + '_Help.htm'
|
||||||
@@ -52,6 +52,7 @@ from __future__ import with_statement
|
|||||||
"""
|
"""
|
||||||
Retrieve Adobe ADEPT user key.
|
Retrieve Adobe ADEPT user key.
|
||||||
"""
|
"""
|
||||||
|
from __future__ import print_function
|
||||||
|
|
||||||
__license__ = 'GPL v3'
|
__license__ = 'GPL v3'
|
||||||
__version__ = '6.0'
|
__version__ = '6.0'
|
||||||
@@ -407,7 +408,7 @@ if iswindows:
|
|||||||
keys.append(userkey)
|
keys.append(userkey)
|
||||||
if len(keys) == 0:
|
if len(keys) == 0:
|
||||||
raise ADEPTError('Could not locate privateLicenseKey')
|
raise ADEPTError('Could not locate privateLicenseKey')
|
||||||
print u"Found {0:d} keys".format(len(keys))
|
print(u"Found {0:d} keys".format(len(keys)))
|
||||||
return keys
|
return keys
|
||||||
|
|
||||||
|
|
||||||
@@ -465,7 +466,7 @@ def getkey(outpath):
|
|||||||
outfile = outpath
|
outfile = outpath
|
||||||
with file(outfile, 'wb') as keyfileout:
|
with file(outfile, 'wb') as keyfileout:
|
||||||
keyfileout.write(keys[0])
|
keyfileout.write(keys[0])
|
||||||
print u"Saved a key to {0}".format(outfile)
|
print(u"Saved a key to {0}".format(outfile))
|
||||||
else:
|
else:
|
||||||
keycount = 0
|
keycount = 0
|
||||||
for key in keys:
|
for key in keys:
|
||||||
@@ -476,28 +477,28 @@ def getkey(outpath):
|
|||||||
break
|
break
|
||||||
with file(outfile, 'wb') as keyfileout:
|
with file(outfile, 'wb') as keyfileout:
|
||||||
keyfileout.write(key)
|
keyfileout.write(key)
|
||||||
print u"Saved a key to {0}".format(outfile)
|
print(u"Saved a key to {0}".format(outfile))
|
||||||
return True
|
return True
|
||||||
return False
|
return False
|
||||||
|
|
||||||
def usage(progname):
|
def usage(progname):
|
||||||
print u"Finds, decrypts and saves the default Adobe Adept encryption key(s)."
|
print(u"Finds, decrypts and saves the default Adobe Adept encryption key(s).")
|
||||||
print u"Keys are saved to the current directory, or a specified output directory."
|
print(u"Keys are saved to the current directory, or a specified output directory.")
|
||||||
print u"If a file name is passed instead of a directory, only the first key is saved, in that file."
|
print(u"If a file name is passed instead of a directory, only the first key is saved, in that file.")
|
||||||
print u"Usage:"
|
print(u"Usage:")
|
||||||
print u" {0:s} [-h] [<outpath>]".format(progname)
|
print(u" {0:s} [-h] [<outpath>]".format(progname))
|
||||||
|
|
||||||
def cli_main():
|
def cli_main():
|
||||||
sys.stdout=SafeUnbuffered(sys.stdout)
|
sys.stdout=SafeUnbuffered(sys.stdout)
|
||||||
sys.stderr=SafeUnbuffered(sys.stderr)
|
sys.stderr=SafeUnbuffered(sys.stderr)
|
||||||
argv=unicode_argv()
|
argv=unicode_argv()
|
||||||
progname = os.path.basename(argv[0])
|
progname = os.path.basename(argv[0])
|
||||||
print u"{0} v{1}\nCopyright © 2009-2013 i♥cabbages and Apprentice Alf".format(progname,__version__)
|
print(u"{0} v{1}\nCopyright © 2009-2013 i♥cabbages and Apprentice Alf".format(progname,__version__))
|
||||||
|
|
||||||
try:
|
try:
|
||||||
opts, args = getopt.getopt(argv[1:], "h")
|
opts, args = getopt.getopt(argv[1:], "h")
|
||||||
except getopt.GetoptError, err:
|
except getopt.GetoptError, err:
|
||||||
print u"Error in options or arguments: {0}".format(err.args[0])
|
print(u"Error in options or arguments: {0}".format(err.args[0]))
|
||||||
usage(progname)
|
usage(progname)
|
||||||
sys.exit(2)
|
sys.exit(2)
|
||||||
|
|
||||||
@@ -528,7 +529,7 @@ def cli_main():
|
|||||||
outfile = outpath
|
outfile = outpath
|
||||||
with file(outfile, 'wb') as keyfileout:
|
with file(outfile, 'wb') as keyfileout:
|
||||||
keyfileout.write(keys[0])
|
keyfileout.write(keys[0])
|
||||||
print u"Saved a key to {0}".format(outfile)
|
print(u"Saved a key to {0}".format(outfile))
|
||||||
else:
|
else:
|
||||||
keycount = 0
|
keycount = 0
|
||||||
for key in keys:
|
for key in keys:
|
||||||
@@ -539,9 +540,9 @@ def cli_main():
|
|||||||
break
|
break
|
||||||
with file(outfile, 'wb') as keyfileout:
|
with file(outfile, 'wb') as keyfileout:
|
||||||
keyfileout.write(key)
|
keyfileout.write(key)
|
||||||
print u"Saved a key to {0}".format(outfile)
|
print(u"Saved a key to {0}".format(outfile))
|
||||||
else:
|
else:
|
||||||
print u"Could not retrieve Adobe Adept key."
|
print(u"Could not retrieve Adobe Adept key.")
|
||||||
return 0
|
return 0
|
||||||
|
|
||||||
|
|
||||||
@@ -8,6 +8,7 @@
|
|||||||
# pbkdf2.py Copyright © 2009 Daniel Holth <dholth@fastmail.fm>
|
# pbkdf2.py Copyright © 2009 Daniel Holth <dholth@fastmail.fm>
|
||||||
# pbkdf2.py This code may be freely used and modified for any purpose.
|
# pbkdf2.py This code may be freely used and modified for any purpose.
|
||||||
|
|
||||||
|
from __future__ import print_function
|
||||||
import sys, os
|
import sys, os
|
||||||
import hmac
|
import hmac
|
||||||
from struct import pack
|
from struct import pack
|
||||||
@@ -158,7 +159,7 @@ def _load_libalfcrypto():
|
|||||||
topazCryptoDecrypt(ctx, data, out, len(data))
|
topazCryptoDecrypt(ctx, data, out, len(data))
|
||||||
return out.raw
|
return out.raw
|
||||||
|
|
||||||
print u"Using Library AlfCrypto DLL/DYLIB/SO"
|
print(u"Using Library AlfCrypto DLL/DYLIB/SO")
|
||||||
return (AES_CBC, Pukall_Cipher, Topaz_Cipher)
|
return (AES_CBC, Pukall_Cipher, Topaz_Cipher)
|
||||||
|
|
||||||
|
|
||||||
@@ -244,7 +245,7 @@ def _load_python_alfcrypto():
|
|||||||
cleartext = self.aes.decrypt(iv + data)
|
cleartext = self.aes.decrypt(iv + data)
|
||||||
return cleartext
|
return cleartext
|
||||||
|
|
||||||
print u"Using Library AlfCrypto Python"
|
print(u"Using Library AlfCrypto Python")
|
||||||
return (AES_CBC, Pukall_Cipher, Topaz_Cipher)
|
return (AES_CBC, Pukall_Cipher, Topaz_Cipher)
|
||||||
|
|
||||||
|
|
||||||
@@ -21,6 +21,7 @@ from __future__ import with_statement
|
|||||||
"""
|
"""
|
||||||
Retrieve Kindle for Android Serial Number.
|
Retrieve Kindle for Android Serial Number.
|
||||||
"""
|
"""
|
||||||
|
from __future__ import print_function
|
||||||
|
|
||||||
__license__ = 'GPL v3'
|
__license__ = 'GPL v3'
|
||||||
__version__ = '1.5'
|
__version__ = '1.5'
|
||||||
@@ -228,7 +229,7 @@ def get_serials2(path=STORAGE2):
|
|||||||
if len(userdata_utf8) > 0:
|
if len(userdata_utf8) > 0:
|
||||||
dsns.append(userdata_utf8)
|
dsns.append(userdata_utf8)
|
||||||
except:
|
except:
|
||||||
print "Error getting one of the device serial name keys"
|
print("Error getting one of the device serial name keys")
|
||||||
traceback.print_exc()
|
traceback.print_exc()
|
||||||
pass
|
pass
|
||||||
dsns = list(set(dsns))
|
dsns = list(set(dsns))
|
||||||
@@ -243,7 +244,7 @@ def get_serials2(path=STORAGE2):
|
|||||||
if len(userdata_utf8) > 0:
|
if len(userdata_utf8) > 0:
|
||||||
tokens.append(userdata_utf8)
|
tokens.append(userdata_utf8)
|
||||||
except:
|
except:
|
||||||
print "Error getting one of the account token keys"
|
print("Error getting one of the account token keys")
|
||||||
traceback.print_exc()
|
traceback.print_exc()
|
||||||
pass
|
pass
|
||||||
tokens = list(set(tokens))
|
tokens = list(set(tokens))
|
||||||
@@ -321,13 +322,13 @@ def getkey(outfile, inpath):
|
|||||||
|
|
||||||
|
|
||||||
def usage(progname):
|
def usage(progname):
|
||||||
print u"Decrypts the serial number(s) of Kindle For Android from Android backup or file"
|
print(u"Decrypts the serial number(s) of Kindle For Android from Android backup or file")
|
||||||
print u"Get backup.ab file using adb backup com.amazon.kindle for Android 4.0+."
|
print(u"Get backup.ab file using adb backup com.amazon.kindle for Android 4.0+.")
|
||||||
print u"Otherwise extract AmazonSecureStorage.xml from /data/data/com.amazon.kindle/shared_prefs/AmazonSecureStorage.xml"
|
print(u"Otherwise extract AmazonSecureStorage.xml from /data/data/com.amazon.kindle/shared_prefs/AmazonSecureStorage.xml")
|
||||||
print u"Or map_data_storage.db from /data/data/com.amazon.kindle/databases/map_data_storage.db"
|
print(u"Or map_data_storage.db from /data/data/com.amazon.kindle/databases/map_data_storage.db")
|
||||||
print u""
|
print(u"")
|
||||||
print u"Usage:"
|
print(u"Usage:")
|
||||||
print u" {0:s} [-h] [-b <backup.ab>] [<outfile.k4a>]".format(progname)
|
print(u" {0:s} [-h] [-b <backup.ab>] [<outfile.k4a>]".format(progname))
|
||||||
|
|
||||||
|
|
||||||
def cli_main():
|
def cli_main():
|
||||||
@@ -335,13 +336,13 @@ def cli_main():
|
|||||||
sys.stderr=SafeUnbuffered(sys.stderr)
|
sys.stderr=SafeUnbuffered(sys.stderr)
|
||||||
argv=unicode_argv()
|
argv=unicode_argv()
|
||||||
progname = os.path.basename(argv[0])
|
progname = os.path.basename(argv[0])
|
||||||
print u"{0} v{1}\nCopyright © 2010-2015 Thom, some_updates, Apprentice Alf and Apprentice Harper".format(progname,__version__)
|
print(u"{0} v{1}\nCopyright © 2010-2015 Thom, some_updates, Apprentice Alf and Apprentice Harper".format(progname,__version__))
|
||||||
|
|
||||||
try:
|
try:
|
||||||
opts, args = getopt.getopt(argv[1:], "hb:")
|
opts, args = getopt.getopt(argv[1:], "hb:")
|
||||||
except getopt.GetoptError, err:
|
except getopt.GetoptError, err:
|
||||||
usage(progname)
|
usage(progname)
|
||||||
print u"\nError in options or arguments: {0}".format(err.args[0])
|
print(u"\nError in options or arguments: {0}".format(err.args[0]))
|
||||||
return 2
|
return 2
|
||||||
|
|
||||||
inpath = ""
|
inpath = ""
|
||||||
@@ -373,13 +374,13 @@ def cli_main():
|
|||||||
|
|
||||||
if not os.path.isfile(inpath):
|
if not os.path.isfile(inpath):
|
||||||
usage(progname)
|
usage(progname)
|
||||||
print u"\n{0:s} file not found".format(inpath)
|
print(u"\n{0:s} file not found".format(inpath))
|
||||||
return 2
|
return 2
|
||||||
|
|
||||||
if getkey(outfile, inpath):
|
if getkey(outfile, inpath):
|
||||||
print u"\nSaved Kindle for Android key to {0}".format(outfile)
|
print(u"\nSaved Kindle for Android key to {0}".format(outfile))
|
||||||
else:
|
else:
|
||||||
print u"\nCould not retrieve Kindle for Android key."
|
print(u"\nCould not retrieve Kindle for Android key.")
|
||||||
return 0
|
return 0
|
||||||
|
|
||||||
|
|
||||||
@@ -390,7 +391,7 @@ def gui_main():
|
|||||||
import tkMessageBox
|
import tkMessageBox
|
||||||
import tkFileDialog
|
import tkFileDialog
|
||||||
except:
|
except:
|
||||||
print "Tkinter not installed"
|
print("Tkinter not installed")
|
||||||
return cli_main()
|
return cli_main()
|
||||||
|
|
||||||
class DecryptionDialog(Tkinter.Frame):
|
class DecryptionDialog(Tkinter.Frame):
|
||||||
@@ -2,6 +2,7 @@
|
|||||||
# -*- coding: utf-8 -*-
|
# -*- coding: utf-8 -*-
|
||||||
|
|
||||||
from __future__ import with_statement
|
from __future__ import with_statement
|
||||||
|
from __future__ import print_function
|
||||||
|
|
||||||
__license__ = 'GPL v3'
|
__license__ = 'GPL v3'
|
||||||
|
|
||||||
@@ -457,7 +458,7 @@ class ManageKeysDialog(QDialog):
|
|||||||
|
|
||||||
class RenameKeyDialog(QDialog):
|
class RenameKeyDialog(QDialog):
|
||||||
def __init__(self, parent=None,):
|
def __init__(self, parent=None,):
|
||||||
print repr(self), repr(parent)
|
print(repr(self), repr(parent))
|
||||||
QDialog.__init__(self, parent)
|
QDialog.__init__(self, parent)
|
||||||
self.parent = parent
|
self.parent = parent
|
||||||
self.setWindowTitle("{0} {1}: Rename {0}".format(PLUGIN_NAME, PLUGIN_VERSION, parent.key_type_name))
|
self.setWindowTitle("{0} {1}: Rename {0}".format(PLUGIN_NAME, PLUGIN_VERSION, parent.key_type_name))
|
||||||
@@ -879,7 +880,7 @@ class AddSerialDialog(QDialog):
|
|||||||
|
|
||||||
@property
|
@property
|
||||||
def key_value(self):
|
def key_value(self):
|
||||||
return unicode(self.key_ledit.text()).strip()
|
return unicode(self.key_ledit.text()).replace(' ', '')
|
||||||
|
|
||||||
def accept(self):
|
def accept(self):
|
||||||
if len(self.key_name) == 0 or self.key_name.isspace():
|
if len(self.key_name) == 0 or self.key_name.isspace():
|
||||||
@@ -2,6 +2,7 @@
|
|||||||
# vim:ts=4:sw=4:softtabstop=4:smarttab:expandtab
|
# vim:ts=4:sw=4:softtabstop=4:smarttab:expandtab
|
||||||
# For use with Topaz Scripts Version 2.6
|
# For use with Topaz Scripts Version 2.6
|
||||||
|
|
||||||
|
from __future__ import print_function
|
||||||
class Unbuffered:
|
class Unbuffered:
|
||||||
def __init__(self, stream):
|
def __init__(self, stream):
|
||||||
self.stream = stream
|
self.stream = stream
|
||||||
@@ -139,7 +140,7 @@ class Dictionary(object):
|
|||||||
self.pos = val
|
self.pos = val
|
||||||
return self.stable[self.pos]
|
return self.stable[self.pos]
|
||||||
else:
|
else:
|
||||||
print "Error - %d outside of string table limits" % val
|
print("Error - %d outside of string table limits" % val)
|
||||||
raise TpzDRMError('outside of string table limits')
|
raise TpzDRMError('outside of string table limits')
|
||||||
# sys.exit(-1)
|
# sys.exit(-1)
|
||||||
|
|
||||||
@@ -151,7 +152,7 @@ class Dictionary(object):
|
|||||||
|
|
||||||
def dumpDict(self):
|
def dumpDict(self):
|
||||||
for i in xrange(self.size):
|
for i in xrange(self.size):
|
||||||
print "%d %s %s" % (i, convert(i), self.stable[i])
|
print("%d %s %s" % (i, convert(i), self.stable[i]))
|
||||||
return
|
return
|
||||||
|
|
||||||
# parses the xml snippets that are represented by each page*.dat file.
|
# parses the xml snippets that are represented by each page*.dat file.
|
||||||
@@ -457,7 +458,7 @@ class PageParser(object):
|
|||||||
elif (argtype == 'snippets') :
|
elif (argtype == 'snippets') :
|
||||||
result = arg
|
result = arg
|
||||||
else :
|
else :
|
||||||
print "Error Unknown argtype %s" % argtype
|
print("Error Unknown argtype %s" % argtype)
|
||||||
sys.exit(-2)
|
sys.exit(-2)
|
||||||
return result
|
return result
|
||||||
|
|
||||||
@@ -469,7 +470,7 @@ class PageParser(object):
|
|||||||
known_token = False
|
known_token = False
|
||||||
self.tag_push(token)
|
self.tag_push(token)
|
||||||
|
|
||||||
if self.debug : print 'Processing: ', self.get_tagpath(0)
|
if self.debug : print('Processing: ', self.get_tagpath(0))
|
||||||
cnt = self.tagpath_len()
|
cnt = self.tagpath_len()
|
||||||
for j in xrange(cnt):
|
for j in xrange(cnt):
|
||||||
tkn = self.get_tagpath(j)
|
tkn = self.get_tagpath(j)
|
||||||
@@ -495,7 +496,7 @@ class PageParser(object):
|
|||||||
|
|
||||||
if (subtags == 1):
|
if (subtags == 1):
|
||||||
ntags = readEncodedNumber(self.fo)
|
ntags = readEncodedNumber(self.fo)
|
||||||
if self.debug : print 'subtags: ' + token + ' has ' + str(ntags)
|
if self.debug : print('subtags: ' + token + ' has ' + str(ntags))
|
||||||
for j in xrange(ntags):
|
for j in xrange(ntags):
|
||||||
val = readEncodedNumber(self.fo)
|
val = readEncodedNumber(self.fo)
|
||||||
subtagres.append(self.procToken(self.dict.lookup(val)))
|
subtagres.append(self.procToken(self.dict.lookup(val)))
|
||||||
@@ -529,7 +530,7 @@ class PageParser(object):
|
|||||||
else:
|
else:
|
||||||
result = []
|
result = []
|
||||||
if (self.debug or self.first_unknown):
|
if (self.debug or self.first_unknown):
|
||||||
print 'Unknown Token:', token
|
print('Unknown Token:', token)
|
||||||
self.first_unknown = False
|
self.first_unknown = False
|
||||||
self.tag_pop()
|
self.tag_pop()
|
||||||
return result
|
return result
|
||||||
@@ -544,9 +545,9 @@ class PageParser(object):
|
|||||||
result = 'Set of '+ str(cnt) + ' xml snippets. The overall structure \n'
|
result = 'Set of '+ str(cnt) + ' xml snippets. The overall structure \n'
|
||||||
result += 'of the document is indicated by snippet number sets at the\n'
|
result += 'of the document is indicated by snippet number sets at the\n'
|
||||||
result += 'end of each snippet. \n'
|
result += 'end of each snippet. \n'
|
||||||
print result
|
print(result)
|
||||||
for i in xrange(cnt):
|
for i in xrange(cnt):
|
||||||
if self.debug: print 'Snippet:',str(i)
|
if self.debug: print('Snippet:',str(i))
|
||||||
snippet = []
|
snippet = []
|
||||||
snippet.append(i)
|
snippet.append(i)
|
||||||
val = readEncodedNumber(self.fo)
|
val = readEncodedNumber(self.fo)
|
||||||
@@ -588,10 +589,10 @@ class PageParser(object):
|
|||||||
cnt = readEncodedNumber(self.fo)
|
cnt = readEncodedNumber(self.fo)
|
||||||
mode = readEncodedNumber(self.fo)
|
mode = readEncodedNumber(self.fo)
|
||||||
|
|
||||||
if self.debug : print 'Loop for', cnt, 'with mode', mode, ': '
|
if self.debug : print('Loop for', cnt, 'with mode', mode, ': ')
|
||||||
return self.doLoop76Mode(argtype, cnt, mode)
|
return self.doLoop76Mode(argtype, cnt, mode)
|
||||||
|
|
||||||
if self.dbug: print "Unknown command", cmd
|
if self.dbug: print("Unknown command", cmd)
|
||||||
result = []
|
result = []
|
||||||
return result
|
return result
|
||||||
|
|
||||||
@@ -720,7 +721,7 @@ class PageParser(object):
|
|||||||
else:
|
else:
|
||||||
rlst.append(self.formatTag(j))
|
rlst.append(self.formatTag(j))
|
||||||
result = "".join(rlst)
|
result = "".join(rlst)
|
||||||
if self.debug : print result
|
if self.debug : print(result)
|
||||||
return result
|
return result
|
||||||
|
|
||||||
|
|
||||||
@@ -775,7 +776,7 @@ class PageParser(object):
|
|||||||
self.doc.append(tag)
|
self.doc.append(tag)
|
||||||
else:
|
else:
|
||||||
if self.debug:
|
if self.debug:
|
||||||
print "Main Loop: Unknown value: %x" % v
|
print("Main Loop: Unknown value: %x" % v)
|
||||||
if (v == 0):
|
if (v == 0):
|
||||||
if (self.peek(1) == 0x5f):
|
if (self.peek(1) == 0x5f):
|
||||||
skip = self.fo.read(1)
|
skip = self.fo.read(1)
|
||||||
@@ -783,11 +784,11 @@ class PageParser(object):
|
|||||||
|
|
||||||
# now do snippet injection
|
# now do snippet injection
|
||||||
if len(self.snippetList) > 0 :
|
if len(self.snippetList) > 0 :
|
||||||
if self.debug : print 'Injecting Snippets:'
|
if self.debug : print('Injecting Snippets:')
|
||||||
snippet = self.injectSnippets(self.snippetList[0])
|
snippet = self.injectSnippets(self.snippetList[0])
|
||||||
snipno = snippet[0]
|
snipno = snippet[0]
|
||||||
tag_add = snippet[1]
|
tag_add = snippet[1]
|
||||||
if self.debug : print self.formatTag(tag_add)
|
if self.debug : print(self.formatTag(tag_add))
|
||||||
if len(tag_add) > 0:
|
if len(tag_add) > 0:
|
||||||
self.doc.append(tag_add)
|
self.doc.append(tag_add)
|
||||||
|
|
||||||
@@ -812,19 +813,19 @@ def getXML(dict, fname):
|
|||||||
return xmlpage
|
return xmlpage
|
||||||
|
|
||||||
def usage():
|
def usage():
|
||||||
print 'Usage: '
|
print('Usage: ')
|
||||||
print ' convert2xml.py dict0000.dat infile.dat '
|
print(' convert2xml.py dict0000.dat infile.dat ')
|
||||||
print ' '
|
print(' ')
|
||||||
print ' Options:'
|
print(' Options:')
|
||||||
print ' -h print this usage help message '
|
print(' -h print this usage help message ')
|
||||||
print ' -d turn on debug output to check for potential errors '
|
print(' -d turn on debug output to check for potential errors ')
|
||||||
print ' --flat-xml output the flattened xml page description only '
|
print(' --flat-xml output the flattened xml page description only ')
|
||||||
print ' '
|
print(' ')
|
||||||
print ' This program will attempt to convert a page*.dat file or '
|
print(' This program will attempt to convert a page*.dat file or ')
|
||||||
print ' glyphs*.dat file, using the dict0000.dat file, to its xml description. '
|
print(' glyphs*.dat file, using the dict0000.dat file, to its xml description. ')
|
||||||
print ' '
|
print(' ')
|
||||||
print ' Use "cmbtc_dump.py" first to unencrypt, uncompress, and dump '
|
print(' Use "cmbtc_dump.py" first to unencrypt, uncompress, and dump ')
|
||||||
print ' the *.dat files from a Topaz format e-book.'
|
print(' the *.dat files from a Topaz format e-book.')
|
||||||
|
|
||||||
#
|
#
|
||||||
# Main
|
# Main
|
||||||
@@ -846,7 +847,7 @@ def main(argv):
|
|||||||
except getopt.GetoptError, err:
|
except getopt.GetoptError, err:
|
||||||
|
|
||||||
# print help information and exit:
|
# print help information and exit:
|
||||||
print str(err) # will print something like "option -a not recognized"
|
print(str(err)) # will print something like "option -a not recognized"
|
||||||
usage()
|
usage()
|
||||||
sys.exit(2)
|
sys.exit(2)
|
||||||
|
|
||||||
@@ -875,7 +876,7 @@ def main(argv):
|
|||||||
xmlpage = pp.process()
|
xmlpage = pp.process()
|
||||||
|
|
||||||
if printOutput:
|
if printOutput:
|
||||||
print xmlpage
|
print(xmlpage)
|
||||||
return 0
|
return 0
|
||||||
|
|
||||||
return xmlpage
|
return xmlpage
|
||||||
@@ -15,6 +15,7 @@ Provide base64 encoding.
|
|||||||
"""
|
"""
|
||||||
|
|
||||||
from __future__ import with_statement
|
from __future__ import with_statement
|
||||||
|
from __future__ import print_function
|
||||||
|
|
||||||
__license__ = 'GPL v3'
|
__license__ = 'GPL v3'
|
||||||
|
|
||||||
@@ -23,9 +24,9 @@ import os
|
|||||||
import base64
|
import base64
|
||||||
|
|
||||||
def usage(progname):
|
def usage(progname):
|
||||||
print "Applies base64 encoding to the supplied file, sending to standard output"
|
print("Applies base64 encoding to the supplied file, sending to standard output")
|
||||||
print "Usage:"
|
print("Usage:")
|
||||||
print " %s <infile>" % progname
|
print(" %s <infile>" % progname)
|
||||||
|
|
||||||
def cli_main(argv=sys.argv):
|
def cli_main(argv=sys.argv):
|
||||||
progname = os.path.basename(argv[0])
|
progname = os.path.basename(argv[0])
|
||||||
@@ -37,7 +38,7 @@ def cli_main(argv=sys.argv):
|
|||||||
keypath = argv[1]
|
keypath = argv[1]
|
||||||
with open(keypath, 'rb') as f:
|
with open(keypath, 'rb') as f:
|
||||||
keyder = f.read()
|
keyder = f.read()
|
||||||
print keyder.encode('base64')
|
print(keyder.encode('base64'))
|
||||||
return 0
|
return 0
|
||||||
|
|
||||||
|
|
||||||
@@ -45,10 +45,11 @@
|
|||||||
#
|
#
|
||||||
|
|
||||||
from __future__ import with_statement
|
from __future__ import with_statement
|
||||||
|
from __future__ import print_function
|
||||||
|
|
||||||
__version__ = '1.01'
|
__version__ = '1.01'
|
||||||
|
|
||||||
import sys, struct, os
|
import sys, struct, os, traceback
|
||||||
import zlib
|
import zlib
|
||||||
import zipfile
|
import zipfile
|
||||||
import xml.etree.ElementTree as etree
|
import xml.etree.ElementTree as etree
|
||||||
@@ -199,7 +200,7 @@ def encryption(infile):
|
|||||||
|
|
||||||
def main():
|
def main():
|
||||||
argv=unicode_argv()
|
argv=unicode_argv()
|
||||||
print encryption(argv[1])
|
print(encryption(argv[1]))
|
||||||
return 0
|
return 0
|
||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
@@ -2,6 +2,7 @@
|
|||||||
# vim:ts=4:sw=4:softtabstop=4:smarttab:expandtab
|
# vim:ts=4:sw=4:softtabstop=4:smarttab:expandtab
|
||||||
# For use with Topaz Scripts Version 2.6
|
# For use with Topaz Scripts Version 2.6
|
||||||
|
|
||||||
|
from __future__ import print_function
|
||||||
import sys
|
import sys
|
||||||
import csv
|
import csv
|
||||||
import os
|
import os
|
||||||
@@ -750,11 +751,11 @@ class DocParser(object):
|
|||||||
hlst.append('<div class="graphic"><img src="img/img%04d.jpg" alt="" /></div>' % int(simgsrc))
|
hlst.append('<div class="graphic"><img src="img/img%04d.jpg" alt="" /></div>' % int(simgsrc))
|
||||||
|
|
||||||
else :
|
else :
|
||||||
print ' Making region type', regtype,
|
print(' Making region type', regtype, end=' ')
|
||||||
(pos, temp) = self.findinDoc('paragraph',start,end)
|
(pos, temp) = self.findinDoc('paragraph',start,end)
|
||||||
(pos2, temp) = self.findinDoc('span',start,end)
|
(pos2, temp) = self.findinDoc('span',start,end)
|
||||||
if pos != -1 or pos2 != -1:
|
if pos != -1 or pos2 != -1:
|
||||||
print ' a "text" region'
|
print(' a "text" region')
|
||||||
orig_regtype = regtype
|
orig_regtype = regtype
|
||||||
regtype = 'fixed'
|
regtype = 'fixed'
|
||||||
ptype = 'full'
|
ptype = 'full'
|
||||||
@@ -779,7 +780,7 @@ class DocParser(object):
|
|||||||
else :
|
else :
|
||||||
hlst.append(self.buildParagraph(pclass, pdesc, ptype, regtype))
|
hlst.append(self.buildParagraph(pclass, pdesc, ptype, regtype))
|
||||||
else :
|
else :
|
||||||
print ' a "graphic" region'
|
print(' a "graphic" region')
|
||||||
(pos, simgsrc) = self.findinDoc('img.src',start,end)
|
(pos, simgsrc) = self.findinDoc('img.src',start,end)
|
||||||
if simgsrc:
|
if simgsrc:
|
||||||
hlst.append('<div class="graphic"><img src="img/img%04d.jpg" alt="" /></div>' % int(simgsrc))
|
hlst.append('<div class="graphic"><img src="img/img%04d.jpg" alt="" /></div>' % int(simgsrc))
|
||||||
@@ -1,6 +1,9 @@
|
|||||||
#! /usr/bin/python
|
#! /usr/bin/python
|
||||||
# vim:ts=4:sw=4:softtabstop=4:smarttab:expandtab
|
# vim:ts=4:sw=4:softtabstop=4:smarttab:expandtab
|
||||||
|
|
||||||
|
from __future__ import print_function
|
||||||
|
from .convert2xml import encodeNumber
|
||||||
|
|
||||||
class Unbuffered:
|
class Unbuffered:
|
||||||
def __init__(self, stream):
|
def __init__(self, stream):
|
||||||
self.stream = stream
|
self.stream = stream
|
||||||
@@ -117,7 +120,7 @@ class Dictionary(object):
|
|||||||
self.pos = val
|
self.pos = val
|
||||||
return self.stable[self.pos]
|
return self.stable[self.pos]
|
||||||
else:
|
else:
|
||||||
print "Error: %d outside of string table limits" % val
|
print("Error: %d outside of string table limits" % val)
|
||||||
raise TpzDRMError('outside or string table limits')
|
raise TpzDRMError('outside or string table limits')
|
||||||
# sys.exit(-1)
|
# sys.exit(-1)
|
||||||
def getSize(self):
|
def getSize(self):
|
||||||
@@ -268,32 +271,32 @@ class GlyphDict(object):
|
|||||||
def generateBook(bookDir, raw, fixedimage):
|
def generateBook(bookDir, raw, fixedimage):
|
||||||
# sanity check Topaz file extraction
|
# sanity check Topaz file extraction
|
||||||
if not os.path.exists(bookDir) :
|
if not os.path.exists(bookDir) :
|
||||||
print "Can not find directory with unencrypted book"
|
print("Can not find directory with unencrypted book")
|
||||||
return 1
|
return 1
|
||||||
|
|
||||||
dictFile = os.path.join(bookDir,'dict0000.dat')
|
dictFile = os.path.join(bookDir,'dict0000.dat')
|
||||||
if not os.path.exists(dictFile) :
|
if not os.path.exists(dictFile) :
|
||||||
print "Can not find dict0000.dat file"
|
print("Can not find dict0000.dat file")
|
||||||
return 1
|
return 1
|
||||||
|
|
||||||
pageDir = os.path.join(bookDir,'page')
|
pageDir = os.path.join(bookDir,'page')
|
||||||
if not os.path.exists(pageDir) :
|
if not os.path.exists(pageDir) :
|
||||||
print "Can not find page directory in unencrypted book"
|
print("Can not find page directory in unencrypted book")
|
||||||
return 1
|
return 1
|
||||||
|
|
||||||
imgDir = os.path.join(bookDir,'img')
|
imgDir = os.path.join(bookDir,'img')
|
||||||
if not os.path.exists(imgDir) :
|
if not os.path.exists(imgDir) :
|
||||||
print "Can not find image directory in unencrypted book"
|
print("Can not find image directory in unencrypted book")
|
||||||
return 1
|
return 1
|
||||||
|
|
||||||
glyphsDir = os.path.join(bookDir,'glyphs')
|
glyphsDir = os.path.join(bookDir,'glyphs')
|
||||||
if not os.path.exists(glyphsDir) :
|
if not os.path.exists(glyphsDir) :
|
||||||
print "Can not find glyphs directory in unencrypted book"
|
print("Can not find glyphs directory in unencrypted book")
|
||||||
return 1
|
return 1
|
||||||
|
|
||||||
metaFile = os.path.join(bookDir,'metadata0000.dat')
|
metaFile = os.path.join(bookDir,'metadata0000.dat')
|
||||||
if not os.path.exists(metaFile) :
|
if not os.path.exists(metaFile) :
|
||||||
print "Can not find metadata0000.dat in unencrypted book"
|
print("Can not find metadata0000.dat in unencrypted book")
|
||||||
return 1
|
return 1
|
||||||
|
|
||||||
svgDir = os.path.join(bookDir,'svg')
|
svgDir = os.path.join(bookDir,'svg')
|
||||||
@@ -307,10 +310,10 @@ def generateBook(bookDir, raw, fixedimage):
|
|||||||
|
|
||||||
otherFile = os.path.join(bookDir,'other0000.dat')
|
otherFile = os.path.join(bookDir,'other0000.dat')
|
||||||
if not os.path.exists(otherFile) :
|
if not os.path.exists(otherFile) :
|
||||||
print "Can not find other0000.dat in unencrypted book"
|
print("Can not find other0000.dat in unencrypted book")
|
||||||
return 1
|
return 1
|
||||||
|
|
||||||
print "Updating to color images if available"
|
print("Updating to color images if available")
|
||||||
spath = os.path.join(bookDir,'color_img')
|
spath = os.path.join(bookDir,'color_img')
|
||||||
dpath = os.path.join(bookDir,'img')
|
dpath = os.path.join(bookDir,'img')
|
||||||
filenames = os.listdir(spath)
|
filenames = os.listdir(spath)
|
||||||
@@ -322,7 +325,7 @@ def generateBook(bookDir, raw, fixedimage):
|
|||||||
imgdata = file(sfile,'rb').read()
|
imgdata = file(sfile,'rb').read()
|
||||||
file(dfile,'wb').write(imgdata)
|
file(dfile,'wb').write(imgdata)
|
||||||
|
|
||||||
print "Creating cover.jpg"
|
print("Creating cover.jpg")
|
||||||
isCover = False
|
isCover = False
|
||||||
cpath = os.path.join(bookDir,'img')
|
cpath = os.path.join(bookDir,'img')
|
||||||
cpath = os.path.join(cpath,'img0000.jpg')
|
cpath = os.path.join(cpath,'img0000.jpg')
|
||||||
@@ -333,10 +336,10 @@ def generateBook(bookDir, raw, fixedimage):
|
|||||||
isCover = True
|
isCover = True
|
||||||
|
|
||||||
|
|
||||||
print 'Processing Dictionary'
|
print('Processing Dictionary')
|
||||||
dict = Dictionary(dictFile)
|
dict = Dictionary(dictFile)
|
||||||
|
|
||||||
print 'Processing Meta Data and creating OPF'
|
print('Processing Meta Data and creating OPF')
|
||||||
meta_array = getMetaArray(metaFile)
|
meta_array = getMetaArray(metaFile)
|
||||||
|
|
||||||
# replace special chars in title and authors like & < >
|
# replace special chars in title and authors like & < >
|
||||||
@@ -360,7 +363,7 @@ def generateBook(bookDir, raw, fixedimage):
|
|||||||
mlst = None
|
mlst = None
|
||||||
file(xname, 'wb').write(metastr)
|
file(xname, 'wb').write(metastr)
|
||||||
|
|
||||||
print 'Processing StyleSheet'
|
print('Processing StyleSheet')
|
||||||
|
|
||||||
# get some scaling info from metadata to use while processing styles
|
# get some scaling info from metadata to use while processing styles
|
||||||
# and first page info
|
# and first page info
|
||||||
@@ -426,7 +429,7 @@ def generateBook(bookDir, raw, fixedimage):
|
|||||||
xname = os.path.join(xmlDir, 'other0000.xml')
|
xname = os.path.join(xmlDir, 'other0000.xml')
|
||||||
file(xname, 'wb').write(convert2xml.getXML(dict, otherFile))
|
file(xname, 'wb').write(convert2xml.getXML(dict, otherFile))
|
||||||
|
|
||||||
print 'Processing Glyphs'
|
print('Processing Glyphs')
|
||||||
gd = GlyphDict()
|
gd = GlyphDict()
|
||||||
filenames = os.listdir(glyphsDir)
|
filenames = os.listdir(glyphsDir)
|
||||||
filenames = sorted(filenames)
|
filenames = sorted(filenames)
|
||||||
@@ -440,7 +443,7 @@ def generateBook(bookDir, raw, fixedimage):
|
|||||||
counter = 0
|
counter = 0
|
||||||
for filename in filenames:
|
for filename in filenames:
|
||||||
# print ' ', filename
|
# print ' ', filename
|
||||||
print '.',
|
print('.', end=' ')
|
||||||
fname = os.path.join(glyphsDir,filename)
|
fname = os.path.join(glyphsDir,filename)
|
||||||
flat_xml = convert2xml.fromData(dict, fname)
|
flat_xml = convert2xml.fromData(dict, fname)
|
||||||
|
|
||||||
@@ -459,7 +462,7 @@ def generateBook(bookDir, raw, fixedimage):
|
|||||||
glyfile.write('</defs>\n')
|
glyfile.write('</defs>\n')
|
||||||
glyfile.write('</svg>\n')
|
glyfile.write('</svg>\n')
|
||||||
glyfile.close()
|
glyfile.close()
|
||||||
print " "
|
print(" ")
|
||||||
|
|
||||||
|
|
||||||
# start up the html
|
# start up the html
|
||||||
@@ -481,7 +484,7 @@ def generateBook(bookDir, raw, fixedimage):
|
|||||||
hlst.append('<link href="style.css" rel="stylesheet" type="text/css" />\n')
|
hlst.append('<link href="style.css" rel="stylesheet" type="text/css" />\n')
|
||||||
hlst.append('</head>\n<body>\n')
|
hlst.append('</head>\n<body>\n')
|
||||||
|
|
||||||
print 'Processing Pages'
|
print('Processing Pages')
|
||||||
# Books are at 1440 DPI. This is rendering at twice that size for
|
# Books are at 1440 DPI. This is rendering at twice that size for
|
||||||
# readability when rendering to the screen.
|
# readability when rendering to the screen.
|
||||||
scaledpi = 1440.0
|
scaledpi = 1440.0
|
||||||
@@ -495,7 +498,7 @@ def generateBook(bookDir, raw, fixedimage):
|
|||||||
|
|
||||||
for filename in filenames:
|
for filename in filenames:
|
||||||
# print ' ', filename
|
# print ' ', filename
|
||||||
print ".",
|
print(".", end=' ')
|
||||||
fname = os.path.join(pageDir,filename)
|
fname = os.path.join(pageDir,filename)
|
||||||
flat_xml = convert2xml.fromData(dict, fname)
|
flat_xml = convert2xml.fromData(dict, fname)
|
||||||
|
|
||||||
@@ -517,8 +520,8 @@ def generateBook(bookDir, raw, fixedimage):
|
|||||||
hlst = None
|
hlst = None
|
||||||
file(os.path.join(bookDir, htmlFileName), 'wb').write(htmlstr)
|
file(os.path.join(bookDir, htmlFileName), 'wb').write(htmlstr)
|
||||||
|
|
||||||
print " "
|
print(" ")
|
||||||
print 'Extracting Table of Contents from Amazon OCR'
|
print('Extracting Table of Contents from Amazon OCR')
|
||||||
|
|
||||||
# first create a table of contents file for the svg images
|
# first create a table of contents file for the svg images
|
||||||
tlst = []
|
tlst = []
|
||||||
@@ -550,7 +553,7 @@ def generateBook(bookDir, raw, fixedimage):
|
|||||||
toclst = tocentries.split('\n')
|
toclst = tocentries.split('\n')
|
||||||
toclst.pop()
|
toclst.pop()
|
||||||
for entry in toclst:
|
for entry in toclst:
|
||||||
print entry
|
print(entry)
|
||||||
title, pagenum = entry.split('|')
|
title, pagenum = entry.split('|')
|
||||||
id = pageidnums[int(pagenum)]
|
id = pageidnums[int(pagenum)]
|
||||||
if (raw):
|
if (raw):
|
||||||
@@ -580,7 +583,7 @@ def generateBook(bookDir, raw, fixedimage):
|
|||||||
slst.append('</head>\n')
|
slst.append('</head>\n')
|
||||||
slst.append('<body>\n')
|
slst.append('<body>\n')
|
||||||
|
|
||||||
print "Building svg images of each book page"
|
print("Building svg images of each book page")
|
||||||
slst.append('<h2>List of Pages</h2>\n')
|
slst.append('<h2>List of Pages</h2>\n')
|
||||||
slst.append('<div>\n')
|
slst.append('<div>\n')
|
||||||
idlst = sorted(pageIDMap.keys())
|
idlst = sorted(pageIDMap.keys())
|
||||||
@@ -593,7 +596,7 @@ def generateBook(bookDir, raw, fixedimage):
|
|||||||
nextid = idlst[j+1]
|
nextid = idlst[j+1]
|
||||||
else:
|
else:
|
||||||
nextid = None
|
nextid = None
|
||||||
print '.',
|
print('.', end=' ')
|
||||||
pagelst = pageIDMap[pageid]
|
pagelst = pageIDMap[pageid]
|
||||||
flst = []
|
flst = []
|
||||||
for page in pagelst:
|
for page in pagelst:
|
||||||
@@ -618,7 +621,7 @@ def generateBook(bookDir, raw, fixedimage):
|
|||||||
slst = None
|
slst = None
|
||||||
file(os.path.join(bookDir, 'index_svg.xhtml'), 'wb').write(svgindex)
|
file(os.path.join(bookDir, 'index_svg.xhtml'), 'wb').write(svgindex)
|
||||||
|
|
||||||
print " "
|
print(" ")
|
||||||
|
|
||||||
# build the opf file
|
# build the opf file
|
||||||
opfname = os.path.join(bookDir, 'book.opf')
|
opfname = os.path.join(bookDir, 'book.opf')
|
||||||
@@ -667,20 +670,20 @@ def generateBook(bookDir, raw, fixedimage):
|
|||||||
olst = None
|
olst = None
|
||||||
file(opfname, 'wb').write(opfstr)
|
file(opfname, 'wb').write(opfstr)
|
||||||
|
|
||||||
print 'Processing Complete'
|
print('Processing Complete')
|
||||||
|
|
||||||
return 0
|
return 0
|
||||||
|
|
||||||
def usage():
|
def usage():
|
||||||
print "genbook.py generates a book from the extract Topaz Files"
|
print("genbook.py generates a book from the extract Topaz Files")
|
||||||
print "Usage:"
|
print("Usage:")
|
||||||
print " genbook.py [-r] [-h [--fixed-image] <bookDir> "
|
print(" genbook.py [-r] [-h [--fixed-image] <bookDir> ")
|
||||||
print " "
|
print(" ")
|
||||||
print "Options:"
|
print("Options:")
|
||||||
print " -h : help - print this usage message"
|
print(" -h : help - print this usage message")
|
||||||
print " -r : generate raw svg files (not wrapped in xhtml)"
|
print(" -r : generate raw svg files (not wrapped in xhtml)")
|
||||||
print " --fixed-image : genearate any Fixed Area as an svg image in the html"
|
print(" --fixed-image : genearate any Fixed Area as an svg image in the html")
|
||||||
print " "
|
print(" ")
|
||||||
|
|
||||||
|
|
||||||
def main(argv):
|
def main(argv):
|
||||||
@@ -692,7 +695,7 @@ def main(argv):
|
|||||||
opts, args = getopt.getopt(argv[1:], "rh:",["fixed-image"])
|
opts, args = getopt.getopt(argv[1:], "rh:",["fixed-image"])
|
||||||
|
|
||||||
except getopt.GetoptError, err:
|
except getopt.GetoptError, err:
|
||||||
print str(err)
|
print(str(err))
|
||||||
usage()
|
usage()
|
||||||
return 1
|
return 1
|
||||||
|
|
||||||
@@ -41,6 +41,7 @@ from __future__ import with_statement
|
|||||||
"""
|
"""
|
||||||
Decrypt Barnes & Noble encrypted ePub books.
|
Decrypt Barnes & Noble encrypted ePub books.
|
||||||
"""
|
"""
|
||||||
|
from __future__ import print_function
|
||||||
|
|
||||||
__license__ = 'GPL v3'
|
__license__ = 'GPL v3'
|
||||||
__version__ = "4.1"
|
__version__ = "4.1"
|
||||||
@@ -262,7 +263,7 @@ def decryptBook(keyb64, inpath, outpath):
|
|||||||
namelist = set(inf.namelist())
|
namelist = set(inf.namelist())
|
||||||
if 'META-INF/rights.xml' not in namelist or \
|
if 'META-INF/rights.xml' not in namelist or \
|
||||||
'META-INF/encryption.xml' not in namelist:
|
'META-INF/encryption.xml' not in namelist:
|
||||||
print u"{0:s} is DRM-free.".format(os.path.basename(inpath))
|
print(u"{0:s} is DRM-free.".format(os.path.basename(inpath)))
|
||||||
return 1
|
return 1
|
||||||
for name in META_NAMES:
|
for name in META_NAMES:
|
||||||
namelist.remove(name)
|
namelist.remove(name)
|
||||||
@@ -272,7 +273,7 @@ def decryptBook(keyb64, inpath, outpath):
|
|||||||
expr = './/%s' % (adept('encryptedKey'),)
|
expr = './/%s' % (adept('encryptedKey'),)
|
||||||
bookkey = ''.join(rights.findtext(expr))
|
bookkey = ''.join(rights.findtext(expr))
|
||||||
if len(bookkey) != 64:
|
if len(bookkey) != 64:
|
||||||
print u"{0:s} is not a secure Barnes & Noble ePub.".format(os.path.basename(inpath))
|
print(u"{0:s} is not a secure Barnes & Noble ePub.".format(os.path.basename(inpath)))
|
||||||
return 1
|
return 1
|
||||||
bookkey = aes.decrypt(bookkey.decode('base64'))
|
bookkey = aes.decrypt(bookkey.decode('base64'))
|
||||||
bookkey = bookkey[:-ord(bookkey[-1])]
|
bookkey = bookkey[:-ord(bookkey[-1])]
|
||||||
@@ -315,7 +316,7 @@ def decryptBook(keyb64, inpath, outpath):
|
|||||||
pass
|
pass
|
||||||
outf.writestr(zi, decryptor.decrypt(path, data))
|
outf.writestr(zi, decryptor.decrypt(path, data))
|
||||||
except:
|
except:
|
||||||
print u"Could not decrypt {0:s} because of an exception:\n{1:s}".format(os.path.basename(inpath), traceback.format_exc())
|
print(u"Could not decrypt {0:s} because of an exception:\n{1:s}".format(os.path.basename(inpath), traceback.format_exc()))
|
||||||
return 2
|
return 2
|
||||||
return 0
|
return 0
|
||||||
|
|
||||||
@@ -326,13 +327,13 @@ def cli_main():
|
|||||||
argv=unicode_argv()
|
argv=unicode_argv()
|
||||||
progname = os.path.basename(argv[0])
|
progname = os.path.basename(argv[0])
|
||||||
if len(argv) != 4:
|
if len(argv) != 4:
|
||||||
print u"usage: {0} <keyfile.b64> <inbook.epub> <outbook.epub>".format(progname)
|
print(u"usage: {0} <keyfile.b64> <inbook.epub> <outbook.epub>".format(progname))
|
||||||
return 1
|
return 1
|
||||||
keypath, inpath, outpath = argv[1:]
|
keypath, inpath, outpath = argv[1:]
|
||||||
userkey = open(keypath,'rb').read()
|
userkey = open(keypath,'rb').read()
|
||||||
result = decryptBook(userkey, inpath, outpath)
|
result = decryptBook(userkey, inpath, outpath)
|
||||||
if result == 0:
|
if result == 0:
|
||||||
print u"Successfully decrypted {0:s} as {1:s}".format(os.path.basename(inpath),os.path.basename(outpath))
|
print(u"Successfully decrypted {0:s} as {1:s}".format(os.path.basename(inpath),os.path.basename(outpath)))
|
||||||
return result
|
return result
|
||||||
|
|
||||||
def gui_main():
|
def gui_main():
|
||||||
@@ -18,6 +18,7 @@ from __future__ import with_statement
|
|||||||
"""
|
"""
|
||||||
Get Barnes & Noble EPUB user key from nook Studio log file
|
Get Barnes & Noble EPUB user key from nook Studio log file
|
||||||
"""
|
"""
|
||||||
|
from __future__ import print_function
|
||||||
|
|
||||||
__license__ = 'GPL v3'
|
__license__ = 'GPL v3'
|
||||||
__version__ = "1.1"
|
__version__ = "1.1"
|
||||||
@@ -198,7 +199,7 @@ def nookkeys(files = []):
|
|||||||
for file in files:
|
for file in files:
|
||||||
fileKeys = getKeysFromLog(file)
|
fileKeys = getKeysFromLog(file)
|
||||||
if fileKeys:
|
if fileKeys:
|
||||||
print u"Found {0} keys in the Nook Study log files".format(len(fileKeys))
|
print(u"Found {0} keys in the Nook Study log files".format(len(fileKeys)))
|
||||||
keys.extend(fileKeys)
|
keys.extend(fileKeys)
|
||||||
return list(set(keys))
|
return list(set(keys))
|
||||||
|
|
||||||
@@ -211,7 +212,7 @@ def getkey(outpath, files=[]):
|
|||||||
outfile = outpath
|
outfile = outpath
|
||||||
with file(outfile, 'w') as keyfileout:
|
with file(outfile, 'w') as keyfileout:
|
||||||
keyfileout.write(keys[-1])
|
keyfileout.write(keys[-1])
|
||||||
print u"Saved a key to {0}".format(outfile)
|
print(u"Saved a key to {0}".format(outfile))
|
||||||
else:
|
else:
|
||||||
keycount = 0
|
keycount = 0
|
||||||
for key in keys:
|
for key in keys:
|
||||||
@@ -222,16 +223,16 @@ def getkey(outpath, files=[]):
|
|||||||
break
|
break
|
||||||
with file(outfile, 'w') as keyfileout:
|
with file(outfile, 'w') as keyfileout:
|
||||||
keyfileout.write(key)
|
keyfileout.write(key)
|
||||||
print u"Saved a key to {0}".format(outfile)
|
print(u"Saved a key to {0}".format(outfile))
|
||||||
return True
|
return True
|
||||||
return False
|
return False
|
||||||
|
|
||||||
def usage(progname):
|
def usage(progname):
|
||||||
print u"Finds the nook Study encryption keys."
|
print(u"Finds the nook Study encryption keys.")
|
||||||
print u"Keys are saved to the current directory, or a specified output directory."
|
print(u"Keys are saved to the current directory, or a specified output directory.")
|
||||||
print u"If a file name is passed instead of a directory, only the first key is saved, in that file."
|
print(u"If a file name is passed instead of a directory, only the first key is saved, in that file.")
|
||||||
print u"Usage:"
|
print(u"Usage:")
|
||||||
print u" {0:s} [-h] [-k <logFile>] [<outpath>]".format(progname)
|
print(u" {0:s} [-h] [-k <logFile>] [<outpath>]".format(progname))
|
||||||
|
|
||||||
|
|
||||||
def cli_main():
|
def cli_main():
|
||||||
@@ -239,12 +240,12 @@ def cli_main():
|
|||||||
sys.stderr=SafeUnbuffered(sys.stderr)
|
sys.stderr=SafeUnbuffered(sys.stderr)
|
||||||
argv=unicode_argv()
|
argv=unicode_argv()
|
||||||
progname = os.path.basename(argv[0])
|
progname = os.path.basename(argv[0])
|
||||||
print u"{0} v{1}\nCopyright © 2015 Apprentice Alf".format(progname,__version__)
|
print(u"{0} v{1}\nCopyright © 2015 Apprentice Alf".format(progname,__version__))
|
||||||
|
|
||||||
try:
|
try:
|
||||||
opts, args = getopt.getopt(argv[1:], "hk:")
|
opts, args = getopt.getopt(argv[1:], "hk:")
|
||||||
except getopt.GetoptError, err:
|
except getopt.GetoptError, err:
|
||||||
print u"Error in options or arguments: {0}".format(err.args[0])
|
print(u"Error in options or arguments: {0}".format(err.args[0]))
|
||||||
usage(progname)
|
usage(progname)
|
||||||
sys.exit(2)
|
sys.exit(2)
|
||||||
|
|
||||||
@@ -273,7 +274,7 @@ def cli_main():
|
|||||||
outpath = os.path.realpath(os.path.normpath(outpath))
|
outpath = os.path.realpath(os.path.normpath(outpath))
|
||||||
|
|
||||||
if not getkey(outpath, files):
|
if not getkey(outpath, files):
|
||||||
print u"Could not retrieve nook Study key."
|
print(u"Could not retrieve nook Study key.")
|
||||||
return 0
|
return 0
|
||||||
|
|
||||||
|
|
||||||
@@ -307,7 +308,7 @@ def gui_main():
|
|||||||
keys = nookkeys()
|
keys = nookkeys()
|
||||||
keycount = 0
|
keycount = 0
|
||||||
for key in keys:
|
for key in keys:
|
||||||
print key
|
print(key)
|
||||||
while True:
|
while True:
|
||||||
keycount += 1
|
keycount += 1
|
||||||
outfile = os.path.join(progpath,u"nookkey{0:d}.b64".format(keycount))
|
outfile = os.path.join(progpath,u"nookkey{0:d}.b64".format(keycount))
|
||||||
@@ -28,6 +28,7 @@ from __future__ import with_statement
|
|||||||
"""
|
"""
|
||||||
Fetch Barnes & Noble EPUB user key from B&N servers using email and password
|
Fetch Barnes & Noble EPUB user key from B&N servers using email and password
|
||||||
"""
|
"""
|
||||||
|
from __future__ import print_function
|
||||||
|
|
||||||
__license__ = 'GPL v3'
|
__license__ = 'GPL v3'
|
||||||
__version__ = "1.1"
|
__version__ = "1.1"
|
||||||
@@ -155,14 +156,14 @@ def cli_main():
|
|||||||
argv=unicode_argv()
|
argv=unicode_argv()
|
||||||
progname = os.path.basename(argv[0])
|
progname = os.path.basename(argv[0])
|
||||||
if len(argv) != 4:
|
if len(argv) != 4:
|
||||||
print u"usage: {0} <email> <password> <keyfileout.b64>".format(progname)
|
print(u"usage: {0} <email> <password> <keyfileout.b64>".format(progname))
|
||||||
return 1
|
return 1
|
||||||
email, password, keypath = argv[1:]
|
email, password, keypath = argv[1:]
|
||||||
userkey = fetch_key(email, password)
|
userkey = fetch_key(email, password)
|
||||||
if len(userkey) == 28:
|
if len(userkey) == 28:
|
||||||
open(keypath,'wb').write(userkey)
|
open(keypath,'wb').write(userkey)
|
||||||
return 0
|
return 0
|
||||||
print u"Failed to fetch key."
|
print(u"Failed to fetch key.")
|
||||||
return 1
|
return 1
|
||||||
|
|
||||||
|
|
||||||
@@ -38,6 +38,7 @@ from __future__ import with_statement
|
|||||||
"""
|
"""
|
||||||
Generate Barnes & Noble EPUB user key from name and credit card number.
|
Generate Barnes & Noble EPUB user key from name and credit card number.
|
||||||
"""
|
"""
|
||||||
|
from __future__ import print_function
|
||||||
|
|
||||||
__license__ = 'GPL v3'
|
__license__ = 'GPL v3'
|
||||||
__version__ = "2.8"
|
__version__ = "2.8"
|
||||||
@@ -223,12 +224,12 @@ def cli_main():
|
|||||||
argv=unicode_argv()
|
argv=unicode_argv()
|
||||||
progname = os.path.basename(argv[0])
|
progname = os.path.basename(argv[0])
|
||||||
if AES is None:
|
if AES is None:
|
||||||
print "%s: This script requires OpenSSL or PyCrypto, which must be installed " \
|
print("%s: This script requires OpenSSL or PyCrypto, which must be installed " \
|
||||||
"separately. Read the top-of-script comment for details." % \
|
"separately. Read the top-of-script comment for details." % \
|
||||||
(progname,)
|
(progname,))
|
||||||
return 1
|
return 1
|
||||||
if len(argv) != 4:
|
if len(argv) != 4:
|
||||||
print u"usage: {0} <Name> <CC#> <keyfileout.b64>".format(progname)
|
print(u"usage: {0} <Name> <CC#> <keyfileout.b64>".format(progname))
|
||||||
return 1
|
return 1
|
||||||
name, ccn, keypath = argv[1:]
|
name, ccn, keypath = argv[1:]
|
||||||
userkey = generate_key(name, ccn)
|
userkey = generate_key(name, ccn)
|
||||||
2168
DeDRM_plugin/ignoblepdf.py
Normal file
2168
DeDRM_plugin/ignoblepdf.py
Normal file
File diff suppressed because it is too large
Load Diff
@@ -2,25 +2,18 @@
|
|||||||
# -*- coding: utf-8 -*-
|
# -*- coding: utf-8 -*-
|
||||||
|
|
||||||
from __future__ import with_statement
|
from __future__ import with_statement
|
||||||
|
from __future__ import absolute_import
|
||||||
|
from __future__ import print_function
|
||||||
|
|
||||||
# ineptepub.pyw, version 6.6
|
# ineptepub.pyw, version 6.6
|
||||||
# Copyright © 2009-2010 by i♥cabbages
|
# Copyright © 2009-2020 by Apprentice Harper et al.
|
||||||
|
|
||||||
# Released under the terms of the GNU General Public Licence, version 3
|
# Released under the terms of the GNU General Public Licence, version 3
|
||||||
# <http://www.gnu.org/licenses/>
|
# <http://www.gnu.org/licenses/>
|
||||||
|
|
||||||
|
# Original script by i♥cabbages
|
||||||
# Modified 2010–2013 by some_updates, DiapDealer and Apprentice Alf
|
# Modified 2010–2013 by some_updates, DiapDealer and Apprentice Alf
|
||||||
# Modified 2015–2017 by Apprentice Harper
|
# Modified 2015–2020 by Apprentice Harper et al.
|
||||||
|
|
||||||
# Windows users: Before running this program, you must first install Python 2.7
|
|
||||||
# from <http://www.python.org/download/> and PyCrypto from
|
|
||||||
# <http://www.voidspace.org.uk/python/modules.shtml#pycrypto> (make sure to
|
|
||||||
# install the version for Python 2.7). Save this script file as
|
|
||||||
# ineptepub.pyw and double-click on it to run it.
|
|
||||||
#
|
|
||||||
# Mac OS X users: Save this script file as ineptepub.pyw. You can run this
|
|
||||||
# program from the command line (pythonw ineptepub.pyw) or by double-clicking
|
|
||||||
# it when it has been associated with PythonLauncher.
|
|
||||||
|
|
||||||
# Revision history:
|
# Revision history:
|
||||||
# 1 - Initial release
|
# 1 - Initial release
|
||||||
@@ -43,14 +36,17 @@ from __future__ import with_statement
|
|||||||
# 6.4 - Remove erroneous check on DER file sanity
|
# 6.4 - Remove erroneous check on DER file sanity
|
||||||
# 6.5 - Completely remove erroneous check on DER file sanity
|
# 6.5 - Completely remove erroneous check on DER file sanity
|
||||||
# 6.6 - Import tkFileDialog, don't assume something else will import it.
|
# 6.6 - Import tkFileDialog, don't assume something else will import it.
|
||||||
|
# 6.7 - Add Python 3 compatibility while still working with Python 2.7
|
||||||
|
|
||||||
"""
|
"""
|
||||||
Decrypt Adobe Digital Editions encrypted ePub books.
|
Decrypt Adobe Digital Editions encrypted ePub books.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
__license__ = 'GPL v3'
|
__license__ = 'GPL v3'
|
||||||
__version__ = "6.6"
|
__version__ = "6.7"
|
||||||
|
|
||||||
|
import six
|
||||||
|
from six.moves import range
|
||||||
import sys
|
import sys
|
||||||
import os
|
import os
|
||||||
import traceback
|
import traceback
|
||||||
@@ -59,6 +55,7 @@ import zipfile
|
|||||||
from zipfile import ZipInfo, ZipFile, ZIP_STORED, ZIP_DEFLATED
|
from zipfile import ZipInfo, ZipFile, ZIP_STORED, ZIP_DEFLATED
|
||||||
from contextlib import closing
|
from contextlib import closing
|
||||||
import xml.etree.ElementTree as etree
|
import xml.etree.ElementTree as etree
|
||||||
|
import base64
|
||||||
|
|
||||||
# Wrap a stream so that output gets flushed immediately
|
# Wrap a stream so that output gets flushed immediately
|
||||||
# and also make sure that any unicode strings get
|
# and also make sure that any unicode strings get
|
||||||
@@ -70,7 +67,7 @@ class SafeUnbuffered:
|
|||||||
if self.encoding == None:
|
if self.encoding == None:
|
||||||
self.encoding = "utf-8"
|
self.encoding = "utf-8"
|
||||||
def write(self, data):
|
def write(self, data):
|
||||||
if isinstance(data,unicode):
|
if isinstance(data,six.text_type):
|
||||||
data = data.encode(self.encoding,"replace")
|
data = data.encode(self.encoding,"replace")
|
||||||
self.stream.write(data)
|
self.stream.write(data)
|
||||||
self.stream.flush()
|
self.stream.flush()
|
||||||
@@ -111,13 +108,13 @@ def unicode_argv():
|
|||||||
# Remove Python executable and commands if present
|
# Remove Python executable and commands if present
|
||||||
start = argc.value - len(sys.argv)
|
start = argc.value - len(sys.argv)
|
||||||
return [argv[i] for i in
|
return [argv[i] for i in
|
||||||
xrange(start, argc.value)]
|
range(start, argc.value)]
|
||||||
return [u"ineptepub.py"]
|
return [u"ineptepub.py"]
|
||||||
else:
|
else:
|
||||||
argvencoding = sys.stdin.encoding
|
argvencoding = sys.stdin.encoding
|
||||||
if argvencoding == None:
|
if argvencoding == None:
|
||||||
argvencoding = "utf-8"
|
argvencoding = "utf-8"
|
||||||
return [arg if (type(arg) == unicode) else unicode(arg,argvencoding) for arg in sys.argv]
|
return [arg if (type(arg) == six.text_type) else six.text_type(arg,argvencoding) for arg in sys.argv]
|
||||||
|
|
||||||
|
|
||||||
class ADEPTError(Exception):
|
class ADEPTError(Exception):
|
||||||
@@ -205,7 +202,7 @@ def _load_crypto_libcrypto():
|
|||||||
|
|
||||||
def decrypt(self, data):
|
def decrypt(self, data):
|
||||||
out = create_string_buffer(len(data))
|
out = create_string_buffer(len(data))
|
||||||
iv = ("\x00" * self._blocksize)
|
iv = (b"\x00" * self._blocksize)
|
||||||
rv = AES_cbc_encrypt(data, out, len(data), self._key, iv, 0)
|
rv = AES_cbc_encrypt(data, out, len(data), self._key, iv, 0)
|
||||||
if rv == 0:
|
if rv == 0:
|
||||||
raise ADEPTError('AES decryption failed')
|
raise ADEPTError('AES decryption failed')
|
||||||
@@ -315,12 +312,12 @@ def _load_crypto_pycrypto():
|
|||||||
class RSA(object):
|
class RSA(object):
|
||||||
def __init__(self, der):
|
def __init__(self, der):
|
||||||
key = ASN1Parser([ord(x) for x in der])
|
key = ASN1Parser([ord(x) for x in der])
|
||||||
key = [key.getChild(x).value for x in xrange(1, 4)]
|
key = [key.getChild(x).value for x in range(1, 4)]
|
||||||
key = [self.bytesToNumber(v) for v in key]
|
key = [self.bytesToNumber(v) for v in key]
|
||||||
self._rsa = _RSA.construct(key)
|
self._rsa = _RSA.construct(key)
|
||||||
|
|
||||||
def bytesToNumber(self, bytes):
|
def bytesToNumber(self, bytes):
|
||||||
total = 0L
|
total = 0
|
||||||
for byte in bytes:
|
for byte in bytes:
|
||||||
total = (total << 8) + byte
|
total = (total << 8) + byte
|
||||||
return total
|
return total
|
||||||
@@ -366,7 +363,7 @@ class Decryptor(object):
|
|||||||
def decompress(self, bytes):
|
def decompress(self, bytes):
|
||||||
dc = zlib.decompressobj(-15)
|
dc = zlib.decompressobj(-15)
|
||||||
bytes = dc.decompress(bytes)
|
bytes = dc.decompress(bytes)
|
||||||
ex = dc.decompress('Z') + dc.flush()
|
ex = dc.decompress(b'Z') + dc.flush()
|
||||||
if ex:
|
if ex:
|
||||||
bytes = bytes + ex
|
bytes = bytes + ex
|
||||||
return bytes
|
return bytes
|
||||||
@@ -374,7 +371,11 @@ class Decryptor(object):
|
|||||||
def decrypt(self, path, data):
|
def decrypt(self, path, data):
|
||||||
if path.encode('utf-8') in self._encrypted:
|
if path.encode('utf-8') in self._encrypted:
|
||||||
data = self._aes.decrypt(data)[16:]
|
data = self._aes.decrypt(data)[16:]
|
||||||
data = data[:-ord(data[-1])]
|
if type(data[-1]) != int:
|
||||||
|
place = ord(data[-1])
|
||||||
|
else:
|
||||||
|
place = data[-1]
|
||||||
|
data = data[:-place]
|
||||||
data = self.decompress(data)
|
data = self.decompress(data)
|
||||||
return data
|
return data
|
||||||
|
|
||||||
@@ -405,7 +406,7 @@ def decryptBook(userkey, inpath, outpath):
|
|||||||
namelist = set(inf.namelist())
|
namelist = set(inf.namelist())
|
||||||
if 'META-INF/rights.xml' not in namelist or \
|
if 'META-INF/rights.xml' not in namelist or \
|
||||||
'META-INF/encryption.xml' not in namelist:
|
'META-INF/encryption.xml' not in namelist:
|
||||||
print u"{0:s} is DRM-free.".format(os.path.basename(inpath))
|
print(u"{0:s} is DRM-free.".format(os.path.basename(inpath)))
|
||||||
return 1
|
return 1
|
||||||
for name in META_NAMES:
|
for name in META_NAMES:
|
||||||
namelist.remove(name)
|
namelist.remove(name)
|
||||||
@@ -415,12 +416,14 @@ def decryptBook(userkey, inpath, outpath):
|
|||||||
expr = './/%s' % (adept('encryptedKey'),)
|
expr = './/%s' % (adept('encryptedKey'),)
|
||||||
bookkey = ''.join(rights.findtext(expr))
|
bookkey = ''.join(rights.findtext(expr))
|
||||||
if len(bookkey) != 172:
|
if len(bookkey) != 172:
|
||||||
print u"{0:s} is not a secure Adobe Adept ePub.".format(os.path.basename(inpath))
|
print(u"{0:s} is not a secure Adobe Adept ePub.".format(os.path.basename(inpath)))
|
||||||
return 1
|
return 1
|
||||||
bookkey = rsa.decrypt(bookkey.decode('base64'))
|
bookkey = bookkey.encode('ascii')
|
||||||
|
bookkey = base64.b64decode(bookkey)
|
||||||
|
bookkey = rsa.decrypt(bookkey)
|
||||||
# Padded as per RSAES-PKCS1-v1_5
|
# Padded as per RSAES-PKCS1-v1_5
|
||||||
if bookkey[-17] != '\x00':
|
if bookkey[-17] != '\x00' and bookkey[-17] != 0:
|
||||||
print u"Could not decrypt {0:s}. Wrong key".format(os.path.basename(inpath))
|
print(u"Could not decrypt {0:s}. Wrong key".format(os.path.basename(inpath)))
|
||||||
return 2
|
return 2
|
||||||
encryption = inf.read('META-INF/encryption.xml')
|
encryption = inf.read('META-INF/encryption.xml')
|
||||||
decryptor = Decryptor(bookkey[-16:], encryption)
|
decryptor = Decryptor(bookkey[-16:], encryption)
|
||||||
@@ -461,7 +464,7 @@ def decryptBook(userkey, inpath, outpath):
|
|||||||
pass
|
pass
|
||||||
outf.writestr(zi, decryptor.decrypt(path, data))
|
outf.writestr(zi, decryptor.decrypt(path, data))
|
||||||
except:
|
except:
|
||||||
print u"Could not decrypt {0:s} because of an exception:\n{1:s}".format(os.path.basename(inpath), traceback.format_exc())
|
print(u"Could not decrypt {0:s} because of an exception:\n{1:s}".format(os.path.basename(inpath), traceback.format_exc()))
|
||||||
return 2
|
return 2
|
||||||
return 0
|
return 0
|
||||||
|
|
||||||
@@ -472,90 +475,90 @@ def cli_main():
|
|||||||
argv=unicode_argv()
|
argv=unicode_argv()
|
||||||
progname = os.path.basename(argv[0])
|
progname = os.path.basename(argv[0])
|
||||||
if len(argv) != 4:
|
if len(argv) != 4:
|
||||||
print u"usage: {0} <keyfile.der> <inbook.epub> <outbook.epub>".format(progname)
|
print(u"usage: {0} <keyfile.der> <inbook.epub> <outbook.epub>".format(progname))
|
||||||
return 1
|
return 1
|
||||||
keypath, inpath, outpath = argv[1:]
|
keypath, inpath, outpath = argv[1:]
|
||||||
userkey = open(keypath,'rb').read()
|
userkey = open(keypath,'rb').read()
|
||||||
result = decryptBook(userkey, inpath, outpath)
|
result = decryptBook(userkey, inpath, outpath)
|
||||||
if result == 0:
|
if result == 0:
|
||||||
print u"Successfully decrypted {0:s} as {1:s}".format(os.path.basename(inpath),os.path.basename(outpath))
|
print(u"Successfully decrypted {0:s} as {1:s}".format(os.path.basename(inpath),os.path.basename(outpath)))
|
||||||
return result
|
return result
|
||||||
|
|
||||||
def gui_main():
|
def gui_main():
|
||||||
try:
|
try:
|
||||||
import Tkinter
|
import six.moves.tkinter
|
||||||
import Tkconstants
|
import six.moves.tkinter_constants
|
||||||
import tkFileDialog
|
import six.moves.tkinter_filedialog
|
||||||
import tkMessageBox
|
import six.moves.tkinter_messagebox
|
||||||
import traceback
|
import traceback
|
||||||
except:
|
except:
|
||||||
return cli_main()
|
return cli_main()
|
||||||
|
|
||||||
class DecryptionDialog(Tkinter.Frame):
|
class DecryptionDialog(six.moves.tkinter.Frame):
|
||||||
def __init__(self, root):
|
def __init__(self, root):
|
||||||
Tkinter.Frame.__init__(self, root, border=5)
|
six.moves.tkinter.Frame.__init__(self, root, border=5)
|
||||||
self.status = Tkinter.Label(self, text=u"Select files for decryption")
|
self.status = six.moves.tkinter.Label(self, text=u"Select files for decryption")
|
||||||
self.status.pack(fill=Tkconstants.X, expand=1)
|
self.status.pack(fill=six.moves.tkinter_constants.X, expand=1)
|
||||||
body = Tkinter.Frame(self)
|
body = six.moves.tkinter.Frame(self)
|
||||||
body.pack(fill=Tkconstants.X, expand=1)
|
body.pack(fill=six.moves.tkinter_constants.X, expand=1)
|
||||||
sticky = Tkconstants.E + Tkconstants.W
|
sticky = six.moves.tkinter_constants.E + six.moves.tkinter_constants.W
|
||||||
body.grid_columnconfigure(1, weight=2)
|
body.grid_columnconfigure(1, weight=2)
|
||||||
Tkinter.Label(body, text=u"Key file").grid(row=0)
|
six.moves.tkinter.Label(body, text=u"Key file").grid(row=0)
|
||||||
self.keypath = Tkinter.Entry(body, width=30)
|
self.keypath = six.moves.tkinter.Entry(body, width=30)
|
||||||
self.keypath.grid(row=0, column=1, sticky=sticky)
|
self.keypath.grid(row=0, column=1, sticky=sticky)
|
||||||
if os.path.exists(u"adeptkey.der"):
|
if os.path.exists(u"adeptkey.der"):
|
||||||
self.keypath.insert(0, u"adeptkey.der")
|
self.keypath.insert(0, u"adeptkey.der")
|
||||||
button = Tkinter.Button(body, text=u"...", command=self.get_keypath)
|
button = six.moves.tkinter.Button(body, text=u"...", command=self.get_keypath)
|
||||||
button.grid(row=0, column=2)
|
button.grid(row=0, column=2)
|
||||||
Tkinter.Label(body, text=u"Input file").grid(row=1)
|
six.moves.tkinter.Label(body, text=u"Input file").grid(row=1)
|
||||||
self.inpath = Tkinter.Entry(body, width=30)
|
self.inpath = six.moves.tkinter.Entry(body, width=30)
|
||||||
self.inpath.grid(row=1, column=1, sticky=sticky)
|
self.inpath.grid(row=1, column=1, sticky=sticky)
|
||||||
button = Tkinter.Button(body, text=u"...", command=self.get_inpath)
|
button = six.moves.tkinter.Button(body, text=u"...", command=self.get_inpath)
|
||||||
button.grid(row=1, column=2)
|
button.grid(row=1, column=2)
|
||||||
Tkinter.Label(body, text=u"Output file").grid(row=2)
|
six.moves.tkinter.Label(body, text=u"Output file").grid(row=2)
|
||||||
self.outpath = Tkinter.Entry(body, width=30)
|
self.outpath = six.moves.tkinter.Entry(body, width=30)
|
||||||
self.outpath.grid(row=2, column=1, sticky=sticky)
|
self.outpath.grid(row=2, column=1, sticky=sticky)
|
||||||
button = Tkinter.Button(body, text=u"...", command=self.get_outpath)
|
button = six.moves.tkinter.Button(body, text=u"...", command=self.get_outpath)
|
||||||
button.grid(row=2, column=2)
|
button.grid(row=2, column=2)
|
||||||
buttons = Tkinter.Frame(self)
|
buttons = six.moves.tkinter.Frame(self)
|
||||||
buttons.pack()
|
buttons.pack()
|
||||||
botton = Tkinter.Button(
|
botton = six.moves.tkinter.Button(
|
||||||
buttons, text=u"Decrypt", width=10, command=self.decrypt)
|
buttons, text=u"Decrypt", width=10, command=self.decrypt)
|
||||||
botton.pack(side=Tkconstants.LEFT)
|
botton.pack(side=six.moves.tkinter_constants.LEFT)
|
||||||
Tkinter.Frame(buttons, width=10).pack(side=Tkconstants.LEFT)
|
six.moves.tkinter.Frame(buttons, width=10).pack(side=six.moves.tkinter_constants.LEFT)
|
||||||
button = Tkinter.Button(
|
button = six.moves.tkinter.Button(
|
||||||
buttons, text=u"Quit", width=10, command=self.quit)
|
buttons, text=u"Quit", width=10, command=self.quit)
|
||||||
button.pack(side=Tkconstants.RIGHT)
|
button.pack(side=six.moves.tkinter_constants.RIGHT)
|
||||||
|
|
||||||
def get_keypath(self):
|
def get_keypath(self):
|
||||||
keypath = tkFileDialog.askopenfilename(
|
keypath = six.moves.tkinter_filedialog.askopenfilename(
|
||||||
parent=None, title=u"Select Adobe Adept \'.der\' key file",
|
parent=None, title=u"Select Adobe Adept \'.der\' key file",
|
||||||
defaultextension=u".der",
|
defaultextension=u".der",
|
||||||
filetypes=[('Adobe Adept DER-encoded files', '.der'),
|
filetypes=[('Adobe Adept DER-encoded files', '.der'),
|
||||||
('All Files', '.*')])
|
('All Files', '.*')])
|
||||||
if keypath:
|
if keypath:
|
||||||
keypath = os.path.normpath(keypath)
|
keypath = os.path.normpath(keypath)
|
||||||
self.keypath.delete(0, Tkconstants.END)
|
self.keypath.delete(0, six.moves.tkinter_constants.END)
|
||||||
self.keypath.insert(0, keypath)
|
self.keypath.insert(0, keypath)
|
||||||
return
|
return
|
||||||
|
|
||||||
def get_inpath(self):
|
def get_inpath(self):
|
||||||
inpath = tkFileDialog.askopenfilename(
|
inpath = six.moves.tkinter_filedialog.askopenfilename(
|
||||||
parent=None, title=u"Select ADEPT-encrypted ePub file to decrypt",
|
parent=None, title=u"Select ADEPT-encrypted ePub file to decrypt",
|
||||||
defaultextension=u".epub", filetypes=[('ePub files', '.epub')])
|
defaultextension=u".epub", filetypes=[('ePub files', '.epub')])
|
||||||
if inpath:
|
if inpath:
|
||||||
inpath = os.path.normpath(inpath)
|
inpath = os.path.normpath(inpath)
|
||||||
self.inpath.delete(0, Tkconstants.END)
|
self.inpath.delete(0, six.moves.tkinter_constants.END)
|
||||||
self.inpath.insert(0, inpath)
|
self.inpath.insert(0, inpath)
|
||||||
return
|
return
|
||||||
|
|
||||||
def get_outpath(self):
|
def get_outpath(self):
|
||||||
outpath = tkFileDialog.asksaveasfilename(
|
outpath = six.moves.tkinter_filedialog.asksaveasfilename(
|
||||||
parent=None, title=u"Select unencrypted ePub file to produce",
|
parent=None, title=u"Select unencrypted ePub file to produce",
|
||||||
defaultextension=u".epub", filetypes=[('ePub files', '.epub')])
|
defaultextension=u".epub", filetypes=[('ePub files', '.epub')])
|
||||||
if outpath:
|
if outpath:
|
||||||
outpath = os.path.normpath(outpath)
|
outpath = os.path.normpath(outpath)
|
||||||
self.outpath.delete(0, Tkconstants.END)
|
self.outpath.delete(0, six.moves.tkinter_constants.END)
|
||||||
self.outpath.insert(0, outpath)
|
self.outpath.insert(0, outpath)
|
||||||
return
|
return
|
||||||
|
|
||||||
@@ -579,7 +582,7 @@ def gui_main():
|
|||||||
self.status['text'] = u"Decrypting..."
|
self.status['text'] = u"Decrypting..."
|
||||||
try:
|
try:
|
||||||
decrypt_status = decryptBook(userkey, inpath, outpath)
|
decrypt_status = decryptBook(userkey, inpath, outpath)
|
||||||
except Exception, e:
|
except Exception as e:
|
||||||
self.status['text'] = u"Error: {0}".format(e.args[0])
|
self.status['text'] = u"Error: {0}".format(e.args[0])
|
||||||
return
|
return
|
||||||
if decrypt_status == 0:
|
if decrypt_status == 0:
|
||||||
@@ -587,11 +590,11 @@ def gui_main():
|
|||||||
else:
|
else:
|
||||||
self.status['text'] = u"The was an error decrypting the file."
|
self.status['text'] = u"The was an error decrypting the file."
|
||||||
|
|
||||||
root = Tkinter.Tk()
|
root = six.moves.tkinter.Tk()
|
||||||
root.title(u"Adobe Adept ePub Decrypter v.{0}".format(__version__))
|
root.title(u"Adobe Adept ePub Decrypter v.{0}".format(__version__))
|
||||||
root.resizable(True, False)
|
root.resizable(True, False)
|
||||||
root.minsize(300, 0)
|
root.minsize(300, 0)
|
||||||
DecryptionDialog(root).pack(fill=Tkconstants.X, expand=1)
|
DecryptionDialog(root).pack(fill=six.moves.tkinter_constants.X, expand=1)
|
||||||
root.mainloop()
|
root.mainloop()
|
||||||
return 0
|
return 0
|
||||||
|
|
||||||
@@ -3,24 +3,14 @@
|
|||||||
|
|
||||||
from __future__ import with_statement
|
from __future__ import with_statement
|
||||||
|
|
||||||
# ineptpdf.pyw, version 8.0.6
|
# ineptpdf.py
|
||||||
# Copyright © 2009-2010 by i♥cabbages
|
# Copyright © 2009-2017 by Apprentice Harper et al.
|
||||||
|
|
||||||
# Released under the terms of the GNU General Public Licence, version 3
|
# Released under the terms of the GNU General Public Licence, version 3
|
||||||
# <http://www.gnu.org/licenses/>
|
# <http://www.gnu.org/licenses/>
|
||||||
|
|
||||||
# Modified 2010–2012 by some_updates, DiapDealer and Apprentice Alf
|
# Modified 2010–2012 by some_updates, DiapDealer and Apprentice Alf
|
||||||
# Modified 2015-2017 by Apprentice Harper
|
# Modified 2015-2017 by Apprentice Harper et al.
|
||||||
|
|
||||||
# Windows users: Before running this program, you must first install Python 2.7
|
|
||||||
# from <http://www.python.org/download/> and PyCrypto from
|
|
||||||
# <http://www.voidspace.org.uk/python/modules.shtml#pycrypto> (make sure to
|
|
||||||
# install the version for Python 2.7). Save this script file as
|
|
||||||
# ineptpdf.pyw and double-click on it to run it.
|
|
||||||
#
|
|
||||||
# Mac OS X users: Save this script file as ineptpdf.pyw. You can run this
|
|
||||||
# program from the command line (pythonw ineptpdf.pyw) or by double-clicking
|
|
||||||
# it when it has been associated with PythonLauncher.
|
|
||||||
|
|
||||||
# Revision history:
|
# Revision history:
|
||||||
# 1 - Initial release
|
# 1 - Initial release
|
||||||
@@ -1,10 +1,28 @@
|
|||||||
#!/usr/bin/env python
|
#!/usr/bin/env python
|
||||||
# -*- coding: utf-8 -*-
|
# -*- coding: utf-8 -*-
|
||||||
# Pascal implementation by lulzkabulz. Python translation by apprenticenaomi. DeDRM integration by anon.
|
|
||||||
# BinaryIon.pas + DrmIon.pas + IonSymbols.pas
|
|
||||||
|
|
||||||
from __future__ import with_statement
|
from __future__ import with_statement
|
||||||
|
|
||||||
|
# ion.py
|
||||||
|
# Copyright © 2013-2020 Apprentice Harper et al.
|
||||||
|
|
||||||
|
__license__ = 'GPL v3'
|
||||||
|
__version__ = '2.0'
|
||||||
|
|
||||||
|
# Revision history:
|
||||||
|
# Pascal implementation by lulzkabulz.
|
||||||
|
# BinaryIon.pas + DrmIon.pas + IonSymbols.pas
|
||||||
|
# 1.0 - Python translation by apprenticenaomi.
|
||||||
|
# 1.1 - DeDRM integration by anon.
|
||||||
|
# 1.2 - Added pylzma import fallback
|
||||||
|
# 1.3 - Fixed lzma support for calibre 4.6+
|
||||||
|
# 2.0 - VoucherEnvelope v2/v3 support by apprenticesakuya.
|
||||||
|
|
||||||
|
|
||||||
|
"""
|
||||||
|
Decrypt Kindle KFX files.
|
||||||
|
"""
|
||||||
|
|
||||||
import collections
|
import collections
|
||||||
import hashlib
|
import hashlib
|
||||||
import hmac
|
import hmac
|
||||||
@@ -21,19 +39,24 @@ from Crypto.Cipher import AES
|
|||||||
from Crypto.Util.py3compat import bchr, bord
|
from Crypto.Util.py3compat import bchr, bord
|
||||||
|
|
||||||
try:
|
try:
|
||||||
# lzma library from calibre 2.35.0 or later
|
# lzma library from calibre 4.6.0 or later
|
||||||
import lzma.lzma1 as calibre_lzma
|
import calibre_lzma.lzma1 as calibre_lzma
|
||||||
except ImportError:
|
except ImportError:
|
||||||
calibre_lzma = None
|
calibre_lzma = None
|
||||||
|
# lzma library from calibre 2.35.0 or later
|
||||||
try:
|
try:
|
||||||
import lzma
|
import lzma.lzma1 as calibre_lzma
|
||||||
except ImportError:
|
except ImportError:
|
||||||
# Need pip backports.lzma on Python <3.3
|
calibre_lzma = None
|
||||||
try:
|
try:
|
||||||
from backports import lzma
|
import lzma
|
||||||
except ImportError:
|
except ImportError:
|
||||||
# Windows-friendly choice: pylzma wheels
|
# Need pip backports.lzma on Python <3.3
|
||||||
import pylzma as lzma
|
try:
|
||||||
|
from backports import lzma
|
||||||
|
except ImportError:
|
||||||
|
# Windows-friendly choice: pylzma wheels
|
||||||
|
import pylzma as lzma
|
||||||
|
|
||||||
|
|
||||||
TID_NULL = 0
|
TID_NULL = 0
|
||||||
@@ -714,7 +737,8 @@ SYM_NAMES = [ 'com.amazon.drm.Envelope@1.0',
|
|||||||
'com.amazon.drm.EnvelopeMetadata@2.0',
|
'com.amazon.drm.EnvelopeMetadata@2.0',
|
||||||
'com.amazon.drm.EncryptedPage@2.0',
|
'com.amazon.drm.EncryptedPage@2.0',
|
||||||
'com.amazon.drm.PlainText@2.0', 'compression_algorithm',
|
'com.amazon.drm.PlainText@2.0', 'compression_algorithm',
|
||||||
'com.amazon.drm.Compressed@1.0', 'priority', 'refines']
|
'com.amazon.drm.Compressed@1.0', 'page_index_table',
|
||||||
|
'com.amazon.drm.VoucherEnvelope@2.0', 'com.amazon.drm.VoucherEnvelope@3.0' ]
|
||||||
|
|
||||||
def addprottable(ion):
|
def addprottable(ion):
|
||||||
ion.addtocatalog("ProtectedData", 1, SYM_NAMES)
|
ion.addtocatalog("ProtectedData", 1, SYM_NAMES)
|
||||||
@@ -736,8 +760,42 @@ def pkcs7unpad(msg, blocklen):
|
|||||||
return msg[:-paddinglen]
|
return msg[:-paddinglen]
|
||||||
|
|
||||||
|
|
||||||
|
# every VoucherEnvelope version has a corresponding "word" and magic number, used in obfuscating the shared secret
|
||||||
|
VOUCHER_VERSION_INFOS = {
|
||||||
|
2: [b'Antidisestablishmentarianism', 5],
|
||||||
|
3: [b'Floccinaucinihilipilification', 8]
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
# obfuscate shared secret according to the VoucherEnvelope version
|
||||||
|
def obfuscate(secret, version):
|
||||||
|
if version == 1: # v1 does not use obfuscation
|
||||||
|
return secret
|
||||||
|
|
||||||
|
params = VOUCHER_VERSION_INFOS[version]
|
||||||
|
word = params[0]
|
||||||
|
magic = params[1]
|
||||||
|
|
||||||
|
# extend secret so that its length is divisible by the magic number
|
||||||
|
if len(secret) % magic != 0:
|
||||||
|
secret = secret + b'\x00' * (magic - len(secret) % magic)
|
||||||
|
|
||||||
|
secret = bytearray(secret)
|
||||||
|
|
||||||
|
obfuscated = bytearray(len(secret))
|
||||||
|
wordhash = bytearray(hashlib.sha256(word).digest())
|
||||||
|
|
||||||
|
# shuffle secret and xor it with the first half of the word hash
|
||||||
|
for i in range(0, len(secret)):
|
||||||
|
index = i // (len(secret) // magic) + magic * (i % (len(secret) // magic))
|
||||||
|
obfuscated[index] = secret[i] ^ wordhash[index % 16]
|
||||||
|
|
||||||
|
return obfuscated
|
||||||
|
|
||||||
|
|
||||||
class DrmIonVoucher(object):
|
class DrmIonVoucher(object):
|
||||||
envelope = None
|
envelope = None
|
||||||
|
version = None
|
||||||
voucher = None
|
voucher = None
|
||||||
drmkey = None
|
drmkey = None
|
||||||
license_type = "Unknown"
|
license_type = "Unknown"
|
||||||
@@ -772,9 +830,9 @@ class DrmIonVoucher(object):
|
|||||||
else:
|
else:
|
||||||
_assert(False, "Unknown lock parameter: %s" % param)
|
_assert(False, "Unknown lock parameter: %s" % param)
|
||||||
|
|
||||||
sharedsecret = shared.encode("UTF-8")
|
sharedsecret = obfuscate(shared.encode('ASCII'), self.version)
|
||||||
|
|
||||||
key = hmac.new(sharedsecret, sharedsecret[:5], digestmod=hashlib.sha256).digest()
|
key = hmac.new(sharedsecret, "PIDv3", digestmod=hashlib.sha256).digest()
|
||||||
aes = AES.new(key[:32], AES.MODE_CBC, self.cipheriv[:16])
|
aes = AES.new(key[:32], AES.MODE_CBC, self.cipheriv[:16])
|
||||||
b = aes.decrypt(self.ciphertext)
|
b = aes.decrypt(self.ciphertext)
|
||||||
b = pkcs7unpad(b, 16)
|
b = pkcs7unpad(b, 16)
|
||||||
@@ -809,8 +867,9 @@ class DrmIonVoucher(object):
|
|||||||
def parse(self):
|
def parse(self):
|
||||||
self.envelope.reset()
|
self.envelope.reset()
|
||||||
_assert(self.envelope.hasnext(), "Envelope is empty")
|
_assert(self.envelope.hasnext(), "Envelope is empty")
|
||||||
_assert(self.envelope.next() == TID_STRUCT and self.envelope.gettypename() == "com.amazon.drm.VoucherEnvelope@1.0",
|
_assert(self.envelope.next() == TID_STRUCT and str.startswith(self.envelope.gettypename(), "com.amazon.drm.VoucherEnvelope@"),
|
||||||
"Unknown type encountered in envelope, expected VoucherEnvelope")
|
"Unknown type encountered in envelope, expected VoucherEnvelope")
|
||||||
|
self.version = int(self.envelope.gettypename().split('@')[1][:-2])
|
||||||
|
|
||||||
self.envelope.stepin()
|
self.envelope.stepin()
|
||||||
while self.envelope.hasnext():
|
while self.envelope.hasnext():
|
||||||
@@ -4,10 +4,10 @@
|
|||||||
from __future__ import with_statement
|
from __future__ import with_statement
|
||||||
|
|
||||||
# k4mobidedrm.py
|
# k4mobidedrm.py
|
||||||
# Copyright © 2008-2017 by Apprentice Harper et al.
|
# Copyright © 2008-2019 by Apprentice Harper et al.
|
||||||
|
|
||||||
__license__ = 'GPL v3'
|
__license__ = 'GPL v3'
|
||||||
__version__ = '5.5'
|
__version__ = '5.7'
|
||||||
|
|
||||||
# Engine to remove drm from Kindle and Mobipocket ebooks
|
# Engine to remove drm from Kindle and Mobipocket ebooks
|
||||||
# for personal use for archiving and converting your ebooks
|
# for personal use for archiving and converting your ebooks
|
||||||
@@ -60,7 +60,8 @@ __version__ = '5.5'
|
|||||||
# 5.3 - Changed Android support to allow passing of backup .ab files
|
# 5.3 - Changed Android support to allow passing of backup .ab files
|
||||||
# 5.4 - Recognise KFX files masquerading as azw, even if we can't decrypt them yet.
|
# 5.4 - Recognise KFX files masquerading as azw, even if we can't decrypt them yet.
|
||||||
# 5.5 - Added GPL v3 licence explicitly.
|
# 5.5 - Added GPL v3 licence explicitly.
|
||||||
# 5.x - Invoke KFXZipBook to handle zipped KFX files
|
# 5.6 - Invoke KFXZipBook to handle zipped KFX files
|
||||||
|
# 5.7 - Revamp cleanup_name
|
||||||
|
|
||||||
import sys, os, re
|
import sys, os, re
|
||||||
import csv
|
import csv
|
||||||
@@ -155,19 +156,24 @@ def unicode_argv():
|
|||||||
# added in removal of control (<32) chars
|
# added in removal of control (<32) chars
|
||||||
# and removal of . at start and end
|
# and removal of . at start and end
|
||||||
# and with some (heavily edited) code from Paul Durrant's kindlenamer.py
|
# and with some (heavily edited) code from Paul Durrant's kindlenamer.py
|
||||||
|
# and some improvements suggested by jhaisley
|
||||||
def cleanup_name(name):
|
def cleanup_name(name):
|
||||||
# substitute filename unfriendly characters
|
# substitute filename unfriendly characters
|
||||||
name = name.replace(u"<",u"[").replace(u">",u"]").replace(u" : ",u" – ").replace(u": ",u" – ").replace(u":",u"—").replace(u"/",u"_").replace(u"\\",u"_").replace(u"|",u"_").replace(u"\"",u"\'").replace(u"*",u"_").replace(u"?",u"")
|
name = name.replace(u"<",u"[").replace(u">",u"]").replace(u" : ",u" – ").replace(u": ",u" – ").replace(u":",u"—").replace(u"/",u"_").replace(u"\\",u"_").replace(u"|",u"_").replace(u"\"",u"\'").replace(u"*",u"_").replace(u"?",u"")
|
||||||
# delete control characters
|
|
||||||
name = u"".join(char for char in name if ord(char)>=32)
|
|
||||||
# white space to single space, delete leading and trailing while space
|
# white space to single space, delete leading and trailing while space
|
||||||
name = re.sub(ur"\s", u" ", name).strip()
|
name = re.sub(ur"\s", u" ", name).strip()
|
||||||
|
# delete control characters
|
||||||
|
name = u"".join(char for char in name if ord(char)>=32)
|
||||||
|
# delete non-ascii characters
|
||||||
|
name = u"".join(char for char in name if ord(char)<=126)
|
||||||
# remove leading dots
|
# remove leading dots
|
||||||
while len(name)>0 and name[0] == u".":
|
while len(name)>0 and name[0] == u".":
|
||||||
name = name[1:]
|
name = name[1:]
|
||||||
# remove trailing dots (Windows doesn't like them)
|
# remove trailing dots (Windows doesn't like them)
|
||||||
if name.endswith(u'.'):
|
while name.endswith(u'.'):
|
||||||
name = name[:-1]
|
name = name[:-1]
|
||||||
|
if len(name)==0:
|
||||||
|
name=u"DecryptedBook"
|
||||||
return name
|
return name
|
||||||
|
|
||||||
# must be passed unicode
|
# must be passed unicode
|
||||||
@@ -2,6 +2,7 @@
|
|||||||
# -*- coding: utf-8 -*-
|
# -*- coding: utf-8 -*-
|
||||||
|
|
||||||
from __future__ import with_statement
|
from __future__ import with_statement
|
||||||
|
from __future__ import print_function
|
||||||
|
|
||||||
# Engine to remove drm from Kindle KFX ebooks
|
# Engine to remove drm from Kindle KFX ebooks
|
||||||
|
|
||||||
@@ -43,7 +44,7 @@ class KFXZipBook:
|
|||||||
data += fh.read()
|
data += fh.read()
|
||||||
if self.voucher is None:
|
if self.voucher is None:
|
||||||
self.decrypt_voucher(totalpids)
|
self.decrypt_voucher(totalpids)
|
||||||
print u'Decrypting KFX DRMION: {0}'.format(filename)
|
print(u'Decrypting KFX DRMION: {0}'.format(filename))
|
||||||
outfile = StringIO()
|
outfile = StringIO()
|
||||||
ion.DrmIon(StringIO(data[8:-8]), lambda name: self.voucher).parse(outfile)
|
ion.DrmIon(StringIO(data[8:-8]), lambda name: self.voucher).parse(outfile)
|
||||||
self.decrypted[filename] = outfile.getvalue()
|
self.decrypted[filename] = outfile.getvalue()
|
||||||
@@ -65,10 +66,10 @@ class KFXZipBook:
|
|||||||
else:
|
else:
|
||||||
raise Exception(u'The .kfx-zip archive contains an encrypted DRMION file without a DRM voucher')
|
raise Exception(u'The .kfx-zip archive contains an encrypted DRMION file without a DRM voucher')
|
||||||
|
|
||||||
print u'Decrypting KFX DRM voucher: {0}'.format(info.filename)
|
print(u'Decrypting KFX DRM voucher: {0}'.format(info.filename))
|
||||||
|
|
||||||
for pid in [''] + totalpids:
|
for pid in [''] + totalpids:
|
||||||
for dsn_len,secret_len in [(0,0), (16,0), (16,40), (32,40), (40,40)]:
|
for dsn_len,secret_len in [(0,0), (16,0), (16,40), (32,40), (40,0), (40,40)]:
|
||||||
if len(pid) == dsn_len + secret_len:
|
if len(pid) == dsn_len + secret_len:
|
||||||
break # split pid into DSN and account secret
|
break # split pid into DSN and account secret
|
||||||
else:
|
else:
|
||||||
@@ -84,7 +85,7 @@ class KFXZipBook:
|
|||||||
else:
|
else:
|
||||||
raise Exception(u'Failed to decrypt KFX DRM voucher with any key')
|
raise Exception(u'Failed to decrypt KFX DRM voucher with any key')
|
||||||
|
|
||||||
print u'KFX DRM voucher successfully decrypted'
|
print(u'KFX DRM voucher successfully decrypted')
|
||||||
|
|
||||||
license_type = voucher.getlicensetype()
|
license_type = voucher.getlicensetype()
|
||||||
if license_type != "Purchase":
|
if license_type != "Purchase":
|
||||||
@@ -2,6 +2,7 @@
|
|||||||
# -*- coding: utf-8 -*-
|
# -*- coding: utf-8 -*-
|
||||||
|
|
||||||
from __future__ import with_statement
|
from __future__ import with_statement
|
||||||
|
from __future__ import print_function
|
||||||
|
|
||||||
# kgenpids.py
|
# kgenpids.py
|
||||||
# Copyright © 2008-2017 Apprentice Harper et al.
|
# Copyright © 2008-2017 Apprentice Harper et al.
|
||||||
@@ -214,7 +215,7 @@ def getK4Pids(rec209, token, kindleDatabase):
|
|||||||
try:
|
try:
|
||||||
# Get the DSN token, if present
|
# Get the DSN token, if present
|
||||||
DSN = (kindleDatabase[1])['DSN'].decode('hex')
|
DSN = (kindleDatabase[1])['DSN'].decode('hex')
|
||||||
print u"Got DSN key from database {0}".format(kindleDatabase[0])
|
print(u"Got DSN key from database {0}".format(kindleDatabase[0]))
|
||||||
except KeyError:
|
except KeyError:
|
||||||
# See if we have the info to generate the DSN
|
# See if we have the info to generate the DSN
|
||||||
try:
|
try:
|
||||||
@@ -225,7 +226,7 @@ def getK4Pids(rec209, token, kindleDatabase):
|
|||||||
try:
|
try:
|
||||||
# Get the SerialNumber token, if present
|
# Get the SerialNumber token, if present
|
||||||
IDString = (kindleDatabase[1])['SerialNumber'].decode('hex')
|
IDString = (kindleDatabase[1])['SerialNumber'].decode('hex')
|
||||||
print u"Got SerialNumber from database {0}".format(kindleDatabase[0])
|
print(u"Got SerialNumber from database {0}".format(kindleDatabase[0]))
|
||||||
except KeyError:
|
except KeyError:
|
||||||
# Get the IDString we added
|
# Get the IDString we added
|
||||||
IDString = (kindleDatabase[1])['IDString'].decode('hex')
|
IDString = (kindleDatabase[1])['IDString'].decode('hex')
|
||||||
@@ -233,7 +234,7 @@ def getK4Pids(rec209, token, kindleDatabase):
|
|||||||
try:
|
try:
|
||||||
# Get the UsernameHash token, if present
|
# Get the UsernameHash token, if present
|
||||||
encodedUsername = (kindleDatabase[1])['UsernameHash'].decode('hex')
|
encodedUsername = (kindleDatabase[1])['UsernameHash'].decode('hex')
|
||||||
print u"Got UsernameHash from database {0}".format(kindleDatabase[0])
|
print(u"Got UsernameHash from database {0}".format(kindleDatabase[0]))
|
||||||
except KeyError:
|
except KeyError:
|
||||||
# Get the UserName we added
|
# Get the UserName we added
|
||||||
UserName = (kindleDatabase[1])['UserName'].decode('hex')
|
UserName = (kindleDatabase[1])['UserName'].decode('hex')
|
||||||
@@ -241,7 +242,7 @@ def getK4Pids(rec209, token, kindleDatabase):
|
|||||||
encodedUsername = encodeHash(UserName,charMap1)
|
encodedUsername = encodeHash(UserName,charMap1)
|
||||||
#print u"encodedUsername",encodedUsername.encode('hex')
|
#print u"encodedUsername",encodedUsername.encode('hex')
|
||||||
except KeyError:
|
except KeyError:
|
||||||
print u"Keys not found in the database {0}.".format(kindleDatabase[0])
|
print(u"Keys not found in the database {0}.".format(kindleDatabase[0]))
|
||||||
return pids
|
return pids
|
||||||
|
|
||||||
# Get the ID string used
|
# Get the ID string used
|
||||||
@@ -297,14 +298,14 @@ def getPidList(md1, md2, serials=[], kDatabases=[]):
|
|||||||
try:
|
try:
|
||||||
pidlst.extend(getK4Pids(md1, md2, kDatabase))
|
pidlst.extend(getK4Pids(md1, md2, kDatabase))
|
||||||
except Exception, e:
|
except Exception, e:
|
||||||
print u"Error getting PIDs from database {0}: {1}".format(kDatabase[0],e.args[0])
|
print(u"Error getting PIDs from database {0}: {1}".format(kDatabase[0],e.args[0]))
|
||||||
traceback.print_exc()
|
traceback.print_exc()
|
||||||
|
|
||||||
for serialnum in serials:
|
for serialnum in serials:
|
||||||
try:
|
try:
|
||||||
pidlst.extend(getKindlePids(md1, md2, serialnum))
|
pidlst.extend(getKindlePids(md1, md2, serialnum))
|
||||||
except Exception, e:
|
except Exception, e:
|
||||||
print u"Error getting PIDs from serial number {0}: {1}".format(serialnum ,e.args[0])
|
print(u"Error getting PIDs from serial number {0}: {1}".format(serialnum ,e.args[0]))
|
||||||
traceback.print_exc()
|
traceback.print_exc()
|
||||||
|
|
||||||
return pidlst
|
return pidlst
|
||||||
@@ -4,10 +4,10 @@
|
|||||||
from __future__ import with_statement
|
from __future__ import with_statement
|
||||||
|
|
||||||
# kindlekey.py
|
# kindlekey.py
|
||||||
# Copyright © 2008-2017 Apprentice Harper et al.
|
# Copyright © 2008-2020 Apprentice Harper et al.
|
||||||
|
|
||||||
__license__ = 'GPL v3'
|
__license__ = 'GPL v3'
|
||||||
__version__ = '2.5'
|
__version__ = '2.8'
|
||||||
|
|
||||||
# Revision history:
|
# Revision history:
|
||||||
# 1.0 - Kindle info file decryption, extracted from k4mobidedrm, etc.
|
# 1.0 - Kindle info file decryption, extracted from k4mobidedrm, etc.
|
||||||
@@ -28,6 +28,9 @@ __version__ = '2.5'
|
|||||||
# 2.3 - Added more field names thanks to concavegit's KFX code.
|
# 2.3 - Added more field names thanks to concavegit's KFX code.
|
||||||
# 2.4 - Fix for complex Mac disk setups, thanks to Tibs
|
# 2.4 - Fix for complex Mac disk setups, thanks to Tibs
|
||||||
# 2.5 - Final Fix for Windows user names with non-ascii characters, thanks to oneofusoneofus
|
# 2.5 - Final Fix for Windows user names with non-ascii characters, thanks to oneofusoneofus
|
||||||
|
# 2.6 - Start adding support for Kindle 1.25+ .kinf2018 file
|
||||||
|
# 2.7 - Finish .kinf2018 support, PC & Mac by Apprentice Sakuya
|
||||||
|
# 2.8 - Fix for Mac OS X Big Sur
|
||||||
|
|
||||||
|
|
||||||
"""
|
"""
|
||||||
@@ -35,10 +38,16 @@ Retrieve Kindle for PC/Mac user key.
|
|||||||
"""
|
"""
|
||||||
|
|
||||||
import sys, os, re
|
import sys, os, re
|
||||||
from struct import pack, unpack, unpack_from
|
from struct import pack, unpack
|
||||||
import json
|
import json
|
||||||
import getopt
|
import getopt
|
||||||
|
|
||||||
|
try:
|
||||||
|
RegError
|
||||||
|
except NameError:
|
||||||
|
class RegError(Exception):
|
||||||
|
pass
|
||||||
|
|
||||||
# Routines common to Mac and PC
|
# Routines common to Mac and PC
|
||||||
|
|
||||||
# Wrap a stream so that output gets flushed immediately
|
# Wrap a stream so that output gets flushed immediately
|
||||||
@@ -200,7 +209,7 @@ if iswindows:
|
|||||||
Original Version
|
Original Version
|
||||||
Copyright (c) 2002 by Paul A. Lambert
|
Copyright (c) 2002 by Paul A. Lambert
|
||||||
Under:
|
Under:
|
||||||
CryptoPy Artisitic License Version 1.0
|
CryptoPy Artistic License Version 1.0
|
||||||
See the wonderful pure python package cryptopy-1.2.5
|
See the wonderful pure python package cryptopy-1.2.5
|
||||||
and read its LICENSE.txt for complete license details.
|
and read its LICENSE.txt for complete license details.
|
||||||
"""
|
"""
|
||||||
@@ -893,7 +902,7 @@ if iswindows:
|
|||||||
# double the buffer size
|
# double the buffer size
|
||||||
buffer = create_unicode_buffer(len(buffer) * 2)
|
buffer = create_unicode_buffer(len(buffer) * 2)
|
||||||
size.value = len(buffer)
|
size.value = len(buffer)
|
||||||
|
|
||||||
# replace any non-ASCII values with 0xfffd
|
# replace any non-ASCII values with 0xfffd
|
||||||
for i in xrange(0,len(buffer)):
|
for i in xrange(0,len(buffer)):
|
||||||
if buffer[i]>u"\u007f":
|
if buffer[i]>u"\u007f":
|
||||||
@@ -973,6 +982,13 @@ if iswindows:
|
|||||||
# Probably not the best. To Fix (shouldn't ignore in encoding) or use utf-8
|
# Probably not the best. To Fix (shouldn't ignore in encoding) or use utf-8
|
||||||
print(u'searching for kinfoFiles in ' + path.encode('ascii', 'ignore'))
|
print(u'searching for kinfoFiles in ' + path.encode('ascii', 'ignore'))
|
||||||
|
|
||||||
|
# look for (K4PC 1.25.1 and later) .kinf2018 file
|
||||||
|
kinfopath = path +'\\Amazon\\Kindle\\storage\\.kinf2018'
|
||||||
|
if os.path.isfile(kinfopath):
|
||||||
|
found = True
|
||||||
|
print('Found K4PC 1.25+ kinf2018 file: ' + kinfopath.encode('ascii','ignore'))
|
||||||
|
kInfoFiles.append(kinfopath)
|
||||||
|
|
||||||
# look for (K4PC 1.9.0 and later) .kinf2011 file
|
# look for (K4PC 1.9.0 and later) .kinf2011 file
|
||||||
kinfopath = path +'\\Amazon\\Kindle\\storage\\.kinf2011'
|
kinfopath = path +'\\Amazon\\Kindle\\storage\\.kinf2011'
|
||||||
if os.path.isfile(kinfopath):
|
if os.path.isfile(kinfopath):
|
||||||
@@ -1036,7 +1052,7 @@ if iswindows:
|
|||||||
DB = {}
|
DB = {}
|
||||||
with open(kInfoFile, 'rb') as infoReader:
|
with open(kInfoFile, 'rb') as infoReader:
|
||||||
data = infoReader.read()
|
data = infoReader.read()
|
||||||
# assume newest .kinf2011 style .kinf file
|
# assume .kinf2011 or .kinf2018 style .kinf file
|
||||||
# the .kinf file uses "/" to separate it into records
|
# the .kinf file uses "/" to separate it into records
|
||||||
# so remove the trailing "/" to make it easy to use split
|
# so remove the trailing "/" to make it easy to use split
|
||||||
data = data[:-1]
|
data = data[:-1]
|
||||||
@@ -1050,8 +1066,17 @@ if iswindows:
|
|||||||
# now extract the pieces that form the added entropy
|
# now extract the pieces that form the added entropy
|
||||||
pattern = re.compile(r'''\[Version:(\d+)\]\[Build:(\d+)\]\[Cksum:([^\]]+)\]\[Guid:([\{\}a-z0-9\-]+)\]''', re.IGNORECASE)
|
pattern = re.compile(r'''\[Version:(\d+)\]\[Build:(\d+)\]\[Cksum:([^\]]+)\]\[Guid:([\{\}a-z0-9\-]+)\]''', re.IGNORECASE)
|
||||||
for m in re.finditer(pattern, cleartext):
|
for m in re.finditer(pattern, cleartext):
|
||||||
added_entropy = m.group(2) + m.group(4)
|
version = int(m.group(1))
|
||||||
|
build = m.group(2)
|
||||||
|
guid = m.group(4)
|
||||||
|
|
||||||
|
if version == 5: # .kinf2011
|
||||||
|
added_entropy = build + guid
|
||||||
|
elif version == 6: # .kinf2018
|
||||||
|
salt = str(0x6d8 * int(build)) + guid
|
||||||
|
sp = GetUserName() + '+@#$%+' + GetIDString()
|
||||||
|
passwd = encode(SHA256(sp), charMap5)
|
||||||
|
key = KeyIVGen().pbkdf2(passwd, salt, 10000, 0x400)[:32] # this is very slow
|
||||||
|
|
||||||
# loop through the item records until all are processed
|
# loop through the item records until all are processed
|
||||||
while len(items) > 0:
|
while len(items) > 0:
|
||||||
@@ -1063,10 +1088,6 @@ if iswindows:
|
|||||||
# is the MD5 hash of the key name encoded by charMap5
|
# is the MD5 hash of the key name encoded by charMap5
|
||||||
keyhash = item[0:32]
|
keyhash = item[0:32]
|
||||||
|
|
||||||
# the sha1 of raw keyhash string is used to create entropy along
|
|
||||||
# with the added entropy provided above from the headerblob
|
|
||||||
entropy = SHA1(keyhash) + added_entropy
|
|
||||||
|
|
||||||
# the remainder of the first record when decoded with charMap5
|
# the remainder of the first record when decoded with charMap5
|
||||||
# has the ':' split char followed by the string representation
|
# has the ':' split char followed by the string representation
|
||||||
# of the number of records that follow
|
# of the number of records that follow
|
||||||
@@ -1114,11 +1135,29 @@ if iswindows:
|
|||||||
encdata = encdata + pfx
|
encdata = encdata + pfx
|
||||||
#print "rearranged data:",encdata
|
#print "rearranged data:",encdata
|
||||||
|
|
||||||
|
if version == 5:
|
||||||
|
# decode using new testMap8 to get the original CryptProtect Data
|
||||||
|
encryptedValue = decode(encdata,testMap8)
|
||||||
|
#print "decoded data:",encryptedValue.encode('hex')
|
||||||
|
entropy = SHA1(keyhash) + added_entropy
|
||||||
|
cleartext = CryptUnprotectData(encryptedValue, entropy, 1)
|
||||||
|
elif version == 6:
|
||||||
|
from Crypto.Cipher import AES
|
||||||
|
from Crypto.Util import Counter
|
||||||
|
# decode using new testMap8 to get IV + ciphertext
|
||||||
|
iv_ciphertext = decode(encdata, testMap8)
|
||||||
|
# pad IV so that we can substitute AES-CTR for GCM
|
||||||
|
iv = iv_ciphertext[:12] + b'\x00\x00\x00\x02'
|
||||||
|
ciphertext = iv_ciphertext[12:]
|
||||||
|
# convert IV to int for use with pycrypto
|
||||||
|
iv_ints = unpack('>QQ', iv)
|
||||||
|
iv = iv_ints[0] << 64 | iv_ints[1]
|
||||||
|
# set up AES-CTR
|
||||||
|
ctr = Counter.new(128, initial_value=iv)
|
||||||
|
cipher = AES.new(key, AES.MODE_CTR, counter=ctr)
|
||||||
|
# decrypt and decode
|
||||||
|
cleartext = decode(cipher.decrypt(ciphertext), charMap5)
|
||||||
|
|
||||||
# decode using new testMap8 to get the original CryptProtect Data
|
|
||||||
encryptedValue = decode(encdata,testMap8)
|
|
||||||
#print "decoded data:",encryptedValue.encode('hex')
|
|
||||||
cleartext = CryptUnprotectData(encryptedValue, entropy, 1)
|
|
||||||
if len(cleartext)>0:
|
if len(cleartext)>0:
|
||||||
#print "cleartext data:",cleartext,":end data"
|
#print "cleartext data:",cleartext,":end data"
|
||||||
DB[keyname] = cleartext
|
DB[keyname] = cleartext
|
||||||
@@ -1145,8 +1184,12 @@ elif isosx:
|
|||||||
|
|
||||||
libcrypto = find_library('crypto')
|
libcrypto = find_library('crypto')
|
||||||
if libcrypto is None:
|
if libcrypto is None:
|
||||||
raise DrmException(u"libcrypto not found")
|
libcrypto = '/usr/lib/libcrypto.dylib'
|
||||||
libcrypto = CDLL(libcrypto)
|
try:
|
||||||
|
libcrypto = CDLL(libcrypto)
|
||||||
|
except Exception as e:
|
||||||
|
raise DrmException(u"libcrypto not found: " % e)
|
||||||
|
|
||||||
|
|
||||||
# From OpenSSL's crypto aes header
|
# From OpenSSL's crypto aes header
|
||||||
#
|
#
|
||||||
@@ -1294,7 +1337,7 @@ elif isosx:
|
|||||||
uuids = []
|
uuids = []
|
||||||
uuidnum = os.getenv('MYUUIDNUMBER')
|
uuidnum = os.getenv('MYUUIDNUMBER')
|
||||||
if uuidnum != None:
|
if uuidnum != None:
|
||||||
uuids.append(strip(uuidnum))
|
uuids.append(uuidnum.strip())
|
||||||
cmdline = '/usr/sbin/ioreg -l -S -w 0 -r -c AppleAHCIDiskDriver'
|
cmdline = '/usr/sbin/ioreg -l -S -w 0 -r -c AppleAHCIDiskDriver'
|
||||||
cmdline = cmdline.encode(sys.getfilesystemencoding())
|
cmdline = cmdline.encode(sys.getfilesystemencoding())
|
||||||
p = subprocess.Popen(cmdline, shell=True, stdin=None, stdout=subprocess.PIPE, stderr=subprocess.PIPE, close_fds=False)
|
p = subprocess.Popen(cmdline, shell=True, stdin=None, stdout=subprocess.PIPE, stderr=subprocess.PIPE, close_fds=False)
|
||||||
@@ -1411,6 +1454,18 @@ elif isosx:
|
|||||||
kInfoFiles=[]
|
kInfoFiles=[]
|
||||||
found = False
|
found = False
|
||||||
home = os.getenv('HOME')
|
home = os.getenv('HOME')
|
||||||
|
# check for .kinf2018 file in new location (App Store Kindle for Mac)
|
||||||
|
testpath = home + '/Library/Containers/com.amazon.Kindle/Data/Library/Application Support/Kindle/storage/.kinf2018'
|
||||||
|
if os.path.isfile(testpath):
|
||||||
|
kInfoFiles.append(testpath)
|
||||||
|
print('Found k4Mac kinf2018 file: ' + testpath)
|
||||||
|
found = True
|
||||||
|
# check for .kinf2018 files
|
||||||
|
testpath = home + '/Library/Application Support/Kindle/storage/.kinf2018'
|
||||||
|
if os.path.isfile(testpath):
|
||||||
|
kInfoFiles.append(testpath)
|
||||||
|
print('Found k4Mac kinf2018 file: ' + testpath)
|
||||||
|
found = True
|
||||||
# check for .kinf2011 file in new location (App Store Kindle for Mac)
|
# check for .kinf2011 file in new location (App Store Kindle for Mac)
|
||||||
testpath = home + '/Library/Containers/com.amazon.Kindle/Data/Library/Application Support/Kindle/storage/.kinf2011'
|
testpath = home + '/Library/Containers/com.amazon.Kindle/Data/Library/Application Support/Kindle/storage/.kinf2011'
|
||||||
if os.path.isfile(testpath):
|
if os.path.isfile(testpath):
|
||||||
@@ -1484,19 +1539,28 @@ elif isosx:
|
|||||||
try:
|
try:
|
||||||
DB = {}
|
DB = {}
|
||||||
items = data.split('/')
|
items = data.split('/')
|
||||||
|
|
||||||
# the headerblob is the encrypted information needed to build the entropy string
|
# the headerblob is the encrypted information needed to build the entropy string
|
||||||
headerblob = items.pop(0)
|
headerblob = items.pop(0)
|
||||||
encryptedValue = decode(headerblob, charMap1)
|
encryptedValue = decode(headerblob, charMap1)
|
||||||
cleartext = UnprotectHeaderData(encryptedValue)
|
cleartext = UnprotectHeaderData(encryptedValue)
|
||||||
|
|
||||||
# now extract the pieces in the same way
|
# now extract the pieces in the same way
|
||||||
# this version is different from K4PC it scales the build number by multipying by 735
|
|
||||||
pattern = re.compile(r'''\[Version:(\d+)\]\[Build:(\d+)\]\[Cksum:([^\]]+)\]\[Guid:([\{\}a-z0-9\-]+)\]''', re.IGNORECASE)
|
pattern = re.compile(r'''\[Version:(\d+)\]\[Build:(\d+)\]\[Cksum:([^\]]+)\]\[Guid:([\{\}a-z0-9\-]+)\]''', re.IGNORECASE)
|
||||||
for m in re.finditer(pattern, cleartext):
|
for m in re.finditer(pattern, cleartext):
|
||||||
entropy = str(int(m.group(2)) * 0x2df) + m.group(4)
|
version = int(m.group(1))
|
||||||
|
build = m.group(2)
|
||||||
|
guid = m.group(4)
|
||||||
|
|
||||||
cud = CryptUnprotectData(entropy,IDString)
|
if version == 5: # .kinf2011: identical to K4PC, except the build number gets multiplied
|
||||||
|
entropy = str(0x2df * int(build)) + guid
|
||||||
|
cud = CryptUnprotectData(entropy,IDString)
|
||||||
|
|
||||||
|
elif version == 6: # .kinf2018: identical to K4PC
|
||||||
|
salt = str(0x6d8 * int(build)) + guid
|
||||||
|
sp = GetUserName() + '+@#$%+' + IDString
|
||||||
|
passwd = encode(SHA256(sp), charMap5)
|
||||||
|
key = LibCrypto().keyivgen(passwd, salt, 10000, 0x400)[:32]
|
||||||
|
|
||||||
# loop through the item records until all are processed
|
# loop through the item records until all are processed
|
||||||
while len(items) > 0:
|
while len(items) > 0:
|
||||||
@@ -1557,9 +1621,28 @@ elif isosx:
|
|||||||
encdata = encdata[noffset:]
|
encdata = encdata[noffset:]
|
||||||
encdata = encdata + pfx
|
encdata = encdata + pfx
|
||||||
|
|
||||||
# decode using testMap8 to get the CryptProtect Data
|
if version == 5:
|
||||||
encryptedValue = decode(encdata,testMap8)
|
# decode using testMap8 to get the CryptProtect Data
|
||||||
cleartext = cud.decrypt(encryptedValue)
|
encryptedValue = decode(encdata,testMap8)
|
||||||
|
cleartext = cud.decrypt(encryptedValue)
|
||||||
|
|
||||||
|
elif version == 6:
|
||||||
|
from Crypto.Cipher import AES
|
||||||
|
from Crypto.Util import Counter
|
||||||
|
# decode using new testMap8 to get IV + ciphertext
|
||||||
|
iv_ciphertext = decode(encdata, testMap8)
|
||||||
|
# pad IV so that we can substitute AES-CTR for GCM
|
||||||
|
iv = iv_ciphertext[:12] + b'\x00\x00\x00\x02'
|
||||||
|
ciphertext = iv_ciphertext[12:]
|
||||||
|
# convert IV to int for use with pycrypto
|
||||||
|
iv_ints = unpack('>QQ', iv)
|
||||||
|
iv = iv_ints[0] << 64 | iv_ints[1]
|
||||||
|
# set up AES-CTR
|
||||||
|
ctr = Counter.new(128, initial_value=iv)
|
||||||
|
cipher = AES.new(key, AES.MODE_CTR, counter=ctr)
|
||||||
|
# decrypt and decode
|
||||||
|
cleartext = decode(cipher.decrypt(ciphertext), charMap5)
|
||||||
|
|
||||||
# print keyname
|
# print keyname
|
||||||
# print cleartext
|
# print cleartext
|
||||||
if len(cleartext) > 0:
|
if len(cleartext) > 0:
|
||||||
@@ -11,6 +11,7 @@
|
|||||||
# 0.4 Added support for serial numbers starting with '9', fixed unicode bugs.
|
# 0.4 Added support for serial numbers starting with '9', fixed unicode bugs.
|
||||||
# 0.5 moved unicode_argv call inside main for Windows DeDRM compatibility
|
# 0.5 moved unicode_argv call inside main for Windows DeDRM compatibility
|
||||||
|
|
||||||
|
from __future__ import print_function
|
||||||
import sys
|
import sys
|
||||||
import binascii
|
import binascii
|
||||||
|
|
||||||
@@ -73,7 +74,7 @@ def unicode_argv():
|
|||||||
return [arg if (type(arg) == unicode) else unicode(arg,argvencoding) for arg in sys.argv]
|
return [arg if (type(arg) == unicode) else unicode(arg,argvencoding) for arg in sys.argv]
|
||||||
|
|
||||||
if sys.hexversion >= 0x3000000:
|
if sys.hexversion >= 0x3000000:
|
||||||
print 'This script is incompatible with Python 3.x. Please install Python 2.7.x.'
|
print('This script is incompatible with Python 3.x. Please install Python 2.7.x.')
|
||||||
sys.exit(2)
|
sys.exit(2)
|
||||||
|
|
||||||
letters = 'ABCDEFGHIJKLMNPQRSTUVWXYZ123456789'
|
letters = 'ABCDEFGHIJKLMNPQRSTUVWXYZ123456789'
|
||||||
@@ -113,28 +114,28 @@ def pidFromSerial(s, l):
|
|||||||
return pid
|
return pid
|
||||||
|
|
||||||
def cli_main():
|
def cli_main():
|
||||||
print u"Mobipocket PID calculator for Amazon Kindle. Copyright © 2007, 2009 Igor Skochinsky"
|
print(u"Mobipocket PID calculator for Amazon Kindle. Copyright © 2007, 2009 Igor Skochinsky")
|
||||||
argv=unicode_argv()
|
argv=unicode_argv()
|
||||||
if len(argv)==2:
|
if len(argv)==2:
|
||||||
serial = argv[1]
|
serial = argv[1]
|
||||||
else:
|
else:
|
||||||
print u"Usage: kindlepid.py <Kindle Serial Number>/<iPhone/iPod Touch UDID>"
|
print(u"Usage: kindlepid.py <Kindle Serial Number>/<iPhone/iPod Touch UDID>")
|
||||||
return 1
|
return 1
|
||||||
if len(serial)==16:
|
if len(serial)==16:
|
||||||
if serial.startswith("B") or serial.startswith("9"):
|
if serial.startswith("B") or serial.startswith("9"):
|
||||||
print u"Kindle serial number detected"
|
print(u"Kindle serial number detected")
|
||||||
else:
|
else:
|
||||||
print u"Warning: unrecognized serial number. Please recheck input."
|
print(u"Warning: unrecognized serial number. Please recheck input.")
|
||||||
return 1
|
return 1
|
||||||
pid = pidFromSerial(serial.encode("utf-8"),7)+'*'
|
pid = pidFromSerial(serial.encode("utf-8"),7)+'*'
|
||||||
print u"Mobipocket PID for Kindle serial#{0} is {1}".format(serial,checksumPid(pid))
|
print(u"Mobipocket PID for Kindle serial#{0} is {1}".format(serial,checksumPid(pid)))
|
||||||
return 0
|
return 0
|
||||||
elif len(serial)==40:
|
elif len(serial)==40:
|
||||||
print u"iPhone serial number (UDID) detected"
|
print(u"iPhone serial number (UDID) detected")
|
||||||
pid = pidFromSerial(serial.encode("utf-8"),8)
|
pid = pidFromSerial(serial.encode("utf-8"),8)
|
||||||
print u"Mobipocket PID for iPhone serial#{0} is {1}".format(serial,checksumPid(pid))
|
print(u"Mobipocket PID for iPhone serial#{0} is {1}".format(serial,checksumPid(pid)))
|
||||||
return 0
|
return 0
|
||||||
print u"Warning: unrecognized serial number. Please recheck input."
|
print(u"Warning: unrecognized serial number. Please recheck input.")
|
||||||
return 1
|
return 1
|
||||||
|
|
||||||
|
|
||||||
@@ -5,6 +5,7 @@
|
|||||||
# Copyright © 2008 The Dark Reverser
|
# Copyright © 2008 The Dark Reverser
|
||||||
# Portions © 2008–2017 Apprentice Harper et al.
|
# Portions © 2008–2017 Apprentice Harper et al.
|
||||||
|
|
||||||
|
from __future__ import print_function
|
||||||
__license__ = 'GPL v3'
|
__license__ = 'GPL v3'
|
||||||
__version__ = u"0.42"
|
__version__ = u"0.42"
|
||||||
|
|
||||||
@@ -80,7 +81,7 @@ import binascii
|
|||||||
try:
|
try:
|
||||||
from alfcrypto import Pukall_Cipher
|
from alfcrypto import Pukall_Cipher
|
||||||
except:
|
except:
|
||||||
print u"AlfCrypto not found. Using python PC1 implementation."
|
print(u"AlfCrypto not found. Using python PC1 implementation.")
|
||||||
|
|
||||||
# Wrap a stream so that output gets flushed immediately
|
# Wrap a stream so that output gets flushed immediately
|
||||||
# and also make sure that any unicode strings get
|
# and also make sure that any unicode strings get
|
||||||
@@ -244,12 +245,12 @@ class MobiBook:
|
|||||||
pass
|
pass
|
||||||
|
|
||||||
def __init__(self, infile):
|
def __init__(self, infile):
|
||||||
print u"MobiDeDrm v{0:s}.\nCopyright © 2008-2017 The Dark Reverser, Apprentice Harper et al.".format(__version__)
|
print(u"MobiDeDrm v{0:s}.\nCopyright © 2008-2017 The Dark Reverser, Apprentice Harper et al.".format(__version__))
|
||||||
|
|
||||||
try:
|
try:
|
||||||
from alfcrypto import Pukall_Cipher
|
from alfcrypto import Pukall_Cipher
|
||||||
except:
|
except:
|
||||||
print u"AlfCrypto not found. Using python PC1 implementation."
|
print(u"AlfCrypto not found. Using python PC1 implementation.")
|
||||||
|
|
||||||
# initial sanity check on file
|
# initial sanity check on file
|
||||||
self.data_file = file(infile, 'rb').read()
|
self.data_file = file(infile, 'rb').read()
|
||||||
@@ -282,7 +283,7 @@ class MobiBook:
|
|||||||
self.mobi_version = -1
|
self.mobi_version = -1
|
||||||
|
|
||||||
if self.magic == 'TEXtREAd':
|
if self.magic == 'TEXtREAd':
|
||||||
print u"PalmDoc format book detected."
|
print(u"PalmDoc format book detected.")
|
||||||
return
|
return
|
||||||
|
|
||||||
self.mobi_length, = struct.unpack('>L',self.sect[0x14:0x18])
|
self.mobi_length, = struct.unpack('>L',self.sect[0x14:0x18])
|
||||||
@@ -424,10 +425,10 @@ class MobiBook:
|
|||||||
|
|
||||||
def processBook(self, pidlist):
|
def processBook(self, pidlist):
|
||||||
crypto_type, = struct.unpack('>H', self.sect[0xC:0xC+2])
|
crypto_type, = struct.unpack('>H', self.sect[0xC:0xC+2])
|
||||||
print u"Crypto Type is: {0:d}".format(crypto_type)
|
print(u"Crypto Type is: {0:d}".format(crypto_type))
|
||||||
self.crypto_type = crypto_type
|
self.crypto_type = crypto_type
|
||||||
if crypto_type == 0:
|
if crypto_type == 0:
|
||||||
print u"This book is not encrypted."
|
print(u"This book is not encrypted.")
|
||||||
# we must still check for Print Replica
|
# we must still check for Print Replica
|
||||||
self.print_replica = (self.loadSection(1)[0:4] == '%MOP')
|
self.print_replica = (self.loadSection(1)[0:4] == '%MOP')
|
||||||
self.mobi_data = self.data_file
|
self.mobi_data = self.data_file
|
||||||
@@ -444,12 +445,12 @@ class MobiBook:
|
|||||||
for pid in pidlist:
|
for pid in pidlist:
|
||||||
if len(pid)==10:
|
if len(pid)==10:
|
||||||
if checksumPid(pid[0:-2]) != pid:
|
if checksumPid(pid[0:-2]) != pid:
|
||||||
print u"Warning: PID {0} has incorrect checksum, should have been {1}".format(pid,checksumPid(pid[0:-2]))
|
print(u"Warning: PID {0} has incorrect checksum, should have been {1}".format(pid,checksumPid(pid[0:-2])))
|
||||||
goodpids.append(pid[0:-2])
|
goodpids.append(pid[0:-2])
|
||||||
elif len(pid)==8:
|
elif len(pid)==8:
|
||||||
goodpids.append(pid)
|
goodpids.append(pid)
|
||||||
else:
|
else:
|
||||||
print u"Warning: PID {0} has wrong number of digits".format(pid)
|
print(u"Warning: PID {0} has wrong number of digits".format(pid))
|
||||||
|
|
||||||
if self.crypto_type == 1:
|
if self.crypto_type == 1:
|
||||||
t1_keyvec = 'QDCVEPMU675RUBSZ'
|
t1_keyvec = 'QDCVEPMU675RUBSZ'
|
||||||
@@ -475,22 +476,22 @@ class MobiBook:
|
|||||||
self.patchSection(0, '\xff' * 4 + '\0' * 12, 0xA8)
|
self.patchSection(0, '\xff' * 4 + '\0' * 12, 0xA8)
|
||||||
|
|
||||||
if pid=='00000000':
|
if pid=='00000000':
|
||||||
print u"File has default encryption, no specific key needed."
|
print(u"File has default encryption, no specific key needed.")
|
||||||
else:
|
else:
|
||||||
print u"File is encoded with PID {0}.".format(checksumPid(pid))
|
print(u"File is encoded with PID {0}.".format(checksumPid(pid)))
|
||||||
|
|
||||||
# clear the crypto type
|
# clear the crypto type
|
||||||
self.patchSection(0, "\0" * 2, 0xC)
|
self.patchSection(0, "\0" * 2, 0xC)
|
||||||
|
|
||||||
# decrypt sections
|
# decrypt sections
|
||||||
print u"Decrypting. Please wait . . .",
|
print(u"Decrypting. Please wait . . .", end=' ')
|
||||||
mobidataList = []
|
mobidataList = []
|
||||||
mobidataList.append(self.data_file[:self.sections[1][0]])
|
mobidataList.append(self.data_file[:self.sections[1][0]])
|
||||||
for i in xrange(1, self.records+1):
|
for i in xrange(1, self.records+1):
|
||||||
data = self.loadSection(i)
|
data = self.loadSection(i)
|
||||||
extra_size = getSizeOfTrailingDataEntries(data, len(data), self.extra_data_flags)
|
extra_size = getSizeOfTrailingDataEntries(data, len(data), self.extra_data_flags)
|
||||||
if i%100 == 0:
|
if i%100 == 0:
|
||||||
print u".",
|
print(u".", end=' ')
|
||||||
# print "record %d, extra_size %d" %(i,extra_size)
|
# print "record %d, extra_size %d" %(i,extra_size)
|
||||||
decoded_data = PC1(found_key, data[0:len(data) - extra_size])
|
decoded_data = PC1(found_key, data[0:len(data) - extra_size])
|
||||||
if i==1:
|
if i==1:
|
||||||
@@ -501,7 +502,7 @@ class MobiBook:
|
|||||||
if self.num_sections > self.records+1:
|
if self.num_sections > self.records+1:
|
||||||
mobidataList.append(self.data_file[self.sections[self.records+1][0]:])
|
mobidataList.append(self.data_file[self.sections[self.records+1][0]:])
|
||||||
self.mobi_data = "".join(mobidataList)
|
self.mobi_data = "".join(mobidataList)
|
||||||
print u"done"
|
print(u"done")
|
||||||
return
|
return
|
||||||
|
|
||||||
def getUnencryptedBook(infile,pidlist):
|
def getUnencryptedBook(infile,pidlist):
|
||||||
@@ -516,15 +517,15 @@ def cli_main():
|
|||||||
argv=unicode_argv()
|
argv=unicode_argv()
|
||||||
progname = os.path.basename(argv[0])
|
progname = os.path.basename(argv[0])
|
||||||
if len(argv)<3 or len(argv)>4:
|
if len(argv)<3 or len(argv)>4:
|
||||||
print u"MobiDeDrm v{0:s}.\nCopyright © 2008-2017 The Dark Reverser, Apprentice Harper et al.".format(__version__)
|
print(u"MobiDeDrm v{0:s}.\nCopyright © 2008-2017 The Dark Reverser, Apprentice Harper et al.".format(__version__))
|
||||||
print u"Removes protection from Kindle/Mobipocket, Kindle/KF8 and Kindle/Print Replica ebooks"
|
print(u"Removes protection from Kindle/Mobipocket, Kindle/KF8 and Kindle/Print Replica ebooks")
|
||||||
print u"Usage:"
|
print(u"Usage:")
|
||||||
print u" {0} <infile> <outfile> [<Comma separated list of PIDs to try>]".format(progname)
|
print(u" {0} <infile> <outfile> [<Comma separated list of PIDs to try>]".format(progname))
|
||||||
return 1
|
return 1
|
||||||
else:
|
else:
|
||||||
infile = argv[1]
|
infile = argv[1]
|
||||||
outfile = argv[2]
|
outfile = argv[2]
|
||||||
if len(argv) is 4:
|
if len(argv) == 4:
|
||||||
pidlist = argv[3].split(',')
|
pidlist = argv[3].split(',')
|
||||||
else:
|
else:
|
||||||
pidlist = []
|
pidlist = []
|
||||||
@@ -532,7 +533,7 @@ def cli_main():
|
|||||||
stripped_file = getUnencryptedBook(infile, pidlist)
|
stripped_file = getUnencryptedBook(infile, pidlist)
|
||||||
file(outfile, 'wb').write(stripped_file)
|
file(outfile, 'wb').write(stripped_file)
|
||||||
except DrmException, e:
|
except DrmException, e:
|
||||||
print u"MobiDeDRM v{0} Error: {1:s}".format(__version__,e.args[0])
|
print(u"MobiDeDRM v{0} Error: {1:s}".format(__version__,e.args[0]))
|
||||||
return 1
|
return 1
|
||||||
return 0
|
return 0
|
||||||
|
|
||||||
@@ -11,7 +11,7 @@ def load_pycrypto():
|
|||||||
class DES(object):
|
class DES(object):
|
||||||
def __init__(self, key):
|
def __init__(self, key):
|
||||||
if len(key) != 8 :
|
if len(key) != 8 :
|
||||||
raise Error('DES improper key used')
|
raise ValueError('DES improper key used')
|
||||||
self.key = key
|
self.key = key
|
||||||
self._des = _DES.new(key,_DES.MODE_ECB)
|
self._des = _DES.new(key,_DES.MODE_ECB)
|
||||||
def desdecrypt(self, data):
|
def desdecrypt(self, data):
|
||||||
@@ -2,6 +2,7 @@
|
|||||||
# -*- coding: utf-8 -*-
|
# -*- coding: utf-8 -*-
|
||||||
# vim:ts=4:sw=4:softtabstop=4:smarttab:expandtab
|
# vim:ts=4:sw=4:softtabstop=4:smarttab:expandtab
|
||||||
|
|
||||||
|
from __future__ import print_function
|
||||||
import sys
|
import sys
|
||||||
import os
|
import os
|
||||||
import re
|
import re
|
||||||
@@ -23,7 +24,7 @@ def decryptepub(infile, outdir, rscpath):
|
|||||||
zippath = os.path.join(bpath,name + '_temp.zip')
|
zippath = os.path.join(bpath,name + '_temp.zip')
|
||||||
rv = zipfix.repairBook(infile, zippath)
|
rv = zipfix.repairBook(infile, zippath)
|
||||||
if rv != 0:
|
if rv != 0:
|
||||||
print "Error while trying to fix epub"
|
print("Error while trying to fix epub")
|
||||||
return rv
|
return rv
|
||||||
|
|
||||||
# determine a good name for the output file
|
# determine a good name for the output file
|
||||||
@@ -43,7 +44,7 @@ def decryptepub(infile, outdir, rscpath):
|
|||||||
try:
|
try:
|
||||||
rv = ineptepub.decryptBook(userkey, zippath, outfile)
|
rv = ineptepub.decryptBook(userkey, zippath, outfile)
|
||||||
if rv == 0:
|
if rv == 0:
|
||||||
print "Decrypted Adobe ePub with key file {0}".format(filename)
|
print("Decrypted Adobe ePub with key file {0}".format(filename))
|
||||||
break
|
break
|
||||||
except Exception, e:
|
except Exception, e:
|
||||||
errlog += traceback.format_exc()
|
errlog += traceback.format_exc()
|
||||||
@@ -63,7 +64,7 @@ def decryptepub(infile, outdir, rscpath):
|
|||||||
try:
|
try:
|
||||||
rv = ignobleepub.decryptBook(userkey, zippath, outfile)
|
rv = ignobleepub.decryptBook(userkey, zippath, outfile)
|
||||||
if rv == 0:
|
if rv == 0:
|
||||||
print "Decrypted B&N ePub with key file {0}".format(filename)
|
print("Decrypted B&N ePub with key file {0}".format(filename))
|
||||||
break
|
break
|
||||||
except Exception, e:
|
except Exception, e:
|
||||||
errlog += traceback.format_exc()
|
errlog += traceback.format_exc()
|
||||||
@@ -72,14 +73,14 @@ def decryptepub(infile, outdir, rscpath):
|
|||||||
else:
|
else:
|
||||||
encryption = epubtest.encryption(zippath)
|
encryption = epubtest.encryption(zippath)
|
||||||
if encryption == "Unencrypted":
|
if encryption == "Unencrypted":
|
||||||
print "{0} is not DRMed.".format(name)
|
print("{0} is not DRMed.".format(name))
|
||||||
rv = 0
|
rv = 0
|
||||||
else:
|
else:
|
||||||
print "{0} has an unknown encryption.".format(name)
|
print("{0} has an unknown encryption.".format(name))
|
||||||
|
|
||||||
os.remove(zippath)
|
os.remove(zippath)
|
||||||
if rv != 0:
|
if rv != 0:
|
||||||
print errlog
|
print(errlog)
|
||||||
return rv
|
return rv
|
||||||
|
|
||||||
|
|
||||||
@@ -109,11 +110,12 @@ def decryptpdf(infile, outdir, rscpath):
|
|||||||
rv = 1
|
rv = 1
|
||||||
|
|
||||||
if rv != 0:
|
if rv != 0:
|
||||||
print errlog
|
print(errlog)
|
||||||
return rv
|
return rv
|
||||||
|
|
||||||
|
|
||||||
def decryptpdb(infile, outdir, rscpath):
|
def decryptpdb(infile, outdir, rscpath):
|
||||||
|
errlog = ''
|
||||||
outname = os.path.splitext(os.path.basename(infile))[0] + ".pmlz"
|
outname = os.path.splitext(os.path.basename(infile))[0] + ".pmlz"
|
||||||
outpath = os.path.join(outdir, outname)
|
outpath = os.path.join(outdir, outname)
|
||||||
rv = 1
|
rv = 1
|
||||||
@@ -126,7 +128,7 @@ def decryptpdb(infile, outdir, rscpath):
|
|||||||
try:
|
try:
|
||||||
name, cc8 = i.split(':')
|
name, cc8 = i.split(':')
|
||||||
except ValueError:
|
except ValueError:
|
||||||
print ' Error parsing user supplied social drm data.'
|
print(' Error parsing user supplied social drm data.')
|
||||||
return 1
|
return 1
|
||||||
try:
|
try:
|
||||||
rv = erdr2pml.decryptBook(infile, outpath, True, erdr2pml.getuser_key(name, cc8))
|
rv = erdr2pml.decryptBook(infile, outpath, True, erdr2pml.getuser_key(name, cc8))
|
||||||
@@ -141,6 +143,7 @@ def decryptpdb(infile, outdir, rscpath):
|
|||||||
|
|
||||||
|
|
||||||
def decryptk4mobi(infile, outdir, rscpath):
|
def decryptk4mobi(infile, outdir, rscpath):
|
||||||
|
errlog = ''
|
||||||
rv = 1
|
rv = 1
|
||||||
pidnums = []
|
pidnums = []
|
||||||
pidspath = os.path.join(rscpath,'pidlist.txt')
|
pidspath = os.path.join(rscpath,'pidlist.txt')
|
||||||
@@ -2,6 +2,7 @@
|
|||||||
# vim:ts=4:sw=4:softtabstop=4:smarttab:expandtab
|
# vim:ts=4:sw=4:softtabstop=4:smarttab:expandtab
|
||||||
# For use with Topaz Scripts Version 2.6
|
# For use with Topaz Scripts Version 2.6
|
||||||
|
|
||||||
|
from __future__ import print_function
|
||||||
import csv
|
import csv
|
||||||
import sys
|
import sys
|
||||||
import os
|
import os
|
||||||
@@ -114,9 +115,9 @@ class DocParser(object):
|
|||||||
|
|
||||||
# process each style converting what you can
|
# process each style converting what you can
|
||||||
|
|
||||||
if debug: print ' ', 'Processing styles.'
|
if debug: print(' ', 'Processing styles.')
|
||||||
for j in xrange(stylecnt):
|
for j in xrange(stylecnt):
|
||||||
if debug: print ' ', 'Processing style %d' %(j)
|
if debug: print(' ', 'Processing style %d' %(j))
|
||||||
start = styleList[j]
|
start = styleList[j]
|
||||||
end = styleList[j+1]
|
end = styleList[j+1]
|
||||||
|
|
||||||
@@ -135,7 +136,7 @@ class DocParser(object):
|
|||||||
else :
|
else :
|
||||||
sclass = ''
|
sclass = ''
|
||||||
|
|
||||||
if debug: print 'sclass', sclass
|
if debug: print('sclass', sclass)
|
||||||
|
|
||||||
# check for any "after class" specifiers
|
# check for any "after class" specifiers
|
||||||
(pos, aftclass) = self.findinDoc('style._after_class',start,end)
|
(pos, aftclass) = self.findinDoc('style._after_class',start,end)
|
||||||
@@ -145,7 +146,7 @@ class DocParser(object):
|
|||||||
else :
|
else :
|
||||||
aftclass = ''
|
aftclass = ''
|
||||||
|
|
||||||
if debug: print 'aftclass', aftclass
|
if debug: print('aftclass', aftclass)
|
||||||
|
|
||||||
cssargs = {}
|
cssargs = {}
|
||||||
|
|
||||||
@@ -154,8 +155,8 @@ class DocParser(object):
|
|||||||
(pos1, attr) = self.findinDoc('style.rule.attr', start, end)
|
(pos1, attr) = self.findinDoc('style.rule.attr', start, end)
|
||||||
(pos2, val) = self.findinDoc('style.rule.value', start, end)
|
(pos2, val) = self.findinDoc('style.rule.value', start, end)
|
||||||
|
|
||||||
if debug: print 'attr', attr
|
if debug: print('attr', attr)
|
||||||
if debug: print 'val', val
|
if debug: print('val', val)
|
||||||
|
|
||||||
if attr == None : break
|
if attr == None : break
|
||||||
|
|
||||||
@@ -182,7 +183,7 @@ class DocParser(object):
|
|||||||
try:
|
try:
|
||||||
f = float(val)
|
f = float(val)
|
||||||
except:
|
except:
|
||||||
print "Warning: unrecognised val, ignoring"
|
print("Warning: unrecognised val, ignoring")
|
||||||
val = 0
|
val = 0
|
||||||
pv = float(val)/scale
|
pv = float(val)/scale
|
||||||
cssargs[attr] = (self.attr_val_map[attr], pv)
|
cssargs[attr] = (self.attr_val_map[attr], pv)
|
||||||
@@ -194,7 +195,7 @@ class DocParser(object):
|
|||||||
if aftclass != "" : keep = False
|
if aftclass != "" : keep = False
|
||||||
|
|
||||||
if keep :
|
if keep :
|
||||||
if debug: print 'keeping style'
|
if debug: print('keeping style')
|
||||||
# make sure line-space does not go below 100% or above 300% since
|
# make sure line-space does not go below 100% or above 300% since
|
||||||
# it can be wacky in some styles
|
# it can be wacky in some styles
|
||||||
if 'line-space' in cssargs:
|
if 'line-space' in cssargs:
|
||||||
@@ -266,15 +267,15 @@ class DocParser(object):
|
|||||||
|
|
||||||
def convert2CSS(flatxml, fontsize, ph, pw):
|
def convert2CSS(flatxml, fontsize, ph, pw):
|
||||||
|
|
||||||
print ' ', 'Using font size:',fontsize
|
print(' ', 'Using font size:',fontsize)
|
||||||
print ' ', 'Using page height:', ph
|
print(' ', 'Using page height:', ph)
|
||||||
print ' ', 'Using page width:', pw
|
print(' ', 'Using page width:', pw)
|
||||||
|
|
||||||
# create a document parser
|
# create a document parser
|
||||||
dp = DocParser(flatxml, fontsize, ph, pw)
|
dp = DocParser(flatxml, fontsize, ph, pw)
|
||||||
if debug: print ' ', 'Created DocParser.'
|
if debug: print(' ', 'Created DocParser.')
|
||||||
csspage = dp.process()
|
csspage = dp.process()
|
||||||
if debug: print ' ', 'Processed DocParser.'
|
if debug: print(' ', 'Processed DocParser.')
|
||||||
return csspage
|
return csspage
|
||||||
|
|
||||||
|
|
||||||
@@ -8,6 +8,7 @@
|
|||||||
# 4.9 - moved unicode_argv call inside main for Windows DeDRM compatibility
|
# 4.9 - moved unicode_argv call inside main for Windows DeDRM compatibility
|
||||||
# 5.0 - Fixed potential unicode problem with command line interface
|
# 5.0 - Fixed potential unicode problem with command line interface
|
||||||
|
|
||||||
|
from __future__ import print_function
|
||||||
__version__ = '5.0'
|
__version__ = '5.0'
|
||||||
|
|
||||||
import sys
|
import sys
|
||||||
@@ -213,7 +214,7 @@ class TopazBook:
|
|||||||
# Read and return the data of one header record at the current book file position
|
# Read and return the data of one header record at the current book file position
|
||||||
# [[offset,decompressedLength,compressedLength],...]
|
# [[offset,decompressedLength,compressedLength],...]
|
||||||
nbValues = bookReadEncodedNumber(self.fo)
|
nbValues = bookReadEncodedNumber(self.fo)
|
||||||
if debug: print "%d records in header " % nbValues,
|
if debug: print("%d records in header " % nbValues, end=' ')
|
||||||
values = []
|
values = []
|
||||||
for i in range (0,nbValues):
|
for i in range (0,nbValues):
|
||||||
values.append([bookReadEncodedNumber(self.fo),bookReadEncodedNumber(self.fo),bookReadEncodedNumber(self.fo)])
|
values.append([bookReadEncodedNumber(self.fo),bookReadEncodedNumber(self.fo),bookReadEncodedNumber(self.fo)])
|
||||||
@@ -227,10 +228,10 @@ class TopazBook:
|
|||||||
record = bookReadHeaderRecordData()
|
record = bookReadHeaderRecordData()
|
||||||
return [tag,record]
|
return [tag,record]
|
||||||
nbRecords = bookReadEncodedNumber(self.fo)
|
nbRecords = bookReadEncodedNumber(self.fo)
|
||||||
if debug: print "Headers: %d" % nbRecords
|
if debug: print("Headers: %d" % nbRecords)
|
||||||
for i in range (0,nbRecords):
|
for i in range (0,nbRecords):
|
||||||
result = parseTopazHeaderRecord()
|
result = parseTopazHeaderRecord()
|
||||||
if debug: print result[0], ": ", result[1]
|
if debug: print(result[0], ": ", result[1])
|
||||||
self.bookHeaderRecords[result[0]] = result[1]
|
self.bookHeaderRecords[result[0]] = result[1]
|
||||||
if ord(self.fo.read(1)) != 0x64 :
|
if ord(self.fo.read(1)) != 0x64 :
|
||||||
raise DrmException(u"Parse Error : Invalid Header")
|
raise DrmException(u"Parse Error : Invalid Header")
|
||||||
@@ -244,12 +245,12 @@ class TopazBook:
|
|||||||
raise DrmException(u"Parse Error : Record Names Don't Match")
|
raise DrmException(u"Parse Error : Record Names Don't Match")
|
||||||
flags = ord(self.fo.read(1))
|
flags = ord(self.fo.read(1))
|
||||||
nbRecords = ord(self.fo.read(1))
|
nbRecords = ord(self.fo.read(1))
|
||||||
if debug: print "Metadata Records: %d" % nbRecords
|
if debug: print("Metadata Records: %d" % nbRecords)
|
||||||
for i in range (0,nbRecords) :
|
for i in range (0,nbRecords) :
|
||||||
keyval = bookReadString(self.fo)
|
keyval = bookReadString(self.fo)
|
||||||
content = bookReadString(self.fo)
|
content = bookReadString(self.fo)
|
||||||
if debug: print keyval
|
if debug: print(keyval)
|
||||||
if debug: print content
|
if debug: print(content)
|
||||||
self.bookMetadata[keyval] = content
|
self.bookMetadata[keyval] = content
|
||||||
return self.bookMetadata
|
return self.bookMetadata
|
||||||
|
|
||||||
@@ -319,11 +320,11 @@ class TopazBook:
|
|||||||
try:
|
try:
|
||||||
keydata = self.getBookPayloadRecord('dkey', 0)
|
keydata = self.getBookPayloadRecord('dkey', 0)
|
||||||
except DrmException, e:
|
except DrmException, e:
|
||||||
print u"no dkey record found, book may not be encrypted"
|
print(u"no dkey record found, book may not be encrypted")
|
||||||
print u"attempting to extrct files without a book key"
|
print(u"attempting to extrct files without a book key")
|
||||||
self.createBookDirectory()
|
self.createBookDirectory()
|
||||||
self.extractFiles()
|
self.extractFiles()
|
||||||
print u"Successfully Extracted Topaz contents"
|
print(u"Successfully Extracted Topaz contents")
|
||||||
if inCalibre:
|
if inCalibre:
|
||||||
from calibre_plugins.dedrm import genbook
|
from calibre_plugins.dedrm import genbook
|
||||||
else:
|
else:
|
||||||
@@ -331,7 +332,7 @@ class TopazBook:
|
|||||||
|
|
||||||
rv = genbook.generateBook(self.outdir, raw, fixedimage)
|
rv = genbook.generateBook(self.outdir, raw, fixedimage)
|
||||||
if rv == 0:
|
if rv == 0:
|
||||||
print u"Book Successfully generated."
|
print(u"Book Successfully generated.")
|
||||||
return rv
|
return rv
|
||||||
|
|
||||||
# try each pid to decode the file
|
# try each pid to decode the file
|
||||||
@@ -339,7 +340,7 @@ class TopazBook:
|
|||||||
for pid in pidlst:
|
for pid in pidlst:
|
||||||
# use 8 digit pids here
|
# use 8 digit pids here
|
||||||
pid = pid[0:8]
|
pid = pid[0:8]
|
||||||
print u"Trying: {0}".format(pid)
|
print(u"Trying: {0}".format(pid))
|
||||||
bookKeys = []
|
bookKeys = []
|
||||||
data = keydata
|
data = keydata
|
||||||
try:
|
try:
|
||||||
@@ -348,7 +349,7 @@ class TopazBook:
|
|||||||
pass
|
pass
|
||||||
else:
|
else:
|
||||||
bookKey = bookKeys[0]
|
bookKey = bookKeys[0]
|
||||||
print u"Book Key Found! ({0})".format(bookKey.encode('hex'))
|
print(u"Book Key Found! ({0})".format(bookKey.encode('hex')))
|
||||||
break
|
break
|
||||||
|
|
||||||
if not bookKey:
|
if not bookKey:
|
||||||
@@ -357,7 +358,7 @@ class TopazBook:
|
|||||||
self.setBookKey(bookKey)
|
self.setBookKey(bookKey)
|
||||||
self.createBookDirectory()
|
self.createBookDirectory()
|
||||||
self.extractFiles()
|
self.extractFiles()
|
||||||
print u"Successfully Extracted Topaz contents"
|
print(u"Successfully Extracted Topaz contents")
|
||||||
if inCalibre:
|
if inCalibre:
|
||||||
from calibre_plugins.dedrm import genbook
|
from calibre_plugins.dedrm import genbook
|
||||||
else:
|
else:
|
||||||
@@ -365,7 +366,7 @@ class TopazBook:
|
|||||||
|
|
||||||
rv = genbook.generateBook(self.outdir, raw, fixedimage)
|
rv = genbook.generateBook(self.outdir, raw, fixedimage)
|
||||||
if rv == 0:
|
if rv == 0:
|
||||||
print u"Book Successfully generated"
|
print(u"Book Successfully generated")
|
||||||
return rv
|
return rv
|
||||||
|
|
||||||
def createBookDirectory(self):
|
def createBookDirectory(self):
|
||||||
@@ -394,7 +395,7 @@ class TopazBook:
|
|||||||
ext = u".dat"
|
ext = u".dat"
|
||||||
if name == 'img': ext = u".jpg"
|
if name == 'img': ext = u".jpg"
|
||||||
if name == 'color' : ext = u".jpg"
|
if name == 'color' : ext = u".jpg"
|
||||||
print u"Processing Section: {0}\n. . .".format(name),
|
print(u"Processing Section: {0}\n. . .".format(name), end=' ')
|
||||||
for index in range (0,len(self.bookHeaderRecords[name])) :
|
for index in range (0,len(self.bookHeaderRecords[name])) :
|
||||||
fname = u"{0}{1:04d}{2}".format(name,index,ext)
|
fname = u"{0}{1:04d}{2}".format(name,index,ext)
|
||||||
destdir = outdir
|
destdir = outdir
|
||||||
@@ -407,11 +408,11 @@ class TopazBook:
|
|||||||
if name == 'glyphs':
|
if name == 'glyphs':
|
||||||
destdir = os.path.join(outdir,u"glyphs")
|
destdir = os.path.join(outdir,u"glyphs")
|
||||||
outputFile = os.path.join(destdir,fname)
|
outputFile = os.path.join(destdir,fname)
|
||||||
print u".",
|
print(u".", end=' ')
|
||||||
record = self.getBookPayloadRecord(name,index)
|
record = self.getBookPayloadRecord(name,index)
|
||||||
if record != '':
|
if record != '':
|
||||||
file(outputFile, 'wb').write(record)
|
file(outputFile, 'wb').write(record)
|
||||||
print u" "
|
print(u" ")
|
||||||
|
|
||||||
def getFile(self, zipname):
|
def getFile(self, zipname):
|
||||||
htmlzip = zipfile.ZipFile(zipname,'w',zipfile.ZIP_DEFLATED, False)
|
htmlzip = zipfile.ZipFile(zipname,'w',zipfile.ZIP_DEFLATED, False)
|
||||||
@@ -441,20 +442,20 @@ class TopazBook:
|
|||||||
shutil.rmtree(self.outdir, True)
|
shutil.rmtree(self.outdir, True)
|
||||||
|
|
||||||
def usage(progname):
|
def usage(progname):
|
||||||
print u"Removes DRM protection from Topaz ebooks and extracts the contents"
|
print(u"Removes DRM protection from Topaz ebooks and extracts the contents")
|
||||||
print u"Usage:"
|
print(u"Usage:")
|
||||||
print u" {0} [-k <kindle.k4i>] [-p <comma separated PIDs>] [-s <comma separated Kindle serial numbers>] <infile> <outdir>".format(progname)
|
print(u" {0} [-k <kindle.k4i>] [-p <comma separated PIDs>] [-s <comma separated Kindle serial numbers>] <infile> <outdir>".format(progname))
|
||||||
|
|
||||||
# Main
|
# Main
|
||||||
def cli_main():
|
def cli_main():
|
||||||
argv=unicode_argv()
|
argv=unicode_argv()
|
||||||
progname = os.path.basename(argv[0])
|
progname = os.path.basename(argv[0])
|
||||||
print u"TopazExtract v{0}.".format(__version__)
|
print(u"TopazExtract v{0}.".format(__version__))
|
||||||
|
|
||||||
try:
|
try:
|
||||||
opts, args = getopt.getopt(argv[1:], "k:p:s:x")
|
opts, args = getopt.getopt(argv[1:], "k:p:s:x")
|
||||||
except getopt.GetoptError, err:
|
except getopt.GetoptError, err:
|
||||||
print u"Error in options or arguments: {0}".format(err.args[0])
|
print(u"Error in options or arguments: {0}".format(err.args[0]))
|
||||||
usage(progname)
|
usage(progname)
|
||||||
return 1
|
return 1
|
||||||
if len(args)<2:
|
if len(args)<2:
|
||||||
@@ -464,11 +465,11 @@ def cli_main():
|
|||||||
infile = args[0]
|
infile = args[0]
|
||||||
outdir = args[1]
|
outdir = args[1]
|
||||||
if not os.path.isfile(infile):
|
if not os.path.isfile(infile):
|
||||||
print u"Input File {0} Does Not Exist.".format(infile)
|
print(u"Input File {0} Does Not Exist.".format(infile))
|
||||||
return 1
|
return 1
|
||||||
|
|
||||||
if not os.path.exists(outdir):
|
if not os.path.exists(outdir):
|
||||||
print u"Output Directory {0} Does Not Exist.".format(outdir)
|
print(u"Output Directory {0} Does Not Exist.".format(outdir))
|
||||||
return 1
|
return 1
|
||||||
|
|
||||||
kDatabaseFiles = []
|
kDatabaseFiles = []
|
||||||
@@ -493,19 +494,19 @@ def cli_main():
|
|||||||
|
|
||||||
tb = TopazBook(infile)
|
tb = TopazBook(infile)
|
||||||
title = tb.getBookTitle()
|
title = tb.getBookTitle()
|
||||||
print u"Processing Book: {0}".format(title)
|
print(u"Processing Book: {0}".format(title))
|
||||||
md1, md2 = tb.getPIDMetaInfo()
|
md1, md2 = tb.getPIDMetaInfo()
|
||||||
pids.extend(kgenpids.getPidList(md1, md2, serials, kDatabaseFiles))
|
pids.extend(kgenpids.getPidList(md1, md2, serials, kDatabaseFiles))
|
||||||
|
|
||||||
try:
|
try:
|
||||||
print u"Decrypting Book"
|
print(u"Decrypting Book")
|
||||||
tb.processBook(pids)
|
tb.processBook(pids)
|
||||||
|
|
||||||
print u" Creating HTML ZIP Archive"
|
print(u" Creating HTML ZIP Archive")
|
||||||
zipname = os.path.join(outdir, bookname + u"_nodrm.htmlz")
|
zipname = os.path.join(outdir, bookname + u"_nodrm.htmlz")
|
||||||
tb.getFile(zipname)
|
tb.getFile(zipname)
|
||||||
|
|
||||||
print u" Creating SVG ZIP Archive"
|
print(u" Creating SVG ZIP Archive")
|
||||||
zipname = os.path.join(outdir, bookname + u"_SVG.zip")
|
zipname = os.path.join(outdir, bookname + u"_SVG.zip")
|
||||||
tb.getSVGZip(zipname)
|
tb.getSVGZip(zipname)
|
||||||
|
|
||||||
@@ -513,7 +514,7 @@ def cli_main():
|
|||||||
tb.cleanup()
|
tb.cleanup()
|
||||||
|
|
||||||
except DrmException, e:
|
except DrmException, e:
|
||||||
print u"Decryption failed\n{0}".format(traceback.format_exc())
|
print(u"Decryption failed\n{0}".format(traceback.format_exc()))
|
||||||
|
|
||||||
try:
|
try:
|
||||||
tb.cleanup()
|
tb.cleanup()
|
||||||
@@ -522,7 +523,7 @@ def cli_main():
|
|||||||
return 1
|
return 1
|
||||||
|
|
||||||
except Exception, e:
|
except Exception, e:
|
||||||
print u"Decryption failed\m{0}".format(traceback.format_exc())
|
print(u"Decryption failed\m{0}".format(traceback.format_exc()))
|
||||||
try:
|
try:
|
||||||
tb.cleanup()
|
tb.cleanup()
|
||||||
except:
|
except:
|
||||||
@@ -3,6 +3,8 @@
|
|||||||
|
|
||||||
from __future__ import with_statement
|
from __future__ import with_statement
|
||||||
|
|
||||||
|
from ignoblekeygen import generate_key
|
||||||
|
|
||||||
__license__ = 'GPL v3'
|
__license__ = 'GPL v3'
|
||||||
|
|
||||||
DETAILED_MESSAGE = \
|
DETAILED_MESSAGE = \
|
||||||
@@ -2,11 +2,12 @@
|
|||||||
# -*- coding: utf-8 -*-
|
# -*- coding: utf-8 -*-
|
||||||
|
|
||||||
from __future__ import with_statement
|
from __future__ import with_statement
|
||||||
|
from __future__ import print_function
|
||||||
|
|
||||||
__license__ = 'GPL v3'
|
__license__ = 'GPL v3'
|
||||||
|
|
||||||
# Standard Python modules.
|
# Standard Python modules.
|
||||||
import os, sys, re, hashlib
|
import os, sys, re, hashlib, traceback
|
||||||
from calibre_plugins.dedrm.__init__ import PLUGIN_NAME, PLUGIN_VERSION
|
from calibre_plugins.dedrm.__init__ import PLUGIN_NAME, PLUGIN_VERSION
|
||||||
|
|
||||||
def WineGetKeys(scriptpath, extension, wineprefix=""):
|
def WineGetKeys(scriptpath, extension, wineprefix=""):
|
||||||
@@ -20,37 +21,39 @@ def WineGetKeys(scriptpath, extension, wineprefix=""):
|
|||||||
import json
|
import json
|
||||||
|
|
||||||
basepath, script = os.path.split(scriptpath)
|
basepath, script = os.path.split(scriptpath)
|
||||||
print u"{0} v{1}: Running {2} under Wine".format(PLUGIN_NAME, PLUGIN_VERSION, script)
|
print(u"{0} v{1}: Running {2} under Wine".format(PLUGIN_NAME, PLUGIN_VERSION, script))
|
||||||
|
|
||||||
outdirpath = os.path.join(basepath, u"winekeysdir")
|
outdirpath = os.path.join(basepath, u"winekeysdir")
|
||||||
if not os.path.exists(outdirpath):
|
if not os.path.exists(outdirpath):
|
||||||
os.makedirs(outdirpath)
|
os.makedirs(outdirpath)
|
||||||
|
|
||||||
wineprefix = os.path.abspath(os.path.expanduser(os.path.expandvars(wineprefix)))
|
if wineprefix != "":
|
||||||
|
wineprefix = os.path.abspath(os.path.expanduser(os.path.expandvars(wineprefix)))
|
||||||
|
|
||||||
if wineprefix != "" and os.path.exists(wineprefix):
|
if wineprefix != "" and os.path.exists(wineprefix):
|
||||||
cmdline = u"WINEPREFIX=\"{2}\" wine python.exe \"{0}\" \"{1}\"".format(scriptpath,outdirpath,wineprefix)
|
cmdline = u"WINEPREFIX=\"{2}\" wine python.exe \"{0}\" \"{1}\"".format(scriptpath,outdirpath,wineprefix)
|
||||||
else:
|
else:
|
||||||
cmdline = u"wine python.exe \"{0}\" \"{1}\"".format(scriptpath,outdirpath)
|
cmdline = u"wine python.exe \"{0}\" \"{1}\"".format(scriptpath,outdirpath)
|
||||||
print u"{0} v{1}: Command line: '{2}'".format(PLUGIN_NAME, PLUGIN_VERSION, cmdline)
|
print(u"{0} v{1}: Command line: '{2}'".format(PLUGIN_NAME, PLUGIN_VERSION, cmdline))
|
||||||
|
|
||||||
try:
|
try:
|
||||||
cmdline = cmdline.encode(sys.getfilesystemencoding())
|
cmdline = cmdline.encode(sys.getfilesystemencoding())
|
||||||
p2 = Process(cmdline, shell=True, bufsize=1, stdin=None, stdout=sys.stdout, stderr=STDOUT, close_fds=False)
|
p2 = Process(cmdline, shell=True, bufsize=1, stdin=None, stdout=sys.stdout, stderr=STDOUT, close_fds=False)
|
||||||
result = p2.wait("wait")
|
result = p2.wait("wait")
|
||||||
except Exception, e:
|
except Exception, e:
|
||||||
print u"{0} v{1}: Wine subprocess call error: {2}".format(PLUGIN_NAME, PLUGIN_VERSION, e.args[0])
|
print(u"{0} v{1}: Wine subprocess call error: {2}".format(PLUGIN_NAME, PLUGIN_VERSION, e.args[0]))
|
||||||
if wineprefix != "" and os.path.exists(wineprefix):
|
if wineprefix != "" and os.path.exists(wineprefix):
|
||||||
cmdline = u"WINEPREFIX=\"{2}\" wine C:\\Python27\\python.exe \"{0}\" \"{1}\"".format(scriptpath,outdirpath,wineprefix)
|
cmdline = u"WINEPREFIX=\"{2}\" wine C:\\Python27\\python.exe \"{0}\" \"{1}\"".format(scriptpath,outdirpath,wineprefix)
|
||||||
else:
|
else:
|
||||||
cmdline = u"wine C:\\Python27\\python.exe \"{0}\" \"{1}\"".format(scriptpath,outdirpath)
|
cmdline = u"wine C:\\Python27\\python.exe \"{0}\" \"{1}\"".format(scriptpath,outdirpath)
|
||||||
print u"{0} v{1}: Command line: “{2}”".format(PLUGIN_NAME, PLUGIN_VERSION, cmdline)
|
print(u"{0} v{1}: Command line: “{2}”".format(PLUGIN_NAME, PLUGIN_VERSION, cmdline))
|
||||||
|
|
||||||
try:
|
try:
|
||||||
cmdline = cmdline.encode(sys.getfilesystemencoding())
|
cmdline = cmdline.encode(sys.getfilesystemencoding())
|
||||||
p2 = Process(cmdline, shell=True, bufsize=1, stdin=None, stdout=sys.stdout, stderr=STDOUT, close_fds=False)
|
p2 = Process(cmdline, shell=True, bufsize=1, stdin=None, stdout=sys.stdout, stderr=STDOUT, close_fds=False)
|
||||||
result = p2.wait("wait")
|
result = p2.wait("wait")
|
||||||
except Exception, e:
|
except Exception, e:
|
||||||
print u"{0} v{1}: Wine subprocess call error: {2}".format(PLUGIN_NAME, PLUGIN_VERSION, e.args[0])
|
print(u"{0} v{1}: Wine subprocess call error: {2}".format(PLUGIN_NAME, PLUGIN_VERSION, e.args[0]))
|
||||||
|
|
||||||
# try finding winekeys anyway, even if above code errored
|
# try finding winekeys anyway, even if above code errored
|
||||||
winekeys = []
|
winekeys = []
|
||||||
@@ -66,8 +69,8 @@ def WineGetKeys(scriptpath, extension, wineprefix=""):
|
|||||||
new_key_value = keyfile.read()
|
new_key_value = keyfile.read()
|
||||||
winekeys.append(new_key_value)
|
winekeys.append(new_key_value)
|
||||||
except:
|
except:
|
||||||
print u"{0} v{1}: Error loading file {2}".format(PLUGIN_NAME, PLUGIN_VERSION, filename)
|
print(u"{0} v{1}: Error loading file {2}".format(PLUGIN_NAME, PLUGIN_VERSION, filename))
|
||||||
traceback.print_exc()
|
traceback.print_exc()
|
||||||
os.remove(fpath)
|
os.remove(fpath)
|
||||||
print u"{0} v{1}: Found and decrypted {2} {3}".format(PLUGIN_NAME, PLUGIN_VERSION, len(winekeys), u"key file" if len(winekeys) == 1 else u"key files")
|
print(u"{0} v{1}: Found and decrypted {2} {3}".format(PLUGIN_NAME, PLUGIN_VERSION, len(winekeys), u"key file" if len(winekeys) == 1 else u"key files"))
|
||||||
return winekeys
|
return winekeys
|
||||||
@@ -14,6 +14,7 @@
|
|||||||
"""
|
"""
|
||||||
Re-write zip (or ePub) fixing problems with file names (and mimetype entry).
|
Re-write zip (or ePub) fixing problems with file names (and mimetype entry).
|
||||||
"""
|
"""
|
||||||
|
from __future__ import print_function
|
||||||
|
|
||||||
__license__ = 'GPL v3'
|
__license__ = 'GPL v3'
|
||||||
__version__ = "1.1"
|
__version__ = "1.1"
|
||||||
@@ -156,22 +157,22 @@ class fixZip:
|
|||||||
|
|
||||||
|
|
||||||
def usage():
|
def usage():
|
||||||
print """usage: zipfix.py inputzip outputzip
|
print("""usage: zipfix.py inputzip outputzip
|
||||||
inputzip is the source zipfile to fix
|
inputzip is the source zipfile to fix
|
||||||
outputzip is the fixed zip archive
|
outputzip is the fixed zip archive
|
||||||
"""
|
""")
|
||||||
|
|
||||||
|
|
||||||
def repairBook(infile, outfile):
|
def repairBook(infile, outfile):
|
||||||
if not os.path.exists(infile):
|
if not os.path.exists(infile):
|
||||||
print "Error: Input Zip File does not exist"
|
print("Error: Input Zip File does not exist")
|
||||||
return 1
|
return 1
|
||||||
try:
|
try:
|
||||||
fr = fixZip(infile, outfile)
|
fr = fixZip(infile, outfile)
|
||||||
fr.fix()
|
fr.fix()
|
||||||
return 0
|
return 0
|
||||||
except Exception, e:
|
except Exception, e:
|
||||||
print "Error Occurred ", e
|
print("Error Occurred ", e)
|
||||||
return 2
|
return 2
|
||||||
|
|
||||||
|
|
||||||
34
DeDRM_plugin_ReadMe.txt
Normal file
34
DeDRM_plugin_ReadMe.txt
Normal file
@@ -0,0 +1,34 @@
|
|||||||
|
DeDRM_plugin.zip
|
||||||
|
================
|
||||||
|
|
||||||
|
This plugin will remove the DRM from:
|
||||||
|
|
||||||
|
- Kindle ebooks (files from Kindle for Mac/PC and eInk Kindles).
|
||||||
|
- Adobe Digital Editions (v2.0.1***) ePubs (including Kobo and Google ePubs downloaded to ADE)
|
||||||
|
- Adobe Digital Editions (v2.0.1) PDFs
|
||||||
|
|
||||||
|
For limitations and work-arounds, see the FAQ at https://github.com/apprenticeharper/DeDRM_tools/blob/master/FAQs.md
|
||||||
|
|
||||||
|
|
||||||
|
Installation
|
||||||
|
------------
|
||||||
|
Open calibre's Preferences dialog. Click on the "Plugins" button. Next, click on the button, "Load plugin from file". Navigate to the unzipped DeDRM_tools folder, find the file "DeDRM_plugin.zip". Click to select the file and select "Open". Click "Yes" in the "Are you sure?" dialog box. Click the "OK" button in the "Success" dialog box.
|
||||||
|
|
||||||
|
|
||||||
|
Customization
|
||||||
|
-------------
|
||||||
|
For Kindle ebooks from an E-Ink based Kindle (e.g. Voyage), or books downloaded from the Amazon web site 'for transfer via USB' to an E-Ink base Kindle, you must enter the Kindle's serial number in the customisation dialog.
|
||||||
|
|
||||||
|
When you have finished entering your configuration information, you must click the OK button to save it. If you click the Cancel button, all your changes in all the configuration dialogs will be lost.
|
||||||
|
|
||||||
|
|
||||||
|
Troubleshooting
|
||||||
|
---------------
|
||||||
|
If you find that the DeDRM plugin is not working for you (imported ebooks still have DRM - that is, they won't convert or open in the calibre ebook viewer), you should make a log of the import process by deleting the DRMed ebook from calibre and then adding the ebook to calibre when it's running in debug mode. This will generate a lot of helpful debugging info that can be copied into any online help requests. Here's how to do it:
|
||||||
|
|
||||||
|
- Remove the DRMed book from calibre.
|
||||||
|
- Click the Preferences drop-down menu and choose 'Restart in debug mode'.
|
||||||
|
- Once calibre has re-started, import the problem ebook.
|
||||||
|
- Now close calibre.
|
||||||
|
|
||||||
|
A log will appear that you can copy and paste into a comment at Apprentice Alf's blog, http://apprenticealf.wordpress.com/ or an issue at Apprentice Harper's repository, https://github.com/apprenticeharper/DeDRM_tools/issues . You should also give details of your computer, and how you obtained the ebook file.
|
||||||
113
FAQs.md
113
FAQs.md
@@ -19,107 +19,90 @@ Just download and use these tools, that's all! Uh, almost. There are a few, uh,
|
|||||||
But otherwise, if your ebook is from Amazon, Kobo, Barnes & Noble or any of the ebook stores selling ebooks compatible with Adobe Digital Editions 2.0.1, you should be able to remove the DRM that's been applied to your ebooks.
|
But otherwise, if your ebook is from Amazon, Kobo, Barnes & Noble or any of the ebook stores selling ebooks compatible with Adobe Digital Editions 2.0.1, you should be able to remove the DRM that's been applied to your ebooks.
|
||||||
|
|
||||||
### A Recent Change to Kindle for PC/Kindle for Mac
|
### A Recent Change to Kindle for PC/Kindle for Mac
|
||||||
Starting with version 1.19, Kindle for PC/Mac uses Amazon's new KFX format which isn't quite as good a source fro conversion to ePub as the older KF8 (& MOBI) formats. There are two options to get the older formats. Either stick with version 1.17 or earlier, or modify the executable by changing a file name. Note that with Kindle for Mac 1.25 and later, there is no current solution even for FKX. You must use 1.24 or earlier.
|
Starting with version 1.19, Kindle for PC/Mac uses Amazon's new KFX format which isn't quite as good a source for conversion to ePub as the older KF8 (& MOBI) formats. There are two options to get the older formats. Either stick with version 1.17 or earlier, or modify the executable by changing a file name (PC) or disabling a component of the application (Mac).
|
||||||
|
|
||||||
Version 1.17 of Kindle is are no longer available directly from Amazon, so you will need to search for the proper file name and find it on a third party site. The name is "KindleForPC-installer-1.17.44170.exe" for PC and "KindleForMac-44182.dmg" for Mac.
|
Version 1.17 of Kindle is no longer available directly from Amazon, so you will need to search for the proper file name and find it on a third party site. The name is `KindleForPC-installer-1.17.44170.exe` for PC and `KindleForMac-44182.dmg` for Mac.
|
||||||
Verify the one of the following cryptographic hash values, using software of your choice, before installing the downloaded file in order to avoid viruses. If the hash does not match, delete the downloaded file and try again from another site.
|
Verify the one of the following cryptographic hash values, using software of your choice, before installing the downloaded file in order to avoid viruses. If the hash does not match, delete the downloaded file and try again from another site.
|
||||||
Kindle for PC:
|
|
||||||
MD-5: 53F793B562F4823721AA47D7DE099869
|
|
||||||
SHA-1: 73C404D719F0DD8D4AE1C2C96612B095D6C86255
|
|
||||||
SHA-256: 14E0F0053F1276C0C7C446892DC170344F707FBFE99B695176 2C120144163200
|
|
||||||
Kindle for Mac:
|
|
||||||
MD-5: E7E36D5369E1F3CF1D28E5D9115DF15F
|
|
||||||
SHA-1: 7AB9A86B954CB23D622BD79E3257F8E2182D791C
|
|
||||||
SHA-256: 28DC21246A9C7CDEDD2D6F0F4082E6BF7EF9DB9CE9D485548E 8A9E1D19EAE2AC.
|
|
||||||
|
|
||||||
You will need to go to the preferences and uncheck the auto update checkbox. Then download and install 1.17 over the top of the 1.19 installation. You'll also need to delete the KFX folders from your My Kindle Content folder.
|
#### Kindle for PC `KindleForPC-installer-1.17.44170.exe`:
|
||||||
|
* MD-5: 53F793B562F4823721AA47D7DE099869
|
||||||
|
* SHA-1: 73C404D719F0DD8D4AE1C2C96612B095D6C86255
|
||||||
|
* SHA-256: 14E0F0053F1276C0C7C446892DC170344F707FBFE99B695176 2C120144163200
|
||||||
|
|
||||||
A other possible solution is to use 1.19 or later, but disable KFX by renaming or disabling a necessary component of the application. This may or may not work on versions after 1.20. In a command window, enter the following commands when Kindle for PC/Mac is not running:
|
#### Kindle for Mac `KindleForMac-44182.dmg`:
|
||||||
|
* MD-5: E7E36D5369E1F3CF1D28E5D9115DF15F
|
||||||
|
* SHA-1: 7AB9A86B954CB23D622BD79E3257F8E2182D791C
|
||||||
|
* SHA-256: 28DC21246A9C7CDEDD2D6F0F4082E6BF7EF9DB9CE9D485548E 8A9E1D19EAE2AC.
|
||||||
|
|
||||||
|
You will need to go to the preferences and uncheck the auto update checkbox. Then download and install 1.17 over the top of the newer installation. You'll also need to delete the KFX folders from your My Kindle Content folder.
|
||||||
|
|
||||||
|
Another possible solution is to use 1.19 or later, but disable KFX by renaming or disabling a necessary component of the application. This may or may not work on versions after 1.25. In a command window, enter the following commands when Kindle for PC/Mac is not running:
|
||||||
|
|
||||||
#### Windows
|
#### Windows
|
||||||
ren %localappdata%\Amazon\Kindle\application\renderer-test.exe renderer-test.xxx
|
`ren %localappdata%\Amazon\Kindle\application\renderer-test.exe renderer-test.xxx`
|
||||||
|
|
||||||
PC Note: The renderer-test program may be in a different location in some Kindle for PC installations. If the rename command fails look in other folders, such as C:\Program Files\Amazon\Kindle.
|
PC Note: The renderer-test program may be in a different location in some Kindle for PC installations. If the rename command fails look in other folders, such as `C:\Program Files\Amazon\Kindle`.
|
||||||
|
|
||||||
#### Macintosh
|
#### Macintosh
|
||||||
chmod -x /Applications/Kindle.app/Contents/MacOS/renderer-test
|
`chmod -x /Applications/Kindle.app/Contents/MacOS/renderer-test`
|
||||||
|
|
||||||
Mac Note: If the chmod command fails with a permission error try again using sudo before chmod - sudo chmod [...]
|
Mac Note: If the chmod command fails with a permission error try again using `sudo` before `chmod` - `sudo chmod` [...]
|
||||||
|
|
||||||
After restarting the Kindle program any books previously downloaded in KFX format will no longer open. You will need to remove them from your device and re-download them. All future downloads will use the older Kindle formats instead of KFX although they will continue to be placed in one individual subdirectory per book.
|
After restarting the Kindle program any books previously downloaded in KFX format will no longer open. You will need to remove them from your device and re-download them. All future downloads will use the older Kindle formats instead of KFX although they will continue to be placed in one individual subdirectory per book. Note that books soudl be downoad by right-click and 'Download', not by just opening the book. Recent (1.25+) versions of Kindle for Mac/PC may convert KF8 files to a new format that is not supported by these tools when the book is opened for reading.
|
||||||
|
|
||||||
#### Decrypting KFX
|
#### Decrypting KFX
|
||||||
Thanks to work by several people, the tools can now decrypt KFX format ebooks from Kindle for PC. In addition to the DeDRM plugin, calibre users will also need to install jhowell's KFX Input plugin which is available through the standard plugin menu in calibre, or directly from [his plugin thread](https://www.mobileread.com/forums/showthread.php?t=291290) on Mobileread. Not that KFX decryption does not work for Kindle for Mac 1.25 and later.
|
Thanks to work by several people, the tools can now decrypt KFX format ebooks from Kindle for Mac/PC. In addition to the DeDRM plugin, calibre users will also need to install jhowell's KFX Input plugin which is available through the standard plugin menu in calibre, or directly from [his plugin thread](https://www.mobileread.com/forums/showthread.php?t=291290) on Mobileread.
|
||||||
|
|
||||||
#### Thanks
|
#### Thanks
|
||||||
Thanks to jhowell for his investigations into KFX format and the KFX Input plugin. Some of these instructions are from [his thread on the subject](https://www.mobileread.com/forums/showthread.php?t=283371) at MobileRead.
|
Thanks to jhowell for his investigations into KFX format and the KFX Input plugin. Some of these instructions are from [his thread on the subject](https://www.mobileread.com/forums/showthread.php?t=283371) at MobileRead.
|
||||||
|
|
||||||
## Where can I get the latest version of these free DRM removal tools?
|
## Where can I get the latest version of these free DRM removal tools?
|
||||||
Right here at github. Just go to the [releases page](https://github.com/apprenticeharper/DeDRM_tools/releases) and download the latest zip archive of the tools, named DeDRM\_tools\_X.X.X.zip, where X.X.X is the version number. You do not need to download the source code archive.
|
Right here at github. Just go to the [releases page](https://github.com/apprenticeharper/DeDRM_tools/releases) and download the latest zip archive of the tools, named `DeDRM\_tools\_X.X.X.zip`, where X.X.X is the version number. You do not need to download the source code archive.
|
||||||
|
|
||||||
## I've downloaded the tools archive. Now what?
|
## I've downloaded the tools archive. Now what?
|
||||||
First, unzip the archive. You should now have a DeDRM folder containing several other folders and a ReadMe\_First.txt file. Please read the ReadMe\_First file! That will explain what the folders are, and you'll be able to work out which of the tools you need.
|
First, unzip the archive. You should now have a DeDRM folder containing several other folders and a `ReadMe_Overview.txt` file. Please read the `ReadMe_Overview.txt` file! That will explain what the folders are, and you'll be able to work out which of the tools you need.
|
||||||
|
|
||||||
## That's a big complicated ReadMe file! Isn't there a quick guide?
|
## That's a big complicated ReadMe file! Isn't there a quick guide?
|
||||||
Install calibre. Install the DeDRM\_plugin in calibre. Install the Obok\_plugin in calibre. Restart calibre. In the DeDRM_plugin customisation dialog add in any E-Ink Kindle serial numbers and your B&N account email address and password. Remember that the plugin only tries to remove DRM when ebooks are imported.
|
Install calibre. Install the DeDRM\_plugin in calibre. Install the Obok\_plugin in calibre. Restart calibre. In the DeDRM_plugin customisation dialog add in any E-Ink Kindle serial numbers. Remember that the plugin only tries to remove DRM when ebooks are imported.
|
||||||
|
|
||||||
# Installing the Tools
|
# Installing the Tools
|
||||||
## The calibre plugin
|
## The calibre plugin
|
||||||
### I am trying to install the calibre plugin, but calibre says "ERROR: Unhandled exception: InvalidPlugin: The plugin in u’[path]DeDRM\_tools\_6.5.3.zip’ is invalid. It does not contain a top-level \_\_init\_\_.py file"
|
### I am trying to install the calibre plugin, but calibre says "ERROR: Unhandled exception: InvalidPlugin: The plugin in u’[path]DeDRM\_tools\_6.5.3.zip’ is invalid. It does not contain a top-level \_\_init\_\_.py file"
|
||||||
You are trying to add the tools archive (e.g. DeDRM\_tools\_6.5.3.zip) instead of the plugin. The tools archive is not the plugin. It is a collection of DRM removal tools which includes the plugin. You must unzip the archive, and install the calibre plugin (DeDRM\_plugin.zip) from a folder called “DeDRM\_calibre_plugin” in the unzipped archive.
|
You are trying to add the tools archive (e.g. `DeDRM_tools_6.5.3.zip`) instead of the plugin. The tools archive is not the plugin. It is a collection of DRM removal tools which includes the plugin. You must unzip the archive, and install the calibre plugin `DeDRM_plugin.zip` from a folder called `DeDRM_calibre_plugin` in the unzipped archive.
|
||||||
|
|
||||||
### I’ve unzipped the tools archive, but I can’t find the calibre plugin when I try to add them to calibre. I use Windows.
|
### I’ve unzipped the tools archive, but I can’t find the calibre plugin when I try to add them to calibre. I use Windows.
|
||||||
You should select the zip file that is in the “DeDRM\_calibre\_plugin” folder, not any files inside the plugin’s zip archive. Make sure you are selecting from the folder that you created when you unzipped the tools archive and not selecting a file inside the still-zipped tools archive.
|
You should select the zip file that is in the `DeDRM_calibre_plugin` folder, not any files inside the plugin’s zip archive. Make sure you are selecting from the folder that you created when you unzipped the tools archive and not selecting a file inside the still-zipped tools archive.
|
||||||
|
|
||||||
(The problem is that Windows will allow apps to browse inside zip archives without needing to unzip them first. If there are zip archives inside the main zip archives, Windows will show them as unzipped as well. So what happens is people will unzip the DeDRM\_tools\_X.X.X.zip to a folder, but when using calibre they will actually navigate to the still zipped file by mistake and cannot tell they have done so because they do not have file extensions showing. So to the unwary Windows user, it appears that the zip archive was unzipped and that everything inside it was unzipped as well so there is no way to install the plugins.
|
(The problem is that Windows will allow apps to browse inside zip archives without needing to unzip them first. If there are zip archives inside the main zip archives, Windows will show them as unzipped as well. So what happens is people will unzip the `DeDRM_tools_X.X.X.zip` to a folder, but when using calibre they will actually navigate to the still zipped file by mistake and cannot tell they have done so because they do not have file extensions showing. So to the unwary Windows user, it appears that the zip archive was unzipped and that everything inside it was unzipped as well so there is no way to install the plugins.
|
||||||
|
|
||||||
We strongly recommend renaming the DeDRM\_tools\_X.X.X.zip archive (after extracting its contents) to DeDRM\_tools\_X.X.X_archive.zip. If you do that, you are less likely to navigate to the wrong location from inside calibre.)
|
We strongly recommend renaming the `DeDRM_tools_X.X.X.zip` archive (after extracting its contents) to `DeDRM_tools_X.X.X_archive.zip`. If you do that, you are less likely to navigate to the wrong location from inside calibre.)
|
||||||
|
|
||||||
## The Windows Application
|
|
||||||
### I've installed ActiveState Python and PyCrypto, but the Windows application won't run. What have I done wrong?
|
|
||||||
Nothing. There's a bug in the some older ActiveState Python Windows installers that puts the Tcl code in the wrong place. See [this comment of mine at ActiveState community](https://community.activestate.com/node/19090). Just move the Tcl code to the correct place manually and the Windows app should run.
|
|
||||||
|
|
||||||
## The Macintosh Application
|
|
||||||
### I can't open the Macintosh Application. Some message about it not being signed or something.
|
|
||||||
Try right-clicking and select open. That might give you the option to open it anyway. Otherwise you'll need to change your security settings to allow unsigned applications to run. You can probably change these back after running it for the first time.
|
|
||||||
|
|
||||||
### I can't open the Macintosh Application at all. I get 'The aplication "DeDRM" can't be opened'
|
|
||||||
Some unzip applications do not respect the execution bit setting. Try unzipping the main tools archive using the built-in Mac unzip utility.
|
|
||||||
|
|
||||||
Alternatively, sometimes the execution bit isn't set correctly in the archive. If you put the extracted DeDRM application in your Applications folder, you can set the executable bit on the 'droplet' file from the terminal using the command chmod +x /Applications/DeDRM.app/Contents/MacOS/droplet
|
|
||||||
|
|
||||||
### I can't open the Macintosh Application at all. I get 'spawn_via_launchd() failed, errno=111'
|
|
||||||
There seems to be a bug in Apple's launch services. Try using the free [Maintenance utility](https://www.titanium-software.fr/en/maintenance.html) from Titanium Software to clear the launch cache and database.
|
|
||||||
|
|
||||||
### The application opens, but always gives an error in the log 'ImportError: No module named Crypto.Cipher'
|
|
||||||
Some version of MacOS don't include PyCrpto. Your should be able to install it by using this command in the Terminal app: python -m pip pycrypto
|
|
||||||
|
|
||||||
# Using the Tools
|
# Using the Tools
|
||||||
## I can’t get the tools to work on my rented or library ebooks.
|
## I can’t get the tools to work on my rented or library ebooks.
|
||||||
The tools are not designed to remove DRM from rented or library ebooks.
|
The tools are not designed to remove DRM from rented or library ebooks.
|
||||||
|
|
||||||
## I've unzipped the tools, but what are all the different files, and how do I use them?
|
## I've unzipped the tools, but what are all the different files, and how do I use them?
|
||||||
Read the ReadMe_First.txt file and then the ReadMe files included in the tools folder(s) you're interested in. That's what they're for.
|
Read the `ReadMe_Overview.txt` file and then the ReadMe files for the tools you're interested in. That's what they're for.
|
||||||
|
|
||||||
## I have installed the calibre plugin, but my books still have DRM. When I try to view or convert my books, calibre says they have DRM.
|
## I have installed the calibre plugin, but my books still have DRM. When I try to view or convert my books, calibre says they have DRM.
|
||||||
DRM only gets removed when an ebook is imported into calibre. Also, if the book is already in calibre, by default calibre will discard the newly imported file. You can change this in calibre's Adding books preferences page (Automerge..../Overwrite....), so that newly imported files overwrite existing ebook formats. Then just re-import your books and the DRM-free versions will overwrite the DRMed versions while retaining your books' metadata.
|
DRM only gets removed when an ebook is imported into calibre. Also, if the book is already in calibre, by default calibre will discard the newly imported file. You can change this in calibre's Adding books preferences page (Automerge..../Overwrite....), so that newly imported files overwrite existing ebook formats. Then just re-import your books and the DRM-free versions will overwrite the DRMed versions while retaining your books' metadata.
|
||||||
|
|
||||||
## I have installed the calibre plugin or I am trying to use one of the other tools, but I don’t know where my ebooks are stored.
|
## I have installed the calibre plugin, but I don’t know where my ebooks are stored.
|
||||||
Your ebooks are stored on your computer or on your ebook reader. You need to find them to be able to remove the DRM. If they are on your reader, you should be able to locate them easily. On your computer it’s not so obvious. Here are the default locations.
|
Your ebooks are stored on your computer or on your ebook reader. You need to find them to be able to remove the DRM. If they are on your reader, you should be able to locate them easily. On your computer it’s not so obvious. Here are the default locations.
|
||||||
|
|
||||||
### Macintosh
|
### Macintosh
|
||||||
Navigating from your home folder,
|
Navigating from your home folder,
|
||||||
|
|
||||||
Kindle for Mac ebooks are in either Library/Application Support/Kindle/My Kindle Content or Documents/My Kindle Content or Library/Containers/com.amazon.Kindle/Data/Library/Application Support/Kindle/My Kindle Content, depending on your version of Kindle for Mac.
|
Kindle for Mac ebooks are in either `Library/Application Support/Kindle/My Kindle Content` or `Documents/My Kindle Content or Library/Containers/com.amazon.Kindle/Data/Library/Application Support/Kindle/My Kindle Content`, depending on your version of Kindle for Mac.
|
||||||
|
|
||||||
Adobe Digital Editions ebooks are in Documents/Digital Editions
|
Adobe Digital Editions ebooks are in `Documents/Digital Editions`
|
||||||
|
|
||||||
### Windows
|
### Windows
|
||||||
Navigating from your "Documents" folder ("My Documents" folder, pre-Windows 7)
|
Navigating from your `Documents` folder (`My Documents` folder, pre-Windows 7)
|
||||||
|
|
||||||
Kindle for PC ebooks are in My Kindle Content
|
Kindle for PC ebooks are in `My Kindle Content`
|
||||||
|
|
||||||
Adobe Digital Editions ebooks are in My Digital Editions
|
Adobe Digital Editions ebooks are in `My Digital Editions`
|
||||||
|
|
||||||
|
|
||||||
## I have installed the calibre plugin, and the book is not already in calibre, but the DRM does not get removed.
|
## I have installed the calibre plugin, and the book is not already in calibre, but the DRM does not get removed.
|
||||||
@@ -133,15 +116,13 @@ If this book is from an eInk Kindle (e.g. Paperwhite), you must enter the serial
|
|||||||
|
|
||||||
If this book is from Kindle for Mac or Kindle for PC, you must have the Kindle Software installed on the same computer and user account as your copy of calibre.
|
If this book is from Kindle for Mac or Kindle for PC, you must have the Kindle Software installed on the same computer and user account as your copy of calibre.
|
||||||
|
|
||||||
If this book is from Kindle for Mac you must be using version 1.24 or below, even if you have the Input plugin installed.
|
|
||||||
|
|
||||||
If the book is from Kindle for PC or Kindle for Mac and you think you are doing everything right, and you are getting this message, it is possible that the files containing the encryption key aren’t quite in the format the tools expect. To try to fix this:
|
If the book is from Kindle for PC or Kindle for Mac and you think you are doing everything right, and you are getting this message, it is possible that the files containing the encryption key aren’t quite in the format the tools expect. To try to fix this:
|
||||||
|
|
||||||
1. Deregister Kindle for PC(Mac) from your Amazon account.
|
1. Deregister Kindle for PC(Mac) from your Amazon account.
|
||||||
1. Uninstall Kindle for PC(Mac)
|
1. Uninstall Kindle for PC(Mac)
|
||||||
1. Delete the Kindle for PC(Mac) preferences
|
1. Delete the Kindle for PC(Mac) preferences
|
||||||
* PC: Delete the directory [home folder]\AppData\Local\Amazon (it might be hidden) and [home folder]\My Documents\My Kindle Content
|
* PC: Delete the directory `[home folder]\AppData\Local\Amazon` (it might be hidden) and `[home folder]\My Documents\My Kindle Content`
|
||||||
* Mac: Delete the directory [home folder]/Library/Application Support/Kindle/and/or [home folder]/Library/Containers/com.amazon.Kindle/Data/Library/Application Support/Kindle/ (one or both may be present and should be deleted)
|
* Mac: Delete the directory `[home folder]/Library/Application Support/Kindle/` and/or `[home folder]/Library/Containers/com.amazon.Kindle/Data/Library/Application Support/Kindle/` (one or both may be present and should be deleted)
|
||||||
1. Reinstall Kindle for PC(Mac) version 1.17 or earlier (see above for download links).
|
1. Reinstall Kindle for PC(Mac) version 1.17 or earlier (see above for download links).
|
||||||
1. Re-register Kindle for PC(Mac) with your Amazon account
|
1. Re-register Kindle for PC(Mac) with your Amazon account
|
||||||
1. Download the ebook again. Do not use the files you have downloaded previously.
|
1. Download the ebook again. Do not use the files you have downloaded previously.
|
||||||
@@ -154,16 +135,13 @@ There are several possible reasons why only some books get their DRM removed.
|
|||||||
If you are still having problems with particular books, you will need to create a log of the DRM removal attempt for one of the problem books, and post that in a comment at Apprentice Alf's blog or in a new issue at Apprentice Harper's github repository.
|
If you are still having problems with particular books, you will need to create a log of the DRM removal attempt for one of the problem books, and post that in a comment at Apprentice Alf's blog or in a new issue at Apprentice Harper's github repository.
|
||||||
|
|
||||||
## My Kindle book has imported and the DRM has been removed, but all the pictures are gone.
|
## My Kindle book has imported and the DRM has been removed, but all the pictures are gone.
|
||||||
Most likely, this is a book downloaded from Amazon directly to an eInk Kindle (e.g. Paperwhite). Unfortunately, the pictures are probably in a .azw6 file that the tools don't understand. You must download the book manually from Amazon's web site "For transfer via USB" to your Kindle. When you download the eBook in this manner, Amazon will package the pictures in the with text in a single file that the tools will be able to import successfully.
|
Most likely, this is a book downloaded from Amazon directly to an eInk Kindle (e.g. Paperwhite). Unfortunately, the pictures are probably in a `.azw6` file that the tools don't understand. You must download the book manually from Amazon's web site "For transfer via USB" to your Kindle. When you download the eBook in this manner, Amazon will package the pictures in the with text in a single file that the tools will be able to import successfully.
|
||||||
|
|
||||||
## My Kindle book has imported, but it's showing up as an AZW4 format. Conversions take a long time and/or are very poor.
|
## My Kindle book has imported, but it's showing up as an AZW4 format. Conversions take a long time and/or are very poor.
|
||||||
You have found a Print Replica Kindle ebook. This is a PDF in a Kindle wrapper. Now the DRM has been removed, you can extract the PDF from the wrapper using the KindleUnpack plugin. Conversion of PDFs rarely gives good results.
|
You have found a Print Replica Kindle ebook. This is a PDF in a Kindle wrapper. Now the DRM has been removed, you can extract the PDF from the wrapper using the KindleUnpack plugin. Conversion of PDFs rarely gives good results.
|
||||||
|
|
||||||
## The tools can't see an ebook that was downloaded directly to my eInk kindle, although it's definitely there, and I can read it on the Kindle. I can't even try to import it.
|
|
||||||
Mostly likely, this is a book downloaded from Amazon directly to one of the newer eInk Kindles (e.g. Paperwhite). Unfortunately, it is probably in a new multi-file KFX format that the tools don't understand. You must download the book manually from Amazon's web site "For transfer via USB" to your Kindle. When you download the ebook in this manner, Amazon will send a single KF8-format file that the tools will be able to import successfully.
|
|
||||||
|
|
||||||
## Do the tools work on books from Kobo?
|
## Do the tools work on books from Kobo?
|
||||||
If you use the Kobo desktop application for Mac or PC, install the obok plugin. This will import and remove the DRM from your Kobo books, and is the easiest method for Kobo ebooks.
|
If you use the Kobo desktop application for Mac or PC, install the Obok plugin. This will import and remove the DRM from your Kobo books, and is the easiest method for Kobo ebooks.
|
||||||
|
|
||||||
## I registered Adobe Digital Editions 3.0 or later with an Adobe ID before downloading, but my epub or PDF still has DRM.
|
## I registered Adobe Digital Editions 3.0 or later with an Adobe ID before downloading, but my epub or PDF still has DRM.
|
||||||
Adobe introduced a new DRM scheme with ADE 3.0 and later. Install ADE 2.0.1 and register with the same Adobe ID. If you can't open your book in ADE 2.01, then you have a book with the new DRM scheme. These tools can't help. You can avoid the new DRM scheme by always downloading your ebooks with ADE 2.0.1. Some retailers will require ADE 3.0 or later, in which case you won't be able to download with ADE 2.0.1.
|
Adobe introduced a new DRM scheme with ADE 3.0 and later. Install ADE 2.0.1 and register with the same Adobe ID. If you can't open your book in ADE 2.01, then you have a book with the new DRM scheme. These tools can't help. You can avoid the new DRM scheme by always downloading your ebooks with ADE 2.0.1. Some retailers will require ADE 3.0 or later, in which case you won't be able to download with ADE 2.0.1.
|
||||||
@@ -174,12 +152,6 @@ You're trying to remove the DRM from an ebook that's only on loan to you. No hel
|
|||||||
## I cannot solve my problem with the DeDRM plugin, and now I need to ‘post a log’. How do I do that?
|
## I cannot solve my problem with the DeDRM plugin, and now I need to ‘post a log’. How do I do that?
|
||||||
Remove the DRMed book from calibre. Click the Preferences drop-down menu and choose 'Restart in debug mode'. Once calibre has re-started, import the problem ebook. Now close calibre. A log will appear that you can copy and paste into a comment at Apprentice Alf's blog, or into a new issue at Apprentice Harper's github repository.
|
Remove the DRMed book from calibre. Click the Preferences drop-down menu and choose 'Restart in debug mode'. Once calibre has re-started, import the problem ebook. Now close calibre. A log will appear that you can copy and paste into a comment at Apprentice Alf's blog, or into a new issue at Apprentice Harper's github repository.
|
||||||
|
|
||||||
## I cannot solve my problem with the Macintosh DeDRM application, and now I need to ‘post a log’. How do I do that?
|
|
||||||
The Macintosh DeDRM application creates a log file on your desktop every time it is run. After unsuccessfully removing DRM from one ebook, copy the contents of the log file (it is a simple text file) and paste it into your comment at Apprentice Alf's blog or in a new issue at Apprentice Harper's github repository.
|
|
||||||
|
|
||||||
## I cannot solve my problem with the Windows DeDRM application, and now I need to ‘post a log’. How do I do that?
|
|
||||||
The Windows DeDRM application creates a log file in your home directory (C:\Users\[username]) every time it is run. After unsuccessfully removing DRM from one ebook, copy the contents of the log file (it is a simple text file) and paste it into your comment at Apprentice Alf's blog or in a new issue at Apprentice Harper's github repository.
|
|
||||||
|
|
||||||
## Is there a way to use the DeDRM plugin for Calibre from the command line?
|
## Is there a way to use the DeDRM plugin for Calibre from the command line?
|
||||||
See the [Calibre command line interface (CLI) instructions](CALIBRE_CLI_INSTRUCTIONS.md).
|
See the [Calibre command line interface (CLI) instructions](CALIBRE_CLI_INSTRUCTIONS.md).
|
||||||
|
|
||||||
@@ -188,7 +160,7 @@ See the [Calibre command line interface (CLI) instructions](CALIBRE_CLI_INSTRUCT
|
|||||||
## Once the DRM has been removed, is there any trace of my personal identity left in the ebook?
|
## Once the DRM has been removed, is there any trace of my personal identity left in the ebook?
|
||||||
The tools only remove the DRM. No attempt is made to remove any personally identifying information.
|
The tools only remove the DRM. No attempt is made to remove any personally identifying information.
|
||||||
|
|
||||||
## What do some of my Kindle ebooks import as HTMLZ format in calibre?
|
## Why do some of my Kindle ebooks import as HTMLZ format in calibre?
|
||||||
Most Amazon Kindle ebooks are Mobipocket format ebooks, or the new KF8 format. However, some are in a format known as Topaz. The Topaz format is only used by Amazon. A Topaz ebook is a collections of glyphs and their positions on each page tagged with some additional information from that page including OCRed text (Optical Character Recognition generated Text) to allow searching, and some additional layout information. Each page of a Topaz ebook is effectively a description of an image of that page. To convert a Topaz ebook to another format is not easy as there is not a one-to-one mapping between glyphs and characters/fonts. To account for this, two different formats are generated by the DRM removal software. The first is an html description built from the OCRtext and images stored in the Topaz file (HTMLZ). This format is easily reflowed but may suffer from typical OCRtext errors including typos, garbled text, missing italics, missing bolds, etc. The second format uses the glyph and position information to create an accurate scalable vector graphics (SVG) image of each page of the book that can be viewed in web browsers that support svg images (Safari, Firefox 4 or later, etc). Additional conversion software can be used to convert these SVG images to an image only PDF file. The DeDRM calibre plugin only imports the HTMLZ versions of the Topaz ebook. The html version can be manually cleaned up and spell checked and then converted using Sigil/calibre to epubs, mobi ebooks, and etc.
|
Most Amazon Kindle ebooks are Mobipocket format ebooks, or the new KF8 format. However, some are in a format known as Topaz. The Topaz format is only used by Amazon. A Topaz ebook is a collections of glyphs and their positions on each page tagged with some additional information from that page including OCRed text (Optical Character Recognition generated Text) to allow searching, and some additional layout information. Each page of a Topaz ebook is effectively a description of an image of that page. To convert a Topaz ebook to another format is not easy as there is not a one-to-one mapping between glyphs and characters/fonts. To account for this, two different formats are generated by the DRM removal software. The first is an html description built from the OCRtext and images stored in the Topaz file (HTMLZ). This format is easily reflowed but may suffer from typical OCRtext errors including typos, garbled text, missing italics, missing bolds, etc. The second format uses the glyph and position information to create an accurate scalable vector graphics (SVG) image of each page of the book that can be viewed in web browsers that support svg images (Safari, Firefox 4 or later, etc). Additional conversion software can be used to convert these SVG images to an image only PDF file. The DeDRM calibre plugin only imports the HTMLZ versions of the Topaz ebook. The html version can be manually cleaned up and spell checked and then converted using Sigil/calibre to epubs, mobi ebooks, and etc.
|
||||||
|
|
||||||
## Are the tools open source? How can I be sure they are safe and not a trojan horse?
|
## Are the tools open source? How can I be sure they are safe and not a trojan horse?
|
||||||
@@ -214,8 +186,8 @@ Amazon turned off backup for Kindle for Android, so the tools can no longer find
|
|||||||
Apple regularly change the details of their DRM and so the tools in the main tools archive will not work with these ebooks. Apple’s Fairplay DRM scheme can be removed using Requiem if the appropriate version of iTunes can still be installed and used. See the post Apple and ebooks: iBookstore DRM and how to remove it at Apprentice Alf's blog for more details.
|
Apple regularly change the details of their DRM and so the tools in the main tools archive will not work with these ebooks. Apple’s Fairplay DRM scheme can be removed using Requiem if the appropriate version of iTunes can still be installed and used. See the post Apple and ebooks: iBookstore DRM and how to remove it at Apprentice Alf's blog for more details.
|
||||||
|
|
||||||
## I’ve got the tools archive and I’ve read all the FAQs but I still can’t install the tools and/or the DRM removal doesn’t work
|
## I’ve got the tools archive and I’ve read all the FAQs but I still can’t install the tools and/or the DRM removal doesn’t work
|
||||||
* Read the ReadMe_First.txt file in the top level of the tools archive
|
* Read the `ReadMe_Overview.txt` file in the top level of the tools archive
|
||||||
* Read the ReadMe file in the folder of the tools you want to use.
|
* Read the ReadMe file for the tool you want to use.
|
||||||
* If you still can’t remove the DRM, ask in the comments section of Apprentice Alf's blog or create a new issue at Apprentice Harper's github repository, reporting the error as precisely as you can, what platform you use, what tool you have tried, what errors you get, and what versions you are using. If the problem happens when running one of the tools, post a log (see previous questions on how to do this).
|
* If you still can’t remove the DRM, ask in the comments section of Apprentice Alf's blog or create a new issue at Apprentice Harper's github repository, reporting the error as precisely as you can, what platform you use, what tool you have tried, what errors you get, and what versions you are using. If the problem happens when running one of the tools, post a log (see previous questions on how to do this).
|
||||||
|
|
||||||
## Who wrote these scripts?
|
## Who wrote these scripts?
|
||||||
@@ -226,9 +198,8 @@ The authors tend to identify themselves only by pseudonyms:
|
|||||||
* The Amazon K4 Mobi tool was created by by some_updates, mdlnx and others
|
* The Amazon K4 Mobi tool was created by by some_updates, mdlnx and others
|
||||||
* The Amazon Topaz DRM removal script was created by CMBDTC
|
* The Amazon Topaz DRM removal script was created by CMBDTC
|
||||||
* The Amazon Topaz format conversion was created by some_updates, clarknova, and Bart Simpson
|
* The Amazon Topaz format conversion was created by some_updates, clarknova, and Bart Simpson
|
||||||
* The DeDRM all-in-one AppleScript application was created by Apprentice Alf
|
|
||||||
* The DeDRM all-in-one Python application was created by some_updates
|
|
||||||
* The DeDRM all-in-one calibre plugin was created by Apprentice Alf
|
* The DeDRM all-in-one calibre plugin was created by Apprentice Alf
|
||||||
|
* The support for .kinf2018 key files and KFX 2&3 was by Apprentice Sakuya
|
||||||
* The Scuolabooks tool was created by Hex
|
* The Scuolabooks tool was created by Hex
|
||||||
* The Microsoft code was created by drs
|
* The Microsoft code was created by drs
|
||||||
* The Apple DRM removal tool was created by Brahms
|
* The Apple DRM removal tool was created by Brahms
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ from __future__ import (unicode_literals, division, absolute_import,
|
|||||||
print_function)
|
print_function)
|
||||||
|
|
||||||
__license__ = 'GPL v3'
|
__license__ = 'GPL v3'
|
||||||
|
__version__ = '6.7.0'
|
||||||
__docformat__ = 'restructuredtext en'
|
__docformat__ = 'restructuredtext en'
|
||||||
|
|
||||||
#####################################################################
|
#####################################################################
|
||||||
@@ -19,7 +20,7 @@ except NameError:
|
|||||||
PLUGIN_NAME = 'Obok DeDRM'
|
PLUGIN_NAME = 'Obok DeDRM'
|
||||||
PLUGIN_SAFE_NAME = PLUGIN_NAME.strip().lower().replace(' ', '_')
|
PLUGIN_SAFE_NAME = PLUGIN_NAME.strip().lower().replace(' ', '_')
|
||||||
PLUGIN_DESCRIPTION = _('Removes DRM from Kobo kepubs and adds them to the library.')
|
PLUGIN_DESCRIPTION = _('Removes DRM from Kobo kepubs and adds them to the library.')
|
||||||
PLUGIN_VERSION_TUPLE = (6, 5, 4)
|
PLUGIN_VERSION_TUPLE = (6, 7, 0)
|
||||||
PLUGIN_VERSION = '.'.join([str(x) for x in PLUGIN_VERSION_TUPLE])
|
PLUGIN_VERSION = '.'.join([str(x) for x in PLUGIN_VERSION_TUPLE])
|
||||||
HELPFILE_NAME = PLUGIN_SAFE_NAME + '_Help.htm'
|
HELPFILE_NAME = PLUGIN_SAFE_NAME + '_Help.htm'
|
||||||
PLUGIN_AUTHORS = 'Anon'
|
PLUGIN_AUTHORS = 'Anon'
|
||||||
@@ -93,7 +93,7 @@ class InterfacePluginAction(InterfaceAction):
|
|||||||
debug_print("Exception getting device path. Probably not an E-Ink Kobo device")
|
debug_print("Exception getting device path. Probably not an E-Ink Kobo device")
|
||||||
|
|
||||||
# Get the Kobo Library object (obok v3.01)
|
# Get the Kobo Library object (obok v3.01)
|
||||||
self.library = KoboLibrary(tmpserials, device_path)
|
self.library = KoboLibrary(tmpserials, device_path, cfg['kobo_directory'])
|
||||||
debug_print ("got kobodir %s" % self.library.kobodir)
|
debug_print ("got kobodir %s" % self.library.kobodir)
|
||||||
if (self.library.kobodir == ''):
|
if (self.library.kobodir == ''):
|
||||||
# linux and no device connected, but could be extended
|
# linux and no device connected, but could be extended
|
||||||
@@ -8,6 +8,7 @@ __copyright__ = '2012, David Forrester <davidfor@internode.on.net>'
|
|||||||
__docformat__ = 'restructuredtext en'
|
__docformat__ = 'restructuredtext en'
|
||||||
|
|
||||||
import os, time, re, sys
|
import os, time, re, sys
|
||||||
|
from datetime import datetime
|
||||||
try:
|
try:
|
||||||
from PyQt5.Qt import (Qt, QIcon, QPixmap, QLabel, QDialog, QHBoxLayout, QProgressBar,
|
from PyQt5.Qt import (Qt, QIcon, QPixmap, QLabel, QDialog, QHBoxLayout, QProgressBar,
|
||||||
QTableWidgetItem, QFont, QLineEdit, QComboBox,
|
QTableWidgetItem, QFont, QLineEdit, QComboBox,
|
||||||
@@ -3,9 +3,9 @@ from __future__ import (unicode_literals, division, absolute_import,
|
|||||||
print_function)
|
print_function)
|
||||||
|
|
||||||
try:
|
try:
|
||||||
from PyQt5.Qt import (Qt, QGroupBox, QListWidget, QLineEdit, QDialogButtonBox, QWidget, QLabel, QDialog, QVBoxLayout, QAbstractItemView, QIcon, QHBoxLayout, QComboBox, QListWidgetItem)
|
from PyQt5.Qt import (Qt, QGroupBox, QListWidget, QLineEdit, QDialogButtonBox, QWidget, QLabel, QDialog, QVBoxLayout, QAbstractItemView, QIcon, QHBoxLayout, QComboBox, QListWidgetItem, QFileDialog)
|
||||||
except ImportError:
|
except ImportError:
|
||||||
from PyQt4.Qt import (Qt, QGroupBox, QListWidget, QLineEdit, QDialogButtonBox, QWidget, QLabel, QDialog, QVBoxLayout, QAbstractItemView, QIcon, QHBoxLayout, QComboBox, QListWidgetItem)
|
from PyQt4.Qt import (Qt, QGroupBox, QListWidget, QLineEdit, QDialogButtonBox, QWidget, QLabel, QDialog, QVBoxLayout, QAbstractItemView, QIcon, QHBoxLayout, QComboBox, QListWidgetItem, QFileDialog)
|
||||||
|
|
||||||
try:
|
try:
|
||||||
from PyQt5 import Qt as QtGui
|
from PyQt5 import Qt as QtGui
|
||||||
@@ -18,6 +18,7 @@ from calibre.utils.config import JSONConfig, config_dir
|
|||||||
plugin_prefs = JSONConfig('plugins/obok_dedrm_prefs')
|
plugin_prefs = JSONConfig('plugins/obok_dedrm_prefs')
|
||||||
plugin_prefs.defaults['finding_homes_for_formats'] = 'Ask'
|
plugin_prefs.defaults['finding_homes_for_formats'] = 'Ask'
|
||||||
plugin_prefs.defaults['kobo_serials'] = []
|
plugin_prefs.defaults['kobo_serials'] = []
|
||||||
|
plugin_prefs.defaults['kobo_directory'] = u''
|
||||||
|
|
||||||
from calibre_plugins.obok_dedrm.__init__ import PLUGIN_NAME, PLUGIN_VERSION
|
from calibre_plugins.obok_dedrm.__init__ import PLUGIN_NAME, PLUGIN_VERSION
|
||||||
from calibre_plugins.obok_dedrm.utilities import (debug_print)
|
from calibre_plugins.obok_dedrm.utilities import (debug_print)
|
||||||
@@ -37,6 +38,7 @@ class ConfigWidget(QWidget):
|
|||||||
|
|
||||||
# copy of preferences
|
# copy of preferences
|
||||||
self.tmpserials = plugin_prefs['kobo_serials']
|
self.tmpserials = plugin_prefs['kobo_serials']
|
||||||
|
self.kobodirectory = plugin_prefs['kobo_directory']
|
||||||
|
|
||||||
combo_label = QLabel(_('When should Obok try to insert EPUBs into existing calibre entries?'), self)
|
combo_label = QLabel(_('When should Obok try to insert EPUBs into existing calibre entries?'), self)
|
||||||
layout.addWidget(combo_label)
|
layout.addWidget(combo_label)
|
||||||
@@ -53,15 +55,30 @@ class ConfigWidget(QWidget):
|
|||||||
self.serials_button.clicked.connect(self.edit_serials)
|
self.serials_button.clicked.connect(self.edit_serials)
|
||||||
layout.addWidget(self.serials_button)
|
layout.addWidget(self.serials_button)
|
||||||
|
|
||||||
|
self.kobo_directory_button = QtGui.QPushButton(self)
|
||||||
|
self.kobo_directory_button.setToolTip(_(u"Click to specify the Kobo directory"))
|
||||||
|
self.kobo_directory_button.setText(u"Kobo directory")
|
||||||
|
self.kobo_directory_button.clicked.connect(self.edit_kobo_directory)
|
||||||
|
layout.addWidget(self.kobo_directory_button)
|
||||||
|
|
||||||
|
|
||||||
def edit_serials(self):
|
def edit_serials(self):
|
||||||
d = ManageKeysDialog(self,u"Kobo device serial numbers",self.tmpserials, AddSerialDialog)
|
d = ManageKeysDialog(self,u"Kobo device serial number",self.tmpserials, AddSerialDialog)
|
||||||
d.exec_()
|
d.exec_()
|
||||||
|
|
||||||
|
|
||||||
|
def edit_kobo_directory(self):
|
||||||
|
tmpkobodirectory = QFileDialog.getExistingDirectory(self, u"Select Kobo directory", self.kobodirectory or "/home", QFileDialog.ShowDirsOnly)
|
||||||
|
|
||||||
|
if tmpkobodirectory != u"" and tmpkobodirectory is not None:
|
||||||
|
self.kobodirectory = tmpkobodirectory
|
||||||
|
|
||||||
|
|
||||||
def save_settings(self):
|
def save_settings(self):
|
||||||
plugin_prefs['finding_homes_for_formats'] = unicode(self.find_homes.currentText())
|
plugin_prefs['finding_homes_for_formats'] = unicode(self.find_homes.currentText())
|
||||||
plugin_prefs['kobo_serials'] = self.tmpserials
|
plugin_prefs['kobo_serials'] = self.tmpserials
|
||||||
|
plugin_prefs['kobo_directory'] = self.kobodirectory
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
@@ -145,28 +162,6 @@ class ManageKeysDialog(QDialog):
|
|||||||
self.listy.clear()
|
self.listy.clear()
|
||||||
self.populate_list()
|
self.populate_list()
|
||||||
|
|
||||||
def rename_key(self):
|
|
||||||
if not self.listy.currentItem():
|
|
||||||
errmsg = u"No {0} selected to rename. Highlight a keyfile first.".format(self.key_type_name)
|
|
||||||
r = error_dialog(None, "{0} {1}".format(PLUGIN_NAME, PLUGIN_VERSION),
|
|
||||||
_(errmsg), show=True, show_copy_button=False)
|
|
||||||
return
|
|
||||||
|
|
||||||
d = RenameKeyDialog(self)
|
|
||||||
d.exec_()
|
|
||||||
|
|
||||||
if d.result() != d.Accepted:
|
|
||||||
# rename cancelled or moot.
|
|
||||||
return
|
|
||||||
keyname = unicode(self.listy.currentItem().text())
|
|
||||||
if not question_dialog(self, "{0} {1}: Confirm Rename".format(PLUGIN_NAME, PLUGIN_VERSION), u"Do you really want to rename the {2} named <strong>{0}</strong> to <strong>{1}</strong>?".format(keyname,d.key_name,self.key_type_name), show_copy_button=False, default_yes=False):
|
|
||||||
return
|
|
||||||
self.plugin_keys[d.key_name] = self.plugin_keys[keyname]
|
|
||||||
del self.plugin_keys[keyname]
|
|
||||||
|
|
||||||
self.listy.clear()
|
|
||||||
self.populate_list()
|
|
||||||
|
|
||||||
def delete_key(self):
|
def delete_key(self):
|
||||||
if not self.listy.currentItem():
|
if not self.listy.currentItem():
|
||||||
return
|
return
|
||||||
|
Before Width: | Height: | Size: 3.1 KiB After Width: | Height: | Size: 3.1 KiB |
@@ -150,6 +150,7 @@
|
|||||||
# after all.
|
# after all.
|
||||||
#
|
#
|
||||||
"""Manage all Kobo books, either encrypted or DRM-free."""
|
"""Manage all Kobo books, either encrypted or DRM-free."""
|
||||||
|
from __future__ import print_function
|
||||||
|
|
||||||
__version__ = '3.2.4'
|
__version__ = '3.2.4'
|
||||||
__about__ = u"Obok v{0}\nCopyright © 2012-2016 Physisticated et al.".format(__version__)
|
__about__ = u"Obok v{0}\nCopyright © 2012-2016 Physisticated et al.".format(__version__)
|
||||||
@@ -290,8 +291,8 @@ class KoboLibrary(object):
|
|||||||
written by the Kobo Desktop Edition application, including the list
|
written by the Kobo Desktop Edition application, including the list
|
||||||
of books, their titles, and the user's encryption key(s)."""
|
of books, their titles, and the user's encryption key(s)."""
|
||||||
|
|
||||||
def __init__ (self, serials = [], device_path = None):
|
def __init__ (self, serials = [], device_path = None, desktopkobodir = u""):
|
||||||
print __about__
|
print(__about__)
|
||||||
self.kobodir = u""
|
self.kobodir = u""
|
||||||
kobodb = u""
|
kobodb = u""
|
||||||
|
|
||||||
@@ -344,19 +345,23 @@ class KoboLibrary(object):
|
|||||||
|
|
||||||
if (self.kobodir == u""):
|
if (self.kobodir == u""):
|
||||||
# step 4. we haven't found a device with serials, so try desktop apps
|
# step 4. we haven't found a device with serials, so try desktop apps
|
||||||
if sys.platform.startswith('win'):
|
if desktopkobodir != u'':
|
||||||
import _winreg as winreg
|
self.kobodir = desktopkobodir
|
||||||
if sys.getwindowsversion().major > 5:
|
|
||||||
if 'LOCALAPPDATA' in os.environ.keys():
|
if (self.kobodir == u""):
|
||||||
# Python 2.x does not return unicode env. Use Python 3.x
|
if sys.platform.startswith('win'):
|
||||||
self.kobodir = winreg.ExpandEnvironmentStrings(u"%LOCALAPPDATA%")
|
import _winreg as winreg
|
||||||
if (self.kobodir == u""):
|
if sys.getwindowsversion().major > 5:
|
||||||
if 'USERPROFILE' in os.environ.keys():
|
if 'LOCALAPPDATA' in os.environ.keys():
|
||||||
# Python 2.x does not return unicode env. Use Python 3.x
|
# Python 2.x does not return unicode env. Use Python 3.x
|
||||||
self.kobodir = os.path.join(winreg.ExpandEnvironmentStrings(u"%USERPROFILE%"), u"Local Settings", u"Application Data")
|
self.kobodir = winreg.ExpandEnvironmentStrings(u"%LOCALAPPDATA%")
|
||||||
self.kobodir = os.path.join(self.kobodir, u"Kobo", u"Kobo Desktop Edition")
|
if (self.kobodir == u""):
|
||||||
elif sys.platform.startswith('darwin'):
|
if 'USERPROFILE' in os.environ.keys():
|
||||||
self.kobodir = os.path.join(os.environ['HOME'], u"Library", u"Application Support", u"Kobo", u"Kobo Desktop Edition")
|
# Python 2.x does not return unicode env. Use Python 3.x
|
||||||
|
self.kobodir = os.path.join(winreg.ExpandEnvironmentStrings(u"%USERPROFILE%"), u"Local Settings", u"Application Data")
|
||||||
|
self.kobodir = os.path.join(self.kobodir, u"Kobo", u"Kobo Desktop Edition")
|
||||||
|
elif sys.platform.startswith('darwin'):
|
||||||
|
self.kobodir = os.path.join(os.environ['HOME'], u"Library", u"Application Support", u"Kobo", u"Kobo Desktop Edition")
|
||||||
#elif linux_path != None:
|
#elif linux_path != None:
|
||||||
# Probably Linux, let's get the wine prefix and path to Kobo.
|
# Probably Linux, let's get the wine prefix and path to Kobo.
|
||||||
# self.kobodir = os.path.join(linux_path, u"Local Settings", u"Application Data", u"Kobo", u"Kobo Desktop Edition")
|
# self.kobodir = os.path.join(linux_path, u"Local Settings", u"Application Data", u"Kobo", u"Kobo Desktop Edition")
|
||||||
@@ -368,13 +373,12 @@ class KoboLibrary(object):
|
|||||||
self.kobodir = u""
|
self.kobodir = u""
|
||||||
kobodb = u""
|
kobodb = u""
|
||||||
|
|
||||||
|
|
||||||
if (self.kobodir != u""):
|
if (self.kobodir != u""):
|
||||||
self.bookdir = os.path.join(self.kobodir, u"kepub")
|
self.bookdir = os.path.join(self.kobodir, u"kepub")
|
||||||
# make a copy of the database in a temporary file
|
# make a copy of the database in a temporary file
|
||||||
# so we can ensure it's not using WAL logging which sqlite3 can't do.
|
# so we can ensure it's not using WAL logging which sqlite3 can't do.
|
||||||
self.newdb = tempfile.NamedTemporaryFile(mode='wb', delete=False)
|
self.newdb = tempfile.NamedTemporaryFile(mode='wb', delete=False)
|
||||||
print self.newdb.name
|
print(self.newdb.name)
|
||||||
olddb = open(kobodb, 'rb')
|
olddb = open(kobodb, 'rb')
|
||||||
self.newdb.write(olddb.read(18))
|
self.newdb.write(olddb.read(18))
|
||||||
self.newdb.write('\x01\x01')
|
self.newdb.write('\x01\x01')
|
||||||
@@ -450,7 +454,16 @@ class KoboLibrary(object):
|
|||||||
# print u"m:{0}".format(m[0])
|
# print u"m:{0}".format(m[0])
|
||||||
macaddrs.append(m[0].upper())
|
macaddrs.append(m[0].upper())
|
||||||
else:
|
else:
|
||||||
# probably linux, let's try ipconfig under wine
|
# probably linux
|
||||||
|
|
||||||
|
# let's try ip
|
||||||
|
c = re.compile('\s(' + '[0-9a-f]{2}:' * 5 + '[0-9a-f]{2})(\s|$)', re.IGNORECASE)
|
||||||
|
for line in os.popen('ip -br link'):
|
||||||
|
m = c.search(line)
|
||||||
|
if m:
|
||||||
|
macaddrs.append(m.group(1).upper())
|
||||||
|
|
||||||
|
# let's try ipconfig under wine
|
||||||
c = re.compile('\s(' + '[0-9a-f]{2}-' * 5 + '[0-9a-f]{2})(\s|$)', re.IGNORECASE)
|
c = re.compile('\s(' + '[0-9a-f]{2}-' * 5 + '[0-9a-f]{2})(\s|$)', re.IGNORECASE)
|
||||||
for line in os.popen('ipconfig /all'):
|
for line in os.popen('ipconfig /all'):
|
||||||
m = c.search(line)
|
m = c.search(line)
|
||||||
@@ -591,32 +604,32 @@ class KoboFile(object):
|
|||||||
# assume utf-8 with no BOM
|
# assume utf-8 with no BOM
|
||||||
textoffset = 0
|
textoffset = 0
|
||||||
stride = 1
|
stride = 1
|
||||||
print u"Checking text:{0}:".format(contents[:10])
|
print(u"Checking text:{0}:".format(contents[:10]))
|
||||||
# check for byte order mark
|
# check for byte order mark
|
||||||
if contents[:3]=="\xef\xbb\xbf":
|
if contents[:3]=="\xef\xbb\xbf":
|
||||||
# seems to be utf-8 with BOM
|
# seems to be utf-8 with BOM
|
||||||
print u"Could be utf-8 with BOM"
|
print(u"Could be utf-8 with BOM")
|
||||||
textoffset = 3
|
textoffset = 3
|
||||||
elif contents[:2]=="\xfe\xff":
|
elif contents[:2]=="\xfe\xff":
|
||||||
# seems to be utf-16BE
|
# seems to be utf-16BE
|
||||||
print u"Could be utf-16BE"
|
print(u"Could be utf-16BE")
|
||||||
textoffset = 3
|
textoffset = 3
|
||||||
stride = 2
|
stride = 2
|
||||||
elif contents[:2]=="\xff\xfe":
|
elif contents[:2]=="\xff\xfe":
|
||||||
# seems to be utf-16LE
|
# seems to be utf-16LE
|
||||||
print u"Could be utf-16LE"
|
print(u"Could be utf-16LE")
|
||||||
textoffset = 2
|
textoffset = 2
|
||||||
stride = 2
|
stride = 2
|
||||||
else:
|
else:
|
||||||
print u"Perhaps utf-8 without BOM"
|
print(u"Perhaps utf-8 without BOM")
|
||||||
|
|
||||||
# now check that the first few characters are in the ASCII range
|
# now check that the first few characters are in the ASCII range
|
||||||
for i in xrange(textoffset,textoffset+5*stride,stride):
|
for i in xrange(textoffset,textoffset+5*stride,stride):
|
||||||
if ord(contents[i])<32 or ord(contents[i])>127:
|
if ord(contents[i])<32 or ord(contents[i])>127:
|
||||||
# Non-ascii, so decryption probably failed
|
# Non-ascii, so decryption probably failed
|
||||||
print u"Bad character at {0}, value {1}".format(i,ord(contents[i]))
|
print(u"Bad character at {0}, value {1}".format(i,ord(contents[i])))
|
||||||
raise ValueError
|
raise ValueError
|
||||||
print u"Seems to be good text"
|
print(u"Seems to be good text")
|
||||||
return True
|
return True
|
||||||
if contents[:5]=="<?xml" or contents[:8]=="\xef\xbb\xbf<?xml":
|
if contents[:5]=="<?xml" or contents[:8]=="\xef\xbb\xbf<?xml":
|
||||||
# utf-8
|
# utf-8
|
||||||
@@ -637,13 +650,13 @@ class KoboFile(object):
|
|||||||
# utf-16LE of weird <!DOCTYPE start
|
# utf-16LE of weird <!DOCTYPE start
|
||||||
return True
|
return True
|
||||||
else:
|
else:
|
||||||
print u"Bad XML: {0}".format(contents[:8])
|
print(u"Bad XML: {0}".format(contents[:8]))
|
||||||
raise ValueError
|
raise ValueError
|
||||||
elif self.mimetype == 'image/jpeg':
|
elif self.mimetype == 'image/jpeg':
|
||||||
if contents[:3] == '\xff\xd8\xff':
|
if contents[:3] == '\xff\xd8\xff':
|
||||||
return True
|
return True
|
||||||
else:
|
else:
|
||||||
print u"Bad JPEG: {0}".format(contents[:3].encode('hex'))
|
print(u"Bad JPEG: {0}".format(contents[:3].encode('hex')))
|
||||||
raise ValueError()
|
raise ValueError()
|
||||||
return False
|
return False
|
||||||
|
|
||||||
@@ -666,18 +679,18 @@ class KoboFile(object):
|
|||||||
return contents
|
return contents
|
||||||
|
|
||||||
def decrypt_book(book, lib):
|
def decrypt_book(book, lib):
|
||||||
print u"Converting {0}".format(book.title)
|
print(u"Converting {0}".format(book.title))
|
||||||
zin = zipfile.ZipFile(book.filename, "r")
|
zin = zipfile.ZipFile(book.filename, "r")
|
||||||
# make filename out of Unicode alphanumeric and whitespace equivalents from title
|
# make filename out of Unicode alphanumeric and whitespace equivalents from title
|
||||||
outname = u"{0}.epub".format(re.sub('[^\s\w]', '_', book.title, 0, re.UNICODE))
|
outname = u"{0}.epub".format(re.sub('[^\s\w]', '_', book.title, 0, re.UNICODE))
|
||||||
if (book.type == 'drm-free'):
|
if (book.type == 'drm-free'):
|
||||||
print u"DRM-free book, conversion is not needed"
|
print(u"DRM-free book, conversion is not needed")
|
||||||
shutil.copyfile(book.filename, outname)
|
shutil.copyfile(book.filename, outname)
|
||||||
print u"Book saved as {0}".format(os.path.join(os.getcwd(), outname))
|
print(u"Book saved as {0}".format(os.path.join(os.getcwd(), outname)))
|
||||||
return 0
|
return 0
|
||||||
result = 1
|
result = 1
|
||||||
for userkey in lib.userkeys:
|
for userkey in lib.userkeys:
|
||||||
print u"Trying key: {0}".format(userkey.encode('hex_codec'))
|
print(u"Trying key: {0}".format(userkey.encode('hex_codec')))
|
||||||
try:
|
try:
|
||||||
zout = zipfile.ZipFile(outname, "w", zipfile.ZIP_DEFLATED)
|
zout = zipfile.ZipFile(outname, "w", zipfile.ZIP_DEFLATED)
|
||||||
for filename in zin.namelist():
|
for filename in zin.namelist():
|
||||||
@@ -689,12 +702,12 @@ def decrypt_book(book, lib):
|
|||||||
file.check(contents)
|
file.check(contents)
|
||||||
zout.writestr(filename, contents)
|
zout.writestr(filename, contents)
|
||||||
zout.close()
|
zout.close()
|
||||||
print u"Decryption succeeded."
|
print(u"Decryption succeeded.")
|
||||||
print u"Book saved as {0}".format(os.path.join(os.getcwd(), outname))
|
print(u"Book saved as {0}".format(os.path.join(os.getcwd(), outname)))
|
||||||
result = 0
|
result = 0
|
||||||
break
|
break
|
||||||
except ValueError:
|
except ValueError:
|
||||||
print u"Decryption failed."
|
print(u"Decryption failed.")
|
||||||
zout.close()
|
zout.close()
|
||||||
os.remove(outname)
|
os.remove(outname)
|
||||||
zin.close()
|
zin.close()
|
||||||
@@ -719,8 +732,8 @@ def cli_main():
|
|||||||
books = lib.books
|
books = lib.books
|
||||||
else:
|
else:
|
||||||
for i, book in enumerate(lib.books):
|
for i, book in enumerate(lib.books):
|
||||||
print u"{0}: {1}".format(i + 1, book.title)
|
print(u"{0}: {1}".format(i + 1, book.title))
|
||||||
print u"Or 'all'"
|
print(u"Or 'all'")
|
||||||
|
|
||||||
choice = raw_input(u"Convert book number... ")
|
choice = raw_input(u"Convert book number... ")
|
||||||
if choice == u'all':
|
if choice == u'all':
|
||||||
@@ -730,14 +743,14 @@ def cli_main():
|
|||||||
num = int(choice)
|
num = int(choice)
|
||||||
books = [lib.books[num - 1]]
|
books = [lib.books[num - 1]]
|
||||||
except (ValueError, IndexError):
|
except (ValueError, IndexError):
|
||||||
print u"Invalid choice. Exiting..."
|
print(u"Invalid choice. Exiting...")
|
||||||
exit()
|
exit()
|
||||||
|
|
||||||
results = [decrypt_book(book, lib) for book in books]
|
results = [decrypt_book(book, lib) for book in books]
|
||||||
lib.close()
|
lib.close()
|
||||||
overall_result = all(result != 0 for result in results)
|
overall_result = all(result != 0 for result in results)
|
||||||
if overall_result != 0:
|
if overall_result != 0:
|
||||||
print u"Could not decrypt book with any of the keys found."
|
print(u"Could not decrypt book with any of the keys found.")
|
||||||
return overall_result
|
return overall_result
|
||||||
|
|
||||||
|
|
||||||
BIN
Obok_plugin/translations/sv.mo
Normal file
BIN
Obok_plugin/translations/sv.mo
Normal file
Binary file not shown.
366
Obok_plugin/translations/sv.po
Normal file
366
Obok_plugin/translations/sv.po
Normal file
@@ -0,0 +1,366 @@
|
|||||||
|
# SOME DESCRIPTIVE TITLE.
|
||||||
|
# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER
|
||||||
|
# This file is distributed under the same license as the PACKAGE package.
|
||||||
|
# FIRST AUTHOR <EMAIL@ADDRESS>, YEAR.
|
||||||
|
#
|
||||||
|
msgid ""
|
||||||
|
msgstr ""
|
||||||
|
"Project-Id-Version: \n"
|
||||||
|
"Report-Msgid-Bugs-To: \n"
|
||||||
|
"POT-Creation-Date: 2014-11-17 12:51+0100\n"
|
||||||
|
"PO-Revision-Date: 2020-02-02 09:18+0100\n"
|
||||||
|
"Language: sv\n"
|
||||||
|
"MIME-Version: 1.0\n"
|
||||||
|
"Content-Type: text/plain; charset=UTF-8\n"
|
||||||
|
"Content-Transfer-Encoding: 8bit\n"
|
||||||
|
"Plural-Forms: nplurals=2; plural=(n != 1);\n"
|
||||||
|
"Last-Translator: \n"
|
||||||
|
"Language-Team: \n"
|
||||||
|
"X-Generator: Poedit 2.2.4\n"
|
||||||
|
|
||||||
|
#: I:\Herramientas\PoeditPortable\App\Poedit\bin\obok_plugin-3.1.0_trad\action.py:80
|
||||||
|
msgid ""
|
||||||
|
"<p>No books found in Kobo Library\n"
|
||||||
|
"Are you sure it's installed\\configured\\synchronized?"
|
||||||
|
msgstr ""
|
||||||
|
"<p>Inga böcker finns i Kobo-bibliotek\n"
|
||||||
|
"Är du säker på att den är installerad\\konfigurerad\\synkroniserad?"
|
||||||
|
|
||||||
|
#: I:\Herramientas\PoeditPortable\App\Poedit\bin\obok_plugin-3.1.0_trad\action.py:87
|
||||||
|
msgid "Legacy key found: "
|
||||||
|
msgstr "Äldre nyckel hittades: "
|
||||||
|
|
||||||
|
#: I:\Herramientas\PoeditPortable\App\Poedit\bin\obok_plugin-3.1.0_trad\action.py:93
|
||||||
|
msgid "Trouble retrieving keys with newer obok method."
|
||||||
|
msgstr "Problem med att hämta nycklar med nyare obok-metod."
|
||||||
|
|
||||||
|
#: I:\Herramientas\PoeditPortable\App\Poedit\bin\obok_plugin-3.1.0_trad\action.py:97
|
||||||
|
msgid "Found {0} possible keys to try."
|
||||||
|
msgstr "Hittade {0} möjliga nycklar att pröva med."
|
||||||
|
|
||||||
|
#: I:\Herramientas\PoeditPortable\App\Poedit\bin\obok_plugin-3.1.0_trad\action.py:99
|
||||||
|
msgid "<p>No userkeys found to decrypt books with. No point in proceeding."
|
||||||
|
msgstr ""
|
||||||
|
"<p>Inga användarnycklar hittades för att dekryptera böcker med. Det är ingen "
|
||||||
|
"idé att fortsätta."
|
||||||
|
|
||||||
|
#: I:\Herramientas\PoeditPortable\App\Poedit\bin\obok_plugin-3.1.0_trad\action.py:115
|
||||||
|
msgid "{} - Decryption canceled by user."
|
||||||
|
msgstr "{} - Dekryptering avbryts av användaren."
|
||||||
|
|
||||||
|
#: I:\Herramientas\PoeditPortable\App\Poedit\bin\obok_plugin-3.1.0_trad\action.py:135
|
||||||
|
msgid "{} - \"Add books\" canceled by user."
|
||||||
|
msgstr "{} - \"Lägg till böcker\" avbröts av användaren."
|
||||||
|
|
||||||
|
#: I:\Herramientas\PoeditPortable\App\Poedit\bin\obok_plugin-3.1.0_trad\action.py:137
|
||||||
|
#: I:\Herramientas\PoeditPortable\App\Poedit\bin\obok_plugin-3.1.0_trad\action.py:156
|
||||||
|
msgid "{} - wrapping up results."
|
||||||
|
msgstr "{} - samlar in resultat."
|
||||||
|
|
||||||
|
#: I:\Herramientas\PoeditPortable\App\Poedit\bin\obok_plugin-3.1.0_trad\action.py:153
|
||||||
|
msgid "{} - User opted not to try to insert EPUB formats"
|
||||||
|
msgstr "{} - Användaren valde att inte försöka infoga EPUB-format"
|
||||||
|
|
||||||
|
#: I:\Herramientas\PoeditPortable\App\Poedit\bin\obok_plugin-3.1.0_trad\action.py:188
|
||||||
|
msgid "{0} - Decrypting {1}"
|
||||||
|
msgstr "{0} - Dekrypterar {1}"
|
||||||
|
|
||||||
|
#: I:\Herramientas\PoeditPortable\App\Poedit\bin\obok_plugin-3.1.0_trad\action.py:197
|
||||||
|
msgid "{0} - Couldn't decrypt {1}"
|
||||||
|
msgstr "{0} - Kunde inte dekryptera {1}"
|
||||||
|
|
||||||
|
#: I:\Herramientas\PoeditPortable\App\Poedit\bin\obok_plugin-3.1.0_trad\action.py:198
|
||||||
|
msgid "decryption errors"
|
||||||
|
msgstr "dekrypteringsfel"
|
||||||
|
|
||||||
|
#: I:\Herramientas\PoeditPortable\App\Poedit\bin\obok_plugin-3.1.0_trad\action.py:213
|
||||||
|
msgid "{0} - Added {1}"
|
||||||
|
msgstr "{0} - Lade till {1}"
|
||||||
|
|
||||||
|
#: I:\Herramientas\PoeditPortable\App\Poedit\bin\obok_plugin-3.1.0_trad\action.py:218
|
||||||
|
msgid "{0} - {1} already exists. Will try to add format later."
|
||||||
|
msgstr "{0} - {1} finns redan. Kommer att försöka lägga till format senare."
|
||||||
|
|
||||||
|
#: I:\Herramientas\PoeditPortable\App\Poedit\bin\obok_plugin-3.1.0_trad\action.py:219
|
||||||
|
msgid "duplicate detected"
|
||||||
|
msgstr "dubblett upptäcktes"
|
||||||
|
|
||||||
|
#: I:\Herramientas\PoeditPortable\App\Poedit\bin\obok_plugin-3.1.0_trad\action.py:233
|
||||||
|
msgid "{0} - Successfully added EPUB format to existing {1}"
|
||||||
|
msgstr "{0} - Lade till EPUB-format till befintliga {1}"
|
||||||
|
|
||||||
|
#: I:\Herramientas\PoeditPortable\App\Poedit\bin\obok_plugin-3.1.0_trad\action.py:236
|
||||||
|
msgid ""
|
||||||
|
"{0} - Error adding EPUB format to existing {1}. This really shouldn't happen."
|
||||||
|
msgstr ""
|
||||||
|
"{0} - Fel vid tillägg av EPUB-format till befintligt {1}. Det här borde inte "
|
||||||
|
"hända."
|
||||||
|
|
||||||
|
#: I:\Herramientas\PoeditPortable\App\Poedit\bin\obok_plugin-3.1.0_trad\action.py:259
|
||||||
|
msgid "{} - \"Insert formats\" canceled by user."
|
||||||
|
msgstr "{} - \"Infoga format\" avbröts av användaren."
|
||||||
|
|
||||||
|
#: I:\Herramientas\PoeditPortable\App\Poedit\bin\obok_plugin-3.1.0_trad\action.py:291
|
||||||
|
msgid ""
|
||||||
|
"<p><b>{0}</b> EPUB{2} successfully added to library.<br /><br /><b>{1}</b> "
|
||||||
|
msgstr "<p><b>{0}</b> EPUB{2} lades till bibliotek.<br /><br /><b>{1}</b> "
|
||||||
|
|
||||||
|
#: I:\Herramientas\PoeditPortable\App\Poedit\bin\obok_plugin-3.1.0_trad\action.py:292
|
||||||
|
msgid ""
|
||||||
|
"not added because books with the same title/author were detected.<br /><br /"
|
||||||
|
">Would you like to try and add the EPUB format{0}"
|
||||||
|
msgstr ""
|
||||||
|
"inte tillagd eftersom böcker med samma titel/författare upptäcktes.<br/><br /"
|
||||||
|
">Vill du försöka lägga till EPUB-formatet{0}"
|
||||||
|
|
||||||
|
#: I:\Herramientas\PoeditPortable\App\Poedit\bin\obok_plugin-3.1.0_trad\action.py:293
|
||||||
|
msgid ""
|
||||||
|
" to those existing entries?<br /><br />NOTE: no pre-existing EPUBs will be "
|
||||||
|
"overwritten."
|
||||||
|
msgstr ""
|
||||||
|
" till dessa befintliga poster?<br /><br />OBS: inga befintliga EPUB:er "
|
||||||
|
"kommer att skrivas över."
|
||||||
|
|
||||||
|
#: I:\Herramientas\PoeditPortable\App\Poedit\bin\obok_plugin-3.1.0_trad\action.py:295
|
||||||
|
msgid ""
|
||||||
|
"{0} -- not added because of {1} in your library.\n"
|
||||||
|
"\n"
|
||||||
|
msgstr ""
|
||||||
|
"{0} -- inte tillagd på grund av {1} i ditt bibliotek.\n"
|
||||||
|
"\n"
|
||||||
|
|
||||||
|
#: I:\Herramientas\PoeditPortable\App\Poedit\bin\obok_plugin-3.1.0_trad\action.py:297
|
||||||
|
msgid "<p><b>{0}</b> -- not added because of {1} in your library.<br /><br />"
|
||||||
|
msgstr ""
|
||||||
|
"<p><b>{0}</b> -- inte tillagd på grund av {1} i ditt bibliotek.<br /><br />"
|
||||||
|
|
||||||
|
#: I:\Herramientas\PoeditPortable\App\Poedit\bin\obok_plugin-3.1.0_trad\action.py:298
|
||||||
|
msgid ""
|
||||||
|
"Would you like to try and add the EPUB format to an available calibre "
|
||||||
|
"duplicate?<br /><br />"
|
||||||
|
msgstr ""
|
||||||
|
"Vill du försöka lägga till EPUB-formatet till en tillgänglig calibre-"
|
||||||
|
"dubblett?<br /><br />"
|
||||||
|
|
||||||
|
#: I:\Herramientas\PoeditPortable\App\Poedit\bin\obok_plugin-3.1.0_trad\action.py:299
|
||||||
|
msgid "NOTE: no pre-existing EPUB will be overwritten."
|
||||||
|
msgstr "OBS: ingen befintlig EPUB kommer att skrivas över."
|
||||||
|
|
||||||
|
#: I:\Herramientas\PoeditPortable\App\Poedit\bin\obok_plugin-3.1.0_trad\action.py:346
|
||||||
|
msgid "Trying key: "
|
||||||
|
msgstr "Prövar nyckel: "
|
||||||
|
|
||||||
|
#: I:\Herramientas\PoeditPortable\App\Poedit\bin\obok_plugin-3.1.0_trad\action.py:378
|
||||||
|
msgid "Decryption failed, trying next key."
|
||||||
|
msgstr "Det gick inte att dekryptera, prövar nästa nyckel."
|
||||||
|
|
||||||
|
#: I:\Herramientas\PoeditPortable\App\Poedit\bin\obok_plugin-3.1.0_trad\action.py:382
|
||||||
|
msgid "Unknown Error decrypting, trying next key.."
|
||||||
|
msgstr "Okänt fel dekryptering, prövar nästa nyckel.."
|
||||||
|
|
||||||
|
#: I:\Herramientas\PoeditPortable\App\Poedit\bin\obok_plugin-3.1.0_trad\action.py:395
|
||||||
|
msgid ""
|
||||||
|
"<p>All selected Kobo books added as new calibre books or inserted into "
|
||||||
|
"existing calibre ebooks.<br /><br />No issues."
|
||||||
|
msgstr ""
|
||||||
|
"<p>Alla valda Kobo-böcker läggs till som nya calibre-böcker eller infogas i "
|
||||||
|
"befintliga calibre-e-böcker.<br /><br />Inga problem."
|
||||||
|
|
||||||
|
#: I:\Herramientas\PoeditPortable\App\Poedit\bin\obok_plugin-3.1.0_trad\action.py:399
|
||||||
|
msgid "<p>{0} successfully added."
|
||||||
|
msgstr "<p>{0} har lagts till."
|
||||||
|
|
||||||
|
#: I:\Herramientas\PoeditPortable\App\Poedit\bin\obok_plugin-3.1.0_trad\action.py:403
|
||||||
|
msgid ""
|
||||||
|
"<p>Not all selected Kobo books made it into calibre.<br /><br />View report "
|
||||||
|
"for details."
|
||||||
|
msgstr ""
|
||||||
|
"<p>Inte alla valda Kobo-böcker lades till i calibre.<br /><br />Visa rapport "
|
||||||
|
"för detaljer."
|
||||||
|
|
||||||
|
#: I:\Herramientas\PoeditPortable\App\Poedit\bin\obok_plugin-3.1.0_trad\action.py:404
|
||||||
|
msgid "<p><b>Total attempted:</b> {}</p>\n"
|
||||||
|
msgstr "<p><b>Försök totalt:</b> {}</p>\n"
|
||||||
|
|
||||||
|
#: I:\Herramientas\PoeditPortable\App\Poedit\bin\obok_plugin-3.1.0_trad\action.py:405
|
||||||
|
msgid "<p><b>Decryption errors:</b> {}</p>\n"
|
||||||
|
msgstr "<p><b>Dekrypteringsfel:</b> {}</p>\n"
|
||||||
|
|
||||||
|
#: I:\Herramientas\PoeditPortable\App\Poedit\bin\obok_plugin-3.1.0_trad\action.py:411
|
||||||
|
msgid "<p><b>New Books created:</b> {}</p>\n"
|
||||||
|
msgstr "<p><b>Nya böcker skapade:</b> {}</p>\n"
|
||||||
|
|
||||||
|
#: I:\Herramientas\PoeditPortable\App\Poedit\bin\obok_plugin-3.1.0_trad\action.py:418
|
||||||
|
msgid "<p><b>Duplicates that weren't added:</b> {}</p>\n"
|
||||||
|
msgstr "<p><b>Dubbletter som inte tillsattes:</b> {}</p>\n"
|
||||||
|
|
||||||
|
#: I:\Herramientas\PoeditPortable\App\Poedit\bin\obok_plugin-3.1.0_trad\action.py:426
|
||||||
|
msgid "<p><b>Book imports cancelled by user:</b> {}</p>\n"
|
||||||
|
msgstr "<p><b>Bokimport avbröts av användaren:</b> {}</p>\n"
|
||||||
|
|
||||||
|
#: I:\Herramientas\PoeditPortable\App\Poedit\bin\obok_plugin-3.1.0_trad\action.py:428
|
||||||
|
msgid ""
|
||||||
|
"<p><b>New EPUB formats inserted in existing calibre books:</b> {0}</p>\n"
|
||||||
|
msgstr ""
|
||||||
|
"<p><b>Nya EPUB-format infogade i befintliga calibre-böcker:</b> {0}</p>\n"
|
||||||
|
|
||||||
|
#: I:\Herramientas\PoeditPortable\App\Poedit\bin\obok_plugin-3.1.0_trad\action.py:434
|
||||||
|
msgid ""
|
||||||
|
"<p><b>EPUB formats NOT inserted into existing calibre books:</b> {}<br />\n"
|
||||||
|
msgstr ""
|
||||||
|
"<p><b>EPUB-format som INTE infogats i befintliga calibre-böcker:</b> {}<br /"
|
||||||
|
">\n"
|
||||||
|
|
||||||
|
#: I:\Herramientas\PoeditPortable\App\Poedit\bin\obok_plugin-3.1.0_trad\action.py:435
|
||||||
|
msgid ""
|
||||||
|
"(Either because the user <i>chose</i> not to insert them, or because all "
|
||||||
|
"duplicates already had an EPUB format)"
|
||||||
|
msgstr ""
|
||||||
|
"(Antingen för att användaren <i>valde</i> att inte infoga dem, eller för att "
|
||||||
|
"alla dubbletter redan hade ett EPUB-format)"
|
||||||
|
|
||||||
|
#: I:\Herramientas\PoeditPortable\App\Poedit\bin\obok_plugin-3.1.0_trad\action.py:444
|
||||||
|
msgid "<p><b>Format imports cancelled by user:</b> {}</p>\n"
|
||||||
|
msgstr "<p><b>Format-import avbröts av användaren:</b> {}</p>\n"
|
||||||
|
|
||||||
|
#: I:\Herramientas\PoeditPortable\App\Poedit\bin\obok_plugin-3.1.0_trad\action.py:458
|
||||||
|
msgid "Unknown Book Title"
|
||||||
|
msgstr "Okänd boktitel"
|
||||||
|
|
||||||
|
#: I:\Herramientas\PoeditPortable\App\Poedit\bin\obok_plugin-3.1.0_trad\action.py:460
|
||||||
|
msgid "it couldn't be decrypted."
|
||||||
|
msgstr "den kunde inte dekrypteras."
|
||||||
|
|
||||||
|
#: I:\Herramientas\PoeditPortable\App\Poedit\bin\obok_plugin-3.1.0_trad\action.py:462
|
||||||
|
msgid ""
|
||||||
|
"user CHOSE not to insert the new EPUB format, or all existing calibre "
|
||||||
|
"entries HAD an EPUB format already."
|
||||||
|
msgstr ""
|
||||||
|
"användaren VALDE att inte infoga det nya EPUB-formatet, eller alla "
|
||||||
|
"befintliga calibre-poster hade redan ett EPUB-format."
|
||||||
|
|
||||||
|
#: I:\Herramientas\PoeditPortable\App\Poedit\bin\obok_plugin-3.1.0_trad\action.py:464
|
||||||
|
msgid "of unknown reasons. Gosh I'm embarrassed!"
|
||||||
|
msgstr "av okända skäl. Jag skäms!"
|
||||||
|
|
||||||
|
#: I:\Herramientas\PoeditPortable\App\Poedit\bin\obok_plugin-3.1.0_trad\action.py:465
|
||||||
|
msgid "<p>{0} not added because {1}"
|
||||||
|
msgstr "<p>{0} inte tillagd eftersom {1}"
|
||||||
|
|
||||||
|
#: I:\Herramientas\PoeditPortable\App\Poedit\bin\obok_plugin-3.1.0_trad\common_utils.py:226
|
||||||
|
msgid "Help"
|
||||||
|
msgstr "Hjälp"
|
||||||
|
|
||||||
|
#: I:\Herramientas\PoeditPortable\App\Poedit\bin\obok_plugin-3.1.0_trad\common_utils.py:235
|
||||||
|
#: I:\Herramientas\PoeditPortable\App\Poedit\bin\obok_plugin-3.1.0_trad\utilities.py:214
|
||||||
|
msgid "Restart required"
|
||||||
|
msgstr "Omstart krävs"
|
||||||
|
|
||||||
|
#: I:\Herramientas\PoeditPortable\App\Poedit\bin\obok_plugin-3.1.0_trad\common_utils.py:236
|
||||||
|
#: I:\Herramientas\PoeditPortable\App\Poedit\bin\obok_plugin-3.1.0_trad\utilities.py:215
|
||||||
|
msgid ""
|
||||||
|
"Title image not found - you must restart Calibre before using this plugin!"
|
||||||
|
msgstr ""
|
||||||
|
"Titelbild hittades inte - du måste starta calibre innan du använder denna "
|
||||||
|
"insticksmodul!"
|
||||||
|
|
||||||
|
#: I:\Herramientas\PoeditPortable\App\Poedit\bin\obok_plugin-3.1.0_trad\common_utils.py:322
|
||||||
|
msgid "Undefined"
|
||||||
|
msgstr "Obestämd"
|
||||||
|
|
||||||
|
#: I:\Herramientas\PoeditPortable\App\Poedit\bin\obok_plugin-3.1.0_trad\config.py:30
|
||||||
|
msgid "When should Obok try to insert EPUBs into existing calibre entries?"
|
||||||
|
msgstr "När ska Obok försöka infoga EPUB:er i befintliga calibre-böcker?"
|
||||||
|
|
||||||
|
#: I:\Herramientas\PoeditPortable\App\Poedit\bin\obok_plugin-3.1.0_trad\config.py:33
|
||||||
|
msgid ""
|
||||||
|
"<p>Default behavior when duplicates are detected. None of the choices will "
|
||||||
|
"cause calibre ebooks to be overwritten"
|
||||||
|
msgstr ""
|
||||||
|
"<p>Standardbeteende när dubbletter upptäcks. Inget av alternativen kommer "
|
||||||
|
"att orsaka calibre-e-böcker att skrivas över"
|
||||||
|
|
||||||
|
#: I:\Herramientas\PoeditPortable\App\Poedit\bin\obok_plugin-3.1.0_trad\config.py:35
|
||||||
|
msgid "Ask"
|
||||||
|
msgstr "Fråga"
|
||||||
|
|
||||||
|
#: I:\Herramientas\PoeditPortable\App\Poedit\bin\obok_plugin-3.1.0_trad\config.py:35
|
||||||
|
msgid "Always"
|
||||||
|
msgstr "Alltid"
|
||||||
|
|
||||||
|
#: I:\Herramientas\PoeditPortable\App\Poedit\bin\obok_plugin-3.1.0_trad\config.py:35
|
||||||
|
msgid "Never"
|
||||||
|
msgstr "Aldrig"
|
||||||
|
|
||||||
|
#: I:\Herramientas\PoeditPortable\App\Poedit\bin\obok_plugin-3.1.0_trad\dialogs.py:60
|
||||||
|
#: I:\Herramientas\PoeditPortable\App\Poedit\bin\obok_plugin-3.1.0_trad\utilities.py:150
|
||||||
|
msgid " v"
|
||||||
|
msgstr " v"
|
||||||
|
|
||||||
|
#: I:\Herramientas\PoeditPortable\App\Poedit\bin\obok_plugin-3.1.0_trad\dialogs.py:65
|
||||||
|
msgid "Obok DeDRM"
|
||||||
|
msgstr "Obok DeDRM"
|
||||||
|
|
||||||
|
#: I:\Herramientas\PoeditPortable\App\Poedit\bin\obok_plugin-3.1.0_trad\dialogs.py:68
|
||||||
|
msgid "<a href=\"http://www.foo.com/\">Help</a>"
|
||||||
|
msgstr "<a href=\"http://www.foo.com/\">Hjälp</a>"
|
||||||
|
|
||||||
|
#: I:\Herramientas\PoeditPortable\App\Poedit\bin\obok_plugin-3.1.0_trad\dialogs.py:89
|
||||||
|
msgid "Select All"
|
||||||
|
msgstr "Välj alla"
|
||||||
|
|
||||||
|
#: I:\Herramientas\PoeditPortable\App\Poedit\bin\obok_plugin-3.1.0_trad\dialogs.py:90
|
||||||
|
msgid "Select all books to add them to the calibre library."
|
||||||
|
msgstr "Välj alla böcker för att lägga till dem i calibre-biblioteket."
|
||||||
|
|
||||||
|
#: I:\Herramientas\PoeditPortable\App\Poedit\bin\obok_plugin-3.1.0_trad\dialogs.py:92
|
||||||
|
msgid "All with DRM"
|
||||||
|
msgstr "Alla med DRM"
|
||||||
|
|
||||||
|
#: I:\Herramientas\PoeditPortable\App\Poedit\bin\obok_plugin-3.1.0_trad\dialogs.py:93
|
||||||
|
msgid "Select all books with DRM."
|
||||||
|
msgstr "Välj alla böcker med DRM."
|
||||||
|
|
||||||
|
#: I:\Herramientas\PoeditPortable\App\Poedit\bin\obok_plugin-3.1.0_trad\dialogs.py:95
|
||||||
|
msgid "All DRM free"
|
||||||
|
msgstr "Alla DRM fria"
|
||||||
|
|
||||||
|
#: I:\Herramientas\PoeditPortable\App\Poedit\bin\obok_plugin-3.1.0_trad\dialogs.py:96
|
||||||
|
msgid "Select all books without DRM."
|
||||||
|
msgstr "Välj alla böcker utan DRM."
|
||||||
|
|
||||||
|
#: I:\Herramientas\PoeditPortable\App\Poedit\bin\obok_plugin-3.1.0_trad\dialogs.py:146
|
||||||
|
msgid "Title"
|
||||||
|
msgstr "Titel"
|
||||||
|
|
||||||
|
#: I:\Herramientas\PoeditPortable\App\Poedit\bin\obok_plugin-3.1.0_trad\dialogs.py:146
|
||||||
|
msgid "Author"
|
||||||
|
msgstr "Författare"
|
||||||
|
|
||||||
|
#: I:\Herramientas\PoeditPortable\App\Poedit\bin\obok_plugin-3.1.0_trad\dialogs.py:146
|
||||||
|
msgid "Series"
|
||||||
|
msgstr "Serier"
|
||||||
|
|
||||||
|
#: I:\Herramientas\PoeditPortable\App\Poedit\bin\obok_plugin-3.1.0_trad\dialogs.py:369
|
||||||
|
msgid "Copy to clipboard"
|
||||||
|
msgstr "Kopiera till urklipp"
|
||||||
|
|
||||||
|
#: I:\Herramientas\PoeditPortable\App\Poedit\bin\obok_plugin-3.1.0_trad\dialogs.py:397
|
||||||
|
msgid "View Report"
|
||||||
|
msgstr "Visa rapport"
|
||||||
|
|
||||||
|
#: I:\Herramientas\PoeditPortable\App\Poedit\bin\obok_plugin-3.1.0_trad\__init__.py:21
|
||||||
|
msgid "Removes DRM from Kobo kepubs and adds them to the library."
|
||||||
|
msgstr "Tar bort DRM från Kobo-kepubs och lägger till dem i biblioteket."
|
||||||
|
|
||||||
|
#: I:\Herramientas\PoeditPortable\App\Poedit\bin\obok_plugin-3.1.0_trad\obok\obok.py:162
|
||||||
|
msgid "AES improper key used"
|
||||||
|
msgstr "AES felaktig nyckel används"
|
||||||
|
|
||||||
|
#: I:\Herramientas\PoeditPortable\App\Poedit\bin\obok_plugin-3.1.0_trad\obok\obok.py:167
|
||||||
|
msgid "Failed to initialize AES key"
|
||||||
|
msgstr "Det gick inte att initiera AES-nyckel"
|
||||||
|
|
||||||
|
#: I:\Herramientas\PoeditPortable\App\Poedit\bin\obok_plugin-3.1.0_trad\obok\obok.py:175
|
||||||
|
msgid "AES decryption failed"
|
||||||
|
msgstr "AES dekryptering misslyckades"
|
||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user