Merged upstream changes.

This commit is contained in:
Marshall T. Vandegrift 2009-01-13 18:56:03 -05:00
commit 9cce46ad08
55 changed files with 18094 additions and 14919 deletions

View File

@ -2,7 +2,7 @@ __license__ = 'GPL v3'
__copyright__ = '2008, Kovid Goyal kovid@kovidgoyal.net' __copyright__ = '2008, Kovid Goyal kovid@kovidgoyal.net'
__docformat__ = 'restructuredtext en' __docformat__ = 'restructuredtext en'
__appname__ = 'calibre' __appname__ = 'calibre'
__version__ = '0.4.126' __version__ = '0.4.127'
__author__ = "Kovid Goyal <kovid@kovidgoyal.net>" __author__ = "Kovid Goyal <kovid@kovidgoyal.net>"
''' '''
Various run time constants. Various run time constants.

View File

@ -43,7 +43,11 @@ def update_module(mod, path):
zp = os.path.join(os.path.dirname(sys.executable), 'library.zip') zp = os.path.join(os.path.dirname(sys.executable), 'library.zip')
elif isosx: elif isosx:
zp = os.path.join(os.path.dirname(getattr(sys, 'frameworks_dir')), zp = os.path.join(os.path.dirname(getattr(sys, 'frameworks_dir')),
'Resources', 'lib', 'python2.5', 'site-packages.zip') 'Resources', 'lib',
'python'+'.'.join(map(str, sys.version_info[:2])),
'site-packages.zip')
else:
zp = os.path.join(getattr(sys, 'frozen_path'), 'loader.zip')
if zp is not None: if zp is not None:
update_zipfile(zp, mod, path) update_zipfile(zp, mod, path)
else: else:

View File

@ -9,31 +9,25 @@ import os, fnmatch
from calibre.devices.usbms.driver import USBMS from calibre.devices.usbms.driver import USBMS
class CYBOOKG3(USBMS): class CYBOOKG3(USBMS):
MIME_MAP = {
'mobi' : 'application/mobi',
'prc' : 'application/prc',
'html' : 'application/html',
'pdf' : 'application/pdf',
'rtf' : 'application/rtf',
'txt' : 'text/plain',
}
# Ordered list of supported formats # Ordered list of supported formats
FORMATS = MIME_MAP.keys() # Be sure these have an entry in calibre.devices.mime
FORMATS = ['mobi', 'prc', 'html', 'pdf', 'rtf', 'txt']
VENDOR_ID = 0x0bda VENDOR_ID = 0x0bda
PRODUCT_ID = 0x0703 PRODUCT_ID = 0x0703
BCD = [0x110, 0x132] BCD = [0x110, 0x132]
VENDOR_NAME = 'BOOKEEN' VENDOR_NAME = 'BOOKEEN'
PRODUCT_NAME = 'CYBOOK_GEN3' WINDOWS_MAIN_MEM = 'CYBOOK_GEN3__-FD'
WINDOWS_CARD_MEM = 'CYBOOK_GEN3__-SD'
OSX_NAME_MAIN_MEM = 'Bookeen Cybook Gen3 -FD Media' OSX_MAIN_MEM = 'Bookeen Cybook Gen3 -FD Media'
OSX_NAME_CARD_MEM = 'Bookeen Cybook Gen3 -SD Media' OSX_CARD_MEM = 'Bookeen Cybook Gen3 -SD Media'
MAIN_MEMORY_VOLUME_LABEL = 'Cybook Gen 3 Main Memory' MAIN_MEMORY_VOLUME_LABEL = 'Cybook Gen 3 Main Memory'
STORAGE_CARD_VOLUME_LABEL = 'Cybook Gen 3 Storage Card' STORAGE_CARD_VOLUME_LABEL = 'Cybook Gen 3 Storage Card'
EBOOK_DIR = "eBooks" EBOOK_DIR_MAIN = "eBooks"
def delete_books(self, paths, end_session=True): def delete_books(self, paths, end_session=True):
for path in paths: for path in paths:

View File

@ -41,6 +41,20 @@ class Device(object):
'''Return the FDI description of this device for HAL on linux.''' '''Return the FDI description of this device for HAL on linux.'''
return '' return ''
@classmethod
def can_handle(cls, device_info):
'''
Optional method to perform further checks on a device to see if this driver
is capable of handling it. If it is not it should return False. This method
is only called after the vendor, product ids and the bcd have matched, so
it can do some relatively time intensive checks. The default implementation
returns True.
:param device_info: On windows a device ID string. On Unix a tuple of
``(vendor_id, product_id, bcd)``.
'''
return True
def open(self): def open(self):
''' '''
Perform any device specific initialization. Called after the device is Perform any device specific initialization. Called after the device is

View File

@ -9,24 +9,30 @@ import os, fnmatch
from calibre.devices.usbms.driver import USBMS from calibre.devices.usbms.driver import USBMS
class KINDLE(USBMS): class KINDLE(USBMS):
MIME_MAP = {
'azw' : 'application/azw',
'mobi' : 'application/mobi',
'prc' : 'application/prc',
'txt' : 'text/plain',
}
# Ordered list of supported formats # Ordered list of supported formats
FORMATS = MIME_MAP.keys() FORMATS = ['azw', 'mobi', 'prc', 'txt']
VENDOR_ID = 0x1949 VENDOR_ID = 0x1949
PRODUCT_ID = 0x0001 PRODUCT_ID = 0x0001
BCD = 0x399 BCD = [0x399]
VENDOR_NAME = 'AMAZON' VENDOR_NAME = 'AMAZON'
PRODUCT_NAME = 'KINDLE' WINDOWS_MAIN_MEM = 'KINDLE'
MAIN_MEMORY_VOLUME_LABEL = 'Kindle Main Memory' MAIN_MEMORY_VOLUME_LABEL = 'Kindle Main Memory'
STORAGE_CARD_VOLUME_LABEL = 'Kindle Storage Card' STORAGE_CARD_VOLUME_LABEL = 'Kindle Storage Card'
EBOOK_DIR = "documents" EBOOK_DIR_MAIN = "documents"
def delete_books(self, paths, end_session=True):
for path in paths:
if os.path.exists(path):
os.unlink(path)
filepath, ext = os.path.splitext(path)
basepath, filename = os.path.split(filepath)
# Delete the ebook auxiliary file
if os.path.exists(filepath + '.mbp'):
os.unlink(filepath + '.mbp')

