From 18a208a9f4d1171e1016aedc7d44a8fe58105486 Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Wed, 30 Dec 2009 10:48:46 -0700 Subject: [PATCH 01/17] New recipes for Pro Publica, Big Government, El Universal Impressa and Journal of Accountacy by kwetal --- resources/images/news/biggovernment.png | Bin 0 -> 1578 bytes resources/images/news/eluniversal.png | Bin 0 -> 878 bytes resources/images/news/propublica.png | Bin 0 -> 1406 bytes resources/recipes/biggovernment.recipe | 28 ++++++ resources/recipes/eluniversalimpresa.recipe | 82 ++++++++++++++++++ resources/recipes/journalofaccountancy.recipe | 44 ++++++++++ resources/recipes/propublica.recipe | 60 +++++++++++++ 7 files changed, 214 insertions(+) create mode 100644 resources/images/news/biggovernment.png create mode 100644 resources/images/news/eluniversal.png create mode 100644 resources/images/news/propublica.png create mode 100644 resources/recipes/biggovernment.recipe create mode 100644 resources/recipes/eluniversalimpresa.recipe create mode 100644 resources/recipes/journalofaccountancy.recipe create mode 100644 resources/recipes/propublica.recipe diff --git a/resources/images/news/biggovernment.png b/resources/images/news/biggovernment.png new file mode 100644 index 0000000000000000000000000000000000000000..d5c2442ebbcd3086bb235a7811e9b341f4fdf7a0 GIT binary patch literal 1578 zcmV+_2G#kAP)}^jtm{GfcWYzf#m5jjP?cXv@@NV>xaFL_w;ta(UnZ* z-gDMo>%Z6fugC3Jg48K>@HD&-ugA-Aa=h-~2p++2u?4%ToM)C|yR$^+Yw#hg#j~*j z%UU+(FpYoVVcd@IV}F%%@Bac&r!*8d{uthdQ*q)FF13I?*pBbvrl*R=q5$fYMsX27 zj#otZ2c68L7dAQ=Uc}x%@FRQ^+ZT(*aRBO+mf;e738xP@oy9I}#h)>S5v;;lI1?wu zvqt7n<63MT1koLEK7x1S%UIodHG_NcQ~VLz@Nkv$e4HB({paH?csrgKQR(8c`2X2R z&G8Wkvb_#pi?BDc8@FIXmGc9kcWT^vMci;V{#NDuc%9OTcq=}LH)6D9>sz?0%K6Y@ z08S3)?`<7;6yLygRnAB1l+MSSaG^rb2Eb!@2%GVfD(9&>rE~Dbh-ee(JqoFBsd8Qr zAyTI_jP>{^o*v4N<2$&v%K2!W(i!+XK7yoQhFzs6oP#%@cj?5$WR>%dOD86_;=D+W zh{iK;N4%%eZ!~1Z1m2l-_Ro6NC0hQ2nVX1=jxQ!p5cM~h?nVeNz;i>P7c76MPN^G2dQA{fozl7ZRD|_V=nu!uZ?03?5KO-U zWB3K`L4^|nzy+Z{-HpSiwQ@S6^z;XnQd%3SnvVAg7h}2Op4AXi87z}k&a-t&XU3uFFeVJ$8fO;bStmaK5(lv^Qkuj5E@eifR=Sp> z(~S=+Tct7|AmEvp2+}nr`v5)>9GDx3z*JPsg+a)b5!r*-KS0_Q4S?Bj*tE!|uZwpx zfw+;=@QyG(+d4Oj&2>t5<4*y4F3yh)5VjW)?RAxM+8YYpAj(OhxEucrXN|lJUr@Rs z#{$1eX-FG`h>r%yasBTayh57RVa7R3Uk7K9n zUi`Y*lA2u3C=FyiUL0u8#?^I7Q&rBB(NSunp^<-8>pmRI7naoh3Gc}&@kZmV+M8S~`|d^#@F$W~ll<$Qnh z;Fy)HPU%JXivCm-p}r3r;%0vh-H`y=!%UU)LacLZ@UDo|s+OHS_#|#@HmSt`3@MDi zMp-->=_&o-UfhX4C@c6v@L*J#8?REfsx|t1MdSS2xMpxeUTi5H3PYd6oAv9Tew*GG zMi;`yGNsJM0!-U(#&>ajmGcvW{+?_tA5zBcmC6`B#lQl)1UaP4jq8-%;_zZhPx;HJ zPH9Z(2Oq|(<91{Evtm$Yl*zdlKURL2Y-{KIe*zF7hLzcHhQfmKZ^a-RNg_Wun7pw8)NpvDyd001R)MObuXVRU6WV{&C-bY%cCFfchSF)}SPFjO%x zIx;vqGBYhOH##sdgFgH3dFc_rvVgLXD07*qoM6N<$f?x~rLI3~& literal 0 HcmV?d00001 diff --git a/resources/images/news/eluniversal.png b/resources/images/news/eluniversal.png new file mode 100644 index 0000000000000000000000000000000000000000..cd970ab9e50469124ce47e4c19faa6bec066842c GIT binary patch literal 878 zcmeAS@N?(olHy`uVBq!ia0vp^0wB!61|;P_|4#%`Y)RhkE)4%caKYZ?lYt_f1s;*b zK-vS0-A-oPfdtD69Mgd`SU*F|v9*U87?`Gex;TbdoL)L1)8lfX#Buw}`)}XLn4Eda z<&;?ElCt_N|Z`$2nUOU{xZdfT|E$bkH`(>T%B_| z%N9HmPV1h!CG=rO`b~|J&TyAsvkbVhqAomgy?T`Q&YH}pUs|gsU1JxPe&>I3v)`@V zYh`YTocwrYr;md5`;`iI@lU4j5q9DzTzpGsT8e1rlF9up38zXObLJT=y(d)bT=r32 z#EIkLt=ywlZ&Knq?zenWoISbiSw7G07@PY0rE%(;+dIYfq~eP$S`0Q!HG5vlNjOc5ouiqd z^7l?MLs0mvh{_j?wttP(7SB5cYTh_KR$||IL%e^=2o+kphSlLz}ps_pPSW0*&a9_U+_b|jwwz) zv2H=)zpcRZs#@Y2QIe8al4_M)lnSI6j0_Acbq$SljSWH!EUgSJt$nC}Q!>*kku?|^Ss9yJ85uw{e0Ssd3e>>h>FVdQ&MBb@00h!~jsO4v literal 0 HcmV?d00001 diff --git a/resources/images/news/propublica.png b/resources/images/news/propublica.png new file mode 100644 index 0000000000000000000000000000000000000000..02954be4ea36161044ad0896bb01d110f5a91157 GIT binary patch literal 1406 zcmV-^1%djBP)u(fQ7>A$Pncbbebm`V^ufT3=?QV^i+D1W&sS>ZC0#(1@HxrG8G7t{mIe;TaeqnV> z3txQpDNa#j>ii^5rxW3JcR^oYKOemR9>==73Dw<8Fj$X6mWjn;l-yH#`v4S0VaK~W zIIwRomA+CMLLtg4D@pu$k?>%Ia|2;i-Ml>jd%oSnu3ev#oSLE`SWitL06-)XAv_cz zHZh6A;h?>}{htNs{`Dw3cYa8J?{C!j{XF!*{WxTq3m2y7>Fp&F`vXi(9ox6F zX3bOA22d0QRaLRsY`9#mzZH%~qkQtw$8>dd5vZ!jr+a34n*MWP&JBc_zI2&TsDX}- zw^_eoeg1n30z{)xQmHi4msRv^7A-ZKf3~!`iuz!%ApWIwFETthKtr&e`upn8v@DTG zguZZ?@%R)=+)m!uyp=6mHnViuvVwP|tA!JZBxz&>tBhFW#b%W$GSUDTYKo!Z2q%sm zr|q>@ak*S5ib6wefST$`irg;7#wYka9L}fv^-Y`Dymc$h%}ontFQ0+FzJB8IIC7~U zXR$9IV6_MU$aWjD1Q|`|d@M}w>Eo38%JBJ0si~>qo3Fm)@S#JDjEymU=`yQZ9_Ou& zcW8ZL)iv#t0LDf~2@ejjJlMn%ZwZR3qw6_mblpT01+-KK!zSXiOVouPBt4rTol4aP0Y;4BQ4>fZ3)G1c1TuF0t6K$`w-IO?h<;*DiEiXBfJ7p>cmP?JF|_&AdmLgxK*kivYT1IqyaJ+T z4Q6I$@Oq0E58w|3m>3@|nDjU#WO3e(j48~m+(1$F{CsLQ$#gPFd3pKb0h*he7>y2+ zQB^D!G4FA)Th6U=t_IR`R_VHlW|&w+iJ|_}ghHWfZZy{g0H~;_U}T^VtCZVlAlG)q zvV=_(@(_?~OP5V#HhGcmpMT=z4eJ*zo(EXFZY?9>KBgvfRygevvQ@z25Gixp@H!-l zWeJZ%!Xry$Q%Qc@_cg0pT5h`UuO9icKv5Jr_a7kC_$XEWx_rM|EFu|A#}q^aQztn! z$>ATqqokySZEtS7CGi4)E2FdPAZ}U4SG5$Mw+Pc&N@^xS{QLx?(E)mTdT3qqG;5!K z=9a}51OP}R5}Z77lCkkIGMNmSbQ-%X6AXn|vEs2?D!Lm2xRc|*_h3 zCB9Jr001R)MObuXVRU6WV{&C-bY%cCFfchSF)}SPFjO%xIx;vqGBYhOH##sd= 0: + return None + + main, sep, id = url.rpartition('/') + + return main + '/vi_' + id + + def preprocess_html(self, soup): + table = soup.find('table') + table.extract() + + for p in soup.findAll('p'): + if self.tag_to_string(p).strip() == '': + p.extract() + + tag = soup.find('font', attrs = {'color': '#0F046A'}) + if tag: + for attr in ['color', 'face', 'helvetica,', 'sans-serif', 'size']: + if tag.has_key(attr): + del tag[attr] + tag.name = 'h1' + + return soup diff --git a/resources/recipes/journalofaccountancy.recipe b/resources/recipes/journalofaccountancy.recipe new file mode 100644 index 0000000000..51a6ac8d29 --- /dev/null +++ b/resources/recipes/journalofaccountancy.recipe @@ -0,0 +1,44 @@ +from calibre.web.feeds.news import BasicNewsRecipe + +class JournalOfAccountancyRecipe(BasicNewsRecipe): + __license__ = 'GPL v3' + __author__ = 'kwetal' + language = 'en' + version = 1 + + title = u'Journal of Accountancy' + publisher = u'AICPA' + category = u'News, Accountancy' + description = u'Publication of the American Institute of Certified Public Accountants' + + use_embedded_content = False + remove_empty_feeds = True + oldest_article = 30 + max_articles_per_feed = 100 + + no_stylesheets = True + remove_javascript = True + + extra_css = ''' + body{font-family:verdana,arial,helvetica,geneva,sans-serif;} + div#Rubricname {font-size: small; color: #666666; margin-bottom: 1em;} + div#Headline {font-size: x-large; font-weight: bold; margin-bottom: 0.6em} + div#SubHeadline {font-size: medium; font-weight: bold; margin-bottom: 1em} + div#Authorname, div#Date {font-size: x-small; color: #696969;} + ''' + + conversion_options = {'comments': description, 'tags': category, 'language': 'en', + 'publisher': publisher} + + keep_only_tags = [] + keep_only_tags.append(dict(name = 'div', attrs = {'id': 'Rubricname'})) + keep_only_tags.append(dict(name = 'div', attrs = {'id': 'Headline'})) + keep_only_tags.append(dict(name = 'div', attrs = {'id': 'SubHeadline'})) + keep_only_tags.append(dict(name = 'div', attrs = {'id': 'Authorname'})) + keep_only_tags.append(dict(name = 'div', attrs = {'id': 'Date'})) + keep_only_tags.append(dict(name = 'div', attrs = {'id': 'BodyContent'})) + + remove_attributes = ['style'] + + feeds = [] + feeds.append((u'Journal of Accountancy', u'http://feeds2.feedburner.com/JournalOfAccountancy')) diff --git a/resources/recipes/propublica.recipe b/resources/recipes/propublica.recipe new file mode 100644 index 0000000000..1e1f0af7a9 --- /dev/null +++ b/resources/recipes/propublica.recipe @@ -0,0 +1,60 @@ +from calibre.web.feeds.news import BasicNewsRecipe +from calibre.ebooks.BeautifulSoup import BeautifulSoup + +class ProPublicaRecipe(BasicNewsRecipe): + __license__ = 'GPL v3' + __author__ = 'kwetal' + language = 'en_US' + version = 1 + + title = u'Pro Publica' + publisher = u'ProPublica.org' + category = u'Political blog' + description = u'Independent investigative journalism in the public interest.' + + oldest_article = 14 + max_articles_per_feed = 100 + use_embedded_content = False + + remove_empty_feeds = True + no_stylesheets = True + remove_javascript = True + + keep_only_tags = [] + keep_only_tags.append(dict(name = 'div', attrs = {'class': 'article'})) + + remove_tags = [] + remove_tags.append(dict(name = 'div', attrs = {'id': 'rollups'})) + remove_tags.append(dict(name = 'div', attrs = {'class': 'follow_info'})) + remove_tags.append(dict(name = 'ul', attrs = {'class': 'long-tools-top'})) + remove_tags.append(dict(name = 'ul', attrs = {'id': 'share-box'})) + remove_tags.append(dict(name = 'div', attrs = {'class': 'tags'})) + remove_tags.append(dict(name = 'ul', attrs = {'class': 'long-tools'})) + remove_tags.append(dict(name = 'ul', attrs = {'id': 'share-box2'})) + remove_tags.append(dict(name = 'p', attrs = {'id': 'original-url'})) + + feeds = [] + feeds.append((u'Top Stories', u'http://feeds.propublica.org/propublica/main')) + feeds.append((u'Stimulus', u'http://feeds.propublica.org/propublica/watchdog/stimulus')) + feeds.append((u'Bailout', u'http://feeds.propublica.org/propublica/watchdog/bailout')) + feeds.append((u'Business', u'http://feeds.propublica.org/propublica/business-money')) + feeds.append((u'Justice', u'http://feeds.propublica.org/propublica/justice-law')) + feeds.append((u'Energy & Environment', u'http://feeds.propublica.org/propublica/energy-environment')) + feeds.append((u'Government & Politics', u'http://feeds.propublica.org/propublica/government-politics')) + feeds.append((u'Health & Science', u'http://feeds.propublica.org/propublica/health-science')) + feeds.append((u'Media & Technology', u'http://feeds.propublica.org/propublica/media-technology')) + feeds.append((u'National Security', u'http://feeds.propublica.org/propublica/national-security')) + #feeds.append((u'', u'')) + + conversion_options = {'comments': description, 'tags': category, 'language': 'en', + 'publisher': publisher} + + extra_css = ''' + body{font-family:verdana,arial,helvetica,geneva,sans-serif;} + img {float: left; margin-right: 0.5em;} + h1 {text-align: left;} + a, a[href] {text-decoration: none; color: blue;} + div.cat {font-size: x-small; color: #666666; margin-bottom: 0.1em;} + div.info {font-size: small; color: #696969;} + ''' + \ No newline at end of file From b433d2db0eefb73e74dbc5fb3c7cb75bf29927b8 Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Wed, 30 Dec 2009 11:13:41 -0700 Subject: [PATCH 02/17] MOBI Input: Change default encoding for files that don't specify a codepage from cp1251 to cp1252 --- src/calibre/ebooks/mobi/reader.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/calibre/ebooks/mobi/reader.py b/src/calibre/ebooks/mobi/reader.py index f26d8c9e2a..1f6c8d6ca5 100644 --- a/src/calibre/ebooks/mobi/reader.py +++ b/src/calibre/ebooks/mobi/reader.py @@ -116,7 +116,7 @@ class BookHeader(object): if ident == 'TEXTREAD': self.codepage = 1252 if len(raw) <= 16: - self.codec = 'cp1251' + self.codec = 'cp1252' self.extra_flags = 0 self.title = _('Unknown') self.language = 'ENGLISH' From a07585ef253b1a595652085d403eb92f8f4607e9 Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Wed, 30 Dec 2009 14:18:44 -0700 Subject: [PATCH 03/17] MOBI metadata: Don't leave around a styles.css file when trying to extract embedded metadata --- src/calibre/ebooks/mobi/reader.py | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/src/calibre/ebooks/mobi/reader.py b/src/calibre/ebooks/mobi/reader.py index 1f6c8d6ca5..1a648c4d9b 100644 --- a/src/calibre/ebooks/mobi/reader.py +++ b/src/calibre/ebooks/mobi/reader.py @@ -21,7 +21,7 @@ except ImportError: from lxml import html, etree -from calibre import entity_to_unicode +from calibre import entity_to_unicode, CurrentDir from calibre.utils.filenames import ascii_filename from calibre.ptempfile import TemporaryDirectory from calibre.ebooks import DRMError @@ -790,11 +790,12 @@ def get_metadata(stream): mi = mh.exth.mi else: with TemporaryDirectory('_mobi_meta_reader') as tdir: - mr = MobiReader(stream, log) - parse_cache = {} - mr.extract_content(tdir, parse_cache) - if mr.embedded_mi is not None: - mi = mr.embedded_mi + with CurrentDir(tdir): + mr = MobiReader(stream, log) + parse_cache = {} + mr.extract_content(tdir, parse_cache) + if mr.embedded_mi is not None: + mi = mr.embedded_mi if hasattr(mh.exth, 'cover_offset'): cover_index = mh.first_image_index + mh.exth.cover_offset data = mh.section_data(int(cover_index)) From a6b078e26b3c96038349af791c80dd269e5ff792 Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Wed, 30 Dec 2009 15:50:49 -0700 Subject: [PATCH 04/17] News downloads: Automagically convert PDF covers to PNG --- src/calibre/ebooks/metadata/pdf.py | 14 +++++++------- src/calibre/web/feeds/news.py | 8 ++++++++ 2 files changed, 15 insertions(+), 7 deletions(-) diff --git a/src/calibre/ebooks/metadata/pdf.py b/src/calibre/ebooks/metadata/pdf.py index 6b4fc70d11..b4bc6f962f 100644 --- a/src/calibre/ebooks/metadata/pdf.py +++ b/src/calibre/ebooks/metadata/pdf.py @@ -3,7 +3,7 @@ __license__ = 'GPL v3' __copyright__ = '2008, Kovid Goyal ' '''Read meta information from PDF files''' -import re +#import re from functools import partial from calibre import prints @@ -12,15 +12,15 @@ from calibre.ebooks.metadata import MetaInformation, string_to_authors, authors_ pdfreflow, pdfreflow_error = plugins['pdfreflow'] -_isbn_pat = re.compile(r'ISBN[: ]*([-0-9Xx]+)') +#_isbn_pat = re.compile(r'ISBN[: ]*([-0-9Xx]+)') def get_metadata(stream, cover=True): if pdfreflow is None: raise RuntimeError(pdfreflow_error) raw = stream.read() - isbn = _isbn_pat.search(raw) - if isbn is not None: - isbn = isbn.group(1).replace('-', '').replace(' ', '') + #isbn = _isbn_pat.search(raw) + #if isbn is not None: + # isbn = isbn.group(1).replace('-', '').replace(' ', '') info = pdfreflow.get_metadata(raw, cover) title = info.get('Title', None) au = info.get('Author', None) @@ -29,8 +29,8 @@ def get_metadata(stream, cover=True): else: au = string_to_authors(au) mi = MetaInformation(title, au) - if isbn is not None: - mi.isbn = isbn + #if isbn is not None: + # mi.isbn = isbn creator = info.get('Creator', None) if creator: diff --git a/src/calibre/web/feeds/news.py b/src/calibre/web/feeds/news.py index 789710d764..4ade7fa73c 100644 --- a/src/calibre/web/feeds/news.py +++ b/src/calibre/web/feeds/news.py @@ -823,6 +823,14 @@ class BasicNewsRecipe(Recipe): cpath = os.path.join(self.output_dir, 'cover.'+ext) with nested(open(cpath, 'wb'), closing(self.browser.open(cu))) as (cfile, r): cfile.write(r.read()) + if ext.lower() == 'pdf': + from calibre.ebook.metadata.pdf import get_metadata + stream = open(cpath, 'rb') + mi = get_metadata(stream) + cpath = None + if mi.cover_data and mi.cover_data[1]: + cpath = os.path.join(self.output_dir, 'cover.png') + open(cpath, 'wb').write(mi.cover_data[1]) self.cover_path = cpath From 81c5bc7d7958ffc603ef72c756098e53b901ecf7 Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Wed, 30 Dec 2009 20:14:33 -0700 Subject: [PATCH 05/17] Browse by tags: Make clicking on a tag cause all other tags to be un selected, unless CTRL or SHIFT is pressed. --- src/calibre/gui2/tag_view.py | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/src/calibre/gui2/tag_view.py b/src/calibre/gui2/tag_view.py index 20c439cbe8..6cf5defda4 100644 --- a/src/calibre/gui2/tag_view.py +++ b/src/calibre/gui2/tag_view.py @@ -9,7 +9,7 @@ Browsing book collection by tags. from itertools import izip -from PyQt4.Qt import Qt, QTreeView, \ +from PyQt4.Qt import Qt, QTreeView, QApplication, \ QFont, SIGNAL, QSize, QIcon, QPoint, \ QAbstractItemModel, QVariant, QModelIndex from calibre.gui2 import config, NONE @@ -36,7 +36,9 @@ class TagsView(QTreeView): self.model().refresh() def toggle(self, index): - if self._model.toggle(index): + modifiers = int(QApplication.keyboardModifiers()) + exclusive = modifiers not in (Qt.CTRL, Qt.SHIFT) + if self._model.toggle(index, exclusive): self.emit(SIGNAL('tags_marked(PyQt_PyObject, PyQt_PyObject)'), self._model.tokens(), self.match_all.isChecked()) @@ -227,12 +229,14 @@ class TagsModel(QAbstractItemModel): return len(parent_item.children) - def reset_all_states(self): + def reset_all_states(self, except_=None): for i in xrange(self.rowCount(QModelIndex())): category_index = self.index(i, 0, QModelIndex()) for j in xrange(self.rowCount(category_index)): tag_index = self.index(j, 0, category_index) tag_item = tag_index.internalPointer() + if tag_item is except_: + continue tag = tag_item.tag if tag.state != 0: tag.state = 0 @@ -248,10 +252,12 @@ class TagsModel(QAbstractItemModel): else: self.ignore_next_search -= 1 - def toggle(self, index): + def toggle(self, index, exclusive): if not index.isValid(): return False item = index.internalPointer() if item.type == TagTreeItem.TAG: + if exclusive: + self.reset_all_states(except_=item) item.toggle() self.ignore_next_search = 2 self.emit(SIGNAL('dataChanged(QModelIndex,QModelIndex)'), index, index) From 7333cc7319304072fed2a9b9c40fcfedd051b07a Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Wed, 30 Dec 2009 20:34:16 -0700 Subject: [PATCH 06/17] New recipe for The Times of India by Krittika Goyal --- resources/recipes/toi.recipe | 54 ++++++++++++++++++++++++++++++++++++ 1 file changed, 54 insertions(+) create mode 100644 resources/recipes/toi.recipe diff --git a/resources/recipes/toi.recipe b/resources/recipes/toi.recipe new file mode 100644 index 0000000000..ed462ae94f --- /dev/null +++ b/resources/recipes/toi.recipe @@ -0,0 +1,54 @@ +from calibre.web.feeds.news import BasicNewsRecipe +from calibre.ebooks.BeautifulSoup import BeautifulSoup + +class TimesOfIndia(BasicNewsRecipe): + title = u'Times of India' + language = 'en_IN' + __author__ = 'Krittika Goyal' + oldest_article = 1 #days + max_articles_per_feed = 25 + + remove_stylesheets = True + remove_tags = [ + dict(name='iframe'), + dict(name='td', attrs={'class':'newptool1'}), + dict(name='div', attrs={'id':'newptool'}), + dict(name='ul', attrs={'class':'newtabcontent_tabs_new'}), + dict(name='b', text='Topics'), + dict(name='span', text=':'), + ] + + feeds = [ +('Top Stories', + 'http://timesofindia.indiatimes.com/rssfeedstopstories.cms'), +('India', + 'http://timesofindia.indiatimes.com/rssfeeds/-2128936835.cms'), +('World', + 'http://timesofindia.indiatimes.com/rssfeeds/296589292.cms'), +('Mumbai', + 'http://timesofindia.indiatimes.com/rssfeeds/-2128838597.cms'), +('Entertainment', + 'http://timesofindia.indiatimes.com/rssfeeds/1081479906.cms'), +('Cricket', + 'http://timesofindia.indiatimes.com/rssfeeds/4719161.cms'), +('Sunday TOI', + 'http://timesofindia.indiatimes.com/rssfeeds/1945062111.cms'), +('Life and Style', + 'http://timesofindia.indiatimes.com/rssfeeds/2886704.cms'), +('Business', + 'http://timesofindia.indiatimes.com/rssfeeds/1898055.cms'), +('Mad Mad World', + 'http://timesofindia.indiatimes.com/rssfeeds/2178430.cms'), +('Most Read', + 'http://timesofindia.indiatimes.com/rssfeedmostread.cms') +] + + def preprocess_html(self, soup): + heading = soup.find(name='h1', attrs={'class':'heading'}) + td = heading.findParent(name='td') + td.extract() + soup = BeautifulSoup('t') + body = soup.find(name='body') + body.insert(0, td) + td.name = 'div' + return soup From 4b66da2fe628d5dcf58a3723ba2ea23a79c8b404 Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Wed, 30 Dec 2009 22:56:07 -0700 Subject: [PATCH 07/17] New recipe for India Today and improved recipe for The Independent --- resources/recipes/independent.recipe | 69 +++++++++++++++++-------- resources/recipes/india_today.recipe | 76 ++++++++++++++++++++++++++++ src/calibre/web/feeds/news.py | 10 ++-- 3 files changed, 132 insertions(+), 23 deletions(-) create mode 100644 resources/recipes/india_today.recipe diff --git a/resources/recipes/independent.recipe b/resources/recipes/independent.recipe index 4d677b58a6..e9e15e2ba9 100644 --- a/resources/recipes/independent.recipe +++ b/resources/recipes/independent.recipe @@ -1,26 +1,55 @@ from calibre.web.feeds.news import BasicNewsRecipe +from calibre.ebooks.BeautifulSoup import BeautifulSoup -class Independent(BasicNewsRecipe): +class TheIndependent(BasicNewsRecipe): title = u'The Independent' - oldest_article = 1 - language = 'en_GB' - __author__ = 'Jimmy Patrick' - max_articles_per_feed = 100 + language = 'en_UK' + __author__ = 'Krittika Goyal' + oldest_article = 1 #days + max_articles_per_feed = 25 + encoding = 'latin1' - feeds = [(u'UK', u'http://www.independent.co.uk/news/uk/rss'), - (u'World', u'http://www.independent.co.uk/news/world/rss'), - (u'Sport', u'http://www.independent.co.uk/sport/rss'), - (u'Arts & Entertainment', u'http://www.independent.co.uk/arts-entertainment/rss'), - (u'Life & Style',u'http://www.independent.co.uk/life-style/fashion/news/rss'), - (u'Business',u'http://www.independent.co.uk/news/business/rss'), - (u'Science',u'http://www.independent.co.uk/news/science/rss'), - (u'Media',u'http://www.independent.co.uk/news/media/rss') - ] + remove_stylesheets = True + #remove_tags_before = dict(name='h1', attrs={'class':'heading'}) + #remove_tags_after = dict(name='td', attrs={'class':'newptool1'}) + remove_tags = [ + dict(name='iframe'), + dict(name='div', attrs={'class':'related-articles'}), + dict(name='div', attrs={'id':['qrformdiv', 'inSection', 'alpha-inner']}), + dict(name='ul', attrs={'class':'article-tools'}), + dict(name='ul', attrs={'class':'articleTools'}), + ] - keep_only_tags = [dict(id=['article'])] - remove_tags = [dict(name='div', attrs={'class':'share-links'}), - dict(name='ul', attrs={'class':'article-tools'}), - dict(name='div', attrs={'class':'related-articles'}) - ] + feeds = [ +('UK', + 'http://www.independent.co.uk/news/uk/rss'), +('World', + 'http://www.independent.co.uk/news/world/rss'), +('Sport', + 'http://www.independent.co.uk/sport/rss'), +('Arts and Entertainment', + 'http://www.independent.co.uk/arts-entertainment/rss'), +('Business', + 'http://www.independent.co.uk/news/business/rss'), +('Life and Style', + 'http://www.independent.co.uk/life-style/gadgets-and-tech/news/rss'), +('Science', + 'http://www.independent.co.uk/news/science/rss'), +('People', + 'http://www.independent.co.uk/news/people/rss'), +('Media', + 'http://www.independent.co.uk/news/media/rss'), +('Health and Families', + 'http://www.independent.co.uk/life-style/health-and-families/rss'), +('Obituaries', + 'http://www.independent.co.uk/news/obituaries/rss'), +] - extra_css = "body{color:black;}" + def preprocess_html(self, soup): + story = soup.find(name='div', attrs={'id':'mainColumn'}) + #td = heading.findParent(name='td') + #td.extract() + soup = BeautifulSoup('t') + body = soup.find(name='body') + body.insert(0, story) + return soup diff --git a/resources/recipes/india_today.recipe b/resources/recipes/india_today.recipe new file mode 100644 index 0000000000..604a7f57ad --- /dev/null +++ b/resources/recipes/india_today.recipe @@ -0,0 +1,76 @@ +from calibre.web.feeds.news import BasicNewsRecipe + +class IndiaToday(BasicNewsRecipe): + + title = 'India Today' + __author__ = 'Kovid Goyal' + language = 'en_IN' + timefmt = ' [%d %m, %Y]' + + oldest_article = 700 + max_articles_per_feed = 10 + + no_stylesheets = True + + remove_tags_before = dict(id='content_story_title') + remove_tags_after = dict(id='rightblockdiv') + remove_tags = [dict(id=['rightblockdiv', 'share_links'])] + + extra_css = '#content_story_title { font-size: 170%; font-weight: bold;}' + conversion_options = { 'linearize_tables': True } + + def it_get_index(self): + soup = self.index_to_soup('http://indiatoday.intoday.in/site/archive') + a = soup.find('a', href=lambda x: x and 'issueId=' in x) + url = 'http://indiatoday.intoday.in/site/'+a.get('href') + img = a.find('img') + self.cover_url = img.get('src') + return self.index_to_soup(url) + + def parse_index(self): + soup = self.it_get_index() + feeds, current_section, current_articles = [], None, [] + for x in soup.findAll(name=['h1', 'a']): + if x.name == 'h1': + if current_section and current_articles: + feeds.append((current_section, current_articles)) + current_section = self.tag_to_string(x) + current_articles = [] + self.log('\tFound section:', current_section) + elif x.name == 'a' and 'Story' in x.get('href', ''): + title = self.tag_to_string(x) + url = x.get('href') + url = url.replace(' ', '%20') + if not url.startswith('/'): + url = 'http://indiatoday.intoday.in/site/' + url + if title and url: + url += '?complete=1' + self.log('\tFound article:', title) + self.log('\t\t', url) + desc = '' + h3 = x.parent.findNextSibling('h3') + if h3 is not None: + desc = 'By ' + self.tag_to_string(h3) + h4 = h3.findNextSibling('h4') + if h4 is not None: + desc = self.tag_to_string(h4) + ' ' + desc + if desc: + self.log('\t\t', desc) + current_articles.append({'title':title, 'description':desc, + 'url':url, 'date':''}) + + if current_section and current_articles: + feeds.append((current_section, current_articles)) + + return feeds + + def postprocess_html(self, soup, first): + a = soup.find(text='Print') + if a is not None: + tr = a.findParent('tr') + if tr is not None: + tr.extract() + return soup + + + diff --git a/src/calibre/web/feeds/news.py b/src/calibre/web/feeds/news.py index 4ade7fa73c..2778c1c5e9 100644 --- a/src/calibre/web/feeds/news.py +++ b/src/calibre/web/feeds/news.py @@ -819,10 +819,14 @@ class BasicNewsRecipe(Recipe): if '?' in ext: ext = '' ext = ext.lower() if ext else 'jpg' - self.report_progress(1, _('Downloading cover from %s')%cu) cpath = os.path.join(self.output_dir, 'cover.'+ext) - with nested(open(cpath, 'wb'), closing(self.browser.open(cu))) as (cfile, r): - cfile.write(r.read()) + if os.access(cu, os.R_OK): + with open(cpath, 'wb') as cfile: + cfile.write(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 ext.lower() == 'pdf': from calibre.ebook.metadata.pdf import get_metadata stream = open(cpath, 'rb') From 928f7b8db8a1df0555e6265093dee9146b45a325 Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Thu, 31 Dec 2009 09:53:45 -0700 Subject: [PATCH 08/17] Make more space available vertically for tag browser --- src/calibre/gui2/main.ui | 53 +++++++++++++++++------------------- src/calibre/gui2/tag_view.py | 11 ++++++-- src/calibre/gui2/ui.py | 11 +++----- 3 files changed, 37 insertions(+), 38 deletions(-) diff --git a/src/calibre/gui2/main.ui b/src/calibre/gui2/main.ui index 8849c2fa07..5cf8f9d35e 100644 --- a/src/calibre/gui2/main.ui +++ b/src/calibre/gui2/main.ui @@ -231,7 +231,7 @@ - 3 + 0 @@ -239,33 +239,6 @@ - - - - Match any - - - false - - - - - - - Match all - - - true - - - - - - - Sort by &popularity - - - @@ -282,6 +255,30 @@ + + + + Sort by &popularity + + + + + + + 0 + + + + Match any + + + + + Match all + + + + diff --git a/src/calibre/gui2/tag_view.py b/src/calibre/gui2/tag_view.py index 6cf5defda4..0f02f2a591 100644 --- a/src/calibre/gui2/tag_view.py +++ b/src/calibre/gui2/tag_view.py @@ -21,16 +21,21 @@ class TagsView(QTreeView): self.setUniformRowHeights(True) self.setCursor(Qt.PointingHandCursor) self.setIconSize(QSize(30, 30)) + self.tag_match = None - def set_database(self, db, match_all, popularity): + def set_database(self, db, tag_match, popularity): self._model = TagsModel(db, parent=self) self.popularity = popularity - self.match_all = match_all + self.tag_match = tag_match self.setModel(self._model) self.connect(self, SIGNAL('clicked(QModelIndex)'), self.toggle) self.popularity.setChecked(config['sort_by_popularity']) self.connect(self.popularity, SIGNAL('stateChanged(int)'), self.sort_changed) + @property + def match_all(self): + return self.tag_match and self.tag_match.currentIndex() > 0 + def sort_changed(self, state): config.set('sort_by_popularity', state == Qt.Checked) self.model().refresh() @@ -40,7 +45,7 @@ class TagsView(QTreeView): exclusive = modifiers not in (Qt.CTRL, Qt.SHIFT) if self._model.toggle(index, exclusive): self.emit(SIGNAL('tags_marked(PyQt_PyObject, PyQt_PyObject)'), - self._model.tokens(), self.match_all.isChecked()) + self._model.tokens(), self.match_all) def clear(self): self.model().clear_state() diff --git a/src/calibre/gui2/ui.py b/src/calibre/gui2/ui.py index b847186c2a..f9fbcbae3a 100644 --- a/src/calibre/gui2/ui.py +++ b/src/calibre/gui2/ui.py @@ -498,10 +498,9 @@ class Main(MainWindow, Ui_MainWindow, DeviceGUI): self.cover_cache.start() self.library_view.model().cover_cache = self.cover_cache self.tags_view.setVisible(False) - self.match_all.setVisible(False) - self.match_any.setVisible(False) + self.tag_match.setVisible(False) self.popularity.setVisible(False) - self.tags_view.set_database(db, self.match_all, self.popularity) + self.tags_view.set_database(db, self.tag_match, self.popularity) self.connect(self.tags_view, SIGNAL('tags_marked(PyQt_PyObject, PyQt_PyObject)'), self.search.search_from_tags) @@ -708,14 +707,12 @@ class Main(MainWindow, Ui_MainWindow, DeviceGUI): def toggle_tags_view(self, show): if show: self.tags_view.setVisible(True) - self.match_all.setVisible(True) - self.match_any.setVisible(True) + self.tag_match.setVisible(True) self.popularity.setVisible(True) self.tags_view.setFocus(Qt.OtherFocusReason) else: self.tags_view.setVisible(False) - self.match_all.setVisible(False) - self.match_any.setVisible(False) + self.tag_match.setVisible(False) self.popularity.setVisible(False) def search_done(self, view, ok): From dc59842f6954e02ac93b1ce35bed4d42afc85a44 Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Thu, 31 Dec 2009 10:50:45 -0700 Subject: [PATCH 09/17] Auto populate author sort field in conversion dialog when author is changed. Add command line option to content server to specify the path to the library to be served --- src/calibre/gui2/convert/metadata.py | 12 +- src/calibre/library/server.py | 10 +- src/calibre/translations/calibre.pot | 400 ++++++++++++++------------- src/calibre/web/feeds/news.py | 8 +- 4 files changed, 233 insertions(+), 197 deletions(-) diff --git a/src/calibre/gui2/convert/metadata.py b/src/calibre/gui2/convert/metadata.py index 2ab1eb8301..8200f466c5 100644 --- a/src/calibre/gui2/convert/metadata.py +++ b/src/calibre/gui2/convert/metadata.py @@ -6,14 +6,14 @@ __license__ = 'GPL v3' __copyright__ = '2009, Kovid Goyal ' __docformat__ = 'restructuredtext en' -import os, uuid +import os, uuid, re from PyQt4.Qt import QPixmap, SIGNAL from calibre.gui2 import choose_images, error_dialog from calibre.gui2.convert.metadata_ui import Ui_Form from calibre.ebooks.metadata import authors_to_string, string_to_authors, \ - MetaInformation + MetaInformation, authors_to_sort_string from calibre.ebooks.metadata.opf2 import metadata_to_opf from calibre.ptempfile import PersistentTemporaryFile from calibre.gui2.convert import Widget @@ -53,8 +53,16 @@ class MetadataWidget(Widget, Ui_Form): self.initialize_options(get_option, get_help, db, book_id) self.connect(self.cover_button, SIGNAL("clicked()"), self.select_cover) + def deduce_author_sort(self, *args): + au = unicode(self.author.currentText()) + au = re.sub(r'\s+et al\.$', '', au) + authors = string_to_authors(au) + self.author_sort.setText(authors_to_sort_string(authors)) + + def initialize_metadata_options(self): self.initialize_combos() + self.author.editTextChanged.connect(self.deduce_author_sort) mi = self.db.get_metadata(self.book_id, index_is_id=True) self.title.setText(mi.title) diff --git a/src/calibre/library/server.py b/src/calibre/library/server.py index cfe81486cf..e5e2fe8941 100644 --- a/src/calibre/library/server.py +++ b/src/calibre/library/server.py @@ -853,14 +853,20 @@ def stop_threaded_server(server): server.thread = None def option_parser(): - return config().option_parser('%prog '+ _('[options]\n\nStart the calibre content server.')) + parser = config().option_parser('%prog '+ _('[options]\n\nStart the calibre content server.')) + parser.add_option('--with-library', default=None, + help=_('Path to the library folder to serve with the content server')) + return parser + def main(args=sys.argv): parser = option_parser() opts, args = parser.parse_args(args) cherrypy.log.screen = True from calibre.utils.config import prefs - db = LibraryDatabase2(prefs['library_path']) + if opts.with_library is None: + opts.with_library = prefs['library_path'] + db = LibraryDatabase2(opts.with_library) server = LibraryServer(db, opts) server.start() return 0 diff --git a/src/calibre/translations/calibre.pot b/src/calibre/translations/calibre.pot index 6ad58d7025..c209f9b628 100644 --- a/src/calibre/translations/calibre.pot +++ b/src/calibre/translations/calibre.pot @@ -5,8 +5,8 @@ msgid "" msgstr "" "Project-Id-Version: calibre 0.6.31\n" -"POT-Creation-Date: 2009-12-27 16:02+MST\n" -"PO-Revision-Date: 2009-12-27 16:02+MST\n" +"POT-Creation-Date: 2009-12-31 10:50+MST\n" +"PO-Revision-Date: 2009-12-31 10:50+MST\n" "Last-Translator: Automatically generated\n" "Language-Team: LANGUAGE\n" "MIME-Version: 1.0\n" @@ -103,9 +103,9 @@ msgstr "" #: /home/kovid/work/calibre/src/calibre/gui2/add.py:121 #: /home/kovid/work/calibre/src/calibre/gui2/add.py:128 #: /home/kovid/work/calibre/src/calibre/gui2/convert/__init__.py:21 -#: /home/kovid/work/calibre/src/calibre/gui2/convert/metadata.py:99 -#: /home/kovid/work/calibre/src/calibre/gui2/convert/metadata.py:124 -#: /home/kovid/work/calibre/src/calibre/gui2/convert/metadata.py:126 +#: /home/kovid/work/calibre/src/calibre/gui2/convert/metadata.py:107 +#: /home/kovid/work/calibre/src/calibre/gui2/convert/metadata.py:132 +#: /home/kovid/work/calibre/src/calibre/gui2/convert/metadata.py:134 #: /home/kovid/work/calibre/src/calibre/gui2/device.py:548 #: /home/kovid/work/calibre/src/calibre/gui2/device.py:557 #: /home/kovid/work/calibre/src/calibre/gui2/device.py:775 @@ -132,7 +132,7 @@ msgstr "" #: /home/kovid/work/calibre/src/calibre/library/server.py:645 #: /home/kovid/work/calibre/src/calibre/library/server.py:717 #: /home/kovid/work/calibre/src/calibre/library/server.py:764 -#: /home/kovid/work/calibre/src/calibre/utils/localization.py:107 +#: /home/kovid/work/calibre/src/calibre/utils/localization.py:108 #: /home/kovid/work/calibre/src/calibre/utils/podofo/__init__.py:45 #: /home/kovid/work/calibre/src/calibre/utils/podofo/__init__.py:63 #: /home/kovid/work/calibre/src/calibre/utils/podofo/__init__.py:77 @@ -389,6 +389,10 @@ msgstr "" msgid "Comma separated list of directories to send e-books to on the device. The first one that exists will be used" msgstr "" +#: /home/kovid/work/calibre/src/calibre/devices/binatone/driver.py:17 +msgid "Communicate with the Binatone Readme eBook reader." +msgstr "" + #: /home/kovid/work/calibre/src/calibre/devices/blackberry/driver.py:12 msgid "Communicate with the Blackberry smart phone." msgstr "" @@ -407,7 +411,7 @@ msgstr "" msgid "Communicate with the Cybook Opus eBook reader." msgstr "" -#: /home/kovid/work/calibre/src/calibre/devices/eb600/driver.py:23 +#: /home/kovid/work/calibre/src/calibre/devices/eb600/driver.py:24 msgid "Communicate with the EB600 eBook reader." msgstr "" @@ -427,6 +431,10 @@ msgstr "" msgid "Communicate with the BOOX eBook reader." msgstr "" +#: /home/kovid/work/calibre/src/calibre/devices/hanvon/driver.py:17 +msgid "Communicate with the Hanvon N520 eBook reader." +msgstr "" + #: /home/kovid/work/calibre/src/calibre/devices/iliad/driver.py:16 msgid "Communicate with the IRex Iliad eBook reader." msgstr "" @@ -576,7 +584,7 @@ msgstr "" #: /home/kovid/work/calibre/src/calibre/devices/usbms/device.py:853 #: /home/kovid/work/calibre/src/calibre/gui2/dialogs/scheduler.py:232 -#: /home/kovid/work/calibre/src/calibre/gui2/tag_view.py:125 +#: /home/kovid/work/calibre/src/calibre/gui2/tag_view.py:132 #: /home/kovid/work/calibre/src/calibre/library/database2.py:1065 #: /home/kovid/work/calibre/src/calibre/library/database2.py:1069 #: /home/kovid/work/calibre/src/calibre/library/database2.py:1403 @@ -1344,7 +1352,7 @@ msgstr "" #: /home/kovid/work/calibre/src/calibre/gui2/library.py:1041 #: /home/kovid/work/calibre/src/calibre/gui2/library.py:1101 #: /home/kovid/work/calibre/src/calibre/gui2/status.py:96 -#: /home/kovid/work/calibre/src/calibre/gui2/tag_view.py:125 +#: /home/kovid/work/calibre/src/calibre/gui2/tag_view.py:132 msgid "Tags" msgstr "" @@ -1352,7 +1360,7 @@ msgstr "" #: /home/kovid/work/calibre/src/calibre/gui2/library.py:171 #: /home/kovid/work/calibre/src/calibre/gui2/library.py:360 #: /home/kovid/work/calibre/src/calibre/gui2/status.py:95 -#: /home/kovid/work/calibre/src/calibre/gui2/tag_view.py:125 +#: /home/kovid/work/calibre/src/calibre/gui2/tag_view.py:132 msgid "Series" msgstr "" @@ -2067,7 +2075,7 @@ msgid "Limit max simultaneous jobs to number of CPUs" msgstr "" #: /home/kovid/work/calibre/src/calibre/gui2/__init__.py:127 -#: /home/kovid/work/calibre/src/calibre/gui2/wizard/__init__.py:410 +#: /home/kovid/work/calibre/src/calibre/gui2/wizard/__init__.py:439 msgid "Copied" msgstr "" @@ -2325,9 +2333,9 @@ msgstr "" #: /home/kovid/work/calibre/src/calibre/gui2/dialogs/user_profiles_ui.py:267 #: /home/kovid/work/calibre/src/calibre/gui2/dialogs/user_profiles_ui.py:269 #: /home/kovid/work/calibre/src/calibre/gui2/dialogs/user_profiles_ui.py:270 +#: /home/kovid/work/calibre/src/calibre/gui2/main_ui.py:332 #: /home/kovid/work/calibre/src/calibre/gui2/main_ui.py:334 -#: /home/kovid/work/calibre/src/calibre/gui2/main_ui.py:336 -#: /home/kovid/work/calibre/src/calibre/gui2/main_ui.py:342 +#: /home/kovid/work/calibre/src/calibre/gui2/main_ui.py:340 #: /home/kovid/work/calibre/src/calibre/gui2/shortcuts_ui.py:74 #: /home/kovid/work/calibre/src/calibre/gui2/shortcuts_ui.py:79 msgid "..." @@ -2573,33 +2581,33 @@ msgstr "" msgid "Set the metadata. The output file will contain as much of this metadata as possible." msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/convert/metadata.py:152 +#: /home/kovid/work/calibre/src/calibre/gui2/convert/metadata.py:160 #: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single.py:99 msgid "Choose cover for " msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/convert/metadata.py:159 +#: /home/kovid/work/calibre/src/calibre/gui2/convert/metadata.py:167 #: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single.py:106 msgid "Cannot read" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/convert/metadata.py:160 +#: /home/kovid/work/calibre/src/calibre/gui2/convert/metadata.py:168 #: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single.py:107 msgid "You do not have permission to read the file: " msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/convert/metadata.py:168 -#: /home/kovid/work/calibre/src/calibre/gui2/convert/metadata.py:175 +#: /home/kovid/work/calibre/src/calibre/gui2/convert/metadata.py:176 +#: /home/kovid/work/calibre/src/calibre/gui2/convert/metadata.py:183 #: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single.py:115 msgid "Error reading file" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/convert/metadata.py:169 +#: /home/kovid/work/calibre/src/calibre/gui2/convert/metadata.py:177 #: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single.py:116 msgid "

