Merge upstream changes.

This commit is contained in:
Marshall T. Vandegrift 2008-12-17 13:45:32 -05:00
commit 28064174fc
51 changed files with 5674 additions and 4606 deletions

View File

@ -5,9 +5,5 @@
<pydev_property name="org.python.pydev.PYTHON_PROJECT_VERSION">python 2.5</pydev_property> <pydev_property name="org.python.pydev.PYTHON_PROJECT_VERSION">python 2.5</pydev_property>
<pydev_pathproperty name="org.python.pydev.PROJECT_SOURCE_PATH"> <pydev_pathproperty name="org.python.pydev.PROJECT_SOURCE_PATH">
<path>/calibre/src</path> <path>/calibre/src</path>
<path>/calibre/devices</path>
<path>/calibre/libprs500.devices.prs500</path>
<path>/calibre/prs500</path>
<path>/calibre/gui2</path>
</pydev_pathproperty> </pydev_pathproperty>
</pydev_project> </pydev_project>

View File

@ -122,6 +122,8 @@ def freeze():
elif exe not in executables: elif exe not in executables:
print >>sys.stderr, 'Invalid invocation of calibre loader. CALIBRE_CX_EXE=%%s is unknown'%%exe print >>sys.stderr, 'Invalid invocation of calibre loader. CALIBRE_CX_EXE=%%s is unknown'%%exe
else: else:
from PyQt4.QtCore import QCoreApplication
QCoreApplication.setLibraryPaths([sys.frozen_path, os.path.join(sys.frozen_path, "qtplugins")])
sys.argv[0] = exe sys.argv[0] = exe
module, func = executables[exe] module, func = executables[exe]
module = __import__(module, fromlist=[1]) module = __import__(module, fromlist=[1])
@ -179,7 +181,7 @@ def freeze():
if not f.endswith('.so') or 'designer' in dirpath or 'codecs' in dirpath or 'sqldrivers' in dirpath: if not f.endswith('.so') or 'designer' in dirpath or 'codecs' in dirpath or 'sqldrivers' in dirpath:
continue continue
f = os.path.join(dirpath, f) f = os.path.join(dirpath, f)
dest_dir = dirpath.replace(plugdir, os.path.join(FREEZE_DIR, 'qtlugins')) dest_dir = dirpath.replace(plugdir, os.path.join(FREEZE_DIR, 'qtplugins'))
copy_binary(f, dest_dir) copy_binary(f, dest_dir)
print 'Creating launchers' print 'Creating launchers'

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.113' __version__ = '0.4.115'
__author__ = "Kovid Goyal <kovid@kovidgoyal.net>" __author__ = "Kovid Goyal <kovid@kovidgoyal.net>"
''' '''
Various run time constants. Various run time constants.

View File

@ -88,10 +88,10 @@ def initialize_container(path_to_container, opf_name='metadata.opf'):
zf.writestr('META-INF/container.xml', CONTAINER) zf.writestr('META-INF/container.xml', CONTAINER)
return zf return zf
def config(defaults=None): def config(defaults=None, name='epub'):
desc = _('Options to control the conversion to EPUB') desc = _('Options to control the conversion to EPUB')
if defaults is None: if defaults is None:
c = Config('epub', desc) c = Config(name, desc)
else: else:
c = StringConfig(defaults, desc) c = StringConfig(defaults, desc)

View File

@ -116,7 +116,8 @@ def unarchive(path, tdir):
return f, ext return f, ext
return find_html_index(files) return find_html_index(files)
def any2epub(opts, path, notification=None): def any2epub(opts, path, notification=None, create_epub=True,
oeb_cover=False, extract_to=None):
ext = os.path.splitext(path)[1] ext = os.path.splitext(path)[1]
if not ext: if not ext:
raise ValueError('Unknown file type: '+path) raise ValueError('Unknown file type: '+path)
@ -139,7 +140,9 @@ def any2epub(opts, path, notification=None):
raise ValueError('Conversion from %s is not supported'%ext.upper()) raise ValueError('Conversion from %s is not supported'%ext.upper())
print 'Creating EPUB file...' print 'Creating EPUB file...'
html2epub(path, opts, notification=notification) html2epub(path, opts, notification=notification,
create_epub=create_epub, oeb_cover=oeb_cover,
extract_to=extract_to)
def config(defaults=None): def config(defaults=None):
return common_config(defaults=defaults) return common_config(defaults=defaults)
@ -148,14 +151,14 @@ def config(defaults=None):
def formats(): def formats():
return ['html', 'rar', 'zip', 'oebzip']+list(MAP.keys()) return ['html', 'rar', 'zip', 'oebzip']+list(MAP.keys())
def option_parser(): USAGE = _('''\
return config().option_parser(usage=_('''\
%%prog [options] filename %%prog [options] filename
Convert any of a large number of ebook formats to an epub file. Supported formats are: %s Convert any of a large number of ebook formats to a %s file. Supported formats are: %s
''')%formats() ''')
)
def option_parser(usage=USAGE):
return config().option_parser(usage=usage%('EPUB', formats()))
def main(args=sys.argv): def main(args=sys.argv):
parser = option_parser() parser = option_parser()

View File

