Compare commits

...

4 Commits
v1.3 ... v1.6

Author SHA1 Message Date
Apprentice Alf
9c73801685 tools v1.6 2015-03-02 18:02:20 +00:00
Apprentice Alf
86357531a5 mobidedrm 0.13 2015-03-02 17:54:38 +00:00
Apprentice Alf
8e7d2657a4 tools v1.5 2015-03-02 07:43:31 +00:00
Apprentice Alf
6fb13373cf tools v1.4 2015-03-02 07:41:20 +00:00
38 changed files with 1038 additions and 291 deletions

View File

@@ -0,0 +1,123 @@
#! /usr/bin/env python
# ineptkeymac.py, version 1
# This program runs on Mac OS X, version 10.6.2 and probably several other
# versions. It uses Python 2.6, but it probably also runs on all versions
# 2.x with x >= 5.
# This program extracts the private RSA key for your ADE account in a
# standard binary form (DER format) in a file of your choosing. Its purpose
# is to make a backup of that key so that your legally bought ADE encoded
# ebooks can be salvaged in case they would no longer be supported by ADE
# software. No other usages are intended.
# It has been tested with the key storage structure of ADE 1.7.1 and 1.7.2
# and Sony Reader Library.
# This software does not contain any encryption code. Its only use of
# external encryption software is the use of openssl for the conversion of
# the private key from pem to der format. It doesn't use encryption or
# decryption, however.
# You can run this program from the command line (python ineptkeymac.py
# filename), or by doubleclicking when it has been associated with
# Pythonlauncher. When no filename is given it will show a dialog to obtain one.
from __future__ import with_statement
__license__ = 'GPL v3'
import sys
import os
import xml.etree.ElementTree as etree
from contextlib import closing
import Tkinter
import Tkconstants
import tkFileDialog
from tkMessageBox import showerror
from subprocess import Popen, PIPE
import textwrap
NS = 'http://ns.adobe.com/adept'
ACTFILE = '~/Library/Application Support/Adobe/Digital Editions/activation.dat'
HEADER = '-----BEGIN PRIVATE KEY-----\n'
FOOTER = '\n-----END PRIVATE KEY-----\n'
Gui = False
def get_key():
'''Returns the private key as a binary string (DER format)'''
try:
filename = os.path.expanduser(ACTFILE)
tree = etree.parse(filename)
xpath = '//{%s}credentials/{%s}privateLicenseKey' % (NS, NS)
b64key = tree.findtext(xpath)
pemkey = HEADER + textwrap.fill(b64key, 64) + FOOTER
cmd = ['openssl', 'rsa', '-outform', 'der']
proc = Popen(cmd, stdin=PIPE, stdout=PIPE, stderr=PIPE)
stdout, stderr = proc.communicate(pemkey)
if proc.returncode != 0:
error("openssl error: " + stderr)
return None
return stdout
except IOError:
error("Can find keyfile. Maybe you should activate your Adobe ID.")
sys.exit(1)
def store_key(key, keypath):
'''Store the key in the file given as keypath. If no keypath is given a
dialog will ask for one.'''
try:
if keypath is None:
keypath = get_keypath()
if not keypath: # Cancelled
return
with closing(open(keypath, 'wb')) as outf:
outf.write(key)
except IOError, e:
error("Can write keyfile: " + str(e))
def get_keypath():
keypath = tkFileDialog.asksaveasfilename(
parent = None, title = 'Select file to store ADEPT key',
initialdir = os.path.expanduser('~/Desktop'),
initialfile = 'adeptkey.der',
defaultextension = '.der', filetypes = [('DER-encoded files', '.der'),
('All Files', '.*')])
if keypath:
keypath = os.path.normpath(keypath)
return keypath
def error(text):
print text
if Gui: showerror('Error!', text)
def gui_main():
root = Tkinter.Tk()
root.iconify()
global Gui
Gui = True
store_key(get_key(), None)
return 0
def main(argv=sys.argv):
progname = os.path.basename(argv[0])
if len(argv) == 1: # assume GUI if no argument given
return gui_main()
if len(argv) != 2:
print "usage: %s KEYFILE" % (progname,)
return 1
store_key(get_key(), argv[1])
if __name__ == '__main__':
sys.exit(main())

View File