View File

@ -0,0 +1,19 @@
__license__ = 'GPL v3'
__copyright__ = '2009, John Schember <john at nachtimwald.com>'
'''
Global Mime mapping of ebook types.
'''
MIME_MAP = {
'azw' : 'application/azw',
'epub' : 'application/epub+zip',
'html' : 'text/html',
'lrf' : 'application/x-sony-bbeb',
'lrx' : 'application/x-sony-bbeb',
'mobi' : 'application/mobi',
'pdf' : 'application/pdf',
'prc' : 'application/prc',
'rtf' : 'application/rtf',
'txt' : 'text/plain',
}

View File

@ -63,12 +63,14 @@ class DeviceScanner(object):
for device_id in self.devices: for device_id in self.devices:
if vid in device_id and pid in device_id: if vid in device_id and pid in device_id:
if self.test_bcd_windows(device_id, getattr(device, 'BCD', None)): if self.test_bcd_windows(device_id, getattr(device, 'BCD', None)):
return True if device.can_handle(device_id):
return True
else: else:
for vendor, product, bcdDevice in self.devices: for vendor, product, bcdDevice in self.devices:
if device.VENDOR_ID == vendor and device.PRODUCT_ID == product: if device.VENDOR_ID == vendor and device.PRODUCT_ID == product:
if self.test_bcd(bcdDevice, getattr(device, 'BCD', None)): if self.test_bcd(bcdDevice, getattr(device, 'BCD', None)):
return True if device.can_handle((vendor, product, bcdDevice)):
return True
return False return False

View File

@ -6,7 +6,7 @@ intended to be subclassed with the relevant parts implemented for a particular
device. This class handles devive detection. device. This class handles devive detection.
''' '''
import os, time import os, subprocess, time
from calibre.devices.interface import Device as _Device from calibre.devices.interface import Device as _Device
from calibre.devices.errors import DeviceError from calibre.devices.errors import DeviceError
@ -23,11 +23,12 @@ class Device(_Device):
PRODUCT_ID = 0x0 PRODUCT_ID = 0x0
BCD = None BCD = None
VENDOR_NAME = '' VENDOR_NAME = None
PRODUCT_NAME = '' WINDOWS_MAIN_MEM = None
WINDOWS_CARD_MEM = None
OSX_NAME_MAIN_MEM = '' OSX_MAIN_MEM = None
OSX_NAME_CARD_MEM = '' OSX_CARD_MEM = None
MAIN_MEMORY_VOLUME_LABEL = '' MAIN_MEMORY_VOLUME_LABEL = ''
STORAGE_CARD_VOLUME_LABEL = '' STORAGE_CARD_VOLUME_LABEL = ''
@ -148,43 +149,47 @@ class Device(_Device):
return (msz, 0, csz) return (msz, 0, csz)
@classmethod def windows_match_device(self, pnp_id, device_id):
def windows_match_device(cls, device_id): pnp_id = pnp_id.upper()
device_id = device_id.upper()
if 'VEN_'+cls.VENDOR_NAME in device_id and \ if device_id and pnp_id is not None:
'PROD_'+cls.PRODUCT_NAME in device_id: device_id = device_id.upper()
return True
vid, pid = hex(cls.VENDOR_ID)[2:], hex(cls.PRODUCT_ID)[2:] if 'VEN_' + self.VENDOR_NAME in pnp_id and 'PROD_' + device_id in pnp_id:
while len(vid) < 4: vid = '0' + vid return True
while len(pid) < 4: pid = '0' + pid
if 'VID_'+vid in device_id and 'PID_'+pid in device_id:
return True
return False return False
# This only supports Windows >= 2000 def windows_get_drive_prefix(self, drive):
prefix = None
try:
partition = drive.associators("Win32_DiskDriveToDiskPartition")[0]
logical_disk = partition.associators('Win32_LogicalDiskToPartition')[0]
prefix = logical_disk.DeviceID + os.sep
except IndexError:
pass
return prefix
def open_windows(self): def open_windows(self):
drives = [] drives = {}
wmi = __import__('wmi', globals(), locals(), [], -1) wmi = __import__('wmi', globals(), locals(), [], -1)
c = wmi.WMI() c = wmi.WMI()
for drive in c.Win32_DiskDrive(): for drive in c.Win32_DiskDrive():
if self.__class__.windows_match_device(str(drive.PNPDeviceID)): if self.windows_match_device(str(drive.PNPDeviceID), WINDOWS_MAIN_MEM):
if drive.Partitions == 0: drives['main'] = self.windows_get_drive_prefix(drive)
continue elif self.windows_match_device(str(drive.PNPDeviceID), WINDOWS_CARD_MEM):
try: drives['card'] = self.windows_get_drive_prefix(drive)
partition = drive.associators("Win32_DiskDriveToDiskPartition")[0]
logical_disk = partition.associators('Win32_LogicalDiskToPartition')[0] if 'main' and 'card' in drives.keys():
prefix = logical_disk.DeviceID+os.sep break
drives.append((drive.Index, prefix))
except IndexError:
continue
if not drives: if not drives:
raise DeviceError(_('Unable to detect the %s disk drive. Try rebooting.')%self.__class__.__name__) raise DeviceError(_('Unable to detect the %s disk drive. Try rebooting.') % self.__class__.__name__)
drives.sort(cmp=lambda a, b: cmp(a[0], b[0])) self._main_prefix = drives['main'] if 'main' in names.keys() else None
self._main_prefix = drives[0][1] self._card_prefix = drives['card'] if 'card' in names.keys() else None
if len(drives) > 1:
self._card_prefix = drives[1][1]
@classmethod @classmethod
def get_osx_mountpoints(self, raw=None): def get_osx_mountpoints(self, raw=None):
@ -207,9 +212,9 @@ class Device(_Device):
break break
for i, line in enumerate(lines): for i, line in enumerate(lines):
if line.strip().endswith('<class IOMedia>') and self.OSX_NAME_MAIN_MEM in line: if self.OSX_MAIN_MEM is not None and line.strip().endswith('<class IOMedia>') and self.OSX_MAIN_MEM in line:
get_dev_node(lines[i+1:], 'main') get_dev_node(lines[i+1:], 'main')
if line.strip().endswith('<class IOMedia>') and self.OSX_NAME_CARD_MEM in line: if self.OSX_CARD_MEM is not None and line.strip().endswith('<class IOMedia>') and self.OSX_CARD_MEM in line:
get_dev_node(lines[i+1:], 'card') get_dev_node(lines[i+1:], 'card')
if len(names.keys()) == 2: if len(names.keys()) == 2:
break break

