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
9fdc1709ed
BIN
icons/book.icns
Normal file
BIN
icons/book.icns
Normal file
Binary file not shown.
After Width: | Height: | Size: 11 KiB |
@ -8,7 +8,7 @@ p.title {
|
||||
font-size:xx-large;
|
||||
border-bottom: solid black 4px;
|
||||
}
|
||||
|
||||
|
||||
p.author {
|
||||
margin-top:0em;
|
||||
margin-bottom:0em;
|
||||
@ -31,25 +31,34 @@ p.description {
|
||||
margin-top: 0em;
|
||||
}
|
||||
|
||||
p.date_index {
|
||||
font-size:x-large;
|
||||
text-align:center;
|
||||
font-weight:bold;
|
||||
margin-top:1em;
|
||||
margin-bottom:0px;
|
||||
}
|
||||
|
||||
p.letter_index {
|
||||
font-size:x-large;
|
||||
text-align:left;
|
||||
margin-top:0px;
|
||||
margin-bottom:0px;
|
||||
text-align:center;
|
||||
font-weight:bold;
|
||||
margin-top:1em;
|
||||
margin-bottom:0px;
|
||||
}
|
||||
|
||||
p.author_index {
|
||||
font-size:large;
|
||||
text-align:left;
|
||||
margin-top:0px;
|
||||
margin-bottom:0px;
|
||||
margin-bottom:0px;
|
||||
text-indent: 0em;
|
||||
}
|
||||
|
||||
p.read_book {
|
||||
text-align:left;
|
||||
margin-top:0px;
|
||||
margin-bottom:0px;
|
||||
margin-bottom:0px;
|
||||
margin-left:2em;
|
||||
text-indent:-2em;
|
||||
}
|
||||
@ -57,8 +66,8 @@ p.read_book {
|
||||
p.unread_book {
|
||||
text-align:left;
|
||||
margin-top:0px;
|
||||
margin-bottom:0px;
|
||||
margin-bottom:0px;
|
||||
margin-left:2em;
|
||||
text-indent:-2em;
|
||||
}
|
||||
|
||||
|
||||
|
BIN
resources/images/news/information_dk.png
Normal file
BIN
resources/images/news/information_dk.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 343 B |
BIN
resources/images/news/jp_dk.png
Normal file
BIN
resources/images/news/jp_dk.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 609 B |
BIN
resources/images/news/michellemalkin_icon.png
Normal file
BIN
resources/images/news/michellemalkin_icon.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 419 B |
BIN
resources/images/news/politiken_dk.png
Normal file
BIN
resources/images/news/politiken_dk.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 482 B |
50
resources/recipes/heraldo.recipe
Normal file
50
resources/recipes/heraldo.recipe
Normal file
@ -0,0 +1,50 @@
|
||||
#!/usr/bin/env python
|
||||
__license__ = 'GPL v3'
|
||||
__author__ = 'Lorenzo Vigentini'
|
||||
__copyright__ = '2009, Lorenzo Vigentini <l.vigentini at gmail.com>'
|
||||
__description__ = 'Daily newspaper from Aragon'
|
||||
__version__ = 'v1.01'
|
||||
__date__ = '30, January 2010'
|
||||
|
||||
'''
|
||||
http://www.heraldo.es/
|
||||
'''
|
||||
|
||||
from calibre.web.feeds.news import BasicNewsRecipe
|
||||
|
||||
class heraldo(BasicNewsRecipe):
|
||||
author = 'Lorenzo Vigentini'
|
||||
description = 'Daily newspaper from Aragon'
|
||||
|
||||
cover_url = 'http://www.heraldo.es/MODULOS/global/publico/interfaces/img/logo.gif'
|
||||
title = u'Heraldo de Aragon'
|
||||
publisher = 'OJD Nielsen'
|
||||
category = 'News, politics, culture, economy, general interest'
|
||||
|
||||
language = 'es'
|
||||
timefmt = '[%a, %d %b, %Y]'
|
||||
|
||||
oldest_article = 1
|
||||
max_articles_per_feed = 25
|
||||
|
||||
use_embedded_content = False
|
||||
recursion = 10
|
||||
|
||||
remove_javascript = True
|
||||
no_stylesheets = True
|
||||
|
||||
keep_only_tags = [
|
||||
dict(name='div', attrs={'class':['titularNoticiaNN','textoGrisVerdanaContenidos']})
|
||||
]
|
||||
|
||||
feeds = [
|
||||
(u'Portadas ', u'http://www.heraldo.es/index.php/mod.portadas/mem.rss')
|
||||
]
|
||||
extra_css = '''
|
||||
.articledate {color: gray;font-family: monospace;}
|
||||
.articledescription {display: block;font-family: sans;font-size: 0.7em; text-indent: 0;}
|
||||
.firma {color: #666;display: block;font-family: verdana, arial, helvetica;font-size: 1em;margin-bottom: 8px;}
|
||||
.textoGrisVerdanaContenidos {color: #56595c;display: block;font-family: Verdana;font-size: 1.28571em;padding-bottom: 10px}
|
||||
.titularNoticiaNN {display: block;padding-bottom: 10px;padding-left: 0;padding-right: 0;padding-top: 4px}
|
||||
.titulo {color: #003066;font-family: Tahoma;font-size: 1.92857em;font-weight: bold;line-height: 1.2em}
|
||||
'''
|
50
resources/recipes/information_dk.recipe
Normal file
50
resources/recipes/information_dk.recipe
Normal file
@ -0,0 +1,50 @@
|
||||
|
||||
__license__ = 'GPL v3'
|
||||
__copyright__ = '2010, Darko Miletic <darko.miletic at gmail.com>'
|
||||
'''
|
||||
information.dk
|
||||
'''
|
||||
|
||||
from calibre.web.feeds.news import BasicNewsRecipe
|
||||
|
||||
class Information_dk(BasicNewsRecipe):
|
||||
title = 'Information - Denmark'
|
||||
__author__ = 'Darko Miletic'
|
||||
description = 'News from Denmark'
|
||||
publisher = 'information.dk'
|
||||
category = 'news, politics, Denmark'
|
||||
oldest_article = 2
|
||||
max_articles_per_feed = 100
|
||||
no_stylesheets = True
|
||||
remove_empty_feeds = True
|
||||
use_embedded_content = False
|
||||
encoding = 'utf8'
|
||||
language = 'da'
|
||||
|
||||
conversion_options = {
|
||||
'comment' : description
|
||||
, 'tags' : category
|
||||
, 'publisher': publisher
|
||||
, 'language' : language
|
||||
}
|
||||
|
||||
feeds = [
|
||||
(u'Nyheder fra' , u'http://www.information.dk/feed')
|
||||
,(u'Bedst lige nu' , u'http://www.information.dk/bedstligenu/feed')
|
||||
,(u'Politik og internationalt' , u'http://www.information.dk/politik/feed')
|
||||
,(u'Kunst og kultur' , u'http://www.information.dk/kultur/feed')
|
||||
,(u'Moderne Tider' , u'http://www.information.dk/modernetider/feed')
|
||||
,(u'Klima' , u'http://www.information.dk/klima/feed')
|
||||
,(u'Opinion' , u'http://www.information.dk/opinion/feed')
|
||||
,(u'Literatur' , u'http://www.information.dk/litteratur/feed')
|
||||
,(u'Film' , u'http://www.information.dk/film/feed')
|
||||
,(u'Kunst' , u'http://www.information.dk/kunst/feed')
|
||||
]
|
||||
|
||||
remove_tags_before = dict(name='h1',attrs={'class':'print-title'})
|
||||
remove_tags_after = dict(name='div',attrs={'class':'print-footer'})
|
||||
remove_tags = [dict(name=['object','link'])]
|
||||
|
||||
def print_version(self, url):
|
||||
return url.replace('information.dk/','information.dk/print/')
|
||||
|
50
resources/recipes/jp_dk.recipe
Normal file
50
resources/recipes/jp_dk.recipe
Normal file
@ -0,0 +1,50 @@
|
||||
|
||||
__license__ = 'GPL v3'
|
||||
__copyright__ = '2010, Darko Miletic <darko.miletic at gmail.com>'
|
||||
'''
|
||||
jp.dk
|
||||
'''
|
||||
|
||||
from calibre.web.feeds.news import BasicNewsRecipe
|
||||
|
||||
class JP_dk(BasicNewsRecipe):
|
||||
title = 'Jyllands-Posten'
|
||||
__author__ = 'Darko Miletic'
|
||||
description = 'News from Denmark'
|
||||
publisher = 'jp.dk'
|
||||
category = 'news, politics, Denmark'
|
||||
oldest_article = 2
|
||||
max_articles_per_feed = 100
|
||||
no_stylesheets = True
|
||||
use_embedded_content = False
|
||||
encoding = 'cp1252'
|
||||
language = 'da'
|
||||
|
||||
extra_css = ' body{font-family: Arial,Verdana,Helvetica,Geneva,sans-serif } h1{font-family: Times,Georgia,Verdana,serif } '
|
||||
|
||||
conversion_options = {
|
||||
'comment' : description
|
||||
, 'tags' : category
|
||||
, 'publisher': publisher
|
||||
, 'language' : language
|
||||
}
|
||||
|
||||
feeds = [
|
||||
(u'Tophistorier', u'http://www.jp.dk/rss/topnyheder.jsp')
|
||||
,(u'Seneste nyt' , u'http://jp.dk/index.jsp?service=rssfeed&submode=seneste')
|
||||
,(u'Indland' , u'http://www.jp.dk/rss/indland.jsp')
|
||||
,(u'Udland' , u'http://www.jp.dk/rss/udland.jsp')
|
||||
,(u'Ny viden' , u'http://www.jp.dk/rss/nyviden.jsp')
|
||||
,(u'Timeout' , u'http://www.jp.dk/rss/timeout.jsp')
|
||||
,(u'Kultur' , u'http://www.jp.dk/rss/kultur.jsp')
|
||||
,(u'Sport' , u'http://www.jp.dk/rss/sport.jsp')
|
||||
]
|
||||
|
||||
remove_tags = [
|
||||
dict(name=['object','link'])
|
||||
,dict(name='p',attrs={'class':'artByline'})
|
||||
]
|
||||
|
||||
def print_version(self, url):
|
||||
return url + '?service=printversion'
|
||||
|
@ -4,7 +4,7 @@ class Metro_Montreal(BasicNewsRecipe):
|
||||
|
||||
title = u'M\xe9tro Montr\xe9al'
|
||||
__author__ = 'Jerry Clapperton'
|
||||
description = 'Le quotidien le plus branché sur le monde'
|
||||
description = u'Le quotidien le plus branch\xe9 sur le monde'
|
||||
language = 'fr'
|
||||
|
||||
oldest_article = 7
|
||||
|
49
resources/recipes/michellemalkin.recipe
Normal file
49
resources/recipes/michellemalkin.recipe
Normal file
@ -0,0 +1,49 @@
|
||||
__license__ = 'GPL v3'
|
||||
__copyright__ = '2010, Walt Anthony <workshop.northpole at gmail.com>'
|
||||
'''
|
||||
www.michellemalkin.com
|
||||
'''
|
||||
from calibre.web.feeds.news import BasicNewsRecipe
|
||||
|
||||
class MichelleMalkin(BasicNewsRecipe):
|
||||
title = u'Michelle Malkin'
|
||||
description = "Michelle Malkin's take on events, a mother, wife, blogger, conservative syndicated columnist, author, and Fox News Channel contributor."
|
||||
__author__ = 'Walt Anthony'
|
||||
publisher = 'Michelle Malkin LLC'
|
||||
category = 'news, politics, USA'
|
||||
oldest_article = 7 #days
|
||||
max_articles_per_feed = 50
|
||||
summary_length = 150
|
||||
language = 'en'
|
||||
|
||||
remove_javascript = True
|
||||
no_stylesheets = True
|
||||
|
||||
|
||||
conversion_options = {
|
||||
'comment' : description
|
||||
, 'tags' : category
|
||||
, 'publisher' : publisher
|
||||
, 'language' : language
|
||||
, 'linearize_tables' : True
|
||||
}
|
||||
|
||||
|
||||
keep_only_tags = [
|
||||
dict(name='div', attrs={'class':'article'})
|
||||
]
|
||||
|
||||
remove_tags = [
|
||||
dict(name=['iframe', 'embed', 'object']),
|
||||
dict(name='div', attrs={'id':['comments', 'commentForm']}),
|
||||
dict(name='div', attrs={'class':['postCategories', 'comments', 'blogInfo', 'postInfo']})
|
||||
|
||||
]
|
||||
|
||||
|
||||
feeds = [(u'http://feeds.feedburner.com/michellemalkin/posts')]
|
||||
|
||||
|
||||
|
||||
def print_version(self, url):
|
||||
return url + '?print=1'
|
@ -1,46 +1,42 @@
|
||||
#!/usr/bin/env python
|
||||
|
||||
__license__ = 'GPL v3'
|
||||
__copyright__ = '2008-2009, Darko Miletic <darko.miletic at gmail.com>'
|
||||
__copyright__ = '2008-2010, Darko Miletic <darko.miletic at gmail.com>'
|
||||
'''
|
||||
nin.co.rs
|
||||
www.nin.co.rs
|
||||
'''
|
||||
|
||||
import re, urllib
|
||||
from calibre import strftime
|
||||
from calibre.web.feeds.news import BasicNewsRecipe
|
||||
from calibre.ebooks.BeautifulSoup import Tag
|
||||
|
||||
class Nin(BasicNewsRecipe):
|
||||
title = 'NIN online'
|
||||
__author__ = 'Darko Miletic'
|
||||
description = 'Nedeljne informativne novine'
|
||||
publisher = 'NIN D.O.O.'
|
||||
description = 'Nedeljne Informativne Novine'
|
||||
publisher = 'NIN d.o.o.'
|
||||
category = 'news, politics, Serbia'
|
||||
no_stylesheets = True
|
||||
oldest_article = 15
|
||||
simultaneous_downloads = 1
|
||||
delay = 1
|
||||
encoding = 'utf-8'
|
||||
needs_subscription = True
|
||||
remove_empty_feeds = True
|
||||
PREFIX = 'http://www.nin.co.rs'
|
||||
INDEX = PREFIX + '/?change_lang=ls'
|
||||
LOGIN = PREFIX + '/?logout=true'
|
||||
use_embedded_content = False
|
||||
language = 'sr'
|
||||
lang = 'sr-Latn-RS'
|
||||
direction = 'ltr'
|
||||
extra_css = '@font-face {font-family: "serif1";src:url(res:///opt/sony/ebook/FONT/tt0011m_.ttf)} @font-face {font-family: "sans1";src:url(res:///opt/sony/ebook/FONT/tt0003m_.ttf)} body{font-family: serif1, serif} .article_description{font-family: sans1, sans-serif} .artTitle{font-size: x-large; font-weight: bold} .columnhead{font-size: small; font-weight: bold}'
|
||||
extra_css = ' @font-face {font-family: "sans1";src:url(res:///opt/sony/ebook/FONT/tt0003m_.ttf)} body{font-family: Verdana, Lucida, sans1, sans-serif} .article_description{font-family: Verdana, Lucida, sans1, sans-serif} .artTitle{font-size: x-large; font-weight: bold; color: #900} .izjava{font-size: x-large; font-weight: bold} .columnhead{font-size: small; font-weight: bold;} img{margin-top:0.5em; margin-bottom: 0.7em} b{margin-top: 1em} '
|
||||
|
||||
conversion_options = {
|
||||
'comment' : description
|
||||
, 'tags' : category
|
||||
, 'publisher' : publisher
|
||||
, 'language' : language
|
||||
, 'pretty_print' : True
|
||||
, 'linearize_tables' : True
|
||||
}
|
||||
|
||||
preprocess_regexps = [(re.compile(u'\u0110'), lambda match: u'\u00D0')]
|
||||
remove_attributes = ['height','width']
|
||||
|
||||
def get_browser(self):
|
||||
br = BasicNewsRecipe.get_browser()
|
||||
@ -65,35 +61,20 @@ class Nin(BasicNewsRecipe):
|
||||
cover_url = self.PREFIX + link_item['src']
|
||||
return cover_url
|
||||
|
||||
def preprocess_html(self, soup):
|
||||
soup.html['lang'] = self.lang
|
||||
soup.html['dir' ] = self.direction
|
||||
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)
|
||||
attribs = [ 'style','font','valign'
|
||||
,'colspan','width','height'
|
||||
,'rowspan','summary','align'
|
||||
,'cellspacing','cellpadding'
|
||||
,'frames','rules','border'
|
||||
]
|
||||
for item in soup.body.findAll(name=['table','td','tr','th','caption','thead','tfoot','tbody','colgroup','col']):
|
||||
item.name = 'div'
|
||||
for attrib in attribs:
|
||||
if item.has_key(attrib):
|
||||
del item[attrib]
|
||||
return soup
|
||||
|
||||
def parse_index(self):
|
||||
articles = []
|
||||
count = 0
|
||||
soup = self.index_to_soup(self.PREFIX)
|
||||
for item in soup.findAll('a',attrs={'class':'lmeninavFont'}):
|
||||
count = count +1
|
||||
if self.test and count > 2:
|
||||
return articles
|
||||
section = self.tag_to_string(item)
|
||||
feedlink = self.PREFIX + item['href']
|
||||
feedpage = self.index_to_soup(feedlink)
|
||||
self.report_progress(0, _('Fetching feed')+' %s...'%(section))
|
||||
inarts = []
|
||||
count2 = 0
|
||||
for art in feedpage.findAll('span',attrs={'class':'artTitle'}):
|
||||
alink = art.parent
|
||||
url = self.PREFIX + alink['href']
|
||||
@ -110,3 +91,4 @@ class Nin(BasicNewsRecipe):
|
||||
})
|
||||
articles.append((section,inarts))
|
||||
return articles
|
||||
|
||||
|
73
resources/recipes/oc_register.recipe
Normal file
73
resources/recipes/oc_register.recipe
Normal file
@ -0,0 +1,73 @@
|
||||
#!/usr/bin/env python
|
||||
__license__ = 'GPL v3'
|
||||
__author__ = 'Lorenzo Vigentini'
|
||||
__copyright__ = '2009, Lorenzo Vigentini <l.vigentini at gmail.com>'
|
||||
description = 'News from the Orange county - v1.01 (29, January 2010)'
|
||||
|
||||
'''
|
||||
http://www.ocregister.com/
|
||||
'''
|
||||
|
||||
from calibre.web.feeds.news import BasicNewsRecipe
|
||||
|
||||
class ocRegister(BasicNewsRecipe):
|
||||
author = 'Lorenzo Vigentini'
|
||||
description = 'News from the Orange county'
|
||||
|
||||
cover_url = 'http://images.onset.freedom.com/ocregister/logo.gif'
|
||||
title = u'Orange County Register'
|
||||
publisher = 'Orange County Register Communication'
|
||||
category = 'News, finance, economy, politics'
|
||||
|
||||
language = 'en'
|
||||
timefmt = '[%a, %d %b, %Y]'
|
||||
|
||||
oldest_article = 1
|
||||
max_articles_per_feed = 25
|
||||
use_embedded_content = False
|
||||
recursion = 10
|
||||
|
||||
remove_javascript = True
|
||||
no_stylesheets = True
|
||||
|
||||
def print_version(self,url):
|
||||
printUrl = 'http://www.ocregister.com/common/printer/view.php?db=ocregister&id='
|
||||
segments = url.split('/')
|
||||
subSegments = (segments[4]).split('.')
|
||||
myArticle = (subSegments[0]).replace('-', '')
|
||||
myURL= printUrl + myArticle
|
||||
return myURL
|
||||
|
||||
keep_only_tags = [
|
||||
dict(name='div', attrs={'id':'ArticleContentWrap'})
|
||||
]
|
||||
|
||||
remove_tags = [
|
||||
dict(name='div', attrs={'class':'hideForPrint'}),
|
||||
dict(name='div', attrs={'id':'ContentFooter'})
|
||||
]
|
||||
|
||||
feeds = [
|
||||
(u'News', u'http://www.ocregister.com/common/rss/rss.php?catID=18800'),
|
||||
(u'Today paper', u'http://www.ocregister.com/common/rss/rss.php?catID=18976'),
|
||||
(u'Business', u'http://www.ocregister.com/common/rss/rss.php?catID=18909'),
|
||||
(u'Cars', u'http://www.ocregister.com/common/rss/rss.php?catID=20128'),
|
||||
(u'Entertainment', u'http://www.ocregister.com/common/rss/rss.php?catID=18926'),
|
||||
(u'Home', u'http://www.ocregister.com/common/rss/rss.php?catID=19142'),
|
||||
(u'Life', u'http://www.ocregister.com/common/rss/rss.php?catID=18936'),
|
||||
(u'Opinion', u'http://www.ocregister.com/common/rss/rss.php?catID=18963'),
|
||||
(u'Sports', u'http://www.ocregister.com/common/rss/rss.php?catID=18901'),
|
||||
(u'Travel', u'http://www.ocregister.com/common/rss/rss.php?catID=18959')
|
||||
]
|
||||
|
||||
extra_css = '''
|
||||
h1 {color:#ff6600;font-family:Arial,Helvetica,sans-serif; font-size:20px; font-size-adjust:none; font-stretch:normal; font-style:normal; font-variant:normal; font-weight:bold; line-height:20px;}
|
||||
h2 {color:#4D4D4D;font-family:Arial,Helvetica,sans-serif; font-size:16px; font-size-adjust:none; font-stretch:normal; font-style:normal; font-variant:normal; font-weight:bold; line-height:16px; }
|
||||
h3 {color:#4D4D4D;font-family:Arial,Helvetica,sans-serif; font-size:15px; font-size-adjust:none; font-stretch:normal; font-style:normal; font-variant:normal; font-weight:bold; line-height:15px;}
|
||||
h4 {color:#333333; font-family:Arial,Helvetica,sans-serif;font-size:13px; font-size-adjust:none; font-stretch:normal; font-style:normal; font-variant:normal; font-weight:bold; line-height:13px; }
|
||||
h5 {color:#333333; font-family:Arial,Helvetica,sans-serif; font-size:11px; font-size-adjust:none; font-stretch:normal; font-style:normal; font-variant:normal; font-weight:bold; line-height:11px; text-transform:uppercase;}
|
||||
#articledate {color:#333333;font-family:Arial,Helvetica,sans-serif;font-size:10px; font-size-adjust:none; font-stretch:normal; font-style:italic; font-variant:normal; font-weight:bold; line-height:10px; text-decoration:none;}
|
||||
#articlebyline {color:#4D4D4D;font-family:Arial,Helvetica,sans-serif;font-size:10px; font-size-adjust:none; font-stretch:normal; font-style:bold; font-variant:normal; font-weight:bold; line-height:10px; text-decoration:none;}
|
||||
img {align:left;}
|
||||
#topstoryhead {color:#ff6600;font-family:Arial,Helvetica,sans-serif; font-size:22px; font-size-adjust:none; font-stretch:normal; font-style:normal; font-variant:normal; font-weight:bold; line-height:20px;}
|
||||
'''
|
22
resources/recipes/open_left.recipe
Normal file
22
resources/recipes/open_left.recipe
Normal file
@ -0,0 +1,22 @@
|
||||
from calibre.web.feeds.news import BasicNewsRecipe
|
||||
|
||||
class OpenLeft(BasicNewsRecipe):
|
||||
# Information about the recipe
|
||||
|
||||
title = 'Open Left'
|
||||
description = 'Progressive American commentary on current events'
|
||||
category = 'news, commentary'
|
||||
language = 'en'
|
||||
__author__ = 'Xanthan Gum'
|
||||
|
||||
# Fetch no article older than seven days
|
||||
|
||||
oldest_article = 7
|
||||
|
||||
# Fetch no more than 100 articles
|
||||
|
||||
max_articles_per_feed = 100
|
||||
|
||||
# Fetch the articles from the RSS feed
|
||||
|
||||
feeds = [(u'Articles', u'http://www.openleft.com/rss/rss2.xml')]
|
55
resources/recipes/politiken_dk.recipe
Normal file
55
resources/recipes/politiken_dk.recipe
Normal file
@ -0,0 +1,55 @@
|
||||
|
||||
__license__ = 'GPL v3'
|
||||
__copyright__ = '2010, Darko Miletic <darko.miletic at gmail.com>'
|
||||
'''
|
||||
politiken.dk
|
||||
'''
|
||||
|
||||
from calibre.web.feeds.news import BasicNewsRecipe
|
||||
|
||||
class Politiken_dk(BasicNewsRecipe):
|
||||
title = 'Politiken.dk'
|
||||
__author__ = 'Darko Miletic'
|
||||
description = 'News from Denmark'
|
||||
publisher = 'politiken.dk'
|
||||
category = 'news, politics, Denmark'
|
||||
oldest_article = 2
|
||||
max_articles_per_feed = 100
|
||||
no_stylesheets = True
|
||||
remove_empty_feeds = True
|
||||
use_embedded_content = False
|
||||
encoding = 'cp1252'
|
||||
language = 'da'
|
||||
|
||||
extra_css = ' body{font-family: Arial,Helvetica,sans-serif } h1{font-family: Georgia,"Times New Roman",Times,serif } '
|
||||
|
||||
conversion_options = {
|
||||
'comment' : description
|
||||
, 'tags' : category
|
||||
, 'publisher': publisher
|
||||
, 'language' : language
|
||||
}
|
||||
|
||||
feeds = [
|
||||
(u'Tophistorier' , u'http://politiken.dk/rss/tophistorier.rss')
|
||||
,(u'Seneste nyt' , u'http://politiken.dk/rss/senestenyt.rss')
|
||||
,(u'Mest laeste' , u'http://politiken.dk/rss/mestlaeste.rss')
|
||||
,(u'Danmark' , u'http://politiken.dk/rss/indland.rss')
|
||||
,(u'Politik' , u'http://politiken.dk/rss/politik.rss')
|
||||
,(u'Klima' , u'http://politiken.dk/rss/klima.rss')
|
||||
,(u'Internationalt' , u'http://politiken.dk/rss/udland.rss')
|
||||
,(u'Erhverv' , u'http://politiken.dk/rss/erhverv.rss')
|
||||
,(u'Kultur' , u'http://politiken.dk/rss/kultur.rss')
|
||||
,(u'Sport' , u'http://politiken.dk/rss/sport.rss')
|
||||
,(u'Uddannelse' , u'http://politiken.dk/rss/uddannelse.rss')
|
||||
,(u'Videnskab' , u'http://politiken.dk/rss/videnskab.rss')
|
||||
]
|
||||
remove_tags_before = dict(name='h1')
|
||||
remove_tags = [
|
||||
dict(name=['object','link'])
|
||||
,dict(name='div',attrs={'class':'footer'})
|
||||
]
|
||||
|
||||
def print_version(self, url):
|
||||
return url + '?service=print'
|
||||
|
@ -46,3 +46,10 @@ class WashingtonPost(BasicNewsRecipe):
|
||||
div['style'] = ''
|
||||
return soup
|
||||
|
||||
def preprocess_html(self, soup):
|
||||
for tag in soup.findAll('font'):
|
||||
if tag.has_key('size'):
|
||||
if tag['size'] == '+2':
|
||||
if tag.b:
|
||||
return soup
|
||||
return None
|
||||
|
@ -20,4 +20,20 @@ function setup_image_scaling_handlers() {
|
||||
});
|
||||
}
|
||||
|
||||
function extract_svged_images() {
|
||||
$("svg").each(function() {
|
||||
var children = $(this).children("img");
|
||||
if (children.length == 1) {
|
||||
var img = $(children[0]);
|
||||
var href = img.attr('xlink:href');
|
||||
if (href != undefined) {
|
||||
$(this).replaceWith('<div style="text-align:center; margin: 0; padding: 0"><img style="height: 98%" alt="SVG Image" src="' + href +'"></img></div>');
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
$(document).ready(function() {
|
||||
//extract_svged_images();
|
||||
});
|
||||
|
||||
|
@ -266,6 +266,7 @@ class Py2App(object):
|
||||
def get_local_dependencies(self, path_to_lib):
|
||||
for x in self.get_dependencies(path_to_lib):
|
||||
for y in (SW+'/lib/', '/usr/local/lib/', SW+'/qt/lib/',
|
||||
'/opt/local/lib/',
|
||||
'/Library/Frameworks/Python.framework/', SW+'/freetype/lib/'):
|
||||
if x.startswith(y):
|
||||
if y == '/Library/Frameworks/Python.framework/':
|
||||
@ -338,8 +339,8 @@ class Py2App(object):
|
||||
c = join(self.build_dir, 'Contents')
|
||||
for x in ('Frameworks', 'MacOS', 'Resources'):
|
||||
os.makedirs(join(c, x))
|
||||
x = 'library.icns'
|
||||
shutil.copyfile(join('icons', x), join(self.resources_dir, x))
|
||||
for x in ('library.icns', 'book.icns'):
|
||||
shutil.copyfile(join('icons', x), join(self.resources_dir, x))
|
||||
|
||||
@flush
|
||||
def add_calibre_plugins(self):
|
||||
@ -355,8 +356,13 @@ class Py2App(object):
|
||||
|
||||
@flush
|
||||
def create_plist(self):
|
||||
from calibre.ebooks import BOOK_EXTENSIONS
|
||||
env = dict(**ENV)
|
||||
env['CALIBRE_LAUNCHED_FROM_BUNDLE']='1';
|
||||
docs = [{'CFBundleTypeName':'E-book',
|
||||
'CFBundleTypeExtensions':list(BOOK_EXTENSIONS),
|
||||
'CFBundleTypeRole':'Viewer',
|
||||
}]
|
||||
|
||||
pl = dict(
|
||||
CFBundleDevelopmentRegion='English',
|
||||
@ -367,10 +373,11 @@ class Py2App(object):
|
||||
CFBundlePackageType='APPL',
|
||||
CFBundleSignature='????',
|
||||
CFBundleExecutable='calibre',
|
||||
CFBundleDocumentTypes=docs,
|
||||
LSMinimumSystemVersion='10.4.2',
|
||||
LSRequiresNativeExecution=True,
|
||||
NSAppleScriptEnabled=False,
|
||||
NSHumanReadableCopyright='Copyright 2008, Kovid Goyal',
|
||||
NSHumanReadableCopyright='Copyright 2010, Kovid Goyal',
|
||||
CFBundleGetInfoString=('calibre, an E-book management '
|
||||
'application. Visit http://calibre-ebook.com for details.'),
|
||||
CFBundleIconFile='library.icns',
|
||||
@ -594,6 +601,7 @@ class Py2App(object):
|
||||
if x == 'Info.plist':
|
||||
plist = plistlib.readPlist(join(self.contents_dir, x))
|
||||
plist['LSUIElement'] = '1'
|
||||
plist.pop('CFBundleDocumentTypes')
|
||||
plistlib.writePlist(plist, join(cc_dir, x))
|
||||
else:
|
||||
os.symlink(join('../..', x),
|
||||
|
@ -132,9 +132,12 @@ def prints(*args, **kwargs):
|
||||
try:
|
||||
arg = arg.encode(enc)
|
||||
except UnicodeEncodeError:
|
||||
if not safe_encode:
|
||||
raise
|
||||
arg = repr(arg)
|
||||
try:
|
||||
arg = arg.encode('utf-8')
|
||||
except:
|
||||
if not safe_encode:
|
||||
raise
|
||||
arg = repr(arg)
|
||||
|
||||
file.write(arg)
|
||||
if i != len(args)-1:
|
||||
|
@ -288,7 +288,7 @@ class CatalogPlugin(Plugin):
|
||||
fields = list(all_fields)
|
||||
|
||||
fields.sort()
|
||||
if opts.sort_by:
|
||||
if opts.sort_by and opts.sort_by in fields:
|
||||
fields.insert(0,fields.pop(int(fields.index(opts.sort_by))))
|
||||
return fields
|
||||
|
||||
|
@ -274,7 +274,7 @@ class BookList(_BookList):
|
||||
node.setAttribute(attr, attrs[attr])
|
||||
try:
|
||||
w, h, data = mi.thumbnail
|
||||
except TypeError:
|
||||
except:
|
||||
w, h, data = None, None, None
|
||||
|
||||
if data:
|
||||
|
@ -7,7 +7,7 @@ __docformat__ = 'restructuredtext en'
|
||||
Based on ideas from comiclrf created by FangornUK.
|
||||
'''
|
||||
|
||||
import os, shutil, traceback, textwrap, time
|
||||
import os, shutil, traceback, textwrap, time, codecs
|
||||
from ctypes import byref
|
||||
from Queue import Empty
|
||||
|
||||
@ -338,8 +338,9 @@ class ComicInput(InputFormatPlugin):
|
||||
if not os.path.exists('comics.txt'):
|
||||
raise ValueError('%s is not a valid comic collection'
|
||||
%stream.name)
|
||||
for line in open('comics.txt',
|
||||
'rb').read().decode('utf-8').splitlines():
|
||||
raw = open('comics.txt', 'rb').read().decode('utf-8')
|
||||
raw.lstrip(unicode(codecs.BOM_UTF8, "utf8" ))
|
||||
for line in raw.splitlines():
|
||||
line = line.strip()
|
||||
if not line:
|
||||
continue
|
||||
|
@ -269,7 +269,7 @@ class EPUBOutput(OutputFormatPlugin):
|
||||
bad = []
|
||||
for x in XPath('//h:img')(body):
|
||||
src = x.get('src', '').strip()
|
||||
if src in ('', '#'):
|
||||
if src in ('', '#') or src.startswith('http:'):
|
||||
bad.append(x)
|
||||
for img in bad:
|
||||
img.getparent().remove(img)
|
||||
|
@ -50,7 +50,7 @@ class MOBIOutput(OutputFormatPlugin):
|
||||
def check_for_masthead(self):
|
||||
found = 'masthead' in self.oeb.guide
|
||||
if not found:
|
||||
self.oeb.log.debug('No masthead found, generating default one...')
|
||||
self.oeb.log.debug('No masthead found in manifest, generating default mastheadImage...')
|
||||
try:
|
||||
from PIL import Image as PILImage
|
||||
PILImage
|
||||
@ -65,6 +65,9 @@ class MOBIOutput(OutputFormatPlugin):
|
||||
id, href = self.oeb.manifest.generate('masthead', 'masthead')
|
||||
self.oeb.manifest.add(id, href, 'image/gif', data=raw)
|
||||
self.oeb.guide.add('masthead', 'Masthead Image', href)
|
||||
else:
|
||||
self.oeb.log.debug('Using mastheadImage supplied in manifest...')
|
||||
|
||||
|
||||
def dump_toc(self, toc) :
|
||||
self.log( "\n >>> TOC contents <<<")
|
||||
|
@ -573,6 +573,8 @@ class MobiReader(object):
|
||||
attrib[attr] = "%dpx"%int(nval)
|
||||
except:
|
||||
del attrib[attr]
|
||||
elif val.lower().endswith('%'):
|
||||
del attrib[attr]
|
||||
elif tag.tag == 'pre':
|
||||
if not tag.text:
|
||||
tag.tag = 'div'
|
||||
|
@ -256,11 +256,16 @@ class Region(object):
|
||||
return len(self.columns) == 0
|
||||
|
||||
@property
|
||||
def is_small(self):
|
||||
def line_count(self):
|
||||
max_lines = 0
|
||||
for c in self.columns:
|
||||
max_lines = max(max_lines, len(c))
|
||||
return max_lines > 2
|
||||
return max_lines
|
||||
|
||||
|
||||
@property
|
||||
def is_small(self):
|
||||
return self.line_count < 3
|
||||
|
||||
def absorb(self, singleton):
|
||||
|
||||
@ -431,7 +436,7 @@ class Page(object):
|
||||
def coalesce_regions(self):
|
||||
# find contiguous sets of small regions
|
||||
# absorb into a neighboring region (prefer the one with number of cols
|
||||
# closer to the avg number of cols in the set, if equal use large
|
||||
# closer to the avg number of cols in the set, if equal use larger
|
||||
# region)
|
||||
# merge contiguous regions that can contain each other
|
||||
absorbed = set([])
|
||||
|
@ -3,7 +3,8 @@ __copyright__ = '2008, Kovid Goyal <kovid at kovidgoyal.net>'
|
||||
""" The GUI """
|
||||
import os
|
||||
from PyQt4.QtCore import QVariant, QFileInfo, QObject, SIGNAL, QBuffer, Qt, QSize, \
|
||||
QByteArray, QTranslator, QCoreApplication, QThread
|
||||
QByteArray, QTranslator, QCoreApplication, QThread, \
|
||||
QEvent
|
||||
from PyQt4.QtGui import QFileDialog, QMessageBox, QPixmap, QFileIconProvider, \
|
||||
QIcon, QTableView, QApplication, QDialog, QPushButton
|
||||
|
||||
@ -88,6 +89,8 @@ def _config():
|
||||
help=_('Maximum number of waiting worker processes'))
|
||||
c.add_opt('get_social_metadata', default=True,
|
||||
help=_('Download social metadata (tags/rating/etc.)'))
|
||||
c.add_opt('overwrite_author_title_metadata', default=True,
|
||||
help=_('Overwrite author and title with new metadata'))
|
||||
c.add_opt('enforce_cpu_limit', default=True,
|
||||
help=_('Limit max simultaneous jobs to number of CPUs'))
|
||||
|
||||
@ -524,6 +527,7 @@ class Application(QApplication):
|
||||
def __init__(self, args):
|
||||
qargs = [i.encode('utf-8') if isinstance(i, unicode) else i for i in args]
|
||||
QApplication.__init__(self, qargs)
|
||||
self.file_event_hook = None
|
||||
global gui_thread, qt_app
|
||||
gui_thread = QThread.currentThread()
|
||||
self._translator = None
|
||||
@ -549,6 +553,15 @@ class Application(QApplication):
|
||||
if set_qt_translator(self._translator):
|
||||
self.installTranslator(self._translator)
|
||||
|
||||
def event(self, e):
|
||||
if callable(self.file_event_hook) and e.type() == QEvent.FileOpen:
|
||||
path = unicode(e.file())
|
||||
if os.access(path, os.R_OK):
|
||||
self.file_event_hook(path)
|
||||
return True
|
||||
else:
|
||||
return QApplication.event(self, e)
|
||||
|
||||
_store_app = None
|
||||
|
||||
def is_ok_to_use_qt():
|
||||
|
@ -12,7 +12,7 @@ from calibre.gui2.dialogs.progress import ProgressDialog
|
||||
from calibre.gui2 import question_dialog, error_dialog, info_dialog
|
||||
from calibre.ebooks.metadata.opf2 import OPF
|
||||
from calibre.ebooks.metadata import MetaInformation
|
||||
from calibre.constants import preferred_encoding
|
||||
from calibre.constants import preferred_encoding, filesystem_encoding
|
||||
|
||||
class DuplicatesAdder(QThread):
|
||||
|
||||
@ -46,6 +46,8 @@ class RecursiveFind(QThread):
|
||||
def run(self):
|
||||
root = os.path.abspath(self.path)
|
||||
self.books = []
|
||||
if isinstance(root, unicode):
|
||||
root = root.encode(filesystem_encoding)
|
||||
try:
|
||||
for dirpath in os.walk(root):
|
||||
if self.canceled:
|
||||
@ -55,6 +57,8 @@ class RecursiveFind(QThread):
|
||||
self.books += list(self.db.find_books_in_directory(dirpath[0],
|
||||
self.single_book_per_directory))
|
||||
except Exception, err:
|
||||
import traceback
|
||||
traceback.print_exc()
|
||||
try:
|
||||
msg = unicode(err)
|
||||
except:
|
||||
|
@ -14,19 +14,6 @@
|
||||
<string>Form</string>
|
||||
</property>
|
||||
<layout class="QGridLayout" name="gridLayout">
|
||||
<item row="0" column="0">
|
||||
<widget class="QLabel" name="label">
|
||||
<property name="text">
|
||||
<string>Tags to exclude as genres (regex):</string>
|
||||
</property>
|
||||
<property name="textFormat">
|
||||
<enum>Qt::LogText</enum>
|
||||
</property>
|
||||
<property name="wordWrap">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="1" column="0">
|
||||
<widget class="QLabel" name="label_2">
|
||||
<property name="text">
|
||||
@ -37,7 +24,7 @@
|
||||
<item row="1" column="1">
|
||||
<widget class="QLineEdit" name="exclude_tags">
|
||||
<property name="toolTip">
|
||||
<string extracomment="Tooltip comment here"/>
|
||||
<string extracomment="Default: ~,Catalog"/>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
@ -51,7 +38,7 @@
|
||||
<item row="2" column="1">
|
||||
<widget class="QLineEdit" name="read_tag">
|
||||
<property name="toolTip">
|
||||
<string extracomment="Tooltip comment here"/>
|
||||
<string extracomment="Default: +"/>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
@ -65,18 +52,67 @@
|
||||
<item row="3" column="1">
|
||||
<widget class="QLineEdit" name="note_tag">
|
||||
<property name="toolTip">
|
||||
<string extracomment="Tooltip comment here"/>
|
||||
<string extracomment="Default: *"/>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="0" column="1">
|
||||
<item row="8" column="0">
|
||||
<widget class="QCheckBox" name="numbers_as_text">
|
||||
<property name="text">
|
||||
<string>Sort numbers as text</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="5" column="1">
|
||||
<widget class="QLineEdit" name="exclude_genre">
|
||||
<property name="toolTip">
|
||||
<string extracomment="Tooltip comment here"/>
|
||||
<string extracomment="Default: \[[\w]*\]"/>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="4" column="0">
|
||||
<item row="5" column="0">
|
||||
<widget class="QLabel" name="label">
|
||||
<property name="text">
|
||||
<string>Regex pattern describing tags to exclude as genres:</string>
|
||||
</property>
|
||||
<property name="textFormat">
|
||||
<enum>Qt::LogText</enum>
|
||||
</property>
|
||||
<property name="wordWrap">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="0" column="0" colspan="2">
|
||||
<widget class="QLabel" name="label_5">
|
||||
<property name="font">
|
||||
<font>
|
||||
<pointsize>14</pointsize>
|
||||
<weight>75</weight>
|
||||
<bold>true</bold>
|
||||
</font>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>Special marker tags for catalog generation</string>
|
||||
</property>
|
||||
<property name="alignment">
|
||||
<set>Qt::AlignCenter</set>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="6" column="1">
|
||||
<widget class="QLabel" name="label_6">
|
||||
<property name="text">
|
||||
<string>Regex tips:
|
||||
- The default regex of '\[[\w]*\]' ignores tags of the form '[tag]', e.g., '[Amazon Freebie]'
|
||||
- A regex of '.' ignores all tags, generating no genre categories in the catalog</string>
|
||||
</property>
|
||||
<property name="wordWrap">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="7" column="0">
|
||||
<spacer name="verticalSpacer">
|
||||
<property name="orientation">
|
||||
<enum>Qt::Vertical</enum>
|
||||
@ -89,13 +125,6 @@
|
||||
</property>
|
||||
</spacer>
|
||||
</item>
|
||||
<item row="5" column="0">
|
||||
<widget class="QCheckBox" name="numbers_as_text">
|
||||
<property name="text">
|
||||
<string>Sort numbers as text</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
<resources/>
|
||||
|
@ -23,7 +23,7 @@ def gui_convert(input, output, recommendations, notification=DummyReporter(),
|
||||
|
||||
plumber.run()
|
||||
|
||||
def gui_catalog(fmt, title, dbspec, ids, out_file_name, fmt_options,
|
||||
def gui_catalog(fmt, title, dbspec, ids, out_file_name, sync, fmt_options,
|
||||
notification=DummyReporter(), log=None):
|
||||
if log is None:
|
||||
log = Log()
|
||||
@ -46,6 +46,7 @@ def gui_catalog(fmt, title, dbspec, ids, out_file_name, fmt_options,
|
||||
opts.ids = ids
|
||||
opts.search_text = None
|
||||
opts.sort_by = None
|
||||
opts.sync = sync
|
||||
|
||||
# Extract the option dictionary to comma-separated lists
|
||||
for option in fmt_options:
|
||||
|
@ -107,7 +107,7 @@
|
||||
<rect>
|
||||
<x>12</x>
|
||||
<y>12</y>
|
||||
<width>205</width>
|
||||
<width>301</width>
|
||||
<height>17</height>
|
||||
</rect>
|
||||
</property>
|
||||
|
@ -458,6 +458,7 @@ class ConfigDialog(ResizableDialog, Ui_Dialog):
|
||||
self.connect(self.button_open_config_dir, SIGNAL('clicked()'),
|
||||
self.open_config_dir)
|
||||
self.opt_get_social_metadata.setChecked(config['get_social_metadata'])
|
||||
self.opt_overwrite_author_title_metadata.setChecked(config['overwrite_author_title_metadata'])
|
||||
self.opt_enforce_cpu_limit.setChecked(config['enforce_cpu_limit'])
|
||||
self.device_detection_button.clicked.connect(self.debug_device_detection)
|
||||
|
||||
@ -751,6 +752,7 @@ class ConfigDialog(ResizableDialog, Ui_Dialog):
|
||||
config['upload_news_to_device'] = self.sync_news.isChecked()
|
||||
config['search_as_you_type'] = self.search_as_you_type.isChecked()
|
||||
config['get_social_metadata'] = self.opt_get_social_metadata.isChecked()
|
||||
config['overwrite_author_title_metadata'] = self.opt_overwrite_author_title_metadata.isChecked()
|
||||
config['enforce_cpu_limit'] = bool(self.opt_enforce_cpu_limit.isChecked())
|
||||
fmts = []
|
||||
for i in range(self.viewer.count()):
|
||||
|
@ -171,6 +171,13 @@
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QCheckBox" name="opt_overwrite_author_title_metadata">
|
||||
<property name="text">
|
||||
<string>Overwrite & author/title by default when fetching metadata</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<layout class="QGridLayout" name="gridLayout_2">
|
||||
<item row="1" column="0">
|
||||
|
@ -119,6 +119,7 @@ class FetchMetadata(QDialog, Ui_FetchMetadata):
|
||||
self.matches.setMouseTracking(True)
|
||||
self.fetch_metadata()
|
||||
self.opt_get_social_metadata.setChecked(config['get_social_metadata'])
|
||||
self.opt_overwrite_author_title_metadata.setChecked(config['overwrite_author_title_metadata'])
|
||||
|
||||
|
||||
def show_summary(self, current, *args):
|
||||
@ -149,7 +150,8 @@ class FetchMetadata(QDialog, Ui_FetchMetadata):
|
||||
self.fetcher.start()
|
||||
self.pi.start(_('Finding metadata...'))
|
||||
self._hangcheck = QTimer(self)
|
||||
self.connect(self._hangcheck, SIGNAL('timeout()'), self.hangcheck)
|
||||
self.connect(self._hangcheck, SIGNAL('timeout()'), self.hangcheck,
|
||||
Qt.QueuedConnection)
|
||||
self.start_time = time.time()
|
||||
self._hangcheck.start(100)
|
||||
|
||||
|
@ -116,6 +116,13 @@
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QCheckBox" name="opt_overwrite_author_title_metadata">
|
||||
<property name="text">
|
||||
<string>Overwrite &author/title with author/title of selected book</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QDialogButtonBox" name="buttonBox">
|
||||
<property name="standardButtons">
|
||||
|
@ -20,10 +20,8 @@ class MetadataBulkDialog(QDialog, Ui_MetadataBulkDialog):
|
||||
self.db = db
|
||||
self.ids = [ db.id(r) for r in rows]
|
||||
self.write_series = False
|
||||
self.write_rating = False
|
||||
self.changed = False
|
||||
QObject.connect(self.button_box, SIGNAL("accepted()"), self.sync)
|
||||
QObject.connect(self.rating, SIGNAL('valueChanged(int)'), self.rating_changed)
|
||||
|
||||
self.tags.update_tags_cache(self.db.all_tags())
|
||||
self.remove_tags.update_tags_cache(self.db.all_tags())
|
||||
@ -99,7 +97,7 @@ class MetadataBulkDialog(QDialog, Ui_MetadataBulkDialog):
|
||||
aus = unicode(self.author_sort.text())
|
||||
if aus and self.author_sort.isEnabled():
|
||||
self.db.set_author_sort(id, aus, notify=False)
|
||||
if self.write_rating:
|
||||
if self.rating.value() != -1:
|
||||
self.db.set_rating(id, 2*self.rating.value(), notify=False)
|
||||
pub = unicode(self.publisher.text())
|
||||
if pub:
|
||||
@ -134,5 +132,3 @@ class MetadataBulkDialog(QDialog, Ui_MetadataBulkDialog):
|
||||
def series_changed(self):
|
||||
self.write_series = True
|
||||
|
||||
def rating_changed(self):
|
||||
self.write_rating = True
|
||||
|
@ -96,12 +96,21 @@
|
||||
<property name="buttonSymbols">
|
||||
<enum>QAbstractSpinBox::PlusMinus</enum>
|
||||
</property>
|
||||
<property name="specialValueText">
|
||||
<string>No change</string>
|
||||
</property>
|
||||
<property name="suffix">
|
||||
<string> stars</string>
|
||||
</property>
|
||||
<property name="minimum">
|
||||
<number>-1</number>
|
||||
</property>
|
||||
<property name="maximum">
|
||||
<number>5</number>
|
||||
</property>
|
||||
<property name="value">
|
||||
<number>-1</number>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="4" column="0">
|
||||
|
@ -574,9 +574,10 @@ class MetadataSingleDialog(ResizableDialog, Ui_MetadataSingleDialog):
|
||||
det_msg=det, show=True)
|
||||
else:
|
||||
book.tags = []
|
||||
self.title.setText(book.title)
|
||||
self.authors.setText(authors_to_string(book.authors))
|
||||
if book.author_sort: self.author_sort.setText(book.author_sort)
|
||||
if d.opt_overwrite_author_title_metadata.isChecked():
|
||||
self.title.setText(book.title)
|
||||
self.authors.setText(authors_to_string(book.authors))
|
||||
if book.author_sort: self.author_sort.setText(book.author_sort)
|
||||
if book.publisher: self.publisher.setEditText(book.publisher)
|
||||
if book.isbn: self.isbn.setText(book.isbn)
|
||||
if book.pubdate:
|
||||
|
@ -2,6 +2,7 @@ __license__ = 'GPL v3'
|
||||
__copyright__ = '2008, Kovid Goyal <kovid at kovidgoyal.net>'
|
||||
|
||||
import sys, os, time, socket, traceback
|
||||
from functools import partial
|
||||
|
||||
from PyQt4.Qt import QCoreApplication, QIcon, QMessageBox
|
||||
|
||||
@ -52,10 +53,12 @@ def run_gui(opts, args, actions, listener, app):
|
||||
wizard().exec_()
|
||||
dynamic.set('welcome_wizard_was_run', True)
|
||||
main = Main(listener, opts, actions)
|
||||
add_filesystem_book = partial(main.add_filesystem_book, allow_device=False)
|
||||
sys.excepthook = main.unhandled_exception
|
||||
if len(args) > 1:
|
||||
args[1] = os.path.abspath(args[1])
|
||||
main.add_filesystem_book(args[1])
|
||||
add_filesystem_book(args[1])
|
||||
app.file_event_hook = add_filesystem_book
|
||||
ret = app.exec_()
|
||||
if getattr(main, 'run_wizard_b4_shutdown', False):
|
||||
from calibre.gui2.wizard import wizard
|
||||
|
@ -12,6 +12,7 @@ from Queue import Queue, Empty
|
||||
|
||||
|
||||
from calibre.ebooks.metadata.fetch import search, get_social_metadata
|
||||
from calibre.gui2 import config
|
||||
from calibre.ebooks.metadata.library_thing import cover_from_isbn
|
||||
from calibre.customize.ui import get_isbndb_key
|
||||
|
||||
@ -98,6 +99,10 @@ class DownloadMetadata(Thread):
|
||||
self.fetched_metadata[id] = fmi
|
||||
if fmi.isbn and self.get_covers:
|
||||
self.worker.jobs.put(fmi.isbn)
|
||||
if (not config['overwrite_author_title_metadata']):
|
||||
fmi.authors = mi.authors
|
||||
fmi.author_sort = mi.author_sort
|
||||
fmi.title = mi.title
|
||||
mi.smart_update(fmi)
|
||||
if mi.isbn and self.get_social_metadata:
|
||||
self.social_metadata_exceptions = get_social_metadata(mi)
|
||||
|
@ -254,11 +254,13 @@ def generate_catalog(parent, dbspec, ids):
|
||||
dbspec,
|
||||
ids,
|
||||
out.name,
|
||||
d.catalog_sync,
|
||||
d.fmt_options
|
||||
]
|
||||
out.close()
|
||||
|
||||
# This calls gui2.convert.gui_conversion:gui_catalog()
|
||||
# This returns to gui2.ui:generate_catalog()
|
||||
# Which then calls gui2.convert.gui_conversion:gui_catalog()
|
||||
return 'gui_catalog', args, _('Generate catalog'), out.name, d.catalog_sync, \
|
||||
d.catalog_title
|
||||
|
||||
|
@ -987,10 +987,10 @@ class Main(MainWindow, Ui_MainWindow, DeviceGUI):
|
||||
self.cover_cache.refresh([cid])
|
||||
self.library_view.model().current_changed(current_idx, current_idx)
|
||||
|
||||
def add_filesystem_book(self, path):
|
||||
def add_filesystem_book(self, path, allow_device=True):
|
||||
if os.access(path, os.R_OK):
|
||||
books = [os.path.abspath(path)]
|
||||
to_device = self.stack.currentIndex() != 0
|
||||
to_device = allow_device and self.stack.currentIndex() != 0
|
||||
self._add_books(books, to_device)
|
||||
if to_device:
|
||||
self.status_bar.showMessage(\
|
||||
@ -1048,6 +1048,8 @@ class Main(MainWindow, Ui_MainWindow, DeviceGUI):
|
||||
if self._adder.critical:
|
||||
det_msg = []
|
||||
for name, log in self._adder.critical.items():
|
||||
if isinstance(name, str):
|
||||
name = name.decode(filesystem_encoding, 'replace')
|
||||
det_msg.append(name+'\n'+log)
|
||||
warning_dialog(self, _('Failed to read metadata'),
|
||||
_('Failed to read metadata from the following')+':',
|
||||
|
@ -10,7 +10,7 @@ from base64 import b64encode
|
||||
from PyQt4.Qt import QSize, QSizePolicy, QUrl, SIGNAL, Qt, QTimer, \
|
||||
QPainter, QPalette, QBrush, QFontDatabase, QDialog, \
|
||||
QColor, QPoint, QImage, QRegion, QVariant, QIcon, \
|
||||
QFont, pyqtSignature, QAction
|
||||
QFont, pyqtSignature, QAction, QByteArray
|
||||
from PyQt4.QtWebKit import QWebPage, QWebView, QWebSettings
|
||||
|
||||
from calibre.utils.config import Config, StringConfig
|
||||
@ -514,14 +514,18 @@ class DocumentView(QWebView):
|
||||
mt = guess_type(path)[0]
|
||||
html = open(path, 'rb').read().decode(path.encoding, 'replace')
|
||||
html = EntityDeclarationProcessor(html).processed_html
|
||||
has_svg = re.search(r'<[:a-zA-Z]*svg', html) is not None
|
||||
|
||||
if 'xhtml' in mt:
|
||||
html = self.self_closing_pat.sub(self.self_closing_sub, html)
|
||||
if self.manager is not None:
|
||||
self.manager.load_started()
|
||||
self.loading_url = QUrl.fromLocalFile(path)
|
||||
#self.setContent(QByteArray(html.encode(path.encoding)), mt, QUrl.fromLocalFile(path))
|
||||
#open('/tmp/t.html', 'wb').write(html.encode(path.encoding))
|
||||
self.setHtml(html, self.loading_url)
|
||||
if has_svg:
|
||||
prints('Rendering as XHTML...')
|
||||
self.setContent(QByteArray(html.encode(path.encoding)), mt, QUrl.fromLocalFile(path))
|
||||
else:
|
||||
self.setHtml(html, self.loading_url)
|
||||
self.turn_off_internal_scrollbars()
|
||||
|
||||
def initialize_scrollbar(self):
|
||||
|
@ -1,9 +1,10 @@
|
||||
import os, re, shutil, htmlentitydefs
|
||||
|
||||
from collections import namedtuple
|
||||
from datetime import date
|
||||
from xml.sax.saxutils import escape
|
||||
|
||||
from calibre import filesystem_encoding, prints
|
||||
from calibre import filesystem_encoding, prints, strftime
|
||||
from calibre.customize import CatalogPlugin
|
||||
from calibre.customize.conversion import OptionRecommendation, DummyReporter
|
||||
from calibre.ebooks.BeautifulSoup import BeautifulSoup, BeautifulStoneSoup, Tag, NavigableString
|
||||
@ -50,18 +51,23 @@ class CSV_XML(CatalogPlugin):
|
||||
self.fmt = path_to_output.rpartition('.')[2]
|
||||
self.notification = notification
|
||||
|
||||
if False and opts.verbose:
|
||||
log("%s:run" % self.name)
|
||||
log(" path_to_output: %s" % path_to_output)
|
||||
log(" Output format: %s" % self.fmt)
|
||||
|
||||
# Display opts
|
||||
if opts.verbose:
|
||||
opts_dict = vars(opts)
|
||||
keys = opts_dict.keys()
|
||||
keys.sort()
|
||||
log(" opts:")
|
||||
for key in keys:
|
||||
log(" %s: %s" % (key, opts_dict[key]))
|
||||
log("%s(): Generating %s" % (self.name,self.fmt))
|
||||
if opts_dict['search_text']:
|
||||
log(" --search='%s'" % opts_dict['search_text'])
|
||||
|
||||
if opts_dict['ids']:
|
||||
log(" Book count: %d" % len(opts_dict['ids']))
|
||||
if opts_dict['search_text']:
|
||||
log(" (--search ignored when a subset of the database is specified)")
|
||||
|
||||
if opts_dict['fields']:
|
||||
if opts_dict['fields'] == 'all':
|
||||
log(" Fields: %s" % ', '.join(FIELDS[1:]))
|
||||
else:
|
||||
log(" Fields: %s" % opts_dict['fields'])
|
||||
|
||||
|
||||
# If a list of ids are provided, don't use search_text
|
||||
if opts.ids:
|
||||
@ -94,6 +100,10 @@ class CSV_XML(CatalogPlugin):
|
||||
item = ', '.join(fmt_list)
|
||||
elif field in ['authors','tags']:
|
||||
item = ', '.join(item)
|
||||
elif field == 'isbn':
|
||||
# Could be 9, 10 or 13 digits
|
||||
field = u'%s' % re.sub(r'[\D]','',field)
|
||||
|
||||
if x < len(fields) - 1:
|
||||
if item is not None:
|
||||
outstr += u'"%s",' % unicode(item).replace('"','""')
|
||||
@ -481,11 +491,8 @@ class EPUB_MOBI(CatalogPlugin):
|
||||
|
||||
# Number of discrete steps to catalog creation
|
||||
current_step = 0.0
|
||||
total_steps = 13.0
|
||||
total_steps = 14.0
|
||||
|
||||
# Used to xlate pubdate to friendly format
|
||||
MONTHS = ['January', 'February','March','April','May','June',
|
||||
'July','August','September','October','November','December']
|
||||
THUMB_WIDTH = 75
|
||||
THUMB_HEIGHT = 100
|
||||
|
||||
@ -500,7 +507,7 @@ class EPUB_MOBI(CatalogPlugin):
|
||||
# verbosity level of diagnostic printout
|
||||
|
||||
def __init__(self, db, opts, plugin,
|
||||
notification=DummyReporter(),
|
||||
report_progress=DummyReporter(),
|
||||
stylesheet="content/stylesheet.css"):
|
||||
self.__opts = opts
|
||||
self.__authors = None
|
||||
@ -525,16 +532,12 @@ class EPUB_MOBI(CatalogPlugin):
|
||||
self.__plugin_path = opts.plugin_path
|
||||
self.__progressInt = 0.0
|
||||
self.__progressString = ''
|
||||
self.__reporter = notification
|
||||
self.__reporter = report_progress
|
||||
self.__stylesheet = stylesheet
|
||||
self.__thumbs = None
|
||||
self.__title = opts.catalog_title
|
||||
self.__verbose = opts.verbose
|
||||
|
||||
self.opts.log.info("CatalogBuilder(): Generating %s %s"% \
|
||||
(self.opts.fmt,
|
||||
"for %s" % self.opts.output_profile if self.opts.output_profile \
|
||||
else ''))
|
||||
# Accessors
|
||||
'''
|
||||
@dynamic_property
|
||||
@ -753,53 +756,27 @@ class EPUB_MOBI(CatalogPlugin):
|
||||
|
||||
# Methods
|
||||
def buildSources(self):
|
||||
if getattr(self.reporter, 'cancel_requested', False): return 1
|
||||
if not self.booksByTitle:
|
||||
self.fetchBooksByTitle()
|
||||
|
||||
if getattr(self.reporter, 'cancel_requested', False): return 1
|
||||
self.fetchBooksByTitle()
|
||||
self.fetchBooksByAuthor()
|
||||
|
||||
if getattr(self.reporter, 'cancel_requested', False): return 1
|
||||
self.generateHTMLDescriptions()
|
||||
|
||||
if getattr(self.reporter, 'cancel_requested', False): return 1
|
||||
self.generateHTMLByTitle()
|
||||
|
||||
if getattr(self.reporter, 'cancel_requested', False): return 1
|
||||
self.generateHTMLByAuthor()
|
||||
|
||||
if getattr(self.reporter, 'cancel_requested', False): return 1
|
||||
self.generateHTMLByTitle()
|
||||
self.generateHTMLByDateAdded()
|
||||
self.generateHTMLByTags()
|
||||
|
||||
if getattr(self.reporter, 'cancel_requested', False): return 1
|
||||
from calibre.utils.PythonMagickWand import ImageMagick
|
||||
with ImageMagick():
|
||||
self.generateThumbnails()
|
||||
|
||||
if getattr(self.reporter, 'cancel_requested', False): return 1
|
||||
self.generateOPF()
|
||||
|
||||
if getattr(self.reporter, 'cancel_requested', False): return 1
|
||||
self.generateNCXHeader()
|
||||
|
||||
if getattr(self.reporter, 'cancel_requested', False): return 1
|
||||
self.generateNCXDescriptions("Descriptions")
|
||||
|
||||
if getattr(self.reporter, 'cancel_requested', False): return 1
|
||||
self.generateNCXByTitle("Titles")
|
||||
|
||||
if getattr(self.reporter, 'cancel_requested', False): return 1
|
||||
self.generateNCXByAuthor("Authors")
|
||||
|
||||
if getattr(self.reporter, 'cancel_requested', False): return 1
|
||||
self.generateNCXByTags("Genres")
|
||||
|
||||
if getattr(self.reporter, 'cancel_requested', False): return 1
|
||||
self.generateNCXByTitle("Titles")
|
||||
self.generateNCXByDateAdded("Recently Added")
|
||||
self.generateNCXByGenre("Genres")
|
||||
self.writeNCX()
|
||||
|
||||
return 0
|
||||
|
||||
def cleanUp(self):
|
||||
pass
|
||||
|
||||
@ -819,8 +796,14 @@ class EPUB_MOBI(CatalogPlugin):
|
||||
shutil.copy(os.path.join(catalog_resources,file[1]),
|
||||
os.path.join(self.catalogPath, file[0]))
|
||||
|
||||
# Create the custom masthead image overwriting default
|
||||
try:
|
||||
self.generate_masthead_image(os.path.join(self.catalogPath, 'images/mastheadImage.gif'))
|
||||
except:
|
||||
pass
|
||||
|
||||
def fetchBooksByTitle(self):
|
||||
self.opts.log.info(self.updateProgressFullStep("fetchBooksByTitle()"))
|
||||
self.updateProgressFullStep("Fetching database")
|
||||
|
||||
# Get the database as a dictionary
|
||||
# Sort by title
|
||||
@ -867,10 +850,8 @@ class EPUB_MOBI(CatalogPlugin):
|
||||
this_title['publisher'] = re.sub('&', '&', record['publisher'])
|
||||
|
||||
this_title['rating'] = record['rating'] if record['rating'] else 0
|
||||
# <pubdate>2009-11-05 09:29:37</pubdate>
|
||||
date_strings = str(record['pubdate']).split("-")
|
||||
this_title['date'] = '%s %s' % (self.MONTHS[int(date_strings[1])-1], date_strings[0])
|
||||
|
||||
this_title['date'] = strftime(u'%B %Y', record['pubdate'].timetuple())
|
||||
this_title['timestamp'] = record['timestamp']
|
||||
if record['comments']:
|
||||
this_title['description'] = re.sub('&', '&', record['comments'])
|
||||
this_title['short_description'] = self.generateShortDescription(this_title['description'])
|
||||
@ -906,7 +887,7 @@ class EPUB_MOBI(CatalogPlugin):
|
||||
def fetchBooksByAuthor(self):
|
||||
# Generate a list of titles sorted by author from the database
|
||||
|
||||
self.opts.log.info(self.updateProgressFullStep("fetchBooksByAuthor()"))
|
||||
self.updateProgressFullStep("Sorting database")
|
||||
|
||||
# Sort titles case-insensitive
|
||||
self.booksByAuthor = sorted(self.booksByTitle,
|
||||
@ -959,14 +940,15 @@ class EPUB_MOBI(CatalogPlugin):
|
||||
|
||||
def generateHTMLDescriptions(self):
|
||||
# Write each title to a separate HTML file in contentdir
|
||||
self.opts.log.info(self.updateProgressFullStep("generateHTMLDescriptions()"))
|
||||
self.updateProgressFullStep("'Descriptions'")
|
||||
|
||||
for (title_num, title) in enumerate(self.booksByTitle):
|
||||
if False:
|
||||
self.opts.log.info("%3s: %s - %s" % (title['id'], title['title'], title['author']))
|
||||
|
||||
self.updateProgressMicroStep("generating book descriptions ...",
|
||||
float(title_num*100/len(self.booksByTitle))/100)
|
||||
self.updateProgressMicroStep("Description %d of %d" % \
|
||||
(title_num, len(self.booksByTitle)),
|
||||
float(title_num*100/len(self.booksByTitle))/100)
|
||||
|
||||
# Generate the header
|
||||
soup = self.generateHTMLDescriptionHeader("%s" % title['title'])
|
||||
@ -1087,7 +1069,7 @@ class EPUB_MOBI(CatalogPlugin):
|
||||
def generateHTMLByTitle(self):
|
||||
# Write books by title A-Z to HTML file
|
||||
|
||||
self.opts.log.info(self.updateProgressFullStep("generateHTMLByTitle()"))
|
||||
self.updateProgressFullStep("'Titles'")
|
||||
|
||||
soup = self.generateHTMLEmptyHeader("Books By Alpha Title")
|
||||
body = soup.find('body')
|
||||
@ -1189,7 +1171,7 @@ class EPUB_MOBI(CatalogPlugin):
|
||||
|
||||
def generateHTMLByAuthor(self):
|
||||
# Write books by author A-Z
|
||||
self.opts.log.info(self.updateProgressFullStep("generateHTMLByAuthor()"))
|
||||
self.updateProgressFullStep("'Authors'")
|
||||
|
||||
friendly_name = "By Author"
|
||||
|
||||
@ -1317,11 +1299,141 @@ class EPUB_MOBI(CatalogPlugin):
|
||||
outfile.close()
|
||||
self.htmlFileList.append("content/ByAlphaAuthor.html")
|
||||
|
||||
def generateHTMLByDateAdded(self):
|
||||
# Write books by reverse chronological order
|
||||
self.updateProgressFullStep("'Recently Added'")
|
||||
|
||||
def add_books_to_HTML(this_months_list, dtc):
|
||||
if len(this_months_list):
|
||||
date_string = strftime(u'%B %Y', current_date.timetuple())
|
||||
this_months_list = sorted(this_months_list,
|
||||
key=lambda x:(x['title_sort'], x['title_sort']))
|
||||
this_months_list = sorted(this_months_list,
|
||||
key=lambda x:(x['author_sort'], x['author_sort']))
|
||||
# Create a new month anchor
|
||||
pIndexTag = Tag(soup, "p")
|
||||
pIndexTag['class'] = "date_index"
|
||||
aTag = Tag(soup, "a")
|
||||
aTag['name'] = "%s-%s" % (current_date.year, current_date.month)
|
||||
pIndexTag.insert(0,aTag)
|
||||
pIndexTag.insert(1,NavigableString(date_string))
|
||||
divTag.insert(dtc,pIndexTag)
|
||||
dtc += 1
|
||||
current_author = None
|
||||
|
||||
for new_entry in this_months_list:
|
||||
if new_entry['author'] != current_author:
|
||||
# Start a new author
|
||||
current_author = new_entry['author']
|
||||
pAuthorTag = Tag(soup, "p")
|
||||
pAuthorTag['class'] = "author_index"
|
||||
emTag = Tag(soup, "em")
|
||||
aTag = Tag(soup, "a")
|
||||
aTag['name'] = "%s" % self.generateAuthorAnchor(current_author)
|
||||
aTag.insert(0,NavigableString(current_author))
|
||||
emTag.insert(0,aTag)
|
||||
pAuthorTag.insert(0,emTag)
|
||||
divTag.insert(dtc,pAuthorTag)
|
||||
dtc += 1
|
||||
|
||||
# Add books
|
||||
pBookTag = Tag(soup, "p")
|
||||
ptc = 0
|
||||
|
||||
# Prefix book with read/unread symbol
|
||||
if new_entry['read']:
|
||||
# check mark
|
||||
pBookTag.insert(ptc,NavigableString(self.READ_SYMBOL))
|
||||
pBookTag['class'] = "read_book"
|
||||
ptc += 1
|
||||
else:
|
||||
# hidden check mark
|
||||
pBookTag['class'] = "unread_book"
|
||||
pBookTag.insert(ptc,NavigableString(self.NOT_READ_SYMBOL))
|
||||
ptc += 1
|
||||
|
||||
aTag = Tag(soup, "a")
|
||||
aTag['href'] = "book_%d.html" % (int(float(new_entry['id'])))
|
||||
aTag.insert(0,escape(new_entry['title']))
|
||||
pBookTag.insert(ptc, aTag)
|
||||
ptc += 1
|
||||
|
||||
divTag.insert(dtc, pBookTag)
|
||||
dtc += 1
|
||||
return dtc
|
||||
|
||||
|
||||
# Sort titles case-insensitive
|
||||
self.booksByDate = sorted(self.booksByTitle,
|
||||
key=lambda x:(x['timestamp'], x['timestamp']),reverse=True)
|
||||
|
||||
friendly_name = "Recently Added"
|
||||
|
||||
soup = self.generateHTMLEmptyHeader(friendly_name)
|
||||
body = soup.find('body')
|
||||
|
||||
btc = 0
|
||||
|
||||
# Insert section tag
|
||||
aTag = Tag(soup,'a')
|
||||
aTag['name'] = 'section_start'
|
||||
body.insert(btc, aTag)
|
||||
btc += 1
|
||||
|
||||
# Insert the anchor
|
||||
aTag = Tag(soup, "a")
|
||||
anchor_name = friendly_name.lower()
|
||||
aTag['name'] = anchor_name.replace(" ","")
|
||||
body.insert(btc, aTag)
|
||||
btc += 1
|
||||
'''
|
||||
# We don't need this because the kindle inserts section titles
|
||||
#<h2><a name="byalphaauthor" id="byalphaauthor"></a>By Author</h2>
|
||||
h2Tag = Tag(soup, "h2")
|
||||
aTag = Tag(soup, "a")
|
||||
anchor_name = friendly_name.lower()
|
||||
aTag['name'] = anchor_name.replace(" ","")
|
||||
h2Tag.insert(0,aTag)
|
||||
h2Tag.insert(1,NavigableString('%s' % friendly_name))
|
||||
body.insert(btc,h2Tag)
|
||||
btc += 1
|
||||
'''
|
||||
|
||||
# <p class="letter_index">
|
||||
# <p class="author_index">
|
||||
divTag = Tag(soup, "div")
|
||||
dtc = 0
|
||||
|
||||
current_date = date.fromordinal(1)
|
||||
|
||||
# Loop through books by date
|
||||
this_months_list = []
|
||||
for book in self.booksByDate:
|
||||
if book['timestamp'].month != current_date.month or \
|
||||
book['timestamp'].year != current_date.year:
|
||||
dtc = add_books_to_HTML(this_months_list, dtc)
|
||||
this_months_list = []
|
||||
current_date = book['timestamp'].date()
|
||||
this_months_list.append(book)
|
||||
|
||||
# Add the last month's list
|
||||
add_books_to_HTML(this_months_list, dtc)
|
||||
|
||||
# Add the divTag to the body
|
||||
body.insert(btc, divTag)
|
||||
|
||||
# Write the generated file to contentdir
|
||||
outfile_spec = "%s/ByDateAdded.html" % (self.contentDir)
|
||||
outfile = open(outfile_spec, 'w')
|
||||
outfile.write(soup.prettify())
|
||||
outfile.close()
|
||||
self.htmlFileList.append("content/ByDateAdded.html")
|
||||
|
||||
def generateHTMLByTags(self):
|
||||
# Generate individual HTML files for each tag, e.g. Fiction, Nonfiction ...
|
||||
# Note that special tags - ~+*[] - have already been filtered from books[]
|
||||
|
||||
self.opts.log.info(self.updateProgressFullStep("generateHTMLByTags()"))
|
||||
self.updateProgressFullStep("'Genres'")
|
||||
|
||||
# Filter out REMOVE_TAGS, sort
|
||||
filtered_tags = self.filterDbTags(self.db.all_tags())
|
||||
@ -1402,7 +1514,8 @@ class EPUB_MOBI(CatalogPlugin):
|
||||
|
||||
for (i,title) in enumerate(self.booksByTitle):
|
||||
# Update status
|
||||
self.updateProgressMicroStep("generating thumbnails ...",
|
||||
self.updateProgressMicroStep("Thumbnail %d of %d" % \
|
||||
(i,len(self.booksByTitle)),
|
||||
i/float(len(self.booksByTitle)))
|
||||
# Check to see if source file exists
|
||||
if 'cover' in title and os.path.isfile(title['cover']):
|
||||
@ -1424,7 +1537,7 @@ class EPUB_MOBI(CatalogPlugin):
|
||||
self.generateThumbnail(title, image_dir, thumb_file)
|
||||
else:
|
||||
# Use default cover
|
||||
if self.verbose:
|
||||
if False and self.verbose:
|
||||
self.opts.log.warn(" using default cover for '%s'" % \
|
||||
(title['title']))
|
||||
# Check to make sure default is current
|
||||
@ -1457,13 +1570,13 @@ class EPUB_MOBI(CatalogPlugin):
|
||||
cover_timestamp = os.path.getmtime(cover)
|
||||
thumb_timestamp = os.path.getmtime(thumb_fp)
|
||||
if thumb_timestamp < cover_timestamp:
|
||||
if self.verbose:
|
||||
if False and self.verbose:
|
||||
self.opts.log.warn("updating thumbnail_default for %s" % title['title'])
|
||||
#title['cover'] = "%s/DefaultCover.jpg" % self.catalogPath
|
||||
title['cover'] = cover
|
||||
self.generateThumbnail(title, image_dir, "thumbnail_default.jpg")
|
||||
else:
|
||||
if self.verbose:
|
||||
if False and self.verbose:
|
||||
self.opts.log.warn(" generating new thumbnail_default.jpg")
|
||||
#title['cover'] = "%s/DefaultCover.jpg" % self.catalogPath
|
||||
title['cover'] = cover
|
||||
@ -1473,7 +1586,7 @@ class EPUB_MOBI(CatalogPlugin):
|
||||
|
||||
def generateOPF(self):
|
||||
|
||||
self.opts.log.info(self.updateProgressFullStep("generateOPF()"))
|
||||
self.updateProgressFullStep("Saving OPF")
|
||||
|
||||
header = '''
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
@ -1605,7 +1718,7 @@ class EPUB_MOBI(CatalogPlugin):
|
||||
|
||||
def generateNCXHeader(self):
|
||||
|
||||
self.opts.log.info(self.updateProgressFullStep("generateNCXHeader()"))
|
||||
self.updateProgressFullStep("NCX header")
|
||||
|
||||
header = '''
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
@ -1641,7 +1754,7 @@ class EPUB_MOBI(CatalogPlugin):
|
||||
|
||||
def generateNCXDescriptions(self, tocTitle):
|
||||
|
||||
self.opts.log.info(self.updateProgressFullStep("generateNCXDescriptions()"))
|
||||
self.updateProgressFullStep("NCX 'Descriptions'")
|
||||
|
||||
# --- Construct the 'Books by Title' section ---
|
||||
ncx_soup = self.ncxSoup
|
||||
@ -1687,7 +1800,11 @@ class EPUB_MOBI(CatalogPlugin):
|
||||
# Add the author tag
|
||||
cmTag = Tag(ncx_soup, '%s' % 'calibre:meta')
|
||||
cmTag['name'] = "author"
|
||||
cmTag.insert(0, NavigableString(self.formatNCXText(book['author'])))
|
||||
navStr = '%s | %s' % (self.formatNCXText(book['author']),
|
||||
book['date'].split()[1])
|
||||
if 'tags' in book:
|
||||
navStr += ' | %s' % self.formatNCXText(' · '.join(sorted(book['tags'])))
|
||||
cmTag.insert(0, NavigableString(navStr))
|
||||
navPointVolumeTag.insert(2, cmTag)
|
||||
|
||||
# Add the description tag
|
||||
@ -1708,8 +1825,12 @@ class EPUB_MOBI(CatalogPlugin):
|
||||
self.ncxSoup = ncx_soup
|
||||
|
||||
def generateNCXByTitle(self, tocTitle):
|
||||
self.updateProgressFullStep("NCX 'Titles'")
|
||||
|
||||
self.opts.log.info(self.updateProgressFullStep("generateNCXByTitle()"))
|
||||
def add_to_books_by_letter(current_book_list):
|
||||
current_book_list = " • ".join(current_book_list)
|
||||
current_book_list = self.generateShortDescription(self.formatNCXText(current_book_list))
|
||||
books_by_letter.append(current_book_list)
|
||||
|
||||
soup = self.ncxSoup
|
||||
output = "ByAlphaTitle"
|
||||
@ -1744,9 +1865,7 @@ class EPUB_MOBI(CatalogPlugin):
|
||||
for book in self.booksByTitle:
|
||||
if self.letter_or_symbol(book['title_sort'][0]) != current_letter:
|
||||
# Save the old list
|
||||
book_list = " • ".join(current_book_list)
|
||||
short_description = self.generateShortDescription(self.formatNCXText(book_list))
|
||||
books_by_letter.append(short_description)
|
||||
add_to_books_by_letter(current_book_list)
|
||||
|
||||
# Start the new list
|
||||
current_letter = self.letter_or_symbol(book['title_sort'][0])
|
||||
@ -1760,9 +1879,7 @@ class EPUB_MOBI(CatalogPlugin):
|
||||
current_book_list.append(book['title'])
|
||||
|
||||
# Add the last book list
|
||||
book_list = " • ".join(current_book_list)
|
||||
short_description = self.generateShortDescription(self.formatNCXText(book_list))
|
||||
books_by_letter.append(short_description)
|
||||
add_to_books_by_letter(current_book_list)
|
||||
|
||||
# Add *article* entries for each populated title letter
|
||||
for (i,books) in enumerate(books_by_letter):
|
||||
@ -1773,7 +1890,8 @@ class EPUB_MOBI(CatalogPlugin):
|
||||
self.playOrder += 1
|
||||
navLabelTag = Tag(soup, 'navLabel')
|
||||
textTag = Tag(soup, 'text')
|
||||
textTag.insert(0, NavigableString("Titles beginning with %s" % (title_letters[i])))
|
||||
textTag.insert(0, NavigableString(u"Titles beginning with %s" % \
|
||||
(title_letters[i] if len(title_letters[i])>1 else "'" + title_letters[i] + "'")))
|
||||
navLabelTag.insert(0, textTag)
|
||||
navPointByLetterTag.insert(0,navLabelTag)
|
||||
contentTag = Tag(soup, 'content')
|
||||
@ -1796,8 +1914,12 @@ class EPUB_MOBI(CatalogPlugin):
|
||||
self.ncxSoup = soup
|
||||
|
||||
def generateNCXByAuthor(self, tocTitle):
|
||||
self.updateProgressFullStep("NCX 'Authors'")
|
||||
|
||||
self.opts.log.info(self.updateProgressFullStep("generateNCXByAuthor()"))
|
||||
def add_to_author_list(current_author_list, current_letter):
|
||||
current_author_list = " • ".join(current_author_list)
|
||||
current_author_list = self.generateShortDescription(self.formatNCXText(current_author_list))
|
||||
master_author_list.append((current_author_list, current_letter))
|
||||
|
||||
soup = self.ncxSoup
|
||||
HTML_file = "content/ByAlphaAuthor.html"
|
||||
@ -1835,14 +1957,7 @@ class EPUB_MOBI(CatalogPlugin):
|
||||
for author in self.authors:
|
||||
if author[1][0] != current_letter:
|
||||
# Save the old list
|
||||
author_list = " • ".join(current_author_list)
|
||||
if len(current_author_list) == self.descriptionClip:
|
||||
author_list += " …"
|
||||
|
||||
author_list = self.formatNCXText(author_list)
|
||||
if False and self.verbose:
|
||||
self.opts.log.info(" adding '%s' to master_author_list" % current_letter)
|
||||
master_author_list.append((author_list, current_letter))
|
||||
add_to_author_list(current_author_list, current_letter)
|
||||
|
||||
# Start the new list
|
||||
current_letter = author[1][0]
|
||||
@ -1852,13 +1967,7 @@ class EPUB_MOBI(CatalogPlugin):
|
||||
current_author_list.append(author[0])
|
||||
|
||||
# Add the last author list
|
||||
author_list = " • ".join(current_author_list)
|
||||
if len(current_author_list) == self.descriptionClip:
|
||||
author_list += " …"
|
||||
author_list = self.formatNCXText(author_list)
|
||||
if False and self.verbose:
|
||||
self.opts.log.info(" adding '%s' to master_author_list" % current_letter)
|
||||
master_author_list.append((author_list, current_letter))
|
||||
add_to_author_list(current_author_list, current_letter)
|
||||
|
||||
# Add *article* entries for each populated author initial letter
|
||||
# master_author_list{}: [0]:author list [1]:Initial letter
|
||||
@ -1893,12 +2002,113 @@ class EPUB_MOBI(CatalogPlugin):
|
||||
|
||||
self.ncxSoup = soup
|
||||
|
||||
def generateNCXByTags(self, tocTitle):
|
||||
def generateNCXByDateAdded(self, tocTitle):
|
||||
self.updateProgressFullStep("NCX 'Recently Added'")
|
||||
|
||||
def add_to_master_month_list(current_titles_list):
|
||||
book_count = len(current_titles_list)
|
||||
current_titles_list = " • ".join(current_titles_list)
|
||||
current_titles_list = self.generateShortDescription(self.formatNCXText(current_titles_list))
|
||||
master_month_list.append((current_titles_list, current_date, book_count))
|
||||
|
||||
soup = self.ncxSoup
|
||||
HTML_file = "content/ByDateAdded.html"
|
||||
body = soup.find("navPoint")
|
||||
btc = len(body.contents)
|
||||
|
||||
# --- Construct the 'Recently Added' *section* ---
|
||||
navPointTag = Tag(soup, 'navPoint')
|
||||
navPointTag['class'] = "section"
|
||||
file_ID = "%s" % tocTitle.lower()
|
||||
file_ID = file_ID.replace(" ","")
|
||||
navPointTag['id'] = "%s-ID" % file_ID
|
||||
navPointTag['playOrder'] = self.playOrder
|
||||
self.playOrder += 1
|
||||
navLabelTag = Tag(soup, 'navLabel')
|
||||
textTag = Tag(soup, 'text')
|
||||
textTag.insert(0, NavigableString('%s' % tocTitle))
|
||||
navLabelTag.insert(0, textTag)
|
||||
nptc = 0
|
||||
navPointTag.insert(nptc, navLabelTag)
|
||||
nptc += 1
|
||||
contentTag = Tag(soup,"content")
|
||||
contentTag['src'] = "%s#section_start" % HTML_file
|
||||
navPointTag.insert(nptc, contentTag)
|
||||
nptc += 1
|
||||
|
||||
# Create an NCX article entry for each populated month
|
||||
# Loop over the booksByDate list, find start of each month,
|
||||
# add description_preview_count titles
|
||||
# master_month_list(list,date,count)
|
||||
current_titles_list = []
|
||||
master_month_list = []
|
||||
current_date = self.booksByDate[0]['timestamp']
|
||||
|
||||
for book in self.booksByDate:
|
||||
if book['timestamp'].month != current_date.month or \
|
||||
book['timestamp'].year != current_date.year:
|
||||
# Save the old lists
|
||||
add_to_master_month_list(current_titles_list)
|
||||
|
||||
# Start the new list
|
||||
current_date = book['timestamp'].date()
|
||||
current_titles_list = [book['title']]
|
||||
else:
|
||||
current_titles_list.append(book['title'])
|
||||
|
||||
# Add the last month list
|
||||
add_to_master_month_list(current_titles_list)
|
||||
|
||||
# Add *article* entries for each populated month
|
||||
# master_months_list{}: [0]:titles list [1]:date
|
||||
for books_by_month in master_month_list:
|
||||
datestr = strftime(u'%B %Y', books_by_month[1].timetuple())
|
||||
navPointByMonthTag = Tag(soup, 'navPoint')
|
||||
navPointByMonthTag['class'] = "article"
|
||||
navPointByMonthTag['id'] = "%s-%s-ID" % (books_by_month[1].year,books_by_month[1].month )
|
||||
navPointTag['playOrder'] = self.playOrder
|
||||
self.playOrder += 1
|
||||
navLabelTag = Tag(soup, 'navLabel')
|
||||
textTag = Tag(soup, 'text')
|
||||
textTag.insert(0, NavigableString(datestr))
|
||||
navLabelTag.insert(0, textTag)
|
||||
navPointByMonthTag.insert(0,navLabelTag)
|
||||
contentTag = Tag(soup, 'content')
|
||||
contentTag['src'] = "%s#%s-%s" % (HTML_file,
|
||||
books_by_month[1].year,books_by_month[1].month)
|
||||
|
||||
navPointByMonthTag.insert(1,contentTag)
|
||||
|
||||
if self.generateForKindle:
|
||||
cmTag = Tag(soup, '%s' % 'calibre:meta')
|
||||
cmTag['name'] = "description"
|
||||
cmTag.insert(0, NavigableString(books_by_month[0]))
|
||||
navPointByMonthTag.insert(2, cmTag)
|
||||
|
||||
cmTag = Tag(soup, '%s' % 'calibre:meta')
|
||||
cmTag['name'] = "author"
|
||||
navStr = '%d titles' % books_by_month[2] if books_by_month[2] > 1 else \
|
||||
'%d title' % books_by_month[2]
|
||||
cmTag.insert(0, NavigableString(navStr))
|
||||
navPointByMonthTag.insert(3, cmTag)
|
||||
|
||||
navPointTag.insert(nptc, navPointByMonthTag)
|
||||
nptc += 1
|
||||
|
||||
# Add this section to the body
|
||||
body.insert(btc, navPointTag)
|
||||
btc += 1
|
||||
self.ncxSoup = soup
|
||||
|
||||
def generateNCXByGenre(self, tocTitle):
|
||||
# Create an NCX section for 'By Genre'
|
||||
# Add each genre as an article
|
||||
# 'tag', 'file', 'authors'
|
||||
|
||||
self.opts.log.info(self.updateProgressFullStep("generateNCXByTags()"))
|
||||
self.updateProgressFullStep("NCX 'Genres'")
|
||||
|
||||
|
||||
|
||||
|
||||
if not len(self.genres):
|
||||
self.opts.log.warn(" No genres found in tags.\n"
|
||||
@ -1980,7 +2190,7 @@ class EPUB_MOBI(CatalogPlugin):
|
||||
for title in genre['books']:
|
||||
titles.append(title['title'])
|
||||
titles = sorted(titles, key=lambda x:(self.generateSortTitle(x),self.generateSortTitle(x)))
|
||||
titles_list = self.generateShortDescription(" • ".join(titles))
|
||||
titles_list = self.generateShortDescription(u" • ".join(titles))
|
||||
cmTag.insert(0, NavigableString(self.formatNCXText(titles_list)))
|
||||
|
||||
navPointVolumeTag.insert(3, cmTag)
|
||||
@ -1996,8 +2206,7 @@ class EPUB_MOBI(CatalogPlugin):
|
||||
self.ncxSoup = ncx_soup
|
||||
|
||||
def writeNCX(self):
|
||||
|
||||
self.opts.log.info(self.updateProgressFullStep("writeNCX()"))
|
||||
self.updateProgressFullStep("Saving NCX")
|
||||
|
||||
outfile = open("%s/%s.ncx" % (self.catalogPath, self.basename), 'w')
|
||||
outfile.write(self.ncxSoup.prettify())
|
||||
@ -2061,6 +2270,13 @@ class EPUB_MOBI(CatalogPlugin):
|
||||
# Remove the special marker tags from the database's tag list,
|
||||
# return sorted list of tags representing valid genres
|
||||
|
||||
def next_tag(tags):
|
||||
for (i, tag) in enumerate(tags):
|
||||
if i < len(tags) - 1:
|
||||
yield tag + ", "
|
||||
else:
|
||||
yield tag
|
||||
|
||||
filtered_tags = []
|
||||
for tag in tags:
|
||||
if tag[0] in self.markerTags:
|
||||
@ -2084,9 +2300,16 @@ class EPUB_MOBI(CatalogPlugin):
|
||||
else:
|
||||
continue
|
||||
if self.verbose:
|
||||
self.opts.log.info(' %d Genre tags in database (exclude_genre: %s):' % \
|
||||
self.opts.log.info(u' %d Genre tags in database (exclude_genre: %s):' % \
|
||||
(len(filtered_tags), self.opts.exclude_genre))
|
||||
self.opts.log.info(' %s' % ', '.join(filtered_tags))
|
||||
out_buf = ''
|
||||
|
||||
for tag in next_tag(filtered_tags):
|
||||
out_buf += tag
|
||||
if len(out_buf) > 72:
|
||||
self.opts.log(u' %s' % out_buf.rstrip())
|
||||
out_buf = ''
|
||||
self.opts.log(u' %s' % out_buf)
|
||||
|
||||
return filtered_tags
|
||||
|
||||
@ -2288,9 +2511,28 @@ class EPUB_MOBI(CatalogPlugin):
|
||||
titleTag.insert(0,escape(NavigableString(title)))
|
||||
return soup
|
||||
|
||||
def generate_masthead_image(self, out_path):
|
||||
MI_WIDTH = 600
|
||||
MI_HEIGHT = 60
|
||||
|
||||
try:
|
||||
from PIL import Image, ImageDraw, ImageFont
|
||||
Image, ImageDraw, ImageFont
|
||||
except ImportError:
|
||||
import Image, ImageDraw, ImageFont
|
||||
|
||||
img = Image.new('RGB', (MI_WIDTH, MI_HEIGHT), 'white')
|
||||
draw = ImageDraw.Draw(img)
|
||||
font = ImageFont.truetype(P('fonts/liberation/LiberationSerif-Bold.ttf'), 48)
|
||||
text = self.title.encode('utf-8')
|
||||
width, height = draw.textsize(text, font=font)
|
||||
left = max(int((MI_WIDTH - width)/2.), 0)
|
||||
top = max(int((MI_HEIGHT - height)/2.), 0)
|
||||
draw.text((left, top), text, fill=(0,0,0), font=font)
|
||||
img.save(open(out_path, 'wb'), 'GIF')
|
||||
|
||||
def generateShortDescription(self, description):
|
||||
# Truncate the description to description_clip, on word boundaries if necessary
|
||||
|
||||
if not description:
|
||||
return None
|
||||
|
||||
@ -2302,7 +2544,7 @@ class EPUB_MOBI(CatalogPlugin):
|
||||
|
||||
# Start adding words until we reach description_clip
|
||||
short_description = ""
|
||||
words = description.split(" ")
|
||||
words = description.split()
|
||||
for word in words:
|
||||
short_description += word + " "
|
||||
if len(short_description) > self.descriptionClip:
|
||||
@ -2322,6 +2564,7 @@ class EPUB_MOBI(CatalogPlugin):
|
||||
|
||||
for (i,word) in enumerate(title_words):
|
||||
# Leading numbers optionally translated to text equivalent
|
||||
# Capitalize leading sort word
|
||||
if i==0:
|
||||
if self.opts.numbers_as_text and re.search('[0-9]+',word):
|
||||
translated.append(EPUB_MOBI.NumberToText(word).text.capitalize())
|
||||
@ -2339,7 +2582,7 @@ class EPUB_MOBI(CatalogPlugin):
|
||||
word = '%10.2f' % float(re.sub('[^\d\.]','.',word))
|
||||
except:
|
||||
word = '%10.2f' % float(EPUB_MOBI.NumberToText(word).number_as_float)
|
||||
translated.append(word)
|
||||
translated.append(word.capitalize())
|
||||
else:
|
||||
if re.search('[0-9]+',word):
|
||||
# Coerce standard-width strings for numbers
|
||||
@ -2400,12 +2643,12 @@ class EPUB_MOBI(CatalogPlugin):
|
||||
self.opts.log.info('%s not implemented' % self.error)
|
||||
|
||||
def updateProgressFullStep(self, description):
|
||||
|
||||
self.current_step += 1
|
||||
self.progressString = description
|
||||
self.progressInt = float((self.current_step-1)/self.total_steps)
|
||||
self.reporter(self.progressInt/100., self.progressString)
|
||||
return u"%.2f%% %s" % (self.progressInt, self.progressString)
|
||||
self.reporter(self.progressInt, self.progressString)
|
||||
if self.opts.cli_environment:
|
||||
self.opts.log(u"%3.0f%% %s" % (self.progressInt*100, self.progressString))
|
||||
|
||||
def updateProgressMicroStep(self, description, micro_step_pct):
|
||||
step_range = 100/self.total_steps
|
||||
@ -2413,45 +2656,49 @@ class EPUB_MOBI(CatalogPlugin):
|
||||
coarse_progress = float((self.current_step-1)/self.total_steps)
|
||||
fine_progress = float((micro_step_pct*step_range)/100)
|
||||
self.progressInt = coarse_progress + fine_progress
|
||||
self.reporter(self.progressInt/100., self.progressString)
|
||||
return u"%.2f%% %s" % (self.progressInt, self.progressString)
|
||||
self.reporter(self.progressInt, self.progressString)
|
||||
|
||||
def run(self, path_to_output, opts, db, notification=DummyReporter()):
|
||||
|
||||
opts.log = log = Log()
|
||||
opts.fmt = self.fmt = path_to_output.rpartition('.')[2]
|
||||
self.opts = opts
|
||||
|
||||
# Add local options
|
||||
opts.creator = "calibre"
|
||||
opts.descriptionClip = 250
|
||||
op = self.opts.output_profile
|
||||
if op is None:
|
||||
op = 'default'
|
||||
opts.descriptionClip = 380 if op.endswith('dx') or 'kindle' not in op else 90
|
||||
opts.basename = "Catalog"
|
||||
opts.plugin_path = self.plugin_path
|
||||
opts.cli_environment = getattr(opts,'sync',True)
|
||||
|
||||
if opts.verbose:
|
||||
opts_dict = vars(opts)
|
||||
log("%s:run" % self.name)
|
||||
log(" path_to_output: %s" % path_to_output)
|
||||
log(" Output format: %s" % self.fmt)
|
||||
log("%s(): Generating %s for %s in %s environment" %
|
||||
(self.name,self.fmt,opts.output_profile,
|
||||
'CLI' if opts.cli_environment else 'GUI'))
|
||||
if opts_dict['ids']:
|
||||
log(" Book count: %d" % len(opts_dict['ids']))
|
||||
# Display opts
|
||||
keys = opts_dict.keys()
|
||||
keys.sort()
|
||||
log(" opts:")
|
||||
|
||||
for key in keys:
|
||||
if key == 'ids':
|
||||
if opts_dict[key]:
|
||||
continue
|
||||
else:
|
||||
log(" %s: (all)" % key)
|
||||
log(" %s: %s" % (key, opts_dict[key]))
|
||||
if key in ['catalog_title','exclude_genre','exclude_tags','note_tag',
|
||||
'numbers_as_text','read_tag','search_text','sort_by','sync']:
|
||||
log(" %s: %s" % (key, opts_dict[key]))
|
||||
|
||||
# Launch the Catalog builder
|
||||
catalog = self.CatalogBuilder(db, opts, self, notification=notification)
|
||||
if opts.verbose:
|
||||
log.info("Begin generating catalog source")
|
||||
catalog = self.CatalogBuilder(db, opts, self, report_progress=notification)
|
||||
catalog.createDirectoryStructure()
|
||||
catalog.copyResources()
|
||||
catalog.buildSources()
|
||||
if opts.verbose:
|
||||
log.info("Finished generating catalog source\n")
|
||||
|
||||
recommendations = []
|
||||
|
||||
|
@ -84,6 +84,7 @@ if not _run_once:
|
||||
return res
|
||||
|
||||
os.path.abspath = my_abspath
|
||||
|
||||
_join = os.path.join
|
||||
def my_join(a, *p):
|
||||
encoding=sys.getfilesystemencoding()
|
||||
|
@ -52,10 +52,13 @@ class BaseJob(object):
|
||||
else:
|
||||
self._status_text = _('Error') if self.failed else _('Finished')
|
||||
if DEBUG:
|
||||
prints('Job:', self.id, self.description, 'finished',
|
||||
try:
|
||||
prints('Job:', self.id, self.description, 'finished',
|
||||
safe_encode=True)
|
||||
prints('\t'.join(self.details.splitlines(True)),
|
||||
prints('\t'.join(self.details.splitlines(True)),
|
||||
safe_encode=True)
|
||||
except:
|
||||
pass
|
||||
if not self._done_called:
|
||||
self._done_called = True
|
||||
try:
|
||||
|
@ -274,6 +274,10 @@ class BasicNewsRecipe(Recipe):
|
||||
}
|
||||
'''
|
||||
|
||||
#: By default, calibre will use a default image for the masthead (Kindle only).
|
||||
#: Override this in your recipe to provide a url to use as a masthead.
|
||||
masthead_url = None
|
||||
|
||||
#: Set to a non empty string to disable this recipe
|
||||
#: The string will be used as the disabled message
|
||||
recipe_disabled = None
|
||||
@ -294,6 +298,17 @@ class BasicNewsRecipe(Recipe):
|
||||
'''
|
||||
return getattr(self, 'cover_url', None)
|
||||
|
||||
def get_masthead_url(self):
|
||||
'''
|
||||
Return a :term:`URL` to the masthead image for this issue or `None`.
|
||||
By default it returns the value of the member `self.masthead_url` which
|
||||
is normally `None`. If you want your recipe to download a masthead for the e-book
|
||||
override this method in your subclass, or set the member variable `self.masthead_url`
|
||||
before this method is called.
|
||||
Masthead images are used in Kindle MOBI files.
|
||||
'''
|
||||
return getattr(self, 'masthead_url', None)
|
||||
|
||||
def get_feeds(self):
|
||||
'''
|
||||
Return a list of :term:`RSS` feeds to fetch for this profile. Each element of the list
|
||||
@ -543,8 +558,6 @@ class BasicNewsRecipe(Recipe):
|
||||
'--max-recursions', str(self.recursions),
|
||||
'--delay', str(self.delay),
|
||||
]
|
||||
if self.encoding is not None:
|
||||
web2disk_cmdline.extend(['--encoding', self.encoding])
|
||||
|
||||
if self.verbose:
|
||||
web2disk_cmdline.append('--verbose')
|
||||
@ -563,6 +576,7 @@ class BasicNewsRecipe(Recipe):
|
||||
'preprocess_html', 'remove_tags_after', 'remove_tags_before'):
|
||||
setattr(self.web2disk_options, extra, getattr(self, extra))
|
||||
self.web2disk_options.postprocess_html = self._postprocess_html
|
||||
self.web2disk_options.encoding = self.encoding
|
||||
|
||||
if self.delay > 0:
|
||||
self.simultaneous_downloads = 1
|
||||
@ -745,6 +759,23 @@ class BasicNewsRecipe(Recipe):
|
||||
|
||||
self.report_progress(0, _('Trying to download cover...'))
|
||||
self.download_cover()
|
||||
self.report_progress(0, _('Generating masthead...'))
|
||||
self.masthead_path = None
|
||||
try:
|
||||
murl = self.get_masthead_url()
|
||||
except:
|
||||
self.log.exception('Failed to get masthead url')
|
||||
murl = None
|
||||
if murl is not None:
|
||||
self.download_masthead(murl)
|
||||
if self.masthead_path is None:
|
||||
self.masthead_path = os.path.join(self.output_dir, 'mastheadImage.jpg')
|
||||
try:
|
||||
self.default_masthead_image(self.masthead_path)
|
||||
except:
|
||||
self.log.exception('Failed to generate default masthead image')
|
||||
self.masthead_path = None
|
||||
|
||||
if self.test:
|
||||
feeds = feeds[:2]
|
||||
self.has_single_feed = len(feeds) == 1
|
||||
@ -861,6 +892,32 @@ class BasicNewsRecipe(Recipe):
|
||||
self.log.exception('Failed to download cover')
|
||||
self.cover_path = None
|
||||
|
||||
def _download_masthead(self, mu):
|
||||
ext = mu.rpartition('.')[-1]
|
||||
if '?' in ext:
|
||||
ext = ''
|
||||
ext = ext.lower() if ext else 'jpg'
|
||||
mpath = os.path.join(self.output_dir, 'masthead_source.'+ext)
|
||||
outfile = os.path.join(self.output_dir, 'mastheadImage.jpg')
|
||||
if os.access(mu, os.R_OK):
|
||||
with open(mpath, 'wb') as mfile:
|
||||
mfile.write(open(mu, 'rb').read())
|
||||
else:
|
||||
with nested(open(mpath, 'wb'), closing(self.browser.open(mu))) as (mfile, r):
|
||||
mfile.write(r.read())
|
||||
self.report_progress(1, _('Masthead image downloaded'))
|
||||
self.prepare_masthead_image(mpath, outfile)
|
||||
self.masthead_path = outfile
|
||||
if os.path.exists(mpath):
|
||||
os.remove(mpath)
|
||||
|
||||
|
||||
def download_masthead(self, url):
|
||||
try:
|
||||
self._download_masthead(url)
|
||||
except:
|
||||
self.log.exception("Failed to download supplied masthead_url, synthesizing")
|
||||
|
||||
def default_cover(self, cover_file):
|
||||
'''
|
||||
Create a generic cover for recipes that dont have a cover
|
||||
@ -928,6 +985,9 @@ class BasicNewsRecipe(Recipe):
|
||||
'Override in subclass to use something other than the recipe title'
|
||||
return self.title
|
||||
|
||||
MI_WIDTH = 600
|
||||
MI_HEIGHT = 60
|
||||
|
||||
def default_masthead_image(self, out_path):
|
||||
try:
|
||||
from PIL import Image, ImageDraw, ImageFont
|
||||
@ -935,14 +995,13 @@ class BasicNewsRecipe(Recipe):
|
||||
except ImportError:
|
||||
import Image, ImageDraw, ImageFont
|
||||
|
||||
|
||||
img = Image.new('RGB', (600, 100), 'white')
|
||||
img = Image.new('RGB', (self.MI_WIDTH, self.MI_HEIGHT), 'white')
|
||||
draw = ImageDraw.Draw(img)
|
||||
font = ImageFont.truetype(P('fonts/liberation/LiberationSerif-Bold.ttf'), 48)
|
||||
text = self.get_masthead_title().encode('utf-8')
|
||||
width, height = draw.textsize(text, font=font)
|
||||
left = max(int((600 - width)/2.), 0)
|
||||
top = max(int((100 - height)/2.), 0)
|
||||
left = max(int((self.MI_WIDTH - width)/2.), 0)
|
||||
top = max(int((self.MI_HEIGHT - height)/2.), 0)
|
||||
draw.text((left, top), text, fill=(0,0,0), font=font)
|
||||
img.save(open(out_path, 'wb'), 'JPEG')
|
||||
|
||||
@ -964,11 +1023,11 @@ class BasicNewsRecipe(Recipe):
|
||||
raise IOError('Failed to read image from: %s: %s'
|
||||
%(path_to_image, msg))
|
||||
pw.PixelSetColor(p, 'white')
|
||||
width, height = pw.MagickGetImageWidth(img),pw.MagickGetImageHeight(img)[1:]
|
||||
scaled, nwidth, nheight = fit_image(width, height, 600, 100)
|
||||
width, height = pw.MagickGetImageWidth(img),pw.MagickGetImageHeight(img)
|
||||
scaled, nwidth, nheight = fit_image(width, height, self.MI_WIDTH, self.MI_HEIGHT)
|
||||
if not pw.MagickNewImage(img2, width, height, p):
|
||||
raise RuntimeError('Out of memory')
|
||||
if not pw.MagickNewImage(frame, 600, 100, p):
|
||||
if not pw.MagickNewImage(frame, self.MI_WIDTH, self.MI_HEIGHT, p):
|
||||
raise RuntimeError('Out of memory')
|
||||
if not pw.MagickCompositeImage(img2, img, pw.OverCompositeOp, 0, 0):
|
||||
raise RuntimeError('Out of memory')
|
||||
@ -976,8 +1035,8 @@ class BasicNewsRecipe(Recipe):
|
||||
if not pw.MagickResizeImage(img2, nwidth, nheight, pw.LanczosFilter,
|
||||
0.5):
|
||||
raise RuntimeError('Out of memory')
|
||||
left = int((600 - nwidth)/2.0)
|
||||
top = int((100 - nheight)/2.0)
|
||||
left = int((self.MI_WIDTH - nwidth)/2.0)
|
||||
top = int((self.MI_HEIGHT - nheight)/2.0)
|
||||
if not pw.MagickCompositeImage(frame, img2, pw.OverCompositeOp,
|
||||
left, top):
|
||||
raise RuntimeError('Out of memory')
|
||||
@ -988,7 +1047,6 @@ class BasicNewsRecipe(Recipe):
|
||||
for x in (img, img2, frame):
|
||||
pw.DestroyMagickWand(x)
|
||||
|
||||
|
||||
def create_opf(self, feeds, dir=None):
|
||||
if dir is None:
|
||||
dir = self.output_dir
|
||||
@ -1003,11 +1061,22 @@ class BasicNewsRecipe(Recipe):
|
||||
mi.pubdate = datetime.now()
|
||||
opf_path = os.path.join(dir, 'index.opf')
|
||||
ncx_path = os.path.join(dir, 'index.ncx')
|
||||
|
||||
opf = OPFCreator(dir, mi)
|
||||
# Add mastheadImage entry to <guide> section
|
||||
mp = getattr(self, 'masthead_path', None)
|
||||
if mp is not None and os.access(mp, os.R_OK):
|
||||
from calibre.ebooks.metadata.opf2 import Guide
|
||||
ref = Guide.Reference(os.path.basename(self.masthead_path), os.getcwdu())
|
||||
ref.type = 'masthead'
|
||||
ref.title = 'Masthead Image'
|
||||
opf.guide.append(ref)
|
||||
|
||||
manifest = [os.path.join(dir, 'feed_%d'%i) for i in range(len(feeds))]
|
||||
manifest.append(os.path.join(dir, 'index.html'))
|
||||
manifest.append(os.path.join(dir, 'index.ncx'))
|
||||
|
||||
# Get cover
|
||||
cpath = getattr(self, 'cover_path', None)
|
||||
if cpath is None:
|
||||
pf = open(os.path.join(dir, 'cover.jpg'), 'wb')
|
||||
@ -1016,10 +1085,18 @@ class BasicNewsRecipe(Recipe):
|
||||
if cpath is not None and os.access(cpath, os.R_OK):
|
||||
opf.cover = cpath
|
||||
manifest.append(cpath)
|
||||
|
||||
# Get masthead
|
||||
mpath = getattr(self, 'masthead_path', None)
|
||||
if mpath is not None and os.access(mpath, os.R_OK):
|
||||
manifest.append(mpath)
|
||||
|
||||
opf.create_manifest_from_files_in(manifest)
|
||||
for mani in opf.manifest:
|
||||
if mani.path.endswith('.ncx'):
|
||||
mani.id = 'ncx'
|
||||
if mani.path.endswith('mastheadImage.jpg'):
|
||||
mani.id = 'masthead-image'
|
||||
|
||||
entries = ['index.html']
|
||||
toc = TOC(base_path=dir)
|
||||
|
Loading…
x
Reference in New Issue
Block a user