diff --git a/libprs500/gui/__init__.py b/libprs500/gui/__init__.py index cc149ad4ed..6d06c2ed1e 100644 --- a/libprs500/gui/__init__.py +++ b/libprs500/gui/__init__.py @@ -14,8 +14,8 @@ ## 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. """ The GUI to libprs500. Also has ebook library management features. """ __docformat__ = "epytext" -__author__ = "Kovid Goyal " -APP_TITLE = "libprs500" +__author__ = "Kovid Goyal " +APP_TITLE = "libprs500" import pkg_resources, sys, os, re, StringIO, traceback from PyQt4.uic.Compiler import compiler diff --git a/libprs500/gui/main.ui b/libprs500/gui/main.ui index f3e2ed5562..ee77b0aac9 100644 --- a/libprs500/gui/main.ui +++ b/libprs500/gui/main.ui @@ -87,7 +87,7 @@ - For help visit <a href="http://libprs500.kovidgoyal.net">http://libprs500.kovidgoyal.net</a><br><br><b>libprs500</b> was created by <b>Kovid Goyal</b> &copy; 2006<br>%1 %2 %3 + For help visit <a href="https://libprs500.kovidgoyal.net/wiki/GuiUsage">http://libprs500.kovidgoyal.net</a><br><br><b>libprs500</b> was created by <b>Kovid Goyal</b> &copy; 2006<br>%1 %2 %3 Qt::RichText diff --git a/libprs500/lrf/BBeBook-0.2.jar b/libprs500/lrf/BBeBook-0.2.jar new file mode 100644 index 0000000000..75390c82e9 Binary files /dev/null and b/libprs500/lrf/BBeBook-0.2.jar differ diff --git a/libprs500/lrf/makelrf.py b/libprs500/lrf/makelrf.py new file mode 100755 index 0000000000..0c6f98af98 --- /dev/null +++ b/libprs500/lrf/makelrf.py @@ -0,0 +1,191 @@ +## Copyright (C) 2006 Kovid Goyal kovid@kovidgoyal.net +## This program is free software; you can redistribute it and/or modify +## it under the terms of the GNU General Public License as published by +## the Free Software Foundation; either version 2 of the License, or +## (at your option) any later version. +## +## This program is distributed in the hope that it will be useful, +## but WITHOUT ANY WARRANTY; without even the implied warranty of +## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +## GNU General Public License for more details. +## +## You should have received a copy of the GNU General Public License along +## with this program; if not, write to the Free Software Foundation, Inc., +## 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. +import os +import shutil +import sys +import hashlib +import re +import time +import pkg_resources +import subprocess +from tempfile import mkdtemp +from optparse import OptionParser +import xml.dom.minidom as dom + +from libprs500.lrf.meta import LRFException, LRFMetaFile +from libprs500.ptempfile import PersistentTemporaryFile + +_bbebook = 'BBeBook-0.2.jar' + +def generate_thumbnail(path): + """ Generate a JPEG thumbnail of size ~ 128x128 (aspect ratio preserved)""" + try: + import Image + except ImportError: + raise LRFException("Unable to initialize Python Imaging Library." \ + "Thumbnail generation is disabled") + im = Image.open(path) + im.thumbnail((128, 128), Image.ANTIALIAS) + thumb = PersistentTemporaryFile(prefix="makelrf_", suffix=".jpeg") + thumb.close() + im = im.convert() + im.save(thumb.name) + return thumb + +def create_xml(cfg): + doc = dom.getDOMImplementation().createDocument(None, None, None) + def add_field(parent, tag, value): + elem = doc.createElement(tag) + elem.appendChild(doc.createTextNode(value)) + parent.appendChild(elem) + + info = doc.createElement('Info') + info.setAttribute('version', '1.0') + book_info = doc.createElement('BookInfo') + doc_info = doc.createElement('DocInfo') + info.appendChild(book_info) + info.appendChild(doc_info) + add_field(book_info, 'File', cfg['File']) + add_field(doc_info, 'Output', cfg['Output']) + for field in ['Title', 'Author', 'BookID', 'Publisher', 'Label', \ + 'Category', 'Classification', 'Icon', 'Cover', 'FreeText']: + if cfg.has_key(field): + add_field(book_info, field, cfg[field]) + add_field(doc_info, 'Language', 'en') + add_field(doc_info, 'Creator', _bbebook) + add_field(doc_info, 'CreationDate', time.strftime('%Y-%m-%d', time.gmtime())) + doc.appendChild(info) + return doc.toxml() + +def makelrf(author=None, title=None, \ + thumbnail=None, src=None, odir=".",\ + rasterize=True, cover=None): + src = os.path.normpath(os.path.abspath(src)) + bbebook = pkg_resources.resource_filename(__name__, _bbebook) + if not os.access(src, os.R_OK): + raise LRFException("Unable to read from file: " + src) + if thumbnail: + thumb = os.path.abspath(options.thumbnail) + if not os.access(thumb, os.R_OK): + raise LRFException("Unable to read from " + thumb) + else: + thumb = pkg_resources.resource_filename(__name__, 'cover.jpg') + + if not author: + author = "Unknown" + if not title: + title = os.path.basename(src) + label = os.path.basename(src) + id = hashlib.md5(os.path.basename(label)).hexdigest() + name, ext = os.path.splitext(label) + cwd = os.path.dirname(src) + dirpath = None + try: + if ext == ".rar": + dirpath = mkdtemp('','makelrf') + cwd = dirpath + cmd = " ".join(["unrar", "e", '"'+src+'"']) + proc = subprocess.Popen(cmd, cwd=cwd, shell=True, stderr=subprocess.PIPE) + if proc.wait(): + raise LRFException("unrar failed with error:\n\n" + \ + proc.stderr.read()) + path, msize = None, 0 + for root, dirs, files in os.walk(dirpath): + for name in files: + if os.path.splitext(name)[1] == ".html": + size = os.stat(os.path.join(root, name)).st_size + if size > msize: + msize, path = size, os.path.join(root, name) + if not path: + raise LRFException("Could not find .html file in rar archive") + src = path + + name = re.sub("\s", "_", name) + name = os.path.abspath(os.path.join(odir, name)) + ".lrf" + cfg = { 'File' : src, 'Output' : name, 'Label' : label, 'BookID' : id, \ + 'Author' : author, 'Title' : title, 'Publisher' : 'Unknown' \ + } + + + if cover: + cover = os.path.normpath(os.path.abspath(cover)) + try: + thumbf = generate_thumbnail(cover) + thumb = thumbf.name + except Exception, e: + print >> sys.stderr, "WARNING: Unable to generate thumbnail:\n", \ + str(e) + thumb = cover + cfg['Cover'] = cover + cfg['Icon'] = thumb + config = PersistentTemporaryFile(prefix='makelrf_', suffix='.xml') + config.write(create_xml(cfg)) + config.close() + jar = '-jar "' + bbebook + '"' + cmd = " ".join(["java", jar, "-r" if rasterize else "", '"'+config.name+'"']) + proc = subprocess.Popen(cmd, \ + cwd=cwd, shell=True, stderr=subprocess.PIPE) + if proc.wait(): + raise LRFException("BBeBook failed with error:\n\n" + \ + proc.stderr.read()) + # Needed as BBeBook-0.2 doesn't handle non GIF thumbnails correctly. + lrf = open(name, "r+b") + LRFMetaFile(lrf).fix_thumbnail_type() + lrf.close() + return name + finally: + if dirpath: + shutil.rmtree(dirpath, True) + +def main(cargs=None): + parser = OptionParser(usage=\ + """usage: %prog [options] mybook.[html|pdf|rar] + + %prog converts mybook to mybook.lrf + If you specify a rar file you must have the unrar command line client + installed. makelrf assumes the rar file is an archive containing the + html file you want converted."""\ + ) + + parser.add_option("-t", "--title", action="store", type="string", \ + dest="title", help="Set the book title") + parser.add_option("-a", "--author", action="store", type="string", \ + dest="author", help="Set the author") + parser.add_option('-r', '--rasterize', action='store_true', \ + dest="rasterize", + help="Convert pdfs into image files.") + parser.add_option('-c', '--cover', action='store', dest='cover',\ + help="Path to a graphic that will be set as the cover. "\ + "If it is specified the thumbnail is automatically "\ + "generated from it") + parser.add_option("--thumbnail", action="store", type="string", \ + dest="thumbnail", \ + help="Path to a graphic that will be set as the thumbnail") + if not cargs: + cargs = sys.argv + options, args = parser.parse_args() + if len(args) != 1: + parser.print_help() + sys.exit(1) + src = args[0] + root, ext = os.path.splitext(src) + if ext not in ['.html', '.pdf', '.rar']: + print >> sys.stderr, "Can only convert files ending in .html|.pdf|.rar" + parser.print_help() + sys.exit(1) + name = makelrf(author=options.author, title=options.title, \ + thumbnail=options.thumbnail, src=src, cover=options.cover, \ + rasterize=options.rasterize) + print "LRF generated:", name diff --git a/libprs500/lrf/meta.py b/libprs500/lrf/meta.py index 43e0795499..ff4522d0c2 100644 --- a/libprs500/lrf/meta.py +++ b/libprs500/lrf/meta.py @@ -155,7 +155,7 @@ class xml_field(object): class LRFMetaFile(object): """ Has properties to read and write all Meta information in a LRF file. """ # The first 8 bytes of all valid LRF files - LRF_HEADER = u'LRF'.encode('utf-16')[2:]+'\0\0' + LRF_HEADER = 'LRF'.encode('utf-16le')+'\0\0' lrf_header = fixed_stringfield(length=8, start=0) version = field(fmt=WORD, start=8) @@ -281,6 +281,19 @@ class LRFMetaFile(object): return self.info_start+ self.compressed_info_size-4 return { "fget":fget, "doc":doc } + @classmethod + def _detect_thumbnail_type(cls, slice): + """ @param slice: The first 16 bytes of the thumbnail """ + ttype = 0x14 # GIF + if "PNG" in slice: + ttype = 0x12 + if "BM" in slice: + ttype = 0x13 + if "JFIF" in slice: + ttype = 0x11 + return ttype + + @safe_property def thumbnail(): doc = \ @@ -299,6 +312,7 @@ class LRFMetaFile(object): if self.version <= 800: raise LRFException("Cannot store thumbnails in LRF files \ of version <= 800") + slice = data[0:16] orig_size = self.thumbnail_size self._file.seek(self.toc_object_offset) toc = self._file.read(self.object_index_offset - self.toc_object_offset) @@ -314,14 +328,7 @@ class LRFMetaFile(object): self._file.write(objects) self._file.flush() self._file.truncate() # Incase old thumbnail was bigger than new - ttype = 0x14 - if data[1:4] == "PNG": - ttype = 0x12 - if data[0:2] == "BM": - ttype = 0x13 - if data[0:4] == "JIFF": - ttype = 0x11 - self.thumbnail_type = ttype + self.thumbnail_type = self._detect_thumbnail_type(slice) # Needed as new thumbnail may have different size than old thumbnail self.update_object_offsets(self.toc_object_offset - orig_offset) return { "fget":fget, "fset":fset, "doc":doc } @@ -392,6 +399,11 @@ class LRFMetaFile(object): self._file.flush() def thumbail_extension(self): + """ + Return the extension for the thumbnail image type as specified + by L{self.thumbnail_type}. If the LRF file was created by buggy + software, the extension maye be incorrect. See L{self.fix_thumbnail_type}. + """ ext = "gif" ttype = self.thumbnail_type if ttype == 0x11: @@ -401,6 +413,15 @@ class LRFMetaFile(object): elif ttype == 0x13: ext = "bm" return ext + + def fix_thumbnail_type(self): + """ + Attempt to guess the thumbnail image format and set + L{self.thumbnail_type} accordingly. + """ + slice = self.thumbnail[0:16] + self.thumbnail_type = self._detect_thumbnail_type(slice) + def main(): import sys, os.path diff --git a/libprs500/ptempfile.py b/libprs500/ptempfile.py index 8cca86fb95..5a8467720f 100644 --- a/libprs500/ptempfile.py +++ b/libprs500/ptempfile.py @@ -56,4 +56,4 @@ def PersistentTemporaryFile(suffix="", prefix=""): prefix = "" fd, name = tempfile.mkstemp(suffix, "libprs500_"+ __version__+"_" + prefix) _file = os.fdopen(fd, "wb") - return _TemporaryFileWrapper(_file, name) + return _TemporaryFileWrapper(_file, name) diff --git a/prs-500.e4p b/prs-500.e4p index a8a809c216..201a4e2ab6 100644 --- a/prs-500.e4p +++ b/prs-500.e4p @@ -1,8 +1,8 @@ - - + + Python Qt4 @@ -84,6 +84,15 @@ gui widgets.py + + libprs500 + lrf + makelrf.py + + + libprs500 + ptempfile.py +
@@ -230,10 +239,10 @@ - - + + diff --git a/setup.py b/setup.py index 6f6f6335ee..a65e2b4ae6 100644 --- a/setup.py +++ b/setup.py @@ -13,18 +13,25 @@ ## with this program; if not, write to the Free Software Foundation, Inc., ## 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. #!/usr/bin/env python2.5 +import sys + import ez_setup ez_setup.use_setuptools() -# Try to install the Python imaging library as the package name (PIL) doesn't match the distributionfile name, thus declaring itas a dependency is useless -#from setuptools.command.easy_install import main as easy_install -#try: -# try: -# import Image -# except ImportError: -# print "Trying to install thePython Imaging Library" -# easy_install(["-f", "http://www.pythonware.com/products/pil/", "Imaging"]) -#except: pass +# Try to install the Python imaging library as the package name (PIL) doesn't +# match the distribution file name, thus declaring itas a dependency is useless +from setuptools.command.easy_install import main as easy_install +try: + try: + import Image + except ImportError: + print "Trying to install the Python Imaging Library" + easy_install(["-f", "http://www.pythonware.com/products/pil/", "Imaging"]) +except Exception, e: + print >> sys.stderr, e + print >> sys.stderr, \ + "WARNING: Could not install the Python Imaging Library.", \ + "Some functionality will be unavailable" import sys from setuptools import setup, find_packages @@ -43,11 +50,15 @@ setup( author='Kovid Goyal', author_email='kovid@kovidgoyal.net', url = 'http://libprs500.kovidgoyal.net', - package_data = { 'libprs500.gui' : ['*.ui'] }, + package_data = { \ + 'libprs500.gui' : ['*.ui'], \ + 'libprs500.lrf' : ['*.jar', '*.jpg'] \ + }, entry_points = { 'console_scripts': [ \ - 'prs500 = libprs500.cli.main:main', \ - 'lrf-meta = libprs500.lrf.meta:main' \ + 'prs500 = libprs500.cli.main:main', \ + 'lrf-meta = libprs500.lrf.meta:main', \ + 'makelrf = libprs500.lrf.makelrf:main'\ ], 'gui_scripts' : [ 'prs500-gui = libprs500.gui.main:main'] },