mirror of
https://github.com/apprenticeharper/DeDRM_tools
synced 2024-11-15 06:12:51 +00:00
afa4ac5716
THIS IS ON THE MASTER BRANCH. The Master branch will be Python 3.0 from now on. While Python 2.7 support will not be deliberately broken, all efforts should now focus on Python 3.0 compatibility. I can see a lot of work has been done. There's more to do. I've bumped the version number of everything I came across to the next major number for Python 3.0 compatibility indication. Thanks everyone. I hope to update here at least once a week until we have a stable 7.0 release for calibre 5.0
123 lines
3.8 KiB
Python
123 lines
3.8 KiB
Python
#!/usr/bin/env python
|
|
# -*- coding: utf-8 -*-
|
|
|
|
from __future__ import with_statement
|
|
from __future__ import print_function
|
|
|
|
# Engine to remove drm from Kindle KFX ebooks
|
|
|
|
# 2.0 - Added Python 3 compatibility for calibre 5.0
|
|
|
|
|
|
import os
|
|
import shutil
|
|
import zipfile
|
|
|
|
try:
|
|
from cStringIO import StringIO
|
|
except ImportError:
|
|
try:
|
|
from StringIO import StringIO
|
|
except ImportError:
|
|
from io import StringIO
|
|
|
|
try:
|
|
from calibre_plugins.dedrm import ion
|
|
except ImportError:
|
|
import ion
|
|
|
|
|
|
__license__ = 'GPL v3'
|
|
__version__ = '2.0'
|
|
|
|
|
|
class KFXZipBook:
|
|
def __init__(self, infile):
|
|
self.infile = infile
|
|
self.voucher = None
|
|
self.decrypted = {}
|
|
|
|
def getPIDMetaInfo(self):
|
|
return (None, None)
|
|
|
|
def processBook(self, totalpids):
|
|
with zipfile.ZipFile(self.infile, 'r') as zf:
|
|
for filename in zf.namelist():
|
|
with zf.open(filename) as fh:
|
|
data = fh.read(8)
|
|
if data != '\xeaDRMION\xee':
|
|
continue
|
|
data += fh.read()
|
|
if self.voucher is None:
|
|
self.decrypt_voucher(totalpids)
|
|
print(u'Decrypting KFX DRMION: {0}'.format(filename))
|
|
outfile = StringIO()
|
|
ion.DrmIon(StringIO(data[8:-8]), lambda name: self.voucher).parse(outfile)
|
|
self.decrypted[filename] = outfile.getvalue()
|
|
|
|
if not self.decrypted:
|
|
print(u'The .kfx-zip archive does not contain an encrypted DRMION file')
|
|
|
|
def decrypt_voucher(self, totalpids):
|
|
with zipfile.ZipFile(self.infile, 'r') as zf:
|
|
for info in zf.infolist():
|
|
with zf.open(info.filename) as fh:
|
|
data = fh.read(4)
|
|
if data != '\xe0\x01\x00\xea':
|
|
continue
|
|
|
|
data += fh.read()
|
|
if 'ProtectedData' in data:
|
|
break # found DRM voucher
|
|
else:
|
|
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))
|
|
|
|
for pid in [''] + totalpids:
|
|
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:
|
|
break # split pid into DSN and account secret
|
|
else:
|
|
continue
|
|
|
|
try:
|
|
voucher = ion.DrmIonVoucher(StringIO(data), pid[:dsn_len], pid[dsn_len:])
|
|
voucher.parse()
|
|
voucher.decryptvoucher()
|
|
break
|
|
except:
|
|
pass
|
|
else:
|
|
raise Exception(u'Failed to decrypt KFX DRM voucher with any key')
|
|
|
|
print(u'KFX DRM voucher successfully decrypted')
|
|
|
|
license_type = voucher.getlicensetype()
|
|
if license_type != "Purchase":
|
|
raise Exception((u'This book is licensed as {0}. '
|
|
'These tools are intended for use on purchased books.').format(license_type))
|
|
|
|
self.voucher = voucher
|
|
|
|
def getBookTitle(self):
|
|
return os.path.splitext(os.path.split(self.infile)[1])[0]
|
|
|
|
def getBookExtension(self):
|
|
return '.kfx-zip'
|
|
|
|
def getBookType(self):
|
|
return 'KFX-ZIP'
|
|
|
|
def cleanup(self):
|
|
pass
|
|
|
|
def getFile(self, outpath):
|
|
if not self.decrypted:
|
|
shutil.copyfile(self.infile, outpath)
|
|
else:
|
|
with zipfile.ZipFile(self.infile, 'r') as zif:
|
|
with zipfile.ZipFile(outpath, 'w') as zof:
|
|
for info in zif.infolist():
|
|
zof.writestr(info, self.decrypted.get(info.filename, zif.read(info.filename)))
|