KG 0.7.7 update

This commit is contained in:
GRiker 2010-07-03 04:11:23 -06:00
commit c481203b4c
46 changed files with 17274 additions and 13042 deletions

View File

@ -4,6 +4,61 @@
# for important features/bug fixes. # for important features/bug fixes.
# Also, each release can have new and improved recipes. # Also, each release can have new and improved recipes.
- version: 0.7.7
date: 2010-07-02
new features:
- title: "Support for the Nokia E52"
- title: "Searching on the size column"
- title: "iTunes driver: Add option to disable cover fetching for speeding up the fetching of large book collections"
bug fixes:
- title: "SONY driver: Only update metadata when books are sent to device."
- title: "TXT Input: Ensure the generated html is splittable"
tickets: [5904]
- title: "Fix infinite loop in default cover generation."
tickets: [6061]
- title: "HTML Input: Fix a parsing bug that was triggered in rare conditions"
tickets: [6064]
- title: "HTML2Zip plugin: Do not replace ligatures"
tickets: [6019]
- title: "iTunes driver: Fix transmission of non integral series numbers"
tickets: [6046]
- title: "Simplify implementation of cover caching and ensure cover browser is updated when covers are changed"
- title: "PDF metadata: Fix last character corrupted when setting metadata in encrypted files."
- title: "PDF metadata: Update the version of PoDoFo used to set metadata to 0.8.1. Hopefully that means more PDF files will work"
- title: "Device drivers: Speedup for dumping metadata cache to devices on Windows XP"
- title: "EPUB Output: Ensure that language setting is conformant to the specs"
- title: "MOBI Output: Fix a memory leak and a crash in the palmdoc compression routine"
- title: "Metadata download: Fix a regressiont at resulted in a failed download for some books"
new recipes:
- title: "Foreign Policy and Alo!"
author: Darko Miletic
- title: Statesman and ifzm
author: rty
improved recipes:
- Akter
- The Old New Thing
- version: 0.7.6 - version: 0.7.6
date: 2010-06-28 date: 2010-06-28

View File

@ -7,7 +7,7 @@ class AdvancedUserRecipe1277228948(BasicNewsRecipe):
__author__ = 'rty' __author__ = 'rty'
__version__ = '1.0' __version__ = '1.0'
language = 'zh_CN' language = 'zh'
pubisher = 'www.chinapressusa.com' pubisher = 'www.chinapressusa.com'
description = 'Overseas Chinese Network Newspaper in the USA' description = 'Overseas Chinese Network Newspaper in the USA'
category = 'News in Chinese, USA' category = 'News in Chinese, USA'

View File

@ -0,0 +1,45 @@
__license__ = 'GPL v3'
__copyright__ = '2010, Darko Miletic <darko.miletic at gmail.com>'
'''
www.foreignpolicy.com
'''
from calibre.web.feeds.news import BasicNewsRecipe
class ForeignPolicy(BasicNewsRecipe):
title = 'Foreign Policy'
__author__ = 'Darko Miletic'
description = 'International News'
publisher = 'Washingtonpost.Newsweek Interactive, LLC'
category = 'news, politics, USA'
oldest_article = 31
max_articles_per_feed = 200
no_stylesheets = True
encoding = 'utf8'
use_embedded_content = False
language = 'en'
remove_empty_feeds = True
extra_css = ' body{font-family: Georgia,"Times New Roman",Times,serif } img{margin-bottom: 0.4em} h1,h2,h3,h4,h5,h6{font-family: Arial,Helvetica,sans-serif} '
conversion_options = {
'comment' : description
, 'tags' : category
, 'publisher' : publisher
, 'language' : language
}
keep_only_tags = [dict(attrs={'id':['art-mast','art-body','auth-bio']})]
remove_tags = [dict(name='iframe'),dict(attrs={'id':['share-box','base-ad']})]
remove_attributes = ['height','width']
feeds = [(u'Articles', u'http://www.foreignpolicy.com/node/feed')]
def print_version(self, url):
return url + '?print=yes&page=full'
def preprocess_html(self, soup):
for item in soup.findAll(style=True):
del item['style']
return soup

View File