There was an error reading from file:
" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/convert/metadata.py:176 +#: /home/kovid/work/calibre/src/calibre/gui2/convert/metadata.py:184 #: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single.py:124 msgid " is not a valid picture" msgstr "" @@ -2795,7 +2803,7 @@ msgid "RB Output" msgstr "" #: /home/kovid/work/calibre/src/calibre/gui2/convert/regex_builder.py:77 -#: /home/kovid/work/calibre/src/calibre/gui2/ui.py:1513 +#: /home/kovid/work/calibre/src/calibre/gui2/ui.py:1510 msgid "Choose the format to view" msgstr "" @@ -3300,7 +3308,7 @@ msgstr "" #: /home/kovid/work/calibre/src/calibre/gui2/dialogs/config/__init__.py:216 #: /home/kovid/work/calibre/src/calibre/gui2/library.py:350 #: /home/kovid/work/calibre/src/calibre/gui2/status.py:93 -#: /home/kovid/work/calibre/src/calibre/gui2/tag_view.py:125 +#: /home/kovid/work/calibre/src/calibre/gui2/tag_view.py:132 msgid "Formats" msgstr "" @@ -3403,7 +3411,7 @@ msgstr "" #: /home/kovid/work/calibre/src/calibre/gui2/dialogs/config/__init__.py:475 #: /home/kovid/work/calibre/src/calibre/gui2/dialogs/config/__init__.py:819 #: /home/kovid/work/calibre/src/calibre/gui2/ui.py:158 -#: /home/kovid/work/calibre/src/calibre/gui2/ui.py:1186 +#: /home/kovid/work/calibre/src/calibre/gui2/ui.py:1183 #: /home/kovid/work/calibre/src/calibre/utils/ipc/job.py:53 msgid "Error" msgstr "" @@ -3473,12 +3481,12 @@ msgid "Access log:" msgstr "" #: /home/kovid/work/calibre/src/calibre/gui2/dialogs/config/__init__.py:674 -#: /home/kovid/work/calibre/src/calibre/gui2/ui.py:635 +#: /home/kovid/work/calibre/src/calibre/gui2/ui.py:634 msgid "Failed to start content server" msgstr "" #: /home/kovid/work/calibre/src/calibre/gui2/dialogs/config/__init__.py:698 -#: /home/kovid/work/calibre/src/calibre/gui2/wizard/__init__.py:520 +#: /home/kovid/work/calibre/src/calibre/gui2/wizard/__init__.py:549 msgid "Select location for books" msgstr "" @@ -3612,7 +3620,7 @@ msgid "&Saving books" msgstr "" #: /home/kovid/work/calibre/src/calibre/gui2/dialogs/config/config_ui.py:499 -#: /home/kovid/work/calibre/src/calibre/gui2/main_ui.py:368 +#: /home/kovid/work/calibre/src/calibre/gui2/main_ui.py:366 #: /home/kovid/work/calibre/src/calibre/gui2/viewer/main_ui.py:173 msgid "Preferences" msgstr "" @@ -4067,7 +4075,7 @@ msgid "Choose formats for " msgstr "" #: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single.py:137 -#: /home/kovid/work/calibre/src/calibre/gui2/ui.py:947 +#: /home/kovid/work/calibre/src/calibre/gui2/ui.py:944 msgid "Books" msgstr "" @@ -4967,135 +4975,135 @@ msgstr "" msgid "try deleting the file" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/main_ui.py:333 +#: /home/kovid/work/calibre/src/calibre/gui2/main_ui.py:331 msgid "calibre" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/main_ui.py:335 +#: /home/kovid/work/calibre/src/calibre/gui2/main_ui.py:333 msgid "Advanced search" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/main_ui.py:337 +#: /home/kovid/work/calibre/src/calibre/gui2/main_ui.py:335 msgid "Alt+S" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/main_ui.py:338 +#: /home/kovid/work/calibre/src/calibre/gui2/main_ui.py:336 msgid "&Search:" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/main_ui.py:339 -#: /home/kovid/work/calibre/src/calibre/gui2/main_ui.py:340 +#: /home/kovid/work/calibre/src/calibre/gui2/main_ui.py:337 +#: /home/kovid/work/calibre/src/calibre/gui2/main_ui.py:338 msgid "

