tools v2.2
This commit is contained in:
17
ePub_Fixer/README_ePub_Fixer.txt
Normal file
17
ePub_Fixer/README_ePub_Fixer.txt
Normal file
@@ -0,0 +1,17 @@
|
||||
ePub_Fixer
|
||||
|
||||
ePubs are specially crafted zip archives. Unfortunately, many of te DRM encoded Adobe Adept and Barnes & Noble ePubs are not "proper" zip archives in that the names of some files in the zip central directory do NOT match the local name given in archive itself. This type of zip archive is technically incorrect/corrupted and can not be read by many other programs.
|
||||
|
||||
ePub_Fixer was designed to fix improperly created zip archives of this type.
|
||||
|
||||
1. Simply double-click to launch ePub_Fixer.pyw.
|
||||
|
||||
2. use the first "..." button to select the ePub (with DRM) that needs to be fixed
|
||||
|
||||
3. use the second "..." button to select where you want the fixed ePub (still with DRM!) to be placed.
|
||||
|
||||
4. Hit the start button.
|
||||
|
||||
Once the program has successfully completed, you can now feed the fixed ePubs into other programs such as DRM removal programs like ignobleepub.pyw and ineptepub.pyw.
|
||||
|
||||
|
||||
189
ePub_Fixer/ePub_Fixer.pyw
Normal file
189
ePub_Fixer/ePub_Fixer.pyw
Normal file
@@ -0,0 +1,189 @@
|
||||
#!/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 Improper ePubs')
|
||||
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='ePub input file').grid(row=0, sticky=Tkconstants.E)
|
||||
self.epubpath = Tkinter.Entry(body, width=50)
|
||||
self.epubpath.grid(row=0, column=1, sticky=sticky)
|
||||
cwd = os.getcwdu()
|
||||
cwd = cwd.encode('utf-8')
|
||||
self.epubpath.insert(0, cwd)
|
||||
button = Tkinter.Button(body, text="...", command=self.get_epubpath)
|
||||
button.grid(row=0, column=2)
|
||||
|
||||
Tkinter.Label(body, text='Directory to store fixed ePub in').grid(row=1, sticky=Tkconstants.E)
|
||||
self.outpath = Tkinter.Entry(body, width=50)
|
||||
self.outpath.grid(row=1, column=1, sticky=sticky)
|
||||
cwd = os.getcwdu()
|
||||
cwd = cwd.encode('utf-8')
|
||||
outname = cwd
|
||||
self.outpath.insert(0, outname)
|
||||
button = Tkinter.Button(body, text="...", command=self.get_outpath)
|
||||
button.grid(row=1, column=2)
|
||||
|
||||
msg1 = '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.fixit)
|
||||
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' + 'ePub successfully fixed\n'
|
||||
if poll != 0:
|
||||
msg = text + '\n\n' + 'Error: ePub Fixing Failed\n'
|
||||
self.showCmdOutput(msg)
|
||||
self.p2 = None
|
||||
self.sbotton.configure(state='normal')
|
||||
return
|
||||
text = self.p2.readerr()
|
||||
text += self.p2.read()
|
||||
self.showCmdOutput(text)
|
||||
# make sure we get invoked again by event loop after interval
|
||||
self.stext.after(self.interval,self.processPipe)
|
||||
return
|
||||
|
||||
# post output from subprocess in scrolled text widget
|
||||
def showCmdOutput(self, msg):
|
||||
if msg and msg !='':
|
||||
msg = msg.encode('utf-8')
|
||||
self.stext.insert(Tkconstants.END,msg)
|
||||
self.stext.yview_pickplace(Tkconstants.END)
|
||||
return
|
||||
|
||||
# run as a subprocess via pipes and collect stdout
|
||||
def zipfixrdr(self, infile, outfile):
|
||||
# os.putenv('PYTHONUNBUFFERED', '1')
|
||||
cmdline = 'python ./lib/zipfix.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\zipfix.py "' + infile + '" "' + outfile + '"'
|
||||
else :
|
||||
cmdline = 'lib\zipfix.py "' + infile + '" "' + outfile + '"'
|
||||
|
||||
cmdline = cmdline.encode(sys.getfilesystemencoding())
|
||||
p2 = Process(cmdline, shell=True, bufsize=1, stdin=None, stdout=PIPE, stderr=PIPE, close_fds=False)
|
||||
return p2
|
||||
|
||||
|
||||
def get_epubpath(self):
|
||||
epubpath = tkFileDialog.askopenfilename(
|
||||
parent=None, title='Select ePub to be Fixed',
|
||||
defaultextension='.epub', filetypes=[('ePub eBook File', '.epub'), ('Zip File', '.zip'),('All Files', '.*')])
|
||||
if epubpath:
|
||||
epubpath = os.path.normpath(epubpath)
|
||||
self.epubpath.delete(0, Tkconstants.END)
|
||||
self.epubpath.insert(0, epubpath)
|
||||
return
|
||||
|
||||
def get_outpath(self):
|
||||
cwd = os.getcwdu()
|
||||
cwd = cwd.encode('utf-8')
|
||||
outpath = tkFileDialog.askdirectory(
|
||||
parent=None, title='Directory to Store Fixed ePub into',
|
||||
initialdir=cwd, 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 fixit(self):
|
||||
# now disable the button to prevent multiple launches
|
||||
self.sbotton.configure(state='disabled')
|
||||
epubpath = self.epubpath.get()
|
||||
outpath = self.outpath.get()
|
||||
if not epubpath or not os.path.exists(epubpath):
|
||||
self.status['text'] = 'Specified ePub eBook file does not exist'
|
||||
self.sbotton.configure(state='normal')
|
||||
return
|
||||
if not outpath:
|
||||
self.status['text'] = 'Error specified output directory does not exist'
|
||||
self.sbotton.configure(state='normal')
|
||||
return
|
||||
|
||||
# default output file name to be input file name + '_fixed.epub'
|
||||
initname = os.path.splitext(os.path.basename(epubpath))[0]
|
||||
initname += '_fixed.epub'
|
||||
outpath += os.sep + initname
|
||||
|
||||
log = 'Command = "python zipfix.py"\n'
|
||||
log += 'ePub Path = "'+ epubpath + '"\n'
|
||||
log += 'Output File = "' + outpath + '"\n'
|
||||
log += '\n\n'
|
||||
log += 'Please Wait ...\n\n'
|
||||
log = log.encode('utf-8')
|
||||
self.stext.insert(Tkconstants.END,log)
|
||||
self.p2 = self.zipfixrdr(epubpath, 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('Fix Incorrect ePubs')
|
||||
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())
|
||||
27
ePub_Fixer/lib/scrolltextwidget.py
Normal file
27
ePub_Fixer/lib/scrolltextwidget.py
Normal file
@@ -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)
|
||||
149
ePub_Fixer/lib/subasyncio.py
Normal file
149
ePub_Fixer/lib/subasyncio.py
Normal file
@@ -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()
|
||||
|
||||
136
ePub_Fixer/lib/zipfix.py
Normal file
136
ePub_Fixer/lib/zipfix.py
Normal file
@@ -0,0 +1,136 @@
|
||||
#!/usr/bin/env python
|
||||
|
||||
import sys
|
||||
import zlib
|
||||
import zipfile
|
||||
import os
|
||||
import os.path
|
||||
import getopt
|
||||
from struct import unpack
|
||||
|
||||
|
||||
_FILENAME_LEN_OFFSET = 26
|
||||
_EXTRA_LEN_OFFSET = 28
|
||||
_FILENAME_OFFSET = 30
|
||||
_MAX_SIZE = 64 * 1024
|
||||
|
||||
class fixZip:
|
||||
def __init__(self, zinput, zoutput):
|
||||
self.inzip = zipfile.ZipFile(zinput,'r')
|
||||
self.outzip = zipfile.ZipFile(zoutput,'w')
|
||||
# open the input zip for reading only as a raw file
|
||||
self.bzf = file(zinput,'rb')
|
||||
|
||||
def getlocalname(self, zi):
|
||||
local_header_offset = zi.header_offset
|
||||
self.bzf.seek(local_header_offset + _FILENAME_LEN_OFFSET)
|
||||
leninfo = self.bzf.read(2)
|
||||
local_name_length, = unpack('<H', leninfo)
|
||||
self.bzf.seek(local_header_offset + _FILENAME_OFFSET)
|
||||
local_name = self.bzf.read(local_name_length)
|
||||
return local_name
|
||||
|
||||
def uncompress(self, cmpdata):
|
||||
dc = zlib.decompressobj(-15)
|
||||
data = ''
|
||||
while len(cmpdata) > 0:
|
||||
if len(cmpdata) > _MAX_SIZE :
|
||||
newdata = cmpdata[0:_MAX_SIZE]
|
||||
cmpdata = cmpdata[_MAX_SIZE:]
|
||||
else:
|
||||
newdata = cmpdata
|
||||
cmpdata = ''
|
||||
newdata = dc.decompress(newdata)
|
||||
unprocessed = dc.unconsumed_tail
|
||||
if len(unprocessed) == 0:
|
||||
newdata += dc.flush()
|
||||
data += newdata
|
||||
cmpdata += unprocessed
|
||||
unprocessed = ''
|
||||
return data
|
||||
|
||||
def getfiledata(self, zi):
|
||||
# get file name length and exta data length to find start of file data
|
||||
local_header_offset = zi.header_offset
|
||||
|
||||
self.bzf.seek(local_header_offset + _FILENAME_LEN_OFFSET)
|
||||
leninfo = self.bzf.read(2)
|
||||
local_name_length, = unpack('<H', leninfo)
|
||||
|
||||
self.bzf.seek(local_header_offset + _EXTRA_LEN_OFFSET)
|
||||
exinfo = self.bzf.read(2)
|
||||
extra_field_length, = unpack('<H', exinfo)
|
||||
|
||||
self.bzf.seek(local_header_offset + _FILENAME_OFFSET + local_name_length + extra_field_length)
|
||||
data = None
|
||||
|
||||
# if not compressed we are good to go
|
||||
if zi.compress_type == zipfile.ZIP_STORED:
|
||||
data = self.bzf.read(zi.file_size)
|
||||
|
||||
# if compressed we must decompress it using zlib
|
||||
if zi.compress_type == zipfile.ZIP_DEFLATED:
|
||||
cmpdata = self.bzf.read(zi.compress_size)
|
||||
data = self.uncompress(cmpdata)
|
||||
|
||||
return data
|
||||
|
||||
|
||||
|
||||
def fix(self):
|
||||
# get the zipinfo for each member of the input archive
|
||||
# and copy member over to output archive
|
||||
# if problems exist with local vs central filename, fix them
|
||||
|
||||
for i, zinfo in enumerate(self.inzip.infolist()):
|
||||
data = None
|
||||
nzinfo = zinfo
|
||||
|
||||
try:
|
||||
data = self.inzip.read(zinfo)
|
||||
except zipfile.BadZipfile or zipfile.error:
|
||||
local_name = self.getlocalname(zinfo)
|
||||
data = self.getfiledata(zinfo)
|
||||
nzinfo.filename = local_name
|
||||
|
||||
nzinfo.date_time = zinfo.date_time
|
||||
nzinfo.compress_type = zinfo.compress_type
|
||||
nzinfo.flag_bits = 0
|
||||
nzinfo.internal_attr = 0
|
||||
self.outzip.writestr(nzinfo,data)
|
||||
|
||||
self.bzf.close()
|
||||
self.inzip.close()
|
||||
self.outzip.close()
|
||||
|
||||
|
||||
def usage():
|
||||
print """usage: zipfix.py inputzip outputzip
|
||||
inputzip is the source zipfile to fix
|
||||
outputzip is the fixed zip archive
|
||||
"""
|
||||
|
||||
|
||||
def main(argv=sys.argv):
|
||||
if len(argv)!=3:
|
||||
usage()
|
||||
return 1
|
||||
infile = None
|
||||
outfile = None
|
||||
infile = argv[1]
|
||||
outfile = argv[2]
|
||||
if not os.path.exists(infile):
|
||||
print "Error: Input Zip File does not exist"
|
||||
return 1
|
||||
try:
|
||||
fr = fixZip(infile, outfile)
|
||||
fr.fix()
|
||||
return 0
|
||||
except Exception, e:
|
||||
print "Error Occurred ", e
|
||||
return 2
|
||||
|
||||
if __name__ == '__main__' :
|
||||
sys.exit(main())
|
||||
|
||||
|
||||
Reference in New Issue
Block a user