@ -32,14 +32,14 @@ Conversion of HTML/OPF files follows several stages:
* The EPUB container is created. * The EPUB container is created.
''' '''
import os, sys, cStringIO, logging, re, functools import os, sys, cStringIO, logging, re, functools, shutil
from lxml.etree import XPath from lxml.etree import XPath
from lxml import html from lxml import html
from PyQt4.Qt import QApplication, QPixmap from PyQt4.Qt import QApplication, QPixmap
from calibre.ebooks.html import Processor, merge_metadata, get_filelist,\ from calibre.ebooks.html import Processor, merge_metadata, get_filelist,\
opf_traverse, create_metadata, rebase_toc, Link opf_traverse, create_metadata, rebase_toc, Link, parser
from calibre.ebooks.epub import config as common_config, tostring from calibre.ebooks.epub import config as common_config, tostring
from calibre.ptempfile import TemporaryDirectory from calibre.ptempfile import TemporaryDirectory
from calibre.ebooks.metadata.toc import TOC from calibre.ebooks.metadata.toc import TOC
@ -62,9 +62,10 @@ def remove_bad_link(element, attribute, link, pos):
def check(opf_path, pretty_print): def check(opf_path, pretty_print):
''' '''
Find a remove all invalid links in the HTML files Find and remove all invalid links in the HTML files
''' '''
print '\tChecking files for bad links...' logger = logging.getLogger('html2epub')
logger.info('\tChecking files for bad links...')
pathtoopf = os.path.abspath(opf_path) pathtoopf = os.path.abspath(opf_path)
with CurrentDir(os.path.dirname(pathtoopf)): with CurrentDir(os.path.dirname(pathtoopf)):
opf = OPF(open(pathtoopf, 'rb'), os.path.dirname(pathtoopf)) opf = OPF(open(pathtoopf, 'rb'), os.path.dirname(pathtoopf))
@ -76,7 +77,7 @@ def check(opf_path, pretty_print):
for path in html_files: for path in html_files:
base = os.path.dirname(path) base = os.path.dirname(path)
root = html.fromstring(open(content(path), 'rb').read()) root = html.fromstring(open(content(path), 'rb').read(), parser=parser)
for element, attribute, link, pos in list(root.iterlinks()): for element, attribute, link, pos in list(root.iterlinks()):
link = to_unicode(link) link = to_unicode(link)
plink = Link(link, base) plink = Link(link, base)
@ -209,17 +210,16 @@ TITLEPAGE = '''\
</html> </html>
''' '''
def create_cover_image(src, dest, screen_size): def create_cover_image(src, dest, screen_size, rescale_cover=True):
from PyQt4.Qt import QApplication, QImage, Qt
if QApplication.instance() is None:
app = QApplication([])
app
im = QImage()
try: try:
from PyQt4.Qt import QImage, Qt
if QApplication.instance() is None:
QApplication([])
im = QImage()
im.load(src) im.load(src)
if im.isNull(): if im.isNull():
raise ValueError raise ValueError('Invalid cover image')
if screen_size is not None: if rescale_cover and screen_size is not None:
width, height = im.width(), im.height() width, height = im.width(), im.height()
dw, dh = (screen_size[0]-width)/float(width), (screen_size[1]-height)/float(height) dw, dh = (screen_size[0]-width)/float(width), (screen_size[1]-height)/float(height)
delta = min(dw, dh) delta = min(dw, dh)
@ -227,7 +227,7 @@ def create_cover_image(src, dest, screen_size):
nwidth = int(width + delta*(width)) nwidth = int(width + delta*(width))
nheight = int(height + delta*(height)) nheight = int(height + delta*(height))
im = im.scaled(int(nwidth), int(nheight), Qt.IgnoreAspectRatio, Qt.SmoothTransformation) im = im.scaled(int(nwidth), int(nheight), Qt.IgnoreAspectRatio, Qt.SmoothTransformation)
im.save(dest) im.save(dest)
except: except:
import traceback import traceback
traceback.print_exc() traceback.print_exc()
@ -240,7 +240,6 @@ def process_title_page(mi, filelist, htmlfilemap, opts, tdir):
if mi.cover: if mi.cover:
if f(filelist[0].path) == f(mi.cover): if f(filelist[0].path) == f(mi.cover):
old_title_page = htmlfilemap[filelist[0].path] old_title_page = htmlfilemap[filelist[0].path]
#logger = logging.getLogger('html2epub') #logger = logging.getLogger('html2epub')
metadata_cover = mi.cover metadata_cover = mi.cover
if metadata_cover and not os.path.exists(metadata_cover): if metadata_cover and not os.path.exists(metadata_cover):
@ -249,14 +248,15 @@ def process_title_page(mi, filelist, htmlfilemap, opts, tdir):
cpath = '/'.join(('resources', '_cover_.jpg')) cpath = '/'.join(('resources', '_cover_.jpg'))
cover_dest = os.path.join(tdir, 'content', *cpath.split('/')) cover_dest = os.path.join(tdir, 'content', *cpath.split('/'))
if metadata_cover is not None: if metadata_cover is not None:
if not create_cover_image(metadata_cover, cover_dest, opts.profile.screen_size): if not create_cover_image(metadata_cover, cover_dest,
opts.profile.screen_size):
metadata_cover = None metadata_cover = None
specified_cover = opts.cover specified_cover = opts.cover
if specified_cover and not os.path.exists(specified_cover): if specified_cover and not os.path.exists(specified_cover):
specified_cover = None specified_cover = None
if specified_cover is not None: if specified_cover is not None:
if not create_cover_image(specified_cover, cover_dest, opts.profile.screen_size): if not create_cover_image(specified_cover, cover_dest,
opts.profile.screen_size):
specified_cover = None specified_cover = None
cover = metadata_cover if specified_cover is None or (opts.prefer_metadata_cover and metadata_cover is not None) else specified_cover cover = metadata_cover if specified_cover is None or (opts.prefer_metadata_cover and metadata_cover is not None) else specified_cover
@ -271,9 +271,16 @@ def process_title_page(mi, filelist, htmlfilemap, opts, tdir):
elif os.path.exists(cover_dest): elif os.path.exists(cover_dest):
os.remove(cover_dest) os.remove(cover_dest)
return None, old_title_page is not None return None, old_title_page is not None
def convert(htmlfile, opts, notification=None): def find_oeb_cover(htmlfile):
if os.stat(htmlfile).st_size > 2048:
return None
match = re.search(r'(?i)<img[^<>]+src\s*=\s*[\'"](.+?)[\'"]', open(htmlfile, 'rb').read())
if match:
return match.group(1)
def convert(htmlfile, opts, notification=None, create_epub=True,
oeb_cover=False, extract_to=None):
htmlfile = os.path.abspath(htmlfile) htmlfile = os.path.abspath(htmlfile)
if opts.output is None: if opts.output is None:
opts.output = os.path.splitext(os.path.basename(htmlfile))[0] + '.epub' opts.output = os.path.splitext(os.path.basename(htmlfile))[0] + '.epub'
@ -325,7 +332,7 @@ def convert(htmlfile, opts, notification=None):
title_page, has_title_page = process_title_page(mi, filelist, htmlfile_map, opts, tdir) title_page, has_title_page = process_title_page(mi, filelist, htmlfile_map, opts, tdir)
spine = [htmlfile_map[f.path] for f in filelist] spine = [htmlfile_map[f.path] for f in filelist]
if title_page is not None: if not oeb_cover and title_page is not None:
spine = [title_page] + spine spine = [title_page] + spine
mi.cover = None mi.cover = None
mi.cover_data = (None, None) mi.cover_data = (None, None)
@ -357,24 +364,43 @@ def convert(htmlfile, opts, notification=None):
check(opf_path, opts.pretty_print) check(opf_path, opts.pretty_print)
opf = OPF(opf_path, tdir) opf = OPF(opf_path, tdir)
opf.remove_guide() opf.remove_guide()
if has_title_page: oeb_cover_file = None
if oeb_cover and title_page is not None:
oeb_cover_file = find_oeb_cover(os.path.join(tdir, 'content', title_page))
if has_title_page or (oeb_cover and oeb_cover_file):
opf.create_guide_element() opf.create_guide_element()
opf.add_guide_item('cover', 'Cover', 'content/'+spine[0]) if has_title_page and not oeb_cover:
opf.add_guide_item('cover', 'Cover', 'content/'+spine[0])
if oeb_cover and oeb_cover_file:
opf.add_guide_item('cover', 'Cover', 'content/'+oeb_cover_file)
opf.add_path_to_manifest(os.path.join(tdir, 'content', 'resources', '_cover_.jpg'), 'image/jpeg') cpath = os.path.join(tdir, 'content', 'resources', '_cover_.jpg')
if os.path.exists(cpath):
opf.add_path_to_manifest(cpath, 'image/jpeg')
with open(opf_path, 'wb') as f: with open(opf_path, 'wb') as f:
raw = opf.render() raw = opf.render()
if not raw.startswith('<?xml '): if not raw.startswith('<?xml '):
raw = '<?xml version="1.0" encoding="UTF-8"?>\n'+raw raw = '<?xml version="1.0" encoding="UTF-8"?>\n'+raw
f.write(raw) f.write(raw)
epub = initialize_container(opts.output) if create_epub:
epub.add_dir(tdir) epub = initialize_container(opts.output)
epub.add_dir(tdir)
epub.close()
logger.info(_('Output written to ')+opts.output)
if opts.show_opf: if opts.show_opf:
print open(os.path.join(tdir, 'metadata.opf')).read() print open(os.path.join(tdir, 'metadata.opf')).read()
logger.info('Output written to %s'%opts.output)
if opts.extract_to is not None: if opts.extract_to is not None:
epub.extractall(opts.extract_to) if os.path.exists(opts.extract_to):
epub.close() shutil.rmtree(opts.extract_to)
shutil.copytree(tdir, opts.extract_to)
if extract_to is not None:
if os.path.exists(extract_to):
shutil.rmtree(extract_to)
shutil.copytree(tdir, extract_to)
def main(args=sys.argv): def main(args=sys.argv):

View File

@ -170,7 +170,10 @@ class EbookIterator(object):
dat = self.serialize_bookmarks(bookmarks) dat = self.serialize_bookmarks(bookmarks)
if os.path.splitext(self.pathtoebook)[1].lower() == '.epub' and \ if os.path.splitext(self.pathtoebook)[1].lower() == '.epub' and \
os.access(self.pathtoebook, os.R_OK): os.access(self.pathtoebook, os.R_OK):
zf = open(self.pathtoebook, 'r+b') try:
zf = open(self.pathtoebook, 'r+b')
except IOError:
return
zipf = ZipFile(zf, mode='a') zipf = ZipFile(zf, mode='a')
for name in zipf.namelist(): for name in zipf.namelist():
if name == 'META-INF/calibre_bookmarks.txt': if name == 'META-INF/calibre_bookmarks.txt':

View File

@ -0,0 +1,59 @@
from __future__ import with_statement
__license__ = 'GPL v3'
__copyright__ = '2008, Kovid Goyal kovid@kovidgoyal.net'
__docformat__ = 'restructuredtext en'
'''
Convert any ebook format to LIT.
'''
import sys, os, glob, logging
from calibre.ebooks.epub.from_any import any2epub, formats, USAGE
from calibre.ebooks.epub import config as common_config
from calibre.ptempfile import TemporaryDirectory
from calibre.ebooks.lit.writer import oeb2lit
def config(defaults=None):
c = common_config(defaults=defaults, name='lit')
return c
def option_parser(usage=USAGE):
return config().option_parser(usage=usage%('LIT', formats()))
def any2lit(opts, path):
ext = os.path.splitext(path)[1]
if not ext:
raise ValueError('Unknown file type: '+path)
ext = ext.lower()[1:]
if opts.output is None:
opts.output = os.path.splitext(os.path.basename(path))[0]+'.lit'
opts.output = os.path.abspath(opts.output)
orig_output = opts.output
with TemporaryDirectory('_any2lit') as tdir:
oebdir = os.path.join(tdir, 'oeb')
os.mkdir(oebdir)
opts.output = os.path.join(tdir, 'dummy.epub')
opts.profile = 'None'
any2epub(opts, path, create_epub=False, oeb_cover=True, extract_to=oebdir)
opf = glob.glob(os.path.join(oebdir, '*.opf'))[0]
opts.output = orig_output
logging.getLogger('html2epub').info(_('Creating LIT file from EPUB...'))
oeb2lit(opts, opf)
def main(args=sys.argv):
parser = option_parser()
opts, args = parser.parse_args(args)
if len(args) < 2:
parser.print_help()
print 'No input file specified.'
return 1
any2lit(opts, args[1])
return 0
if __name__ == '__main__':
sys.exit(main())

View File

@ -13,7 +13,9 @@ from types import StringTypes
from itertools import izip, count from itertools import izip, count
from urlparse import urldefrag, urlparse, urlunparse from urlparse import urldefrag, urlparse, urlunparse
from urllib import unquote as urlunquote from urllib import unquote as urlunquote
import logging
from lxml import etree from lxml import etree
from calibre import LoggingInterface
XML_PARSER = etree.XMLParser(recover=True, resolve_entities=False) XML_PARSER = etree.XMLParser(recover=True, resolve_entities=False)
XML_NS = 'http://www.w3.org/XML/1998/namespace' XML_NS = 'http://www.w3.org/XML/1998/namespace'
@ -88,6 +90,13 @@ def urlnormalize(href):
return urlunparse(parts) return urlunparse(parts)
class FauxLogger(object):
def __getattr__(self, name):
return self
def __call__(self, message):
print message
class AbstractContainer(object): class AbstractContainer(object):
def read_xml(self, path): def read_xml(self, path):
return etree.fromstring( return etree.fromstring(
@ -108,6 +117,10 @@ class DirContainer(AbstractContainer):
with open(urlunquote(path), 'wb') as f: with open(urlunquote(path), 'wb') as f:
return f.write(data) return f.write(data)
def exists(self, path):
path = os.path.join(self.rootdir, path)
return os.path.isfile(path)
class Metadata(object): class Metadata(object):
TERMS = set(['contributor', 'coverage', 'creator', 'date', 'description', TERMS = set(['contributor', 'coverage', 'creator', 'date', 'description',
@ -530,13 +543,14 @@ class TOC(object):
node.to_ncx(point, playorder, depth+1) node.to_ncx(point, playorder, depth+1)
return parent return parent
class OEBBook(object): class OEBBook(object):
def __init__(self, opfpath=None, container=None): def __init__(self, opfpath=None, container=None, logger=FauxLogger()):
if not container: if not container:
container = DirContainer(os.path.dirname(opfpath)) container = DirContainer(os.path.dirname(opfpath))
opfpath = os.path.basename(opfpath) opfpath = os.path.basename(opfpath)
self.container = container self.container = container
self.logger = logger
opf = self._read_opf(opfpath) opf = self._read_opf(opfpath)
self._all_from_opf(opf) self._all_from_opf(opf)
@ -590,17 +604,28 @@ class OEBBook(object):
if item.id == uid: if item.id == uid:
self.uid = item self.uid = item
break break
else:
self.logger.log_warn(u'Unique-identifier %r not found.' % uid)
self.uid = metadata.identifier[0]
def _manifest_from_opf(self, opf): def _manifest_from_opf(self, opf):
self.manifest = manifest = Manifest(self) self.manifest = manifest = Manifest(self)
for elem in xpath(opf, '/o2:package/o2:manifest/o2:item'): for elem in xpath(opf, '/o2:package/o2:manifest/o2:item'):
manifest.add(elem.get('id'), elem.get('href'), href = elem.get('href')
elem.get('media-type'), elem.get('fallback')) if not self.container.exists(href):
self.logger.log_warn(u'Manifest item %r not found.' % href)
continue
manifest.add(elem.get('id'), href, elem.get('media-type'),
elem.get('fallback'))
def _spine_from_opf(self, opf): def _spine_from_opf(self, opf):
self.spine = spine = Spine(self) self.spine = spine = Spine(self)
for elem in xpath(opf, '/o2:package/o2:spine/o2:itemref'): for elem in xpath(opf, '/o2:package/o2:spine/o2:itemref'):
item = self.manifest[elem.get('idref')] idref = elem.get('idref')
if idref not in self.manifest:
self.logger.log_warn(u'Spine item %r not found.' % idref)
continue
item = self.manifest[idref]
spine.add(item, elem.get('linear')) spine.add(item, elem.get('linear'))
extras = [] extras = []
for item in self.manifest.values(): for item in self.manifest.values():
@ -614,7 +639,11 @@ class OEBBook(object):
def _guide_from_opf(self, opf): def _guide_from_opf(self, opf):
self.guide = guide = Guide(self) self.guide = guide = Guide(self)
for elem in xpath(opf, '/o2:package/o2:guide/o2:reference'): for elem in xpath(opf, '/o2:package/o2:guide/o2:reference'):
guide.add(elem.get('type'), elem.get('title'), elem.get('href')) href = elem.get('href')
if href not in self.manifest.hrefs:
self.logger.log_warn(u'Guide reference %r not found' % href)
continue
guide.add(elem.get('type'), elem.get('title'), href)
def _toc_from_navpoint(self, toc, navpoint): def _toc_from_navpoint(self, toc, navpoint):
children = xpath(navpoint, 'ncx:navPoint') children = xpath(navpoint, 'ncx:navPoint')

View File

@ -9,7 +9,7 @@ __copyright__ = '2008, Marshall T. Vandegrift <llasram@gmail.com>'
import sys import sys
import os import os
from cStringIO import StringIO from cStringIO import StringIO
from struct import pack, unpack from struct import pack
from itertools import izip, count, chain from itertools import izip, count, chain
import time import time
import random import random
@ -17,19 +17,20 @@ import re
import copy import copy
import uuid import uuid
import functools import functools
import logging
from urlparse import urldefrag from urlparse import urldefrag
from urllib import unquote as urlunquote from urllib import unquote as urlunquote
from lxml import etree from lxml import etree
from calibre.ebooks.lit import LitError from calibre.ebooks.lit.reader import DirectoryEntry
from calibre.ebooks.lit.reader import msguid, DirectoryEntry
import calibre.ebooks.lit.maps as maps import calibre.ebooks.lit.maps as maps
from calibre.ebooks.lit.oeb import OEB_DOCS, OEB_STYLES, OEB_CSS_MIME, \ from calibre.ebooks.lit.oeb import OEB_DOCS, OEB_STYLES, OEB_CSS_MIME, \
CSS_MIME, XHTML_MIME, OPF_MIME, XML_NS, XML CSS_MIME, OPF_MIME, XML_NS, XML
from calibre.ebooks.lit.oeb import namespace, barename, urlnormalize, xpath from calibre.ebooks.lit.oeb import namespace, barename, urlnormalize, xpath
from calibre.ebooks.lit.oeb import OEBBook from calibre.ebooks.lit.oeb import FauxLogger, OEBBook
from calibre.ebooks.lit.stylizer import Stylizer from calibre.ebooks.lit.stylizer import Stylizer
from calibre.ebooks.lit.lzx import Compressor from calibre.ebooks.lit.lzx import Compressor
import calibre import calibre
from calibre import LoggingInterface
from calibre import plugins from calibre import plugins
msdes, msdeserror = plugins['msdes'] msdes, msdeserror = plugins['msdes']
import calibre.ebooks.lit.mssha1 as mssha1 import calibre.ebooks.lit.mssha1 as mssha1
@ -135,11 +136,15 @@ def decint(value):
def randbytes(n): def randbytes(n):
return ''.join(chr(random.randint(0, 255)) for x in xrange(n)) return ''.join(chr(random.randint(0, 255)) for x in xrange(n))
def warn(x):
print x
class ReBinary(object): 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, logger=FauxLogger()):
self.path = path self.path = path
self.logger = logger
self.dir = os.path.dirname(path) self.dir = os.path.dirname(path)
self.manifest = oeb.manifest self.manifest = oeb.manifest
self.tags, self.tattrs = map self.tags, self.tattrs = map
@ -268,8 +273,8 @@ class ReBinary(object):
def build_ahc(self): def build_ahc(self):
if len(self.anchors) > 6: if len(self.anchors) > 6:
print "calibre: warning: 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.path)
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:
@ -293,8 +298,9 @@ def preserve(function):
return wrapper return wrapper
class LitWriter(object): class LitWriter(object):
def __init__(self, oeb): def __init__(self, oeb, logger=FauxLogger()):
self._oeb = oeb self._oeb = oeb
self._logger = logger
self._litize_oeb() self._litize_oeb()
def _litize_oeb(self): def _litize_oeb(self):
@ -307,6 +313,9 @@ class LitWriter(object):
elif MS_COVER_TYPE in oeb.guide: elif MS_COVER_TYPE in oeb.guide:
href = oeb.guide[MS_COVER_TYPE].href href = oeb.guide[MS_COVER_TYPE].href
cover = oeb.manifest.hrefs[href] cover = oeb.manifest.hrefs[href]
elif 'cover' in oeb.guide:
href = oeb.guide['cover'].href
cover = oeb.manifest.hrefs[href]
else: else:
html = oeb.spine[0].data html = oeb.spine[0].data
imgs = xpath(html, '//img[position()=1]') imgs = xpath(html, '//img[position()=1]')
@ -319,7 +328,7 @@ class LitWriter(object):
if type not in oeb.guide: if type not in oeb.guide:
oeb.guide.add(type, title, cover.href) oeb.guide.add(type, title, cover.href)
else: else:
print "calibre: warning: No suitable cover image found." self._logger.log_warn('No suitable cover image found.')
def dump(self, stream): def dump(self, stream):
self._stream = stream self._stream = stream
@ -461,15 +470,16 @@ class LitWriter(object):
self._add_folder('/data') self._add_folder('/data')
for item in self._oeb.manifest.values(): for item in self._oeb.manifest.values():
if item.media_type not in LIT_MIMES: if item.media_type not in LIT_MIMES:
print "calibre: warning: File %r of unknown media-type %r " \ self._logger.log_warn("File %r of unknown media-type %r " \
"excluded from output." % (item.href, item.media_type) "excluded from output." % (item.href, item.media_type))
continue continue
name = '/data/' + item.id name = '/data/' + item.id
data = item.data data = item.data
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) rebin = ReBinary(data, item.href, self._oeb, map=HTML_MAP,
logger=self._logger)
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
@ -548,7 +558,8 @@ 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, OPF_MAP) rebin = ReBinary(meta, 'content.opf', self._oeb, map=OPF_MAP,
logger=self._logger)
meta = rebin.content meta = rebin.content
self._meta = meta self._meta = meta
self._add_file('/meta', meta) self._add_file('/meta', meta)
@ -707,8 +718,25 @@ def option_parser():
parser.add_option( parser.add_option(
'-o', '--output', default=None, '-o', '--output', default=None,
help=_('Output file. Default is derived from input filename.')) help=_('Output file. Default is derived from input filename.'))
parser.add_option(
'--verbose', default=False, action='store_true',
help=_('Useful for debugging.'))
return parser return parser
def oeb2lit(opts, opfpath):
logger = LoggingInterface(logging.getLogger('oeb2lit'))
logger.setup_cli_handler(opts.verbose)
litpath = opts.output
if litpath is None:
litpath = os.path.basename(opfpath)
litpath = os.path.splitext(litpath)[0] + '.lit'
litpath = os.path.abspath(litpath)
lit = LitWriter(OEBBook(opfpath))
with open(litpath, 'wb') as f:
lit.dump(f)
logger.log_info(_('Output written to ')+litpath)
def main(argv=sys.argv): def main(argv=sys.argv):
parser = option_parser() parser = option_parser()
opts, args = parser.parse_args(argv[1:]) opts, args = parser.parse_args(argv[1:])
@ -716,14 +744,7 @@ def main(argv=sys.argv):
parser.print_help() parser.print_help()
return 1 return 1
opfpath = args[0] opfpath = args[0]
litpath = opts.output oeb2lit(opts, opfpath)
if litpath is None:
litpath = os.path.basename(opfpath)
litpath = os.path.splitext(litpath)[0] + '.lit'
lit = LitWriter(OEBBook(opfpath))
with open(litpath, 'wb') as f:
lit.dump(f)
print _('LIT ebook created at'), litpath
return 0 return 0
if __name__ == '__main__': if __name__ == '__main__':

View File

@ -1920,7 +1920,7 @@ def process_file(path, options, logger=None):
options.anchor_ids = True options.anchor_ids = True
files = options.spine if (options.use_spine and hasattr(options, 'spine')) else [path] files = options.spine if (options.use_spine and hasattr(options, 'spine')) else [path]
conv = HTMLConverter(book, fonts, options, logger, files) conv = HTMLConverter(book, fonts, options, logger, files)
if options.use_spine and hasattr(options, 'toc'): if options.use_spine and hasattr(options, 'toc') and options.toc is not None:
conv.create_toc(options.toc) conv.create_toc(options.toc)
oname = options.output oname = options.output
if not oname: if not oname:

View File

@ -101,7 +101,6 @@ class ConfigDialog(QDialog, Ui_Dialog):
for item in items: for item in items:
self.language.addItem(item[1], QVariant(item[0])) self.language.addItem(item[1], QVariant(item[0]))
self.output_format.setCurrentIndex(0 if prefs['output_format'] == 'LRF' else 1)
self.pdf_metadata.setChecked(prefs['read_file_metadata']) self.pdf_metadata.setChecked(prefs['read_file_metadata'])
added_html = False added_html = False
@ -255,16 +254,11 @@ class ConfigDialog(QDialog, Ui_Dialog):
sc.set('max_cover', mcs) sc.set('max_cover', mcs)
config['delete_news_from_library_on_upload'] = self.delete_news.isChecked() config['delete_news_from_library_on_upload'] = self.delete_news.isChecked()
config['upload_news_to_device'] = self.sync_news.isChecked() config['upload_news_to_device'] = self.sync_news.isChecked()
of = str(self.output_format.currentText())
fmts = [] fmts = []
for i in range(self.viewer.count()): for i in range(self.viewer.count()):
if self.viewer.item(i).checkState() == Qt.Checked: if self.viewer.item(i).checkState() == Qt.Checked:
fmts.append(str(self.viewer.item(i).text())) fmts.append(str(self.viewer.item(i).text()))
config['internally_viewed_formats'] = fmts config['internally_viewed_formats'] = fmts
if of != prefs['output_format'] and 'epub' in of.lower():
warning_dialog(self, 'Warning',
'<p>EPUB support is still in beta. If you find bugs, please report them by opening a <a href="http://calibre.kovidgoyal.net">ticket</a>.').exec_()
prefs['output_format'] = of
if not path or not os.path.exists(path) or not os.path.isdir(path): if not path or not os.path.exists(path) or not os.path.isdir(path):
d = error_dialog(self, _('Invalid database location'), d = error_dialog(self, _('Invalid database location'),

View File

@ -149,7 +149,7 @@
</item> </item>
<item> <item>
<layout class="QGridLayout" name="gridLayout_2" > <layout class="QGridLayout" name="gridLayout_2" >
<item row="1" column="0" > <item row="0" column="0" >
<widget class="QLabel" name="label_5" > <widget class="QLabel" name="label_5" >
<property name="text" > <property name="text" >
<string>Format for &amp;single file save:</string> <string>Format for &amp;single file save:</string>
@ -159,10 +159,10 @@
</property> </property>
</widget> </widget>
</item> </item>
<item row="1" column="1" > <item row="0" column="1" >
<widget class="QComboBox" name="single_format" /> <widget class="QComboBox" name="single_format" />
</item> </item>
<item row="2" column="0" > <item row="1" column="0" >
<widget class="QLabel" name="label_2" > <widget class="QLabel" name="label_2" >
<property name="text" > <property name="text" >
<string>Default network &amp;timeout:</string> <string>Default network &amp;timeout:</string>
@ -172,7 +172,7 @@
</property> </property>
</widget> </widget>
</item> </item>
<item row="2" column="1" > <item row="1" column="1" >
<widget class="QSpinBox" name="timeout" > <widget class="QSpinBox" name="timeout" >
<property name="toolTip" > <property name="toolTip" >
<string>Set the default timeout for network fetches (i.e. anytime we go out to the internet to get information)</string> <string>Set the default timeout for network fetches (i.e. anytime we go out to the internet to get information)</string>
@ -191,10 +191,10 @@
</property> </property>
</widget> </widget>
</item> </item>
<item row="3" column="1" > <item row="2" column="1" >
<widget class="QComboBox" name="language" /> <widget class="QComboBox" name="language" />
</item> </item>
<item row="3" column="0" > <item row="2" column="0" >
<widget class="QLabel" name="label_7" > <widget class="QLabel" name="label_7" >
<property name="text" > <property name="text" >
<string>Choose &amp;language (requires restart):</string> <string>Choose &amp;language (requires restart):</string>
@ -204,34 +204,7 @@
</property> </property>
</widget> </widget>
</item> </item>
<item row="0" column="1" > <item row="3" column="1" >
<widget class="QComboBox" name="output_format" >
<property name="toolTip" >
<string>The default output format for ebook conversions.</string>
</property>
<item>
<property name="text" >
<string>LRF</string>
</property>
</item>
<item>
<property name="text" >
<string>EPUB</string>
</property>
</item>
</widget>
</item>
<item row="0" column="0" >
<widget class="QLabel" name="label_8" >
<property name="text" >
<string>&amp;Output format:</string>
</property>
<property name="buddy" >
<cstring>output_format</cstring>
</property>
</widget>
</item>
<item row="4" column="1" >
<widget class="QComboBox" name="priority" > <widget class="QComboBox" name="priority" >
<item> <item>
<property name="text" > <property name="text" >
@ -250,7 +223,7 @@
</item> </item>
</widget> </widget>
</item> </item>
<item row="4" column="0" > <item row="3" column="0" >
<widget class="QLabel" name="priority_label" > <widget class="QLabel" name="priority_label" >
<property name="text" > <property name="text" >
<string>Job &amp;priority:</string> <string>Job &amp;priority:</string>

View File

@ -0,0 +1,581 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!-- Generator: Adobe Illustrator 12.0.0, SVG Export Plug-In . SVG Version: 6.00 Build 51448) -->
<svg
xmlns:ns="http://ns.adobe.com/SaveForWeb/1.0/"
xmlns:i="http://ns.adobe.com/AdobeIllustrator/10.0/"
xmlns:dc="http://purl.org/dc/elements/1.1/"
xmlns:cc="http://web.resource.org/cc/"
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
xmlns:svg="http://www.w3.org/2000/svg"
xmlns="http://www.w3.org/2000/svg"
xmlns:xlink="http://www.w3.org/1999/xlink"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
version="1.1"
id="Layer_1"
width="128"
height="128"
viewBox="0 0 128 128"
overflow="visible"
enable-background="new 0 0 128 128"
xml:space="preserve"
sodipodi:version="0.32"
inkscape:version="0.45.1"
sodipodi:docname="edit-copy.svg"
sodipodi:docbase="/home/david/sandbox"
inkscape:output_extension="org.inkscape.output.svg.inkscape"
inkscape:export-filename="/home/david/sandbox/edit-copy.png"
inkscape:export-xdpi="22.5"
inkscape:export-ydpi="22.5"><defs
id="defs105"><linearGradient
id="linearGradient3291"><stop
style="stop-color:black;stop-opacity:1"
offset="0"
id="stop3293" /><stop
style="stop-color:black;stop-opacity:0"
offset="1"
id="stop3295" /></linearGradient><linearGradient
y2="0"
x2="28"
y1="57.5"
x1="28"
gradientUnits="userSpaceOnUse"
id="linearGradient18668">
<stop
id="stop18670"
style="stop-color:#fffccf;stop-opacity:1;"
offset="0" />
<stop
id="stop18672"
style="stop-color:white;stop-opacity:0;"
offset="1" />
</linearGradient><linearGradient
y2="0"
x2="28"
y1="57.5"
x1="28"
gradientUnits="userSpaceOnUse"
id="linearGradient15967">
<stop
id="stop15969"
style="stop-color:white;stop-opacity:1;"
offset="0" />
<stop
id="stop15971"
style="stop-color:white;stop-opacity:0;"
offset="1" />
</linearGradient><linearGradient
id="XMLID_2_"
gradientUnits="userSpaceOnUse"
x1="28"
y1="57.5"
x2="28"
y2="0">
<stop
offset="0"
style="stop-color:#FFEA00"
id="stop12" />
<stop
offset="1"
style="stop-color:#FFCC00"
id="stop14" />
</linearGradient><linearGradient
inkscape:collect="always"
xlink:href="#XMLID_9_"
id="linearGradient2391"
gradientUnits="userSpaceOnUse"
x1="94.3438"
y1="102.3447"
x2="86.5356"
y2="94.5366" /><linearGradient
inkscape:collect="always"
xlink:href="#XMLID_10_"
id="linearGradient2393"
gradientUnits="userSpaceOnUse"
x1="95"
y1="103"
x2="86.5865"
y2="94.5865" /><linearGradient
inkscape:collect="always"
xlink:href="#XMLID_11_"
id="linearGradient2395"
gradientUnits="userSpaceOnUse"
x1="95"
y1="103"
x2="87.293"
y2="95.293" /><linearGradient
inkscape:collect="always"
xlink:href="#XMLID_12_"
id="linearGradient2397"
gradientUnits="userSpaceOnUse"
x1="96"
y1="104"
x2="88.0002"
y2="96.0002" /><radialGradient
inkscape:collect="always"
xlink:href="#XMLID_7_"
id="radialGradient2465"
gradientUnits="userSpaceOnUse"
cx="102"
cy="112.3047"
r="139.5585" /><radialGradient
inkscape:collect="always"
xlink:href="#XMLID_8_"
id="radialGradient2467"
gradientUnits="userSpaceOnUse"
cx="102"
cy="112.3047"
r="139.55859" />
<foreignObject
requiredExtensions="http://ns.adobe.com/AdobeIllustrator/10.0/"
x="0"
y="0"
width="1"
height="1"
id="foreignObject7">
<i:pgfRef
xlink:href="#adobe_illustrator_pgf">
</i:pgfRef>
</foreignObject>
<linearGradient
inkscape:collect="always"
xlink:href="#XMLID_2_"
id="linearGradient12378"
gradientUnits="userSpaceOnUse"
x1="28"
y1="57.5"
x2="28"
y2="0" /><linearGradient
inkscape:collect="always"
xlink:href="#linearGradient2309"
id="linearGradient14180"
gradientUnits="userSpaceOnUse"
x1="-74.820707"
y1="100.82378"
x2="-18.121965"
y2="100.82378" /><linearGradient
inkscape:collect="always"
xlink:href="#linearGradient2309"
id="linearGradient14189"
gradientUnits="userSpaceOnUse"
x1="-74.820707"
y1="100.82378"
x2="-18.121965"
y2="100.82378" /><linearGradient
inkscape:collect="always"
xlink:href="#linearGradient15967"
id="linearGradient15973"
gradientUnits="userSpaceOnUse"
x1="27.719746"
y1="7.881104"
x2="27.719746"
y2="30.441185"
gradientTransform="translate(1.470416e-5,0)" /><linearGradient
inkscape:collect="always"
xlink:href="#linearGradient2309"
id="linearGradient15977"
gradientUnits="userSpaceOnUse"
x1="-74.820707"
y1="100.82378"
x2="-18.121965"
y2="100.82378" /><radialGradient
inkscape:collect="always"
xlink:href="#linearGradient15967"
id="radialGradient15986"
cx="26.954102"
cy="31.045055"
fx="26.954102"
fy="31.045055"
r="8.968153"
gradientTransform="matrix(0.754978,-2.959381e-2,0,0.905772,7.650275,10.87807)"
gradientUnits="userSpaceOnUse" /><linearGradient
inkscape:collect="always"
xlink:href="#XMLID_2_"
id="linearGradient18657"
gradientUnits="userSpaceOnUse"
x1="28"
y1="57.5"
x2="28"
y2="0" />
<linearGradient
id="linearGradient18649"
gradientUnits="userSpaceOnUse"
x1="28"
y1="57.5"
x2="28"
y2="0">
<stop
offset="0"
style="stop-color:#FFEA00"
id="stop18651" />
<stop
offset="1"
style="stop-color:#FFCC00"
id="stop18653" />
</linearGradient>
<linearGradient
inkscape:collect="always"
xlink:href="#linearGradient18668"
id="linearGradient18674"
gradientUnits="userSpaceOnUse"
x1="-39.53125"
y1="78"
x2="-39.53125"
y2="51.1875" /><linearGradient
inkscape:collect="always"
xlink:href="#linearGradient18668"
id="linearGradient18746"
gradientUnits="userSpaceOnUse"
x1="-39.53125"
y1="78"
x2="-39.53125"
y2="51.1875" /><radialGradient
inkscape:collect="always"
xlink:href="#XMLID_8_"
id="radialGradient2311"
gradientUnits="userSpaceOnUse"
gradientTransform="matrix(0.856383,0,0,0.8415585,11.191492,18.14026)"
cx="99.080742"
cy="109.33402"
r="139.55859"
fx="99.080742"
fy="109.33402" /><radialGradient
inkscape:collect="always"
xlink:href="#XMLID_7_"
id="radialGradient2314"
gradientUnits="userSpaceOnUse"
gradientTransform="matrix(0.8749999,0,0,0.8571428,10.000003,17.142857)"
cx="102"
cy="112.3047"
r="139.5585" /><linearGradient
inkscape:collect="always"
xlink:href="#XMLID_12_"
id="linearGradient2339"
gradientUnits="userSpaceOnUse"
x1="96"
y1="104"
x2="86.571632"
y2="94.104362"
gradientTransform="matrix(0.8749999,0,0,0.8571428,10.000003,17.142857)" /><filter
inkscape:collect="always"
id="filter6241"><feGaussianBlur
inkscape:collect="always"
stdDeviation="1.2065414"
id="feGaussianBlur6243" /></filter><radialGradient
inkscape:collect="always"
xlink:href="#XMLID_7_"
id="radialGradient6272"
gradientUnits="userSpaceOnUse"
gradientTransform="matrix(0.8749999,0,0,0.8571428,10.000003,17.142857)"
cx="102"
cy="112.3047"
r="139.5585" /><radialGradient
inkscape:collect="always"
xlink:href="#XMLID_8_"
id="radialGradient6274"
gradientUnits="userSpaceOnUse"
gradientTransform="matrix(0.856383,0,0,0.8415585,11.191492,18.14026)"
cx="99.080742"
cy="109.33402"
fx="99.080742"
fy="109.33402"
r="139.55859" /><linearGradient
inkscape:collect="always"
xlink:href="#XMLID_12_"
id="linearGradient6276"
gradientUnits="userSpaceOnUse"
gradientTransform="matrix(0.8749999,0,0,0.8571428,10.000003,17.142857)"
x1="96"
y1="104"
x2="86.571632"
y2="94.104362" /><filter
inkscape:collect="always"
id="filter3217"><feGaussianBlur
inkscape:collect="always"
stdDeviation="0.89955545"
id="feGaussianBlur3219" /></filter></defs><sodipodi:namedview
inkscape:window-height="670"
inkscape:window-width="1022"
inkscape:pageshadow="2"
inkscape:pageopacity="0.0"
guidetolerance="10.0"
gridtolerance="10.0"
objecttolerance="10.0"
borderopacity="1.0"
bordercolor="#666666"
pagecolor="#ffffff"
id="base"
inkscape:zoom="4"
inkscape:cx="92.02737"
inkscape:cy="54.798944"
inkscape:window-x="0"
inkscape:window-y="0"
inkscape:current-layer="g6245"
showgrid="true"
gridspacingx="4px"
gridspacingy="4px"
gridempspacing="0"
inkscape:grid-points="false"
inkscape:grid-bbox="true"
inkscape:object-points="true" />
<metadata
id="metadata3">
<ns:sfw>
<ns:slices>
<ns:slice
y="0"
x="0"
height="128"
width="128"
sliceID="1316743234" />
</ns:slices>
<ns:sliceSourceBounds
y="0"
x="0"
height="128"
width="128"
bottomLeftOrigin="true" />
<ns:optimizationSettings>
<ns:targetSettings
targetSettingsID="0"
fileFormat="PNG24Format">
<ns:PNG24Format
transparency="true"
filtered="false"
matteColor="#FFFFFF"
noMatteColor="false"
interlaced="false">
</ns:PNG24Format>
</ns:targetSettings>
<ns:targetSettings
targetSettingsID="1696735251"
fileFormat="PNG24Format">
<ns:PNG24Format
transparency="true"
filtered="false"
matteColor="#FFFFFF"
noMatteColor="false"
interlaced="false">
</ns:PNG24Format>
</ns:targetSettings>
</ns:optimizationSettings>
</ns:sfw>
<rdf:RDF><cc:Work
rdf:about=""><dc:format>image/svg+xml</dc:format><dc:type
rdf:resource="http://purl.org/dc/dcmitype/StillImage" /></cc:Work></rdf:RDF></metadata>
<radialGradient
id="XMLID_7_"
cx="102"
cy="112.3047"
r="139.5585"
gradientUnits="userSpaceOnUse">
<stop
offset="0"
style="stop-color:#00537D"
id="stop16" />
<stop
offset="0.0151"
style="stop-color:#186389"
id="stop18" />
<stop
offset="0.0558"
style="stop-color:#558CA8"
id="stop20" />
<stop
offset="0.0964"
style="stop-color:#89AFC3"
id="stop22" />
<stop
offset="0.1357"
style="stop-color:#B3CCD8"
id="stop24" />
<stop
offset="0.1737"
style="stop-color:#D4E2E9"
id="stop26" />
<stop
offset="0.2099"
style="stop-color:#ECF2F5"
id="stop28" />
<stop
offset="0.2435"
style="stop-color:#FAFCFD"
id="stop30" />
<stop
offset="0.2722"
style="stop-color:#FFFFFF"
id="stop32" />
</radialGradient>
<radialGradient
id="XMLID_8_"
cx="102"
cy="112.3047"
r="139.55859"
gradientUnits="userSpaceOnUse">
<stop
offset="0"
style="stop-color:#7a7d80;stop-opacity:1;"
id="stop37" />
<stop
offset="0.12617962"
style="stop-color:#c2c2c2;stop-opacity:1;"
id="stop47" />
<stop
offset="0.23250513"
style="stop-color:#FAFAFA"
id="stop49" />
<stop
offset="0.2722"
style="stop-color:#FFFFFF"
id="stop51" />
<stop
offset="0.5313"
style="stop-color:#FAFAFA"
id="stop53" />
<stop
offset="0.8449"
style="stop-color:#EBECEC"
id="stop55" />
<stop
offset="1"
style="stop-color:#E1E2E3"
id="stop57" />
</radialGradient>
<linearGradient
id="XMLID_9_"
gradientUnits="userSpaceOnUse"
x1="94.3438"
y1="102.3447"
x2="86.5356"
y2="94.5366">
<stop
offset="0"
style="stop-color:#FFFFFF"
id="stop62" />
<stop
offset="1"
style="stop-color:#555753"
id="stop64" />
</linearGradient>
<linearGradient
id="XMLID_10_"
gradientUnits="userSpaceOnUse"
x1="95"
y1="103"
x2="86.5865"
y2="94.5865">
<stop
offset="0"
style="stop-color:#FFFFFF"
id="stop69" />
<stop
offset="1"
style="stop-color:#555753"
id="stop71" />
</linearGradient>
<linearGradient
id="XMLID_11_"
gradientUnits="userSpaceOnUse"
x1="95"
y1="103"
x2="87.293"
y2="95.293">
<stop
offset="0"
style="stop-color:#FFFFFF"
id="stop76" />
<stop
offset="1"
style="stop-color:#393B38"
id="stop78" />
</linearGradient>
<linearGradient
id="XMLID_12_"
gradientUnits="userSpaceOnUse"
x1="96"
y1="104"
x2="88.0002"
y2="96.0002">
<stop
offset="0"
style="stop-color:#888A85"
id="stop83" />
<stop
offset="0.0072"
style="stop-color:#8C8E89"
id="stop85" />
<stop
offset="0.0673"
style="stop-color:#ABACA9"
id="stop87" />
<stop
offset="0.1347"
style="stop-color:#C5C6C4"
id="stop89" />
<stop
offset="0.2115"
style="stop-color:#DBDBDA"
id="stop91" />
<stop
offset="0.3012"
style="stop-color:#EBEBEB"
id="stop93" />
<stop
offset="0.4122"
style="stop-color:#F7F7F6"
id="stop95" />
<stop
offset="0.5679"
style="stop-color:#FDFDFD"
id="stop97" />
<stop
offset="1"
style="stop-color:#FFFFFF"
id="stop99" />
</linearGradient>
<g
id="g6245"><g
id="g6263"
transform="translate(12,0)"
style="opacity:1"><path
sodipodi:nodetypes="cccccc"
id="path2350"
d="M 23,25 L 23,121 L 76.525498,121 C 76.989247,121 107,91.601715 107,91.147428 L 107,25 L 23,25 z "
style="opacity:0.6;fill:#000000;fill-opacity:1;filter:url(#filter3217)"
transform="matrix(1.047619,0,0,1.0416667,-2.0952381,-4.041666)" /><path
style="fill:url(#radialGradient6272)"
d="M 24.000002,24 L 24.000002,120 L 77.5255,120 C 77.989249,120 108,90.601715 108,90.147428 L 108,24 L 24.000002,24 z "
id="path34"
sodipodi:nodetypes="cccccc" /><path
style="fill:url(#radialGradient6274);fill-opacity:1"
d="M 26.606384,25.714285 C 26.134518,25.714285 25.750001,26.092145 25.750001,26.555844 L 25.750001,117.44415 C 25.750001,117.9087 26.134518,118.28572 26.606384,118.28572 L 77.280277,118.28572 C 77.505506,118.28572 77.726453,118.19652 77.885739,118.03914 L 105.99908,90.412457 C 106.15921,90.255085 106.25,90.038805 106.25,89.817475 L 106.25,26.555844 C 106.25,26.092145 105.86634,25.714285 105.39361,25.714285 L 26.606384,25.714285 z "
id="path59" /><path
d="M 76.5255,120 C 76.5255,120 88.18749,110.99999 92.99999,106.28571 C 97.81249,101.57142 107,90.147428 107,90.147428 C 107,90.147428 99,96 83,96 C 83,112 76.5255,120 76.5255,120 z "
id="path101"
style="opacity:0.5;fill:#000000;fill-opacity:1;stroke:none;filter:url(#filter6241)"
sodipodi:nodetypes="csccc" /><path
sodipodi:nodetypes="csccc"
style="fill:url(#linearGradient6276)"
id="path6233"
d="M 77.5255,120 C 77.5255,120 89.18749,110.99999 93.99999,106.28571 C 98.81249,101.57142 108,90.147428 108,90.147428 C 108,90.147428 100,96 84,96 C 84,112 77.5255,120 77.5255,120 z " /></g><use
x="0"
y="0"
xlink:href="#g6263"
id="use6270"
width="128"
height="128"
transform="translate(-28,-16)" /></g>
</svg>

After

Width:  |  Height:  |  Size: 14 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 899 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 670 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.2 KiB

View File

@ -188,6 +188,6 @@ class DetailView(QDialog, Ui_Dialog):
def update(self): def update(self):
self.log.setPlainText(self.job.gui_text()) self.log.setPlainText(self.job.console_text())
vbar = self.log.verticalScrollBar() vbar = self.log.verticalScrollBar()
vbar.setValue(vbar.maximum()) vbar.setValue(vbar.maximum())

View File

@ -575,7 +575,7 @@ class BooksView(TableView):
self.setItemDelegateForColumn(col, self.rating_delegate) self.setItemDelegateForColumn(col, self.rating_delegate)
def set_context_menu(self, edit_metadata, send_to_device, convert, view, def set_context_menu(self, edit_metadata, send_to_device, convert, view,
save, open_folder, book_details): save, open_folder, book_details, similar_menu=None):
self.setContextMenuPolicy(Qt.DefaultContextMenu) self.setContextMenuPolicy(Qt.DefaultContextMenu)
self.context_menu = QMenu(self) self.context_menu = QMenu(self)
if edit_metadata is not None: if edit_metadata is not None:
@ -590,6 +590,8 @@ class BooksView(TableView):
self.context_menu.addAction(open_folder) self.context_menu.addAction(open_folder)
if book_details is not None: if book_details is not None:
self.context_menu.addAction(book_details) self.context_menu.addAction(book_details)
if similar_menu is not None:
self.context_menu.addMenu(similar_menu)
def contextMenuEvent(self, event): def contextMenuEvent(self, event):
self.context_menu.popup(event.globalPos()) self.context_menu.popup(event.globalPos())

View File

@ -126,6 +126,21 @@ class Main(MainWindow, Ui_MainWindow):
self.location_selected) self.location_selected)
QObject.connect(self.stack, SIGNAL('currentChanged(int)'), QObject.connect(self.stack, SIGNAL('currentChanged(int)'),
self.location_view.location_changed) self.location_view.location_changed)
self.output_formats = sorted(['EPUB', 'LRF'])
for f in self.output_formats:
self.output_format.addItem(f)
self.output_format.setCurrentIndex(self.output_formats.index(prefs['output_format']))
def change_output_format(x):
of = unicode(x).strip()
if of != prefs['output_format']:
if of in ('EPUB', 'LIT'):
warning_dialog(self, 'Warning',
'<p>%s support is still in beta. If you find bugs, please report them by opening a <a href="http://calibre.kovidgoyal.net">ticket</a>.'%of).exec_()
prefs.set('output_format', of)
self.connect(self.output_format, SIGNAL('currentIndexChanged(QString)'),
change_output_format)
####################### Vanity ######################## ####################### Vanity ########################
self.vanity_template = _('<p>For help visit <a href="http://%s.kovidgoyal.net/user_manual">%s.kovidgoyal.net</a><br>')%(__appname__, __appname__) self.vanity_template = _('<p>For help visit <a href="http://%s.kovidgoyal.net/user_manual">%s.kovidgoyal.net</a><br>')%(__appname__, __appname__)
@ -234,10 +249,33 @@ class Main(MainWindow, Ui_MainWindow):
QObject.connect(self.advanced_search_button, SIGNAL('clicked(bool)'), self.do_advanced_search) QObject.connect(self.advanced_search_button, SIGNAL('clicked(bool)'), self.do_advanced_search)
####################### Library view ######################## ####################### Library view ########################
similar_menu = QMenu(_('Similar books...'))
similar_menu.addAction(self.action_books_by_same_author)
similar_menu.addAction(self.action_books_in_this_series)
similar_menu.addAction(self.action_books_with_the_same_tags)
similar_menu.addAction(self.action_books_by_this_publisher)
self.action_books_by_same_author.setShortcut(Qt.ALT + Qt.Key_A)
self.action_books_in_this_series.setShortcut(Qt.ALT + Qt.Key_S)
self.action_books_by_this_publisher.setShortcut(Qt.ALT + Qt.Key_P)
self.action_books_with_the_same_tags.setShortcut(Qt.ALT+Qt.Key_T)
self.addAction(self.action_books_by_same_author)
self.addAction(self.action_books_by_this_publisher)
self.addAction(self.action_books_in_this_series)
self.addAction(self.action_books_with_the_same_tags)
self.similar_menu = similar_menu
self.connect(self.action_books_by_same_author, SIGNAL('triggered()'),
lambda : self.show_similar_books('author'))
self.connect(self.action_books_in_this_series, SIGNAL('triggered()'),
lambda : self.show_similar_books('series'))
self.connect(self.action_books_with_the_same_tags, SIGNAL('triggered()'),
lambda : self.show_similar_books('tag'))
self.connect(self.action_books_by_this_publisher, SIGNAL('triggered()'),
lambda : self.show_similar_books('publisher'))
self.library_view.set_context_menu(self.action_edit, self.action_sync, self.library_view.set_context_menu(self.action_edit, self.action_sync,
self.action_convert, self.action_view, self.action_convert, self.action_view,
self.action_save, self.action_open_containing_folder, self.action_save, self.action_open_containing_folder,
self.action_show_book_details) self.action_show_book_details,
similar_menu=similar_menu)
self.memory_view.set_context_menu(None, None, None, self.action_view, self.action_save, None, None) self.memory_view.set_context_menu(None, None, None, self.action_view, self.action_save, None, None)
self.card_view.set_context_menu(None, None, None, self.action_view, self.action_save, None, None) self.card_view.set_context_menu(None, None, None, self.action_view, self.action_save, None, None)
QObject.connect(self.library_view, SIGNAL('files_dropped(PyQt_PyObject)'), QObject.connect(self.library_view, SIGNAL('files_dropped(PyQt_PyObject)'),
@ -258,7 +296,7 @@ class Main(MainWindow, Ui_MainWindow):
db = LibraryDatabase2(self.library_path) db = LibraryDatabase2(self.library_path)
except OSError, err: except OSError, err:
error_dialog(self, _('Bad database location'), unicode(err)).exec_() error_dialog(self, _('Bad database location'), unicode(err)).exec_()
dir = unicode(QFileDialog.getExistingDirectory(self, dir = unicode(QFileDialog.getExistingDirectory(self,
_('Choose a location for your ebook library.'), os.path.expanduser('~'))) _('Choose a location for your ebook library.'), os.path.expanduser('~')))
if not dir: if not dir:
QCoreApplication.exit(1) QCoreApplication.exit(1)
@ -341,6 +379,34 @@ class Main(MainWindow, Ui_MainWindow):
error_dialog(self, _('Failed to start content server'), error_dialog(self, _('Failed to start content server'),
unicode(self.content_server.exception)).exec_() unicode(self.content_server.exception)).exec_()
def show_similar_books(self, type):
search, join = [], ' '
idx = self.library_view.currentIndex()
if not idx.isValid():
return
row = idx.row()
if type == 'series':
series = idx.model().db.series(row)
if series:
search = ['series:'+series]
elif type == 'publisher':
publisher = idx.model().db.publisher(row)
if publisher:
search = ['publisher:'+publisher]
elif type == 'tag':
tags = idx.model().db.tags(row)
if tags:
search = ['tag:'+t for t in tags.split(',')]
elif type == 'author':
authors = idx.model().db.authors(row)
if authors:
search = ['author:'+a.strip().replace('|', ',') for a in authors.split(',')]
join = ' or '
if search:
self.search.set_search_string(join.join(search))
def toggle_cover_flow(self, show): def toggle_cover_flow(self, show):
if show: if show:
self.library_view.setCurrentIndex(self.library_view.currentIndex()) self.library_view.setCurrentIndex(self.library_view.currentIndex())
@ -438,7 +504,7 @@ class Main(MainWindow, Ui_MainWindow):
return return
info, cp, fs = job.result info, cp, fs = job.result
self.location_view.model().update_devices(cp, fs) self.location_view.model().update_devices(cp, fs)
self.device_info = _('Connected ')+' '.join(info[:-1]) self.device_info = _('Connected ')+info[0]
self.vanity.setText(self.vanity_template%dict(version=self.latest_version, device=self.device_info)) self.vanity.setText(self.vanity_template%dict(version=self.latest_version, device=self.device_info))
self.device_manager.books(Dispatcher(self.metadata_downloaded)) self.device_manager.books(Dispatcher(self.metadata_downloaded))
@ -621,13 +687,13 @@ class Main(MainWindow, Ui_MainWindow):
files, names, on_card=on_card, files, names, on_card=on_card,
titles=titles titles=titles
) )
self.upload_memory[job] = (metadata, on_card, memory) self.upload_memory[job] = (metadata, on_card, memory, files)
def books_uploaded(self, job): def books_uploaded(self, job):
''' '''
Called once books have been uploaded. Called once books have been uploaded.
''' '''
metadata, on_card, memory = self.upload_memory.pop(job) metadata, on_card, memory, files = self.upload_memory.pop(job)
if job.exception is not None: if job.exception is not None:
if isinstance(job.exception, FreeSpaceError): if isinstance(job.exception, FreeSpaceError):
@ -648,6 +714,8 @@ class Main(MainWindow, Ui_MainWindow):
view = self.card_view if on_card else self.memory_view view = self.card_view if on_card else self.memory_view
view.model().resort(reset=False) view.model().resort(reset=False)
view.model().research() view.model().research()
for f in files:
getattr(f, 'close', lambda : True)()
if memory and memory[1]: if memory and memory[1]:
self.library_view.model().delete_books_by_id(memory[1]) self.library_view.model().delete_books_by_id(memory[1])

View File

@ -27,15 +27,9 @@
<normaloff>:/library</normaloff>:/library</iconset> <normaloff>:/library</normaloff>:/library</iconset>
</property> </property>
<widget class="QWidget" name="centralwidget" > <widget class="QWidget" name="centralwidget" >
<layout class="QGridLayout" > <layout class="QGridLayout" name="gridLayout" >
<item row="0" column="0" > <item row="0" column="0" >
<layout class="QHBoxLayout" > <layout class="QHBoxLayout" name="horizontalLayout_3" >
<property name="spacing" >
<number>6</number>
</property>
<property name="margin" >
<number>0</number>
</property>
<item> <item>
<widget class="LocationView" name="location_view" > <widget class="LocationView" name="location_view" >
<property name="sizePolicy" > <property name="sizePolicy" >
@ -89,29 +83,47 @@
</widget> </widget>
</item> </item>
<item> <item>
<widget class="QLabel" name="vanity" > <layout class="QVBoxLayout" name="verticalLayout_3" >
<property name="sizePolicy" > <item>
<sizepolicy vsizetype="Preferred" hsizetype="Preferred" > <widget class="QLabel" name="vanity" >
<horstretch>0</horstretch> <property name="sizePolicy" >
<verstretch>0</verstretch> <sizepolicy vsizetype="Preferred" hsizetype="Preferred" >
</sizepolicy> <horstretch>0</horstretch>
</property> <verstretch>0</verstretch>
<property name="maximumSize" > </sizepolicy>
<size> </property>
<width>16777215</width> <property name="maximumSize" >
<height>90</height> <size>
</size> <width>16777215</width>
</property> <height>90</height>
<property name="text" > </size>
<string/> </property>
</property> <property name="text" >
<property name="textFormat" > <string/>
<enum>Qt::RichText</enum> </property>
</property> <property name="textFormat" >
<property name="openExternalLinks" > <enum>Qt::RichText</enum>
<bool>true</bool> </property>
</property> <property name="openExternalLinks" >
</widget> <bool>true</bool>
</property>
</widget>
</item>
<item>
<layout class="QHBoxLayout" name="horizontalLayout_2" >
<item>
<widget class="QLabel" name="label_2" >
<property name="text" >
<string>Output:</string>
</property>
</widget>
</item>
<item>
<widget class="QComboBox" name="output_format" />
</item>
</layout>
</item>
</layout>
</item> </item>
</layout> </layout>
</item> </item>
@ -581,6 +593,42 @@
<string>Show book details</string> <string>Show book details</string>
</property> </property>
</action> </action>
<action name="action_books_by_same_author" >
<property name="icon" >
<iconset resource="images.qrc" >
<normaloff>:/images/user_profile.svg</normaloff>:/images/user_profile.svg</iconset>
</property>
<property name="text" >
<string>Books by same author</string>
</property>
</action>
<action name="action_books_in_this_series" >
<property name="icon" >
<iconset resource="images.qrc" >
<normaloff>:/images/books_in_series.svg</normaloff>:/images/books_in_series.svg</iconset>
</property>
<property name="text" >
<string>Books in this series</string>
</property>
</action>
<action name="action_books_by_this_publisher" >
<property name="icon" >
<iconset resource="images.qrc" >
<normaloff>:/images/publisher.png</normaloff>:/images/publisher.png</iconset>
</property>
<property name="text" >
<string>Books by this publisher</string>
</property>
</action>
<action name="action_books_with_the_same_tags" >
<property name="icon" >
<iconset resource="images.qrc" >
<normaloff>:/images/tags.svg</normaloff>:/images/tags.svg</iconset>
</property>
<property name="text" >
<string>Books with the same tags</string>
</property>
</action>
</widget> </widget>
<customwidgets> <customwidgets>
<customwidget> <customwidget>

View File

@ -32,7 +32,10 @@ class BookInfoDisplay(QWidget):
self.setMaximumWidth(width) self.setMaximumWidth(width)
QLabel.setPixmap(self, pixmap) QLabel.setPixmap(self, pixmap)
aspect_ratio = pixmap.width()/float(pixmap.height()) try:
aspect_ratio = pixmap.width()/float(pixmap.height())
except ZeroDivisionError:
aspect_ratio = 1
self.setMaximumWidth(int(aspect_ratio*self.HEIGHT)) self.setMaximumWidth(int(aspect_ratio*self.HEIGHT))
def sizeHint(self): def sizeHint(self):

View File

@ -473,9 +473,12 @@ class EbookViewer(MainWindow, Ui_EbookViewer):
return current_page return current_page
def save_current_position(self): def save_current_position(self):
pos = self.view.bookmark() try:
bookmark = '%d#%s'%(self.current_index, pos) pos = self.view.bookmark()
self.iterator.add_bookmark(('calibre_current_page_bookmark', bookmark)) bookmark = '%d#%s'%(self.current_index, pos)
self.iterator.add_bookmark(('calibre_current_page_bookmark', bookmark))
except:
traceback.print_exc()
def load_ebook(self, pathtoebook): def load_ebook(self, pathtoebook):
if self.iterator is not None: if self.iterator is not None:

View File

@ -47,6 +47,7 @@ entry_points = {
'fb2-meta = calibre.ebooks.metadata.fb2:main', 'fb2-meta = calibre.ebooks.metadata.fb2:main',
'any2lrf = calibre.ebooks.lrf.any.convert_from:main', 'any2lrf = calibre.ebooks.lrf.any.convert_from:main',
'any2epub = calibre.ebooks.epub.from_any:main', 'any2epub = calibre.ebooks.epub.from_any:main',
'any2lit = calibre.ebooks.lit.from_any:main',
'lrf2lrs = calibre.ebooks.lrf.lrfparser:main', 'lrf2lrs = calibre.ebooks.lrf.lrfparser:main',
'lrs2lrf = calibre.ebooks.lrf.lrs.convert_from:main', 'lrs2lrf = calibre.ebooks.lrf.lrs.convert_from:main',
'pdfreflow = calibre.ebooks.lrf.pdf.reflow:main', 'pdfreflow = calibre.ebooks.lrf.pdf.reflow:main',
@ -184,6 +185,7 @@ def setup_completion(fatal_errors):
from calibre.ebooks.odt.to_oeb import option_parser as odt2oeb from calibre.ebooks.odt.to_oeb import option_parser as odt2oeb
from calibre.ebooks.epub.from_feeds import option_parser as feeds2epub from calibre.ebooks.epub.from_feeds import option_parser as feeds2epub
from calibre.ebooks.epub.from_any import option_parser as any2epub from calibre.ebooks.epub.from_any import option_parser as any2epub
from calibre.ebooks.lit.from_any import option_parser as any2lit
from calibre.ebooks.epub.from_comic import option_parser as comic2epub from calibre.ebooks.epub.from_comic import option_parser as comic2epub
from calibre.gui2.main import option_parser as guiop from calibre.gui2.main import option_parser as guiop
any_formats = ['epub', 'htm', 'html', 'xhtml', 'xhtm', 'rar', 'zip', any_formats = ['epub', 'htm', 'html', 'xhtml', 'xhtm', 'rar', 'zip',
@ -207,7 +209,8 @@ def setup_completion(fatal_errors):
f.write(opts_and_exts('pdf2lrf', htmlop, ['pdf'])) f.write(opts_and_exts('pdf2lrf', htmlop, ['pdf']))
f.write(opts_and_exts('any2lrf', htmlop, any_formats)) f.write(opts_and_exts('any2lrf', htmlop, any_formats))
f.write(opts_and_exts('calibre', guiop, any_formats)) f.write(opts_and_exts('calibre', guiop, any_formats))
f.write(opts_and_exts('any2lrf', any2epub, any_formats)) f.write(opts_and_exts('any2epub', any2epub, any_formats))
f.write(opts_and_exts('any2lit', any2lit, any_formats))
f.write(opts_and_exts('lrf2lrs', lrf2lrsop, ['lrf'])) f.write(opts_and_exts('lrf2lrs', lrf2lrsop, ['lrf']))
f.write(opts_and_exts('lrf-meta', metaop, ['lrf'])) f.write(opts_and_exts('lrf-meta', metaop, ['lrf']))
f.write(opts_and_exts('rtf-meta', metaop, ['rtf'])) f.write(opts_and_exts('rtf-meta', metaop, ['rtf']))
@ -423,7 +426,8 @@ def install_man_pages(fatal_errors):
raise raise
print 'Failed to install MAN pages as help2man is missing from your system' print 'Failed to install MAN pages as help2man is missing from your system'
break break
raw = re.compile(r'^\.IP\s*^([A-Z :]+)$', re.MULTILINE).sub(r'.SS\n\1', p.stdout.read()) o = p.stdout.read()
raw = re.compile(r'^\.IP\s*^([A-Z :]+)$', re.MULTILINE).sub(r'.SS\n\1', o)
if not raw.strip(): if not raw.strip():
print 'Unable to create MAN page for', prog print 'Unable to create MAN page for', prog
continue continue

View File

@ -567,23 +567,27 @@ class Job(object):
return 'ERROR' return 'ERROR'
def console_text(self): def console_text(self):
ans = [u'Error in job: '] ans = [u'Job: ']
if self.description: if self.description:
ans[0] += self.description ans[0] += self.description
if self.exception is not None:
header = unicode(self.exception.__class__.__name__) if \
hasattr(self.exception, '__class__') else u'Error'
header = u'**%s**'%header
header += u': '
try:
header += unicode(self.exception)
except:
header += unicode(repr(self.exception))
ans.append(header)
if self.traceback:
ans.append(u'**Traceback**:')
ans.extend(self.traceback.split('\n'))
if self.log: if self.log:
if isinstance(self.log, str): if isinstance(self.log, str):
self.log = unicode(self.log, 'utf-8', 'replace') self.log = unicode(self.log, 'utf-8', 'replace')
ans.append(self.log) ans.append(self.log)
header = unicode(self.exception.__class__.__name__) if \
hasattr(self.exception, '__class__') else u'Error'
header += u': '
try:
header += unicode(self.exception)
except:
header += unicode(repr(self.exception))
ans.append(header)
if self.traceback:
ans.append(self.traceback)
return (u'\n'.join(ans)).encode('utf-8') return (u'\n'.join(ans)).encode('utf-8')
def gui_text(self): def gui_text(self):
@ -611,7 +615,7 @@ class Job(object):
self.log = unicode(self.log, 'utf-8', 'replace') self.log = unicode(self.log, 'utf-8', 'replace')
ans.extend(self.log.split('\n')) ans.extend(self.log.split('\n'))
return '\n'.join(ans) return '<br>'.join(ans)
class ParallelJob(Job): class ParallelJob(Job):

View File

@ -240,7 +240,7 @@ If not, head over to <a href="http://calibre.kovidgoyal.net/wiki/Development#Tra
return 'download.html', data, None return 'download.html', data, None
LINUX_INSTALLER = ''' LINUX_INSTALLER = r'''
import sys, os, shutil, tarfile, subprocess, tempfile, urllib2, re, stat import sys, os, shutil, tarfile, subprocess, tempfile, urllib2, re, stat
MOBILEREAD='https://dev.mobileread.com/dist/kovid/calibre/' MOBILEREAD='https://dev.mobileread.com/dist/kovid/calibre/'
@ -428,7 +428,7 @@ def main():
print 'Extracting files to %s ...'%destdir print 'Extracting files to %s ...'%destdir
extract_tarball(f, destdir) extract_tarball(f, destdir)
pi = os.path.join(destdir, calibre_postinstall) pi = os.path.join(destdir, 'calibre_postinstall')
subprocess.call('pi', shell=True) subprocess.call(pi, shell=True)
return 0 return 0
''' '''

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

@ -175,7 +175,7 @@ mark_frame(void *context, uint32_t uncomp, uint32_t comp)
PyObject *rtable = self->rtable; PyObject *rtable = self->rtable;
PyObject *entry = NULL; PyObject *entry = NULL;
entry = Py_BuildValue("(LL)", uncomp, comp); entry = Py_BuildValue("(II)", uncomp, comp);
if (entry) { if (entry) {
PyList_Append(rtable, entry); PyList_Append(rtable, entry);
Py_DECREF(entry); Py_DECREF(entry);

View File

@ -17,7 +17,8 @@ from PyQt4.Qt import QApplication, QFile, Qt, QPalette, QSize, QImage, QPainter,
from PyQt4.QtWebKit import QWebPage from PyQt4.QtWebKit import QWebPage
from calibre import browser, __appname__, iswindows, LoggingInterface, strftime, __version__ from calibre import browser, __appname__, iswindows, LoggingInterface, \
strftime, __version__, preferred_encoding
from calibre.ebooks.BeautifulSoup import BeautifulSoup, NavigableString, CData, Tag from calibre.ebooks.BeautifulSoup import BeautifulSoup, NavigableString, CData, Tag
from calibre.ebooks.metadata.opf import OPFCreator from calibre.ebooks.metadata.opf import OPFCreator
from calibre.ebooks.lrf import entity_to_unicode from calibre.ebooks.lrf import entity_to_unicode
@ -788,6 +789,7 @@ class BasicNewsRecipe(object, LoggingInterface):
html= u'''\ html= u'''\
<html> <html>
<head> <head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
<style type="text/css"> <style type="text/css">
body { body {
background: white no-repeat fixed center center; background: white no-repeat fixed center center;
@ -817,12 +819,13 @@ class BasicNewsRecipe(object, LoggingInterface):
</div> </div>
</body> </body>
</html> </html>
'''%dict(title=self.title, author=self.__author__, '''%dict(title=self.title if isinstance(self.title, unicode) else self.title.decode(preferred_encoding, 'replace'),
date=time.strftime(self.timefmt), author=self.__author__ if isinstance(self.__author__, unicode) else self.__author__.decode(preferred_encoding, 'replace'),
date=strftime(self.timefmt),
app=__appname__ +' '+__version__, app=__appname__ +' '+__version__,
img=img) img=img)
f2 = tempfile.NamedTemporaryFile(suffix='cover.html') f2 = tempfile.NamedTemporaryFile(suffix='cover.html')
f2.write(html) f2.write(html.encode('utf-8'))
f2.flush() f2.flush()
page = QWebPage() page = QWebPage()
pal = page.palette() pal = page.palette()

View File

@ -17,7 +17,7 @@ recipe_modules = [
'blic', 'novosti', 'danas', 'vreme', 'times_online', 'the_scotsman', 'blic', 'novosti', 'danas', 'vreme', 'times_online', 'the_scotsman',
'nytimes_sub', 'security_watch', 'cyberpresse', 'st_petersburg_times', 'nytimes_sub', 'security_watch', 'cyberpresse', 'st_petersburg_times',
'clarin', 'financial_times', 'heise', 'le_monde', 'harpers', 'science_aas', 'clarin', 'financial_times', 'heise', 'le_monde', 'harpers', 'science_aas',
'science_news', 'the_nation', 'lrb', 'harpers_full' 'science_news', 'the_nation', 'lrb', 'harpers_full', 'liberation',
] ]
import re, imp, inspect, time, os import re, imp, inspect, time, os

View File

@ -0,0 +1,38 @@
#!/usr/bin/env python
__license__ = 'GPL v3'
__copyright__ = '2008, Darko Miletic <darko.miletic at gmail.com>'
'''
liberation.fr
'''
from calibre.web.feeds.news import BasicNewsRecipe
class Liberation(BasicNewsRecipe):
title = u'Liberation'
__author__ = 'Darko Miletic'
description = 'News from France'
oldest_article = 7
max_articles_per_feed = 100
no_stylesheets = True
use_embedded_content = False
html2lrf_options = ['--base-font-size', '10']
keep_only_tags = [
dict(name='h1')
,dict(name='div', attrs={'class':'articleContent'})
,dict(name='div', attrs={'class':'entry'})
]
remove_tags = [
dict(name='p', attrs={'class':'clear'})
,dict(name='ul', attrs={'class':'floatLeft clear'})
,dict(name='div', attrs={'class':'clear floatRight'})
,dict(name='object')
]
feeds = [
(u'La une', u'http://www.liberation.fr/rss/laune')
,(u'Monde' , u'http://www.liberation.fr/rss/monde')
,(u'Sports', u'http://www.liberation.fr/rss/sports')
]

View File

@ -1,78 +1,34 @@
#!/usr/bin/env python #!/usr/bin/env python
__license__ = 'GPL v3'
__copyright__ = '2008, Kovid Goyal kovid@kovidgoyal.net' __license__ = 'GPL v3'
__docformat__ = 'restructuredtext en' __copyright__ = '2008, Darko Miletic <darko.miletic at gmail.com>'
'''
import re newyorker.com
from calibre import strftime '''
from calibre.web.feeds.news import BasicNewsRecipe
from calibre.ebooks.BeautifulSoup import NavigableString from calibre.web.feeds.news import BasicNewsRecipe
class NewYorker(BasicNewsRecipe): class NewYorker(BasicNewsRecipe):
title = u'The New Yorker'
title = 'The New Yorker' __author__ = 'Darko Miletic'
__author__ = 'Kovid Goyal' description = 'Best of the US journalism'
description = 'News and opinion' oldest_article = 7
max_articles_per_feed = 100
remove_tags = [ no_stylesheets = False
dict(name='div', id=['printoptions', 'header', 'articleBottom']), use_embedded_content = False
dict(name='div', attrs={'class':['utils', 'icons']})
] keep_only_tags = [
dict(name='div' , attrs={'id':'printbody' })
]
def parse_index(self): remove_tags = [
toc_pat = re.compile(r'/magazine/toc/\d+/\d+/\d+/toc_\d+') dict(name='div' , attrs={'class':'utils' })
soup = self.soup(self.browser.open('http://www.newyorker.com/').read()) ,dict(name='div' , attrs={'id':'bottomFeatures' })
a = soup.find('a', href=toc_pat) ,dict(name='div' , attrs={'id':'articleBottom' })
if a is None: ]
raise Exception('Could not find the current issue of The New Yorker')
href = a['href'] feeds = [
href = 'http://www.newyorker.com'+href[href.index('/magazine'):] (u'The New Yorker', u'http://feeds.newyorker.com/services/rss/feeds/everything.xml')
soup = self.soup(self.browser.open(href).read()) ]
img = soup.find(id='inThisIssuePhoto')
if img is not None: def print_version(self, url):
self.cover_url = 'http://www.newyorker.com'+img['src'] return url + '?printable=true'
alt = img.get('alt', None)
if alt:
self.timefmt = ' [%s]'%alt
features = soup.findAll(attrs={'class':re.compile('feature')})
category, sections, articles = None, [], []
for feature in features:
head = feature.find('img', alt=True, attrs={'class':'featurehed'})
if head is None:
continue
if articles:
sections.append((category, articles))
category, articles = head['alt'], []
if category in ('', 'AUDIO', 'VIDEO', 'BLOGS', 'GOINGS ON'):
continue
for a in feature.findAll('a', href=True):
href = 'http://www.newyorker.com'+a['href']+'?printable=true'
title, in_title, desc = '', True, ''
for tag in a.contents:
if getattr(tag, 'name', None) == 'br':
in_title = False
continue
if isinstance(tag, NavigableString):
text = unicode(tag)
if in_title:
title += text
else:
desc += text
if title and not 'Audio:' in title:
art = {
'title': title,
'desc': desc, 'content':'',
'url': href,
'date': strftime('%a, %d %b'),
}
articles.append(art)
# from IPython.Shell import IPShellEmbed
# ipshell = IPShellEmbed()
# ipshell()
# raise Exception()
return sections

View File

@ -213,7 +213,7 @@ def upload_src_tarball():
check_call('scp dist/calibre-*.tar.gz divok:%s/'%DOWNLOADS) check_call('scp dist/calibre-*.tar.gz divok:%s/'%DOWNLOADS)
def stage_one(): def stage_one():
check_call('sudo rm -rf build', shell=True) check_call('sudo rm -rf build src/calibre/plugins/*', shell=True)
os.mkdir('build') os.mkdir('build')
shutil.rmtree('docs') shutil.rmtree('docs')
os.mkdir('docs') os.mkdir('docs')