Merge lp:calibre

This commit is contained in:
Marshall T. Vandegrift 2009-02-06 23:14:44 -05:00
commit 73d76f4eb6
13 changed files with 253 additions and 79 deletions

View File

@ -96,7 +96,7 @@ class PRS500(Device):
# Location of cache.xml on storage card in device # Location of cache.xml on storage card in device
CACHE_XML = "/Sony Reader/database/cache.xml" CACHE_XML = "/Sony Reader/database/cache.xml"
# Ordered list of supported formats # Ordered list of supported formats
FORMATS = ["lrf", "rtf", "pdf", "txt"] FORMATS = ["lrf", "lrx", "rtf", "pdf", "txt"]
# Height for thumbnails of books/images on the device # Height for thumbnails of books/images on the device
THUMBNAIL_HEIGHT = 68 THUMBNAIL_HEIGHT = 68
# Directory on card to which books are copied # Directory on card to which books are copied

View File

@ -27,12 +27,12 @@ class File(object):
class PRS505(Device): class PRS505(Device):
VENDOR_ID = 0x054c #: SONY Vendor Id VENDOR_ID = 0x054c #: SONY Vendor Id
PRODUCT_ID = 0x031e #: Product Id for the PRS-505 PRODUCT_ID = 0x031e #: Product Id for the PRS-505
BCD = [0x229] #: Needed to disambiguate 505 and 700 on linux BCD = [0x229] #: Needed to disambiguate 505 and 700 on linux
PRODUCT_NAME = 'PRS-505' PRODUCT_NAME = 'PRS-505'
VENDOR_NAME = 'SONY' VENDOR_NAME = 'SONY'
FORMATS = ['lrf', 'epub', "rtf", "pdf", "txt"] FORMATS = ['lrf', 'epub', 'lrx', 'rtf', 'pdf', 'txt']
MEDIA_XML = 'database/cache/media.xml' MEDIA_XML = 'database/cache/media.xml'
CACHE_XML = 'Sony Reader/database/cache.xml' CACHE_XML = 'Sony Reader/database/cache.xml'

View File

