tools v5.5
Plugins now include unaltered stand-alone scripts, so no longer need to keep separate copies.
This commit is contained in:
@@ -1,8 +1,11 @@
|
||||
#!/usr/bin/env python
|
||||
# vim:ts=4:sw=4:softtabstop=4:smarttab:expandtab
|
||||
#
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
# erdr2pml.py
|
||||
# Copyright © 2008 The Dark Reverser
|
||||
#
|
||||
# Modified 2008–2012 by some_updates, DiapDealer and Apprentice Alf
|
||||
|
||||
# This is a python script. You need a Python interpreter to run it.
|
||||
# For example, ActiveState Python, which exists for windows.
|
||||
# Changelog
|
||||
@@ -62,19 +65,11 @@
|
||||
# 0.21 - Support eReader (drm) version 11.
|
||||
# - Don't reject dictionary format.
|
||||
# - Ignore sidebars for dictionaries (different format?)
|
||||
# 0.22 - Unicode and plugin support, different image folders for PMLZ and source
|
||||
|
||||
__version__='0.21'
|
||||
__version__='0.22'
|
||||
|
||||
class Unbuffered:
|
||||
def __init__(self, stream):
|
||||
self.stream = stream
|
||||
def write(self, data):
|
||||
self.stream.write(data)
|
||||
self.stream.flush()
|
||||
def __getattr__(self, attr):
|
||||
return getattr(self.stream, attr)
|
||||
|
||||
import sys
|
||||
import sys, re
|
||||
import struct, binascii, getopt, zlib, os, os.path, urllib, tempfile
|
||||
|
||||
if 'calibre' in sys.modules:
|
||||
@@ -82,8 +77,66 @@ if 'calibre' in sys.modules:
|
||||
else:
|
||||
inCalibre = False
|
||||
|
||||
# Wrap a stream so that output gets flushed immediately
|
||||
# and also make sure that any unicode strings get
|
||||
# encoded using "replace" before writing them.
|
||||
class SafeUnbuffered:
|
||||
def __init__(self, stream):
|
||||
self.stream = stream
|
||||
self.encoding = stream.encoding
|
||||
if self.encoding == None:
|
||||
self.encoding = "utf-8"
|
||||
def write(self, data):
|
||||
if isinstance(data,unicode):
|
||||
data = data.encode(self.encoding,"replace")
|
||||
self.stream.write(data)
|
||||
self.stream.flush()
|
||||
def __getattr__(self, attr):
|
||||
return getattr(self.stream, attr)
|
||||
|
||||
iswindows = sys.platform.startswith('win')
|
||||
isosx = sys.platform.startswith('darwin')
|
||||
|
||||
def unicode_argv():
|
||||
if iswindows:
|
||||
# Uses shell32.GetCommandLineArgvW to get sys.argv as a list of Unicode
|
||||
# strings.
|
||||
|
||||
# Versions 2.x of Python don't support Unicode in sys.argv on
|
||||
# Windows, with the underlying Windows API instead replacing multi-byte
|
||||
# characters with '?'.
|
||||
|
||||
|
||||
from ctypes import POINTER, byref, cdll, c_int, windll
|
||||
from ctypes.wintypes import LPCWSTR, LPWSTR
|
||||
|
||||
GetCommandLineW = cdll.kernel32.GetCommandLineW
|
||||
GetCommandLineW.argtypes = []
|
||||
GetCommandLineW.restype = LPCWSTR
|
||||
|
||||
CommandLineToArgvW = windll.shell32.CommandLineToArgvW
|
||||
CommandLineToArgvW.argtypes = [LPCWSTR, POINTER(c_int)]
|
||||
CommandLineToArgvW.restype = POINTER(LPWSTR)
|
||||
|
||||
cmd = GetCommandLineW()
|
||||
argc = c_int(0)
|
||||
argv = CommandLineToArgvW(cmd, byref(argc))
|
||||
if argc.value > 0:
|
||||
# Remove Python executable and commands if present
|
||||
start = argc.value - len(sys.argv)
|
||||
return [argv[i] for i in
|
||||
xrange(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"]
|
||||
else:
|
||||
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]
|
||||
|
||||
Des = None
|
||||
if sys.platform.startswith('win'):
|
||||
if iswindows:
|
||||
# first try with pycrypto
|
||||
if inCalibre:
|
||||
from calibre_plugins.erdrpdb2pml import pycrypto_des
|
||||
@@ -168,17 +221,30 @@ class Sectionizer(object):
|
||||
off = self.sections[section][0]
|
||||
return self.contents[off:end_off]
|
||||
|
||||
def sanitizeFileName(s):
|
||||
r = ''
|
||||
for c in s:
|
||||
if c in "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789_.-":
|
||||
r += c
|
||||
return r
|
||||
# cleanup unicode filenames
|
||||
# borrowed from calibre from calibre/src/calibre/__init__.py
|
||||
# added in removal of control (<32) chars
|
||||
# and removal of . at start and end
|
||||
# and with some (heavily edited) code from Paul Durrant's kindlenamer.py
|
||||
def sanitizeFileName(name):
|
||||
# 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"\'")
|
||||
# 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()
|
||||
# remove leading dots
|
||||
while len(name)>0 and name[0] == u".":
|
||||
name = name[1:]
|
||||
# remove trailing dots (Windows doesn't like them)
|
||||
if name.endswith(u'.'):
|
||||
name = name[:-1]
|
||||
return name
|
||||
|
||||
def fixKey(key):
|
||||
def fixByte(b):
|
||||
return b ^ ((b ^ (b<<1) ^ (b<<2) ^ (b<<3) ^ (b<<4) ^ (b<<5) ^ (b<<6) ^ (b<<7) ^ 0x80) & 0x80)
|
||||
return "".join([chr(fixByte(ord(a))) for a in key])
|
||||
return "".join([chr(fixByte(ord(a))) for a in key])
|
||||
|
||||
def deXOR(text, sp, table):
|
||||
r=''
|
||||
@@ -191,7 +257,7 @@ def deXOR(text, sp, table):
|
||||
return r
|
||||
|
||||
class EreaderProcessor(object):
|
||||
def __init__(self, sect, username, creditcard):
|
||||
def __init__(self, sect, user_key):
|
||||
self.section_reader = sect.loadSection
|
||||
data = self.section_reader(0)
|
||||
version, = struct.unpack('>H', data[0:2])
|
||||
@@ -212,18 +278,10 @@ class EreaderProcessor(object):
|
||||
for i in xrange(len(data)):
|
||||
j = (j + shuf) % len(data)
|
||||
r[j] = data[i]
|
||||
assert len("".join(r)) == len(data)
|
||||
assert len("".join(r)) == len(data)
|
||||
return "".join(r)
|
||||
r = unshuff(input[0:-8], cookie_shuf)
|
||||
|
||||
def fixUsername(s):
|
||||
r = ''
|
||||
for c in s.lower():
|
||||
if (c >= 'a' and c <= 'z' or c >= '0' and c <= '9'):
|
||||
r += c
|
||||
return r
|
||||
|
||||
user_key = struct.pack('>LL', binascii.crc32(fixUsername(username)) & 0xffffffff, binascii.crc32(creditcard[-8:])& 0xffffffff)
|
||||
drm_sub_version = struct.unpack('>H', r[0:2])[0]
|
||||
self.num_text_pages = struct.unpack('>H', r[2:4])[0] - 1
|
||||
self.num_image_pages = struct.unpack('>H', r[26:26+2])[0]
|
||||
@@ -302,7 +360,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(name), data
|
||||
return sanitizeFileName(unicode(name,'windows-1252')), data
|
||||
|
||||
|
||||
# def getChapterNamePMLOffsetData(self):
|
||||
@@ -399,60 +457,53 @@ class EreaderProcessor(object):
|
||||
return r
|
||||
|
||||
def cleanPML(pml):
|
||||
# Convert special characters to proper PML code. High ASCII start at (\x80, \a128) and go up to (\xff, \a255)
|
||||
# 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):
|
||||
badChar = chr(k)
|
||||
pml2 = pml2.replace(badChar, '\\a%03d' % k)
|
||||
return pml2
|
||||
|
||||
def convertEreaderToPml(infile, name, cc, outdir):
|
||||
if not os.path.exists(outdir):
|
||||
os.makedirs(outdir)
|
||||
def decryptBook(infile, outpath, make_pmlz, user_key):
|
||||
bookname = os.path.splitext(os.path.basename(infile))[0]
|
||||
print " Decoding File"
|
||||
sect = Sectionizer(infile, 'PNRdPPrs')
|
||||
er = EreaderProcessor(sect, name, cc)
|
||||
|
||||
if er.getNumImages() > 0:
|
||||
print " Extracting images"
|
||||
imagedir = bookname + '_img/'
|
||||
imagedirpath = os.path.join(outdir,imagedir)
|
||||
if not os.path.exists(imagedirpath):
|
||||
os.makedirs(imagedirpath)
|
||||
for i in xrange(er.getNumImages()):
|
||||
name, contents = er.getImage(i)
|
||||
file(os.path.join(imagedirpath, name), 'wb').write(contents)
|
||||
|
||||
print " Extracting pml"
|
||||
pml_string = er.getText()
|
||||
pmlfilename = bookname + ".pml"
|
||||
file(os.path.join(outdir, pmlfilename),'wb').write(cleanPML(pml_string))
|
||||
|
||||
# bkinfo = er.getBookInfo()
|
||||
# if bkinfo != '':
|
||||
# print " Extracting book meta information"
|
||||
# file(os.path.join(outdir, 'bookinfo.txt'),'wb').write(bkinfo)
|
||||
|
||||
|
||||
|
||||
def decryptBook(infile, outdir, name, cc, make_pmlz):
|
||||
if make_pmlz :
|
||||
# ignore specified outdir, use tempdir instead
|
||||
if make_pmlz:
|
||||
# outpath is actually pmlz name
|
||||
pmlzname = outpath
|
||||
outdir = tempfile.mkdtemp()
|
||||
imagedirpath = os.path.join(outdir,u"images")
|
||||
else:
|
||||
pmlzname = None
|
||||
outdir = outpath
|
||||
imagedirpath = os.path.join(outdir,bookname + u"_img")
|
||||
|
||||
try:
|
||||
print "Processing..."
|
||||
convertEreaderToPml(infile, name, cc, outdir)
|
||||
if make_pmlz :
|
||||
if not os.path.exists(outdir):
|
||||
os.makedirs(outdir)
|
||||
print u"Decoding File"
|
||||
sect = Sectionizer(infile, 'PNRdPPrs')
|
||||
er = EreaderProcessor(sect, user_key)
|
||||
|
||||
if er.getNumImages() > 0:
|
||||
print u"Extracting images"
|
||||
if not os.path.exists(imagedirpath):
|
||||
os.makedirs(imagedirpath)
|
||||
for i in xrange(er.getNumImages()):
|
||||
name, contents = er.getImage(i)
|
||||
file(os.path.join(imagedirpath, name), 'wb').write(contents)
|
||||
|
||||
print u"Extracting pml"
|
||||
pml_string = er.getText()
|
||||
pmlfilename = bookname + ".pml"
|
||||
file(os.path.join(outdir, pmlfilename),'wb').write(cleanPML(pml_string))
|
||||
if pmlzname is not None:
|
||||
import zipfile
|
||||
import shutil
|
||||
print " Creating PMLZ file"
|
||||
zipname = infile[:-4] + '.pmlz'
|
||||
myZipFile = zipfile.ZipFile(zipname,'w',zipfile.ZIP_STORED, False)
|
||||
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 file in list:
|
||||
localname = file
|
||||
filePath = os.path.join(outdir,file)
|
||||
for filename in list:
|
||||
localname = filename
|
||||
filePath = os.path.join(outdir,filename)
|
||||
if os.path.isfile(filePath):
|
||||
myZipFile.write(filePath, localname)
|
||||
elif os.path.isdir(filePath):
|
||||
@@ -466,36 +517,46 @@ def decryptBook(infile, outdir, name, cc, make_pmlz):
|
||||
myZipFile.close()
|
||||
# remove temporary directory
|
||||
shutil.rmtree(outdir, True)
|
||||
print 'output is %s' % zipname
|
||||
print u"Output is {0}".format(pmlzname)
|
||||
else :
|
||||
print 'output in %s' % outdir
|
||||
print u"Output is in {0}".format(outdir)
|
||||
print "done"
|
||||
except ValueError, e:
|
||||
print "Error: %s" % e
|
||||
print u"Error: {0}".format(e.args[0])
|
||||
return 1
|
||||
return 0
|
||||
|
||||
|
||||
def usage():
|
||||
print "Converts DRMed eReader books to PML Source"
|
||||
print "Usage:"
|
||||
print " erdr2pml [options] infile.pdb [outdir] \"your name\" credit_card_number "
|
||||
print " "
|
||||
print "Options: "
|
||||
print " -h prints this message"
|
||||
print " --make-pmlz create PMLZ instead of using output directory"
|
||||
print " "
|
||||
print "Note:"
|
||||
print " if ommitted, outdir defaults based on 'infile.pdb'"
|
||||
print " 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):
|
||||
newname = "".join(c for c in name.lower() if c >= 'a' and c <= 'z' or c >= '0' and c <= '9')
|
||||
cc = cc.replace(" ","")
|
||||
return struct.pack('>LL', binascii.crc32(newname) & 0xffffffff,binascii.crc32(cc[-8:])& 0xffffffff)
|
||||
|
||||
def cli_main(argv=unicode_argv()):
|
||||
print u"eRdr2Pml v{0}. Copyright © 2009–2012 The Dark Reverser et al.".format(__version__)
|
||||
|
||||
def main(argv=None):
|
||||
try:
|
||||
opts, args = getopt.getopt(sys.argv[1:], "h", ["make-pmlz"])
|
||||
opts, args = getopt.getopt(argv[1:], "hp", ["make-pmlz"])
|
||||
except getopt.GetoptError, err:
|
||||
print str(err)
|
||||
print err.args[0]
|
||||
usage()
|
||||
return 1
|
||||
make_pmlz = False
|
||||
@@ -503,24 +564,31 @@ def main(argv=None):
|
||||
if o == "-h":
|
||||
usage()
|
||||
return 0
|
||||
elif o == "-p":
|
||||
make_pmlz = True
|
||||
elif o == "--make-pmlz":
|
||||
make_pmlz = True
|
||||
|
||||
print "eRdr2Pml v%s. Copyright (c) 2009 The Dark Reverser" % __version__
|
||||
|
||||
if len(args)!=3 and len(args)!=4:
|
||||
usage()
|
||||
return 1
|
||||
|
||||
if len(args)==3:
|
||||
infile, name, cc = args[0], args[1], args[2]
|
||||
outdir = infile[:-4] + '_Source'
|
||||
infile, name, cc = args
|
||||
if make_pmlz:
|
||||
outpath = os.path.splitext(infile)[0] + u".pmlz"
|
||||
else:
|
||||
outpath = os.path.splitext(infile)[0] + u"_Source"
|
||||
elif len(args)==4:
|
||||
infile, outdir, name, cc = args[0], args[1], args[2], args[3]
|
||||
infile, outpath, name, cc = args
|
||||
|
||||
return decryptBook(infile, outdir, name, cc, make_pmlz)
|
||||
print getuser_key(name,cc).encode('hex')
|
||||
|
||||
return decryptBook(infile, outpath, make_pmlz, getuser_key(name,cc))
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
sys.stdout=Unbuffered(sys.stdout)
|
||||
sys.exit(main())
|
||||
sys.stdout=SafeUnbuffered(sys.stdout)
|
||||
sys.stderr=SafeUnbuffered(sys.stderr)
|
||||
sys.exit(cli_main())
|
||||
|
||||
|
||||
Reference in New Issue
Block a user