diff --git a/Kindle_Mobi_Tools/KindlePID.pyw b/Kindle_Mobi_Tools/KindlePID.pyw new file mode 100644 index 0000000..719d025 --- /dev/null +++ b/Kindle_Mobi_Tools/KindlePID.pyw @@ -0,0 +1,138 @@ +#!/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='Find your Kindle PID') + 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='Kindle Serial # or iPhone UDID').grid(row=1, sticky=Tkconstants.E) + self.serialnum = Tkinter.StringVar() + self.serialinfo = Tkinter.Entry(body, width=45, textvariable=self.serialnum) + self.serialinfo.grid(row=1, 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' + 'Kindle PID Successfully Determined\n' + if poll != 0: + msg = text + '\n\n' + 'Error: Kindle PID 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 pidrdr(self, serial): + # os.putenv('PYTHONUNBUFFERED', '1') + cmdline = 'python ./lib/kindlepid.py "' + serial + '"' + 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\kindlepid.py "' + serial + '"' + else : + cmdline = 'lib\kindlepid.py "' + serial + '"' + + p2 = Process(cmdline, shell=True, bufsize=1, stdin=None, stdout=PIPE, stderr=PIPE, close_fds=False) + return p2 + + 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') + serial = self.serialinfo.get() + if not serial or serial == '': + self.status['text'] = 'No Kindle Serial Number or iPhone UDID specified' + self.sbotton.configure(state='normal') + return + + log = 'Command = "python kindlepid.py"\n' + log += 'Serial = "' + serial + '"\n' + log += '\n\n' + log += 'Please Wait ...\n\n' + self.stext.insert(Tkconstants.END,log) + self.p2 = self.pidrdr(serial) + + # 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('Kindle and iPhone PID Calculator') + 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/Kindle_Mobi_Tools/Kindleizer.pyw b/Kindle_Mobi_Tools/Kindleizer.pyw new file mode 100644 index 0000000..7efec6e --- /dev/null +++ b/Kindle_Mobi_Tools/Kindleizer.pyw @@ -0,0 +1,163 @@ +#!/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='Fix Encrypted Mobi eBooks so the Kindle can read them') + 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='Mobi eBook input file').grid(row=0, sticky=Tkconstants.E) + self.mobipath = Tkinter.Entry(body, width=50) + self.mobipath.grid(row=0, column=1, sticky=sticky) + self.mobipath.insert(0, os.getcwd()) + button = Tkinter.Button(body, text="...", command=self.get_mobipath) + button.grid(row=0, column=2) + + Tkinter.Label(body, text='10 Character PID').grid(row=1, sticky=Tkconstants.E) + self.pidnum = Tkinter.StringVar() + self.pidinfo = Tkinter.Entry(body, width=12, textvariable=self.pidnum) + self.pidinfo.grid(row=1, 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=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' + 'Fix for Kindle successful\n' + if poll != 0: + msg = text + '\n\n' + 'Error: Fix for Kindle 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 krdr(self, infile, pidnum): + # os.putenv('PYTHONUNBUFFERED', '1') + cmdline = 'python ./lib/kindlefix.py "' + infile + '" "' + pidnum + '"' + 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\kindlefix.py "' + infile + '" "' + pidnum + '"' + else : + cmdline = 'lib\kindlefix.py "' + infile + '" "' + pidnum + '"' + + p2 = Process(cmdline, shell=True, bufsize=1, stdin=None, stdout=PIPE, stderr=PIPE, close_fds=False) + return p2 + + + def get_mobipath(self): + mobipath = tkFileDialog.askopenfilename( + parent=None, title='Select Mobi eBook File', + defaultextension='.prc', filetypes=[('Mobi eBook File', '.prc'), ('Mobi eBook File', '.mobi'), + ('All Files', '.*')]) + if mobipath: + mobipath = os.path.normpath(mobipath) + self.mobipath.delete(0, Tkconstants.END) + self.mobipath.insert(0, mobipath) + 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') + mobipath = self.mobipath.get() + pidnum = self.pidinfo.get() + if not mobipath or not os.path.exists(mobipath): + self.status['text'] = 'Specified Mobi eBook file does not exist' + self.sbotton.configure(state='normal') + return + if not pidnum or pidnum == '': + self.status['text'] = 'No PID specified' + self.sbotton.configure(state='normal') + return + + log = 'Command = "python kindlefix.py"\n' + log += 'Mobi Path = "'+ mobipath + '"\n' + log += 'PID = "' + pidnum + '"\n' + log += '\n\n' + log += 'Please Wait ...\n\n' + self.stext.insert(Tkconstants.END,log) + self.p2 = self.krdr(mobipath, 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('Fix Encrypted Mobi eBooks to work with the Kindle') + 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/Kindle_Mobi_Tools/MobiDeDRM.pyw b/Kindle_Mobi_Tools/MobiDeDRM.pyw new file mode 100644 index 0000000..533223f --- /dev/null +++ b/Kindle_Mobi_Tools/MobiDeDRM.pyw @@ -0,0 +1,192 @@ +#!/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='Remove Encryption from a Mobi eBook') + 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='Mobi eBook input file').grid(row=0, sticky=Tkconstants.E) + self.mobipath = Tkinter.Entry(body, width=50) + self.mobipath.grid(row=0, column=1, sticky=sticky) + self.mobipath.insert(0, os.getcwd()) + button = Tkinter.Button(body, text="...", command=self.get_mobipath) + button.grid(row=0, column=2) + + Tkinter.Label(body, text='Name for Unencrypted 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) + + Tkinter.Label(body, text='10 Character PID').grid(row=2, sticky=Tkconstants.E) + self.pidnum = Tkinter.StringVar() + self.pidinfo = Tkinter.Entry(body, width=12, textvariable=self.pidnum) + self.pidinfo.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' + 'Encryption successfully removed\n' + if poll != 0: + msg = text + '\n\n' + 'Error: Encryption Removal 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 mobirdr(self, infile, outfile, pidnum): + # os.putenv('PYTHONUNBUFFERED', '1') + cmdline = 'python ./lib/mobidedrm.py "' + infile + '" "' + outfile + '" "' + pidnum + '"' + 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\mobidedrm.py "' + infile + '" "' + outfile + '" "' + pidnum + '"' + else : + cmdline = 'lib\mobidedrm.py "' + infile + '" "' + outfile + '" "' + pidnum + '"' + + p2 = Process(cmdline, shell=True, bufsize=1, stdin=None, stdout=PIPE, stderr=PIPE, close_fds=False) + return p2 + + + def get_mobipath(self): + mobipath = tkFileDialog.askopenfilename( + parent=None, title='Select Mobi eBook File', + defaultextension='.prc', filetypes=[('Mobi eBook File', '.prc'), ('Mobi eBook File', '.mobi'), + ('All Files', '.*')]) + if mobipath: + mobipath = os.path.normpath(mobipath) + self.mobipath.delete(0, Tkconstants.END) + self.mobipath.insert(0, mobipath) + return + + def get_outpath(self): + mobipath = self.mobipath.get() + initname = os.path.basename(mobipath) + p = initname.find('.') + if p >= 0: initname = initname[0:p] + initname += '_nodrm.mobi' + outpath = tkFileDialog.asksaveasfilename( + parent=None, title='Select Unencrypted Mobi File to produce', + defaultextension='.mobi', initialfile=initname, + filetypes=[('Mobi files', '.mobi'), ('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') + mobipath = self.mobipath.get() + outpath = self.outpath.get() + pidnum = self.pidinfo.get() + if not mobipath or not os.path.exists(mobipath): + self.status['text'] = 'Specified Mobi eBook 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 + if not pidnum or pidnum == '': + self.status['text'] = 'No PID specified' + self.sbotton.configure(state='normal') + return + + log = 'Command = "python mobidedrm.py"\n' + log += 'Mobi Path = "'+ mobipath + '"\n' + log += 'Output File = "' + outpath + '"\n' + log += 'PID = "' + pidnum + '"\n' + log += '\n\n' + log += 'Please Wait ...\n\n' + self.stext.insert(Tkconstants.END,log) + self.p2 = self.mobirdr(mobipath, 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('Mobi eBook Encryption Removal') + 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/Kindle_Mobi_Tools/lib/kindlefix.py b/Kindle_Mobi_Tools/lib/kindlefix.py index 492face..6a0b57d 100644 --- a/Kindle_Mobi_Tools/lib/kindlefix.py +++ b/Kindle_Mobi_Tools/lib/kindlefix.py @@ -1,16 +1,17 @@ -#!/usr/bin/python -# The Kindleizer v0.2. Copyright (c) 2007, 2009 Igor Skochinsky -# This script enables encrypted Mobipocket books to be readable by Kindle -# History: -# 0.1 initial release -# 0.2 fixed corrupted metadata issue (thanks to Mark Peek) +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 prc, sys, struct +import sys +sys.stdout=Unbuffered(sys.stdout) -if sys.hexversion >= 0x3000000: - print "This script is incompatible with Python 3.x. Please install Python 2.6.x from python.org" - sys.exit(2) +import prc, struct from binascii import hexlify def strByte(s,off=0): @@ -104,7 +105,7 @@ def find_key(rec0, pid): drmInfo = strPutDWord(drmInfo,4,(dw4|0x800)) dw0, dw4, dw18, dw1c = struct.unpack(">II16xII", drmInfo) #print "Updated drmInfo:", "%08X, %08X, %s, %08X, %08X"%(dw0, dw4, hexlify(drmInfo[0x8:0x18]), dw18, dw1c) - return rec0[:iOff+0x10] + PC1(temp_key, drmInfo, False) + rec0[iOff+0x30:] + return rec0[:iOff+0x10] + PC1(temp_key, drmInfo, False) + rec0[:iOff+0x30] iOff += dwSize return None @@ -115,7 +116,14 @@ def replaceext(filename, newext): else: return nameparts[0]+newext -def main(fname, pid): +def main(argv=sys.argv): + print "The Kindleizer v0.2. Copyright (c) 2007 Igor Skochinsky" + if len(sys.argv) != 3: + print "Fixes encrypted Mobipocket books to be readable by Kindle" + print "Usage: kindlefix.py file.mobi PID" + return 1 + fname = sys.argv[1] + pid = sys.argv[2] if len(pid)==10 and pid[-3]=='*': pid = pid[:-2] if len(pid)!=8 or pid[-1]!='*': @@ -159,10 +167,6 @@ def main(fname, pid): print "Output written to "+outfname return 0 -print "The Kindleizer v0.2. Copyright (c) 2007, 2009 Igor Skochinsky" -if len(sys.argv)<3: - print "Fixes encrypted Mobipocket books to be readable by Kindle" - print "Usage: kindlefix.py file.mobi PID" -else: - fname = sys.argv[1] - sys.exit(main(fname, sys.argv[2])) + +if __name__ == "__main__": + sys.exit(main()) diff --git a/Kindle_Mobi_Tools/lib/kindlepid.py b/Kindle_Mobi_Tools/lib/kindlepid.py index 37903c2..29e4b30 100644 --- a/Kindle_Mobi_Tools/lib/kindlepid.py +++ b/Kindle_Mobi_Tools/lib/kindlepid.py @@ -4,9 +4,20 @@ # History: # 0.1 Initial release # 0.2 Added support for generating PID for iPhone (thanks to mbp) -# Unofficial: Added support for Kindle DX and Kindle 2 International +# 0.3 changed to autoflush stdout, fixed return code usage +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, binascii +import sys +sys.stdout=Unbuffered(sys.stdout) + +import binascii if sys.hexversion >= 0x3000000: print "This script is incompatible with Python 3.x. Please install Python 2.6.x from python.org" @@ -49,29 +60,38 @@ def pidFromSerial(s, l): return pid -print "Mobipocket PID calculator for Amazon Kindle. Copyright (c) 2007, 2009 Igor Skochinsky" -if len(sys.argv)>1: - serial = sys.argv[1] - if len(serial)==16: - if serial.startswith("B001"): - print "Kindle 1 serial number detected" - elif serial.startswith("B002"): - print "Kindle 2 serial number detected" - elif serial.startswith("B003"): - print "Kindle 2i serial number detected" - elif serial.startswith("B004"): - print "Kindle DX serial number detected" - else: - print "Warning: unrecognized serial number. Please recheck input." - sys.exit(1) - pid = pidFromSerial(serial,7)+"*" - print "Mobipocked PID for Kindle serial# "+serial+" is "+checksumPid(pid) - elif len(serial)==40: - print "iPhone serial number (UDID) detected" - pid = pidFromSerial(serial,8) - print "Mobipocked PID for iPhone serial# "+serial+" is "+checksumPid(pid) +def main(argv=sys.argv): + print "Mobipocket PID calculator for Amazon Kindle. Copyright (c) 2007, 2009 Igor Skochinsky" + if len(sys.argv)==2: + serial = sys.argv[1] else: - print "Warning: unrecognized serial number. Please recheck input." - sys.exit(1) -else: - print "Usage: kindlepid.py /" + print "Usage: kindlepid.py /" + return 1 + if len(serial)==16: + if serial.startswith("B001"): + print "Kindle 1 serial number detected" + elif serial.startswith("B002"): + print "Kindle 2 serial number detected" + elif serial.startswith("B003"): + print "Kindle 2 Global serial number detected" + elif serial.startswith("B004"): + print "Kindle DX serial number detected" + else: + print "Warning: unrecognized serial number. Please recheck input." + return 1 + pid = pidFromSerial(serial,7)+"*" + print "Mobipocked PID for Kindle serial# "+serial+" is "+checksumPid(pid) + return 0 + elif len(serial)==40: + print "iPhone serial number (UDID) detected" + pid = pidFromSerial(serial,8) + print "Mobipocked PID for iPhone serial# "+serial+" is "+checksumPid(pid) + return 0 + else: + print "Warning: unrecognized serial number. Please recheck input." + return 1 + return 0 + + +if __name__ == "__main__": + sys.exit(main()) diff --git a/Kindle_Mobi_Tools/lib/mobidedrm.py b/Kindle_Mobi_Tools/lib/mobidedrm.py index 1f02cf9..4ec5b75 100644 --- a/Kindle_Mobi_Tools/lib/mobidedrm.py +++ b/Kindle_Mobi_Tools/lib/mobidedrm.py @@ -21,8 +21,21 @@ # 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 -import sys,struct,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) + +import sys +sys.stdout=Unbuffered(sys.stdout) + +import struct,binascii class DrmException(Exception): pass @@ -206,7 +219,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, 9) # The version number of this plugin + version = (0, 0, 10) # 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 @@ -232,12 +245,13 @@ if not __name__ == "__main__": def customization_help(self, gui=False): return 'Enter PID (separate multiple PIDs with comma)' -if __name__ == "__main__": - print "MobiDeDrm v0.09. Copyright (c) 2008 The Dark Reverser" +def main(argv=sys.argv): + print "MobiDeDrm v0.10. 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 else: infile = sys.argv[1] outfile = sys.argv[2] @@ -247,3 +261,9 @@ if __name__ == "__main__": file(outfile, 'wb').write(DrmStripper(data_file, pid).getResult()) except DrmException, e: print "Error: %s" % e + return 1 + return 0 + +if __name__ == "__main__": + sys.exit(main()) + diff --git a/Kindle_Mobi_Tools/lib/mobihuff.py b/Kindle_Mobi_Tools/lib/mobihuff.py index 84f0471..fe30719 100644 --- a/Kindle_Mobi_Tools/lib/mobihuff.py +++ b/Kindle_Mobi_Tools/lib/mobihuff.py @@ -9,9 +9,22 @@ # 0.01 - Initial version # 0.02 - Fix issue with size computing # 0.03 - Fix issue with some files +# 0.04 - make stdout self flushing and fix return values + +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, sys +import struct class BitReader: def __init__(self, data): @@ -146,16 +159,18 @@ def unpackBook(input_file): r += decompressSection(i) return r -print "MobiHuff v0.03" -print " Copyright (c) 2008 The Dark Reverser " -if len(sys.argv)!=3: - print "" +def main(argv=sys.argv): + print "MobiHuff v0.03" + print " Copyright (c) 2008 The Dark Reverser " + if len(sys.argv)!=3: + print "" print "Description:" print " Unpacks the new mobipocket huffdic compression." print " This program works with unencrypted files only." print "Usage:" print " mobihuff.py infile.mobi outfile.html" -else: + return 1 + else: infile = sys.argv[1] outfile = sys.argv[2] try: @@ -165,4 +180,10 @@ else: print "done" except ValueError, e: print - print "Error: %s" % e \ No newline at end of file + print "Error: %s" % e + return 1 + return 0 + + +if __name__ == "__main__": + sys.exit(main()) diff --git a/Kindle_Mobi_Tools/lib/scrolltextwidget.py b/Kindle_Mobi_Tools/lib/scrolltextwidget.py new file mode 100644 index 0000000..98b4147 --- /dev/null +++ b/Kindle_Mobi_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/Kindle_Mobi_Tools/lib/subasyncio.py b/Kindle_Mobi_Tools/lib/subasyncio.py new file mode 100644 index 0000000..ed13aa1 --- /dev/null +++ b/Kindle_Mobi_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() +