Search the list of books by title, author, publisher, tags, comments, etc.

Words separated by spaces are ANDed" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/main_ui.py:341 +#: /home/kovid/work/calibre/src/calibre/gui2/main_ui.py:339 msgid "Reset Quick Search" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/main_ui.py:343 -msgid "Match any" -msgstr "" - -#: /home/kovid/work/calibre/src/calibre/gui2/main_ui.py:344 -msgid "Match all" -msgstr "" - -#: /home/kovid/work/calibre/src/calibre/gui2/main_ui.py:345 +#: /home/kovid/work/calibre/src/calibre/gui2/main_ui.py:341 msgid "Sort by &popularity" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/main_ui.py:346 +#: /home/kovid/work/calibre/src/calibre/gui2/main_ui.py:342 +msgid "Match any" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/main_ui.py:343 +msgid "Match all" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/main_ui.py:344 msgid "Add books" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/main_ui.py:347 +#: /home/kovid/work/calibre/src/calibre/gui2/main_ui.py:345 msgid "A" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/main_ui.py:348 -#: /home/kovid/work/calibre/src/calibre/gui2/main_ui.py:349 +#: /home/kovid/work/calibre/src/calibre/gui2/main_ui.py:346 +#: /home/kovid/work/calibre/src/calibre/gui2/main_ui.py:347 msgid "Remove books" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/main_ui.py:350 +#: /home/kovid/work/calibre/src/calibre/gui2/main_ui.py:348 msgid "Del" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/main_ui.py:351 +#: /home/kovid/work/calibre/src/calibre/gui2/main_ui.py:349 msgid "Edit meta information" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/main_ui.py:352 +#: /home/kovid/work/calibre/src/calibre/gui2/main_ui.py:350 msgid "E" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/main_ui.py:353 +#: /home/kovid/work/calibre/src/calibre/gui2/main_ui.py:351 msgid "Send to device" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/main_ui.py:354 +#: /home/kovid/work/calibre/src/calibre/gui2/main_ui.py:352 #: /home/kovid/work/calibre/src/calibre/gui2/ui.py:304 msgid "Save to disk" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/main_ui.py:355 +#: /home/kovid/work/calibre/src/calibre/gui2/main_ui.py:353 msgid "S" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/main_ui.py:356 +#: /home/kovid/work/calibre/src/calibre/gui2/main_ui.py:354 msgid "Fetch news" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/main_ui.py:357 +#: /home/kovid/work/calibre/src/calibre/gui2/main_ui.py:355 msgid "F" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/main_ui.py:358 +#: /home/kovid/work/calibre/src/calibre/gui2/main_ui.py:356 msgid "Convert E-books" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/main_ui.py:359 +#: /home/kovid/work/calibre/src/calibre/gui2/main_ui.py:357 msgid "C" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/main_ui.py:360 +#: /home/kovid/work/calibre/src/calibre/gui2/main_ui.py:358 #: /home/kovid/work/calibre/src/calibre/gui2/ui.py:314 msgid "View" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/main_ui.py:361 +#: /home/kovid/work/calibre/src/calibre/gui2/main_ui.py:359 msgid "V" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/main_ui.py:362 +#: /home/kovid/work/calibre/src/calibre/gui2/main_ui.py:360 msgid "Open containing folder" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/main_ui.py:363 +#: /home/kovid/work/calibre/src/calibre/gui2/main_ui.py:361 msgid "Show book details" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/main_ui.py:364 +#: /home/kovid/work/calibre/src/calibre/gui2/main_ui.py:362 msgid "Books by same author" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/main_ui.py:365 +#: /home/kovid/work/calibre/src/calibre/gui2/main_ui.py:363 msgid "Books in this series" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/main_ui.py:366 +#: /home/kovid/work/calibre/src/calibre/gui2/main_ui.py:364 msgid "Books by this publisher" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/main_ui.py:367 +#: /home/kovid/work/calibre/src/calibre/gui2/main_ui.py:365 msgid "Books with the same tags" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/main_ui.py:369 +#: /home/kovid/work/calibre/src/calibre/gui2/main_ui.py:367 msgid "Configure calibre" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/main_ui.py:370 +#: /home/kovid/work/calibre/src/calibre/gui2/main_ui.py:368 msgid "Ctrl+P" msgstr "" @@ -5207,11 +5215,11 @@ msgstr "" msgid "Click to browse books by tags" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/tag_view.py:125 +#: /home/kovid/work/calibre/src/calibre/gui2/tag_view.py:132 msgid "Authors" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/tag_view.py:125 +#: /home/kovid/work/calibre/src/calibre/gui2/tag_view.py:132 msgid "Publishers" msgstr "" @@ -5327,7 +5335,7 @@ msgid "Save to disk in a single directory" msgstr "" #: /home/kovid/work/calibre/src/calibre/gui2/ui.py:306 -#: /home/kovid/work/calibre/src/calibre/gui2/ui.py:1621 +#: /home/kovid/work/calibre/src/calibre/gui2/ui.py:1618 msgid "Save only %s format to disk" msgstr "" @@ -5373,36 +5381,36 @@ msgid "Bad database location" msgstr "" #: /home/kovid/work/calibre/src/calibre/gui2/ui.py:471 -#: /home/kovid/work/calibre/src/calibre/gui2/wizard/__init__.py:528 +#: /home/kovid/work/calibre/src/calibre/gui2/wizard/__init__.py:557 msgid "Calibre Library" msgstr "" #: /home/kovid/work/calibre/src/calibre/gui2/ui.py:481 -#: /home/kovid/work/calibre/src/calibre/gui2/ui.py:1767 +#: /home/kovid/work/calibre/src/calibre/gui2/ui.py:1774 msgid "Choose a location for your ebook library." msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/ui.py:678 +#: /home/kovid/work/calibre/src/calibre/gui2/ui.py:677 msgid "Browse by covers" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/ui.py:795 +#: /home/kovid/work/calibre/src/calibre/gui2/ui.py:792 msgid "Device: " msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/ui.py:797 +#: /home/kovid/work/calibre/src/calibre/gui2/ui.py:794 msgid " detected." msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/ui.py:821 +#: /home/kovid/work/calibre/src/calibre/gui2/ui.py:818 msgid "Connected " msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/ui.py:833 +#: /home/kovid/work/calibre/src/calibre/gui2/ui.py:830 msgid "Device database corrupted" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/ui.py:834 +#: /home/kovid/work/calibre/src/calibre/gui2/ui.py:831 msgid "" "\n" "

