From 1dd7eac1a7fb5665180f5446fc995f9a09691ccc Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Wed, 7 Jan 2009 20:27:51 -0800 Subject: [PATCH 01/10] IGN:Print tracebacks in plugins --- src/calibre/customize/ui.py | 1 + 1 file changed, 1 insertion(+) diff --git a/src/calibre/customize/ui.py b/src/calibre/customize/ui.py index 067185b0c3..c79b4ed98e 100644 --- a/src/calibre/customize/ui.py +++ b/src/calibre/customize/ui.py @@ -127,6 +127,7 @@ def get_file_type_metadata(stream, ftype): mi = plugin.get_metadata(stream, ftype.lower().strip()) break except: + traceback.print_exc() continue return mi From e1624601895f12e4596476304f3616d74b9574e8 Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Thu, 8 Jan 2009 06:57:41 -0800 Subject: [PATCH 02/10] First attempt at pdfcropping tool --- src/calibre/ebooks/pdf/pdftrim.py | 85 +++++++++++++++++++++++++++++++ src/calibre/linux.py | 1 + 2 files changed, 86 insertions(+) create mode 100644 src/calibre/ebooks/pdf/pdftrim.py diff --git a/src/calibre/ebooks/pdf/pdftrim.py b/src/calibre/ebooks/pdf/pdftrim.py new file mode 100644 index 0000000000..fada1717ed --- /dev/null +++ b/src/calibre/ebooks/pdf/pdftrim.py @@ -0,0 +1,85 @@ +from __future__ import with_statement +__license__ = 'GPL v3' +__copyright__ = '2009, James Beal, james_@catbus.co.uk' +__docformat__ = 'restructuredtext en' + +'crop a pdf file' + +import os, sys, shutil, re +from calibre.utils.config import Config, StringConfig +from pyPdf import PdfFileWriter, PdfFileReader + +def config(defaults=None): + desc = _('Options to control the transformation of pdf') + default_crop=10 + if defaults is None: + c = Config('trimpdf', desc) + else: + c = StringConfig(defaults, desc) + c.add_opt('verbose', ['-v', '--verbose'], default=0, action='count', + help=_('Be verbose, useful for debugging. Can be specified multiple times for greater verbosity.')) + c.add_opt('title', ['-t', '--title'], + help=_('Title for generated ebook. Default is to not change.')) + c.add_opt('author', ['-a', '--author'], + help=_('Set the author in the metadata of the generated ebook. Default is not to change')) + c.add_opt('output', ['-o', '--output'],default='cropped.pdf', + help=_('Path to output file. By default a file is created in the current directory.')) + c.add_opt('bottom_left_x', [ '-x', '--leftx'], default=default_crop, + help=_('Number of pixels to crop from the left most x (default is %d) ')%default_crop ) + c.add_opt('bottom_left_y', [ '-y', '--lefty'], default=default_crop, + help=_('Number of pixels to crop from the left most y (default is %d) ')%default_crop ) + c.add_opt('top_right_x', [ '-v', '--rightx'], default=default_crop, + help=_('Number of pixels to crop from the right most x (default is %d) ')%default_crop ) + c.add_opt('top_right_y', [ '-w', '--righty'], default=default_crop, + help=_('Number of pixels to crop from the right most y (default is %d)')%default_crop ) + c.add_opt('bounding', ['-b', '--bounding'], + help=_('A file generated by ghostscript which allows each page to be individuall cropped')) + return c + + +def option_parser(): + c = config() + return c.option_parser(usage=_('''\ + %prog [options] file.pdf + + Crop a pdf. + ''')) + +def main(args=sys.argv): + parser = option_parser() + opts, args = parser.parse_args(args) + source = os.path.abspath(args[1]) + output_pdf = PdfFileWriter() + input_pdf = PdfFileReader(file(source, "rb")) + if opts.bounding != None: + try: + bounding = open( opts.bounding , 'r' ) + bounding_regex= re.compile('%%BoundingBox: (?P[0-9]+) (?P[0-9]+) (?P[0-9]+) (?P[0-9]+)') + except: + print 'Error opening %s' % opts.bounding + return 1 + print opts.bounding + for page_number in range (0, input_pdf.getNumPages() ): + page = input_pdf.getPage(page_number) + if opts.bounding != None: + while True: + line=bounding.readline() + match=bounding_regex.search(line) + if match !=None: + break + page.mediaBox.upperRight = (match.group('top_x'),match.group('top_y')) + page.mediaBox.lowerLeft = (match.group('bottom_x'),match.group('bottom_y')) + else: + page.mediaBox.upperRight = (page.bleedBox.getUpperRight_x()-opts.top_right_x,page.bleedBox.getUpperRight_y()-opts.top_right_y) + page.mediaBox.lowerLeft = (page.bleedBox.getLowerLeft_x()+opts.bottom_left_x,page.bleedBox.getLowerLeft_y()+opts.bottom_left_y) + output_pdf.addPage(page) + if opts.bounding != None: + bounding.close() + output_file = file(opts.output, "wb") + output_pdf.write(output_file) + output_file.close() + + return 0 + +if __name__ == '__main__': + sys.exit(main()) diff --git a/src/calibre/linux.py b/src/calibre/linux.py index a9d91282e5..cef2e5ddb7 100644 --- a/src/calibre/linux.py +++ b/src/calibre/linux.py @@ -65,6 +65,7 @@ entry_points = { 'calibre-fontconfig = calibre.utils.fontconfig:main', 'calibre-parallel = calibre.parallel:main', 'calibre-customize = calibre.customize.ui:main', + 'pdftrim = calibre.ebooks.pdf.pdftrim:main' , ], 'gui_scripts' : [ __appname__+' = calibre.gui2.main:main', From cfb275598b41fae4d6193dc023fc1fc69385f763 Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Thu, 8 Jan 2009 10:08:26 -0800 Subject: [PATCH 03/10] IGN:Make detection of devices based on BCD optional --- src/calibre/devices/interface.py | 4 +++- src/calibre/devices/prs500/driver.py | 2 +- src/calibre/devices/prs505/driver.py | 2 +- src/calibre/devices/prs700/driver.py | 2 +- src/calibre/devices/scanner.py | 29 +++++++++++++++++++++------- 5 files changed, 28 insertions(+), 11 deletions(-) diff --git a/src/calibre/devices/interface.py b/src/calibre/devices/interface.py index e2959cd49f..85d25d82a4 100644 --- a/src/calibre/devices/interface.py +++ b/src/calibre/devices/interface.py @@ -20,7 +20,9 @@ class Device(object): FORMATS = ["lrf", "rtf", "pdf", "txt"] VENDOR_ID = 0x0000 PRODUCT_ID = 0x0000 - BCD = 0x0000 + # BCD can be either None to not distinguish between devices based on BCD, or + # it can be a list of the BCD numbers of all devices supported by this driver. + BCD = None THUMBNAIL_HEIGHT = 68 # Height for thumbnails on device def __init__(self, key='-1', log_packets=False, report_progress=None) : diff --git a/src/calibre/devices/prs500/driver.py b/src/calibre/devices/prs500/driver.py index 177f57b15f..232d2c758c 100755 --- a/src/calibre/devices/prs500/driver.py +++ b/src/calibre/devices/prs500/driver.py @@ -85,7 +85,7 @@ class PRS500(Device): VENDOR_ID = 0x054c #: SONY Vendor Id PRODUCT_ID = 0x029b #: Product Id for the PRS-500 - BCD = 0x100 + BCD = [0x100] PRODUCT_NAME = 'PRS-500' VENDOR_NAME = 'SONY' INTERFACE_ID = 0 #: The interface we use to talk to the device diff --git a/src/calibre/devices/prs505/driver.py b/src/calibre/devices/prs505/driver.py index 0f60d7238b..61004ca005 100644 --- a/src/calibre/devices/prs505/driver.py +++ b/src/calibre/devices/prs505/driver.py @@ -29,7 +29,7 @@ class File(object): class PRS505(Device): VENDOR_ID = 0x054c #: SONY Vendor Id PRODUCT_ID = 0x031e #: Product Id for the PRS-505 - BCD = 0x229 #: Needed to disambiguate 505 and 700 on linux + BCD = [0x229] #: Needed to disambiguate 505 and 700 on linux PRODUCT_NAME = 'PRS-505' VENDOR_NAME = 'SONY' FORMATS = ['lrf', 'epub', "rtf", "pdf", "txt"] diff --git a/src/calibre/devices/prs700/driver.py b/src/calibre/devices/prs700/driver.py index 812ac0f911..5db60ef506 100644 --- a/src/calibre/devices/prs700/driver.py +++ b/src/calibre/devices/prs700/driver.py @@ -9,7 +9,7 @@ from calibre.devices.prs505.driver import PRS505 class PRS700(PRS505): - BCD = 0x31a + BCD = [0x31a] PRODUCT_NAME = 'PRS-700' OSX_NAME = 'Sony PRS-700' diff --git a/src/calibre/devices/scanner.py b/src/calibre/devices/scanner.py index 40573114a7..3487987d67 100644 --- a/src/calibre/devices/scanner.py +++ b/src/calibre/devices/scanner.py @@ -39,20 +39,35 @@ class DeviceScanner(object): '''Fetch list of connected USB devices from operating system''' self.devices = self.scanner() + def test_bcd_windows(self, device_id, bcd): + if bcd is None or len(bcd) == 0: + return True + for c in bcd: + # Bug in winutil.get_usb_devices converts a to : + rev = ('rev_%4.4x'%c).replace(':', 'a') + if rev in device_id: + return True + return False + + def test_bcd(self, bcdDevice, bcd): + if bcd is None or len(bcd) == 0: + return True + for c in bcd: + if c == bcdDevice: + return True + return False + def is_device_connected(self, device): if iswindows: for device_id in self.devices: vid, pid = 'vid_%4.4x'%device.VENDOR_ID, 'pid_%4.4x'%device.PRODUCT_ID - rev = ('rev_%4.4x'%device.BCD).replace('a', ':') # Bug in winutil.get_usb_devices converts a to : - if vid in device_id and pid in device_id and rev in device_id: - return True - return False + if vid in device_id and pid in device_id: + return self.test_bcd_windows(device_id, getattr(device, 'BCD', None)) else: for vendor, product, bcdDevice in self.devices: if device.VENDOR_ID == vendor and device.PRODUCT_ID == product: - if hasattr(device, 'BCD') and device.BCD == bcdDevice: - return True - return False + return self.test_bcd(bcdDevice, getattr(device, 'BCD', None)) + return False def main(args=sys.argv): From 03a704403d739028dd3927e87c60c3c22bbbfdfa Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Thu, 8 Jan 2009 10:28:37 -0800 Subject: [PATCH 04/10] Fix #1569 (browser /calibre-server conflict?) --- src/calibre/library/server.py | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/src/calibre/library/server.py b/src/calibre/library/server.py index 1fcd824a5f..383612805e 100644 --- a/src/calibre/library/server.py +++ b/src/calibre/library/server.py @@ -7,7 +7,7 @@ __docformat__ = 'restructuredtext en' HTTP server for remote access to the calibre database. ''' -import sys, textwrap, cStringIO, mimetypes, operator, os, re, logging +import sys, textwrap, mimetypes, operator, os, re, logging from itertools import repeat from logging.handlers import RotatingFileHandler from datetime import datetime @@ -285,7 +285,8 @@ class LibraryServer(object): updated=updated, id='urn:calibre:main').render('xml') @expose - def library(self, start='0', num='50', sort=None, search=None, _=None, order='ascending'): + def library(self, start='0', num='50', sort=None, search=None, + _=None, order='ascending'): ''' Serves metadata from the calibre database as XML. @@ -321,7 +322,7 @@ class LibraryServer(object): total=len(ids)).render('xml') @expose - def index(self): + def index(self, **kwargs): 'The / URL' return self.static('index.html') @@ -357,7 +358,8 @@ class LibraryServer(object): '' : 'application/octet-stream', }[name.rpartition('.')[-1].lower()] cherrypy.response.headers['Last-Modified'] = self.last_modified(build_time) - if self.opts.develop and name in ('gui.js', 'gui.css', 'index.html'): + if self.opts.develop and not getattr(sys, 'frozen', False) and \ + name in ('gui.js', 'gui.css', 'index.html'): path = os.path.join(os.path.dirname(__file__), 'static', name) lm = datetime.fromtimestamp(os.stat(path).st_mtime) cherrypy.response.headers['Last-Modified'] = self.last_modified(lm) From 1f05c41c7a7e57565cfa3afb6c457c1892b2e4ad Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Thu, 8 Jan 2009 10:35:06 -0800 Subject: [PATCH 05/10] Fix #1572 (View window pop-under) --- src/calibre/gui2/lrf_renderer/main.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/calibre/gui2/lrf_renderer/main.py b/src/calibre/gui2/lrf_renderer/main.py index 0b746777bf..f080022415 100644 --- a/src/calibre/gui2/lrf_renderer/main.py +++ b/src/calibre/gui2/lrf_renderer/main.py @@ -314,6 +314,8 @@ def main(args=sys.argv, logger=None): sys.excepthook = main.unhandled_exception main.show() main.render() + main.activateWindow() + main.raise_() return app.exec_() return 0 From 33f07eedc8f1cb9b80c4166aed2d32ec45550a6f Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Thu, 8 Jan 2009 11:15:33 -0800 Subject: [PATCH 06/10] Read metadata from ebooks in zip archives. Fixes #1570 (FB2 metadata from ZIP archive) --- src/calibre/customize/builtins.py | 21 +++++++++++++++++++++ src/calibre/ebooks/metadata/zip.py | 24 ++++++++++++++++++++++++ 2 files changed, 45 insertions(+) create mode 100644 src/calibre/ebooks/metadata/zip.py diff --git a/src/calibre/customize/builtins.py b/src/calibre/customize/builtins.py index 28be488dce..9ca7ae590d 100644 --- a/src/calibre/customize/builtins.py +++ b/src/calibre/customize/builtins.py @@ -25,6 +25,17 @@ every time you add an HTML file to the library.\ html2oeb(htmlfile, of) return of.name +class OPFMetadataReader(MetadataReaderPlugin): + + name = 'Read OPF metadata' + file_types = set(['opf']) + description = _('Read metadata from %s files')%'OPF' + + def get_metadata(self, stream, ftype): + from calibre.ebooks.metadata.opf2 import OPF + from calibre.ebooks.metadata import MetaInformation + return MetaInformation(OPF(stream, os.getcwd())) + class RTFMetadataReader(MetadataReaderPlugin): name = 'Read RTF metadata' @@ -167,6 +178,16 @@ class ComicMetadataReader(MetadataReaderPlugin): ext = os.path.splitext(path)[1][1:] mi.cover_data = (ext.lower(), data) return mi + +class ZipMetadataReader(MetadataReaderPlugin): + + name = 'Read ZIP metadata' + file_types = set(['zip', 'oebzip']) + description = _('Read metadata from ebooks in ZIP archives') + + def get_metadata(self, stream, ftype): + from calibre.ebooks.metadata.zip import get_metadata + return get_metadata(stream) class EPUBMetadataWriter(MetadataWriterPlugin): diff --git a/src/calibre/ebooks/metadata/zip.py b/src/calibre/ebooks/metadata/zip.py new file mode 100644 index 0000000000..441aa7e3da --- /dev/null +++ b/src/calibre/ebooks/metadata/zip.py @@ -0,0 +1,24 @@ +from __future__ import with_statement +__license__ = 'GPL v3' +__copyright__ = '2008, Kovid Goyal ' + +import os +from zipfile import ZipFile +from cStringIO import StringIO + + +def get_metadata(stream): + stream_type = None + zf = ZipFile(stream, 'r') + for f in zf.namelist(): + stream_type = os.path.splitext(f)[1].lower() + if stream_type: + stream_type = stream_type[1:] + if stream_type in ('lit', 'opf', 'prc', 'mobi', 'fb2', 'epub', + 'rb', 'imp', 'pdf', 'lrf'): + from calibre.ebooks.metadata.meta import get_metadata + stream = StringIO(zf.read(f)) + return get_metadata(stream, stream_type) + raise ValueError('No ebook found in ZIP archive') + + \ No newline at end of file From 764cf7b8d187859a94b06ab26e06bffe3ef445a2 Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Thu, 8 Jan 2009 11:34:36 -0800 Subject: [PATCH 07/10] IGN:... --- src/calibre/devices/scanner.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/calibre/devices/scanner.py b/src/calibre/devices/scanner.py index 3487987d67..9919648d94 100644 --- a/src/calibre/devices/scanner.py +++ b/src/calibre/devices/scanner.py @@ -62,11 +62,13 @@ class DeviceScanner(object): for device_id in self.devices: vid, pid = 'vid_%4.4x'%device.VENDOR_ID, 'pid_%4.4x'%device.PRODUCT_ID if vid in device_id and pid in device_id: - return self.test_bcd_windows(device_id, getattr(device, 'BCD', None)) + if self.test_bcd_windows(device_id, getattr(device, 'BCD', None)): + return True else: for vendor, product, bcdDevice in self.devices: if device.VENDOR_ID == vendor and device.PRODUCT_ID == product: - return self.test_bcd(bcdDevice, getattr(device, 'BCD', None)) + if self.test_bcd(bcdDevice, getattr(device, 'BCD', None)): + return True return False From 6e12b3e711ec09997c44b48ebb7eea17e46cf186 Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Thu, 8 Jan 2009 11:36:04 -0800 Subject: [PATCH 08/10] IGN:... --- src/calibre/devices/scanner.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/calibre/devices/scanner.py b/src/calibre/devices/scanner.py index 9919648d94..5fab1be2f6 100644 --- a/src/calibre/devices/scanner.py +++ b/src/calibre/devices/scanner.py @@ -44,7 +44,7 @@ class DeviceScanner(object): return True for c in bcd: # Bug in winutil.get_usb_devices converts a to : - rev = ('rev_%4.4x'%c).replace(':', 'a') + rev = ('rev_%4.4x'%c).replace('a', ':') if rev in device_id: return True return False From 8e012bd5294790099df8c24953266d63bba8e4c4 Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Thu, 8 Jan 2009 11:56:05 -0800 Subject: [PATCH 09/10] Trying and failing to set metadata on trimed pdfs --- src/calibre/ebooks/pdf/pdftrim.py | 26 ++++++++++++++++++++++---- 1 file changed, 22 insertions(+), 4 deletions(-) diff --git a/src/calibre/ebooks/pdf/pdftrim.py b/src/calibre/ebooks/pdf/pdftrim.py index fada1717ed..97ada63cc1 100644 --- a/src/calibre/ebooks/pdf/pdftrim.py +++ b/src/calibre/ebooks/pdf/pdftrim.py @@ -8,6 +8,8 @@ __docformat__ = 'restructuredtext en' import os, sys, shutil, re from calibre.utils.config import Config, StringConfig from pyPdf import PdfFileWriter, PdfFileReader +from calibre.ebooks.metadata.pdf import set_metadata +from calibre.ebooks.metadata import MetaInformation def config(defaults=None): desc = _('Options to control the transformation of pdf') @@ -19,9 +21,9 @@ def config(defaults=None): c.add_opt('verbose', ['-v', '--verbose'], default=0, action='count', help=_('Be verbose, useful for debugging. Can be specified multiple times for greater verbosity.')) c.add_opt('title', ['-t', '--title'], - help=_('Title for generated ebook. Default is to not change.')) + help=_('Title for generated ebook. Default is to not change, until our libraries are extended the title is lost.')) c.add_opt('author', ['-a', '--author'], - help=_('Set the author in the metadata of the generated ebook. Default is not to change')) + help=_('Set the author in the metadata of the generated ebook. Default is not to change, until our libraries are extended the author is lost.')) c.add_opt('output', ['-o', '--output'],default='cropped.pdf', help=_('Path to output file. By default a file is created in the current directory.')) c.add_opt('bottom_left_x', [ '-x', '--leftx'], default=default_crop, @@ -49,8 +51,15 @@ def main(args=sys.argv): parser = option_parser() opts, args = parser.parse_args(args) source = os.path.abspath(args[1]) - output_pdf = PdfFileWriter() input_pdf = PdfFileReader(file(source, "rb")) + info = input_pdf.getDocumentInfo() + title = 'Unknown' + author = 'Unknown' + subject = 'Unknown' + if info.title: + title = info.title + author = info.author + subject = info.subject if opts.bounding != None: try: bounding = open( opts.bounding , 'r' ) @@ -58,7 +67,11 @@ def main(args=sys.argv): except: print 'Error opening %s' % opts.bounding return 1 - print opts.bounding + if opts.title != None: + title=opts.title + if opts.author != None: + author=opts.author + output_pdf = PdfFileWriter() for page_number in range (0, input_pdf.getNumPages() ): page = input_pdf.getPage(page_number) if opts.bounding != None: @@ -77,8 +90,13 @@ def main(args=sys.argv): bounding.close() output_file = file(opts.output, "wb") output_pdf.write(output_file) + #mi = MetaInformation(_('Unknown'), [_('Unknown')]) + #mi.title=title + #mi.author=author + #set_metadata(output_file, mi) output_file.close() + return 0 if __name__ == '__main__': From 54e3dc9302f740efdf1fbc2b4c587b65f82ff254 Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Thu, 8 Jan 2009 12:20:36 -0800 Subject: [PATCH 10/10] IGN:... --- src/calibre/devices/scanner.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/calibre/devices/scanner.py b/src/calibre/devices/scanner.py index 5fab1be2f6..ec937fc84d 100644 --- a/src/calibre/devices/scanner.py +++ b/src/calibre/devices/scanner.py @@ -59,8 +59,8 @@ class DeviceScanner(object): def is_device_connected(self, device): if iswindows: + vid, pid = 'vid_%4.4x'%device.VENDOR_ID, 'pid_%4.4x'%device.PRODUCT_ID for device_id in self.devices: - vid, pid = 'vid_%4.4x'%device.VENDOR_ID, 'pid_%4.4x'%device.PRODUCT_ID if vid in device_id and pid in device_id: if self.test_bcd_windows(device_id, getattr(device, 'BCD', None)): return True