@@ -26,6 +26,23 @@
# Also now handles encrypted files that don't need a specific PID. # Also now handles encrypted files that don't need a specific PID.
# 0.11 - use autoflushed stdout and proper return values # 0.11 - use autoflushed stdout and proper return values
# 0.12 - Fix for problems with metadata import as Calibre plugin, report errors # 0.12 - Fix for problems with metadata import as Calibre plugin, report errors
# 0.13 - Formatting fixes: retabbed file, removed trailing whitespace
# and extra blank lines, converted CR/LF pairs at ends of each line,
# and other cosmetic fixes.
# 0.14 - Working out when the extra data flags are present has been problematic
# Versions 7 through 9 have tried to tweak the conditions, but have been
# only partially successful. Closer examination of lots of sample
# files reveals that a confusin has arisen because trailing data entries
# are not encrypted, but it turns out that the multibyte entries
# in utf8 file are encrypted. (Although neither kind gets compressed.)
# This knowledge leads to a simplification of the test for the
# trailing data byte flags - version 5 and higher AND header size >= 0xE4.
__version__ = '0.14'
import sys
import struct
import binascii
class Unbuffered: class Unbuffered:
def __init__(self, stream): def __init__(self, stream):
@@ -36,15 +53,10 @@ class Unbuffered:
def __getattr__(self, attr): def __getattr__(self, attr):
return getattr(self.stream, attr) return getattr(self.stream, attr)
import sys
sys.stdout=Unbuffered(sys.stdout)
import struct,binascii
class DrmException(Exception): class DrmException(Exception):
pass pass
#implementation of Pukall Cipher 1 # Implementation of Pukall Cipher 1
def PC1(key, src, decryption=True): def PC1(key, src, decryption=True):
sum1 = 0; sum1 = 0;
sum2 = 0; sum2 = 0;
@@ -109,8 +121,10 @@ def getSizeOfTrailingDataEntries(ptr, size, flags):
if testflags & 1: if testflags & 1:
num += getSizeOfTrailingDataEntry(ptr, size - num) num += getSizeOfTrailingDataEntry(ptr, size - num)
testflags >>= 1 testflags >>= 1
if flags & 1: # Multibyte data, if present, is included in the encryption, so
num += (ord(ptr[size - num - 1]) & 0x3) + 1 # we do not need to check the low bit.
# if flags & 1:
# num += (ord(ptr[size - num - 1]) & 0x3) + 1
return num return num
class DrmStripper: class DrmStripper:
@@ -160,9 +174,7 @@ class DrmStripper:
break break
return found_key return found_key
def __init__(self, data_file, pid): def __init__(self, data_file, pid):
if checksumPid(pid[0:-2]) != pid: if checksumPid(pid[0:-2]) != pid:
raise DrmException("invalid PID checksum") raise DrmException("invalid PID checksum")
pid = pid[0:-2] pid = pid[0:-2]
@@ -186,11 +198,10 @@ class DrmStripper:
extra_data_flags = 0 extra_data_flags = 0
print "MOBI header length = %d" %mobi_length print "MOBI header length = %d" %mobi_length
print "MOBI header version = %d" %mobi_version print "MOBI header version = %d" %mobi_version
if (mobi_length >= 0xE4) and (mobi_version > 5): if (mobi_length >= 0xE4) and (mobi_version >= 5):
extra_data_flags, = struct.unpack('>H', sect[0xF2:0xF4]) extra_data_flags, = struct.unpack('>H', sect[0xF2:0xF4])
print "Extra Data Flags = %d" %extra_data_flags print "Extra Data Flags = %d" %extra_data_flags
crypto_type, = struct.unpack('>H', sect[0xC:0xC+2]) crypto_type, = struct.unpack('>H', sect[0xC:0xC+2])
if crypto_type == 0: if crypto_type == 0:
print "This book is not encrypted." print "This book is not encrypted."
@@ -223,6 +234,7 @@ class DrmStripper:
# print "record %d, extra_size %d" %(i,extra_size) # print "record %d, extra_size %d" %(i,extra_size)
self.patchSection(i, PC1(found_key, data[0:len(data) - extra_size])) self.patchSection(i, PC1(found_key, data[0:len(data) - extra_size]))
print "done" print "done"
def getResult(self): def getResult(self):
return self.data_file return self.data_file
@@ -230,16 +242,14 @@ if not __name__ == "__main__":
from calibre.customize import FileTypePlugin from calibre.customize import FileTypePlugin
class MobiDeDRM(FileTypePlugin): class MobiDeDRM(FileTypePlugin):
name = 'MobiDeDRM' # Name of the plugin name = 'MobiDeDRM' # Name of the plugin
description = 'Removes DRM from secure Mobi files' description = 'Removes DRM from secure Mobi files'
supported_platforms = ['linux', 'osx', 'windows'] # Platforms this plugin will run on supported_platforms = ['linux', 'osx', 'windows'] # Platforms this plugin will run on
author = 'The Dark Reverser' # The author of this plugin author = 'The Dark Reverser' # The author of this plugin
version = (0, 1, 2) # The version number of this plugin version = (0, 1, 4) # The version number of this plugin
file_types = set(['prc','mobi','azw']) # The file types that this plugin will be applied to file_types = set(['prc','mobi','azw']) # The file types that this plugin will be applied to
on_import = True # Run this plugin during the import on_import = True # Run this plugin during the import
def run(self, path_to_ebook): def run(self, path_to_ebook):
from calibre.gui2 import is_ok_to_use_qt from calibre.gui2 import is_ok_to_use_qt
from PyQt4.Qt import QMessageBox from PyQt4.Qt import QMessageBox
@@ -268,11 +278,13 @@ if not __name__ == "__main__":
return 'Enter PID (separate multiple PIDs with comma)' return 'Enter PID (separate multiple PIDs with comma)'
if __name__ == "__main__": if __name__ == "__main__":
print "MobiDeDrm v0.12. Copyright (c) 2008 The Dark Reverser" sys.stdout=Unbuffered(sys.stdout)
print ('MobiDeDrm v%(__version__)s. '
'Copyright 2008-2010 The Dark Reverser.' % globals())
if len(sys.argv)<4: if len(sys.argv)<4:
print "Removes protection from Mobipocket books" print "Removes protection from Mobipocket books"
print "Usage:" print "Usage:"
print " mobidedrm infile.mobi outfile.mobi (PID)" print " %s <infile> <outfile> <PID>" % sys.argv[0]
sys.exit(1) sys.exit(1)
else: else:
infile = sys.argv[1] infile = sys.argv[1]

View File

