mirror of
https://github.com/kovidgoyal/calibre.git
synced 2025-07-09 03:04:10 -04:00
Sync to trunk.
This commit is contained in:
commit
b92567f538
@ -36,7 +36,7 @@ def run_install_jammer(installer_name='<%AppName%>-<%Version%><%Ext%>', build_fo
|
||||
compression = 'zlib'
|
||||
if build_for_release:
|
||||
cmdline += ['--build-for-release']
|
||||
compression = 'lzma (solid)'
|
||||
#compression = 'lzma (solid)'
|
||||
cmdline += ['-DCompressionMethod', compression]
|
||||
cmdline += ['--build', mpi]
|
||||
#print 'Running installjammer with cmdline:'
|
||||
|
@ -180,7 +180,8 @@ def main():
|
||||
if not cols: # On windows terminal width is unknown
|
||||
cols = 80
|
||||
|
||||
parser = OptionParser(usage="usage: %prog [options] command args\n\ncommand is one of: info, books, df, ls, cp, mkdir, touch, cat, rm\n\n"+
|
||||
parser = OptionParser(usage="usage: %prog [options] command args\n\ncommand "+
|
||||
"is one of: info, books, df, ls, cp, mkdir, touch, cat, rm, eject\n\n"+
|
||||
"For help on a particular command: %prog command", version=__appname__+" version: " + __version__)
|
||||
parser.add_option("--log-packets", help="print out packet stream to stdout. "+\
|
||||
"The numbers in the left column are byte offsets that allow the packet size to be read off easily.",
|
||||
@ -222,6 +223,8 @@ def main():
|
||||
for i in range(3):
|
||||
print "%-10s\t%s\t%s\t%s\t%s"%(where[i], human_readable(total[i]), human_readable(total[i]-free[i]), human_readable(free[i]),\
|
||||
str(0 if total[i]==0 else int(100*(total[i]-free[i])/(total[i]*1.)))+"%")
|
||||
elif command == 'eject':
|
||||
dev.eject()
|
||||
elif command == "books":
|
||||
print "Books in main memory:"
|
||||
for book in dev.books():
|
||||
|
@ -7,7 +7,7 @@ intended to be subclassed with the relevant parts implemented for a particular
|
||||
device. This class handles device detection.
|
||||
'''
|
||||
|
||||
import os, subprocess, time, re, sys, glob
|
||||
import os, subprocess, time, re, sys, glob, shutil
|
||||
from itertools import repeat
|
||||
|
||||
from calibre.devices.interface import DevicePlugin
|
||||
@ -548,13 +548,23 @@ class Device(DeviceConfig, DevicePlugin):
|
||||
drives = self.find_device_nodes()
|
||||
for drive in drives:
|
||||
if drive:
|
||||
cmd = ['pumount']
|
||||
cmd = ['pumount', '-l']
|
||||
try:
|
||||
p = subprocess.Popen(cmd + [drive])
|
||||
except:
|
||||
pass
|
||||
while p.poll() is None:
|
||||
time.sleep(0.1)
|
||||
if p.returncode == 0:
|
||||
for x in ('_main_prefix', '_card_a_prefix', '_card_b_prefix'):
|
||||
x = getattr(self, x, None)
|
||||
if x is not None:
|
||||
if x.startswith('/media/') and os.path.exists(x):
|
||||
try:
|
||||
shutil.rmtree(x)
|
||||
except:
|
||||
pass
|
||||
|
||||
|
||||
def eject(self):
|
||||
if islinux:
|
||||
|
@ -283,7 +283,7 @@ class ComicInput(InputFormatPlugin):
|
||||
OptionRecommendation(name='disable_trim', recommended_value=False,
|
||||
help=_('Disable trimming of comic pages. For some comics, '
|
||||
'trimming might remove content as well as borders.')),
|
||||
OptionRecommendation(name='landspace', recommended_value=False,
|
||||
OptionRecommendation(name='landscape', recommended_value=False,
|
||||
help=_("Don't split landscape images into two portrait images")),
|
||||
OptionRecommendation(name='wide', recommended_value=False,
|
||||
help=_("Keep aspect ratio and scale image using screen height as "
|
||||
|
@ -161,7 +161,7 @@ class ReBinary(object):
|
||||
def tree_to_binary(self, elem, nsrmap=NSRMAP, parents=[],
|
||||
inhead=False, preserve=False):
|
||||
if not isinstance(elem.tag, basestring):
|
||||
self.write(etree.tostring(elem))
|
||||
# Don't emit any comments or raw entities
|
||||
return
|
||||
nsrmap = copy.copy(nsrmap)
|
||||
attrib = dict(elem.attrib)
|
||||
@ -308,23 +308,6 @@ class LitWriter(object):
|
||||
else:
|
||||
self._logger.warn('No suitable cover image found.')
|
||||
|
||||
# Remove comments because they are not supported by LIT HTML
|
||||
for item in oeb.spine:
|
||||
for elem in item.data.getiterator():
|
||||
print elem.tag
|
||||
if isinstance(elem, etree._Comment):
|
||||
tail = elem.tail
|
||||
parent = elem.getparent()
|
||||
parent.remove(elem)
|
||||
|
||||
text = u''
|
||||
if parent.text:
|
||||
text += parent.text
|
||||
if tail:
|
||||
text += tail
|
||||
|
||||
parent.text = text
|
||||
|
||||
def __call__(self, oeb, path):
|
||||
if hasattr(path, 'write'):
|
||||
return self._dump_stream(oeb, path)
|
||||
@ -726,5 +709,3 @@ class LitWriter(object):
|
||||
ichunk = ''.join(['AOLI', pack('<IQ', rem, len(dchunks)),
|
||||
ichunk.getvalue(), ('\0' * pad), pack('<H', len(dchunks))])
|
||||
return dcounts, dchunks, ichunk
|
||||
|
||||
|
||||
|
@ -11,11 +11,14 @@ __docformat__ = 'restructuredtext en'
|
||||
|
||||
from struct import pack, unpack
|
||||
from cStringIO import StringIO
|
||||
from datetime import datetime
|
||||
|
||||
from calibre.ebooks.mobi import MobiError
|
||||
from calibre.ebooks.mobi.writer import rescale_image, MAX_THUMB_DIMEN
|
||||
from calibre.ebooks.mobi.langcodes import iana2mobi
|
||||
|
||||
class StreamSlicer(object):
|
||||
|
||||
def __init__(self, stream, start=0, stop=None):
|
||||
self._stream = stream
|
||||
self.start = start
|
||||
@ -84,17 +87,22 @@ class MetadataUpdater(object):
|
||||
flags, = unpack('>I', record0[128:132])
|
||||
have_exth = self.have_exth = (flags & 0x40) != 0
|
||||
self.cover_record = self.thumbnail_record = None
|
||||
self.timestamp = None
|
||||
if not have_exth:
|
||||
return
|
||||
exth_off = unpack('>I', record0[20:24])[0] + 16 + record0.start
|
||||
exth = self.exth = StreamSlicer(stream, exth_off, record0.stop)
|
||||
nitems, = unpack('>I', exth[8:12])
|
||||
pos = 12
|
||||
# Store any EXTH fields not specifiable in GUI
|
||||
for i in xrange(nitems):
|
||||
id, size = unpack('>II', exth[pos:pos + 8])
|
||||
content = exth[pos + 8: pos + size]
|
||||
pos += size
|
||||
if id == 201:
|
||||
|
||||
if id == 106:
|
||||
self.timestamp = content
|
||||
elif id == 201:
|
||||
rindex, = self.cover_rindex, = unpack('>I', content)
|
||||
self.cover_record = self.record(rindex + image_base)
|
||||
elif id == 202:
|
||||
@ -134,6 +142,16 @@ class MetadataUpdater(object):
|
||||
if mi.tags:
|
||||
subjects = '; '.join(mi.tags)
|
||||
recs.append((105, subjects.encode(self.codec, 'replace')))
|
||||
|
||||
if mi.pubdate:
|
||||
recs.append((106, str(mi.pubdate).encode(self.codec, 'replace')))
|
||||
elif mi.timestamp:
|
||||
recs.append((106, str(mi.timestamp).encode(self.codec, 'replace')))
|
||||
elif self.timestamp:
|
||||
recs.append(106, self.timestamp)
|
||||
else:
|
||||
recs.append((106, str(datetime.now()).encode(self.codec, 'replace')))
|
||||
|
||||
if self.cover_record is not None:
|
||||
recs.append((201, pack('>I', self.cover_rindex)))
|
||||
recs.append((203, pack('>I', 0)))
|
||||
|
@ -49,6 +49,7 @@ class BlockState(object):
|
||||
|
||||
class FormatState(object):
|
||||
def __init__(self):
|
||||
self.rendered = False
|
||||
self.left = 0.
|
||||
self.halign = 'auto'
|
||||
self.indent = 0.
|
||||
@ -159,13 +160,15 @@ class MobiMLizer(object):
|
||||
indent = 0
|
||||
elif indent != 0 and abs(indent) < self.profile.fbase:
|
||||
indent = (indent / abs(indent)) * self.profile.fbase
|
||||
if tag in NESTABLE_TAGS:
|
||||
if tag in NESTABLE_TAGS and not istate.rendered:
|
||||
para = wrapper = etree.SubElement(
|
||||
parent, XHTML(tag), attrib=istate.attrib)
|
||||
bstate.nested.append(para)
|
||||
if tag == 'li' and len(istates) > 1:
|
||||
istates[-2].list_num += 1
|
||||
para.attrib['value'] = str(istates[-2].list_num)
|
||||
elif tag in NESTABLE_TAGS and istate.rendered:
|
||||
para = wrapper = bstate.nested[-1]
|
||||
elif left > 0 and indent >= 0:
|
||||
para = wrapper = etree.SubElement(parent, XHTML('blockquote'))
|
||||
para = wrapper
|
||||
@ -189,6 +192,7 @@ class MobiMLizer(object):
|
||||
vspace -= 1
|
||||
if istate.halign != 'auto' and isinstance(istate.halign, (str, unicode)):
|
||||
para.attrib['align'] = istate.halign
|
||||
istate.rendered = True
|
||||
pstate = bstate.istate
|
||||
if tag in CONTENT_TAGS:
|
||||
bstate.inline = para
|
||||
@ -253,6 +257,7 @@ class MobiMLizer(object):
|
||||
return
|
||||
tag = barename(elem.tag)
|
||||
istate = copy.copy(istates[-1])
|
||||
istate.rendered = False
|
||||
istate.list_num = 0
|
||||
istates.append(istate)
|
||||
left = 0
|
||||
|
@ -24,6 +24,10 @@ class MOBIOutput(OutputFormatPlugin):
|
||||
recommended_value=False, level=OptionRecommendation.LOW,
|
||||
help=_('When present, use author sort field as author.')
|
||||
),
|
||||
OptionRecommendation(name='no_inline_toc',
|
||||
recommended_value=False, level=OptionRecommendation.LOW,
|
||||
help=_('Don\'t add Table of Contents to end of book. Useful if '
|
||||
'the book has its own table of contents.')),
|
||||
OptionRecommendation(name='toc_title', recommended_value=None,
|
||||
help=_('Title for any generated in-line table of contents.')
|
||||
),
|
||||
@ -35,8 +39,6 @@ class MOBIOutput(OutputFormatPlugin):
|
||||
recommended_value=False, level=OptionRecommendation.LOW,
|
||||
help=_('Disable compression of the file contents.')
|
||||
),
|
||||
|
||||
|
||||
])
|
||||
|
||||
def convert(self, oeb, output_path, input_plugin, opts, log):
|
||||
@ -49,8 +51,9 @@ class MOBIOutput(OutputFormatPlugin):
|
||||
from calibre.ebooks.oeb.transforms.htmltoc import HTMLTOCAdder
|
||||
from calibre.customize.ui import plugin_for_input_format
|
||||
imagemax = PALM_MAX_IMAGE_SIZE if opts.rescale_images else None
|
||||
tocadder = HTMLTOCAdder(title=opts.toc_title)
|
||||
tocadder(oeb, opts)
|
||||
if not opts.no_inline_toc:
|
||||
tocadder = HTMLTOCAdder(title=opts.toc_title)
|
||||
tocadder(oeb, opts)
|
||||
mangler = CaseMangler()
|
||||
mangler(oeb, opts)
|
||||
rasterizer = SVGRasterizer()
|
||||
@ -58,7 +61,6 @@ class MOBIOutput(OutputFormatPlugin):
|
||||
mobimlizer = MobiMLizer(ignore_tables=opts.linearize_tables)
|
||||
mobimlizer(oeb, opts)
|
||||
write_page_breaks_after_item = not input_plugin is plugin_for_input_format('cbz')
|
||||
print 111111, write_page_breaks_after_item
|
||||
writer = MobiWriter(opts, imagemax=imagemax,
|
||||
compression=UNCOMPRESSED if opts.dont_compress else PALMDOC,
|
||||
prefer_author_sort=opts.prefer_author_sort,
|
||||
|
@ -345,10 +345,13 @@ class MobiReader(object):
|
||||
root.insert(0, head)
|
||||
head.text = '\n\t'
|
||||
link = head.makeelement('link', {'type':'text/css',
|
||||
'href':'styles.css'})
|
||||
'href':'styles.css', 'rel':'stylesheet'})
|
||||
head.insert(0, link)
|
||||
link.tail = '\n\t'
|
||||
title = head.xpath('descendant::title')
|
||||
m = head.makeelement('meta', {'http-equiv':'Content-Type',
|
||||
'content':'text/html; charset=utf-8'})
|
||||
head.insert(0, m)
|
||||
if not title:
|
||||
title = head.makeelement('title', {})
|
||||
title.text = self.book_header.title
|
||||
|
@ -455,7 +455,7 @@ class Metadata(object):
|
||||
'description', 'format', 'identifier', 'language',
|
||||
'publisher', 'relation', 'rights', 'source',
|
||||
'subject', 'title', 'type'])
|
||||
CALIBRE_TERMS = set(['series', 'series_index', 'rating'])
|
||||
CALIBRE_TERMS = set(['series', 'series_index', 'rating', 'timestamp'])
|
||||
OPF_ATTRS = {'role': OPF('role'), 'file-as': OPF('file-as'),
|
||||
'scheme': OPF('scheme'), 'event': OPF('event'),
|
||||
'type': XSI('type'), 'lang': XML('lang'), 'id': 'id'}
|
||||
@ -693,6 +693,7 @@ class Metadata(object):
|
||||
def to_opf2(self, parent=None):
|
||||
nsmap = self._opf2_nsmap
|
||||
nsrmap = dict((value, key) for key, value in nsmap.items())
|
||||
nsmap.pop('opf', '')
|
||||
elem = element(parent, OPF('metadata'), nsmap=nsmap)
|
||||
for term in self.items:
|
||||
for item in self.items[term]:
|
||||
@ -877,7 +878,7 @@ class Manifest(object):
|
||||
if title:
|
||||
title = unicode(title[0])
|
||||
else:
|
||||
title = 'No title'
|
||||
title = _('Unknown')
|
||||
|
||||
return self._parse_xhtml(txt_to_markdown(data, title))
|
||||
|
||||
|
@ -28,9 +28,8 @@ class OEBOutput(OutputFormatPlugin):
|
||||
href, root = results.pop(key, [None, None])
|
||||
if root is not None:
|
||||
raw = etree.tostring(root, pretty_print=True,
|
||||
encoding='utf-8')
|
||||
encoding='utf-8', xml_declaration=True)
|
||||
with open(href, 'wb') as f:
|
||||
f.write('<?xml version="1.0" encoding="UTF-8" ?>\n')
|
||||
f.write(raw)
|
||||
|
||||
for item in oeb_book.manifest:
|
||||
|
@ -129,6 +129,9 @@ class OEBReader(object):
|
||||
from calibre.ebooks.oeb.transforms.metadata import meta_info_to_oeb_metadata
|
||||
stream = cStringIO.StringIO(etree.tostring(opf))
|
||||
mi = MetaInformation(OPF(stream))
|
||||
if not mi.language:
|
||||
mi.language = get_lang()
|
||||
self.oeb.metadata.add('language', mi.language)
|
||||
if not mi.title:
|
||||
mi.title = self.oeb.translate(__('Unknown'))
|
||||
if not mi.authors:
|
||||
@ -136,8 +139,6 @@ class OEBReader(object):
|
||||
if not mi.book_producer:
|
||||
mi.book_producer = '%(a)s (%(v)s) [http://%(a)s.kovidgoyal.net]'%\
|
||||
dict(a=__appname__, v=__version__)
|
||||
if not mi.language:
|
||||
mi.language = get_lang()
|
||||
meta_info_to_oeb_metadata(mi, self.oeb.metadata, self.logger)
|
||||
bookid = "urn:uuid:%s" % str(uuid.uuid4()) if mi.application_id is None \
|
||||
else mi.application_id
|
||||
|
@ -7,6 +7,7 @@ __copyright__ = '2009, Kovid Goyal <kovid@kovidgoyal.net>'
|
||||
__docformat__ = 'restructuredtext en'
|
||||
|
||||
import os
|
||||
from datetime import datetime
|
||||
|
||||
def meta_info_to_oeb_metadata(mi, m, log):
|
||||
if mi.title:
|
||||
@ -56,6 +57,15 @@ def meta_info_to_oeb_metadata(mi, m, log):
|
||||
m.clear('subject')
|
||||
for t in mi.tags:
|
||||
m.add('subject', t)
|
||||
if mi.pubdate is not None:
|
||||
m.clear('date')
|
||||
m.add('date', mi.pubdate.isoformat())
|
||||
if mi.timestamp is not None:
|
||||
m.clear('timestamp')
|
||||
m.add('timestamp', mi.timestamp.isoformat())
|
||||
if not m.timestamp:
|
||||
m.add('timestamp', datetime.utcnow().isoformat())
|
||||
|
||||
|
||||
|
||||
class MergeMetadata(object):
|
||||
|
@ -24,6 +24,7 @@ class ManifestTrimmer(object):
|
||||
|
||||
def __call__(self, oeb, context):
|
||||
oeb.logger.info('Trimming unused files from manifest...')
|
||||
self.opts = context
|
||||
used = set()
|
||||
for term in oeb.metadata:
|
||||
for item in oeb.metadata[term]:
|
||||
@ -63,5 +64,8 @@ class ManifestTrimmer(object):
|
||||
unchecked = new
|
||||
for item in oeb.manifest.values():
|
||||
if item not in used:
|
||||
if getattr(self.opts, 'mobi_periodical', False) and \
|
||||
item.href == 'images/mastheadImage.gif':
|
||||
continue
|
||||
oeb.logger.info('Trimming %r from manifest' % item.href)
|
||||
oeb.manifest.remove(item)
|
||||
|
@ -35,7 +35,7 @@ class OEBWriter(object):
|
||||
help=_('OPF version to generate. Default is %default.'))
|
||||
oeb('adobe_page_map', ['--adobe-page-map'], default=False,
|
||||
help=_('Generate an Adobe "page-map" file if pagination '
|
||||
'information is avaliable.'))
|
||||
'information is available.'))
|
||||
return cfg
|
||||
|
||||
@classmethod
|
||||
|
@ -23,7 +23,7 @@ class PDBInput(InputFormatPlugin):
|
||||
Reader = get_reader(header.ident)
|
||||
|
||||
if Reader is None:
|
||||
raise PDBError('No reader avaliable for format within container.\n Identity is %s. Book type is %s' % (header.ident, IDENTITY_TO_NAME.get(header.ident, _('Unknown'))))
|
||||
raise PDBError('No reader available for format within container.\n Identity is %s. Book type is %s' % (header.ident, IDENTITY_TO_NAME.get(header.ident, _('Unknown'))))
|
||||
|
||||
log.debug('Detected ebook format as: %s with identity: %s' % (IDENTITY_TO_NAME[header.ident], header.ident))
|
||||
|
||||
|
@ -42,7 +42,7 @@ class PDBOutput(OutputFormatPlugin):
|
||||
Writer = get_writer(opts.format)
|
||||
|
||||
if Writer is None:
|
||||
raise PDBError('No writer avaliable for format %s.' % format)
|
||||
raise PDBError('No writer available for format %s.' % format)
|
||||
|
||||
writer = Writer(opts, log)
|
||||
|
||||
|
@ -17,9 +17,9 @@ def txt_to_markdown(txt, title=''):
|
||||
md = markdown.Markdown(
|
||||
extensions=['footnotes', 'tables', 'toc'],
|
||||
safe_mode=False,)
|
||||
html = '<html><head><title>%s</title></head><body>%s</body></html>' % (title,
|
||||
html = u'<html><head><title>%s</title></head><body>%s</body></html>' % (title,
|
||||
md.convert(txt))
|
||||
|
||||
|
||||
return html
|
||||
|
||||
def opf_writer(path, opf_name, manifest, spine, mi):
|
||||
|
@ -57,7 +57,7 @@ class MetadataWidget(Widget, Ui_Form):
|
||||
try:
|
||||
self.series_index.setValue(mi.series_index)
|
||||
except:
|
||||
self.series_index.setValue(0)
|
||||
self.series_index.setValue(1.0)
|
||||
|
||||
cover = self.db.cover(self.book_id, index_is_id=True)
|
||||
if cover:
|
||||
|
@ -14,7 +14,7 @@
|
||||
<string>Form</string>
|
||||
</property>
|
||||
<layout class="QGridLayout" name="gridLayout">
|
||||
<item row="0" column="0">
|
||||
<item row="1" column="0">
|
||||
<widget class="QLabel" name="label">
|
||||
<property name="text">
|
||||
<string>&Title for Table of Contents:</string>
|
||||
@ -24,24 +24,24 @@
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="0" column="1">
|
||||
<item row="1" column="1">
|
||||
<widget class="QLineEdit" name="opt_toc_title"/>
|
||||
</item>
|
||||
<item row="1" column="0" colspan="2">
|
||||
<item row="2" column="0" colspan="2">
|
||||
<widget class="QCheckBox" name="opt_rescale_images">
|
||||
<property name="text">
|
||||
<string>Rescale images for &Palm devices</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="2" column="0" colspan="2">
|
||||
<item row="3" column="0" colspan="2">
|
||||
<widget class="QCheckBox" name="opt_prefer_author_sort">
|
||||
<property name="text">
|
||||
<string>Use author &sort for author</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="5" column="0">
|
||||
<item row="6" column="0">
|
||||
<spacer name="verticalSpacer">
|
||||
<property name="orientation">
|
||||
<enum>Qt::Vertical</enum>
|
||||
@ -54,20 +54,27 @@
|
||||
</property>
|
||||
</spacer>
|
||||
</item>
|
||||
<item row="3" column="0">
|
||||
<item row="4" column="0">
|
||||
<widget class="QCheckBox" name="opt_dont_compress">
|
||||
<property name="text">
|
||||
<string>Disable compression of the file contents</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="4" column="0">
|
||||
<item row="5" column="0">
|
||||
<widget class="QCheckBox" name="opt_mobi_periodical">
|
||||
<property name="text">
|
||||
<string>Generate a periodical rather than a book</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="0" column="0">
|
||||
<widget class="QCheckBox" name="opt_no_inline_toc">
|
||||
<property name="text">
|
||||
<string>Do not add Table of Contents to book</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
<resources/>
|
||||
|
@ -17,7 +17,7 @@
|
||||
<item row="0" column="0">
|
||||
<widget class="QGroupBox" name="groupBox">
|
||||
<property name="title">
|
||||
<string>Select avaliable formats and their order for this device</string>
|
||||
<string>Select available formats and their order for this device</string>
|
||||
</property>
|
||||
<layout class="QVBoxLayout" name="verticalLayout_7">
|
||||
<item>
|
||||
|
BIN
src/calibre/gui2/images/news/fastcompany.png
Normal file
BIN
src/calibre/gui2/images/news/fastcompany.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 5.3 KiB |
BIN
src/calibre/gui2/images/news/inquirer_net.png
Normal file
BIN
src/calibre/gui2/images/news/inquirer_net.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 837 B |
BIN
src/calibre/gui2/images/news/uncrate.png
Normal file
BIN
src/calibre/gui2/images/news/uncrate.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 269 B |
@ -267,20 +267,7 @@ class BooksModel(QAbstractTableModel):
|
||||
self.endInsertRows()
|
||||
self.count_changed()
|
||||
|
||||
def clean_search_text(self, text):
|
||||
if not text:
|
||||
return text
|
||||
tokens = text.split(' ')
|
||||
for i, token in enumerate(tokens):
|
||||
if token.strip().endswith(':') or token.strip() == '':
|
||||
del tokens[i]
|
||||
text = ' '.join(tokens)
|
||||
if text.strip() == '':
|
||||
text = None
|
||||
return text
|
||||
|
||||
def search(self, text, refinement, reset=True):
|
||||
text = self.clean_search_text(text)
|
||||
self.db.search(text)
|
||||
self.last_search = text
|
||||
if reset:
|
||||
@ -899,9 +886,9 @@ class DeviceBooksModel(BooksModel):
|
||||
flags |= Qt.ItemIsEditable
|
||||
return flags
|
||||
|
||||
|
||||
def search(self, text, refinement, reset=True):
|
||||
text = self.clean_search_text(text)
|
||||
if not text:
|
||||
if not text or not text.strip():
|
||||
self.map = list(range(len(self.db)))
|
||||
else:
|
||||
matches = self.search_engine.parse(text)
|
||||
|
@ -313,6 +313,8 @@ class EbookViewer(MainWindow, Ui_EbookViewer):
|
||||
self.view.goto_bookmark(m)
|
||||
else:
|
||||
self.pending_bookmark = bm
|
||||
if spine_index < 0 or spine_index >= len(self.iterator.spine):
|
||||
spine_index = 0
|
||||
self.load_path(self.iterator.spine[spine_index])
|
||||
|
||||
def toc_clicked(self, index):
|
||||
|
File diff suppressed because it is too large
Load Diff
@ -50,7 +50,8 @@ recipe_modules = ['recipe_' + r for r in (
|
||||
'marca', 'kellog_faculty', 'kellog_insight',
|
||||
'theeconomictimes_india', '7dias', 'buenosaireseconomico',
|
||||
'diagonales', 'miradasalsur', 'newsweek_argentina', 'veintitres',
|
||||
'gva_be', 'hln', 'tijd', 'degentenaar',
|
||||
'gva_be', 'hln', 'tijd', 'degentenaar', 'inquirer_net', 'uncrate',
|
||||
'fastcompany', 'accountancyage',
|
||||
)]
|
||||
|
||||
import re, imp, inspect, time, os
|
||||
|
58
src/calibre/web/feeds/recipes/recipe_accountancyage.py
Normal file
58
src/calibre/web/feeds/recipes/recipe_accountancyage.py
Normal file
@ -0,0 +1,58 @@
|
||||
#!/usr/bin/env python
|
||||
|
||||
__license__ = 'GPL v3'
|
||||
__copyright__ = '2008-2009, Darko Miletic <darko.miletic at gmail.com>'
|
||||
'''
|
||||
www.accountancyage.com
|
||||
'''
|
||||
|
||||
from calibre.web.feeds.news import BasicNewsRecipe
|
||||
from calibre.ebooks.BeautifulSoup import Tag
|
||||
|
||||
class AccountancyAge(BasicNewsRecipe):
|
||||
title = 'Accountancy Age'
|
||||
__author__ = 'Darko Miletic'
|
||||
description = 'business news'
|
||||
publisher = 'accountancyage.com'
|
||||
category = 'news, politics, finances'
|
||||
oldest_article = 2
|
||||
max_articles_per_feed = 100
|
||||
no_stylesheets = True
|
||||
use_embedded_content = False
|
||||
simultaneous_downloads = 1
|
||||
encoding = 'utf-8'
|
||||
lang = 'en'
|
||||
language = _('English')
|
||||
|
||||
html2lrf_options = [
|
||||
'--comment', description
|
||||
, '--category', category
|
||||
, '--publisher', publisher
|
||||
]
|
||||
|
||||
html2epub_options = 'publisher="' + publisher + '"\ncomments="' + description + '"\ntags="' + category + '"'
|
||||
|
||||
keep_only_tags = [dict(name='div', attrs={'class':'bodycol'})]
|
||||
remove_tags = [dict(name=['embed','object'])]
|
||||
remove_tags_after = dict(name='div', attrs={'id':'permalink'})
|
||||
remove_tags_before = dict(name='div', attrs={'class':'gap6'})
|
||||
|
||||
feeds = [(u'All News', u'http://feeds.accountancyage.com/rss/latest/accountancyage/all')]
|
||||
|
||||
def print_version(self, url):
|
||||
rest, sep, miss = url.rpartition('/')
|
||||
rr, ssep, artid = rest.rpartition('/')
|
||||
return u'http://www.accountancyage.com/articles/print/' + artid
|
||||
|
||||
def get_article_url(self, article):
|
||||
return article.get('guid', None)
|
||||
|
||||
def preprocess_html(self, soup):
|
||||
soup.html['xml:lang'] = self.lang
|
||||
soup.html['lang'] = self.lang
|
||||
mlang = Tag(soup,'meta',[("http-equiv","Content-Language"),("content",self.lang)])
|
||||
mcharset = Tag(soup,'meta',[("http-equiv","Content-Type"),("content","text/html; charset=UTF-8")])
|
||||
soup.head.insert(0,mlang)
|
||||
soup.head.insert(1,mcharset)
|
||||
return self.adeify_images(soup)
|
||||
|
54
src/calibre/web/feeds/recipes/recipe_fastcompany.py
Normal file
54
src/calibre/web/feeds/recipes/recipe_fastcompany.py
Normal file
@ -0,0 +1,54 @@
|
||||
#!/usr/bin/env python
|
||||
|
||||
__license__ = 'GPL v3'
|
||||
__copyright__ = '2008-2009, Darko Miletic <darko.miletic at gmail.com>'
|
||||
'''
|
||||
www.fastcompany.com
|
||||
'''
|
||||
|
||||
from calibre.web.feeds.news import BasicNewsRecipe
|
||||
from calibre.ebooks.BeautifulSoup import Tag
|
||||
|
||||
class FastCompany(BasicNewsRecipe):
|
||||
title = 'Fast Company'
|
||||
__author__ = 'Darko Miletic'
|
||||
description = 'Where ideas and people meet'
|
||||
publisher = 'fastcompany.com'
|
||||
category = 'news, technology, gadgets, games'
|
||||
oldest_article = 15
|
||||
max_articles_per_feed = 100
|
||||
no_stylesheets = True
|
||||
use_embedded_content = True
|
||||
simultaneous_downloads = 1
|
||||
encoding = 'utf-8'
|
||||
lang = 'en'
|
||||
language = _('English')
|
||||
|
||||
html2lrf_options = [
|
||||
'--comment', description
|
||||
, '--category', category
|
||||
, '--publisher', publisher
|
||||
]
|
||||
|
||||
html2epub_options = 'publisher="' + publisher + '"\ncomments="' + description + '"\ntags="' + category + '"'
|
||||
|
||||
remove_tags = [dict(name=['embed','object']), dict(name='div',attrs={'class':'feedflare'})]
|
||||
|
||||
feeds = [(u'All News', u'http://feeds.feedburner.com/fastcompany/headlines')]
|
||||
|
||||
def get_article_url(self, article):
|
||||
return article.get('guid', None)
|
||||
|
||||
def preprocess_html(self, soup):
|
||||
soup.html['xml:lang'] = self.lang
|
||||
soup.html['lang'] = self.lang
|
||||
mlang = Tag(soup,'meta',[("http-equiv","Content-Language"),("content",self.lang)])
|
||||
mcharset = Tag(soup,'meta',[("http-equiv","Content-Type"),("content","text/html; charset=UTF-8")])
|
||||
soup.head.insert(0,mlang)
|
||||
soup.head.insert(1,mcharset)
|
||||
for item in soup.findAll('a'):
|
||||
sp = item['href'].find('http://feedads.g.doubleclick.net/')
|
||||
if sp != -1:
|
||||
item.extract()
|
||||
return self.adeify_images(soup)
|
||||
|
61
src/calibre/web/feeds/recipes/recipe_inquirer_net.py
Normal file
61
src/calibre/web/feeds/recipes/recipe_inquirer_net.py
Normal file
@ -0,0 +1,61 @@
|
||||
#!/usr/bin/env python
|
||||
|
||||
__license__ = 'GPL v3'
|
||||
__copyright__ = '2009, Darko Miletic <darko.miletic at gmail.com>'
|
||||
'''
|
||||
www.inquirer.net
|
||||
'''
|
||||
|
||||
from calibre.web.feeds.recipes import BasicNewsRecipe
|
||||
from calibre.ebooks.BeautifulSoup import Tag
|
||||
|
||||
class InquirerNet(BasicNewsRecipe):
|
||||
title = 'Inquirer.net'
|
||||
__author__ = 'Darko Miletic'
|
||||
description = 'News from Philipines'
|
||||
oldest_article = 2
|
||||
max_articles_per_feed = 100
|
||||
no_stylesheets = True
|
||||
use_embedded_content = False
|
||||
encoding = 'cp1252'
|
||||
publisher = 'inquirer.net'
|
||||
category = 'news, politics, philipines'
|
||||
lang = 'en'
|
||||
language = _('English')
|
||||
extra_css = ' .fontheadline{font-size: x-large} .fontsubheadline{font-size: large} .fontkick{font-size: medium}'
|
||||
|
||||
html2lrf_options = [
|
||||
'--comment', description
|
||||
, '--category', category
|
||||
, '--publisher', publisher
|
||||
, '--ignore-tables'
|
||||
]
|
||||
|
||||
html2epub_options = 'publisher="' + publisher + '"\ncomments="' + description + '"\ntags="' + category + '"\nlinearize_tables=True'
|
||||
|
||||
remove_tags = [dict(name=['object','link','script','iframe','form'])]
|
||||
|
||||
feeds = [
|
||||
(u'Breaking news', u'http://services.inquirer.net/rss/breakingnews.xml' )
|
||||
,(u'Top stories' , u'http://services.inquirer.net/rss/topstories.xml' )
|
||||
,(u'Sports' , u'http://services.inquirer.net/rss/brk_breakingnews.xml' )
|
||||
,(u'InfoTech' , u'http://services.inquirer.net/rss/infotech_tech.xml' )
|
||||
,(u'InfoTech' , u'http://services.inquirer.net/rss/infotech_tech.xml' )
|
||||
,(u'Business' , u'http://services.inquirer.net/rss/inq7money_breaking_news.xml' )
|
||||
,(u'Editorial' , u'http://services.inquirer.net/rss/opinion_editorial.xml' )
|
||||
,(u'Global Nation', u'http://services.inquirer.net/rss/globalnation_breakingnews.xml')
|
||||
]
|
||||
|
||||
def preprocess_html(self, soup):
|
||||
mlang = Tag(soup,'meta',[("http-equiv","Content-Language"),("content",self.lang)])
|
||||
mcharset = Tag(soup,'meta',[("http-equiv","Content-Type"),("content","text/html; charset=utf-8")])
|
||||
soup.head.insert(0,mlang)
|
||||
soup.head.insert(1,mcharset)
|
||||
for item in soup.findAll(style=True):
|
||||
del item['style']
|
||||
return soup
|
||||
|
||||
def print_version(self, url):
|
||||
rest, sep, art = url.rpartition('/view/')
|
||||
art_id, sp, rrest = art.partition('/')
|
||||
return 'http://services.inquirer.net/print/print.php?article_id=' + art_id
|
@ -1,95 +1,99 @@
|
||||
#!/usr/bin/env python
|
||||
|
||||
__license__ = 'GPL v3'
|
||||
__copyright__ = '2008, Darko Miletic <darko.miletic at gmail.com>'
|
||||
'''
|
||||
time.com
|
||||
'''
|
||||
|
||||
import re
|
||||
from calibre.web.feeds.news import BasicNewsRecipe
|
||||
|
||||
class Time(BasicNewsRecipe):
|
||||
title = u'Time'
|
||||
__author__ = 'Kovid Goyal'
|
||||
description = 'Weekly magazine'
|
||||
encoding = 'utf-8'
|
||||
no_stylesheets = True
|
||||
language = _('English')
|
||||
|
||||
remove_tags_before = dict(id="artHd")
|
||||
remove_tags_after = {'class':"ltCol"}
|
||||
remove_tags = [
|
||||
{'class':['articleTools', 'enlarge', 'search']},
|
||||
{'id':['quigoArticle', 'contentTools', 'articleSideBar', 'header', 'navTop']},
|
||||
{'target':'_blank'},
|
||||
]
|
||||
recursions = 1
|
||||
match_regexps = [r'/[0-9,]+-(2|3|4|5|6|7|8|9)(,\d+){0,1}.html']
|
||||
|
||||
|
||||
def parse_index(self):
|
||||
soup = self.index_to_soup('http://www.time.com/time/magazine')
|
||||
img = soup.find('a', title="View Large Cover", href=True)
|
||||
if img is not None:
|
||||
cover_url = 'http://www.time.com'+img['href']
|
||||
try:
|
||||
nsoup = self.index_to_soup(cover_url)
|
||||
img = nsoup.find('img', src=re.compile('archive/covers'))
|
||||
if img is not None:
|
||||
self.cover_url = img['src']
|
||||
except:
|
||||
self.log.exception('Failed to fetch cover')
|
||||
|
||||
|
||||
feeds = []
|
||||
parent = soup.find(id='tocGuts')
|
||||
for seched in parent.findAll(attrs={'class':'toc_seched'}):
|
||||
section = self.tag_to_string(seched).capitalize()
|
||||
articles = list(self.find_articles(seched))
|
||||
feeds.append((section, articles))
|
||||
|
||||
return feeds
|
||||
|
||||
def find_articles(self, seched):
|
||||
for a in seched.findNextSiblings('a', href=True, attrs={'class':'toc_hed'}):
|
||||
yield {
|
||||
'title' : self.tag_to_string(a),
|
||||
'url' : 'http://www.time.com'+a['href'],
|
||||
'date' : '',
|
||||
'description' : self.article_description(a)
|
||||
}
|
||||
|
||||
def article_description(self, a):
|
||||
ans = []
|
||||
while True:
|
||||
t = a.nextSibling
|
||||
if t is None:
|
||||
break
|
||||
a = t
|
||||
if getattr(t, 'name', False):
|
||||
if t.get('class', '') == 'toc_parens' or t.name == 'br':
|
||||
continue
|
||||
if t.name in ('div', 'a'):
|
||||
break
|
||||
ans.append(self.tag_to_string(t))
|
||||
else:
|
||||
ans.append(unicode(t))
|
||||
return u' '.join(ans).replace(u'\xa0', u'').strip()
|
||||
|
||||
def postprocess_html(self, soup, first_page):
|
||||
div = soup.find(attrs={'class':'artPag'})
|
||||
if div is not None:
|
||||
div.extract()
|
||||
if not first_page:
|
||||
for cls in ('photoBkt', 'artHd'):
|
||||
div = soup.find(attrs={'class':cls})
|
||||
if div is not None:
|
||||
div.extract()
|
||||
div = soup.find(attrs={'class':'artTxt'})
|
||||
if div is not None:
|
||||
p = div.find('p')
|
||||
if p is not None:
|
||||
p.extract()
|
||||
|
||||
return soup
|
||||
#!/usr/bin/env python
|
||||
|
||||
__license__ = 'GPL v3'
|
||||
__copyright__ = '2008, Kovid Goyal <kovid@kovidgoyal.net>'
|
||||
'''
|
||||
time.com
|
||||
'''
|
||||
|
||||
import re
|
||||
from calibre.web.feeds.news import BasicNewsRecipe
|
||||
|
||||
class Time(BasicNewsRecipe):
|
||||
title = u'Time'
|
||||
__author__ = 'Kovid Goyal and Sujata Raman'
|
||||
description = 'Weekly magazine'
|
||||
encoding = 'utf-8'
|
||||
no_stylesheets = True
|
||||
language = _('English')
|
||||
extra_css = '''.headline {font-size: large;}
|
||||
.fact { padding-top: 10pt }
|
||||
h1 {font-family:Arial,Sans-serif}
|
||||
.byline{font-family:Arial,Sans-serif; font-size:xx-small ;color:blue}
|
||||
.timestamp{font-family:Arial,Sans-serif; font-size:x-small ;color:gray}'''
|
||||
remove_tags_before = dict(id="artHd")
|
||||
remove_tags_after = {'class':"ltCol"}
|
||||
remove_tags = [
|
||||
{'class':['articleTools', 'enlarge', 'search','socialtools','blogtools','moretools','page','nextUp','next','subnav','RSS','line2','first','ybuzz','articlePagination','chiclets','imgcont','createListLink','rlinks','tabsWrap','pagination']},
|
||||
{'id':['quigoArticle', 'contentTools', 'articleSideBar', 'header', 'navTop','articleTools','feedmodule','feedmodule3','promos','footer','linksFooter','timeArchive','belt','relatedStories','packages','Features']},
|
||||
{'target':'_blank'},
|
||||
]
|
||||
recursions = 1
|
||||
match_regexps = [r'/[0-9,]+-(2|3|4|5|6|7|8|9)(,\d+){0,1}.html']
|
||||
|
||||
|
||||
def parse_index(self):
|
||||
soup = self.index_to_soup('http://www.time.com/time/magazine')
|
||||
img = soup.find('a', title="View Large Cover", href=True)
|
||||
if img is not None:
|
||||
cover_url = 'http://www.time.com'+img['href']
|
||||
try:
|
||||
nsoup = self.index_to_soup(cover_url)
|
||||
img = nsoup.find('img', src=re.compile('archive/covers'))
|
||||
if img is not None:
|
||||
self.cover_url = img['src']
|
||||
except:
|
||||
self.log.exception('Failed to fetch cover')
|
||||
|
||||
|
||||
feeds = []
|
||||
parent = soup.find(id='tocGuts')
|
||||
for seched in parent.findAll(attrs={'class':'toc_seched'}):
|
||||
section = self.tag_to_string(seched).capitalize()
|
||||
articles = list(self.find_articles(seched))
|
||||
feeds.append((section, articles))
|
||||
|
||||
return feeds
|
||||
|
||||
def find_articles(self, seched):
|
||||
for a in seched.findNextSiblings('a', href=True, attrs={'class':'toc_hed'}):
|
||||
yield {
|
||||
'title' : self.tag_to_string(a),
|
||||
'url' : 'http://www.time.com'+a['href'],
|
||||
'date' : '',
|
||||
'description' : self.article_description(a)
|
||||
}
|
||||
|
||||
def article_description(self, a):
|
||||
ans = []
|
||||
while True:
|
||||
t = a.nextSibling
|
||||
if t is None:
|
||||
break
|
||||
a = t
|
||||
if getattr(t, 'name', False):
|
||||
if t.get('class', '') == 'toc_parens' or t.name == 'br':
|
||||
continue
|
||||
if t.name in ('div', 'a'):
|
||||
break
|
||||
ans.append(self.tag_to_string(t))
|
||||
else:
|
||||
ans.append(unicode(t))
|
||||
return u' '.join(ans).replace(u'\xa0', u'').strip()
|
||||
|
||||
def postprocess_html(self, soup, first_page):
|
||||
div = soup.find(attrs={'class':'artPag'})
|
||||
if div is not None:
|
||||
div.extract()
|
||||
if not first_page:
|
||||
for cls in ('photoBkt', 'artHd'):
|
||||
div = soup.find(attrs={'class':cls})
|
||||
if div is not None:
|
||||
div.extract()
|
||||
div = soup.find(attrs={'class':'artTxt'})
|
||||
if div is not None:
|
||||
p = div.find('p')
|
||||
if p is not None:
|
||||
p.extract()
|
||||
|
||||
return soup
|
||||
|
47
src/calibre/web/feeds/recipes/recipe_uncrate.py
Normal file
47
src/calibre/web/feeds/recipes/recipe_uncrate.py
Normal file
@ -0,0 +1,47 @@
|
||||
#!/usr/bin/env python
|
||||
|
||||
__license__ = 'GPL v3'
|
||||
__copyright__ = '2009, Darko Miletic <darko.miletic at gmail.com>'
|
||||
'''
|
||||
www.uncrate.com
|
||||
'''
|
||||
|
||||
from calibre.web.feeds.recipes import BasicNewsRecipe
|
||||
from calibre.ebooks.BeautifulSoup import BeautifulSoup, Tag
|
||||
|
||||
class Uncrate(BasicNewsRecipe):
|
||||
title = 'Uncrate'
|
||||
__author__ = 'Darko Miletic'
|
||||
description = 'Uncrate is a web magazine for guys who love stuff. Our team digs up the best gadgets, clothes, cars, DVDs and more. New items are posted daily. Enjoy responsively.'
|
||||
oldest_article = 7
|
||||
max_articles_per_feed = 100
|
||||
no_stylesheets = True
|
||||
use_embedded_content = False
|
||||
encoding = 'utf-8'
|
||||
publisher = 'Zombie corp.'
|
||||
category = 'news, gadgets, clothes, cars, DVDs'
|
||||
lang = 'en-US'
|
||||
language = _('English')
|
||||
|
||||
html2lrf_options = [
|
||||
'--comment', description
|
||||
, '--category', category
|
||||
, '--publisher', publisher
|
||||
]
|
||||
|
||||
html2epub_options = 'publisher="' + publisher + '"\ncomments="' + description + '"\ntags="' + category + '"'
|
||||
|
||||
keep_only_tags = [dict(name='div', attrs={'class':'lefttext'})]
|
||||
remove_tags_after = dict(name='div', attrs={'class':'serif'})
|
||||
remove_tags = [dict(name=['object','link','script','iframe','form'])]
|
||||
|
||||
feeds = [(u'Articles', u'http://feeds.feedburner.com/uncrate')]
|
||||
|
||||
def preprocess_html(self, soup):
|
||||
mlang = Tag(soup,'meta',[("http-equiv","Content-Language"),("content",self.lang)])
|
||||
mcharset = Tag(soup,'meta',[("http-equiv","Content-Type"),("content","text/html; charset=utf-8")])
|
||||
soup.head.insert(0,mlang)
|
||||
soup.head.insert(1,mcharset)
|
||||
for item in soup.findAll(style=True):
|
||||
del item['style']
|
||||
return self.adeify_images(soup)
|
Loading…
x
Reference in New Issue
Block a user