mirror of
https://github.com/kovidgoyal/calibre.git
synced 2025-07-09 03:04:10 -04:00
Merged upstream changes.
This commit is contained in:
commit
9cce46ad08
@ -2,7 +2,7 @@ __license__ = 'GPL v3'
|
||||
__copyright__ = '2008, Kovid Goyal kovid@kovidgoyal.net'
|
||||
__docformat__ = 'restructuredtext en'
|
||||
__appname__ = 'calibre'
|
||||
__version__ = '0.4.126'
|
||||
__version__ = '0.4.127'
|
||||
__author__ = "Kovid Goyal <kovid@kovidgoyal.net>"
|
||||
'''
|
||||
Various run time constants.
|
||||
|
@ -43,7 +43,11 @@ def update_module(mod, path):
|
||||
zp = os.path.join(os.path.dirname(sys.executable), 'library.zip')
|
||||
elif isosx:
|
||||
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:
|
||||
update_zipfile(zp, mod, path)
|
||||
else:
|
||||
|
@ -9,31 +9,25 @@ import os, fnmatch
|
||||
from calibre.devices.usbms.driver import 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
|
||||
FORMATS = MIME_MAP.keys()
|
||||
# Be sure these have an entry in calibre.devices.mime
|
||||
FORMATS = ['mobi', 'prc', 'html', 'pdf', 'rtf', 'txt']
|
||||
|
||||
VENDOR_ID = 0x0bda
|
||||
PRODUCT_ID = 0x0703
|
||||
BCD = [0x110, 0x132]
|
||||
|
||||
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_NAME_CARD_MEM = 'Bookeen Cybook Gen3 -SD Media'
|
||||
OSX_MAIN_MEM = 'Bookeen Cybook Gen3 -FD Media'
|
||||
OSX_CARD_MEM = 'Bookeen Cybook Gen3 -SD Media'
|
||||
|
||||
MAIN_MEMORY_VOLUME_LABEL = 'Cybook Gen 3 Main Memory'
|
||||
STORAGE_CARD_VOLUME_LABEL = 'Cybook Gen 3 Storage Card'
|
||||
|
||||
EBOOK_DIR = "eBooks"
|
||||
EBOOK_DIR_MAIN = "eBooks"
|
||||
|
||||
def delete_books(self, paths, end_session=True):
|
||||
for path in paths:
|
||||
|
@ -41,6 +41,20 @@ class Device(object):
|
||||
'''Return the FDI description of this device for HAL on linux.'''
|
||||
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):
|
||||
'''
|
||||
Perform any device specific initialization. Called after the device is
|
||||
|
@ -9,24 +9,30 @@ import os, fnmatch
|
||||
from calibre.devices.usbms.driver import USBMS
|
||||
|
||||
class KINDLE(USBMS):
|
||||
MIME_MAP = {
|
||||
'azw' : 'application/azw',
|
||||
'mobi' : 'application/mobi',
|
||||
'prc' : 'application/prc',
|
||||
'txt' : 'text/plain',
|
||||
}
|
||||
# Ordered list of supported formats
|
||||
FORMATS = MIME_MAP.keys()
|
||||
FORMATS = ['azw', 'mobi', 'prc', 'txt']
|
||||
|
||||
VENDOR_ID = 0x1949
|
||||
PRODUCT_ID = 0x0001
|
||||
BCD = 0x399
|
||||
BCD = [0x399]
|
||||
|
||||
VENDOR_NAME = 'AMAZON'
|
||||
PRODUCT_NAME = 'KINDLE'
|
||||
WINDOWS_MAIN_MEM = 'KINDLE'
|
||||
|
||||
MAIN_MEMORY_VOLUME_LABEL = 'Kindle Main Memory'
|
||||
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')
|
||||
|
||||
|
19
src/calibre/devices/mime.py
Normal file
19
src/calibre/devices/mime.py
Normal 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',
|
||||
}
|
||||
|
@ -63,12 +63,14 @@ class DeviceScanner(object):
|
||||
for device_id in self.devices:
|
||||
if vid in device_id and pid in device_id:
|
||||
if self.test_bcd_windows(device_id, getattr(device, 'BCD', None)):
|
||||
return True
|
||||
if device.can_handle(device_id):
|
||||
return True
|
||||
else:
|
||||
for vendor, product, bcdDevice in self.devices:
|
||||
if device.VENDOR_ID == vendor and device.PRODUCT_ID == product:
|
||||
if self.test_bcd(bcdDevice, getattr(device, 'BCD', None)):
|
||||
return True
|
||||
if device.can_handle((vendor, product, bcdDevice)):
|
||||
return True
|
||||
return False
|
||||
|
||||
|
||||
|
@ -6,7 +6,7 @@ intended to be subclassed with the relevant parts implemented for a particular
|
||||
device. This class handles devive detection.
|
||||
'''
|
||||
|
||||
import os, time
|
||||
import os, subprocess, time
|
||||
|
||||
from calibre.devices.interface import Device as _Device
|
||||
from calibre.devices.errors import DeviceError
|
||||
@ -23,11 +23,12 @@ class Device(_Device):
|
||||
PRODUCT_ID = 0x0
|
||||
BCD = None
|
||||
|
||||
VENDOR_NAME = ''
|
||||
PRODUCT_NAME = ''
|
||||
VENDOR_NAME = None
|
||||
WINDOWS_MAIN_MEM = None
|
||||
WINDOWS_CARD_MEM = None
|
||||
|
||||
OSX_NAME_MAIN_MEM = ''
|
||||
OSX_NAME_CARD_MEM = ''
|
||||
OSX_MAIN_MEM = None
|
||||
OSX_CARD_MEM = None
|
||||
|
||||
MAIN_MEMORY_VOLUME_LABEL = ''
|
||||
STORAGE_CARD_VOLUME_LABEL = ''
|
||||
@ -148,43 +149,47 @@ class Device(_Device):
|
||||
|
||||
return (msz, 0, csz)
|
||||
|
||||
@classmethod
|
||||
def windows_match_device(cls, device_id):
|
||||
device_id = device_id.upper()
|
||||
if 'VEN_'+cls.VENDOR_NAME in device_id and \
|
||||
'PROD_'+cls.PRODUCT_NAME in device_id:
|
||||
return True
|
||||
vid, pid = hex(cls.VENDOR_ID)[2:], hex(cls.PRODUCT_ID)[2:]
|
||||
while len(vid) < 4: vid = '0' + vid
|
||||
while len(pid) < 4: pid = '0' + pid
|
||||
if 'VID_'+vid in device_id and 'PID_'+pid in device_id:
|
||||
return True
|
||||
def windows_match_device(self, pnp_id, device_id):
|
||||
pnp_id = pnp_id.upper()
|
||||
|
||||
if device_id and pnp_id is not None:
|
||||
device_id = device_id.upper()
|
||||
|
||||
if 'VEN_' + self.VENDOR_NAME in pnp_id and 'PROD_' + device_id in pnp_id:
|
||||
return True
|
||||
|
||||
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):
|
||||
drives = []
|
||||
drives = {}
|
||||
wmi = __import__('wmi', globals(), locals(), [], -1)
|
||||
c = wmi.WMI()
|
||||
for drive in c.Win32_DiskDrive():
|
||||
if self.__class__.windows_match_device(str(drive.PNPDeviceID)):
|
||||
if drive.Partitions == 0:
|
||||
continue
|
||||
try:
|
||||
partition = drive.associators("Win32_DiskDriveToDiskPartition")[0]
|
||||
logical_disk = partition.associators('Win32_LogicalDiskToPartition')[0]
|
||||
prefix = logical_disk.DeviceID+os.sep
|
||||
drives.append((drive.Index, prefix))
|
||||
except IndexError:
|
||||
continue
|
||||
if self.windows_match_device(str(drive.PNPDeviceID), WINDOWS_MAIN_MEM):
|
||||
drives['main'] = self.windows_get_drive_prefix(drive)
|
||||
elif self.windows_match_device(str(drive.PNPDeviceID), WINDOWS_CARD_MEM):
|
||||
drives['card'] = self.windows_get_drive_prefix(drive)
|
||||
|
||||
if 'main' and 'card' in drives.keys():
|
||||
break
|
||||
|
||||
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[0][1]
|
||||
if len(drives) > 1:
|
||||
self._card_prefix = drives[1][1]
|
||||
self._main_prefix = drives['main'] if 'main' in names.keys() else None
|
||||
self._card_prefix = drives['card'] if 'card' in names.keys() else None
|
||||
|
||||
@classmethod
|
||||
def get_osx_mountpoints(self, raw=None):
|
||||
@ -207,9 +212,9 @@ class Device(_Device):
|
||||
break
|
||||
|
||||
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')
|
||||
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')
|
||||
if len(names.keys()) == 2:
|
||||
break
|
||||
|
@ -12,10 +12,11 @@ from itertools import cycle
|
||||
from calibre.devices.usbms.device import Device
|
||||
from calibre.devices.usbms.books import BookList, Book
|
||||
from calibre.devices.errors import FreeSpaceError
|
||||
from calibre.devices.mime import MIME_MAP
|
||||
|
||||
class USBMS(Device):
|
||||
EBOOK_DIR = ''
|
||||
MIME_MAP = {}
|
||||
EBOOK_DIR_MAIN = ''
|
||||
EBOOK_DIR_CARD = ''
|
||||
FORMATS = []
|
||||
|
||||
def __init__(self, key='-1', log_packets=False, report_progress=None):
|
||||
@ -35,11 +36,12 @@ class USBMS(Device):
|
||||
return bl
|
||||
|
||||
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
|
||||
for path, dirs, files in os.walk(os.path.join(prefix, self.EBOOK_DIR)):
|
||||
# Get all books in all directories under the root ebook_dir directory
|
||||
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
|
||||
for book_type in self.MIME_MAP.keys():
|
||||
for book_type in self.FORMATS:
|
||||
for filename in fnmatch.filter(files, '*.%s' % (book_type)):
|
||||
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.'))
|
||||
|
||||
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:
|
||||
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)
|
||||
size = sum(sizes)
|
||||
@ -136,9 +138,10 @@ class USBMS(Device):
|
||||
else:
|
||||
book_title = os.path.splitext(filename)[0].replace('_', ' ')
|
||||
|
||||
fileext = os.path.splitext(filename)[1]
|
||||
if fileext in cls.MIME_MAP.keys():
|
||||
book_mime = cls.MIME_MAP[fileext]
|
||||
fileext = os.path.splitext(filename)[1][1:]
|
||||
|
||||
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
|
||||
|
||||
|
@ -67,6 +67,7 @@ def txt2opf(path, tdir, opts):
|
||||
def pdf2opf(path, tdir, opts):
|
||||
from calibre.ebooks.lrf.pdf.convert_from import generate_html
|
||||
generate_html(path, tdir)
|
||||
opts.dont_split_on_page_breaks = True
|
||||
return os.path.join(tdir, 'metadata.opf')
|
||||
|
||||
def epub2opf(path, tdir, opts):
|
||||
|
@ -335,7 +335,7 @@ class PreProcessor(object):
|
||||
# Fix pdftohtml markup
|
||||
PDFTOHTML = [
|
||||
# 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
|
||||
(re.compile(r'\d+<br>', re.IGNORECASE), lambda match: ''),
|
||||
# Remove <br> and replace <br><br> with <p>
|
||||
@ -560,7 +560,7 @@ class Processor(Parser):
|
||||
hr = etree.Element('hr')
|
||||
if elem.getprevious() is None:
|
||||
elem.getparent()[:0] = [hr]
|
||||
else:
|
||||
elif elem.getparent() is not None:
|
||||
insert = None
|
||||
for i, c in enumerate(elem.getparent()):
|
||||
if c is elem:
|
||||
|
@ -144,16 +144,15 @@ class ReBinary(object):
|
||||
NSRMAP = {'': None, XML_NS: 'xml'}
|
||||
|
||||
def __init__(self, root, path, oeb, map=HTML_MAP):
|
||||
self.path = path
|
||||
self.item = item
|
||||
self.logger = oeb.logger
|
||||
self.dir = os.path.dirname(path)
|
||||
self.manifest = oeb.manifest
|
||||
self.tags, self.tattrs = map
|
||||
self.buf = StringIO()
|
||||
self.anchors = []
|
||||
self.page_breaks = []
|
||||
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.content = self.buf.getvalue()
|
||||
self.ahc = self.build_ahc() if is_html else None
|
||||
@ -210,6 +209,8 @@ class ReBinary(object):
|
||||
if attr in ('href', 'src'):
|
||||
value = urlnormalize(value)
|
||||
path, frag = urldefrag(value)
|
||||
if self.item:
|
||||
path = self.item.abshref(path)
|
||||
prefix = unichr(3)
|
||||
if path in self.manifest.hrefs:
|
||||
prefix = unichr(2)
|
||||
@ -222,7 +223,7 @@ class ReBinary(object):
|
||||
elif attr.startswith('ms--'):
|
||||
attr = '%' + attr[4:]
|
||||
elif tag == 'link' and attr == 'type' and value in OEB_STYLES:
|
||||
value = OEB_CSS_MIME
|
||||
value = CSS_MIME
|
||||
if attr in tattrs:
|
||||
self.write(tattrs[attr])
|
||||
else:
|
||||
@ -275,7 +276,7 @@ class ReBinary(object):
|
||||
def build_ahc(self):
|
||||
if len(self.anchors) > 6:
|
||||
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.write(unichr(len(self.anchors)).encode('utf-8'))
|
||||
for anchor, offset in self.anchors:
|
||||
@ -473,7 +474,7 @@ class LitWriter(object):
|
||||
secnum = 0
|
||||
if not isinstance(data, basestring):
|
||||
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 + '/aht', rebin.aht, 0)
|
||||
item.page_breaks = rebin.page_breaks
|
||||
@ -552,7 +553,7 @@ class LitWriter(object):
|
||||
meta.attrib['ms--minimum_level'] = '0'
|
||||
meta.attrib['ms--attr5'] = '1'
|
||||
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
|
||||
self._meta = meta
|
||||
self._add_file('/meta', meta)
|
||||
|
@ -425,7 +425,7 @@ def do_convert(path_to_file, opts, notification=lambda m, p: p, output_format='l
|
||||
thumbnail = None
|
||||
if not pages:
|
||||
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)
|
||||
if not pages:
|
||||
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':
|
||||
create_pdf(pages, opts.profile, opts, thumbnail=thumbnail)
|
||||
shutil.rmtree(tdir)
|
||||
if not opts.no_process:
|
||||
if not getattr(opts, 'no_process', False):
|
||||
shutil.rmtree(tdir2)
|
||||
|
||||
|
||||
@ -457,7 +457,7 @@ def main(args=sys.argv, notification=None, output_format='lrf'):
|
||||
|
||||
if not callable(notification):
|
||||
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
|
||||
|
||||
source = os.path.abspath(args[1])
|
||||
|
@ -122,7 +122,7 @@ class HTMLConverter(object, LoggingInterface):
|
||||
# Fix pdftohtml markup
|
||||
PDFTOHTML = [
|
||||
# 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
|
||||
(re.compile(r'\d+<br>', re.IGNORECASE), lambda match: ''),
|
||||
# Remove <br> and replace <br><br> with <p>
|
||||
|
@ -799,18 +799,39 @@ class Text(LRFStream):
|
||||
length = len(self.stream)
|
||||
style = self.style.as_dict()
|
||||
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:
|
||||
|
||||
# Is there some text beofre a tag?
|
||||
pos = self.stream.find('\xf5', stream.tell()) - 1
|
||||
if pos > 0:
|
||||
self.add_text(self.stream[stream.tell():pos])
|
||||
stream.seek(pos)
|
||||
elif pos == -2: # No tags in this stream
|
||||
# Is there some text before a tag?
|
||||
def find_first_tag(start):
|
||||
pos = self.stream.find('\xf5', start)
|
||||
if pos == -1:
|
||||
return -1
|
||||
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)
|
||||
stream.seek(0, 2)
|
||||
print repr(self.stream)
|
||||
break
|
||||
|
||||
tag = Tag(stream)
|
||||
@ -1166,7 +1187,8 @@ class TOCObject(LRFStream):
|
||||
refpage = struct.unpack("<I", stream.read(4))[0]
|
||||
refobj = struct.unpack("<I", stream.read(4))[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))
|
||||
c -= 1
|
||||
|
||||
|
@ -33,7 +33,6 @@ class EXTHHeader(object):
|
||||
self.length, self.num_items = struct.unpack('>LL', raw[4:12])
|
||||
raw = raw[12:]
|
||||
pos = 0
|
||||
|
||||
self.mi = MetaInformation('Unknown', ['Unknown'])
|
||||
self.has_fake_cover = True
|
||||
|
||||
@ -49,9 +48,17 @@ class EXTHHeader(object):
|
||||
self.cover_offset, = struct.unpack('>L', content)
|
||||
elif id == 202:
|
||||
self.thumbnail_offset, = struct.unpack('>L', content)
|
||||
#else:
|
||||
# print 'unknown record', id, repr(content)
|
||||
title = re.search(r'\0+([^\0]+)\0+', raw[pos:])
|
||||
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):
|
||||
@ -67,7 +74,8 @@ class EXTHHeader(object):
|
||||
if not self.mi.tags:
|
||||
self.mi.tags = []
|
||||
self.mi.tags.append(content.decode(codec, 'ignore'))
|
||||
|
||||
#else:
|
||||
# print 'unhandled metadata record', id, repr(content), codec
|
||||
|
||||
|
||||
class BookHeader(object):
|
||||
@ -466,6 +474,10 @@ def get_metadata(stream):
|
||||
cover = os.path.join(tdir, mi.cover)
|
||||
if os.access(cover, os.R_OK):
|
||||
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
|
||||
|
||||
def option_parser():
|
||||
|
@ -29,7 +29,7 @@ def config(defaults=None):
|
||||
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'))
|
||||
help=_('A file generated by ghostscript which allows each page to be individually cropped [gs -dSAFER -dNOPAUSE -dBATCH -sDEVICE=bbox > bounding] '))
|
||||
return c
|
||||
|
||||
|
||||
@ -38,14 +38,24 @@ def option_parser():
|
||||
return c.option_parser(usage=_('''\
|
||||
%prog [options] file.pdf
|
||||
|
||||
Crop a pdf.
|
||||
Crops 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"))
|
||||
try:
|
||||
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:
|
||||
try:
|
||||
bounding = open( opts.bounding , 'r' )
|
||||
@ -53,7 +63,7 @@ def main(args=sys.argv):
|
||||
except:
|
||||
print 'Error opening %s' % opts.bounding
|
||||
return 1
|
||||
output_pdf = PdfFileWriter()
|
||||
output_pdf = PdfFileWriter(title=title,author=author)
|
||||
for page_number in range (0, input_pdf.getNumPages() ):
|
||||
page = input_pdf.getPage(page_number)
|
||||
if opts.bounding != None:
|
||||
|
@ -75,7 +75,13 @@ def save_recipes(recipes):
|
||||
|
||||
def load_recipes():
|
||||
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):
|
||||
|
||||
@ -438,7 +444,7 @@ class Scheduler(QObject):
|
||||
self.lock.unlock()
|
||||
|
||||
def main(args=sys.argv):
|
||||
app = QApplication([])
|
||||
QApplication([])
|
||||
from calibre.library.database2 import LibraryDatabase2
|
||||
d = SchedulerDialog(LibraryDatabase2('/home/kovid/documents/library'))
|
||||
d.exec_()
|
||||
|
BIN
src/calibre/gui2/images/news/telepolis.png
Normal file
BIN
src/calibre/gui2/images/news/telepolis.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 586 B |
@ -406,7 +406,8 @@ class Document(QGraphicsScene):
|
||||
for font in lrf.font_map:
|
||||
fdata = QByteArray(lrf.font_map[font].data)
|
||||
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:
|
||||
from calibre.ebooks.lrf.fonts.liberation import LiberationMono_BoldItalic
|
||||
|
@ -889,6 +889,9 @@ class Main(MainWindow, Ui_MainWindow):
|
||||
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 = [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)
|
||||
names = []
|
||||
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
|
||||
|
||||
|
||||
def shutdown(self):
|
||||
self.write_settings()
|
||||
def shutdown(self, write_settings=True):
|
||||
if write_settings:
|
||||
self.write_settings()
|
||||
self.job_manager.terminate_all_jobs()
|
||||
self.device_manager.keep_going = False
|
||||
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):
|
||||
self.write_settings()
|
||||
if self.system_tray_icon.isVisible():
|
||||
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_()
|
||||
@ -1509,7 +1514,7 @@ in which you want to store your books files. Any existing books will be automati
|
||||
else:
|
||||
if self.confirm_quit():
|
||||
try:
|
||||
self.shutdown()
|
||||
self.shutdown(write_settings=False)
|
||||
except:
|
||||
pass
|
||||
e.accept()
|
||||
|
@ -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):
|
||||
return self.conn.get('SELECT id FROM books where id=?', (id,), all=False) is not None
|
||||
|
||||
|
@ -217,7 +217,11 @@ class ResultCache(SearchQueryParser):
|
||||
return self.index(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):
|
||||
for id in ids:
|
||||
@ -558,6 +562,14 @@ class LibraryDatabase2(LibraryDatabase):
|
||||
return img
|
||||
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):
|
||||
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')
|
||||
|
@ -217,8 +217,7 @@ class Server(object):
|
||||
pos = pos.replace(month = 1)
|
||||
else:
|
||||
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 = [self.get_slice(*m) for m in _months]
|
||||
x = [m.min for m in _months]
|
||||
|
@ -35,7 +35,7 @@ class Distribution(object):
|
||||
('xdg-utils', '1.0.2', 'xdg-utils', 'xdg-utils', 'xdg-utils'),
|
||||
('dbus-python', '0.82.2', 'dbus-python', 'python-dbus', 'dbus-python'),
|
||||
('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'),
|
||||
]
|
||||
|
||||
@ -205,23 +205,7 @@ select Install.</li>
|
||||
<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>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 you must create the file <code>~/.MacOSX/environment.plist</code> as shown below:
|
||||
<pre class="wiki">
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!DOCTYPE plist PUBLIC "-//Apple Computer//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||
<plist version="1.0">
|
||||
<dict>
|
||||
<key>LANG</key>
|
||||
<string>de_DE</string>
|
||||
</dict>
|
||||
</plist>
|
||||
</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>
|
||||
<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>
|
||||
</ol>
|
||||
'''))
|
||||
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
@ -338,7 +338,7 @@ class ZipInfo (object):
|
||||
if isinstance(self.filename, unicode):
|
||||
try:
|
||||
return self.filename.encode('ascii'), self.flag_bits
|
||||
except UnicodeEncodeError:
|
||||
except:
|
||||
return self.filename.encode('utf-8'), self.flag_bits | 0x800
|
||||
else:
|
||||
return self.filename, self.flag_bits
|
||||
|
@ -765,6 +765,8 @@ class BasicNewsRecipe(object, LoggingInterface):
|
||||
self.log_debug(traceback.format_exc())
|
||||
if cu is not None:
|
||||
ext = cu.rpartition('.')[-1]
|
||||
if '?' in ext:
|
||||
ext = ''
|
||||
ext = ext.lower() if ext else 'jpg'
|
||||
self.report_progress(1, _('Downloading cover from %s')%cu)
|
||||
cpath = os.path.join(self.output_dir, 'cover.'+ext)
|
||||
|
@ -21,7 +21,7 @@ recipe_modules = ['recipe_' + r for r in (
|
||||
'linux_magazine', 'telegraph_uk', 'utne', 'sciencedaily', 'forbes',
|
||||
'time_magazine', 'endgadget', 'fudzilla', 'nspm_int', 'nspm', 'pescanik',
|
||||
'spiegel_int', 'themarketticker', 'tomshardware', 'xkcd', 'ftd', 'zdnet',
|
||||
'joelonsoftware',
|
||||
'joelonsoftware', 'telepolis', 'common_dreams', 'nin',
|
||||
)]
|
||||
|
||||
import re, imp, inspect, time, os
|
||||
|
@ -42,3 +42,9 @@ class ChristianScienceMonitor(BasicNewsRecipe):
|
||||
feeds[-1][1].append(art)
|
||||
return feeds
|
||||
|
||||
def postprocess_html(self, soup, first_fetch):
|
||||
html = soup.find('html')
|
||||
if html is None:
|
||||
return soup
|
||||
html.extract()
|
||||
return html
|
||||
|
16
src/calibre/web/feeds/recipes/recipe_common_dreams.py
Normal file
16
src/calibre/web/feeds/recipes/recipe_common_dreams.py
Normal 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')
|
||||
]
|
@ -49,8 +49,10 @@ class Economist(BasicNewsRecipe):
|
||||
if not index_started:
|
||||
continue
|
||||
text = string.capwords(text)
|
||||
feeds[text] = []
|
||||
ans.append(text)
|
||||
if text not in feeds.keys():
|
||||
feeds[text] = []
|
||||
if text not in ans:
|
||||
ans.append(text)
|
||||
key = text
|
||||
continue
|
||||
if key is None:
|
||||
|
55
src/calibre/web/feeds/recipes/recipe_nin.py
Normal file
55
src/calibre/web/feeds/recipes/recipe_nin.py
Normal 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
|
34
src/calibre/web/feeds/recipes/recipe_telepolis.py
Normal file
34
src/calibre/web/feeds/recipes/recipe_telepolis.py
Normal 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')]
|
@ -33,6 +33,7 @@ class TimesOnline(BasicNewsRecipe):
|
||||
('Sports News', 'http://www.timesonline.co.uk/tol/feeds/rss/sport.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'),
|
||||
('Literary Supplement', 'http://www.timesonline.co.uk/tol/feeds/rss/thetls.xml'),
|
||||
]
|
||||
|
||||
def print_version(self, url):
|
||||
|
@ -55,7 +55,7 @@ from utils import readNonWhitespace, readUntilWhitespace, ConvertFunctionsToVirt
|
||||
# This class supports writing PDF files out, given pages produced by another
|
||||
# class (typically {@link #PdfFileReader PdfFileReader}).
|
||||
class PdfFileWriter(object):
|
||||
def __init__(self):
|
||||
def __init__(self,title=u"Unknown",author=u"Unknown"):
|
||||
self._header = "%PDF-1.3"
|
||||
self._objects = [] # array of indirect objects
|
||||
|
||||
@ -71,7 +71,9 @@ class PdfFileWriter(object):
|
||||
# info object
|
||||
info = DictionaryObject()
|
||||
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)
|
||||
|
||||
|
Loading…
x
Reference in New Issue
Block a user