diff --git a/Kindle_Mobi_Tools/lib/mobidedrm.py b/Kindle_Mobi_Tools/lib/mobidedrm.py index 4ec5b75..178b046 100644 --- a/Kindle_Mobi_Tools/lib/mobidedrm.py +++ b/Kindle_Mobi_Tools/lib/mobidedrm.py @@ -21,7 +21,10 @@ # 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 - use autoflushed stdout and proper return values +# 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 class Unbuffered: def __init__(self, stream): @@ -143,6 +146,17 @@ class DrmStripper: 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 @@ -178,34 +192,35 @@ class DrmStripper: crypto_type, = struct.unpack('>H', sect[0xC:0xC+2]) if crypto_type == 0: - raise DrmException("it seems that this book isn't encrypted") - 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 "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 @@ -219,7 +234,7 @@ if not __name__ == "__main__": 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, 0, 10) # The version number of this plugin + version = (0, 1, 0) # 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 @@ -245,25 +260,22 @@ if not __name__ == "__main__": def customization_help(self, gui=False): return 'Enter PID (separate multiple PIDs with comma)' -def main(argv=sys.argv): - print "MobiDeDrm v0.10. Copyright (c) 2008 The Dark Reverser" +if __name__ == "__main__": + print "MobiDeDrm v0.11. Copyright (c) 2008 The Dark Reverser" if len(sys.argv)<4: print "Removes protection from Mobipocket books" print "Usage:" - print " mobidedrm infile.mobi outfile.mobi PID" - return 1 + print " mobidedrm infile.mobi outfile.mobi (PID)" + sys.exit(1) else: infile = sys.argv[1] outfile = sys.argv[2] pid = sys.argv[3] data_file = file(infile, 'rb').read() try: - file(outfile, 'wb').write(DrmStripper(data_file, pid).getResult()) + strippedFile = DrmStripper(data_file, pid) + file(outfile, 'wb').write(strippedFile.getResult()) except DrmException, e: print "Error: %s" % e - return 1 - return 0 - -if __name__ == "__main__": - sys.exit(main()) - + sys.exit(1) + sys.exit(0) diff --git a/Topaz_Tools/TopazExtract_Kindle4PC.pyw b/Topaz_Tools/TopazExtract_Kindle4PC.pyw new file mode 100644 index 0000000..c18fc3f --- /dev/null +++ b/Topaz_Tools/TopazExtract_Kindle4PC.pyw @@ -0,0 +1,193 @@ +#!/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='Extract Contents of Topaz eBook to a Directory') + 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='Topaz eBook input file').grid(row=0, sticky=Tkconstants.E) + self.tpzpath = Tkinter.Entry(body, width=50) + self.tpzpath.grid(row=0, column=1, sticky=sticky) + self.tpzpath.insert(0, os.getcwd()) + button = Tkinter.Button(body, text="...", command=self.get_tpzpath) + button.grid(row=0, column=2) + + Tkinter.Label(body, text='Output Directory').grid(row=1, sticky=Tkconstants.E) + self.outpath = Tkinter.Entry(body, width=50) + self.outpath.grid(row=1, column=1, sticky=sticky) + self.outpath.insert(0, os.getcwd()) + button = Tkinter.Button(body, text="...", command=self.get_outpath) + button.grid(row=1, column=2) + + Tkinter.Label(body, text='First 8 char of PID (optional)').grid(row=3, sticky=Tkconstants.E) + self.pidnum = Tkinter.StringVar() + self.ccinfo = Tkinter.Entry(body, width=10, textvariable=self.pidnum) + self.ccinfo.grid(row=3, 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=4, 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' + 'Files successfully extracted\n' + if poll != 0: + msg = text + '\n\n' + 'Error: File Extraction 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 !='': + self.stext.insert(Tkconstants.END,msg) + self.stext.yview_pickplace(Tkconstants.END) + return + + # run as a subprocess via pipes and collect stdout + def topazrdr(self, infile, outdir, pidnum): + # os.putenv('PYTHONUNBUFFERED', '1') + pidoption = '' + if pidnum and pidnum != '': + pidoption = ' -p "' + pidnum + '" ' + outoption = ' -o "' + outdir + '" ' + cmdline = 'python ./lib/cmbtc_dump.py -v -d ' + pidoption + outoption + '"' + infile + '"' + 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\cmbtc_dump.py -v -d ' + pidoption + outoption + '"' + infile + '"' + else : + cmdline = 'lib\cmbtc_dump.py -v -d ' + pidoption + outoption + '"' + infile + '"' + + p2 = Process(cmdline, shell=True, bufsize=1, stdin=None, stdout=PIPE, stderr=PIPE, close_fds=False) + return p2 + + + def get_tpzpath(self): + tpzpath = tkFileDialog.askopenfilename( + parent=None, title='Select Topaz File', + defaultextension='.prc', filetypes=[('Topaz azw1', '.azw1'), ('Topaz prc', '.prc'), + ('All Files', '.*')]) + if tpzpath: + tpzpath = os.path.normpath(tpzpath) + self.tpzpath.delete(0, Tkconstants.END) + self.tpzpath.insert(0, tpzpath) + return + + def get_outpath(self): + outpath = tkFileDialog.askdirectory( + parent=None, title='Directory to Extract Files into', + initialdir=os.getcwd(), initialfile=None) + if outpath: + outpath = os.path.normpath(outpath) + self.outpath.delete(0, Tkconstants.END) + self.outpath.insert(0, outpath) + 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') + tpzpath = self.tpzpath.get() + outpath = self.outpath.get() + if not tpzpath or not os.path.exists(tpzpath): + self.status['text'] = 'Specified Topaz eBook file does not exist' + self.sbotton.configure(state='normal') + return + if not outpath: + self.status['text'] = 'No output directory specified' + self.sbotton.configure(state='normal') + return + if not os.path.exists(outpath): + os.makedirs(outpath) + pidnum = self.pidnum.get() + # if not pidnum or pidnum == '': + # self.status['text'] = 'You have not entered a PID ' + # self.sbotton.configure(state='normal') + # return + + log = 'Command = "python cmbtc_dump.py"\n' + log += 'Topaz Path Path = "'+ tpzpath + '"\n' + log += 'Output Directory = "' + outpath + '"\n' + log += 'First 8 chars of PID = "' + pidnum + '"\n' + log += '\n\n' + log += 'Please Wait ...\n' + self.stext.insert(Tkconstants.END,log) + self.p2 = self.topazrdr(tpzpath, outpath, pidnum) + + # 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('Topaz eBook File Extraction') + 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()) diff --git a/Topaz_Tools/TopazExtract_Kindle_iPhone.pyw b/Topaz_Tools/TopazExtract_Kindle_iPhone.pyw new file mode 100644 index 0000000..30133ec --- /dev/null +++ b/Topaz_Tools/TopazExtract_Kindle_iPhone.pyw @@ -0,0 +1,191 @@ +#!/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='Extract Contents of Topaz eBook to a Directory') + 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='Topaz eBook input file').grid(row=0, sticky=Tkconstants.E) + self.tpzpath = Tkinter.Entry(body, width=50) + self.tpzpath.grid(row=0, column=1, sticky=sticky) + self.tpzpath.insert(0, os.getcwd()) + button = Tkinter.Button(body, text="...", command=self.get_tpzpath) + button.grid(row=0, column=2) + + Tkinter.Label(body, text='Output Directory').grid(row=1, sticky=Tkconstants.E) + self.outpath = Tkinter.Entry(body, width=50) + self.outpath.grid(row=1, column=1, sticky=sticky) + self.outpath.insert(0, os.getcwd()) + button = Tkinter.Button(body, text="...", command=self.get_outpath) + button.grid(row=1, column=2) + + Tkinter.Label(body, text='First 8 characters of PID').grid(row=3, sticky=Tkconstants.E) + self.pidnum = Tkinter.StringVar() + self.ccinfo = Tkinter.Entry(body, width=10, textvariable=self.pidnum) + self.ccinfo.grid(row=3, 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=4, 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' + 'Files successfully extracted\n' + if poll != 0: + msg = text + '\n\n' + 'Error: File Extraction 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 !='': + self.stext.insert(Tkconstants.END,msg) + self.stext.yview_pickplace(Tkconstants.END) + return + + # run as a subprocess via pipes and collect stdout + def topazrdr(self, infile, outdir, pidnum): + # os.putenv('PYTHONUNBUFFERED', '1') + pidoption = ' -p "' + pidnum + '" ' + outoption = ' -o "' + outdir + '" ' + cmdline = 'python ./lib/cmbtc_dump_nonK4PC.py -v -d ' + pidoption + outoption + '"' + infile + '"' + 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\cmbtc_dump_nonK4PC.py -v -d ' + pidoption + outoption + '"' + infile + '"' + else : + cmdline = 'lib\cmbtc_dump_nonK4PC.py -v -d ' + pidoption + outoption + '"' + infile + '"' + + p2 = Process(cmdline, shell=True, bufsize=1, stdin=None, stdout=PIPE, stderr=PIPE, close_fds=False) + return p2 + + + def get_tpzpath(self): + tpzpath = tkFileDialog.askopenfilename( + parent=None, title='Select Topaz File', + defaultextension='.prc', filetypes=[('Topaz azw1', '.azw1'), ('Topaz prc', '.prc'), + ('All Files', '.*')]) + if tpzpath: + tpzpath = os.path.normpath(tpzpath) + self.tpzpath.delete(0, Tkconstants.END) + self.tpzpath.insert(0, tpzpath) + return + + def get_outpath(self): + outpath = tkFileDialog.askdirectory( + parent=None, title='Directory to Extract Files into', + initialdir=os.getcwd(), initialfile=None) + if outpath: + outpath = os.path.normpath(outpath) + self.outpath.delete(0, Tkconstants.END) + self.outpath.insert(0, outpath) + 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') + tpzpath = self.tpzpath.get() + outpath = self.outpath.get() + if not tpzpath or not os.path.exists(tpzpath): + self.status['text'] = 'Specified Topaz eBook file does not exist' + self.sbotton.configure(state='normal') + return + if not outpath: + self.status['text'] = 'No output directory specified' + self.sbotton.configure(state='normal') + return + if not os.path.exists(outpath): + os.makedirs(outpath) + pidnum = self.pidnum.get() + if not pidnum or pidnum == '': + self.status['text'] = 'You have not entered a PID ' + self.sbotton.configure(state='normal') + return + + log = 'Command = "python cmbtc_dump_nonK4PC.py"\n' + log += 'Topaz Path Path = "'+ tpzpath + '"\n' + log += 'Output Directory = "' + outpath + '"\n' + log += 'First 8 chars of PID = "' + pidnum + '"\n' + log += '\n\n' + log += 'Please Wait ...\n' + self.stext.insert(Tkconstants.END,log) + self.p2 = self.topazrdr(tpzpath, outpath, pidnum) + + # 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('Topaz eBook File Extraction') + 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()) diff --git a/Topaz_Tools/TopazFiles2HTML.pyw b/Topaz_Tools/TopazFiles2HTML.pyw new file mode 100644 index 0000000..1e094a5 --- /dev/null +++ b/Topaz_Tools/TopazFiles2HTML.pyw @@ -0,0 +1,152 @@ +#!/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='Convert Files From Topaz eBook to HTML') + 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='Directory you Extracted Topaz Files into').grid(row=0, sticky=Tkconstants.E) + self.bookdir = Tkinter.Entry(body, width=50) + self.bookdir.grid(row=0, column=1, sticky=sticky) + self.bookdir.insert(0, os.getcwd()) + button = Tkinter.Button(body, text="...", command=self.get_bookdir) + button.grid(row=0, column=2) + + 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=4, 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' + 'book.html successfully created in ' + self.bookdir.get() + '\n' + if poll != 0: + msg = text + '\n\n' + 'Error: HTML 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 !='': + self.stext.insert(Tkconstants.END,msg) + self.stext.yview_pickplace(Tkconstants.END) + return + + # run as a subprocess via pipes and collect stdout + def topazrdr(self, bookdir): + # os.putenv('PYTHONUNBUFFERED', '1') + cmdline = 'python ./lib/genhtml.py "' + bookdir + '"' + 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\genhtml.py "' + bookdir + '"' + else : + cmdline = 'lib\genhtml.py "' + bookdir + '"' + + p2 = Process(cmdline, shell=True, bufsize=1, stdin=None, stdout=PIPE, stderr=PIPE, close_fds=False) + return p2 + + + def get_bookdir(self): + bookdir = tkFileDialog.askdirectory( + parent=None, title='Select the Directory you Extracted Topaz Files into', + initialdir=os.getcwd(), initialfile=None) + if bookdir: + bookdir = os.path.normpath(bookdir) + self.bookdir.delete(0, Tkconstants.END) + self.bookdir.insert(0, bookdir) + 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') + bookdir = self.bookdir.get() + if not bookdir: + self.status['text'] = 'No directory specified' + self.sbotton.configure(state='normal') + return + + log = 'Command = "python genhtml.py"\n' + log += 'Book Directory = "' + bookdir + '"\n' + log += '\n\n' + log += 'Please Wait ...\n' + self.stext.insert(Tkconstants.END,log) + self.p2 = self.topazrdr(bookdir) + + # 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('Convert Topaz Files to SVG Files') + 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()) diff --git a/Topaz_Tools/TopazFiles2SVG.pyw b/Topaz_Tools/TopazFiles2SVG.pyw new file mode 100644 index 0000000..a93acc2 --- /dev/null +++ b/Topaz_Tools/TopazFiles2SVG.pyw @@ -0,0 +1,152 @@ +#!/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='Convert Files From Topaz eBook to SVG') + 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='Directory you Extracted Topaz Files into').grid(row=0, sticky=Tkconstants.E) + self.bookdir = Tkinter.Entry(body, width=50) + self.bookdir.grid(row=0, column=1, sticky=sticky) + self.bookdir.insert(0, os.getcwd()) + button = Tkinter.Button(body, text="...", command=self.get_bookdir) + button.grid(row=0, column=2) + + 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=4, 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' + 'SVG embedded in XHTML files successfully created in the svg directory in ' + self.bookdir.get() + '\n' + if poll != 0: + msg = text + '\n\n' + 'Error: SVG 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 !='': + self.stext.insert(Tkconstants.END,msg) + self.stext.yview_pickplace(Tkconstants.END) + return + + # run as a subprocess via pipes and collect stdout + def topazrdr(self, bookdir): + # os.putenv('PYTHONUNBUFFERED', '1') + cmdline = 'python ./lib/gensvg.py "' + bookdir + '"' + 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\gensvg.py "' + bookdir + '"' + else : + cmdline = 'lib\gensvg.py "' + bookdir + '"' + + p2 = Process(cmdline, shell=True, bufsize=1, stdin=None, stdout=PIPE, stderr=PIPE, close_fds=False) + return p2 + + + def get_bookdir(self): + bookdir = tkFileDialog.askdirectory( + parent=None, title='Select the Directory you Extracted Topaz Files into', + initialdir=os.getcwd(), initialfile=None) + if bookdir: + bookdir = os.path.normpath(bookdir) + self.bookdir.delete(0, Tkconstants.END) + self.bookdir.insert(0, bookdir) + 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') + bookdir = self.bookdir.get() + if not bookdir: + self.status['text'] = 'No directory specified' + self.sbotton.configure(state='normal') + return + + log = 'Command = "python gensvg.py"\n' + log += 'Book Directory = "' + bookdir + '"\n' + log += '\n\n' + log += 'Please Wait ...\n' + self.stext.insert(Tkconstants.END,log) + self.p2 = self.topazrdr(bookdir) + + # 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('Convert Topaz Files to SVG Files') + 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()) diff --git a/Topaz_Tools/TopazFiles2XML.pyw b/Topaz_Tools/TopazFiles2XML.pyw new file mode 100644 index 0000000..1d2bd7c --- /dev/null +++ b/Topaz_Tools/TopazFiles2XML.pyw @@ -0,0 +1,152 @@ +#!/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='Convert Files From Topaz eBook to XML') + 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='Directory you Extracted Topaz Files into').grid(row=0, sticky=Tkconstants.E) + self.bookdir = Tkinter.Entry(body, width=50) + self.bookdir.grid(row=0, column=1, sticky=sticky) + self.bookdir.insert(0, os.getcwd()) + button = Tkinter.Button(body, text="...", command=self.get_bookdir) + button.grid(row=0, column=2) + + 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=4, 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' + 'XML files successfully created in the xml directory in ' + self.bookdir.get() + '\n' + if poll != 0: + msg = text + '\n\n' + 'Error: XML 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 !='': + self.stext.insert(Tkconstants.END,msg) + self.stext.yview_pickplace(Tkconstants.END) + return + + # run as a subprocess via pipes and collect stdout + def topazrdr(self, bookdir): + # os.putenv('PYTHONUNBUFFERED', '1') + cmdline = 'python ./lib/genxml.py "' + bookdir + '"' + 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\genxml.py "' + bookdir + '"' + else : + cmdline = 'lib\genxml.py "' + bookdir + '"' + + p2 = Process(cmdline, shell=True, bufsize=1, stdin=None, stdout=PIPE, stderr=PIPE, close_fds=False) + return p2 + + + def get_bookdir(self): + bookdir = tkFileDialog.askdirectory( + parent=None, title='Select the Directory you Extracted Topaz Files into', + initialdir=os.getcwd(), initialfile=None) + if bookdir: + bookdir = os.path.normpath(bookdir) + self.bookdir.delete(0, Tkconstants.END) + self.bookdir.insert(0, bookdir) + 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') + bookdir = self.bookdir.get() + if not bookdir: + self.status['text'] = 'No directory specified' + self.sbotton.configure(state='normal') + return + + log = 'Command = "python genxml.py"\n' + log += 'Book Directory = "' + bookdir + '"\n' + log += '\n\n' + log += 'Please Wait ...\n' + self.stext.insert(Tkconstants.END,log) + self.p2 = self.topazrdr(bookdir) + + # 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('Convert Topaz Files to XML Files') + 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()) diff --git a/Topaz_Tools/lib/cmbtc_dump.py b/Topaz_Tools/lib/cmbtc_dump.py index de0ecf7..c7b08f8 100644 --- a/Topaz_Tools/lib/cmbtc_dump.py +++ b/Topaz_Tools/lib/cmbtc_dump.py @@ -1,5 +1,5 @@ #! /usr/bin/python -# For use in Topaz Scripts version 2.0 +# For use in Topaz Scripts version 2.2 """ @@ -13,11 +13,22 @@ y2/pHuYme7U1TsgSjwIDAQAB -----END PUBLIC KEY----- """ - from __future__ import with_statement -import csv +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 +sys.stdout=Unbuffered(sys.stdout) + + +import csv import os import getopt import zlib @@ -305,7 +316,10 @@ def encodeNumber(number): byte += flag result += chr(byte) flag = 0x80 - if number == 0 : break + if number == 0 : + if (byte == 0xFF and negative == False) : + result += chr(0x80) + break if negative: result += chr(0xFF) @@ -841,13 +855,12 @@ def main(argv=sys.argv): if len(bookKeys) == 0 : if verbose > 0 : print ("Book key could not be found. Maybe this book is not registered with this device.") + return 1 else : bookKey = bookKeys[0] if verbose > 0: print("Book key: " + bookKey.encode('hex')) - - if command == "printRecord" : extractBookPayloadRecord(recordName,int(recordIndex),outputFile) if outputFile != "" and verbose>0 : @@ -859,6 +872,7 @@ def main(argv=sys.argv): print ("Decrypted book saved. Don't pirate!") elif verbose > 0: print("Output directory name was not supplied.") + return 1 return 0 diff --git a/Topaz_Tools/lib/cmbtc_dump_nonK4PC.py b/Topaz_Tools/lib/cmbtc_dump_nonK4PC.py index 5e43ae6..253079f 100644 --- a/Topaz_Tools/lib/cmbtc_dump_nonK4PC.py +++ b/Topaz_Tools/lib/cmbtc_dump_nonK4PC.py @@ -1,10 +1,19 @@ -#! /usr/bin/python -# For use with Topaz Scripts Version 2.0 +#!/usr/bin/python +# For use with Topaz Scripts Version 2.2 -from __future__ import with_statement +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 +sys.stdout=Unbuffered(sys.stdout) import csv -import sys import os import getopt import zlib @@ -90,7 +99,10 @@ def encodeNumber(number): byte += flag result += chr(byte) flag = 0x80 - if number == 0 : break + if number == 0 : + if (byte == 0xFF and negative == False) : + result += chr(0x80) + break if negative: result += chr(0xFF) @@ -480,12 +492,11 @@ def main(argv=sys.argv): if len(bookKeys) == 0 : if verbose > 0 : print ("Book key could not be found. Maybe this book is not registered with this device.") + return 1 else : bookKey = bookKeys[0] if verbose > 0: print("Book key: " + bookKey.encode('hex')) - - if command == "printRecord" : extractBookPayloadRecord(recordName,int(recordIndex),outputFile) @@ -498,6 +509,7 @@ def main(argv=sys.argv): print ("Decrypted book saved. Don't pirate!") elif verbose > 0: print("Output directory name was not supplied.") + return 1 return 0 diff --git a/Topaz_Tools/lib/convert2xml.py b/Topaz_Tools/lib/convert2xml.py index 4e84184..caf5039 100644 --- a/Topaz_Tools/lib/convert2xml.py +++ b/Topaz_Tools/lib/convert2xml.py @@ -1,10 +1,20 @@ #! /usr/bin/python # vim:ts=4:sw=4:softtabstop=4:smarttab:expandtab -# For use with Topaz Scripts Version 2.0 +# For use with Topaz Scripts Version 2.2 + +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) -from __future__ import with_statement -import csv import sys +sys.stdout=Unbuffered(sys.stdout) + +import csv import os import getopt from struct import pack @@ -61,7 +71,10 @@ def encodeNumber(number): byte += flag result += chr(byte) flag = 0x80 - if number == 0 : break + if number == 0 : + if (byte == 0xFF and negative == False) : + result += chr(0x80) + break if negative: result += chr(0xFF) @@ -729,8 +742,6 @@ def main(argv): if len(argv) == 0: printOutput = True argv = sys.argv - else : - argv = argv.split() try: opts, args = getopt.getopt(argv[1:], "hd", ["flat-xml"]) diff --git a/Topaz_Tools/lib/decode_meta.py b/Topaz_Tools/lib/decode_meta.py index 9f58a53..01d8cc9 100644 --- a/Topaz_Tools/lib/decode_meta.py +++ b/Topaz_Tools/lib/decode_meta.py @@ -1,8 +1,7 @@ #! /usr/bin/python # vim:ts=4:sw=4:softtabstop=4:smarttab:expandtab -# For use with Topaz Scripts Version 2.0 +# For use with Topaz Scripts Version 2.2 -from __future__ import with_statement import csv import sys import os @@ -61,8 +60,11 @@ def encodeNumber(number): byte += flag result += chr(byte) flag = 0x80 - if number == 0 : break - + if number == 0 : + if (byte == 0xFF and negative == False) : + result += chr(0x80) + break + if negative: result += chr(0xFF) diff --git a/Topaz_Tools/lib/flatxml2html.py b/Topaz_Tools/lib/flatxml2html.py index eaeeabe..9e3080b 100644 --- a/Topaz_Tools/lib/flatxml2html.py +++ b/Topaz_Tools/lib/flatxml2html.py @@ -1,10 +1,9 @@ #! /usr/bin/python # vim:ts=4:sw=4:softtabstop=4:smarttab:expandtab -# For use with Topaz Scripts Version 2.0 +# For use with Topaz Scripts Version 2.2 -from __future__ import with_statement -import csv import sys +import csv import os import math import getopt diff --git a/Topaz_Tools/lib/genhtml.py b/Topaz_Tools/lib/genhtml.py index 5fcdd38..8df7b52 100644 --- a/Topaz_Tools/lib/genhtml.py +++ b/Topaz_Tools/lib/genhtml.py @@ -1,8 +1,21 @@ #! /usr/bin/python # vim:ts=4:sw=4:softtabstop=4:smarttab:expandtab -# For use with Topaz Scripts Version 2.0 +# For use with Topaz Scripts Version 2.2 -import os, sys, getopt +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 +sys.stdout=Unbuffered(sys.stdout) + + +import os, getopt # local routines import convert2xml @@ -27,8 +40,6 @@ def main(argv): if len(argv) == 0: argv = sys.argv - else : - argv = argv.split() try: opts, args = getopt.getopt(argv[1:], "h:",["fixed-image"]) @@ -36,11 +47,11 @@ def main(argv): except getopt.GetoptError, err: print str(err) usage() - sys.exit(2) + sys.exit(1) if len(opts) == 0 and len(args) == 0 : usage() - sys.exit(2) + sys.exit(1) for o, a in opts: if o =="-h": @@ -53,39 +64,39 @@ def main(argv): if not os.path.exists(bookDir) : print "Can not find directory with unencrypted book" - sys.exit(-1) + sys.exit(1) dictFile = os.path.join(bookDir,'dict0000.dat') if not os.path.exists(dictFile) : print "Can not find dict0000.dat file" - sys.exit(-1) + sys.exit(1) pageDir = os.path.join(bookDir,'page') if not os.path.exists(pageDir) : print "Can not find page directory in unencrypted book" - sys.exit(-1) + sys.exit(1) imgDir = os.path.join(bookDir,'img') if not os.path.exists(imgDir) : print "Can not find image directory in unencrypted book" - sys.exit(-1) + sys.exit(1) svgDir = os.path.join(bookDir,'svg') if not os.path.exists(svgDir) : print "Can not find svg directory in unencrypted book" print "please run gensvg.py before running genhtml.py" - sys.exit(-1) + sys.exit(1) otherFile = os.path.join(bookDir,'other0000.dat') if not os.path.exists(otherFile) : print "Can not find other0000.dat in unencrypted book" - sys.exit(-1) + sys.exit(1) metaFile = os.path.join(bookDir,'metadata0000.dat') if not os.path.exists(metaFile) : print "Can not find metadata0000.dat in unencrypted book" - sys.exit(-1) + sys.exit(1) htmlFileName = "book.html" htmlstr = '\n' @@ -123,18 +134,28 @@ def main(argv): pnum = int(spage) # get page height and width from first text page for use in stylesheet scaling - pname = 'page%04d.dat' % pnum + pname = 'page%04d.dat' % (pnum + 1) fname = os.path.join(pageDir,pname) - flat_xml = convert2xml.main('convert2xml.py --flat-xml ' + dictFile + ' ' + fname) + pargv=[] + pargv.append('convert2xml.py') + pargv.append('--flat-xml') + pargv.append(dictFile) + pargv.append(fname) + flat_xml = convert2xml.main(pargv) (ph, pw) = getpagedim.getPageDim(flat_xml) - if (ph == '-1') : ph = 11000 - if (pw == '-1') : pw = 8500 + if (ph == '-1') or (ph == '0') : ph = '11000' + if (pw == '-1') or (pw == '0') : pw = '8500' # now build up the style sheet print ' ', 'other0000.dat' fname = os.path.join(bookDir,'other0000.dat') xname = os.path.join(bookDir, 'style.css') - xmlstr = convert2xml.main('convert2xml.py --flat-xml ' + dictFile + ' ' + fname) + pargv=[] + pargv.append('convert2xml.py') + pargv.append('--flat-xml') + pargv.append(dictFile) + pargv.append(fname) + xmlstr = convert2xml.main(pargv) cssstr , classlst = stylexml2css.convert2CSS(xmlstr, fontsize, ph, pw) file(xname, 'wb').write(cssstr) htmlstr += '\n' @@ -143,7 +164,12 @@ def main(argv): for filename in filenames: print ' ', filename fname = os.path.join(pageDir,filename) - flat_xml = convert2xml.main('convert2xml.py --flat-xml ' + dictFile + ' ' + fname) + pargv=[] + pargv.append('convert2xml.py') + pargv.append('--flat-xml') + pargv.append(dictFile) + pargv.append(fname) + flat_xml = convert2xml.main(pargv) htmlstr += flatxml2html.convert2HTML(flat_xml, classlst, fname, bookDir, fixedimage) htmlstr += '\n\n' diff --git a/Topaz_Tools/lib/gensvg.py b/Topaz_Tools/lib/gensvg.py index ea198e4..e8dfe06 100644 --- a/Topaz_Tools/lib/gensvg.py +++ b/Topaz_Tools/lib/gensvg.py @@ -1,8 +1,20 @@ #! /usr/bin/python # vim:ts=4:sw=4:softtabstop=4:smarttab:expandtab -# For use with Topaz Scripts Version 2.0 +# For use with Topaz Scripts Version 2.2 -import os, sys, getopt +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 +sys.stdout=Unbuffered(sys.stdout) + +import os, getopt # local routines import convert2xml @@ -190,8 +202,6 @@ def main(argv): if len(argv) == 0: argv = sys.argv - else : - argv = argv.split() try: opts, args = getopt.getopt(argv[1:], "xrh") @@ -199,11 +209,11 @@ def main(argv): except getopt.GetoptError, err: print str(err) usage() - sys.exit(2) + sys.exit(1) if len(opts) == 0 and len(args) == 0 : usage() - sys.exit(2) + sys.exit(1) raw = 0 for o, a in opts: @@ -219,33 +229,33 @@ def main(argv): if not os.path.exists(bookDir) : print "Can not find directory with unencrypted book" - sys.exit(-1) + sys.exit(1) dictFile = os.path.join(bookDir,'dict0000.dat') if not os.path.exists(dictFile) : print "Can not find dict0000.dat file" - sys.exit(-1) + sys.exit(1) pageDir = os.path.join(bookDir,'page') if not os.path.exists(pageDir) : print "Can not find page directory in unencrypted book" - sys.exit(-1) + sys.exit(1) imgDir = os.path.join(bookDir,'img') if not os.path.exists(imgDir) : print "Can not find image directory in unencrypted book" - sys.exit(-1) + sys.exit(1) glyphsDir = os.path.join(bookDir,'glyphs') if not os.path.exists(glyphsDir) : print "Can not find glyphs directory in unencrypted book" - sys.exit(-1) + sys.exit(1) metaFile = os.path.join(bookDir,'metadata0000.dat') if not os.path.exists(metaFile) : print "Can not find metadata0000.dat in unencrypted book" - sys.exit(-1) + sys.exit(1) svgDir = os.path.join(bookDir,'svg') if not os.path.exists(svgDir) : @@ -274,7 +284,12 @@ def main(argv): for filename in filenames: print ' ', filename fname = os.path.join(glyphsDir,filename) - flat_xml = convert2xml.main('convert2xml.py --flat-xml ' + dictFile + ' ' + fname) + pargv=[] + pargv.append('convert2xml.py') + pargv.append('--flat-xml') + pargv.append(dictFile) + pargv.append(fname) + flat_xml = convert2xml.main(pargv) gp = GParser(flat_xml) for i in xrange(0, gp.count): path = gp.getPath(i) @@ -297,7 +312,12 @@ def main(argv): for filename in filenames: print ' ', filename fname = os.path.join(pageDir,filename) - flat_xml = convert2xml.main('convert2xml.py --flat-xml ' + dictFile + ' ' + fname) + pargv=[] + pargv.append('convert2xml.py') + pargv.append('--flat-xml') + pargv.append(dictFile) + pargv.append(fname) + flat_xml = convert2xml.main(pargv) pp = PParser(flat_xml) if (raw) : pfile = open(os.path.join(svgDir,filename.replace('.dat','.svg')), 'w') diff --git a/Topaz_Tools/lib/genxml.py b/Topaz_Tools/lib/genxml.py index 299dde2..868fef1 100644 --- a/Topaz_Tools/lib/genxml.py +++ b/Topaz_Tools/lib/genxml.py @@ -1,8 +1,21 @@ #! /usr/bin/python # vim:ts=4:sw=4:softtabstop=4:smarttab:expandtab -# For use with Topaz Scripts Version 2.0 +# For use with Topaz Scripts Version 2.2 -import os, sys, getopt +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 +sys.stdout=Unbuffered(sys.stdout) + + +import os, getopt # local routines import convert2xml @@ -23,8 +36,6 @@ def main(argv): if len(argv) == 0: argv = sys.argv - else : - argv = argv.split() try: opts, args = getopt.getopt(argv[1:], "h:") @@ -32,11 +43,11 @@ def main(argv): except getopt.GetoptError, err: print str(err) usage() - sys.exit(2) + sys.exit(1) if len(opts) == 0 and len(args) == 0 : usage() - sys.exit(2) + sys.exit(1) for o, a in opts: if o =="-h": @@ -47,32 +58,32 @@ def main(argv): if not os.path.exists(bookDir) : print "Can not find directory with unencrypted book" - sys.exit(-1) + sys.exit(1) dictFile = os.path.join(bookDir,'dict0000.dat') if not os.path.exists(dictFile) : print "Can not find dict0000.dat file" - sys.exit(-1) + sys.exit(1) pageDir = os.path.join(bookDir,'page') if not os.path.exists(pageDir) : print "Can not find page directory in unencrypted book" - sys.exit(-1) + sys.exit(1) glyphsDir = os.path.join(bookDir,'glyphs') if not os.path.exists(glyphsDir) : print "Can not find glyphs directory in unencrypted book" - sys.exit(-1) + sys.exit(1) otherFile = os.path.join(bookDir,'other0000.dat') if not os.path.exists(otherFile) : print "Can not find other0000.dat in unencrypted book" - sys.exit(-1) + sys.exit(1) metaFile = os.path.join(bookDir,'metadata0000.dat') if not os.path.exists(metaFile) : print "Can not find metadata0000.dat in unencrypted book" - sys.exit(-1) + sys.exit(1) xmlDir = os.path.join(bookDir,'xml') if not os.path.exists(xmlDir): @@ -90,7 +101,11 @@ def main(argv): print ' ', 'other0000.dat' fname = os.path.join(bookDir,'other0000.dat') xname = os.path.join(xmlDir, 'stylesheet.xml') - xmlstr = convert2xml.main('convert2xml.py ' + dictFile + ' ' + fname) + pargv=[] + pargv.append('convert2xml.py') + pargv.append(dictFile) + pargv.append(fname) + xmlstr = convert2xml.main(pargv) file(xname, 'wb').write(xmlstr) filenames = os.listdir(pageDir) @@ -100,7 +115,11 @@ def main(argv): print ' ', filename fname = os.path.join(pageDir,filename) xname = os.path.join(xmlDir, filename.replace('.dat','.xml')) - xmlstr = convert2xml.main('convert2xml.py ' + dictFile + ' ' + fname) + pargv=[] + pargv.append('convert2xml.py') + pargv.append(dictFile) + pargv.append(fname) + xmlstr = convert2xml.main(pargv) file(xname, 'wb').write(xmlstr) filenames = os.listdir(glyphsDir) @@ -110,7 +129,11 @@ def main(argv): print ' ', filename fname = os.path.join(glyphsDir,filename) xname = os.path.join(xmlDir, filename.replace('.dat','.xml')) - xmlstr = convert2xml.main('convert2xml.py ' + dictFile + ' ' + fname) + pargv=[] + pargv.append('convert2xml.py') + pargv.append(dictFile) + pargv.append(fname) + xmlstr = convert2xml.main(pargv) file(xname, 'wb').write(xmlstr) diff --git a/Topaz_Tools/lib/getpagedim.py b/Topaz_Tools/lib/getpagedim.py index b2f66c0..e59e83f 100644 --- a/Topaz_Tools/lib/getpagedim.py +++ b/Topaz_Tools/lib/getpagedim.py @@ -1,8 +1,7 @@ #! /usr/bin/python # vim:ts=4:sw=4:softtabstop=4:smarttab:expandtab -# For use with Topaz Scripts Version 2.0 +# For use with Topaz Scripts Version 2.2 -from __future__ import with_statement import csv import sys import os diff --git a/Topaz_Tools/lib/scrolltextwidget.py b/Topaz_Tools/lib/scrolltextwidget.py new file mode 100644 index 0000000..98b4147 --- /dev/null +++ b/Topaz_Tools/lib/scrolltextwidget.py @@ -0,0 +1,27 @@ +#!/usr/bin/env python +# vim:ts=4:sw=4:softtabstop=4:smarttab:expandtab + +import Tkinter +import Tkconstants + +# basic scrolled text widget +class ScrolledText(Tkinter.Text): + def __init__(self, master=None, **kw): + self.frame = Tkinter.Frame(master) + self.vbar = Tkinter.Scrollbar(self.frame) + self.vbar.pack(side=Tkconstants.RIGHT, fill=Tkconstants.Y) + kw.update({'yscrollcommand': self.vbar.set}) + Tkinter.Text.__init__(self, self.frame, **kw) + self.pack(side=Tkconstants.LEFT, fill=Tkconstants.BOTH, expand=True) + self.vbar['command'] = self.yview + # Copy geometry methods of self.frame without overriding Text + # methods = hack! + text_meths = vars(Tkinter.Text).keys() + methods = vars(Tkinter.Pack).keys() + vars(Tkinter.Grid).keys() + vars(Tkinter.Place).keys() + methods = set(methods).difference(text_meths) + for m in methods: + if m[0] != '_' and m != 'config' and m != 'configure': + setattr(self, m, getattr(self.frame, m)) + + def __str__(self): + return str(self.frame) diff --git a/Topaz_Tools/lib/stylexml2css.py b/Topaz_Tools/lib/stylexml2css.py index f0d07d7..a540e9a 100644 --- a/Topaz_Tools/lib/stylexml2css.py +++ b/Topaz_Tools/lib/stylexml2css.py @@ -1,8 +1,7 @@ #! /usr/bin/python # vim:ts=4:sw=4:softtabstop=4:smarttab:expandtab -# For use with Topaz Scripts Version 2.0 +# For use with Topaz Scripts Version 2.2 -from __future__ import with_statement import csv import sys import os diff --git a/Topaz_Tools/lib/subasyncio.py b/Topaz_Tools/lib/subasyncio.py new file mode 100644 index 0000000..ed13aa1 --- /dev/null +++ b/Topaz_Tools/lib/subasyncio.py @@ -0,0 +1,149 @@ +#!/usr/bin/env python +# vim:ts=4:sw=4:softtabstop=4:smarttab:expandtab + +import os, sys +import signal +import threading +import subprocess +from subprocess import Popen, PIPE, STDOUT + +# **heavily** chopped up and modfied version of asyncproc.py +# to make it actually work on Windows as well as Mac/Linux +# For the original see: +# "http://www.lysator.liu.se/~bellman/download/" +# author is "Thomas Bellman " +# available under GPL version 3 or Later + +# create an asynchronous subprocess whose output can be collected in +# a non-blocking manner + +# What a mess! Have to use threads just to get non-blocking io +# in a cross-platform manner + +# luckily all thread use is hidden within this class + +class Process(object): + def __init__(self, *params, **kwparams): + if len(params) <= 3: + kwparams.setdefault('stdin', subprocess.PIPE) + if len(params) <= 4: + kwparams.setdefault('stdout', subprocess.PIPE) + if len(params) <= 5: + kwparams.setdefault('stderr', subprocess.PIPE) + self.__pending_input = [] + self.__collected_outdata = [] + self.__collected_errdata = [] + self.__exitstatus = None + self.__lock = threading.Lock() + self.__inputsem = threading.Semaphore(0) + self.__quit = False + + self.__process = subprocess.Popen(*params, **kwparams) + + if self.__process.stdin: + self.__stdin_thread = threading.Thread( + name="stdin-thread", + target=self.__feeder, args=(self.__pending_input, + self.__process.stdin)) + self.__stdin_thread.setDaemon(True) + self.__stdin_thread.start() + + if self.__process.stdout: + self.__stdout_thread = threading.Thread( + name="stdout-thread", + target=self.__reader, args=(self.__collected_outdata, + self.__process.stdout)) + self.__stdout_thread.setDaemon(True) + self.__stdout_thread.start() + + if self.__process.stderr: + self.__stderr_thread = threading.Thread( + name="stderr-thread", + target=self.__reader, args=(self.__collected_errdata, + self.__process.stderr)) + self.__stderr_thread.setDaemon(True) + self.__stderr_thread.start() + + def pid(self): + return self.__process.pid + + def kill(self, signal): + self.__process.send_signal(signal) + + # check on subprocess (pass in 'nowait') to act like poll + def wait(self, flag): + if flag.lower() == 'nowait': + rc = self.__process.poll() + else: + rc = self.__process.wait() + if rc != None: + if self.__process.stdin: + self.closeinput() + if self.__process.stdout: + self.__stdout_thread.join() + if self.__process.stderr: + self.__stderr_thread.join() + return self.__process.returncode + + def terminate(self): + if self.__process.stdin: + self.closeinput() + self.__process.terminate() + + # thread gets data from subprocess stdout + def __reader(self, collector, source): + while True: + data = os.read(source.fileno(), 65536) + self.__lock.acquire() + collector.append(data) + self.__lock.release() + if data == "": + source.close() + break + return + + # thread feeds data to subprocess stdin + def __feeder(self, pending, drain): + while True: + self.__inputsem.acquire() + self.__lock.acquire() + if not pending and self.__quit: + drain.close() + self.__lock.release() + break + data = pending.pop(0) + self.__lock.release() + drain.write(data) + + # non-blocking read of data from subprocess stdout + def read(self): + self.__lock.acquire() + outdata = "".join(self.__collected_outdata) + del self.__collected_outdata[:] + self.__lock.release() + return outdata + + # non-blocking read of data from subprocess stderr + def readerr(self): + self.__lock.acquire() + errdata = "".join(self.__collected_errdata) + del self.__collected_errdata[:] + self.__lock.release() + return errdata + + # non-blocking write to stdin of subprocess + def write(self, data): + if self.__process.stdin is None: + raise ValueError("Writing to process with stdin not a pipe") + self.__lock.acquire() + self.__pending_input.append(data) + self.__inputsem.release() + self.__lock.release() + + # close stdinput of subprocess + def closeinput(self): + self.__lock.acquire() + self.__quit = True + self.__inputsem.release() + self.__lock.release() + diff --git a/Topaz_Tools/lib/topaz-changes.txt b/Topaz_Tools/lib/topaz-changes.txt new file mode 100644 index 0000000..a80ef5f --- /dev/null +++ b/Topaz_Tools/lib/topaz-changes.txt @@ -0,0 +1,75 @@ +Changes in 2.2 + - fix for minor bug in encode_Number from clark nova + - more fixes to handle paths with spaces in them + - updates to work better with the gui front end + + +Changes in 2.1 + - extremely minor changes to support a gui frontend + - no changes to functionality + + +Changes in version 2.0 + + - gensvg.py now accepts two options + -x : output browseable XHTML+SVG pages (default) + -r : output raw SVG images (useful for later conversion to pdf) + + - flatxml2html.py now understands page.groups of type graphic + and handles vertical regions as svg images + + - genhtml.py now accepts an option + --fixed-image : which will force the conversion + of all fixed regions to svg images + + - minor bug fixes and html conversion improvements + + +Changes in version 1.8 + - gensvg.py now builds wonderful xhtml pages with embedded svg + that can be easily paged through as if reading a book! + (tested in Safari for Mac and Win and Firefox) + (requires javascript to be enabled) + - genhtml.py now REQUIRES that gensvg.py be run FIRST + this allows create of images on the fly from glyphs + - genhtml.py now automatically makes tables of words into svg + based images and will handle glyph based ornate first + letters of words + - cmbtc_dump_mac_linux.py has been renamed to be + cmbtc_dump_nonK4PC.py to make it clearer + when it needs to be used + + +Changes in version 1.7 + - gensvg.py has been improved so that the glyphs render exactly (ClarkNova) + - gensvg.py has fixed a render order "bug" that allowed some images to cover or hide text. (ClarkNova) + - change generated html to use external stylesheet via a link to "style.css" + - add missing tag + - make xhtml compliant doctype and minor changes to write correct xhtml + - make divs that act as anchors be hidden visually and to take up 0 height and 0 width to prevent any impact on layout + +Changes in version 1.6 + - support for books whose paragraphs have no styles + - support to run cmbtc_dump on Linux and Mac OSX provided you know your PID of your ipod or standalone Kindle + (contributed by DiapDealer) + +Changes in version 1.5 + - completely reworked generation of styles to use actual page heights and widths + - added new script getpagedim.py to support the above + - style names with underscores in them are now properly paired with their base class + - fixed hanging indents that did not ever set a left margin + - added support for a number of not previously known region types + - added support for a previously unknown snippet - <empty></empty> + - corrected a bug that caused unknown regions to abort the program + - added code to make the handling of unknown regions better in general + - corrected a bug that caused the last link on a page to be missing (if it was the last thing on the page) + +Changes in version 1.3 + - font generation by gensvg.py is now greatly improved with support for contour points added + - support for more region types + - support for inline images in paragraphs or text fields (ie. initial graphics for the first letter of a word) + - greatly improved dtd information used for the xml to prevent parsing mistakes + +Version 1.0 + - initial release + diff --git a/Topaz_Tools/lib/readme.txt b/Topaz_Tools/lib/topaz-readme.txt similarity index 100% rename from Topaz_Tools/lib/readme.txt rename to Topaz_Tools/lib/topaz-readme.txt diff --git a/eReader_Tools/Pml2HTML.pyw b/eReader_Tools/Pml2HTML.pyw new file mode 100644 index 0000000..7800463 --- /dev/null +++ b/eReader_Tools/Pml2HTML.pyw @@ -0,0 +1,181 @@ +#!/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 subasyncio +from subasyncio import Process +import Tkinter +import Tkconstants +import tkFileDialog +import tkMessageBox +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='Pml to HTML Conversion') + 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 Pml input file').grid(row=0, sticky=Tkconstants.E) + self.pmlpath = Tkinter.Entry(body, width=50) + self.pmlpath.grid(row=0, column=1, sticky=sticky) + self.pmlpath.insert(0, os.getcwd()) + button = Tkinter.Button(body, text="...", command=self.get_pmlpath) + button.grid(row=0, column=2) + + Tkinter.Label(body, text='Name for HTML Output File').grid(row=1, sticky=Tkconstants.E) + self.outpath = Tkinter.Entry(body, width=50) + self.outpath.grid(row=1, column=1, sticky=sticky) + self.outpath.insert(0, '') + button = Tkinter.Button(body, text="...", command=self.get_outpath) + button.grid(row=1, column=2) + + 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=2, 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 !='': + self.stext.insert(Tkconstants.END,msg) + self.stext.yview_pickplace(Tkconstants.END) + return + + # run xpml2hxtml.py as a subprocess via pipes and collect stdout + def pmlhtml(self, infile, outfile): + # os.putenv('PYTHONUNBUFFERED', '1') + cmdline = 'python ./lib/xpml2xhtml.py "' + infile + '" "' + outfile + '"' + 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\\xpml2xhtml.py "' + infile + '" "' + outfile + '"' + else : + cmdline = 'lib\\xpml2xhtml.py "' + infile + '" "' + outfile + '"' + + p2 = Process(cmdline, shell=True, bufsize=1, stdin=None, stdout=PIPE, stderr=PIPE, close_fds=False) + return p2 + + + def get_pmlpath(self): + pmlpath = tkFileDialog.askopenfilename( + parent=None, title='Select eBook Pml File', + defaultextension='.pml', filetypes=[('eBook Pml File', '.pml'), + ('All Files', '.*')]) + if pmlpath: + pmlpath = os.path.normpath(pmlpath) + self.pmlpath.delete(0, Tkconstants.END) + self.pmlpath.insert(0, pmlpath) + return + + def get_outpath(self): + pmlpath = self.pmlpath.get() + initname = os.path.basename(pmlpath) + p = initname.find('.') + if p >= 0: initname = initname[0:p] + initname += '.html' + outpath = tkFileDialog.asksaveasfilename( + parent=None, title='Select HTML file to produce', + defaultextension='.html', initialfile=initname, + filetypes=[('HTML files', '.html'), ('All files', '.*')]) + if outpath: + outpath = os.path.normpath(outpath) + self.outpath.delete(0, Tkconstants.END) + self.outpath.insert(0, outpath) + 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') + pmlpath = self.pmlpath.get() + outpath = self.outpath.get() + if not pmlpath or not os.path.exists(pmlpath): + self.status['text'] = 'Specified eBook pml file does not exist' + self.sbotton.configure(state='normal') + return + if not outpath: + self.status['text'] = 'No output file specified' + self.sbotton.configure(state='normal') + return + + log = 'Command = "python xpml2xhtml.py"\n' + log += 'PDB Path = "'+ pmlpath + '"\n' + log += 'HTML Output File = "' + outpath + '"\n' + log += '\n\n' + log += 'Please Wait ...\n\n' + self.stext.insert(Tkconstants.END,log) + self.p2 = self.pmlhtml(pmlpath, outpath) + + # 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('eBook Pml to HTML 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()) diff --git a/eReader_Tools/eReaderPDB2PML.pyw b/eReader_Tools/eReaderPDB2PML.pyw new file mode 100644 index 0000000..cb1569c --- /dev/null +++ b/eReader_Tools/eReaderPDB2PML.pyw @@ -0,0 +1,200 @@ +#!/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') + 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) + self.pdbpath.insert(0, os.getcwd()) + button = Tkinter.Button(body, text="...", command=self.get_pdbpath) + button.grid(row=0, column=2) + + Tkinter.Label(body, text='Output Directory').grid(row=1, sticky=Tkconstants.E) + self.outpath = Tkinter.Entry(body, width=50) + self.outpath.grid(row=1, column=1, sticky=sticky) + self.outpath.insert(0, os.getcwd()) + button = Tkinter.Button(body, text="...", command=self.get_outpath) + button.grid(row=1, column=2) + + Tkinter.Label(body, text='Name on CC').grid(row=2, sticky=Tkconstants.E) + self.name = Tkinter.StringVar() + self.nameinfo = Tkinter.Entry(body, width=40, textvariable=self.name) + self.nameinfo.grid(row=2, column=1, sticky=sticky) + + Tkinter.Label(body, text='Last 8 digits of CC Number').grid(row=3, sticky=Tkconstants.E) + self.ccnum = Tkinter.StringVar() + self.ccinfo = Tkinter.Entry(body, width=10, textvariable=self.ccnum) + self.ccinfo.grid(row=3, 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=4, 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 !='': + 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, outdir, name, ccnum): + # os.putenv('PYTHONUNBUFFERED', '1') + cmdline = 'python ./lib/erdr2pml.py "' + infile + '" "' + outdir + '" "' + 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 "' + infile + '" "' + outdir + '" "' + name + '" ' + ccnum + else : + cmdline = 'lib\erdr2pml.py "' + infile + '" "' + outdir + '" "' + name + '" ' + ccnum + + 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 get_outpath(self): + outpath = tkFileDialog.askdirectory( + parent=None, title='Directory to Store Output into', + initialdir=os.getcwd(), initialfile=None) + if outpath: + outpath = os.path.normpath(outpath) + self.outpath.delete(0, Tkconstants.END) + self.outpath.insert(0, outpath) + 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() + outpath = self.outpath.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 + if not outpath: + self.status['text'] = 'No output directory specified' + self.sbotton.configure(state='normal') + return + if not os.path.exists(outpath): + os.makedirs(outpath) + 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"\n' + log += 'PDB Path = "'+ pdbpath + '"\n' + log += 'Output Directory = "' + outpath + '"\n' + log += 'Name = "' + name + '"\n' + log += 'Last 8 of CC = "' + ccnum + '"\n' + log += '\n\n' + log += 'Please Wait ...\n' + self.stext.insert(Tkconstants.END,log) + self.p2 = self.erdr(pdbpath, outpath, 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 PML 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()) diff --git a/eReader_Tools/lib/erdr2pml.py b/eReader_Tools/lib/erdr2pml.py index 6bae3f5..3e9133a 100644 --- a/eReader_Tools/lib/erdr2pml.py +++ b/eReader_Tools/lib/erdr2pml.py @@ -1,7 +1,7 @@ #!/usr/bin/env python # vim:ts=4:sw=4:softtabstop=4:smarttab:expandtab # -# eRdr2Pml.py +# erdr2pml.py # # This is a python script. You need a Python interpreter to run it. # For example, ActiveState Python, which exists for windows. @@ -50,9 +50,10 @@ # 0.09 - fixed typos in first_pages to first_page to again support older formats # 0.10 - minor cleanups # 0.11 - fixups for using correct xml for footnotes and sidebars for use with Dropbook -# 0.12 - fixup for file name cleaning - no longer converts to lower case +# 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 -__version__='0.12' +__version__='0.13' # Import Psyco if available try: @@ -72,7 +73,20 @@ try: except ImportError: pass -import struct, binascii, zlib, os, sys, os.path, urllib +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 +sys.stdout=Unbuffered(sys.stdout) + +import struct, binascii, zlib, os, os.path, urllib + try: from hashlib import sha1 except ImportError: @@ -85,7 +99,6 @@ import logging logging.basicConfig() #logging.basicConfig(level=logging.DEBUG) - ECB = 0 CBC = 1 class Des(object): @@ -593,6 +606,7 @@ def main(argv=None): 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" + return 1 else: if len(argv)==4: infile, name, cc = argv[1], argv[2], argv[3] @@ -613,6 +627,8 @@ def main(argv=None): print "done" except ValueError, e: print "Error: %s" % e + return 1 + return 0 if __name__ == "__main__": #import cProfile diff --git a/eReader_Tools/lib/scrolltextwidget.py b/eReader_Tools/lib/scrolltextwidget.py new file mode 100644 index 0000000..98b4147 --- /dev/null +++ b/eReader_Tools/lib/scrolltextwidget.py @@ -0,0 +1,27 @@ +#!/usr/bin/env python +# vim:ts=4:sw=4:softtabstop=4:smarttab:expandtab + +import Tkinter +import Tkconstants + +# basic scrolled text widget +class ScrolledText(Tkinter.Text): + def __init__(self, master=None, **kw): + self.frame = Tkinter.Frame(master) + self.vbar = Tkinter.Scrollbar(self.frame) + self.vbar.pack(side=Tkconstants.RIGHT, fill=Tkconstants.Y) + kw.update({'yscrollcommand': self.vbar.set}) + Tkinter.Text.__init__(self, self.frame, **kw) + self.pack(side=Tkconstants.LEFT, fill=Tkconstants.BOTH, expand=True) + self.vbar['command'] = self.yview + # Copy geometry methods of self.frame without overriding Text + # methods = hack! + text_meths = vars(Tkinter.Text).keys() + methods = vars(Tkinter.Pack).keys() + vars(Tkinter.Grid).keys() + vars(Tkinter.Place).keys() + methods = set(methods).difference(text_meths) + for m in methods: + if m[0] != '_' and m != 'config' and m != 'configure': + setattr(self, m, getattr(self.frame, m)) + + def __str__(self): + return str(self.frame) diff --git a/eReader_Tools/lib/subasyncio.py b/eReader_Tools/lib/subasyncio.py new file mode 100644 index 0000000..ed13aa1 --- /dev/null +++ b/eReader_Tools/lib/subasyncio.py @@ -0,0 +1,149 @@ +#!/usr/bin/env python +# vim:ts=4:sw=4:softtabstop=4:smarttab:expandtab + +import os, sys +import signal +import threading +import subprocess +from subprocess import Popen, PIPE, STDOUT + +# **heavily** chopped up and modfied version of asyncproc.py +# to make it actually work on Windows as well as Mac/Linux +# For the original see: +# "http://www.lysator.liu.se/~bellman/download/" +# author is "Thomas Bellman <bellman@lysator.liu.se>" +# available under GPL version 3 or Later + +# create an asynchronous subprocess whose output can be collected in +# a non-blocking manner + +# What a mess! Have to use threads just to get non-blocking io +# in a cross-platform manner + +# luckily all thread use is hidden within this class + +class Process(object): + def __init__(self, *params, **kwparams): + if len(params) <= 3: + kwparams.setdefault('stdin', subprocess.PIPE) + if len(params) <= 4: + kwparams.setdefault('stdout', subprocess.PIPE) + if len(params) <= 5: + kwparams.setdefault('stderr', subprocess.PIPE) + self.__pending_input = [] + self.__collected_outdata = [] + self.__collected_errdata = [] + self.__exitstatus = None + self.__lock = threading.Lock() + self.__inputsem = threading.Semaphore(0) + self.__quit = False + + self.__process = subprocess.Popen(*params, **kwparams) + + if self.__process.stdin: + self.__stdin_thread = threading.Thread( + name="stdin-thread", + target=self.__feeder, args=(self.__pending_input, + self.__process.stdin)) + self.__stdin_thread.setDaemon(True) + self.__stdin_thread.start() + + if self.__process.stdout: + self.__stdout_thread = threading.Thread( + name="stdout-thread", + target=self.__reader, args=(self.__collected_outdata, + self.__process.stdout)) + self.__stdout_thread.setDaemon(True) + self.__stdout_thread.start() + + if self.__process.stderr: + self.__stderr_thread = threading.Thread( + name="stderr-thread", + target=self.__reader, args=(self.__collected_errdata, + self.__process.stderr)) + self.__stderr_thread.setDaemon(True) + self.__stderr_thread.start() + + def pid(self): + return self.__process.pid + + def kill(self, signal): + self.__process.send_signal(signal) + + # check on subprocess (pass in 'nowait') to act like poll + def wait(self, flag): + if flag.lower() == 'nowait': + rc = self.__process.poll() + else: + rc = self.__process.wait() + if rc != None: + if self.__process.stdin: + self.closeinput() + if self.__process.stdout: + self.__stdout_thread.join() + if self.__process.stderr: + self.__stderr_thread.join() + return self.__process.returncode + + def terminate(self): + if self.__process.stdin: + self.closeinput() + self.__process.terminate() + + # thread gets data from subprocess stdout + def __reader(self, collector, source): + while True: + data = os.read(source.fileno(), 65536) + self.__lock.acquire() + collector.append(data) + self.__lock.release() + if data == "": + source.close() + break + return + + # thread feeds data to subprocess stdin + def __feeder(self, pending, drain): + while True: + self.__inputsem.acquire() + self.__lock.acquire() + if not pending and self.__quit: + drain.close() + self.__lock.release() + break + data = pending.pop(0) + self.__lock.release() + drain.write(data) + + # non-blocking read of data from subprocess stdout + def read(self): + self.__lock.acquire() + outdata = "".join(self.__collected_outdata) + del self.__collected_outdata[:] + self.__lock.release() + return outdata + + # non-blocking read of data from subprocess stderr + def readerr(self): + self.__lock.acquire() + errdata = "".join(self.__collected_errdata) + del self.__collected_errdata[:] + self.__lock.release() + return errdata + + # non-blocking write to stdin of subprocess + def write(self, data): + if self.__process.stdin is None: + raise ValueError("Writing to process with stdin not a pipe") + self.__lock.acquire() + self.__pending_input.append(data) + self.__inputsem.release() + self.__lock.release() + + # close stdinput of subprocess + def closeinput(self): + self.__lock.acquire() + self.__quit = True + self.__inputsem.release() + self.__lock.release() + diff --git a/eReader_Tools/lib/xpml2xhtml.py b/eReader_Tools/lib/xpml2xhtml.py index f4ceef4..c449f2f 100644 --- a/eReader_Tools/lib/xpml2xhtml.py +++ b/eReader_Tools/lib/xpml2xhtml.py @@ -29,10 +29,23 @@ # 0.16 - use proper and safe temporary file when passing things to tidy # 0.17 - add support for tidy.exe under windows # 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 -__version__='0.18' +__version__='0.19' -import struct, binascii, zlib, os, getopt, sys, os.path, urllib, re, tempfile +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 +sys.stdout=Unbuffered(sys.stdout) + +import struct, binascii, zlib, os, getopt, os.path, urllib, re, tempfile import logging from subprocess import Popen, PIPE, STDOUT @@ -790,10 +803,10 @@ def main(argv=None): except getopt.GetoptError, err: print str(err) usage() - return 2 + return 1 if len(args) != 2: usage() - return 2 + return 1 sigil_breaks = False use_tidy = False for o, a in opts: @@ -832,7 +845,7 @@ def main(argv=None): print "Finished Processing" except ValueError, e: print "Error: %s" % e - return 2 + return 1 return 0 if __name__ == "__main__":