The database of books on the reader is corrupted. Try the following:\n" @@ -5413,277 +5421,281 @@ msgid "" " " msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/ui.py:895 +#: /home/kovid/work/calibre/src/calibre/gui2/ui.py:892 msgid "How many empty books?" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/ui.py:896 +#: /home/kovid/work/calibre/src/calibre/gui2/ui.py:893 msgid "How many empty books should be added?" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/ui.py:940 -#: /home/kovid/work/calibre/src/calibre/gui2/ui.py:987 +#: /home/kovid/work/calibre/src/calibre/gui2/ui.py:937 +#: /home/kovid/work/calibre/src/calibre/gui2/ui.py:984 msgid "Uploading books to device." msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/ui.py:948 +#: /home/kovid/work/calibre/src/calibre/gui2/ui.py:945 msgid "EPUB Books" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/ui.py:949 +#: /home/kovid/work/calibre/src/calibre/gui2/ui.py:946 msgid "LRF Books" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/ui.py:950 +#: /home/kovid/work/calibre/src/calibre/gui2/ui.py:947 msgid "HTML Books" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/ui.py:951 +#: /home/kovid/work/calibre/src/calibre/gui2/ui.py:948 msgid "LIT Books" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/ui.py:952 +#: /home/kovid/work/calibre/src/calibre/gui2/ui.py:949 msgid "MOBI Books" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/ui.py:953 +#: /home/kovid/work/calibre/src/calibre/gui2/ui.py:950 msgid "Text books" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/ui.py:954 +#: /home/kovid/work/calibre/src/calibre/gui2/ui.py:951 msgid "PDF Books" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/ui.py:955 +#: /home/kovid/work/calibre/src/calibre/gui2/ui.py:952 msgid "Comics" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/ui.py:956 +#: /home/kovid/work/calibre/src/calibre/gui2/ui.py:953 msgid "Archives" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/ui.py:960 +#: /home/kovid/work/calibre/src/calibre/gui2/ui.py:957 msgid "Supported books" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/ui.py:996 +#: /home/kovid/work/calibre/src/calibre/gui2/ui.py:993 msgid "Failed to read metadata" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/ui.py:997 +#: /home/kovid/work/calibre/src/calibre/gui2/ui.py:994 msgid "Failed to read metadata from the following" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/ui.py:1016 +#: /home/kovid/work/calibre/src/calibre/gui2/ui.py:1013 msgid "Cannot delete" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/ui.py:1019 -#: /home/kovid/work/calibre/src/calibre/gui2/ui.py:1507 -#: /home/kovid/work/calibre/src/calibre/gui2/ui.py:1526 +#: /home/kovid/work/calibre/src/calibre/gui2/ui.py:1016 +#: /home/kovid/work/calibre/src/calibre/gui2/ui.py:1504 +#: /home/kovid/work/calibre/src/calibre/gui2/ui.py:1523 msgid "No book selected" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/ui.py:1029 +#: /home/kovid/work/calibre/src/calibre/gui2/ui.py:1026 msgid "Choose formats to be deleted" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/ui.py:1045 +#: /home/kovid/work/calibre/src/calibre/gui2/ui.py:1042 msgid "Choose formats not to be deleted" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/ui.py:1081 +#: /home/kovid/work/calibre/src/calibre/gui2/ui.py:1078 msgid "The selected books will be permanently deleted and the files removed from your computer. Are you sure?" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/ui.py:1108 +#: /home/kovid/work/calibre/src/calibre/gui2/ui.py:1105 msgid "Deleting books from device." msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/ui.py:1139 +#: /home/kovid/work/calibre/src/calibre/gui2/ui.py:1136 msgid "Cannot download metadata" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/ui.py:1140 -#: /home/kovid/work/calibre/src/calibre/gui2/ui.py:1197 -#: /home/kovid/work/calibre/src/calibre/gui2/ui.py:1230 -#: /home/kovid/work/calibre/src/calibre/gui2/ui.py:1255 -#: /home/kovid/work/calibre/src/calibre/gui2/ui.py:1368 +#: /home/kovid/work/calibre/src/calibre/gui2/ui.py:1137 +#: /home/kovid/work/calibre/src/calibre/gui2/ui.py:1194 +#: /home/kovid/work/calibre/src/calibre/gui2/ui.py:1227 +#: /home/kovid/work/calibre/src/calibre/gui2/ui.py:1252 +#: /home/kovid/work/calibre/src/calibre/gui2/ui.py:1365 msgid "No books selected" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/ui.py:1155 +#: /home/kovid/work/calibre/src/calibre/gui2/ui.py:1152 msgid "social metadata" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/ui.py:1157 +#: /home/kovid/work/calibre/src/calibre/gui2/ui.py:1154 msgid "covers" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/ui.py:1157 +#: /home/kovid/work/calibre/src/calibre/gui2/ui.py:1154 msgid "metadata" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/ui.py:1159 +#: /home/kovid/work/calibre/src/calibre/gui2/ui.py:1156 msgid "Downloading %s for %d book(s)" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/ui.py:1181 +#: /home/kovid/work/calibre/src/calibre/gui2/ui.py:1178 msgid "Failed to download some metadata" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/ui.py:1182 +#: /home/kovid/work/calibre/src/calibre/gui2/ui.py:1179 msgid "Failed to download metadata for the following:" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/ui.py:1185 +#: /home/kovid/work/calibre/src/calibre/gui2/ui.py:1182 msgid "Failed to download metadata:" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/ui.py:1196 -#: /home/kovid/work/calibre/src/calibre/gui2/ui.py:1229 +#: /home/kovid/work/calibre/src/calibre/gui2/ui.py:1193 +#: /home/kovid/work/calibre/src/calibre/gui2/ui.py:1226 msgid "Cannot edit metadata" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/ui.py:1254 +#: /home/kovid/work/calibre/src/calibre/gui2/ui.py:1251 msgid "Cannot save to disk" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/ui.py:1257 +#: /home/kovid/work/calibre/src/calibre/gui2/ui.py:1254 msgid "Choose destination directory" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/ui.py:1284 +#: /home/kovid/work/calibre/src/calibre/gui2/ui.py:1281 msgid "Error while saving" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/ui.py:1285 +#: /home/kovid/work/calibre/src/calibre/gui2/ui.py:1282 msgid "There was an error while saving." msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/ui.py:1292 -#: /home/kovid/work/calibre/src/calibre/gui2/ui.py:1293 +#: /home/kovid/work/calibre/src/calibre/gui2/ui.py:1289 +#: /home/kovid/work/calibre/src/calibre/gui2/ui.py:1290 msgid "Could not save some books" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/ui.py:1294 +#: /home/kovid/work/calibre/src/calibre/gui2/ui.py:1291 msgid "Click the show details button to see which ones." msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/ui.py:1313 +#: /home/kovid/work/calibre/src/calibre/gui2/ui.py:1310 msgid "Fetching news from " msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/ui.py:1327 +#: /home/kovid/work/calibre/src/calibre/gui2/ui.py:1324 msgid " fetched." msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/ui.py:1367 +#: /home/kovid/work/calibre/src/calibre/gui2/ui.py:1364 msgid "Cannot convert" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/ui.py:1396 +#: /home/kovid/work/calibre/src/calibre/gui2/ui.py:1393 msgid "Starting conversion of %d book(s)" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/ui.py:1507 -#: /home/kovid/work/calibre/src/calibre/gui2/ui.py:1563 +#: /home/kovid/work/calibre/src/calibre/gui2/ui.py:1504 +#: /home/kovid/work/calibre/src/calibre/gui2/ui.py:1560 msgid "Cannot view" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/ui.py:1525 +#: /home/kovid/work/calibre/src/calibre/gui2/ui.py:1522 msgid "Cannot open folder" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/ui.py:1547 +#: /home/kovid/work/calibre/src/calibre/gui2/ui.py:1544 msgid "Multiple Books Selected" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/ui.py:1548 +#: /home/kovid/work/calibre/src/calibre/gui2/ui.py:1545 msgid "You are attempting to open %d books. Opening too many books at once can be slow and have a negative effect on the responsiveness of your computer. Once started the process cannot be stopped until complete. Do you wish to continue?" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/ui.py:1564 +#: /home/kovid/work/calibre/src/calibre/gui2/ui.py:1561 msgid "%s has no available formats." msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/ui.py:1605 +#: /home/kovid/work/calibre/src/calibre/gui2/ui.py:1602 msgid "Cannot configure" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/ui.py:1606 +#: /home/kovid/work/calibre/src/calibre/gui2/ui.py:1603 msgid "Cannot configure while there are running jobs." msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/ui.py:1649 +#: /home/kovid/work/calibre/src/calibre/gui2/ui.py:1646 msgid "No detailed info available" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/ui.py:1650 +#: /home/kovid/work/calibre/src/calibre/gui2/ui.py:1647 msgid "No detailed information is available for books on the device." msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/ui.py:1705 +#: /home/kovid/work/calibre/src/calibre/gui2/ui.py:1702 msgid "Error talking to device" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/ui.py:1706 +#: /home/kovid/work/calibre/src/calibre/gui2/ui.py:1703 msgid "There was a temporary error talking to the device. Please unplug and reconnect the device and or reboot." msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/ui.py:1729 -#: /home/kovid/work/calibre/src/calibre/gui2/ui.py:1747 +#: /home/kovid/work/calibre/src/calibre/gui2/ui.py:1726 +#: /home/kovid/work/calibre/src/calibre/gui2/ui.py:1754 msgid "Conversion Error" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/ui.py:1730 +#: /home/kovid/work/calibre/src/calibre/gui2/ui.py:1727 msgid "

Could not convert: %s

It is a DRMed book. You must first remove the DRM using third party tools." msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/ui.py:1748 +#: /home/kovid/work/calibre/src/calibre/gui2/ui.py:1740 +msgid "Recipe Disabled" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/ui.py:1755 msgid "Failed" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/ui.py:1776 +#: /home/kovid/work/calibre/src/calibre/gui2/ui.py:1783 msgid "Invalid library location" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/ui.py:1777 +#: /home/kovid/work/calibre/src/calibre/gui2/ui.py:1784 msgid "Could not access %s. Using %s as the library." msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/ui.py:1825 +#: /home/kovid/work/calibre/src/calibre/gui2/ui.py:1832 msgid "is the result of the efforts of many volunteers from all over the world. If you find it useful, please consider donating to support its development." msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/ui.py:1850 +#: /home/kovid/work/calibre/src/calibre/gui2/ui.py:1857 msgid "There are active jobs. Are you sure you want to quit?" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/ui.py:1853 +#: /home/kovid/work/calibre/src/calibre/gui2/ui.py:1860 msgid "" " is communicating with the device!
\n" " Quitting may cause corruption on the device.
\n" " Are you sure you want to quit?" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/ui.py:1857 +#: /home/kovid/work/calibre/src/calibre/gui2/ui.py:1864 msgid "WARNING: Active jobs" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/ui.py:1909 +#: /home/kovid/work/calibre/src/calibre/gui2/ui.py:1916 msgid "will keep running in the system tray. To close it, choose Quit in the context menu of the system tray." msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/ui.py:1928 +#: /home/kovid/work/calibre/src/calibre/gui2/ui.py:1935 msgid "Latest version: %s" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/ui.py:1936 +#: /home/kovid/work/calibre/src/calibre/gui2/ui.py:1943 msgid "Update available" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/ui.py:1937 +#: /home/kovid/work/calibre/src/calibre/gui2/ui.py:1944 msgid "%s has been updated to version %s. See the new features. Visit the download page?" msgstr "" @@ -6138,40 +6150,40 @@ msgstr "" msgid "Title Case" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/wizard/__init__.py:301 +#: /home/kovid/work/calibre/src/calibre/gui2/wizard/__init__.py:330 msgid "If you use the WordPlayer e-book app on your Android phone, you can access your calibre book collection directly on the device. To do this you have to turn on the content server." msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/wizard/__init__.py:305 +#: /home/kovid/work/calibre/src/calibre/gui2/wizard/__init__.py:334 msgid "Remember to leave calibre running as the server only runs as long as calibre is running." msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/wizard/__init__.py:307 +#: /home/kovid/work/calibre/src/calibre/gui2/wizard/__init__.py:336 msgid "You have to add the URL http://myhostname:8080 as your calibre library in WordPlayer. Here myhostname should be the fully qualified hostname or the IP address of the computer calibre is running on." msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/wizard/__init__.py:384 +#: /home/kovid/work/calibre/src/calibre/gui2/wizard/__init__.py:413 msgid "Moving library..." msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/wizard/__init__.py:400 -#: /home/kovid/work/calibre/src/calibre/gui2/wizard/__init__.py:401 +#: /home/kovid/work/calibre/src/calibre/gui2/wizard/__init__.py:429 +#: /home/kovid/work/calibre/src/calibre/gui2/wizard/__init__.py:430 msgid "Failed to move library" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/wizard/__init__.py:455 +#: /home/kovid/work/calibre/src/calibre/gui2/wizard/__init__.py:484 msgid "Invalid database" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/wizard/__init__.py:456 +#: /home/kovid/work/calibre/src/calibre/gui2/wizard/__init__.py:485 msgid "