View File

@ -12,10 +12,11 @@ from itertools import cycle
from calibre.devices.usbms.device import Device from calibre.devices.usbms.device import Device
from calibre.devices.usbms.books import BookList, Book from calibre.devices.usbms.books import BookList, Book
from calibre.devices.errors import FreeSpaceError from calibre.devices.errors import FreeSpaceError
from calibre.devices.mime import MIME_MAP
class USBMS(Device): class USBMS(Device):
EBOOK_DIR = '' EBOOK_DIR_MAIN = ''
MIME_MAP = {} EBOOK_DIR_CARD = ''
FORMATS = [] FORMATS = []
def __init__(self, key='-1', log_packets=False, report_progress=None): def __init__(self, key='-1', log_packets=False, report_progress=None):
@ -35,11 +36,12 @@ class USBMS(Device):
return bl return bl
prefix = self._card_prefix if oncard else self._main_prefix prefix = self._card_prefix if oncard else self._main_prefix
ebook_dir = self.EBOOK_DIR_CARD if oncard else self.EBOOK_DIR_MAIN
# Get all books in all directories under the root EBOOK_DIR directory # Get all books in all directories under the root ebook_dir directory
for path, dirs, files in os.walk(os.path.join(prefix, self.EBOOK_DIR)): for path, dirs, files in os.walk(os.path.join(prefix, ebook_dir)):
# Filter out anything that isn't in the list of supported ebook types # Filter out anything that isn't in the list of supported ebook types
for book_type in self.MIME_MAP.keys(): for book_type in self.FORMATS:
for filename in fnmatch.filter(files, '*.%s' % (book_type)): for filename in fnmatch.filter(files, '*.%s' % (book_type)):
title, author, mime = self.__class__.extract_book_metadata_by_filename(filename) title, author, mime = self.__class__.extract_book_metadata_by_filename(filename)
@ -51,9 +53,9 @@ class USBMS(Device):
raise ValueError(_('The reader has no storage card connected.')) raise ValueError(_('The reader has no storage card connected.'))
if not on_card: if not on_card:
path = os.path.join(self._main_prefix, self.EBOOK_DIR) path = os.path.join(self._main_prefix, self.EBOOK_DIR_MAIN)
else: else:
path = os.path.join(self._card_prefix, self.EBOOK_DIR) path = os.path.join(self._card_prefix, self.EBOOK_DIR_CARD)
sizes = map(os.path.getsize, files) sizes = map(os.path.getsize, files)
size = sum(sizes) size = sum(sizes)
@ -136,10 +138,11 @@ class USBMS(Device):
else: else:
book_title = os.path.splitext(filename)[0].replace('_', ' ') book_title = os.path.splitext(filename)[0].replace('_', ' ')
fileext = os.path.splitext(filename)[1] fileext = os.path.splitext(filename)[1][1:]
if fileext in cls.MIME_MAP.keys():
book_mime = cls.MIME_MAP[fileext] if fileext in cls.FORMATS:
book_mime = MIME_MAP[fileext] if fileext in MIME_MAP.keys() else 'Unknown'
return book_title, book_author, book_mime return book_title, book_author, book_mime
# ls, rm, cp, mkdir, touch, cat # ls, rm, cp, mkdir, touch, cat

View File

@ -67,6 +67,7 @@ def txt2opf(path, tdir, opts):
def pdf2opf(path, tdir, opts): def pdf2opf(path, tdir, opts):
from calibre.ebooks.lrf.pdf.convert_from import generate_html from calibre.ebooks.lrf.pdf.convert_from import generate_html
generate_html(path, tdir) generate_html(path, tdir)
opts.dont_split_on_page_breaks = True
return os.path.join(tdir, 'metadata.opf') return os.path.join(tdir, 'metadata.opf')
def epub2opf(path, tdir, opts): def epub2opf(path, tdir, opts):

View File