@@ -0,0 +1,300 @@
#!/usr/bin/python
#
# This is a python script. You need a Python interpreter to run it.
# For example, ActiveState Python, which exists for windows.
#
# It can run standalone to convert files, or it can be installed as a
# plugin for Calibre (http://calibre-ebook.com/about) so that
# importing files with DRM 'Just Works'.
#
# To create a Calibre plugin, rename this file so that the filename
# ends in '_plugin.py', put it into a ZIP file and import that Calibre
# using its plugin configuration GUI.
#
# Changelog
# 0.01 - Initial version
# 0.02 - Huffdic compressed books were not properly decrypted
# 0.03 - Wasn't checking MOBI header length
# 0.04 - Wasn't sanity checking size of data record
# 0.05 - It seems that the extra data flags take two bytes not four
# 0.06 - And that low bit does mean something after all :-)
# 0.07 - The extra data flags aren't present in MOBI header < 0xE8 in size
# 0.08 - ...and also not in Mobi header version < 6
# 0.09 - ...but they are there with Mobi header version 6, header size 0xE4!
# 0.10 - Outputs unencrypted files as-is, so that when run as a Calibre
# import filter it works when importing unencrypted files.
# Also now handles encrypted files that don't need a specific PID.
# 0.11 - use autoflushed stdout and proper return values
# 0.12 - Fix for problems with metadata import as Calibre plugin, report errors
# 0.13 - Formatting fixes: retabbed file, removed trailing whitespace
# and extra blank lines, converted CR/LF pairs at ends of each line,
# and other cosmetic fixes.
# 0.14 - Working out when the extra data flags are present has been problematic
# Versions 7 through 9 have tried to tweak the conditions, but have been
# only partially successful. Closer examination of lots of sample
# files reveals that a confusin has arisen because trailing data entries
# are not encrypted, but it turns out that the multibyte entries
# in utf8 file are encrypted. (Although neither kind gets compressed.)
# This knowledge leads to a simplification of the test for the
# trailing data byte flags - version 5 and higher AND header size >= 0xE4.
__version__ = '0.14'
import sys
import struct
import binascii
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)
class DrmException(Exception):
pass
# Implementation of Pukall Cipher 1
def PC1(key, src, decryption=True):
sum1 = 0;
sum2 = 0;
keyXorVal = 0;
if len(key)!=16:
print "Bad key length!"
return None
wkey = []
for i in xrange(8):
wkey.append(ord(key[i*2])<<8 | ord(key[i*2+1]))
dst = ""
for i in xrange(len(src)):
temp1 = 0;
byteXorVal = 0;
for j in xrange(8):
temp1 ^= wkey[j]
sum2 = (sum2+j)*20021 + sum1
sum1 = (temp1*346)&0xFFFF
sum2 = (sum2+sum1)&0xFFFF
temp1 = (temp1*20021+1)&0xFFFF
byteXorVal ^= temp1 ^ sum2
curByte = ord(src[i])
if not decryption:
keyXorVal = curByte * 257;
curByte = ((curByte ^ (byteXorVal >> 8)) ^ byteXorVal) & 0xFF
if decryption:
keyXorVal = curByte * 257;
for j in xrange(8):
wkey[j] ^= keyXorVal;
dst+=chr(curByte)
return dst
def checksumPid(s):
letters = "ABCDEFGHIJKLMNPQRSTUVWXYZ123456789"
crc = (~binascii.crc32(s,-1))&0xFFFFFFFF
crc = crc ^ (crc >> 16)
res = s
l = len(letters)
for i in (0,1):
b = crc & 0xff
pos = (b // l) ^ (b % l)
res += letters[pos%l]
crc >>= 8
return res
def getSizeOfTrailingDataEntries(ptr, size, flags):
def getSizeOfTrailingDataEntry(ptr, size):
bitpos, result = 0, 0
if size <= 0:
return result
while True:
v = ord(ptr[size-1])
result |= (v & 0x7F) << bitpos
bitpos += 7
size -= 1
if (v & 0x80) != 0 or (bitpos >= 28) or (size == 0):
return result
num = 0
testflags = flags >> 1
while testflags:
if testflags & 1:
num += getSizeOfTrailingDataEntry(ptr, size - num)
testflags >>= 1
# Multibyte data, if present, is included in the encryption, so
# we do not need to check the low bit.
# if flags & 1:
# num += (ord(ptr[size - num - 1]) & 0x3) + 1
return num
class DrmStripper:
def loadSection(self, section):
if (section + 1 == self.num_sections):
endoff = len(self.data_file)
else:
endoff = self.sections[section + 1][0]
off = self.sections[section][0]
return self.data_file[off:endoff]
def patch(self, off, new):
self.data_file = self.data_file[:off] + new + self.data_file[off+len(new):]
def patchSection(self, section, new, in_off = 0):
if (section + 1 == self.num_sections):
endoff = len(self.data_file)
else:
endoff = self.sections[section + 1][0]
off = self.sections[section][0]
assert off + in_off + len(new) <= endoff
self.patch(off + in_off, new)
def parseDRM(self, data, count, pid):
pid = pid.ljust(16,'\0')
keyvec1 = "\x72\x38\x33\xB0\xB4\xF2\xE3\xCA\xDF\x09\x01\xD6\xE2\xE0\x3F\x96"
temp_key = PC1(keyvec1, pid, False)
temp_key_sum = sum(map(ord,temp_key)) & 0xff
found_key = None
for i in xrange(count):
verification, size, type, cksum, cookie = struct.unpack('>LLLBxxx32s', data[i*0x30:i*0x30+0x30])
cookie = PC1(temp_key, cookie)
ver,flags,finalkey,expiry,expiry2 = struct.unpack('>LL16sLL', cookie)
if verification == ver and cksum == temp_key_sum and (flags & 0x1F) == 1:
found_key = finalkey
break
if not found_key:
# Then try the default encoding that doesn't require a PID
temp_key = keyvec1
temp_key_sum = sum(map(ord,temp_key)) & 0xff
for i in xrange(count):
verification, size, type, cksum, cookie = struct.unpack('>LLLBxxx32s', data[i*0x30:i*0x30+0x30])
cookie = PC1(temp_key, cookie)
ver,flags,finalkey,expiry,expiry2 = struct.unpack('>LL16sLL', cookie)
if verification == ver and cksum == temp_key_sum:
found_key = finalkey
break
return found_key
def __init__(self, data_file, pid):
if checksumPid(pid[0:-2]) != pid:
raise DrmException("invalid PID checksum")
pid = pid[0:-2]
self.data_file = data_file
header = data_file[0:72]
if header[0x3C:0x3C+8] != 'BOOKMOBI':
raise DrmException("invalid file format")
self.num_sections, = struct.unpack('>H', data_file[76:78])
self.sections = []
for i in xrange(self.num_sections):
offset, a1,a2,a3,a4 = struct.unpack('>LBBBB', data_file[78+i*8:78+i*8+8])
flags, val = a1, a2<<16|a3<<8|a4
self.sections.append( (offset, flags, val) )
sect = self.loadSection(0)
records, = struct.unpack('>H', sect[0x8:0x8+2])
mobi_length, = struct.unpack('>L',sect[0x14:0x18])
mobi_version, = struct.unpack('>L',sect[0x68:0x6C])
extra_data_flags = 0
print "MOBI header length = %d" %mobi_length
print "MOBI header version = %d" %mobi_version
if (mobi_length >= 0xE4) and (mobi_version >= 5):
extra_data_flags, = struct.unpack('>H', sect[0xF2:0xF4])
print "Extra Data Flags = %d" %extra_data_flags
crypto_type, = struct.unpack('>H', sect[0xC:0xC+2])
if crypto_type == 0:
print "This book is not encrypted."
else:
if crypto_type == 1:
raise DrmException("cannot decode Mobipocket encryption type 1")
if crypto_type != 2:
raise DrmException("unknown encryption type: %d" % crypto_type)
# calculate the keys
drm_ptr, drm_count, drm_size, drm_flags = struct.unpack('>LLLL', sect[0xA8:0xA8+16])
if drm_count == 0:
raise DrmException("no PIDs found in this file")
found_key = self.parseDRM(sect[drm_ptr:drm_ptr+drm_size], drm_count, pid)
if not found_key:
raise DrmException("no key found. maybe the PID is incorrect")
# kill the drm keys
self.patchSection(0, "\0" * drm_size, drm_ptr)
# kill the drm pointers
self.patchSection(0, "\xff" * 4 + "\0" * 12, 0xA8)
# clear the crypto type
self.patchSection(0, "\0" * 2, 0xC)
# decrypt sections
print "Decrypting. Please wait...",
for i in xrange(1, records+1):
data = self.loadSection(i)
extra_size = getSizeOfTrailingDataEntries(data, len(data), extra_data_flags)
# print "record %d, extra_size %d" %(i,extra_size)
self.patchSection(i, PC1(found_key, data[0:len(data) - extra_size]))
print "done"
def getResult(self):
return self.data_file
if not __name__ == "__main__":
from calibre.customize import FileTypePlugin
class MobiDeDRM(FileTypePlugin):
name = 'MobiDeDRM' # Name of the plugin
description = 'Removes DRM from secure Mobi files'
supported_platforms = ['linux', 'osx', 'windows'] # Platforms this plugin will run on
author = 'The Dark Reverser' # The author of this plugin
version = (0, 1, 4) # The version number of this plugin
file_types = set(['prc','mobi','azw']) # The file types that this plugin will be applied to
on_import = True # Run this plugin during the import
def run(self, path_to_ebook):
from calibre.gui2 import is_ok_to_use_qt
from PyQt4.Qt import QMessageBox
PID = self.site_customization
data_file = file(path_to_ebook, 'rb').read()
ar = PID.split(',')
for i in ar:
try:
unlocked_file = DrmStripper(data_file, i).getResult()
except DrmException:
# ignore the error
pass
else:
of = self.temporary_file('.mobi')
of.write(unlocked_file)
of.close()
return of.name
if is_ok_to_use_qt():
d = QMessageBox(QMessageBox.Warning, "MobiDeDRM Plugin", "Couldn't decode: %s\n\nImporting encrypted version." % path_to_ebook)
d.show()
d.raise_()
d.exec_()
return path_to_ebook
def customization_help(self, gui=False):
return 'Enter PID (separate multiple PIDs with comma)'
if __name__ == "__main__":
sys.stdout=Unbuffered(sys.stdout)
print ('MobiDeDrm v%(__version__)s. '
'Copyright 2008-2010 The Dark Reverser.' % globals())
if len(sys.argv)<4:
print "Removes protection from Mobipocket books"
print "Usage:"
print " %s <infile> <outfile> <PID>" % sys.argv[0]
sys.exit(1)
else:
infile = sys.argv[1]
outfile = sys.argv[2]
pid = sys.argv[3]
data_file = file(infile, 'rb').read()
try:
strippedFile = DrmStripper(data_file, pid)
file(outfile, 'wb').write(strippedFile.getResult())
except DrmException, e:
print "Error: %s" % e
sys.exit(1)
sys.exit(0)

View File

@@ -1,5 +1,5 @@
#! /usr/bin/python #! /usr/bin/python
# For use in Topaz Scripts version 2.3 # For use in Topaz Scripts version 2.6
""" """

View File

@@ -1,5 +1,5 @@
#!/usr/bin/python #!/usr/bin/python
# For use with Topaz Scripts Version 2.3 # For use with Topaz Scripts Version 2.6
class Unbuffered: class Unbuffered:
def __init__(self, stream): def __init__(self, stream):

View File

@@ -1,6 +1,6 @@
#! /usr/bin/python #! /usr/bin/python
# 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.3 # For use with Topaz Scripts Version 2.6
class Unbuffered: class Unbuffered:
def __init__(self, stream): def __init__(self, stream):
@@ -243,6 +243,8 @@ class PageParser(object):
'region.h' : (1, 'scalar_number', 0, 0), 'region.h' : (1, 'scalar_number', 0, 0),
'region.w' : (1, 'scalar_number', 0, 0), 'region.w' : (1, 'scalar_number', 0, 0),
'empty_text_region' : (1, 'snippets', 1, 0),
'img' : (1, 'snippets', 1, 0), 'img' : (1, 'snippets', 1, 0),
'img.x' : (1, 'scalar_number', 0, 0), 'img.x' : (1, 'scalar_number', 0, 0),
'img.y' : (1, 'scalar_number', 0, 0), 'img.y' : (1, 'scalar_number', 0, 0),
@@ -313,6 +315,12 @@ class PageParser(object):
'version.findlists' : (1, 'scalar_text', 0, 0), 'version.findlists' : (1, 'scalar_text', 0, 0),
'version.page_num' : (1, 'scalar_text', 0, 0), 'version.page_num' : (1, 'scalar_text', 0, 0),
'version.page_type' : (1, 'scalar_text', 0, 0), 'version.page_type' : (1, 'scalar_text', 0, 0),
'version.bad_text' : (1, 'scalar_text', 0, 0),
'version.glyph_mismatch' : (1, 'scalar_text', 0, 0),
'version.margins' : (1, 'scalar_text', 0, 0),
'version.staggered_lines' : (1, 'scalar_text', 0, 0),
'version.paragraph_continuation' : (1, 'scalar_text', 0, 0),
'version.toc' : (1, 'scalar_text', 0, 0),
'stylesheet' : (1, 'snippets', 1, 0), 'stylesheet' : (1, 'snippets', 1, 0),
'style' : (1, 'snippets', 1, 0), 'style' : (1, 'snippets', 1, 0),
@@ -660,16 +668,19 @@ class PageParser(object):
def process(self): def process(self):
# peek at the first bytes to see what type of file it is # peek at the first bytes to see what type of file it is
magic = self.fo.read(11) magic = self.fo.read(9)
if (magic[0:1] == 'p') and (magic[2:10] == '__PAGE__'): if (magic[0:1] == 'p') and (magic[2:9] == 'marker_'):
first_token = 'info' first_token = 'info'
elif (magic[0:1] == 'g') and (magic[2:11] == '__GLYPH__'): elif (magic[0:1] == 'p') and (magic[2:9] == '__PAGE_'):
skip = self.fo.read(1) skip = self.fo.read(2)
first_token = 'info'
elif (magic[0:1] == 'g') and (magic[2:9] == '__GLYPH'):
skip = self.fo.read(3)
first_token = 'info' first_token = 'info'
else : else :
# other0.dat file # other0.dat file
first_token = None first_token = None
self.fo.seek(-11,1) self.fo.seek(-9,1)
# main loop to read and build the document tree # main loop to read and build the document tree

View File

@@ -1,6 +1,6 @@
#! /usr/bin/python #! /usr/bin/python
# 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.3 # For use with Topaz Scripts Version 2.6
import csv import csv
import sys import sys

View File

@@ -1,6 +1,6 @@
#! /usr/bin/python #! /usr/bin/python
# 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.3 # For use with Topaz Scripts Version 2.6
import sys import sys
import csv import csv
@@ -32,6 +32,8 @@ class DocParser(object):
self.link_id = [] self.link_id = []
self.link_title = [] self.link_title = []
self.link_page = [] self.link_page = []
self.link_href = []
self.link_type = []
self.dehyphen_rootid = [] self.dehyphen_rootid = []
self.paracont_stemid = [] self.paracont_stemid = []
self.parastems_stemid = [] self.parastems_stemid = []
@@ -197,6 +199,7 @@ class DocParser(object):
# get the class # get the class
def getClass(self, pclass): def getClass(self, pclass):
nclass = pclass nclass = pclass
# class names are an issue given topaz may start them with numerals (not allowed), # class names are an issue given topaz may start them with numerals (not allowed),
# use a mix of cases (which cause some browsers problems), and actually # use a mix of cases (which cause some browsers problems), and actually
# attach numbers after "_reclustered*" to the end to deal classeses that inherit # attach numbers after "_reclustered*" to the end to deal classeses that inherit
@@ -206,7 +209,10 @@ class DocParser(object):
# so we clean this up by lowercasing, prepend 'cl-', and getting any baseclass # so we clean this up by lowercasing, prepend 'cl-', and getting any baseclass
# that exists in the stylesheet first, and then adding this specific class # that exists in the stylesheet first, and then adding this specific class
# after # after
# also some class names have spaces in them so need to convert to dashes
if nclass != None : if nclass != None :
nclass = nclass.replace(' ','-')
classres = '' classres = ''
nclass = nclass.lower() nclass = nclass.lower()
nclass = 'cl-' + nclass nclass = 'cl-' + nclass
@@ -334,7 +340,7 @@ class DocParser(object):
result.append(('svg', num)) result.append(('svg', num))
return pclass, result return pclass, result
# this type of paragrph may be made up of multiple spans, inline # this type of paragraph may be made up of multiple spans, inline
# word monograms (images), and words with semantic meaning, # word monograms (images), and words with semantic meaning,
# plus glyphs used to form starting letter of first word # plus glyphs used to form starting letter of first word
@@ -391,6 +397,9 @@ class DocParser(object):
result.append(('img' + word_class, int(argres))) result.append(('img' + word_class, int(argres)))
word_class = '' word_class = ''
elif name.endswith('region.img.src'):
result.append(('img' + word_class, int(argres)))
if (sp_first != -1) and (sp_last != -1): if (sp_first != -1) and (sp_last != -1):
for wordnum in xrange(sp_first, sp_last): for wordnum in xrange(sp_first, sp_last):
result.append(('ocr', wordnum)) result.append(('ocr', wordnum))
@@ -437,6 +446,8 @@ class DocParser(object):
if (type == 'end'): if (type == 'end'):
parares += ' ' parares += ' '
lstart = len(parares)
cnt = len(pdesc) cnt = len(pdesc)
for j in xrange( 0, cnt) : for j in xrange( 0, cnt) :
@@ -450,17 +461,27 @@ class DocParser(object):
if handle_links: if handle_links:
link = self.link_id[num] link = self.link_id[num]
if (link > 0): if (link > 0):
linktype = self.link_type[link-1]
title = self.link_title[link-1] title = self.link_title[link-1]
if (title == "") or (parares.rfind(title) < 0): if (title == "") or (parares.rfind(title) < 0):
title='_link_' title=parares[lstart:]
if linktype == 'external' :
linkhref = self.link_href[link-1]
linkhtml = '<a href="%s">' % linkhref
else :
if len(self.link_page) >= link :
ptarget = self.link_page[link-1] - 1 ptarget = self.link_page[link-1] - 1
linkhtml = '<a href="#page%04d">' % ptarget linkhtml = '<a href="#page%04d">' % ptarget
else :
# just link to the current page
linkhtml = '<a href="#' + self.id + '">'
linkhtml += title + '</a>' linkhtml += title + '</a>'
pos = parares.rfind(title) pos = parares.rfind(title)
if pos >= 0: if pos >= 0:
parares = parares[0:pos] + linkhtml + parares[pos+len(title):] parares = parares[0:pos] + linkhtml + parares[pos+len(title):]
else : else :
parares += linkhtml parares += linkhtml
lstart = len(parares)
if word == '_link_' : word = '' if word == '_link_' : word = ''
elif (link < 0) : elif (link < 0) :
if word == '_link_' : word = '' if word == '_link_' : word = ''
@@ -532,6 +553,14 @@ class DocParser(object):
# collect link destination page numbers # collect link destination page numbers
self.link_page = self.getData('info.links.page',0,-1) self.link_page = self.getData('info.links.page',0,-1)
# collect link types (container versus external)
(pos, argres) = self.findinDoc('info.links.type',0,-1)
if argres : self.link_type = argres.split('|')
# collect link destinations
(pos, argres) = self.findinDoc('info.links.href',0,-1)
if argres : self.link_href = argres.split('|')
# collect link titles # collect link titles
(pos, argres) = self.findinDoc('info.links.title',0,-1) (pos, argres) = self.findinDoc('info.links.title',0,-1)
if argres : if argres :
@@ -641,16 +670,18 @@ class DocParser(object):
htmlpage += self.buildParagraph(pclass, pdesc, ptype, regtype) htmlpage += self.buildParagraph(pclass, pdesc, ptype, regtype)
elif (regtype == 'synth_fcvr.center') or (regtype == 'synth_text.center'): elif (regtype == 'synth_fcvr.center'):
(pos, simgsrc) = self.findinDoc('img.src',start,end) (pos, simgsrc) = self.findinDoc('img.src',start,end)
if simgsrc: if simgsrc:
htmlpage += '<div class="graphic"><img src="img/img%04d.jpg" alt="" /></div>' % int(simgsrc) htmlpage += '<div class="graphic"><img src="img/img%04d.jpg" alt="" /></div>' % int(simgsrc)
else : else :
print 'Warning: region type', regtype print ' Making region type', regtype,
(pos, temp) = self.findinDoc('paragraph',start,end) (pos, temp) = self.findinDoc('paragraph',start,end)
if pos != -1: (pos2, temp) = self.findinDoc('span',start,end)
print ' is a "text" region' if pos != -1 or pos2 != -1:
print ' a "text" region'
orig_regtype = regtype
regtype = 'fixed' regtype = 'fixed'
ptype = 'full' ptype = 'full'
# check to see if this is a continution from the previous page # check to see if this is a continution from the previous page
@@ -658,6 +689,11 @@ class DocParser(object):
ptype = 'end' ptype = 'end'
first_para_continued = False first_para_continued = False
(pclass, pdesc) = self.getParaDescription(start,end, regtype) (pclass, pdesc) = self.getParaDescription(start,end, regtype)
if not pclass:
if orig_regtype.endswith('.right') : pclass = 'cl-right'
elif orig_regtype.endswith('.center') : pclass = 'cl-center'
elif orig_regtype.endswith('.left') : pclass = 'cl-left'
elif orig_regtype.endswith('.justify') : pclass = 'cl-justify'
if pclass and (ptype == 'full') and (len(pclass) >= 6): if pclass and (ptype == 'full') and (len(pclass) >= 6):
tag = 'p' tag = 'p'
if pclass[3:6] == 'h1-' : tag = 'h4' if pclass[3:6] == 'h1-' : tag = 'h4'
@@ -669,7 +705,7 @@ class DocParser(object):
else : else :
htmlpage += self.buildParagraph(pclass, pdesc, ptype, regtype) htmlpage += self.buildParagraph(pclass, pdesc, ptype, regtype)
else : else :
print ' is 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:
htmlpage += '<div class="graphic"><img src="img/img%04d.jpg" alt="" /></div>' % int(simgsrc) htmlpage += '<div class="graphic"><img src="img/img%04d.jpg" alt="" /></div>' % int(simgsrc)

View File

@@ -1,6 +1,6 @@
#! /usr/bin/python #! /usr/bin/python
# 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.3 # For use with Topaz Scripts Version 2.6
class Unbuffered: class Unbuffered:
def __init__(self, stream): def __init__(self, stream):

View File

@@ -1,6 +1,6 @@
#! /usr/bin/python #! /usr/bin/python
# 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.3 # For use with Topaz Scripts Version 2.6
class Unbuffered: class Unbuffered:
def __init__(self, stream): def __init__(self, stream):
@@ -28,15 +28,24 @@ class GParser(object):
self.gh = self.getData('info.glyph.h') self.gh = self.getData('info.glyph.h')
self.gw = self.getData('info.glyph.w') self.gw = self.getData('info.glyph.w')
self.guse = self.getData('info.glyph.use') self.guse = self.getData('info.glyph.use')
if self.guse :
self.count = len(self.guse) self.count = len(self.guse)
else :
self.count = 0
self.gvtx = self.getData('info.glyph.vtx') self.gvtx = self.getData('info.glyph.vtx')
self.glen = self.getData('info.glyph.len') self.glen = self.getData('info.glyph.len')
self.gdpi = self.getData('info.glyph.dpi') self.gdpi = self.getData('info.glyph.dpi')
self.vx = self.getData('info.vtx.x') self.vx = self.getData('info.vtx.x')
self.vy = self.getData('info.vtx.y') self.vy = self.getData('info.vtx.y')
self.vlen = self.getData('info.len.n') self.vlen = self.getData('info.len.n')
if self.vlen :
self.glen.append(len(self.vlen)) self.glen.append(len(self.vlen))
elif self.glen:
self.glen.append(0)
if self.vx :
self.gvtx.append(len(self.vx)) self.gvtx.append(len(self.vx))
elif self.gvtx :
self.gvtx.append(0)
def getData(self, path): def getData(self, path):
result = None result = None

View File

@@ -1,6 +1,6 @@
#! /usr/bin/python #! /usr/bin/python
# 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.3 # For use with Topaz Scripts Version 2.6
class Unbuffered: class Unbuffered:
def __init__(self, stream): def __init__(self, stream):

View File

@@ -1,6 +1,6 @@
#! /usr/bin/python #! /usr/bin/python
# 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.3 # For use with Topaz Scripts Version 2.6
import csv import csv
import sys import sys

View File

@@ -1,6 +1,6 @@
#! /usr/bin/python #! /usr/bin/python
# 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.3 # For use with Topaz Scripts Version 2.6
import csv import csv
import sys import sys
@@ -85,7 +85,10 @@ class DocParser(object):
def process(self): def process(self):
classlst = '' classlst = ''
csspage = '' csspage = '.cl-center { text-align: center; margin-left: auto; margin-right: auto; }\n'
csspage += '.cl-right { text-align: right; }\n'
csspage += '.cl-left { text-align: left; }\n'
csspage += '.cl-justify { text-align: justify; }\n'
# generate a list of each <style> starting point in the stylesheet # generate a list of each <style> starting point in the stylesheet
styleList= self.posinDoc('book.stylesheet.style') styleList= self.posinDoc('book.stylesheet.style')
@@ -108,6 +111,7 @@ class DocParser(object):
# get the style class # get the style class
(pos, sclass) = self.findinDoc('style.class',start,end) (pos, sclass) = self.findinDoc('style.class',start,end)
if sclass != None: if sclass != None:
sclass = sclass.replace(' ','-')
sclass = '.cl-' + sclass.lower() sclass = '.cl-' + sclass.lower()
else : else :
sclass = '' sclass = ''
@@ -115,6 +119,7 @@ class DocParser(object):
# 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)
if aftclass != None: if aftclass != None:
aftclass = aftclass.replace(' ','-')
aftclass = '.cl-' + aftclass.lower() aftclass = '.cl-' + aftclass.lower()
else : else :
aftclass = '' aftclass = ''
@@ -216,6 +221,7 @@ class DocParser(object):
if ctype == 'h3_' : if ctype == 'h3_' :
csspage += 'h6' + cssline + '\n' csspage += 'h6' + cssline + '\n'
if cssline != ' { }':
csspage += self.stags[tag] + cssline + '\n' csspage += self.stags[tag] + cssline + '\n'

View File

@@ -1,4 +1,18 @@
Canges in 2.3 Changes in this Version
- bug fix to prevent problems with sample books
modified version of patch submitted by that-guy
Changes in 2.6
- fix for many additional version tags
- fixes to generate better links
- fixes to handle external links
- now handles new "marker" page .dat files
- improved special region handling
- properly handle class names with spaces
- handle default alignment for synthetic regions
Changes in 2.3
- fix for use with non-latin1 based systems (thank you Tedd) - fix for use with non-latin1 based systems (thank you Tedd)
- fixes for out of order tokens in xml - fixes for out of order tokens in xml

View File

@@ -0,0 +1,180 @@
#!/usr/bin/env python
# vim:ts=4:sw=4:softtabstop=4:smarttab:expandtab
import sys
sys.path.append('lib')
import os, os.path, urllib
import subprocess
from subprocess import Popen, PIPE, STDOUT
import Tkinter
import Tkconstants
import tkFileDialog
import tkMessageBox
import subasyncio
from subasyncio import Process
from scrolltextwidget import ScrolledText
class MainDialog(Tkinter.Frame):
def __init__(self, root):
Tkinter.Frame.__init__(self, root, border=5)
self.root = root
self.interval = 2000
self.p2 = None
self.status = Tkinter.Label(self, text='eReader eBook Conversion to PMLZ')
self.status.pack(fill=Tkconstants.X, expand=1)
body = Tkinter.Frame(self)
body.pack(fill=Tkconstants.X, expand=1)
sticky = Tkconstants.E + Tkconstants.W
body.grid_columnconfigure(1, weight=2)
Tkinter.Label(body, text='eBook PDB input file').grid(row=0, sticky=Tkconstants.E)
self.pdbpath = Tkinter.Entry(body, width=50)
self.pdbpath.grid(row=0, column=1, sticky=sticky)
cwd = os.getcwdu()
cwd = cwd.encode('utf-8')
self.pdbpath.insert(0, cwd)
button = Tkinter.Button(body, text="...", command=self.get_pdbpath)
button.grid(row=0, column=2)
Tkinter.Label(body, text='Name on CC').grid(row=1, sticky=Tkconstants.E)
self.name = Tkinter.StringVar()
self.nameinfo = Tkinter.Entry(body, width=40, textvariable=self.name)
self.nameinfo.grid(row=1, column=1, sticky=sticky)
Tkinter.Label(body, text='Last 8 digits of CC Number').grid(row=2, sticky=Tkconstants.E)
self.ccnum = Tkinter.StringVar()
self.ccinfo = Tkinter.Entry(body, width=10, textvariable=self.ccnum)
self.ccinfo.grid(row=2, column=1, sticky=sticky)
msg1 = 'Conversion Log \n\n'
self.stext = ScrolledText(body, bd=5, relief=Tkconstants.RIDGE, height=15, width=60, wrap=Tkconstants.WORD)
self.stext.grid(row=3, column=0, columnspan=2,sticky=sticky)
self.stext.insert(Tkconstants.END,msg1)
buttons = Tkinter.Frame(self)
buttons.pack()
self.sbotton = Tkinter.Button(
buttons, text="Start", width=10, command=self.convertit)
self.sbotton.pack(side=Tkconstants.LEFT)
Tkinter.Frame(buttons, width=10).pack(side=Tkconstants.LEFT)
self.qbutton = Tkinter.Button(
buttons, text="Quit", width=10, command=self.quitting)
self.qbutton.pack(side=Tkconstants.RIGHT)
# read from subprocess pipe without blocking
# invoked every interval via the widget "after"
# option being used, so need to reset it for the next time
def processPipe(self):
poll = self.p2.wait('nowait')
if poll != None:
text = self.p2.readerr()
text += self.p2.read()
msg = text + '\n\n' + 'File successfully converted\n'
if poll != 0:
msg = text + '\n\n' + 'Error: Conversion Failed\n'
self.showCmdOutput(msg)
self.p2 = None
self.sbotton.configure(state='normal')
return
text = self.p2.readerr()
text += self.p2.read()
self.showCmdOutput(text)
# make sure we get invoked again by event loop after interval
self.stext.after(self.interval,self.processPipe)
return
# post output from subprocess in scrolled text widget
def showCmdOutput(self, msg):
if msg and msg !='':
msg = msg.encode('utf-8')
self.stext.insert(Tkconstants.END,msg)
self.stext.yview_pickplace(Tkconstants.END)
return
# run erdr2pml.py as a subprocess via pipes and collect stdout
def erdr(self, infile, name, ccnum):
# os.putenv('PYTHONUNBUFFERED', '1')
cmdline = 'python ./lib/erdr2pml.py --make-pmlz "' + infile + '" "' + name + '" ' + ccnum
if sys.platform[0:3] == 'win':
search_path = os.environ['PATH']
search_path = search_path.lower()
if search_path.find('python') >= 0:
cmdline = 'python lib\erdr2pml.py --make-pmlz "' + infile + '" "' + name + '" ' + ccnum
else :
cmdline = 'lib\erdr2pml.py --make-pmlz "' + infile + '" "' + name + '" ' + ccnum
cmdline = cmdline.encode(sys.getfilesystemencoding())
p2 = Process(cmdline, shell=True, bufsize=1, stdin=None, stdout=PIPE, stderr=PIPE, close_fds=False)
return p2
def get_pdbpath(self):
pdbpath = tkFileDialog.askopenfilename(
parent=None, title='Select eReader PDB File',
defaultextension='.pdb', filetypes=[('eReader eBooks', '.pdb'),
('All Files', '.*')])
if pdbpath:
pdbpath = os.path.normpath(pdbpath)
self.pdbpath.delete(0, Tkconstants.END)
self.pdbpath.insert(0, pdbpath)
return
def quitting(self):
# kill any still running subprocess
if self.p2 != None:
if (self.p2.wait('nowait') == None):
self.p2.terminate()
self.root.destroy()
# actually ready to run the subprocess and get its output
def convertit(self):
# now disable the button to prevent multiple launches
self.sbotton.configure(state='disabled')
pdbpath = self.pdbpath.get()
if not pdbpath or not os.path.exists(pdbpath):
self.status['text'] = 'Specified eBook file does not exist'
self.sbotton.configure(state='normal')
return
name = self.name.get()
if not name or name == '':
self.status['text'] = 'Your forgot to enter the Name on the CC'
self.sbotton.configure(state='normal')
return
ccnum = self.ccnum.get()
if not ccnum or ccnum == '':
self.status['text'] = 'Your forgot to enter the last 8 digits on the CC'
self.sbotton.configure(state='normal')
return
log = 'Command = "python erdr2pml.py --make-pmlz "\n'
log += 'PDB Path = "'+ pdbpath + '"\n'
log += 'Name = "' + name + '"\n'
log += 'Last 8 of CC = "' + ccnum + '"\n'
log += '\n\n'
log += 'Please Wait ...\n'
log = log.encode('utf-8')
self.stext.insert(Tkconstants.END,log)
self.p2 = self.erdr(pdbpath, name, ccnum)
# python does not seem to allow you to create
# your own eventloop which every other gui does - strange
# so need to use the widget "after" command to force
# event loop to run non-gui events every interval
self.stext.after(self.interval,self.processPipe)
return
def main(argv=None):
root = Tkinter.Tk()
root.title('eReader PDB to PMLZ Conversion')
root.resizable(True, False)
root.minsize(300, 0)
MainDialog(root).pack(fill=Tkconstants.X, expand=1)
root.mainloop()
return 0
if __name__ == "__main__":
sys.exit(main())

View File

@@ -52,8 +52,10 @@
# 0.11 - fixups for using correct xml for footnotes and sidebars for use with Dropbook # 0.11 - fixups for using correct xml for footnotes and sidebars for use with Dropbook
# 0.12 - Fix added to prevent lowercasing of image names when the pml code itself uses a different case in the link name. # 0.12 - Fix added to prevent lowercasing of image names when the pml code itself uses a different case in the link name.
# 0.13 - change to unbuffered stdout for use with gui front ends # 0.13 - change to unbuffered stdout for use with gui front ends
# 0.14 - contributed enhancement to support --make-pmlz switch
# 0.15 - enabled high-ascii to pml character encoding. DropBook now works on Mac.
__version__='0.13' __version__='0.15'
# Import Psyco if available # Import Psyco if available
try: try:
@@ -85,7 +87,7 @@ class Unbuffered:
import sys import sys
sys.stdout=Unbuffered(sys.stdout) sys.stdout=Unbuffered(sys.stdout)
import struct, binascii, zlib, os, os.path, urllib import struct, binascii, getopt, zlib, os, os.path, urllib, tempfile
try: try:
from hashlib import sha1 from hashlib import sha1
@@ -464,17 +466,6 @@ class EreaderProcessor(object):
data = sect[62:] data = sect[62:]
return sanitizeFileName(name), data return sanitizeFileName(name), data
def cleanPML(self,pml):
# Update old \b font tag with correct \B bold font tag
pml2 = pml.replace('\\b', '\\B')
# Convert special characters to proper PML code. High ASCII start at (\x82, \a130) and go up to (\xff, \a255)
for k in xrange(130,256):
# a2b_hex takes in a hexidecimal as a string and converts it
# to a binary ascii code that we search and replace for
badChar=binascii.a2b_hex('%02x' % k)
pml2 = pml2.replace(badChar, '\\a%03d' % k)
#end for k
return pml2
# def getChapterNamePMLOffsetData(self): # def getChapterNamePMLOffsetData(self):
# cv = '' # cv = ''
@@ -563,6 +554,14 @@ class EreaderProcessor(object):
return r return r
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):
badChar = chr(k)
pml2 = pml2.replace(badChar, '\\a%03d' % k)
return pml2
def convertEreaderToPml(infile, name, cc, outdir): def convertEreaderToPml(infile, name, cc, outdir):
if not os.path.exists(outdir): if not os.path.exists(outdir):
os.makedirs(outdir) os.makedirs(outdir)
@@ -584,7 +583,7 @@ def convertEreaderToPml(infile, name, cc, outdir):
print " Extracting pml" print " Extracting pml"
pml_string = er.getText() pml_string = er.getText()
pmlfilename = bookname + ".pml" pmlfilename = bookname + ".pml"
file(os.path.join(outdir, pmlfilename),'wb').write(pml_string) file(os.path.join(outdir, pmlfilename),'wb').write(cleanPML(pml_string))
# bkinfo = er.getBookInfo() # bkinfo = er.getBookInfo()
# if bkinfo != '': # if bkinfo != '':
@@ -592,27 +591,54 @@ def convertEreaderToPml(infile, name, cc, outdir):
# file(os.path.join(outdir, 'bookinfo.txt'),'wb').write(bkinfo) # file(os.path.join(outdir, 'bookinfo.txt'),'wb').write(bkinfo)
def main(argv=None): def usage():
global bookname
if argv is None:
argv = sys.argv
print "eRdr2Pml v%s. Copyright (c) 2009 The Dark Reverser" % __version__
if len(argv)!=4 and len(argv)!=5:
print "Converts DRMed eReader books to PML Source" print "Converts DRMed eReader books to PML Source"
print "Usage:" print "Usage:"
print " erdr2pml infile.pdb [outdir] \"your name\" credit_card_number " 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 "Note:"
print " if ommitted, outdir defaults based on 'infile.pdb'" 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 " It's enough to enter the last 8 digits of the credit card number"
return
def main(argv=None):
global bookname
try:
opts, args = getopt.getopt(sys.argv[1:], "h", ["make-pmlz"])
except getopt.GetoptError, err:
print str(err)
usage()
return 1
make_pmlz = False
zipname = None
for o, a in opts:
if o == "-h":
usage()
return 0
elif o == "--make-pmlz":
make_pmlz = True
zipname = ''
print "eRdr2Pml v%s. Copyright (c) 2009 The Dark Reverser" % __version__
if len(args)!=3 and len(args)!=4:
usage()
return 1 return 1
else: else:
if len(argv)==4: if len(args)==3:
infile, name, cc = argv[1], argv[2], argv[3] infile, name, cc = args[0], args[1], args[2]
outdir = infile[:-4] + '_Source' outdir = infile[:-4] + '_Source'
elif len(argv)==5: elif len(args)==4:
infile, outdir, name, cc = argv[1], argv[2], argv[3], argv[4] infile, outdir, name, cc = args[0], args[1], args[2], args[3]
if make_pmlz :
# ignore specified outdir, use tempdir instead
outdir = tempfile.mkdtemp()
bookname = os.path.splitext(os.path.basename(infile))[0] bookname = os.path.splitext(os.path.basename(infile))[0]
try: try:
@@ -620,9 +646,37 @@ def main(argv=None):
import time import time
start_time = time.time() start_time = time.time()
convertEreaderToPml(infile, name, cc, outdir) convertEreaderToPml(infile, name, cc, outdir)
if make_pmlz :
import zipfile
import shutil
print " Creating PMLZ file"
zipname = infile[:-4] + '.pmlz'
myZipFile = zipfile.ZipFile(zipname,'w',zipfile.ZIP_STORED, False)
list = os.listdir(outdir)
for file in list:
localname = file
filePath = os.path.join(outdir,file)
if os.path.isfile(filePath):
myZipFile.write(filePath, localname)
elif os.path.isdir(filePath):
imageList = os.listdir(filePath)
localimgdir = os.path.basename(filePath)
for image in imageList:
localname = os.path.join(localimgdir,image)
imagePath = os.path.join(filePath,image)
if os.path.isfile(imagePath):
myZipFile.write(imagePath, localname)
myZipFile.close()
# remove temporary directory
shutil.rmtree(outdir)
end_time = time.time() end_time = time.time()
search_time = end_time - start_time search_time = end_time - start_time
print 'elapsed time: %.2f seconds' % (search_time, ) print 'elapsed time: %.2f seconds' % (search_time, )
if make_pmlz :
print 'output is %s' % zipname
else :
print 'output in %s' % outdir print 'output in %s' % outdir
print "done" print "done"
except ValueError, e: except ValueError, e:

View File

@@ -30,8 +30,10 @@
# 0.17 - add support for tidy.exe under windows # 0.17 - add support for tidy.exe under windows
# 0.18 - fix corner case of lines that start with \axxx or \Uxxxx tags # 0.18 - fix corner case of lines that start with \axxx or \Uxxxx tags
# 0.19 - change to use auto flushed stdout, and use proper return values # 0.19 - change to use auto flushed stdout, and use proper return values
# 0.20 - properly handle T markup inside links
# 0.21 - properly handle new sigil Chapter Breaks for 0.2X series and up
__version__='0.19' __version__='0.21'
class Unbuffered: class Unbuffered:
def __init__(self, stream): def __init__(self, stream):
@@ -624,7 +626,7 @@ class PmlConverter(object):
if sigil_breaks: if sigil_breaks:
if (len(final) - lastbreaksize) > 3000: if (len(final) - lastbreaksize) > 3000:
final += '<div>\n <hr class="sigilChapterBreak" />\n</div>\n' final += '<hr class="sigilChapterBreak" />\n'
lastbreaksize = len(final) lastbreaksize = len(final)
# now create new start tags for all tags that # now create new start tags for all tags that
@@ -699,7 +701,7 @@ class PmlConverter(object):
self.skipNewLine() self.skipNewLine()
elif cmd == 'T': elif cmd == 'T':
if inBlock(): if inBlock() or inLink() or inComment():
final += '<span style="margin-left: %s;">&nbsp;</span>' % attr final += '<span style="margin-left: %s;">&nbsp;</span>' % attr
else: else:
final += '<p style="text-indent: %s;">' % attr final += '<p style="text-indent: %s;">' % attr