@ -153,6 +153,14 @@ help on using this feature.
'slow and if your source file contains a very large ' 'slow and if your source file contains a very large '
'number of page breaks, you should turn off splitting ' 'number of page breaks, you should turn off splitting '
'on page breaks.')) 'on page breaks.'))
structure('page', ['--page'], default=None,
help=_('XPath expression to detect page boundaries for building '
'a custom pagination map, as used by AdobeDE. Default is '
'not to build an explicit pagination map.'))
structure('page_names', ['--page-names'], default=None,
help=_('XPath expression to find the name of each page in the '
'pagination map relative to its boundary element. '
'Default is to number all pages staring with 1.'))
toc = c.add_group('toc', toc = c.add_group('toc',
_('''\ _('''\
Control the automatic generation of a Table of Contents. If an OPF file is detected Control the automatic generation of a Table of Contents. If an OPF file is detected
@ -230,4 +238,4 @@ to auto-generate a Table of Contents.
c.add_opt('extract_to', ['--extract-to'], group='debug', default=None, c.add_opt('extract_to', ['--extract-to'], group='debug', default=None,
help=_('Extract the contents of the produced EPUB file to the ' help=_('Extract the contents of the produced EPUB file to the '
'specified directory.')) 'specified directory.'))
return c return c

View File

@ -46,6 +46,7 @@ from calibre.ebooks.metadata.toc import TOC
from calibre.ebooks.metadata.opf2 import OPF from calibre.ebooks.metadata.opf2 import OPF
from calibre.ebooks.epub import initialize_container, PROFILES from calibre.ebooks.epub import initialize_container, PROFILES
from calibre.ebooks.epub.split import split from calibre.ebooks.epub.split import split
from calibre.ebooks.epub.pages import add_page_map
from calibre.ebooks.epub.fonts import Rationalizer from calibre.ebooks.epub.fonts import Rationalizer
from calibre.constants import preferred_encoding from calibre.constants import preferred_encoding
from calibre.customize.ui import run_plugins_on_postprocess from calibre.customize.ui import run_plugins_on_postprocess
@ -438,6 +439,9 @@ def convert(htmlfile, opts, notification=None, create_epub=True,
if opts.show_ncx: if opts.show_ncx:
print toc print toc
split(opf_path, opts, stylesheet_map) split(opf_path, opts, stylesheet_map)
if opts.page:
logger.info('\tBuilding page map...')
add_page_map(opf_path, opts)
check_links(opf_path, opts.pretty_print) check_links(opf_path, opts.pretty_print)
opf = OPF(opf_path, tdir) opf = OPF(opf_path, tdir)

View File

@ -0,0 +1,59 @@
'''
Add page mapping information to an EPUB book.
'''
from __future__ import with_statement
__license__ = 'GPL v3'
__copyright__ = '2008, Marshall T. Vandegrift <llasram@gmail.com>'
__docformat__ = 'restructuredtext en'
import os, re
from itertools import count, chain
from calibre.ebooks.oeb.base import XHTML, XHTML_NS
from calibre.ebooks.oeb.base import OEBBook, DirWriter
from lxml import etree, html
from lxml.etree import XPath
NSMAP = {'h': XHTML_NS, 'html': XHTML_NS, 'xhtml': XHTML_NS}
PAGE_RE = re.compile(r'page', re.IGNORECASE)
ROMAN_RE = re.compile(r'^[ivxlcdm]+$', re.IGNORECASE)
def filter_name(name):
name = name.strip()
name = PAGE_RE.sub('', name)
for word in name.split():
if word.isdigit() or ROMAN_RE.match(word):
name = word
break
return name
def build_name_for(expr):
if not expr:
counter = count(1)
return lambda elem: str(counter.next())
selector = XPath(expr, namespaces=NSMAP)
def name_for(elem):
results = selector(elem)
if not results:
return ''
name = ' '.join(results)
return filter_name(name)
return name_for
def add_page_map(opfpath, opts):
oeb = OEBBook(opfpath)
selector = XPath(opts.page, namespaces=NSMAP)
name_for = build_name_for(opts.page_names)
idgen = ("calibre-page-%d" % n for n in count(1))
for item in oeb.spine:
data = item.data
for elem in selector(data):
name = name_for(elem)
id = elem.get('id', None)
if id is None:
id = elem.attrib['id'] = idgen.next()
href = '#'.join((item.href, id))
oeb.pages.add(name, href)
writer = DirWriter(version='2.0', page_map=True)
writer.dump(oeb, opfpath)

View File

@ -246,6 +246,10 @@ class DirWriter(object):
def dump(self, oeb, path): def dump(self, oeb, path):
version = int(self.version[0]) version = int(self.version[0])
opfname = None
if os.path.splitext(path)[1].lower() == '.opf':
opfname = os.path.basename(path)
path = os.path.dirname(path)
if not os.path.isdir(path): if not os.path.isdir(path):
os.mkdir(path) os.mkdir(path)
output = DirContainer(path) output = DirContainer(path)
@ -257,7 +261,9 @@ class DirWriter(object):
metadata = oeb.to_opf2(page_map=self.page_map) metadata = oeb.to_opf2(page_map=self.page_map)
else: else:
raise OEBError("Unrecognized OPF version %r" % self.version) raise OEBError("Unrecognized OPF version %r" % self.version)
for href, data in metadata.values(): for mime, (href, data) in metadata.items():
if opfname and mime == OPF_MIME:
href = opfname
output.write(href, xml2str(data)) output.write(href, xml2str(data))
return return
@ -551,9 +557,6 @@ class Manifest(object):
for elem in data: for elem in data:
nroot.append(elem) nroot.append(elem)
data = nroot data = nroot
# Remove any encoding-specifying <meta/> elements
for meta in self.META_XP(data):
meta.getparent().remove(meta)
# Ensure has a <head/> # Ensure has a <head/>
head = xpath(data, '/h:html/h:head') head = xpath(data, '/h:html/h:head')
head = head[0] if head else None head = head[0] if head else None
@ -569,6 +572,12 @@ class Manifest(object):
'File %r missing <title/> element' % self.href) 'File %r missing <title/> element' % self.href)
title = etree.SubElement(head, XHTML('title')) title = etree.SubElement(head, XHTML('title'))
title.text = self.oeb.translate(__('Unknown')) title.text = self.oeb.translate(__('Unknown'))
# Remove any encoding-specifying <meta/> elements
for meta in self.META_XP(data):
meta.getparent().remove(meta)
etree.SubElement(head, XHTML('meta'),
attrib={'http-equiv': 'Content-Type',
'content': '%s; charset=utf-8' % XHTML_NS})
# Ensure has a <body/> # Ensure has a <body/>
if not xpath(data, '/h:html/h:body'): if not xpath(data, '/h:html/h:body'):
self.oeb.logger.warn( self.oeb.logger.warn(

View File

@ -224,6 +224,7 @@ class Config(ResizableDialog, Ui_Dialog):
g.setValue(val) g.setValue(val)
elif isinstance(g, (QLineEdit, QTextEdit)): elif isinstance(g, (QLineEdit, QTextEdit)):
getattr(g, 'setPlainText', g.setText)(val) getattr(g, 'setPlainText', g.setText)(val)
getattr(g, 'setCursorPosition', lambda x: x)(0)
elif isinstance(g, QComboBox): elif isinstance(g, QComboBox):
for value in pref.choices: for value in pref.choices:
g.addItem(value) g.addItem(value)
@ -253,7 +254,8 @@ class Config(ResizableDialog, Ui_Dialog):
self.source_format = d.format() self.source_format = d.format()
def accept(self): def accept(self):
for opt in ('chapter', 'level1_toc', 'level2_toc', 'level3_toc'): for opt in ('chapter', 'level1_toc', 'level2_toc', 'level3_toc', 'page',
'page_names'):
text = unicode(getattr(self, 'opt_'+opt).text()) text = unicode(getattr(self, 'opt_'+opt).text())
if text: if text:
try: try:

View File

@ -524,7 +524,7 @@
</layout> </layout>
</widget> </widget>
<widget class="QWidget" name="pagesetup_page" > <widget class="QWidget" name="pagesetup_page" >
<layout class="QGridLayout" name="_13" > <layout class="QGridLayout" name="gridLayout_7" >
<item row="0" column="0" > <item row="0" column="0" >
<widget class="QLabel" name="profile_label" > <widget class="QLabel" name="profile_label" >
<property name="text" > <property name="text" >
@ -545,6 +545,32 @@
</property> </property>
</widget> </widget>
</item> </item>
<item row="1" column="0" >
<widget class="QLabel" name="source_profile_label" >
<property name="text" >
<string>&amp;Source profile:</string>
</property>
<property name="buddy" >
<cstring>opt_source_profile</cstring>
</property>
</widget>
</item>
<item row="1" column="1" >
<widget class="QComboBox" name="opt_source_profile" />
</item>
<item row="2" column="0" >
<widget class="QLabel" name="dest_profile_label" >
<property name="text" >
<string>&amp;Destination profile:</string>
</property>
<property name="buddy" >
<cstring>opt_dest_profile</cstring>
</property>
</widget>
</item>
<item row="2" column="1" >
<widget class="QComboBox" name="opt_dest_profile" />
</item>
<item row="3" column="0" > <item row="3" column="0" >
<widget class="QLabel" name="label_12" > <widget class="QLabel" name="label_12" >
<property name="text" > <property name="text" >
@ -644,31 +670,72 @@
</property> </property>
</widget> </widget>
</item> </item>
<item row="1" column="0" > <item row="8" column="0" colspan="2" >
<widget class="QLabel" name="source_profile_label" > <widget class="QGroupBox" name="page_map_box" >
<property name="text" > <property name="title" >
<string>&amp;Source profile:</string> <string>&amp;Page map</string>
</property>
<property name="buddy" >
<cstring>opt_source_profile</cstring>
</property> </property>
<layout class="QGridLayout" name="gridLayout" >
<item rowspan="2" row="0" column="0" colspan="4" >
<widget class="QLabel" name="label_23" >
<property name="sizePolicy" >
<sizepolicy vsizetype="Minimum" hsizetype="Preferred" >
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="text" >
<string>&lt;p>You can control how calibre detects page boundaries using a XPath expression. To learn how to use XPath expressions see the &lt;a href="http://calibre.kovidgoyal.net/user_manual/xpath.html">XPath tutorial&lt;/a>. The page boundaries are useful only if you want a mapping from pages in a paper book, to locations in the e-book. This controls where Adobe Digital Editions displays the page numbers in the right margin.&lt;/p></string>
</property>
<property name="wordWrap" >
<bool>true</bool>
</property>
<property name="openExternalLinks" >
<bool>true</bool>
</property>
</widget>
</item>
<item row="1" column="0" >
<widget class="QLabel" name="label_21" >
<property name="text" >
<string>&amp;Boundary XPath:</string>
</property>
<property name="buddy" >
<cstring>opt_page</cstring>
</property>
</widget>
</item>
<item row="1" column="1" >
<widget class="QLineEdit" name="opt_page" />
</item>
<item row="1" column="2" >
<widget class="QLabel" name="label_22" >
<property name="text" >
<string>&amp;Name XPath:</string>
</property>
<property name="buddy" >
<cstring>opt_page_names</cstring>
</property>
</widget>
</item>
<item row="1" column="3" >
<widget class="QLineEdit" name="opt_page_names" />
</item>
</layout>
</widget> </widget>
</item> </item>
<item row="1" column="1" > <item row="9" column="0" >
<widget class="QComboBox" name="opt_source_profile" /> <spacer name="verticalSpacer" >
</item> <property name="orientation" >
<item row="2" column="0" > <enum>Qt::Vertical</enum>
<widget class="QLabel" name="dest_profile_label" >
<property name="text" >
<string>&amp;Destination profile:</string>
</property> </property>
<property name="buddy" > <property name="sizeHint" stdset="0" >
<cstring>opt_dest_profile</cstring> <size>
<width>20</width>
<height>40</height>
</size>
</property> </property>
</widget> </spacer>
</item>
<item row="2" column="1" >
<widget class="QComboBox" name="opt_dest_profile" />
</item> </item>
</layout> </layout>
</widget> </widget>
@ -679,21 +746,8 @@
<property name="title" > <property name="title" >
<string>Automatic &amp;chapter detection</string> <string>Automatic &amp;chapter detection</string>
</property> </property>
<layout class="QGridLayout" name="gridLayout" > <layout class="QVBoxLayout" name="verticalLayout_4" >
<item row="1" column="0" > <item>
<widget class="QLabel" name="label_17" >
<property name="text" >
<string>&amp;XPath:</string>
</property>
<property name="buddy" >
<cstring>opt_chapter</cstring>
</property>
</widget>
</item>
<item row="1" column="1" >
<widget class="QLineEdit" name="opt_chapter" />
</item>
<item row="0" column="0" colspan="2" >
<widget class="QLabel" name="label_8" > <widget class="QLabel" name="label_8" >
<property name="text" > <property name="text" >
<string>&lt;p>You can control how calibre detects chapters using a XPath expression. To learn how to use XPath expressions see the &lt;a href="http://calibre.kovidgoyal.net/user_manual/xpath.html">XPath tutorial&lt;/a>&lt;/p></string> <string>&lt;p>You can control how calibre detects chapters using a XPath expression. To learn how to use XPath expressions see the &lt;a href="http://calibre.kovidgoyal.net/user_manual/xpath.html">XPath tutorial&lt;/a>&lt;/p></string>
@ -709,18 +763,35 @@
</property> </property>
</widget> </widget>
</item> </item>
<item row="2" column="1" > <item>
<widget class="QComboBox" name="opt_chapter_mark" /> <layout class="QHBoxLayout" name="horizontalLayout_3" >
</item> <item>
<item row="2" column="0" > <widget class="QLabel" name="label_17" >
<widget class="QLabel" name="label_9" > <property name="text" >
<property name="text" > <string>&amp;XPath:</string>
<string>Chapter &amp;mark:</string> </property>
</property> <property name="buddy" >
<property name="buddy" > <cstring>opt_chapter</cstring>
<cstring>opt_chapter_mark</cstring> </property>
</property> </widget>
</widget> </item>
<item>
<widget class="QLineEdit" name="opt_chapter" />
</item>
<item>
<widget class="QLabel" name="label_9" >
<property name="text" >
<string>Chapter &amp;mark:</string>
</property>
<property name="buddy" >
<cstring>opt_chapter_mark</cstring>
</property>
</widget>
</item>
<item>
<widget class="QComboBox" name="opt_chapter_mark" />
</item>
</layout>
</item> </item>
</layout> </layout>
</widget> </widget>
@ -847,7 +918,7 @@
</property> </property>
</widget> </widget>
</item> </item>
<item row="2" column="0" colspan="2" > <item row="3" column="0" colspan="2" >
<widget class="QDialogButtonBox" name="buttonBox" > <widget class="QDialogButtonBox" name="buttonBox" >
<property name="orientation" > <property name="orientation" >
<enum>Qt::Horizontal</enum> <enum>Qt::Horizontal</enum>

View File

@ -19,4 +19,5 @@ class Config(_Config):
self.opt_dont_split_on_page_breaks.setVisible(False) self.opt_dont_split_on_page_breaks.setVisible(False)
self.opt_preserve_tag_structure.setVisible(False) self.opt_preserve_tag_structure.setVisible(False)
self.opt_linearize_tables.setVisible(False) self.opt_linearize_tables.setVisible(False)
self.opt_no_justification.setVisible(False) self.opt_no_justification.setVisible(False)
self.page_map_box.setVisible(False)

View File

@ -114,6 +114,9 @@
<property name="text" > <property name="text" >
<string>See the &lt;a href="http://calibre.kovidgoyal.net/user_manual/gui.html#the-search-interface">User Manual&lt;/a> for more help</string> <string>See the &lt;a href="http://calibre.kovidgoyal.net/user_manual/gui.html#the-search-interface">User Manual&lt;/a> for more help</string>
</property> </property>
<property name="openExternalLinks" >
<bool>true</bool>
</property>
</widget> </widget>
</item> </item>
<item> <item>

View File

@ -115,16 +115,11 @@ class Main(MainWindow, Ui_MainWindow):
self.connect(self.donate_action, SIGNAL('triggered(bool)'), self.donate) self.connect(self.donate_action, SIGNAL('triggered(bool)'), self.donate)
self.connect(self.restore_action, SIGNAL('triggered(bool)'), lambda c : self.show()) self.connect(self.restore_action, SIGNAL('triggered(bool)'), lambda c : self.show())
self.connect(self.action_show_book_details, SIGNAL('triggered(bool)'), self.show_book_info) self.connect(self.action_show_book_details, SIGNAL('triggered(bool)'), self.show_book_info)
def restart_app(c): self.connect(self.action_restart, SIGNAL('triggered(bool)'),
self.quit(None, restart=True) lambda c : self.quit(None, restart=True))
self.connect(self.action_restart, SIGNAL('triggered(bool)'), restart_app) self.connect(self.system_tray_icon, SIGNAL('activated(QSystemTrayIcon::ActivationReason)'),
def sta(r): self.system_tray_icon_activated)
if r == QSystemTrayIcon.Trigger: self.tool_bar.contextMenuEvent = self.no_op
self.hide() if self.isVisible() else self.show()
self.connect(self.system_tray_icon, SIGNAL('activated(QSystemTrayIcon::ActivationReason)'), sta)
def tcme(self, *args):
pass
self.tool_bar.contextMenuEvent = tcme
####################### Location View ######################## ####################### Location View ########################
QObject.connect(self.location_view, SIGNAL('location_selected(PyQt_PyObject)'), QObject.connect(self.location_view, SIGNAL('location_selected(PyQt_PyObject)'),
self.location_selected) self.location_selected)
@ -165,15 +160,11 @@ class Main(MainWindow, Ui_MainWindow):
sm.addSeparator() sm.addSeparator()
sm.addAction(_('Send to storage card by default')) sm.addAction(_('Send to storage card by default'))
sm.actions()[-1].setCheckable(True) sm.actions()[-1].setCheckable(True)
def default_sync(checked): QObject.connect(sm.actions()[-1], SIGNAL('toggled(bool)'),
config.set('send_to_storage_card_by_default', bool(checked)) self.do_default_sync)
QObject.disconnect(self.action_sync, SIGNAL("triggered(bool)"), self.sync_to_main_memory)
QObject.disconnect(self.action_sync, SIGNAL("triggered(bool)"), self.sync_to_card)
QObject.connect(self.action_sync, SIGNAL("triggered(bool)"), self.sync_to_card if checked else self.sync_to_main_memory)
QObject.connect(sm.actions()[-1], SIGNAL('toggled(bool)'), default_sync)
sm.actions()[-1].setChecked(config.get('send_to_storage_card_by_default')) sm.actions()[-1].setChecked(config.get('send_to_storage_card_by_default'))
default_sync(sm.actions()[-1].isChecked()) self.do_default_sync(sm.actions()[-1].isChecked())
self.sync_menu = sm # Needed self.sync_menu = sm # Needed
md = QMenu() md = QMenu()
md.addAction(_('Edit metadata individually')) md.addAction(_('Edit metadata individually'))
@ -371,6 +362,31 @@ class Main(MainWindow, Ui_MainWindow):
self.connect(self.action_news, SIGNAL('triggered(bool)'), self.scheduler.show_dialog) self.connect(self.action_news, SIGNAL('triggered(bool)'), self.scheduler.show_dialog)
self.location_view.setCurrentIndex(self.location_view.model().index(0)) self.location_view.setCurrentIndex(self.location_view.model().index(0))
def no_op(self, *args):
pass
def system_tray_icon_activated(self, r):
if r == QSystemTrayIcon.Trigger:
if self.isVisible():
for window in QApplication.topLevelWidgets():
if isinstance(window, (MainWindow, QDialog)):
window.hide()
else:
for window in QApplication.topLevelWidgets():
if isinstance(window, (MainWindow, QDialog)):
if window not in (self.device_error_dialog, self.jobs_dialog):
window.show()
def do_default_sync(self, checked):
config.set('send_to_storage_card_by_default', bool(checked))
QObject.disconnect(self.action_sync, SIGNAL("triggered(bool)"),
self.sync_to_main_memory)
QObject.disconnect(self.action_sync, SIGNAL("triggered(bool)"),
self.sync_to_card)
QObject.connect(self.action_sync, SIGNAL("triggered(bool)"),
self.sync_to_card if checked else self.sync_to_main_memory)
def change_output_format(self, x): def change_output_format(self, x):
of = unicode(x).strip() of = unicode(x).strip()
if of != prefs['output_format']: if of != prefs['output_format']:

View File

@ -15,7 +15,7 @@ from PyQt4.QtCore import QCoreApplication, QThread, QReadWriteLock
from PyQt4.QtGui import QApplication, QPixmap, QImage from PyQt4.QtGui import QApplication, QPixmap, QImage
__app = None __app = None
from calibre.ebooks.metadata import title_sort from calibre.library import title_sort
from calibre.library.database import LibraryDatabase from calibre.library.database import LibraryDatabase
from calibre.library.sqlite import connect, IntegrityError from calibre.library.sqlite import connect, IntegrityError
from calibre.utils.search_query_parser import SearchQueryParser from calibre.utils.search_query_parser import SearchQueryParser

View File

@ -32,5 +32,6 @@ class GoogleReader(BasicNewsRecipe):
soup = self.index_to_soup('http://www.google.com/reader/api/0/tag/list') soup = self.index_to_soup('http://www.google.com/reader/api/0/tag/list')
for id in soup.findAll(True, attrs={'name':['id']}): for id in soup.findAll(True, attrs={'name':['id']}):
url = id.contents[0] url = id.contents[0]
feeds.append((re.search('/([^/]*)$', url).group(1), self.base_url + urllib.quote(url) + self.get_options)) feeds.append((re.search('/([^/]*)$', url).group(1),
self.base_url + urllib.quote(url.encode('utf-8')) + self.get_options))
return feeds return feeds