@ -335,7 +335,7 @@ class PreProcessor(object):
# Fix pdftohtml markup # Fix pdftohtml markup
PDFTOHTML = [ PDFTOHTML = [
# Remove <hr> tags # Remove <hr> tags
(re.compile(r'<hr.*?>', re.IGNORECASE), lambda match: '<span style="page-break-after:always"> </span>'), (re.compile(r'<hr.*?>', re.IGNORECASE), lambda match: '<br />'),
# Remove page numbers # Remove page numbers
(re.compile(r'\d+<br>', re.IGNORECASE), lambda match: ''), (re.compile(r'\d+<br>', re.IGNORECASE), lambda match: ''),
# Remove <br> and replace <br><br> with <p> # Remove <br> and replace <br><br> with <p>
@ -560,7 +560,7 @@ class Processor(Parser):
hr = etree.Element('hr') hr = etree.Element('hr')
if elem.getprevious() is None: if elem.getprevious() is None:
elem.getparent()[:0] = [hr] elem.getparent()[:0] = [hr]
else: elif elem.getparent() is not None:
insert = None insert = None
for i, c in enumerate(elem.getparent()): for i, c in enumerate(elem.getparent()):
if c is elem: if c is elem:

View File

@ -144,16 +144,15 @@ class ReBinary(object):
NSRMAP = {'': None, XML_NS: 'xml'} NSRMAP = {'': None, XML_NS: 'xml'}
def __init__(self, root, path, oeb, map=HTML_MAP): def __init__(self, root, path, oeb, map=HTML_MAP):
self.path = path self.item = item
self.logger = oeb.logger self.logger = oeb.logger
self.dir = os.path.dirname(path)
self.manifest = oeb.manifest self.manifest = oeb.manifest
self.tags, self.tattrs = map self.tags, self.tattrs = map
self.buf = StringIO() self.buf = StringIO()
self.anchors = [] self.anchors = []
self.page_breaks = [] self.page_breaks = []
self.is_html = is_html = map is HTML_MAP self.is_html = is_html = map is HTML_MAP
self.stylizer = Stylizer(root, path, oeb) if is_html else None self.stylizer = Stylizer(root, item.href, oeb) if is_html else None
self.tree_to_binary(root) self.tree_to_binary(root)
self.content = self.buf.getvalue() self.content = self.buf.getvalue()
self.ahc = self.build_ahc() if is_html else None self.ahc = self.build_ahc() if is_html else None
@ -210,6 +209,8 @@ class ReBinary(object):
if attr in ('href', 'src'): if attr in ('href', 'src'):
value = urlnormalize(value) value = urlnormalize(value)
path, frag = urldefrag(value) path, frag = urldefrag(value)
if self.item:
path = self.item.abshref(path)
prefix = unichr(3) prefix = unichr(3)
if path in self.manifest.hrefs: if path in self.manifest.hrefs:
prefix = unichr(2) prefix = unichr(2)
@ -222,7 +223,7 @@ class ReBinary(object):
elif attr.startswith('ms--'): elif attr.startswith('ms--'):
attr = '%' + attr[4:] attr = '%' + attr[4:]
elif tag == 'link' and attr == 'type' and value in OEB_STYLES: elif tag == 'link' and attr == 'type' and value in OEB_STYLES:
value = OEB_CSS_MIME value = CSS_MIME
if attr in tattrs: if attr in tattrs:
self.write(tattrs[attr]) self.write(tattrs[attr])
else: else:
@ -275,7 +276,7 @@ class ReBinary(object):
def build_ahc(self): def build_ahc(self):
if len(self.anchors) > 6: if len(self.anchors) > 6:
self.logger.log_warn("More than six anchors in file %r. " \ self.logger.log_warn("More than six anchors in file %r. " \
"Some links may not work properly." % self.path) "Some links may not work properly." % self.item.href)
data = StringIO() data = StringIO()
data.write(unichr(len(self.anchors)).encode('utf-8')) data.write(unichr(len(self.anchors)).encode('utf-8'))
for anchor, offset in self.anchors: for anchor, offset in self.anchors:
@ -473,7 +474,7 @@ class LitWriter(object):
secnum = 0 secnum = 0
if not isinstance(data, basestring): if not isinstance(data, basestring):
self._add_folder(name) self._add_folder(name)
rebin = ReBinary(data, item.href, self._oeb, map=HTML_MAP) rebin = ReBinary(data, item, self._oeb, map=HTML_MAP)
self._add_file(name + '/ahc', rebin.ahc, 0) self._add_file(name + '/ahc', rebin.ahc, 0)
self._add_file(name + '/aht', rebin.aht, 0) self._add_file(name + '/aht', rebin.aht, 0)
item.page_breaks = rebin.page_breaks item.page_breaks = rebin.page_breaks
@ -552,7 +553,7 @@ class LitWriter(object):
meta.attrib['ms--minimum_level'] = '0' meta.attrib['ms--minimum_level'] = '0'
meta.attrib['ms--attr5'] = '1' meta.attrib['ms--attr5'] = '1'
meta.attrib['ms--guid'] = '{%s}' % str(uuid.uuid4()).upper() meta.attrib['ms--guid'] = '{%s}' % str(uuid.uuid4()).upper()
rebin = ReBinary(meta, 'content.opf', self._oeb, map=OPF_MAP) rebin = ReBinary(meta, None, self._oeb, map=OPF_MAP)
meta = rebin.content meta = rebin.content
self._meta = meta self._meta = meta
self._add_file('/meta', meta) self._add_file('/meta', meta)

View File

@ -425,7 +425,7 @@ def do_convert(path_to_file, opts, notification=lambda m, p: p, output_format='l
thumbnail = None thumbnail = None
if not pages: if not pages:
raise ValueError('Could not find any pages in the comic: %s'%source) raise ValueError('Could not find any pages in the comic: %s'%source)
if not opts.no_process: if not getattr(opts, 'no_process', False):
pages, failures, tdir2 = process_pages(pages, opts, notification) pages, failures, tdir2 = process_pages(pages, opts, notification)
if not pages: if not pages:
raise ValueError('Could not find any valid pages in the comic: %s'%source) raise ValueError('Could not find any valid pages in the comic: %s'%source)
@ -443,7 +443,7 @@ def do_convert(path_to_file, opts, notification=lambda m, p: p, output_format='l
if output_format == 'pdf': if output_format == 'pdf':
create_pdf(pages, opts.profile, opts, thumbnail=thumbnail) create_pdf(pages, opts.profile, opts, thumbnail=thumbnail)
shutil.rmtree(tdir) shutil.rmtree(tdir)
if not opts.no_process: if not getattr(opts, 'no_process', False):
shutil.rmtree(tdir2) shutil.rmtree(tdir2)
@ -457,7 +457,7 @@ def main(args=sys.argv, notification=None, output_format='lrf'):
if not callable(notification): if not callable(notification):
pb = ProgressBar(terminal_controller, _('Rendering comic pages...'), pb = ProgressBar(terminal_controller, _('Rendering comic pages...'),
no_progress_bar=opts.no_progress_bar) no_progress_bar=opts.no_progress_bar or getattr(opts, 'no_process', False))
notification = pb.update notification = pb.update
source = os.path.abspath(args[1]) source = os.path.abspath(args[1])

View File

@ -122,7 +122,7 @@ class HTMLConverter(object, LoggingInterface):
# Fix pdftohtml markup # Fix pdftohtml markup
PDFTOHTML = [ PDFTOHTML = [
# Remove <hr> tags # Remove <hr> tags
(re.compile(r'<hr.*?>', re.IGNORECASE), lambda match: '<span style="page-break-after:always"> </span>'), (re.compile(r'<hr.*?>', re.IGNORECASE), lambda match: '<br />'),
# Remove page numbers # Remove page numbers
(re.compile(r'\d+<br>', re.IGNORECASE), lambda match: ''), (re.compile(r'\d+<br>', re.IGNORECASE), lambda match: ''),
# Remove <br> and replace <br><br> with <p> # Remove <br> and replace <br><br> with <p>

View File

@ -700,7 +700,7 @@ class Text(LRFStream):
def add_text(self, text): def add_text(self, text):
s = unicode(text, "utf-16-le") s = unicode(text, "utf-16-le")
if s: if s:
s = s.translate(self.text_map) s = s.translate(self.text_map)
self.content.append(self.entity_pattern.sub(entity_to_unicode, s)) self.content.append(self.entity_pattern.sub(entity_to_unicode, s))
def end_container(self, tag, stream): def end_container(self, tag, stream):
@ -799,18 +799,39 @@ class Text(LRFStream):
length = len(self.stream) length = len(self.stream)
style = self.style.as_dict() style = self.style.as_dict()
current_style = style.copy() current_style = style.copy()
text_tags = set(list(TextAttr.tag_map.keys()) + \
list(Text.text_tags.keys()) + \
list(ruby_tags.keys()))
text_tags -= set([0xf500+i for i in range(10)])
text_tags.add(0xf5cc)
while stream.tell() < length: while stream.tell() < length:
# Is there some text beofre a tag? # Is there some text before a tag?
pos = self.stream.find('\xf5', stream.tell()) - 1 def find_first_tag(start):
if pos > 0: pos = self.stream.find('\xf5', start)
self.add_text(self.stream[stream.tell():pos]) if pos == -1:
stream.seek(pos) return -1
elif pos == -2: # No tags in this stream try:
stream.seek(pos-1)
_t = Tag(stream)
if _t.id in text_tags:
return pos-1
return find_first_tag(pos+1)
except:
return find_first_tag(pos+1)
start_pos = stream.tell()
tag_pos = find_first_tag(start_pos)
if tag_pos >= start_pos:
if tag_pos > start_pos:
self.add_text(self.stream[start_pos:tag_pos])
stream.seek(tag_pos)
else: # No tags in this stream
self.add_text(self.stream) self.add_text(self.stream)
stream.seek(0, 2) stream.seek(0, 2)
print repr(self.stream)
break break
tag = Tag(stream) tag = Tag(stream)
@ -1166,7 +1187,8 @@ class TOCObject(LRFStream):
refpage = struct.unpack("<I", stream.read(4))[0] refpage = struct.unpack("<I", stream.read(4))[0]
refobj = struct.unpack("<I", stream.read(4))[0] refobj = struct.unpack("<I", stream.read(4))[0]
cnt = struct.unpack("<H", stream.read(2))[0] cnt = struct.unpack("<H", stream.read(2))[0]
label = unicode(stream.read(cnt), "utf_16") raw = stream.read(cnt)
label = raw.decode('utf_16_le')
self._contents.append(TocLabel(refpage, refobj, label)) self._contents.append(TocLabel(refpage, refobj, label))
c -= 1 c -= 1

View File

@ -33,7 +33,6 @@ class EXTHHeader(object):
self.length, self.num_items = struct.unpack('>LL', raw[4:12]) self.length, self.num_items = struct.unpack('>LL', raw[4:12])
raw = raw[12:] raw = raw[12:]
pos = 0 pos = 0
self.mi = MetaInformation('Unknown', ['Unknown']) self.mi = MetaInformation('Unknown', ['Unknown'])
self.has_fake_cover = True self.has_fake_cover = True
@ -49,9 +48,17 @@ class EXTHHeader(object):
self.cover_offset, = struct.unpack('>L', content) self.cover_offset, = struct.unpack('>L', content)
elif id == 202: elif id == 202:
self.thumbnail_offset, = struct.unpack('>L', content) self.thumbnail_offset, = struct.unpack('>L', content)
#else:
# print 'unknown record', id, repr(content)
title = re.search(r'\0+([^\0]+)\0+', raw[pos:]) title = re.search(r'\0+([^\0]+)\0+', raw[pos:])
if title: if title:
self.mi.title = title.group(1).decode(codec, 'ignore') title = title.group(1).decode(codec, 'replace')
if len(title) > 2:
self.mi.title = title
else:
title = re.search(r'\0+([^\0]+)\0+', ''.join(reversed(raw[pos:])))
if title:
self.mi.title = ''.join(reversed(title.group(1).decode(codec, 'replace')))
def process_metadata(self, id, content, codec): def process_metadata(self, id, content, codec):
@ -67,7 +74,8 @@ class EXTHHeader(object):
if not self.mi.tags: if not self.mi.tags:
self.mi.tags = [] self.mi.tags = []
self.mi.tags.append(content.decode(codec, 'ignore')) self.mi.tags.append(content.decode(codec, 'ignore'))
#else:
# print 'unhandled metadata record', id, repr(content), codec
class BookHeader(object): class BookHeader(object):
@ -466,6 +474,10 @@ def get_metadata(stream):
cover = os.path.join(tdir, mi.cover) cover = os.path.join(tdir, mi.cover)
if os.access(cover, os.R_OK): if os.access(cover, os.R_OK):
mi.cover_data = ('JPEG', open(os.path.join(tdir, mi.cover), 'rb').read()) mi.cover_data = ('JPEG', open(os.path.join(tdir, mi.cover), 'rb').read())
else:
path = os.path.join(tdir, 'images', '00001.jpg')
if os.access(path, os.R_OK):
mi.cover_data = ('JPEG', open(path, 'rb').read())
return mi return mi
def option_parser(): def option_parser():

View File

@ -29,7 +29,7 @@ def config(defaults=None):
c.add_opt('top_right_y', [ '-w', '--righty'], default=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 ) help=_('Number of pixels to crop from the right most y (default is %d)')%default_crop )
c.add_opt('bounding', ['-b', '--bounding'], c.add_opt('bounding', ['-b', '--bounding'],
help=_('A file generated by ghostscript which allows each page to be individually cropped')) help=_('A file generated by ghostscript which allows each page to be individually cropped [gs -dSAFER -dNOPAUSE -dBATCH -sDEVICE=bbox > bounding] '))
return c return c
@ -38,14 +38,24 @@ def option_parser():
return c.option_parser(usage=_('''\ return c.option_parser(usage=_('''\
%prog [options] file.pdf %prog [options] file.pdf
Crop a pdf. Crops a pdf.
''')) '''))
def main(args=sys.argv): def main(args=sys.argv):
parser = option_parser() parser = option_parser()
opts, args = parser.parse_args(args) opts, args = parser.parse_args(args)
source = os.path.abspath(args[1]) try:
input_pdf = PdfFileReader(file(source, "rb")) source = os.path.abspath(args[1])
input_pdf = PdfFileReader(file(source, "rb"))
except:
print "Unable to read input"
return 2
info = input_pdf.getDocumentInfo()
title = 'Unknown'
author = 'Unknown'
if info.title:
title = info.title
author = info.author
if opts.bounding != None: if opts.bounding != None:
try: try:
bounding = open( opts.bounding , 'r' ) bounding = open( opts.bounding , 'r' )
@ -53,7 +63,7 @@ def main(args=sys.argv):
except: except:
print 'Error opening %s' % opts.bounding print 'Error opening %s' % opts.bounding
return 1 return 1
output_pdf = PdfFileWriter() output_pdf = PdfFileWriter(title=title,author=author)
for page_number in range (0, input_pdf.getNumPages() ): for page_number in range (0, input_pdf.getNumPages() ):
page = input_pdf.getPage(page_number) page = input_pdf.getPage(page_number)
if opts.bounding != None: if opts.bounding != None:

View File

@ -75,7 +75,13 @@ def save_recipes(recipes):
def load_recipes(): def load_recipes():
config.refresh() config.refresh()
return [Recipe().unpickle(r) for r in config.get('scheduled_recipes', [])] recipes = []
for r in config.get('scheduled_recipes', []):
r = Recipe().unpickle(r)
if r.builtin and not str(r.id).startswith('recipe_'):
continue
recipes.append(r)
return recipes
class RecipeModel(QAbstractListModel, SearchQueryParser): class RecipeModel(QAbstractListModel, SearchQueryParser):
@ -438,7 +444,7 @@ class Scheduler(QObject):
self.lock.unlock() self.lock.unlock()
def main(args=sys.argv): def main(args=sys.argv):
app = QApplication([]) QApplication([])
from calibre.library.database2 import LibraryDatabase2 from calibre.library.database2 import LibraryDatabase2
d = SchedulerDialog(LibraryDatabase2('/home/kovid/documents/library')) d = SchedulerDialog(LibraryDatabase2('/home/kovid/documents/library'))
d.exec_() d.exec_()

Binary file not shown.

After

Width:  |  Height:  |  Size: 586 B

View File

@ -406,7 +406,8 @@ class Document(QGraphicsScene):
for font in lrf.font_map: for font in lrf.font_map:
fdata = QByteArray(lrf.font_map[font].data) fdata = QByteArray(lrf.font_map[font].data)
id = QFontDatabase.addApplicationFontFromData(fdata) id = QFontDatabase.addApplicationFontFromData(fdata)
font_map[font] = [str(i) for i in QFontDatabase.applicationFontFamilies(id)][0] if id != -1:
font_map[font] = [str(i) for i in QFontDatabase.applicationFontFamilies(id)][0]
if load_substitutions: if load_substitutions:
from calibre.ebooks.lrf.fonts.liberation import LiberationMono_BoldItalic from calibre.ebooks.lrf.fonts.liberation import LiberationMono_BoldItalic

View File

@ -889,6 +889,9 @@ class Main(MainWindow, Ui_MainWindow):
ids = [id for id in ids if self.library_view.model().db.has_id(id)] ids = [id for id in ids if self.library_view.model().db.has_id(id)]
files = [self.library_view.model().db.format(id, prefs['output_format'], index_is_id=True, as_file=True) for id in ids] files = [self.library_view.model().db.format(id, prefs['output_format'], index_is_id=True, as_file=True) for id in ids]
files = [f for f in files if f is not None] files = [f for f in files if f is not None]
if not files:
dynamic.set('news_to_be_synced', set([]))
return
metadata = self.library_view.model().get_metadata(ids, rows_are_ids=True) metadata = self.library_view.model().get_metadata(ids, rows_are_ids=True)
names = [] names = []
for mi in metadata: for mi in metadata:
@ -1479,8 +1482,9 @@ in which you want to store your books files. Any existing books will be automati
return True return True
def shutdown(self): def shutdown(self, write_settings=True):
self.write_settings() if write_settings:
self.write_settings()
self.job_manager.terminate_all_jobs() self.job_manager.terminate_all_jobs()
self.device_manager.keep_going = False self.device_manager.keep_going = False
self.cover_cache.stop() self.cover_cache.stop()
@ -1500,6 +1504,7 @@ in which you want to store your books files. Any existing books will be automati
def closeEvent(self, e): def closeEvent(self, e):
self.write_settings()
if self.system_tray_icon.isVisible(): if self.system_tray_icon.isVisible():
if not dynamic['systray_msg'] and not isosx: if not dynamic['systray_msg'] and not isosx:
info_dialog(self, 'calibre', 'calibre '+_('will keep running in the system tray. To close it, choose <b>Quit</b> in the context menu of the system tray.')).exec_() info_dialog(self, 'calibre', 'calibre '+_('will keep running in the system tray. To close it, choose <b>Quit</b> in the context menu of the system tray.')).exec_()
@ -1509,7 +1514,7 @@ in which you want to store your books files. Any existing books will be automati
else: else:
if self.confirm_quit(): if self.confirm_quit():
try: try:
self.shutdown() self.shutdown(write_settings=False)
except: except:
pass pass
e.accept() e.accept()

View File

@ -1551,9 +1551,6 @@ ALTER TABLE books ADD COLUMN isbn TEXT DEFAULT "" COLLATE NOCASE;
def has_book(self, mi):
return bool(self.conn.get('SELECT id FROM books where title=?', (mi.title,), all=False))
def has_id(self, id): def has_id(self, id):
return self.conn.get('SELECT id FROM books where id=?', (id,), all=False) is not None return self.conn.get('SELECT id FROM books where id=?', (id,), all=False) is not None

View File

@ -217,7 +217,11 @@ class ResultCache(SearchQueryParser):
return self.index(id) return self.index(id)
def has_id(self, id): def has_id(self, id):
return self._data[id] is not None try:
return self._data[id] is not None
except IndexError:
pass
return False
def refresh_ids(self, conn, ids): def refresh_ids(self, conn, ids):
for id in ids: for id in ids:
@ -557,7 +561,15 @@ class LibraryDatabase2(LibraryDatabase):
img.loadFromData(f.read()) img.loadFromData(f.read())
return img return img
return f if as_file else f.read() return f if as_file else f.read()
def has_book(self, mi):
title = mi.title
if title:
if not isinstance(title, unicode):
title = title.decode(preferred_encoding, 'replace')
return bool(self.conn.get('SELECT id FROM books where title=?', (title,), all=False))
return False
def has_cover(self, index, index_is_id=False): def has_cover(self, index, index_is_id=False):
id = index if index_is_id else self.id(index) id = index if index_is_id else self.id(index)
path = os.path.join(self.library_path, self.path(id, index_is_id=True), 'cover.jpg') path = os.path.join(self.library_path, self.path(id, index_is_id=True), 'cover.jpg')

View File

@ -217,8 +217,7 @@ class Server(object):
pos = pos.replace(month = 1) pos = pos.replace(month = 1)
else: else:
pos = pos.replace(month = pos.month + 1) pos = pos.replace(month = pos.month + 1)
_months = list(months(self.earliest, self.latest))[:-1][-12:]
_months = list(months(self.earliest, self.latest))[:-1][:12]
_months = [range_for_month(*m) for m in _months] _months = [range_for_month(*m) for m in _months]
_months = [self.get_slice(*m) for m in _months] _months = [self.get_slice(*m) for m in _months]
x = [m.min for m in _months] x = [m.min for m in _months]

View File

@ -35,7 +35,7 @@ class Distribution(object):
('xdg-utils', '1.0.2', 'xdg-utils', 'xdg-utils', 'xdg-utils'), ('xdg-utils', '1.0.2', 'xdg-utils', 'xdg-utils', 'xdg-utils'),
('dbus-python', '0.82.2', 'dbus-python', 'python-dbus', 'dbus-python'), ('dbus-python', '0.82.2', 'dbus-python', 'python-dbus', 'dbus-python'),
('lxml', '2.0.5', 'lxml', 'python-lxml', 'python-lxml'), ('lxml', '2.0.5', 'lxml', 'python-lxml', 'python-lxml'),
('BeautifulSoup', '3.0.5', 'beautifulsoup', 'python-beautifulsoup', 'python-beautifulsoup'), ('BeautifulSoup', '3.0.5', 'beautifulsoup', 'python-beautifulsoup', 'python-BeautifulSoup'),
('help2man', '1.36.4', 'help2man', 'help2man', 'help2man'), ('help2man', '1.36.4', 'help2man', 'help2man', 'help2man'),
] ]
@ -205,23 +205,7 @@ select Install.</li>
<ol> <ol>
<li>Before trying to use the command line tools, you must run the app at least once. This will ask you for you password and then setup the symbolic links for the command line tools.</li> <li>Before trying to use the command line tools, you must run the app at least once. This will ask you for you password and then setup the symbolic links for the command line tools.</li>
<li>The app cannot be run from within the dmg. You must drag it to a folder on your filesystem (The Desktop, Applications, wherever).</li> <li>The app cannot be run from within the dmg. You must drag it to a folder on your filesystem (The Desktop, Applications, wherever).</li>
<li>In order for the conversion of RTF to LRF to support WMF images (common in older RTF files) you need to install ImageMagick.</li> <li>In order for localization of the user interface in your language, select your language in the configuration dialog (by clicking the hammer icon next to the search bar) and select your language.</li>
<li>In order for localization of the user interface in your language you must create the file <code>~/.MacOSX/environment.plist</code> as shown below:
<pre class="wiki">
&lt;?xml version="1.0" encoding="UTF-8"?&gt;
&lt;!DOCTYPE plist PUBLIC "-//Apple Computer//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd"&gt;
&lt;plist version="1.0"&gt;
&lt;dict&gt;
&lt;key&gt;LANG&lt;/key&gt;
&lt;string&gt;de_DE&lt;/string&gt;
&lt;/dict&gt;
&lt;/plist&gt;
</pre>
The example above is for the German language. Substitute the language code you need.
After creating the file you need to log out and log in again for the changes to become
active. Of course, this will only work if calibre has been translated for your language.
If not, head over to <a href="http://calibre.kovidgoyal.net/wiki/Development#Translations">Translations</a> to see how you can translate it.
</li>
</ol> </ol>
''')) '''))
return 'binary.html', data, None return 'binary.html', data, None

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@ -338,7 +338,7 @@ class ZipInfo (object):
if isinstance(self.filename, unicode): if isinstance(self.filename, unicode):
try: try:
return self.filename.encode('ascii'), self.flag_bits return self.filename.encode('ascii'), self.flag_bits
except UnicodeEncodeError: except:
return self.filename.encode('utf-8'), self.flag_bits | 0x800 return self.filename.encode('utf-8'), self.flag_bits | 0x800
else: else:
return self.filename, self.flag_bits return self.filename, self.flag_bits

View File

@ -765,6 +765,8 @@ class BasicNewsRecipe(object, LoggingInterface):
self.log_debug(traceback.format_exc()) self.log_debug(traceback.format_exc())
if cu is not None: if cu is not None:
ext = cu.rpartition('.')[-1] ext = cu.rpartition('.')[-1]
if '?' in ext:
ext = ''
ext = ext.lower() if ext else 'jpg' ext = ext.lower() if ext else 'jpg'
self.report_progress(1, _('Downloading cover from %s')%cu) self.report_progress(1, _('Downloading cover from %s')%cu)
cpath = os.path.join(self.output_dir, 'cover.'+ext) cpath = os.path.join(self.output_dir, 'cover.'+ext)

View File

@ -21,7 +21,7 @@ recipe_modules = ['recipe_' + r for r in (
'linux_magazine', 'telegraph_uk', 'utne', 'sciencedaily', 'forbes', 'linux_magazine', 'telegraph_uk', 'utne', 'sciencedaily', 'forbes',
'time_magazine', 'endgadget', 'fudzilla', 'nspm_int', 'nspm', 'pescanik', 'time_magazine', 'endgadget', 'fudzilla', 'nspm_int', 'nspm', 'pescanik',
'spiegel_int', 'themarketticker', 'tomshardware', 'xkcd', 'ftd', 'zdnet', 'spiegel_int', 'themarketticker', 'tomshardware', 'xkcd', 'ftd', 'zdnet',
'joelonsoftware', 'joelonsoftware', 'telepolis', 'common_dreams', 'nin',
)] )]
import re, imp, inspect, time, os import re, imp, inspect, time, os