@ -0,0 +1,50 @@
from calibre.web.feeds.news import BasicNewsRecipe
class AdvancedUserRecipe1277305250(BasicNewsRecipe):
title = u'infzm - China Southern Weekly'
oldest_article = 14
max_articles_per_feed = 100
feeds = [(u'\u5357\u65b9\u5468\u672b-\u70ed\u70b9\u65b0\u95fb', u'http://www.infzm.com/rss/home/rss2.0.xml'),
(u'\u5357\u65b9\u5468\u672b-\u7ecf\u6d4e\u65b0\u95fb', u'http://www.infzm.com/rss/economic.xml'),
(u'\u5357\u65b9\u5468\u672b-\u6587\u5316\u65b0\u95fb', u'http://www.infzm.com/rss/culture.xml'),
(u'\u5357\u65b9\u5468\u672b-\u751f\u6d3b\u65f6\u5c1a', u'http://www.infzm.com/rss/lifestyle.xml'),
(u'\u5357\u65b9\u5468\u672b-\u89c2\u70b9', u'http://www.infzm.com/rss/opinion.xml')
]
__author__ = 'rty'
__version__ = '1.0'
language = 'zh'
pubisher = 'http://www.infzm.com'
description = 'Chinese Weekly Tabloid'
category = 'News, China'
remove_javascript = True
use_embedded_content = False
no_stylesheets = True
#encoding = 'GB2312'
encoding = 'UTF-8'
conversion_options = {'linearize_tables':True}
masthead_url = 'http://i50.tinypic.com/2qmfb7l.jpg'
extra_css = '''
@font-face { font-family: "DroidFont", serif, sans-serif; src: url(res:///system/fonts/DroidSansFallback.ttf); }\n
body {
margin-right: 8pt;
font-family: 'DroidFont', serif;}
.detailContent {font-family: 'DroidFont', serif, sans-serif}
'''
keep_only_tags = [
dict(name='div', attrs={'id':'detailContent'}),
]
remove_tags = [
dict(name='div', attrs={'id':['detailTools', 'detailSideL', 'pageNum']}),
]
remove_tags_after = [
dict(name='div', attrs={'id':'pageNum'}),
]
def preprocess_html(self, soup):
for item in soup.findAll(color=True):
del item['font']
for item in soup.findAll(style=True):
del item['style']
return soup

View File

@ -0,0 +1,35 @@
from calibre.web.feeds.news import BasicNewsRecipe
class AdvancedUserRecipe1278049615(BasicNewsRecipe):
title = u'Statesman'
pubisher = 'http://www.statesman.com/'
description = 'Austin Texas Daily Newspaper'
category = 'News, Austin, Texas'
__author__ = 'rty'
oldest_article = 3
max_articles_per_feed = 100
feeds = [(u'News', u'http://www.statesman.com/section-rss.do?source=news&includeSubSections=true'),
(u'Business', u'http://www.statesman.com/section-rss.do?source=business&includeSubSections=true'),
(u'Life', u'http://www.statesman.com/section-rss.do?source=life&includesubsection=true'),
(u'Editorial', u'http://www.statesman.com/section-rss.do?source=opinion&includesubsections=true'),
(u'Sports', u'http://www.statesman.com/section-rss.do?source=sports&includeSubSections=true')
]
masthead_url = "http://www.statesman.com/images/cmg-logo.gif"
#temp_files = []
#articles_are_obfuscated = True
remove_javascript = True
use_embedded_content = False
no_stylesheets = True
language = 'en'
encoding = 'utf-8'
conversion_options = {'linearize_tables':True}
remove_tags = [
dict(name='div', attrs={'id':'cxArticleOptions'}),
]
keep_only_tags = [
dict(name='div', attrs={'class':'cxArticleHeader'}),
dict(name='div', attrs={'id':'cxArticleBodyText'}),
]

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.7.6' __version__ = '0.7.7'
__author__ = "Kovid Goyal <kovid@kovidgoyal.net>" __author__ = "Kovid Goyal <kovid@kovidgoyal.net>"
import re import re

View File

