diff --git a/imgsrc/trim.svg b/imgsrc/trim.svg
new file mode 100644
index 0000000000..8c8810fc66
--- /dev/null
+++ b/imgsrc/trim.svg
@@ -0,0 +1,688 @@
+
+
+
+
diff --git a/resources/images/trim.png b/resources/images/trim.png
new file mode 100644
index 0000000000..3cb93adfa6
Binary files /dev/null and b/resources/images/trim.png differ
diff --git a/resources/recipes/danas.recipe b/resources/recipes/danas.recipe
index 3543acd684..1e0e319334 100644
--- a/resources/recipes/danas.recipe
+++ b/resources/recipes/danas.recipe
@@ -49,7 +49,11 @@ class Danas(BasicNewsRecipe):
, 'language' : language
}
- preprocess_regexps = [(re.compile(u'\u0110'), lambda match: u'\u00D0')]
+ preprocess_regexps = [
+ (re.compile(u'\u0110'), lambda match: u'\u00D0')
+ ,(re.compile(u'\u201c'), lambda match: '"')
+ ,(re.compile(u'\u201e'), lambda match: '"')
+ ]
keep_only_tags = [dict(name='div', attrs={'id':'left'})]
remove_tags = [
diff --git a/resources/recipes/novosti.recipe b/resources/recipes/novosti.recipe
index 0465b59e17..d66e7d28b7 100644
--- a/resources/recipes/novosti.recipe
+++ b/resources/recipes/novosti.recipe
@@ -21,8 +21,8 @@ class Novosti(BasicNewsRecipe):
encoding = 'utf-8'
language = 'sr'
publication_type = 'newspaper'
- extra_css = """ @font-face {font-family: "sans1";src:url(res:///opt/sony/ebook/FONT/tt0003m_.ttf)}
- .article_description,body{font-family: Arial,Helvetica,sans1,sans-serif}
+ extra_css = """ @font-face {font-family: "sans1";src:url(res:///opt/sony/ebook/FONT/tt0003m_.ttf)}
+ .article_description,body{font-family: Arial,Helvetica,sans1,sans-serif}
.author{font-size: small}
.articleLead{font-size: large; font-weight: bold}
"""
@@ -47,6 +47,8 @@ class Novosti(BasicNewsRecipe):
item.name='p'
for item in soup.findAll('img'):
if not item.has_key('alt'):
- item['alt'] = 'image'
+ item['alt'] = 'image'
return soup
-
\ No newline at end of file
+
+
+
diff --git a/resources/recipes/superesportes.recipe b/resources/recipes/superesportes.recipe
new file mode 100644
index 0000000000..49289f188d
--- /dev/null
+++ b/resources/recipes/superesportes.recipe
@@ -0,0 +1,79 @@
+__license__ = 'GPL v3'
+__copyright__ = '2010, Luciano Furtado '
+'''
+www.superesportes.com.br
+'''
+
+from calibre.web.feeds.news import BasicNewsRecipe
+
+class SuperEsportesRecipe(BasicNewsRecipe):
+
+ title = u'www.superesportes.com.br'
+ description = u'Superesportes - Notícias do esporte no Brasil e no mundo'
+ __author__ = 'Luciano Furtado'
+ language = 'pt'
+ category = 'esportes, Brasil'
+ no_stylesheets = True
+ oldest_article = 7
+
+ use_embedded_content=0
+ max_articles_per_feed = 10
+ cover_url = 'http://imgs.mg.superesportes.com.br/superesportes_logo.png'
+
+ extra_css = 'div.info_noticias h1 { font-size: 100% }'
+
+
+
+ remove_tags = [
+ dict(name='div',attrs={'class':'topo'}),
+ dict(name='div',attrs={'class':'rodape'}),
+ dict(name='div',attrs={'class':'navegacao'}),
+ dict(name='div',attrs={'class':'lateral2'}),
+ dict(name='div',attrs={'class':'leia_mais'}),
+ dict(name='div',attrs={'id':'comentar'}),
+ dict(name='div',attrs={'id':'vrumelc_noticia'}),
+ dict(name='div',attrs={'class':'compartilhe'}),
+ dict(name='div',attrs={'class':'linha_noticias'}),
+ dict(name='div',attrs={'class':'botoes_noticias'}),
+ dict(name='div',attrs={'class':'barra_time bg_time'}),
+ ]
+
+
+
+ def parse_index(self):
+ feeds = []
+ sections = [
+ (u'Atletico', 'http://www.df.superesportes.com.br/futebol/atletico-mg/capa_atletico_mg/index.shtml'),
+ (u'Botafogo', 'http://www.df.superesportes.com.br/futebol/botafogo/capa_botafogo/index.shtml'),
+ (u'Corinthinas', 'http://www.df.superesportes.com.br/futebol/corinthians/capa_corinthians/index.shtml'),
+ (u'Cruzeiro', 'http://www.df.superesportes.com.br/futebol/cruzeiro/capa_cruzeiro/index.shtml'),
+ (u'Flamengo', 'http://www.df.superesportes.com.br/futebol/flamengo/capa_flamengo/index.shtml'),
+ (u'Fluminense', 'http://www.df.superesportes.com.br/futebol/fluminense/capa_fluminense/index.shtml'),
+ (u'Palmeiras', 'http://www.df.superesportes.com.br/futebol/palmeiras/capa_palmeiras/index.shtml'),
+ (u'Santos', 'http://www.df.superesportes.com.br/futebol/santos/capa_santos/index.shtml'),
+ (u'S√£o Paulo', 'http://www.df.superesportes.com.br/futebol/sao-paulo/capa_sao_paulo/index.shtml'),
+ (u'Vasco', 'http://www.df.superesportes.com.br/futebol/vasco/capa_vasco/index.shtml'),
+ ]
+
+
+ for section, url in sections:
+ current_articles = []
+
+ soup = self.index_to_soup(url)
+ latestNews = soup.find(name='ul',attrs={'class': 'lista_ultimas_noticias'})
+
+ for li_tag in latestNews.findAll(name='li'):
+ a_tag = li_tag.find('a', href= True)
+ if a_tag is None:
+ continue
+ title = self.tag_to_string(a_tag)
+ url = a_tag.get('href', False)
+ self.log("\n\nFound title: " + title + "\nUrl: " + url + "\nSection: " + section)
+ current_articles.append({'title': title, 'url': url, 'description': title, 'date':''})
+
+ if current_articles:
+ feeds.append((section, current_articles))
+
+
+ return feeds
+
diff --git a/resources/recipes/tagesan.recipe b/resources/recipes/tagesan.recipe
new file mode 100644
index 0000000000..8514162598
--- /dev/null
+++ b/resources/recipes/tagesan.recipe
@@ -0,0 +1,45 @@
+from calibre.web.feeds.news import BasicNewsRecipe
+
+class AdvancedUserRecipe1284927619(BasicNewsRecipe):
+ title = u'Tagesanzeiger'
+ publisher = u'Tamedia AG'
+ oldest_article = 2
+ __author__ = 'noxxx'
+ max_articles_per_feed = 100
+ description = 'tagesanzeiger.ch: Nichts verpassen'
+ category = 'News, Politik, Nachrichten, Schweiz, Zürich'
+ language = 'de'
+
+ conversion_options = {
+ 'comments' : description
+ ,'tags' : category
+ ,'language' : language
+ ,'publisher' : publisher
+ }
+
+ remove_tags = [
+ dict(name='img')
+ ,dict(name='div',attrs={'class':['swissquote ad','boxNews','centerAD','contentTabs2','sbsLabel']})
+ ,dict(name='div',attrs={'id':['colRightAd','singleRight','singleSmallRight','MailInfo','metaLine','sidebarSky','contentFooter','commentInfo','commentInfo2','commentInfo3','footerBottom','clear','boxExclusiv','singleLogo','navSearch','headerLogin','headerBottomRight','horizontalNavigation','subnavigation','googleAdSense','footerAd','contentbox','articleGalleryNav']})
+ ,dict(name='form',attrs={'id':['articleMailForm','commentform']})
+ ,dict(name='div',attrs={'style':['position:absolute']})
+ ,dict(name='script',attrs={'type':['text/javascript']})
+ ,dict(name='p',attrs={'class':['schreiben','smallPrint','charCounter','caption']})
+ ]
+ feeds = [
+ (u'Front', u'http://www.tagesanzeiger.ch/rss.html')
+ ,(u'Zürich', u'http://www.tagesanzeiger.ch/zuerich/rss.html')
+ ,(u'Schweiz', u'http://www.tagesanzeiger.ch/schweiz/rss.html')
+ ,(u'Ausland', u'http://www.tagesanzeiger.ch/ausland/rss.html')
+ ,(u'Digital', u'http://www.tagesanzeiger.ch/digital/rss.html')
+ ,(u'Wissen', u'http://www.tagesanzeiger.ch/wissen/rss.html')
+ ,(u'Panorama', u'http://www.tagesanzeiger.ch/panorama/rss.html')
+ ,(u'Wirtschaft', u'http://www.tagesanzeiger.ch/wirtschaft/rss.html')
+ ,(u'Sport', u'http://www.tagesanzeiger.ch/sport/rss.html')
+ ,(u'Kultur', u'http://www.tagesanzeiger.ch/kultur/rss.html')
+ ,(u'Leben', u'http://www.tagesanzeiger.ch/leben/rss.html')
+ ,(u'Auto', u'http://www.tagesanzeiger.ch/auto/rss.html')]
+
+ def print_version(self, url):
+ return url + '/print.html'
+
diff --git a/resources/recipes/the_marker.recipe b/resources/recipes/the_marker.recipe
new file mode 100644
index 0000000000..e5f1ffc761
--- /dev/null
+++ b/resources/recipes/the_marker.recipe
@@ -0,0 +1,52 @@
+import re
+from calibre.web.feeds.news import BasicNewsRecipe
+
+class AdvancedUserRecipe1283848012(BasicNewsRecipe):
+ description = 'TheMarker Financial News in Hebrew'
+ __author__ = 'TonyTheBookworm, Marbs'
+ cover_url = 'http://static.ispot.co.il/wp-content/upload/2009/09/themarker.jpg'
+ title = u'TheMarker'
+ language = 'he'
+ simultaneous_downloads = 5
+ remove_javascript = True
+ timefmt = '[%a, %d %b, %Y]'
+ oldest_article = 1
+ remove_tags = [dict(name='tr', attrs={'bgcolor':['#738A94']}) ]
+ max_articles_per_feed = 10
+ extra_css='body{direction: rtl;} .article_description{direction: rtl; } a.article{direction: rtl; } .calibre_feed_description{direction: rtl; }'
+ feeds = [(u'Head Lines', u'http://www.themarker.com/tmc/content/xml/rss/hpfeed.xml'),
+ (u'TA Market', u'http://www.themarker.com/tmc/content/xml/rss/sections/marketfeed.xml'),
+ (u'Real Estate', u'http://www.themarker.com/tmc/content/xml/rss/sections/realEstaterfeed.xml'),
+ (u'Wall Street & Global', u'http://www.themarker.com/tmc/content/xml/rss/sections/wallsfeed.xml'),
+ (u'Law', u'http://www.themarker.com/tmc/content/xml/rss/sections/lawfeed.xml'),
+ (u'Media', u'http://www.themarker.com/tmc/content/xml/rss/sections/mediafeed.xml'),
+ (u'Consumer', u'http://www.themarker.com/tmc/content/xml/rss/sections/consumerfeed.xml'),
+ (u'Career', u'http://www.themarker.com/tmc/content/xml/rss/sections/careerfeed.xml'),
+ (u'Car', u'http://www.themarker.com/tmc/content/xml/rss/sections/carfeed.xml'),
+ (u'High Tech', u'http://www.themarker.com/tmc/content/xml/rss/sections/hightechfeed.xml'),
+ (u'Investor Guide', u'http://www.themarker.com/tmc/content/xml/rss/sections/investorGuidefeed.xml')]
+
+ def print_version(self, url):
+ split1 = url.split("=")
+ weblinks = url
+
+ if weblinks is not None:
+ for link in weblinks:
+ #---------------------------------------------------------
+ #here we need some help with some regexpressions
+ #we are trying to find it.themarker.com in a url
+ #-----------------------------------------------------------
+ re1='.*?' # Non-greedy match on filler
+ re2='(it\\.themarker\\.com)' # Fully Qualified Domain Name 1
+ rg = re.compile(re1+re2,re.IGNORECASE|re.DOTALL)
+ m = rg.search(url)
+
+
+ if m:
+ split2 = url.split("article/")
+ print_url = 'http://it.themarker.com/tmit/PrintArticle/' + split2[1]
+
+ else:
+ print_url = 'http://www.themarker.com/ibo/misc/printFriendly.jhtml?ElementId=%2Fibo%2Frepositories%2Fstories%2Fm1_2000%2F' + split1[1]+'.xml'
+
+ return print_url
diff --git a/resources/recipes/wsj.recipe b/resources/recipes/wsj.recipe
index fd5e977d10..88e07bcea3 100644
--- a/resources/recipes/wsj.recipe
+++ b/resources/recipes/wsj.recipe
@@ -70,13 +70,16 @@ class WallStreetJournal(BasicNewsRecipe):
def wsj_add_feed(self,feeds,title,url):
self.log('Found section:', title)
- if url.endswith('whatsnews'):
- articles = self.wsj_find_wn_articles(url)
- else:
- articles = self.wsj_find_articles(url)
+ try:
+ if url.endswith('whatsnews'):
+ articles = self.wsj_find_wn_articles(url)
+ else:
+ articles = self.wsj_find_articles(url)
+ except:
+ articles = []
if articles:
feeds.append((title, articles))
- return feeds
+ return feeds
def parse_index(self):
soup = self.wsj_get_index()
@@ -99,7 +102,7 @@ class WallStreetJournal(BasicNewsRecipe):
url = 'http://online.wsj.com' + a['href']
feeds = self.wsj_add_feed(feeds,title,url)
title = 'What''s News'
- url = url.replace('pageone','whatsnews')
+ url = url.replace('pageone','whatsnews')
feeds = self.wsj_add_feed(feeds,title,url)
else:
title = self.tag_to_string(a)
@@ -141,7 +144,7 @@ class WallStreetJournal(BasicNewsRecipe):
articles = []
flavorarea = soup.find('div', attrs={'class':lambda x: x and 'ahed' in x})
- if flavorarea is not None:
+ if flavorarea is not None:
flavorstory = flavorarea.find('a', href=lambda x: x and x.startswith('/article'))
if flavorstory is not None:
flavorstory['class'] = 'mjLinkItem'
diff --git a/resources/recipes/wsj_free.recipe b/resources/recipes/wsj_free.recipe
index 7f3664f1c4..df8234e8e2 100644
--- a/resources/recipes/wsj_free.recipe
+++ b/resources/recipes/wsj_free.recipe
@@ -54,10 +54,13 @@ class WallStreetJournal(BasicNewsRecipe):
def wsj_add_feed(self,feeds,title,url):
self.log('Found section:', title)
- if url.endswith('whatsnews'):
- articles = self.wsj_find_wn_articles(url)
- else:
- articles = self.wsj_find_articles(url)
+ try:
+ if url.endswith('whatsnews'):
+ articles = self.wsj_find_wn_articles(url)
+ else:
+ articles = self.wsj_find_articles(url)
+ except:
+ articles = []
if articles:
feeds.append((title, articles))
return feeds
diff --git a/src/calibre/customize/builtins.py b/src/calibre/customize/builtins.py
index 68df832048..ec9f7e2bc2 100644
--- a/src/calibre/customize/builtins.py
+++ b/src/calibre/customize/builtins.py
@@ -666,13 +666,17 @@ class ActionCopyToLibrary(InterfaceActionBase):
name = 'Copy To Library'
actual_plugin = 'calibre.gui2.actions.copy_to_library:CopyToLibraryAction'
+class ActionTweakEpub(InterfaceActionBase):
+ name = 'Tweak ePub'
+ actual_plugin = 'calibre.gui2.actions.tweak_epub:TweakEpubAction'
+
plugins += [ActionAdd, ActionFetchAnnotations, ActionGenerateCatalog,
ActionConvert, ActionDelete, ActionEditMetadata, ActionView,
ActionFetchNews, ActionSaveToDisk, ActionShowBookDetails,
ActionRestart, ActionOpenFolder, ActionConnectShare,
ActionSendToDevice, ActionHelp, ActionPreferences, ActionSimilarBooks,
ActionAddToLibrary, ActionEditCollections, ActionChooseLibrary,
- ActionCopyToLibrary]
+ ActionCopyToLibrary, ActionTweakEpub]
# }}}
diff --git a/src/calibre/devices/android/driver.py b/src/calibre/devices/android/driver.py
index 7a451112c0..c9c0827759 100644
--- a/src/calibre/devices/android/driver.py
+++ b/src/calibre/devices/android/driver.py
@@ -29,7 +29,9 @@ class ANDROID(USBMS):
# Sony Ericsson
0xfce : { 0xd12e : [0x0100]},
- 0x18d1 : { 0x4e11 : [0x0100, 0x226], 0x4e12: [0x0100, 0x226]},
+ # Google
+ 0x18d1 : { 0x4e11 : [0x0100, 0x226, 0x227], 0x4e12: [0x0100, 0x226,
+ 0x227]},
# Samsung
0x04e8 : { 0x681d : [0x0222, 0x0400],
diff --git a/src/calibre/devices/apple/driver.py b/src/calibre/devices/apple/driver.py
index 5fe36faf75..0946bd2f51 100644
--- a/src/calibre/devices/apple/driver.py
+++ b/src/calibre/devices/apple/driver.py
@@ -739,7 +739,7 @@ class ITUNES(DriverBase):
# Purge the booklist, self.cached_books, thumb cache
for i,bl_book in enumerate(booklists[0]):
- if DEBUG:
+ if False:
self.log.info(" evaluating '%s' by '%s' uuid:%s" %
(bl_book.title, bl_book.author,bl_book.uuid))
diff --git a/src/calibre/devices/hanvon/driver.py b/src/calibre/devices/hanvon/driver.py
index 75728a94ea..6291864b86 100644
--- a/src/calibre/devices/hanvon/driver.py
+++ b/src/calibre/devices/hanvon/driver.py
@@ -11,6 +11,10 @@ import re
from calibre.devices.usbms.driver import USBMS
+def is_alex(device_info):
+ return device_info[3] == u'Linux 2.6.28 with pxa3xx_u2d' and \
+ device_info[4] == u'Seleucia Disk'
+
class N516(USBMS):
name = 'N516 driver'
@@ -34,6 +38,9 @@ class N516(USBMS):
EBOOK_DIR_MAIN = 'e_book'
SUPPORTS_SUB_DIRS = True
+ def can_handle(self, device_info, debug=False):
+ return not is_alex(device_info)
+
class THEBOOK(N516):
name = 'The Book driver'
gui_name = 'The Book'
@@ -61,6 +68,9 @@ class ALEX(N516):
EBOOK_DIR_MAIN = 'eBooks'
SUPPORTS_SUB_DIRS = True
+ def can_handle(self, device_info, debug=False):
+ return is_alex(device_info)
+
class AZBOOKA(ALEX):
name = 'Azbooka driver'
@@ -74,6 +84,9 @@ class AZBOOKA(ALEX):
EBOOK_DIR_MAIN = ''
+ def can_handle(self, device_info, debug=False):
+ return not is_alex(device_info)
+
class EB511(USBMS):
name = 'Elonex EB 511 driver'
diff --git a/src/calibre/devices/kobo/driver.py b/src/calibre/devices/kobo/driver.py
index 762a05d193..104553b675 100644
--- a/src/calibre/devices/kobo/driver.py
+++ b/src/calibre/devices/kobo/driver.py
@@ -98,6 +98,8 @@ class KOBO(USBMS):
if readstatus == 1:
playlist_map[lpath]= "Im_Reading"
+ elif readstatus == 2:
+ playlist_map[lpath]= "Read"
path = self.normalize_path(path)
# print "Normalized FileName: " + path
@@ -441,43 +443,99 @@ class KOBO(USBMS):
connection = sqlite.connect(self._main_prefix + '.kobo/KoboReader.sqlite')
cursor = connection.cursor()
- # Reset Im_Reading list in the database
- if oncard == 'carda':
- query= 'update content set ReadStatus=0, FirstTimeReading = \'true\' where BookID is Null and ContentID like \'file:///mnt/sd/%\''
- elif oncard != 'carda' and oncard != 'cardb':
- query= 'update content set ReadStatus=0, FirstTimeReading = \'true\' where BookID is Null and ContentID not like \'file:///mnt/sd/%\''
+
+ if collections:
+ # Process any collections that exist
+ for category, books in collections.items():
+ if category == 'Im_Reading':
+ # Reset Im_Reading list in the database
+ if oncard == 'carda':
+ query= 'update content set ReadStatus=0, FirstTimeReading = \'true\' where BookID is Null and ReadStatus = 1 and ContentID like \'file:///mnt/sd/%\''
+ elif oncard != 'carda' and oncard != 'cardb':
+ query= 'update content set ReadStatus=0, FirstTimeReading = \'true\' where BookID is Null and ReadStatus = 1 and ContentID not like \'file:///mnt/sd/%\''
- try:
- cursor.execute (query)
- except:
- debug_print('Database Exception: Unable to reset Im_Reading list')
- raise
- else:
-# debug_print('Commit: Reset Im_Reading list')
- connection.commit()
-
- for category, books in collections.items():
- if category == 'Im_Reading':
- for book in books:
-# debug_print('Title:', book.title, 'lpath:', book.path)
- book.device_collections = ['Im_Reading']
-
- extension = os.path.splitext(book.path)[1]
- ContentType = self.get_content_type_from_extension(extension)
-
- ContentID = self.contentid_from_path(book.path, ContentType)
- datelastread = time.strftime("%Y-%m-%dT%H:%M:%S", time.gmtime())
-
- t = (datelastread,ContentID,)
-
try:
- cursor.execute('update content set ReadStatus=1,FirstTimeReading=\'false\',DateLastRead=? where BookID is Null and ContentID = ?', t)
+ cursor.execute (query)
except:
- debug_print('Database Exception: Unable create Im_Reading list')
+ debug_print('Database Exception: Unable to reset Im_Reading list')
raise
else:
+# debug_print('Commit: Reset Im_Reading list')
connection.commit()
- # debug_print('Database: Commit create Im_Reading list')
+
+ for book in books:
+# debug_print('Title:', book.title, 'lpath:', book.path)
+ book.device_collections = ['Im_Reading']
+
+ extension = os.path.splitext(book.path)[1]
+ ContentType = self.get_content_type_from_extension(extension)
+
+ ContentID = self.contentid_from_path(book.path, ContentType)
+ datelastread = time.strftime("%Y-%m-%dT%H:%M:%S", time.gmtime())
+
+ t = (datelastread,ContentID,)
+
+ try:
+ cursor.execute('update content set ReadStatus=1,FirstTimeReading=\'false\',DateLastRead=? where BookID is Null and ContentID = ?', t)
+ except:
+ debug_print('Database Exception: Unable create Im_Reading list')
+ raise
+ else:
+ connection.commit()
+ # debug_print('Database: Commit create Im_Reading list')
+ if category == 'Read':
+ # Reset Im_Reading list in the database
+ if oncard == 'carda':
+ query= 'update content set ReadStatus=0, FirstTimeReading = \'true\' where BookID is Null and ReadStatus = 2 and ContentID like \'file:///mnt/sd/%\''
+ elif oncard != 'carda' and oncard != 'cardb':
+ query= 'update content set ReadStatus=0, FirstTimeReading = \'true\' where BookID is Null and ReadStatus = 2 and ContentID not like \'file:///mnt/sd/%\''
+
+ try:
+ cursor.execute (query)
+ except:
+ debug_print('Database Exception: Unable to reset Im_Reading list')
+ raise
+ else:
+# debug_print('Commit: Reset Im_Reading list')
+ connection.commit()
+
+ for book in books:
+# debug_print('Title:', book.title, 'lpath:', book.path)
+ book.device_collections = ['Read']
+
+ extension = os.path.splitext(book.path)[1]
+ ContentType = self.get_content_type_from_extension(extension)
+
+ ContentID = self.contentid_from_path(book.path, ContentType)
+# datelastread = time.strftime("%Y-%m-%dT%H:%M:%S", time.gmtime())
+
+ t = (ContentID,)
+
+ try:
+ cursor.execute('update content set ReadStatus=2,FirstTimeReading=\'true\' where BookID is Null and ContentID = ?', t)
+ except:
+ debug_print('Database Exception: Unable set book as Rinished')
+ raise
+ else:
+ connection.commit()
+# debug_print('Database: Commit set ReadStatus as Finished')
+ else: # No collections
+ # Since no collections exist the ReadStatus needs to be reset to 0 (Unread)
+ print "Reseting ReadStatus to 0"
+ # Reset Im_Reading list in the database
+ if oncard == 'carda':
+ query= 'update content set ReadStatus=0, FirstTimeReading = \'true\' where BookID is Null and ContentID like \'file:///mnt/sd/%\''
+ elif oncard != 'carda' and oncard != 'cardb':
+ query= 'update content set ReadStatus=0, FirstTimeReading = \'true\' where BookID is Null and ContentID not like \'file:///mnt/sd/%\''
+
+ try:
+ cursor.execute (query)
+ except:
+ debug_print('Database Exception: Unable to reset Im_Reading list')
+ raise
+ else:
+# debug_print('Commit: Reset Im_Reading list')
+ connection.commit()
cursor.close()
connection.close()
diff --git a/src/calibre/ebooks/conversion/plumber.py b/src/calibre/ebooks/conversion/plumber.py
index 3ea2926461..395447edba 100644
--- a/src/calibre/ebooks/conversion/plumber.py
+++ b/src/calibre/ebooks/conversion/plumber.py
@@ -241,7 +241,7 @@ OptionRecommendation(name='toc_filter',
OptionRecommendation(name='chapter',
recommended_value="//*[((name()='h1' or name()='h2') and "
- r"re:test(., 'chapter|book|section|part\s+', 'i')) or @class "
+ r"re:test(., 'chapter|book|section|part|prologue|epilogue\s+', 'i')) or @class "
"= 'chapter']", level=OptionRecommendation.LOW,
help=_('An XPath expression to detect chapter titles. The default '
'is to consider or tags that contain the words '
diff --git a/src/calibre/ebooks/mobi/writer.py b/src/calibre/ebooks/mobi/writer.py
index 5d5de7b153..23f92d1fd2 100644
--- a/src/calibre/ebooks/mobi/writer.py
+++ b/src/calibre/ebooks/mobi/writer.py
@@ -1574,14 +1574,15 @@ class MobiWriter(object):
id = unicode(oeb.metadata.cover[0])
item = oeb.manifest.ids[id]
href = item.href
- index = self._images[href] - 1
- exth.write(pack('>III', 0xc9, 0x0c, index))
- exth.write(pack('>III', 0xcb, 0x0c, 0))
- nrecs += 2
- index = self._add_thumbnail(item)
- if index is not None:
- exth.write(pack('>III', 0xca, 0x0c, index - 1))
- nrecs += 1
+ if href in self._images:
+ index = self._images[href] - 1
+ exth.write(pack('>III', 0xc9, 0x0c, index))
+ exth.write(pack('>III', 0xcb, 0x0c, 0))
+ nrecs += 2
+ index = self._add_thumbnail(item)
+ if index is not None:
+ exth.write(pack('>III', 0xca, 0x0c, index - 1))
+ nrecs += 1
exth = exth.getvalue()
trail = len(exth) % 4
diff --git a/src/calibre/gui2/__init__.py b/src/calibre/gui2/__init__.py
index e58dce5559..c0c7b0a9ed 100644
--- a/src/calibre/gui2/__init__.py
+++ b/src/calibre/gui2/__init__.py
@@ -1,7 +1,7 @@
__license__ = 'GPL v3'
__copyright__ = '2008, Kovid Goyal '
""" The GUI """
-import os, sys
+import os, sys, Queue
from threading import RLock
from PyQt4.Qt import QVariant, QFileInfo, QObject, SIGNAL, QBuffer, Qt, \
@@ -39,7 +39,7 @@ gprefs.defaults['action-layout-context-menu'] = (
'Edit Metadata', 'Send To Device', 'Save To Disk',
'Connect Share', 'Copy To Library', None,
'Convert Books', 'View', 'Open Folder', 'Show Book Details',
- 'Similar Books', None, 'Remove Books',
+ 'Similar Books', 'Tweak ePub', None, 'Remove Books',
)
gprefs.defaults['action-layout-context-menu-device'] = (
@@ -296,6 +296,34 @@ class Dispatcher(QObject):
def dispatch(self, args, kwargs):
self.func(*args, **kwargs)
+class FunctionDispatcher(QObject):
+ '''
+ Convenience class to use Qt signals with arbitrary python functions.
+ By default, ensures that a function call always happens in the
+ thread this Dispatcher was created in.
+ '''
+ dispatch_signal = pyqtSignal(object, object, object)
+
+ def __init__(self, func, queued=True, parent=None):
+ QObject.__init__(self, parent)
+ self.func = func
+ typ = Qt.QueuedConnection
+ if not queued:
+ typ = Qt.AutoConnection if queued is None else Qt.DirectConnection
+ self.dispatch_signal.connect(self.dispatch, type=typ)
+
+ def __call__(self, *args, **kwargs):
+ q = Queue.Queue()
+ self.dispatch_signal.emit(q, args, kwargs)
+ return q.get()
+
+ def dispatch(self, q, args, kwargs):
+ try:
+ res = self.func(*args, **kwargs)
+ except:
+ res = None
+ q.put(res)
+
class GetMetadata(QObject):
'''
Convenience class to ensure that metadata readers are used only in the
@@ -575,18 +603,6 @@ class Application(QApplication):
self._file_open_paths = []
self._file_open_lock = RLock()
- if islinux:
- self.setStyleSheet('''
- QToolTip {
- border: 2px solid black;
- padding: 5px;
- border-radius: 10px;
- opacity: 200;
- background-color: #e1e1ff;
- color: black;
- }
- ''')
-
def _send_file_open_events(self):
with self._file_open_lock:
if self._file_open_paths:
diff --git a/src/calibre/gui2/actions/tweak_epub.py b/src/calibre/gui2/actions/tweak_epub.py
new file mode 100755
index 0000000000..212aff8019
--- /dev/null
+++ b/src/calibre/gui2/actions/tweak_epub.py
@@ -0,0 +1,55 @@
+#!/usr/bin/env python
+# vim:fileencoding=UTF-8:ts=4:sw=4:sta:et:sts=4:ai
+
+__license__ = 'GPL v3'
+__copyright__ = '2010, Kovid Goyal '
+__docformat__ = 'restructuredtext en'
+
+from calibre.gui2 import error_dialog
+from calibre.gui2.actions import InterfaceAction
+from calibre.gui2.dialogs.tweak_epub import TweakEpub
+
+class TweakEpubAction(InterfaceAction):
+
+ name = 'Tweak ePub'
+ action_spec = (_('Tweak ePub'), 'trim.png',
+ _('Make small changes to ePub format books'),
+ _('T'))
+ dont_add_to = frozenset(['toolbar-device', 'context-menu-device'])
+ action_type = 'current'
+
+ def genesis(self):
+ self.qaction.triggered.connect(self.edit_epub_in_situ)
+
+ def edit_epub_in_situ(self, *args):
+ row = self.gui.library_view.currentIndex()
+ if not row.isValid():
+ return error_dialog(self.gui, _('Cannot tweak ePub'),
+ _('No book selected'), show=True)
+
+ # Confirm 'EPUB' in formats
+ book_id = self.gui.library_view.model().id(row)
+ try:
+ path_to_epub = self.gui.library_view.model().db.format_abspath(
+ book_id, 'EPUB', index_is_id=True)
+ except:
+ path_to_epub = None
+
+ if not path_to_epub:
+ return error_dialog(self.gui, _('Cannot tweak ePub'),
+ _('No ePub available. First convert the book to ePub.'),
+ show=True)
+
+ # Launch modal dialog waiting for user to tweak or cancel
+ dlg = TweakEpub(self.gui, path_to_epub)
+ if dlg.exec_() == dlg.Accepted:
+ self.update_db(book_id, dlg._output)
+ dlg.cleanup()
+
+ def update_db(self, book_id, rebuilt):
+ '''
+ Update the calibre db with the tweaked epub
+ '''
+ self.gui.library_view.model().db.add_format(book_id, 'EPUB',
+ open(rebuilt, 'rb'), index_is_id=True)
+
diff --git a/src/calibre/gui2/device.py b/src/calibre/gui2/device.py
index ae3141db56..a7e55c4619 100644
--- a/src/calibre/gui2/device.py
+++ b/src/calibre/gui2/device.py
@@ -800,7 +800,7 @@ class DeviceMixin(object): # {{{
# if set_books_in_library did not.
if not self.set_books_in_library(self.booklists(), reset=True):
self.upload_booklists()
- self.book_on_device(None, None, reset=True)
+ self.book_on_device(None, reset=True)
# We need to reset the ondevice flags in the library. Use a big hammer,
# so we don't need to worry about whether some succeeded or not.
self.refresh_ondevice_info(device_connected=True, reset_only=False)
@@ -1309,7 +1309,7 @@ class DeviceMixin(object): # {{{
for f in files:
getattr(f, 'close', lambda : True)()
- def book_on_device(self, id, format=None, reset=False):
+ def book_on_device(self, id, reset=False):
'''
Return an indication of whether the given book represented by its db id
is on the currently connected device. It returns a 5 element list. The
@@ -1338,8 +1338,6 @@ class DeviceMixin(object): # {{{
self.book_db_id_cache.append(set())
for book in l:
db_id = getattr(book, 'application_id', None)
- if db_id is None:
- db_id = book.db_id
if db_id is not None:
# increment the count of books on the device with this
# db_id.
diff --git a/src/calibre/gui2/dialogs/metadata_single.py b/src/calibre/gui2/dialogs/metadata_single.py
index 26dbda6ca4..53788809b6 100644
--- a/src/calibre/gui2/dialogs/metadata_single.py
+++ b/src/calibre/gui2/dialogs/metadata_single.py
@@ -300,6 +300,24 @@ class MetadataSingleDialog(ResizableDialog, Ui_MetadataSingleDialog):
self.cpixmap = pix
self.cover_data = cdata
+ def trim_cover(self, *args):
+ from calibre.utils.magick import Image
+ cdata = self.cover_data
+ if not cdata:
+ return
+ im = Image()
+ im.load(cdata)
+ im.trim(10)
+ cdata = im.export('jpg')
+ pix = QPixmap()
+ pix.loadFromData(cdata)
+ self.cover.setPixmap(pix)
+ self.cover_changed = True
+ self.cpixmap = pix
+ self.cover_data = cdata
+
+
+
def sync_formats(self):
old_extensions, new_extensions, paths = set(), set(), {}
for row in range(self.formats.count()):
@@ -380,6 +398,7 @@ class MetadataSingleDialog(ResizableDialog, Ui_MetadataSingleDialog):
self.remove_unused_series)
QObject.connect(self.auto_author_sort, SIGNAL('clicked()'),
self.deduce_author_sort)
+ self.trim_cover_button.clicked.connect(self.trim_cover)
self.connect(self.author_sort, SIGNAL('textChanged(const QString&)'),
self.author_sort_box_changed)
self.connect(self.authors, SIGNAL('editTextChanged(const QString&)'),
diff --git a/src/calibre/gui2/dialogs/metadata_single.ui b/src/calibre/gui2/dialogs/metadata_single.ui
index 74febf9c29..dbf825e706 100644
--- a/src/calibre/gui2/dialogs/metadata_single.ui
+++ b/src/calibre/gui2/dialogs/metadata_single.ui
@@ -625,6 +625,17 @@ Using this button to create author sort will change author sort from red to gree
+ -
+
+
+ Remove border (if any) from cover
+
+
+
+ :/images/trim.png:/images/trim.png
+
+
+
-
diff --git a/src/calibre/gui2/dialogs/tweak_epub.py b/src/calibre/gui2/dialogs/tweak_epub.py
new file mode 100755
index 0000000000..db6e93fd7a
--- /dev/null
+++ b/src/calibre/gui2/dialogs/tweak_epub.py
@@ -0,0 +1,81 @@
+#!/usr/bin/env python
+# vim:fileencoding=UTF-8:ts=4:sw=4:sta:et:sts=4:ai
+from __future__ import with_statement
+
+__license__ = 'GPL v3'
+__copyright__ = '2010, Kovid Goyal '
+__docformat__ = 'restructuredtext en'
+
+import os, shutil
+from contextlib import closing
+from zipfile import ZipFile, ZIP_DEFLATED, ZIP_STORED
+
+from PyQt4.Qt import QDialog
+
+from calibre.gui2 import open_local_file
+from calibre.gui2.dialogs.tweak_epub_ui import Ui_Dialog
+from calibre.libunzip import extract as zipextract
+from calibre.ptempfile import PersistentTemporaryDirectory
+
+class TweakEpub(QDialog, Ui_Dialog):
+ '''
+ Display controls for tweaking ePubs
+
+ '''
+
+ def __init__(self, parent, epub):
+ QDialog.__init__(self, parent)
+
+ self._epub = epub
+ self._exploded = None
+ self._output = None
+
+ # Run the dialog setup generated from tweak_epub.ui
+ self.setupUi(self)
+
+ self.cancel_button.clicked.connect(self.reject)
+ self.explode_button.clicked.connect(self.explode)
+ self.rebuild_button.clicked.connect(self.rebuild)
+
+ # Position update dialog overlaying top left of app window
+ parent_loc = parent.pos()
+ self.move(parent_loc.x(),parent_loc.y())
+
+ def cleanup(self):
+ # Delete directory containing exploded ePub
+ if self._exploded is not None:
+ shutil.rmtree(self._exploded, ignore_errors=True)
+
+
+ def display_exploded(self):
+ '''
+ Generic subprocess launch of native file browser
+ User can use right-click to 'Open with ...'
+ '''
+ open_local_file(self._exploded)
+
+ def explode(self, *args):
+ if self._exploded is None:
+ self._exploded = PersistentTemporaryDirectory("_exploded", prefix='')
+ zipextract(self._epub, self._exploded)
+ self.display_exploded()
+ self.rebuild_button.setEnabled(True)
+ self.explode_button.setEnabled(False)
+
+ def rebuild(self, *args):
+ self._output = os.path.join(self._exploded, 'rebuilt.epub')
+ with closing(ZipFile(self._output, 'w', compression=ZIP_DEFLATED)) as zf:
+ # Write mimetype
+ zf.write(os.path.join(self._exploded,'mimetype'), 'mimetype', compress_type=ZIP_STORED)
+ # Write everything else
+ exclude_files = ['.DS_Store','mimetype','iTunesMetadata.plist','rebuilt.epub']
+ for root, dirs, files in os.walk(self._exploded):
+ for fn in files:
+ if fn in exclude_files:
+ continue
+ absfn = os.path.join(root, fn)
+ zfn = os.path.relpath(absfn,
+ self._exploded).replace(os.sep, '/')
+ zf.write(absfn, zfn)
+ return QDialog.accept(self)
+
diff --git a/src/calibre/gui2/dialogs/tweak_epub.ui b/src/calibre/gui2/dialogs/tweak_epub.ui
new file mode 100644
index 0000000000..ccd33f44ab
--- /dev/null
+++ b/src/calibre/gui2/dialogs/tweak_epub.ui
@@ -0,0 +1,87 @@
+
+
+ Dialog
+
+
+ Qt::NonModal
+
+
+
+ 0
+ 0
+ 382
+ 242
+
+
+
+ Tweak ePub
+
+
+ false
+
+
+ false
+
+
+
-
+
+
+ Display contents of exploded ePub
+
+
+ &Explode ePub
+
+
+
+ :/images/wizard.png:/images/wizard.png
+
+
+
+ -
+
+
+ false
+
+
+ Rebuild ePub from exploded contents
+
+
+ &Rebuild ePub
+
+
+
+ :/images/exec.png:/images/exec.png
+
+
+
+ -
+
+
+ Discard changes
+
+
+ &Cancel
+
+
+
+ :/images/window-close.png:/images/window-close.png
+
+
+
+ -
+
+
+ Explode the ePub to display contents in a file browser window. To tweak individual files, right-click, then 'Open with...' your editor of choice. When tweaks are complete, close the file browser window. Rebuild the ePub, updating your calibre library.
+
+
+ true
+
+
+
+
+
+
+
+
+
+
diff --git a/src/calibre/gui2/library/models.py b/src/calibre/gui2/library/models.py
index 3370fd4b75..53f701386b 100644
--- a/src/calibre/gui2/library/models.py
+++ b/src/calibre/gui2/library/models.py
@@ -12,7 +12,7 @@ from operator import attrgetter
from PyQt4.Qt import QAbstractTableModel, Qt, pyqtSignal, QIcon, QImage, \
QModelIndex, QVariant, QDate
-from calibre.gui2 import NONE, config, UNDEFINED_QDATE
+from calibre.gui2 import NONE, config, UNDEFINED_QDATE, FunctionDispatcher
from calibre.utils.pyparsing import ParseException
from calibre.ebooks.metadata import fmt_sidx, authors_to_string, string_to_authors
from calibre.ptempfile import PersistentTemporaryFile
@@ -151,7 +151,7 @@ class BooksModel(QAbstractTableModel): # {{{
self.database_changed.emit(db)
if self.cover_cache is not None:
self.cover_cache.stop()
- self.cover_cache = CoverCache(db)
+ self.cover_cache = CoverCache(db, FunctionDispatcher(self.db.cover))
self.cover_cache.start()
def refresh_cover(event, ids):
if event == 'cover' and self.cover_cache is not None:
diff --git a/src/calibre/gui2/tools.py b/src/calibre/gui2/tools.py
index 7a516bb4ff..2f0452a773 100644
--- a/src/calibre/gui2/tools.py
+++ b/src/calibre/gui2/tools.py
@@ -217,6 +217,10 @@ def fetch_scheduled_recipe(arg):
if 'output_profile' in ps:
recs.append(('output_profile', ps['output_profile'],
OptionRecommendation.HIGH))
+ if ps['output_profile'] == 'kindle':
+ recs.append(('no_inline_toc', True,
+ OptionRecommendation.HIGH))
+
lf = load_defaults('look_and_feel')
if lf.get('base_font_size', 0.0) != 0.0:
recs.append(('base_font_size', lf['base_font_size'],
diff --git a/src/calibre/gui2/widgets.py b/src/calibre/gui2/widgets.py
index 60224aefc7..5efce74c08 100644
--- a/src/calibre/gui2/widgets.py
+++ b/src/calibre/gui2/widgets.py
@@ -863,11 +863,11 @@ class SplitterHandle(QSplitterHandle):
self.update()
def paintEvent(self, ev):
- QSplitterHandle.paintEvent(self, ev)
if self.highlight:
painter = QPainter(self)
painter.setClipRect(ev.rect())
painter.fillRect(self.rect(), Qt.yellow)
+ QSplitterHandle.paintEvent(self, ev)
def mouseDoubleClickEvent(self, ev):
self.double_clicked.emit(self)
diff --git a/src/calibre/library/caches.py b/src/calibre/library/caches.py
index 211baeb634..58edd89cb2 100644
--- a/src/calibre/library/caches.py
+++ b/src/calibre/library/caches.py
@@ -6,7 +6,7 @@ __license__ = 'GPL v3'
__copyright__ = '2010, Kovid Goyal '
__docformat__ = 'restructuredtext en'
-import re, itertools
+import re, itertools, time
from itertools import repeat
from datetime import timedelta
from threading import Thread, RLock
@@ -23,10 +23,11 @@ from calibre import fit_image
class CoverCache(Thread):
- def __init__(self, db):
+ def __init__(self, db, cover_func):
Thread.__init__(self)
self.daemon = True
self.db = db
+ self.cover_func = cover_func
self.load_queue = Queue()
self.keep_running = True
self.cache = {}
@@ -37,7 +38,8 @@ class CoverCache(Thread):
self.keep_running = False
def _image_for_id(self, id_):
- img = self.db.cover(id_, index_is_id=True, as_image=True)
+ time.sleep(0.050) # Limit 20/second to not overwhelm the GUI
+ img = self.cover_func(id_, index_is_id=True, as_image=True)
if img is None:
img = QImage()
if not img.isNull():
diff --git a/src/calibre/library/database2.py b/src/calibre/library/database2.py
index eb6e8336f9..1a2eef2c81 100644
--- a/src/calibre/library/database2.py
+++ b/src/calibre/library/database2.py
@@ -402,7 +402,7 @@ class LibraryDatabase2(LibraryDatabase, SchemaUpgrade, CustomColumns):
path = path.lower()
return path
- def set_path(self, index, index_is_id=False, commit=True):
+ def set_path(self, index, index_is_id=False):
'''
Set the path to the directory containing this books files based on its
current title and author. If there was a previous directory, its contents
@@ -432,7 +432,8 @@ class LibraryDatabase2(LibraryDatabase, SchemaUpgrade, CustomColumns):
if current_path and os.path.exists(spath): # Migrate existing files
cdata = self.cover(id, index_is_id=True)
if cdata is not None:
- open(os.path.join(tpath, 'cover.jpg'), 'wb').write(cdata)
+ with open(os.path.join(tpath, 'cover.jpg'), 'wb') as f:
+ f.write(cdata)
for format in formats:
# Get data as string (can't use file as source and target files may be the same)
f = self.format(id, format, index_is_id=True, as_file=False)
@@ -442,8 +443,6 @@ class LibraryDatabase2(LibraryDatabase, SchemaUpgrade, CustomColumns):
self.add_format(id, format, stream, index_is_id=True,
path=tpath, notify=False)
self.conn.execute('UPDATE books SET path=? WHERE id=?', (path, id))
- if commit:
- self.conn.commit()
self.data.set(id, self.FIELD_MAP['path'], path, row_is_id=True)
# Delete not needed directories
if current_path and os.path.exists(spath):
@@ -1131,7 +1130,10 @@ class LibraryDatabase2(LibraryDatabase, SchemaUpgrade, CustomColumns):
def set_authors(self, id, authors, notify=True, commit=True):
'''
- `authors`: A list of authors.
+ Note that even if commit is False, the db will still be committed to
+ because this causes the location of files to change
+
+ :param authors: A list of authors.
'''
if not authors:
authors = [_('Unknown')]
@@ -1163,11 +1165,15 @@ class LibraryDatabase2(LibraryDatabase, SchemaUpgrade, CustomColumns):
','.join([a.replace(',', '|') for a in authors]),
row_is_id=True)
self.data.set(id, self.FIELD_MAP['author_sort'], ss, row_is_id=True)
- self.set_path(id, index_is_id=True, commit=commit)
+ self.set_path(id, index_is_id=True)
if notify:
self.notify('metadata', [id])
def set_title(self, id, title, notify=True, commit=True):
+ '''
+ Note that even if commit is False, the db will still be committed to
+ because this causes the location of files to change
+ '''
if not title:
return
if not isinstance(title, unicode):
@@ -1178,7 +1184,7 @@ class LibraryDatabase2(LibraryDatabase, SchemaUpgrade, CustomColumns):
self.data.set(id, self.FIELD_MAP['sort'], title_sort(title), row_is_id=True)
else:
self.data.set(id, self.FIELD_MAP['sort'], title, row_is_id=True)
- self.set_path(id, index_is_id=True, commit=commit)
+ self.set_path(id, index_is_id=True)
if commit:
self.conn.commit()
if notify:
diff --git a/src/calibre/utils/magick/draw.py b/src/calibre/utils/magick/draw.py
index ed9e3d3d83..dcf9d7b671 100644
--- a/src/calibre/utils/magick/draw.py
+++ b/src/calibre/utils/magick/draw.py
@@ -60,15 +60,15 @@ def identify(path):
data = open(path, 'rb').read()
return identify_data(data)
-def add_borders_to_image(path_to_image, left=0, top=0, right=0, bottom=0,
- border_color='#ffffff'):
+def add_borders_to_image(img_data, left=0, top=0, right=0, bottom=0,
+ border_color='#ffffff', fmt='jpg'):
img = Image()
- img.open(path_to_image)
+ img.load(img_data)
lwidth, lheight = img.size
canvas = create_canvas(lwidth+left+right, lheight+top+bottom,
border_color)
canvas.compose(img, left, top)
- canvas.save(path_to_image)
+ return canvas.export(fmt)
def create_text_wand(font_size, font_path=None):
if font_path is None:
diff --git a/src/calibre/web/feeds/news.py b/src/calibre/web/feeds/news.py
index a140dfbf05..d1e7866198 100644
--- a/src/calibre/web/feeds/news.py
+++ b/src/calibre/web/feeds/news.py
@@ -7,7 +7,7 @@ Defines various abstract base classes that can be subclassed to create powerful
__docformat__ = "restructuredtext en"
-import os, time, traceback, re, urlparse, sys
+import os, time, traceback, re, urlparse, sys, cStringIO
from collections import defaultdict
from functools import partial
from contextlib import nested, closing
@@ -27,6 +27,7 @@ from calibre.web.fetch.simple import RecursiveFetcher
from calibre.utils.threadpool import WorkRequest, ThreadPool, NoResultsPending
from calibre.ptempfile import PersistentTemporaryFile
from calibre.utils.date import now as nowf
+from calibre.utils.magick.draw import save_cover_data_to, add_borders_to_image
class LoginFailed(ValueError):
pass
@@ -948,38 +949,36 @@ class BasicNewsRecipe(Recipe):
try:
cu = self.get_cover_url()
except Exception, err:
- cu = None
self.log.error(_('Could not download cover: %s')%str(err))
self.log.debug(traceback.format_exc())
- if cu is not None:
- ext = cu.split('/')[-1].rpartition('.')[-1]
- if '?' in ext:
- ext = ''
- ext = ext.lower() if ext and '/' not in ext else 'jpg'
- cpath = os.path.join(self.output_dir, 'cover.'+ext)
+ else:
+ cdata = None
if os.access(cu, os.R_OK):
- with open(cpath, 'wb') as cfile:
- cfile.write(open(cu, 'rb').read())
+ cdata = open(cu, 'rb').read()
else:
self.report_progress(1, _('Downloading cover from %s')%cu)
- with nested(open(cpath, 'wb'), closing(self.browser.open(cu))) as (cfile, r):
- cfile.write(r.read())
- if self.cover_margins[0] or self.cover_margins[1]:
- from calibre.utils.magick.draw import add_borders_to_image
- add_borders_to_image(cpath,
- left=self.cover_margins[0],right=self.cover_margins[0],
- top=self.cover_margins[1],bottom=self.cover_margins[1],
- border_color=self.cover_margins[2])
- if ext.lower() == 'pdf':
+ with closing(self.browser.open(cu)) as r:
+ cdata = r.read()
+ if not cdata:
+ return
+ ext = cu.split('/')[-1].rpartition('.')[-1].lower().strip()
+ if ext == 'pdf':
from calibre.ebooks.metadata.pdf import get_metadata
- stream = open(cpath, 'rb')
+ stream = cStringIO.StringIO(cdata)
+ cdata = None
mi = get_metadata(stream)
- cpath = None
if mi.cover_data and mi.cover_data[1]:
- cpath = os.path.join(self.output_dir,
- 'cover.'+mi.cover_data[0])
- with open(cpath, 'wb') as f:
- f.write(mi.cover_data[1])
+ cdata = mi.cover_data[1]
+ if not cdata:
+ return
+ if self.cover_margins[0] or self.cover_margins[1]:
+ cdata = add_borders_to_image(cdata,
+ left=self.cover_margins[0],right=self.cover_margins[0],
+ top=self.cover_margins[1],bottom=self.cover_margins[1],
+ border_color=self.cover_margins[2])
+
+ cpath = os.path.join(self.output_dir, 'cover.jpg')
+ save_cover_data_to(cdata, cpath)
self.cover_path = cpath
def download_cover(self):
@@ -1422,7 +1421,6 @@ class CalibrePeriodical(BasicNewsRecipe):
return br
def download(self):
- import cStringIO
self.log('Fetching downloaded recipe')
try:
raw = self.browser.open_novisit(