View File

@ -42,3 +42,9 @@ class ChristianScienceMonitor(BasicNewsRecipe):
feeds[-1][1].append(art) feeds[-1][1].append(art)
return feeds return feeds
def postprocess_html(self, soup, first_fetch):
html = soup.find('html')
if html is None:
return soup
html.extract()
return html

View File

@ -0,0 +1,16 @@
from calibre.web.feeds.news import BasicNewsRecipe
class CommonDreams(BasicNewsRecipe):
title = u'Common Dreams'
description = u'Progressive news and views'
__author__ = u'XanthanGum'
oldest_article = 7
max_articles_per_feed = 100
feeds = [
(u'Common Dreams Headlines',
u'http://www.commondreams.org/feed/headlines_rss'),
(u'Common Dreams Views', u'http://www.commondreams.org/feed/views_rss'),
(u'Common Dreams Newswire', u'http://www.commondreams.org/feed/newswire_rss')
]

View File

@ -49,8 +49,10 @@ class Economist(BasicNewsRecipe):
if not index_started: if not index_started:
continue continue
text = string.capwords(text) text = string.capwords(text)
feeds[text] = [] if text not in feeds.keys():
ans.append(text) feeds[text] = []
if text not in ans:
ans.append(text)
key = text key = text
continue continue
if key is None: if key is None:

