Starting on Version 7.0 using the work done by others. Completely untested. I will be testing things, but I thought I'd get this base version up for others to give pull requests.

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
This commit is contained in:
Apprentice Harper
2020-09-26 21:22:47 +01:00
parent 4868a7460e
commit afa4ac5716
40 changed files with 757 additions and 729 deletions

View File

@@ -67,8 +67,9 @@
# - Ignore sidebars for dictionaries (different format?)
# 0.22 - Unicode and plugin support, different image folders for PMLZ and source
# 0.23 - moved unicode_argv call inside main for Windows DeDRM compatibility
# 1.00 - Added Python 3 compatibility for calibre 5.0
__version__='0.23'
__version__='1.00'
import sys, re
import struct, binascii, getopt, zlib, os, os.path, urllib, tempfile, traceback
@@ -88,7 +89,7 @@ class SafeUnbuffered:
if self.encoding == None:
self.encoding = "utf-8"
def write(self, data):
if isinstance(data,unicode):
if isinstance(data,bytes):
data = data.encode(self.encoding,"replace")
self.stream.write(data)
self.stream.flush()
@@ -126,7 +127,7 @@ def unicode_argv():
# Remove Python executable and commands if present
start = argc.value - len(sys.argv)
return [argv[i] for i in
xrange(start, argc.value)]
range(start, argc.value)]
# if we don't have any arguments at all, just pass back script name
# this should never happen
return [u"mobidedrm.py"]
@@ -134,7 +135,7 @@ def unicode_argv():
argvencoding = sys.stdin.encoding
if argvencoding == None:
argvencoding = "utf-8"
return [arg if (type(arg) == unicode) else unicode(arg,argvencoding) for arg in sys.argv]
return argv
Des = None
if iswindows:
@@ -200,7 +201,7 @@ class Sectionizer(object):
bkType = "Book"
def __init__(self, filename, ident):
self.contents = file(filename, 'rb').read()
self.contents = open(filename, 'rb').read()
self.header = self.contents[0:72]
self.num_sections, = struct.unpack('>H', self.contents[76:78])
# Dictionary or normal content (TODO: Not hard-coded)
@@ -210,7 +211,7 @@ class Sectionizer(object):
else:
raise ValueError('Invalid file format')
self.sections = []
for i in xrange(self.num_sections):
for i in range(self.num_sections):
offset, a1,a2,a3,a4 = struct.unpack('>LBBBB', self.contents[78+i*8:78+i*8+8])
flags, val = a1, a2<<16|a3<<8|a4
self.sections.append( (offset, flags, val) )
@@ -233,7 +234,7 @@ def sanitizeFileName(name):
# 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
name = re.sub(ur"\s", u" ", name).strip()
name = re.sub(r"\s", u" ", name).strip()
# remove leading dots
while len(name)>0 and name[0] == u".":
name = name[1:]
@@ -250,7 +251,7 @@ def fixKey(key):
def deXOR(text, sp, table):
r=''
j = sp
for i in xrange(len(text)):
for i in range(len(text)):
r += chr(ord(table[j]) ^ ord(text[i]))
j = j + 1
if j == len(table):
@@ -276,7 +277,7 @@ class EreaderProcessor(object):
def unshuff(data, shuf):
r = [''] * len(data)
j = 0
for i in xrange(len(data)):
for i in range(len(data)):
j = (j + shuf) % len(data)
r[j] = data[i]
assert len("".join(r)) == len(data)
@@ -330,7 +331,7 @@ class EreaderProcessor(object):
self.flags = struct.unpack('>L', r[4:8])[0]
reqd_flags = (1<<9) | (1<<7) | (1<<10)
if (self.flags & reqd_flags) != reqd_flags:
print "Flags: 0x%X" % self.flags
print("Flags: 0x%X" % self.flags)
raise ValueError('incompatible eReader file')
des = Des(fixKey(user_key))
if version == 259:
@@ -361,7 +362,7 @@ class EreaderProcessor(object):
sect = self.section_reader(self.first_image_page + i)
name = sect[4:4+32].strip('\0')
data = sect[62:]
return sanitizeFileName(unicode(name,'windows-1252')), data
return sanitizeFileName(name.decode('windows-1252')), data
# def getChapterNamePMLOffsetData(self):
@@ -410,7 +411,7 @@ class EreaderProcessor(object):
def getText(self):
des = Des(fixKey(self.content_key))
r = ''
for i in xrange(self.num_text_pages):
for i in range(self.num_text_pages):
logging.debug('get page %d', i)
r += zlib.decompress(des.decrypt(self.section_reader(1 + i)))
@@ -422,7 +423,7 @@ class EreaderProcessor(object):
fnote_ids = deXOR(sect, 0, self.xortable)
# the remaining records of the footnote sections need to be decoded with the content_key and zlib inflated
des = Des(fixKey(self.content_key))
for i in xrange(1,self.num_footnote_pages):
for i in range(1,self.num_footnote_pages):
logging.debug('get footnotepage %d', i)
id_len = ord(fnote_ids[2])
id = fnote_ids[3:3+id_len]
@@ -446,7 +447,7 @@ class EreaderProcessor(object):
sbar_ids = deXOR(sect, 0, self.xortable)
# the remaining records of the sidebar sections need to be decoded with the content_key and zlib inflated
des = Des(fixKey(self.content_key))
for i in xrange(1,self.num_sidebar_pages):
for i in range(1,self.num_sidebar_pages):
id_len = ord(sbar_ids[2])
id = sbar_ids[3:3+id_len]
smarker = '<sidebar id="%s">\n' % id
@@ -460,7 +461,7 @@ class EreaderProcessor(object):
def cleanPML(pml):
# Convert special characters to proper PML code. High ASCII start at (\x80, \a128) and go up to (\xff, \a255)
pml2 = pml
for k in xrange(128,256):
for k in range(128,256):
badChar = chr(k)
pml2 = pml2.replace(badChar, '\\a%03d' % k)
return pml2
@@ -480,26 +481,26 @@ def decryptBook(infile, outpath, make_pmlz, user_key):
try:
if not os.path.exists(outdir):
os.makedirs(outdir)
print u"Decoding File"
print(u"Decoding File")
sect = Sectionizer(infile, 'PNRdPPrs')
er = EreaderProcessor(sect, user_key)
if er.getNumImages() > 0:
print u"Extracting images"
print(u"Extracting images")
if not os.path.exists(imagedirpath):
os.makedirs(imagedirpath)
for i in xrange(er.getNumImages()):
for i in range(er.getNumImages()):
name, contents = er.getImage(i)
file(os.path.join(imagedirpath, name), 'wb').write(contents)
open(os.path.join(imagedirpath, name), 'wb').write(contents)
print u"Extracting pml"
print(u"Extracting pml")
pml_string = er.getText()
pmlfilename = bookname + ".pml"
file(os.path.join(outdir, pmlfilename),'wb').write(cleanPML(pml_string))
open(os.path.join(outdir, pmlfilename),'wb').write(cleanPML(pml_string))
if pmlzname is not None:
import zipfile
import shutil
print u"Creating PMLZ file {0}".format(os.path.basename(pmlzname))
print(u"Creating PMLZ file {0}".format(os.path.basename(pmlzname)))
myZipFile = zipfile.ZipFile(pmlzname,'w',zipfile.ZIP_STORED, False)
list = os.listdir(outdir)
for filename in list:
@@ -518,33 +519,33 @@ def decryptBook(infile, outpath, make_pmlz, user_key):
myZipFile.close()
# remove temporary directory
shutil.rmtree(outdir, True)
print u"Output is {0}".format(pmlzname)
print(u"Output is {0}".format(pmlzname))
else :
print u"Output is in {0}".format(outdir)
print "done"
except ValueError, e:
print u"Error: {0}".format(e)
print(u"Output is in {0}".format(outdir))
print("done")
except ValueError as e:
print(u"Error: {0}".format(e))
traceback.print_exc()
return 1
return 0
def usage():
print u"Converts DRMed eReader books to PML Source"
print u"Usage:"
print u" erdr2pml [options] infile.pdb [outpath] \"your name\" credit_card_number"
print u" "
print u"Options: "
print u" -h prints this message"
print u" -p create PMLZ instead of source folder"
print u" --make-pmlz create PMLZ instead of source folder"
print u" "
print u"Note:"
print u" if outpath is ommitted, creates source in 'infile_Source' folder"
print u" if outpath is ommitted and pmlz option, creates PMLZ 'infile.pmlz'"
print u" if source folder created, images are in infile_img folder"
print u" if pmlz file created, images are in images folder"
print u" It's enough to enter the last 8 digits of the credit card number"
print(u"Converts DRMed eReader books to PML Source")
print(u"Usage:")
print(u" erdr2pml [options] infile.pdb [outpath] \"your name\" credit_card_number")
print(u" ")
print(u"Options: ")
print(u" -h prints this message")
print(u" -p create PMLZ instead of source folder")
print(u" --make-pmlz create PMLZ instead of source folder")
print(u" ")
print(u"Note:")
print(u" if outpath is ommitted, creates source in 'infile_Source' folder")
print(u" if outpath is ommitted and pmlz option, creates PMLZ 'infile.pmlz'")
print(u" if source folder created, images are in infile_img folder")
print(u" if pmlz file created, images are in images folder")
print(u" It's enough to enter the last 8 digits of the credit card number")
return
def getuser_key(name,cc):
@@ -553,13 +554,13 @@ def getuser_key(name,cc):
return struct.pack('>LL', binascii.crc32(newname) & 0xffffffff,binascii.crc32(cc[-8:])& 0xffffffff)
def cli_main():
print u"eRdr2Pml v{0}. Copyright © 20092012 The Dark Reverser et al.".format(__version__)
print(u"eRdr2Pml v{0}. Copyright © 20092012 The Dark Reverser et al.".format(__version__))
argv=unicode_argv()
try:
opts, args = getopt.getopt(argv[1:], "hp", ["make-pmlz"])
except getopt.GetoptError, err:
print err.args[0]
except getopt.GetoptError as err:
print(err.args[0])
usage()
return 1
make_pmlz = False
@@ -585,7 +586,7 @@ def cli_main():
elif len(args)==4:
infile, outpath, name, cc = args
print getuser_key(name,cc).encode('hex')
print(getuser_key(name,cc).encode('hex'))
return decryptBook(infile, outpath, make_pmlz, getuser_key(name,cc))