An invalid library already exists at %s, delete it before trying to move the existing library.
Error: %s" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/wizard/__init__.py:467 +#: /home/kovid/work/calibre/src/calibre/gui2/wizard/__init__.py:496 msgid "Could not move library" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/wizard/__init__.py:595 +#: /home/kovid/work/calibre/src/calibre/gui2/wizard/__init__.py:624 msgid "welcome wizard" msgstr "" @@ -6719,6 +6731,10 @@ msgid "" "Start the calibre content server." msgstr "" +#: /home/kovid/work/calibre/src/calibre/library/server.py:858 +msgid "Path to the library folder to serve with the content server" +msgstr "" + #: /home/kovid/work/calibre/src/calibre/utils/config.py:48 msgid "" "%sUsage%s: %s\n" @@ -6837,14 +6853,18 @@ msgid "English (CY)" msgstr "" #: /home/kovid/work/calibre/src/calibre/utils/localization.py:104 -msgid "German (AT)" +msgid "English (PK)" msgstr "" #: /home/kovid/work/calibre/src/calibre/utils/localization.py:105 -msgid "Dutch (NL)" +msgid "German (AT)" msgstr "" #: /home/kovid/work/calibre/src/calibre/utils/localization.py:106 +msgid "Dutch (NL)" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/utils/localization.py:107 msgid "Dutch (BE)" msgstr "" @@ -6878,99 +6898,99 @@ msgstr "" msgid "Untitled article" msgstr "" -#: /home/kovid/work/calibre/src/calibre/web/feeds/input.py:18 +#: /home/kovid/work/calibre/src/calibre/web/feeds/input.py:21 msgid "Download periodical content from the internet" msgstr "" -#: /home/kovid/work/calibre/src/calibre/web/feeds/input.py:33 +#: /home/kovid/work/calibre/src/calibre/web/feeds/input.py:36 msgid "Useful for recipe development. Forces max_articles_per_feed to 2 and downloads at most 2 feeds." msgstr "" -#: /home/kovid/work/calibre/src/calibre/web/feeds/input.py:36 +#: /home/kovid/work/calibre/src/calibre/web/feeds/input.py:39 msgid "Username for sites that require a login to access content." msgstr "" -#: /home/kovid/work/calibre/src/calibre/web/feeds/input.py:39 +#: /home/kovid/work/calibre/src/calibre/web/feeds/input.py:42 msgid "Password for sites that require a login to access content." msgstr "" -#: /home/kovid/work/calibre/src/calibre/web/feeds/input.py:43 -msgid "Download latest version of builtin recipes" +#: /home/kovid/work/calibre/src/calibre/web/feeds/input.py:46 +msgid "Do not download latest version of builtin recipes from the calibre server" msgstr "" #: /home/kovid/work/calibre/src/calibre/web/feeds/news.py:40 msgid "Unknown News Source" msgstr "" -#: /home/kovid/work/calibre/src/calibre/web/feeds/news.py:513 +#: /home/kovid/work/calibre/src/calibre/web/feeds/news.py:520 msgid "The \"%s\" recipe needs a username and password." msgstr "" -#: /home/kovid/work/calibre/src/calibre/web/feeds/news.py:599 +#: /home/kovid/work/calibre/src/calibre/web/feeds/news.py:606 msgid "Download finished" msgstr "" -#: /home/kovid/work/calibre/src/calibre/web/feeds/news.py:601 +#: /home/kovid/work/calibre/src/calibre/web/feeds/news.py:608 msgid "Failed to download the following articles:" msgstr "" -#: /home/kovid/work/calibre/src/calibre/web/feeds/news.py:607 +#: /home/kovid/work/calibre/src/calibre/web/feeds/news.py:614 msgid "Failed to download parts of the following articles:" msgstr "" -#: /home/kovid/work/calibre/src/calibre/web/feeds/news.py:609 +#: /home/kovid/work/calibre/src/calibre/web/feeds/news.py:616 msgid " from " msgstr "" -#: /home/kovid/work/calibre/src/calibre/web/feeds/news.py:611 +#: /home/kovid/work/calibre/src/calibre/web/feeds/news.py:618 msgid "\tFailed links:" msgstr "" -#: /home/kovid/work/calibre/src/calibre/web/feeds/news.py:692 +#: /home/kovid/work/calibre/src/calibre/web/feeds/news.py:699 msgid "Could not fetch article. Run with -vv to see the reason" msgstr "" -#: /home/kovid/work/calibre/src/calibre/web/feeds/news.py:713 +#: /home/kovid/work/calibre/src/calibre/web/feeds/news.py:720 msgid "Fetching feeds..." msgstr "" -#: /home/kovid/work/calibre/src/calibre/web/feeds/news.py:718 +#: /home/kovid/work/calibre/src/calibre/web/feeds/news.py:725 msgid "Got feeds from index page" msgstr "" -#: /home/kovid/work/calibre/src/calibre/web/feeds/news.py:724 +#: /home/kovid/work/calibre/src/calibre/web/feeds/news.py:731 msgid "Trying to download cover..." msgstr "" -#: /home/kovid/work/calibre/src/calibre/web/feeds/news.py:782 +#: /home/kovid/work/calibre/src/calibre/web/feeds/news.py:789 msgid "Starting download [%d thread(s)]..." msgstr "" -#: /home/kovid/work/calibre/src/calibre/web/feeds/news.py:798 +#: /home/kovid/work/calibre/src/calibre/web/feeds/news.py:805 msgid "Feeds downloaded to %s" msgstr "" -#: /home/kovid/work/calibre/src/calibre/web/feeds/news.py:808 +#: /home/kovid/work/calibre/src/calibre/web/feeds/news.py:815 msgid "Could not download cover: %s" msgstr "" -#: /home/kovid/work/calibre/src/calibre/web/feeds/news.py:815 +#: /home/kovid/work/calibre/src/calibre/web/feeds/news.py:827 msgid "Downloading cover from %s" msgstr "" -#: /home/kovid/work/calibre/src/calibre/web/feeds/news.py:941 +#: /home/kovid/work/calibre/src/calibre/web/feeds/news.py:962 msgid "Untitled Article" msgstr "" -#: /home/kovid/work/calibre/src/calibre/web/feeds/news.py:1012 +#: /home/kovid/work/calibre/src/calibre/web/feeds/news.py:1033 msgid "Article downloaded: %s" msgstr "" -#: /home/kovid/work/calibre/src/calibre/web/feeds/news.py:1023 +#: /home/kovid/work/calibre/src/calibre/web/feeds/news.py:1044 msgid "Article download failed: %s" msgstr "" -#: /home/kovid/work/calibre/src/calibre/web/feeds/news.py:1040 +#: /home/kovid/work/calibre/src/calibre/web/feeds/news.py:1061 msgid "Fetching feed" msgstr "" diff --git a/src/calibre/web/feeds/news.py b/src/calibre/web/feeds/news.py index 2778c1c5e9..60943de806 100644 --- a/src/calibre/web/feeds/news.py +++ b/src/calibre/web/feeds/news.py @@ -828,13 +828,15 @@ class BasicNewsRecipe(Recipe): with nested(open(cpath, 'wb'), closing(self.browser.open(cu))) as (cfile, r): cfile.write(r.read()) if ext.lower() == 'pdf': - from calibre.ebook.metadata.pdf import get_metadata + from calibre.ebooks.metadata.pdf import get_metadata stream = open(cpath, 'rb') mi = get_metadata(stream) cpath = None if mi.cover_data and mi.cover_data[1]: - cpath = os.path.join(self.output_dir, 'cover.png') - open(cpath, 'wb').write(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]) self.cover_path = cpath From 4ec47fbfb6e4e264527ec0d884b6de5cda6a3404 Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Thu, 31 Dec 2009 13:47:06 -0700 Subject: [PATCH 10/17] Fix economist free recipe to only download at most week old articles --- resources/recipes/economist_free.recipe | 16 ++++---- resources/recipes/independent.recipe | 50 ++++++++++++------------- 2 files changed, 34 insertions(+), 32 deletions(-) diff --git a/resources/recipes/economist_free.recipe b/resources/recipes/economist_free.recipe index 7c27764b8d..79c17c5ed8 100644 --- a/resources/recipes/economist_free.recipe +++ b/resources/recipes/economist_free.recipe @@ -13,7 +13,7 @@ class Economist(BasicNewsRecipe): description = ('Global news and current affairs from a European perspective.' ' Much slower than the subscription based version.') - oldest_article = 6.5 + oldest_article = 7.0 cover_url = 'http://www.economist.com/images/covers/currentcovereu_large.jpg' remove_tags = [dict(name=['script', 'noscript', 'title', 'iframe', 'cf_floatingcontent']), dict(attrs={'class':['dblClkTrk']})] @@ -29,8 +29,15 @@ class Economist(BasicNewsRecipe): self.feed_dict = {} requests = [] for i, item in enumerate(entries): - published = time.gmtime(item.get('timestamp', time.time())) title = item.get('title', _('Untitled article')) + published = item.date_parsed + if not published: + published = time.gmtime() + utctime = datetime(*published[:6]) + delta = datetime.utcnow() - utctime + if delta.days*24*3600 + delta.seconds > 24*3600*self.oldest_article: + self.log.debug('Skipping article %s as it is too old.'%title) + continue link = item.get('link', None) description = item.get('description', '') author = item.get('author', '') @@ -64,11 +71,6 @@ class Economist(BasicNewsRecipe): self.log('Found print version for article:', title) a = Article(i, title, link, author, description, published, '') - delta = datetime.utcnow() - a.utctime - if delta.days*24*3600 + delta.seconds > 24*3600*self.oldest_article: - self.log.debug('Skipping article %s (%s) from feed %s as it is too old.'%(title, a.localtime.strftime('%a, %d %b, %Y %H:%M'), title)) - return - article = dict(title=a.title, description=a.text_summary, date=time.strftime(self.timefmt, a.date), author=a.author, url=a.url) diff --git a/resources/recipes/independent.recipe b/resources/recipes/independent.recipe index e9e15e2ba9..de33d64d93 100644 --- a/resources/recipes/independent.recipe +++ b/resources/recipes/independent.recipe @@ -9,7 +9,7 @@ class TheIndependent(BasicNewsRecipe): max_articles_per_feed = 25 encoding = 'latin1' - remove_stylesheets = True + no_stylesheets = True #remove_tags_before = dict(name='h1', attrs={'class':'heading'}) #remove_tags_after = dict(name='td', attrs={'class':'newptool1'}) remove_tags = [ @@ -17,33 +17,33 @@ class TheIndependent(BasicNewsRecipe): dict(name='div', attrs={'class':'related-articles'}), dict(name='div', attrs={'id':['qrformdiv', 'inSection', 'alpha-inner']}), dict(name='ul', attrs={'class':'article-tools'}), - dict(name='ul', attrs={'class':'articleTools'}), + dict(name='ul', attrs={'class':'articleTools'}), ] feeds = [ -('UK', - 'http://www.independent.co.uk/news/uk/rss'), -('World', - 'http://www.independent.co.uk/news/world/rss'), -('Sport', - 'http://www.independent.co.uk/sport/rss'), -('Arts and Entertainment', - 'http://www.independent.co.uk/arts-entertainment/rss'), -('Business', - 'http://www.independent.co.uk/news/business/rss'), -('Life and Style', - 'http://www.independent.co.uk/life-style/gadgets-and-tech/news/rss'), -('Science', - 'http://www.independent.co.uk/news/science/rss'), -('People', - 'http://www.independent.co.uk/news/people/rss'), -('Media', - 'http://www.independent.co.uk/news/media/rss'), -('Health and Families', - 'http://www.independent.co.uk/life-style/health-and-families/rss'), -('Obituaries', - 'http://www.independent.co.uk/news/obituaries/rss'), -] + ('UK', + 'http://www.independent.co.uk/news/uk/rss'), + ('World', + 'http://www.independent.co.uk/news/world/rss'), + ('Sport', + 'http://www.independent.co.uk/sport/rss'), + ('Arts and Entertainment', + 'http://www.independent.co.uk/arts-entertainment/rss'), + ('Business', + 'http://www.independent.co.uk/news/business/rss'), + ('Life and Style', + 'http://www.independent.co.uk/life-style/gadgets-and-tech/news/rss'), + ('Science', + 'http://www.independent.co.uk/news/science/rss'), + ('People', + 'http://www.independent.co.uk/news/people/rss'), + ('Media', + 'http://www.independent.co.uk/news/media/rss'), + ('Health and Families', + 'http://www.independent.co.uk/life-style/health-and-families/rss'), + ('Obituaries', + 'http://www.independent.co.uk/news/obituaries/rss'), + ] def preprocess_html(self, soup): story = soup.find(name='div', attrs={'id':'mainColumn'}) From 11e362a837543fd338fe4b84e423010a4c67d936 Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Thu, 31 Dec 2009 13:48:33 -0700 Subject: [PATCH 11/17] New recipe for the Denver Post by Krittika Goyal --- resources/recipes/denver_post.recipe | 57 ++++++++++++++++++++++++++++ 1 file changed, 57 insertions(+) create mode 100644 resources/recipes/denver_post.recipe diff --git a/resources/recipes/denver_post.recipe b/resources/recipes/denver_post.recipe new file mode 100644 index 0000000000..fe7ead9de7 --- /dev/null +++ b/resources/recipes/denver_post.recipe @@ -0,0 +1,57 @@ +from calibre.web.feeds.news import BasicNewsRecipe +from calibre.ebooks.BeautifulSoup import BeautifulSoup + +class DenverPost(BasicNewsRecipe): + title = u'Denver Post' + language = 'en' + __author__ = 'Krittika Goyal' + oldest_article = 1 #days + max_articles_per_feed = 20 + + conversion_options = {'linearize_tables':True} + + no_stylesheets = True + #remove_tags_before = dict(name='h1', attrs={'class':'heading'}) + #remove_tags_after = dict(name='td', attrs={'class':'newptool1'}) + remove_tags = [ + dict(name='iframe'), + dict(name='img', src=lambda x: not x or '/tracking/' in x), + dict(name='span', attrs={'fd-id':True}), + dict(name='div', attrs={'class':['articleOptions', 'articlePosition2']}), + #dict(name='div', attrs={'id':['qrformdiv', 'inSection', 'alpha-inner']}), + #dict(name='ul', attrs={'class':'article-tools'}), + #dict(name='ul', attrs={'class':'articleTools'}), + ] + + feeds = [ +('Top Stories', + 'http://feeds.denverpost.com/dp-news-topstories'), +('Business', + 'http://feeds.denverpost.com/dp-business'), +('Sports', + 'http://feeds.denverpost.com/dp-sports'), +('Lifestyles', + 'http://feeds.denverpost.com/dp-lifestyles'), +('Politics', + 'http://feeds.denverpost.com/dp-politics'), +('Entertainment', + 'http://feeds.denverpost.com/dp-entertainment'), + +] + + def preprocess_html(self, soup): + story = soup.find(name='td', attrs={'class':'articleBox'}) + #td = heading.findParent(name='td') + #td.extract() + story.extract() + soup = BeautifulSoup('t') + body = soup.find(name='body') + body.insert(0, story) + story.name = 'div' + + for img in soup.findAll(name='img', style='visibility:hidden;'): + del img['style'] + + for div in soup.findAll(id='caption', style=True): + del div['style'] + return soup From 49383b0db54c86f7eb3c1754ae85dffaff289646 Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Thu, 31 Dec 2009 14:42:39 -0700 Subject: [PATCH 12/17] New recipe for The Providence Journal by Krittika Goyal --- resources/recipes/projo.recipe | 30 ++++++++++++++++++++++++++++++ 1 file changed, 30 insertions(+) create mode 100644 resources/recipes/projo.recipe diff --git a/resources/recipes/projo.recipe b/resources/recipes/projo.recipe new file mode 100644 index 0000000000..e68f2883fc --- /dev/null +++ b/resources/recipes/projo.recipe @@ -0,0 +1,30 @@ +from calibre.web.feeds.news import BasicNewsRecipe + +class ProvidenceJournal(BasicNewsRecipe): + title = u'Providence Journal' + language = 'en' + __author__ = 'Krittika Goyal' + oldest_article = 7 #days + max_articles_per_feed = 25 + + no_stylesheets = True + remove_tags_before = dict(name='span', attrs={'class':'vitstorybody'}) + #remove_tags_after = dict(name='td', attrs={'class':'newptool1'}) + remove_tags = [ + dict(name='iframe'), + dict(name='div', attrs={'id':['storycontentright','slcgm_comments_block', 'footercontainer']}), + #dict(name='span', text=':'), + ] + + feeds = [ + ('Red Sox', + 'http://www.projo.com/newskiosk/rss/projoredsox.xml'), + ('Political Scene', + 'http://www.projo.com/newskiosk/rss/projopolitical.xml'), + ('Rhode Island News', + 'http://www.projo.com/newskiosk/rss/projolocalnews.xml'), + ('Music', + 'http://www.projo.com/newskiosk/rss/projomusic.xml'), + ] + + From 95cf14a3add64c87cec689543d9b3d15a6714620 Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Thu, 31 Dec 2009 16:21:18 -0700 Subject: [PATCH 13/17] New recipe for The Indian Express by Krittika Goyal --- resources/recipes/biggovernment.recipe | 55 +++--- resources/recipes/eluniversalimpresa.recipe | 164 +++++++++--------- resources/recipes/indian_express.recipe | 57 ++++++ resources/recipes/journalofaccountancy.recipe | 89 +++++----- resources/recipes/propublica.recipe | 120 ++++++------- 5 files changed, 271 insertions(+), 214 deletions(-) create mode 100644 resources/recipes/indian_express.recipe diff --git a/resources/recipes/biggovernment.recipe b/resources/recipes/biggovernment.recipe index f14b78f1b8..ccb4e64678 100644 --- a/resources/recipes/biggovernment.recipe +++ b/resources/recipes/biggovernment.recipe @@ -1,28 +1,27 @@ -from calibre.web.feeds.news import BasicNewsRecipe -from calibre.ebooks.BeautifulSoup import BeautifulSoup - -class BigGovernmentRecipe(BasicNewsRecipe): - __license__ = 'GPL v3' - __author__ = 'kwetal' - language = 'en_US' - version = 1 - - title = u'Big Government' - publisher = u'Andrew Breitbart' - category = u'Political blog' - description = u'Political news from the USA' - - oldest_article = 30 - max_articles_per_feed = 100 - use_embedded_content = True - - feeds = [(u'Big Government', u'http://feeds.feedburner.com/BigGovernment')] - - conversion_options = {'comments': description, 'tags': category, 'language': 'en', - 'publisher': publisher} - - extra_css = ''' - body{font-family:verdana,arial,helvetica,geneva,sans-serif;} - img {float: left; margin-right: 0.5em;} - ''' - +from calibre.web.feeds.news import BasicNewsRecipe + +class BigGovernmentRecipe(BasicNewsRecipe): + __license__ = 'GPL v3' + __author__ = 'kwetal' + language = 'en_US' + version = 1 + + title = u'Big Government' + publisher = u'Andrew Breitbart' + category = u'Political blog' + description = u'Political news from the USA' + + oldest_article = 30 + max_articles_per_feed = 100 + use_embedded_content = True + + feeds = [(u'Big Government', u'http://feeds.feedburner.com/BigGovernment')] + + conversion_options = {'comments': description, 'tags': category, 'language': 'en', + 'publisher': publisher} + + extra_css = ''' + body{font-family:verdana,arial,helvetica,geneva,sans-serif;} + img {float: left; margin-right: 0.5em;} + ''' + diff --git a/resources/recipes/eluniversalimpresa.recipe b/resources/recipes/eluniversalimpresa.recipe index c7046a31c4..7263a76e2a 100644 --- a/resources/recipes/eluniversalimpresa.recipe +++ b/resources/recipes/eluniversalimpresa.recipe @@ -1,82 +1,82 @@ -from calibre.web.feeds.news import BasicNewsRecipe - -class ElUniversalImpresaRecipe(BasicNewsRecipe): - __license__ = 'GPL v3' - __author__ = 'kwetal' - language = 'es' - version = 1 - - title = u'El Universal (Edici\u00F3n Impresa)' - publisher = u'El Universal' - category = u'News, Mexico' - description = u'News from Mexico' - - remove_empty_feeds = True - remove_javascript = True - - INDEX = 'http://www.eluniversal.com.mx' - - extra_css = ''' - body{font-family:verdana,arial,helvetica,geneva,sans-serif;} - ''' - - conversion_options = {'comments': description, 'tags': category, 'language': 'en', - 'publisher': publisher, 'linearize_tables': True} - - def parse_index(self): - soup = self.index_to_soup('http://www.eluniversal.com.mx/edicion_impresa.html') - index = [] - - table = soup.find('table', attrs = {'width': '500'}) - articles = [] - for td in table.findAll(lambda tag: tag.name == 'td' and tag.has_key('class') and tag['class'] == 'arnegro12'): - a = td.a - a.extract() - title = self.tag_to_string(a) - url = self.INDEX + a['href'] - description = self.tag_to_string(td) - articles.append({'title': title, 'date': None, 'url': url, 'description' : description}) - - index.append(('Primera Plana', articles)) - - for td in table.findAll(lambda tag: tag.name == 'td' and len(tag.attrs) == 0): - articles = [] - feedTitle = None - for a in td.findAll('a'): - if not feedTitle: - feedTitle = self.tag_to_string(a) - continue - - title = self.tag_to_string(a) - - url = self.INDEX + a['href'] - articles.append({'title': title, 'date': None, 'url': url, 'description': ''}) - - index.append((feedTitle, articles)) - - return index - - def print_version(self, url): - if url.find('wcarton') >= 0: - return None - - main, sep, id = url.rpartition('/') - - return main + '/vi_' + id - - def preprocess_html(self, soup): - table = soup.find('table') - table.extract() - - for p in soup.findAll('p'): - if self.tag_to_string(p).strip() == '': - p.extract() - - tag = soup.find('font', attrs = {'color': '#0F046A'}) - if tag: - for attr in ['color', 'face', 'helvetica,', 'sans-serif', 'size']: - if tag.has_key(attr): - del tag[attr] - tag.name = 'h1' - - return soup +from calibre.web.feeds.news import BasicNewsRecipe + +class ElUniversalImpresaRecipe(BasicNewsRecipe): + __license__ = 'GPL v3' + __author__ = 'kwetal' + language = 'es' + version = 1 + + title = u'El Universal (Edici\u00F3n Impresa)' + publisher = u'El Universal' + category = u'News, Mexico' + description = u'News from Mexico' + + remove_empty_feeds = True + remove_javascript = True + + INDEX = 'http://www.eluniversal.com.mx' + + extra_css = ''' + body{font-family:verdana,arial,helvetica,geneva,sans-serif;} + ''' + + conversion_options = {'comments': description, 'tags': category, 'language': 'en', + 'publisher': publisher, 'linearize_tables': True} + + def parse_index(self): + soup = self.index_to_soup('http://www.eluniversal.com.mx/edicion_impresa.html') + index = [] + + table = soup.find('table', attrs = {'width': '500'}) + articles = [] + for td in table.findAll(lambda tag: tag.name == 'td' and tag.has_key('class') and tag['class'] == 'arnegro12'): + a = td.a + a.extract() + title = self.tag_to_string(a) + url = self.INDEX + a['href'] + description = self.tag_to_string(td) + articles.append({'title': title, 'date': None, 'url': url, 'description' : description}) + + index.append(('Primera Plana', articles)) + + for td in table.findAll(lambda tag: tag.name == 'td' and len(tag.attrs) == 0): + articles = [] + feedTitle = None + for a in td.findAll('a'): + if not feedTitle: + feedTitle = self.tag_to_string(a) + continue + + title = self.tag_to_string(a) + + url = self.INDEX + a['href'] + articles.append({'title': title, 'date': None, 'url': url, 'description': ''}) + + index.append((feedTitle, articles)) + + return index + + def print_version(self, url): + if url.find('wcarton') >= 0: + return None + + main, sep, id = url.rpartition('/') + + return main + '/vi_' + id + + def preprocess_html(self, soup): + table = soup.find('table') + table.extract() + + for p in soup.findAll('p'): + if self.tag_to_string(p).strip() == '': + p.extract() + + tag = soup.find('font', attrs = {'color': '#0F046A'}) + if tag: + for attr in ['color', 'face', 'helvetica,', 'sans-serif', 'size']: + if tag.has_key(attr): + del tag[attr] + tag.name = 'h1' + + return soup diff --git a/resources/recipes/indian_express.recipe b/resources/recipes/indian_express.recipe new file mode 100644 index 0000000000..80a5840517 --- /dev/null +++ b/resources/recipes/indian_express.recipe @@ -0,0 +1,57 @@ +from calibre.web.feeds.news import BasicNewsRecipe + +class IndianExpress(BasicNewsRecipe): + title = u'Indian Express' + language = 'en_IN' + __author__ = 'Krittika Goyal' + oldest_article = 1 #days + max_articles_per_feed = 25 + encoding = 'cp1252' + + no_stylesheets = True + remove_tags_before = dict(name='div', attrs={'class':'top_head'}) + #remove_tags_after = dict(name='td', attrs={'class':'newptool1'}) + remove_tags = [ + dict(name='iframe'), + dict(name='div', attrs={'class':['bookmarks_div', 'comment_box', 'bookmarks_div_bot', 'box']}), + dict(name='div', attrs={'id':['footer', 'tab_innerhc', 'discussion', 'google_new']}), + dict(name='a', attrs={'class':'nobdr'}), + #dict(name='span', text=':'), + ] + + feeds = [ +('Front Page', + 'http://syndication.indianexpress.com/rss/33/front-page.xml'), +('Markets', + 'http://syndication.indianexpress.com/rss/793/markets.xml'), +('Editorials', + 'http://syndication.indianexpress.com/rss/35/editorials.xml'), +('Crime', + 'http://syndication.indianexpress.com/rss/801/crime-&-justice.xml'), +('Cricket', + 'http://syndication.indianexpress.com/rss/777/cricket.xml'), +('Health', + 'http://syndication.indianexpress.com/rss/697/health.xml'), +('Asia', + 'http://syndication.indianexpress.com/rss/790/asia.xml'), +('Politics', + 'http://syndication.indianexpress.com/rss/799/politics.xml'), +('Mumbai', + 'http://syndication.indianexpress.com/rss/707/mumbai.xml'), +('Op-Ed', + 'http://syndication.indianexpress.com/rss/36/oped.xml'), +('Economy', + 'http://syndication.indianexpress.com/rss/794/economy.xml'), +('Lifestyle', + 'http://syndication.indianexpress.com/rss/713/lifestyle.xml'), +('Letters to the Editor', + 'http://syndication.indianexpress.com/rss/40/letters-to-editor.xml'), +('Movie Reviews', + 'http://syndication.indianexpress.com/rss/665/movie-reviews.xml'), +('Bollywood', + 'http://syndication.indianexpress.com/rss/887/bollywood.xml'), +] + + def print_version(self, url): + return url+'/0' + diff --git a/resources/recipes/journalofaccountancy.recipe b/resources/recipes/journalofaccountancy.recipe index 51a6ac8d29..5407b5eae3 100644 --- a/resources/recipes/journalofaccountancy.recipe +++ b/resources/recipes/journalofaccountancy.recipe @@ -1,44 +1,45 @@ -from calibre.web.feeds.news import BasicNewsRecipe - -class JournalOfAccountancyRecipe(BasicNewsRecipe): - __license__ = 'GPL v3' - __author__ = 'kwetal' - language = 'en' - version = 1 - - title = u'Journal of Accountancy' - publisher = u'AICPA' - category = u'News, Accountancy' - description = u'Publication of the American Institute of Certified Public Accountants' - - use_embedded_content = False - remove_empty_feeds = True - oldest_article = 30 - max_articles_per_feed = 100 - - no_stylesheets = True - remove_javascript = True - - extra_css = ''' - body{font-family:verdana,arial,helvetica,geneva,sans-serif;} - div#Rubricname {font-size: small; color: #666666; margin-bottom: 1em;} - div#Headline {font-size: x-large; font-weight: bold; margin-bottom: 0.6em} - div#SubHeadline {font-size: medium; font-weight: bold; margin-bottom: 1em} - div#Authorname, div#Date {font-size: x-small; color: #696969;} - ''' - - conversion_options = {'comments': description, 'tags': category, 'language': 'en', - 'publisher': publisher} - - keep_only_tags = [] - keep_only_tags.append(dict(name = 'div', attrs = {'id': 'Rubricname'})) - keep_only_tags.append(dict(name = 'div', attrs = {'id': 'Headline'})) - keep_only_tags.append(dict(name = 'div', attrs = {'id': 'SubHeadline'})) - keep_only_tags.append(dict(name = 'div', attrs = {'id': 'Authorname'})) - keep_only_tags.append(dict(name = 'div', attrs = {'id': 'Date'})) - keep_only_tags.append(dict(name = 'div', attrs = {'id': 'BodyContent'})) - - remove_attributes = ['style'] - - feeds = [] - feeds.append((u'Journal of Accountancy', u'http://feeds2.feedburner.com/JournalOfAccountancy')) + +from calibre.web.feeds.news import BasicNewsRecipe + +class JournalOfAccountancyRecipe(BasicNewsRecipe): + __license__ = 'GPL v3' + __author__ = 'kwetal' + language = 'en' + version = 1 + + title = u'Journal of Accountancy' + publisher = u'AICPA' + category = u'News, Accountancy' + description = u'Publication of the American Institute of Certified Public Accountants' + + use_embedded_content = False + remove_empty_feeds = True + oldest_article = 30 + max_articles_per_feed = 100 + + no_stylesheets = True + remove_javascript = True + + extra_css = ''' + body{font-family:verdana,arial,helvetica,geneva,sans-serif;} + div#Rubricname {font-size: small; color: #666666; margin-bottom: 1em;} + div#Headline {font-size: x-large; font-weight: bold; margin-bottom: 0.6em} + div#SubHeadline {font-size: medium; font-weight: bold; margin-bottom: 1em} + div#Authorname, div#Date {font-size: x-small; color: #696969;} + ''' + + conversion_options = {'comments': description, 'tags': category, 'language': 'en', + 'publisher': publisher} + + keep_only_tags = [] + keep_only_tags.append(dict(name = 'div', attrs = {'id': 'Rubricname'})) + keep_only_tags.append(dict(name = 'div', attrs = {'id': 'Headline'})) + keep_only_tags.append(dict(name = 'div', attrs = {'id': 'SubHeadline'})) + keep_only_tags.append(dict(name = 'div', attrs = {'id': 'Authorname'})) + keep_only_tags.append(dict(name = 'div', attrs = {'id': 'Date'})) + keep_only_tags.append(dict(name = 'div', attrs = {'id': 'BodyContent'})) + + remove_attributes = ['style'] + + feeds = [] + feeds.append((u'Journal of Accountancy', u'http://feeds2.feedburner.com/JournalOfAccountancy')) diff --git a/resources/recipes/propublica.recipe b/resources/recipes/propublica.recipe index 1e1f0af7a9..5c35fe648e 100644 --- a/resources/recipes/propublica.recipe +++ b/resources/recipes/propublica.recipe @@ -1,60 +1,60 @@ -from calibre.web.feeds.news import BasicNewsRecipe -from calibre.ebooks.BeautifulSoup import BeautifulSoup - -class ProPublicaRecipe(BasicNewsRecipe): - __license__ = 'GPL v3' - __author__ = 'kwetal' - language = 'en_US' - version = 1 - - title = u'Pro Publica' - publisher = u'ProPublica.org' - category = u'Political blog' - description = u'Independent investigative journalism in the public interest.' - - oldest_article = 14 - max_articles_per_feed = 100 - use_embedded_content = False - - remove_empty_feeds = True - no_stylesheets = True - remove_javascript = True - - keep_only_tags = [] - keep_only_tags.append(dict(name = 'div', attrs = {'class': 'article'})) - - remove_tags = [] - remove_tags.append(dict(name = 'div', attrs = {'id': 'rollups'})) - remove_tags.append(dict(name = 'div', attrs = {'class': 'follow_info'})) - remove_tags.append(dict(name = 'ul', attrs = {'class': 'long-tools-top'})) - remove_tags.append(dict(name = 'ul', attrs = {'id': 'share-box'})) - remove_tags.append(dict(name = 'div', attrs = {'class': 'tags'})) - remove_tags.append(dict(name = 'ul', attrs = {'class': 'long-tools'})) - remove_tags.append(dict(name = 'ul', attrs = {'id': 'share-box2'})) - remove_tags.append(dict(name = 'p', attrs = {'id': 'original-url'})) - - feeds = [] - feeds.append((u'Top Stories', u'http://feeds.propublica.org/propublica/main')) - feeds.append((u'Stimulus', u'http://feeds.propublica.org/propublica/watchdog/stimulus')) - feeds.append((u'Bailout', u'http://feeds.propublica.org/propublica/watchdog/bailout')) - feeds.append((u'Business', u'http://feeds.propublica.org/propublica/business-money')) - feeds.append((u'Justice', u'http://feeds.propublica.org/propublica/justice-law')) - feeds.append((u'Energy & Environment', u'http://feeds.propublica.org/propublica/energy-environment')) - feeds.append((u'Government & Politics', u'http://feeds.propublica.org/propublica/government-politics')) - feeds.append((u'Health & Science', u'http://feeds.propublica.org/propublica/health-science')) - feeds.append((u'Media & Technology', u'http://feeds.propublica.org/propublica/media-technology')) - feeds.append((u'National Security', u'http://feeds.propublica.org/propublica/national-security')) - #feeds.append((u'', u'')) - - conversion_options = {'comments': description, 'tags': category, 'language': 'en', - 'publisher': publisher} - - extra_css = ''' - body{font-family:verdana,arial,helvetica,geneva,sans-serif;} - img {float: left; margin-right: 0.5em;} - h1 {text-align: left;} - a, a[href] {text-decoration: none; color: blue;} - div.cat {font-size: x-small; color: #666666; margin-bottom: 0.1em;} - div.info {font-size: small; color: #696969;} - ''' - \ No newline at end of file + +from calibre.web.feeds.news import BasicNewsRecipe + +class ProPublicaRecipe(BasicNewsRecipe): + __license__ = 'GPL v3' + __author__ = 'kwetal' + language = 'en_US' + version = 1 + + title = u'Pro Publica' + publisher = u'ProPublica.org' + category = u'Political blog' + description = u'Independent investigative journalism in the public interest.' + + oldest_article = 14 + max_articles_per_feed = 100 + use_embedded_content = False + + remove_empty_feeds = True + no_stylesheets = True + remove_javascript = True + + keep_only_tags = [] + keep_only_tags.append(dict(name = 'div', attrs = {'class': 'article'})) + + remove_tags = [] + remove_tags.append(dict(name = 'div', attrs = {'id': 'rollups'})) + remove_tags.append(dict(name = 'div', attrs = {'class': 'follow_info'})) + remove_tags.append(dict(name = 'ul', attrs = {'class': 'long-tools-top'})) + remove_tags.append(dict(name = 'ul', attrs = {'id': 'share-box'})) + remove_tags.append(dict(name = 'div', attrs = {'class': 'tags'})) + remove_tags.append(dict(name = 'ul', attrs = {'class': 'long-tools'})) + remove_tags.append(dict(name = 'ul', attrs = {'id': 'share-box2'})) + remove_tags.append(dict(name = 'p', attrs = {'id': 'original-url'})) + + feeds = [] + feeds.append((u'Top Stories', u'http://feeds.propublica.org/propublica/main')) + feeds.append((u'Stimulus', u'http://feeds.propublica.org/propublica/watchdog/stimulus')) + feeds.append((u'Bailout', u'http://feeds.propublica.org/propublica/watchdog/bailout')) + feeds.append((u'Business', u'http://feeds.propublica.org/propublica/business-money')) + feeds.append((u'Justice', u'http://feeds.propublica.org/propublica/justice-law')) + feeds.append((u'Energy & Environment', u'http://feeds.propublica.org/propublica/energy-environment')) + feeds.append((u'Government & Politics', u'http://feeds.propublica.org/propublica/government-politics')) + feeds.append((u'Health & Science', u'http://feeds.propublica.org/propublica/health-science')) + feeds.append((u'Media & Technology', u'http://feeds.propublica.org/propublica/media-technology')) + feeds.append((u'National Security', u'http://feeds.propublica.org/propublica/national-security')) + #feeds.append((u'', u'')) + + conversion_options = {'comments': description, 'tags': category, 'language': 'en', + 'publisher': publisher} + + extra_css = ''' + body{font-family:verdana,arial,helvetica,geneva,sans-serif;} + img {float: left; margin-right: 0.5em;} + h1 {text-align: left;} + a, a[href] {text-decoration: none; color: blue;} + div.cat {font-size: x-small; color: #666666; margin-bottom: 0.1em;} + div.info {font-size: small; color: #696969;} + ''' + From bf090b2ba23013b523ef81b83bde879d49b37431 Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Thu, 31 Dec 2009 19:28:58 -0700 Subject: [PATCH 14/17] New recipe for Psychology Today by Krittika Goyal --- resources/recipes/psych.recipe | 39 ++++++++++ src/calibre/devices/cybookg3/driver.py | 7 +- src/calibre/devices/interface.py | 15 ++-- src/calibre/devices/jetbook/driver.py | 38 ++-------- src/calibre/devices/nook/driver.py | 4 +- src/calibre/devices/usbms/device.py | 76 +++++++++++-------- src/calibre/devices/usbms/deviceconfig.py | 14 ++++ src/calibre/devices/usbms/driver.py | 3 +- src/calibre/gui2/__init__.py | 2 + src/calibre/gui2/device.py | 30 ++++---- .../gui2/device_drivers/configwidget.py | 1 + .../gui2/device_drivers/configwidget.ui | 17 ++++- src/calibre/gui2/dialogs/config/add_save.py | 40 +++------- src/calibre/gui2/dialogs/config/add_save.ui | 62 +++++++-------- .../gui2/dialogs/config/save_template.py | 58 ++++++++++++++ .../gui2/dialogs/config/save_template.ui | 60 +++++++++++++++ src/calibre/gui2/library.py | 2 + src/calibre/library/save_to_disk.py | 9 +++ 18 files changed, 319 insertions(+), 158 deletions(-) create mode 100644 resources/recipes/psych.recipe create mode 100644 src/calibre/gui2/dialogs/config/save_template.py create mode 100644 src/calibre/gui2/dialogs/config/save_template.ui diff --git a/resources/recipes/psych.recipe b/resources/recipes/psych.recipe new file mode 100644 index 0000000000..46290450cb --- /dev/null +++ b/resources/recipes/psych.recipe @@ -0,0 +1,39 @@ +from calibre.web.feeds.news import BasicNewsRecipe +from calibre.ebooks.BeautifulSoup import BeautifulSoup + +class PsychologyToday(BasicNewsRecipe): + title = u'Psychology Today' + language = 'en' + __author__ = 'Krittika Goyal' + oldest_article = 1 #days + max_articles_per_feed = 25 + #encoding = 'latin1' + + remove_stylesheets = True + #remove_tags_before = dict(name='h1', attrs={'class':'heading'}) + #remove_tags_after = dict(name='td', attrs={'class':'newptool1'}) + remove_tags = [ + dict(name='iframe'), + dict(name='div', attrs={'class':['pt-box-title', 'pt-box-content', 'blog-entry-footer', 'item-list', 'article-sub-meta']}), + dict(name='div', attrs={'id':['block-td_search_160', 'block-cam_search_160']}), + #dict(name='ul', attrs={'class':'article-tools'}), + #dict(name='ul', attrs={'class':'articleTools'}), + ] + + feeds = [ +('PSY TODAY', + 'http://www.psychologytoday.com/articles/index.rss'), +] + + def preprocess_html(self, soup): + story = soup.find(name='div', attrs={'id':'contentColumn'}) + #td = heading.findParent(name='td') + #td.extract() + soup = BeautifulSoup('t') + body = soup.find(name='body') + body.insert(0, story) + for x in soup.findAll(name='p', text=lambda x:x and '-->' in x): + p = x.findParent('p') + if p is not None: + p.extract() + return soup diff --git a/src/calibre/devices/cybookg3/driver.py b/src/calibre/devices/cybookg3/driver.py index 2cfeee4cbd..cdc41d3aca 100644 --- a/src/calibre/devices/cybookg3/driver.py +++ b/src/calibre/devices/cybookg3/driver.py @@ -47,11 +47,10 @@ class CYBOOKG3(USBMS): SUPPORTS_SUB_DIRS = True def upload_cover(self, path, filename, metadata): - coverdata = metadata.get('cover', None) + coverdata = getattr(metadata, 'thumbnail', None) if coverdata: - coverdata = coverdata[2] - with open('%s_6090.t2b' % os.path.join(path, filename), 'wb') as t2bfile: - t2b.write_t2b(t2bfile, coverdata) + with open('%s_6090.t2b' % os.path.join(path, filename), 'wb') as t2bfile: + t2b.write_t2b(t2bfile, coverdata) @classmethod def can_handle(cls, device_info, debug=False): diff --git a/src/calibre/devices/interface.py b/src/calibre/devices/interface.py index 8ce94d43b3..d8c22170f5 100644 --- a/src/calibre/devices/interface.py +++ b/src/calibre/devices/interface.py @@ -267,15 +267,16 @@ class DevicePlugin(Plugin): This method should raise a L{FreeSpaceError} if there is not enough free space on the device. The text of the FreeSpaceError must contain the word "card" if C{on_card} is not None otherwise it must contain the word "memory". - @param files: A list of paths and/or file-like objects. - @param names: A list of file names that the books should have + :files: A list of paths and/or file-like objects. + :names: A list of file names that the books should have once uploaded to the device. len(names) == len(files) - @return: A list of 3-element tuples. The list is meant to be passed + :return: A list of 3-element tuples. The list is meant to be passed to L{add_books_to_metadata}. - @param metadata: If not None, it is a list of dictionaries. Each dictionary - will have at least the key tags to allow the driver to choose book location - based on tags. len(metadata) == len(files). If your device does not support - hierarchical ebook folders, you can safely ignore this parameter. + :metadata: If not None, it is a list of :class:`MetaInformation` objects. + The idea is to use the metadata to determine where on the device to + put the book. len(metadata) == len(files). Apart from the regular + cover_data, there may also be a thumbnail attribute, which should + be used in preference. ''' raise NotImplementedError() diff --git a/src/calibre/devices/jetbook/driver.py b/src/calibre/devices/jetbook/driver.py index 6a09c7c345..e4fd840dc0 100644 --- a/src/calibre/devices/jetbook/driver.py +++ b/src/calibre/devices/jetbook/driver.py @@ -11,10 +11,8 @@ Device driver for Ectaco Jetbook firmware >= JL04_v030e import os import re import sys -from itertools import cycle from calibre.devices.usbms.driver import USBMS -from calibre.utils.filenames import ascii_filename as sanitize from calibre.ebooks.metadata import string_to_authors class JETBOOK(USBMS): @@ -50,34 +48,14 @@ class JETBOOK(USBMS): r'(?P.+)#(?P.+)' ) - def upload_books(self, files, names, on_card=False, end_session=True, - metadata=None): - - base_path = self._sanity_check(on_card, files) - - paths = [] - names = iter(names) - metadata = iter(metadata) - - for i, infile in enumerate(files): - mdata, fname = metadata.next(), names.next() - path = os.path.dirname(self.create_upload_path(base_path, mdata, fname)) - - author = sanitize(mdata.get('authors','Unknown')).replace(' ', '_') - title = sanitize(mdata.get('title', 'Unknown')).replace(' ', '_') - fileext = os.path.splitext(os.path.basename(fname))[1] - fname = '%s#%s%s' % (author, title, fileext) - - filepath = os.path.join(path, fname) - paths.append(filepath) - - self.put_file(infile, filepath, replace_file=True) - - self.report_progress((i+1) / float(len(files)), _('Transferring books to device...')) - - self.report_progress(1.0, _('Transferring books to device...')) - - return zip(paths, cycle([on_card])) + def filename_callback(self, fname, mi): + fileext = os.path.splitext(os.path.basename(fname))[1] + title = mi.title if mi.title else 'Unknown' + title = title.replace(' ', '_') + au = mi.format_authors() + if not au: + au = 'Unknown' + return '%s#%s%s' % (au, title, fileext) @classmethod def metadata_from_path(cls, path): diff --git a/src/calibre/devices/nook/driver.py b/src/calibre/devices/nook/driver.py index 0f1df903f3..fc28363bff 100644 --- a/src/calibre/devices/nook/driver.py +++ b/src/calibre/devices/nook/driver.py @@ -53,9 +53,9 @@ class NOOK(USBMS): import Image, ImageDraw - coverdata = metadata.get('cover', None) + coverdata = getattr(metadata, 'thumbnail', None) if coverdata: - cover = Image.open(cStringIO.StringIO(coverdata[2])) + cover = Image.open(cStringIO.StringIO(coverdata)) else: coverdata = open(I('library.png'), 'rb').read() diff --git a/src/calibre/devices/usbms/device.py b/src/calibre/devices/usbms/device.py index e3ae1ee99e..5e74904bc9 100644 --- a/src/calibre/devices/usbms/device.py +++ b/src/calibre/devices/usbms/device.py @@ -842,49 +842,65 @@ class Device(DeviceConfig, DevicePlugin): raise FreeSpaceError(_("There is insufficient free space on the storage card")) return path + def filename_callback(self, default, mi): + ''' + Callback to allow drivers to change the default file name + set by :method:`create_upload_path`. + ''' + return default + def create_upload_path(self, path, mdata, fname): path = os.path.abspath(path) - newpath = path extra_components = [] - if self.SUPPORTS_SUB_DIRS and self.settings().use_subdirs: - if 'tags' in mdata.keys(): - for tag in mdata['tags']: - if tag.startswith(_('News')): - extra_components.append('news') - c = sanitize(mdata.get('title', '')) - if c: - extra_components.append(c) - c = sanitize(mdata.get('timestamp', '')) - if c: - extra_components.append(c) - break - elif tag.startswith('/'): - for c in tag.split('/'): - c = sanitize(c) - if not c: continue - extra_components.append(c) - break + special_tag = None + if mdata.tags: + for t in mdata.tags: + if t.startswith(_('News')) or t.startswith('/'): + special_tag = t + break - if not extra_components: - c = sanitize(mdata.get('authors', _('Unknown'))) - if c: - extra_components.append(c) - c = sanitize(mdata.get('title', _('Unknown'))) - if c: - extra_components.append(c) - newpath = os.path.join(newpath, c) + settings = self.settings() + template = settings.save_template + use_subdirs = self.SUPPORTS_SUB_DIRS and settings.use_subdirs fname = sanitize(fname) - extra_components.append(fname) - extra_components = [str(x) for x in extra_components] + + if special_tag is None: + from calibre.library.save_to_disk import get_components + extra_components = get_components(template, mdata, fname, + replace_whitespace=True) + else: + tag = special_tag + if tag.startswith(_('News')): + extra_components.append('News') + c = sanitize(mdata.title if mdata.title else '') + c = c.split('[')[0].strip() + if c: + extra_components.append(c) + else: + for c in tag.split('/'): + c = sanitize(c) + if not c: continue + extra_components.append(c) + + + if not use_subdirs: + extra_components = extra_components[:1] + + if not extra_components: + fname = sanitize(self.filename_callback(fname, mdata)) + extra_components.append(fname) + extra_components = [str(x) for x in extra_components] + def remove_trailing_periods(x): ans = x while ans.endswith('.'): - ans = ans[:-1] + ans = ans[:-1].strip() if not ans: ans = 'x' return ans + extra_components = list(map(remove_trailing_periods, extra_components)) components = shorten_components_to(250 - len(path), extra_components) filepath = os.path.join(path, *components) diff --git a/src/calibre/devices/usbms/deviceconfig.py b/src/calibre/devices/usbms/deviceconfig.py index 8c5c1cf47d..96477dbece 100644 --- a/src/calibre/devices/usbms/deviceconfig.py +++ b/src/calibre/devices/usbms/deviceconfig.py @@ -6,12 +6,22 @@ __docformat__ = 'restructuredtext en' from calibre.utils.config import Config, ConfigProxy + class DeviceConfig(object): HELP_MESSAGE = _('Configure Device') EXTRA_CUSTOMIZATION_MESSAGE = None EXTRA_CUSTOMIZATION_DEFAULT = None + #: If None the default is used + SAVE_TEMPLATE = None + + @classmethod + def _default_save_template(cls): + from calibre.library.save_to_disk import config + return cls.SAVE_TEMPLATE if cls.SAVE_TEMPLATE else \ + config().parse().send_template + @classmethod def _config(cls): klass = cls if isinstance(cls, type) else cls.__class__ @@ -22,6 +32,8 @@ class DeviceConfig(object): help=_('Place files in sub directories if the device supports them')) c.add_opt('read_metadata', default=True, help=_('Read metadata from files on device')) + c.add_opt('save_template', default=cls._default_save_template(), + help=_('Template to control how books are saved')) c.add_opt('extra_customization', default=cls.EXTRA_CUSTOMIZATION_DEFAULT, help=_('Extra customization')) @@ -52,6 +64,8 @@ class DeviceConfig(object): if not ec: ec = None proxy['extra_customization'] = ec + st = unicode(config_widget.opt_save_template.text()) + proxy['save_template'] = st @classmethod def settings(cls): diff --git a/src/calibre/devices/usbms/driver.py b/src/calibre/devices/usbms/driver.py index 499c464937..fc39655396 100644 --- a/src/calibre/devices/usbms/driver.py +++ b/src/calibre/devices/usbms/driver.py @@ -123,7 +123,8 @@ class USBMS(CLI, Device): ''' :path: the full path were the associated book is located. :filename: the name of the book file without the extension. - :metatdata: metadata belonging to the book. metadata.cover[2] for coverdata. + :metatdata: metadata belonging to the book. Use metadata.thumbnail + for cover ''' pass diff --git a/src/calibre/gui2/__init__.py b/src/calibre/gui2/__init__.py index 22b3cf7462..317fe9c94a 100644 --- a/src/calibre/gui2/__init__.py +++ b/src/calibre/gui2/__init__.py @@ -73,6 +73,8 @@ def _config(): 'only take place when the Enter or Return key is pressed.') c.add_opt('save_to_disk_template_history', default=[], help='Previously used Save to Disk templates') + c.add_opt('send_to_device_template_history', default=[], + help='Previously used Send to Device templates') c.add_opt('main_search_history', default=[], help='Search history for the main GUI') c.add_opt('viewer_search_history', default=[], diff --git a/src/calibre/gui2/device.py b/src/calibre/gui2/device.py index 5720d87b5e..7f81b99223 100644 --- a/src/calibre/gui2/device.py +++ b/src/calibre/gui2/device.py @@ -536,8 +536,7 @@ class DeviceGUI(object): else: _auto_ids = [] - full_metadata = self.library_view.model().get_metadata( - ids, full_metadata=True, rows_are_ids=True)[-1] + full_metadata = self.library_view.model().metadata_for(ids) files = [getattr(f, 'name', None) for f in files] bad, remove_ids, jobnames = [], [], [] @@ -707,19 +706,17 @@ class DeviceGUI(object): if not files: dynamic.set('news_to_be_synced', set([])) return - metadata = self.library_view.model().get_metadata(ids, - rows_are_ids=True) + metadata = self.library_view.model().metadata_for(ids) names = [] for mi in metadata: - prefix = ascii_filename(mi['title']) + prefix = ascii_filename(mi.title) if not isinstance(prefix, unicode): prefix = prefix.decode(preferred_encoding, 'replace') prefix = ascii_filename(prefix) names.append('%s_%d%s'%(prefix, id, os.path.splitext(f.name)[1])) - cdata = mi['cover'] - if cdata: - mi['cover'] = self.cover_to_thumbnail(cdata) + if mi.cover_data and mi.cover_data[1]: + mi.thumbnail = self.cover_to_thumbnail(mi.cover_data[1]) dynamic.set('news_to_be_synced', set([])) if config['upload_news_to_device'] and files: remove = ids if \ @@ -751,29 +748,28 @@ class DeviceGUI(object): else: _auto_ids = [] - metadata = self.library_view.model().get_metadata(ids, True) + metadata = self.library_view.model().metadata_for(ids) ids = iter(ids) for mi in metadata: - cdata = mi['cover'] - if cdata: - mi['cover'] = self.cover_to_thumbnail(cdata) - metadata = iter(metadata) + if mi.cover_data and mi.cover_data[1]: + mi.thumbnail = self.cover_to_thumbnail(mi.cover_data[1]) + imetadata = iter(metadata) files = [getattr(f, 'name', None) for f in _files] bad, good, gf, names, remove_ids = [], [], [], [], [] for f in files: - mi = metadata.next() + mi = imetadata.next() id = ids.next() if f is None: - bad.append(mi['title']) + bad.append(mi.title) else: remove_ids.append(id) good.append(mi) gf.append(f) - t = mi['title'] + t = mi.title if not t: t = _('Unknown') - a = mi['authors'] + a = mi.format_authors() if not a: a = _('Unknown') prefix = ascii_filename(t+' - '+a) diff --git a/src/calibre/gui2/device_drivers/configwidget.py b/src/calibre/gui2/device_drivers/configwidget.py index 735a3d8c2e..cb821d89b9 100644 --- a/src/calibre/gui2/device_drivers/configwidget.py +++ b/src/calibre/gui2/device_drivers/configwidget.py @@ -46,6 +46,7 @@ class ConfigWidget(QWidget, Ui_ConfigWidget): else: self.extra_customization_label.setVisible(False) self.opt_extra_customization.setVisible(False) + self.opt_save_template.setText(settings.save_template) def up_column(self): diff --git a/src/calibre/gui2/device_drivers/configwidget.ui b/src/calibre/gui2/device_drivers/configwidget.ui index 12ce01549c..d007599424 100644 --- a/src/calibre/gui2/device_drivers/configwidget.ui +++ b/src/calibre/gui2/device_drivers/configwidget.ui @@ -90,7 +90,7 @@ </property> </widget> </item> - <item row="3" column="0"> + <item row="5" column="0"> <widget class="QLabel" name="extra_customization_label"> <property name="text"> <string>Extra customization</string> @@ -103,9 +103,22 @@ </property> </widget> </item> - <item row="4" column="0"> + <item row="6" column="0"> <widget class="QLineEdit" name="opt_extra_customization"/> </item> + <item row="3" column="0"> + <widget class="QLabel" name="label"> + <property name="text"> + <string>Save &template:</string> + </property> + <property name="buddy"> + <cstring>opt_save_template</cstring> + </property> + </widget> + </item> + <item row="4" column="0"> + <widget class="QLineEdit" name="opt_save_template"/> + </item> </layout> </widget> <resources> diff --git a/src/calibre/gui2/dialogs/config/add_save.py b/src/calibre/gui2/dialogs/config/add_save.py index 54d948044b..3c1e30ff01 100644 --- a/src/calibre/gui2/dialogs/config/add_save.py +++ b/src/calibre/gui2/dialogs/config/add_save.py @@ -11,9 +11,7 @@ import textwrap from PyQt4.Qt import QTabWidget from calibre.gui2.dialogs.config.add_save_ui import Ui_TabWidget -from calibre.library.save_to_disk import config, FORMAT_ARG_DESCS, \ - preprocess_template -from calibre.gui2 import error_dialog +from calibre.library.save_to_disk import config from calibre.utils.config import prefs from calibre.gui2.widgets import FilenamePattern @@ -22,8 +20,8 @@ class AddSave(QTabWidget, Ui_TabWidget): def __init__(self, parent=None): QTabWidget.__init__(self, parent) self.setupUi(self) - while self.count() > 2: - self.removeTab(2) + while self.count() > 3: + self.removeTab(3) c = config() opts = c.parse() for x in ('asciiize', 'update_metadata', 'save_cover', 'write_opf', @@ -41,36 +39,17 @@ class AddSave(QTabWidget, Ui_TabWidget): g.setToolTip(help) g.setWhatsThis(help) - help = '\n'.join(textwrap.wrap(c.get_option('template').help, 75)) - self.opt_template.initialize('save_to_disk_template_history', - opts.template, help) - - variables = sorted(FORMAT_ARG_DESCS.keys()) - rows = [] - for var in variables: - rows.append(u'<tr><td>%s</td><td>%s</td></tr>'% - (var, FORMAT_ARG_DESCS[var])) - table = u'<table>%s</table>'%(u'\n'.join(rows)) - self.template_variables.setText(table) self.opt_read_metadata_from_filename.setChecked(not prefs['read_file_metadata']) self.filename_pattern = FilenamePattern(self) self.metadata_box.layout().insertWidget(0, self.filename_pattern) self.opt_swap_author_names.setChecked(prefs['swap_author_names']) + help = '\n'.join(textwrap.wrap(c.get_option('template').help, 75)) + self.save_template.initialize('save_to_disk', opts.template, help) + self.send_template.initialize('send_to_device', opts.send_template, help) def validate(self): - tmpl = preprocess_template(self.opt_template.text()) - fa = {} - for x in FORMAT_ARG_DESCS.keys(): - fa[x]='' - try: - tmpl.format(**fa) - except Exception, err: - error_dialog(self, _('Invalid template'), - '<p>'+_('The template %s is invalid:')%tmpl + \ - '<br>'+str(err), show=True) - return False - return True + return self.save_template.validate() and self.send_template.validate() def save_settings(self): if not self.validate(): @@ -79,12 +58,13 @@ class AddSave(QTabWidget, Ui_TabWidget): for x in ('asciiize', 'update_metadata', 'save_cover', 'write_opf', 'replace_whitespace', 'to_lowercase'): c.set(x, getattr(self, 'opt_'+x).isChecked()) - for x in ('formats', 'template', 'timefmt'): + for x in ('formats', 'timefmt'): val = unicode(getattr(self, 'opt_'+x).text()).strip() if x == 'formats' and not val: val = 'all' c.set(x, val) - self.opt_template.save_history('save_to_disk_template_history') + self.save_template.save_settings(c, 'template') + self.send_template.save_settings(c, 'send_template') prefs['read_file_metadata'] = not bool(self.opt_read_metadata_from_filename.isChecked()) pattern = self.filename_pattern.commit() prefs['filename_pattern'] = pattern diff --git a/src/calibre/gui2/dialogs/config/add_save.ui b/src/calibre/gui2/dialogs/config/add_save.ui index 806f77dade..fbf9ceaf2a 100644 --- a/src/calibre/gui2/dialogs/config/add_save.ui +++ b/src/calibre/gui2/dialogs/config/add_save.ui @@ -141,38 +141,6 @@ <item row="6" column="1"> <widget class="QLineEdit" name="opt_formats"/> </item> - <item row="7" column="0" colspan="2"> - <widget class="QGroupBox" name="groupBox"> - <property name="title"> - <string>Save &template</string> - </property> - <layout class="QGridLayout" name="gridLayout_2"> - <item row="0" column="0"> - <widget class="QLabel" name="label_4"> - <property name="text"> - <string>By adjusting the template below, you can control what folders the files are saved in and what filenames they are given. You can use the / character to indicate sub-folders. Available metadata variables are described below. If a particular book does not have some metadata, the variable will be replaced by the empty string.</string> - </property> - <property name="wordWrap"> - <bool>true</bool> - </property> - </widget> - </item> - <item row="2" column="0"> - <widget class="QLabel" name="label_5"> - <property name="text"> - <string>Available variables:</string> - </property> - </widget> - </item> - <item row="3" column="0"> - <widget class="QTextBrowser" name="template_variables"/> - </item> - <item row="1" column="0"> - <widget class="HistoryBox" name="opt_template"/> - </item> - </layout> - </widget> - </item> <item row="1" column="1"> <widget class="QCheckBox" name="opt_replace_whitespace"> <property name="text"> @@ -187,14 +155,38 @@ </property> </widget> </item> + <item row="7" column="0" colspan="2"> + <widget class="SaveTemplate" name="save_template" native="true"/> + </item> + </layout> + </widget> + <widget class="QWidget" name="tab_2"> + <attribute name="title"> + <string>Sending to &device</string> + </attribute> + <layout class="QVBoxLayout" name="verticalLayout_2"> + <item> + <widget class="QLabel" name="label_4"> + <property name="text"> + <string>Here you can control how calibre will save your books when you click the Send to Device button. This setting can be overriden for individual devices by customizing the device interface plugins in Preferences->Plugins</string> + </property> + <property name="wordWrap"> + <bool>true</bool> + </property> + </widget> + </item> + <item> + <widget class="SaveTemplate" name="send_template" native="true"/> + </item> </layout> </widget> </widget> <customwidgets> <customwidget> - <class>HistoryBox</class> - <extends>QComboBox</extends> - <header>calibre/gui2/dialogs/config/history.h</header> + <class>SaveTemplate</class> + <extends>QWidget</extends> + <header>calibre/gui2/dialogs/config/save_template.h</header> + <container>1</container> </customwidget> </customwidgets> <resources/> diff --git a/src/calibre/gui2/dialogs/config/save_template.py b/src/calibre/gui2/dialogs/config/save_template.py new file mode 100644 index 0000000000..8fe36c430f --- /dev/null +++ b/src/calibre/gui2/dialogs/config/save_template.py @@ -0,0 +1,58 @@ +#!/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__ = '2009, Kovid Goyal <kovid@kovidgoyal.net>' +__docformat__ = 'restructuredtext en' + +from PyQt4.Qt import QWidget + +from calibre.gui2 import error_dialog +from calibre.gui2.dialogs.config.save_template_ui import Ui_Form +from calibre.library.save_to_disk import FORMAT_ARG_DESCS, \ + preprocess_template + +class SaveTemplate(QWidget, Ui_Form): + + def __init__(self, *args): + QWidget.__init__(self, *args) + Ui_Form.__init__(self) + self.setupUi(self) + + def initialize(self, name, default, help): + variables = sorted(FORMAT_ARG_DESCS.keys()) + rows = [] + for var in variables: + rows.append(u'<tr><td>%s</td><td>%s</td></tr>'% + (var, FORMAT_ARG_DESCS[var])) + table = u'<table>%s</table>'%(u'\n'.join(rows)) + self.template_variables.setText(table) + + self.opt_template.initialize(name+'_template_history', + default, help) + self.option_name = name + + def validate(self): + tmpl = preprocess_template(self.opt_template.text()) + fa = {} + for x in FORMAT_ARG_DESCS.keys(): + fa[x]='' + try: + tmpl.format(**fa) + except Exception, err: + error_dialog(self, _('Invalid template'), + '<p>'+_('The template %s is invalid:')%tmpl + \ + '<br>'+str(err), show=True) + return False + return True + + def save_settings(self, config, name): + val = unicode(self.opt_template.text()) + config.set(name, val) + self.opt_template.save_history(self.option_name+'_template_history') + + + + + diff --git a/src/calibre/gui2/dialogs/config/save_template.ui b/src/calibre/gui2/dialogs/config/save_template.ui new file mode 100644 index 0000000000..02506891bb --- /dev/null +++ b/src/calibre/gui2/dialogs/config/save_template.ui @@ -0,0 +1,60 @@ +<?xml version="1.0" encoding="UTF-8"?> +<ui version="4.0"> + <class>Form</class> + <widget class="QWidget" name="Form"> + <property name="geometry"> + <rect> + <x>0</x> + <y>0</y> + <width>400</width> + <height>300</height> + </rect> + </property> + <property name="windowTitle"> + <string>Form</string> + </property> + <layout class="QVBoxLayout" name="verticalLayout"> + <item> + <widget class="QGroupBox" name="groupBox"> + <property name="title"> + <string>Save &template</string> + </property> + <layout class="QGridLayout" name="gridLayout_2"> + <item row="0" column="0"> + <widget class="QLabel" name="label_4"> + <property name="text"> + <string>By adjusting the template below, you can control what folders the files are saved in and what filenames they are given. You can use the / character to indicate sub-folders. Available metadata variables are described below. If a particular book does not have some metadata, the variable will be replaced by the empty string.</string> + </property> + <property name="wordWrap"> + <bool>true</bool> + </property> + </widget> + </item> + <item row="2" column="0"> + <widget class="QLabel" name="label_5"> + <property name="text"> + <string>Available variables:</string> + </property> + </widget> + </item> + <item row="3" column="0"> + <widget class="QTextBrowser" name="template_variables"/> + </item> + <item row="1" column="0"> + <widget class="HistoryBox" name="opt_template"/> + </item> + </layout> + </widget> + </item> + </layout> + </widget> + <customwidgets> + <customwidget> + <class>HistoryBox</class> + <extends>QComboBox</extends> + <header>calibre/gui2/dialogs/config/history.h</header> + </customwidget> + </customwidgets> + <resources/> + <connections/> +</ui> diff --git a/src/calibre/gui2/library.py b/src/calibre/gui2/library.py index 55d6d94967..6463d11daa 100644 --- a/src/calibre/gui2/library.py +++ b/src/calibre/gui2/library.py @@ -399,6 +399,8 @@ class BooksModel(QAbstractTableModel): data[_('Author(s)')] = au return data + def metadata_for(self, ids): + return [self.db.get_metadata(id, index_is_id=True) for id in ids] def get_metadata(self, rows, rows_are_ids=False, full_metadata=False): metadata, _full_metadata = [], [] diff --git a/src/calibre/library/save_to_disk.py b/src/calibre/library/save_to_disk.py index e169222bb5..537d7db7c1 100644 --- a/src/calibre/library/save_to_disk.py +++ b/src/calibre/library/save_to_disk.py @@ -18,6 +18,8 @@ from calibre.constants import preferred_encoding, filesystem_encoding from calibre import strftime DEFAULT_TEMPLATE = '{author_sort}/{title}/{title} - {authors}' +DEFAULT_SEND_TEMPLATE = '{author_sort}/{title} - {authors}' + FORMAT_ARG_DESCS = dict( title=_('The title'), authors=_('The authors'), @@ -62,6 +64,13 @@ def config(defaults=None): 'Default is "%s" which will save books into a per-author ' 'subdirectory with filenames containing title and author. ' 'Available controls are: {%s}')%(DEFAULT_TEMPLATE, ', '.join(FORMAT_ARGS))) + x('send_template', default=DEFAULT_SEND_TEMPLATE, + help=_('The template to control the filename and directory structure of files ' + 'sent to the device. ' + 'Default is "%s" which will save books into a per-author ' + 'directory with filenames containing title and author. ' + 'Available controls are: {%s}')%(DEFAULT_SEND_TEMPLATE, ', '.join(FORMAT_ARGS))) + x('asciiize', default=True, help=_('Normally, calibre will convert all non English characters into English equivalents ' 'for the file names. ' From 9a28d7017f63a12fc9dc698a7b32e552a4384428 Mon Sep 17 00:00:00 2001 From: Kovid Goyal <kovid@kovidgoyal.net> Date: Fri, 1 Jan 2010 09:55:20 -0700 Subject: [PATCH 15/17] New recipes for Cyprus Weekly by kwetal and RTE by Robin Phillips --- resources/recipes/cyprus_weekly.recipe | 108 +++++++++++++++++++++++++ resources/recipes/rte.recipe | 15 ++++ src/calibre/devices/usbms/device.py | 3 + src/calibre/gui2/device.py | 2 +- 4 files changed, 127 insertions(+), 1 deletion(-) create mode 100644 resources/recipes/cyprus_weekly.recipe create mode 100644 resources/recipes/rte.recipe diff --git a/resources/recipes/cyprus_weekly.recipe b/resources/recipes/cyprus_weekly.recipe new file mode 100644 index 0000000000..d8762609a2 --- /dev/null +++ b/resources/recipes/cyprus_weekly.recipe @@ -0,0 +1,108 @@ +from calibre.web.feeds.news import BasicNewsRecipe +from datetime import datetime, timedelta + +class CyNewsLiveRecipe(BasicNewsRecipe): + __license__ = 'GPL v3' + __author__ = 'kwetal' + language = 'en_CY' + version = 1 + + title = u'Cyprus Weekly' + publisher = u'The Cyprus Weekly' + category = u'News, Newspaper' + description = u'News from Cyprus' + + use_embedded_content = False + remove_empty_feeds = True + oldest_article = 7 + max_articles_per_feed = 100 + + no_stylesheets = True + remove_javascript = True + + pubTime = None + minTime = None + articleCount = 0 + + INDEX = 'http://www.cyprusweekly.com.cy/main/default.aspx' + + feeds = [] + feeds.append(('News: Cyprus', 'http://www.cyprusweekly.com.cy/main/92,0,0,0-CYPRUS.aspx')) + feeds.append(('News: World', 'http://www.cyprusweekly.com.cy/main/78,0,0,0-UKWORLD.aspx')) + feeds.append(('Sport: Football', 'http://www.cyprusweekly.com.cy/main/82,0,0,0-FOOTBALL.aspx')) + feeds.append(('Sport: Rugby', 'http://www.cyprusweekly.com.cy/main/83,0,0,0-RUGBY.aspx')) + feeds.append(('Sport: Cricket', 'http://www.cyprusweekly.com.cy/main/85,0,0,0-CRICKET.aspx')) + feeds.append(('Sport: Tennis', 'http://www.cyprusweekly.com.cy/main/84,0,0,0-TENNIS.aspx')) + feeds.append(('Sport: Other', 'http://www.cyprusweekly.com.cy/main/86,0,0,0-OTHER.aspx')) + feeds.append(('Business: Local', 'http://www.cyprusweekly.com.cy/main/100,0,0,0-LOCAL.aspx')) + feeds.append(('Business: Foreign', 'http://www.cyprusweekly.com.cy/main/101,0,0,0-FOREIGN.aspx')) + feeds.append(('Whats On: Places of Interest', 'http://www.cyprusweekly.com.cy/main/123,0,0,0-PLACES-OF-INTEREST.aspx')) + feeds.append(('Whats On: Going Out', 'http://www.cyprusweekly.com.cy/main/153,0,0,0-GOING-OUT.aspx')) + feeds.append(('Whats On: Arts & Entertainment', 'http://www.cyprusweekly.com.cy/main/135,0,0,0-ARTS--and-ENTERTAINMENT.aspx')) + feeds.append(('Whats On: Things To Do', 'http://www.cyprusweekly.com.cy/main/136,0,0,0-THINGS-TO-DO.aspx')) + feeds.append(('Whats On: Shopping Guide', 'http://www.cyprusweekly.com.cy/main/142,0,0,0-SHOPPING-GUIDE.aspx')) + feeds.append(('Culture', 'http://www.cyprusweekly.com.cy/main/208,0,0,0-CULTURE.aspx')) + feeds.append(('Environment', 'http://www.cyprusweekly.com.cy/main/93,0,0,0-ENVIRONMENT.aspx')) + feeds.append(('Info', 'http://www.cyprusweekly.com.cy/main/91,0,0,0-INFO.aspx')) + + keep_only_tags = [] + keep_only_tags.append(dict(name = 'div', attrs = {'class': 'ArticleCategories'})) + + extra_css = ''' + body{font-family:verdana,arial,helvetica,geneva,sans-serif ;} + ''' + + def parse_index(self): + answer = [] + for feed in self.feeds: + self.articleCount = 0 + articles = [] + soup = self.index_to_soup(feed[1]) + + table = soup.find('table', attrs = {'id': 'ctl00_cp_ctl01_listp'}) + if table: + self.pubTime = datetime.now() + self.minTime = self.pubTime - timedelta(days = self.oldest_article) + + self.find_articles(table, articles) + + answer.append((feed[0], articles)) + + return answer + + def postprocess_html(self, soup, first): + for el in soup.findAll(attrs = {'style': True}): + del el['style'] + + for el in soup.findAll('font'): + el.name = 'div' + for attr, value in el: + del el[attr] + + return soup + + def find_articles(self, table, articles): + for div in table.findAll('div', attrs = {'class': 'ListArticle'}): + el = div.find('div', attrs = {'class': 'ListArticle_T'}) + title = self.tag_to_string(el.a) + url = self.INDEX + el.a['href'] + + description = self.tag_to_string(div.find('div', attrs = {'class': 'ListArticle_BODY300'})) + + el = div.find('div', attrs = {'class': 'ListArticle_D'}) + if el: + dateParts = self.tag_to_string(el).split(' ') + monthNames = {'January': 1, 'February': 2, 'March': 3, 'April': 4, 'May': 5, 'June': 6, + 'July': 7, 'August': 8, 'September': 9, 'October': 10, 'November': 11, + 'December': 12} + timeParts = dateParts[3].split(':') + self.pubTime = datetime(year = int(dateParts[2]), month = int(monthNames[dateParts[1]]), + day = int(dateParts[0]), hour = int(timeParts[0]), + minute = int(timeParts[1])) + + if self.pubTime >= self.minTime and self.articleCount <= self.max_articles_per_feed: + articles.append({'title': title, 'date': self.pubTime, 'url': url, 'description': description}) + self.articleCount += 1 + else: + return + diff --git a/resources/recipes/rte.recipe b/resources/recipes/rte.recipe new file mode 100644 index 0000000000..eba684115f --- /dev/null +++ b/resources/recipes/rte.recipe @@ -0,0 +1,15 @@ +from calibre.web.feeds.news import BasicNewsRecipe + +class RTE(BasicNewsRecipe): + title = u'RTE News' + oldest_article = 7 + max_articles_per_feed = 100 + __author__ = u'Robin Phillips' + language = 'en_GB' + + remove_tags = [dict(attrs={'class':['topAd','botad','previousNextItem','headline','footerLinks','footernav']})] + + feeds = [(u'News', u'http://www.rte.ie/rss/news.xml'), (u'Sport', u'http://www.rte.ie/rss/sport.xml'), (u'Soccer', u'http://www.rte.ie/rss/soccer.xml'), (u'GAA', u'http://www.rte.ie/rss/gaa.xml'), (u'Rugby', u'http://www.rte.ie/rss/rugby.xml'), (u'Racing', u'http://www.rte.ie/rss/racing.xml'), (u'Business', u'http://www.rte.ie/rss/business.xml'), (u'Entertainment', u'http://www.rte.ie/rss/entertainment.xml')] + + def print_version(self, url): + return url.replace('http://www', 'http://m') diff --git a/src/calibre/devices/usbms/device.py b/src/calibre/devices/usbms/device.py index 5e74904bc9..095f01ee21 100644 --- a/src/calibre/devices/usbms/device.py +++ b/src/calibre/devices/usbms/device.py @@ -865,6 +865,7 @@ class Device(DeviceConfig, DevicePlugin): use_subdirs = self.SUPPORTS_SUB_DIRS and settings.use_subdirs fname = sanitize(fname) + ext = os.path.splitext(fname)[1] if special_tag is None: from calibre.library.save_to_disk import get_components @@ -892,6 +893,8 @@ class Device(DeviceConfig, DevicePlugin): fname = sanitize(self.filename_callback(fname, mdata)) extra_components.append(fname) extra_components = [str(x) for x in extra_components] + else: + extra_components[-1] += ext def remove_trailing_periods(x): ans = x diff --git a/src/calibre/gui2/device.py b/src/calibre/gui2/device.py index 7f81b99223..a8c30f9532 100644 --- a/src/calibre/gui2/device.py +++ b/src/calibre/gui2/device.py @@ -846,7 +846,7 @@ class DeviceGUI(object): Upload books to device. :param files: List of either paths to files or file like objects ''' - titles = [i['title'] for i in metadata] + titles = [i.title for i in metadata] job = self.device_manager.upload_books( Dispatcher(self.books_uploaded), files, names, on_card=on_card, From c065a4d8fa660b948a4267fe66873c8a3e142299 Mon Sep 17 00:00:00 2001 From: Kovid Goyal <kovid@kovidgoyal.net> Date: Fri, 1 Jan 2010 10:51:00 -0700 Subject: [PATCH 16/17] Various fixes needed for the new metainformation based drivers --- src/calibre/devices/cybookg3/driver.py | 4 ++-- src/calibre/devices/interface.py | 17 +++++------------ src/calibre/devices/nook/driver.py | 4 ++-- src/calibre/devices/prs505/books.py | 25 +++++++++++++++---------- src/calibre/gui2/library.py | 8 +++++++- 5 files changed, 31 insertions(+), 27 deletions(-) diff --git a/src/calibre/devices/cybookg3/driver.py b/src/calibre/devices/cybookg3/driver.py index cdc41d3aca..8f58c65ae7 100644 --- a/src/calibre/devices/cybookg3/driver.py +++ b/src/calibre/devices/cybookg3/driver.py @@ -48,9 +48,9 @@ class CYBOOKG3(USBMS): def upload_cover(self, path, filename, metadata): coverdata = getattr(metadata, 'thumbnail', None) - if coverdata: + if coverdata and coverdata[2]: with open('%s_6090.t2b' % os.path.join(path, filename), 'wb') as t2bfile: - t2b.write_t2b(t2bfile, coverdata) + t2b.write_t2b(t2bfile, coverdata[2]) @classmethod def can_handle(cls, device_info, debug=False): diff --git a/src/calibre/devices/interface.py b/src/calibre/devices/interface.py index d8c22170f5..a774ed37ed 100644 --- a/src/calibre/devices/interface.py +++ b/src/calibre/devices/interface.py @@ -276,7 +276,9 @@ class DevicePlugin(Plugin): The idea is to use the metadata to determine where on the device to put the book. len(metadata) == len(files). Apart from the regular cover_data, there may also be a thumbnail attribute, which should - be used in preference. + be used in preference. The thumbnail attribute is of the form + (width, height, cover_data as jpeg). In addition the MetaInformation + objects can have a tag_order attribute. ''' raise NotImplementedError() @@ -286,17 +288,8 @@ class DevicePlugin(Plugin): Add locations to the booklists. This function must not communicate with the device. @param locations: Result of a call to L{upload_books} - @param metadata: List of dictionaries. Each dictionary must have the - keys C{title}, C{authors}, C{author_sort}, C{cover}, C{tags}. - The value of the C{cover} - element can be None or a three element tuple (width, height, data) - where data is the image data in JPEG format as a string. C{tags} must be - a possibly empty list of strings. C{authors} must be a string. - C{author_sort} may be None. It is upto the driver to decide whether to - use C{author_sort} or not. - The dictionary can also have an optional key "tag order" which should be - another dictionary that maps tag names to lists of book ids. The ids are - ids from the book database. + @param metadata: List of MetaInformation objects, same as for + :method:`upload_books`. @param booklists: A tuple containing the result of calls to (L{books}(oncard=None), L{books}(oncard='carda'), L{books}(oncard='cardb')). diff --git a/src/calibre/devices/nook/driver.py b/src/calibre/devices/nook/driver.py index fc28363bff..c74a964648 100644 --- a/src/calibre/devices/nook/driver.py +++ b/src/calibre/devices/nook/driver.py @@ -54,8 +54,8 @@ class NOOK(USBMS): coverdata = getattr(metadata, 'thumbnail', None) - if coverdata: - cover = Image.open(cStringIO.StringIO(coverdata)) + if coverdata and coverdata[2]: + cover = Image.open(cStringIO.StringIO(coverdata[2])) else: coverdata = open(I('library.png'), 'rb').read() diff --git a/src/calibre/devices/prs505/books.py b/src/calibre/devices/prs505/books.py index 6e268e734a..227356bda9 100644 --- a/src/calibre/devices/prs505/books.py +++ b/src/calibre/devices/prs505/books.py @@ -180,7 +180,7 @@ class BookList(_BookList): return child return None - def add_book(self, info, name, size, ctime): + def add_book(self, mi, name, size, ctime): """ Add a node into the DOM tree, representing a book """ book = self.book_by_path(name) if book is not None: @@ -194,9 +194,9 @@ class BookList(_BookList): except: sourceid = '1' attrs = { - "title" : info["title"], - 'titleSorter' : sortable_title(info['title']), - "author" : info["authors"] if info['authors'] else _('Unknown'), + "title" : mi.title, + 'titleSorter' : sortable_title(mi.title), + "author" : mi.format_authors() if mi.format_authors() else _('Unknown'), "page":"0", "part":"0", "scale":"0", \ "sourceid":sourceid, "id":str(cid), "date":"", \ "mime":mime, "path":name, "size":str(size) @@ -205,8 +205,8 @@ class BookList(_BookList): node.setAttributeNode(self.document.createAttribute(attr)) node.setAttribute(attr, attrs[attr]) try: - w, h, data = info["cover"] - except TypeError: + w, h, data = mi.thumbnail + except: w, h, data = None, None, None if data: @@ -221,10 +221,15 @@ class BookList(_BookList): book = Book(node, self.mountpath, [], prefix=self.prefix) book.datetime = ctime self.append(book) - if info.has_key('tags'): - if info.has_key('tag order'): - self.tag_order.update(info['tag order']) - self.set_tags(book, info['tags']) + tags = [] + if mi.tags: + tags.extend(mi.tags) + if mi.series: + tags.append(mi.series) + if tags: + if hasattr(mi, 'tag_order'): + self.tag_order.update(mi.tag_order) + self.set_tags(book, tags) def _delete_book(self, node): nid = node.getAttribute('id') diff --git a/src/calibre/gui2/library.py b/src/calibre/gui2/library.py index 6463d11daa..70f75917be 100644 --- a/src/calibre/gui2/library.py +++ b/src/calibre/gui2/library.py @@ -400,7 +400,13 @@ class BooksModel(QAbstractTableModel): return data def metadata_for(self, ids): - return [self.db.get_metadata(id, index_is_id=True) for id in ids] + ans = [] + for id in ids: + mi = self.db.get_metadata(id, index_is_id=True) + if mi.series is not None: + mi.tag_order = self.db.books_in_series_of(id, index_is_id=True) + ans.append(mi) + return ans def get_metadata(self, rows, rows_are_ids=False, full_metadata=False): metadata, _full_metadata = [], [] From 89a8ca7e8d29e88f574514175107d3f811ce8895 Mon Sep 17 00:00:00 2001 From: Kovid Goyal <kovid@kovidgoyal.net> Date: Fri, 1 Jan 2010 10:53:15 -0700 Subject: [PATCH 17/17] ... --- src/calibre/devices/prs500/books.py | 24 ++++++++++++++---------- 1 file changed, 14 insertions(+), 10 deletions(-) diff --git a/src/calibre/devices/prs500/books.py b/src/calibre/devices/prs500/books.py index 5eb8d7f011..07f9310e87 100644 --- a/src/calibre/devices/prs500/books.py +++ b/src/calibre/devices/prs500/books.py @@ -252,7 +252,7 @@ class BookList(_BookList): return child return None - def add_book(self, info, name, size, ctime): + def add_book(self, mi, name, size, ctime): """ Add a node into DOM tree representing a book """ book = self.book_by_path(name) if book is not None: @@ -262,9 +262,9 @@ class BookList(_BookList): cid = self.max_id()+1 sourceid = str(self[0].sourceid) if len(self) else "1" attrs = { - "title" : info["title"], - 'titleSorter' : sortable_title(info['title']), - "author" : info["authors"] if info['authors'] else 'Unknown', \ + "title" : mi.title, + 'titleSorter' : sortable_title(mi.title), + "author" : mi.format_authors() if mi.format_authors() else _('Unknown'), "page":"0", "part":"0", "scale":"0", \ "sourceid":sourceid, "id":str(cid), "date":"", \ "mime":mime, "path":name, "size":str(size) @@ -273,7 +273,7 @@ class BookList(_BookList): node.setAttributeNode(self.document.createAttribute(attr)) node.setAttribute(attr, attrs[attr]) try: - w, h, data = info["cover"] + w, h, data = mi.thumbnail except TypeError: w, h, data = None, None, None @@ -290,11 +290,15 @@ class BookList(_BookList): book.datetime = ctime self.append(book) self.set_next_id(cid+1) - if self.prefix and info.has_key('tags'): # Playlists only supportted in main memory - if info.has_key('tag order'): - self.tag_order.update(info['tag order']) - self.set_playlists(book.id, info['tags']) - + tags = [] + if mi.tags: + tags.extend(mi.tags) + if mi.series: + tags.append(mi.series) + if self.prefix and tags: # Playlists only supportted in main memory + if hasattr(mi, 'tag_order'): + self.tag_order.update(mi.tag_order) + self.set_tags(book, tags) def playlist_by_title(self, title): for pl in self.playlists():