View File

@ -0,0 +1,55 @@
#!/usr/bin/env python
__license__ = 'GPL v3'
__copyright__ = '2008, Darko Miletic <darko.miletic at gmail.com>'
'''
nin.co.yu
'''
import re, urllib
from calibre.web.feeds.news import BasicNewsRecipe
class Nin(BasicNewsRecipe):
title = 'NIN online'
__author__ = 'Darko Miletic'
description = 'Nedeljne informativne novine'
no_stylesheets = True
oldest_article = 15
simultaneous_downloads = 1
delay = 1
encoding = 'utf8'
needs_subscription = True
PREFIX = 'http://www.nin.co.yu'
INDEX = PREFIX + '/?change_lang=ls'
LOGIN = PREFIX + '/?logout=true'
html2lrf_options = [
'--comment' , description
, '--category' , 'news, politics, Serbia'
, '--publisher' , 'NIN'
]
preprocess_regexps = [(re.compile(u'\u0110'), lambda match: u'\u00D0')]
def get_browser(self):
br = BasicNewsRecipe.get_browser()
br.open(self.INDEX)
if self.username is not None and self.password is not None:
data = urllib.urlencode({ 'login_name':self.username
,'login_password':self.password
,'imageField.x':'32'
,'imageField.y':'15'
})
br.open(self.LOGIN,data)
return br
keep_only_tags =[dict(name='td', attrs={'width':'520'})]
remove_tags_after =dict(name='html')
feeds =[(u'NIN', u'http://www.nin.co.yu/misc/rss.php?feed=RSS2.0')]
def get_cover_url(self):
cover_url = None
soup = self.index_to_soup(self.INDEX)
link_item = soup.find('img',attrs={'width':'100','height':'137','border':'0'})
if link_item:
cover_url = self.PREFIX + link_item['src']
return cover_url

