Pulled upstream changes

This commit is contained in:
Marshall T. Vandegrift 2008-07-17 23:42:01 -04:00
commit 7060bcc681
12 changed files with 135 additions and 56 deletions

View File

@ -29,6 +29,11 @@ islinux = not(iswindows or isosx)
try: try:
locale.setlocale(locale.LC_ALL, '') locale.setlocale(locale.LC_ALL, '')
except:
dl = locale.getdefaultlocale()
try:
if dl:
locale.setlocale(dl[0])
except: except:
pass pass

View File

@ -3,15 +3,13 @@ __copyright__ = '2008, Anatoly Shipitsin <norguhtar at gmail.com>'
""" """
Convert .fb2 files to .lrf Convert .fb2 files to .lrf
""" """
import os, sys, tempfile, subprocess, shutil, logging, glob import os, sys, tempfile, shutil, logging
from base64 import b64decode
from calibre.ptempfile import PersistentTemporaryFile
from calibre.ebooks.lrf import option_parser as lrf_option_parser from calibre.ebooks.lrf import option_parser as lrf_option_parser
from calibre.ebooks.metadata.meta import get_metadata from calibre.ebooks.metadata.meta import get_metadata
from calibre.ebooks import ConversionError
from calibre.ebooks.lrf.html.convert_from import process_file as html_process_file from calibre.ebooks.lrf.html.convert_from import process_file as html_process_file
from calibre import setup_cli_handlers, __appname__ from calibre import setup_cli_handlers, __appname__
from calibre.ebooks.BeautifulSoup import BeautifulStoneSoup
from calibre.resources import fb2_xsl from calibre.resources import fb2_xsl
def option_parser(): def option_parser():
@ -22,25 +20,27 @@ _('''%prog [options] mybook.fb2
%prog converts mybook.fb2 to mybook.lrf''')) %prog converts mybook.fb2 to mybook.lrf'''))
parser.add_option('--debug-html-generation', action='store_true', default=False, parser.add_option('--debug-html-generation', action='store_true', default=False,
dest='debug_html_generation', help=_('Print generated HTML to stdout and quit.')) dest='debug_html_generation', help=_('Print generated HTML to stdout and quit.'))
parser.add_option('--keep-intermediate-files', action='store_true', default=False,
help=_('Keep generated HTML files after completing conversion to LRF.'))
return parser return parser
def extract_embedded_content(doc):
for elem in doc.xpath('./*'):
if 'binary' in elem.tag and elem.attrib.has_key('id'):
fname = elem.attrib['id']
data = b64decode(elem.text.strip())
open(fname, 'wb').write(data)
def generate_html(fb2file, encoding, logger): def generate_html(fb2file, encoding, logger):
from lxml import etree from lxml import etree
tdir = tempfile.mkdtemp(prefix=__appname__+'_') tdir = tempfile.mkdtemp(prefix=__appname__+'_fb2_')
ofile = os.path.join(tdir, 'index.xml')
cwd = os.getcwdu() cwd = os.getcwdu()
os.chdir(tdir) os.chdir(tdir)
try: try:
logger.info('Parsing XML...') logger.info('Parsing XML...')
parser = etree.XMLParser(recover=True, no_network=True) parser = etree.XMLParser(recover=True, no_network=True)
try:
doc = etree.parse(fb2file, parser) doc = etree.parse(fb2file, parser)
except: extract_embedded_content(doc)
raise
logger.info('Parsing failed. Trying to clean up XML...')
soup = BeautifulStoneSoup(open(fb2file, 'rb').read())
doc = etree.fromstring(str(soup))
logger.info('Converting XML to HTML...') logger.info('Converting XML to HTML...')
styledoc = etree.fromstring(fb2_xsl) styledoc = etree.fromstring(fb2_xsl)
@ -72,7 +72,7 @@ def process_file(path, options, logger=None):
options.output = os.path.abspath(os.path.basename(os.path.splitext(path)[0]) + ext) options.output = os.path.abspath(os.path.basename(os.path.splitext(path)[0]) + ext)
options.output = os.path.abspath(os.path.expanduser(options.output)) options.output = os.path.abspath(os.path.expanduser(options.output))
if not mi.title: if not mi.title:
mi.title = os.path.splitext(os.path.basename(rtf))[0] mi.title = os.path.splitext(os.path.basename(fb2))[0]
if (not options.title or options.title == 'Unknown'): if (not options.title or options.title == 'Unknown'):
options.title = mi.title options.title = mi.title
if (not options.author or options.author == 'Unknown') and mi.authors: if (not options.author or options.author == 'Unknown') and mi.authors:
@ -85,7 +85,7 @@ def process_file(path, options, logger=None):
html_process_file(htmlfile, options, logger) html_process_file(htmlfile, options, logger)
finally: finally:
os.chdir(cwd) os.chdir(cwd)
if hasattr(options, 'keep_intermediate_files') and options.keep_intermediate_files: if getattr(options, 'keep_intermediate_files', False):
logger.debug('Intermediate files in '+ tdir) logger.debug('Intermediate files in '+ tdir)
else: else:
shutil.rmtree(tdir) shutil.rmtree(tdir)

View File

@ -128,21 +128,40 @@
</xsl:template> </xsl:template>
<xsl:template match="fb:section"> <xsl:template match="fb:section">
<a name="TOC_{generate-id()}"></a> <xsl:variable name="section_has_title">
<xsl:choose>
<xsl:when test="./fb:title"><xsl:value-of select="generate-id()" /></xsl:when>
<xsl:otherwise>None</xsl:otherwise>
</xsl:choose>
</xsl:variable>
<xsl:if test="$section_has_title = 'None'">
<a name="TOC_{generate-id()}" />
<xsl:if test="@id"> <xsl:if test="@id">
<xsl:element name="a"> <xsl:element name="a">
<xsl:attribute name="name"><xsl:value-of select="@id"/></xsl:attribute> <xsl:attribute name="name"><xsl:value-of select="@id"/></xsl:attribute>
</xsl:element> </xsl:element>
</xsl:if> </xsl:if>
<xsl:apply-templates/> </xsl:if>
<xsl:apply-templates>
<xsl:with-param name="section_toc_id" select="$section_has_title" />
</xsl:apply-templates>
</xsl:template> </xsl:template>
<!-- section/title --> <!-- section/title -->
<xsl:template match="fb:section/fb:title|fb:poem/fb:title"> <xsl:template match="fb:section/fb:title|fb:poem/fb:title">
<xsl:param name="section_toc_id" />
<xsl:choose> <xsl:choose>
<xsl:when test="count(ancestor::node()) &lt; 9"> <xsl:when test="count(ancestor::node()) &lt; 9">
<xsl:element name="{concat('h',count(ancestor::node())-3)}"> <xsl:element name="{concat('h',count(ancestor::node())-3)}">
<xsl:if test="../@id">
<xsl:attribute name="id"><xsl:value-of select="../@id" /></xsl:attribute>
</xsl:if>
<xsl:if test="$section_toc_id != 'None'">
<xsl:element name="a">
<xsl:attribute name="name">TOC_<xsl:value-of select="$section_toc_id"/></xsl:attribute>
</xsl:element>
</xsl:if>
<a name="TOC_{generate-id()}"></a> <a name="TOC_{generate-id()}"></a>
<xsl:if test="@id"> <xsl:if test="@id">
<xsl:element name="a"> <xsl:element name="a">
@ -166,7 +185,9 @@
</xsl:template> </xsl:template>
<!-- section/title --> <!-- section/title -->
<xsl:template match="fb:body/fb:title"> <xsl:template match="fb:body/fb:title">
<h1><xsl:apply-templates mode="title"/></h1> <xsl:element name="h1">
<xsl:apply-templates mode="title"/>
</xsl:element>
</xsl:template> </xsl:template>
<xsl:template match="fb:title/fb:p"> <xsl:template match="fb:title/fb:p">

View File

@ -5,7 +5,8 @@ __copyright__ = '2008, Anatoly Shipitsin <norguhtar at gmail.com>'
'''Read meta information from fb2 files''' '''Read meta information from fb2 files'''
import sys, os import sys, os, mimetypes
from base64 import b64decode
from calibre.ebooks.BeautifulSoup import BeautifulStoneSoup from calibre.ebooks.BeautifulSoup import BeautifulStoneSoup
from calibre.ebooks.metadata import MetaInformation from calibre.ebooks.metadata import MetaInformation
@ -18,15 +19,30 @@ def get_metadata(stream):
author= [firstname+" "+lastname] author= [firstname+" "+lastname]
title = soup.find("book-title").string title = soup.find("book-title").string
comments = soup.find("annotation") comments = soup.find("annotation")
cp = soup.find('coverpage')
cdata = None
if cp:
cimage = cp.find('image', attrs={'l:href':True})
if cimage:
id = cimage['l:href'].replace('#', '')
binary = soup.find('binary', id=id, attrs={'content-type':True})
if binary:
mt = binary['content-type']
exts = mimetypes.guess_all_extensions(mt)
if not exts:
exts = ['.jpg']
cdata = (exts[0][1:], b64decode(binary.string.strip()))
if comments and len(comments) > 1: if comments and len(comments) > 1:
comments = comments.p.contents[0] comments = comments.p.contents[0]
series = soup.find("sequence") series = soup.find("sequence")
# series_index = series.index
mi = MetaInformation(title, author) mi = MetaInformation(title, author)
mi.comments = comments mi.comments = comments
mi.author_sort = lastname+'; '+firstname
if series: if series:
mi.series = series.get('name', None) mi.series = series.get('name', None)
# mi.series_index = series_index if cdata:
mi.cover_data = cdata
return mi return mi
def main(args=sys.argv): def main(args=sys.argv):

View File

@ -70,7 +70,7 @@ class TOC(list):
break break
if toc is not None: if toc is not None:
if toc.lower() != 'ncx': if toc.lower() not in ('ncx', 'ncxtoc'):
toc = urlparse(unquote(toc))[2] toc = urlparse(unquote(toc))[2]
toc = toc.replace('/', os.sep) toc = toc.replace('/', os.sep)
if not os.path.isabs(toc): if not os.path.isabs(toc):
@ -88,6 +88,10 @@ class TOC(list):
traceback.print_exc(file=sys.stdout) traceback.print_exc(file=sys.stdout)
print 'Continuing anyway' print 'Continuing anyway'
else: else:
path = opfreader.manifest.item(toc.lower())
if path and os.access(path, os.R_OK):
self.read_ncx_toc(path)
return
cwd = os.path.abspath(self.base_path) cwd = os.path.abspath(self.base_path)
m = glob.glob(os.path.join(cwd, '*.ncx')) m = glob.glob(os.path.join(cwd, '*.ncx'))
if m: if m:

View File

@ -691,7 +691,7 @@ class DeviceBooksModel(BooksModel):
dt = item.datetime dt = item.datetime
dt = datetime(*dt[0:6]) dt = datetime(*dt[0:6])
dt = dt - timedelta(seconds=time.timezone) + timedelta(hours=time.daylight) dt = dt - timedelta(seconds=time.timezone) + timedelta(hours=time.daylight)
data[_('Timestamp')] = dt.ctime() data[_('Timestamp')] = dt.strftime('%a %b %d %H:%M:%S %Y')
data[_('Tags')] = ', '.join(item.tags) data[_('Tags')] = ', '.join(item.tags)
self.emit(SIGNAL('new_bookdisplay_data(PyQt_PyObject)'), data) self.emit(SIGNAL('new_bookdisplay_data(PyQt_PyObject)'), data)

View File

@ -59,6 +59,9 @@ class Main(MainWindow, Ui_MainWindow):
def __init__(self, single_instance, opts, parent=None): def __init__(self, single_instance, opts, parent=None):
MainWindow.__init__(self, opts, parent) MainWindow.__init__(self, opts, parent)
# Initialize fontconfig in a separate thread as this can be a lengthy
# process if run for the first time on this machine
self.fc = __import__('calibre.utils.fontconfig', fromlist=1)
self.single_instance = single_instance self.single_instance = single_instance
if self.single_instance is not None: if self.single_instance is not None:
self.connect(self.single_instance, SIGNAL('message_received(PyQt_PyObject)'), self.connect(self.single_instance, SIGNAL('message_received(PyQt_PyObject)'),

View File

@ -170,6 +170,7 @@ def do_add(db, paths, one_book_per_directory, recurse, add_duplicates):
for mi, formats in dir_dups: for mi, formats in dir_dups:
db.import_book(mi, formats) db.import_book(mi, formats)
else: else:
if dir_dups or file_duplicates:
print >>sys.stderr, _('The following books were not added as they already exist in the database (see --duplicates option):') print >>sys.stderr, _('The following books were not added as they already exist in the database (see --duplicates option):')
for mi, formats in dir_dups: for mi, formats in dir_dups:
title = mi.title title = mi.title

View File

@ -240,13 +240,21 @@ def do_postinstall(destdir):
os.chdir(cwd) os.chdir(cwd)
def download_tarball(): def download_tarball():
try:
pb = ProgressBar(TerminalController(sys.stdout), 'Downloading calibre...') pb = ProgressBar(TerminalController(sys.stdout), 'Downloading calibre...')
except ValueError:
print 'Downloading calibre...'
pb = None
src = urllib2.urlopen(MOBILEREAD+'calibre-%version-i686.tar.bz2') src = urllib2.urlopen(MOBILEREAD+'calibre-%version-i686.tar.bz2')
size = int(src.info()['content-length']) size = int(src.info()['content-length'])
f = tempfile.NamedTemporaryFile() f = tempfile.NamedTemporaryFile()
while f.tell() < size: while f.tell() < size:
f.write(src.read(4*1024)) f.write(src.read(4*1024))
pb.update(f.tell()/float(size)) percent = f.tell()/float(size)
if pb is not None:
pb.update(percent)
else:
print '%d%%, '%int(percent*100),
f.seek(0) f.seek(0)
return f return f

View File

@ -128,7 +128,10 @@ lib.FcConfigParseAndLoad.restype = c_int
lib.FcConfigBuildFonts.argtypes = [c_void_p] lib.FcConfigBuildFonts.argtypes = [c_void_p]
lib.FcConfigBuildFonts.restype = c_int lib.FcConfigBuildFonts.restype = c_int
_init_error = None
_initialized = False
from threading import Timer
def _do_init():
# Initialize the fontconfig library. This has to be done manually # Initialize the fontconfig library. This has to be done manually
# for the OS X bundle as it may have its own private fontconfig. # for the OS X bundle as it may have its own private fontconfig.
if hasattr(sys, 'frameworks_dir'): if hasattr(sys, 'frameworks_dir'):
@ -137,21 +140,37 @@ if hasattr(sys, 'frameworks_dir'):
config_dir = config_dir.encode(sys.getfilesystemencoding()) config_dir = config_dir.encode(sys.getfilesystemencoding())
config = lib.FcConfigCreate() config = lib.FcConfigCreate()
if not lib.FcConfigParseAndLoad(config, os.path.join(config_dir, 'fonts.conf'), 1): if not lib.FcConfigParseAndLoad(config, os.path.join(config_dir, 'fonts.conf'), 1):
raise RuntimeError('Could not parse the fontconfig configuration') _init_error = 'Could not parse the fontconfig configuration'
return
if not lib.FcConfigBuildFonts(config): if not lib.FcConfigBuildFonts(config):
raise RuntimeError('Could not build fonts') _init_error = 'Could not build fonts'
return
if not lib.FcConfigSetCurrent(config): if not lib.FcConfigSetCurrent(config):
raise RuntimeError('Could not set font config') _init_error = 'Could not set font config'
return
elif not lib.FcInit(): elif not lib.FcInit():
raise RuntimeError(_('Could not initialize the fontconfig library')) _init_error = _('Could not initialize the fontconfig library')
return
global _initialized
_initialized = True
def find_font_families(allowed_extensions=['ttf']):
_init_timer = Timer(0.1, _do_init)
_init_timer.start()
def join():
_init_timer.join()
if _init_error is not None:
raise RuntimeError(_init_error)
def find_font_families(allowed_extensions=['ttf', 'otf']):
''' '''
Return an alphabetically sorted list of font families available on the system. Return an alphabetically sorted list of font families available on the system.
`allowed_extensions`: A list of allowed extensions for font file types. Defaults to `allowed_extensions`: A list of allowed extensions for font file types. Defaults to
`['ttf']`. If it is empty, it is ignored. `['ttf', 'otf']`. If it is empty, it is ignored.
''' '''
join()
allowed_extensions = [i.lower() for i in allowed_extensions] allowed_extensions = [i.lower() for i in allowed_extensions]
empty_pattern = lib.FcPatternCreate() empty_pattern = lib.FcPatternCreate()
@ -193,6 +212,7 @@ def files_for_family(family, normalize=True):
they are a tuple (slant, weight) otherwise they are strings from the set they are a tuple (slant, weight) otherwise they are strings from the set
`('normal', 'bold', 'italic', 'bi', 'light', 'li')` `('normal', 'bold', 'italic', 'bi', 'light', 'li')`
''' '''
join()
if isinstance(family, unicode): if isinstance(family, unicode):
family = family.encode(preferred_encoding) family = family.encode(preferred_encoding)
family_pattern = lib.FcPatternBuild(0, 'family', FcTypeString, family, 0) family_pattern = lib.FcPatternBuild(0, 'family', FcTypeString, family, 0)
@ -268,6 +288,7 @@ def match(name, sort=False, verbose=False):
decreasing closeness of matching. decreasing closeness of matching.
`verbose`: If `True` print debugging information to stdout `verbose`: If `True` print debugging information to stdout
''' '''
join()
if isinstance(name, unicode): if isinstance(name, unicode):
name = name.encode(preferred_encoding) name = name.encode(preferred_encoding)
pat = lib.FcNameParse(name) pat = lib.FcNameParse(name)