@ -30,6 +30,7 @@ every time you add an HTML file to the library.\
with TemporaryDirectory('_plugin_html2zip') as tdir: with TemporaryDirectory('_plugin_html2zip') as tdir:
recs =[('debug_pipeline', tdir, OptionRecommendation.HIGH)] recs =[('debug_pipeline', tdir, OptionRecommendation.HIGH)]
recs.append(['keep_ligatures', True, OptionRecommendation.HIGH])
if self.site_customization and self.site_customization.strip(): if self.site_customization and self.site_customization.strip():
recs.append(['input_encoding', self.site_customization.strip(), recs.append(['input_encoding', self.site_customization.strip(),
OptionRecommendation.HIGH]) OptionRecommendation.HIGH])
@ -81,7 +82,7 @@ class PML2PMLZ(FileTypePlugin):
return of.name return of.name
# Metadata reader plugins {{{
class ComicMetadataReader(MetadataReaderPlugin): class ComicMetadataReader(MetadataReaderPlugin):
name = 'Read comic metadata' name = 'Read comic metadata'
@ -319,7 +320,9 @@ class ZipMetadataReader(MetadataReaderPlugin):
def get_metadata(self, stream, ftype): def get_metadata(self, stream, ftype):
from calibre.ebooks.metadata.zip import get_metadata from calibre.ebooks.metadata.zip import get_metadata
return get_metadata(stream) return get_metadata(stream)
# }}}
# Metadata writer plugins {{{
class EPUBMetadataWriter(MetadataWriterPlugin): class EPUBMetadataWriter(MetadataWriterPlugin):
@ -395,6 +398,7 @@ class TOPAZMetadataWriter(MetadataWriterPlugin):
from calibre.ebooks.metadata.topaz import set_metadata from calibre.ebooks.metadata.topaz import set_metadata
set_metadata(stream, mi) set_metadata(stream, mi)
# }}}
from calibre.ebooks.comic.input import ComicInput from calibre.ebooks.comic.input import ComicInput
from calibre.ebooks.epub.input import EPUBInput from calibre.ebooks.epub.input import EPUBInput
@ -444,7 +448,7 @@ from calibre.devices.kindle.driver import KINDLE, KINDLE2, KINDLE_DX
from calibre.devices.nook.driver import NOOK from calibre.devices.nook.driver import NOOK
from calibre.devices.prs505.driver import PRS505 from calibre.devices.prs505.driver import PRS505
from calibre.devices.android.driver import ANDROID, S60 from calibre.devices.android.driver import ANDROID, S60
from calibre.devices.nokia.driver import N770, N810, E71X from calibre.devices.nokia.driver import N770, N810, E71X, E52
from calibre.devices.eslick.driver import ESLICK, EBK52 from calibre.devices.eslick.driver import ESLICK, EBK52
from calibre.devices.nuut2.driver import NUUT2 from calibre.devices.nuut2.driver import NUUT2
from calibre.devices.iriver.driver import IRIVER_STORY from calibre.devices.iriver.driver import IRIVER_STORY
@ -519,6 +523,7 @@ plugins += [
S60, S60,
N770, N770,
E71X, E71X,
E52,
N810, N810,
COOL_ER, COOL_ER,
ESLICK, ESLICK,

View File

@ -20,7 +20,7 @@ from calibre.utils.config import config_dir
from calibre.utils.date import isoformat, now, parse_date from calibre.utils.date import isoformat, now, parse_date
from calibre.utils.localization import get_lang from calibre.utils.localization import get_lang
from calibre.utils.logging import Log from calibre.utils.logging import Log
from calibre.utils.zipfile import ZipFile, safe_replace from calibre.utils.zipfile import ZipFile
from PIL import Image as PILImage from PIL import Image as PILImage
@ -1727,7 +1727,6 @@ class ITUNES(DriverBase):
return thumb_data return thumb_data
thumb_path = book_path.rpartition('.')[0] + '.jpg' thumb_path = book_path.rpartition('.')[0] + '.jpg'
format = book_path.rpartition('.')[2].lower()
if isosx: if isosx:
title = book.name() title = book.name()
elif iswindows: elif iswindows:

View File

@ -67,3 +67,24 @@ class E71X(USBMS):
VENDOR_NAME = 'NOKIA' VENDOR_NAME = 'NOKIA'
WINDOWS_MAIN_MEM = 'S60' WINDOWS_MAIN_MEM = 'S60'
class E52(USBMS):
name = 'Nokia E52 device interface'
gui_name = 'Nokia E52'
description = _('Communicate with the Nokia E52')
author = 'David Ignjic'
supported_platforms = ['windows', 'linux', 'osx']
VENDOR_ID = [0x421]
PRODUCT_ID = [0x1CD]
BCD = [0x100]
FORMATS = ['mobi', 'prc']
EBOOK_DIR_MAIN = 'eBooks'
SUPPORTS_SUB_DIRS = True
VENDOR_NAME = 'NOKIA'
WINDOWS_MAIN_MEM = 'S60'

View File

@ -350,9 +350,7 @@ class XMLCache(object):
record = lpath_map.get(book.lpath, None) record = lpath_map.get(book.lpath, None)
if record is None: if record is None:
record = self.create_text_record(root, i, book.lpath) record = self.create_text_record(root, i, book.lpath)
date = self.check_timestamp(record, book, path) self.update_text_record(record, book, path, i)
if date is not None:
self.update_text_record(record, book, date, path, i)
# Ensure the collections in the XML database are recorded for # Ensure the collections in the XML database are recorded for
# this book # this book
if book.device_collections is None: if book.device_collections is None:
@ -390,23 +388,35 @@ class XMLCache(object):
debug_print('WARNING: Some elements in the JSON cache were not' debug_print('WARNING: Some elements in the JSON cache were not'
' found in the XML cache') ' found in the XML cache')
records = [x for x in records if x is not None] records = [x for x in records if x is not None]
ids = set() # Ensure each book has an ID.
for rec in records: for rec in records:
id = rec.get('id', None) if rec.get('id', None) is None:
if id is None:
rec.set('id', str(self.max_id(root)+1)) rec.set('id', str(self.max_id(root)+1))
id = rec.get('id', None) ids = [x.get('id', None) for x in records]
ids.add(id) # Given that we set the ids, there shouldn't be any None's. But
# ids cannot contain None, so no reason to check # better to be safe...
if None in ids:
debug_print('WARNING: Some <text> elements do not have ids')
ids = [x for x in ids if x is not None]
playlist = self.get_or_create_playlist(bl_index, category) playlist = self.get_or_create_playlist(bl_index, category)
# Reduce ids to books not already in the playlist # Get the books currently in the playlist. We will need them to be
# sure to put back any books that were manually added.
playlist_ids = []
for item in playlist: for item in playlist:
id_ = item.get('id', None) id_ = item.get('id', None)
if id_ is not None: if id_ is not None:
ids.discard(id_) playlist_ids.append(id_)
# Add the books in ids that were not already in the playlist # Empty the playlist. We do this so that the playlist will have the
for id_ in ids: # order specified by get_collections
for item in list(playlist):
playlist.remove(item)
# Get a list of ids not known by get_collections
extra_ids = [x for x in playlist_ids if x not in ids]
# Rebuild the collection in the order specified by get_collections. Then
# add the ids that get_collections didn't know about.
for id_ in ids + extra_ids:
item = playlist.makeelement( item = playlist.makeelement(
'{%s}item'%self.namespaces[bl_index], '{%s}item'%self.namespaces[bl_index],
nsmap=playlist.nsmap, attrib={'id':id_}) nsmap=playlist.nsmap, attrib={'id':id_})
@ -443,23 +453,15 @@ class XMLCache(object):
root.append(ans) root.append(ans)
return ans return ans
def check_timestamp(self, record, book, path): def update_text_record(self, record, book, path, bl_index):
'''
Checks the timestamp in the Sony DB against the file. If different,
return the file timestamp. Otherwise return None.
'''
timestamp = os.path.getmtime(path)
date = strftime(timestamp)
if date != record.get('date', None):
return date
return None
def update_text_record(self, record, book, date, path, bl_index):
''' '''
Update the Sony database from the book. This is done if the timestamp in Update the Sony database from the book. This is done if the timestamp in
the db differs from the timestamp on the file. the db differs from the timestamp on the file.
''' '''
record.set('date', date) timestamp = os.path.getmtime(path)
date = strftime(timestamp)
if date != record.get('date', None):
record.set('date', date)
record.set('size', str(os.stat(path).st_size)) record.set('size', str(os.stat(path).st_size))
title = book.title if book.title else _('Unknown') title = book.title if book.title else _('Unknown')
record.set('title', title) record.set('title', title)

View File

@ -174,8 +174,8 @@ class CollectionsBookList(BookList):
if lpath not in collections_lpaths[category]: if lpath not in collections_lpaths[category]:
collections_lpaths[category].add(lpath) collections_lpaths[category].add(lpath)
collections[category].append(book) collections[category].append(book)
if attr == 'series': if attr == 'series':
series_categories.add(category) series_categories.add(category)
# Sort collections # Sort collections
for category, books in collections.items(): for category, books in collections.items():
def tgetter(x): def tgetter(x):

View File

@ -0,0 +1,15 @@
#!/usr/bin/env python
# vim:fileencoding=UTF-8:ts=4:sw=4:sta:et:sts=4:ai
__license__ = 'GPL v3'
__copyright__ = '2010, Kovid Goyal <kovid@kovidgoyal.net>'
__docformat__ = 'restructuredtext en'
from calibre.customize import Plugin
class CoverDownload(Plugin):
supported_platforms = ['windows', 'osx', 'linux']
author = 'Kovid Goyal'
type = _('Cover download')

View File

@ -15,7 +15,6 @@ from calibre.utils.config import OptionParser
from calibre.ebooks.metadata.fetch import MetadataSource from calibre.ebooks.metadata.fetch import MetadataSource
from calibre.utils.date import parse_date, utcnow from calibre.utils.date import parse_date, utcnow
DOUBAN_API_KEY = None
NAMESPACES = { NAMESPACES = {
'openSearch':'http://a9.com/-/spec/opensearchrss/1.0/', 'openSearch':'http://a9.com/-/spec/opensearchrss/1.0/',
'atom' : 'http://www.w3.org/2005/Atom', 'atom' : 'http://www.w3.org/2005/Atom',
@ -35,13 +34,15 @@ date = XPath("descendant::db:attribute[@name='pubdate']")
creator = XPath("descendant::db:attribute[@name='author']") creator = XPath("descendant::db:attribute[@name='author']")
tag = XPath("descendant::db:tag") tag = XPath("descendant::db:tag")
CALIBRE_DOUBAN_API_KEY = '0bd1672394eb1ebf2374356abec15c3d'
class DoubanBooks(MetadataSource): class DoubanBooks(MetadataSource):
name = 'Douban Books' name = 'Douban Books'
description = _('Downloads metadata from Douban.com') description = _('Downloads metadata from Douban.com')
supported_platforms = ['windows', 'osx', 'linux'] # Platforms this plugin will run on supported_platforms = ['windows', 'osx', 'linux'] # Platforms this plugin will run on
author = 'Li Fanxi <lifanxi@freemindworld.com>' # The author of this plugin author = 'Li Fanxi <lifanxi@freemindworld.com>' # The author of this plugin
version = (1, 0, 0) # The version number of this plugin version = (1, 0, 1) # The version number of this plugin
def fetch(self): def fetch(self):
try: try:
@ -65,7 +66,7 @@ class Query(object):
type = "search" type = "search"
def __init__(self, title=None, author=None, publisher=None, isbn=None, def __init__(self, title=None, author=None, publisher=None, isbn=None,
max_results=20, start_index=1): max_results=20, start_index=1, api_key=''):
assert not(title is None and author is None and publisher is None and \ assert not(title is None and author is None and publisher is None and \
isbn is None) isbn is None)
assert (int(max_results) < 21) assert (int(max_results) < 21)
@ -89,16 +90,16 @@ class Query(object):
if self.type == "isbn": if self.type == "isbn":
self.url = self.ISBN_URL + q self.url = self.ISBN_URL + q
if DOUBAN_API_KEY is not None: if api_key != '':
self.url = self.url + "?apikey=" + DOUBAN_API_KEY self.url = self.url + "?apikey=" + api_key
else: else:
self.url = self.SEARCH_URL+urlencode({ self.url = self.SEARCH_URL+urlencode({
'q':q, 'q':q,
'max-results':max_results, 'max-results':max_results,
'start-index':start_index, 'start-index':start_index,
}) })
if DOUBAN_API_KEY is not None: if api_key != '':
self.url = self.url + "&apikey=" + DOUBAN_API_KEY self.url = self.url + "&apikey=" + api_key
def __call__(self, browser, verbose): def __call__(self, browser, verbose):
if verbose: if verbose:
@ -177,7 +178,7 @@ class ResultList(list):
d = None d = None
return d return d
def populate(self, entries, browser, verbose=False): def populate(self, entries, browser, verbose=False, api_key=''):
for x in entries: for x in entries:
try: try:
id_url = entry_id(x)[0].text id_url = entry_id(x)[0].text
@ -186,8 +187,8 @@ class ResultList(list):
report(verbose) report(verbose)
mi = MetaInformation(title, self.get_authors(x)) mi = MetaInformation(title, self.get_authors(x))
try: try:
if DOUBAN_API_KEY is not None: if api_key != '':
id_url = id_url + "?apikey=" + DOUBAN_API_KEY id_url = id_url + "?apikey=" + api_key
raw = browser.open(id_url).read() raw = browser.open(id_url).read()
feed = etree.fromstring(raw) feed = etree.fromstring(raw)
x = entry(feed)[0] x = entry(feed)[0]
@ -203,12 +204,16 @@ class ResultList(list):
self.append(mi) self.append(mi)
def search(title=None, author=None, publisher=None, isbn=None, def search(title=None, author=None, publisher=None, isbn=None,
verbose=False, max_results=40): verbose=False, max_results=40, api_key=None):
br = browser() br = browser()
start, entries = 1, [] start, entries = 1, []
if api_key is None:
api_key = CALIBRE_DOUBAN_API_KEY
while start > 0 and len(entries) <= max_results: while start > 0 and len(entries) <= max_results:
new, start = Query(title=title, author=author, publisher=publisher, new, start = Query(title=title, author=author, publisher=publisher,
isbn=isbn, max_results=max_results, start_index=start)(br, verbose) isbn=isbn, max_results=max_results, start_index=start, api_key=api_key)(br, verbose)
if not new: if not new:
break break
entries.extend(new) entries.extend(new)
@ -216,7 +221,7 @@ def search(title=None, author=None, publisher=None, isbn=None,
entries = entries[:max_results] entries = entries[:max_results]
ans = ResultList() ans = ResultList()
ans.populate(entries, br, verbose) ans.populate(entries, br, verbose, api_key)
return ans return ans
def option_parser(): def option_parser():

View File

@ -808,7 +808,8 @@ class Manifest(object):
pat = re.compile(r'&(%s);'%('|'.join(user_entities.keys()))) pat = re.compile(r'&(%s);'%('|'.join(user_entities.keys())))
data = pat.sub(lambda m:user_entities[m.group(1)], data) data = pat.sub(lambda m:user_entities[m.group(1)], data)
parser = etree.XMLParser(no_network=True, huge_tree=True) # Setting huge_tree=True causes crashes in windows with large files
parser = etree.XMLParser(no_network=True)
# Try with more & more drastic measures to parse # Try with more & more drastic measures to parse
def first_pass(data): def first_pass(data):
try: try:
@ -844,7 +845,7 @@ class Manifest(object):
nroot = etree.fromstring('<html></html>') nroot = etree.fromstring('<html></html>')
has_body = False has_body = False
for child in list(data): for child in list(data):
if barename(child.tag) == 'body': if isinstance(child.tag, (unicode, str)) and barename(child.tag) == 'body':
has_body = True has_body = True
break break
parent = nroot parent = nroot

View File

@ -25,10 +25,17 @@ from calibre.ebooks.oeb.base import XHTML, XHTML_NS, CSS_MIME, OEB_STYLES
from calibre.ebooks.oeb.base import XPNSMAP, xpath, urlnormalize from calibre.ebooks.oeb.base import XPNSMAP, xpath, urlnormalize
from calibre.ebooks.oeb.profile import PROFILES from calibre.ebooks.oeb.profile import PROFILES
html_css = open(P('templates/html.css'), 'rb').read() _html_css_stylesheet = None
def html_css_stylesheet():
global _html_css_stylesheet
if _html_css_stylesheet is None:
html_css = open(P('templates/html.css'), 'rb').read()
_html_css_stylesheet = cssutils.parseString(html_css)
_html_css_stylesheet.namespaces['h'] = XHTML_NS
return _html_css_stylesheet
XHTML_CSS_NAMESPACE = '@namespace "%s";\n' % XHTML_NS XHTML_CSS_NAMESPACE = '@namespace "%s";\n' % XHTML_NS
HTML_CSS_STYLESHEET = cssutils.parseString(html_css)
HTML_CSS_STYLESHEET.namespaces['h'] = XHTML_NS
INHERITED = set(['azimuth', 'border-collapse', 'border-spacing', INHERITED = set(['azimuth', 'border-collapse', 'border-spacing',
'caption-side', 'color', 'cursor', 'direction', 'elevation', 'caption-side', 'color', 'cursor', 'direction', 'elevation',
@ -120,7 +127,7 @@ class Stylizer(object):
item = oeb.manifest.hrefs[path] item = oeb.manifest.hrefs[path]
basename = os.path.basename(path) basename = os.path.basename(path)
cssname = os.path.splitext(basename)[0] + '.css' cssname = os.path.splitext(basename)[0] + '.css'
stylesheets = [HTML_CSS_STYLESHEET] stylesheets = [html_css_stylesheet()]
head = xpath(tree, '/h:html/h:head') head = xpath(tree, '/h:html/h:head')
if head: if head:
head = head[0] head = head[0]

View File

@ -63,7 +63,8 @@ class TXTInput(InputFormatPlugin):
raise ValueError('This txt file has malformed markup, it cannot be' raise ValueError('This txt file has malformed markup, it cannot be'
' converted by calibre. See http://daringfireball.net/projects/markdown/syntax') ' converted by calibre. See http://daringfireball.net/projects/markdown/syntax')
else: else:
html = convert_basic(txt) flow_size = getattr(options, 'flow_size', 0)
html = convert_basic(txt, epub_split_size_kb=flow_size)
from calibre.customize.ui import plugin_for_input_format from calibre.customize.ui import plugin_for_input_format
html_input = plugin_for_input_format('html') html_input = plugin_for_input_format('html')

View File

@ -17,14 +17,11 @@ __docformat__ = 'restructuredtext en'
HTML_TEMPLATE = u'<html><head><meta http-equiv="Content-Type" content="text/html; charset=utf-8"/><title>%s</title></head><body>\n%s\n</body></html>' HTML_TEMPLATE = u'<html><head><meta http-equiv="Content-Type" content="text/html; charset=utf-8"/><title>%s</title></head><body>\n%s\n</body></html>'
def convert_basic(txt, title=''): def convert_basic(txt, title='', epub_split_size_kb=0):
lines = []
# Strip whitespace from the beginning and end of the line. Also replace # Strip whitespace from the beginning and end of the line. Also replace
# all line breaks with \n. # all line breaks with \n.
for line in txt.splitlines(): txt = '\n'.join([line.strip() for line in txt.splitlines()])
lines.append(line.strip())
txt = '\n'.join(lines)
# Condense redundant spaces # Condense redundant spaces
txt = re.sub('[ ]{2,}', ' ', txt) txt = re.sub('[ ]{2,}', ' ', txt)
@ -34,6 +31,15 @@ def convert_basic(txt, title=''):
# Remove excessive line breaks. # Remove excessive line breaks.
txt = re.sub('\n{3,}', '\n\n', txt) txt = re.sub('\n{3,}', '\n\n', txt)
#Takes care if there is no point to split
if epub_split_size_kb > 0:
length_byte = len(txt.encode('utf-8'))
#Calculating the average chunk value for easy splitting as EPUB (+2 as a safe margin)
chunk_size = long(length_byte / (int(length_byte / (epub_split_size_kb * 1024) ) + 2 ))
#if there are chunks with a superior size then go and break
if (len(filter(lambda x: len(x.encode('utf-8')) > chunk_size, txt.split('\n\n')))) :
txt = u'\n\n'.join([split_string_separator(line, chunk_size) for line in txt.split('\n\n')])
lines = [] lines = []
# Split into paragraphs based on having a blank line between text. # Split into paragraphs based on having a blank line between text.
for line in txt.split('\n\n'): for line in txt.split('\n\n'):
@ -71,3 +77,10 @@ def opf_writer(path, opf_name, manifest, spine, mi):
with open(os.path.join(path, opf_name), 'wb') as opffile: with open(os.path.join(path, opf_name), 'wb') as opffile:
opf.render(opffile) opf.render(opffile)
def split_string_separator(txt, size) :
if len(txt.encode('utf-8')) > size:
txt = u''.join([re.sub(u'\.(?P<ends>[^.]*)$', u'.\n\n\g<ends>',
txt[i:i+size], 1) for i in
xrange(0, len(txt.encode('utf-8')), size)])
return txt

View File

@ -84,6 +84,7 @@ typedef unsigned short QRgb565;
#define REFLECTION_FACTOR 1.5 #define REFLECTION_FACTOR 1.5
#define MAX(x, y) ((x > y) ? x : y) #define MAX(x, y) ((x > y) ? x : y)
#define MIN(x, y) ((x < y) ? x : y)
#define RGB565_RED_MASK 0xF800 #define RGB565_RED_MASK 0xF800
#define RGB565_GREEN_MASK 0x07E0 #define RGB565_GREEN_MASK 0x07E0
@ -800,7 +801,7 @@ QRect PictureFlowPrivate::renderCenterSlide(const SlideInfo &slide) {
QRect rect(buffer.width()/2 - sw/2, 0, sw, h-1); QRect rect(buffer.width()/2 - sw/2, 0, sw, h-1);
int left = rect.left(); int left = rect.left();
for(int x = 0; x < sh-1; x++) for(int x = 0; x < MIN(h-1, sh-1); x++)
for(int y = 0; y < sw; y++) for(int y = 0; y < sw; y++)
buffer.setPixel(left + y, 1+x, src->pixel(x, y)); buffer.setPixel(left + y, 1+x, src->pixel(x, y));

View File

@ -162,6 +162,9 @@ turned into a collection on the reader. Note that the PRS-500 does not support c
How do I use |app| with my iPad/iPhone/iTouch? How do I use |app| with my iPad/iPhone/iTouch?
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Over the air
^^^^^^^^^^^^^^
The easiest way to browse your |app| collection on your Apple device (iPad/iPhone/iPod) is by using the *free* Stanza app, available from the Apple app store. You need at least Stanza version 3.0. Stanza allows you to access your |app| collection wirelessly, over the air. The easiest way to browse your |app| collection on your Apple device (iPad/iPhone/iPod) is by using the *free* Stanza app, available from the Apple app store. You need at least Stanza version 3.0. Stanza allows you to access your |app| collection wirelessly, over the air.
First perform the following steps in |app| First perform the following steps in |app|
@ -181,13 +184,13 @@ Replace ``192.168.1.2`` with the local IP address of the computer running |app|.
If you get timeout errors while browsing the calibre catalog in Stanza, try increasing the connection timeout value in the stanza settings. Go to Info->Settings and increase the value of Download Timeout. If you get timeout errors while browsing the calibre catalog in Stanza, try increasing the connection timeout value in the stanza settings. Go to Info->Settings and increase the value of Download Timeout.
Alternative for the iPad With the USB cable
^^^^^^^^^^^^^^^^^^^^^^^^^^^ ^^^^^^^^^^^^^^^^^^^^^^^^^^^
As of |app| version 0.7.0, you can plugin your iPad into the computer using its charging cable, and |app| will detect it and show you a list of books on the iPad. You can then use the Send to device button to send books directly to iBooks on the iPad. As of |app| version 0.7.0, you can plug your iDevice into the computer using its charging cable, and |app| will detect it and show you a list of books on the device. You can then use the *Send to device button* to send books directly to iBooks on the device. Note that you must have at least iOS 4 installed on your iPhone/iTouch for this to work.
This method only works on Windows XP and higher and OS X 10.5 and higher. Linux is not supported (iTunes is not available in linux) and OS X 10.4 is not supported. For more details, see This method only works on Windows XP and higher and OS X 10.5 and higher. Linux is not supported (iTunes is not available in linux) and OS X 10.4 is not supported.
`this forum post <http://www.mobileread.com/forums/showpost.php?p=944079&postcount=1>`_. For more details on how this works, see `this forum post <http://www.mobileread.com/forums/showpost.php?p=944079&postcount=1>`_.
How do I use |app| with my Android phone? How do I use |app| with my Android phone?
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

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

@ -65,6 +65,9 @@ class TextLine(object):
self.bottom_margin = bottom_margin self.bottom_margin = bottom_margin
self.font_path = font_path self.font_path = font_path
def __repr__(self):
return u'TextLine:%r:%f'%(self.text, self.font_size)
def alloc_wand(name): def alloc_wand(name):
ans = getattr(p, name)() ans = getattr(p, name)()
if ans < 0: if ans < 0:
@ -120,6 +123,10 @@ def draw_centered_text(img, dw, text, top, margin=10):
tokens = text.split(' ') tokens = text.split(' ')
while tokens: while tokens:
line, tokens = _get_line(img, dw, tokens, img_width-2*margin) line, tokens = _get_line(img, dw, tokens, img_width-2*margin)
if not line:
# Could not fit the first token on the line
line = tokens[:1]
tokens = tokens[1:]
bottom = draw_centered_line(img, dw, ' '.join(line), top) bottom = draw_centered_line(img, dw, ' '.join(line), top)
top = bottom top = bottom
return top return top

View File

@ -8,7 +8,7 @@ import copy
from lxml import html, etree from lxml import html, etree
from lxml.html.builder import HTML, HEAD, TITLE, STYLE, DIV, BODY, \ from lxml.html.builder import HTML, HEAD, TITLE, STYLE, DIV, BODY, \
STRONG, EM, BR, SPAN, A, HR, UL, LI, H2, H3, IMG, P as PT, \ STRONG, BR, SPAN, A, HR, UL, LI, H2, H3, IMG, P as PT, \
TABLE, TD, TR TABLE, TD, TR
from calibre import preferred_encoding, strftime, isbytestring from calibre import preferred_encoding, strftime, isbytestring