View File

@ -0,0 +1,34 @@
#!/usr/bin/env python
__license__ = 'GPL v3'
__copyright__ = '2008, Darko Miletic <darko.miletic at gmail.com>'
'''
www.heise.de/tp
'''
from calibre.web.feeds.news import BasicNewsRecipe
class Telepolis(BasicNewsRecipe):
title = 'Telepolis'
__author__ = 'Darko Miletic'
description = 'News from Germany in German'
oldest_article = 2
max_articles_per_feed = 100
no_stylesheets = True
use_embedded_content = False
encoding = 'utf-8'
html2lrf_options = [ '--comment' , description
, '--category' , 'blog,news'
]
keep_only_tags = [
dict(name='table', attrs={'class':'inhalt-table'})
,dict(name='table', attrs={'class':'blogtable' })
]
remove_tags = [
dict(name='table', attrs={'class':'img' })
,dict(name='img' , attrs={'src':'/tp/r4/icons/inline/extlink.gif'})
]
feeds = [(u'Telepolis Newsfeed', u'http://www.heise.de/tp/news.rdf')]

View File

@ -33,6 +33,7 @@ class TimesOnline(BasicNewsRecipe):
('Sports News', 'http://www.timesonline.co.uk/tol/feeds/rss/sport.xml'), ('Sports News', 'http://www.timesonline.co.uk/tol/feeds/rss/sport.xml'),
('Film News', 'http://www.timesonline.co.uk/tol/feeds/rss/film.xml'), ('Film News', 'http://www.timesonline.co.uk/tol/feeds/rss/film.xml'),
('Tech news', 'http://www.timesonline.co.uk/tol/feeds/rss/tech.xml'), ('Tech news', 'http://www.timesonline.co.uk/tol/feeds/rss/tech.xml'),
('Literary Supplement', 'http://www.timesonline.co.uk/tol/feeds/rss/thetls.xml'),
] ]
def print_version(self, url): def print_version(self, url):

View File

@ -55,7 +55,7 @@ from utils import readNonWhitespace, readUntilWhitespace, ConvertFunctionsToVirt
# This class supports writing PDF files out, given pages produced by another # This class supports writing PDF files out, given pages produced by another
# class (typically {@link #PdfFileReader PdfFileReader}). # class (typically {@link #PdfFileReader PdfFileReader}).
class PdfFileWriter(object): class PdfFileWriter(object):
def __init__(self): def __init__(self,title=u"Unknown",author=u"Unknown"):
self._header = "%PDF-1.3" self._header = "%PDF-1.3"
self._objects = [] # array of indirect objects self._objects = [] # array of indirect objects
@ -71,7 +71,9 @@ class PdfFileWriter(object):
# info object # info object
info = DictionaryObject() info = DictionaryObject()
info.update({ info.update({
NameObject("/Producer"): createStringObject(u"Python PDF Library - http://pybrary.net/pyPdf/") NameObject("/Producer"): createStringObject(u"Python PDF Library - http://pybrary.net/pyPdf/"),
NameObject("/Author"): createStringObject(author),
NameObject("/Title"): createStringObject(title),
}) })
self._info = self._addObject(info) self._info = self._addObject(info)