diff --git a/Changelog.yaml b/Changelog.yaml index 7dde319f65..38b63df60e 100644 --- a/Changelog.yaml +++ b/Changelog.yaml @@ -5,7 +5,7 @@ # Also, each release can have new and improved recipes. # - version: ?.?.? -# date: 2012-??-?? +# date: 2013-??-?? # # new features: # - title: @@ -19,6 +19,275 @@ # new recipes: # - title: +- version: 0.9.14 + date: 2013-01-11 + + new features: + - title: "When adding multiple books and duplicates are found, allow the user to select which of the duplicate books will be added anyway." + tickets: [1095256] + + - title: "Device drivers for Kobo Arc on linux, Polaroid Android tablet" + tickets: [1098049] + + - title: "When sorting by series, use the language of the book to decide what leading articles to remove, just as is done for sorting by title" + + bug fixes: + - title: "PDF Output: Do not error out when the input document contains links with anchors not present in the document." + tickets: [1096428] + + - title: "Add support for upgraded db on newest Kobo firmware" + tickets: [1095617] + + - title: "PDF Output: Fix typo that broke use of custom paper sizes." + tickets: [1097563] + + - title: "PDF Output: Handle empty anchors present at the end of a page" + + - title: "PDF Output: Fix side margins of last page in a flow being incorrect when large side margins are used." + tickets: [1096290] + + - title: "Edit metadata dialog: Allow setting the series number for custom series type columns to zero" + + - title: "When bulk editing custom series-type columns and not provding a series number use 1 as the default, instead of None" + + - title: "Catalogs: Fix issue with catalog generation using Hungarian UI and author_sort beginning with multiple letter groups." + tickets: [1091581] + + - title: "PDF Output: Dont error out on files that have invalid font-family declarations." + tickets: [1096279] + + - title: "Do not load QRawFont at global level, to allow calibre installation on systems with missing dependencies" + tickets: [1096170] + + - title: "PDF Output: Fix cover not present in generated PDF files" + tickets: [1096098] + + improved recipes: + - Sueddeutsche Zeitung mobil + - Boerse Online + - TidBits + - New York Review of Books + - Fleshbot + - Il Messaggero + - Libero + + new recipes: + - title: Spectator Magazine, Oxford Mail and Outside Magazine + author: Krittika Goyal + + - title: Libartes + author: Darko Miletic + + - title: El Diplo + author: Tomas De Domenico + +- version: 0.9.13 + date: 2013-01-04 + + new features: + - title: "Complete rewrite of the PDF Output engine, to support links and fix various bugs" + type: major + description: "calibre now has a new PDF output engine that supports links in the text. It also fixes various bugs, detailed below. In order to implement support for links and fix these bugs, the engine had to be completely rewritten, so there may be some regressions." + + - title: "Show disabled device plugins in Preferences->Ignored Devices" + + - title: "Get Books: Fix Smashwords, Google books and B&N stores. Add Nook UK store" + + - title: "Allow series numbers lower than -100 for custom series columns." + tickets: [1094475] + + - title: "Add mass storage driver for rockhip based android smart phones" + tickets: [1087809] + + - title: "Add a clear ratings button to the edit metadata dialog" + + bug fixes: + - title: "PDF Output: Fix custom page sizes not working on OS X" + + - title: "PDF Output: Fix embedding of many fonts not supported (note that embedding of OpenType fonts with Postscript outlines is still not supported on windows, though it is supported on other operating systems)" + + - title: "PDF Output: Fix crashes converting some books to PDF on OS X" + tickets: [1087688] + + - title: "HTML Input: Handle entities inside href attributes when following the links in an HTML file." + tickets: [1094203] + + - title: "Content server: Fix custom icons not used for sub categories" + tickets: [1095016] + + - title: "Force use of non-unicode constants in compiled templates. Fixes a problem with regular expression character classes and probably other things." + + - title: "Kobo driver: Do not error out if there are invalid dates in the device database" + tickets: [1094597] + + - title: "Content server: Fix for non-unicode hostnames when using mDNS" + tickets: [1094063] + + improved recipes: + - Today's Zaman + - The Economist + - Foreign Affairs + - New York Times + - Alternet + - Harper's Magazine + - La Stampa + +- version: 0.9.12 + date: 2012-12-28 + + new features: + - title: "Drivers for Kibano e-reader and Slick ER-700-2" + tickets: [1093570, 1093732] + + - title: "Add support for downloading metadata from Amazon Brazil." + tickets: [1092594] + + - title: "Copy to library: Allow specifying the destination library by path." + tickets: [1093231] + + - title: "When adding empty books, allow setting of the series for the new books. Also select the newly added book records after adding." + + - title: "PDF Output: Add a checkbox to override the page size defined by the output profile. This allows you to specify a custom page size even if the output profile is not set to default." + + - title: "Add usb ids for newer kindle fire to the linux mtp driver" + + bug fixes: + - title: "Linux: Temporarily redirect stdout to get rid of the annoying and pointless message about mtpz during libmtp initialization" + + - title: "Fix multiple 'All column' coloring rules not being applied" + tickets: [1093574] + + - title: "Use custom icons in the content server as well." + tickets: [1092098] + + improved recipes: + - La Voce + - Harpers Magazine (printed edition) + - Pajamas Media + - NSFW corp + - The Hindu + - Nikkei News + + new recipes: + - title: Various Ukranian news sources + author: rpalyvoda + +- version: 0.9.11 + date: 2012-12-21 + + new features: + - title: "Merry Christmas and Happy Holidays to all ☺" + + - title: "When connecting to MTP devices such as the Kindle Fire HD or the Nook HD, speed up the process by ignoring some folders." + description: "calibre will now ignore folders for music, video, pictures, etc. when scanning the device. This can substantially speed up the connection process if you have thousands of non-ebook files on the device. The list of folders to be ignored can be customized by right clicking on the device icon in calibre and selecting 'Configure this device'." + + - title: "Allow changing the icons for categories in the Tag Browser. Right click on a category and choose 'Change category icon'." + tickets: [1092098] + + - title: "Allow setting the color of all columns with a single rule in Preferences->Look & Feel->Column Coloring" + + - title: "MOBI: When reading metadata from mobi files, put the contents of the ASIN field into an identifier named mobi-asin. Note that this value is not used when downloading metadata as it is not possible to know which (country specific) amazon website the ASIN comes from." + tickets: [1090394] + + bug fixes: + - title: "Windows build: Fix a regression in 0.9.9 that caused calibre to not start on some windows system that were missing the VC.90 dlls (some older XP systems)" + + - title: "Kobo driver: Workaround for invalid shelves created by bugs in the Kobo server" + tickets: [1091932] + + - title: "Metadata download: Fix cover downloading from non-US amazon sites broken by a website change." + tickets: [1090765] + + improved recipes: + - Le Devoir + - Nin online + - countryfile + - Birmingham Post + - The Independent + - Various Polish news sources + + new recipes: + - title: MobileBulgaria + author: Martin Tsanchev + + - title: Various Polish news sources + author: fenuks + +- version: 0.9.10 + date: 2012-12-14 + + new features: + - title: "Drivers for Nextbook Premium 8 se, HTC Desire X and Emerson EM 543" + tickets: [1088149, 1088112, 1087978] + + bug fixes: + - title: "Fix rich text delegate not working with Qt compiled in debug mode." + tickets: [1089011] + + - title: "When deleting all books in the library, blank the book details panel" + + - title: "Conversion: Fix malformed values in the bgcolor attribute causing conversion to abort" + + - title: "Conversion: Fix heuristics applying incorrect style in some circumstances" + tickets: [1066507] + + - title: "Possible fix for 64bit calibre not starting up on some Windows systems" + tickets: [1087816] + + improved recipes: + - Sivil Dusunce + - Anchorage Daily News + - Le Monde + - Harpers + + new recipes: + - title: Titanic + author: Krittika Goyal + +- version: 0.9.9 + date: 2012-12-07 + + new features: + - title: "64 bit build for windows" + type: major + description: "calibre now has a 64 bit version for windows, available at: http://calibre-ebook.com/download_windows64 The 64bit build is not limited to using only 3GB of RAM when converting large/complex documents. It may also be slightly faster for some tasks. You can have both the 32 bit and the 64 bit build installed at the same time, they will use the same libraries, plugins and settings." + + - title: "Content server: Make the identifiers in each books metadata clickable." + tickets: [1085726] + + bug fixes: + - title: "EPUB Input: Fix an infinite loop while trying to recover a damaged EPUB file." + tickets: [1086917] + + - title: "KF8 Input: Fix handling of links in files that link to the obsolete tags instead of tags with an id attribute." + tickets: [1086705] + + - title: "Conversion: Fix a bug in removal of invalid entries from the spine, where not all invalid entries were removed, causing conversion to fail." + tickets: [1086054] + + - title: "KF8 Input: Ignore invalid flow references in the KF8 document instead of erroring out on them." + tickets: [1085306] + + - title: "Fix command line output on linux systems with incorrect LANG/LC_TYPE env vars." + tickets: [1085103] + + - title: "KF8 Input: Fix page breaks specified using the data-AmznPageBreak attribute being ignored by calibre." + + - title: "PDF Output: Fix custom size field not accepting fractional numbers as sizes" + + - title: "Get Books: Update libre.de and publio for website changes" + + - title: "Wireless driver: Increase timeout interval, and when allocating a random port try 9090 first" + + improved recipes: + - New York Times + - Weblogs SL + - Zaman Gazetesi + - Aksiyon Dergisi + - Endgadget + - Metro UK + - Heise Online + - version: 0.9.8 date: 2012-11-30 diff --git a/README b/README index 2ffab4e2f6..a1e3081988 100644 --- a/README +++ b/README @@ -1,7 +1,7 @@ -calibre is an e-book library manager. It can view, convert and catalog e-books \ -in most of the major e-book formats. It can also talk to e-book reader \ -devices. It can go out to the internet and fetch metadata for your books. \ -It can download newspapers and convert them into e-books for convenient \ +calibre is an e-book library manager. It can view, convert and catalog e-books +in most of the major e-book formats. It can also talk to e-book reader +devices. It can go out to the internet and fetch metadata for your books. +It can download newspapers and convert them into e-books for convenient reading. It is cross platform, running on Linux, Windows and OS X. For screenshots: https://calibre-ebook.com/demo @@ -15,5 +15,5 @@ bzr branch lp:calibre To update your copy of the source code: bzr merge -Tarballs of the source code for each release are now available \ +Tarballs of the source code for each release are now available at http://code.google.com/p/calibre-ebook diff --git a/manual/develop.rst b/manual/develop.rst index b9fba195d3..719c876b33 100644 --- a/manual/develop.rst +++ b/manual/develop.rst @@ -49,7 +49,7 @@ All the |app| python code is in the ``calibre`` package. This package contains t * Metadata reading, writing, and downloading is all in ebooks.metadata * Conversion happens in a pipeline, for the structure of the pipeline, see :ref:`conversion-introduction`. The pipeline consists of an input - plugin, various transforms and an output plugin. The code constructs + plugin, various transforms and an output plugin. The that code constructs and drives the pipeline is in plumber.py. The pipeline works on a representation of an ebook that is like an unzipped epub, with manifest, spine, toc, guide, html content, etc. The @@ -74,10 +74,6 @@ After installing Bazaar, you can get the |app| source code with the command:: On Windows you will need the complete path name, that will be something like :file:`C:\\Program Files\\Bazaar\\bzr.exe`. -To update a branch to the latest code, use the command:: - - bzr merge - |app| is a very large project with a very long source control history, so the above can take a while (10mins to an hour depending on your internet speed). @@ -88,6 +84,11 @@ using:: bzr branch --stacked lp:calibre + +To update a branch to the latest code, use the command:: + + bzr merge + Submitting your changes to be included ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ diff --git a/manual/faq.rst b/manual/faq.rst index 109aff440d..215b71e860 100644 --- a/manual/faq.rst +++ b/manual/faq.rst @@ -162,7 +162,8 @@ Follow these steps to find the problem: * If you are connecting an Apple iDevice (iPad, iPod Touch, iPhone), use the 'Connect to iTunes' method in the 'Getting started' instructions in `Calibre + Apple iDevices: Start here `_. * Make sure you are running the latest version of |app|. The latest version can always be downloaded from `the calibre website `_. * Ensure your operating system is seeing the device. That is, the device should show up in Windows Explorer (in Windows) or Finder (in OS X). - * In |app|, go to Preferences->Plugins->Device Interface plugin and make sure the plugin for your device is enabled, the plugin icon next to it should be green when it is enabled. + * In |app|, go to Preferences->Ignored Devices and check that your device + is not being ignored * If all the above steps fail, go to Preferences->Miscellaneous and click debug device detection with your device attached and post the output as a ticket on `the calibre bug tracker `_. My device is non-standard or unusual. What can I do to connect to it? @@ -436,10 +437,10 @@ that allows you to create collections on your Kindle from the |app| metadata. It .. note:: Amazon have removed the ability to manipulate collections completely in their newer models, like the Kindle Touch and Kindle Fire, making even the above plugin useless. If you really want the ability to manage collections on your Kindle via a USB connection, we encourage you to complain to Amazon about it, or get a reader where this is supported, like the SONY or Kobo Readers. -I am getting an error when I try to use |app| with my Kobo Touch? -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +I am getting an error when I try to use |app| with my Kobo Touch/Glo/etc.? +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -The Kobo Touch has very buggy firmware. Connecting to it has been known to fail at random. Certain combinations of motherboard, USB ports/cables/hubs can exacerbate this tendency to fail. If you are getting an error when connecting to your touch with |app| try the following, each of which has solved the problem for *some* |app| users. +The Kobo has very buggy firmware. Connecting to it has been known to fail at random. Certain combinations of motherboard, USB ports/cables/hubs can exacerbate this tendency to fail. If you are getting an error when connecting to your touch with |app| try the following, each of which has solved the problem for *some* |app| users. * Connect the Kobo directly to your computer, not via USB Hub * Try a different USB cable and a different USB port on your computer @@ -668,6 +669,22 @@ There are three possible things I know of, that can cause this: the blacklist of programs inside RoboForm to fix this. Or uninstall RoboForm. + * The Logitech SetPoint Settings application causes random crashes in + |app| when it is open. Close it before starting |app|. + +If none of the above apply to you, then there is some other program on your +computer that is interfering with |app|. First reboot your computer is safe +mode, to have as few running programs as possible, and see if the crashes still +happen. If they do not, then you know it is some program causing the problem. +The most likely such culprit is a program that modifies other programs' +behavior, such as an antivirus, a device driver, something like RoboForm (an +automatic form filling app) or an assistive technology like Voice Control or a +Screen Reader. + +The only way to find the culprit is to eliminate the programs one by one and +see which one is causing the issue. Basically, stop a program, run calibre, +check for crashes. If they still happen, stop another program and repeat. + |app| is not starting on OS X? ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ diff --git a/recipes/adventure_zone_pl.recipe b/recipes/adventure_zone_pl.recipe index 485a2e0c5b..2224937f3c 100644 --- a/recipes/adventure_zone_pl.recipe +++ b/recipes/adventure_zone_pl.recipe @@ -9,11 +9,12 @@ class Adventure_zone(BasicNewsRecipe): no_stylesheets = True oldest_article = 20 max_articles_per_feed = 100 + cover_url = 'http://www.adventure-zone.info/inne/logoaz_2012.png' index='http://www.adventure-zone.info/fusion/' use_embedded_content=False preprocess_regexps = [(re.compile(r"Komentarze", re.IGNORECASE), lambda m: ''), - (re.compile(r'\'), lambda match: ''), - (re.compile(r'\'), lambda match: '')] + (re.compile(r''), lambda match: ''), + (re.compile(r''), lambda match: '')] remove_tags_before= dict(name='td', attrs={'class':'main-bg'}) remove_tags= [dict(name='img', attrs={'alt':'Drukuj'})] remove_tags_after= dict(id='comments') @@ -36,11 +37,11 @@ class Adventure_zone(BasicNewsRecipe): return feeds - def get_cover_url(self): + '''def get_cover_url(self): soup = self.index_to_soup('http://www.adventure-zone.info/fusion/news.php') cover=soup.find(id='box_OstatninumerAZ') self.cover_url='http://www.adventure-zone.info/fusion/'+ cover.center.a.img['src'] - return getattr(self, 'cover_url', self.cover_url) + return getattr(self, 'cover_url', self.cover_url)''' def skip_ad_pages(self, soup): diff --git a/recipes/alternet.recipe b/recipes/alternet.recipe index e58376cc42..0bd608e0e7 100644 --- a/recipes/alternet.recipe +++ b/recipes/alternet.recipe @@ -10,14 +10,12 @@ class Alternet(BasicNewsRecipe): category = 'News, Magazine' description = 'News magazine and online community' feeds = [ - (u'Front Page', u'http://feeds.feedblitz.com/alternet'), - (u'Breaking News', u'http://feeds.feedblitz.com/alternet_breaking_news'), - (u'Top Ten Campaigns', u'http://feeds.feedblitz.com/alternet_top_10_campaigns'), - (u'Special Coverage Areas', u'http://feeds.feedblitz.com/alternet_coverage') + (u'Front Page', u'http://feeds.feedblitz.com/alternet') ] + remove_attributes = ['width', 'align','cellspacing'] remove_javascript = True - use_embedded_content = False + use_embedded_content = True no_stylesheets = True language = 'en' encoding = 'UTF-8' diff --git a/recipes/anchorage_daily.recipe b/recipes/anchorage_daily.recipe index 4ce2f13a14..7bda0f5bcd 100644 --- a/recipes/anchorage_daily.recipe +++ b/recipes/anchorage_daily.recipe @@ -5,14 +5,16 @@ class AdvancedUserRecipe1278347258(BasicNewsRecipe): __author__ = 'rty' oldest_article = 7 max_articles_per_feed = 100 + auto_cleanup = True + feeds = [(u'Alaska News', u'http://www.adn.com/news/alaska/index.xml'), - (u'Business', u'http://www.adn.com/money/index.xml'), - (u'Sports', u'http://www.adn.com/sports/index.xml'), - (u'Politics', u'http://www.adn.com/politics/index.xml'), - (u'Lifestyles', u'http://www.adn.com/life/index.xml'), - (u'Iditarod', u'http://www.adn.com/iditarod/index.xml') - ] + (u'Business', u'http://www.adn.com/money/index.xml'), + (u'Sports', u'http://www.adn.com/sports/index.xml'), + (u'Politics', u'http://www.adn.com/politics/index.xml'), + (u'Lifestyles', u'http://www.adn.com/life/index.xml'), + (u'Iditarod', u'http://www.adn.com/iditarod/index.xml') + ] description = ''''Alaska's Newspaper''' publisher = 'http://www.adn.com' category = 'news, Alaska, Anchorage' @@ -28,13 +30,13 @@ class AdvancedUserRecipe1278347258(BasicNewsRecipe): conversion_options = {'linearize_tables':True} masthead_url = 'http://media.adn.com/includes/assets/images/adn_logo.2.gif' - keep_only_tags = [ - dict(name='div', attrs={'class':'left_col story_mainbar'}), - ] - remove_tags = [ - dict(name='div', attrs={'class':'story_tools'}), - dict(name='p', attrs={'class':'ad_label'}), - ] - remove_tags_after = [ - dict(name='div', attrs={'class':'advertisement'}), - ] + #keep_only_tags = [ + #dict(name='div', attrs={'class':'left_col story_mainbar'}), + #] + #remove_tags = [ + #dict(name='div', attrs={'class':'story_tools'}), + #dict(name='p', attrs={'class':'ad_label'}), + #] + #remove_tags_after = [ + #dict(name='div', attrs={'class':'advertisement'}), + #] diff --git a/recipes/android_com_pl.recipe b/recipes/android_com_pl.recipe index c7a4a97d3c..a4a387d414 100644 --- a/recipes/android_com_pl.recipe +++ b/recipes/android_com_pl.recipe @@ -3,11 +3,11 @@ from calibre.web.feeds.news import BasicNewsRecipe class Android_com_pl(BasicNewsRecipe): title = u'Android.com.pl' __author__ = 'fenuks' - description = 'Android.com.pl - biggest polish Android site' + description = u'Android.com.pl - to największe w Polsce centrum Android OS. Znajdziesz tu: nowości, forum, pomoc, recenzje, gry, aplikacje.' category = 'Android, mobile' language = 'pl' use_embedded_content=True - cover_url =u'http://upload.wikimedia.org/wikipedia/commons/thumb/d/d7/Android_robot.svg/220px-Android_robot.svg.png' + cover_url =u'http://android.com.pl/wp-content/themes/android/images/logo.png' oldest_article = 8 max_articles_per_feed = 100 - feeds = [(u'Android', u'http://android.com.pl/component/content/frontpage/frontpage.feed?type=rss')] + feeds = [(u'Android', u'http://android.com.pl/feed/')] diff --git a/recipes/asco_de_vida.recipe b/recipes/asco_de_vida.recipe new file mode 100644 index 0000000000..fa1944f95d --- /dev/null +++ b/recipes/asco_de_vida.recipe @@ -0,0 +1,20 @@ +from calibre.web.feeds.news import BasicNewsRecipe + +class HindustanTimes(BasicNewsRecipe): + title = u'Asco de vida' + language = 'es' + __author__ = 'Krittika Goyal' + oldest_article = 1 #days + max_articles_per_feed = 25 + #encoding = 'cp1252' + use_embedded_content = False + + no_stylesheets = True + keep_only_tags = dict(name='div', attrs={'class':'box story'}) + + + feeds = [ +('News', + 'http://feeds2.feedburner.com/AscoDeVida'), +] + diff --git a/recipes/astroflesz.recipe b/recipes/astroflesz.recipe new file mode 100644 index 0000000000..0b92fdfa29 --- /dev/null +++ b/recipes/astroflesz.recipe @@ -0,0 +1,19 @@ +# vim:fileencoding=UTF-8:ts=4:sw=4:sta:et:sts=4:fdm=marker:ai +from calibre.web.feeds.news import BasicNewsRecipe + +class Astroflesz(BasicNewsRecipe): + title = u'Astroflesz' + oldest_article = 7 + __author__ = 'fenuks' + description = u'astroflesz.pl - to portal poświęcony astronomii. Informuje zarówno o aktualnych wydarzeniach i odkryciach naukowych, jak również zapowiada ciekawe zjawiska astronomiczne' + category = 'astronomy' + language = 'pl' + cover_url = 'http://www.astroflesz.pl/templates/astroflesz/images/logo/logo.png' + ignore_duplicate_articles = {'title', 'url'} + max_articles_per_feed = 100 + no_stylesheets = True + use_embedded_content = False + keep_only_tags = [dict(id="k2Container")] + remove_tags_after = dict(name='div', attrs={'class':'itemLinks'}) + remove_tags = [dict(name='div', attrs={'class':['itemLinks', 'itemToolbar', 'itemRatingBlock']})] + feeds = [(u'Wszystkie', u'http://astroflesz.pl/?format=feed')] diff --git a/recipes/birmingham_post.recipe b/recipes/birmingham_post.recipe index b9b3c3fc57..49c86fe3b8 100644 --- a/recipes/birmingham_post.recipe +++ b/recipes/birmingham_post.recipe @@ -1,9 +1,11 @@ from calibre.web.feeds.news import BasicNewsRecipe +import re +import mechanize + class AdvancedUserRecipe1306097511(BasicNewsRecipe): title = u'Birmingham post' description = 'Author D.Asbury. News for Birmingham UK' #timefmt = '' - # last update 8/9/12 __author__ = 'Dave Asbury' cover_url = 'http://profile.ak.fbcdn.net/hprofile-ak-snc4/161987_9010212100_2035706408_n.jpg' oldest_article = 2 @@ -15,8 +17,30 @@ class AdvancedUserRecipe1306097511(BasicNewsRecipe): #auto_cleanup = True language = 'en_GB' + cover_url = 'http://profile.ak.fbcdn.net/hprofile-ak-snc4/161987_9010212100_2035706408_n.jpg' - masthead_url = 'http://www.pressgazette.co.uk/Pictures/web/t/c/g/birmingham_post.jpg' + masthead_url = 'http://www.trinitymirror.com/images/birminghampost-logo.gif' + def get_cover_url(self): + soup = self.index_to_soup('http://www.birminghampost.net') + # look for the block containing the sun button and url + cov = soup.find(attrs={'height' : re.compile('3'), 'alt' : re.compile('Birmingham Post')}) + print + print '%%%%%%%%%%%%%%%',cov + print + cov2 = str(cov['src']) + # cov2=cov2[7:] + print '88888888 ',cov2,' 888888888888' + + #cover_url=cov2 + #return cover_url + br = mechanize.Browser() + br.set_handle_redirect(False) + try: + br.open_novisit(cov2) + cover_url = cov2 + except: + cover_url = 'http://profile.ak.fbcdn.net/hprofile-ak-snc4/161987_9010212100_2035706408_n.jpg' + return cover_url keep_only_tags = [ diff --git a/recipes/borse_online.recipe b/recipes/borse_online.recipe index c192ce2b8d..ddd9ac456b 100644 --- a/recipes/borse_online.recipe +++ b/recipes/borse_online.recipe @@ -1,33 +1,36 @@ from calibre.web.feeds.recipes import BasicNewsRecipe class AdvancedUserRecipe1303841067(BasicNewsRecipe): - title = u'Börse-online' - __author__ = 'schuster' - oldest_article = 1 + title = u'Börse-online' + __author__ = 'schuster, Armin Geller' + oldest_article = 1 max_articles_per_feed = 100 - no_stylesheets = True - use_embedded_content = False - language = 'de' - remove_javascript = True - cover_url = 'http://www.dpv.de/images/1995/source.gif' - masthead_url = 'http://www.zeitschriften-cover.de/cover/boerse-online-cover-januar-2010-x1387.jpg' - extra_css = ''' - h1{font-family:Arial,Helvetica,sans-serif; font-weight:bold;font-size:large;} - h4{font-family:Arial,Helvetica,sans-serif; font-weight:normal;font-size:small;} - img {min-width:300px; max-width:600px; min-height:300px; max-height:800px} - p{font-family:Arial,Helvetica,sans-serif;font-size:small;} - body{font-family:Helvetica,Arial,sans-serif;font-size:small;} - ''' - remove_tags_bevor = [dict(name='h3')] - remove_tags_after = [dict(name='div', attrs={'class':'artikelfuss'})] - remove_tags = [dict(attrs={'class':['moduleTopNav', 'moduleHeaderNav', 'text', 'blau', 'poll1150']}), - dict(id=['newsletterlayer', 'newsletterlayerClose', 'newsletterlayer_body', 'newsletterarray_error', 'newsletterlayer_emailadress', 'newsletterlayer_submit', 'kommentar']), - dict(name=['h2', 'Gesamtranking', 'h3',''])] + no_stylesheets = True + use_embedded_content = False + language = 'de' + remove_javascript = True + encoding = 'iso-8859-1' + timefmt = ' [%a, %d %b %Y]' + + + cover_url = 'http://www.wirtschaftsmedien-shop.de/s/media/coverimages/7576_2013107.jpg' + masthead_url = 'http://upload.wikimedia.org/wikipedia/de/5/56/B%C3%B6rse_Online_Logo.svg' + remove_tags_after = [dict(name='div', attrs={'class':['artikelfuss', 'rahmen600']})] + + remove_tags = [ + dict(name='div', attrs={'id':['breadcrumb', 'rightCol', 'clearall']}), + dict(name='div', attrs={'class':['footer', 'artikelfuss']}), + ] + + keep_only_tags = [ + dict(name='div', attrs={'id':['contentWrapper']}) + ] + + feeds = [(u'Börsennachrichten', u'http://www.boerse-online.de/rss/')] + def print_version(self, url): return url.replace('.html#nv=rss', '.html?mode=print') - feeds = [(u'Börsennachrichten', u'http://www.boerse-online.de/rss/')] - diff --git a/recipes/bwmagazine2.recipe b/recipes/bwmagazine2.recipe index 77143bbefc..2c714d91c7 100644 --- a/recipes/bwmagazine2.recipe +++ b/recipes/bwmagazine2.recipe @@ -11,16 +11,15 @@ class BusinessWeekMagazine(BasicNewsRecipe): category = 'news' encoding = 'UTF-8' keep_only_tags = [ - dict(name='div', attrs={'id':'article_body_container'}), - ] - remove_tags = [dict(name='ui'),dict(name='li')] + dict(name='div', attrs={'id':'article_body_container'}), + ] + remove_tags = [dict(name='ui'),dict(name='li'),dict(name='div', attrs={'id':['share-email']})] no_javascript = True no_stylesheets = True cover_url = 'http://images.businessweek.com/mz/covers/current_120x160.jpg' def parse_index(self): - #Go to the issue soup = self.index_to_soup('http://www.businessweek.com/magazine/news/articles/business_news.htm') @@ -47,7 +46,6 @@ class BusinessWeekMagazine(BasicNewsRecipe): if section_title not in feeds: feeds[section_title] = [] feeds[section_title] += articles - div1 = soup.find ('div', attrs={'class':'column center'}) section_title = '' for div in div1.findAll('h5'): diff --git a/recipes/chronicle_higher_ed.recipe b/recipes/chronicle_higher_ed.recipe index 15b284cd7a..66b17cafcf 100644 --- a/recipes/chronicle_higher_ed.recipe +++ b/recipes/chronicle_higher_ed.recipe @@ -12,10 +12,10 @@ class Chronicle(BasicNewsRecipe): category = 'news' encoding = 'UTF-8' keep_only_tags = [ - dict(name='div', attrs={'class':'article'}), + dict(name='div', attrs={'class':['article','blog-mod']}), ] - remove_tags = [dict(name='div',attrs={'class':['related module1','maintitle']}), - dict(name='div', attrs={'id':['section-nav','icon-row', 'enlarge-popup']}), + remove_tags = [dict(name='div',attrs={'class':['related module1','maintitle','entry-utility','object-meta']}), + dict(name='div', attrs={'id':['section-nav','icon-row', 'enlarge-popup','confirm-popup']}), dict(name='a', attrs={'class':'show-enlarge enlarge'})] no_javascript = True no_stylesheets = True diff --git a/recipes/countryfile.recipe b/recipes/countryfile.recipe index 4f2e8cd95f..86769b78cd 100644 --- a/recipes/countryfile.recipe +++ b/recipes/countryfile.recipe @@ -7,25 +7,30 @@ class AdvancedUserRecipe1325006965(BasicNewsRecipe): #cover_url = 'http://www.countryfile.com/sites/default/files/imagecache/160px_wide/cover/2_1.jpg' __author__ = 'Dave Asbury' description = 'The official website of Countryfile Magazine' - # last updated 7/10/12 + # last updated 8/12/12 language = 'en_GB' oldest_article = 30 max_articles_per_feed = 25 remove_empty_feeds = True no_stylesheets = True auto_cleanup = True + ignore_duplicate_articles = {'title', 'url'} #articles_are_obfuscated = True - ignore_duplicate_articles = {'title'} + #article_already_exists = False + #feed_hash = '' def get_cover_url(self): - soup = self.index_to_soup('http://www.countryfile.com/') + soup = self.index_to_soup('http://www.countryfile.com/magazine') + cov = soup.find(attrs={'class' : re.compile('imagecache imagecache-250px_wide')})#'width' : '160', + print '&&&&&&&& ',cov,' ***' + cov=str(cov) + #cov2 = re.findall('http[s]?://(?:[a-zA-Z]|[0-9]|[$-_@.&+]|[!*\(\),]|(?:%[0-9a-fA-F][0-9a-fA-F]))+', cov) + cov2 = re.findall('/(?:[a-zA-Z]|[0-9]|[$-_@.&+]|[!*\(\),]|(?:%[0-9a-fA-F][0-9a-fA-F]))+', cov) + + cov2 = str(cov2) + cov2= "http://www.countryfile.com"+cov2[2:len(cov2)-8] - cov = soup.find(attrs={'width' : '160', 'class' : re.compile('imagecache imagecache-160px_wide')}) - print '******** ',cov,' ***' - cov2 = str(cov) - cov2=cov2[10:101] print '******** ',cov2,' ***' - #cov2='http://www.countryfile.com/sites/default/files/imagecache/160px_wide/cover/1b_0.jpg' - # try to get cover - if can't get known cover + # try to get cover - if can't get known cover br = browser() br.set_handle_redirect(False) @@ -45,5 +50,3 @@ class AdvancedUserRecipe1325006965(BasicNewsRecipe): (u'Countryside', u'http://www.countryfile.com/rss/countryside'), ] - - diff --git a/recipes/czas_gentlemanow.recipe b/recipes/czas_gentlemanow.recipe new file mode 100644 index 0000000000..6df677f25f --- /dev/null +++ b/recipes/czas_gentlemanow.recipe @@ -0,0 +1,20 @@ +# vim:fileencoding=UTF-8:ts=4:sw=4:sta:et:sts=4:fdm=marker:ai +from calibre.web.feeds.news import BasicNewsRecipe + +class CzasGentlemanow(BasicNewsRecipe): + title = u'Czas Gentlemanów' + __author__ = 'fenuks' + description = u'Historia mężczyzn z dala od wielkiej polityki' + category = 'blog' + language = 'pl' + cover_url = 'http://czasgentlemanow.pl/wp-content/uploads/2012/10/logo-Czas-Gentlemanow1.jpg' + ignore_duplicate_articles = {'title', 'url'} + oldest_article = 7 + max_articles_per_feed = 100 + no_stylesheets = True + remove_empty_feeds = True + use_embedded_content = False + keep_only_tags = [dict(name='div', attrs={'class':'content'})] + remove_tags = [dict(attrs={'class':'meta_comments'})] + remove_tags_after = dict(name='div', attrs={'class':'fblikebutton_button'}) + feeds = [(u'M\u0119ski \u015awiat', u'http://czasgentlemanow.pl/category/meski-swiat/feed/'), (u'Styl', u'http://czasgentlemanow.pl/category/styl/feed/'), (u'Vademecum Gentlemana', u'http://czasgentlemanow.pl/category/vademecum/feed/'), (u'Dom i rodzina', u'http://czasgentlemanow.pl/category/dom-i-rodzina/feed/'), (u'Honor', u'http://czasgentlemanow.pl/category/honor/feed/'), (u'Gad\u017cety Gentlemana', u'http://czasgentlemanow.pl/category/gadzety-gentlemana/feed/')] diff --git a/recipes/dzieje_pl.recipe b/recipes/dzieje_pl.recipe index 0aafa5d2f4..603591e9f0 100644 --- a/recipes/dzieje_pl.recipe +++ b/recipes/dzieje_pl.recipe @@ -7,18 +7,64 @@ class Dzieje(BasicNewsRecipe): cover_url = 'http://www.dzieje.pl/sites/default/files/dzieje_logo.png' category = 'history' language = 'pl' - index='http://dzieje.pl' + ignore_duplicate_articles = {'title', 'url'} + index = 'http://dzieje.pl' oldest_article = 8 max_articles_per_feed = 100 remove_javascript=True no_stylesheets= True keep_only_tags = [dict(name='h1', attrs={'class':'title'}), dict(id='content-area')] remove_tags = [dict(attrs={'class':'field field-type-computed field-field-tagi'}), dict(id='dogory')] - feeds = [(u'Dzieje', u'http://dzieje.pl/rss.xml')] + #feeds = [(u'Dzieje', u'http://dzieje.pl/rss.xml')] + def append_page(self, soup, appendtag): + tag = appendtag.find('li', attrs={'class':'pager-next'}) + if tag: + while tag: + url = tag.a['href'] + if not url.startswith('http'): + url = 'http://dzieje.pl'+tag.a['href'] + soup2 = self.index_to_soup(url) + pagetext = soup2.find(id='content-area').find(attrs={'class':'content'}) + for r in pagetext.findAll(attrs={'class':['fieldgroup group-groupkul', 'fieldgroup group-zdjeciekult', 'fieldgroup group-zdjecieciekaw', 'fieldgroup group-zdjecieksiazka', 'fieldgroup group-zdjeciedu', 'field field-type-filefield field-field-zdjecieglownawyd']}): + r.extract() + pos = len(appendtag.contents) + appendtag.insert(pos, pagetext) + tag = soup2.find('li', attrs={'class':'pager-next'}) + for r in appendtag.findAll(attrs={'class':['item-list', 'field field-type-computed field-field-tagi', ]}): + r.extract() + + def find_articles(self, url): + articles = [] + soup=self.index_to_soup(url) + tag=soup.find(id='content-area').div.div + for i in tag.findAll('div', recursive=False): + temp = i.find(attrs={'class':'views-field-title'}).span.a + title = temp.string + url = self.index + temp['href'] + date = '' #i.find(attrs={'class':'views-field-created'}).span.string + articles.append({'title' : title, + 'url' : url, + 'date' : date, + 'description' : '' + }) + return articles + + def parse_index(self): + feeds = [] + feeds.append((u"Wiadomości", self.find_articles('http://dzieje.pl/wiadomosci'))) + feeds.append((u"Kultura i sztuka", self.find_articles('http://dzieje.pl/kulturaisztuka'))) + feeds.append((u"Film", self.find_articles('http://dzieje.pl/kino'))) + feeds.append((u"Rozmaitości historyczne", self.find_articles('http://dzieje.pl/rozmaitości'))) + feeds.append((u"Książka", self.find_articles('http://dzieje.pl/ksiazka'))) + feeds.append((u"Wystawa", self.find_articles('http://dzieje.pl/wystawa'))) + feeds.append((u"Edukacja", self.find_articles('http://dzieje.pl/edukacja'))) + feeds.append((u"Dzieje się", self.find_articles('http://dzieje.pl/wydarzenia'))) + return feeds def preprocess_html(self, soup): for a in soup('a'): if a.has_key('href') and 'http://' not in a['href'] and 'https://' not in a['href']: a['href']=self.index + a['href'] + self.append_page(soup, soup.body) return soup \ No newline at end of file diff --git a/recipes/economist.recipe b/recipes/economist.recipe index 25e46892f8..b5e2a1fd9b 100644 --- a/recipes/economist.recipe +++ b/recipes/economist.recipe @@ -70,18 +70,6 @@ class Economist(BasicNewsRecipe): return br ''' - def get_cover_url(self): - soup = self.index_to_soup('http://www.economist.com/printedition/covers') - div = soup.find('div', attrs={'class':lambda x: x and - 'print-cover-links' in x}) - a = div.find('a', href=True) - url = a.get('href') - if url.startswith('/'): - url = 'http://www.economist.com' + url - soup = self.index_to_soup(url) - div = soup.find('div', attrs={'class':'cover-content'}) - img = div.find('img', src=True) - return img.get('src') def parse_index(self): return self.economist_parse_index() @@ -92,7 +80,7 @@ class Economist(BasicNewsRecipe): if div is not None: img = div.find('img', src=True) if img is not None: - self.cover_url = img['src'] + self.cover_url = re.sub('thumbnail','full',img['src']) feeds = OrderedDict() for section in soup.findAll(attrs={'class':lambda x: x and 'section' in x}): diff --git a/recipes/economist_free.recipe b/recipes/economist_free.recipe index a64310c252..b5e2a1fd9b 100644 --- a/recipes/economist_free.recipe +++ b/recipes/economist_free.recipe @@ -9,7 +9,7 @@ from calibre.web.feeds.news import BasicNewsRecipe from calibre.ebooks.BeautifulSoup import Tag, NavigableString from collections import OrderedDict -import time, re +import re class Economist(BasicNewsRecipe): @@ -37,7 +37,6 @@ class Economist(BasicNewsRecipe): padding: 7px 0px 9px; } ''' - oldest_article = 7.0 remove_tags = [ dict(name=['script', 'noscript', 'title', 'iframe', 'cf_floatingcontent']), @@ -46,7 +45,6 @@ class Economist(BasicNewsRecipe): {'class': lambda x: x and 'share-links-header' in x}, ] keep_only_tags = [dict(id='ec-article-body')] - needs_subscription = False no_stylesheets = True preprocess_regexps = [(re.compile('.*', re.DOTALL), lambda x:'')] @@ -55,28 +53,26 @@ class Economist(BasicNewsRecipe): # downloaded with connection reset by peer (104) errors. delay = 1 - def get_cover_url(self): - soup = self.index_to_soup('http://www.economist.com/printedition/covers') - div = soup.find('div', attrs={'class':lambda x: x and - 'print-cover-links' in x}) - a = div.find('a', href=True) - url = a.get('href') - if url.startswith('/'): - url = 'http://www.economist.com' + url - soup = self.index_to_soup(url) - div = soup.find('div', attrs={'class':'cover-content'}) - img = div.find('img', src=True) - return img.get('src') + needs_subscription = False + ''' + def get_browser(self): + br = BasicNewsRecipe.get_browser() + if self.username and self.password: + br.open('http://www.economist.com/user/login') + br.select_form(nr=1) + br['name'] = self.username + br['pass'] = self.password + res = br.submit() + raw = res.read() + if '>Log out<' not in raw: + raise ValueError('Failed to login to economist.com. ' + 'Check your username and password.') + return br + ''' + def parse_index(self): - try: - return self.economist_parse_index() - except: - raise - self.log.warn( - 'Initial attempt to parse index failed, retrying in 30 seconds') - time.sleep(30) - return self.economist_parse_index() + return self.economist_parse_index() def economist_parse_index(self): soup = self.index_to_soup(self.INDEX) @@ -84,7 +80,7 @@ class Economist(BasicNewsRecipe): if div is not None: img = div.find('img', src=True) if img is not None: - self.cover_url = img['src'] + self.cover_url = re.sub('thumbnail','full',img['src']) feeds = OrderedDict() for section in soup.findAll(attrs={'class':lambda x: x and 'section' in x}): @@ -151,154 +147,3 @@ class Economist(BasicNewsRecipe): div.insert(2, img) table.replaceWith(div) return soup - -''' -from calibre.web.feeds.news import BasicNewsRecipe -from calibre.utils.threadpool import ThreadPool, makeRequests -from calibre.ebooks.BeautifulSoup import Tag, NavigableString -import time, string, re -from datetime import datetime -from lxml import html - -class Economist(BasicNewsRecipe): - - title = 'The Economist (RSS)' - language = 'en' - - __author__ = "Kovid Goyal" - description = ('Global news and current affairs from a European' - ' perspective. Best downloaded on Friday mornings (GMT).' - ' Much slower than the print edition based version.') - extra_css = '.headline {font-size: x-large;} \n h2 { font-size: small; } \n h1 { font-size: medium; }' - oldest_article = 7.0 - cover_url = 'http://media.economist.com/sites/default/files/imagecache/print-cover-thumbnail/print-covers/currentcoverus_large.jpg' - #cover_url = 'http://www.economist.com/images/covers/currentcoverus_large.jpg' - remove_tags = [ - dict(name=['script', 'noscript', 'title', 'iframe', 'cf_floatingcontent']), - dict(attrs={'class':['dblClkTrk', 'ec-article-info', - 'share_inline_header', 'related-items']}), - {'class': lambda x: x and 'share-links-header' in x}, - ] - keep_only_tags = [dict(id='ec-article-body')] - no_stylesheets = True - preprocess_regexps = [(re.compile('.*', re.DOTALL), - lambda x:'')] - - def parse_index(self): - from calibre.web.feeds.feedparser import parse - if self.test: - self.oldest_article = 14.0 - raw = self.index_to_soup( - 'http://feeds.feedburner.com/economist/full_print_edition', - raw=True) - entries = parse(raw).entries - pool = ThreadPool(10) - self.feed_dict = {} - requests = [] - for i, item in enumerate(entries): - 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', '') - - requests.append([i, link, title, description, author, published]) - if self.test: - requests = requests[:4] - requests = makeRequests(self.process_eco_feed_article, requests, self.eco_article_found, - self.eco_article_failed) - for r in requests: pool.putRequest(r) - pool.wait() - - return self.eco_sort_sections([(t, a) for t, a in - self.feed_dict.items()]) - - def eco_sort_sections(self, feeds): - if not feeds: - raise ValueError('No new articles found') - order = { - 'The World This Week': 1, - 'Leaders': 2, - 'Letters': 3, - 'Briefing': 4, - 'Business': 5, - 'Finance And Economics': 6, - 'Science & Technology': 7, - 'Books & Arts': 8, - 'International': 9, - 'United States': 10, - 'Asia': 11, - 'Europe': 12, - 'The Americas': 13, - 'Middle East & Africa': 14, - 'Britain': 15, - 'Obituary': 16, - } - return sorted(feeds, cmp=lambda x,y:cmp(order.get(x[0], 100), - order.get(y[0], 100))) - - def process_eco_feed_article(self, args): - from calibre import browser - i, url, title, description, author, published = args - br = browser() - ret = br.open(url) - raw = ret.read() - url = br.geturl().split('?')[0]+'/print' - root = html.fromstring(raw) - matches = root.xpath('//*[@class = "ec-article-info"]') - feedtitle = 'Miscellaneous' - if matches: - feedtitle = string.capwords(html.tostring(matches[-1], method='text', - encoding=unicode).split('|')[-1].strip()) - return (i, feedtitle, url, title, description, author, published) - - def eco_article_found(self, req, result): - from calibre.web.feeds import Article - i, feedtitle, link, title, description, author, published = result - self.log('Found print version for article:', title, 'in', feedtitle, - 'at', link) - - a = Article(i, title, link, author, description, published, '') - - article = dict(title=a.title, description=a.text_summary, - date=time.strftime(self.timefmt, a.date), author=a.author, url=a.url) - if feedtitle not in self.feed_dict: - self.feed_dict[feedtitle] = [] - self.feed_dict[feedtitle].append(article) - - def eco_article_failed(self, req, tb): - self.log.error('Failed to download %s with error:'%req.args[0][2]) - self.log.debug(tb) - - def eco_find_image_tables(self, soup): - for x in soup.findAll('table', align=['right', 'center']): - if len(x.findAll('font')) in (1,2) and len(x.findAll('img')) == 1: - yield x - - def postprocess_html(self, soup, first): - body = soup.find('body') - for name, val in body.attrs: - del body[name] - for table in list(self.eco_find_image_tables(soup)): - caption = table.find('font') - img = table.find('img') - div = Tag(soup, 'div') - div['style'] = 'text-align:left;font-size:70%' - ns = NavigableString(self.tag_to_string(caption)) - div.insert(0, ns) - div.insert(1, Tag(soup, 'br')) - img.extract() - del img['width'] - del img['height'] - div.insert(2, img) - table.replaceWith(div) - return soup -''' - diff --git a/recipes/ekologia_pl.recipe b/recipes/ekologia_pl.recipe new file mode 100644 index 0000000000..2b0933b58d --- /dev/null +++ b/recipes/ekologia_pl.recipe @@ -0,0 +1,24 @@ +# vim:fileencoding=UTF-8:ts=4:sw=4:sta:et:sts=4:fdm=marker:ai +from calibre.web.feeds.news import BasicNewsRecipe +import re +class EkologiaPl(BasicNewsRecipe): + title = u'Ekologia.pl' + __author__ = 'fenuks' + description = u'Portal ekologiczny - eko, ekologia, ochrona przyrody, ochrona środowiska, przyroda, środowisko online. Ekologia i ochrona środowiska. Ekologia dla dzieci.' + category = 'ecology' + language = 'pl' + cover_url = 'http://www.ekologia.pl/assets/images/logo/ekologia_pl_223x69.png' + ignore_duplicate_articles = {'title', 'url'} + extra_css = '.title {font-size: 200%;}' + oldest_article = 7 + max_articles_per_feed = 100 + no_stylesheets = True + remove_empty_feeds = True + use_embedded_content = False + remove_tags = [dict(attrs={'class':['ekoLogo', 'powrocArt', 'butonDrukuj']})] + + feeds = [(u'Wiadomo\u015bci', u'http://www.ekologia.pl/rss/20,53,0'), (u'\u015arodowisko', u'http://www.ekologia.pl/rss/20,56,0'), (u'Styl \u017cycia', u'http://www.ekologia.pl/rss/20,55,0')] + + def print_version(self, url): + id = re.search(r',(?P\d+)\.html', url).group('id') + return 'http://drukuj.ekologia.pl/artykul/' + id diff --git a/recipes/el_diplo.recipe b/recipes/el_diplo.recipe new file mode 100644 index 0000000000..619e99e5cd --- /dev/null +++ b/recipes/el_diplo.recipe @@ -0,0 +1,118 @@ +# Copyright 2013 Tomás Di Domenico +# +# This is a news fetching recipe for the Calibre ebook software, for +# fetching the Cono Sur edition of Le Monde Diplomatique (www.eldiplo.org). +# +# This recipe is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This software is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this recipe. If not, see . + +import re +from contextlib import closing +from calibre.web.feeds.recipes import BasicNewsRecipe +from calibre.ptempfile import PersistentTemporaryFile +from calibre.utils.magick import Image + +class ElDiplo_Recipe(BasicNewsRecipe): + title = u'El Diplo' + __author__ = 'Tomas Di Domenico' + description = 'Publicacion mensual de Le Monde Diplomatique, edicion Argentina' + langauge = 'es_AR' + needs_subscription = True + auto_cleanup = True + + def get_cover(self,url): + tmp_cover = PersistentTemporaryFile(suffix = ".jpg", prefix = "eldiplo_") + self.cover_url = tmp_cover.name + + with closing(self.browser.open(url)) as r: + imgdata = r.read() + + img = Image() + img.load(imgdata) + img.crop(img.size[0],img.size[1]/2,0,0) + + img.save(tmp_cover.name) + + def get_browser(self): + br = BasicNewsRecipe.get_browser() + if self.username is not None and self.password is not None: + br.open('http://www.eldiplo.org/index.php/login/-/do_login/index.html') + br.select_form(nr=3) + br['uName'] = self.username + br['uPassword'] = self.password + br.submit() + self.browser = br + return br + + def parse_index(self): + default_sect = 'General' + articles = {default_sect:[]} + ans = [default_sect] + sectionsmarker = 'DOSSIER_TITLE: ' + sectionsre = re.compile('^'+sectionsmarker) + + soup = self.index_to_soup('http://www.eldiplo.org/index.php') + + coverdivs = soup.findAll(True,attrs={'id':['lmd-foto']}) + a = coverdivs[0].find('a', href=True) + coverurl = a['href'].split("?imagen=")[1] + self.get_cover(coverurl) + + thedivs = soup.findAll(True,attrs={'class':['lmd-leermas']}) + for div in thedivs: + a = div.find('a', href=True) + if 'Sumario completo' in self.tag_to_string(a, use_alt=True): + summaryurl = re.sub(r'\?.*', '', a['href']) + summaryurl = 'http://www.eldiplo.org' + summaryurl + + for pagenum in xrange(1,10): + soup = self.index_to_soup('{0}/?cms1_paging_p_b32={1}'.format(summaryurl,pagenum)) + thedivs = soup.findAll(True,attrs={'class':['interna']}) + + if len(thedivs) == 0: + break + + for div in thedivs: + section = div.find(True,text=sectionsre).replace(sectionsmarker,'') + if section == '': + section = default_sect + + if section not in articles.keys(): + articles[section] = [] + ans.append(section) + + nota = div.find(True,attrs={'class':['lmd-pl-titulo-nota-dossier']}) + a = nota.find('a', href=True) + if not a: + continue + + url = re.sub(r'\?.*', '', a['href']) + url = 'http://www.eldiplo.org' + url + title = self.tag_to_string(a, use_alt=True).strip() + + summary = div.find(True, attrs={'class':'lmd-sumario-descript'}).find('p') + if summary: + description = self.tag_to_string(summary, use_alt=False) + + aut = div.find(True, attrs={'class':'lmd-autor-sumario'}) + if aut: + auth = self.tag_to_string(aut, use_alt=False).strip() + + if not articles.has_key(section): + articles[section] = [] + + articles[section].append(dict(title=title,author=auth,url=url,date=None,description=description,content='')) + + #ans = self.sort_index_by(ans, {'The Front Page':-1, 'Dining In, Dining Out':1, 'Obituaries':2}) + ans = [(section, articles[section]) for section in ans if articles.has_key(section)] + return ans diff --git a/recipes/empire_magazine.recipe b/recipes/empire_magazine.recipe index 138b7bffd1..2d7a574dde 100644 --- a/recipes/empire_magazine.recipe +++ b/recipes/empire_magazine.recipe @@ -5,6 +5,7 @@ class AdvancedUserRecipe1341650280(BasicNewsRecipe): title = u'Empire Magazine' description = 'Author D.Asbury. Film articles from Empire Mag. ' + language = 'en' __author__ = 'Dave Asbury' # last updated 7/7/12 remove_empty_feeds = True @@ -15,7 +16,7 @@ class AdvancedUserRecipe1341650280(BasicNewsRecipe): cover_url = 'http://www.empireonline.com/images/magazine/cover.jpg' conversion_options = { 'linearize_tables' : True, - } + } #auto_cleanup = True preprocess_regexps = [ (re.compile(r'Przeczytaj także:.*', re.IGNORECASE|re.DOTALL), lambda m: ''), (re.compile(ur'
Artykuł
', re.IGNORECASE), lambda m: ''), (re.compile(ur'
Ludzie filmu
', re.IGNORECASE), lambda m: '')] + remove_tags = [dict(name='img', attrs={'alt':['Ludzie filmu', u'Artykuł']})] + feeds = [(u'Recenzje', u'http://film.org.pl/r/recenzje/feed/'), (u'Artyku\u0142', u'http://film.org.pl/a/artykul/feed/'), (u'Analiza', u'http://film.org.pl/a/analiza/feed/'), (u'Ranking', u'http://film.org.pl/a/ranking/feed/'), (u'Blog', u'http://film.org.pl/kmf/blog/feed/'), (u'Ludzie', u'http://film.org.pl/a/ludzie/feed/'), (u'Seriale', u'http://film.org.pl/a/seriale/feed/'), (u'Oceanarium', u'http://film.org.pl/a/ocenarium/feed/'), (u'VHS', u'http://film.org.pl/a/vhs-a/feed/')] diff --git a/recipes/film_web.recipe b/recipes/film_web.recipe index 01d7514e0d..6b014e8f93 100644 --- a/recipes/film_web.recipe +++ b/recipes/film_web.recipe @@ -17,6 +17,7 @@ class FilmWebPl(BasicNewsRecipe): preprocess_regexps = [(re.compile(u'\(kliknij\,\ aby powiększyć\)', re.IGNORECASE), lambda m: ''), ]#(re.compile(ur' | ', re.IGNORECASE), lambda m: '')] extra_css = '.hdrBig {font-size:22px;} ul {list-style-type:none; padding: 0; margin: 0;}' remove_tags= [dict(name='div', attrs={'class':['recommendOthers']}), dict(name='ul', attrs={'class':'fontSizeSet'}), dict(attrs={'class':'userSurname anno'})] + remove_attributes = ['style',] keep_only_tags= [dict(name='h1', attrs={'class':['hdrBig', 'hdrEntity']}), dict(name='div', attrs={'class':['newsInfo', 'newsInfoSmall', 'reviewContent description']})] feeds = [(u'News / Filmy w produkcji', 'http://www.filmweb.pl/feed/news/category/filminproduction'), (u'News / Festiwale, nagrody i przeglądy', u'http://www.filmweb.pl/feed/news/category/festival'), @@ -50,4 +51,9 @@ class FilmWebPl(BasicNewsRecipe): for i in soup.findAll('sup'): if not i.string or i.string.startswith('(kliknij'): i.extract() + tag = soup.find(name='ul', attrs={'class':'inline sep-line'}) + if tag: + tag.name = 'div' + for t in tag.findAll('li'): + t.name = 'div' return soup diff --git a/recipes/fleshbot.recipe b/recipes/fleshbot.recipe index 0a56e42795..0059d8855d 100644 --- a/recipes/fleshbot.recipe +++ b/recipes/fleshbot.recipe @@ -18,7 +18,7 @@ class Fleshbot(BasicNewsRecipe): encoding = 'utf-8' use_embedded_content = True language = 'en' - masthead_url = 'http://cache.gawkerassets.com/assets/kotaku.com/img/logo.png' + masthead_url = 'http://fbassets.s3.amazonaws.com/images/uploads/2012/01/fleshbot-logo.png' extra_css = ''' body{font-family: "Lucida Grande",Helvetica,Arial,sans-serif} img{margin-bottom: 1em} @@ -31,7 +31,7 @@ class Fleshbot(BasicNewsRecipe): , 'language' : language } - feeds = [(u'Articles', u'http://feeds.gawker.com/fleshbot/vip?format=xml')] + feeds = [(u'Articles', u'http://www.fleshbot.com/feed')] remove_tags = [ {'class': 'feedflare'}, diff --git a/recipes/foreignaffairs.recipe b/recipes/foreignaffairs.recipe index 6b36170288..b383609860 100644 --- a/recipes/foreignaffairs.recipe +++ b/recipes/foreignaffairs.recipe @@ -11,21 +11,21 @@ class ForeignAffairsRecipe(BasicNewsRecipe): by Chen Wei weichen302@gmx.com, 2012-02-05''' __license__ = 'GPL v3' - __author__ = 'kwetal' + __author__ = 'Rick Shang, kwetal' language = 'en' version = 1.01 - title = u'Foreign Affairs (Subcription or (free) Registration)' + title = u'Foreign Affairs (Subcription)' publisher = u'Council on Foreign Relations' category = u'USA, Foreign Affairs' description = u'The leading forum for serious discussion of American foreign policy and international affairs.' no_stylesheets = True remove_javascript = True + needs_subscription = True INDEX = 'http://www.foreignaffairs.com' FRONTPAGE = 'http://www.foreignaffairs.com/magazine' - INCLUDE_PREMIUM = False remove_tags = [] @@ -68,43 +68,57 @@ class ForeignAffairsRecipe(BasicNewsRecipe): def parse_index(self): + answer = [] soup = self.index_to_soup(self.FRONTPAGE) - sec_start = soup.findAll('div', attrs={'class':'panel-separator'}) + #get dates + date = re.split('\s\|\s',self.tag_to_string(soup.head.title.string))[0] + self.timefmt = u' [%s]'%date + + sec_start = soup.findAll('div', attrs= {'class':'panel-pane'}) for sec in sec_start: - content = sec.nextSibling - if content: - section = self.tag_to_string(content.find('h2')) - articles = [] - - tags = [] - for div in content.findAll('div', attrs = {'class': re.compile(r'view-row\s+views-row-[0-9]+\s+views-row-[odd|even].*')}): - tags.append(div) - for li in content.findAll('li'): - tags.append(li) - - for div in tags: - title = url = description = author = None - - if self.INCLUDE_PREMIUM: - found_premium = False - else: - found_premium = div.findAll('span', attrs={'class': - 'premium-icon'}) - if not found_premium: - tag = div.find('div', attrs={'class': 'views-field-title'}) - - if tag: - a = tag.find('a') - if a: - title = self.tag_to_string(a) - url = self.INDEX + a['href'] - author = self.tag_to_string(div.find('div', attrs = {'class': 'views-field-field-article-display-authors-value'})) - tag_summary = div.find('span', attrs = {'class': 'views-field-field-article-summary-value'}) - description = self.tag_to_string(tag_summary) - articles.append({'title':title, 'date':None, 'url':url, - 'description':description, 'author':author}) - if articles: + articles = [] + section = self.tag_to_string(sec.find('h2')) + if 'Books' in section: + reviewsection=sec.find('div', attrs = {'class': 'item-list'}) + for subsection in reviewsection.findAll('div'): + subsectiontitle=self.tag_to_string(subsection.span.a) + subsectionurl=self.INDEX + subsection.span.a['href'] + soup1 = self.index_to_soup(subsectionurl) + for div in soup1.findAll('div', attrs = {'class': 'views-field-title'}): + if div.find('a') is not None: + originalauthor=self.tag_to_string(div.findNext('div', attrs = {'class':'views-field-field-article-book-nid'}).div.a) + title=subsectiontitle+': '+self.tag_to_string(div.span.a)+' by '+originalauthor + url=self.INDEX+div.span.a['href'] + atr=div.findNext('div', attrs = {'class': 'views-field-field-article-display-authors-value'}) + if atr is not None: + author=self.tag_to_string(atr.span.a) + else: + author='' + desc=div.findNext('span', attrs = {'class': 'views-field-field-article-summary-value'}) + if desc is not None: + description=self.tag_to_string(desc.div.p) + else: + description='' + articles.append({'title':title, 'date':None, 'url':url, 'description':description, 'author':author}) + subsectiontitle='' + else: + for div in sec.findAll('div', attrs = {'class': 'views-field-title'}): + if div.find('a') is not None: + title=self.tag_to_string(div.span.a) + url=self.INDEX+div.span.a['href'] + atr=div.findNext('div', attrs = {'class': 'views-field-field-article-display-authors-value'}) + if atr is not None: + author=self.tag_to_string(atr.span.a) + else: + author='' + desc=div.findNext('span', attrs = {'class': 'views-field-field-article-summary-value'}) + if desc is not None: + description=self.tag_to_string(desc.div.p) + else: + description='' + articles.append({'title':title, 'date':None, 'url':url, 'description':description, 'author':author}) + if articles: answer.append((section, articles)) return answer @@ -115,15 +129,17 @@ class ForeignAffairsRecipe(BasicNewsRecipe): return soup - needs_subscription = True + def get_browser(self): br = BasicNewsRecipe.get_browser() if self.username is not None and self.password is not None: - br.open('https://www.foreignaffairs.com/user?destination=home') + br.open('https://www.foreignaffairs.com/user?destination=user%3Fop%3Dlo') br.select_form(nr = 1) br['name'] = self.username br['pass'] = self.password br.submit() return br + def cleanup(self): + self.browser.open('http://www.foreignaffairs.com/logout?destination=user%3Fop=lo') diff --git a/recipes/gildia_pl.recipe b/recipes/gildia_pl.recipe index def57203e4..525cf6c605 100644 --- a/recipes/gildia_pl.recipe +++ b/recipes/gildia_pl.recipe @@ -4,9 +4,10 @@ import re class Gildia(BasicNewsRecipe): title = u'Gildia.pl' __author__ = 'fenuks' - description = 'Gildia - cultural site' + description = u'Fantastyczny Portal Kulturalny - newsy, recenzje, galerie, wywiady. Literatura, film, gry komputerowe i planszowe, komiks, RPG, sklep. Nie lekceważ potęgi wyobraźni!' cover_url = 'http://www.film.gildia.pl/_n_/portal/redakcja/logo/logo-gildia.pl-500.jpg' category = 'culture' + cover_url = 'http://gildia.pl/images/logo-main.png' language = 'pl' oldest_article = 8 max_articles_per_feed = 100 @@ -23,10 +24,13 @@ class Gildia(BasicNewsRecipe): content = soup.find('div', attrs={'class':'news'}) if 'recenzj' in soup.title.string.lower(): for link in content.findAll(name='a'): - if 'recenzj' in link['href']: - self.log.warn('odnosnik') - self.log.warn(link['href']) + if 'recenzj' in link['href'] or 'muzyka/plyty' in link['href']: return self.index_to_soup(link['href'], raw=True) + if 'fragmen' in soup.title.string.lower(): + for link in content.findAll(name='a'): + if 'fragment' in link['href']: + return self.index_to_soup(link['href'], raw=True) + def preprocess_html(self, soup): for a in soup('a'): diff --git a/recipes/gram_pl.recipe b/recipes/gram_pl.recipe index 79157630f5..3852f65d32 100644 --- a/recipes/gram_pl.recipe +++ b/recipes/gram_pl.recipe @@ -1,19 +1,20 @@ from calibre.web.feeds.news import BasicNewsRecipe - +from calibre.ebooks.BeautifulSoup import BeautifulSoup class Gram_pl(BasicNewsRecipe): title = u'Gram.pl' __author__ = 'fenuks' - description = 'Gram.pl - site about computer games' + description = u'Serwis społecznościowy o grach: recenzje, newsy, zapowiedzi, encyklopedia gier, forum. Gry PC, PS3, X360, PS Vita, sprzęt dla graczy.' category = 'games' language = 'pl' oldest_article = 8 index='http://www.gram.pl' max_articles_per_feed = 100 + ignore_duplicate_articles = {'title', 'url'} no_stylesheets= True - extra_css = 'h2 {font-style: italic; font-size:20px;} .picbox div {float: left;}' + #extra_css = 'h2 {font-style: italic; font-size:20px;} .picbox div {float: left;}' cover_url=u'http://www.gram.pl/www/01/img/grampl_zima.png' - remove_tags= [dict(name='p', attrs={'class':['extraText', 'must-log-in']}), dict(attrs={'class':['el', 'headline', 'post-info', 'entry-footer clearfix']}), dict(name='div', attrs={'class':['twojaOcena', 'comment-body', 'comment-author vcard', 'comment-meta commentmetadata', 'tw_button', 'entry-comment-counter', 'snap_nopreview sharing robots-nocontent', 'sharedaddy sd-sharing-enabled']}), dict(id=['igit_rpwt_css', 'comments', 'reply-title', 'igit_title'])] - keep_only_tags= [dict(name='div', attrs={'class':['main', 'arkh-postmetadataheader', 'arkh-postcontent', 'post', 'content', 'news_header', 'news_subheader', 'news_text']}), dict(attrs={'class':['contentheading', 'contentpaneopen']}), dict(name='article')] + keep_only_tags= [dict(id='articleModule')] + remove_tags = [dict(attrs={'class':['breadCrump', 'dymek', 'articleFooter']})] feeds = [(u'Informacje', u'http://www.gram.pl/feed_news.asp'), (u'Publikacje', u'http://www.gram.pl/feed_news.asp?type=articles'), (u'Kolektyw- Indie Games', u'http://indie.gram.pl/feed/'), @@ -28,35 +29,21 @@ class Gram_pl(BasicNewsRecipe): feed.articles.remove(article) return feeds - def append_page(self, soup, appendtag): - nexturl = appendtag.find('a', attrs={'class':'cpn'}) - while nexturl: - soup2 = self.index_to_soup('http://www.gram.pl'+ nexturl['href']) - r=appendtag.find(id='pgbox') - if r: - r.extract() - pagetext = soup2.find(attrs={'class':'main'}) - r=pagetext.find('h1') - if r: - r.extract() - r=pagetext.find('h2') - if r: - r.extract() - for r in pagetext.findAll('script'): - r.extract() - pos = len(appendtag.contents) - appendtag.insert(pos, pagetext) - nexturl = appendtag.find('a', attrs={'class':'cpn'}) - r=appendtag.find(id='pgbox') - if r: - r.extract() def preprocess_html(self, soup): - self.append_page(soup, soup.body) - tag=soup.findAll(name='div', attrs={'class':'picbox'}) - for t in tag: - t['style']='float: left;' + tag=soup.find(name='div', attrs={'class':'summary'}) + if tag: + tag.find(attrs={'class':'pros'}).insert(0, BeautifulSoup('

Plusy:

').h2) + tag.find(attrs={'class':'cons'}).insert(0, BeautifulSoup('

Minusy:

').h2) + tag = soup.find(name='section', attrs={'class':'cenzurka'}) + if tag: + rate = tag.p.img['data-ocena'] + tag.p.img.extract() + tag.p.insert(len(tag.p.contents)-2, BeautifulSoup('

Ocena: {0}

'.format(rate)).h2) for a in soup('a'): if a.has_key('href') and 'http://' not in a['href'] and 'https://' not in a['href']: a['href']=self.index + a['href'] + tag=soup.find(name='span', attrs={'class':'platforma'}) + if tag: + tag.name = 'p' return soup diff --git a/recipes/harpers.recipe b/recipes/harpers.recipe index a4576792d0..18e75dce6e 100644 --- a/recipes/harpers.recipe +++ b/recipes/harpers.recipe @@ -1,5 +1,5 @@ __license__ = 'GPL v3' -__copyright__ = '2008-2010, Darko Miletic ' +__copyright__ = '2008-2012, Darko Miletic ' ''' harpers.org ''' @@ -16,6 +16,7 @@ class Harpers(BasicNewsRecipe): max_articles_per_feed = 100 no_stylesheets = True use_embedded_content = False + masthead_url = 'http://harpers.org/wp-content/themes/harpers/images/pheader.gif' conversion_options = { 'comment' : description @@ -31,27 +32,9 @@ class Harpers(BasicNewsRecipe): .caption{font-family:Verdana,sans-serif;font-size:x-small;color:#666666;} ''' - keep_only_tags = [ dict(name='div', attrs={'id':'cached'}) ] - remove_tags = [ - dict(name='table', attrs={'class':['rcnt','rcnt topline']}) - ,dict(name=['link','object','embed','meta','base']) - ] + keep_only_tags = [ dict(name='div', attrs={'class':['postdetailFull', 'articlePost']}) ] + remove_tags = [dict(name=['link','object','embed','meta','base'])] remove_attributes = ['width','height'] - feeds = [(u"Harper's Magazine", u'http://www.harpers.org/rss/frontpage-rss20.xml')] + feeds = [(u"Harper's Magazine", u'http://harpers.org/feed/')] - def get_cover_url(self): - cover_url = None - index = 'http://harpers.org/' - soup = self.index_to_soup(index) - link_item = soup.find(name = 'img',attrs= {'class':"cover"}) - if link_item: - cover_url = 'http://harpers.org' + link_item['src'] - return cover_url - - def preprocess_html(self, soup): - for item in soup.findAll(style=True): - del item['style'] - for item in soup.findAll(xmlns=True): - del item['xmlns'] - return soup diff --git a/recipes/harpers_full.recipe b/recipes/harpers_full.recipe index ff558e9c5b..153f82db7b 100644 --- a/recipes/harpers_full.recipe +++ b/recipes/harpers_full.recipe @@ -1,18 +1,22 @@ __license__ = 'GPL v3' -__copyright__ = '2008-2010, Darko Miletic ' +__copyright__ = '2008-2012, Darko Miletic ' ''' harpers.org - paid subscription/ printed issue articles This recipe only get's article's published in text format images and pdf's are ignored +If you have institutional subscription based on access IP you do not need to enter +anything in username/password fields ''' +import time, re +import urllib from calibre import strftime from calibre.web.feeds.news import BasicNewsRecipe class Harpers_full(BasicNewsRecipe): title = "Harper's Magazine - articles from printed edition" __author__ = 'Darko Miletic' - description = "Harper's Magazine: Founded June 1850." + description = "Harper's Magazine, the oldest general-interest monthly in America, explores the issues that drive our national conversation, through long-form narrative journalism and essays, and such celebrated features as the iconic Harper's Index." publisher = "Harpers's" category = 'news, politics, USA' oldest_article = 30 @@ -21,52 +25,86 @@ class Harpers_full(BasicNewsRecipe): use_embedded_content = False delay = 1 language = 'en' - needs_subscription = True - masthead_url = 'http://www.harpers.org/media/image/Harpers_305x100.gif' - publication_type = 'magazine' - INDEX = strftime('http://www.harpers.org/archive/%Y/%m') - LOGIN = 'http://www.harpers.org' - cover_url = strftime('http://www.harpers.org/media/pages/%Y/%m/gif/0001.gif') - extra_css = ' body{font-family: "Georgia",serif} ' + encoding = 'utf8' + needs_subscription = 'optional' + masthead_url = 'http://harpers.org/wp-content/themes/harpers/images/pheader.gif' + publication_type = 'magazine' + LOGIN = 'http://harpers.org/wp-content/themes/harpers/ajax_login.php' + extra_css = """ + body{font-family: adobe-caslon-pro,serif} + .category{font-size: small} + .articlePost p:first-letter{display: inline; font-size: xx-large; font-weight: bold} + """ conversion_options = { - 'comment' : description - , 'tags' : category - , 'publisher' : publisher - , 'language' : language + 'comment' : description + , 'tags' : category + , 'publisher' : publisher + , 'language' : language } - keep_only_tags = [ dict(name='div', attrs={'id':'cached'}) ] + keep_only_tags = [ dict(name='div', attrs={'class':['postdetailFull','articlePost']}) ] remove_tags = [ - dict(name='table', attrs={'class':['rcnt','rcnt topline']}) - ,dict(name='link') + dict(name='div', attrs={'class':'fRight rightDivPad'}) + ,dict(name=['link','meta','object','embed','iframe']) ] - remove_attributes=['xmlns'] + remove_attributes=['xmlns'] def get_browser(self): br = BasicNewsRecipe.get_browser() + br.open('http://harpers.org/') if self.username is not None and self.password is not None: - br.open(self.LOGIN) - br.select_form(nr=1) - br['handle' ] = self.username - br['password'] = self.password - br.submit() + tt = time.localtime()*1000 + data = urllib.urlencode({ 'm':self.username + ,'p':self.password + ,'rt':'http://harpers.org/' + ,'tt':tt + }) + br.open(self.LOGIN, data) return br def parse_index(self): + #find current issue + soup = self.index_to_soup('http://harpers.org/') + currentIssue=soup.find('div',attrs={'class':'mainNavi'}).find('li',attrs={'class':'curentIssue'}) + currentIssue_url=self.tag_to_string(currentIssue.a['href']) + self.log(currentIssue_url) + + #go to the current issue + soup1 = self.index_to_soup(currentIssue_url) + date = re.split('\s\|\s',self.tag_to_string(soup1.head.title.string))[0] + self.timefmt = u' [%s]'%date + + #get cover + coverurl='http://harpers.org/wp-content/themes/harpers/ajax_microfiche.php?img=harpers-'+re.split('harpers.org/',currentIssue_url)[1]+'gif/0001.gif' + soup2 = self.index_to_soup(coverurl) + self.cover_url = self.tag_to_string(soup2.find('img')['src']) + self.log(self.cover_url) articles = [] - print 'Processing ' + self.INDEX - soup = self.index_to_soup(self.INDEX) - for item in soup.findAll('div', attrs={'class':'title'}): - text_link = item.parent.find('img',attrs={'alt':'Text'}) - if text_link: - url = self.LOGIN + item.a['href'] - title = item.a.contents[0] - date = strftime(' %B %Y') - articles.append({ - 'title' :title - ,'date' :date - ,'url' :url - ,'description':'' - }) - return [(soup.head.title.string, articles)] + count = 0 + for item in soup1.findAll('div', attrs={'class':'articleData'}): + text_links = item.findAll('h2') + for text_link in text_links: + if count == 0: + count = 1 + else: + url = text_link.a['href'] + title = text_link.a.contents[0] + date = strftime(' %B %Y') + articles.append({ + 'title' :title + ,'date' :date + ,'url' :url + ,'description':'' + }) + return [(soup1.head.title.string, articles)] + + def print_version(self, url): + return url + '?single=1' + + def cleanup(self): + soup = self.index_to_soup('http://harpers.org/') + signouturl=self.tag_to_string(soup.find('li', attrs={'class':'subLogOut'}).findNext('li').a['href']) + self.log(signouturl) + self.browser.open(signouturl) + diff --git a/recipes/heise_online.recipe b/recipes/heise_online.recipe index 4d82570698..29f63ce1ac 100644 --- a/recipes/heise_online.recipe +++ b/recipes/heise_online.recipe @@ -15,23 +15,12 @@ class AdvancedUserRecipe(BasicNewsRecipe): timeout = 5 no_stylesheets = True + keep_only_tags = [dict(name='div', attrs={'id':'mitte_news'}), + dict(name='h1', attrs={'class':'clear'}), + dict(name='div', attrs={'class':'meldung_wrapper'})] - remove_tags_after = dict(name ='p', attrs={'class':'editor'}) remove_tags = [dict(id='navi_top_container'), - dict(id='navi_bottom'), - dict(id='mitte_rechts'), - dict(id='navigation'), - dict(id='subnavi'), - dict(id='social_bookmarks'), - dict(id='permalink'), - dict(id='content_foren'), - dict(id='seiten_navi'), - dict(id='adbottom'), - dict(id='sitemap'), - dict(name='div', attrs={'id':'sitemap'}), - dict(name='ul', attrs={'class':'erste_zeile'}), - dict(name='ul', attrs={'class':'zweite_zeile'}), - dict(name='div', attrs={'class':'navi_top_container'})] + dict(name='p', attrs={'class':'size80'})] feeds = [ ('Newsticker', 'http://www.heise.de/newsticker/heise.rdf'), @@ -54,5 +43,3 @@ class AdvancedUserRecipe(BasicNewsRecipe): def print_version(self, url): return url + '?view=print' - - diff --git a/recipes/hindu.recipe b/recipes/hindu.recipe index cc5305eb77..eb84fc4031 100644 --- a/recipes/hindu.recipe +++ b/recipes/hindu.recipe @@ -16,10 +16,14 @@ class TheHindu(BasicNewsRecipe): keep_only_tags = [dict(id='content')] remove_tags = [dict(attrs={'class':['article-links', 'breadcr']}), - dict(id=['email-section', 'right-column', 'printfooter'])] + dict(id=['email-section', 'right-column', 'printfooter', 'topover', + 'slidebox', 'th_footer'])] extra_css = '.photo-caption { font-size: smaller }' + def preprocess_raw_html(self, raw, url): + return raw.replace('

', '

').replace('

', '

') + def postprocess_html(self, soup, first_fetch): for t in soup.findAll(['table', 'tr', 'td','center']): t.name = 'div' diff --git a/recipes/historia_pl.recipe b/recipes/historia_pl.recipe index f3353fe89f..60554c0924 100644 --- a/recipes/historia_pl.recipe +++ b/recipes/historia_pl.recipe @@ -3,7 +3,7 @@ from calibre.web.feeds.news import BasicNewsRecipe class Historia_org_pl(BasicNewsRecipe): title = u'Historia.org.pl' __author__ = 'fenuks' - description = u'history site' + description = u'Artykuły dotyczące historii w układzie epok i tematów, forum. Najlepsza strona historii. Matura z historii i egzamin gimnazjalny z historii.' cover_url = 'http://lh3.googleusercontent.com/_QeRQus12wGg/TOvHsZ2GN7I/AAAAAAAAD_o/LY1JZDnq7ro/logo5.jpg' category = 'history' language = 'pl' @@ -12,16 +12,15 @@ class Historia_org_pl(BasicNewsRecipe): no_stylesheets = True use_embedded_content = True max_articles_per_feed = 100 + ignore_duplicate_articles = {'title', 'url'} - feeds = [(u'Wszystkie', u'http://www.historia.org.pl/index.php?format=feed&type=atom'), - (u'Wiadomości', u'http://www.historia.org.pl/index.php/wiadomosci.feed?type=atom'), - (u'Publikacje', u'http://www.historia.org.pl/index.php/publikacje.feed?type=atom'), - (u'Publicystyka', u'http://www.historia.org.pl/index.php/publicystyka.feed?type=atom'), - (u'Recenzje', u'http://historia.org.pl/index.php/recenzje.feed?type=atom'), - (u'Kultura i sztuka', u'http://www.historia.org.pl/index.php/kultura-i-sztuka.feed?type=atom'), - (u'Rekonstykcje', u'http://www.historia.org.pl/index.php/rekonstrukcje.feed?type=atom'), - (u'Projekty', u'http://www.historia.org.pl/index.php/projekty.feed?type=atom'), - (u'Konkursy'), (u'http://www.historia.org.pl/index.php/konkursy.feed?type=atom')] + + feeds = [(u'Wszystkie', u'http://historia.org.pl/feed/'), + (u'Wiadomości', u'http://historia.org.pl/Kategoria/wiadomosci/feed/'), + (u'Publikacje', u'http://historia.org.pl/Kategoria/artykuly/feed/'), + (u'Publicystyka', u'http://historia.org.pl/Kategoria/publicystyka/feed/'), + (u'Recenzje', u'http://historia.org.pl/Kategoria/recenzje/feed/'), + (u'Projekty', u'http://historia.org.pl/Kategoria/projekty/feed/'),] def print_version(self, url): diff --git a/recipes/icons/astroflesz.png b/recipes/icons/astroflesz.png new file mode 100644 index 0000000000..37a9e21675 Binary files /dev/null and b/recipes/icons/astroflesz.png differ diff --git a/recipes/icons/czas_gentlemanow.png b/recipes/icons/czas_gentlemanow.png new file mode 100644 index 0000000000..0d20f80c05 Binary files /dev/null and b/recipes/icons/czas_gentlemanow.png differ diff --git a/recipes/icons/ekologia_pl.png b/recipes/icons/ekologia_pl.png new file mode 100644 index 0000000000..797421420d Binary files /dev/null and b/recipes/icons/ekologia_pl.png differ diff --git a/recipes/icons/libartes.png b/recipes/icons/libartes.png new file mode 100644 index 0000000000..0954c40273 Binary files /dev/null and b/recipes/icons/libartes.png differ diff --git a/recipes/icons/poradnia_pwn.png b/recipes/icons/poradnia_pwn.png new file mode 100644 index 0000000000..6cafb534fc Binary files /dev/null and b/recipes/icons/poradnia_pwn.png differ diff --git a/recipes/icons/tvp_info.png b/recipes/icons/tvp_info.png new file mode 100644 index 0000000000..1414f38d5c Binary files /dev/null and b/recipes/icons/tvp_info.png differ diff --git a/recipes/icons/zaufana_trzecia_strona.png b/recipes/icons/zaufana_trzecia_strona.png new file mode 100644 index 0000000000..cdaeb95d27 Binary files /dev/null and b/recipes/icons/zaufana_trzecia_strona.png differ diff --git a/recipes/il_messaggero.recipe b/recipes/il_messaggero.recipe index 93c35f4695..f0983f438c 100644 --- a/recipes/il_messaggero.recipe +++ b/recipes/il_messaggero.recipe @@ -28,12 +28,15 @@ class IlMessaggero(BasicNewsRecipe): recursion = 10 remove_javascript = True + extra_css = ' .bianco31lucida{color: black} ' - - keep_only_tags = [dict(name='h1', attrs={'class':'titoloLettura2'}), - dict(name='h2', attrs={'class':'sottotitLettura'}), - dict(name='span', attrs={'class':'testoArticoloG'}) + keep_only_tags = [dict(name='h1', attrs={'class':['titoloLettura2','titoloart','bianco31lucida']}), + dict(name='h2', attrs={'class':['sottotitLettura','grigio16']}), + dict(name='span', attrs={'class':'testoArticoloG'}), + dict(name='div', attrs={'id':'testodim'}) ] + + def get_cover_url(self): cover = None st = time.localtime() @@ -55,17 +58,16 @@ class IlMessaggero(BasicNewsRecipe): feeds = [ (u'HomePage', u'http://www.ilmessaggero.it/rss/home.xml'), (u'Primo Piano', u'http://www.ilmessaggero.it/rss/initalia_primopiano.xml'), - (u'Cronaca Bianca', u'http://www.ilmessaggero.it/rss/initalia_cronacabianca.xml'), - (u'Cronaca Nera', u'http://www.ilmessaggero.it/rss/initalia_cronacanera.xml'), (u'Economia e Finanza', u'http://www.ilmessaggero.it/rss/economia.xml'), (u'Politica', u'http://www.ilmessaggero.it/rss/initalia_politica.xml'), - (u'Scienza e Tecnologia', u'http://www.ilmessaggero.it/rss/scienza.xml'), - (u'Cinema', u'http://www.ilmessaggero.it/rss.php?refresh_ce#'), - (u'Viaggi', u'http://www.ilmessaggero.it/rss.php?refresh_ce#'), + (u'Cultura', u'http://www.ilmessaggero.it/rss/cultura.xml'), + (u'Tecnologia', u'http://www.ilmessaggero.it/rss/tecnologia.xml'), + (u'Spettacoli', u'http://www.ilmessaggero.it/rss/spettacoli.xml'), + (u'Edizioni Locali', u'http://www.ilmessaggero.it/rss/edlocali.xml'), (u'Roma', u'http://www.ilmessaggero.it/rss/roma.xml'), - (u'Cultura e Tendenze', u'http://www.ilmessaggero.it/rss/roma_culturaspet.xml'), + (u'Benessere', u'http://www.ilmessaggero.it/rss/benessere.xml'), (u'Sport', u'http://www.ilmessaggero.it/rss/sport.xml'), - (u'Calcio', u'http://www.ilmessaggero.it/rss/sport_calcio.xml'), - (u'Motori', u'http://www.ilmessaggero.it/rss/sport_motori.xml') + (u'Moda', u'http://www.ilmessaggero.it/rss/moda.xml') ] + diff --git a/recipes/independent.recipe b/recipes/independent.recipe index 5e746145ee..43f0f9acde 100644 --- a/recipes/independent.recipe +++ b/recipes/independent.recipe @@ -47,9 +47,10 @@ class TheIndependentNew(BasicNewsRecipe): dict(name='img',attrs={'alt' : ['Get Adobe Flash player']}), dict(name='img',attrs={'alt' : ['view gallery']}), dict(attrs={'style' : re.compile('.*')}), + dict(attrs={'class':lambda x: x and 'voicesRelatedTopics' in x.split()}), ] - keep_only_tags =[dict(attrs={'id':'main'})] + keep_only_tags =[dict(attrs={'id':['main','top']})] recursions = 0 # fixes non compliant html nesting and 'marks' article graphics links @@ -69,7 +70,7 @@ class TheIndependentNew(BasicNewsRecipe): } extra_css = """ - h1{font-family: Georgia,serif } + h1{font-family: Georgia,serif ; font-size: x-large; } body{font-family: Verdana,Arial,Helvetica,sans-serif} img{margin-bottom: 0.4em; display:block} .starRating img {float: left} @@ -77,16 +78,21 @@ class TheIndependentNew(BasicNewsRecipe): .image {clear:left; font-size: x-small; color:#888888;} .articleByTimeLocation {font-size: x-small; color:#888888; margin-bottom:0.2em ; margin-top:0.2em ; display:block} - .subtitle {clear:left} + .subtitle {clear:left ;} .column-1 h1 { color: #191919} .column-1 h2 { color: #333333} .column-1 h3 { color: #444444} - .column-1 p { color: #777777} - .column-1 p,a,h1,h2,h3 { margin: 0; } - .column-1 div{color:#888888; margin: 0;} + .subtitle { color: #777777; font-size: medium;} + .column-1 a,h1,h2,h3 { margin: 0; } + .column-1 div{margin: 0;} .articleContent {display: block; clear:left;} + .articleContent {color: #000000; font-size: medium;} + .ivDrip-section {color: #000000; font-size: medium;} + .datetime {color: #888888} + .title {font-weight:bold;} .storyTop{} .pictureContainer img { max-width: 400px; max-height: 400px;} + .image img { max-width: 400px; max-height: 400px;} """ oldest_article = 1 @@ -325,6 +331,20 @@ class TheIndependentNew(BasicNewsRecipe): item.contents[0] = '' def postprocess_html(self,soup, first_fetch): + + #mark subtitle parent as non-compliant nesting causes + # p's to be 'popped out' of the h3 tag they are nested in. + subtitle = soup.find('h3', attrs={'class' : 'subtitle'}) + subtitle_div = None + if subtitle: + subtitle_div = subtitle.parent + if subtitle_div: + clazz = '' + if 'class' in subtitle_div: + clazz = subtitle_div['class'] + ' ' + clazz = clazz + 'subtitle' + subtitle_div['class'] = clazz + #find broken images and remove captions items_to_extract = [] for item in soup.findAll('div', attrs={'class' : 'image'}): @@ -501,6 +521,9 @@ class TheIndependentNew(BasicNewsRecipe): ), (u'Opinion', u'http://www.independent.co.uk/opinion/?service=rss'), + (u'Voices', + u'http://www.independent.co.uk/voices/?service=rss' + ), (u'Environment', u'http://www.independent.co.uk/environment/?service=rss'), (u'Sport - Athletics', diff --git a/recipes/kosmonauta_pl.recipe b/recipes/kosmonauta_pl.recipe index ddfa26df36..d1caa85950 100644 --- a/recipes/kosmonauta_pl.recipe +++ b/recipes/kosmonauta_pl.recipe @@ -9,6 +9,21 @@ class Kosmonauta(BasicNewsRecipe): language = 'pl' cover_url='http://bi.gazeta.pl/im/4/10393/z10393414X,Kosmonauta-net.jpg' no_stylesheets = True + INDEX = 'http://www.kosmonauta.net' oldest_article = 7 + no_stylesheets = True max_articles_per_feed = 100 - feeds = [(u'Kosmonauta.net', u'http://www.kosmonauta.net/index.php/feed/rss.html')] + keep_only_tags = [dict(name='div', attrs={'class':'item-page'})] + remove_tags = [dict(attrs={'class':['article-tools clearfix', 'cedtag', 'nav clearfix', 'jwDisqusForm']})] + remove_tags_after = dict(name='div', attrs={'class':'cedtag'}) + feeds = [(u'Kosmonauta.net', u'http://www.kosmonauta.net/?format=feed&type=atom')] + + def preprocess_html(self, soup): + for a in soup.findAll(name='a'): + if a.has_key('href'): + href = a['href'] + if not href.startswith('http'): + a['href'] = self.INDEX + href + print '%%%%%%%%%%%%%%%%%%%%%%%%%', a['href'] + return soup + \ No newline at end of file diff --git a/recipes/ksiazka_pl.recipe b/recipes/ksiazka_pl.recipe index 7f9999f782..f91cb4f4f7 100644 --- a/recipes/ksiazka_pl.recipe +++ b/recipes/ksiazka_pl.recipe @@ -1,15 +1,16 @@ from calibre.web.feeds.news import BasicNewsRecipe import re class Ksiazka_net_pl(BasicNewsRecipe): - title = u'ksiazka.net.pl' + title = u'książka.net.pl' __author__ = 'fenuks' - description = u'Ksiazka.net.pl - book vortal' + description = u'Portal Księgarski - tematyczny serwis o książkach. Wydarzenia z rynku księgarsko-wydawniczego, nowości, zapowiedzi, bestsellery, setki recenzji. Niezbędne informacje dla każdego miłośnika książek, księgarza, bibliotekarza i wydawcy.' cover_url = 'http://www.ksiazka.net.pl/fileadmin/templates/ksiazka.net.pl/images/1PortalKsiegarski-logo.jpg' category = 'books' language = 'pl' oldest_article = 8 max_articles_per_feed = 100 no_stylesheets= True + remove_empty_feeds = True #extra_css = 'img {float: right;}' preprocess_regexps = [(re.compile(ur'Podoba mi się, kupuję:'), lambda match: '
')] remove_tags_before= dict(name='div', attrs={'class':'m-body'}) diff --git a/recipes/la_stampa.recipe b/recipes/la_stampa.recipe index b9d8a469aa..06a7debe9d 100644 --- a/recipes/la_stampa.recipe +++ b/recipes/la_stampa.recipe @@ -2,7 +2,7 @@ __license__ = 'GPL v3' __author__ = 'Gabriele Marini, based on Darko Miletic' __copyright__ = '2009, Darko Miletic ' -__description__ = 'La Stampa 05/05/2010' +__description__ = 'La Stampa 28/12/2012' ''' http://www.lastampa.it/ @@ -14,10 +14,11 @@ class LaStampa(BasicNewsRecipe): title = u'La Stampa' language = 'it' __author__ = 'Gabriele Marini' - oldest_article = 15 + #oldest_article = 15 + oldest_articlce = 7 #for daily schedule max_articles_per_feed = 50 recursion = 100 - cover_url = 'http://www.lastampa.it/edicola/PDF/1.pdf' + cover_url = 'http://www1.lastampa.it/edicola/PDF/1.pdf' use_embedded_content = False remove_javascript = True no_stylesheets = True @@ -33,35 +34,41 @@ class LaStampa(BasicNewsRecipe): if link: return link[0]['href'] - keep_only_tags = [dict(attrs={'class':['boxocchiello2','titoloRub','titologir','catenaccio','sezione','articologirata']}), + keep_only_tags = [dict(attrs={'class':['boxocchiello2','titoloRub','titologir','autore-girata','luogo-girata','catenaccio','sezione','articologirata','bodytext','news-single-img','ls-articoloCorpo','ls-blog-list-1col']}), dict(name='div', attrs={'id':'corpoarticolo'}) ] - remove_tags = [dict(name='div', attrs={'id':'menutop'}), - dict(name='div', attrs={'id':'fwnetblocco'}), - dict(name='table', attrs={'id':'strumenti'}), - dict(name='table', attrs={'id':'imgesterna'}), - dict(name='a', attrs={'class':'linkblu'}), - dict(name='a', attrs={'class':'link'}), + + remove_tags = [dict(name='div', attrs={'id':['menutop','fwnetblocco']}), + dict(attrs={'class':['ls-toolbarCommenti','ls-boxCommentsBlog']}), + dict(name='table', attrs={'id':['strumenti','imgesterna']}), + dict(name='a', attrs={'class':['linkblu','link']}), dict(name='span', attrs={'class':['boxocchiello','boxocchiello2','sezione']}) ] - - feeds = [ - (u'Home', u'http://www.lastampa.it/redazione/rss_home.xml'), - (u'Editoriali', u'http://www.lastampa.it/cmstp/rubriche/oggetti/rss.asp?ID_blog=25'), - (u'Politica', u'http://www.lastampa.it/redazione/cmssezioni/politica/rss_politica.xml'), - (u'ArciItaliana', u'http://www.lastampa.it/cmstp/rubriche/oggetti/rss.asp?ID_blog=14'), - (u'Cronache', u'http://www.lastampa.it/redazione/cmssezioni/cronache/rss_cronache.xml'), - (u'Esteri', u'http://www.lastampa.it/redazione/cmssezioni/esteri/rss_esteri.xml'), - (u'Danni Collaterali', u'http://www.lastampa.it/cmstp/rubriche/oggetti/rss.asp?ID_blog=90'), - (u'Economia', u'http://www.lastampa.it/redazione/cmssezioni/economia/rss_economia.xml'), - (u'Tecnologia ', u'http://www.lastampa.it/cmstp/rubriche/oggetti/rss.asp?ID_blog=30'), - (u'Spettacoli', u'http://www.lastampa.it/redazione/cmssezioni/spettacoli/rss_spettacoli.xml'), - (u'Sport', u'http://www.lastampa.it/sport/rss_home.xml'), - (u'Torino', u'http://rss.feedsportal.com/c/32418/f/466938/index.rss'), - (u'Motori', u'http://www.lastampa.it/cmstp/rubriche/oggetti/rss.asp?ID_blog=57'), - (u'Scienza', u'http://www.lastampa.it/cmstp/rubriche/oggetti/rss.asp?ID_blog=38'), - (u'Fotografia', u'http://rss.feedsportal.com/c/32418/f/478449/index.rss'), - (u'Scuola', u'http://www.lastampa.it/cmstp/rubriche/oggetti/rss.asp?ID_blog=60'), - (u'Tempo Libero', u'http://www.lastampa.it/tempolibero/rss_home.xml') + feeds = [(u'BuonGiorno',u'http://www.lastampa.it/cultura/opinioni/buongiorno/rss.xml'), + (u'Jena', u'http://www.lastampa.it/cultura/opinioni/jena/rss.xml'), + (u'Editoriali', u'http://www.lastampa.it/cultura/opinioni/editoriali'), + (u'Finestra sull America', u'http://lastampa.feedsportal.com/c/32418/f/625713/index.rss'), + (u'HomePage', u'http://www.lastampa.it/rss.xml'), + (u'Politica Italia', u'http://www.lastampa.it/italia/politica/rss.xml'), + (u'ArciItaliana', u'http://www.lastampa.it/rss/blog/arcitaliana'), + (u'Cronache', u'http://www.lastampa.it/italia/cronache/rss.xml'), + (u'Esteri', u'http://www.lastampa.it/esteri/rss.xml'), + (u'Danni Collaterali', u'http://www.lastampa.it/rss/blog/danni-collaterali'), + (u'Economia', u'http://www.lastampa.it/economia/rss.xml'), + (u'Tecnologia ', u'http://www.lastampa.it/tecnologia/rss.xml'), + (u'Spettacoli', u'http://www.lastampa.it/spettacoli/rss.xml'), + (u'Sport', u'http://www.lastampa.it/sport/rss.xml'), + (u'Torino', u'http://www.lastampa.it/cronaca/rss.xml'), + (u'Motori', u'http://www.lastampa.it/motori/rss.xml'), + (u'Scienza', u'http://www.lastampa.it/scienza/rss.xml'), + (u'Cultura', u'http://www.lastampa.it/cultura/rss.xml'), + (u'Scuola', u'http://www.lastampa.it/cultura/scuola/rss.xml'), + (u'Benessere', u'http://www.lastampa.it/scienza/benessere/rss.xml'), + (u'Cucina', u'http://www.lastampa.it/societa/cucina/rss.xml'), + (u'Casa', u'http://www.lastampa.it/societa/casa/rss.xml'), + (u'Moda',u'http://www.lastampa.it/societa/moda/rss.xml'), + (u'Giochi',u'http://www.lastampa.it/tecnologia/giochi/rss.xml'), + (u'Viaggi',u'http://www.lastampa.it/societa/viaggi/rss.xml'), + (u'Ambiente', u'http://www.lastampa.it/scienza/ambiente/rss.xml') ] diff --git a/recipes/la_voce.recipe b/recipes/la_voce.recipe index 140adbb84c..18db9346a8 100644 --- a/recipes/la_voce.recipe +++ b/recipes/la_voce.recipe @@ -7,9 +7,9 @@ class AdvancedUserRecipe1324114228(BasicNewsRecipe): max_articles_per_feed = 100 auto_cleanup = True masthead_url = 'http://www.lavoce.info/binary/la_voce/testata/lavoce.1184661635.gif' - feeds = [(u'La Voce', u'http://www.lavoce.info/feed_rss.php?id_feed=1')] + feeds = [(u'La Voce', u'http://www.lavoce.info/feed/')] __author__ = 'faber1971' - description = 'Italian website on Economy - v1.01 (17, December 2011)' + description = 'Italian website on Economy - v1.02 (27, December 2012)' language = 'it' diff --git a/recipes/le_monde.recipe b/recipes/le_monde.recipe index 8693676da9..318df7e362 100644 --- a/recipes/le_monde.recipe +++ b/recipes/le_monde.recipe @@ -22,13 +22,15 @@ class LeMonde(BasicNewsRecipe): #publication_type = 'newsportal' extra_css = ''' h1{font-size:130%;} + h2{font-size:100%;} + blockquote.aside {background-color: #DDD; padding: 0.5em;} .ariane{font-size:xx-small;} .source{font-size:xx-small;} - #.href{font-size:xx-small;} - #.figcaption style{color:#666666; font-size:x-small;} - #.main-article-info{font-family:Arial,Helvetica,sans-serif;} - #full-contents{font-size:small; font-family:Arial,Helvetica,sans-serif;font-weight:normal;} - #match-stats-summary{font-size:small; font-family:Arial,Helvetica,sans-serif;font-weight:normal;} + /*.href{font-size:xx-small;}*/ + /*.figcaption style{color:#666666; font-size:x-small;}*/ + /*.main-article-info{font-family:Arial,Helvetica,sans-serif;}*/ + /*full-contents{font-size:small; font-family:Arial,Helvetica,sans-serif;font-weight:normal;}*/ + /*match-stats-summary{font-size:small; font-family:Arial,Helvetica,sans-serif;font-weight:normal;}*/ ''' #preprocess_regexps = [(re.compile(r'', re.DOTALL), lambda m: '')] conversion_options = { @@ -44,6 +46,9 @@ class LeMonde(BasicNewsRecipe): filterDuplicates = True def preprocess_html(self, soup): + for aside in soup.findAll('aside'): + aside.name='blockquote' + aside['class'] = "aside" for alink in soup.findAll('a'): if alink.string is not None: tstr = alink.string @@ -107,7 +112,9 @@ class LeMonde(BasicNewsRecipe): ] remove_tags = [ - dict(name='div', attrs={'class':['bloc_base meme_sujet']}), + dict(attrs={'class':['rubriques_liees']}), + dict(attrs={'class':['sociaux']}), + dict(attrs={'class':['bloc_base meme_sujet']}), dict(name='p', attrs={'class':['lire']}) ] diff --git a/recipes/ledevoir.recipe b/recipes/ledevoir.recipe index 0811289827..d99ca53841 100644 --- a/recipes/ledevoir.recipe +++ b/recipes/ledevoir.recipe @@ -32,26 +32,28 @@ class ledevoir(BasicNewsRecipe): recursion = 10 needs_subscription = 'optional' - filterDuplicates = False url_list = [] remove_javascript = True no_stylesheets = True + auto_cleanup = True preprocess_regexps = [(re.compile(r'(title|alt)=".*?>.*?"', re.DOTALL), lambda m: '')] - keep_only_tags = [ - dict(name='div', attrs={'id':'article'}), - dict(name='div', attrs={'id':'colonne_principale'}) - ] + #keep_only_tags = [ + #dict(name='div', attrs={'id':'article_detail'}), + #dict(name='div', attrs={'id':'colonne_principale'}) + #] - remove_tags = [ - dict(name='div', attrs={'id':'dialog'}), - dict(name='div', attrs={'class':['interesse_actions','reactions']}), - dict(name='ul', attrs={'class':'mots_cles'}), - dict(name='a', attrs={'class':'haut'}), - dict(name='h5', attrs={'class':'interesse_actions'}) - ] + #remove_tags = [ + #dict(name='div', attrs={'id':'dialog'}), + #dict(name='div', attrs={'class':['interesse_actions','reactions','taille_du_texte right clearfix','partage_sociaux clearfix']}), + #dict(name='aside', attrs={'class':['article_actions clearfix','reactions','partage_sociaux_wrapper']}), + #dict(name='ul', attrs={'class':'mots_cles'}), + #dict(name='ul', attrs={'id':'commentaires'}), + #dict(name='a', attrs={'class':'haut'}), + #dict(name='h5', attrs={'class':'interesse_actions'}) + #] feeds = [ (u'A la une', 'http://www.ledevoir.com/rss/manchettes.xml'), @@ -95,10 +97,4 @@ class ledevoir(BasicNewsRecipe): br.submit() return br - def print_version(self, url): - if self.filterDuplicates: - if url in self.url_list: - return - self.url_list.append(url) - return url diff --git a/recipes/libartes.recipe b/recipes/libartes.recipe new file mode 100644 index 0000000000..6ddae87119 --- /dev/null +++ b/recipes/libartes.recipe @@ -0,0 +1,69 @@ +__license__ = 'GPL v3' +__copyright__ = '2013, Darko Miletic ' +''' +libartes.com +''' + +import re +from calibre import strftime +from calibre.web.feeds.news import BasicNewsRecipe + +class Libartes(BasicNewsRecipe): + title = 'Libartes' + __author__ = 'Darko Miletic' + description = 'Elektronski časopis Libartes delo je kulturnih entuzijasta, umetnika i teoretičara umetnosti i književnosti. Časopis Libartes izlazi tromesečno i bavi se različitim granama umetnosti - književnošću, muzikom, filmom, likovnim umetnostima, dizajnom i arhitekturom.' + publisher = 'Libartes' + category = 'literatura, knjizevnost, film, dizajn, arhitektura, muzika' + no_stylesheets = True + INDEX = 'http://libartes.com/' + use_embedded_content = False + encoding = 'utf-8' + language = 'sr' + publication_type = 'magazine' + masthead_url = 'http://libartes.com/index_files/logo.gif' + extra_css = """ + @font-face {font-family: "serif1";src:url(res:///opt/sony/ebook/FONT/tt0011m_.ttf)} + @font-face {font-family: "sans1";src:url(res:///opt/sony/ebook/FONT/tt0003m_.ttf)} + body{font-family: "Times New Roman",Times,serif1, serif} + img{display:block} + .naslov{font-size: xx-large; font-weight: bold} + .nag{font-size: large; font-weight: bold} + """ + + conversion_options = { + 'comment' : description + , 'tags' : category + , 'publisher' : publisher + , 'language' : language + } + + + preprocess_regexps = [(re.compile(u'\u0110'), lambda match: u'\u00D0')] + remove_tags_before = dict(attrs={'id':'nav'}) + remove_tags_after = dict(attrs={'id':'fb' }) + keep_only_tags = [dict(name='div', attrs={'id':'center_content'})] + remove_tags = [ + dict(name=['object','link','iframe','embed','meta']) + ,dict(attrs={'id':'nav'}) + ] + + def parse_index(self): + articles = [] + soup = self.index_to_soup(self.INDEX) + for item in soup.findAll(name='a', attrs={'class':'belad'}, href=True): + feed_link = item + if feed_link['href'].startswith(self.INDEX): + url = feed_link['href'] + else: + url = self.INDEX + feed_link['href'] + + title = self.tag_to_string(feed_link) + date = strftime(self.timefmt) + articles.append({ + 'title' :title + ,'date' :date + ,'url' :url + ,'description':'' + }) + return [('Casopis Libartes', articles)] + diff --git a/recipes/libero.recipe b/recipes/libero.recipe index f2208d01a3..01c2da36c7 100644 --- a/recipes/libero.recipe +++ b/recipes/libero.recipe @@ -14,7 +14,8 @@ class LiberoNews(BasicNewsRecipe): __author__ = 'Marini Gabriele' description = 'Italian daily newspaper' - cover_url = 'http://www.libero-news.it/images/logo.png' + #cover_url = 'http://www.liberoquotidiano.it/images/Libero%20Quotidiano.jpg' + cover_url = 'http://www.edicola.liberoquotidiano.it/vnlibero/fpcut.jsp?testata=milano' title = u'Libero ' publisher = 'EDITORIALE LIBERO s.r.l 2006' category = 'News, politics, culture, economy, general interest' @@ -32,10 +33,11 @@ class LiberoNews(BasicNewsRecipe): remove_javascript = True keep_only_tags = [ - dict(name='div', attrs={'class':'Articolo'}) + dict(name='div', attrs={'class':'Articolo'}), + dict(name='article') ] remove_tags = [ - dict(name='div', attrs={'class':['CommentaFoto','Priva2']}), + dict(name='div', attrs={'class':['CommentaFoto','Priva2','login_commenti','box_16']}), dict(name='div', attrs={'id':['commentigenerale']}) ] feeds = [ diff --git a/recipes/lvivs_ks_ghazieta.recipe b/recipes/lvivs_ks_ghazieta.recipe new file mode 100644 index 0000000000..cc4b326d42 --- /dev/null +++ b/recipes/lvivs_ks_ghazieta.recipe @@ -0,0 +1,12 @@ +from calibre.web.feeds.news import BasicNewsRecipe + +class AdvancedUserRecipe1356270446(BasicNewsRecipe): + title = u'\u041b\u044c\u0432\u0456\u0432\u0441\u044c\u043a\u0430 \u0433\u0430\u0437\u0435\u0442\u0430' + __author__ = 'rpalyvoda' + oldest_article = 7 + max_articles_per_feed = 100 + language = 'uk' + cover_url = 'http://lvivska.com/sites/all/themes/biblos/images/logo.png' + masthead_url = 'http://lvivska.com/sites/all/themes/biblos/images/logo.png' + auto_cleanup = True + feeds = [(u'\u041d\u043e\u0432\u0438\u043d\u0438', u'http://lvivska.com/rss/news.xml'), (u'\u041f\u043e\u043b\u0456\u0442\u0438\u043a\u0430', u'http://lvivska.com/rss/politic.xml'), (u'\u0415\u043a\u043e\u043d\u043e\u043c\u0456\u043a\u0430', u'http://lvivska.com/rss/economic.xml'), (u'\u041f\u0440\u0430\u0432\u043e', u'http://lvivska.com/rss/law.xml'), (u'\u0421\u0432\u0456\u0442', u'http://lvivska.com/rss/world.xml'), (u'\u0416\u0438\u0442\u0442\u044f', u'http://lvivska.com/rss/life.xml'), (u'\u041a\u0443\u043b\u044c\u0442\u0443\u0440\u0430', u'http://lvivska.com/rss/culture.xml'), (u'\u041b\u0430\u0441\u0443\u043d', u'http://lvivska.com/rss/cooking.xml'), (u'\u0421\u0442\u0438\u043b\u044c', u'http://lvivska.com/rss/style.xml'), (u'Galicia Incognita', u'http://lvivska.com/rss/galiciaincognita.xml'), (u'\u0421\u043f\u043e\u0440\u0442', u'http://lvivska.com/rss/sport.xml'), (u'\u0415\u043a\u043e\u043b\u043e\u0433\u0456\u044f', u'http://lvivska.com/rss/ecology.xml'), (u"\u0417\u0434\u043e\u0440\u043e\u0432'\u044f", u'http://lvivska.com/rss/health.xml'), (u'\u0410\u0432\u0442\u043e', u'http://lvivska.com/rss/auto.xml'), (u'\u0411\u043b\u043e\u0433\u0438', u'http://lvivska.com/rss/blog.xml')] diff --git a/recipes/metro_uk.recipe b/recipes/metro_uk.recipe index fcceba4ce7..934fbab793 100644 --- a/recipes/metro_uk.recipe +++ b/recipes/metro_uk.recipe @@ -1,43 +1,74 @@ from calibre.web.feeds.news import BasicNewsRecipe +from calibre import strftime +import re +import datetime +import time + class AdvancedUserRecipe1306097511(BasicNewsRecipe): title = u'Metro UK' - description = 'Author Dave Asbury : News from The Metro - UK' + description = 'News as provided by The Metro -UK' #timefmt = '' __author__ = 'Dave Asbury' - #last update 9/9/12 + #last update 9/6/12 cover_url = 'http://profile.ak.fbcdn.net/hprofile-ak-snc4/276636_117118184990145_2132092232_n.jpg' - no_stylesheets = True oldest_article = 1 - max_articles_per_feed = 12 remove_empty_feeds = True remove_javascript = True - #auto_cleanup = True + auto_cleanup = True encoding = 'UTF-8' - cover_url ='http://profile.ak.fbcdn.net/hprofile-ak-snc4/157897_117118184990145_840702264_n.jpg' + language = 'en_GB' masthead_url = 'http://e-edition.metro.co.uk/images/metro_logo.gif' - extra_css = ''' - h1{font-family:Arial,Helvetica,sans-serif; font-weight:900;font-size:1.6em;} - h2{font-family:Arial,Helvetica,sans-serif; font-weight:normal;font-size:1.2em;} - p{font-family:Arial,Helvetica,sans-serif;font-size:1.0em;} - body{font-family:Helvetica,Arial,sans-serif;font-size:1.0em;} - ''' - keep_only_tags = [ - #dict(name='h1'), - #dict(name='h2'), - #dict(name='div', attrs={'class' : ['row','article','img-cnt figure','clrd']}) - #dict(name='h3'), - #dict(attrs={'class' : 'BText'}), - ] - remove_tags = [ - dict(name='div',attrs={'class' : 'art-fd fd-gr1-b clrd'}), - dict(name='span',attrs={'class' : 'share'}), - dict(name='li'), - dict(attrs={'class' : ['twitter-share-button','header-forms','hdr-lnks','close','art-rgt','fd-gr1-b clrd google-article','news m12 clrd clr-b p5t shareBtm','item-ds csl-3-img news','c-1of3 c-last','c-1of1','pd','item-ds csl-3-img sport']}), - dict(attrs={'id' : ['','sky-left','sky-right','ftr-nav','and-ftr','notificationList','logo','miniLogo','comments-news','metro_extras']}) - ] - remove_tags_before = dict(name='h1') - #remove_tags_after = dict(attrs={'id':['topic-buttons']}) - feeds = [ - (u'News', u'http://www.metro.co.uk/rss/news/'), (u'Money', u'http://www.metro.co.uk/rss/money/'), (u'Sport', u'http://www.metro.co.uk/rss/sport/'), (u'Film', u'http://www.metro.co.uk/rss/metrolife/film/'), (u'Music', u'http://www.metro.co.uk/rss/metrolife/music/'), (u'TV', u'http://www.metro.co.uk/rss/tv/'), (u'Showbiz', u'http://www.metro.co.uk/rss/showbiz/'), (u'Weird News', u'http://www.metro.co.uk/rss/weird/'), (u'Travel', u'http://www.metro.co.uk/rss/travel/'), (u'Lifestyle', u'http://www.metro.co.uk/rss/lifestyle/'), (u'Books', u'http://www.metro.co.uk/rss/lifestyle/books/'), (u'Food', u'http://www.metro.co.uk/rss/lifestyle/restaurants/')] + def parse_index(self): + articles = {} + key = None + ans = [] + feeds = [ ('UK', 'http://metro.co.uk/news/uk/'), + ('World', 'http://metro.co.uk/news/world/'), + ('Weird', 'http://metro.co.uk/news/weird/'), + ('Money', 'http://metro.co.uk/news/money/'), + ('Sport', 'http://metro.co.uk/sport/'), + ('Guilty Pleasures', 'http://metro.co.uk/guilty-pleasures/') + ] + for key, feed in feeds: + soup = self.index_to_soup(feed) + articles[key] = [] + ans.append(key) + + today = datetime.date.today() + today = time.mktime(today.timetuple())-60*60*24 + + for a in soup.findAll('a'): + for name, value in a.attrs: + if name == "class" and value=="post": + url = a['href'] + title = a['title'] + print title + description = '' + m = re.search('^.*uk/([^/]*)/([^/]*)/([^/]*)/', url) + skip = 1 + if len(m.groups()) == 3: + g = m.groups() + dt = datetime.datetime.strptime(''+g[0]+'-'+g[1]+'-'+g[2], '%Y-%m-%d') + pubdate = time.strftime('%a, %d %b', dt.timetuple()) + + dt = time.mktime(dt.timetuple()) + if dt >= today: + print pubdate + skip = 0 + else: + pubdate = strftime('%a, %d %b') + + summary = a.find(True, attrs={'class':'excerpt'}) + if summary: + description = self.tag_to_string(summary, use_alt=False) + + if skip == 0: + articles[key].append( + dict(title=title, url=url, date=pubdate, + description=description, + content='')) + #ans = self.sort_index_by(ans, {'The Front Page':-1, 'Dining In, Dining Out':1, 'Obituaries':2}) + ans = [(key, articles[key]) for key in ans if articles.has_key(key)] + return ans diff --git a/recipes/microwave_and_rf.recipe b/recipes/microwave_and_rf.recipe deleted file mode 100644 index 3cdf6e5acc..0000000000 --- a/recipes/microwave_and_rf.recipe +++ /dev/null @@ -1,224 +0,0 @@ -#!/usr/bin/env python -## -## Title: Microwave and RF -## -## License: GNU General Public License v3 - http://www.gnu.org/copyleft/gpl.html - -# Feb 2012: Initial release - -__license__ = 'GNU General Public License v3 - http://www.gnu.org/copyleft/gpl.html' -''' -mwrf.com -''' - -import re -from calibre.web.feeds.news import BasicNewsRecipe -from calibre.utils.magick import Image - -class Microwaves_and_RF(BasicNewsRecipe): - - Convert_Grayscale = False # Convert images to gray scale or not - - # Add sections that want to be excluded from the magazine - exclude_sections = [] - - # Add sections that want to be included from the magazine - include_sections = [] - - title = u'Microwaves and RF' - __author__ = u'kiavash' - description = u'Microwaves and RF Montly Magazine' - publisher = 'Penton Media, Inc.' - publication_type = 'magazine' - site = 'http://mwrf.com' - - language = 'en' - asciiize = True - timeout = 120 - simultaneous_downloads = 1 # very peaky site! - - # Main article is inside this tag - keep_only_tags = [dict(name='table', attrs={'id':'prtContent'})] - - no_stylesheets = True - remove_javascript = True - - # Flattens all the tables to make it compatible with Nook - conversion_options = {'linearize_tables' : True} - - remove_tags = [ - dict(name='span', attrs={'class':'body12'}), - ] - - remove_attributes = [ 'border', 'cellspacing', 'align', 'cellpadding', 'colspan', - 'valign', 'vspace', 'hspace', 'alt', 'width', 'height' ] - - # Specify extra CSS - overrides ALL other CSS (IE. Added last). - extra_css = 'body { font-family: verdana, helvetica, sans-serif; } \ - .introduction, .first { font-weight: bold; } \ - .cross-head { font-weight: bold; font-size: 125%; } \ - .cap, .caption { display: block; font-size: 80%; font-style: italic; } \ - .cap, .caption, .caption img, .caption span { display: block; margin: 5px auto; } \ - .byl, .byd, .byline img, .byline-name, .byline-title, .author-name, .author-position, \ - .correspondent-portrait img, .byline-lead-in, .name, .bbc-role { display: block; \ - font-size: 80%; font-style: italic; margin: 1px auto; } \ - .story-date, .published { font-size: 80%; } \ - table { width: 100%; } \ - td img { display: block; margin: 5px auto; } \ - ul { padding-top: 10px; } \ - ol { padding-top: 10px; } \ - li { padding-top: 5px; padding-bottom: 5px; } \ - h1 { font-size: 175%; font-weight: bold; } \ - h2 { font-size: 150%; font-weight: bold; } \ - h3 { font-size: 125%; font-weight: bold; } \ - h4, h5, h6 { font-size: 100%; font-weight: bold; }' - - # Remove the line breaks and float left/right and picture width/height. - preprocess_regexps = [(re.compile(r'', re.IGNORECASE), lambda m: ''), - (re.compile(r'', re.IGNORECASE), lambda m: ''), - (re.compile(r'float:.*?'), lambda m: ''), - (re.compile(r'width:.*?px'), lambda m: ''), - (re.compile(r'height:.*?px'), lambda m: '') - ] - - - def print_version(self, url): - url = re.sub(r'.html', '', url) - url = re.sub('/ArticleID/.*?/', '/Print.cfm?ArticleID=', url) - return url - - # Need to change the user agent to avoid potential download errors - def get_browser(self, *args, **kwargs): - from calibre import browser - kwargs['user_agent'] = 'Mozilla/5.0 (Windows NT 5.1; rv:10.0) Gecko/20100101 Firefox/10.0' - return browser(*args, **kwargs) - - - def parse_index(self): - - # Fetches the main page of Microwaves and RF - soup = self.index_to_soup(self.site) - - # First page has the ad, Let's find the redirect address. - url = soup.find('span', attrs={'class':'commonCopy'}).find('a').get('href') - if url.startswith('/'): - url = self.site + url - - soup = self.index_to_soup(url) - - # Searches the site for Issue ID link then returns the href address - # pointing to the latest issue - latest_issue = soup.find('a', attrs={'href':lambda x: x and 'IssueID' in x}).get('href') - - # Fetches the index page for of the latest issue - soup = self.index_to_soup(latest_issue) - - # Finds the main section of the page containing cover, issue date and - # TOC - ts = soup.find('div', attrs={'id':'columnContainer'}) - - # Finds the issue date - ds = ' '.join(self.tag_to_string(ts.find('span', attrs={'class':'CurrentIssueSectionHead'})).strip().split()[-2:]).capitalize() - self.log('Found Current Issue:', ds) - self.timefmt = ' [%s]'%ds - - # Finds the cover image - cover = ts.find('img', src = lambda x: x and 'Cover' in x) - if cover is not None: - self.cover_url = self.site + cover['src'] - self.log('Found Cover image:', self.cover_url) - - feeds = [] - article_info = [] - - # Finds all the articles (tiles and links) - articles = ts.findAll('a', attrs={'class':'commonArticleTitle'}) - - # Finds all the descriptions - descriptions = ts.findAll('span', attrs={'class':'commonCopy'}) - - # Find all the sections - sections = ts.findAll('span', attrs={'class':'kicker'}) - - title_number = 0 - - # Goes thru all the articles one by one and sort them out - for section in sections: - title_number = title_number + 1 - - # Removes the unwanted sections - if self.tag_to_string(section) in self.exclude_sections: - continue - - # Only includes the wanted sections - if self.include_sections: - if self.tag_to_string(section) not in self.include_sections: - continue - - - title = self.tag_to_string(articles[title_number]) - url = articles[title_number].get('href') - if url.startswith('/'): - url = self.site + url - - self.log('\tFound article:', title, 'at', url) - desc = self.tag_to_string(descriptions[title_number]) - self.log('\t\t', desc) - - article_info.append({'title':title, 'url':url, 'description':desc, - 'date':self.timefmt}) - - if article_info: - feeds.append((self.title, article_info)) - - #self.log(feeds) - return feeds - - def postprocess_html(self, soup, first): - if self.Convert_Grayscale: - #process all the images - for tag in soup.findAll(lambda tag: tag.name.lower()=='img' and tag.has_key('src')): - iurl = tag['src'] - img = Image() - img.open(iurl) - if img < 0: - raise RuntimeError('Out of memory') - img.type = "GrayscaleType" - img.save(iurl) - return soup - - def preprocess_html(self, soup): - - # Includes all the figures inside the final ebook - # Finds all the jpg links - for figure in soup.findAll('a', attrs = {'href' : lambda x: x and 'jpg' in x}): - - # makes sure that the link points to the absolute web address - if figure['href'].startswith('/'): - figure['href'] = self.site + figure['href'] - - figure.name = 'img' # converts the links to img - figure['src'] = figure['href'] # with the same address as href - figure['style'] = 'display:block' # adds /n before and after the image - del figure['href'] - del figure['target'] - - # Makes the title standing out - for title in soup.findAll('a', attrs = {'class': 'commonSectionTitle'}): - title.name = 'h1' - del title['href'] - del title['target'] - - # Makes the section name more visible - for section_name in soup.findAll('a', attrs = {'class': 'kicker2'}): - section_name.name = 'h5' - del section_name['href'] - del section_name['target'] - - # Removes all unrelated links - for link in soup.findAll('a', attrs = {'href': True}): - link.name = 'font' - del link['href'] - del link['target'] - - return soup diff --git a/recipes/mlody_technik_pl.recipe b/recipes/mlody_technik_pl.recipe index d019efb94c..741397d08a 100644 --- a/recipes/mlody_technik_pl.recipe +++ b/recipes/mlody_technik_pl.recipe @@ -2,7 +2,7 @@ from calibre.web.feeds.news import BasicNewsRecipe class Mlody_technik(BasicNewsRecipe): - title = u'Mlody technik' + title = u'Młody technik' __author__ = 'fenuks' description = u'Młody technik' category = 'science' diff --git a/recipes/mobile_bulgaria.recipe b/recipes/mobile_bulgaria.recipe new file mode 100644 index 0000000000..85440cf376 --- /dev/null +++ b/recipes/mobile_bulgaria.recipe @@ -0,0 +1,27 @@ +from calibre.web.feeds.news import BasicNewsRecipe + +class AdvancedUserRecipe1329123365(BasicNewsRecipe): + title = u'Mobilebulgaria.com' + __author__ = 'M3 Web' + description = 'The biggest Bulgarian site covering mobile consumer electronics. Offers detailed reviews, popular discussion forum, shop and platform for selling new and second hand phones and gadgets.' + category = 'News, Reviews, Offers, Forum' + oldest_article = 45 + max_articles_per_feed = 10 + language = 'bg' + encoding = 'windows-1251' + no_stylesheets = False + remove_javascript = True + keep_only_tags = [dict(name='div', attrs={'class':'bigblock'}), +dict(name='div', attrs={'class':'verybigblock'}), +dict(name='table', attrs={'class':'obiaviresults'}), +dict(name='div', attrs={'class':'forumblock'}), +dict(name='div', attrs={'class':'forumblock_b1'}), +dict(name='div', attrs={'class':'block2_2colswrap'})] + + feeds = [(u'News', u'http://www.mobilebulgaria.com/rss_full.php'), +(u'Reviews', u'http://www.mobilebulgaria.com/rss_reviews.php'), +(u'Offers', u'http://www.mobilebulgaria.com/obiavi/rss.php'), +(u'Forum', u'http://www.mobilebulgaria.com/rss_forum_last10.php')] + + extra_css = ''' + #gallery1 div{display: block; float: left; margin: 0 10px 10px 0;} ''' diff --git a/recipes/new_york_review_of_books.recipe b/recipes/new_york_review_of_books.recipe index bd18b95c43..bff7421b43 100644 --- a/recipes/new_york_review_of_books.recipe +++ b/recipes/new_york_review_of_books.recipe @@ -66,21 +66,22 @@ class NewYorkReviewOfBooks(BasicNewsRecipe): self.log('Issue date:', date) # Find TOC - toc = soup.find('ul', attrs={'class':'issue-article-list'}) + tocs = soup.findAll('ul', attrs={'class':'issue-article-list'}) articles = [] - for li in toc.findAll('li'): - h3 = li.find('h3') - title = self.tag_to_string(h3) - author = self.tag_to_string(li.find('h4')) - title = title + u' (%s)'%author - url = 'http://www.nybooks.com'+h3.find('a', href=True)['href'] - desc = '' - for p in li.findAll('p'): - desc += self.tag_to_string(p) - self.log('Found article:', title) - self.log('\t', url) - self.log('\t', desc) - articles.append({'title':title, 'url':url, 'date':'', + for toc in tocs: + for li in toc.findAll('li'): + h3 = li.find('h3') + title = self.tag_to_string(h3) + author = self.tag_to_string(li.find('h4')) + title = title + u' (%s)'%author + url = 'http://www.nybooks.com'+h3.find('a', href=True)['href'] + desc = '' + for p in li.findAll('p'): + desc += self.tag_to_string(p) + self.log('Found article:', title) + self.log('\t', url) + self.log('\t', desc) + articles.append({'title':title, 'url':url, 'date':'', 'description':desc}) return [('Current Issue', articles)] diff --git a/recipes/nikkei_news.recipe b/recipes/nikkei_news.recipe index aa351e0a0d..3260b8c168 100644 --- a/recipes/nikkei_news.recipe +++ b/recipes/nikkei_news.recipe @@ -13,8 +13,11 @@ class NikkeiNet_paper_subscription(BasicNewsRecipe): max_articles_per_feed = 30 language = 'ja' no_stylesheets = True - cover_url = 'http://parts.nikkei.com/parts/ds/images/common/logo_r1.svg' - masthead_url = 'http://parts.nikkei.com/parts/ds/images/common/logo_r1.svg' + #cover_url = 'http://parts.nikkei.com/parts/ds/images/common/logo_r1.svg' + cover_url = 'http://cdn.nikkei.co.jp/parts/ds/images/common/st_nikkei_r1_20101003_1.gif' + #masthead_url = 'http://parts.nikkei.com/parts/ds/images/common/logo_r1.svg' + masthead_url = 'http://cdn.nikkei.co.jp/parts/ds/images/common/st_nikkei_r1_20101003_1.gif' + cover_margins = (10, 188, '#ffffff') remove_tags_before = {'class':"cmn-indent"} remove_tags = [ @@ -40,8 +43,11 @@ class NikkeiNet_paper_subscription(BasicNewsRecipe): print "-------------------------open top page-------------------------------------" br.open('http://www.nikkei.com/') print "-------------------------open first login form-----------------------------" - link = br.links(url_regex="www.nikkei.com/etc/accounts/login").next() - br.follow_link(link) + try: + url = br.links(url_regex="www.nikkei.com/etc/accounts/login").next().url + except StopIteration: + url = 'http://www.nikkei.com/etc/accounts/login?dps=3&pageflag=top&url=http%3A%2F%2Fwww.nikkei.com%2F' + br.open(url) #br.follow_link(link) #response = br.response() #print response.get_data() print "-------------------------JS redirect(send autoPostForm)--------------------" diff --git a/recipes/nin.recipe b/recipes/nin.recipe index 78c9dd4324..084c49ab2b 100644 --- a/recipes/nin.recipe +++ b/recipes/nin.recipe @@ -15,7 +15,7 @@ class Nin(BasicNewsRecipe): publisher = 'NIN d.o.o. - Ringier d.o.o.' category = 'news, politics, Serbia' no_stylesheets = True - oldest_article = 15 + oldest_article = 180 encoding = 'utf-8' needs_subscription = True remove_empty_feeds = True @@ -25,7 +25,7 @@ class Nin(BasicNewsRecipe): use_embedded_content = False language = 'sr' publication_type = 'magazine' - masthead_url = 'http://www.nin.co.rs/img/head/logo.jpg' + masthead_url = 'http://www.nin.co.rs/img/logo_print.jpg' extra_css = """ @font-face {font-family: "sans1";src:url(res:///opt/sony/ebook/FONT/tt0003m_.ttf)} body{font-family: Verdana, Lucida, sans1, sans-serif} @@ -42,11 +42,11 @@ class Nin(BasicNewsRecipe): , 'tags' : category , 'publisher' : publisher , 'language' : language + , 'linearize_tables': True } preprocess_regexps = [ - (re.compile(r'.*?', re.DOTALL|re.IGNORECASE),lambda match: '') - ,(re.compile(r'.*?', re.DOTALL|re.IGNORECASE),lambda match: '') + (re.compile(r'
.*', re.DOTALL|re.IGNORECASE),lambda match: '') ,(re.compile(u'\u0110'), lambda match: u'\u00D0') ] @@ -60,42 +60,21 @@ class Nin(BasicNewsRecipe): br.submit() return br - keep_only_tags =[dict(name='td', attrs={'width':'520'})] - remove_tags_before =dict(name='span', attrs={'class':'izjava'}) - remove_tags_after =dict(name='html') - remove_tags = [ - dict(name=['object','link','iframe','meta','base']) - ,dict(attrs={'class':['fb-like','twitter-share-button']}) - ,dict(attrs={'rel':'nofollow'}) - ] - remove_attributes=['border','background','height','width','align','valign'] + remove_tags_before = dict(name='div', attrs={'class':'titleFont'}) + remove_tags_after = dict(name='div', attrs={'class':'standardFont'}) + remove_tags = [dict(name=['object','link','iframe','meta','base'])] + remove_attributes = ['border','background','height','width','align','valign'] def get_cover_url(self): cover_url = None soup = self.index_to_soup(self.INDEX) - for item in soup.findAll('a', href=True): - if item['href'].startswith('/pages/issue.php?id='): - simg = item.find('img') - if simg: - return self.PREFIX + item.img['src'] + cover = soup.find('img', attrs={'class':'issueImg'}) + if cover: + return self.PREFIX + cover['src'] return cover_url feeds = [(u'NIN Online', u'http://www.nin.co.rs/misc/rss.php?feed=RSS2.0')] - def preprocess_html(self, soup): - for item in soup.findAll(style=True): - del item['style'] - for item in soup.findAll('div'): - if len(item.contents) == 0: - item.extract() - for item in soup.findAll(['td','tr']): - item.name='div' - for item in soup.findAll('img'): - if not item.has_key('alt'): - item['alt'] = 'image' - for tbl in soup.findAll('table'): - img = tbl.find('img') - if img: - img.extract() - tbl.replaceWith(img) - return soup + def print_version(self, url): + return url + '&pf=1' + diff --git a/recipes/nsfw_corp.recipe b/recipes/nsfw_corp.recipe index c88bdd705e..0ed40ade3a 100644 --- a/recipes/nsfw_corp.recipe +++ b/recipes/nsfw_corp.recipe @@ -6,7 +6,6 @@ www.nsfwcorp.com ''' import urllib -from calibre import strftime from calibre.web.feeds.news import BasicNewsRecipe class NotSafeForWork(BasicNewsRecipe): @@ -21,8 +20,9 @@ class NotSafeForWork(BasicNewsRecipe): needs_subscription = True auto_cleanup = False INDEX = 'https://www.nsfwcorp.com' - LOGIN = INDEX + '/login' - use_embedded_content = False + LOGIN = INDEX + '/login/target/' + SETTINGS = INDEX + '/settings/' + use_embedded_content = True language = 'en' publication_type = 'magazine' masthead_url = 'http://assets.nsfwcorp.com/media/headers/nsfw_banner.jpg' @@ -46,15 +46,6 @@ class NotSafeForWork(BasicNewsRecipe): , 'language' : language } - remove_tags_before = dict(attrs={'id':'fromToLine'}) - remove_tags_after = dict(attrs={'id':'unlockButtonDiv'}) - remove_tags=[ - dict(name=['meta', 'link', 'iframe', 'embed', 'object']) - ,dict(name='a', attrs={'class':'switchToDeskNotes'}) - ,dict(attrs={'id':'unlockButtonDiv'}) - ] - remove_attributes = ['lang'] - def get_browser(self): br = BasicNewsRecipe.get_browser() br.open(self.LOGIN) @@ -65,30 +56,12 @@ class NotSafeForWork(BasicNewsRecipe): br.open(self.LOGIN, data) return br - def parse_index(self): - articles = [] - soup = self.index_to_soup(self.INDEX) - dispatches = soup.find(attrs={'id':'dispatches'}) - if dispatches: - for item in dispatches.findAll('h3'): - description = u'' - title_link = item.find('span', attrs={'class':'dispatchTitle'}) - description_link = item.find('span', attrs={'class':'dispatchSubtitle'}) - feed_link = item.find('a', href=True) - if feed_link: - url = self.INDEX + feed_link['href'] - title = self.tag_to_string(title_link) - description = self.tag_to_string(description_link) - date = strftime(self.timefmt) - articles.append({ - 'title' :title - ,'date' :date - ,'url' :url - ,'description':description - }) - return [('Dispatches', articles)] + def get_feeds(self): + self.feeds = [] + soup = self.index_to_soup(self.SETTINGS) + for item in soup.findAll('input', attrs={'type':'text'}): + if item.has_key('value') and item['value'].startswith('http://www.nsfwcorp.com/feed/'): + self.feeds.append(item['value']) + return self.feeds + return self.feeds - def preprocess_html(self, soup): - for item in soup.findAll(style=True): - del item['style'] - return soup diff --git a/recipes/nytimes.recipe b/recipes/nytimes.recipe index ba4e680158..f5b994275e 100644 --- a/recipes/nytimes.recipe +++ b/recipes/nytimes.recipe @@ -15,6 +15,7 @@ from calibre.ebooks.BeautifulSoup import BeautifulSoup, Tag, BeautifulStoneSoup class NYTimes(BasicNewsRecipe): recursions=1 # set this to zero to omit Related articles lists + match_regexps=[r'/[12][0-9][0-9][0-9]/[0-9]+/'] # speeds up processing by preventing index page links from being followed # set getTechBlogs to True to include the technology blogs # set tech_oldest_article to control article age @@ -24,6 +25,14 @@ class NYTimes(BasicNewsRecipe): tech_oldest_article = 14 tech_max_articles_per_feed = 25 + # set getPopularArticles to False if you don't want the Most E-mailed and Most Viewed articles + # otherwise you will get up to 20 of the most popular e-mailed and viewed articles (in each category) + getPopularArticles = True + popularPeriod = '1' # set this to the number of days to include in the measurement + # e.g. 7 will get the most popular measured over the last 7 days + # and 30 will get the most popular measured over 30 days. + # you still only get up to 20 articles in each category + # set headlinesOnly to True for the headlines-only version. If True, webEdition is ignored. headlinesOnly = True @@ -153,7 +162,7 @@ class NYTimes(BasicNewsRecipe): timefmt = '' - simultaneous_downloads = 1 + #simultaneous_downloads = 1 # no longer required to deal with ads cover_margins = (18,18,'grey99') @@ -204,7 +213,8 @@ class NYTimes(BasicNewsRecipe): re.compile('^subNavigation'), re.compile('^leaderboard'), re.compile('^module'), - re.compile('commentCount') + re.compile('commentCount'), + 'credit' ]}), dict(name='div', attrs={'class':re.compile('toolsList')}), # bits dict(name='div', attrs={'class':re.compile('postNavigation')}), # bits @@ -291,11 +301,11 @@ class NYTimes(BasicNewsRecipe): del ans[idx] idx_max = idx_max-1 continue - if self.verbose: + if True: #self.verbose self.log("Section %s: %d articles" % (ans[idx][0], len(ans[idx][1])) ) for article in ans[idx][1]: total_article_count += 1 - if self.verbose: + if True: #self.verbose self.log("\t%-40.40s... \t%-60.60s..." % (article['title'].encode('cp1252','replace'), article['url'].encode('cp1252','replace'))) idx = idx+1 @@ -351,23 +361,8 @@ class NYTimes(BasicNewsRecipe): br = BasicNewsRecipe.get_browser() return br -## This doesn't work (and probably never did). It either gets another serve of the advertisement, -## or if it gets the article then get_soup (from which it is invoked) traps trying to do xml decoding. -## -## def skip_ad_pages(self, soup): -## # Skip ad pages served before actual article -## skip_tag = soup.find(True, {'name':'skip'}) -## if skip_tag is not None: -## self.log.warn("Found forwarding link: %s" % skip_tag.parent['href']) -## url = 'http://www.nytimes.com' + re.sub(r'\?.*', '', skip_tag.parent['href']) -## url += '?pagewanted=all' -## self.log.warn("Skipping ad to article at '%s'" % url) -## return self.index_to_soup(url, raw=True) - - cover_tag = 'NY_NYT' def get_cover_url(self): - from datetime import timedelta, date cover = 'http://webmedia.newseum.org/newseum-multimedia/dfp/jpg'+str(date.today().day)+'/lg/'+self.cover_tag+'.jpg' br = BasicNewsRecipe.get_browser() daysback=1 @@ -390,6 +385,7 @@ class NYTimes(BasicNewsRecipe): masthead_url = 'http://graphics8.nytimes.com/images/misc/nytlogo379x64.gif' + def short_title(self): return self.title @@ -398,6 +394,7 @@ class NYTimes(BasicNewsRecipe): from contextlib import closing import copy from calibre.ebooks.chardet import xml_to_unicode + print("ARTICLE_TO_SOUP "+url_or_raw) if re.match(r'\w+://', url_or_raw): br = self.clone_browser(self.browser) open_func = getattr(br, 'open_novisit', br.open) @@ -489,6 +486,67 @@ class NYTimes(BasicNewsRecipe): description=description, author=author, content='')) + def get_popular_articles(self,ans): + if self.getPopularArticles: + popular_articles = {} + key_list = [] + + def handleh3(h3tag): + try: + url = h3tag.a['href'] + except: + return ('','','','') + url = re.sub(r'\?.*', '', url) + if self.exclude_url(url): + return ('','','','') + url += '?pagewanted=all' + title = self.tag_to_string(h3tag.a,False) + h6tag = h3tag.findNextSibling('h6') + if h6tag is not None: + author = self.tag_to_string(h6tag,False) + else: + author = '' + ptag = h3tag.findNextSibling('p') + if ptag is not None: + desc = self.tag_to_string(ptag,False) + else: + desc = '' + return(title,url,author,desc) + + + have_emailed = False + emailed_soup = self.index_to_soup('http://www.nytimes.com/most-popular-emailed?period='+self.popularPeriod) + for h3tag in emailed_soup.findAll('h3'): + (title,url,author,desc) = handleh3(h3tag) + if url=='': + continue + if not have_emailed: + key_list.append('Most E-Mailed') + popular_articles['Most E-Mailed'] = [] + have_emailed = True + popular_articles['Most E-Mailed'].append( + dict(title=title, url=url, date=strftime('%a, %d %b'), + description=desc, author=author, + content='')) + have_viewed = False + viewed_soup = self.index_to_soup('http://www.nytimes.com/most-popular-viewed?period='+self.popularPeriod) + for h3tag in viewed_soup.findAll('h3'): + (title,url,author,desc) = handleh3(h3tag) + if url=='': + continue + if not have_viewed: + key_list.append('Most Viewed') + popular_articles['Most Viewed'] = [] + have_viewed = True + popular_articles['Most Viewed'].append( + dict(title=title, url=url, date=strftime('%a, %d %b'), + description=desc, author=author, + content='')) + viewed_ans = [(k, popular_articles[k]) for k in key_list if popular_articles.has_key(k)] + for x in viewed_ans: + ans.append(x) + return ans + def get_tech_feeds(self,ans): if self.getTechBlogs: tech_articles = {} @@ -550,7 +608,7 @@ class NYTimes(BasicNewsRecipe): self.handle_article(lidiv) self.ans = [(k, self.articles[k]) for k in self.ans if self.articles.has_key(k)] - return self.filter_ans(self.get_tech_feeds(self.ans)) + return self.filter_ans(self.get_tech_feeds(self.get_popular_articles(self.ans))) def parse_todays_index(self): @@ -583,7 +641,7 @@ class NYTimes(BasicNewsRecipe): self.handle_article(lidiv) self.ans = [(k, self.articles[k]) for k in self.ans if self.articles.has_key(k)] - return self.filter_ans(self.get_tech_feeds(self.ans)) + return self.filter_ans(self.get_tech_feeds(self.get_popular_articles(self.ans))) def parse_headline_index(self): @@ -657,7 +715,7 @@ class NYTimes(BasicNewsRecipe): self.articles[section_name].append(dict(title=title, url=url, date=pubdate, description=description, author=author, content='')) self.ans = [(k, self.articles[k]) for k in self.ans if self.articles.has_key(k)] - return self.filter_ans(self.get_tech_feeds(self.ans)) + return self.filter_ans(self.get_tech_feeds(self.get_popular_articles(self.ans))) def parse_index(self): if self.headlinesOnly: @@ -745,11 +803,12 @@ class NYTimes(BasicNewsRecipe): def preprocess_html(self, soup): - print("PREPROCESS TITLE="+self.tag_to_string(soup.title)) + #print(strftime("%H:%M:%S")+" -- PREPROCESS TITLE="+self.tag_to_string(soup.title)) skip_tag = soup.find(True, {'name':'skip'}) if skip_tag is not None: - url = 'http://www.nytimes.com' + re.sub(r'\?.*', '', skip_tag.parent['href']) - url += '?pagewanted=all' + #url = 'http://www.nytimes.com' + re.sub(r'\?.*', '', skip_tag.parent['href']) + url = 'http://www.nytimes.com' + skip_tag.parent['href'] + #url += '?pagewanted=all' self.log.warn("Skipping ad to article at '%s'" % url) sleep(5) soup = self.handle_tags(self.article_to_soup(url)) @@ -920,6 +979,7 @@ class NYTimes(BasicNewsRecipe): for aside in soup.findAll('div','aside'): aside.extract() soup = self.strip_anchors(soup,True) + #print("RECURSIVE: "+self.tag_to_string(soup.title)) if soup.find('div',attrs={'id':'blogcontent'}) is None: if first_fetch: @@ -969,122 +1029,122 @@ class NYTimes(BasicNewsRecipe): self.log("ERROR: One picture per article in postprocess_html") try: - # Change captions to italic - for caption in soup.findAll(True, {'class':'caption'}) : - if caption and len(caption) > 0: - cTag = Tag(soup, "p", [("class", "caption")]) - c = self.fixChars(self.tag_to_string(caption,use_alt=False)).strip() - mp_off = c.find("More Photos") - if mp_off >= 0: - c = c[:mp_off] - cTag.insert(0, c) - caption.replaceWith(cTag) + # Change captions to italic + for caption in soup.findAll(True, {'class':'caption'}) : + if caption and len(caption) > 0: + cTag = Tag(soup, "p", [("class", "caption")]) + c = self.fixChars(self.tag_to_string(caption,use_alt=False)).strip() + mp_off = c.find("More Photos") + if mp_off >= 0: + c = c[:mp_off] + cTag.insert(0, c) + caption.replaceWith(cTag) except: - self.log("ERROR: Problem in change captions to italic") + self.log("ERROR: Problem in change captions to italic") try: - # Change to

- h1 = soup.find('h1') - blogheadline = str(h1) #added for dealbook - if h1: - headline = h1.find("nyt_headline") - if headline: - tag = Tag(soup, "h2") - tag['class'] = "headline" - tag.insert(0, self.fixChars(headline.contents[0])) - h1.replaceWith(tag) - elif blogheadline.find('entry-title'):#added for dealbook - tag = Tag(soup, "h2")#added for dealbook - tag['class'] = "headline"#added for dealbook - tag.insert(0, self.fixChars(h1.contents[0]))#added for dealbook - h1.replaceWith(tag)#added for dealbook + # Change to

+ h1 = soup.find('h1') + blogheadline = str(h1) #added for dealbook + if h1: + headline = h1.find("nyt_headline") + if headline: + tag = Tag(soup, "h2") + tag['class'] = "headline" + tag.insert(0, self.fixChars(headline.contents[0])) + h1.replaceWith(tag) + elif blogheadline.find('entry-title'):#added for dealbook + tag = Tag(soup, "h2")#added for dealbook + tag['class'] = "headline"#added for dealbook + tag.insert(0, self.fixChars(h1.contents[0]))#added for dealbook + h1.replaceWith(tag)#added for dealbook - else: - # Blog entry - replace headline, remove
tags - BCC I think this is no longer functional 1-18-2011 - headline = soup.find('title') - if headline: - tag = Tag(soup, "h2") - tag['class'] = "headline" - tag.insert(0, self.fixChars(headline.renderContents())) - soup.insert(0, tag) - hrs = soup.findAll('hr') - for hr in hrs: - hr.extract() + else: + # Blog entry - replace headline, remove
tags - BCC I think this is no longer functional 1-18-2011 + headline = soup.find('title') + if headline: + tag = Tag(soup, "h2") + tag['class'] = "headline" + tag.insert(0, self.fixChars(self.tag_to_string(headline,False))) + soup.insert(0, tag) + hrs = soup.findAll('hr') + for hr in hrs: + hr.extract() except: - self.log("ERROR: Problem in Change to

") + self.log("ERROR: Problem in Change to

") try: - #if this is from a blog (dealbook, fix the byline format - bylineauthor = soup.find('address',attrs={'class':'byline author vcard'}) - if bylineauthor: - tag = Tag(soup, "h6") - tag['class'] = "byline" - tag.insert(0, self.fixChars(bylineauthor.renderContents())) - bylineauthor.replaceWith(tag) + #if this is from a blog (dealbook, fix the byline format + bylineauthor = soup.find('address',attrs={'class':'byline author vcard'}) + if bylineauthor: + tag = Tag(soup, "h6") + tag['class'] = "byline" + tag.insert(0, self.fixChars(self.tag_to_string(bylineauthor,False))) + bylineauthor.replaceWith(tag) except: self.log("ERROR: fixing byline author format") try: - #if this is a blog (dealbook) fix the credit style for the pictures - blogcredit = soup.find('div',attrs={'class':'credit'}) - if blogcredit: - tag = Tag(soup, "h6") - tag['class'] = "credit" - tag.insert(0, self.fixChars(blogcredit.renderContents())) - blogcredit.replaceWith(tag) + #if this is a blog (dealbook) fix the credit style for the pictures + blogcredit = soup.find('div',attrs={'class':'credit'}) + if blogcredit: + tag = Tag(soup, "h6") + tag['class'] = "credit" + tag.insert(0, self.fixChars(self.tag_to_string(blogcredit,False))) + blogcredit.replaceWith(tag) except: self.log("ERROR: fixing credit format") try: - # Change

to

- used in editorial blogs - masthead = soup.find("h1") - if masthead: - # Nuke the href - if masthead.a: - del(masthead.a['href']) - tag = Tag(soup, "h3") - tag.insert(0, self.fixChars(masthead.contents[0])) - masthead.replaceWith(tag) + # Change

to

- used in editorial blogs + masthead = soup.find("h1") + if masthead: + # Nuke the href + if masthead.a: + del(masthead.a['href']) + tag = Tag(soup, "h3") + tag.insert(0, self.fixChars(masthead.contents[0])) + masthead.replaceWith(tag) except: - self.log("ERROR: Problem in Change

to

- used in editorial blogs") + self.log("ERROR: Problem in Change

to

- used in editorial blogs") try: - # Change to - for subhead in soup.findAll(True, {'class':'bold'}) : - if subhead.contents: - bTag = Tag(soup, "b") - bTag.insert(0, subhead.contents[0]) - subhead.replaceWith(bTag) + # Change to + for subhead in soup.findAll(True, {'class':'bold'}) : + if subhead.contents: + bTag = Tag(soup, "b") + bTag.insert(0, subhead.contents[0]) + subhead.replaceWith(bTag) except: - self.log("ERROR: Problem in Change

to

- used in editorial blogs") + self.log("ERROR: Problem in Change

to

- used in editorial blogs") try: - #remove the update tag - blogupdated = soup.find('span', {'class':'update'}) - if blogupdated: - blogupdated.replaceWith("") + #remove the update tag + blogupdated = soup.find('span', {'class':'update'}) + if blogupdated: + blogupdated.replaceWith("") except: - self.log("ERROR: Removing strong tag") + self.log("ERROR: Removing strong tag") try: - divTag = soup.find('div',attrs={'id':'articleBody'}) - if divTag: - divTag['class'] = divTag['id'] + divTag = soup.find('div',attrs={'id':'articleBody'}) + if divTag: + divTag['class'] = divTag['id'] except: - self.log("ERROR: Problem in soup.find(div,attrs={id:articleBody})") + self.log("ERROR: Problem in soup.find(div,attrs={id:articleBody})") try: - # Add class="authorId" to
so we can format with CSS - divTag = soup.find('div',attrs={'id':'authorId'}) - if divTag and divTag.contents[0]: - tag = Tag(soup, "p") - tag['class'] = "authorId" - tag.insert(0, self.fixChars(self.tag_to_string(divTag.contents[0], - use_alt=False))) - divTag.replaceWith(tag) + # Add class="authorId" to
so we can format with CSS + divTag = soup.find('div',attrs={'id':'authorId'}) + if divTag and divTag.contents[0]: + tag = Tag(soup, "p") + tag['class'] = "authorId" + tag.insert(0, self.fixChars(self.tag_to_string(divTag.contents[0], + use_alt=False))) + divTag.replaceWith(tag) except: - self.log("ERROR: Problem in Add class=authorId to
so we can format with CSS") - + self.log("ERROR: Problem in Add class=authorId to
so we can format with CSS") + #print(strftime("%H:%M:%S")+" -- POSTPROCESS TITLE="+self.tag_to_string(soup.title)) return soup def populate_article_metadata(self, article, soup, first): diff --git a/recipes/nytimes_sub.recipe b/recipes/nytimes_sub.recipe index 023a787983..cf25865b9c 100644 --- a/recipes/nytimes_sub.recipe +++ b/recipes/nytimes_sub.recipe @@ -15,6 +15,7 @@ from calibre.ebooks.BeautifulSoup import BeautifulSoup, Tag, BeautifulStoneSoup class NYTimes(BasicNewsRecipe): recursions=1 # set this to zero to omit Related articles lists + match_regexps=[r'/[12][0-9][0-9][0-9]/[0-9]+/'] # speeds up processing by preventing index page links from being followed # set getTechBlogs to True to include the technology blogs # set tech_oldest_article to control article age @@ -24,6 +25,14 @@ class NYTimes(BasicNewsRecipe): tech_oldest_article = 14 tech_max_articles_per_feed = 25 + # set getPopularArticles to False if you don't want the Most E-mailed and Most Viewed articles + # otherwise you will get up to 20 of the most popular e-mailed and viewed articles (in each category) + getPopularArticles = True + popularPeriod = '1' # set this to the number of days to include in the measurement + # e.g. 7 will get the most popular measured over the last 7 days + # and 30 will get the most popular measured over 30 days. + # you still only get up to 20 articles in each category + # set headlinesOnly to True for the headlines-only version. If True, webEdition is ignored. headlinesOnly = False @@ -32,7 +41,7 @@ class NYTimes(BasicNewsRecipe): # number of days old an article can be for inclusion. If oldest_web_article = None all articles # will be included. Note: oldest_web_article is ignored if webEdition = False webEdition = False - oldest_web_article = 7 + oldest_web_article = None # download higher resolution images than the small thumbnails typically included in the article # the down side of having large beautiful images is the file size is much larger, on the order of 7MB per paper @@ -115,19 +124,19 @@ class NYTimes(BasicNewsRecipe): if headlinesOnly: title='New York Times Headlines' description = 'Headlines from the New York Times' - needs_subscription = False + needs_subscription = 'optional' elif webEdition: title='New York Times (Web)' description = 'New York Times on the Web' - needs_subscription = False + needs_subscription = 'optional' elif replaceKindleVersion: title='The New York Times' description = 'Today\'s New York Times' - needs_subscription = False + needs_subscription = 'optional' else: title='New York Times' description = 'Today\'s New York Times' - needs_subscription = False + needs_subscription = 'optional' def decode_url_date(self,url): urlitems = url.split('/') @@ -153,7 +162,7 @@ class NYTimes(BasicNewsRecipe): timefmt = '' - simultaneous_downloads = 1 + #simultaneous_downloads = 1 # no longer required to deal with ads cover_margins = (18,18,'grey99') @@ -204,7 +213,8 @@ class NYTimes(BasicNewsRecipe): re.compile('^subNavigation'), re.compile('^leaderboard'), re.compile('^module'), - re.compile('commentCount') + re.compile('commentCount'), + 'credit' ]}), dict(name='div', attrs={'class':re.compile('toolsList')}), # bits dict(name='div', attrs={'class':re.compile('postNavigation')}), # bits @@ -291,11 +301,11 @@ class NYTimes(BasicNewsRecipe): del ans[idx] idx_max = idx_max-1 continue - if self.verbose: + if True: #self.verbose self.log("Section %s: %d articles" % (ans[idx][0], len(ans[idx][1])) ) for article in ans[idx][1]: total_article_count += 1 - if self.verbose: + if True: #self.verbose self.log("\t%-40.40s... \t%-60.60s..." % (article['title'].encode('cp1252','replace'), article['url'].encode('cp1252','replace'))) idx = idx+1 @@ -349,25 +359,18 @@ class NYTimes(BasicNewsRecipe): def get_browser(self): br = BasicNewsRecipe.get_browser() + if self.username is not None and self.password is not None: + br.open('http://www.nytimes.com/auth/login') + br.form = br.forms().next() + br['userid'] = self.username + br['password'] = self.password + raw = br.submit().read() + if 'Please try again' in raw: + raise Exception('Your username and password are incorrect') return br -## This doesn't work (and probably never did). It either gets another serve of the advertisement, -## or if it gets the article then get_soup (from which it is invoked) traps trying to do xml decoding. -## -## def skip_ad_pages(self, soup): -## # Skip ad pages served before actual article -## skip_tag = soup.find(True, {'name':'skip'}) -## if skip_tag is not None: -## self.log.warn("Found forwarding link: %s" % skip_tag.parent['href']) -## url = 'http://www.nytimes.com' + re.sub(r'\?.*', '', skip_tag.parent['href']) -## url += '?pagewanted=all' -## self.log.warn("Skipping ad to article at '%s'" % url) -## return self.index_to_soup(url, raw=True) - - cover_tag = 'NY_NYT' def get_cover_url(self): - from datetime import timedelta, date cover = 'http://webmedia.newseum.org/newseum-multimedia/dfp/jpg'+str(date.today().day)+'/lg/'+self.cover_tag+'.jpg' br = BasicNewsRecipe.get_browser() daysback=1 @@ -390,6 +393,7 @@ class NYTimes(BasicNewsRecipe): masthead_url = 'http://graphics8.nytimes.com/images/misc/nytlogo379x64.gif' + def short_title(self): return self.title @@ -398,6 +402,7 @@ class NYTimes(BasicNewsRecipe): from contextlib import closing import copy from calibre.ebooks.chardet import xml_to_unicode + print("ARTICLE_TO_SOUP "+url_or_raw) if re.match(r'\w+://', url_or_raw): br = self.clone_browser(self.browser) open_func = getattr(br, 'open_novisit', br.open) @@ -489,6 +494,67 @@ class NYTimes(BasicNewsRecipe): description=description, author=author, content='')) + def get_popular_articles(self,ans): + if self.getPopularArticles: + popular_articles = {} + key_list = [] + + def handleh3(h3tag): + try: + url = h3tag.a['href'] + except: + return ('','','','') + url = re.sub(r'\?.*', '', url) + if self.exclude_url(url): + return ('','','','') + url += '?pagewanted=all' + title = self.tag_to_string(h3tag.a,False) + h6tag = h3tag.findNextSibling('h6') + if h6tag is not None: + author = self.tag_to_string(h6tag,False) + else: + author = '' + ptag = h3tag.findNextSibling('p') + if ptag is not None: + desc = self.tag_to_string(ptag,False) + else: + desc = '' + return(title,url,author,desc) + + + have_emailed = False + emailed_soup = self.index_to_soup('http://www.nytimes.com/most-popular-emailed?period='+self.popularPeriod) + for h3tag in emailed_soup.findAll('h3'): + (title,url,author,desc) = handleh3(h3tag) + if url=='': + continue + if not have_emailed: + key_list.append('Most E-Mailed') + popular_articles['Most E-Mailed'] = [] + have_emailed = True + popular_articles['Most E-Mailed'].append( + dict(title=title, url=url, date=strftime('%a, %d %b'), + description=desc, author=author, + content='')) + have_viewed = False + viewed_soup = self.index_to_soup('http://www.nytimes.com/most-popular-viewed?period='+self.popularPeriod) + for h3tag in viewed_soup.findAll('h3'): + (title,url,author,desc) = handleh3(h3tag) + if url=='': + continue + if not have_viewed: + key_list.append('Most Viewed') + popular_articles['Most Viewed'] = [] + have_viewed = True + popular_articles['Most Viewed'].append( + dict(title=title, url=url, date=strftime('%a, %d %b'), + description=desc, author=author, + content='')) + viewed_ans = [(k, popular_articles[k]) for k in key_list if popular_articles.has_key(k)] + for x in viewed_ans: + ans.append(x) + return ans + def get_tech_feeds(self,ans): if self.getTechBlogs: tech_articles = {} @@ -550,7 +616,7 @@ class NYTimes(BasicNewsRecipe): self.handle_article(lidiv) self.ans = [(k, self.articles[k]) for k in self.ans if self.articles.has_key(k)] - return self.filter_ans(self.get_tech_feeds(self.ans)) + return self.filter_ans(self.get_tech_feeds(self.get_popular_articles(self.ans))) def parse_todays_index(self): @@ -583,7 +649,7 @@ class NYTimes(BasicNewsRecipe): self.handle_article(lidiv) self.ans = [(k, self.articles[k]) for k in self.ans if self.articles.has_key(k)] - return self.filter_ans(self.get_tech_feeds(self.ans)) + return self.filter_ans(self.get_tech_feeds(self.get_popular_articles(self.ans))) def parse_headline_index(self): @@ -657,7 +723,7 @@ class NYTimes(BasicNewsRecipe): self.articles[section_name].append(dict(title=title, url=url, date=pubdate, description=description, author=author, content='')) self.ans = [(k, self.articles[k]) for k in self.ans if self.articles.has_key(k)] - return self.filter_ans(self.get_tech_feeds(self.ans)) + return self.filter_ans(self.get_tech_feeds(self.get_popular_articles(self.ans))) def parse_index(self): if self.headlinesOnly: @@ -745,11 +811,12 @@ class NYTimes(BasicNewsRecipe): def preprocess_html(self, soup): - print("PREPROCESS TITLE="+self.tag_to_string(soup.title)) + #print(strftime("%H:%M:%S")+" -- PREPROCESS TITLE="+self.tag_to_string(soup.title)) skip_tag = soup.find(True, {'name':'skip'}) if skip_tag is not None: - url = 'http://www.nytimes.com' + re.sub(r'\?.*', '', skip_tag.parent['href']) - url += '?pagewanted=all' + #url = 'http://www.nytimes.com' + re.sub(r'\?.*', '', skip_tag.parent['href']) + url = 'http://www.nytimes.com' + skip_tag.parent['href'] + #url += '?pagewanted=all' self.log.warn("Skipping ad to article at '%s'" % url) sleep(5) soup = self.handle_tags(self.article_to_soup(url)) @@ -920,6 +987,7 @@ class NYTimes(BasicNewsRecipe): for aside in soup.findAll('div','aside'): aside.extract() soup = self.strip_anchors(soup,True) + #print("RECURSIVE: "+self.tag_to_string(soup.title)) if soup.find('div',attrs={'id':'blogcontent'}) is None: if first_fetch: @@ -969,122 +1037,122 @@ class NYTimes(BasicNewsRecipe): self.log("ERROR: One picture per article in postprocess_html") try: - # Change captions to italic - for caption in soup.findAll(True, {'class':'caption'}) : - if caption and len(caption) > 0: - cTag = Tag(soup, "p", [("class", "caption")]) - c = self.fixChars(self.tag_to_string(caption,use_alt=False)).strip() - mp_off = c.find("More Photos") - if mp_off >= 0: - c = c[:mp_off] - cTag.insert(0, c) - caption.replaceWith(cTag) + # Change captions to italic + for caption in soup.findAll(True, {'class':'caption'}) : + if caption and len(caption) > 0: + cTag = Tag(soup, "p", [("class", "caption")]) + c = self.fixChars(self.tag_to_string(caption,use_alt=False)).strip() + mp_off = c.find("More Photos") + if mp_off >= 0: + c = c[:mp_off] + cTag.insert(0, c) + caption.replaceWith(cTag) except: - self.log("ERROR: Problem in change captions to italic") + self.log("ERROR: Problem in change captions to italic") try: - # Change to

- h1 = soup.find('h1') - blogheadline = str(h1) #added for dealbook - if h1: - headline = h1.find("nyt_headline") - if headline: - tag = Tag(soup, "h2") - tag['class'] = "headline" - tag.insert(0, self.fixChars(headline.contents[0])) - h1.replaceWith(tag) - elif blogheadline.find('entry-title'):#added for dealbook - tag = Tag(soup, "h2")#added for dealbook - tag['class'] = "headline"#added for dealbook - tag.insert(0, self.fixChars(h1.contents[0]))#added for dealbook - h1.replaceWith(tag)#added for dealbook + # Change to

+ h1 = soup.find('h1') + blogheadline = str(h1) #added for dealbook + if h1: + headline = h1.find("nyt_headline") + if headline: + tag = Tag(soup, "h2") + tag['class'] = "headline" + tag.insert(0, self.fixChars(headline.contents[0])) + h1.replaceWith(tag) + elif blogheadline.find('entry-title'):#added for dealbook + tag = Tag(soup, "h2")#added for dealbook + tag['class'] = "headline"#added for dealbook + tag.insert(0, self.fixChars(h1.contents[0]))#added for dealbook + h1.replaceWith(tag)#added for dealbook - else: - # Blog entry - replace headline, remove
tags - BCC I think this is no longer functional 1-18-2011 - headline = soup.find('title') - if headline: - tag = Tag(soup, "h2") - tag['class'] = "headline" - tag.insert(0, self.fixChars(headline.renderContents())) - soup.insert(0, tag) - hrs = soup.findAll('hr') - for hr in hrs: - hr.extract() + else: + # Blog entry - replace headline, remove
tags - BCC I think this is no longer functional 1-18-2011 + headline = soup.find('title') + if headline: + tag = Tag(soup, "h2") + tag['class'] = "headline" + tag.insert(0, self.fixChars(self.tag_to_string(headline,False))) + soup.insert(0, tag) + hrs = soup.findAll('hr') + for hr in hrs: + hr.extract() except: - self.log("ERROR: Problem in Change to

") + self.log("ERROR: Problem in Change to

") try: - #if this is from a blog (dealbook, fix the byline format - bylineauthor = soup.find('address',attrs={'class':'byline author vcard'}) - if bylineauthor: - tag = Tag(soup, "h6") - tag['class'] = "byline" - tag.insert(0, self.fixChars(bylineauthor.renderContents())) - bylineauthor.replaceWith(tag) + #if this is from a blog (dealbook, fix the byline format + bylineauthor = soup.find('address',attrs={'class':'byline author vcard'}) + if bylineauthor: + tag = Tag(soup, "h6") + tag['class'] = "byline" + tag.insert(0, self.fixChars(self.tag_to_string(bylineauthor,False))) + bylineauthor.replaceWith(tag) except: self.log("ERROR: fixing byline author format") try: - #if this is a blog (dealbook) fix the credit style for the pictures - blogcredit = soup.find('div',attrs={'class':'credit'}) - if blogcredit: - tag = Tag(soup, "h6") - tag['class'] = "credit" - tag.insert(0, self.fixChars(blogcredit.renderContents())) - blogcredit.replaceWith(tag) + #if this is a blog (dealbook) fix the credit style for the pictures + blogcredit = soup.find('div',attrs={'class':'credit'}) + if blogcredit: + tag = Tag(soup, "h6") + tag['class'] = "credit" + tag.insert(0, self.fixChars(self.tag_to_string(blogcredit,False))) + blogcredit.replaceWith(tag) except: self.log("ERROR: fixing credit format") try: - # Change

to

- used in editorial blogs - masthead = soup.find("h1") - if masthead: - # Nuke the href - if masthead.a: - del(masthead.a['href']) - tag = Tag(soup, "h3") - tag.insert(0, self.fixChars(masthead.contents[0])) - masthead.replaceWith(tag) + # Change

to

- used in editorial blogs + masthead = soup.find("h1") + if masthead: + # Nuke the href + if masthead.a: + del(masthead.a['href']) + tag = Tag(soup, "h3") + tag.insert(0, self.fixChars(masthead.contents[0])) + masthead.replaceWith(tag) except: - self.log("ERROR: Problem in Change

to

- used in editorial blogs") + self.log("ERROR: Problem in Change

to

- used in editorial blogs") try: - # Change to - for subhead in soup.findAll(True, {'class':'bold'}) : - if subhead.contents: - bTag = Tag(soup, "b") - bTag.insert(0, subhead.contents[0]) - subhead.replaceWith(bTag) + # Change to + for subhead in soup.findAll(True, {'class':'bold'}) : + if subhead.contents: + bTag = Tag(soup, "b") + bTag.insert(0, subhead.contents[0]) + subhead.replaceWith(bTag) except: - self.log("ERROR: Problem in Change

to

- used in editorial blogs") + self.log("ERROR: Problem in Change

to

- used in editorial blogs") try: - #remove the update tag - blogupdated = soup.find('span', {'class':'update'}) - if blogupdated: - blogupdated.replaceWith("") + #remove the update tag + blogupdated = soup.find('span', {'class':'update'}) + if blogupdated: + blogupdated.replaceWith("") except: - self.log("ERROR: Removing strong tag") + self.log("ERROR: Removing strong tag") try: - divTag = soup.find('div',attrs={'id':'articleBody'}) - if divTag: - divTag['class'] = divTag['id'] + divTag = soup.find('div',attrs={'id':'articleBody'}) + if divTag: + divTag['class'] = divTag['id'] except: - self.log("ERROR: Problem in soup.find(div,attrs={id:articleBody})") + self.log("ERROR: Problem in soup.find(div,attrs={id:articleBody})") try: - # Add class="authorId" to
so we can format with CSS - divTag = soup.find('div',attrs={'id':'authorId'}) - if divTag and divTag.contents[0]: - tag = Tag(soup, "p") - tag['class'] = "authorId" - tag.insert(0, self.fixChars(self.tag_to_string(divTag.contents[0], - use_alt=False))) - divTag.replaceWith(tag) + # Add class="authorId" to
so we can format with CSS + divTag = soup.find('div',attrs={'id':'authorId'}) + if divTag and divTag.contents[0]: + tag = Tag(soup, "p") + tag['class'] = "authorId" + tag.insert(0, self.fixChars(self.tag_to_string(divTag.contents[0], + use_alt=False))) + divTag.replaceWith(tag) except: - self.log("ERROR: Problem in Add class=authorId to
so we can format with CSS") - + self.log("ERROR: Problem in Add class=authorId to
so we can format with CSS") + #print(strftime("%H:%M:%S")+" -- POSTPROCESS TITLE="+self.tag_to_string(soup.title)) return soup def populate_article_metadata(self, article, soup, first): diff --git a/recipes/outside_magazine.recipe b/recipes/outside_magazine.recipe new file mode 100644 index 0000000000..15eaf3221e --- /dev/null +++ b/recipes/outside_magazine.recipe @@ -0,0 +1,65 @@ +from calibre.web.feeds.recipes import BasicNewsRecipe + +class NYTimes(BasicNewsRecipe): + + title = 'Outside Magazine' + __author__ = 'Krittika Goyal' + description = 'Outside Magazine - Free 1 Month Old Issue' + timefmt = ' [%d %b, %Y]' + needs_subscription = False + language = 'en' + + no_stylesheets = True + #auto_cleanup = True + #auto_cleanup_keep = '//div[@class="thumbnail"]' + + keep_only_tags = dict(name='div', attrs={'class':'masonry-box width-four'}) + remove_tags = [ + dict(name='div', attrs={'id':['share-bar', 'outbrain_widget_0', 'outbrain_widget_1', 'livefyre']}), + #dict(name='div', attrs={'id':['qrformdiv', 'inSection', 'alpha-inner']}), + #dict(name='form', attrs={'onsubmit':''}), + dict(name='section', attrs={'id':['article-quote', 'article-navigation']}), + ] + #TO GET ARTICLE TOC + def out_get_index(self): + super_url = 'http://www.outsideonline.com/magazine/' + super_soup = self.index_to_soup(super_url) + div = super_soup.find(attrs={'class':'masonry-box width-four'}) + issue = div.findAll(name='article')[1] + super_a = issue.find('a', href=True) + return super_a.get('href') + + + # To parse artice toc + def parse_index(self): + parse_soup = self.index_to_soup(self.out_get_index()) + + feeds = [] + feed_title = 'Articles' + + articles = [] + self.log('Found section:', feed_title) + div = parse_soup.find(attrs={'class':'print clearfix'}) + for art in div.findAll(name='p'): + art_info = art.find(name = 'a') + if art_info is None: + continue + art_title = self.tag_to_string(art_info) + url = art_info.get('href') + '?page=all' + self.log.info('\tFound article:', art_title, 'at', url) + article = {'title':art_title, 'url':url, 'date':''} + #au = art.find(attrs={'class':'articleAuthors'}) + #if au is not None: + #article['author'] = self.tag_to_string(au) + #desc = art.find(attrs={'class':'hover_text'}) + #if desc is not None: + #desc = self.tag_to_string(desc) + #if 'author' in article: + #desc = ' by ' + article['author'] + ' ' +desc + #article['description'] = desc + articles.append(article) + if articles: + feeds.append((feed_title, articles)) + + return feeds + diff --git a/recipes/oxford_mail.recipe b/recipes/oxford_mail.recipe new file mode 100644 index 0000000000..3096b867f4 --- /dev/null +++ b/recipes/oxford_mail.recipe @@ -0,0 +1,22 @@ +from calibre.web.feeds.news import BasicNewsRecipe + +class HindustanTimes(BasicNewsRecipe): + title = u'Oxford Mail' + language = 'en_GB' + __author__ = 'Krittika Goyal' + oldest_article = 1 #days + max_articles_per_feed = 25 + #encoding = 'cp1252' + use_embedded_content = False + + no_stylesheets = True + auto_cleanup = True + + + feeds = [ +('News', + 'http://www.oxfordmail.co.uk/news/rss/'), +('Sports', + 'http://www.oxfordmail.co.uk/sport/rss/'), +] + diff --git a/recipes/pajama.recipe b/recipes/pajama.recipe index 8c5ba74317..9b474b6e65 100644 --- a/recipes/pajama.recipe +++ b/recipes/pajama.recipe @@ -1,27 +1,27 @@ from calibre.web.feeds.news import BasicNewsRecipe -from calibre.ebooks.BeautifulSoup import BeautifulSoup class PajamasMedia(BasicNewsRecipe): title = u'Pajamas Media' description = u'Provides exclusive news and opinion for forty countries.' language = 'en' __author__ = 'Krittika Goyal' - oldest_article = 1 #days + oldest_article = 2 #days max_articles_per_feed = 25 recursions = 1 match_regexps = [r'http://pajamasmedia.com/blog/.*/2/$'] #encoding = 'latin1' remove_stylesheets = True - #remove_tags_before = dict(name='h1', attrs={'class':'heading'}) - remove_tags_after = dict(name='div', attrs={'class':'paged-nav'}) - remove_tags = [ - dict(name='iframe'), - dict(name='div', attrs={'class':['pages']}), - #dict(name='div', attrs={'id':['bookmark']}), - #dict(name='span', attrs={'class':['related_link', 'slideshowcontrols']}), - #dict(name='ul', attrs={'class':'articleTools'}), - ] + auto_cleanup = True + ##remove_tags_before = dict(name='h1', attrs={'class':'heading'}) + #remove_tags_after = dict(name='div', attrs={'class':'paged-nav'}) + #remove_tags = [ + #dict(name='iframe'), + #dict(name='div', attrs={'class':['pages']}), + ##dict(name='div', attrs={'id':['bookmark']}), + ##dict(name='span', attrs={'class':['related_link', 'slideshowcontrols']}), + ##dict(name='ul', attrs={'class':'articleTools'}), + #] feeds = [ ('pajamas Media', @@ -29,20 +29,20 @@ class PajamasMedia(BasicNewsRecipe): ] - def preprocess_html(self, soup): - story = soup.find(name='div', attrs={'id':'innerpage-content'}) - #td = heading.findParent(name='td') - #td.extract() + #def preprocess_html(self, soup): + #story = soup.find(name='div', attrs={'id':'innerpage-content'}) + ##td = heading.findParent(name='td') + ##td.extract() - soup = BeautifulSoup('t') - body = soup.find(name='body') - body.insert(0, story) - return soup + #soup = BeautifulSoup('t') + #body = soup.find(name='body') + #body.insert(0, story) + #return soup - def postprocess_html(self, soup, first): - if not first: - h = soup.find(attrs={'class':'innerpage-header'}) - if h: h.extract() - auth = soup.find(attrs={'class':'author'}) - if auth: auth.extract() - return soup + #def postprocess_html(self, soup, first): + #if not first: + #h = soup.find(attrs={'class':'innerpage-header'}) + #if h: h.extract() + #auth = soup.find(attrs={'class':'author'}) + #if auth: auth.extract() + #return soup diff --git a/recipes/phillosophy_now.recipe b/recipes/phillosophy_now.recipe index 7c12832c70..748a81ade1 100644 --- a/recipes/phillosophy_now.recipe +++ b/recipes/phillosophy_now.recipe @@ -6,7 +6,6 @@ class PhilosophyNow(BasicNewsRecipe): title = 'Philosophy Now' __author__ = 'Rick Shang' - description = '''Philosophy Now is a lively magazine for everyone interested in ideas. It isn't afraid to tackle all the major questions of life, the universe and everything. Published every two months, it tries to @@ -27,7 +26,7 @@ class PhilosophyNow(BasicNewsRecipe): def get_browser(self): br = BasicNewsRecipe.get_browser() br.open('https://philosophynow.org/auth/login') - br.select_form(nr = 1) + br.select_form(name="loginForm") br['username'] = self.username br['password'] = self.password br.submit() @@ -50,19 +49,20 @@ class PhilosophyNow(BasicNewsRecipe): #Go to the main body current_issue_url = 'http://philosophynow.org/issues/' + issuenum soup = self.index_to_soup(current_issue_url) - div = soup.find ('div', attrs={'class':'articlesColumn'}) + div = soup.find ('div', attrs={'class':'contentsColumn'}) feeds = OrderedDict() - for post in div.findAll('h3'): + + for post in div.findAll('h1'): articles = [] a=post.find('a',href=True) if a is not None: url="http://philosophynow.org" + a['href'] title=self.tag_to_string(a).strip() - s=post.findPrevious('h4') + s=post.findPrevious('h3') section_title = self.tag_to_string(s).strip() - d=post.findNext('p') + d=post.findNext('h2') desc = self.tag_to_string(d).strip() articles.append({'title':title, 'url':url, 'description':desc, 'date':''}) @@ -73,3 +73,5 @@ class PhilosophyNow(BasicNewsRecipe): ans = [(key, val) for key, val in feeds.iteritems()] return ans + def cleanup(self): + self.browser.open('http://philosophynow.org/auth/logout') diff --git a/recipes/poradnia_pwn.recipe b/recipes/poradnia_pwn.recipe new file mode 100644 index 0000000000..b3e2825618 --- /dev/null +++ b/recipes/poradnia_pwn.recipe @@ -0,0 +1,63 @@ +# vim:fileencoding=UTF-8:ts=4:sw=4:sta:et:sts=4:fdm=marker:ai +from calibre.web.feeds.news import BasicNewsRecipe +class PoradniaPWN(BasicNewsRecipe): + title = u'Poradnia Językowa PWN' + __author__ = 'fenuks' + description = u'Internetowa poradnia językowa Wydawnictwa Naukowego PWN. Poradnię prowadzi Redaktor Naczelny Słowników Języka Polskiego, prof. Mirosław Bańko. Pomagają mu eksperci - znani polscy językoznawcy. Współpracuje z nami m.in. prof. Jerzy Bralczyk oraz dr Jan Grzenia.' + category = 'language' + language = 'pl' + #cover_url = '' + oldest_article = 14 + max_articles_per_feed = 100000 + INDEX = "http://poradnia.pwn.pl/" + no_stylesheets = True + remove_attributes = ['style'] + remove_javascript = True + use_embedded_content = False + #preprocess_regexps = [(re.compile('', re.IGNORECASE), lambda m: '
'), (re.compile('', re.IGNORECASE), lambda m: '
')] + keep_only_tags = [dict(name="div", attrs={"class":"searchhi"})] + feeds = [(u'Poradnia', u'http://rss.pwn.pl/poradnia.rss')] + + '''def find_articles(self, url): + articles = [] + soup=self.index_to_soup(url) + counter = int(soup.find(name='p', attrs={'class':'count'}).findAll('b')[-1].string) + counter = 500 + pos = 0 + next = url + while next: + soup=self.index_to_soup(next) + tag=soup.find(id="listapytan") + art=tag.findAll(name='li') + for i in art: + if i.h4: + title=i.h4.a.string + url=self.INDEX+i.h4.a['href'] + #date=soup.find(id='footer').ul.li.string[41:-1] + articles.append({'title' : title, + 'url' : url, + 'date' : '', + 'description' : '' + }) + pos += 10 + if not pos >=counter: + next = 'http://poradnia.pwn.pl/lista.php?kat=18&od=' + str(pos) + print u'Tworzenie listy artykułów dla', next + else: + next = None + print articles + return articles + + def parse_index(self): + feeds = [] + feeds.append((u"Poradnia", self.find_articles('http://poradnia.pwn.pl/lista.php'))) + + return feeds''' + + def preprocess_html(self, soup): + for i in soup.findAll(name=['ul', 'li']): + i.name="div" + for z in soup.findAll(name='a'): + if not z['href'].startswith('http'): + z['href'] = 'http://poradnia.pwn.pl/' + z['href'] + return soup diff --git a/recipes/schattenblick.recipe b/recipes/schattenblick.recipe new file mode 100644 index 0000000000..d02ec1d272 --- /dev/null +++ b/recipes/schattenblick.recipe @@ -0,0 +1,13 @@ +from calibre.web.feeds.news import BasicNewsRecipe + +class AdvancedUserRecipe1345802300(BasicNewsRecipe): + title = u'Online-Zeitung Schattenblick' + language = 'de' + __author__ = 'ThB' + publisher = u'MA-Verlag' + category = u'Nachrichten' + oldest_article = 7 + max_articles_per_feed = 100 + cover_url = 'http://www.schattenblick.de/mobi/rss/cover.jpg' + feeds = [(u'Schattenblick Tagesausgabe', u'http://www.schattenblick.de/mobi/rss/rss.xml')] + diff --git a/recipes/sivil_dusunce.recipe b/recipes/sivil_dusunce.recipe index 66bb895f0f..3ae665771a 100644 --- a/recipes/sivil_dusunce.recipe +++ b/recipes/sivil_dusunce.recipe @@ -1,12 +1,13 @@ -from calibre.web.feeds.news import BasicNewsRecipe +# -*- coding: utf-8 -*- -class BasicUserRecipe1324913680(BasicNewsRecipe): +from calibre.web.feeds.news import BasicNewsRecipe +class AdvancedUserRecipe1355341662(BasicNewsRecipe): title = u'Sivil Dusunce' language = 'tr' __author__ = 'asalet_r' oldest_article = 7 - max_articles_per_feed = 20 + max_articles_per_feed = 50 auto_cleanup = True - feeds = [(u'Sivil Dusunce', u'http://www.sivildusunce.com/feed/')] + feeds = [(u'Sivil Dusunce', u'http://www.sivildusunce.com/?t=rss&xml=1')] diff --git a/recipes/smith.recipe b/recipes/smith.recipe index 3d6a95c494..cd0c94ab35 100644 --- a/recipes/smith.recipe +++ b/recipes/smith.recipe @@ -48,10 +48,14 @@ class Smithsonian(BasicNewsRecipe): link=post.find('a',href=True) url=link['href']+'?c=y&story=fullstory' if subsection is not None: - subsection_title = self.tag_to_string(subsection) + subsection_title = self.tag_to_string(subsection).strip() prefix = (subsection_title+': ') description=self.tag_to_string(post('p', limit=2)[1]).strip() else: + if post.find('img') is not None: + subsection_title = self.tag_to_string(post.findPrevious('div', attrs={'class':'departments plainModule'}).find('p', attrs={'class':'article-cat'})).strip() + prefix = (subsection_title+': ') + description=self.tag_to_string(post.find('p')).strip() desc=re.sub('\sBy\s.*', '', description, re.DOTALL) author=re.sub('.*By\s', '', description, re.DOTALL) @@ -64,4 +68,3 @@ class Smithsonian(BasicNewsRecipe): feeds[section_title] += articles ans = [(key, val) for key, val in feeds.iteritems()] return ans - diff --git a/recipes/spectator_magazine.recipe b/recipes/spectator_magazine.recipe new file mode 100644 index 0000000000..eb61a8babd --- /dev/null +++ b/recipes/spectator_magazine.recipe @@ -0,0 +1,60 @@ +from calibre.web.feeds.recipes import BasicNewsRecipe + +class NYTimes(BasicNewsRecipe): + + title = 'Spectator Magazine' + __author__ = 'Krittika Goyal' + description = 'Magazine' + timefmt = ' [%d %b, %Y]' + needs_subscription = False + language = 'en' + + no_stylesheets = True + #auto_cleanup = True + #auto_cleanup_keep = '//div[@class="thumbnail"]' + + keep_only_tags = dict(name='div', attrs={'id':'content'}) + remove_tags = [ + dict(name='div', attrs={'id':['disqus_thread']}), + ##dict(name='div', attrs={'id':['qrformdiv', 'inSection', 'alpha-inner']}), + ##dict(name='form', attrs={'onsubmit':''}), + #dict(name='section', attrs={'id':['article-quote', 'article-navigation']}), + ] + + #TO GET ARTICLE TOC + def spec_get_index(self): + return self.index_to_soup('http://www.spectator.co.uk/') + + # To parse artice toc + def parse_index(self): + parse_soup = self.index_to_soup('http://www.spectator.co.uk/') + + feeds = [] + feed_title = 'Spectator Magazine Articles' + + articles = [] + self.log('Found section:', feed_title) + div = parse_soup.find(attrs={'class':'one-col-tax-widget magazine-list columns-1 post-8 taxonomy-category full-width widget section-widget icit-taxonomical-listings'}) + for art in div.findAll(name='h2'): + art_info = art.find(name = 'a') + if art_info is None: + continue + art_title = self.tag_to_string(art_info) + url = art_info.get('href') + self.log.info('\tFound article:', art_title, 'at', url) + article = {'title':art_title, 'url':url, 'date':''} + #au = art.find(attrs={'class':'articleAuthors'}) + #if au is not None: + #article['author'] = self.tag_to_string(au) + #desc = art.find(attrs={'class':'hover_text'}) + #if desc is not None: + #desc = self.tag_to_string(desc) + #if 'author' in article: + #desc = ' by ' + article['author'] + ' ' +desc + #article['description'] = desc + articles.append(article) + if articles: + feeds.append((feed_title, articles)) + + return feeds + diff --git a/recipes/sueddeutsche_mobil.recipe b/recipes/sueddeutsche_mobil.recipe index d1b08cbcba..7b1a9c4d8d 100644 --- a/recipes/sueddeutsche_mobil.recipe +++ b/recipes/sueddeutsche_mobil.recipe @@ -1,13 +1,16 @@ -# vim:fileencoding=UTF-8:ts=4:sw=4:sta:et:sts=4:ai __license__ = 'GPL v3' -__copyright__ = '2012, Andreas Zeiser ' +__copyright__ = '2012, 2013 Andreas Zeiser ' ''' szmobil.sueddeutsche.de/ ''' +# History +# 2013.01.09 Fixed bugs in article titles containing "strong" and +# other small changes +# 2012.08.04 Initial release from calibre import strftime from calibre.web.feeds.recipes import BasicNewsRecipe -import re +import re class SZmobil(BasicNewsRecipe): title = u'Süddeutsche Zeitung mobil' @@ -26,6 +29,8 @@ class SZmobil(BasicNewsRecipe): delay = 1 cover_source = 'http://www.sueddeutsche.de/verlag' + # if you want to get rid of the date on the title page use + # timefmt = '' timefmt = ' [%a, %d %b, %Y]' root_url ='http://szmobil.sueddeutsche.de/' @@ -50,7 +55,7 @@ class SZmobil(BasicNewsRecipe): return browser - def parse_index(self): + def parse_index(self): # find all sections src = self.index_to_soup('http://szmobil.sueddeutsche.de') feeds = [] @@ -76,10 +81,10 @@ class SZmobil(BasicNewsRecipe): # first check if link is a special article in section "Meinungsseite" if itt.find('strong')!= None: article_name = itt.strong.string - article_shorttitle = itt.contents[1] + if len(itt.contents)>1: + shorttitles[article_id] = itt.contents[1] articles.append( (article_name, article_url, article_id) ) - shorttitles[article_id] = article_shorttitle continue @@ -89,7 +94,7 @@ class SZmobil(BasicNewsRecipe): else: article_name = itt.string - if (article_name[0:10] == " mehr"): + if (article_name.find(" mehr") == 0): # just another link ("mehr") to an article continue @@ -102,7 +107,9 @@ class SZmobil(BasicNewsRecipe): for article_name, article_url, article_id in articles: url = self.root_url + article_url title = article_name - pubdate = strftime('%a, %d %b') + # if you want to get rid of date for each article use + # pubdate = strftime('') + pubdate = strftime('[%a, %d %b]') description = '' if shorttitles.has_key(article_id): description = shorttitles[article_id] @@ -115,3 +122,4 @@ class SZmobil(BasicNewsRecipe): return all_articles + diff --git a/recipes/tidbits.recipe b/recipes/tidbits.recipe index 702c65e9e4..a053dfb91f 100644 --- a/recipes/tidbits.recipe +++ b/recipes/tidbits.recipe @@ -16,8 +16,9 @@ class TidBITS(BasicNewsRecipe): oldest_article = 2 max_articles_per_feed = 100 no_stylesheets = True + #auto_cleanup = True encoding = 'utf-8' - use_embedded_content = True + use_embedded_content = False language = 'en' remove_empty_feeds = True masthead_url = 'http://db.tidbits.com/images/tblogo9.gif' @@ -30,9 +31,11 @@ class TidBITS(BasicNewsRecipe): , 'language' : language } - remove_attributes = ['width','height'] - remove_tags = [dict(name='small')] - remove_tags_after = dict(name='small') + #remove_attributes = ['width','height'] + #remove_tags = [dict(name='small')] + #remove_tags_after = dict(name='small') + keep_only_tags = [dict(name='div', attrs={'id':'center_ajax_sub'})] + remove_tags = [dict(name='div', attrs={'id':'social-media'})] feeds = [ (u'Business Apps' , u'http://db.tidbits.com/feeds/business.rss' ) diff --git a/recipes/titanic_de.recipe b/recipes/titanic_de.recipe new file mode 100644 index 0000000000..edc9580602 --- /dev/null +++ b/recipes/titanic_de.recipe @@ -0,0 +1,20 @@ +from calibre.web.feeds.news import BasicNewsRecipe + +class Titanic(BasicNewsRecipe): + title = u'Titanic' + language = 'de' + __author__ = 'Krittika Goyal' + oldest_article = 14 #days + max_articles_per_feed = 25 + #encoding = 'cp1252' + use_embedded_content = False + + no_stylesheets = True + auto_cleanup = True + + + feeds = [ +('News', + 'http://www.titanic-magazin.de/ich.war.bei.der.waffen.rss'), +] + diff --git a/recipes/todays_zaman.recipe b/recipes/todays_zaman.recipe index 5f3b85131a..13d82e31fb 100644 --- a/recipes/todays_zaman.recipe +++ b/recipes/todays_zaman.recipe @@ -26,28 +26,33 @@ class TodaysZaman_en(BasicNewsRecipe): # remove_attributes = ['width','height'] feeds = [ - ( u'Home', u'http://www.todayszaman.com/rss?sectionId=0'), - ( u'News', u'http://www.todayszaman.com/rss?sectionId=100'), - ( u'Business', u'http://www.todayszaman.com/rss?sectionId=105'), - ( u'Interviews', u'http://www.todayszaman.com/rss?sectionId=8'), - ( u'Columnists', u'http://www.todayszaman.com/rss?sectionId=6'), - ( u'Op-Ed', u'http://www.todayszaman.com/rss?sectionId=109'), - ( u'Arts & Culture', u'http://www.todayszaman.com/rss?sectionId=110'), - ( u'Expat Zone', u'http://www.todayszaman.com/rss?sectionId=132'), - ( u'Sports', u'http://www.todayszaman.com/rss?sectionId=5'), - ( u'Features', u'http://www.todayszaman.com/rss?sectionId=116'), - ( u'Travel', u'http://www.todayszaman.com/rss?sectionId=117'), - ( u'Leisure', u'http://www.todayszaman.com/rss?sectionId=118'), - ( u'Weird But True', u'http://www.todayszaman.com/rss?sectionId=134'), - ( u'Life', u'http://www.todayszaman.com/rss?sectionId=133'), - ( u'Health', u'http://www.todayszaman.com/rss?sectionId=126'), - ( u'Press Review', u'http://www.todayszaman.com/rss?sectionId=130'), - ( u'Todays think tanks', u'http://www.todayszaman.com/rss?sectionId=159'), - - ] + ( u'Home', u'http://www.todayszaman.com/0.rss'), + ( u'Sports', u'http://www.todayszaman.com/5.rss'), + ( u'Columnists', u'http://www.todayszaman.com/6.rss'), + ( u'Interviews', u'http://www.todayszaman.com/9.rss'), + ( u'News', u'http://www.todayszaman.com/100.rss'), + ( u'National', u'http://www.todayszaman.com/101.rss'), + ( u'Diplomacy', u'http://www.todayszaman.com/102.rss'), + ( u'World', u'http://www.todayszaman.com/104.rss'), + ( u'Business', u'http://www.todayszaman.com/105.rss'), + ( u'Op-Ed', u'http://www.todayszaman.com/109.rss'), + ( u'Arts & Culture', u'http://www.todayszaman.com/110.rss'), + ( u'Features', u'http://www.todayszaman.com/116.rss'), + ( u'Travel', u'http://www.todayszaman.com/117.rss'), + ( u'Food', u'http://www.todayszaman.com/124.rss'), + ( u'Press Review', u'http://www.todayszaman.com/130.rss'), + ( u'Expat Zone', u'http://www.todayszaman.com/132.rss'), + ( u'Life', u'http://www.todayszaman.com/133.rss'), + ( u'Think Tanks', u'http://www.todayszaman.com/159.rss'), + ( u'Almanac', u'http://www.todayszaman.com/161.rss'), + ( u'Health', u'http://www.todayszaman.com/162.rss'), + ( u'Fashion & Beauty', u'http://www.todayszaman.com/163.rss'), + ( u'Science & Technology', u'http://www.todayszaman.com/349.rss'), + ] #def preprocess_html(self, soup): # return self.adeify_images(soup) #def print_version(self, url): #there is a probem caused by table format #return url.replace('http://www.todayszaman.com/newsDetail_getNewsById.action?load=detay&', 'http://www.todayszaman.com/newsDetail_openPrintPage.action?') + diff --git a/recipes/tvp_info.recipe b/recipes/tvp_info.recipe new file mode 100644 index 0000000000..64528d4194 --- /dev/null +++ b/recipes/tvp_info.recipe @@ -0,0 +1,20 @@ +# vim:fileencoding=UTF-8:ts=4:sw=4:sta:et:sts=4:fdm=marker:ai +from calibre.web.feeds.news import BasicNewsRecipe +class TVPINFO(BasicNewsRecipe): + title = u'TVP.INFO' + __author__ = 'fenuks' + description = u'Serwis informacyjny TVP.INFO' + category = 'news' + language = 'pl' + cover_url = 'http://s.v3.tvp.pl/files/tvp-info/gfx/logo.png' + oldest_article = 7 + max_articles_per_feed = 100 + no_stylesheets = True + remove_empty_feeds = True + remove_javascript = True + use_embedded_content = False + ignore_duplicate_articles = {'title', 'url'} + keep_only_tags = [dict(id='contentNews')] + remove_tags = [dict(attrs={'class':['toolbox', 'modulBox read', 'modulBox social', 'videoPlayerBox']}), dict(id='belka')] + feeds = [(u'Wiadomo\u015bci', u'http://tvp.info/informacje?xslt=tvp-info/news/rss.xslt&src_id=191865'), + (u'\u015awiat', u'http://tvp.info/informacje/swiat?xslt=tvp-info/news/rss.xslt&src_id=191867'), (u'Biznes', u'http://tvp.info/informacje/biznes?xslt=tvp-info/news/rss.xslt&src_id=191868'), (u'Nauka', u'http://tvp.info/informacje/nauka?xslt=tvp-info/news/rss.xslt&src_id=191870'), (u'Kultura', u'http://tvp.info/informacje/kultura?xslt=tvp-info/news/rss.xslt&src_id=191869'), (u'Rozmaito\u015bci', u'http://tvp.info/informacje/rozmaitosci?xslt=tvp-info/news/rss.xslt&src_id=191872'), (u'Opinie', u'http://tvp.info/opinie?xslt=tvp-info/news/rss.xslt&src_id=191875'), (u'Komentarze', u'http://tvp.info/opinie/komentarze?xslt=tvp-info/news/rss.xslt&src_id=238200'), (u'Wywiady', u'http://tvp.info/opinie/wywiady?xslt=tvp-info/news/rss.xslt&src_id=236644')] diff --git a/recipes/ukraiyns_kii_tizhdien.recipe b/recipes/ukraiyns_kii_tizhdien.recipe new file mode 100644 index 0000000000..3064ebfe55 --- /dev/null +++ b/recipes/ukraiyns_kii_tizhdien.recipe @@ -0,0 +1,13 @@ +from calibre.web.feeds.news import BasicNewsRecipe + +class AdvancedUserRecipe1356283265(BasicNewsRecipe): + title = u'\u0423\u043a\u0440\u0430\u0457\u043d\u0441\u044c\u043a\u0438\u0439 \u0422\u0438\u0436\u0434\u0435\u043d\u044c' + __author__ = 'rpalyvoda' + oldest_article = 7 + max_articles_per_feed = 100 + language = 'uk' + cover_url = 'http://tyzhden.ua/Images/Style1/tyzhden.ua-logo2.gif' + masthead_url = 'http://tyzhden.ua/Images/Style1/tyzhden.ua-logo2.gif' + auto_cleanup = True + + feeds = [(u'\u041d\u043e\u0432\u0438\u043d\u0438', u'http://tyzhden.ua/RSS/News/'), (u'\u041e\u0440\u0438\u0433\u0456\u043d\u0430\u043b\u044c\u043d\u0456 \u043d\u043e\u0432\u0438\u043d\u0438', u'http://tyzhden.ua/RSS/News.Original/'), (u'\u041f\u0443\u0431\u043b\u0456\u043a\u0430\u0446\u0456\u0457', u'http://tyzhden.ua/RSS/Publications/')] diff --git a/recipes/zaufana_trzecia_strona.recipe b/recipes/zaufana_trzecia_strona.recipe new file mode 100644 index 0000000000..13e7d98cce --- /dev/null +++ b/recipes/zaufana_trzecia_strona.recipe @@ -0,0 +1,16 @@ +# vim:fileencoding=UTF-8:ts=4:sw=4:sta:et:sts=4:fdm=marker:ai +from calibre.web.feeds.news import BasicNewsRecipe +class ZTS(BasicNewsRecipe): + title = u'Zaufana Trzecia Strona' + __author__ = 'fenuks' + description = u'Niezależne źródło wiadomości o świecie bezpieczeństwa IT' + category = 'IT, security' + language = 'pl' + cover_url = 'http://www.zaufanatrzeciastrona.pl/wp-content/uploads/2012/08/z3s_h100.png' + oldest_article = 7 + max_articles_per_feed = 100 + no_stylesheets = True + remove_empty_feeds = True + keep_only_tags = [dict(name='div', attrs={'class':'post postcontent'})] + remove_tags = [dict(name='div', attrs={'class':'dolna-ramka'})] + feeds = [(u'Strona g\u0142\xf3wna', u'http://feeds.feedburner.com/ZaufanaTrzeciaStronaGlowna'), (u'Drobiazgi', u'http://feeds.feedburner.com/ZaufanaTrzeciaStronaDrobiazgi')] diff --git a/recipes/zaxid_net.recipe b/recipes/zaxid_net.recipe new file mode 100644 index 0000000000..c1dde37320 --- /dev/null +++ b/recipes/zaxid_net.recipe @@ -0,0 +1,13 @@ +from calibre.web.feeds.news import BasicNewsRecipe + +class AdvancedUserRecipe1356281741(BasicNewsRecipe): + title = u'Zaxid.net' + __author__ = 'rpalyvoda' + oldest_article = 7 + max_articles_per_feed = 100 + language = 'uk' + cover_url = 'http://upload.wikimedia.org/wikipedia/uk/b/bc/Zaxid-net.jpg' + masthead_url = 'http://upload.wikimedia.org/wikipedia/uk/b/bc/Zaxid-net.jpg' + auto_cleanup = True + + feeds = [(u'\u0422\u043e\u043f \u043d\u043e\u0432\u0438\u043d\u0438', u'http://feeds.feedburner.com/zaxid/topNews'), (u'\u0421\u0442\u0440\u0456\u0447\u043a\u0430 \u043d\u043e\u0432\u0438\u043d', u'http://feeds.feedburner.com/zaxid/AllNews'), (u'\u041d\u043e\u0432\u0438\u043d\u0438 \u041b\u044c\u0432\u043e\u0432\u0430', u'http://feeds.feedburner.com/zaxid/Lviv'), (u'\u041d\u043e\u0432\u0438\u043d\u0438 \u0423\u043a\u0440\u0430\u0457\u043d\u0438', u'http://feeds.feedburner.com/zaxid/Ukraine'), (u'\u041d\u043e\u0432\u0438\u043d\u0438 \u0441\u0432\u0456\u0442\u0443', u'http://feeds.feedburner.com/zaxid/World'), (u'\u041d\u043e\u0432\u0438\u043d\u0438 - \u0420\u0430\u0434\u0456\u043e 24', u'\u0420\u0430\u0434\u0456\u043e 24'), (u'\u0411\u043b\u043e\u0433\u0438', u'http://feeds.feedburner.com/zaxid/Blogs'), (u"\u041f\u0443\u0431\u043b\u0456\u043a\u0430\u0446\u0456\u0457 - \u0406\u043d\u0442\u0435\u0440\u0432'\u044e", u'http://feeds.feedburner.com/zaxid/Interview'), (u'\u041f\u0443\u0431\u043b\u0456\u043a\u0430\u0446\u0456\u0457 - \u0421\u0442\u0430\u0442\u0442\u0456', u'http://feeds.feedburner.com/zaxid/Articles'), (u'\u0410\u0444\u0456\u0448\u0430', u'http://zaxid.net/rss/subcategory/140.xml'), (u'\u0413\u0430\u043b\u0438\u0447\u0438\u043d\u0430', u'http://feeds.feedburner.com/zaxid/Galicia'), (u'\u041a\u0443\u043b\u044c\u0442\u0443\u0440\u0430.NET', u'http://feeds.feedburner.com/zaxid/KulturaNET'), (u"\u043d\u0435\u0412\u0456\u0434\u043e\u043c\u0456 \u043b\u044c\u0432\u0456\u0432'\u044f\u043d\u0438", u'http://feeds.feedburner.com/zaxid/UnknownLviv'), (u'\u041b\u0435\u043e\u043f\u043e\u043b\u0456\u0441 MULTIPLEX', u'http://feeds.feedburner.com/zaxid/LeopolisMULTIPLEX'), (u'\u0411\u0438\u0442\u0432\u0430 \u0437\u0430 \u043c\u043e\u0432\u0443', u'http://zaxid.net/rss/subcategory/138.xml'), (u'\u0422\u0440\u0430\u043d\u0441\u043f\u043e\u0440\u0442\u043d\u0430 \u0441\u0445\u0435\u043c\u0430 \u041b\u044c\u0432\u043e\u0432\u0430', u'http://zaxid.net/rss/subcategory/132.xml'), (u'\u0414\u0435\u043c\u0456\u0444\u043e\u043b\u043e\u0433\u0456\u0437\u0430\u0446\u0456\u044f', u'http://zaxid.net/rss/subcategory/130.xml'), (u"\u041c\u0438 \u043f\u0430\u043c'\u044f\u0442\u0430\u0454\u043c\u043e", u'http://feeds.feedburner.com/zaxid/WeRemember'), (u'20 \u0440\u043e\u043a\u0456\u0432 \u041d\u0435\u0437\u0430\u043b\u0435\u0436\u043d\u043e\u0441\u0442\u0456', u'http://zaxid.net/rss/subcategory/129.xml'), (u'\u041f\u0440\u0430\u0432\u043e \u043d\u0430 \u0434\u0438\u0442\u0438\u043d\u0441\u0442\u0432\u043e', u'http://feeds.feedburner.com/zaxid/Childhood'), (u'\u0410\u043d\u043e\u043d\u0441\u0438', u'http://feeds.feedburner.com/zaxid/Announcements')] diff --git a/resources/compiled_coffeescript.zip b/resources/compiled_coffeescript.zip index a2b20c7a69..8186cccaa6 100644 Binary files a/resources/compiled_coffeescript.zip and b/resources/compiled_coffeescript.zip differ diff --git a/resources/content_server/browse/browse.css b/resources/content_server/browse/browse.css index 2c50853ae9..b32c470803 100644 --- a/resources/content_server/browse/browse.css +++ b/resources/content_server/browse/browse.css @@ -81,6 +81,7 @@ body { background-color: #39a9cf; -moz-border-radius: 5px; -webkit-border-radius: 5px; + border-radius: 5px; text-shadow: #27211b 1px 1px 1px; -moz-box-shadow: 5px 5px 5px #222; -webkit-box-shadow: 5px 5px 5px #222; diff --git a/resources/quick_start.epub b/resources/quick_start.epub index a3f74213a6..23728547cd 100644 Binary files a/resources/quick_start.epub and b/resources/quick_start.epub differ diff --git a/session.vim b/session.vim index 9bcbbe7800..54c269978f 100644 --- a/session.vim +++ b/session.vim @@ -12,6 +12,7 @@ let g:syntastic_cpp_include_dirs = [ \'/usr/include/fontconfig', \'src/qtcurve/common', 'src/qtcurve', \'src/unrar', + \'src/qt-harfbuzz/src', \'/usr/include/ImageMagick', \] let g:syntastic_c_include_dirs = g:syntastic_cpp_include_dirs diff --git a/setup/build_environment.py b/setup/build_environment.py index afb7c61920..e192d2627e 100644 --- a/setup/build_environment.py +++ b/setup/build_environment.py @@ -6,12 +6,13 @@ __license__ = 'GPL v3' __copyright__ = '2009, Kovid Goyal ' __docformat__ = 'restructuredtext en' -import os, socket, struct, subprocess, sys, glob +import os, socket, struct, subprocess, glob from distutils.spawn import find_executable from PyQt4 import pyqtconfig from setup import isosx, iswindows, islinux, is64bit +is64bit OSX_SDK = '/Developer/SDKs/MacOSX10.5.sdk' @@ -81,6 +82,7 @@ def consolidate(envvar, default): pyqt = pyqtconfig.Configuration() qt_inc = pyqt.qt_inc_dir +qt_private_inc = [] qt_lib = pyqt.qt_lib_dir ft_lib_dirs = [] ft_libs = [] @@ -140,6 +142,8 @@ elif isosx: png_libs = ['png12'] ft_libs = ['freetype'] ft_inc_dirs = ['/sw/include/freetype2'] + bq = glob.glob('/sw/build/qt-*/include')[-1] + qt_private_inc = ['%s/%s'%(bq, m) for m in ('QtGui', 'QtCore')] else: # Include directories png_inc_dirs = pkgconfig_include_dirs('libpng', 'PNG_INC_DIR', diff --git a/setup/check.py b/setup/check.py index 538f33289c..d00c8b2786 100644 --- a/setup/check.py +++ b/setup/check.py @@ -102,7 +102,8 @@ class Check(Command): errors = True if errors: cPickle.dump(cache, open(self.CACHE, 'wb'), -1) - subprocess.call(['gvim', '-f', f]) + subprocess.call(['gvim', '-S', + self.j(self.SRC, '../session.vim'), '-f', f]) raise SystemExit(1) cache[f] = mtime for x in builtins: diff --git a/setup/extensions.py b/setup/extensions.py index c167916afb..a2ed890e71 100644 --- a/setup/extensions.py +++ b/setup/extensions.py @@ -18,7 +18,7 @@ from setup.build_environment import (chmlib_inc_dirs, msvc, MT, win_inc, win_lib, win_ddk, magick_inc_dirs, magick_lib_dirs, magick_libs, chmlib_lib_dirs, sqlite_inc_dirs, icu_inc_dirs, icu_lib_dirs, win_ddk_lib_dirs, ft_libs, ft_lib_dirs, ft_inc_dirs, - zlib_libs, zlib_lib_dirs, zlib_inc_dirs, is64bit) + zlib_libs, zlib_lib_dirs, zlib_inc_dirs, is64bit, qt_private_inc) MT isunix = islinux or isosx or isbsd @@ -183,6 +183,13 @@ extensions = [ sip_files = ['calibre/gui2/progress_indicator/QProgressIndicator.sip'] ), + Extension('qt_hack', + ['calibre/ebooks/pdf/render/qt_hack.cpp'], + inc_dirs = qt_private_inc + ['calibre/ebooks/pdf/render', 'qt-harfbuzz/src'], + headers = ['calibre/ebooks/pdf/render/qt_hack.h'], + sip_files = ['calibre/ebooks/pdf/render/qt_hack.sip'] + ), + Extension('unrar', ['unrar/%s.cpp'%(x.partition('.')[0]) for x in ''' rar.o strlist.o strfn.o pathfn.o savepos.o smallfn.o global.o file.o @@ -545,6 +552,9 @@ class Build(Command): VERSION = 1.0.0 CONFIG += %s ''')%(ext.name, ' '.join(ext.headers), ' '.join(ext.sources), archs) + if ext.inc_dirs: + idir = ' '.join(ext.inc_dirs) + pro += 'INCLUDEPATH = %s\n'%idir pro = pro.replace('\\', '\\\\') open(ext.name+'.pro', 'wb').write(pro) qmc = [QMAKE, '-o', 'Makefile'] diff --git a/setup/installer/windows/freeze.py b/setup/installer/windows/freeze.py index 4008317341..c420e867d7 100644 --- a/setup/installer/windows/freeze.py +++ b/setup/installer/windows/freeze.py @@ -102,7 +102,8 @@ class Win32Freeze(Command, WixMixIn): repl_pat = re.compile( r'(?is).*?Microsoft\.VC\d+\.CRT.*?') - for dll in glob.glob(self.j(self.dll_dir, '*.dll')): + for dll in (glob.glob(self.j(self.dll_dir, '*.dll')) + + glob.glob(self.j(self.plugins_dir, '*.pyd'))): bn = self.b(dll) with open(dll, 'rb') as f: raw = f.read() @@ -598,6 +599,10 @@ class Win32Freeze(Command, WixMixIn): # from files 'unrar.pyd', 'wpd.pyd', 'podofo.pyd', 'progress_indicator.pyd', + # As per this https://bugs.launchpad.net/bugs/1087816 + # on some systems magick.pyd fails to load from memory + # on 64 bit + 'magick.pyd', }: self.add_to_zipfile(zf, pyd, x) os.remove(self.j(x, pyd)) diff --git a/setup/iso_639/ca.po b/setup/iso_639/ca.po index 31c366ebbd..8ca2f37de2 100644 --- a/setup/iso_639/ca.po +++ b/setup/iso_639/ca.po @@ -12,14 +12,14 @@ msgstr "" "Report-Msgid-Bugs-To: Debian iso-codes team \n" "POT-Creation-Date: 2011-11-25 14:01+0000\n" -"PO-Revision-Date: 2012-11-25 22:19+0000\n" +"PO-Revision-Date: 2012-12-31 12:50+0000\n" "Last-Translator: Ferran Rius \n" "Language-Team: Catalan \n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" -"X-Launchpad-Export-Date: 2012-11-26 04:39+0000\n" -"X-Generator: Launchpad (build 16293)\n" +"X-Launchpad-Export-Date: 2013-01-01 04:45+0000\n" +"X-Generator: Launchpad (build 16378)\n" "Language: ca\n" #. name for aaa @@ -1744,7 +1744,7 @@ msgstr "Asu (Nigèria)" #. name for aun msgid "One; Molmo" -msgstr "One; Molmo" +msgstr "Oneià; Molmo" #. name for auo msgid "Auyokawa" @@ -1964,7 +1964,7 @@ msgstr "Leyigha" #. name for ayk msgid "Akuku" -msgstr "Akuku" +msgstr "Okpe-Idesa-Akuku; Akuku" #. name for ayl msgid "Arabic; Libyan" @@ -2048,7 +2048,7 @@ msgstr "Atta; Faire" #. name for azz msgid "Nahuatl; Highland Puebla" -msgstr "Nahuatl; serra de Puebla" +msgstr "Nàhuatl; Puebla serra" #. name for baa msgid "Babatana" @@ -6156,7 +6156,7 @@ msgstr "Desano" #. name for deu msgid "German" -msgstr "Espanyol" +msgstr "Alemany" #. name for dev msgid "Domung" @@ -8052,7 +8052,7 @@ msgstr "Francès crioll; Guaiana" #. name for gct msgid "German; Colonia Tovar" -msgstr "Espanyol; Colonia Tovar" +msgstr "Alemany; Colonial Tovar" #. name for gda msgid "Lohar; Gade" @@ -9984,7 +9984,7 @@ msgstr "Indri" #. name for ids msgid "Idesa" -msgstr "Idesa" +msgstr "Okpe-Idesa-Akuku; Idesa" #. name for idt msgid "Idaté" @@ -10836,7 +10836,7 @@ msgstr "Jumleli" #. name for jmn msgid "Naga; Makuri" -msgstr "" +msgstr "Naga; Makuri" #. name for jmr msgid "Kamara" @@ -11624,7 +11624,7 @@ msgstr "Kurmukar" #. name for kfw msgid "Naga; Kharam" -msgstr "" +msgstr "Naga; Kharam" #. name for kfx msgid "Pahari; Kullu" @@ -11776,7 +11776,7 @@ msgstr "Kuturmi" #. name for khk msgid "Mongolian; Halh" -msgstr "" +msgstr "Mongol; Halh" #. name for khl msgid "Lusi" @@ -11932,7 +11932,7 @@ msgstr "Kiwai; Septentrional" #. name for kix msgid "Naga; Khiamniungan" -msgstr "" +msgstr "Naga; Khiamniungan" #. name for kiy msgid "Kirikiri" @@ -13400,7 +13400,7 @@ msgstr "Karo (Etiòpia)" #. name for kxi msgid "Murut; Keningau" -msgstr "" +msgstr "Keningau Murut" #. name for kxj msgid "Kulfa" @@ -14692,7 +14692,7 @@ msgstr "Lepki" #. name for lpn msgid "Naga; Long Phuri" -msgstr "" +msgstr "Naga; Long Phuri" #. name for lpo msgid "Lipo" @@ -15016,7 +15016,7 @@ msgstr "Litzlitz" #. name for lzn msgid "Naga; Leinong" -msgstr "" +msgstr "Naga; Leinong" #. name for lzz msgid "Laz" @@ -16456,7 +16456,7 @@ msgstr "Monimbo" #. name for mon msgid "Mongolian" -msgstr "mongol" +msgstr "Mongol" #. name for moo msgid "Monom" @@ -16560,15 +16560,15 @@ msgstr "Mindiri" #. name for mpo msgid "Miu" -msgstr "" +msgstr "Miu" #. name for mpp msgid "Migabac" -msgstr "" +msgstr "Migabac" #. name for mpq msgid "Matís" -msgstr "" +msgstr "Matis" #. name for mpr msgid "Vangunu" @@ -16580,51 +16580,51 @@ msgstr "" #. name for mpt msgid "Mian" -msgstr "" +msgstr "Mian" #. name for mpu msgid "Makuráp" -msgstr "" +msgstr "Makurap" #. name for mpv msgid "Mungkip" -msgstr "" +msgstr "Munkip" #. name for mpw msgid "Mapidian" -msgstr "" +msgstr "Mauaiana; Mapidian" #. name for mpx msgid "Misima-Paneati" -msgstr "" +msgstr "Misima-Paneati" #. name for mpy msgid "Mapia" -msgstr "" +msgstr "Mapia" #. name for mpz msgid "Mpi" -msgstr "" +msgstr "Mpi" #. name for mqa msgid "Maba (Indonesia)" -msgstr "" +msgstr "Maba (Indonèsia)" #. name for mqb msgid "Mbuko" -msgstr "" +msgstr "Mbuko" #. name for mqc msgid "Mangole" -msgstr "" +msgstr "Mangole" #. name for mqe msgid "Matepi" -msgstr "" +msgstr "Matepí" #. name for mqf msgid "Momuna" -msgstr "" +msgstr "Momuna" #. name for mqg msgid "Malay; Kota Bangun Kutai" @@ -16636,11 +16636,11 @@ msgstr "Mixtec; Tlazoyaltepec" #. name for mqi msgid "Mariri" -msgstr "" +msgstr "Mariri" #. name for mqj msgid "Mamasa" -msgstr "" +msgstr "Mamasa" #. name for mqk msgid "Manobo; Rajah Kabunsuwan" @@ -16648,31 +16648,31 @@ msgstr "Manobo; Rajah Kabunsuwan" #. name for mql msgid "Mbelime" -msgstr "" +msgstr "Mbelime" #. name for mqm msgid "Marquesan; South" -msgstr "" +msgstr "Marquesà: Meridional" #. name for mqn msgid "Moronene" -msgstr "" +msgstr "Moronene" #. name for mqo msgid "Modole" -msgstr "" +msgstr "Modole" #. name for mqp msgid "Manipa" -msgstr "" +msgstr "Manipa" #. name for mqq msgid "Minokok" -msgstr "" +msgstr "Minokok" #. name for mqr msgid "Mander" -msgstr "" +msgstr "Mander" #. name for mqs msgid "Makian; West" @@ -16680,7 +16680,7 @@ msgstr "Makià; Occidental" #. name for mqt msgid "Mok" -msgstr "" +msgstr "Mok" #. name for mqu msgid "Mandari" @@ -16688,35 +16688,35 @@ msgstr "Mandari" #. name for mqv msgid "Mosimo" -msgstr "" +msgstr "Mosimo" #. name for mqw msgid "Murupi" -msgstr "" +msgstr "Murupi" #. name for mqx msgid "Mamuju" -msgstr "" +msgstr "Mamuju" #. name for mqy msgid "Manggarai" -msgstr "" +msgstr "Mangarai" #. name for mqz msgid "Malasanga" -msgstr "" +msgstr "Malasanga" #. name for mra msgid "Mlabri" -msgstr "" +msgstr "Mlabri" #. name for mrb msgid "Marino" -msgstr "" +msgstr "Marino" #. name for mrc msgid "Maricopa" -msgstr "" +msgstr "Maricopa" #. name for mrd msgid "Magar; Western" @@ -16732,7 +16732,7 @@ msgstr "" #. name for mrg msgid "Mising" -msgstr "" +msgstr "Miri" #. name for mrh msgid "Chin; Mara" @@ -16740,7 +16740,7 @@ msgstr "Chin; Mara" #. name for mri msgid "Maori" -msgstr "" +msgstr "Maori" #. name for mrj msgid "Mari; Western" @@ -16752,11 +16752,11 @@ msgstr "" #. name for mrl msgid "Mortlockese" -msgstr "" +msgstr "Mortlockès" #. name for mrm msgid "Merlav" -msgstr "" +msgstr "Merlav" #. name for mrn msgid "Cheke Holo" @@ -16764,15 +16764,15 @@ msgstr "" #. name for mro msgid "Mru" -msgstr "" +msgstr "Mru" #. name for mrp msgid "Morouas" -msgstr "" +msgstr "Morouas" #. name for mrq msgid "Marquesan; North" -msgstr "" +msgstr "Marquesà; Septentrional" #. name for mrr msgid "Maria (India)" @@ -16780,7 +16780,7 @@ msgstr "Maria; Índia" #. name for mrs msgid "Maragus" -msgstr "" +msgstr "Maragus" #. name for mrt msgid "Marghi Central" @@ -16792,23 +16792,23 @@ msgstr "Mono (Camerun)" #. name for mrv msgid "Mangareva" -msgstr "" +msgstr "Mangareva" #. name for mrw msgid "Maranao" -msgstr "" +msgstr "Maranao" #. name for mrx msgid "Maremgi" -msgstr "" +msgstr "Maremgi" #. name for mry msgid "Mandaya" -msgstr "" +msgstr "Mandaya" #. name for mrz msgid "Marind" -msgstr "" +msgstr "Marind" #. name for msa msgid "Malay (macrolanguage)" @@ -16816,7 +16816,7 @@ msgstr "Malai (macrollengua)" #. name for msb msgid "Masbatenyo" -msgstr "" +msgstr "Masbatenyo" #. name for msc msgid "Maninka; Sankaran" @@ -16828,15 +16828,15 @@ msgstr "Llenguatge de signes maia iucatec" #. name for mse msgid "Musey" -msgstr "" +msgstr "Musei" #. name for msf msgid "Mekwei" -msgstr "" +msgstr "Mekwei" #. name for msg msgid "Moraid" -msgstr "" +msgstr "Moraid" #. name for msh msgid "Malagasy; Masikoro" @@ -16848,15 +16848,15 @@ msgstr "Malai; Sabah" #. name for msj msgid "Ma (Democratic Republic of Congo)" -msgstr "" +msgstr "Ma (República Democràtica del Congo)" #. name for msk msgid "Mansaka" -msgstr "" +msgstr "Mansaka" #. name for msl msgid "Molof" -msgstr "" +msgstr "Molof" #. name for msm msgid "Manobo; Agusan" @@ -16868,11 +16868,11 @@ msgstr "" #. name for mso msgid "Mombum" -msgstr "" +msgstr "Mombum" #. name for msp msgid "Maritsauá" -msgstr "" +msgstr "Maritsauà" #. name for msq msgid "Caac" @@ -16884,23 +16884,23 @@ msgstr "Llenguatge de signes mongol" #. name for mss msgid "Masela; West" -msgstr "" +msgstr "Masela; Occidental" #. name for msu msgid "Musom" -msgstr "" +msgstr "Musom" #. name for msv msgid "Maslam" -msgstr "" +msgstr "Maslam" #. name for msw msgid "Mansoanka" -msgstr "" +msgstr "Mansoanka" #. name for msx msgid "Moresada" -msgstr "" +msgstr "Moresada" #. name for msy msgid "Aruamu" @@ -16908,7 +16908,7 @@ msgstr "" #. name for msz msgid "Momare" -msgstr "" +msgstr "Momare" #. name for mta msgid "Manobo; Cotabato" @@ -16920,11 +16920,11 @@ msgstr "" #. name for mtc msgid "Munit" -msgstr "" +msgstr "Munit" #. name for mtd msgid "Mualang" -msgstr "" +msgstr "Mualang" #. name for mte msgid "Mono (Solomon Islands)" @@ -16932,7 +16932,7 @@ msgstr "Mono (Illes Salomó)" #. name for mtf msgid "Murik (Papua New Guinea)" -msgstr "" +msgstr "Múric" #. name for mtg msgid "Una" @@ -16940,15 +16940,15 @@ msgstr "" #. name for mth msgid "Munggui" -msgstr "" +msgstr "Munggui" #. name for mti msgid "Maiwa (Papua New Guinea)" -msgstr "" +msgstr "Maiwa (Papua Nova Guinea)" #. name for mtj msgid "Moskona" -msgstr "" +msgstr "Moskona" #. name for mtk msgid "Mbe'" @@ -16956,15 +16956,15 @@ msgstr "Mbe (Camerun)" #. name for mtl msgid "Montol" -msgstr "" +msgstr "Montol" #. name for mtm msgid "Mator" -msgstr "" +msgstr "Mator" #. name for mtn msgid "Matagalpa" -msgstr "" +msgstr "Matagalpa" #. name for mto msgid "Mixe; Totontepec" @@ -16976,11 +16976,11 @@ msgstr "" #. name for mtq msgid "Muong" -msgstr "" +msgstr "Muong" #. name for mtr msgid "Mewari" -msgstr "" +msgstr "Mewari" #. name for mts msgid "Yora" @@ -16988,7 +16988,7 @@ msgstr "" #. name for mtt msgid "Mota" -msgstr "" +msgstr "Mota" #. name for mtu msgid "Mixtec; Tututepec" @@ -17012,15 +17012,15 @@ msgstr "" #. name for mua msgid "Mundang" -msgstr "" +msgstr "Mundang" #. name for mub msgid "Mubi" -msgstr "" +msgstr "Mubi" #. name for muc msgid "Mbu'" -msgstr "" +msgstr "Mbu" #. name for mud msgid "Aleut; Mednyj" @@ -17028,35 +17028,35 @@ msgstr "" #. name for mue msgid "Media Lengua" -msgstr "" +msgstr "Media Lengua" #. name for mug msgid "Musgu" -msgstr "" +msgstr "Musgu" #. name for muh msgid "Mündü" -msgstr "" +msgstr "Mundu" #. name for mui msgid "Musi" -msgstr "" +msgstr "Musi" #. name for muj msgid "Mabire" -msgstr "" +msgstr "Mabire" #. name for muk msgid "Mugom" -msgstr "" +msgstr "Mugu" #. name for mul msgid "Multiple languages" -msgstr "" +msgstr "Llengües diverses" #. name for mum msgid "Maiwala" -msgstr "" +msgstr "Maiwala" #. name for muo msgid "Nyong" @@ -17064,7 +17064,7 @@ msgstr "" #. name for mup msgid "Malvi" -msgstr "" +msgstr "Malvi" #. name for muq msgid "Miao; Eastern Xiangxi" @@ -17072,7 +17072,7 @@ msgstr "Miao; Xiangxi oriental" #. name for mur msgid "Murle" -msgstr "" +msgstr "Murle" #. name for mus msgid "Creek" @@ -17088,7 +17088,7 @@ msgstr "" #. name for muv msgid "Muthuvan" -msgstr "" +msgstr "Muthuvan" #. name for mux msgid "Bo-Ung" @@ -17096,31 +17096,31 @@ msgstr "" #. name for muy msgid "Muyang" -msgstr "" +msgstr "Muyang" #. name for muz msgid "Mursi" -msgstr "" +msgstr "Mursi" #. name for mva msgid "Manam" -msgstr "" +msgstr "Manam" #. name for mvb msgid "Mattole" -msgstr "" +msgstr "Mattole" #. name for mvd msgid "Mamboru" -msgstr "" +msgstr "Mamboru" #. name for mve msgid "Marwari (Pakistan)" -msgstr "" +msgstr "Marwari; Pakistan" #. name for mvf msgid "Mongolian; Peripheral" -msgstr "" +msgstr "Mongol; Perifèric" #. name for mvg msgid "Mixtec; Yucuañe" @@ -17128,15 +17128,15 @@ msgstr "Mixtec; Yucuañe" #. name for mvh msgid "Mire" -msgstr "" +msgstr "Mire" #. name for mvi msgid "Miyako" -msgstr "" +msgstr "Miyako" #. name for mvk msgid "Mekmek" -msgstr "" +msgstr "Mekmek" #. name for mvl msgid "Mbara (Australia)" @@ -17144,15 +17144,15 @@ msgstr "Mbara (Austràlia)" #. name for mvm msgid "Muya" -msgstr "" +msgstr "Muya" #. name for mvn msgid "Minaveha" -msgstr "" +msgstr "Minaveha" #. name for mvo msgid "Marovo" -msgstr "" +msgstr "Marovo" #. name for mvp msgid "Duri" @@ -17160,35 +17160,35 @@ msgstr "" #. name for mvq msgid "Moere" -msgstr "" +msgstr "Moere" #. name for mvr msgid "Marau" -msgstr "" +msgstr "Marau" #. name for mvs msgid "Massep" -msgstr "" +msgstr "Massep" #. name for mvt msgid "Mpotovoro" -msgstr "" +msgstr "Mpotovoro" #. name for mvu msgid "Marfa" -msgstr "" +msgstr "Marfa" #. name for mvv msgid "Murut; Tagal" -msgstr "" +msgstr "Tagal Murut" #. name for mvw msgid "Machinga" -msgstr "" +msgstr "Machinga" #. name for mvx msgid "Meoswar" -msgstr "" +msgstr "Meoswar" #. name for mvy msgid "Kohistani; Indus" @@ -17196,11 +17196,11 @@ msgstr "" #. name for mvz msgid "Mesqan" -msgstr "" +msgstr "Masqan" #. name for mwa msgid "Mwatebu" -msgstr "" +msgstr "Mwatebu" #. name for mwb msgid "Juwal" @@ -17212,7 +17212,7 @@ msgstr "" #. name for mwd msgid "Mudbura" -msgstr "" +msgstr "Mudbura" #. name for mwe msgid "Mwera (Chimwera)" @@ -17220,7 +17220,7 @@ msgstr "Mwera (Lindi)" #. name for mwf msgid "Murrinh-Patha" -msgstr "" +msgstr "Murrinh-Patha" #. name for mwg msgid "Aiklep" @@ -17228,7 +17228,7 @@ msgstr "" #. name for mwh msgid "Mouk-Aria" -msgstr "" +msgstr "Mouk-Aria" #. name for mwi msgid "Labo" @@ -17236,7 +17236,7 @@ msgstr "" #. name for mwj msgid "Maligo" -msgstr "" +msgstr "Maligo" #. name for mwk msgid "Maninkakan; Kita" @@ -17244,7 +17244,7 @@ msgstr "Maninkakan; Kita" #. name for mwl msgid "Mirandese" -msgstr "" +msgstr "Mirandès" #. name for mwm msgid "Sar" @@ -17256,7 +17256,7 @@ msgstr "" #. name for mwo msgid "Maewo; Central" -msgstr "" +msgstr "Maewo" #. name for mwp msgid "Kala Lagaw Ya" @@ -17268,23 +17268,23 @@ msgstr "Chin; Mün" #. name for mwr msgid "Marwari" -msgstr "" +msgstr "Marwari" #. name for mws msgid "Mwimbi-Muthambi" -msgstr "" +msgstr "Mwimbi-Muthambi" #. name for mwt msgid "Moken" -msgstr "" +msgstr "Moken" #. name for mwu msgid "Mittu" -msgstr "" +msgstr "Mittu" #. name for mwv msgid "Mentawai" -msgstr "" +msgstr "Mentawai" #. name for mww msgid "Hmong Daw" @@ -17292,15 +17292,15 @@ msgstr "Miao; blanc" #. name for mwx msgid "Mediak" -msgstr "" +msgstr "Mediak" #. name for mwy msgid "Mosiro" -msgstr "" +msgstr "Mosiro" #. name for mwz msgid "Moingi" -msgstr "" +msgstr "Moingi" #. name for mxa msgid "Mixtec; Northwest Oaxaca" @@ -17312,31 +17312,31 @@ msgstr "Mixtec; Tezoatlan" #. name for mxc msgid "Manyika" -msgstr "" +msgstr "Manyika" #. name for mxd msgid "Modang" -msgstr "" +msgstr "Modang" #. name for mxe msgid "Mele-Fila" -msgstr "" +msgstr "Mele-Fila" #. name for mxf msgid "Malgbe" -msgstr "" +msgstr "Malgbe" #. name for mxg msgid "Mbangala" -msgstr "" +msgstr "Mbangala" #. name for mxh msgid "Mvuba" -msgstr "" +msgstr "Mvuba" #. name for mxi msgid "Mozarabic" -msgstr "" +msgstr "Mossaràbic" #. name for mxj msgid "Deng; Geman" @@ -17344,7 +17344,7 @@ msgstr "" #. name for mxk msgid "Monumbo" -msgstr "" +msgstr "Monumbo" #. name for mxl msgid "Gbe; Maxi" @@ -17352,7 +17352,7 @@ msgstr "Gbe; Maxi" #. name for mxm msgid "Meramera" -msgstr "" +msgstr "Meramera" #. name for mxn msgid "Moi (Indonesia)" @@ -17360,7 +17360,7 @@ msgstr "Moi (Indonèsia)" #. name for mxo msgid "Mbowe" -msgstr "" +msgstr "Mbowe" #. name for mxp msgid "Mixe; Tlahuitoltepec" @@ -17372,7 +17372,7 @@ msgstr "Mixe; Juquila" #. name for mxr msgid "Murik (Malaysia)" -msgstr "" +msgstr "Kayan; Murik" #. name for mxs msgid "Mixtec; Huitepec" @@ -17384,7 +17384,7 @@ msgstr "Mixtec; Jamiltepec" #. name for mxu msgid "Mada (Cameroon)" -msgstr "" +msgstr "Mada (Camerun)" #. name for mxv msgid "Mixtec; Metlatónoc" @@ -17396,7 +17396,7 @@ msgstr "" #. name for mxx msgid "Mahou" -msgstr "" +msgstr "Mahou" #. name for mxy msgid "Mixtec; Southeastern Nochixtlán" @@ -17404,7 +17404,7 @@ msgstr "Mixtec; Nochixtlan sud-oriental" #. name for mxz msgid "Masela; Central" -msgstr "" +msgstr "Masela; Central" #. name for mya msgid "Burmese" @@ -17412,19 +17412,19 @@ msgstr "birmà" #. name for myb msgid "Mbay" -msgstr "" +msgstr "Mbay" #. name for myc msgid "Mayeka" -msgstr "" +msgstr "Mayeka" #. name for myd msgid "Maramba" -msgstr "" +msgstr "Maramba" #. name for mye msgid "Myene" -msgstr "" +msgstr "Myene" #. name for myf msgid "Bambassi" @@ -17432,19 +17432,19 @@ msgstr "" #. name for myg msgid "Manta" -msgstr "" +msgstr "Manta" #. name for myh msgid "Makah" -msgstr "" +msgstr "Makah" #. name for myi msgid "Mina (India)" -msgstr "" +msgstr "Mina (Índia)" #. name for myj msgid "Mangayat" -msgstr "" +msgstr "Mangayat" #. name for myk msgid "Senoufo; Mamara" @@ -17452,11 +17452,11 @@ msgstr "Senufo; Mamara" #. name for myl msgid "Moma" -msgstr "" +msgstr "Moma" #. name for mym msgid "Me'en" -msgstr "" +msgstr "Meen" #. name for myo msgid "Anfillo" @@ -17472,15 +17472,15 @@ msgstr "Maninka; Forest" #. name for myr msgid "Muniche" -msgstr "" +msgstr "Munitxe" #. name for mys msgid "Mesmes" -msgstr "" +msgstr "Mesmes" #. name for myu msgid "Mundurukú" -msgstr "" +msgstr "Munduruku" #. name for myv msgid "Erzya" @@ -17488,19 +17488,19 @@ msgstr "" #. name for myw msgid "Muyuw" -msgstr "" +msgstr "Muyuw" #. name for myx msgid "Masaaba" -msgstr "" +msgstr "Masaba" #. name for myy msgid "Macuna" -msgstr "" +msgstr "Macuna" #. name for myz msgid "Mandaic; Classical" -msgstr "" +msgstr "Mandaic Clàssic" #. name for mza msgid "Mixtec; Santa María Zacatepec" @@ -17516,11 +17516,11 @@ msgstr "Llenguatge de signes de Madagascar" #. name for mzd msgid "Malimba" -msgstr "" +msgstr "Malimba" #. name for mze msgid "Morawa" -msgstr "" +msgstr "Morawa" #. name for mzg msgid "Monastic Sign Language" @@ -17536,7 +17536,7 @@ msgstr "Mazatec; Ixcatlán" #. name for mzj msgid "Manya" -msgstr "" +msgstr "Manya" #. name for mzk msgid "Mambila; Nigeria" @@ -17548,35 +17548,35 @@ msgstr "Mixe; Mazatlan" #. name for mzm msgid "Mumuye" -msgstr "" +msgstr "Mumuye" #. name for mzn msgid "Mazanderani" -msgstr "" +msgstr "Mazanderaní" #. name for mzo msgid "Matipuhy" -msgstr "" +msgstr "Matipu" #. name for mzp msgid "Movima" -msgstr "" +msgstr "Movima" #. name for mzq msgid "Mori Atas" -msgstr "" +msgstr "Mori; Atas" #. name for mzr msgid "Marúbo" -msgstr "" +msgstr "Marubo" #. name for mzs msgid "Macanese" -msgstr "" +msgstr "Crioll Macau" #. name for mzt msgid "Mintil" -msgstr "" +msgstr "Mintil" #. name for mzu msgid "Inapang" @@ -17584,7 +17584,7 @@ msgstr "" #. name for mzv msgid "Manza" -msgstr "" +msgstr "Manza" #. name for mzw msgid "Deg" @@ -17592,7 +17592,7 @@ msgstr "" #. name for mzx msgid "Mawayana" -msgstr "" +msgstr "Mauaiana" #. name for mzy msgid "Mozambican Sign Language" @@ -17600,31 +17600,31 @@ msgstr "Llenguatge de signes moçambiquès" #. name for mzz msgid "Maiadomu" -msgstr "" +msgstr "Maiadom" #. name for naa msgid "Namla" -msgstr "" +msgstr "Namla" #. name for nab msgid "Nambikuára; Southern" -msgstr "" +msgstr "Nambikwara; Meridional" #. name for nac msgid "Narak" -msgstr "" +msgstr "Narak" #. name for nad msgid "Nijadali" -msgstr "" +msgstr "Nijadali" #. name for nae msgid "Naka'ela" -msgstr "" +msgstr "Naka'ela" #. name for naf msgid "Nabak" -msgstr "" +msgstr "Nabac" #. name for nag msgid "Naga Pidgin" @@ -17632,19 +17632,19 @@ msgstr "Naga; parla mixta" #. name for naj msgid "Nalu" -msgstr "" +msgstr "Nalu" #. name for nak msgid "Nakanai" -msgstr "" +msgstr "Nakanai" #. name for nal msgid "Nalik" -msgstr "" +msgstr "Nalik" #. name for nam msgid "Nangikurrunggurr" -msgstr "" +msgstr "Nangikurrunggurr" #. name for nan msgid "Chinese; Min Nan" @@ -17652,15 +17652,15 @@ msgstr "Xinès; Min Nan" #. name for nao msgid "Naaba" -msgstr "" +msgstr "Naapa Sherpa" #. name for nap msgid "Neapolitan" -msgstr "" +msgstr "Napolità-calabrès" #. name for naq msgid "Nama (Namibia)" -msgstr "" +msgstr "Nama (Namíbia)" #. name for nar msgid "Iguta" @@ -17668,7 +17668,7 @@ msgstr "" #. name for nas msgid "Naasioi" -msgstr "" +msgstr "Nasioi" #. name for nat msgid "Hungworo" @@ -17680,71 +17680,71 @@ msgstr "nauruà" #. name for nav msgid "Navajo" -msgstr "" +msgstr "Navaho" #. name for naw msgid "Nawuri" -msgstr "" +msgstr "Nawuri" #. name for nax msgid "Nakwi" -msgstr "" +msgstr "Nakwi" #. name for nay msgid "Narrinyeri" -msgstr "" +msgstr "Narrinyeri" #. name for naz msgid "Nahuatl; Coatepec" -msgstr "" +msgstr "Nàhuatl; Coatepec" #. name for nba msgid "Nyemba" -msgstr "" +msgstr "Nyemba" #. name for nbb msgid "Ndoe" -msgstr "" +msgstr "Ndoe" #. name for nbc msgid "Naga; Chang" -msgstr "" +msgstr "Naga; Chang" #. name for nbd msgid "Ngbinda" -msgstr "" +msgstr "Ngbinda" #. name for nbe msgid "Naga; Konyak" -msgstr "" +msgstr "Naga; Konyak" #. name for nbg msgid "Nagarchal" -msgstr "" +msgstr "Nagarchal" #. name for nbh msgid "Ngamo" -msgstr "" +msgstr "Ngamo" #. name for nbi msgid "Naga; Mao" -msgstr "" +msgstr "Naga; Mao" #. name for nbj msgid "Ngarinman" -msgstr "" +msgstr "Ngarinman" #. name for nbk msgid "Nake" -msgstr "" +msgstr "Nake" #. name for nbl msgid "Ndebele; South" -msgstr "" +msgstr "Ndebele (Sudàfrica)" #. name for nbm msgid "Ngbaka Ma'bo" -msgstr "" +msgstr "Ngbaka Ma'bo" #. name for nbn msgid "Kuri" @@ -17760,11 +17760,11 @@ msgstr "" #. name for nbq msgid "Nggem" -msgstr "" +msgstr "Nggem" #. name for nbr msgid "Numana-Nunku-Gbantu-Numbu" -msgstr "" +msgstr "Sanga" #. name for nbs msgid "Namibian Sign Language" @@ -17772,27 +17772,27 @@ msgstr "Llenguatge de signes namibi" #. name for nbt msgid "Na" -msgstr "" +msgstr "Na" #. name for nbu msgid "Naga; Rongmei" -msgstr "" +msgstr "Naga; Rongmei" #. name for nbv msgid "Ngamambo" -msgstr "" +msgstr "Ngamambo" #. name for nbw msgid "Ngbandi; Southern" -msgstr "" +msgstr "Ngbandi; Meridional" #. name for nbx msgid "Ngura" -msgstr "" +msgstr "Ngura" #. name for nby msgid "Ningera" -msgstr "" +msgstr "Ningera" #. name for nca msgid "Iyo" @@ -17808,7 +17808,7 @@ msgstr "" #. name for ncd msgid "Nachering" -msgstr "" +msgstr "Nachering" #. name for nce msgid "Yale" @@ -17816,39 +17816,39 @@ msgstr "" #. name for ncf msgid "Notsi" -msgstr "" +msgstr "Notsi" #. name for ncg msgid "Nisga'a" -msgstr "" +msgstr "Nisga'a" #. name for nch msgid "Nahuatl; Central Huasteca" -msgstr "" +msgstr "Nàhuatl; Huasteca Central" #. name for nci msgid "Nahuatl; Classical" -msgstr "" +msgstr "Nàhuatl Clàssic" #. name for ncj msgid "Nahuatl; Northern Puebla" -msgstr "" +msgstr "Nàhuatl; Puebla septentrional" #. name for nck msgid "Nakara" -msgstr "" +msgstr "Nakara" #. name for ncl msgid "Nahuatl; Michoacán" -msgstr "" +msgstr "Nàhuatl; Michoacan" #. name for ncm msgid "Nambo" -msgstr "" +msgstr "Nambu" #. name for ncn msgid "Nauna" -msgstr "" +msgstr "Nauna" #. name for nco msgid "Sibe" @@ -17856,11 +17856,11 @@ msgstr "" #. name for ncp msgid "Ndaktup" -msgstr "" +msgstr "Ndaktup" #. name for ncr msgid "Ncane" -msgstr "" +msgstr "Ncane" #. name for ncs msgid "Nicaraguan Sign Language" @@ -17868,7 +17868,7 @@ msgstr "Llenguatge de signes nicaragüenc" #. name for nct msgid "Naga; Chothe" -msgstr "" +msgstr "Naga; Chothe" #. name for ncu msgid "Chumburung" @@ -17876,15 +17876,15 @@ msgstr "" #. name for ncx msgid "Nahuatl; Central Puebla" -msgstr "" +msgstr "Nàhuatl; Puebla Central" #. name for ncz msgid "Natchez" -msgstr "" +msgstr "Natchez" #. name for nda msgid "Ndasa" -msgstr "" +msgstr "Ndasa" #. name for ndb msgid "Kenswei Nsei" @@ -17892,27 +17892,27 @@ msgstr "" #. name for ndc msgid "Ndau" -msgstr "" +msgstr "Ndau" #. name for ndd msgid "Nde-Nsele-Nta" -msgstr "" +msgstr "Nde-Nsele-Nta" #. name for nde msgid "Ndebele; North" -msgstr "" +msgstr "Ndebele (Zimbawe)" #. name for ndf msgid "Nadruvian" -msgstr "" +msgstr "Nadruvià" #. name for ndg msgid "Ndengereko" -msgstr "" +msgstr "Ndengereko" #. name for ndh msgid "Ndali" -msgstr "" +msgstr "Ndali" #. name for ndi msgid "Samba Leko" @@ -17920,47 +17920,47 @@ msgstr "" #. name for ndj msgid "Ndamba" -msgstr "" +msgstr "Ndamba" #. name for ndk msgid "Ndaka" -msgstr "" +msgstr "Ndaka" #. name for ndl msgid "Ndolo" -msgstr "" +msgstr "Ndolo" #. name for ndm msgid "Ndam" -msgstr "" +msgstr "Ndam" #. name for ndn msgid "Ngundi" -msgstr "" +msgstr "Ngundi" #. name for ndo msgid "Ndonga" -msgstr "" +msgstr "Ndonga" #. name for ndp msgid "Ndo" -msgstr "" +msgstr "Ndo" #. name for ndq msgid "Ndombe" -msgstr "" +msgstr "Ndombe" #. name for ndr msgid "Ndoola" -msgstr "" +msgstr "Ndoola" #. name for nds msgid "German; Low" -msgstr "" +msgstr "Alemany; Baix" #. name for ndt msgid "Ndunga" -msgstr "" +msgstr "Ndunga" #. name for ndu msgid "Dugun" @@ -17968,15 +17968,15 @@ msgstr "" #. name for ndv msgid "Ndut" -msgstr "" +msgstr "Ndut" #. name for ndw msgid "Ndobo" -msgstr "" +msgstr "Ndobo" #. name for ndx msgid "Nduga" -msgstr "" +msgstr "Nduga" #. name for ndy msgid "Lutos" @@ -17984,11 +17984,11 @@ msgstr "" #. name for ndz msgid "Ndogo" -msgstr "" +msgstr "Ndogo" #. name for nea msgid "Ngad'a; Eastern" -msgstr "" +msgstr "Ngada; Oriental" #. name for neb msgid "Toura (Côte d'Ivoire)" @@ -17996,55 +17996,55 @@ msgstr "Toura (Costa d'Ivori)" #. name for nec msgid "Nedebang" -msgstr "" +msgstr "Nedebang" #. name for ned msgid "Nde-Gbite" -msgstr "" +msgstr "Nde-Gbite" #. name for nee msgid "Nêlêmwa-Nixumwak" -msgstr "" +msgstr "kumak" #. name for nef msgid "Nefamese" -msgstr "" +msgstr "Nefamese" #. name for neg msgid "Negidal" -msgstr "" +msgstr "Negidal" #. name for neh msgid "Nyenkha" -msgstr "" +msgstr "Nyenkha" #. name for nei msgid "Hittite; Neo-" -msgstr "" +msgstr "Neo-Hittita" #. name for nej msgid "Neko" -msgstr "" +msgstr "Neko" #. name for nek msgid "Neku" -msgstr "" +msgstr "Neku" #. name for nem msgid "Nemi" -msgstr "" +msgstr "Nemi" #. name for nen msgid "Nengone" -msgstr "" +msgstr "Nengone" #. name for neo msgid "Ná-Meo" -msgstr "" +msgstr "Na-Meo" #. name for nep msgid "Nepali" -msgstr "" +msgstr "Nepalès" #. name for neq msgid "Mixe; North Central" @@ -18060,11 +18060,11 @@ msgstr "Kinnauri; Bhoti" #. name for net msgid "Nete" -msgstr "" +msgstr "Nete" #. name for nev msgid "Nyaheun" -msgstr "" +msgstr "Nyaheun" #. name for new msgid "Bhasa; Nepal" @@ -18072,15 +18072,15 @@ msgstr "" #. name for nex msgid "Neme" -msgstr "" +msgstr "Neme" #. name for ney msgid "Neyo" -msgstr "" +msgstr "Neyo" #. name for nez msgid "Nez Perce" -msgstr "" +msgstr "Nez" #. name for nfa msgid "Dhao" @@ -18096,7 +18096,7 @@ msgstr "" #. name for nfr msgid "Nafaanra" -msgstr "" +msgstr "Nafaanra" #. name for nfu msgid "Mfumte" @@ -18104,43 +18104,43 @@ msgstr "" #. name for nga msgid "Ngbaka" -msgstr "" +msgstr "Ngbaka" #. name for ngb msgid "Ngbandi; Northern" -msgstr "" +msgstr "Ngbandi; Septentrional" #. name for ngc msgid "Ngombe (Democratic Republic of Congo)" -msgstr "" +msgstr "Ngombe (República Democràtica del Congo)" #. name for ngd msgid "Ngando (Central African Republic)" -msgstr "" +msgstr "Ngando (República Centrafricana)" #. name for nge msgid "Ngemba" -msgstr "" +msgstr "Ngemba" #. name for ngg msgid "Ngbaka Manza" -msgstr "" +msgstr "Ngbaka Manza" #. name for ngh msgid "N/u" -msgstr "" +msgstr "N'u" #. name for ngi msgid "Ngizim" -msgstr "" +msgstr "Ngizim" #. name for ngj msgid "Ngie" -msgstr "" +msgstr "Ngie" #. name for ngk msgid "Ngalkbun" -msgstr "" +msgstr "Ngalkbun" #. name for ngl msgid "Lomwe" @@ -18152,23 +18152,23 @@ msgstr "Crioll dels homes Ngatik" #. name for ngn msgid "Ngwo" -msgstr "" +msgstr "Ngwo" #. name for ngo msgid "Ngoni" -msgstr "" +msgstr "Ngoní" #. name for ngp msgid "Ngulu" -msgstr "" +msgstr "Ngulu" #. name for ngq msgid "Ngurimi" -msgstr "" +msgstr "Ngurimi" #. name for ngr msgid "Nanggu" -msgstr "" +msgstr "Nanggu" #. name for ngs msgid "Gvoko" @@ -18176,23 +18176,23 @@ msgstr "" #. name for ngt msgid "Ngeq" -msgstr "" +msgstr "Ngeq" #. name for ngu msgid "Nahuatl; Guerrero" -msgstr "" +msgstr "Nàhuatl; Guerrero" #. name for ngv msgid "Nagumi" -msgstr "" +msgstr "Nagumi" #. name for ngw msgid "Ngwaba" -msgstr "" +msgstr "Ngwaba" #. name for ngx msgid "Nggwahyi" -msgstr "" +msgstr "Nggwahyi" #. name for ngy msgid "Tibea" @@ -18200,7 +18200,7 @@ msgstr "" #. name for ngz msgid "Ngungwel" -msgstr "" +msgstr "Ngungwel" #. name for nha msgid "Nhanda" @@ -18212,7 +18212,7 @@ msgstr "" #. name for nhc msgid "Nahuatl; Tabasco" -msgstr "" +msgstr "Nàhuatl; Tabasco" #. name for nhd msgid "Guaraní; Ava" @@ -18220,35 +18220,35 @@ msgstr "Guaraní; Ava" #. name for nhe msgid "Nahuatl; Eastern Huasteca" -msgstr "" +msgstr "Nàhuatl; Huastec oriental" #. name for nhf msgid "Nhuwala" -msgstr "" +msgstr "Nhuwala" #. name for nhg msgid "Nahuatl; Tetelcingo" -msgstr "" +msgstr "Nàhuatl; Tetelcingo" #. name for nhh msgid "Nahari" -msgstr "" +msgstr "Nahali" #. name for nhi msgid "Nahuatl; Zacatlán-Ahuacatlán-Tepetzintla" -msgstr "" +msgstr "Nàhuatl; Tenango" #. name for nhk msgid "Nahuatl; Isthmus-Cosoleacaque" -msgstr "" +msgstr "Nàhuatl; Cosoleacaque" #. name for nhm msgid "Nahuatl; Morelos" -msgstr "" +msgstr "Nàhuatl; Morelos" #. name for nhn msgid "Nahuatl; Central" -msgstr "" +msgstr "Nàhuatl; Central" #. name for nho msgid "Takuu" @@ -18256,79 +18256,79 @@ msgstr "" #. name for nhp msgid "Nahuatl; Isthmus-Pajapan" -msgstr "" +msgstr "Nàhuatl; Pajapan" #. name for nhq msgid "Nahuatl; Huaxcaleca" -msgstr "" +msgstr "Nàhuatl; Huaxcalec" #. name for nhr msgid "Naro" -msgstr "" +msgstr "Naro" #. name for nht msgid "Nahuatl; Ometepec" -msgstr "" +msgstr "Nàhuatl; Ometepec" #. name for nhu msgid "Noone" -msgstr "" +msgstr "Noone" #. name for nhv msgid "Nahuatl; Temascaltepec" -msgstr "" +msgstr "Nàhuatl; Temascaltepec" #. name for nhw msgid "Nahuatl; Western Huasteca" -msgstr "" +msgstr "Nàhuatl; Huastec occidental" #. name for nhx msgid "Nahuatl; Isthmus-Mecayapan" -msgstr "" +msgstr "Nàhuatl; Mecayapan" #. name for nhy msgid "Nahuatl; Northern Oaxaca" -msgstr "" +msgstr "Nàhuatl; Oaxaca Septentrional" #. name for nhz msgid "Nahuatl; Santa María La Alta" -msgstr "" +msgstr "Nàhuatl; Santa Maria" #. name for nia msgid "Nias" -msgstr "" +msgstr "Nias" #. name for nib msgid "Nakame" -msgstr "" +msgstr "Nakama" #. name for nid msgid "Ngandi" -msgstr "" +msgstr "Ngandi" #. name for nie msgid "Niellim" -msgstr "" +msgstr "Niellim" #. name for nif msgid "Nek" -msgstr "" +msgstr "Nek" #. name for nig msgid "Ngalakan" -msgstr "" +msgstr "Ngalakan" #. name for nih msgid "Nyiha (Tanzania)" -msgstr "" +msgstr "Nyiha (Tanzània)" #. name for nii msgid "Nii" -msgstr "" +msgstr "Nii" #. name for nij msgid "Ngaju" -msgstr "" +msgstr "Ngaju" #. name for nik msgid "Nicobarese; Southern" @@ -18336,31 +18336,31 @@ msgstr "Nicobarès; meridional" #. name for nil msgid "Nila" -msgstr "" +msgstr "Nila" #. name for nim msgid "Nilamba" -msgstr "" +msgstr "Nilamba" #. name for nin msgid "Ninzo" -msgstr "" +msgstr "Ninzam" #. name for nio msgid "Nganasan" -msgstr "" +msgstr "Nganassan" #. name for niq msgid "Nandi" -msgstr "" +msgstr "Nandí" #. name for nir msgid "Nimboran" -msgstr "" +msgstr "Nimboran" #. name for nis msgid "Nimi" -msgstr "" +msgstr "Nimi" #. name for nit msgid "Kolami; Southeastern" @@ -18368,7 +18368,7 @@ msgstr "Kolami; Meridional" #. name for niu msgid "Niuean" -msgstr "" +msgstr "Niueà" #. name for niv msgid "Gilyak" @@ -18376,7 +18376,7 @@ msgstr "" #. name for niw msgid "Nimo" -msgstr "" +msgstr "Nimo" #. name for nix msgid "Hema" @@ -18384,27 +18384,27 @@ msgstr "" #. name for niy msgid "Ngiti" -msgstr "" +msgstr "Ngiti" #. name for niz msgid "Ningil" -msgstr "" +msgstr "Ningil" #. name for nja msgid "Nzanyi" -msgstr "" +msgstr "Nzanyi" #. name for njb msgid "Naga; Nocte" -msgstr "" +msgstr "Naga; Nocte" #. name for njd msgid "Ndonde Hamba" -msgstr "" +msgstr "Ndonde" #. name for njh msgid "Naga; Lotha" -msgstr "" +msgstr "Naga; Lotha" #. name for nji msgid "Gudanji" @@ -18412,31 +18412,31 @@ msgstr "" #. name for njj msgid "Njen" -msgstr "" +msgstr "Njen" #. name for njl msgid "Njalgulgule" -msgstr "" +msgstr "Njalgulgule" #. name for njm msgid "Naga; Angami" -msgstr "" +msgstr "Naga; Angami" #. name for njn msgid "Naga; Liangmai" -msgstr "" +msgstr "Naga; Liangmai" #. name for njo msgid "Naga; Ao" -msgstr "" +msgstr "Naga; Ao" #. name for njr msgid "Njerep" -msgstr "" +msgstr "Njerep" #. name for njs msgid "Nisa" -msgstr "" +msgstr "Nisa" #. name for njt msgid "Ndyuka-Trio Pidgin" @@ -18444,7 +18444,7 @@ msgstr "Ndyuka-Trio; parla mixta" #. name for nju msgid "Ngadjunmaya" -msgstr "" +msgstr "Ngadjunmaya" #. name for njx msgid "Kunyi" @@ -18452,19 +18452,19 @@ msgstr "" #. name for njy msgid "Njyem" -msgstr "" +msgstr "Ndjem" #. name for nka msgid "Nkoya" -msgstr "" +msgstr "Nkoya" #. name for nkb msgid "Naga; Khoibu" -msgstr "" +msgstr "Naga; Khoibu" #. name for nkc msgid "Nkongho" -msgstr "" +msgstr "Nkongho" #. name for nkd msgid "Koireng" @@ -18476,43 +18476,43 @@ msgstr "" #. name for nkf msgid "Naga; Inpui" -msgstr "" +msgstr "Naga; Inpui" #. name for nkg msgid "Nekgini" -msgstr "" +msgstr "Nekgini" #. name for nkh msgid "Naga; Khezha" -msgstr "" +msgstr "Naga; Khezha" #. name for nki msgid "Naga; Thangal" -msgstr "" +msgstr "Naga; Thangal" #. name for nkj msgid "Nakai" -msgstr "" +msgstr "Nakai" #. name for nkk msgid "Nokuku" -msgstr "" +msgstr "Nokuku" #. name for nkm msgid "Namat" -msgstr "" +msgstr "Namat" #. name for nkn msgid "Nkangala" -msgstr "" +msgstr "Nkangala" #. name for nko msgid "Nkonya" -msgstr "" +msgstr "Nkonya" #. name for nkp msgid "Niuatoputapu" -msgstr "" +msgstr "Niuatoputapu" #. name for nkq msgid "Nkami" @@ -18520,7 +18520,7 @@ msgstr "" #. name for nkr msgid "Nukuoro" -msgstr "" +msgstr "Nukuoro" #. name for nks msgid "Asmat; North" @@ -18540,23 +18540,23 @@ msgstr "" #. name for nkw msgid "Nkutu" -msgstr "" +msgstr "Nkutu" #. name for nkx msgid "Nkoroo" -msgstr "" +msgstr "Nkoro" #. name for nkz msgid "Nkari" -msgstr "" +msgstr "Nkari" #. name for nla msgid "Ngombale" -msgstr "" +msgstr "Ngombale" #. name for nlc msgid "Nalca" -msgstr "" +msgstr "Nalca" #. name for nld msgid "Dutch" @@ -18564,7 +18564,7 @@ msgstr "Holandès" #. name for nle msgid "Nyala; East" -msgstr "" +msgstr "Nyala" #. name for nlg msgid "Gela" @@ -18576,7 +18576,7 @@ msgstr "" #. name for nlj msgid "Nyali" -msgstr "" +msgstr "Nyali" #. name for nlk msgid "Yali; Ninia" @@ -18584,63 +18584,63 @@ msgstr "" #. name for nll msgid "Nihali" -msgstr "" +msgstr "Nihali" #. name for nln msgid "Nahuatl; Durango" -msgstr "" +msgstr "Nàhuatl; Durango" #. name for nlo msgid "Ngul" -msgstr "" +msgstr "Ngul" #. name for nlr msgid "Ngarla" -msgstr "" +msgstr "Ngarla" #. name for nlu msgid "Nchumbulu" -msgstr "" +msgstr "Nchumbulu" #. name for nlv msgid "Nahuatl; Orizaba" -msgstr "" +msgstr "Nàhuatl; Orizaba" #. name for nlx msgid "Nahali" -msgstr "" +msgstr "Nahali" #. name for nly msgid "Nyamal" -msgstr "" +msgstr "Nyamal" #. name for nlz msgid "Nalögo" -msgstr "" +msgstr "Santa Cruz; Sudoccidental" #. name for nma msgid "Naga; Maram" -msgstr "" +msgstr "Naga; Maram" #. name for nmb msgid "Nambas; Big" -msgstr "" +msgstr "Nambas" #. name for nmc msgid "Ngam" -msgstr "" +msgstr "Ngam" #. name for nmd msgid "Ndumu" -msgstr "" +msgstr "Ndumu" #. name for nme msgid "Naga; Mzieme" -msgstr "" +msgstr "Naga; Mzieme" #. name for nmf msgid "Naga; Tangkhul" -msgstr "" +msgstr "Naga; Tangkhul" #. name for nmg msgid "Kwasio" @@ -18648,23 +18648,23 @@ msgstr "" #. name for nmh msgid "Naga; Monsang" -msgstr "" +msgstr "Naga; Monsang" #. name for nmi msgid "Nyam" -msgstr "" +msgstr "Nyam" #. name for nmj msgid "Ngombe (Central African Republic)" -msgstr "" +msgstr "Ngombe (República Centrafricana)" #. name for nmk msgid "Namakura" -msgstr "" +msgstr "Namakura" #. name for nml msgid "Ndemli" -msgstr "" +msgstr "Ndemli" #. name for nmm msgid "Manangba" @@ -18676,19 +18676,19 @@ msgstr "" #. name for nmo msgid "Naga; Moyon" -msgstr "" +msgstr "Naga; Moyon" #. name for nmp msgid "Nimanbur" -msgstr "" +msgstr "Nimanbur" #. name for nmq msgid "Nambya" -msgstr "" +msgstr "Nambya" #. name for nmr msgid "Nimbari" -msgstr "" +msgstr "Nimbari" #. name for nms msgid "Letemboi" @@ -18696,7 +18696,7 @@ msgstr "" #. name for nmt msgid "Namonuito" -msgstr "" +msgstr "Namonuito" #. name for nmu msgid "Maidu; Northeast" @@ -18704,35 +18704,35 @@ msgstr "Maidu; Nordoriental" #. name for nmv msgid "Ngamini" -msgstr "" +msgstr "Ngamini" #. name for nmw msgid "Nimoa" -msgstr "" +msgstr "Nimoa" #. name for nmx msgid "Nama (Papua New Guinea)" -msgstr "" +msgstr "Nama (Papua Nova Guinea)" #. name for nmy msgid "Namuyi" -msgstr "" +msgstr "Namuyi" #. name for nmz msgid "Nawdm" -msgstr "" +msgstr "Nawdm" #. name for nna msgid "Nyangumarta" -msgstr "" +msgstr "Nyangumarta" #. name for nnb msgid "Nande" -msgstr "" +msgstr "Nandí" #. name for nnc msgid "Nancere" -msgstr "" +msgstr "Nanceré" #. name for nnd msgid "Ambae; West" @@ -18740,43 +18740,43 @@ msgstr "" #. name for nne msgid "Ngandyera" -msgstr "" +msgstr "Ngandyera" #. name for nnf msgid "Ngaing" -msgstr "" +msgstr "Ngaing" #. name for nng msgid "Naga; Maring" -msgstr "" +msgstr "Naga; Maring" #. name for nnh msgid "Ngiemboon" -msgstr "" +msgstr "Ngiemboon" #. name for nni msgid "Nuaulu; North" -msgstr "" +msgstr "Nuaulu; Septentrional" #. name for nnj msgid "Nyangatom" -msgstr "" +msgstr "Nyangatom" #. name for nnk msgid "Nankina" -msgstr "" +msgstr "Nankina" #. name for nnl msgid "Naga; Northern Rengma" -msgstr "" +msgstr "Naga; Rengma septentrional" #. name for nnm msgid "Namia" -msgstr "" +msgstr "Namia" #. name for nnn msgid "Ngete" -msgstr "" +msgstr "Ngete" #. name for nno msgid "Norwegian Nynorsk" @@ -18784,23 +18784,23 @@ msgstr "Noruec; Nynorsk" #. name for nnp msgid "Naga; Wancho" -msgstr "" +msgstr "Naga; Wancho" #. name for nnq msgid "Ngindo" -msgstr "" +msgstr "Ngindo" #. name for nnr msgid "Narungga" -msgstr "" +msgstr "Narungga" #. name for nns msgid "Ningye" -msgstr "" +msgstr "Ningye" #. name for nnt msgid "Nanticoke" -msgstr "" +msgstr "Nanticoke" #. name for nnu msgid "Dwang" @@ -18808,23 +18808,23 @@ msgstr "" #. name for nnv msgid "Nugunu (Australia)" -msgstr "" +msgstr "Nugunu" #. name for nnw msgid "Nuni; Southern" -msgstr "" +msgstr "Nuni; Meridional" #. name for nnx msgid "Ngong" -msgstr "" +msgstr "Ngong" #. name for nny msgid "Nyangga" -msgstr "" +msgstr "Nyangga" #. name for nnz msgid "Nda'nda'" -msgstr "" +msgstr "Ndanda" #. name for noa msgid "Woun Meu" @@ -18836,7 +18836,7 @@ msgstr "Noruec; Bokmål" #. name for noc msgid "Nuk" -msgstr "" +msgstr "Nuk" #. name for nod msgid "Thai; Northern" @@ -18844,47 +18844,47 @@ msgstr "" #. name for noe msgid "Nimadi" -msgstr "" +msgstr "Nimadi" #. name for nof msgid "Nomane" -msgstr "" +msgstr "Nomane" #. name for nog msgid "Nogai" -msgstr "" +msgstr "Nogai" #. name for noh msgid "Nomu" -msgstr "" +msgstr "Nomu" #. name for noi msgid "Noiri" -msgstr "" +msgstr "Noiri" #. name for noj msgid "Nonuya" -msgstr "" +msgstr "Nonyua" #. name for nok msgid "Nooksack" -msgstr "" +msgstr "Nooksack" #. name for nom msgid "Nocamán" -msgstr "" +msgstr "Nocaman" #. name for non msgid "Norse; Old" -msgstr "" +msgstr "Nòrdic antic" #. name for nop msgid "Numanggang" -msgstr "" +msgstr "Numanggang" #. name for noq msgid "Ngongo" -msgstr "" +msgstr "Ngongo" #. name for nor msgid "Norwegian" @@ -18892,11 +18892,11 @@ msgstr "Noruec" #. name for nos msgid "Nisu; Eastern" -msgstr "" +msgstr "Yi; Lolo meridional" #. name for not msgid "Nomatsiguenga" -msgstr "" +msgstr "Nomatsiguenga" #. name for nou msgid "Ewage-Notu" @@ -18904,35 +18904,35 @@ msgstr "" #. name for nov msgid "Novial" -msgstr "" +msgstr "Novial" #. name for now msgid "Nyambo" -msgstr "" +msgstr "Nyambo" #. name for noy msgid "Noy" -msgstr "" +msgstr "Noy" #. name for noz msgid "Nayi" -msgstr "" +msgstr "Nayi" #. name for npa msgid "Nar Phu" -msgstr "" +msgstr "Nar-Phu" #. name for npb msgid "Nupbikha" -msgstr "" +msgstr "Nupbikha" #. name for nph msgid "Naga; Phom" -msgstr "" +msgstr "Naga; Phom" #. name for npl msgid "Nahuatl; Southeastern Puebla" -msgstr "" +msgstr "Nàhuatl; Puebla sudoriental" #. name for npn msgid "Mondropolon" @@ -18940,23 +18940,23 @@ msgstr "" #. name for npo msgid "Naga; Pochuri" -msgstr "" +msgstr "Naga; Pochuri" #. name for nps msgid "Nipsan" -msgstr "" +msgstr "Nipsan" #. name for npu msgid "Naga; Puimei" -msgstr "" +msgstr "Naga; Puimei" #. name for npy msgid "Napu" -msgstr "" +msgstr "Napu" #. name for nqg msgid "Nago; Southern" -msgstr "" +msgstr "Ede; Nago" #. name for nqk msgid "Ede Nago; Kura" @@ -18964,11 +18964,11 @@ msgstr "Ede;Nago Kura" #. name for nqm msgid "Ndom" -msgstr "" +msgstr "Ndom" #. name for nqn msgid "Nen" -msgstr "" +msgstr "Nen" #. name for nqo msgid "N'Ko" @@ -18976,39 +18976,39 @@ msgstr "" #. name for nra msgid "Ngom" -msgstr "" +msgstr "Ngom" #. name for nrb msgid "Nara" -msgstr "" +msgstr "Nara" #. name for nrc msgid "Noric" -msgstr "" +msgstr "Noric" #. name for nre msgid "Naga; Southern Rengma" -msgstr "" +msgstr "Naga; Rengma meridional" #. name for nrg msgid "Narango" -msgstr "" +msgstr "Narango" #. name for nri msgid "Naga; Chokri" -msgstr "" +msgstr "Naga; Chokri" #. name for nrl msgid "Ngarluma" -msgstr "" +msgstr "Ngarluma" #. name for nrm msgid "Narom" -msgstr "" +msgstr "Narom" #. name for nrn msgid "Norn" -msgstr "" +msgstr "Norn" #. name for nrp msgid "Picene; North" @@ -19016,7 +19016,7 @@ msgstr "" #. name for nrr msgid "Norra" -msgstr "" +msgstr "Norra" #. name for nrt msgid "Kalapuya; Northern" @@ -19028,7 +19028,7 @@ msgstr "" #. name for nrx msgid "Ngurmbur" -msgstr "" +msgstr "Ngurmbur" #. name for nrz msgid "Lala" @@ -19036,27 +19036,27 @@ msgstr "" #. name for nsa msgid "Naga; Sangtam" -msgstr "" +msgstr "Naga; Sangtam" #. name for nsc msgid "Nshi" -msgstr "" +msgstr "Nshi" #. name for nsd msgid "Nisu; Southern" -msgstr "" +msgstr "Nisu; Meridional" #. name for nse msgid "Nsenga" -msgstr "" +msgstr "Nsenga" #. name for nsg msgid "Ngasa" -msgstr "" +msgstr "Ngasa" #. name for nsh msgid "Ngoshie" -msgstr "" +msgstr "Ngishe" #. name for nsi msgid "Nigerian Sign Language" @@ -19064,7 +19064,7 @@ msgstr "Llenguatge de signes nigerià" #. name for nsk msgid "Naskapi" -msgstr "" +msgstr "Naskapi" #. name for nsl msgid "Norwegian Sign Language" @@ -19072,11 +19072,11 @@ msgstr "Llenguatge de signes noruec" #. name for nsm msgid "Naga; Sumi" -msgstr "" +msgstr "Naga; Sumi" #. name for nsn msgid "Nehan" -msgstr "" +msgstr "Nehan" #. name for nso msgid "Sotho; Northern" @@ -19096,27 +19096,27 @@ msgstr "Llenguatge de signes marítim" #. name for nss msgid "Nali" -msgstr "" +msgstr "Nali" #. name for nst msgid "Naga; Tase" -msgstr "" +msgstr "Naga; Tase" #. name for nsu msgid "Nahuatl; Sierra Negra" -msgstr "" +msgstr "Nàhuatl; Sierra Negra" #. name for nsv msgid "Nisu; Southwestern" -msgstr "" +msgstr "Nisu; Sudoccidental" #. name for nsw msgid "Navut" -msgstr "" +msgstr "Navut" #. name for nsx msgid "Nsongo" -msgstr "" +msgstr "Nsongo" #. name for nsy msgid "Nasal" @@ -19124,19 +19124,19 @@ msgstr "" #. name for nsz msgid "Nisenan" -msgstr "" +msgstr "Nisenan" #. name for nte msgid "Nathembo" -msgstr "" +msgstr "Nathembo" #. name for nti msgid "Natioro" -msgstr "" +msgstr "Natioro" #. name for ntj msgid "Ngaanyatjarra" -msgstr "" +msgstr "Ngaanyatjarra" #. name for ntk msgid "Ikoma-Nata-Isenye" @@ -19144,11 +19144,11 @@ msgstr "" #. name for ntm msgid "Nateni" -msgstr "" +msgstr "Nateni" #. name for nto msgid "Ntomba" -msgstr "" +msgstr "Ntomba" #. name for ntp msgid "Tepehuan; Northern" @@ -19160,15 +19160,15 @@ msgstr "" #. name for nts msgid "Natagaimas" -msgstr "" +msgstr "Natagaimas" #. name for ntu msgid "Natügu" -msgstr "" +msgstr "Santa Cruz: Septentrional" #. name for ntw msgid "Nottoway" -msgstr "" +msgstr "Nottoway" #. name for nty msgid "Mantsi" @@ -19176,7 +19176,7 @@ msgstr "" #. name for ntz msgid "Natanzi" -msgstr "" +msgstr "Natanzi" #. name for nua msgid "Yuaga" @@ -19184,35 +19184,35 @@ msgstr "" #. name for nuc msgid "Nukuini" -msgstr "" +msgstr "Nukini" #. name for nud msgid "Ngala" -msgstr "" +msgstr "Ngala" #. name for nue msgid "Ngundu" -msgstr "" +msgstr "Ngundu" #. name for nuf msgid "Nusu" -msgstr "" +msgstr "Nusu" #. name for nug msgid "Nungali" -msgstr "" +msgstr "Nungali" #. name for nuh msgid "Ndunda" -msgstr "" +msgstr "Ndunda" #. name for nui msgid "Ngumbi" -msgstr "" +msgstr "Ngumbi" #. name for nuj msgid "Nyole" -msgstr "" +msgstr "Nyole" #. name for nuk msgid "Nuu-chah-nulth" @@ -19220,11 +19220,11 @@ msgstr "" #. name for nul msgid "Nusa Laut" -msgstr "" +msgstr "Nusa Laut" #. name for num msgid "Niuafo'ou" -msgstr "" +msgstr "Niuafo'ou" #. name for nun msgid "Anong" @@ -19232,39 +19232,39 @@ msgstr "" #. name for nuo msgid "Nguôn" -msgstr "" +msgstr "Nguon" #. name for nup msgid "Nupe-Nupe-Tako" -msgstr "" +msgstr "Nupe" #. name for nuq msgid "Nukumanu" -msgstr "" +msgstr "Nukumanu" #. name for nur msgid "Nukuria" -msgstr "" +msgstr "Nuguria" #. name for nus msgid "Nuer" -msgstr "" +msgstr "Nuer" #. name for nut msgid "Nung (Viet Nam)" -msgstr "" +msgstr "Nung (VietNam)" #. name for nuu msgid "Ngbundu" -msgstr "" +msgstr "Ngbundu" #. name for nuv msgid "Nuni; Northern" -msgstr "" +msgstr "Nuni; Septentrional" #. name for nuw msgid "Nguluwan" -msgstr "" +msgstr "Nguluwà" #. name for nux msgid "Mehek" @@ -19272,35 +19272,35 @@ msgstr "" #. name for nuy msgid "Nunggubuyu" -msgstr "" +msgstr "Nunggubuyu" #. name for nuz msgid "Nahuatl; Tlamacazapa" -msgstr "" +msgstr "Nàhuatl; Tlamacazapa" #. name for nvh msgid "Nasarian" -msgstr "" +msgstr "Nasarià" #. name for nvm msgid "Namiae" -msgstr "" +msgstr "Namiae" #. name for nwa msgid "Nawathinehena" -msgstr "" +msgstr "Nawathinahana" #. name for nwb msgid "Nyabwa" -msgstr "" +msgstr "Nyabwa-Nyédébwa" #. name for nwc msgid "Newari; Old" -msgstr "" +msgstr "Newar; Antic" #. name for nwe msgid "Ngwe" -msgstr "" +msgstr "Ngwe" #. name for nwi msgid "Tanna; Southwest" @@ -19308,79 +19308,79 @@ msgstr "" #. name for nwm msgid "Nyamusa-Molo" -msgstr "" +msgstr "Nyamusa-Molo" #. name for nwr msgid "Nawaru" -msgstr "" +msgstr "Nawaru" #. name for nwx msgid "Newar; Middle" -msgstr "" +msgstr "Newar; Mitjà" #. name for nwy msgid "Nottoway-Meherrin" -msgstr "" +msgstr "Nottoway" #. name for nxa msgid "Nauete" -msgstr "" +msgstr "Naueti" #. name for nxd msgid "Ngando (Democratic Republic of Congo)" -msgstr "" +msgstr "Ngando (República Democràtica del Congo)" #. name for nxe msgid "Nage" -msgstr "" +msgstr "Nage" #. name for nxg msgid "Ngad'a" -msgstr "" +msgstr "Ngada; Central" #. name for nxi msgid "Nindi" -msgstr "" +msgstr "Nindi" #. name for nxl msgid "Nuaulu; South" -msgstr "" +msgstr "Nuaulu; Meridional" #. name for nxm msgid "Numidian" -msgstr "" +msgstr "Líbic" #. name for nxn msgid "Ngawun" -msgstr "" +msgstr "Ngawun" #. name for nxq msgid "Naxi" -msgstr "" +msgstr "Naxi" #. name for nxr msgid "Ninggerum" -msgstr "" +msgstr "Ninggirum" #. name for nxu msgid "Narau" -msgstr "" +msgstr "Narau" #. name for nxx msgid "Nafri" -msgstr "" +msgstr "Nafri" #. name for nya msgid "Nyanja" -msgstr "" +msgstr "Nyanja" #. name for nyb msgid "Nyangbo" -msgstr "" +msgstr "Nyangbo" #. name for nyc msgid "Nyanga-li" -msgstr "" +msgstr "Nyanga-li" #. name for nyd msgid "Nyore" @@ -19388,7 +19388,7 @@ msgstr "" #. name for nye msgid "Nyengo" -msgstr "" +msgstr "Nyengo" #. name for nyf msgid "Giryama" @@ -19396,11 +19396,11 @@ msgstr "" #. name for nyg msgid "Nyindu" -msgstr "" +msgstr "Nyindu" #. name for nyh msgid "Nyigina" -msgstr "" +msgstr "Nyigina" #. name for nyi msgid "Ama (Sudan)" @@ -19408,35 +19408,35 @@ msgstr "" #. name for nyj msgid "Nyanga" -msgstr "" +msgstr "Nyanga" #. name for nyk msgid "Nyaneka" -msgstr "" +msgstr "Nyaneka" #. name for nyl msgid "Nyeu" -msgstr "" +msgstr "Nyeu" #. name for nym msgid "Nyamwezi" -msgstr "" +msgstr "Nyamwesi" #. name for nyn msgid "Nyankole" -msgstr "" +msgstr "Nyankore" #. name for nyo msgid "Nyoro" -msgstr "" +msgstr "Nyoro" #. name for nyp msgid "Nyang'i" -msgstr "" +msgstr "Nyangi" #. name for nyq msgid "Nayini" -msgstr "" +msgstr "Nayini" #. name for nyr msgid "Nyiha (Malawi)" @@ -19444,31 +19444,31 @@ msgstr "" #. name for nys msgid "Nyunga" -msgstr "" +msgstr "Nyunga" #. name for nyt msgid "Nyawaygi" -msgstr "" +msgstr "Nyawaygi" #. name for nyu msgid "Nyungwe" -msgstr "" +msgstr "Nyungwe" #. name for nyv msgid "Nyulnyul" -msgstr "" +msgstr "Nyulnyui" #. name for nyw msgid "Nyaw" -msgstr "" +msgstr "Nyaw" #. name for nyx msgid "Nganyaywana" -msgstr "" +msgstr "Nganyaywana" #. name for nyy msgid "Nyakyusa-Ngonde" -msgstr "" +msgstr "Nyakyusa-Ngonde" #. name for nza msgid "Mbembe; Tigon" @@ -19476,19 +19476,19 @@ msgstr "Mbembe Tigon" #. name for nzb msgid "Njebi" -msgstr "" +msgstr "Njebi" #. name for nzi msgid "Nzima" -msgstr "" +msgstr "Nzema" #. name for nzk msgid "Nzakara" -msgstr "" +msgstr "Nzakara" #. name for nzm msgid "Naga; Zeme" -msgstr "" +msgstr "Naga; Zeme" #. name for nzs msgid "New Zealand Sign Language" @@ -19500,7 +19500,7 @@ msgstr "Teke; Nzikou" #. name for nzy msgid "Nzakambay" -msgstr "" +msgstr "Nzakambay" #. name for nzz msgid "Dogon; Nanga Dama" @@ -19508,11 +19508,11 @@ msgstr "Dogon; Nanga Dama" #. name for oaa msgid "Orok" -msgstr "" +msgstr "Orok" #. name for oac msgid "Oroch" -msgstr "" +msgstr "Orotx" #. name for oar msgid "Aramaic; Old (up to 700 BCE)" @@ -19524,7 +19524,7 @@ msgstr "" #. name for obi msgid "Obispeño" -msgstr "" +msgstr "Obispeño" #. name for obk msgid "Bontok; Southern" @@ -19532,7 +19532,7 @@ msgstr "Bontoc; meridional" #. name for obl msgid "Oblo" -msgstr "" +msgstr "Oblo" #. name for obm msgid "Moabite" @@ -19552,11 +19552,11 @@ msgstr "Bretó; antic" #. name for obu msgid "Obulom" -msgstr "" +msgstr "Obulom" #. name for oca msgid "Ocaina" -msgstr "" +msgstr "Ocaina" #. name for och msgid "Chinese; Old" @@ -19576,11 +19576,11 @@ msgstr "Matlazinca; Atzingo" #. name for oda msgid "Odut" -msgstr "" +msgstr "Odut" #. name for odk msgid "Od" -msgstr "" +msgstr "Od" #. name for odt msgid "Dutch; Old" @@ -19588,11 +19588,11 @@ msgstr "Holandès; antic" #. name for odu msgid "Odual" -msgstr "" +msgstr "Odual" #. name for ofo msgid "Ofo" -msgstr "" +msgstr "Ofo" #. name for ofs msgid "Frisian; Old" @@ -19604,11 +19604,11 @@ msgstr "" #. name for ogb msgid "Ogbia" -msgstr "" +msgstr "Ogbia" #. name for ogc msgid "Ogbah" -msgstr "" +msgstr "Ogbah" #. name for oge msgid "Georgian; Old" @@ -19616,7 +19616,7 @@ msgstr "" #. name for ogg msgid "Ogbogolo" -msgstr "" +msgstr "Ogbogolo" #. name for ogo msgid "Khana" @@ -19624,11 +19624,11 @@ msgstr "" #. name for ogu msgid "Ogbronuagum" -msgstr "" +msgstr "Ogbronuagum" #. name for oht msgid "Hittite; Old" -msgstr "" +msgstr "Hittita; antic" #. name for ohu msgid "Hungarian; Old" @@ -19636,27 +19636,27 @@ msgstr "Hongarès; antic" #. name for oia msgid "Oirata" -msgstr "" +msgstr "Oirata" #. name for oin msgid "One; Inebu" -msgstr "" +msgstr "Oneià; Inebu" #. name for ojb msgid "Ojibwa; Northwestern" -msgstr "" +msgstr "Ojibwa; Nordoccidental" #. name for ojc msgid "Ojibwa; Central" -msgstr "" +msgstr "Ojibwa; Central" #. name for ojg msgid "Ojibwa; Eastern" -msgstr "" +msgstr "Ojibwa; Oriental" #. name for oji msgid "Ojibwa" -msgstr "" +msgstr "Ojibwa; Occidental" #. name for ojp msgid "Japanese; Old" @@ -19664,11 +19664,11 @@ msgstr "Japonès; antic" #. name for ojs msgid "Ojibwa; Severn" -msgstr "" +msgstr "Ojibwa; Severn" #. name for ojv msgid "Ontong Java" -msgstr "" +msgstr "Ontong Java" #. name for ojw msgid "Ojibwa; Western" @@ -19676,19 +19676,19 @@ msgstr "" #. name for oka msgid "Okanagan" -msgstr "" +msgstr "Colville-Okanagà" #. name for okb msgid "Okobo" -msgstr "" +msgstr "Okobo" #. name for okd msgid "Okodia" -msgstr "" +msgstr "Okodia" #. name for oke msgid "Okpe (Southwestern Edo)" -msgstr "" +msgstr "Okpe" #. name for okh msgid "Koresh-e Rostam" @@ -19696,15 +19696,15 @@ msgstr "" #. name for oki msgid "Okiek" -msgstr "" +msgstr "Okiek" #. name for okj msgid "Oko-Juwoi" -msgstr "" +msgstr "Oko-Juwoi" #. name for okk msgid "One; Kwamtim" -msgstr "" +msgstr "Oneià; Kwamtim" #. name for okl msgid "Kentish Sign Language; Old" @@ -19716,7 +19716,7 @@ msgstr "" #. name for okn msgid "Oki-No-Erabu" -msgstr "" +msgstr "Oki-No-Erabu" #. name for oko msgid "Korean; Old (3rd-9th cent.)" @@ -19728,19 +19728,19 @@ msgstr "" #. name for oks msgid "Oko-Eni-Osayen" -msgstr "" +msgstr "Oko-Eni-Osayen" #. name for oku msgid "Oku" -msgstr "" +msgstr "Oku" #. name for okv msgid "Orokaiva" -msgstr "" +msgstr "Orokaiwa" #. name for okx msgid "Okpe (Northwestern Edo)" -msgstr "" +msgstr "Okpe-Idesa-Akuku; Okpe" #. name for ola msgid "Walungge" @@ -19752,11 +19752,11 @@ msgstr "" #. name for ole msgid "Olekha" -msgstr "" +msgstr "Olekha" #. name for olm msgid "Oloma" -msgstr "" +msgstr "Oloma" #. name for olo msgid "Livvi" @@ -19768,7 +19768,7 @@ msgstr "" #. name for oma msgid "Omaha-Ponca" -msgstr "" +msgstr "Omaha-Ponca" #. name for omb msgid "Ambae; East" @@ -19780,23 +19780,23 @@ msgstr "" #. name for ome msgid "Omejes" -msgstr "" +msgstr "Omejes" #. name for omg msgid "Omagua" -msgstr "" +msgstr "Omagua" #. name for omi msgid "Omi" -msgstr "" +msgstr "Omi" #. name for omk msgid "Omok" -msgstr "" +msgstr "Omok" #. name for oml msgid "Ombo" -msgstr "" +msgstr "Ombo" #. name for omn msgid "Minoan" @@ -19816,11 +19816,11 @@ msgstr "" #. name for omt msgid "Omotik" -msgstr "" +msgstr "Omotik" #. name for omu msgid "Omurano" -msgstr "" +msgstr "Omurano" #. name for omw msgid "Tairora; South" @@ -19832,7 +19832,7 @@ msgstr "" #. name for ona msgid "Ona" -msgstr "" +msgstr "Ona" #. name for onb msgid "Lingao" @@ -19840,31 +19840,31 @@ msgstr "" #. name for one msgid "Oneida" -msgstr "" +msgstr "Oneida" #. name for ong msgid "Olo" -msgstr "" +msgstr "Olo" #. name for oni msgid "Onin" -msgstr "" +msgstr "Onin" #. name for onj msgid "Onjob" -msgstr "" +msgstr "Onjob" #. name for onk msgid "One; Kabore" -msgstr "" +msgstr "Oneià; Kabore" #. name for onn msgid "Onobasulu" -msgstr "" +msgstr "Onobasulu" #. name for ono msgid "Onondaga" -msgstr "" +msgstr "Onondaga" #. name for onp msgid "Sartang" @@ -19872,15 +19872,15 @@ msgstr "" #. name for onr msgid "One; Northern" -msgstr "" +msgstr "Oneià; Septentrional" #. name for ons msgid "Ono" -msgstr "" +msgstr "Ono" #. name for ont msgid "Ontenu" -msgstr "" +msgstr "Ontenu" #. name for onu msgid "Unua" @@ -19900,23 +19900,23 @@ msgstr "" #. name for oog msgid "Ong" -msgstr "" +msgstr "Ong" #. name for oon msgid "Önge" -msgstr "" +msgstr "Onge" #. name for oor msgid "Oorlams" -msgstr "" +msgstr "Oorlams" #. name for oos msgid "Ossetic; Old" -msgstr "" +msgstr "Osset" #. name for opa msgid "Okpamheri" -msgstr "" +msgstr "Okpamheri" #. name for opk msgid "Kopkaka" @@ -19924,39 +19924,39 @@ msgstr "" #. name for opm msgid "Oksapmin" -msgstr "" +msgstr "Oksapmin" #. name for opo msgid "Opao" -msgstr "" +msgstr "Opao" #. name for opt msgid "Opata" -msgstr "" +msgstr "Opata" #. name for opy msgid "Ofayé" -msgstr "" +msgstr "Opaie" #. name for ora msgid "Oroha" -msgstr "" +msgstr "Oroha" #. name for orc msgid "Orma" -msgstr "" +msgstr "Orma" #. name for ore msgid "Orejón" -msgstr "" +msgstr "Orejon" #. name for org msgid "Oring" -msgstr "" +msgstr "Oring" #. name for orh msgid "Oroqen" -msgstr "" +msgstr "Orotxen" #. name for ori msgid "Oriya" @@ -19968,19 +19968,19 @@ msgstr "Oromo" #. name for orn msgid "Orang Kanaq" -msgstr "" +msgstr "Orang; Kanaq" #. name for oro msgid "Orokolo" -msgstr "" +msgstr "Orocolo" #. name for orr msgid "Oruma" -msgstr "" +msgstr "Oruma" #. name for ors msgid "Orang Seletar" -msgstr "" +msgstr "Orang; Seletar" #. name for ort msgid "Oriya; Adivasi" @@ -19988,7 +19988,7 @@ msgstr "Oriya; Adivasi" #. name for oru msgid "Ormuri" -msgstr "" +msgstr "Ormuri" #. name for orv msgid "Russian; Old" @@ -19996,31 +19996,31 @@ msgstr "Rus; antic" #. name for orw msgid "Oro Win" -msgstr "" +msgstr "Oro Win" #. name for orx msgid "Oro" -msgstr "" +msgstr "Oro" #. name for orz msgid "Ormu" -msgstr "" +msgstr "Ormu" #. name for osa msgid "Osage" -msgstr "" +msgstr "Osage" #. name for osc msgid "Oscan" -msgstr "" +msgstr "Osc" #. name for osi msgid "Osing" -msgstr "" +msgstr "Osing" #. name for oso msgid "Ososo" -msgstr "" +msgstr "Ososo" #. name for osp msgid "Spanish; Old" @@ -20028,15 +20028,15 @@ msgstr "Espanyol; antic" #. name for oss msgid "Ossetian" -msgstr "" +msgstr "Osset" #. name for ost msgid "Osatu" -msgstr "" +msgstr "Osatu" #. name for osu msgid "One; Southern" -msgstr "" +msgstr "One; Meridional" #. name for osx msgid "Saxon; Old" @@ -20052,15 +20052,15 @@ msgstr "" #. name for otd msgid "Ot Danum" -msgstr "" +msgstr "Dohoi" #. name for ote msgid "Otomi; Mezquital" -msgstr "" +msgstr "Otomí; Mezquital" #. name for oti msgid "Oti" -msgstr "" +msgstr "Oti" #. name for otk msgid "Turkish; Old" @@ -20068,43 +20068,43 @@ msgstr "Turc; antic" #. name for otl msgid "Otomi; Tilapa" -msgstr "" +msgstr "Otomí; Tilapa" #. name for otm msgid "Otomi; Eastern Highland" -msgstr "" +msgstr "Otomí; Oriental" #. name for otn msgid "Otomi; Tenango" -msgstr "" +msgstr "Otomí; Tenango" #. name for otq msgid "Otomi; Querétaro" -msgstr "" +msgstr "Otomí; Queretaro" #. name for otr msgid "Otoro" -msgstr "" +msgstr "Otoro" #. name for ots msgid "Otomi; Estado de México" -msgstr "" +msgstr "Otomí; Estat de Mèxic" #. name for ott msgid "Otomi; Temoaya" -msgstr "" +msgstr "Otomí; Temoaya" #. name for otu msgid "Otuke" -msgstr "" +msgstr "Otuke" #. name for otw msgid "Ottawa" -msgstr "" +msgstr "Ottawa" #. name for otx msgid "Otomi; Texcatepec" -msgstr "" +msgstr "Otomí; Texcatepec" #. name for oty msgid "Tamil; Old" @@ -20112,7 +20112,7 @@ msgstr "" #. name for otz msgid "Otomi; Ixtenco" -msgstr "" +msgstr "Otomí; Ixtenc" #. name for oua msgid "Tagargrent" @@ -20124,7 +20124,7 @@ msgstr "" #. name for oue msgid "Oune" -msgstr "" +msgstr "Oune" #. name for oui msgid "Uighur; Old" @@ -20132,15 +20132,15 @@ msgstr "" #. name for oum msgid "Ouma" -msgstr "" +msgstr "Ouma" #. name for oun msgid "!O!ung" -msgstr "" +msgstr "Oung" #. name for owi msgid "Owiniga" -msgstr "" +msgstr "Owiniga" #. name for owl msgid "Welsh; Old" @@ -20148,11 +20148,11 @@ msgstr "Gal·lès; antic" #. name for oyb msgid "Oy" -msgstr "" +msgstr "Oy" #. name for oyd msgid "Oyda" -msgstr "" +msgstr "Oyda" #. name for oym msgid "Wayampi" @@ -20160,7 +20160,7 @@ msgstr "" #. name for oyy msgid "Oya'oya" -msgstr "" +msgstr "Oya'oya" #. name for ozm msgid "Koonzime" @@ -20168,27 +20168,27 @@ msgstr "" #. name for pab msgid "Parecís" -msgstr "" +msgstr "Pareci" #. name for pac msgid "Pacoh" -msgstr "" +msgstr "Pacoh" #. name for pad msgid "Paumarí" -msgstr "" +msgstr "Paumarí" #. name for pae msgid "Pagibete" -msgstr "" +msgstr "Pagibete" #. name for paf msgid "Paranawát" -msgstr "" +msgstr "Paranawat" #. name for pag msgid "Pangasinan" -msgstr "" +msgstr "Pangasi" #. name for pah msgid "Tenharim" @@ -20196,19 +20196,19 @@ msgstr "" #. name for pai msgid "Pe" -msgstr "" +msgstr "Pe" #. name for pak msgid "Parakanã" -msgstr "" +msgstr "Akwawa; Parakanà" #. name for pal msgid "Pahlavi" -msgstr "" +msgstr "Pahlavi" #. name for pam msgid "Pampanga" -msgstr "" +msgstr "Pampangà" #. name for pan msgid "Panjabi" @@ -20220,63 +20220,63 @@ msgstr "" #. name for pap msgid "Papiamento" -msgstr "" +msgstr "Papiament" #. name for paq msgid "Parya" -msgstr "" +msgstr "Parya" #. name for par msgid "Panamint" -msgstr "" +msgstr "Panamint" #. name for pas msgid "Papasena" -msgstr "" +msgstr "Papasena" #. name for pat msgid "Papitalai" -msgstr "" +msgstr "Papitalai" #. name for pau msgid "Palauan" -msgstr "" +msgstr "Palavà" #. name for pav msgid "Pakaásnovos" -msgstr "" +msgstr "Pakaa Nova" #. name for paw msgid "Pawnee" -msgstr "" +msgstr "Pawnee" #. name for pax msgid "Pankararé" -msgstr "" +msgstr "Pankararé" #. name for pay msgid "Pech" -msgstr "" +msgstr "Pech" #. name for paz msgid "Pankararú" -msgstr "" +msgstr "Pankarurú" #. name for pbb msgid "Páez" -msgstr "" +msgstr "Páez" #. name for pbc msgid "Patamona" -msgstr "" +msgstr "Patamona" #. name for pbe msgid "Popoloca; Mezontla" -msgstr "" +msgstr "Popoloca; Mezontla" #. name for pbf msgid "Popoloca; Coyotepec" -msgstr "" +msgstr "Popoloca; Coyotepec" #. name for pbg msgid "Paraujano" @@ -20288,7 +20288,7 @@ msgstr "" #. name for pbi msgid "Parkwa" -msgstr "" +msgstr "Parkwa" #. name for pbl msgid "Mak (Nigeria)" @@ -20300,7 +20300,7 @@ msgstr "" #. name for pbo msgid "Papel" -msgstr "" +msgstr "Papel" #. name for pbp msgid "Badyara" @@ -20336,7 +20336,7 @@ msgstr "" #. name for pca msgid "Popoloca; Santa Inés Ahuatempan" -msgstr "" +msgstr "Popoloca; Ahuatempan" #. name for pcb msgid "Pear" @@ -20408,7 +20408,7 @@ msgstr "" #. name for pdc msgid "German; Pennsylvania" -msgstr "Alemany; Pennsylvania" +msgstr "Alemany; Pensilvàmia" #. name for pdi msgid "Pa Di" @@ -20832,7 +20832,7 @@ msgstr "Senufo; Palaka" #. name for pls msgid "Popoloca; San Marcos Tlalcoyalco" -msgstr "" +msgstr "Popoloca; Tlalcoyalc" #. name for plt msgid "Malagasy; Plateau" @@ -20936,7 +20936,7 @@ msgstr "Miwok; plana" #. name for pmx msgid "Naga; Poumei" -msgstr "" +msgstr "Naga; Poumei" #. name for pmy msgid "Malay; Papuan" @@ -21040,7 +21040,7 @@ msgstr "" #. name for poe msgid "Popoloca; San Juan Atzingo" -msgstr "" +msgstr "Popoloca; Atzingo" #. name for pof msgid "Poke" @@ -21104,7 +21104,7 @@ msgstr "" #. name for pow msgid "Popoloca; San Felipe Otlaltepec" -msgstr "" +msgstr "Popoloca; Otlaltepec" #. name for pox msgid "Polabian" @@ -21160,7 +21160,7 @@ msgstr "" #. name for pps msgid "Popoloca; San Luís Temalacayuca" -msgstr "" +msgstr "Popoloca; Temalacayuca" #. name for ppt msgid "Pare" @@ -21480,7 +21480,7 @@ msgstr "" #. name for puz msgid "Naga; Purum" -msgstr "" +msgstr "Naga; Purum" #. name for pwa msgid "Pawaia" @@ -21548,7 +21548,7 @@ msgstr "" #. name for pzn msgid "Naga; Para" -msgstr "" +msgstr "Naga; Para" #. name for qua msgid "Quapaw" @@ -22372,7 +22372,7 @@ msgstr "" #. name for rwr msgid "Marwari (India)" -msgstr "" +msgstr "Marwari; Índia" #. name for ryn msgid "Amami-Oshima; Northern" @@ -22552,7 +22552,7 @@ msgstr "" #. name for sbr msgid "Sembakung Murut" -msgstr "" +msgstr "Sembakung Murut" #. name for sbs msgid "Subiya" @@ -23304,7 +23304,7 @@ msgstr "Llenguatge de signes suís-italià" #. name for slg msgid "Selungai Murut" -msgstr "" +msgstr "Selungai Murut" #. name for slh msgid "Salish; Southern Puget Sound" @@ -23832,7 +23832,7 @@ msgstr "" #. name for srk msgid "Serudung Murut" -msgstr "" +msgstr "Serudung Murut" #. name for srl msgid "Isirawa" @@ -25128,7 +25128,7 @@ msgstr "" #. name for tih msgid "Murut; Timugon" -msgstr "" +msgstr "Timugon Murut" #. name for tii msgid "Tiene" @@ -25876,7 +25876,7 @@ msgstr "" #. name for tro msgid "Naga; Tarao" -msgstr "" +msgstr "Naga; Tarao" #. name for trp msgid "Kok Borok" @@ -26256,7 +26256,7 @@ msgstr "" #. name for tvt msgid "Naga; Tutsa" -msgstr "" +msgstr "Naga; Tutsa" #. name for tvw msgid "Sedoa" @@ -26744,7 +26744,7 @@ msgstr "" #. name for umn msgid "Naga; Makyan" -msgstr "" +msgstr "Naga; Makyan" #. name for umo msgid "Umotína" @@ -27224,7 +27224,7 @@ msgstr "Koraga; Mudu" #. name for vme msgid "Masela; East" -msgstr "" +msgstr "Masela; Oriental" #. name for vmf msgid "Mainfränkisch" @@ -27856,7 +27856,7 @@ msgstr "" #. name for wmm msgid "Maiwa (Indonesia)" -msgstr "" +msgstr "Maiwa (Indonèsia)" #. name for wmn msgid "Waamwang" @@ -28784,7 +28784,7 @@ msgstr "" #. name for xmz msgid "Mori Bawah" -msgstr "" +msgstr "Mori; Bawah" #. name for xna msgid "North Arabian; Ancient" @@ -28796,7 +28796,7 @@ msgstr "" #. name for xng msgid "Mongolian; Middle" -msgstr "" +msgstr "Mongol; Pre-clàssic" #. name for xnh msgid "Kuanhua" @@ -29568,7 +29568,7 @@ msgstr "" #. name for yim msgid "Naga; Yimchungru" -msgstr "" +msgstr "Naga; Yimchungru" #. name for yin msgid "Yinchia" @@ -29600,7 +29600,7 @@ msgstr "" #. name for yiv msgid "Nisu; Northern" -msgstr "" +msgstr "Yi; Eshan-Xinping" #. name for yix msgid "Yi; Axi" diff --git a/setup/iso_639/es.po b/setup/iso_639/es.po index 6e5893efc9..93582e18ab 100644 --- a/setup/iso_639/es.po +++ b/setup/iso_639/es.po @@ -9,14 +9,14 @@ msgstr "" "Project-Id-Version: calibre\n" "Report-Msgid-Bugs-To: FULL NAME \n" "POT-Creation-Date: 2011-11-25 14:01+0000\n" -"PO-Revision-Date: 2012-08-15 10:30+0000\n" +"PO-Revision-Date: 2012-12-28 09:13+0000\n" "Last-Translator: Jellby \n" "Language-Team: Español; Castellano <>\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" -"X-Launchpad-Export-Date: 2012-08-16 04:40+0000\n" -"X-Generator: Launchpad (build 15810)\n" +"X-Launchpad-Export-Date: 2012-12-29 05:00+0000\n" +"X-Generator: Launchpad (build 16378)\n" #. name for aaa msgid "Ghotuo" @@ -9584,27 +9584,27 @@ msgstr "Holikachuk" #. name for hoj msgid "Hadothi" -msgstr "" +msgstr "Hadoti" #. name for hol msgid "Holu" -msgstr "" +msgstr "Holu" #. name for hom msgid "Homa" -msgstr "" +msgstr "Homa" #. name for hoo msgid "Holoholo" -msgstr "" +msgstr "Holoholo" #. name for hop msgid "Hopi" -msgstr "" +msgstr "Hopi" #. name for hor msgid "Horo" -msgstr "" +msgstr "Horo" #. name for hos msgid "Ho Chi Minh City Sign Language" @@ -9612,27 +9612,27 @@ msgstr "Lengua de signos de Ho Chi Minh" #. name for hot msgid "Hote" -msgstr "" +msgstr "Hote" #. name for hov msgid "Hovongan" -msgstr "" +msgstr "Hovongan" #. name for how msgid "Honi" -msgstr "" +msgstr "Honi" #. name for hoy msgid "Holiya" -msgstr "" +msgstr "Holiya" #. name for hoz msgid "Hozo" -msgstr "" +msgstr "Hozo" #. name for hpo msgid "Hpon" -msgstr "" +msgstr "Hpon" #. name for hps msgid "Hawai'i Pidgin Sign Language" @@ -9640,15 +9640,15 @@ msgstr "Lengua de signos pidyin hawaiana" #. name for hra msgid "Hrangkhol" -msgstr "" +msgstr "Hrangkhol" #. name for hre msgid "Hre" -msgstr "" +msgstr "Hre" #. name for hrk msgid "Haruku" -msgstr "" +msgstr "Haruku" #. name for hrm msgid "Miao; Horned" @@ -9656,19 +9656,19 @@ msgstr "" #. name for hro msgid "Haroi" -msgstr "" +msgstr "Haroi" #. name for hrr msgid "Horuru" -msgstr "" +msgstr "Horuru" #. name for hrt msgid "Hértevin" -msgstr "" +msgstr "Hértevin" #. name for hru msgid "Hruso" -msgstr "" +msgstr "Hruso" #. name for hrv msgid "Croatian" @@ -11796,7 +11796,7 @@ msgstr "" #. name for khq msgid "Songhay; Koyra Chiini" -msgstr "" +msgstr "Songhay koyra chiini" #. name for khr msgid "Kharia" diff --git a/setup/iso_639/ja.po b/setup/iso_639/ja.po index d7f9fa4a59..d42dd10667 100644 --- a/setup/iso_639/ja.po +++ b/setup/iso_639/ja.po @@ -12,14 +12,14 @@ msgstr "" "Report-Msgid-Bugs-To: Debian iso-codes team \n" "POT-Creation-Date: 2011-11-25 14:01+0000\n" -"PO-Revision-Date: 2011-09-27 15:44+0000\n" -"Last-Translator: IIDA Yosiaki \n" +"PO-Revision-Date: 2012-12-13 13:56+0000\n" +"Last-Translator: Shushi Kurose \n" "Language-Team: Japanese \n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" -"X-Launchpad-Export-Date: 2011-11-26 05:21+0000\n" -"X-Generator: Launchpad (build 14381)\n" +"X-Launchpad-Export-Date: 2012-12-14 05:34+0000\n" +"X-Generator: Launchpad (build 16369)\n" "Language: ja\n" #. name for aaa @@ -86,12 +86,9 @@ msgstr "" msgid "Abnaki; Eastern" msgstr "" -# 以下「国国」は、国立国会図書館のサイト。 -# ジブチ -# マイペディア「ジブチ」の項に「アファル語」 #. name for aar msgid "Afar" -msgstr "アファール語" +msgstr "アファル語" #. name for aas msgid "Aasáx" diff --git a/setup/iso_639/pt_BR.po b/setup/iso_639/pt_BR.po index 0985886f33..55ad09de20 100644 --- a/setup/iso_639/pt_BR.po +++ b/setup/iso_639/pt_BR.po @@ -9,35 +9,35 @@ msgstr "" "Report-Msgid-Bugs-To: Debian iso-codes team \n" "POT-Creation-Date: 2011-11-25 14:01+0000\n" -"PO-Revision-Date: 2011-09-27 16:27+0000\n" -"Last-Translator: Kovid Goyal \n" +"PO-Revision-Date: 2012-12-21 03:31+0000\n" +"Last-Translator: Fábio Malcher Miranda \n" "Language-Team: Brazilian Portuguese\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" -"X-Launchpad-Export-Date: 2011-11-26 05:47+0000\n" -"X-Generator: Launchpad (build 14381)\n" +"X-Launchpad-Export-Date: 2012-12-22 04:59+0000\n" +"X-Generator: Launchpad (build 16378)\n" "Language: \n" #. name for aaa msgid "Ghotuo" -msgstr "" +msgstr "Ghotuo" #. name for aab msgid "Alumu-Tesu" -msgstr "" +msgstr "Alumu-Tesu" #. name for aac msgid "Ari" -msgstr "" +msgstr "Ari" #. name for aad msgid "Amal" -msgstr "" +msgstr "Amal" #. name for aae msgid "Albanian; Arbëreshë" -msgstr "" +msgstr "Albanês; Arbëreshë" #. name for aaf msgid "Aranadan" @@ -45,7 +45,7 @@ msgstr "" #. name for aag msgid "Ambrak" -msgstr "" +msgstr "Ambrak" #. name for aah msgid "Arapesh; Abu'" @@ -53,23 +53,23 @@ msgstr "" #. name for aai msgid "Arifama-Miniafia" -msgstr "" +msgstr "Arifama-Miniafia" #. name for aak msgid "Ankave" -msgstr "" +msgstr "Ankave" #. name for aal msgid "Afade" -msgstr "" +msgstr "Afade" #. name for aam msgid "Aramanik" -msgstr "" +msgstr "Aramaico" #. name for aan msgid "Anambé" -msgstr "" +msgstr "Anambé" #. name for aao msgid "Arabic; Algerian Saharan" @@ -77,7 +77,7 @@ msgstr "" #. name for aap msgid "Arára; Pará" -msgstr "" +msgstr "Arara; Pará" #. name for aaq msgid "Abnaki; Eastern" @@ -89,7 +89,7 @@ msgstr "" #. name for aas msgid "Aasáx" -msgstr "" +msgstr "Aasáx" #. name for aat msgid "Albanian; Arvanitika" @@ -97,27 +97,27 @@ msgstr "" #. name for aau msgid "Abau" -msgstr "" +msgstr "Abau" #. name for aaw msgid "Solong" -msgstr "" +msgstr "Solong" #. name for aax msgid "Mandobo Atas" -msgstr "" +msgstr "Mandobo Atas" #. name for aaz msgid "Amarasi" -msgstr "" +msgstr "Amarasi" #. name for aba msgid "Abé" -msgstr "" +msgstr "Abé" #. name for abb msgid "Bankon" -msgstr "" +msgstr "Bankon" #. name for abc msgid "Ayta; Ambala" @@ -125,7 +125,7 @@ msgstr "" #. name for abd msgid "Manide" -msgstr "" +msgstr "Manide" #. name for abe msgid "Abnaki; Western" @@ -145,11 +145,11 @@ msgstr "" #. name for abi msgid "Abidji" -msgstr "" +msgstr "Abidji" #. name for abj msgid "Aka-Bea" -msgstr "" +msgstr "Aka-Bea" #. name for abk msgid "Abkhazian" @@ -157,19 +157,19 @@ msgstr "" #. name for abl msgid "Lampung Nyo" -msgstr "" +msgstr "Lampung Nyo" #. name for abm msgid "Abanyom" -msgstr "" +msgstr "Abanyom" #. name for abn msgid "Abua" -msgstr "" +msgstr "Abua" #. name for abo msgid "Abon" -msgstr "" +msgstr "Abon" #. name for abp msgid "Ayta; Abellen" @@ -177,11 +177,11 @@ msgstr "" #. name for abq msgid "Abaza" -msgstr "" +msgstr "Abaza" #. name for abr msgid "Abron" -msgstr "" +msgstr "Abron" #. name for abs msgid "Malay; Ambonese" @@ -189,11 +189,11 @@ msgstr "" #. name for abt msgid "Ambulas" -msgstr "" +msgstr "Ambulas" #. name for abu msgid "Abure" -msgstr "" +msgstr "Abure" #. name for abv msgid "Arabic; Baharna" @@ -201,15 +201,15 @@ msgstr "" #. name for abw msgid "Pal" -msgstr "" +msgstr "Pal" #. name for abx msgid "Inabaknon" -msgstr "" +msgstr "Inabaknon" #. name for aby msgid "Aneme Wake" -msgstr "" +msgstr "Aneme Wake" #. name for abz msgid "Abui" @@ -225,7 +225,7 @@ msgstr "" #. name for acd msgid "Gikyode" -msgstr "" +msgstr "Gikyode" #. name for ace msgid "Achinese" @@ -241,15 +241,15 @@ msgstr "" #. name for aci msgid "Aka-Cari" -msgstr "" +msgstr "Aka-Cari" #. name for ack msgid "Aka-Kora" -msgstr "" +msgstr "Aka-Kora" #. name for acl msgid "Akar-Bale" -msgstr "" +msgstr "Akar-Bale" #. name for acm msgid "Arabic; Mesopotamian" @@ -257,7 +257,7 @@ msgstr "" #. name for acn msgid "Achang" -msgstr "" +msgstr "Achang" #. name for acp msgid "Acipa; Eastern" @@ -269,23 +269,23 @@ msgstr "" #. name for acr msgid "Achi" -msgstr "" +msgstr "Achi" #. name for acs msgid "Acroá" -msgstr "" +msgstr "Acroá" #. name for act msgid "Achterhoeks" -msgstr "" +msgstr "Achterhoeks" #. name for acu msgid "Achuar-Shiwiar" -msgstr "" +msgstr "Achuar-Shiwiar" #. name for acv msgid "Achumawi" -msgstr "" +msgstr "Achumawi" #. name for acw msgid "Arabic; Hijazi" @@ -301,23 +301,23 @@ msgstr "" #. name for acz msgid "Acheron" -msgstr "" +msgstr "Acheron" #. name for ada msgid "Adangme" -msgstr "" +msgstr "Adangme" #. name for adb msgid "Adabe" -msgstr "" +msgstr "Adabe" #. name for add msgid "Dzodinka" -msgstr "" +msgstr "Dzodinka" #. name for ade msgid "Adele" -msgstr "" +msgstr "Adele" #. name for adf msgid "Arabic; Dhofari" @@ -325,59 +325,59 @@ msgstr "" #. name for adg msgid "Andegerebinha" -msgstr "" +msgstr "Andegerebinha" #. name for adh msgid "Adhola" -msgstr "" +msgstr "Adhola" #. name for adi msgid "Adi" -msgstr "" +msgstr "Adi" #. name for adj msgid "Adioukrou" -msgstr "" +msgstr "Adioukrou" #. name for adl msgid "Galo" -msgstr "" +msgstr "Gaulês" #. name for adn msgid "Adang" -msgstr "" +msgstr "Adang" #. name for ado msgid "Abu" -msgstr "" +msgstr "Abu" #. name for adp msgid "Adap" -msgstr "" +msgstr "Adap" #. name for adq msgid "Adangbe" -msgstr "" +msgstr "Adangbe" #. name for adr msgid "Adonara" -msgstr "" +msgstr "Adonara" #. name for ads msgid "Adamorobe Sign Language" -msgstr "" +msgstr "Idiomas de Sinais Adamorobe" #. name for adt msgid "Adnyamathanha" -msgstr "" +msgstr "Adnyamathanha" #. name for adu msgid "Aduge" -msgstr "" +msgstr "Aduge" #. name for adw msgid "Amundava" -msgstr "" +msgstr "Amundava" #. name for adx msgid "Tibetan; Amdo" @@ -385,11 +385,11 @@ msgstr "" #. name for ady msgid "Adyghe" -msgstr "" +msgstr "Adigue" #. name for adz msgid "Adzera" -msgstr "" +msgstr "Adzera" #. name for aea msgid "Areba" @@ -405,7 +405,7 @@ msgstr "" #. name for aed msgid "Argentine Sign Language" -msgstr "" +msgstr "Idiomas de Sinais Argentino" #. name for aee msgid "Pashayi; Northeast" @@ -413,23 +413,23 @@ msgstr "" #. name for aek msgid "Haeke" -msgstr "" +msgstr "Haeke" #. name for ael msgid "Ambele" -msgstr "" +msgstr "Ambele" #. name for aem msgid "Arem" -msgstr "" +msgstr "Arem" #. name for aen msgid "Armenian Sign Language" -msgstr "" +msgstr "Idiomas de Sinais Americano" #. name for aeq msgid "Aer" -msgstr "" +msgstr "Aer" #. name for aer msgid "Arrernte; Eastern" @@ -437,23 +437,23 @@ msgstr "" #. name for aes msgid "Alsea" -msgstr "" +msgstr "Alsea" #. name for aeu msgid "Akeu" -msgstr "" +msgstr "Akeu" #. name for aew msgid "Ambakich" -msgstr "" +msgstr "Ambakich" #. name for aey msgid "Amele" -msgstr "" +msgstr "Amele" #. name for aez msgid "Aeka" -msgstr "" +msgstr "Aeka" #. name for afb msgid "Arabic; Gulf" @@ -461,11 +461,11 @@ msgstr "" #. name for afd msgid "Andai" -msgstr "" +msgstr "Andai" #. name for afe msgid "Putukwam" -msgstr "" +msgstr "Putukwam" #. name for afg msgid "Afghan Sign Language" @@ -473,27 +473,27 @@ msgstr "" #. name for afh msgid "Afrihili" -msgstr "" +msgstr "Afrihili" #. name for afi msgid "Akrukay" -msgstr "" +msgstr "Akrukay" #. name for afk msgid "Nanubae" -msgstr "" +msgstr "Nanubae" #. name for afn msgid "Defaka" -msgstr "" +msgstr "Defaka" #. name for afo msgid "Eloyi" -msgstr "" +msgstr "Eloyi" #. name for afp msgid "Tapei" -msgstr "" +msgstr "Tapei" #. name for afr msgid "Afrikaans" @@ -505,55 +505,55 @@ msgstr "" #. name for aft msgid "Afitti" -msgstr "" +msgstr "Afitti" #. name for afu msgid "Awutu" -msgstr "" +msgstr "Awutu" #. name for afz msgid "Obokuitai" -msgstr "" +msgstr "Obokuitai" #. name for aga msgid "Aguano" -msgstr "" +msgstr "Aguano" #. name for agb msgid "Legbo" -msgstr "" +msgstr "Legbo" #. name for agc msgid "Agatu" -msgstr "" +msgstr "Agatu" #. name for agd msgid "Agarabi" -msgstr "" +msgstr "Agarabi" #. name for age msgid "Angal" -msgstr "" +msgstr "Angal" #. name for agf msgid "Arguni" -msgstr "" +msgstr "Arguni" #. name for agg msgid "Angor" -msgstr "" +msgstr "Angor" #. name for agh msgid "Ngelima" -msgstr "" +msgstr "Ngelima" #. name for agi msgid "Agariya" -msgstr "" +msgstr "Agariya" #. name for agj msgid "Argobba" -msgstr "" +msgstr "Argobba" #. name for agk msgid "Agta; Isarog" @@ -561,31 +561,31 @@ msgstr "" #. name for agl msgid "Fembe" -msgstr "" +msgstr "Fembe" #. name for agm msgid "Angaataha" -msgstr "" +msgstr "Angaataha" #. name for agn msgid "Agutaynen" -msgstr "" +msgstr "Agutaynen" #. name for ago msgid "Tainae" -msgstr "" +msgstr "Tainae" #. name for agq msgid "Aghem" -msgstr "" +msgstr "Aghem" #. name for agr msgid "Aguaruna" -msgstr "" +msgstr "Aguaruna" #. name for ags msgid "Esimbi" -msgstr "" +msgstr "Esimbi" #. name for agt msgid "Agta; Central Cagayan" @@ -593,7 +593,7 @@ msgstr "" #. name for agu msgid "Aguacateco" -msgstr "" +msgstr "Aguacateco" #. name for agv msgid "Dumagat; Remontado" @@ -601,11 +601,11 @@ msgstr "" #. name for agw msgid "Kahua" -msgstr "" +msgstr "Kahua" #. name for agx msgid "Aghul" -msgstr "" +msgstr "Aghul" #. name for agy msgid "Alta; Southern" @@ -617,19 +617,19 @@ msgstr "" #. name for aha msgid "Ahanta" -msgstr "" +msgstr "Ahanta" #. name for ahb msgid "Axamb" -msgstr "" +msgstr "Axamb" #. name for ahg msgid "Qimant" -msgstr "" +msgstr "Qimant" #. name for ahh msgid "Aghu" -msgstr "" +msgstr "Aghu" #. name for ahi msgid "Aizi; Tiagbamrin" @@ -637,11 +637,11 @@ msgstr "" #. name for ahk msgid "Akha" -msgstr "" +msgstr "Akha" #. name for ahl msgid "Igo" -msgstr "" +msgstr "Igo" #. name for ahm msgid "Aizi; Mobumrin" @@ -649,11 +649,11 @@ msgstr "" #. name for ahn msgid "Àhàn" -msgstr "" +msgstr "Àhàn" #. name for aho msgid "Ahom" -msgstr "" +msgstr "Ahom" #. name for ahp msgid "Aizi; Aproumu" @@ -661,39 +661,39 @@ msgstr "" #. name for ahr msgid "Ahirani" -msgstr "" +msgstr "Ahirani" #. name for ahs msgid "Ashe" -msgstr "" +msgstr "Ashe" #. name for aht msgid "Ahtena" -msgstr "" +msgstr "Ahtena" #. name for aia msgid "Arosi" -msgstr "" +msgstr "Arosi" #. name for aib msgid "Ainu (China)" -msgstr "" +msgstr "Ainu (China)" #. name for aic msgid "Ainbai" -msgstr "" +msgstr "Ainbai" #. name for aid msgid "Alngith" -msgstr "" +msgstr "Alngith" #. name for aie msgid "Amara" -msgstr "" +msgstr "Amara" #. name for aif msgid "Agi" -msgstr "" +msgstr "Agi" #. name for aig msgid "Creole English; Antigua and Barbuda" @@ -701,7 +701,7 @@ msgstr "" #. name for aih msgid "Ai-Cham" -msgstr "" +msgstr "Ai-Cham" #. name for aii msgid "Neo-Aramaic; Assyrian" @@ -709,35 +709,35 @@ msgstr "" #. name for aij msgid "Lishanid Noshan" -msgstr "" +msgstr "Lishanid Noshan" #. name for aik msgid "Ake" -msgstr "" +msgstr "Ake" #. name for ail msgid "Aimele" -msgstr "" +msgstr "Aimele" #. name for aim msgid "Aimol" -msgstr "" +msgstr "Aimol" #. name for ain msgid "Ainu (Japan)" -msgstr "" +msgstr "Ainu (Japão)" #. name for aio msgid "Aiton" -msgstr "" +msgstr "Aiton" #. name for aip msgid "Burumakok" -msgstr "" +msgstr "Burumakok" #. name for aiq msgid "Aimaq" -msgstr "" +msgstr "Aimaq" #. name for air msgid "Airoran" @@ -749,31 +749,31 @@ msgstr "" #. name for ait msgid "Arikem" -msgstr "" +msgstr "Arikem" #. name for aiw msgid "Aari" -msgstr "" +msgstr "Aari" #. name for aix msgid "Aighon" -msgstr "" +msgstr "Aighon" #. name for aiy msgid "Ali" -msgstr "" +msgstr "Ali" #. name for aja msgid "Aja (Sudan)" -msgstr "" +msgstr "Aja (Sudão)" #. name for ajg msgid "Aja (Benin)" -msgstr "" +msgstr "Aja (Benin)" #. name for aji msgid "Ajië" -msgstr "" +msgstr "Ajië" #. name for ajp msgid "Arabic; South Levantine" @@ -789,7 +789,7 @@ msgstr "" #. name for ajw msgid "Ajawa" -msgstr "" +msgstr "Ajawa" #. name for ajz msgid "Karbi; Amri" @@ -801,39 +801,39 @@ msgstr "" #. name for akb msgid "Batak Angkola" -msgstr "" +msgstr "Batak Angkola" #. name for akc msgid "Mpur" -msgstr "" +msgstr "Mpur" #. name for akd msgid "Ukpet-Ehom" -msgstr "" +msgstr "Ukpet-Ehom" #. name for ake msgid "Akawaio" -msgstr "" +msgstr "Akawaio" #. name for akf msgid "Akpa" -msgstr "" +msgstr "Akpa" #. name for akg msgid "Anakalangu" -msgstr "" +msgstr "Anakalangu" #. name for akh msgid "Angal Heneng" -msgstr "" +msgstr "Angal Heneng" #. name for aki msgid "Aiome" -msgstr "" +msgstr "Aiome" #. name for akj msgid "Aka-Jeru" -msgstr "" +msgstr "Aka-Jeru" #. name for akk msgid "Akkadian" @@ -845,51 +845,51 @@ msgstr "" #. name for akm msgid "Aka-Bo" -msgstr "" +msgstr "Aka-Bo" #. name for ako msgid "Akurio" -msgstr "" +msgstr "Akurio" #. name for akp msgid "Siwu" -msgstr "" +msgstr "Siwu" #. name for akq msgid "Ak" -msgstr "" +msgstr "Ak" #. name for akr msgid "Araki" -msgstr "" +msgstr "Araki" #. name for aks msgid "Akaselem" -msgstr "" +msgstr "Akaselem" #. name for akt msgid "Akolet" -msgstr "" +msgstr "Akolet" #. name for aku msgid "Akum" -msgstr "" +msgstr "Akum" #. name for akv msgid "Akhvakh" -msgstr "" +msgstr "Akhvakh" #. name for akw msgid "Akwa" -msgstr "" +msgstr "Akwa" #. name for akx msgid "Aka-Kede" -msgstr "" +msgstr "Aka-Kede" #. name for aky msgid "Aka-Kol" -msgstr "" +msgstr "Aka-Kol" #. name for akz msgid "Alabama" @@ -897,15 +897,15 @@ msgstr "" #. name for ala msgid "Alago" -msgstr "" +msgstr "Alago" #. name for alc msgid "Qawasqar" -msgstr "" +msgstr "Qawasqar" #. name for ald msgid "Alladian" -msgstr "" +msgstr "Alladian" #. name for ale msgid "Aleut" @@ -913,19 +913,19 @@ msgstr "" #. name for alf msgid "Alege" -msgstr "" +msgstr "Alege" #. name for alh msgid "Alawa" -msgstr "" +msgstr "Alawa" #. name for ali msgid "Amaimon" -msgstr "" +msgstr "Amaimon" #. name for alj msgid "Alangan" -msgstr "" +msgstr "Alangan" #. name for alk msgid "Alak" @@ -933,11 +933,11 @@ msgstr "" #. name for all msgid "Allar" -msgstr "" +msgstr "Allar" #. name for alm msgid "Amblong" -msgstr "" +msgstr "Amblong" #. name for aln msgid "Albanian; Gheg" @@ -945,19 +945,19 @@ msgstr "" #. name for alo msgid "Larike-Wakasihu" -msgstr "" +msgstr "Larike-Wakasihu" #. name for alp msgid "Alune" -msgstr "" +msgstr "Alune" #. name for alq msgid "Algonquin" -msgstr "" +msgstr "Algonquin" #. name for alr msgid "Alutor" -msgstr "" +msgstr "Alutor" #. name for als msgid "Albanian; Tosk" @@ -969,11 +969,11 @@ msgstr "" #. name for alu msgid "'Are'are" -msgstr "" +msgstr "'Are'are" #. name for alw msgid "Alaba-K’abeena" -msgstr "" +msgstr "Alaba-K’abeena" #. name for alx msgid "Amol" @@ -981,35 +981,35 @@ msgstr "" #. name for aly msgid "Alyawarr" -msgstr "" +msgstr "Alyawarr" #. name for alz msgid "Alur" -msgstr "" +msgstr "Alur" #. name for ama msgid "Amanayé" -msgstr "" +msgstr "Amanayé" #. name for amb msgid "Ambo" -msgstr "" +msgstr "Ambo" #. name for amc msgid "Amahuaca" -msgstr "" +msgstr "Amahuaca" #. name for ame msgid "Yanesha'" -msgstr "" +msgstr "Yanesha'" #. name for amf msgid "Hamer-Banna" -msgstr "" +msgstr "Hamer-Banna" #. name for amg msgid "Amarag" -msgstr "" +msgstr "Amarag" #. name for amh msgid "Amharic" @@ -1017,35 +1017,35 @@ msgstr "" #. name for ami msgid "Amis" -msgstr "" +msgstr "Amis" #. name for amj msgid "Amdang" -msgstr "" +msgstr "Amdang" #. name for amk msgid "Ambai" -msgstr "" +msgstr "Ambai" #. name for aml msgid "War-Jaintia" -msgstr "" +msgstr "Guerra-Jaintia" #. name for amm msgid "Ama (Papua New Guinea)" -msgstr "" +msgstr "Ama (Papua Nova Guiné)" #. name for amn msgid "Amanab" -msgstr "" +msgstr "Amanab" #. name for amo msgid "Amo" -msgstr "" +msgstr "Amo" #. name for amp msgid "Alamblak" -msgstr "" +msgstr "Alamblak" #. name for amq msgid "Amahai" @@ -1053,7 +1053,7 @@ msgstr "" #. name for amr msgid "Amarakaeri" -msgstr "" +msgstr "Amarakaeri" #. name for ams msgid "Amami-Oshima; Southern" @@ -1061,7 +1061,7 @@ msgstr "" #. name for amt msgid "Amto" -msgstr "" +msgstr "Amto" #. name for amu msgid "Amuzgo; Guerrero" @@ -1069,7 +1069,7 @@ msgstr "" #. name for amv msgid "Ambelau" -msgstr "" +msgstr "Ambelau" #. name for amw msgid "Neo-Aramaic; Western" @@ -1077,7 +1077,7 @@ msgstr "" #. name for amx msgid "Anmatyerre" -msgstr "" +msgstr "Anmatyerre" #. name for amy msgid "Ami" @@ -1085,31 +1085,31 @@ msgstr "" #. name for amz msgid "Atampaya" -msgstr "" +msgstr "Atampaya" #. name for ana msgid "Andaqui" -msgstr "" +msgstr "Andaqui" #. name for anb msgid "Andoa" -msgstr "" +msgstr "Andoa" #. name for anc msgid "Ngas" -msgstr "" +msgstr "Ngas" #. name for and msgid "Ansus" -msgstr "" +msgstr "Ansus" #. name for ane msgid "Xârâcùù" -msgstr "" +msgstr "Xârâcùù" #. name for anf msgid "Animere" -msgstr "" +msgstr "Animere" #. name for ang msgid "English; Old (ca. 450-1100)" @@ -1117,71 +1117,71 @@ msgstr "" #. name for anh msgid "Nend" -msgstr "" +msgstr "Nend" #. name for ani msgid "Andi" -msgstr "" +msgstr "Andi" #. name for anj msgid "Anor" -msgstr "" +msgstr "Anor" #. name for ank msgid "Goemai" -msgstr "" +msgstr "Goemai" #. name for anl msgid "Anu" -msgstr "" +msgstr "Anu" #. name for anm msgid "Anal" -msgstr "" +msgstr "Anal" #. name for ann msgid "Obolo" -msgstr "" +msgstr "Obolo" #. name for ano msgid "Andoque" -msgstr "" +msgstr "Andoque" #. name for anp msgid "Angika" -msgstr "" +msgstr "Angika" #. name for anq msgid "Jarawa (India)" -msgstr "" +msgstr "Jarawa (Índia)" #. name for anr msgid "Andh" -msgstr "" +msgstr "Andh" #. name for ans msgid "Anserma" -msgstr "" +msgstr "Anserma" #. name for ant msgid "Antakarinya" -msgstr "" +msgstr "Antakarinya" #. name for anu msgid "Anuak" -msgstr "" +msgstr "Anuak" #. name for anv msgid "Denya" -msgstr "" +msgstr "Denya" #. name for anw msgid "Anaang" -msgstr "" +msgstr "Anaang" #. name for anx msgid "Andra-Hus" -msgstr "" +msgstr "Andra-Hus" #. name for any msgid "Anyin" @@ -1189,7 +1189,7 @@ msgstr "" #. name for anz msgid "Anem" -msgstr "" +msgstr "Anem" #. name for aoa msgid "Angolar" @@ -1197,27 +1197,27 @@ msgstr "" #. name for aob msgid "Abom" -msgstr "" +msgstr "Abom" #. name for aoc msgid "Pemon" -msgstr "" +msgstr "Pemon" #. name for aod msgid "Andarum" -msgstr "" +msgstr "Andarum" #. name for aoe msgid "Angal Enen" -msgstr "" +msgstr "Angal Enen" #. name for aof msgid "Bragat" -msgstr "" +msgstr "Bragat" #. name for aog msgid "Angoram" -msgstr "" +msgstr "Angoram" #. name for aoh msgid "Arma" @@ -2021,7 +2021,7 @@ msgstr "" #. name for aze msgid "Azerbaijani" -msgstr "Azerbaidjani" +msgstr "Azerbaijano" #. name for azg msgid "Amuzgo; San Pedro Amuzgos" diff --git a/setup/translations.py b/setup/translations.py index 3942953179..e0a512d21c 100644 --- a/setup/translations.py +++ b/setup/translations.py @@ -227,9 +227,22 @@ class GetTranslations(Translations): # {{{ ans.append(line.split()[-1]) return ans + def resolve_conflicts(self): + conflict = False + for line in subprocess.check_output(['bzr', 'status']).splitlines(): + if line == 'conflicts:': + conflict = True + break + if not conflict: + raise Exception('bzr merge failed and no conflicts found') + subprocess.check_call(['bzr', 'resolve', '--take-other']) + def run(self, opts): if not self.modified_translations: - subprocess.check_call(['bzr', 'merge', self.BRANCH]) + try: + subprocess.check_call(['bzr', 'merge', self.BRANCH]) + except subprocess.CalledProcessError: + self.resolve_conflicts() self.check_for_errors() if self.modified_translations: diff --git a/src/calibre/constants.py b/src/calibre/constants.py index 164f37314a..aa96dc0553 100644 --- a/src/calibre/constants.py +++ b/src/calibre/constants.py @@ -4,7 +4,7 @@ __license__ = 'GPL v3' __copyright__ = '2008, Kovid Goyal kovid@kovidgoyal.net' __docformat__ = 'restructuredtext en' __appname__ = u'calibre' -numeric_version = (0, 9, 8) +numeric_version = (0, 9, 14) __version__ = u'.'.join(map(unicode, numeric_version)) __author__ = u"Kovid Goyal " @@ -100,6 +100,7 @@ class Plugins(collections.Mapping): 'freetype', 'woff', 'unrar', + 'qt_hack', ] if iswindows: plugins.extend(['winutil', 'wpd', 'winfonts']) diff --git a/src/calibre/customize/builtins.py b/src/calibre/customize/builtins.py index 61f3031b8e..20552d5d9c 100644 --- a/src/calibre/customize/builtins.py +++ b/src/calibre/customize/builtins.py @@ -661,7 +661,7 @@ from calibre.devices.nuut2.driver import NUUT2 from calibre.devices.iriver.driver import IRIVER_STORY from calibre.devices.binatone.driver import README from calibre.devices.hanvon.driver import (N516, EB511, ALEX, AZBOOKA, THEBOOK, - LIBREAIR, ODYSSEY) + LIBREAIR, ODYSSEY, KIBANO) from calibre.devices.edge.driver import EDGE from calibre.devices.teclast.driver import (TECLAST_K3, NEWSMY, IPAPYRUS, SOVOS, PICO, SUNSTECH_EB700, ARCHOS7O, STASH, WEXLER) @@ -712,7 +712,7 @@ plugins += [ BOOQ, EB600, README, - N516, + N516, KIBANO, THEBOOK, LIBREAIR, EB511, ELONEX, @@ -1471,9 +1471,9 @@ class StoreLegimiStore(StoreBase): affiliate = True class StoreLibreDEStore(StoreBase): - name = 'Libri DE' + name = 'ebook.de' author = 'Charles Haley' - description = u'Sicher Bücher, Hörbücher und Downloads online bestellen.' + description = u'All Ihre Bücher immer dabei. Suchen, finden, kaufen: so einfach wie nie. ebook.de war libre.de' actual_plugin = 'calibre.gui2.store.stores.libri_de_plugin:LibreDEStore' headquarters = 'DE' @@ -1529,6 +1529,15 @@ class StoreNextoStore(StoreBase): formats = ['EPUB', 'MOBI', 'PDF'] affiliate = True +class StoreNookUKStore(StoreBase): + name = 'Nook UK' + author = 'John Schember' + description = u'Barnes & Noble S.à r.l, a subsidiary of Barnes & Noble, Inc., a leading retailer of content, digital media and educational products, is proud to bring the award-winning NOOK® reading experience and a leading digital bookstore to the UK.' + actual_plugin = 'calibre.gui2.store.stores.nook_uk_plugin:NookUKStore' + + headquarters = 'UK' + formats = ['NOOK'] + class StoreOpenBooksStore(StoreBase): name = 'Open Books' description = u'Comprehensive listing of DRM free ebooks from a variety of sources provided by users of calibre.' @@ -1660,7 +1669,7 @@ plugins += [ StoreAmazonITKindleStore, StoreAmazonUKKindleStore, StoreBaenWebScriptionStore, - StoreBNStore, StoreSonyStore, + StoreBNStore, StoreBeWriteStore, StoreBiblioStore, StoreBookotekaStore, @@ -1686,12 +1695,14 @@ plugins += [ StoreMillsBoonUKStore, StoreMobileReadStore, StoreNextoStore, + StoreNookUKStore, StoreOpenBooksStore, StoreOzonRUStore, StorePragmaticBookshelfStore, StorePublioStore, StoreRW2010Store, StoreSmashwordsStore, + StoreSonyStore, StoreVirtualoStore, StoreWaterstonesUKStore, StoreWeightlessBooksStore, diff --git a/src/calibre/customize/profiles.py b/src/calibre/customize/profiles.py index 00ec6294fb..7eba099bd2 100644 --- a/src/calibre/customize/profiles.py +++ b/src/calibre/customize/profiles.py @@ -770,13 +770,25 @@ class PocketBook900Output(OutputProfile): dpi = 150.0 comic_screen_size = screen_size +class PocketBookPro912Output(OutputProfile): + + author = 'Daniele Pizzolli' + name = 'PocketBook Pro 912' + short_name = 'pocketbook_pro_912' + description = _('This profile is intended for the PocketBook Pro 912 series of devices.') + + # According to http://download.pocketbook-int.com/user-guides/E_Ink/912/User_Guide_PocketBook_912(EN).pdf + screen_size = (825, 1200) + dpi = 155.0 + comic_screen_size = screen_size + output_profiles = [OutputProfile, SonyReaderOutput, SonyReader300Output, SonyReader900Output, MSReaderOutput, MobipocketOutput, HanlinV3Output, HanlinV5Output, CybookG3Output, CybookOpusOutput, KindleOutput, iPadOutput, iPad3Output, KoboReaderOutput, TabletOutput, SamsungGalaxy, SonyReaderLandscapeOutput, KindleDXOutput, IlliadOutput, IRexDR1000Output, IRexDR800Output, JetBook5Output, NookOutput, - BambookOutput, NookColorOutput, PocketBook900Output, GenericEink, - GenericEinkLarge, KindleFireOutput, KindlePaperWhiteOutput] + BambookOutput, NookColorOutput, PocketBook900Output, PocketBookPro912Output, + GenericEink, GenericEinkLarge, KindleFireOutput, KindlePaperWhiteOutput] output_profiles.sort(cmp=lambda x,y:cmp(x.name.lower(), y.name.lower())) diff --git a/src/calibre/devices/__init__.py b/src/calibre/devices/__init__.py index ad719c8be8..97a5dbd9ef 100644 --- a/src/calibre/devices/__init__.py +++ b/src/calibre/devices/__init__.py @@ -121,6 +121,8 @@ def debug(ioreg_to_tmp=False, buf=None, plugins=None, out('\nDisabled plugins:', textwrap.fill(' '.join([x.__class__.__name__ for x in disabled_plugins]))) out(' ') + else: + out('\nNo disabled plugins') found_dev = False for dev in devplugins: if not dev.MANAGES_DEVICE_PRESENCE: continue diff --git a/src/calibre/devices/android/driver.py b/src/calibre/devices/android/driver.py index 87aede5f84..acee4938f5 100644 --- a/src/calibre/devices/android/driver.py +++ b/src/calibre/devices/android/driver.py @@ -10,7 +10,7 @@ import cStringIO from calibre.devices.usbms.driver import USBMS -HTC_BCDS = [0x100, 0x0222, 0x0226, 0x227, 0x228, 0x229, 0x9999] +HTC_BCDS = [0x100, 0x0222, 0x0226, 0x227, 0x228, 0x229, 0x0231, 0x9999] class ANDROID(USBMS): @@ -48,6 +48,7 @@ class ANDROID(USBMS): 0x2910 : HTC_BCDS, 0xe77 : HTC_BCDS, 0xff9 : HTC_BCDS, + 0x0001 : [0x255], }, # Eken @@ -92,7 +93,7 @@ class ANDROID(USBMS): # Google 0x18d1 : { 0x0001 : [0x0223, 0x230, 0x9999], - 0x0003 : [0x0230], + 0x0003 : [0x0230, 0x9999], 0x4e11 : [0x0100, 0x226, 0x227], 0x4e12 : [0x0100, 0x226, 0x227], 0x4e21 : [0x0100, 0x226, 0x227, 0x231], @@ -190,7 +191,7 @@ class ANDROID(USBMS): 0x10a9 : { 0x6050 : [0x227] }, # Prestigio - 0x2207 : { 0 : [0x222] }, + 0x2207 : { 0 : [0x222], 0x10 : [0x222] }, } EBOOK_DIR_MAIN = ['eBooks/import', 'wordplayer/calibretransfer', 'Books', @@ -212,7 +213,8 @@ class ANDROID(USBMS): 'VIZIO', 'GOOGLE', 'FREESCAL', 'KOBO_INC', 'LENOVO', 'ROCKCHIP', 'POCKET', 'ONDA_MID', 'ZENITHIN', 'INGENIC', 'PMID701C', 'PD', 'PMP5097C', 'MASS', 'NOVO7', 'ZEKI', 'COBY', 'SXZ', 'USB_2.0', - 'COBY_MID', 'VS', 'AINOL', 'TOPWISE', 'PAD703'] + 'COBY_MID', 'VS', 'AINOL', 'TOPWISE', 'PAD703', 'NEXT8D12', + 'MEDIATEK', 'KEENHI'] WINDOWS_MAIN_MEM = ['ANDROID_PHONE', 'A855', 'A853', 'INC.NEXUS_ONE', '__UMS_COMPOSITE', '_MB200', 'MASS_STORAGE', '_-_CARD', 'SGH-I897', 'GT-I9000', 'FILE-STOR_GADGET', 'SGH-T959_CARD', 'SGH-T959', 'SAMSUNG_ANDROID', @@ -232,7 +234,8 @@ class ANDROID(USBMS): 'THINKPAD_TABLET', 'SGH-T989', 'YP-G70', 'STORAGE_DEVICE', 'ADVANCED', 'SGH-I727', 'USB_FLASH_DRIVER', 'ANDROID', 'S5830I_CARD', 'MID7042', 'LINK-CREATE', '7035', 'VIEWPAD_7E', - 'NOVO7', 'MB526', '_USB#WYK7MSF8KE', 'TABLET_PC'] + 'NOVO7', 'MB526', '_USB#WYK7MSF8KE', 'TABLET_PC', 'F', 'MT65XX_MS', + 'ICS'] WINDOWS_CARD_A_MEM = ['ANDROID_PHONE', 'GT-I9000_CARD', 'SGH-I897', 'FILE-STOR_GADGET', 'SGH-T959_CARD', 'SGH-T959', 'SAMSUNG_ANDROID', 'GT-P1000_CARD', 'A70S', 'A101IT', '7', 'INCREDIBLE', 'A7EB', 'SGH-T849_CARD', @@ -243,7 +246,7 @@ class ANDROID(USBMS): 'FILE-CD_GADGET', 'GT-I9001_CARD', 'USB_2.0', 'XT875', 'UMS_COMPOSITE', 'PRO', '.KOBO_VOX', 'SGH-T989_CARD', 'SGH-I727', 'USB_FLASH_DRIVER', 'ANDROID', 'MID7042', '7035', 'VIEWPAD_7E', - 'NOVO7', 'ADVANCED', 'TABLET_PC'] + 'NOVO7', 'ADVANCED', 'TABLET_PC', 'F'] OSX_MAIN_MEM = 'Android Device Main Memory' diff --git a/src/calibre/devices/apple/driver.py b/src/calibre/devices/apple/driver.py index d8af3bac4b..eacb143790 100644 --- a/src/calibre/devices/apple/driver.py +++ b/src/calibre/devices/apple/driver.py @@ -1130,6 +1130,7 @@ class ITUNES(DriverBase): metadata[i].uuid)) self.cached_books[this_book.path] = { 'author': authors_to_string(metadata[i].authors), + 'authors': metadata[i].authors, 'dev_book': db_added, 'format': format, 'lib_book': lb_added, @@ -1176,6 +1177,7 @@ class ITUNES(DriverBase): metadata[i].uuid)) self.cached_books[this_book.path] = { 'author': authors_to_string(metadata[i].authors), + 'authors': metadata[i].authors, 'dev_book': db_added, 'format': format, 'lib_book': lb_added, @@ -1393,21 +1395,18 @@ class ITUNES(DriverBase): db_added = None lb_added = None - # If using iTunes_local_storage, copy the file, redirect iTunes to use local copy - if not self.settings().extra_customization[self.USE_ITUNES_STORAGE]: - local_copy = os.path.join(self.iTunes_local_storage, str(metadata.uuid) + os.path.splitext(fpath)[1]) - shutil.copyfile(fpath, local_copy) - fpath = local_copy - if self.manual_sync_mode: ''' - Unsupported direct-connect mode. + DC mode. Add to iBooks only. ''' db_added = self._add_device_book(fpath, metadata) - lb_added = self._add_library_book(fpath, metadata) - if not lb_added and DEBUG: - logger().warn(" failed to add '%s' to iTunes, iTunes Media folder inaccessible" % metadata.title) else: + # If using iTunes_local_storage, copy the file, redirect iTunes to use local copy + if not self.settings().extra_customization[self.USE_ITUNES_STORAGE]: + local_copy = os.path.join(self.iTunes_local_storage, str(metadata.uuid) + os.path.splitext(fpath)[1]) + shutil.copyfile(fpath, local_copy) + fpath = local_copy + lb_added = self._add_library_book(fpath, metadata) if not lb_added: raise UserFeedback("iTunes Media folder inaccessible", @@ -2441,13 +2440,13 @@ class ITUNES(DriverBase): as_binding = "dynamic" try: # Try dynamic binding - works with iTunes <= 10.6.1 - foo = self.iTunes.name() + self.iTunes.name() except: # Try static binding import itunes self.iTunes = appscript.app('iTunes', terms=itunes) try: - foo = self.iTunes.name() + self.iTunes.name() as_binding = "static" except: self.iTunes = None @@ -2500,8 +2499,8 @@ class ITUNES(DriverBase): self.iTunes = win32com.client.Dispatch("iTunes.Application") except: self.iTunes = None - raise UserFeedback(' %s._launch_iTunes(): unable to find installed iTunes' - % self.__class__.__name__, details=None, level=UserFeedback.WARN) + raise OpenFeedback('Unable to launch iTunes.\n' + + 'Try launching calibre as Administrator') if not DEBUG: self.iTunes.Windows[0].Minimized = True @@ -2509,8 +2508,7 @@ class ITUNES(DriverBase): try: # Pre-emptive test to confirm functional iTunes automation interface - foo = self.iTunes.Version - foo + logger().info(" automation interface with iTunes %s established" % self.iTunes.Version) except: self.iTunes = None raise OpenFeedback('Unable to connect to iTunes.\n' + @@ -3118,7 +3116,7 @@ class ITUNES(DriverBase): def _wait_for_writable_metadata(self, db_added, delay=2.0): ''' - Ensure iDevice metadata is writable. Direct connect mode only + Ensure iDevice metadata is writable. DC mode only ''' if DEBUG: logger().info(" %s._wait_for_writable_metadata()" % self.__class__.__name__) diff --git a/src/calibre/devices/eb600/driver.py b/src/calibre/devices/eb600/driver.py index 04501d193a..045eb2b4b7 100644 --- a/src/calibre/devices/eb600/driver.py +++ b/src/calibre/devices/eb600/driver.py @@ -234,7 +234,7 @@ class POCKETBOOK301(USBMS): class POCKETBOOK602(USBMS): name = 'PocketBook Pro 602/902 Device Interface' - description = _('Communicate with the PocketBook 602/603/902/903 reader.') + description = _('Communicate with the PocketBook 602/603/902/903/Pro 912 reader.') author = 'Kovid Goyal' supported_platforms = ['windows', 'osx', 'linux'] FORMATS = ['epub', 'fb2', 'prc', 'mobi', 'pdf', 'djvu', 'rtf', 'chm', @@ -249,7 +249,7 @@ class POCKETBOOK602(USBMS): VENDOR_NAME = '' WINDOWS_MAIN_MEM = WINDOWS_CARD_A_MEM = ['PB602', 'PB603', 'PB902', - 'PB903', 'PB'] + 'PB903', 'Pocket912', 'PB'] class POCKETBOOK622(POCKETBOOK602): diff --git a/src/calibre/devices/hanvon/driver.py b/src/calibre/devices/hanvon/driver.py index defa5bf477..c967f2c54c 100644 --- a/src/calibre/devices/hanvon/driver.py +++ b/src/calibre/devices/hanvon/driver.py @@ -41,6 +41,20 @@ class N516(USBMS): def can_handle(self, device_info, debug=False): return not is_alex(device_info) +class KIBANO(N516): + + name = 'Kibano driver' + gui_name = 'Kibano' + description = _('Communicate with the Kibano eBook reader.') + FORMATS = ['epub', 'pdf', 'txt'] + BCD = [0x323] + + VENDOR_NAME = 'EBOOK' + # We use EXTERNAL_SD_CARD for main mem as some devices have not working + # main memories + WINDOWS_MAIN_MEM = WINDOWS_CARD_A_MEM = ['INTERNAL_SD_CARD', + 'EXTERNAL_SD_CARD'] + class THEBOOK(N516): name = 'The Book driver' gui_name = 'The Book' diff --git a/src/calibre/devices/kobo/books.py b/src/calibre/devices/kobo/books.py index bcf6353e83..fc18a61fc8 100644 --- a/src/calibre/devices/kobo/books.py +++ b/src/calibre/devices/kobo/books.py @@ -199,6 +199,11 @@ class KTCollectionsBookList(CollectionsBookList): ('series' in collection_attributes and book.get('series', None) == category): is_series = True + + # The category should not be None, but, it has happened. + if not category: + continue + cat_name = category.strip(' ,') if cat_name not in collections: diff --git a/src/calibre/devices/kobo/driver.py b/src/calibre/devices/kobo/driver.py index 0aa946c848..4fb260f9cf 100644 --- a/src/calibre/devices/kobo/driver.py +++ b/src/calibre/devices/kobo/driver.py @@ -33,11 +33,11 @@ class KOBO(USBMS): gui_name = 'Kobo Reader' description = _('Communicate with the Kobo Reader') author = 'Timothy Legge and David Forrester' - version = (2, 0, 4) + version = (2, 0, 5) dbversion = 0 fwversion = 0 - supported_dbversion = 65 + supported_dbversion = 75 has_kepubs = False supported_platforms = ['windows', 'osx', 'linux'] @@ -1537,7 +1537,11 @@ class KOBOTOUCH(KOBO): return bookshelves cursor = connection.cursor() - query = "select ShelfName from ShelfContent where ContentId = ? and _IsDeleted = 'false'" + query = "select ShelfName " \ + "from ShelfContent " \ + "where ContentId = ? " \ + "and _IsDeleted = 'false' " \ + "and ShelfName is not null" # This should never be nulll, but it is protection against an error cause by a sync to the Kobo server values = (ContentID, ) cursor.execute(query, values) for i, row in enumerate(cursor): @@ -2349,10 +2353,17 @@ class KOBOTOUCH(KOBO): debug_print('KoboTouch:set_series book.series="%s"'%book.series) debug_print('KoboTouch:set_series book.series_index=', book.series_index) - if book.series == book.kobo_series and book.series_index == book.kobo_series_number: - if show_debug: - debug_print('KoboTouch:set_series - series info the same - not changing') - return + if book.series == book.kobo_series: + kobo_series_number = None + if book.kobo_series_number is not None: + try: + kobo_series_number = float(book.kobo_series_number) + except: + kobo_series_number = None + if kobo_series_number == book.series_index: + if show_debug: + debug_print('KoboTouch:set_series - series info the same - not changing') + return update_query = 'UPDATE content SET Series=?, SeriesNumber==? where BookID is Null and ContentID = ?' if book.series is None: diff --git a/src/calibre/devices/mtp/driver.py b/src/calibre/devices/mtp/driver.py index bc55654b1e..b1850c9e49 100644 --- a/src/calibre/devices/mtp/driver.py +++ b/src/calibre/devices/mtp/driver.py @@ -13,6 +13,7 @@ from itertools import izip from calibre import prints from calibre.constants import iswindows, numeric_version +from calibre.devices.errors import PathError from calibre.devices.mtp.base import debug from calibre.devices.mtp.defaults import DeviceDefaults from calibre.ptempfile import SpooledTemporaryFile, PersistentTemporaryDirectory @@ -23,6 +24,12 @@ from calibre.utils.filenames import shorten_components_to BASE = importlib.import_module('calibre.devices.mtp.%s.driver'%( 'windows' if iswindows else 'unix')).MTP_DEVICE +class MTPInvalidSendPathError(PathError): + + def __init__(self, folder): + PathError.__init__(self, 'Trying to send to ignored folder: %s'%folder) + self.folder = folder + class MTP_DEVICE(BASE): METADATA_CACHE = 'metadata.calibre' @@ -46,6 +53,7 @@ class MTP_DEVICE(BASE): self._prefs = None self.device_defaults = DeviceDefaults() self.current_device_defaults = {} + self.highlight_ignored_folders = False @property def prefs(self): @@ -59,9 +67,25 @@ class MTP_DEVICE(BASE): p.defaults['blacklist'] = [] p.defaults['history'] = {} p.defaults['rules'] = [] + p.defaults['ignored_folders'] = {} return self._prefs + def is_folder_ignored(self, storage_or_storage_id, name, + ignored_folders=None): + storage_id = unicode(getattr(storage_or_storage_id, 'object_id', + storage_or_storage_id)) + name = icu_lower(name) + if ignored_folders is None: + ignored_folders = self.get_pref('ignored_folders') + if storage_id in ignored_folders: + return name in {icu_lower(x) for x in ignored_folders[storage_id]} + + return name in { + 'alarms', 'android', 'dcim', 'movies', 'music', 'notifications', + 'pictures', 'ringtones', 'samsung', 'sony', 'htc', 'bluetooth', + 'games', 'lost.dir', 'video', 'whatsapp', 'image'} + def configure_for_kindle_app(self): proxy = self.prefs with proxy: @@ -371,6 +395,8 @@ class MTP_DEVICE(BASE): for infile, fname, mi in izip(files, names, metadata): path = self.create_upload_path(prefix, mi, fname, routing) + if path and self.is_folder_ignored(storage, path[0]): + raise MTPInvalidSendPathError(path[0]) parent = self.ensure_parent(storage, path) if hasattr(infile, 'read'): pos = infile.tell() @@ -472,7 +498,7 @@ class MTP_DEVICE(BASE): def config_widget(self): from calibre.gui2.device_drivers.mtp_config import MTPConfig - return MTPConfig(self) + return MTPConfig(self, highlight_ignored_folders=self.highlight_ignored_folders) def save_settings(self, cw): cw.commit() diff --git a/src/calibre/devices/mtp/test.py b/src/calibre/devices/mtp/test.py index 4eaf28a385..b0e29db566 100644 --- a/src/calibre/devices/mtp/test.py +++ b/src/calibre/devices/mtp/test.py @@ -239,12 +239,12 @@ class TestDeviceInteraction(unittest.TestCase): # Test get_filesystem used_by_one = self.measure_memory_usage(1, - self.dev.dev.get_filesystem, self.storage.object_id, lambda x: - x) + self.dev.dev.get_filesystem, self.storage.object_id, + lambda x, l:True) used_by_many = self.measure_memory_usage(5, - self.dev.dev.get_filesystem, self.storage.object_id, lambda x: - x) + self.dev.dev.get_filesystem, self.storage.object_id, + lambda x, l: True) self.check_memory(used_by_one, used_by_many, 'Memory consumption during get_filesystem') diff --git a/src/calibre/devices/mtp/unix/devices.c b/src/calibre/devices/mtp/unix/devices.c index cfbce8c66c..b6d50bac5b 100644 --- a/src/calibre/devices/mtp/unix/devices.c +++ b/src/calibre/devices/mtp/unix/devices.c @@ -13,11 +13,16 @@ const calibre_device_entry_t calibre_mtp_device_table[] = { // Amazon Kindle Fire HD , { "Amazon", 0x1949, "Fire HD", 0x0007, DEVICE_FLAGS_ANDROID_BUGS} + , { "Amazon", 0x1949, "Fire HD", 0x0008, DEVICE_FLAGS_ANDROID_BUGS} + , { "Amazon", 0x1949, "Fire HD", 0x000a, DEVICE_FLAGS_ANDROID_BUGS} // Nexus 10 , { "Google", 0x18d1, "Nexus 10", 0x4ee2, DEVICE_FLAGS_ANDROID_BUGS} , { "Google", 0x18d1, "Nexus 10", 0x4ee1, DEVICE_FLAGS_ANDROID_BUGS} + // Kobo Arc + , { "Kobo", 0x2237, "Arc", 0xd108, DEVICE_FLAGS_ANDROID_BUGS} + , { NULL, 0xffff, NULL, 0xffff, DEVICE_FLAG_NONE } }; diff --git a/src/calibre/devices/mtp/unix/driver.py b/src/calibre/devices/mtp/unix/driver.py index b8e8938c93..0e4461df7d 100644 --- a/src/calibre/devices/mtp/unix/driver.py +++ b/src/calibre/devices/mtp/unix/driver.py @@ -13,7 +13,7 @@ from collections import namedtuple from functools import partial from calibre import prints, as_unicode -from calibre.constants import plugins +from calibre.constants import plugins, islinux from calibre.ptempfile import SpooledTemporaryFile from calibre.devices.errors import OpenFailed, DeviceError, BlacklistedDevice from calibre.devices.mtp.base import MTPDeviceBase, synchronous, debug @@ -44,6 +44,17 @@ class MTP_DEVICE(MTPDeviceBase): self.blacklisted_devices = set() self.ejected_devices = set() self.currently_connected_dev = None + self._is_device_mtp = None + if islinux: + from calibre.devices.mtp.unix.sysfs import MTPDetect + self._is_device_mtp = MTPDetect() + + def is_device_mtp(self, d, debug=None): + ''' Returns True iff the _is_device_mtp check returns True and libmtp + is able to probe the device successfully. ''' + if self._is_device_mtp is None: return False + return (self._is_device_mtp(d, debug=debug) and + self.libmtp.is_mtp_device(d.busnum, d.devnum)) def set_debug_level(self, lvl): self.libmtp.set_debug_level(lvl) @@ -77,7 +88,9 @@ class MTP_DEVICE(MTPDeviceBase): for d in devs: ans = cache.get(d, None) if ans is None: - ans = (d.vendor_id, d.product_id) in self.known_devices + ans = ( + (d.vendor_id, d.product_id) in self.known_devices or + self.is_device_mtp(d)) cache[d] = ans if ans: return d @@ -95,12 +108,13 @@ class MTP_DEVICE(MTPDeviceBase): err = 'startup() not called on this device driver' p(err) return False - devs = [d for d in devices_on_system if (d.vendor_id, d.product_id) - in self.known_devices and d.vendor_id != APPLE] + devs = [d for d in devices_on_system if + ( (d.vendor_id, d.product_id) in self.known_devices or + self.is_device_mtp(d, debug=p)) and d.vendor_id != APPLE] if not devs: - p('No known MTP devices connected to system') + p('No MTP devices connected to system') return False - p('Known MTP devices connected:') + p('MTP devices connected:') for d in devs: p(d) for d in devs: @@ -212,8 +226,13 @@ class MTP_DEVICE(MTPDeviceBase): ans += pprint.pformat(storage) return ans - def _filesystem_callback(self, entry): - self.filesystem_callback(_('Found object: %s')%entry.get('name', '')) + def _filesystem_callback(self, entry, level): + name = entry.get('name', '') + self.filesystem_callback(_('Found object: %s')%name) + if (level == 0 and + self.is_folder_ignored(self._currently_getting_sid, name)): + return False + return True @property def filesystem_cache(self): @@ -234,6 +253,7 @@ class MTP_DEVICE(MTPDeviceBase): storage.append({'id':sid, 'size':capacity, 'is_folder':True, 'name':name, 'can_delete':False, 'is_system':True}) + self._currently_getting_sid = unicode(sid) items, errs = self.dev.get_filesystem(sid, self._filesystem_callback) all_items.extend(items), all_errs.extend(errs) diff --git a/src/calibre/devices/mtp/unix/libmtp.c b/src/calibre/devices/mtp/unix/libmtp.c index b62bd8a9c7..0191e04ef1 100644 --- a/src/calibre/devices/mtp/unix/libmtp.c +++ b/src/calibre/devices/mtp/unix/libmtp.c @@ -8,7 +8,9 @@ #define UNICODE #include - +#include +#include +#include #include #include @@ -122,7 +124,7 @@ static PyObject* build_file_metadata(LIBMTP_file_t *nf, uint32_t storage_id) { PyObject *ans = NULL; ans = Py_BuildValue("{s:s, s:k, s:k, s:k, s:K, s:L, s:O}", - "name", (unsigned long)nf->filename, + "name", nf->filename, "id", (unsigned long)nf->item_id, "parent_id", (unsigned long)nf->parent_id, "storage_id", (unsigned long)storage_id, @@ -357,10 +359,10 @@ Device_storage_info(Device *self, void *closure) { // Device.get_filesystem {{{ -static int recursive_get_files(LIBMTP_mtpdevice_t *dev, uint32_t storage_id, uint32_t parent_id, PyObject *ans, PyObject *errs, PyObject *callback) { +static int recursive_get_files(LIBMTP_mtpdevice_t *dev, uint32_t storage_id, uint32_t parent_id, PyObject *ans, PyObject *errs, PyObject *callback, unsigned int level) { LIBMTP_file_t *f, *files; - PyObject *entry; - int ok = 1; + PyObject *entry, *r; + int ok = 1, recurse; Py_BEGIN_ALLOW_THREADS; files = LIBMTP_Get_Files_And_Folders(dev, storage_id, parent_id); @@ -372,13 +374,15 @@ static int recursive_get_files(LIBMTP_mtpdevice_t *dev, uint32_t storage_id, uin entry = build_file_metadata(f, storage_id); if (entry == NULL) { ok = 0; } else { - Py_XDECREF(PyObject_CallFunctionObjArgs(callback, entry, NULL)); + r = PyObject_CallFunction(callback, "OI", entry, level); + recurse = (r != NULL && PyObject_IsTrue(r)) ? 1 : 0; + Py_XDECREF(r); if (PyList_Append(ans, entry) != 0) { ok = 0; } Py_DECREF(entry); } - if (ok && f->filetype == LIBMTP_FILETYPE_FOLDER) { - if (!recursive_get_files(dev, storage_id, f->item_id, ans, errs, callback)) { + if (ok && recurse && f->filetype == LIBMTP_FILETYPE_FOLDER) { + if (!recursive_get_files(dev, storage_id, f->item_id, ans, errs, callback, level+1)) { ok = 0; } } @@ -408,7 +412,7 @@ Device_get_filesystem(Device *self, PyObject *args) { if (errs == NULL || ans == NULL) { PyErr_NoMemory(); return NULL; } LIBMTP_Clear_Errorstack(self->device); - ok = recursive_get_files(self->device, (uint32_t)storage_id, 0, ans, errs, callback); + ok = recursive_get_files(self->device, (uint32_t)storage_id, 0xFFFFFFFF, ans, errs, callback, 0); dump_errorstack(self->device, errs); if (!ok) { Py_DECREF(ans); @@ -537,7 +541,7 @@ static PyMethodDef Device_methods[] = { }, {"get_filesystem", (PyCFunction)Device_get_filesystem, METH_VARARGS, - "get_filesystem(storage_id, callback) -> Get the list of files and folders on the device in storage_id. Returns files, errors. callback must be a callable that accepts a single argument. It is called with every found object." + "get_filesystem(storage_id, callback) -> Get the list of files and folders on the device in storage_id. Returns files, errors. callback must be a callable that is called as with (entry, level). It is called with every found object. If callback returns False and the object is a folder, it is not recursed into." }, {"get_file", (PyCFunction)Device_get_file, METH_VARARGS, @@ -658,13 +662,6 @@ is_mtp_device(PyObject *self, PyObject *args) { if (!PyArg_ParseTuple(args, "ii", &busnum, &devnum)) return NULL; - /* - * LIBMTP_Check_Specific_Device does not seem to work at least on my linux - * system. Need to investigate why later. Most devices are in the device - * table so this is not terribly important. - */ - /* LIBMTP_Set_Debug(LIBMTP_DEBUG_ALL); */ - /* printf("Calling check: %d %d\n", busnum, devnum); */ Py_BEGIN_ALLOW_THREADS; ans = LIBMTP_Check_Specific_Device(busnum, devnum); Py_END_ALLOW_THREADS; @@ -726,7 +723,21 @@ initlibmtp(void) { if (MTPError == NULL) return; PyModule_AddObject(m, "MTPError", MTPError); + // Redirect stdout to get rid of the annoying message about mtpz. Really, + // who designs a library without anyway to control/redirect the debugging + // output, and hardcoded paths that cannot be changed? + int bak, new; + fprintf(stdout, "\n"); // This is needed, without it, for some odd reason the code below causes stdout to buffer all output after it is restored, rather than using line buffering, and setlinebuf does not work. + fflush(stdout); + bak = dup(STDOUT_FILENO); + new = open("/dev/null", O_WRONLY); + dup2(new, STDOUT_FILENO); + close(new); LIBMTP_Init(); + fflush(stdout); + dup2(bak, STDOUT_FILENO); + close(bak); + LIBMTP_Set_Debug(LIBMTP_DEBUG_NONE); Py_INCREF(&DeviceType); diff --git a/src/calibre/devices/mtp/unix/sysfs.py b/src/calibre/devices/mtp/unix/sysfs.py new file mode 100644 index 0000000000..737ea1916b --- /dev/null +++ b/src/calibre/devices/mtp/unix/sysfs.py @@ -0,0 +1,53 @@ +#!/usr/bin/env python +# vim:fileencoding=UTF-8:ts=4:sw=4:sta:et:sts=4:fdm=marker:ai +from __future__ import (unicode_literals, division, absolute_import, + print_function) + +__license__ = 'GPL v3' +__copyright__ = '2013, Kovid Goyal ' +__docformat__ = 'restructuredtext en' + +import os, glob + +class MTPDetect(object): + + SYSFS_PATH = os.environ.get('SYSFS_PATH', '/sys') + + def __init__(self): + self.base = os.path.join(self.SYSFS_PATH, 'subsystem', 'usb', 'devices') + if not os.path.exists(self.base): + self.base = os.path.join(self.SYSFS_PATH, 'bus', 'usb', 'devices') + self.ok = os.path.exists(self.base) + + def __call__(self, dev, debug=None): + ''' + Check if the device has an interface named "MTP" using sysfs, which + avoids probing the device. + ''' + if not self.ok: return False + + def read(x): + try: + with open(x, 'rb') as f: + return f.read() + except EnvironmentError: + pass + + ipath = os.path.join(self.base, '{0}-*/{0}-*/interface'.format(dev.busnum)) + for x in glob.glob(ipath): + raw = read(x) + if not raw or raw.strip() != b'MTP': continue + raw = read(os.path.join(os.path.dirname(os.path.dirname(x)), + 'devnum')) + try: + if raw and int(raw) == dev.devnum: + if debug is not None: + debug('Unknown device {0} claims to be an MTP device' + .format(dev)) + return True + except (ValueError, TypeError): + continue + + return False + + diff --git a/src/calibre/devices/mtp/windows/content_enumeration.cpp b/src/calibre/devices/mtp/windows/content_enumeration.cpp index f7e585c55e..e642d38131 100644 --- a/src/calibre/devices/mtp/windows/content_enumeration.cpp +++ b/src/calibre/devices/mtp/windows/content_enumeration.cpp @@ -133,12 +133,14 @@ class GetBulkCallback : public IPortableDevicePropertiesBulkCallback { public: PyObject *items; + PyObject *subfolders; + unsigned int level; HANDLE complete; ULONG self_ref; PyThreadState *thread_state; PyObject *callback; - GetBulkCallback(PyObject *items_dict, HANDLE ev, PyObject* pycallback) : items(items_dict), complete(ev), self_ref(1), thread_state(NULL), callback(pycallback) {} + GetBulkCallback(PyObject *items_dict, PyObject *subfolders, unsigned int level, HANDLE ev, PyObject* pycallback) : items(items_dict), subfolders(subfolders), level(level), complete(ev), self_ref(1), thread_state(NULL), callback(pycallback) {} ~GetBulkCallback() {} HRESULT __stdcall OnStart(REFGUID Context) { return S_OK; } @@ -172,7 +174,7 @@ public: DWORD num = 0, i; wchar_t *property = NULL; IPortableDeviceValues *properties = NULL; - PyObject *temp, *obj; + PyObject *temp, *obj, *r; HRESULT hr; if (SUCCEEDED(values->GetCount(&num))) { @@ -196,7 +198,11 @@ public: Py_DECREF(temp); set_properties(obj, properties); - Py_XDECREF(PyObject_CallFunctionObjArgs(callback, obj, NULL)); + r = PyObject_CallFunction(callback, "OI", obj, this->level); + if (r != NULL && PyObject_IsTrue(r)) { + PyList_Append(this->subfolders, PyDict_GetItemString(obj, "id")); + } + Py_XDECREF(r); properties->Release(); properties = NULL; } @@ -209,8 +215,7 @@ public: }; -static PyObject* bulk_get_filesystem(IPortableDevice *device, IPortableDevicePropertiesBulk *bulk_properties, const wchar_t *storage_id, IPortableDevicePropVariantCollection *object_ids, PyObject *pycallback) { - PyObject *folders = NULL; +static bool bulk_get_filesystem(unsigned int level, IPortableDevice *device, IPortableDevicePropertiesBulk *bulk_properties, IPortableDevicePropVariantCollection *object_ids, PyObject *pycallback, PyObject *ans, PyObject *subfolders) { GUID guid_context = GUID_NULL; HANDLE ev = NULL; IPortableDeviceKeyCollection *properties; @@ -218,18 +223,15 @@ static PyObject* bulk_get_filesystem(IPortableDevice *device, IPortableDevicePro HRESULT hr; DWORD wait_result; int pump_result; - BOOL ok = TRUE; + bool ok = true; ev = CreateEvent(NULL, FALSE, FALSE, NULL); - if (ev == NULL) return PyErr_NoMemory(); - - folders = PyDict_New(); - if (folders == NULL) {PyErr_NoMemory(); goto end;} + if (ev == NULL) {PyErr_NoMemory(); return false; } properties = create_filesystem_properties_collection(); if (properties == NULL) goto end; - callback = new (std::nothrow) GetBulkCallback(folders, ev, pycallback); + callback = new (std::nothrow) GetBulkCallback(ans, subfolders, level, ev, pycallback); if (callback == NULL) { PyErr_NoMemory(); goto end; } hr = bulk_properties->QueueGetValuesByObjectList(object_ids, properties, callback, &guid_context); @@ -245,13 +247,13 @@ static PyObject* bulk_get_filesystem(IPortableDevice *device, IPortableDevicePro break; // Event was signalled, bulk operation complete } else if (wait_result == WAIT_OBJECT_0 + 1) { // Messages need to be dispatched pump_result = pump_waiting_messages(); - if (pump_result == 1) { PyErr_SetString(PyExc_RuntimeError, "Application has been asked to quit."); ok = FALSE; break;} + if (pump_result == 1) { PyErr_SetString(PyExc_RuntimeError, "Application has been asked to quit."); ok = false; break;} } else if (wait_result == WAIT_TIMEOUT) { // 60 seconds with no updates, looks bad - PyErr_SetString(WPDError, "The device seems to have hung."); ok = FALSE; break; + PyErr_SetString(WPDError, "The device seems to have hung."); ok = false; break; } else if (wait_result == WAIT_ABANDONED_0) { // This should never happen - PyErr_SetString(WPDError, "An unknown error occurred (mutex abandoned)"); ok = FALSE; break; + PyErr_SetString(WPDError, "An unknown error occurred (mutex abandoned)"); ok = false; break; } else { // The wait failed for some reason PyErr_SetFromWindowsErr(0); ok = FALSE; break; @@ -261,22 +263,21 @@ static PyObject* bulk_get_filesystem(IPortableDevice *device, IPortableDevicePro if (!ok) { bulk_properties->Cancel(guid_context); pump_waiting_messages(); - Py_DECREF(folders); folders = NULL; } end: if (ev != NULL) CloseHandle(ev); if (properties != NULL) properties->Release(); if (callback != NULL) callback->Release(); - return folders; + return ok; } // }}} -// find_all_objects_in() {{{ -static BOOL find_all_objects_in(IPortableDeviceContent *content, IPortableDevicePropVariantCollection *object_ids, const wchar_t *parent_id, PyObject *callback) { +// find_objects_in() {{{ +static bool find_objects_in(IPortableDeviceContent *content, IPortableDevicePropVariantCollection *object_ids, const wchar_t *parent_id) { /* - * Find all children of the object identified by parent_id, recursively. + * Find all children of the object identified by parent_id. * The child ids are put into object_ids. Returns False if any errors * occurred (also sets the python exception). */ @@ -285,8 +286,7 @@ static BOOL find_all_objects_in(IPortableDeviceContent *content, IPortableDevice PWSTR child_ids[10]; DWORD fetched, i; PROPVARIANT pv; - BOOL ok = 1; - PyObject *id; + bool ok = true; PropVariantInit(&pv); pv.vt = VT_LPWSTR; @@ -295,7 +295,7 @@ static BOOL find_all_objects_in(IPortableDeviceContent *content, IPortableDevice hr = content->EnumObjects(0, parent_id, NULL, &children); Py_END_ALLOW_THREADS; - if (FAILED(hr)) {hresult_set_exc("Failed to get children from device", hr); ok = 0; goto end;} + if (FAILED(hr)) {hresult_set_exc("Failed to get children from device", hr); ok = false; goto end;} hr = S_OK; @@ -306,19 +306,12 @@ static BOOL find_all_objects_in(IPortableDeviceContent *content, IPortableDevice if (SUCCEEDED(hr)) { for(i = 0; i < fetched; i++) { pv.pwszVal = child_ids[i]; - id = wchar_to_unicode(pv.pwszVal); - if (id != NULL) { - Py_XDECREF(PyObject_CallFunctionObjArgs(callback, id, NULL)); - Py_DECREF(id); - } hr2 = object_ids->Add(&pv); pv.pwszVal = NULL; if (FAILED(hr2)) { hresult_set_exc("Failed to add child ids to propvariantcollection", hr2); break; } - ok = find_all_objects_in(content, object_ids, child_ids[i], callback); - if (!ok) break; } for (i = 0; i < fetched; i++) { CoTaskMemFree(child_ids[i]); child_ids[i] = NULL; } - if (FAILED(hr2) || !ok) { ok = 0; goto end; } + if (FAILED(hr2) || !ok) { ok = false; goto end; } } } @@ -340,13 +333,8 @@ static PyObject* get_object_properties(IPortableDeviceProperties *devprops, IPor Py_END_ALLOW_THREADS; if (FAILED(hr)) { hresult_set_exc("Failed to get properties for object", hr); goto end; } - temp = wchar_to_unicode(object_id); - if (temp == NULL) goto end; - - ans = PyDict_New(); - if (ans == NULL) { PyErr_NoMemory(); goto end; } - if (PyDict_SetItemString(ans, "id", temp) != 0) { Py_DECREF(ans); ans = NULL; PyErr_NoMemory(); goto end; } - + ans = Py_BuildValue("{s:N}", "id", wchar_to_unicode(object_id)); + if (ans == NULL) goto end; set_properties(ans, values); end: @@ -355,12 +343,12 @@ end: return ans; } -static PyObject* single_get_filesystem(IPortableDeviceContent *content, const wchar_t *storage_id, IPortableDevicePropVariantCollection *object_ids, PyObject *callback) { +static bool single_get_filesystem(unsigned int level, IPortableDeviceContent *content, IPortableDevicePropVariantCollection *object_ids, PyObject *callback, PyObject *ans, PyObject *subfolders) { DWORD num, i; PROPVARIANT pv; HRESULT hr; - BOOL ok = 1; - PyObject *ans = NULL, *item = NULL; + bool ok = true; + PyObject *item = NULL, *r = NULL, *recurse = NULL; IPortableDeviceProperties *devprops = NULL; IPortableDeviceKeyCollection *properties = NULL; @@ -373,32 +361,36 @@ static PyObject* single_get_filesystem(IPortableDeviceContent *content, const wc hr = object_ids->GetCount(&num); if (FAILED(hr)) { hresult_set_exc("Failed to get object id count", hr); goto end; } - ans = PyDict_New(); - if (ans == NULL) goto end; - for (i = 0; i < num; i++) { - ok = 0; + ok = false; + recurse = NULL; PropVariantInit(&pv); hr = object_ids->GetAt(i, &pv); if (SUCCEEDED(hr) && pv.pwszVal != NULL) { item = get_object_properties(devprops, properties, pv.pwszVal); if (item != NULL) { - Py_XDECREF(PyObject_CallFunctionObjArgs(callback, item, NULL)); + r = PyObject_CallFunction(callback, "OI", item, level); + if (r != NULL && PyObject_IsTrue(r)) recurse = item; + Py_XDECREF(r); PyDict_SetItem(ans, PyDict_GetItemString(item, "id"), item); Py_DECREF(item); item = NULL; - ok = 1; + ok = true; } } else hresult_set_exc("Failed to get item from IPortableDevicePropVariantCollection", hr); PropVariantClear(&pv); - if (!ok) { Py_DECREF(ans); ans = NULL; break; } + if (!ok) break; + if (recurse != NULL) { + if (PyList_Append(subfolders, PyDict_GetItemString(recurse, "id")) == -1) ok = false; + } + if (!ok) break; } end: if (devprops != NULL) devprops->Release(); if (properties != NULL) properties->Release(); - return ans; + return ok; } // }}} @@ -438,35 +430,60 @@ end: return values; } // }}} -PyObject* wpd::get_filesystem(IPortableDevice *device, const wchar_t *storage_id, IPortableDevicePropertiesBulk *bulk_properties, PyObject *callback) { // {{{ - PyObject *folders = NULL; +static bool get_files_and_folders(unsigned int level, IPortableDevice *device, IPortableDeviceContent *content, IPortableDevicePropertiesBulk *bulk_properties, const wchar_t *parent_id, PyObject *callback, PyObject *ans) { // {{{ + bool ok = true; IPortableDevicePropVariantCollection *object_ids = NULL; + PyObject *subfolders = NULL; + HRESULT hr; + + subfolders = PyList_New(0); + if (subfolders == NULL) { ok = false; goto end; } + + Py_BEGIN_ALLOW_THREADS; + hr = CoCreateInstance(CLSID_PortableDevicePropVariantCollection, NULL, + CLSCTX_INPROC_SERVER, IID_PPV_ARGS(&object_ids)); + Py_END_ALLOW_THREADS; + if (FAILED(hr)) { hresult_set_exc("Failed to create propvariantcollection", hr); ok = false; goto end; } + + ok = find_objects_in(content, object_ids, parent_id); + if (!ok) goto end; + + if (bulk_properties != NULL) ok = bulk_get_filesystem(level, device, bulk_properties, object_ids, callback, ans, subfolders); + else ok = single_get_filesystem(level, content, object_ids, callback, ans, subfolders); + if (!ok) goto end; + + for (Py_ssize_t i = 0; i < PyList_GET_SIZE(subfolders); i++) { + const wchar_t *child_id = unicode_to_wchar(PyList_GET_ITEM(subfolders, i)); + if (child_id == NULL) { ok = false; break; } + ok = get_files_and_folders(level+1, device, content, bulk_properties, child_id, callback, ans); + if (!ok) break; + } +end: + if (object_ids != NULL) object_ids->Release(); + Py_XDECREF(subfolders); + return ok; +} // }}} + +PyObject* wpd::get_filesystem(IPortableDevice *device, const wchar_t *storage_id, IPortableDevicePropertiesBulk *bulk_properties, PyObject *callback) { // {{{ + PyObject *ans = NULL; IPortableDeviceContent *content = NULL; HRESULT hr; - BOOL ok; + + ans = PyDict_New(); + if (ans == NULL) return PyErr_NoMemory(); Py_BEGIN_ALLOW_THREADS; hr = device->Content(&content); Py_END_ALLOW_THREADS; if (FAILED(hr)) { hresult_set_exc("Failed to create content interface", hr); goto end; } - Py_BEGIN_ALLOW_THREADS; - hr = CoCreateInstance(CLSID_PortableDevicePropVariantCollection, NULL, - CLSCTX_INPROC_SERVER, IID_PPV_ARGS(&object_ids)); - Py_END_ALLOW_THREADS; - if (FAILED(hr)) { hresult_set_exc("Failed to create propvariantcollection", hr); goto end; } - - ok = find_all_objects_in(content, object_ids, storage_id, callback); - if (!ok) goto end; - - if (bulk_properties != NULL) folders = bulk_get_filesystem(device, bulk_properties, storage_id, object_ids, callback); - else folders = single_get_filesystem(content, storage_id, object_ids, callback); + if (!get_files_and_folders(0, device, content, bulk_properties, storage_id, callback, ans)) { + Py_DECREF(ans); ans = NULL; + } end: if (content != NULL) content->Release(); - if (object_ids != NULL) object_ids->Release(); - - return folders; + return ans; } // }}} PyObject* wpd::get_file(IPortableDevice *device, const wchar_t *object_id, PyObject *dest, PyObject *callback) { // {{{ diff --git a/src/calibre/devices/mtp/windows/device.cpp b/src/calibre/devices/mtp/windows/device.cpp index 3886bb5e56..88b57776ec 100644 --- a/src/calibre/devices/mtp/windows/device.cpp +++ b/src/calibre/devices/mtp/windows/device.cpp @@ -164,7 +164,7 @@ static PyMethodDef Device_methods[] = { }, {"get_filesystem", (PyCFunction)py_get_filesystem, METH_VARARGS, - "get_filesystem(storage_id, callback) -> Get all files/folders on the storage identified by storage_id. Tries to use bulk operations when possible. callback must be a callable that accepts a single argument. It is called with every found id and then with the metadata for every id." + "get_filesystem(storage_id, callback) -> Get all files/folders on the storage identified by storage_id. Tries to use bulk operations when possible. callback must be a callable that is called as (object, level). It is called with every found object. If the callback returns False and the object is a folder, it is not recursed into." }, {"get_file", (PyCFunction)py_get_file, METH_VARARGS, diff --git a/src/calibre/devices/mtp/windows/driver.py b/src/calibre/devices/mtp/windows/driver.py index 7253b4490c..154bde0d8d 100644 --- a/src/calibre/devices/mtp/windows/driver.py +++ b/src/calibre/devices/mtp/windows/driver.py @@ -214,13 +214,14 @@ class MTP_DEVICE(MTPDeviceBase): return True - def _filesystem_callback(self, obj): - if isinstance(obj, dict): - n = obj.get('name', '') - msg = _('Found object: %s')%n - else: - msg = _('Found id: %s')%obj + def _filesystem_callback(self, obj, level): + n = obj.get('name', '') + msg = _('Found object: %s')%n + if (level == 0 and + self.is_folder_ignored(self._currently_getting_sid, n)): + return False self.filesystem_callback(msg) + return obj.get('is_folder', False) @property def filesystem_cache(self): @@ -241,6 +242,7 @@ class MTP_DEVICE(MTPDeviceBase): break storage = {'id':storage_id, 'size':capacity, 'name':name, 'is_folder':True, 'can_delete':False, 'is_system':True} + self._currently_getting_sid = unicode(storage_id) id_map = self.dev.get_filesystem(storage_id, self._filesystem_callback) for x in id_map.itervalues(): x['storage_id'] = storage_id diff --git a/src/calibre/devices/mtp/windows/remote.py b/src/calibre/devices/mtp/windows/remote.py index f1dfa92767..628839a197 100644 --- a/src/calibre/devices/mtp/windows/remote.py +++ b/src/calibre/devices/mtp/windows/remote.py @@ -12,24 +12,24 @@ pprint, io def build(mod='wpd'): master = subprocess.Popen('ssh -MN getafix'.split()) - master2 = subprocess.Popen('ssh -MN xp_build'.split()) + master2 = subprocess.Popen('ssh -MN win64'.split()) try: - while not glob.glob(os.path.expanduser('~/.ssh/*kovid@xp_build*')): + while not glob.glob(os.path.expanduser('~/.ssh/*kovid@win64*')): time.sleep(0.05) - builder = subprocess.Popen('ssh xp_build ~/build-wpd'.split()) + builder = subprocess.Popen('ssh win64 ~/build-wpd'.split()) if builder.wait() != 0: raise Exception('Failed to build plugin') while not glob.glob(os.path.expanduser('~/.ssh/*kovid@getafix*')): time.sleep(0.05) - syncer = subprocess.Popen('ssh getafix ~/test-wpd'.split()) + syncer = subprocess.Popen('ssh getafix ~/update-calibre'.split()) if syncer.wait() != 0: raise Exception('Failed to rsync to getafix') subprocess.check_call( - ('scp xp_build:build/calibre/src/calibre/plugins/%s.pyd /tmp'%mod).split()) + ('scp win64:build/calibre/src/calibre/plugins/%s.pyd /tmp'%mod).split()) subprocess.check_call( - ('scp /tmp/%s.pyd getafix:calibre/src/calibre/devices/mtp/windows'%mod).split()) + ('scp /tmp/%s.pyd getafix:calibre-src/src/calibre/devices/mtp/windows'%mod).split()) p = subprocess.Popen( - 'ssh getafix calibre-debug -e calibre/src/calibre/devices/mtp/windows/remote.py'.split()) + 'ssh getafix calibre-debug -e calibre-src/src/calibre/devices/mtp/windows/remote.py'.split()) p.wait() print() finally: @@ -59,7 +59,7 @@ def main(): # return from calibre.devices.scanner import win_scanner - from calibre.devices.mtp.windows.driver import MTP_DEVICE + from calibre.devices.mtp.driver import MTP_DEVICE dev = MTP_DEVICE(None) dev.startup() print (dev.wpd, dev.wpd_error) diff --git a/src/calibre/devices/smart_device_app/driver.py b/src/calibre/devices/smart_device_app/driver.py index a9df1c0d94..9da1ab9635 100644 --- a/src/calibre/devices/smart_device_app/driver.py +++ b/src/calibre/devices/smart_device_app/driver.py @@ -300,19 +300,21 @@ class SMART_DEVICE_APP(DeviceConfig, DevicePlugin): 'particular IP address. The driver will listen only on the ' 'entered address, and this address will be the one advertized ' 'over mDNS (bonjour).') + '

', + _('Replace books with the same calibre identifier') + ':::

' + + _('Use this option to overwrite a book on the device if that book ' + 'has the same calibre identifier as the book being sent. The file name of the ' + 'book will not change even if the save template produces a ' + 'different result. Using this option in most cases prevents ' + 'having multiple copies of a book on the device.') + '

', ] EXTRA_CUSTOMIZATION_DEFAULT = [ - False, - '', - '', - '', + False, '', + '', '', False, '9090', - False, - '', - '', - '', - True, - '' + False, '', + '', '', + True, '', + True ] OPT_AUTOSTART = 0 OPT_PASSWORD = 2 @@ -322,6 +324,7 @@ class SMART_DEVICE_APP(DeviceConfig, DevicePlugin): OPT_COLLECTIONS = 8 OPT_AUTODISCONNECT = 10 OPT_FORCE_IP_ADDRESS = 11 + OPT_OVERWRITE_BOOKS_UUID = 12 def __init__(self, path): @@ -385,6 +388,20 @@ class SMART_DEVICE_APP(DeviceConfig, DevicePlugin): fname = sanitize(fname) ext = os.path.splitext(fname)[1] + try: + # If we have already seen this book's UUID, use the existing path + if self.settings().extra_customization[self.OPT_OVERWRITE_BOOKS_UUID]: + existing_book = self._uuid_already_on_device(mdata.uuid, ext) + if existing_book and existing_book.lpath: + return existing_book.lpath + + # If the device asked for it, try to use the UUID as the file name. + # Fall back to the ch if the UUID doesn't exist. + if self.client_wants_uuid_file_names and mdata.uuid: + return (mdata.uuid + ext) + except: + pass + maxlen = (self.MAX_PATH_LEN - (self.PATH_FUDGE_FACTOR + self.exts_path_lengths.get(ext, self.PATH_FUDGE_FACTOR))) @@ -671,12 +688,24 @@ class SMART_DEVICE_APP(DeviceConfig, DevicePlugin): return not v_thumb or v_thumb[1] == b_thumb[1] return False + def _uuid_already_on_device(self, uuid, ext): + try: + return self.known_uuids.get(uuid + ext, None) + except: + return None + def _set_known_metadata(self, book, remove=False): lpath = book.lpath + ext = os.path.splitext(lpath)[1] + uuid = book.get('uuid', None) if remove: self.known_metadata.pop(lpath, None) + if uuid and ext: + self.known_uuids.pop(uuid+ext, None) else: - self.known_metadata[lpath] = book.deepcopy() + new_book = self.known_metadata[lpath] = book.deepcopy() + if uuid and ext: + self.known_uuids[uuid+ext] = new_book def _close_device_socket(self): if self.device_socket is not None: @@ -845,6 +874,10 @@ class SMART_DEVICE_APP(DeviceConfig, DevicePlugin): self._close_device_socket() return False + self.client_wants_uuid_file_names = result.get('useUuidFileNames', False) + self._debug('Device wants UUID file names', self.client_wants_uuid_file_names) + + config = self._configProxy() config['format_map'] = exts self._debug('selected formats', config['format_map']) @@ -1085,6 +1118,7 @@ class SMART_DEVICE_APP(DeviceConfig, DevicePlugin): for i, infile in enumerate(files): mdata, fname = metadata.next(), names.next() lpath = self._create_upload_path(mdata, fname, create_dirs=False) + self._debug('lpath', lpath) if not hasattr(infile, 'read'): infile = USBMS.normalize_path(infile) book = SDBook(self.PREFIX, lpath, other=mdata) @@ -1246,6 +1280,7 @@ class SMART_DEVICE_APP(DeviceConfig, DevicePlugin): self.device_socket = None self.json_codec = JsonCodec() self.known_metadata = {} + self.known_uuids = {} self.debug_time = time.time() self.debug_start_time = time.time() self.max_book_packet_len = 0 @@ -1253,6 +1288,7 @@ class SMART_DEVICE_APP(DeviceConfig, DevicePlugin): self.connection_attempts = {} self.client_can_stream_books = False self.client_can_stream_metadata = False + self.client_wants_uuid_file_names = False self._debug("All IP addresses", get_all_ips()) diff --git a/src/calibre/devices/teclast/driver.py b/src/calibre/devices/teclast/driver.py index 192f03cb45..acd20308ad 100644 --- a/src/calibre/devices/teclast/driver.py +++ b/src/calibre/devices/teclast/driver.py @@ -19,9 +19,10 @@ class TECLAST_K3(USBMS): PRODUCT_ID = [0x3203] BCD = [0x0000, 0x0100] - VENDOR_NAME = ['TECLAST', 'IMAGIN', 'RK28XX', 'PER3274B', 'BEBOOK'] + VENDOR_NAME = ['TECLAST', 'IMAGIN', 'RK28XX', 'PER3274B', 'BEBOOK', + 'RK2728', 'MR700'] WINDOWS_MAIN_MEM = WINDOWS_CARD_A_MEM = ['DIGITAL_PLAYER', 'TL-K5', - 'EREADER', 'USB-MSC', 'PER3274B', 'BEBOOK'] + 'EREADER', 'USB-MSC', 'PER3274B', 'BEBOOK', 'USER'] MAIN_MEMORY_VOLUME_LABEL = 'K3 Main Memory' STORAGE_CARD_VOLUME_LABEL = 'K3 Storage Card' diff --git a/src/calibre/ebooks/conversion/plugins/pdf_output.py b/src/calibre/ebooks/conversion/plugins/pdf_output.py index da66a9be0d..b9d016de2d 100644 --- a/src/calibre/ebooks/conversion/plugins/pdf_output.py +++ b/src/calibre/ebooks/conversion/plugins/pdf_output.py @@ -8,56 +8,38 @@ __docformat__ = 'restructuredtext en' Convert OEB ebook format to PDF. ''' -import glob -import os +import glob, os -from calibre.customize.conversion import OutputFormatPlugin, \ - OptionRecommendation +from calibre.constants import iswindows, islinux +from calibre.customize.conversion import (OutputFormatPlugin, + OptionRecommendation) from calibre.ptempfile import TemporaryDirectory -from calibre.constants import iswindows -UNITS = [ - 'millimeter', - 'point', - 'inch' , - 'pica' , - 'didot', - 'cicero', - 'devicepixel', - ] +UNITS = ['millimeter', 'centimeter', 'point', 'inch' , 'pica' , 'didot', + 'cicero', 'devicepixel'] -PAPER_SIZES = ['b2', - 'a9', - 'executive', - 'tabloid', - 'b4', - 'b5', - 'b6', - 'b7', - 'b0', - 'b1', - 'letter', - 'b3', - 'a7', - 'a8', - 'b8', - 'b9', - 'a3', - 'a1', - 'folio', - 'c5e', - 'dle', - 'a0', - 'ledger', - 'legal', - 'a6', - 'a2', - 'b10', - 'a5', - 'comm10e', - 'a4'] +PAPER_SIZES = [u'a0', u'a1', u'a2', u'a3', u'a4', u'a5', u'a6', u'b0', u'b1', + u'b2', u'b3', u'b4', u'b5', u'b6', u'legal', u'letter'] -ORIENTATIONS = ['portrait', 'landscape'] +class PDFMetadata(object): # {{{ + def __init__(self, oeb_metadata=None): + from calibre import force_unicode + from calibre.ebooks.metadata import authors_to_string + self.title = _(u'Unknown') + self.author = _(u'Unknown') + self.tags = u'' + + if oeb_metadata != None: + if len(oeb_metadata.title) >= 1: + self.title = oeb_metadata.title[0].value + if len(oeb_metadata.creator) >= 1: + self.author = authors_to_string([x.value for x in oeb_metadata.creator]) + if oeb_metadata.subject: + self.tags = u', '.join(map(unicode, oeb_metadata.subject)) + + self.title = force_unicode(self.title) + self.author = force_unicode(self.author) +# }}} class PDFOutput(OutputFormatPlugin): @@ -66,9 +48,14 @@ class PDFOutput(OutputFormatPlugin): file_type = 'pdf' options = set([ + OptionRecommendation(name='override_profile_size', recommended_value=False, + help=_('Normally, the PDF page size is set by the output profile' + ' chosen under page options. This option will cause the ' + ' page size settings under PDF Output to override the ' + ' size specified by the output profile.')), OptionRecommendation(name='unit', recommended_value='inch', level=OptionRecommendation.LOW, short_switch='u', choices=UNITS, - help=_('The unit of measure. Default is inch. Choices ' + help=_('The unit of measure for page sizes. Default is inch. Choices ' 'are %s ' 'Note: This does not override the unit for margins!') % UNITS), OptionRecommendation(name='paper_size', recommended_value='letter', @@ -80,23 +67,19 @@ class PDFOutput(OutputFormatPlugin): help=_('Custom size of the document. Use the form widthxheight ' 'EG. `123x321` to specify the width and height. ' 'This overrides any specified paper-size.')), - OptionRecommendation(name='orientation', recommended_value='portrait', - level=OptionRecommendation.LOW, choices=ORIENTATIONS, - help=_('The orientation of the page. Default is portrait. Choices ' - 'are %s') % ORIENTATIONS), OptionRecommendation(name='preserve_cover_aspect_ratio', recommended_value=False, help=_('Preserve the aspect ratio of the cover, instead' ' of stretching it to fill the full first page of the' ' generated pdf.')), OptionRecommendation(name='pdf_serif_family', - recommended_value='Times New Roman', help=_( + recommended_value='Liberation Serif' if islinux else 'Times New Roman', help=_( 'The font family used to render serif fonts')), OptionRecommendation(name='pdf_sans_family', - recommended_value='Helvetica', help=_( + recommended_value='Liberation Sans' if islinux else 'Helvetica', help=_( 'The font family used to render sans-serif fonts')), OptionRecommendation(name='pdf_mono_family', - recommended_value='Courier New', help=_( + recommended_value='Liberation Mono' if islinux else 'Courier New', help=_( 'The font family used to render monospaced fonts')), OptionRecommendation(name='pdf_standard_font', choices=['serif', 'sans', 'mono'], @@ -108,9 +91,21 @@ class PDFOutput(OutputFormatPlugin): OptionRecommendation(name='pdf_mono_font_size', recommended_value=16, help=_( 'The default font size for monospaced text')), + OptionRecommendation(name='pdf_mark_links', recommended_value=False, + help=_('Surround all links with a red box, useful for debugging.')), + OptionRecommendation(name='old_pdf_engine', recommended_value=False, + help=_('Use the old, less capable engine to generate the PDF')), + OptionRecommendation(name='uncompressed_pdf', + recommended_value=False, help=_( + 'Generate an uncompressed PDF, useful for debugging, ' + 'only works with the new PDF engine.')), ]) def convert(self, oeb_book, output_path, input_plugin, opts, log): + from calibre.gui2 import must_use_qt, load_builtin_fonts + must_use_qt() + load_builtin_fonts() + self.oeb = oeb_book self.input_plugin, self.opts, self.log = input_plugin, opts, log self.output_path = output_path @@ -144,9 +139,8 @@ class PDFOutput(OutputFormatPlugin): If you ever move to Qt WebKit 2.3+ then this will be unnecessary. ''' from calibre.ebooks.oeb.base import urlnormalize - from calibre.gui2 import must_use_qt - from calibre.utils.fonts.utils import get_font_names, remove_embed_restriction - from PyQt4.Qt import QFontDatabase, QByteArray + from calibre.utils.fonts.utils import remove_embed_restriction + from PyQt4.Qt import QFontDatabase, QByteArray, QRawFont, QFont # First find all @font-face rules and remove them, adding the embedded # fonts to Qt @@ -174,12 +168,13 @@ class PDFOutput(OutputFormatPlugin): raw = remove_embed_restriction(raw) except: continue - must_use_qt() - QFontDatabase.addApplicationFontFromData(QByteArray(raw)) - try: - family_name = get_font_names(raw)[0] - except: - family_name = None + fid = QFontDatabase.addApplicationFontFromData(QByteArray(raw)) + family_name = None + if fid > -1: + try: + family_name = unicode(QFontDatabase.applicationFontFamilies(fid)[0]) + except (IndexError, KeyError): + pass if family_name: family_map[icu_lower(font_family)] = family_name @@ -188,6 +183,7 @@ class PDFOutput(OutputFormatPlugin): # Now map the font family name specified in the css to the actual # family name of the embedded font (they may be different in general). + font_warnings = set() for item in self.oeb.manifest: if not hasattr(item.data, 'cssRules'): continue for i, rule in enumerate(item.data.cssRules): @@ -196,37 +192,41 @@ class PDFOutput(OutputFormatPlugin): if ff is None: continue val = ff.propertyValue for i in xrange(val.length): - k = icu_lower(val[i].value) + try: + k = icu_lower(val[i].value) + except (AttributeError, TypeError): + val[i].value = k = 'times' if k in family_map: val[i].value = family_map[k] - - def remove_font_specification(self): - # Qt produces image based pdfs on windows when non-generic fonts are specified - # This might change in Qt WebKit 2.3+ you will have to test. - for item in self.oeb.manifest: - if not hasattr(item.data, 'cssRules'): continue - for i, rule in enumerate(item.data.cssRules): - if rule.type != rule.STYLE_RULE: continue - ff = rule.style.getProperty('font-family') - if ff is None: continue - val = ff.propertyValue - for i in xrange(val.length): - k = icu_lower(val[i].value) - if k not in {'serif', 'sans', 'sans-serif', 'sansserif', - 'monospace', 'cursive', 'fantasy'}: - val[i].value = '' + if iswindows: + # On windows, Qt uses GDI which does not support OpenType + # (CFF) fonts, so we need to nuke references to OpenType + # fonts. Note that you could compile QT with configure + # -directwrite, but that requires atleast Vista SP2 + for i in xrange(val.length): + family = val[i].value + if family: + f = QRawFont.fromFont(QFont(family)) + if len(f.fontTable('head')) == 0: + if family not in font_warnings: + self.log.warn('Ignoring unsupported font: %s' + %family) + font_warnings.add(family) + # Either a bitmap or (more likely) a CFF font + val[i].value = 'times' def convert_text(self, oeb_book): - from calibre.ebooks.pdf.writer import PDFWriter from calibre.ebooks.metadata.opf2 import OPF + if self.opts.old_pdf_engine: + from calibre.ebooks.pdf.writer import PDFWriter + PDFWriter + else: + from calibre.ebooks.pdf.render.from_html import PDFWriter self.log.debug('Serializing oeb input to disk for processing...') self.get_cover_data() - if iswindows: - self.remove_font_specification() - else: - self.handle_embedded_fonts() + self.handle_embedded_fonts() with TemporaryDirectory('_pdf_out') as oeb_dir: from calibre.customize.ui import plugin_for_output_format @@ -240,9 +240,9 @@ class PDFOutput(OutputFormatPlugin): 'toc', None)) def write(self, Writer, items, toc): - from calibre.ebooks.pdf.writer import PDFMetadata writer = Writer(self.opts, self.log, cover_data=self.cover_data, toc=toc) + writer.report_progress = self.report_progress close = False if not hasattr(self.output_path, 'write'): @@ -256,7 +256,15 @@ class PDFOutput(OutputFormatPlugin): out_stream.seek(0) out_stream.truncate() self.log.debug('Rendering pages to PDF...') - writer.dump(items, out_stream, PDFMetadata(self.metadata)) + import time + st = time.time() + if False: + import cProfile + cProfile.runctx('writer.dump(items, out_stream, PDFMetadata(self.metadata))', + globals(), locals(), '/tmp/profile') + else: + writer.dump(items, out_stream, PDFMetadata(self.metadata)) + self.log('Rendered PDF in %g seconds:'%(time.time()-st)) if close: out_stream.close() diff --git a/src/calibre/ebooks/conversion/plumber.py b/src/calibre/ebooks/conversion/plumber.py index 8c5a99b64e..11e434c635 100644 --- a/src/calibre/ebooks/conversion/plumber.py +++ b/src/calibre/ebooks/conversion/plumber.py @@ -1125,7 +1125,7 @@ OptionRecommendation(name='search_replace', RemoveFakeMargins()(self.oeb, self.log, self.opts) RemoveAdobeMargins()(self.oeb, self.log, self.opts) - if self.opts.subset_embedded_fonts: + if self.opts.subset_embedded_fonts and self.output_plugin.file_type != 'pdf': from calibre.ebooks.oeb.transforms.subset import SubsetFonts SubsetFonts()(self.oeb, self.log, self.opts) diff --git a/src/calibre/ebooks/conversion/utils.py b/src/calibre/ebooks/conversion/utils.py index ddeba79365..2dc91b7530 100644 --- a/src/calibre/ebooks/conversion/utils.py +++ b/src/calibre/ebooks/conversion/utils.py @@ -335,32 +335,50 @@ class HeuristicProcessor(object): This function intentionally leaves hyphenated content alone as that is handled by the dehyphenate routine in a separate step ''' + def style_unwrap(match): + style_close = match.group('style_close') + style_open = match.group('style_open') + if style_open and style_close: + return style_close+' '+style_open + elif style_open and not style_close: + return ' '+style_open + elif not style_open and style_close: + return style_close+' ' + else: + return ' ' + # define the pieces of the regex lookahead = "(?<=.{"+str(length)+u"}([a-zäëïöüàèìòùáćéíĺóŕńśúýâêîôûçąężıãõñæøþðßěľščťžňďřů,:)\IA\u00DF]|(?\s*()?" + line_ending = "\s*(?P)?\s*()?" blanklines = "\s*(?P<(p|span|div)[^>]*>\s*(<(p|span|div)[^>]*>\s*\s*)\s*){0,3}\s*" - line_opening = "<(span|[iubp]|div)[^>]*>\s*(<(span|[iubp]|div)[^>]*>)?\s*" + line_opening = "<(p|div)[^>]*>\s*(?P<(span|[iub])[^>]*>)?\s*" txt_line_wrap = u"((\u0020|\u0009)*\n){1,4}" - unwrap_regex = lookahead+line_ending+blanklines+line_opening - em_en_unwrap_regex = em_en_lookahead+line_ending+blanklines+line_opening - shy_unwrap_regex = soft_hyphen+line_ending+blanklines+line_opening - if format == 'txt': unwrap_regex = lookahead+txt_line_wrap em_en_unwrap_regex = em_en_lookahead+txt_line_wrap shy_unwrap_regex = soft_hyphen+txt_line_wrap + else: + unwrap_regex = lookahead+line_ending+blanklines+line_opening + em_en_unwrap_regex = em_en_lookahead+line_ending+blanklines+line_opening + shy_unwrap_regex = soft_hyphen+line_ending+blanklines+line_opening unwrap = re.compile(u"%s" % unwrap_regex, re.UNICODE) em_en_unwrap = re.compile(u"%s" % em_en_unwrap_regex, re.UNICODE) shy_unwrap = re.compile(u"%s" % shy_unwrap_regex, re.UNICODE) - content = unwrap.sub(' ', content) - content = em_en_unwrap.sub('', content) - content = shy_unwrap.sub('', content) + if format == 'txt': + content = unwrap.sub(' ', content) + content = em_en_unwrap.sub('', content) + content = shy_unwrap.sub('', content) + else: + content = unwrap.sub(style_unwrap, content) + content = em_en_unwrap.sub(style_unwrap, content) + content = shy_unwrap.sub(style_unwrap, content) + return content def txt_process(self, match): diff --git a/src/calibre/ebooks/html/input.py b/src/calibre/ebooks/html/input.py index c2ec6f9bce..9683837ad6 100644 --- a/src/calibre/ebooks/html/input.py +++ b/src/calibre/ebooks/html/input.py @@ -17,7 +17,7 @@ from urllib import unquote from calibre.ebooks.chardet import detect_xml_encoding from calibre.constants import iswindows -from calibre import unicode_path, as_unicode +from calibre import unicode_path, as_unicode, replace_entities class Link(object): ''' @@ -147,6 +147,7 @@ class HTMLFile(object): url = match.group(i) if url: break + url = replace_entities(url) try: link = self.resolve(url) except ValueError: diff --git a/src/calibre/ebooks/lit/reader.py b/src/calibre/ebooks/lit/reader.py index a673de87d7..98b230e5bb 100644 --- a/src/calibre/ebooks/lit/reader.py +++ b/src/calibre/ebooks/lit/reader.py @@ -11,13 +11,17 @@ import struct, os, functools, re from urlparse import urldefrag from cStringIO import StringIO from urllib import unquote as urlunquote + +from lxml import etree + from calibre.ebooks.lit import LitError from calibre.ebooks.lit.maps import OPF_MAP, HTML_MAP import calibre.ebooks.lit.mssha1 as mssha1 -from calibre.ebooks.oeb.base import urlnormalize +from calibre.ebooks.oeb.base import urlnormalize, xpath from calibre.ebooks.oeb.reader import OEBReader from calibre.ebooks import DRMError from calibre import plugins + lzx, lxzerror = plugins['lzx'] msdes, msdeserror = plugins['msdes'] @@ -907,3 +911,16 @@ class LitReader(OEBReader): Container = LitContainer DEFAULT_PROFILE = 'MSReader' + def _spine_from_opf(self, opf): + manifest = self.oeb.manifest + for elem in xpath(opf, '/o2:package/o2:spine/o2:itemref'): + idref = elem.get('idref') + if idref not in manifest.ids: + continue + item = manifest.ids[idref] + if (item.media_type.lower() == 'application/xml' and + hasattr(item.data, 'xpath') and item.data.xpath('/html')): + item.media_type = 'application/xhtml+xml' + item.data = item._parse_xhtml(etree.tostring(item.data)) + super(LitReader, self)._spine_from_opf(opf) + diff --git a/src/calibre/ebooks/lrf/__init__.py b/src/calibre/ebooks/lrf/__init__.py index 725338ead8..d99d914ed0 100644 --- a/src/calibre/ebooks/lrf/__init__.py +++ b/src/calibre/ebooks/lrf/__init__.py @@ -41,7 +41,6 @@ def find_custom_fonts(options, logger): if options.serif_family: f = family(options.serif_family) fonts['serif'] = font_scanner.legacy_fonts_for_family(f) - print (111111, fonts['serif']) if not fonts['serif']: logger.warn('Unable to find serif family %s'%f) if options.sans_family: diff --git a/src/calibre/ebooks/metadata/epub.py b/src/calibre/ebooks/metadata/epub.py index bc81df5a79..e216610ad5 100644 --- a/src/calibre/ebooks/metadata/epub.py +++ b/src/calibre/ebooks/metadata/epub.py @@ -291,6 +291,8 @@ def set_metadata(stream, mi, apply_null=False, update_timestamp=False): reader.opf.smart_update(mi) + if getattr(mi, 'uuid', None): + reader.opf.application_id = mi.uuid if apply_null: if not getattr(mi, 'series', None): reader.opf.series = None diff --git a/src/calibre/ebooks/metadata/mobi.py b/src/calibre/ebooks/metadata/mobi.py index e701946c01..e223dcac23 100644 --- a/src/calibre/ebooks/metadata/mobi.py +++ b/src/calibre/ebooks/metadata/mobi.py @@ -390,6 +390,10 @@ class MetadataUpdater(object): not added_501 and not share_not_sync): from uuid import uuid4 update_exth_record((113, str(uuid4()))) + # Add a 112 record with actual UUID + if getattr(mi, 'uuid', None): + update_exth_record((112, + (u"calibre:%s" % mi.uuid).encode(self.codec, 'replace'))) if 503 in self.original_exth_records: update_exth_record((503, mi.title.encode(self.codec, 'replace'))) diff --git a/src/calibre/ebooks/metadata/opf2.py b/src/calibre/ebooks/metadata/opf2.py index 3e5d95f1ce..92287589eb 100644 --- a/src/calibre/ebooks/metadata/opf2.py +++ b/src/calibre/ebooks/metadata/opf2.py @@ -941,12 +941,11 @@ class OPF(object): # {{{ return self.get_text(match) or None def fset(self, val): - matches = self.application_id_path(self.metadata) - if not matches: - attrib = {'{%s}scheme'%self.NAMESPACES['opf']: 'calibre'} - matches = [self.create_metadata_element('identifier', - attrib=attrib)] - self.set_text(matches[0], unicode(val)) + for x in tuple(self.application_id_path(self.metadata)): + x.getparent().remove(x) + attrib = {'{%s}scheme'%self.NAMESPACES['opf']: 'calibre'} + self.set_text(self.create_metadata_element( + 'identifier', attrib=attrib), unicode(val)) return property(fget=fget, fset=fset) diff --git a/src/calibre/ebooks/metadata/sources/amazon.py b/src/calibre/ebooks/metadata/sources/amazon.py index 6e24f4caf7..63783ba8eb 100644 --- a/src/calibre/ebooks/metadata/sources/amazon.py +++ b/src/calibre/ebooks/metadata/sources/amazon.py @@ -75,6 +75,20 @@ class Worker(Thread): # Get details {{{ 9: ['sept'], 12: ['déc'], }, + 'br': { + 1: ['janeiro'], + 2: ['fevereiro'], + 3: ['março'], + 4: ['abril'], + 5: ['maio'], + 6: ['junho'], + 7: ['julho'], + 8: ['agosto'], + 9: ['setembro'], + 10: ['outubro'], + 11: ['novembro'], + 12: ['dezembro'], + }, 'es': { 1: ['enero'], 2: ['febrero'], @@ -89,7 +103,7 @@ class Worker(Thread): # Get details {{{ 11: ['noviembre'], 12: ['diciembre'], }, - 'jp': { + 'jp': { 1: [u'1月'], 2: [u'2月'], 3: [u'3月'], @@ -117,6 +131,7 @@ class Worker(Thread): # Get details {{{ text()="Product details" or \ text()="Détails sur le produit" or \ text()="Detalles del producto" or \ + text()="Detalhes do produto" or \ text()="登録情報"]/../div[@class="content"] ''' # Editor: is for Spanish @@ -126,6 +141,7 @@ class Worker(Thread): # Get details {{{ starts-with(text(), "Editore:") or \ starts-with(text(), "Editeur") or \ starts-with(text(), "Editor:") or \ + starts-with(text(), "Editora:") or \ starts-with(text(), "出版社:")] ''' self.language_xpath = ''' @@ -141,7 +157,7 @@ class Worker(Thread): # Get details {{{ ''' self.ratings_pat = re.compile( - r'([0-9.]+) ?(out of|von|su|étoiles sur|つ星のうち|de un máximo de) ([\d\.]+)( (stars|Sternen|stelle|estrellas)){0,1}') + r'([0-9.]+) ?(out of|von|su|étoiles sur|つ星のうち|de un máximo de|de) ([\d\.]+)( (stars|Sternen|stelle|estrellas|estrelas)){0,1}') lm = { 'eng': ('English', 'Englisch'), @@ -150,6 +166,7 @@ class Worker(Thread): # Get details {{{ 'deu': ('German', 'Deutsch'), 'spa': ('Spanish', 'Espa\xf1ol', 'Espaniol'), 'jpn': ('Japanese', u'日本語'), + 'por': ('Portuguese', 'Português'), } self.lang_map = {} for code, names in lm.iteritems(): @@ -435,7 +452,7 @@ class Worker(Thread): # Get details {{{ def parse_cover(self, root): - imgs = root.xpath('//img[@id="prodImage" and @src]') + imgs = root.xpath('//img[(@id="prodImage" or @id="original-main-image") and @src]') if imgs: src = imgs[0].get('src') if '/no-image-avail' not in src: @@ -505,6 +522,7 @@ class Amazon(Source): 'it' : _('Italy'), 'jp' : _('Japan'), 'es' : _('Spain'), + 'br' : _('Brazil'), } options = ( @@ -570,6 +588,8 @@ class Amazon(Source): url = 'http://amzn.com/'+asin elif domain == 'uk': url = 'http://www.amazon.co.uk/dp/'+asin + elif domain == 'br': + url = 'http://www.amazon.com.br/dp/'+asin else: url = 'http://www.amazon.%s/dp/%s'%(domain, asin) if url: @@ -629,7 +649,7 @@ class Amazon(Source): q['field-isbn'] = isbn else: # Only return book results - q['search-alias'] = 'stripbooks' + q['search-alias'] = 'digital-text' if domain == 'br' else 'stripbooks' if title: title_tokens = list(self.get_title_tokens(title)) if title_tokens: @@ -661,6 +681,8 @@ class Amazon(Source): udomain = 'co.uk' elif domain == 'jp': udomain = 'co.jp' + elif domain == 'br': + udomain = 'com.br' url = 'http://www.amazon.%s/s/?'%udomain + urlencode(encoded_q) return url, domain @@ -978,6 +1000,16 @@ if __name__ == '__main__': # tests {{{ ), ] # }}} + br_tests = [ # {{{ + ( + {'title':'Guerra dos Tronos'}, + [title_test('A Guerra dos Tronos - As Crônicas de Gelo e Fogo', + exact=True), authors_test(['George R. R. Martin']) + ] + + ), + ] # }}} + def do_test(domain, start=0, stop=None): tests = globals().get(domain+'_tests') if stop is None: @@ -988,7 +1020,7 @@ if __name__ == '__main__': # tests {{{ do_test('com') - #do_test('de') + # do_test('de') # }}} diff --git a/src/calibre/ebooks/metadata/sources/identify.py b/src/calibre/ebooks/metadata/sources/identify.py index 16aa39d59d..4a214d4e66 100644 --- a/src/calibre/ebooks/metadata/sources/identify.py +++ b/src/calibre/ebooks/metadata/sources/identify.py @@ -483,8 +483,8 @@ def identify(log, abort, # {{{ log('The identify phase took %.2f seconds'%(time.time() - start_time)) log('The longest time (%f) was taken by:'%longest, lp) - log('Merging results from different sources and finding earliest', - 'publication dates from the xisbn service') + log('Merging results from different sources and finding earliest ', + 'publication dates from the worldcat.org service') start_time = time.time() results = merge_identify_results(results, log) diff --git a/src/calibre/ebooks/mobi/reader/headers.py b/src/calibre/ebooks/mobi/reader/headers.py index ac7e076787..5a3de7e705 100644 --- a/src/calibre/ebooks/mobi/reader/headers.py +++ b/src/calibre/ebooks/mobi/reader/headers.py @@ -126,6 +126,7 @@ class EXTHHeader(object): # {{{ elif idx == 113: # ASIN or other id try: self.uuid = content.decode('ascii') + self.mi.set_identifier('mobi-asin', self.uuid) except: self.uuid = None elif idx == 116: diff --git a/src/calibre/ebooks/mobi/writer8/exth.py b/src/calibre/ebooks/mobi/writer8/exth.py index a060e338d1..31792d2156 100644 --- a/src/calibre/ebooks/mobi/writer8/exth.py +++ b/src/calibre/ebooks/mobi/writer8/exth.py @@ -110,6 +110,12 @@ def build_exth(metadata, prefer_author_sort=False, is_periodical=False, exth.write(uuid) nrecs += 1 + # Write UUID as SOURCE + c_uuid = b'calibre:%s' % uuid + exth.write(pack(b'>II', 112, len(c_uuid) + 8)) + exth.write(c_uuid) + nrecs += 1 + # Write cdetype if not is_periodical: if not share_not_sync: diff --git a/src/calibre/ebooks/oeb/display/indexing.coffee b/src/calibre/ebooks/oeb/display/indexing.coffee index 48f0697506..cd4a5a83e5 100644 --- a/src/calibre/ebooks/oeb/display/indexing.coffee +++ b/src/calibre/ebooks/oeb/display/indexing.coffee @@ -92,6 +92,31 @@ class BookIndexing this.last_check = [body.scrollWidth, body.scrollHeight] return ans + all_links_and_anchors: () -> + body = document.body + links = [] + anchors = {} + for a in document.querySelectorAll("body a[href], body [id], body a[name]") + if window.paged_display?.in_paged_mode + geom = window.paged_display.column_location(a) + else + br = a.getBoundingClientRect() + [left, top] = viewport_to_document(br.left, br.top, a.ownerDocument) + geom = {'left':left, 'top':top, 'width':br.right-br.left, 'height':br.bottom-br.top} + + href = a.getAttribute('href') + if href + links.push([href, geom]) + id = a.getAttribute("id") + if id and id not in anchors + anchors[id] = geom + if a.tagName in ['A', "a"] + name = a.getAttribute("name") + if name and name not in anchors + anchors[name] = geom + + return {'links':links, 'anchors':anchors} + if window? window.book_indexing = new BookIndexing() diff --git a/src/calibre/ebooks/oeb/display/paged.coffee b/src/calibre/ebooks/oeb/display/paged.coffee index c5f2dbc97a..aea51b5b23 100644 --- a/src/calibre/ebooks/oeb/display/paged.coffee +++ b/src/calibre/ebooks/oeb/display/paged.coffee @@ -242,6 +242,18 @@ class PagedDisplay # Return the number of the column that contains xpos return Math.floor(xpos/this.page_width) + column_location: (elem) -> + # Return the location of elem relative to its containing column + br = elem.getBoundingClientRect() + [left, top] = calibre_utils.viewport_to_document(br.left, br.top, elem.ownerDocument) + c = this.column_at(left) + width = Math.min(br.right, (c+1)*this.page_width) - br.left + if br.bottom < br.top + br.bottom = window.innerHeight + height = Math.min(br.bottom, window.innerHeight) - br.top + left -= c*this.page_width + return {'column':c, 'left':left, 'top':top, 'width':width, 'height':height} + column_boundaries: () -> # Return the column numbers at the left edge and after the right edge # of the viewport diff --git a/src/calibre/ebooks/oeb/transforms/flatcss.py b/src/calibre/ebooks/oeb/transforms/flatcss.py index 28ab4a362c..12fbd3b7f1 100644 --- a/src/calibre/ebooks/oeb/transforms/flatcss.py +++ b/src/calibre/ebooks/oeb/transforms/flatcss.py @@ -356,7 +356,7 @@ class CSSFlattener(object): if 'bgcolor' in node.attrib: try: cssdict['background-color'] = Property('background-color', node.attrib['bgcolor']).value - except ValueError: + except (ValueError, SyntaxErr): pass del node.attrib['bgcolor'] if cssdict.get('font-weight', '').lower() == 'medium': diff --git a/src/calibre/ebooks/oeb/transforms/metadata.py b/src/calibre/ebooks/oeb/transforms/metadata.py index bfa52368a4..6da1e4c529 100644 --- a/src/calibre/ebooks/oeb/transforms/metadata.py +++ b/src/calibre/ebooks/oeb/transforms/metadata.py @@ -115,8 +115,11 @@ class MergeMetadata(object): if mi.uuid is not None: m.filter('identifier', lambda x:x.id=='uuid_id') self.oeb.metadata.add('identifier', mi.uuid, id='uuid_id', - scheme='uuid') + scheme='uuid') self.oeb.uid = self.oeb.metadata.identifier[-1] + if mi.application_id is not None: + m.filter('identifier', lambda x:x.scheme=='calibre') + self.oeb.metadata.add('identifier', mi.application_id, scheme='calibre') def set_cover(self, mi, prefer_metadata_cover): cdata, ext = '', 'jpg' diff --git a/src/calibre/ebooks/oeb/transforms/subset.py b/src/calibre/ebooks/oeb/transforms/subset.py index f85f786547..0cca73fcce 100644 --- a/src/calibre/ebooks/oeb/transforms/subset.py +++ b/src/calibre/ebooks/oeb/transforms/subset.py @@ -36,7 +36,15 @@ class SubsetFonts(object): self.oeb.manifest.remove(font['item']) font['rule'].parentStyleSheet.deleteRule(font['rule']) + fonts = {} for font in self.embedded_fonts: + item, chars = font['item'], font['chars'] + if item.href in fonts: + fonts[item.href]['chars'] |= chars + else: + fonts[item.href] = font + + for font in fonts.itervalues(): if not font['chars']: self.log('The font %s is unused. Removing it.'%font['src']) remove(font) diff --git a/src/calibre/gui2/duplicates.py b/src/calibre/ebooks/pdf/render/__init__.py similarity index 61% rename from src/calibre/gui2/duplicates.py rename to src/calibre/ebooks/pdf/render/__init__.py index 6e45b0e9e6..743a9d0561 100644 --- a/src/calibre/gui2/duplicates.py +++ b/src/calibre/ebooks/pdf/render/__init__.py @@ -1,10 +1,11 @@ #!/usr/bin/env python -# vim:fileencoding=UTF-8:ts=4:sw=4:sta:et:sts=4:ai +# vim:fileencoding=UTF-8:ts=4:sw=4:sta:et:sts=4:fdm=marker:ai from __future__ import (unicode_literals, division, absolute_import, print_function) __license__ = 'GPL v3' -__copyright__ = '2011, Kovid Goyal ' +__copyright__ = '2012, Kovid Goyal ' __docformat__ = 'restructuredtext en' + diff --git a/src/calibre/ebooks/pdf/render/common.py b/src/calibre/ebooks/pdf/render/common.py new file mode 100644 index 0000000000..d5f4b6a66b --- /dev/null +++ b/src/calibre/ebooks/pdf/render/common.py @@ -0,0 +1,196 @@ +#!/usr/bin/env python +# vim:fileencoding=UTF-8:ts=4:sw=4:sta:et:sts=4:fdm=marker:ai +from __future__ import (unicode_literals, division, absolute_import, + print_function) + +__license__ = 'GPL v3' +__copyright__ = '2012, Kovid Goyal ' +__docformat__ = 'restructuredtext en' + +import codecs, zlib +from io import BytesIO +from datetime import datetime + +from calibre.constants import plugins, ispy3 + +pdf_float = plugins['speedup'][0].pdf_float + +EOL = b'\n' + +# Sizes {{{ +inch = 72.0 +cm = inch / 2.54 +mm = cm * 0.1 +pica = 12.0 +didot = 0.375 * mm +cicero = 12 * didot + +_W, _H = (21*cm, 29.7*cm) + +A6 = (_W*.5, _H*.5) +A5 = (_H*.5, _W) +A4 = (_W, _H) +A3 = (_H, _W*2) +A2 = (_W*2, _H*2) +A1 = (_H*2, _W*4) +A0 = (_W*4, _H*4) + +LETTER = (8.5*inch, 11*inch) +LEGAL = (8.5*inch, 14*inch) +ELEVENSEVENTEEN = (11*inch, 17*inch) + +_BW, _BH = (25*cm, 35.3*cm) +B6 = (_BW*.5, _BH*.5) +B5 = (_BH*.5, _BW) +B4 = (_BW, _BH) +B3 = (_BH*2, _BW) +B2 = (_BW*2, _BH*2) +B1 = (_BH*4, _BW*2) +B0 = (_BW*4, _BH*4) + +PAPER_SIZES = {k:globals()[k.upper()] for k in ('a0 a1 a2 a3 a4 a5 a6 b0 b1 b2' + ' b3 b4 b5 b6 letter legal').split()} + +# }}} + +# Basic PDF datatypes {{{ + +ic = str if ispy3 else unicode +icb = (lambda x: str(x).encode('ascii')) if ispy3 else bytes + +def fmtnum(o): + if isinstance(o, float): + return pdf_float(o) + return ic(o) + +def serialize(o, stream): + if isinstance(o, float): + stream.write_raw(pdf_float(o).encode('ascii')) + elif isinstance(o, bool): + # Must check bool before int as bools are subclasses of int + stream.write_raw(b'true' if o else b'false') + elif isinstance(o, (int, long)): + stream.write_raw(icb(o)) + elif hasattr(o, 'pdf_serialize'): + o.pdf_serialize(stream) + elif o is None: + stream.write_raw(b'null') + elif isinstance(o, datetime): + val = o.strftime("D:%Y%m%d%H%M%%02d%z")%min(59, o.second) + if datetime.tzinfo is not None: + val = "(%s'%s')"%(val[:-2], val[-2:]) + stream.write(val.encode('ascii')) + else: + raise ValueError('Unknown object: %r'%o) + +class Name(unicode): + + def pdf_serialize(self, stream): + raw = self.encode('ascii') + if len(raw) > 126: + raise ValueError('Name too long: %r'%self) + buf = [x if 33 < ord(x) < 126 and x != b'#' else b'#'+hex(ord(x)) for x + in raw] + stream.write(b'/'+b''.join(buf)) + +class String(unicode): + + def pdf_serialize(self, stream): + s = self.replace('\\', '\\\\').replace('(', r'\(').replace(')', r'\)') + try: + raw = s.encode('latin1') + if raw.startswith(codecs.BOM_UTF16_BE): + raise UnicodeEncodeError('') + except UnicodeEncodeError: + raw = codecs.BOM_UTF16_BE + s.encode('utf-16-be') + stream.write(b'('+raw+b')') + +class Dictionary(dict): + + def pdf_serialize(self, stream): + stream.write(b'<<' + EOL) + sorted_keys = sorted(self.iterkeys(), + key=lambda x:({'Type':'1', 'Subtype':'2'}.get( + x, x)+x)) + for k in sorted_keys: + serialize(Name(k), stream) + stream.write(b' ') + serialize(self[k], stream) + stream.write(EOL) + stream.write(b'>>' + EOL) + +class InlineDictionary(Dictionary): + + def pdf_serialize(self, stream): + stream.write(b'<< ') + for k, v in self.iteritems(): + serialize(Name(k), stream) + stream.write(b' ') + serialize(v, stream) + stream.write(b' ') + stream.write(b'>>') + +class Array(list): + + def pdf_serialize(self, stream): + stream.write(b'[') + for i, o in enumerate(self): + if i != 0: + stream.write(b' ') + serialize(o, stream) + stream.write(b']') + +class Stream(BytesIO): + + def __init__(self, compress=False): + BytesIO.__init__(self) + self.compress = compress + self.filters = Array() + + def add_extra_keys(self, d): + pass + + def pdf_serialize(self, stream): + raw = self.getvalue() + dl = len(raw) + filters = self.filters + if self.compress: + filters.append(Name('FlateDecode')) + raw = zlib.compress(raw) + + d = InlineDictionary({'Length':len(raw), 'DL':dl}) + self.add_extra_keys(d) + if filters: + d['Filter'] = filters + serialize(d, stream) + stream.write(EOL+b'stream'+EOL) + stream.write(raw) + stream.write(EOL+b'endstream'+EOL) + + def write_line(self, raw=b''): + self.write(raw if isinstance(raw, bytes) else raw.encode('ascii')) + self.write(EOL) + + def write(self, raw): + super(Stream, self).write(raw if isinstance(raw, bytes) else + raw.encode('ascii')) + + def write_raw(self, raw): + BytesIO.write(self, raw) + +class Reference(object): + + def __init__(self, num, obj): + self.num, self.obj = num, obj + + def pdf_serialize(self, stream): + raw = '%d 0 R'%self.num + stream.write(raw.encode('ascii')) + + def __repr__(self): + return '%d 0 R'%self.num + + def __str__(self): + return repr(self) +# }}} + diff --git a/src/calibre/ebooks/pdf/render/engine.py b/src/calibre/ebooks/pdf/render/engine.py new file mode 100644 index 0000000000..1be8613cea --- /dev/null +++ b/src/calibre/ebooks/pdf/render/engine.py @@ -0,0 +1,370 @@ +#!/usr/bin/env python +# vim:fileencoding=UTF-8:ts=4:sw=4:sta:et:sts=4:fdm=marker:ai +from __future__ import (unicode_literals, division, absolute_import, + print_function) + +__license__ = 'GPL v3' +__copyright__ = '2012, Kovid Goyal ' +__docformat__ = 'restructuredtext en' + +import sys, traceback +from collections import namedtuple +from functools import wraps, partial +from future_builtins import map + +import sip +from PyQt4.Qt import (QPaintEngine, QPaintDevice, Qt, QTransform, QBrush) + +from calibre.constants import plugins +from calibre.ebooks.pdf.render.serialize import (PDFStream, Path) +from calibre.ebooks.pdf.render.common import inch, A4, fmtnum +from calibre.ebooks.pdf.render.graphics import convert_path, Graphics +from calibre.utils.fonts.sfnt.container import Sfnt, UnsupportedFont +from calibre.utils.fonts.sfnt.metrics import FontMetrics + +Point = namedtuple('Point', 'x y') +ColorState = namedtuple('ColorState', 'color opacity do') + +def repr_transform(t): + vals = map(fmtnum, (t.m11(), t.m12(), t.m21(), t.m22(), t.dx(), t.dy())) + return '[%s]'%' '.join(vals) + +def store_error(func): + + @wraps(func) + def errh(self, *args, **kwargs): + try: + func(self, *args, **kwargs) + except: + self.errors_occurred = True + self.errors(traceback.format_exc()) + + return errh + +class Font(FontMetrics): + + def __init__(self, sfnt): + FontMetrics.__init__(self, sfnt) + self.glyph_map = {} + +class PdfEngine(QPaintEngine): + + FEATURES = QPaintEngine.AllFeatures & ~( + QPaintEngine.PorterDuff | QPaintEngine.PerspectiveTransform + | QPaintEngine.ObjectBoundingModeGradients + | QPaintEngine.RadialGradientFill + | QPaintEngine.ConicalGradientFill + ) + + def __init__(self, file_object, page_width, page_height, left_margin, + top_margin, right_margin, bottom_margin, width, height, + errors=print, debug=print, compress=True, + mark_links=False): + QPaintEngine.__init__(self, self.FEATURES) + self.file_object = file_object + self.compress, self.mark_links = compress, mark_links + self.page_height, self.page_width = page_height, page_width + self.left_margin, self.top_margin = left_margin, top_margin + self.right_margin, self.bottom_margin = right_margin, bottom_margin + self.pixel_width, self.pixel_height = width, height + # Setup a co-ordinate transform that allows us to use co-ords + # from Qt's pixel based co-ordinate system with its origin at the top + # left corner. PDF's co-ordinate system is based on pts and has its + # origin in the bottom left corner. We also have to implement the page + # margins. Therefore, we need to translate, scale and reflect about the + # x-axis. + dy = self.page_height - self.top_margin + dx = self.left_margin + sx = (self.page_width - self.left_margin - + self.right_margin) / self.pixel_width + sy = (self.page_height - self.top_margin - + self.bottom_margin) / self.pixel_height + + self.pdf_system = QTransform(sx, 0, 0, -sy, dx, dy) + self.graphics = Graphics(self.pixel_width, self.pixel_height) + self.errors_occurred = False + self.errors, self.debug = errors, debug + self.fonts = {} + self.current_page_num = 1 + self.current_page_inited = False + self.qt_hack, err = plugins['qt_hack'] + if err: + raise RuntimeError('Failed to load qt_hack with err: %s'%err) + + def apply_graphics_state(self): + self.graphics(self.pdf_system, self.painter()) + + def resolve_fill(self, rect): + self.graphics.resolve_fill(rect, self.pdf_system, + self.painter().transform()) + + @property + def do_fill(self): + return self.graphics.current_state.do_fill + + @property + def do_stroke(self): + return self.graphics.current_state.do_stroke + + def init_page(self): + self.pdf.transform(self.pdf_system) + self.graphics.reset() + self.pdf.save_stack() + self.current_page_inited = True + + def begin(self, device): + if not hasattr(self, 'pdf'): + try: + self.pdf = PDFStream(self.file_object, (self.page_width, + self.page_height), compress=self.compress, + mark_links=self.mark_links, + debug=self.debug) + self.graphics.begin(self.pdf) + except: + self.errors(traceback.format_exc()) + self.errors_occurred = True + return False + return True + + def end_page(self): + if self.current_page_inited: + self.pdf.restore_stack() + self.pdf.end_page() + self.current_page_inited = False + self.current_page_num += 1 + + def end(self): + try: + self.end_page() + self.pdf.end() + except: + self.errors(traceback.format_exc()) + self.errors_occurred = True + return False + finally: + self.pdf = self.file_object = None + return True + + def type(self): + return QPaintEngine.Pdf + + def add_image(self, img, cache_key): + if img.isNull(): return + return self.pdf.add_image(img, cache_key) + + @store_error + def drawTiledPixmap(self, rect, pixmap, point): + self.apply_graphics_state() + brush = QBrush(pixmap) + bl = rect.topLeft() + color, opacity, pattern, do_fill = self.graphics.convert_brush( + brush, bl-point, 1.0, self.pdf_system, + self.painter().transform()) + self.pdf.save_stack() + self.pdf.apply_fill(color, pattern) + self.pdf.draw_rect(bl.x(), bl.y(), rect.width(), rect.height(), + stroke=False, fill=True) + self.pdf.restore_stack() + + @store_error + def drawPixmap(self, rect, pixmap, source_rect): + self.apply_graphics_state() + source_rect = source_rect.toRect() + pixmap = (pixmap if source_rect == pixmap.rect() else + pixmap.copy(source_rect)) + image = pixmap.toImage() + ref = self.add_image(image, pixmap.cacheKey()) + if ref is not None: + self.pdf.draw_image(rect.x(), rect.y(), rect.width(), + rect.height(), ref) + + @store_error + def drawImage(self, rect, image, source_rect, flags=Qt.AutoColor): + self.apply_graphics_state() + source_rect = source_rect.toRect() + image = (image if source_rect == image.rect() else + image.copy(source_rect)) + ref = self.add_image(image, image.cacheKey()) + if ref is not None: + self.pdf.draw_image(rect.x(), rect.y(), rect.width(), + rect.height(), ref) + + @store_error + def updateState(self, state): + self.graphics.update_state(state, self.painter()) + + @store_error + def drawPath(self, path): + self.apply_graphics_state() + p = convert_path(path) + fill_rule = {Qt.OddEvenFill:'evenodd', + Qt.WindingFill:'winding'}[path.fillRule()] + self.pdf.draw_path(p, stroke=self.do_stroke, + fill=self.do_fill, fill_rule=fill_rule) + + @store_error + def drawPoints(self, points): + self.apply_graphics_state() + p = Path() + for point in points: + p.move_to(point.x(), point.y()) + p.line_to(point.x(), point.y() + 0.001) + self.pdf.draw_path(p, stroke=self.do_stroke, fill=False) + + @store_error + def drawRects(self, rects): + self.apply_graphics_state() + with self.graphics: + for rect in rects: + self.resolve_fill(rect) + bl = rect.topLeft() + self.pdf.draw_rect(bl.x(), bl.y(), rect.width(), rect.height(), + stroke=self.do_stroke, fill=self.do_fill) + + def create_sfnt(self, text_item): + get_table = partial(self.qt_hack.get_sfnt_table, text_item) + try: + ans = Font(Sfnt(get_table)) + except UnsupportedFont as e: + raise UnsupportedFont('The font %s is not a valid sfnt. Error: %s'%( + text_item.font().family(), e)) + glyph_map = self.qt_hack.get_glyph_map(text_item) + gm = {} + for uc, glyph_id in enumerate(glyph_map): + if glyph_id not in gm: + gm[glyph_id] = unichr(uc) + ans.full_glyph_map = gm + return ans + + @store_error + def drawTextItem(self, point, text_item): + # return super(PdfEngine, self).drawTextItem(point, text_item) + self.apply_graphics_state() + gi = self.qt_hack.get_glyphs(point, text_item) + if not gi.indices: + sip.delete(gi) + return + name = hash(bytes(gi.name)) + if name not in self.fonts: + try: + self.fonts[name] = self.create_sfnt(text_item) + except UnsupportedFont: + return super(PdfEngine, self).drawTextItem(point, text_item) + metrics = self.fonts[name] + for glyph_id in gi.indices: + try: + metrics.glyph_map[glyph_id] = metrics.full_glyph_map[glyph_id] + except (KeyError, ValueError): + pass + glyphs = [] + last_x = last_y = 0 + for i, pos in enumerate(gi.positions): + x, y = pos.x(), pos.y() + glyphs.append((x-last_x, last_y - y, gi.indices[i])) + last_x, last_y = x, y + + self.pdf.draw_glyph_run([gi.stretch, 0, 0, -1, 0, 0], gi.size, metrics, + glyphs) + sip.delete(gi) + + @store_error + def drawPolygon(self, points, mode): + self.apply_graphics_state() + if not points: return + p = Path() + p.move_to(points[0].x(), points[0].y()) + for point in points[1:]: + p.line_to(point.x(), point.y()) + p.close() + fill_rule = {self.OddEvenMode:'evenodd', + self.WindingMode:'winding'}.get(mode, 'evenodd') + self.pdf.draw_path(p, stroke=True, fill_rule=fill_rule, + fill=(mode in (self.OddEvenMode, self.WindingMode, self.ConvexMode))) + + def set_metadata(self, *args, **kwargs): + self.pdf.set_metadata(*args, **kwargs) + + def add_outline(self, toc): + self.pdf.links.add_outline(toc) + + def add_links(self, current_item, start_page, links, anchors): + for pos in anchors.itervalues(): + pos['left'], pos['top'] = self.pdf_system.map(pos['left'], pos['top']) + for link in links: + pos = link[1] + llx = pos['left'] + lly = pos['top'] + pos['height'] + urx = pos['left'] + pos['width'] + ury = pos['top'] + llx, lly = self.pdf_system.map(llx, lly) + urx, ury = self.pdf_system.map(urx, ury) + link[1] = pos['column'] + start_page + link.append((llx, lly, urx, ury)) + self.pdf.links.add(current_item, start_page, links, anchors) + +class PdfDevice(QPaintDevice): # {{{ + + + def __init__(self, file_object, page_size=A4, left_margin=inch, + top_margin=inch, right_margin=inch, bottom_margin=inch, + xdpi=1200, ydpi=1200, errors=print, debug=print, + compress=True, mark_links=False): + QPaintDevice.__init__(self) + self.xdpi, self.ydpi = xdpi, ydpi + self.page_width, self.page_height = page_size + self.body_width = self.page_width - left_margin - right_margin + self.body_height = self.page_height - top_margin - bottom_margin + self.engine = PdfEngine(file_object, self.page_width, self.page_height, + left_margin, top_margin, right_margin, + bottom_margin, self.width(), self.height(), + errors=errors, debug=debug, compress=compress, + mark_links=mark_links) + self.add_outline = self.engine.add_outline + self.add_links = self.engine.add_links + + def paintEngine(self): + return self.engine + + def metric(self, m): + if m in (self.PdmDpiX, self.PdmPhysicalDpiX): + return self.xdpi + if m in (self.PdmDpiY, self.PdmPhysicalDpiY): + return self.ydpi + if m == self.PdmDepth: + return 32 + if m == self.PdmNumColors: + return sys.maxint + if m == self.PdmWidthMM: + return int(round(self.body_width * 0.35277777777778)) + if m == self.PdmHeightMM: + return int(round(self.body_height * 0.35277777777778)) + if m == self.PdmWidth: + return int(round(self.body_width * self.xdpi / 72.0)) + if m == self.PdmHeight: + return int(round(self.body_height * self.ydpi / 72.0)) + return 0 + + def end_page(self, *args, **kwargs): + self.engine.end_page(*args, **kwargs) + + def init_page(self): + self.engine.init_page() + + @property + def current_page_num(self): + return self.engine.current_page_num + + @property + def errors_occurred(self): + return self.engine.errors_occurred + + def to_px(self, pt, vertical=True): + return pt * (self.height()/self.page_height if vertical else + self.width()/self.page_width) + + def set_metadata(self, *args, **kwargs): + self.engine.set_metadata(*args, **kwargs) + +# }}} + + diff --git a/src/calibre/ebooks/pdf/render/fonts.py b/src/calibre/ebooks/pdf/render/fonts.py new file mode 100644 index 0000000000..e99cc7c218 --- /dev/null +++ b/src/calibre/ebooks/pdf/render/fonts.py @@ -0,0 +1,227 @@ +#!/usr/bin/env python +# vim:fileencoding=UTF-8:ts=4:sw=4:sta:et:sts=4:fdm=marker:ai +from __future__ import (unicode_literals, division, absolute_import, + print_function) + +__license__ = 'GPL v3' +__copyright__ = '2012, Kovid Goyal ' +__docformat__ = 'restructuredtext en' + +import re +from itertools import izip, groupby +from operator import itemgetter +from collections import Counter, OrderedDict +from future_builtins import map + +from calibre.ebooks.pdf.render.common import (Array, String, Stream, + Dictionary, Name) +from calibre.utils.fonts.sfnt.subset import pdf_subset + +STANDARD_FONTS = { + 'Times-Roman', 'Helvetica', 'Courier', 'Symbol', 'Times-Bold', + 'Helvetica-Bold', 'Courier-Bold', 'ZapfDingbats', 'Times-Italic', + 'Helvetica-Oblique', 'Courier-Oblique', 'Times-BoldItalic', + 'Helvetica-BoldOblique', 'Courier-BoldOblique', } + +''' +Notes +======= + +We must use Type 0 CID keyed fonts to represent unicode text. + +For TrueType +-------------- + +The mapping from the text strings to glyph ids is defined by two things: + + The /Encoding key of the Type-0 font dictionary + The /CIDToGIDMap key of the descendant font dictionary (for TrueType fonts) + +We set /Encoding to /Identity-H and /CIDToGIDMap to /Identity. This means that +text strings are interpreted as a sequence of two-byte numbers, high order byte +first. Each number gets mapped to a glyph id equal to itself by the +/CIDToGIDMap. + +''' + +import textwrap + +class FontStream(Stream): + + def __init__(self, is_otf, compress=False): + Stream.__init__(self, compress=compress) + self.is_otf = is_otf + + def add_extra_keys(self, d): + d['Length1'] = d['DL'] + if self.is_otf: + d['Subtype'] = Name('CIDFontType0C') + +def to_hex_string(c): + return bytes(hex(int(c))[2:]).rjust(4, b'0').decode('ascii') + +class CMap(Stream): + + skeleton = textwrap.dedent('''\ + /CIDInit /ProcSet findresource begin + 12 dict begin + begincmap + /CMapName {name}-cmap def + /CMapType 2 def + /CIDSystemInfo << + /Registry (Adobe) + /Ordering (UCS) + /Supplement 0 + >> def + 1 begincodespacerange + <0000> + endcodespacerange + {mapping} + endcmap + CMapName currentdict /CMap defineresource pop + end + end + ''') + + + def __init__(self, name, glyph_map, compress=False): + Stream.__init__(self, compress) + current_map = OrderedDict() + maps = [] + for glyph_id in sorted(glyph_map): + if len(current_map) > 99: + maps.append(current_map) + current_map = OrderedDict() + val = [] + for c in glyph_map[glyph_id]: + c = ord(c) + val.append(to_hex_string(c)) + glyph_id = '<%s>'%to_hex_string(glyph_id) + current_map[glyph_id] = '<%s>'%''.join(val) + if current_map: + maps.append(current_map) + mapping = [] + for m in maps: + meat = '\n'.join('%s %s'%(k, v) for k, v in m.iteritems()) + mapping.append('%d beginbfchar\n%s\nendbfchar'%(len(m), meat)) + self.write(self.skeleton.format(name=name, mapping='\n'.join(mapping))) + +class Font(object): + + def __init__(self, metrics, num, objects, compress): + self.metrics, self.compress = metrics, compress + self.is_otf = self.metrics.is_otf + self.subset_tag = bytes(re.sub('.', lambda m: chr(int(m.group())+ord('A')), + oct(num))).rjust(6, b'A').decode('ascii') + self.font_stream = FontStream(metrics.is_otf, compress=compress) + self.font_descriptor = Dictionary({ + 'Type': Name('FontDescriptor'), + 'FontName': Name('%s+%s'%(self.subset_tag, metrics.postscript_name)), + 'Flags': 0b100, # Symbolic font + 'FontBBox': Array(metrics.pdf_bbox), + 'ItalicAngle': metrics.post.italic_angle, + 'Ascent': metrics.pdf_ascent, + 'Descent': metrics.pdf_descent, + 'CapHeight': metrics.pdf_capheight, + 'AvgWidth': metrics.pdf_avg_width, + 'StemV': metrics.pdf_stemv, + }) + self.descendant_font = Dictionary({ + 'Type':Name('Font'), + 'Subtype':Name('CIDFontType' + ('0' if metrics.is_otf else '2')), + 'BaseFont': self.font_descriptor['FontName'], + 'FontDescriptor':objects.add(self.font_descriptor), + 'CIDSystemInfo':Dictionary({ + 'Registry':String('Adobe'), + 'Ordering':String('Identity'), + 'Supplement':0, + }), + }) + if not self.is_otf: + self.descendant_font['CIDToGIDMap'] = Name('Identity') + + self.font_dict = Dictionary({ + 'Type':Name('Font'), + 'Subtype':Name('Type0'), + 'Encoding':Name('Identity-H'), + 'BaseFont':self.descendant_font['BaseFont'], + 'DescendantFonts':Array([objects.add(self.descendant_font)]), + }) + + self.used_glyphs = set() + + def embed(self, objects): + self.font_descriptor['FontFile'+('3' if self.is_otf else '2') + ] = objects.add(self.font_stream) + self.write_widths(objects) + self.write_to_unicode(objects) + pdf_subset(self.metrics.sfnt, self.used_glyphs) + if self.is_otf: + self.font_stream.write(self.metrics.sfnt['CFF '].raw) + else: + self.metrics.os2.zero_fstype() + self.metrics.sfnt(self.font_stream) + + def write_to_unicode(self, objects): + cmap = CMap(self.metrics.postscript_name, self.metrics.glyph_map, + compress=self.compress) + self.font_dict['ToUnicode'] = objects.add(cmap) + + def write_widths(self, objects): + glyphs = sorted(self.used_glyphs|{0}) + widths = {g:self.metrics.pdf_scale(w) for g, w in izip(glyphs, + self.metrics.glyph_widths(glyphs))} + counter = Counter() + for g, w in widths.iteritems(): + counter[w] += 1 + most_common = counter.most_common(1)[0][0] + self.descendant_font['DW'] = most_common + widths = {g:w for g, w in widths.iteritems() if w != most_common} + + groups = Array() + for k, g in groupby(enumerate(widths.iterkeys()), lambda (i,x):i-x): + group = list(map(itemgetter(1), g)) + gwidths = [widths[g] for g in group] + if len(set(gwidths)) == 1 and len(group) > 1: + w = (min(group), max(group), gwidths[0]) + else: + w = (min(group), Array(gwidths)) + groups.extend(w) + self.descendant_font['W'] = objects.add(groups) + + +class FontManager(object): + + def __init__(self, objects, compress): + self.objects = objects + self.compress = compress + self.std_map = {} + self.font_map = {} + self.fonts = [] + + def add_font(self, font_metrics, glyph_ids): + if font_metrics not in self.font_map: + self.fonts.append(Font(font_metrics, len(self.fonts), + self.objects, self.compress)) + d = self.objects.add(self.fonts[-1].font_dict) + self.font_map[font_metrics] = (d, self.fonts[-1]) + + fontref, font = self.font_map[font_metrics] + font.used_glyphs |= glyph_ids + return fontref + + def add_standard_font(self, name): + if name not in STANDARD_FONTS: + raise ValueError('%s is not a standard font'%name) + if name not in self.std_map: + self.std_map[name] = self.objects.add(Dictionary({ + 'Type':Name('Font'), + 'Subtype':Name('Type1'), + 'BaseFont':Name(name) + })) + return self.std_map[name] + + def embed_fonts(self): + for font in self.fonts: + font.embed(self.objects) + diff --git a/src/calibre/ebooks/pdf/render/from_html.py b/src/calibre/ebooks/pdf/render/from_html.py new file mode 100644 index 0000000000..69636f8b9a --- /dev/null +++ b/src/calibre/ebooks/pdf/render/from_html.py @@ -0,0 +1,285 @@ +#!/usr/bin/env python +# vim:fileencoding=UTF-8:ts=4:sw=4:sta:et:sts=4:fdm=marker:ai +from __future__ import (unicode_literals, division, absolute_import, + print_function) + +__license__ = 'GPL v3' +__copyright__ = '2012, Kovid Goyal ' +__docformat__ = 'restructuredtext en' + +import json, os +from future_builtins import map +from math import floor + +from PyQt4.Qt import (QObject, QPainter, Qt, QSize, QString, QTimer, + pyqtProperty, QEventLoop, QPixmap, QRect) +from PyQt4.QtWebKit import QWebView, QWebPage, QWebSettings + +from calibre import fit_image +from calibre.ebooks.oeb.display.webview import load_html +from calibre.ebooks.pdf.render.common import (inch, cm, mm, pica, cicero, + didot, PAPER_SIZES) +from calibre.ebooks.pdf.render.engine import PdfDevice + +def get_page_size(opts, for_comic=False): # {{{ + use_profile = not (opts.override_profile_size or + opts.output_profile.short_name == 'default' or + opts.output_profile.width > 9999) + if use_profile: + w = (opts.output_profile.comic_screen_size[0] if for_comic else + opts.output_profile.width) + h = (opts.output_profile.comic_screen_size[1] if for_comic else + opts.output_profile.height) + dpi = opts.output_profile.dpi + factor = 72.0 / dpi + page_size = (factor * w, factor * h) + else: + page_size = None + if opts.custom_size != None: + width, sep, height = opts.custom_size.partition('x') + if height: + try: + width = float(width) + height = float(height) + except: + pass + else: + if opts.unit == 'devicepixel': + factor = 72.0 / opts.output_profile.dpi + else: + factor = {'point':1.0, 'inch':inch, 'cicero':cicero, + 'didot':didot, 'pica':pica, 'millimeter':mm, + 'centimeter':cm}[opts.unit] + page_size = (factor*width, factor*height) + if page_size is None: + page_size = PAPER_SIZES[opts.paper_size] + return page_size +# }}} + +class Page(QWebPage): # {{{ + + def __init__(self, opts, log): + self.log = log + QWebPage.__init__(self) + settings = self.settings() + settings.setFontSize(QWebSettings.DefaultFontSize, + opts.pdf_default_font_size) + settings.setFontSize(QWebSettings.DefaultFixedFontSize, + opts.pdf_mono_font_size) + settings.setFontSize(QWebSettings.MinimumLogicalFontSize, 8) + settings.setFontSize(QWebSettings.MinimumFontSize, 8) + + std = {'serif':opts.pdf_serif_family, 'sans':opts.pdf_sans_family, + 'mono':opts.pdf_mono_family}.get(opts.pdf_standard_font, + opts.pdf_serif_family) + if std: + settings.setFontFamily(QWebSettings.StandardFont, std) + if opts.pdf_serif_family: + settings.setFontFamily(QWebSettings.SerifFont, opts.pdf_serif_family) + if opts.pdf_sans_family: + settings.setFontFamily(QWebSettings.SansSerifFont, + opts.pdf_sans_family) + if opts.pdf_mono_family: + settings.setFontFamily(QWebSettings.FixedFont, opts.pdf_mono_family) + + def javaScriptConsoleMessage(self, msg, lineno, msgid): + self.log.debug(u'JS:', unicode(msg)) + + def javaScriptAlert(self, frame, msg): + self.log(unicode(msg)) +# }}} + +def draw_image_page(page_rect, painter, p, preserve_aspect_ratio=True): + if preserve_aspect_ratio: + aspect_ratio = float(p.width())/p.height() + nw, nh = page_rect.width(), page_rect.height() + if aspect_ratio > 1: + nh = int(page_rect.width()/aspect_ratio) + else: # Width is smaller than height + nw = page_rect.height()*aspect_ratio + __, nnw, nnh = fit_image(nw, nh, page_rect.width(), + page_rect.height()) + dx = int((page_rect.width() - nnw)/2.) + dy = int((page_rect.height() - nnh)/2.) + page_rect.moveTo(dx, dy) + page_rect.setHeight(nnh) + page_rect.setWidth(nnw) + painter.drawPixmap(page_rect, p, p.rect()) + +class PDFWriter(QObject): + + def _pass_json_value_getter(self): + val = json.dumps(self.bridge_value) + return QString(val) + + def _pass_json_value_setter(self, value): + self.bridge_value = json.loads(unicode(value)) + + _pass_json_value = pyqtProperty(QString, fget=_pass_json_value_getter, + fset=_pass_json_value_setter) + + def __init__(self, opts, log, cover_data=None, toc=None): + from calibre.gui2 import is_ok_to_use_qt + if not is_ok_to_use_qt(): + raise Exception('Not OK to use Qt') + QObject.__init__(self) + + self.logger = self.log = log + self.opts = opts + self.cover_data = cover_data + self.paged_js = None + self.toc = toc + + self.loop = QEventLoop() + self.view = QWebView() + self.page = Page(opts, self.log) + self.view.setPage(self.page) + self.view.setRenderHints(QPainter.Antialiasing| + QPainter.TextAntialiasing|QPainter.SmoothPixmapTransform) + self.view.loadFinished.connect(self.render_html, + type=Qt.QueuedConnection) + for x in (Qt.Horizontal, Qt.Vertical): + self.view.page().mainFrame().setScrollBarPolicy(x, + Qt.ScrollBarAlwaysOff) + self.report_progress = lambda x, y: x + + def dump(self, items, out_stream, pdf_metadata): + opts = self.opts + page_size = get_page_size(self.opts) + xdpi, ydpi = self.view.logicalDpiX(), self.view.logicalDpiY() + # We cannot set the side margins in the webview as there is no right + # margin for the last page (the margins are implemented with + # -webkit-column-gap) + ml, mr = opts.margin_left, opts.margin_right + self.doc = PdfDevice(out_stream, page_size=page_size, left_margin=ml, + top_margin=0, right_margin=mr, bottom_margin=0, + xdpi=xdpi, ydpi=ydpi, errors=self.log.error, + debug=self.log.debug, compress=not + opts.uncompressed_pdf, + mark_links=opts.pdf_mark_links) + + self.page.setViewportSize(QSize(self.doc.width(), self.doc.height())) + self.render_queue = items + self.total_items = len(items) + + mt, mb = map(self.doc.to_px, (opts.margin_top, opts.margin_bottom)) + self.margin_top, self.margin_bottom = map(lambda x:int(floor(x)), (mt, mb)) + + self.painter = QPainter(self.doc) + self.doc.set_metadata(title=pdf_metadata.title, + author=pdf_metadata.author, + tags=pdf_metadata.tags) + self.painter.save() + try: + if self.cover_data is not None: + p = QPixmap() + p.loadFromData(self.cover_data) + if not p.isNull(): + self.doc.init_page() + draw_image_page(QRect(0, 0, self.doc.width(), self.doc.height()), + self.painter, p, + preserve_aspect_ratio=self.opts.preserve_cover_aspect_ratio) + self.doc.end_page() + finally: + self.painter.restore() + + QTimer.singleShot(0, self.render_book) + if self.loop.exec_() == 1: + raise Exception('PDF Output failed, see log for details') + + if self.toc is not None and len(self.toc) > 0: + self.doc.add_outline(self.toc) + + self.painter.end() + + if self.doc.errors_occurred: + raise Exception('PDF Output failed, see log for details') + + def render_book(self): + if self.doc.errors_occurred: + return self.loop.exit(1) + try: + if not self.render_queue: + self.loop.exit() + else: + self.render_next() + except: + self.logger.exception('Rendering failed') + self.loop.exit(1) + + def render_next(self): + item = unicode(self.render_queue.pop(0)) + + self.logger.debug('Processing %s...' % item) + self.current_item = item + load_html(item, self.view) + + def render_html(self, ok): + if ok: + try: + self.do_paged_render() + except: + self.log.exception('Rendering failed') + self.loop.exit(1) + else: + # The document is so corrupt that we can't render the page. + self.logger.error('Document cannot be rendered.') + self.loop.exit(1) + return + done = self.total_items - len(self.render_queue) + self.report_progress(done/self.total_items, + _('Rendered %s'%os.path.basename(self.current_item))) + self.render_book() + + @property + def current_page_num(self): + return self.doc.current_page_num + + def do_paged_render(self): + if self.paged_js is None: + from calibre.utils.resources import compiled_coffeescript + self.paged_js = compiled_coffeescript('ebooks.oeb.display.utils') + self.paged_js += compiled_coffeescript('ebooks.oeb.display.indexing') + self.paged_js += compiled_coffeescript('ebooks.oeb.display.paged') + + self.view.page().mainFrame().addToJavaScriptWindowObject("py_bridge", self) + evaljs = self.view.page().mainFrame().evaluateJavaScript + evaljs(self.paged_js) + evaljs(''' + py_bridge.__defineGetter__('value', function() { + return JSON.parse(this._pass_json_value); + }); + py_bridge.__defineSetter__('value', function(val) { + this._pass_json_value = JSON.stringify(val); + }); + + document.body.style.backgroundColor = "white"; + paged_display.set_geometry(1, %d, %d, %d); + paged_display.layout(); + paged_display.fit_images(); + py_bridge.value = book_indexing.all_links_and_anchors(); + '''%(self.margin_top, 0, self.margin_bottom)) + + amap = self.bridge_value + if not isinstance(amap, dict): + amap = {'links':[], 'anchors':{}} # Some javascript error occurred + start_page = self.current_page_num + + mf = self.view.page().mainFrame() + while True: + self.doc.init_page() + self.painter.save() + mf.render(self.painter) + self.painter.restore() + nsl = evaljs('paged_display.next_screen_location()').toInt() + self.doc.end_page() + if not nsl[1] or nsl[0] <= 0: + break + evaljs('window.scrollTo(%d, 0)'%nsl[0]) + if self.doc.errors_occurred: + break + + if not self.doc.errors_occurred: + self.doc.add_links(self.current_item, start_page, amap['links'], + amap['anchors']) + diff --git a/src/calibre/ebooks/pdf/render/gradients.py b/src/calibre/ebooks/pdf/render/gradients.py new file mode 100644 index 0000000000..0c6a2485d1 --- /dev/null +++ b/src/calibre/ebooks/pdf/render/gradients.py @@ -0,0 +1,153 @@ +#!/usr/bin/env python +# vim:fileencoding=UTF-8:ts=4:sw=4:sta:et:sts=4:fdm=marker:ai +from __future__ import (unicode_literals, division, absolute_import, + print_function) + +__license__ = 'GPL v3' +__copyright__ = '2013, Kovid Goyal ' +__docformat__ = 'restructuredtext en' + +import sys, copy +from future_builtins import map +from collections import namedtuple + +import sip +from PyQt4.Qt import QLinearGradient, QPointF + +from calibre.ebooks.pdf.render.common import Name, Array, Dictionary + +Stop = namedtuple('Stop', 't color') + +class LinearGradientPattern(Dictionary): + + def __init__(self, brush, matrix, pdf, pixel_page_width, pixel_page_height): + self.matrix = (matrix.m11(), matrix.m12(), matrix.m21(), matrix.m22(), + matrix.dx(), matrix.dy()) + gradient = sip.cast(brush.gradient(), QLinearGradient) + + start, stop, stops = self.spread_gradient(gradient, pixel_page_width, + pixel_page_height, matrix) + + # TODO: Handle colors with different opacities + self.const_opacity = stops[0].color[-1] + + funcs = Array() + bounds = Array() + encode = Array() + + for i, current_stop in enumerate(stops): + if i < len(stops) - 1: + next_stop = stops[i+1] + func = Dictionary({ + 'FunctionType': 2, + 'Domain': Array([0, 1]), + 'C0': Array(current_stop.color[:3]), + 'C1': Array(next_stop.color[:3]), + 'N': 1, + }) + funcs.append(func) + encode.extend((0, 1)) + if i+1 < len(stops) - 1: + bounds.append(next_stop.t) + + func = Dictionary({ + 'FunctionType': 3, + 'Domain': Array([stops[0].t, stops[-1].t]), + 'Functions': funcs, + 'Bounds': bounds, + 'Encode': encode, + }) + + shader = Dictionary({ + 'ShadingType': 2, + 'ColorSpace': Name('DeviceRGB'), + 'AntiAlias': True, + 'Coords': Array([start.x(), start.y(), stop.x(), stop.y()]), + 'Function': func, + 'Extend': Array([True, True]), + }) + + Dictionary.__init__(self, { + 'Type': Name('Pattern'), + 'PatternType': 2, + 'Shading': shader, + 'Matrix': Array(self.matrix), + }) + + self.cache_key = (self.__class__.__name__, self.matrix, + tuple(shader['Coords']), stops) + + def spread_gradient(self, gradient, pixel_page_width, pixel_page_height, + matrix): + start = gradient.start() + stop = gradient.finalStop() + stops = list(map(lambda x: [x[0], x[1].getRgbF()], gradient.stops())) + spread = gradient.spread() + if spread != gradient.PadSpread: + inv = matrix.inverted()[0] + page_rect = tuple(map(inv.map, ( + QPointF(0, 0), QPointF(pixel_page_width, 0), QPointF(0, pixel_page_height), + QPointF(pixel_page_width, pixel_page_height)))) + maxx = maxy = -sys.maxint-1 + minx = miny = sys.maxint + + for p in page_rect: + minx, maxx = min(minx, p.x()), max(maxx, p.x()) + miny, maxy = min(miny, p.y()), max(maxy, p.y()) + + def in_page(point): + return (minx <= point.x() <= maxx and miny <= point.y() <= maxy) + + offset = stop - start + llimit, rlimit = start, stop + + reflect = False + base_stops = copy.deepcopy(stops) + reversed_stops = list(reversed(stops)) + do_reflect = spread == gradient.ReflectSpread + totl = abs(stops[-1][0] - stops[0][0]) + intervals = [abs(stops[i+1][0] - stops[i][0])/totl + for i in xrange(len(stops)-1)] + + while in_page(llimit): + reflect ^= True + llimit -= offset + estops = reversed_stops if (reflect and do_reflect) else base_stops + stops = copy.deepcopy(estops) + stops + + first_is_reflected = reflect + reflect = False + + while in_page(rlimit): + reflect ^= True + rlimit += offset + estops = reversed_stops if (reflect and do_reflect) else base_stops + stops = stops + copy.deepcopy(estops) + + start, stop = llimit, rlimit + + num = len(stops) // len(base_stops) + if num > 1: + # Adjust the stop parameter values + t = base_stops[0][0] + rlen = totl/num + reflect = first_is_reflected ^ True + intervals = [i*rlen for i in intervals] + rintervals = list(reversed(intervals)) + + for i in xrange(num): + reflect ^= True + pos = i * len(base_stops) + tvals = [t] + for ival in (rintervals if reflect and do_reflect else + intervals): + tvals.append(tvals[-1] + ival) + for j in xrange(len(base_stops)): + stops[pos+j][0] = tvals[j] + t = tvals[-1] + + # In case there were rounding errors + stops[-1][0] = base_stops[-1][0] + + return start, stop, tuple(Stop(s[0], s[1]) for s in stops) + diff --git a/src/calibre/ebooks/pdf/render/graphics.py b/src/calibre/ebooks/pdf/render/graphics.py new file mode 100644 index 0000000000..25e23fcd0b --- /dev/null +++ b/src/calibre/ebooks/pdf/render/graphics.py @@ -0,0 +1,479 @@ +#!/usr/bin/env python +# vim:fileencoding=UTF-8:ts=4:sw=4:sta:et:sts=4:fdm=marker:ai +from __future__ import (unicode_literals, division, absolute_import, + print_function) + +__license__ = 'GPL v3' +__copyright__ = '2012, Kovid Goyal ' +__docformat__ = 'restructuredtext en' + +from math import sqrt +from collections import namedtuple + +from PyQt4.Qt import ( + QBrush, QPen, Qt, QPointF, QTransform, QPaintEngine, QImage) + +from calibre.ebooks.pdf.render.common import ( + Name, Array, fmtnum, Stream, Dictionary) +from calibre.ebooks.pdf.render.serialize import Path +from calibre.ebooks.pdf.render.gradients import LinearGradientPattern + +def convert_path(path): # {{{ + p = Path() + i = 0 + while i < path.elementCount(): + elem = path.elementAt(i) + em = (elem.x, elem.y) + i += 1 + if elem.isMoveTo(): + p.move_to(*em) + elif elem.isLineTo(): + p.line_to(*em) + elif elem.isCurveTo(): + added = False + if path.elementCount() > i+1: + c1, c2 = path.elementAt(i), path.elementAt(i+1) + if (c1.type == path.CurveToDataElement and c2.type == + path.CurveToDataElement): + i += 2 + p.curve_to(em[0], em[1], c1.x, c1.y, c2.x, c2.y) + added = True + if not added: + raise ValueError('Invalid curve to operation') + return p +# }}} + +Brush = namedtuple('Brush', 'origin brush color') + +class TilingPattern(Stream): + + def __init__(self, cache_key, matrix, w=8, h=8, paint_type=2, compress=False): + Stream.__init__(self, compress=compress) + self.paint_type = paint_type + self.w, self.h = w, h + self.matrix = (matrix.m11(), matrix.m12(), matrix.m21(), matrix.m22(), + matrix.dx(), matrix.dy()) + self.resources = Dictionary() + self.cache_key = (self.__class__.__name__, cache_key, self.matrix) + + def add_extra_keys(self, d): + d['Type'] = Name('Pattern') + d['PatternType'] = 1 + d['PaintType'] = self.paint_type + d['TilingType'] = 1 + d['BBox'] = Array([0, 0, self.w, self.h]) + d['XStep'] = self.w + d['YStep'] = self.h + d['Matrix'] = Array(self.matrix) + d['Resources'] = self.resources + +class QtPattern(TilingPattern): + + qt_patterns = ( # {{{ + "0 J\n" + "6 w\n" + "[] 0 d\n" + "4 0 m\n" + "4 8 l\n" + "0 4 m\n" + "8 4 l\n" + "S\n", # Dense1Pattern + + "0 J\n" + "2 w\n" + "[6 2] 1 d\n" + "0 0 m\n" + "0 8 l\n" + "8 0 m\n" + "8 8 l\n" + "S\n" + "[] 0 d\n" + "2 0 m\n" + "2 8 l\n" + "6 0 m\n" + "6 8 l\n" + "S\n" + "[6 2] -3 d\n" + "4 0 m\n" + "4 8 l\n" + "S\n", # Dense2Pattern + + "0 J\n" + "2 w\n" + "[6 2] 1 d\n" + "0 0 m\n" + "0 8 l\n" + "8 0 m\n" + "8 8 l\n" + "S\n" + "[2 2] -1 d\n" + "2 0 m\n" + "2 8 l\n" + "6 0 m\n" + "6 8 l\n" + "S\n" + "[6 2] -3 d\n" + "4 0 m\n" + "4 8 l\n" + "S\n", # Dense3Pattern + + "0 J\n" + "2 w\n" + "[2 2] 1 d\n" + "0 0 m\n" + "0 8 l\n" + "8 0 m\n" + "8 8 l\n" + "S\n" + "[2 2] -1 d\n" + "2 0 m\n" + "2 8 l\n" + "6 0 m\n" + "6 8 l\n" + "S\n" + "[2 2] 1 d\n" + "4 0 m\n" + "4 8 l\n" + "S\n", # Dense4Pattern + + "0 J\n" + "2 w\n" + "[2 6] -1 d\n" + "0 0 m\n" + "0 8 l\n" + "8 0 m\n" + "8 8 l\n" + "S\n" + "[2 2] 1 d\n" + "2 0 m\n" + "2 8 l\n" + "6 0 m\n" + "6 8 l\n" + "S\n" + "[2 6] 3 d\n" + "4 0 m\n" + "4 8 l\n" + "S\n", # Dense5Pattern + + "0 J\n" + "2 w\n" + "[2 6] -1 d\n" + "0 0 m\n" + "0 8 l\n" + "8 0 m\n" + "8 8 l\n" + "S\n" + "[2 6] 3 d\n" + "4 0 m\n" + "4 8 l\n" + "S\n", # Dense6Pattern + + "0 J\n" + "2 w\n" + "[2 6] -1 d\n" + "0 0 m\n" + "0 8 l\n" + "8 0 m\n" + "8 8 l\n" + "S\n", # Dense7Pattern + + "1 w\n" + "0 4 m\n" + "8 4 l\n" + "S\n", # HorPattern + + "1 w\n" + "4 0 m\n" + "4 8 l\n" + "S\n", # VerPattern + + "1 w\n" + "4 0 m\n" + "4 8 l\n" + "0 4 m\n" + "8 4 l\n" + "S\n", # CrossPattern + + "1 w\n" + "-1 5 m\n" + "5 -1 l\n" + "3 9 m\n" + "9 3 l\n" + "S\n", # BDiagPattern + + "1 w\n" + "-1 3 m\n" + "5 9 l\n" + "3 -1 m\n" + "9 5 l\n" + "S\n", # FDiagPattern + + "1 w\n" + "-1 3 m\n" + "5 9 l\n" + "3 -1 m\n" + "9 5 l\n" + "-1 5 m\n" + "5 -1 l\n" + "3 9 m\n" + "9 3 l\n" + "S\n", # DiagCrossPattern + ) # }}} + + def __init__(self, pattern_num, matrix): + super(QtPattern, self).__init__(pattern_num, matrix) + self.write(self.qt_patterns[pattern_num-2]) + +class TexturePattern(TilingPattern): + + def __init__(self, pixmap, matrix, pdf, clone=None): + if clone is None: + image = pixmap.toImage() + cache_key = pixmap.cacheKey() + imgref = pdf.add_image(image, cache_key) + paint_type = (2 if image.format() in {QImage.Format_MonoLSB, + QImage.Format_Mono} else 1) + super(TexturePattern, self).__init__( + cache_key, matrix, w=image.width(), h=image.height(), + paint_type=paint_type) + m = (self.w, 0, 0, -self.h, 0, self.h) + self.resources['XObject'] = Dictionary({'Texture':imgref}) + self.write_line('%s cm /Texture Do'%(' '.join(map(fmtnum, m)))) + else: + super(TexturePattern, self).__init__( + clone.cache_key[1], matrix, w=clone.w, h=clone.h, + paint_type=clone.paint_type) + self.resources['XObject'] = Dictionary(clone.resources['XObject']) + self.write(clone.getvalue()) + +class GraphicsState(object): + + FIELDS = ('fill', 'stroke', 'opacity', 'transform', 'brush_origin', + 'clip_updated', 'do_fill', 'do_stroke') + + def __init__(self): + self.fill = QBrush() + self.stroke = QPen() + self.opacity = 1.0 + self.transform = QTransform() + self.brush_origin = QPointF() + self.clip_updated = False + self.do_fill = False + self.do_stroke = True + self.qt_pattern_cache = {} + + def __eq__(self, other): + for x in self.FIELDS: + if getattr(other, x) != getattr(self, x): + return False + return True + + def copy(self): + ans = GraphicsState() + ans.fill = QBrush(self.fill) + ans.stroke = QPen(self.stroke) + ans.opacity = self.opacity + ans.transform = self.transform * QTransform() + ans.brush_origin = QPointF(self.brush_origin) + ans.clip_updated = self.clip_updated + ans.do_fill, ans.do_stroke = self.do_fill, self.do_stroke + return ans + +class Graphics(object): + + def __init__(self, page_width_px, page_height_px): + self.base_state = GraphicsState() + self.current_state = GraphicsState() + self.pending_state = None + self.page_width_px, self.page_height_px = (page_width_px, page_height_px) + + def begin(self, pdf): + self.pdf = pdf + + def update_state(self, state, painter): + flags = state.state() + if self.pending_state is None: + self.pending_state = self.current_state.copy() + + s = self.pending_state + + if flags & QPaintEngine.DirtyTransform: + s.transform = state.transform() + + if flags & QPaintEngine.DirtyBrushOrigin: + s.brush_origin = state.brushOrigin() + + if flags & QPaintEngine.DirtyBrush: + s.fill = state.brush() + + if flags & QPaintEngine.DirtyPen: + s.stroke = state.pen() + + if flags & QPaintEngine.DirtyOpacity: + s.opacity = state.opacity() + + if flags & QPaintEngine.DirtyClipPath or flags & QPaintEngine.DirtyClipRegion: + s.clip_updated = True + + def reset(self): + self.current_state = GraphicsState() + self.pending_state = None + + def __call__(self, pdf_system, painter): + # Apply the currently pending state to the PDF + if self.pending_state is None: + return + + pdf_state = self.current_state + ps = self.pending_state + pdf = self.pdf + + if ps.transform != pdf_state.transform or ps.clip_updated: + pdf.restore_stack() + pdf.save_stack() + pdf_state = self.base_state + + if (pdf_state.transform != ps.transform): + pdf.transform(ps.transform) + + if (pdf_state.opacity != ps.opacity or pdf_state.stroke != ps.stroke): + self.apply_stroke(ps, pdf_system, painter) + + if (pdf_state.opacity != ps.opacity or pdf_state.fill != ps.fill or + pdf_state.brush_origin != ps.brush_origin): + self.apply_fill(ps, pdf_system, painter) + + if ps.clip_updated: + ps.clip_updated = False + path = painter.clipPath() + if not path.isEmpty(): + p = convert_path(path) + fill_rule = {Qt.OddEvenFill:'evenodd', + Qt.WindingFill:'winding'}[path.fillRule()] + pdf.add_clip(p, fill_rule=fill_rule) + + self.current_state = self.pending_state + self.pending_state = None + + def convert_brush(self, brush, brush_origin, global_opacity, + pdf_system, qt_system): + # Convert a QBrush to PDF operators + style = brush.style() + pdf = self.pdf + + pattern = color = pat = None + opacity = global_opacity + do_fill = True + + matrix = (QTransform.fromTranslate(brush_origin.x(), brush_origin.y()) + * pdf_system * qt_system.inverted()[0]) + vals = list(brush.color().getRgbF()) + self.brushobj = None + + if style <= Qt.DiagCrossPattern: + opacity *= vals[-1] + color = vals[:3] + + if style > Qt.SolidPattern: + pat = QtPattern(style, matrix) + + elif style == Qt.TexturePattern: + pat = TexturePattern(brush.texture(), matrix, pdf) + if pat.paint_type == 2: + opacity *= vals[-1] + color = vals[:3] + + elif style == Qt.LinearGradientPattern: + pat = LinearGradientPattern(brush, matrix, pdf, self.page_width_px, + self.page_height_px) + opacity *= pat.const_opacity + # TODO: Add support for radial/conical gradient fills + + if opacity < 1e-4 or style == Qt.NoBrush: + do_fill = False + self.brushobj = Brush(brush_origin, pat, color) + + if pat is not None: + pattern = pdf.add_pattern(pat) + return color, opacity, pattern, do_fill + + def apply_stroke(self, state, pdf_system, painter): + # TODO: Support miter limit by using QPainterPathStroker + pen = state.stroke + self.pending_state.do_stroke = True + pdf = self.pdf + + # Width + w = pen.widthF() + if pen.isCosmetic(): + t = painter.transform() + w /= sqrt(t.m11()**2 + t.m22()**2) + pdf.serialize(w) + pdf.current_page.write(' w ') + + # Line cap + cap = {Qt.FlatCap:0, Qt.RoundCap:1, Qt.SquareCap: + 2}.get(pen.capStyle(), 0) + pdf.current_page.write('%d J '%cap) + + # Line join + join = {Qt.MiterJoin:0, Qt.RoundJoin:1, + Qt.BevelJoin:2}.get(pen.joinStyle(), 0) + pdf.current_page.write('%d j '%join) + + # Dash pattern + ps = {Qt.DashLine:[3], Qt.DotLine:[1,2], Qt.DashDotLine:[3,2,1,2], + Qt.DashDotDotLine:[3, 2, 1, 2, 1, 2]}.get(pen.style(), []) + if ps: + pdf.serialize(Array(ps)) + pdf.current_page.write(' 0 d ') + + # Stroke fill + color, opacity, pattern, self.pending_state.do_stroke = self.convert_brush( + pen.brush(), state.brush_origin, state.opacity, pdf_system, + painter.transform()) + self.pdf.apply_stroke(color, pattern, opacity) + if pen.style() == Qt.NoPen: + self.pending_state.do_stroke = False + + def apply_fill(self, state, pdf_system, painter): + self.pending_state.do_fill = True + color, opacity, pattern, self.pending_state.do_fill = self.convert_brush( + state.fill, state.brush_origin, state.opacity, pdf_system, + painter.transform()) + self.pdf.apply_fill(color, pattern, opacity) + self.last_fill = self.brushobj + + def __enter__(self): + self.pdf.save_stack() + + def __exit__(self, *args): + self.pdf.restore_stack() + + def resolve_fill(self, rect, pdf_system, qt_system): + ''' + Qt's paint system does not update brushOrigin when using + TexturePatterns and it also uses TexturePatterns to emulate gradients, + leading to brokenness. So this method allows the paint engine to update + the brush origin before painting an object. While not perfect, this is + better than nothing. The problem is that if the rect being filled has a + border, then QtWebKit generates an image of the rect size - border but + fills the full rect, and there's no way for the paint engine to know + that and adjust the brush origin. + ''' + if not hasattr(self, 'last_fill') or not self.current_state.do_fill: + return + + if isinstance(self.last_fill.brush, TexturePattern): + tl = rect.topLeft() + if tl == self.last_fill.origin: + return + + matrix = (QTransform.fromTranslate(tl.x(), tl.y()) + * pdf_system * qt_system.inverted()[0]) + + pat = TexturePattern(None, matrix, self.pdf, clone=self.last_fill.brush) + pattern = self.pdf.add_pattern(pat) + self.pdf.apply_fill(self.last_fill.color, pattern) + + diff --git a/src/calibre/ebooks/pdf/render/links.py b/src/calibre/ebooks/pdf/render/links.py new file mode 100644 index 0000000000..97a6551dbd --- /dev/null +++ b/src/calibre/ebooks/pdf/render/links.py @@ -0,0 +1,132 @@ +#!/usr/bin/env python +# vim:fileencoding=UTF-8:ts=4:sw=4:sta:et:sts=4:fdm=marker:ai +from __future__ import (unicode_literals, division, absolute_import, + print_function) + +__license__ = 'GPL v3' +__copyright__ = '2012, Kovid Goyal ' +__docformat__ = 'restructuredtext en' + +import os +from future_builtins import map +from urlparse import urlparse, urlunparse +from urllib2 import quote, unquote + +from calibre.ebooks.pdf.render.common import Array, Name, Dictionary, String + +class Destination(Array): + + def __init__(self, start_page, pos, get_pageref): + pnum = start_page + pos['column'] + try: + pref = get_pageref(pnum) + except IndexError: + pref = get_pageref(pnum-1) + super(Destination, self).__init__([ + pref, Name('XYZ'), pos['left'], pos['top'], None + ]) + +class Links(object): + + def __init__(self, pdf, mark_links, page_size): + self.anchors = {} + self.links = [] + self.start = {'top':page_size[1], 'column':0, 'left':0} + self.pdf = pdf + self.mark_links = mark_links + + def add(self, base_path, start_page, links, anchors): + path = os.path.normcase(os.path.abspath(base_path)) + self.anchors[path] = a = {} + a[None] = Destination(start_page, self.start, self.pdf.get_pageref) + for anchor, pos in anchors.iteritems(): + a[anchor] = Destination(start_page, pos, self.pdf.get_pageref) + for link in links: + href, page, rect = link + p, frag = href.partition('#')[0::2] + link = ((path, p, frag or None), self.pdf.get_pageref(page).obj, Array(rect)) + self.links.append(link) + + def add_links(self): + for link in self.links: + path, href, frag = link[0] + page, rect = link[1:] + combined_path = os.path.abspath(os.path.join(os.path.dirname(path), *href.split('/'))) + is_local = not href or combined_path in self.anchors + annot = Dictionary({ + 'Type':Name('Annot'), 'Subtype':Name('Link'), + 'Rect':rect, 'Border':Array([0,0,0]), + }) + if self.mark_links: + annot.update({'Border':Array([16, 16, 1]), 'C':Array([1.0, 0, + 0])}) + if is_local: + path = combined_path if href else path + try: + annot['Dest'] = self.anchors[path][frag] + except KeyError: + try: + annot['Dest'] = self.anchors[path][None] + except KeyError: + pass + else: + url = href + (('#'+frag) if frag else '') + purl = urlparse(url) + if purl.scheme and purl.scheme != 'file': + action = Dictionary({ + 'Type':Name('Action'), 'S':Name('URI'), + }) + parts = (x.encode('utf-8') if isinstance(x, type(u'')) else + x for x in purl) + url = urlunparse(map(quote, map(unquote, + parts))).decode('ascii') + action['URI'] = String(url) + annot['A'] = action + if 'A' in annot or 'Dest' in annot: + if 'Annots' not in page: + page['Annots'] = Array() + page['Annots'].append(self.pdf.objects.add(annot)) + else: + self.pdf.debug('Could not find destination for link: %s in file %s'% + (href, path)) + + def add_outline(self, toc): + parent = Dictionary({'Type':Name('Outlines')}) + parentref = self.pdf.objects.add(parent) + self.process_children(toc, parentref, parent_is_root=True) + self.pdf.catalog.obj['Outlines'] = parentref + + def process_children(self, toc, parentref, parent_is_root=False): + childrefs = [] + for child in toc: + childref = self.process_toc_item(child, parentref) + if childref is None: + continue + if childrefs: + childrefs[-1].obj['Next'] = childref + childref.obj['Prev'] = childrefs[-1] + childrefs.append(childref) + + if len(child) > 0: + self.process_children(child, childref) + if childrefs: + parentref.obj['First'] = childrefs[0] + parentref.obj['Last'] = childrefs[-1] + if not parent_is_root: + parentref.obj['Count'] = -len(childrefs) + + def process_toc_item(self, toc, parentref): + path = toc.abspath or None + frag = toc.fragment or None + if path is None: + return + path = os.path.normcase(os.path.abspath(path)) + if path not in self.anchors: + return None + a = self.anchors[path] + dest = a.get(frag, a[None]) + item = Dictionary({'Parent':parentref, 'Dest':dest, + 'Title':String(toc.text or _('Unknown'))}) + return self.pdf.objects.add(item) + + diff --git a/src/calibre/ebooks/pdf/render/qt_hack.cpp b/src/calibre/ebooks/pdf/render/qt_hack.cpp new file mode 100644 index 0000000000..1a9a33c35f --- /dev/null +++ b/src/calibre/ebooks/pdf/render/qt_hack.cpp @@ -0,0 +1,73 @@ +/* + * qt_hack.cpp + * Copyright (C) 2012 Kovid Goyal + * + * Distributed under terms of the GPL3 license. + */ + +#include "qt_hack.h" + +#include + +#include "private/qtextengine_p.h" +#include "private/qfontengine_p.h" + +GlyphInfo* get_glyphs(QPointF &p, const QTextItem &text_item) { + QTextItemInt ti = static_cast(text_item); + QFontEngine *fe = ti.fontEngine; + qreal size = ti.fontEngine->fontDef.pixelSize; +#ifdef Q_WS_WIN + if (false && ti.fontEngine->type() == QFontEngine::Win) { + // This is used in the Qt sourcecode, but it gives incorrect results, + // so I have disabled it. I dont understand how it works in qpdf.cpp + QFontEngineWin *fe = static_cast(ti.fontEngine); + // I think this should be tmHeight - tmInternalLeading, but pixelSize + // seems to work on windows as well, so leave it as pixelSize + size = fe->tm.tmHeight; + } +#endif + int synthesized = ti.fontEngine->synthesized(); + qreal stretch = synthesized & QFontEngine::SynthesizedStretch ? ti.fontEngine->fontDef.stretch/100. : 1.; + + QVarLengthArray glyphs; + QVarLengthArray positions; + QTransform m = QTransform::fromTranslate(p.x(), p.y()); + fe->getGlyphPositions(ti.glyphs, m, ti.flags, glyphs, positions); + QVector points = QVector(positions.count()); + for (int i = 0; i < positions.count(); i++) { + points[i].setX(positions[i].x.toReal()/stretch); + points[i].setY(positions[i].y.toReal()); + } + + QVector indices = QVector(glyphs.count()); + for (int i = 0; i < glyphs.count(); i++) + indices[i] = (unsigned int)glyphs[i]; + + const quint32 *tag = reinterpret_cast("name"); + + return new GlyphInfo(fe->getSfntTable(qToBigEndian(*tag)), size, stretch, points, indices); +} + +GlyphInfo::GlyphInfo(const QByteArray& name, qreal size, qreal stretch, const QVector &positions, const QVector &indices) :name(name), positions(positions), size(size), stretch(stretch), indices(indices) { +} + +QByteArray get_sfnt_table(const QTextItem &text_item, const char* tag_name) { + QTextItemInt ti = static_cast(text_item); + const quint32 *tag = reinterpret_cast(tag_name); + return ti.fontEngine->getSfntTable(qToBigEndian(*tag)); +} + +QVector* get_glyph_map(const QTextItem &text_item) { + QTextItemInt ti = static_cast(text_item); + QVector *ans = new QVector(0x10000); + QGlyphLayoutArray<10> glyphs; + int nglyphs = 10; + + for (uint uc = 0; uc < 0x10000; ++uc) { + QChar ch(uc); + ti.fontEngine->stringToCMap(&ch, 1, &glyphs, &nglyphs, QTextEngine::GlyphIndicesOnly); + (*ans)[uc] = glyphs.glyphs[0]; + } + return ans; +} + diff --git a/src/calibre/ebooks/pdf/render/qt_hack.h b/src/calibre/ebooks/pdf/render/qt_hack.h new file mode 100644 index 0000000000..1bae8f2624 --- /dev/null +++ b/src/calibre/ebooks/pdf/render/qt_hack.h @@ -0,0 +1,35 @@ +/* + * qt_hack.h + * Copyright (C) 2012 Kovid Goyal + * + * Distributed under terms of the GPL3 license. + */ + +#pragma once + +#include +#include +#include + + +class GlyphInfo { + public: + QByteArray name; + QVector positions; + qreal size; + qreal stretch; + QVector indices; + + GlyphInfo(const QByteArray &name, qreal size, qreal stretch, const QVector &positions, const QVector &indices); + + private: + GlyphInfo(const GlyphInfo&); + GlyphInfo &operator=(const GlyphInfo&); +}; + +GlyphInfo* get_glyphs(QPointF &p, const QTextItem &text_item); + +QByteArray get_sfnt_table(const QTextItem &text_item, const char* tag_name); + +QVector* get_glyph_map(const QTextItem &text_item); + diff --git a/src/calibre/ebooks/pdf/render/qt_hack.sip b/src/calibre/ebooks/pdf/render/qt_hack.sip new file mode 100644 index 0000000000..54d8726f4d --- /dev/null +++ b/src/calibre/ebooks/pdf/render/qt_hack.sip @@ -0,0 +1,29 @@ +//Define the SIP wrapper to the qt_hack code +//Author - Kovid Goyal + +%Module(name=qt_hack, version=1) + +%Import QtCore/QtCoremod.sip +%Import QtGui/QtGuimod.sip + +class GlyphInfo { +%TypeHeaderCode +#include +%End +public: + QByteArray name; + qreal size; + qreal stretch; + QVector &positions; + QVector indices; + GlyphInfo(const QByteArray &name, qreal size, qreal stretch, const QVector &positions, const QVector &indices); +private: + GlyphInfo(const GlyphInfo& g); + +}; + +GlyphInfo* get_glyphs(QPointF &p, const QTextItem &text_item); + +QByteArray get_sfnt_table(const QTextItem &text_item, const char* tag_name); + +QVector* get_glyph_map(const QTextItem &text_item); diff --git a/src/calibre/ebooks/pdf/render/serialize.py b/src/calibre/ebooks/pdf/render/serialize.py new file mode 100644 index 0000000000..936cb5f156 --- /dev/null +++ b/src/calibre/ebooks/pdf/render/serialize.py @@ -0,0 +1,505 @@ +#!/usr/bin/env python +# vim:fileencoding=UTF-8:ts=4:sw=4:sta:et:sts=4:fdm=marker:ai +from __future__ import (unicode_literals, division, absolute_import, + print_function) + +__license__ = 'GPL v3' +__copyright__ = '2012, Kovid Goyal ' +__docformat__ = 'restructuredtext en' + +import hashlib +from future_builtins import map + +from PyQt4.Qt import QBuffer, QByteArray, QImage, Qt, QColor, qRgba + +from calibre.constants import (__appname__, __version__) +from calibre.ebooks.pdf.render.common import ( + Reference, EOL, serialize, Stream, Dictionary, String, Name, Array, + fmtnum) +from calibre.ebooks.pdf.render.fonts import FontManager +from calibre.ebooks.pdf.render.links import Links +from calibre.utils.date import utcnow + +PDFVER = b'%PDF-1.3' + +class IndirectObjects(object): + + def __init__(self): + self._list = [] + self._map = {} + self._offsets = [] + + def __len__(self): + return len(self._list) + + def add(self, o): + self._list.append(o) + ref = Reference(len(self._list), o) + self._map[id(o)] = ref + self._offsets.append(None) + return ref + + def commit(self, ref, stream): + self.write_obj(stream, ref.num, ref.obj) + + def write_obj(self, stream, num, obj): + stream.write(EOL) + self._offsets[num-1] = stream.tell() + stream.write('%d 0 obj'%num) + stream.write(EOL) + serialize(obj, stream) + if stream.last_char != EOL: + stream.write(EOL) + stream.write('endobj') + stream.write(EOL) + + def __getitem__(self, o): + try: + return self._map[id(self._list[o] if isinstance(o, int) else o)] + except (KeyError, IndexError): + raise KeyError('The object %r was not found'%o) + + def pdf_serialize(self, stream): + for i, obj in enumerate(self._list): + offset = self._offsets[i] + if offset is None: + self.write_obj(stream, i+1, obj) + + def write_xref(self, stream): + self.xref_offset = stream.tell() + stream.write(b'xref'+EOL) + stream.write('0 %d'%(1+len(self._offsets))) + stream.write(EOL) + stream.write('%010d 65535 f '%0) + stream.write(EOL) + + for offset in self._offsets: + line = '%010d 00000 n '%offset + stream.write(line.encode('ascii') + EOL) + return self.xref_offset + +class Page(Stream): + + def __init__(self, parentref, *args, **kwargs): + super(Page, self).__init__(*args, **kwargs) + self.page_dict = Dictionary({ + 'Type': Name('Page'), + 'Parent': parentref, + }) + self.opacities = {} + self.fonts = {} + self.xobjects = {} + self.patterns = {} + + def set_opacity(self, opref): + if opref not in self.opacities: + self.opacities[opref] = 'Opa%d'%len(self.opacities) + name = self.opacities[opref] + serialize(Name(name), self) + self.write(b' gs ') + + def add_font(self, fontref): + if fontref not in self.fonts: + self.fonts[fontref] = 'F%d'%len(self.fonts) + return self.fonts[fontref] + + def add_image(self, imgref): + if imgref not in self.xobjects: + self.xobjects[imgref] = 'Image%d'%len(self.xobjects) + return self.xobjects[imgref] + + def add_pattern(self, patternref): + if patternref not in self.patterns: + self.patterns[patternref] = 'Pat%d'%len(self.patterns) + return self.patterns[patternref] + + def add_resources(self): + r = Dictionary() + if self.opacities: + extgs = Dictionary() + for opref, name in self.opacities.iteritems(): + extgs[name] = opref + r['ExtGState'] = extgs + if self.fonts: + fonts = Dictionary() + for ref, name in self.fonts.iteritems(): + fonts[name] = ref + r['Font'] = fonts + if self.xobjects: + xobjects = Dictionary() + for ref, name in self.xobjects.iteritems(): + xobjects[name] = ref + r['XObject'] = xobjects + if self.patterns: + r['ColorSpace'] = Dictionary({'PCSp':Array( + [Name('Pattern'), Name('DeviceRGB')])}) + patterns = Dictionary() + for ref, name in self.patterns.iteritems(): + patterns[name] = ref + r['Pattern'] = patterns + if r: + self.page_dict['Resources'] = r + + def end(self, objects, stream): + contents = objects.add(self) + objects.commit(contents, stream) + self.page_dict['Contents'] = contents + self.add_resources() + ret = objects.add(self.page_dict) + # objects.commit(ret, stream) + return ret + +class Path(object): + + def __init__(self): + self.ops = [] + + def move_to(self, x, y): + self.ops.append((x, y, 'm')) + + def line_to(self, x, y): + self.ops.append((x, y, 'l')) + + def curve_to(self, x1, y1, x2, y2, x, y): + self.ops.append((x1, y1, x2, y2, x, y, 'c')) + + def close(self): + self.ops.append(('h',)) + +class Catalog(Dictionary): + + def __init__(self, pagetree): + super(Catalog, self).__init__({'Type':Name('Catalog'), + 'Pages': pagetree}) + +class PageTree(Dictionary): + + def __init__(self, page_size): + super(PageTree, self).__init__({'Type':Name('Pages'), + 'MediaBox':Array([0, 0, page_size[0], page_size[1]]), + 'Kids':Array(), 'Count':0, + }) + + def add_page(self, pageref): + self['Kids'].append(pageref) + self['Count'] += 1 + + def get_ref(self, num): + return self['Kids'][num-1] + +class HashingStream(object): + + def __init__(self, f): + self.f = f + self.tell = f.tell + self.hashobj = hashlib.sha256() + self.last_char = b'' + + def write(self, raw): + self.write_raw(raw if isinstance(raw, bytes) else raw.encode('ascii')) + + def write_raw(self, raw): + self.f.write(raw) + self.hashobj.update(raw) + if raw: + self.last_char = raw[-1] + +class Image(Stream): + + def __init__(self, data, w, h, depth, mask, soft_mask, dct): + Stream.__init__(self) + self.width, self.height, self.depth = w, h, depth + self.mask, self.soft_mask = mask, soft_mask + if dct: + self.filters.append(Name('DCTDecode')) + else: + self.compress = True + self.write(data) + + def add_extra_keys(self, d): + d['Type'] = Name('XObject') + d['Subtype']= Name('Image') + d['Width'] = self.width + d['Height'] = self.height + if self.depth == 1: + d['ImageMask'] = True + d['Decode'] = Array([1, 0]) + else: + d['BitsPerComponent'] = 8 + d['ColorSpace'] = Name('Device' + ('RGB' if self.depth == 32 else + 'Gray')) + if self.mask is not None: + d['Mask'] = self.mask + if self.soft_mask is not None: + d['SMask'] = self.soft_mask + +class PDFStream(object): + + PATH_OPS = { + # stroke fill fill-rule + ( False, False, 'winding') : 'n', + ( False, False, 'evenodd') : 'n', + ( False, True, 'winding') : 'f', + ( False, True, 'evenodd') : 'f*', + ( True, False, 'winding') : 'S', + ( True, False, 'evenodd') : 'S', + ( True, True, 'winding') : 'B', + ( True, True, 'evenodd') : 'B*', + } + + def __init__(self, stream, page_size, compress=False, mark_links=False, + debug=print): + self.stream = HashingStream(stream) + self.compress = compress + self.write_line(PDFVER) + self.write_line(b'%íì¦"') + creator = ('%s %s [http://calibre-ebook.com]'%(__appname__, + __version__)) + self.write_line('%% Created by %s'%creator) + self.objects = IndirectObjects() + self.objects.add(PageTree(page_size)) + self.objects.add(Catalog(self.page_tree)) + self.current_page = Page(self.page_tree, compress=self.compress) + self.info = Dictionary({ + 'Creator':String(creator), + 'Producer':String(creator), + 'CreationDate': utcnow(), + }) + self.stroke_opacities, self.fill_opacities = {}, {} + self.font_manager = FontManager(self.objects, self.compress) + self.image_cache = {} + self.pattern_cache, self.shader_cache = {}, {} + self.debug = debug + self.links = Links(self, mark_links, page_size) + i = QImage(1, 1, QImage.Format_ARGB32) + i.fill(qRgba(0, 0, 0, 255)) + self.alpha_bit = i.constBits().asstring(4).find(b'\xff') + + @property + def page_tree(self): + return self.objects[0] + + @property + def catalog(self): + return self.objects[1] + + def get_pageref(self, pagenum): + return self.page_tree.obj.get_ref(pagenum) + + def set_metadata(self, title=None, author=None, tags=None): + if title: + self.info['Title'] = String(title) + if author: + self.info['Author'] = String(author) + if tags: + self.info['Keywords'] = String(tags) + + def write_line(self, byts=b''): + byts = byts if isinstance(byts, bytes) else byts.encode('ascii') + self.stream.write(byts + EOL) + + def transform(self, *args): + if len(args) == 1: + m = args[0] + vals = [m.m11(), m.m12(), m.m21(), m.m22(), m.dx(), m.dy()] + else: + vals = args + cm = ' '.join(map(fmtnum, vals)) + self.current_page.write_line(cm + ' cm') + + def save_stack(self): + self.current_page.write_line('q') + + def restore_stack(self): + self.current_page.write_line('Q') + + def reset_stack(self): + self.current_page.write_line('Q q') + + def draw_rect(self, x, y, width, height, stroke=True, fill=False): + self.current_page.write('%s re '%' '.join(map(fmtnum, (x, y, width, height)))) + self.current_page.write_line(self.PATH_OPS[(stroke, fill, 'winding')]) + + def write_path(self, path): + for i, op in enumerate(path.ops): + if i != 0: + self.current_page.write_line() + for x in op: + self.current_page.write( + (fmtnum(x) if isinstance(x, (int, long, float)) else x) + ' ') + + def draw_path(self, path, stroke=True, fill=False, fill_rule='winding'): + if not path.ops: return + self.write_path(path) + self.current_page.write_line(self.PATH_OPS[(stroke, fill, fill_rule)]) + + def add_clip(self, path, fill_rule='winding'): + if not path.ops: return + self.write_path(path) + op = 'W' if fill_rule == 'winding' else 'W*' + self.current_page.write_line(op + ' ' + 'n') + + def serialize(self, o): + serialize(o, self.current_page) + + def set_stroke_opacity(self, opacity): + if opacity not in self.stroke_opacities: + op = Dictionary({'Type':Name('ExtGState'), 'CA': opacity}) + self.stroke_opacities[opacity] = self.objects.add(op) + self.current_page.set_opacity(self.stroke_opacities[opacity]) + + def set_fill_opacity(self, opacity): + opacity = float(opacity) + if opacity not in self.fill_opacities: + op = Dictionary({'Type':Name('ExtGState'), 'ca': opacity}) + self.fill_opacities[opacity] = self.objects.add(op) + self.current_page.set_opacity(self.fill_opacities[opacity]) + + def end_page(self): + pageref = self.current_page.end(self.objects, self.stream) + self.page_tree.obj.add_page(pageref) + self.current_page = Page(self.page_tree, compress=self.compress) + + def draw_glyph_run(self, transform, size, font_metrics, glyphs): + glyph_ids = {x[-1] for x in glyphs} + fontref = self.font_manager.add_font(font_metrics, glyph_ids) + name = self.current_page.add_font(fontref) + self.current_page.write(b'BT ') + serialize(Name(name), self.current_page) + self.current_page.write(' %s Tf '%fmtnum(size)) + self.current_page.write('%s Tm '%' '.join(map(fmtnum, transform))) + for x, y, glyph_id in glyphs: + self.current_page.write_raw(('%s %s Td <%04X> Tj '%( + fmtnum(x), fmtnum(y), glyph_id)).encode('ascii')) + self.current_page.write_line(b' ET') + + def get_image(self, cache_key): + return self.image_cache.get(cache_key, None) + + def write_image(self, data, w, h, depth, dct=False, mask=None, + soft_mask=None, cache_key=None): + imgobj = Image(data, w, h, depth, mask, soft_mask, dct) + self.image_cache[cache_key] = r = self.objects.add(imgobj) + self.objects.commit(r, self.stream) + return r + + def add_image(self, img, cache_key): + ref = self.get_image(cache_key) + if ref is not None: + return ref + + fmt = img.format() + image = QImage(img) + if (image.depth() == 1 and img.colorTable().size() == 2 and + img.colorTable().at(0) == QColor(Qt.black).rgba() and + img.colorTable().at(1) == QColor(Qt.white).rgba()): + if fmt == QImage.Format_MonoLSB: + image = image.convertToFormat(QImage.Format_Mono) + fmt = QImage.Format_Mono + else: + if (fmt != QImage.Format_RGB32 and fmt != QImage.Format_ARGB32): + image = image.convertToFormat(QImage.Format_ARGB32) + fmt = QImage.Format_ARGB32 + + w = image.width() + h = image.height() + d = image.depth() + + if fmt == QImage.Format_Mono: + bytes_per_line = (w + 7) >> 3 + data = image.constBits().asstring(bytes_per_line * h) + return self.write_image(data, w, h, d, cache_key=cache_key) + + ba = QByteArray() + buf = QBuffer(ba) + image.save(buf, 'jpeg', 94) + data = bytes(ba.data()) + has_alpha = has_mask = False + soft_mask = mask = None + + if fmt == QImage.Format_ARGB32: + tmask = image.constBits().asstring(4*w*h)[self.alpha_bit::4] + sdata = bytearray(tmask) + vals = set(sdata) + vals.discard(255) + has_mask = bool(vals) + vals.discard(0) + has_alpha = bool(vals) + + if has_alpha: + soft_mask = self.write_image(tmask, w, h, 8) + elif has_mask: + # dither the soft mask to 1bit and add it. This also helps PDF + # viewers without transparency support + bytes_per_line = (w + 7) >> 3 + mdata = bytearray(0 for i in xrange(bytes_per_line * h)) + spos = mpos = 0 + for y in xrange(h): + for x in xrange(w): + if sdata[spos]: + mdata[mpos + x>>3] |= (0x80 >> (x&7)) + spos += 1 + mpos += bytes_per_line + mdata = bytes(mdata) + mask = self.write_image(mdata, w, h, 1) + + return self.write_image(data, w, h, 32, mask=mask, dct=True, + soft_mask=soft_mask, cache_key=cache_key) + + def add_pattern(self, pattern): + if pattern.cache_key not in self.pattern_cache: + self.pattern_cache[pattern.cache_key] = self.objects.add(pattern) + return self.current_page.add_pattern(self.pattern_cache[pattern.cache_key]) + + def add_shader(self, shader): + if shader.cache_key not in self.shader_cache: + self.shader_cache[shader.cache_key] = self.objects.add(shader) + return self.shader_cache[shader.cache_key] + + def draw_image(self, x, y, width, height, imgref): + name = self.current_page.add_image(imgref) + self.current_page.write('q %s 0 0 %s %s %s cm '%(fmtnum(width), + fmtnum(-height), fmtnum(x), fmtnum(y+height))) + serialize(Name(name), self.current_page) + self.current_page.write_line(' Do Q') + + def apply_color_space(self, color, pattern, stroke=False): + wl = self.current_page.write_line + if color is not None and pattern is None: + wl(' '.join(map(fmtnum, color)) + (' RG' if stroke else ' rg')) + elif color is None and pattern is not None: + wl('/Pattern %s /%s %s'%('CS' if stroke else 'cs', pattern, + 'SCN' if stroke else 'scn')) + elif color is not None and pattern is not None: + col = ' '.join(map(fmtnum, color)) + wl('/PCSp %s %s /%s %s'%('CS' if stroke else 'cs', col, pattern, + 'SCN' if stroke else 'scn')) + + def apply_fill(self, color=None, pattern=None, opacity=None): + if opacity is not None: + self.set_fill_opacity(opacity) + self.apply_color_space(color, pattern) + + def apply_stroke(self, color=None, pattern=None, opacity=None): + if opacity is not None: + self.set_stroke_opacity(opacity) + self.apply_color_space(color, pattern, stroke=True) + + def end(self): + if self.current_page.getvalue(): + self.end_page() + self.font_manager.embed_fonts() + inforef = self.objects.add(self.info) + self.links.add_links() + self.objects.pdf_serialize(self.stream) + self.write_line() + startxref = self.objects.write_xref(self.stream) + file_id = String(self.stream.hashobj.hexdigest().decode('ascii')) + self.write_line('trailer') + trailer = Dictionary({'Root':self.catalog, 'Size':len(self.objects)+1, + 'ID':Array([file_id, file_id]), 'Info':inforef}) + serialize(trailer, self.stream) + self.write_line('startxref') + self.write_line('%d'%startxref) + self.stream.write('%%EOF') + diff --git a/src/calibre/ebooks/pdf/render/test.py b/src/calibre/ebooks/pdf/render/test.py new file mode 100644 index 0000000000..a68daea97b --- /dev/null +++ b/src/calibre/ebooks/pdf/render/test.py @@ -0,0 +1,135 @@ +#!/usr/bin/env python +# vim:fileencoding=UTF-8:ts=4:sw=4:sta:et:sts=4:fdm=marker:ai +from __future__ import (unicode_literals, division, absolute_import, + print_function) + +__license__ = 'GPL v3' +__copyright__ = '2012, Kovid Goyal ' +__docformat__ = 'restructuredtext en' + +import os + +from PyQt4.Qt import (QBrush, QColor, QPoint, QPixmap, QPainterPath, QRectF, + QApplication, QPainter, Qt, QImage, QLinearGradient, + QPointF, QPen) +QBrush, QColor, QPoint, QPixmap, QPainterPath, QRectF, Qt, QPointF + +from calibre.ebooks.pdf.render.engine import PdfDevice + +def full(p, xmax, ymax): + p.drawRect(0, 0, xmax, ymax) + p.drawPolyline(QPoint(0, 0), QPoint(xmax, 0), QPoint(xmax, ymax), + QPoint(0, ymax), QPoint(0, 0)) + pp = QPainterPath() + pp.addRect(0, 0, xmax, ymax) + p.drawPath(pp) + p.save() + for i in xrange(3): + col = [0, 0, 0, 200] + col[i] = 255 + p.setOpacity(0.3) + p.fillRect(0, 0, xmax/10, xmax/10, QBrush(QColor(*col))) + p.setOpacity(1) + p.drawRect(0, 0, xmax/10, xmax/10) + p.translate(xmax/10, xmax/10) + p.scale(1, 1.5) + p.restore() + + # p.scale(2, 2) + # p.rotate(45) + p.drawPixmap(0, 0, xmax/4, xmax/4, QPixmap(I('library.png'))) + p.drawRect(0, 0, xmax/4, xmax/4) + + f = p.font() + f.setPointSize(20) + # f.setLetterSpacing(f.PercentageSpacing, 200) + f.setUnderline(True) + # f.setOverline(True) + # f.setStrikeOut(True) + f.setFamily('Calibri') + p.setFont(f) + # p.setPen(QColor(0, 0, 255)) + # p.scale(2, 2) + # p.rotate(45) + p.drawText(QPoint(xmax/3.9, 30), 'Some—text not By’s ū --- Д AV ff ff') + + b = QBrush(Qt.HorPattern) + b.setColor(QColor(Qt.blue)) + pix = QPixmap(I('console.png')) + w = xmax/4 + p.fillRect(0, ymax/3, w, w, b) + p.fillRect(xmax/3, ymax/3, w, w, QBrush(pix)) + x, y = 2*xmax/3, ymax/3 + p.drawTiledPixmap(QRectF(x, y, w, w), pix, QPointF(10, 10)) + + x, y = 1, ymax/1.9 + g = QLinearGradient(QPointF(x, y), QPointF(x+w, y+w)) + g.setColorAt(0, QColor('#00f')) + g.setColorAt(1, QColor('#fff')) + p.fillRect(x, y, w, w, QBrush(g)) + + +def run(dev, func): + p = QPainter(dev) + if isinstance(dev, PdfDevice): + dev.init_page() + xmax, ymax = p.viewport().width(), p.viewport().height() + try: + func(p, xmax, ymax) + finally: + p.end() + if isinstance(dev, PdfDevice): + if dev.engine.errors_occurred: + raise SystemExit(1) + +def brush(p, xmax, ymax): + x = 0 + y = 0 + w = xmax/2 + g = QLinearGradient(QPointF(x, y+w/3), QPointF(x, y+(2*w/3))) + g.setColorAt(0, QColor('#f00')) + g.setColorAt(0.5, QColor('#fff')) + g.setColorAt(1, QColor('#00f')) + g.setSpread(g.ReflectSpread) + p.fillRect(x, y, w, w, QBrush(g)) + p.drawRect(x, y, w, w) + +def pen(p, xmax, ymax): + pix = QPixmap(I('console.png')) + pen = QPen(QBrush(pix), 60) + p.setPen(pen) + p.drawRect(0, xmax/3, xmax/3, xmax/2) + +def text(p, xmax, ymax): + f = p.font() + f.setPixelSize(24) + f.setFamily('Candara') + p.setFont(f) + p.drawText(QPoint(0, 100), + 'Test intra glyph spacing ffagain imceo') + +def main(): + app = QApplication([]) + app + tdir = os.path.abspath('.') + pdf = os.path.join(tdir, 'painter.pdf') + func = brush + dpi = 100 + with open(pdf, 'wb') as f: + dev = PdfDevice(f, xdpi=dpi, ydpi=dpi, compress=False) + img = QImage(dev.width(), dev.height(), + QImage.Format_ARGB32_Premultiplied) + img.setDotsPerMeterX(dpi*39.37) + img.setDotsPerMeterY(dpi*39.37) + img.fill(Qt.white) + run(dev, func) + run(img, func) + path = os.path.join(tdir, 'painter.png') + img.save(path) + print ('PDF written to:', pdf) + print ('Image written to:', path) + +if __name__ == '__main__': + main() + + diff --git a/src/calibre/ebooks/pdf/writer.py b/src/calibre/ebooks/pdf/writer.py index 76ab6b9096..eaec0e79bc 100644 --- a/src/calibre/ebooks/pdf/writer.py +++ b/src/calibre/ebooks/pdf/writer.py @@ -9,18 +9,16 @@ Write content to PDF. ''' import os, shutil, json -from future_builtins import map from PyQt4.Qt import (QEventLoop, QObject, QPrinter, QSizeF, Qt, QPainter, QPixmap, QTimer, pyqtProperty, QString, QSize) from PyQt4.QtWebKit import QWebView, QWebPage, QWebSettings from calibre.ptempfile import PersistentTemporaryDirectory -from calibre.ebooks.pdf.pageoptions import (unit, paper_size, orientation) +from calibre.ebooks.pdf.pageoptions import (unit, paper_size) from calibre.ebooks.pdf.outline_writer import Outline -from calibre.ebooks.metadata import authors_to_string from calibre.ptempfile import PersistentTemporaryFile -from calibre import (__appname__, __version__, fit_image, isosx, force_unicode) +from calibre import (__appname__, __version__, fit_image, isosx) from calibre.ebooks.oeb.display.webview import load_html def get_custom_size(opts): @@ -52,7 +50,7 @@ def get_pdf_printer(opts, for_comic=False, output_file_name=None): # {{{ printer.setPaperSize(paper_size(opts.paper_size)) else: if opts.output_profile.short_name == 'default' or \ - opts.output_profile.width > 9999: + opts.output_profile.width > 9999 or opts.override_profile_size: if custom_size is None: printer.setPaperSize(paper_size(opts.paper_size)) else: @@ -72,7 +70,6 @@ def get_pdf_printer(opts, for_comic=False, output_file_name=None): # {{{ else: printer.setPageMargins(opts.margin_left, opts.margin_top, opts.margin_right, opts.margin_bottom, QPrinter.Point) - printer.setOrientation(orientation(opts.orientation)) printer.setOutputFormat(QPrinter.PdfFormat) printer.setFullPage(for_comic) if output_file_name: @@ -103,24 +100,6 @@ def draw_image_page(printer, painter, p, preserve_aspect_ratio=True): painter.drawPixmap(page_rect, p, p.rect()) -class PDFMetadata(object): # {{{ - def __init__(self, oeb_metadata=None): - self.title = _(u'Unknown') - self.author = _(u'Unknown') - self.tags = u'' - - if oeb_metadata != None: - if len(oeb_metadata.title) >= 1: - self.title = oeb_metadata.title[0].value - if len(oeb_metadata.creator) >= 1: - self.author = authors_to_string([x.value for x in oeb_metadata.creator]) - if oeb_metadata.subject: - self.tags = u', '.join(map(unicode, oeb_metadata.subject)) - - self.title = force_unicode(self.title) - self.author = force_unicode(self.author) -# }}} - class Page(QWebPage): # {{{ def __init__(self, opts, log): diff --git a/src/calibre/gui2/__init__.py b/src/calibre/gui2/__init__.py index ddeba9e190..d7a2669b0b 100644 --- a/src/calibre/gui2/__init__.py +++ b/src/calibre/gui2/__init__.py @@ -106,6 +106,7 @@ gprefs.defaults['tag_browser_old_look'] = False gprefs.defaults['book_list_tooltips'] = True gprefs.defaults['bd_show_cover'] = True gprefs.defaults['bd_overlay_cover_size'] = False +gprefs.defaults['tags_browser_category_icons'] = {} # }}} NONE = QVariant() #: Null value to return from the data function of item models @@ -765,6 +766,26 @@ class Translator(QTranslator): gui_thread = None qt_app = None + +def load_builtin_fonts(): + global _rating_font + # Load the builtin fonts and any fonts added to calibre by the user to + # Qt + for ff in glob.glob(P('fonts/liberation/*.?tf')) + \ + [P('fonts/calibreSymbols.otf')] + \ + glob.glob(os.path.join(config_dir, 'fonts', '*.?tf')): + if ff.rpartition('.')[-1].lower() in {'ttf', 'otf'}: + with open(ff, 'rb') as s: + # Windows requires font files to be executable for them to be + # loaded successfully, so we use the in memory loader + fid = QFontDatabase.addApplicationFontFromData(s.read()) + if fid > -1: + fam = QFontDatabase.applicationFontFamilies(fid) + fam = set(map(unicode, fam)) + if u'calibre Symbols' in fam: + _rating_font = u'calibre Symbols' + + class Application(QApplication): def __init__(self, args, force_calibre_style=False, @@ -797,27 +818,12 @@ class Application(QApplication): return ret def load_builtin_fonts(self, scan_for_fonts=False): - global _rating_font if scan_for_fonts: from calibre.utils.fonts.scanner import font_scanner # Start scanning the users computer for fonts font_scanner - # Load the builtin fonts and any fonts added to calibre by the user to - # Qt - for ff in glob.glob(P('fonts/liberation/*.?tf')) + \ - [P('fonts/calibreSymbols.otf')] + \ - glob.glob(os.path.join(config_dir, 'fonts', '*.?tf')): - if ff.rpartition('.')[-1].lower() in {'ttf', 'otf'}: - with open(ff, 'rb') as s: - # Windows requires font files to be executable for them to be - # loaded successfully, so we use the in memory loader - fid = QFontDatabase.addApplicationFontFromData(s.read()) - if fid > -1: - fam = QFontDatabase.applicationFontFamilies(fid) - fam = set(map(unicode, fam)) - if u'calibre Symbols' in fam: - _rating_font = u'calibre Symbols' + load_builtin_fonts() def load_calibre_style(self): # On OS X QtCurve resets the palette, so we preserve it explicitly @@ -1034,7 +1040,9 @@ def build_forms(srcdir, info=None): dat = dat.replace('from widgets import', 'from calibre.gui2.widgets import') dat = dat.replace('from convert.xpath_wizard import', 'from calibre.gui2.convert.xpath_wizard import') - dat = re.compile(r'QtGui.QApplication.translate\(.+?,\s+"(.+?)(?' + diff --git a/src/calibre/gui2/add.py b/src/calibre/gui2/add.py index 2b817b663a..3a958c4cfa 100644 --- a/src/calibre/gui2/add.py +++ b/src/calibre/gui2/add.py @@ -8,10 +8,10 @@ from functools import partial from PyQt4.Qt import QThread, QObject, Qt, QProgressDialog, pyqtSignal, QTimer from calibre.gui2.dialogs.progress import ProgressDialog -from calibre.gui2 import (question_dialog, error_dialog, info_dialog, gprefs, +from calibre.gui2 import (error_dialog, info_dialog, gprefs, warning_dialog, available_width) from calibre.ebooks.metadata.opf2 import OPF -from calibre.ebooks.metadata import MetaInformation, authors_to_string +from calibre.ebooks.metadata import MetaInformation from calibre.constants import preferred_encoding, filesystem_encoding, DEBUG from calibre.utils.config import prefs from calibre import prints, force_unicode, as_unicode @@ -391,25 +391,10 @@ class Adder(QObject): # {{{ if not duplicates: return self.duplicates_processed() self.pd.hide() - duplicate_message = [] - for x in duplicates: - duplicate_message.append(_('Already in calibre:')) - matching_books = self.db.books_with_same_title(x[0]) - for book_id in matching_books: - aut = [a.replace('|', ',') for a in (self.db.authors(book_id, - index_is_id=True) or '').split(',')] - duplicate_message.append('\t'+ _('%(title)s by %(author)s')% - dict(title=self.db.title(book_id, index_is_id=True), - author=authors_to_string(aut))) - duplicate_message.append(_('You are trying to add:')) - duplicate_message.append('\t'+_('%(title)s by %(author)s')% - dict(title=x[0].title, - author=x[0].format_field('authors')[1])) - duplicate_message.append('') - if question_dialog(self._parent, _('Duplicates found!'), - _('Books with the same title as the following already ' - 'exist in calibre. Add them anyway?'), - '\n'.join(duplicate_message)): + from calibre.gui2.dialogs.duplicates import DuplicatesQuestion + d = DuplicatesQuestion(self.db, duplicates, self._parent) + duplicates = tuple(d.duplicates) + if duplicates: pd = QProgressDialog(_('Adding duplicates...'), '', 0, len(duplicates), self._parent) pd.setCancelButton(None) diff --git a/src/calibre/gui2/convert/look_and_feel.ui b/src/calibre/gui2/convert/look_and_feel.ui index 505079a14c..43736fb1f2 100644 --- a/src/calibre/gui2/convert/look_and_feel.ui +++ b/src/calibre/gui2/convert/look_and_feel.ui @@ -411,7 +411,7 @@ - &Subset all embedded fonts (Experimental) + &Subset all embedded fonts diff --git a/src/calibre/gui2/convert/metadata.py b/src/calibre/gui2/convert/metadata.py index a2c1427b91..9af287f641 100644 --- a/src/calibre/gui2/convert/metadata.py +++ b/src/calibre/gui2/convert/metadata.py @@ -26,6 +26,7 @@ def create_opf_file(db, book_id): mi.application_id = uuid.uuid4() old_cover = mi.cover mi.cover = None + mi.application_id = mi.uuid raw = metadata_to_opf(mi) mi.cover = old_cover opf_file = PersistentTemporaryFile('.opf') diff --git a/src/calibre/gui2/convert/pdf_output.py b/src/calibre/gui2/convert/pdf_output.py index a2bfcc667f..e0674d066c 100644 --- a/src/calibre/gui2/convert/pdf_output.py +++ b/src/calibre/gui2/convert/pdf_output.py @@ -18,16 +18,17 @@ class PluginWidget(Widget, Ui_Form): ICON = I('mimetypes/pdf.png') def __init__(self, parent, get_option, get_help, db=None, book_id=None): - Widget.__init__(self, parent, ['paper_size', 'custom_size', - 'orientation', 'preserve_cover_aspect_ratio', 'pdf_serif_family', + Widget.__init__(self, parent, [ + 'override_profile_size', 'paper_size', 'custom_size', + 'preserve_cover_aspect_ratio', 'pdf_serif_family', 'unit', 'pdf_sans_family', 'pdf_mono_family', 'pdf_standard_font', 'pdf_default_font_size', 'pdf_mono_font_size']) self.db, self.book_id = db, book_id for x in get_option('paper_size').option.choices: self.opt_paper_size.addItem(x) - for x in get_option('orientation').option.choices: - self.opt_orientation.addItem(x) + for x in get_option('unit').option.choices: + self.opt_unit.addItem(x) for x in get_option('pdf_standard_font').option.choices: self.opt_pdf_standard_font.addItem(x) diff --git a/src/calibre/gui2/convert/pdf_output.ui b/src/calibre/gui2/convert/pdf_output.ui index 5f77f526d0..5e3c4c9137 100644 --- a/src/calibre/gui2/convert/pdf_output.ui +++ b/src/calibre/gui2/convert/pdf_output.ui @@ -14,7 +14,27 @@ Form - + + QFormLayout::ExpandingFieldsGrow + + + + + <b>Note:</b> The paper size settings below only take effect if you enable the "Override" checkbox below. Otherwise the size from the output profile will be used. + + + true + + + + + + + &Override paper size set in output profile + + + + &Paper Size: @@ -24,21 +44,8 @@ - - - - - - - &Orientation: - - - opt_orientation - - - - + @@ -51,7 +58,24 @@ - + + + + + + + + &Unit: + + + opt_unit + + + + + + + @@ -60,19 +84,6 @@ - - - - Qt::Vertical - - - - 20 - 213 - - - - @@ -159,15 +170,18 @@ - - - - <b>Note:</b> The paper size settings below only take effect if you have set the output profile to the default output profile. Otherwise the output profile will override these settings. + + + + Qt::Vertical - - true + + + 20 + 213 + - + diff --git a/src/calibre/gui2/convert/single.py b/src/calibre/gui2/convert/single.py index 332dc4ae92..cff7ee8c3d 100644 --- a/src/calibre/gui2/convert/single.py +++ b/src/calibre/gui2/convert/single.py @@ -33,7 +33,10 @@ from calibre.utils.config import prefs from calibre.utils.logging import Log class NoSupportedInputFormats(Exception): - pass + + def __init__(self, available_formats): + Exception.__init__(self) + self.available_formats = available_formats def sort_formats_by_preference(formats, prefs): uprefs = [x.upper() for x in prefs] @@ -86,7 +89,7 @@ def get_supported_input_formats_for_book(db, book_id): input_formats = set([x.lower() for x in supported_input_formats()]) input_formats = sorted(available_formats.intersection(input_formats)) if not input_formats: - raise NoSupportedInputFormats + raise NoSupportedInputFormats(tuple(x for x in available_formats if x)) return input_formats diff --git a/src/calibre/gui2/custom_column_widgets.py b/src/calibre/gui2/custom_column_widgets.py index 2b45769185..0f5bb0a1c9 100644 --- a/src/calibre/gui2/custom_column_widgets.py +++ b/src/calibre/gui2/custom_column_widgets.py @@ -369,10 +369,11 @@ class Series(Base): w.setMinimumContentsLength(25) self.name_widget = w self.widgets = [QLabel('&'+self.col_metadata['name']+':', parent), w] + w.editTextChanged.connect(self.series_changed) self.widgets.append(QLabel('&'+self.col_metadata['name']+_(' index:'), parent)) w = QDoubleSpinBox(parent) - w.setRange(-100., float(100000000)) + w.setRange(-10000., float(100000000)) w.setDecimals(2) w.setSingleStep(1) self.idx_widget=w @@ -382,33 +383,42 @@ class Series(Base): values = list(self.db.all_custom(num=self.col_id)) values.sort(key=sort_key) val = self.db.get_custom(book_id, num=self.col_id, index_is_id=True) - s_index = self.db.get_custom_extra(book_id, num=self.col_id, index_is_id=True) - if s_index is None: - s_index = 0.0 - self.idx_widget.setValue(s_index) - self.initial_index = s_index self.initial_val = val + s_index = self.db.get_custom_extra(book_id, num=self.col_id, index_is_id=True) + self.initial_index = s_index + try: + s_index = float(s_index) + except (ValueError, TypeError): + s_index = 1.0 + self.idx_widget.setValue(s_index) val = self.normalize_db_val(val) + self.name_widget.blockSignals(True) self.name_widget.update_items_cache(values) self.name_widget.show_initial_value(val) + self.name_widget.blockSignals(False) def getter(self): n = unicode(self.name_widget.currentText()).strip() i = self.idx_widget.value() return n, i + def series_changed(self, val): + val, s_index = self.gui_val + if tweaks['series_index_auto_increment'] == 'no_change': + pass + elif tweaks['series_index_auto_increment'] == 'const': + s_index = 1.0 + else: + s_index = self.db.get_next_cc_series_num_for(val, + num=self.col_id) + self.idx_widget.setValue(s_index) + def commit(self, book_id, notify=False): val, s_index = self.gui_val val = self.normalize_ui_val(val) if val != self.initial_val or s_index != self.initial_index: if val == '': val = s_index = None - elif s_index == 0.0: - if tweaks['series_index_auto_increment'] != 'const': - s_index = self.db.get_next_cc_series_num_for(val, - num=self.col_id) - else: - s_index = None return self.db.set_custom(book_id, val, extra=s_index, num=self.col_id, notify=notify, commit=False, allow_case_change=True) else: diff --git a/src/calibre/gui2/device.py b/src/calibre/gui2/device.py index 1047fef1b6..85e992e264 100644 --- a/src/calibre/gui2/device.py +++ b/src/calibre/gui2/device.py @@ -985,6 +985,12 @@ class DeviceMixin(object): # {{{ return except: pass + if getattr(job, 'exception', None).__class__.__name__ == 'MTPInvalidSendPathError': + try: + from calibre.gui2.device_drivers.mtp_config import SendError + return SendError(self, job.exception).exec_() + except: + traceback.print_exc() try: prints(job.details, file=sys.stderr) except: diff --git a/src/calibre/gui2/device_drivers/mtp_config.py b/src/calibre/gui2/device_drivers/mtp_config.py index 5ab1dd692e..b71766b7da 100644 --- a/src/calibre/gui2/device_drivers/mtp_config.py +++ b/src/calibre/gui2/device_drivers/mtp_config.py @@ -13,13 +13,13 @@ from PyQt4.Qt import (QWidget, QListWidgetItem, Qt, QToolButton, QLabel, QTabWidget, QGridLayout, QListWidget, QIcon, QLineEdit, QVBoxLayout, QPushButton, QGroupBox, QScrollArea, QHBoxLayout, QComboBox, pyqtSignal, QSizePolicy, QDialog, QDialogButtonBox, QPlainTextEdit, - QApplication) + QApplication, QSize) from calibre.ebooks import BOOK_EXTENSIONS from calibre.gui2 import error_dialog from calibre.gui2.dialogs.template_dialog import TemplateDialog from calibre.utils.date import parse_date -from calibre.gui2.device_drivers.mtp_folder_browser import Browser +from calibre.gui2.device_drivers.mtp_folder_browser import Browser, TopLevel class FormatsConfig(QWidget): # {{{ @@ -328,7 +328,7 @@ class FormatRules(QGroupBox): class MTPConfig(QTabWidget): - def __init__(self, device, parent=None): + def __init__(self, device, parent=None, highlight_ignored_folders=False): QTabWidget.__init__(self, parent) self._device = weakref.ref(device) @@ -373,23 +373,33 @@ class MTPConfig(QTabWidget): _('&Ignore the %s in calibre')%device.current_friendly_name, self.base) b.clicked.connect(self.ignore_device) + self.config_ign_folders_button = cif = QPushButton( + QIcon(I('tb_folder.png')), _('Change scanned &folders')) + cif.setStyleSheet( + 'QPushButton { font-weight: bold; }') + if highlight_ignored_folders: + cif.setIconSize(QSize(64, 64)) self.show_debug_button = bd = QPushButton(QIcon(I('debug.png')), _('Show device information')) bd.clicked.connect(self.show_debug_info) + cif.clicked.connect(self.change_ignored_folders) l.addWidget(b, 0, 0, 1, 2) l.addWidget(la, 1, 0, 1, 1) - l.addWidget(self.formats, 2, 0, 4, 1) - l.addWidget(self.send_to, 2, 1, 1, 1) + l.addWidget(self.formats, 2, 0, 5, 1) + l.addWidget(cif, 2, 1, 1, 1) l.addWidget(self.template, 3, 1, 1, 1) - l.addWidget(self.show_debug_button, 4, 1, 1, 1) - l.setRowStretch(5, 10) - l.addWidget(r, 6, 0, 1, 2) - l.setRowStretch(6, 100) + l.addWidget(self.send_to, 4, 1, 1, 1) + l.addWidget(self.show_debug_button, 5, 1, 1, 1) + l.setRowStretch(6, 10) + l.addWidget(r, 7, 0, 1, 2) + l.setRowStretch(7, 100) self.igntab = IgnoredDevices(self.device.prefs['history'], self.device.prefs['blacklist']) self.addTab(self.igntab, _('Ignored devices')) + self.current_ignored_folders = self.get_pref('ignored_folders') + self.initial_ignored_folders = self.current_ignored_folders self.setCurrentIndex(1 if msg else 0) @@ -413,6 +423,12 @@ class MTPConfig(QTabWidget): QApplication.clipboard().setText(v.toPlainText())) d.exec_() + def change_ignored_folders(self): + d = TopLevel(self.device, + self.current_ignored_folders, parent=self) + if d.exec_() == d.Accepted: + self.current_ignored_folders = d.ignored_folders + def ignore_device(self): self.igntab.ignore_device(self.device.current_serial_num) self.base.b.setEnabled(False) @@ -464,8 +480,42 @@ class MTPConfig(QTabWidget): if r and r != self.device.prefs['rules']: p['rules'] = r + if self.current_ignored_folders != self.initial_ignored_folders: + p['ignored_folders'] = self.current_ignored_folders + self.device.prefs[self.current_device_key] = p +class SendError(QDialog): + + def __init__(self, gui, error): + QDialog.__init__(self, gui) + self.l = l = QVBoxLayout() + self.setLayout(l) + self.la = la = QLabel('

'+ + _('You are trying to send books into the %s folder. This ' + 'folder is currently ignored by calibre when scanning the ' + 'device. You have tell calibre you want this folder scanned ' + 'in order to be able to send books to it. Click the ' + 'configure button below to send books to it.')%error.folder) + la.setWordWrap(True) + la.setMinimumWidth(500) + l.addWidget(la) + self.bb = bb = QDialogButtonBox(QDialogButtonBox.Close) + self.b = bb.addButton(_('Configure'), bb.AcceptRole) + bb.accepted.connect(self.accept) + bb.rejected.connect(self.reject) + l.addWidget(bb) + self.setWindowTitle(_('Cannot send to %s')%error.folder) + self.setWindowIcon(QIcon(I('dialog_error.png'))) + + self.resize(self.sizeHint()) + + def accept(self): + QDialog.accept(self) + dev = self.parent().device_manager.connected_device + dev.highlight_ignored_folders = True + self.parent().configure_connected_device() + dev.highlight_ignored_folders = False if __name__ == '__main__': from calibre.gui2 import Application diff --git a/src/calibre/gui2/device_drivers/mtp_folder_browser.py b/src/calibre/gui2/device_drivers/mtp_folder_browser.py index 1e108ff117..6a54f65b38 100644 --- a/src/calibre/gui2/device_drivers/mtp_folder_browser.py +++ b/src/calibre/gui2/device_drivers/mtp_folder_browser.py @@ -10,7 +10,8 @@ __docformat__ = 'restructuredtext en' from operator import attrgetter from PyQt4.Qt import (QTabWidget, QTreeWidget, QTreeWidgetItem, Qt, QDialog, - QDialogButtonBox, QVBoxLayout, QSize, pyqtSignal, QIcon) + QDialogButtonBox, QVBoxLayout, QSize, pyqtSignal, QIcon, QLabel, + QListWidget, QListWidgetItem) from calibre.gui2 import file_icon_provider @@ -95,25 +96,105 @@ class Browser(QDialog): def current_item(self): return self.folders.current_item -def browse(): - from calibre.gui2 import Application +class TopLevel(QDialog): + + def __init__(self, dev, ignored_folders=None, parent=None): + QDialog.__init__(self, parent) + self.l = l = QVBoxLayout() + self.setLayout(l) + self.la = la = QLabel('

'+ _('Scanned folders:') + ' ' + + _('You can select which top level folders calibre will ' + 'scan when searching this device for books.')) + la.setWordWrap(True) + l.addWidget(la) + self.tabs = QTabWidget(self) + l.addWidget(self.tabs) + self.widgets = [] + + for storage in dev.filesystem_cache.entries: + w = QListWidget(self) + w.storage = storage + self.tabs.addTab(w, storage.name) + self.widgets.append(w) + for child in sorted(storage.folders, key=attrgetter('name')): + i = QListWidgetItem(child.name) + i.setFlags(Qt.ItemIsUserCheckable | Qt.ItemIsEnabled) + i.setCheckState(Qt.Unchecked if + dev.is_folder_ignored(storage, child.name, + ignored_folders=ignored_folders) else Qt.Checked) + w.addItem(i) + + self.bb = QDialogButtonBox(QDialogButtonBox.Ok | + QDialogButtonBox.Cancel) + self.bb.accepted.connect(self.accept) + self.bb.rejected.connect(self.reject) + self.sab = self.bb.addButton(_('Select &All'), self.bb.ActionRole) + self.sab.clicked.connect(self.select_all) + self.snb = self.bb.addButton(_('Select &None'), self.bb.ActionRole) + self.snb.clicked.connect(self.select_none) + l.addWidget(self.bb) + self.setWindowTitle(_('Choose folders to scan')) + self.setWindowIcon(QIcon(I('devices/tablet.png'))) + + self.resize(500, 500) + + def select_all(self): + w = self.tabs.currentWidget() + for i in xrange(w.count()): + x = w.item(i) + x.setCheckState(Qt.Checked) + + def select_none(self): + w = self.tabs.currentWidget() + for i in xrange(w.count()): + x = w.item(i) + x.setCheckState(Qt.Unchecked) + + @property + def ignored_folders(self): + ans = {} + for w in self.widgets: + ans[unicode(w.storage.object_id)] = folders = [] + for i in xrange(w.count()): + x = w.item(i) + if x.checkState() != Qt.Checked: + folders.append(unicode(x.text())) + return ans + +def setup_device(): from calibre.devices.mtp.driver import MTP_DEVICE from calibre.devices.scanner import DeviceScanner s = DeviceScanner() s.scan() - app = Application([]) - app dev = MTP_DEVICE(None) dev.startup() cd = dev.detect_managed_devices(s.devices) if cd is None: raise ValueError('No MTP device found') dev.open(cd, 'test') + return dev + +def browse(): + from calibre.gui2 import Application + app = Application([]) + app + dev = setup_device() d = Browser(dev.filesystem_cache) d.exec_() dev.shutdown() return d.current_item -if __name__ == '__main__': - print (browse()) +def top_level(): + from calibre.gui2 import Application + app = Application([]) + app + dev = setup_device() + d = TopLevel(dev, None) + d.exec_() + dev.shutdown() + return d.ignored_folders + +if __name__ == '__main__': + # print (browse()) + print ('Ignored:', top_level()) diff --git a/src/calibre/gui2/dialogs/add_empty_book.py b/src/calibre/gui2/dialogs/add_empty_book.py index 98992f85bb..185a3699e7 100644 --- a/src/calibre/gui2/dialogs/add_empty_book.py +++ b/src/calibre/gui2/dialogs/add_empty_book.py @@ -12,7 +12,7 @@ from calibre.utils.config import tweaks class AddEmptyBookDialog(QDialog): - def __init__(self, parent, db, author): + def __init__(self, parent, db, author, series=None): QDialog.__init__(self, parent) self.db = db @@ -45,6 +45,22 @@ class AddEmptyBookDialog(QDialog): self.clear_button.clicked.connect(self.reset_author) self._layout.addWidget(self.clear_button, 3, 1, 1, 1) + self.series_label = QLabel(_('Set the series of the new books to:')) + self._layout.addWidget(self.series_label, 4, 0, 1, 2) + + self.series_combo = EditWithComplete(self) + self.authors_combo.setSizeAdjustPolicy( + self.authors_combo.AdjustToMinimumContentsLengthWithIcon) + self.series_combo.setEditable(True) + self._layout.addWidget(self.series_combo, 5, 0, 1, 1) + self.initialize_series(db, series) + + self.sclear_button = QToolButton(self) + self.sclear_button.setIcon(QIcon(I('trash.png'))) + self.sclear_button.setToolTip(_('Reset series')) + self.sclear_button.clicked.connect(self.reset_series) + self._layout.addWidget(self.sclear_button, 5, 1, 1, 1) + button_box = QDialogButtonBox(QDialogButtonBox.Ok | QDialogButtonBox.Cancel) button_box.accepted.connect(self.accept) button_box.rejected.connect(self.reject) @@ -54,6 +70,9 @@ class AddEmptyBookDialog(QDialog): def reset_author(self, *args): self.authors_combo.setEditText(_('Unknown')) + def reset_series(self): + self.series_combo.setEditText('') + def initialize_authors(self, db, author): au = author if not au: @@ -65,6 +84,11 @@ class AddEmptyBookDialog(QDialog): self.authors_combo.set_add_separator(tweaks['authors_completer_append_separator']) self.authors_combo.update_items_cache(db.all_author_names()) + def initialize_series(self, db, series): + self.series_combo.show_initial_value(series or '') + self.series_combo.update_items_cache(db.all_series_names()) + self.series_combo.set_separator(None) + @property def qty_to_add(self): return self.qty_spinbox.value() @@ -73,6 +97,10 @@ class AddEmptyBookDialog(QDialog): def selected_authors(self): return string_to_authors(unicode(self.authors_combo.text())) + @property + def selected_series(self): + return unicode(self.series_combo.text()) + if __name__ == '__main__': app = QApplication([]) d = AddEmptyBookDialog() diff --git a/src/calibre/gui2/dialogs/duplicates.py b/src/calibre/gui2/dialogs/duplicates.py new file mode 100644 index 0000000000..eb1787c0c5 --- /dev/null +++ b/src/calibre/gui2/dialogs/duplicates.py @@ -0,0 +1,118 @@ +#!/usr/bin/env python +# vim:fileencoding=UTF-8:ts=4:sw=4:sta:et:sts=4:fdm=marker:ai +from __future__ import (unicode_literals, division, absolute_import, + print_function) + +__license__ = 'GPL v3' +__copyright__ = '2013, Kovid Goyal ' +__docformat__ = 'restructuredtext en' + +from PyQt4.Qt import (QDialog, QGridLayout, QIcon, QLabel, QTreeWidget, + QTreeWidgetItem, Qt, QFont, QDialogButtonBox) + +from calibre.ebooks.metadata import authors_to_string + +class DuplicatesQuestion(QDialog): + + def __init__(self, db, duplicates, parent=None): + QDialog.__init__(self, parent) + self.l = l = QGridLayout() + self.setLayout(l) + self.setWindowTitle(_('Duplicates found!')) + self.i = i = QIcon(I('dialog_question.png')) + self.setWindowIcon(i) + + self.l1 = l1 = QLabel() + self.l2 = l2 = QLabel(_( + 'Books with the same titles as the following already ' + 'exist in calibre. Select which books you want added anyway.')) + l2.setWordWrap(True) + l1.setPixmap(i.pixmap(128, 128)) + l.addWidget(l1, 0, 0) + l.addWidget(l2, 0, 1) + + self.dup_list = dl = QTreeWidget(self) + l.addWidget(dl, 1, 0, 1, 2) + dl.setHeaderHidden(True) + dl.addTopLevelItems(list(self.process_duplicates(db, duplicates))) + dl.expandAll() + dl.setIndentation(30) + + self.bb = bb = QDialogButtonBox(QDialogButtonBox.Ok|QDialogButtonBox.Cancel) + bb.accepted.connect(self.accept) + bb.rejected.connect(self.reject) + l.addWidget(bb, 2, 0, 1, 2) + self.ab = ab = bb.addButton(_('Select &all'), bb.ActionRole) + ab.clicked.connect(self.select_all) + self.nb = ab = bb.addButton(_('Select &none'), bb.ActionRole) + ab.clicked.connect(self.select_none) + + self.resize(self.sizeHint()) + self.exec_() + + def select_all(self): + for i in xrange(self.dup_list.topLevelItemCount()): + x = self.dup_list.topLevelItem(i) + x.setCheckState(0, Qt.Checked) + + def select_none(self): + for i in xrange(self.dup_list.topLevelItemCount()): + x = self.dup_list.topLevelItem(i) + x.setCheckState(0, Qt.Unchecked) + + def reject(self): + self.select_none() + QDialog.reject(self) + + def process_duplicates(self, db, duplicates): + ta = _('%(title)s by %(author)s') + bf = QFont(self.dup_list.font()) + bf.setBold(True) + itf = QFont(self.dup_list.font()) + itf.setItalic(True) + + for mi, cover, formats in duplicates: + item = QTreeWidgetItem([ta%dict( + title=mi.title, author=mi.format_field('authors')[1])] , 0) + item.setCheckState(0, Qt.Checked) + item.setFlags(Qt.ItemIsEnabled|Qt.ItemIsUserCheckable) + item.setData(0, Qt.FontRole, bf) + item.setData(0, Qt.UserRole, (mi, cover, formats)) + matching_books = db.books_with_same_title(mi) + + def add_child(text): + c = QTreeWidgetItem([text], 1) + c.setFlags(Qt.ItemIsEnabled) + item.addChild(c) + return c + + add_child(_('Already in calibre:')).setData(0, Qt.FontRole, itf) + + for book_id in matching_books: + aut = [a.replace('|', ',') for a in (db.authors(book_id, + index_is_id=True) or '').split(',')] + add_child(ta%dict( + title=db.title(book_id, index_is_id=True), + author=authors_to_string(aut))) + add_child('') + + yield item + + @property + def duplicates(self): + for i in xrange(self.dup_list.topLevelItemCount()): + x = self.dup_list.topLevelItem(i) + if x.checkState(0) == Qt.Checked: + yield x.data(0, Qt.UserRole).toPyObject() + +if __name__ == '__main__': + from PyQt4.Qt import QApplication + from calibre.ebooks.metadata.book.base import Metadata as M + from calibre.library import db + + app = QApplication([]) + db = db() + d = DuplicatesQuestion(db, [(M('Life of Pi', ['Yann Martel']), None, None), + (M('Heirs of the blade', ['Adrian Tchaikovsky']), None, None)]) + print (tuple(d.duplicates)) + diff --git a/src/calibre/gui2/dialogs/metadata_bulk.ui b/src/calibre/gui2/dialogs/metadata_bulk.ui index 917dfbb159..24a84eb1b4 100644 --- a/src/calibre/gui2/dialogs/metadata_bulk.ui +++ b/src/calibre/gui2/dialogs/metadata_bulk.ui @@ -1109,8 +1109,8 @@ not multiple and the destination field is multiple 0 0 - 205 - 66 + 934 + 213 @@ -1269,8 +1269,8 @@ not multiple and the destination field is multiple accept() - 252 - 382 + 258 + 638 157 @@ -1285,8 +1285,8 @@ not multiple and the destination field is multiple reject() - 320 - 382 + 326 + 638 286 @@ -1294,5 +1294,37 @@ not multiple and the destination field is multiple + + remove_all_tags + toggled(bool) + remove_tags + setDisabled(bool) + + + 888 + 266 + + + 814 + 268 + + + + + clear_languages + toggled(bool) + languages + setDisabled(bool) + + + 874 + 418 + + + 817 + 420 + + + diff --git a/src/calibre/gui2/dialogs/plugin_updater.py b/src/calibre/gui2/dialogs/plugin_updater.py index 4e56db9016..3820169876 100644 --- a/src/calibre/gui2/dialogs/plugin_updater.py +++ b/src/calibre/gui2/dialogs/plugin_updater.py @@ -519,6 +519,7 @@ class PluginUpdaterDialog(SizePersistedDialog): self.description.setFrameStyle(QFrame.Panel | QFrame.Sunken) self.description.setAlignment(Qt.AlignTop | Qt.AlignLeft) self.description.setMinimumHeight(40) + self.description.setWordWrap(True) layout.addWidget(self.description) self.button_box = QDialogButtonBox(QDialogButtonBox.Close) diff --git a/src/calibre/gui2/dialogs/user_profiles.ui b/src/calibre/gui2/dialogs/user_profiles.ui index 6493846c2b..1e29477e6c 100644 --- a/src/calibre/gui2/dialogs/user_profiles.ui +++ b/src/calibre/gui2/dialogs/user_profiles.ui @@ -227,7 +227,7 @@ p, li { white-space: pre-wrap; } 1 - 365 + 36500 7 diff --git a/src/calibre/gui2/email.py b/src/calibre/gui2/email.py index 4ecf28e519..f4164befe6 100644 --- a/src/calibre/gui2/email.py +++ b/src/calibre/gui2/email.py @@ -273,7 +273,10 @@ class EmailMixin(object): # {{{ 5000) if remove: try: + next_id = self.library_view.next_id self.library_view.model().delete_books_by_id(remove) + self.iactions['Remove Books'].library_ids_deleted2(remove, + next_id=next_id) except: import traceback # Probably the user deleted the files, in any case, failing diff --git a/src/calibre/gui2/layout.py b/src/calibre/gui2/layout.py index df277aa13c..6563059821 100644 --- a/src/calibre/gui2/layout.py +++ b/src/calibre/gui2/layout.py @@ -201,6 +201,7 @@ class SearchBar(QWidget): # {{{ x.setObjectName("search") x.setToolTip(_("

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

Words separated by spaces are ANDed")) + x.setMinimumContentsLength(10) l.addWidget(x) self.search_button = QToolButton() @@ -225,7 +226,7 @@ class SearchBar(QWidget): # {{{ x = parent.saved_search = SavedSearchBox(self) x.setMaximumSize(QSize(150, 16777215)) - x.setMinimumContentsLength(15) + x.setMinimumContentsLength(10) x.setObjectName("saved_search") l.addWidget(x) diff --git a/src/calibre/gui2/library/delegates.py b/src/calibre/gui2/library/delegates.py index f715ae6580..7b3d4de5f2 100644 --- a/src/calibre/gui2/library/delegates.py +++ b/src/calibre/gui2/library/delegates.py @@ -88,13 +88,16 @@ class DateDelegate(QStyledItemDelegate): # {{{ class PubDateDelegate(QStyledItemDelegate): # {{{ + def __init__(self, *args, **kwargs): + QStyledItemDelegate.__init__(self, *args, **kwargs) + self.format = tweaks['gui_pubdate_display_format'] + if self.format is None: + self.format = 'MMM yyyy' + def displayText(self, val, locale): d = val.toDateTime() if d <= UNDEFINED_QDATETIME: return '' - self.format = tweaks['gui_pubdate_display_format'] - if self.format is None: - self.format = 'MMM yyyy' return format_date(qt_to_dt(d, as_utc=False), self.format) def createEditor(self, parent, option, index): diff --git a/src/calibre/gui2/library/models.py b/src/calibre/gui2/library/models.py index b2b2597434..891b775448 100644 --- a/src/calibre/gui2/library/models.py +++ b/src/calibre/gui2/library/models.py @@ -5,7 +5,7 @@ __license__ = 'GPL v3' __copyright__ = '2010, Kovid Goyal ' __docformat__ = 'restructuredtext en' -import functools, re, os, traceback, errno +import functools, re, os, traceback, errno, time from collections import defaultdict from PyQt4.Qt import (QAbstractTableModel, Qt, pyqtSignal, QIcon, QImage, @@ -27,6 +27,7 @@ from calibre import strftime, isbytestring from calibre.constants import filesystem_encoding, DEBUG from calibre.gui2.library import DEFAULT_SORT from calibre.utils.localization import calibre_langcode_to_name +from calibre.library.coloring import color_row_key def human_readable(size, precision=1): """ Convert a size in bytes into megabytes """ @@ -45,6 +46,30 @@ def default_image(): _default_image = QImage(I('default_cover.png')) return _default_image +class ColumnColor(object): + + def __init__(self): + self.mi = None + + def __call__(self, id_, key, fmt, db, formatter, color_cache, colors): + if id_ in color_cache and key in color_cache[id_]: + self.mi = None + return color_cache[id_][key] + try: + if self.mi is None: + self.mi = db.get_metadata(id_, index_is_id=True) + color = formatter.safe_format(fmt, self.mi, '', self.mi) + if color in colors: + color = QColor(color) + if color.isValid(): + color = QVariant(color) + color_cache[id_][key] = color + self.mi = None + return color + except: + pass + + class BooksModel(QAbstractTableModel): # {{{ about_to_be_sorted = pyqtSignal(object, name='aboutToBeSorted') @@ -72,6 +97,7 @@ class BooksModel(QAbstractTableModel): # {{{ def __init__(self, parent=None, buffer=40): QAbstractTableModel.__init__(self, parent) self.db = None + self.column_color = ColumnColor() self.book_on_device = None self.editable_cols = ['title', 'authors', 'rating', 'publisher', 'tags', 'series', 'timestamp', 'pubdate', @@ -84,6 +110,7 @@ class BooksModel(QAbstractTableModel): # {{{ self.headers = {} self.alignment_map = {} self.color_cache = defaultdict(dict) + self.color_row_fmt_cache = None self.buffer_size = buffer self.metadata_backup = None self.bool_yes_icon = QIcon(I('ok.png')) @@ -169,12 +196,14 @@ class BooksModel(QAbstractTableModel): # {{{ def refresh_ids(self, ids, current_row=-1): self.color_cache = defaultdict(dict) + self.color_row_fmt_cache = None rows = self.db.refresh_ids(ids) if rows: self.refresh_rows(rows, current_row=current_row) def refresh_rows(self, rows, current_row=-1): self.color_cache = defaultdict(dict) + self.color_row_fmt_cache = None for row in rows: if row == current_row: self.new_bookdisplay_data.emit( @@ -206,6 +235,7 @@ class BooksModel(QAbstractTableModel): # {{{ def count_changed(self, *args): self.color_cache = defaultdict(dict) + self.color_row_fmt_cache = None self.count_changed_signal.emit(self.db.count()) def row_indices(self, index): @@ -337,6 +367,7 @@ class BooksModel(QAbstractTableModel): # {{{ def reset(self): self.color_cache = defaultdict(dict) + self.color_row_fmt_cache = None QAbstractTableModel.reset(self) def resort(self, reset=True): @@ -727,26 +758,20 @@ class BooksModel(QAbstractTableModel): # {{{ return QVariant(QColor('lightgreen')) elif role == Qt.ForegroundRole: key = self.column_map[col] - mi = None + id_ = self.id(index) + self.column_color.mi = None + + if self.color_row_fmt_cache is None: + self.color_row_fmt_cache = tuple(fmt for key, fmt in + self.db.prefs['column_color_rules'] if key == color_row_key) + for k, fmt in self.db.prefs['column_color_rules']: - if k != key: - continue - id_ = self.id(index) - if id_ in self.color_cache: - if key in self.color_cache[id_]: - return self.color_cache[id_][key] - try: - if mi is None: - mi = self.db.get_metadata(id_, index_is_id=True) - color = self.formatter.safe_format(fmt, mi, '', mi) - if color in self.colors: - color = QColor(color) - if color.isValid(): - color = QVariant(color) - self.color_cache[id_][key] = color - return color - except: - continue + if k == key: + ccol = self.column_color(id_, key, fmt, self.db, + self.formatter, self.color_cache, self.colors) + if ccol is not None: + return ccol + if self.is_custom_column(key) and \ self.custom_columns[key]['datatype'] == 'enumeration': cc = self.custom_columns[self.column_map[col]]['display'] @@ -757,9 +782,18 @@ class BooksModel(QAbstractTableModel): # {{{ try: color = QColor(colors[values.index(txt)]) if color.isValid(): + self.column_color.mi = None return QVariant(color) except: pass + + for fmt in self.color_row_fmt_cache: + ccol = self.column_color(id_, color_row_key, fmt, self.db, + self.formatter, self.color_cache, self.colors) + if ccol is not None: + return ccol + + self.column_color.mi = None return NONE elif role == Qt.DecorationRole: if self.column_to_dc_decorator_map[col] is not None: @@ -1385,7 +1419,11 @@ class DeviceBooksModel(BooksModel): # {{{ return QVariant(human_readable(size)) elif cname == 'timestamp': dt = self.db[self.map[row]].datetime - dt = dt_factory(dt, assume_utc=True, as_utc=False) + try: + dt = dt_factory(dt, assume_utc=True, as_utc=False) + except OverflowError: + dt = dt_factory(time.gmtime(), assume_utc=True, + as_utc=False) return QVariant(strftime(TIME_FMT, dt.timetuple())) elif cname == 'collections': tags = self.db[self.map[row]].device_collections diff --git a/src/calibre/gui2/metadata/basic_widgets.py b/src/calibre/gui2/metadata/basic_widgets.py index 95255d4abb..2d1e1fe7c3 100644 --- a/src/calibre/gui2/metadata/basic_widgets.py +++ b/src/calibre/gui2/metadata/basic_widgets.py @@ -1094,6 +1094,9 @@ class RatingEdit(QSpinBox): # {{{ db.set_rating(id_, 2*self.current_val, notify=False, commit=False) return True + def zero(self): + self.setValue(0) + # }}} class TagsEdit(EditWithComplete): # {{{ diff --git a/src/calibre/gui2/metadata/single.py b/src/calibre/gui2/metadata/single.py index a736f9fb27..654a5a474e 100644 --- a/src/calibre/gui2/metadata/single.py +++ b/src/calibre/gui2/metadata/single.py @@ -181,6 +181,11 @@ class MetadataSingleDialogBase(ResizableDialog): self.basic_metadata_widgets.append(self.comments) self.rating = RatingEdit(self) + self.clear_ratings_button = QToolButton(self) + self.clear_ratings_button.setToolTip(_('Clear rating')) + self.clear_ratings_button.setIcon(QIcon(I('trash.png'))) + self.clear_ratings_button.clicked.connect(self.rating.zero) + self.basic_metadata_widgets.append(self.rating) self.tags = TagsEdit(self) @@ -659,8 +664,9 @@ class MetadataSingleDialog(MetadataSingleDialogBase): # {{{ QSizePolicy.Expanding) l.addItem(self.tabs[0].spc_one, 1, 0, 1, 3) sto(self.cover.buttons[-1], self.rating) - create_row2(1, self.rating) - sto(self.rating, self.tags_editor_button) + create_row2(1, self.rating, self.clear_ratings_button) + sto(self.rating, self.clear_ratings_button) + sto(self.clear_ratings_button, self.tags_editor_button) sto(self.tags_editor_button, self.tags) create_row2(2, self.tags, self.clear_tags_button, front_button=self.tags_editor_button) sto(self.clear_tags_button, self.paste_isbn_button) @@ -780,7 +786,7 @@ class MetadataSingleDialogAlt1(MetadataSingleDialogBase): # {{{ button=self.clear_series_button, icon='trash.png') create_row(5, self.series_index, self.tags) create_row(6, self.tags, self.rating, button=self.clear_tags_button) - create_row(7, self.rating, self.pubdate) + create_row(7, self.rating, self.pubdate, button=self.clear_ratings_button) create_row(8, self.pubdate, self.publisher, button=self.pubdate.clear_button, icon='trash.png') create_row(9, self.publisher, self.languages) @@ -917,7 +923,7 @@ class MetadataSingleDialogAlt2(MetadataSingleDialogBase): # {{{ button=self.clear_series_button, icon='trash.png') create_row(5, self.series_index, self.tags) create_row(6, self.tags, self.rating, button=self.clear_tags_button) - create_row(7, self.rating, self.pubdate) + create_row(7, self.rating, self.pubdate, button=self.clear_ratings_button) create_row(8, self.pubdate, self.publisher, button=self.pubdate.clear_button, icon='trash.png') create_row(9, self.publisher, self.languages) diff --git a/src/calibre/gui2/metadata/single_download.py b/src/calibre/gui2/metadata/single_download.py index cff1527b6f..ee8cd84261 100644 --- a/src/calibre/gui2/metadata/single_download.py +++ b/src/calibre/gui2/metadata/single_download.py @@ -17,10 +17,12 @@ from Queue import Queue, Empty from io import BytesIO from PyQt4.Qt import (QStyledItemDelegate, QTextDocument, QRectF, QIcon, Qt, - QApplication, QDialog, QVBoxLayout, QLabel, QDialogButtonBox, QStyle, - QStackedWidget, QWidget, QTableView, QGridLayout, QFontInfo, QPalette, - QTimer, pyqtSignal, QAbstractTableModel, QVariant, QSize, QListView, - QPixmap, QAbstractListModel, QColor, QRect, QTextBrowser, QModelIndex) + QApplication, QDialog, QVBoxLayout, QLabel, + QDialogButtonBox, QStyle, QStackedWidget, QWidget, + QTableView, QGridLayout, QFontInfo, QPalette, QTimer, + pyqtSignal, QAbstractTableModel, QVariant, QSize, + QListView, QPixmap, QAbstractListModel, QColor, QRect, + QTextBrowser, QStringListModel) from PyQt4.QtWebKit import QWebView from calibre.customize.ui import metadata_plugins @@ -44,6 +46,8 @@ class RichTextDelegate(QStyledItemDelegate): # {{{ def __init__(self, parent=None, max_width=160): QStyledItemDelegate.__init__(self, parent) self.max_width = max_width + self.dummy_model = QStringListModel([' '], self) + self.dummy_index = self.dummy_model.index(0) def to_doc(self, index, option=None): doc = QTextDocument() @@ -66,7 +70,7 @@ class RichTextDelegate(QStyledItemDelegate): # {{{ return ans def paint(self, painter, option, index): - QStyledItemDelegate.paint(self, painter, option, QModelIndex()) + QStyledItemDelegate.paint(self, painter, option, self.dummy_index) painter.save() painter.setClipRect(QRectF(option.rect)) painter.translate(option.rect.topLeft()) diff --git a/src/calibre/gui2/preferences/adding.ui b/src/calibre/gui2/preferences/adding.ui index bd06470383..b98a476864 100644 --- a/src/calibre/gui2/preferences/adding.ui +++ b/src/calibre/gui2/preferences/adding.ui @@ -158,36 +158,6 @@ Author matching is exact. &Automatic Adding - - - - If set, this option will causes calibre to check if a file - being auto-added is already in the calibre library. - If it is, a message will pop up asking you whether - you want to add it anyway. - - - Check for &duplicates when auto-adding files - - - - - - - Specify a folder. Any files you put into this folder will be automatically added to calibre (restart required). - - - true - - - - - - - <b>WARNING:</b> Files in the above folder will be deleted after being added to calibre. - - - @@ -214,6 +184,36 @@ Author matching is exact. + + + + Specify a folder. Any files you put into this folder will be automatically added to calibre (restart required). + + + true + + + + + + + <b>WARNING:</b> Files in the above folder will be deleted after being added to calibre. + + + + + + + If set, this option will causes calibre to check if a file + being auto-added is already in the calibre library. + If it is, a message will pop up asking you whether + you want to add it anyway. + + + Check for &duplicates when auto-adding files + + + @@ -259,6 +259,19 @@ Author matching is exact. + + + + Qt::Vertical + + + + 20 + 40 + + + + diff --git a/src/calibre/gui2/preferences/coloring.py b/src/calibre/gui2/preferences/coloring.py index 831e658b59..4b867f347d 100644 --- a/src/calibre/gui2/preferences/coloring.py +++ b/src/calibre/gui2/preferences/coloring.py @@ -19,10 +19,12 @@ from calibre.gui2 import error_dialog from calibre.gui2.dialogs.template_dialog import TemplateDialog from calibre.gui2.metadata.single_download import RichTextDelegate from calibre.library.coloring import (Rule, conditionable_columns, - displayable_columns, rule_from_template) + displayable_columns, rule_from_template, color_row_key) from calibre.utils.localization import lang_map from calibre.utils.icu import lower +all_columns_string = _('All Columns') + class ConditionEditor(QWidget): # {{{ ACTION_MAP = { @@ -104,8 +106,8 @@ class ConditionEditor(QWidget): # {{{ self.column_box.addItem('', '') for key in sorted( conditionable_columns(fm), - key=sort_key): - self.column_box.addItem(key, key) + key=lambda(key): sort_key(fm[key]['name'])): + self.column_box.addItem(fm[key]['name'], key) self.column_box.setCurrentIndex(0) self.column_box.currentIndexChanged.connect(self.init_action_box) @@ -312,12 +314,11 @@ class RuleEditor(QDialog): # {{{ b.setSizeAdjustPolicy(b.AdjustToMinimumContentsLengthWithIcon) b.setMinimumContentsLength(15) - for key in sorted( - displayable_columns(fm), - key=sort_key): - name = fm[key]['name'] + for key in sorted(displayable_columns(fm), + key=lambda(k): sort_key(fm[k]['name']) if k != color_row_key else 0): + name = all_columns_string if key == color_row_key else fm[key]['name'] if name: - self.column_box.addItem(key, key) + self.column_box.addItem(name, key) self.column_box.setCurrentIndex(0) self.color_box.addItems(QColor.colorNames()) @@ -427,8 +428,11 @@ class RulesModel(QAbstractListModel): # {{{ col, rule = self.rules[row] except: return None - if role == Qt.DisplayRole: + if col == color_row_key: + col = all_columns_string + else: + col = self.fm[col]['name'] return self.rule_to_html(col, rule) if role == Qt.UserRole: return (col, rule) @@ -485,6 +489,7 @@ class RulesModel(QAbstractListModel): # {{{ def condition_to_html(self, condition): c, a, v = condition + c = self.fm[c]['name'] action_name = a for actions in ConditionEditor.ACTION_MAP.itervalues(): for trans, ac in actions: @@ -568,9 +573,9 @@ class EditRules(QWidget): # {{{ self.changed.emit() def add_rule(self): - d = RuleEditor(self.model.fm) - d.add_blank_condition() - self._add_rule(d) + d = RuleEditor(self.model.fm) + d.add_blank_condition() + self._add_rule(d) def add_advanced(self): td = TemplateDialog(self, '', mi=self.mi, fm=self.fm, color_field='') diff --git a/src/calibre/gui2/preferences/ignored_devices.py b/src/calibre/gui2/preferences/ignored_devices.py index 99fa350f73..e0c0ae1dc1 100644 --- a/src/calibre/gui2/preferences/ignored_devices.py +++ b/src/calibre/gui2/preferences/ignored_devices.py @@ -7,8 +7,10 @@ __license__ = 'GPL v3' __copyright__ = '2012, Kovid Goyal ' __docformat__ = 'restructuredtext en' -from PyQt4.Qt import (QLabel, QVBoxLayout, QListWidget, QListWidgetItem, Qt) +from PyQt4.Qt import (QLabel, QVBoxLayout, QListWidget, QListWidgetItem, Qt, + QIcon) +from calibre.customize.ui import enable_plugin from calibre.gui2.preferences import ConfigWidgetBase, test_widget class ConfigWidget(ConfigWidgetBase): @@ -31,6 +33,18 @@ class ConfigWidget(ConfigWidgetBase): f.itemChanged.connect(self.changed_signal) f.itemDoubleClicked.connect(self.toggle_item) + self.la2 = la = QLabel(_( + 'The list of device plugins you have disabled. Uncheck an entry ' + 'to enable the plugin. calibre cannot detect devices that are ' + 'managed by disabled plugins.')) + la.setWordWrap(True) + l.addWidget(la) + + self.device_plugins = f = QListWidget(f) + l.addWidget(f) + f.itemChanged.connect(self.changed_signal) + f.itemDoubleClicked.connect(self.toggle_item) + def toggle_item(self, item): item.setCheckState(Qt.Checked if item.checkState() == Qt.Unchecked else Qt.Unchecked) @@ -46,6 +60,17 @@ class ConfigWidget(ConfigWidgetBase): item.setCheckState(Qt.Checked) self.devices.blockSignals(False) + self.device_plugins.blockSignals(True) + for dev in self.gui.device_manager.disabled_device_plugins: + n = dev.get_gui_name() + item = QListWidgetItem(n, self.device_plugins) + item.setData(Qt.UserRole, dev) + item.setFlags(Qt.ItemIsEnabled|Qt.ItemIsUserCheckable|Qt.ItemIsSelectable) + item.setCheckState(Qt.Checked) + item.setIcon(QIcon(I('plugins.png'))) + self.device_plugins.sortItems() + self.device_plugins.blockSignals(False) + def restore_defaults(self): if self.devices.count() > 0: self.devices.clear() @@ -63,6 +88,12 @@ class ConfigWidget(ConfigWidgetBase): for dev, bl in devs.iteritems(): dev.set_user_blacklisted_devices(bl) + for i in xrange(self.device_plugins.count()): + e = self.device_plugins.item(i) + dev = e.data(Qt.UserRole).toPyObject() + if e.checkState() == Qt.Unchecked: + enable_plugin(dev) + return True # Restart required if __name__ == '__main__': diff --git a/src/calibre/gui2/preferences/server.ui b/src/calibre/gui2/preferences/server.ui index a229da594f..163221594f 100644 --- a/src/calibre/gui2/preferences/server.ui +++ b/src/calibre/gui2/preferences/server.ui @@ -273,7 +273,7 @@ <p>Remember to leave calibre running as the server only runs as long as calibre is running. -<p>To connect to the calibre server from your device you should use a URL of the form <b>http://myhostname:8080</b> as a new catalog in the Stanza reader on your iPhone. Here myhostname should be either the fully qualified hostname or the IP address of the computer calibre is running on. +<p>To connect to the calibre server from your device you should use a URL of the form <b>http://myhostname:8080</b>. Here myhostname should be either the fully qualified hostname or the IP address of the computer calibre is running on. If you want to access the server from anywhere in the world, you will have to setup port forwarding for it on your router. true diff --git a/src/calibre/gui2/store/__init__.py b/src/calibre/gui2/store/__init__.py index ae42d82032..3af0a14cda 100644 --- a/src/calibre/gui2/store/__init__.py +++ b/src/calibre/gui2/store/__init__.py @@ -49,13 +49,16 @@ class StorePlugin(object): # {{{ See declined.txt for a list of stores that do not want to be included. ''' - def __init__(self, gui, name): - from calibre.gui2 import JSONConfig + minimum_calibre_version = (0, 9, 14) + def __init__(self, gui, name, config=None, base_plugin=None): self.gui = gui self.name = name - self.base_plugin = None - self.config = JSONConfig('store/stores/' + ascii_filename(self.name)) + self.base_plugin = base_plugin + if config is None: + from calibre.gui2 import JSONConfig + config = JSONConfig('store/stores/' + ascii_filename(self.name)) + self.config = config def open(self, gui, parent=None, detail_item=None, external=False): ''' diff --git a/src/calibre/gui2/store/loader.py b/src/calibre/gui2/store/loader.py new file mode 100644 index 0000000000..45c258a915 --- /dev/null +++ b/src/calibre/gui2/store/loader.py @@ -0,0 +1,197 @@ +#!/usr/bin/env python +# vim:fileencoding=UTF-8:ts=4:sw=4:sta:et:sts=4:fdm=marker:ai +from __future__ import (unicode_literals, division, absolute_import, + print_function) + +__license__ = 'GPL v3' +__copyright__ = '2013, Kovid Goyal ' +__docformat__ = 'restructuredtext en' + +import sys, time, io, re +from zlib import decompressobj +from collections import OrderedDict +from threading import Thread +from urllib import urlencode + +from calibre import prints, browser +from calibre.constants import numeric_version, DEBUG +from calibre.gui2.store import StorePlugin +from calibre.utils.config import JSONConfig + +class VersionMismatch(ValueError): + def __init__(self, ver): + ValueError.__init__(self, 'calibre too old') + self.ver = ver + +def download_updates(ver_map={}, server='http://status.calibre-ebook.com'): + data = {k:type(u'')(v) for k, v in ver_map.iteritems()} + data['ver'] = '1' + url = '%s/stores?%s'%(server, urlencode(data)) + br = browser() + # We use a timeout here to ensure the non-daemonic update thread does not + # cause calibre to hang indefinitely during shutdown + raw = br.open(url, timeout=4.0).read() + + while raw: + name, raw = raw.partition(b'\0')[0::2] + name = name.decode('utf-8') + d = decompressobj() + src = d.decompress(raw) + src = src.decode('utf-8') + # Python complains if there is a coding declaration in a unicode string + src = re.sub(r'^#.*coding\s*[:=]\s*([-\w.]+)', '#', src, flags=re.MULTILINE) + # Translate newlines to \n + src = io.StringIO(src, newline=None).getvalue() + yield name, src + raw = d.unused_data + +class Stores(OrderedDict): + + CHECK_INTERVAL = 24 * 60 * 60 + + def builtins_loaded(self): + self.last_check_time = 0 + self.version_map = {} + self.cached_version_map = {} + self.name_rmap = {} + for key, val in self.iteritems(): + prefix, name = val.__module__.rpartition('.')[0::2] + if prefix == 'calibre.gui2.store.stores' and name.endswith('_plugin'): + module = sys.modules[val.__module__] + sv = getattr(module, 'store_version', None) + if sv is not None: + name = name.rpartition('_')[0] + self.version_map[name] = sv + self.name_rmap[name] = key + self.cache_file = JSONConfig('store/plugin_cache') + self.load_cache() + + def load_cache(self): + # Load plugins from on disk cache + remove = set() + pat = re.compile(r'^store_version\s*=\s*(\d+)', re.M) + for name, src in self.cache_file.iteritems(): + try: + key = self.name_rmap[name] + except KeyError: + # Plugin has been disabled + m = pat.search(src[:512]) + if m is not None: + try: + self.cached_version_map[name] = int(m.group(1)) + except (TypeError, ValueError): + pass + continue + + try: + obj, ver = self.load_object(src, key) + except VersionMismatch as e: + self.cached_version_map[name] = e.ver + continue + except: + import traceback + prints('Failed to load cached store:', name) + traceback.print_exc() + else: + if not self.replace_plugin(ver, name, obj, 'cached'): + # Builtin plugin is newer than cached + remove.add(name) + + if remove: + with self.cache_file: + for name in remove: + del self.cache_file[name] + + def check_for_updates(self): + if hasattr(self, 'update_thread') and self.update_thread.is_alive(): + return + if time.time() - self.last_check_time < self.CHECK_INTERVAL: + return + self.last_check_time = time.time() + try: + self.update_thread.start() + except (RuntimeError, AttributeError): + self.update_thread = Thread(target=self.do_update) + self.update_thread.start() + + def join(self, timeout=None): + hasattr(self, 'update_thread') and self.update_thread.join(timeout) + + def download_updates(self): + ver_map = {name:max(ver, self.cached_version_map.get(name, -1)) + for name, ver in self.version_map.iteritems()} + try: + updates = download_updates(ver_map) + except: + import traceback + traceback.print_exc() + else: + for name, code in updates: + yield name, code + + def do_update(self): + replacements = {} + + for name, src in self.download_updates(): + try: + key = self.name_rmap[name] + except KeyError: + # Plugin has been disabled + replacements[name] = src + continue + try: + obj, ver = self.load_object(src, key) + except VersionMismatch as e: + self.cached_version_map[name] = e.ver + replacements[name] = src + continue + except: + import traceback + prints('Failed to load downloaded store:', name) + traceback.print_exc() + else: + if self.replace_plugin(ver, name, obj, 'downloaded'): + replacements[name] = src + + if replacements: + with self.cache_file: + for name, src in replacements.iteritems(): + self.cache_file[name] = src + + def replace_plugin(self, ver, name, obj, source): + if ver > self.version_map[name]: + if DEBUG: + prints('Loaded', source, 'store plugin for:', + self.name_rmap[name], 'at version:', ver) + self[self.name_rmap[name]] = obj + self.version_map[name] = ver + return True + return False + + def load_object(self, src, key): + namespace = {} + builtin = self[key] + exec src in namespace + ver = namespace['store_version'] + cls = None + for x in namespace.itervalues(): + if (isinstance(x, type) and issubclass(x, StorePlugin) and x is not + StorePlugin): + cls = x + break + if cls is None: + raise ValueError('No store plugin found') + if cls.minimum_calibre_version > numeric_version: + raise VersionMismatch(ver) + return cls(builtin.gui, builtin.name, config=builtin.config, + base_plugin=builtin.base_plugin), ver + +if __name__ == '__main__': + st = time.time() + for name, code in download_updates(): + print(name) + print(code) + print('\n', '_'*80, '\n', sep='') + print ('Time to download all plugins: %.2f'%( time.time() - st)) + + diff --git a/src/calibre/gui2/store/search/search.py b/src/calibre/gui2/store/search/search.py index b4ae0bc943..20c6c09a03 100644 --- a/src/calibre/gui2/store/search/search.py +++ b/src/calibre/gui2/store/search/search.py @@ -194,6 +194,7 @@ class SearchDialog(QDialog, Ui_Dialog): query = self.clean_query(query) shuffle(store_names) # Add plugins that the user has checked to the search pool's work queue. + self.gui.istores.join(4.0) # Wait for updated plugins to load for n in store_names: if self.store_checks[n].isChecked(): self.search_pool.add_task(query, n, self.gui.istores[n], self.max_results, self.timeout) diff --git a/src/calibre/gui2/store/stores/amazon_de_plugin.py b/src/calibre/gui2/store/stores/amazon_de_plugin.py index 3ccbef0b6e..71ed8b0491 100644 --- a/src/calibre/gui2/store/stores/amazon_de_plugin.py +++ b/src/calibre/gui2/store/stores/amazon_de_plugin.py @@ -1,14 +1,106 @@ # -*- coding: utf-8 -*- from __future__ import (unicode_literals, division, absolute_import, print_function) +store_version = 1 # Needed for dynamic plugin loading __license__ = 'GPL 3' __copyright__ = '2011, John Schember ' __docformat__ = 'restructuredtext en' -from calibre.gui2.store.stores.amazon_uk_plugin import AmazonUKKindleStore +from contextlib import closing +from lxml import html -class AmazonDEKindleStore(AmazonUKKindleStore): +from PyQt4.Qt import QUrl + +from calibre.gui2.store import StorePlugin +from calibre import browser +from calibre.gui2 import open_url +from calibre.gui2.store.search_result import SearchResult + + + +# This class is copy/pasted from amason_uk_plugin. Do not modify it in any +# other amazon EU plugin. Be sure to paste it into all other amazon EU plugins +# when modified. + +class AmazonEUBase(StorePlugin): + ''' + For comments on the implementation, please see amazon_plugin.py + ''' + + def open(self, parent=None, detail_item=None, external=False): + + store_link = self.store_link % self.aff_id + if detail_item: + self.aff_id['asin'] = detail_item + store_link = self.store_link_details % self.aff_id + open_url(QUrl(store_link)) + + def search(self, query, max_results=10, timeout=60): + url = self.search_url + query.encode('ascii', 'backslashreplace').replace('%', '%25').replace('\\x', '%').replace(' ', '+') + br = browser() + + counter = max_results + with closing(br.open(url, timeout=timeout)) as f: + doc = html.fromstring(f.read())#.decode('latin-1', 'replace')) + + data_xpath = '//div[contains(@class, "prod")]' + format_xpath = './/ul[contains(@class, "rsltL")]//span[contains(@class, "lrg") and not(contains(@class, "bld"))]/text()' + asin_xpath = '@name' + cover_xpath = './/img[@class="productImage"]/@src' + title_xpath = './/h3[@class="newaps"]/a//text()' + author_xpath = './/h3[@class="newaps"]//span[contains(@class, "reg")]/text()' + price_xpath = './/ul[contains(@class, "rsltL")]//span[contains(@class, "lrg") and contains(@class, "bld")]/text()' + + for data in doc.xpath(data_xpath): + if counter <= 0: + break + + # Even though we are searching digital-text only Amazon will still + # put in results for non Kindle books (author pages). Se we need + # to explicitly check if the item is a Kindle book and ignore it + # if it isn't. + format_ = ''.join(data.xpath(format_xpath)) + if 'kindle' not in format_.lower(): + continue + + # We must have an asin otherwise we can't easily reference the + # book later. + asin = data.xpath(asin_xpath) + if asin: + asin = asin[0] + else: + continue + + cover_url = ''.join(data.xpath(cover_xpath)) + + title = ''.join(data.xpath(title_xpath)) + author = ''.join(data.xpath(author_xpath)) + try: + if self.author_article: + author = author.split(self.author_article, 1)[1].split(" (")[0] + except: + pass + + price = ''.join(data.xpath(price_xpath)) + + counter -= 1 + + s = SearchResult() + s.cover_url = cover_url.strip() + s.title = title.strip() + s.author = author.strip() + s.price = price.strip() + s.detail_item = asin.strip() + s.drm = SearchResult.DRM_UNKNOWN + s.formats = 'Kindle' + + yield s + + def get_details(self, search_result, timeout): + pass + +class AmazonDEKindleStore(AmazonEUBase): ''' For comments on the implementation, please see amazon_plugin.py ''' diff --git a/src/calibre/gui2/store/stores/amazon_es_plugin.py b/src/calibre/gui2/store/stores/amazon_es_plugin.py index 131f77c7e9..d613ced2a5 100644 --- a/src/calibre/gui2/store/stores/amazon_es_plugin.py +++ b/src/calibre/gui2/store/stores/amazon_es_plugin.py @@ -1,14 +1,105 @@ # -*- coding: utf-8 -*- from __future__ import (unicode_literals, division, absolute_import, print_function) +store_version = 1 # Needed for dynamic plugin loading __license__ = 'GPL 3' __copyright__ = '2011, John Schember ' __docformat__ = 'restructuredtext en' -from calibre.gui2.store.stores.amazon_uk_plugin import AmazonUKKindleStore +from contextlib import closing +from lxml import html -class AmazonESKindleStore(AmazonUKKindleStore): +from PyQt4.Qt import QUrl + +from calibre.gui2.store import StorePlugin +from calibre import browser +from calibre.gui2 import open_url +from calibre.gui2.store.search_result import SearchResult + + +# This class is copy/pasted from amason_uk_plugin. Do not modify it in any +# other amazon EU plugin. Be sure to paste it into all other amazon EU plugins +# when modified. + +class AmazonEUBase(StorePlugin): + ''' + For comments on the implementation, please see amazon_plugin.py + ''' + + def open(self, parent=None, detail_item=None, external=False): + + store_link = self.store_link % self.aff_id + if detail_item: + self.aff_id['asin'] = detail_item + store_link = self.store_link_details % self.aff_id + open_url(QUrl(store_link)) + + def search(self, query, max_results=10, timeout=60): + url = self.search_url + query.encode('ascii', 'backslashreplace').replace('%', '%25').replace('\\x', '%').replace(' ', '+') + br = browser() + + counter = max_results + with closing(br.open(url, timeout=timeout)) as f: + doc = html.fromstring(f.read())#.decode('latin-1', 'replace')) + + data_xpath = '//div[contains(@class, "prod")]' + format_xpath = './/ul[contains(@class, "rsltL")]//span[contains(@class, "lrg") and not(contains(@class, "bld"))]/text()' + asin_xpath = '@name' + cover_xpath = './/img[@class="productImage"]/@src' + title_xpath = './/h3[@class="newaps"]/a//text()' + author_xpath = './/h3[@class="newaps"]//span[contains(@class, "reg")]/text()' + price_xpath = './/ul[contains(@class, "rsltL")]//span[contains(@class, "lrg") and contains(@class, "bld")]/text()' + + for data in doc.xpath(data_xpath): + if counter <= 0: + break + + # Even though we are searching digital-text only Amazon will still + # put in results for non Kindle books (author pages). Se we need + # to explicitly check if the item is a Kindle book and ignore it + # if it isn't. + format_ = ''.join(data.xpath(format_xpath)) + if 'kindle' not in format_.lower(): + continue + + # We must have an asin otherwise we can't easily reference the + # book later. + asin = data.xpath(asin_xpath) + if asin: + asin = asin[0] + else: + continue + + cover_url = ''.join(data.xpath(cover_xpath)) + + title = ''.join(data.xpath(title_xpath)) + author = ''.join(data.xpath(author_xpath)) + try: + if self.author_article: + author = author.split(self.author_article, 1)[1].split(" (")[0] + except: + pass + + price = ''.join(data.xpath(price_xpath)) + + counter -= 1 + + s = SearchResult() + s.cover_url = cover_url.strip() + s.title = title.strip() + s.author = author.strip() + s.price = price.strip() + s.detail_item = asin.strip() + s.drm = SearchResult.DRM_UNKNOWN + s.formats = 'Kindle' + + yield s + + def get_details(self, search_result, timeout): + pass + +class AmazonESKindleStore(AmazonEUBase): ''' For comments on the implementation, please see amazon_plugin.py ''' @@ -21,4 +112,4 @@ class AmazonESKindleStore(AmazonUKKindleStore): '&linkCode=ur2&camp=3626&creative=24790') search_url = 'http://www.amazon.es/s/?url=search-alias%3Ddigital-text&field-keywords=' - author_article = 'de ' \ No newline at end of file + author_article = 'de ' diff --git a/src/calibre/gui2/store/stores/amazon_fr_plugin.py b/src/calibre/gui2/store/stores/amazon_fr_plugin.py index cd59be0313..22e5d8ec8e 100644 --- a/src/calibre/gui2/store/stores/amazon_fr_plugin.py +++ b/src/calibre/gui2/store/stores/amazon_fr_plugin.py @@ -1,15 +1,107 @@ # -*- coding: utf-8 -*- from __future__ import (unicode_literals, division, absolute_import, print_function) +store_version = 1 # Needed for dynamic plugin loading __license__ = 'GPL 3' __copyright__ = '2011, John Schember ' __docformat__ = 'restructuredtext en' -from calibre.gui2.store.stores.amazon_uk_plugin import AmazonUKKindleStore +from contextlib import closing +from lxml import html -class AmazonFRKindleStore(AmazonUKKindleStore): +from PyQt4.Qt import QUrl + +from calibre.gui2.store import StorePlugin +from calibre import browser +from calibre.gui2 import open_url +from calibre.gui2.store.search_result import SearchResult + + + +# This class is copy/pasted from amason_uk_plugin. Do not modify it in any +# other amazon EU plugin. Be sure to paste it into all other amazon EU plugins +# when modified. + +class AmazonEUBase(StorePlugin): + ''' + For comments on the implementation, please see amazon_plugin.py + ''' + + def open(self, parent=None, detail_item=None, external=False): + + store_link = self.store_link % self.aff_id + if detail_item: + self.aff_id['asin'] = detail_item + store_link = self.store_link_details % self.aff_id + open_url(QUrl(store_link)) + + def search(self, query, max_results=10, timeout=60): + url = self.search_url + query.encode('ascii', 'backslashreplace').replace('%', '%25').replace('\\x', '%').replace(' ', '+') + br = browser() + + counter = max_results + with closing(br.open(url, timeout=timeout)) as f: + doc = html.fromstring(f.read())#.decode('latin-1', 'replace')) + + data_xpath = '//div[contains(@class, "prod")]' + format_xpath = './/ul[contains(@class, "rsltL")]//span[contains(@class, "lrg") and not(contains(@class, "bld"))]/text()' + asin_xpath = '@name' + cover_xpath = './/img[@class="productImage"]/@src' + title_xpath = './/h3[@class="newaps"]/a//text()' + author_xpath = './/h3[@class="newaps"]//span[contains(@class, "reg")]/text()' + price_xpath = './/ul[contains(@class, "rsltL")]//span[contains(@class, "lrg") and contains(@class, "bld")]/text()' + + for data in doc.xpath(data_xpath): + if counter <= 0: + break + + # Even though we are searching digital-text only Amazon will still + # put in results for non Kindle books (author pages). Se we need + # to explicitly check if the item is a Kindle book and ignore it + # if it isn't. + format_ = ''.join(data.xpath(format_xpath)) + if 'kindle' not in format_.lower(): + continue + + # We must have an asin otherwise we can't easily reference the + # book later. + asin = data.xpath(asin_xpath) + if asin: + asin = asin[0] + else: + continue + + cover_url = ''.join(data.xpath(cover_xpath)) + + title = ''.join(data.xpath(title_xpath)) + author = ''.join(data.xpath(author_xpath)) + try: + if self.author_article: + author = author.split(self.author_article, 1)[1].split(" (")[0] + except: + pass + + price = ''.join(data.xpath(price_xpath)) + + counter -= 1 + + s = SearchResult() + s.cover_url = cover_url.strip() + s.title = title.strip() + s.author = author.strip() + s.price = price.strip() + s.detail_item = asin.strip() + s.drm = SearchResult.DRM_UNKNOWN + s.formats = 'Kindle' + + yield s + + def get_details(self, search_result, timeout): + pass + +class AmazonFRKindleStore(AmazonEUBase): ''' For comments on the implementation, please see amazon_plugin.py ''' diff --git a/src/calibre/gui2/store/stores/amazon_it_plugin.py b/src/calibre/gui2/store/stores/amazon_it_plugin.py index ad028bf963..14c571e8e1 100644 --- a/src/calibre/gui2/store/stores/amazon_it_plugin.py +++ b/src/calibre/gui2/store/stores/amazon_it_plugin.py @@ -1,14 +1,106 @@ # -*- coding: utf-8 -*- from __future__ import (unicode_literals, division, absolute_import, print_function) +store_version = 1 # Needed for dynamic plugin loading __license__ = 'GPL 3' __copyright__ = '2011, John Schember ' __docformat__ = 'restructuredtext en' -from calibre.gui2.store.stores.amazon_uk_plugin import AmazonUKKindleStore +from contextlib import closing +from lxml import html -class AmazonITKindleStore(AmazonUKKindleStore): +from PyQt4.Qt import QUrl + +from calibre.gui2.store import StorePlugin +from calibre import browser +from calibre.gui2 import open_url +from calibre.gui2.store.search_result import SearchResult + + +# This class is copy/pasted from amason_uk_plugin. Do not modify it in any +# other amazon EU plugin. Be sure to paste it into all other amazon EU plugins +# when modified. + +class AmazonEUBase(StorePlugin): + ''' + For comments on the implementation, please see amazon_plugin.py + ''' + + def open(self, parent=None, detail_item=None, external=False): + + store_link = self.store_link % self.aff_id + if detail_item: + self.aff_id['asin'] = detail_item + store_link = self.store_link_details % self.aff_id + open_url(QUrl(store_link)) + + def search(self, query, max_results=10, timeout=60): + url = self.search_url + query.encode('ascii', 'backslashreplace').replace('%', '%25').replace('\\x', '%').replace(' ', '+') + br = browser() + + counter = max_results + with closing(br.open(url, timeout=timeout)) as f: + doc = html.fromstring(f.read())#.decode('latin-1', 'replace')) + + data_xpath = '//div[contains(@class, "prod")]' + format_xpath = './/ul[contains(@class, "rsltL")]//span[contains(@class, "lrg") and not(contains(@class, "bld"))]/text()' + asin_xpath = '@name' + cover_xpath = './/img[@class="productImage"]/@src' + title_xpath = './/h3[@class="newaps"]/a//text()' + author_xpath = './/h3[@class="newaps"]//span[contains(@class, "reg")]/text()' + price_xpath = './/ul[contains(@class, "rsltL")]//span[contains(@class, "lrg") and contains(@class, "bld")]/text()' + + for data in doc.xpath(data_xpath): + if counter <= 0: + break + + # Even though we are searching digital-text only Amazon will still + # put in results for non Kindle books (author pages). Se we need + # to explicitly check if the item is a Kindle book and ignore it + # if it isn't. + format_ = ''.join(data.xpath(format_xpath)) + if 'kindle' not in format_.lower(): + continue + + # We must have an asin otherwise we can't easily reference the + # book later. + asin = data.xpath(asin_xpath) + if asin: + asin = asin[0] + else: + continue + + cover_url = ''.join(data.xpath(cover_xpath)) + + title = ''.join(data.xpath(title_xpath)) + author = ''.join(data.xpath(author_xpath)) + try: + if self.author_article: + author = author.split(self.author_article, 1)[1].split(" (")[0] + except: + pass + + price = ''.join(data.xpath(price_xpath)) + + counter -= 1 + + s = SearchResult() + s.cover_url = cover_url.strip() + s.title = title.strip() + s.author = author.strip() + s.price = price.strip() + s.detail_item = asin.strip() + s.drm = SearchResult.DRM_UNKNOWN + s.formats = 'Kindle' + + yield s + + def get_details(self, search_result, timeout): + pass + + +class AmazonITKindleStore(AmazonEUBase): ''' For comments on the implementation, please see amazon_plugin.py ''' @@ -21,4 +113,4 @@ class AmazonITKindleStore(AmazonUKKindleStore): 'linkCode=ur2&camp=3370&creative=23322') search_url = 'http://www.amazon.it/s/?url=search-alias%3Ddigital-text&field-keywords=' - author_article = 'di ' \ No newline at end of file + author_article = 'di ' diff --git a/src/calibre/gui2/store/stores/amazon_plugin.py b/src/calibre/gui2/store/stores/amazon_plugin.py index 4bd1a42c9d..cf29e19fa4 100644 --- a/src/calibre/gui2/store/stores/amazon_plugin.py +++ b/src/calibre/gui2/store/stores/amazon_plugin.py @@ -1,13 +1,12 @@ # -*- coding: utf-8 -*- from __future__ import (unicode_literals, division, absolute_import, print_function) +store_version = 1 # Needed for dynamic plugin loading __license__ = 'GPL 3' __copyright__ = '2011, John Schember ' __docformat__ = 'restructuredtext en' -import random -import re from contextlib import closing from lxml import html @@ -130,16 +129,16 @@ class AmazonKindleStore(StorePlugin): data_xpath = '//div[contains(@class, "prod")]' format_xpath = './/ul[contains(@class, "rsltL")]//span[contains(@class, "lrg") and not(contains(@class, "bld"))]/text()' - asin_xpath = './/div[@class="image"]/a[1]' + asin_xpath = '@name' cover_xpath = './/img[@class="productImage"]/@src' title_xpath = './/h3[@class="newaps"]/a//text()' author_xpath = './/h3[@class="newaps"]//span[contains(@class, "reg")]/text()' price_xpath = './/ul[contains(@class, "rsltL")]//span[contains(@class, "lrg") and contains(@class, "bld")]/text()' - + for data in doc.xpath(data_xpath): if counter <= 0: break - + # Even though we are searching digital-text only Amazon will still # put in results for non Kindle books (author pages). Se we need # to explicitly check if the item is a Kindle book and ignore it @@ -147,21 +146,15 @@ class AmazonKindleStore(StorePlugin): format = ''.join(data.xpath(format_xpath)) if 'kindle' not in format.lower(): continue - + # We must have an asin otherwise we can't easily reference the # book later. - asin_href = None - asin_a = data.xpath(asin_xpath) - if asin_a: - asin_href = asin_a[0].get('href', '') - m = re.search(r'/dp/(?P.+?)(/|$)', asin_href) - if m: - asin = m.group('asin') - else: - continue + asin = data.xpath(asin_xpath) + if asin: + asin = asin[0] else: continue - + cover_url = ''.join(data.xpath(cover_xpath)) title = ''.join(data.xpath(title_xpath)) @@ -172,9 +165,9 @@ class AmazonKindleStore(StorePlugin): pass price = ''.join(data.xpath(price_xpath)) - + counter -= 1 - + s = SearchResult() s.cover_url = cover_url.strip() s.title = title.strip() diff --git a/src/calibre/gui2/store/stores/amazon_uk_plugin.py b/src/calibre/gui2/store/stores/amazon_uk_plugin.py index 0f9caf8f3e..0abc19f92e 100644 --- a/src/calibre/gui2/store/stores/amazon_uk_plugin.py +++ b/src/calibre/gui2/store/stores/amazon_uk_plugin.py @@ -1,13 +1,12 @@ # -*- coding: utf-8 -*- from __future__ import (unicode_literals, division, absolute_import, print_function) +store_version = 1 # Needed for dynamic plugin loading __license__ = 'GPL 3' __copyright__ = '2011, John Schember ' __docformat__ = 'restructuredtext en' -import re - from contextlib import closing from lxml import html @@ -18,19 +17,12 @@ from calibre.gui2 import open_url from calibre.gui2.store import StorePlugin from calibre.gui2.store.search_result import SearchResult -class AmazonUKKindleStore(StorePlugin): - aff_id = {'tag': 'calcharles-21'} - store_link = ('http://www.amazon.co.uk/gp/redirect.html?ie=UTF8&' - 'location=http://www.amazon.co.uk/Kindle-eBooks/b?' - 'ie=UTF8&node=341689031&ref_=sa_menu_kbo2&tag=%(tag)s&' - 'linkCode=ur2&camp=1634&creative=19450') - store_link_details = ('http://www.amazon.co.uk/gp/redirect.html?ie=UTF8&' - 'location=http://www.amazon.co.uk/dp/%(asin)s&tag=%(tag)s&' - 'linkCode=ur2&camp=1634&creative=6738') - search_url = 'http://www.amazon.co.uk/s/?url=search-alias%3Ddigital-text&field-keywords=' - author_article = 'by ' +# This class is copy/pasted from amason_uk_plugin. Do not modify it in any +# other amazon EU plugin. Be sure to paste it into all other amazon EU plugins +# when modified. +class AmazonEUBase(StorePlugin): ''' For comments on the implementation, please see amazon_plugin.py ''' @@ -53,7 +45,7 @@ class AmazonUKKindleStore(StorePlugin): data_xpath = '//div[contains(@class, "prod")]' format_xpath = './/ul[contains(@class, "rsltL")]//span[contains(@class, "lrg") and not(contains(@class, "bld"))]/text()' - asin_xpath = './/div[@class="image"]/a[1]' + asin_xpath = '@name' cover_xpath = './/img[@class="productImage"]/@src' title_xpath = './/h3[@class="newaps"]/a//text()' author_xpath = './/h3[@class="newaps"]//span[contains(@class, "reg")]/text()' @@ -73,15 +65,9 @@ class AmazonUKKindleStore(StorePlugin): # We must have an asin otherwise we can't easily reference the # book later. - asin_href = None - asin_a = data.xpath(asin_xpath) - if asin_a: - asin_href = asin_a[0].get('href', '') - m = re.search(r'/dp/(?P.+?)(/|$)', asin_href) - if m: - asin = m.group('asin') - else: - continue + asin = data.xpath(asin_xpath) + if asin: + asin = asin[0] else: continue @@ -112,3 +98,17 @@ class AmazonUKKindleStore(StorePlugin): def get_details(self, search_result, timeout): pass + +class AmazonUKKindleStore(AmazonEUBase): + aff_id = {'tag': 'calcharles-21'} + store_link = ('http://www.amazon.co.uk/gp/redirect.html?ie=UTF8&' + 'location=http://www.amazon.co.uk/Kindle-eBooks/b?' + 'ie=UTF8&node=341689031&ref_=sa_menu_kbo2&tag=%(tag)s&' + 'linkCode=ur2&camp=1634&creative=19450') + store_link_details = ('http://www.amazon.co.uk/gp/redirect.html?ie=UTF8&' + 'location=http://www.amazon.co.uk/dp/%(asin)s&tag=%(tag)s&' + 'linkCode=ur2&camp=1634&creative=6738') + search_url = 'http://www.amazon.co.uk/s/?url=search-alias%3Ddigital-text&field-keywords=' + + author_article = 'by ' + diff --git a/src/calibre/gui2/store/stores/archive_org_plugin.py b/src/calibre/gui2/store/stores/archive_org_plugin.py index 7439056baa..ed83b1c433 100644 --- a/src/calibre/gui2/store/stores/archive_org_plugin.py +++ b/src/calibre/gui2/store/stores/archive_org_plugin.py @@ -1,6 +1,7 @@ # -*- coding: utf-8 -*- from __future__ import (unicode_literals, division, absolute_import, print_function) +store_version = 1 # Needed for dynamic plugin loading __license__ = 'GPL 3' __copyright__ = '2011, John Schember ' diff --git a/src/calibre/gui2/store/stores/baen_webscription_plugin.py b/src/calibre/gui2/store/stores/baen_webscription_plugin.py index a2a4e63d74..63203693a5 100644 --- a/src/calibre/gui2/store/stores/baen_webscription_plugin.py +++ b/src/calibre/gui2/store/stores/baen_webscription_plugin.py @@ -1,6 +1,7 @@ # -*- coding: utf-8 -*- from __future__ import (unicode_literals, division, absolute_import, print_function) +store_version = 1 # Needed for dynamic plugin loading __license__ = 'GPL 3' __copyright__ = '2011, John Schember ' @@ -22,7 +23,7 @@ from calibre.gui2.store.search_result import SearchResult from calibre.gui2.store.web_store_dialog import WebStoreDialog class BaenWebScriptionStore(BasicStoreConfig, StorePlugin): - + def open(self, parent=None, detail_item=None, external=False): url = 'http://www.baenebooks.com/' @@ -41,26 +42,26 @@ class BaenWebScriptionStore(BasicStoreConfig, StorePlugin): def search(self, query, max_results=10, timeout=60): url = 'http://www.baenebooks.com/searchadv.aspx?IsSubmit=true&SearchTerm=' + urllib2.quote(query) - + br = browser() - + counter = max_results with closing(br.open(url, timeout=timeout)) as f: doc = html.fromstring(f.read()) for data in doc.xpath('//table//table//table//table//tr'): if counter <= 0: break - + id = ''.join(data.xpath('./td[1]/a/@href')) if not id or not id.startswith('p-'): continue - + title = ''.join(data.xpath('./td[1]/a/text()')) - + author = '' cover_url = '' price = '' - + with closing(br.open('http://www.baenebooks.com/' + id.strip(), timeout=timeout/4)) as nf: idata = html.fromstring(nf.read()) author = ''.join(idata.xpath('//span[@class="ProductNameText"]/../b/text()')) @@ -68,16 +69,16 @@ class BaenWebScriptionStore(BasicStoreConfig, StorePlugin): price = ''.join(idata.xpath('//span[@class="variantprice"]/text()')) a, b, price = price.partition('$') price = b + price - + pnum = '' mo = re.search(r'p-(?P\d+)-', id.strip()) if mo: pnum = mo.group('num') if pnum: cover_url = 'http://www.baenebooks.com/' + ''.join(idata.xpath('//img[@id="ProductPic%s"]/@src' % pnum)) - + counter -= 1 - + s = SearchResult() s.cover_url = cover_url s.title = title.strip() @@ -86,5 +87,5 @@ class BaenWebScriptionStore(BasicStoreConfig, StorePlugin): s.detail_item = id.strip() s.drm = SearchResult.DRM_UNLOCKED s.formats = 'RB, MOBI, EPUB, LIT, LRF, RTF, HTML' - + yield s diff --git a/src/calibre/gui2/store/stores/bewrite_plugin.py b/src/calibre/gui2/store/stores/bewrite_plugin.py index b702f15623..3ccd28d976 100644 --- a/src/calibre/gui2/store/stores/bewrite_plugin.py +++ b/src/calibre/gui2/store/stores/bewrite_plugin.py @@ -1,6 +1,7 @@ # -*- coding: utf-8 -*- from __future__ import (unicode_literals, division, absolute_import, print_function) +store_version = 1 # Needed for dynamic plugin loading __license__ = 'GPL 3' __copyright__ = '2011, John Schember ' @@ -71,7 +72,7 @@ class BeWriteStore(BasicStoreConfig, StorePlugin): with closing(br.open(search_result.detail_item, timeout=timeout)) as nf: idata = html.fromstring(nf.read()) - + price = ''.join(idata.xpath('//div[@id="content"]//td[contains(text(), "ePub")]/text()')) if not price: price = ''.join(idata.xpath('//div[@id="content"]//td[contains(text(), "MOBI")]/text()')) @@ -79,7 +80,7 @@ class BeWriteStore(BasicStoreConfig, StorePlugin): price = ''.join(idata.xpath('//div[@id="content"]//td[contains(text(), "PDF")]/text()')) price = '$' + price.split('$')[-1] search_result.price = price.strip() - + cover_img = idata.xpath('//div[@id="content"]//img/@src') if cover_img: for i in cover_img: @@ -87,7 +88,7 @@ class BeWriteStore(BasicStoreConfig, StorePlugin): cover_url = 'http://www.bewrite.net/mm5/' + i search_result.cover_url = cover_url.strip() break - + formats = set([]) if idata.xpath('boolean(//div[@id="content"]//td[contains(text(), "ePub")])'): formats.add('EPUB') diff --git a/src/calibre/gui2/store/stores/biblio_plugin.py b/src/calibre/gui2/store/stores/biblio_plugin.py index 5a40ec57cc..db7d909b3b 100644 --- a/src/calibre/gui2/store/stores/biblio_plugin.py +++ b/src/calibre/gui2/store/stores/biblio_plugin.py @@ -1,6 +1,7 @@ # -*- coding: utf-8 -*- from __future__ import (unicode_literals, division, absolute_import, print_function) +store_version = 1 # Needed for dynamic plugin loading __license__ = 'GPL 3' __copyright__ = '2012, Alex Stanev ' @@ -26,7 +27,7 @@ class BiblioStore(BasicStoreConfig, OpenSearchOPDSStore): for s in OpenSearchOPDSStore.search(self, query, max_results, timeout): yield s - + def get_details(self, search_result, timeout): # get format and DRM status from calibre import browser @@ -39,13 +40,13 @@ class BiblioStore(BasicStoreConfig, OpenSearchOPDSStore): search_result.formats = '' if idata.xpath('.//span[@class="format epub"]'): search_result.formats = 'EPUB' - + if idata.xpath('.//span[@class="format pdf"]'): if search_result.formats == '': search_result.formats = 'PDF' else: search_result.formats.join(', PDF') - + if idata.xpath('.//span[@class="format nodrm-icon"]'): search_result.drm = SearchResult.DRM_UNLOCKED else: diff --git a/src/calibre/gui2/store/stores/bn_plugin.py b/src/calibre/gui2/store/stores/bn_plugin.py index 65a7eee194..6138181fde 100644 --- a/src/calibre/gui2/store/stores/bn_plugin.py +++ b/src/calibre/gui2/store/stores/bn_plugin.py @@ -1,11 +1,13 @@ # -*- coding: utf-8 -*- from __future__ import (unicode_literals, division, absolute_import, print_function) +store_version = 1 # Needed for dynamic plugin loading __license__ = 'GPL 3' __copyright__ = '2011, John Schember ' __docformat__ = 'restructuredtext en' +import re import urllib from contextlib import closing @@ -50,12 +52,17 @@ class BNStore(BasicStoreConfig, StorePlugin): if not id: continue - cover_url = ''.join(data.xpath('.//img[contains(@class, "product-image")]/@src')) + cover_url = '' + cover_id = ''.join(data.xpath('.//img[contains(@class, "product-image")]/@id')) + m = re.search(r"%s'.*?srcUrl: '(?P.*?)'.*?}" % cover_id, raw) + if m: + cover_url = m.group('iurl') title = ''.join(data.xpath('descendant::p[@class="title"]//span[@class="name"]//text()')).strip() - if not title: continue + if not title: + continue - author = ', '.join(data.xpath('.//ul[@class="contributors"]//a[@class="subtle"]//text()')).strip() + author = ', '.join(data.xpath('.//ul[contains(@class, "contributors")]//a[contains(@class, "subtle")]//text()')).strip() price = ''.join(data.xpath('.//a[contains(@class, "bn-price")]//text()')) counter -= 1 diff --git a/src/calibre/gui2/store/stores/bookoteka_plugin.py b/src/calibre/gui2/store/stores/bookoteka_plugin.py index 4df22060ed..7c3b2e8242 100644 --- a/src/calibre/gui2/store/stores/bookoteka_plugin.py +++ b/src/calibre/gui2/store/stores/bookoteka_plugin.py @@ -1,6 +1,7 @@ # -*- coding: utf-8 -*- from __future__ import (unicode_literals, division, absolute_import, print_function) +store_version = 1 # Needed for dynamic plugin loading __license__ = 'GPL 3' __copyright__ = '2011, Tomasz Długosz ' diff --git a/src/calibre/gui2/store/stores/chitanka_plugin.py b/src/calibre/gui2/store/stores/chitanka_plugin.py index 58ef109dba..30fc543849 100644 --- a/src/calibre/gui2/store/stores/chitanka_plugin.py +++ b/src/calibre/gui2/store/stores/chitanka_plugin.py @@ -1,6 +1,7 @@ # -*- coding: utf-8 -*- from __future__ import (unicode_literals, division, absolute_import, print_function) +store_version = 1 # Needed for dynamic plugin loading __license__ = 'GPL 3' __copyright__ = '2011, Alex Stanev ' diff --git a/src/calibre/gui2/store/stores/diesel_ebooks_plugin.py b/src/calibre/gui2/store/stores/diesel_ebooks_plugin.py index 309ee98e4c..eebd1376ba 100644 --- a/src/calibre/gui2/store/stores/diesel_ebooks_plugin.py +++ b/src/calibre/gui2/store/stores/diesel_ebooks_plugin.py @@ -1,6 +1,7 @@ # -*- coding: utf-8 -*- from __future__ import (unicode_literals, division, absolute_import, print_function) +store_version = 1 # Needed for dynamic plugin loading __license__ = 'GPL 3' __copyright__ = '2011, John Schember ' diff --git a/src/calibre/gui2/store/stores/ebook_nl_plugin.py b/src/calibre/gui2/store/stores/ebook_nl_plugin.py index 0a79026dbb..6f895f1325 100644 --- a/src/calibre/gui2/store/stores/ebook_nl_plugin.py +++ b/src/calibre/gui2/store/stores/ebook_nl_plugin.py @@ -1,6 +1,7 @@ # -*- coding: utf-8 -*- from __future__ import (unicode_literals, division, absolute_import, print_function) +store_version = 1 # Needed for dynamic plugin loading __license__ = 'GPL 3' __copyright__ = '2011, John Schember ' diff --git a/src/calibre/gui2/store/stores/ebookpoint_plugin.py b/src/calibre/gui2/store/stores/ebookpoint_plugin.py index 94e6cc73ca..d0306a45ee 100644 --- a/src/calibre/gui2/store/stores/ebookpoint_plugin.py +++ b/src/calibre/gui2/store/stores/ebookpoint_plugin.py @@ -1,6 +1,7 @@ # -*- coding: utf-8 -*- from __future__ import (unicode_literals, division, absolute_import, print_function) +store_version = 1 # Needed for dynamic plugin loading __license__ = 'GPL 3' __copyright__ = '2011-2012, Tomasz Długosz ' diff --git a/src/calibre/gui2/store/stores/ebooks_com_plugin.py b/src/calibre/gui2/store/stores/ebooks_com_plugin.py index 826b59d41d..5c901bd65e 100644 --- a/src/calibre/gui2/store/stores/ebooks_com_plugin.py +++ b/src/calibre/gui2/store/stores/ebooks_com_plugin.py @@ -1,6 +1,7 @@ # -*- coding: utf-8 -*- from __future__ import (unicode_literals, division, absolute_import, print_function) +store_version = 1 # Needed for dynamic plugin loading __license__ = 'GPL 3' __copyright__ = '2011, John Schember ' diff --git a/src/calibre/gui2/store/stores/ebooksgratuits_plugin.py b/src/calibre/gui2/store/stores/ebooksgratuits_plugin.py index add4bb2d40..2f13e0be86 100644 --- a/src/calibre/gui2/store/stores/ebooksgratuits_plugin.py +++ b/src/calibre/gui2/store/stores/ebooksgratuits_plugin.py @@ -1,6 +1,7 @@ # -*- coding: utf-8 -*- from __future__ import (unicode_literals, division, absolute_import, print_function) +store_version = 1 # Needed for dynamic plugin loading __license__ = 'GPL 3' __copyright__ = '2012, Florent FAYOLLE ' diff --git a/src/calibre/gui2/store/stores/ebookshoppe_uk_plugin.py b/src/calibre/gui2/store/stores/ebookshoppe_uk_plugin.py index 804279d3fd..77801d8584 100644 --- a/src/calibre/gui2/store/stores/ebookshoppe_uk_plugin.py +++ b/src/calibre/gui2/store/stores/ebookshoppe_uk_plugin.py @@ -1,6 +1,7 @@ # -*- coding: utf-8 -*- from __future__ import (unicode_literals, division, absolute_import, print_function) +store_version = 1 # Needed for dynamic plugin loading __license__ = 'GPL 3' __copyright__ = '2011, John Schember ' diff --git a/src/calibre/gui2/store/stores/eharlequin_plugin.py b/src/calibre/gui2/store/stores/eharlequin_plugin.py index ec85ccf1d3..5c863af856 100644 --- a/src/calibre/gui2/store/stores/eharlequin_plugin.py +++ b/src/calibre/gui2/store/stores/eharlequin_plugin.py @@ -1,6 +1,7 @@ # -*- coding: utf-8 -*- from __future__ import (unicode_literals, division, absolute_import, print_function) +store_version = 1 # Needed for dynamic plugin loading __license__ = 'GPL 3' __copyright__ = '2011, John Schember ' @@ -36,9 +37,9 @@ class EHarlequinStore(BasicStoreConfig, StorePlugin): def search(self, query, max_results=10, timeout=60): url = 'http://ebooks.eharlequin.com/BANGSearch.dll?Type=FullText&FullTextField=All&FullTextCriteria=' + urllib2.quote(query) - + br = browser() - + counter = max_results with closing(br.open(url, timeout=timeout)) as f: doc = html.fromstring(f.read()) @@ -64,19 +65,19 @@ class EHarlequinStore(BasicStoreConfig, StorePlugin): s.price = price.strip() s.detail_item = 'http://ebooks.eharlequin.com/' + id.strip() s.formats = 'EPUB' - + yield s - + def get_details(self, search_result, timeout): url = 'http://ebooks.eharlequin.com/en/ContentDetails.htm?ID=' - + mo = re.search(r'\?ID=(?P.+)', search_result.detail_item) if mo: id = mo.group('id') if not id: return - - + + br = browser() with closing(br.open(url + id, timeout=timeout)) as nf: idata = html.fromstring(nf.read()) diff --git a/src/calibre/gui2/store/stores/eknigi_plugin.py b/src/calibre/gui2/store/stores/eknigi_plugin.py index 7d88465f62..7cafba421e 100644 --- a/src/calibre/gui2/store/stores/eknigi_plugin.py +++ b/src/calibre/gui2/store/stores/eknigi_plugin.py @@ -1,6 +1,7 @@ # -*- coding: utf-8 -*- from __future__ import (unicode_literals, division, absolute_import, print_function) +store_version = 1 # Needed for dynamic plugin loading __license__ = 'GPL 3' __copyright__ = '2011, Alex Stanev ' diff --git a/src/calibre/gui2/store/stores/empik_plugin.py b/src/calibre/gui2/store/stores/empik_plugin.py index 16a7ee13e3..08b1cdcb64 100644 --- a/src/calibre/gui2/store/stores/empik_plugin.py +++ b/src/calibre/gui2/store/stores/empik_plugin.py @@ -1,6 +1,7 @@ # -*- coding: utf-8 -*- from __future__ import (unicode_literals, division, absolute_import, print_function) +store_version = 1 # Needed for dynamic plugin loading __license__ = 'GPL 3' __copyright__ = '2011-2012, Tomasz Długosz ' @@ -68,7 +69,7 @@ class EmpikStore(BasicStoreConfig, StorePlugin): counter -= 1 s = SearchResult() - s.cover_url = cover_url + s.cover_url = cover_url s.title = title.strip() + ' ' + formats s.author = author.strip() s.price = price diff --git a/src/calibre/gui2/store/stores/escapemagazine_plugin.py b/src/calibre/gui2/store/stores/escapemagazine_plugin.py index 7f3f24e7d6..e3b1ef335a 100644 --- a/src/calibre/gui2/store/stores/escapemagazine_plugin.py +++ b/src/calibre/gui2/store/stores/escapemagazine_plugin.py @@ -1,6 +1,7 @@ # -*- coding: utf-8 -*- from __future__ import (unicode_literals, division, absolute_import, print_function) +store_version = 1 # Needed for dynamic plugin loading __license__ = 'GPL 3' __copyright__ = '2011, Tomasz Długosz ' diff --git a/src/calibre/gui2/store/stores/feedbooks_plugin.py b/src/calibre/gui2/store/stores/feedbooks_plugin.py index cac44fd8df..36521406bb 100644 --- a/src/calibre/gui2/store/stores/feedbooks_plugin.py +++ b/src/calibre/gui2/store/stores/feedbooks_plugin.py @@ -1,6 +1,7 @@ # -*- coding: utf-8 -*- from __future__ import (unicode_literals, division, absolute_import, print_function) +store_version = 1 # Needed for dynamic plugin loading __license__ = 'GPL 3' __copyright__ = '2011, John Schember ' @@ -11,10 +12,10 @@ from calibre.gui2.store.opensearch_store import OpenSearchOPDSStore from calibre.gui2.store.search_result import SearchResult class FeedbooksStore(BasicStoreConfig, OpenSearchOPDSStore): - + open_search_url = 'http://assets0.feedbooks.net/opensearch.xml?t=1253087147' web_url = 'http://feedbooks.com/' - + # http://www.feedbooks.com/catalog def search(self, query, max_results=10, timeout=60): diff --git a/src/calibre/gui2/store/stores/foyles_uk_plugin.py b/src/calibre/gui2/store/stores/foyles_uk_plugin.py index 819c412758..7c224f4f70 100644 --- a/src/calibre/gui2/store/stores/foyles_uk_plugin.py +++ b/src/calibre/gui2/store/stores/foyles_uk_plugin.py @@ -1,6 +1,7 @@ # -*- coding: utf-8 -*- from __future__ import (unicode_literals, division, absolute_import, print_function) +store_version = 1 # Needed for dynamic plugin loading __license__ = 'GPL 3' __copyright__ = '2011, John Schember ' diff --git a/src/calibre/gui2/store/stores/google_books_plugin.py b/src/calibre/gui2/store/stores/google_books_plugin.py index 63fc3ef942..f6f91fd81d 100644 --- a/src/calibre/gui2/store/stores/google_books_plugin.py +++ b/src/calibre/gui2/store/stores/google_books_plugin.py @@ -1,6 +1,7 @@ # -*- coding: utf-8 -*- from __future__ import (unicode_literals, division, absolute_import, print_function) +store_version = 1 # Needed for dynamic plugin loading __license__ = 'GPL 3' __copyright__ = '2011, John Schember ' @@ -38,7 +39,7 @@ class GoogleBooksStore(BasicStoreConfig, StorePlugin): 'ganpub': 'k352583', 'ganclk': 'GOOG_1335335464', } - + url = 'http://gan.doubleclick.net/gan_click?lid=%(lid)s&pubid=%(pubid)s' % aff_id if detail_item: detail_item += '&ganpub=%(ganpub)s&ganclk=%(ganclk)s' % aff_id @@ -53,13 +54,13 @@ class GoogleBooksStore(BasicStoreConfig, StorePlugin): def search(self, query, max_results=10, timeout=60): url = 'http://www.google.com/search?tbm=bks&q=' + urllib.quote_plus(query) - + br = browser() - + counter = max_results with closing(br.open(url, timeout=timeout)) as f: doc = html.fromstring(f.read()) - for data in doc.xpath('//ol[@id="rso"]/li'): + for data in doc.xpath('//ol/li'): if counter <= 0: break @@ -68,7 +69,7 @@ class GoogleBooksStore(BasicStoreConfig, StorePlugin): continue title = ''.join(data.xpath('.//h3/a//text()')) - authors = data.xpath('.//div[@class="f"]//a//text()') + authors = data.xpath('.//span[contains(@class, "f")]//a//text()') while authors and authors[-1].strip().lower() in ('preview', 'read', 'more editions'): authors = authors[:-1] if not authors: @@ -76,22 +77,22 @@ class GoogleBooksStore(BasicStoreConfig, StorePlugin): author = ', '.join(authors) counter -= 1 - + s = SearchResult() s.title = title.strip() s.author = author.strip() s.detail_item = id.strip() s.drm = SearchResult.DRM_UNKNOWN - + yield s - + def get_details(self, search_result, timeout): br = browser() with closing(br.open(search_result.detail_item, timeout=timeout)) as nf: doc = html.fromstring(nf.read()) - + search_result.cover_url = ''.join(doc.xpath('//div[@class="sidebarcover"]//img/@src')) - + # Try to get the set price. price = ''.join(doc.xpath('//div[@id="gb-get-book-container"]//a/text()')) if 'read' in price.lower(): @@ -101,10 +102,10 @@ class GoogleBooksStore(BasicStoreConfig, StorePlugin): elif '-' in price: a, b, price = price.partition(' - ') search_result.price = price.strip() - + search_result.formats = ', '.join(doc.xpath('//div[contains(@class, "download-panel-div")]//a/text()')).upper() if not search_result.formats: search_result.formats = _('Unknown') - + return True diff --git a/src/calibre/gui2/store/stores/gutenberg_plugin.py b/src/calibre/gui2/store/stores/gutenberg_plugin.py index cbf3a2f565..422165f263 100644 --- a/src/calibre/gui2/store/stores/gutenberg_plugin.py +++ b/src/calibre/gui2/store/stores/gutenberg_plugin.py @@ -1,6 +1,7 @@ # -*- coding: utf-8 -*- from __future__ import (unicode_literals, division, absolute_import, print_function) +store_version = 1 # Needed for dynamic plugin loading __license__ = 'GPL 3' __copyright__ = '2011, John Schember ' diff --git a/src/calibre/gui2/store/stores/kobo_plugin.py b/src/calibre/gui2/store/stores/kobo_plugin.py index 5a8b5618d5..44f4f4001c 100644 --- a/src/calibre/gui2/store/stores/kobo_plugin.py +++ b/src/calibre/gui2/store/stores/kobo_plugin.py @@ -1,6 +1,7 @@ # -*- coding: utf-8 -*- from __future__ import (unicode_literals, division, absolute_import, print_function) +store_version = 1 # Needed for dynamic plugin loading __license__ = 'GPL 3' __copyright__ = '2011, John Schember ' diff --git a/src/calibre/gui2/store/stores/legimi_plugin.py b/src/calibre/gui2/store/stores/legimi_plugin.py index 509ca88104..85561c63f3 100644 --- a/src/calibre/gui2/store/stores/legimi_plugin.py +++ b/src/calibre/gui2/store/stores/legimi_plugin.py @@ -1,6 +1,7 @@ # -*- coding: utf-8 -*- from __future__ import (unicode_literals, division, absolute_import, print_function) +store_version = 1 # Needed for dynamic plugin loading __license__ = 'GPL 3' __copyright__ = '2011, Tomasz Długosz ' @@ -24,7 +25,7 @@ from calibre.gui2.store.web_store_dialog import WebStoreDialog class LegimiStore(BasicStoreConfig, StorePlugin): def open(self, parent=None, detail_item=None, external=False): - + plain_url = 'http://www.legimi.com/pl/ebooki/' url = 'https://ssl.afiliant.com/affskrypt,,2f9de2,,11483,,,?u=(' + plain_url + ')' detail_url = None @@ -42,17 +43,17 @@ class LegimiStore(BasicStoreConfig, StorePlugin): def search(self, query, max_results=10, timeout=60): url = 'http://www.legimi.com/pl/ebooki/?szukaj=' + urllib.quote_plus(query) - + br = browser() drm_pattern = re.compile("zabezpieczona DRM") - + counter = max_results with closing(br.open(url, timeout=timeout)) as f: doc = html.fromstring(f.read()) for data in doc.xpath('//div[@id="listBooks"]/div'): if counter <= 0: break - + id = ''.join(data.xpath('.//a[@class="plainLink"]/@href')) if not id: continue @@ -73,7 +74,7 @@ class LegimiStore(BasicStoreConfig, StorePlugin): drm = drm_pattern.search(''.join(idata.xpath('.//div[@id="fullBookFormats"]/p/text()'))) counter -= 1 - + s = SearchResult() s.cover_url = 'http://www.legimi.com/' + cover_url s.title = title.strip() @@ -82,5 +83,5 @@ class LegimiStore(BasicStoreConfig, StorePlugin): s.detail_item = 'http://www.legimi.com/' + id.strip() s.formats = ', '.join(formats) s.drm = SearchResult.DRM_LOCKED if drm else SearchResult.DRM_UNLOCKED - + yield s diff --git a/src/calibre/gui2/store/stores/libri_de_plugin.py b/src/calibre/gui2/store/stores/libri_de_plugin.py index 60f7471272..d7d0807a99 100644 --- a/src/calibre/gui2/store/stores/libri_de_plugin.py +++ b/src/calibre/gui2/store/stores/libri_de_plugin.py @@ -1,6 +1,7 @@ # -*- coding: utf-8 -*- from __future__ import (unicode_literals, division, absolute_import, print_function) +store_version = 1 # Needed for dynamic plugin loading __license__ = 'GPL 3' __copyright__ = '2011, John Schember ' diff --git a/src/calibre/gui2/store/stores/litres_plugin.py b/src/calibre/gui2/store/stores/litres_plugin.py index 6f4c386dda..5a1d2271fe 100644 --- a/src/calibre/gui2/store/stores/litres_plugin.py +++ b/src/calibre/gui2/store/stores/litres_plugin.py @@ -1,6 +1,7 @@ # -*- coding: utf-8 -*- from __future__ import (unicode_literals, division, absolute_import, print_function) +store_version = 1 # Needed for dynamic plugin loading __license__ = 'GPL 3' __copyright__ = '2011, Roman Mukhin ' diff --git a/src/calibre/gui2/store/stores/manybooks_plugin.py b/src/calibre/gui2/store/stores/manybooks_plugin.py index 2b06798630..2344193b47 100644 --- a/src/calibre/gui2/store/stores/manybooks_plugin.py +++ b/src/calibre/gui2/store/stores/manybooks_plugin.py @@ -1,6 +1,7 @@ # -*- coding: utf-8 -*- from __future__ import (unicode_literals, division, absolute_import, print_function) +store_version = 1 # Needed for dynamic plugin loading __license__ = 'GPL 3' __copyright__ = '2011, John Schember ' @@ -26,7 +27,7 @@ class ManyBooksStore(BasicStoreConfig, OpenSearchOPDSStore): def search(self, query, max_results=10, timeout=60): ''' Manybooks uses a very strange opds feed. The opds - main feed is structured like a stanza feed. The + main feed is structured like a stanza feed. The search result entries give very little information and requires you to go to a detail link. The detail link has the wrong type specified (text/html instead @@ -45,7 +46,7 @@ class ManyBooksStore(BasicStoreConfig, OpenSearchOPDSStore): oquery.searchTerms = query oquery.count = max_results url = oquery.url() - + counter = max_results br = browser() with closing(br.open(url, timeout=timeout)) as f: @@ -55,11 +56,11 @@ class ManyBooksStore(BasicStoreConfig, OpenSearchOPDSStore): for data in doc.xpath('//*[local-name() = "entry"]'): if counter <= 0: break - + counter -= 1 - + s = SearchResult() - + detail_links = data.xpath('./*[local-name() = "link" and @type = "text/html"]') if not detail_links: continue @@ -73,7 +74,7 @@ class ManyBooksStore(BasicStoreConfig, OpenSearchOPDSStore): # just in case. s.title = ''.join(data.xpath('./*[local-name() = "title"]//text()')).strip() s.author = ', '.join(data.xpath('./*[local-name() = "author"]//text()')).strip() - + # Follow the detail link to get the rest of the info. with closing(br.open(detail_href, timeout=timeout/4)) as df: ddoc = etree.fromstring(df.read()) @@ -89,9 +90,9 @@ class ManyBooksStore(BasicStoreConfig, OpenSearchOPDSStore): s.author = s.author[1:] if s.author.endswith(','): s.author = s.author[:-1] - + s.cover_url = ''.join(ddata.xpath('./*[local-name() = "link" and @rel = "http://opds-spec.org/thumbnail"][1]/@href')).strip() - + for link in ddata.xpath('./*[local-name() = "link" and @rel = "http://opds-spec.org/acquisition"]'): type = link.get('type') href = link.get('href') diff --git a/src/calibre/gui2/store/stores/mills_boon_uk_plugin.py b/src/calibre/gui2/store/stores/mills_boon_uk_plugin.py index 6aa4b4b0b7..b8969beaed 100644 --- a/src/calibre/gui2/store/stores/mills_boon_uk_plugin.py +++ b/src/calibre/gui2/store/stores/mills_boon_uk_plugin.py @@ -1,6 +1,7 @@ # -*- coding: utf-8 -*- from __future__ import (unicode_literals, division, absolute_import, print_function) +store_version = 1 # Needed for dynamic plugin loading __license__ = 'GPL 3' __copyright__ = '2011, John Schember ' diff --git a/src/calibre/gui2/store/stores/nexto_plugin.py b/src/calibre/gui2/store/stores/nexto_plugin.py index 79cb1be2f1..e5f9e31980 100644 --- a/src/calibre/gui2/store/stores/nexto_plugin.py +++ b/src/calibre/gui2/store/stores/nexto_plugin.py @@ -1,6 +1,7 @@ # -*- coding: utf-8 -*- from __future__ import (unicode_literals, division, absolute_import, print_function) +store_version = 1 # Needed for dynamic plugin loading __license__ = 'GPL 3' __copyright__ = '2011-2012, Tomasz Długosz ' diff --git a/src/calibre/gui2/store/stores/nook_uk_plugin.py b/src/calibre/gui2/store/stores/nook_uk_plugin.py new file mode 100644 index 0000000000..cc97d5cf93 --- /dev/null +++ b/src/calibre/gui2/store/stores/nook_uk_plugin.py @@ -0,0 +1,76 @@ +# -*- coding: utf-8 -*- + +from __future__ import (unicode_literals, division, absolute_import, print_function) +store_version = 1 # Needed for dynamic plugin loading + +__license__ = 'GPL 3' +__copyright__ = '2012, John Schember ' +__docformat__ = 'restructuredtext en' + +import re +import urllib +from contextlib import closing + +from lxml import html + +from PyQt4.Qt import QUrl + +from calibre import browser, url_slash_cleaner +from calibre.gui2 import open_url +from calibre.gui2.store import StorePlugin +from calibre.gui2.store.basic_config import BasicStoreConfig +from calibre.gui2.store.search_result import SearchResult +from calibre.gui2.store.web_store_dialog import WebStoreDialog + +class NookUKStore(BasicStoreConfig, StorePlugin): + + def open(self, parent=None, detail_item=None, external=False): + url = "http://uk.nook.com" + + if external or self.config.get('open_external', False): + open_url(QUrl(url_slash_cleaner(detail_item if detail_item else url))) + else: + d = WebStoreDialog(self.gui, url, parent, detail_item) + d.setWindowTitle(self.name) + d.set_tags(self.config.get('tags', '')) + d.exec_() + + def search(self, query, max_results=10, timeout=60): + url = u'http://uk.nook.com/s/%s?s%%5Bdref%%5D=1&s%%5Bkeyword%%5D=%s' % (query.replace(' ', '-'), urllib.quote(query)) + + br = browser() + + counter = max_results + with closing(br.open(url, timeout=timeout)) as f: + raw = f.read() + doc = html.fromstring(raw) + for data in doc.xpath('//ul[contains(@class, "product_list")]/li'): + if counter <= 0: + break + + id = ''.join(data.xpath('.//span[contains(@class, "image")]/a/@href')) + if not id: + continue + + cover_url = ''.join(data.xpath('.//span[contains(@class, "image")]//img/@data-src')) + + title = ''.join(data.xpath('.//div[contains(@class, "title")]//text()')).strip() + if not title: + continue + + author = ', '.join(data.xpath('.//div[contains(@class, "contributor")]//a/text()')).strip() + price = ''.join(data.xpath('.//div[contains(@class, "action")]//a//text()')).strip() + price = re.sub(r'[^\d.,£]', '', price); + + counter -= 1 + + s = SearchResult() + s.cover_url = cover_url + s.title = title.strip() + s.author = author.strip() + s.price = price.strip() + s.detail_item = 'http://uk.nook.com/' + id.strip() + s.drm = SearchResult.DRM_UNKNOWN + s.formats = 'Nook' + + yield s diff --git a/src/calibre/gui2/store/stores/open_books_plugin.py b/src/calibre/gui2/store/stores/open_books_plugin.py index 99b68656e9..66642ab679 100644 --- a/src/calibre/gui2/store/stores/open_books_plugin.py +++ b/src/calibre/gui2/store/stores/open_books_plugin.py @@ -1,6 +1,7 @@ # -*- coding: utf-8 -*- from __future__ import (unicode_literals, division, absolute_import, print_function) +store_version = 1 # Needed for dynamic plugin loading __license__ = 'GPL 3' __copyright__ = '2011, John Schember ' diff --git a/src/calibre/gui2/store/stores/ozon_ru_plugin.py b/src/calibre/gui2/store/stores/ozon_ru_plugin.py index b54bf01daf..9a3c2dabaa 100644 --- a/src/calibre/gui2/store/stores/ozon_ru_plugin.py +++ b/src/calibre/gui2/store/stores/ozon_ru_plugin.py @@ -1,6 +1,7 @@ # -*- coding: utf-8 -*- from __future__ import (unicode_literals, division, absolute_import, print_function) +store_version = 1 # Needed for dynamic plugin loading __license__ = 'GPL 3' __copyright__ = '2011, Roman Mukhin ' @@ -24,33 +25,33 @@ from calibre.gui2.store.web_store_dialog import WebStoreDialog class OzonRUStore(BasicStoreConfig, StorePlugin): shop_url = 'http://www.ozon.ru' - + def open(self, parent=None, detail_item=None, external=False): - + aff_id = '?partner=romuk' # Use Kovid's affiliate id 30% of the time. if random.randint(1, 10) in (1, 2, 3): aff_id = '?partner=kovidgoyal' - + url = self.shop_url + aff_id detail_url = None if detail_item: # http://www.ozon.ru/context/detail/id/3037277/ detail_url = self.shop_url + '/context/detail/id/' + urllib2.quote(detail_item) + aff_id - + if external or self.config.get('open_external', False): open_url(QUrl(url_slash_cleaner(detail_url if detail_url else url))) else: d = WebStoreDialog(self.gui, url, parent, detail_url) d.setWindowTitle(self.name) d.set_tags(self.config.get('tags', '')) - d.exec_() - + d.exec_() + def search(self, query, max_results=15, timeout=60): search_url = self.shop_url + '/webservice/webservice.asmx/SearchWebService?'\ 'searchText=%s&searchContext=ebook' % urllib2.quote(query) search_urls = [ search_url ] - + ## add this as the fist try if it looks like ozon ID if re.match("^\d{6,9}$", query): ozon_detail = self.shop_url + '/webservices/OzonWebSvc.asmx/ItemDetail?ID=%s' % query @@ -59,7 +60,7 @@ class OzonRUStore(BasicStoreConfig, StorePlugin): xp_template = 'normalize-space(./*[local-name() = "{0}"]/text())' counter = max_results br = browser() - + for url in search_urls: with closing(br.open(url, timeout=timeout)) as f: raw = xml_to_unicode(f.read(), strip_encoding_pats=True, assume_utf8=True)[0] @@ -86,10 +87,10 @@ class OzonRUStore(BasicStoreConfig, StorePlugin): with closing(br.open(url, timeout=timeout)) as f: raw = xml_to_unicode(f.read(), verbose=True)[0] doc = html.fromstring(raw) - + # example where we are going to find formats #

- #

+ #

# Доступно: #

#
@@ -104,16 +105,16 @@ class OzonRUStore(BasicStoreConfig, StorePlugin): search_result.formats = ', '.join(_parse_ebook_formats(formats)) # unfortunately no direct links to download books (only buy link) # search_result.downloads['BF2'] = self.shop_url + '/order/digitalorder.aspx?id=' + + urllib2.quote(search_result.detail_item) - + #

21500 руб.

# # - + # if the price not in the search result (the ID search case) if not search_result.price: price = doc.xpath(u'normalize-space(//*[@itemprop="price"]/text())') search_result.price = format_price_in_RUR(price) - + return result def format_price_in_RUR(price): @@ -134,12 +135,12 @@ def format_price_in_RUR(price): def _parse_ebook_formats(formatsStr): ''' Creates a list with displayable names of the formats - - :param formatsStr: string with comma separated book formats + + :param formatsStr: string with comma separated book formats as it provided by ozon.ru :return: a list with displayable book formats ''' - + formatsUnstruct = formatsStr.lower() formats = [] if 'epub' in formatsUnstruct: diff --git a/src/calibre/gui2/store/stores/pragmatic_bookshelf_plugin.py b/src/calibre/gui2/store/stores/pragmatic_bookshelf_plugin.py index 99b94778bf..544fa06fe8 100644 --- a/src/calibre/gui2/store/stores/pragmatic_bookshelf_plugin.py +++ b/src/calibre/gui2/store/stores/pragmatic_bookshelf_plugin.py @@ -1,6 +1,7 @@ # -*- coding: utf-8 -*- from __future__ import (unicode_literals, division, absolute_import, print_function) +store_version = 1 # Needed for dynamic plugin loading __license__ = 'GPL 3' __copyright__ = '2011, John Schember ' @@ -14,7 +15,7 @@ class PragmaticBookshelfStore(BasicStoreConfig, OpenSearchOPDSStore): open_search_url = 'http://pragprog.com/catalog/search-description' web_url = 'http://pragprog.com/' - + # http://pragprog.com/catalog.opds def search(self, query, max_results=10, timeout=60): diff --git a/src/calibre/gui2/store/stores/publio_plugin.py b/src/calibre/gui2/store/stores/publio_plugin.py index eb00f231ea..44f3267b35 100644 --- a/src/calibre/gui2/store/stores/publio_plugin.py +++ b/src/calibre/gui2/store/stores/publio_plugin.py @@ -1,6 +1,7 @@ # -*- coding: utf-8 -*- from __future__ import (unicode_literals, division, absolute_import, print_function) +store_version = 1 # Needed for dynamic plugin loading __license__ = 'GPL 3' __copyright__ = '2012, Tomasz Długosz ' diff --git a/src/calibre/gui2/store/stores/rw2010_plugin.py b/src/calibre/gui2/store/stores/rw2010_plugin.py index ed4d5a53f7..fc86ae4967 100644 --- a/src/calibre/gui2/store/stores/rw2010_plugin.py +++ b/src/calibre/gui2/store/stores/rw2010_plugin.py @@ -1,6 +1,7 @@ # -*- coding: utf-8 -*- from __future__ import (unicode_literals, division, absolute_import, print_function) +store_version = 1 # Needed for dynamic plugin loading __license__ = 'GPL 3' __copyright__ = '2011, Tomasz Długosz ' @@ -73,5 +74,5 @@ class RW2010Store(BasicStoreConfig, StorePlugin): s.detail_item = re.sub(r'%3D', '=', id) s.drm = SearchResult.DRM_UNLOCKED s.formats = formats[0:-2].upper() - + yield s diff --git a/src/calibre/gui2/store/stores/smashwords_plugin.py b/src/calibre/gui2/store/stores/smashwords_plugin.py index 2c69417612..580e3c2907 100644 --- a/src/calibre/gui2/store/stores/smashwords_plugin.py +++ b/src/calibre/gui2/store/stores/smashwords_plugin.py @@ -1,6 +1,7 @@ # -*- coding: utf-8 -*- from __future__ import (unicode_literals, division, absolute_import, print_function) +store_version = 1 # Needed for dynamic plugin loading __license__ = 'GPL 3' __copyright__ = '2011, John Schember ' @@ -36,7 +37,7 @@ class SmashwordsStore(BasicStoreConfig, StorePlugin): if detail_item: detail_url = url + detail_item + aff_id url = url + aff_id - + if external or self.config.get('open_external', False): open_url(QUrl(url_slash_cleaner(detail_url if detail_url else url))) else: @@ -47,9 +48,9 @@ class SmashwordsStore(BasicStoreConfig, StorePlugin): def search(self, query, max_results=10, timeout=60): url = 'http://www.smashwords.com/books/search?query=' + urllib2.quote(query) - + br = browser() - + counter = max_results with closing(br.open(url, timeout=timeout)) as f: doc = html.fromstring(f.read()) @@ -57,7 +58,7 @@ class SmashwordsStore(BasicStoreConfig, StorePlugin): if counter <= 0: break data = html.fromstring(html.tostring(data)) - + id = None id_a = data.xpath('//a[@class="bookTitle"]') if id_a: @@ -66,17 +67,17 @@ class SmashwordsStore(BasicStoreConfig, StorePlugin): id = id.split('/')[-1] if not id: continue - + cover_url = '' c_url = data.get('style', None) if c_url: mo = re.search(r'http://[^\'"]+', c_url) if mo: cover_url = mo.group() - + title = ''.join(data.xpath('//a[@class="bookTitle"]/text()')) subnote = ''.join(data.xpath('//span[@class="subnote"]/text()')) - author = ''.join(data.xpath('//span[@class="subnote"]/a/text()')) + author = ''.join(data.xpath('//span[@class="subnote"]//a[1]//text()')) if '$' in subnote: price = subnote.partition('$')[2] price = price.split(u'\xa0')[0] @@ -85,7 +86,7 @@ class SmashwordsStore(BasicStoreConfig, StorePlugin): price = '$0.00' counter -= 1 - + s = SearchResult() s.cover_url = cover_url s.title = title.strip() @@ -93,12 +94,12 @@ class SmashwordsStore(BasicStoreConfig, StorePlugin): s.price = price.strip() s.detail_item = '/books/view/' + id.strip() s.drm = SearchResult.DRM_UNLOCKED - + yield s def get_details(self, search_result, timeout): url = 'http://www.smashwords.com/' - + br = browser() with closing(br.open(url + search_result.detail_item, timeout=timeout)) as nf: idata = html.fromstring(nf.read()) diff --git a/src/calibre/gui2/store/stores/sony_plugin.py b/src/calibre/gui2/store/stores/sony_plugin.py index aa0c65bcde..030919c925 100644 --- a/src/calibre/gui2/store/stores/sony_plugin.py +++ b/src/calibre/gui2/store/stores/sony_plugin.py @@ -2,6 +2,7 @@ # vim:fileencoding=UTF-8:ts=4:sw=4:sta:et:sts=4:fdm=marker:ai from __future__ import (unicode_literals, division, absolute_import, print_function) +store_version = 1 # Needed for dynamic plugin loading __license__ = 'GPL v3' __copyright__ = '2012, Kovid Goyal ' diff --git a/src/calibre/gui2/store/stores/virtualo_plugin.py b/src/calibre/gui2/store/stores/virtualo_plugin.py index e6b60fbe91..02396b7f19 100644 --- a/src/calibre/gui2/store/stores/virtualo_plugin.py +++ b/src/calibre/gui2/store/stores/virtualo_plugin.py @@ -1,6 +1,7 @@ # -*- coding: utf-8 -*- from __future__ import (unicode_literals, division, absolute_import, print_function) +store_version = 1 # Needed for dynamic plugin loading __license__ = 'GPL 3' __copyright__ = '2011, Tomasz Długosz ' diff --git a/src/calibre/gui2/store/stores/waterstones_uk_plugin.py b/src/calibre/gui2/store/stores/waterstones_uk_plugin.py index df17372d0a..1bcbb865bf 100644 --- a/src/calibre/gui2/store/stores/waterstones_uk_plugin.py +++ b/src/calibre/gui2/store/stores/waterstones_uk_plugin.py @@ -1,6 +1,7 @@ # -*- coding: utf-8 -*- from __future__ import (unicode_literals, division, absolute_import, print_function) +store_version = 1 # Needed for dynamic plugin loading __license__ = 'GPL 3' __copyright__ = '2011, John Schember ' @@ -40,7 +41,7 @@ class WaterstonesUKStore(BasicStoreConfig, StorePlugin): d.exec_() def search(self, query, max_results=10, timeout=60): - url = 'http://www.waterstones.com/waterstonesweb/advancedSearch.do?buttonClicked=1&format=3757&bookkeywords=' + urllib2.quote(query) + url = 'http://www.waterstones.com/waterstonesweb/simpleSearch.do?simpleSearchString=ebook+' + urllib2.quote(query) br = browser() @@ -55,6 +56,8 @@ class WaterstonesUKStore(BasicStoreConfig, StorePlugin): if not id: continue cover_url = ''.join(data.xpath('.//div[@class="image"]/a/img/@src')) + if not cover_url.startswith("http"): + cover_url = 'http://www.waterstones.com' + cover_url title = ''.join(data.xpath('./div/div/h2/a/text()')) author = ', '.join(data.xpath('.//p[@class="byAuthor"]/a/text()')) price = ''.join(data.xpath('.//p[@class="price"]/span[@class="priceRed2"]/text()')) diff --git a/src/calibre/gui2/store/stores/weightless_books_plugin.py b/src/calibre/gui2/store/stores/weightless_books_plugin.py index 330f3fdf0f..cc18cf5807 100644 --- a/src/calibre/gui2/store/stores/weightless_books_plugin.py +++ b/src/calibre/gui2/store/stores/weightless_books_plugin.py @@ -1,6 +1,7 @@ # -*- coding: utf-8 -*- from __future__ import (unicode_literals, division, absolute_import, print_function) +store_version = 1 # Needed for dynamic plugin loading __license__ = 'GPL 3' __copyright__ = '2011, John Schember ' @@ -35,9 +36,9 @@ class WeightlessBooksStore(BasicStoreConfig, StorePlugin): def search(self, query, max_results=10, timeout=60): url = 'http://weightlessbooks.com/?s=' + urllib.quote_plus(query) - + br = browser() - + counter = max_results with closing(br.open(url, timeout=timeout)) as f: doc = html.fromstring(f.read()) @@ -50,20 +51,20 @@ class WeightlessBooksStore(BasicStoreConfig, StorePlugin): continue cover_url = ''.join(data.xpath('.//div[@class="cover"]/a/img/@src')) - + price = ''.join(data.xpath('.//div[@class="buy_buttons"]/b[1]/text()')) if not price: continue - + formats = ', '.join(data.xpath('.//select[@class="eStore_variation"]//option//text()')) formats = formats.upper() - + title = ''.join(data.xpath('.//h3/a/text()')) author = ''.join(data.xpath('.//h3//text()')) author = author.replace(title, '') counter -= 1 - + s = SearchResult() s.cover_url = cover_url s.title = title.strip() @@ -72,5 +73,5 @@ class WeightlessBooksStore(BasicStoreConfig, StorePlugin): s.detail_item = id.strip() s.drm = SearchResult.DRM_UNLOCKED s.formats = formats - + yield s diff --git a/src/calibre/gui2/store/stores/whsmith_uk_plugin.py b/src/calibre/gui2/store/stores/whsmith_uk_plugin.py index 5d78340517..6f2de93523 100644 --- a/src/calibre/gui2/store/stores/whsmith_uk_plugin.py +++ b/src/calibre/gui2/store/stores/whsmith_uk_plugin.py @@ -1,6 +1,7 @@ # -*- coding: utf-8 -*- from __future__ import (unicode_literals, division, absolute_import, print_function) +store_version = 1 # Needed for dynamic plugin loading __license__ = 'GPL 3' __copyright__ = '2011, John Schember ' diff --git a/src/calibre/gui2/store/stores/woblink_plugin.py b/src/calibre/gui2/store/stores/woblink_plugin.py index 37861766f7..63ec259dbf 100644 --- a/src/calibre/gui2/store/stores/woblink_plugin.py +++ b/src/calibre/gui2/store/stores/woblink_plugin.py @@ -1,6 +1,7 @@ # -*- coding: utf-8 -*- from __future__ import (unicode_literals, division, absolute_import, print_function) +store_version = 1 # Needed for dynamic plugin loading __license__ = 'GPL 3' __copyright__ = '2011-2012, Tomasz Długosz ' diff --git a/src/calibre/gui2/store/stores/xinxii_plugin.py b/src/calibre/gui2/store/stores/xinxii_plugin.py index e8721a79b8..4be0a410be 100644 --- a/src/calibre/gui2/store/stores/xinxii_plugin.py +++ b/src/calibre/gui2/store/stores/xinxii_plugin.py @@ -1,6 +1,7 @@ # -*- coding: utf-8 -*- from __future__ import (unicode_literals, division, absolute_import, print_function) +store_version = 1 # Needed for dynamic plugin loading __license__ = 'GPL 3' __copyright__ = '2011, John Schember ' @@ -20,25 +21,25 @@ class XinXiiStore(BasicStoreConfig, OpenSearchOPDSStore): open_search_url = 'http://www.xinxii.com/catalog-search/' web_url = 'http://xinxii.com/' - + # http://www.xinxii.com/catalog/ def search(self, query, max_results=10, timeout=60): ''' XinXii's open search url is: http://www.xinxii.com/catalog-search/query/?keywords={searchTerms}&pw={startPage?}&doc_lang={docLang}&ff={docFormat},{docFormat},{docFormat} - + This url requires the docLang and docFormat. However, the search itself sent to XinXii does not require them. They can be ignored. We cannot push this into the stanard OpenSearchOPDSStore search because of the required attributes. - + XinXii doesn't return all info supported by OpenSearchOPDSStore search function so this one is modified to remove parts that are used. ''' - + url = 'http://www.xinxii.com/catalog-search/query/?keywords=' + urllib.quote_plus(query) - + counter = max_results br = browser() with closing(br.open(url, timeout=timeout)) as f: @@ -46,29 +47,29 @@ class XinXiiStore(BasicStoreConfig, OpenSearchOPDSStore): for data in doc.xpath('//*[local-name() = "entry"]'): if counter <= 0: break - + counter -= 1 - + s = SearchResult() - + s.detail_item = ''.join(data.xpath('./*[local-name() = "id"]/text()')).strip() for link in data.xpath('./*[local-name() = "link"]'): rel = link.get('rel') href = link.get('href') type = link.get('type') - + if rel and href and type: if rel in ('http://opds-spec.org/thumbnail', 'http://opds-spec.org/image/thumbnail'): s.cover_url = href if rel == 'alternate': s.detail_item = href - + s.formats = 'EPUB, PDF' - + s.title = ' '.join(data.xpath('./*[local-name() = "title"]//text()')).strip() s.author = ', '.join(data.xpath('./*[local-name() = "author"]//*[local-name() = "name"]//text()')).strip() - + price_e = data.xpath('.//*[local-name() = "price"][1]') if price_e: price_e = price_e[0] @@ -76,6 +77,6 @@ class XinXiiStore(BasicStoreConfig, OpenSearchOPDSStore): price = ''.join(price_e.xpath('.//text()')).strip() s.price = currency_code + ' ' + price s.price = s.price.strip() - + yield s diff --git a/src/calibre/gui2/store/stores/zixo_plugin.py b/src/calibre/gui2/store/stores/zixo_plugin.py index b4e54736c0..98bbdf3155 100644 --- a/src/calibre/gui2/store/stores/zixo_plugin.py +++ b/src/calibre/gui2/store/stores/zixo_plugin.py @@ -1,6 +1,7 @@ # -*- coding: utf-8 -*- from __future__ import (unicode_literals, division, absolute_import, print_function) +store_version = 1 # Needed for dynamic plugin loading __license__ = 'GPL 3' __copyright__ = '2011, Tomasz Długosz ' diff --git a/src/calibre/gui2/tag_browser/model.py b/src/calibre/gui2/tag_browser/model.py index c39026859a..2d6f40690b 100644 --- a/src/calibre/gui2/tag_browser/model.py +++ b/src/calibre/gui2/tag_browser/model.py @@ -8,11 +8,12 @@ __license__ = 'GPL v3' __copyright__ = '2011, Kovid Goyal ' __docformat__ = 'restructuredtext en' -import traceback, cPickle, copy +import traceback, cPickle, copy, os from PyQt4.Qt import (QAbstractItemModel, QIcon, QVariant, QFont, Qt, QMimeData, QModelIndex, pyqtSignal, QObject) +from calibre.constants import config_dir from calibre.gui2 import NONE, gprefs, config, error_dialog from calibre.library.database2 import Tag from calibre.utils.config import tweaks @@ -213,6 +214,11 @@ class TagsModel(QAbstractItemModel): # {{{ for key in category_icon_map: iconmap[key] = QIcon(I(category_icon_map[key])) self.category_icon_map = TagsIcons(iconmap) + self.category_custom_icons = dict() + for k, v in gprefs['tags_browser_category_icons'].iteritems(): + icon = QIcon(os.path.join(config_dir, 'tb_icons', v)) + if len(icon.availableSizes()) > 0: + self.category_custom_icons[k] = icon self.categories_with_ratings = ['authors', 'series', 'publisher', 'tags'] self.icon_state_map = [None, QIcon(I('plus.png')), QIcon(I('plusplus.png')), QIcon(I('minus.png')), QIcon(I('minusminus.png'))] @@ -231,6 +237,23 @@ class TagsModel(QAbstractItemModel): # {{{ def gui_parent(self): return QObject.parent(self) + def set_custom_category_icon(self, key, path): + d = gprefs['tags_browser_category_icons'] + if path: + d[key] = path + self.category_custom_icons[key] = QIcon(os.path.join(config_dir, + 'tb_icons', path)) + else: + if key in d: + path = os.path.join(config_dir, 'tb_icons', d[key]) + try: + os.remove(path) + except: + pass + del d[key] + del self.category_custom_icons[key] + gprefs['tags_browser_category_icons'] = d + def reread_collapse_model(self, state_map, rebuild=True): if gprefs['tags_browser_collapse_at'] == 0: self.collapse_model = 'disable' @@ -304,13 +327,18 @@ class TagsModel(QAbstractItemModel): # {{{ continue is_gst = False if key.startswith('@') and key[1:] in gst: - tt = _(u'The grouped search term name is "{0}"').format(key[1:]) + tt = _(u'The grouped search term name is "{0}"').format(key) is_gst = True elif key == 'news': tt = '' else: tt = _(u'The lookup/search name is "{0}"').format(key) + if self.category_custom_icons.get(key, None) is None: + self.category_custom_icons[key] = ( + self.category_icon_map['gst'] if is_gst else + self.category_icon_map.get(key, self.category_icon_map['custom:'])) + if key.startswith('@'): path_parts = [p for p in key.split('.')] path = '' @@ -319,14 +347,12 @@ class TagsModel(QAbstractItemModel): # {{{ for i,p in enumerate(path_parts): path += p if path not in category_node_map: - icon = self.category_icon_map['gst'] if is_gst else \ - self.category_icon_map[key] node = self.create_node(parent=last_category_node, - data=p[1:] if i == 0 else p, - category_icon=icon, - tooltip=tt if path == key else path, - category_key=path, - icon_map=self.icon_state_map) + data=p[1:] if i == 0 else p, + category_icon=self.category_custom_icons[key], + tooltip=tt if path == key else path, + category_key=path, + icon_map=self.icon_state_map) last_category_node = node category_node_map[path] = node self.category_nodes.append(node) @@ -343,7 +369,7 @@ class TagsModel(QAbstractItemModel): # {{{ else: node = self.create_node(parent=self.root_item, data=self.categories[key], - category_icon=self.category_icon_map[key], + category_icon=self.category_custom_icons[key], tooltip=tt, category_key=key, icon_map=self.icon_state_map) node.is_gst = False @@ -504,6 +530,7 @@ class TagsModel(QAbstractItemModel): # {{{ if (not tag.is_hierarchical) and (in_uc or (fm['is_custom'] and fm['display'].get('is_names', False)) or not category_is_hierarchical or len(components) == 1): + tag.icon = self.category_custom_icons[key] n = self.create_node(parent=node_parent, data=tag, tooltip=tt, icon_map=self.icon_state_map) if tag.id_set is not None: @@ -540,6 +567,7 @@ class TagsModel(QAbstractItemModel): # {{{ t.is_hierarchical = \ '5state' if t.category != 'search' else '3state' t.name = comp + t.icon = self.category_custom_icons[key] node_parent = self.create_node(parent=node_parent, data=t, tooltip=tt, icon_map=self.icon_state_map) child_map[(comp,tag.category)] = node_parent diff --git a/src/calibre/gui2/tag_browser/view.py b/src/calibre/gui2/tag_browser/view.py index a9882a1a35..ac9a937b65 100644 --- a/src/calibre/gui2/tag_browser/view.py +++ b/src/calibre/gui2/tag_browser/view.py @@ -7,7 +7,7 @@ __license__ = 'GPL v3' __copyright__ = '2011, Kovid Goyal ' __docformat__ = 'restructuredtext en' -import cPickle +import cPickle, os from functools import partial from itertools import izip @@ -15,9 +15,11 @@ from PyQt4.Qt import (QStyledItemDelegate, Qt, QTreeView, pyqtSignal, QSize, QIcon, QApplication, QMenu, QPoint, QModelIndex, QToolTip, QCursor, QDrag) +from calibre import sanitize_file_name_unicode +from calibre.constants import config_dir from calibre.gui2.tag_browser.model import (TagTreeItem, TAG_SEARCH_STATES, TagsModel) -from calibre.gui2 import config, gprefs +from calibre.gui2 import config, gprefs, choose_files, pixmap_to_data from calibre.utils.search_query_parser import saved_searches from calibre.utils.icu import sort_key @@ -296,6 +298,33 @@ class TagsView(QTreeView): # {{{ if not action: return try: + if action == 'set_icon': + try: + path = choose_files(self, 'choose_category_icon', + _('Change Icon for: %s')%key, filters=[ + ('Images', ['png', 'gif', 'jpg', 'jpeg'])], + all_files=False, select_only_single_file=True) + if path: + path = path[0] + p = QIcon(path).pixmap(QSize(128, 128)) + d = os.path.join(config_dir, 'tb_icons') + if not os.path.exists(d): + os.makedirs(d) + with open(os.path.join(d, 'icon_'+ + sanitize_file_name_unicode(key)+'.png'), 'wb') as f: + f.write(pixmap_to_data(p, format='PNG')) + path = os.path.basename(f.name) + self._model.set_custom_category_icon(key, unicode(path)) + self.recount() + except: + import traceback + traceback.print_exc() + return + if action == 'clear_icon': + self._model.set_custom_category_icon(key, None) + self.recount() + return + if action == 'edit_item': self.edit(index) return @@ -533,6 +562,12 @@ class TagsView(QTreeView): # {{{ partial(self.context_menu_handler, action='manage_searches', category=tag.name if tag else None)) + self.context_menu.addSeparator() + self.context_menu.addAction(_('Change category icon'), + partial(self.context_menu_handler, action='set_icon', key=key)) + self.context_menu.addAction(_('Restore default icon'), + partial(self.context_menu_handler, action='clear_icon', key=key)) + # Always show the user categories editor self.context_menu.addSeparator() if key.startswith('@') and \ @@ -551,6 +586,7 @@ class TagsView(QTreeView): # {{{ self.context_menu.addAction(_('Show all categories'), partial(self.context_menu_handler, action='defaults')) + m = self.context_menu.addMenu(_('Change sub-categorization scheme')) da = m.addAction(_('Disable'), partial(self.context_menu_handler, action='categorization', category='disable')) diff --git a/src/calibre/gui2/tools.py b/src/calibre/gui2/tools.py index f4ee92b565..98a59ccdd5 100644 --- a/src/calibre/gui2/tools.py +++ b/src/calibre/gui2/tools.py @@ -88,20 +88,35 @@ def convert_single_ebook(parent, db, book_ids, auto_conversion=False, # {{{ changed = True d.break_cycles() - except NoSupportedInputFormats: - bad.append(book_id) + except NoSupportedInputFormats as nsif: + bad.append((book_id, nsif.available_formats)) if bad and show_no_format_warning: - res = [] - for id in bad: - title = db.title(id, True) - res.append('%s'%title) + if len(bad) == 1 and not bad[0][1]: + title = db.title(bad[0][0], True) + warning_dialog(parent, _('Could not convert'), '

'+ + _('Could not convert %s as it has no ebook files. If you ' + 'think it should have files, but calibre is not finding ' + 'them, that is most likely because you moved the book\'s ' + 'files around outside of calibre. You will need to find those files ' + 'and re-add them to calibre.')%title, show=True) + else: + res = [] + for id, available_formats in bad: + title = db.title(id, True) + if available_formats: + msg = _('No supported formats (Available formats: %s)')%( + ', '.join(available_formats)) + else: + msg = _('This book has no actual ebook files') + res.append('%s - %s'%(title, msg)) - msg = '%s' % '\n'.join(res) - warning_dialog(parent, _('Could not convert some books'), - _('Could not convert %(num)d of %(tot)d books, because no suitable source' - ' format was found.') % dict(num=len(res), tot=total), - msg).exec_() + + msg = '%s' % '\n'.join(res) + warning_dialog(parent, _('Could not convert some books'), + _('Could not convert %(num)d of %(tot)d books, because no supported source' + ' formats were found.') % dict(num=len(res), tot=total), + msg).exec_() return jobs, changed, bad # }}} diff --git a/src/calibre/gui2/ui.py b/src/calibre/gui2/ui.py index db64969179..65993ff31c 100644 --- a/src/calibre/gui2/ui.py +++ b/src/calibre/gui2/ui.py @@ -155,7 +155,8 @@ class Main(MainWindow, MainWindowMixin, DeviceMixin, EmailMixin, # {{{ acmap[ac.name] = ac def load_store_plugins(self): - self.istores = OrderedDict() + from calibre.gui2.store.loader import Stores + self.istores = Stores() for store in available_store_plugins(): if self.opts.ignore_plugins and store.plugin_path is not None: continue @@ -169,6 +170,7 @@ class Main(MainWindow, MainWindowMixin, DeviceMixin, EmailMixin, # {{{ if store.plugin_path is None: raise continue + self.istores.builtins_loaded() def init_istore(self, store): st = store.load_actual_plugin(self) @@ -790,6 +792,7 @@ class Main(MainWindow, MainWindowMixin, DeviceMixin, EmailMixin, # {{{ except KeyboardInterrupt: pass time.sleep(2) + self.istores.join() self.hide_windows() # Do not report any errors that happen after the shutdown sys.excepthook = sys.__excepthook__ diff --git a/src/calibre/gui2/viewer/documentview.py b/src/calibre/gui2/viewer/documentview.py index 69704de1c7..6f6f202619 100644 --- a/src/calibre/gui2/viewer/documentview.py +++ b/src/calibre/gui2/viewer/documentview.py @@ -544,7 +544,7 @@ class DocumentView(QWebView): # {{{ self.goto_location_action.setMenu(self.goto_location_menu) self.grabGesture(Qt.SwipeGesture) - self.restore_fonts_action = QAction(_('Normal font size'), self) + self.restore_fonts_action = QAction(_('Default font size'), self) self.restore_fonts_action.setCheckable(True) self.restore_fonts_action.triggered.connect(self.restore_font_size) diff --git a/src/calibre/gui2/viewer/main.py b/src/calibre/gui2/viewer/main.py index 0ca35ebf46..903f41eeb5 100644 --- a/src/calibre/gui2/viewer/main.py +++ b/src/calibre/gui2/viewer/main.py @@ -697,11 +697,11 @@ class EbookViewer(MainWindow, Ui_EbookViewer): self.view.shrink_fonts() def magnification_changed(self, val): - tt = _('Make font size %(which)s\nCurrent magnification: %(mag).1f') + tt = _('%(which)s font size\nCurrent magnification: %(mag).1f') self.action_font_size_larger.setToolTip( - tt %dict(which=_('larger'), mag=val)) + tt %dict(which=_('Increase'), mag=val)) self.action_font_size_smaller.setToolTip( - tt %dict(which=_('smaller'), mag=val)) + tt %dict(which=_('Decrease'), mag=val)) self.action_font_size_larger.setEnabled(self.view.multiplier < 3) self.action_font_size_smaller.setEnabled(self.view.multiplier > 0.2) @@ -758,7 +758,7 @@ class EbookViewer(MainWindow, Ui_EbookViewer): self.view.scroll_to(frag) else: # Scroll to top - self.view.scroll_to('#') + self.view.scroll_to(0) if self.view.document.ypos == oldpos: # If we are coming from goto_next_section() call this will # cause another goto next section call with the next toc diff --git a/src/calibre/gui2/viewer/main.ui b/src/calibre/gui2/viewer/main.ui index d6deb315b2..d6eb7e2fd9 100644 --- a/src/calibre/gui2/viewer/main.ui +++ b/src/calibre/gui2/viewer/main.ui @@ -198,7 +198,7 @@ :/images/font_size_larger.png:/images/font_size_larger.png - Font size larger + Increase font size @@ -207,7 +207,7 @@ :/images/font_size_smaller.png:/images/font_size_smaller.png - Font size smaller + Decrease font size diff --git a/src/calibre/gui2/wizard/__init__.py b/src/calibre/gui2/wizard/__init__.py index b5d71dd9aa..1cdcb85d4c 100644 --- a/src/calibre/gui2/wizard/__init__.py +++ b/src/calibre/gui2/wizard/__init__.py @@ -197,7 +197,7 @@ class NookColor(Nook): class NookTablet(NookColor): id = 'nook_tablet' - name = 'Nook Tablet' + name = 'Nook Tablet/HD' class CybookG3(Device): @@ -245,6 +245,13 @@ class PocketBook900(PocketBook): id = 'pocketbook900' output_profile = 'pocketbook_900' +class PocketBookPro912(PocketBook): + + name = 'PocketBook Pro 912' + id = 'pocketbookpro912' + output_profile = 'pocketbook_pro_912' + + class iPhone(Device): name = 'iPhone/iTouch' diff --git a/src/calibre/library/caches.py b/src/calibre/library/caches.py index 0162258764..507305528d 100644 --- a/src/calibre/library/caches.py +++ b/src/calibre/library/caches.py @@ -1106,6 +1106,7 @@ class SortKeyGenerator(object): self.library_order = tweaks['title_series_sorting'] == 'library_order' self.data = data self.string_sort_key = sort_key + self.lang_idx = field_metadata['languages']['rec_index'] def __call__(self, record): values = tuple(self.itervals(self.data[record])) @@ -1159,7 +1160,12 @@ class SortKeyGenerator(object): val = ('', 1) else: if self.library_order: - val = title_sort(val) + try: + lang = record[self.lang_idx].partition(u',')[0] + except (AttributeError, ValueError, KeyError, + IndexError, TypeError): + lang = None + val = title_sort(val, order='library_order', lang=lang) sidx_fm = self.field_metadata[name + '_index'] sidx = record[sidx_fm['rec_index']] val = (self.string_sort_key(val), sidx) diff --git a/src/calibre/library/catalogs/csv_xml.py b/src/calibre/library/catalogs/csv_xml.py index fd2bb5113b..c594a346d4 100644 --- a/src/calibre/library/catalogs/csv_xml.py +++ b/src/calibre/library/catalogs/csv_xml.py @@ -133,8 +133,8 @@ class CSV_XML(CatalogPlugin): elif field in ['authors', 'tags']: item = ', '.join(item) elif field == 'isbn': - # Could be 9, 10 or 13 digits - item = u'%s' % re.sub(r'[\D]', '', item) + # Could be 9, 10 or 13 digits, with hyphens, possibly ending in 'X' + item = u'%s' % re.sub(r'[^\dX-]', '', item) elif field in ['pubdate', 'timestamp']: item = isoformat(item) elif field == 'comments': diff --git a/src/calibre/library/catalogs/epub_mobi.py b/src/calibre/library/catalogs/epub_mobi.py index 9a7e728220..96290601cd 100644 --- a/src/calibre/library/catalogs/epub_mobi.py +++ b/src/calibre/library/catalogs/epub_mobi.py @@ -17,7 +17,7 @@ from calibre.ebooks import calibre_cover from calibre.library import current_library_name from calibre.library.catalogs import AuthorSortMismatchException, EmptyCatalogException from calibre.ptempfile import PersistentTemporaryFile -from calibre.utils.localization import get_lang +from calibre.utils.localization import calibre_langcode_to_name, canonicalize_lang, get_lang Option = namedtuple('Option', 'option, default, dest, action, help') @@ -223,7 +223,8 @@ class EPUB_MOBI(CatalogPlugin): self.fmt, 'for %s ' % opts.output_profile if opts.output_profile else '', 'CLI' if opts.cli_environment else 'GUI', - get_lang())) + calibre_langcode_to_name(canonicalize_lang(get_lang()), localize=False)) + ) # If exclude_genre is blank, assume user wants all tags as genres if opts.exclude_genre.strip() == '': diff --git a/src/calibre/library/catalogs/epub_mobi_builder.py b/src/calibre/library/catalogs/epub_mobi_builder.py index 65ed89da5c..8f27db61be 100644 --- a/src/calibre/library/catalogs/epub_mobi_builder.py +++ b/src/calibre/library/catalogs/epub_mobi_builder.py @@ -663,9 +663,9 @@ class CatalogBuilder(object): # Hack to force the cataloged leading letter to be # an unadorned character if the accented version sorts before the unaccented exceptions = { - u'Ä': u'A', - u'Ö': u'O', - u'Ü': u'U' + u'Ä': u'A', + u'Ö': u'O', + u'Ü': u'U' } if key is not None: @@ -3473,7 +3473,7 @@ class CatalogBuilder(object): self.play_order += 1 navLabelTag = Tag(ncx_soup, 'navLabel') textTag = Tag(ncx_soup, 'text') - if len(authors_by_letter[1]) > 1: + if authors_by_letter[1] == self.SYMBOLS: fmt_string = _(u"Authors beginning with %s") else: fmt_string = _(u"Authors beginning with '%s'") @@ -4422,12 +4422,12 @@ class CatalogBuilder(object): Generate a legal XHTML anchor from unicode character. Args: - c (unicode): character + c (unicode): character(s) Return: - (str): legal XHTML anchor string of unicode charactar name + (str): legal XHTML anchor string of unicode character name """ - fullname = unicodedata.name(unicode(c)) + fullname = u''.join(unicodedata.name(unicode(cc)) for cc in c) terms = fullname.split() return "_".join(terms) diff --git a/src/calibre/library/coloring.py b/src/calibre/library/coloring.py index 4847a48c7d..9366b3e5d7 100644 --- a/src/calibre/library/coloring.py +++ b/src/calibre/library/coloring.py @@ -11,6 +11,8 @@ __docformat__ = 'restructuredtext en' import binascii, re, json from textwrap import dedent +color_row_key = '*row' + class Rule(object): # {{{ SIGNATURE = '# BasicColorRule():' @@ -205,6 +207,7 @@ def conditionable_columns(fm): yield key def displayable_columns(fm): + yield color_row_key for key in fm.displayable_field_keys(): if key not in ('sort', 'author_sort', 'comments', 'formats', 'identifiers', 'path'): diff --git a/src/calibre/library/custom_columns.py b/src/calibre/library/custom_columns.py index 2401ef3026..d65eaa8ecc 100644 --- a/src/calibre/library/custom_columns.py +++ b/src/calibre/library/custom_columns.py @@ -532,6 +532,8 @@ class CustomColumns(object): if data['datatype'] == 'series' and extra is None: (val, extra) = self._get_series_values(val) + if extra is None: + extra = 1.0 books_to_refresh = set([]) if data['normalized']: diff --git a/src/calibre/library/server/browse.py b/src/calibre/library/server/browse.py index cd5a7c0ffa..0f4878b06c 100644 --- a/src/calibre/library/server/browse.py +++ b/src/calibre/library/server/browse.py @@ -11,11 +11,11 @@ from collections import OrderedDict import cherrypy -from calibre.constants import filesystem_encoding -from calibre import isbytestring, force_unicode, fit_image, \ - prepare_string_for_xml +from calibre.constants import filesystem_encoding, config_dir +from calibre import (isbytestring, force_unicode, fit_image, + prepare_string_for_xml, sanitize_file_name2) from calibre.utils.filenames import ascii_filename -from calibre.utils.config import prefs +from calibre.utils.config import prefs, JSONConfig from calibre.utils.icu import sort_key from calibre.utils.magick import Image from calibre.library.comments import comments_to_html @@ -239,9 +239,13 @@ class BrowseServer(object): self.browse_details) connect('browse_book', base_href+'/book/{id}', self.browse_book) + connect('browse_random', base_href+'/random', + self.browse_random) connect('browse_category_icon', base_href+'/icon/{name}', self.browse_icon) + self.icon_map = JSONConfig('gui').get('tags_browser_category_icons', {}) + # Templates {{{ def browse_template(self, sort, category=True, initial_search=''): @@ -321,10 +325,18 @@ class BrowseServer(object): if not hasattr(self, '__browse_icon_cache__'): self.__browse_icon_cache__ = {} if name not in self.__browse_icon_cache__: - try: - data = I(name, data=True) - except: - raise cherrypy.HTTPError(404, 'no icon named: %r'%name) + if name.startswith('_'): + name = sanitize_file_name2(name[1:]) + try: + with open(os.path.join(config_dir, 'tb_icons', name), 'rb') as f: + data = f.read() + except: + raise cherrypy.HTTPError(404, 'no icon named: %r'%name) + else: + try: + data = I(name, data=True) + except: + raise cherrypy.HTTPError(404, 'no icon named: %r'%name) img = Image() img.load(data) width, height = img.size @@ -341,6 +353,7 @@ class BrowseServer(object): cats = [ (_('Newest'), 'newest', 'forward.png'), (_('All books'), 'allbooks', 'book.png'), + (_('Random book'), 'randombook', 'random.png'), ] def getter(x): @@ -359,7 +372,9 @@ class BrowseServer(object): if meta['is_custom'] and category not in displayed_custom_fields: continue # get the icon files - if category in category_icon_map: + if category in self.icon_map: + icon = '_'+quote(self.icon_map[category]) + elif category in category_icon_map: icon = category_icon_map[category] elif meta['is_custom']: icon = category_icon_map['custom:'] @@ -429,7 +444,11 @@ class BrowseServer(object): cat_len = len(category) if not (len(ucat) > cat_len and ucat.startswith(category+'.')): continue - icon = category_icon_map['user:'] + + if ucat in self.icon_map: + icon = '_'+quote(self.icon_map[ucat]) + else: + icon = category_icon_map['user:'] # we have a subcategory. Find any further dots (further subcats) cat_len += 1 cat = ucat[cat_len:] @@ -583,6 +602,9 @@ class BrowseServer(object): elif category == 'allbooks': raise cherrypy.InternalRedirect(prefix + '/browse/matches/allbooks/dummy') + elif category == 'randombook': + raise cherrypy.InternalRedirect(prefix + + '/browse/random') else: ans = self.browse_category(category, category_sort) @@ -869,6 +891,14 @@ class BrowseServer(object): return json.dumps(ans, ensure_ascii=False) + @Endpoint() + def browse_random(self, *args, **kwargs): + import random + book_id = random.choice(self.db.search_getting_ids( + '', self.search_restriction)) + ans = self.browse_render_details(book_id) + return self.browse_template('').format( + title='', script='book();', main=ans) @Endpoint() def browse_book(self, id=None, category_sort=None): diff --git a/src/calibre/library/server/content.py b/src/calibre/library/server/content.py index 5b723d078e..6953deff5f 100644 --- a/src/calibre/library/server/content.py +++ b/src/calibre/library/server/content.py @@ -236,7 +236,7 @@ class ContentServer(object): newmi = mi.deepcopy_metadata() newmi.template_to_attribute(mi, cpb) - if format in ('MOBI', 'EPUB'): + if format in {'MOBI', 'EPUB', 'AZW3'}: # Write the updated file from calibre.ebooks.metadata.meta import set_metadata set_metadata(fmt, newmi, format.lower()) diff --git a/src/calibre/translations/af.po b/src/calibre/translations/af.po index 1baee51fbd..a7768351ce 100644 --- a/src/calibre/translations/af.po +++ b/src/calibre/translations/af.po @@ -7,7 +7,7 @@ msgid "" msgstr "" "Project-Id-Version: calibre\n" "Report-Msgid-Bugs-To: FULL NAME \n" -"POT-Creation-Date: 2012-11-30 03:42+0000\n" +"POT-Creation-Date: 2013-01-04 05:12+0000\n" "PO-Revision-Date: 2012-08-14 16:03+0000\n" "Last-Translator: Albé Theunissen \n" "Language-Team: Afrikaans \n" @@ -15,8 +15,8 @@ msgstr "" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" "Plural-Forms: nplurals=2; plural=n != 1;\n" -"X-Launchpad-Export-Date: 2012-12-01 04:35+0000\n" -"X-Generator: Launchpad (build 16319)\n" +"X-Launchpad-Export-Date: 2013-01-05 04:42+0000\n" +"X-Generator: Launchpad (build 16393)\n" #: /home/kovid/work/calibre/src/calibre/customize/__init__.py:56 msgid "Does absolutely nothing" @@ -26,17 +26,17 @@ msgstr "Doen absolute niks" #: /home/kovid/work/calibre/src/calibre/db/cache.py:106 #: /home/kovid/work/calibre/src/calibre/db/cache.py:109 #: /home/kovid/work/calibre/src/calibre/db/cache.py:120 -#: /home/kovid/work/calibre/src/calibre/devices/android/driver.py:376 -#: /home/kovid/work/calibre/src/calibre/devices/android/driver.py:377 -#: /home/kovid/work/calibre/src/calibre/devices/hanvon/driver.py:100 -#: /home/kovid/work/calibre/src/calibre/devices/hanvon/driver.py:101 +#: /home/kovid/work/calibre/src/calibre/devices/android/driver.py:378 +#: /home/kovid/work/calibre/src/calibre/devices/android/driver.py:379 +#: /home/kovid/work/calibre/src/calibre/devices/hanvon/driver.py:114 +#: /home/kovid/work/calibre/src/calibre/devices/hanvon/driver.py:115 #: /home/kovid/work/calibre/src/calibre/devices/jetbook/driver.py:74 #: /home/kovid/work/calibre/src/calibre/devices/kindle/driver.py:77 #: /home/kovid/work/calibre/src/calibre/devices/kobo/driver.py:667 #: /home/kovid/work/calibre/src/calibre/devices/mtp/books.py:45 #: /home/kovid/work/calibre/src/calibre/devices/mtp/books.py:69 -#: /home/kovid/work/calibre/src/calibre/devices/mtp/unix/driver.py:229 -#: /home/kovid/work/calibre/src/calibre/devices/mtp/windows/driver.py:237 +#: /home/kovid/work/calibre/src/calibre/devices/mtp/unix/driver.py:234 +#: /home/kovid/work/calibre/src/calibre/devices/mtp/windows/driver.py:238 #: /home/kovid/work/calibre/src/calibre/devices/nook/driver.py:71 #: /home/kovid/work/calibre/src/calibre/devices/nook/driver.py:72 #: /home/kovid/work/calibre/src/calibre/devices/prs505/sony_cache.py:661 @@ -53,6 +53,8 @@ msgstr "Doen absolute niks" #: /home/kovid/work/calibre/src/calibre/ebooks/conversion/plugins/html_input.py:121 #: /home/kovid/work/calibre/src/calibre/ebooks/conversion/plugins/lrf_output.py:29 #: /home/kovid/work/calibre/src/calibre/ebooks/conversion/plugins/pdb_input.py:27 +#: /home/kovid/work/calibre/src/calibre/ebooks/conversion/plugins/pdf_output.py:30 +#: /home/kovid/work/calibre/src/calibre/ebooks/conversion/plugins/pdf_output.py:31 #: /home/kovid/work/calibre/src/calibre/ebooks/conversion/plugins/rtf_input.py:289 #: /home/kovid/work/calibre/src/calibre/ebooks/conversion/plugins/rtf_input.py:291 #: /home/kovid/work/calibre/src/calibre/ebooks/epub/periodical.py:140 @@ -108,8 +110,8 @@ msgstr "Doen absolute niks" #: /home/kovid/work/calibre/src/calibre/ebooks/metadata/txt.py:18 #: /home/kovid/work/calibre/src/calibre/ebooks/mobi/reader/headers.py:27 #: /home/kovid/work/calibre/src/calibre/ebooks/mobi/reader/headers.py:95 -#: /home/kovid/work/calibre/src/calibre/ebooks/mobi/reader/headers.py:153 -#: /home/kovid/work/calibre/src/calibre/ebooks/mobi/reader/headers.py:192 +#: /home/kovid/work/calibre/src/calibre/ebooks/mobi/reader/headers.py:154 +#: /home/kovid/work/calibre/src/calibre/ebooks/mobi/reader/headers.py:193 #: /home/kovid/work/calibre/src/calibre/ebooks/mobi/reader/mobi6.py:615 #: /home/kovid/work/calibre/src/calibre/ebooks/mobi/utils.py:316 #: /home/kovid/work/calibre/src/calibre/ebooks/mobi/writer2/indexer.py:463 @@ -131,11 +133,10 @@ msgstr "Doen absolute niks" #: /home/kovid/work/calibre/src/calibre/ebooks/pdb/ereader/writer.py:174 #: /home/kovid/work/calibre/src/calibre/ebooks/pdb/palmdoc/writer.py:29 #: /home/kovid/work/calibre/src/calibre/ebooks/pdb/ztxt/writer.py:27 -#: /home/kovid/work/calibre/src/calibre/ebooks/pdf/writer.py:108 -#: /home/kovid/work/calibre/src/calibre/ebooks/pdf/writer.py:109 -#: /home/kovid/work/calibre/src/calibre/gui2/__init__.py:446 -#: /home/kovid/work/calibre/src/calibre/gui2/__init__.py:454 -#: /home/kovid/work/calibre/src/calibre/gui2/actions/add.py:166 +#: /home/kovid/work/calibre/src/calibre/ebooks/pdf/render/links.py:119 +#: /home/kovid/work/calibre/src/calibre/gui2/__init__.py:447 +#: /home/kovid/work/calibre/src/calibre/gui2/__init__.py:455 +#: /home/kovid/work/calibre/src/calibre/gui2/actions/add.py:171 #: /home/kovid/work/calibre/src/calibre/gui2/actions/edit_metadata.py:411 #: /home/kovid/work/calibre/src/calibre/gui2/actions/edit_metadata.py:414 #: /home/kovid/work/calibre/src/calibre/gui2/add.py:167 @@ -145,32 +146,32 @@ msgstr "Doen absolute niks" #: /home/kovid/work/calibre/src/calibre/gui2/convert/metadata.py:124 #: /home/kovid/work/calibre/src/calibre/gui2/convert/metadata.py:143 #: /home/kovid/work/calibre/src/calibre/gui2/convert/metadata.py:145 -#: /home/kovid/work/calibre/src/calibre/gui2/device.py:1410 -#: /home/kovid/work/calibre/src/calibre/gui2/device.py:1413 -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/add_empty_book.py:55 -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/add_empty_book.py:60 +#: /home/kovid/work/calibre/src/calibre/gui2/device.py:1416 +#: /home/kovid/work/calibre/src/calibre/gui2/device.py:1419 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/add_empty_book.py:71 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/add_empty_book.py:79 #: /home/kovid/work/calibre/src/calibre/gui2/dialogs/book_info.py:128 #: /home/kovid/work/calibre/src/calibre/gui2/dialogs/comicconf.py:47 #: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk.py:825 #: /home/kovid/work/calibre/src/calibre/gui2/dialogs/scheduler.py:380 #: /home/kovid/work/calibre/src/calibre/gui2/email.py:193 #: /home/kovid/work/calibre/src/calibre/gui2/email.py:208 -#: /home/kovid/work/calibre/src/calibre/gui2/library/models.py:408 -#: /home/kovid/work/calibre/src/calibre/gui2/library/models.py:1069 -#: /home/kovid/work/calibre/src/calibre/gui2/library/models.py:1285 -#: /home/kovid/work/calibre/src/calibre/gui2/library/models.py:1288 -#: /home/kovid/work/calibre/src/calibre/gui2/library/models.py:1291 -#: /home/kovid/work/calibre/src/calibre/gui2/library/models.py:1379 +#: /home/kovid/work/calibre/src/calibre/gui2/library/models.py:439 +#: /home/kovid/work/calibre/src/calibre/gui2/library/models.py:1103 +#: /home/kovid/work/calibre/src/calibre/gui2/library/models.py:1319 +#: /home/kovid/work/calibre/src/calibre/gui2/library/models.py:1322 +#: /home/kovid/work/calibre/src/calibre/gui2/library/models.py:1325 +#: /home/kovid/work/calibre/src/calibre/gui2/library/models.py:1413 #: /home/kovid/work/calibre/src/calibre/gui2/metadata/basic_widgets.py:85 #: /home/kovid/work/calibre/src/calibre/gui2/metadata/basic_widgets.py:250 #: /home/kovid/work/calibre/src/calibre/gui2/metadata/basic_widgets.py:261 -#: /home/kovid/work/calibre/src/calibre/gui2/metadata/single.py:402 -#: /home/kovid/work/calibre/src/calibre/gui2/metadata/single_download.py:174 +#: /home/kovid/work/calibre/src/calibre/gui2/metadata/single.py:407 #: /home/kovid/work/calibre/src/calibre/gui2/metadata/single_download.py:178 +#: /home/kovid/work/calibre/src/calibre/gui2/metadata/single_download.py:182 #: /home/kovid/work/calibre/src/calibre/gui2/store/search/models.py:202 #: /home/kovid/work/calibre/src/calibre/gui2/store/stores/google_books_plugin.py:107 #: /home/kovid/work/calibre/src/calibre/gui2/viewer/main.py:191 -#: /home/kovid/work/calibre/src/calibre/library/catalogs/epub_mobi_builder.py:885 +#: /home/kovid/work/calibre/src/calibre/library/catalogs/epub_mobi_builder.py:924 #: /home/kovid/work/calibre/src/calibre/library/cli.py:244 #: /home/kovid/work/calibre/src/calibre/library/database.py:914 #: /home/kovid/work/calibre/src/calibre/library/database2.py:587 @@ -237,7 +238,7 @@ msgstr "Gebruikerskoppelvlakaksie" #: /home/kovid/work/calibre/src/calibre/gui2/preferences/main.py:197 #: /home/kovid/work/calibre/src/calibre/gui2/preferences/main.py:288 #: /home/kovid/work/calibre/src/calibre/gui2/preferences/main.py:311 -#: /home/kovid/work/calibre/src/calibre/gui2/viewer/main_ui.py:221 +#: /home/kovid/work/calibre/src/calibre/gui2/viewer/main_ui.py:228 msgid "Preferences" msgstr "Voorkeure" @@ -610,7 +611,7 @@ msgid "Control how calibre downloads ebook metadata from the net" msgstr "" #: /home/kovid/work/calibre/src/calibre/customize/builtins.py:1125 -#: /home/kovid/work/calibre/src/calibre/gui2/device_drivers/mtp_config.py:392 +#: /home/kovid/work/calibre/src/calibre/gui2/device_drivers/mtp_config.py:400 msgid "Ignored devices" msgstr "" @@ -981,27 +982,27 @@ msgstr "Ontfoutlogboek" msgid "Communicate with Android phones." msgstr "Kommunikeer met Android-fone" -#: /home/kovid/work/calibre/src/calibre/devices/android/driver.py:198 +#: /home/kovid/work/calibre/src/calibre/devices/android/driver.py:199 msgid "" "Comma separated list of directories to send e-books to on the device's " "main memory. The first one that exists will be used" msgstr "" -#: /home/kovid/work/calibre/src/calibre/devices/android/driver.py:201 +#: /home/kovid/work/calibre/src/calibre/devices/android/driver.py:202 msgid "" "Comma separated list of directories to send e-books to on the device's " "storage cards. The first one that exists will be used" msgstr "" -#: /home/kovid/work/calibre/src/calibre/devices/android/driver.py:316 +#: /home/kovid/work/calibre/src/calibre/devices/android/driver.py:318 msgid "Communicate with S60 phones." msgstr "Kommunikeer met S60-fone" -#: /home/kovid/work/calibre/src/calibre/devices/android/driver.py:335 +#: /home/kovid/work/calibre/src/calibre/devices/android/driver.py:337 msgid "Communicate with WebOS tablets." msgstr "Kommunikeer met WebOS-tablette." -#: /home/kovid/work/calibre/src/calibre/devices/apple/driver.py:61 +#: /home/kovid/work/calibre/src/calibre/devices/apple/driver.py:63 msgid "" "

If you do not want calibre to recognize your Apple iDevice when it is " "connected to your computer, click Disable Apple Driver.

To " @@ -1023,38 +1024,38 @@ msgstr "" "met iToestelle is ’n gevorderde gebruikersmodus wat nie ondersteun word " "nie

" -#: /home/kovid/work/calibre/src/calibre/devices/apple/driver.py:78 +#: /home/kovid/work/calibre/src/calibre/devices/apple/driver.py:80 msgid "Disable Apple driver" msgstr "Deaktiveer Apple-drywer" -#: /home/kovid/work/calibre/src/calibre/devices/apple/driver.py:82 +#: /home/kovid/work/calibre/src/calibre/devices/apple/driver.py:84 msgid "Enable Apple driver" msgstr "Aktiveer Apple-drywer" -#: /home/kovid/work/calibre/src/calibre/devices/apple/driver.py:118 +#: /home/kovid/work/calibre/src/calibre/devices/apple/driver.py:121 msgid "Use Series as Category in iTunes/iBooks" msgstr "Gebruik Reeks as Kategorie in iTunes/iBooks" -#: /home/kovid/work/calibre/src/calibre/devices/apple/driver.py:119 +#: /home/kovid/work/calibre/src/calibre/devices/apple/driver.py:122 msgid "Enable to use the series name as the iTunes Genre, iBooks Category" msgstr "" -#: /home/kovid/work/calibre/src/calibre/devices/apple/driver.py:121 +#: /home/kovid/work/calibre/src/calibre/devices/apple/driver.py:124 msgid "Cache covers from iTunes/iBooks" msgstr "" -#: /home/kovid/work/calibre/src/calibre/devices/apple/driver.py:123 +#: /home/kovid/work/calibre/src/calibre/devices/apple/driver.py:126 msgid "Enable to cache and display covers from iTunes/iBooks" msgstr "" -#: /home/kovid/work/calibre/src/calibre/devices/apple/driver.py:124 +#: /home/kovid/work/calibre/src/calibre/devices/apple/driver.py:127 #, python-format msgid "" "\"Copy files to iTunes Media folder %s\" is enabled in iTunes " "Preferences|Advanced" msgstr "" -#: /home/kovid/work/calibre/src/calibre/devices/apple/driver.py:126 +#: /home/kovid/work/calibre/src/calibre/devices/apple/driver.py:129 msgid "" "

This setting should match your iTunes Preferences|Advanced " "setting.

Disabling will store copies of books transferred to iTunes in " @@ -1062,61 +1063,61 @@ msgid "" "is configured to store copies in your iTunes Media folder.

" msgstr "" -#: /home/kovid/work/calibre/src/calibre/devices/apple/driver.py:190 +#: /home/kovid/work/calibre/src/calibre/devices/apple/driver.py:196 msgid "Apple device" msgstr "Apple-toestel" -#: /home/kovid/work/calibre/src/calibre/devices/apple/driver.py:192 +#: /home/kovid/work/calibre/src/calibre/devices/apple/driver.py:198 msgid "Communicate with iTunes/iBooks." msgstr "Kommunikeer met iTunes/iBooks." -#: /home/kovid/work/calibre/src/calibre/devices/apple/driver.py:206 -msgid "Apple device detected, launching iTunes, please wait ..." -msgstr "Apple-toestel bespeur, laai tans iTunes, wag asb. …" +#: /home/kovid/work/calibre/src/calibre/devices/apple/driver.py:212 +msgid "Apple iDevice detected, launching iTunes, please wait ..." +msgstr "" -#: /home/kovid/work/calibre/src/calibre/devices/apple/driver.py:208 +#: /home/kovid/work/calibre/src/calibre/devices/apple/driver.py:214 msgid "" "Cannot copy books directly from iDevice. Drag from iTunes Library to " "desktop, then add to calibre's Library window." msgstr "" -#: /home/kovid/work/calibre/src/calibre/devices/apple/driver.py:211 +#: /home/kovid/work/calibre/src/calibre/devices/apple/driver.py:217 msgid "" -"Unsupported direct connect mode. See " +"*** Unsupported direct connect mode. See " "http://www.mobileread.com/forums/showthread.php?t=118559 for instructions on " -"using 'Connect to iTunes'" +"using 'Connect to iTunes' ***" msgstr "" -#: /home/kovid/work/calibre/src/calibre/devices/apple/driver.py:215 +#: /home/kovid/work/calibre/src/calibre/devices/apple/driver.py:221 msgid "" "

Unable to communicate with iTunes.

Refer to this " "forum post for more information.

" msgstr "" -#: /home/kovid/work/calibre/src/calibre/devices/apple/driver.py:382 -#: /home/kovid/work/calibre/src/calibre/devices/apple/driver.py:385 +#: /home/kovid/work/calibre/src/calibre/devices/apple/driver.py:375 +#: /home/kovid/work/calibre/src/calibre/devices/apple/driver.py:378 msgid "Updating device metadata listing..." msgstr "" -#: /home/kovid/work/calibre/src/calibre/devices/apple/driver.py:462 -#: /home/kovid/work/calibre/src/calibre/devices/apple/driver.py:502 -#: /home/kovid/work/calibre/src/calibre/devices/apple/driver.py:1117 -#: /home/kovid/work/calibre/src/calibre/devices/apple/driver.py:1163 -#: /home/kovid/work/calibre/src/calibre/devices/apple/driver.py:3262 -#: /home/kovid/work/calibre/src/calibre/devices/apple/driver.py:3304 +#: /home/kovid/work/calibre/src/calibre/devices/apple/driver.py:456 +#: /home/kovid/work/calibre/src/calibre/devices/apple/driver.py:497 +#: /home/kovid/work/calibre/src/calibre/devices/apple/driver.py:1143 +#: /home/kovid/work/calibre/src/calibre/devices/apple/driver.py:1190 +#: /home/kovid/work/calibre/src/calibre/devices/apple/driver.py:3292 +#: /home/kovid/work/calibre/src/calibre/devices/apple/driver.py:3334 #, python-format msgid "%(num)d of %(tot)d" msgstr "" -#: /home/kovid/work/calibre/src/calibre/devices/apple/driver.py:510 -#: /home/kovid/work/calibre/src/calibre/devices/apple/driver.py:1168 -#: /home/kovid/work/calibre/src/calibre/devices/apple/driver.py:3311 +#: /home/kovid/work/calibre/src/calibre/devices/apple/driver.py:505 +#: /home/kovid/work/calibre/src/calibre/devices/apple/driver.py:1195 +#: /home/kovid/work/calibre/src/calibre/devices/apple/driver.py:3341 #: /home/kovid/work/calibre/src/calibre/gui2/ebook_download.py:110 msgid "finished" msgstr "Klaar" -#: /home/kovid/work/calibre/src/calibre/devices/apple/driver.py:702 +#: /home/kovid/work/calibre/src/calibre/devices/apple/driver.py:697 msgid "" "Some books not found in iTunes database.\n" "Delete using the iBooks app.\n" @@ -1126,7 +1127,7 @@ msgstr "" "Skrap boeke d.m.v. die iBooks-toep.\n" "Klik ‘Toon Detail’ vir ’n lys." -#: /home/kovid/work/calibre/src/calibre/devices/apple/driver.py:1080 +#: /home/kovid/work/calibre/src/calibre/devices/apple/driver.py:1105 msgid "" "Some cover art could not be converted.\n" "Click 'Show Details' for a list." @@ -1134,22 +1135,22 @@ msgstr "" "Sommige dekbladkuns kom nie omgeskakel word nie.\n" "Klik ‘Toon Detail’ vir ’n lys." -#: /home/kovid/work/calibre/src/calibre/devices/apple/driver.py:2785 +#: /home/kovid/work/calibre/src/calibre/devices/apple/driver.py:2816 #: /home/kovid/work/calibre/src/calibre/devices/nook/driver.py:106 #: /home/kovid/work/calibre/src/calibre/devices/prs505/sony_cache.py:448 #: /home/kovid/work/calibre/src/calibre/devices/prs505/sony_cache.py:471 #: /home/kovid/work/calibre/src/calibre/devices/prst1/driver.py:773 #: /home/kovid/work/calibre/src/calibre/devices/prst1/driver.py:792 -#: /home/kovid/work/calibre/src/calibre/devices/smart_device_app/driver.py:392 -#: /home/kovid/work/calibre/src/calibre/devices/smart_device_app/driver.py:398 -#: /home/kovid/work/calibre/src/calibre/devices/smart_device_app/driver.py:429 +#: /home/kovid/work/calibre/src/calibre/devices/smart_device_app/driver.py:394 +#: /home/kovid/work/calibre/src/calibre/devices/smart_device_app/driver.py:400 +#: /home/kovid/work/calibre/src/calibre/devices/smart_device_app/driver.py:431 #: /home/kovid/work/calibre/src/calibre/devices/utils.py:80 #: /home/kovid/work/calibre/src/calibre/devices/utils.py:84 #: /home/kovid/work/calibre/src/calibre/devices/utils.py:116 #: /home/kovid/work/calibre/src/calibre/gui2/actions/fetch_news.py:73 #: /home/kovid/work/calibre/src/calibre/gui2/dialogs/scheduler.py:469 -#: /home/kovid/work/calibre/src/calibre/gui2/tag_browser/model.py:1197 -#: /home/kovid/work/calibre/src/calibre/gui2/tag_browser/model.py:1199 +#: /home/kovid/work/calibre/src/calibre/gui2/tag_browser/model.py:1225 +#: /home/kovid/work/calibre/src/calibre/gui2/tag_browser/model.py:1227 #: /home/kovid/work/calibre/src/calibre/library/database2.py:372 #: /home/kovid/work/calibre/src/calibre/library/database2.py:385 #: /home/kovid/work/calibre/src/calibre/library/database2.py:3386 @@ -1157,13 +1158,13 @@ msgstr "" msgid "News" msgstr "Nuus" -#: /home/kovid/work/calibre/src/calibre/devices/apple/driver.py:2786 +#: /home/kovid/work/calibre/src/calibre/devices/apple/driver.py:2817 #: /home/kovid/work/calibre/src/calibre/library/database2.py:3342 #: /home/kovid/work/calibre/src/calibre/library/database2.py:3360 msgid "Catalog" msgstr "Katalogus" -#: /home/kovid/work/calibre/src/calibre/devices/apple/driver.py:3154 +#: /home/kovid/work/calibre/src/calibre/devices/apple/driver.py:3184 msgid "Communicate with iTunes." msgstr "Kommunikeer met iTunes." @@ -1212,7 +1213,7 @@ msgstr "Bambook" #: /home/kovid/work/calibre/src/calibre/devices/kobo/driver.py:1325 #: /home/kovid/work/calibre/src/calibre/devices/kobo/driver.py:1329 #: /home/kovid/work/calibre/src/calibre/devices/kobo/driver.py:1333 -#: /home/kovid/work/calibre/src/calibre/devices/kobo/driver.py:1683 +#: /home/kovid/work/calibre/src/calibre/devices/kobo/driver.py:1687 #: /home/kovid/work/calibre/src/calibre/devices/prst1/driver.py:155 #: /home/kovid/work/calibre/src/calibre/devices/usbms/driver.py:144 #: /home/kovid/work/calibre/src/calibre/devices/usbms/driver.py:147 @@ -1226,9 +1227,9 @@ msgstr "Kry lys van boeke op toestel…" #: /home/kovid/work/calibre/src/calibre/devices/bambook/driver.py:264 #: /home/kovid/work/calibre/src/calibre/devices/bambook/driver.py:268 #: /home/kovid/work/calibre/src/calibre/devices/bambook/driver.py:324 -#: /home/kovid/work/calibre/src/calibre/devices/mtp/driver.py:367 -#: /home/kovid/work/calibre/src/calibre/devices/smart_device_app/driver.py:1095 +#: /home/kovid/work/calibre/src/calibre/devices/mtp/driver.py:391 #: /home/kovid/work/calibre/src/calibre/devices/smart_device_app/driver.py:1097 +#: /home/kovid/work/calibre/src/calibre/devices/smart_device_app/driver.py:1099 #: /home/kovid/work/calibre/src/calibre/devices/usbms/driver.py:277 #: /home/kovid/work/calibre/src/calibre/devices/usbms/driver.py:279 msgid "Transferring books to device..." @@ -1238,9 +1239,9 @@ msgstr "Dra boeke na toestel oor…" #: /home/kovid/work/calibre/src/calibre/devices/bambook/driver.py:344 #: /home/kovid/work/calibre/src/calibre/devices/kobo/driver.py:491 #: /home/kovid/work/calibre/src/calibre/devices/kobo/driver.py:525 -#: /home/kovid/work/calibre/src/calibre/devices/mtp/driver.py:404 -#: /home/kovid/work/calibre/src/calibre/devices/smart_device_app/driver.py:1108 -#: /home/kovid/work/calibre/src/calibre/devices/smart_device_app/driver.py:1119 +#: /home/kovid/work/calibre/src/calibre/devices/mtp/driver.py:430 +#: /home/kovid/work/calibre/src/calibre/devices/smart_device_app/driver.py:1110 +#: /home/kovid/work/calibre/src/calibre/devices/smart_device_app/driver.py:1121 #: /home/kovid/work/calibre/src/calibre/devices/usbms/driver.py:301 #: /home/kovid/work/calibre/src/calibre/devices/usbms/driver.py:332 msgid "Adding books to device metadata listing..." @@ -1248,8 +1249,8 @@ msgstr "Voeg boeke toe tot toestel se metadatalys…" #: /home/kovid/work/calibre/src/calibre/devices/bambook/driver.py:352 #: /home/kovid/work/calibre/src/calibre/devices/bambook/driver.py:354 -#: /home/kovid/work/calibre/src/calibre/devices/hanvon/driver.py:115 -#: /home/kovid/work/calibre/src/calibre/devices/hanvon/driver.py:126 +#: /home/kovid/work/calibre/src/calibre/devices/hanvon/driver.py:129 +#: /home/kovid/work/calibre/src/calibre/devices/hanvon/driver.py:140 #: /home/kovid/work/calibre/src/calibre/devices/kobo/driver.py:440 #: /home/kovid/work/calibre/src/calibre/devices/kobo/driver.py:472 #: /home/kovid/work/calibre/src/calibre/devices/kobo/driver.py:615 @@ -1262,8 +1263,8 @@ msgstr "Verwyder boeke van toestel…" #: /home/kovid/work/calibre/src/calibre/devices/bambook/driver.py:374 #: /home/kovid/work/calibre/src/calibre/devices/kobo/driver.py:479 #: /home/kovid/work/calibre/src/calibre/devices/kobo/driver.py:486 -#: /home/kovid/work/calibre/src/calibre/devices/smart_device_app/driver.py:1157 -#: /home/kovid/work/calibre/src/calibre/devices/smart_device_app/driver.py:1163 +#: /home/kovid/work/calibre/src/calibre/devices/smart_device_app/driver.py:1159 +#: /home/kovid/work/calibre/src/calibre/devices/smart_device_app/driver.py:1165 #: /home/kovid/work/calibre/src/calibre/devices/usbms/driver.py:366 #: /home/kovid/work/calibre/src/calibre/devices/usbms/driver.py:371 msgid "Removing books from device metadata listing..." @@ -1393,27 +1394,31 @@ msgstr "" msgid "Communicate with the Hanvon N520 eBook reader." msgstr "Kommunikeer met die Hanvon N520 eBoek-leser." -#: /home/kovid/work/calibre/src/calibre/devices/hanvon/driver.py:47 +#: /home/kovid/work/calibre/src/calibre/devices/hanvon/driver.py:48 +msgid "Communicate with the Kibano eBook reader." +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/devices/hanvon/driver.py:61 msgid "Communicate with The Book reader." msgstr "Kommunikeer met The Book-leser." -#: /home/kovid/work/calibre/src/calibre/devices/hanvon/driver.py:59 +#: /home/kovid/work/calibre/src/calibre/devices/hanvon/driver.py:73 msgid "Communicate with the Libre Air reader." msgstr "Kommunikeer met die Libre Air-leser." -#: /home/kovid/work/calibre/src/calibre/devices/hanvon/driver.py:72 +#: /home/kovid/work/calibre/src/calibre/devices/hanvon/driver.py:86 msgid "Communicate with the SpringDesign Alex eBook reader." msgstr "Kommunikeer met die SpringDesign Alex eBoek-leser" -#: /home/kovid/work/calibre/src/calibre/devices/hanvon/driver.py:132 +#: /home/kovid/work/calibre/src/calibre/devices/hanvon/driver.py:146 msgid "Communicate with the Azbooka" msgstr "Kommunikeer met die Azbooka" -#: /home/kovid/work/calibre/src/calibre/devices/hanvon/driver.py:151 +#: /home/kovid/work/calibre/src/calibre/devices/hanvon/driver.py:165 msgid "Communicate with the Elonex EB 511 eBook reader." msgstr "Kommunikeer met die Elonex EB 511 eBoek-leser." -#: /home/kovid/work/calibre/src/calibre/devices/hanvon/driver.py:171 +#: /home/kovid/work/calibre/src/calibre/devices/hanvon/driver.py:185 msgid "Communicate with the Cybook Odyssey eBook reader." msgstr "Kommunikeer met die Cybook Odyssey eBoek-leser." @@ -1647,7 +1652,7 @@ msgid "" msgstr "" #: /home/kovid/work/calibre/src/calibre/devices/kobo/driver.py:646 -#: /home/kovid/work/calibre/src/calibre/gui2/actions/add.py:393 +#: /home/kovid/work/calibre/src/calibre/gui2/actions/add.py:404 msgid "Not Implemented" msgstr "Nie geïmplementeer nie" @@ -1845,84 +1850,84 @@ msgstr "" msgid "Communicate with MTP devices" msgstr "" -#: /home/kovid/work/calibre/src/calibre/devices/mtp/driver.py:143 -#: /home/kovid/work/calibre/src/calibre/devices/smart_device_app/driver.py:912 +#: /home/kovid/work/calibre/src/calibre/devices/mtp/driver.py:167 +#: /home/kovid/work/calibre/src/calibre/devices/smart_device_app/driver.py:914 #: /home/kovid/work/calibre/src/calibre/devices/usbms/driver.py:95 msgid "Get device information..." msgstr "" -#: /home/kovid/work/calibre/src/calibre/devices/mtp/driver.py:166 +#: /home/kovid/work/calibre/src/calibre/devices/mtp/driver.py:190 msgid "Listing files, this can take a while" msgstr "" -#: /home/kovid/work/calibre/src/calibre/devices/mtp/driver.py:181 +#: /home/kovid/work/calibre/src/calibre/devices/mtp/driver.py:205 msgid "Reading ebook metadata" msgstr "" -#: /home/kovid/work/calibre/src/calibre/devices/mtp/driver.py:214 +#: /home/kovid/work/calibre/src/calibre/devices/mtp/driver.py:238 #, python-format msgid "Reading metadata from %s" msgstr "" -#: /home/kovid/work/calibre/src/calibre/devices/mtp/driver.py:233 +#: /home/kovid/work/calibre/src/calibre/devices/mtp/driver.py:257 msgid "Updating metadata cache on device" msgstr "" -#: /home/kovid/work/calibre/src/calibre/devices/mtp/driver.py:235 +#: /home/kovid/work/calibre/src/calibre/devices/mtp/driver.py:259 msgid "Finished reading metadata from device" msgstr "" -#: /home/kovid/work/calibre/src/calibre/devices/mtp/driver.py:393 +#: /home/kovid/work/calibre/src/calibre/devices/mtp/driver.py:419 #, python-format msgid "Transferred %s to device" msgstr "" -#: /home/kovid/work/calibre/src/calibre/devices/mtp/driver.py:395 +#: /home/kovid/work/calibre/src/calibre/devices/mtp/driver.py:421 msgid "Transfer to device finished..." msgstr "" -#: /home/kovid/work/calibre/src/calibre/devices/mtp/driver.py:416 +#: /home/kovid/work/calibre/src/calibre/devices/mtp/driver.py:442 #, python-format msgid "Added %s" msgstr "" -#: /home/kovid/work/calibre/src/calibre/devices/mtp/driver.py:418 +#: /home/kovid/work/calibre/src/calibre/devices/mtp/driver.py:444 msgid "Adding complete" msgstr "" -#: /home/kovid/work/calibre/src/calibre/devices/mtp/driver.py:434 +#: /home/kovid/work/calibre/src/calibre/devices/mtp/driver.py:460 msgid "Deleting books from device..." msgstr "" -#: /home/kovid/work/calibre/src/calibre/devices/mtp/driver.py:440 +#: /home/kovid/work/calibre/src/calibre/devices/mtp/driver.py:466 #, python-format msgid "Deleted %s" msgstr "" -#: /home/kovid/work/calibre/src/calibre/devices/mtp/driver.py:441 +#: /home/kovid/work/calibre/src/calibre/devices/mtp/driver.py:467 msgid "All books deleted" msgstr "" -#: /home/kovid/work/calibre/src/calibre/devices/mtp/driver.py:444 +#: /home/kovid/work/calibre/src/calibre/devices/mtp/driver.py:470 msgid "Removing books from metadata" msgstr "" -#: /home/kovid/work/calibre/src/calibre/devices/mtp/driver.py:456 +#: /home/kovid/work/calibre/src/calibre/devices/mtp/driver.py:482 #, python-format msgid "Removed %s" msgstr "" -#: /home/kovid/work/calibre/src/calibre/devices/mtp/driver.py:458 +#: /home/kovid/work/calibre/src/calibre/devices/mtp/driver.py:484 msgid "All books removed" msgstr "" #: /home/kovid/work/calibre/src/calibre/devices/mtp/unix/driver.py:198 -#: /home/kovid/work/calibre/src/calibre/devices/mtp/windows/driver.py:313 +#: /home/kovid/work/calibre/src/calibre/devices/mtp/windows/driver.py:315 msgid "Unknown MTP device" msgstr "" -#: /home/kovid/work/calibre/src/calibre/devices/mtp/unix/driver.py:216 -#: /home/kovid/work/calibre/src/calibre/devices/mtp/windows/driver.py:220 +#: /home/kovid/work/calibre/src/calibre/devices/mtp/unix/driver.py:217 +#: /home/kovid/work/calibre/src/calibre/devices/mtp/windows/driver.py:219 #, python-format msgid "Found object: %s" msgstr "" @@ -1938,11 +1943,6 @@ msgid "" "computer" msgstr "" -#: /home/kovid/work/calibre/src/calibre/devices/mtp/windows/driver.py:222 -#, python-format -msgid "Found id: %s" -msgstr "" - #: /home/kovid/work/calibre/src/calibre/devices/nokia/driver.py:17 msgid "Communicate with the Nokia 770 internet tablet." msgstr "Kommunikeer met die Nokia 770 internettablet." @@ -1980,12 +1980,12 @@ msgid "Comments have been removed as the SONY reader chokes on them" msgstr "" #: /home/kovid/work/calibre/src/calibre/devices/prs505/driver.py:66 -#: /home/kovid/work/calibre/src/calibre/devices/smart_device_app/driver.py:262 +#: /home/kovid/work/calibre/src/calibre/devices/smart_device_app/driver.py:264 msgid "All by title" msgstr "Alles volgens titel" #: /home/kovid/work/calibre/src/calibre/devices/prs505/driver.py:67 -#: /home/kovid/work/calibre/src/calibre/devices/smart_device_app/driver.py:263 +#: /home/kovid/work/calibre/src/calibre/devices/smart_device_app/driver.py:265 msgid "All by author" msgstr "Alles volgens outeur" @@ -2089,77 +2089,77 @@ msgid "" "multiple authors. Leave this disabled if you use Metadata Plugboards." msgstr "" -#: /home/kovid/work/calibre/src/calibre/devices/smart_device_app/driver.py:174 +#: /home/kovid/work/calibre/src/calibre/devices/smart_device_app/driver.py:176 msgid "Wireless Device" msgstr "" -#: /home/kovid/work/calibre/src/calibre/devices/smart_device_app/driver.py:178 +#: /home/kovid/work/calibre/src/calibre/devices/smart_device_app/driver.py:180 msgid "Communicate with Smart Device apps" msgstr "" -#: /home/kovid/work/calibre/src/calibre/devices/smart_device_app/driver.py:264 +#: /home/kovid/work/calibre/src/calibre/devices/smart_device_app/driver.py:266 msgid "All by something" msgstr "" -#: /home/kovid/work/calibre/src/calibre/devices/smart_device_app/driver.py:267 +#: /home/kovid/work/calibre/src/calibre/devices/smart_device_app/driver.py:269 msgid "Enable connections at startup" msgstr "" -#: /home/kovid/work/calibre/src/calibre/devices/smart_device_app/driver.py:268 +#: /home/kovid/work/calibre/src/calibre/devices/smart_device_app/driver.py:270 msgid "Check this box to allow connections when calibre starts" msgstr "" -#: /home/kovid/work/calibre/src/calibre/devices/smart_device_app/driver.py:270 +#: /home/kovid/work/calibre/src/calibre/devices/smart_device_app/driver.py:272 msgid "Security password" msgstr "" -#: /home/kovid/work/calibre/src/calibre/devices/smart_device_app/driver.py:271 +#: /home/kovid/work/calibre/src/calibre/devices/smart_device_app/driver.py:273 msgid "Enter a password that the device app must use to connect to calibre" msgstr "" -#: /home/kovid/work/calibre/src/calibre/devices/smart_device_app/driver.py:273 +#: /home/kovid/work/calibre/src/calibre/devices/smart_device_app/driver.py:275 msgid "Use fixed network port" msgstr "" -#: /home/kovid/work/calibre/src/calibre/devices/smart_device_app/driver.py:274 +#: /home/kovid/work/calibre/src/calibre/devices/smart_device_app/driver.py:276 msgid "" "If checked, use the port number in the \"Port\" box, otherwise the driver " "will pick a random port" msgstr "" -#: /home/kovid/work/calibre/src/calibre/devices/smart_device_app/driver.py:276 +#: /home/kovid/work/calibre/src/calibre/devices/smart_device_app/driver.py:278 msgid "Port number: " msgstr "" -#: /home/kovid/work/calibre/src/calibre/devices/smart_device_app/driver.py:277 +#: /home/kovid/work/calibre/src/calibre/devices/smart_device_app/driver.py:279 msgid "" "Enter the port number the driver is to use if the \"fixed port\" box is " "checked" msgstr "" -#: /home/kovid/work/calibre/src/calibre/devices/smart_device_app/driver.py:278 +#: /home/kovid/work/calibre/src/calibre/devices/smart_device_app/driver.py:280 msgid "Print extra debug information" msgstr "" -#: /home/kovid/work/calibre/src/calibre/devices/smart_device_app/driver.py:279 +#: /home/kovid/work/calibre/src/calibre/devices/smart_device_app/driver.py:281 msgid "Check this box if requested when reporting problems" msgstr "" -#: /home/kovid/work/calibre/src/calibre/devices/smart_device_app/driver.py:281 +#: /home/kovid/work/calibre/src/calibre/devices/smart_device_app/driver.py:283 msgid "" "Comma separated list of metadata fields to turn into collections on the " "device." msgstr "" -#: /home/kovid/work/calibre/src/calibre/devices/smart_device_app/driver.py:283 +#: /home/kovid/work/calibre/src/calibre/devices/smart_device_app/driver.py:285 msgid "Possibilities include: series, tags, authors, etc" msgstr "" -#: /home/kovid/work/calibre/src/calibre/devices/smart_device_app/driver.py:291 +#: /home/kovid/work/calibre/src/calibre/devices/smart_device_app/driver.py:293 msgid "Enable the no-activity timeout" msgstr "" -#: /home/kovid/work/calibre/src/calibre/devices/smart_device_app/driver.py:292 +#: /home/kovid/work/calibre/src/calibre/devices/smart_device_app/driver.py:294 #, python-format msgid "" "If this box is checked, calibre will automatically disconnect if a connected " @@ -2167,33 +2167,33 @@ msgid "" "timeout, so calibre will never automatically disconnect." msgstr "" -#: /home/kovid/work/calibre/src/calibre/devices/smart_device_app/driver.py:296 +#: /home/kovid/work/calibre/src/calibre/devices/smart_device_app/driver.py:298 msgid "Use this IP address" msgstr "" -#: /home/kovid/work/calibre/src/calibre/devices/smart_device_app/driver.py:297 +#: /home/kovid/work/calibre/src/calibre/devices/smart_device_app/driver.py:299 msgid "" "Use this option if you want to force the driver to listen on a particular IP " "address. The driver will listen only on the entered address, and this " "address will be the one advertized over mDNS (bonjour)." msgstr "" -#: /home/kovid/work/calibre/src/calibre/devices/smart_device_app/driver.py:766 +#: /home/kovid/work/calibre/src/calibre/devices/smart_device_app/driver.py:768 #, python-format msgid "Too many connection attempts from %s" msgstr "" -#: /home/kovid/work/calibre/src/calibre/devices/smart_device_app/driver.py:1271 +#: /home/kovid/work/calibre/src/calibre/devices/smart_device_app/driver.py:1273 #, python-format msgid "Invalid port in options: %s" msgstr "" -#: /home/kovid/work/calibre/src/calibre/devices/smart_device_app/driver.py:1279 +#: /home/kovid/work/calibre/src/calibre/devices/smart_device_app/driver.py:1281 #, python-format msgid "Failed to connect to port %d. Try a different value." msgstr "" -#: /home/kovid/work/calibre/src/calibre/devices/smart_device_app/driver.py:1291 +#: /home/kovid/work/calibre/src/calibre/devices/smart_device_app/driver.py:1293 msgid "Failed to allocate a random port" msgstr "" @@ -2205,35 +2205,35 @@ msgstr "Kommunikeer met die Samsung SNE eBoek-leser." msgid "Communicate with the Teclast K3/K5 reader." msgstr "Kommunikeer met die Teclast K3/K5-leser." -#: /home/kovid/work/calibre/src/calibre/devices/teclast/driver.py:37 +#: /home/kovid/work/calibre/src/calibre/devices/teclast/driver.py:38 msgid "Communicate with the Newsmy reader." msgstr "Kommunikeer met die Newsmy-leser." -#: /home/kovid/work/calibre/src/calibre/devices/teclast/driver.py:48 +#: /home/kovid/work/calibre/src/calibre/devices/teclast/driver.py:49 msgid "Communicate with the Archos reader." msgstr "Kommunikeer met die Archos-leser." -#: /home/kovid/work/calibre/src/calibre/devices/teclast/driver.py:58 +#: /home/kovid/work/calibre/src/calibre/devices/teclast/driver.py:59 msgid "Communicate with the Pico reader." msgstr "" -#: /home/kovid/work/calibre/src/calibre/devices/teclast/driver.py:70 +#: /home/kovid/work/calibre/src/calibre/devices/teclast/driver.py:71 msgid "Communicate with the iPapyrus reader." msgstr "" -#: /home/kovid/work/calibre/src/calibre/devices/teclast/driver.py:81 +#: /home/kovid/work/calibre/src/calibre/devices/teclast/driver.py:82 msgid "Communicate with the Sovos reader." msgstr "" -#: /home/kovid/work/calibre/src/calibre/devices/teclast/driver.py:91 +#: /home/kovid/work/calibre/src/calibre/devices/teclast/driver.py:92 msgid "Communicate with the Sunstech EB700 reader." msgstr "" -#: /home/kovid/work/calibre/src/calibre/devices/teclast/driver.py:102 +#: /home/kovid/work/calibre/src/calibre/devices/teclast/driver.py:103 msgid "Communicate with the Stash W950 reader." msgstr "" -#: /home/kovid/work/calibre/src/calibre/devices/teclast/driver.py:114 +#: /home/kovid/work/calibre/src/calibre/devices/teclast/driver.py:115 msgid "Communicate with the Wexler reader." msgstr "" @@ -2432,6 +2432,7 @@ msgid "There is insufficient free space on the storage card" msgstr "" #: /home/kovid/work/calibre/src/calibre/ebooks/comic/input.py:210 +#: /home/kovid/work/calibre/src/calibre/ebooks/pdf/render/from_html.py:230 #, python-format msgid "Rendered %s" msgstr "" @@ -2969,58 +2970,74 @@ msgstr "" msgid "Use the new PDF conversion engine." msgstr "" -#: /home/kovid/work/calibre/src/calibre/ebooks/conversion/plugins/pdf_output.py:71 -#, python-format +#: /home/kovid/work/calibre/src/calibre/ebooks/conversion/plugins/pdf_output.py:54 msgid "" -"The unit of measure. Default is inch. Choices are %s Note: This does not " -"override the unit for margins!" +"Normally, the PDF page size is set by the output profile chosen under page " +"options. This option will cause the page size settings under PDF Output to " +"override the size specified by the output profile." msgstr "" -#: /home/kovid/work/calibre/src/calibre/ebooks/conversion/plugins/pdf_output.py:76 +#: /home/kovid/work/calibre/src/calibre/ebooks/conversion/plugins/pdf_output.py:60 +#, python-format +msgid "" +"The unit of measure for page sizes. Default is inch. Choices are %s Note: " +"This does not override the unit for margins!" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/ebooks/conversion/plugins/pdf_output.py:65 #, python-format msgid "" "The size of the paper. This size will be overridden when a non default " "output profile is used. Default is letter. Choices are %s" msgstr "" -#: /home/kovid/work/calibre/src/calibre/ebooks/conversion/plugins/pdf_output.py:80 +#: /home/kovid/work/calibre/src/calibre/ebooks/conversion/plugins/pdf_output.py:69 msgid "" "Custom size of the document. Use the form widthxheight EG. `123x321` to " "specify the width and height. This overrides any specified paper-size." msgstr "" -#: /home/kovid/work/calibre/src/calibre/ebooks/conversion/plugins/pdf_output.py:85 -#, python-format -msgid "The orientation of the page. Default is portrait. Choices are %s" -msgstr "" - -#: /home/kovid/work/calibre/src/calibre/ebooks/conversion/plugins/pdf_output.py:89 +#: /home/kovid/work/calibre/src/calibre/ebooks/conversion/plugins/pdf_output.py:74 msgid "" "Preserve the aspect ratio of the cover, instead of stretching it to fill the " "full first page of the generated pdf." msgstr "" -#: /home/kovid/work/calibre/src/calibre/ebooks/conversion/plugins/pdf_output.py:94 +#: /home/kovid/work/calibre/src/calibre/ebooks/conversion/plugins/pdf_output.py:79 msgid "The font family used to render serif fonts" msgstr "" -#: /home/kovid/work/calibre/src/calibre/ebooks/conversion/plugins/pdf_output.py:97 +#: /home/kovid/work/calibre/src/calibre/ebooks/conversion/plugins/pdf_output.py:82 msgid "The font family used to render sans-serif fonts" msgstr "" -#: /home/kovid/work/calibre/src/calibre/ebooks/conversion/plugins/pdf_output.py:100 -#: /home/kovid/work/calibre/src/calibre/ebooks/conversion/plugins/pdf_output.py:104 +#: /home/kovid/work/calibre/src/calibre/ebooks/conversion/plugins/pdf_output.py:85 +#: /home/kovid/work/calibre/src/calibre/ebooks/conversion/plugins/pdf_output.py:89 msgid "The font family used to render monospaced fonts" msgstr "" -#: /home/kovid/work/calibre/src/calibre/ebooks/conversion/plugins/pdf_output.py:107 +#: /home/kovid/work/calibre/src/calibre/ebooks/conversion/plugins/pdf_output.py:92 msgid "The default font size" msgstr "" -#: /home/kovid/work/calibre/src/calibre/ebooks/conversion/plugins/pdf_output.py:110 +#: /home/kovid/work/calibre/src/calibre/ebooks/conversion/plugins/pdf_output.py:95 msgid "The default font size for monospaced text" msgstr "" +#: /home/kovid/work/calibre/src/calibre/ebooks/conversion/plugins/pdf_output.py:97 +msgid "Surround all links with a red box, useful for debugging." +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/ebooks/conversion/plugins/pdf_output.py:99 +msgid "Use the old, less capable engine to generate the PDF" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/ebooks/conversion/plugins/pdf_output.py:102 +msgid "" +"Generate an uncompressed PDF, useful for debugging, only works with the new " +"PDF engine." +msgstr "" + #: /home/kovid/work/calibre/src/calibre/ebooks/conversion/plugins/pml_output.py:22 msgid "" "Specify the character encoding of the output document. The default is cp1252." @@ -4046,9 +4063,9 @@ msgstr "" #: /home/kovid/work/calibre/src/calibre/gui2/dialogs/delete_matching_from_device.py:76 #: /home/kovid/work/calibre/src/calibre/gui2/dialogs/quickview.py:85 #: /home/kovid/work/calibre/src/calibre/gui2/dialogs/template_dialog.py:222 -#: /home/kovid/work/calibre/src/calibre/gui2/library/models.py:58 -#: /home/kovid/work/calibre/src/calibre/gui2/library/models.py:1074 -#: /home/kovid/work/calibre/src/calibre/gui2/metadata/single_download.py:146 +#: /home/kovid/work/calibre/src/calibre/gui2/library/models.py:83 +#: /home/kovid/work/calibre/src/calibre/gui2/library/models.py:1108 +#: /home/kovid/work/calibre/src/calibre/gui2/metadata/single_download.py:150 #: /home/kovid/work/calibre/src/calibre/gui2/preferences/metadata_sources.py:162 #: /home/kovid/work/calibre/src/calibre/gui2/store/search/models.py:39 #: /home/kovid/work/calibre/src/calibre/gui2/store/stores/mobileread/models.py:23 @@ -4059,14 +4076,14 @@ msgid "Title" msgstr "" #: /home/kovid/work/calibre/src/calibre/ebooks/metadata/book/base.py:770 -#: /home/kovid/work/calibre/src/calibre/gui2/library/models.py:60 -#: /home/kovid/work/calibre/src/calibre/gui2/library/models.py:1075 +#: /home/kovid/work/calibre/src/calibre/gui2/library/models.py:85 +#: /home/kovid/work/calibre/src/calibre/gui2/library/models.py:1109 #: /home/kovid/work/calibre/src/calibre/gui2/store/stores/mobileread/models.py:23 msgid "Author(s)" msgstr "" #: /home/kovid/work/calibre/src/calibre/ebooks/metadata/book/base.py:771 -#: /home/kovid/work/calibre/src/calibre/gui2/library/models.py:65 +#: /home/kovid/work/calibre/src/calibre/gui2/library/models.py:90 #: /home/kovid/work/calibre/src/calibre/gui2/preferences/metadata_sources.py:159 msgid "Publisher" msgstr "" @@ -4076,7 +4093,7 @@ msgid "Producer" msgstr "" #: /home/kovid/work/calibre/src/calibre/ebooks/metadata/book/base.py:773 -#: /home/kovid/work/calibre/src/calibre/gui2/metadata/single.py:957 +#: /home/kovid/work/calibre/src/calibre/gui2/metadata/single.py:963 #: /home/kovid/work/calibre/src/calibre/gui2/preferences/metadata_sources.py:157 #: /home/kovid/work/calibre/src/calibre/library/field_metadata.py:245 msgid "Comments" @@ -4099,13 +4116,13 @@ msgstr "" #: /home/kovid/work/calibre/src/calibre/gui2/catalog/catalog_epub_mobi.py:535 #: /home/kovid/work/calibre/src/calibre/gui2/catalog/catalog_epub_mobi.py:842 #: /home/kovid/work/calibre/src/calibre/gui2/dialogs/tag_categories.py:60 -#: /home/kovid/work/calibre/src/calibre/gui2/library/models.py:66 +#: /home/kovid/work/calibre/src/calibre/gui2/library/models.py:91 #: /home/kovid/work/calibre/src/calibre/gui2/preferences/create_custom_column.py:70 #: /home/kovid/work/calibre/src/calibre/gui2/preferences/metadata_sources.py:161 -#: /home/kovid/work/calibre/src/calibre/library/catalogs/epub_mobi_builder.py:943 -#: /home/kovid/work/calibre/src/calibre/library/catalogs/epub_mobi_builder.py:1188 +#: /home/kovid/work/calibre/src/calibre/library/catalogs/epub_mobi_builder.py:982 +#: /home/kovid/work/calibre/src/calibre/library/catalogs/epub_mobi_builder.py:1228 #: /home/kovid/work/calibre/src/calibre/library/field_metadata.py:201 -#: /home/kovid/work/calibre/src/calibre/library/server/browse.py:779 +#: /home/kovid/work/calibre/src/calibre/library/server/browse.py:796 msgid "Tags" msgstr "" @@ -4114,11 +4131,11 @@ msgstr "" #: /home/kovid/work/calibre/src/calibre/gui2/dialogs/quickview.py:89 #: /home/kovid/work/calibre/src/calibre/gui2/dialogs/tag_categories.py:60 #: /home/kovid/work/calibre/src/calibre/gui2/dialogs/template_dialog.py:224 -#: /home/kovid/work/calibre/src/calibre/gui2/library/models.py:67 +#: /home/kovid/work/calibre/src/calibre/gui2/library/models.py:92 #: /home/kovid/work/calibre/src/calibre/gui2/preferences/create_custom_column.py:70 #: /home/kovid/work/calibre/src/calibre/gui2/preferences/metadata_sources.py:163 -#: /home/kovid/work/calibre/src/calibre/library/catalogs/epub_mobi_builder.py:303 -#: /home/kovid/work/calibre/src/calibre/library/catalogs/epub_mobi_builder.py:2266 +#: /home/kovid/work/calibre/src/calibre/library/catalogs/epub_mobi_builder.py:307 +#: /home/kovid/work/calibre/src/calibre/library/catalogs/epub_mobi_builder.py:2306 #: /home/kovid/work/calibre/src/calibre/library/field_metadata.py:140 msgid "Series" msgid_plural "Series" @@ -4126,7 +4143,7 @@ msgstr[0] "" msgstr[1] "" #: /home/kovid/work/calibre/src/calibre/ebooks/metadata/book/base.py:778 -#: /home/kovid/work/calibre/src/calibre/gui2/library/models.py:69 +#: /home/kovid/work/calibre/src/calibre/gui2/library/models.py:94 #: /home/kovid/work/calibre/src/calibre/gui2/preferences/metadata_sources.py:164 #: /home/kovid/work/calibre/src/calibre/library/field_metadata.py:127 msgid "Languages" @@ -4138,8 +4155,8 @@ msgstr "" #: /home/kovid/work/calibre/src/calibre/ebooks/metadata/book/base.py:782 #: /home/kovid/work/calibre/src/calibre/ebooks/oeb/transforms/jacket.py:183 -#: /home/kovid/work/calibre/src/calibre/gui2/library/models.py:63 -#: /home/kovid/work/calibre/src/calibre/gui2/metadata/single_download.py:146 +#: /home/kovid/work/calibre/src/calibre/gui2/library/models.py:88 +#: /home/kovid/work/calibre/src/calibre/gui2/metadata/single_download.py:150 #: /home/kovid/work/calibre/src/calibre/library/field_metadata.py:305 msgid "Published" msgstr "" @@ -4253,53 +4270,57 @@ msgstr "" #: /home/kovid/work/calibre/src/calibre/ebooks/metadata/opf2.py:1487 #: /home/kovid/work/calibre/src/calibre/ebooks/oeb/base.py:1279 -#: /home/kovid/work/calibre/src/calibre/gui2/metadata/single.py:969 +#: /home/kovid/work/calibre/src/calibre/gui2/metadata/single.py:975 #: /home/kovid/work/calibre/src/calibre/gui2/store/search/models.py:39 msgid "Cover" msgstr "" -#: /home/kovid/work/calibre/src/calibre/ebooks/metadata/sources/amazon.py:491 +#: /home/kovid/work/calibre/src/calibre/ebooks/metadata/sources/amazon.py:508 msgid "Downloads metadata and covers from Amazon" msgstr "" -#: /home/kovid/work/calibre/src/calibre/ebooks/metadata/sources/amazon.py:501 +#: /home/kovid/work/calibre/src/calibre/ebooks/metadata/sources/amazon.py:518 msgid "US" msgstr "" -#: /home/kovid/work/calibre/src/calibre/ebooks/metadata/sources/amazon.py:502 +#: /home/kovid/work/calibre/src/calibre/ebooks/metadata/sources/amazon.py:519 msgid "France" msgstr "" -#: /home/kovid/work/calibre/src/calibre/ebooks/metadata/sources/amazon.py:503 +#: /home/kovid/work/calibre/src/calibre/ebooks/metadata/sources/amazon.py:520 msgid "Germany" msgstr "" -#: /home/kovid/work/calibre/src/calibre/ebooks/metadata/sources/amazon.py:504 +#: /home/kovid/work/calibre/src/calibre/ebooks/metadata/sources/amazon.py:521 msgid "UK" msgstr "" -#: /home/kovid/work/calibre/src/calibre/ebooks/metadata/sources/amazon.py:505 +#: /home/kovid/work/calibre/src/calibre/ebooks/metadata/sources/amazon.py:522 msgid "Italy" msgstr "" -#: /home/kovid/work/calibre/src/calibre/ebooks/metadata/sources/amazon.py:506 +#: /home/kovid/work/calibre/src/calibre/ebooks/metadata/sources/amazon.py:523 msgid "Japan" msgstr "" -#: /home/kovid/work/calibre/src/calibre/ebooks/metadata/sources/amazon.py:507 +#: /home/kovid/work/calibre/src/calibre/ebooks/metadata/sources/amazon.py:524 msgid "Spain" msgstr "" -#: /home/kovid/work/calibre/src/calibre/ebooks/metadata/sources/amazon.py:511 +#: /home/kovid/work/calibre/src/calibre/ebooks/metadata/sources/amazon.py:525 +msgid "Brazil" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/ebooks/metadata/sources/amazon.py:529 msgid "Amazon website to use:" msgstr "" -#: /home/kovid/work/calibre/src/calibre/ebooks/metadata/sources/amazon.py:512 +#: /home/kovid/work/calibre/src/calibre/ebooks/metadata/sources/amazon.py:530 msgid "" "Metadata from Amazon will be fetched using this country's Amazon website." msgstr "" -#: /home/kovid/work/calibre/src/calibre/ebooks/metadata/sources/amazon.py:753 +#: /home/kovid/work/calibre/src/calibre/ebooks/metadata/sources/amazon.py:775 msgid "Amazon timed out. Try again later." msgstr "" @@ -4408,7 +4429,7 @@ msgstr "" #: /home/kovid/work/calibre/src/calibre/ebooks/mobi/writer8/toc.py:15 #: /home/kovid/work/calibre/src/calibre/ebooks/oeb/base.py:1281 #: /home/kovid/work/calibre/src/calibre/ebooks/oeb/transforms/htmltoc.py:15 -#: /home/kovid/work/calibre/src/calibre/gui2/viewer/main_ui.py:214 +#: /home/kovid/work/calibre/src/calibre/gui2/viewer/main_ui.py:221 #: /home/kovid/work/calibre/src/calibre/gui2/viewer/toc.py:217 msgid "Table of Contents" msgstr "" @@ -4490,11 +4511,11 @@ msgid "HTML TOC generation options." msgstr "" #: /home/kovid/work/calibre/src/calibre/ebooks/oeb/transforms/jacket.py:185 -#: /home/kovid/work/calibre/src/calibre/gui2/library/models.py:64 +#: /home/kovid/work/calibre/src/calibre/gui2/library/models.py:89 #: /home/kovid/work/calibre/src/calibre/gui2/preferences/create_custom_column.py:71 #: /home/kovid/work/calibre/src/calibre/gui2/preferences/metadata_sources.py:160 #: /home/kovid/work/calibre/src/calibre/library/field_metadata.py:176 -#: /home/kovid/work/calibre/src/calibre/library/server/browse.py:777 +#: /home/kovid/work/calibre/src/calibre/library/server/browse.py:794 msgid "Rating" msgstr "" @@ -4542,165 +4563,165 @@ msgstr "" msgid "Table of Contents:" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/__init__.py:120 +#: /home/kovid/work/calibre/src/calibre/gui2/__init__.py:121 msgid "Send file to storage card instead of main memory by default" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/__init__.py:122 +#: /home/kovid/work/calibre/src/calibre/gui2/__init__.py:123 msgid "Confirm before deleting" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/__init__.py:124 +#: /home/kovid/work/calibre/src/calibre/gui2/__init__.py:125 msgid "Main window geometry" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/__init__.py:126 +#: /home/kovid/work/calibre/src/calibre/gui2/__init__.py:127 msgid "Notify when a new version is available" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/__init__.py:128 +#: /home/kovid/work/calibre/src/calibre/gui2/__init__.py:129 msgid "Use Roman numerals for series number" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/__init__.py:130 +#: /home/kovid/work/calibre/src/calibre/gui2/__init__.py:131 msgid "Sort tags list by name, popularity, or rating" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/__init__.py:132 +#: /home/kovid/work/calibre/src/calibre/gui2/__init__.py:133 msgid "Match tags by any or all." msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/__init__.py:134 +#: /home/kovid/work/calibre/src/calibre/gui2/__init__.py:135 msgid "Number of covers to show in the cover browsing mode" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/__init__.py:136 +#: /home/kovid/work/calibre/src/calibre/gui2/__init__.py:137 msgid "Defaults for conversion to LRF" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/__init__.py:138 +#: /home/kovid/work/calibre/src/calibre/gui2/__init__.py:139 msgid "Options for the LRF ebook viewer" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/__init__.py:142 +#: /home/kovid/work/calibre/src/calibre/gui2/__init__.py:143 msgid "Formats that are viewed using the internal viewer" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/__init__.py:144 +#: /home/kovid/work/calibre/src/calibre/gui2/__init__.py:145 msgid "Columns to be displayed in the book list" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/__init__.py:145 +#: /home/kovid/work/calibre/src/calibre/gui2/__init__.py:146 msgid "Automatically launch content server on application startup" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/__init__.py:146 +#: /home/kovid/work/calibre/src/calibre/gui2/__init__.py:147 msgid "Oldest news kept in database" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/__init__.py:147 +#: /home/kovid/work/calibre/src/calibre/gui2/__init__.py:148 msgid "Show system tray icon" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/__init__.py:149 +#: /home/kovid/work/calibre/src/calibre/gui2/__init__.py:150 msgid "Upload downloaded news to device" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/__init__.py:151 +#: /home/kovid/work/calibre/src/calibre/gui2/__init__.py:152 msgid "Delete news books from library after uploading to device" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/__init__.py:153 +#: /home/kovid/work/calibre/src/calibre/gui2/__init__.py:154 msgid "" "Show the cover flow in a separate window instead of in the main calibre " "window" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/__init__.py:155 +#: /home/kovid/work/calibre/src/calibre/gui2/__init__.py:156 msgid "Disable notifications from the system tray icon" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/__init__.py:157 +#: /home/kovid/work/calibre/src/calibre/gui2/__init__.py:158 msgid "Default action to perform when send to device button is clicked" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/__init__.py:162 +#: /home/kovid/work/calibre/src/calibre/gui2/__init__.py:163 msgid "" "Start searching as you type. If this is disabled then search will only take " "place when the Enter or Return key is pressed." msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/__init__.py:165 +#: /home/kovid/work/calibre/src/calibre/gui2/__init__.py:166 msgid "" "When searching, show all books with search results highlighted instead of " "showing only the matches. You can use the N or F3 keys to go to the next " "match." msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/__init__.py:190 +#: /home/kovid/work/calibre/src/calibre/gui2/__init__.py:191 msgid "" "Maximum number of simultaneous conversion/news download jobs. This number is " "twice the actual value for historical reasons." msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/__init__.py:193 +#: /home/kovid/work/calibre/src/calibre/gui2/__init__.py:194 msgid "Download social metadata (tags/rating/etc.)" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/__init__.py:195 +#: /home/kovid/work/calibre/src/calibre/gui2/__init__.py:196 msgid "Overwrite author and title with new metadata" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/__init__.py:197 +#: /home/kovid/work/calibre/src/calibre/gui2/__init__.py:198 msgid "Automatically download the cover, if available" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/__init__.py:199 +#: /home/kovid/work/calibre/src/calibre/gui2/__init__.py:200 msgid "Limit max simultaneous jobs to number of CPUs" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/__init__.py:201 +#: /home/kovid/work/calibre/src/calibre/gui2/__init__.py:202 msgid "" "The layout of the user interface. Wide has the book details panel on the " "right and narrow has it at the bottom." msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/__init__.py:205 +#: /home/kovid/work/calibre/src/calibre/gui2/__init__.py:206 msgid "Show the average rating per item indication in the tag browser" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/__init__.py:207 +#: /home/kovid/work/calibre/src/calibre/gui2/__init__.py:208 msgid "Disable UI animations" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/__init__.py:212 +#: /home/kovid/work/calibre/src/calibre/gui2/__init__.py:213 msgid "tag browser categories not to display" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/__init__.py:278 +#: /home/kovid/work/calibre/src/calibre/gui2/__init__.py:279 msgid "WARNING:" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/__init__.py:288 +#: /home/kovid/work/calibre/src/calibre/gui2/__init__.py:289 msgid "ERROR:" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/__init__.py:300 +#: /home/kovid/work/calibre/src/calibre/gui2/__init__.py:301 #: /home/kovid/work/calibre/src/calibre/gui2/tag_browser/ui.py:258 msgid "Show this confirmation again" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/__init__.py:339 +#: /home/kovid/work/calibre/src/calibre/gui2/__init__.py:340 #: /home/kovid/work/calibre/src/calibre/gui2/preferences/server.py:134 msgid "Restart needed" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/__init__.py:341 +#: /home/kovid/work/calibre/src/calibre/gui2/__init__.py:342 #: /home/kovid/work/calibre/src/calibre/gui2/dialogs/plugin_updater.py:741 msgid "Restart calibre now" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/__init__.py:572 +#: /home/kovid/work/calibre/src/calibre/gui2/__init__.py:573 msgid "Choose Files" msgstr "" @@ -4813,7 +4834,7 @@ msgstr "" #: /home/kovid/work/calibre/src/calibre/gui2/actions/annotate.py:120 #: /home/kovid/work/calibre/src/calibre/gui2/actions/catalog.py:38 #: /home/kovid/work/calibre/src/calibre/gui2/actions/convert.py:107 -#: /home/kovid/work/calibre/src/calibre/gui2/actions/copy_to_library.py:175 +#: /home/kovid/work/calibre/src/calibre/gui2/actions/copy_to_library.py:228 #: /home/kovid/work/calibre/src/calibre/gui2/actions/edit_metadata.py:75 #: /home/kovid/work/calibre/src/calibre/gui2/actions/edit_metadata.py:192 #: /home/kovid/work/calibre/src/calibre/gui2/actions/edit_metadata.py:256 @@ -4843,32 +4864,32 @@ msgstr "" msgid "Select book files" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/actions/add.py:178 +#: /home/kovid/work/calibre/src/calibre/gui2/actions/add.py:189 msgid "Adding" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/actions/add.py:179 +#: /home/kovid/work/calibre/src/calibre/gui2/actions/add.py:190 msgid "Creating book records from ISBNs" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/actions/add.py:270 -#: /home/kovid/work/calibre/src/calibre/gui2/actions/add.py:319 +#: /home/kovid/work/calibre/src/calibre/gui2/actions/add.py:281 +#: /home/kovid/work/calibre/src/calibre/gui2/actions/add.py:330 msgid "Uploading books to device." msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/actions/add.py:290 +#: /home/kovid/work/calibre/src/calibre/gui2/actions/add.py:301 msgid "Supported books" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/actions/add.py:293 +#: /home/kovid/work/calibre/src/calibre/gui2/actions/add.py:304 msgid "Select books" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/actions/add.py:331 +#: /home/kovid/work/calibre/src/calibre/gui2/actions/add.py:342 msgid "Merged some books" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/actions/add.py:332 +#: /home/kovid/work/calibre/src/calibre/gui2/actions/add.py:343 #, python-format msgid "" "The following %d duplicate books were found and incoming book formats were " @@ -4876,21 +4897,21 @@ msgid "" "settings:" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/actions/add.py:354 +#: /home/kovid/work/calibre/src/calibre/gui2/actions/add.py:365 msgid "Failed to read metadata" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/actions/add.py:355 +#: /home/kovid/work/calibre/src/calibre/gui2/actions/add.py:366 msgid "Failed to read metadata from the following" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/actions/add.py:376 -#: /home/kovid/work/calibre/src/calibre/gui2/actions/add.py:381 -#: /home/kovid/work/calibre/src/calibre/gui2/actions/add.py:400 +#: /home/kovid/work/calibre/src/calibre/gui2/actions/add.py:387 +#: /home/kovid/work/calibre/src/calibre/gui2/actions/add.py:392 +#: /home/kovid/work/calibre/src/calibre/gui2/actions/add.py:411 msgid "Add to library" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/actions/add.py:381 +#: /home/kovid/work/calibre/src/calibre/gui2/actions/add.py:392 #: /home/kovid/work/calibre/src/calibre/gui2/actions/delete.py:137 #: /home/kovid/work/calibre/src/calibre/gui2/actions/store.py:87 #: /home/kovid/work/calibre/src/calibre/gui2/actions/store.py:106 @@ -4901,32 +4922,32 @@ msgstr "" msgid "No book selected" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/actions/add.py:394 +#: /home/kovid/work/calibre/src/calibre/gui2/actions/add.py:405 msgid "" "The following books are virtual and cannot be added to the calibre library:" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/actions/add.py:400 +#: /home/kovid/work/calibre/src/calibre/gui2/actions/add.py:411 msgid "No book files found" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/actions/add.py:406 +#: /home/kovid/work/calibre/src/calibre/gui2/actions/add.py:417 msgid "Downloading books" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/actions/add.py:407 +#: /home/kovid/work/calibre/src/calibre/gui2/actions/add.py:418 msgid "Downloading books from device" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/actions/add.py:426 +#: /home/kovid/work/calibre/src/calibre/gui2/actions/add.py:437 msgid "Could not download files from the device" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/actions/add.py:429 +#: /home/kovid/work/calibre/src/calibre/gui2/actions/add.py:440 msgid "Could not download some files from the device" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/actions/add.py:433 +#: /home/kovid/work/calibre/src/calibre/gui2/actions/add.py:444 msgid "Could not download files" msgstr "" @@ -5043,6 +5064,7 @@ msgid "No existing calibre library found at %s" msgstr "" #: /home/kovid/work/calibre/src/calibre/gui2/actions/choose_library.py:152 +#: /home/kovid/work/calibre/src/calibre/gui2/actions/copy_to_library.py:160 msgid "Choose Library" msgstr "" @@ -5056,7 +5078,7 @@ msgstr "" #: /home/kovid/work/calibre/src/calibre/gui2/actions/choose_library.py:160 #: /home/kovid/work/calibre/src/calibre/gui2/preferences/toolbar.py:58 -#: /home/kovid/work/calibre/src/calibre/library/server/browse.py:171 +#: /home/kovid/work/calibre/src/calibre/library/server/browse.py:172 #: /home/kovid/work/calibre/src/calibre/library/server/opds.py:129 #, python-format msgid "%d books" @@ -5213,7 +5235,7 @@ msgid "" msgstr "" #: /home/kovid/work/calibre/src/calibre/gui2/actions/choose_library.py:423 -#: /home/kovid/work/calibre/src/calibre/gui2/actions/copy_to_library.py:197 +#: /home/kovid/work/calibre/src/calibre/gui2/actions/copy_to_library.py:250 #: /home/kovid/work/calibre/src/calibre/gui2/device.py:975 #: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk.py:1007 #: /home/kovid/work/calibre/src/calibre/gui2/dialogs/restore_library.py:114 @@ -5241,7 +5263,7 @@ msgstr "" #: /home/kovid/work/calibre/src/calibre/gui2/actions/choose_library.py:534 #: /home/kovid/work/calibre/src/calibre/gui2/actions/choose_library.py:539 -#: /home/kovid/work/calibre/src/calibre/gui2/actions/copy_to_library.py:225 +#: /home/kovid/work/calibre/src/calibre/gui2/actions/copy_to_library.py:278 #: /home/kovid/work/calibre/src/calibre/gui2/actions/save_to_disk.py:91 #: /home/kovid/work/calibre/src/calibre/gui2/library/views.py:1016 msgid "Not allowed" @@ -5291,7 +5313,7 @@ msgstr "" msgid "Empty output file, probably the conversion process crashed" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/actions/copy_to_library.py:84 +#: /home/kovid/work/calibre/src/calibre/gui2/actions/copy_to_library.py:86 #: /home/kovid/work/calibre/src/calibre/gui2/add.py:401 #: /home/kovid/work/calibre/src/calibre/gui2/add.py:405 #: /home/kovid/work/calibre/src/calibre/gui2/auto_add.py:221 @@ -5299,57 +5321,83 @@ msgstr "" msgid "%(title)s by %(author)s" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/actions/copy_to_library.py:131 +#: /home/kovid/work/calibre/src/calibre/gui2/actions/copy_to_library.py:137 +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/toolbar.py:59 +msgid "Choose library" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/actions/copy_to_library.py:138 +msgid "Library &path:" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/actions/copy_to_library.py:146 +msgid "Browse for library" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/actions/copy_to_library.py:149 +msgid "&Delete after copy" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/actions/copy_to_library.py:172 msgid "Copy to library" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/actions/copy_to_library.py:132 +#: /home/kovid/work/calibre/src/calibre/gui2/actions/copy_to_library.py:173 msgid "Copy selected books to the specified library" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/actions/copy_to_library.py:165 +#: /home/kovid/work/calibre/src/calibre/gui2/actions/copy_to_library.py:206 msgid "(delete after copy)" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/actions/copy_to_library.py:174 +#: /home/kovid/work/calibre/src/calibre/gui2/actions/copy_to_library.py:210 +msgid "Choose library by path..." +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/actions/copy_to_library.py:220 +#: /home/kovid/work/calibre/src/calibre/gui2/actions/copy_to_library.py:227 msgid "Cannot copy" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/actions/copy_to_library.py:179 +#: /home/kovid/work/calibre/src/calibre/gui2/actions/copy_to_library.py:221 +msgid "Cannot copy to current library." +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/actions/copy_to_library.py:232 msgid "No library" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/actions/copy_to_library.py:180 +#: /home/kovid/work/calibre/src/calibre/gui2/actions/copy_to_library.py:233 #, python-format msgid "No library found at %s" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/actions/copy_to_library.py:182 -#: /home/kovid/work/calibre/src/calibre/gui2/actions/copy_to_library.py:186 +#: /home/kovid/work/calibre/src/calibre/gui2/actions/copy_to_library.py:235 +#: /home/kovid/work/calibre/src/calibre/gui2/actions/copy_to_library.py:239 msgid "Copying" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/actions/copy_to_library.py:197 +#: /home/kovid/work/calibre/src/calibre/gui2/actions/copy_to_library.py:250 msgid "Could not copy books: " msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/actions/copy_to_library.py:201 +#: /home/kovid/work/calibre/src/calibre/gui2/actions/copy_to_library.py:254 #, python-format msgid "Copied %(num)d books to %(loc)s" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/actions/copy_to_library.py:205 +#: /home/kovid/work/calibre/src/calibre/gui2/actions/copy_to_library.py:258 msgid "Auto merged" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/actions/copy_to_library.py:206 +#: /home/kovid/work/calibre/src/calibre/gui2/actions/copy_to_library.py:259 msgid "" "Some books were automatically merged into existing records in the target " "library. Click Show details to see which ones. This behavior is controlled " "by the Auto merge option in Preferences->Adding books." msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/actions/copy_to_library.py:226 +#: /home/kovid/work/calibre/src/calibre/gui2/actions/copy_to_library.py:279 msgid "" "You cannot use other libraries while using the environment variable " "CALIBRE_OVERRIDE_DATABASE_PATH." @@ -5485,23 +5533,23 @@ msgid "None of the selected books are on the device" msgstr "" #: /home/kovid/work/calibre/src/calibre/gui2/actions/delete.py:263 -#: /home/kovid/work/calibre/src/calibre/gui2/actions/delete.py:361 +#: /home/kovid/work/calibre/src/calibre/gui2/actions/delete.py:363 msgid "Deleting books from device." msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/actions/delete.py:319 +#: /home/kovid/work/calibre/src/calibre/gui2/actions/delete.py:321 msgid "" "Some of the selected books are on the attached device. Where do you " "want the selected files deleted from?" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/actions/delete.py:331 +#: /home/kovid/work/calibre/src/calibre/gui2/actions/delete.py:333 msgid "" "The selected books will be permanently deleted and the files removed " "from your calibre library. Are you sure?" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/actions/delete.py:353 +#: /home/kovid/work/calibre/src/calibre/gui2/actions/delete.py:355 msgid "" "The selected books will be permanently deleted from your device. Are " "you sure?" @@ -5656,8 +5704,8 @@ msgstr "" #: /home/kovid/work/calibre/src/calibre/gui2/actions/edit_metadata.py:101 #: /home/kovid/work/calibre/src/calibre/gui2/dnd.py:84 -#: /home/kovid/work/calibre/src/calibre/gui2/metadata/single_download.py:514 -#: /home/kovid/work/calibre/src/calibre/gui2/metadata/single_download.py:826 +#: /home/kovid/work/calibre/src/calibre/gui2/metadata/single_download.py:518 +#: /home/kovid/work/calibre/src/calibre/gui2/metadata/single_download.py:830 msgid "Download failed" msgstr "" @@ -5693,7 +5741,7 @@ msgid "Download complete" msgstr "" #: /home/kovid/work/calibre/src/calibre/gui2/actions/edit_metadata.py:123 -#: /home/kovid/work/calibre/src/calibre/gui2/metadata/single_download.py:888 +#: /home/kovid/work/calibre/src/calibre/gui2/metadata/single_download.py:892 msgid "Download log" msgstr "" @@ -5799,7 +5847,7 @@ msgstr "" #: /home/kovid/work/calibre/src/calibre/gui2/actions/help.py:16 #: /home/kovid/work/calibre/src/calibre/gui2/preferences/tweaks_ui.py:118 -#: /usr/src/qt-everywhere-opensource-src-4.8.2/src/gui/widgets/qdialogbuttonbox.cpp:679 +#: /usr/src/qt-everywhere-opensource-src-4.8.4/src/gui/widgets/qdialogbuttonbox.cpp:679 msgid "Help" msgstr "" @@ -5808,7 +5856,7 @@ msgid "Move to next match" msgstr "" #: /home/kovid/work/calibre/src/calibre/gui2/actions/next_match.py:13 -#: /home/kovid/work/calibre/src/calibre/gui2/viewer/main_ui.py:219 +#: /home/kovid/work/calibre/src/calibre/gui2/viewer/main_ui.py:226 msgid "F3" msgstr "" @@ -5834,7 +5882,7 @@ msgid "Shift+N" msgstr "" #: /home/kovid/work/calibre/src/calibre/gui2/actions/next_match.py:27 -#: /home/kovid/work/calibre/src/calibre/gui2/viewer/main_ui.py:228 +#: /home/kovid/work/calibre/src/calibre/gui2/viewer/main_ui.py:235 msgid "Shift+F3" msgstr "" @@ -5954,7 +6002,7 @@ msgid "Click the show details button to see which ones." msgstr "" #: /home/kovid/work/calibre/src/calibre/gui2/actions/show_book_details.py:16 -#: /home/kovid/work/calibre/src/calibre/library/server/browse.py:784 +#: /home/kovid/work/calibre/src/calibre/library/server/browse.py:801 msgid "Show book details" msgstr "" @@ -6047,7 +6095,7 @@ msgid "this book" msgstr "" #: /home/kovid/work/calibre/src/calibre/gui2/actions/store.py:32 -#: /home/kovid/work/calibre/src/calibre/gui2/tag_browser/view.py:469 +#: /home/kovid/work/calibre/src/calibre/gui2/tag_browser/view.py:498 #, python-format msgid "Search for %s" msgstr "" @@ -6148,7 +6196,7 @@ msgstr "" #: /home/kovid/work/calibre/src/calibre/gui2/dialogs/comments_dialog.py:25 #: /home/kovid/work/calibre/src/calibre/gui2/dialogs/template_dialog.py:248 #: /home/kovid/work/calibre/src/calibre/gui2/preferences/main.py:234 -#: /usr/src/qt-everywhere-opensource-src-4.8.2/src/gui/widgets/qdialogbuttonbox.cpp:667 +#: /usr/src/qt-everywhere-opensource-src-4.8.4/src/gui/widgets/qdialogbuttonbox.cpp:667 msgid "&Cancel" msgstr "" @@ -6324,7 +6372,7 @@ msgid "The specified directory could not be processed." msgstr "" #: /home/kovid/work/calibre/src/calibre/gui2/add.py:283 -#: /home/kovid/work/calibre/src/calibre/gui2/device.py:1131 +#: /home/kovid/work/calibre/src/calibre/gui2/device.py:1137 msgid "No books" msgstr "" @@ -6496,10 +6544,10 @@ msgstr "" #: /home/kovid/work/calibre/src/calibre/gui2/dialogs/tag_editor_ui.py:140 #: /home/kovid/work/calibre/src/calibre/gui2/dialogs/tag_list_editor_ui.py:78 #: /home/kovid/work/calibre/src/calibre/gui2/dialogs/tag_list_editor_ui.py:80 -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/user_profiles_ui.py:277 -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/user_profiles_ui.py:279 -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/user_profiles_ui.py:280 -#: /home/kovid/work/calibre/src/calibre/gui2/preferences/adding_ui.py:162 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/user_profiles_ui.py:283 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/user_profiles_ui.py:285 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/user_profiles_ui.py:286 +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/adding_ui.py:171 #: /home/kovid/work/calibre/src/calibre/gui2/preferences/behavior_ui.py:166 #: /home/kovid/work/calibre/src/calibre/gui2/preferences/behavior_ui.py:167 #: /home/kovid/work/calibre/src/calibre/gui2/preferences/columns_ui.py:89 @@ -6518,7 +6566,7 @@ msgstr "" #: /home/kovid/work/calibre/src/calibre/gui2/store/search/search_ui.py:174 #: /home/kovid/work/calibre/src/calibre/gui2/store/search/search_ui.py:181 #: /home/kovid/work/calibre/src/calibre/gui2/store/stores/mobileread/store_dialog_ui.py:75 -#: /home/kovid/work/calibre/src/calibre/gui2/viewer/main_ui.py:206 +#: /home/kovid/work/calibre/src/calibre/gui2/viewer/main_ui.py:213 msgid "..." msgstr "" @@ -6559,6 +6607,7 @@ msgid "Click to open" msgstr "" #: /home/kovid/work/calibre/src/calibre/gui2/book_details.py:180 +#: /home/kovid/work/calibre/src/calibre/library/server/browse.py:850 msgid "Ids" msgstr "" @@ -6568,7 +6617,7 @@ msgid "Book %(sidx)s of %(series)s" msgstr "" #: /home/kovid/work/calibre/src/calibre/gui2/book_details.py:233 -#: /home/kovid/work/calibre/src/calibre/gui2/library/models.py:1078 +#: /home/kovid/work/calibre/src/calibre/gui2/library/models.py:1112 msgid "Collections" msgstr "" @@ -6679,7 +6728,7 @@ msgstr "" #: /home/kovid/work/calibre/src/calibre/gui2/convert/page_setup_ui.py:124 #: /home/kovid/work/calibre/src/calibre/gui2/convert/pdb_output_ui.py:47 #: /home/kovid/work/calibre/src/calibre/gui2/convert/pdf_input_ui.py:43 -#: /home/kovid/work/calibre/src/calibre/gui2/convert/pdf_output_ui.py:100 +#: /home/kovid/work/calibre/src/calibre/gui2/convert/pdf_output_ui.py:114 #: /home/kovid/work/calibre/src/calibre/gui2/convert/pmlz_output_ui.py:46 #: /home/kovid/work/calibre/src/calibre/gui2/convert/rb_output_ui.py:33 #: /home/kovid/work/calibre/src/calibre/gui2/convert/search_and_replace_ui.py:145 @@ -6693,13 +6742,13 @@ msgstr "" #: /home/kovid/work/calibre/src/calibre/gui2/device_drivers/configwidget_ui.py:79 #: /home/kovid/work/calibre/src/calibre/gui2/dialogs/search_item_ui.py:40 #: /home/kovid/work/calibre/src/calibre/gui2/filename_pattern_ui.py:128 -#: /home/kovid/work/calibre/src/calibre/gui2/preferences/adding_ui.py:125 +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/adding_ui.py:134 #: /home/kovid/work/calibre/src/calibre/gui2/preferences/behavior_ui.py:146 #: /home/kovid/work/calibre/src/calibre/gui2/preferences/columns_ui.py:86 #: /home/kovid/work/calibre/src/calibre/gui2/preferences/conversion_ui.py:54 #: /home/kovid/work/calibre/src/calibre/gui2/preferences/email_ui.py:65 #: /home/kovid/work/calibre/src/calibre/gui2/preferences/look_feel_ui.py:246 -#: /home/kovid/work/calibre/src/calibre/gui2/preferences/metadata_sources_ui.py:118 +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/metadata_sources_ui.py:125 #: /home/kovid/work/calibre/src/calibre/gui2/preferences/misc_ui.py:74 #: /home/kovid/work/calibre/src/calibre/gui2/preferences/plugboard_ui.py:113 #: /home/kovid/work/calibre/src/calibre/gui2/preferences/plugins_ui.py:108 @@ -6823,8 +6872,8 @@ msgid "Delete Rule" msgstr "" #: /home/kovid/work/calibre/src/calibre/gui2/catalog/catalog_epub_mobi.py:850 -#: /home/kovid/work/calibre/src/calibre/library/catalogs/epub_mobi_builder.py:609 -#: /home/kovid/work/calibre/src/calibre/library/catalogs/epub_mobi_builder.py:4725 +#: /home/kovid/work/calibre/src/calibre/library/catalogs/epub_mobi_builder.py:613 +#: /home/kovid/work/calibre/src/calibre/library/catalogs/epub_mobi_builder.py:4770 msgid "False" msgstr "" @@ -6837,7 +6886,7 @@ msgstr "" #: /home/kovid/work/calibre/src/calibre/gui2/dialogs/check_library.py:342 #: /home/kovid/work/calibre/src/calibre/gui2/store/config/chooser/models.py:21 #: /home/kovid/work/calibre/src/calibre/gui2/viewer/bookmarkmanager.py:90 -#: /home/kovid/work/calibre/src/calibre/library/server/browse.py:257 +#: /home/kovid/work/calibre/src/calibre/library/server/browse.py:260 msgid "Name" msgstr "" @@ -7807,7 +7856,7 @@ msgstr "" #: /home/kovid/work/calibre/src/calibre/gui2/convert/metadata.py:47 #: /home/kovid/work/calibre/src/calibre/gui2/viewer/main.py:98 -#: /home/kovid/work/calibre/src/calibre/gui2/viewer/main_ui.py:215 +#: /home/kovid/work/calibre/src/calibre/gui2/viewer/main_ui.py:222 msgid "Metadata" msgstr "" @@ -7910,7 +7959,7 @@ msgstr "" #: /home/kovid/work/calibre/src/calibre/gui2/convert/metadata_ui.py:171 #: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:561 -#: /home/kovid/work/calibre/src/calibre/gui2/metadata/basic_widgets.py:1101 +#: /home/kovid/work/calibre/src/calibre/gui2/metadata/basic_widgets.py:1104 msgid "" "Tags categorize the book. This is particularly useful while searching. " "

They can be any words or phrases, separated by commas." @@ -8050,48 +8099,59 @@ msgstr "" msgid "PDF Output" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/convert/pdf_output_ui.py:101 +#: /home/kovid/work/calibre/src/calibre/gui2/convert/pdf_output_ui.py:115 +msgid "" +"Note: The paper size settings below only take effect if you enable " +"the \"Override\" checkbox below. Otherwise the size from the output profile " +"will be used." +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/convert/pdf_output_ui.py:116 +msgid "&Override paper size set in output profile" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/convert/pdf_output_ui.py:117 msgid "&Paper Size:" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/convert/pdf_output_ui.py:102 -msgid "&Orientation:" -msgstr "" - -#: /home/kovid/work/calibre/src/calibre/gui2/convert/pdf_output_ui.py:103 +#: /home/kovid/work/calibre/src/calibre/gui2/convert/pdf_output_ui.py:118 msgid "&Custom size:" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/convert/pdf_output_ui.py:104 +#: /home/kovid/work/calibre/src/calibre/gui2/convert/pdf_output_ui.py:119 +msgid "&Unit:" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/convert/pdf_output_ui.py:120 msgid "Preserve &aspect ratio of cover" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/convert/pdf_output_ui.py:105 +#: /home/kovid/work/calibre/src/calibre/gui2/convert/pdf_output_ui.py:121 #: /home/kovid/work/calibre/src/calibre/gui2/viewer/config_ui.py:374 msgid "Se&rif family:" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/convert/pdf_output_ui.py:106 +#: /home/kovid/work/calibre/src/calibre/gui2/convert/pdf_output_ui.py:122 #: /home/kovid/work/calibre/src/calibre/gui2/viewer/config_ui.py:375 msgid "&Sans family:" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/convert/pdf_output_ui.py:107 +#: /home/kovid/work/calibre/src/calibre/gui2/convert/pdf_output_ui.py:123 #: /home/kovid/work/calibre/src/calibre/gui2/viewer/config_ui.py:376 msgid "&Monospace family:" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/convert/pdf_output_ui.py:108 +#: /home/kovid/work/calibre/src/calibre/gui2/convert/pdf_output_ui.py:124 #: /home/kovid/work/calibre/src/calibre/gui2/viewer/config_ui.py:381 msgid "S&tandard font:" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/convert/pdf_output_ui.py:109 +#: /home/kovid/work/calibre/src/calibre/gui2/convert/pdf_output_ui.py:125 msgid "Default font si&ze:" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/convert/pdf_output_ui.py:110 -#: /home/kovid/work/calibre/src/calibre/gui2/convert/pdf_output_ui.py:112 +#: /home/kovid/work/calibre/src/calibre/gui2/convert/pdf_output_ui.py:126 +#: /home/kovid/work/calibre/src/calibre/gui2/convert/pdf_output_ui.py:128 #: /home/kovid/work/calibre/src/calibre/gui2/viewer/config_ui.py:378 #: /home/kovid/work/calibre/src/calibre/gui2/viewer/config_ui.py:380 #: /home/kovid/work/calibre/src/calibre/gui2/viewer/config_ui.py:395 @@ -8101,18 +8161,11 @@ msgstr "" msgid " px" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/convert/pdf_output_ui.py:111 +#: /home/kovid/work/calibre/src/calibre/gui2/convert/pdf_output_ui.py:127 #: /home/kovid/work/calibre/src/calibre/gui2/viewer/config_ui.py:379 msgid "Monospace &font size:" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/convert/pdf_output_ui.py:113 -msgid "" -"Note: The paper size settings below only take effect if you have set " -"the output profile to the default output profile. Otherwise the output " -"profile will override these settings." -msgstr "" - #: /home/kovid/work/calibre/src/calibre/gui2/convert/pml_output.py:14 msgid "PMLZ Output" msgstr "" @@ -8326,7 +8379,7 @@ msgstr "" #: /home/kovid/work/calibre/src/calibre/gui2/convert/search_and_replace_ui.py:157 #: /home/kovid/work/calibre/src/calibre/gui2/preferences/search_ui.py:188 #: /home/kovid/work/calibre/src/calibre/gui2/viewer/config_ui.py:439 -#: /usr/src/qt-everywhere-opensource-src-4.8.2/src/gui/widgets/qdialogbuttonbox.cpp:661 +#: /usr/src/qt-everywhere-opensource-src-4.8.4/src/gui/widgets/qdialogbuttonbox.cpp:661 msgid "&Save" msgstr "" @@ -8348,7 +8401,7 @@ msgid "" "Add button to add it to the list of expressions." msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/convert/single.py:184 +#: /home/kovid/work/calibre/src/calibre/gui2/convert/single.py:187 msgid "Convert" msgstr "" @@ -8701,7 +8754,7 @@ msgstr "" #: /home/kovid/work/calibre/src/calibre/gui2/library/delegates.py:233 #: /home/kovid/work/calibre/src/calibre/gui2/library/delegates.py:290 #: /home/kovid/work/calibre/src/calibre/gui2/library/delegates.py:294 -#: /home/kovid/work/calibre/src/calibre/gui2/metadata/basic_widgets.py:1413 +#: /home/kovid/work/calibre/src/calibre/gui2/metadata/basic_widgets.py:1416 msgid "Undefined" msgstr "" @@ -8895,7 +8948,7 @@ msgid "Detected the %s. Do you want calibre to manage it?" msgstr "" #: /home/kovid/work/calibre/src/calibre/gui2/device.py:884 -#: /home/kovid/work/calibre/src/calibre/gui2/device.py:1460 +#: /home/kovid/work/calibre/src/calibre/gui2/device.py:1466 #: /home/kovid/work/calibre/src/calibre/gui2/email.py:262 msgid "No suitable formats" msgstr "" @@ -8933,89 +8986,89 @@ msgid "" "reconnect the device or reboot." msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/device.py:1027 +#: /home/kovid/work/calibre/src/calibre/gui2/device.py:1033 msgid "Device: " msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/device.py:1029 +#: /home/kovid/work/calibre/src/calibre/gui2/device.py:1035 msgid " detected." msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/device.py:1132 +#: /home/kovid/work/calibre/src/calibre/gui2/device.py:1138 msgid "selected to send" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/device.py:1139 -#: /home/kovid/work/calibre/src/calibre/gui2/device.py:1169 +#: /home/kovid/work/calibre/src/calibre/gui2/device.py:1145 +#: /home/kovid/work/calibre/src/calibre/gui2/device.py:1175 msgid "No device" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/device.py:1140 +#: /home/kovid/work/calibre/src/calibre/gui2/device.py:1146 msgid "No device connected" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/device.py:1156 +#: /home/kovid/work/calibre/src/calibre/gui2/device.py:1162 #, python-format msgid "%(num)i of %(total)i Books" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/device.py:1160 +#: /home/kovid/work/calibre/src/calibre/gui2/device.py:1166 #, python-format msgid "0 of %i Books" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/device.py:1161 +#: /home/kovid/work/calibre/src/calibre/gui2/device.py:1167 msgid "Choose format to send to device" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/device.py:1170 +#: /home/kovid/work/calibre/src/calibre/gui2/device.py:1176 msgid "Cannot send: No device is connected" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/device.py:1173 -#: /home/kovid/work/calibre/src/calibre/gui2/device.py:1177 +#: /home/kovid/work/calibre/src/calibre/gui2/device.py:1179 +#: /home/kovid/work/calibre/src/calibre/gui2/device.py:1183 msgid "No card" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/device.py:1174 -#: /home/kovid/work/calibre/src/calibre/gui2/device.py:1178 +#: /home/kovid/work/calibre/src/calibre/gui2/device.py:1180 +#: /home/kovid/work/calibre/src/calibre/gui2/device.py:1184 msgid "Cannot send: Device has no storage card" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/device.py:1239 -#: /home/kovid/work/calibre/src/calibre/gui2/device.py:1322 -#: /home/kovid/work/calibre/src/calibre/gui2/device.py:1454 +#: /home/kovid/work/calibre/src/calibre/gui2/device.py:1245 +#: /home/kovid/work/calibre/src/calibre/gui2/device.py:1328 +#: /home/kovid/work/calibre/src/calibre/gui2/device.py:1460 msgid "Auto convert the following books before uploading to the device?" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/device.py:1268 +#: /home/kovid/work/calibre/src/calibre/gui2/device.py:1274 msgid "Sending catalogs to device." msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/device.py:1367 +#: /home/kovid/work/calibre/src/calibre/gui2/device.py:1373 msgid "Sending news to device." msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/device.py:1421 +#: /home/kovid/work/calibre/src/calibre/gui2/device.py:1427 msgid "Sending books to device." msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/device.py:1461 +#: /home/kovid/work/calibre/src/calibre/gui2/device.py:1467 msgid "" "Could not upload the following books to the device, as no suitable formats " "were found. Convert the book(s) to a format supported by your device first." msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/device.py:1534 +#: /home/kovid/work/calibre/src/calibre/gui2/device.py:1540 msgid "No space on device" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/device.py:1535 +#: /home/kovid/work/calibre/src/calibre/gui2/device.py:1541 msgid "" "

Cannot upload books to device there is no more free space available " msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/device.py:1540 +#: /home/kovid/work/calibre/src/calibre/gui2/device.py:1546 msgid "Incorrect destination" msgstr "" @@ -9168,26 +9221,71 @@ msgid "&Ignore the %s in calibre" msgstr "" #: /home/kovid/work/calibre/src/calibre/gui2/device_drivers/mtp_config.py:377 +msgid "Change scanned &folders" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/device_drivers/mtp_config.py:383 msgid "Show device information" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/device_drivers/mtp_config.py:411 +#: /home/kovid/work/calibre/src/calibre/gui2/device_drivers/mtp_config.py:421 #: /home/kovid/work/calibre/src/calibre/gui2/dialogs/message_box.py:141 -#: /home/kovid/work/calibre/src/calibre/gui2/metadata/single_download.py:881 +#: /home/kovid/work/calibre/src/calibre/gui2/metadata/single_download.py:885 #: /home/kovid/work/calibre/src/calibre/gui2/preferences/tweaks.py:344 -#: /home/kovid/work/calibre/src/calibre/gui2/viewer/main_ui.py:220 +#: /home/kovid/work/calibre/src/calibre/gui2/viewer/main_ui.py:227 msgid "Copy to clipboard" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/device_drivers/mtp_config.py:419 +#: /home/kovid/work/calibre/src/calibre/gui2/device_drivers/mtp_config.py:435 #, python-format msgid "The %s will be ignored in calibre" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/device_drivers/mtp_folder_browser.py:91 +#: /home/kovid/work/calibre/src/calibre/gui2/device_drivers/mtp_config.py:495 +#, python-format +msgid "" +"You are trying to send books into the %s folder. This folder is " +"currently ignored by calibre when scanning the device. You have tell calibre " +"you want this folder scanned in order to be able to send books to it. Click " +"the configure button below to send books to it." +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/device_drivers/mtp_config.py:504 +#: /home/kovid/work/calibre/src/calibre/gui2/lrf_renderer/main_ui.py:137 +msgid "Configure" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/device_drivers/mtp_config.py:508 +#, python-format +msgid "Cannot send to %s" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/device_drivers/mtp_folder_browser.py:92 msgid "Choose folder on device" msgstr "" +#: /home/kovid/work/calibre/src/calibre/gui2/device_drivers/mtp_folder_browser.py:105 +msgid "Scanned folders:" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/device_drivers/mtp_folder_browser.py:106 +msgid "" +"You can select which top level folders calibre will scan when searching this " +"device for books." +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/device_drivers/mtp_folder_browser.py:131 +msgid "Select &All" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/device_drivers/mtp_folder_browser.py:133 +msgid "Select &None" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/device_drivers/mtp_folder_browser.py:136 +msgid "Choose folders to scan" +msgstr "" + #: /home/kovid/work/calibre/src/calibre/gui2/dialogs/add_empty_book.py:19 msgid "How many empty books?" msgstr "" @@ -9204,6 +9302,14 @@ msgstr "" msgid "Reset author to Unknown" msgstr "" +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/add_empty_book.py:48 +msgid "Set the series of the new books to:" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/add_empty_book.py:60 +msgid "Reset series" +msgstr "" + #: /home/kovid/work/calibre/src/calibre/gui2/dialogs/add_from_isbn.py:72 msgid "Some invalid ISBNs" msgstr "" @@ -9263,7 +9369,7 @@ msgid "No help available for this output format." msgstr "" #: /home/kovid/work/calibre/src/calibre/gui2/dialogs/catalog_ui.py:92 -#: /home/kovid/work/calibre/src/calibre/gui2/tools.py:333 +#: /home/kovid/work/calibre/src/calibre/gui2/tools.py:348 msgid "Generate catalog" msgstr "" @@ -9611,7 +9717,7 @@ msgstr "" #: /home/kovid/work/calibre/src/calibre/gui2/dialogs/comments_dialog.py:24 #: /home/kovid/work/calibre/src/calibre/gui2/dialogs/template_dialog.py:247 -#: /usr/src/qt-everywhere-opensource-src-4.8.2/src/gui/widgets/qdialogbuttonbox.cpp:658 +#: /usr/src/qt-everywhere-opensource-src-4.8.4/src/gui/widgets/qdialogbuttonbox.cpp:658 msgid "&OK" msgstr "" @@ -9665,8 +9771,8 @@ msgid "Location" msgstr "" #: /home/kovid/work/calibre/src/calibre/gui2/dialogs/delete_matching_from_device.py:77 -#: /home/kovid/work/calibre/src/calibre/gui2/library/models.py:62 -#: /home/kovid/work/calibre/src/calibre/gui2/library/models.py:1076 +#: /home/kovid/work/calibre/src/calibre/gui2/library/models.py:87 +#: /home/kovid/work/calibre/src/calibre/gui2/library/models.py:1110 #: /home/kovid/work/calibre/src/calibre/gui2/preferences/create_custom_column.py:35 #: /home/kovid/work/calibre/src/calibre/gui2/preferences/create_custom_column.py:76 #: /home/kovid/work/calibre/src/calibre/library/field_metadata.py:365 @@ -9685,13 +9791,13 @@ msgstr "" #: /home/kovid/work/calibre/src/calibre/gui2/dialogs/device_category_editor.py:86 #: /home/kovid/work/calibre/src/calibre/gui2/dialogs/tag_list_editor.py:209 -#: /home/kovid/work/calibre/src/calibre/gui2/tag_browser/model.py:929 +#: /home/kovid/work/calibre/src/calibre/gui2/tag_browser/model.py:957 msgid "Item is blank" msgstr "" #: /home/kovid/work/calibre/src/calibre/gui2/dialogs/device_category_editor.py:87 #: /home/kovid/work/calibre/src/calibre/gui2/dialogs/tag_list_editor.py:210 -#: /home/kovid/work/calibre/src/calibre/gui2/tag_browser/model.py:930 +#: /home/kovid/work/calibre/src/calibre/gui2/tag_browser/model.py:958 msgid "An item cannot be set to nothing. Delete it instead." msgstr "" @@ -9771,7 +9877,7 @@ msgstr "" #: /home/kovid/work/calibre/src/calibre/gui2/dialogs/edit_authors_dialog.py:122 #: /home/kovid/work/calibre/src/calibre/gui2/lrf_renderer/main.py:160 -#: /home/kovid/work/calibre/src/calibre/gui2/metadata/single_download.py:523 +#: /home/kovid/work/calibre/src/calibre/gui2/metadata/single_download.py:527 #: /home/kovid/work/calibre/src/calibre/gui2/viewer/main.py:721 msgid "No matches found" msgstr "" @@ -9819,12 +9925,12 @@ msgid "Copy to author" msgstr "" #: /home/kovid/work/calibre/src/calibre/gui2/dialogs/edit_authors_dialog.py:313 -#: /home/kovid/work/calibre/src/calibre/gui2/tag_browser/model.py:979 +#: /home/kovid/work/calibre/src/calibre/gui2/tag_browser/model.py:1007 msgid "Invalid author name" msgstr "" #: /home/kovid/work/calibre/src/calibre/gui2/dialogs/edit_authors_dialog.py:314 -#: /home/kovid/work/calibre/src/calibre/gui2/tag_browser/model.py:980 +#: /home/kovid/work/calibre/src/calibre/gui2/tag_browser/model.py:1008 msgid "Author names cannot contain & characters." msgstr "" @@ -9950,8 +10056,8 @@ msgstr "" #: /home/kovid/work/calibre/src/calibre/gui2/dialogs/message_box.py:196 #: /home/kovid/work/calibre/src/calibre/gui2/dialogs/message_box.py:251 -#: /home/kovid/work/calibre/src/calibre/gui2/metadata/single_download.py:946 -#: /home/kovid/work/calibre/src/calibre/gui2/metadata/single_download.py:1055 +#: /home/kovid/work/calibre/src/calibre/gui2/metadata/single_download.py:950 +#: /home/kovid/work/calibre/src/calibre/gui2/metadata/single_download.py:1059 #: /home/kovid/work/calibre/src/calibre/gui2/proceed.py:48 msgid "View log" msgstr "" @@ -9970,7 +10076,7 @@ msgid "Standard metadata" msgstr "" #: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk.py:63 -#: /home/kovid/work/calibre/src/calibre/gui2/metadata/single.py:939 +#: /home/kovid/work/calibre/src/calibre/gui2/metadata/single.py:945 msgid "Custom metadata" msgstr "" @@ -10156,7 +10262,7 @@ msgstr "" #: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:562 #: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:563 -#: /home/kovid/work/calibre/src/calibre/gui2/metadata/single.py:188 +#: /home/kovid/work/calibre/src/calibre/gui2/metadata/single.py:193 msgid "Open Tag Editor" msgstr "" @@ -10209,7 +10315,7 @@ msgid "&Force numbers to start with:" msgstr "" #: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:582 -#: /home/kovid/work/calibre/src/calibre/gui2/metadata/basic_widgets.py:1395 +#: /home/kovid/work/calibre/src/calibre/gui2/metadata/basic_widgets.py:1398 msgid "&Date:" msgstr "" @@ -10231,7 +10337,7 @@ msgid "Clear published date" msgstr "" #: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:591 -#: /home/kovid/work/calibre/src/calibre/gui2/metadata/basic_widgets.py:1164 +#: /home/kovid/work/calibre/src/calibre/gui2/metadata/basic_widgets.py:1167 msgid "&Languages:" msgstr "" @@ -10301,13 +10407,13 @@ msgid "Set from &ebook file(s)" msgstr "" #: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:613 -#: /home/kovid/work/calibre/src/calibre/gui2/metadata/single.py:575 -#: /home/kovid/work/calibre/src/calibre/gui2/metadata/single.py:741 +#: /home/kovid/work/calibre/src/calibre/gui2/metadata/single.py:580 +#: /home/kovid/work/calibre/src/calibre/gui2/metadata/single.py:747 msgid "&Basic metadata" msgstr "" #: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:614 -#: /home/kovid/work/calibre/src/calibre/gui2/metadata/single.py:582 +#: /home/kovid/work/calibre/src/calibre/gui2/metadata/single.py:587 msgid "&Custom metadata" msgstr "" @@ -10815,8 +10921,8 @@ msgstr "" #: /home/kovid/work/calibre/src/calibre/gui2/dialogs/quickview.py:87 #: /home/kovid/work/calibre/src/calibre/gui2/dialogs/tag_categories.py:60 #: /home/kovid/work/calibre/src/calibre/gui2/preferences/metadata_sources.py:156 -#: /home/kovid/work/calibre/src/calibre/library/catalogs/epub_mobi_builder.py:299 -#: /home/kovid/work/calibre/src/calibre/library/catalogs/epub_mobi_builder.py:1379 +#: /home/kovid/work/calibre/src/calibre/library/catalogs/epub_mobi_builder.py:303 +#: /home/kovid/work/calibre/src/calibre/library/catalogs/epub_mobi_builder.py:1419 #: /home/kovid/work/calibre/src/calibre/library/field_metadata.py:113 msgid "Authors" msgstr "" @@ -10855,7 +10961,7 @@ msgstr "" #: /home/kovid/work/calibre/src/calibre/gui2/store/config/chooser/chooser_widget_ui.py:80 #: /home/kovid/work/calibre/src/calibre/gui2/store/stores/mobileread/store_dialog_ui.py:76 #: /home/kovid/work/calibre/src/calibre/gui2/widgets.py:652 -#: /home/kovid/work/calibre/src/calibre/library/server/browse.py:281 +#: /home/kovid/work/calibre/src/calibre/library/server/browse.py:284 msgid "Search" msgstr "" @@ -11193,7 +11299,7 @@ msgid "never delete" msgstr "" #: /home/kovid/work/calibre/src/calibre/gui2/dialogs/scheduler_ui.py:230 -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/user_profiles_ui.py:273 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/user_profiles_ui.py:279 msgid " days" msgstr "" @@ -11331,7 +11437,7 @@ msgid "&Author:" msgstr "" #: /home/kovid/work/calibre/src/calibre/gui2/dialogs/search_ui.py:199 -#: /home/kovid/work/calibre/src/calibre/gui2/metadata/basic_widgets.py:1100 +#: /home/kovid/work/calibre/src/calibre/gui2/metadata/basic_widgets.py:1103 msgid "Ta&gs:" msgstr "" @@ -11704,7 +11810,7 @@ msgid "The template box cannot be empty" msgstr "" #: /home/kovid/work/calibre/src/calibre/gui2/dialogs/template_dialog_ui.py:110 -#: /home/kovid/work/calibre/src/calibre/gui2/preferences/coloring.py:264 +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/coloring.py:266 msgid "Set the color of the column:" msgstr "" @@ -11778,7 +11884,7 @@ msgstr "" #: /home/kovid/work/calibre/src/calibre/gui2/dialogs/user_profiles.py:169 #: /home/kovid/work/calibre/src/calibre/gui2/dialogs/user_profiles.py:180 -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/user_profiles_ui.py:265 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/user_profiles_ui.py:271 msgid "Switch to Advanced mode" msgstr "" @@ -11852,39 +11958,39 @@ msgid "" "Add/Update recipe button. Continue?" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/user_profiles_ui.py:257 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/user_profiles_ui.py:263 msgid "Add custom news source" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/user_profiles_ui.py:258 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/user_profiles_ui.py:264 msgid "Available user recipes" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/user_profiles_ui.py:259 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/user_profiles_ui.py:265 msgid "Add/Update &recipe" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/user_profiles_ui.py:260 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/user_profiles_ui.py:266 msgid "&Remove recipe" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/user_profiles_ui.py:261 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/user_profiles_ui.py:267 msgid "&Share recipe" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/user_profiles_ui.py:262 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/user_profiles_ui.py:268 msgid "S&how recipe files" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/user_profiles_ui.py:263 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/user_profiles_ui.py:269 msgid "Customize &builtin recipe" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/user_profiles_ui.py:264 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/user_profiles_ui.py:270 msgid "&Load recipe from file" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/user_profiles_ui.py:266 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/user_profiles_ui.py:272 msgid "" "\n" "

Alap szintű " -"hírösszeállítás létrehozása hírforrások hozzáadásával.
Legtöbb esetben " -"a „Haladó mód”-ot is használnia kell a letöltés " -"testreszabásához.

" +"right:0px; -qt-block-indent:0; text-indent:0px;\">Alapszintű hírösszeállítás " +"létrehozása hírcsatornák hozzáadásával.
Legtöbb esetben a „Haladó mód”-" +"ot is használnia kell a letöltés testreszabásához.

" -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/user_profiles_ui.py:270 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/user_profiles_ui.py:276 msgid "Recipe &title:" msgstr "A hírösszeállí&tás címe:" -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/user_profiles_ui.py:271 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/user_profiles_ui.py:277 msgid "&Oldest article:" msgstr "Leg&régebbi cikk:" -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/user_profiles_ui.py:272 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/user_profiles_ui.py:278 msgid "The oldest article to download" msgstr "A legrégebbi letöltendő cikk" -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/user_profiles_ui.py:274 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/user_profiles_ui.py:280 msgid "&Max. number of articles per feed:" -msgstr "A hírforrásban szereplő cikkek &maximális száma:" - -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/user_profiles_ui.py:275 -msgid "Maximum number of articles to download per feed." -msgstr "A hírforrásban letöltendő cikkek maximális száma:" - -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/user_profiles_ui.py:276 -msgid "Feeds in recipe" -msgstr "Hírforrások a hírösszeállításban" - -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/user_profiles_ui.py:278 -msgid "Remove feed from recipe" -msgstr "Hírforrás eltávolítása a hírösszeállításból" +msgstr "A hírcsatorna cikkeinek &maximális száma:" #: /home/kovid/work/calibre/src/calibre/gui2/dialogs/user_profiles_ui.py:281 -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/user_profiles_ui.py:284 -msgid "Add feed to recipe" -msgstr "Hírforrás hozzáadása a hírösszeállításhoz" +msgid "Maximum number of articles to download per feed." +msgstr "A hírcsatornából letöltendő cikkek maximális száma:" #: /home/kovid/work/calibre/src/calibre/gui2/dialogs/user_profiles_ui.py:282 +msgid "Feeds in recipe" +msgstr "Hírcsatornák a hírösszeállításban" + +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/user_profiles_ui.py:284 +msgid "Remove feed from recipe" +msgstr "Hírcsatorna eltávolítása a hírösszeállításból" + +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/user_profiles_ui.py:287 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/user_profiles_ui.py:290 +msgid "Add feed to recipe" +msgstr "Hírcsatorna hozzáadása a hírösszeállításhoz" + +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/user_profiles_ui.py:288 msgid "&Feed title:" -msgstr "&Hírforrás címe:" +msgstr "&Hírcsatorna címe:" -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/user_profiles_ui.py:283 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/user_profiles_ui.py:289 msgid "Feed &URL:" -msgstr "Hírforrás URL-je:" +msgstr "Hírcsatorna URL-je:" -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/user_profiles_ui.py:285 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/user_profiles_ui.py:291 msgid "&Add feed" -msgstr "Hírforrás hozzácadása" +msgstr "&Hírcsatorna hozzácadása" -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/user_profiles_ui.py:286 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/user_profiles_ui.py:292 msgid "" "For help with writing advanced news recipes, please visit User Recipes" @@ -13181,7 +13314,7 @@ msgstr "" "ebook.com/news.html\">Felhasználói Hírösszeállítások
oldalon kaphat " "további információkat." -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/user_profiles_ui.py:287 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/user_profiles_ui.py:293 msgid "Recipe source code (python)" msgstr "Hírösszeállítás forráskódja (python)" @@ -13225,7 +13358,7 @@ msgstr "Az e-book letöltése nem sikerült" #: /home/kovid/work/calibre/src/calibre/gui2/email.py:125 #, python-format msgid "Email %(name)s to %(to)s" -msgstr "%(name)s elküldése e-mailben ide: %(to)s" +msgstr "%(name)s elküldése emailben ide: %(to)s" #: /home/kovid/work/calibre/src/calibre/gui2/email.py:144 msgid "News:" @@ -13256,30 +13389,30 @@ msgstr "%s formátumban." #: /home/kovid/work/calibre/src/calibre/gui2/email.py:225 msgid "Sending email to" -msgstr "E-mail küldése ide:" +msgstr "Email küldése ide:" #: /home/kovid/work/calibre/src/calibre/gui2/email.py:256 msgid "Auto convert the following books before sending via email?" msgstr "" -"Az e-mailben történő küldés előtt kívánja automatikusan konvertálni a " -"kijelölt könyveket?" +"Az eailben történő küldés előtt kívánja automatikusan konvertálni a kijelölt " +"könyveket?" #: /home/kovid/work/calibre/src/calibre/gui2/email.py:263 msgid "" "Could not email the following books as no suitable formats were found:" msgstr "" -"Nem lehet elküldeni e-mailben a következő könyveket, mert nem léteznek a " +"Nem lehet elküldeni emailben a következő könyveket, mert nem léteznek a " "megadott formátumokban:" #: /home/kovid/work/calibre/src/calibre/gui2/email.py:269 msgid "Failed to email book" -msgstr "A könyv e-mailben történő elküldése meghiúsult" +msgstr "A könyv emailben történő elküldése meghiúsult" #: /home/kovid/work/calibre/src/calibre/gui2/email.py:272 msgid "sent" msgstr "elküldve" -#: /home/kovid/work/calibre/src/calibre/gui2/email.py:298 +#: /home/kovid/work/calibre/src/calibre/gui2/email.py:301 msgid "Sent news to" msgstr "Hírek elküldve:" @@ -13362,7 +13495,7 @@ msgid "Regular expression (?P)" msgstr "Sorozaton belüli sorszám. Reguláris kifejezés (?P)" #: /home/kovid/work/calibre/src/calibre/gui2/filename_pattern_ui.py:149 -#: /home/kovid/work/calibre/src/calibre/gui2/metadata/basic_widgets.py:1297 +#: /home/kovid/work/calibre/src/calibre/gui2/metadata/basic_widgets.py:1300 msgid "ISBN:" msgstr "ISBN:" @@ -13388,7 +13521,7 @@ msgstr "Reguláris kifejezés (?P)" #: /home/kovid/work/calibre/src/calibre/gui2/font_family_chooser.py:123 msgid "Choose a font family" -msgstr "" +msgstr "Betűtípus kiválasztása" #: /home/kovid/work/calibre/src/calibre/gui2/font_family_chooser.py:136 #, python-format @@ -13401,7 +13534,7 @@ msgstr "Betűtípus kiválasztása" #: /home/kovid/work/calibre/src/calibre/gui2/font_family_chooser.py:195 msgid "Add &fonts" -msgstr "" +msgstr "&Hozzáadás" #: /home/kovid/work/calibre/src/calibre/gui2/font_family_chooser.py:199 msgid "Choose a font family from the list below:" @@ -13409,11 +13542,11 @@ msgstr "Válasszon betűtípust az alábbi listából:" #: /home/kovid/work/calibre/src/calibre/gui2/font_family_chooser.py:205 msgid "Find Next" -msgstr "" +msgstr "Következő találat" #: /home/kovid/work/calibre/src/calibre/gui2/font_family_chooser.py:208 msgid "Find Previous" -msgstr "" +msgstr "Előző találat" #: /home/kovid/work/calibre/src/calibre/gui2/font_family_chooser.py:258 #: /home/kovid/work/calibre/src/calibre/gui2/keyboard.py:377 @@ -13432,15 +13565,15 @@ msgstr "Nincs" #: /home/kovid/work/calibre/src/calibre/gui2/font_family_chooser.py:264 msgid "Select font files" -msgstr "" +msgstr "Betűfájlok kiválasztása" #: /home/kovid/work/calibre/src/calibre/gui2/font_family_chooser.py:264 msgid "TrueType/OpenType Fonts" -msgstr "" +msgstr "TrueType/OpenType betűtípusok" #: /home/kovid/work/calibre/src/calibre/gui2/font_family_chooser.py:274 msgid "Corrupt font" -msgstr "" +msgstr "Hibás betűkészlet" #: /home/kovid/work/calibre/src/calibre/gui2/font_family_chooser.py:275 #, python-format @@ -13449,12 +13582,12 @@ msgstr "" #: /home/kovid/work/calibre/src/calibre/gui2/font_family_chooser.py:294 msgid "Added fonts" -msgstr "" +msgstr "Hozzáadott betűtípusok" #: /home/kovid/work/calibre/src/calibre/gui2/font_family_chooser.py:295 #, python-format msgid "Added font families: %s" -msgstr "" +msgstr "Hozzáadott betűtípusok: %s" #: /home/kovid/work/calibre/src/calibre/gui2/font_family_chooser.py:320 msgid "Choose &font family" @@ -13756,75 +13889,75 @@ msgstr "csillag" msgid "Y" msgstr "Y" -#: /home/kovid/work/calibre/src/calibre/gui2/library/models.py:59 +#: /home/kovid/work/calibre/src/calibre/gui2/library/models.py:84 #: /home/kovid/work/calibre/src/calibre/library/field_metadata.py:285 msgid "On Device" msgstr "Eszközön" -#: /home/kovid/work/calibre/src/calibre/gui2/library/models.py:61 +#: /home/kovid/work/calibre/src/calibre/gui2/library/models.py:86 msgid "Size (MB)" msgstr "Méret (MB)" -#: /home/kovid/work/calibre/src/calibre/gui2/library/models.py:68 +#: /home/kovid/work/calibre/src/calibre/gui2/library/models.py:93 #: /home/kovid/work/calibre/src/calibre/library/field_metadata.py:275 msgid "Modified" msgstr "Módosítva" -#: /home/kovid/work/calibre/src/calibre/gui2/library/models.py:785 -#: /home/kovid/work/calibre/src/calibre/gui2/library/models.py:1417 -#: /home/kovid/work/calibre/src/calibre/gui2/tag_browser/model.py:312 +#: /home/kovid/work/calibre/src/calibre/gui2/library/models.py:819 +#: /home/kovid/work/calibre/src/calibre/gui2/library/models.py:1455 +#: /home/kovid/work/calibre/src/calibre/gui2/tag_browser/model.py:335 msgid "The lookup/search name is \"{0}\"" msgstr "Keresési/rendezési feltétel: „{0}”" -#: /home/kovid/work/calibre/src/calibre/gui2/library/models.py:791 -#: /home/kovid/work/calibre/src/calibre/gui2/library/models.py:1419 +#: /home/kovid/work/calibre/src/calibre/gui2/library/models.py:825 +#: /home/kovid/work/calibre/src/calibre/gui2/library/models.py:1457 msgid "This book's UUID is \"{0}\"" msgstr "A könyv UUID-je: „{0}”" -#: /home/kovid/work/calibre/src/calibre/gui2/library/models.py:878 +#: /home/kovid/work/calibre/src/calibre/gui2/library/models.py:912 #: /home/kovid/work/calibre/src/calibre/gui2/metadata/basic_widgets.py:108 #: /home/kovid/work/calibre/src/calibre/gui2/metadata/basic_widgets.py:280 -#: /home/kovid/work/calibre/src/calibre/gui2/metadata/single.py:324 -#: /home/kovid/work/calibre/src/calibre/gui2/metadata/single.py:454 +#: /home/kovid/work/calibre/src/calibre/gui2/metadata/single.py:329 +#: /home/kovid/work/calibre/src/calibre/gui2/metadata/single.py:459 msgid "Permission denied" msgstr "Hozzáférés megtagadva" -#: /home/kovid/work/calibre/src/calibre/gui2/library/models.py:879 +#: /home/kovid/work/calibre/src/calibre/gui2/library/models.py:913 #: /home/kovid/work/calibre/src/calibre/gui2/metadata/basic_widgets.py:109 #: /home/kovid/work/calibre/src/calibre/gui2/metadata/basic_widgets.py:281 -#: /home/kovid/work/calibre/src/calibre/gui2/metadata/single.py:455 +#: /home/kovid/work/calibre/src/calibre/gui2/metadata/single.py:460 msgid "" "Could not change the on disk location of this book. Is it open in another " "program?" msgstr "" "A könyv helyét nem sikerült megváltoztatni. Esetleg más program is használja?" -#: /home/kovid/work/calibre/src/calibre/gui2/library/models.py:883 -#: /home/kovid/work/calibre/src/calibre/gui2/library/models.py:889 +#: /home/kovid/work/calibre/src/calibre/gui2/library/models.py:917 +#: /home/kovid/work/calibre/src/calibre/gui2/library/models.py:923 msgid "Failed to set data" msgstr "Sikertelen adatbeállítás" -#: /home/kovid/work/calibre/src/calibre/gui2/library/models.py:884 -#: /home/kovid/work/calibre/src/calibre/gui2/library/models.py:890 +#: /home/kovid/work/calibre/src/calibre/gui2/library/models.py:918 +#: /home/kovid/work/calibre/src/calibre/gui2/library/models.py:924 msgid "Could not set data, click Show Details to see why." msgstr "" "Nem sikerült az adatokat beállítani, további információkért kattintson a " "Részletek megjelenítésére." -#: /home/kovid/work/calibre/src/calibre/gui2/library/models.py:1073 +#: /home/kovid/work/calibre/src/calibre/gui2/library/models.py:1107 msgid "In Library" msgstr "Könyvtárban" -#: /home/kovid/work/calibre/src/calibre/gui2/library/models.py:1077 +#: /home/kovid/work/calibre/src/calibre/gui2/library/models.py:1111 #: /home/kovid/work/calibre/src/calibre/library/field_metadata.py:355 msgid "Size" msgstr "Méret" -#: /home/kovid/work/calibre/src/calibre/gui2/library/models.py:1399 +#: /home/kovid/work/calibre/src/calibre/gui2/library/models.py:1437 msgid "Marked for deletion" msgstr "Megjelölve törlése" -#: /home/kovid/work/calibre/src/calibre/gui2/library/models.py:1402 +#: /home/kovid/work/calibre/src/calibre/gui2/library/models.py:1440 msgid "Double click to edit me

" msgstr "Dupla-kattintás a szerkesztéshez

" @@ -13931,14 +14064,14 @@ msgid "Previous Page" msgstr "Előző oldal" #: /home/kovid/work/calibre/src/calibre/gui2/lrf_renderer/main_ui.py:133 -#: /home/kovid/work/calibre/src/calibre/gui2/metadata/single_download.py:943 +#: /home/kovid/work/calibre/src/calibre/gui2/metadata/single_download.py:947 #: /home/kovid/work/calibre/src/calibre/gui2/store/web_store_dialog_ui.py:62 -#: /home/kovid/work/calibre/src/calibre/gui2/viewer/main_ui.py:208 +#: /home/kovid/work/calibre/src/calibre/gui2/viewer/main_ui.py:215 msgid "Back" msgstr "Vissza" #: /home/kovid/work/calibre/src/calibre/gui2/lrf_renderer/main_ui.py:134 -#: /home/kovid/work/calibre/src/calibre/gui2/viewer/main_ui.py:209 +#: /home/kovid/work/calibre/src/calibre/gui2/viewer/main_ui.py:216 msgid "Forward" msgstr "Előre" @@ -13947,14 +14080,10 @@ msgid "Next match" msgstr "Következő találat" #: /home/kovid/work/calibre/src/calibre/gui2/lrf_renderer/main_ui.py:136 -#: /home/kovid/work/calibre/src/calibre/gui2/viewer/main_ui.py:216 +#: /home/kovid/work/calibre/src/calibre/gui2/viewer/main_ui.py:223 msgid "Open ebook" msgstr "eBook megnyitása" -#: /home/kovid/work/calibre/src/calibre/gui2/lrf_renderer/main_ui.py:137 -msgid "Configure" -msgstr "Beállítás" - #: /home/kovid/work/calibre/src/calibre/gui2/main.py:35 msgid "Use the library located at the specified path." msgstr "A megadott elérési úton lévő adatbázis használata." @@ -13981,7 +14110,7 @@ msgstr "" #: /home/kovid/work/calibre/src/calibre/gui2/main.py:75 msgid "Path too long" -msgstr "" +msgstr "Elérési út túl hosszú" #: /home/kovid/work/calibre/src/calibre/gui2/main.py:76 #, python-format @@ -14286,11 +14415,11 @@ msgstr "Érvénytelen borító" msgid "Could not change cover as the image is invalid." msgstr "Nem lehet megváltoztatni a borítót, mert a képfájl érvénytelen." -#: /home/kovid/work/calibre/src/calibre/gui2/metadata/basic_widgets.py:1136 +#: /home/kovid/work/calibre/src/calibre/gui2/metadata/basic_widgets.py:1139 msgid "Tags changed" msgstr "Címke megváltoztatva" -#: /home/kovid/work/calibre/src/calibre/gui2/metadata/basic_widgets.py:1137 +#: /home/kovid/work/calibre/src/calibre/gui2/metadata/basic_widgets.py:1140 msgid "" "You have changed the tags. In order to use the tags editor, you must either " "discard or apply these changes. Apply changes?" @@ -14299,26 +14428,26 @@ msgstr "" "vetnie a változásokat, vagy pedig alkalmaznia kell azokat. Alkalmazza a " "változásokat?" -#: /home/kovid/work/calibre/src/calibre/gui2/metadata/basic_widgets.py:1165 +#: /home/kovid/work/calibre/src/calibre/gui2/metadata/basic_widgets.py:1168 msgid "A comma separated list of languages for this book" msgstr "A könyvben használt nyelvek vesszővel elválasztott listája" -#: /home/kovid/work/calibre/src/calibre/gui2/metadata/basic_widgets.py:1188 +#: /home/kovid/work/calibre/src/calibre/gui2/metadata/basic_widgets.py:1191 msgid "Unknown language" msgstr "Ismeretlen nyelv" -#: /home/kovid/work/calibre/src/calibre/gui2/metadata/basic_widgets.py:1189 +#: /home/kovid/work/calibre/src/calibre/gui2/metadata/basic_widgets.py:1192 #, python-format msgid "The language %s is not recognized" msgid_plural "The languages %s are not recognized" msgstr[0] "Ismeretlen nyelv: %s" msgstr[1] "Ismeretlen nyelvek: %s" -#: /home/kovid/work/calibre/src/calibre/gui2/metadata/basic_widgets.py:1201 +#: /home/kovid/work/calibre/src/calibre/gui2/metadata/basic_widgets.py:1204 msgid "I&ds:" msgstr "&Azonosítók:" -#: /home/kovid/work/calibre/src/calibre/gui2/metadata/basic_widgets.py:1202 +#: /home/kovid/work/calibre/src/calibre/gui2/metadata/basic_widgets.py:1205 #, python-format msgid "" "Edit the identifiers for this book. For example: \n" @@ -14329,38 +14458,38 @@ msgstr "" "\n" "%s" -#: /home/kovid/work/calibre/src/calibre/gui2/metadata/basic_widgets.py:1266 -#: /home/kovid/work/calibre/src/calibre/gui2/metadata/basic_widgets.py:1328 +#: /home/kovid/work/calibre/src/calibre/gui2/metadata/basic_widgets.py:1269 +#: /home/kovid/work/calibre/src/calibre/gui2/metadata/basic_widgets.py:1331 msgid "This ISBN number is valid" msgstr "Az ISBN szám érvényes" -#: /home/kovid/work/calibre/src/calibre/gui2/metadata/basic_widgets.py:1269 -#: /home/kovid/work/calibre/src/calibre/gui2/metadata/basic_widgets.py:1331 +#: /home/kovid/work/calibre/src/calibre/gui2/metadata/basic_widgets.py:1272 +#: /home/kovid/work/calibre/src/calibre/gui2/metadata/basic_widgets.py:1334 msgid "This ISBN number is invalid" msgstr "Nem érvényes ISBN szám" -#: /home/kovid/work/calibre/src/calibre/gui2/metadata/basic_widgets.py:1294 -#: /home/kovid/work/calibre/src/calibre/gui2/metadata/basic_widgets.py:1316 +#: /home/kovid/work/calibre/src/calibre/gui2/metadata/basic_widgets.py:1297 +#: /home/kovid/work/calibre/src/calibre/gui2/metadata/basic_widgets.py:1319 msgid "Invalid ISBN" msgstr "Érvénytelen ISBN szám" -#: /home/kovid/work/calibre/src/calibre/gui2/metadata/basic_widgets.py:1295 +#: /home/kovid/work/calibre/src/calibre/gui2/metadata/basic_widgets.py:1298 msgid "Enter an ISBN" msgstr "ISBN szám megadása" -#: /home/kovid/work/calibre/src/calibre/gui2/metadata/basic_widgets.py:1317 +#: /home/kovid/work/calibre/src/calibre/gui2/metadata/basic_widgets.py:1320 msgid "The ISBN you entered is not valid. Try again." msgstr "A megadott ISBN szám érvénytelen. Próbálja újra." -#: /home/kovid/work/calibre/src/calibre/gui2/metadata/basic_widgets.py:1341 +#: /home/kovid/work/calibre/src/calibre/gui2/metadata/basic_widgets.py:1344 msgid "&Publisher:" msgstr "Kiadó:" -#: /home/kovid/work/calibre/src/calibre/gui2/metadata/basic_widgets.py:1416 +#: /home/kovid/work/calibre/src/calibre/gui2/metadata/basic_widgets.py:1419 msgid "Clear date" msgstr "Dátum törlése" -#: /home/kovid/work/calibre/src/calibre/gui2/metadata/basic_widgets.py:1450 +#: /home/kovid/work/calibre/src/calibre/gui2/metadata/basic_widgets.py:1453 msgid "Publishe&d:" msgstr "Kiadva:" @@ -14432,24 +14561,24 @@ msgid "Processed %s" msgstr "%s feldolgozva" #: /home/kovid/work/calibre/src/calibre/gui2/metadata/config.py:61 -#: /home/kovid/work/calibre/src/calibre/gui2/preferences/metadata_sources_ui.py:124 +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/metadata_sources_ui.py:131 msgid "Downloaded metadata fields" -msgstr "Letöltendő metaadat mezők" +msgstr "Letöltött metaadat mezők" #: /home/kovid/work/calibre/src/calibre/gui2/metadata/single.py:31 msgid "Edit Metadata" msgstr "Metaadat szerkesztése" #: /home/kovid/work/calibre/src/calibre/gui2/metadata/single.py:63 -#: /home/kovid/work/calibre/src/calibre/gui2/metadata/single_download.py:936 -#: /home/kovid/work/calibre/src/calibre/library/server/browse.py:107 +#: /home/kovid/work/calibre/src/calibre/gui2/metadata/single_download.py:940 +#: /home/kovid/work/calibre/src/calibre/library/server/browse.py:108 #: /home/kovid/work/calibre/src/calibre/web/feeds/templates.py:219 #: /home/kovid/work/calibre/src/calibre/web/feeds/templates.py:410 msgid "Next" msgstr "Következő" #: /home/kovid/work/calibre/src/calibre/gui2/metadata/single.py:67 -#: /home/kovid/work/calibre/src/calibre/library/server/browse.py:106 +#: /home/kovid/work/calibre/src/calibre/library/server/browse.py:107 #: /home/kovid/work/calibre/src/calibre/web/feeds/templates.py:229 #: /home/kovid/work/calibre/src/calibre/web/feeds/templates.py:393 msgid "Previous" @@ -14511,15 +14640,19 @@ msgstr "" msgid "Clear series" msgstr "Sorozatok törlése" -#: /home/kovid/work/calibre/src/calibre/gui2/metadata/single.py:192 -msgid "Clear all tags" +#: /home/kovid/work/calibre/src/calibre/gui2/metadata/single.py:185 +msgid "Clear rating" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/metadata/single.py:201 +#: /home/kovid/work/calibre/src/calibre/gui2/metadata/single.py:197 +msgid "Clear all tags" +msgstr "Minden címke törlése" + +#: /home/kovid/work/calibre/src/calibre/gui2/metadata/single.py:206 msgid "Clear Ids" msgstr "Azonosítók törlése" -#: /home/kovid/work/calibre/src/calibre/gui2/metadata/single.py:205 +#: /home/kovid/work/calibre/src/calibre/gui2/metadata/single.py:210 msgid "" "Paste the contents of the clipboard into the identifiers box prefixed with " "isbn:" @@ -14527,88 +14660,88 @@ msgstr "" "A vágólap tartalmának beillesztése azonosítóként a szövegmezőbe „isbn:” " "előtaggal" -#: /home/kovid/work/calibre/src/calibre/gui2/metadata/single.py:218 +#: /home/kovid/work/calibre/src/calibre/gui2/metadata/single.py:223 msgid "&Download metadata" msgstr "Metaa&datok letöltése" -#: /home/kovid/work/calibre/src/calibre/gui2/metadata/single.py:230 +#: /home/kovid/work/calibre/src/calibre/gui2/metadata/single.py:235 msgid "Configure download metadata" msgstr "Metaadatok letöltésének beállítása" -#: /home/kovid/work/calibre/src/calibre/gui2/metadata/single.py:234 +#: /home/kovid/work/calibre/src/calibre/gui2/metadata/single.py:239 msgid "Change how calibre downloads metadata" msgstr "A metaadatok letöltésének beállítása" -#: /home/kovid/work/calibre/src/calibre/gui2/metadata/single.py:296 +#: /home/kovid/work/calibre/src/calibre/gui2/metadata/single.py:301 #, python-format msgid " [%(num)d of %(tot)d]" msgstr " [%(num)d/%(tot)d]" -#: /home/kovid/work/calibre/src/calibre/gui2/metadata/single.py:325 +#: /home/kovid/work/calibre/src/calibre/gui2/metadata/single.py:330 #, python-format msgid "Could not open %s. Is it being used by another program?" msgstr "Nem lehet megnyitni: %s. Esetleg másik program használja?" -#: /home/kovid/work/calibre/src/calibre/gui2/metadata/single.py:338 -#: /home/kovid/work/calibre/src/calibre/gui2/metadata/single.py:345 +#: /home/kovid/work/calibre/src/calibre/gui2/metadata/single.py:343 +#: /home/kovid/work/calibre/src/calibre/gui2/metadata/single.py:350 msgid "Could not read cover" msgstr "Nem lehet olvasni a borítót" -#: /home/kovid/work/calibre/src/calibre/gui2/metadata/single.py:339 +#: /home/kovid/work/calibre/src/calibre/gui2/metadata/single.py:344 #, python-format msgid "Could not read cover from %s format" msgstr "Nem lehet kiolvasni a borítót a %s formátumból" -#: /home/kovid/work/calibre/src/calibre/gui2/metadata/single.py:346 +#: /home/kovid/work/calibre/src/calibre/gui2/metadata/single.py:351 #, python-format msgid "The cover in the %s format is invalid" msgstr "A %s formátumban lévő borító érvénytelen" -#: /home/kovid/work/calibre/src/calibre/gui2/metadata/single.py:522 #: /home/kovid/work/calibre/src/calibre/gui2/metadata/single.py:527 +#: /home/kovid/work/calibre/src/calibre/gui2/metadata/single.py:532 #, python-format msgid "Save changes and edit the metadata of %s" msgstr "Változtatások mentése és a következő metaadatainak szerkesztése: %s" -#: /home/kovid/work/calibre/src/calibre/gui2/metadata/single.py:625 -#: /home/kovid/work/calibre/src/calibre/gui2/metadata/single.py:831 +#: /home/kovid/work/calibre/src/calibre/gui2/metadata/single.py:630 +#: /home/kovid/work/calibre/src/calibre/gui2/metadata/single.py:837 msgid "Change cover" msgstr "Borítócsere" -#: /home/kovid/work/calibre/src/calibre/gui2/metadata/single.py:684 +#: /home/kovid/work/calibre/src/calibre/gui2/metadata/single.py:690 msgid "Co&mments" msgstr "&Megjegyzés" -#: /home/kovid/work/calibre/src/calibre/gui2/metadata/single.py:724 -#: /home/kovid/work/calibre/src/calibre/gui2/metadata/single.py:872 +#: /home/kovid/work/calibre/src/calibre/gui2/metadata/single.py:730 +#: /home/kovid/work/calibre/src/calibre/gui2/metadata/single.py:878 msgid "&Metadata" msgstr "&Metaadat" -#: /home/kovid/work/calibre/src/calibre/gui2/metadata/single.py:729 +#: /home/kovid/work/calibre/src/calibre/gui2/metadata/single.py:735 msgid "&Cover and formats" msgstr "&Borító és formátumok" -#: /home/kovid/work/calibre/src/calibre/gui2/metadata/single.py:801 +#: /home/kovid/work/calibre/src/calibre/gui2/metadata/single.py:807 msgid "C&ustom metadata" msgstr "&Egyéni metaadat" -#: /home/kovid/work/calibre/src/calibre/gui2/metadata/single.py:812 +#: /home/kovid/work/calibre/src/calibre/gui2/metadata/single.py:818 msgid "&Comments" msgstr "&Megjegyzések" -#: /home/kovid/work/calibre/src/calibre/gui2/metadata/single.py:878 +#: /home/kovid/work/calibre/src/calibre/gui2/metadata/single.py:884 msgid "Basic metadata" msgstr "Alap metaadat" -#: /home/kovid/work/calibre/src/calibre/gui2/metadata/single_download.py:146 +#: /home/kovid/work/calibre/src/calibre/gui2/metadata/single_download.py:150 msgid "Has cover" msgstr "Van borítója" -#: /home/kovid/work/calibre/src/calibre/gui2/metadata/single_download.py:146 +#: /home/kovid/work/calibre/src/calibre/gui2/metadata/single_download.py:150 msgid "Has summary" msgstr "Van összefoglalója" -#: /home/kovid/work/calibre/src/calibre/gui2/metadata/single_download.py:203 +#: /home/kovid/work/calibre/src/calibre/gui2/metadata/single_download.py:207 msgid "" "The has cover indication is not fully\n" "reliable. Sometimes results marked as not\n" @@ -14619,29 +14752,29 @@ msgstr "" "Néha előfordul, hogy olyan könyvhöz is töltődik le\n" "borító, ami borító nélküliként volt jelölve és fordítva." -#: /home/kovid/work/calibre/src/calibre/gui2/metadata/single_download.py:288 +#: /home/kovid/work/calibre/src/calibre/gui2/metadata/single_download.py:292 msgid "See at" msgstr "Nézze itt:" -#: /home/kovid/work/calibre/src/calibre/gui2/metadata/single_download.py:442 +#: /home/kovid/work/calibre/src/calibre/gui2/metadata/single_download.py:446 msgid "calibre is downloading metadata from: " msgstr "A calibre metaadatokat tölt le a következő helyről: " -#: /home/kovid/work/calibre/src/calibre/gui2/metadata/single_download.py:464 +#: /home/kovid/work/calibre/src/calibre/gui2/metadata/single_download.py:468 msgid "Please wait" msgstr "Kérem várjon" -#: /home/kovid/work/calibre/src/calibre/gui2/metadata/single_download.py:496 +#: /home/kovid/work/calibre/src/calibre/gui2/metadata/single_download.py:500 msgid "Query: " msgstr "Lekérdezés: " -#: /home/kovid/work/calibre/src/calibre/gui2/metadata/single_download.py:515 +#: /home/kovid/work/calibre/src/calibre/gui2/metadata/single_download.py:519 msgid "Failed to download metadata. Click Show Details to see details" msgstr "" "Nem sikerült a metaadatok letöltése. Kattintson a „Részletek” gombra a " "további információkért." -#: /home/kovid/work/calibre/src/calibre/gui2/metadata/single_download.py:524 +#: /home/kovid/work/calibre/src/calibre/gui2/metadata/single_download.py:528 msgid "" "Failed to find any books that match your search. Try making the search " "less specific. For example, use only the author's last name and a " @@ -14653,42 +14786,42 @@ msgstr "" "jellemző szót adjon meg a címből.

További információkért kattintson a " "„Részletek” gombra." -#: /home/kovid/work/calibre/src/calibre/gui2/metadata/single_download.py:632 +#: /home/kovid/work/calibre/src/calibre/gui2/metadata/single_download.py:636 msgid "Current cover" msgstr "Aktuális borító" -#: /home/kovid/work/calibre/src/calibre/gui2/metadata/single_download.py:635 +#: /home/kovid/work/calibre/src/calibre/gui2/metadata/single_download.py:639 msgid "Searching..." msgstr "Keresés…" -#: /home/kovid/work/calibre/src/calibre/gui2/metadata/single_download.py:796 +#: /home/kovid/work/calibre/src/calibre/gui2/metadata/single_download.py:800 #, python-format msgid "Downloading covers for %s, please wait..." msgstr "%s borítójának letöltése, kérem várjon…" -#: /home/kovid/work/calibre/src/calibre/gui2/metadata/single_download.py:827 +#: /home/kovid/work/calibre/src/calibre/gui2/metadata/single_download.py:831 msgid "Failed to download any covers, click \"Show details\" for details." msgstr "" "Nem sikerült a borító letöltése, kattintson a „Részletek” gombra további " "információkért." -#: /home/kovid/work/calibre/src/calibre/gui2/metadata/single_download.py:833 +#: /home/kovid/work/calibre/src/calibre/gui2/metadata/single_download.py:837 #, python-format msgid "Could not find any covers for %s" msgstr "Nem található borító ehhez: %s" -#: /home/kovid/work/calibre/src/calibre/gui2/metadata/single_download.py:835 +#: /home/kovid/work/calibre/src/calibre/gui2/metadata/single_download.py:839 #, python-format msgid "Found %(num)d covers of %(title)s. Pick the one you like best." msgstr "" "%(title)s könyvhöz %(num)d borító is található. Válassza amelyik " "szeretné." -#: /home/kovid/work/calibre/src/calibre/gui2/metadata/single_download.py:924 +#: /home/kovid/work/calibre/src/calibre/gui2/metadata/single_download.py:928 msgid "Downloading metadata..." msgstr "Metaadatok letöltése…" -#: /home/kovid/work/calibre/src/calibre/gui2/metadata/single_download.py:1039 +#: /home/kovid/work/calibre/src/calibre/gui2/metadata/single_download.py:1043 msgid "Downloading cover..." msgstr "Borító letöltése…" @@ -14749,7 +14882,7 @@ msgstr "" "előtt, törlésre kerül miután bekerül a calibre adatbázisba. Biztosan " "folytatja?" -#: /home/kovid/work/calibre/src/calibre/gui2/preferences/adding_ui.py:126 +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/adding_ui.py:135 msgid "" "Automerge: If books with similar titles and authors found, merge the " "incoming formats automatically into\n" @@ -14771,13 +14904,13 @@ msgstr "" "szavak(„the”, „a”, „an”), írásjelek, kis- és nagybetűs eltérések.\n" "A szerző egyezésének vizsgálata viszont betű szerinti." -#: /home/kovid/work/calibre/src/calibre/gui2/preferences/adding_ui.py:131 +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/adding_ui.py:140 msgid "&Automerge added books if they already exist in the calibre library:" msgstr "" "Automatikusan fűzze össze az új könyveket a meglévő bejegyzésekkel, ha azok " "már léteznek a könyvtárban:" -#: /home/kovid/work/calibre/src/calibre/gui2/preferences/adding_ui.py:132 +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/adding_ui.py:141 msgid "" "Automerge: If books with similar titles and authors found, merge the " "incoming formats automatically into\n" @@ -14813,7 +14946,7 @@ msgstr "" "szavak(„the”, „a”, „an”), írásjelek, kis- és nagybetűs eltérések.\n" "A szerző egyezésének vizsgálata viszont betű szerinti." -#: /home/kovid/work/calibre/src/calibre/gui2/preferences/adding_ui.py:142 +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/adding_ui.py:151 msgid "" "Here you can control how calibre will read metadata from the files you add " "to it. calibre can either read metadata from the contents of the file, or " @@ -14822,11 +14955,11 @@ msgstr "" "Itt azt tudja beállítani, hogy a calibre hogyan olvassa ki a metaadatokat a " "fájlokból. A calibre a fájlból, és a fájlnévből is tud metaadatokat olvasni." -#: /home/kovid/work/calibre/src/calibre/gui2/preferences/adding_ui.py:143 +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/adding_ui.py:152 msgid "Read &metadata from file contents rather than file name" msgstr "Metaadatok olvasása elsősorban a fájlból, másodsorban a fájlnévből" -#: /home/kovid/work/calibre/src/calibre/gui2/preferences/adding_ui.py:144 +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/adding_ui.py:153 msgid "" "Swap the firstname and lastname of the author. This affects only metadata " "read from file names." @@ -14834,15 +14967,15 @@ msgstr "" "Az író kereszt- és családnevének felcserélése. Ez csak a fájlnévből kinyert " "metaadatokra van hatással." -#: /home/kovid/work/calibre/src/calibre/gui2/preferences/adding_ui.py:145 +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/adding_ui.py:154 msgid "&Swap author firstname and lastname" msgstr "Az író kereszt- és családnevének felcserélése" -#: /home/kovid/work/calibre/src/calibre/gui2/preferences/adding_ui.py:146 +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/adding_ui.py:155 msgid "&Tags to apply when adding a book:" msgstr "A következő címkék alkalmazása egy könyv hozzáadásakor:" -#: /home/kovid/work/calibre/src/calibre/gui2/preferences/adding_ui.py:147 +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/adding_ui.py:156 msgid "" "A comma-separated list of tags that will be applied to books added to the " "library" @@ -14850,11 +14983,11 @@ msgstr "" "Olyan címkék listája, vesszővel tagolva, melyek minden új könyvtárba " "importált könyvhöz hozzá lesznek adva" -#: /home/kovid/work/calibre/src/calibre/gui2/preferences/adding_ui.py:148 +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/adding_ui.py:157 msgid "&Configure metadata from file name" msgstr "Beállítások a metaadatok &kiolvasására a fájlnévből" -#: /home/kovid/work/calibre/src/calibre/gui2/preferences/adding_ui.py:149 +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/adding_ui.py:158 msgid "" "When using the \"&Copy to library\" action to copy books between libraries, " "preserve the date" @@ -14862,27 +14995,23 @@ msgstr "" "Könyv másik adatbázisba (könyvtárba) történő másolásakor őrizze meg az " "eredeti dátumot" -#: /home/kovid/work/calibre/src/calibre/gui2/preferences/adding_ui.py:150 +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/adding_ui.py:159 msgid "Automatically &convert added books to the current output format" msgstr "" +"A hozzáadott &könyvek automatikus konvertálása a jelenlegi kimeneti " +"formátumba" -#: /home/kovid/work/calibre/src/calibre/gui2/preferences/adding_ui.py:151 +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/adding_ui.py:160 msgid "The Add &Process" msgstr "Könyvek hozzáadásának beállításai" -#: /home/kovid/work/calibre/src/calibre/gui2/preferences/adding_ui.py:152 -msgid "" -"If set, this option will causes calibre to check if a file\n" -" being auto-added is already in the calibre library.\n" -" If it is, a message will pop up asking you whether\n" -" you want to add it anyway." +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/adding_ui.py:161 +msgid "Ignore files with the following extensions when automatically adding " msgstr "" +"A következő kiterjesztésű fájlok ki lesznek hagyva az automatikus " +"hozzáadásból " -#: /home/kovid/work/calibre/src/calibre/gui2/preferences/adding_ui.py:156 -msgid "Check for &duplicates when auto-adding files" -msgstr "Duplikációk ellenőrzése" - -#: /home/kovid/work/calibre/src/calibre/gui2/preferences/adding_ui.py:157 +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/adding_ui.py:162 msgid "" "Specify a folder. Any files you put into this folder will be automatically " "added to calibre (restart required)." @@ -14890,34 +15019,40 @@ msgstr "" "Adjon meg egy mappát. Minden ide helyezett fájl automatikusan hozzá lesz " "adva a calibre adatbázishoz (újraindítás szükséges)." -#: /home/kovid/work/calibre/src/calibre/gui2/preferences/adding_ui.py:158 +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/adding_ui.py:163 msgid "" "WARNING: Files in the above folder will be deleted after being added " "to calibre." msgstr "" "FIGYELEM!A fenti mappában lévő fájlok a hozzáadás után törlődnek." -#: /home/kovid/work/calibre/src/calibre/gui2/preferences/adding_ui.py:159 -msgid "Ignore files with the following extensions when automatically adding " +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/adding_ui.py:164 +msgid "" +"If set, this option will causes calibre to check if a file\n" +" being auto-added is already in the calibre library.\n" +" If it is, a message will pop up asking you whether\n" +" you want to add it anyway." msgstr "" -"A következő kiterjesztésű fájlok ki lesznek hagyva az automatikus " -"hozzáadásból " -#: /home/kovid/work/calibre/src/calibre/gui2/preferences/adding_ui.py:160 +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/adding_ui.py:168 +msgid "Check for &duplicates when auto-adding files" +msgstr "Duplikációk ellenőrzése" + +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/adding_ui.py:169 msgid "Folder to auto-add files from" msgstr "Az automatikus hozzáadás mappája" -#: /home/kovid/work/calibre/src/calibre/gui2/preferences/adding_ui.py:161 +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/adding_ui.py:170 msgid "Browse for folder" msgstr "Mappa tallózása" -#: /home/kovid/work/calibre/src/calibre/gui2/preferences/adding_ui.py:163 +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/adding_ui.py:172 msgid "Automatically &convert added files to the current output format" msgstr "" "A &calibre a hozzáadott fájlokat automatikusan átalakítja az aktuális " "kimeneti formátumba." -#: /home/kovid/work/calibre/src/calibre/gui2/preferences/adding_ui.py:164 +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/adding_ui.py:173 msgid "&Automatic Adding" msgstr "&Automatikus hozzáadás" @@ -15031,97 +15166,101 @@ msgstr "" #: /home/kovid/work/calibre/src/calibre/gui2/preferences/behavior_ui.py:165 msgid "Preferred &input format order:" -msgstr "&Bemeneti formátumok előnyberészesítési sorrendje:" +msgstr "&Bemeneti formátumok előnyben részesítési sorrendje:" #: /home/kovid/work/calibre/src/calibre/gui2/preferences/behavior_ui.py:168 msgid "Use internal &viewer for:" -msgstr "A beépített ol&vasó program használata a következőkhöz:" +msgstr "A beépített ol&vasóprogram használata a következőkhöz:" #: /home/kovid/work/calibre/src/calibre/gui2/preferences/behavior_ui.py:169 msgid "Reset all disabled &confirmation dialogs" msgstr "&Minden letiltott megerősítő párbeszédablak engedélyezése" -#: /home/kovid/work/calibre/src/calibre/gui2/preferences/coloring.py:30 -#: /home/kovid/work/calibre/src/calibre/gui2/preferences/coloring.py:35 +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/coloring.py:26 +msgid "All Columns" +msgstr "Minden oszlop" + +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/coloring.py:32 +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/coloring.py:37 msgid "is true" msgstr "igaz" -#: /home/kovid/work/calibre/src/calibre/gui2/preferences/coloring.py:31 -#: /home/kovid/work/calibre/src/calibre/gui2/preferences/coloring.py:36 +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/coloring.py:33 +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/coloring.py:38 msgid "is false" msgstr "hamis" -#: /home/kovid/work/calibre/src/calibre/gui2/preferences/coloring.py:32 +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/coloring.py:34 msgid "is undefined" msgstr "nem definiált" -#: /home/kovid/work/calibre/src/calibre/gui2/preferences/coloring.py:39 +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/coloring.py:41 msgid "has id" msgstr "azonosítója" -#: /home/kovid/work/calibre/src/calibre/gui2/preferences/coloring.py:40 +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/coloring.py:42 msgid "does not have id" msgstr "azonosítója nem" -#: /home/kovid/work/calibre/src/calibre/gui2/preferences/coloring.py:43 +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/coloring.py:45 msgid "is equal to" msgstr "egyenlő" -#: /home/kovid/work/calibre/src/calibre/gui2/preferences/coloring.py:44 +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/coloring.py:46 msgid "is less than" msgstr "kisebb, mint" -#: /home/kovid/work/calibre/src/calibre/gui2/preferences/coloring.py:45 +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/coloring.py:47 msgid "is greater than" msgstr "nagyobb, mint" -#: /home/kovid/work/calibre/src/calibre/gui2/preferences/coloring.py:48 +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/coloring.py:50 msgid "has" msgstr "van ilyen" -#: /home/kovid/work/calibre/src/calibre/gui2/preferences/coloring.py:49 +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/coloring.py:51 msgid "does not have" msgstr "nincs ilyen" -#: /home/kovid/work/calibre/src/calibre/gui2/preferences/coloring.py:50 +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/coloring.py:52 msgid "has pattern" msgstr "van ilyen mintának megfelelő" -#: /home/kovid/work/calibre/src/calibre/gui2/preferences/coloring.py:51 +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/coloring.py:53 msgid "does not have pattern" msgstr "nincs ilyen mintának megfelelő" -#: /home/kovid/work/calibre/src/calibre/gui2/preferences/coloring.py:52 -#: /home/kovid/work/calibre/src/calibre/gui2/preferences/coloring.py:60 +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/coloring.py:54 +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/coloring.py:62 msgid "is set" msgstr "beállítva" -#: /home/kovid/work/calibre/src/calibre/gui2/preferences/coloring.py:53 -#: /home/kovid/work/calibre/src/calibre/gui2/preferences/coloring.py:61 +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/coloring.py:55 +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/coloring.py:63 msgid "is not set" msgstr "nincs beállítva" -#: /home/kovid/work/calibre/src/calibre/gui2/preferences/coloring.py:56 +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/coloring.py:58 msgid "is" msgstr "egyenlő" -#: /home/kovid/work/calibre/src/calibre/gui2/preferences/coloring.py:57 +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/coloring.py:59 msgid "is not" msgstr "nem" -#: /home/kovid/work/calibre/src/calibre/gui2/preferences/coloring.py:58 +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/coloring.py:60 msgid "matches pattern" msgstr "találatot ad a mintára" -#: /home/kovid/work/calibre/src/calibre/gui2/preferences/coloring.py:59 +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/coloring.py:61 msgid "does not match pattern" msgstr "nem ad találatot a mintára" -#: /home/kovid/work/calibre/src/calibre/gui2/preferences/coloring.py:78 +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/coloring.py:80 msgid "If the ___ column ___ values" msgstr "Ha a(z) ___ oszlop ___ , érték:" -#: /home/kovid/work/calibre/src/calibre/gui2/preferences/coloring.py:217 +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/coloring.py:219 msgid "" "Enter either an identifier type or an identifier type and value of the form " "identifier:value" @@ -15129,82 +15268,82 @@ msgstr "" "Adjon meg egy azonosítót, vagy egy azonosítót és értéket az azonosító:érték " "formában" -#: /home/kovid/work/calibre/src/calibre/gui2/preferences/coloring.py:220 +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/coloring.py:222 msgid "" "Enter a 3 letter ISO language code, like fra for French or deu for German or " "eng for English. You can also use the full language name, in which case " "calibre will try to automatically convert it to the language code." msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/preferences/coloring.py:225 +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/coloring.py:227 msgid "Enter a number" msgstr "Adjon meg egy számot" -#: /home/kovid/work/calibre/src/calibre/gui2/preferences/coloring.py:230 +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/coloring.py:232 msgid "Enter a date in the format YYYY-MM-DD" msgstr "Adjon megy dátumot a következő formában: ÉÉÉÉ-HH-NN" -#: /home/kovid/work/calibre/src/calibre/gui2/preferences/coloring.py:232 +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/coloring.py:234 msgid "Enter a string." msgstr "Adjon meg egy karakterláncot." -#: /home/kovid/work/calibre/src/calibre/gui2/preferences/coloring.py:234 +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/coloring.py:236 msgid "Enter a regular expression" msgstr "Adjon meg egy reguláris kifejezést" -#: /home/kovid/work/calibre/src/calibre/gui2/preferences/coloring.py:236 +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/coloring.py:238 #, python-format msgid "You can match multiple values by separating them with %s" msgstr "Több értéket is megadhat ezzel elválasztva: %s" -#: /home/kovid/work/calibre/src/calibre/gui2/preferences/coloring.py:251 +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/coloring.py:253 msgid "Create/edit a column coloring rule" msgstr "Oszlop színezési szabály létrehozása/szerkesztése" -#: /home/kovid/work/calibre/src/calibre/gui2/preferences/coloring.py:256 +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/coloring.py:258 msgid "Create a coloring rule by filling in the boxes below" msgstr "Készítsen színezési szabályokat az alábbi eszközökkel" -#: /home/kovid/work/calibre/src/calibre/gui2/preferences/coloring.py:270 +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/coloring.py:272 msgid "to" msgstr "erre:" -#: /home/kovid/work/calibre/src/calibre/gui2/preferences/coloring.py:281 +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/coloring.py:283 msgid "Only if the following conditions are all satisfied:" msgstr "Csak ha minden alábbi feltétel teljesül:" -#: /home/kovid/work/calibre/src/calibre/gui2/preferences/coloring.py:291 +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/coloring.py:293 msgid "Add another condition" msgstr "Újabb feltétel hozzáadása" -#: /home/kovid/work/calibre/src/calibre/gui2/preferences/coloring.py:295 +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/coloring.py:297 msgid "You can disable a condition by blanking all of its boxes" msgstr "" "Egy feltételt úgy kapcsolhat ki, hogy a hozzá tartozó értékeket üresre " "állítja" -#: /home/kovid/work/calibre/src/calibre/gui2/preferences/coloring.py:338 +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/coloring.py:339 msgid "Sample Text" msgstr "Mintaszöveg" -#: /home/kovid/work/calibre/src/calibre/gui2/preferences/coloring.py:380 +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/coloring.py:381 msgid "Invalid condition" msgstr "Érvénytelen feltétel" -#: /home/kovid/work/calibre/src/calibre/gui2/preferences/coloring.py:381 +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/coloring.py:382 #, python-format msgid "One of the conditions for this rule is invalid: %s" msgstr "A következő szabály egyik feltétele nem megfelelő: %s" -#: /home/kovid/work/calibre/src/calibre/gui2/preferences/coloring.py:386 +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/coloring.py:387 msgid "No conditions" msgstr "Nincs feltétel megadva" -#: /home/kovid/work/calibre/src/calibre/gui2/preferences/coloring.py:387 +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/coloring.py:388 msgid "You must specify at least one non-empty condition for this rule" msgstr "Legalább egy, nem üres feltételt meg kell adnia a szabályhoz" -#: /home/kovid/work/calibre/src/calibre/gui2/preferences/coloring.py:475 +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/coloring.py:479 #, python-format msgid "" "\n" @@ -15217,7 +15356,7 @@ msgstr "" "

%(rule)s
\n" " " -#: /home/kovid/work/calibre/src/calibre/gui2/preferences/coloring.py:480 +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/coloring.py:484 #, python-format msgid "" "

Set the color of %(col)s to %(color)s if the " @@ -15232,7 +15371,7 @@ msgstr "" "

    %(rule)s
\n" " " -#: /home/kovid/work/calibre/src/calibre/gui2/preferences/coloring.py:495 +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/coloring.py:500 #, python-format msgid "" "
  • If the %(col)s column %(action)s value: %(val)s" @@ -15240,7 +15379,7 @@ msgstr "" "
  • Ha a(z) %(col)s oszlop %(action)s értékű(kel/re..): " "%(val)s" -#: /home/kovid/work/calibre/src/calibre/gui2/preferences/coloring.py:511 +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/coloring.py:516 msgid "" "You can control the color of columns in the book list by creating \"rules\" " "that tell calibre what color to use. Click the Add Rule button below to get " @@ -15250,36 +15389,36 @@ msgstr "" "kattintson a „Szabály hozzáadása” gombra.

    A szabályra történő dupla " "kattintással módosíthatja a már meglévőt." -#: /home/kovid/work/calibre/src/calibre/gui2/preferences/coloring.py:519 +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/coloring.py:524 msgid "Add Rule" msgstr "Sz&abály hozzáadása" -#: /home/kovid/work/calibre/src/calibre/gui2/preferences/coloring.py:522 +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/coloring.py:527 msgid "Remove Rule" msgstr "Szabály &eltávolítása" -#: /home/kovid/work/calibre/src/calibre/gui2/preferences/coloring.py:539 +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/coloring.py:544 msgid "Move the selected rule up" msgstr "A kiválasztott szabály mozgatása felfelé" -#: /home/kovid/work/calibre/src/calibre/gui2/preferences/coloring.py:544 +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/coloring.py:549 msgid "Move the selected rule down" msgstr "A kiválasztott szabály mozgatása lefelé" -#: /home/kovid/work/calibre/src/calibre/gui2/preferences/coloring.py:552 +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/coloring.py:557 msgid "Add Advanced Rule" msgstr "Össze&tett szabály hozzáadása" -#: /home/kovid/work/calibre/src/calibre/gui2/preferences/coloring.py:600 +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/coloring.py:605 msgid "No rule selected" msgstr "Nincs kiválasztott szabály" -#: /home/kovid/work/calibre/src/calibre/gui2/preferences/coloring.py:601 +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/coloring.py:606 #, python-format msgid "No rule selected for %s." msgstr "Nincs kiválasztott szabály a következőhöz: %s." -#: /home/kovid/work/calibre/src/calibre/gui2/preferences/coloring.py:606 +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/coloring.py:611 msgid "removal" msgstr "eltávolítás" @@ -15766,6 +15905,9 @@ msgid "" "A device (%s) is already detected by calibre. If you wish to debug the " "detection of another device, first disconnect this device." msgstr "" +"Egy (%s) eszközt már felismert a calibre. Ha szeretné egy másik eszköz " +"kapcsolódását ellenőrizni, ezt a felismert eszközt először le kell " +"választania." #: /home/kovid/work/calibre/src/calibre/gui2/preferences/device_debug.py:57 msgid "Debugging failed" @@ -15822,17 +15964,17 @@ msgid "" "automatically sent for downloaded news to all email addresses that have Auto-" "send checked." msgstr "" -"A calibre könyveket tud továbbítani Önnek e-mailen keresztül. Ha bejelöli az " -"Automatikus küldést, akkor az e-mailek a letöltött hírekkel együtt " +"A calibre könyveket tud továbbítani Önnek emaiben. Ha bejelöli az " +"Automatikus küldést, akkor az emailek a letöltött hírekkel együtt " "postázódnak az összes címre." #: /home/kovid/work/calibre/src/calibre/gui2/preferences/email_ui.py:67 msgid "Add an email address to which to send books" -msgstr "E-mail cím hozzáadása könyvküldéshez" +msgstr "Email cím hozzáadása könyvküldéshez" #: /home/kovid/work/calibre/src/calibre/gui2/preferences/email_ui.py:68 msgid "&Add email" -msgstr "E-mail &hozzáadása" +msgstr "Email &hozzáadása" #: /home/kovid/work/calibre/src/calibre/gui2/preferences/email_ui.py:69 msgid "Make &default" @@ -15840,11 +15982,11 @@ msgstr "Legyen &alapértelmezett" #: /home/kovid/work/calibre/src/calibre/gui2/preferences/email_ui.py:70 msgid "&Remove email" -msgstr "E-mail &eltávolítása" +msgstr "Email &eltávolítása" #: /home/kovid/work/calibre/src/calibre/gui2/preferences/emailp.py:28 msgid "Email" -msgstr "E-mail" +msgstr "Email" #: /home/kovid/work/calibre/src/calibre/gui2/preferences/emailp.py:28 msgid "Subject" @@ -15852,7 +15994,7 @@ msgstr "Tárgy" #: /home/kovid/work/calibre/src/calibre/gui2/preferences/emailp.py:29 msgid "Alias" -msgstr "" +msgstr "Álnév" #: /home/kovid/work/calibre/src/calibre/gui2/preferences/emailp.py:29 msgid "Auto send" @@ -15861,7 +16003,7 @@ msgstr "Auto küld" #: /home/kovid/work/calibre/src/calibre/gui2/preferences/emailp.py:34 msgid "Formats to email. The first matching format will be sent." msgstr "" -"E-mailben elküldendő formátum. Az első létező formátum kerül elküldésre." +"Emailben elküldendő formátum. Az első létező formátum kerül elküldésre." #: /home/kovid/work/calibre/src/calibre/gui2/preferences/emailp.py:35 msgid "" @@ -15869,7 +16011,7 @@ msgid "" "used for the subject. Also, the same templates used for \"Save to disk\" " "such as {title} and {author_sort} can be used here." msgstr "" -"Az e-mail tárgya. Ha üres, akkor a cím lesz a tárgy. A „Mentés lemezre” " +"Az email tárgya. Ha üres, akkor a cím lesz a tárgy. A „Mentés lemezre” " "sablonjait, mint a {title} vagy az {author_sort} is használhatja." #: /home/kovid/work/calibre/src/calibre/gui2/preferences/emailp.py:39 @@ -15878,8 +16020,8 @@ msgid "" "address (provided it is in one of the listed formats)." msgstr "" "Ha be van jelölve, akkor a letöltött hírek automatikusan
    el lesznek " -"küldve e-mailben erre a címre (ha létezik a „Formátumok” oszlopban " -"beírtaknak megfelelő)" +"küldve emailben erre a címre (ha létezik a „Formátumok” oszlopban beírtaknak " +"megfelelő)" #: /home/kovid/work/calibre/src/calibre/gui2/preferences/emailp.py:42 msgid "Friendly name to use for this email address" @@ -15887,14 +16029,20 @@ msgstr "" #: /home/kovid/work/calibre/src/calibre/gui2/preferences/emailp.py:128 msgid "new email address" -msgstr "új e-mail cím" +msgstr "új email cím" -#: /home/kovid/work/calibre/src/calibre/gui2/preferences/ignored_devices.py:24 +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/ignored_devices.py:26 msgid "" "The list of devices that you have asked calibre to ignore. Uncheck a device " "to have calibre stop ignoring it." msgstr "" +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/ignored_devices.py:37 +msgid "" +"The list of device plugins you have disabled. Uncheck an entry to enable the " +"plugin. calibre cannot detect devices that are managed by disabled plugins." +msgstr "" + #: /home/kovid/work/calibre/src/calibre/gui2/preferences/look_feel.py:103 msgid "Narrow" msgstr "Keskeny" @@ -15940,7 +16088,7 @@ msgid "Never" msgstr "Soha" #: /home/kovid/work/calibre/src/calibre/gui2/preferences/look_feel.py:149 -#: /home/kovid/work/calibre/src/calibre/gui2/tag_browser/view.py:557 +#: /home/kovid/work/calibre/src/calibre/gui2/tag_browser/view.py:593 msgid "By first letter" msgstr "Első betű szerint" @@ -16049,7 +16197,7 @@ msgstr "Mozgatás lefelé" #: /home/kovid/work/calibre/src/calibre/gui2/preferences/look_feel_ui.py:267 msgid "Default author link template:" -msgstr "Alapértelmezett szerzőhivatkozás minta:" +msgstr "Alapértelmezett szerzőhivatkozás sablon:" #: /home/kovid/work/calibre/src/calibre/gui2/preferences/look_feel_ui.py:268 msgid "" @@ -16062,15 +16210,15 @@ msgstr "" #: /home/kovid/work/calibre/src/calibre/gui2/preferences/look_feel_ui.py:273 msgid "Show &cover in the book details panel" -msgstr "" +msgstr "&Borító megjelenítése a könyv részletei panelen" #: /home/kovid/work/calibre/src/calibre/gui2/preferences/look_feel_ui.py:274 msgid "Show the size of the book's cover in pixels" -msgstr "" +msgstr "A borítókép méretét mutatja meg képpontban" #: /home/kovid/work/calibre/src/calibre/gui2/preferences/look_feel_ui.py:275 msgid "Show cover &size" -msgstr "" +msgstr "Borítóméret &mutatása" #: /home/kovid/work/calibre/src/calibre/gui2/preferences/look_feel_ui.py:277 msgid "" @@ -16116,7 +16264,7 @@ msgstr "" #: /home/kovid/work/calibre/src/calibre/gui2/preferences/look_feel_ui.py:292 msgid "Categories not to partition:" -msgstr "" +msgstr "Csoportosítás nélküli kategóriák:" #: /home/kovid/work/calibre/src/calibre/gui2/preferences/look_feel_ui.py:293 msgid "" @@ -16156,7 +16304,7 @@ msgstr "Ha a borítóböngésző külön ablakban van, az legyen &teljes képern #: /home/kovid/work/calibre/src/calibre/gui2/preferences/look_feel_ui.py:305 #, python-format msgid "You can press the %s keys to toggle full screen mode." -msgstr "A következő billentyűkkel válthat teljes képernyős üzemmódra: %s" +msgstr "A következő billentyűkkel válthat teljesképernyős üzemmódra: %s" #: /home/kovid/work/calibre/src/calibre/gui2/preferences/main.py:231 #: /home/kovid/work/calibre/src/calibre/gui2/preferences/tweaks_ui.py:123 @@ -16236,11 +16384,11 @@ msgstr "Nincs forrás kiválasztva" msgid "No source selected, cannot configure." msgstr "Nincs forrás kiválasztva, beállítás nem lehetséges." -#: /home/kovid/work/calibre/src/calibre/gui2/preferences/metadata_sources_ui.py:119 +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/metadata_sources_ui.py:126 msgid "Metadata sources" msgstr "Metaadat források" -#: /home/kovid/work/calibre/src/calibre/gui2/preferences/metadata_sources_ui.py:120 +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/metadata_sources_ui.py:127 msgid "" "Disable any metadata sources you do not want by unchecking them. You can " "also set the cover priority. Covers from sources that have a higher " @@ -16250,41 +16398,41 @@ msgstr "" "prioritását is beállíthatja. A magasabb prioritású (kisebb számmal jelölt) " "források élveznek elsőbbséget csoportos metaadat letöltéskor.\n" -#: /home/kovid/work/calibre/src/calibre/gui2/preferences/metadata_sources_ui.py:122 +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/metadata_sources_ui.py:129 msgid "" "Sources with a red X next to their names must be configured before they will " "be used. " msgstr "" "A piros X-szel jelölt források használat előtt beállítást igényelnek. " -#: /home/kovid/work/calibre/src/calibre/gui2/preferences/metadata_sources_ui.py:123 +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/metadata_sources_ui.py:130 msgid "Configure selected source" msgstr "Kiválasztott &forrás beállítása" -#: /home/kovid/work/calibre/src/calibre/gui2/preferences/metadata_sources_ui.py:125 +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/metadata_sources_ui.py:132 msgid "" "If you uncheck any fields, metadata for those fields will not be downloaded" msgstr "A be nem jelölt mezőkhöz nem lesz letöltve metaadat" -#: /home/kovid/work/calibre/src/calibre/gui2/preferences/metadata_sources_ui.py:126 +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/metadata_sources_ui.py:133 msgid "&Select all" msgstr "&Mindegyik kijelölése" -#: /home/kovid/work/calibre/src/calibre/gui2/preferences/metadata_sources_ui.py:127 +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/metadata_sources_ui.py:134 msgid "&Clear all" msgstr "Egyik &sem" -#: /home/kovid/work/calibre/src/calibre/gui2/preferences/metadata_sources_ui.py:128 +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/metadata_sources_ui.py:135 msgid "" "Restore your own subset of checked fields that you define using the 'Set as " "default' button" msgstr "A „Legyen alapértelmezett” gombbal beállított értékek visszaállítása" -#: /home/kovid/work/calibre/src/calibre/gui2/preferences/metadata_sources_ui.py:129 +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/metadata_sources_ui.py:136 msgid "&Select default" msgstr "&Alapértelmezettek kijelölése" -#: /home/kovid/work/calibre/src/calibre/gui2/preferences/metadata_sources_ui.py:130 +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/metadata_sources_ui.py:137 msgid "" "Store the currently checked fields as a default you can restore using the " "'Select default' button" @@ -16292,37 +16440,37 @@ msgstr "" "A jelenleg kijelölt mezők legyenek azok az alapértelmezettek, melyek az " "„Alapértelmezettek kijelölése” gombbal visszaállíthatók." -#: /home/kovid/work/calibre/src/calibre/gui2/preferences/metadata_sources_ui.py:131 +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/metadata_sources_ui.py:138 msgid "&Set as default" msgstr "&Legyen alapértelmezett" -#: /home/kovid/work/calibre/src/calibre/gui2/preferences/metadata_sources_ui.py:132 +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/metadata_sources_ui.py:139 msgid "Convert all downloaded comments to plain &text" msgstr "Megjegyzések konver&tálása egyszerű, formázatlan szöveggé" -#: /home/kovid/work/calibre/src/calibre/gui2/preferences/metadata_sources_ui.py:133 +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/metadata_sources_ui.py:140 msgid "Swap author names from FN LN to LN, FN" msgstr "Szer&ző keresztnév(KN) vezetéknév(VN) cseréje VN, KN-re" -#: /home/kovid/work/calibre/src/calibre/gui2/preferences/metadata_sources_ui.py:134 +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/metadata_sources_ui.py:141 msgid "Max. number of &tags to download:" msgstr "Letölthető címkék ma&ximális száma:" -#: /home/kovid/work/calibre/src/calibre/gui2/preferences/metadata_sources_ui.py:135 +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/metadata_sources_ui.py:142 msgid "Max. &time to wait after first match is found:" msgstr "Az első találat utáni maximális &várakozási idő:" -#: /home/kovid/work/calibre/src/calibre/gui2/preferences/metadata_sources_ui.py:136 -#: /home/kovid/work/calibre/src/calibre/gui2/preferences/metadata_sources_ui.py:138 +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/metadata_sources_ui.py:143 +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/metadata_sources_ui.py:145 #: /home/kovid/work/calibre/src/calibre/gui2/viewer/config_ui.py:421 msgid " secs" msgstr " másodperc" -#: /home/kovid/work/calibre/src/calibre/gui2/preferences/metadata_sources_ui.py:137 +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/metadata_sources_ui.py:144 msgid "Max. time to wait after first &cover is found:" msgstr "Az első &borítótalálat utáni maximális várakozási idő:" -#: /home/kovid/work/calibre/src/calibre/gui2/preferences/metadata_sources_ui.py:139 +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/metadata_sources_ui.py:146 msgid "" "

    Different metadata sources have different sets of tags for the same book. " "If this option is checked, then calibre will use the smaller tag sets. These " @@ -16342,11 +16490,11 @@ msgstr "" "címkekészlettel. A valóságban a legtöbb forrás bővített címkekészlettel " "rendelkezik." -#: /home/kovid/work/calibre/src/calibre/gui2/preferences/metadata_sources_ui.py:141 +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/metadata_sources_ui.py:148 msgid "Prefer &fewer tags" msgstr "Kevesebb &címke letöltése" -#: /home/kovid/work/calibre/src/calibre/gui2/preferences/metadata_sources_ui.py:142 +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/metadata_sources_ui.py:149 msgid "Use published date of \"first edition\" (from worldcat.org)" msgstr "" @@ -16397,7 +16545,7 @@ msgstr "" #: /home/kovid/work/calibre/src/calibre/gui2/preferences/misc_ui.py:77 msgid "Debug &device detection" -msgstr "Eszközkapcsolódás ellenőrzése" +msgstr "Eszközkapcsolódás &ellenőrzése" #: /home/kovid/work/calibre/src/calibre/gui2/preferences/misc_ui.py:78 msgid "Get information to setup the &user defined device" @@ -16627,7 +16775,7 @@ msgstr "" #: /home/kovid/work/calibre/src/calibre/gui2/preferences/plugins.py:368 #, python-format msgid "Are you sure you want to remove the plugin: %s?" -msgstr "" +msgstr "Biztosan törli a következő bővítményt: %s?" #: /home/kovid/work/calibre/src/calibre/gui2/preferences/plugins.py:373 msgid "Plugin {0} successfully removed" @@ -16702,7 +16850,7 @@ msgid "" "particular book does not have some metadata, the variable will be replaced " "by the empty string." msgstr "" -"A lenti mintát pontosítva be tudja állítani a lemezre történő mentés mappa " +"A lenti sablont pontosítva be tudja állítani a lemezre történő mentés mappa " "és fájlneveit. A „/” karakter jeleni az almappákat. A használható metaadat-" "változókat a listamezőben láthatja. Ha az adott könyv nem tartalmazza a " "megadott metaadatot, akkor az egy üres karakterlánccal lesz helyettesítve." @@ -16855,7 +17003,7 @@ msgstr "A keresés elkezdődik, ahogy beírja a szöveget" #: /home/kovid/work/calibre/src/calibre/gui2/preferences/search_ui.py:169 msgid "Unaccented characters match accented characters" -msgstr "" +msgstr "Ékezetes karakterek megfeleltetése ékezet nélkülivel" #: /home/kovid/work/calibre/src/calibre/gui2/preferences/search_ui.py:170 msgid "" @@ -17406,10 +17554,6 @@ msgstr "Váltás az eszköz és a könyvtár nézet között" msgid "Separator" msgstr "Elválasztó" -#: /home/kovid/work/calibre/src/calibre/gui2/preferences/toolbar.py:59 -msgid "Choose library" -msgstr "Könyvtár kiválasztása" - #: /home/kovid/work/calibre/src/calibre/gui2/preferences/toolbar.py:220 msgid "The main toolbar" msgstr "A fő eszköztár" @@ -17970,7 +18114,7 @@ msgstr "" #: /home/kovid/work/calibre/src/calibre/gui2/store/search/search.py:178 msgid "You must enter a title, author or keyword to search for." -msgstr "" +msgstr "Meg kell adnia egy címet, szerzőt vagy kulcsszót a kereséshez." #: /home/kovid/work/calibre/src/calibre/gui2/store/search/search.py:300 msgid "Customize get books search" @@ -17994,19 +18138,19 @@ msgstr "Könyv letöltése" #: /home/kovid/work/calibre/src/calibre/gui2/store/search/search_ui.py:171 msgid "Search by title" -msgstr "" +msgstr "Keresés cím alapjáán" #: /home/kovid/work/calibre/src/calibre/gui2/store/search/search_ui.py:173 msgid "Search by author" -msgstr "" +msgstr "Keresés szerző alapján" #: /home/kovid/work/calibre/src/calibre/gui2/store/search/search_ui.py:175 msgid "&Keyword:" -msgstr "" +msgstr "&Kulcsszó:" #: /home/kovid/work/calibre/src/calibre/gui2/store/search/search_ui.py:176 msgid "Search by any keyword" -msgstr "" +msgstr "Keresés bármilyen kulcsszó alapján" #: /home/kovid/work/calibre/src/calibre/gui2/store/search/search_ui.py:182 msgid "Open a selected book in the system's web browser" @@ -18022,7 +18166,7 @@ msgid "Books:" msgstr "Könyvek:" #: /home/kovid/work/calibre/src/calibre/gui2/store/search/search_ui.py:186 -#: /usr/src/qt-everywhere-opensource-src-4.8.2/src/gui/widgets/qdialogbuttonbox.cpp:670 +#: /usr/src/qt-everywhere-opensource-src-4.8.4/src/gui/widgets/qdialogbuttonbox.cpp:670 msgid "&Close" msgstr "&Bezár" @@ -18071,7 +18215,7 @@ msgstr "&Lekérdezés:" #: /home/kovid/work/calibre/src/calibre/gui2/store/stores/mobileread/store_dialog_ui.py:79 #: /home/kovid/work/calibre/src/calibre/gui2/store/web_store_dialog_ui.py:63 -#: /usr/src/qt-everywhere-opensource-src-4.8.2/src/gui/widgets/qdialogbuttonbox.cpp:670 +#: /usr/src/qt-everywhere-opensource-src-4.8.4/src/gui/widgets/qdialogbuttonbox.cpp:670 msgid "Close" msgstr "Bezárás" @@ -18106,49 +18250,49 @@ msgstr "Frissítés" msgid "%p%" msgstr "%p%" -#: /home/kovid/work/calibre/src/calibre/gui2/tag_browser/model.py:307 +#: /home/kovid/work/calibre/src/calibre/gui2/tag_browser/model.py:330 msgid "The grouped search term name is \"{0}\"" msgstr "A csoportosított keresési feltétel neve: {0}" -#: /home/kovid/work/calibre/src/calibre/gui2/tag_browser/model.py:766 +#: /home/kovid/work/calibre/src/calibre/gui2/tag_browser/model.py:794 msgid "" "Changing the authors for several books can take a while. Are you sure?" msgstr "" "Sok könyv szerzőjének megváltoztatása eltarthat egy ideig. Folytatja?" -#: /home/kovid/work/calibre/src/calibre/gui2/tag_browser/model.py:771 +#: /home/kovid/work/calibre/src/calibre/gui2/tag_browser/model.py:799 msgid "" "Changing the metadata for that many books can take a while. Are you sure?" msgstr "" "Sok könyv metaadatának megváltoztatása hosszú ideig is eltarthat. Folytatja?" -#: /home/kovid/work/calibre/src/calibre/gui2/tag_browser/model.py:858 +#: /home/kovid/work/calibre/src/calibre/gui2/tag_browser/model.py:886 #: /home/kovid/work/calibre/src/calibre/library/database2.py:495 msgid "Searches" msgstr "Keresés" -#: /home/kovid/work/calibre/src/calibre/gui2/tag_browser/model.py:935 -#: /home/kovid/work/calibre/src/calibre/gui2/tag_browser/model.py:955 -#: /home/kovid/work/calibre/src/calibre/gui2/tag_browser/model.py:964 +#: /home/kovid/work/calibre/src/calibre/gui2/tag_browser/model.py:963 +#: /home/kovid/work/calibre/src/calibre/gui2/tag_browser/model.py:983 +#: /home/kovid/work/calibre/src/calibre/gui2/tag_browser/model.py:992 msgid "Rename user category" msgstr "Felhasználói kategória átnevezése" -#: /home/kovid/work/calibre/src/calibre/gui2/tag_browser/model.py:936 +#: /home/kovid/work/calibre/src/calibre/gui2/tag_browser/model.py:964 msgid "You cannot use periods in the name when renaming user categories" msgstr "" "Felhasználói kategória átnevezésénél az új névben nem használhat pontot." -#: /home/kovid/work/calibre/src/calibre/gui2/tag_browser/model.py:956 -#: /home/kovid/work/calibre/src/calibre/gui2/tag_browser/model.py:965 +#: /home/kovid/work/calibre/src/calibre/gui2/tag_browser/model.py:984 +#: /home/kovid/work/calibre/src/calibre/gui2/tag_browser/model.py:993 #, python-format msgid "The name %s is already used" msgstr "A(z) %s név már használatban van" -#: /home/kovid/work/calibre/src/calibre/gui2/tag_browser/model.py:984 +#: /home/kovid/work/calibre/src/calibre/gui2/tag_browser/model.py:1012 msgid "Duplicate search name" msgstr "Duplikált keresési név" -#: /home/kovid/work/calibre/src/calibre/gui2/tag_browser/model.py:985 +#: /home/kovid/work/calibre/src/calibre/gui2/tag_browser/model.py:1013 #, python-format msgid "The saved search name %s is already used." msgstr "A menteni kívánt keresésnél megadott név (%s) már használatban van." @@ -18170,13 +18314,13 @@ msgid "Manage Tags" msgstr "Címkék kezelése" #: /home/kovid/work/calibre/src/calibre/gui2/tag_browser/ui.py:57 -#: /home/kovid/work/calibre/src/calibre/gui2/tag_browser/view.py:540 -#: /home/kovid/work/calibre/src/calibre/gui2/tag_browser/view.py:544 +#: /home/kovid/work/calibre/src/calibre/gui2/tag_browser/view.py:575 +#: /home/kovid/work/calibre/src/calibre/gui2/tag_browser/view.py:579 msgid "Manage User Categories" msgstr "Felhasználói kategóriák kezelése" #: /home/kovid/work/calibre/src/calibre/gui2/tag_browser/ui.py:59 -#: /home/kovid/work/calibre/src/calibre/gui2/tag_browser/view.py:532 +#: /home/kovid/work/calibre/src/calibre/gui2/tag_browser/view.py:561 msgid "Manage Saved Searches" msgstr "Elmentett keresések kezelése" @@ -18277,7 +18421,7 @@ msgid "Alter Tag Browser" msgstr "Címkeböngésző megváltoztatása" #: /home/kovid/work/calibre/src/calibre/gui2/tag_browser/ui.py:397 -#: /home/kovid/work/calibre/src/calibre/library/server/browse.py:276 +#: /home/kovid/work/calibre/src/calibre/library/server/browse.py:279 msgid "Sort by" msgstr "Rendezés" @@ -18320,105 +18464,118 @@ msgstr "" "Ezek a kategória kezelők a fenti címke böngészőből is elérhetők az elemekre " "történő jobb egérgomb kattintással" -#: /home/kovid/work/calibre/src/calibre/gui2/tag_browser/view.py:413 -#: /home/kovid/work/calibre/src/calibre/gui2/tag_browser/view.py:452 -#: /home/kovid/work/calibre/src/calibre/gui2/tag_browser/view.py:482 +#: /home/kovid/work/calibre/src/calibre/gui2/tag_browser/view.py:304 +#, python-format +msgid "Change Icon for: %s" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/tag_browser/view.py:442 +#: /home/kovid/work/calibre/src/calibre/gui2/tag_browser/view.py:481 +#: /home/kovid/work/calibre/src/calibre/gui2/tag_browser/view.py:511 #, python-format msgid "Rename %s" msgstr "%s átnevezése" -#: /home/kovid/work/calibre/src/calibre/gui2/tag_browser/view.py:419 +#: /home/kovid/work/calibre/src/calibre/gui2/tag_browser/view.py:448 #, python-format msgid "Delete %s" msgstr "%s törlése" -#: /home/kovid/work/calibre/src/calibre/gui2/tag_browser/view.py:423 +#: /home/kovid/work/calibre/src/calibre/gui2/tag_browser/view.py:452 #, python-format msgid "Edit sort for %s" msgstr "%s rendezési forma szerkesztése" -#: /home/kovid/work/calibre/src/calibre/gui2/tag_browser/view.py:426 +#: /home/kovid/work/calibre/src/calibre/gui2/tag_browser/view.py:455 #, python-format msgid "Edit link for %s" msgstr "%s linkjének szerkesztése" -#: /home/kovid/work/calibre/src/calibre/gui2/tag_browser/view.py:433 +#: /home/kovid/work/calibre/src/calibre/gui2/tag_browser/view.py:462 #, python-format msgid "Add %s to user category" msgstr "%s hozzáadása felhasználói kategóriához" -#: /home/kovid/work/calibre/src/calibre/gui2/tag_browser/view.py:446 +#: /home/kovid/work/calibre/src/calibre/gui2/tag_browser/view.py:475 #, python-format msgid "Children of %s" msgstr "%s gyermeke" -#: /home/kovid/work/calibre/src/calibre/gui2/tag_browser/view.py:456 +#: /home/kovid/work/calibre/src/calibre/gui2/tag_browser/view.py:485 #, python-format msgid "Delete search %s" msgstr "%s keresés törlése" -#: /home/kovid/work/calibre/src/calibre/gui2/tag_browser/view.py:461 +#: /home/kovid/work/calibre/src/calibre/gui2/tag_browser/view.py:490 #, python-format msgid "Remove %(item)s from category %(cat)s" msgstr "%(item)s eltávolítása a %(cat)s kategóriából" -#: /home/kovid/work/calibre/src/calibre/gui2/tag_browser/view.py:474 +#: /home/kovid/work/calibre/src/calibre/gui2/tag_browser/view.py:503 #, python-format msgid "Search for everything but %s" msgstr "A(z) %s kivételével mindent keressen" -#: /home/kovid/work/calibre/src/calibre/gui2/tag_browser/view.py:486 +#: /home/kovid/work/calibre/src/calibre/gui2/tag_browser/view.py:515 #, python-format msgid "Add sub-category to %s" msgstr "Alkategória hozzáadása a következőhöz: %s" -#: /home/kovid/work/calibre/src/calibre/gui2/tag_browser/view.py:490 +#: /home/kovid/work/calibre/src/calibre/gui2/tag_browser/view.py:519 #, python-format msgid "Delete user category %s" msgstr "%s felhasználói kategória törlése" -#: /home/kovid/work/calibre/src/calibre/gui2/tag_browser/view.py:495 +#: /home/kovid/work/calibre/src/calibre/gui2/tag_browser/view.py:524 #, python-format msgid "Hide category %s" msgstr "A(z) %s kategória elrejtése" -#: /home/kovid/work/calibre/src/calibre/gui2/tag_browser/view.py:499 +#: /home/kovid/work/calibre/src/calibre/gui2/tag_browser/view.py:528 msgid "Show category" msgstr "Kategória megjelenítése" -#: /home/kovid/work/calibre/src/calibre/gui2/tag_browser/view.py:509 +#: /home/kovid/work/calibre/src/calibre/gui2/tag_browser/view.py:538 #, python-format msgid "Search for books in category %s" msgstr "Könyvek keresése a(z) %s kategóriában" -#: /home/kovid/work/calibre/src/calibre/gui2/tag_browser/view.py:515 +#: /home/kovid/work/calibre/src/calibre/gui2/tag_browser/view.py:544 #, python-format msgid "Search for books not in category %s" msgstr "Ne keressen könyveket a(z) %s kategóriában" -#: /home/kovid/work/calibre/src/calibre/gui2/tag_browser/view.py:524 -#: /home/kovid/work/calibre/src/calibre/gui2/tag_browser/view.py:529 +#: /home/kovid/work/calibre/src/calibre/gui2/tag_browser/view.py:553 +#: /home/kovid/work/calibre/src/calibre/gui2/tag_browser/view.py:558 #, python-format msgid "Manage %s" msgstr "A(z) %s kezelése" -#: /home/kovid/work/calibre/src/calibre/gui2/tag_browser/view.py:551 +#: /home/kovid/work/calibre/src/calibre/gui2/tag_browser/view.py:566 +msgid "Change category icon" +msgstr "Kategória ikon megváltoztatása" + +#: /home/kovid/work/calibre/src/calibre/gui2/tag_browser/view.py:568 +msgid "Restore default icon" +msgstr "Alapértelmezett ikon visszaállítása" + +#: /home/kovid/work/calibre/src/calibre/gui2/tag_browser/view.py:586 msgid "Show all categories" msgstr "Minden kategória megjelenítése" -#: /home/kovid/work/calibre/src/calibre/gui2/tag_browser/view.py:554 +#: /home/kovid/work/calibre/src/calibre/gui2/tag_browser/view.py:590 msgid "Change sub-categorization scheme" msgstr "Az alkategória séma megváltoztatása" -#: /home/kovid/work/calibre/src/calibre/gui2/tag_browser/view.py:555 +#: /home/kovid/work/calibre/src/calibre/gui2/tag_browser/view.py:591 msgid "Disable" msgstr "Tiltás" -#: /home/kovid/work/calibre/src/calibre/gui2/tag_browser/view.py:559 +#: /home/kovid/work/calibre/src/calibre/gui2/tag_browser/view.py:595 msgid "Partition" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/tag_browser/view.py:574 +#: /home/kovid/work/calibre/src/calibre/gui2/tag_browser/view.py:610 msgid "First letter is usable only when sorting by name" msgstr "Az első betű csak a névre rendezéskor használható" @@ -18427,13 +18584,54 @@ msgstr "Az első betű csak a névre rendezéskor használható" msgid "Convert book %(num)d of %(total)d (%(title)s)" msgstr "Könyvek konvertálása: %(num)d/%(total)d (%(title)s)" -#: /home/kovid/work/calibre/src/calibre/gui2/tools.py:101 -#: /home/kovid/work/calibre/src/calibre/gui2/tools.py:227 +#: /home/kovid/work/calibre/src/calibre/gui2/tools.py:97 +msgid "Could not convert" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/tools.py:98 +#, python-format +msgid "" +"Could not convert %s as it has no ebook files. If you think it should " +"have files, but calibre is not finding them, that is most likely because you " +"moved the book's files around outside of calibre. You will need to find " +"those files and re-add them to calibre." +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/tools.py:108 +#, python-format +msgid "No supported formats (Available formats: %s)" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/tools.py:111 +msgid "This book has no actual ebook files" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/tools.py:116 +#: /home/kovid/work/calibre/src/calibre/gui2/tools.py:242 msgid "Could not convert some books" msgstr "Néhány könyvet nem sikerült konvertálni" -#: /home/kovid/work/calibre/src/calibre/gui2/tools.py:102 -#: /home/kovid/work/calibre/src/calibre/gui2/tools.py:228 +#: /home/kovid/work/calibre/src/calibre/gui2/tools.py:117 +#, python-format +msgid "" +"Could not convert %(num)d of %(tot)d books, because no supported source " +"formats were found." +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/tools.py:151 +msgid "Queueing books for bulk conversion" +msgstr "Könyvek sorba állítása csoportos konvertáláshoz" + +#: /home/kovid/work/calibre/src/calibre/gui2/tools.py:216 +msgid "Queueing " +msgstr "Sorbaállás " + +#: /home/kovid/work/calibre/src/calibre/gui2/tools.py:217 +#, python-format +msgid "Convert book %(num)d of %(tot)d (%(title)s)" +msgstr "Könyvek konvertálása: %(num)d/%(tot)d (%(title)s)" + +#: /home/kovid/work/calibre/src/calibre/gui2/tools.py:243 #, python-format msgid "" "Could not convert %(num)d of %(tot)d books, because no suitable source " @@ -18442,28 +18640,15 @@ msgstr "" "Nem sikerült %(num)d konvertálása %(tot)d könyvből, mert nem található " "megfelelő forrásformátum." -#: /home/kovid/work/calibre/src/calibre/gui2/tools.py:136 -msgid "Queueing books for bulk conversion" -msgstr "Könyvek sorba állítása csoportos konvertáláshoz" - -#: /home/kovid/work/calibre/src/calibre/gui2/tools.py:201 -msgid "Queueing " -msgstr "Sorbaállás " - -#: /home/kovid/work/calibre/src/calibre/gui2/tools.py:202 -#, python-format -msgid "Convert book %(num)d of %(tot)d (%(title)s)" -msgstr "Könyvek konvertálása: %(num)d/%(tot)d (%(title)s)" - -#: /home/kovid/work/calibre/src/calibre/gui2/tools.py:273 +#: /home/kovid/work/calibre/src/calibre/gui2/tools.py:288 msgid "Fetch news from " msgstr "Hírek letöltése a következő helyről: " -#: /home/kovid/work/calibre/src/calibre/gui2/tools.py:346 +#: /home/kovid/work/calibre/src/calibre/gui2/tools.py:361 msgid "Convert existing" msgstr "Létező átalakítása" -#: /home/kovid/work/calibre/src/calibre/gui2/tools.py:347 +#: /home/kovid/work/calibre/src/calibre/gui2/tools.py:362 #, python-format msgid "" "The following books have already been converted to %s format. Do you wish to " @@ -18494,7 +18679,7 @@ msgstr "Az aktuális keresés törlése" #: /home/kovid/work/calibre/src/calibre/gui2/ui.py:368 msgid "Debug mode" -msgstr "Hibakövetési mód" +msgstr "Hibakeresési mód" #: /home/kovid/work/calibre/src/calibre/gui2/ui.py:369 #, python-format @@ -18589,7 +18774,7 @@ msgid "" msgstr "" " kommunikál az eszközzel!
    \n" " A kilépés adatvesztést okozhat az eszközön.
    \n" -" Biztos, hogy ki akarsz lépni??" +" Biztos, hogy ki akar lépni??" #: /home/kovid/work/calibre/src/calibre/gui2/ui.py:745 msgid "Active jobs" @@ -18603,53 +18788,53 @@ msgstr "" "tovább fut a tálcán. A bezáráshoz válassza a Kilépést a tálcaikon " "menüjéből." -#: /home/kovid/work/calibre/src/calibre/gui2/update.py:79 +#: /home/kovid/work/calibre/src/calibre/gui2/update.py:86 #, python-format msgid "" "New version %(ver)s of %(app)s is available for download. See the new features." msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/update.py:85 +#: /home/kovid/work/calibre/src/calibre/gui2/update.py:92 msgid "Update available!" msgstr "Új frissítés érhető el!" -#: /home/kovid/work/calibre/src/calibre/gui2/update.py:90 +#: /home/kovid/work/calibre/src/calibre/gui2/update.py:97 msgid "Show this notification for future updates" msgstr "Mutassa ezt az ablakot az elkövetkezendő frissítések során is" -#: /home/kovid/work/calibre/src/calibre/gui2/update.py:95 +#: /home/kovid/work/calibre/src/calibre/gui2/update.py:102 msgid "&Get update" msgstr "&Letöltés" -#: /home/kovid/work/calibre/src/calibre/gui2/update.py:99 +#: /home/kovid/work/calibre/src/calibre/gui2/update.py:106 msgid "Update &plugins" msgstr "&Bővítmény frissítése" -#: /home/kovid/work/calibre/src/calibre/gui2/update.py:158 +#: /home/kovid/work/calibre/src/calibre/gui2/update.py:162 #, python-format msgid " (%d plugin updates)" msgstr " (%d bővítmény frissítése)" -#: /home/kovid/work/calibre/src/calibre/gui2/update.py:161 +#: /home/kovid/work/calibre/src/calibre/gui2/update.py:165 msgid "Update found" msgstr "Új verzió" -#: /home/kovid/work/calibre/src/calibre/gui2/update.py:164 +#: /home/kovid/work/calibre/src/calibre/gui2/update.py:168 msgid "updated plugins" -msgstr "Frissített bővítmények" +msgstr "frissített bővítmény" -#: /home/kovid/work/calibre/src/calibre/gui2/update.py:192 -#: /home/kovid/work/calibre/src/calibre/gui2/update.py:197 +#: /home/kovid/work/calibre/src/calibre/gui2/update.py:196 +#: /home/kovid/work/calibre/src/calibre/gui2/update.py:201 msgid "Plugin Updates" msgstr "Bővítmény frissítések" -#: /home/kovid/work/calibre/src/calibre/gui2/update.py:195 +#: /home/kovid/work/calibre/src/calibre/gui2/update.py:199 #, python-format msgid "There are %d plugin updates available" msgstr "%d bővítményhez érhető el frissítés" -#: /home/kovid/work/calibre/src/calibre/gui2/update.py:199 +#: /home/kovid/work/calibre/src/calibre/gui2/update.py:203 msgid "Install and configure user plugins" msgstr "Felhasználó bővítmények telepítése és beállítása" @@ -18692,7 +18877,7 @@ msgstr "Szerkesztés" #: /home/kovid/work/calibre/src/calibre/gui2/viewer/bookmarkmanager_ui.py:65 #: /home/kovid/work/calibre/src/calibre/gui2/viewer/config_ui.py:413 #: /home/kovid/work/calibre/src/calibre/gui2/viewer/config_ui.py:417 -#: /usr/src/qt-everywhere-opensource-src-4.8.2/src/gui/widgets/qdialogbuttonbox.cpp:676 +#: /usr/src/qt-everywhere-opensource-src-4.8.4/src/gui/widgets/qdialogbuttonbox.cpp:676 msgid "Reset" msgstr "Visszaállítás" @@ -18783,11 +18968,11 @@ msgstr "" #: /home/kovid/work/calibre/src/calibre/gui2/viewer/config.py:63 msgid "Start viewer in full screen mode" -msgstr "" +msgstr "Olvasóprogram indítása teljesképernyős módban" #: /home/kovid/work/calibre/src/calibre/gui2/viewer/config.py:65 msgid "Show full screen usage help" -msgstr "" +msgstr "Teljesképernyő súgó mutatása" #: /home/kovid/work/calibre/src/calibre/gui2/viewer/config.py:74 msgid "Font options" @@ -18862,7 +19047,7 @@ msgstr "E-book olvasó beállítása" #: /home/kovid/work/calibre/src/calibre/gui2/viewer/config_ui.py:377 msgid "&Default font size:" -msgstr "&Alap betűméret:" +msgstr "Alapértelmezett &betűméret:" #: /home/kovid/work/calibre/src/calibre/gui2/viewer/config_ui.py:382 msgid "Serif" @@ -18937,7 +19122,7 @@ msgstr "Teljesképernyős módban a szöveg maximális szélessége:" #: /home/kovid/work/calibre/src/calibre/gui2/viewer/config_ui.py:404 msgid "Show &clock in full screen mode" -msgstr "&Óra megjelenítése teljes képernyős módban" +msgstr "&Óra megjelenítése teljesképernyős módban" #: /home/kovid/work/calibre/src/calibre/gui2/viewer/config_ui.py:405 msgid "Show reading &position in full screen mode" @@ -18945,19 +19130,19 @@ msgstr "" #: /home/kovid/work/calibre/src/calibre/gui2/viewer/config_ui.py:406 msgid "Show &scrollbar in full screen mode" -msgstr "" +msgstr "Gördítősáv mutatása teljesképernyős módban" #: /home/kovid/work/calibre/src/calibre/gui2/viewer/config_ui.py:407 msgid "&Start viewer in full screen mode" -msgstr "" +msgstr "Olvasóprogram &indítása teljesképernyős módban" #: /home/kovid/work/calibre/src/calibre/gui2/viewer/config_ui.py:408 msgid "Show &help message when starting full screen mode" -msgstr "" +msgstr "&Súgóüzenet megjelenítése teljesképernyős üzemmódban" #: /home/kovid/work/calibre/src/calibre/gui2/viewer/config_ui.py:409 msgid "F&ull screen options" -msgstr "&Teljes képernyős beállítások" +msgstr "&Teljesképernyős beállítások" #: /home/kovid/work/calibre/src/calibre/gui2/viewer/config_ui.py:410 msgid "Background color:" @@ -19075,7 +19260,7 @@ msgstr "" #: /home/kovid/work/calibre/src/calibre/gui2/viewer/config_ui.py:442 msgid "Delete a saved theme:" -msgstr "" +msgstr "Mentett téma törlése:" #: /home/kovid/work/calibre/src/calibre/gui2/viewer/config_ui.py:444 msgid "&Theming" @@ -19091,11 +19276,11 @@ msgstr "Keresés szótárban" #: /home/kovid/work/calibre/src/calibre/gui2/viewer/documentview.py:507 msgid "View &image..." -msgstr "" +msgstr "Kép megtek&intése..." #: /home/kovid/work/calibre/src/calibre/gui2/viewer/documentview.py:509 msgid "View &table..." -msgstr "" +msgstr "&Táblázat megtekintése..." #: /home/kovid/work/calibre/src/calibre/gui2/viewer/documentview.py:512 msgid "&Search for next occurrence" @@ -19131,8 +19316,8 @@ msgid "Section End" msgstr "Szakasz vége" #: /home/kovid/work/calibre/src/calibre/gui2/viewer/documentview.py:547 -msgid "Normal font size" -msgstr "" +msgid "Default font size" +msgstr "Alpértelmezett betűméret" #: /home/kovid/work/calibre/src/calibre/gui2/viewer/documentview.py:614 #, python-format @@ -19155,7 +19340,7 @@ msgstr "&Mentés másként" #: /home/kovid/work/calibre/src/calibre/gui2/viewer/image_popup.py:41 msgid "&Rotate" -msgstr "" +msgstr "Fo&rgatás" #: /home/kovid/work/calibre/src/calibre/gui2/viewer/image_popup.py:67 msgid "Choose a file to save to" @@ -19252,7 +19437,7 @@ msgstr "Szöveg keresése a könyvben" #: /home/kovid/work/calibre/src/calibre/gui2/viewer/main.py:246 #, python-format msgid "Toggle full screen (%s)" -msgstr "Teljesképernyős mód be/ki (%s)" +msgstr "Teljesképernyős be/ki (%s)" #: /home/kovid/work/calibre/src/calibre/gui2/viewer/main.py:286 msgid "Full screen mode" @@ -19306,19 +19491,19 @@ msgstr "E-bookok" #: /home/kovid/work/calibre/src/calibre/gui2/viewer/main.py:700 #, python-format msgid "" -"Make font size %(which)s\n" +"%(which)s font size\n" "Current magnification: %(mag).1f" msgstr "" -"Betűméret változtatása %(which)s\n" -"Jelenlegi nagyítás mértéke: %(mag).1f" +"%(which)s betű méret\n" +"Jelenlegi nagyítás: %(mag).1f" #: /home/kovid/work/calibre/src/calibre/gui2/viewer/main.py:702 -msgid "larger" -msgstr "nagyobbra" +msgid "Increase" +msgstr "" #: /home/kovid/work/calibre/src/calibre/gui2/viewer/main.py:704 -msgid "smaller" -msgstr "kisebbre" +msgid "Decrease" +msgstr "" #: /home/kovid/work/calibre/src/calibre/gui2/viewer/main.py:722 #, python-format @@ -19361,7 +19546,7 @@ msgstr "Nem lehet megnyitni a könyvet" #: /home/kovid/work/calibre/src/calibre/gui2/viewer/main.py:988 msgid "Unknown error" -msgstr "" +msgstr "Ismeretlen hiba" #: /home/kovid/work/calibre/src/calibre/gui2/viewer/main.py:1103 msgid "Options to control the ebook viewer" @@ -19378,7 +19563,7 @@ msgstr "" msgid "" "If specified, viewer window will try to open full screen when started." msgstr "" -"Ha be van állítva, akkor az olvasóprogram megpróbál teljes képernyősként " +"Ha be van állítva, akkor az olvasóprogram megpróbál teljesképernyősként " "indulni" #: /home/kovid/work/calibre/src/calibre/gui2/viewer/main.py:1118 @@ -19401,77 +19586,77 @@ msgstr "" "\n" "E-book olvasása.\n" -#: /home/kovid/work/calibre/src/calibre/gui2/viewer/main_ui.py:204 +#: /home/kovid/work/calibre/src/calibre/gui2/viewer/main_ui.py:211 msgid "E-book Viewer" msgstr "E-book olvasó" -#: /home/kovid/work/calibre/src/calibre/gui2/viewer/main_ui.py:205 +#: /home/kovid/work/calibre/src/calibre/gui2/viewer/main_ui.py:212 msgid "Close dictionary" msgstr "Szótár bezárása" -#: /home/kovid/work/calibre/src/calibre/gui2/viewer/main_ui.py:207 +#: /home/kovid/work/calibre/src/calibre/gui2/viewer/main_ui.py:214 msgid "toolBar" msgstr "eszközTár" -#: /home/kovid/work/calibre/src/calibre/gui2/viewer/main_ui.py:210 +#: /home/kovid/work/calibre/src/calibre/gui2/viewer/main_ui.py:217 msgid "Next page" msgstr "Következő oldal" -#: /home/kovid/work/calibre/src/calibre/gui2/viewer/main_ui.py:211 +#: /home/kovid/work/calibre/src/calibre/gui2/viewer/main_ui.py:218 msgid "Previous page" msgstr "Előző oldal" -#: /home/kovid/work/calibre/src/calibre/gui2/viewer/main_ui.py:212 -msgid "Font size larger" -msgstr "Nagyobb betűméret" +#: /home/kovid/work/calibre/src/calibre/gui2/viewer/main_ui.py:219 +msgid "Increase font size" +msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/viewer/main_ui.py:213 -msgid "Font size smaller" -msgstr "Kisebb betűméret" +#: /home/kovid/work/calibre/src/calibre/gui2/viewer/main_ui.py:220 +msgid "Decrease font size" +msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/viewer/main_ui.py:217 +#: /home/kovid/work/calibre/src/calibre/gui2/viewer/main_ui.py:224 msgid "Find next" msgstr "Következő keresése" -#: /home/kovid/work/calibre/src/calibre/gui2/viewer/main_ui.py:218 +#: /home/kovid/work/calibre/src/calibre/gui2/viewer/main_ui.py:225 msgid "Find next occurrence" msgstr "Következő előfordulás keresése" -#: /home/kovid/work/calibre/src/calibre/gui2/viewer/main_ui.py:222 +#: /home/kovid/work/calibre/src/calibre/gui2/viewer/main_ui.py:229 msgid "Reference Mode" msgstr "Referencia Mód" -#: /home/kovid/work/calibre/src/calibre/gui2/viewer/main_ui.py:223 +#: /home/kovid/work/calibre/src/calibre/gui2/viewer/main_ui.py:230 msgid "Bookmark" msgstr "Könyvjelző" -#: /home/kovid/work/calibre/src/calibre/gui2/viewer/main_ui.py:224 +#: /home/kovid/work/calibre/src/calibre/gui2/viewer/main_ui.py:231 msgid "Toggle full screen" -msgstr "Teljes képernyő be/ki" +msgstr "Teljesképernyő be/ki" -#: /home/kovid/work/calibre/src/calibre/gui2/viewer/main_ui.py:225 +#: /home/kovid/work/calibre/src/calibre/gui2/viewer/main_ui.py:232 msgid "Print" msgstr "Nyomtatás" -#: /home/kovid/work/calibre/src/calibre/gui2/viewer/main_ui.py:226 +#: /home/kovid/work/calibre/src/calibre/gui2/viewer/main_ui.py:233 msgid "Find previous" msgstr "Előző keresése" -#: /home/kovid/work/calibre/src/calibre/gui2/viewer/main_ui.py:227 +#: /home/kovid/work/calibre/src/calibre/gui2/viewer/main_ui.py:234 msgid "Find previous occurrence" msgstr "Előző előfordulás keresése" -#: /home/kovid/work/calibre/src/calibre/gui2/viewer/main_ui.py:229 +#: /home/kovid/work/calibre/src/calibre/gui2/viewer/main_ui.py:236 msgid "Toggle Paged mode" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/viewer/main_ui.py:230 +#: /home/kovid/work/calibre/src/calibre/gui2/viewer/main_ui.py:237 msgid "Load theme" -msgstr "" +msgstr "Téma betöltése" -#: /home/kovid/work/calibre/src/calibre/gui2/viewer/main_ui.py:231 +#: /home/kovid/work/calibre/src/calibre/gui2/viewer/main_ui.py:238 msgid "Load a theme" -msgstr "" +msgstr "Téma betöltése" #: /home/kovid/work/calibre/src/calibre/gui2/viewer/printing.py:68 msgid "Failed to render" @@ -19484,7 +19669,7 @@ msgstr "Nem sikerült a következő dokumentum renderelése: %s" #: /home/kovid/work/calibre/src/calibre/gui2/viewer/table_popup.py:57 msgid "View Table" -msgstr "" +msgstr "Táblázat" #: /home/kovid/work/calibre/src/calibre/gui2/viewer/table_popup.py:72 msgid "No table found" @@ -19584,7 +19769,7 @@ msgid "< &Back" msgstr "< &Vissza" #: /home/kovid/work/calibre/src/calibre/gui2/wizard/__init__.py:856 -#: /usr/src/qt-everywhere-opensource-src-4.8.2/src/gui/widgets/qdialogbuttonbox.cpp:667 +#: /usr/src/qt-everywhere-opensource-src-4.8.4/src/gui/widgets/qdialogbuttonbox.cpp:667 msgid "Cancel" msgstr "Mégse" @@ -19658,15 +19843,15 @@ msgid "" "button below. You will also have to register your gmail address in your " "Amazon account." msgstr "" -"

    A calibre e-mailben automatikusan tud könyveket küldeni Kindle " -"olvasójára. Ehhez alul be kell állítania az e-mail küldést. A legegyszerűbb, " -"ha létrehoz egy ingyenes gmail fiókot és a " -"„Gmail használata” gombra kattint. Ezt a Gmail címet természetesen az Ön " -"Amazon fiókjában is regisztrálni kell." +"

    A calibre emailben automatikusan tud könyveket küldeni Kindle olvasójára. " +"Ehhez alul be kell állítania az email küldést. A legegyszerűbb, ha létrehoz " +"egy ingyenes gmail fiókot és a „Gmail " +"használata” gombra kattint. Ezt a Gmail címet természetesen az Ön Amazon " +"fiókjában is regisztrálni kell." #: /home/kovid/work/calibre/src/calibre/gui2/wizard/kindle_ui.py:50 msgid "&Kindle email:" -msgstr "&Kindle e-mail:" +msgstr "&Kindle email:" #: /home/kovid/work/calibre/src/calibre/gui2/wizard/library_ui.py:57 msgid "Choose your &language:" @@ -19706,25 +19891,25 @@ msgstr "A levél elküldése sikerült." #: /home/kovid/work/calibre/src/calibre/gui2/wizard/send_email.py:59 msgid "Setup sending email using" -msgstr "Küldésre szolgáló e-mail beállítása a következővel:" +msgstr "Küldésre szolgáló email beállítása a következővel:" #: /home/kovid/work/calibre/src/calibre/gui2/wizard/send_email.py:61 msgid "" "If you don't have an account, you can sign up for a free {name} email " "account at http://{url}. {extra}" msgstr "" -"Ha még nincs fiókja, regisztrálhat egy ingyenes {name} e-mail címet a http://{url} oldalon. {extra}" #: /home/kovid/work/calibre/src/calibre/gui2/wizard/send_email.py:68 #, python-format msgid "Your %s &email address:" -msgstr "Az Ön %s &e-mail címe:" +msgstr "Az Ön %s &email címe:" #: /home/kovid/work/calibre/src/calibre/gui2/wizard/send_email.py:69 #, python-format msgid "Your %s &username:" -msgstr "%s &felhasználóneve:" +msgstr "Az Ön %s &felhasználóneve:" #: /home/kovid/work/calibre/src/calibre/gui2/wizard/send_email.py:70 #, python-format @@ -19738,9 +19923,9 @@ msgid "" "your %s email address to the allowed email addresses in your Amazon.com " "Kindle management page." msgstr "" -"Ha tervezi, hogy Kindle eszközére e-mailen keresztül küld könyveket, akkor " -"a(z) %s e-mail címét hozzá kell adnia az Amazon.com-ra bejelentkezés után a " -"„Your Account > Manage Your Kindle > Personal Document Settings > Approved " +"Ha tervezi, hogy Kindle eszközére emailben küld könyveket, akkor a(z) %s e-" +"mail címét hozzá kell adnia az Amazon.com-ra bejelentkezés után a „Your " +"Account > Manage Your Kindle > Personal Document Settings > Approved " "Personal Document E-mail List” résznél az „Add a new approved e-mail " "address”-re kattintva." @@ -19755,7 +19940,7 @@ msgstr "Helytelen felhasználói név" #: /home/kovid/work/calibre/src/calibre/gui2/wizard/send_email.py:103 #, python-format msgid "%s needs the full email address as your username" -msgstr "%s esetén a teljes e-mail cím használandó felhasználónévként" +msgstr "%s esetén a teljes email cím használandó felhasználónévként" #: /home/kovid/work/calibre/src/calibre/gui2/wizard/send_email.py:154 msgid "OK to proceed?" @@ -19765,7 +19950,7 @@ msgstr "Kívánja folytatni?" msgid "" "This will display your email password on the screen. Is it OK to proceed?" msgstr "" -"Ennek hatására megjelenik az e-mail címéhez tartozó jelszó a képernyőn. " +"Ennek hatására megjelenik az email címéhez tartozó jelszó a képernyőn. " "Kívánja folytatni?" #: /home/kovid/work/calibre/src/calibre/gui2/wizard/send_email.py:199 @@ -19775,7 +19960,7 @@ msgid "" "this case, I strongly suggest you setup a free gmail account instead." msgstr "" "Ha új Hotmail fiókot hoz létre, a Microsoft időnként felszólíthatja a " -"felhasználói fiók érvényesítésére mielőtt engedi, hogy a calibre e-mailt " +"felhasználói fiók érvényesítésére mielőtt engedi, hogy a calibre emailt " "küldjön. Javasoljuk, hogy ebben az esetben használjon GMail fiókot." #: /home/kovid/work/calibre/src/calibre/gui2/wizard/send_email.py:221 @@ -19811,13 +19996,13 @@ msgstr "" #: /home/kovid/work/calibre/src/calibre/gui2/wizard/send_email_ui.py:124 msgid "Send email &from:" -msgstr "&E-mail küldése a következőről:" +msgstr "&Email küldése a következőről:" #: /home/kovid/work/calibre/src/calibre/gui2/wizard/send_email_ui.py:125 msgid "" "

    This is what will be present in the From: field of emails sent by " "calibre.
    Set it to your email address" -msgstr "Ez fog megjelenni a Feladó mezőben.
    Írja be az e-mail címét." +msgstr "Ez fog megjelenni a Feladó mezőben.
    Írja be az email címét." #: /home/kovid/work/calibre/src/calibre/gui2/wizard/send_email_ui.py:126 msgid "" @@ -19834,7 +20019,7 @@ msgstr "&Levelező kiszolgáló" #: /home/kovid/work/calibre/src/calibre/gui2/wizard/send_email_ui.py:128 msgid "calibre can optionally use a server to send mail" msgstr "" -"A calibre opcionálisan tud kiszolgálót is használni az e-mailek " +"A calibre opcionálisan tud kiszolgálót is használni az emailek " "küldéséhez" #: /home/kovid/work/calibre/src/calibre/gui2/wizard/send_email_ui.py:129 @@ -19856,7 +20041,7 @@ msgstr "Kimenő levek kiszolgálójának portja. Alapbeállítás: 25" #: /home/kovid/work/calibre/src/calibre/gui2/wizard/send_email_ui.py:134 msgid "Your username on the mail server" -msgstr "Az Ön Felhasználóneve a levelező kiszolgálón" +msgstr "Az Ön felhasználóneve a levelező kiszolgálón" #: /home/kovid/work/calibre/src/calibre/gui2/wizard/send_email_ui.py:136 msgid "Your password on the mail server" @@ -19909,7 +20094,7 @@ msgstr "Hotmail használata" #: /home/kovid/work/calibre/src/calibre/gui2/wizard/send_email_ui.py:147 msgid "&Test email" -msgstr "Tes&zt e-mail küldése" +msgstr "Tes&zt email küldése" #: /home/kovid/work/calibre/src/calibre/gui2/wizard/stanza_ui.py:49 msgid "" @@ -20104,7 +20289,7 @@ msgstr "" "Alapértelmezett: '%default'\n" "BIBTEX kimeneti formátum esetén használható" -#: /home/kovid/work/calibre/src/calibre/library/catalogs/csv_xml.py:32 +#: /home/kovid/work/calibre/src/calibre/library/catalogs/csv_xml.py:33 #, python-format msgid "" "The fields to output when cataloging books in the database. Should be a " @@ -20122,7 +20307,7 @@ msgstr "" "Alapértelmezett: „%%default”\n" "Alkalmazható: CSV, XML kimeneti formátumoknál" -#: /home/kovid/work/calibre/src/calibre/library/catalogs/csv_xml.py:45 +#: /home/kovid/work/calibre/src/calibre/library/catalogs/csv_xml.py:46 #, python-format msgid "" "Output field to sort on.\n" @@ -20135,7 +20320,7 @@ msgstr "" "Alapértelmezett: '%default'\n" "Alkalmazható: CSV és XML kimeneti formátumnál" -#: /home/kovid/work/calibre/src/calibre/library/catalogs/epub_mobi.py:42 +#: /home/kovid/work/calibre/src/calibre/library/catalogs/epub_mobi.py:43 #, python-format msgid "" "Title of generated catalog used as title in metadata.\n" @@ -20143,7 +20328,7 @@ msgid "" "Applies to: AZW3, ePub, MOBI output formats" msgstr "" -#: /home/kovid/work/calibre/src/calibre/library/catalogs/epub_mobi.py:49 +#: /home/kovid/work/calibre/src/calibre/library/catalogs/epub_mobi.py:50 #, python-format msgid "" "Create cross-references in Authors section for books with multiple authors.\n" @@ -20151,7 +20336,7 @@ msgid "" "Applies to: AZW3, ePub, MOBI output formats" msgstr "" -#: /home/kovid/work/calibre/src/calibre/library/catalogs/epub_mobi.py:56 +#: /home/kovid/work/calibre/src/calibre/library/catalogs/epub_mobi.py:57 #, python-format msgid "" "Save the output from different stages of the conversion pipeline to the " @@ -20161,7 +20346,7 @@ msgid "" "Applies to: AZW3, ePub, MOBI output formats" msgstr "" -#: /home/kovid/work/calibre/src/calibre/library/catalogs/epub_mobi.py:66 +#: /home/kovid/work/calibre/src/calibre/library/catalogs/epub_mobi.py:67 #, python-format msgid "" "Regex describing tags to exclude as genres.\n" @@ -20170,7 +20355,7 @@ msgid "" "Applies to: AZW3, ePub, MOBI output formats" msgstr "" -#: /home/kovid/work/calibre/src/calibre/library/catalogs/epub_mobi.py:73 +#: /home/kovid/work/calibre/src/calibre/library/catalogs/epub_mobi.py:74 msgid "" "Specifies the rules used to exclude books from the generated catalog.\n" "The model for an exclusion rule is either\n" @@ -20184,7 +20369,7 @@ msgid "" "Default: \n" msgstr "" -#: /home/kovid/work/calibre/src/calibre/library/catalogs/epub_mobi.py:86 +#: /home/kovid/work/calibre/src/calibre/library/catalogs/epub_mobi.py:87 #, python-format msgid "" "Include 'Authors' section in catalog.\n" @@ -20192,7 +20377,7 @@ msgid "" "Applies to: AZW3, ePub, MOBI output formats" msgstr "" -#: /home/kovid/work/calibre/src/calibre/library/catalogs/epub_mobi.py:93 +#: /home/kovid/work/calibre/src/calibre/library/catalogs/epub_mobi.py:94 #, python-format msgid "" "Include 'Descriptions' section in catalog.\n" @@ -20200,7 +20385,7 @@ msgid "" "Applies to: AZW3, ePub, MOBI output formats" msgstr "" -#: /home/kovid/work/calibre/src/calibre/library/catalogs/epub_mobi.py:100 +#: /home/kovid/work/calibre/src/calibre/library/catalogs/epub_mobi.py:101 #, python-format msgid "" "Include 'Genres' section in catalog.\n" @@ -20208,7 +20393,7 @@ msgid "" "Applies to: AZW3, ePub, MOBI output formats" msgstr "" -#: /home/kovid/work/calibre/src/calibre/library/catalogs/epub_mobi.py:107 +#: /home/kovid/work/calibre/src/calibre/library/catalogs/epub_mobi.py:108 #, python-format msgid "" "Include 'Titles' section in catalog.\n" @@ -20216,7 +20401,7 @@ msgid "" "Applies to: AZW3, ePub, MOBI output formats" msgstr "" -#: /home/kovid/work/calibre/src/calibre/library/catalogs/epub_mobi.py:114 +#: /home/kovid/work/calibre/src/calibre/library/catalogs/epub_mobi.py:115 #, python-format msgid "" "Include 'Series' section in catalog.\n" @@ -20224,7 +20409,7 @@ msgid "" "Applies to: AZW3, ePub, MOBI output formats" msgstr "" -#: /home/kovid/work/calibre/src/calibre/library/catalogs/epub_mobi.py:121 +#: /home/kovid/work/calibre/src/calibre/library/catalogs/epub_mobi.py:122 #, python-format msgid "" "Include 'Recently Added' section in catalog.\n" @@ -20232,7 +20417,7 @@ msgid "" "Applies to: AZW3, ePub, MOBI output formats" msgstr "" -#: /home/kovid/work/calibre/src/calibre/library/catalogs/epub_mobi.py:128 +#: /home/kovid/work/calibre/src/calibre/library/catalogs/epub_mobi.py:129 #, python-format msgid "" "Source field for Genres section.\n" @@ -20240,7 +20425,7 @@ msgid "" "Applies to: AZW3, ePub, MOBI output formats" msgstr "" -#: /home/kovid/work/calibre/src/calibre/library/catalogs/epub_mobi.py:135 +#: /home/kovid/work/calibre/src/calibre/library/catalogs/epub_mobi.py:136 #, python-format msgid "" "Custom field containing note text to insert in Description header.\n" @@ -20248,7 +20433,7 @@ msgid "" "Applies to: AZW3, ePub, MOBI output formats" msgstr "" -#: /home/kovid/work/calibre/src/calibre/library/catalogs/epub_mobi.py:142 +#: /home/kovid/work/calibre/src/calibre/library/catalogs/epub_mobi.py:143 #, python-format msgid "" "#:[before|after]:[True|False] specifying:\n" @@ -20259,7 +20444,7 @@ msgid "" "Applies to AZW3, ePub, MOBI output formats" msgstr "" -#: /home/kovid/work/calibre/src/calibre/library/catalogs/epub_mobi.py:152 +#: /home/kovid/work/calibre/src/calibre/library/catalogs/epub_mobi.py:153 #, python-format msgid "" "Specifies the output profile. In some cases, an output profile is required " @@ -20270,7 +20455,7 @@ msgid "" "Applies to: AZW3, ePub, MOBI output formats" msgstr "" -#: /home/kovid/work/calibre/src/calibre/library/catalogs/epub_mobi.py:159 +#: /home/kovid/work/calibre/src/calibre/library/catalogs/epub_mobi.py:160 msgid "" "Specifies the rules used to include prefixes indicating read books, wishlist " "items and other user-specified prefixes.\n" @@ -20280,7 +20465,7 @@ msgid "" "Default:\n" msgstr "" -#: /home/kovid/work/calibre/src/calibre/library/catalogs/epub_mobi.py:168 +#: /home/kovid/work/calibre/src/calibre/library/catalogs/epub_mobi.py:169 #, python-format msgid "" "Replace existing cover when generating the catalog.\n" @@ -20288,7 +20473,7 @@ msgid "" "Applies to: AZW3, ePub, MOBI output formats" msgstr "" -#: /home/kovid/work/calibre/src/calibre/library/catalogs/epub_mobi.py:175 +#: /home/kovid/work/calibre/src/calibre/library/catalogs/epub_mobi.py:176 #, python-format msgid "" "Size hint (in inches) for book covers in catalog.\n" @@ -20297,7 +20482,7 @@ msgid "" "Applies to AZW3, ePub, MOBI output formats" msgstr "" -#: /home/kovid/work/calibre/src/calibre/library/catalogs/epub_mobi.py:286 +#: /home/kovid/work/calibre/src/calibre/library/catalogs/epub_mobi.py:287 msgid "" "\n" "*** Adding 'By Authors' Section required for MOBI output ***" @@ -20305,170 +20490,170 @@ msgstr "" "\n" "*** a „Szerzők szerint” rész hozzáadása szükséges MOBI kimenet esetén ***" -#: /home/kovid/work/calibre/src/calibre/library/catalogs/epub_mobi_builder.py:56 +#: /home/kovid/work/calibre/src/calibre/library/catalogs/epub_mobi_builder.py:57 msgid "Symbols" msgstr "Szimbólumok" -#: /home/kovid/work/calibre/src/calibre/library/catalogs/epub_mobi_builder.py:284 +#: /home/kovid/work/calibre/src/calibre/library/catalogs/epub_mobi_builder.py:288 msgid "No genres to catalog.\n" msgstr "" -#: /home/kovid/work/calibre/src/calibre/library/catalogs/epub_mobi_builder.py:286 +#: /home/kovid/work/calibre/src/calibre/library/catalogs/epub_mobi_builder.py:290 msgid "Check 'Excluded genres' regex in E-book options.\n" msgstr "" -#: /home/kovid/work/calibre/src/calibre/library/catalogs/epub_mobi_builder.py:288 +#: /home/kovid/work/calibre/src/calibre/library/catalogs/epub_mobi_builder.py:292 msgid "No books available to catalog" msgstr "Nincs elérhető könyv a katalógus készítéséhez" -#: /home/kovid/work/calibre/src/calibre/library/catalogs/epub_mobi_builder.py:301 -#: /home/kovid/work/calibre/src/calibre/library/catalogs/epub_mobi_builder.py:2437 -msgid "Titles" -msgstr "" - #: /home/kovid/work/calibre/src/calibre/library/catalogs/epub_mobi_builder.py:305 -msgid "Genres" -msgstr "" - -#: /home/kovid/work/calibre/src/calibre/library/catalogs/epub_mobi_builder.py:307 -#: /home/kovid/work/calibre/src/calibre/library/catalogs/epub_mobi_builder.py:1740 -msgid "Recently Added" -msgstr "" +#: /home/kovid/work/calibre/src/calibre/library/catalogs/epub_mobi_builder.py:2477 +msgid "Titles" +msgstr "Címek" #: /home/kovid/work/calibre/src/calibre/library/catalogs/epub_mobi_builder.py:309 -#: /home/kovid/work/calibre/src/calibre/library/catalogs/epub_mobi_builder.py:1939 -msgid "Recently Read" -msgstr "" +msgid "Genres" +msgstr "Műfajok" #: /home/kovid/work/calibre/src/calibre/library/catalogs/epub_mobi_builder.py:311 +#: /home/kovid/work/calibre/src/calibre/library/catalogs/epub_mobi_builder.py:1780 +msgid "Recently Added" +msgstr "Utoljára hozzáadva" + +#: /home/kovid/work/calibre/src/calibre/library/catalogs/epub_mobi_builder.py:313 +#: /home/kovid/work/calibre/src/calibre/library/catalogs/epub_mobi_builder.py:1979 +msgid "Recently Read" +msgstr "Utoljára olvasva" + +#: /home/kovid/work/calibre/src/calibre/library/catalogs/epub_mobi_builder.py:315 msgid "Descriptions" msgstr "" -#: /home/kovid/work/calibre/src/calibre/library/catalogs/epub_mobi_builder.py:538 +#: /home/kovid/work/calibre/src/calibre/library/catalogs/epub_mobi_builder.py:542 msgid "

    Inconsistent Author Sort values for Author
    " msgstr "" -#: /home/kovid/work/calibre/src/calibre/library/catalogs/epub_mobi_builder.py:555 +#: /home/kovid/work/calibre/src/calibre/library/catalogs/epub_mobi_builder.py:559 msgid "Warning: Inconsistent Author Sort values for Author '{!s}':\n" msgstr "" -#: /home/kovid/work/calibre/src/calibre/library/catalogs/epub_mobi_builder.py:727 +#: /home/kovid/work/calibre/src/calibre/library/catalogs/epub_mobi_builder.py:744 msgid "Sorting database" -msgstr "" +msgstr "Adatbázis rendezése" -#: /home/kovid/work/calibre/src/calibre/library/catalogs/epub_mobi_builder.py:808 +#: /home/kovid/work/calibre/src/calibre/library/catalogs/epub_mobi_builder.py:846 msgid "Sorting titles" -msgstr "" +msgstr "Címek rendezése" -#: /home/kovid/work/calibre/src/calibre/library/catalogs/epub_mobi_builder.py:820 +#: /home/kovid/work/calibre/src/calibre/library/catalogs/epub_mobi_builder.py:858 msgid "" "No books to catalog.\n" "Check 'Excluded books' rules in E-book options.\n" msgstr "" -#: /home/kovid/work/calibre/src/calibre/library/catalogs/epub_mobi_builder.py:822 +#: /home/kovid/work/calibre/src/calibre/library/catalogs/epub_mobi_builder.py:860 msgid "No books available to include in catalog" msgstr "Nincs elérhető könyv a katalógusba illesztéshez" -#: /home/kovid/work/calibre/src/calibre/library/catalogs/epub_mobi_builder.py:2020 +#: /home/kovid/work/calibre/src/calibre/library/catalogs/epub_mobi_builder.py:2060 msgid "Genres HTML" -msgstr "" +msgstr "Műfajok HTML" -#: /home/kovid/work/calibre/src/calibre/library/catalogs/epub_mobi_builder.py:2417 +#: /home/kovid/work/calibre/src/calibre/library/catalogs/epub_mobi_builder.py:2457 msgid "Titles HTML" -msgstr "" +msgstr "Címek HTML" -#: /home/kovid/work/calibre/src/calibre/library/catalogs/epub_mobi_builder.py:2614 -#: /home/kovid/work/calibre/src/calibre/library/catalogs/epub_mobi_builder.py:2616 -#: /home/kovid/work/calibre/src/calibre/library/catalogs/epub_mobi_builder.py:2618 +#: /home/kovid/work/calibre/src/calibre/library/catalogs/epub_mobi_builder.py:2654 +#: /home/kovid/work/calibre/src/calibre/library/catalogs/epub_mobi_builder.py:2656 +#: /home/kovid/work/calibre/src/calibre/library/catalogs/epub_mobi_builder.py:2658 msgid "by " msgstr "" -#: /home/kovid/work/calibre/src/calibre/library/catalogs/epub_mobi_builder.py:2755 +#: /home/kovid/work/calibre/src/calibre/library/catalogs/epub_mobi_builder.py:2795 msgid "Descriptions HTML" msgstr "" -#: /home/kovid/work/calibre/src/calibre/library/catalogs/epub_mobi_builder.py:2759 +#: /home/kovid/work/calibre/src/calibre/library/catalogs/epub_mobi_builder.py:2799 msgid "Description HTML" msgstr "" -#: /home/kovid/work/calibre/src/calibre/library/catalogs/epub_mobi_builder.py:2892 +#: /home/kovid/work/calibre/src/calibre/library/catalogs/epub_mobi_builder.py:2932 msgid "NCX header" -msgstr "" +msgstr "NCX fejléc" -#: /home/kovid/work/calibre/src/calibre/library/catalogs/epub_mobi_builder.py:2971 +#: /home/kovid/work/calibre/src/calibre/library/catalogs/epub_mobi_builder.py:3009 msgid "NCX for Descriptions" msgstr "" -#: /home/kovid/work/calibre/src/calibre/library/catalogs/epub_mobi_builder.py:3098 +#: /home/kovid/work/calibre/src/calibre/library/catalogs/epub_mobi_builder.py:3136 msgid "NCX for Series" -msgstr "" +msgstr "Sorozatok NCX" -#: /home/kovid/work/calibre/src/calibre/library/catalogs/epub_mobi_builder.py:3180 +#: /home/kovid/work/calibre/src/calibre/library/catalogs/epub_mobi_builder.py:3221 #, python-format msgid "Series beginning with %s" msgstr "" -#: /home/kovid/work/calibre/src/calibre/library/catalogs/epub_mobi_builder.py:3182 +#: /home/kovid/work/calibre/src/calibre/library/catalogs/epub_mobi_builder.py:3223 #, python-format msgid "Series beginning with '%s'" msgstr "" -#: /home/kovid/work/calibre/src/calibre/library/catalogs/epub_mobi_builder.py:3226 +#: /home/kovid/work/calibre/src/calibre/library/catalogs/epub_mobi_builder.py:3267 msgid "NCX for Titles" -msgstr "" - -#: /home/kovid/work/calibre/src/calibre/library/catalogs/epub_mobi_builder.py:3310 -#, python-format -msgid "Titles beginning with %s" -msgstr "" - -#: /home/kovid/work/calibre/src/calibre/library/catalogs/epub_mobi_builder.py:3312 -#, python-format -msgid "Titles beginning with '%s'" -msgstr "" +msgstr "Címek NCX" #: /home/kovid/work/calibre/src/calibre/library/catalogs/epub_mobi_builder.py:3354 -msgid "NCX for Authors" -msgstr "" +#, python-format +msgid "Titles beginning with %s" +msgstr "Ezzel kezdődő címek: %s" -#: /home/kovid/work/calibre/src/calibre/library/catalogs/epub_mobi_builder.py:3430 +#: /home/kovid/work/calibre/src/calibre/library/catalogs/epub_mobi_builder.py:3356 +#, python-format +msgid "Titles beginning with '%s'" +msgstr "Ezzel kezdődő címek: %s" + +#: /home/kovid/work/calibre/src/calibre/library/catalogs/epub_mobi_builder.py:3398 +msgid "NCX for Authors" +msgstr "Szerzők NCX" + +#: /home/kovid/work/calibre/src/calibre/library/catalogs/epub_mobi_builder.py:3477 #, python-format msgid "Authors beginning with %s" -msgstr "" +msgstr "Szerzők ezzel kezdődően: %s" -#: /home/kovid/work/calibre/src/calibre/library/catalogs/epub_mobi_builder.py:3432 +#: /home/kovid/work/calibre/src/calibre/library/catalogs/epub_mobi_builder.py:3479 #, python-format msgid "Authors beginning with '%s'" -msgstr "" +msgstr "Szerzők ezzel kezdődően: %s" -#: /home/kovid/work/calibre/src/calibre/library/catalogs/epub_mobi_builder.py:3473 +#: /home/kovid/work/calibre/src/calibre/library/catalogs/epub_mobi_builder.py:3520 msgid "NCX for Recently Added" -msgstr "" +msgstr "Utoljára hozzáadva NCX" -#: /home/kovid/work/calibre/src/calibre/library/catalogs/epub_mobi_builder.py:3666 +#: /home/kovid/work/calibre/src/calibre/library/catalogs/epub_mobi_builder.py:3713 msgid "NCX for Recently Read" -msgstr "" +msgstr "Utoljára olvasva NCX" -#: /home/kovid/work/calibre/src/calibre/library/catalogs/epub_mobi_builder.py:3808 +#: /home/kovid/work/calibre/src/calibre/library/catalogs/epub_mobi_builder.py:3855 msgid "NCX for Genres" -msgstr "" +msgstr "Műfajok NCX" -#: /home/kovid/work/calibre/src/calibre/library/catalogs/epub_mobi_builder.py:3931 +#: /home/kovid/work/calibre/src/calibre/library/catalogs/epub_mobi_builder.py:3980 msgid "Generating OPF" -msgstr "" +msgstr "OPF generálása" -#: /home/kovid/work/calibre/src/calibre/library/catalogs/epub_mobi_builder.py:4311 +#: /home/kovid/work/calibre/src/calibre/library/catalogs/epub_mobi_builder.py:4357 msgid "Thumbnails" -msgstr "" +msgstr "Miniatűrök" -#: /home/kovid/work/calibre/src/calibre/library/catalogs/epub_mobi_builder.py:4317 +#: /home/kovid/work/calibre/src/calibre/library/catalogs/epub_mobi_builder.py:4363 msgid "Thumbnail" -msgstr "" +msgstr "Miniatűr" -#: /home/kovid/work/calibre/src/calibre/library/catalogs/epub_mobi_builder.py:4852 +#: /home/kovid/work/calibre/src/calibre/library/catalogs/epub_mobi_builder.py:4897 msgid "Saving NCX" -msgstr "" +msgstr "NCX mentése" #: /home/kovid/work/calibre/src/calibre/library/check_library.py:26 msgid "Invalid titles" @@ -21342,6 +21527,7 @@ msgstr "" #: /home/kovid/work/calibre/src/calibre/library/restore.py:125 msgid "Cannot restore preferences. Backup file not found." msgstr "" +"A beállításokat nem lehet visszaállítani. Biztonsági mentés nem található." #: /home/kovid/work/calibre/src/calibre/library/restore.py:136 msgid "Finished restoring preferences and column metadata" @@ -21523,7 +21709,7 @@ msgid "" "Failed to calculate path for save to disk. Template: %(templ)s\n" "Error: %(err)s" msgstr "" -"A lemezre mentés útvonalát nem sikerült meghatározni. sablon: %(templ)s\n" +"A lemezre mentés útvonalát nem sikerült meghatározni. Sablon: %(templ)s\n" "Hiba: %(err)s" #: /home/kovid/work/calibre/src/calibre/library/save_to_disk.py:316 @@ -21601,127 +21787,127 @@ msgstr "" "szerverekről kell ehhez a szerverhez a visszautat megadni." #: /home/kovid/work/calibre/src/calibre/library/server/ajax.py:317 -#: /home/kovid/work/calibre/src/calibre/library/server/browse.py:342 -#: /home/kovid/work/calibre/src/calibre/library/server/browse.py:626 +#: /home/kovid/work/calibre/src/calibre/library/server/browse.py:353 +#: /home/kovid/work/calibre/src/calibre/library/server/browse.py:643 msgid "All books" msgstr "Minden könyv" #: /home/kovid/work/calibre/src/calibre/library/server/ajax.py:318 -#: /home/kovid/work/calibre/src/calibre/library/server/browse.py:341 -#: /home/kovid/work/calibre/src/calibre/library/server/browse.py:625 +#: /home/kovid/work/calibre/src/calibre/library/server/browse.py:352 +#: /home/kovid/work/calibre/src/calibre/library/server/browse.py:642 #: /home/kovid/work/calibre/src/calibre/library/server/opds.py:584 msgid "Newest" msgstr "Legújabb" -#: /home/kovid/work/calibre/src/calibre/library/server/browse.py:64 -#: /home/kovid/work/calibre/src/calibre/library/server/browse.py:498 +#: /home/kovid/work/calibre/src/calibre/library/server/browse.py:65 +#: /home/kovid/work/calibre/src/calibre/library/server/browse.py:515 msgid "Loading, please wait" msgstr "Betöltés, kérjük várjon" -#: /home/kovid/work/calibre/src/calibre/library/server/browse.py:90 -#: /home/kovid/work/calibre/src/calibre/library/server/browse.py:111 +#: /home/kovid/work/calibre/src/calibre/library/server/browse.py:91 +#: /home/kovid/work/calibre/src/calibre/library/server/browse.py:112 msgid "Go to" msgstr "Ugrás ide" -#: /home/kovid/work/calibre/src/calibre/library/server/browse.py:106 +#: /home/kovid/work/calibre/src/calibre/library/server/browse.py:107 msgid "First" msgstr "Első" -#: /home/kovid/work/calibre/src/calibre/library/server/browse.py:106 +#: /home/kovid/work/calibre/src/calibre/library/server/browse.py:107 msgid "Last" msgstr "Utolsó" -#: /home/kovid/work/calibre/src/calibre/library/server/browse.py:109 +#: /home/kovid/work/calibre/src/calibre/library/server/browse.py:110 #, python-format msgid "Browsing %d books" msgstr "%d könyv böngészése" -#: /home/kovid/work/calibre/src/calibre/library/server/browse.py:126 -#: /home/kovid/work/calibre/src/calibre/library/server/browse.py:256 +#: /home/kovid/work/calibre/src/calibre/library/server/browse.py:127 +#: /home/kovid/work/calibre/src/calibre/library/server/browse.py:259 msgid "Average rating" msgstr "Átlagos értékelés" -#: /home/kovid/work/calibre/src/calibre/library/server/browse.py:127 +#: /home/kovid/work/calibre/src/calibre/library/server/browse.py:128 #, python-format msgid "%(prefix)s: %(rating).1f stars" msgstr "%(prefix)s: %(rating).1f csillag" -#: /home/kovid/work/calibre/src/calibre/library/server/browse.py:164 +#: /home/kovid/work/calibre/src/calibre/library/server/browse.py:165 #, python-format msgid "%d stars" msgstr "%d csillag" -#: /home/kovid/work/calibre/src/calibre/library/server/browse.py:257 +#: /home/kovid/work/calibre/src/calibre/library/server/browse.py:260 msgid "Popularity" msgstr "Gyakoriság" -#: /home/kovid/work/calibre/src/calibre/library/server/browse.py:279 +#: /home/kovid/work/calibre/src/calibre/library/server/browse.py:282 msgid "library" msgstr "könyvtár" -#: /home/kovid/work/calibre/src/calibre/library/server/browse.py:280 +#: /home/kovid/work/calibre/src/calibre/library/server/browse.py:283 msgid "home" msgstr "Kezdőlap" -#: /home/kovid/work/calibre/src/calibre/library/server/browse.py:387 -#: /home/kovid/work/calibre/src/calibre/library/server/browse.py:452 +#: /home/kovid/work/calibre/src/calibre/library/server/browse.py:400 +#: /home/kovid/work/calibre/src/calibre/library/server/browse.py:469 msgid "Browse books by" msgstr "Könyvek böngészése e szerint:" -#: /home/kovid/work/calibre/src/calibre/library/server/browse.py:392 +#: /home/kovid/work/calibre/src/calibre/library/server/browse.py:405 msgid "Choose a category to browse by:" msgstr "Válassza ki, mely kategória szerint kíván böngészni:" -#: /home/kovid/work/calibre/src/calibre/library/server/browse.py:523 +#: /home/kovid/work/calibre/src/calibre/library/server/browse.py:540 msgid "Browsing by" msgstr "Böngészés e szerint:" -#: /home/kovid/work/calibre/src/calibre/library/server/browse.py:524 +#: /home/kovid/work/calibre/src/calibre/library/server/browse.py:541 msgid "Up" msgstr "Fel" -#: /home/kovid/work/calibre/src/calibre/library/server/browse.py:661 +#: /home/kovid/work/calibre/src/calibre/library/server/browse.py:678 msgid "in" msgstr "ebben" -#: /home/kovid/work/calibre/src/calibre/library/server/browse.py:664 +#: /home/kovid/work/calibre/src/calibre/library/server/browse.py:681 msgid "Books in" msgstr "Könyvek:" -#: /home/kovid/work/calibre/src/calibre/library/server/browse.py:758 +#: /home/kovid/work/calibre/src/calibre/library/server/browse.py:775 msgid "Other formats" msgstr "Egyéb formátumok" -#: /home/kovid/work/calibre/src/calibre/library/server/browse.py:765 +#: /home/kovid/work/calibre/src/calibre/library/server/browse.py:782 #, python-format msgid "Read %(title)s in the %(fmt)s format" msgstr "%(title)s olvasása %(fmt)s formátumban" -#: /home/kovid/work/calibre/src/calibre/library/server/browse.py:770 +#: /home/kovid/work/calibre/src/calibre/library/server/browse.py:787 msgid "Get" msgstr "Letöltés" -#: /home/kovid/work/calibre/src/calibre/library/server/browse.py:783 +#: /home/kovid/work/calibre/src/calibre/library/server/browse.py:800 msgid "Details" msgstr "Részletek" -#: /home/kovid/work/calibre/src/calibre/library/server/browse.py:785 +#: /home/kovid/work/calibre/src/calibre/library/server/browse.py:802 msgid "Permalink" msgstr "Permalink" -#: /home/kovid/work/calibre/src/calibre/library/server/browse.py:786 +#: /home/kovid/work/calibre/src/calibre/library/server/browse.py:803 msgid "A permanent link to this book" msgstr "A könyvhöz tartozó permalink" -#: /home/kovid/work/calibre/src/calibre/library/server/browse.py:798 +#: /home/kovid/work/calibre/src/calibre/library/server/browse.py:815 msgid "This book has been deleted" msgstr "Ezt a könyvet törölték" -#: /home/kovid/work/calibre/src/calibre/library/server/browse.py:886 +#: /home/kovid/work/calibre/src/calibre/library/server/browse.py:913 msgid "in search" msgstr "a keresésben" -#: /home/kovid/work/calibre/src/calibre/library/server/browse.py:888 +#: /home/kovid/work/calibre/src/calibre/library/server/browse.py:915 msgid "Matching books" msgstr "Egyező könyvek" @@ -21878,7 +22064,7 @@ msgstr "Az e-book konvertálás alapértelmezett kimeneti formátuma." #: /home/kovid/work/calibre/src/calibre/utils/config_base.py:393 msgid "Ordered list of formats to prefer for input." -msgstr "Rendezett lista az előnyberészesített formátumokról." +msgstr "Rendezett lista az előnyben részesített formátumokról." #: /home/kovid/work/calibre/src/calibre/utils/config_base.py:395 msgid "Read metadata from files" @@ -21947,7 +22133,7 @@ msgstr "" #: /home/kovid/work/calibre/src/calibre/utils/filenames.py:295 msgid "File is open in another process" -msgstr "" +msgstr "A fájlt egy másik művelet is használja" #: /home/kovid/work/calibre/src/calibre/utils/formatter.py:31 #: /home/kovid/work/calibre/src/calibre/utils/formatter.py:182 @@ -23080,9 +23266,9 @@ msgstr "Nem sikerült az SSH kapcsolat: " msgid "Failed to authenticate with server: %s" msgstr "A hitelesítés sikertelen a következő kiszolgálóval: %s" -#: /home/kovid/work/calibre/src/calibre/utils/smtp.py:257 +#: /home/kovid/work/calibre/src/calibre/utils/smtp.py:258 msgid "Control email delivery" -msgstr "E-mail küldés beállításai" +msgstr "Email küldés beállításai" #: /home/kovid/work/calibre/src/calibre/web/feeds/__init__.py:121 msgid "Unknown section" @@ -23110,7 +23296,8 @@ msgstr "A következő letöltése nem sikerült: %s" #, python-format msgid "The \"%s\" recipe needs a username and password." msgstr "" -"A következő recepthez felhasználónévre és jelszóra van szüksége: „%s”." +"A következő hírösszeállításhoz felhasználónévre és jelszóra van szüksége: " +"„%s”." #: /home/kovid/work/calibre/src/calibre/web/feeds/news.py:892 msgid "Download finished" @@ -23204,7 +23391,7 @@ msgstr "A következő cikk letöltése nem sikerült: %s" #: /home/kovid/work/calibre/src/calibre/web/feeds/news.py:1489 msgid "Fetching feed" -msgstr "Hír letöltése" +msgstr "Hírek letöltése" #: /home/kovid/work/calibre/src/calibre/web/feeds/news.py:1633 msgid "" @@ -23224,7 +23411,7 @@ msgstr "" #: /home/kovid/work/calibre/src/calibre/web/feeds/recipes/collection.py:45 msgid "You" -msgstr "Te" +msgstr "Ön" #: /home/kovid/work/calibre/src/calibre/web/feeds/recipes/model.py:75 #: /home/kovid/work/calibre/src/calibre/web/feeds/recipes/model.py:84 @@ -23346,67 +23533,67 @@ msgstr "" msgid "Do not download CSS stylesheets." msgstr "Ne töltse le a CSS stíluslapokat." -#: /usr/src/qt-everywhere-opensource-src-4.8.2/src/gui/widgets/qdialogbuttonbox.cpp:658 +#: /usr/src/qt-everywhere-opensource-src-4.8.4/src/gui/widgets/qdialogbuttonbox.cpp:658 msgid "OK" msgstr "OK" -#: /usr/src/qt-everywhere-opensource-src-4.8.2/src/gui/widgets/qdialogbuttonbox.cpp:661 +#: /usr/src/qt-everywhere-opensource-src-4.8.4/src/gui/widgets/qdialogbuttonbox.cpp:661 msgid "Save" msgstr "Mentés" -#: /usr/src/qt-everywhere-opensource-src-4.8.2/src/gui/widgets/qdialogbuttonbox.cpp:664 +#: /usr/src/qt-everywhere-opensource-src-4.8.4/src/gui/widgets/qdialogbuttonbox.cpp:664 msgid "Open" msgstr "Megnyit" -#: /usr/src/qt-everywhere-opensource-src-4.8.2/src/gui/widgets/qdialogbuttonbox.cpp:673 +#: /usr/src/qt-everywhere-opensource-src-4.8.4/src/gui/widgets/qdialogbuttonbox.cpp:673 msgid "Apply" msgstr "Alkalmaz" -#: /usr/src/qt-everywhere-opensource-src-4.8.2/src/gui/widgets/qdialogbuttonbox.cpp:683 +#: /usr/src/qt-everywhere-opensource-src-4.8.4/src/gui/widgets/qdialogbuttonbox.cpp:683 msgid "Don't Save" msgstr "Ne mentse" -#: /usr/src/qt-everywhere-opensource-src-4.8.2/src/gui/widgets/qdialogbuttonbox.cpp:685 +#: /usr/src/qt-everywhere-opensource-src-4.8.4/src/gui/widgets/qdialogbuttonbox.cpp:685 msgid "Close without Saving" msgstr "Bezárás mentés nélkül" -#: /usr/src/qt-everywhere-opensource-src-4.8.2/src/gui/widgets/qdialogbuttonbox.cpp:687 +#: /usr/src/qt-everywhere-opensource-src-4.8.4/src/gui/widgets/qdialogbuttonbox.cpp:687 msgid "Discard" msgstr "Elvetés" -#: /usr/src/qt-everywhere-opensource-src-4.8.2/src/gui/widgets/qdialogbuttonbox.cpp:690 +#: /usr/src/qt-everywhere-opensource-src-4.8.4/src/gui/widgets/qdialogbuttonbox.cpp:690 msgid "&Yes" msgstr "&Igen" -#: /usr/src/qt-everywhere-opensource-src-4.8.2/src/gui/widgets/qdialogbuttonbox.cpp:693 +#: /usr/src/qt-everywhere-opensource-src-4.8.4/src/gui/widgets/qdialogbuttonbox.cpp:693 msgid "Yes to &All" msgstr "Ig&en, mind" -#: /usr/src/qt-everywhere-opensource-src-4.8.2/src/gui/widgets/qdialogbuttonbox.cpp:696 +#: /usr/src/qt-everywhere-opensource-src-4.8.4/src/gui/widgets/qdialogbuttonbox.cpp:696 msgid "&No" msgstr "&Nem" -#: /usr/src/qt-everywhere-opensource-src-4.8.2/src/gui/widgets/qdialogbuttonbox.cpp:699 +#: /usr/src/qt-everywhere-opensource-src-4.8.4/src/gui/widgets/qdialogbuttonbox.cpp:699 msgid "N&o to All" msgstr "Egyik &sem" -#: /usr/src/qt-everywhere-opensource-src-4.8.2/src/gui/widgets/qdialogbuttonbox.cpp:702 +#: /usr/src/qt-everywhere-opensource-src-4.8.4/src/gui/widgets/qdialogbuttonbox.cpp:702 msgid "Save All" msgstr "Összes mentése" -#: /usr/src/qt-everywhere-opensource-src-4.8.2/src/gui/widgets/qdialogbuttonbox.cpp:705 +#: /usr/src/qt-everywhere-opensource-src-4.8.4/src/gui/widgets/qdialogbuttonbox.cpp:705 msgid "Abort" msgstr "Megszakítás" -#: /usr/src/qt-everywhere-opensource-src-4.8.2/src/gui/widgets/qdialogbuttonbox.cpp:708 +#: /usr/src/qt-everywhere-opensource-src-4.8.4/src/gui/widgets/qdialogbuttonbox.cpp:708 msgid "Retry" msgstr "Újra" -#: /usr/src/qt-everywhere-opensource-src-4.8.2/src/gui/widgets/qdialogbuttonbox.cpp:711 +#: /usr/src/qt-everywhere-opensource-src-4.8.4/src/gui/widgets/qdialogbuttonbox.cpp:711 msgid "Ignore" msgstr "Kihagyás" -#: /usr/src/qt-everywhere-opensource-src-4.8.2/src/gui/widgets/qdialogbuttonbox.cpp:714 +#: /usr/src/qt-everywhere-opensource-src-4.8.4/src/gui/widgets/qdialogbuttonbox.cpp:714 msgid "Restore Defaults" msgstr "Alapértelmezések visszaállítása" @@ -24332,7 +24519,7 @@ msgstr "" #: /home/kovid/work/calibre/resources/default_tweaks.py:428 msgid "The number of seconds to wait before sending emails" -msgstr "Az e-mailek küldése előtti várakozás másodpercben" +msgstr "Az emailek küldése előtti várakozás másodpercben" #: /home/kovid/work/calibre/resources/default_tweaks.py:429 msgid "" @@ -24342,7 +24529,7 @@ msgid "" "making email sending fail. Changes will take effect only after a restart of\n" "calibre." msgstr "" -"Nyilvános e-mail szerverek, mint a gmail vagy a hotmail, használata estén " +"Nyilvános email szerverek, mint a gmail vagy a hotmail, használata estén " "ennyi másodpercet\n" "vár a levél elküldése előtt. Alapérték: 5 perc.\n" "Ennél alacsonyabb érték esetén a szerverek SPAM ellenőrzése kidobhatja a " @@ -24484,7 +24671,7 @@ msgstr "" #: /home/kovid/work/calibre/resources/default_tweaks.py:484 msgid "Compile General Program Mode templates to Python" -msgstr "Általános Program Mód sablonok lefordítása" +msgstr "Általános Program Mód sablonok lefordítása Pythonnal" #: /home/kovid/work/calibre/resources/default_tweaks.py:485 msgid "" @@ -24509,7 +24696,7 @@ msgstr "" #: /home/kovid/work/calibre/resources/default_tweaks.py:494 msgid "What format to default to when using the Tweak feature" -msgstr "" +msgstr "A Finomhangolásnál használt alapértelmezett formátum" #: /home/kovid/work/calibre/resources/default_tweaks.py:495 msgid "" diff --git a/src/calibre/translations/id.po b/src/calibre/translations/id.po index 017cf31f28..fe8b9232a3 100644 --- a/src/calibre/translations/id.po +++ b/src/calibre/translations/id.po @@ -7,7 +7,7 @@ msgid "" msgstr "" "Project-Id-Version: calibre\n" "Report-Msgid-Bugs-To: FULL NAME \n" -"POT-Creation-Date: 2012-11-30 03:42+0000\n" +"POT-Creation-Date: 2013-01-04 05:12+0000\n" "PO-Revision-Date: 2012-01-18 11:51+0000\n" "Last-Translator: Amri Ristadi \n" "Language-Team: Indonesian \n" @@ -15,8 +15,8 @@ msgstr "" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" "Plural-Forms: nplurals=1; plural=0;\n" -"X-Launchpad-Export-Date: 2012-12-01 04:43+0000\n" -"X-Generator: Launchpad (build 16319)\n" +"X-Launchpad-Export-Date: 2013-01-05 04:50+0000\n" +"X-Generator: Launchpad (build 16393)\n" #: /home/kovid/work/calibre/src/calibre/customize/__init__.py:56 msgid "Does absolutely nothing" @@ -26,17 +26,17 @@ msgstr "Tidak ada apa-apanya" #: /home/kovid/work/calibre/src/calibre/db/cache.py:106 #: /home/kovid/work/calibre/src/calibre/db/cache.py:109 #: /home/kovid/work/calibre/src/calibre/db/cache.py:120 -#: /home/kovid/work/calibre/src/calibre/devices/android/driver.py:376 -#: /home/kovid/work/calibre/src/calibre/devices/android/driver.py:377 -#: /home/kovid/work/calibre/src/calibre/devices/hanvon/driver.py:100 -#: /home/kovid/work/calibre/src/calibre/devices/hanvon/driver.py:101 +#: /home/kovid/work/calibre/src/calibre/devices/android/driver.py:378 +#: /home/kovid/work/calibre/src/calibre/devices/android/driver.py:379 +#: /home/kovid/work/calibre/src/calibre/devices/hanvon/driver.py:114 +#: /home/kovid/work/calibre/src/calibre/devices/hanvon/driver.py:115 #: /home/kovid/work/calibre/src/calibre/devices/jetbook/driver.py:74 #: /home/kovid/work/calibre/src/calibre/devices/kindle/driver.py:77 #: /home/kovid/work/calibre/src/calibre/devices/kobo/driver.py:667 #: /home/kovid/work/calibre/src/calibre/devices/mtp/books.py:45 #: /home/kovid/work/calibre/src/calibre/devices/mtp/books.py:69 -#: /home/kovid/work/calibre/src/calibre/devices/mtp/unix/driver.py:229 -#: /home/kovid/work/calibre/src/calibre/devices/mtp/windows/driver.py:237 +#: /home/kovid/work/calibre/src/calibre/devices/mtp/unix/driver.py:234 +#: /home/kovid/work/calibre/src/calibre/devices/mtp/windows/driver.py:238 #: /home/kovid/work/calibre/src/calibre/devices/nook/driver.py:71 #: /home/kovid/work/calibre/src/calibre/devices/nook/driver.py:72 #: /home/kovid/work/calibre/src/calibre/devices/prs505/sony_cache.py:661 @@ -53,6 +53,8 @@ msgstr "Tidak ada apa-apanya" #: /home/kovid/work/calibre/src/calibre/ebooks/conversion/plugins/html_input.py:121 #: /home/kovid/work/calibre/src/calibre/ebooks/conversion/plugins/lrf_output.py:29 #: /home/kovid/work/calibre/src/calibre/ebooks/conversion/plugins/pdb_input.py:27 +#: /home/kovid/work/calibre/src/calibre/ebooks/conversion/plugins/pdf_output.py:30 +#: /home/kovid/work/calibre/src/calibre/ebooks/conversion/plugins/pdf_output.py:31 #: /home/kovid/work/calibre/src/calibre/ebooks/conversion/plugins/rtf_input.py:289 #: /home/kovid/work/calibre/src/calibre/ebooks/conversion/plugins/rtf_input.py:291 #: /home/kovid/work/calibre/src/calibre/ebooks/epub/periodical.py:140 @@ -108,8 +110,8 @@ msgstr "Tidak ada apa-apanya" #: /home/kovid/work/calibre/src/calibre/ebooks/metadata/txt.py:18 #: /home/kovid/work/calibre/src/calibre/ebooks/mobi/reader/headers.py:27 #: /home/kovid/work/calibre/src/calibre/ebooks/mobi/reader/headers.py:95 -#: /home/kovid/work/calibre/src/calibre/ebooks/mobi/reader/headers.py:153 -#: /home/kovid/work/calibre/src/calibre/ebooks/mobi/reader/headers.py:192 +#: /home/kovid/work/calibre/src/calibre/ebooks/mobi/reader/headers.py:154 +#: /home/kovid/work/calibre/src/calibre/ebooks/mobi/reader/headers.py:193 #: /home/kovid/work/calibre/src/calibre/ebooks/mobi/reader/mobi6.py:615 #: /home/kovid/work/calibre/src/calibre/ebooks/mobi/utils.py:316 #: /home/kovid/work/calibre/src/calibre/ebooks/mobi/writer2/indexer.py:463 @@ -131,11 +133,10 @@ msgstr "Tidak ada apa-apanya" #: /home/kovid/work/calibre/src/calibre/ebooks/pdb/ereader/writer.py:174 #: /home/kovid/work/calibre/src/calibre/ebooks/pdb/palmdoc/writer.py:29 #: /home/kovid/work/calibre/src/calibre/ebooks/pdb/ztxt/writer.py:27 -#: /home/kovid/work/calibre/src/calibre/ebooks/pdf/writer.py:108 -#: /home/kovid/work/calibre/src/calibre/ebooks/pdf/writer.py:109 -#: /home/kovid/work/calibre/src/calibre/gui2/__init__.py:446 -#: /home/kovid/work/calibre/src/calibre/gui2/__init__.py:454 -#: /home/kovid/work/calibre/src/calibre/gui2/actions/add.py:166 +#: /home/kovid/work/calibre/src/calibre/ebooks/pdf/render/links.py:119 +#: /home/kovid/work/calibre/src/calibre/gui2/__init__.py:447 +#: /home/kovid/work/calibre/src/calibre/gui2/__init__.py:455 +#: /home/kovid/work/calibre/src/calibre/gui2/actions/add.py:171 #: /home/kovid/work/calibre/src/calibre/gui2/actions/edit_metadata.py:411 #: /home/kovid/work/calibre/src/calibre/gui2/actions/edit_metadata.py:414 #: /home/kovid/work/calibre/src/calibre/gui2/add.py:167 @@ -145,32 +146,32 @@ msgstr "Tidak ada apa-apanya" #: /home/kovid/work/calibre/src/calibre/gui2/convert/metadata.py:124 #: /home/kovid/work/calibre/src/calibre/gui2/convert/metadata.py:143 #: /home/kovid/work/calibre/src/calibre/gui2/convert/metadata.py:145 -#: /home/kovid/work/calibre/src/calibre/gui2/device.py:1410 -#: /home/kovid/work/calibre/src/calibre/gui2/device.py:1413 -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/add_empty_book.py:55 -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/add_empty_book.py:60 +#: /home/kovid/work/calibre/src/calibre/gui2/device.py:1416 +#: /home/kovid/work/calibre/src/calibre/gui2/device.py:1419 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/add_empty_book.py:71 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/add_empty_book.py:79 #: /home/kovid/work/calibre/src/calibre/gui2/dialogs/book_info.py:128 #: /home/kovid/work/calibre/src/calibre/gui2/dialogs/comicconf.py:47 #: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk.py:825 #: /home/kovid/work/calibre/src/calibre/gui2/dialogs/scheduler.py:380 #: /home/kovid/work/calibre/src/calibre/gui2/email.py:193 #: /home/kovid/work/calibre/src/calibre/gui2/email.py:208 -#: /home/kovid/work/calibre/src/calibre/gui2/library/models.py:408 -#: /home/kovid/work/calibre/src/calibre/gui2/library/models.py:1069 -#: /home/kovid/work/calibre/src/calibre/gui2/library/models.py:1285 -#: /home/kovid/work/calibre/src/calibre/gui2/library/models.py:1288 -#: /home/kovid/work/calibre/src/calibre/gui2/library/models.py:1291 -#: /home/kovid/work/calibre/src/calibre/gui2/library/models.py:1379 +#: /home/kovid/work/calibre/src/calibre/gui2/library/models.py:439 +#: /home/kovid/work/calibre/src/calibre/gui2/library/models.py:1103 +#: /home/kovid/work/calibre/src/calibre/gui2/library/models.py:1319 +#: /home/kovid/work/calibre/src/calibre/gui2/library/models.py:1322 +#: /home/kovid/work/calibre/src/calibre/gui2/library/models.py:1325 +#: /home/kovid/work/calibre/src/calibre/gui2/library/models.py:1413 #: /home/kovid/work/calibre/src/calibre/gui2/metadata/basic_widgets.py:85 #: /home/kovid/work/calibre/src/calibre/gui2/metadata/basic_widgets.py:250 #: /home/kovid/work/calibre/src/calibre/gui2/metadata/basic_widgets.py:261 -#: /home/kovid/work/calibre/src/calibre/gui2/metadata/single.py:402 -#: /home/kovid/work/calibre/src/calibre/gui2/metadata/single_download.py:174 +#: /home/kovid/work/calibre/src/calibre/gui2/metadata/single.py:407 #: /home/kovid/work/calibre/src/calibre/gui2/metadata/single_download.py:178 +#: /home/kovid/work/calibre/src/calibre/gui2/metadata/single_download.py:182 #: /home/kovid/work/calibre/src/calibre/gui2/store/search/models.py:202 #: /home/kovid/work/calibre/src/calibre/gui2/store/stores/google_books_plugin.py:107 #: /home/kovid/work/calibre/src/calibre/gui2/viewer/main.py:191 -#: /home/kovid/work/calibre/src/calibre/library/catalogs/epub_mobi_builder.py:885 +#: /home/kovid/work/calibre/src/calibre/library/catalogs/epub_mobi_builder.py:924 #: /home/kovid/work/calibre/src/calibre/library/cli.py:244 #: /home/kovid/work/calibre/src/calibre/library/database.py:914 #: /home/kovid/work/calibre/src/calibre/library/database2.py:587 @@ -237,7 +238,7 @@ msgstr "Aksi antarmuka pengguna" #: /home/kovid/work/calibre/src/calibre/gui2/preferences/main.py:197 #: /home/kovid/work/calibre/src/calibre/gui2/preferences/main.py:288 #: /home/kovid/work/calibre/src/calibre/gui2/preferences/main.py:311 -#: /home/kovid/work/calibre/src/calibre/gui2/viewer/main_ui.py:221 +#: /home/kovid/work/calibre/src/calibre/gui2/viewer/main_ui.py:228 msgid "Preferences" msgstr "Pengaturan" @@ -629,7 +630,7 @@ msgid "Control how calibre downloads ebook metadata from the net" msgstr "Kendalikan bagaimana calibre mengunduh metadata ebook dari jaringan" #: /home/kovid/work/calibre/src/calibre/customize/builtins.py:1125 -#: /home/kovid/work/calibre/src/calibre/gui2/device_drivers/mtp_config.py:392 +#: /home/kovid/work/calibre/src/calibre/gui2/device_drivers/mtp_config.py:400 msgid "Ignored devices" msgstr "" @@ -993,27 +994,27 @@ msgstr "" msgid "Communicate with Android phones." msgstr "" -#: /home/kovid/work/calibre/src/calibre/devices/android/driver.py:198 +#: /home/kovid/work/calibre/src/calibre/devices/android/driver.py:199 msgid "" "Comma separated list of directories to send e-books to on the device's " "main memory. The first one that exists will be used" msgstr "" -#: /home/kovid/work/calibre/src/calibre/devices/android/driver.py:201 +#: /home/kovid/work/calibre/src/calibre/devices/android/driver.py:202 msgid "" "Comma separated list of directories to send e-books to on the device's " "storage cards. The first one that exists will be used" msgstr "" -#: /home/kovid/work/calibre/src/calibre/devices/android/driver.py:316 +#: /home/kovid/work/calibre/src/calibre/devices/android/driver.py:318 msgid "Communicate with S60 phones." msgstr "" -#: /home/kovid/work/calibre/src/calibre/devices/android/driver.py:335 +#: /home/kovid/work/calibre/src/calibre/devices/android/driver.py:337 msgid "Communicate with WebOS tablets." msgstr "" -#: /home/kovid/work/calibre/src/calibre/devices/apple/driver.py:61 +#: /home/kovid/work/calibre/src/calibre/devices/apple/driver.py:63 msgid "" "

    If you do not want calibre to recognize your Apple iDevice when it is " "connected to your computer, click Disable Apple Driver.

    To " @@ -1025,38 +1026,38 @@ msgid "" "to iDevices is an unsupported advanced user mode.

    " msgstr "" -#: /home/kovid/work/calibre/src/calibre/devices/apple/driver.py:78 +#: /home/kovid/work/calibre/src/calibre/devices/apple/driver.py:80 msgid "Disable Apple driver" msgstr "" -#: /home/kovid/work/calibre/src/calibre/devices/apple/driver.py:82 +#: /home/kovid/work/calibre/src/calibre/devices/apple/driver.py:84 msgid "Enable Apple driver" msgstr "" -#: /home/kovid/work/calibre/src/calibre/devices/apple/driver.py:118 +#: /home/kovid/work/calibre/src/calibre/devices/apple/driver.py:121 msgid "Use Series as Category in iTunes/iBooks" msgstr "" -#: /home/kovid/work/calibre/src/calibre/devices/apple/driver.py:119 +#: /home/kovid/work/calibre/src/calibre/devices/apple/driver.py:122 msgid "Enable to use the series name as the iTunes Genre, iBooks Category" msgstr "" -#: /home/kovid/work/calibre/src/calibre/devices/apple/driver.py:121 +#: /home/kovid/work/calibre/src/calibre/devices/apple/driver.py:124 msgid "Cache covers from iTunes/iBooks" msgstr "" -#: /home/kovid/work/calibre/src/calibre/devices/apple/driver.py:123 +#: /home/kovid/work/calibre/src/calibre/devices/apple/driver.py:126 msgid "Enable to cache and display covers from iTunes/iBooks" msgstr "" -#: /home/kovid/work/calibre/src/calibre/devices/apple/driver.py:124 +#: /home/kovid/work/calibre/src/calibre/devices/apple/driver.py:127 #, python-format msgid "" "\"Copy files to iTunes Media folder %s\" is enabled in iTunes " "Preferences|Advanced" msgstr "" -#: /home/kovid/work/calibre/src/calibre/devices/apple/driver.py:126 +#: /home/kovid/work/calibre/src/calibre/devices/apple/driver.py:129 msgid "" "

    This setting should match your iTunes Preferences|Advanced " "setting.

    Disabling will store copies of books transferred to iTunes in " @@ -1064,89 +1065,89 @@ msgid "" "is configured to store copies in your iTunes Media folder.

    " msgstr "" -#: /home/kovid/work/calibre/src/calibre/devices/apple/driver.py:190 +#: /home/kovid/work/calibre/src/calibre/devices/apple/driver.py:196 msgid "Apple device" msgstr "" -#: /home/kovid/work/calibre/src/calibre/devices/apple/driver.py:192 +#: /home/kovid/work/calibre/src/calibre/devices/apple/driver.py:198 msgid "Communicate with iTunes/iBooks." msgstr "" -#: /home/kovid/work/calibre/src/calibre/devices/apple/driver.py:206 -msgid "Apple device detected, launching iTunes, please wait ..." +#: /home/kovid/work/calibre/src/calibre/devices/apple/driver.py:212 +msgid "Apple iDevice detected, launching iTunes, please wait ..." msgstr "" -#: /home/kovid/work/calibre/src/calibre/devices/apple/driver.py:208 +#: /home/kovid/work/calibre/src/calibre/devices/apple/driver.py:214 msgid "" "Cannot copy books directly from iDevice. Drag from iTunes Library to " "desktop, then add to calibre's Library window." msgstr "" -#: /home/kovid/work/calibre/src/calibre/devices/apple/driver.py:211 +#: /home/kovid/work/calibre/src/calibre/devices/apple/driver.py:217 msgid "" -"Unsupported direct connect mode. See " +"*** Unsupported direct connect mode. See " "http://www.mobileread.com/forums/showthread.php?t=118559 for instructions on " -"using 'Connect to iTunes'" +"using 'Connect to iTunes' ***" msgstr "" -#: /home/kovid/work/calibre/src/calibre/devices/apple/driver.py:215 +#: /home/kovid/work/calibre/src/calibre/devices/apple/driver.py:221 msgid "" "

    Unable to communicate with iTunes.

    Refer to this " "forum post for more information.

    " msgstr "" -#: /home/kovid/work/calibre/src/calibre/devices/apple/driver.py:382 -#: /home/kovid/work/calibre/src/calibre/devices/apple/driver.py:385 +#: /home/kovid/work/calibre/src/calibre/devices/apple/driver.py:375 +#: /home/kovid/work/calibre/src/calibre/devices/apple/driver.py:378 msgid "Updating device metadata listing..." msgstr "" -#: /home/kovid/work/calibre/src/calibre/devices/apple/driver.py:462 -#: /home/kovid/work/calibre/src/calibre/devices/apple/driver.py:502 -#: /home/kovid/work/calibre/src/calibre/devices/apple/driver.py:1117 -#: /home/kovid/work/calibre/src/calibre/devices/apple/driver.py:1163 -#: /home/kovid/work/calibre/src/calibre/devices/apple/driver.py:3262 -#: /home/kovid/work/calibre/src/calibre/devices/apple/driver.py:3304 +#: /home/kovid/work/calibre/src/calibre/devices/apple/driver.py:456 +#: /home/kovid/work/calibre/src/calibre/devices/apple/driver.py:497 +#: /home/kovid/work/calibre/src/calibre/devices/apple/driver.py:1143 +#: /home/kovid/work/calibre/src/calibre/devices/apple/driver.py:1190 +#: /home/kovid/work/calibre/src/calibre/devices/apple/driver.py:3292 +#: /home/kovid/work/calibre/src/calibre/devices/apple/driver.py:3334 #, python-format msgid "%(num)d of %(tot)d" msgstr "" -#: /home/kovid/work/calibre/src/calibre/devices/apple/driver.py:510 -#: /home/kovid/work/calibre/src/calibre/devices/apple/driver.py:1168 -#: /home/kovid/work/calibre/src/calibre/devices/apple/driver.py:3311 +#: /home/kovid/work/calibre/src/calibre/devices/apple/driver.py:505 +#: /home/kovid/work/calibre/src/calibre/devices/apple/driver.py:1195 +#: /home/kovid/work/calibre/src/calibre/devices/apple/driver.py:3341 #: /home/kovid/work/calibre/src/calibre/gui2/ebook_download.py:110 msgid "finished" msgstr "" -#: /home/kovid/work/calibre/src/calibre/devices/apple/driver.py:702 +#: /home/kovid/work/calibre/src/calibre/devices/apple/driver.py:697 msgid "" "Some books not found in iTunes database.\n" "Delete using the iBooks app.\n" "Click 'Show Details' for a list." msgstr "" -#: /home/kovid/work/calibre/src/calibre/devices/apple/driver.py:1080 +#: /home/kovid/work/calibre/src/calibre/devices/apple/driver.py:1105 msgid "" "Some cover art could not be converted.\n" "Click 'Show Details' for a list." msgstr "" -#: /home/kovid/work/calibre/src/calibre/devices/apple/driver.py:2785 +#: /home/kovid/work/calibre/src/calibre/devices/apple/driver.py:2816 #: /home/kovid/work/calibre/src/calibre/devices/nook/driver.py:106 #: /home/kovid/work/calibre/src/calibre/devices/prs505/sony_cache.py:448 #: /home/kovid/work/calibre/src/calibre/devices/prs505/sony_cache.py:471 #: /home/kovid/work/calibre/src/calibre/devices/prst1/driver.py:773 #: /home/kovid/work/calibre/src/calibre/devices/prst1/driver.py:792 -#: /home/kovid/work/calibre/src/calibre/devices/smart_device_app/driver.py:392 -#: /home/kovid/work/calibre/src/calibre/devices/smart_device_app/driver.py:398 -#: /home/kovid/work/calibre/src/calibre/devices/smart_device_app/driver.py:429 +#: /home/kovid/work/calibre/src/calibre/devices/smart_device_app/driver.py:394 +#: /home/kovid/work/calibre/src/calibre/devices/smart_device_app/driver.py:400 +#: /home/kovid/work/calibre/src/calibre/devices/smart_device_app/driver.py:431 #: /home/kovid/work/calibre/src/calibre/devices/utils.py:80 #: /home/kovid/work/calibre/src/calibre/devices/utils.py:84 #: /home/kovid/work/calibre/src/calibre/devices/utils.py:116 #: /home/kovid/work/calibre/src/calibre/gui2/actions/fetch_news.py:73 #: /home/kovid/work/calibre/src/calibre/gui2/dialogs/scheduler.py:469 -#: /home/kovid/work/calibre/src/calibre/gui2/tag_browser/model.py:1197 -#: /home/kovid/work/calibre/src/calibre/gui2/tag_browser/model.py:1199 +#: /home/kovid/work/calibre/src/calibre/gui2/tag_browser/model.py:1225 +#: /home/kovid/work/calibre/src/calibre/gui2/tag_browser/model.py:1227 #: /home/kovid/work/calibre/src/calibre/library/database2.py:372 #: /home/kovid/work/calibre/src/calibre/library/database2.py:385 #: /home/kovid/work/calibre/src/calibre/library/database2.py:3386 @@ -1154,13 +1155,13 @@ msgstr "" msgid "News" msgstr "" -#: /home/kovid/work/calibre/src/calibre/devices/apple/driver.py:2786 +#: /home/kovid/work/calibre/src/calibre/devices/apple/driver.py:2817 #: /home/kovid/work/calibre/src/calibre/library/database2.py:3342 #: /home/kovid/work/calibre/src/calibre/library/database2.py:3360 msgid "Catalog" msgstr "" -#: /home/kovid/work/calibre/src/calibre/devices/apple/driver.py:3154 +#: /home/kovid/work/calibre/src/calibre/devices/apple/driver.py:3184 msgid "Communicate with iTunes." msgstr "" @@ -1207,7 +1208,7 @@ msgstr "" #: /home/kovid/work/calibre/src/calibre/devices/kobo/driver.py:1325 #: /home/kovid/work/calibre/src/calibre/devices/kobo/driver.py:1329 #: /home/kovid/work/calibre/src/calibre/devices/kobo/driver.py:1333 -#: /home/kovid/work/calibre/src/calibre/devices/kobo/driver.py:1683 +#: /home/kovid/work/calibre/src/calibre/devices/kobo/driver.py:1687 #: /home/kovid/work/calibre/src/calibre/devices/prst1/driver.py:155 #: /home/kovid/work/calibre/src/calibre/devices/usbms/driver.py:144 #: /home/kovid/work/calibre/src/calibre/devices/usbms/driver.py:147 @@ -1221,9 +1222,9 @@ msgstr "" #: /home/kovid/work/calibre/src/calibre/devices/bambook/driver.py:264 #: /home/kovid/work/calibre/src/calibre/devices/bambook/driver.py:268 #: /home/kovid/work/calibre/src/calibre/devices/bambook/driver.py:324 -#: /home/kovid/work/calibre/src/calibre/devices/mtp/driver.py:367 -#: /home/kovid/work/calibre/src/calibre/devices/smart_device_app/driver.py:1095 +#: /home/kovid/work/calibre/src/calibre/devices/mtp/driver.py:391 #: /home/kovid/work/calibre/src/calibre/devices/smart_device_app/driver.py:1097 +#: /home/kovid/work/calibre/src/calibre/devices/smart_device_app/driver.py:1099 #: /home/kovid/work/calibre/src/calibre/devices/usbms/driver.py:277 #: /home/kovid/work/calibre/src/calibre/devices/usbms/driver.py:279 msgid "Transferring books to device..." @@ -1233,9 +1234,9 @@ msgstr "" #: /home/kovid/work/calibre/src/calibre/devices/bambook/driver.py:344 #: /home/kovid/work/calibre/src/calibre/devices/kobo/driver.py:491 #: /home/kovid/work/calibre/src/calibre/devices/kobo/driver.py:525 -#: /home/kovid/work/calibre/src/calibre/devices/mtp/driver.py:404 -#: /home/kovid/work/calibre/src/calibre/devices/smart_device_app/driver.py:1108 -#: /home/kovid/work/calibre/src/calibre/devices/smart_device_app/driver.py:1119 +#: /home/kovid/work/calibre/src/calibre/devices/mtp/driver.py:430 +#: /home/kovid/work/calibre/src/calibre/devices/smart_device_app/driver.py:1110 +#: /home/kovid/work/calibre/src/calibre/devices/smart_device_app/driver.py:1121 #: /home/kovid/work/calibre/src/calibre/devices/usbms/driver.py:301 #: /home/kovid/work/calibre/src/calibre/devices/usbms/driver.py:332 msgid "Adding books to device metadata listing..." @@ -1243,8 +1244,8 @@ msgstr "" #: /home/kovid/work/calibre/src/calibre/devices/bambook/driver.py:352 #: /home/kovid/work/calibre/src/calibre/devices/bambook/driver.py:354 -#: /home/kovid/work/calibre/src/calibre/devices/hanvon/driver.py:115 -#: /home/kovid/work/calibre/src/calibre/devices/hanvon/driver.py:126 +#: /home/kovid/work/calibre/src/calibre/devices/hanvon/driver.py:129 +#: /home/kovid/work/calibre/src/calibre/devices/hanvon/driver.py:140 #: /home/kovid/work/calibre/src/calibre/devices/kobo/driver.py:440 #: /home/kovid/work/calibre/src/calibre/devices/kobo/driver.py:472 #: /home/kovid/work/calibre/src/calibre/devices/kobo/driver.py:615 @@ -1257,8 +1258,8 @@ msgstr "" #: /home/kovid/work/calibre/src/calibre/devices/bambook/driver.py:374 #: /home/kovid/work/calibre/src/calibre/devices/kobo/driver.py:479 #: /home/kovid/work/calibre/src/calibre/devices/kobo/driver.py:486 -#: /home/kovid/work/calibre/src/calibre/devices/smart_device_app/driver.py:1157 -#: /home/kovid/work/calibre/src/calibre/devices/smart_device_app/driver.py:1163 +#: /home/kovid/work/calibre/src/calibre/devices/smart_device_app/driver.py:1159 +#: /home/kovid/work/calibre/src/calibre/devices/smart_device_app/driver.py:1165 #: /home/kovid/work/calibre/src/calibre/devices/usbms/driver.py:366 #: /home/kovid/work/calibre/src/calibre/devices/usbms/driver.py:371 msgid "Removing books from device metadata listing..." @@ -1388,27 +1389,31 @@ msgstr "" msgid "Communicate with the Hanvon N520 eBook reader." msgstr "" -#: /home/kovid/work/calibre/src/calibre/devices/hanvon/driver.py:47 +#: /home/kovid/work/calibre/src/calibre/devices/hanvon/driver.py:48 +msgid "Communicate with the Kibano eBook reader." +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/devices/hanvon/driver.py:61 msgid "Communicate with The Book reader." msgstr "" -#: /home/kovid/work/calibre/src/calibre/devices/hanvon/driver.py:59 +#: /home/kovid/work/calibre/src/calibre/devices/hanvon/driver.py:73 msgid "Communicate with the Libre Air reader." msgstr "" -#: /home/kovid/work/calibre/src/calibre/devices/hanvon/driver.py:72 +#: /home/kovid/work/calibre/src/calibre/devices/hanvon/driver.py:86 msgid "Communicate with the SpringDesign Alex eBook reader." msgstr "" -#: /home/kovid/work/calibre/src/calibre/devices/hanvon/driver.py:132 +#: /home/kovid/work/calibre/src/calibre/devices/hanvon/driver.py:146 msgid "Communicate with the Azbooka" msgstr "" -#: /home/kovid/work/calibre/src/calibre/devices/hanvon/driver.py:151 +#: /home/kovid/work/calibre/src/calibre/devices/hanvon/driver.py:165 msgid "Communicate with the Elonex EB 511 eBook reader." msgstr "" -#: /home/kovid/work/calibre/src/calibre/devices/hanvon/driver.py:171 +#: /home/kovid/work/calibre/src/calibre/devices/hanvon/driver.py:185 msgid "Communicate with the Cybook Odyssey eBook reader." msgstr "" @@ -1638,7 +1643,7 @@ msgid "" msgstr "" #: /home/kovid/work/calibre/src/calibre/devices/kobo/driver.py:646 -#: /home/kovid/work/calibre/src/calibre/gui2/actions/add.py:393 +#: /home/kovid/work/calibre/src/calibre/gui2/actions/add.py:404 msgid "Not Implemented" msgstr "" @@ -1836,84 +1841,84 @@ msgstr "" msgid "Communicate with MTP devices" msgstr "" -#: /home/kovid/work/calibre/src/calibre/devices/mtp/driver.py:143 -#: /home/kovid/work/calibre/src/calibre/devices/smart_device_app/driver.py:912 +#: /home/kovid/work/calibre/src/calibre/devices/mtp/driver.py:167 +#: /home/kovid/work/calibre/src/calibre/devices/smart_device_app/driver.py:914 #: /home/kovid/work/calibre/src/calibre/devices/usbms/driver.py:95 msgid "Get device information..." msgstr "" -#: /home/kovid/work/calibre/src/calibre/devices/mtp/driver.py:166 +#: /home/kovid/work/calibre/src/calibre/devices/mtp/driver.py:190 msgid "Listing files, this can take a while" msgstr "" -#: /home/kovid/work/calibre/src/calibre/devices/mtp/driver.py:181 +#: /home/kovid/work/calibre/src/calibre/devices/mtp/driver.py:205 msgid "Reading ebook metadata" msgstr "" -#: /home/kovid/work/calibre/src/calibre/devices/mtp/driver.py:214 +#: /home/kovid/work/calibre/src/calibre/devices/mtp/driver.py:238 #, python-format msgid "Reading metadata from %s" msgstr "" -#: /home/kovid/work/calibre/src/calibre/devices/mtp/driver.py:233 +#: /home/kovid/work/calibre/src/calibre/devices/mtp/driver.py:257 msgid "Updating metadata cache on device" msgstr "" -#: /home/kovid/work/calibre/src/calibre/devices/mtp/driver.py:235 +#: /home/kovid/work/calibre/src/calibre/devices/mtp/driver.py:259 msgid "Finished reading metadata from device" msgstr "" -#: /home/kovid/work/calibre/src/calibre/devices/mtp/driver.py:393 +#: /home/kovid/work/calibre/src/calibre/devices/mtp/driver.py:419 #, python-format msgid "Transferred %s to device" msgstr "" -#: /home/kovid/work/calibre/src/calibre/devices/mtp/driver.py:395 +#: /home/kovid/work/calibre/src/calibre/devices/mtp/driver.py:421 msgid "Transfer to device finished..." msgstr "" -#: /home/kovid/work/calibre/src/calibre/devices/mtp/driver.py:416 +#: /home/kovid/work/calibre/src/calibre/devices/mtp/driver.py:442 #, python-format msgid "Added %s" msgstr "" -#: /home/kovid/work/calibre/src/calibre/devices/mtp/driver.py:418 +#: /home/kovid/work/calibre/src/calibre/devices/mtp/driver.py:444 msgid "Adding complete" msgstr "" -#: /home/kovid/work/calibre/src/calibre/devices/mtp/driver.py:434 +#: /home/kovid/work/calibre/src/calibre/devices/mtp/driver.py:460 msgid "Deleting books from device..." msgstr "" -#: /home/kovid/work/calibre/src/calibre/devices/mtp/driver.py:440 +#: /home/kovid/work/calibre/src/calibre/devices/mtp/driver.py:466 #, python-format msgid "Deleted %s" msgstr "" -#: /home/kovid/work/calibre/src/calibre/devices/mtp/driver.py:441 +#: /home/kovid/work/calibre/src/calibre/devices/mtp/driver.py:467 msgid "All books deleted" msgstr "" -#: /home/kovid/work/calibre/src/calibre/devices/mtp/driver.py:444 +#: /home/kovid/work/calibre/src/calibre/devices/mtp/driver.py:470 msgid "Removing books from metadata" msgstr "" -#: /home/kovid/work/calibre/src/calibre/devices/mtp/driver.py:456 +#: /home/kovid/work/calibre/src/calibre/devices/mtp/driver.py:482 #, python-format msgid "Removed %s" msgstr "" -#: /home/kovid/work/calibre/src/calibre/devices/mtp/driver.py:458 +#: /home/kovid/work/calibre/src/calibre/devices/mtp/driver.py:484 msgid "All books removed" msgstr "" #: /home/kovid/work/calibre/src/calibre/devices/mtp/unix/driver.py:198 -#: /home/kovid/work/calibre/src/calibre/devices/mtp/windows/driver.py:313 +#: /home/kovid/work/calibre/src/calibre/devices/mtp/windows/driver.py:315 msgid "Unknown MTP device" msgstr "" -#: /home/kovid/work/calibre/src/calibre/devices/mtp/unix/driver.py:216 -#: /home/kovid/work/calibre/src/calibre/devices/mtp/windows/driver.py:220 +#: /home/kovid/work/calibre/src/calibre/devices/mtp/unix/driver.py:217 +#: /home/kovid/work/calibre/src/calibre/devices/mtp/windows/driver.py:219 #, python-format msgid "Found object: %s" msgstr "" @@ -1929,11 +1934,6 @@ msgid "" "computer" msgstr "" -#: /home/kovid/work/calibre/src/calibre/devices/mtp/windows/driver.py:222 -#, python-format -msgid "Found id: %s" -msgstr "" - #: /home/kovid/work/calibre/src/calibre/devices/nokia/driver.py:17 msgid "Communicate with the Nokia 770 internet tablet." msgstr "" @@ -1971,12 +1971,12 @@ msgid "Comments have been removed as the SONY reader chokes on them" msgstr "" #: /home/kovid/work/calibre/src/calibre/devices/prs505/driver.py:66 -#: /home/kovid/work/calibre/src/calibre/devices/smart_device_app/driver.py:262 +#: /home/kovid/work/calibre/src/calibre/devices/smart_device_app/driver.py:264 msgid "All by title" msgstr "" #: /home/kovid/work/calibre/src/calibre/devices/prs505/driver.py:67 -#: /home/kovid/work/calibre/src/calibre/devices/smart_device_app/driver.py:263 +#: /home/kovid/work/calibre/src/calibre/devices/smart_device_app/driver.py:265 msgid "All by author" msgstr "" @@ -2080,77 +2080,77 @@ msgid "" "multiple authors. Leave this disabled if you use Metadata Plugboards." msgstr "" -#: /home/kovid/work/calibre/src/calibre/devices/smart_device_app/driver.py:174 +#: /home/kovid/work/calibre/src/calibre/devices/smart_device_app/driver.py:176 msgid "Wireless Device" msgstr "" -#: /home/kovid/work/calibre/src/calibre/devices/smart_device_app/driver.py:178 +#: /home/kovid/work/calibre/src/calibre/devices/smart_device_app/driver.py:180 msgid "Communicate with Smart Device apps" msgstr "" -#: /home/kovid/work/calibre/src/calibre/devices/smart_device_app/driver.py:264 +#: /home/kovid/work/calibre/src/calibre/devices/smart_device_app/driver.py:266 msgid "All by something" msgstr "" -#: /home/kovid/work/calibre/src/calibre/devices/smart_device_app/driver.py:267 +#: /home/kovid/work/calibre/src/calibre/devices/smart_device_app/driver.py:269 msgid "Enable connections at startup" msgstr "" -#: /home/kovid/work/calibre/src/calibre/devices/smart_device_app/driver.py:268 +#: /home/kovid/work/calibre/src/calibre/devices/smart_device_app/driver.py:270 msgid "Check this box to allow connections when calibre starts" msgstr "" -#: /home/kovid/work/calibre/src/calibre/devices/smart_device_app/driver.py:270 +#: /home/kovid/work/calibre/src/calibre/devices/smart_device_app/driver.py:272 msgid "Security password" msgstr "" -#: /home/kovid/work/calibre/src/calibre/devices/smart_device_app/driver.py:271 +#: /home/kovid/work/calibre/src/calibre/devices/smart_device_app/driver.py:273 msgid "Enter a password that the device app must use to connect to calibre" msgstr "" -#: /home/kovid/work/calibre/src/calibre/devices/smart_device_app/driver.py:273 +#: /home/kovid/work/calibre/src/calibre/devices/smart_device_app/driver.py:275 msgid "Use fixed network port" msgstr "" -#: /home/kovid/work/calibre/src/calibre/devices/smart_device_app/driver.py:274 +#: /home/kovid/work/calibre/src/calibre/devices/smart_device_app/driver.py:276 msgid "" "If checked, use the port number in the \"Port\" box, otherwise the driver " "will pick a random port" msgstr "" -#: /home/kovid/work/calibre/src/calibre/devices/smart_device_app/driver.py:276 +#: /home/kovid/work/calibre/src/calibre/devices/smart_device_app/driver.py:278 msgid "Port number: " msgstr "" -#: /home/kovid/work/calibre/src/calibre/devices/smart_device_app/driver.py:277 +#: /home/kovid/work/calibre/src/calibre/devices/smart_device_app/driver.py:279 msgid "" "Enter the port number the driver is to use if the \"fixed port\" box is " "checked" msgstr "" -#: /home/kovid/work/calibre/src/calibre/devices/smart_device_app/driver.py:278 +#: /home/kovid/work/calibre/src/calibre/devices/smart_device_app/driver.py:280 msgid "Print extra debug information" msgstr "" -#: /home/kovid/work/calibre/src/calibre/devices/smart_device_app/driver.py:279 +#: /home/kovid/work/calibre/src/calibre/devices/smart_device_app/driver.py:281 msgid "Check this box if requested when reporting problems" msgstr "" -#: /home/kovid/work/calibre/src/calibre/devices/smart_device_app/driver.py:281 +#: /home/kovid/work/calibre/src/calibre/devices/smart_device_app/driver.py:283 msgid "" "Comma separated list of metadata fields to turn into collections on the " "device." msgstr "" -#: /home/kovid/work/calibre/src/calibre/devices/smart_device_app/driver.py:283 +#: /home/kovid/work/calibre/src/calibre/devices/smart_device_app/driver.py:285 msgid "Possibilities include: series, tags, authors, etc" msgstr "" -#: /home/kovid/work/calibre/src/calibre/devices/smart_device_app/driver.py:291 +#: /home/kovid/work/calibre/src/calibre/devices/smart_device_app/driver.py:293 msgid "Enable the no-activity timeout" msgstr "" -#: /home/kovid/work/calibre/src/calibre/devices/smart_device_app/driver.py:292 +#: /home/kovid/work/calibre/src/calibre/devices/smart_device_app/driver.py:294 #, python-format msgid "" "If this box is checked, calibre will automatically disconnect if a connected " @@ -2158,33 +2158,33 @@ msgid "" "timeout, so calibre will never automatically disconnect." msgstr "" -#: /home/kovid/work/calibre/src/calibre/devices/smart_device_app/driver.py:296 +#: /home/kovid/work/calibre/src/calibre/devices/smart_device_app/driver.py:298 msgid "Use this IP address" msgstr "" -#: /home/kovid/work/calibre/src/calibre/devices/smart_device_app/driver.py:297 +#: /home/kovid/work/calibre/src/calibre/devices/smart_device_app/driver.py:299 msgid "" "Use this option if you want to force the driver to listen on a particular IP " "address. The driver will listen only on the entered address, and this " "address will be the one advertized over mDNS (bonjour)." msgstr "" -#: /home/kovid/work/calibre/src/calibre/devices/smart_device_app/driver.py:766 +#: /home/kovid/work/calibre/src/calibre/devices/smart_device_app/driver.py:768 #, python-format msgid "Too many connection attempts from %s" msgstr "" -#: /home/kovid/work/calibre/src/calibre/devices/smart_device_app/driver.py:1271 +#: /home/kovid/work/calibre/src/calibre/devices/smart_device_app/driver.py:1273 #, python-format msgid "Invalid port in options: %s" msgstr "" -#: /home/kovid/work/calibre/src/calibre/devices/smart_device_app/driver.py:1279 +#: /home/kovid/work/calibre/src/calibre/devices/smart_device_app/driver.py:1281 #, python-format msgid "Failed to connect to port %d. Try a different value." msgstr "" -#: /home/kovid/work/calibre/src/calibre/devices/smart_device_app/driver.py:1291 +#: /home/kovid/work/calibre/src/calibre/devices/smart_device_app/driver.py:1293 msgid "Failed to allocate a random port" msgstr "" @@ -2196,35 +2196,35 @@ msgstr "" msgid "Communicate with the Teclast K3/K5 reader." msgstr "" -#: /home/kovid/work/calibre/src/calibre/devices/teclast/driver.py:37 +#: /home/kovid/work/calibre/src/calibre/devices/teclast/driver.py:38 msgid "Communicate with the Newsmy reader." msgstr "" -#: /home/kovid/work/calibre/src/calibre/devices/teclast/driver.py:48 +#: /home/kovid/work/calibre/src/calibre/devices/teclast/driver.py:49 msgid "Communicate with the Archos reader." msgstr "" -#: /home/kovid/work/calibre/src/calibre/devices/teclast/driver.py:58 +#: /home/kovid/work/calibre/src/calibre/devices/teclast/driver.py:59 msgid "Communicate with the Pico reader." msgstr "" -#: /home/kovid/work/calibre/src/calibre/devices/teclast/driver.py:70 +#: /home/kovid/work/calibre/src/calibre/devices/teclast/driver.py:71 msgid "Communicate with the iPapyrus reader." msgstr "" -#: /home/kovid/work/calibre/src/calibre/devices/teclast/driver.py:81 +#: /home/kovid/work/calibre/src/calibre/devices/teclast/driver.py:82 msgid "Communicate with the Sovos reader." msgstr "" -#: /home/kovid/work/calibre/src/calibre/devices/teclast/driver.py:91 +#: /home/kovid/work/calibre/src/calibre/devices/teclast/driver.py:92 msgid "Communicate with the Sunstech EB700 reader." msgstr "" -#: /home/kovid/work/calibre/src/calibre/devices/teclast/driver.py:102 +#: /home/kovid/work/calibre/src/calibre/devices/teclast/driver.py:103 msgid "Communicate with the Stash W950 reader." msgstr "" -#: /home/kovid/work/calibre/src/calibre/devices/teclast/driver.py:114 +#: /home/kovid/work/calibre/src/calibre/devices/teclast/driver.py:115 msgid "Communicate with the Wexler reader." msgstr "" @@ -2423,6 +2423,7 @@ msgid "There is insufficient free space on the storage card" msgstr "" #: /home/kovid/work/calibre/src/calibre/ebooks/comic/input.py:210 +#: /home/kovid/work/calibre/src/calibre/ebooks/pdf/render/from_html.py:230 #, python-format msgid "Rendered %s" msgstr "" @@ -2960,58 +2961,74 @@ msgstr "" msgid "Use the new PDF conversion engine." msgstr "" -#: /home/kovid/work/calibre/src/calibre/ebooks/conversion/plugins/pdf_output.py:71 -#, python-format +#: /home/kovid/work/calibre/src/calibre/ebooks/conversion/plugins/pdf_output.py:54 msgid "" -"The unit of measure. Default is inch. Choices are %s Note: This does not " -"override the unit for margins!" +"Normally, the PDF page size is set by the output profile chosen under page " +"options. This option will cause the page size settings under PDF Output to " +"override the size specified by the output profile." msgstr "" -#: /home/kovid/work/calibre/src/calibre/ebooks/conversion/plugins/pdf_output.py:76 +#: /home/kovid/work/calibre/src/calibre/ebooks/conversion/plugins/pdf_output.py:60 +#, python-format +msgid "" +"The unit of measure for page sizes. Default is inch. Choices are %s Note: " +"This does not override the unit for margins!" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/ebooks/conversion/plugins/pdf_output.py:65 #, python-format msgid "" "The size of the paper. This size will be overridden when a non default " "output profile is used. Default is letter. Choices are %s" msgstr "" -#: /home/kovid/work/calibre/src/calibre/ebooks/conversion/plugins/pdf_output.py:80 +#: /home/kovid/work/calibre/src/calibre/ebooks/conversion/plugins/pdf_output.py:69 msgid "" "Custom size of the document. Use the form widthxheight EG. `123x321` to " "specify the width and height. This overrides any specified paper-size." msgstr "" -#: /home/kovid/work/calibre/src/calibre/ebooks/conversion/plugins/pdf_output.py:85 -#, python-format -msgid "The orientation of the page. Default is portrait. Choices are %s" -msgstr "" - -#: /home/kovid/work/calibre/src/calibre/ebooks/conversion/plugins/pdf_output.py:89 +#: /home/kovid/work/calibre/src/calibre/ebooks/conversion/plugins/pdf_output.py:74 msgid "" "Preserve the aspect ratio of the cover, instead of stretching it to fill the " "full first page of the generated pdf." msgstr "" -#: /home/kovid/work/calibre/src/calibre/ebooks/conversion/plugins/pdf_output.py:94 +#: /home/kovid/work/calibre/src/calibre/ebooks/conversion/plugins/pdf_output.py:79 msgid "The font family used to render serif fonts" msgstr "" -#: /home/kovid/work/calibre/src/calibre/ebooks/conversion/plugins/pdf_output.py:97 +#: /home/kovid/work/calibre/src/calibre/ebooks/conversion/plugins/pdf_output.py:82 msgid "The font family used to render sans-serif fonts" msgstr "" -#: /home/kovid/work/calibre/src/calibre/ebooks/conversion/plugins/pdf_output.py:100 -#: /home/kovid/work/calibre/src/calibre/ebooks/conversion/plugins/pdf_output.py:104 +#: /home/kovid/work/calibre/src/calibre/ebooks/conversion/plugins/pdf_output.py:85 +#: /home/kovid/work/calibre/src/calibre/ebooks/conversion/plugins/pdf_output.py:89 msgid "The font family used to render monospaced fonts" msgstr "" -#: /home/kovid/work/calibre/src/calibre/ebooks/conversion/plugins/pdf_output.py:107 +#: /home/kovid/work/calibre/src/calibre/ebooks/conversion/plugins/pdf_output.py:92 msgid "The default font size" msgstr "" -#: /home/kovid/work/calibre/src/calibre/ebooks/conversion/plugins/pdf_output.py:110 +#: /home/kovid/work/calibre/src/calibre/ebooks/conversion/plugins/pdf_output.py:95 msgid "The default font size for monospaced text" msgstr "" +#: /home/kovid/work/calibre/src/calibre/ebooks/conversion/plugins/pdf_output.py:97 +msgid "Surround all links with a red box, useful for debugging." +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/ebooks/conversion/plugins/pdf_output.py:99 +msgid "Use the old, less capable engine to generate the PDF" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/ebooks/conversion/plugins/pdf_output.py:102 +msgid "" +"Generate an uncompressed PDF, useful for debugging, only works with the new " +"PDF engine." +msgstr "" + #: /home/kovid/work/calibre/src/calibre/ebooks/conversion/plugins/pml_output.py:22 msgid "" "Specify the character encoding of the output document. The default is cp1252." @@ -4037,9 +4054,9 @@ msgstr "" #: /home/kovid/work/calibre/src/calibre/gui2/dialogs/delete_matching_from_device.py:76 #: /home/kovid/work/calibre/src/calibre/gui2/dialogs/quickview.py:85 #: /home/kovid/work/calibre/src/calibre/gui2/dialogs/template_dialog.py:222 -#: /home/kovid/work/calibre/src/calibre/gui2/library/models.py:58 -#: /home/kovid/work/calibre/src/calibre/gui2/library/models.py:1074 -#: /home/kovid/work/calibre/src/calibre/gui2/metadata/single_download.py:146 +#: /home/kovid/work/calibre/src/calibre/gui2/library/models.py:83 +#: /home/kovid/work/calibre/src/calibre/gui2/library/models.py:1108 +#: /home/kovid/work/calibre/src/calibre/gui2/metadata/single_download.py:150 #: /home/kovid/work/calibre/src/calibre/gui2/preferences/metadata_sources.py:162 #: /home/kovid/work/calibre/src/calibre/gui2/store/search/models.py:39 #: /home/kovid/work/calibre/src/calibre/gui2/store/stores/mobileread/models.py:23 @@ -4050,14 +4067,14 @@ msgid "Title" msgstr "" #: /home/kovid/work/calibre/src/calibre/ebooks/metadata/book/base.py:770 -#: /home/kovid/work/calibre/src/calibre/gui2/library/models.py:60 -#: /home/kovid/work/calibre/src/calibre/gui2/library/models.py:1075 +#: /home/kovid/work/calibre/src/calibre/gui2/library/models.py:85 +#: /home/kovid/work/calibre/src/calibre/gui2/library/models.py:1109 #: /home/kovid/work/calibre/src/calibre/gui2/store/stores/mobileread/models.py:23 msgid "Author(s)" msgstr "" #: /home/kovid/work/calibre/src/calibre/ebooks/metadata/book/base.py:771 -#: /home/kovid/work/calibre/src/calibre/gui2/library/models.py:65 +#: /home/kovid/work/calibre/src/calibre/gui2/library/models.py:90 #: /home/kovid/work/calibre/src/calibre/gui2/preferences/metadata_sources.py:159 msgid "Publisher" msgstr "" @@ -4067,7 +4084,7 @@ msgid "Producer" msgstr "" #: /home/kovid/work/calibre/src/calibre/ebooks/metadata/book/base.py:773 -#: /home/kovid/work/calibre/src/calibre/gui2/metadata/single.py:957 +#: /home/kovid/work/calibre/src/calibre/gui2/metadata/single.py:963 #: /home/kovid/work/calibre/src/calibre/gui2/preferences/metadata_sources.py:157 #: /home/kovid/work/calibre/src/calibre/library/field_metadata.py:245 msgid "Comments" @@ -4090,13 +4107,13 @@ msgstr "" #: /home/kovid/work/calibre/src/calibre/gui2/catalog/catalog_epub_mobi.py:535 #: /home/kovid/work/calibre/src/calibre/gui2/catalog/catalog_epub_mobi.py:842 #: /home/kovid/work/calibre/src/calibre/gui2/dialogs/tag_categories.py:60 -#: /home/kovid/work/calibre/src/calibre/gui2/library/models.py:66 +#: /home/kovid/work/calibre/src/calibre/gui2/library/models.py:91 #: /home/kovid/work/calibre/src/calibre/gui2/preferences/create_custom_column.py:70 #: /home/kovid/work/calibre/src/calibre/gui2/preferences/metadata_sources.py:161 -#: /home/kovid/work/calibre/src/calibre/library/catalogs/epub_mobi_builder.py:943 -#: /home/kovid/work/calibre/src/calibre/library/catalogs/epub_mobi_builder.py:1188 +#: /home/kovid/work/calibre/src/calibre/library/catalogs/epub_mobi_builder.py:982 +#: /home/kovid/work/calibre/src/calibre/library/catalogs/epub_mobi_builder.py:1228 #: /home/kovid/work/calibre/src/calibre/library/field_metadata.py:201 -#: /home/kovid/work/calibre/src/calibre/library/server/browse.py:779 +#: /home/kovid/work/calibre/src/calibre/library/server/browse.py:796 msgid "Tags" msgstr "" @@ -4105,11 +4122,11 @@ msgstr "" #: /home/kovid/work/calibre/src/calibre/gui2/dialogs/quickview.py:89 #: /home/kovid/work/calibre/src/calibre/gui2/dialogs/tag_categories.py:60 #: /home/kovid/work/calibre/src/calibre/gui2/dialogs/template_dialog.py:224 -#: /home/kovid/work/calibre/src/calibre/gui2/library/models.py:67 +#: /home/kovid/work/calibre/src/calibre/gui2/library/models.py:92 #: /home/kovid/work/calibre/src/calibre/gui2/preferences/create_custom_column.py:70 #: /home/kovid/work/calibre/src/calibre/gui2/preferences/metadata_sources.py:163 -#: /home/kovid/work/calibre/src/calibre/library/catalogs/epub_mobi_builder.py:303 -#: /home/kovid/work/calibre/src/calibre/library/catalogs/epub_mobi_builder.py:2266 +#: /home/kovid/work/calibre/src/calibre/library/catalogs/epub_mobi_builder.py:307 +#: /home/kovid/work/calibre/src/calibre/library/catalogs/epub_mobi_builder.py:2306 #: /home/kovid/work/calibre/src/calibre/library/field_metadata.py:140 msgid "Series" msgid_plural "Series" @@ -4117,7 +4134,7 @@ msgstr[0] "" msgstr[1] "" #: /home/kovid/work/calibre/src/calibre/ebooks/metadata/book/base.py:778 -#: /home/kovid/work/calibre/src/calibre/gui2/library/models.py:69 +#: /home/kovid/work/calibre/src/calibre/gui2/library/models.py:94 #: /home/kovid/work/calibre/src/calibre/gui2/preferences/metadata_sources.py:164 #: /home/kovid/work/calibre/src/calibre/library/field_metadata.py:127 msgid "Languages" @@ -4129,8 +4146,8 @@ msgstr "" #: /home/kovid/work/calibre/src/calibre/ebooks/metadata/book/base.py:782 #: /home/kovid/work/calibre/src/calibre/ebooks/oeb/transforms/jacket.py:183 -#: /home/kovid/work/calibre/src/calibre/gui2/library/models.py:63 -#: /home/kovid/work/calibre/src/calibre/gui2/metadata/single_download.py:146 +#: /home/kovid/work/calibre/src/calibre/gui2/library/models.py:88 +#: /home/kovid/work/calibre/src/calibre/gui2/metadata/single_download.py:150 #: /home/kovid/work/calibre/src/calibre/library/field_metadata.py:305 msgid "Published" msgstr "" @@ -4244,53 +4261,57 @@ msgstr "" #: /home/kovid/work/calibre/src/calibre/ebooks/metadata/opf2.py:1487 #: /home/kovid/work/calibre/src/calibre/ebooks/oeb/base.py:1279 -#: /home/kovid/work/calibre/src/calibre/gui2/metadata/single.py:969 +#: /home/kovid/work/calibre/src/calibre/gui2/metadata/single.py:975 #: /home/kovid/work/calibre/src/calibre/gui2/store/search/models.py:39 msgid "Cover" msgstr "" -#: /home/kovid/work/calibre/src/calibre/ebooks/metadata/sources/amazon.py:491 +#: /home/kovid/work/calibre/src/calibre/ebooks/metadata/sources/amazon.py:508 msgid "Downloads metadata and covers from Amazon" msgstr "" -#: /home/kovid/work/calibre/src/calibre/ebooks/metadata/sources/amazon.py:501 +#: /home/kovid/work/calibre/src/calibre/ebooks/metadata/sources/amazon.py:518 msgid "US" msgstr "" -#: /home/kovid/work/calibre/src/calibre/ebooks/metadata/sources/amazon.py:502 +#: /home/kovid/work/calibre/src/calibre/ebooks/metadata/sources/amazon.py:519 msgid "France" msgstr "" -#: /home/kovid/work/calibre/src/calibre/ebooks/metadata/sources/amazon.py:503 +#: /home/kovid/work/calibre/src/calibre/ebooks/metadata/sources/amazon.py:520 msgid "Germany" msgstr "" -#: /home/kovid/work/calibre/src/calibre/ebooks/metadata/sources/amazon.py:504 +#: /home/kovid/work/calibre/src/calibre/ebooks/metadata/sources/amazon.py:521 msgid "UK" msgstr "" -#: /home/kovid/work/calibre/src/calibre/ebooks/metadata/sources/amazon.py:505 +#: /home/kovid/work/calibre/src/calibre/ebooks/metadata/sources/amazon.py:522 msgid "Italy" msgstr "" -#: /home/kovid/work/calibre/src/calibre/ebooks/metadata/sources/amazon.py:506 +#: /home/kovid/work/calibre/src/calibre/ebooks/metadata/sources/amazon.py:523 msgid "Japan" msgstr "" -#: /home/kovid/work/calibre/src/calibre/ebooks/metadata/sources/amazon.py:507 +#: /home/kovid/work/calibre/src/calibre/ebooks/metadata/sources/amazon.py:524 msgid "Spain" msgstr "" -#: /home/kovid/work/calibre/src/calibre/ebooks/metadata/sources/amazon.py:511 +#: /home/kovid/work/calibre/src/calibre/ebooks/metadata/sources/amazon.py:525 +msgid "Brazil" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/ebooks/metadata/sources/amazon.py:529 msgid "Amazon website to use:" msgstr "" -#: /home/kovid/work/calibre/src/calibre/ebooks/metadata/sources/amazon.py:512 +#: /home/kovid/work/calibre/src/calibre/ebooks/metadata/sources/amazon.py:530 msgid "" "Metadata from Amazon will be fetched using this country's Amazon website." msgstr "" -#: /home/kovid/work/calibre/src/calibre/ebooks/metadata/sources/amazon.py:753 +#: /home/kovid/work/calibre/src/calibre/ebooks/metadata/sources/amazon.py:775 msgid "Amazon timed out. Try again later." msgstr "" @@ -4399,7 +4420,7 @@ msgstr "" #: /home/kovid/work/calibre/src/calibre/ebooks/mobi/writer8/toc.py:15 #: /home/kovid/work/calibre/src/calibre/ebooks/oeb/base.py:1281 #: /home/kovid/work/calibre/src/calibre/ebooks/oeb/transforms/htmltoc.py:15 -#: /home/kovid/work/calibre/src/calibre/gui2/viewer/main_ui.py:214 +#: /home/kovid/work/calibre/src/calibre/gui2/viewer/main_ui.py:221 #: /home/kovid/work/calibre/src/calibre/gui2/viewer/toc.py:217 msgid "Table of Contents" msgstr "" @@ -4481,11 +4502,11 @@ msgid "HTML TOC generation options." msgstr "" #: /home/kovid/work/calibre/src/calibre/ebooks/oeb/transforms/jacket.py:185 -#: /home/kovid/work/calibre/src/calibre/gui2/library/models.py:64 +#: /home/kovid/work/calibre/src/calibre/gui2/library/models.py:89 #: /home/kovid/work/calibre/src/calibre/gui2/preferences/create_custom_column.py:71 #: /home/kovid/work/calibre/src/calibre/gui2/preferences/metadata_sources.py:160 #: /home/kovid/work/calibre/src/calibre/library/field_metadata.py:176 -#: /home/kovid/work/calibre/src/calibre/library/server/browse.py:777 +#: /home/kovid/work/calibre/src/calibre/library/server/browse.py:794 msgid "Rating" msgstr "" @@ -4533,165 +4554,165 @@ msgstr "" msgid "Table of Contents:" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/__init__.py:120 +#: /home/kovid/work/calibre/src/calibre/gui2/__init__.py:121 msgid "Send file to storage card instead of main memory by default" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/__init__.py:122 +#: /home/kovid/work/calibre/src/calibre/gui2/__init__.py:123 msgid "Confirm before deleting" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/__init__.py:124 +#: /home/kovid/work/calibre/src/calibre/gui2/__init__.py:125 msgid "Main window geometry" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/__init__.py:126 +#: /home/kovid/work/calibre/src/calibre/gui2/__init__.py:127 msgid "Notify when a new version is available" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/__init__.py:128 +#: /home/kovid/work/calibre/src/calibre/gui2/__init__.py:129 msgid "Use Roman numerals for series number" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/__init__.py:130 +#: /home/kovid/work/calibre/src/calibre/gui2/__init__.py:131 msgid "Sort tags list by name, popularity, or rating" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/__init__.py:132 +#: /home/kovid/work/calibre/src/calibre/gui2/__init__.py:133 msgid "Match tags by any or all." msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/__init__.py:134 +#: /home/kovid/work/calibre/src/calibre/gui2/__init__.py:135 msgid "Number of covers to show in the cover browsing mode" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/__init__.py:136 +#: /home/kovid/work/calibre/src/calibre/gui2/__init__.py:137 msgid "Defaults for conversion to LRF" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/__init__.py:138 +#: /home/kovid/work/calibre/src/calibre/gui2/__init__.py:139 msgid "Options for the LRF ebook viewer" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/__init__.py:142 +#: /home/kovid/work/calibre/src/calibre/gui2/__init__.py:143 msgid "Formats that are viewed using the internal viewer" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/__init__.py:144 +#: /home/kovid/work/calibre/src/calibre/gui2/__init__.py:145 msgid "Columns to be displayed in the book list" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/__init__.py:145 +#: /home/kovid/work/calibre/src/calibre/gui2/__init__.py:146 msgid "Automatically launch content server on application startup" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/__init__.py:146 +#: /home/kovid/work/calibre/src/calibre/gui2/__init__.py:147 msgid "Oldest news kept in database" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/__init__.py:147 +#: /home/kovid/work/calibre/src/calibre/gui2/__init__.py:148 msgid "Show system tray icon" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/__init__.py:149 +#: /home/kovid/work/calibre/src/calibre/gui2/__init__.py:150 msgid "Upload downloaded news to device" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/__init__.py:151 +#: /home/kovid/work/calibre/src/calibre/gui2/__init__.py:152 msgid "Delete news books from library after uploading to device" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/__init__.py:153 +#: /home/kovid/work/calibre/src/calibre/gui2/__init__.py:154 msgid "" "Show the cover flow in a separate window instead of in the main calibre " "window" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/__init__.py:155 +#: /home/kovid/work/calibre/src/calibre/gui2/__init__.py:156 msgid "Disable notifications from the system tray icon" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/__init__.py:157 +#: /home/kovid/work/calibre/src/calibre/gui2/__init__.py:158 msgid "Default action to perform when send to device button is clicked" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/__init__.py:162 +#: /home/kovid/work/calibre/src/calibre/gui2/__init__.py:163 msgid "" "Start searching as you type. If this is disabled then search will only take " "place when the Enter or Return key is pressed." msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/__init__.py:165 +#: /home/kovid/work/calibre/src/calibre/gui2/__init__.py:166 msgid "" "When searching, show all books with search results highlighted instead of " "showing only the matches. You can use the N or F3 keys to go to the next " "match." msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/__init__.py:190 +#: /home/kovid/work/calibre/src/calibre/gui2/__init__.py:191 msgid "" "Maximum number of simultaneous conversion/news download jobs. This number is " "twice the actual value for historical reasons." msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/__init__.py:193 +#: /home/kovid/work/calibre/src/calibre/gui2/__init__.py:194 msgid "Download social metadata (tags/rating/etc.)" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/__init__.py:195 +#: /home/kovid/work/calibre/src/calibre/gui2/__init__.py:196 msgid "Overwrite author and title with new metadata" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/__init__.py:197 +#: /home/kovid/work/calibre/src/calibre/gui2/__init__.py:198 msgid "Automatically download the cover, if available" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/__init__.py:199 +#: /home/kovid/work/calibre/src/calibre/gui2/__init__.py:200 msgid "Limit max simultaneous jobs to number of CPUs" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/__init__.py:201 +#: /home/kovid/work/calibre/src/calibre/gui2/__init__.py:202 msgid "" "The layout of the user interface. Wide has the book details panel on the " "right and narrow has it at the bottom." msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/__init__.py:205 +#: /home/kovid/work/calibre/src/calibre/gui2/__init__.py:206 msgid "Show the average rating per item indication in the tag browser" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/__init__.py:207 +#: /home/kovid/work/calibre/src/calibre/gui2/__init__.py:208 msgid "Disable UI animations" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/__init__.py:212 +#: /home/kovid/work/calibre/src/calibre/gui2/__init__.py:213 msgid "tag browser categories not to display" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/__init__.py:278 +#: /home/kovid/work/calibre/src/calibre/gui2/__init__.py:279 msgid "WARNING:" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/__init__.py:288 +#: /home/kovid/work/calibre/src/calibre/gui2/__init__.py:289 msgid "ERROR:" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/__init__.py:300 +#: /home/kovid/work/calibre/src/calibre/gui2/__init__.py:301 #: /home/kovid/work/calibre/src/calibre/gui2/tag_browser/ui.py:258 msgid "Show this confirmation again" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/__init__.py:339 +#: /home/kovid/work/calibre/src/calibre/gui2/__init__.py:340 #: /home/kovid/work/calibre/src/calibre/gui2/preferences/server.py:134 msgid "Restart needed" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/__init__.py:341 +#: /home/kovid/work/calibre/src/calibre/gui2/__init__.py:342 #: /home/kovid/work/calibre/src/calibre/gui2/dialogs/plugin_updater.py:741 msgid "Restart calibre now" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/__init__.py:572 +#: /home/kovid/work/calibre/src/calibre/gui2/__init__.py:573 msgid "Choose Files" msgstr "" @@ -4804,7 +4825,7 @@ msgstr "" #: /home/kovid/work/calibre/src/calibre/gui2/actions/annotate.py:120 #: /home/kovid/work/calibre/src/calibre/gui2/actions/catalog.py:38 #: /home/kovid/work/calibre/src/calibre/gui2/actions/convert.py:107 -#: /home/kovid/work/calibre/src/calibre/gui2/actions/copy_to_library.py:175 +#: /home/kovid/work/calibre/src/calibre/gui2/actions/copy_to_library.py:228 #: /home/kovid/work/calibre/src/calibre/gui2/actions/edit_metadata.py:75 #: /home/kovid/work/calibre/src/calibre/gui2/actions/edit_metadata.py:192 #: /home/kovid/work/calibre/src/calibre/gui2/actions/edit_metadata.py:256 @@ -4834,32 +4855,32 @@ msgstr "" msgid "Select book files" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/actions/add.py:178 +#: /home/kovid/work/calibre/src/calibre/gui2/actions/add.py:189 msgid "Adding" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/actions/add.py:179 +#: /home/kovid/work/calibre/src/calibre/gui2/actions/add.py:190 msgid "Creating book records from ISBNs" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/actions/add.py:270 -#: /home/kovid/work/calibre/src/calibre/gui2/actions/add.py:319 +#: /home/kovid/work/calibre/src/calibre/gui2/actions/add.py:281 +#: /home/kovid/work/calibre/src/calibre/gui2/actions/add.py:330 msgid "Uploading books to device." msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/actions/add.py:290 +#: /home/kovid/work/calibre/src/calibre/gui2/actions/add.py:301 msgid "Supported books" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/actions/add.py:293 +#: /home/kovid/work/calibre/src/calibre/gui2/actions/add.py:304 msgid "Select books" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/actions/add.py:331 +#: /home/kovid/work/calibre/src/calibre/gui2/actions/add.py:342 msgid "Merged some books" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/actions/add.py:332 +#: /home/kovid/work/calibre/src/calibre/gui2/actions/add.py:343 #, python-format msgid "" "The following %d duplicate books were found and incoming book formats were " @@ -4867,21 +4888,21 @@ msgid "" "settings:" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/actions/add.py:354 +#: /home/kovid/work/calibre/src/calibre/gui2/actions/add.py:365 msgid "Failed to read metadata" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/actions/add.py:355 +#: /home/kovid/work/calibre/src/calibre/gui2/actions/add.py:366 msgid "Failed to read metadata from the following" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/actions/add.py:376 -#: /home/kovid/work/calibre/src/calibre/gui2/actions/add.py:381 -#: /home/kovid/work/calibre/src/calibre/gui2/actions/add.py:400 +#: /home/kovid/work/calibre/src/calibre/gui2/actions/add.py:387 +#: /home/kovid/work/calibre/src/calibre/gui2/actions/add.py:392 +#: /home/kovid/work/calibre/src/calibre/gui2/actions/add.py:411 msgid "Add to library" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/actions/add.py:381 +#: /home/kovid/work/calibre/src/calibre/gui2/actions/add.py:392 #: /home/kovid/work/calibre/src/calibre/gui2/actions/delete.py:137 #: /home/kovid/work/calibre/src/calibre/gui2/actions/store.py:87 #: /home/kovid/work/calibre/src/calibre/gui2/actions/store.py:106 @@ -4892,32 +4913,32 @@ msgstr "" msgid "No book selected" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/actions/add.py:394 +#: /home/kovid/work/calibre/src/calibre/gui2/actions/add.py:405 msgid "" "The following books are virtual and cannot be added to the calibre library:" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/actions/add.py:400 +#: /home/kovid/work/calibre/src/calibre/gui2/actions/add.py:411 msgid "No book files found" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/actions/add.py:406 +#: /home/kovid/work/calibre/src/calibre/gui2/actions/add.py:417 msgid "Downloading books" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/actions/add.py:407 +#: /home/kovid/work/calibre/src/calibre/gui2/actions/add.py:418 msgid "Downloading books from device" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/actions/add.py:426 +#: /home/kovid/work/calibre/src/calibre/gui2/actions/add.py:437 msgid "Could not download files from the device" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/actions/add.py:429 +#: /home/kovid/work/calibre/src/calibre/gui2/actions/add.py:440 msgid "Could not download some files from the device" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/actions/add.py:433 +#: /home/kovid/work/calibre/src/calibre/gui2/actions/add.py:444 msgid "Could not download files" msgstr "" @@ -5034,6 +5055,7 @@ msgid "No existing calibre library found at %s" msgstr "" #: /home/kovid/work/calibre/src/calibre/gui2/actions/choose_library.py:152 +#: /home/kovid/work/calibre/src/calibre/gui2/actions/copy_to_library.py:160 msgid "Choose Library" msgstr "" @@ -5047,7 +5069,7 @@ msgstr "" #: /home/kovid/work/calibre/src/calibre/gui2/actions/choose_library.py:160 #: /home/kovid/work/calibre/src/calibre/gui2/preferences/toolbar.py:58 -#: /home/kovid/work/calibre/src/calibre/library/server/browse.py:171 +#: /home/kovid/work/calibre/src/calibre/library/server/browse.py:172 #: /home/kovid/work/calibre/src/calibre/library/server/opds.py:129 #, python-format msgid "%d books" @@ -5204,7 +5226,7 @@ msgid "" msgstr "" #: /home/kovid/work/calibre/src/calibre/gui2/actions/choose_library.py:423 -#: /home/kovid/work/calibre/src/calibre/gui2/actions/copy_to_library.py:197 +#: /home/kovid/work/calibre/src/calibre/gui2/actions/copy_to_library.py:250 #: /home/kovid/work/calibre/src/calibre/gui2/device.py:975 #: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk.py:1007 #: /home/kovid/work/calibre/src/calibre/gui2/dialogs/restore_library.py:114 @@ -5232,7 +5254,7 @@ msgstr "" #: /home/kovid/work/calibre/src/calibre/gui2/actions/choose_library.py:534 #: /home/kovid/work/calibre/src/calibre/gui2/actions/choose_library.py:539 -#: /home/kovid/work/calibre/src/calibre/gui2/actions/copy_to_library.py:225 +#: /home/kovid/work/calibre/src/calibre/gui2/actions/copy_to_library.py:278 #: /home/kovid/work/calibre/src/calibre/gui2/actions/save_to_disk.py:91 #: /home/kovid/work/calibre/src/calibre/gui2/library/views.py:1016 msgid "Not allowed" @@ -5282,7 +5304,7 @@ msgstr "" msgid "Empty output file, probably the conversion process crashed" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/actions/copy_to_library.py:84 +#: /home/kovid/work/calibre/src/calibre/gui2/actions/copy_to_library.py:86 #: /home/kovid/work/calibre/src/calibre/gui2/add.py:401 #: /home/kovid/work/calibre/src/calibre/gui2/add.py:405 #: /home/kovid/work/calibre/src/calibre/gui2/auto_add.py:221 @@ -5290,57 +5312,83 @@ msgstr "" msgid "%(title)s by %(author)s" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/actions/copy_to_library.py:131 +#: /home/kovid/work/calibre/src/calibre/gui2/actions/copy_to_library.py:137 +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/toolbar.py:59 +msgid "Choose library" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/actions/copy_to_library.py:138 +msgid "Library &path:" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/actions/copy_to_library.py:146 +msgid "Browse for library" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/actions/copy_to_library.py:149 +msgid "&Delete after copy" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/actions/copy_to_library.py:172 msgid "Copy to library" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/actions/copy_to_library.py:132 +#: /home/kovid/work/calibre/src/calibre/gui2/actions/copy_to_library.py:173 msgid "Copy selected books to the specified library" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/actions/copy_to_library.py:165 +#: /home/kovid/work/calibre/src/calibre/gui2/actions/copy_to_library.py:206 msgid "(delete after copy)" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/actions/copy_to_library.py:174 +#: /home/kovid/work/calibre/src/calibre/gui2/actions/copy_to_library.py:210 +msgid "Choose library by path..." +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/actions/copy_to_library.py:220 +#: /home/kovid/work/calibre/src/calibre/gui2/actions/copy_to_library.py:227 msgid "Cannot copy" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/actions/copy_to_library.py:179 +#: /home/kovid/work/calibre/src/calibre/gui2/actions/copy_to_library.py:221 +msgid "Cannot copy to current library." +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/actions/copy_to_library.py:232 msgid "No library" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/actions/copy_to_library.py:180 +#: /home/kovid/work/calibre/src/calibre/gui2/actions/copy_to_library.py:233 #, python-format msgid "No library found at %s" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/actions/copy_to_library.py:182 -#: /home/kovid/work/calibre/src/calibre/gui2/actions/copy_to_library.py:186 +#: /home/kovid/work/calibre/src/calibre/gui2/actions/copy_to_library.py:235 +#: /home/kovid/work/calibre/src/calibre/gui2/actions/copy_to_library.py:239 msgid "Copying" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/actions/copy_to_library.py:197 +#: /home/kovid/work/calibre/src/calibre/gui2/actions/copy_to_library.py:250 msgid "Could not copy books: " msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/actions/copy_to_library.py:201 +#: /home/kovid/work/calibre/src/calibre/gui2/actions/copy_to_library.py:254 #, python-format msgid "Copied %(num)d books to %(loc)s" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/actions/copy_to_library.py:205 +#: /home/kovid/work/calibre/src/calibre/gui2/actions/copy_to_library.py:258 msgid "Auto merged" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/actions/copy_to_library.py:206 +#: /home/kovid/work/calibre/src/calibre/gui2/actions/copy_to_library.py:259 msgid "" "Some books were automatically merged into existing records in the target " "library. Click Show details to see which ones. This behavior is controlled " "by the Auto merge option in Preferences->Adding books." msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/actions/copy_to_library.py:226 +#: /home/kovid/work/calibre/src/calibre/gui2/actions/copy_to_library.py:279 msgid "" "You cannot use other libraries while using the environment variable " "CALIBRE_OVERRIDE_DATABASE_PATH." @@ -5476,23 +5524,23 @@ msgid "None of the selected books are on the device" msgstr "" #: /home/kovid/work/calibre/src/calibre/gui2/actions/delete.py:263 -#: /home/kovid/work/calibre/src/calibre/gui2/actions/delete.py:361 +#: /home/kovid/work/calibre/src/calibre/gui2/actions/delete.py:363 msgid "Deleting books from device." msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/actions/delete.py:319 +#: /home/kovid/work/calibre/src/calibre/gui2/actions/delete.py:321 msgid "" "Some of the selected books are on the attached device. Where do you " "want the selected files deleted from?" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/actions/delete.py:331 +#: /home/kovid/work/calibre/src/calibre/gui2/actions/delete.py:333 msgid "" "The selected books will be permanently deleted and the files removed " "from your calibre library. Are you sure?" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/actions/delete.py:353 +#: /home/kovid/work/calibre/src/calibre/gui2/actions/delete.py:355 msgid "" "The selected books will be permanently deleted from your device. Are " "you sure?" @@ -5647,8 +5695,8 @@ msgstr "" #: /home/kovid/work/calibre/src/calibre/gui2/actions/edit_metadata.py:101 #: /home/kovid/work/calibre/src/calibre/gui2/dnd.py:84 -#: /home/kovid/work/calibre/src/calibre/gui2/metadata/single_download.py:514 -#: /home/kovid/work/calibre/src/calibre/gui2/metadata/single_download.py:826 +#: /home/kovid/work/calibre/src/calibre/gui2/metadata/single_download.py:518 +#: /home/kovid/work/calibre/src/calibre/gui2/metadata/single_download.py:830 msgid "Download failed" msgstr "" @@ -5684,7 +5732,7 @@ msgid "Download complete" msgstr "" #: /home/kovid/work/calibre/src/calibre/gui2/actions/edit_metadata.py:123 -#: /home/kovid/work/calibre/src/calibre/gui2/metadata/single_download.py:888 +#: /home/kovid/work/calibre/src/calibre/gui2/metadata/single_download.py:892 msgid "Download log" msgstr "" @@ -5790,7 +5838,7 @@ msgstr "" #: /home/kovid/work/calibre/src/calibre/gui2/actions/help.py:16 #: /home/kovid/work/calibre/src/calibre/gui2/preferences/tweaks_ui.py:118 -#: /usr/src/qt-everywhere-opensource-src-4.8.2/src/gui/widgets/qdialogbuttonbox.cpp:679 +#: /usr/src/qt-everywhere-opensource-src-4.8.4/src/gui/widgets/qdialogbuttonbox.cpp:679 msgid "Help" msgstr "" @@ -5799,7 +5847,7 @@ msgid "Move to next match" msgstr "" #: /home/kovid/work/calibre/src/calibre/gui2/actions/next_match.py:13 -#: /home/kovid/work/calibre/src/calibre/gui2/viewer/main_ui.py:219 +#: /home/kovid/work/calibre/src/calibre/gui2/viewer/main_ui.py:226 msgid "F3" msgstr "" @@ -5825,7 +5873,7 @@ msgid "Shift+N" msgstr "" #: /home/kovid/work/calibre/src/calibre/gui2/actions/next_match.py:27 -#: /home/kovid/work/calibre/src/calibre/gui2/viewer/main_ui.py:228 +#: /home/kovid/work/calibre/src/calibre/gui2/viewer/main_ui.py:235 msgid "Shift+F3" msgstr "" @@ -5945,7 +5993,7 @@ msgid "Click the show details button to see which ones." msgstr "" #: /home/kovid/work/calibre/src/calibre/gui2/actions/show_book_details.py:16 -#: /home/kovid/work/calibre/src/calibre/library/server/browse.py:784 +#: /home/kovid/work/calibre/src/calibre/library/server/browse.py:801 msgid "Show book details" msgstr "" @@ -6038,7 +6086,7 @@ msgid "this book" msgstr "" #: /home/kovid/work/calibre/src/calibre/gui2/actions/store.py:32 -#: /home/kovid/work/calibre/src/calibre/gui2/tag_browser/view.py:469 +#: /home/kovid/work/calibre/src/calibre/gui2/tag_browser/view.py:498 #, python-format msgid "Search for %s" msgstr "" @@ -6139,7 +6187,7 @@ msgstr "" #: /home/kovid/work/calibre/src/calibre/gui2/dialogs/comments_dialog.py:25 #: /home/kovid/work/calibre/src/calibre/gui2/dialogs/template_dialog.py:248 #: /home/kovid/work/calibre/src/calibre/gui2/preferences/main.py:234 -#: /usr/src/qt-everywhere-opensource-src-4.8.2/src/gui/widgets/qdialogbuttonbox.cpp:667 +#: /usr/src/qt-everywhere-opensource-src-4.8.4/src/gui/widgets/qdialogbuttonbox.cpp:667 msgid "&Cancel" msgstr "" @@ -6315,7 +6363,7 @@ msgid "The specified directory could not be processed." msgstr "" #: /home/kovid/work/calibre/src/calibre/gui2/add.py:283 -#: /home/kovid/work/calibre/src/calibre/gui2/device.py:1131 +#: /home/kovid/work/calibre/src/calibre/gui2/device.py:1137 msgid "No books" msgstr "" @@ -6487,10 +6535,10 @@ msgstr "" #: /home/kovid/work/calibre/src/calibre/gui2/dialogs/tag_editor_ui.py:140 #: /home/kovid/work/calibre/src/calibre/gui2/dialogs/tag_list_editor_ui.py:78 #: /home/kovid/work/calibre/src/calibre/gui2/dialogs/tag_list_editor_ui.py:80 -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/user_profiles_ui.py:277 -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/user_profiles_ui.py:279 -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/user_profiles_ui.py:280 -#: /home/kovid/work/calibre/src/calibre/gui2/preferences/adding_ui.py:162 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/user_profiles_ui.py:283 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/user_profiles_ui.py:285 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/user_profiles_ui.py:286 +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/adding_ui.py:171 #: /home/kovid/work/calibre/src/calibre/gui2/preferences/behavior_ui.py:166 #: /home/kovid/work/calibre/src/calibre/gui2/preferences/behavior_ui.py:167 #: /home/kovid/work/calibre/src/calibre/gui2/preferences/columns_ui.py:89 @@ -6509,7 +6557,7 @@ msgstr "" #: /home/kovid/work/calibre/src/calibre/gui2/store/search/search_ui.py:174 #: /home/kovid/work/calibre/src/calibre/gui2/store/search/search_ui.py:181 #: /home/kovid/work/calibre/src/calibre/gui2/store/stores/mobileread/store_dialog_ui.py:75 -#: /home/kovid/work/calibre/src/calibre/gui2/viewer/main_ui.py:206 +#: /home/kovid/work/calibre/src/calibre/gui2/viewer/main_ui.py:213 msgid "..." msgstr "" @@ -6550,6 +6598,7 @@ msgid "Click to open" msgstr "" #: /home/kovid/work/calibre/src/calibre/gui2/book_details.py:180 +#: /home/kovid/work/calibre/src/calibre/library/server/browse.py:850 msgid "Ids" msgstr "" @@ -6559,7 +6608,7 @@ msgid "Book %(sidx)s of %(series)s" msgstr "" #: /home/kovid/work/calibre/src/calibre/gui2/book_details.py:233 -#: /home/kovid/work/calibre/src/calibre/gui2/library/models.py:1078 +#: /home/kovid/work/calibre/src/calibre/gui2/library/models.py:1112 msgid "Collections" msgstr "" @@ -6670,7 +6719,7 @@ msgstr "" #: /home/kovid/work/calibre/src/calibre/gui2/convert/page_setup_ui.py:124 #: /home/kovid/work/calibre/src/calibre/gui2/convert/pdb_output_ui.py:47 #: /home/kovid/work/calibre/src/calibre/gui2/convert/pdf_input_ui.py:43 -#: /home/kovid/work/calibre/src/calibre/gui2/convert/pdf_output_ui.py:100 +#: /home/kovid/work/calibre/src/calibre/gui2/convert/pdf_output_ui.py:114 #: /home/kovid/work/calibre/src/calibre/gui2/convert/pmlz_output_ui.py:46 #: /home/kovid/work/calibre/src/calibre/gui2/convert/rb_output_ui.py:33 #: /home/kovid/work/calibre/src/calibre/gui2/convert/search_and_replace_ui.py:145 @@ -6684,13 +6733,13 @@ msgstr "" #: /home/kovid/work/calibre/src/calibre/gui2/device_drivers/configwidget_ui.py:79 #: /home/kovid/work/calibre/src/calibre/gui2/dialogs/search_item_ui.py:40 #: /home/kovid/work/calibre/src/calibre/gui2/filename_pattern_ui.py:128 -#: /home/kovid/work/calibre/src/calibre/gui2/preferences/adding_ui.py:125 +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/adding_ui.py:134 #: /home/kovid/work/calibre/src/calibre/gui2/preferences/behavior_ui.py:146 #: /home/kovid/work/calibre/src/calibre/gui2/preferences/columns_ui.py:86 #: /home/kovid/work/calibre/src/calibre/gui2/preferences/conversion_ui.py:54 #: /home/kovid/work/calibre/src/calibre/gui2/preferences/email_ui.py:65 #: /home/kovid/work/calibre/src/calibre/gui2/preferences/look_feel_ui.py:246 -#: /home/kovid/work/calibre/src/calibre/gui2/preferences/metadata_sources_ui.py:118 +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/metadata_sources_ui.py:125 #: /home/kovid/work/calibre/src/calibre/gui2/preferences/misc_ui.py:74 #: /home/kovid/work/calibre/src/calibre/gui2/preferences/plugboard_ui.py:113 #: /home/kovid/work/calibre/src/calibre/gui2/preferences/plugins_ui.py:108 @@ -6814,8 +6863,8 @@ msgid "Delete Rule" msgstr "" #: /home/kovid/work/calibre/src/calibre/gui2/catalog/catalog_epub_mobi.py:850 -#: /home/kovid/work/calibre/src/calibre/library/catalogs/epub_mobi_builder.py:609 -#: /home/kovid/work/calibre/src/calibre/library/catalogs/epub_mobi_builder.py:4725 +#: /home/kovid/work/calibre/src/calibre/library/catalogs/epub_mobi_builder.py:613 +#: /home/kovid/work/calibre/src/calibre/library/catalogs/epub_mobi_builder.py:4770 msgid "False" msgstr "" @@ -6828,7 +6877,7 @@ msgstr "" #: /home/kovid/work/calibre/src/calibre/gui2/dialogs/check_library.py:342 #: /home/kovid/work/calibre/src/calibre/gui2/store/config/chooser/models.py:21 #: /home/kovid/work/calibre/src/calibre/gui2/viewer/bookmarkmanager.py:90 -#: /home/kovid/work/calibre/src/calibre/library/server/browse.py:257 +#: /home/kovid/work/calibre/src/calibre/library/server/browse.py:260 msgid "Name" msgstr "" @@ -7798,7 +7847,7 @@ msgstr "" #: /home/kovid/work/calibre/src/calibre/gui2/convert/metadata.py:47 #: /home/kovid/work/calibre/src/calibre/gui2/viewer/main.py:98 -#: /home/kovid/work/calibre/src/calibre/gui2/viewer/main_ui.py:215 +#: /home/kovid/work/calibre/src/calibre/gui2/viewer/main_ui.py:222 msgid "Metadata" msgstr "" @@ -7901,7 +7950,7 @@ msgstr "" #: /home/kovid/work/calibre/src/calibre/gui2/convert/metadata_ui.py:171 #: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:561 -#: /home/kovid/work/calibre/src/calibre/gui2/metadata/basic_widgets.py:1101 +#: /home/kovid/work/calibre/src/calibre/gui2/metadata/basic_widgets.py:1104 msgid "" "Tags categorize the book. This is particularly useful while searching. " "

    They can be any words or phrases, separated by commas." @@ -8041,48 +8090,59 @@ msgstr "" msgid "PDF Output" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/convert/pdf_output_ui.py:101 +#: /home/kovid/work/calibre/src/calibre/gui2/convert/pdf_output_ui.py:115 +msgid "" +"Note: The paper size settings below only take effect if you enable " +"the \"Override\" checkbox below. Otherwise the size from the output profile " +"will be used." +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/convert/pdf_output_ui.py:116 +msgid "&Override paper size set in output profile" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/convert/pdf_output_ui.py:117 msgid "&Paper Size:" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/convert/pdf_output_ui.py:102 -msgid "&Orientation:" -msgstr "" - -#: /home/kovid/work/calibre/src/calibre/gui2/convert/pdf_output_ui.py:103 +#: /home/kovid/work/calibre/src/calibre/gui2/convert/pdf_output_ui.py:118 msgid "&Custom size:" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/convert/pdf_output_ui.py:104 +#: /home/kovid/work/calibre/src/calibre/gui2/convert/pdf_output_ui.py:119 +msgid "&Unit:" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/convert/pdf_output_ui.py:120 msgid "Preserve &aspect ratio of cover" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/convert/pdf_output_ui.py:105 +#: /home/kovid/work/calibre/src/calibre/gui2/convert/pdf_output_ui.py:121 #: /home/kovid/work/calibre/src/calibre/gui2/viewer/config_ui.py:374 msgid "Se&rif family:" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/convert/pdf_output_ui.py:106 +#: /home/kovid/work/calibre/src/calibre/gui2/convert/pdf_output_ui.py:122 #: /home/kovid/work/calibre/src/calibre/gui2/viewer/config_ui.py:375 msgid "&Sans family:" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/convert/pdf_output_ui.py:107 +#: /home/kovid/work/calibre/src/calibre/gui2/convert/pdf_output_ui.py:123 #: /home/kovid/work/calibre/src/calibre/gui2/viewer/config_ui.py:376 msgid "&Monospace family:" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/convert/pdf_output_ui.py:108 +#: /home/kovid/work/calibre/src/calibre/gui2/convert/pdf_output_ui.py:124 #: /home/kovid/work/calibre/src/calibre/gui2/viewer/config_ui.py:381 msgid "S&tandard font:" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/convert/pdf_output_ui.py:109 +#: /home/kovid/work/calibre/src/calibre/gui2/convert/pdf_output_ui.py:125 msgid "Default font si&ze:" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/convert/pdf_output_ui.py:110 -#: /home/kovid/work/calibre/src/calibre/gui2/convert/pdf_output_ui.py:112 +#: /home/kovid/work/calibre/src/calibre/gui2/convert/pdf_output_ui.py:126 +#: /home/kovid/work/calibre/src/calibre/gui2/convert/pdf_output_ui.py:128 #: /home/kovid/work/calibre/src/calibre/gui2/viewer/config_ui.py:378 #: /home/kovid/work/calibre/src/calibre/gui2/viewer/config_ui.py:380 #: /home/kovid/work/calibre/src/calibre/gui2/viewer/config_ui.py:395 @@ -8092,18 +8152,11 @@ msgstr "" msgid " px" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/convert/pdf_output_ui.py:111 +#: /home/kovid/work/calibre/src/calibre/gui2/convert/pdf_output_ui.py:127 #: /home/kovid/work/calibre/src/calibre/gui2/viewer/config_ui.py:379 msgid "Monospace &font size:" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/convert/pdf_output_ui.py:113 -msgid "" -"Note: The paper size settings below only take effect if you have set " -"the output profile to the default output profile. Otherwise the output " -"profile will override these settings." -msgstr "" - #: /home/kovid/work/calibre/src/calibre/gui2/convert/pml_output.py:14 msgid "PMLZ Output" msgstr "" @@ -8317,7 +8370,7 @@ msgstr "" #: /home/kovid/work/calibre/src/calibre/gui2/convert/search_and_replace_ui.py:157 #: /home/kovid/work/calibre/src/calibre/gui2/preferences/search_ui.py:188 #: /home/kovid/work/calibre/src/calibre/gui2/viewer/config_ui.py:439 -#: /usr/src/qt-everywhere-opensource-src-4.8.2/src/gui/widgets/qdialogbuttonbox.cpp:661 +#: /usr/src/qt-everywhere-opensource-src-4.8.4/src/gui/widgets/qdialogbuttonbox.cpp:661 msgid "&Save" msgstr "" @@ -8339,7 +8392,7 @@ msgid "" "Add button to add it to the list of expressions." msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/convert/single.py:184 +#: /home/kovid/work/calibre/src/calibre/gui2/convert/single.py:187 msgid "Convert" msgstr "" @@ -8692,7 +8745,7 @@ msgstr "" #: /home/kovid/work/calibre/src/calibre/gui2/library/delegates.py:233 #: /home/kovid/work/calibre/src/calibre/gui2/library/delegates.py:290 #: /home/kovid/work/calibre/src/calibre/gui2/library/delegates.py:294 -#: /home/kovid/work/calibre/src/calibre/gui2/metadata/basic_widgets.py:1413 +#: /home/kovid/work/calibre/src/calibre/gui2/metadata/basic_widgets.py:1416 msgid "Undefined" msgstr "" @@ -8886,7 +8939,7 @@ msgid "Detected the %s. Do you want calibre to manage it?" msgstr "" #: /home/kovid/work/calibre/src/calibre/gui2/device.py:884 -#: /home/kovid/work/calibre/src/calibre/gui2/device.py:1460 +#: /home/kovid/work/calibre/src/calibre/gui2/device.py:1466 #: /home/kovid/work/calibre/src/calibre/gui2/email.py:262 msgid "No suitable formats" msgstr "" @@ -8924,89 +8977,89 @@ msgid "" "reconnect the device or reboot." msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/device.py:1027 +#: /home/kovid/work/calibre/src/calibre/gui2/device.py:1033 msgid "Device: " msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/device.py:1029 +#: /home/kovid/work/calibre/src/calibre/gui2/device.py:1035 msgid " detected." msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/device.py:1132 +#: /home/kovid/work/calibre/src/calibre/gui2/device.py:1138 msgid "selected to send" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/device.py:1139 -#: /home/kovid/work/calibre/src/calibre/gui2/device.py:1169 +#: /home/kovid/work/calibre/src/calibre/gui2/device.py:1145 +#: /home/kovid/work/calibre/src/calibre/gui2/device.py:1175 msgid "No device" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/device.py:1140 +#: /home/kovid/work/calibre/src/calibre/gui2/device.py:1146 msgid "No device connected" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/device.py:1156 +#: /home/kovid/work/calibre/src/calibre/gui2/device.py:1162 #, python-format msgid "%(num)i of %(total)i Books" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/device.py:1160 +#: /home/kovid/work/calibre/src/calibre/gui2/device.py:1166 #, python-format msgid "0 of %i Books" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/device.py:1161 +#: /home/kovid/work/calibre/src/calibre/gui2/device.py:1167 msgid "Choose format to send to device" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/device.py:1170 +#: /home/kovid/work/calibre/src/calibre/gui2/device.py:1176 msgid "Cannot send: No device is connected" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/device.py:1173 -#: /home/kovid/work/calibre/src/calibre/gui2/device.py:1177 +#: /home/kovid/work/calibre/src/calibre/gui2/device.py:1179 +#: /home/kovid/work/calibre/src/calibre/gui2/device.py:1183 msgid "No card" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/device.py:1174 -#: /home/kovid/work/calibre/src/calibre/gui2/device.py:1178 +#: /home/kovid/work/calibre/src/calibre/gui2/device.py:1180 +#: /home/kovid/work/calibre/src/calibre/gui2/device.py:1184 msgid "Cannot send: Device has no storage card" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/device.py:1239 -#: /home/kovid/work/calibre/src/calibre/gui2/device.py:1322 -#: /home/kovid/work/calibre/src/calibre/gui2/device.py:1454 +#: /home/kovid/work/calibre/src/calibre/gui2/device.py:1245 +#: /home/kovid/work/calibre/src/calibre/gui2/device.py:1328 +#: /home/kovid/work/calibre/src/calibre/gui2/device.py:1460 msgid "Auto convert the following books before uploading to the device?" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/device.py:1268 +#: /home/kovid/work/calibre/src/calibre/gui2/device.py:1274 msgid "Sending catalogs to device." msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/device.py:1367 +#: /home/kovid/work/calibre/src/calibre/gui2/device.py:1373 msgid "Sending news to device." msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/device.py:1421 +#: /home/kovid/work/calibre/src/calibre/gui2/device.py:1427 msgid "Sending books to device." msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/device.py:1461 +#: /home/kovid/work/calibre/src/calibre/gui2/device.py:1467 msgid "" "Could not upload the following books to the device, as no suitable formats " "were found. Convert the book(s) to a format supported by your device first." msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/device.py:1534 +#: /home/kovid/work/calibre/src/calibre/gui2/device.py:1540 msgid "No space on device" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/device.py:1535 +#: /home/kovid/work/calibre/src/calibre/gui2/device.py:1541 msgid "" "

    Cannot upload books to device there is no more free space available " msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/device.py:1540 +#: /home/kovid/work/calibre/src/calibre/gui2/device.py:1546 msgid "Incorrect destination" msgstr "" @@ -9159,26 +9212,71 @@ msgid "&Ignore the %s in calibre" msgstr "" #: /home/kovid/work/calibre/src/calibre/gui2/device_drivers/mtp_config.py:377 +msgid "Change scanned &folders" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/device_drivers/mtp_config.py:383 msgid "Show device information" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/device_drivers/mtp_config.py:411 +#: /home/kovid/work/calibre/src/calibre/gui2/device_drivers/mtp_config.py:421 #: /home/kovid/work/calibre/src/calibre/gui2/dialogs/message_box.py:141 -#: /home/kovid/work/calibre/src/calibre/gui2/metadata/single_download.py:881 +#: /home/kovid/work/calibre/src/calibre/gui2/metadata/single_download.py:885 #: /home/kovid/work/calibre/src/calibre/gui2/preferences/tweaks.py:344 -#: /home/kovid/work/calibre/src/calibre/gui2/viewer/main_ui.py:220 +#: /home/kovid/work/calibre/src/calibre/gui2/viewer/main_ui.py:227 msgid "Copy to clipboard" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/device_drivers/mtp_config.py:419 +#: /home/kovid/work/calibre/src/calibre/gui2/device_drivers/mtp_config.py:435 #, python-format msgid "The %s will be ignored in calibre" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/device_drivers/mtp_folder_browser.py:91 +#: /home/kovid/work/calibre/src/calibre/gui2/device_drivers/mtp_config.py:495 +#, python-format +msgid "" +"You are trying to send books into the %s folder. This folder is " +"currently ignored by calibre when scanning the device. You have tell calibre " +"you want this folder scanned in order to be able to send books to it. Click " +"the configure button below to send books to it." +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/device_drivers/mtp_config.py:504 +#: /home/kovid/work/calibre/src/calibre/gui2/lrf_renderer/main_ui.py:137 +msgid "Configure" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/device_drivers/mtp_config.py:508 +#, python-format +msgid "Cannot send to %s" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/device_drivers/mtp_folder_browser.py:92 msgid "Choose folder on device" msgstr "" +#: /home/kovid/work/calibre/src/calibre/gui2/device_drivers/mtp_folder_browser.py:105 +msgid "Scanned folders:" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/device_drivers/mtp_folder_browser.py:106 +msgid "" +"You can select which top level folders calibre will scan when searching this " +"device for books." +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/device_drivers/mtp_folder_browser.py:131 +msgid "Select &All" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/device_drivers/mtp_folder_browser.py:133 +msgid "Select &None" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/device_drivers/mtp_folder_browser.py:136 +msgid "Choose folders to scan" +msgstr "" + #: /home/kovid/work/calibre/src/calibre/gui2/dialogs/add_empty_book.py:19 msgid "How many empty books?" msgstr "" @@ -9195,6 +9293,14 @@ msgstr "" msgid "Reset author to Unknown" msgstr "" +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/add_empty_book.py:48 +msgid "Set the series of the new books to:" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/add_empty_book.py:60 +msgid "Reset series" +msgstr "" + #: /home/kovid/work/calibre/src/calibre/gui2/dialogs/add_from_isbn.py:72 msgid "Some invalid ISBNs" msgstr "" @@ -9254,7 +9360,7 @@ msgid "No help available for this output format." msgstr "" #: /home/kovid/work/calibre/src/calibre/gui2/dialogs/catalog_ui.py:92 -#: /home/kovid/work/calibre/src/calibre/gui2/tools.py:333 +#: /home/kovid/work/calibre/src/calibre/gui2/tools.py:348 msgid "Generate catalog" msgstr "" @@ -9602,7 +9708,7 @@ msgstr "" #: /home/kovid/work/calibre/src/calibre/gui2/dialogs/comments_dialog.py:24 #: /home/kovid/work/calibre/src/calibre/gui2/dialogs/template_dialog.py:247 -#: /usr/src/qt-everywhere-opensource-src-4.8.2/src/gui/widgets/qdialogbuttonbox.cpp:658 +#: /usr/src/qt-everywhere-opensource-src-4.8.4/src/gui/widgets/qdialogbuttonbox.cpp:658 msgid "&OK" msgstr "" @@ -9656,8 +9762,8 @@ msgid "Location" msgstr "" #: /home/kovid/work/calibre/src/calibre/gui2/dialogs/delete_matching_from_device.py:77 -#: /home/kovid/work/calibre/src/calibre/gui2/library/models.py:62 -#: /home/kovid/work/calibre/src/calibre/gui2/library/models.py:1076 +#: /home/kovid/work/calibre/src/calibre/gui2/library/models.py:87 +#: /home/kovid/work/calibre/src/calibre/gui2/library/models.py:1110 #: /home/kovid/work/calibre/src/calibre/gui2/preferences/create_custom_column.py:35 #: /home/kovid/work/calibre/src/calibre/gui2/preferences/create_custom_column.py:76 #: /home/kovid/work/calibre/src/calibre/library/field_metadata.py:365 @@ -9676,13 +9782,13 @@ msgstr "" #: /home/kovid/work/calibre/src/calibre/gui2/dialogs/device_category_editor.py:86 #: /home/kovid/work/calibre/src/calibre/gui2/dialogs/tag_list_editor.py:209 -#: /home/kovid/work/calibre/src/calibre/gui2/tag_browser/model.py:929 +#: /home/kovid/work/calibre/src/calibre/gui2/tag_browser/model.py:957 msgid "Item is blank" msgstr "" #: /home/kovid/work/calibre/src/calibre/gui2/dialogs/device_category_editor.py:87 #: /home/kovid/work/calibre/src/calibre/gui2/dialogs/tag_list_editor.py:210 -#: /home/kovid/work/calibre/src/calibre/gui2/tag_browser/model.py:930 +#: /home/kovid/work/calibre/src/calibre/gui2/tag_browser/model.py:958 msgid "An item cannot be set to nothing. Delete it instead." msgstr "" @@ -9762,7 +9868,7 @@ msgstr "" #: /home/kovid/work/calibre/src/calibre/gui2/dialogs/edit_authors_dialog.py:122 #: /home/kovid/work/calibre/src/calibre/gui2/lrf_renderer/main.py:160 -#: /home/kovid/work/calibre/src/calibre/gui2/metadata/single_download.py:523 +#: /home/kovid/work/calibre/src/calibre/gui2/metadata/single_download.py:527 #: /home/kovid/work/calibre/src/calibre/gui2/viewer/main.py:721 msgid "No matches found" msgstr "" @@ -9810,12 +9916,12 @@ msgid "Copy to author" msgstr "" #: /home/kovid/work/calibre/src/calibre/gui2/dialogs/edit_authors_dialog.py:313 -#: /home/kovid/work/calibre/src/calibre/gui2/tag_browser/model.py:979 +#: /home/kovid/work/calibre/src/calibre/gui2/tag_browser/model.py:1007 msgid "Invalid author name" msgstr "" #: /home/kovid/work/calibre/src/calibre/gui2/dialogs/edit_authors_dialog.py:314 -#: /home/kovid/work/calibre/src/calibre/gui2/tag_browser/model.py:980 +#: /home/kovid/work/calibre/src/calibre/gui2/tag_browser/model.py:1008 msgid "Author names cannot contain & characters." msgstr "" @@ -9941,8 +10047,8 @@ msgstr "" #: /home/kovid/work/calibre/src/calibre/gui2/dialogs/message_box.py:196 #: /home/kovid/work/calibre/src/calibre/gui2/dialogs/message_box.py:251 -#: /home/kovid/work/calibre/src/calibre/gui2/metadata/single_download.py:946 -#: /home/kovid/work/calibre/src/calibre/gui2/metadata/single_download.py:1055 +#: /home/kovid/work/calibre/src/calibre/gui2/metadata/single_download.py:950 +#: /home/kovid/work/calibre/src/calibre/gui2/metadata/single_download.py:1059 #: /home/kovid/work/calibre/src/calibre/gui2/proceed.py:48 msgid "View log" msgstr "" @@ -9961,7 +10067,7 @@ msgid "Standard metadata" msgstr "" #: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk.py:63 -#: /home/kovid/work/calibre/src/calibre/gui2/metadata/single.py:939 +#: /home/kovid/work/calibre/src/calibre/gui2/metadata/single.py:945 msgid "Custom metadata" msgstr "" @@ -10147,7 +10253,7 @@ msgstr "" #: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:562 #: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:563 -#: /home/kovid/work/calibre/src/calibre/gui2/metadata/single.py:188 +#: /home/kovid/work/calibre/src/calibre/gui2/metadata/single.py:193 msgid "Open Tag Editor" msgstr "" @@ -10200,7 +10306,7 @@ msgid "&Force numbers to start with:" msgstr "" #: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:582 -#: /home/kovid/work/calibre/src/calibre/gui2/metadata/basic_widgets.py:1395 +#: /home/kovid/work/calibre/src/calibre/gui2/metadata/basic_widgets.py:1398 msgid "&Date:" msgstr "" @@ -10222,7 +10328,7 @@ msgid "Clear published date" msgstr "" #: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:591 -#: /home/kovid/work/calibre/src/calibre/gui2/metadata/basic_widgets.py:1164 +#: /home/kovid/work/calibre/src/calibre/gui2/metadata/basic_widgets.py:1167 msgid "&Languages:" msgstr "" @@ -10292,13 +10398,13 @@ msgid "Set from &ebook file(s)" msgstr "" #: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:613 -#: /home/kovid/work/calibre/src/calibre/gui2/metadata/single.py:575 -#: /home/kovid/work/calibre/src/calibre/gui2/metadata/single.py:741 +#: /home/kovid/work/calibre/src/calibre/gui2/metadata/single.py:580 +#: /home/kovid/work/calibre/src/calibre/gui2/metadata/single.py:747 msgid "&Basic metadata" msgstr "" #: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:614 -#: /home/kovid/work/calibre/src/calibre/gui2/metadata/single.py:582 +#: /home/kovid/work/calibre/src/calibre/gui2/metadata/single.py:587 msgid "&Custom metadata" msgstr "" @@ -10806,8 +10912,8 @@ msgstr "" #: /home/kovid/work/calibre/src/calibre/gui2/dialogs/quickview.py:87 #: /home/kovid/work/calibre/src/calibre/gui2/dialogs/tag_categories.py:60 #: /home/kovid/work/calibre/src/calibre/gui2/preferences/metadata_sources.py:156 -#: /home/kovid/work/calibre/src/calibre/library/catalogs/epub_mobi_builder.py:299 -#: /home/kovid/work/calibre/src/calibre/library/catalogs/epub_mobi_builder.py:1379 +#: /home/kovid/work/calibre/src/calibre/library/catalogs/epub_mobi_builder.py:303 +#: /home/kovid/work/calibre/src/calibre/library/catalogs/epub_mobi_builder.py:1419 #: /home/kovid/work/calibre/src/calibre/library/field_metadata.py:113 msgid "Authors" msgstr "" @@ -10846,7 +10952,7 @@ msgstr "" #: /home/kovid/work/calibre/src/calibre/gui2/store/config/chooser/chooser_widget_ui.py:80 #: /home/kovid/work/calibre/src/calibre/gui2/store/stores/mobileread/store_dialog_ui.py:76 #: /home/kovid/work/calibre/src/calibre/gui2/widgets.py:652 -#: /home/kovid/work/calibre/src/calibre/library/server/browse.py:281 +#: /home/kovid/work/calibre/src/calibre/library/server/browse.py:284 msgid "Search" msgstr "" @@ -11184,7 +11290,7 @@ msgid "never delete" msgstr "" #: /home/kovid/work/calibre/src/calibre/gui2/dialogs/scheduler_ui.py:230 -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/user_profiles_ui.py:273 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/user_profiles_ui.py:279 msgid " days" msgstr "" @@ -11322,7 +11428,7 @@ msgid "&Author:" msgstr "" #: /home/kovid/work/calibre/src/calibre/gui2/dialogs/search_ui.py:199 -#: /home/kovid/work/calibre/src/calibre/gui2/metadata/basic_widgets.py:1100 +#: /home/kovid/work/calibre/src/calibre/gui2/metadata/basic_widgets.py:1103 msgid "Ta&gs:" msgstr "" @@ -11695,7 +11801,7 @@ msgid "The template box cannot be empty" msgstr "" #: /home/kovid/work/calibre/src/calibre/gui2/dialogs/template_dialog_ui.py:110 -#: /home/kovid/work/calibre/src/calibre/gui2/preferences/coloring.py:264 +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/coloring.py:266 msgid "Set the color of the column:" msgstr "" @@ -11769,7 +11875,7 @@ msgstr "" #: /home/kovid/work/calibre/src/calibre/gui2/dialogs/user_profiles.py:169 #: /home/kovid/work/calibre/src/calibre/gui2/dialogs/user_profiles.py:180 -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/user_profiles_ui.py:265 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/user_profiles_ui.py:271 msgid "Switch to Advanced mode" msgstr "" @@ -11843,39 +11949,39 @@ msgid "" "Add/Update recipe button. Continue?" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/user_profiles_ui.py:257 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/user_profiles_ui.py:263 msgid "Add custom news source" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/user_profiles_ui.py:258 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/user_profiles_ui.py:264 msgid "Available user recipes" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/user_profiles_ui.py:259 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/user_profiles_ui.py:265 msgid "Add/Update &recipe" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/user_profiles_ui.py:260 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/user_profiles_ui.py:266 msgid "&Remove recipe" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/user_profiles_ui.py:261 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/user_profiles_ui.py:267 msgid "&Share recipe" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/user_profiles_ui.py:262 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/user_profiles_ui.py:268 msgid "S&how recipe files" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/user_profiles_ui.py:263 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/user_profiles_ui.py:269 msgid "Customize &builtin recipe" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/user_profiles_ui.py:264 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/user_profiles_ui.py:270 msgid "&Load recipe from file" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/user_profiles_ui.py:266 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/user_profiles_ui.py:272 msgid "" "