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/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 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..2e8a6197a2 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"] @@ -86,7 +86,7 @@ class PRS505(Device): deviceclass=cls.__name__, vendor_id=hex(cls.VENDOR_ID), product_id=hex(cls.PRODUCT_ID), - bcd=hex(cls.BCD), + bcd=hex(cls.BCD[0]), main_memory=cls.MAIN_MEMORY_VOLUME_LABEL, storage_card=cls.STORAGE_CARD_VOLUME_LABEL, ) 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..ec937fc84d 100644 --- a/src/calibre/devices/scanner.py +++ b/src/calibre/devices/scanner.py @@ -39,20 +39,37 @@ 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: + 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 - 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: + 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: - if hasattr(device, 'BCD') and device.BCD == bcdDevice: + if self.test_bcd(bcdDevice, getattr(device, 'BCD', None)): return True - return False + return False def main(args=sys.argv): diff --git a/src/calibre/devices/usbms/device.py b/src/calibre/devices/usbms/device.py index 24ad957b12..070ec54dc1 100644 --- a/src/calibre/devices/usbms/device.py +++ b/src/calibre/devices/usbms/device.py @@ -21,7 +21,7 @@ class Device(_Device): VENDOR_ID = 0x0 PRODUCT_ID = 0x0 - BCD = 0x0 + BCD = None VENDOR_NAME = '' PRODUCT_NAME = '' @@ -38,11 +38,9 @@ class Device(_Device): - - + %(main_memory)s %(deviceclass)s - @@ -52,11 +50,9 @@ class Device(_Device): - - + %(storage_card)s %(deviceclass)s - @@ -68,17 +64,22 @@ class Device(_Device): self._main_prefix = self._card_prefix = None @classmethod - def get_fdi(cls): + def get_bcd_less_fdi(cls): return cls.FDI_TEMPLATE%dict( app=__appname__, deviceclass=cls.__name__, vendor_id=hex(cls.VENDOR_ID), product_id=hex(cls.PRODUCT_ID), - bcd=hex(cls.BCD), main_memory=cls.MAIN_MEMORY_VOLUME_LABEL, storage_card=cls.STORAGE_CARD_VOLUME_LABEL, ) + @classmethod + def get_fdi(cls): + if cls.BCD is None: + return cls.get_bcd_less_fdi() + raise NotImplementedError('TODO:') + def set_progress_reporter(self, report_progress): self.report_progress = report_progress 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 diff --git a/src/calibre/ebooks/pdf/pdftrim.py b/src/calibre/ebooks/pdf/pdftrim.py new file mode 100644 index 0000000000..b194d93b9d --- /dev/null +++ b/src/calibre/ebooks/pdf/pdftrim.py @@ -0,0 +1,81 @@ +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, 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('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 individually 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]) + 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 + output_pdf = PdfFileWriter() + 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/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 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) 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',