diff --git a/.bzrignore b/.bzrignore index e2d1636ddd..005391bf46 100644 --- a/.bzrignore +++ b/.bzrignore @@ -12,6 +12,7 @@ resources/images.qrc resources/scripts.pickle resources/ebook-convert-complete.pickle resources/builtin_recipes.xml +resources/builtin_recipes.zip setup/installer/windows/calibre/build.log src/calibre/translations/.errors src/cssutils/.svn/ @@ -29,3 +30,5 @@ nbproject/ .project .pydevproject .settings/ +*.DS_Store +calibre_plugins/ \ No newline at end of file diff --git a/Changelog.old.yaml b/Changelog.old.yaml index 0d556d99e0..0bdd7ba746 100644 --- a/Changelog.old.yaml +++ b/Changelog.old.yaml @@ -478,7 +478,7 @@ type: major description : > "You can now save your frequently used searches and access them with a single click. For details - see http://calibre-ebook.com/user_manual/gui.html#search-sort" + see http://manual.calibre-ebook.com/gui.html#search-sort" - title: "Add searching by date/published date" tickets: [5244] diff --git a/Changelog.yaml b/Changelog.yaml index b8b8f2b480..ee3d240115 100644 --- a/Changelog.yaml +++ b/Changelog.yaml @@ -19,16 +19,957 @@ # new recipes: # - title: -# - title: "Launch of a new website that catalogues DRM free books. http://drmfree.calibre-ebook.com" -# description: "A growing catalogue of DRM free books. Books that you actually own after buying instead of renting." -# type: major +- version: 0.8.3 + date: 2011-05-27 + + new features: + - title: "Allow the coloring of columns in the book list." + description: "You can either create a custom column with a fixed set of values and assign a color to each value, or you can use the calibre template language to color any column in arbitrarily powerful ways. For example, you can have the title appear in red if the book has a particular tag." + type: major + + - title: "Support for the Nook Simple Reader" + + - title: "Get Books, new stores: Virtualo, lulu.net" + + - title: "A store chooser dialog for Get Books (click the little preferences icon at the bottom of the Get Books screen)." + + - title: "Add a merge_lists, and, or, not template functions to the calibre template language" + + - title: "EPUB Output: Change any white-space:pre declarations in the CSS to pre-wrap to accomodate readers that cannot scroll horizontally." + tickets: [786722] + + - title: "Windows installer: Remember and use previous installation folder when upgrading. Note that this will work for future upgrades, after this one." + + bug fixes: + - title: "MOBI Output: Fix hidden tags with id attributes also hiding their trailing text" + tickets: [788570] + + - title: "Fix switching from one news source to another via a search not saving changes to the scheduling of the first source" + tickets: [774849] + + - title: "Dont allow user to use non email usernames when setting up Hotmail or Gmail accounts" + + - title: "Amazon metadata download: Use separate identifiers for country specific downloads so that the links to Amazon in the Book details panel work when downloading metadata from country specific amazon websites." + tickets: [786146] + + - title: "Nicer error message when user attempts to set title/author via Edit metadata dialog and one of the files is open in another program." + + - title: "Fix {id} not working in send to device templates" + + - title: "Windows: If creating a bytestring temp dir fails, create a unicode one and hope the rest of calibre can handle it" + + - title: "Get Books: Fix some results from Amazon missing." + tickets: [785962] + + improved recipes: + - Kathermini + - Faz.net + - The Washington Post + - El Mundo + - Marca + - The Nation + + new recipes: + - title: Various German news sources + author: schuster + + - title: "George R. R. Martin's Blog" + author: Darko Miletic + + - title: "Focus (DE) and National Geographic" + auhtor: Anonymous + + + +- version: 0.8.2 + date: 2011-05-20 + + new features: + - title: "Various new ebook sources added to Get Books: Google Books, O'Reilly, archive.org, some Polish ebooks stores, etc." + + - title: "Amazon metadata download: Allow user to configure Amazon plugin to use any of the US, UK, German, French and Italian Amazon websites" + + - title: "When deleting large numbers of books, give the user the option to skip the Recycle Bin, since sending lots of files to the recycle bin can be very slow." + tickets: [784987] + + - title: "OS X: The unified title+toolbar was disabled as it had various bugs. If you really want it you can turn it on again via Preferences->Tweaks, but be aware that you will see problems like the calibre windowd being too wide, weird animations when a device is detected, etc." + + - title: "Add a tweak that controls what words are treated as suffixes when generating an author sort string from an author name." + + - title: "Get Books: Store last few searches in history" + + bug fixes: + - title: "Fix a crash when a device is connected/disconnected while a modal dialog opened from the toolbar is visible" + tickets: [780484] + + - title: "Fix incorrect results from ebooks.com when searching via Get Books" + + - title: "Metadata plugboards: Add prioritization scheme to allow for using different settings for different locations" + tickets: [783229] + + - title: "Fix manage authors dialog too wide" + tickets: [783065] + + - title: "Fix multiple bracket types in author names not handled correctly when generating author sort string" + tickets: [782551] + + - title: "MOBI Input: Don't error out when detecting TOC structure if one of the elements has an invalid margin unit" + + - title: "More fixes for japanese language calibre on windows" + tickets: [782408] + + - title: "Linux binaries: Always use either Cleanlook or Plastique styles for the GUI if no style can be loaded from the host computer" + + improved recipes: + - Newsweek + - Economist + - Dvhn + - United Daily + - Dagens Nyheter + - GoComics + - faz.net + - golem.de + + new recipes: + - title: National Geographic + author: gagsays + + - title: Various German news sources + author: schuster + + - title: Dilema Veche + author: Silviu Cotoara + + - title: "Glamour, Good to Know, Good Housekeeping and Men's Health" + author: Anonymous + + - title: "Financial Sense and iProfessional" + author: Darko Miletic + + +- version: 0.8.1 + date: 2011-05-13 + + new features: + - title: "Add Amazon DE, Beam EBooks, Beam DE, Weightless Books, Wizards Tower Books to the list of ebook stores searched by Get Books" + + - title: "TXT output: All new Textile output with much greater preservation of formatting from the input document" + + - title: "Migrate metadata plugin for Douban Books to the 0.8 API" + + - title: "Driver for Dell Streak on windows" + + - title: "Add menu items to Get Books action to search by title and author of current book" + + - title: "Add title_sort as available field to CSV/XML catalogs" + + - title: "Add a context menu to the manage authors dialog" + + - title: "Add a button to paste isbn into the identifiers field in the edit metadata dialog automatically" + + bug fixes: + - title: "Amazon metadata download plugin: Fix links being stripped from comments. Also fix ratings/isbn not being parsed from kindle edition pages." + tickets: [782012] + + - title: "Fix one source of segfaults on shutdown in the linux binary builds." + + - title: "Allow the use of condensed/expanded fonts as interface fonts" + + - title: "EPUB Input: Ignore missing cover file when converting, instead of erroring out." + tickets: [781848] + + - title: "Fix custom identifier being erased by metadata download" + tickets: [781759] + + - title: "Fix regression that broke various things when using Japanese language calibre on windows" + tickets: [780804] + + - title: "RTF Input: Handle null color codes correctly" + tickets: [780728] + + - title: "ODT Input: Handle inline special styles defined on tags." + tickets: [780250] + + - title: "Fix error when pressing next previous button with an empty search in the Plugins preferences" + tickets: [781135] + + - title: "Ignore 'Unknown' author when downloading metadata." + tickets: [779348] + + - title: "Fix timezone bug when setting dates in the edit metadata dialog" + tickets: [779497] + + - title: "Fix ebook-convert not recognizing output paths starting with .." + tickets: [779322] + + improved recipes: + - "Strategy+Business" + - Readers Digest + - Ming Pao + - Telepolis + - Fronda + - Rzeczpospolita + + new recipes: + - title: "Various Taiwanese news sources" + author: Eddie Lau + + - title: Replica Vedetelor, Ziua Veche + author: Silviu Cotoara + + - title: Welt der Physik + author: schuster + + - title: Korea Herald + author: Seongkyoun Yoo + + +- version: 0.8.0 + date: 2010-05-06 + + new features: + - title: "Go to http://calibre-ebook.com/new-in/eight to see what's new in 0.8.0" + type: major + +- version: 0.7.59 + date: 2011-04-30 + + bug fixes: + - title: "Fixes a bug in 0.7.58 that caused too small fonts when converting to MOBI for the Kindle. Apologies." + + - title: "Apple driver: Handle invalid EPUBs that do not contain an OPF file" + + new recipes: + - title: The Big Picture and Auto industry news + author: welovelucy + + - title: Gazeta Prawna + author: Vroo + + - title: Various Czech news sources + author: Tomas Latal + + - title: Diario de Ibiza + author: Joan Tur + +- version: 0.7.58 + date: 2011-04-29 + + new features: + - title: "Support for converting and reading metadata from Plucker format PDB files" + type: major + + - title: "The metadata that is displayed in the book details panel on the right is now completely configurable via Preferences->Look & Feel" + + - title: "Add a column that shows the date when the metadata of a book record was last modified in calibre. To see the column, right click on the column headers in calibre and select Show column->Modified. Note that the dates may be incorrect for books added with older versions of calibre." + + - title: "Add command line option to shutdown running calibre" + + - title: "CHM Input: Store extracted files in the input/ sub dir for easy debugging when --debug-pipeline is specified" + + - title: "Add a popup menu to the 'Create saved search button' to allow easy deleting of saved searches" + + bug fixes: + - title: "Fix regression that broke converting to LIT in 0.7.57" + tickets: [769334] + + - title: "Conversion pipeline: Remove encoding declarations from input HTML documents to guarantee that there is only a single encoding declaration in the output HTML." + tickets: [773337] + + - title: "Correctly parenthesize searches that are used to make search restrictions" + + - title: "Fix ratings in save to disk templates not being divided by 2" + + - title: "TXT to EPUB: Underlined words (following quotes?) fail to become italics" + tickets: [772267] + + - title: "Fix template function source code unavailable when not running calibre from source" + + - title: "Fix adding html books from the top of a deep folder hierarchy very slow" + + - title: "Only set language in MOBI metadata if it is not null" + + - title: "Fix 'count-of' searches (e.g., tags:#>3)." + tickets: [771175] + + - title: "Fix regression that broke connection to iTunes in some cases" + tickets: [771164] + + - title: "Fix buggy regex that made converting PDFs with the string ****************** very slow" + tickets: [770534] + + - title: "Fix Ctrl+L shortcut to lookup word not working in ebook viewer" + tickets: [769492] + + - title: "Fix regression that broke searching on boolean columns" + + improved recipes: + - HBR Blogs + - The Marker + - Financial Times + - Clarin + - Honolulu Star Advertiser + + new recipes: + - title: Novi Standard + author: Darko Miletic + + - title: Autobild.ro and Social Diva + author: Silviu Cotoara + + - title: Novinky + author: Tomas Latal + + - title: "De Volksrant (subscriber version)" + author: Selcal + + +- version: 0.7.57 + date: 2011-04-22 + + new features: + - title: "Launch worker processes on demand instead of keeping a pool of them in memory. Reduces memory footprint." + + - title: "Use the visual formatting of the Table of Contents to try to automatically create a multi-level TOC when converting/viewing MOBI files." + tickets: [763681] + + - title: "Add a new function booksize() to the template language to get the value of the size column in calibre." + + - title: "Add support for using metadata plugboards with the content server (only with the epub format)" + + - title: "Change default algorithm for automatically computing author sort to be more intelligent and handle the case when the author name has a comma in it" + + - title: "Show cover size in the tooltips of the book details panel and book details popup window" + + bug fixes: + - title: "Dragging and dropping a cover onto the book details panel did not change the cover size" + tickets: [768332] + + - title: "Fix non-escaped '|' when searching for commas in authors using REGEXP_MATCH" + + - title: "Fix ratings in templates being multiplied by 2" + + - title: "Fix adding a comma to custom series values when using completion." + tickets: [763788] + + - title: "CHM Input: Another workaround for a Microsoft mess." + tickets: [763336] + + - title: "Fix job count in the spinner not always being updated when a job completes" + + - title: "Changing case only of a title does not update title sort" + tickets: [768904] + + improved recipes: + - ecuisine.ro, egirl.ro and tabu.ro + - Daily Telegraph + - Handelsblatt + - Il Sole 24 Ore + - Newsweek + - Arcamax + + new recipes: + - title: BabyOnline.ro + author: Silviu Cotoara + + - title: "The Journal.ie" + author: Phil Burns + + - title: "Der Spiegel" + author: Nikolas Mangold + +- version: 0.7.56 + date: 2011-04-17 + + new features: + - title: "This is primarily a bug fix release that fixes a bug in 0.7.55 that caused calibre to rescan the files on the device every time the device is connected. If you updated to 0.7.55 it is highly recommended you update to 0.7.56" + + - title: "Device driver for Coby Kyros" + + - title: "Remove the quick access to search options from next to the search bar, as we now have a separate search highlights toggle button" + + - title: "MOBI Output: Ensure that MOBI files always have 8KB worth of null bytes at the end of record 0. This appears to be necessary for Amazon to be able to add DRM to calibre generated MOBI files sent to their publishing service." + + - title: "Add a tool to inspect MOBI files. To use: calibre-debug -m file.mobi" + + bug fixes: + - title: "Fixed regression taht caused calibre to rescan files on the device on every reconnect" + + - title: "Fix donate button causing the toolbar to be too large on OS X" + + - title: "MOBI Input: Fix detection of Table of Contents for MOBI files that have a page break between the location designated as the Table of Contents and the actual table of contents." + tickets: [763504] + + - title: "Comic Input: Fix handling of some CBZ files that have wrongly encoded non ASCII filenames on windows." + tickets: [763280] + + - title: "PML Input: Fix multi-line chapter title causing a spurious page break" + tickets: [763238] + + - title: "EPUB Input: Speed up processing of files with very large manifest/spines" + + - title: "Fix regression that broke cover:False searches in 0.7.55" + + improved recipes: + - Suedduetsche Zeitung + - Irish Times + - Big Oven + - NSPM + + +- version: 0.7.55 + date: 2011-04-15 + + new features: + - title: "Add a menu bar. Useful if you use a lot of plugins and are running out of space in your toolbars. By default the menu bar is hidden (except on OS X). You can add actions to it via Preferences->Toolbars. As soon as you add actions, it will become visible." + + - title: "OS X: Make the main calibre window look a little more 'native' on OS X" + + - title: "Show recently viewed books in the View button's drop down menu" + + - title: "Add a button next to the search bar to toggle easily between highlight and restrict search modes" + + - title: "Allow the use of arbitrary searches as search restrictions, rather than just saved searches. Do this by using the special entry '*Current Search' in the Search Restriction dropdown." + + - title: "The Connect/share icon now changes color to indicate that the content server is running" + tickets: [755444] + + - title: "Device drivers for Viewpad 7, Motorola Xoom and Asus Eee Note" + + - title: "Add tags like composite custom column." + tickets: [759663] + + - title: "Add a new date format code 'iso'. Permits formatting dates to see the complete time (via Preferences->Tweaks)" + + - title: "Allow the use of data from the size column in the template language" + tickets: [759645] + + - title: "Support reading/writing covers to txtz/htmlz files" + + - title: "Speedup for large library sorting when using composite custom columns" + + - title: "Move the boolean columns are tristate tweak to Preferences->Behavior" + + bug fixes: + - title: "Fix a regression in 0.7.54 that broke reading covers/metadata from cbz files." + tickets: [756892] + + - title: "Fix tweak names and help not translatable" + tickets: [756736] + + - title: "When the size of a book is less that 0.1MB but not zero, display the size as <0.1 instead of 0.0." + tickets: [755768] + + - title: "HTMLZ input: Fix handling of HTML files encoded in an encoding other than UTF-8" + + - title: "EPUB Input: Fix EPUB files with empty Adobe PAGE templates causing conversion to abort." + tickets: [760390] + + - title: "Fix CHM input plugin not closing opened input file" + tickets: [760589] + + - title: "MOBI Output: Make super/subscripts use a slightly smaller font when rendered on a Kindle. Also allow the use of vertical-align:top/bottom in the CSS to specify a super/subscript." + tickets: [758667] + + - title: "LRF Input: Detect and workaround LRF files that have deeply nested spans, instead of crashing." + tickets: [759680] + + - title: "MOBI Output: Fix bug that would cause conversion to unneccessarily abort when malformed hyperlinks are present in the input document." + tickets: [759313] + + - title: "Make true and false searches work correctly for numeric fields." + + - title: "MOBI Output: The Ignore margins setting no longer ignores blockquotes, only margins set via CSS on other elements." + tickets: [758675] + + - title: "Fix regression that caused clicking auto send to also change the email address in Preferences->Email" + + improved recipes: + - Wall Street Journal + - Weblogs SL + - Tabu.ro + - Vecernje Novosti + + new recipes: + - title: Hallo Assen and Dvhn + author: Reijendert + + +- version: 0.7.54 + date: 2011-04-08 + + new features: + - title: "New output format, HTMLZ which is a single HTML file with its associated images/stylesheets in a zipped up file" + description: "Useful when you want to convert your ebook into a single HTML file for easy editing. Note that this output plugin is still new and needs testing" + + - title: "When dealing with ZIP/RAR archives, use the file header rather than the file extension to detrmine the file type, when possible. This fixes the common case of CBZ files being actually cbr files and vice versa" + + - title: "Support for the Motorola Atrix" + + - title: "Allow the icons in the toolbar to be turned off completely via Preferences->Look & Feel" + + - title: "When downloading metadata use the gzip transfer encoding when possible for a speedup." + tickets: [749304] + + bug fixes: + - title: "Conversion pipeline: Workaround for bug in lxml that causes a massive mem leak on windows and OS X when the input document contains non ASCII CSS selectors." + tickets: [754555] + + - title: "Conversion pipeline: Handle inline + - - - -
- -
-
-
- - -
+ + + +
+ +
+
+
+ + +
- - - -
-
- -

- -

-
- - -
- - - - - - - - + + + +
+
+ +

+ +

+
+ + +
+ + +
+ + + + + - -
-
- - -
  • - - - , # - - - -
      - - - -
    -
    - - - - - - - - - -
  • - - -
    -
    -
  • - - -
    - - - - - - -
    -
    + +
    + + + +
  • + + + , # + + + +
      + + + +
    +
    + + + + + + + + + +
  • + + +
    +
    +
  • + + +
    + + + + + + +
    +
    - + @@ -164,15 +165,15 @@ - - - - - + + + + + - - - + + + @@ -181,79 +182,79 @@ TOC_ - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + - + - -
    -
    - - - - - - - -
    - -
    -
    - - + +
    +
    + + + + + + + +
    + +
    +
    + + paragraph - - - - + + + + - - - - - - - - - - - - - - - -
    -
    +
    + + + + + + + + + + + + + + +
    +
    @@ -261,123 +262,140 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

    Annotation

    - -
    - - -
    - - - - - - -
    -
    - - -
    - -
    -
    - - -
    - - - - - - -
    -
    - - -
    -
    -
    - - - - -     -
    -
    - -     -
    -
    -
    -
    - - -
    - - - - - - -
    -
    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +

    Annotation

    + +
    + + + + +
    +
    + + + + + + + + + + + + + +
    + + + + + + +
    +
    + + +
    + +
    +
    + + +
    + + + + + + +
    +
    + + +
    +
    +
    + + + + +     +
    +
    + +     +
    +
    +
    +
    + + +
    + + + + + + +
    +
    - - - -
    -
    - - - - - - - -
    -
    - - -
    - - - - - - - - - - -
    -
    + + + +
    +
    + + + + + + + +
    +
    + + +
    + + + + + + + + + + +
    +
    diff --git a/session.vim b/session.vim index f2adf71de9..fa14a92fba 100644 --- a/session.vim +++ b/session.vim @@ -18,6 +18,6 @@ def recipe_title_callback(raw): return eval(raw.decode('utf-8')) vipy.session.add_content_browser('.r', ',r', 'Recipe', - vipy.session.glob_based_iterator(os.path.join(project_dir, 'resources', 'recipes', '*.recipe')), + vipy.session.glob_based_iterator(os.path.join(project_dir, 'recipes', '*.recipe')), vipy.session.regexp_based_matcher(r'title\s*=\s*(?P.+)', 'title', recipe_title_callback)) EOFPY diff --git a/setup.py b/setup.py index d8bd0267ee..1424d83137 100644 --- a/setup.py +++ b/setup.py @@ -15,9 +15,9 @@ from setup import prints, get_warnings def check_version_info(): vi = sys.version_info - if vi[0] == 2 and vi[1] > 5: + if vi[0] == 2 and vi[1] > 6: return None - return 'calibre requires python >= 2.6' + return 'calibre requires python >= 2.7 and < 3' def option_parser(): parser = optparse.OptionParser() diff --git a/setup/__init__.py b/setup/__init__.py index 9e62fb377d..4a8d9870be 100644 --- a/setup/__init__.py +++ b/setup/__init__.py @@ -12,7 +12,9 @@ is64bit = platform.architecture()[0] == '64bit' iswindows = re.search('win(32|64)', sys.platform) isosx = 'darwin' in sys.platform isfreebsd = 'freebsd' in sys.platform -islinux = not isosx and not iswindows and not isfreebsd +isnetbsd = 'netbsd' in sys.platform +isbsd = isnetbsd or isfreebsd +islinux = not isosx and not iswindows and not isbsd SRC = os.path.abspath('src') sys.path.insert(0, SRC) sys.resources_location = os.path.join(os.path.dirname(SRC), 'resources') @@ -24,8 +26,10 @@ def initialize_constants(): global __version__, __appname__, modules, functions, basenames, scripts src = open('src/calibre/constants.py', 'rb').read() - __version__ = re.search(r'__version__\s+=\s+[\'"]([^\'"]+)[\'"]', src).group(1) - __appname__ = re.search(r'__appname__\s+=\s+[\'"]([^\'"]+)[\'"]', src).group(1) + nv = re.search(r'numeric_version\s+=\s+\((\d+), (\d+), (\d+)\)', src) + __version__ = '%s.%s.%s'%(nv.group(1), nv.group(2), nv.group(3)) + __appname__ = re.search(r'__appname__\s+=\s+(u{0,1})[\'"]([^\'"]+)[\'"]', + src).group(2) epsrc = re.compile(r'entry_points = (\{.*?\})', re.DOTALL).\ search(open('src/calibre/linux.py', 'rb').read()).group(1) entry_points = eval(epsrc, {'__appname__': __appname__}) diff --git a/setup/check.py b/setup/check.py index 3e94b9dda1..f34e3323a3 100644 --- a/setup/check.py +++ b/setup/check.py @@ -78,7 +78,7 @@ class Check(Command): 'prs500/driver.py' not in y: yield y, mtime - for x in os.walk(self.j(self.d(self.SRC), 'resources', 'recipes')): + for x in os.walk(self.j(self.d(self.SRC), 'recipes')): for f in x[-1]: f = self.j(x[0], f) mtime = os.stat(f).st_mtime diff --git a/setup/commands.py b/setup/commands.py index 7e22ff14f3..febc684c08 100644 --- a/setup/commands.py +++ b/setup/commands.py @@ -11,7 +11,7 @@ __all__ = [ 'build', 'build_pdf2xml', 'server', 'gui', 'develop', 'install', - 'resources', + 'kakasi', 'resources', 'check', 'sdist', 'manual', 'tag_release', @@ -49,8 +49,9 @@ gui = GUI() from setup.check import Check check = Check() -from setup.resources import Resources +from setup.resources import Resources, Kakasi resources = Resources() +kakasi = Kakasi() from setup.publish import Manual, TagRelease, Stage1, Stage2, \ Stage3, Stage4, Publish diff --git a/setup/extensions.py b/setup/extensions.py index 6a9cce7625..678859432d 100644 --- a/setup/extensions.py +++ b/setup/extensions.py @@ -11,7 +11,7 @@ from distutils import sysconfig from PyQt4.pyqtconfig import QtGuiModuleMakefile -from setup import Command, islinux, isfreebsd, isosx, SRC, iswindows +from setup import Command, islinux, isfreebsd, isbsd, isosx, SRC, iswindows from setup.build_environment import fc_inc, fc_lib, chmlib_inc_dirs, \ fc_error, poppler_libs, poppler_lib_dirs, poppler_inc_dirs, podofo_inc, \ podofo_lib, podofo_error, poppler_error, pyqt, OSX_SDK, NMAKE, \ @@ -21,7 +21,7 @@ from setup.build_environment import fc_inc, fc_lib, chmlib_inc_dirs, \ jpg_lib_dirs, chmlib_lib_dirs, sqlite_inc_dirs, icu_inc_dirs, \ icu_lib_dirs MT -isunix = islinux or isosx or isfreebsd +isunix = islinux or isosx or isbsd make = 'make' if isunix else NMAKE @@ -68,6 +68,10 @@ if isosx: extensions = [ + Extension('speedup', + ['calibre/utils/speedup.c'], + ), + Extension('icu', ['calibre/utils/icu.c'], libraries=icu_libs, @@ -201,7 +205,7 @@ if islinux: ldflags.append('-lpython'+sysconfig.get_python_version()) -if isfreebsd: +if isbsd: cflags.append('-pthread') ldflags.append('-shared') cflags.append('-I'+sysconfig.get_python_inc()) diff --git a/setup/install.py b/setup/install.py index 381ce2dcef..7290dbd799 100644 --- a/setup/install.py +++ b/setup/install.py @@ -8,11 +8,11 @@ __docformat__ = 'restructuredtext en' import sys, os, textwrap, subprocess, shutil, tempfile, atexit, stat, shlex -from setup import Command, islinux, isfreebsd, basenames, modules, functions, \ +from setup import Command, islinux, isfreebsd, isbsd, basenames, modules, functions, \ __appname__, __version__ HEADER = '''\ -#!/usr/bin/env python +#!/usr/bin/env python2 """ This is the standard runscript for all of calibre's tools. @@ -116,7 +116,7 @@ class Develop(Command): def pre_sub_commands(self, opts): - if not (islinux or isfreebsd): + if not (islinux or isbsd): self.info('\nSetting up a source based development environment is only ' 'supported on linux. On other platforms, see the User Manual' ' for help with setting up a development environment.') @@ -156,7 +156,7 @@ class Develop(Command): self.warn('Failed to compile mount helper. Auto mounting of', ' devices will not work') - if not isfreebsd and os.geteuid() != 0: + if not isbsd and os.geteuid() != 0: return self.warn('Must be run as root to compile mount helper. Auto ' 'mounting of devices will not work.') src = os.path.join(self.SRC, 'calibre', 'devices', 'linux_mount_helper.c') @@ -168,7 +168,7 @@ class Develop(Command): ret = p.wait() if ret != 0: return warn() - if not isfreebsd: + if not isbsd: os.chown(dest, 0, 0) os.chmod(dest, stat.S_ISUID|stat.S_ISGID|stat.S_IRUSR|stat.S_IWUSR|\ stat.S_IXUSR|stat.S_IXGRP|stat.S_IXOTH) diff --git a/setup/installer/__init__.py b/setup/installer/__init__.py index c25334dbe4..79bb942cde 100644 --- a/setup/installer/__init__.py +++ b/setup/installer/__init__.py @@ -16,7 +16,7 @@ EXCLUDES = [] for x in [ 'src/calibre/plugins', 'src/calibre/manual', 'src/calibre/trac', '.bzr', '.build', '.svn', 'build', 'dist', 'imgsrc', '*.pyc', '*.pyo', '*.swp', - '*.swo']: + '*.swo', 'format_docs']: EXCLUDES.extend(['--exclude', x]) SAFE_EXCLUDES = ['"%s"'%x if '*' in x else x for x in EXCLUDES] @@ -138,7 +138,7 @@ class VMInstaller(Command): self.vm = self.VM if not self.vmware_started(): self.start_vmware() - subprocess.call(['chmod', '-R', '+r', 'resources/recipes']) + subprocess.call(['chmod', '-R', '+r', 'recipes']) self.start_vm() self.download_installer() if not self.dont_shutdown: diff --git a/setup/installer/linux/util.c b/setup/installer/linux/util.c index dfbaaef62c..b06d6083c9 100644 --- a/setup/installer/linux/util.c +++ b/setup/installer/linux/util.c @@ -30,11 +30,12 @@ int report_libc_error(const char *msg) { } int pyobject_to_int(PyObject *res) { - int ret; PyObject *tmp; - tmp = PyNumber_Int(res); - if (tmp == NULL) ret = (PyObject_IsTrue(res)) ? 1 : 0; - else ret = (int)PyInt_AS_LONG(tmp); - + int ret = 0; PyObject *tmp; + if (res != NULL) { + tmp = PyNumber_Int(res); + if (tmp == NULL) ret = (PyObject_IsTrue(res)) ? 1 : 0; + else ret = (int)PyInt_AS_LONG(tmp); + } return ret; } diff --git a/setup/installer/windows/freeze.py b/setup/installer/windows/freeze.py index e9e47816fd..7fb60968e7 100644 --- a/setup/installer/windows/freeze.py +++ b/setup/installer/windows/freeze.py @@ -6,14 +6,15 @@ __license__ = 'GPL v3' __copyright__ = '2009, Kovid Goyal <kovid@kovidgoyal.net>' __docformat__ = 'restructuredtext en' -import sys, os, shutil, glob, py_compile, subprocess, re +import sys, os, shutil, glob, py_compile, subprocess, re, zipfile, time from setup import Command, modules, functions, basenames, __version__, \ __appname__ from setup.build_environment import msvc, MT, RC from setup.installer.windows.wix import WixMixIn -QT_DIR = 'Q:\\Qt\\4.7.1' +OPENSSL_DIR = r'Q:\openssl' +QT_DIR = 'Q:\\Qt\\4.7.3' QT_DLLS = ['Core', 'Gui', 'Network', 'Svg', 'WebKit', 'Xml', 'XmlPatterns'] LIBUSB_DIR = 'C:\\libusb' LIBUNRAR = 'C:\\Program Files\\UnrarDLL\\unrar.dll' @@ -40,6 +41,13 @@ DESCRIPTIONS = { 'calibre-smtp' : 'Command line interface for sending books via email', } +def walk(dir): + ''' A nice interface to os.walk ''' + for record in os.walk(dir): + for f in record[-1]: + yield os.path.join(record[0], f) + + class Win32Freeze(Command, WixMixIn): description = 'Free windows calibre installation' @@ -63,12 +71,15 @@ class Win32Freeze(Command, WixMixIn): self.rc_template = self.j(self.d(self.a(__file__)), 'template.rc') self.py_ver = ''.join(map(str, sys.version_info[:2])) self.lib_dir = self.j(self.base, 'Lib') + self.pydlib = self.j(self.base, 'pydlib') + self.pylib = self.j(self.base, 'pylib.zip') self.initbase() self.build_launchers() self.freeze() self.embed_manifests() self.install_site_py() + self.archive_lib_dir() self.create_installer() def initbase(self): @@ -98,6 +109,8 @@ class Win32Freeze(Command, WixMixIn): self.dll_dir = self.j(self.base, 'DLLs') shutil.copytree(r'C:\Python%s\DLLs'%self.py_ver, self.dll_dir, ignore=shutil.ignore_patterns('msvc*.dll', 'Microsoft.*')) + for x in glob.glob(self.j(OPENSSL_DIR, 'bin', '*.dll')): + shutil.copy2(x, self.dll_dir) for x in QT_DLLS: x += '4.dll' if not x.startswith('phonon'): x = 'Qt'+x @@ -356,4 +369,108 @@ class Win32Freeze(Command, WixMixIn): dest, lib] self.run_builder(cmd) + def archive_lib_dir(self): + self.info('Putting all python code into a zip file for performance') + if os.path.exists(self.pydlib): + shutil.rmtree(self.pydlib) + os.makedirs(self.pydlib) + self.zf_timestamp = time.localtime(time.time())[:6] + self.zf_names = set() + with zipfile.ZipFile(self.pylib, 'w', zipfile.ZIP_STORED) as zf: + for x in os.listdir(self.lib_dir): + if x == 'site-packages': + continue + self.add_to_zipfile(zf, x, self.lib_dir) + + sp = self.j(self.lib_dir, 'site-packages') + handled = set(['site.pyo']) + for pth in ('PIL.pth', 'pywin32.pth'): + handled.add(pth) + shutil.copyfile(self.j(sp, pth), self.j(self.pydlib, pth)) + for d in self.get_pth_dirs(self.j(sp, pth)): + shutil.copytree(d, self.j(self.pydlib, self.b(d)), True) + handled.add(self.b(d)) + + handled.add('easy-install.pth') + for d in self.get_pth_dirs(self.j(sp, 'easy-install.pth')): + handled.add(self.b(d)) + zip_safe = self.is_zip_safe(d) + for x in os.listdir(d): + if x == 'EGG-INFO': + continue + if zip_safe: + self.add_to_zipfile(zf, x, d) + else: + absp = self.j(d, x) + dest = self.j(self.pydlib, x) + if os.path.isdir(absp): + shutil.copytree(absp, dest, True) + else: + shutil.copy2(absp, dest) + + for x in os.listdir(sp): + if x in handled or x.endswith('.egg-info'): + continue + absp = self.j(sp, x) + if os.path.isdir(absp): + if not os.listdir(absp): + continue + if self.is_zip_safe(absp): + self.add_to_zipfile(zf, x, sp) + else: + shutil.copytree(absp, self.j(self.pydlib, x), True) + else: + if x.endswith('.pyd'): + shutil.copy2(absp, self.j(self.pydlib, x)) + else: + self.add_to_zipfile(zf, x, sp) + + shutil.rmtree(self.lib_dir) + + def is_zip_safe(self, path): + for f in walk(path): + ext = os.path.splitext(f)[1].lower() + if ext in ('.pyd', '.dll', '.exe'): + return False + return True + + def get_pth_dirs(self, pth): + base = os.path.dirname(pth) + for line in open(pth).readlines(): + line = line.strip() + if not line or line.startswith('#') or line.startswith('import'): + continue + if line == 'win32\\lib': + continue + candidate = self.j(base, line) + if os.path.exists(candidate): + yield candidate + + def add_to_zipfile(self, zf, name, base, exclude=frozenset()): + abspath = self.j(base, name) + name = name.replace(os.sep, '/') + if name in self.zf_names: + raise ValueError('Already added %r to zipfile [%r]'%(name, abspath)) + zinfo = zipfile.ZipInfo(filename=name, date_time=self.zf_timestamp) + + if os.path.isdir(abspath): + if not os.listdir(abspath): + return + zinfo.external_attr = 0700 << 16 + zf.writestr(zinfo, '') + for x in os.listdir(abspath): + if x not in exclude: + self.add_to_zipfile(zf, name + os.sep + x, base) + else: + ext = os.path.splitext(name)[1].lower() + if ext in ('.pyd', '.dll', '.exe'): + raise ValueError('Cannot add %r to zipfile'%abspath) + zinfo.external_attr = 0600 << 16 + if ext in ('.py', '.pyc', '.pyo'): + with open(abspath, 'rb') as f: + zf.writestr(zinfo, f.read()) + + self.zf_names.add(name) + + diff --git a/setup/installer/windows/main.c b/setup/installer/windows/main.c index d76850504e..780be94330 100644 --- a/setup/installer/windows/main.c +++ b/setup/installer/windows/main.c @@ -23,6 +23,9 @@ wWinMain(HINSTANCE Inst, HINSTANCE PrevInst, ret = execute_python_entrypoint(BASENAME, MODULE, FUNCTION, stdout_redirect, stderr_redirect); + if (stdout != NULL) fclose(stdout); + if (stderr != NULL) fclose(stderr); + DeleteFile(stdout_redirect); DeleteFile(stderr_redirect); diff --git a/setup/installer/windows/notes.rst b/setup/installer/windows/notes.rst index 5dfd956ce2..11b5bccf79 100644 --- a/setup/installer/windows/notes.rst +++ b/setup/installer/windows/notes.rst @@ -53,12 +53,42 @@ SQLite Put sqlite3*.h from the sqlite windows amlgamation in ~/sw/include +OpenSSL +-------- + +First install ActiveState Perl if you dont already have perl in windows +Download and untar the openssl tarball, follow the instructions in INSTALL.W32 (use no-asm) +to install use prefix q:\openssl + +perl Configure VC-WIN32 no-asm enable-static-engine --prefix=Q:/openssl +ms\do_ms.bat +nmake -f ms\ntdll.mak +nmake -f ms\ntdll.mak test +nmake -f ms\ntdll.mak install + Qt -------- -Extract Qt sourcecode to C:\Qt\4.x.x. Run configure and make:: +Extract Qt sourcecode to C:\Qt\4.x.x. - configure -opensource -release -qt-zlib -qt-gif -qt-libmng -qt-libpng -qt-libtiff -qt-libjpeg -release -platform win32-msvc2008 -no-qt3support -webkit -xmlpatterns -no-phonon -no-style-plastique -no-style-cleanlooks -no-style-motif -no-style-cde -no-declarative -no-scripttools -no-audio-backend -no-multimedia -no-dbus -no-openvg -no-opengl -no-qt3support -confirm-license -nomake examples -nomake demos -nomake docs && nmake +Qt uses its own routine to locate and load "system libraries" including the openssl libraries needed for "Get Books". This means that we have to apply the following patch to have Qt load the openssl libraries bundled with calibre: + + +--- src/corelib/plugin/qsystemlibrary.cpp 2011-02-22 05:04:00.000000000 -0700 ++++ src/corelib/plugin/qsystemlibrary.cpp 2011-04-25 20:53:13.635247466 -0600 +@@ -110,7 +110,7 @@ HINSTANCE QSystemLibrary::load(const wch + + #if !defined(QT_BOOTSTRAPPED) + if (!onlySystemDirectory) +- searchOrder << QFileInfo(qAppFileName()).path(); ++ searchOrder << (QFileInfo(qAppFileName()).path().replace(QLatin1Char('/'), QLatin1Char('\\')) + QString::fromLatin1("\\DLLs\\")); + #endif + searchOrder << qSystemDirectory(); + + +Now, run configure and make:: + + configure -opensource -release -qt-zlib -qt-gif -qt-libmng -qt-libpng -qt-libtiff -qt-libjpeg -release -platform win32-msvc2008 -no-qt3support -webkit -xmlpatterns -no-phonon -no-style-plastique -no-style-cleanlooks -no-style-motif -no-style-cde -no-declarative -no-scripttools -no-audio-backend -no-multimedia -no-dbus -no-openvg -no-opengl -no-qt3support -confirm-license -nomake examples -nomake demos -nomake docs -openssl -I Q:\openssl\include -L Q:\openssl\lib && nmake SIP ----- diff --git a/setup/installer/windows/site.py b/setup/installer/windows/site.py index 0e770f3253..5610ff197e 100644 --- a/setup/installer/windows/site.py +++ b/setup/installer/windows/site.py @@ -96,7 +96,7 @@ def main(): abs__file__() - addsitedir(os.path.join(sys.app_dir, 'Lib', 'site-packages')) + addsitedir(os.path.join(sys.app_dir, 'pydlib')) add_calibre_vars() diff --git a/setup/installer/windows/util.c b/setup/installer/windows/util.c index fdec6d786f..329e3bf8c3 100644 --- a/setup/installer/windows/util.c +++ b/setup/installer/windows/util.c @@ -198,7 +198,7 @@ void initialize_interpreter(wchar_t *outr, wchar_t *errr, buf[strlen(buf)-1] = '\0'; _snprintf_s(python_home, MAX_PATH, _TRUNCATE, "%s", buf); - _snprintf_s(path, 3*MAX_PATH, _TRUNCATE, "%s\\DLLs;%s\\Lib;%s\\Lib\\site-packages", + _snprintf_s(path, 3*MAX_PATH, _TRUNCATE, "%s\\pylib.zip;%s\\pydlib;%s\\DLLs", buf, buf, buf); free(buf); diff --git a/setup/installer/windows/wix-template.xml b/setup/installer/windows/wix-template.xml index 37dd8b25a8..0a85b6fb81 100644 --- a/setup/installer/windows/wix-template.xml +++ b/setup/installer/windows/wix-template.xml @@ -11,12 +11,13 @@ SummaryCodepage='1252' /> <Media Id="1" Cabinet="{app}.cab" CompressionLevel="{compression}" EmbedCab="yes" /> - + <Upgrade Id="{upgrade_code}"> <UpgradeVersion Maximum="{version}" IncludeMaximum="yes" OnlyDetect="no" Language="1033" + MigrateFeatures="yes" Property="OLDPRODUCTFOUND"/> <UpgradeVersion Minimum="{version}" IncludeMinimum="no" @@ -26,6 +27,11 @@ </Upgrade> <CustomAction Id="PreventDowngrading" Error="Newer version already installed."/> + <Property Id="APPLICATIONFOLDER"> + <RegistrySearch Id='calibreInstDir' Type='raw' + Root='HKLM' Key="Software\{app}\Installer" Name="InstallPath" /> + </Property> + <Directory Id='TARGETDIR' Name='SourceDir'> <Merge Id="VCRedist" SourceFile="{crt_msm}" DiskId="1" Language="0"/> <Directory Id='ProgramFilesFolder' Name='PFiles'> @@ -43,6 +49,9 @@ <Environment Id='UpdatePath' Name='PATH' Action='set' System='yes' Part='last' Value='[APPLICATIONFOLDER]' /> <RegistryValue Root="HKCU" Key="Software\Microsoft\{app}" Name="system_path_updated" Type="integer" Value="1" KeyPath="yes"/> </Component> + <Component Id="RememberInstallDir" Guid="*"> + <RegistryValue Root="HKLM" Key="Software\{app}\Installer" Name="InstallPath" Type="string" Value="[APPLICATIONFOLDER]" KeyPath="yes"/> + </Component> </DirectoryRef> <DirectoryRef Id="ApplicationProgramsFolder"> @@ -61,7 +70,7 @@ WorkingDirectory="APPLICATIONROOTDIRECTORY" /> <util:InternetShortcut Id="OnlineDocumentationShortcut" Name="User Manual" Type="url" - Target="http://calibre-ebook.com/user_manual"/> + Target="http://manual.calibre-ebook.com"/> <util:InternetShortcut Id="GetInvolvedS" Name="Get Involved" Type="url" Target="http://calibre-ebook.com/get-involved"/> @@ -87,7 +96,8 @@ ConfigurableDirectory="APPLICATIONFOLDER"> <Feature Id="MainApplication" Title="Program Files" Level="1" - Description="All the files need to run {app}" Absent="disallow"> + Description="All the files needed to run {app}" Absent="disallow"> + <ComponentRef Id="RememberInstallDir"/> </Feature> <Feature Id="VCRedist" Title="Visual C++ 8.0 Runtime" AllowAdvertise="no" Display="hidden" Level="1"> @@ -115,7 +125,7 @@ <Property Id="ARPPRODUCTICON" Value="main_icon" /> <Condition - Message="This application is only supported on Windows XP SP2, or higher."> + Message="This application is only supported on Windows XP SP3, or higher."> <![CDATA[Installed OR (VersionNT >= 501)]]> </Condition> <InstallExecuteSequence> diff --git a/setup/publish.py b/setup/publish.py index 6aa2aa0e06..5c6b5563e9 100644 --- a/setup/publish.py +++ b/setup/publish.py @@ -45,7 +45,6 @@ class Stage3(Command): sub_commands = ['upload_user_manual', 'upload_demo', 'sdist', 'upload_to_sourceforge', 'upload_to_google_code', 'tag_release', 'upload_to_server', - 'upload_to_mobileread', ] class Stage4(Command): diff --git a/setup/pygettext.py b/setup/pygettext.py index bc171396f4..322758871d 100644 --- a/setup/pygettext.py +++ b/setup/pygettext.py @@ -170,8 +170,8 @@ from setup import __appname__, __version__ as version # there. pot_header = '''\ # Translation template file.. -# Copyright (C) 2007 Kovid Goyal -# Kovid Goyal <kovid@kovidgoyal.net>, 2007. +# Copyright (C) %(year)s Kovid Goyal +# Kovid Goyal <kovid@kovidgoyal.net>, %(year)s. # msgid "" msgstr "" @@ -185,7 +185,7 @@ msgstr "" "Content-Transfer-Encoding: 8bit\\n" "Generated-By: pygettext.py %%(version)s\\n" -'''%dict(appname=__appname__, version=version) +'''%dict(appname=__appname__, version=version, year=time.strftime('%Y')) def usage(code, msg=''): diff --git a/setup/resources.py b/setup/resources.py index b77c986668..15772e1830 100644 --- a/setup/resources.py +++ b/setup/resources.py @@ -6,7 +6,7 @@ __license__ = 'GPL v3' __copyright__ = '2009, Kovid Goyal <kovid@kovidgoyal.net>' __docformat__ = 'restructuredtext en' -import os, cPickle, re, anydbm, shutil, marshal +import os, cPickle, re, shutil, marshal, zipfile, glob from zlib import compress from setup import Command, basenames, __appname__ @@ -23,13 +23,114 @@ def get_opts_from_parser(parser): for o in g.option_list: for x in do_opt(o): yield x -class Resources(Command): +class Kakasi(Command): - description = 'Compile various needed calibre resources' + description = 'Compile resources for unihandecode' KAKASI_PATH = os.path.join(Command.SRC, __appname__, 'ebooks', 'unihandecode', 'pykakasi') + def run(self, opts): + self.records = {} + src = self.j(self.KAKASI_PATH, 'kakasidict.utf8') + dest = self.j(self.RESOURCES, 'localization', + 'pykakasi','kanwadict2.pickle') + base = os.path.dirname(dest) + if not os.path.exists(base): + os.makedirs(base) + + if self.newer(dest, src): + self.info('\tGenerating Kanwadict') + + for line in open(src, "r"): + self.parsekdict(line) + self.kanwaout(dest) + + src = self.j(self.KAKASI_PATH, 'itaijidict.utf8') + dest = self.j(self.RESOURCES, 'localization', + 'pykakasi','itaijidict2.pickle') + + if self.newer(dest, src): + self.info('\tGenerating Itaijidict') + self.mkitaiji(src, dest) + + src = self.j(self.KAKASI_PATH, 'kanadict.utf8') + dest = self.j(self.RESOURCES, 'localization', + 'pykakasi','kanadict2.pickle') + + if self.newer(dest, src): + self.info('\tGenerating kanadict') + self.mkkanadict(src, dest) + + return + + + def mkitaiji(self, src, dst): + dic = {} + for line in open(src, "r"): + line = line.decode("utf-8").strip() + if line.startswith(';;'): # skip comment + continue + if re.match(r"^$",line): + continue + pair = re.sub(r'\\u([0-9a-fA-F]{4})', lambda x:unichr(int(x.group(1),16)), line) + dic[pair[0]] = pair[1] + cPickle.dump(dic, open(dst, 'wb'), protocol=-1) #pickle + + def mkkanadict(self, src, dst): + dic = {} + for line in open(src, "r"): + line = line.decode("utf-8").strip() + if line.startswith(';;'): # skip comment + continue + if re.match(r"^$",line): + continue + (alpha, kana) = line.split(' ') + dic[kana] = alpha + cPickle.dump(dic, open(dst, 'wb'), protocol=-1) #pickle + + def parsekdict(self, line): + line = line.decode("utf-8").strip() + if line.startswith(';;'): # skip comment + return + (yomi, kanji) = line.split(' ') + if ord(yomi[-1:]) <= ord('z'): + tail = yomi[-1:] + yomi = yomi[:-1] + else: + tail = '' + self.updaterec(kanji, yomi, tail) + + def updaterec(self, kanji, yomi, tail): + key = "%04x"%ord(kanji[0]) + if key in self.records: + if kanji in self.records[key]: + rec = self.records[key][kanji] + rec.append((yomi,tail)) + self.records[key].update( {kanji: rec} ) + else: + self.records[key][kanji]=[(yomi, tail)] + else: + self.records[key] = {} + self.records[key][kanji]=[(yomi, tail)] + + def kanwaout(self, out): + with open(out, 'wb') as f: + dic = {} + for k, v in self.records.iteritems(): + dic[k] = compress(marshal.dumps(v)) + cPickle.dump(dic, f, -1) + + def clean(self): + kakasi = self.j(self.RESOURCES, 'localization', 'pykakasi') + if os.path.exists(kakasi): + shutil.rmtree(kakasi) + +class Resources(Command): + + description = 'Compile various needed calibre resources' + sub_commands = ['kakasi'] + def run(self, opts): scripts = {} for x in ('console', 'gui'): @@ -56,6 +157,18 @@ class Resources(Command): with open(dest, 'wb') as f: f.write(xml) + recipe_icon_dir = self.a(self.j(self.RESOURCES, '..', 'recipes', + 'icons')) + dest = os.path.splitext(dest)[0] + '.zip' + files += glob.glob(self.j(recipe_icon_dir, '*.png')) + if self.newer(dest, files): + self.info('\tCreating builtin_recipes.zip') + with zipfile.ZipFile(dest, 'w', zipfile.ZIP_STORED) as zf: + for n in sorted(files, key=self.b): + with open(n, 'rb') as f: + zf.writestr(os.path.basename(n), f.read()) + + dest = self.j(self.RESOURCES, 'ebook-convert-complete.pickle') files = [] for x in os.walk(self.j(self.SRC, 'calibre')): @@ -105,108 +218,13 @@ class Resources(Command): import json json.dump(function_dict, open(dest, 'wb'), indent=4) - self.run_kakasi(opts) - - def run_kakasi(self, opts): - self.records = {} - src = self.j(self.KAKASI_PATH, 'kakasidict.utf8') - dest = self.j(self.RESOURCES, 'localization', - 'pykakasi','kanwadict2.db') - base = os.path.dirname(dest) - if not os.path.exists(base): - os.makedirs(base) - - if self.newer(dest, src): - self.info('\tGenerating Kanwadict') - - for line in open(src, "r"): - self.parsekdict(line) - self.kanwaout(dest) - - src = self.j(self.KAKASI_PATH, 'itaijidict.utf8') - dest = self.j(self.RESOURCES, 'localization', - 'pykakasi','itaijidict2.pickle') - - if self.newer(dest, src): - self.info('\tGenerating Itaijidict') - self.mkitaiji(src, dest) - - src = self.j(self.KAKASI_PATH, 'kanadict.utf8') - dest = self.j(self.RESOURCES, 'localization', - 'pykakasi','kanadict2.pickle') - - if self.newer(dest, src): - self.info('\tGenerating kanadict') - self.mkkanadict(src, dest) - - return - - - def mkitaiji(self, src, dst): - dic = {} - for line in open(src, "r"): - line = line.decode("utf-8").strip() - if line.startswith(';;'): # skip comment - continue - if re.match(r"^$",line): - continue - pair = re.sub(r'\\u([0-9a-fA-F]{4})', lambda x:unichr(int(x.group(1),16)), line) - dic[pair[0]] = pair[1] - cPickle.dump(dic, open(dst, 'w'), protocol=-1) #pickle - - def mkkanadict(self, src, dst): - dic = {} - for line in open(src, "r"): - line = line.decode("utf-8").strip() - if line.startswith(';;'): # skip comment - continue - if re.match(r"^$",line): - continue - (alpha, kana) = line.split(' ') - dic[kana] = alpha - cPickle.dump(dic, open(dst, 'w'), protocol=-1) #pickle - - def parsekdict(self, line): - line = line.decode("utf-8").strip() - if line.startswith(';;'): # skip comment - return - (yomi, kanji) = line.split(' ') - if ord(yomi[-1:]) <= ord('z'): - tail = yomi[-1:] - yomi = yomi[:-1] - else: - tail = '' - self.updaterec(kanji, yomi, tail) - - def updaterec(self, kanji, yomi, tail): - key = "%04x"%ord(kanji[0]) - if key in self.records: - if kanji in self.records[key]: - rec = self.records[key][kanji] - rec.append((yomi,tail)) - self.records[key].update( {kanji: rec} ) - else: - self.records[key][kanji]=[(yomi, tail)] - else: - self.records[key] = {} - self.records[key][kanji]=[(yomi, tail)] - - def kanwaout(self, out): - dic = anydbm.open(out, 'c') - for (k, v) in self.records.iteritems(): - dic[k] = compress(marshal.dumps(v)) - dic.close() - - def clean(self): for x in ('scripts', 'recipes', 'ebook-convert-complete'): x = self.j(self.RESOURCES, x+'.pickle') if os.path.exists(x): os.remove(x) - kakasi = self.j(self.RESOURCES, 'localization', 'pykakasi') - if os.path.exists(kakasi): - shutil.rmtree(kakasi) - + from setup.commands import kakasi + kakasi.clean() diff --git a/setup/translations.py b/setup/translations.py index 7f81abf8f5..1f026555ec 100644 --- a/setup/translations.py +++ b/setup/translations.py @@ -26,6 +26,38 @@ class POT(Command): ans.append(os.path.abspath(os.path.join(root, name))) return ans + def get_tweaks_docs(self): + path = self.a(self.j(self.SRC, '..', 'resources', 'default_tweaks.py')) + with open(path, 'rb') as f: + raw = f.read().decode('utf-8') + msgs = [] + lines = list(raw.splitlines()) + for i, line in enumerate(lines): + if line.startswith('#:'): + msgs.append((i, line[2:].strip())) + j = i + block = [] + while True: + j += 1 + line = lines[j] + if not line.startswith('#'): + break + block.append(line[1:].strip()) + if block: + msgs.append((i+1, '\n'.join(block))) + + ans = [] + for lineno, msg in msgs: + ans.append('#: %s:%d'%(path, lineno)) + slash = unichr(92) + msg = msg.replace(slash, slash*2).replace('"', r'\"').replace('\n', + r'\n').replace('\r', r'\r').replace('\t', r'\t') + ans.append('msgid "%s"'%msg) + ans.append('msgstr ""') + ans.append('') + + return '\n'.join(ans) + def run(self, opts): files = self.source_files() @@ -35,10 +67,10 @@ class POT(Command): atexit.register(shutil.rmtree, tempdir) pygettext(buf, ['-k', '__', '-p', tempdir]+files) src = buf.getvalue() + src += '\n\n' + self.get_tweaks_docs() pot = os.path.join(self.PATH, __appname__+'.pot') - f = open(pot, 'wb') - f.write(src) - f.close() + with open(pot, 'wb') as f: + f.write(src) self.info('Translations template:', os.path.abspath(pot)) return pot diff --git a/setup/upload.py b/setup/upload.py index cb363be5d7..a0138a42e4 100644 --- a/setup/upload.py +++ b/setup/upload.py @@ -1,21 +1,22 @@ #!/usr/bin/env python # vim:fileencoding=UTF-8:ts=4:sw=4:sta:et:sts=4:ai -from __future__ import with_statement __license__ = 'GPL v3' __copyright__ = '2009, Kovid Goyal <kovid@kovidgoyal.net>' __docformat__ = 'restructuredtext en' -import os, re, cStringIO, base64, httplib, subprocess, hashlib, shutil, time +import os, re, cStringIO, base64, httplib, subprocess, hashlib, shutil, time, \ + glob, stat from subprocess import check_call from tempfile import NamedTemporaryFile, mkdtemp +from zipfile import ZipFile from setup import Command, __version__, installer_name, __appname__ PREFIX = "/var/www/calibre-ebook.com" DOWNLOADS = PREFIX+"/htdocs/downloads" BETAS = DOWNLOADS +'/betas' -USER_MANUAL = PREFIX+'/htdocs/user_manual' +USER_MANUAL = '/var/www/localhost/htdocs/' HTML2LRF = "calibre/ebooks/lrf/html/demo" TXT2LRF = "src/calibre/ebooks/lrf/txt/demo" MOBILEREAD = 'ftp://dev.mobileread.com/calibre/' @@ -93,9 +94,11 @@ class UploadToGoogleCode(Command): # {{{ ext = os.path.splitext(fname)[1][1:] op = 'OpSys-'+{'msi':'Windows','dmg':'OSX','bz2':'Linux','gz':'All'}[ext] desc = installer_description(fname) + start = time.time() path = self.upload(os.path.abspath(fname), desc, labels=[typ, op, 'Featured']) - self.info('\tUploaded to:', path) + self.info('\tUploaded to:', path, 'in', int(time.time() - start), + 'seconds') return path def run(self, opts): @@ -248,10 +251,13 @@ class UploadToSourceForge(Command): # {{{ def upload_installers(self): for x in installers(): if not os.path.exists(x): continue + start = time.time() self.info('Uploading', x) check_call(['rsync', '-v', '-e', 'ssh -x', x, '%s,%s@frs.sourceforge.net:%s'%(self.USERNAME, self.PROJECT, self.rdir+'/')]) + print 'Uploaded in', int(time.time() - start), 'seconds' + print ('\n') def run(self, opts): self.opts = opts @@ -336,9 +342,30 @@ class UploadUserManual(Command): # {{{ description = 'Build and upload the User Manual' sub_commands = ['manual'] + def build_plugin_example(self, path): + from calibre import CurrentDir + with NamedTemporaryFile(suffix='.zip') as f: + os.fchmod(f.fileno(), + stat.S_IRUSR|stat.S_IRGRP|stat.S_IROTH|stat.S_IWRITE) + with CurrentDir(path): + with ZipFile(f, 'w') as zf: + for x in os.listdir('.'): + if x.endswith('.swp'): continue + zf.write(x) + if os.path.isdir(x): + for y in os.listdir(x): + zf.write(os.path.join(x, y)) + bname = self.b(path) + '_plugin.zip' + dest = '%s/%s'%(DOWNLOADS, bname) + subprocess.check_call(['scp', f.name, 'divok:'+dest]) + def run(self, opts): + path = self.j(self.SRC, 'calibre', 'manual', 'plugin_examples') + for x in glob.glob(self.j(path, '*')): + self.build_plugin_example(x) + check_call(' '.join(['scp', '-r', 'src/calibre/manual/.build/html/*', - 'divok:%s'%USER_MANUAL]), shell=True) + 'bugs:%s'%USER_MANUAL]), shell=True) # }}} class UploadDemo(Command): # {{{ diff --git a/src/calibre/__init__.py b/src/calibre/__init__.py index 221f5911c6..3a35feb66f 100644 --- a/src/calibre/__init__.py +++ b/src/calibre/__init__.py @@ -3,9 +3,7 @@ __license__ = 'GPL v3' __copyright__ = '2008, Kovid Goyal <kovid@kovidgoyal.net>' __docformat__ = 'restructuredtext en' -import uuid, sys, os, re, logging, time, \ - __builtin__, warnings, multiprocessing -from urllib import getproxies +import sys, os, re, time, random, __builtin__, warnings __builtin__.__dict__['dynamic_property'] = lambda(func): func(None) from htmlentitydefs import name2codepoint from math import floor @@ -14,25 +12,51 @@ from functools import partial warnings.simplefilter('ignore', DeprecationWarning) -from calibre.constants import iswindows, isosx, islinux, isfreebsd, isfrozen, \ - terminal_controller, preferred_encoding, \ - __appname__, __version__, __author__, \ - win32event, win32api, winerror, fcntl, \ - filesystem_encoding, plugins, config_dir -from calibre.startup import winutil, winutilerror, guess_type +from calibre.constants import (iswindows, isosx, islinux, isfrozen, + isbsd, preferred_encoding, __appname__, __version__, __author__, + win32event, win32api, winerror, fcntl, + filesystem_encoding, plugins, config_dir) +from calibre.startup import winutil, winutilerror -if islinux and not getattr(sys, 'frozen', False): - # Imported before PyQt4 to workaround PyQt4 util-linux conflict on gentoo +if False and islinux and not getattr(sys, 'frozen', False): + # Imported before PyQt4 to workaround PyQt4 util-linux conflict discovered on gentoo + # See http://bugs.gentoo.org/show_bug.cgi?id=317557 + # Importing uuid is slow so get rid of this at some point, maybe in a few + # years when even Debian has caught up + # Also remember to remove it from site.py in the binary builds + import uuid uuid.uuid4() if False: # Prevent pyflakes from complaining winutil, winutilerror, __appname__, islinux, __version__ - fcntl, win32event, isfrozen, __author__, terminal_controller - winerror, win32api, isfreebsd, guess_type + fcntl, win32event, isfrozen, __author__ + winerror, win32api, isbsd -import cssutils -cssutils.log.setLevel(logging.WARN) +_mt_inited = False +def _init_mimetypes(): + global _mt_inited + import mimetypes + mimetypes.init([P('mime.types')]) + _mt_inited = True + +def guess_type(*args, **kwargs): + import mimetypes + if not _mt_inited: + _init_mimetypes() + return mimetypes.guess_type(*args, **kwargs) + +def guess_all_extensions(*args, **kwargs): + import mimetypes + if not _mt_inited: + _init_mimetypes() + return mimetypes.guess_all_extensions(*args, **kwargs) + +def get_types_map(): + import mimetypes + if not _mt_inited: + _init_mimetypes() + return mimetypes.types_map def to_unicode(raw, encoding='utf-8', errors='strict'): if isinstance(raw, unicode): @@ -61,8 +85,12 @@ def osx_version(): if m: return int(m.group(1)), int(m.group(2)), int(m.group(3)) +def confirm_config_name(name): + return name + '_again' _filename_sanitize = re.compile(r'[\xae\0\\|\?\*<":>\+/]') +_filename_sanitize_unicode = frozenset([u'\\', u'|', u'?', u'*', u'<', + u'"', u':', u'>', u'+', u'/'] + list(map(unichr, xrange(32)))) def sanitize_file_name(name, substitute='_', as_unicode=False): ''' @@ -83,10 +111,45 @@ def sanitize_file_name(name, substitute='_', as_unicode=False): one = one.decode(filesystem_encoding) one = one.replace('..', substitute) # Windows doesn't like path components that end with a period - if one.endswith('.'): + if one and one[-1] in ('.', ' '): one = one[:-1]+'_' + # Names starting with a period are hidden on Unix + if one.startswith('.'): + one = '_' + one[1:] return one +def sanitize_file_name_unicode(name, substitute='_'): + ''' + Sanitize the filename `name`. All invalid characters are replaced by `substitute`. + The set of invalid characters is the union of the invalid characters in Windows, + OS X and Linux. Also removes leading and trailing whitespace. + **WARNING:** This function also replaces path separators, so only pass file names + and not full paths to it. + ''' + if isbytestring(name): + return sanitize_file_name(name, substitute=substitute, as_unicode=True) + chars = [substitute if c in _filename_sanitize_unicode else c for c in + name] + one = u''.join(chars) + one = re.sub(r'\s', ' ', one).strip() + one = re.sub(r'^\.+$', '_', one) + one = one.replace('..', substitute) + # Windows doesn't like path components that end with a period or space + if one and one[-1] in ('.', ' '): + one = one[:-1]+'_' + # Names starting with a period are hidden on Unix + if one.startswith('.'): + one = '_' + one[1:] + return one + +def sanitize_file_name2(name, substitute='_'): + ''' + Sanitize filenames removing invalid chars. Keeps unicode names as unicode + and bytestrings as bytestrings + ''' + if isbytestring(name): + return sanitize_file_name(name, substitute=substitute) + return sanitize_file_name_unicode(name, substitute=substitute) def prints(*args, **kwargs): ''' @@ -134,13 +197,14 @@ def prints(*args, **kwargs): except: file.write(repr(arg)) if i != len(args)-1: - file.write(sep) - file.write(end) + file.write(bytes(sep)) + file.write(bytes(end)) class CommandLineError(Exception): pass def setup_cli_handlers(logger, level): + import logging if os.environ.get('CALIBRE_WORKER', None) is not None and logger.handlers: return logger.setLevel(level) @@ -178,19 +242,31 @@ def filename_to_utf8(name): return name.decode(codec, 'replace').encode('utf8') def extract(path, dir): - ext = os.path.splitext(path)[1][1:].lower() extractor = None - if ext in ['zip', 'cbz', 'epub', 'oebzip']: - from calibre.libunzip import extract as zipextract - extractor = zipextract - elif ext in ['cbr', 'rar']: + # First use the file header to identify its type + with open(path, 'rb') as f: + id_ = f.read(3) + if id_ == b'Rar': from calibre.libunrar import extract as rarextract extractor = rarextract + elif id_.startswith(b'PK'): + from calibre.libunzip import extract as zipextract + extractor = zipextract + if extractor is None: + # Fallback to file extension + ext = os.path.splitext(path)[1][1:].lower() + if ext in ['zip', 'cbz', 'epub', 'oebzip']: + from calibre.libunzip import extract as zipextract + extractor = zipextract + elif ext in ['cbr', 'rar']: + from calibre.libunrar import extract as rarextract + extractor = rarextract if extractor is None: raise Exception('Unknown archive type') extractor(path, dir) def get_proxies(debug=True): + from urllib import getproxies proxies = getproxies() for key, proxy in list(proxies.items()): if not proxy or '..' in proxy: @@ -240,6 +316,23 @@ def get_parsed_proxy(typ='http', debug=True): prints('Using http proxy', str(ans)) return ans +USER_AGENT = 'Mozilla/5.0 (X11; U; Linux x86_64; en-US; rv:1.9.2.13) Gecko/20101210 Gentoo Firefox/3.6.13' +USER_AGENT_MOBILE = 'Mozilla/5.0 (Windows; U; Windows CE 5.1; rv:1.8.1a3) Gecko/20060610 Minimo/0.016' + +def random_user_agent(): + choices = [ + 'Mozilla/5.0 (Windows NT 5.2; rv:2.0.1) Gecko/20100101 Firefox/4.0.1', + 'Mozilla/5.0 (Windows NT 6.1; Win64; x64; rv:2.0.1) Gecko/20100101 Firefox/4.0.1', + 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10.6; rv:2.0.1) Gecko/20100101 Firefox/4.0.1', + 'Mozilla/5.0 (Windows; U; Windows NT 6.0; en-US; rv:1.9.2.11) Gecko/20101012 Firefox/3.6.11', + 'Mozilla/5.0 (Windows; U; Windows NT 5.1; en-US) AppleWebKit/525.19 (KHTML, like Gecko) Chrome/0.2.153.1 Safari/525.19', + 'Mozilla/5.0 (Windows; U; Windows NT 6.0; en-US; rv:1.9.2.11) Gecko/20101012 Firefox/3.6.11', + 'Mozilla/5.0 (Windows; U; Windows NT 5.1; en-US) AppleWebKit/534.3 (KHTML, like Gecko) Chrome/6.0.472.63 Safari/534.3', + 'Mozilla/5.0 (Windows; U; Windows NT 6.1; en-US) AppleWebKit/532.5 (KHTML, like Gecko) Chrome/4.0.249.78 Safari/532.5', + 'Mozilla/5.0 (compatible; MSIE 9.0; Windows NT 6.1; WOW64; Trident/5.0)', + ] + #return choices[-1] + return choices[random.randint(0, len(choices)-1)] def browser(honor_time=True, max_time=2, mobile_browser=False, user_agent=None): ''' @@ -254,8 +347,7 @@ def browser(honor_time=True, max_time=2, mobile_browser=False, user_agent=None): opener.set_handle_refresh(True, max_time=max_time, honor_time=honor_time) opener.set_handle_robots(False) if user_agent is None: - user_agent = ' Mozilla/5.0 (Windows; U; Windows CE 5.1; rv:1.8.1a3) Gecko/20060610 Minimo/0.016' if mobile_browser else \ - 'Mozilla/5.0 (X11; U; Linux x86_64; en-US; rv:1.9.2.13) Gecko/20101210 Gentoo Firefox/3.6.13' + user_agent = USER_AGENT_MOBILE if mobile_browser else USER_AGENT opener.addheaders = [('User-agent', user_agent)] http_proxy = get_proxies().get('http', None) if http_proxy: @@ -296,7 +388,11 @@ class CurrentDir(object): return self.cwd def __exit__(self, *args): - os.chdir(self.cwd) + try: + os.chdir(self.cwd) + except: + # The previous CWD no longer exists + pass class StreamReadWrapper(object): @@ -318,6 +414,7 @@ class StreamReadWrapper(object): def detect_ncpus(): """Detects the number of effective CPUs in the system""" + import multiprocessing ans = -1 try: ans = multiprocessing.cpu_count() @@ -472,7 +569,52 @@ def as_unicode(obj, enc=preferred_encoding): obj = repr(obj) return force_unicode(obj, enc=enc) +def url_slash_cleaner(url): + ''' + Removes redundant /'s from url's. + ''' + return re.sub(r'(?<!:)/{2,}', '/', url) +def get_download_filename(url, cookie_file=None): + ''' + Get a local filename for a URL using the content disposition header + ''' + from contextlib import closing + from urllib2 import unquote as urllib2_unquote + + filename = '' + + br = browser() + if cookie_file: + from mechanize import MozillaCookieJar + cj = MozillaCookieJar() + cj.load(cookie_file) + br.set_cookiejar(cj) + + try: + with closing(br.open(url)) as r: + disposition = r.info().get('Content-disposition', '') + for p in disposition.split(';'): + if 'filename' in p: + if '*=' in disposition: + parts = disposition.split('*=')[-1] + filename = parts.split('\'')[-1] + else: + filename = disposition.split('=')[-1] + if filename[0] in ('\'', '"'): + filename = filename[1:] + if filename[-1] in ('\'', '"'): + filename = filename[:-1] + filename = urllib2_unquote(filename) + break + except: + import traceback + traceback.print_exc() + + if not filename: + filename = r.geturl().split('/')[-1] + + return filename def human_readable(size): """ Convert a size in bytes into a human readable form """ @@ -488,6 +630,24 @@ def human_readable(size): size = size[:-2] return size + " " + suffix +def remove_bracketed_text(src, + brackets={u'(':u')', u'[':u']', u'{':u'}'}): + from collections import Counter + counts = Counter() + buf = [] + src = force_unicode(src) + rmap = dict([(v, k) for k, v in brackets.iteritems()]) + for char in src: + if char in brackets: + counts[char] += 1 + elif char in rmap: + idx = rmap[char] + if counts[idx] > 0: + counts[idx] -= 1 + elif sum(counts.itervalues()) < 1: + buf.append(char) + return u''.join(buf) + if isosx: import glob, shutil fdir = os.path.expanduser('~/.fonts') @@ -569,4 +729,3 @@ main() ipshell() sys.argv = old_argv - diff --git a/src/calibre/constants.py b/src/calibre/constants.py index def8e631c0..81b8eccdbc 100644 --- a/src/calibre/constants.py +++ b/src/calibre/constants.py @@ -1,29 +1,35 @@ +from future_builtins import map + __license__ = 'GPL v3' __copyright__ = '2008, Kovid Goyal kovid@kovidgoyal.net' __docformat__ = 'restructuredtext en' -__appname__ = 'calibre' -__version__ = '0.7.47' -__author__ = "Kovid Goyal <kovid@kovidgoyal.net>" - -import re -_ver = __version__.split('.') -_ver = [int(re.search(r'(\d+)', x).group(1)) for x in _ver] -numeric_version = tuple(_ver) +__appname__ = u'calibre' +numeric_version = (0, 8, 3) +__version__ = u'.'.join(map(unicode, numeric_version)) +__author__ = u"Kovid Goyal <kovid@kovidgoyal.net>" ''' Various run time constants. ''' -import sys, locale, codecs, os -from calibre.utils.terminfo import TerminalController +import sys, locale, codecs, os, importlib, collections -terminal_controller = TerminalController(sys.stdout) +_tc = None +def terminal_controller(): + global _tc + if _tc is None: + from calibre.utils.terminfo import TerminalController + _tc = TerminalController(sys.stdout) + return _tc -iswindows = 'win32' in sys.platform.lower() or 'win64' in sys.platform.lower() -isosx = 'darwin' in sys.platform.lower() -isnewosx = isosx and getattr(sys, 'new_app_bundle', False) -isfreebsd = 'freebsd' in sys.platform.lower() -islinux = not(iswindows or isosx or isfreebsd) +_plat = sys.platform.lower() +iswindows = 'win32' in _plat or 'win64' in _plat +isosx = 'darwin' in _plat +isnewosx = isosx and getattr(sys, 'new_app_bundle', False) +isfreebsd = 'freebsd' in _plat +isnetbsd = 'netbsd' in _plat +isbsd = isfreebsd or isnetbsd +islinux = not(iswindows or isosx or isbsd) isfrozen = hasattr(sys, 'frozen') isunix = isosx or islinux @@ -33,14 +39,15 @@ try: except: preferred_encoding = 'utf-8' -win32event = __import__('win32event') if iswindows else None -winerror = __import__('winerror') if iswindows else None -win32api = __import__('win32api') if iswindows else None -fcntl = None if iswindows else __import__('fcntl') +win32event = importlib.import_module('win32event') if iswindows else None +winerror = importlib.import_module('winerror') if iswindows else None +win32api = importlib.import_module('win32api') if iswindows else None +fcntl = None if iswindows else importlib.import_module('fcntl') filesystem_encoding = sys.getfilesystemencoding() if filesystem_encoding is None: filesystem_encoding = 'utf-8' + DEBUG = False def debug(): @@ -48,15 +55,12 @@ def debug(): DEBUG = True # plugins {{{ -plugins = None -if plugins is None: - # Load plugins - def load_plugins(): - plugins = {} - plugin_path = sys.extensions_location - sys.path.insert(0, plugin_path) - for plugin in [ +class Plugins(collections.Mapping): + + def __init__(self): + self._plugins = {} + plugins = [ 'pictureflow', 'lzx', 'msdes', @@ -69,19 +73,45 @@ if plugins is None: 'chmlib', 'chm_extra', 'icu', - ] + \ - (['winutil'] if iswindows else []) + \ - (['usbobserver'] if isosx else []): - try: - p, err = __import__(plugin), '' - except Exception, err: - p = None - err = str(err) - plugins[plugin] = (p, err) - sys.path.remove(plugin_path) - return plugins + 'speedup', + ] + if iswindows: + plugins.append('winutil') + if isosx: + plugins.append('usbobserver') + self.plugins = frozenset(plugins) - plugins = load_plugins() + def load_plugin(self, name): + if name in self._plugins: + return + sys.path.insert(0, sys.extensions_location) + try: + p, err = importlib.import_module(name), '' + except Exception as err: + p = None + err = str(err) + self._plugins[name] = (p, err) + sys.path.remove(sys.extensions_location) + + def __iter__(self): + return iter(self.plugins) + + def __len__(self): + return len(self.plugins) + + def __contains__(self, name): + return name in self.plugins + + def __getitem__(self, name): + if name not in self.plugins: + raise KeyError('No plugin named %r'%name) + self.load_plugin(name) + return self._plugins[name] + + +plugins = None +if plugins is None: + plugins = Plugins() # }}} # config_dir {{{ diff --git a/src/calibre/customize/__init__.py b/src/calibre/customize/__init__.py index 1f44eb4ae2..d087eb5351 100644 --- a/src/calibre/customize/__init__.py +++ b/src/calibre/customize/__init__.py @@ -2,11 +2,24 @@ from __future__ import with_statement __license__ = 'GPL v3' __copyright__ = '2008, Kovid Goyal <kovid at kovidgoyal.net>' -import os, sys, zipfile +import os, sys, zipfile, importlib -from calibre.constants import numeric_version +from calibre.constants import numeric_version, iswindows, isosx from calibre.ptempfile import PersistentTemporaryFile +platform = 'linux' +if iswindows: + platform = 'windows' +elif isosx: + platform = 'osx' + + +class PluginNotFound(ValueError): + pass + +class InvalidPlugin(ValueError): + pass + class Plugin(object): # {{{ ''' @@ -436,7 +449,7 @@ class CatalogPlugin(Plugin): # {{{ ['author_sort','authors','comments','cover','formats', 'id','isbn','ondevice','pubdate','publisher','rating', 'series_index','series','size','tags','timestamp', - 'title','uuid']) + 'title_sort','title','uuid']) all_custom_fields = set(db.custom_field_keys()) all_fields = all_std_fields.union(all_custom_fields) @@ -512,13 +525,21 @@ class InterfaceActionBase(Plugin): # {{{ actual_plugin = None + def __init__(self, *args, **kwargs): + Plugin.__init__(self, *args, **kwargs) + self.actual_plugin_ = None + def load_actual_plugin(self, gui): ''' This method must return the actual interface action plugin object. ''' - mod, cls = self.actual_plugin.split(':') - return getattr(__import__(mod, fromlist=['1'], level=0), cls)(gui, - self.site_customization) + ac = self.actual_plugin_ + if ac is None: + mod, cls = self.actual_plugin.split(':') + ac = getattr(importlib.import_module(mod), cls)(gui, + self.site_customization) + self.actual_plugin_ = ac + return ac # }}} @@ -575,9 +596,57 @@ class PreferencesPlugin(Plugin): # {{{ base, _, wc = self.config_widget.partition(':') if not wc: wc = 'ConfigWidget' - base = __import__(base, fromlist=[1]) + base = importlib.import_module(base) widget = getattr(base, wc) return widget(parent) # }}} +class StoreBase(Plugin): # {{{ + + supported_platforms = ['windows', 'osx', 'linux'] + author = 'John Schember' + type = _('Store') + # Information about the store. Should be in the primary language + # of the store. This should not be translatable when set by + # a subclass. + description = _('An ebook store.') + minimum_calibre_version = (0, 8, 0) + version = (1, 0, 1) + + actual_plugin = None + + # Does the store only distribute ebooks without DRM. + drm_free_only = False + # This is the 2 letter country code for the corporate + # headquarters of the store. + headquarters = '' + # All formats the store distributes ebooks in. + formats = [] + # Is this store on an affiliate program? + affiliate = False + + def load_actual_plugin(self, gui): + ''' + This method must return the actual interface action plugin object. + ''' + mod, cls = self.actual_plugin.split(':') + self.actual_plugin_object = getattr(importlib.import_module(mod), cls)(gui, self.name) + return self.actual_plugin_object + + def customization_help(self, gui=False): + if getattr(self, 'actual_plugin_object', None) is not None: + return self.actual_plugin_object.customization_help(gui) + raise NotImplementedError() + + def config_widget(self): + if getattr(self, 'actual_plugin_object', None) is not None: + return self.actual_plugin_object.config_widget() + raise NotImplementedError() + + def save_settings(self, config_widget): + if getattr(self, 'actual_plugin_object', None) is not None: + return self.actual_plugin_object.save_settings(config_widget) + raise NotImplementedError() + +# }}} diff --git a/src/calibre/customize/builtins.py b/src/calibre/customize/builtins.py index cd4c866562..5cde30f72e 100644 --- a/src/calibre/customize/builtins.py +++ b/src/calibre/customize/builtins.py @@ -1,15 +1,15 @@ -import os.path +# -*- coding: utf-8 -*- + __license__ = 'GPL v3' __copyright__ = '2008, Kovid Goyal <kovid at kovidgoyal.net>' import textwrap, os, glob, functools, re from calibre import guess_type from calibre.customize import FileTypePlugin, MetadataReaderPlugin, \ - MetadataWriterPlugin, PreferencesPlugin, InterfaceActionBase + MetadataWriterPlugin, PreferencesPlugin, InterfaceActionBase, StoreBase from calibre.constants import numeric_version from calibre.ebooks.metadata.archive import ArchiveExtract, get_cbz_metadata from calibre.ebooks.metadata.opf2 import metadata_to_opf -from calibre.ebooks.oeb.base import OEB_IMAGES # To archive plugins {{{ class HTML2ZIP(FileTypePlugin): @@ -92,11 +92,13 @@ class TXT2TXTZ(FileTypePlugin): 'containing Markdown or Textile references to images. The referenced ' 'images as well as the TXT file are added to the archive.') version = numeric_version - file_types = set(['txt']) + file_types = set(['txt', 'text']) supported_platforms = ['windows', 'osx', 'linux'] on_import = True def _get_image_references(self, txt, base_dir): + from calibre.ebooks.oeb.base import OEB_IMAGES + images = [] # Textile @@ -166,6 +168,14 @@ class ComicMetadataReader(MetadataReaderPlugin): description = _('Extract cover from comic files') def get_metadata(self, stream, ftype): + if hasattr(stream, 'seek') and hasattr(stream, 'tell'): + pos = stream.tell() + id_ = stream.read(3) + stream.seek(pos) + if id_ == b'Rar': + ftype = 'cbr' + elif id_.startswith(b'PK'): + ftype = 'cbz' if ftype == 'cbr': from calibre.libunrar import extract_first_alphabetically as extract_first extract_first @@ -231,6 +241,17 @@ class HTMLMetadataReader(MetadataReaderPlugin): from calibre.ebooks.metadata.html import get_metadata return get_metadata(stream) +class HTMLZMetadataReader(MetadataReaderPlugin): + + name = 'Read HTMLZ metadata' + file_types = set(['htmlz']) + description = _('Read metadata from %s files') % 'HTMLZ' + author = 'John Schember' + + def get_metadata(self, stream, ftype): + from calibre.ebooks.metadata.extz import get_metadata + return get_metadata(stream) + class IMPMetadataReader(MetadataReaderPlugin): name = 'Read IMP metadata' @@ -407,7 +428,7 @@ class TXTZMetadataReader(MetadataReaderPlugin): author = 'John Schember' def get_metadata(self, stream, ftype): - from calibre.ebooks.metadata.txtz import get_metadata + from calibre.ebooks.metadata.extz import get_metadata return get_metadata(stream) class ZipMetadataReader(MetadataReaderPlugin): @@ -433,6 +454,17 @@ class EPUBMetadataWriter(MetadataWriterPlugin): from calibre.ebooks.metadata.epub import set_metadata set_metadata(stream, mi, apply_null=self.apply_null) +class HTMLZMetadataWriter(MetadataWriterPlugin): + + name = 'Set HTMLZ metadata' + file_types = set(['htmlz']) + description = _('Set metadata from %s files') % 'HTMLZ' + author = 'John Schember' + + def set_metadata(self, stream, mi, type): + from calibre.ebooks.metadata.extz import set_metadata + set_metadata(stream, mi) + class LRFMetadataWriter(MetadataWriterPlugin): name = 'Set LRF metadata' @@ -505,7 +537,7 @@ class TXTZMetadataWriter(MetadataWriterPlugin): author = 'John Schember' def set_metadata(self, stream, mi, type): - from calibre.ebooks.metadata.txtz import set_metadata + from calibre.ebooks.metadata.extz import set_metadata set_metadata(stream, mi) # }}} @@ -514,6 +546,7 @@ from calibre.ebooks.comic.input import ComicInput from calibre.ebooks.epub.input import EPUBInput from calibre.ebooks.fb2.input import FB2Input from calibre.ebooks.html.input import HTMLInput +from calibre.ebooks.htmlz.input import HTMLZInput from calibre.ebooks.lit.input import LITInput from calibre.ebooks.mobi.input import MOBIInput from calibre.ebooks.odt.input import ODTInput @@ -544,6 +577,7 @@ from calibre.ebooks.tcr.output import TCROutput from calibre.ebooks.txt.output import TXTOutput from calibre.ebooks.txt.output import TXTZOutput from calibre.ebooks.html.output import HTMLOutput +from calibre.ebooks.htmlz.output import HTMLZOutput from calibre.ebooks.snb.output import SNBOutput from calibre.customize.profiles import input_profiles, output_profiles @@ -560,8 +594,9 @@ from calibre.devices.iliad.driver import ILIAD from calibre.devices.irexdr.driver import IREXDR1000, IREXDR800 from calibre.devices.jetbook.driver import JETBOOK, MIBUK, JETBOOK_MINI from calibre.devices.kindle.driver import KINDLE, KINDLE2, KINDLE_DX -from calibre.devices.nook.driver import NOOK, NOOK_COLOR +from calibre.devices.nook.driver import NOOK, NOOK_COLOR, NOOK_TSR from calibre.devices.prs505.driver import PRS505 +from calibre.devices.user_defined.driver import USER_DEFINED from calibre.devices.android.driver import ANDROID, S60 from calibre.devices.nokia.driver import N770, N810, E71X, E52 from calibre.devices.eslick.driver import ESLICK, EBK52 @@ -579,26 +614,33 @@ from calibre.devices.misc import PALMPRE, AVANT, SWEEX, PDNOVEL, \ from calibre.devices.folder_device.driver import FOLDER_DEVICE_FOR_CONFIG from calibre.devices.kobo.driver import KOBO from calibre.devices.bambook.driver import BAMBOOK +from calibre.devices.boeye.driver import BOEYE_BEX, BOEYE_BDX -from calibre.ebooks.metadata.fetch import GoogleBooks, ISBNDB, Amazon, \ - KentDistrictLibrary -from calibre.ebooks.metadata.douban import DoubanBooks -from calibre.ebooks.metadata.nicebooks import NiceBooks, NiceBooksCovers -from calibre.ebooks.metadata.covers import OpenLibraryCovers, \ - AmazonCovers, DoubanCovers from calibre.library.catalog import CSV_XML, EPUB_MOBI, BIBTEX from calibre.ebooks.epub.fix.unmanifested import Unmanifested from calibre.ebooks.epub.fix.epubcheck import Epubcheck -plugins = [HTML2ZIP, PML2PMLZ, TXT2TXTZ, ArchiveExtract, GoogleBooks, ISBNDB, Amazon, - KentDistrictLibrary, DoubanBooks, NiceBooks, CSV_XML, EPUB_MOBI, BIBTEX, Unmanifested, - Epubcheck, OpenLibraryCovers, AmazonCovers, DoubanCovers, - NiceBooksCovers] +plugins = [HTML2ZIP, PML2PMLZ, TXT2TXTZ, ArchiveExtract, CSV_XML, EPUB_MOBI, BIBTEX, Unmanifested, + Epubcheck, ] + +# New metadata download plugins {{{ +from calibre.ebooks.metadata.sources.google import GoogleBooks +from calibre.ebooks.metadata.sources.amazon import Amazon +from calibre.ebooks.metadata.sources.openlibrary import OpenLibrary +from calibre.ebooks.metadata.sources.isbndb import ISBNDB +from calibre.ebooks.metadata.sources.overdrive import OverDrive +from calibre.ebooks.metadata.sources.douban import Douban + +plugins += [GoogleBooks, Amazon, OpenLibrary, ISBNDB, OverDrive, Douban] + +# }}} + plugins += [ ComicInput, EPUBInput, FB2Input, HTMLInput, + HTMLZInput, LITInput, MOBIInput, ODTInput, @@ -630,6 +672,7 @@ plugins += [ TXTOutput, TXTZOutput, HTMLOutput, + HTMLZOutput, SNBOutput, ] # Order here matters. The first matched device is the one used. @@ -650,8 +693,7 @@ plugins += [ KINDLE, KINDLE2, KINDLE_DX, - NOOK, - NOOK_COLOR, + NOOK, NOOK_COLOR, NOOK_TSR, PRS505, ANDROID, S60, @@ -703,6 +745,9 @@ plugins += [ EEEREADER, NEXTBOOK, ITUNES, + BOEYE_BEX, + BOEYE_BDX, + USER_DEFINED, ] plugins += [x for x in list(locals().values()) if isinstance(x, type) and \ x.__name__.endswith('MetadataReader')] @@ -804,13 +849,29 @@ class ActionNextMatch(InterfaceActionBase): name = 'Next Match' actual_plugin = 'calibre.gui2.actions.next_match:NextMatchAction' +class ActionStore(InterfaceActionBase): + name = 'Store' + author = 'John Schember' + actual_plugin = 'calibre.gui2.actions.store:StoreAction' + + def customization_help(self, gui=False): + return 'Customize the behavior of the store search.' + + def config_widget(self): + from calibre.gui2.store.config.store import config_widget as get_cw + return get_cw() + + def save_settings(self, config_widget): + from calibre.gui2.store.config.store import save_settings as save + save(config_widget) + plugins += [ActionAdd, ActionFetchAnnotations, ActionGenerateCatalog, ActionConvert, ActionDelete, ActionEditMetadata, ActionView, ActionFetchNews, ActionSaveToDisk, ActionShowBookDetails, ActionRestart, ActionOpenFolder, ActionConnectShare, ActionSendToDevice, ActionHelp, ActionPreferences, ActionSimilarBooks, ActionAddToLibrary, ActionEditCollections, ActionChooseLibrary, - ActionCopyToLibrary, ActionTweakEpub, ActionNextMatch] + ActionCopyToLibrary, ActionTweakEpub, ActionNextMatch, ActionStore] # }}} @@ -853,7 +914,7 @@ class Columns(PreferencesPlugin): class Toolbar(PreferencesPlugin): name = 'Toolbar' icon = I('wizard.png') - gui_name = _('Customize the toolbar') + gui_name = _('Toolbar') category = 'Interface' gui_category = _('Interface') category_order = 1 @@ -865,7 +926,7 @@ class Toolbar(PreferencesPlugin): class Search(PreferencesPlugin): name = 'Search' icon = I('search.png') - gui_name = _('Customize searching') + gui_name = _('Searching') category = 'Interface' gui_category = _('Interface') category_order = 1 @@ -989,6 +1050,17 @@ class Server(PreferencesPlugin): 'give you access to your calibre library from anywhere, ' 'on any device, over the internet') +class MetadataSources(PreferencesPlugin): + name = 'Metadata download' + icon = I('metadata.png') + gui_name = _('Metadata download') + category = 'Sharing' + gui_category = _('Sharing') + category_order = 4 + name_order = 3 + config_widget = 'calibre.gui2.preferences.metadata_sources' + description = _('Control how calibre downloads ebook metadata from the net') + class Plugins(PreferencesPlugin): name = 'Plugins' icon = I('plugins.png') @@ -1025,13 +1097,351 @@ class Misc(PreferencesPlugin): plugins += [LookAndFeel, Behavior, Columns, Toolbar, Search, InputOptions, CommonOptions, OutputOptions, Adding, Saving, Sending, Plugboard, - Email, Server, Plugins, Tweaks, Misc, TemplateFunctions] + Email, Server, Plugins, Tweaks, Misc, TemplateFunctions, + MetadataSources] #}}} -# New metadata download plugins {{{ -from calibre.ebooks.metadata.sources.google import GoogleBooks +# Store plugins {{{ +class StoreAmazonKindleStore(StoreBase): + name = 'Amazon Kindle' + description = u'Kindle books from Amazon.' + actual_plugin = 'calibre.gui2.store.amazon_plugin:AmazonKindleStore' -plugins += [GoogleBooks] + headquarters = 'US' + formats = ['KINDLE'] + affiliate = True + +class StoreAmazonDEKindleStore(StoreBase): + name = 'Amazon DE Kindle' + author = 'Charles Haley' + description = u'Kindle Bücher von Amazon.' + actual_plugin = 'calibre.gui2.store.amazon_de_plugin:AmazonDEKindleStore' + + headquarters = 'DE' + formats = ['KINDLE'] + affiliate = True + +class StoreAmazonUKKindleStore(StoreBase): + name = 'Amazon UK Kindle' + author = 'Charles Haley' + description = u'Kindle books from Amazon\'s UK web site. Also, includes French language ebooks.' + actual_plugin = 'calibre.gui2.store.amazon_uk_plugin:AmazonUKKindleStore' + + headquarters = 'UK' + formats = ['KINDLE'] + affiliate = True + +class StoreArchiveOrgStore(StoreBase): + name = 'Archive.org' + description = u'An Internet library offering permanent access for researchers, historians, scholars, people with disabilities, and the general public to historical collections that exist in digital format.' + actual_plugin = 'calibre.gui2.store.archive_org_plugin:ArchiveOrgStore' + + drm_free_only = True + headquarters = 'US' + formats = ['DAISY', 'DJVU', 'EPUB', 'MOBI', 'PDF', 'TXT'] + +class StoreBaenWebScriptionStore(StoreBase): + name = 'Baen WebScription' + description = u'Sci-Fi & Fantasy brought to you by Jim Baen.' + actual_plugin = 'calibre.gui2.store.baen_webscription_plugin:BaenWebScriptionStore' + + drm_free_only = True + headquarters = 'US' + formats = ['EPUB', 'LIT', 'LRF', 'MOBI', 'RB', 'RTF', 'ZIP'] + +class StoreBNStore(StoreBase): + name = 'Barnes and Noble' + description = u'The world\'s largest book seller. As the ultimate destination for book lovers, Barnes & Noble.com offers an incredible array of content.' + actual_plugin = 'calibre.gui2.store.bn_plugin:BNStore' + + headquarters = 'US' + formats = ['NOOK'] + affiliate = True + +class StoreBeamEBooksDEStore(StoreBase): + name = 'Beam EBooks DE' + author = 'Charles Haley' + description = u'Bei uns finden Sie: Tausende deutschsprachige eBooks; Alle eBooks ohne hartes DRM; PDF, ePub und Mobipocket Format; Sofortige Verfügbarkeit - 24 Stunden am Tag; Günstige Preise; eBooks für viele Lesegeräte, PC,Mac und Smartphones; Viele Gratis eBooks' + actual_plugin = 'calibre.gui2.store.beam_ebooks_de_plugin:BeamEBooksDEStore' + + drm_free_only = True + headquarters = 'DE' + formats = ['EPUB', 'MOBI', 'PDF'] + affiliate = True + +class StoreBeWriteStore(StoreBase): + name = 'BeWrite Books' + description = u'Publishers of fine books. Highly selective and editorially driven. Does not offer: books for children or exclusively YA, erotica, swords-and-sorcery fantasy and space-opera-style science fiction. All other genres are represented.' + actual_plugin = 'calibre.gui2.store.bewrite_plugin:BeWriteStore' + + drm_free_only = True + headquarters = 'US' + formats = ['EPUB', 'MOBI', 'PDF'] + +class StoreDieselEbooksStore(StoreBase): + name = 'Diesel eBooks' + description = u'Instant access to over 2.4 million titles from hundreds of publishers including Harlequin, HarperCollins, John Wiley & Sons, McGraw-Hill, Simon & Schuster and Random House.' + actual_plugin = 'calibre.gui2.store.diesel_ebooks_plugin:DieselEbooksStore' + + headquarters = 'US' + formats = ['EPUB', 'PDF'] + affiliate = True + +class StoreEbookscomStore(StoreBase): + name = 'eBooks.com' + description = u'Sells books in multiple electronic formats in all categories. Technical infrastructure is cutting edge, robust and scalable, with servers in the US and Europe.' + actual_plugin = 'calibre.gui2.store.ebooks_com_plugin:EbookscomStore' + + headquarters = 'US' + formats = ['EPUB', 'LIT', 'MOBI', 'PDF'] + affiliate = True + +class StoreEPubBuyDEStore(StoreBase): + name = 'EPUBBuy DE' + author = 'Charles Haley' + description = u'Bei EPUBBuy.com finden Sie ausschliesslich eBooks im weitverbreiteten EPUB-Format und ohne DRM. So haben Sie die freie Wahl, wo Sie Ihr eBook lesen: Tablet, eBook-Reader, Smartphone oder einfach auf Ihrem PC. So macht eBook-Lesen Spaß!' + actual_plugin = 'calibre.gui2.store.epubbuy_de_plugin:EPubBuyDEStore' + + drm_free_only = True + headquarters = 'DE' + formats = ['EPUB'] + affiliate = True + +class StoreEBookShoppeUKStore(StoreBase): + name = 'ebookShoppe UK' + author = u'Charles Haley' + description = u'We made this website in an attempt to offer the widest range of UK eBooks possible across and as many formats as we could manage.' + actual_plugin = 'calibre.gui2.store.ebookshoppe_uk_plugin:EBookShoppeUKStore' + + headquarters = 'UK' + formats = ['EPUB', 'PDF'] + affiliate = True + +class StoreEHarlequinStore(StoreBase): + name = 'eHarlequin' + description = u'A global leader in series romance and one of the world\'s leading publishers of books for women. Offers women a broad range of reading from romance to bestseller fiction, from young adult novels to erotic literature, from nonfiction to fantasy, from African-American novels to inspirational romance, and more.' + actual_plugin = 'calibre.gui2.store.eharlequin_plugin:EHarlequinStore' + + headquarters = 'CA' + formats = ['EPUB', 'PDF'] + affiliate = True + +class StoreFeedbooksStore(StoreBase): + name = 'Feedbooks' + description = u'Feedbooks is a cloud publishing and distribution service, connected to a large ecosystem of reading systems and social networks. Provides a variety of genres from independent and classic books.' + actual_plugin = 'calibre.gui2.store.feedbooks_plugin:FeedbooksStore' + + headquarters = 'FR' + formats = ['EPUB', 'MOBI', 'PDF'] + +class StoreFoylesUKStore(StoreBase): + name = 'Foyles UK' + author = 'Charles Haley' + description = u'Foyles of London\'s ebook store. Provides extensive range covering all subjects.' + actual_plugin = 'calibre.gui2.store.foyles_uk_plugin:FoylesUKStore' + + headquarters = 'UK' + formats = ['EPUB', 'PDF'] + affiliate = True + +class StoreGandalfStore(StoreBase): + name = 'Gandalf' + author = u'Tomasz Długosz' + description = u'Księgarnia internetowa Gandalf.' + actual_plugin = 'calibre.gui2.store.gandalf_plugin:GandalfStore' + + headquarters = 'PL' + formats = ['EPUB', 'PDF'] + +class StoreGoogleBooksStore(StoreBase): + name = 'Google Books' + description = u'Google Books' + actual_plugin = 'calibre.gui2.store.google_books_plugin:GoogleBooksStore' + + headquarters = 'US' + formats = ['EPUB', 'PDF', 'TXT'] + +class StoreGutenbergStore(StoreBase): + name = 'Project Gutenberg' + description = u'The first producer of free ebooks. Free in the United States because their copyright has expired. They may not be free of copyright in other countries. Readers outside of the United States must check the copyright laws of their countries before downloading or redistributing our ebooks.' + actual_plugin = 'calibre.gui2.store.gutenberg_plugin:GutenbergStore' + + drm_free_only = True + headquarters = 'US' + formats = ['EPUB', 'HTML', 'MOBI', 'PDB', 'TXT'] + +class StoreKoboStore(StoreBase): + name = 'Kobo' + description = u'With over 2.3 million eBooks to browse we have engaged readers in over 200 countries in Kobo eReading. Our eBook listings include New York Times Bestsellers, award winners, classics and more!' + actual_plugin = 'calibre.gui2.store.kobo_plugin:KoboStore' + + headquarters = 'CA' + formats = ['EPUB'] + affiliate = True + +class StoreLegimiStore(StoreBase): + name = 'Legimi' + author = u'Tomasz Długosz' + description = u'Tanie oraz darmowe ebooki, egazety i blogi w formacie EPUB, wprost na Twój e-czytnik, iPhone, iPad, Android i komputer' + actual_plugin = 'calibre.gui2.store.legimi_plugin:LegimiStore' + + headquarters = 'PL' + formats = ['EPUB'] + +class StoreManyBooksStore(StoreBase): + name = 'ManyBooks' + description = u'Public domain and creative commons works from many sources.' + actual_plugin = 'calibre.gui2.store.manybooks_plugin:ManyBooksStore' + + drm_free_only = True + headquarters = 'US' + formats = ['EPUB', 'FB2', 'JAR', 'LIT', 'LRF', 'MOBI', 'PDB', 'PDF', 'RB', 'RTF', 'TCR', 'TXT', 'ZIP'] + +class StoreMobileReadStore(StoreBase): + name = 'MobileRead' + description = u'Ebooks handcrafted with the utmost care.' + actual_plugin = 'calibre.gui2.store.mobileread.mobileread_plugin:MobileReadStore' + + drm_free_only = True + headquarters = 'CH' + formats = ['EPUB', 'IMP', 'LRF', 'LIT', 'MOBI', 'PDF'] + +class StoreNextoStore(StoreBase): + name = 'Nexto' + author = u'Tomasz Długosz' + description = u'Największy w Polsce sklep internetowy z audiobookami mp3, ebookami pdf oraz prasą do pobrania on-line.' + actual_plugin = 'calibre.gui2.store.nexto_plugin:NextoStore' + + headquarters = 'PL' + formats = ['EPUB', 'PDF'] + affiliate = True + +class StoreOpenLibraryStore(StoreBase): + name = 'Open Library' + description = u'One web page for every book ever published. The goal is to be a true online library. Over 20 million records from a variety of large catalogs as well as single contributions, with more on the way.' + actual_plugin = 'calibre.gui2.store.open_library_plugin:OpenLibraryStore' + + drm_free_only = True + headquarters = 'US' + formats = ['DAISY', 'DJVU', 'EPUB', 'MOBI', 'PDF', 'TXT'] + +class StoreOReillyStore(StoreBase): + name = 'OReilly' + description = u'Programming and tech ebooks from OReilly.' + actual_plugin = 'calibre.gui2.store.oreilly_plugin:OReillyStore' + + drm_free_only = True + headquarters = 'US' + formats = ['APK', 'DAISY', 'EPUB', 'MOBI', 'PDF'] + +class StorePragmaticBookshelfStore(StoreBase): + name = 'Pragmatic Bookshelf' + description = u'The Pragmatic Bookshelf\'s collection of programming and tech books avaliable as ebooks.' + actual_plugin = 'calibre.gui2.store.pragmatic_bookshelf_plugin:PragmaticBookshelfStore' + + drm_free_only = True + headquarters = 'US' + formats = ['EPUB', 'MOBI', 'PDF'] + +class StoreSmashwordsStore(StoreBase): + name = 'Smashwords' + description = u'An ebook publishing and distribution platform for ebook authors, publishers and readers. Covers many genres and formats.' + actual_plugin = 'calibre.gui2.store.smashwords_plugin:SmashwordsStore' + + drm_free_only = True + headquarters = 'US' + formats = ['EPUB', 'HTML', 'LRF', 'MOBI', 'PDB', 'RTF', 'TXT'] + affiliate = True + +class StoreVirtualoStore(StoreBase): + name = 'Virtualo' + author = u'Tomasz Długosz' + description = u'Księgarnia internetowa, która oferuje bezpieczny i szeroki dostęp do książek w formie cyfrowej.' + actual_plugin = 'calibre.gui2.store.virtualo_plugin:VirtualoStore' + + headquarters = 'PL' + formats = ['EPUB', 'PDF'] + +class StoreWaterstonesUKStore(StoreBase): + name = 'Waterstones UK' + author = 'Charles Haley' + description = u'Waterstone\'s mission is to be the leading Bookseller on the High Street and online providing customers the widest choice, great value and expert advice from a team passionate about Bookselling.' + actual_plugin = 'calibre.gui2.store.waterstones_uk_plugin:WaterstonesUKStore' + + headquarters = 'UK' + formats = ['EPUB', 'PDF'] + +class StoreWeightlessBooksStore(StoreBase): + name = 'Weightless Books' + description = u'An independent DRM-free ebooksite devoted to ebooks of all sorts.' + actual_plugin = 'calibre.gui2.store.weightless_books_plugin:WeightlessBooksStore' + + drm_free_only = True + headquarters = 'US' + formats = ['EPUB', 'HTML', 'LIT', 'MOBI', 'PDF'] + +class StoreWHSmithUKStore(StoreBase): + name = 'WH Smith UK' + author = 'Charles Haley' + description = u"Shop for savings on Books, discounted Magazine subscriptions and great prices on Stationery, Toys & Games" + actual_plugin = 'calibre.gui2.store.whsmith_uk_plugin:WHSmithUKStore' + + headquarters = 'UK' + formats = ['EPUB', 'PDF'] + +class StoreWizardsTowerBooksStore(StoreBase): + name = 'Wizards Tower Books' + description = u'A science fiction and fantasy publisher. Concentrates mainly on making out-of-print works available once more as e-books, and helping other small presses exploit the e-book market. Also publishes a small number of limited-print-run anthologies with a view to encouraging diversity in the science fiction and fantasy field.' + actual_plugin = 'calibre.gui2.store.wizards_tower_books_plugin:WizardsTowerBooksStore' + + drm_free_only = True + headquarters = 'UK' + formats = ['EPUB', 'MOBI'] + +class StoreWoblinkStore(StoreBase): + name = 'Woblink' + author = u'Tomasz Długosz' + description = u'Czytanie zdarza się wszędzie!' + actual_plugin = 'calibre.gui2.store.woblink_plugin:WoblinkStore' + + headquarters = 'PL' + formats = ['EPUB'] + +plugins += [ + StoreArchiveOrgStore, + StoreAmazonKindleStore, + StoreAmazonDEKindleStore, + StoreAmazonUKKindleStore, + StoreBaenWebScriptionStore, + StoreBNStore, + StoreBeamEBooksDEStore, + StoreBeWriteStore, + StoreDieselEbooksStore, + StoreEbookscomStore, + StoreEBookShoppeUKStore, + StoreEPubBuyDEStore, + StoreEHarlequinStore, + StoreFeedbooksStore, + StoreFoylesUKStore, + StoreGandalfStore, + StoreGoogleBooksStore, + StoreGutenbergStore, + StoreKoboStore, + StoreLegimiStore, + StoreManyBooksStore, + StoreMobileReadStore, + StoreNextoStore, + StoreOpenLibraryStore, + StoreOReillyStore, + StorePragmaticBookshelfStore, + StoreSmashwordsStore, + StoreVirtualoStore, + StoreWaterstonesUKStore, + StoreWeightlessBooksStore, + StoreWHSmithUKStore, + StoreWizardsTowerBooksStore, + StoreWoblinkStore +] # }}} diff --git a/src/calibre/customize/profiles.py b/src/calibre/customize/profiles.py index bebaebced6..e04930dd0c 100644 --- a/src/calibre/customize/profiles.py +++ b/src/calibre/customize/profiles.py @@ -253,7 +253,7 @@ class OutputProfile(Plugin): periodical_date_in_title = True #: Characters used in jackets and catalogs - missing_char = u'x' + missing_char = u'x' ratings_char = u'*' empty_ratings_char = u' ' read_char = u'+' @@ -293,38 +293,38 @@ class iPadOutput(OutputProfile): } ] - missing_char = u'\u2715\u200a' # stylized 'x' plus hair space - ratings_char = u'\u2605' # filled star - empty_ratings_char = u'\u2606' # hollow star - read_char = u'\u2713' # check mark + missing_char = u'\u2715\u200a' # stylized 'x' plus hair space + ratings_char = u'\u2605' # filled star + empty_ratings_char = u'\u2606' # hollow star + read_char = u'\u2713' # check mark touchscreen = True # touchscreen_news_css {{{ touchscreen_news_css = u''' - /* hr used in articles */ - .article_articles_list { + /* hr used in articles */ + .article_articles_list { width:18%; - } + } .article_link { - color: #593f29; + color: #593f29; font-style: italic; } .article_next { - -webkit-border-top-right-radius:4px; - -webkit-border-bottom-right-radius:4px; + -webkit-border-top-right-radius:4px; + -webkit-border-bottom-right-radius:4px; font-style: italic; width:32%; } .article_prev { - -webkit-border-top-left-radius:4px; - -webkit-border-bottom-left-radius:4px; + -webkit-border-top-left-radius:4px; + -webkit-border-bottom-left-radius:4px; font-style: italic; width:32%; } - .article_sections_list { + .article_sections_list { width:18%; - } + } .articles_link { font-weight: bold; } @@ -334,8 +334,8 @@ class iPadOutput(OutputProfile): .caption_divider { - border:#ccc 1px solid; - } + border:#ccc 1px solid; + } .touchscreen_navbar { background:#c3bab2; @@ -344,6 +344,7 @@ class iPadOutput(OutputProfile): border-spacing:1px; margin-left: 5%; margin-right: 5%; + page-break-inside:avoid; width: 90%; -webkit-border-radius:4px; } @@ -356,50 +357,50 @@ class iPadOutput(OutputProfile): text-align:center; } - .touchscreen_navbar td a:link { - color: #593f29; - text-decoration: none; - } + .touchscreen_navbar td a:link { + color: #593f29; + text-decoration: none; + } - /* Index formatting */ - .publish_date { - text-align:center; - } - .divider { - border-bottom:1em solid white; - border-top:1px solid gray; - } + /* Index formatting */ + .publish_date { + text-align:center; + } + .divider { + border-bottom:1em solid white; + border-top:1px solid gray; + } - hr.caption_divider { - border-color:black; - border-style:solid; - border-width:1px; - } + hr.caption_divider { + border-color:black; + border-style:solid; + border-width:1px; + } /* Feed summary formatting */ .article_summary { - display:inline-block; - } + display:inline-block; + } .feed { font-family:sans-serif; font-weight:bold; font-size:larger; - } + } .feed_link { font-style: italic; } .feed_next { - -webkit-border-top-right-radius:4px; - -webkit-border-bottom-right-radius:4px; + -webkit-border-top-right-radius:4px; + -webkit-border-bottom-right-radius:4px; font-style: italic; width:40%; } .feed_prev { - -webkit-border-top-left-radius:4px; - -webkit-border-bottom-left-radius:4px; + -webkit-border-top-left-radius:4px; + -webkit-border-bottom-left-radius:4px; font-style: italic; width:40%; } @@ -409,24 +410,24 @@ class iPadOutput(OutputProfile): font-size: 160%; } - .feed_up { + .feed_up { font-weight: bold; width:20%; - } + } .summary_headline { font-weight:bold; text-align:left; - } + } .summary_byline { text-align:left; font-family:monospace; - } + } .summary_text { text-align:left; - } + } ''' # }}} @@ -470,8 +471,8 @@ class KoboReaderOutput(OutputProfile): description = _('This profile is intended for the Kobo Reader.') - screen_size = (540, 718) - comic_screen_size = (540, 718) + screen_size = (536, 710) + comic_screen_size = (536, 710) dpi = 168.451 fbase = 12 fsizes = [7.5, 9, 10, 12, 15.5, 20, 22, 24] @@ -616,8 +617,8 @@ class KindleOutput(OutputProfile): supports_mobi_indexing = True periodical_date_in_title = False - missing_char = u'x\u2009' - empty_ratings_char = u'\u2606' + missing_char = u'x\u2009' + empty_ratings_char = u'\u2606' ratings_char = u'\u2605' read_char = u'\u2713' @@ -641,8 +642,8 @@ class KindleDXOutput(OutputProfile): #comic_screen_size = (741, 1022) supports_mobi_indexing = True periodical_date_in_title = False - missing_char = u'x\u2009' - empty_ratings_char = u'\u2606' + missing_char = u'x\u2009' + empty_ratings_char = u'\u2606' ratings_char = u'\u2605' read_char = u'\u2713' mobi_ems_per_blockquote = 2.0 diff --git a/src/calibre/customize/ui.py b/src/calibre/customize/ui.py index 0f5508a89e..0a21b0b42e 100644 --- a/src/calibre/customize/ui.py +++ b/src/calibre/customize/ui.py @@ -2,33 +2,29 @@ from __future__ import with_statement __license__ = 'GPL v3' __copyright__ = '2008, Kovid Goyal <kovid at kovidgoyal.net>' -import os, shutil, traceback, functools, sys, re -from contextlib import closing +import os, shutil, traceback, functools, sys -from calibre.customize import Plugin, CatalogPlugin, FileTypePlugin, \ - MetadataReaderPlugin, MetadataWriterPlugin, \ - InterfaceActionBase as InterfaceAction, \ - PreferencesPlugin +from calibre.customize import (CatalogPlugin, FileTypePlugin, PluginNotFound, + MetadataReaderPlugin, MetadataWriterPlugin, + InterfaceActionBase as InterfaceAction, + PreferencesPlugin, platform, InvalidPlugin, + StoreBase as Store) from calibre.customize.conversion import InputFormatPlugin, OutputFormatPlugin +from calibre.customize.zipplugin import loader from calibre.customize.profiles import InputProfile, OutputProfile from calibre.customize.builtins import plugins as builtin_plugins -from calibre.constants import numeric_version as version, iswindows, isosx from calibre.devices.interface import DevicePlugin from calibre.ebooks.metadata import MetaInformation -from calibre.ebooks.metadata.covers import CoverDownload -from calibre.ebooks.metadata.fetch import MetadataSource -from calibre.utils.config import make_config_dir, Config, ConfigProxy, \ - plugin_dir, OptionParser, prefs +from calibre.utils.config import (make_config_dir, Config, ConfigProxy, + plugin_dir, OptionParser) from calibre.ebooks.epub.fix import ePubFixer from calibre.ebooks.metadata.sources.base import Source +from calibre.constants import DEBUG -platform = 'linux' -if iswindows: - platform = 'windows' -elif isosx: - platform = 'osx' +builtin_names = frozenset([p.name for p in builtin_plugins]) -from zipfile import ZipFile +class NameConflict(ValueError): + pass def _config(): c = Config('customize') @@ -42,11 +38,6 @@ def _config(): config = _config() -class InvalidPlugin(ValueError): - pass - -class PluginNotFound(ValueError): - pass def find_plugin(name): for plugin in _initialized_plugins: @@ -60,38 +51,7 @@ def load_plugin(path_to_zip_file): # {{{ :return: A :class:`Plugin` instance. ''' - #print 'Loading plugin from', path_to_zip_file - if not os.access(path_to_zip_file, os.R_OK): - raise PluginNotFound - with closing(ZipFile(path_to_zip_file)) as zf: - for name in zf.namelist(): - if name.lower().endswith('plugin.py'): - locals = {} - raw = zf.read(name) - lines, encoding = raw.splitlines(), 'utf-8' - cr = re.compile(r'coding[:=]\s*([-\w.]+)') - raw = [] - for l in lines[:2]: - match = cr.search(l) - if match is not None: - encoding = match.group(1) - else: - raw.append(l) - raw += lines[2:] - raw = '\n'.join(raw) - raw = raw.decode(encoding) - raw = re.sub('\r\n', '\n', raw) - exec raw in locals - for x in locals.values(): - if isinstance(x, type) and issubclass(x, Plugin) and \ - x.name != 'Trivial Plugin': - if x.minimum_calibre_version > version or \ - platform not in x.supported_platforms: - continue - - return x - - raise InvalidPlugin(_('No valid plugin found in ')+path_to_zip_file) + return loader.load(path_to_zip_file) # }}} @@ -120,9 +80,19 @@ def enable_plugin(plugin_or_name): ep.add(x) config['enabled_plugins'] = ep +def restore_plugin_state_to_default(plugin_or_name): + x = getattr(plugin_or_name, 'name', plugin_or_name) + dp = config['disabled_plugins'] + if x in dp: + dp.remove(x) + config['disabled_plugins'] = dp + ep = config['enabled_plugins'] + if x in ep: + ep.remove(x) + config['enabled_plugins'] = ep + default_disabled_plugins = set([ - 'Douban Books', 'Douban.com covers', 'Nicebooks', 'Nicebooks covers', - 'Kent District Library' + 'Overdrive', 'Douban Books', ]) def is_disabled(plugin): @@ -218,44 +188,6 @@ def output_profiles(): yield plugin # }}} -# Metadata sources {{{ -def metadata_sources(metadata_type='basic', customize=True, isbndb_key=None): - for plugin in _initialized_plugins: - if isinstance(plugin, MetadataSource) and \ - plugin.metadata_type == metadata_type: - if is_disabled(plugin): - continue - if customize: - customization = config['plugin_customization'] - plugin.site_customization = customization.get(plugin.name, None) - if plugin.name == 'IsbnDB' and isbndb_key is not None: - plugin.site_customization = isbndb_key - yield plugin - -def get_isbndb_key(): - return config['plugin_customization'].get('IsbnDB', None) - -def set_isbndb_key(key): - for plugin in _initialized_plugins: - if plugin.name == 'IsbnDB': - return customize_plugin(plugin, key) - -def migrate_isbndb_key(): - key = prefs['isbndb_com_key'] - if key: - prefs.set('isbndb_com_key', '') - set_isbndb_key(key) - -def cover_sources(): - customization = config['plugin_customization'] - for plugin in _initialized_plugins: - if isinstance(plugin, CoverDownload): - if not is_disabled(plugin): - plugin.site_customization = customization.get(plugin.name, '') - yield plugin - -# }}} - # Interface Actions # {{{ def interface_actions(): @@ -278,6 +210,34 @@ def preferences_plugins(): yield plugin # }}} +# Store Plugins # {{{ + +def store_plugins(): + customization = config['plugin_customization'] + for plugin in _initialized_plugins: + if isinstance(plugin, Store): + plugin.site_customization = customization.get(plugin.name, '') + yield plugin + +def available_store_plugins(): + for plugin in store_plugins(): + if not is_disabled(plugin): + yield plugin + +def stores(): + stores = set([]) + for plugin in store_plugins(): + stores.add(plugin.name) + return stores + +def available_stores(): + stores = set([]) + for plugin in available_store_plugins(): + stores.add(plugin.name) + return stores + +# }}} + # Metadata read/write {{{ _metadata_readers = {} _metadata_writers = {} @@ -377,6 +337,9 @@ def set_file_type_metadata(stream, mi, ftype): def add_plugin(path_to_zip_file): make_config_dir() plugin = load_plugin(path_to_zip_file) + if plugin.name in builtin_names: + raise NameConflict( + 'A builtin plugin with the name %r already exists' % plugin.name) plugin = initialize_plugin(plugin, path_to_zip_file) plugins = config['plugins'] zfp = os.path.join(plugin_dir, plugin.name+'.zip') @@ -498,12 +461,15 @@ def epub_fixers(): # Metadata sources2 {{{ def metadata_plugins(capabilities): capabilities = frozenset(capabilities) - for plugin in _initialized_plugins: - if isinstance(plugin, Source) and \ - plugin.capabilities.intersection(capabilities) and \ + for plugin in all_metadata_plugins(): + if plugin.capabilities.intersection(capabilities) and \ not is_disabled(plugin): yield plugin +def all_metadata_plugins(): + for plugin in _initialized_plugins: + if isinstance(plugin, Source): + yield plugin # }}} # Initialize plugins {{{ @@ -525,7 +491,11 @@ def initialize_plugin(plugin, path_to_zip_file): def initialize_plugins(): global _initialized_plugins _initialized_plugins = [] - for zfp in list(config['plugins'].values()) + builtin_plugins: + conflicts = [name for name in config['plugins'] if name in + builtin_names] + for p in conflicts: + remove_plugin(p) + for zfp in list(config['plugins'].itervalues()) + builtin_plugins: try: try: plugin = load_plugin(zfp) if not isinstance(zfp, type) else zfp @@ -534,8 +504,9 @@ def initialize_plugins(): plugin = initialize_plugin(plugin, None if isinstance(zfp, type) else zfp) _initialized_plugins.append(plugin) except: - print 'Failed to initialize plugin...' - traceback.print_exc() + print 'Failed to initialize plugin:', repr(zfp) + if DEBUG: + traceback.print_exc() _initialized_plugins.sort(cmp=lambda x,y:cmp(x.priority, y.priority), reverse=True) reread_filetype_plugins() reread_metadata_plugins() diff --git a/src/calibre/customize/zipplugin.py b/src/calibre/customize/zipplugin.py new file mode 100644 index 0000000000..ca07462b9c --- /dev/null +++ b/src/calibre/customize/zipplugin.py @@ -0,0 +1,284 @@ +#!/usr/bin/env python +# vim:fileencoding=UTF-8:ts=4:sw=4:sta:et:sts=4:ai +from __future__ import (unicode_literals, division, absolute_import, + print_function) +from future_builtins import map + +__license__ = 'GPL v3' +__copyright__ = '2011, Kovid Goyal <kovid@kovidgoyal.net>' +__docformat__ = 'restructuredtext en' + +import os, zipfile, posixpath, importlib, threading, re, imp, sys +from collections import OrderedDict +from functools import partial + +from calibre import as_unicode +from calibre.customize import (Plugin, numeric_version, platform, + InvalidPlugin, PluginNotFound) + +# PEP 302 based plugin loading mechanism, works around the bug in zipimport in +# python 2.x that prevents importing from zip files in locations whose paths +# have non ASCII characters + +def get_resources(zfp, name_or_list_of_names): + ''' + Load resources from the plugin zip file + + :param name_or_list_of_names: List of paths to resources in the zip file using / as + separator, or a single path + + :return: A dictionary of the form ``{name : file_contents}``. Any names + that were not found in the zip file will not be present in the + dictionary. If a single path is passed in the return value will + be just the bytes of the resource or None if it wasn't found. + ''' + names = name_or_list_of_names + if isinstance(names, basestring): + names = [names] + ans = {} + with zipfile.ZipFile(zfp) as zf: + for name in names: + try: + ans[name] = zf.read(name) + except: + import traceback + traceback.print_exc() + if len(names) == 1: + ans = ans.pop(names[0], None) + + return ans + +def get_icons(zfp, name_or_list_of_names): + ''' + Load icons from the plugin zip file + + :param name_or_list_of_names: List of paths to resources in the zip file using / as + separator, or a single path + + :return: A dictionary of the form ``{name : QIcon}``. Any names + that were not found in the zip file will be null QIcons. + If a single path is passed in the return value will + be A QIcon. + ''' + from PyQt4.Qt import QIcon, QPixmap + names = name_or_list_of_names + ans = get_resources(zfp, names) + if isinstance(names, basestring): + names = [names] + if ans is None: + ans = {} + if isinstance(ans, basestring): + ans = dict([(names[0], ans)]) + + ians = {} + for name in names: + p = QPixmap() + raw = ans.get(name, None) + if raw: + p.loadFromData(raw) + ians[name] = QIcon(p) + if len(names) == 1: + ians = ians.pop(names[0]) + return ians + +class PluginLoader(object): + + def __init__(self): + self.loaded_plugins = {} + self._lock = threading.RLock() + self._identifier_pat = re.compile(r'[a-zA-Z][_0-9a-zA-Z]*') + + def _get_actual_fullname(self, fullname): + parts = fullname.split('.') + if parts[0] == 'calibre_plugins': + if len(parts) == 1: + return parts[0], None + plugin_name = parts[1] + with self._lock: + names = self.loaded_plugins.get(plugin_name, None) + if names is None: + raise ImportError('No plugin named %r loaded'%plugin_name) + names = names[1] + fullname = '.'.join(parts[2:]) + if not fullname: + fullname = '__init__' + if fullname in names: + return fullname, plugin_name + if fullname+'.__init__' in names: + return fullname+'.__init__', plugin_name + return None, None + + def find_module(self, fullname, path=None): + fullname, plugin_name = self._get_actual_fullname(fullname) + if fullname is None and plugin_name is None: + return None + return self + + def load_module(self, fullname): + import_name, plugin_name = self._get_actual_fullname(fullname) + if import_name is None and plugin_name is None: + raise ImportError('No plugin named %r is loaded'%fullname) + mod = sys.modules.setdefault(fullname, imp.new_module(fullname)) + mod.__file__ = "<calibre Plugin Loader>" + mod.__loader__ = self + + if import_name.endswith('.__init__') or import_name in ('__init__', + 'calibre_plugins'): + # We have a package + mod.__path__ = [] + + if plugin_name is not None: + # We have some actual code to load + with self._lock: + zfp, names = self.loaded_plugins.get(plugin_name, (None, None)) + if names is None: + raise ImportError('No plugin named %r loaded'%plugin_name) + zinfo = names.get(import_name, None) + if zinfo is None: + raise ImportError('Plugin %r has no module named %r' % + (plugin_name, import_name)) + with zipfile.ZipFile(zfp) as zf: + try: + code = zf.read(zinfo) + except: + # Maybe the zip file changed from under us + code = zf.read(zinfo.filename) + compiled = compile(code, 'calibre_plugins.%s.%s'%(plugin_name, + import_name), 'exec', dont_inherit=True) + mod.__dict__['get_resources'] = partial(get_resources, zfp) + mod.__dict__['get_icons'] = partial(get_icons, zfp) + exec compiled in mod.__dict__ + + return mod + + + def load(self, path_to_zip_file): + if not os.access(path_to_zip_file, os.R_OK): + raise PluginNotFound('Cannot access %r'%path_to_zip_file) + + with zipfile.ZipFile(path_to_zip_file) as zf: + plugin_name = self._locate_code(zf, path_to_zip_file) + + try: + ans = None + plugin_module = 'calibre_plugins.%s'%plugin_name + m = sys.modules.get(plugin_module, None) + if m is not None: + reload(m) + else: + m = importlib.import_module(plugin_module) + for obj in m.__dict__.itervalues(): + if isinstance(obj, type) and issubclass(obj, Plugin) and \ + obj.name != 'Trivial Plugin': + ans = obj + break + if ans is None: + raise InvalidPlugin('No plugin class found in %s:%s'%( + as_unicode(path_to_zip_file), plugin_name)) + + if ans.minimum_calibre_version > numeric_version: + raise InvalidPlugin( + 'The plugin at %s needs a version of calibre >= %s' % + (as_unicode(path_to_zip_file), '.'.join(map(unicode, + ans.minimum_calibre_version)))) + + if platform not in ans.supported_platforms: + raise InvalidPlugin( + 'The plugin at %s cannot be used on %s' % + (as_unicode(path_to_zip_file), platform)) + + return ans + except: + with self._lock: + del self.loaded_plugins[plugin_name] + raise + + + def _locate_code(self, zf, path_to_zip_file): + names = [x if isinstance(x, unicode) else x.decode('utf-8') for x in + zf.namelist()] + names = [x[1:] if x[0] == '/' else x for x in names] + + plugin_name = None + for name in names: + name, ext = posixpath.splitext(name) + if name.startswith('plugin-import-name-') and ext == '.txt': + plugin_name = name.rpartition('-')[-1] + + if plugin_name is None: + c = 0 + while True: + c += 1 + plugin_name = 'dummy%d'%c + if plugin_name not in self.loaded_plugins: + break + else: + if self._identifier_pat.match(plugin_name) is None: + raise InvalidPlugin(( + 'The plugin at %r uses an invalid import name: %r' % + (path_to_zip_file, plugin_name))) + + pynames = [x for x in names if x.endswith('.py')] + + candidates = [posixpath.dirname(x) for x in pynames if + x.endswith('/__init__.py')] + candidates.sort(key=lambda x: x.count('/')) + valid_packages = set() + + for candidate in candidates: + parts = candidate.split('/') + parent = '.'.join(parts[:-1]) + if parent and parent not in valid_packages: + continue + valid_packages.add('.'.join(parts)) + + names = OrderedDict() + + for candidate in pynames: + parts = posixpath.splitext(candidate)[0].split('/') + package = '.'.join(parts[:-1]) + if package and package not in valid_packages: + continue + name = '.'.join(parts) + names[name] = zf.getinfo(candidate) + + # Legacy plugins + if '__init__' not in names: + for name in list(names.iterkeys()): + if '.' not in name and name.endswith('plugin'): + names['__init__'] = names[name] + break + + if '__init__' not in names: + raise InvalidPlugin(('The plugin in %r is invalid. It does not ' + 'contain a top-level __init__.py file') + % path_to_zip_file) + + with self._lock: + self.loaded_plugins[plugin_name] = (path_to_zip_file, names) + + return plugin_name + + +loader = PluginLoader() +sys.meta_path.insert(0, loader) + + +if __name__ == '__main__': + from tempfile import NamedTemporaryFile + from calibre.customize.ui import add_plugin + from calibre import CurrentDir + path = sys.argv[-1] + with NamedTemporaryFile(suffix='.zip') as f: + with zipfile.ZipFile(f, 'w') as zf: + with CurrentDir(path): + for x in os.listdir('.'): + if x[0] != '.': + print ('Adding', x) + zf.write(x) + if os.path.isdir(x): + for y in os.listdir(x): + zf.write(os.path.join(x, y)) + add_plugin(f.name) + print ('Added plugin from', sys.argv[-1]) + diff --git a/src/calibre/debug.py b/src/calibre/debug.py index 3a080fc57b..8d65c37bbf 100644 --- a/src/calibre/debug.py +++ b/src/calibre/debug.py @@ -51,6 +51,8 @@ Run an embedded python interpreter. 'with sqlite3 works.') parser.add_option('-p', '--py-console', help='Run python console', default=False, action='store_true') + parser.add_option('-m', '--inspect-mobi', + help='Inspect the MOBI file at the specified path', default=None) return parser @@ -104,7 +106,7 @@ def migrate(old, new): from calibre.library.database import LibraryDatabase from calibre.library.database2 import LibraryDatabase2 from calibre.utils.terminfo import ProgressBar - from calibre import terminal_controller + from calibre.constants import terminal_controller class Dummy(ProgressBar): def setLabelText(self, x): pass def setAutoReset(self, y): pass @@ -117,7 +119,7 @@ def migrate(old, new): db = LibraryDatabase(old) db2 = LibraryDatabase2(new) - db2.migrate_old(db, Dummy(terminal_controller, 'Migrating database...')) + db2.migrate_old(db, Dummy(terminal_controller(), 'Migrating database...')) prefs['library_path'] = os.path.abspath(new) print 'Database migrated to', os.path.abspath(new) @@ -227,6 +229,9 @@ def main(args=sys.argv): if len(args) > 1 and os.access(args[-1], os.R_OK): sql_dump = args[-1] reinit_db(opts.reinitialize_db, sql_dump=sql_dump) + elif opts.inspect_mobi is not None: + from calibre.ebooks.mobi.debug import inspect_mobi + inspect_mobi(opts.inspect_mobi) else: from calibre import ipython ipython() diff --git a/src/calibre/devices/__init__.py b/src/calibre/devices/__init__.py index 1918a36cc8..e47cd82b50 100644 --- a/src/calibre/devices/__init__.py +++ b/src/calibre/devices/__init__.py @@ -47,7 +47,7 @@ def get_connected_device(): for d in connected_devices: try: - d.open() + d.open(None) except: continue else: @@ -121,7 +121,7 @@ def debug(ioreg_to_tmp=False, buf=None): out('Trying to open', dev.name, '...', end=' ') try: dev.reset(detected_device=det) - dev.open() + dev.open(None) out('OK') except: import traceback @@ -156,3 +156,60 @@ def debug(ioreg_to_tmp=False, buf=None): sys.stdout = oldo sys.stderr = olde +def device_info(ioreg_to_tmp=False, buf=None): + from calibre.devices.scanner import DeviceScanner, win_pnp_drives + from calibre.constants import iswindows + import re + + res = {} + device_details = {} + device_set = set() + drive_details = {} + drive_set = set() + res['device_set'] = device_set + res['device_details'] = device_details + res['drive_details'] = drive_details + res['drive_set'] = drive_set + + try: + s = DeviceScanner() + s.scan() + devices = (s.devices) + if not iswindows: + devices = [list(x) for x in devices] + for dev in devices: + for i in range(3): + dev[i] = hex(dev[i]) + d = dev[0] + dev[1] + dev[2] + device_set.add(d) + device_details[d] = dev[0:3] + else: + for dev in devices: + vid = re.search('vid_([0-9a-f]*)&', dev) + if vid: + vid = vid.group(1) + pid = re.search('pid_([0-9a-f]*)&', dev) + if pid: + pid = pid.group(1) + rev = re.search('rev_([0-9a-f]*)$', dev) + if rev: + rev = rev.group(1) + d = vid+pid+rev + device_set.add(d) + device_details[d] = (vid, pid, rev) + + drives = win_pnp_drives(debug=False) + for drive,details in drives.iteritems(): + order = 'ORD_' + str(drive.order) + ven = re.search('VEN_([^&]*)&', details) + if ven: + ven = ven.group(1) + prod = re.search('PROD_([^&]*)&', details) + if prod: + prod = prod.group(1) + d = (order, ven, prod) + drive_details[drive] = d + drive_set.add(drive) + finally: + pass + return res diff --git a/src/calibre/devices/android/driver.py b/src/calibre/devices/android/driver.py index 3724f02ca2..3c6ea243e2 100644 --- a/src/calibre/devices/android/driver.py +++ b/src/calibre/devices/android/driver.py @@ -26,7 +26,9 @@ class ANDROID(USBMS): 0xc92 : [0x100], 0xc97 : [0x226], 0xc99 : [0x0100], + 0xca2 : [0x226], 0xca3 : [0x100], + 0xca4 : [0x226], }, # Eken @@ -35,7 +37,9 @@ class ANDROID(USBMS): # Motorola 0x22b8 : { 0x41d9 : [0x216], 0x2d61 : [0x100], 0x2d67 : [0x100], 0x41db : [0x216], 0x4285 : [0x216], 0x42a3 : [0x216], - 0x4286 : [0x216], 0x42b3 : [0x216] }, + 0x4286 : [0x216], 0x42b3 : [0x216], 0x42b4 : [0x216], + 0x7086 : [0x0226], 0x70a8: [0x9999], + }, # Sony Ericsson 0xfce : { 0xd12e : [0x0100]}, @@ -48,21 +52,27 @@ class ANDROID(USBMS): 0x04e8 : { 0x681d : [0x0222, 0x0223, 0x0224, 0x0400], 0x681c : [0x0222, 0x0224, 0x0400], 0x6640 : [0x0100], + 0x6877 : [0x0400], }, + # Viewsonic + 0x0489 : { 0xc001 : [0x0226], 0xc004 : [0x0226], }, + # Acer - 0x502 : { 0x3203 : [0x0100]}, + 0x502 : { 0x3203 : [0x0100, 0x224]}, # Dell - 0x413c : { 0xb007 : [0x0100, 0x0224]}, + 0x413c : { 0xb007 : [0x0100, 0x0224, 0x0226]}, # LG - 0x1004 : { 0x61cc : [0x100] }, + 0x1004 : { 0x61cc : [0x100], 0x61ce : [0x100], 0x618e : [0x226] }, # Archos 0x0e79 : { 0x1400 : [0x0222, 0x0216], 0x1408 : [0x0222, 0x0216], + 0x1411 : [0x216], + 0x1417 : [0x0216], 0x1419 : [0x0216], 0x1420 : [0x0216], 0x1422 : [0x0216] @@ -78,6 +88,9 @@ class ANDROID(USBMS): # Xperia 0x13d3 : { 0x3304 : [0x0001, 0x0002] }, + # CREEL?? Also Nextbook + 0x5e3 : { 0x726 : [0x222] }, + } EBOOK_DIR_MAIN = ['eBooks/import', 'wordplayer/calibretransfer', 'Books'] EXTRA_CUSTOMIZATION_MESSAGE = _('Comma separated list of directories to ' @@ -87,16 +100,20 @@ class ANDROID(USBMS): VENDOR_NAME = ['HTC', 'MOTOROLA', 'GOOGLE_', 'ANDROID', 'ACER', 'GT-I5700', 'SAMSUNG', 'DELL', 'LINUX', 'GOOGLE', 'ARCHOS', - 'TELECHIP', 'HUAWEI', 'T-MOBILE', 'SEMC'] + 'TELECHIP', 'HUAWEI', 'T-MOBILE', 'SEMC', 'LGE', 'NVIDIA', + 'GENERIC-'] 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', 'SAMSUNG_ANDROID', 'SCH-I500_CARD', 'SPH-D700_CARD', 'MB810', 'GT-P1000', 'DESIRE', 'SGH-T849', '_MB300', 'A70S', 'S_ANDROID', 'A101IT', 'A70H', - 'IDEOS_TABLET', 'MYTOUCH_4G', 'UMS_COMPOSITE', 'SCH-I800_CARD', '7'] + 'IDEOS_TABLET', 'MYTOUCH_4G', 'UMS_COMPOSITE', 'SCH-I800_CARD', + '7', 'A956', 'A955', 'A43', 'ANDROID_PLATFORM', 'TEGRA_2', + 'MB860', 'MULTI-CARD', 'MID7015A', 'INCREDIBLE', 'A7EB', 'STREAK', + 'MB525'] WINDOWS_CARD_A_MEM = ['ANDROID_PHONE', 'GT-I9000_CARD', 'SGH-I897', 'FILE-STOR_GADGET', 'SGH-T959', 'SAMSUNG_ANDROID', 'GT-P1000_CARD', - 'A70S', 'A101IT', '7'] + 'A70S', 'A101IT', '7', 'INCREDIBLE', 'A7EB', 'SGH-T849_CARD'] OSX_MAIN_MEM = 'Android Device Main Memory' diff --git a/src/calibre/devices/apple/driver.py b/src/calibre/devices/apple/driver.py index aaa9382612..42949215b2 100644 --- a/src/calibre/devices/apple/driver.py +++ b/src/calibre/devices/apple/driver.py @@ -7,9 +7,9 @@ __docformat__ = 'restructuredtext en' import cStringIO, ctypes, datetime, os, re, shutil, subprocess, sys, tempfile, time from calibre.constants import __appname__, __version__, DEBUG -from calibre import fit_image +from calibre import fit_image, confirm_config_name from calibre.constants import isosx, iswindows -from calibre.devices.errors import UserFeedback +from calibre.devices.errors import OpenFeedback, UserFeedback from calibre.devices.usbms.deviceconfig import DeviceConfig from calibre.devices.interface import DevicePlugin from calibre.ebooks.BeautifulSoup import BeautifulSoup @@ -18,11 +18,77 @@ from calibre.ebooks.metadata import authors_to_string, MetaInformation, \ from calibre.ebooks.metadata.book.base import Metadata from calibre.ebooks.metadata.epub import set_metadata from calibre.library.server.utils import strftime -from calibre.utils.config import config_dir, prefs +from calibre.utils.config import config_dir, dynamic, prefs from calibre.utils.date import now, parse_date from calibre.utils.logging import Log from calibre.utils.zipfile import ZipFile + +class AppleOpenFeedback(OpenFeedback): + + def __init__(self, plugin): + OpenFeedback.__init__(self, u'') + self.log = plugin.log + self.plugin = plugin + + def custom_dialog(self, parent): + from PyQt4.Qt import (QDialog, QDialogButtonBox, QIcon, + QLabel, QPushButton, QVBoxLayout) + + class Dialog(QDialog): + + def __init__(self, p, cd, pixmap='dialog_information.png'): + QDialog.__init__(self, p) + self.cd = cd + self.setWindowTitle("Apple iDevice detected") + self.l = l = QVBoxLayout() + self.setLayout(l) + msg = QLabel() + msg.setText(_( + '<p>If you do not want calibre to recognize your Apple iDevice ' + 'when it is connected to your computer, ' + 'click <b>Disable Apple Driver</b>.</p>' + '<p>To transfer books to your iDevice, ' + 'click <b>Disable Apple Driver</b>, ' + "then use the 'Connect to iTunes' method recommended in the " + '<a href="http://www.mobileread.com/forums/showthread.php?t=118559">Calibre + iDevices FAQ</a>, ' + 'using the <em>Connect/Share</em>|<em>Connect to iTunes</em> menu item.</p>' + '<p>Enabling the Apple driver for direct connection to iDevices ' + 'is an unsupported advanced user mode.</p>' + '<p></p>' + )) + msg.setOpenExternalLinks(True) + msg.setWordWrap(True) + l.addWidget(msg) + + self.bb = QDialogButtonBox() + disable_driver = QPushButton(_("Disable Apple driver")) + disable_driver.setDefault(True) + self.bb.addButton(disable_driver, QDialogButtonBox.RejectRole) + + enable_driver = QPushButton(_("Enable Apple driver")) + self.bb.addButton(enable_driver, QDialogButtonBox.AcceptRole) + l.addWidget(self.bb) + self.bb.accepted.connect(self.accept) + self.bb.rejected.connect(self.reject) + + self.setWindowIcon(QIcon(I(pixmap))) + self.resize(self.sizeHint()) + + self.finished.connect(self.do_it) + + def do_it(self, return_code): + if return_code == self.Accepted: + self.cd.log.info(" Apple driver ENABLED") + dynamic[confirm_config_name(self.cd.plugin.DISPLAY_DISABLE_DIALOG)] = False + else: + from calibre.customize.ui import disable_plugin + self.cd.log.info(" Apple driver DISABLED") + disable_plugin(self.cd.plugin) + + return Dialog(parent, self) + + from PIL import Image as PILImage from lxml import etree @@ -41,7 +107,25 @@ class DriverBase(DeviceConfig, DevicePlugin): # Needed for config_widget to work FORMATS = ['epub', 'pdf'] USER_CAN_ADD_NEW_FORMATS = False - SUPPORTS_SUB_DIRS = True # To enable second checkbox in customize widget + + # Hide the standard customization widgets + SUPPORTS_SUB_DIRS = False + MUST_READ_METADATA = True + SUPPORTS_USE_AUTHOR_SORT = False + + EXTRA_CUSTOMIZATION_MESSAGE = [ + _('Use Series as Category in iTunes/iBooks') + + ':::'+_('Enable to use the series name as the iTunes Genre, ' + 'iBooks Category'), + _('Cache covers from iTunes/iBooks') + + ':::' + + _('Enable to cache and display covers from iTunes/iBooks') + ] + EXTRA_CUSTOMIZATION_DEFAULT = [ + True, + True, + ] + @classmethod def _config_base_name(cls): @@ -79,6 +163,8 @@ class ITUNES(DriverBase): settings() set_progress_reporter() upload_books() + _get_fpath() + _update_epub_metadata() add_books_to_metadata() use_plugboard_ext() set_plugboard() @@ -95,7 +181,13 @@ class ITUNES(DriverBase): supported_platforms = ['osx','windows'] author = 'GRiker' #: The version of this plugin as a 3-tuple (major, minor, revision) - version = (0,9,0) + version = (1,0,0) + + DISPLAY_DISABLE_DIALOG = "display_disable_apple_driver_dialog" + + # EXTRA_CUSTOMIZATION_MESSAGE indexes + USE_SERIES_AS_CATEGORY = 0 + CACHE_COVERS = 1 OPEN_FEEDBACK_MESSAGE = _( 'Apple device detected, launching iTunes, please wait ...') @@ -111,8 +203,11 @@ class ITUNES(DriverBase): # 0x1294 iPhone 3GS # 0x1297 iPhone 4 # 0x129a iPad + # 0x129f iPad2 (WiFi) + # 0x12a2 iPad2 (GSM) + # 0x12a3 iPad2 (CDMA) VENDOR_ID = [0x05ac] - PRODUCT_ID = [0x1292,0x1293,0x1294,0x1297,0x1299,0x129a] + PRODUCT_ID = [0x1292,0x1293,0x1294,0x1297,0x1299,0x129a,0x129f,0x12a2,0x12a3] BCD = [0x01] # Plugboard ID @@ -259,7 +354,7 @@ class ITUNES(DriverBase): break break if self.report_progress is not None: - self.report_progress(j+1/task_count, _('Updating device metadata listing...')) + self.report_progress((j+1)/task_count, _('Updating device metadata listing...')) if self.report_progress is not None: self.report_progress(1.0, _('Updating device metadata listing...')) @@ -295,7 +390,7 @@ class ITUNES(DriverBase): if not oncard: if DEBUG: self.log.info("ITUNES:books():") - if self.settings().use_subdirs: + if self.settings().extra_customization[self.CACHE_COVERS]: self.log.info(" Cover fetching/caching enabled") else: self.log.info(" Cover fetching/caching disabled") @@ -331,14 +426,14 @@ class ITUNES(DriverBase): cached_books[this_book.path] = { 'title':book.name(), - 'author':[book.artist()], + 'author':book.artist().split(' & '), 'lib_book':library_books[this_book.path] if this_book.path in library_books else None, 'dev_book':book, 'uuid': book.composer() } if self.report_progress is not None: - self.report_progress(i+1/book_count, _('%d of %d') % (i+1, book_count)) + self.report_progress((i+1)/book_count, _('%d of %d') % (i+1, book_count)) self._purge_orphans(library_books, cached_books) elif iswindows: @@ -369,14 +464,14 @@ class ITUNES(DriverBase): cached_books[this_book.path] = { 'title':book.Name, - 'author':book.Artist, + 'author':book.Artist.split(' & '), 'lib_book':library_books[this_book.path] if this_book.path in library_books else None, 'uuid': book.Composer, 'format': 'pdf' if book.KindAsString.startswith('PDF') else 'epub' } if self.report_progress is not None: - self.report_progress(i+1/book_count, + self.report_progress((i+1)/book_count, _('%d of %d') % (i+1, book_count)) self._purge_orphans(library_books, cached_books) @@ -413,7 +508,7 @@ class ITUNES(DriverBase): if self.iTunes: # Check for connected book-capable device self.sources = self._get_sources() - if 'iPod' in self.sources: + if 'iPod' in self.sources and not self.ejected: #if DEBUG: #sys.stdout.write('.') #sys.stdout.flush() @@ -558,10 +653,6 @@ class ITUNES(DriverBase): # Turn off the Save template cw.opt_save_template.setVisible(False) cw.label.setVisible(False) - # Repurpose the metadata checkbox - cw.opt_read_metadata.setText(_("Use Series as Category in iTunes/iBooks")) - # Repurpose the use_subdirs checkbox - cw.opt_use_subdirs.setText(_("Cache covers from iTunes/iBooks")) return cw def delete_books(self, paths, end_session=True): @@ -701,7 +792,7 @@ class ITUNES(DriverBase): self.log.info("ITUNES.get_file(): exporting '%s'" % path) outfile.write(open(self.cached_books[path]['lib_book'].location().path).read()) - def open(self): + def open(self, library_uuid): ''' Perform any device specific initialization. Called after the device is detected but before any other functions that communicate with the device. @@ -715,9 +806,18 @@ class ITUNES(DriverBase): Note that most of the initialization is necessarily performed in can_handle(), as we need to talk to iTunes to discover if there's a connected iPod ''' + if DEBUG: self.log.info("ITUNES.open()") + # Display a dialog recommending using 'Connect to iTunes' if user hasn't + # previously disabled the dialog + if dynamic.get(confirm_config_name(self.DISPLAY_DISABLE_DIALOG),True): + raise AppleOpenFeedback(self) + else: + if DEBUG: + self.log.info(" advanced user mode, directly connecting to iDevice") + # Confirm/create thumbs archive if not os.path.exists(self.cache_dir): if DEBUG: @@ -821,6 +921,8 @@ class ITUNES(DriverBase): """ if DEBUG: self.log.info("ITUNES.reset()") + if report_progress: + self.set_progress_reporter(report_progress) def set_progress_reporter(self, report_progress): ''' @@ -829,6 +931,9 @@ class ITUNES(DriverBase): If it is called with -1 that means that the task does not have any progress information ''' + if DEBUG: + self.log.info("ITUNES.set_progress_reporter()") + self.report_progress = report_progress def set_plugboards(self, plugboards, pb_func): @@ -836,7 +941,7 @@ class ITUNES(DriverBase): # declared in use_plugboard_ext and a device name of ITUNES if DEBUG: self.log.info("ITUNES.set_plugboard()") - #self.log.info(' using plugboard %s' % plugboards) + #self.log.info(' plugboard: %s' % plugboards) self.plugboards = plugboards self.plugboard_func = pb_func @@ -921,7 +1026,9 @@ class ITUNES(DriverBase): if isosx: for (i,file) in enumerate(files): format = file.rpartition('.')[2].lower() - path = self.path_template % (metadata[i].title, metadata[i].author[0],format) + path = self.path_template % (metadata[i].title, + authors_to_string(metadata[i].authors), + format) self._remove_existing_copy(path, metadata[i]) fpath = self._get_fpath(file, metadata[i], format, update_md=True) db_added, lb_added = self._add_new_copy(fpath, metadata[i]) @@ -934,19 +1041,20 @@ class ITUNES(DriverBase): if DEBUG: self.log.info("ITUNES.upload_books()") self.log.info(" adding '%s' by '%s' uuid:%s to self.cached_books" % - ( metadata[i].title, metadata[i].author, metadata[i].uuid)) + (metadata[i].title, + authors_to_string(metadata[i].authors), + metadata[i].uuid)) self.cached_books[this_book.path] = { - 'author': metadata[i].author, + 'author': authors_to_string(metadata[i].authors), 'dev_book': db_added, 'format': format, 'lib_book': lb_added, 'title': metadata[i].title, 'uuid': metadata[i].uuid } - # Report progress if self.report_progress is not None: - self.report_progress(i+1/file_count, _('%d of %d') % (i+1, file_count)) + self.report_progress((i+1)/file_count, _('%d of %d') % (i+1, file_count)) elif iswindows: try: @@ -955,7 +1063,9 @@ class ITUNES(DriverBase): for (i,file) in enumerate(files): format = file.rpartition('.')[2].lower() - path = self.path_template % (metadata[i].title, metadata[i].author[0],format) + path = self.path_template % (metadata[i].title, + authors_to_string(metadata[i].authors), + format) self._remove_existing_copy(path, metadata[i]) fpath = self._get_fpath(file, metadata[i],format, update_md=True) db_added, lb_added = self._add_new_copy(fpath, metadata[i]) @@ -975,9 +1085,11 @@ class ITUNES(DriverBase): if DEBUG: self.log.info("ITUNES.upload_books()") self.log.info(" adding '%s' by '%s' uuid:%s to self.cached_books" % - ( metadata[i].title, metadata[i].author, metadata[i].uuid)) + (metadata[i].title, + authors_to_string(metadata[i].authors), + metadata[i].uuid)) self.cached_books[this_book.path] = { - 'author': metadata[i].author[0], + 'author': authors_to_string(metadata[i].authors), 'dev_book': db_added, 'format': format, 'lib_book': lb_added, @@ -986,7 +1098,7 @@ class ITUNES(DriverBase): # Report progress if self.report_progress is not None: - self.report_progress(i+1/file_count, _('%d of %d') % (i+1, file_count)) + self.report_progress((i+1)/file_count, _('%d of %d') % (i+1, file_count)) finally: pythoncom.CoUninitialize() @@ -1090,7 +1202,7 @@ class ITUNES(DriverBase): base_fn = base_fn.rpartition('.')[0] db_added = self._find_device_book( { 'title': base_fn if format == 'pdf' else metadata.title, - 'author': metadata.authors[0], + 'author': authors_to_string(metadata.authors), 'uuid': metadata.uuid, 'format': format}) return db_added @@ -1155,7 +1267,7 @@ class ITUNES(DriverBase): base_fn = base_fn.rpartition('.')[0] added = self._find_library_book( { 'title': base_fn if format == 'pdf' else metadata.title, - 'author': metadata.author[0], + 'author': authors_to_string(metadata.authors), 'uuid': metadata.uuid, 'format': format}) return added @@ -1214,7 +1326,7 @@ class ITUNES(DriverBase): with open(metadata.cover,'r+b') as cd: cover_data = cd.read() except: - self.problem_titles.append("'%s' by %s" % (metadata.title, metadata.author[0])) + self.problem_titles.append("'%s' by %s" % (metadata.title, authors_to_string(metadata.authors))) self.log.error(" error scaling '%s' for '%s'" % (metadata.cover,metadata.title)) import traceback @@ -1289,7 +1401,7 @@ class ITUNES(DriverBase): thumb_path = path.rpartition('.')[0] + '.jpg' zfw.writestr(thumb_path, thumb) except: - self.problem_titles.append("'%s' by %s" % (metadata.title, metadata.author[0])) + self.problem_titles.append("'%s' by %s" % (metadata.title, authors_to_string(metadata.authors))) self.log.error(" error converting '%s' to thumb for '%s'" % (metadata.cover,metadata.title)) finally: try: @@ -1307,7 +1419,7 @@ class ITUNES(DriverBase): if DEBUG: self.log.info(" ITUNES._create_new_book()") - this_book = Book(metadata.title, authors_to_string(metadata.author)) + this_book = Book(metadata.title, authors_to_string(metadata.authors)) this_book.datetime = time.gmtime() this_book.db_id = None this_book.device_collections = [] @@ -1787,9 +1899,7 @@ class ITUNES(DriverBase): as of iTunes 9.2, iBooks 1.1, can't set artwork for PDF files via automation ''' - # self.settings().use_subdirs is a repurposed DeviceConfig field - # We're using it to skip fetching/caching covers to speed things up - if not self.settings().use_subdirs: + if not self.settings().extra_customization[self.CACHE_COVERS]: thumb_data = None return thumb_data @@ -1927,16 +2037,17 @@ class ITUNES(DriverBase): if 'iPod' in self.sources: connected_device = self.sources['iPod'] device = self.iTunes.sources[connected_device] + dev_books = None for pl in device.playlists(): if pl.special_kind() == appscript.k.Books: if DEBUG: self.log.info(" Book playlist: '%s'" % (pl.name())) - books = pl.file_tracks() + dev_books = pl.file_tracks() break else: self.log.error(" book_playlist not found") - for book in books: + for book in dev_books: # This may need additional entries for international iTunes users if book.kind() in self.Audiobooks: if DEBUG: @@ -2353,7 +2464,7 @@ class ITUNES(DriverBase): for book in self.cached_books: if self.cached_books[book]['uuid'] == metadata.uuid or \ (self.cached_books[book]['title'] == metadata.title and \ - self.cached_books[book]['author'] == metadata.authors[0]): + self.cached_books[book]['author'] == authors_to_string(metadata.authors)): self.update_list.append(self.cached_books[book]) self._remove_from_device(self.cached_books[book]) if DEBUG: @@ -2372,7 +2483,7 @@ class ITUNES(DriverBase): for book in self.cached_books: if self.cached_books[book]['uuid'] == metadata.uuid or \ (self.cached_books[book]['title'] == metadata.title and \ - self.cached_books[book]['author'] == metadata.authors[0]): + self.cached_books[book]['author'] == authors_to_string(metadata.authors)): self.update_list.append(self.cached_books[book]) self._remove_from_iTunes(self.cached_books[book]) if DEBUG: @@ -2512,39 +2623,44 @@ class ITUNES(DriverBase): # Refresh epub metadata with open(fpath,'r+b') as zfo: # Touch the OPF timestamp - zf_opf = ZipFile(fpath,'r') - fnames = zf_opf.namelist() - opf = [x for x in fnames if '.opf' in x][0] - if opf: - opf_tree = etree.fromstring(zf_opf.read(opf)) - md_els = opf_tree.xpath('.//*[local-name()="metadata"]') - if md_els: - ts = md_els[0].find('.//*[@name="calibre:timestamp"]') - if ts is not None: - timestamp = ts.get('content') - old_ts = parse_date(timestamp) - metadata.timestamp = datetime.datetime(old_ts.year, old_ts.month, old_ts.day, old_ts.hour, - old_ts.minute, old_ts.second, old_ts.microsecond+1, old_ts.tzinfo) - if DEBUG: - self.log.info(" existing timestamp: %s" % metadata.timestamp) - else: - metadata.timestamp = now() - if DEBUG: - self.log.info(" add timestamp: %s" % metadata.timestamp) + try: + zf_opf = ZipFile(fpath,'r') + fnames = zf_opf.namelist() + opf = [x for x in fnames if '.opf' in x][0] + except: + raise UserFeedback("'%s' is not a valid EPUB" % metadata.title, + None, + level=UserFeedback.WARN) + + opf_tree = etree.fromstring(zf_opf.read(opf)) + md_els = opf_tree.xpath('.//*[local-name()="metadata"]') + if md_els: + ts = md_els[0].find('.//*[@name="calibre:timestamp"]') + if ts is not None: + timestamp = ts.get('content') + old_ts = parse_date(timestamp) + metadata.timestamp = datetime.datetime(old_ts.year, old_ts.month, old_ts.day, old_ts.hour, + old_ts.minute, old_ts.second, old_ts.microsecond+1, old_ts.tzinfo) + if DEBUG: + self.log.info(" existing timestamp: %s" % metadata.timestamp) else: metadata.timestamp = now() if DEBUG: - self.log.warning(" missing <metadata> block in OPF file") self.log.info(" add timestamp: %s" % metadata.timestamp) - # Force the language declaration for iBooks 1.1 - #metadata.language = get_lang().replace('_', '-') - - # Updates from metadata plugboard (ignoring publisher) - metadata.language = metadata_x.language - + else: + metadata.timestamp = now() if DEBUG: - if metadata.language != metadata_x.language: - self.log.info(" rewriting language: <dc:language>%s</dc:language>" % metadata.language) + self.log.warning(" missing <metadata> block in OPF file") + self.log.info(" add timestamp: %s" % metadata.timestamp) + # Force the language declaration for iBooks 1.1 + #metadata.language = get_lang().replace('_', '-') + + # Updates from metadata plugboard (ignoring publisher) + metadata.language = metadata_x.language + + if DEBUG: + if metadata.language != metadata_x.language: + self.log.info(" rewriting language: <dc:language>%s</dc:language>" % metadata.language) zf_opf.close() @@ -2627,7 +2743,7 @@ class ITUNES(DriverBase): # Update metadata from plugboard # If self.plugboard is None (no transforms), original metadata is returned intact metadata_x = self._xform_metadata_via_plugboard(metadata, this_book.format) - + self.log("metadata.title_sort: %s metadata_x.title_sort: %s" % (metadata.title_sort, metadata_x.title_sort)) if isosx: if lb_added: lb_added.name.set(metadata_x.title) @@ -2637,8 +2753,7 @@ class ITUNES(DriverBase): lb_added.description.set("%s %s" % (self.description_prefix,strftime('%Y-%m-%d %H:%M:%S'))) lb_added.enabled.set(True) lb_added.sort_artist.set(icu_title(metadata_x.author_sort)) - lb_added.sort_name.set(metadata.title_sort) - + lb_added.sort_name.set(metadata_x.title_sort) if db_added: db_added.name.set(metadata_x.title) @@ -2648,7 +2763,7 @@ class ITUNES(DriverBase): db_added.description.set("%s %s" % (self.description_prefix,strftime('%Y-%m-%d %H:%M:%S'))) db_added.enabled.set(True) db_added.sort_artist.set(icu_title(metadata_x.author_sort)) - db_added.sort_name.set(metadata.title_sort) + db_added.sort_name.set(metadata_x.title_sort) if metadata_x.comments: if lb_added: @@ -2668,8 +2783,8 @@ class ITUNES(DriverBase): # Set genre from series if available, else first alpha tag # Otherwise iTunes grabs the first dc:subject from the opf metadata - # self.settings().read_metadata is used as a surrogate for "Use Series name as Genre" - if metadata_x.series and self.settings().read_metadata: + # If title_sort applied in plugboard, that overrides using series/index as title_sort + if metadata_x.series and self.settings().extra_customization[self.USE_SERIES_AS_CATEGORY]: if DEBUG: self.log.info(" ITUNES._update_iTunes_metadata()") self.log.info(" using Series name as Genre") @@ -2680,7 +2795,9 @@ class ITUNES(DriverBase): fraction = index-integer series_index = '%04d%s' % (integer, str('%0.4f' % fraction).lstrip('0')) if lb_added: - lb_added.sort_name.set("%s %s" % (self.title_sorter(metadata_x.series), series_index)) + # If no title_sort plugboard tweak, create sort_name from series/index + if metadata.title_sort == metadata_x.title_sort: + lb_added.sort_name.set("%s %s" % (self.title_sorter(metadata_x.series), series_index)) lb_added.episode_ID.set(metadata_x.series) lb_added.episode_number.set(metadata_x.series_index) @@ -2694,7 +2811,9 @@ class ITUNES(DriverBase): break if db_added: - db_added.sort_name.set("%s %s" % (self.title_sorter(metadata_x.series), series_index)) + # If no title_sort plugboard tweak, create sort_name from series/index + if metadata.title_sort == metadata_x.title_sort: + db_added.sort_name.set("%s %s" % (self.title_sorter(metadata_x.series), series_index)) db_added.episode_ID.set(metadata_x.series) db_added.episode_number.set(metadata_x.series_index) @@ -2711,7 +2830,7 @@ class ITUNES(DriverBase): elif metadata_x.tags is not None: if DEBUG: self.log.info(" %susing Tag as Genre" % - "no Series name available, " if self.settings().read_metadata else '') + "no Series name available, " if self.settings().extra_customization[self.USE_SERIES_AS_CATEGORY] else '') for tag in metadata_x.tags: if self._is_alpha(tag[0]): if lb_added: @@ -2729,7 +2848,7 @@ class ITUNES(DriverBase): lb_added.Description = ("%s %s" % (self.description_prefix,strftime('%Y-%m-%d %H:%M:%S'))) lb_added.Enabled = True lb_added.SortArtist = icu_title(metadata_x.author_sort) - lb_added.SortName = metadata.title_sort + lb_added.SortName = metadata_x.title_sort if db_added: db_added.Name = metadata_x.title @@ -2739,7 +2858,7 @@ class ITUNES(DriverBase): db_added.Description = ("%s %s" % (self.description_prefix,strftime('%Y-%m-%d %H:%M:%S'))) db_added.Enabled = True db_added.SortArtist = icu_title(metadata_x.author_sort) - db_added.SortName = metadata.title_sort + db_added.SortName = metadata_x.title_sort if metadata_x.comments: if lb_added: @@ -2763,7 +2882,7 @@ class ITUNES(DriverBase): # Otherwise iBooks uses first <dc:subject> from opf # iTunes balks on setting EpisodeNumber, but it sticks (9.1.1.12) - if metadata_x.series and self.settings().read_metadata: + if metadata_x.series and self.settings().extra_customization[self.USE_SERIES_AS_CATEGORY]: if DEBUG: self.log.info(" using Series name as Genre") # Format the index as a sort key @@ -2772,7 +2891,9 @@ class ITUNES(DriverBase): fraction = index-integer series_index = '%04d%s' % (integer, str('%0.4f' % fraction).lstrip('0')) if lb_added: - lb_added.SortName = "%s %s" % (self.title_sorter(metadata_x.series), series_index) + # If no title_sort plugboard tweak, create sort_name from series/index + if metadata.title_sort == metadata_x.title_sort: + lb_added.SortName = "%s %s" % (self.title_sorter(metadata_x.series), series_index) lb_added.EpisodeID = metadata_x.series try: @@ -2798,7 +2919,9 @@ class ITUNES(DriverBase): break if db_added: - db_added.SortName = "%s %s" % (self.title_sorter(metadata_x.series), series_index) + # If no title_sort plugboard tweak, create sort_name from series/index + if metadata.title_sort == metadata_x.title_sort: + db_added.SortName = "%s %s" % (self.title_sorter(metadata_x.series), series_index) db_added.EpisodeID = metadata_x.series try: @@ -2837,13 +2960,13 @@ class ITUNES(DriverBase): def _xform_metadata_via_plugboard(self, book, format): ''' Transform book metadata from plugboard templates ''' if DEBUG: - self.log.info(" ITUNES._xform_metadata_via_plugboard()") + self.log.info(" ITUNES._xform_metadata_via_plugboard()") if self.plugboard_func: pb = self.plugboard_func(self.DEVICE_PLUGBOARD_NAME, format, self.plugboards) newmi = book.deepcopy_metadata() newmi.template_to_attribute(book, pb) - if DEBUG: + if pb is not None and DEBUG: self.log.info(" transforming %s using %s:" % (format, pb)) self.log.info(" title: %s %s" % (book.title, ">>> %s" % newmi.title if book.title != newmi.title else '')) @@ -2859,6 +2982,9 @@ class ITUNES(DriverBase): newmi.publisher if book.publisher != newmi.publisher else '')) self.log.info(" tags: %s %s" % (book.tags, ">>> %s" % newmi.tags if book.tags != newmi.tags else '')) + else: + self.log(" matching plugboard not found") + else: newmi = book return newmi @@ -2922,7 +3048,7 @@ class ITUNES_ASYNC(ITUNES): if not oncard: if DEBUG: self.log.info("ITUNES_ASYNC:books()") - if self.settings().use_subdirs: + if self.settings().extra_customization[self.CACHE_COVERS]: self.log.info(" Cover fetching/caching enabled") else: self.log.info(" Cover fetching/caching disabled") @@ -2960,7 +3086,7 @@ class ITUNES_ASYNC(ITUNES): cached_books[this_book.path] = { 'title':library_books[book].name(), - 'author':[library_books[book].artist()], + 'author':library_books[book].artist().split(' & '), 'lib_book':library_books[book], 'dev_book':None, 'uuid': library_books[book].composer(), @@ -2968,7 +3094,7 @@ class ITUNES_ASYNC(ITUNES): } if self.report_progress is not None: - self.report_progress(i+1/book_count, _('%d of %d') % (i+1, book_count)) + self.report_progress((i+1)/book_count, _('%d of %d') % (i+1, book_count)) elif iswindows: try: @@ -3000,14 +3126,14 @@ class ITUNES_ASYNC(ITUNES): cached_books[this_book.path] = { 'title':library_books[book].Name, - 'author':library_books[book].Artist, + 'author':library_books[book].Artist.split(' & '), 'lib_book':library_books[book], 'uuid': library_books[book].Composer, 'format': format } if self.report_progress is not None: - self.report_progress(i+1/book_count, + self.report_progress((i+1)/book_count, _('%d of %d') % (i+1, book_count)) finally: @@ -3070,6 +3196,38 @@ class ITUNES_ASYNC(ITUNES): only_presence=False): return self.connected, self + def open(self, library_uuid): + ''' + Perform any device specific initialization. Called after the device is + detected but before any other functions that communicate with the device. + For example: For devices that present themselves as USB Mass storage + devices, this method would be responsible for mounting the device or + if the device has been automounted, for finding out where it has been + mounted. The base class within USBMS device.py has a implementation of + this function that should serve as a good example for USB Mass storage + devices. + + Note that most of the initialization is necessarily performed in can_handle(), as + we need to talk to iTunes to discover if there's a connected iPod + ''' + if DEBUG: + self.log.info("ITUNES_ASYNC.open()") + + # Confirm/create thumbs archive + if not os.path.exists(self.cache_dir): + if DEBUG: + self.log.info(" creating thumb cache '%s'" % self.cache_dir) + os.makedirs(self.cache_dir) + + if not os.path.exists(self.archive_path): + self.log.info(" creating zip archive") + zfw = ZipFile(self.archive_path, mode='w') + zfw.writestr("iTunes Thumbs Archive",'') + zfw.close() + else: + if DEBUG: + self.log.info(" existing thumb cache at '%s'" % self.archive_path) + def sync_booklists(self, booklists, end_session=True): ''' Update metadata on device. @@ -3154,7 +3312,7 @@ class Book(Metadata): See ebooks.metadata.book.base ''' def __init__(self,title,author): - Metadata.__init__(self, title, authors=[author]) + Metadata.__init__(self, title, authors=author.split(' & ')) @property def title_sorter(self): diff --git a/src/calibre/devices/bambook/driver.py b/src/calibre/devices/bambook/driver.py index 3cc0245cf7..f251310d77 100644 --- a/src/calibre/devices/bambook/driver.py +++ b/src/calibre/devices/bambook/driver.py @@ -61,7 +61,7 @@ class BAMBOOK(DeviceConfig, DevicePlugin): detected_device=None) : self.open() - def open(self): + def open(self, library_uuid): # Make sure the Bambook library is ready if not is_bambook_lib_ready(): raise OpenFeedback(_("Unable to connect to Bambook, you need to install Bambook library first.")) diff --git a/src/calibre/devices/bambook/libbambookcore.py b/src/calibre/devices/bambook/libbambookcore.py index 35d04ba4ac..e77ac1da7b 100644 --- a/src/calibre/devices/bambook/libbambookcore.py +++ b/src/calibre/devices/bambook/libbambookcore.py @@ -10,7 +10,7 @@ Sanda library wrapper import ctypes, uuid, hashlib, os, sys from threading import Event, Lock -from calibre.constants import iswindows, islinux, isosx +from calibre.constants import iswindows from calibre import load_library try: @@ -29,12 +29,9 @@ try: except: lib_handle = None +text_encoding = 'utf-8' if iswindows: text_encoding = 'mbcs' -elif islinux: - text_encoding = 'utf-8' -elif isosx: - text_encoding = 'utf-8' def is_bambook_lib_ready(): return lib_handle != None diff --git a/src/calibre/devices/blackberry/driver.py b/src/calibre/devices/blackberry/driver.py index e816883957..1ae6a6c49f 100644 --- a/src/calibre/devices/blackberry/driver.py +++ b/src/calibre/devices/blackberry/driver.py @@ -19,7 +19,7 @@ class BLACKBERRY(USBMS): VENDOR_ID = [0x0fca] PRODUCT_ID = [0x8004, 0x0004] - BCD = [0x0200, 0x0107, 0x0210, 0x0201, 0x0211] + BCD = [0x0200, 0x0107, 0x0210, 0x0201, 0x0211, 0x0220] VENDOR_NAME = 'RIM' WINDOWS_MAIN_MEM = 'BLACKBERRY_SD' diff --git a/src/calibre/devices/boeye/__init__.py b/src/calibre/devices/boeye/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/src/calibre/devices/boeye/driver.py b/src/calibre/devices/boeye/driver.py new file mode 100644 index 0000000000..5f82b68417 --- /dev/null +++ b/src/calibre/devices/boeye/driver.py @@ -0,0 +1,56 @@ +__license__ = 'GPL v3' +__copyright__ = '2011, Ken <ken at szboeye.com>' +__docformat__ = 'restructuredtext en' + +''' +Device driver for BOEYE serial readers +''' + +from calibre.devices.usbms.driver import USBMS + +class BOEYE_BEX(USBMS): + name = 'BOEYE BEX reader driver' + gui_name = 'BOEYE BEX' + description = _('Communicate with BOEYE BEX Serial eBook readers.') + author = 'szboeye' + supported_platforms = ['windows', 'osx', 'linux'] + + FORMATS = ['epub', 'mobi', 'fb2', 'lit', 'prc', 'pdf', 'rtf', 'txt', 'djvu', 'doc', 'chm', 'html', 'zip', 'pdb'] + + VENDOR_ID = [0x0085] + PRODUCT_ID = [0x600] + + VENDOR_NAME = 'LINUX' + WINDOWS_MAIN_MEM = 'FILE-STOR_GADGET' + OSX_MAIN_MEM = 'Linux File-Stor Gadget Media' + + MAIN_MEMORY_VOLUME_LABEL = 'BOEYE BEX Storage Card' + + EBOOK_DIR_MAIN = 'Documents' + SUPPORTS_SUB_DIRS = True + +class BOEYE_BDX(USBMS): + name = 'BOEYE BDX reader driver' + gui_name = 'BOEYE BDX' + description = _('Communicate with BOEYE BDX serial eBook readers.') + author = 'szboeye' + supported_platforms = ['windows', 'osx', 'linux'] + + FORMATS = ['epub', 'mobi', 'fb2', 'lit', 'prc', 'pdf', 'rtf', 'txt', 'djvu', 'doc', 'chm', 'html', 'zip', 'pdb'] + + VENDOR_ID = [0x0085] + PRODUCT_ID = [0x800] + + VENDOR_NAME = 'LINUX' + WINDOWS_MAIN_MEM = 'FILE-STOR_GADGET' + WINDOWS_CARD_A_MEM = 'FILE-STOR_GADGET' + + OSX_MAIN_MEM = 'Linux File-Stor Gadget Media' + OSX_CARD_A_MEM = 'Linux File-Stor Gadget Media' + + MAIN_MEMORY_VOLUME_LABEL = 'BOEYE BDX Internal Memory' + STORAGE_CARD_VOLUME_LABEL = 'BOEYE BDX Storage Card' + + EBOOK_DIR_MAIN = 'Documents' + EBOOK_DIR_CARD_A = 'Documents' + SUPPORTS_SUB_DIRS = True diff --git a/src/calibre/devices/eb600/driver.py b/src/calibre/devices/eb600/driver.py index 5374c6c4e2..dbccd72ee9 100644 --- a/src/calibre/devices/eb600/driver.py +++ b/src/calibre/devices/eb600/driver.py @@ -95,9 +95,8 @@ class POCKETBOOK360(EB600): FORMATS = ['epub', 'fb2', 'prc', 'mobi', 'pdf', 'djvu', 'rtf', 'chm', 'txt'] - VENDOR_NAME = 'PHILIPS' - WINDOWS_MAIN_MEM = 'MASS_STORGE' - WINDOWS_CARD_A_MEM = 'MASS_STORGE' + VENDOR_NAME = ['PHILIPS', '__POCKET'] + WINDOWS_MAIN_MEM = WINDOWS_CARD_A_MEM = ['MASS_STORGE', 'BOOK_USB_STORAGE'] OSX_MAIN_MEM = 'Philips Mass Storge Media' OSX_CARD_A_MEM = 'Philips Mass Storge Media' @@ -244,7 +243,8 @@ class POCKETBOOK602(USBMS): BCD = [0x0324] VENDOR_NAME = '' - WINDOWS_MAIN_MEM = WINDOWS_CARD_A_MEM = ['PB602', 'PB603', 'PB902', 'PB903'] + WINDOWS_MAIN_MEM = WINDOWS_CARD_A_MEM = ['PB602', 'PB603', 'PB902', + 'PB903', 'PB'] class POCKETBOOK701(USBMS): diff --git a/src/calibre/devices/edge/driver.py b/src/calibre/devices/edge/driver.py index d14763f313..9491b9bc68 100644 --- a/src/calibre/devices/edge/driver.py +++ b/src/calibre/devices/edge/driver.py @@ -26,9 +26,9 @@ class EDGE(USBMS): PRODUCT_ID = [0x0c02] BCD = [0x0223] - VENDOR_NAME = 'ANDROID' - WINDOWS_MAIN_MEM = '__FILE-STOR_GADG' - WINDOWS_CARD_A_MEM = '__FILE-STOR_GADG' + VENDOR_NAME = ['ANDROID', 'LINUX'] + WINDOWS_MAIN_MEM = ['__FILE-STOR_GADG', 'FILE-CD_GADGET'] + WINDOWS_CARD_A_MEM = ['__FILE-STOR_GADG', 'FILE-CD_GADGET'] MAIN_MEMORY_VOLUME_LABEL = 'Edge Main Memory' STORAGE_CARD_VOLUME_LABEL = 'Edge Storage Card' diff --git a/src/calibre/devices/errors.py b/src/calibre/devices/errors.py index 3d88eb741f..ecd61a1169 100644 --- a/src/calibre/devices/errors.py +++ b/src/calibre/devices/errors.py @@ -41,6 +41,13 @@ class OpenFeedback(DeviceError): self.feedback_msg = msg DeviceError.__init__(self, msg) + def custom_dialog(self, parent): + ''' + If you need to show the user a custom dialog, instead of just + displaying the feedback_msg, create and return it here. + ''' + raise NotImplementedError + class DeviceBusy(ProtocolError): """ Raised when device is busy """ def __init__(self, uerr=""): diff --git a/src/calibre/devices/folder_device/driver.py b/src/calibre/devices/folder_device/driver.py index d75697a6cb..c08448051d 100644 --- a/src/calibre/devices/folder_device/driver.py +++ b/src/calibre/devices/folder_device/driver.py @@ -47,6 +47,7 @@ class FOLDER_DEVICE(USBMS): #: Icon for this device icon = I('devices/folder.png') METADATA_CACHE = '.metadata.calibre' + DRIVEINFO = '.driveinfo.calibre' _main_prefix = '' _card_a_prefix = None @@ -77,7 +78,8 @@ class FOLDER_DEVICE(USBMS): only_presence=False): return self.is_connected, self - def open(self): + def open(self, library_uuid): + self.current_library_uuid = library_uuid if not self._main_prefix: return False return True diff --git a/src/calibre/devices/hanlin/driver.py b/src/calibre/devices/hanlin/driver.py index 37f8430a66..2234e2112c 100644 --- a/src/calibre/devices/hanlin/driver.py +++ b/src/calibre/devices/hanlin/driver.py @@ -64,7 +64,7 @@ class HANLINV3(USBMS): return names def linux_swap_drives(self, drives): - if len(drives) < 2: return drives + if len(drives) < 2 or not drives[1] or not drives[2]: return drives drives = list(drives) t = drives[0] drives[0] = drives[1] @@ -95,7 +95,6 @@ class HANLINV5(HANLINV3): gui_name = 'Hanlin V5' description = _('Communicate with Hanlin V5 eBook readers.') - VENDOR_ID = [0x0492] PRODUCT_ID = [0x8813] BCD = [0x319] @@ -116,6 +115,7 @@ class BOOX(HANLINV3): author = 'Jesus Manuel Marinho Valcarce' supported_platforms = ['windows', 'osx', 'linux'] METADATA_CACHE = '.metadata.calibre' + DRIVEINFO = '.driveinfo.calibre' # Ordered list of supported formats FORMATS = ['epub', 'fb2', 'djvu', 'pdf', 'html', 'txt', 'rtf', 'mobi', diff --git a/src/calibre/devices/interface.py b/src/calibre/devices/interface.py index bc442f5853..b265331ace 100644 --- a/src/calibre/devices/interface.py +++ b/src/calibre/devices/interface.py @@ -215,7 +215,7 @@ class DevicePlugin(Plugin): return True - def open(self): + def open(self, library_uuid): ''' Perform any device specific initialization. Called after the device is detected but before any other functions that communicate with the device. @@ -260,6 +260,8 @@ class DevicePlugin(Plugin): Ask device for device information. See L{DeviceInfoQuery}. :return: (device name, device version, software version on device, mime type) + The tuple can optionally have a fifth element, which is a + drive information diction. See usbms.driver for an example. """ raise NotImplementedError() @@ -447,6 +449,15 @@ class DevicePlugin(Plugin): ''' pass + def set_driveinfo_name(self, location_code, name): + ''' + Set the device name in the driveinfo file to 'name'. This setting will + persist until the file is re-created or the name is changed again. + + Non-disk devices will ignore this request. + ''' + pass + class BookList(list): ''' A list of books. Each Book object must have the fields diff --git a/src/calibre/devices/kobo/driver.py b/src/calibre/devices/kobo/driver.py index f1c0d3f3d3..f5c9a74733 100644 --- a/src/calibre/devices/kobo/driver.py +++ b/src/calibre/devices/kobo/driver.py @@ -38,7 +38,7 @@ class KOBO(USBMS): VENDOR_ID = [0x2237] PRODUCT_ID = [0x4161] - BCD = [0x0110] + BCD = [0x0110, 0x0323] VENDOR_NAME = ['KOBO_INC', 'KOBO'] WINDOWS_MAIN_MEM = WINDOWS_CARD_A_MEM = ['.KOBOEREADER', 'EREADER'] @@ -115,6 +115,8 @@ class KOBO(USBMS): playlist_map[lpath]= "Im_Reading" elif readstatus == 2: playlist_map[lpath]= "Read" + elif readstatus == 3: + playlist_map[lpath]= "Closed" path = self.normalize_path(path) # print "Normalized FileName: " + path @@ -599,11 +601,47 @@ class KOBO(USBMS): try: cursor.execute('update content set ReadStatus=2,FirstTimeReading=\'true\' where BookID is Null and ContentID = ?', t) except: - debug_print('Database Exception: Unable set book as Rinished') + debug_print('Database Exception: Unable set book as Finished') raise else: connection.commit() # debug_print('Database: Commit set ReadStatus as Finished') + if category == 'Closed': + # Reset Im_Reading list in the database + if oncard == 'carda': + query= 'update content set ReadStatus=0, FirstTimeReading = \'true\' where BookID is Null and ReadStatus = 3 and ContentID like \'file:///mnt/sd/%\'' + elif oncard != 'carda' and oncard != 'cardb': + query= 'update content set ReadStatus=0, FirstTimeReading = \'true\' where BookID is Null and ReadStatus = 3 and ContentID not like \'file:///mnt/sd/%\'' + + try: + cursor.execute (query) + except: + debug_print('Database Exception: Unable to reset Closed list') + raise + else: +# debug_print('Commit: Reset Closed list') + connection.commit() + + for book in books: +# debug_print('Title:', book.title, 'lpath:', book.path) + book.device_collections = ['Closed'] + + extension = os.path.splitext(book.path)[1] + ContentType = self.get_content_type_from_extension(extension) if extension != '' else self.get_content_type_from_path(book.path) + + ContentID = self.contentid_from_path(book.path, ContentType) +# datelastread = time.strftime("%Y-%m-%dT%H:%M:%S", time.gmtime()) + + t = (ContentID,) + + try: + cursor.execute('update content set ReadStatus=3,FirstTimeReading=\'true\' where BookID is Null and ContentID = ?', t) + except: + debug_print('Database Exception: Unable set book as Closed') + raise + else: + connection.commit() +# debug_print('Database: Commit set ReadStatus as Closed') else: # No collections # Since no collections exist the ReadStatus needs to be reset to 0 (Unread) print "Reseting ReadStatus to 0" diff --git a/src/calibre/devices/libusb.py b/src/calibre/devices/libusb.py index 2d4911c799..016a6b18aa 100644 --- a/src/calibre/devices/libusb.py +++ b/src/calibre/devices/libusb.py @@ -8,10 +8,10 @@ from ctypes import cdll, POINTER, byref, pointer, Structure as _Structure, \ c_ubyte, c_ushort, c_int, c_char, c_void_p, c_byte, c_uint from errno import EBUSY, ENOMEM -from calibre import iswindows, isosx, isfreebsd, load_library +from calibre import iswindows, isosx, isbsd, load_library _libusb_name = 'libusb' -PATH_MAX = 511 if iswindows else 1024 if (isosx or isfreebsd) else 4096 +PATH_MAX = 511 if iswindows else 1024 if (isosx or isbsd) else 4096 if iswindows: class Structure(_Structure): _pack_ = 1 diff --git a/src/calibre/devices/misc.py b/src/calibre/devices/misc.py index 8cf0fb5a06..152f704528 100644 --- a/src/calibre/devices/misc.py +++ b/src/calibre/devices/misc.py @@ -187,7 +187,7 @@ class LUMIREAD(USBMS): cfilepath = cfilepath.replace(os.sep+'books'+os.sep, os.sep+'covers'+os.sep, 1) pdir = os.path.dirname(cfilepath) - if not os.exists(pdir): + if not os.path.exists(pdir): os.makedirs(pdir) with open(cfilepath+'.jpg', 'wb') as f: f.write(metadata.thumbnail[-1]) @@ -224,7 +224,7 @@ class TREKSTOR(USBMS): FORMATS = ['epub', 'txt', 'pdf'] VENDOR_ID = [0x1e68] - PRODUCT_ID = [0x0041] + PRODUCT_ID = [0x0041, 0x0042] BCD = [0x0002] EBOOK_DIR_MAIN = 'Ebooks' @@ -244,7 +244,7 @@ class EEEREADER(USBMS): FORMATS = ['epub', 'fb2', 'txt', 'pdf'] VENDOR_ID = [0x0b05] - PRODUCT_ID = [0x178f] + PRODUCT_ID = [0x178f, 0x17a1] BCD = [0x0319] EBOOK_DIR_MAIN = EBOOK_DIR_CARD_A = 'Book' @@ -269,9 +269,10 @@ class NEXTBOOK(USBMS): EBOOK_DIR_MAIN = '' - VENDOR_NAME = 'NEXT2' - WINDOWS_MAIN_MEM = WINDOWS_CARD_A_MEM = '1.0.14' + VENDOR_NAME = ['NEXT2', 'BK7005'] + WINDOWS_MAIN_MEM = WINDOWS_CARD_A_MEM = ['1.0.14', 'PLAYER'] SUPPORTS_SUB_DIRS = True + THUMBNAIL_HEIGHT = 120 ''' def upload_cover(self, path, filename, metadata, filepath): diff --git a/src/calibre/devices/nook/driver.py b/src/calibre/devices/nook/driver.py index 39d0763735..3c30b88568 100644 --- a/src/calibre/devices/nook/driver.py +++ b/src/calibre/devices/nook/driver.py @@ -107,3 +107,13 @@ class NOOK_COLOR(NOOK): return filepath +class NOOK_TSR(NOOK): + gui_name = _('Nook Simple') + description = _('Communicate with the Nook TSR eBook reader.') + + PRODUCT_ID = [0x003] + BCD = [0x216] + + EBOOK_DIR_MAIN = EBOOK_DIR_CARD_A = 'My Files/Books' + + diff --git a/src/calibre/devices/prs500/cli/main.py b/src/calibre/devices/prs500/cli/main.py index cd8395467b..f122f6332e 100755 --- a/src/calibre/devices/prs500/cli/main.py +++ b/src/calibre/devices/prs500/cli/main.py @@ -213,7 +213,7 @@ def main(): for d in connected_devices: try: - d.open() + d.open(None) except: continue else: @@ -282,7 +282,7 @@ def main(): outfile = os.path.join(outfile, path[path.rfind("/")+1:]) try: outfile = open(outfile, "wb") - except IOError, e: + except IOError as e: print >> sys.stderr, e parser.print_help() return 1 @@ -291,13 +291,13 @@ def main(): elif args[1].startswith("prs500:"): try: infile = open(args[0], "rb") - except IOError, e: + except IOError as e: print >> sys.stderr, e parser.print_help() return 1 try: dev.put_file(infile, args[1][7:]) - except PathError, err: + except PathError as err: if options.force and 'exists' in str(err): dev.del_file(err.path, False) dev.put_file(infile, args[1][7:]) @@ -355,7 +355,7 @@ def main(): return 1 except DeviceLocked: print >> sys.stderr, "The device is locked. Use the --unlock option" - except (ArgumentError, DeviceError), e: + except (ArgumentError, DeviceError) as e: print >>sys.stderr, e return 1 return 0 diff --git a/src/calibre/devices/prs500/driver.py b/src/calibre/devices/prs500/driver.py index 445ddd757b..aaba094fb3 100644 --- a/src/calibre/devices/prs500/driver.py +++ b/src/calibre/devices/prs500/driver.py @@ -177,7 +177,7 @@ class PRS500(DeviceConfig, DevicePlugin): dev.send_validated_command(BeginEndSession(end=True)) dev.in_session = False raise - except USBError, err: + except USBError as err: if "No such device" in str(err): raise DeviceError() elif "Connection timed out" in str(err): @@ -240,7 +240,7 @@ class PRS500(DeviceConfig, DevicePlugin): def set_progress_reporter(self, report_progress): self.report_progress = report_progress - def open(self) : + def open(self, library_uuid) : """ Claim an interface on the device for communication. Requires write privileges to the device file. @@ -272,7 +272,7 @@ class PRS500(DeviceConfig, DevicePlugin): self.bulk_read_max_packet_size = red.MaxPacketSize self.bulk_write_max_packet_size = wed.MaxPacketSize self.handle.claim_interface(self.INTERFACE_ID) - except USBError, err: + except USBError as err: raise DeviceBusy(str(err)) # Large timeout as device may still be initializing res = self.send_validated_command(GetUSBProtocolVersion(), timeout=20000) @@ -303,7 +303,7 @@ class PRS500(DeviceConfig, DevicePlugin): try: self.handle.reset() self.handle.release_interface(self.INTERFACE_ID) - except Exception, err: + except Exception as err: print >> sys.stderr, err self.handle, self.device = None, None self.in_session = False @@ -509,7 +509,7 @@ class PRS500(DeviceConfig, DevicePlugin): outfile.write("".join(map(chr, packets[0][16:]))) for i in range(1, len(packets)): outfile.write("".join(map(chr, packets[i]))) - except IOError, err: + except IOError as err: self.send_validated_command(FileClose(_id)) raise ArgumentError("File get operation failed. " + \ "Could not write to local location: " + str(err)) @@ -656,7 +656,7 @@ class PRS500(DeviceConfig, DevicePlugin): dest = None try: dest = self.path_properties(path, end_session=False) - except PathError, err: + except PathError as err: if "does not exist" in str(err) or "not mounted" in str(err): return (False, None) else: raise diff --git a/src/calibre/devices/prs505/driver.py b/src/calibre/devices/prs505/driver.py index 3768b8be62..2f962fc218 100644 --- a/src/calibre/devices/prs505/driver.py +++ b/src/calibre/devices/prs505/driver.py @@ -57,6 +57,7 @@ class PRS505(USBMS): MUST_READ_METADATA = True SUPPORTS_USE_AUTHOR_SORT = True EBOOK_DIR_MAIN = 'database/media/books' + SCAN_FROM_ROOT = False ALL_BY_TITLE = _('All by title') ALL_BY_AUTHOR = _('All by author') @@ -87,18 +88,27 @@ class PRS505(USBMS): _('Set this option if you want the cover thumbnails to have ' 'the same aspect ratio (width to height) as the cover. ' 'Unset it if you want the thumbnail to be the maximum size, ' - 'ignoring aspect ratio.') + 'ignoring aspect ratio.'), + _('Search for books in all folders') + + ':::' + + _('Setting this option tells calibre to look for books in all ' + 'folders on the device and its cards. This permits calibre to ' + 'find books put on the device by other software and by ' + 'wireless download.') ] EXTRA_CUSTOMIZATION_DEFAULT = [ ', '.join(['series', 'tags']), False, False, + True, True ] OPT_COLLECTIONS = 0 OPT_UPLOAD_COVERS = 1 OPT_REFRESH_COVERS = 2 + OPT_PRESERVE_ASPECT_RATIO = 3 + OPT_SCAN_FROM_ROOT = 4 plugboard = None plugboard_func = None @@ -147,14 +157,13 @@ class PRS505(USBMS): self.booklist_class.rebuild_collections = self.rebuild_collections # Set the thumbnail width to the theoretical max if the user has asked # that we do not preserve aspect ratio - if not self.settings().extra_customization[3]: + if not self.settings().extra_customization[self.OPT_PRESERVE_ASPECT_RATIO]: self.THUMBNAIL_WIDTH = 168 # Set WANTS_UPDATED_THUMBNAILS if the user has asked that thumbnails be # updated on every connect - self.WANTS_UPDATED_THUMBNAILS = self.settings().extra_customization[2] - - def get_device_information(self, end_session=True): - return (self.gui_name, '', '', '') + self.WANTS_UPDATED_THUMBNAILS = \ + self.settings().extra_customization[self.OPT_REFRESH_COVERS] + self.SCAN_FROM_ROOT = self.settings().extra_customization[self.OPT_SCAN_FROM_ROOT] def filename_callback(self, fname, mi): if getattr(mi, 'application_id', None) is not None: @@ -224,7 +233,8 @@ class PRS505(USBMS): os.path.splitext(os.path.basename(p))[0], book, p) except: - debug_print('FAILED to upload cover', p) + debug_print('FAILED to upload cover', + prefix, book.lpath) else: debug_print('PRS505: NOT uploading covers in sync_booklists') diff --git a/src/calibre/devices/scanner.py b/src/calibre/devices/scanner.py index c63eada0c8..9b729a3561 100644 --- a/src/calibre/devices/scanner.py +++ b/src/calibre/devices/scanner.py @@ -8,7 +8,7 @@ manner. import sys, os, re from threading import RLock -from calibre import iswindows, isosx, plugins, islinux +from calibre.constants import iswindows, isosx, plugins, islinux osx_scanner = win_scanner = linux_scanner = None diff --git a/src/calibre/devices/usbms/books.py b/src/calibre/devices/usbms/books.py index 8c92aa8a6e..cfebe796a3 100644 --- a/src/calibre/devices/usbms/books.py +++ b/src/calibre/devices/usbms/books.py @@ -203,6 +203,8 @@ class CollectionsBookList(BookList): val = [orig_val] elif fm['datatype'] == 'text' and fm['is_multiple']: val = orig_val + elif fm['datatype'] == 'composite' and fm['is_multiple']: + val = [v.strip() for v in val.split(fm['is_multiple'])] else: val = [val] diff --git a/src/calibre/devices/usbms/device.py b/src/calibre/devices/usbms/device.py index b0857de909..45b72abe74 100644 --- a/src/calibre/devices/usbms/device.py +++ b/src/calibre/devices/usbms/device.py @@ -124,11 +124,11 @@ class Device(DeviceConfig, DevicePlugin): if not prefix: return 0, 0 prefix = prefix[:-1] - win32file = __import__('win32file', globals(), locals(), [], -1) + import win32file try: sectors_per_cluster, bytes_per_sector, free_clusters, total_clusters = \ win32file.GetDiskFreeSpace(prefix) - except Exception, err: + except Exception as err: if getattr(err, 'args', [None])[0] == 21: # Disk not ready time.sleep(3) sectors_per_cluster, bytes_per_sector, free_clusters, total_clusters = \ @@ -700,7 +700,7 @@ class Device(DeviceConfig, DevicePlugin): - def open(self): + def open(self, library_uuid): time.sleep(5) self._main_prefix = self._card_a_prefix = self._card_b_prefix = None if islinux: @@ -722,6 +722,7 @@ class Device(DeviceConfig, DevicePlugin): time.sleep(7) self.open_osx() + self.current_library_uuid = library_uuid self.post_open_callback() def post_open_callback(self): @@ -770,7 +771,7 @@ class Device(DeviceConfig, DevicePlugin): for d in drives: try: eject(d) - except Exception, e: + except Exception as e: print 'Udisks eject call for:', d, 'failed:' print '\t', e failures = True @@ -925,8 +926,8 @@ class Device(DeviceConfig, DevicePlugin): if not isinstance(template, unicode): template = template.decode('utf-8') app_id = str(getattr(mdata, 'application_id', '')) - # The db id will be in the created filename - extra_components = get_components(template, mdata, fname, + id_ = mdata.get('id', fname) + extra_components = get_components(template, mdata, id_, timefmt=opts.send_timefmt, length=maxlen-len(app_id)-1) if not extra_components: extra_components.append(sanitize(self.filename_callback(fname, diff --git a/src/calibre/devices/usbms/deviceconfig.py b/src/calibre/devices/usbms/deviceconfig.py index 3c79652463..3f669f1e24 100644 --- a/src/calibre/devices/usbms/deviceconfig.py +++ b/src/calibre/devices/usbms/deviceconfig.py @@ -94,6 +94,9 @@ class DeviceConfig(object): if isinstance(cls.EXTRA_CUSTOMIZATION_MESSAGE, list): ec = [] for i in range(0, len(cls.EXTRA_CUSTOMIZATION_MESSAGE)): + if config_widget.opt_extra_customization[i] is None: + ec.append(None) + continue if hasattr(config_widget.opt_extra_customization[i], 'isChecked'): ec.append(config_widget.opt_extra_customization[i].isChecked()) else: diff --git a/src/calibre/devices/usbms/driver.py b/src/calibre/devices/usbms/driver.py index ef654ac428..c8580f6741 100644 --- a/src/calibre/devices/usbms/driver.py +++ b/src/calibre/devices/usbms/driver.py @@ -10,17 +10,18 @@ driver. It is intended to be subclassed with the relevant parts implemented for a particular device. ''' -import os -import re -import time +import os, re, time, json, uuid, functools from itertools import cycle +from calibre.constants import numeric_version from calibre import prints, isbytestring from calibre.constants import filesystem_encoding, DEBUG from calibre.devices.usbms.cli import CLI from calibre.devices.usbms.device import Device from calibre.devices.usbms.books import BookList, Book from calibre.ebooks.metadata.book.json_codec import JsonCodec +from calibre.utils.config import from_json, to_json +from calibre.utils.date import now, isoformat BASE_TIME = None def debug_print(*args): @@ -52,10 +53,61 @@ class USBMS(CLI, Device): FORMATS = [] CAN_SET_METADATA = [] METADATA_CACHE = 'metadata.calibre' + DRIVEINFO = 'driveinfo.calibre' + + SCAN_FROM_ROOT = False + + def _update_driveinfo_record(self, dinfo, prefix, location_code, name=None): + if not isinstance(dinfo, dict): + dinfo = {} + if dinfo.get('device_store_uuid', None) is None: + dinfo['device_store_uuid'] = unicode(uuid.uuid4()) + if dinfo.get('device_name') is None: + dinfo['device_name'] = self.get_gui_name() + if name is not None: + dinfo['device_name'] = name + dinfo['location_code'] = location_code + dinfo['last_library_uuid'] = getattr(self, 'current_library_uuid', None) + dinfo['calibre_version'] = '.'.join([unicode(i) for i in numeric_version]) + dinfo['date_last_connected'] = isoformat(now()) + dinfo['prefix'] = prefix.replace('\\', '/') + return dinfo + + def _update_driveinfo_file(self, prefix, location_code, name=None): + if os.path.exists(os.path.join(prefix, self.DRIVEINFO)): + with open(os.path.join(prefix, self.DRIVEINFO), 'rb') as f: + try: + driveinfo = json.loads(f.read(), object_hook=from_json) + except: + driveinfo = None + driveinfo = self._update_driveinfo_record(driveinfo, prefix, + location_code, name) + with open(os.path.join(prefix, self.DRIVEINFO), 'wb') as f: + f.write(json.dumps(driveinfo, default=to_json)) + else: + driveinfo = self._update_driveinfo_record({}, prefix, location_code, name) + with open(os.path.join(prefix, self.DRIVEINFO), 'wb') as f: + f.write(json.dumps(driveinfo, default=to_json)) + return driveinfo def get_device_information(self, end_session=True): self.report_progress(1.0, _('Get device information...')) - return (self.get_gui_name(), '', '', '') + self.driveinfo = {} + if self._main_prefix is not None: + self.driveinfo['main'] = self._update_driveinfo_file(self._main_prefix, 'main') + if self._card_a_prefix is not None: + self.driveinfo['A'] = self._update_driveinfo_file(self._card_a_prefix, 'A') + if self._card_b_prefix is not None: + self.driveinfo['B'] = self._update_driveinfo_file(self._card_b_prefix, 'B') + return (self.get_gui_name(), '', '', '', self.driveinfo) + + def set_driveinfo_name(self, location_code, name): + if location_code == 'main': + self._update_driveinfo_file(self._main_prefix, location_code, name) + elif location_code == 'A': + self._update_driveinfo_file(self._card_a_prefix, location_code, name) + elif location_code == 'B': + self._update_driveinfo_file(self._card_b_prefix, location_code, name) def books(self, oncard=None, end_session=True): from calibre.ebooks.metadata.meta import path_to_ext @@ -123,9 +175,13 @@ class USBMS(CLI, Device): ebook_dirs = [ebook_dirs] for ebook_dir in ebook_dirs: ebook_dir = self.path_to_unicode(ebook_dir) - ebook_dir = self.normalize_path( \ + if self.SCAN_FROM_ROOT: + ebook_dir = self.normalize_path(prefix) + else: + ebook_dir = self.normalize_path( \ os.path.join(prefix, *(ebook_dir.split('/'))) \ if ebook_dir else prefix) + debug_print('USBMS: scan from root', self.SCAN_FROM_ROOT, ebook_dir) if not os.path.exists(ebook_dir): continue # Get all books in the ebook_dir directory if self.SUPPORTS_SUB_DIRS: @@ -322,15 +378,21 @@ class USBMS(CLI, Device): @classmethod def build_template_regexp(cls): - def replfunc(match): - if match.group(1) in ['title', 'series', 'series_index', 'isbn']: - return '(?P<' + match.group(1) + '>.+?)' - elif match.group(1) in ['authors', 'author_sort']: - return '(?P<author>.+?)' - else: - return '(.+?)' + def replfunc(match, seen=None): + v = match.group(1) + if v in ['title', 'series', 'series_index', 'isbn']: + if v not in seen: + seen |= set([v]) + return '(?P<' + v + '>.+?)' + elif v in ['authors', 'author_sort']: + if v not in seen: + seen |= set([v]) + return '(?P<author>.+?)' + return '(.+?)' + s = set() + f = functools.partial(replfunc, seen=s) template = cls.save_template().rpartition('/')[2] - return re.compile(re.sub('{([^}]*)}', replfunc, template) + '([_\d]*$)') + return re.compile(re.sub('{([^}]*)}', f, template) + '([_\d]*$)') @classmethod def path_to_unicode(cls, path): diff --git a/src/calibre/devices/user_defined/__init__.py b/src/calibre/devices/user_defined/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/src/calibre/devices/user_defined/driver.py b/src/calibre/devices/user_defined/driver.py new file mode 100644 index 0000000000..d613a09508 --- /dev/null +++ b/src/calibre/devices/user_defined/driver.py @@ -0,0 +1,110 @@ +# -*- coding: utf-8 -*- + +__license__ = 'GPL v3' +__copyright__ = '2009, Kovid Goyal <kovid@kovidgoyal.net>' +__docformat__ = 'restructuredtext en' + +from calibre.devices.usbms.driver import USBMS + +class USER_DEFINED(USBMS): + + name = 'User Defined USB driver' + gui_name = 'User Defined USB Device' + author = 'Kovid Goyal' + supported_platforms = ['windows', 'osx', 'linux'] + + # Ordered list of supported formats + FORMATS = ['epub', 'mobi', 'pdf'] + + VENDOR_ID = 0xFFFF + PRODUCT_ID = 0xFFFF + BCD = None + + EBOOK_DIR_MAIN = '' + EBOOK_DIR_CARD_A = '' + + VENDOR_NAME = [] + WINDOWS_MAIN_MEM = '' + WINDOWS_CARD_A_MEM = '' + + OSX_MAIN_MEM = 'Device Main Memory' + + MAIN_MEMORY_VOLUME_LABEL = 'Device Main Memory' + + SUPPORTS_SUB_DIRS = True + + EXTRA_CUSTOMIZATION_MESSAGE = [ + _('USB Vendor ID (in hex)') + ':::<p>' + + _('Get this ID using Preferences -> Misc -> Get information to ' + 'set up the user-defined device') + '</p>', + _('USB Product ID (in hex)')+ ':::<p>' + + _('Get this ID using Preferences -> Misc -> Get information to ' + 'set up the user-defined device') + '</p>', + _('USB Revision ID (in hex)')+ ':::<p>' + + _('Get this ID using Preferences -> Misc -> Get information to ' + 'set up the user-defined device') + '</p>', + '', + _('Windows main memory vendor string') + ':::<p>' + + _('This field is used only on windows. ' + 'Get this ID using Preferences -> Misc -> Get information to ' + 'set up the user-defined device') + '</p>', + _('Windows main memory ID string') + ':::<p>' + + _('This field is used only on windows. ' + 'Get this ID using Preferences -> Misc -> Get information to ' + 'set up the user-defined device') + '</p>', + _('Windows card A vendor string') + ':::<p>' + + _('This field is used only on windows. ' + 'Get this ID using Preferences -> Misc -> Get information to ' + 'set up the user-defined device') + '</p>', + _('Windows card A ID string') + ':::<p>' + + _('This field is used only on windows. ' + 'Get this ID using Preferences -> Misc -> Get information to ' + 'set up the user-defined device') + '</p>', + _('Main memory folder') + ':::<p>' + + _('Enter the folder where the books are to be stored. This folder ' + 'is prepended to any send_to_device template') + '</p>', + _('Card A folder') + ':::<p>' + + _('Enter the folder where the books are to be stored. This folder ' + 'is prepended to any send_to_device template') + '</p>', + ] + EXTRA_CUSTOMIZATION_DEFAULT = [ + '0xffff', + '0xffff', + '0xffff', + None, + '', + '', + '', + '', + '', + '', + ] + OPT_USB_VENDOR_ID = 0 + OPT_USB_PRODUCT_ID = 1 + OPT_USB_REVISION_ID = 2 + OPT_USB_WINDOWS_MM_VEN_ID = 4 + OPT_USB_WINDOWS_MM_ID = 5 + OPT_USB_WINDOWS_CA_VEN_ID = 6 + OPT_USB_WINDOWS_CA_ID = 7 + OPT_MAIN_MEM_FOLDER = 8 + OPT_CARD_A_FOLDER = 9 + + def initialize(self): + try: + e = self.settings().extra_customization + self.VENDOR_ID = int(e[self.OPT_USB_VENDOR_ID], 16) + self.PRODUCT_ID = int(e[self.OPT_USB_PRODUCT_ID], 16) + self.BCD = [int(e[self.OPT_USB_REVISION_ID], 16)] + if e[self.OPT_USB_WINDOWS_MM_VEN_ID]: + self.VENDOR_NAME.append(e[self.OPT_USB_WINDOWS_MM_VEN_ID]) + if e[self.OPT_USB_WINDOWS_CA_VEN_ID] and \ + e[self.OPT_USB_WINDOWS_CA_VEN_ID] not in self.VENDOR_NAME: + self.VENDOR_NAME.append(e[self.OPT_USB_WINDOWS_CA_VEN_ID]) + self.WINDOWS_MAIN_MEM = e[self.OPT_USB_WINDOWS_MM_ID] + '&' + self.WINDOWS_CARD_A_MEM = e[self.OPT_USB_WINDOWS_CA_ID] + '&' + self.EBOOK_DIR_MAIN = e[self.OPT_MAIN_MEM_FOLDER] + self.EBOOK_DIR_CARD_A = e[self.OPT_CARD_A_FOLDER] + except: + import traceback + traceback.print_exc() + USBMS.initialize(self) diff --git a/src/calibre/ebooks/__init__.py b/src/calibre/ebooks/__init__.py index dcd32811b3..d5b214884e 100644 --- a/src/calibre/ebooks/__init__.py +++ b/src/calibre/ebooks/__init__.py @@ -7,7 +7,7 @@ Code for the conversion of ebook formats and the reading of metadata from various formats. ''' -import traceback, os +import traceback, os, re from calibre import CurrentDir class ConversionError(Exception): @@ -25,10 +25,10 @@ class DRMError(ValueError): class ParserError(ValueError): pass -BOOK_EXTENSIONS = ['lrf', 'rar', 'zip', 'rtf', 'lit', 'txt', 'txtz', 'htm', 'xhtm', - 'html', 'xhtml', 'pdf', 'pdb', 'pdr', 'prc', 'mobi', 'azw', 'doc', +BOOK_EXTENSIONS = ['lrf', 'rar', 'zip', 'rtf', 'lit', 'txt', 'txtz', 'text', 'htm', 'xhtm', + 'html', 'htmlz', 'xhtml', 'pdf', 'pdb', 'pdr', 'prc', 'mobi', 'azw', 'doc', 'epub', 'fb2', 'djvu', 'lrx', 'cbr', 'cbz', 'cbc', 'oebzip', - 'rb', 'imp', 'odt', 'chm', 'tpz', 'azw1', 'pml', 'mbp', 'tan', 'snb'] + 'rb', 'imp', 'odt', 'chm', 'tpz', 'azw1', 'pml', 'pmlz', 'mbp', 'tan', 'snb'] class HTMLRenderer(object): @@ -57,7 +57,7 @@ class HTMLRenderer(object): buf.open(QBuffer.WriteOnly) image.save(buf, 'JPEG') self.data = str(ba.data()) - except Exception, e: + except Exception as e: self.exception = e self.traceback = traceback.format_exc() finally: @@ -169,3 +169,42 @@ def calibre_cover(title, author_string, series_string=None, lines.append(TextLine(series_string, author_size)) return create_cover_page(lines, I('library.png'), output_format='jpg') +UNIT_RE = re.compile(r'^(-*[0-9]*[.]?[0-9]*)\s*(%|em|ex|en|px|mm|cm|in|pt|pc)$') + +def unit_convert(value, base, font, dpi): + ' Return value in pts' + if isinstance(value, (int, long, float)): + return value + try: + return float(value) * 72.0 / dpi + except: + pass + result = value + m = UNIT_RE.match(value) + if m is not None and m.group(1): + value = float(m.group(1)) + unit = m.group(2) + if unit == '%': + result = (value / 100.0) * base + elif unit == 'px': + result = value * 72.0 / dpi + elif unit == 'in': + result = value * 72.0 + elif unit == 'pt': + result = value + elif unit == 'em': + result = value * font + elif unit in ('ex', 'en'): + # This is a hack for ex since we have no way to know + # the x-height of the font + font = font + result = value * font * 0.5 + elif unit == 'pc': + result = value * 12.0 + elif unit == 'mm': + result = value * 0.04 + elif unit == 'cm': + result = value * 0.40 + return result + + diff --git a/src/calibre/ebooks/chardet/__init__.py b/src/calibre/ebooks/chardet/__init__.py index 571ceafe53..604cbdd360 100644 --- a/src/calibre/ebooks/chardet/__init__.py +++ b/src/calibre/ebooks/chardet/__init__.py @@ -100,6 +100,12 @@ def xml_to_unicode(raw, verbose=False, strip_encoding_pats=False, try: if encoding.lower().strip() == 'macintosh': encoding = 'mac-roman' + if encoding.lower().replace('_', '-').strip() in ( + 'gb2312', 'chinese', 'csiso58gb231280', 'euc-cn', 'euccn', + 'eucgb2312-cn', 'gb2312-1980', 'gb2312-80', 'iso-ir-58'): + # Microsoft Word exports to HTML with encoding incorrectly set to + # gb2312 instead of gbk. gbk is a superset of gb2312, anyway. + encoding = 'gbk' raw = raw.decode(encoding, 'replace') except LookupError: encoding = 'utf-8' @@ -110,4 +116,6 @@ def xml_to_unicode(raw, verbose=False, strip_encoding_pats=False, if resolve_entities: raw = substitute_entites(raw) + + return raw, encoding diff --git a/src/calibre/ebooks/chm/input.py b/src/calibre/ebooks/chm/input.py index 89efa2b4d1..b5074e8a72 100644 --- a/src/calibre/ebooks/chm/input.py +++ b/src/calibre/ebooks/chm/input.py @@ -19,12 +19,12 @@ class CHMInput(InputFormatPlugin): description = 'Convert CHM files to OEB' file_types = set(['chm']) - def _chmtohtml(self, output_dir, chm_path, no_images, log): + def _chmtohtml(self, output_dir, chm_path, no_images, log, debug_dump=False): from calibre.ebooks.chm.reader import CHMReader log.debug('Opening CHM file') - rdr = CHMReader(chm_path, log) + rdr = CHMReader(chm_path, log, self.opts) log.debug('Extracting CHM to %s' % output_dir) - rdr.extract_content(output_dir) + rdr.extract_content(output_dir, debug_dump=debug_dump) self._chm_reader = rdr return rdr.hhc_path @@ -32,13 +32,13 @@ class CHMInput(InputFormatPlugin): def convert(self, stream, options, file_ext, log, accelerators): from calibre.ebooks.chm.metadata import get_metadata_from_reader from calibre.customize.ui import plugin_for_input_format + self.opts = options log.debug('Processing CHM...') with TemporaryDirectory('_chm2oeb') as tdir: html_input = plugin_for_input_format('html') for opt in html_input.options: setattr(options, opt.option.name, opt.recommended_value) - options.input_encoding = 'utf-8' no_images = False #options.no_images chm_name = stream.name #chm_data = stream.read() @@ -47,13 +47,22 @@ class CHMInput(InputFormatPlugin): stream.close() log.debug('tdir=%s' % tdir) log.debug('stream.name=%s' % stream.name) - mainname = self._chmtohtml(tdir, chm_name, no_images, log) + debug_dump = False + odi = options.debug_pipeline + if odi: + debug_dump = os.path.join(odi, 'input') + mainname = self._chmtohtml(tdir, chm_name, no_images, log, + debug_dump=debug_dump) mainpath = os.path.join(tdir, mainname) metadata = get_metadata_from_reader(self._chm_reader) + self._chm_reader.CloseCHM() + #print tdir + #from calibre import ipython + #ipython() - odi = options.debug_pipeline options.debug_pipeline = None + options.input_encoding = 'utf-8' # try a custom conversion: #oeb = self._create_oebbook(mainpath, tdir, options, log, metadata) # try using html converter: diff --git a/src/calibre/ebooks/chm/reader.py b/src/calibre/ebooks/chm/reader.py index 04ce6d5efe..5f23ad0241 100644 --- a/src/calibre/ebooks/chm/reader.py +++ b/src/calibre/ebooks/chm/reader.py @@ -5,8 +5,8 @@ __copyright__ = '2008, Kovid Goyal <kovid at kovidgoyal.net>,' \ ' and Alex Bramley <a.bramley at gmail.com>.' import os, re -from mimetypes import guess_type as guess_mimetype +from calibre import guess_type as guess_mimetype from calibre.ebooks.BeautifulSoup import BeautifulSoup, NavigableString from calibre.constants import iswindows, filesystem_encoding from calibre.utils.chm.chm import CHMFile @@ -40,13 +40,14 @@ class CHMError(Exception): pass class CHMReader(CHMFile): - def __init__(self, input, log): + def __init__(self, input, log, opts): CHMFile.__init__(self) if isinstance(input, unicode): input = input.encode(filesystem_encoding) if not self.LoadCHM(input): raise CHMError("Unable to open CHM file '%s'"%(input,)) self.log = log + self.opts = opts self._sourcechm = input self._contents = None self._playorder = 0 @@ -54,8 +55,12 @@ class CHMReader(CHMFile): self._extracted = False # location of '.hhc' file, which is the CHM TOC. - self.root, ext = os.path.splitext(self.topics.lstrip('/')) - self.hhc_path = self.root + ".hhc" + if self.topics is None: + self.root, ext = os.path.splitext(self.home.lstrip('/')) + self.hhc_path = self.root + ".hhc" + else: + self.root, ext = os.path.splitext(self.topics.lstrip('/')) + self.hhc_path = self.root + ".hhc" def _parse_toc(self, ul, basedir=os.getcwdu()): toc = TOC(play_order=self._playorder, base_path=basedir, text='') @@ -92,7 +97,7 @@ class CHMReader(CHMFile): raise CHMError("'%s' is zero bytes in length!"%(path,)) return data - def ExtractFiles(self, output_dir=os.getcwdu()): + def ExtractFiles(self, output_dir=os.getcwdu(), debug_dump=False): html_files = set([]) for path in self.Contents(): lpath = os.path.join(output_dir, path) @@ -118,6 +123,9 @@ class CHMReader(CHMFile): self.log.warn('%r filename too long, skipping'%path) continue raise + if debug_dump: + import shutil + shutil.copytree(output_dir, os.path.join(debug_dump, 'debug_dump')) for lpath in html_files: with open(lpath, 'r+b') as f: data = f.read() @@ -142,11 +150,14 @@ class CHMReader(CHMFile): if self.hhc_path == '.hhc' and self.hhc_path not in files: from calibre import walk for x in walk(output_dir): - if os.path.basename(x).lower() in ('index.htm', 'index.html'): + if os.path.basename(x).lower() in ('index.htm', 'index.html', + 'contents.htm', 'contents.html'): self.hhc_path = os.path.relpath(x, output_dir) break def _reformat(self, data, htmlpath): + if self.opts.input_encoding: + data = data.decode(self.opts.input_encoding) try: data = xml_to_unicode(data, strip_encoding_pats=True)[0] soup = BeautifulSoup(data) @@ -241,8 +252,8 @@ class CHMReader(CHMFile): if not os.path.isdir(dir): os.makedirs(dir) - def extract_content(self, output_dir=os.getcwdu()): - self.ExtractFiles(output_dir=output_dir) + def extract_content(self, output_dir=os.getcwdu(), debug_dump=False): + self.ExtractFiles(output_dir=output_dir, debug_dump=debug_dump) diff --git a/src/calibre/ebooks/comic/input.py b/src/calibre/ebooks/comic/input.py index 7710d41fb3..56f7683c57 100755 --- a/src/calibre/ebooks/comic/input.py +++ b/src/calibre/ebooks/comic/input.py @@ -12,6 +12,7 @@ from Queue import Empty from calibre.customize.conversion import InputFormatPlugin, OptionRecommendation from calibre import extract, CurrentDir, prints +from calibre.constants import filesystem_encoding from calibre.ptempfile import PersistentTemporaryDirectory from calibre.utils.ipc.server import Server from calibre.utils.ipc.job import ParallelJob @@ -21,6 +22,10 @@ def extract_comic(path_to_comic_file): Un-archive the comic file. ''' tdir = PersistentTemporaryDirectory(suffix='_comic_extract') + if not isinstance(tdir, unicode): + # Needed in case the zip file has wrongly encoded unicode file/dir + # names + tdir = tdir.decode(filesystem_encoding) extract(path_to_comic_file, tdir) return tdir @@ -131,9 +136,12 @@ class PageProcessor(list): # {{{ newsizey = int(newsizex / aspect) deltax = 0 deltay = (SCRHEIGHT - newsizey) / 2 - wand.size = (newsizex, newsizey) - wand.set_border_color(pw) - wand.add_border(pw, deltax, deltay) + if newsizex < 20000 and newsizey < 20000: + # Too large and resizing fails, so better + # to leave it as original size + wand.size = (newsizex, newsizey) + wand.set_border_color(pw) + wand.add_border(pw, deltax, deltay) elif self.opts.wide: # Keep aspect and Use device height as scaled image width so landscape mode is clean aspect = float(sizex) / float(sizey) @@ -152,11 +160,15 @@ class PageProcessor(list): # {{{ newsizey = int(newsizex / aspect) deltax = 0 deltay = (wscreeny - newsizey) / 2 - wand.size = (newsizex, newsizey) - wand.set_border_color(pw) - wand.add_border(pw, deltax, deltay) + if newsizex < 20000 and newsizey < 20000: + # Too large and resizing fails, so better + # to leave it as original size + wand.size = (newsizex, newsizey) + wand.set_border_color(pw) + wand.add_border(pw, deltax, deltay) else: - wand.size = (SCRWIDTH, SCRHEIGHT) + if SCRWIDTH < 20000 and SCRHEIGHT < 20000: + wand.size = (SCRWIDTH, SCRHEIGHT) if not self.opts.dont_sharpen: wand.sharpen(0.0, 1.0) diff --git a/src/calibre/ebooks/compression/palmdoc.c b/src/calibre/ebooks/compression/palmdoc.c index 4d913dfd2b..6b07bb9cd5 100644 --- a/src/calibre/ebooks/compression/palmdoc.c +++ b/src/calibre/ebooks/compression/palmdoc.c @@ -17,6 +17,7 @@ #define BUFFER 6000 #define MIN(x, y) ( ((x) < (y)) ? (x) : (y) ) +#define MAX(x, y) ( ((x) > (y)) ? (x) : (y) ) typedef unsigned short int Byte; typedef struct { @@ -53,7 +54,7 @@ cpalmdoc_decompress(PyObject *self, PyObject *args) { // Map chars to bytes for (j = 0; j < input_len; j++) input[j] = (_input[j] < 0) ? _input[j]+256 : _input[j]; - output = (char *)PyMem_Malloc(sizeof(char)*BUFFER); + output = (char *)PyMem_Malloc(sizeof(char)*(MAX(BUFFER, 5*input_len))); if (output == NULL) return PyErr_NoMemory(); while (i < input_len) { diff --git a/src/calibre/ebooks/conversion/cli.py b/src/calibre/ebooks/conversion/cli.py index 975507e2a7..1767019972 100644 --- a/src/calibre/ebooks/conversion/cli.py +++ b/src/calibre/ebooks/conversion/cli.py @@ -40,7 +40,7 @@ To get help on them specify the input and output file and then use the -h \ option. For full documentation of the conversion system see -''') + 'http://calibre-ebook.com/user_manual/conversion.html' +''') + 'http://manual.calibre-ebook.com/conversion.html' HEURISTIC_OPTIONS = ['markup_chapter_headings', 'italicize_common_cases', 'fix_indents', @@ -49,6 +49,8 @@ HEURISTIC_OPTIONS = ['markup_chapter_headings', 'dehyphenate', 'renumber_headings', 'replace_scene_breaks'] +DEFAULT_TRUE_OPTIONS = HEURISTIC_OPTIONS + ['remove_fake_margins'] + def print_help(parser, log): help = parser.format_help().encode(preferred_encoding, 'replace') log(help) @@ -66,7 +68,8 @@ def check_command_line_options(parser, args, log): raise SystemExit(1) output = args[2] - if output.startswith('.') and output != '.': + if output.startswith('.') and (output != '.' and not + output.startswith('..')): output = os.path.splitext(os.path.basename(input))[0]+output output = os.path.abspath(output) @@ -90,7 +93,7 @@ def option_recommendation_to_cli_option(add_option, rec): if opt.long_switch == 'verbose': attrs['action'] = 'count' attrs.pop('type', '') - if opt.name in HEURISTIC_OPTIONS and rec.recommended_value is True: + if opt.name in DEFAULT_TRUE_OPTIONS and rec.recommended_value is True: switches = ['--disable-'+opt.long_switch] add_option(Option(*switches, **attrs)) @@ -162,6 +165,7 @@ def add_pipeline_options(parser, plumber): 'chapter', 'chapter_mark', 'prefer_metadata_cover', 'remove_first_image', 'insert_metadata', 'page_breaks_before', + 'remove_fake_margins', ] ), diff --git a/src/calibre/ebooks/conversion/plumber.py b/src/calibre/ebooks/conversion/plumber.py index 9a0c3f3c7f..3eb59a21b9 100644 --- a/src/calibre/ebooks/conversion/plumber.py +++ b/src/calibre/ebooks/conversion/plumber.py @@ -14,7 +14,8 @@ from calibre.ebooks.conversion.preprocess import HTMLPreProcessor from calibre.ptempfile import PersistentTemporaryDirectory from calibre.utils.date import parse_date from calibre.utils.zipfile import ZipFile -from calibre import extract, walk, isbytestring, filesystem_encoding +from calibre import (extract, walk, isbytestring, filesystem_encoding, + get_types_map) from calibre.constants import __version__ DEBUG_README=u''' @@ -304,6 +305,17 @@ OptionRecommendation(name='page_breaks_before', 'before the specified elements.') ), +OptionRecommendation(name='remove_fake_margins', + recommended_value=True, level=OptionRecommendation.LOW, + help=_('Some documents specify page margins by ' + 'specifying a left and right margin on each individual ' + 'paragraph. calibre will try to detect and remove these ' + 'margins. Sometimes, this can cause the removal of ' + 'margins that should not have been removed. In this ' + 'case you can disable the removal.') + ), + + OptionRecommendation(name='margin_top', recommended_value=5.0, level=OptionRecommendation.LOW, help=_('Set the top margin in pts. Default is %default. ' @@ -842,7 +854,8 @@ OptionRecommendation(name='sr3_replace', if isinstance(ret, basestring): shutil.copytree(output_dir, out_dir) else: - os.makedirs(out_dir) + if not os.path.exists(out_dir): + os.makedirs(out_dir) self.dump_oeb(ret, out_dir) if self.input_fmt == 'recipe': zf = ZipFile(os.path.join(self.opts.debug_pipeline, @@ -864,6 +877,9 @@ OptionRecommendation(name='sr3_replace', if self.opts.verbose: self.log.filter_level = self.log.DEBUG self.flush() + import cssutils, logging + cssutils.log.setLevel(logging.WARN) + get_types_map() # Ensure the mimetypes module is intialized if self.opts.debug_pipeline is not None: self.opts.verbose = max(self.opts.verbose, 4) @@ -988,9 +1004,15 @@ OptionRecommendation(name='sr3_replace', page_break_on_body=self.output_plugin.file_type in ('mobi', 'lit')) flattener(self.oeb, self.opts) + self.opts.insert_blank_line = oibl self.opts.remove_paragraph_spacing = orps + from calibre.ebooks.oeb.transforms.page_margin import \ + RemoveFakeMargins, RemoveAdobeMargins + RemoveFakeMargins()(self.oeb, self.log, self.opts) + RemoveAdobeMargins()(self.oeb, self.log, self.opts) + pr(0.9) self.flush() diff --git a/src/calibre/ebooks/conversion/preprocess.py b/src/calibre/ebooks/conversion/preprocess.py index 5f6402f746..885d0621e0 100644 --- a/src/calibre/ebooks/conversion/preprocess.py +++ b/src/calibre/ebooks/conversion/preprocess.py @@ -265,16 +265,28 @@ class CSSPreProcessor(object): PAGE_PAT = re.compile(r'@page[^{]*?{[^}]*?}') # Remove some of the broken CSS Microsoft products - # create, slightly dangerous as it removes to end of line - # rather than semi-colon - MS_PAT = re.compile(r'^\s*(mso-|panose-).+?$', - re.MULTILINE|re.IGNORECASE) + # create + MS_PAT = re.compile(r''' + (?P<start>^|;|\{)\s* # The end of the previous rule or block start + (%s).+? # The invalid selectors + (?P<end>$|;|\}) # The end of the declaration + '''%'mso-|panose-|text-underline|tab-interval', + re.MULTILINE|re.IGNORECASE|re.VERBOSE) + + def ms_sub(self, match): + end = match.group('end') + try: + start = match.group('start') + except: + start = '' + if end == ';': + end = '' + return start + end def __call__(self, data, add_namespace=False): from calibre.ebooks.oeb.base import XHTML_CSS_NAMESPACE data = self.PAGE_PAT.sub('', data) - if '\n' in data: - data = self.MS_PAT.sub('', data) + data = self.MS_PAT.sub(self.ms_sub, data) if not add_namespace: return data ans, namespaced = [], False @@ -387,10 +399,10 @@ class HTMLPreProcessor(object): (re.compile(u'˙\s*(<br.*?>)*\s*Z', re.UNICODE), lambda match: u'Ż'), # If pdf printed from a browser then the header/footer has a reliable pattern - (re.compile(r'((?<=</a>)\s*file:////?[A-Z].*<br>|file:////?[A-Z].*<br>(?=\s*<hr>))', re.IGNORECASE), lambda match: ''), + (re.compile(r'((?<=</a>)\s*file:/{2,4}[A-Z].*<br>|file:////?[A-Z].*<br>(?=\s*<hr>))', re.IGNORECASE), lambda match: ''), # Center separator lines - (re.compile(u'<br>\s*(?P<break>([*#•✦=]+\s*)+)\s*<br>'), lambda match: '<p>\n<p style="text-align:center">' + match.group(1) + '</p>'), + (re.compile(u'<br>\s*(?P<break>([*#•✦=] *){3,})\s*<br>'), lambda match: '<p>\n<p style="text-align:center">' + match.group('break') + '</p>'), # Remove page links (re.compile(r'<a name=\d+></a>', re.IGNORECASE), lambda match: ''), diff --git a/src/calibre/ebooks/conversion/utils.py b/src/calibre/ebooks/conversion/utils.py index f1f2f87293..7488df4609 100644 --- a/src/calibre/ebooks/conversion/utils.py +++ b/src/calibre/ebooks/conversion/utils.py @@ -156,17 +156,17 @@ class HeuristicProcessor(object): ] ITALICIZE_STYLE_PATS = [ - r'(?msu)(?<=[\s>])_(?P<words>[^_]+)_', - r'(?msu)(?<=[\s>])/(?P<words>[^/\*>]+)/', - r'(?msu)(?<=[\s>])~~(?P<words>[^~]+)~~', - r'(?msu)(?<=[\s>])\*(?P<words>[^\*]+)\*', - r'(?msu)(?<=[\s>])~(?P<words>[^~]+)~', - r'(?msu)(?<=[\s>])_/(?P<words>[^/_]+)/_', - r'(?msu)(?<=[\s>])_\*(?P<words>[^\*_]+)\*_', - r'(?msu)(?<=[\s>])\*/(?P<words>[^/\*]+)/\*', - r'(?msu)(?<=[\s>])_\*/(?P<words>[^\*_]+)/\*_', - r'(?msu)(?<=[\s>])/:(?P<words>[^:/]+):/', - r'(?msu)(?<=[\s>])\|:(?P<words>[^:\|]+):\|', + ur'(?msu)(?<=[\s>"“\'‘])_(?P<words>[^_]+)_', + ur'(?msu)(?<=[\s>"“\'‘])/(?P<words>[^/\*>]+)/', + ur'(?msu)(?<=[\s>"“\'‘])~~(?P<words>[^~]+)~~', + ur'(?msu)(?<=[\s>"“\'‘])\*(?P<words>[^\*]+)\*', + ur'(?msu)(?<=[\s>"“\'‘])~(?P<words>[^~]+)~', + ur'(?msu)(?<=[\s>"“\'‘])_/(?P<words>[^/_]+)/_', + ur'(?msu)(?<=[\s>"“\'‘])_\*(?P<words>[^\*_]+)\*_', + ur'(?msu)(?<=[\s>"“\'‘])\*/(?P<words>[^/\*]+)/\*', + ur'(?msu)(?<=[\s>"“\'‘])_\*/(?P<words>[^\*_]+)/\*_', + ur'(?msu)(?<=[\s>"“\'‘])/:(?P<words>[^:/]+):/', + ur'(?msu)(?<=[\s>"“\'‘])\|:(?P<words>[^:\|]+):\|', ] for word in ITALICIZE_WORDS: @@ -518,13 +518,13 @@ class HeuristicProcessor(object): if re.findall('(<|>)', replacement_break): if re.match('^<hr', replacement_break): if replacement_break.find('width') != -1: - width = int(re.sub('.*?width(:|=)(?P<wnum>\d+).*', '\g<wnum>', replacement_break)) - replacement_break = re.sub('(?i)(width=\d+\%?|width:\s*\d+(\%|px|pt|em)?;?)', '', replacement_break) - divpercent = (100 - width) / 2 - hr_open = re.sub('45', str(divpercent), hr_open) - scene_break = hr_open+replacement_break+'</div>' + width = int(re.sub('.*?width(:|=)(?P<wnum>\d+).*', '\g<wnum>', replacement_break)) + replacement_break = re.sub('(?i)(width=\d+\%?|width:\s*\d+(\%|px|pt|em)?;?)', '', replacement_break) + divpercent = (100 - width) / 2 + hr_open = re.sub('45', str(divpercent), hr_open) + scene_break = hr_open+replacement_break+'</div>' else: - scene_break = hr_open+'<hr style="height: 3px; background:#505050" /></div>' + scene_break = hr_open+'<hr style="height: 3px; background:#505050" /></div>' elif re.match('^<img', replacement_break): scene_break = self.scene_break_open+replacement_break+'</p>' else: @@ -584,10 +584,10 @@ class HeuristicProcessor(object): #print "styles for this line are: "+str(styles) split_styles = [] for style in styles: - #print "style is: "+str(style) - newstyle = style.split(':') - #print "newstyle is: "+str(newstyle) - split_styles.append(newstyle) + #print "style is: "+str(style) + newstyle = style.split(':') + #print "newstyle is: "+str(newstyle) + split_styles.append(newstyle) styles = split_styles for style, setting in styles: if style == 'text-align' and setting != 'left': @@ -764,6 +764,7 @@ class HeuristicProcessor(object): # Multiple sequential blank paragraphs are merged with appropriate margins # If non-blank scene breaks exist they are center aligned and styled with appropriate margins. if getattr(self.extra_opts, 'format_scene_breaks', False): + html = re.sub('(?i)<div[^>]*>\s*<br(\s?/)?>\s*</div>', '<p></p>', html) html = self.detect_whitespace(html) html = self.detect_soft_breaks(html) blanks_count = len(self.any_multi_blank.findall(html)) diff --git a/src/calibre/ebooks/epub/fix/container.py b/src/calibre/ebooks/epub/fix/container.py index 539d886312..1669290a7b 100644 --- a/src/calibre/ebooks/epub/fix/container.py +++ b/src/calibre/ebooks/epub/fix/container.py @@ -151,7 +151,7 @@ class Container(object): if name in self.mime_map: try: raw = self._parse(raw, self.mime_map[name]) - except XMLSyntaxError, err: + except XMLSyntaxError as err: raise ParseError(name, unicode(err)) self.cache[name] = raw return raw diff --git a/src/calibre/ebooks/epub/fix/main.py b/src/calibre/ebooks/epub/fix/main.py index fbfe80551d..e4c1a60a77 100644 --- a/src/calibre/ebooks/epub/fix/main.py +++ b/src/calibre/ebooks/epub/fix/main.py @@ -54,7 +54,7 @@ def main(args=sys.argv): epub = os.path.abspath(args[1]) try: run(epub, opts, default_log) - except ParseError, err: + except ParseError as err: default_log.error(unicode(err)) raise SystemExit(1) diff --git a/src/calibre/ebooks/epub/input.py b/src/calibre/ebooks/epub/input.py index e22ed27371..ac1d61ce59 100644 --- a/src/calibre/ebooks/epub/input.py +++ b/src/calibre/ebooks/epub/input.py @@ -103,10 +103,11 @@ class EPUBInput(InputFormatPlugin): t.set('href', guide_cover) t.set('title', 'Title Page') from calibre.ebooks import render_html_svg_workaround - renderer = render_html_svg_workaround(guide_cover, log) - if renderer is not None: - open('calibre_raster_cover.jpg', 'wb').write( - renderer) + if os.path.exists(guide_cover): + renderer = render_html_svg_workaround(guide_cover, log) + if renderer is not None: + open('calibre_raster_cover.jpg', 'wb').write( + renderer) def find_opf(self): def attr(n, attr): @@ -175,18 +176,18 @@ class EPUBInput(InputFormatPlugin): raise ValueError( 'EPUB files with DTBook markup are not supported') + not_for_spine = set() + for y in opf.itermanifest(): + id_ = y.get('id', None) + if id_ and y.get('media-type', None) in \ + ('application/vnd.adobe-page-template+xml',): + not_for_spine.add(id_) + for x in list(opf.iterspine()): ref = x.get('idref', None) - if ref is None: + if ref is None or ref in not_for_spine: x.getparent().remove(x) continue - for y in opf.itermanifest(): - if y.get('id', None) == ref and y.get('media-type', None) in \ - ('application/vnd.adobe-page-template+xml',): - p = x.getparent() - if p is not None: - p.remove(x) - break with open('content.opf', 'wb') as nopf: nopf.write(opf.render()) diff --git a/src/calibre/ebooks/epub/output.py b/src/calibre/ebooks/epub/output.py index 0ed6d7e222..bea90eeba8 100644 --- a/src/calibre/ebooks/epub/output.py +++ b/src/calibre/ebooks/epub/output.py @@ -413,6 +413,13 @@ class EPUBOutput(OutputFormatPlugin): rule.style.removeProperty('margin-left') # padding-left breaks rendering in webkit and gecko rule.style.removeProperty('padding-left') + # Change whitespace:pre to pre-line to accommodate readers that + # cannot scroll horizontally + for rule in stylesheet.data.cssRules.rulesOfType(CSSRule.STYLE_RULE): + style = rule.style + ws = style.getPropertyValue('white-space') + if ws == 'pre': + style.setProperty('white-space', 'pre-wrap') # }}} diff --git a/src/calibre/ebooks/fb2/fb2ml.py b/src/calibre/ebooks/fb2/fb2ml.py index 43f93807a1..b45f8f9f9e 100644 --- a/src/calibre/ebooks/fb2/fb2ml.py +++ b/src/calibre/ebooks/fb2/fb2ml.py @@ -10,7 +10,6 @@ Transform OEB content into FB2 markup from base64 import b64encode from datetime import datetime -from mimetypes import types_map import re import uuid @@ -18,9 +17,6 @@ from lxml import etree from calibre import prepare_string_for_xml from calibre.constants import __appname__, __version__ -from calibre.ebooks.oeb.base import XHTML, XHTML_NS, barename, namespace -from calibre.ebooks.oeb.stylizer import Stylizer -from calibre.ebooks.oeb.base import OEB_RASTER_IMAGES, OPF from calibre.utils.magick import Image class FB2MLizer(object): @@ -71,8 +67,8 @@ class FB2MLizer(object): return u'<?xml version="1.0" encoding="UTF-8"?>' + output def clean_text(self, text): - # Condense empty paragraphs into a line break. - text = re.sub(r'(?miu)(<p>\s*</p>\s*){3,}', '<p><empty-line /></p>', text) + # Condense empty paragraphs into a line break. + text = re.sub(r'(?miu)(<p>\s*</p>\s*){3,}', '<empty-line />', text) # Remove empty paragraphs. text = re.sub(r'(?miu)<p>\s*</p>', '', text) # Clean up pargraph endings. @@ -100,10 +96,8 @@ class FB2MLizer(object): return text def fb2_header(self): + from calibre.ebooks.oeb.base import OPF metadata = {} - metadata['author_first'] = u'' - metadata['author_middle'] = u'' - metadata['author_last'] = u'' metadata['title'] = self.oeb_book.metadata.title[0].value metadata['appname'] = __appname__ metadata['version'] = __version__ @@ -114,17 +108,38 @@ class FB2MLizer(object): metadata['lang'] = u'en' metadata['id'] = None metadata['cover'] = self.get_cover() + metadata['genre'] = self.opts.fb2_genre - author_parts = self.oeb_book.metadata.creator[0].value.split(' ') - if len(author_parts) == 1: - metadata['author_last'] = author_parts[0] - elif len(author_parts) == 2: - metadata['author_first'] = author_parts[0] - metadata['author_last'] = author_parts[1] - else: - metadata['author_first'] = author_parts[0] - metadata['author_middle'] = ' '.join(author_parts[1:-2]) - metadata['author_last'] = author_parts[-1] + metadata['author'] = u'' + for auth in self.oeb_book.metadata.creator: + author_first = u'' + author_middle = u'' + author_last = u'' + author_parts = auth.value.split(' ') + if len(author_parts) == 1: + author_last = author_parts[0] + elif len(author_parts) == 2: + author_first = author_parts[0] + author_last = author_parts[1] + else: + author_first = author_parts[0] + author_middle = ' '.join(author_parts[1:-1]) + author_last = author_parts[-1] + metadata['author'] += '<author>' + metadata['author'] += '<first-name>%s</first-name>' % prepare_string_for_xml(author_first) + if author_middle: + metadata['author'] += '<middle-name>%s</middle-name>' % prepare_string_for_xml(author_middle) + metadata['author'] += '<last-name>%s</last-name>' % prepare_string_for_xml(author_last) + metadata['author'] += '</author>' + if not metadata['author']: + metadata['author'] = u'<author><first-name></first-name><last-name><last-name></author>' + + metadata['sequence'] = u'' + if self.oeb_book.metadata.series: + index = '1' + if self.oeb_book.metadata.series_index: + index = self.oeb_book.metadata.series_index[0] + metadata['sequence'] = u'<sequence name="%s" number="%s" />' % (prepare_string_for_xml(u'%s' % self.oeb_book.metadata.series[0]), index) identifiers = self.oeb_book.metadata['identifier'] for x in identifiers: @@ -136,28 +151,21 @@ class FB2MLizer(object): metadata['id'] = str(uuid.uuid4()) for key, value in metadata.items(): - if not key == 'cover': + if key not in ('author', 'cover', 'sequence'): metadata[key] = prepare_string_for_xml(value) return u'<FictionBook xmlns="http://www.gribuser.ru/xml/fictionbook/2.0" xmlns:xlink="http://www.w3.org/1999/xlink">' \ '<description>' \ '<title-info>' \ - '<genre>antique</genre>' \ - '<author>' \ - '<first-name>%(author_first)s</first-name>' \ - '<middle-name>%(author_middle)s</middle-name>' \ - '<last-name>%(author_last)s</last-name>' \ - '</author>' \ + '<genre>%(genre)s</genre>' \ + '%(author)s' \ '<book-title>%(title)s</book-title>' \ '%(cover)s' \ '<lang>%(lang)s</lang>' \ + '%(sequence)s' \ '</title-info>' \ '<document-info>' \ - '<author>' \ - '<first-name></first-name>' \ - '<middle-name></middle-name>' \ - '<last-name></last-name>' \ - '</author>' \ + '%(author)s' \ '<program-used>%(appname)s %(version)s</program-used>' \ '<date>%(date)s</date>' \ '<id>%(id)s</id>' \ @@ -169,6 +177,8 @@ class FB2MLizer(object): return u'</FictionBook>' def get_cover(self): + from calibre.ebooks.oeb.base import OEB_RASTER_IMAGES + cover_href = None # Get the raster cover if it's available. @@ -202,6 +212,8 @@ class FB2MLizer(object): return u'' def get_text(self): + from calibre.ebooks.oeb.base import XHTML + from calibre.ebooks.oeb.stylizer import Stylizer text = ['<body>'] # Create main section if there are no others to create @@ -237,6 +249,8 @@ class FB2MLizer(object): ''' This function uses the self.image_hrefs dictionary mapping. It is populated by the dump_text function. ''' + from calibre.ebooks.oeb.base import OEB_RASTER_IMAGES + images = [] for item in self.oeb_book.manifest: # Don't write the image if it's not referenced in the document's text. @@ -244,7 +258,7 @@ class FB2MLizer(object): continue if item.media_type in OEB_RASTER_IMAGES: try: - if not item.media_type == types_map['.jpeg'] or not item.media_type == types_map['.jpg']: + if item.media_type != 'image/jpeg': im = Image() im.load(item.data) im.set_compression_quality(70) @@ -333,6 +347,8 @@ class FB2MLizer(object): @return: List of string representing the XHTML converted to FB2 markup. ''' + from calibre.ebooks.oeb.base import XHTML_NS, barename, namespace + # Ensure what we are converting is not a string and that the fist tag is part of the XHTML namespace. if not isinstance(elem_tree.tag, basestring) or namespace(elem_tree.tag) != XHTML_NS: return [] diff --git a/src/calibre/ebooks/fb2/output.py b/src/calibre/ebooks/fb2/output.py index bce0a00029..54bb4550d5 100644 --- a/src/calibre/ebooks/fb2/output.py +++ b/src/calibre/ebooks/fb2/output.py @@ -15,6 +15,133 @@ class FB2Output(OutputFormatPlugin): author = 'John Schember' file_type = 'fb2' + FB2_GENRES = [ + # Science Fiction & Fantasy + 'sf_history', # Alternative history + 'sf_action', # Action + 'sf_epic', # Epic + 'sf_heroic', # Heroic + 'sf_detective', # Detective + 'sf_cyberpunk', # Cyberpunk + 'sf_space', # Space + 'sf_social', # Social#philosophical + 'sf_horror', # Horror & mystic + 'sf_humor', # Humor + 'sf_fantasy', # Fantasy + 'sf', # Science Fiction + # Detectives & Thrillers + 'det_classic', # Classical detectives + 'det_police', # Police Stories + 'det_action', # Action + 'det_irony', # Ironical detectives + 'det_history', # Historical detectives + 'det_espionage', # Espionage detectives + 'det_crime', # Crime detectives + 'det_political', # Political detectives + 'det_maniac', # Maniacs + 'det_hard', # Hard#boiled + 'thriller', # Thrillers + 'detective', # Detectives + # Prose + 'prose_classic', # Classics prose + 'prose_history', # Historical prose + 'prose_contemporary', # Contemporary prose + 'prose_counter', # Counterculture + 'prose_rus_classic', # Russial classics prose + 'prose_su_classics', # Soviet classics prose + # Romance + 'love_contemporary', # Contemporary Romance + 'love_history', # Historical Romance + 'love_detective', # Detective Romance + 'love_short', # Short Romance + 'love_erotica', # Erotica + # Adventure + 'adv_western', # Western + 'adv_history', # History + 'adv_indian', # Indians + 'adv_maritime', # Maritime Fiction + 'adv_geo', # Travel & geography + 'adv_animal', # Nature & animals + 'adventure', # Other + # Children's + 'child_tale', # Fairy Tales + 'child_verse', # Verses + 'child_prose', # Prose + 'child_sf', # Science Fiction + 'child_det', # Detectives & Thrillers + 'child_adv', # Adventures + 'child_education', # Educational + 'children', # Other + # Poetry & Dramaturgy + 'poetry', # Poetry + 'dramaturgy', # Dramaturgy + # Antique literature + 'antique_ant', # Antique + 'antique_european', # European + 'antique_russian', # Old russian + 'antique_east', # Old east + 'antique_myths', # Myths. Legends. Epos + 'antique', # Other + # Scientific#educational + 'sci_history', # History + 'sci_psychology', # Psychology + 'sci_culture', # Cultural science + 'sci_religion', # Religious studies + 'sci_philosophy', # Philosophy + 'sci_politics', # Politics + 'sci_business', # Business literature + 'sci_juris', # Jurisprudence + 'sci_linguistic', # Linguistics + 'sci_medicine', # Medicine + 'sci_phys', # Physics + 'sci_math', # Mathematics + 'sci_chem', # Chemistry + 'sci_biology', # Biology + 'sci_tech', # Technical + 'science', # Other + # Computers & Internet + 'comp_www', # Internet + 'comp_programming', # Programming + 'comp_hard', # Hardware + 'comp_soft', # Software + 'comp_db', # Databases + 'comp_osnet', # OS & Networking + 'computers', # Other + # Reference + 'ref_encyc', # Encyclopedias + 'ref_dict', # Dictionaries + 'ref_ref', # Reference + 'ref_guide', # Guidebooks + 'reference', # Other + # Nonfiction + 'nonf_biography', # Biography & Memoirs + 'nonf_publicism', # Publicism + 'nonf_criticism', # Criticism + 'design', # Art & design + 'nonfiction', # Other + # Religion & Inspiration + 'religion_rel', # Religion + 'religion_esoterics', # Esoterics + 'religion_self', # Self#improvement + 'religion', # Other + # Humor + 'humor_anecdote', # Anecdote (funny stories) + 'humor_prose', # Prose + 'humor_verse', # Verses + 'humor', # Other + # Home & Family + 'home_cooking', # Cooking + 'home_pets', # Pets + 'home_crafts', # Hobbies & Crafts + 'home_entertain', # Entertaining + 'home_health', # Health + 'home_garden', # Garden + 'home_diy', # Do it yourself + 'home_sport', # Sports + 'home_sex', # Erotica & sex + 'home', # Other + ] + options = set([ OptionRecommendation(name='sectionize', recommended_value='files', level=OptionRecommendation.LOW, @@ -25,12 +152,17 @@ class FB2Output(OutputFormatPlugin): 'A value of "Table of Contents" turns the entries in the Table of Contents into titles and creates sections; ' 'if it fails, adjust the "Structure Detection" and/or "Table of Contents" settings ' '(turn on "Force use of auto-generated Table of Contents).')), + OptionRecommendation(name='fb2_genre', + recommended_value='antique', level=OptionRecommendation.LOW, + choices=FB2_GENRES, + help=(_('Genre for the book. Choices: %s\n\n See: ') % FB2_GENRES) + 'http://www.fictionbook.org/index.php/Eng:FictionBook_2.1_genres ' \ + + _('for a complete list with descriptions.')), ]) def convert(self, oeb_book, output_path, input_plugin, opts, log): from calibre.ebooks.oeb.transforms.jacket import linearize_jacket from calibre.ebooks.oeb.transforms.rasterize import SVGRasterizer, Unavailable - + try: rasterizer = SVGRasterizer() rasterizer(oeb_book, opts) diff --git a/src/calibre/ebooks/html/input.py b/src/calibre/ebooks/html/input.py index 1599d3c896..3d5f6c00ef 100644 --- a/src/calibre/ebooks/html/input.py +++ b/src/calibre/ebooks/html/input.py @@ -20,7 +20,7 @@ from itertools import izip from calibre.customize.conversion import InputFormatPlugin from calibre.ebooks.chardet import xml_to_unicode from calibre.customize.conversion import OptionRecommendation -from calibre.constants import islinux, isfreebsd, iswindows +from calibre.constants import islinux, isbsd, iswindows from calibre import unicode_path, as_unicode from calibre.utils.localization import get_lang from calibre.utils.filenames import ascii_filename @@ -110,7 +110,7 @@ class HTMLFile(object): try: with open(self.path, 'rb') as f: src = f.read() - except IOError, err: + except IOError as err: msg = 'Could not read from file: %s with error: %s'%(self.path, as_unicode(err)) if level == 0: raise IOError(msg) @@ -202,7 +202,7 @@ def traverse(path_to_html_file, max_levels=sys.maxint, verbose=0, encoding=None) raise IgnoreFile('%s is a binary file'%nf.path, -1) nl.append(nf) flat.append(nf) - except IgnoreFile, err: + except IgnoreFile as err: rejects.append(link) if not err.doesnt_exist or verbose > 1: print repr(err) @@ -302,20 +302,21 @@ class HTMLInput(InputFormatPlugin): if getattr(self, '_is_case_sensitive', None) is not None: return self._is_case_sensitive if not path or not os.path.exists(path): - return islinux or isfreebsd + return islinux or isbsd self._is_case_sensitive = not (os.path.exists(path.lower()) \ and os.path.exists(path.upper())) return self._is_case_sensitive def create_oebbook(self, htmlpath, basedir, opts, log, mi): from calibre.ebooks.conversion.plumber import create_oebbook - from calibre.ebooks.oeb.base import DirContainer, \ - rewrite_links, urlnormalize, urldefrag, BINARY_MIME, OEB_STYLES, \ - xpath + from calibre.ebooks.oeb.base import (DirContainer, + rewrite_links, urlnormalize, urldefrag, BINARY_MIME, OEB_STYLES, + xpath) from calibre import guess_type from calibre.ebooks.oeb.transforms.metadata import \ meta_info_to_oeb_metadata - import cssutils + import cssutils, logging + cssutils.log.setLevel(logging.WARN) self.OEB_STYLES = OEB_STYLES oeb = create_oebbook(log, None, opts, self, encoding=opts.input_encoding, populate=False) @@ -344,7 +345,8 @@ class HTMLInput(InputFormatPlugin): htmlfile_map = {} for f in filelist: path = f.path - oeb.container = DirContainer(os.path.dirname(path), log) + oeb.container = DirContainer(os.path.dirname(path), log, + ignore_opf=True) bname = os.path.basename(path) id, href = oeb.manifest.generate(id='html', href=ascii_filename(bname)) @@ -368,7 +370,7 @@ class HTMLInput(InputFormatPlugin): for f in filelist: path = f.path dpath = os.path.dirname(path) - oeb.container = DirContainer(dpath, log) + oeb.container = DirContainer(dpath, log, ignore_opf=True) item = oeb.manifest.hrefs[htmlfile_map[path]] rewrite_links(item.data, partial(self.resource_adder, base=dpath)) @@ -408,7 +410,7 @@ class HTMLInput(InputFormatPlugin): if not item.linear: continue toc.add(title, item.href) - oeb.container = DirContainer(os.getcwdu(), oeb.log) + oeb.container = DirContainer(os.getcwdu(), oeb.log, ignore_opf=True) return oeb def link_to_local_path(self, link_, base=None): @@ -455,7 +457,7 @@ class HTMLInput(InputFormatPlugin): href=bhref) self.oeb.log.debug('Added', link) self.oeb.container = self.DirContainer(os.path.dirname(link), - self.oeb.log) + self.oeb.log, ignore_opf=True) # Load into memory guessed = self.guess_type(href)[0] media_type = guessed or self.BINARY_MIME diff --git a/src/calibre/ebooks/html/meta.py b/src/calibre/ebooks/html/meta.py index 9a088efb16..07cf9236fc 100644 --- a/src/calibre/ebooks/html/meta.py +++ b/src/calibre/ebooks/html/meta.py @@ -4,7 +4,6 @@ __copyright__ = '2010, Fabian Grassl <fg@jusmeum.de>' __docformat__ = 'restructuredtext en' -from calibre.ebooks.oeb.base import namespace, barename, DC11_NS class EasyMeta(object): @@ -12,6 +11,7 @@ class EasyMeta(object): self.meta = meta def __iter__(self): + from calibre.ebooks.oeb.base import namespace, barename, DC11_NS meta = self.meta for item_name in meta.items: for item in meta[item_name]: diff --git a/src/calibre/ebooks/html/output.py b/src/calibre/ebooks/html/output.py index 5c984162ac..fe7b4cf274 100644 --- a/src/calibre/ebooks/html/output.py +++ b/src/calibre/ebooks/html/output.py @@ -12,7 +12,6 @@ from os.path import dirname, abspath, relpath, exists, basename from lxml import etree from templite import Templite -from calibre.ebooks.oeb.base import element from calibre.customize.conversion import OutputFormatPlugin, OptionRecommendation from calibre import CurrentDir from calibre.ptempfile import PersistentTemporaryDirectory @@ -51,6 +50,7 @@ class HTMLOutput(OutputFormatPlugin): ''' Generate table of contents ''' + from calibre.ebooks.oeb.base import element with CurrentDir(output_dir): def build_node(current_node, parent=None): if parent is None: diff --git a/src/calibre/ebooks/htmlz/__init__.py b/src/calibre/ebooks/htmlz/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/src/calibre/ebooks/htmlz/input.py b/src/calibre/ebooks/htmlz/input.py new file mode 100644 index 0000000000..743d8e53eb --- /dev/null +++ b/src/calibre/ebooks/htmlz/input.py @@ -0,0 +1,95 @@ +# -*- coding: utf-8 -*- + +from __future__ import (unicode_literals, division, absolute_import, print_function) + +__license__ = 'GPL 3' +__copyright__ = '2011, John Schember <john@nachtimwald.com>' +__docformat__ = 'restructuredtext en' + +import os + +from calibre import guess_type, walk +from calibre.customize.conversion import InputFormatPlugin +from calibre.ebooks.chardet import xml_to_unicode +from calibre.ebooks.metadata.opf2 import OPF +from calibre.utils.zipfile import ZipFile + +class HTMLZInput(InputFormatPlugin): + + name = 'HTLZ Input' + author = 'John Schember' + description = 'Convert HTML files to HTML' + file_types = set(['htmlz']) + + def convert(self, stream, options, file_ext, log, + accelerators): + self.log = log + html = u'' + + # Extract content from zip archive. + zf = ZipFile(stream) + zf.extractall() + + for x in walk('.'): + if os.path.splitext(x)[1].lower() in ('.html', '.xhtml', '.htm'): + with open(x, 'rb') as tf: + html = tf.read() + break + + # Encoding + if options.input_encoding: + ienc = options.input_encoding + else: + ienc = xml_to_unicode(html[:4096])[-1] + html = html.decode(ienc, 'replace') + + # Run the HTML through the html processing plugin. + from calibre.customize.ui import plugin_for_input_format + html_input = plugin_for_input_format('html') + for opt in html_input.options: + setattr(options, opt.option.name, opt.recommended_value) + options.input_encoding = 'utf-8' + base = os.getcwdu() + fname = os.path.join(base, 'index.html') + c = 0 + while os.path.exists(fname): + c += 1 + fname = 'index%d.html'%c + htmlfile = open(fname, 'wb') + with htmlfile: + htmlfile.write(html.encode('utf-8')) + odi = options.debug_pipeline + options.debug_pipeline = None + # Generate oeb from html conversion. + oeb = html_input.convert(open(htmlfile.name, 'rb'), options, 'html', log, + {}) + options.debug_pipeline = odi + os.remove(htmlfile.name) + + # Set metadata from file. + from calibre.customize.ui import get_file_type_metadata + from calibre.ebooks.oeb.transforms.metadata import meta_info_to_oeb_metadata + mi = get_file_type_metadata(stream, file_ext) + meta_info_to_oeb_metadata(mi, oeb.metadata, log) + + # Get the cover path from the OPF. + cover_path = None + opf = None + for x in walk('.'): + if os.path.splitext(x)[1].lower() in ('.opf'): + opf = x + break + if opf: + opf = OPF(opf, basedir=os.getcwd()) + cover_path = opf.raster_cover + # Set the cover. + if cover_path: + cdata = None + with open(os.path.join(os.getcwd(), cover_path), 'rb') as cf: + cdata = cf.read() + cover_name = os.path.basename(cover_path) + id, href = oeb.manifest.generate('cover', cover_name) + oeb.manifest.add(id, href, guess_type(cover_name)[0], data=cdata) + oeb.guide.add('cover', 'Cover', href) + + return oeb diff --git a/src/calibre/ebooks/htmlz/oeb2html.py b/src/calibre/ebooks/htmlz/oeb2html.py new file mode 100644 index 0000000000..b3bd9d7782 --- /dev/null +++ b/src/calibre/ebooks/htmlz/oeb2html.py @@ -0,0 +1,385 @@ +# -*- coding: utf-8 -*- + +from __future__ import (unicode_literals, division, absolute_import, print_function) + +__license__ = 'GPL 3' +__copyright__ = '2011, John Schember <john@nachtimwald.com>' +__docformat__ = 'restructuredtext en' + +''' +Transform OEB content into a single (more or less) HTML file. +''' + +import os + +from functools import partial +from lxml import html +from urlparse import urldefrag + +from calibre import prepare_string_for_xml +from calibre.ebooks.oeb.base import XHTML, XHTML_NS, barename, namespace,\ + OEB_IMAGES, XLINK, rewrite_links +from calibre.ebooks.oeb.stylizer import Stylizer +from calibre.utils.logging import default_log + +class OEB2HTML(object): + ''' + Base class. All subclasses should implement dump_text to actually transform + content. Also, callers should use oeb2html to get the transformed html. + links and images can be retrieved after calling oeb2html to get the mapping + of OEB links and images to the new names used in the html returned by oeb2html. + Images will always be referenced as if they are in an images directory. + + Use get_css to get the CSS classes for the OEB document as a string. + ''' + + def __init__(self, log=None): + self.log = default_log if log is None else log + self.links = {} + self.images = {} + + def oeb2html(self, oeb_book, opts): + self.log.info('Converting OEB book to HTML...') + self.opts = opts + self.links = {} + self.images = {} + self.base_hrefs = [item.href for item in oeb_book.spine] + self.map_resources(oeb_book) + + return self.mlize_spine(oeb_book) + + def mlize_spine(self, oeb_book): + output = [u'<html><body><head><meta http-equiv="Content-Type" content="text/html;charset=utf-8" /></head>'] + for item in oeb_book.spine: + self.log.debug('Converting %s to HTML...' % item.href) + self.rewrite_ids(item.data, item) + rewrite_links(item.data, partial(self.rewrite_link, page=item)) + stylizer = Stylizer(item.data, item.href, oeb_book, self.opts) + output += self.dump_text(item.data.find(XHTML('body')), stylizer, item) + output.append('\n\n') + output.append('</body></html>') + return ''.join(output) + + def dump_text(self, elem, stylizer, page): + raise NotImplementedError + + def get_link_id(self, href, id=''): + if id: + href += '#%s' % id + if href not in self.links: + self.links[href] = '#calibre_link-%s' % len(self.links.keys()) + return self.links[href] + + def map_resources(self, oeb_book): + for item in oeb_book.manifest: + if item.media_type in OEB_IMAGES: + if item.href not in self.images: + ext = os.path.splitext(item.href)[1] + fname = '%s%s' % (len(self.images), ext) + fname = fname.zfill(10) + self.images[item.href] = fname + if item in oeb_book.spine: + self.get_link_id(item.href) + root = item.data.find(XHTML('body')) + link_attrs = set(html.defs.link_attrs) + link_attrs.add(XLINK('href')) + for el in root.iter(): + attribs = el.attrib + try: + if not isinstance(el.tag, basestring): + continue + except: + continue + for attr in attribs: + if attr in link_attrs: + href = item.abshref(attribs[attr]) + href, id = urldefrag(href) + if href in self.base_hrefs: + self.get_link_id(href, id) + + def rewrite_link(self, url, page=None): + if not page: + return url + abs_url = page.abshref(url) + if abs_url in self.images: + return 'images/%s' % self.images[abs_url] + if abs_url in self.links: + return self.links[abs_url] + return url + + def rewrite_ids(self, root, page): + for el in root.iter(): + try: + tag = el.tag + except UnicodeDecodeError: + continue + if tag == XHTML('body'): + el.attrib['id'] = self.get_link_id(page.href)[1:] + continue + if 'id' in el.attrib: + el.attrib['id'] = self.get_link_id(page.href, el.attrib['id'])[1:] + + def get_css(self, oeb_book): + css = u'' + for item in oeb_book.manifest: + if item.media_type == 'text/css': + css = item.data.cssText + break + return css + + +class OEB2HTMLNoCSSizer(OEB2HTML): + ''' + This will remap a small number of CSS styles to equivalent HTML tags. + ''' + + def dump_text(self, elem, stylizer, page): + ''' + @elem: The element in the etree that we are working on. + @stylizer: The style information attached to the element. + ''' + + # We can only processes tags. If there isn't a tag return any text. + if not isinstance(elem.tag, basestring) \ + or namespace(elem.tag) != XHTML_NS: + p = elem.getparent() + if p is not None and isinstance(p.tag, basestring) and namespace(p.tag) == XHTML_NS \ + and elem.tail: + return [elem.tail] + return [''] + + # Setup our variables. + text = [''] + style = stylizer.style(elem) + tags = [] + tag = barename(elem.tag) + attribs = elem.attrib + + if tag == 'body': + tag = 'div' + tags.append(tag) + + # Ignore anything that is set to not be displayed. + if style['display'] in ('none', 'oeb-page-head', 'oeb-page-foot') \ + or style['visibility'] == 'hidden': + return [''] + + # Remove attributes we won't want. + if 'class' in attribs: + del attribs['class'] + if 'style' in attribs: + del attribs['style'] + + # Turn the rest of the attributes into a string we can write with the tag. + at = '' + for k, v in attribs.items(): + at += ' %s="%s"' % (k, prepare_string_for_xml(v, attribute=True)) + + # Write the tag. + text.append('<%s%s>' % (tag, at)) + + # Turn styles into tags. + if style['font-weight'] in ('bold', 'bolder'): + text.append('<b>') + tags.append('b') + if style['font-style'] == 'italic': + text.append('<i>') + tags.append('i') + if style['text-decoration'] == 'underline': + text.append('<u>') + tags.append('u') + if style['text-decoration'] == 'line-through': + text.append('<s>') + tags.append('s') + + # Process tags that contain text. + if hasattr(elem, 'text') and elem.text: + text.append(elem.text) + + # Recurse down into tags within the tag we are in. + for item in elem: + text += self.dump_text(item, stylizer, page) + + # Close all open tags. + tags.reverse() + for t in tags: + text.append('</%s>' % t) + + # Add the text that is outside of the tag. + if hasattr(elem, 'tail') and elem.tail: + text.append(elem.tail) + + return text + + +class OEB2HTMLInlineCSSizer(OEB2HTML): + ''' + Turns external CSS classes into inline style attributes. + ''' + + def dump_text(self, elem, stylizer, page): + ''' + @elem: The element in the etree that we are working on. + @stylizer: The style information attached to the element. + ''' + + # We can only processes tags. If there isn't a tag return any text. + if not isinstance(elem.tag, basestring) \ + or namespace(elem.tag) != XHTML_NS: + p = elem.getparent() + if p is not None and isinstance(p.tag, basestring) and namespace(p.tag) == XHTML_NS \ + and elem.tail: + return [elem.tail] + return [''] + + # Setup our variables. + text = [''] + style = stylizer.style(elem) + tags = [] + tag = barename(elem.tag) + attribs = elem.attrib + + style_a = '%s' % style + if tag == 'body': + tag = 'div' + if not style['page-break-before'] == 'always': + style_a = 'page-break-before: always;' + ' ' if style_a else '' + style_a + tags.append(tag) + + # Remove attributes we won't want. + if 'class' in attribs: + del attribs['class'] + if 'style' in attribs: + del attribs['style'] + + # Turn the rest of the attributes into a string we can write with the tag. + at = '' + for k, v in attribs.items(): + at += ' %s="%s"' % (k, prepare_string_for_xml(v, attribute=True)) + + # Turn style into strings for putting in the tag. + style_t = '' + if style_a: + style_t = ' style="%s"' % style_a + + # Write the tag. + text.append('<%s%s%s>' % (tag, at, style_t)) + + # Process tags that contain text. + if hasattr(elem, 'text') and elem.text: + text.append(elem.text) + + # Recurse down into tags within the tag we are in. + for item in elem: + text += self.dump_text(item, stylizer, page) + + # Close all open tags. + tags.reverse() + for t in tags: + text.append('</%s>' % t) + + # Add the text that is outside of the tag. + if hasattr(elem, 'tail') and elem.tail: + text.append(elem.tail) + + return text + + +class OEB2HTMLClassCSSizer(OEB2HTML): + ''' + Use CSS classes. css_style option can specify whether to use + inline classes (style tag in the head) or reference an external + CSS file called style.css. + ''' + + def mlize_spine(self, oeb_book): + output = [] + for item in oeb_book.spine: + self.log.debug('Converting %s to HTML...' % item.href) + self.rewrite_ids(item.data, item) + rewrite_links(item.data, partial(self.rewrite_link, page=item)) + stylizer = Stylizer(item.data, item.href, oeb_book, self.opts) + output += self.dump_text(item.data.find(XHTML('body')), stylizer, item) + output.append('\n\n') + if self.opts.htmlz_class_style == 'external': + css = u'<link href="style.css" rel="stylesheet" type="text/css" />' + else: + css = u'<style type="text/css">' + self.get_css(oeb_book) + u'</style>' + output = [u'<html><head><meta http-equiv="Content-Type" content="text/html;charset=utf-8" />'] + [css] + [u'</head><body>'] + output + [u'</body></html>'] + return ''.join(output) + + def dump_text(self, elem, stylizer, page): + ''' + @elem: The element in the etree that we are working on. + @stylizer: The style information attached to the element. + ''' + + # We can only processes tags. If there isn't a tag return any text. + if not isinstance(elem.tag, basestring) \ + or namespace(elem.tag) != XHTML_NS: + p = elem.getparent() + if p is not None and isinstance(p.tag, basestring) and namespace(p.tag) == XHTML_NS \ + and elem.tail: + return [elem.tail] + return [''] + + # Setup our variables. + text = [''] + tags = [] + tag = barename(elem.tag) + attribs = elem.attrib + + if tag == 'body': + tag = 'div' + tags.append(tag) + + # Remove attributes we won't want. + if 'style' in attribs: + del attribs['style'] + + # Turn the rest of the attributes into a string we can write with the tag. + at = '' + for k, v in attribs.items(): + at += ' %s="%s"' % (k, prepare_string_for_xml(v, attribute=True)) + + # Write the tag. + text.append('<%s%s>' % (tag, at)) + + # Process tags that contain text. + if hasattr(elem, 'text') and elem.text: + text.append(elem.text) + + # Recurse down into tags within the tag we are in. + for item in elem: + text += self.dump_text(item, stylizer, page) + + # Close all open tags. + tags.reverse() + for t in tags: + text.append('</%s>' % t) + + # Add the text that is outside of the tag. + if hasattr(elem, 'tail') and elem.tail: + text.append(elem.tail) + + return text + + +def oeb2html_no_css(oeb_book, log, opts): + izer = OEB2HTMLNoCSSizer(log) + html = izer.oeb2html(oeb_book, opts) + images = izer.images + return (html, images) + +def oeb2html_inline_css(oeb_book, log, opts): + izer = OEB2HTMLInlineCSSizer(log) + html = izer.oeb2html(oeb_book, opts) + images = izer.images + return (html, images) + +def oeb2html_class_css(oeb_book, log, opts): + izer = OEB2HTMLClassCSSizer(log) + setattr(opts, 'class_style', 'inline') + html = izer.oeb2html(oeb_book, opts) + images = izer.images + return (html, images) diff --git a/src/calibre/ebooks/htmlz/output.py b/src/calibre/ebooks/htmlz/output.py new file mode 100644 index 0000000000..a1ef57af2c --- /dev/null +++ b/src/calibre/ebooks/htmlz/output.py @@ -0,0 +1,111 @@ +# -*- coding: utf-8 -*- + +from __future__ import (unicode_literals, division, absolute_import, print_function) + +__license__ = 'GPL 3' +__copyright__ = '2011, John Schember <john@nachtimwald.com>' +__docformat__ = 'restructuredtext en' + +import os +from cStringIO import StringIO + +from lxml import etree + +from calibre.customize.conversion import OutputFormatPlugin, \ + OptionRecommendation +from calibre.ebooks.metadata.opf2 import OPF, metadata_to_opf +from calibre.ptempfile import TemporaryDirectory +from calibre.utils.zipfile import ZipFile + +class HTMLZOutput(OutputFormatPlugin): + + name = 'HTMLZ Output' + author = 'John Schember' + file_type = 'htmlz' + + options = set([ + OptionRecommendation(name='htmlz_css_type', recommended_value='class', + level=OptionRecommendation.LOW, + choices=['class', 'inline', 'tag'], + help=_('Specify the handling of CSS. Default is class.\n' + 'class: Use CSS classes and have elements reference them.\n' + 'inline: Write the CSS as an inline style attribute.\n' + 'tag: Turn as many CSS styles as possible into HTML tags.' + )), + OptionRecommendation(name='htmlz_class_style', recommended_value='external', + level=OptionRecommendation.LOW, + choices=['external', 'inline'], + help=_('How to handle the CSS when using css-type = \'class\'.\n' + 'Default is external.\n' + 'external: Use an external CSS file that is linked in the document.\n' + 'inline: Place the CSS in the head section of the document.' + )), + ]) + + def convert(self, oeb_book, output_path, input_plugin, opts, log): + from calibre.ebooks.oeb.base import OEB_IMAGES, SVG_MIME + + # HTML + if opts.htmlz_css_type == 'inline': + from calibre.ebooks.htmlz.oeb2html import OEB2HTMLInlineCSSizer + OEB2HTMLizer = OEB2HTMLInlineCSSizer + elif opts.htmlz_css_type == 'tag': + from calibre.ebooks.htmlz.oeb2html import OEB2HTMLNoCSSizer + OEB2HTMLizer = OEB2HTMLNoCSSizer + else: + from calibre.ebooks.htmlz.oeb2html import OEB2HTMLClassCSSizer as OEB2HTMLizer + + with TemporaryDirectory('_htmlz_output') as tdir: + htmlizer = OEB2HTMLizer(log) + html = htmlizer.oeb2html(oeb_book, opts) + + with open(os.path.join(tdir, 'index.html'), 'wb') as tf: + tf.write(html) + + # CSS + if opts.htmlz_css_type == 'class' and opts.htmlz_class_style == 'external': + with open(os.path.join(tdir, 'style.css'), 'wb') as tf: + tf.write(htmlizer.get_css(oeb_book)) + + # Images + images = htmlizer.images + if images: + if not os.path.exists(os.path.join(tdir, 'images')): + os.makedirs(os.path.join(tdir, 'images')) + for item in oeb_book.manifest: + if item.media_type in OEB_IMAGES and item.href in images: + if item.media_type == SVG_MIME: + data = unicode(etree.tostring(item.data, encoding=unicode)) + else: + data = item.data + fname = os.path.join(tdir, 'images', images[item.href]) + with open(fname, 'wb') as img: + img.write(data) + + # Cover + cover_path = None + try: + cover_data = None + if oeb_book.metadata.cover: + term = oeb_book.metadata.cover[0].term + cover_data = oeb_book.guide[term].item.data + if cover_data: + from calibre.utils.magick.draw import save_cover_data_to + cover_path = os.path.join(tdir, 'cover.jpg') + with open(cover_path, 'w') as cf: + cf.write('') + save_cover_data_to(cover_data, cover_path) + except: + import traceback + traceback.print_exc() + + # Metadata + with open(os.path.join(tdir, 'metadata.opf'), 'wb') as mdataf: + opf = OPF(StringIO(etree.tostring(oeb_book.metadata.to_opf1()))) + mi = opf.to_book_metadata() + if cover_path: + mi.cover = 'cover.jpg' + mdataf.write(metadata_to_opf(mi)) + + htmlz = ZipFile(output_path, 'w') + htmlz.add_dir(tdir) diff --git a/src/calibre/ebooks/lrf/html/convert_from.py b/src/calibre/ebooks/lrf/html/convert_from.py index 3be8f85e45..4ee1538e3f 100644 --- a/src/calibre/ebooks/lrf/html/convert_from.py +++ b/src/calibre/ebooks/lrf/html/convert_from.py @@ -332,7 +332,7 @@ class HTMLConverter(object): soup = BeautifulSoup(raw, convertEntities=BeautifulSoup.XHTML_ENTITIES, markupMassage=nmassage) - except ConversionError, err: + except ConversionError as err: if 'Failed to coerce to unicode' in str(err): raw = unicode(raw, 'utf8', 'replace') soup = BeautifulSoup(raw, @@ -935,7 +935,7 @@ class HTMLConverter(object): try: im = PILImage.open(path) - except IOError, err: + except IOError as err: self.log.warning('Unable to process image: %s\n%s'%( original_path, err)) return encoding = detect_encoding(im) @@ -953,7 +953,7 @@ class HTMLConverter(object): pt.close() self.scaled_images[path] = pt return pt.name - except (IOError, SystemError), err: # PIL chokes on interlaced PNG images as well a some GIF images + except (IOError, SystemError) as err: # PIL chokes on interlaced PNG images as well a some GIF images self.log.warning(_('Unable to process image %s. Error: %s')%(path, err)) if width == None or height == None: @@ -1013,7 +1013,7 @@ class HTMLConverter(object): if not self.images.has_key(path): try: self.images[path] = ImageStream(path, encoding=encoding) - except LrsError, err: + except LrsError as err: self.log.warning(_('Could not process image: %s\n%s')%( original_path, err)) return @@ -1768,7 +1768,7 @@ class HTMLConverter(object): tag_css = self.tag_css(tag)[0] # Table should not inherit CSS try: self.process_table(tag, tag_css) - except Exception, err: + except Exception as err: self.log.warning(_('An error occurred while processing a table: %s. Ignoring table markup.')%repr(err)) self.log.exception('') self.log.debug(_('Bad table:\n%s')%unicode(tag)[:300]) @@ -1858,7 +1858,7 @@ def process_file(path, options, logger): tf.close() tim.save(tf.name) tpath = tf.name - except IOError, err: # PIL sometimes fails, for example on interlaced PNG files + except IOError as err: # PIL sometimes fails, for example on interlaced PNG files logger.warn(_('Could not read cover image: %s'), err) options.cover = None else: diff --git a/src/calibre/ebooks/lrf/input.py b/src/calibre/ebooks/lrf/input.py index 70f3c3a15a..9777a8a998 100644 --- a/src/calibre/ebooks/lrf/input.py +++ b/src/calibre/ebooks/lrf/input.py @@ -6,8 +6,8 @@ __license__ = 'GPL v3' __copyright__ = '2009, Kovid Goyal <kovid@kovidgoyal.net>' __docformat__ = 'restructuredtext en' -import os, textwrap -from copy import deepcopy +import os, textwrap, sys, operator +from copy import deepcopy, copy from lxml import etree @@ -149,9 +149,65 @@ class TextBlock(etree.XSLTExtension): self.root = root self.parent = root self.add_text_to = (self.parent, 'text') + self.fix_deep_nesting(node) for child in node: self.process_child(child) + def fix_deep_nesting(self, node): + deepest = 1 + + def depth(node): + parent = node.getparent() + ans = 1 + while parent is not None: + ans += 1 + parent = parent.getparent() + return ans + + for span in node.xpath('descendant::Span'): + d = depth(span) + if d > deepest: + deepest = d + if d > 500: + break + + if deepest < 500: + return + + self.log.warn('Found deeply nested spans. Flattening.') + #with open('/t/before.xml', 'wb') as f: + # f.write(etree.tostring(node, method='xml')) + + spans = [(depth(span), span) for span in node.xpath('descendant::Span')] + spans.sort(key=operator.itemgetter(0), reverse=True) + + for depth, span in spans: + if depth < 3: + continue + p = span.getparent() + gp = p.getparent() + idx = p.index(span) + pidx = gp.index(p) + children = list(p)[idx:] + t = children[-1].tail + t = t if t else '' + children[-1].tail = t + (p.tail if p.tail else '') + p.tail = '' + pattrib = dict(**p.attrib) if p.tag == 'Span' else {} + for child in children: + p.remove(child) + if pattrib and child.tag == "Span": + attrib = copy(pattrib) + attrib.update(child.attrib) + child.attrib.update(attrib) + + + for child in reversed(children): + gp.insert(pidx+1, child) + + #with open('/t/after.xml', 'wb') as f: + # f.write(etree.tostring(node, method='xml')) + def add_text(self, text): if text: if getattr(self.add_text_to[0], self.add_text_to[1]) is None: @@ -413,7 +469,12 @@ class LRFInput(InputFormatPlugin): ('calibre', 'image-block'): image_block, } transform = etree.XSLT(styledoc, extensions=extensions) - result = transform(doc) + try: + result = transform(doc) + except RuntimeError: + sys.setrecursionlimit(5000) + result = transform(doc) + with open('content.opf', 'wb') as f: f.write(result) styles.write() diff --git a/src/calibre/ebooks/markdown/markdown.py b/src/calibre/ebooks/markdown/markdown.py index e734079116..677047878a 100644 --- a/src/calibre/ebooks/markdown/markdown.py +++ b/src/calibre/ebooks/markdown/markdown.py @@ -34,7 +34,7 @@ License: GPL 2 (http://www.gnu.org/copyleft/gpl.html) or BSD import re, sys, codecs from logging import getLogger, StreamHandler, Formatter, \ - DEBUG, INFO, WARN, ERROR, CRITICAL + DEBUG, INFO, WARN, CRITICAL MESSAGE_THRESHOLD = CRITICAL @@ -95,7 +95,7 @@ def removeBOM(text, encoding): # and uses the actual name of the executable called.) EXECUTABLE_NAME_FOR_USAGE = "python markdown.py" - + # --------------- CONSTANTS YOU _SHOULD NOT_ HAVE TO CHANGE ---------- @@ -242,8 +242,6 @@ class Element: if bidi: - orig_bidi = self.bidi - if not self.bidi or self.isDocumentElement: # Once the bidi is set don't change it (except for doc element) self.bidi = bidi @@ -319,7 +317,7 @@ class Element: childBuffer += "/>" - + buffer += "<" + self.nodeName if self.nodeName in ['p', 'li', 'ul', 'ol', @@ -330,10 +328,10 @@ class Element: bidi = self.bidi else: bidi = self.doc.bidi - + if bidi=="rtl": self.setAttribute("dir", "rtl") - + for attr in self.attributes: value = self.attribute_values[attr] value = self.doc.normalizeEntities(value, @@ -358,7 +356,7 @@ class TextNode: attrRegExp = re.compile(r'\{@([^\}]*)=([^\}]*)}') # {@id=123} def __init__ (self, text): - self.value = text + self.value = text def attributeCallback(self, match): @@ -372,7 +370,7 @@ class TextNode: text = self.value self.parent.setBidi(getBidiType(text)) - + if not text.startswith(HTML_PLACEHOLDER_PREFIX): if self.parent.nodeName == "p": text = text.replace("\n", "\n ") @@ -413,11 +411,11 @@ There are two types of preprocessors: TextPreprocessor and Preprocessor. class TextPreprocessor: ''' TextPreprocessors are run before the text is broken into lines. - + Each TextPreprocessor implements a "run" method that takes a pointer to a text string of the document, modifies it as necessary and returns - either the same pointer or a pointer to a new string. - + either the same pointer or a pointer to a new string. + TextPreprocessors must extend markdown.TextPreprocessor. ''' @@ -431,18 +429,18 @@ class Preprocessor: Each preprocessor implements a "run" method that takes a pointer to a list of lines of the document, modifies it as necessary and returns - either the same pointer or a pointer to a new list. - + either the same pointer or a pointer to a new list. + Preprocessors must extend markdown.Preprocessor. ''' def run(self, lines): pass - + class HtmlBlockPreprocessor(TextPreprocessor): """Removes html blocks from the source text and stores it.""" - + def _get_left_tag(self, block): return block[1:].replace(">", " ", 1).split()[0].lower() @@ -451,7 +449,7 @@ class HtmlBlockPreprocessor(TextPreprocessor): return block.rstrip()[-len(left_tag)-2:-1].lower() def _equal_tags(self, left_tag, right_tag): - + if left_tag == 'div' or left_tag[0] in ['?', '@', '%']: # handle PHP, etc. return True if ("/" + left_tag) == right_tag: @@ -467,17 +465,17 @@ class HtmlBlockPreprocessor(TextPreprocessor): def _is_oneliner(self, tag): return (tag in ['hr', 'hr/']) - + def run(self, text): new_blocks = [] text = text.split("\n\n") - + items = [] left_tag = '' right_tag = '' in_tag = False # flag - + for block in text: if block.startswith("\n"): block = block[1:] @@ -485,7 +483,7 @@ class HtmlBlockPreprocessor(TextPreprocessor): if not in_tag: if block.startswith("<"): - + left_tag = self._get_left_tag(block) right_tag = self._get_right_tag(left_tag, block) @@ -497,13 +495,13 @@ class HtmlBlockPreprocessor(TextPreprocessor): if self._is_oneliner(left_tag): new_blocks.append(block.strip()) continue - + if block[1] == "!": # is a comment block left_tag = "--" right_tag = self._get_right_tag(left_tag, block) # keep checking conditions below and maybe just append - + if block.rstrip().endswith(">") \ and self._equal_tags(left_tag, right_tag): new_blocks.append( @@ -519,9 +517,9 @@ class HtmlBlockPreprocessor(TextPreprocessor): else: items.append(block.strip()) - + right_tag = self._get_right_tag(left_tag, block) - + if self._equal_tags(left_tag, right_tag): # if find closing tag in_tag = False @@ -532,7 +530,7 @@ class HtmlBlockPreprocessor(TextPreprocessor): if items: new_blocks.append(self.stash.store('\n\n'.join(items))) new_blocks.append('\n') - + return "\n\n".join(new_blocks) HTML_BLOCK_PREPROCESSOR = HtmlBlockPreprocessor() @@ -605,7 +603,7 @@ LINE_PREPROCESSOR = LinePreprocessor() class ReferencePreprocessor(Preprocessor): - ''' + ''' Removes reference definitions from the text and stores them for later use. ''' @@ -760,7 +758,7 @@ class BacktickPattern (Pattern): return el -class DoubleTagPattern (SimpleTagPattern): +class DoubleTagPattern (SimpleTagPattern): def handleMatch(self, m, doc): tag1, tag2 = self.tag.split(",") @@ -775,7 +773,6 @@ class HtmlPattern (Pattern): def handleMatch (self, m, doc): rawhtml = m.group(2) - inline = True place_holder = self.stash.store(rawhtml) return doc.createTextNode(place_holder) @@ -926,11 +923,11 @@ There are two types of post-processors: Postprocessor and TextPostprocessor class Postprocessor: ''' Postprocessors are run before the dom it converted back into text. - + Each Postprocessor implements a "run" method that takes a pointer to a - NanoDom document, modifies it as necessary and returns a NanoDom + NanoDom document, modifies it as necessary and returns a NanoDom document. - + Postprocessors must extend markdown.Postprocessor. There are currently no standard post-processors, but the footnote @@ -945,10 +942,10 @@ class Postprocessor: class TextPostprocessor: ''' TextPostprocessors are run after the dom it converted back into text. - + Each TextPostprocessor implements a "run" method that takes a pointer to a text string, modifies it as necessary and returns a text string. - + TextPostprocessors must extend markdown.TextPostprocessor. ''' @@ -971,7 +968,7 @@ class RawHtmlTextPostprocessor(TextPostprocessor): html = '' else: html = HTML_REMOVED_TEXT - + text = text.replace("<p>%s\n</p>" % (HTML_PLACEHOLDER % i), html + "\n") text = text.replace(HTML_PLACEHOLDER % i, html) @@ -1031,7 +1028,6 @@ class BlockGuru: remainder of the original list""" items = [] - item = -1 i = 0 # to keep track of where we are @@ -1187,7 +1183,7 @@ class Markdown: RAWHTMLTEXTPOSTPROCESSOR] self.prePatterns = [] - + self.inlinePatterns = [DOUBLE_BACKTICK_PATTERN, BACKTICK_PATTERN, @@ -1241,7 +1237,7 @@ class Markdown: configs_for_ext = configs[ext] else: configs_for_ext = [] - extension = module.makeExtension(configs_for_ext) + extension = module.makeExtension(configs_for_ext) extension.extendMarkdown(self, globals()) @@ -1310,7 +1306,7 @@ class Markdown: else: buffer.append(line) self._processSection(self.top_element, buffer) - + #self._processSection(self.top_element, self.lines) # Not sure why I put this in but let's leave it for now. @@ -1426,7 +1422,7 @@ class Markdown: for item in list: el.appendChild(item) - + def _processUList(self, parent_elem, lines, inList): self._processList(parent_elem, lines, inList, @@ -1458,7 +1454,7 @@ class Markdown: i = 0 # a counter to keep track of where we are - for line in lines: + for line in lines: loose = 0 if not line.strip(): @@ -1477,7 +1473,7 @@ class Markdown: # Check if the next non-blank line is still a part of the list if ( RE.regExp['ul'].match(next) or - RE.regExp['ol'].match(next) or + RE.regExp['ol'].match(next) or RE.regExp['tabbed'].match(next) ): # get rid of any white space in the line items[item].append(line.strip()) @@ -1618,7 +1614,7 @@ class Markdown: i = 0 while i < len(parts): - + x = parts[i] if isinstance(x, (str, unicode)): @@ -1641,14 +1637,14 @@ class Markdown: parts[i] = self.doc.createTextNode(x) return parts - + def _applyPattern(self, line, pattern, patternIndex): """ Given a pattern name, this function checks if the line fits the pattern, creates the necessary elements, and returns back a list consisting of NanoDom elements and/or strings. - + @param line: the text to be processed @param pattern: the pattern to be checked @@ -1676,19 +1672,19 @@ class Markdown: if not node.nodeName in ["code", "pre"]: for child in node.childNodes: if isinstance(child, TextNode): - + result = self._handleInline(child.value, patternIndex+1) - + if result: if result == [child]: continue - + result.reverse() #to make insertion easier position = node.childNodes.index(child) - + node.removeChild(child) for item in result: @@ -1699,7 +1695,7 @@ class Markdown: self.doc.createTextNode(item)) else: node.insertChild(position, item) - + @@ -1798,14 +1794,14 @@ def markdownFromFile(input = None, def markdown(text, extensions = [], safe_mode = False): - + message(DEBUG, "in markdown.markdown(), received text:\n%s" % text) extension_names = [] extension_configs = {} - + for ext in extensions: - pos = ext.find("(") + pos = ext.find("(") if pos == -1: extension_names.append(ext) else: @@ -1820,7 +1816,7 @@ def markdown(text, safe_mode = safe_mode) return md.convert(text) - + class Extension: @@ -1845,26 +1841,11 @@ Python 2.3 or higher required for advanced command line options. For lower versions of Python use: %s INPUT_FILE > OUTPUT_FILE - + """ % EXECUTABLE_NAME_FOR_USAGE def parse_options(): - - try: - optparse = __import__("optparse") - except: - if len(sys.argv) == 2: - return {'input': sys.argv[1], - 'output': None, - 'message_threshold': CRITICAL, - 'safe': False, - 'extensions': [], - 'encoding': None } - - else: - print OPTPARSE_WARNING - return None - + import optparse parser = optparse.OptionParser(usage="%prog INPUTFILE [options]") parser.add_option("-f", "--file", dest="filename", @@ -1881,7 +1862,7 @@ def parse_options(): parser.add_option("-s", "--safe", dest="safe", default=False, metavar="SAFE_MODE", help="same mode ('replace', 'remove' or 'escape' user's HTML tag)") - + parser.add_option("--noisy", action="store_const", const=DEBUG, dest="verbose", help="print debug messages") @@ -1914,14 +1895,14 @@ def main(): if not options: sys.exit(0) - + markdownFromFile(**options) if __name__ == '__main__': sys.exit(main()) """ Run Markdown from the command line. """ - + diff --git a/src/calibre/ebooks/metadata/__init__.py b/src/calibre/ebooks/metadata/__init__.py index 6078a0aa94..2c26d011b7 100644 --- a/src/calibre/ebooks/metadata/__init__.py +++ b/src/calibre/ebooks/metadata/__init__.py @@ -6,11 +6,11 @@ __docformat__ = 'restructuredtext en' """ Provides abstraction for metadata reading.writing from a variety of ebook formats. """ -import os, mimetypes, sys, re +import os, sys, re from urllib import unquote, quote from urlparse import urlparse -from calibre import relpath +from calibre import relpath, guess_type, remove_bracketed_text from calibre.utils.config import tweaks @@ -27,20 +27,37 @@ def authors_to_string(authors): else: return '' -_bracket_pat = re.compile(r'[\[({].*?[})\]]') -def author_to_author_sort(author): +def author_to_author_sort(author, method=None): if not author: - return '' - method = tweaks['author_sort_copy_method'] - if method == 'copy' or (method == 'comma' and ',' in author): + return u'' + sauthor = remove_bracketed_text(author).strip() + tokens = sauthor.split() + if len(tokens) < 2: return author - author = _bracket_pat.sub('', author).strip() - tokens = author.split() - if tokens and tokens[-1] not in ('Inc.', 'Inc'): - tokens = tokens[-1:] + tokens[:-1] - if len(tokens) > 1 and method != 'nocomma': - tokens[0] += ',' - return ' '.join(tokens) + if method is None: + method = tweaks['author_sort_copy_method'] + if method == u'copy': + return author + suffixes = set([x.lower() for x in tweaks['author_name_suffixes']]) + suffixes |= set([x+u'.' for x in suffixes]) + + last = tokens[-1].lower() + suffix = None + if last in suffixes: + suffix = tokens[-1] + tokens = tokens[:-1] + + if method == u'comma' and u',' in u''.join(tokens): + return author + + atokens = tokens[-1:] + tokens[:-1] + if suffix: + atokens.append(suffix) + + if method != u'nocomma' and len(atokens) > 1: + atokens[0] += u',' + + return u' '.join(atokens) def authors_to_sort_string(authors): return ' & '.join(map(author_to_author_sort, authors)) @@ -118,7 +135,7 @@ class Resource(object): self.path = None self.fragment = '' try: - self.mime_type = mimetypes.guess_type(href_or_path)[0] + self.mime_type = guess_type(href_or_path)[0] except: self.mime_type = None if self.mime_type is None: @@ -274,6 +291,9 @@ def check_isbn(isbn): if not isbn: return None isbn = re.sub(r'[^0-9X]', '', isbn.upper()) + all_same = re.match(r'(\d)\1{9,12}$', isbn) + if all_same is not None: + return None if len(isbn) == 10: return check_isbn10(isbn) if len(isbn) == 13: diff --git a/src/calibre/ebooks/metadata/amazon.py b/src/calibre/ebooks/metadata/amazon.py deleted file mode 100644 index c87249ed39..0000000000 --- a/src/calibre/ebooks/metadata/amazon.py +++ /dev/null @@ -1,216 +0,0 @@ -#!/usr/bin/env python -__license__ = 'GPL v3' -__copyright__ = '2008, Kovid Goyal kovid@kovidgoyal.net' -__docformat__ = 'restructuredtext en' - -''' -Fetch metadata using Amazon AWS -''' -import sys, re -from threading import RLock - -from lxml import html -from lxml.html import soupparser - -from calibre import browser -from calibre.ebooks.metadata import check_isbn -from calibre.ebooks.metadata.book.base import Metadata -from calibre.ebooks.chardet import xml_to_unicode -from calibre.library.comments import sanitize_comments_html - -asin_cache = {} -cover_url_cache = {} -cache_lock = RLock() - -def find_asin(br, isbn): - q = 'http://www.amazon.com/s?field-keywords='+isbn - raw = br.open_novisit(q).read() - raw = xml_to_unicode(raw, strip_encoding_pats=True, - resolve_entities=True)[0] - root = html.fromstring(raw) - revs = root.xpath('//*[@class="asinReviewsSummary" and @name]') - revs = [x.get('name') for x in revs] - if revs: - return revs[0] - -def to_asin(br, isbn): - with cache_lock: - ans = asin_cache.get(isbn, None) - if ans: - return ans - if ans is False: - return None - if len(isbn) == 13: - try: - asin = find_asin(br, isbn) - except: - import traceback - traceback.print_exc() - asin = None - else: - asin = isbn - with cache_lock: - asin_cache[isbn] = asin if asin else False - return asin - - -def get_social_metadata(title, authors, publisher, isbn): - mi = Metadata(title, authors) - if not isbn: - return mi - isbn = check_isbn(isbn) - if not isbn: - return mi - br = browser() - asin = to_asin(br, isbn) - if asin and get_metadata(br, asin, mi): - return mi - from calibre.ebooks.metadata.xisbn import xisbn - for i in xisbn.get_associated_isbns(isbn): - asin = to_asin(br, i) - if asin and get_metadata(br, asin, mi): - return mi - return mi - -def get_cover_url(isbn, br): - isbn = check_isbn(isbn) - if not isbn: - return None - with cache_lock: - ans = cover_url_cache.get(isbn, None) - if ans: - return ans - if ans is False: - return None - asin = to_asin(br, isbn) - if asin: - ans = _get_cover_url(br, asin) - if ans: - with cache_lock: - cover_url_cache[isbn] = ans - return ans - from calibre.ebooks.metadata.xisbn import xisbn - for i in xisbn.get_associated_isbns(isbn): - asin = to_asin(br, i) - if asin: - ans = _get_cover_url(br, asin) - if ans: - with cache_lock: - cover_url_cache[isbn] = ans - cover_url_cache[i] = ans - return ans - with cache_lock: - cover_url_cache[isbn] = False - return None - -def _get_cover_url(br, asin): - q = 'http://amzn.com/'+asin - try: - raw = br.open_novisit(q).read() - except Exception, e: - if callable(getattr(e, 'getcode', None)) and \ - e.getcode() == 404: - return None - raise - if '<title>404 - ' in raw: - return None - raw = xml_to_unicode(raw, strip_encoding_pats=True, - resolve_entities=True)[0] - try: - root = soupparser.fromstring(raw) - except: - return False - - imgs = root.xpath('//img[@id="prodImage" and @src]') - if imgs: - src = imgs[0].get('src') - parts = src.split('/') - if len(parts) > 3: - bn = parts[-1] - sparts = bn.split('_') - if len(sparts) > 2: - bn = sparts[0] + sparts[-1] - return ('/'.join(parts[:-1]))+'/'+bn - return None - - -def get_metadata(br, asin, mi): - q = 'http://amzn.com/'+asin - try: - raw = br.open_novisit(q).read() - except Exception, e: - if callable(getattr(e, 'getcode', None)) and \ - e.getcode() == 404: - return False - raise - if '<title>404 - ' in raw: - return False - raw = xml_to_unicode(raw, strip_encoding_pats=True, - resolve_entities=True)[0] - try: - root = soupparser.fromstring(raw) - except: - return False - ratings = root.xpath('//form[@id="handleBuy"]/descendant::*[@class="asinReviewsSummary"]') - if ratings: - pat = re.compile(r'([0-9.]+) out of (\d+) stars') - r = ratings[0] - for elem in r.xpath('descendant::*[@title]'): - t = elem.get('title') - m = pat.match(t) - if m is not None: - try: - mi.rating = float(m.group(1))/float(m.group(2)) * 5 - break - except: - pass - - desc = root.xpath('//div[@id="productDescription"]/*[@class="content"]') - if desc: - desc = desc[0] - for c in desc.xpath('descendant::*[@class="seeAll" or' - ' @class="emptyClear" or @href]'): - c.getparent().remove(c) - desc = html.tostring(desc, method='html', encoding=unicode).strip() - # remove all attributes from tags - desc = re.sub(r'<([a-zA-Z0-9]+)\s[^>]+>', r'<\1>', desc) - # Collapse whitespace - #desc = re.sub('\n+', '\n', desc) - #desc = re.sub(' +', ' ', desc) - # Remove the notice about text referring to out of print editions - desc = re.sub(r'(?s)<em>--This text ref.*?</em>', '', desc) - # Remove comments - desc = re.sub(r'(?s)<!--.*?-->', '', desc) - mi.comments = sanitize_comments_html(desc) - - return True - - -def main(args=sys.argv): - import tempfile, os - tdir = tempfile.gettempdir() - br = browser() - for title, isbn in [ - ('Learning Python', '8324616489'), # Test xisbn - ('Angels & Demons', '9781416580829'), # Test sophisticated comment formatting - # Random tests - ('Star Trek: Destiny: Mere Mortals', '9781416551720'), - ('The Great Gatsby', '0743273567'), - ]: - cpath = os.path.join(tdir, title+'.jpg') - curl = get_cover_url(isbn, br) - if curl is None: - print 'No cover found for', title - else: - open(cpath, 'wb').write(br.open_novisit(curl).read()) - print 'Cover for', title, 'saved to', cpath - - #import time - #st = time.time() - print get_social_metadata(title, None, None, isbn) - #print '\n\n', time.time() - st, '\n\n' - - return 0 - -if __name__ == '__main__': - sys.exit(main()) diff --git a/src/calibre/ebooks/metadata/amazonfr.py b/src/calibre/ebooks/metadata/amazonfr.py deleted file mode 100644 index 156fff3d75..0000000000 --- a/src/calibre/ebooks/metadata/amazonfr.py +++ /dev/null @@ -1,516 +0,0 @@ -from __future__ import with_statement -__license__ = 'GPL 3' -__copyright__ = '2010, sengian <sengian1@gmail.com>' - -import sys, textwrap, re, traceback -from urllib import urlencode -from math import ceil - -from lxml import html -from lxml.html import soupparser - -from calibre.utils.date import parse_date, utcnow, replace_months -from calibre.utils.cleantext import clean_ascii_chars -from calibre import browser, preferred_encoding -from calibre.ebooks.chardet import xml_to_unicode -from calibre.ebooks.metadata import MetaInformation, check_isbn, \ - authors_to_sort_string -from calibre.ebooks.metadata.fetch import MetadataSource -from calibre.utils.config import OptionParser -from calibre.library.comments import sanitize_comments_html - - -class AmazonFr(MetadataSource): - - name = 'Amazon French' - description = _('Downloads metadata from amazon.fr') - supported_platforms = ['windows', 'osx', 'linux'] - author = 'Sengian' - version = (1, 0, 0) - has_html_comments = True - - def fetch(self): - try: - self.results = search(self.title, self.book_author, self.publisher, - self.isbn, max_results=10, verbose=self.verbose, lang='fr') - except Exception, e: - self.exception = e - self.tb = traceback.format_exc() - -class AmazonEs(MetadataSource): - - name = 'Amazon Spanish' - description = _('Downloads metadata from amazon.com in spanish') - supported_platforms = ['windows', 'osx', 'linux'] - author = 'Sengian' - version = (1, 0, 0) - has_html_comments = True - - def fetch(self): - try: - self.results = search(self.title, self.book_author, self.publisher, - self.isbn, max_results=10, verbose=self.verbose, lang='es') - except Exception, e: - self.exception = e - self.tb = traceback.format_exc() - -class AmazonEn(MetadataSource): - - name = 'Amazon English' - description = _('Downloads metadata from amazon.com in english') - supported_platforms = ['windows', 'osx', 'linux'] - author = 'Sengian' - version = (1, 0, 0) - has_html_comments = True - - def fetch(self): - try: - self.results = search(self.title, self.book_author, self.publisher, - self.isbn, max_results=10, verbose=self.verbose, lang='en') - except Exception, e: - self.exception = e - self.tb = traceback.format_exc() - -class AmazonDe(MetadataSource): - - name = 'Amazon German' - description = _('Downloads metadata from amazon.de') - supported_platforms = ['windows', 'osx', 'linux'] - author = 'Sengian' - version = (1, 0, 0) - has_html_comments = True - - def fetch(self): - try: - self.results = search(self.title, self.book_author, self.publisher, - self.isbn, max_results=10, verbose=self.verbose, lang='de') - except Exception, e: - self.exception = e - self.tb = traceback.format_exc() - -class Amazon(MetadataSource): - - name = 'Amazon' - description = _('Downloads metadata from amazon.com') - supported_platforms = ['windows', 'osx', 'linux'] - author = 'Kovid Goyal & Sengian' - version = (1, 1, 0) - has_html_comments = True - - def fetch(self): - # if not self.site_customization: - # return - try: - self.results = search(self.title, self.book_author, self.publisher, - self.isbn, max_results=10, verbose=self.verbose, lang='all') - except Exception, e: - self.exception = e - self.tb = traceback.format_exc() - - # @property - # def string_customization_help(self): - # return _('You can select here the language for metadata search with amazon.com') - - -def report(verbose): - if verbose: - traceback.print_exc() - - -class Query(object): - - BASE_URL_ALL = 'http://www.amazon.com' - BASE_URL_FR = 'http://www.amazon.fr' - BASE_URL_DE = 'http://www.amazon.de' - - def __init__(self, title=None, author=None, publisher=None, isbn=None, keywords=None, - max_results=20, rlang='all'): - assert not(title is None and author is None and publisher is None \ - and isbn is None and keywords is None) - assert (max_results < 21) - - self.max_results = int(max_results) - self.renbres = re.compile(u'\s*(\d+)\s*') - - q = { 'search-alias' : 'stripbooks' , - 'unfiltered' : '1', - 'field-keywords' : '', - 'field-author' : '', - 'field-title' : '', - 'field-isbn' : '', - 'field-publisher' : '' - #get to amazon detailed search page to get all options - # 'node' : '', - # 'field-binding' : '', - #before, during, after - # 'field-dateop' : '', - #month as number - # 'field-datemod' : '', - # 'field-dateyear' : '', - #french only - # 'field-collection' : '', - #many options available - } - - if rlang =='all': - q['sort'] = 'relevanceexprank' - self.urldata = self.BASE_URL_ALL - elif rlang =='es': - q['sort'] = 'relevanceexprank' - q['field-language'] = 'Spanish' - self.urldata = self.BASE_URL_ALL - elif rlang =='en': - q['sort'] = 'relevanceexprank' - q['field-language'] = 'English' - self.urldata = self.BASE_URL_ALL - elif rlang =='fr': - q['sort'] = 'relevancerank' - self.urldata = self.BASE_URL_FR - elif rlang =='de': - q['sort'] = 'relevancerank' - self.urldata = self.BASE_URL_DE - self.baseurl = self.urldata - - if isbn is not None: - q['field-isbn'] = isbn.replace('-', '') - else: - if title is not None: - q['field-title'] = title - if author is not None: - q['field-author'] = author - if publisher is not None: - q['field-publisher'] = publisher - if keywords is not None: - q['field-keywords'] = keywords - - if isinstance(q, unicode): - q = q.encode('utf-8') - self.urldata += '/gp/search/ref=sr_adv_b/?' + urlencode(q) - - def __call__(self, browser, verbose, timeout = 5.): - if verbose: - print 'Query:', self.urldata - - try: - raw = browser.open_novisit(self.urldata, timeout=timeout).read() - except Exception, e: - report(verbose) - if callable(getattr(e, 'getcode', None)) and \ - e.getcode() == 404: - return - raise - if '<title>404 - ' in raw: - return - raw = xml_to_unicode(raw, strip_encoding_pats=True, - resolve_entities=True)[0] - - try: - feed = soupparser.fromstring(raw) - except: - try: - #remove ASCII invalid chars - return soupparser.fromstring(clean_ascii_chars(raw)) - except: - return None, self.urldata - - #nb of page - try: - nbresults = self.renbres.findall(feed.xpath("//*[@class='resultCount']")[0].text) - except: - return None, self.urldata - - pages =[feed] - if len(nbresults) > 1: - nbpagetoquery = int(ceil(float(min(int(nbresults[2]), self.max_results))/ int(nbresults[1]))) - for i in xrange(2, nbpagetoquery + 1): - try: - urldata = self.urldata + '&page=' + str(i) - raw = browser.open_novisit(urldata, timeout=timeout).read() - except Exception, e: - continue - if '<title>404 - ' in raw: - continue - raw = xml_to_unicode(raw, strip_encoding_pats=True, - resolve_entities=True)[0] - try: - feed = soupparser.fromstring(raw) - except: - try: - #remove ASCII invalid chars - return soupparser.fromstring(clean_ascii_chars(raw)) - except: - continue - pages.append(feed) - - results = [] - for x in pages: - results.extend([i.getparent().get('href') \ - for i in x.xpath("//a/span[@class='srTitle']")]) - return results[:self.max_results], self.baseurl - -class ResultList(list): - - def __init__(self, baseurl, lang = 'all'): - self.baseurl = baseurl - self.lang = lang - self.repub = re.compile(u'\((.*)\)') - self.rerat = re.compile(u'([0-9.]+)') - self.reattr = re.compile(r'<([a-zA-Z0-9]+)\s[^>]+>') - self.reoutp = re.compile(r'(?s)<em>--This text ref.*?</em>') - self.recom = re.compile(r'(?s)<!--.*?-->') - self.republi = re.compile(u'(Editeur|Publisher|Verlag)', re.I) - self.reisbn = re.compile(u'(ISBN-10|ISBN-10|ASIN)', re.I) - self.relang = re.compile(u'(Language|Langue|Sprache)', re.I) - self.reratelt = re.compile(u'(Average\s*Customer\s*Review|Moyenne\s*des\s*commentaires\s*client|Durchschnittliche\s*Kundenbewertung)', re.I) - self.reprod = re.compile(u'(Product\s*Details|D.tails\s*sur\s*le\s*produit|Produktinformation)', re.I) - - def strip_tags_etree(self, etreeobj, invalid_tags): - for (itag, rmv) in invalid_tags.iteritems(): - if rmv: - for elts in etreeobj.getiterator(itag): - elts.drop_tree() - else: - for elts in etreeobj.getiterator(itag): - elts.drop_tag() - - def clean_entry(self, entry, invalid_tags = {'script': True}, - invalid_id = (), invalid_class=()): - #invalid_tags: remove tag and keep content if False else remove - #remove tags - if invalid_tags: - self.strip_tags_etree(entry, invalid_tags) - #remove id - if invalid_id: - for eltid in invalid_id: - elt = entry.get_element_by_id(eltid) - if elt is not None: - elt.drop_tree() - #remove class - if invalid_class: - for eltclass in invalid_class: - elts = entry.find_class(eltclass) - if elts is not None: - for elt in elts: - elt.drop_tree() - - def get_title(self, entry): - title = entry.get_element_by_id('btAsinTitle') - if title is not None: - title = title.text - return unicode(title.replace('\n', '').strip()) - - def get_authors(self, entry): - author = entry.get_element_by_id('btAsinTitle') - while author.getparent().tag != 'div': - author = author.getparent() - author = author.getparent() - authortext = [] - for x in author.getiterator('a'): - authortext.append(unicode(x.text_content().strip())) - return authortext - - def get_description(self, entry, verbose): - try: - description = entry.get_element_by_id("productDescription").find("div[@class='content']") - inv_class = ('seeAll', 'emptyClear') - inv_tags ={'img': True, 'a': False} - self.clean_entry(description, invalid_tags=inv_tags, invalid_class=inv_class) - description = html.tostring(description, method='html', encoding=unicode).strip() - # remove all attributes from tags - description = self.reattr.sub(r'<\1>', description) - # Remove the notice about text referring to out of print editions - description = self.reoutp.sub('', description) - # Remove comments - description = self.recom.sub('', description) - return unicode(sanitize_comments_html(description)) - except: - report(verbose) - return None - - def get_tags(self, entry, browser, verbose): - try: - tags = entry.get_element_by_id('tagContentHolder') - testptag = tags.find_class('see-all') - if testptag: - for x in testptag: - alink = x.xpath('descendant-or-self::a') - if alink: - if alink[0].get('class') == 'tgJsActive': - continue - link = self.baseurl + alink[0].get('href') - entry = self.get_individual_metadata(browser, link, verbose) - tags = entry.get_element_by_id('tagContentHolder') - break - tags = [a.text for a in tags.getiterator('a') if a.get('rel') == 'tag'] - except: - report(verbose) - tags = [] - return tags - - def get_book_info(self, entry, mi, verbose): - try: - entry = entry.get_element_by_id('SalesRank').getparent() - except: - try: - for z in entry.getiterator('h2'): - if self.reprod.search(z.text_content()): - entry = z.getparent().find("div[@class='content']/ul") - break - except: - report(verbose) - return mi - elts = entry.findall('li') - #pub & date - elt = filter(lambda x: self.republi.search(x.find('b').text), elts) - if elt: - pub = elt[0].find('b').tail - mi.publisher = unicode(self.repub.sub('', pub).strip()) - d = self.repub.search(pub) - if d is not None: - d = d.group(1) - try: - default = utcnow().replace(day=15) - if self.lang != 'all': - d = replace_months(d, self.lang) - d = parse_date(d, assume_utc=True, default=default) - mi.pubdate = d - except: - report(verbose) - #ISBN - elt = filter(lambda x: self.reisbn.search(x.find('b').text), elts) - if elt: - isbn = elt[0].find('b').tail.replace('-', '').strip() - if check_isbn(isbn): - mi.isbn = unicode(isbn) - elif len(elt) > 1: - isbn = elt[1].find('b').tail.replace('-', '').strip() - if check_isbn(isbn): - mi.isbn = unicode(isbn) - #Langue - elt = filter(lambda x: self.relang.search(x.find('b').text), elts) - if elt: - langue = elt[0].find('b').tail.strip() - if langue: - mi.language = unicode(langue) - #ratings - elt = filter(lambda x: self.reratelt.search(x.find('b').text), elts) - if elt: - ratings = elt[0].find_class('swSprite') - if ratings: - ratings = self.rerat.findall(ratings[0].get('title')) - if len(ratings) == 2: - mi.rating = float(ratings[0])/float(ratings[1]) * 5 - return mi - - def fill_MI(self, entry, title, authors, browser, verbose): - mi = MetaInformation(title, authors) - mi.author_sort = authors_to_sort_string(authors) - mi.comments = self.get_description(entry, verbose) - mi = self.get_book_info(entry, mi, verbose) - mi.tags = self.get_tags(entry, browser, verbose) - return mi - - def get_individual_metadata(self, browser, linkdata, verbose): - try: - raw = browser.open_novisit(linkdata).read() - except Exception, e: - report(verbose) - if callable(getattr(e, 'getcode', None)) and \ - e.getcode() == 404: - return - raise - if '<title>404 - ' in raw: - report(verbose) - return - raw = xml_to_unicode(raw, strip_encoding_pats=True, - resolve_entities=True)[0] - try: - return soupparser.fromstring(raw) - except: - try: - #remove ASCII invalid chars - return soupparser.fromstring(clean_ascii_chars(raw)) - except: - report(verbose) - return - - def populate(self, entries, browser, verbose=False): - for x in entries: - try: - entry = self.get_individual_metadata(browser, x, verbose) - # clean results - # inv_ids = ('divsinglecolumnminwidth', 'sims.purchase', 'AutoBuyXGetY', 'A9AdsMiddleBoxTop') - # inv_class = ('buyingDetailsGrid', 'productImageGrid') - # inv_tags ={'script': True, 'style': True, 'form': False} - # self.clean_entry(entry, invalid_id=inv_ids) - title = self.get_title(entry) - authors = self.get_authors(entry) - except Exception, e: - if verbose: - print 'Failed to get all details for an entry' - print e - print 'URL who failed:', x - report(verbose) - continue - self.append(self.fill_MI(entry, title, authors, browser, verbose)) - - -def search(title=None, author=None, publisher=None, isbn=None, - max_results=5, verbose=False, keywords=None, lang='all'): - br = browser() - entries, baseurl = Query(title=title, author=author, isbn=isbn, publisher=publisher, - keywords=keywords, max_results=max_results,rlang=lang)(br, verbose) - - if entries is None or len(entries) == 0: - return - - #List of entry - ans = ResultList(baseurl, lang) - ans.populate(entries, br, verbose) - return ans - -def option_parser(): - parser = OptionParser(textwrap.dedent(\ - _('''\ - %prog [options] - - Fetch book metadata from Amazon. You must specify one of title, author, - ISBN, publisher or keywords. Will fetch a maximum of 10 matches, - so you should make your query as specific as possible. - You can chose the language for metadata retrieval: - All & english & french & german & spanish - ''' - ))) - parser.add_option('-t', '--title', help='Book title') - parser.add_option('-a', '--author', help='Book author(s)') - parser.add_option('-p', '--publisher', help='Book publisher') - parser.add_option('-i', '--isbn', help='Book ISBN') - parser.add_option('-k', '--keywords', help='Keywords') - parser.add_option('-m', '--max-results', default=10, - help='Maximum number of results to fetch') - parser.add_option('-l', '--lang', default='all', - help='Chosen language for metadata search (all, en, fr, es, de)') - parser.add_option('-v', '--verbose', default=0, action='count', - help='Be more verbose about errors') - return parser - -def main(args=sys.argv): - parser = option_parser() - opts, args = parser.parse_args(args) - try: - results = search(opts.title, opts.author, isbn=opts.isbn, publisher=opts.publisher, - keywords=opts.keywords, verbose=opts.verbose, max_results=opts.max_results, - lang=opts.lang) - except AssertionError: - report(True) - parser.print_help() - return 1 - if results is None or len(results) == 0: - print 'No result found for this search!' - return 0 - for result in results: - print unicode(result).encode(preferred_encoding, 'replace') - print - -if __name__ == '__main__': - sys.exit(main()) diff --git a/src/calibre/ebooks/metadata/archive.py b/src/calibre/ebooks/metadata/archive.py index f5982406ea..b9136e5a13 100644 --- a/src/calibre/ebooks/metadata/archive.py +++ b/src/calibre/ebooks/metadata/archive.py @@ -83,6 +83,7 @@ class ArchiveExtract(FileTypePlugin): return of.name def get_comic_book_info(d, mi): + # See http://code.google.com/p/comicbookinfo/wiki/Example series = d.get('series', '') if series.strip(): mi.series = series @@ -111,6 +112,7 @@ def get_comic_book_info(d, mi): def get_cbz_metadata(stream): + # See http://code.google.com/p/comicbookinfo/wiki/Example from calibre.utils.zipfile import ZipFile from calibre.ebooks.metadata import MetaInformation import json diff --git a/src/calibre/ebooks/metadata/book/__init__.py b/src/calibre/ebooks/metadata/book/__init__.py index 033a78d611..fae858aabd 100644 --- a/src/calibre/ebooks/metadata/book/__init__.py +++ b/src/calibre/ebooks/metadata/book/__init__.py @@ -18,14 +18,14 @@ SOCIAL_METADATA_FIELDS = frozenset([ 'series_index', # A floating point number # Of the form { scheme1:value1, scheme2:value2} # For example: {'isbn':'123456789', 'doi':'xxxx', ... } - 'classifiers', + 'identifiers', ]) ''' -The list of names that convert to classifiers when in get and set. +The list of names that convert to identifiers when in get and set. ''' -TOP_LEVEL_CLASSIFIERS = frozenset([ +TOP_LEVEL_IDENTIFIERS = frozenset([ 'isbn', ]) @@ -108,7 +108,7 @@ STANDARD_METADATA_FIELDS = SOCIAL_METADATA_FIELDS.union( SC_FIELDS_NOT_COPIED = frozenset(['title', 'title_sort', 'authors', 'author_sort', 'author_sort_map', 'cover_data', 'tags', 'language', - 'classifiers']) + 'identifiers']) # Metadata fields that smart update should copy only if the source is not None SC_FIELDS_COPY_NOT_NULL = frozenset(['lpath', 'size', 'comments', 'thumbnail']) diff --git a/src/calibre/ebooks/metadata/book/base.py b/src/calibre/ebooks/metadata/book/base.py index b47cc373a7..69407dcb2e 100644 --- a/src/calibre/ebooks/metadata/book/base.py +++ b/src/calibre/ebooks/metadata/book/base.py @@ -12,19 +12,22 @@ from calibre.constants import DEBUG from calibre.ebooks.metadata.book import SC_COPYABLE_FIELDS from calibre.ebooks.metadata.book import SC_FIELDS_COPY_NOT_NULL from calibre.ebooks.metadata.book import STANDARD_METADATA_FIELDS -from calibre.ebooks.metadata.book import TOP_LEVEL_CLASSIFIERS +from calibre.ebooks.metadata.book import TOP_LEVEL_IDENTIFIERS from calibre.ebooks.metadata.book import ALL_METADATA_FIELDS from calibre.library.field_metadata import FieldMetadata from calibre.utils.date import isoformat, format_date from calibre.utils.icu import sort_key from calibre.utils.formatter import TemplateFormatter +def human_readable(size, precision=2): + """ Convert a size in bytes into megabytes """ + return ('%.'+str(precision)+'f'+ 'MB') % ((size/(1024.*1024.)),) NULL_VALUES = { 'user_metadata': {}, 'cover_data' : (None, None), 'tags' : [], - 'classifiers' : {}, + 'identifiers' : {}, 'languages' : [], 'device_collections': [], 'author_sort_map': {}, @@ -38,34 +41,45 @@ field_metadata = FieldMetadata() class SafeFormat(TemplateFormatter): - def get_value(self, key, args, kwargs): - try: - key = key.lower() - if key != 'title_sort': - key = field_metadata.search_term_to_field_key(key) - b = self.book.get_user_metadata(key, False) - if b and b['datatype'] == 'int' and self.book.get(key, 0) == 0: - v = '' - elif b and b['datatype'] == 'float' and self.book.get(key, 0.0) == 0.0: - v = '' - else: - ign, v = self.book.format_field(key, series_with_index=False) - if v is None: - return '' - if v == '': - return '' - return v - except: - if DEBUG: - traceback.print_exc() - return key + def get_value(self, orig_key, args, kwargs): + if not orig_key: + return '' + key = orig_key.lower() + if key != 'title_sort' and key not in TOP_LEVEL_IDENTIFIERS: + key = field_metadata.search_term_to_field_key(key) + if key is None or (self.book and key not in self.book.all_field_keys()): + raise ValueError(_('Value: unknown field ') + orig_key) + b = self.book.get_user_metadata(key, False) + if b and b['datatype'] == 'int' and self.book.get(key, 0) == 0: + v = '' + elif b and b['datatype'] == 'float' and self.book.get(key, 0.0) == 0.0: + v = '' + else: + v = self.book.format_field(key, series_with_index=False)[1] + if v is None: + return '' + if v == '': + return '' + return v composite_formatter = SafeFormat() class Metadata(object): ''' - A class representing all the metadata for a book. + A class representing all the metadata for a book. The various standard metadata + fields are available as attributes of this object. You can also stick + arbitrary attributes onto this object. + + Metadata from custom columns should be accessed via the get() method, + passing in the lookup name for the column, for example: "#mytags". + + Use the :meth:`is_null` method to test if a field is null. + + This object also has functions to format fields into strings. + + The list of standard metadata fields grows with time is in + :data:`STANDARD_METADATA_FIELDS`. Please keep the method based API of this class to a minimum. Every method becomes a reserved field name. @@ -85,19 +99,32 @@ class Metadata(object): if title: self.title = title if authors: - #: List of strings or [] + # List of strings or [] self.author = list(authors) if authors else []# Needed for backward compatibility self.authors = list(authors) if authors else [] def is_null(self, field): - null_val = NULL_VALUES.get(field, None) - val = getattr(self, field, None) - return not val or val == null_val + ''' + Return True if the value of field is null in this object. + 'null' means it is unknown or evaluates to False. So a title of + _('Unknown') is null or a language of 'und' is null. + + Be careful with numeric fields since this will return True for zero as + well as None. + + Also returns True if the field does not exist. + ''' + try: + null_val = NULL_VALUES.get(field, None) + val = getattr(self, field, None) + return not val or val == null_val + except: + return True def __getattribute__(self, field): _data = object.__getattribute__(self, '_data') - if field in TOP_LEVEL_CLASSIFIERS: - return _data.get('classifiers').get(field, None) + if field in TOP_LEVEL_IDENTIFIERS: + return _data.get('identifiers').get(field, None) if field in STANDARD_METADATA_FIELDS: return _data.get(field, None) try: @@ -117,17 +144,29 @@ class Metadata(object): _('TEMPLATE ERROR'), self).strip() return val - + if field.startswith('#') and field.endswith('_index'): + try: + return self.get_extra(field[:-6]) + except: + pass raise AttributeError( 'Metadata object has no attribute named: '+ repr(field)) def __setattr__(self, field, val, extra=None): _data = object.__getattribute__(self, '_data') - if field in TOP_LEVEL_CLASSIFIERS: - _data['classifiers'].update({field: val}) + if field in TOP_LEVEL_IDENTIFIERS: + field, val = self._clean_identifier(field, val) + identifiers = _data['identifiers'] + identifiers.pop(field, None) + if val: + identifiers[field] = val + elif field == 'identifiers': + if not val: + val = copy.copy(NULL_VALUES.get('identifiers', None)) + self.set_identifiers(val) elif field in STANDARD_METADATA_FIELDS: if val is None: - val = NULL_VALUES.get(field, None) + val = copy.copy(NULL_VALUES.get(field, None)) _data[field] = val elif field in _data['user_metadata'].iterkeys(): _data['user_metadata'][field]['#value#'] = val @@ -159,34 +198,69 @@ class Metadata(object): try: return self.__getattribute__(field) except AttributeError: - if field.startswith('#') and field.endswith('_index'): - try: - return self.get_extra(field[:-6]) - except: - pass return default - def get_extra(self, field): + def get_extra(self, field, default=None): _data = object.__getattribute__(self, '_data') if field in _data['user_metadata'].iterkeys(): - return _data['user_metadata'][field]['#extra#'] + try: + return _data['user_metadata'][field]['#extra#'] + except: + return default raise AttributeError( 'Metadata object has no attribute named: '+ repr(field)) def set(self, field, val, extra=None): self.__setattr__(field, val, extra) - def get_classifiers(self): + def get_identifiers(self): ''' - Return a copy of the classifiers dictionary. + Return a copy of the identifiers dictionary. The dict is small, and the penalty for using a reference where a copy is needed is large. Also, we don't want any manipulations of the returned dict to show up in the book. ''' - return copy.deepcopy(object.__getattribute__(self, '_data')['classifiers']) + ans = object.__getattribute__(self, + '_data')['identifiers'] + if not ans: + ans = {} + return copy.deepcopy(ans) - def set_classifiers(self, classifiers): - object.__getattribute__(self, '_data')['classifiers'] = classifiers + def _clean_identifier(self, typ, val): + if typ: + typ = icu_lower(typ).strip().replace(':', '').replace(',', '') + if val: + val = val.strip().replace(',', '|').replace(':', '|') + return typ, val + + def set_identifiers(self, identifiers): + ''' + Set all identifiers. Note that if you previously set ISBN, calling + this method will delete it. + ''' + cleaned = {} + for key, val in identifiers.iteritems(): + key, val = self._clean_identifier(key, val) + if key and val: + cleaned[key] = val + object.__getattribute__(self, '_data')['identifiers'] = cleaned + + def set_identifier(self, typ, val): + 'If val is empty, deletes identifier of type typ' + typ, val = self._clean_identifier(typ, val) + if not typ: + return + identifiers = object.__getattribute__(self, + '_data')['identifiers'] + + identifiers.pop(typ, None) + if val: + identifiers[typ] = val + + def has_identifier(self, typ): + identifiers = object.__getattribute__(self, + '_data')['identifiers'] + return typ in identifiers # field-oriented interface. Intended to be the same as in LibraryDatabase @@ -229,7 +303,7 @@ class Metadata(object): if v is not None: result[attr] = v # separate these because it uses the self.get(), not _data.get() - for attr in TOP_LEVEL_CLASSIFIERS: + for attr in TOP_LEVEL_IDENTIFIERS: v = self.get(attr, None) if v is not None: result[attr] = v @@ -400,8 +474,8 @@ class Metadata(object): self.set_all_user_metadata(other.get_all_user_metadata(make_copy=True)) for x in SC_FIELDS_COPY_NOT_NULL: copy_not_none(self, other, x) - if callable(getattr(other, 'get_classifiers', None)): - self.set_classifiers(other.get_classifiers()) + if callable(getattr(other, 'get_identifiers', None)): + self.set_identifiers(other.get_identifiers()) # language is handled below else: for attr in SC_COPYABLE_FIELDS: @@ -435,7 +509,7 @@ class Metadata(object): self_tags = self.get(x, []) self.set_user_metadata(x, meta) # get... did the deepcopy other_tags = other.get(x, []) - if meta['is_multiple']: + if meta['datatype'] == 'text' and meta['is_multiple']: # Case-insensitive but case preserving merging lotags = [t.lower() for t in other_tags] lstags = [t.lower() for t in self_tags] @@ -456,15 +530,15 @@ class Metadata(object): if len(other_comments.strip()) > len(my_comments.strip()): self.comments = other_comments - # Copy all the non-none classifiers - if callable(getattr(other, 'get_classifiers', None)): - d = self.get_classifiers() - s = other.get_classifiers() + # Copy all the non-none identifiers + if callable(getattr(other, 'get_identifiers', None)): + d = self.get_identifiers() + s = other.get_identifiers() d.update([v for v in s.iteritems() if v[1] is not None]) - self.set_classifiers(d) + self.set_identifiers(d) else: - # other structure not Metadata. Copy the top-level classifiers - for attr in TOP_LEVEL_CLASSIFIERS: + # other structure not Metadata. Copy the top-level identifiers + for attr in TOP_LEVEL_IDENTIFIERS: copy_not_none(self, other, attr) other_lang = getattr(other, 'language', None) @@ -493,17 +567,25 @@ class Metadata(object): def format_tags(self): return u', '.join([unicode(t) for t in sorted(self.tags, key=sort_key)]) - def format_rating(self): - return unicode(self.rating) + def format_rating(self, v=None, divide_by=1.0): + if v is None: + if self.rating is not None: + return unicode(self.rating/divide_by) + return u'None' + return unicode(v/divide_by) def format_field(self, key, series_with_index=True): + ''' + Returns the tuple (display_name, formatted_value) + ''' name, val, ign, ign = self.format_field_extended(key, series_with_index) return (name, val) def format_field_extended(self, key, series_with_index=True): from calibre.ebooks.metadata import authors_to_string ''' - returns the tuple (field_name, formatted_value) + returns the tuple (display_name, formatted_value, original_value, + field_metadata) ''' # Handle custom series index @@ -531,7 +613,10 @@ class Metadata(object): orig_res = res datatype = cmeta['datatype'] if datatype == 'text' and cmeta['is_multiple']: - res = u', '.join(sorted(res, key=sort_key)) + if cmeta['display'].get('is_names', False): + res = u' & '.join(res) + else: + res = u', '.join(sorted(res, key=sort_key)) elif datatype == 'series' and series_with_index: if self.get_extra(key) is not None: res = res + \ @@ -541,12 +626,24 @@ class Metadata(object): elif datatype == 'bool': res = _('Yes') if res else _('No') elif datatype == 'rating': - res = res/2 + res = res/2.0 + elif datatype in ['int', 'float']: + try: + fmt = cmeta['display'].get('number_format', None) + res = fmt.format(res) + except: + pass return (name, unicode(res), orig_res, cmeta) + # convert top-level ids into their value + if key in TOP_LEVEL_IDENTIFIERS: + fmeta = field_metadata['identifiers'] + name = key + res = self.get(key, None) + return (name, res, res, fmeta) + # Translate aliases into the standard field name fmkey = field_metadata.search_term_to_field_key(key) - if fmkey in field_metadata and field_metadata[fmkey]['kind'] == 'field': res = self.get(key, None) fmeta = field_metadata[fmkey] @@ -561,16 +658,26 @@ class Metadata(object): elif key == 'series_index': res = self.format_series_index(res) elif datatype == 'text' and fmeta['is_multiple']: + if isinstance(res, dict): + res = [k + ':' + v for k,v in res.items()] res = u', '.join(sorted(res, key=sort_key)) elif datatype == 'series' and series_with_index: res = res + ' [%s]'%self.format_series_index() elif datatype == 'datetime': res = format_date(res, fmeta['display'].get('date_format','dd MMM yyyy')) + elif datatype == 'rating': + res = res/2.0 + elif key == 'size': + res = human_readable(res) return (name, unicode(res), orig_res, fmeta) return (None, None, None, None) def __unicode__(self): + ''' + A string representation of this object, suitable for printing to + console + ''' from calibre.ebooks.metadata import authors_to_string ans = [] def fmt(x, y): @@ -586,15 +693,11 @@ class Metadata(object): fmt('Publisher', self.publisher) if getattr(self, 'book_producer', False): fmt('Book Producer', self.book_producer) - if self.comments: - fmt('Comments', self.comments) - if self.isbn: - fmt('ISBN', self.isbn) if self.tags: fmt('Tags', u', '.join([unicode(t) for t in self.tags])) if self.series: fmt('Series', self.series + ' #%s'%self.format_series_index()) - if self.language: + if not self.is_null('language'): fmt('Language', self.language) if self.rating is not None: fmt('Rating', self.rating) @@ -604,6 +707,12 @@ class Metadata(object): fmt('Published', isoformat(self.pubdate)) if self.rights is not None: fmt('Rights', unicode(self.rights)) + if self.identifiers: + fmt('Identifiers', u', '.join(['%s:%s'%(k, v) for k, v in + self.identifiers.iteritems()])) + if self.comments: + fmt('Comments', self.comments) + for key in self.custom_field_keys(): val = self.get(key, None) if val: @@ -612,6 +721,9 @@ class Metadata(object): return u'\n'.join(ans) def to_html(self): + ''' + A HTML representation of this object. + ''' from calibre.ebooks.metadata import authors_to_string ans = [(_('Title'), unicode(self.title))] ans += [(_('Author(s)'), (authors_to_string(self.authors) if self.authors else _('Unknown')))] diff --git a/src/calibre/ebooks/metadata/book/json_codec.py b/src/calibre/ebooks/metadata/book/json_codec.py index c02d4e953d..1d93b5dece 100644 --- a/src/calibre/ebooks/metadata/book/json_codec.py +++ b/src/calibre/ebooks/metadata/book/json_codec.py @@ -123,6 +123,8 @@ class JsonCodec(object): if key == 'user_metadata': book.set_all_user_metadata(meta) else: + if key == 'classifiers': + key = 'identifiers' setattr(book, key, meta) booklist.append(book) except: @@ -130,6 +132,8 @@ class JsonCodec(object): traceback.print_exc() def decode_metadata(self, key, value): + if key == 'classifiers': + key = 'identifiers' if key == 'user_metadata': for k in value: if value[k]['datatype'] == 'datetime': diff --git a/src/calibre/ebooks/metadata/covers.py b/src/calibre/ebooks/metadata/covers.py deleted file mode 100644 index 15e0a05c1e..0000000000 --- a/src/calibre/ebooks/metadata/covers.py +++ /dev/null @@ -1,299 +0,0 @@ -#!/usr/bin/env python -# vim:fileencoding=UTF-8:ts=4:sw=4:sta:et:sts=4:ai - -__license__ = 'GPL v3' -__copyright__ = '2010, Kovid Goyal <kovid@kovidgoyal.net>' -__docformat__ = 'restructuredtext en' - -import traceback, socket, sys -from functools import partial -from threading import Thread, Event -from Queue import Queue, Empty -from lxml import etree - -import mechanize - -from calibre.customize import Plugin -from calibre import browser, prints -from calibre.constants import preferred_encoding, DEBUG - -class CoverDownload(Plugin): - ''' - These plugins are used to download covers for books. - ''' - - supported_platforms = ['windows', 'osx', 'linux'] - author = 'Kovid Goyal' - type = _('Cover download') - - def has_cover(self, mi, ans, timeout=5.): - ''' - Check if the book described by mi has a cover. Call ans.set() if it - does. Do nothing if it doesn't. - - :param mi: MetaInformation object - :param timeout: timeout in seconds - :param ans: A threading.Event object - ''' - raise NotImplementedError() - - def get_covers(self, mi, result_queue, abort, timeout=5.): - ''' - Download covers for books described by the mi object. Downloaded covers - must be put into the result_queue. If more than one cover is available, - the plugin should continue downloading them and putting them into - result_queue until abort.is_set() returns True. - - :param mi: MetaInformation object - :param result_queue: A multithreaded Queue - :param abort: A threading.Event object - :param timeout: timeout in seconds - ''' - raise NotImplementedError() - - def exception_to_string(self, ex): - try: - return unicode(ex) - except: - try: - return str(ex).decode(preferred_encoding, 'replace') - except: - return repr(ex) - - def debug(self, *args, **kwargs): - if DEBUG: - prints('\t'+self.name+':', *args, **kwargs) - - - -class HeadRequest(mechanize.Request): - - def get_method(self): - return 'HEAD' - -class OpenLibraryCovers(CoverDownload): # {{{ - 'Download covers from openlibrary.org' - - OPENLIBRARY = 'http://covers.openlibrary.org/b/isbn/%s-L.jpg?default=false' - name = 'openlibrary.org covers' - description = _('Download covers from openlibrary.org') - author = 'Kovid Goyal' - - def has_cover(self, mi, ans, timeout=5.): - if not mi.isbn: - return False - br = browser() - br.set_handle_redirect(False) - try: - br.open_novisit(HeadRequest(self.OPENLIBRARY%mi.isbn), timeout=timeout) - self.debug('cover for', mi.isbn, 'found') - ans.set() - except Exception, e: - if callable(getattr(e, 'getcode', None)) and e.getcode() == 302: - self.debug('cover for', mi.isbn, 'found') - ans.set() - else: - self.debug(e) - - def get_covers(self, mi, result_queue, abort, timeout=5.): - if not mi.isbn: - return - br = browser() - try: - ans = br.open(self.OPENLIBRARY%mi.isbn, timeout=timeout).read() - result_queue.put((True, ans, 'jpg', self.name)) - except Exception, e: - if callable(getattr(e, 'getcode', None)) and e.getcode() == 404: - result_queue.put((False, _('ISBN: %s not found')%mi.isbn, '', self.name)) - else: - result_queue.put((False, self.exception_to_string(e), - traceback.format_exc(), self.name)) - -# }}} - -class AmazonCovers(CoverDownload): # {{{ - - name = 'amazon.com covers' - description = _('Download covers from amazon.com') - author = 'Kovid Goyal' - - - def has_cover(self, mi, ans, timeout=5.): - if not mi.isbn: - return False - from calibre.ebooks.metadata.amazon import get_cover_url - br = browser() - try: - get_cover_url(mi.isbn, br) - self.debug('cover for', mi.isbn, 'found') - ans.set() - except Exception, e: - self.debug(e) - - def get_covers(self, mi, result_queue, abort, timeout=5.): - if not mi.isbn: - return - from calibre.ebooks.metadata.amazon import get_cover_url - br = browser() - try: - url = get_cover_url(mi.isbn, br) - cover_data = br.open_novisit(url).read() - result_queue.put((True, cover_data, 'jpg', self.name)) - except Exception, e: - result_queue.put((False, self.exception_to_string(e), - traceback.format_exc(), self.name)) - -# }}} - -def check_for_cover(mi, timeout=5.): # {{{ - from calibre.customize.ui import cover_sources - ans = Event() - checkers = [partial(p.has_cover, mi, ans, timeout=timeout) for p in - cover_sources()] - workers = [Thread(target=c) for c in checkers] - for w in workers: - w.daemon = True - w.start() - while not ans.is_set(): - ans.wait(0.1) - if sum([int(w.is_alive()) for w in workers]) == 0: - break - return ans.is_set() - -# }}} - -def download_covers(mi, result_queue, max_covers=50, timeout=5.): # {{{ - from calibre.customize.ui import cover_sources - abort = Event() - temp = Queue() - getters = [partial(p.get_covers, mi, temp, abort, timeout=timeout) for p in - cover_sources()] - workers = [Thread(target=c) for c in getters] - for w in workers: - w.daemon = True - w.start() - count = 0 - while count < max_covers: - try: - result = temp.get_nowait() - if result[0]: - count += 1 - result_queue.put(result) - except Empty: - pass - if sum([int(w.is_alive()) for w in workers]) == 0: - break - - abort.set() - - while True: - try: - result = temp.get_nowait() - count += 1 - result_queue.put(result) - except Empty: - break - -# }}} - -class DoubanCovers(CoverDownload): # {{{ - 'Download covers from Douban.com' - - DOUBAN_ISBN_URL = 'http://api.douban.com/book/subject/isbn/' - CALIBRE_DOUBAN_API_KEY = '0bd1672394eb1ebf2374356abec15c3d' - name = 'Douban.com covers' - description = _('Download covers from Douban.com') - author = 'Li Fanxi' - - def get_cover_url(self, isbn, br, timeout=5.): - try: - url = self.DOUBAN_ISBN_URL + isbn + "?apikey=" + self.CALIBRE_DOUBAN_API_KEY - src = br.open(url, timeout=timeout).read() - except Exception, err: - if isinstance(getattr(err, 'args', [None])[0], socket.timeout): - err = Exception(_('Douban.com API timed out. Try again later.')) - raise err - else: - feed = etree.fromstring(src) - NAMESPACES = { - 'openSearch':'http://a9.com/-/spec/opensearchrss/1.0/', - 'atom' : 'http://www.w3.org/2005/Atom', - 'db': 'http://www.douban.com/xmlns/' - } - XPath = partial(etree.XPath, namespaces=NAMESPACES) - entries = XPath('//atom:entry')(feed) - if len(entries) < 1: - return None - try: - cover_url = XPath("descendant::atom:link[@rel='image']/attribute::href") - u = cover_url(entries[0])[0].replace('/spic/', '/lpic/'); - # If URL contains "book-default", the book doesn't have a cover - if u.find('book-default') != -1: - return None - except: - return None - return u - - def has_cover(self, mi, ans, timeout=5.): - if not mi.isbn: - return False - br = browser() - try: - if self.get_cover_url(mi.isbn, br, timeout=timeout) != None: - self.debug('cover for', mi.isbn, 'found') - ans.set() - except Exception, e: - self.debug(e) - - def get_covers(self, mi, result_queue, abort, timeout=5.): - if not mi.isbn: - return - br = browser() - try: - url = self.get_cover_url(mi.isbn, br, timeout=timeout) - cover_data = br.open_novisit(url).read() - result_queue.put((True, cover_data, 'jpg', self.name)) - except Exception, e: - result_queue.put((False, self.exception_to_string(e), - traceback.format_exc(), self.name)) -# }}} - -def download_cover(mi, timeout=5.): # {{{ - results = Queue() - download_covers(mi, results, max_covers=1, timeout=timeout) - errors, ans = [], None - while True: - try: - x = results.get_nowait() - if x[0]: - ans = x[1] - else: - errors.append(x) - except Empty: - break - return ans, errors - -# }}} - -def test(isbns): # {{{ - from calibre.ebooks.metadata import MetaInformation - mi = MetaInformation('test', ['test']) - for isbn in isbns: - prints('Testing ISBN:', isbn) - mi.isbn = isbn - found = check_for_cover(mi) - prints('Has cover:', found) - ans, errors = download_cover(mi) - if ans is not None: - prints('Cover downloaded') - else: - prints('Download failed:') - for err in errors: - prints('\t', err[-1]+':', err[1]) - print '\n' - -# }}} - -if __name__ == '__main__': - isbns = sys.argv[1:] + ['9781591025412', '9780307272119'] - test(isbns) diff --git a/src/calibre/ebooks/metadata/douban.py b/src/calibre/ebooks/metadata/douban.py deleted file mode 100644 index c6a34b6162..0000000000 --- a/src/calibre/ebooks/metadata/douban.py +++ /dev/null @@ -1,263 +0,0 @@ -from __future__ import with_statement -__license__ = 'GPL 3' -__copyright__ = '2009, Kovid Goyal <kovid@kovidgoyal.net>; 2010, Li Fanxi <lifanxi@freemindworld.com>' -__docformat__ = 'restructuredtext en' - -import sys, textwrap -import traceback -from urllib import urlencode -from functools import partial -from lxml import etree - -from calibre import browser, preferred_encoding -from calibre.ebooks.metadata import MetaInformation -from calibre.utils.config import OptionParser -from calibre.ebooks.metadata.fetch import MetadataSource -from calibre.utils.date import parse_date, utcnow - -NAMESPACES = { - 'openSearch':'http://a9.com/-/spec/opensearchrss/1.0/', - 'atom' : 'http://www.w3.org/2005/Atom', - 'db': 'http://www.douban.com/xmlns/' - } -XPath = partial(etree.XPath, namespaces=NAMESPACES) -total_results = XPath('//openSearch:totalResults') -start_index = XPath('//openSearch:startIndex') -items_per_page = XPath('//openSearch:itemsPerPage') -entry = XPath('//atom:entry') -entry_id = XPath('descendant::atom:id') -title = XPath('descendant::atom:title') -description = XPath('descendant::atom:summary') -publisher = XPath("descendant::db:attribute[@name='publisher']") -isbn = XPath("descendant::db:attribute[@name='isbn13']") -date = XPath("descendant::db:attribute[@name='pubdate']") -creator = XPath("descendant::db:attribute[@name='author']") -tag = XPath("descendant::db:tag") - -CALIBRE_DOUBAN_API_KEY = '0bd1672394eb1ebf2374356abec15c3d' - -class DoubanBooks(MetadataSource): - - name = 'Douban Books' - description = _('Downloads metadata from Douban.com') - supported_platforms = ['windows', 'osx', 'linux'] # Platforms this plugin will run on - author = 'Li Fanxi <lifanxi@freemindworld.com>' # The author of this plugin - version = (1, 0, 1) # The version number of this plugin - - def fetch(self): - try: - self.results = search(self.title, self.book_author, self.publisher, - self.isbn, max_results=10, - verbose=self.verbose) - except Exception, e: - self.exception = e - self.tb = traceback.format_exc() - -def report(verbose): - if verbose: - import traceback - traceback.print_exc() - -class Query(object): - - SEARCH_URL = 'http://api.douban.com/book/subjects?' - ISBN_URL = 'http://api.douban.com/book/subject/isbn/' - - type = "search" - - def __init__(self, title=None, author=None, publisher=None, isbn=None, - max_results=20, start_index=1, api_key=''): - assert not(title is None and author is None and publisher is None and \ - isbn is None) - assert (int(max_results) < 21) - q = '' - if isbn is not None: - q = isbn - self.type = 'isbn' - else: - def build_term(parts): - return ' '.join(x for x in parts) - if title is not None: - q += build_term(title.split()) - if author is not None: - q += (' ' if q else '') + build_term(author.split()) - if publisher is not None: - q += (' ' if q else '') + build_term(publisher.split()) - self.type = 'search' - - if isinstance(q, unicode): - q = q.encode('utf-8') - - if self.type == "isbn": - self.url = self.ISBN_URL + q - if api_key != '': - self.url = self.url + "?apikey=" + api_key - else: - self.url = self.SEARCH_URL+urlencode({ - 'q':q, - 'max-results':max_results, - 'start-index':start_index, - }) - if api_key != '': - self.url = self.url + "&apikey=" + api_key - - def __call__(self, browser, verbose): - if verbose: - print 'Query:', self.url - if self.type == "search": - feed = etree.fromstring(browser.open(self.url).read()) - total = int(total_results(feed)[0].text) - start = int(start_index(feed)[0].text) - entries = entry(feed) - new_start = start + len(entries) - if new_start > total: - new_start = 0 - return entries, new_start - elif self.type == "isbn": - feed = etree.fromstring(browser.open(self.url).read()) - entries = entry(feed) - return entries, 0 - -class ResultList(list): - - def get_description(self, entry, verbose): - try: - desc = description(entry) - if desc: - return 'SUMMARY:\n'+desc[0].text - except: - report(verbose) - - def get_title(self, entry): - candidates = [x.text for x in title(entry)] - return ': '.join(candidates) - - def get_authors(self, entry): - m = creator(entry) - if not m: - m = [] - m = [x.text for x in m] - return m - - def get_tags(self, entry, verbose): - try: - btags = [x.attrib["name"] for x in tag(entry)] - tags = [] - for t in btags: - tags.extend([y.strip() for y in t.split('/')]) - tags = list(sorted(list(set(tags)))) - except: - report(verbose) - tags = [] - return [x.replace(',', ';') for x in tags] - - def get_publisher(self, entry, verbose): - try: - pub = publisher(entry)[0].text - except: - pub = None - return pub - - def get_isbn(self, entry, verbose): - try: - isbn13 = isbn(entry)[0].text - except Exception: - isbn13 = None - return isbn13 - - def get_date(self, entry, verbose): - try: - d = date(entry) - if d: - default = utcnow().replace(day=15) - d = parse_date(d[0].text, assume_utc=True, default=default) - else: - d = None - except: - report(verbose) - d = None - return d - - def populate(self, entries, browser, verbose=False, api_key=''): - for x in entries: - try: - id_url = entry_id(x)[0].text - title = self.get_title(x) - except: - report(verbose) - mi = MetaInformation(title, self.get_authors(x)) - try: - if api_key != '': - id_url = id_url + "?apikey=" + api_key - raw = browser.open(id_url).read() - feed = etree.fromstring(raw) - x = entry(feed)[0] - except Exception, e: - if verbose: - print 'Failed to get all details for an entry' - print e - mi.comments = self.get_description(x, verbose) - mi.tags = self.get_tags(x, verbose) - mi.isbn = self.get_isbn(x, verbose) - mi.publisher = self.get_publisher(x, verbose) - mi.pubdate = self.get_date(x, verbose) - self.append(mi) - -def search(title=None, author=None, publisher=None, isbn=None, - verbose=False, max_results=40, api_key=None): - br = browser() - start, entries = 1, [] - - if api_key is None: - api_key = CALIBRE_DOUBAN_API_KEY - - while start > 0 and len(entries) <= max_results: - new, start = Query(title=title, author=author, publisher=publisher, - isbn=isbn, max_results=max_results, start_index=start, api_key=api_key)(br, verbose) - if not new: - break - entries.extend(new) - - entries = entries[:max_results] - - ans = ResultList() - ans.populate(entries, br, verbose, api_key) - return ans - -def option_parser(): - parser = OptionParser(textwrap.dedent( - '''\ - %prog [options] - - Fetch book metadata from Douban. You must specify one of title, author, - publisher or ISBN. If you specify ISBN the others are ignored. Will - fetch a maximum of 100 matches, so you should make your query as - specific as possible. - ''' - )) - parser.add_option('-t', '--title', help='Book title') - parser.add_option('-a', '--author', help='Book author(s)') - parser.add_option('-p', '--publisher', help='Book publisher') - parser.add_option('-i', '--isbn', help='Book ISBN') - parser.add_option('-m', '--max-results', default=10, - help='Maximum number of results to fetch') - parser.add_option('-v', '--verbose', default=0, action='count', - help='Be more verbose about errors') - return parser - -def main(args=sys.argv): - parser = option_parser() - opts, args = parser.parse_args(args) - try: - results = search(opts.title, opts.author, opts.publisher, opts.isbn, - verbose=opts.verbose, max_results=int(opts.max_results)) - except AssertionError: - report(True) - parser.print_help() - return 1 - for result in results: - print unicode(result).encode(preferred_encoding) - print - -if __name__ == '__main__': - sys.exit(main()) diff --git a/src/calibre/ebooks/metadata/epub.py b/src/calibre/ebooks/metadata/epub.py index f19b89eb88..27fa94e217 100644 --- a/src/calibre/ebooks/metadata/epub.py +++ b/src/calibre/ebooks/metadata/epub.py @@ -5,7 +5,7 @@ __copyright__ = '2008, Kovid Goyal <kovid at kovidgoyal.net>' '''Read meta information from epub files''' -import os, re, posixpath, shutil +import os, re, posixpath from cStringIO import StringIO from contextlib import closing @@ -192,6 +192,13 @@ def get_metadata(stream, extract_cover=True): def get_quick_metadata(stream): return get_metadata(stream, False) +def _write_new_cover(new_cdata, cpath): + from calibre.utils.magick.draw import save_cover_data_to + new_cover = PersistentTemporaryFile(suffix=os.path.splitext(cpath)[1]) + new_cover.close() + save_cover_data_to(new_cdata, new_cover.name) + return new_cover + def set_metadata(stream, mi, apply_null=False, update_timestamp=False): stream.seek(0) reader = OCFZipReader(stream, root=os.getcwdu()) @@ -208,6 +215,7 @@ def set_metadata(stream, mi, apply_null=False, update_timestamp=False): new_cdata = open(mi.cover, 'rb').read() except: pass + new_cover = cpath = None if new_cdata and raster_cover: try: cpath = posixpath.join(posixpath.dirname(reader.opf_path), @@ -215,19 +223,7 @@ def set_metadata(stream, mi, apply_null=False, update_timestamp=False): cover_replacable = not reader.encryption_meta.is_encrypted(cpath) and \ os.path.splitext(cpath)[1].lower() in ('.png', '.jpg', '.jpeg') if cover_replacable: - from calibre.utils.magick.draw import save_cover_data_to, \ - identify - new_cover = PersistentTemporaryFile(suffix=os.path.splitext(cpath)[1]) - resize_to = None - if False: # Resize new cover to same size as old cover - shutil.copyfileobj(reader.open(cpath), new_cover) - new_cover.close() - width, height, fmt = identify(new_cover.name) - resize_to = (width, height) - else: - new_cover.close() - save_cover_data_to(new_cdata, new_cover.name, - resize_to=resize_to) + new_cover = _write_new_cover(new_cdata, cpath) replacements[cpath] = open(new_cover.name, 'rb') except: import traceback @@ -249,4 +245,11 @@ def set_metadata(stream, mi, apply_null=False, update_timestamp=False): newopf = StringIO(reader.opf.render()) safe_replace(stream, reader.container[OPF.MIMETYPE], newopf, extra_replacements=replacements) + try: + if cpath is not None: + replacements[cpath].close() + os.remove(replacements[cpath].name) + except: + pass + diff --git a/src/calibre/ebooks/metadata/extz.py b/src/calibre/ebooks/metadata/extz.py new file mode 100644 index 0000000000..f3725027a9 --- /dev/null +++ b/src/calibre/ebooks/metadata/extz.py @@ -0,0 +1,97 @@ +# -*- coding: utf-8 -*- + +__license__ = 'GPL v3' +__copyright__ = '2011, John Schember <john@nachtimwald.com>' + +''' +Read meta information from extZ (TXTZ, HTMLZ...) files. +''' + +import os + +from cStringIO import StringIO + +from calibre.ebooks.metadata import MetaInformation +from calibre.ebooks.metadata.opf2 import OPF +from calibre.ptempfile import PersistentTemporaryFile +from calibre.utils.zipfile import ZipFile, safe_replace + +def get_metadata(stream, extract_cover=True): + ''' + Return metadata as a L{MetaInfo} object + ''' + mi = MetaInformation(_('Unknown'), [_('Unknown')]) + stream.seek(0) + + try: + with ZipFile(stream) as zf: + opf_name = get_first_opf_name(zf) + opf_stream = StringIO(zf.read(opf_name)) + opf = OPF(opf_stream) + mi = opf.to_book_metadata() + if extract_cover: + cover_href = opf.raster_cover + if cover_href: + mi.cover_data = (os.path.splitext(cover_href)[1], zf.read(cover_href)) + except: + return mi + return mi + +def set_metadata(stream, mi): + replacements = {} + + # Get the OPF in the archive. + with ZipFile(stream) as zf: + opf_path = get_first_opf_name(zf) + opf_stream = StringIO(zf.read(opf_path)) + opf = OPF(opf_stream) + + # Cover. + new_cdata = None + try: + new_cdata = mi.cover_data[1] + if not new_cdata: + raise Exception('no cover') + except: + try: + new_cdata = open(mi.cover, 'rb').read() + except: + pass + if new_cdata: + cpath = opf.raster_cover + if not cpath: + cpath = 'cover.jpg' + new_cover = _write_new_cover(new_cdata, cpath) + replacements[cpath] = open(new_cover.name, 'rb') + mi.cover = cpath + + # Update the metadata. + opf.smart_update(mi, replace_metadata=True) + newopf = StringIO(opf.render()) + safe_replace(stream, opf_path, newopf, extra_replacements=replacements, add_missing=True) + + # Cleanup temporary files. + try: + if cpath is not None: + replacements[cpath].close() + os.remove(replacements[cpath].name) + except: + pass + +def get_first_opf_name(zf): + names = zf.namelist() + opfs = [] + for n in names: + if n.endswith('.opf') and '/' not in n: + opfs.append(n) + if not opfs: + raise Exception('No OPF found') + opfs.sort() + return opfs[0] + +def _write_new_cover(new_cdata, cpath): + from calibre.utils.magick.draw import save_cover_data_to + new_cover = PersistentTemporaryFile(suffix=os.path.splitext(cpath)[1]) + new_cover.close() + save_cover_data_to(new_cdata, new_cover.name) + return new_cover diff --git a/src/calibre/ebooks/metadata/fb2.py b/src/calibre/ebooks/metadata/fb2.py index 2d6192f949..21f15b05ae 100644 --- a/src/calibre/ebooks/metadata/fb2.py +++ b/src/calibre/ebooks/metadata/fb2.py @@ -5,11 +5,12 @@ __copyright__ = '2008, Anatoly Shipitsin <norguhtar at gmail.com>' '''Read meta information from fb2 files''' -import mimetypes, os +import os from base64 import b64decode from lxml import etree from calibre.ebooks.metadata import MetaInformation from calibre.ebooks.chardet import xml_to_unicode +from calibre import guess_all_extensions XLINK_NS = 'http://www.w3.org/1999/xlink' def XLINK(name): @@ -71,7 +72,7 @@ def get_metadata(stream): binary = XPath('//fb2:binary[@id="%s"]'%id)(root) if binary: mt = binary[0].get('content-type', 'image/jpeg') - exts = mimetypes.guess_all_extensions(mt) + exts = guess_all_extensions(mt) if not exts: exts = ['.jpg'] cdata = (exts[0][1:], b64decode(tostring(binary[0]))) diff --git a/src/calibre/ebooks/metadata/fetch.py b/src/calibre/ebooks/metadata/fetch.py deleted file mode 100644 index 667b4f4d7c..0000000000 --- a/src/calibre/ebooks/metadata/fetch.py +++ /dev/null @@ -1,523 +0,0 @@ -from __future__ import with_statement -__license__ = 'GPL 3' -__copyright__ = '2009, Kovid Goyal <kovid@kovidgoyal.net>' -__docformat__ = 'restructuredtext en' - -import traceback, sys, textwrap, re -from threading import Thread - -from calibre import prints -from calibre.utils.config import OptionParser -from calibre.utils.logging import default_log -from calibre.utils.titlecase import titlecase -from calibre.customize import Plugin -from calibre.ebooks.metadata.covers import check_for_cover -from calibre.utils.html2text import html2text - -metadata_config = None - -class MetadataSource(Plugin): # {{{ - ''' - Represents a source to query for metadata. Subclasses must implement - at least the fetch method. - - When :meth:`fetch` is called, the `self` object will have the following - useful attributes (each of which may be None):: - - title, book_author, publisher, isbn, log, verbose and extra - - Use these attributes to construct the search query. extra is reserved for - future use. - - The fetch method must store the results in `self.results` as a list of - :class:`Metadata` objects. If there is an error, it should be stored - in `self.exception` and `self.tb` (for the traceback). - ''' - - author = 'Kovid Goyal' - - supported_platforms = ['windows', 'osx', 'linux'] - - #: The type of metadata fetched. 'basic' means basic metadata like - #: title/author/isbn/etc. 'social' means social metadata like - #: tags/rating/reviews/etc. - metadata_type = 'basic' - - #: If not None, the customization dialog will allow for string - #: based customization as well the default customization. The - #: string customization will be saved in the site_customization - #: member. - string_customization_help = None - - #: Set this to true if your plugin returns HTML markup in comments. - #: Then if the user disables HTML, calibre will automagically convert - #: the HTML to Markdown. - has_html_comments = False - - type = _('Metadata download') - - def __call__(self, title, author, publisher, isbn, verbose, log=None, - extra=None): - self.worker = Thread(target=self._fetch) - self.worker.daemon = True - self.title = title - self.verbose = verbose - self.book_author = author - self.publisher = publisher - self.isbn = isbn - self.log = log if log is not None else default_log - self.extra = extra - self.exception, self.tb, self.results = None, None, [] - self.worker.start() - - def _fetch(self): - try: - self.fetch() - if self.results: - c = self.config_store().get(self.name, {}) - res = self.results - if hasattr(res, 'authors'): - res = [res] - for mi in res: - if not c.get('rating', True): - mi.rating = None - if not c.get('comments', True): - mi.comments = None - if not c.get('tags', True): - mi.tags = [] - if self.has_html_comments and mi.comments and \ - c.get('textcomments', False): - try: - mi.comments = html2text(mi.comments) - except: - traceback.print_exc() - mi.comments = None - - except Exception, e: - self.exception = e - self.tb = traceback.format_exc() - - def fetch(self): - ''' - All the actual work is done here. - ''' - raise NotImplementedError - - def join(self): - return self.worker.join() - - def is_alive(self): - return self.worker.is_alive() - - def is_customizable(self): - return True - - def config_store(self): - global metadata_config - if metadata_config is None: - from calibre.utils.config import XMLConfig - metadata_config = XMLConfig('plugins/metadata_download') - return metadata_config - - def config_widget(self): - from PyQt4.Qt import QWidget, QVBoxLayout, QLabel, Qt, QLineEdit, \ - QCheckBox - from calibre.customize.ui import config - w = QWidget() - w._layout = QVBoxLayout(w) - w.setLayout(w._layout) - if self.string_customization_help is not None: - w._sc_label = QLabel(self.string_customization_help, w) - w._layout.addWidget(w._sc_label) - customization = config['plugin_customization'] - def_sc = customization.get(self.name, '') - if not def_sc: - def_sc = '' - w._sc = QLineEdit(def_sc, w) - w._layout.addWidget(w._sc) - w._sc_label.setWordWrap(True) - w._sc_label.setTextInteractionFlags(Qt.LinksAccessibleByMouse - | Qt.LinksAccessibleByKeyboard) - w._sc_label.setOpenExternalLinks(True) - c = self.config_store() - c = c.get(self.name, {}) - for x, l in {'rating':_('ratings'), 'tags':_('tags'), - 'comments':_('description/reviews')}.items(): - cb = QCheckBox(_('Download %s from %s')%(l, - self.name)) - setattr(w, '_'+x, cb) - cb.setChecked(c.get(x, True)) - w._layout.addWidget(cb) - - if self.has_html_comments: - cb = QCheckBox(_('Convert comments downloaded from %s to plain text')%(self.name)) - setattr(w, '_textcomments', cb) - cb.setChecked(c.get('textcomments', False)) - w._layout.addWidget(cb) - - return w - - def save_settings(self, w): - dl_settings = {} - for x in ('rating', 'tags', 'comments'): - dl_settings[x] = getattr(w, '_'+x).isChecked() - if self.has_html_comments: - dl_settings['textcomments'] = getattr(w, '_textcomments').isChecked() - c = self.config_store() - c.set(self.name, dl_settings) - if hasattr(w, '_sc'): - sc = unicode(w._sc.text()).strip() - from calibre.customize.ui import customize_plugin - customize_plugin(self, sc) - - def customization_help(self): - return 'This plugin can only be customized using the GUI' - - # }}} - -class GoogleBooks(MetadataSource): # {{{ - - name = 'Google Books' - description = _('Downloads metadata from Google Books') - - def fetch(self): - from calibre.ebooks.metadata.google_books import search - try: - self.results = search(self.title, self.book_author, self.publisher, - self.isbn, max_results=10, - verbose=self.verbose) - except Exception, e: - self.exception = e - self.tb = traceback.format_exc() - - # }}} - -class ISBNDB(MetadataSource): # {{{ - - name = 'IsbnDB' - description = _('Downloads metadata from isbndb.com') - - def fetch(self): - if not self.site_customization: - return - from calibre.ebooks.metadata.isbndb import option_parser, create_books - args = ['isbndb'] - if self.isbn: - args.extend(['--isbn', self.isbn]) - else: - if self.title: - args.extend(['--title', self.title]) - if self.book_author: - args.extend(['--author', self.book_author]) - if self.publisher: - args.extend(['--publisher', self.publisher]) - if self.verbose: - args.extend(['--verbose']) - args.append(self.site_customization) # IsbnDb key - try: - opts, args = option_parser().parse_args(args) - self.results = create_books(opts, args) - except Exception, e: - self.exception = e - self.tb = traceback.format_exc() - - @property - def string_customization_help(self): - ans = _('To use isbndb.com you must sign up for a %sfree account%s ' - 'and enter your access key below.') - return '<p>'+ans%('<a href="http://www.isbndb.com">', '</a>') - - # }}} - -class Amazon(MetadataSource): # {{{ - - name = 'Amazon' - metadata_type = 'social' - description = _('Downloads social metadata from amazon.com') - - has_html_comments = True - - def fetch(self): - if not self.isbn: - return - from calibre.ebooks.metadata.amazon import get_social_metadata - try: - self.results = get_social_metadata(self.title, self.book_author, - self.publisher, self.isbn) - except Exception, e: - self.exception = e - self.tb = traceback.format_exc() - - # }}} - -class KentDistrictLibrary(MetadataSource): # {{{ - - name = 'Kent District Library' - metadata_type = 'social' - description = _('Downloads series information from ww2.kdl.org. ' - 'This website cannot handle large numbers of queries, ' - 'so the plugin is disabled by default.') - - def fetch(self): - if not self.title or not self.book_author: - return - from calibre.ebooks.metadata.kdl import get_series - try: - self.results = get_series(self.title, self.book_author) - except Exception, e: - import traceback - traceback.print_exc() - self.exception = e - self.tb = traceback.format_exc() - - # }}} - - -def result_index(source, result): - if not result.isbn: - return -1 - for i, x in enumerate(source): - if x.isbn == result.isbn: - return i - return -1 - -def merge_results(one, two): - if two is not None and one is not None: - for x in two: - idx = result_index(one, x) - if idx < 0: - one.append(x) - else: - one[idx].smart_update(x) - -class MetadataSources(object): - - def __init__(self, sources): - self.sources = sources - - def __enter__(self): - for s in self.sources: - s.__enter__() - return self - - def __exit__(self, *args): - for s in self.sources: - s.__exit__() - - def __call__(self, *args, **kwargs): - for s in self.sources: - s(*args, **kwargs) - - def join(self): - for s in self.sources: - s.join() - -def filter_metadata_results(item): - keywords = ["audio", "tape", "cassette", "abridged", "playaway"] - for keyword in keywords: - if item.publisher and keyword in item.publisher.lower(): - return False - return True - -def do_cover_check(item): - item.has_cover = False - try: - item.has_cover = check_for_cover(item) - except: - pass # Cover not found - -def check_for_covers(items): - threads = [Thread(target=do_cover_check, args=(item,)) for item in items] - for t in threads: t.start() - for t in threads: t.join() - -def search(title=None, author=None, publisher=None, isbn=None, isbndb_key=None, - verbose=0): - assert not(title is None and author is None and publisher is None and \ - isbn is None) - from calibre.customize.ui import metadata_sources, migrate_isbndb_key - migrate_isbndb_key() - if isbn is not None: - isbn = re.sub(r'[^a-zA-Z0-9]', '', isbn).upper() - fetchers = list(metadata_sources(isbndb_key=isbndb_key)) - with MetadataSources(fetchers) as manager: - manager(title, author, publisher, isbn, verbose) - manager.join() - - results = list(fetchers[0].results) if fetchers else [] - for fetcher in fetchers[1:]: - merge_results(results, fetcher.results) - - results = list(filter(filter_metadata_results, results)) - - check_for_covers(results) - - words = ("the", "a", "an", "of", "and") - prefix_pat = re.compile(r'^(%s)\s+'%("|".join(words))) - trailing_paren_pat = re.compile(r'\(.*\)$') - whitespace_pat = re.compile(r'\s+') - - def sort_func(x, y): - - def cleanup_title(s): - if s is None: - s = _('Unknown') - s = s.strip().lower() - s = prefix_pat.sub(' ', s) - s = trailing_paren_pat.sub('', s) - s = whitespace_pat.sub(' ', s) - return s.strip() - - t = cleanup_title(title) - x_title = cleanup_title(x.title) - y_title = cleanup_title(y.title) - - # prefer titles that start with the search title - tx = cmp(t, x_title) - ty = cmp(t, y_title) - result = 0 if abs(tx) == abs(ty) else abs(tx) - abs(ty) - - # then prefer titles that have a cover image - if result == 0: - result = -cmp(x.has_cover, y.has_cover) - - # then prefer titles with the longest comment, with in 10% - if result == 0: - cx = len(x.comments.strip() if x.comments else '') - cy = len(y.comments.strip() if y.comments else '') - t = (cx + cy) / 20 - result = cy - cx - if abs(result) < t: - result = 0 - - return result - - results = sorted(results, cmp=sort_func) - - # if for some reason there is no comment in the top selection, go looking for one - if len(results) > 1: - if not results[0].comments or len(results[0].comments) == 0: - for r in results[1:]: - try: - if title and title.lower() == r.title[:len(title)].lower() \ - and r.comments and len(r.comments): - results[0].comments = r.comments - break - except: - pass - # Find a pubdate - pubdate = None - for r in results: - if r.pubdate is not None: - pubdate = r.pubdate - break - if pubdate is not None: - for r in results: - if r.pubdate is None: - r.pubdate = pubdate - - def fix_case(x): - if x: - x = titlecase(x) - return x - - for r in results: - r.title = fix_case(r.title) - if r.authors: - r.authors = list(map(fix_case, r.authors)) - - return results, [(x.name, x.exception, x.tb) for x in fetchers] - -def get_social_metadata(mi, verbose=0): - from calibre.customize.ui import metadata_sources - fetchers = list(metadata_sources(metadata_type='social')) - with MetadataSources(fetchers) as manager: - manager(mi.title, mi.authors, mi.publisher, mi.isbn, verbose) - manager.join() - ratings, tags, comments, series, series_index = [], set([]), set([]), None, None - for fetcher in fetchers: - if fetcher.results: - dmi = fetcher.results - if dmi.rating is not None: - ratings.append(dmi.rating) - if dmi.tags: - for t in dmi.tags: - tags.add(t) - if mi.pubdate is None and dmi.pubdate is not None: - mi.pubdate = dmi.pubdate - if dmi.comments: - comments.add(dmi.comments) - if dmi.series is not None: - series = dmi.series - if dmi.series_index is not None: - series_index = dmi.series_index - if ratings: - rating = sum(ratings)/float(len(ratings)) - if mi.rating is None or mi.rating < 0.1: - mi.rating = rating - else: - mi.rating = (mi.rating + rating)/2.0 - if tags: - if not mi.tags: - mi.tags = [] - mi.tags += list(tags) - mi.tags = list(sorted(list(set(mi.tags)))) - if comments: - if not mi.comments or len(mi.comments)+20 < len(' '.join(comments)): - mi.comments = '' - for x in comments: - mi.comments += x+'\n\n' - if series and series_index is not None: - mi.series = series - mi.series_index = series_index - - return [(x.name, x.exception, x.tb) for x in fetchers if x.exception is not - None] - - - -def option_parser(): - parser = OptionParser(textwrap.dedent( - '''\ - %prog [options] - - Fetch book metadata from online sources. You must specify at least one - of title, author, publisher or ISBN. If you specify ISBN, the others - are ignored. - ''' - )) - parser.add_option('-t', '--title', help='Book title') - parser.add_option('-a', '--author', help='Book author(s)') - parser.add_option('-p', '--publisher', help='Book publisher') - parser.add_option('-i', '--isbn', help='Book ISBN') - parser.add_option('-m', '--max-results', default=10, - help='Maximum number of results to fetch') - parser.add_option('-k', '--isbndb-key', - help=('The access key for your ISBNDB.com account. ' - 'Only needed if you want to search isbndb.com ' - 'and you haven\'t customized the IsbnDB plugin.')) - parser.add_option('-v', '--verbose', default=0, action='count', - help='Be more verbose about errors') - return parser - -def main(args=sys.argv): - parser = option_parser() - opts, args = parser.parse_args(args) - results, exceptions = search(opts.title, opts.author, opts.publisher, - opts.isbn, opts.isbndb_key, opts.verbose) - social_exceptions = [] - for result in results: - social_exceptions.extend(get_social_metadata(result, opts.verbose)) - prints(unicode(result)) - print - - for name, exception, tb in exceptions+social_exceptions: - if exception is not None: - print 'WARNING: Fetching from', name, 'failed with error:' - print exception - print tb - - return 0 - -if __name__ == '__main__': - sys.exit(main()) diff --git a/src/calibre/ebooks/metadata/fictionwise.py b/src/calibre/ebooks/metadata/fictionwise.py deleted file mode 100644 index b780f2b39d..0000000000 --- a/src/calibre/ebooks/metadata/fictionwise.py +++ /dev/null @@ -1,390 +0,0 @@ -from __future__ import with_statement -__license__ = 'GPL 3' -__copyright__ = '2010, sengian <sengian1@gmail.com>' -__docformat__ = 'restructuredtext en' - -import sys, textwrap, re, traceback, socket -from urllib import urlencode - -from lxml.html import soupparser, tostring - -from calibre import browser, preferred_encoding -from calibre.ebooks.chardet import xml_to_unicode -from calibre.ebooks.metadata import MetaInformation, check_isbn, \ - authors_to_sort_string -from calibre.library.comments import sanitize_comments_html -from calibre.ebooks.metadata.fetch import MetadataSource -from calibre.utils.config import OptionParser -from calibre.utils.date import parse_date, utcnow -from calibre.utils.cleantext import clean_ascii_chars - -class Fictionwise(MetadataSource): # {{{ - - author = 'Sengian' - name = 'Fictionwise' - description = _('Downloads metadata from Fictionwise') - - has_html_comments = True - - def fetch(self): - try: - self.results = search(self.title, self.book_author, self.publisher, - self.isbn, max_results=10, verbose=self.verbose) - except Exception, e: - self.exception = e - self.tb = traceback.format_exc() - - # }}} - -class FictionwiseError(Exception): - pass - -def report(verbose): - if verbose: - traceback.print_exc() - -class Query(object): - - BASE_URL = 'http://www.fictionwise.com/servlet/mw' - - def __init__(self, title=None, author=None, publisher=None, keywords=None, max_results=20): - assert not(title is None and author is None and publisher is None and keywords is None) - assert (max_results < 21) - - self.max_results = int(max_results) - q = { 'template' : 'searchresults_adv.htm' , - 'searchtitle' : '', - 'searchauthor' : '', - 'searchpublisher' : '', - 'searchkeyword' : '', - #possibilities startoflast, fullname, lastfirst - 'searchauthortype' : 'startoflast', - 'searchcategory' : '', - 'searchcategory2' : '', - 'searchprice_s' : '0', - 'searchprice_e' : 'ANY', - 'searchformat' : '', - 'searchgeo' : 'US', - 'searchfwdatetype' : '', - #maybe use dates fields if needed? - #'sortorder' : 'DESC', - #many options available: b.SortTitle, a.SortName, - #b.DateFirstPublished, b.FWPublishDate - 'sortby' : 'b.SortTitle' - } - if title is not None: - q['searchtitle'] = title - if author is not None: - q['searchauthor'] = author - if publisher is not None: - q['searchpublisher'] = publisher - if keywords is not None: - q['searchkeyword'] = keywords - - if isinstance(q, unicode): - q = q.encode('utf-8') - self.urldata = urlencode(q) - - def __call__(self, browser, verbose, timeout = 5.): - if verbose: - print _('Query: %s') % self.BASE_URL+self.urldata - - try: - raw = browser.open_novisit(self.BASE_URL, self.urldata, timeout=timeout).read() - except Exception, e: - report(verbose) - if callable(getattr(e, 'getcode', None)) and \ - e.getcode() == 404: - return - if isinstance(getattr(e, 'args', [None])[0], socket.timeout): - raise FictionwiseError(_('Fictionwise timed out. Try again later.')) - raise FictionwiseError(_('Fictionwise encountered an error.')) - if '<title>404 - ' in raw: - return - raw = xml_to_unicode(raw, strip_encoding_pats=True, - resolve_entities=True)[0] - try: - feed = soupparser.fromstring(raw) - except: - try: - #remove ASCII invalid chars - feed = soupparser.fromstring(clean_ascii_chars(raw)) - except: - return None - - # get list of results as links - results = feed.xpath("//table[3]/tr/td[2]/table/tr/td/p/table[2]/tr[@valign]") - results = results[:self.max_results] - results = [i.xpath('descendant-or-self::a')[0].get('href') for i in results] - #return feed if no links ie normally a single book or nothing - if not results: - results = [feed] - return results - -class ResultList(list): - - BASE_URL = 'http://www.fictionwise.com' - COLOR_VALUES = {'BLUE': 4, 'GREEN': 3, 'YELLOW': 2, 'RED': 1, 'NA': 0} - - def __init__(self): - self.retitle = re.compile(r'\[[^\[\]]+\]') - self.rechkauth = re.compile(r'.*book\s*by', re.I) - self.redesc = re.compile(r'book\s*description\s*:\s*(<br[^>]+>)*(?P<desc>.*)<br[^>]*>.{,15}publisher\s*:', re.I) - self.repub = re.compile(r'.*publisher\s*:\s*', re.I) - self.redate = re.compile(r'.*release\s*date\s*:\s*', re.I) - self.retag = re.compile(r'.*book\s*category\s*:\s*', re.I) - self.resplitbr = re.compile(r'<br[^>]*>', re.I) - self.recomment = re.compile(r'(?s)<!--.*?-->') - self.reimg = re.compile(r'<img[^>]*>', re.I) - self.resanitize = re.compile(r'\[HTML_REMOVED\]\s*', re.I) - self.renbcom = re.compile('(?P<nbcom>\d+)\s*Reader Ratings:') - self.recolor = re.compile('(?P<ncolor>[^/]+).gif') - self.resplitbrdiv = re.compile(r'(<br[^>]+>|</?div[^>]*>)', re.I) - self.reisbn = re.compile(r'.*ISBN\s*:\s*', re.I) - - def strip_tags_etree(self, etreeobj, invalid_tags): - for (itag, rmv) in invalid_tags.iteritems(): - if rmv: - for elts in etreeobj.getiterator(itag): - elts.drop_tree() - else: - for elts in etreeobj.getiterator(itag): - elts.drop_tag() - - def clean_entry(self, entry, invalid_tags = {'script': True}, - invalid_id = (), invalid_class=(), invalid_xpath = ()): - #invalid_tags: remove tag and keep content if False else remove - #remove tags - if invalid_tags: - self.strip_tags_etree(entry, invalid_tags) - #remove xpath - if invalid_xpath: - for eltid in invalid_xpath: - elt = entry.xpath(eltid) - for el in elt: - el.drop_tree() - #remove id - if invalid_id: - for eltid in invalid_id: - elt = entry.get_element_by_id(eltid) - if elt is not None: - elt.drop_tree() - #remove class - if invalid_class: - for eltclass in invalid_class: - elts = entry.find_class(eltclass) - if elts is not None: - for elt in elts: - elt.drop_tree() - - def output_entry(self, entry, prettyout = True, htmlrm="\d+"): - out = tostring(entry, pretty_print=prettyout) - #try to work around tostring to remove this encoding for exemle - reclean = re.compile('(\n+|\t+|\r+|&#'+htmlrm+';)') - return reclean.sub('', out) - - def get_title(self, entry): - title = entry.findtext('./') - return self.retitle.sub('', title).strip() - - def get_authors(self, entry): - authortext = entry.find('./br').tail - if not self.rechkauth.search(authortext): - return [] - authortext = self.rechkauth.sub('', authortext) - return [a.strip() for a in authortext.split('&')] - - def get_rating(self, entrytable, verbose): - nbcomment = tostring(entrytable.getprevious()) - try: - nbcomment = self.renbcom.search(nbcomment).group("nbcom") - except: - report(verbose) - return None - hval = dict((self.COLOR_VALUES[self.recolor.search(image.get('src', default='NA.gif')).group("ncolor")], - float(image.get('height', default=0))) \ - for image in entrytable.getiterator('img')) - #ratings as x/5 - return float(1.25*sum(k*v for (k, v) in hval.iteritems())/sum(hval.itervalues())) - - def get_description(self, entry): - description = self.output_entry(entry.xpath('./p')[1],htmlrm="") - description = self.redesc.search(description) - if not description or not description.group("desc"): - return None - #remove invalid tags - description = self.reimg.sub('', description.group("desc")) - description = self.recomment.sub('', description) - description = self.resanitize.sub('', sanitize_comments_html(description)) - return _('SUMMARY:\n %s') % re.sub(r'\n\s+</p>','\n</p>', description) - - def get_publisher(self, entry): - publisher = self.output_entry(entry.xpath('./p')[1]) - publisher = filter(lambda x: self.repub.search(x) is not None, - self.resplitbr.split(publisher)) - if not len(publisher): - return None - publisher = self.repub.sub('', publisher[0]) - return publisher.split(',')[0].strip() - - def get_tags(self, entry): - tag = self.output_entry(entry.xpath('./p')[1]) - tag = filter(lambda x: self.retag.search(x) is not None, - self.resplitbr.split(tag)) - if not len(tag): - return [] - return map(lambda x: x.strip(), self.retag.sub('', tag[0]).split('/')) - - def get_date(self, entry, verbose): - date = self.output_entry(entry.xpath('./p')[1]) - date = filter(lambda x: self.redate.search(x) is not None, - self.resplitbr.split(date)) - if not len(date): - return None - try: - d = self.redate.sub('', date[0]) - if d: - default = utcnow().replace(day=15) - d = parse_date(d, assume_utc=True, default=default) - else: - d = None - except: - report(verbose) - d = None - return d - - def get_ISBN(self, entry): - isbns = self.output_entry(entry.xpath('./p')[2]) - isbns = filter(lambda x: self.reisbn.search(x) is not None, - self.resplitbrdiv.split(isbns)) - if not len(isbns): - return None - isbns = [self.reisbn.sub('', x) for x in isbns if check_isbn(self.reisbn.sub('', x))] - return sorted(isbns, cmp=lambda x,y:cmp(len(x), len(y)))[-1] - - def fill_MI(self, entry, title, authors, ratings, verbose): - mi = MetaInformation(title, authors) - mi.rating = ratings - mi.comments = self.get_description(entry) - mi.publisher = self.get_publisher(entry) - mi.tags = self.get_tags(entry) - mi.pubdate = self.get_date(entry, verbose) - mi.isbn = self.get_ISBN(entry) - mi.author_sort = authors_to_sort_string(authors) - return mi - - def get_individual_metadata(self, browser, linkdata, verbose): - try: - raw = browser.open_novisit(self.BASE_URL + linkdata).read() - except Exception, e: - report(verbose) - if callable(getattr(e, 'getcode', None)) and \ - e.getcode() == 404: - return - if isinstance(getattr(e, 'args', [None])[0], socket.timeout): - raise FictionwiseError(_('Fictionwise timed out. Try again later.')) - raise FictionwiseError(_('Fictionwise encountered an error.')) - if '<title>404 - ' in raw: - report(verbose) - return - raw = xml_to_unicode(raw, strip_encoding_pats=True, - resolve_entities=True)[0] - try: - return soupparser.fromstring(raw) - except: - try: - #remove ASCII invalid chars - return soupparser.fromstring(clean_ascii_chars(raw)) - except: - return None - - def populate(self, entries, browser, verbose=False): - inv_tags ={'script': True, 'a': False, 'font': False, 'strong': False, 'b': False, - 'ul': False, 'span': False} - inv_xpath =('./table',) - #single entry - if len(entries) == 1 and not isinstance(entries[0], str): - try: - entry = entries.xpath("//table[3]/tr/td[2]/table[1]/tr/td/font/table/tr/td") - self.clean_entry(entry, invalid_tags=inv_tags, invalid_xpath=inv_xpath) - title = self.get_title(entry) - #maybe strenghten the search - ratings = self.get_rating(entry.xpath("./p/table")[1], verbose) - authors = self.get_authors(entry) - except Exception, e: - if verbose: - print _('Failed to get all details for an entry') - print e - return - self.append(self.fill_MI(entry, title, authors, ratings, verbose)) - else: - #multiple entries - for x in entries: - try: - entry = self.get_individual_metadata(browser, x, verbose) - entry = entry.xpath("//table[3]/tr/td[2]/table[1]/tr/td/font/table/tr/td")[0] - self.clean_entry(entry, invalid_tags=inv_tags, invalid_xpath=inv_xpath) - title = self.get_title(entry) - #maybe strenghten the search - ratings = self.get_rating(entry.xpath("./p/table")[1], verbose) - authors = self.get_authors(entry) - except Exception, e: - if verbose: - print _('Failed to get all details for an entry') - print e - continue - self.append(self.fill_MI(entry, title, authors, ratings, verbose)) - - -def search(title=None, author=None, publisher=None, isbn=None, - min_viewability='none', verbose=False, max_results=5, - keywords=None): - br = browser() - entries = Query(title=title, author=author, publisher=publisher, - keywords=keywords, max_results=max_results)(br, verbose, timeout = 15.) - - #List of entry - ans = ResultList() - ans.populate(entries, br, verbose) - return ans - - -def option_parser(): - parser = OptionParser(textwrap.dedent(\ - _('''\ - %prog [options] - - Fetch book metadata from Fictionwise. You must specify one of title, author, - or keywords. No ISBN specification possible. Will fetch a maximum of 20 matches, - so you should make your query as specific as possible. - ''') - )) - parser.add_option('-t', '--title', help=_('Book title')) - parser.add_option('-a', '--author', help=_('Book author(s)')) - parser.add_option('-p', '--publisher', help=_('Book publisher')) - parser.add_option('-k', '--keywords', help=_('Keywords')) - parser.add_option('-m', '--max-results', default=20, - help=_('Maximum number of results to fetch')) - parser.add_option('-v', '--verbose', default=0, action='count', - help=_('Be more verbose about errors')) - return parser - -def main(args=sys.argv): - parser = option_parser() - opts, args = parser.parse_args(args) - try: - results = search(opts.title, opts.author, publisher=opts.publisher, - keywords=opts.keywords, verbose=opts.verbose, max_results=opts.max_results) - except AssertionError: - report(True) - parser.print_help() - return 1 - if results is None or len(results) == 0: - print _('No result found for this search!') - return 0 - for result in results: - print unicode(result).encode(preferred_encoding, 'replace') - print - -if __name__ == '__main__': - sys.exit(main()) diff --git a/src/calibre/ebooks/metadata/google_books.py b/src/calibre/ebooks/metadata/google_books.py deleted file mode 100644 index 2087b7c489..0000000000 --- a/src/calibre/ebooks/metadata/google_books.py +++ /dev/null @@ -1,246 +0,0 @@ -from __future__ import with_statement -__license__ = 'GPL 3' -__copyright__ = '2009, Kovid Goyal <kovid@kovidgoyal.net>' -__docformat__ = 'restructuredtext en' - -import sys, textwrap -from urllib import urlencode -from functools import partial - -from lxml import etree - -from calibre import browser, preferred_encoding -from calibre.ebooks.metadata import MetaInformation -from calibre.utils.config import OptionParser -from calibre.utils.date import parse_date, utcnow - -NAMESPACES = { - 'openSearch':'http://a9.com/-/spec/opensearchrss/1.0/', - 'atom' : 'http://www.w3.org/2005/Atom', - 'dc': 'http://purl.org/dc/terms' - } -XPath = partial(etree.XPath, namespaces=NAMESPACES) - -total_results = XPath('//openSearch:totalResults') -start_index = XPath('//openSearch:startIndex') -items_per_page = XPath('//openSearch:itemsPerPage') -entry = XPath('//atom:entry') -entry_id = XPath('descendant::atom:id') -creator = XPath('descendant::dc:creator') -identifier = XPath('descendant::dc:identifier') -title = XPath('descendant::dc:title') -date = XPath('descendant::dc:date') -publisher = XPath('descendant::dc:publisher') -subject = XPath('descendant::dc:subject') -description = XPath('descendant::dc:description') -language = XPath('descendant::dc:language') - -def report(verbose): - if verbose: - import traceback - traceback.print_exc() - - -class Query(object): - - BASE_URL = 'http://books.google.com/books/feeds/volumes?' - - def __init__(self, title=None, author=None, publisher=None, isbn=None, - max_results=20, min_viewability='none', start_index=1): - assert not(title is None and author is None and publisher is None and \ - isbn is None) - assert (max_results < 21) - assert (min_viewability in ('none', 'partial', 'full')) - q = '' - if isbn is not None: - q += 'isbn:'+isbn - else: - def build_term(prefix, parts): - return ' '.join('in'+prefix + ':' + x for x in parts) - if title is not None: - q += build_term('title', title.split()) - if author is not None: - q += ('+' if q else '')+build_term('author', author.split()) - if publisher is not None: - q += ('+' if q else '')+build_term('publisher', publisher.split()) - - if isinstance(q, unicode): - q = q.encode('utf-8') - self.url = self.BASE_URL+urlencode({ - 'q':q, - 'max-results':max_results, - 'start-index':start_index, - 'min-viewability':min_viewability, - }) - - def __call__(self, browser, verbose): - if verbose: - print 'Query:', self.url - feed = etree.fromstring(browser.open(self.url).read()) - #print etree.tostring(feed, pretty_print=True) - total = int(total_results(feed)[0].text) - start = int(start_index(feed)[0].text) - entries = entry(feed) - new_start = start + len(entries) - if new_start > total: - new_start = 0 - return entries, new_start - - -class ResultList(list): - - def get_description(self, entry, verbose): - try: - desc = description(entry) - if desc: - return 'SUMMARY:\n'+desc[0].text - except: - report(verbose) - - def get_language(self, entry, verbose): - try: - l = language(entry) - if l: - return l[0].text - except: - report(verbose) - - def get_title(self, entry): - candidates = [x.text for x in title(entry)] - return ': '.join(candidates) - - def get_authors(self, entry): - m = creator(entry) - if not m: - m = [] - m = [x.text for x in m] - return m - - def get_author_sort(self, entry, verbose): - for x in creator(entry): - for key, val in x.attrib.items(): - if key.endswith('file-as'): - return val - - def get_identifiers(self, entry, mi): - isbns = [] - for x in identifier(entry): - t = str(x.text).strip() - if t[:5].upper() in ('ISBN:', 'LCCN:', 'OCLC:'): - if t[:5].upper() == 'ISBN:': - isbns.append(t[5:]) - if isbns: - mi.isbn = sorted(isbns, cmp=lambda x,y:cmp(len(x), len(y)))[-1] - - def get_tags(self, entry, verbose): - try: - btags = [x.text for x in subject(entry)] - tags = [] - for t in btags: - tags.extend([y.strip() for y in t.split('/')]) - tags = list(sorted(list(set(tags)))) - except: - report(verbose) - tags = [] - return [x.replace(',', ';') for x in tags] - - def get_publisher(self, entry, verbose): - try: - pub = publisher(entry)[0].text - except: - pub = None - return pub - - def get_date(self, entry, verbose): - try: - d = date(entry) - if d: - default = utcnow().replace(day=15) - d = parse_date(d[0].text, assume_utc=True, default=default) - else: - d = None - except: - report(verbose) - d = None - return d - - def populate(self, entries, browser, verbose=False): - for x in entries: - try: - id_url = entry_id(x)[0].text - title = self.get_title(x) - except: - report(verbose) - mi = MetaInformation(title, self.get_authors(x)) - try: - raw = browser.open(id_url).read() - feed = etree.fromstring(raw) - x = entry(feed)[0] - except Exception, e: - if verbose: - print 'Failed to get all details for an entry' - print e - mi.author_sort = self.get_author_sort(x, verbose) - mi.comments = self.get_description(x, verbose) - self.get_identifiers(x, mi) - mi.tags = self.get_tags(x, verbose) - mi.publisher = self.get_publisher(x, verbose) - mi.pubdate = self.get_date(x, verbose) - mi.language = self.get_language(x, verbose) - self.append(mi) - - -def search(title=None, author=None, publisher=None, isbn=None, - min_viewability='none', verbose=False, max_results=40): - br = browser() - start, entries = 1, [] - while start > 0 and len(entries) <= max_results: - new, start = Query(title=title, author=author, publisher=publisher, - isbn=isbn, min_viewability=min_viewability)(br, verbose) - if not new: - break - entries.extend(new) - - entries = entries[:max_results] - - ans = ResultList() - ans.populate(entries, br, verbose) - return ans - -def option_parser(): - parser = OptionParser(textwrap.dedent( - '''\ - %prog [options] - - Fetch book metadata from Google. You must specify one of title, author, - publisher or ISBN. If you specify ISBN the others are ignored. Will - fetch a maximum of 100 matches, so you should make your query as - specific as possible. - ''' - )) - parser.add_option('-t', '--title', help='Book title') - parser.add_option('-a', '--author', help='Book author(s)') - parser.add_option('-p', '--publisher', help='Book publisher') - parser.add_option('-i', '--isbn', help='Book ISBN') - parser.add_option('-m', '--max-results', default=10, - help='Maximum number of results to fetch') - parser.add_option('-v', '--verbose', default=0, action='count', - help='Be more verbose about errors') - return parser - -def main(args=sys.argv): - parser = option_parser() - opts, args = parser.parse_args(args) - try: - results = search(opts.title, opts.author, opts.publisher, opts.isbn, - verbose=opts.verbose, max_results=opts.max_results) - except AssertionError: - report(True) - parser.print_help() - return 1 - for result in results: - print unicode(result).encode(preferred_encoding) - print - -if __name__ == '__main__': - sys.exit(main()) diff --git a/src/calibre/ebooks/metadata/imp.py b/src/calibre/ebooks/metadata/imp.py index e2a2b61f31..28bc2bc00f 100644 --- a/src/calibre/ebooks/metadata/imp.py +++ b/src/calibre/ebooks/metadata/imp.py @@ -38,7 +38,7 @@ def get_metadata(stream): mi.author = author if category: mi.category = category - except Exception, err: + except Exception as err: msg = u'Couldn\'t read metadata from imp: %s with error %s'%(mi.title, unicode(err)) print >>sys.stderr, msg.encode('utf8') return mi diff --git a/src/calibre/ebooks/metadata/isbndb.py b/src/calibre/ebooks/metadata/isbndb.py deleted file mode 100644 index 1c5f706593..0000000000 --- a/src/calibre/ebooks/metadata/isbndb.py +++ /dev/null @@ -1,159 +0,0 @@ -__license__ = 'GPL v3' -__copyright__ = '2008, Kovid Goyal <kovid at kovidgoyal.net>' -''' -Interface to isbndb.com. My key HLLXQX2A. -''' - -import sys, re -from urllib import quote - -from calibre.utils.config import OptionParser -from calibre.ebooks.metadata.book.base import Metadata -from calibre.ebooks.BeautifulSoup import BeautifulStoneSoup -from calibre import browser - -BASE_URL = 'http://isbndb.com/api/books.xml?access_key=%(key)s&page_number=1&results=subjects,authors,texts&' - -class ISBNDBError(Exception): - pass - -def fetch_metadata(url, max=3, timeout=5.): - books = [] - page_number = 1 - total_results = 31 - br = browser() - while len(books) < total_results and max > 0: - try: - raw = br.open(url, timeout=timeout).read() - except Exception, err: - raise ISBNDBError('Could not fetch ISBNDB metadata. Error: '+str(err)) - soup = BeautifulStoneSoup(raw, - convertEntities=BeautifulStoneSoup.XML_ENTITIES) - book_list = soup.find('booklist') - if book_list is None: - errmsg = soup.find('errormessage').string - raise ISBNDBError('Error fetching metadata: '+errmsg) - total_results = int(book_list['total_results']) - page_number += 1 - np = '&page_number=%s&'%page_number - url = re.sub(r'\&page_number=\d+\&', np, url) - books.extend(book_list.findAll('bookdata')) - max -= 1 - return books - - -class ISBNDBMetadata(Metadata): - - def __init__(self, book): - Metadata.__init__(self, None) - - def tostring(e): - if not hasattr(e, 'string'): - return None - ans = e.string - if ans is not None: - ans = unicode(ans).strip() - if not ans: - ans = None - return ans - - self.isbn = unicode(book.get('isbn13', book.get('isbn'))) - title = tostring(book.find('titlelong')) - if not title: - title = tostring(book.find('title')) - self.title = title - self.title = unicode(self.title).strip() - authors = [] - au = tostring(book.find('authorstext')) - if au: - au = au.strip() - temp = au.split(',') - for au in temp: - if not au: continue - authors.extend([a.strip() for a in au.split('&')]) - if authors: - self.authors = authors - try: - self.author_sort = tostring(book.find('authors').find('person')) - if self.authors and self.author_sort == self.authors[0]: - self.author_sort = None - except: - pass - self.publisher = tostring(book.find('publishertext')) - - summ = tostring(book.find('summary')) - if summ: - self.comments = 'SUMMARY:\n'+summ - - -def build_isbn(base_url, opts): - return base_url + 'index1=isbn&value1='+opts.isbn - -def build_combined(base_url, opts): - query = ' '.join([e for e in (opts.title, opts.author, opts.publisher) \ - if e is not None ]) - query = query.strip() - if len(query) == 0: - raise ISBNDBError('You must specify at least one of --author, --title or --publisher') - - query = re.sub(r'\s+', '+', query) - if isinstance(query, unicode): - query = query.encode('utf-8') - return base_url+'index1=combined&value1='+quote(query, '+') - - -def option_parser(): - parser = OptionParser(usage=\ -_(''' -%prog [options] key - -Fetch metadata for books from isndb.com. You can specify either the -books ISBN ID or its title and author. If you specify the title and author, -then more than one book may be returned. - -key is the account key you generate after signing up for a free account from isbndb.com. - -''')) - parser.add_option('-i', '--isbn', default=None, dest='isbn', - help=_('The ISBN ID of the book you want metadata for.')) - parser.add_option('-a', '--author', dest='author', - default=None, help=_('The author whose book to search for.')) - parser.add_option('-t', '--title', dest='title', - default=None, help=_('The title of the book to search for.')) - parser.add_option('-p', '--publisher', default=None, dest='publisher', - help=_('The publisher of the book to search for.')) - parser.add_option('-v', '--verbose', default=False, - action='store_true', help=_('Verbose processing')) - - return parser - - -def create_books(opts, args, timeout=5.): - base_url = BASE_URL%dict(key=args[1]) - if opts.isbn is not None: - url = build_isbn(base_url, opts) - else: - url = build_combined(base_url, opts) - - if opts.verbose: - print ('ISBNDB query: '+url) - - tans = [ISBNDBMetadata(book) for book in fetch_metadata(url, timeout=timeout)] - #remove duplicates ISBN - return list(dict((book.isbn, book) for book in tans).values()) - -def main(args=sys.argv): - parser = option_parser() - opts, args = parser.parse_args(args) - if len(args) != 2: - parser.print_help() - print ('You must supply the isbndb.com key') - return 1 - - for book in create_books(opts, args): - print unicode(book).encode('utf-8') - - return 0 - -if __name__ == '__main__': - sys.exit(main()) diff --git a/src/calibre/ebooks/metadata/kdl.py b/src/calibre/ebooks/metadata/kdl.py index b0b961b603..aa2f0d7246 100644 --- a/src/calibre/ebooks/metadata/kdl.py +++ b/src/calibre/ebooks/metadata/kdl.py @@ -43,7 +43,7 @@ def get_series(title, authors, timeout=60): br = browser() try: raw = br.open_novisit(url, timeout=timeout).read() - except URLError, e: + except URLError as e: if isinstance(e.reason, socket.timeout): raise Exception('KDL Server busy, try again later') raise diff --git a/src/calibre/ebooks/metadata/library_thing.py b/src/calibre/ebooks/metadata/library_thing.py index a0f28a3c21..ddd4b64f58 100644 --- a/src/calibre/ebooks/metadata/library_thing.py +++ b/src/calibre/ebooks/metadata/library_thing.py @@ -4,34 +4,23 @@ __copyright__ = '2008, Kovid Goyal <kovid at kovidgoyal.net>' Fetch cover from LibraryThing.com based on ISBN number. ''' -import sys, re, random +import sys, re from lxml import html import mechanize -from calibre import browser, prints +from calibre import browser, prints, random_user_agent from calibre.utils.config import OptionParser from calibre.ebooks.chardet import strip_encoding_declarations OPENLIBRARY = 'http://covers.openlibrary.org/b/isbn/%s-L.jpg?default=false' -def get_ua(): - choices = [ - 'Mozilla/5.0 (Windows; U; Windows NT 6.0; en-US; rv:1.9.2.11) Gecko/20101012 Firefox/3.6.11' - 'Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.1)' - 'Mozilla/4.0 (compatible; MSIE 7.0; Windows NT 6.0)' - 'Mozilla/4.0 (compatible; MSIE 8.0; Windows NT 6.1)' - 'Mozilla/5.0 (iPhone; U; CPU iPhone OS 3_0 like Mac OS X; en-us) AppleWebKit/528.18 (KHTML, like Gecko) Version/4.0 Mobile/7A341 Safari/528.16' - 'Mozilla/5.0 (Windows; U; Windows NT 5.1; en-US) AppleWebKit/525.19 (KHTML, like Gecko) Chrome/0.2.153.1 Safari/525.19' - 'Mozilla/5.0 (Windows; U; Windows NT 6.0; en-US; rv:1.9.2.11) Gecko/20101012 Firefox/3.6.11' - ] - return choices[random.randint(0, len(choices)-1)] _lt_br = None def get_browser(): global _lt_br if _lt_br is None: - _lt_br = browser(user_agent=get_ua()) + _lt_br = browser(user_agent=random_user_agent()) return _lt_br.clone_browser() class HeadRequest(mechanize.Request): @@ -45,7 +34,7 @@ def check_for_cover(isbn, timeout=5.): try: br.open_novisit(HeadRequest(OPENLIBRARY%isbn), timeout=timeout) return True - except Exception, e: + except Exception as e: if callable(getattr(e, 'getcode', None)) and e.getcode() == 302: return True return False diff --git a/src/calibre/ebooks/metadata/meta.py b/src/calibre/ebooks/metadata/meta.py index cbd9db3f04..b0c43a8182 100644 --- a/src/calibre/ebooks/metadata/meta.py +++ b/src/calibre/ebooks/metadata/meta.py @@ -182,6 +182,19 @@ def metadata_from_filename(name, pat=None): mi.isbn = si except (IndexError, ValueError): pass + try: + publisher = match.group('publisher') + mi.publisher = publisher + except (IndexError, ValueError): + pass + try: + pubdate = match.group('published') + if pubdate: + from calibre.utils.date import parse_date + mi.pubdate = parse_date(pubdate) + except: + pass + if mi.is_null('title'): mi.title = name return mi diff --git a/src/calibre/ebooks/metadata/mobi.py b/src/calibre/ebooks/metadata/mobi.py index 963391dcf8..74db3b3a58 100644 --- a/src/calibre/ebooks/metadata/mobi.py +++ b/src/calibre/ebooks/metadata/mobi.py @@ -259,6 +259,7 @@ class MetadataUpdater(object): trail = len(new_record0.getvalue()) % 4 pad = '\0' * (4 - trail) # Always pad w/ at least 1 byte new_record0.write(pad) + new_record0.write('\0'*(1024*8)) # Rebuild the stream, update the pdbrecords pointers self.patchSection(0,new_record0.getvalue()) @@ -271,11 +272,11 @@ class MetadataUpdater(object): FILTER=''.join([(len(repr(chr(x)))==3) and chr(x) or '.' for x in range(256)]) N=0; result='' while src: - s,src = src[:length],src[length:] - hexa = ' '.join(["%02X"%ord(x) for x in s]) - s = s.translate(FILTER) - result += "%04X %-*s %s\n" % (N, length*3, hexa, s) - N+=length + s,src = src[:length],src[length:] + hexa = ' '.join(["%02X"%ord(x) for x in s]) + s = s.translate(FILTER) + result += "%04X %-*s %s\n" % (N, length*3, hexa, s) + N+=length print result def get_pdbrecords(self): @@ -323,6 +324,7 @@ class MetadataUpdater(object): "\tThis is a '%s' file of type '%s'" % (self.type[0:4], self.type[4:8])) recs = [] + added_501 = False try: from calibre.ebooks.conversion.config import load_defaults prefs = load_defaults('mobi_output') @@ -355,6 +357,7 @@ class MetadataUpdater(object): update_exth_record((105, normalize(subjects).encode(self.codec, 'replace'))) if kindle_pdoc and kindle_pdoc in mi.tags: + added_501 = True update_exth_record((501, str('PDOC'))) if mi.pubdate: @@ -370,6 +373,12 @@ class MetadataUpdater(object): update_exth_record((203, pack('>I', 0))) if self.thumbnail_record is not None: update_exth_record((202, pack('>I', self.thumbnail_rindex))) + # Add a 113 record if not present to allow Amazon syncing + if (113 not in self.original_exth_records and + self.original_exth_records.get(501, None) == 'EBOK' and + not added_501): + from uuid import uuid4 + update_exth_record((113, str(uuid4()))) if 503 in self.original_exth_records: update_exth_record((503, mi.title.encode(self.codec, 'replace'))) @@ -391,7 +400,8 @@ class MetadataUpdater(object): if getattr(self, 'exth', None) is None: raise MobiError('No existing EXTH record. Cannot update metadata.') - self.record0[92:96] = iana2mobi(mi.language) + if not mi.is_null('language'): + self.record0[92:96] = iana2mobi(mi.language) self.create_exth(exth=exth, new_title=mi.title) # Fetch updated timestamp, cover_record, thumbnail_record diff --git a/src/calibre/ebooks/metadata/nicebooks.py b/src/calibre/ebooks/metadata/nicebooks.py deleted file mode 100644 index 8914e2d985..0000000000 --- a/src/calibre/ebooks/metadata/nicebooks.py +++ /dev/null @@ -1,411 +0,0 @@ -from __future__ import with_statement -__license__ = 'GPL 3' -__copyright__ = '2010, sengian <sengian1@gmail.com>' -__docformat__ = 'restructuredtext en' - -import sys, textwrap, re, traceback, socket -from urllib import urlencode -from math import ceil -from copy import deepcopy - -from lxml.html import soupparser - -from calibre.utils.date import parse_date, utcnow, replace_months -from calibre.utils.cleantext import clean_ascii_chars -from calibre import browser, preferred_encoding -from calibre.ebooks.chardet import xml_to_unicode -from calibre.ebooks.metadata import MetaInformation, check_isbn, \ - authors_to_sort_string -from calibre.ebooks.metadata.fetch import MetadataSource -from calibre.ebooks.metadata.covers import CoverDownload -from calibre.utils.config import OptionParser - -class NiceBooks(MetadataSource): - - name = 'Nicebooks' - description = _('Downloads metadata from french Nicebooks') - supported_platforms = ['windows', 'osx', 'linux'] - author = 'Sengian' - version = (1, 0, 0) - - def fetch(self): - try: - self.results = search(self.title, self.book_author, self.publisher, - self.isbn, max_results=10, verbose=self.verbose) - except Exception, e: - self.exception = e - self.tb = traceback.format_exc() - -class NiceBooksCovers(CoverDownload): - - name = 'Nicebooks covers' - description = _('Downloads covers from french Nicebooks') - supported_platforms = ['windows', 'osx', 'linux'] - author = 'Sengian' - type = _('Cover download') - version = (1, 0, 0) - - def has_cover(self, mi, ans, timeout=5.): - if not mi.isbn: - return False - br = browser() - try: - entry = Query(isbn=mi.isbn, max_results=1)(br, False, timeout)[0] - if Covers(mi.isbn)(entry).check_cover(): - self.debug('cover for', mi.isbn, 'found') - ans.set() - except Exception, e: - self.debug(e) - - def get_covers(self, mi, result_queue, abort, timeout=5.): - if not mi.isbn: - return - br = browser() - try: - entry = Query(isbn=mi.isbn, max_results=1)(br, False, timeout)[0] - cover_data, ext = Covers(mi.isbn)(entry).get_cover(br, timeout) - if not ext: - ext = 'jpg' - result_queue.put((True, cover_data, ext, self.name)) - except Exception, e: - result_queue.put((False, self.exception_to_string(e), - traceback.format_exc(), self.name)) - - -class NiceBooksError(Exception): - pass - -class ISBNNotFound(NiceBooksError): - pass - -def report(verbose): - if verbose: - traceback.print_exc() - -class Query(object): - - BASE_URL = 'http://fr.nicebooks.com/' - - def __init__(self, title=None, author=None, publisher=None, isbn=None, keywords=None, max_results=20): - assert not(title is None and author is None and publisher is None \ - and isbn is None and keywords is None) - assert (max_results < 21) - - self.max_results = int(max_results) - - if isbn is not None: - q = isbn - else: - q = ' '.join([i for i in (title, author, publisher, keywords) \ - if i is not None]) - - if isinstance(q, unicode): - q = q.encode('utf-8') - self.urldata = 'search?' + urlencode({'q':q,'s':'Rechercher'}) - - def __call__(self, browser, verbose, timeout = 5.): - if verbose: - print _('Query: %s') % self.BASE_URL+self.urldata - - try: - raw = browser.open_novisit(self.BASE_URL+self.urldata, timeout=timeout).read() - except Exception, e: - report(verbose) - if callable(getattr(e, 'getcode', None)) and \ - e.getcode() == 404: - return - if isinstance(getattr(e, 'args', [None])[0], socket.timeout): - raise NiceBooksError(_('Nicebooks timed out. Try again later.')) - raise NiceBooksError(_('Nicebooks encountered an error.')) - if '<title>404 - ' in raw: - return - raw = xml_to_unicode(raw, strip_encoding_pats=True, - resolve_entities=True)[0] - try: - feed = soupparser.fromstring(raw) - except: - try: - #remove ASCII invalid chars - feed = soupparser.fromstring(clean_ascii_chars(raw)) - except: - return None - - #nb of page to call - try: - nbresults = int(feed.xpath("//div[@id='topbar']/b")[0].text) - except: - #direct hit - return [feed] - - nbpagetoquery = int(ceil(float(min(nbresults, self.max_results))/10)) - pages =[feed] - if nbpagetoquery > 1: - for i in xrange(2, nbpagetoquery + 1): - try: - urldata = self.urldata + '&p=' + str(i) - raw = browser.open_novisit(self.BASE_URL+urldata, timeout=timeout).read() - except Exception, e: - continue - if '<title>404 - ' in raw: - continue - raw = xml_to_unicode(raw, strip_encoding_pats=True, - resolve_entities=True)[0] - try: - feed = soupparser.fromstring(raw) - except: - try: - #remove ASCII invalid chars - feed = soupparser.fromstring(clean_ascii_chars(raw)) - except: - continue - pages.append(feed) - - results = [] - for x in pages: - results.extend([i.find_class('title')[0].get('href') \ - for i in x.xpath("//ul[@id='results']/li")]) - return results[:self.max_results] - -class ResultList(list): - - BASE_URL = 'http://fr.nicebooks.com' - - def __init__(self): - self.repub = re.compile(u'\s*.diteur\s*', re.I) - self.reauteur = re.compile(u'\s*auteur.*', re.I) - self.reautclean = re.compile(u'\s*\(.*\)\s*') - - def get_title(self, entry): - title = deepcopy(entry) - title.remove(title.find("dl[@title='Informations sur le livre']")) - title = ' '.join([i.text_content() for i in title.iterchildren()]) - return unicode(title.replace('\n', '')) - - def get_authors(self, entry): - author = entry.find("dl[@title='Informations sur le livre']") - authortext = [] - for x in author.getiterator('dt'): - if self.reauteur.match(x.text): - elt = x.getnext() - while elt.tag == 'dd': - authortext.append(unicode(elt.text_content())) - elt = elt.getnext() - break - if len(authortext) == 1: - authortext = [self.reautclean.sub('', authortext[0])] - return authortext - - def get_description(self, entry, verbose): - try: - return u'RESUME:\n' + unicode(entry.getparent().xpath("//p[@id='book-description']")[0].text) - except: - report(verbose) - return None - - def get_book_info(self, entry, mi, verbose): - entry = entry.find("dl[@title='Informations sur le livre']") - for x in entry.getiterator('dt'): - if x.text == 'ISBN': - isbntext = x.getnext().text_content().replace('-', '') - if check_isbn(isbntext): - mi.isbn = unicode(isbntext) - elif self.repub.match(x.text): - mi.publisher = unicode(x.getnext().text_content()) - elif x.text == 'Langue': - mi.language = unicode(x.getnext().text_content()) - elif x.text == 'Date de parution': - d = x.getnext().text_content() - try: - default = utcnow().replace(day=15) - d = replace_months(d, 'fr') - d = parse_date(d, assume_utc=True, default=default) - mi.pubdate = d - except: - report(verbose) - return mi - - def fill_MI(self, entry, title, authors, verbose): - mi = MetaInformation(title, authors) - mi.author_sort = authors_to_sort_string(authors) - mi.comments = self.get_description(entry, verbose) - return self.get_book_info(entry, mi, verbose) - - def get_individual_metadata(self, browser, linkdata, verbose): - try: - raw = browser.open_novisit(self.BASE_URL + linkdata).read() - except Exception, e: - report(verbose) - if callable(getattr(e, 'getcode', None)) and \ - e.getcode() == 404: - return - if isinstance(getattr(e, 'args', [None])[0], socket.timeout): - raise NiceBooksError(_('Nicebooks timed out. Try again later.')) - raise NiceBooksError(_('Nicebooks encountered an error.')) - if '<title>404 - ' in raw: - report(verbose) - return - raw = xml_to_unicode(raw, strip_encoding_pats=True, - resolve_entities=True)[0] - try: - feed = soupparser.fromstring(raw) - except: - try: - #remove ASCII invalid chars - feed = soupparser.fromstring(clean_ascii_chars(raw)) - except: - return None - - # get results - return feed.xpath("//div[@id='container']")[0] - - def populate(self, entries, browser, verbose=False): - #single entry - if len(entries) == 1 and not isinstance(entries[0], str): - try: - entry = entries[0].xpath("//div[@id='container']")[0] - entry = entry.find("div[@id='book-info']") - title = self.get_title(entry) - authors = self.get_authors(entry) - except Exception, e: - if verbose: - print 'Failed to get all details for an entry' - print e - return - self.append(self.fill_MI(entry, title, authors, verbose)) - else: - #multiple entries - for x in entries: - try: - entry = self.get_individual_metadata(browser, x, verbose) - entry = entry.find("div[@id='book-info']") - title = self.get_title(entry) - authors = self.get_authors(entry) - except Exception, e: - if verbose: - print 'Failed to get all details for an entry' - print e - continue - self.append(self.fill_MI(entry, title, authors, verbose)) - -class Covers(object): - - def __init__(self, isbn = None): - assert isbn is not None - self.urlimg = '' - self.isbn = isbn - self.isbnf = False - - def __call__(self, entry = None): - try: - self.urlimg = entry.xpath("//div[@id='book-picture']/a")[0].get('href') - except: - return self - isbno = entry.get_element_by_id('book-info').find("dl[@title='Informations sur le livre']") - for x in isbno.getiterator('dt'): - if x.text == 'ISBN' and check_isbn(x.getnext().text_content()): - self.isbnf = True - break - return self - - def check_cover(self): - return True if self.urlimg else False - - def get_cover(self, browser, timeout = 5.): - try: - cover, ext = browser.open_novisit(self.urlimg, timeout=timeout).read(), \ - self.urlimg.rpartition('.')[-1] - return cover, ext if ext else 'jpg' - except Exception, err: - if isinstance(getattr(err, 'args', [None])[0], socket.timeout): - raise NiceBooksError(_('Nicebooks timed out. Try again later.')) - if not len(self.urlimg): - if not self.isbnf: - raise ISBNNotFound(_('ISBN: %s not found.') % self.isbn) - raise NiceBooksError(_('An errror occured with Nicebooks cover fetcher')) - - -def search(title=None, author=None, publisher=None, isbn=None, - max_results=5, verbose=False, keywords=None): - br = browser() - entries = Query(title=title, author=author, isbn=isbn, publisher=publisher, - keywords=keywords, max_results=max_results)(br, verbose,timeout = 10.) - - if entries is None or len(entries) == 0: - return None - - #List of entry - ans = ResultList() - ans.populate(entries, br, verbose) - return ans - -def check_for_cover(isbn): - br = browser() - entry = Query(isbn=isbn, max_results=1)(br, False)[0] - return Covers(isbn)(entry).check_cover() - -def cover_from_isbn(isbn, timeout = 5.): - br = browser() - entry = Query(isbn=isbn, max_results=1)(br, False, timeout)[0] - return Covers(isbn)(entry).get_cover(br, timeout) - - -def option_parser(): - parser = OptionParser(textwrap.dedent(\ - _('''\ - %prog [options] - - Fetch book metadata from Nicebooks. You must specify one of title, author, - ISBN, publisher or keywords. Will fetch a maximum of 20 matches, - so you should make your query as specific as possible. - It can also get covers if the option is activated. - ''') - )) - parser.add_option('-t', '--title', help=_('Book title')) - parser.add_option('-a', '--author', help=_('Book author(s)')) - parser.add_option('-p', '--publisher', help=_('Book publisher')) - parser.add_option('-i', '--isbn', help=_('Book ISBN')) - parser.add_option('-k', '--keywords', help=_('Keywords')) - parser.add_option('-c', '--covers', default=0, - help=_('Covers: 1-Check/ 2-Download')) - parser.add_option('-p', '--coverspath', default='', - help=_('Covers files path')) - parser.add_option('-m', '--max-results', default=20, - help=_('Maximum number of results to fetch')) - parser.add_option('-v', '--verbose', default=0, action='count', - help=_('Be more verbose about errors')) - return parser - -def main(args=sys.argv): - import os - parser = option_parser() - opts, args = parser.parse_args(args) - try: - results = search(opts.title, opts.author, isbn=opts.isbn, publisher=opts.publisher, - keywords=opts.keywords, verbose=opts.verbose, max_results=opts.max_results) - except AssertionError: - report(True) - parser.print_help() - return 1 - if results is None or len(results) == 0: - print _('No result found for this search!') - return 0 - for result in results: - print unicode(result).encode(preferred_encoding, 'replace') - covact = int(opts.covers) - if covact == 1: - textcover = _('No cover found!') - if check_for_cover(result.isbn): - textcover = _('A cover was found for this book') - print textcover - elif covact == 2: - cover_data, ext = cover_from_isbn(result.isbn) - cpath = result.isbn - if len(opts.coverspath): - cpath = os.path.normpath(opts.coverspath + '/' + result.isbn) - oname = os.path.abspath(cpath+'.'+ext) - open(oname, 'wb').write(cover_data) - print _('Cover saved to file '), oname - print - -if __name__ == '__main__': - sys.exit(main()) diff --git a/src/calibre/ebooks/metadata/opf2.py b/src/calibre/ebooks/metadata/opf2.py index d34a563110..1d91236757 100644 --- a/src/calibre/ebooks/metadata/opf2.py +++ b/src/calibre/ebooks/metadata/opf2.py @@ -7,7 +7,7 @@ __docformat__ = 'restructuredtext en' lxml based OPF parser. ''' -import re, sys, unittest, functools, os, mimetypes, uuid, glob, cStringIO, json +import re, sys, unittest, functools, os, uuid, glob, cStringIO, json from urllib import unquote from urlparse import urlparse @@ -16,11 +16,12 @@ from lxml import etree from calibre.ebooks.chardet import xml_to_unicode from calibre.constants import __appname__, __version__, filesystem_encoding from calibre.ebooks.metadata.toc import TOC -from calibre.ebooks.metadata import string_to_authors, MetaInformation +from calibre.ebooks.metadata import string_to_authors, MetaInformation, check_isbn from calibre.ebooks.metadata.book.base import Metadata from calibre.utils.date import parse_date, isoformat from calibre.utils.localization import get_lang -from calibre import prints +from calibre import prints, guess_type +from calibre.utils.cleantext import clean_ascii_chars class Resource(object): # {{{ ''' @@ -41,7 +42,7 @@ class Resource(object): # {{{ self.path = None self.fragment = '' try: - self.mime_type = mimetypes.guess_type(href_or_path)[0] + self.mime_type = guess_type(href_or_path)[0] except: self.mime_type = None if self.mime_type is None: @@ -596,6 +597,9 @@ class OPF(object): # {{{ ans = MetaInformation(self) for n, v in self._user_metadata_.items(): ans.set_user_metadata(n, v) + + ans.set_identifiers(self.get_identifiers()) + return ans def write_user_metadata(self): @@ -855,6 +859,30 @@ class OPF(object): # {{{ return property(fget=fget, fset=fset) + def get_identifiers(self): + identifiers = {} + for x in self.XPath( + 'descendant::*[local-name() = "identifier" and text()]')( + self.metadata): + found_scheme = False + for attr, val in x.attrib.iteritems(): + if attr.endswith('scheme'): + typ = icu_lower(val) + val = etree.tostring(x, with_tail=False, encoding=unicode, + method='text').strip() + if val and typ not in ('calibre', 'uuid'): + identifiers[typ] = val + found_scheme = True + break + if not found_scheme: + val = etree.tostring(x, with_tail=False, encoding=unicode, + method='text').strip() + if val.lower().startswith('urn:isbn:'): + val = check_isbn(val.split(':')[-1]) + if val is not None: + identifiers['isbn'] = val + return identifiers + @dynamic_property def application_id(self): @@ -938,7 +966,9 @@ class OPF(object): # {{{ cover_id = covers[0].get('content') for item in self.itermanifest(): if item.get('id', None) == cover_id: - return item.get('href', None) + mt = item.get('media-type', '') + if 'xml' not in mt: + return item.get('href', None) @dynamic_property def cover(self): @@ -972,7 +1002,7 @@ class OPF(object): # {{{ for t in ('cover', 'other.ms-coverimage-standard', 'other.ms-coverimage'): for item in self.guide: if item.type.lower() == t: - self.create_manifest_item(item.href(), mimetypes.guess_type(path)[0]) + self.create_manifest_item(item.href(), guess_type(path)[0]) return property(fget=fget, fset=fset) @@ -1130,7 +1160,7 @@ class OPFCreator(Metadata): def DC_ELEM(tag, text, dc_attrs={}, opf_attrs={}): if text: - elem = getattr(DC, tag)(text, **dc_attrs) + elem = getattr(DC, tag)(clean_ascii_chars(text), **dc_attrs) else: elem = getattr(DC, tag)(**dc_attrs) for k, v in opf_attrs.items(): @@ -1166,8 +1196,8 @@ class OPFCreator(Metadata): a(DC_ELEM('description', self.comments)) if self.publisher: a(DC_ELEM('publisher', self.publisher)) - if self.isbn: - a(DC_ELEM('identifier', self.isbn, opf_attrs={'scheme':'ISBN'})) + for key, val in self.get_identifiers().iteritems(): + a(DC_ELEM('identifier', val, opf_attrs={'scheme':icu_upper(key)})) if self.rights: a(DC_ELEM('rights', self.rights)) if self.tags: @@ -1288,11 +1318,11 @@ def metadata_to_opf(mi, as_string=True): if hasattr(mi, 'category') and mi.category: factory(DC('type'), mi.category) if mi.comments: - factory(DC('description'), mi.comments) + factory(DC('description'), clean_ascii_chars(mi.comments)) if mi.publisher: factory(DC('publisher'), mi.publisher) - if mi.isbn: - factory(DC('identifier'), mi.isbn, scheme='ISBN') + for key, val in mi.get_identifiers().iteritems(): + factory(DC('identifier'), val, scheme=icu_upper(key)) if mi.rights: factory(DC('rights'), mi.rights) factory(DC('language'), mi.language if mi.language and mi.language.lower() @@ -1342,7 +1372,7 @@ def test_m2o(): mi.language = 'en' mi.comments = 'what a fun book\n\n' mi.publisher = 'publisher' - mi.isbn = 'boooo' + mi.set_identifiers({'isbn':'booo', 'dummy':'dummy'}) mi.tags = ['a', 'b'] mi.series = 's"c\'l&<>' mi.series_index = 3.34 @@ -1350,7 +1380,7 @@ def test_m2o(): mi.timestamp = nowf() mi.publication_type = 'ooooo' mi.rights = 'yes' - mi.cover = 'asd.jpg' + mi.cover = os.path.abspath('asd.jpg') opf = metadata_to_opf(mi) print opf newmi = MetaInformation(OPF(StringIO(opf))) @@ -1363,6 +1393,9 @@ def test_m2o(): o, n = getattr(mi, attr), getattr(newmi, attr) if o != n and o.strip() != n.strip(): print 'FAILED:', attr, getattr(mi, attr), '!=', getattr(newmi, attr) + if mi.get_identifiers() != newmi.get_identifiers(): + print 'FAILED:', 'identifiers', mi.get_identifiers(), + print '!=', newmi.get_identifiers() class OPFTest(unittest.TestCase): @@ -1378,6 +1411,7 @@ class OPFTest(unittest.TestCase): <creator opf:role="aut">Next</creator> <dc:subject>One</dc:subject><dc:subject>Two</dc:subject> <dc:identifier scheme="ISBN">123456789</dc:identifier> + <dc:identifier scheme="dummy">dummy</dc:identifier> <meta name="calibre:series" content="A one book series" /> <meta name="calibre:rating" content="4"/> <meta name="calibre:publication_type" content="test"/> @@ -1405,6 +1439,8 @@ class OPFTest(unittest.TestCase): self.assertEqual(opf.rating, 4) self.assertEqual(opf.publication_type, 'test') self.assertEqual(list(opf.itermanifest())[0].get('href'), 'a ~ b') + self.assertEqual(opf.get_identifiers(), {'isbn':'123456789', + 'dummy':'dummy'}) def testWriting(self): for test in [('title', 'New & Title'), ('authors', ['One', 'Two']), @@ -1461,5 +1497,5 @@ def test_user_metadata(): if __name__ == '__main__': #test_user_metadata() - #test_m2o() + test_m2o() test() diff --git a/src/calibre/ebooks/metadata/pdb.py b/src/calibre/ebooks/metadata/pdb.py index ddf2b0c818..d01bb0ecdb 100644 --- a/src/calibre/ebooks/metadata/pdb.py +++ b/src/calibre/ebooks/metadata/pdb.py @@ -1,7 +1,7 @@ # -*- coding: utf-8 -*- ''' -Read meta information from eReader pdb files. +Read meta information from pdb files. ''' __license__ = 'GPL v3' @@ -13,10 +13,12 @@ import re from calibre.ebooks.metadata import MetaInformation from calibre.ebooks.pdb.header import PdbHeaderReader from calibre.ebooks.metadata.ereader import get_metadata as get_eReader +from calibre.ebooks.metadata.plucker import get_metadata as get_plucker MREADER = { 'PNPdPPrs' : get_eReader, 'PNRdPPrs' : get_eReader, + 'DataPlkr' : get_plucker, } from calibre.ebooks.metadata.ereader import set_metadata as set_eReader diff --git a/src/calibre/ebooks/metadata/plucker.py b/src/calibre/ebooks/metadata/plucker.py new file mode 100644 index 0000000000..fabaa080d2 --- /dev/null +++ b/src/calibre/ebooks/metadata/plucker.py @@ -0,0 +1,73 @@ +# -*- coding: utf-8 -*- + +from __future__ import (unicode_literals, division, absolute_import, print_function) + +''' +Read meta information from Plucker pdb files. +''' + +__license__ = 'GPL v3' +__copyright__ = '2009, John Schember <john@nachtimwald.com>' +__docformat__ = 'restructuredtext en' + +import struct +from datetime import datetime + +from calibre.ebooks.metadata import MetaInformation +from calibre.ebooks.pdb.header import PdbHeaderReader +from calibre.ebooks.pdb.plucker.reader import SectionHeader, DATATYPE_METADATA, \ + MIBNUM_TO_NAME + +def get_metadata(stream, extract_cover=True): + ''' + Return metadata as a L{MetaInfo} object + ''' + mi = MetaInformation(_('Unknown'), [_('Unknown')]) + stream.seek(0) + + pheader = PdbHeaderReader(stream) + section_data = None + for i in range(1, pheader.num_sections): + raw_data = pheader.section_data(i) + section_header = SectionHeader(raw_data) + if section_header.type == DATATYPE_METADATA: + section_data = raw_data[8:] + break + + if not section_data: + return mi + + default_encoding = 'latin-1' + record_count, = struct.unpack('>H', section_data[0:2]) + adv = 0 + title = None + author = None + pubdate = 0 + for i in xrange(record_count): + type, = struct.unpack('>H', section_data[2+adv:4+adv]) + length, = struct.unpack('>H', section_data[4+adv:6+adv]) + + # CharSet + if type == 1: + val, = struct.unpack('>H', section_data[6+adv:8+adv]) + default_encoding = MIBNUM_TO_NAME.get(val, 'latin-1') + # Author + elif type == 4: + author = section_data[6+adv+(2*length)] + # Title + elif type == 5: + title = section_data[6+adv+(2*length)] + # Publication Date + elif type == 6: + pubdate, = struct.unpack('>I', section_data[6+adv:6+adv+4]) + + adv += 2*length + + if title: + mi.title = title.replace('\0', '').decode(default_encoding, 'replace') + if author: + author = author.replace('\0', '').decode(default_encoding, 'replace') + mi.author = author.split(',') + mi.pubdate = datetime.fromtimestamp(pubdate) + + return mi diff --git a/src/calibre/ebooks/metadata/rb.py b/src/calibre/ebooks/metadata/rb.py index 1f13ce1d9d..c8ab657146 100644 --- a/src/calibre/ebooks/metadata/rb.py +++ b/src/calibre/ebooks/metadata/rb.py @@ -43,7 +43,7 @@ def get_metadata(stream): elif key.strip() == 'AUTHOR': mi.author = value mi.authors = string_to_authors(value) - except Exception, err: + except Exception as err: msg = u'Couldn\'t read metadata from rb: %s with error %s'%(mi.title, unicode(err)) print >>sys.stderr, msg.encode('utf8') raise diff --git a/src/calibre/ebooks/metadata/sources/amazon.py b/src/calibre/ebooks/metadata/sources/amazon.py index 88ac1213c5..7da37ce5af 100644 --- a/src/calibre/ebooks/metadata/sources/amazon.py +++ b/src/calibre/ebooks/metadata/sources/amazon.py @@ -7,16 +7,737 @@ __license__ = 'GPL v3' __copyright__ = '2011, Kovid Goyal <kovid@kovidgoyal.net>' __docformat__ = 'restructuredtext en' +import socket, time, re +from urllib import urlencode +from threading import Thread +from Queue import Queue, Empty -from calibre.ebooks.metadata.sources.base import Source +from lxml.html import soupparser, tostring + +from calibre import as_unicode +from calibre.ebooks.metadata import check_isbn +from calibre.ebooks.metadata.sources.base import Source, Option +from calibre.utils.cleantext import clean_ascii_chars +from calibre.ebooks.chardet import xml_to_unicode +from calibre.ebooks.metadata.book.base import Metadata +from calibre.library.comments import sanitize_comments_html +from calibre.utils.date import parse_date + +class Worker(Thread): # Get details {{{ + + ''' + Get book details from amazons book page in a separate thread + ''' + + def __init__(self, url, result_queue, browser, log, relevance, domain, plugin, timeout=20): + Thread.__init__(self) + self.daemon = True + self.url, self.result_queue = url, result_queue + self.log, self.timeout = log, timeout + self.relevance, self.plugin = relevance, plugin + self.browser = browser.clone_browser() + self.cover_url = self.amazon_id = self.isbn = None + self.domain = domain + + months = { + 'de': { + 1 : ['jän'], + 3 : ['märz'], + 5 : ['mai'], + 6 : ['juni'], + 7 : ['juli'], + 10: ['okt'], + 12: ['dez'] + }, + 'it': { + 1: ['enn'], + 2: ['febbr'], + 5: ['magg'], + 6: ['giugno'], + 7: ['luglio'], + 8: ['ag'], + 9: ['sett'], + 10: ['ott'], + 12: ['dic'], + }, + 'fr': { + 1: ['janv'], + 2: ['févr'], + 3: ['mars'], + 4: ['avril'], + 5: ['mai'], + 6: ['juin'], + 7: ['juil'], + 8: ['août'], + 9: ['sept'], + 12: ['déc'], + }, + + } + + self.english_months = [None, 'Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', + 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec'] + self.months = months.get(self.domain, {}) + + self.pd_xpath = ''' + //h2[text()="Product Details" or \ + text()="Produktinformation" or \ + text()="Dettagli prodotto" or \ + text()="Product details" or \ + text()="Détails sur le produit"]/../div[@class="content"] + ''' + self.publisher_xpath = ''' + descendant::*[starts-with(text(), "Publisher:") or \ + starts-with(text(), "Verlag:") or \ + starts-with(text(), "Editore:") or \ + starts-with(text(), "Editeur")] + ''' + self.language_xpath = ''' + descendant::*[ + starts-with(text(), "Language:") \ + or text() = "Language" \ + or text() = "Sprache:" \ + or text() = "Lingua:" \ + or starts-with(text(), "Langue") \ + ] + ''' + self.ratings_pat = re.compile( + r'([0-9.]+) (out of|von|su|étoiles sur) (\d+)( (stars|Sternen|stelle)){0,1}') + + lm = { + 'en': ('English', 'Englisch'), + 'fr': ('French', 'Français'), + 'it': ('Italian', 'Italiano'), + 'de': ('German', 'Deutsch'), + } + self.lang_map = {} + for code, names in lm.iteritems(): + for name in names: + self.lang_map[name] = code + + def delocalize_datestr(self, raw): + if not self.months: + return raw + ans = raw.lower() + for i, vals in self.months.iteritems(): + for x in vals: + ans = ans.replace(x, self.english_months[i]) + return ans + + def run(self): + try: + self.get_details() + except: + self.log.exception('get_details failed for url: %r'%self.url) + + def get_details(self): + try: + raw = self.browser.open_novisit(self.url, timeout=self.timeout).read().strip() + except Exception as e: + if callable(getattr(e, 'getcode', None)) and \ + e.getcode() == 404: + self.log.error('URL malformed: %r'%self.url) + return + attr = getattr(e, 'args', [None]) + attr = attr if attr else [None] + if isinstance(attr[0], socket.timeout): + msg = 'Amazon timed out. Try again later.' + self.log.error(msg) + else: + msg = 'Failed to make details query: %r'%self.url + self.log.exception(msg) + return + + raw = xml_to_unicode(raw, strip_encoding_pats=True, + resolve_entities=True)[0] + #open('/t/t.html', 'wb').write(raw) + + if '<title>404 - ' in raw: + self.log.error('URL malformed: %r'%self.url) + return + + try: + root = soupparser.fromstring(clean_ascii_chars(raw)) + except: + msg = 'Failed to parse amazon details page: %r'%self.url + self.log.exception(msg) + return + + errmsg = root.xpath('//*[@id="errorMessage"]') + if errmsg: + msg = 'Failed to parse amazon details page: %r'%self.url + msg += tostring(errmsg, method='text', encoding=unicode).strip() + self.log.error(msg) + return + + self.parse_details(root) + + def parse_details(self, root): + try: + asin = self.parse_asin(root) + except: + self.log.exception('Error parsing asin for url: %r'%self.url) + asin = None + + try: + title = self.parse_title(root) + except: + self.log.exception('Error parsing title for url: %r'%self.url) + title = None + + try: + authors = self.parse_authors(root) + except: + self.log.exception('Error parsing authors for url: %r'%self.url) + authors = [] + + + if not title or not authors or not asin: + self.log.error('Could not find title/authors/asin for %r'%self.url) + self.log.error('ASIN: %r Title: %r Authors: %r'%(asin, title, + authors)) + return + + mi = Metadata(title, authors) + idtype = 'amazon' if self.domain == 'com' else 'amazon_'+self.domain + mi.set_identifier(idtype, asin) + self.amazon_id = asin + + try: + mi.rating = self.parse_rating(root) + except: + self.log.exception('Error parsing ratings for url: %r'%self.url) + + try: + mi.comments = self.parse_comments(root) + except: + self.log.exception('Error parsing comments for url: %r'%self.url) + + try: + self.cover_url = self.parse_cover(root) + except: + self.log.exception('Error parsing cover for url: %r'%self.url) + mi.has_cover = bool(self.cover_url) + + pd = root.xpath(self.pd_xpath) + if pd: + pd = pd[0] + + try: + isbn = self.parse_isbn(pd) + if isbn: + self.isbn = mi.isbn = isbn + except: + self.log.exception('Error parsing ISBN for url: %r'%self.url) + + try: + mi.publisher = self.parse_publisher(pd) + except: + self.log.exception('Error parsing publisher for url: %r'%self.url) + + try: + mi.pubdate = self.parse_pubdate(pd) + except: + self.log.exception('Error parsing publish date for url: %r'%self.url) + + try: + lang = self.parse_language(pd) + if lang: + mi.language = lang + except: + self.log.exception('Error parsing language for url: %r'%self.url) + + else: + self.log.warning('Failed to find product description for url: %r'%self.url) + + mi.source_relevance = self.relevance + + if self.amazon_id: + if self.isbn: + self.plugin.cache_isbn_to_identifier(self.isbn, self.amazon_id) + if self.cover_url: + self.plugin.cache_identifier_to_cover_url(self.amazon_id, + self.cover_url) + + self.plugin.clean_downloaded_metadata(mi) + + self.result_queue.put(mi) + + def parse_asin(self, root): + link = root.xpath('//link[@rel="canonical" and @href]') + for l in link: + return l.get('href').rpartition('/')[-1] + + def parse_title(self, root): + tdiv = root.xpath('//h1[@class="parseasinTitle"]')[0] + actual_title = tdiv.xpath('descendant::*[@id="btAsinTitle"]') + if actual_title: + title = tostring(actual_title[0], encoding=unicode, + method='text').strip() + else: + title = tostring(tdiv, encoding=unicode, method='text').strip() + return re.sub(r'[(\[].*[)\]]', '', title).strip() + + def parse_authors(self, root): + x = '//h1[@class="parseasinTitle"]/following-sibling::span/*[(name()="a" and @href) or (name()="span" and @class="contributorNameTrigger")]' + aname = root.xpath(x) + if not aname: + aname = root.xpath(''' + //h1[@class="parseasinTitle"]/following-sibling::*[(name()="a" and @href) or (name()="span" and @class="contributorNameTrigger")] + ''') + for x in aname: + x.tail = '' + authors = [tostring(x, encoding=unicode, method='text').strip() for x + in aname] + authors = [a for a in authors if a] + return authors + + def parse_rating(self, root): + ratings = root.xpath('//div[@class="jumpBar"]/descendant::span[@class="asinReviewsSummary"]') + if not ratings: + ratings = root.xpath('//div[@class="buying"]/descendant::span[@class="asinReviewsSummary"]') + if not ratings: + ratings = root.xpath('//span[@class="crAvgStars"]/descendant::span[@class="asinReviewsSummary"]') + if ratings: + for elem in ratings[0].xpath('descendant::*[@title]'): + t = elem.get('title').strip() + m = self.ratings_pat.match(t) + if m is not None: + return float(m.group(1))/float(m.group(3)) * 5 + + def parse_comments(self, root): + desc = root.xpath('//div[@id="productDescription"]/*[@class="content"]') + if desc: + desc = desc[0] + for c in desc.xpath('descendant::*[@class="seeAll" or' + ' @class="emptyClear"]'): + c.getparent().remove(c) + for a in desc.xpath('descendant::a[@href]'): + del a.attrib['href'] + a.tag = 'span' + desc = tostring(desc, method='html', encoding=unicode).strip() + + # Encoding bug in Amazon data U+fffd (replacement char) + # in some examples it is present in place of ' + desc = desc.replace('\ufffd', "'") + # remove all attributes from tags + desc = re.sub(r'<([a-zA-Z0-9]+)\s[^>]+>', r'<\1>', desc) + # Collapse whitespace + #desc = re.sub('\n+', '\n', desc) + #desc = re.sub(' +', ' ', desc) + # Remove the notice about text referring to out of print editions + desc = re.sub(r'(?s)<em>--This text ref.*?</em>', '', desc) + # Remove comments + desc = re.sub(r'(?s)<!--.*?-->', '', desc) + return sanitize_comments_html(desc) + + def parse_cover(self, root): + imgs = root.xpath('//img[@id="prodImage" and @src]') + if imgs: + src = imgs[0].get('src') + if '/no-image-avail' not in src: + parts = src.split('/') + if len(parts) > 3: + bn = parts[-1] + sparts = bn.split('_') + if len(sparts) > 2: + bn = sparts[0] + sparts[-1] + return ('/'.join(parts[:-1]))+'/'+bn + + def parse_isbn(self, pd): + items = pd.xpath( + 'descendant::*[starts-with(text(), "ISBN")]') + if not items: + items = pd.xpath( + 'descendant::b[contains(text(), "ISBN:")]') + for x in reversed(items): + if x.tail: + ans = check_isbn(x.tail.strip()) + if ans: + return ans + + def parse_publisher(self, pd): + for x in reversed(pd.xpath(self.publisher_xpath)): + if x.tail: + ans = x.tail.partition(';')[0] + return ans.partition('(')[0].strip() + + def parse_pubdate(self, pd): + for x in reversed(pd.xpath(self.publisher_xpath)): + if x.tail: + ans = x.tail + date = ans.partition('(')[-1].replace(')', '').strip() + date = self.delocalize_datestr(date) + return parse_date(date, assume_utc=True) + + def parse_language(self, pd): + for x in reversed(pd.xpath(self.language_xpath)): + if x.tail: + ans = x.tail.strip() + ans = self.lang_map.get(ans, None) + if ans: + return ans +# }}} class Amazon(Source): - name = 'Amazon' - description = _('Downloads metadata from Amazon') + name = 'Amazon.com' + description = _('Downloads metadata and covers from Amazon') capabilities = frozenset(['identify', 'cover']) - touched_fields = frozenset(['title', 'authors', 'isbn', 'pubdate', - 'comments', 'cover_data']) + touched_fields = frozenset(['title', 'authors', 'identifier:amazon', + 'identifier:isbn', 'rating', 'comments', 'publisher', 'pubdate', + 'language']) + has_html_comments = True + supports_gzip_transfer_encoding = True + + AMAZON_DOMAINS = { + 'com': _('US'), + 'fr' : _('France'), + 'de' : _('Germany'), + 'uk' : _('UK'), + 'it' : _('Italy'), + } + + options = ( + Option('domain', 'choices', 'com', _('Amazon website to use:'), + _('Metadata from Amazon will be fetched using this ' + 'country\'s Amazon website.'), choices=AMAZON_DOMAINS), + ) + + def get_domain_and_asin(self, identifiers): + for key, val in identifiers.iteritems(): + key = key.lower() + if key in ('amazon', 'asin'): + return 'com', val + if key.startswith('amazon_'): + domain = key.split('_')[-1] + if domain and domain in self.AMAZON_DOMAINS: + return domain, val + return None, None + + def get_book_url(self, identifiers): # {{{ + domain, asin = self.get_domain_and_asin(identifiers) + if domain and asin: + url = None + if domain == 'com': + url = 'http://amzn.com/'+asin + elif domain == 'uk': + url = 'http://www.amazon.co.uk/dp/'+asin + else: + url = 'http://www.amazon.%s/dp/%s'%(domain, asin) + if url: + idtype = 'amazon' if self.domain == 'com' else 'amazon_'+self.domain + return (idtype, asin, url) + # }}} + + @property + def domain(self): + domain = self.prefs['domain'] + if domain not in self.AMAZON_DOMAINS: + domain = 'com' + + return domain + + def create_query(self, log, title=None, authors=None, identifiers={}, # {{{ + domain=None): + if domain is None: + domain = self.domain + + idomain, asin = self.get_domain_and_asin(identifiers) + if idomain is not None: + domain = idomain + + # See the amazon detailed search page to get all options + q = { 'search-alias' : 'aps', + 'unfiltered' : '1', + } + + if domain == 'com': + q['sort'] = 'relevanceexprank' + else: + q['sort'] = 'relevancerank' + + isbn = check_isbn(identifiers.get('isbn', None)) + + if asin is not None: + q['field-keywords'] = asin + elif isbn is not None: + q['field-isbn'] = isbn + else: + # Only return book results + q['search-alias'] = 'stripbooks' + if title: + title_tokens = list(self.get_title_tokens(title)) + if title_tokens: + q['field-title'] = ' '.join(title_tokens) + if authors: + author_tokens = self.get_author_tokens(authors, + only_first_author=True) + if author_tokens: + q['field-author'] = ' '.join(author_tokens) + + if not ('field-keywords' in q or 'field-isbn' in q or + ('field-title' in q)): + # Insufficient metadata to make an identify query + return None, None + + latin1q = dict([(x.encode('latin1', 'ignore'), y.encode('latin1', + 'ignore')) for x, y in + q.iteritems()]) + udomain = domain + if domain == 'uk': + udomain = 'co.uk' + url = 'http://www.amazon.%s/s/?'%udomain + urlencode(latin1q) + return url, domain + + # }}} + + def get_cached_cover_url(self, identifiers): # {{{ + url = None + domain, asin = self.get_domain_and_asin(identifiers) + if asin is None: + isbn = identifiers.get('isbn', None) + if isbn is not None: + asin = self.cached_isbn_to_identifier(isbn) + if asin is not None: + url = self.cached_identifier_to_cover_url(asin) + + return url + # }}} + + def identify(self, log, result_queue, abort, title=None, authors=None, # {{{ + identifiers={}, timeout=30): + ''' + Note this method will retry without identifiers automatically if no + match is found with identifiers. + ''' + query, domain = self.create_query(log, title=title, authors=authors, + identifiers=identifiers) + if query is None: + log.error('Insufficient metadata to construct query') + return + br = self.browser + try: + raw = br.open_novisit(query, timeout=timeout).read().strip() + except Exception as e: + if callable(getattr(e, 'getcode', None)) and \ + e.getcode() == 404: + log.error('Query malformed: %r'%query) + return + attr = getattr(e, 'args', [None]) + attr = attr if attr else [None] + if isinstance(attr[0], socket.timeout): + msg = _('Amazon timed out. Try again later.') + log.error(msg) + else: + msg = 'Failed to make identify query: %r'%query + log.exception(msg) + return as_unicode(msg) + raw = xml_to_unicode(raw, strip_encoding_pats=True, + resolve_entities=True)[0] + + matches = [] + found = '<title>404 - ' not in raw + + if found: + try: + root = soupparser.fromstring(clean_ascii_chars(raw)) + except: + msg = 'Failed to parse amazon page for query: %r'%query + log.exception(msg) + return msg + + errmsg = root.xpath('//*[@id="errorMessage"]') + if errmsg: + msg = tostring(errmsg, method='text', encoding=unicode).strip() + log.error(msg) + # The error is almost always a not found error + found = False + + if found: + for div in root.xpath(r'//div[starts-with(@id, "result_")]'): + for a in div.xpath(r'descendant::a[@class="title" and @href]'): + title = tostring(a, method='text', encoding=unicode).lower() + if 'bulk pack' not in title: + matches.append(a.get('href')) + break + if not matches: + # This can happen for some user agents that Amazon thinks are + # mobile/less capable + log('Trying alternate results page markup') + for td in root.xpath( + r'//div[@id="Results"]/descendant::td[starts-with(@id, "search:Td:")]'): + for a in td.xpath(r'descendant::td[@class="dataColumn"]/descendant::a[@href]/span[@class="srTitle"]/..'): + title = tostring(a, method='text', encoding=unicode).lower() + if ('bulk pack' not in title and '[audiobook]' not in + title and '[audio cd]' not in title): + matches.append(a.get('href')) + break + + + # Keep only the top 5 matches as the matches are sorted by relevance by + # Amazon so lower matches are not likely to be very relevant + matches = matches[:5] + + if abort.is_set(): + return + + if not matches: + if identifiers and title and authors: + log('No matches found with identifiers, retrying using only' + ' title and authors') + return self.identify(log, result_queue, abort, title=title, + authors=authors, timeout=timeout) + log.error('No matches found with query: %r'%query) + return + + workers = [Worker(url, result_queue, br, log, i, domain, self) for i, url in + enumerate(matches)] + + for w in workers: + w.start() + # Don't send all requests at the same time + time.sleep(0.1) + + while not abort.is_set(): + a_worker_is_alive = False + for w in workers: + w.join(0.2) + if abort.is_set(): + break + if w.is_alive(): + a_worker_is_alive = True + if not a_worker_is_alive: + break + + return None + # }}} + + def download_cover(self, log, result_queue, abort, # {{{ + title=None, authors=None, identifiers={}, timeout=30): + cached_url = self.get_cached_cover_url(identifiers) + if cached_url is None: + log.info('No cached cover found, running identify') + rq = Queue() + self.identify(log, rq, abort, title=title, authors=authors, + identifiers=identifiers) + if abort.is_set(): + return + results = [] + while True: + try: + results.append(rq.get_nowait()) + except Empty: + break + results.sort(key=self.identify_results_keygen( + title=title, authors=authors, identifiers=identifiers)) + for mi in results: + cached_url = self.get_cached_cover_url(mi.identifiers) + if cached_url is not None: + break + if cached_url is None: + log.info('No cover found') + return + + if abort.is_set(): + return + br = self.browser + log('Downloading cover from:', cached_url) + try: + cdata = br.open_novisit(cached_url, timeout=timeout).read() + result_queue.put((self, cdata)) + except: + log.exception('Failed to download cover from:', cached_url) + # }}} + +if __name__ == '__main__': # tests {{{ + # To run these test use: calibre-debug -e + # src/calibre/ebooks/metadata/sources/amazon.py + from calibre.ebooks.metadata.sources.test import (test_identify_plugin, + title_test, authors_test) + com_tests = [ # {{{ + + ( # Description has links + {'identifiers':{'isbn': '9780671578275'}}, + [title_test('A Civil Campaign: A Comedy of Biology and Manners', + exact=True), authors_test(['Lois McMaster Bujold']) + ] + + ), + + ( # An e-book ISBN not on Amazon, the title/author search matches + # the Kindle edition, which has different markup for ratings and + # isbn + {'identifiers':{'isbn': '9780307459671'}, + 'title':'Invisible Gorilla', 'authors':['Christopher Chabris']}, + [title_test('The Invisible Gorilla: And Other Ways Our Intuitions Deceive Us', + exact=True), authors_test(['Christopher Chabris', 'Daniel Simons'])] + + ), + + ( # This isbn not on amazon + {'identifiers':{'isbn': '8324616489'}, 'title':'Learning Python', + 'authors':['Lutz']}, + [title_test('Learning Python, 3rd Edition', + exact=True), authors_test(['Mark Lutz']) + ] + + ), + + ( # Sophisticated comment formatting + {'identifiers':{'isbn': '9781416580829'}}, + [title_test('Angels & Demons - Movie Tie-In: A Novel', + exact=True), authors_test(['Dan Brown'])] + ), + + ( # No specific problems + {'identifiers':{'isbn': '0743273567'}}, + [title_test('The great gatsby', exact=True), + authors_test(['F. Scott Fitzgerald'])] + ), + + ( # A newer book + {'identifiers':{'isbn': '9780316044981'}}, + [title_test('The Heroes', exact=True), + authors_test(['Joe Abercrombie'])] + + ), + + ] # }}} + + de_tests = [ # {{{ + ( + {'identifiers':{'isbn': '3548283519'}}, + [title_test('Wer Wind sät', + exact=True), authors_test(['Nele Neuhaus']) + ] + + ), + ] # }}} + + it_tests = [ # {{{ + ( + {'identifiers':{'isbn': '8838922195'}}, + [title_test('La briscola in cinque', + exact=True), authors_test(['Marco Malvaldi']) + ] + + ), + ] # }}} + + fr_tests = [ # {{{ + ( + {'identifiers':{'isbn': '2221116798'}}, + [title_test('L\'étrange voyage de Monsieur Daldry', + exact=True), authors_test(['Marc Levy']) + ] + + ), + ] # }}} + + test_identify_plugin(Amazon.name, com_tests) +# }}} + diff --git a/src/calibre/ebooks/metadata/sources/base.py b/src/calibre/ebooks/metadata/sources/base.py index 54d7d49d6d..0c318727e0 100644 --- a/src/calibre/ebooks/metadata/sources/base.py +++ b/src/calibre/ebooks/metadata/sources/base.py @@ -8,15 +8,152 @@ __copyright__ = '2011, Kovid Goyal <kovid@kovidgoyal.net>' __docformat__ = 'restructuredtext en' import re, threading +from future_builtins import map +from calibre import browser, random_user_agent from calibre.customize import Plugin from calibre.utils.logging import ThreadSafeLog, FileStream +from calibre.utils.config import JSONConfig +from calibre.utils.titlecase import titlecase +from calibre.utils.icu import capitalize, lower +from calibre.ebooks.metadata import check_isbn + +msprefs = JSONConfig('metadata_sources/global.json') +msprefs.defaults['txt_comments'] = False +msprefs.defaults['ignore_fields'] = [] +msprefs.defaults['max_tags'] = 20 +msprefs.defaults['wait_after_first_identify_result'] = 30 # seconds +msprefs.defaults['wait_after_first_cover_result'] = 60 # seconds +msprefs.defaults['swap_author_names'] = False +msprefs.defaults['fewer_tags'] = True + +# Google covers are often poor quality (scans/errors) but they have high +# resolution, so they trump covers from better sources. So make sure they +# are only used if no other covers are found. +msprefs.defaults['cover_priorities'] = {'Google':2} def create_log(ostream=None): log = ThreadSafeLog(level=ThreadSafeLog.DEBUG) log.outputs = [FileStream(ostream)] return log +# Comparing Metadata objects for relevance {{{ +words = ("the", "a", "an", "of", "and") +prefix_pat = re.compile(r'^(%s)\s+'%("|".join(words))) +trailing_paren_pat = re.compile(r'\(.*\)$') +whitespace_pat = re.compile(r'\s+') + +def cleanup_title(s): + if not s: + s = _('Unknown') + s = s.strip().lower() + s = prefix_pat.sub(' ', s) + s = trailing_paren_pat.sub('', s) + s = whitespace_pat.sub(' ', s) + return s.strip() + +class InternalMetadataCompareKeyGen(object): + + ''' + Generate a sort key for comparison of the relevance of Metadata objects, + given a search query. This is used only to compare results from the same + metadata source, not across different sources. + + The sort key ensures that an ascending order sort is a sort by order of + decreasing relevance. + + The algorithm is: + + * Prefer results that have the same ISBN as specified in the query + * Prefer results with a cached cover URL + * Prefer results with all available fields filled in + * Prefer results that are an exact title match to the query + * Prefer results with longer comments (greater than 10% longer) + * Use the relevance of the result as reported by the metadata source's search + engine + ''' + + def __init__(self, mi, source_plugin, title, authors, identifiers): + isbn = 1 if mi.isbn and mi.isbn == identifiers.get('isbn', None) else 2 + + all_fields = 1 if source_plugin.test_fields(mi) is None else 2 + + exact_title = 1 if title and \ + cleanup_title(title) == cleanup_title(mi.title) else 2 + + has_cover = 2 if (not source_plugin.cached_cover_url_is_reliable or + source_plugin.get_cached_cover_url(mi.identifiers) is None) else 1 + + self.base = (isbn, has_cover, all_fields, exact_title) + self.comments_len = len(mi.comments.strip() if mi.comments else '') + self.extra = (getattr(mi, 'source_relevance', 0), ) + + def __cmp__(self, other): + result = cmp(self.base, other.base) + if result == 0: + # Now prefer results with the longer comments, within 10% + cx, cy = self.comments_len, other.comments_len + t = (cx + cy) / 20 + delta = cy - cx + if abs(delta) > t: + result = delta + else: + result = cmp(self.extra, other.extra) + return result + +# }}} + +def get_cached_cover_urls(mi): + from calibre.customize.ui import metadata_plugins + plugins = list(metadata_plugins(['identify'])) + for p in plugins: + url = p.get_cached_cover_url(mi.identifiers) + if url: + yield (p, url) + +def cap_author_token(token): + lt = lower(token) + if lt in ('von', 'de', 'el', 'van', 'le'): + return lt + if re.match(r'([a-z]\.){2,}$', lt) is not None: + # Normalize tokens of the form J.K. to J. K. + parts = token.split('.') + return '. '.join(map(capitalize, parts)).strip() + return capitalize(token) + +def fixauthors(authors): + if not authors: + return authors + ans = [] + for x in authors: + ans.append(' '.join(map(cap_author_token, x.split()))) + return ans + +def fixcase(x): + if x: + x = titlecase(x) + return x + +class Option(object): + __slots__ = ['type', 'default', 'label', 'desc', 'name', 'choices'] + + def __init__(self, name, type_, default, label, desc, choices=None): + ''' + :param name: The name of this option. Must be a valid python identifier + :param type_: The type of this option, one of ('number', 'string', + 'bool', 'choices') + :param default: The default value for this option + :param label: A short (few words) description of this option + :param desc: A longer description of this option + :param choices: A dict of possible values, used only if type='choices'. + dict is of the form {key:human readable label, ...} + ''' + self.name, self.type, self.default, self.label, self.desc = (name, + type_, default, label, desc) + if choices and not isinstance(choices, dict): + choices = dict([(x, x) for x in choices]) + self.choices = choices + class Source(Plugin): type = _('Metadata source') @@ -24,18 +161,96 @@ class Source(Plugin): supported_platforms = ['windows', 'osx', 'linux'] - result_of_identify_is_complete = True - + #: Set of capabilities supported by this plugin. + #: Useful capabilities are: 'identify', 'cover' capabilities = frozenset() + #: List of metadata fields that can potentially be download by this plugin + #: during the identify phase touched_fields = frozenset() + #: Set this to True if your plugin return HTML formatted comments + has_html_comments = False + + #: Setting this to True means that the browser object will add + #: Accept-Encoding: gzip to all requests. This can speedup downloads + #: but make sure that the source actually supports gzip transfer encoding + #: correctly first + supports_gzip_transfer_encoding = False + + #: Cached cover URLs can sometimes be unreliable (i.e. the download could + #: fail or the returned image could be bogus. If that is often the case + #: with this source set to False + cached_cover_url_is_reliable = True + + #: A list of :class:`Option` objects. They will be used to automatically + #: construct the configuration widget for this plugin + options = () + + #: A string that is displayed at the top of the config widget for this + #: plugin + config_help_message = None + + def __init__(self, *args, **kwargs): Plugin.__init__(self, *args, **kwargs) self._isbn_to_identifier_cache = {} + self._identifier_to_cover_url_cache = {} self.cache_lock = threading.RLock() + self._config_obj = None + self._browser = None + self.prefs.defaults['ignore_fields'] = [] + for opt in self.options: + self.prefs.defaults[opt.name] = opt.default - # Utility functions {{{ + # Configuration {{{ + + def is_configured(self): + ''' + Return False if your plugin needs to be configured before it can be + used. For example, it might need a username/password/API key. + ''' + return True + + def is_customizable(self): + return True + + def customization_help(self): + return 'This plugin can only be customized using the GUI' + + def config_widget(self): + from calibre.gui2.metadata.config import ConfigWidget + return ConfigWidget(self) + + def save_settings(self, config_widget): + config_widget.commit() + + @property + def prefs(self): + if self._config_obj is None: + self._config_obj = JSONConfig('metadata_sources/%s.json'%self.name) + return self._config_obj + # }}} + + # Browser {{{ + + @property + def browser(self): + if self._browser is None: + self._browser = browser(user_agent=random_user_agent()) + if self.supports_gzip_transfer_encoding: + self._browser.set_handle_gzip(True) + return self._browser.clone_browser() + + # }}} + + # Caching {{{ + + def get_related_isbns(self, id_): + with self.cache_lock: + for isbn, q in self._isbn_to_identifier_cache.iteritems(): + if q == id_: + yield isbn def cache_isbn_to_identifier(self, isbn, identifier): with self.cache_lock: @@ -45,6 +260,18 @@ class Source(Plugin): with self.cache_lock: return self._isbn_to_identifier_cache.get(isbn, None) + def cache_identifier_to_cover_url(self, id_, url): + with self.cache_lock: + self._identifier_to_cover_url_cache[id_] = url + + def cached_identifier_to_cover_url(self, id_): + with self.cache_lock: + return self._identifier_to_cover_url_cache.get(id_, None) + + # }}} + + # Utility functions {{{ + def get_author_tokens(self, authors, only_first_author=True): ''' Take a list of authors and return a list of tokens useful for an @@ -55,31 +282,59 @@ class Source(Plugin): if authors: # Leave ' in there for Irish names - pat = re.compile(r'[-,:;+!@#$%^&*(){}.`~"\s\[\]/]') + remove_pat = re.compile(r'[,!@#$%^&*(){}`~"\s\[\]/]') + replace_pat = re.compile(r'[-+.:;]') if only_first_author: authors = authors[:1] for au in authors: + au = replace_pat.sub(' ', au) parts = au.split() if ',' in au: # au probably in ln, fn form parts = parts[1:] + parts[:1] for tok in parts: - tok = pat.sub('', tok).strip() - yield tok + tok = remove_pat.sub('', tok).strip() + if len(tok) > 2 and tok.lower() not in ('von', 'van', + _('Unknown').lower()): + yield tok - - def get_title_tokens(self, title): + def get_title_tokens(self, title, strip_joiners=True, strip_subtitle=False): ''' Take a title and return a list of tokens useful for an AND search query. - Excludes connectives and punctuation. + Excludes connectives(optionally) and punctuation. ''' if title: - pat = re.compile(r'''[-,:;+!@#$%^&*(){}.`~"'\s\[\]/]''') - title = pat.sub(' ', title) + # strip sub-titles + if strip_subtitle: + subtitle = re.compile(r'([\(\[\{].*?[\)\]\}]|[/:\\].*$)') + if len(subtitle.sub('', title)) > 1: + title = subtitle.sub('', title) + + title_patterns = [(re.compile(pat, re.IGNORECASE), repl) for pat, repl in + [ + # Remove things like: (2010) (Omnibus) etc. + (r'(?i)[({\[](\d{4}|omnibus|anthology|hardcover|audiobook|audio\scd|paperback|turtleback|mass\s*market|edition|ed\.)[\])}]', ''), + # Remove any strings that contain the substring edition inside + # parentheses + (r'(?i)[({\[].*?(edition|ed.).*?[\]})]', ''), + # Remove commas used a separators in numbers + (r'(\d+),(\d+)', r'\1\2'), + # Remove hyphens only if they have whitespace before them + (r'(\s-)', ' '), + # Remove single quotes not followed by 's' + (r"'(?!s)", ''), + # Replace other special chars with a space + (r'''[:,;+!@#$%^&*(){}.`~"\s\[\]/]''', ' ') + ]] + + for pat, repl in title_patterns: + title = pat.sub(repl, title) + tokens = title.split() for token in tokens: token = token.strip() - if token and token.lower() not in ('a', 'and', 'the'): + if token and (not strip_joiners or token.lower() not in ('a', + 'and', 'the', '&')): yield token def split_jobs(self, jobs, num): @@ -95,15 +350,103 @@ class Source(Plugin): gr.append(job) return [g for g in groups if g] + def test_fields(self, mi): + ''' + Return the first field from self.touched_fields that is null on the + mi object + ''' + for key in self.touched_fields: + if key.startswith('identifier:'): + key = key.partition(':')[-1] + if not mi.has_identifier(key): + return 'identifier: ' + key + elif mi.is_null(key): + return key + + def clean_downloaded_metadata(self, mi): + ''' + Call this method in your plugin's identify method to normalize metadata + before putting the Metadata object into result_queue. You can of + course, use a custom algorithm suited to your metadata source. + ''' + if mi.title: + mi.title = fixcase(mi.title) + mi.authors = fixauthors(mi.authors) + mi.tags = list(map(fixcase, mi.tags)) + mi.isbn = check_isbn(mi.isbn) + # }}} # Metadata API {{{ + def get_book_url(self, identifiers): + ''' + Return a 3-tuple or None. The 3-tuple is of the form: + (identifier_type, identifier_value, URL). + The URL is the URL for the book identified by identifiers at this + source. identifier_type, identifier_value specify the identifier + corresponding to the URL. + This URL must be browseable to by a human using a browser. It is meant + to provide a clickable link for the user to easily visit the books page + at this source. + If no URL is found, return None. This method must be quick, and + consistent, so only implement it if it is possible to construct the URL + from a known scheme given identifiers. + ''' + return None + + def get_cached_cover_url(self, identifiers): + ''' + Return cached cover URL for the book identified by + the identifiers dict or None if no such URL exists. + + Note that this method must only return validated URLs, i.e. not URLS + that could result in a generic cover image or a not found error. + ''' + return None + + def identify_results_keygen(self, title=None, authors=None, + identifiers={}): + ''' + Return a function that is used to generate a key that can sort Metadata + objects by their relevance given a search query (title, authors, + identifiers). + + These keys are used to sort the results of a call to :meth:`identify`. + + For details on the default algorithm see + :class:`InternalMetadataCompareKeyGen`. Re-implement this function in + your plugin if the default algorithm is not suitable. + ''' + def keygen(mi): + return InternalMetadataCompareKeyGen(mi, self, title, authors, + identifiers) + return keygen + def identify(self, log, result_queue, abort, title=None, authors=None, - identifiers={}, timeout=5): + identifiers={}, timeout=30): ''' Identify a book by its title/author/isbn/etc. + If identifiers(s) are specified and no match is found and this metadata + source does not store all related identifiers (for example, all ISBNs + of a book), this method should retry with just the title and author + (assuming they were specified). + + If this metadata source also provides covers, the URL to the cover + should be cached so that a subsequent call to the get covers API with + the same ISBN/special identifier does not need to get the cover URL + again. Use the caching API for this. + + Every Metadata object put into result_queue by this method must have a + `source_relevance` attribute that is an integer indicating the order in + which the results were returned by the metadata source for this query. + This integer will be used by :meth:`compare_identify_results`. If the + order is unimportant, set it to zero for every result. + + Make sure that any cover/isbn mapping information is cached before the + Metadata object is put into result_queue. + :param log: A log object, use it to output debugging information/errors :param result_queue: A result Queue, results should be put into it. Each result is a Metadata object @@ -121,5 +464,18 @@ class Source(Plugin): ''' return None + def download_cover(self, log, result_queue, abort, + title=None, authors=None, identifiers={}, timeout=30): + ''' + Download a cover and put it into result_queue. The parameters all have + the same meaning as for :meth:`identify`. Put (self, cover_data) into + result_queue. + + This method should use cached cover URLs for efficiency whenever + possible. When cached data is not present, most plugins simply call + identify and use its results. + ''' + pass + # }}} diff --git a/src/calibre/ebooks/metadata/sources/cli.py b/src/calibre/ebooks/metadata/sources/cli.py new file mode 100644 index 0000000000..f8b9c6b7a9 --- /dev/null +++ b/src/calibre/ebooks/metadata/sources/cli.py @@ -0,0 +1,97 @@ +#!/usr/bin/env python +# vim:fileencoding=UTF-8:ts=4:sw=4:sta:et:sts=4:ai +from __future__ import (unicode_literals, division, absolute_import, + print_function) + +__license__ = 'GPL v3' +__copyright__ = '2011, Kovid Goyal <kovid@kovidgoyal.net>' +__docformat__ = 'restructuredtext en' + +import sys, textwrap +from io import BytesIO +from threading import Event + +from calibre import prints +from calibre.utils.config import OptionParser +from calibre.utils.magick.draw import save_cover_data_to +from calibre.ebooks.metadata import string_to_authors +from calibre.ebooks.metadata.opf2 import metadata_to_opf +from calibre.ebooks.metadata.sources.base import create_log +from calibre.ebooks.metadata.sources.identify import identify +from calibre.ebooks.metadata.sources.covers import download_cover + +def option_parser(): + parser = OptionParser(textwrap.dedent( + '''\ + %prog [options] + + Fetch book metadata from online sources. You must specify at least one + of title, authors or ISBN. + ''' + )) + parser.add_option('-t', '--title', help='Book title') + parser.add_option('-a', '--authors', help='Book author(s)') + parser.add_option('-i', '--isbn', help='Book ISBN') + parser.add_option('-v', '--verbose', default=False, action='store_true', + help='Print the log to the console (stderr)') + parser.add_option('-o', '--opf', help='Output the metadata in OPF format') + parser.add_option('-c', '--cover', + help='Specify a filename. The cover, if available, will be saved to it') + parser.add_option('-d', '--timeout', default='30', + help='Timeout in seconds. Default is 30') + + return parser + +def main(args=sys.argv): + parser = option_parser() + opts, args = parser.parse_args(args) + + buf = BytesIO() + log = create_log(buf) + abort = Event() + + authors = [] + if opts.authors: + authors = string_to_authors(opts.authors) + + identifiers = {} + if opts.isbn: + identifiers['isbn'] = opts.isbn + + results = identify(log, abort, title=opts.title, authors=authors, + identifiers=identifiers, timeout=int(opts.timeout)) + + if not results: + print (log, file=sys.stderr) + prints('No results found', file=sys.stderr) + raise SystemExit(1) + result = results[0] + + cf = None + if opts.cover and results: + cover = download_cover(log, title=opts.title, authors=authors, + identifiers=result.identifiers, timeout=int(opts.timeout)) + if cover is None: + prints('No cover found', file=sys.stderr) + else: + save_cover_data_to(cover[-1], opts.cover) + result.cover = cf = opts.cover + + + log = buf.getvalue() + + + result = (metadata_to_opf(result) if opts.opf else + unicode(result).encode('utf-8')) + + if opts.verbose: + print (log, file=sys.stderr) + + print (result) + if not opts.opf and opts.cover: + prints('Cover :', cf) + + return 0 + +if __name__ == '__main__': + sys.exit(main()) diff --git a/src/calibre/ebooks/metadata/sources/covers.py b/src/calibre/ebooks/metadata/sources/covers.py new file mode 100644 index 0000000000..d28ce146c6 --- /dev/null +++ b/src/calibre/ebooks/metadata/sources/covers.py @@ -0,0 +1,183 @@ +#!/usr/bin/env python +# vim:fileencoding=UTF-8:ts=4:sw=4:sta:et:sts=4:ai +from __future__ import (unicode_literals, division, absolute_import, + print_function) + +__license__ = 'GPL v3' +__copyright__ = '2011, Kovid Goyal <kovid@kovidgoyal.net>' +__docformat__ = 'restructuredtext en' + +import time +from Queue import Queue, Empty +from threading import Thread, Event +from io import BytesIO + +from calibre.customize.ui import metadata_plugins +from calibre.ebooks.metadata.sources.base import msprefs, create_log +from calibre.utils.magick.draw import Image, save_cover_data_to + +class Worker(Thread): + + def __init__(self, plugin, abort, title, authors, identifiers, timeout, rq): + Thread.__init__(self) + self.daemon = True + + self.plugin = plugin + self.abort = abort + self.buf = BytesIO() + self.log = create_log(self.buf) + self.title, self.authors, self.identifiers = (title, authors, + identifiers) + self.timeout, self.rq = timeout, rq + self.time_spent = None + + def run(self): + start_time = time.time() + if not self.abort.is_set(): + try: + self.plugin.download_cover(self.log, self.rq, self.abort, + title=self.title, authors=self.authors, + identifiers=self.identifiers, timeout=self.timeout) + except: + self.log.exception('Failed to download cover from', + self.plugin.name) + self.time_spent = time.time() - start_time + +def is_worker_alive(workers): + for w in workers: + if w.is_alive(): + return True + return False + +def process_result(log, result): + plugin, data = result + try: + im = Image() + im.load(data) + im.trim(10) + width, height = im.size + fmt = im.format + + if width < 50 or height < 50: + raise ValueError('Image too small') + data = save_cover_data_to(im, '/cover.jpg', return_data=True) + except: + log.exception('Invalid cover from', plugin.name) + return None + return (plugin, width, height, fmt, data) + +def run_download(log, results, abort, + title=None, authors=None, identifiers={}, timeout=30): + ''' + Run the cover download, putting results into the queue :param:`results`. + + Each result is a tuple of the form: + + (plugin, width, height, fmt, bytes) + + ''' + if title == _('Unknown'): + title = None + if authors == [_('Unknown')]: + authors = None + + plugins = [p for p in metadata_plugins(['cover']) if p.is_configured()] + + rq = Queue() + workers = [Worker(p, abort, title, authors, identifiers, timeout, rq) for p + in plugins] + for w in workers: + w.start() + + first_result_at = None + wait_time = msprefs['wait_after_first_cover_result'] + found_results = {} + + while True: + time.sleep(0.1) + try: + x = rq.get_nowait() + result = process_result(log, x) + if result is not None: + results.put(result) + found_results[result[0]] = result + if first_result_at is not None: + first_result_at = time.time() + except Empty: + pass + + if not is_worker_alive(workers): + break + + if first_result_at is not None and time.time() - first_result_at > wait_time: + log('Not waiting for any more results') + abort.set() + + if abort.is_set(): + break + + while True: + try: + x = rq.get_nowait() + result = process_result(log, x) + if result is not None: + results.put(result) + found_results[result[0]] = result + except Empty: + break + + for w in workers: + wlog = w.buf.getvalue().strip() + log('\n'+'*'*30, w.plugin.name, 'Covers', '*'*30) + log('Request extra headers:', w.plugin.browser.addheaders) + if w.plugin in found_results: + result = found_results[w.plugin] + log('Downloaded cover:', '%dx%d'%(result[1], result[2])) + else: + log('Failed to download valid cover') + if w.time_spent is None: + log('Download aborted') + else: + log('Took', w.time_spent, 'seconds') + if wlog: + log(wlog) + log('\n'+'*'*80) + + +def download_cover(log, + title=None, authors=None, identifiers={}, timeout=30): + ''' + Synchronous cover download. Returns the "best" cover as per user + prefs/cover resolution. + + Returned cover is a tuple: (plugin, width, height, fmt, data) + + Returns None if no cover is found. + ''' + rq = Queue() + abort = Event() + + run_download(log, rq, abort, title=title, authors=authors, + identifiers=identifiers, timeout=timeout) + + results = [] + + while True: + try: + results.append(rq.get_nowait()) + except Empty: + break + + cp = msprefs['cover_priorities'] + + def keygen(result): + plugin, width, height, fmt, data = result + return (cp.get(plugin.name, 1), 1/(width*height)) + + results.sort(key=keygen) + + return results[0] if results else None + + + + diff --git a/src/calibre/ebooks/metadata/sources/douban.py b/src/calibre/ebooks/metadata/sources/douban.py new file mode 100644 index 0000000000..8f8f5b80c4 --- /dev/null +++ b/src/calibre/ebooks/metadata/sources/douban.py @@ -0,0 +1,352 @@ +#!/usr/bin/env python +# vim:fileencoding=UTF-8:ts=4:sw=4:sta:et:sts=4:ai +from __future__ import (unicode_literals, division, absolute_import, + print_function) + +__license__ = 'GPL v3' +__copyright__ = '2011, Kovid Goyal <kovid@kovidgoyal.net>; 2011, Li Fanxi <lifanxi@freemindworld.com>' +__docformat__ = 'restructuredtext en' + +import time +from urllib import urlencode +from functools import partial +from Queue import Queue, Empty + +from lxml import etree + +from calibre.ebooks.metadata import check_isbn +from calibre.ebooks.metadata.sources.base import Source +from calibre.ebooks.metadata.book.base import Metadata +from calibre.ebooks.chardet import xml_to_unicode +from calibre.utils.date import parse_date, utcnow +from calibre.utils.cleantext import clean_ascii_chars +from calibre import as_unicode + +NAMESPACES = { + 'openSearch':'http://a9.com/-/spec/opensearchrss/1.0/', + 'atom' : 'http://www.w3.org/2005/Atom', + 'db': 'http://www.douban.com/xmlns/', + 'gd': 'http://schemas.google.com/g/2005' + } +XPath = partial(etree.XPath, namespaces=NAMESPACES) +total_results = XPath('//openSearch:totalResults') +start_index = XPath('//openSearch:startIndex') +items_per_page = XPath('//openSearch:itemsPerPage') +entry = XPath('//atom:entry') +entry_id = XPath('descendant::atom:id') +title = XPath('descendant::atom:title') +description = XPath('descendant::atom:summary') +publisher = XPath("descendant::db:attribute[@name='publisher']") +isbn = XPath("descendant::db:attribute[@name='isbn13']") +date = XPath("descendant::db:attribute[@name='pubdate']") +creator = XPath("descendant::db:attribute[@name='author']") +booktag = XPath("descendant::db:tag/attribute::name") +rating = XPath("descendant::gd:rating/attribute::average") +cover_url = XPath("descendant::atom:link[@rel='image']/attribute::href") + +def get_details(browser, url, timeout): # {{{ + try: + if Douban.DOUBAN_API_KEY and Douban.DOUBAN_API_KEY != '': + url = url + "?apikey=" + Douban.DOUBAN_API_KEY + raw = browser.open_novisit(url, timeout=timeout).read() + except Exception as e: + gc = getattr(e, 'getcode', lambda : -1) + if gc() != 403: + raise + # Douban is throttling us, wait a little + time.sleep(2) + raw = browser.open_novisit(url, timeout=timeout).read() + + return raw +# }}} + +def to_metadata(browser, log, entry_, timeout): # {{{ + def get_text(extra, x): + try: + ans = x(extra) + if ans: + ans = ans[0].text + if ans and ans.strip(): + return ans.strip() + except: + log.exception('Programming error:') + return None + + id_url = entry_id(entry_)[0].text + douban_id = id_url.split('/')[-1] + title_ = ': '.join([x.text for x in title(entry_)]).strip() + authors = [x.text.strip() for x in creator(entry_) if x.text] + if not authors: + authors = [_('Unknown')] + if not id_url or not title: + # Silently discard this entry + return None + + mi = Metadata(title_, authors) + mi.identifiers = {'douban':douban_id} + try: + raw = get_details(browser, id_url, timeout) + feed = etree.fromstring(xml_to_unicode(clean_ascii_chars(raw), + strip_encoding_pats=True)[0]) + extra = entry(feed)[0] + except: + log.exception('Failed to get additional details for', mi.title) + return mi + mi.comments = get_text(extra, description) + mi.publisher = get_text(extra, publisher) + + # ISBN + isbns = [] + for x in [t.text for t in isbn(extra)]: + if check_isbn(x): + isbns.append(x) + if isbns: + mi.isbn = sorted(isbns, key=len)[-1] + mi.all_isbns = isbns + + # Tags + try: + btags = [x for x in booktag(extra) if x] + tags = [] + for t in btags: + atags = [y.strip() for y in t.split('/')] + for tag in atags: + if tag not in tags: + tags.append(tag) + except: + log.exception('Failed to parse tags:') + tags = [] + if tags: + mi.tags = [x.replace(',', ';') for x in tags] + + # pubdate + pubdate = get_text(extra, date) + if pubdate: + try: + default = utcnow().replace(day=15) + mi.pubdate = parse_date(pubdate, assume_utc=True, default=default) + except: + log.error('Failed to parse pubdate %r'%pubdate) + + # Ratings + if rating(extra): + try: + mi.rating = float(rating(extra)[0]) / 2.0 + except: + log.exception('Failed to parse rating') + mi.rating = 0 + + # Cover + mi.has_douban_cover = None + u = cover_url(extra) + if u: + u = u[0].replace('/spic/', '/lpic/'); + # If URL contains "book-default", the book doesn't have a cover + if u.find('book-default') == -1: + mi.has_douban_cover = u + return mi +# }}} + +class Douban(Source): + + name = 'Douban Books' + author = 'Li Fanxi' + version = (2, 0, 0) + + description = _('Downloads metadata and covers from Douban.com') + + capabilities = frozenset(['identify', 'cover']) + touched_fields = frozenset(['title', 'authors', 'tags', + 'pubdate', 'comments', 'publisher', 'identifier:isbn', 'rating', + 'identifier:douban']) # language currently disabled + supports_gzip_transfer_encoding = True + cached_cover_url_is_reliable = True + + DOUBAN_API_KEY = '0bd1672394eb1ebf2374356abec15c3d' + DOUBAN_BOOK_URL = 'http://book.douban.com/subject/%s/' + + def get_book_url(self, identifiers): # {{{ + db = identifiers.get('douban', None) + if db is not None: + return ('douban', db, self.DOUBAN_BOOK_URL%db) + # }}} + + def create_query(self, log, title=None, authors=None, identifiers={}): # {{{ + SEARCH_URL = 'http://api.douban.com/book/subjects?' + ISBN_URL = 'http://api.douban.com/book/subject/isbn/' + SUBJECT_URL = 'http://api.douban.com/book/subject/' + + q = '' + t = None + isbn = check_isbn(identifiers.get('isbn', None)) + subject = identifiers.get('douban', None) + if isbn is not None: + q = isbn + t = 'isbn' + elif subject is not None: + q = subject + t = 'subject' + elif title or authors: + def build_term(prefix, parts): + return ' '.join(x for x in parts) + title_tokens = list(self.get_title_tokens(title)) + if title_tokens: + q += build_term('title', title_tokens) + author_tokens = self.get_author_tokens(authors, + only_first_author=True) + if author_tokens: + q += ((' ' if q != '' else '') + + build_term('author', author_tokens)) + t = 'search' + q = q.strip() + if isinstance(q, unicode): + q = q.encode('utf-8') + if not q: + return None + url = None + if t == "isbn": + url = ISBN_URL + q + elif t == 'subject': + url = SUBJECT_URL + q + else: + url = SEARCH_URL + urlencode({ + 'q': q, + }) + if self.DOUBAN_API_KEY and self.DOUBAN_API_KEY != '': + if t == "isbn" or t == "subject": + url = url + "?apikey=" + self.DOUBAN_API_KEY + else: + url = url + "&apikey=" + self.DOUBAN_API_KEY + return url + # }}} + + def download_cover(self, log, result_queue, abort, # {{{ + title=None, authors=None, identifiers={}, timeout=30): + cached_url = self.get_cached_cover_url(identifiers) + if cached_url is None: + log.info('No cached cover found, running identify') + rq = Queue() + self.identify(log, rq, abort, title=title, authors=authors, + identifiers=identifiers) + if abort.is_set(): + return + results = [] + while True: + try: + results.append(rq.get_nowait()) + except Empty: + break + results.sort(key=self.identify_results_keygen( + title=title, authors=authors, identifiers=identifiers)) + for mi in results: + cached_url = self.get_cached_cover_url(mi.identifiers) + if cached_url is not None: + break + if cached_url is None: + log.info('No cover found') + return + + if abort.is_set(): + return + br = self.browser + log('Downloading cover from:', cached_url) + try: + cdata = br.open_novisit(cached_url, timeout=timeout).read() + if cdata: + result_queue.put((self, cdata)) + except: + log.exception('Failed to download cover from:', cached_url) + + # }}} + + def get_cached_cover_url(self, identifiers): # {{{ + url = None + db = identifiers.get('douban', None) + if db is None: + isbn = identifiers.get('isbn', None) + if isbn is not None: + db = self.cached_isbn_to_identifier(isbn) + if db is not None: + url = self.cached_identifier_to_cover_url(db) + + return url + # }}} + + def get_all_details(self, br, log, entries, abort, # {{{ + result_queue, timeout): + for relevance, i in enumerate(entries): + try: + ans = to_metadata(br, log, i, timeout) + if isinstance(ans, Metadata): + ans.source_relevance = relevance + db = ans.identifiers['douban'] + for isbn in getattr(ans, 'all_isbns', []): + self.cache_isbn_to_identifier(isbn, db) + if ans.has_douban_cover: + self.cache_identifier_to_cover_url(db, + ans.has_douban_cover) + self.clean_downloaded_metadata(ans) + result_queue.put(ans) + except: + log.exception( + 'Failed to get metadata for identify entry:', + etree.tostring(i)) + if abort.is_set(): + break + # }}} + + def identify(self, log, result_queue, abort, title=None, authors=None, # {{{ + identifiers={}, timeout=30): + query = self.create_query(log, title=title, authors=authors, + identifiers=identifiers) + if not query: + log.error('Insufficient metadata to construct query') + return + br = self.browser + try: + raw = br.open_novisit(query, timeout=timeout).read() + except Exception as e: + log.exception('Failed to make identify query: %r'%query) + return as_unicode(e) + try: + parser = etree.XMLParser(recover=True, no_network=True) + feed = etree.fromstring(xml_to_unicode(clean_ascii_chars(raw), + strip_encoding_pats=True)[0], parser=parser) + entries = entry(feed) + except Exception as e: + log.exception('Failed to parse identify results') + return as_unicode(e) + if not entries and identifiers and title and authors and \ + not abort.is_set(): + return self.identify(log, result_queue, abort, title=title, + authors=authors, timeout=timeout) + + # There is no point running these queries in threads as douban + # throttles requests returning 403 Forbidden errors + self.get_all_details(br, log, entries, abort, result_queue, timeout) + + return None + # }}} + +if __name__ == '__main__': # tests {{{ + # To run these test use: calibre-debug -e src/calibre/ebooks/metadata/sources/douban.py + from calibre.ebooks.metadata.sources.test import (test_identify_plugin, + title_test, authors_test) + test_identify_plugin(Douban.name, + [ + + + ( + {'identifiers':{'isbn': '9787536692930'}, 'title':'三体', + 'authors':['刘慈欣']}, + [title_test('三体', exact=True), + authors_test(['刘慈欣'])] + ), + + ( + {'title': 'Linux内核修炼之道', 'authors':['任桥伟']}, + [title_test('Linux内核修炼之道', exact=False)] + ), + ]) +# }}} + diff --git a/src/calibre/ebooks/metadata/sources/google.py b/src/calibre/ebooks/metadata/sources/google.py index 0720b21ded..bd1043b774 100644 --- a/src/calibre/ebooks/metadata/sources/google.py +++ b/src/calibre/ebooks/metadata/sources/google.py @@ -7,9 +7,10 @@ __license__ = 'GPL v3' __copyright__ = '2011, Kovid Goyal <kovid@kovidgoyal.net>' __docformat__ = 'restructuredtext en' -import time +import time, hashlib from urllib import urlencode from functools import partial +from Queue import Queue, Empty from lxml import etree @@ -19,12 +20,13 @@ from calibre.ebooks.metadata.book.base import Metadata from calibre.ebooks.chardet import xml_to_unicode from calibre.utils.date import parse_date, utcnow from calibre.utils.cleantext import clean_ascii_chars -from calibre import browser, as_unicode +from calibre import as_unicode NAMESPACES = { 'openSearch':'http://a9.com/-/spec/opensearchrss/1.0/', 'atom' : 'http://www.w3.org/2005/Atom', - 'dc': 'http://purl.org/dc/terms' + 'dc' : 'http://purl.org/dc/terms', + 'gd' : 'http://schemas.google.com/g/2005' } XPath = partial(etree.XPath, namespaces=NAMESPACES) @@ -41,8 +43,9 @@ publisher = XPath('descendant::dc:publisher') subject = XPath('descendant::dc:subject') description = XPath('descendant::dc:description') language = XPath('descendant::dc:language') +rating = XPath('descendant::gd:rating[@average]') -def get_details(browser, url, timeout): +def get_details(browser, url, timeout): # {{{ try: raw = browser.open_novisit(url, timeout=timeout).read() except Exception as e: @@ -50,12 +53,13 @@ def get_details(browser, url, timeout): if gc() != 403: raise # Google is throttling us, wait a little - time.sleep(1) + time.sleep(2) raw = browser.open_novisit(url, timeout=timeout).read() return raw +# }}} -def to_metadata(browser, log, entry_, timeout): +def to_metadata(browser, log, entry_, timeout): # {{{ def get_text(extra, x): try: @@ -94,12 +98,6 @@ def to_metadata(browser, log, entry_, timeout): #mi.language = get_text(extra, language) mi.publisher = get_text(extra, publisher) - # Author sort - for x in creator(extra): - for key, val in x.attrib.items(): - if key.endswith('file-as') and val and val.strip(): - mi.author_sort = val - break # ISBN isbns = [] for x in identifier(extra): @@ -118,8 +116,10 @@ def to_metadata(browser, log, entry_, timeout): btags = [x.text for x in subject(extra) if x.text] tags = [] for t in btags: - tags.extend([y.strip() for y in t.split('/')]) - tags = list(sorted(list(set(tags)))) + atags = [y.strip() for y in t.split('/')] + for tag in atags: + if tag not in tags: + tags.append(tag) except: log.exception('Failed to parse tags:') tags = [] @@ -133,24 +133,52 @@ def to_metadata(browser, log, entry_, timeout): default = utcnow().replace(day=15) mi.pubdate = parse_date(pubdate, assume_utc=True, default=default) except: - log.exception('Failed to parse pubdate') + log.error('Failed to parse pubdate %r'%pubdate) + # Ratings + for x in rating(extra): + try: + mi.rating = float(x.get('average')) + if mi.rating > 5: + mi.rating /= 2 + except: + log.exception('Failed to parse rating') + + # Cover + mi.has_google_cover = None + for x in extra.xpath( + '//*[@href and @rel="http://schemas.google.com/books/2008/thumbnail"]'): + mi.has_google_cover = x.get('href') + break return mi - +# }}} class GoogleBooks(Source): - name = 'Google Books' - description = _('Downloads metadata from Google Books') + name = 'Google' + description = _('Downloads metadata and covers from Google Books') - capabilities = frozenset(['identify']) - touched_fields = frozenset(['title', 'authors', 'isbn', 'tags', 'pubdate', - 'comments', 'publisher', 'author_sort']) # language currently disabled + capabilities = frozenset(['identify', 'cover']) + touched_fields = frozenset(['title', 'authors', 'tags', 'pubdate', + 'comments', 'publisher', 'identifier:isbn', 'rating', + 'identifier:google']) # language currently disabled + supports_gzip_transfer_encoding = True + cached_cover_url_is_reliable = False - def create_query(self, log, title=None, authors=None, identifiers={}): + GOOGLE_COVER = 'http://books.google.com/books?id=%s&printsec=frontcover&img=1' + + DUMMY_IMAGE_MD5 = frozenset(['0de4383ebad0adad5eeb8975cd796657']) + + def get_book_url(self, identifiers): # {{{ + goog = identifiers.get('google', None) + if goog is not None: + return ('google', goog, 'http://books.google.com/books?id=%s'%goog) + # }}} + + def create_query(self, log, title=None, authors=None, identifiers={}): # {{{ BASE_URL = 'http://books.google.com/books/feeds/volumes?' - isbn = identifiers.get('isbn', None) + isbn = check_isbn(identifiers.get('isbn', None)) q = '' if isbn is not None: q += 'isbn:'+isbn @@ -176,46 +204,97 @@ class GoogleBooks(Source): 'start-index':1, 'min-viewability':'none', }) + # }}} - def cover_url_from_identifiers(self, identifiers): + def download_cover(self, log, result_queue, abort, # {{{ + title=None, authors=None, identifiers={}, timeout=30): + cached_url = self.get_cached_cover_url(identifiers) + if cached_url is None: + log.info('No cached cover found, running identify') + rq = Queue() + self.identify(log, rq, abort, title=title, authors=authors, + identifiers=identifiers) + if abort.is_set(): + return + results = [] + while True: + try: + results.append(rq.get_nowait()) + except Empty: + break + results.sort(key=self.identify_results_keygen( + title=title, authors=authors, identifiers=identifiers)) + for mi in results: + cached_url = self.get_cached_cover_url(mi.identifiers) + if cached_url is not None: + break + if cached_url is None: + log.info('No cover found') + return + + if abort.is_set(): + return + br = self.browser + log('Downloading cover from:', cached_url) + try: + cdata = br.open_novisit(cached_url, timeout=timeout).read() + if cdata: + if hashlib.md5(cdata).hexdigest() in self.DUMMY_IMAGE_MD5: + log.warning('Google returned a dummy image, ignoring') + else: + result_queue.put((self, cdata)) + except: + log.exception('Failed to download cover from:', cached_url) + + # }}} + + def get_cached_cover_url(self, identifiers): # {{{ + url = None goog = identifiers.get('google', None) if goog is None: isbn = identifiers.get('isbn', None) - goog = self.cached_isbn_to_identifier(isbn) + if isbn is not None: + goog = self.cached_isbn_to_identifier(isbn) if goog is not None: - return ('http://books.google.com/books?id=%s&printsec=frontcover&img=1' % - goog) + url = self.cached_identifier_to_cover_url(goog) - def is_cover_image_valid(self, raw): - # When no cover is present, returns a PNG saying image not available - # Try for example google identifier llNqPwAACAAJ - # I have yet to see an actual cover in PNG format - return raw and len(raw) > 17000 and raw[1:4] != 'PNG' + return url + # }}} - def get_all_details(self, br, log, entries, abort, result_queue, timeout): - for i in entries: + def get_all_details(self, br, log, entries, abort, # {{{ + result_queue, timeout): + for relevance, i in enumerate(entries): try: ans = to_metadata(br, log, i, timeout) if isinstance(ans, Metadata): + ans.source_relevance = relevance + goog = ans.identifiers['google'] + for isbn in getattr(ans, 'all_isbns', []): + self.cache_isbn_to_identifier(isbn, goog) + if ans.has_google_cover: + self.cache_identifier_to_cover_url(goog, + self.GOOGLE_COVER%goog) + self.clean_downloaded_metadata(ans) result_queue.put(ans) - for isbn in ans.all_isbns: - self.cache_isbn_to_identifier(isbn, - ans.identifiers['google']) except: log.exception( 'Failed to get metadata for identify entry:', etree.tostring(i)) if abort.is_set(): break + # }}} - def identify(self, log, result_queue, abort, title=None, authors=None, - identifiers={}, timeout=5): + def identify(self, log, result_queue, abort, title=None, authors=None, # {{{ + identifiers={}, timeout=30): query = self.create_query(log, title=title, authors=authors, identifiers=identifiers) - br = browser() + if not query: + log.error('Insufficient metadata to construct query') + return + br = self.browser try: raw = br.open_novisit(query, timeout=timeout).read() - except Exception, e: + except Exception as e: log.exception('Failed to make identify query: %r'%query) return as_unicode(e) @@ -224,30 +303,41 @@ class GoogleBooks(Source): feed = etree.fromstring(xml_to_unicode(clean_ascii_chars(raw), strip_encoding_pats=True)[0], parser=parser) entries = entry(feed) - except Exception, e: + except Exception as e: log.exception('Failed to parse identify results') return as_unicode(e) + if not entries and identifiers and title and authors and \ + not abort.is_set(): + return self.identify(log, result_queue, abort, title=title, + authors=authors, timeout=timeout) + # There is no point running these queries in threads as google # throttles requests returning 403 Forbidden errors self.get_all_details(br, log, entries, abort, result_queue, timeout) return None + # }}} -if __name__ == '__main__': +if __name__ == '__main__': # tests {{{ # To run these test use: calibre-debug -e src/calibre/ebooks/metadata/sources/google.py from calibre.ebooks.metadata.sources.test import (test_identify_plugin, - title_test) + title_test, authors_test) test_identify_plugin(GoogleBooks.name, [ + ( - {'identifiers':{'isbn': '0743273567'}}, - [title_test('The great gatsby', exact=True)] + {'identifiers':{'isbn': '0743273567'}, 'title':'Great Gatsby', + 'authors':['Fitzgerald']}, + [title_test('The great gatsby', exact=True), + authors_test(['Francis Scott Fitzgerald'])] ), - #( - # {'title': 'Great Expectations', 'authors':['Charles Dickens']}, - # [title_test('Great Expectations', exact=True)] - #), + ( + {'title': 'Flatland', 'authors':['Abbott']}, + [title_test('Flatland', exact=False)] + ), ]) +# }}} + diff --git a/src/calibre/ebooks/metadata/sources/identify.py b/src/calibre/ebooks/metadata/sources/identify.py new file mode 100644 index 0000000000..0cc070c3c6 --- /dev/null +++ b/src/calibre/ebooks/metadata/sources/identify.py @@ -0,0 +1,532 @@ +#!/usr/bin/env python +# vim:fileencoding=UTF-8:ts=4:sw=4:sta:et:sts=4:ai +from __future__ import (unicode_literals, division, absolute_import, + print_function) + +__license__ = 'GPL v3' +__copyright__ = '2011, Kovid Goyal <kovid@kovidgoyal.net>' +__docformat__ = 'restructuredtext en' + +import time +from datetime import datetime +from Queue import Queue, Empty +from threading import Thread +from io import BytesIO +from operator import attrgetter +from urlparse import urlparse + +from calibre.customize.ui import metadata_plugins, all_metadata_plugins +from calibre.ebooks.metadata.sources.base import create_log, msprefs +from calibre.ebooks.metadata.xisbn import xisbn +from calibre.ebooks.metadata.book.base import Metadata +from calibre.utils.date import utc_tz +from calibre.utils.html2text import html2text +from calibre.utils.icu import lower + +# Download worker {{{ +class Worker(Thread): + + def __init__(self, plugin, kwargs, abort): + Thread.__init__(self) + self.daemon = True + + self.plugin, self.kwargs, self.rq = plugin, kwargs, Queue() + self.abort = abort + self.buf = BytesIO() + self.log = create_log(self.buf) + + def run(self): + start = time.time() + try: + self.plugin.identify(self.log, self.rq, self.abort, **self.kwargs) + except: + self.log.exception('Plugin', self.plugin.name, 'failed') + self.plugin.dl_time_spent = time.time() - start + + @property + def name(self): + return self.plugin.name + +def is_worker_alive(workers): + for w in workers: + if w.is_alive(): + return True + return False + +# }}} + +# Merge results from different sources {{{ + +class ISBNMerge(object): + + def __init__(self): + self.pools = {} + self.isbnless_results = [] + + def isbn_in_pool(self, isbn): + if isbn: + for isbns, pool in self.pools.iteritems(): + if isbn in isbns: + return pool + return None + + def pool_has_result_from_same_source(self, pool, result): + results = pool[1] + for r in results: + if r.identify_plugin is result.identify_plugin: + return True + return False + + def add_result(self, result): + isbn = result.isbn + if isbn: + pool = self.isbn_in_pool(isbn) + if pool is None: + isbns, min_year = xisbn.get_isbn_pool(isbn) + if not isbns: + isbns = frozenset([isbn]) + self.pools[isbns] = pool = (min_year, []) + + if not self.pool_has_result_from_same_source(pool, result): + pool[1].append(result) + else: + self.isbnless_results.append(result) + + def finalize(self): + has_isbn_result = False + for results in self.pools.itervalues(): + if results: + has_isbn_result = True + break + self.has_isbn_result = has_isbn_result + + if has_isbn_result: + self.merge_isbn_results() + else: + results = sorted(self.isbnless_results, + key=attrgetter('relevance_in_source')) + # Pick only the most relevant result from each source + self.results = [] + seen = set() + for result in results: + if result.identify_plugin not in seen: + seen.add(result.identify_plugin) + self.results.append(result) + result.average_source_relevance = \ + result.relevance_in_source + + self.merge_metadata_results() + + return self.results + + def merge_metadata_results(self, merge_on_identifiers=False): + ''' + Merge results with identical title and authors or an identical + identifier + ''' + # First title/author + groups = {} + for result in self.results: + title = lower(result.title if result.title else '') + key = (title, tuple([lower(x) for x in result.authors])) + if key not in groups: + groups[key] = [] + groups[key].append(result) + + if len(groups) != len(self.results): + self.results = [] + for rgroup in groups.itervalues(): + rel = [r.average_source_relevance for r in rgroup] + if len(rgroup) > 1: + result = self.merge(rgroup, None, do_asr=False) + result.average_source_relevance = sum(rel)/len(rel) + else: + result = rgroup[0] + self.results.append(result) + + if merge_on_identifiers: + # Now identifiers + groups, empty = {}, [] + for result in self.results: + key = set() + for typ, val in result.identifiers.iteritems(): + if typ and val: + key.add((typ, val)) + if key: + key = frozenset(key) + match = None + for candidate in list(groups): + if candidate.intersection(key): + # We have at least one identifier in common + match = candidate.union(key) + results = groups.pop(candidate) + results.append(result) + groups[match] = results + break + if match is None: + groups[key] = [result] + else: + empty.append(result) + + if len(groups) != len(self.results): + self.results = [] + for rgroup in groups.itervalues(): + rel = [r.average_source_relevance for r in rgroup] + if len(rgroup) > 1: + result = self.merge(rgroup, None, do_asr=False) + result.average_source_relevance = sum(rel)/len(rel) + elif rgroup: + result = rgroup[0] + self.results.append(result) + + if empty: + self.results.extend(empty) + + self.results.sort(key=attrgetter('average_source_relevance')) + + def merge_isbn_results(self): + self.results = [] + for min_year, results in self.pools.itervalues(): + if results: + self.results.append(self.merge(results, min_year)) + + self.results.sort(key=attrgetter('average_source_relevance')) + + def length_merge(self, attr, results, null_value=None, shortest=True): + values = [getattr(x, attr) for x in results if not x.is_null(attr)] + values = [x for x in values if len(x) > 0] + if not values: + return null_value + values.sort(key=len, reverse=not shortest) + return values[0] + + def random_merge(self, attr, results, null_value=None): + values = [getattr(x, attr) for x in results if not x.is_null(attr)] + return values[0] if values else null_value + + def merge(self, results, min_year, do_asr=True): + ans = Metadata(_('Unknown')) + + # We assume the shortest title has the least cruft in it + ans.title = self.length_merge('title', results, null_value=ans.title) + + # No harm in having extra authors, maybe something useful like an + # editor or translator + ans.authors = self.length_merge('authors', results, + null_value=ans.authors, shortest=False) + + # We assume the shortest publisher has the least cruft in it + ans.publisher = self.length_merge('publisher', results, + null_value=ans.publisher) + + # We assume the smallest set of tags has the least cruft in it + ans.tags = self.length_merge('tags', results, + null_value=ans.tags, shortest=msprefs['fewer_tags']) + + # We assume the longest series has the most info in it + ans.series = self.length_merge('series', results, + null_value=ans.series, shortest=False) + for r in results: + if r.series and r.series == ans.series: + ans.series_index = r.series_index + break + + # Average the rating over all sources + ratings = [] + for r in results: + rating = r.rating + if rating and rating > 0 and rating <= 5: + ratings.append(rating) + if ratings: + ans.rating = sum(ratings)/len(ratings) + + # Smallest language is likely to be valid + ans.language = self.length_merge('language', results, + null_value=ans.language) + + # Choose longest comments + ans.comments = self.length_merge('comments', results, + null_value=ans.comments, shortest=False) + + # Published date + if min_year: + min_date = datetime(min_year, 1, 2, tzinfo=utc_tz) + ans.pubdate = min_date + else: + min_date = datetime(3001, 1, 1, tzinfo=utc_tz) + for r in results: + if r.pubdate is not None and r.pubdate < min_date: + min_date = r.pubdate + if min_date.year < 3000: + ans.pubdate = min_date + + # Identifiers + for r in results: + ans.identifiers.update(r.identifiers) + + # Cover URL + ans.has_cached_cover_url = bool([r for r in results if + getattr(r, 'has_cached_cover_url', False)]) + + # Merge any other fields with no special handling (random merge) + touched_fields = set() + for r in results: + if hasattr(r, 'identify_plugin'): + touched_fields |= r.identify_plugin.touched_fields + + for f in touched_fields: + if f.startswith('identifier:') or not ans.is_null(f): + continue + setattr(ans, f, self.random_merge(f, results, + null_value=getattr(ans, f))) + + if do_asr: + avg = [x.relevance_in_source for x in results] + avg = sum(avg)/len(avg) + ans.average_source_relevance = avg + + return ans + + +def merge_identify_results(result_map, log): + isbn_merge = ISBNMerge() + for plugin, results in result_map.iteritems(): + for result in results: + isbn_merge.add_result(result) + + return isbn_merge.finalize() + +# }}} + +def identify(log, abort, # {{{ + title=None, authors=None, identifiers={}, timeout=30): + if title == _('Unknown'): + title = None + if authors == [_('Unknown')]: + authors = None + start_time = time.time() + plugins = [p for p in metadata_plugins(['identify']) if p.is_configured()] + + kwargs = { + 'title': title, + 'authors': authors, + 'identifiers': identifiers, + 'timeout': timeout, + } + + log('Running identify query with parameters:') + log(kwargs) + log('Using plugins:', ', '.join([p.name for p in plugins])) + log('The log from individual plugins is below') + + workers = [Worker(p, kwargs, abort) for p in plugins] + for w in workers: + w.start() + + first_result_at = None + results = {} + for p in plugins: + results[p] = [] + logs = dict([(w.plugin, w.buf) for w in workers]) + + def get_results(): + found = False + for w in workers: + try: + result = w.rq.get_nowait() + except Empty: + pass + else: + results[w.plugin].append(result) + found = True + return found + + wait_time = msprefs['wait_after_first_identify_result'] + while True: + time.sleep(0.2) + + if get_results() and first_result_at is None: + first_result_at = time.time() + + if not is_worker_alive(workers): + break + + if (first_result_at is not None and time.time() - first_result_at > + wait_time): + log.warn('Not waiting any longer for more results. Still running' + ' sources:') + for worker in workers: + if worker.is_alive(): + log.debug('\t' + worker.name) + abort.set() + break + + while not abort.is_set() and get_results(): + pass + + sort_kwargs = dict(kwargs) + for k in list(sort_kwargs.iterkeys()): + if k not in ('title', 'authors', 'identifiers'): + sort_kwargs.pop(k) + + longest, lp = -1, '' + for plugin, presults in results.iteritems(): + presults.sort(key=plugin.identify_results_keygen(**sort_kwargs)) + + # Throw away lower priority results from the same source that have exactly the same + # title and authors as a higher priority result + filter_results = set() + filtered_results = [] + for r in presults: + key = (r.title, tuple(r.authors)) + if key not in filter_results: + filtered_results.append(r) + filter_results.add(key) + results[plugin] = presults = filtered_results + + plog = logs[plugin].getvalue().strip() + log('\n'+'*'*30, plugin.name, '*'*30) + log('Request extra headers:', plugin.browser.addheaders) + log('Found %d results'%len(presults)) + time_spent = getattr(plugin, 'dl_time_spent', None) + if time_spent is None: + log('Downloading was aborted') + longest, lp = -1, plugin.name + else: + log('Downloading from', plugin.name, 'took', time_spent) + if time_spent > longest: + longest, lp = time_spent, plugin.name + for r in presults: + log('\n\n---') + log(unicode(r)) + if plog: + log(plog) + log('\n'+'*'*80) + + dummy = Metadata(_('Unknown')) + for i, result in enumerate(presults): + for f in plugin.prefs['ignore_fields']: + if ':' not in f: + setattr(result, f, getattr(dummy, f)) + result.relevance_in_source = i + result.has_cached_cover_url = (plugin.cached_cover_url_is_reliable + and plugin.get_cached_cover_url(result.identifiers) is not + None) + result.identify_plugin = plugin + if msprefs['txt_comments']: + if plugin.has_html_comments and result.comments: + result.comments = html2text(result.comments) + + 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') + start_time = time.time() + results = merge_identify_results(results, log) + log('We have %d merged results, merging took: %.2f seconds' % + (len(results), time.time() - start_time)) + + + max_tags = msprefs['max_tags'] + for r in results: + r.tags = r.tags[:max_tags] + + if msprefs['swap_author_names']: + for r in results: + def swap_to_ln_fn(a): + if ',' in a: + return a + parts = a.split(None) + if len(parts) <= 1: + return a + surname = parts[-1] + return '%s, %s' % (surname, ' '.join(parts[:-1])) + r.authors = [swap_to_ln_fn(a) for a in r.authors] + + return results +# }}} + +def urls_from_identifiers(identifiers): # {{{ + identifiers = dict([(k.lower(), v) for k, v in identifiers.iteritems()]) + ans = [] + for plugin in all_metadata_plugins(): + try: + id_type, id_val, url = plugin.get_book_url(identifiers) + ans.append((plugin.name, id_type, id_val, url)) + except: + pass + isbn = identifiers.get('isbn', None) + if isbn: + ans.append((isbn, 'isbn', isbn, + 'http://www.worldcat.org/isbn/'+isbn)) + doi = identifiers.get('doi', None) + if doi: + ans.append(('DOI', 'doi', doi, + 'http://dx.doi.org/'+doi)) + arxiv = identifiers.get('arxiv', None) + if arxiv: + ans.append(('arXiv', 'arxiv', arxiv, + 'http://arxiv.org/abs/'+arxiv)) + oclc = identifiers.get('oclc', None) + if oclc: + ans.append(('OCLC', 'oclc', oclc, + 'http://www.worldcat.org/oclc/'+oclc)) + url = identifiers.get('uri', None) + if url is None: + url = identifiers.get('url', None) + if url and url.startswith('http'): + url = url[:8].replace('|', ':') + url[8:].replace('|', ',') + parts = urlparse(url) + name = parts.netloc + ans.append((name, 'url', url, url)) + return ans +# }}} + +if __name__ == '__main__': # tests {{{ + # To run these test use: calibre-debug -e + # src/calibre/ebooks/metadata/sources/identify.py + from calibre.ebooks.metadata.sources.test import (test_identify, + title_test, authors_test) + tests = [ + ( + {'title':'Magykal Papers', + 'authors':['Sage']}, + [title_test('Septimus Heap: The Magykal Papers', exact=True)], + ), + + + ( # An e-book ISBN not on Amazon, one of the authors is + # unknown to Amazon + {'identifiers':{'isbn': '9780307459671'}, + 'title':'Invisible Gorilla', 'authors':['Christopher Chabris']}, + [title_test('The Invisible Gorilla', exact=True)] + + ), + + ( # Test absence of identifiers + {'title':'Learning Python', + 'authors':['Lutz']}, + [title_test('Learning Python', + exact=True), authors_test(['Mark J. Lutz', 'David Ascher']) + ] + + ), + + ( # Sophisticated comment formatting + {'identifiers':{'isbn': '9781416580829'}}, + [title_test('Angels & Demons', + exact=True), authors_test(['Dan Brown'])] + ), + + ( # A newer book + {'identifiers':{'isbn': '9780316044981'}}, + [title_test('The Heroes', exact=True), + authors_test(['Joe Abercrombie'])] + + ), + + ] + #test_identify(tests[1:2]) + test_identify(tests) +# }}} + diff --git a/src/calibre/ebooks/metadata/sources/isbndb.py b/src/calibre/ebooks/metadata/sources/isbndb.py new file mode 100644 index 0000000000..b33a625ca7 --- /dev/null +++ b/src/calibre/ebooks/metadata/sources/isbndb.py @@ -0,0 +1,246 @@ +#!/usr/bin/env python +# vim:fileencoding=UTF-8:ts=4:sw=4:sta:et:sts=4:ai +from __future__ import (unicode_literals, division, absolute_import, + print_function) + +__license__ = 'GPL v3' +__copyright__ = '2011, Kovid Goyal <kovid@kovidgoyal.net>' +__docformat__ = 'restructuredtext en' + +from urllib import quote + +from lxml import etree + +from calibre.ebooks.metadata import check_isbn +from calibre.ebooks.metadata.sources.base import Source, Option +from calibre.ebooks.chardet import xml_to_unicode +from calibre.utils.cleantext import clean_ascii_chars +from calibre.utils.icu import lower +from calibre.ebooks.metadata.book.base import Metadata + +BASE_URL = 'http://isbndb.com/api/books.xml?access_key=%s&page_number=1&results=subjects,authors,texts&' + + +class ISBNDB(Source): + + name = 'ISBNDB' + description = _('Downloads metadata from isbndb.com') + + capabilities = frozenset(['identify']) + touched_fields = frozenset(['title', 'authors', + 'identifier:isbn', 'comments', 'publisher']) + supports_gzip_transfer_encoding = True + # Shortcut, since we have no cached cover URLS + cached_cover_url_is_reliable = False + + options = ( + Option('isbndb_key', 'string', None, _('IsbnDB key:'), + _('To use isbndb.com you have to sign up for a free account' + 'at isbndb.com and get an access key.')), + ) + + config_help_message = '<p>'+_('To use metadata from isbndb.com you must sign' + ' up for a free account and get an isbndb key and enter it below.' + ' Instructions to get the key are ' + '<a href="http://isbndb.com/docs/api/30-keys.html">here</a>.') + + + def __init__(self, *args, **kwargs): + Source.__init__(self, *args, **kwargs) + + prefs = self.prefs + prefs.defaults['key_migrated'] = False + prefs.defaults['isbndb_key'] = None + + if not prefs['key_migrated']: + prefs['key_migrated'] = True + try: + from calibre.customize.ui import config + key = config['plugin_customization']['IsbnDB'] + prefs['isbndb_key'] = key + except: + pass + + @property + def isbndb_key(self): + return self.prefs['isbndb_key'] + + def is_configured(self): + return self.isbndb_key is not None + + def create_query(self, title=None, authors=None, identifiers={}): # {{{ + base_url = BASE_URL%self.isbndb_key + isbn = check_isbn(identifiers.get('isbn', None)) + q = '' + if isbn is not None: + q = 'index1=isbn&value1='+isbn + elif title or authors: + tokens = [] + title_tokens = list(self.get_title_tokens(title)) + tokens += title_tokens + author_tokens = self.get_author_tokens(authors, + only_first_author=True) + tokens += author_tokens + tokens = [quote(t.encode('utf-8') if isinstance(t, unicode) else t) for t in tokens] + q = '+'.join(tokens) + q = 'index1=combined&value1='+q + + if not q: + return None + if isinstance(q, unicode): + q = q.encode('utf-8') + return base_url + q + # }}} + + def identify(self, log, result_queue, abort, title=None, authors=None, # {{{ + identifiers={}, timeout=30): + if not self.is_configured(): + return + query = self.create_query(title=title, authors=authors, + identifiers=identifiers) + if not query: + err = 'Insufficient metadata to construct query' + log.error(err) + return err + + results = [] + try: + results = self.make_query(query, abort, title=title, authors=authors, + identifiers=identifiers, timeout=timeout) + except: + err = 'Failed to make query to ISBNDb, aborting.' + log.exception(err) + return err + + if not results and identifiers.get('isbn', False) and title and authors and \ + not abort.is_set(): + return self.identify(log, result_queue, abort, title=title, + authors=authors, timeout=timeout) + + for result in results: + self.clean_downloaded_metadata(result) + result_queue.put(result) + + def parse_feed(self, feed, seen, orig_title, orig_authors, identifiers): + + def tostring(x): + if x is None: + return '' + return etree.tostring(x, method='text', encoding=unicode).strip() + + orig_isbn = identifiers.get('isbn', None) + title_tokens = list(self.get_title_tokens(orig_title)) + author_tokens = list(self.get_author_tokens(orig_authors)) + results = [] + + def ismatch(title, authors): + authors = lower(' '.join(authors)) + title = lower(title) + match = not title_tokens + for t in title_tokens: + if lower(t) in title: + match = True + break + amatch = not author_tokens + for a in author_tokens: + if lower(a) in authors: + amatch = True + break + if not author_tokens: amatch = True + return match and amatch + + bl = feed.find('BookList') + if bl is None: + err = tostring(etree.find('errormessage')) + raise ValueError('ISBNDb query failed:' + err) + total_results = int(bl.get('total_results')) + shown_results = int(bl.get('shown_results')) + for bd in bl.xpath('.//BookData'): + isbn = check_isbn(bd.get('isbn13', bd.get('isbn', None))) + if not isbn: + continue + if orig_isbn and isbn != orig_isbn: + continue + title = tostring(bd.find('Title')) + if not title: + continue + authors = [] + for au in bd.xpath('.//Authors/Person'): + au = tostring(au) + if au: + if ',' in au: + ln, _, fn = au.partition(',') + au = fn.strip() + ' ' + ln.strip() + authors.append(au) + if not authors: + continue + comments = tostring(bd.find('Summary')) + if not comments: + # Require comments, since without them the result is useless + # anyway + continue + id_ = (title, tuple(authors)) + if id_ in seen: + continue + seen.add(id_) + if not ismatch(title, authors): + continue + publisher = tostring(bd.find('PublisherText')) + if not publisher: publisher = None + if publisher and 'audio' in publisher.lower(): + continue + mi = Metadata(title, authors) + mi.isbn = isbn + mi.publisher = publisher + mi.comments = comments + results.append(mi) + return total_results, shown_results, results + + def make_query(self, q, abort, title=None, authors=None, identifiers={}, + max_pages=10, timeout=30): + page_num = 1 + parser = etree.XMLParser(recover=True, no_network=True) + br = self.browser + + seen = set() + + candidates = [] + total_found = 0 + while page_num <= max_pages and not abort.is_set(): + url = q.replace('&page_number=1&', '&page_number=%d&'%page_num) + page_num += 1 + raw = br.open_novisit(url, timeout=timeout).read() + feed = etree.fromstring(xml_to_unicode(clean_ascii_chars(raw), + strip_encoding_pats=True)[0], parser=parser) + total, found, results = self.parse_feed( + feed, seen, title, authors, identifiers) + total_found += found + candidates += results + if total_found >= total or len(candidates) > 9: + break + + return candidates + # }}} + +if __name__ == '__main__': + # To run these test use: + # calibre-debug -e src/calibre/ebooks/metadata/sources/isbndb.py + from calibre.ebooks.metadata.sources.test import (test_identify_plugin, + title_test, authors_test) + test_identify_plugin(ISBNDB.name, + [ + + + ( + {'title':'Great Gatsby', + 'authors':['Fitzgerald']}, + [title_test('The great gatsby', exact=True), + authors_test(['F. Scott Fitzgerald'])] + ), + + ( + {'title': 'Flatland', 'authors':['Abbott']}, + [title_test('Flatland', exact=False)] + ), + ]) + diff --git a/src/calibre/ebooks/metadata/sources/openlibrary.py b/src/calibre/ebooks/metadata/sources/openlibrary.py new file mode 100644 index 0000000000..4645d2a18a --- /dev/null +++ b/src/calibre/ebooks/metadata/sources/openlibrary.py @@ -0,0 +1,35 @@ +#!/usr/bin/env python +# vim:fileencoding=UTF-8:ts=4:sw=4:sta:et:sts=4:ai +from __future__ import (unicode_literals, division, absolute_import, + print_function) + +__license__ = 'GPL v3' +__copyright__ = '2011, Kovid Goyal <kovid@kovidgoyal.net>' +__docformat__ = 'restructuredtext en' + +from calibre.ebooks.metadata.sources.base import Source + +class OpenLibrary(Source): + + name = 'Open Library' + description = _('Downloads covers from The Open Library') + + capabilities = frozenset(['cover']) + + OPENLIBRARY = 'http://covers.openlibrary.org/b/isbn/%s-L.jpg?default=false' + + def download_cover(self, log, result_queue, abort, + title=None, authors=None, identifiers={}, timeout=30): + if 'isbn' not in identifiers: + return + isbn = identifiers['isbn'] + br = self.browser + try: + ans = br.open_novisit(self.OPENLIBRARY%isbn, timeout=timeout).read() + result_queue.put((self, ans)) + except Exception as e: + if callable(getattr(e, 'getcode', None)) and e.getcode() == 404: + log.error('No cover for ISBN: %r found'%isbn) + else: + log.exception('Failed to download cover for ISBN:', isbn) + diff --git a/src/calibre/ebooks/metadata/sources/overdrive.py b/src/calibre/ebooks/metadata/sources/overdrive.py new file mode 100755 index 0000000000..f52b1f423b --- /dev/null +++ b/src/calibre/ebooks/metadata/sources/overdrive.py @@ -0,0 +1,468 @@ +#!/usr/bin/env python +from __future__ import (unicode_literals, division, absolute_import, + print_function) + +__license__ = 'GPL v3' +__copyright__ = '2011, Kovid Goyal kovid@kovidgoyal.net' +__docformat__ = 'restructuredtext en' + +''' +Fetch metadata using Overdrive Content Reserve +''' +import re, random, mechanize, copy, json +from threading import RLock +from Queue import Queue, Empty + +from lxml import html +from lxml.html import soupparser + +from calibre.ebooks.metadata import check_isbn +from calibre.ebooks.metadata.sources.base import Source, Option +from calibre.ebooks.metadata.book.base import Metadata +from calibre.ebooks.chardet import xml_to_unicode +from calibre.library.comments import sanitize_comments_html + +ovrdrv_data_cache = {} +cache_lock = RLock() +base_url = 'http://search.overdrive.com/' + + +class OverDrive(Source): + + name = 'Overdrive' + description = _('Downloads metadata and covers from Overdrive\'s Content Reserve') + + capabilities = frozenset(['identify', 'cover']) + touched_fields = frozenset(['title', 'authors', 'tags', 'pubdate', + 'comments', 'publisher', 'identifier:isbn', 'series', 'series_index', + 'language', 'identifier:overdrive']) + has_html_comments = True + supports_gzip_transfer_encoding = False + cached_cover_url_is_reliable = True + + options = ( + Option('get_full_metadata', 'bool', True, + _('Download all metadata (slow)'), + _('Enable this option to gather all metadata available from Overdrive.')), + ) + + config_help_message = '<p>'+_('Additional metadata can be taken from Overdrive\'s book detail' + ' page. This includes a limited set of tags used by libraries, comments, language,' + ' and the ebook ISBN. Collecting this data is disabled by default due to the extra' + ' time required. Check the download all metadata option below to' + ' enable downloading this data.') + + def identify(self, log, result_queue, abort, title=None, authors=None, # {{{ + identifiers={}, timeout=30): + ovrdrv_id = identifiers.get('overdrive', None) + isbn = identifiers.get('isbn', None) + + br = self.browser + ovrdrv_data = self.to_ovrdrv_data(br, log, title, authors, ovrdrv_id) + if ovrdrv_data: + title = ovrdrv_data[8] + authors = ovrdrv_data[6] + mi = Metadata(title, authors) + self.parse_search_results(ovrdrv_data, mi) + if ovrdrv_id is None: + ovrdrv_id = ovrdrv_data[7] + + if self.prefs['get_full_metadata']: + self.get_book_detail(br, ovrdrv_data[1], mi, ovrdrv_id, log) + + if isbn is not None: + self.cache_isbn_to_identifier(isbn, ovrdrv_id) + + result_queue.put(mi) + + return None + # }}} + + def download_cover(self, log, result_queue, abort, # {{{ + title=None, authors=None, identifiers={}, timeout=30): + cached_url = self.get_cached_cover_url(identifiers) + if cached_url is None: + log.info('No cached cover found, running identify') + rq = Queue() + self.identify(log, rq, abort, title=title, authors=authors, + identifiers=identifiers) + if abort.is_set(): + return + results = [] + while True: + try: + results.append(rq.get_nowait()) + except Empty: + break + results.sort(key=self.identify_results_keygen( + title=title, authors=authors, identifiers=identifiers)) + for mi in results: + cached_url = self.get_cached_cover_url(mi.identifiers) + if cached_url is not None: + break + if cached_url is None: + log.info('No cover found') + return + + if abort.is_set(): + return + + ovrdrv_id = identifiers.get('overdrive', None) + br = self.browser + req = mechanize.Request(cached_url) + if ovrdrv_id is not None: + referer = self.get_base_referer()+'ContentDetails-Cover.htm?ID='+ovrdrv_id + req.add_header('referer', referer) + + log('Downloading cover from:', cached_url) + try: + cdata = br.open_novisit(req, timeout=timeout).read() + result_queue.put((self, cdata)) + except: + log.exception('Failed to download cover from:', cached_url) + # }}} + + def get_cached_cover_url(self, identifiers): # {{{ + url = None + ovrdrv_id = identifiers.get('overdrive', None) + if ovrdrv_id is None: + isbn = identifiers.get('isbn', None) + if isbn is not None: + ovrdrv_id = self.cached_isbn_to_identifier(isbn) + if ovrdrv_id is not None: + url = self.cached_identifier_to_cover_url(ovrdrv_id) + + return url + # }}} + + def get_base_referer(self): # to be used for passing referrer headers to cover download + choices = [ + 'http://overdrive.chipublib.org/82DC601D-7DDE-4212-B43A-09D821935B01/10/375/en/', + 'http://emedia.clevnet.org/9D321DAD-EC0D-490D-BFD8-64AE2C96ECA8/10/241/en/', + 'http://singapore.lib.overdrive.com/F11D55BE-A917-4D63-8111-318E88B29740/10/382/en/', + 'http://ebooks.nypl.org/20E48048-A377-4520-BC43-F8729A42A424/10/257/en/', + 'http://spl.lib.overdrive.com/5875E082-4CB2-4689-9426-8509F354AFEF/10/335/en/' + ] + return choices[random.randint(0, len(choices)-1)] + + def format_results(self, reserveid, od_title, subtitle, series, publisher, creators, thumbimage, worldcatlink, formatid): + fix_slashes = re.compile(r'\\/') + thumbimage = fix_slashes.sub('/', thumbimage) + worldcatlink = fix_slashes.sub('/', worldcatlink) + cover_url = re.sub('(?P<img>(Ima?g(eType-)?))200', '\g<img>100', thumbimage) + social_metadata_url = base_url+'TitleInfo.aspx?ReserveID='+reserveid+'&FormatID='+formatid + series_num = '' + if not series: + if subtitle: + title = od_title+': '+subtitle + else: + title = od_title + else: + title = od_title + m = re.search("([0-9]+$)", subtitle) + if m: + series_num = float(m.group(1)) + return [cover_url, social_metadata_url, worldcatlink, series, series_num, publisher, creators, reserveid, title] + + def safe_query(self, br, query_url, post=''): + ''' + The query must be initialized by loading an empty search results page + this page attempts to set a cookie that Mechanize doesn't like + copy the cookiejar to a separate instance and make a one-off request with the temp cookiejar + ''' + goodcookies = br._ua_handlers['_cookies'].cookiejar + clean_cj = mechanize.CookieJar() + cookies_to_copy = [] + for cookie in goodcookies: + copied_cookie = copy.deepcopy(cookie) + cookies_to_copy.append(copied_cookie) + for copied_cookie in cookies_to_copy: + clean_cj.set_cookie(copied_cookie) + + if post: + br.open_novisit(query_url, post) + else: + br.open_novisit(query_url) + + br.set_cookiejar(clean_cj) + + def overdrive_search(self, br, log, q, title, author): + # re-initialize the cookiejar to so that it's clean + clean_cj = mechanize.CookieJar() + br.set_cookiejar(clean_cj) + q_query = q+'default.aspx/SearchByKeyword' + q_init_search = q+'SearchResults.aspx' + # get first author as string - convert this to a proper cleanup function later + author_tokens = list(self.get_author_tokens(author, + only_first_author=True)) + title_tokens = list(self.get_title_tokens(title, + strip_joiners=False, strip_subtitle=True)) + + if len(title_tokens) >= len(author_tokens): + initial_q = ' '.join(title_tokens) + xref_q = '+'.join(author_tokens) + else: + initial_q = ' '.join(author_tokens) + xref_q = '+'.join(title_tokens) + #log.error('Initial query is %s'%initial_q) + #log.error('Cross reference query is %s'%xref_q) + + q_xref = q+'SearchResults.svc/GetResults?iDisplayLength=50&sSearch='+xref_q + query = '{"szKeyword":"'+initial_q+'"}' + + # main query, requires specific Content Type header + req = mechanize.Request(q_query) + req.add_header('Content-Type', 'application/json; charset=utf-8') + br.open_novisit(req, query) + + # initiate the search without messing up the cookiejar + self.safe_query(br, q_init_search) + + # get the search results object + results = False + while results == False: + xreq = mechanize.Request(q_xref) + xreq.add_header('X-Requested-With', 'XMLHttpRequest') + xreq.add_header('Referer', q_init_search) + xreq.add_header('Accept', 'application/json, text/javascript, */*') + raw = br.open_novisit(xreq).read() + for m in re.finditer(ur'"iTotalDisplayRecords":(?P<displayrecords>\d+).*?"iTotalRecords":(?P<totalrecords>\d+)', raw): + if int(m.group('displayrecords')) >= 1: + results = True + elif int(m.group('totalrecords')) >= 1: + if int(m.group('totalrecords')) >= 100: + if xref_q.find('+') != -1: + xref_tokens = xref_q.split('+') + xref_q = xref_tokens[0] + #log.error('xref_q is '+xref_q) + else: + xref_q = '' + xref_q = '' + q_xref = q+'SearchResults.svc/GetResults?iDisplayLength=50&sSearch='+xref_q + elif int(m.group('totalrecords')) == 0: + return '' + + return self.sort_ovrdrv_results(raw, log, title, title_tokens, author, author_tokens) + + + def sort_ovrdrv_results(self, raw, log, title=None, title_tokens=None, author=None, author_tokens=None, ovrdrv_id=None): + close_matches = [] + raw = re.sub('.*?\[\[(?P<content>.*?)\]\].*', '[[\g<content>]]', raw) + results = json.loads(raw) + #log.error('raw results are:'+str(results)) + # The search results are either from a keyword search or a multi-format list from a single ID, + # sort through the results for closest match/format + if results: + for reserveid, od_title, subtitle, edition, series, publisher, format, formatid, creators, \ + thumbimage, shortdescription, worldcatlink, excerptlink, creatorfile, sorttitle, \ + availabletolibrary, availabletoretailer, relevancyrank, unknown1, unknown2, unknown3 in results: + #log.error("this record's title is "+od_title+", subtitle is "+subtitle+", author[s] are "+creators+", series is "+series) + if ovrdrv_id is not None and int(formatid) in [1, 50, 410, 900]: + #log.error('overdrive id is not None, searching based on format type priority') + return self.format_results(reserveid, od_title, subtitle, series, publisher, + creators, thumbimage, worldcatlink, formatid) + else: + if creators: + creators = creators.split(', ') + # if an exact match in a preferred format occurs + if ((author and creators and creators[0] == author[0]) or (not author and not creators)) and od_title.lower() == title.lower() and int(formatid) in [1, 50, 410, 900] and thumbimage: + return self.format_results(reserveid, od_title, subtitle, series, publisher, + creators, thumbimage, worldcatlink, formatid) + else: + close_title_match = False + close_author_match = False + for token in title_tokens: + if od_title.lower().find(token.lower()) != -1: + close_title_match = True + else: + close_title_match = False + break + for author in creators: + for token in author_tokens: + if author.lower().find(token.lower()) != -1: + close_author_match = True + else: + close_author_match = False + break + if close_author_match: + break + if close_title_match and close_author_match and int(formatid) in [1, 50, 410, 900] and thumbimage: + if subtitle and series: + close_matches.insert(0, self.format_results(reserveid, od_title, subtitle, series, publisher, creators, thumbimage, worldcatlink, formatid)) + else: + close_matches.append(self.format_results(reserveid, od_title, subtitle, series, publisher, creators, thumbimage, worldcatlink, formatid)) + + elif close_title_match and close_author_match and int(formatid) in [1, 50, 410, 900]: + close_matches.append(self.format_results(reserveid, od_title, subtitle, series, publisher, creators, thumbimage, worldcatlink, formatid)) + + if close_matches: + return close_matches[0] + else: + return '' + else: + return '' + + def overdrive_get_record(self, br, log, q, ovrdrv_id): + search_url = q+'SearchResults.aspx?ReserveID={'+ovrdrv_id+'}' + results_url = q+'SearchResults.svc/GetResults?sEcho=1&iColumns=18&sColumns=ReserveID%2CTitle%2CSubtitle%2CEdition%2CSeries%2CPublisher%2CFormat%2CFormatID%2CCreators%2CThumbImage%2CShortDescription%2CWorldCatLink%2CExcerptLink%2CCreatorFile%2CSortTitle%2CAvailableToLibrary%2CAvailableToRetailer%2CRelevancyRank&iDisplayStart=0&iDisplayLength=10&sSearch=&bEscapeRegex=true&iSortingCols=1&iSortCol_0=17&sSortDir_0=asc' + + # re-initialize the cookiejar to so that it's clean + clean_cj = mechanize.CookieJar() + br.set_cookiejar(clean_cj) + # get the base url to set the proper session cookie + br.open_novisit(q) + + # initialize the search + self.safe_query(br, search_url) + + # get the results + req = mechanize.Request(results_url) + req.add_header('X-Requested-With', 'XMLHttpRequest') + req.add_header('Referer', search_url) + req.add_header('Accept', 'application/json, text/javascript, */*') + raw = br.open_novisit(req) + raw = str(list(raw)) + clean_cj = mechanize.CookieJar() + br.set_cookiejar(clean_cj) + return self.sort_ovrdrv_results(raw, log, None, None, None, ovrdrv_id) + + + def find_ovrdrv_data(self, br, log, title, author, isbn, ovrdrv_id=None): + q = base_url + if ovrdrv_id is None: + return self.overdrive_search(br, log, q, title, author) + else: + return self.overdrive_get_record(br, log, q, ovrdrv_id) + + + + def to_ovrdrv_data(self, br, log, title=None, author=None, ovrdrv_id=None): + ''' + Takes either a title/author combo or an Overdrive ID. One of these + two must be passed to this function. + ''' + if ovrdrv_id is not None: + with cache_lock: + ans = ovrdrv_data_cache.get(ovrdrv_id, None) + if ans: + return ans + elif ans is False: + return None + else: + ovrdrv_data = self.find_ovrdrv_data(br, log, title, author, ovrdrv_id) + else: + try: + ovrdrv_data = self.find_ovrdrv_data(br, log, title, author, ovrdrv_id) + except: + import traceback + traceback.print_exc() + ovrdrv_data = None + with cache_lock: + ovrdrv_data_cache[ovrdrv_id] = ovrdrv_data if ovrdrv_data else False + + return ovrdrv_data if ovrdrv_data else False + + + def parse_search_results(self, ovrdrv_data, mi): + ''' + Parse the formatted search results from the initial Overdrive query and + add the values to the metadta. + + The list object has these values: + [cover_url[0], social_metadata_url[1], worldcatlink[2], series[3], series_num[4], + publisher[5], creators[6], reserveid[7], title[8]] + + ''' + ovrdrv_id = ovrdrv_data[7] + mi.set_identifier('overdrive', ovrdrv_id) + + if len(ovrdrv_data[3]) > 1: + mi.series = ovrdrv_data[3] + if ovrdrv_data[4]: + try: + mi.series_index = float(ovrdrv_data[4]) + except: + pass + mi.publisher = ovrdrv_data[5] + mi.authors = ovrdrv_data[6] + mi.title = ovrdrv_data[8] + cover_url = ovrdrv_data[0] + if cover_url: + self.cache_identifier_to_cover_url(ovrdrv_id, + cover_url) + + + def get_book_detail(self, br, metadata_url, mi, ovrdrv_id, log): + try: + raw = br.open_novisit(metadata_url).read() + except Exception, e: + if callable(getattr(e, 'getcode', None)) and \ + e.getcode() == 404: + return False + raise + raw = xml_to_unicode(raw, strip_encoding_pats=True, + resolve_entities=True)[0] + try: + root = soupparser.fromstring(raw) + except: + return False + + pub_date = root.xpath("//div/label[@id='ctl00_ContentPlaceHolder1_lblPubDate']/text()") + lang = root.xpath("//div/label[@id='ctl00_ContentPlaceHolder1_lblLanguage']/text()") + subjects = root.xpath("//div/label[@id='ctl00_ContentPlaceHolder1_lblSubjects']/text()") + ebook_isbn = root.xpath("//td/label[@id='ctl00_ContentPlaceHolder1_lblIdentifier']/text()") + desc = root.xpath("//div/label[@id='ctl00_ContentPlaceHolder1_lblDescription']/ancestor::div[1]") + + if pub_date: + from calibre.utils.date import parse_date + try: + mi.pubdate = parse_date(pub_date[0].strip()) + except: + pass + if lang: + lang = lang[0].strip().lower() + mi.language = {'english':'en', 'french':'fr', 'german':'de', + 'spanish':'es'}.get(lang, None) + + if ebook_isbn: + #print "ebook isbn is "+str(ebook_isbn[0]) + isbn = check_isbn(ebook_isbn[0].strip()) + if isbn: + self.cache_isbn_to_identifier(isbn, ovrdrv_id) + mi.isbn = isbn + if subjects: + mi.tags = [tag.strip() for tag in subjects[0].split(',')] + + if desc: + desc = desc[0] + desc = html.tostring(desc, method='html', encoding=unicode).strip() + # remove all attributes from tags + desc = re.sub(r'<([a-zA-Z0-9]+)\s[^>]+>', r'<\1>', desc) + # Remove comments + desc = re.sub(r'(?s)<!--.*?-->', '', desc) + mi.comments = sanitize_comments_html(desc) + + return None + + +if __name__ == '__main__': + # To run these test use: + # calibre-debug -e src/calibre/ebooks/metadata/sources/overdrive.py + from calibre.ebooks.metadata.sources.test import (test_identify_plugin, + title_test, authors_test) + test_identify_plugin(OverDrive.name, + [ + + ( + {'title':'Foundation and Earth', + 'authors':['Asimov']}, + [title_test('Foundation and Earth', exact=True), + authors_test(['Isaac Asimov'])] + ), + + ( + {'title': 'Elephants', 'authors':['Agatha']}, + [title_test('Elephants Can Remember', exact=False), + authors_test(['Agatha Christie'])] + ), + ]) diff --git a/src/calibre/ebooks/metadata/sources/test.py b/src/calibre/ebooks/metadata/sources/test.py index 3b41e69d40..c55f963003 100644 --- a/src/calibre/ebooks/metadata/sources/test.py +++ b/src/calibre/ebooks/metadata/sources/test.py @@ -11,18 +11,21 @@ import os, tempfile, time from Queue import Queue, Empty from threading import Event - from calibre.customize.ui import metadata_plugins -from calibre import prints +from calibre import prints, sanitize_file_name2 from calibre.ebooks.metadata import check_isbn -from calibre.ebooks.metadata.sources.base import create_log +from calibre.ebooks.metadata.sources.base import (create_log, + get_cached_cover_urls, msprefs) def isbn_test(isbn): isbn_ = check_isbn(isbn) def test(mi): misbn = check_isbn(mi.isbn) - return misbn and misbn == isbn_ + if misbn and misbn == isbn_: + return True + prints('ISBN test failed. Expected: \'%s\' found \'%s\''%(isbn_, misbn)) + return False return test @@ -32,12 +35,129 @@ def title_test(title, exact=False): def test(mi): mt = mi.title.lower() - return (exact and mt == title) or \ - (not exact and title in mt) + if (exact and mt == title) or \ + (not exact and title in mt): + return True + prints('Title test failed. Expected: \'%s\' found \'%s\''%(title, mt)) + return False return test -def test_identify_plugin(name, tests): +def authors_test(authors): + authors = set([x.lower() for x in authors]) + + def test(mi): + au = set([x.lower() for x in mi.authors]) + if msprefs['swap_author_names']: + def revert_to_fn_ln(a): + if ',' not in a: + return a + parts = a.split(',', 1) + t = parts[-1] + parts = parts[:-1] + parts.insert(0, t) + return ' '.join(parts) + + au = set([revert_to_fn_ln(x) for x in au]) + + if au == authors: + return True + prints('Author test failed. Expected: \'%s\' found \'%s\''%(authors, au)) + return False + + return test + +def series_test(series, series_index): + series = series.lower() + + def test(mi): + ms = mi.series.lower() if mi.series else '' + if (ms == series) and (series_index == mi.series_index): + return True + if mi.series: + prints('Series test failed. Expected: \'%s [%d]\' found \'%s[%d]\''% \ + (series, series_index, ms, mi.series_index)) + else: + prints('Series test failed. Expected: \'%s [%d]\' found no series'% \ + (series, series_index)) + return False + + return test + +def init_test(tdir_name): + tdir = tempfile.gettempdir() + lf = os.path.join(tdir, tdir_name.replace(' ', '')+'_identify_test.txt') + log = create_log(open(lf, 'wb')) + abort = Event() + return tdir, lf, log, abort + +def test_identify(tests): # {{{ + ''' + :param tests: List of 2-tuples. Each two tuple is of the form (args, + test_funcs). args is a dict of keyword arguments to pass to + the identify method. test_funcs are callables that accept a + Metadata object and return True iff the object passes the + test. + ''' + from calibre.ebooks.metadata.sources.identify import identify + + tdir, lf, log, abort = init_test('Full Identify') + prints('Log saved to', lf) + + times = [] + + for kwargs, test_funcs in tests: + log('#'*80) + log('### Running test with:', kwargs) + log('#'*80) + prints('Running test with:', kwargs) + args = (log, abort) + start_time = time.time() + results = identify(*args, **kwargs) + total_time = time.time() - start_time + times.append(total_time) + if not results: + prints('identify failed to find any results') + break + + prints('Found', len(results), 'matches:', end=' ') + prints('Smaller relevance means better match') + + for i, mi in enumerate(results): + prints('*'*30, 'Relevance:', i, '*'*30) + prints(mi) + prints('\nCached cover URLs :', + [x[0].name for x in get_cached_cover_urls(mi)]) + prints('*'*75, '\n\n') + + possibles = [] + for mi in results: + test_failed = False + for tfunc in test_funcs: + if not tfunc(mi): + test_failed = True + break + if not test_failed: + possibles.append(mi) + + if not possibles: + prints('ERROR: No results that passed all tests were found') + prints('Log saved to', lf) + raise SystemExit(1) + + if results[0] is not possibles[0]: + prints('Most relevant result failed the tests') + raise SystemExit(1) + + log('\n\n') + + prints('Average time per query', sum(times)/len(times)) + + prints('Full log is at:', lf) + +# }}} + +def test_identify_plugin(name, tests): # {{{ ''' :param name: Plugin name :param tests: List of 2-tuples. Each two tuple is of the form (args, @@ -52,11 +172,9 @@ def test_identify_plugin(name, tests): plugin = x break prints('Testing the identify function of', plugin.name) + prints('Using extra headers:', plugin.browser.addheaders) - tdir = tempfile.gettempdir() - lf = os.path.join(tdir, plugin.name.replace(' ', '')+'_identify_test.txt') - log = create_log(open(lf, 'wb')) - abort = Event() + tdir, lf, log, abort = init_test(plugin.name) prints('Log saved to', lf) times = [] @@ -80,13 +198,21 @@ def test_identify_plugin(name, tests): except Empty: break - prints('Found', len(results), 'matches:') + prints('Found', len(results), 'matches:', end=' ') + prints('Smaller relevance means better match') - for mi in results: + results.sort(key=plugin.identify_results_keygen( + title=kwargs.get('title', None), authors=kwargs.get('authors', + None), identifiers=kwargs.get('identifiers', {}))) + + for i, mi in enumerate(results): + prints('*'*30, 'Relevance:', i, '*'*30) prints(mi) - prints('\n\n') + prints('\nCached cover URL :', + plugin.get_cached_cover_url(mi.identifiers)) + prints('*'*75, '\n\n') - match_found = None + possibles = [] for mi in results: test_failed = False for tfunc in test_funcs: @@ -94,16 +220,53 @@ def test_identify_plugin(name, tests): test_failed = True break if not test_failed: - match_found = mi - break + possibles.append(mi) - if match_found is None: + if not possibles: prints('ERROR: No results that passed all tests were found') prints('Log saved to', lf) raise SystemExit(1) + good = [x for x in possibles if plugin.test_fields(x) is + None] + if not good: + prints('Failed to find', plugin.test_fields(possibles[0])) + raise SystemExit(1) + + if results[0] is not possibles[0]: + prints('Most relevant result failed the tests') + raise SystemExit(1) + + if 'cover' in plugin.capabilities: + rq = Queue() + mi = results[0] + plugin.download_cover(log, rq, abort, title=mi.title, + authors=mi.authors, identifiers=mi.identifiers) + results = [] + while True: + try: + results.append(rq.get_nowait()) + except Empty: + break + if not results: + prints('Cover download failed') + raise SystemExit(1) + cdata = results[0] + cover = os.path.join(tdir, plugin.name.replace(' ', + '')+'-%s-cover.jpg'%sanitize_file_name2(mi.title.replace(' ', + '_'))) + with open(cover, 'wb') as f: + f.write(cdata[-1]) + + prints('Cover downloaded to:', cover) + + if len(cdata[-1]) < 10240: + prints('Downloaded cover too small') + raise SystemExit(1) + prints('Average time per query', sum(times)/len(times)) if os.stat(lf).st_size > 10: - prints('There were some errors, see log', lf) + prints('There were some errors/warnings, see log', lf) +# }}} diff --git a/src/calibre/ebooks/metadata/toc.py b/src/calibre/ebooks/metadata/toc.py index 10d45186de..17f99150be 100644 --- a/src/calibre/ebooks/metadata/toc.py +++ b/src/calibre/ebooks/metadata/toc.py @@ -147,7 +147,7 @@ class TOC(list): if path and os.access(path, os.R_OK): try: self.read_ncx_toc(path) - except Exception, err: + except Exception as err: print 'WARNING: Invalid NCX file:', err return cwd = os.path.abspath(self.base_path) diff --git a/src/calibre/ebooks/metadata/txtz.py b/src/calibre/ebooks/metadata/txtz.py deleted file mode 100644 index ae6efb4838..0000000000 --- a/src/calibre/ebooks/metadata/txtz.py +++ /dev/null @@ -1,38 +0,0 @@ -# -*- coding: utf-8 -*- - -__license__ = 'GPL v3' -__copyright__ = '2011, John Schember <john@nachtimwald.com>' - -''' -Read meta information from TXT files -''' - -import os - -from cStringIO import StringIO - -from calibre.ebooks.metadata import MetaInformation -from calibre.ebooks.metadata.opf2 import OPF, metadata_to_opf -from calibre.ptempfile import TemporaryDirectory -from calibre.utils.zipfile import ZipFile, safe_replace - -def get_metadata(stream, extract_cover=True): - ''' - Return metadata as a L{MetaInfo} object - ''' - mi = MetaInformation(_('Unknown'), [_('Unknown')]) - stream.seek(0) - - with TemporaryDirectory('_untxtz_mdata') as tdir: - try: - zf = ZipFile(stream) - zf.extract('metadata.opf', tdir) - with open(os.path.join(tdir, 'metadata.opf'), 'rb') as opff: - mi = OPF(opff).to_book_metadata() - except: - return mi - return mi - -def set_metadata(stream, mi): - opf = StringIO(metadata_to_opf(mi)) - safe_replace(stream, 'metadata.opf', opf) diff --git a/src/calibre/ebooks/metadata/worker.py b/src/calibre/ebooks/metadata/worker.py index d059d7e34c..c335cc4c13 100644 --- a/src/calibre/ebooks/metadata/worker.py +++ b/src/calibre/ebooks/metadata/worker.py @@ -222,7 +222,7 @@ class SaveWorker(Thread): if isbytestring(fpath): fpath = fpath.decode(filesystem_encoding) formats[fmt.lower()] = fpath - data[i] = [opf, cpath, formats] + data[i] = [opf, cpath, formats, mi.last_modified.isoformat()] return data def run(self): diff --git a/src/calibre/ebooks/metadata/xisbn.py b/src/calibre/ebooks/metadata/xisbn.py index aaeb1c6b98..56156c034e 100644 --- a/src/calibre/ebooks/metadata/xisbn.py +++ b/src/calibre/ebooks/metadata/xisbn.py @@ -71,14 +71,32 @@ class xISBN(object): ans.add(i) return ans + def get_isbn_pool(self, isbn): + data = self.get_data(isbn) + raw = tuple(x.get('isbn') for x in data if 'isbn' in x) + isbns = [] + for x in raw: + isbns += x + isbns = frozenset(isbns) + min_year = 100000 + for x in data: + try: + year = int(x['year']) + if year < min_year: + min_year = year + except: + continue + if min_year == 100000: + min_year = None + return isbns, min_year xisbn = xISBN() if __name__ == '__main__': - import sys + import sys, pprint isbn = sys.argv[-1] - print xisbn.get_data(isbn) + print pprint.pprint(xisbn.get_data(isbn)) print print xisbn.get_associated_isbns(isbn) diff --git a/src/calibre/ebooks/mobi/debug.py b/src/calibre/ebooks/mobi/debug.py new file mode 100644 index 0000000000..9c5318a5e7 --- /dev/null +++ b/src/calibre/ebooks/mobi/debug.py @@ -0,0 +1,408 @@ +#!/usr/bin/env python +# vim:fileencoding=UTF-8:ts=4:sw=4:sta:et:sts=4:ai +from __future__ import (unicode_literals, division, absolute_import, + print_function) + +__license__ = 'GPL v3' +__copyright__ = '2011, Kovid Goyal <kovid@kovidgoyal.net>' +__docformat__ = 'restructuredtext en' + +import struct, datetime +from calibre.utils.date import utc_tz +from calibre.ebooks.mobi.langcodes import main_language, sub_language + +class PalmDOCAttributes(object): + + class Attr(object): + + def __init__(self, name, field, val): + self.name = name + self.val = val & field + + def __str__(self): + return '%s: %s'%(self.name, bool(self.val)) + + def __init__(self, raw): + self.val = struct.unpack(b'<H', raw)[0] + self.attributes = [] + for name, field in [('Read Only', 0x02), ('Dirty AppInfoArea', 0x04), + ('Backup this database', 0x08), + ('Okay to install newer over existing copy, if present on PalmPilot', 0x10), + ('Force the PalmPilot to reset after this database is installed', 0x12), + ('Don\'t allow copy of file to be beamed to other Pilot', + 0x14)]: + self.attributes.append(PalmDOCAttributes.Attr(name, field, + self.val)) + + def __str__(self): + attrs = '\n\t'.join([str(x) for x in self.attributes]) + return 'PalmDOC Attributes: %s\n\t%s'%(bin(self.val), attrs) + +class PalmDB(object): + + def __init__(self, raw): + self.raw = raw + + if self.raw.startswith(b'TPZ'): + raise ValueError('This is a Topaz file') + + self.name = self.raw[:32].replace(b'\x00', b'') + self.attributes = PalmDOCAttributes(self.raw[32:34]) + self.version = struct.unpack(b'>H', self.raw[34:36])[0] + + palm_epoch = datetime.datetime(1904, 1, 1, tzinfo=utc_tz) + self.creation_date_raw = struct.unpack(b'>I', self.raw[36:40])[0] + self.creation_date = (palm_epoch + + datetime.timedelta(seconds=self.creation_date_raw)) + self.modification_date_raw = struct.unpack(b'>I', self.raw[40:44])[0] + self.modification_date = (palm_epoch + + datetime.timedelta(seconds=self.modification_date_raw)) + self.last_backup_date_raw = struct.unpack(b'>I', self.raw[44:48])[0] + self.last_backup_date = (palm_epoch + + datetime.timedelta(seconds=self.last_backup_date_raw)) + self.modification_number = struct.unpack(b'>I', self.raw[48:52])[0] + self.app_info_id = self.raw[52:56] + self.sort_info_id = self.raw[56:60] + self.type = self.raw[60:64] + self.creator = self.raw[64:68] + self.ident = self.type + self.creator + if self.ident not in (b'BOOKMOBI', b'TEXTREAD'): + raise ValueError('Unknown book ident: %r'%self.ident) + self.uid_seed = self.raw[68:72] + self.next_rec_list_id = self.raw[72:76] + + self.number_of_records, = struct.unpack(b'>H', self.raw[76:78]) + + def __str__(self): + ans = ['*'*20 + ' PalmDB Header '+ '*'*20] + ans.append('Name: %r'%self.name) + ans.append(str(self.attributes)) + ans.append('Version: %s'%self.version) + ans.append('Creation date: %s (%s)'%(self.creation_date.isoformat(), + self.creation_date_raw)) + ans.append('Modification date: %s (%s)'%(self.modification_date.isoformat(), + self.modification_date_raw)) + ans.append('Backup date: %s (%s)'%(self.last_backup_date.isoformat(), + self.last_backup_date_raw)) + ans.append('Modification number: %s'%self.modification_number) + ans.append('App Info ID: %r'%self.app_info_id) + ans.append('Sort Info ID: %r'%self.sort_info_id) + ans.append('Type: %r'%self.type) + ans.append('Creator: %r'%self.creator) + ans.append('UID seed: %r'%self.uid_seed) + ans.append('Next record list id: %r'%self.next_rec_list_id) + ans.append('Number of records: %s'%self.number_of_records) + + return '\n'.join(ans) + +class Record(object): + + def __init__(self, raw, header): + self.offset, self.flags, self.uid = header + self.raw = raw + + @property + def header(self): + return 'Offset: %d Flags: %d UID: %d'%(self.offset, self.flags, + self.uid) + +class EXTHRecord(object): + + def __init__(self, type_, data): + self.type = type_ + self.data = data + self.name = { + 1 : 'DRM Server id', + 2 : 'DRM Commerce id', + 3 : 'DRM ebookbase book id', + 100 : 'author', + 101 : 'publisher', + 102 : 'imprint', + 103 : 'description', + 104 : 'isbn', + 105 : 'subject', + 106 : 'publishingdate', + 107 : 'review', + 108 : 'contributor', + 109 : 'rights', + 110 : 'subjectcode', + 111 : 'type', + 112 : 'source', + 113 : 'asin', + 114 : 'versionnumber', + 115 : 'sample', + 116 : 'startreading', + 117 : 'adult', + 118 : 'retailprice', + 119 : 'retailpricecurrency', + 201 : 'coveroffset', + 202 : 'thumboffset', + 203 : 'hasfakecover', + 204 : 'Creator Software', + 205 : 'Creator Major Version', # '>I' + 206 : 'Creator Minor Version', # '>I' + 207 : 'Creator Build Number', # '>I' + 208 : 'watermark', + 209 : 'tamper_proof_keys', + 300 : 'fontsignature', + 301 : 'clippinglimit', # percentage '>B' + 402 : 'publisherlimit', + 404 : 'TTS flag', # '>B' 1 - TTS disabled 0 - TTS enabled + 501 : 'cdetype', # 4 chars (PDOC or EBOK) + 502 : 'lastupdatetime', + 503 : 'updatedtitle', + }.get(self.type, repr(self.type)) + + if self.name in ('coveroffset', 'thumboffset', 'hasfakecover', + 'Creator Major Version', 'Creator Minor Version', + 'Creator Build Number', 'Creator Software', 'startreading'): + self.data, = struct.unpack(b'>I', self.data) + + def __str__(self): + return '%s (%d): %r'%(self.name, self.type, self.data) + +class EXTHHeader(object): + + def __init__(self, raw): + self.raw = raw + if not self.raw.startswith(b'EXTH'): + raise ValueError('EXTH header does not start with EXTH') + self.length, = struct.unpack(b'>I', self.raw[4:8]) + self.count, = struct.unpack(b'>I', self.raw[8:12]) + + pos = 12 + self.records = [] + for i in xrange(self.count): + pos = self.read_record(pos) + + def read_record(self, pos): + type_, length = struct.unpack(b'>II', self.raw[pos:pos+8]) + data = self.raw[(pos+8):(pos+length)] + self.records.append(EXTHRecord(type_, data)) + return pos + length + + def __str__(self): + ans = ['*'*20 + ' EXTH Header '+ '*'*20] + ans.append('EXTH header length: %d'%self.length) + ans.append('Number of EXTH records: %d'%self.count) + ans.append('EXTH records...') + for r in self.records: + ans.append(str(r)) + return '\n'.join(ans) + + +class MOBIHeader(object): + + def __init__(self, record0): + self.raw = record0.raw + + self.compression_raw = self.raw[:2] + self.compression = {1: 'No compression', 2: 'PalmDoc compression', + 17480: 'HUFF/CDIC compression'}.get(struct.unpack(b'>H', + self.compression_raw)[0], + repr(self.compression_raw)) + self.unused = self.raw[2:4] + self.text_length, = struct.unpack(b'>I', self.raw[4:8]) + self.number_of_text_records, self.text_record_size = \ + struct.unpack(b'>HH', self.raw[8:12]) + self.encryption_type_raw, = struct.unpack(b'>H', self.raw[12:14]) + self.encryption_type = {0: 'No encryption', + 1: 'Old mobipocket encryption', + 2:'Mobipocket encryption'}.get(self.encryption_type_raw, + repr(self.encryption_type_raw)) + self.unknown = self.raw[14:16] + + self.identifier = self.raw[16:20] + if self.identifier != b'MOBI': + raise ValueError('Identifier %r unknown'%self.identifier) + + self.length, = struct.unpack(b'>I', self.raw[20:24]) + self.type_raw, = struct.unpack(b'>I', self.raw[24:28]) + self.type = { + 2 : 'Mobipocket book', + 3 : 'PalmDOC book', + 4 : 'Audio', + 257 : 'News', + 258 : 'News Feed', + 259 : 'News magazine', + 513 : 'PICS', + 514 : 'Word', + 515 : 'XLS', + 516 : 'PPT', + 517 : 'TEXT', + 518 : 'HTML', + }.get(self.type_raw, repr(self.type_raw)) + + self.encoding_raw, = struct.unpack(b'>I', self.raw[28:32]) + self.encoding = { + 1252 : 'cp1252', + 65001: 'utf-8', + }.get(self.encoding_raw, repr(self.encoding_raw)) + self.uid = self.raw[32:36] + self.file_version = struct.unpack(b'>I', self.raw[36:40]) + self.reserved = self.raw[40:48] + self.secondary_index_record, = struct.unpack(b'>I', self.raw[48:52]) + self.reserved2 = self.raw[52:80] + self.first_non_book_record, = struct.unpack(b'>I', self.raw[80:84]) + self.fullname_offset, = struct.unpack(b'>I', self.raw[84:88]) + self.fullname_length, = struct.unpack(b'>I', self.raw[88:92]) + self.locale_raw, = struct.unpack(b'>I', self.raw[92:96]) + langcode = self.locale_raw + langid = langcode & 0xFF + sublangid = (langcode >> 10) & 0xFF + self.language = main_language.get(langid, 'ENGLISH') + self.sublanguage = sub_language.get(sublangid, 'NEUTRAL') + + self.input_language = self.raw[96:100] + self.output_langauage = self.raw[100:104] + self.min_version, = struct.unpack(b'>I', self.raw[104:108]) + self.first_image_index, = struct.unpack(b'>I', self.raw[108:112]) + self.huffman_record_offset, = struct.unpack(b'>I', self.raw[112:116]) + self.huffman_record_count, = struct.unpack(b'>I', self.raw[116:120]) + self.unknown2 = self.raw[120:128] + self.exth_flags, = struct.unpack(b'>I', self.raw[128:132]) + self.has_exth = bool(self.exth_flags & 0x40) + self.has_drm_data = self.length >= 174 and len(self.raw) >= 180 + if self.has_drm_data: + self.unknown3 = self.raw[132:164] + self.drm_offset, = struct.unpack(b'>I', self.raw[164:168]) + self.drm_count, = struct.unpack(b'>I', self.raw[168:172]) + self.drm_size, = struct.unpack(b'>I', self.raw[172:176]) + self.drm_flags = bin(struct.unpack(b'>I', self.raw[176:180])[0]) + self.has_extra_data_flags = self.length >= 232 and len(self.raw) >= 232+16 + self.has_fcis_flis = False + if self.has_extra_data_flags: + self.unknown4 = self.raw[180:192] + self.first_content_record, self.last_content_record = \ + struct.unpack(b'>HH', self.raw[192:196]) + self.unknown5, = struct.unpack(b'>I', self.raw[196:200]) + (self.fcis_number, self.fcis_count, self.flis_number, + self.flis_count) = struct.unpack(b'>IIII', + self.raw[200:216]) + self.unknown6 = self.raw[216:240] + self.extra_data_flags = bin(struct.unpack(b'>I', + self.raw[240:244])[0]) + self.primary_index_record, = struct.unpack(b'>I', + self.raw[244:248]) + + if self.has_exth: + self.exth_offset = 16 + self.length + + self.exth = EXTHHeader(self.raw[self.exth_offset:]) + + self.end_of_exth = self.exth_offset + self.exth.length + self.bytes_after_exth = self.fullname_offset - self.end_of_exth + + def __str__(self): + ans = ['*'*20 + ' MOBI Header '+ '*'*20] + ans.append('Compression: %s'%self.compression) + ans.append('Unused: %r'%self.unused) + ans.append('Number of text records: %d'%self.number_of_text_records) + ans.append('Text record size: %d'%self.text_record_size) + ans.append('Encryption: %s'%self.encryption_type) + ans.append('Unknown: %r'%self.unknown) + ans.append('Identifier: %r'%self.identifier) + ans.append('Header length: %d'% self.length) + ans.append('Type: %s'%self.type) + ans.append('Encoding: %s'%self.encoding) + ans.append('UID: %r'%self.uid) + ans.append('File version: %d'%self.file_version) + ans.append('Reserved: %r'%self.reserved) + ans.append('Secondary index record: %d (null val: %d)'%( + self.secondary_index_record, 0xffffffff)) + ans.append('Reserved2: %r'%self.reserved2) + ans.append('First non-book record: %d'% self.first_non_book_record) + ans.append('Full name offset: %d'%self.fullname_offset) + ans.append('Full name length: %d bytes'%self.fullname_length) + ans.append('Langcode: %r'%self.locale_raw) + ans.append('Language: %s'%self.language) + ans.append('Sub language: %s'%self.sublanguage) + ans.append('Input language: %r'%self.input_language) + ans.append('Output language: %r'%self.output_langauage) + ans.append('Min version: %d'%self.min_version) + ans.append('First Image index: %d'%self.first_image_index) + ans.append('Huffman record offset: %d'%self.huffman_record_offset) + ans.append('Huffman record count: %d'%self.huffman_record_count) + ans.append('Unknown2: %r'%self.unknown2) + ans.append('EXTH flags: %r (%s)'%(self.exth_flags, self.has_exth)) + if self.has_drm_data: + ans.append('Unknown3: %r'%self.unknown3) + ans.append('DRM Offset: %s'%self.drm_offset) + ans.append('DRM Count: %s'%self.drm_count) + ans.append('DRM Size: %s'%self.drm_size) + ans.append('DRM Flags: %r'%self.drm_flags) + if self.has_extra_data_flags: + ans.append('Unknown4: %r'%self.unknown4) + ans.append('First content record: %d'% self.first_content_record) + ans.append('Last content record: %d'% self.last_content_record) + ans.append('Unknown5: %d'% self.unknown5) + ans.append('FCIS number: %d'% self.fcis_number) + ans.append('FCIS count: %d'% self.fcis_count) + ans.append('FLIS number: %d'% self.flis_number) + ans.append('FLIS count: %d'% self.flis_count) + ans.append('Unknown6: %r'% self.unknown6) + ans.append('Extra data flags: %r'%self.extra_data_flags) + ans.append('Primary index record: %d'%self.primary_index_record) + + ans = '\n'.join(ans) + + if self.has_exth: + ans += '\n\n' + str(self.exth) + ans += '\n\nBytes after EXTH: %d'%self.bytes_after_exth + + ans += '\nNumber of bytes after full name: %d' % (len(self.raw) - (self.fullname_offset + + self.fullname_length)) + + ans += '\nRecord 0 length: %d'%len(self.raw) + return ans + +class MOBIFile(object): + + def __init__(self, stream): + self.raw = stream.read() + + self.palmdb = PalmDB(self.raw[:78]) + + self.record_headers = [] + self.records = [] + for i in xrange(self.palmdb.number_of_records): + pos = 78 + i * 8 + offset, a1, a2, a3, a4 = struct.unpack(b'>LBBBB', self.raw[pos:pos+8]) + flags, val = a1, a2 << 16 | a3 << 8 | a4 + self.record_headers.append((offset, flags, val)) + + def section(section_number): + if section_number == self.palmdb.number_of_records - 1: + end_off = len(self.raw) + else: + end_off = self.record_headers[section_number + 1][0] + off = self.record_headers[section_number][0] + return self.raw[off:end_off] + + for i in range(self.palmdb.number_of_records): + self.records.append(Record(section(i), self.record_headers[i])) + + self.mobi_header = MOBIHeader(self.records[0]) + + + def print_header(self): + print (str(self.palmdb).encode('utf-8')) + print () + print ('Record headers:') + for i, r in enumerate(self.records): + print ('%6d. %s'%(i, r.header)) + + print () + print (str(self.mobi_header).encode('utf-8')) + +def inspect_mobi(path_or_stream): + stream = (path_or_stream if hasattr(path_or_stream, 'read') else + open(path_or_stream, 'rb')) + f = MOBIFile(stream) + f.print_header() + +if __name__ == '__main__': + import sys + f = MOBIFile(open(sys.argv[1], 'rb')) + f.print_header() + diff --git a/src/calibre/ebooks/mobi/mobiml.py b/src/calibre/ebooks/mobi/mobiml.py index 189739986d..2275552c15 100644 --- a/src/calibre/ebooks/mobi/mobiml.py +++ b/src/calibre/ebooks/mobi/mobiml.py @@ -102,6 +102,7 @@ class MobiMLizer(object): def __call__(self, oeb, context): oeb.logger.info('Converting XHTML to Mobipocket markup...') self.oeb = oeb + self.log = self.oeb.logger self.opts = context self.profile = profile = context.dest self.fnums = fnums = dict((v, k) for k, v in profile.fnums.items()) @@ -118,6 +119,10 @@ class MobiMLizer(object): del oeb.guide['cover'] item = oeb.manifest.hrefs[href] if item.spine_position is not None: + self.log.warn('Found an HTML cover,', item.href, 'removing it.', + 'If you find some content missing from the output MOBI, it ' + 'is because you misidentified the HTML cover in the input ' + 'document') oeb.spine.remove(item) if item.media_type in OEB_DOCS: self.oeb.manifest.remove(item) @@ -206,7 +211,11 @@ class MobiMLizer(object): vspace = bstate.vpadding + bstate.vmargin bstate.vpadding = bstate.vmargin = 0 if tag not in TABLE_TAGS: - wrapper.attrib['height'] = self.mobimlize_measure(vspace) + if tag in ('ul', 'ol') and vspace > 0: + wrapper.addprevious(etree.Element(XHTML('div'), + height=self.mobimlize_measure(vspace))) + else: + wrapper.attrib['height'] = self.mobimlize_measure(vspace) para.attrib['width'] = self.mobimlize_measure(indent) elif tag == 'table' and vspace > 0: vspace = int(round(vspace / self.profile.fbase)) @@ -288,9 +297,11 @@ class MobiMLizer(object): if id_: # Keep anchors so people can use display:none # to generate hidden TOCs + tail = elem.tail elem.clear() elem.text = None elem.set('id', id_) + elem.tail = tail else: return tag = barename(elem.tag) @@ -300,7 +311,8 @@ class MobiMLizer(object): istates.append(istate) left = 0 display = style['display'] - isblock = not display.startswith('inline') + isblock = (not display.startswith('inline') and style['display'] != + 'none') isblock = isblock and style['float'] == 'none' isblock = isblock and tag != 'br' if isblock: @@ -454,9 +466,9 @@ class MobiMLizer(object): text = COLLAPSE.sub(' ', elem.text) valign = style['vertical-align'] not_baseline = valign in ('super', 'sub', 'text-top', - 'text-bottom') or ( + 'text-bottom', 'top', 'bottom') or ( isinstance(valign, (float, int)) and abs(valign) != 0) - issup = valign in ('super', 'text-top') or ( + issup = valign in ('super', 'text-top', 'top') or ( isinstance(valign, (float, int)) and valign > 0) vtag = 'sup' if issup else 'sub' if not_baseline and not ignore_valign and tag not in NOT_VTAGS and not isblock: @@ -475,6 +487,7 @@ class MobiMLizer(object): parent = bstate.para if bstate.inline is None else bstate.inline if parent is not None: vtag = etree.SubElement(parent, XHTML(vtag)) + vtag = etree.SubElement(vtag, XHTML('small')) # Add anchors for child in vbstate.body: if child is not vbstate.para: @@ -486,6 +499,10 @@ class MobiMLizer(object): vtag.append(child) return + if tag == 'blockquote': + old_mim = self.opts.mobi_ignore_margins + self.opts.mobi_ignore_margins = False + if text or tag in CONTENT_TAGS or tag in NESTABLE_TAGS: self.mobimlize_content(tag, text, bstate, istates) for child in elem: @@ -501,6 +518,8 @@ class MobiMLizer(object): if tail: self.mobimlize_content(tag, tail, bstate, istates) + if tag == 'blockquote': + self.opts.mobi_ignore_margins = old_mim if bstate.content and style['page-break-after'] in PAGE_BREAKS: bstate.pbreak = True diff --git a/src/calibre/ebooks/mobi/reader.py b/src/calibre/ebooks/mobi/reader.py index 9c52a18691..934e8476d2 100644 --- a/src/calibre/ebooks/mobi/reader.py +++ b/src/calibre/ebooks/mobi/reader.py @@ -18,8 +18,9 @@ from calibre import xml_entity_to_unicode, CurrentDir, entity_to_unicode, \ replace_entities from calibre.utils.filenames import ascii_filename from calibre.utils.date import parse_date +from calibre.utils.cleantext import clean_ascii_chars from calibre.ptempfile import TemporaryDirectory -from calibre.ebooks import DRMError +from calibre.ebooks import DRMError, unit_convert from calibre.ebooks.chardet import ENCODING_PATS from calibre.ebooks.mobi import MobiError from calibre.ebooks.mobi.huffcdic import HuffReader @@ -252,11 +253,15 @@ class MobiReader(object): .italic { font-style: italic } + .underline { text-decoration: underline } + .mbp_pagebreak { page-break-after: always; margin: 0; display: block } ''') self.tag_css_rules = {} + self.left_margins = {} + self.text_indents = {} if hasattr(filename_or_stream, 'read'): stream = filename_or_stream @@ -323,6 +328,7 @@ class MobiReader(object): self.cleanup_html() self.log.debug('Parsing HTML...') + self.processed_html = clean_ascii_chars(self.processed_html) try: root = html.fromstring(self.processed_html) if len(root.xpath('//html')) > 5: @@ -565,9 +571,21 @@ class MobiReader(object): elif tag.tag == 'img': tag.set('width', width) else: - styles.append('text-indent: %s' % self.ensure_unit(width)) + ewidth = self.ensure_unit(width) + styles.append('text-indent: %s' % ewidth) + try: + ewidth_val = unit_convert(ewidth, 12, 500, 166) + self.text_indents[tag] = ewidth_val + except: + pass if width.startswith('-'): styles.append('margin-left: %s' % self.ensure_unit(width[1:])) + try: + ewidth_val = unit_convert(ewidth[1:], 12, 500, 166) + self.left_margins[tag] = ewidth_val + except: + pass + if attrib.has_key('align'): align = attrib.pop('align').strip() if align: @@ -585,6 +603,9 @@ class MobiReader(object): elif tag.tag == 'i': tag.tag = 'span' tag.attrib['class'] = 'italic' + elif tag.tag == 'u': + tag.tag = 'span' + tag.attrib['class'] = 'underline' elif tag.tag == 'b': tag.tag = 'span' tag.attrib['class'] = 'bold' @@ -659,6 +680,34 @@ class MobiReader(object): if hasattr(parent, 'remove'): parent.remove(tag) + def get_left_whitespace(self, tag): + + def whitespace(tag): + lm = ti = 0.0 + if tag.tag == 'p': + ti = unit_convert('1.5em', 12, 500, 166) + if tag.tag == 'blockquote': + lm = unit_convert('2em', 12, 500, 166) + lm = self.left_margins.get(tag, lm) + ti = self.text_indents.get(tag, ti) + try: + lm = float(lm) + except: + lm = 0.0 + try: + ti = float(ti) + except: + ti = 0.0 + return lm + ti + + parent = tag + ans = 0.0 + while parent is not None: + ans += whitespace(parent) + parent = parent.getparent() + + return ans + def create_opf(self, htmlfile, guide=None, root=None): mi = getattr(self.book_header.exth, 'mi', self.embedded_mi) if mi is None: @@ -714,6 +763,7 @@ class MobiReader(object): ent_pat = re.compile(r'&(\S+?);') if elems: tocobj = TOC() + found = False reached = False for x in root.iter(): if x == elems[-1]: @@ -728,15 +778,45 @@ class MobiReader(object): except: text = '' text = ent_pat.sub(entity_to_unicode, text) - tocobj.add_item(toc.partition('#')[0], href[1:], + item = tocobj.add_item(toc.partition('#')[0], href[1:], text) - if reached and x.get('class', None) == 'mbp_pagebreak': + item.left_space = int(self.get_left_whitespace(x)) + found = True + if reached and found and x.get('class', None) == 'mbp_pagebreak': break if tocobj is not None: + tocobj = self.structure_toc(tocobj) opf.set_toc(tocobj) return opf, ncx_manifest_entry + def structure_toc(self, toc): + indent_vals = set() + for item in toc: + indent_vals.add(item.left_space) + if len(indent_vals) > 6 or len(indent_vals) < 2: + # Too many or too few levels, give up + return toc + indent_vals = sorted(indent_vals) + + last_found = [None for i in indent_vals] + + newtoc = TOC() + + def find_parent(level): + candidates = last_found[:level] + for x in reversed(candidates): + if x is not None: + return x + return newtoc + + for item in toc: + level = indent_vals.index(item.left_space) + parent = find_parent(level) + last_found[level] = parent.add_item(item.href, item.fragment, + item.text) + + return newtoc def sizeof_trailing_entries(self, data): def sizeof_trailing_entry(ptr, psize): @@ -767,7 +847,8 @@ class MobiReader(object): def extract_text(self): self.log.debug('Extracting text...') - text_sections = [self.text_section(i) for i in range(1, self.book_header.records + 1)] + text_sections = [self.text_section(i) for i in range(1, + min(self.book_header.records + 1, len(self.sections)))] processed_records = list(range(0, self.book_header.records + 1)) self.mobi_html = '' diff --git a/src/calibre/ebooks/mobi/writer.py b/src/calibre/ebooks/mobi/writer.py index 2be699e525..2ca62f0dea 100644 --- a/src/calibre/ebooks/mobi/writer.py +++ b/src/calibre/ebooks/mobi/writer.py @@ -7,8 +7,6 @@ __copyright__ = '2008, Marshall T. Vandegrift <llasram@gmail.cam> and \ Kovid Goyal <kovid@kovidgoyal.net>' from collections import defaultdict -from itertools import count -from itertools import izip import random import re from struct import pack @@ -282,8 +280,8 @@ class Serializer(object): buffer.write('="') self.serialize_text(val, quot=True) buffer.write('"') + buffer.write('>') if elem.text or len(elem) > 0: - buffer.write('>') if elem.text: self.anchor_offset = None self.serialize_text(elem.text) @@ -292,9 +290,7 @@ class Serializer(object): if child.tail: self.anchor_offset = None self.serialize_text(child.tail) - buffer.write('</%s>' % tag) - else: - buffer.write('/>') + buffer.write('</%s>' % tag) def serialize_text(self, text, quot=False): text = text.replace('&', '&') @@ -312,10 +308,11 @@ class Serializer(object): if href not in id_offsets: self.logger.warn('Hyperlink target %r not found' % href) href, _ = urldefrag(href) - ioff = self.id_offsets[href] - for hoff in hoffs: - buffer.seek(hoff) - buffer.write('%010d' % ioff) + if href in self.id_offsets: + ioff = self.id_offsets[href] + for hoff in hoffs: + buffer.seek(hoff) + buffer.write('%010d' % ioff) class MobiWriter(object): COLLAPSE_RE = re.compile(r'[ \t\r\n\v]+') @@ -1098,7 +1095,7 @@ class MobiWriter(object): nodeCountValue = 0x80 if nodeCountValue == 0 else nodeCountValue tbSequence += chr(nodeCountValue) else : - tbSequence += decint(0x00, DECINT_FORWARD) # arg1 = 0x80 + tbSequence += decint(0x00, DECINT_FORWARD) # arg1 = 0x80 tbSequence += decint(len(tbSequence) + 1, DECINT_FORWARD) # len @@ -1188,7 +1185,7 @@ class MobiWriter(object): toc = self._oeb.toc nodes = list(toc.iter())[1:] toc_conforms = True - for (i, child) in enumerate(nodes) : + for child in nodes: if child.klass == "periodical" and child.depth() != 3 or \ child.klass == "section" and child.depth() != 2 or \ child.klass == "article" and child.depth() != 1 : @@ -1512,7 +1509,7 @@ class MobiWriter(object): record0.write(exth) record0.write(title) record0 = record0.getvalue() - self._records[0] = record0 + ('\0' * (2452 - len(record0))) + self._records[0] = record0 + ('\0' * (1024*8)) def _build_exth(self): oeb = self._oeb @@ -1631,8 +1628,8 @@ class MobiWriter(object): self._write(title, pack('>HHIIIIII', 0, 0, now, now, 0, 0, 0, 0), 'BOOK', 'MOBI', pack('>IIH', nrecords, 0, nrecords)) offset = self._tell() + (8 * nrecords) + 2 - for id, record in izip(count(), self._records): - self._write(pack('>I', offset), '\0', pack('>I', id)[1:]) + for i, record in enumerate(self._records): + self._write(pack('>I', offset), '\0', pack('>I', 2*i)[1:]) offset += len(record) self._write('\0\0') @@ -1644,14 +1641,14 @@ class MobiWriter(object): self._oeb.log('Generating INDX ...') self._primary_index_record = None - # Build the NCXEntries and INDX + # Build the NCXEntries and INDX indxt, indxt_count, indices, last_name = self._generate_indxt() if last_name is None: self._oeb.log.warn('Input document has no TOC. No index generated.') return - # Assemble the INDX0[0] and INDX1[0] output streams + # Assemble the INDX0[0] and INDX1[0] output streams indx1 = StringIO() indx1.write('INDX'+pack('>I', 0xc0)) # header length @@ -2310,10 +2307,8 @@ class MobiWriter(object): parentIndex = sectionParent.parentIndex self._write_section_node(indxt, indices, sectionParent.myCtocMapIndex, index, offset, length, c, firstArticle, lastArticle, parentIndex) - last_name = "%04X"%c - # articles - for (i, article) in enumerate(list(sectionParent.articles)) : + for article in list(sectionParent.articles): index = article.myCtocMapIndex offset = article.startAddress length = article.articleLength @@ -2413,7 +2408,6 @@ class MobiWriter(object): # <navPoint> Article(s) child.depth() = 1 # <navpoint> Section 2 - documentType = "unknown" sectionIndices = [] sectionParents = [] currentSection = 0 # Starting section number @@ -2421,7 +2415,6 @@ class MobiWriter(object): indxt, indices, c = StringIO(), StringIO(), 0 indices.write('IDXT') - c = 0 last_name = None # 'book', 'periodical' or None @@ -2449,8 +2442,8 @@ class MobiWriter(object): if self.opts.verbose > 3 : self._oeb.logger.info("unknown document type %12.12s \tdepth:%d" % (child.title, child.depth()) ) - # Original code starts here - # test first node for depth/class + # Original code starts here + # test first node for depth/class entries = list(toc.iter())[1:] for (i, child) in enumerate(entries): if not child.title or not child.title.strip(): diff --git a/src/calibre/ebooks/odt/input.py b/src/calibre/ebooks/odt/input.py index 1184148e80..e724acb981 100644 --- a/src/calibre/ebooks/odt/input.py +++ b/src/calibre/ebooks/odt/input.py @@ -7,6 +7,8 @@ __docformat__ = 'restructuredtext en' Convert an ODT file into a Open Ebook ''' import os + +from lxml import etree from odf.odf2xhtml import ODF2XHTML from calibre import CurrentDir, walk @@ -23,7 +25,51 @@ class Extract(ODF2XHTML): with open(name, 'wb') as f: f.write(data) - def __call__(self, stream, odir): + def filter_css(self, html, log): + root = etree.fromstring(html) + style = root.xpath('//*[local-name() = "style" and @type="text/css"]') + if style: + style = style[0] + css = style.text + if css: + style.text, sel_map = self.do_filter_css(css) + for x in root.xpath('//*[@class]'): + extra = [] + orig = x.get('class') + for cls in orig.split(): + extra.extend(sel_map.get(cls, [])) + if extra: + x.set('class', orig + ' ' + ' '.join(extra)) + html = etree.tostring(root, encoding='utf-8', + xml_declaration=True) + return html + + def do_filter_css(self, css): + from cssutils import parseString + from cssutils.css import CSSRule + sheet = parseString(css) + rules = list(sheet.cssRules.rulesOfType(CSSRule.STYLE_RULE)) + sel_map = {} + count = 0 + for r in rules: + # Check if we have only class selectors for this rule + nc = [x for x in r.selectorList if not + x.selectorText.startswith('.')] + if len(r.selectorList) > 1 and not nc: + # Replace all the class selectors with a single class selector + # This will be added to the class attribute of all elements + # that have one of these selectors. + replace_name = 'c_odt%d'%count + count += 1 + for sel in r.selectorList: + s = sel.selectorText[1:] + if s not in sel_map: + sel_map[s] = [] + sel_map[s].append(replace_name) + r.selectorText = '.'+replace_name + return sheet.cssText, sel_map + + def __call__(self, stream, odir, log): from calibre.utils.zipfile import ZipFile from calibre.ebooks.metadata.meta import get_metadata from calibre.ebooks.metadata.opf2 import OPFCreator @@ -32,13 +78,17 @@ class Extract(ODF2XHTML): if not os.path.exists(odir): os.makedirs(odir) with CurrentDir(odir): - print 'Extracting ODT file...' + log('Extracting ODT file...') html = self.odf2xhtml(stream) # A blanket img specification like this causes problems - # with EPUB output as the contaiing element often has + # with EPUB output as the containing element often has # an absolute height and width set that is larger than # the available screen real estate html = html.replace('img { width: 100%; height: 100%; }', '') + try: + html = self.filter_css(html, log) + except: + log.exception('Failed to filter CSS, conversion may be slow') with open('index.xhtml', 'wb') as f: f.write(html.encode('utf-8')) zf = ZipFile(stream, 'r') @@ -67,7 +117,7 @@ class ODTInput(InputFormatPlugin): def convert(self, stream, options, file_ext, log, accelerators): - return Extract()(stream, '.') + return Extract()(stream, '.', log) def postprocess_book(self, oeb, opts, log): # Fix <p><div> constructs as the asinine epubchecker complains diff --git a/src/calibre/ebooks/oeb/base.py b/src/calibre/ebooks/oeb/base.py index ccc452f1f8..db83fca496 100644 --- a/src/calibre/ebooks/oeb/base.py +++ b/src/calibre/ebooks/oeb/base.py @@ -8,23 +8,18 @@ __copyright__ = '2008, Marshall T. Vandegrift <llasram@gmail.com>' __docformat__ = 'restructuredtext en' import os, re, uuid, logging -from mimetypes import types_map from collections import defaultdict from itertools import count from urlparse import urldefrag, urlparse, urlunparse, urljoin from urllib import unquote as urlunquote from lxml import etree, html -from cssutils import CSSParser, parseString, parseStyle, replaceUrls -from cssutils.css import CSSRule - -import calibre -from calibre.constants import filesystem_encoding +from calibre.constants import filesystem_encoding, __version__ from calibre.translations.dynamic import translate -from calibre.ebooks.chardet import xml_to_unicode +from calibre.ebooks.chardet import xml_to_unicode, strip_encoding_declarations from calibre.ebooks.oeb.entitydefs import ENTITYDEFS from calibre.ebooks.conversion.preprocess import CSSPreProcessor -from calibre import isbytestring +from calibre import isbytestring, as_unicode, get_types_map RECOVER_PARSER = etree.XMLParser(recover=True, no_network=True) @@ -179,6 +174,9 @@ def rewrite_links(root, link_repl_func, resolve_base_href=False): If the ``link_repl_func`` returns None, the attribute or tag text will be removed completely. ''' + from cssutils import parseString, parseStyle, replaceUrls, log + log.setLevel(logging.WARN) + if resolve_base_href: resolve_base_href(root) for el, attrib, link, pos in iterlinks(root, find_links_in_css=False): @@ -229,7 +227,11 @@ def rewrite_links(root, link_repl_func, resolve_base_href=False): if 'style' in el.attrib: text = el.attrib['style'] if _css_url_re.search(text) is not None: - stext = parseStyle(text) + try: + stext = parseStyle(text) + except: + # Parsing errors are raised by cssutils + continue for p in stext.getProperties(all=True): v = p.cssValue if v.CSS_VALUE_LIST == v.cssValueType: @@ -244,7 +246,7 @@ def rewrite_links(root, link_repl_func, resolve_base_href=False): el.attrib['style'] = repl - +types_map = get_types_map() EPUB_MIME = types_map['.epub'] XHTML_MIME = types_map['.xhtml'] CSS_MIME = types_map['.css'] @@ -444,22 +446,23 @@ class NullContainer(object): class DirContainer(object): """Filesystem directory container.""" - def __init__(self, path, log): + def __init__(self, path, log, ignore_opf=False): self.log = log if isbytestring(path): path = path.decode(filesystem_encoding) + self.opfname = None ext = os.path.splitext(path)[1].lower() if ext == '.opf': self.opfname = os.path.basename(path) self.rootdir = os.path.dirname(path) return self.rootdir = path - for path in self.namelist(): - ext = os.path.splitext(path)[1].lower() - if ext == '.opf': - self.opfname = path - return - self.opfname = None + if not ignore_opf: + for path in self.namelist(): + ext = os.path.splitext(path)[1].lower() + if ext == '.opf': + self.opfname = path + return def read(self, path): if path is None: @@ -639,7 +642,7 @@ class Metadata(object): return unicode(self.value).encode('ascii', 'xmlcharrefreplace') def __unicode__(self): - return unicode(self.value) + return as_unicode(self.value) def to_opf1(self, dcmeta=None, xmeta=None, nsrmap={}): attrib = {} @@ -827,10 +830,30 @@ class Manifest(object): return None return etree.fromstring(data, parser=RECOVER_PARSER) + def clean_word_doc(self, data): + prefixes = [] + for match in re.finditer(r'xmlns:(\S+?)=".*?microsoft.*?"', data): + prefixes.append(match.group(1)) + if prefixes: + self.oeb.log.warn('Found microsoft markup, cleaning...') + # Remove empty tags as they are not rendered by browsers + # but can become renderable HTML tags like <p/> if the + # document is parsed by an HTML parser + pat = re.compile( + r'<(%s):([a-zA-Z0-9]+)[^>/]*?></\1:\2>'%('|'.join(prefixes)), + re.DOTALL) + data = pat.sub('', data) + pat = re.compile( + r'<(%s):([a-zA-Z0-9]+)[^>/]*?/>'%('|'.join(prefixes))) + data = pat.sub('', data) + return data + def _parse_xhtml(self, data): + orig_data = data self.oeb.log.debug('Parsing', self.href, '...') # Convert to Unicode and normalize line endings data = self.oeb.decode(data) + data = strip_encoding_declarations(data) data = self.oeb.html_preprocessor(data) # There could be null bytes in data if it had � entities in it data = data.replace('\0', '') @@ -861,13 +884,13 @@ class Manifest(object): def first_pass(data): try: data = etree.fromstring(data, parser=parser) - except etree.XMLSyntaxError, err: + except etree.XMLSyntaxError as err: self.oeb.log.exception('Initial parse failed:') repl = lambda m: ENTITYDEFS.get(m.group(1), m.group(0)) data = ENTITY_RE.sub(repl, data) try: data = etree.fromstring(data, parser=parser) - except etree.XMLSyntaxError, err: + except etree.XMLSyntaxError as err: self.oeb.logger.warn('Parsing file %r as HTML' % self.href) if err.args and err.args[0].startswith('Excessive depth'): from lxml.html import soupparser @@ -884,10 +907,29 @@ class Manifest(object): except etree.XMLSyntaxError: data = etree.fromstring(data, parser=RECOVER_PARSER) return data + try: + data = self.clean_word_doc(data) + except: + pass data = first_pass(data) + if data.tag == 'HTML': + # Lower case all tag and attribute names + data.tag = data.tag.lower() + for x in data.iterdescendants(): + try: + x.tag = x.tag.lower() + for key, val in list(x.attrib.iteritems()): + del x.attrib[key] + key = key.lower() + x.attrib[key] = val + except: + pass + # Handle weird (non-HTML/fragment) files if barename(data.tag) != 'html': + if barename(data.tag) == 'ncx': + return self._parse_xml(orig_data) self.oeb.log.warn('File %r does not appear to be (X)HTML'%self.href) nroot = etree.fromstring('<html></html>') has_body = False @@ -907,6 +949,7 @@ class Manifest(object): parent.append(child) data = nroot + # Force into the XHTML namespace if not namespace(data.tag): self.oeb.log.warn('Forcing', self.href, 'into XHTML namespace') @@ -1006,8 +1049,8 @@ class Manifest(object): # Remove hyperlinks with no content as they cause rendering # artifacts in browser based renderers - # Also remove empty <b> and <i> tags - for a in xpath(data, '//h:a[@href]|//h:i|//h:b'): + # Also remove empty <b>, <u> and <i> tags + for a in xpath(data, '//h:a[@href]|//h:i|//h:b|//h:u'): if a.get('id', None) is None and a.get('name', None) is None \ and len(a) == 0 and not a.text: remove_elem(a) @@ -1032,7 +1075,9 @@ class Manifest(object): def _parse_css(self, data): - + from cssutils.css import CSSRule + from cssutils import CSSParser, log + log.setLevel(logging.WARN) def get_style_rules_from_import(import_rule): ans = [] if not import_rule.styleSheet: @@ -1968,7 +2013,7 @@ class OEBBook(object): name='dtb:uid', content=unicode(self.uid)) etree.SubElement(head, NCX('meta'), name='dtb:depth', content=str(self.toc.depth())) - generator = ''.join(['calibre (', calibre.__version__, ')']) + generator = ''.join(['calibre (', __version__, ')']) etree.SubElement(head, NCX('meta'), name='dtb:generator', content=generator) etree.SubElement(head, NCX('meta'), diff --git a/src/calibre/ebooks/oeb/output.py b/src/calibre/ebooks/oeb/output.py index 6709141a01..816fe71abb 100644 --- a/src/calibre/ebooks/oeb/output.py +++ b/src/calibre/ebooks/oeb/output.py @@ -38,6 +38,11 @@ class OEBOutput(OutputFormatPlugin): except: self.log.exception('Something went wrong while trying to' ' workaround Nook cover bug, ignoring') + try: + self.workaround_pocketbook_cover_bug(root) + except: + self.log.exception('Something went wrong while trying to' + ' workaround Pocketbook cover bug, ignoring') raw = etree.tostring(root, pretty_print=True, encoding='utf-8', xml_declaration=True) if key == OPF_MIME: @@ -59,20 +64,43 @@ class OEBOutput(OutputFormatPlugin): def workaround_nook_cover_bug(self, root): # {{{ cov = root.xpath('//*[local-name() = "meta" and @name="cover" and' ' @content != "cover"]') + + def manifest_items_with_id(id_): + return root.xpath('//*[local-name() = "manifest"]/*[local-name() = "item" ' + ' and @id="%s"]'%id_) + if len(cov) == 1: - manpath = ('//*[local-name() = "manifest"]/*[local-name() = "item" ' - ' and @id="%s" and @media-type]') cov = cov[0] - covid = cov.get('content') - manifest_item = root.xpath(manpath%covid) - has_cover = root.xpath(manpath%'cover') - if len(manifest_item) == 1 and not has_cover and \ - manifest_item[0].get('media-type', - '').startswith('image/'): - self.log.warn('The cover image has an id != "cover". Renaming' - ' to work around Nook Color bug') - manifest_item = manifest_item[0] - manifest_item.set('id', 'cover') - cov.set('content', 'cover') + covid = cov.get('content', '') + + if covid: + manifest_item = manifest_items_with_id(covid) + if len(manifest_item) == 1 and \ + manifest_item[0].get('media-type', + '').startswith('image/'): + self.log.warn('The cover image has an id != "cover". Renaming' + ' to work around bug in Nook Color') + + import uuid + newid = str(uuid.uuid4()) + + for item in manifest_items_with_id('cover'): + item.set('id', newid) + + for x in root.xpath('//*[@idref="cover"]'): + x.set('idref', newid) + + manifest_item = manifest_item[0] + manifest_item.set('id', 'cover') + cov.set('content', 'cover') # }}} + def workaround_pocketbook_cover_bug(self, root): # {{{ + m = root.xpath('//*[local-name() = "manifest"]/*[local-name() = "item" ' + ' and @id="cover"]') + if len(m) == 1: + m = m[0] + p = m.getparent() + p.remove(m) + p.insert(0, m) + # }}} diff --git a/src/calibre/ebooks/oeb/profile.py b/src/calibre/ebooks/oeb/profile.py deleted file mode 100644 index 17408fac78..0000000000 --- a/src/calibre/ebooks/oeb/profile.py +++ /dev/null @@ -1,75 +0,0 @@ -''' -Device profiles. -''' - -__license__ = 'GPL v3' -__copyright__ = '2008, Marshall T. Vandegrift <llasram@gmail.com>' - -from itertools import izip - -FONT_SIZES = [('xx-small', 1), - ('x-small', None), - ('small', 2), - ('medium', 3), - ('large', 4), - ('x-large', 5), - ('xx-large', 6), - (None, 7)] - - -class Profile(object): - def __init__(self, width, height, dpi, fbase, fsizes): - self.width = (float(width) / dpi) * 72. - self.height = (float(height) / dpi) * 72. - self.dpi = float(dpi) - self.fbase = float(fbase) - self.fsizes = [] - for (name, num), size in izip(FONT_SIZES, fsizes): - self.fsizes.append((name, num, float(size))) - self.fnames = dict((name, sz) for name, _, sz in self.fsizes if name) - self.fnums = dict((num, sz) for _, num, sz in self.fsizes if num) - - -PROFILES = { - 'PRS505': - Profile(width=584, height=754, dpi=168.451, fbase=12, - fsizes=[7.5, 9, 10, 12, 15.5, 20, 22, 24]), - - 'MSReader': - Profile(width=480, height=652, dpi=96, fbase=13, - fsizes=[10, 11, 13, 16, 18, 20, 22, 26]), - - # Not really, but let's pretend - 'Mobipocket': - Profile(width=600, height=800, dpi=96, fbase=18, - fsizes=[14, 14, 16, 18, 20, 22, 24, 26]), - - # No clue on usable screen size; DPI should be good - 'HanlinV3': - Profile(width=584, height=754, dpi=168.451, fbase=16, - fsizes=[12, 12, 14, 16, 18, 20, 22, 24]), - - 'CybookG3': - Profile(width=600, height=800, dpi=168.451, fbase=16, - fsizes=[12, 12, 14, 16, 18, 20, 22, 24]), - - 'Kindle': - Profile(width=525, height=640, dpi=168.451, fbase=16, - fsizes=[12, 12, 14, 16, 18, 20, 22, 24]), - - 'Browser': - Profile(width=800, height=600, dpi=100.0, fbase=12, - fsizes=[5, 7, 9, 12, 13.5, 17, 20, 22, 24]) - } - - -class Context(object): - PROFILES = PROFILES - - def __init__(self, source, dest): - if source in PROFILES: - source = PROFILES[source] - if dest in PROFILES: - dest = PROFILES[dest] - self.source = source - self.dest = dest diff --git a/src/calibre/ebooks/oeb/reader.py b/src/calibre/ebooks/oeb/reader.py index 4a09e0b1d4..422252f73e 100644 --- a/src/calibre/ebooks/oeb/reader.py +++ b/src/calibre/ebooks/oeb/reader.py @@ -10,11 +10,9 @@ import sys, os, uuid, copy, re, cStringIO from itertools import izip from urlparse import urldefrag, urlparse from urllib import unquote as urlunquote -from mimetypes import guess_type from collections import defaultdict from lxml import etree -import cssutils from calibre.ebooks.oeb.base import OPF1_NS, OPF2_NS, OPF2_NSMAP, DC11_NS, \ DC_NSES, OPF, xml2text @@ -30,6 +28,7 @@ from calibre.ebooks.oeb.entitydefs import ENTITYDEFS from calibre.utils.localization import get_lang from calibre.ptempfile import TemporaryDirectory from calibre.constants import __appname__, __version__ +from calibre import guess_type __all__ = ['OEBReader'] @@ -103,8 +102,8 @@ class OEBReader(object): data = self.oeb.container.read(None) data = self.oeb.decode(data) data = XMLDECL_RE.sub('', data) - data = data.replace('http://openebook.org/namespaces/oeb-package/1.0', - OPF1_NS) + data = re.sub(r'http://openebook.org/namespaces/oeb-package/1.0(/*)', + OPF1_NS, data) try: opf = etree.fromstring(data) except etree.XMLSyntaxError: @@ -172,6 +171,7 @@ class OEBReader(object): return bad def _manifest_add_missing(self, invalid): + import cssutils manifest = self.oeb.manifest known = set(manifest.hrefs) unchecked = set(manifest.values()) @@ -191,7 +191,11 @@ class OEBReader(object): if not scheme and href not in known: new.add(href) elif item.media_type in OEB_STYLES: - for url in cssutils.getUrls(item.data): + try: + urls = list(cssutils.getUrls(item.data)) + except: + urls = [] + for url in urls: href, _ = urldefrag(url) href = item.abshref(urlnormalize(href)) scheme = urlparse(href).scheme diff --git a/src/calibre/ebooks/oeb/stylizer.py b/src/calibre/ebooks/oeb/stylizer.py index 849d161228..dc73862022 100644 --- a/src/calibre/ebooks/oeb/stylizer.py +++ b/src/calibre/ebooks/oeb/stylizer.py @@ -8,22 +8,21 @@ from __future__ import with_statement __license__ = 'GPL v3' __copyright__ = '2008, Marshall T. Vandegrift <llasram@gmail.com>' -import os -import itertools -import re -import logging -import copy +import os, itertools, re, logging, copy, unicodedata from weakref import WeakKeyDictionary from xml.dom import SyntaxErr as CSSSyntaxError import cssutils -from cssutils.css import CSSStyleRule, CSSPageRule, CSSStyleDeclaration, \ - CSSValueList, CSSFontFaceRule, cssproperties +from cssutils.css import (CSSStyleRule, CSSPageRule, CSSStyleDeclaration, + CSSValueList, CSSFontFaceRule, cssproperties) from cssutils import profile as cssprofiles from lxml import etree from lxml.cssselect import css_to_xpath, ExpressionError, SelectorSyntaxError +from calibre import force_unicode +from calibre.ebooks import unit_convert from calibre.ebooks.oeb.base import XHTML, XHTML_NS, CSS_MIME, OEB_STYLES from calibre.ebooks.oeb.base import XPNSMAP, xpath, urlnormalize -from calibre.ebooks.oeb.profile import PROFILES + +cssutils.log.setLevel(logging.WARN) _html_css_stylesheet = None @@ -99,6 +98,10 @@ class CSSSelector(etree.XPath): def __init__(self, css, namespaces=XPNSMAP): css = self.MIN_SPACE_RE.sub(r'\1', css) + if isinstance(css, unicode): + # Workaround for bug in lxml on windows/OS X that causes a massive + # memory leak with non ASCII selectors + css = css.encode('ascii', 'ignore').decode('ascii') try: path = css_to_xpath(css) except UnicodeEncodeError: # Bug in css_to_xpath @@ -119,10 +122,22 @@ class CSSSelector(etree.XPath): class Stylizer(object): STYLESHEETS = WeakKeyDictionary() - def __init__(self, tree, path, oeb, opts, profile=PROFILES['PRS505'], + def __init__(self, tree, path, oeb, opts, profile=None, extra_css='', user_css=''): self.oeb, self.opts = oeb, opts self.profile = profile + if self.profile is None: + # Use the default profile. This should really be using + # opts.output_profile, but I don't want to risk changing it, as + # doing so might well have hard to debug font size effects. + from calibre.customize.ui import output_profiles + for x in output_profiles(): + if x.short_name == 'default': + self.profile = x + break + if self.profile is None: + # Just in case the default profile is removed in the future :) + self.profile = opts.output_profile self.logger = oeb.logger item = oeb.manifest.hrefs[path] basename = os.path.basename(path) @@ -144,13 +159,22 @@ class Stylizer(object): log=logging.getLogger('calibre.css')) self.font_face_rules = [] for elem in head: - if elem.tag == XHTML('style') and elem.text \ - and elem.get('type', CSS_MIME) in OEB_STYLES: - text = XHTML_CSS_NAMESPACE + elem.text - text = oeb.css_preprocessor(text) - stylesheet = parser.parseString(text, href=cssname) - stylesheet.namespaces['h'] = XHTML_NS - stylesheets.append(stylesheet) + if (elem.tag == XHTML('style') and + elem.get('type', CSS_MIME) in OEB_STYLES): + text = elem.text if elem.text else u'' + for x in elem: + t = getattr(x, 'text', None) + if t: + text += u'\n\n' + force_unicode(t, u'utf-8') + t = getattr(x, 'tail', None) + if t: + text += u'\n\n' + force_unicode(t, u'utf-8') + if text: + text = XHTML_CSS_NAMESPACE + elem.text + text = oeb.css_preprocessor(text) + stylesheet = parser.parseString(text, href=cssname) + stylesheet.namespaces['h'] = XHTML_NS + stylesheets.append(stylesheet) elif elem.tag == XHTML('link') and elem.get('href') \ and elem.get('rel', 'stylesheet').lower() == 'stylesheet' \ and elem.get('type', CSS_MIME).lower() in OEB_STYLES: @@ -234,8 +258,18 @@ class Stylizer(object): for elem in matches: for x in elem.iter(): if x.text: - span = E.span(x.text[0]) - span.tail = x.text[1:] + punctuation_chars = [] + text = unicode(x.text) + while text: + if not unicodedata.category(text[0]).startswith('P'): + break + punctuation_chars.append(text[0]) + text = text[1:] + + special_text = u''.join(punctuation_chars) + \ + (text[0] if text else u'') + span = E.span(special_text) + span.tail = text[1:] x.text = None x.insert(0, span) self.style(span)._update_cssdict(cssdict) @@ -422,7 +456,7 @@ class Stylizer(object): class Style(object): - UNIT_RE = re.compile(r'^(-*[0-9]*[.]?[0-9]*)\s*(%|em|ex|en|px|mm|cm|in|pt|pc)$') + MS_PAT = re.compile(r'^\s*(mso-|panose-|text-underline|tab-interval)') def __init__(self, element, stylizer): self._element = element @@ -447,6 +481,8 @@ class Style(object): return css = attrib['style'].split(';') css = filter(None, (x.strip() for x in css)) + css = [x.strip() for x in css] + css = [x for x in css if self.MS_PAT.match(x) is None] try: style = CSSStyleDeclaration('; '.join(css)) except CSSSyntaxError: @@ -482,43 +518,11 @@ class Style(object): return result def _unit_convert(self, value, base=None, font=None): - ' Return value in pts' - if isinstance(value, (int, long, float)): - return value - try: - return float(value) * 72.0 / self._profile.dpi - except: - pass - result = value - m = self.UNIT_RE.match(value) - if m is not None and m.group(1): - value = float(m.group(1)) - unit = m.group(2) - if unit == '%': - if base is None: - base = self.width - result = (value / 100.0) * base - elif unit == 'px': - result = value * 72.0 / self._profile.dpi - elif unit == 'in': - result = value * 72.0 - elif unit == 'pt': - result = value - elif unit == 'em': - font = font or self.fontSize - result = value * font - elif unit in ('ex', 'en'): - # This is a hack for ex since we have no way to know - # the x-height of the font - font = font or self.fontSize - result = value * font * 0.5 - elif unit == 'pc': - result = value * 12.0 - elif unit == 'mm': - result = value * 0.04 - elif unit == 'cm': - result = value * 0.40 - return result + 'Return value in pts' + if base is None: + base = self.width + font = font or self.fontSize + return unit_convert(value, base, font, self._profile.dpi) def pt_to_px(self, value): return (self._profile.dpi / 72.0) * value diff --git a/src/calibre/ebooks/oeb/transforms/filenames.py b/src/calibre/ebooks/oeb/transforms/filenames.py index bad75b9a6f..c3c7f091c3 100644 --- a/src/calibre/ebooks/oeb/transforms/filenames.py +++ b/src/calibre/ebooks/oeb/transforms/filenames.py @@ -9,7 +9,6 @@ import posixpath from urlparse import urldefrag, urlparse from lxml import etree -import cssutils from calibre.ebooks.oeb.base import rewrite_links, urlnormalize @@ -25,6 +24,7 @@ class RenameFiles(object): # {{{ self.renamed_items_map = renamed_items_map def __call__(self, oeb, opts): + import cssutils self.log = oeb.logger self.opts = opts self.oeb = oeb diff --git a/src/calibre/ebooks/oeb/transforms/margins.py b/src/calibre/ebooks/oeb/transforms/margins.py deleted file mode 100644 index fbdf2e63fd..0000000000 --- a/src/calibre/ebooks/oeb/transforms/margins.py +++ /dev/null @@ -1,56 +0,0 @@ -#!/usr/bin/env python -# vim:fileencoding=UTF-8:ts=4:sw=4:sta:et:sts=4:ai - -__license__ = 'GPL v3' -__copyright__ = '2010, Kovid Goyal <kovid@kovidgoyal.net>' -__docformat__ = 'restructuredtext en' - - -class RemoveFakeMargins(object): - ''' - Try to detect and remove fake margins inserted by asinine ebook creation - software on each paragraph/wrapper div. Can be used only after CSS - flattening. - ''' - - def __call__(self, oeb, opts, log): - self.oeb, self.opts, self.log = oeb, opts, log - - from calibre.ebooks.oeb.base import XPath, OEB_STYLES - - stylesheet = None - for item in self.oeb.manifest: - if item.media_type.lower() in OEB_STYLES: - stylesheet = item.data - break - - if stylesheet is None: - return - - - top_level_elements = {} - second_level_elements = {} - - for x in self.oeb.spine: - root = x.data - body = XPath('//h:body')(root) - if body: - body = body[0] - - if not hasattr(body, 'xpath'): - continue - - # Check for margins on top level elements - for lb in XPath('./h:div|./h:p|./*/h:div|./*/h:p')(body): - cls = lb.get('class', '') - level = top_level_elements if lb.getparent() is body else \ - second_level_elements - if cls not in level: - level[cls] = [] - top_level_elements[cls] = [] - level[cls].append(lb) - - - def get_margins(self, stylesheet, cls): - pass - diff --git a/src/calibre/ebooks/oeb/transforms/metadata.py b/src/calibre/ebooks/oeb/transforms/metadata.py index 19c209b74d..f719ee3eb5 100644 --- a/src/calibre/ebooks/oeb/transforms/metadata.py +++ b/src/calibre/ebooks/oeb/transforms/metadata.py @@ -36,7 +36,7 @@ def meta_info_to_oeb_metadata(mi, m, log, override_input_metadata=False): m.clear('description') m.add('description', mi.comments) elif override_input_metadata: - m.clear('description') + m.clear('description') if not mi.is_null('publisher'): m.clear('publisher') m.add('publisher', mi.publisher) diff --git a/src/calibre/ebooks/oeb/transforms/page_margin.py b/src/calibre/ebooks/oeb/transforms/page_margin.py new file mode 100644 index 0000000000..d7c99d24c6 --- /dev/null +++ b/src/calibre/ebooks/oeb/transforms/page_margin.py @@ -0,0 +1,175 @@ +#!/usr/bin/env python +# vim:fileencoding=UTF-8:ts=4:sw=4:sta:et:sts=4:ai +from __future__ import (unicode_literals, division, absolute_import, + print_function) + +__license__ = 'GPL v3' +__copyright__ = '2011, Kovid Goyal <kovid@kovidgoyal.net>' +__docformat__ = 'restructuredtext en' + +from collections import Counter + +from calibre.ebooks.oeb.base import OEB_STYLES, barename, XPath + +class RemoveAdobeMargins(object): + ''' + Remove margins specified in Adobe's page templates. + ''' + + def __call__(self, oeb, log, opts): + self.oeb, self.opts, self.log = oeb, opts, log + + for item in self.oeb.manifest: + if (item.media_type in ('application/vnd.adobe-page-template+xml', + 'application/vnd.adobe.page-template+xml') and + hasattr(item.data, 'xpath')): + self.log('Removing page margins specified in the' + ' Adobe page template') + for elem in item.data.xpath( + '//*[@margin-bottom or @margin-top ' + 'or @margin-left or @margin-right]'): + for margin in ('left', 'right', 'top', 'bottom'): + attr = 'margin-'+margin + elem.attrib.pop(attr, None) + + +class RemoveFakeMargins(object): + + ''' + Remove left and right margins from paragraph/divs if the same margin is specified + on almost all the elements at that level. + + Must be called only after CSS flattening + ''' + + def __call__(self, oeb, log, opts): + if not opts.remove_fake_margins: + return + self.oeb, self.log, self.opts = oeb, log, opts + stylesheet = None + self.levels = {} + self.stats = {} + self.selector_map = {} + + for item in self.oeb.manifest: + if item.media_type.lower() in OEB_STYLES: + stylesheet = item + break + if stylesheet is None: + return + + self.log('Removing fake margins...') + + stylesheet = stylesheet.data + + from cssutils.css import CSSRule + for rule in stylesheet.cssRules.rulesOfType(CSSRule.STYLE_RULE): + self.selector_map[rule.selectorList.selectorText] = rule.style + + self.find_levels() + + for level in self.levels: + self.process_level(level) + + def get_margins(self, elem): + cls = elem.get('class', None) + if cls: + style = self.selector_map.get('.'+cls, None) + if style: + return style.marginLeft, style.marginRight, style + return '', '', None + + + def process_level(self, level): + elems = self.levels[level] + self.stats[level+'_left'] = Counter() + self.stats[level+'_right'] = Counter() + + for elem in elems: + lm, rm = self.get_margins(elem)[:2] + self.stats[level+'_left'][lm] += 1 + self.stats[level+'_right'][rm] += 1 + + self.log.debug(level, ' left margin stats:', self.stats[level+'_left']) + self.log.debug(level, ' right margin stats:', self.stats[level+'_right']) + + remove_left = self.analyze_stats(self.stats[level+'_left']) + remove_right = self.analyze_stats(self.stats[level+'_right']) + + + if remove_left: + mcl = self.stats[level+'_left'].most_common(1)[0][0] + self.log('Removing level %s left margin of:'%level, mcl) + + if remove_right: + mcr = self.stats[level+'_right'].most_common(1)[0][0] + self.log('Removing level %s right margin of:'%level, mcr) + + if remove_left or remove_right: + for elem in elems: + lm, rm, style = self.get_margins(elem) + if remove_left and lm == mcl: + style.removeProperty('margin-left') + if remove_right and rm == mcr: + style.removeProperty('margin-right') + + def find_levels(self): + + def level_of(elem, body): + ans = 1 + while elem.getparent() is not body: + ans += 1 + elem = elem.getparent() + return ans + + paras = XPath('descendant::h:p|descendant::h:div') + + for item in self.oeb.spine: + body = XPath('//h:body')(item.data) + if not body: + continue + body = body[0] + + for p in paras(body): + level = level_of(p, body) + level = '%s_%d'%(barename(p.tag), level) + if level not in self.levels: + self.levels[level] = [] + self.levels[level].append(p) + + remove = set() + for k, v in self.levels.iteritems(): + num = len(v) + self.log.debug('Found %d items of level:'%num, k) + level = int(k.split('_')[-1]) + tag = k.split('_')[0] + if tag == 'p' and num < 25: + remove.add(k) + if tag == 'div': + if level > 2 and num < 25: + remove.add(k) + elif level < 3: + # Check each level < 3 element and only keep those + # that have many child paras + for elem in list(v): + children = len(paras(elem)) + if children < 5: + v.remove(elem) + + for k in remove: + self.levels.pop(k) + self.log.debug('Ignoring level', k) + + def analyze_stats(self, stats): + if not stats: + return False + mc = stats.most_common(1) + if len(mc) > 1: + return False + mc = mc[0] + most_common, most_common_count = mc + if not most_common or most_common == '0': + return False + total = sum(stats.values()) + # True if greater than 95% of elements have the same margin + return most_common_count/total > 0.95 diff --git a/src/calibre/ebooks/oeb/transforms/split.py b/src/calibre/ebooks/oeb/transforms/split.py index 69de740ddc..0c9b492855 100644 --- a/src/calibre/ebooks/oeb/transforms/split.py +++ b/src/calibre/ebooks/oeb/transforms/split.py @@ -120,7 +120,19 @@ class Split(object): for i, x in enumerate(page_breaks): x.set('id', x.get('id', 'calibre_pb_%d'%i)) id = x.get('id') - page_breaks_.append((XPath('//*[@id=%r]'%id), + try: + xp = XPath('//*[@id="%s"]'%id) + except: + try: + xp = XPath("//*[@id='%s']"%id) + except: + # The id has both a quote and an apostrophe or some other + # Just replace it since I doubt its going to work anywhere else + # either + id = 'calibre_pb_%d'%i + x.set('id', id) + xp = XPath('//*[@id=%r]'%id) + page_breaks_.append((xp, x.get('pb_before', False))) page_break_ids.append(id) diff --git a/src/calibre/ebooks/oeb/transforms/structure.py b/src/calibre/ebooks/oeb/transforms/structure.py index 0db9b153df..613429c3ec 100644 --- a/src/calibre/ebooks/oeb/transforms/structure.py +++ b/src/calibre/ebooks/oeb/transforms/structure.py @@ -10,6 +10,7 @@ import re from lxml import etree from urlparse import urlparse +from collections import OrderedDict from calibre.ebooks.oeb.base import XPNSMAP, TOC, XHTML, xml2text from calibre.ebooks import ConversionError @@ -80,6 +81,7 @@ class DetectStructure(object): page_break_after = 'display: block; page-break-after: always' for item, elem in self.detected_chapters: text = xml2text(elem).strip() + text = re.sub(r'\s+', ' ', text.strip()) self.log('\tDetected chapter:', text[:50]) if chapter_mark == 'none': continue @@ -95,10 +97,8 @@ class DetectStructure(object): self.log.exception('Failed to mark chapter') def create_level_based_toc(self): - if self.opts.level1_toc is None: - return - for item in self.oeb.spine: - self.add_leveled_toc_items(item) + if self.opts.level1_toc is not None: + self.add_leveled_toc_items() def create_toc_from_chapters(self): counter = self.oeb.toc.next_play_order() @@ -138,56 +138,65 @@ class DetectStructure(object): text = elem.get('title', '') if not text: text = elem.get('alt', '') - text = text[:100].strip() + text = re.sub(r'\s+', ' ', text.strip()) + text = text[:1000].strip() id = elem.get('id', 'calibre_toc_%d'%counter) elem.set('id', id) href = '#'.join((item.href, id)) return text, href - def add_leveled_toc_items(self, item): - level1 = XPath(self.opts.level1_toc)(item.data) - level1_order = [] - document = item - + def add_leveled_toc_items(self): + added = OrderedDict() + added2 = OrderedDict() counter = 1 - if level1: - added = {} - for elem in level1: + for document in self.oeb.spine: + previous_level1 = list(added.itervalues())[-1] if added else None + previous_level2 = list(added2.itervalues())[-1] if added2 else None + + for elem in XPath(self.opts.level1_toc)(document.data): text, _href = self.elem_to_link(document, elem, counter) counter += 1 if text: node = self.oeb.toc.add(text, _href, play_order=self.oeb.toc.next_play_order()) - level1_order.append(node) added[elem] = node #node.add(_('Top'), _href) - if self.opts.level2_toc is not None: - added2 = {} - level2 = list(XPath(self.opts.level2_toc)(document.data)) - for elem in level2: + + if self.opts.level2_toc is not None and added: + for elem in XPath(self.opts.level2_toc)(document.data): level1 = None for item in document.data.iterdescendants(): - if item in added.keys(): + if item in added: level1 = added[item] - elif item == elem and level1 is not None: + elif item == elem: + if level1 is None: + if previous_level1 is None: + break + level1 = previous_level1 text, _href = self.elem_to_link(document, elem, counter) counter += 1 if text: added2[elem] = level1.add(text, _href, play_order=self.oeb.toc.next_play_order()) - if self.opts.level3_toc is not None: - level3 = list(XPath(self.opts.level3_toc)(document.data)) - for elem in level3: + break + + if self.opts.level3_toc is not None and added2: + for elem in XPath(self.opts.level3_toc)(document.data): level2 = None for item in document.data.iterdescendants(): - if item in added2.keys(): + if item in added2: level2 = added2[item] - elif item == elem and level2 is not None: + elif item == elem: + if level2 is None: + if previous_level2 is None: + break + level2 = previous_level2 text, _href = \ self.elem_to_link(document, elem, counter) counter += 1 if text: level2.add(text, _href, - play_order=self.oeb.toc.next_play_order()) + play_order=self.oeb.toc.next_play_order()) + break diff --git a/src/calibre/ebooks/oeb/transforms/trimmanifest.py b/src/calibre/ebooks/oeb/transforms/trimmanifest.py index 0baacfd1f9..95501dbb9b 100644 --- a/src/calibre/ebooks/oeb/transforms/trimmanifest.py +++ b/src/calibre/ebooks/oeb/transforms/trimmanifest.py @@ -8,8 +8,6 @@ __copyright__ = '2008, Marshall T. Vandegrift <llasram@gmail.com>' from urlparse import urldefrag -import cssutils - from calibre.ebooks.oeb.base import CSS_MIME, OEB_DOCS from calibre.ebooks.oeb.base import urlnormalize, iterlinks @@ -23,6 +21,7 @@ class ManifestTrimmer(object): return cls() def __call__(self, oeb, context): + import cssutils oeb.logger.info('Trimming unused files from manifest...') self.opts = context used = set() diff --git a/src/calibre/ebooks/pdb/__init__.py b/src/calibre/ebooks/pdb/__init__.py index 092c8a21bd..c8089297db 100644 --- a/src/calibre/ebooks/pdb/__init__.py +++ b/src/calibre/ebooks/pdb/__init__.py @@ -12,6 +12,7 @@ from calibre.ebooks.pdb.ereader.reader import Reader as ereader_reader from calibre.ebooks.pdb.palmdoc.reader import Reader as palmdoc_reader from calibre.ebooks.pdb.ztxt.reader import Reader as ztxt_reader from calibre.ebooks.pdb.pdf.reader import Reader as pdf_reader +from calibre.ebooks.pdb.plucker.reader import Reader as plucker_reader FORMAT_READERS = { 'PNPdPPrs': ereader_reader, @@ -19,6 +20,7 @@ FORMAT_READERS = { 'zTXTGPlm': ztxt_reader, 'TEXtREAd': palmdoc_reader, '.pdfADBE': pdf_reader, + 'DataPlkr': plucker_reader, } from calibre.ebooks.pdb.palmdoc.writer import Writer as palmdoc_writer @@ -37,6 +39,7 @@ IDENTITY_TO_NAME = { 'zTXTGPlm': 'zTXT', 'TEXtREAd': 'PalmDOC', '.pdfADBE': 'Adobe Reader', + 'DataPlkr': 'Plucker', 'BVokBDIC': 'BDicty', 'DB99DBOS': 'DB (Database program)', @@ -50,7 +53,6 @@ IDENTITY_TO_NAME = { 'DATALSdb': 'LIST', 'Mdb1Mdb1': 'MobileDB', 'BOOKMOBI': 'MobiPocket', - 'DataPlkr': 'Plucker', 'DataSprd': 'QuickSheet', 'SM01SMem': 'SuperMemo', 'TEXtTlDc': 'TealDoc', diff --git a/src/calibre/ebooks/pdb/ereader/reader132.py b/src/calibre/ebooks/pdb/ereader/reader132.py index df98ce15b1..09e4b624e5 100644 --- a/src/calibre/ebooks/pdb/ereader/reader132.py +++ b/src/calibre/ebooks/pdb/ereader/reader132.py @@ -129,14 +129,22 @@ class Reader132(FormatReader): footnoteids = re.findall('\w+(?=\x00)', self.section_data(self.header_record.footnote_offset).decode('cp1252' if self.encoding is None else self.encoding)) for fid, i in enumerate(range(self.header_record.footnote_offset + 1, self.header_record.footnote_offset + self.header_record.footnote_count)): self.log.debug('Extracting footnote page %i' % i) - html += footnote_to_html(footnoteids[fid], self.decompress_text(i)) + if fid < len(footnoteids): + fid = footnoteids[fid] + else: + fid = '' + html += footnote_to_html(fid, self.decompress_text(i)) if self.header_record.sidebar_count > 0: html += '<br /><h1>%s</h1>' % _('Sidebar') sidebarids = re.findall('\w+(?=\x00)', self.section_data(self.header_record.sidebar_offset).decode('cp1252' if self.encoding is None else self.encoding)) for sid, i in enumerate(range(self.header_record.sidebar_offset + 1, self.header_record.sidebar_offset + self.header_record.sidebar_count)): self.log.debug('Extracting sidebar page %i' % i) - html += sidebar_to_html(sidebarids[sid], self.decompress_text(i)) + if sid < len(sidebarids): + sid = sidebarids[sid] + else: + sid = '' + html += sidebar_to_html(sid, self.decompress_text(i)) html += '</body></html>' diff --git a/src/calibre/ebooks/pdb/ereader/writer.py b/src/calibre/ebooks/pdb/ereader/writer.py index 4fbd343a6b..eb023c594b 100644 --- a/src/calibre/ebooks/pdb/ereader/writer.py +++ b/src/calibre/ebooks/pdb/ereader/writer.py @@ -21,7 +21,6 @@ except ImportError: import cStringIO from calibre.ebooks.pdb.formatwriter import FormatWriter -from calibre.ebooks.oeb.base import OEB_RASTER_IMAGES from calibre.ebooks.pdb.header import PdbHeaderBuilder from calibre.ebooks.pml.pmlml import PMLMLizer @@ -135,6 +134,7 @@ class Writer(FormatWriter): 62-...: Raw image data in 8 bit PNG format. ''' images = [] + from calibre.ebooks.oeb.base import OEB_RASTER_IMAGES for item in manifest: if item.media_type in OEB_RASTER_IMAGES and item.href in image_hrefs.keys(): diff --git a/src/calibre/ebooks/pdb/plucker/__init__.py b/src/calibre/ebooks/pdb/plucker/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/src/calibre/ebooks/pdb/plucker/reader.py b/src/calibre/ebooks/pdb/plucker/reader.py new file mode 100644 index 0000000000..0d2ca6983c --- /dev/null +++ b/src/calibre/ebooks/pdb/plucker/reader.py @@ -0,0 +1,739 @@ +# -*- coding: utf-8 -*- + +#from __future__ import (unicode_literals, division, absolute_import, print_function) + +__license__ = 'GPL v3' +__copyright__ = '20011, John Schember <john@nachtimwald.com>' +__docformat__ = 'restructuredtext en' + +import os +import struct +import zlib + +from collections import OrderedDict + +from calibre import CurrentDir +from calibre.ebooks.pdb.formatreader import FormatReader +from calibre.ptempfile import TemporaryFile +from calibre.utils.magick import Image, create_canvas +from calibre.ebooks.compression.palmdoc import decompress_doc + +DATATYPE_PHTML = 0 +DATATYPE_PHTML_COMPRESSED = 1 +DATATYPE_TBMP = 2 +DATATYPE_TBMP_COMPRESSED = 3 +DATATYPE_MAILTO = 4 +DATATYPE_LINK_INDEX = 5 +DATATYPE_LINKS = 6 +DATATYPE_LINKS_COMPRESSED = 7 +DATATYPE_BOOKMARKS = 8 +DATATYPE_CATEGORY = 9 +DATATYPE_METADATA = 10 +DATATYPE_STYLE_SHEET = 11 +DATATYPE_FONT_PAGE = 12 +DATATYPE_TABLE = 13 +DATATYPE_TABLE_COMPRESSED = 14 +DATATYPE_COMPOSITE_IMAGE = 15 +DATATYPE_PAGELIST_METADATA = 16 +DATATYPE_SORTED_URL_INDEX = 17 +DATATYPE_SORTED_URL = 18 +DATATYPE_SORTED_URL_COMPRESSED = 19 +DATATYPE_EXT_ANCHOR_INDEX = 20 +DATATYPE_EXT_ANCHOR = 21 +DATATYPE_EXT_ANCHOR_COMPRESSED = 22 + +# IETF IANA MIBenum value for the character set. +# See the http://www.iana.org/assignments/character-sets for valid values. +# Not all character sets are handled by Python. This is a small subset that +# the MIBenum maps to Python standard encodings +# from http://docs.python.org/library/codecs.html#standard-encodings +MIBNUM_TO_NAME = { + 3: 'ascii', + 4: 'latin_1', + 5: 'iso8859_2', + 6: 'iso8859_3', + 7: 'iso8859_4', + 8: 'iso8859_5', + 9: 'iso8859_6', + 10: 'iso8859_7', + 11: 'iso8859_8', + 12: 'iso8859_9', + 13: 'iso8859_10', + 17: 'shift_jis', + 18: 'euc_jp', + 27: 'utf_7', + 36: 'euc_kr', + 37: 'iso2022_kr', + 38: 'euc_kr', + 39: 'iso2022_jp', + 40: 'iso2022_jp_2', + 106: 'utf-8', + 109: 'iso8859_13', + 110: 'iso8859_14', + 111: 'iso8859_15', + 112: 'iso8859_16', + 1013: 'utf_16_be', + 1014: 'utf_16_le', + 1015: 'utf_16', + 2009: 'cp850', + 2010: 'cp852', + 2011: 'cp437', + 2013: 'cp862', + 2025: 'gb2312', + 2026: 'big5', + 2028: 'cp037', + 2043: 'cp424', + 2044: 'cp500', + 2046: 'cp855', + 2047: 'cp857', + 2048: 'cp860', + 2049: 'cp861', + 2050: 'cp863', + 2051: 'cp864', + 2052: 'cp865', + 2054: 'cp869', + 2063: 'cp1026', + 2085: 'hz', + 2086: 'cp866', + 2087: 'cp775', + 2089: 'cp858', + 2091: 'cp1140', + 2102: 'big5hkscs', + 2250: 'cp1250', + 2251: 'cp1251', + 2252: 'cp1252', + 2253: 'cp1253', + 2254: 'cp1254', + 2255: 'cp1255', + 2256: 'cp1256', + 2257: 'cp1257', + 2258: 'cp1258', +} + +class HeaderRecord(object): + ''' + Plucker header. PDB record 0. + ''' + + def __init__(self, raw): + self.uid, = struct.unpack('>H', raw[0:2]) + # This is labled version in the spec. + # 2 is ZLIB compressed, + # 1 is DOC compressed + self.compression, = struct.unpack('>H', raw[2:4]) + self.records, = struct.unpack('>H', raw[4:6]) + # uid of the first html file. This should link + # to other files which in turn may link to others. + self.home_html = None + + self.reserved = {} + for i in xrange(self.records): + adv = 4*i + name, = struct.unpack('>H', raw[6+adv:8+adv]) + id, = struct.unpack('>H', raw[8+adv:10+adv]) + self.reserved[id] = name + if name == 0: + self.home_html = id + + +class SectionHeader(object): + ''' + Every sections (record) has this header. It gives + details about the section such as it's uid. + ''' + + def __init__(self, raw): + self.uid, = struct.unpack('>H', raw[0:2]) + self.paragraphs, = struct.unpack('>H', raw[2:4]) + self.size, = struct.unpack('>H', raw[4:6]) + self.type, = struct.unpack('>B', raw[6]) + self.flags, = struct.unpack('>B', raw[7]) + + +class SectionHeaderText(object): + ''' + Sub header for text records. + ''' + + def __init__(self, section_header, raw): + # The uncompressed size of each paragraph. + self.sizes = [] + # uncompressed offset of each paragraph starting + # at the beginning of the PHTML. + self.paragraph_offsets = [] + # Paragraph attributes. + self.attributes = [] + + for i in xrange(section_header.paragraphs): + adv = 4*i + self.sizes.append(struct.unpack('>H', raw[adv:2+adv])[0]) + self.attributes.append(struct.unpack('>H', raw[2+adv:4+adv])[0]) + + running_offset = 0 + for size in self.sizes: + running_offset += size + self.paragraph_offsets.append(running_offset) + + +class SectionMetadata(object): + ''' + Metadata. + + This does not store metadata such as title, or author. + That metadata would be best retrieved with the PDB (plucker) + metdata reader. + + This stores document specific information such as the + text encoding. + + Note: There is a default encoding but each text section + can be assigned a different encoding. + ''' + + def __init__(self, raw): + self.default_encoding = 'latin-1' + self.exceptional_uid_encodings = {} + self.owner_id = None + + record_count, = struct.unpack('>H', raw[0:2]) + + adv = 0 + for i in xrange(record_count): + type, = struct.unpack('>H', raw[2+adv:4+adv]) + length, = struct.unpack('>H', raw[4+adv:6+adv]) + + # CharSet + if type == 1: + val, = struct.unpack('>H', raw[6+adv:8+adv]) + self.default_encoding = MIBNUM_TO_NAME.get(val, 'latin-1') + # ExceptionalCharSets + elif type == 2: + ii_adv = 0 + for ii in xrange(length / 2): + uid, = struct.unpack('>H', raw[6+adv+ii_adv:8+adv+ii_adv]) + mib, = struct.unpack('>H', raw[8+adv+ii_adv:10+adv+ii_adv]) + self.exceptional_uid_encodings[uid] = MIBNUM_TO_NAME.get(mib, 'latin-1') + ii_adv += 4 + # OwnerID + elif type == 3: + self.owner_id = struct.unpack('>I', raw[6+adv:10+adv]) + # Author, Title, PubDate + # Ignored here. The metadata reader plugin + # will get this info because if it's missing + # the metadata reader plugin will use fall + # back data from elsewhere in the file. + elif type in (4, 5, 6): + pass + # Linked Documents + elif type == 7: + pass + + adv += 2*length + + +class SectionText(object): + ''' + Text data. Stores a text section header and the PHTML. + ''' + + def __init__(self, section_header, raw): + self.header = SectionHeaderText(section_header, raw) + self.data = raw[section_header.paragraphs * 4:] + + +class SectionCompositeImage(object): + ''' + A composite image consists of a a 2D array + of rows and columns. The entries in the array + are uid's. + ''' + + def __init__(self, raw): + self.columns, = struct.unpack('>H', raw[0:2]) + self.rows, = struct.unpack('>H', raw[2:4]) + + # [ + # [uid, uid, uid, ...], + # [uid, uid, uid, ...], + # ... + # ] + # + # Each item in the layout is in it's + # correct position in the final + # composite. + # + # Each item in the layout is a uid + # to an image record. + self.layout = [] + offset = 4 + for i in xrange(self.rows): + col = [] + for j in xrange(self.columns): + col.append(struct.unpack('>H', raw[offset:offset+2])[0]) + offset += 2 + self.layout.append(col) + + +class Reader(FormatReader): + ''' + Convert a plucker archive into HTML. + + TODO: + * UTF 16 and 32 characters. + * Margins. + * Alignment. + * Font color. + * DATATYPE_MAILTO + * DATATYPE_TABLE(_COMPRESSED) + * DATATYPE_EXT_ANCHOR_INDEX + * DATATYPE_EXT_ANCHOR(_COMPRESSED) + ''' + + def __init__(self, header, stream, log, options): + self.stream = stream + self.log = log + self.options = options + + # Mapping of section uid to our internal + # list of sections. + self.uid_section_number = OrderedDict() + self.uid_text_secion_number = OrderedDict() + self.uid_text_secion_encoding = {} + self.uid_image_section_number = {} + self.uid_composite_image_section_number = {} + self.metadata_section_number = None + self.default_encoding = 'latin-1' + self.owner_id = None + self.sections = [] + + # The Plucker record0 header + self.header_record = HeaderRecord(header.section_data(0)) + + for i in range(1, header.num_sections): + section_number = len(self.sections) + # The length of the section header. + # Where the actual data in the section starts. + start = 8 + section = None + + raw_data = header.section_data(i) + # Every sections has a section header. + section_header = SectionHeader(raw_data) + + # Store sections we care able. + if section_header.type in (DATATYPE_PHTML, DATATYPE_PHTML_COMPRESSED): + self.uid_text_secion_number[section_header.uid] = section_number + section = SectionText(section_header, raw_data[start:]) + elif section_header.type in (DATATYPE_TBMP, DATATYPE_TBMP_COMPRESSED): + self.uid_image_section_number[section_header.uid] = section_number + section = raw_data[start:] + elif section_header.type == DATATYPE_METADATA: + self.metadata_section_number = section_number + section = SectionMetadata(raw_data[start:]) + elif section_header.type == DATATYPE_COMPOSITE_IMAGE: + self.uid_composite_image_section_number[section_header.uid] = section_number + section = SectionCompositeImage(raw_data[start:]) + + # Store the section. + if section: + self.uid_section_number[section_header.uid] = section_number + self.sections.append((section_header, section)) + + # Store useful information from the metadata section locally + # to make access easier. + if self.metadata_section_number: + mdata_section = self.sections[self.metadata_section_number][1] + for k, v in mdata_section.exceptional_uid_encodings.items(): + self.uid_text_secion_encoding[k] = v + self.default_encoding = mdata_section.default_encoding + self.owner_id = mdata_section.owner_id + + # Get the metadata (tile, author, ...) with the metadata reader. + from calibre.ebooks.metadata.pdb import get_metadata + self.mi = get_metadata(stream, False) + + def extract_content(self, output_dir): + # Each text record is independent (unless the continuation + # value is set in the previous record). Put each converted + # text recored into a separate file. We will reference the + # home.html file as the first file and let the HTML input + # plugin assemble the order based on hyperlinks. + with CurrentDir(output_dir): + for uid, num in self.uid_text_secion_number.items(): + self.log.debug('Writing record with uid: %s as %s.html' % (uid, uid)) + with open('%s.html' % uid, 'wb') as htmlf: + html = u'<html><body>' + section_header, section_data = self.sections[num] + if section_header.type == DATATYPE_PHTML: + html += self.process_phtml(section_data.data, section_data.header.paragraph_offsets) + elif section_header.type == DATATYPE_PHTML_COMPRESSED: + d = self.decompress_phtml(section_data.data) + html += self.process_phtml(d, section_data.header.paragraph_offsets).decode(self.get_text_uid_encoding(section_header.uid), 'replace') + html += '</body></html>' + htmlf.write(html.encode('utf-8')) + + # Images. + # Cache the image sizes in case they are used by a composite image. + image_sizes = {} + if not os.path.exists(os.path.join(output_dir, 'images/')): + os.makedirs(os.path.join(output_dir, 'images/')) + with CurrentDir(os.path.join(output_dir, 'images/')): + # Single images. + for uid, num in self.uid_image_section_number.items(): + section_header, section_data = self.sections[num] + if section_data: + idata = None + if section_header.type == DATATYPE_TBMP: + idata = section_data + elif section_header.type == DATATYPE_TBMP_COMPRESSED: + if self.header_record.compression == 1: + idata = decompress_doc(section_data) + elif self.header_record.compression == 2: + idata = zlib.decompress(section_data) + try: + with TemporaryFile(suffix='.palm') as itn: + with open(itn, 'wb') as itf: + itf.write(idata) + im = Image() + im.read(itn) + image_sizes[uid] = im.size + im.set_compression_quality(70) + im.save('%s.jpg' % uid) + self.log.debug('Wrote image with uid %s to images/%s.jpg' % (uid, uid)) + except Exception as e: + self.log.error('Failed to write image with uid %s: %s' % (uid, e)) + else: + self.log.error('Failed to write image with uid %s: No data.' % uid) + # Composite images. + # We're going to use the already compressed .jpg images here. + for uid, num in self.uid_composite_image_section_number.items(): + try: + section_header, section_data = self.sections[num] + # Get the final width and height. + width = 0 + height = 0 + for row in section_data.layout: + row_width = 0 + col_height = 0 + for col in row: + if col not in image_sizes: + raise Exception('Image with uid: %s missing.' % col) + im = Image() + im.read('%s.jpg' % col) + w, h = im.size + row_width += w + if col_height < h: + col_height = h + if width < row_width: + width = row_width + height += col_height + # Create a new image the total size of all image + # parts. Put the parts into the new image. + canvas = create_canvas(width, height) + y_off = 0 + for row in section_data.layout: + x_off = 0 + largest_height = 0 + for col in row: + im = Image() + im.read('%s.jpg' % col) + canvas.compose(im, x_off, y_off) + w, h = im.size + x_off += w + if largest_height < h: + largest_height = h + y_off += largest_height + canvas.set_compression_quality(70) + canvas.save('%s.jpg' % uid) + self.log.debug('Wrote composite image with uid %s to images/%s.jpg' % (uid, uid)) + except Exception as e: + self.log.error('Failed to write composite image with uid %s: %s' % (uid, e)) + + # Run the HTML through the html processing plugin. + from calibre.customize.ui import plugin_for_input_format + html_input = plugin_for_input_format('html') + for opt in html_input.options: + setattr(self.options, opt.option.name, opt.recommended_value) + self.options.input_encoding = 'utf-8' + odi = self.options.debug_pipeline + self.options.debug_pipeline = None + # Determine the home.html record uid. This should be set in the + # reserved values in the metadata recored. home.html is the first + # text record (should have hyper link references to other records) + # in the document. + try: + home_html = self.header_record.home_html + if not home_html: + home_html = self.uid_text_secion_number.items()[0][0] + except: + raise Exception('Could not determine home.html') + # Generate oeb from html conversion. + oeb = html_input.convert(open('%s.html' % home_html, 'rb'), self.options, 'html', self.log, {}) + self.options.debug_pipeline = odi + + return oeb + + def decompress_phtml(self, data): + if self.header_record.compression == 2: + if self.owner_id: + raise NotImplementedError + return zlib.decompress(data) + elif self.header_record.compression == 1: + from calibre.ebooks.compression.palmdoc import decompress_doc + return decompress_doc(data) + + def process_phtml(self, d, paragraph_offsets=[]): + html = u'<p id="p0">' + offset = 0 + paragraph_open = True + link_open = False + need_set_p_id = False + p_num = 1 + font_specifier_close = '' + + while offset < len(d): + if not paragraph_open: + if need_set_p_id: + html += u'<p id="p%s">' % p_num + p_num += 1 + need_set_p_id = False + else: + html += u'<p>' + paragraph_open = True + + c = ord(d[offset]) + # PHTML "functions" + if c == 0x0: + offset += 1 + c = ord(d[offset]) + # Page link begins + # 2 Bytes + # record ID + if c == 0x0a: + offset += 1 + id = struct.unpack('>H', d[offset:offset+2])[0] + if id in self.uid_text_secion_number: + html += '<a href="%s.html">' % id + link_open = True + offset += 1 + # Targeted page link begins + # 3 Bytes + # record ID, target + elif c == 0x0b: + offset += 3 + # Paragraph link begins + # 4 Bytes + # record ID, paragraph number + elif c == 0x0c: + offset += 1 + id = struct.unpack('>H', d[offset:offset+2])[0] + offset += 2 + pid = struct.unpack('>H', d[offset:offset+2])[0] + if id in self.uid_text_secion_number: + html += '<a href="%s.html#p%s">' % (id, pid) + link_open = True + offset += 1 + # Targeted paragraph link begins + # 5 Bytes + # record ID, paragraph number, target + elif c == 0x0d: + offset += 5 + # Link ends + # 0 Bytes + elif c == 0x08: + if link_open: + html += '</a>' + link_open = False + # Set font + # 1 Bytes + # font specifier + elif c == 0x11: + offset += 1 + specifier = d[offset] + html += font_specifier_close + # Regular text + if specifier == 0: + font_specifier_close = '' + # h1 + elif specifier == 1: + html += '<h1>' + font_specifier_close = '</h1>' + # h2 + elif specifier == 2: + html += '<h2>' + font_specifier_close = '</h2>' + # h3 + elif specifier == 3: + html += '<h13>' + font_specifier_close = '</h3>' + # h4 + elif specifier == 4: + html += '<h4>' + font_specifier_close = '</h4>' + # h5 + elif specifier == 5: + html += '<h5>' + font_specifier_close = '</h5>' + # h6 + elif specifier == 6: + html += '<h6>' + font_specifier_close = '</h6>' + # Bold + elif specifier == 7: + html += '<b>' + font_specifier_close = '</b>' + # Fixed-width + elif specifier == 8: + html += '<tt>' + font_specifier_close = '</tt>' + # Small + elif specifier == 9: + html += '<small>' + font_specifier_close = '</small>' + # Subscript + elif specifier == 10: + html += '<sub>' + font_specifier_close = '</sub>' + # Superscript + elif specifier == 11: + html += '<sup>' + font_specifier_close = '</sup>' + # Embedded image + # 2 Bytes + # image record ID + elif c == 0x1a: + offset += 1 + uid = struct.unpack('>H', d[offset:offset+2])[0] + html += '<img src="images/%s.jpg" />' % uid + offset += 1 + # Set margin + # 2 Bytes + # left margin, right margin + elif c == 0x22: + offset += 2 + # Alignment of text + # 1 Bytes + # alignment + elif c == 0x29: + offset += 1 + # Horizontal rule + # 3 Bytes + # 8-bit height, 8-bit width (pixels), 8-bit width (%, 1-100) + elif c == 0x33: + offset += 3 + if paragraph_open: + html += u'</p>' + paragraph_open = False + html += u'<hr />' + # New line + # 0 Bytes + elif c == 0x38: + if paragraph_open: + html += u'</p>\n' + paragraph_open = False + # Italic text begins + # 0 Bytes + elif c == 0x40: + html += u'<i>' + # Italic text ends + # 0 Bytes + elif c == 0x48: + html += u'</i>' + # Set text color + # 3 Bytes + # 8-bit red, 8-bit green, 8-bit blue + elif c == 0x53: + offset += 3 + # Multiple embedded image + # 4 Bytes + # alternate image record ID, image record ID + elif c == 0x5c: + offset += 3 + uid = struct.unpack('>H', d[offset:offset+2])[0] + html += '<img src="images/%s.jpg" />' % uid + offset += 1 + # Underline text begins + # 0 Bytes + elif c == 0x60: + html += u'<u>' + # Underline text ends + # 0 Bytes + elif c == 0x68: + html += u'</u>' + # Strike-through text begins + # 0 Bytes + elif c == 0x70: + html += u'<s>' + # Strike-through text ends + # 0 Bytes + elif c == 0x78: + html += u'</s>' + # 16-bit Unicode character + # 3 Bytes + # alternate text length, 16-bit unicode character + elif c == 0x83: + offset += 3 + # 32-bit Unicode character + # 5 Bytes + # alternate text length, 32-bit unicode character + elif c == 0x85: + offset += 5 + # Begin custom font span + # 6 Bytes + # font page record ID, X page position, Y page position + elif c == 0x8e: + offset += 6 + # Adjust custom font glyph position + # 4 Bytes + # X page position, Y page position + elif c == 0x8c: + offset += 4 + # Change font page + # 2 Bytes + # font record ID + elif c == 0x8a: + offset += 2 + # End custom font span + # 0 Bytes + elif c == 0x88: + pass + # Begin new table row + # 0 Bytes + elif c == 0x90: + pass + # Insert table (or table link) + # 2 Bytes + # table record ID + elif c == 0x92: + offset += 2 + # Table cell data + # 7 Bytes + # 8-bit alignment, 16-bit image record ID, 8-bit columns, 8-bit rows, 16-bit text length + elif c == 0x97: + offset += 7 + # Exact link modifier + # 2 Bytes + # Paragraph Offset (The Exact Link Modifier modifies a Paragraph Link or Targeted Paragraph Link function to specify an exact byte offset within the paragraph. This function must be followed immediately by the function it modifies). + elif c == 0x9a: + offset += 2 + elif c == 0xa0: + html += ' ' + else: + html += unichr(c) + offset += 1 + if offset in paragraph_offsets: + need_set_p_id = True + if paragraph_open: + html += u'</p>\n' + paragraph_open = False + + if paragraph_open: + html += u'</p>' + + return html + + def get_text_uid_encoding(self, uid): + # Return the user sepcified input encoding, + # otherwise return the alternate encoding specified for the uid, + # otherwise retur the default encoding for the document. + return self.options.input_encoding if self.options.input_encoding else self.uid_text_secion_encoding.get(uid, self.default_encoding) diff --git a/src/calibre/ebooks/pdf/fonts.cpp b/src/calibre/ebooks/pdf/fonts.cpp index 99ab7517c1..c3a709869e 100644 --- a/src/calibre/ebooks/pdf/fonts.cpp +++ b/src/calibre/ebooks/pdf/fonts.cpp @@ -72,6 +72,7 @@ XMLFont::XMLFont(string* font_name, double size, GfxRGB rgb) : size(size-1), line_size(-1.0), italic(false), bold(false), font_name(font_name), font_family(NULL), color(rgb) { + if (!this->font_name) this->font_name = new string(DEFAULT_FONT_FAMILY); this->font_family = family_name(this->font_name); if (strcasestr(font_name->c_str(), "bold")) this->bold = true; @@ -134,7 +135,12 @@ Fonts::size_type Fonts::add_font(XMLFont *f) { } Fonts::size_type Fonts::add_font(string* font_name, double size, GfxRGB rgb) { - XMLFont *f = new XMLFont(font_name, size, rgb); + XMLFont *f = NULL; + if (font_name == NULL) + font_name = new string("Unknown"); + // font_name must not be deleted + f = new XMLFont(font_name, size, rgb); + return this->add_font(f); } diff --git a/src/calibre/ebooks/pdf/images.cpp b/src/calibre/ebooks/pdf/images.cpp index 0e7d8b0d70..8ca7448001 100644 --- a/src/calibre/ebooks/pdf/images.cpp +++ b/src/calibre/ebooks/pdf/images.cpp @@ -13,6 +13,7 @@ #include <math.h> #include <iostream> #include <wand/MagickWand.h> +#include <zlib.h> #include "images.h" #include "utils.h" diff --git a/src/calibre/ebooks/pdf/input.py b/src/calibre/ebooks/pdf/input.py index 14b3552b04..51f44ba502 100644 --- a/src/calibre/ebooks/pdf/input.py +++ b/src/calibre/ebooks/pdf/input.py @@ -32,10 +32,11 @@ class PDFInput(InputFormatPlugin): def convert_new(self, stream, accelerators): from calibre.ebooks.pdf.reflow import PDFDocument + from calibre.utils.cleantext import clean_ascii_chars if pdfreflow_err: raise RuntimeError('Failed to load pdfreflow: ' + pdfreflow_err) - pdfreflow.reflow(stream.read()) - xml = open('index.xml', 'rb').read() + pdfreflow.reflow(stream.read(), 1, -1) + xml = clean_ascii_chars(open('index.xml', 'rb').read()) PDFDocument(xml, self.opts, self.log) return os.path.join(os.getcwd(), 'metadata.opf') diff --git a/src/calibre/ebooks/pdf/main.cpp b/src/calibre/ebooks/pdf/main.cpp index 4e6ec60388..869204dc1d 100644 --- a/src/calibre/ebooks/pdf/main.cpp +++ b/src/calibre/ebooks/pdf/main.cpp @@ -24,13 +24,14 @@ extern "C" { pdfreflow_reflow(PyObject *self, PyObject *args) { char *pdfdata; Py_ssize_t size; + int first_page, last_page, num = 0; - if (!PyArg_ParseTuple(args, "s#", &pdfdata, &size)) + if (!PyArg_ParseTuple(args, "s#ii", &pdfdata, &size, &first_page, &last_page)) return NULL; try { Reflow reflow(pdfdata, static_cast<std::ifstream::pos_type>(size)); - reflow.render(); + num = reflow.render(first_page, last_page); } catch (std::exception &e) { PyErr_SetString(PyExc_RuntimeError, e.what()); return NULL; } catch (...) { @@ -38,7 +39,7 @@ extern "C" { "Unknown exception raised while rendering PDF"); return NULL; } - Py_RETURN_NONE; + return Py_BuildValue("i", num); } static PyObject * @@ -166,8 +167,8 @@ extern "C" { static PyMethodDef pdfreflow_methods[] = { {"reflow", pdfreflow_reflow, METH_VARARGS, - "reflow(pdf_data)\n\n" - "Reflow the specified PDF." + "reflow(pdf_data, first_page, last_page)\n\n" + "Reflow the specified PDF. Returns the number of pages in the PDF. If last_page is -1 renders to end of document." }, {"get_metadata", pdfreflow_get_metadata, METH_VARARGS, "get_metadata(pdf_data, cover)\n\n" diff --git a/src/calibre/ebooks/pdf/manipulate/decrypt.py b/src/calibre/ebooks/pdf/manipulate/decrypt.py index ede12f15ee..fd8510efc7 100644 --- a/src/calibre/ebooks/pdf/manipulate/decrypt.py +++ b/src/calibre/ebooks/pdf/manipulate/decrypt.py @@ -103,7 +103,7 @@ def main(args=sys.argv, name=''): try: decrypt(args[0], opts.output, args[1]) - except DecryptionError, e: + except DecryptionError as e: print e.value return 1 diff --git a/src/calibre/ebooks/pdf/pdftohtml.py b/src/calibre/ebooks/pdf/pdftohtml.py index 564ba14a32..a791dab48a 100644 --- a/src/calibre/ebooks/pdf/pdftohtml.py +++ b/src/calibre/ebooks/pdf/pdftohtml.py @@ -13,7 +13,7 @@ from functools import partial from calibre.ebooks import ConversionError, DRMError from calibre.ptempfile import PersistentTemporaryFile -from calibre import isosx, iswindows, islinux, isfreebsd +from calibre.constants import isosx, iswindows, islinux, isbsd from calibre import CurrentDir PDFTOHTML = 'pdftohtml' @@ -23,7 +23,7 @@ if isosx and hasattr(sys, 'frameworks_dir'): if iswindows and hasattr(sys, 'frozen'): PDFTOHTML = os.path.join(os.path.dirname(sys.executable), 'pdftohtml.exe') popen = partial(subprocess.Popen, creationflags=0x08) # CREATE_NO_WINDOW=0x08 so that no ugly console is popped up -if (islinux or isfreebsd) and getattr(sys, 'frozen', False): +if (islinux or isbsd) and getattr(sys, 'frozen', False): PDFTOHTML = os.path.join(sys.executables_location, 'bin', 'pdftohtml') def pdftohtml(output_dir, pdf_path, no_images): @@ -43,6 +43,8 @@ def pdftohtml(output_dir, pdf_path, no_images): # This is neccessary as pdftohtml doesn't always (linux) respect absolute paths pdf_path = os.path.abspath(pdf_path) cmd = [PDFTOHTML, '-enc', 'UTF-8', '-noframes', '-p', '-nomerge', '-nodrm', '-q', pdf_path, os.path.basename(index)] + if isbsd: + cmd.remove('-nodrm') if no_images: cmd.append('-i') @@ -50,7 +52,7 @@ def pdftohtml(output_dir, pdf_path, no_images): try: p = popen(cmd, stderr=logf._fd, stdout=logf._fd, stdin=subprocess.PIPE) - except OSError, err: + except OSError as err: if err.errno == 2: raise ConversionError(_('Could not find pdftohtml, check it is in your PATH')) else: @@ -60,7 +62,7 @@ def pdftohtml(output_dir, pdf_path, no_images): try: ret = p.wait() break - except OSError, e: + except OSError as e: if e.errno == errno.EINTR: continue else: diff --git a/src/calibre/ebooks/pdf/reflow.cpp b/src/calibre/ebooks/pdf/reflow.cpp index 0c569fe0d1..65b5de6ae0 100644 --- a/src/calibre/ebooks/pdf/reflow.cpp +++ b/src/calibre/ebooks/pdf/reflow.cpp @@ -712,16 +712,18 @@ Reflow::Reflow(char *pdfdata, size_t sz) : } -void -Reflow::render() { +int +Reflow::render(int first_page, int last_page) { if (!this->doc->okToCopy()) cout << "Warning, this document has the copy protection flag set, ignoring." << endl; globalParams->setTextEncoding(encoding); - int first_page = 1; - int last_page = doc->getNumPages(); + int doc_pages = doc->getNumPages(); + if (last_page < 1 || last_page > doc_pages) last_page = doc_pages; + if (first_page < 1) first_page = 1; + if (first_page > last_page) first_page = last_page; XMLOutputDev *xml_out = new XMLOutputDev(this->doc); doc->displayPages(xml_out, first_page, last_page, @@ -733,9 +735,12 @@ Reflow::render() { false //Printing ); - this->dump_outline(); + if (last_page - first_page == doc_pages - 1) + this->dump_outline(); delete xml_out; + + return doc_pages; } void Reflow::dump_outline() { @@ -887,7 +892,7 @@ vector<char>* Reflow::render_first_page(bool use_crop_box, double x_res, } pg_w *= x_res/72.; - pg_h *= x_res/72.; + pg_h *= y_res/72.; int x=0, y=0; this->doc->displayPageSlice(out, pg, x_res, y_res, 0, diff --git a/src/calibre/ebooks/pdf/reflow.h b/src/calibre/ebooks/pdf/reflow.h index ad4b79929d..768799f004 100644 --- a/src/calibre/ebooks/pdf/reflow.h +++ b/src/calibre/ebooks/pdf/reflow.h @@ -66,7 +66,7 @@ class Reflow { ~Reflow(); /* Convert the PDF to XML. All files are output to the current directory */ - void render(); + int render(int first_page, int last_page); /* Get the PDF Info Dictionary */ map<string, string> get_info(); diff --git a/src/calibre/ebooks/pdf/writer.py b/src/calibre/ebooks/pdf/writer.py index b0884417f6..516509fdd7 100644 --- a/src/calibre/ebooks/pdf/writer.py +++ b/src/calibre/ebooks/pdf/writer.py @@ -46,7 +46,8 @@ def get_pdf_printer(opts, for_comic=False): printer = QPrinter(QPrinter.HighResolution) custom_size = get_custom_size(opts) - if opts.output_profile.short_name == 'default': + if opts.output_profile.short_name == 'default' or \ + opts.output_profile.width > 10000: if custom_size is None: printer.setPaperSize(paper_size(opts.paper_size)) else: diff --git a/src/calibre/ebooks/pml/output.py b/src/calibre/ebooks/pml/output.py index 9d2ddc6ca6..63d8a8b220 100644 --- a/src/calibre/ebooks/pml/output.py +++ b/src/calibre/ebooks/pml/output.py @@ -18,7 +18,6 @@ from calibre.customize.conversion import OutputFormatPlugin from calibre.customize.conversion import OptionRecommendation from calibre.ptempfile import TemporaryDirectory from calibre.utils.zipfile import ZipFile -from calibre.ebooks.oeb.base import OEB_RASTER_IMAGES from calibre.ebooks.pml.pmlml import PMLMLizer class PMLOutput(OutputFormatPlugin): @@ -60,6 +59,7 @@ class PMLOutput(OutputFormatPlugin): pmlz.add_dir(tdir) def write_images(self, manifest, image_hrefs, out_dir, opts): + from calibre.ebooks.oeb.base import OEB_RASTER_IMAGES for item in manifest: if item.media_type in OEB_RASTER_IMAGES and item.href in image_hrefs.keys(): if opts.full_image_depth: diff --git a/src/calibre/ebooks/pml/pmlconverter.py b/src/calibre/ebooks/pml/pmlconverter.py index 04813888ce..7bb23946ca 100644 --- a/src/calibre/ebooks/pml/pmlconverter.py +++ b/src/calibre/ebooks/pml/pmlconverter.py @@ -11,6 +11,7 @@ __docformat__ = 'restructuredtext en' import os import re import StringIO +from copy import deepcopy from calibre import my_unichr, prepare_string_for_xml from calibre.ebooks.metadata.toc import TOC @@ -25,6 +26,7 @@ class PML_HTMLizer(object): 'sp', 'sb', 'h1', + 'h1c', 'h2', 'h3', 'h4', @@ -58,6 +60,7 @@ class PML_HTMLizer(object): STATES_TAGS = { 'h1': ('<h1 style="page-break-before: always;">', '</h1>'), + 'h1c': ('<h1>', '</h1>'), 'h2': ('<h2>', '</h2>'), 'h3': ('<h3>', '</h3>'), 'h4': ('<h4>', '</h4>'), @@ -140,6 +143,10 @@ class PML_HTMLizer(object): 'd', 'b', ] + + NEW_LINE_EXCHANGE_STATES = { + 'h1': 'h1c', + } def __init__(self): self.state = {} @@ -219,11 +226,17 @@ class PML_HTMLizer(object): def start_line(self): start = u'' + state = deepcopy(self.state) div = [] span = [] other = [] + + for key, val in state.items(): + if key in self.NEW_LINE_EXCHANGE_STATES and val[0]: + state[self.NEW_LINE_EXCHANGE_STATES[key]] = val + state[key] = [False, ''] - for key, val in self.state.items(): + for key, val in state.items(): if val[0]: if key in self.DIV_STATES: div.append((key, val[1])) @@ -736,7 +749,10 @@ def pml_to_html(pml): def footnote_sidebar_to_html(pre_id, id, pml): id = id.strip('\x01') - html = '<br /><br style="page-break-after: always;" /><div id="%s-%s"><p>%s</p><small><a href="#r%s-%s">return</a></small></div>' % (pre_id, id, pml_to_html(pml), pre_id, id) + if id.strip(): + html = '<br /><br style="page-break-after: always;" /><div id="%s-%s">%s<small><a href="#r%s-%s">return</a></small></div>' % (pre_id, id, pml_to_html(pml), pre_id, id) + else: + html = '<br /><br style="page-break-after: always;" /><div>%s</div>' % pml_to_html(pml) return html def footnote_to_html(id, pml): diff --git a/src/calibre/ebooks/pml/pmlml.py b/src/calibre/ebooks/pml/pmlml.py index 779e75d713..b04aaacaec 100644 --- a/src/calibre/ebooks/pml/pmlml.py +++ b/src/calibre/ebooks/pml/pmlml.py @@ -12,8 +12,6 @@ import re from lxml import etree -from calibre.ebooks.oeb.base import XHTML, XHTML_NS, barename, namespace -from calibre.ebooks.oeb.stylizer import Stylizer from calibre.ebooks.pdb.ereader import image_name from calibre.ebooks.pml import unipmlcode @@ -110,6 +108,9 @@ class PMLMLizer(object): return output def get_cover_page(self): + from calibre.ebooks.oeb.stylizer import Stylizer + from calibre.ebooks.oeb.base import XHTML + output = u'' if 'cover' in self.oeb_book.guide: output += '\\m="cover.png"\n' @@ -125,6 +126,9 @@ class PMLMLizer(object): return output def get_text(self): + from calibre.ebooks.oeb.stylizer import Stylizer + from calibre.ebooks.oeb.base import XHTML + text = [u''] for item in self.oeb_book.spine: self.log.debug('Converting %s to PML markup...' % item.href) @@ -180,7 +184,7 @@ class PMLMLizer(object): links = set(re.findall(r'(?<=\\q="#).+?(?=")', text)) for unused in anchors.difference(links): text = text.replace('\\Q="%s"' % unused, '') - + # Remove \Cn tags that are within \x and \Xn tags text = re.sub(ur'(?msu)(?P<t>\\(x|X[0-4]))(?P<a>.*?)(?P<c>\\C[0-4]\s*=\s*"[^"]*")(?P<b>.*?)(?P=t)', '\g<t>\g<a>\g<b>\g<t>', text) @@ -214,6 +218,8 @@ class PMLMLizer(object): return text def dump_text(self, elem, stylizer, page, tag_stack=[]): + from calibre.ebooks.oeb.base import XHTML_NS, barename, namespace + if not isinstance(elem.tag, basestring) \ or namespace(elem.tag) != XHTML_NS: return [] diff --git a/src/calibre/ebooks/rb/rbml.py b/src/calibre/ebooks/rb/rbml.py index 50153d7d4d..8cf63e334c 100644 --- a/src/calibre/ebooks/rb/rbml.py +++ b/src/calibre/ebooks/rb/rbml.py @@ -11,8 +11,6 @@ Transform OEB content into RB compatible markup. import re from calibre import prepare_string_for_xml -from calibre.ebooks.oeb.base import XHTML, XHTML_NS, barename, namespace -from calibre.ebooks.oeb.stylizer import Stylizer from calibre.ebooks.rb import unique_name TAGS = [ @@ -81,6 +79,8 @@ class RBMLizer(object): return output def get_cover_page(self): + from calibre.ebooks.oeb.stylizer import Stylizer + from calibre.ebooks.oeb.base import XHTML output = u'' if 'cover' in self.oeb_book.guide: if self.name_map.get(self.oeb_book.guide['cover'].href, None): @@ -109,6 +109,9 @@ class RBMLizer(object): return ''.join(toc) def get_text(self): + from calibre.ebooks.oeb.stylizer import Stylizer + from calibre.ebooks.oeb.base import XHTML + output = [u''] for item in self.oeb_book.spine: self.log.debug('Converting %s to RocketBook HTML...' % item.href) @@ -137,6 +140,8 @@ class RBMLizer(object): return text def dump_text(self, elem, stylizer, page, tag_stack=[]): + from calibre.ebooks.oeb.base import XHTML_NS, barename, namespace + if not isinstance(elem.tag, basestring) \ or namespace(elem.tag) != XHTML_NS: return [u''] diff --git a/src/calibre/ebooks/rb/writer.py b/src/calibre/ebooks/rb/writer.py index c8908ee95f..f71b103fbd 100644 --- a/src/calibre/ebooks/rb/writer.py +++ b/src/calibre/ebooks/rb/writer.py @@ -18,7 +18,6 @@ import cStringIO from calibre.ebooks.rb.rbml import RBMLizer from calibre.ebooks.rb import HEADER from calibre.ebooks.rb import unique_name -from calibre.ebooks.oeb.base import OEB_RASTER_IMAGES from calibre.constants import __appname__, __version__ TEXT_RECORD_SIZE = 4096 @@ -111,6 +110,7 @@ class RBWriter(object): return (size, pages) def _images(self, manifest): + from calibre.ebooks.oeb.base import OEB_RASTER_IMAGES images = [] used_names = [] diff --git a/src/calibre/ebooks/rtf/input.py b/src/calibre/ebooks/rtf/input.py index 52f6feb071..f08aa76605 100644 --- a/src/calibre/ebooks/rtf/input.py +++ b/src/calibre/ebooks/rtf/input.py @@ -22,6 +22,7 @@ border_style_map = { 'dot-dot-dash': 'dotted', 'outset': 'outset', 'tripple': 'double', + 'triple': 'double', 'thick-thin-small': 'solid', 'thin-thick-small': 'solid', 'thin-thick-thin-small': 'solid', @@ -85,7 +86,7 @@ class RTFInput(InputFormatPlugin): run_lev = 4 self.log('Running RTFParser in debug mode') except: - pass + self.log.warn('Impossible to run RTFParser in debug mode') parser = ParseRtf( in_file = stream, out_file = ofile, @@ -267,7 +268,7 @@ class RTFInput(InputFormatPlugin): self.log('Converting RTF to XML...') try: xml = self.generate_xml(stream.name) - except RtfInvalidCodeException, e: + except RtfInvalidCodeException as e: raise ValueError(_('This RTF file has a feature calibre does not ' 'support. Convert it to HTML first and then try it.\n%s')%e) diff --git a/src/calibre/ebooks/rtf/rtfml.py b/src/calibre/ebooks/rtf/rtfml.py index f739207018..f3febb1743 100644 --- a/src/calibre/ebooks/rtf/rtfml.py +++ b/src/calibre/ebooks/rtf/rtfml.py @@ -14,11 +14,7 @@ import cStringIO from lxml import etree -from calibre.ebooks.oeb.base import XHTML, XHTML_NS, barename, namespace, \ - OEB_RASTER_IMAGES -from calibre.ebooks.oeb.stylizer import Stylizer from calibre.ebooks.metadata import authors_to_string -from calibre.utils.filenames import ascii_text from calibre.utils.magick.draw import save_cover_data_to, identify_data TAGS = { @@ -82,8 +78,7 @@ def txt2rtf(text): elif val <= 127: buf.write(x) else: - repl = ascii_text(x) - c = r'\uc{2}\u{0:d}{1}'.format(val, repl, len(repl)) + c = r'\u{0:d}?'.format(val) buf.write(c) return buf.getvalue() @@ -100,6 +95,8 @@ class RTFMLizer(object): return self.mlize_spine() def mlize_spine(self): + from calibre.ebooks.oeb.base import XHTML + from calibre.ebooks.oeb.stylizer import Stylizer output = self.header() if 'titlepage' in self.oeb_book.guide: href = self.oeb_book.guide['titlepage'].href @@ -154,6 +151,8 @@ class RTFMLizer(object): return ' }' def insert_images(self, text): + from calibre.ebooks.oeb.base import OEB_RASTER_IMAGES + for item in self.oeb_book.manifest: if item.media_type in OEB_RASTER_IMAGES: src = os.path.basename(item.href) @@ -201,6 +200,8 @@ class RTFMLizer(object): return text def dump_text(self, elem, stylizer, tag_stack=[]): + from calibre.ebooks.oeb.base import XHTML_NS, namespace, barename + if not isinstance(elem.tag, basestring) \ or namespace(elem.tag) != XHTML_NS: p = elem.getparent() diff --git a/src/calibre/ebooks/rtf2xml/colors.py b/src/calibre/ebooks/rtf2xml/colors.py index e85b59571c..70c069eb80 100755 --- a/src/calibre/ebooks/rtf2xml/colors.py +++ b/src/calibre/ebooks/rtf2xml/colors.py @@ -245,8 +245,11 @@ class Colors: self.__token_info = line[:16] action = self.__state_dict.get(self.__state) if action is None: - sys.stderr.write('no matching state in module fonts.py\n') - sys.stderr.write(self.__state + '\n') + try: + sys.stderr.write('no matching state in module fonts.py\n') + sys.stderr.write(self.__state + '\n') + except: + pass action(line) copy_obj = copy.Copy(bug_handler = self.__bug_handler) if self.__copy: diff --git a/src/calibre/ebooks/rtf2xml/make_lists.py b/src/calibre/ebooks/rtf2xml/make_lists.py index fc544ac499..b785bf85d2 100755 --- a/src/calibre/ebooks/rtf2xml/make_lists.py +++ b/src/calibre/ebooks/rtf2xml/make_lists.py @@ -291,9 +291,12 @@ class MakeLists: if self.__list_of_lists: # older RTF won't generate a list_of_lists index_of_list = self.__get_index_of_list(id) if index_of_list != None:# found a matching id - list_dict = self.__list_of_lists[index_of_list][0] + curlist = self.__list_of_lists[index_of_list] + list_dict = curlist[0] level = int(self.__level) + 1 - level_dict = self.__list_of_lists[index_of_list][level][0] + if level >= len(curlist): + level = len(curlist) - 1 + level_dict = curlist[level][0] list_type = level_dict.get('numbering-type') if list_type == 'bullet': list_type = 'unordered' diff --git a/src/calibre/ebooks/rtf2xml/process_tokens.py b/src/calibre/ebooks/rtf2xml/process_tokens.py index 65162d0d37..11aab48588 100755 --- a/src/calibre/ebooks/rtf2xml/process_tokens.py +++ b/src/calibre/ebooks/rtf2xml/process_tokens.py @@ -197,8 +197,8 @@ class ProcessTokens: # character info => ci 'b' : ('ci', 'bold______', self.bool_st_func), 'blue' : ('ci', 'blue______', self.color_func), - 'caps' : ('ci', 'caps______', self.bool_st_func), - 'cf' : ('ci', 'font-color', self.default_func), + 'caps' : ('ci', 'caps______', self.bool_st_func), + 'cf' : ('ci', 'font-color', self.colorz_func), 'chftn' : ('ci', 'footnot-mk', self.bool_st_func), 'dn' : ('ci', 'font-down_', self.divide_by_2), 'embo' : ('ci', 'emboss____', self.bool_st_func), @@ -624,6 +624,11 @@ class ProcessTokens: num = 'true' return 'cw<%s<%s<nu<%s\n' % (pre, token, num) + def colorz_func(self, pre, token, num): + if num is None: + num = '0' + return 'cw<%s<%s<nu<%s\n' % (pre, token, num) + def __list_type_func(self, pre, token, num): type = 'arabic' if num is None: diff --git a/src/calibre/ebooks/rtf2xml/tokenize.py b/src/calibre/ebooks/rtf2xml/tokenize.py index 3dcaf0fcb1..25640586db 100755 --- a/src/calibre/ebooks/rtf2xml/tokenize.py +++ b/src/calibre/ebooks/rtf2xml/tokenize.py @@ -46,7 +46,8 @@ class Tokenize: def __remove_uc_chars(self, startchar, token): for i in xrange(startchar, len(token)): - if token[i] == " ": + #handle the case of an uc char with a terminating blank before ansi char + if token[i] == " " and self.__uc_char: continue elif self.__uc_char: self.__uc_char -= 1 diff --git a/src/calibre/ebooks/snb/input.py b/src/calibre/ebooks/snb/input.py index 100ac1447f..13b1ca45f9 100755 --- a/src/calibre/ebooks/snb/input.py +++ b/src/calibre/ebooks/snb/input.py @@ -7,7 +7,6 @@ __docformat__ = 'restructuredtext en' import os, uuid from calibre.customize.conversion import InputFormatPlugin -from calibre.ebooks.oeb.base import DirContainer from calibre.ebooks.snb.snbfile import SNBFile from calibre.ptempfile import TemporaryDirectory from calibre.utils.filenames import ascii_filename @@ -30,6 +29,7 @@ class SNBInput(InputFormatPlugin): def convert(self, stream, options, file_ext, log, accelerators): + from calibre.ebooks.oeb.base import DirContainer log.debug("Parsing SNB file...") snbFile = SNBFile() try: diff --git a/src/calibre/ebooks/snb/snbfile.py b/src/calibre/ebooks/snb/snbfile.py index 9a7d65e417..be4e537825 100644 --- a/src/calibre/ebooks/snb/snbfile.py +++ b/src/calibre/ebooks/snb/snbfile.py @@ -5,7 +5,8 @@ __copyright__ = '2010, Li Fanxi <lifanxi@freemindworld.com>' __docformat__ = 'restructuredtext en' import sys, struct, zlib, bz2, os -from mimetypes import types_map + +from calibre import guess_type class FileStream: def IsBinary(self): @@ -85,7 +86,7 @@ class SNBFile: uncompressedData += bzdc.decompress(data) else: uncompressedData += data - except Exception, e: + except Exception as e: print e if len(uncompressedData) != self.plainStreamSizeUncompressed: raise Exception() @@ -180,7 +181,7 @@ class SNBFile: file = open(os.path.join(path, fname), 'wb') file.write(f.fileBody) file.close() - fileNames.append((fname, types_map[ext])) + fileNames.append((fname, guess_type('a'+ext)[0])) return fileNames def Output(self, outputFile): diff --git a/src/calibre/ebooks/snb/snbml.py b/src/calibre/ebooks/snb/snbml.py index 078e7ebe76..a501de1ff0 100644 --- a/src/calibre/ebooks/snb/snbml.py +++ b/src/calibre/ebooks/snb/snbml.py @@ -13,8 +13,6 @@ import re from lxml import etree -from calibre.ebooks.oeb.base import XHTML, XHTML_NS, barename, namespace -from calibre.ebooks.oeb.stylizer import Stylizer def ProcessFileName(fileName): # Flat the path @@ -81,6 +79,8 @@ class SNBMLizer(object): body.append(entity) def mlize(self): + from calibre.ebooks.oeb.base import XHTML + from calibre.ebooks.oeb.stylizer import Stylizer output = [ u'' ] stylizer = Stylizer(self.item.data, self.item.href, self.oeb_book, self.opts, self.opts.output_profile) content = unicode(etree.tostring(self.item.data.find(XHTML('body')), encoding=unicode)) @@ -208,6 +208,7 @@ class SNBMLizer(object): return text def dump_text(self, subitems, elem, stylizer, end='', pre=False, li = ''): + from calibre.ebooks.oeb.base import XHTML_NS, barename, namespace if not isinstance(elem.tag, basestring) \ or namespace(elem.tag) != XHTML_NS: diff --git a/src/calibre/ebooks/textile/functions.py b/src/calibre/ebooks/textile/functions.py old mode 100644 new mode 100755 index eca4bcecff..e088d264fc --- a/src/calibre/ebooks/textile/functions.py +++ b/src/calibre/ebooks/textile/functions.py @@ -1,15 +1,19 @@ #!/usr/bin/env python +# -*- coding: utf-8 -*- + """ PyTextile A Humane Web Text Generator """ -__version__ = '2.1.4' - -__date__ = '2009/12/04' +# Last upstream version basis +# __version__ = '2.1.4' +#__date__ = '2009/12/04' __copyright__ = """ +Copyright (c) 2011, Leigh Parry <leighparry@blueyonder.co.uk> +Copyright (c) 2011, John Schember <john@nachtimwald.com> Copyright (c) 2009, Jason Samsa, http://jsamsa.com/ Copyright (c) 2004, Roberto A. F. De Almeida, http://dealmeida.net/ Copyright (c) 2003, Mark Pilgrim, http://diveintomark.org/ @@ -60,6 +64,8 @@ import re import uuid from urlparse import urlparse +from calibre.utils.smartypants import smartyPants + def _normalize_newlines(string): out = re.sub(r'\r\n', '\n', string) out = re.sub(r'\n{3,}', '\n\n', out) @@ -119,22 +125,113 @@ class Textile(object): btag = ('bq', 'bc', 'notextile', 'pre', 'h[1-6]', 'fn\d+', 'p') btag_lite = ('bq', 'bc', 'p') - glyph_defaults = ( - ('txt_quote_single_open', '‘'), - ('txt_quote_single_close', '’'), - ('txt_quote_double_open', '“'), - ('txt_quote_double_close', '”'), - ('txt_apostrophe', '’'), - ('txt_prime', '′'), - ('txt_prime_double', '″'), - ('txt_ellipsis', '…'), - ('txt_emdash', '—'), - ('txt_endash', '–'), - ('txt_dimension', '×'), - ('txt_trademark', '™'), - ('txt_registered', '®'), - ('txt_copyright', '©'), - ) + macro_defaults = [ + (re.compile(r'{(c\||\|c)}'), r'¢'), # cent + (re.compile(r'{(L-|-L)}'), r'£'), # pound + (re.compile(r'{(Y=|=Y)}'), r'¥'), # yen + (re.compile(r'{\(c\)}'), r'©'), # copyright + (re.compile(r'{\(r\)}'), r'®'), # registered + (re.compile(r'{(\+_|_\+)}'), r'±'), # plus-minus + (re.compile(r'{1/4}'), r'¼'), # quarter + (re.compile(r'{1/2}'), r'½'), # half + (re.compile(r'{3/4}'), r'¾'), # three-quarter + (re.compile(r'{(A`|`A)}'), r'À'), # A-acute + (re.compile(r'{(A\'|\'A)}'), r'Á'), # A-grave + (re.compile(r'{(A\^|\^A)}'), r'Â'), # A-circumflex + (re.compile(r'{(A~|~A)}'), r'Ã'), # A-tilde + (re.compile(r'{(A\"|\"A)}'), r'Ä'), # A-diaeresis + (re.compile(r'{(Ao|oA)}'), r'Å'), # A-ring + (re.compile(r'{(AE)}'), r'Æ'), # AE + (re.compile(r'{(C,|,C)}'), r'Ç'), # C-cedilla + (re.compile(r'{(E`|`E)}'), r'È'), # E-acute + (re.compile(r'{(E\'|\'E)}'), r'É'), # E-grave + (re.compile(r'{(E\^|\^E)}'), r'Ê'), # E-circumflex + (re.compile(r'{(E\"|\"E)}'), r'Ë'), # E-diaeresis + (re.compile(r'{(I`|`I)}'), r'Ì'), # I-acute + (re.compile(r'{(I\'|\'I)}'), r'Í'), # I-grave + (re.compile(r'{(I\^|\^I)}'), r'Î'), # I-circumflex + (re.compile(r'{(I\"|\"I)}'), r'Ï'), # I-diaeresis + (re.compile(r'{(D-|-D)}'), r'Ð'), # ETH + (re.compile(r'{(N~|~N)}'), r'Ñ'), # N-tilde + (re.compile(r'{(O`|`O)}'), r'Ò'), # O-acute + (re.compile(r'{(O\'|\'O)}'), r'Ó'), # O-grave + (re.compile(r'{(O\^|\^O)}'), r'Ô'), # O-circumflex + (re.compile(r'{(O~|~O)}'), r'Õ'), # O-tilde + (re.compile(r'{(O\"|\"O)}'), r'Ö'), # O-diaeresis + (re.compile(r'{x}'), r'×'), # dimension + (re.compile(r'{(O\/|\/O)}'), r'Ø'), # O-slash + (re.compile(r'{(U`|`U)}'), r'Ù'), # U-acute + (re.compile(r'{(U\'|\'U)}'), r'Ú'), # U-grave + (re.compile(r'{(U\^|\^U)}'), r'Û'), # U-circumflex + (re.compile(r'{(U\"|\"U)}'), r'Ü'), # U-diaeresis + (re.compile(r'{(Y\'|\'Y)}'), r'Ý'), # Y-grave + (re.compile(r'{sz}'), r'ß'), # sharp-s + (re.compile(r'{(a`|`a)}'), r'à'), # a-grave + (re.compile(r'{(a\'|\'a)}'), r'á'), # a-acute + (re.compile(r'{(a\^|\^a)}'), r'â'), # a-circumflex + (re.compile(r'{(a~|~a)}'), r'ã'), # a-tilde + (re.compile(r'{(a\"|\"a)}'), r'ä'), # a-diaeresis + (re.compile(r'{(ao|oa)}'), r'å'), # a-ring + (re.compile(r'{ae}'), r'æ'), # ae + (re.compile(r'{(c,|,c)}'), r'ç'), # c-cedilla + (re.compile(r'{(e`|`e)}'), r'è'), # e-grave + (re.compile(r'{(e\'|\'e)}'), r'é'), # e-acute + (re.compile(r'{(e\^|\^e)}'), r'ê'), # e-circumflex + (re.compile(r'{(e\"|\"e)}'), r'ë'), # e-diaeresis + (re.compile(r'{(i`|`i)}'), r'ì'), # i-grave + (re.compile(r'{(i\'|\'i)}'), r'í'), # i-acute + (re.compile(r'{(i\^|\^i)}'), r'î'), # i-circumflex + (re.compile(r'{(i\"|\"i)}'), r'ï'), # i-diaeresis + (re.compile(r'{(d-|-d)}'), r'ð'), # eth + (re.compile(r'{(n~|~n)}'), r'ñ'), # n-tilde + (re.compile(r'{(o`|`o)}'), r'ò'), # o-grave + (re.compile(r'{(o\'|\'o)}'), r'ó'), # o-acute + (re.compile(r'{(o\^|\^o)}'), r'ô'), # o-circumflex + (re.compile(r'{(o~|~o)}'), r'õ'), # o-tilde + (re.compile(r'{(o\"|\"o)}'), r'ö'), # o-diaeresis + (re.compile(r'{(o\/|\/o)}'), r'ø'), # o-stroke + (re.compile(r'{(u`|`u)}'), r'ù'), # u-grave + (re.compile(r'{(u\'|\'u)}'), r'ú'), # u-acute + (re.compile(r'{(u\^|\^u)}'), r'û'), # u-circumflex + (re.compile(r'{(u\"|\"u)}'), r'ü'), # u-diaeresis + (re.compile(r'{(y\'|\'y)}'), r'ý'), # y-acute + (re.compile(r'{(y\"|\"y)}'), r'ÿ'), # y-diaeresis + (re.compile(r'{OE}'), r'Œ'), # OE + (re.compile(r'{oe}'), r'œ'), # oe + (re.compile(r'{(S\^|\^S)}'), r'Š'), # Scaron + (re.compile(r'{(s\^|\^s)}'), r'š'), # scaron + (re.compile(r'{\*}'), r'•'), # bullet + (re.compile(r'{Fr}'), r'₣'), # Franc + (re.compile(r'{(L=|=L)}'), r'₤'), # Lira + (re.compile(r'{Rs}'), r'₨'), # Rupee + (re.compile(r'{(C=|=C)}'), r'€'), # euro + (re.compile(r'{tm}'), r'™'), # trademark + (re.compile(r'{spades?}'), r'♠'), # spade + (re.compile(r'{clubs?}'), r'♣'), # club + (re.compile(r'{hearts?}'), r'♥'), # heart + (re.compile(r'{diam(onds?|s)}'), r'♦'), # diamond + (re.compile(r'{"}'), r'"'), # double-quote + (re.compile(r"{'}"), r'''), # single-quote + (re.compile(r"{(’|'/|/')}"), r'’'), # closing-single-quote - apostrophe + (re.compile(r"{(‘|\\'|'\\)}"), r'‘'), # opening-single-quote + (re.compile(r'{(”|"/|/")}'), r'”'), # closing-double-quote + (re.compile(r'{(“|\\"|"\\)}'), r'“'), # opening-double-quote + ] + glyph_defaults = [ + (re.compile(r'(\d+\'?\"?)( ?)x( ?)(?=\d+)'), r'\1\2×\3'), # dimension sign + (re.compile(r'(\d+)\'(\s)', re.I), r'\1′\2'), # prime + (re.compile(r'(\d+)\"(\s)', re.I), r'\1″\2'), # prime-double + (re.compile(r'\b([A-Z][A-Z0-9]{2,})\b(?:[(]([^)]*)[)])'), r'<acronym title="\2">\1</acronym>'), # 3+ uppercase acronym + (re.compile(r'\b([A-Z][A-Z\'\-]+[A-Z])(?=[\s.,\)>])'), r'<span class="caps">\1</span>'), # 3+ uppercase + (re.compile(r'\b(\s{0,1})?\.{3}'), r'\1…'), # ellipsis + (re.compile(r'^[\*_-]{3,}$', re.M), r'<hr />'), # <hr> scene-break + (re.compile(r'(^|[^-])--([^-]|$)'), r'\1—\2'), # em dash + (re.compile(r'\s-(?:\s|$)'), r' – '), # en dash + (re.compile(r'\b( ?)[([]TM[])]', re.I), r'\1™'), # trademark + (re.compile(r'\b( ?)[([]R[])]', re.I), r'\1®'), # registered + (re.compile(r'\b( ?)[([]C[])]', re.I), r'\1©'), # copyright + ] + def __init__(self, restricted=False, lite=False, noimage=False): """docstring for __init__""" @@ -166,10 +263,9 @@ class Textile(object): self.rel = ' rel="%s"' % rel text = self.getRefs(text) - text = self.block(text, int(head_offset)) - text = self.retrieve(text) + text = smartyPants(text, 'q') return text @@ -593,49 +689,33 @@ class Textile(object): '<p><cite>Cat’s Cradle</cite> by Vonnegut</p>' """ - # fix: hackish + # fix: hackish text = re.sub(r'"\Z', '\" ', text) - glyph_search = ( - re.compile(r"(\w)\'(\w)"), # apostrophe's - re.compile(r'(\s)\'(\d+\w?)\b(?!\')'), # back in '88 - re.compile(r'(\S)\'(?=\s|'+self.pnct+'|<|$)'), # single closing - re.compile(r'\'/'), # single opening - re.compile(r'(\S)\"(?=\s|'+self.pnct+'|<|$)'), # double closing - re.compile(r'"'), # double opening - re.compile(r'\b([A-Z][A-Z0-9]{2,})\b(?:[(]([^)]*)[)])'), # 3+ uppercase acronym - re.compile(r'\b([A-Z][A-Z\'\-]+[A-Z])(?=[\s.,\)>])'), # 3+ uppercase - re.compile(r'\b(\s{0,1})?\.{3}'), # ellipsis - re.compile(r'(\s?)--(\s?)'), # em dash - re.compile(r'\s-(?:\s|$)'), # en dash - re.compile(r'(\d+)( ?)x( ?)(?=\d+)'), # dimension sign - re.compile(r'\b ?[([]TM[])]', re.I), # trademark - re.compile(r'\b ?[([]R[])]', re.I), # registered - re.compile(r'\b ?[([]C[])]', re.I), # copyright - ) - - glyph_replace = [x % dict(self.glyph_defaults) for x in ( - r'\1%(txt_apostrophe)s\2', # apostrophe's - r'\1%(txt_apostrophe)s\2', # back in '88 - r'\1%(txt_quote_single_close)s', # single closing - r'%(txt_quote_single_open)s', # single opening - r'\1%(txt_quote_double_close)s', # double closing - r'%(txt_quote_double_open)s', # double opening - r'<acronym title="\2">\1</acronym>', # 3+ uppercase acronym - r'<span class="caps">\1</span>', # 3+ uppercase - r'\1%(txt_ellipsis)s', # ellipsis - r'\1%(txt_emdash)s\2', # em dash - r' %(txt_endash)s ', # en dash - r'\1\2%(txt_dimension)s\3', # dimension sign - r'%(txt_trademark)s', # trademark - r'%(txt_registered)s', # registered - r'%(txt_copyright)s', # copyright - )] - result = [] for line in re.compile(r'(<.*?>)', re.U).split(text): if not re.search(r'<.*>', line): - for s, r in zip(glyph_search, glyph_replace): + rules = [] + if re.search(r'{.+?}', line): + rules = self.macro_defaults + self.glyph_defaults + else: + rules = self.glyph_defaults + for s, r in rules: + line = s.sub(r, line) + result.append(line) + return ''.join(result) + + def macros_only(self, text): + # fix: hackish + text = re.sub(r'"\Z', '\" ', text) + + result = [] + for line in re.compile(r'(<.*?>)', re.U).split(text): + if not re.search(r'<.*>', line): + rules = [] + if re.search(r'{.+?}', line): + rules = self.macro_defaults + for s, r in rules: line = s.sub(r, line) result.append(line) return ''.join(result) @@ -685,7 +765,7 @@ class Textile(object): return url def shelve(self, text): - id = str(uuid.uuid4()) + id = str(uuid.uuid4()) + 'c' self.shelf[id] = text return id @@ -748,6 +828,7 @@ class Textile(object): 'fooobar ... and hello world ...' """ + text = self.macros_only(text) punct = '!"#$%&\'*+,-./:;=?@\\^_`|~' pattern = r''' @@ -807,7 +888,7 @@ class Textile(object): for qtag in qtags: pattern = re.compile(r""" - (?:^|(?<=[\s>%(pnct)s])|([\]}])) + (?:^|(?<=[\s>%(pnct)s\(])|\[|([\]}])) (%(qtag)s)(?!%(qtag)s) (%(c)s) (?::(\S+))? @@ -978,4 +1059,3 @@ def textile_restricted(text, lite=True, noimage=True, html_type='xhtml'): return Textile(restricted=True, lite=lite, noimage=noimage).textile(text, rel='nofollow', html_type=html_type) - diff --git a/src/calibre/ebooks/txt/input.py b/src/calibre/ebooks/txt/input.py index 1c49eb9b35..99f7035800 100644 --- a/src/calibre/ebooks/txt/input.py +++ b/src/calibre/ebooks/txt/input.py @@ -22,7 +22,7 @@ class TXTInput(InputFormatPlugin): name = 'TXT Input' author = 'John Schember' description = 'Convert TXT files to HTML' - file_types = set(['txt', 'txtz']) + file_types = set(['txt', 'txtz', 'text']) options = set([ OptionRecommendation(name='paragraph_type', recommended_value='auto', @@ -65,7 +65,6 @@ class TXTInput(InputFormatPlugin): txt = '' log.debug('Reading text from file...') length = 0 - # [(u'path', mime),] # Extract content from zip archive. if file_ext == 'txtz': @@ -73,7 +72,7 @@ class TXTInput(InputFormatPlugin): zf.extractall('.') for x in walk('.'): - if os.path.splitext(x)[1].lower() == '.txt': + if os.path.splitext(x)[1].lower() in ('.txt', '.text'): with open(x, 'rb') as tf: txt += tf.read() + '\n\n' else: diff --git a/src/calibre/ebooks/txt/markdownml.py b/src/calibre/ebooks/txt/markdownml.py index c179378049..fe76757eab 100644 --- a/src/calibre/ebooks/txt/markdownml.py +++ b/src/calibre/ebooks/txt/markdownml.py @@ -37,7 +37,7 @@ class MarkdownMLizer(object): if not self.opts.keep_links: html = re.sub(r'<\s*/*\s*a[^>]*>', '', html) if not self.opts.keep_image_references: - html = re.sub(r'<\s*img[^>]*>', '', html)\ + html = re.sub(r'<\s*img[^>]*>', '', html) text = html2text(html) diff --git a/src/calibre/ebooks/txt/output.py b/src/calibre/ebooks/txt/output.py index d021cbbba6..d9c42eb1dc 100644 --- a/src/calibre/ebooks/txt/output.py +++ b/src/calibre/ebooks/txt/output.py @@ -11,7 +11,6 @@ from lxml import etree from calibre.customize.conversion import OutputFormatPlugin, \ OptionRecommendation -from calibre.ebooks.oeb.base import OEB_IMAGES from calibre.ebooks.txt.txtml import TXTMLizer from calibre.ebooks.txt.newlines import TxtNewlines, specified_newlines from calibre.ptempfile import TemporaryDirectory, TemporaryFile @@ -67,19 +66,26 @@ class TXTOutput(OutputFormatPlugin): help=_('Do not remove image references within the document. This is only ' \ 'useful when paired with a txt-output-formatting option that ' 'is not none because links are always removed with plain text output.')), + OptionRecommendation(name='keep_color', + recommended_value=False, level=OptionRecommendation.LOW, + help=_('Do not remove font color from output. This is only useful when ' \ + 'txt-output-formatting is set to textile. Textile is the only ' \ + 'formatting that supports setting font color. If this option is ' \ + 'not specified font color will not be set and default to the ' \ + 'color displayed by the reader (generally this is black).')), ]) def convert(self, oeb_book, output_path, input_plugin, opts, log): if opts.txt_output_formatting.lower() == 'markdown': from calibre.ebooks.txt.markdownml import MarkdownMLizer - writer = MarkdownMLizer(log) + self.writer = MarkdownMLizer(log) elif opts.txt_output_formatting.lower() == 'textile': from calibre.ebooks.txt.textileml import TextileMLizer - writer = TextileMLizer(log) + self.writer = TextileMLizer(log) else: - writer = TXTMLizer(log) + self.writer = TXTMLizer(log) - txt = writer.extract_content(oeb_book, opts) + txt = self.writer.extract_content(oeb_book, opts) txt = clean_ascii_chars(txt) log.debug('\tReplacing newlines with selected type...') @@ -103,30 +109,42 @@ class TXTOutput(OutputFormatPlugin): class TXTZOutput(TXTOutput): - + name = 'TXTZ Output' author = 'John Schember' file_type = 'txtz' def convert(self, oeb_book, output_path, input_plugin, opts, log): + from calibre.ebooks.oeb.base import OEB_IMAGES with TemporaryDirectory('_txtz_output') as tdir: # TXT - with TemporaryFile('index.txt') as tf: + txt_name = 'index.txt' + if opts.txt_output_formatting.lower() == 'textile': + txt_name = 'index.text' + with TemporaryFile(txt_name) as tf: TXTOutput.convert(self, oeb_book, tf, input_plugin, opts, log) - shutil.copy(tf, os.path.join(tdir, 'index.txt')) + shutil.copy(tf, os.path.join(tdir, txt_name)) # Images for item in oeb_book.manifest: if item.media_type in OEB_IMAGES: - path = os.path.join(tdir, os.path.dirname(item.href)) + if hasattr(self.writer, 'images'): + path = os.path.join(tdir, 'images') + if item.href in self.writer.images: + href = self.writer.images[item.href] + else: + continue + else: + path = os.path.join(tdir, os.path.dirname(item.href)) + href = os.path.basename(item.href) if not os.path.exists(path): os.makedirs(path) - with open(os.path.join(tdir, item.href), 'wb') as imgf: + with open(os.path.join(path, href), 'wb') as imgf: imgf.write(item.data) - + # Metadata - with open(os.path.join(tdir, 'metadata.opf'), 'wb') as mdataf: + with open(os.path.join(tdir, 'metadata.opf'), 'wb') as mdataf: mdataf.write(etree.tostring(oeb_book.metadata.to_opf1())) - + txtz = ZipFile(output_path, 'w') txtz.add_dir(tdir) diff --git a/src/calibre/ebooks/txt/processor.py b/src/calibre/ebooks/txt/processor.py index 7e161f63bd..54369190de 100644 --- a/src/calibre/ebooks/txt/processor.py +++ b/src/calibre/ebooks/txt/processor.py @@ -242,6 +242,8 @@ def detect_formatting_type(txt): textile_count += len(re.findall(r'(?mu)(?<=\!)\S+(?=\!)', txt)) # Links textile_count += len(re.findall(r'"[^"]*":\S+', txt)) + # paragraph blocks + textile_count += len(re.findall(r'(?mu)^p(<|<>|=|>)?\. ', txt)) # Decide if either markdown or textile is used in the text # based on the number of unique formatting elements found. diff --git a/src/calibre/ebooks/txt/textileml.py b/src/calibre/ebooks/txt/textileml.py index d7e11695c5..36dc9952d2 100644 --- a/src/calibre/ebooks/txt/textileml.py +++ b/src/calibre/ebooks/txt/textileml.py @@ -1,62 +1,489 @@ # -*- coding: utf-8 -*- __license__ = 'GPL 3' -__copyright__ = '2011, John Schember <john@nachtimwald.com>' +__copyright__ = '2011, Leigh Parry <leighparry@blueyonder.co.uk>' __docformat__ = 'restructuredtext en' ''' Transform OEB content into Textile formatted plain text ''' - import re -from lxml import etree +from functools import partial -from calibre.ebooks.oeb.base import XHTML -from calibre.utils.html2textile import html2textile +from calibre.ebooks.htmlz.oeb2html import OEB2HTML +from calibre.ebooks.oeb.base import XHTML, XHTML_NS, barename, namespace, rewrite_links +from calibre.ebooks.oeb.stylizer import Stylizer +from calibre.ebooks import unit_convert +from calibre.ebooks.txt.unsmarten import unsmarten -class TextileMLizer(object): - - def __init__(self, log): - self.log = log +class TextileMLizer(OEB2HTML): def extract_content(self, oeb_book, opts): self.log.info('Converting XHTML to Textile formatted TXT...') - self.oeb_book = oeb_book self.opts = opts + self.in_pre = False + self.in_table = False + self.links = {} + self.list = [] + self.our_links = [] + self.in_a_link = False + self.our_ids = [] + self.images = {} + self.id_no_text = u'' + self.style_embed = [] + self.remove_space_after_newline = False + self.base_hrefs = [item.href for item in oeb_book.spine] + self.map_resources(oeb_book) - return self.mlize_spine() + self.style_bold = False + self.style_italic = False + self.style_under = False + self.style_strike = False + self.style_smallcap = False - def mlize_spine(self): + txt = self.mlize_spine(oeb_book) + txt = unsmarten(txt) + + # Do some tidying up + txt = self.tidy_up(txt) + + return txt + + def mlize_spine(self, oeb_book): output = [u''] - - for item in self.oeb_book.spine: + for item in oeb_book.spine: self.log.debug('Converting %s to Textile formatted TXT...' % item.href) + self.rewrite_ids(item.data, item) + rewrite_links(item.data, partial(self.rewrite_link, page=item)) + stylizer = Stylizer(item.data, item.href, oeb_book, self.opts, self.opts.output_profile) + output += self.dump_text(item.data.find(XHTML('body')), stylizer) + output.append('\n\n') + return ''.join(output) - html = unicode(etree.tostring(item.data.find(XHTML('body')), encoding=unicode)) + def tidy_up(self, text): + # May need tweaking and finetuning + def check_escaping(text, tests): + for t in tests: + # I'm not checking for duplicated spans '%' as any that follow each other were being incorrectly merged + txt = '%s' % t + if txt != '%': + text = re.sub(r'([^'+t+'|^\n])'+t+'\]\['+t+'([^'+t+'])', r'\1\2', text) + text = re.sub(r'([^'+t+'|^\n])'+t+t+'([^'+t+'])', r'\1\2', text) + text = re.sub(r'(\s|[*_\'"])\[('+t+'[a-zA-Z0-9 \'",.*_]+'+t+')\](\s|[*_\'"?!,.])', r'\1\2\3', text) + return text - if not self.opts.keep_links: - html = re.sub(r'<\s*/*\s*a[^>]*>', '', html) - if not self.opts.keep_image_references: - html = re.sub(r'<\s*img[^>]*>', '', html) + # Now tidyup links and ids - remove ones that don't have a correponding opposite + if self.opts.keep_links: + for i in self.our_links: + if i[0] == '#': + if i not in self.our_ids: + text = re.sub(r'"(.+)":'+i+'(\s)', r'\1\2', text) + for i in self.our_ids: + if i not in self.our_links: + text = re.sub(r'%?\('+i+'\)\xa0?%?', r'', text) + + # Remove obvious non-needed escaping, add sub/sup-script ones + text = check_escaping(text, ['\*', '_', '\*']) + # escape the super/sub-scripts if needed + text = re.sub(r'(\w)([~^]\w+[~^])', r'\1[\2]', text) + # escape the super/sub-scripts if needed + text = re.sub(r'([~^]\w+[~^])(\w)', r'[\1]\2', text) - text = html2textile(html) + #remove empty spans + text = re.sub(r'%\xa0+', r'%', text) + #remove empty spans - MAY MERGE SOME ? + text = re.sub(r'%%', r'', text) + #remove spans from tagged output + text = re.sub(r'%([_+*-]+)%', r'\1', text) + #remove spaces before a newline + text = re.sub(r' +\n', r'\n', text) + #remove newlines at top of file + text = re.sub(r'^\n+', r'', text) + #correct blockcode paras + text = re.sub(r'\npre\.\n?\nbc\.', r'\nbc.', text) + #correct blockquote paras + text = re.sub(r'\nbq\.\n?\np.*\. ', r'\nbq. ', text) - # Ensure the section ends with at least two new line characters. - # This is to prevent the last paragraph from a section being - # combined into the fist paragraph of the next. - end_chars = text[-4:] - # Convert all newlines to \n - end_chars = end_chars.replace('\r\n', '\n') - end_chars = end_chars.replace('\r', '\n') - end_chars = end_chars[-2:] - if not end_chars[1] == '\n': - text += '\n\n' - if end_chars[1] == '\n' and not end_chars[0] == '\n': - text += '\n' + #reduce blank lines + text = re.sub(r'\n{3}', r'\n\np. \n\n', text) + text = re.sub(u'%\n(p[<>=]{1,2}\.|p\.)', r'%\n\n\1', text) + #Check span following blank para + text = re.sub(r'\n+ +%', r' %', text) + text = re.sub(u'p[<>=]{1,2}\.\n\n?', r'', text) + # blank paragraph + text = re.sub(r'\n(p.*\.)\n', r'\n\1 \n\n', text) + # blank paragraph + text = re.sub(u'\n\xa0', r'\np. ', text) + # blank paragraph + text = re.sub(u'\np[<>=]{1,2}?\. \xa0', r'\np. ', text) + text = re.sub(r'(^|\n)(p.*\. ?\n)(p.*\.)', r'\1\3', text) + text = re.sub(r'\n(p\. \n)(p.*\.|h.*\.)', r'\n\2', text) + #sort out spaces in tables + text = re.sub(r' {2,}\|', r' |', text) - output += text + # Now put back spaces removed earlier as they're needed here + text = re.sub(r'\np\.\n', r'\np. \n', text) + #reduce blank lines + text = re.sub(r' \n\n\n', r' \n\n', text) - output = u''.join(output) + return text - return output + def remove_newlines(self, text): + text = text.replace('\r\n', ' ') + text = text.replace('\n', ' ') + text = text.replace('\r', ' ') + # Condense redundant spaces created by replacing newlines with spaces. + text = re.sub(r'[ ]{2,}', ' ', text) + text = re.sub(r'\t+', '', text) + if self.remove_space_after_newline == True: + text = re.sub(r'^ +', '', text) + self.remove_space_after_newline = False + return text + + def check_styles(self, style): + txt = '{' + if self.opts.keep_color: + if 'color' in style.cssdict() and style['color'] != 'black': + txt += 'color:'+style['color']+';' + if 'background' in style.cssdict(): + txt += 'background:'+style['background']+';' + txt += '}' + if txt == '{}': txt = '' + return txt + + def check_halign(self, style): + tests = {'left':'<','justify':'<>','center':'=','right':'>'} + for i in tests: + if style['text-align'] == i: + return tests[i] + return '' + + def check_valign(self, style): + tests = {'top':'^','bottom':'~'} #, 'middle':'-'} + for i in tests: + if style['vertical-align'] == i: + return tests[i] + return '' + + def check_padding(self, style, stylizer): + txt = '' + left_padding_pts = 0 + left_margin_pts = 0 + if 'padding-left' in style.cssdict() and style['padding-left'] != 'auto': + left_padding_pts = unit_convert(style['padding-left'], style.width, style.fontSize, stylizer.profile.dpi) + if 'margin-left' in style.cssdict() and style['margin-left'] != 'auto': + left_margin_pts = unit_convert(style['margin-left'], style.width, style.fontSize, stylizer.profile.dpi) + left = left_margin_pts + left_padding_pts + emleft = int(round(left / stylizer.profile.fbase)) + if emleft >= 1: + txt += '(' * emleft + right_padding_pts = 0 + right_margin_pts = 0 + if 'padding-right' in style.cssdict() and style['padding-right'] != 'auto': + right_padding_pts = unit_convert(style['padding-right'], style.width, style.fontSize, stylizer.profile.dpi) + if 'margin-right' in style.cssdict() and style['margin-right'] != 'auto': + right_margin_pts = unit_convert(style['margin-right'], style.width, style.fontSize, stylizer.profile.dpi) + right = right_margin_pts + right_padding_pts + emright = int(round(right / stylizer.profile.fbase)) + if emright >= 1: + txt += ')' * emright + + return txt + + def check_id_tag(self, attribs): + txt = '' + if attribs.has_key('id'): + txt = '(#'+attribs['id']+ ')' + self.our_ids.append('#'+attribs['id']) + self.id_no_text = u'\xa0' + return txt + + def build_block(self, tag, style, attribs, stylizer): + txt = '\n' + tag + if self.opts.keep_links: + txt += self.check_id_tag(attribs) + txt += self.check_padding(style, stylizer) + txt += self.check_halign(style) + txt += self.check_styles(style) + return txt + + def prepare_string_for_textile(self, txt): + if re.search(r'(\s([*&_+\-~@%|]|\?{2})\S)|(\S([*&_+\-~@%|]|\?{2})\s)', txt): + return ' ==%s== ' % txt + return txt + + def dump_text(self, elem, stylizer): + ''' + @elem: The element in the etree that we are working on. + @stylizer: The style information attached to the element. + ''' + + # We can only processes tags. If there isn't a tag return any text. + if not isinstance(elem.tag, basestring) \ + or namespace(elem.tag) != XHTML_NS: + p = elem.getparent() + if p is not None and isinstance(p.tag, basestring) and namespace(p.tag) == XHTML_NS \ + and elem.tail: + return [elem.tail] + return [''] + + # Setup our variables. + text = [''] + style = stylizer.style(elem) + tags = [] + tag = barename(elem.tag) + attribs = elem.attrib + + # Ignore anything that is set to not be displayed. + if style['display'] in ('none', 'oeb-page-head', 'oeb-page-foot') \ + or style['visibility'] == 'hidden': + return [''] + + # Soft scene breaks. + if 'margin-top' in style.cssdict() and style['margin-top'] != 'auto': + ems = int(round(float(style.marginTop) / style.fontSize) - 1) + if ems >= 1: + text.append(u'\n\n\xa0' * ems) + + if tag in ('h1', 'h2', 'h3', 'h4', 'h5', 'h6', 'p', 'div'): + if tag == 'div': + tag = 'p' + text.append(self.build_block(tag, style, attribs, stylizer)) + text.append('. ') + tags.append('\n') + + if style['font-style'] == 'italic' or tag in ('i', 'em'): + if tag not in ('h1', 'h2', 'h3', 'h4', 'h5', 'h6', 'cite'): + if self.style_italic == False: + if self.in_a_link: + text.append('_') + tags.append('_') + else: + text.append('[_') + tags.append('_]') + self.style_embed.append('_') + self.style_italic = True + if style['font-weight'] in ('bold', 'bolder') or tag in ('b', 'strong'): + if tag not in ('h1', 'h2', 'h3', 'h4', 'h5', 'h6', 'th'): + if self.style_bold == False: + if self.in_a_link: + text.append('*') + tags.append('*') + else: + text.append('[*') + tags.append('*]') + self.style_embed.append('*') + self.style_bold = True + if style['text-decoration'] == 'underline' or tag in ('u', 'ins'): + if tag != 'a': + if self.style_under == False: + text.append('[+') + tags.append('+]') + self.style_embed.append('+') + self.style_under = True + if style['text-decoration'] == 'line-through' or tag in ('strike', 'del', 's'): + if self.style_strike == False: + text.append('[-') + tags.append('-]') + self.style_embed.append('-') + self.style_strike = True + if tag == 'br': + for i in reversed(self.style_embed): + text.append(i) + text.append('\n') + for i in self.style_embed: + text.append(i) + tags.append('') + self.remove_space_after_newline = True + if tag == 'blockquote': + text.append('\nbq. ') + tags.append('\n') + elif tag in ('abbr', 'acronym'): + text.append('') + txt = attribs['title'] + tags.append('(' + txt + ')') + elif tag == 'sup': + text.append('^') + tags.append('^') + elif tag == 'sub': + text.append('~') + tags.append('~') + elif tag == 'code': + if self.in_pre: + text.append('\nbc. ') + tags.append('') + else: + text.append('@') + tags.append('@') + elif tag == 'cite': + text.append('??') + tags.append('??') + elif tag == 'hr': + text.append('\n***') + tags.append('\n') + elif tag == 'pre': + self.in_pre = True + text.append('\npre. ') + tags.append('pre\n') + elif tag == 'a': + if self.opts.keep_links: + if attribs.has_key('href'): + text.append('"') + tags.append('a') + tags.append('":' + attribs['href']) + self.our_links.append(attribs['href']) + if attribs.has_key('title'): + tags.append('(' + attribs['title'] + ')') + self.in_a_link = True + else: + text.append('%') + tags.append('%') + elif tag == 'img': + if self.opts.keep_image_references: + txt = '!' + self.check_halign(style) + txt += self.check_valign(style) + txt += attribs['src'] + text.append(txt) + if attribs.has_key('alt'): + txt = attribs['alt'] + if txt != '': + text.append('(' + txt + ')') + tags.append('!') + elif tag in ('ol', 'ul'): + self.list.append({'name': tag, 'num': 0}) + text.append('') + tags.append(tag) + elif tag == 'li': + if self.list: li = self.list[-1] + else: li = {'name': 'ul', 'num': 0} + text.append('\n') + if li['name'] == 'ul': + text.append('*' * len(self.list) + ' ') + elif li['name'] == 'ol': + text.append('#' * len(self.list) + ' ') + tags.append('') + elif tag == 'dl': + text.append('\n') + tags.append('') + elif tag == 'dt': + text.append('') + tags.append('\n') + elif tag == 'dd': + text.append(' ') + tags.append('') + elif tag == 'dd': + text.append('') + tags.append('\n') + elif tag == 'table': + txt = self.build_block(tag, style, attribs, stylizer) + txt += '. \n' + if txt != '\ntable. \n': + text.append(txt) + else: + text.append('\n') + tags.append('') + elif tag == 'tr': + txt = self.build_block('', style, attribs, stylizer) + txt += '. ' + if txt != '\n. ': + txt = re.sub ('\n', '', txt) + text.append(txt) + tags.append('|\n') + elif tag == 'td': + text.append('|') + txt = '' + txt += self.check_halign(style) + txt += self.check_valign(style) + if attribs.has_key ('colspan'): + txt += '\\' + attribs['colspan'] + if attribs.has_key ('rowspan'): + txt += '/' + attribs['rowspan'] + txt += self.check_styles(style) + if txt != '': + text.append(txt + '. ') + tags.append('') + elif tag == 'th': + text.append('|_. ') + tags.append('') + elif tag == 'span': + if style['font-variant'] == 'small-caps': + if self.style_smallcap == False: + text.append('&') + tags.append('&') + self.style_smallcap = True + else: + if self.in_a_link == False: + txt = '%' + if self.opts.keep_links: + txt += self.check_id_tag(attribs) + txt += self.check_styles(style) + if txt != '%': + text.append(txt) + tags.append('%') + + if self.opts.keep_links and attribs.has_key('id'): + if tag not in ('body', 'div', 'h1', 'h2', 'h3', 'h4', 'h5', 'h6', 'p', 'span', 'table'): + text.append(self.check_id_tag(attribs)) + + # Process the styles for any that we want to keep + if tag not in ('body', 'div', 'h1', 'h2', 'h3', 'h4', 'h5', 'h6', 'p', 'hr', 'a', 'img', \ + 'span', 'table', 'tr', 'td'): + if not self.in_a_link: + text.append(self.check_styles(style)) + + # Process tags that contain text. + if hasattr(elem, 'text') and elem.text: + txt = elem.text + if not self.in_pre: + txt = self.prepare_string_for_textile(self.remove_newlines(txt)) + text.append(txt) + self.id_no_text = u'' + + # Recurse down into tags within the tag we are in. + for item in elem: + text += self.dump_text(item, stylizer) + + # Close all open tags. + tags.reverse() + for t in tags: + if tag in ('pre', 'ul', 'ol', 'li', 'table'): + if tag == 'pre': + self.in_pre = False + elif tag in ('ul', 'ol'): + if self.list: self.list.pop() + if not self.list: text.append('\n') + else: + if t == 'a': + self.in_a_link = False + t = '' + text.append(self.id_no_text) + self.id_no_text = u'' + if t in ('*]', '*'): + self.style_bold = False + elif t in ('_]', '_'): + self.style_italic = False + elif t == '+]': + self.style_under = False + elif t == '-]': + self.style_strike = False + elif t == '&': + self.style_smallcap = False + if t in ('*]', '_]', '+]', '-]', '*', '_'): + txt = self.style_embed.pop() + text.append('%s' % t) + + # Soft scene breaks. + if 'margin-bottom' in style.cssdict() and style['margin-bottom'] != 'auto': + ems = int(round((float(style.marginBottom) / style.fontSize) - 1)) + if ems >= 1: + text.append(u'\n\n\xa0' * ems) + + # Add the text that is outside of the tag. + if hasattr(elem, 'tail') and elem.tail: + tail = elem.tail + if not self.in_pre: + tail = self.prepare_string_for_textile(self.remove_newlines(tail)) + text.append(tail) + + return text diff --git a/src/calibre/ebooks/txt/txtml.py b/src/calibre/ebooks/txt/txtml.py index fa7bfbb380..2320fbbbc7 100644 --- a/src/calibre/ebooks/txt/txtml.py +++ b/src/calibre/ebooks/txt/txtml.py @@ -12,8 +12,6 @@ import re from lxml import etree -from calibre.ebooks.oeb.base import XHTML, XHTML_NS, barename, namespace -from calibre.ebooks.oeb.stylizer import Stylizer BLOCK_TAGS = [ 'div', @@ -58,12 +56,14 @@ class TXTMLizer(object): self.toc_titles = [] self.toc_ids = [] self.last_was_heading = False - + self.create_flat_toc(self.oeb_book.toc) return self.mlize_spine() def mlize_spine(self): + from calibre.ebooks.oeb.base import XHTML + from calibre.ebooks.oeb.stylizer import Stylizer output = [u''] output.append(self.get_toc()) for item in self.oeb_book.spine: @@ -139,7 +139,7 @@ class TXTMLizer(object): # when remove paragraph spacing is enabled. text = re.sub('(?imu)^[ ]+', '', text) text = re.sub('(?imu)[ ]+$', '', text) - + # Remove empty space and newlines at the beginning of the document. text = re.sub(r'(?u)^[ \n]+', '', text) @@ -185,6 +185,7 @@ class TXTMLizer(object): @stylizer: The style information attached to the element. @page: OEB page used to determine absolute urls. ''' + from calibre.ebooks.oeb.base import XHTML_NS, barename, namespace if not isinstance(elem.tag, basestring) \ or namespace(elem.tag) != XHTML_NS: diff --git a/src/calibre/ebooks/txt/unsmarten.py b/src/calibre/ebooks/txt/unsmarten.py new file mode 100644 index 0000000000..40444ba601 --- /dev/null +++ b/src/calibre/ebooks/txt/unsmarten.py @@ -0,0 +1,108 @@ +# -*- coding: utf-8 -*- + +"""unsmarten : html2textile helper function""" + +__version__ = '0.1' +__author__ = 'Leigh Parry' + +import re + +def unsmarten(txt): + txt = re.sub(u'–|–|–', r'-', txt) # en-dash + txt = re.sub(u'—|—|—', r'--', txt) # em-dash + txt = re.sub(u'…|…|…', r'...', txt) # ellipsis + + txt = re.sub(u'“|”|″|“|”|″|“|”|″', r'"', txt) # double quote + txt = re.sub(u'(["\'‘“]|\s)’', r"\1{'/}", txt) # apostrophe + txt = re.sub(u'‘|’|′|‘|’|′|‘|’|′', r"'", txt) # single quote + + txt = re.sub(u'¢|¢|¢', r'{c\}', txt) # cent + txt = re.sub(u'£|£|£', r'{L-}', txt) # pound + txt = re.sub(u'¥|¥|¥', r'{Y=}', txt) # yen + txt = re.sub(u'©|©|©', r'{(c)}', txt) # copyright + txt = re.sub(u'®|®|®', r'{(r)}', txt) # registered + txt = re.sub(u'¼|¼|¼', r'{1/4}', txt) # quarter + txt = re.sub(u'½|½|½', r'{1/2}', txt) # half + txt = re.sub(u'¾|¾|¾', r'{3/4}', txt) # three-quarter + txt = re.sub(u'À|À|À', r'{A`)}', txt) # A-grave + txt = re.sub(u'Á|Á|Á', r"{A'}", txt) # A-acute + txt = re.sub(u'Â|Â|Â', r'{A^}', txt) # A-circumflex + txt = re.sub(u'Ã|Ã|Ã', r'{A~}', txt) # A-tilde + txt = re.sub(u'Ä|Ä|Ä', r'{A"}', txt) # A-umlaut + txt = re.sub(u'Å|Å|Å', r'{Ao}', txt) # A-ring + txt = re.sub(u'Æ|Æ|Æ', r'{AE}', txt) # AE + txt = re.sub(u'Ç|Ç|Ç', r'{C,}', txt) # C-cedilla + txt = re.sub(u'È|È|È', r'{E`}', txt) # E-grave + txt = re.sub(u'É|É|É', r"{E'}", txt) # E-acute + txt = re.sub(u'Ê|Ê|Ê', r'{E^}', txt) # E-circumflex + txt = re.sub(u'Ë|Ë|Ë', r'{E"}', txt) # E-umlaut + txt = re.sub(u'Ì|Ì|Ì', r'{I`}', txt) # I-grave + txt = re.sub(u'Í|Í|Í', r"{I'}", txt) # I-acute + txt = re.sub(u'Î|Î|Î', r'{I^}', txt) # I-circumflex + txt = re.sub(u'Ï|Ï|Ï', r'{I"}', txt) # I-umlaut + txt = re.sub(u'Ð|Ð|Ð', r'{D-}', txt) # ETH + txt = re.sub(u'Ñ|Ñ|Ñ', r'{N~}', txt) # N-tilde + txt = re.sub(u'Ò|Ò|Ò', r'{O`}', txt) # O-grave + txt = re.sub(u'Ó|Ó|Ó', r"{O'}", txt) # O-acute + txt = re.sub(u'Ô|Ô|Ô', r'{O^}', txt) # O-circumflex + txt = re.sub(u'Õ|Õ|Õ', r'{O~}', txt) # O-tilde + txt = re.sub(u'Ö|Ö|Ö', r'{O"}', txt) # O-umlaut + txt = re.sub(u'×|×|×', r'{x}', txt) # dimension + txt = re.sub(u'Ø|Ø|Ø', r'{O/}', txt) # O-slash + txt = re.sub(u'Ù|Ù|Ù', r"{U`}", txt) # U-grave + txt = re.sub(u'Ú|Ú|Ú', r"{U'}", txt) # U-acute + txt = re.sub(u'Û|Û|Û', r'{U^}', txt) # U-circumflex + txt = re.sub(u'Ü|Ü|Ü', r'{U"}', txt) # U-umlaut + txt = re.sub(u'Ý|Ý|Ý', r"{Y'}", txt) # Y-grave + txt = re.sub(u'ß|ß|ß', r'{sz}', txt) # sharp-s + txt = re.sub(u'à|à|à', r'{a`}', txt) # a-grave + txt = re.sub(u'á|á|á', r"{a'}", txt) # a-acute + txt = re.sub(u'â|â|â', r'{a^}', txt) # a-circumflex + txt = re.sub(u'ã|ã|ã', r'{a~}', txt) # a-tilde + txt = re.sub(u'ä|ä|ä', r'{a"}', txt) # a-umlaut + txt = re.sub(u'å|å|å', r'{ao}', txt) # a-ring + txt = re.sub(u'æ|æ|æ', r'{ae}', txt) # ae + txt = re.sub(u'ç|ç|ç', r'{c,}', txt) # c-cedilla + txt = re.sub(u'è|è|è', r'{e`}', txt) # e-grave + txt = re.sub(u'é|é|é', r"{e'}", txt) # e-acute + txt = re.sub(u'ê|ê|ê', r'{e^}', txt) # e-circumflex + txt = re.sub(u'ë|ë|ë', r'{e"}', txt) # e-umlaut + txt = re.sub(u'ì|ì|ì', r'{i`}', txt) # i-grave + txt = re.sub(u'í|í|í', r"{i'}", txt) # i-acute + txt = re.sub(u'î|î|î', r'{i^}', txt) # i-circumflex + txt = re.sub(u'ï|ï|ï', r'{i"}', txt) # i-umlaut + txt = re.sub(u'ð|ð|ð', r'{d-}', txt) # eth + txt = re.sub(u'ñ|ñ|ñ', r'{n~}', txt) # n-tilde + txt = re.sub(u'ò|ò|ò', r'{o`}', txt) # o-grave + txt = re.sub(u'ó|ó|ó', r"{o'}", txt) # o-acute + txt = re.sub(u'ô|ô|ô', r'{o^}', txt) # o-circumflex + txt = re.sub(u'õ|õ|õ', r'{o~}', txt) # o-tilde + txt = re.sub(u'ö|ö|ö', r'{o"}', txt) # o-umlaut + txt = re.sub(u'ø|ø|ø', r'{o/}', txt) # o-stroke + txt = re.sub(u'ù|ù|ù', r'{u`}', txt) # u-grave + txt = re.sub(u'ú|ú|ú', r"{u'}", txt) # u-acute + txt = re.sub(u'û|û|û', r'{u^}', txt) # u-circumflex + txt = re.sub(u'ü|ü|ü', r'{u"}', txt) # u-umlaut + txt = re.sub(u'ý|ý|ý', r"{y'}", txt) # y-acute + txt = re.sub(u'ÿ|ÿ|ÿ', r'{y"}', txt) # y-umlaut + txt = re.sub(u'Œ|Œ|Œ', r'{OE}', txt) # OE + txt = re.sub(u'œ|œ|œ', r'{oe}', txt) # oe + txt = re.sub(u'Ŝ|Š|Ŝ', r'{S^}', txt) # Scaron + txt = re.sub(u'ŝ|š|ŝ', r'{s^}', txt) # scaron + txt = re.sub(u'•|•|•', r'{*}', txt) # bullet + txt = re.sub(u'₣|₣', r'{Fr}', txt) # Franc + txt = re.sub(u'₤|₤', r'{L=}', txt) # Lira + txt = re.sub(u'₨|₨', r'{Rs}', txt) # Rupee + txt = re.sub(u'€|€|€', r'{C=}', txt) # euro + txt = re.sub(u'™|™|™', r'{tm}', txt) # trademark + txt = re.sub(u'♠|♠|♠', r'{spade}', txt) # spade + txt = re.sub(u'♣|♣|♣', r'{club}', txt) # club + txt = re.sub(u'♥|♥|♥', r'{heart}', txt) # heart + txt = re.sub(u'♦|♦|♦', r'{diamond}', txt) # diamond + + # Move into main code? +# txt = re.sub(u'\xa0', r'p. ', txt) # blank paragraph +# txt = re.sub(u'\n\n\n\n', r'\n\np. \n\n', txt) # blank paragraph +# txt = re.sub(u'\n \n', r'\n<br />\n', txt) # blank paragraph - br tag + + return txt diff --git a/src/calibre/ebooks/unihandecode/pykakasi/jisyo.py b/src/calibre/ebooks/unihandecode/pykakasi/jisyo.py index d56e409ee5..03b2c8024f 100644 --- a/src/calibre/ebooks/unihandecode/pykakasi/jisyo.py +++ b/src/calibre/ebooks/unihandecode/pykakasi/jisyo.py @@ -2,12 +2,8 @@ # jisyo.py # # Copyright 2011 Hiroshi Miura <miurahr@linux.com> -from cPickle import load -import anydbm,marshal +import cPickle, marshal from zlib import decompress -import os - -import calibre.utils.resources as resources class jisyo (object): kanwadict = None @@ -25,16 +21,14 @@ class jisyo (object): def __init__(self): if self.kanwadict is None: - dictpath = resources.get_path(os.path.join('localization','pykakasi','kanwadict2.db')) - self.kanwadict = anydbm.open(dictpath,'r') - if self.itaijidict is None: - itaijipath = resources.get_path(os.path.join('localization','pykakasi','itaijidict2.pickle')) - itaiji_pkl = open(itaijipath, 'rb') - self.itaijidict = load(itaiji_pkl) + self.kanwadict = cPickle.loads( + P('localization/pykakasi/kanwadict2.pickle', data=True)) + if self.itaijidict is None: + self.itaijidict = cPickle.loads( + P('localization/pykakasi/itaijidict2.pickle', data=True)) if self.kanadict is None: - kanadictpath = resources.get_path(os.path.join('localization','pykakasi','kanadict2.pickle')) - kanadict_pkl = open(kanadictpath, 'rb') - self.kanadict = load(kanadict_pkl) + self.kanadict = cPickle.loads( + P('localization/pykakasi/kanadict2.pickle', data=True)) def load_jisyo(self, char): try:#python2 diff --git a/src/calibre/gui2/__init__.py b/src/calibre/gui2/__init__.py index efe09e8866..8499e304c3 100644 --- a/src/calibre/gui2/__init__.py +++ b/src/calibre/gui2/__init__.py @@ -4,38 +4,63 @@ __copyright__ = '2008, Kovid Goyal <kovid at kovidgoyal.net>' import os, sys, Queue, threading from threading import RLock from urllib import unquote - -from PyQt4.Qt import QVariant, QFileInfo, QObject, SIGNAL, QBuffer, Qt, \ - QByteArray, QTranslator, QCoreApplication, QThread, \ - QEvent, QTimer, pyqtSignal, QDate, QDesktopServices, \ - QFileDialog, QFileIconProvider, \ - QIcon, QApplication, QDialog, QUrl, QFont +from PyQt4.Qt import (QVariant, QFileInfo, QObject, SIGNAL, QBuffer, Qt, + QByteArray, QTranslator, QCoreApplication, QThread, + QEvent, QTimer, pyqtSignal, QDate, QDesktopServices, + QFileDialog, QFileIconProvider, + QIcon, QApplication, QDialog, QUrl, QFont) ORG_NAME = 'KovidsBrain' APP_UID = 'libprs500' -from calibre.constants import islinux, iswindows, isfreebsd, isfrozen +from calibre.constants import islinux, iswindows, isbsd, isfrozen, isosx from calibre.utils.config import Config, ConfigProxy, dynamic, JSONConfig from calibre.utils.localization import set_qt_translator -from calibre.ebooks.metadata.meta import get_metadata, metadata_from_formats from calibre.ebooks.metadata import MetaInformation from calibre.utils.date import UNDEFINED_DATE # Setup gprefs {{{ gprefs = JSONConfig('gui') -gprefs.defaults['action-layout-toolbar'] = ( - 'Add Books', 'Edit Metadata', None, 'Convert Books', 'View', None, - 'Choose Library', 'Donate', None, 'Fetch News', 'Save To Disk', - 'Connect Share', None, 'Remove Books', None, 'Help', 'Preferences', +if isosx: + gprefs.defaults['action-layout-menubar'] = ( + 'Add Books', 'Edit Metadata', 'Convert Books', + 'Choose Library', 'Save To Disk', 'Preferences', + 'Help', ) - -gprefs.defaults['action-layout-toolbar-device'] = ( + gprefs.defaults['action-layout-menubar-device'] = ( + 'Add Books', 'Edit Metadata', 'Convert Books', + 'Location Manager', 'Send To Device', + 'Save To Disk', 'Preferences', 'Help', + ) + gprefs.defaults['action-layout-toolbar'] = ( + 'Add Books', 'Edit Metadata', None, 'Convert Books', 'View', None, + 'Choose Library', 'Donate', None, 'Fetch News', 'Store', 'Save To Disk', + 'Connect Share', None, 'Remove Books', + ) + gprefs.defaults['action-layout-toolbar-device'] = ( + 'Add Books', 'Edit Metadata', None, 'Convert Books', 'View', + 'Send To Device', None, None, 'Location Manager', None, None, + 'Fetch News', 'Save To Disk', 'Connect Share', None, + 'Remove Books', + ) +else: + gprefs.defaults['action-layout-menubar'] = () + gprefs.defaults['action-layout-menubar-device'] = () + gprefs.defaults['action-layout-toolbar'] = ( + 'Add Books', 'Edit Metadata', None, 'Convert Books', 'View', None, + 'Store', 'Donate', 'Fetch News', 'Help', None, + 'Remove Books', 'Choose Library', 'Save To Disk', + 'Connect Share', 'Preferences', + ) + gprefs.defaults['action-layout-toolbar-device'] = ( 'Add Books', 'Edit Metadata', None, 'Convert Books', 'View', 'Send To Device', None, None, 'Location Manager', None, None, 'Fetch News', 'Save To Disk', 'Connect Share', None, 'Remove Books', None, 'Help', 'Preferences', ) +gprefs.defaults['action-layout-toolbar-child'] = () + gprefs.defaults['action-layout-context-menu'] = ( 'Edit Metadata', 'Send To Device', 'Save To Disk', 'Connect Share', 'Copy To Library', None, @@ -51,11 +76,19 @@ gprefs.defaults['action-layout-context-menu-device'] = ( gprefs.defaults['show_splash_screen'] = True gprefs.defaults['toolbar_icon_size'] = 'medium' gprefs.defaults['automerge'] = 'ignore' -gprefs.defaults['toolbar_text'] = 'auto' -gprefs.defaults['show_child_bar'] = False +gprefs.defaults['toolbar_text'] = 'always' gprefs.defaults['font'] = None gprefs.defaults['tags_browser_partition_method'] = 'first letter' gprefs.defaults['tags_browser_collapse_at'] = 100 +gprefs.defaults['edit_metadata_single_layout'] = 'default' +gprefs.defaults['book_display_fields'] = [ + ('title', False), ('authors', False), ('formats', True), + ('series', True), ('identifiers', True), ('tags', True), + ('path', True), ('publisher', False), ('rating', False), + ('author_sort', False), ('sort', False), ('timestamp', False), + ('uuid', False), ('comments', True), ('id', False), ('pubdate', False), + ('last_modified', False), ('size', False), + ] # }}} @@ -65,7 +98,7 @@ UNDEFINED_QDATE = QDate(UNDEFINED_DATE) ALL_COLUMNS = ['title', 'ondevice', 'authors', 'size', 'timestamp', 'rating', 'publisher', 'tags', 'series', 'pubdate'] -def _config(): +def _config(): # {{{ c = Config('gui', 'preferences for the calibre GUI') c.add_opt('send_to_storage_card_by_default', default=False, help=_('Send file to storage card instead of main memory by default')) @@ -79,6 +112,8 @@ def _config(): help=_('Use Roman numerals for series number')) c.add_opt('sort_tags_by', default='name', help=_('Sort tags list by name, popularity, or rating')) + c.add_opt('match_tags_type', default='any', + help=_('Match tags by any or all.')) c.add_opt('cover_flow_queue_length', default=6, help=_('Number of covers to show in the cover browsing mode')) c.add_opt('LRF_conversion_defaults', default=[], @@ -128,7 +163,9 @@ def _config(): c.add_opt('plugin_search_history', default=[], help='Search history for the recipe scheduler') c.add_opt('worker_limit', default=6, - help=_('Maximum number of waiting worker processes')) + help=_( + 'Maximum number of simultaneous conversion/news download jobs. ' + 'This number is twice the actual value for historical reasons.')) c.add_opt('get_social_metadata', default=True, help=_('Download social metadata (tags/rating/etc.)')) c.add_opt('overwrite_author_title_metadata', default=True, @@ -153,6 +190,8 @@ def _config(): return ConfigProxy(c) config = _config() +# }}} + # Turn off DeprecationWarnings in windows GUI if iswindows: import warnings @@ -302,6 +341,7 @@ class GetMetadata(QObject): id, args, kwargs) def _from_formats(self, id, args, kwargs): + from calibre.ebooks.metadata.meta import metadata_from_formats try: mi = metadata_from_formats(*args, **kwargs) except: @@ -309,6 +349,7 @@ class GetMetadata(QObject): self.emit(SIGNAL('metadataf(PyQt_PyObject, PyQt_PyObject)'), id, mi) def _get_metadata(self, id, args, kwargs): + from calibre.ebooks.metadata.meta import get_metadata try: mi = get_metadata(*args, **kwargs) except: @@ -329,6 +370,7 @@ class FileIconProvider(QFileIconProvider): 'bmp' : 'bmp', 'svg' : 'svg', 'html' : 'html', + 'htmlz' : 'html', 'htm' : 'html', 'xhtml' : 'html', 'xhtm' : 'html', @@ -340,6 +382,7 @@ class FileIconProvider(QFileIconProvider): 'rar' : 'rar', 'zip' : 'zip', 'txt' : 'txt', + 'text' : 'txt', 'prc' : 'mobi', 'azw' : 'mobi', 'mobi' : 'mobi', @@ -578,7 +621,22 @@ class Application(QApplication): self.original_font = QFont(QApplication.font()) fi = gprefs['font'] if fi is not None: - QApplication.setFont(QFont(*fi)) + font = QFont(*(fi[:4])) + s = gprefs.get('font_stretch', None) + if s is not None: + font.setStretch(s) + QApplication.setFont(font) + st = self.style() + if st is not None: + st = unicode(st.objectName()).lower() + if (islinux or isbsd) and st in ('windows', 'motif', 'cde'): + from PyQt4.Qt import QStyleFactory + styles = set(map(unicode, QStyleFactory.keys())) + if 'Plastique' in styles and os.environ.get('KDE_FULL_SESSION', + False): + self.setStyle('Plastique') + elif 'Cleanlooks' in styles: + self.setStyle('Cleanlooks') def _send_file_open_events(self): with self._file_open_lock: @@ -618,6 +676,18 @@ def open_url(qurl): if isfrozen and islinux and paths: os.environ['LD_LIBRARY_PATH'] = os.pathsep.join(paths) +def get_current_db(): + ''' + This method will try to return the current database in use by the user as + efficiently as possible, i.e. without constructing duplicate + LibraryDatabase objects. + ''' + from calibre.gui2.ui import get_gui + gui = get_gui() + if gui is not None and gui.current_db is not None: + return gui.current_db + from calibre.library import db + return db() def open_local_file(path): if iswindows: @@ -628,7 +698,7 @@ def open_local_file(path): def is_ok_to_use_qt(): global gui_thread, _store_app - if (islinux or isfreebsd) and ':' not in os.environ.get('DISPLAY', ''): + if (islinux or isbsd) and ':' not in os.environ.get('DISPLAY', ''): return False if _store_app is None and QApplication.instance() is None: _store_app = QApplication([]) @@ -685,14 +755,9 @@ def build_forms(srcdir, info=None): dat = dat.replace('from QtWebKit.QWebView import QWebView', 'from PyQt4 import QtWebKit\nfrom PyQt4.QtWebKit import QWebView') - if form.endswith('viewer%smain.ui'%os.sep): - info('\t\tPromoting WebView') - dat = dat.replace('self.view = QtWebKit.QWebView(', 'self.view = DocumentView(') - dat = dat.replace('self.view = QWebView(', 'self.view = DocumentView(') - dat += '\n\nfrom calibre.gui2.viewer.documentview import DocumentView' - open(compiled_form, 'wb').write(dat) _df = os.environ.get('CALIBRE_DEVELOP_FROM', None) if _df and os.path.exists(_df): build_forms(_df) + diff --git a/src/calibre/gui2/actions/__init__.py b/src/calibre/gui2/actions/__init__.py index 8563956b28..a5ef402f22 100644 --- a/src/calibre/gui2/actions/__init__.py +++ b/src/calibre/gui2/actions/__init__.py @@ -75,7 +75,7 @@ class InterfaceAction(QObject): dont_remove_from = frozenset([]) all_locations = frozenset(['toolbar', 'toolbar-device', 'context-menu', - 'context-menu-device']) + 'context-menu-device', 'toolbar-child', 'menubar', 'menubar-device']) #: Type of action #: 'current' means acts on the current view @@ -145,11 +145,10 @@ class InterfaceAction(QObject): ans[candidate] = zf.read(candidate) return ans - def genesis(self): ''' Setup this plugin. Only called once during initialization. self.gui is - available. The action secified by :attr:`action_spec` is available as + available. The action specified by :attr:`action_spec` is available as ``self.qaction``. ''' pass diff --git a/src/calibre/gui2/actions/add.py b/src/calibre/gui2/actions/add.py index f99e48eb2b..3556f1db80 100644 --- a/src/calibre/gui2/actions/add.py +++ b/src/calibre/gui2/actions/add.py @@ -20,8 +20,26 @@ from calibre.ebooks import BOOK_EXTENSIONS from calibre.utils.filenames import ascii_filename from calibre.constants import preferred_encoding, filesystem_encoding from calibre.gui2.actions import InterfaceAction -from calibre.gui2 import config +from calibre.gui2 import question_dialog from calibre.ebooks.metadata import MetaInformation +from calibre.ebooks.metadata.sources.base import msprefs + +def get_filters(): + return [ + (_('Books'), BOOK_EXTENSIONS), + (_('EPUB Books'), ['epub']), + (_('LRF Books'), ['lrf']), + (_('HTML Books'), ['htm', 'html', 'xhtm', 'xhtml']), + (_('LIT Books'), ['lit']), + (_('MOBI Books'), ['mobi', 'prc', 'azw']), + (_('Topaz books'), ['tpz','azw1']), + (_('Text books'), ['txt', 'rtf']), + (_('PDF Books'), ['pdf']), + (_('SNB Books'), ['snb']), + (_('Comics'), ['cbz', 'cbr', 'cbc']), + (_('Archives'), ['zip', 'rar']), + ] + class AddAction(InterfaceAction): @@ -47,6 +65,10 @@ class AddAction(InterfaceAction): self.add_menu.addAction(_('Add Empty book. (Book entry with no ' 'formats)'), self.add_empty, _('Shift+Ctrl+E')) self.add_menu.addAction(_('Add from ISBN'), self.add_from_isbn) + self.add_menu.addSeparator() + self.add_menu.addAction(_('Add files to selected book records'), + self.add_formats, _('Shift+A')) + self.qaction.setMenu(self.add_menu) self.qaction.triggered.connect(self.add_books) @@ -55,6 +77,39 @@ class AddAction(InterfaceAction): for action in list(self.add_menu.actions())[1:]: action.setEnabled(enabled) + def add_formats(self, *args): + if self.gui.stack.currentIndex() != 0: + return + view = self.gui.library_view + rows = view.selectionModel().selectedRows() + if not rows: + return + ids = [view.model().id(r) for r in rows] + + if len(ids) > 1 and not question_dialog(self.gui, + _('Are you sure'), + _('Are you sure you want to add the same' + ' files to all %d books? If the format' + 'already exists for a book, it will be replaced.')%len(ids)): + return + + books = choose_files(self.gui, 'add formats dialog dir', + _('Select book files'), filters=get_filters()) + if not books: + return + + db = view.model().db + for id_ in ids: + for fpath in books: + fmt = os.path.splitext(fpath)[1][1:].upper() + if fmt: + db.add_format_with_hooks(id_, fmt, fpath, index_is_id=True, + notify=True) + current_idx = self.gui.library_view.currentIndex() + if current_idx.isValid(): + view.model().current_changed(current_idx, current_idx) + + def add_recursive(self, single): root = choose_dir(self.gui, 'recursive book import root dir dialog', 'Select root folder') @@ -124,13 +179,17 @@ class AddAction(InterfaceAction): except IndexError: self.gui.library_view.model().books_added(self.isbn_add_dialog.value) self.isbn_add_dialog.accept() - orig = config['overwrite_author_title_metadata'] - config['overwrite_author_title_metadata'] = True + orig = msprefs['ignore_fields'] + new = list(orig) + for x in ('title', 'authors'): + if x in new: + new.remove(x) + msprefs['ignore_fields'] = new try: - self.gui.iactions['Edit Metadata'].do_download_metadata( - self.add_by_isbn_ids) + self.gui.iactions['Edit Metadata'].download_metadata( + ids=self.add_by_isbn_ids) finally: - config['overwrite_author_title_metadata'] = orig + msprefs['ignore_fields'] = orig return @@ -150,15 +209,29 @@ class AddAction(InterfaceAction): to_device = self.gui.stack.currentIndex() != 0 self._add_books(paths, to_device) - def files_dropped_on_book(self, event, paths): + def remote_file_dropped_on_book(self, url, fname): + if self.gui.current_view() is not self.gui.library_view: + return + db = self.gui.library_view.model().db + current_idx = self.gui.library_view.currentIndex() + if not current_idx.isValid(): return + cid = db.id(current_idx.row()) + from calibre.gui2.dnd import DownloadDialog + d = DownloadDialog(url, fname, self.gui) + d.start_download() + if d.err is None: + self.files_dropped_on_book(None, [d.fpath], cid=cid) + + def files_dropped_on_book(self, event, paths, cid=None): accept = False if self.gui.current_view() is not self.gui.library_view: return db = self.gui.library_view.model().db cover_changed = False current_idx = self.gui.library_view.currentIndex() - if not current_idx.isValid(): return - cid = db.id(current_idx.row()) + if cid is None: + if not current_idx.isValid(): return + cid = db.id(current_idx.row()) if cid is None else cid for path in paths: ext = os.path.splitext(path)[1].lower() if ext: @@ -173,8 +246,9 @@ class AddAction(InterfaceAction): elif ext in BOOK_EXTENSIONS: db.add_format_with_hooks(cid, ext, path, index_is_id=True) accept = True - if accept: + if accept and event is not None: event.accept() + if current_idx.isValid(): self.gui.library_view.model().current_changed(current_idx, current_idx) if cover_changed: if self.gui.cover_flow: @@ -207,27 +281,14 @@ class AddAction(InterfaceAction): ''' Add books from the local filesystem to either the library or the device. ''' - filters = [ - (_('Books'), BOOK_EXTENSIONS), - (_('EPUB Books'), ['epub']), - (_('LRF Books'), ['lrf']), - (_('HTML Books'), ['htm', 'html', 'xhtm', 'xhtml']), - (_('LIT Books'), ['lit']), - (_('MOBI Books'), ['mobi', 'prc', 'azw']), - (_('Topaz books'), ['tpz','azw1']), - (_('Text books'), ['txt', 'rtf']), - (_('PDF Books'), ['pdf']), - (_('SNB Books'), ['snb']), - (_('Comics'), ['cbz', 'cbr', 'cbc']), - (_('Archives'), ['zip', 'rar']), - ] + filters = get_filters() to_device = self.gui.stack.currentIndex() != 0 if to_device: fmts = self.gui.device_manager.device.settings().format_map filters = [(_('Supported books'), fmts)] - books = choose_files(self.gui, 'add books dialog dir', 'Select books', - filters=filters) + books = choose_files(self.gui, 'add books dialog dir', + _('Select books'), filters=filters) if not books: return self._add_books(books, to_device) @@ -256,6 +317,7 @@ class AddAction(InterfaceAction): _('Uploading books to device.'), 2000) if getattr(self._adder, 'number_of_books_added', 0) > 0: self.gui.library_view.model().books_added(self._adder.number_of_books_added) + self.gui.library_view.set_current_row(0) if hasattr(self.gui, 'db_images'): self.gui.db_images.reset() self.gui.tags_view.recount() @@ -277,7 +339,6 @@ class AddAction(InterfaceAction): self.gui.library_view.model().current_changed(current_idx, current_idx) - if getattr(self._adder, 'critical', None): det_msg = [] for name, log in self._adder.critical.items(): diff --git a/src/calibre/gui2/actions/add_to_library.py b/src/calibre/gui2/actions/add_to_library.py index 05aea8f1dd..2665ffe0a4 100644 --- a/src/calibre/gui2/actions/add_to_library.py +++ b/src/calibre/gui2/actions/add_to_library.py @@ -12,7 +12,7 @@ class AddToLibraryAction(InterfaceAction): name = 'Add To Library' action_spec = (_('Add books to library'), 'add_book.png', _('Add books to your calibre library from the connected device'), None) - dont_add_to = frozenset(['toolbar', 'context-menu']) + dont_add_to = frozenset(['menubar', 'toolbar', 'context-menu', 'toolbar-child']) action_type = 'current' def genesis(self): diff --git a/src/calibre/gui2/actions/annotate.py b/src/calibre/gui2/actions/annotate.py index a702ba045e..f934a4a53c 100644 --- a/src/calibre/gui2/actions/annotate.py +++ b/src/calibre/gui2/actions/annotate.py @@ -18,10 +18,11 @@ class FetchAnnotationsAction(InterfaceAction): name = 'Fetch Annotations' action_spec = (_('Fetch annotations (experimental)'), None, None, None) + dont_add_to = frozenset(['menubar', 'toolbar', 'context-menu', 'toolbar-child']) action_type = 'current' def genesis(self): - pass + self.qaction.triggered.connect(self.fetch_annotations) def fetch_annotations(self, *args): # Generate a path_map from selected ids @@ -51,6 +52,10 @@ class FetchAnnotationsAction(InterfaceAction): return path_map device = self.gui.device_manager.device + if not getattr(device, 'SUPPORTS_ANNOTATIONS', False): + return error_dialog(self.gui, _('Not supported'), + _('Fetching annotations is not supported for this device'), + show=True) if self.gui.current_view() is not self.gui.library_view: return error_dialog(self.gui, _('Use library only'), diff --git a/src/calibre/gui2/actions/catalog.py b/src/calibre/gui2/actions/catalog.py index fadb140be6..45544d8246 100644 --- a/src/calibre/gui2/actions/catalog.py +++ b/src/calibre/gui2/actions/catalog.py @@ -17,8 +17,11 @@ from calibre.gui2.actions import InterfaceAction class GenerateCatalogAction(InterfaceAction): name = 'Generate Catalog' - action_spec = (_('Create a catalog of the books in your calibre library'), None, None, None) - dont_add_to = frozenset(['toolbar-device', 'context-menu-device']) + action_spec = (_('Create a catalog of the books in your calibre library'), 'catalog.png', 'Catalog builder', None) + dont_add_to = frozenset(['menubar-device', 'toolbar-device', 'context-menu-device']) + + def genesis(self): + self.qaction.triggered.connect(self.generate_catalog) def generate_catalog(self): rows = self.gui.library_view.selectionModel().selectedRows() @@ -31,10 +34,10 @@ class GenerateCatalogAction(InterfaceAction): _('No books selected for catalog generation'), show=True) - db = self.gui.library_view.model().db - dbspec = {} - for id in ids: - dbspec[id] = {'ondevice': db.ondevice(id, index_is_id=True)} + db = self.gui.library_view.model().db + dbspec = {} + for id in ids: + dbspec[id] = {'ondevice': db.ondevice(id, index_is_id=True)} # Calling gui2.tools:generate_catalog() ret = generate_catalog(self.gui, dbspec, ids, self.gui.device_manager, diff --git a/src/calibre/gui2/actions/choose_library.py b/src/calibre/gui2/actions/choose_library.py index f3a7f1742d..9fd156b802 100644 --- a/src/calibre/gui2/actions/choose_library.py +++ b/src/calibre/gui2/actions/choose_library.py @@ -8,7 +8,7 @@ __docformat__ = 'restructuredtext en' import os, shutil from functools import partial -from PyQt4.Qt import QMenu, Qt, QInputDialog +from PyQt4.Qt import QMenu, Qt, QInputDialog, QToolButton from calibre import isbytestring from calibre.constants import filesystem_encoding @@ -80,7 +80,7 @@ class ChooseLibraryAction(InterfaceAction): name = 'Choose Library' action_spec = (_('%d books'), 'lt.png', _('Choose calibre library to work with'), None) - dont_add_to = frozenset(['toolbar-device', 'context-menu-device']) + dont_add_to = frozenset(['menubar-device', 'toolbar-device', 'context-menu-device']) def genesis(self): self.count_changed(0) @@ -88,6 +88,9 @@ class ChooseLibraryAction(InterfaceAction): type=Qt.QueuedConnection) self.stats = LibraryUsageStats() + self.popup_type = (QToolButton.InstantPopup if len(self.stats.stats) > 1 else + QToolButton.MenuButtonPopup) + self.create_action(spec=(_('Switch/create library...'), 'lt.png', None, None), attr='action_choose') self.action_choose.triggered.connect(self.choose_library, @@ -123,6 +126,7 @@ class ChooseLibraryAction(InterfaceAction): type=Qt.QueuedConnection) self.choose_menu.addAction(ac) + self.rename_separator = self.choose_menu.addSeparator() self.maintenance_menu = QMenu(_('Library Maintenance')) @@ -172,6 +176,7 @@ class ChooseLibraryAction(InterfaceAction): return db = self.gui.library_view.model().db locations = list(self.stats.locations(db)) + for ac in self.switch_actions: ac.setVisible(False) self.quick_menu.clear() @@ -205,7 +210,6 @@ class ChooseLibraryAction(InterfaceAction): rename_actions, delete_actions, qs_actions, self.action_choose) - def location_selected(self, loc): enabled = loc == 'library' self.qaction.setEnabled(enabled) @@ -242,7 +246,8 @@ class ChooseLibraryAction(InterfaceAction): def delete_requested(self, name, location): loc = location.replace('/', os.sep) if not question_dialog(self.gui, _('Are you sure?'), '<p>'+ - _('All files from %s will be ' + _('<b style="color: red">All files</b> (not just ebooks) ' + 'from <br><br><b>%s</b><br><br> will be ' '<b>permanently deleted</b>. Are you sure?') % loc, show_copy_button=False): return @@ -355,6 +360,7 @@ class ChooseLibraryAction(InterfaceAction): print print 'before:', self.before_mem print 'after:', memory()/1024**2 + print self.dbref = self.before_mem = None diff --git a/src/calibre/gui2/actions/convert.py b/src/calibre/gui2/actions/convert.py index caf65932d8..ed0a064e88 100644 --- a/src/calibre/gui2/actions/convert.py +++ b/src/calibre/gui2/actions/convert.py @@ -20,7 +20,7 @@ class ConvertAction(InterfaceAction): name = 'Convert Books' action_spec = (_('Convert books'), 'convert.png', None, _('C')) - dont_add_to = frozenset(['toolbar-device', 'context-menu-device']) + dont_add_to = frozenset(['menubar-device', 'toolbar-device', 'context-menu-device']) action_type = 'current' def genesis(self): @@ -51,7 +51,7 @@ class ConvertAction(InterfaceAction): self.queue_convert_jobs(jobs, changed, bad, rows, previous, self.book_auto_converted, extra_job_args=[on_card]) - def auto_convert_mail(self, to, fmts, delete_from_library, book_ids, format): + def auto_convert_mail(self, to, fmts, delete_from_library, book_ids, format, subject): previous = self.gui.library_view.currentIndex() rows = [x.row() for x in \ self.gui.library_view.selectionModel().selectedRows()] @@ -59,7 +59,7 @@ class ConvertAction(InterfaceAction): if jobs == []: return self.queue_convert_jobs(jobs, changed, bad, rows, previous, self.book_auto_converted_mail, - extra_job_args=[delete_from_library, to, fmts]) + extra_job_args=[delete_from_library, to, fmts, subject]) def auto_convert_news(self, book_ids, format): previous = self.gui.library_view.currentIndex() @@ -145,9 +145,10 @@ class ConvertAction(InterfaceAction): self.gui.sync_to_device(on_card, False, specific_format=fmt, send_ids=[book_id], do_auto_convert=False) def book_auto_converted_mail(self, job): - temp_files, fmt, book_id, delete_from_library, to, fmts = self.conversion_jobs[job] + temp_files, fmt, book_id, delete_from_library, to, fmts, subject = self.conversion_jobs[job] self.book_converted(job) - self.gui.send_by_mail(to, fmts, delete_from_library, specific_format=fmt, send_ids=[book_id], do_auto_convert=False) + self.gui.send_by_mail(to, fmts, delete_from_library, subject=subject, + specific_format=fmt, send_ids=[book_id], do_auto_convert=False) def book_auto_converted_news(self, job): temp_files, fmt, book_id = self.conversion_jobs[job] diff --git a/src/calibre/gui2/actions/copy_to_library.py b/src/calibre/gui2/actions/copy_to_library.py index 0668baeac6..7190d1486f 100644 --- a/src/calibre/gui2/actions/copy_to_library.py +++ b/src/calibre/gui2/actions/copy_to_library.py @@ -16,7 +16,7 @@ from calibre.gui2 import error_dialog, Dispatcher, warning_dialog from calibre.gui2.dialogs.progress import ProgressDialog from calibre.utils.config import prefs, tweaks -class Worker(Thread): +class Worker(Thread): # {{{ def __init__(self, ids, db, loc, progress, done, delete_after): Thread.__init__(self) @@ -32,7 +32,7 @@ class Worker(Thread): def run(self): try: self.doit() - except Exception, err: + except Exception as err: import traceback try: err = unicode(err) @@ -75,7 +75,7 @@ class Worker(Thread): if co is not None: newdb.set_conversion_options(x, 'PIPE', co) self.processed.add(x) - +# }}} class CopyToLibraryAction(InterfaceAction): diff --git a/src/calibre/gui2/actions/delete.py b/src/calibre/gui2/actions/delete.py index 9bc43f580b..43465512e0 100644 --- a/src/calibre/gui2/actions/delete.py +++ b/src/calibre/gui2/actions/delete.py @@ -9,21 +9,31 @@ from functools import partial from PyQt4.Qt import QMenu, QObject, QTimer -from calibre.gui2 import error_dialog +from calibre.gui2 import error_dialog, question_dialog from calibre.gui2.dialogs.delete_matching_from_device import DeleteMatchingFromDeviceDialog from calibre.gui2.dialogs.confirm_delete import confirm from calibre.gui2.dialogs.confirm_delete_location import confirm_location from calibre.gui2.actions import InterfaceAction +from calibre.utils.recycle_bin import can_recycle single_shot = partial(QTimer.singleShot, 10) class MultiDeleter(QObject): - def __init__(self, gui, rows, callback): + def __init__(self, gui, ids, callback): from calibre.gui2.dialogs.progress import ProgressDialog QObject.__init__(self, gui) self.model = gui.library_view.model() - self.ids = list(map(self.model.id, rows)) + self.ids = ids + self.permanent = False + if can_recycle and len(ids) > 100: + if question_dialog(gui, _('Are you sure?'), '<p>'+ + _('You are trying to delete %d books. ' + 'Sending so many files to the Recycle' + ' Bin <b>can be slow</b>. Should calibre skip the' + ' Recycle Bin? If you click Yes the files' + ' will be <b>permanently deleted</b>.')%len(ids)): + self.permanent = True self.gui = gui self.failures = [] self.deleted_ids = [] @@ -44,7 +54,8 @@ class MultiDeleter(QObject): title_ = self.model.db.title(id_, index_is_id=True) if title_: title = title_ - self.model.db.delete_book(id_, notify=False, commit=False) + self.model.db.delete_book(id_, notify=False, commit=False, + permanent=self.permanent) self.deleted_ids.append(id_) except: import traceback @@ -141,7 +152,8 @@ class DeleteAction(InterfaceAction): if not ids: return fmts = self._get_selected_formats( - '<p>'+_('Choose formats <b>not</b> to be deleted'), ids) + '<p>'+_('Choose formats <b>not</b> to be deleted.<p>Note that ' + 'this will never remove all formats from a book.'), ids) if fmts is None: return for id in ids: @@ -150,9 +162,12 @@ class DeleteAction(InterfaceAction): continue bfmts = set([x.lower() for x in bfmts.split(',')]) rfmts = bfmts - set(fmts) - for fmt in rfmts: - self.gui.library_view.model().db.remove_format(id, fmt, - index_is_id=True, notify=False) + if bfmts - rfmts: + # Do not delete if it will leave the book with no + # formats + for fmt in rfmts: + self.gui.library_view.model().db.remove_format(id, fmt, + index_is_id=True, notify=False) self.gui.library_view.model().refresh_ids(ids) self.gui.library_view.model().current_changed(self.gui.library_view.currentIndex(), self.gui.library_view.currentIndex()) @@ -231,6 +246,7 @@ class DeleteAction(InterfaceAction): return # Library view is visible. if self.gui.stack.currentIndex() == 0: + to_delete_ids = [view.model().id(r) for r in rows] # Ask the user if they want to delete the book from the library or device if it is in both. if self.gui.device_manager.is_device_connected: on_device = False @@ -264,10 +280,10 @@ class DeleteAction(InterfaceAction): if ci.isValid(): row = ci.row() if len(rows) < 5: - ids_deleted = view.model().delete_books(rows) - self.library_ids_deleted(ids_deleted, row) + view.model().delete_books_by_id(to_delete_ids) + self.library_ids_deleted(to_delete_ids, row) else: - self.__md = MultiDeleter(self.gui, rows, + self.__md = MultiDeleter(self.gui, to_delete_ids, partial(self.library_ids_deleted, current_row=row)) # Device view is visible. else: diff --git a/src/calibre/gui2/actions/device.py b/src/calibre/gui2/actions/device.py index 0b0492228e..debcbb6c1a 100644 --- a/src/calibre/gui2/actions/device.py +++ b/src/calibre/gui2/actions/device.py @@ -24,7 +24,7 @@ class ShareConnMenu(QMenu): # {{{ config_email = pyqtSignal() toggle_server = pyqtSignal() - dont_add_to = frozenset(['toolbar-device', 'context-menu-device']) + dont_add_to = frozenset(['menubar-device', 'toolbar-device', 'context-menu-device']) def __init__(self, parent=None): QMenu.__init__(self, parent) @@ -82,7 +82,8 @@ class ShareConnMenu(QMenu): # {{{ keys = sorted(opts.accounts.keys()) for account in keys: formats, auto, default = opts.accounts[account] - dest = 'mail:'+account+';'+formats + subject = opts.subjects.get(account, '') + dest = 'mail:'+account+';'+formats+';'+subject action1 = DeviceAction(dest, False, False, I('mail.png'), account) action2 = DeviceAction(dest, True, False, I('mail.png'), @@ -120,8 +121,7 @@ class SendToDeviceAction(InterfaceAction): name = 'Send To Device' action_spec = (_('Send to device'), 'sync.png', None, _('D')) - dont_remove_from = frozenset(['toolbar-device']) - dont_add_to = frozenset(['toolbar', 'context-menu']) + dont_add_to = frozenset(['menubar', 'toolbar', 'context-menu', 'toolbar-child']) def genesis(self): self.qaction.triggered.connect(self.do_sync) @@ -165,10 +165,14 @@ class ConnectShareAction(InterfaceAction): def content_server_state_changed(self, running): self.share_conn_menu.server_state_changed(running) + if running: + self.qaction.setIcon(QIcon(I('connect_share_on.png'))) + else: + self.qaction.setIcon(QIcon(I('connect_share.png'))) def toggle_content_server(self): if self.gui.content_server is None: - self.gui.start_content_server() + self.gui.start_content_server() else: self.gui.content_server.threaded_exit() self.stopping_msg = info_dialog(self.gui, _('Stopping'), diff --git a/src/calibre/gui2/actions/edit_collections.py b/src/calibre/gui2/actions/edit_collections.py index 7f5dd76538..1a0b1038ce 100644 --- a/src/calibre/gui2/actions/edit_collections.py +++ b/src/calibre/gui2/actions/edit_collections.py @@ -12,7 +12,7 @@ class EditCollectionsAction(InterfaceAction): name = 'Edit Collections' action_spec = (_('Manage collections'), None, _('Manage the collections on this device'), None) - dont_add_to = frozenset(['toolbar', 'context-menu']) + dont_add_to = frozenset(['menubar', 'toolbar', 'context-menu', 'toolbar-child']) action_type = 'current' def genesis(self): diff --git a/src/calibre/gui2/actions/edit_metadata.py b/src/calibre/gui2/actions/edit_metadata.py index 6c2cfb8126..ac475cb027 100644 --- a/src/calibre/gui2/actions/edit_metadata.py +++ b/src/calibre/gui2/actions/edit_metadata.py @@ -8,14 +8,14 @@ __docformat__ = 'restructuredtext en' import os from functools import partial -from PyQt4.Qt import Qt, QMenu, QModelIndex +from PyQt4.Qt import Qt, QMenu, QModelIndex, QTimer -from calibre.gui2 import error_dialog, config -from calibre.gui2.dialogs.metadata_single import MetadataSingleDialog +from calibre.gui2 import error_dialog, Dispatcher, question_dialog from calibre.gui2.dialogs.metadata_bulk import MetadataBulkDialog from calibre.gui2.dialogs.confirm_delete import confirm from calibre.gui2.dialogs.tag_list_editor import TagListEditor from calibre.gui2.actions import InterfaceAction +from calibre.ebooks.metadata import authors_to_string from calibre.utils.icu import sort_key class EditMetadataAction(InterfaceAction): @@ -34,17 +34,8 @@ class EditMetadataAction(InterfaceAction): md.addAction(_('Edit metadata in bulk'), partial(self.edit_metadata, False, bulk=True)) md.addSeparator() - md.addAction(_('Download metadata and covers'), - partial(self.download_metadata, False, covers=True), + md.addAction(_('Download metadata and covers'), self.download_metadata, Qt.ControlModifier+Qt.Key_D) - md.addAction(_('Download only metadata'), - partial(self.download_metadata, False, covers=False)) - md.addAction(_('Download only covers'), - partial(self.download_metadata, False, covers=True, - set_metadata=False, set_social_metadata=False)) - md.addAction(_('Download only social metadata'), - partial(self.download_metadata, False, covers=False, - set_metadata=False, set_social_metadata=True)) self.metadata_menu = md mb = QMenu() @@ -72,49 +63,87 @@ class EditMetadataAction(InterfaceAction): self.qaction.setEnabled(enabled) self.action_merge.setEnabled(enabled) - def download_metadata(self, checked, covers=True, set_metadata=True, - set_social_metadata=None): - rows = self.gui.library_view.selectionModel().selectedRows() - if not rows or len(rows) == 0: - d = error_dialog(self.gui, _('Cannot download metadata'), - _('No books selected')) - d.exec_() - return - db = self.gui.library_view.model().db - ids = [db.id(row.row()) for row in rows] - self.do_download_metadata(ids, covers=covers, - set_metadata=set_metadata, - set_social_metadata=set_social_metadata) + # Download metadata {{{ + def download_metadata(self, ids=None): + if ids is None: + rows = self.gui.library_view.selectionModel().selectedRows() + if not rows or len(rows) == 0: + return error_dialog(self.gui, _('Cannot download metadata'), + _('No books selected'), show=True) + db = self.gui.library_view.model().db + ids = [db.id(row.row()) for row in rows] + from calibre.gui2.metadata.bulk_download import start_download + start_download(self.gui, ids, + Dispatcher(self.metadata_downloaded)) - def do_download_metadata(self, ids, covers=True, set_metadata=True, - set_social_metadata=None): - m = self.gui.library_view.model() - db = m.db - if set_social_metadata is None: - get_social_metadata = config['get_social_metadata'] - else: - get_social_metadata = set_social_metadata - from calibre.gui2.metadata.bulk_download import DoDownload - if set_social_metadata is not None and set_social_metadata: - x = _('social metadata') - else: - x = _('covers') if covers and not set_metadata else _('metadata') - title = _('Downloading {0} for {1} book(s)').format(x, len(ids)) - self._download_book_metadata = DoDownload(self.gui, title, db, ids, - get_covers=covers, set_metadata=set_metadata, - get_social_metadata=get_social_metadata) - m.stop_metadata_backup() - try: - self._download_book_metadata.exec_() - finally: - m.start_metadata_backup() - cr = self.gui.library_view.currentIndex().row() - x = self._download_book_metadata - if x.updated: - self.gui.library_view.model().refresh_ids( - x.updated, cr) - if self.gui.cover_flow: - self.gui.cover_flow.dataChanged() + def metadata_downloaded(self, job): + if job.failed: + self.gui.job_exception(job, dialog_title=_('Failed to download metadata')) + return + from calibre.gui2.metadata.bulk_download import get_job_details + id_map, failed_ids, failed_covers, all_failed, det_msg = \ + get_job_details(job) + if all_failed: + return error_dialog(self.gui, _('Download failed'), + _('Failed to download metadata or covers for any of the %d' + ' book(s).') % len(id_map), det_msg=det_msg, show=True) + + self.gui.status_bar.show_message(_('Metadata download completed'), 3000) + + msg = '<p>' + _('Finished downloading metadata for <b>%d book(s)</b>. ' + 'Proceed with updating the metadata in your library?')%len(id_map) + + show_copy_button = False + if failed_ids or failed_covers: + show_copy_button = True + num = len(failed_ids.union(failed_covers)) + msg += '<p>'+_('Could not download metadata and/or covers for %d of the books. Click' + ' "Show details" to see which books.')%num + + payload = (id_map, failed_ids, failed_covers) + from calibre.gui2.dialogs.message_box import ProceedNotification + p = ProceedNotification(self.apply_downloaded_metadata, + payload, job.html_details, + _('Download log'), _('Download complete'), msg, + det_msg=det_msg, show_copy_button=show_copy_button, + parent=self.gui) + p.show() + + def apply_downloaded_metadata(self, payload): + id_map, failed_ids, failed_covers = payload + id_map = dict([(k, v) for k, v in id_map.iteritems() if k not in + failed_ids]) + if not id_map: + return + + modified = set() + db = self.gui.current_db + + for i, mi in id_map.iteritems(): + lm = db.metadata_last_modified(i, index_is_id=True) + if lm > mi.last_modified: + title = db.title(i, index_is_id=True) + authors = db.authors(i, index_is_id=True) + if authors: + authors = [x.replace('|', ',') for x in authors.split(',')] + title += ' - ' + authors_to_string(authors) + modified.add(title) + + if modified: + from calibre.utils.icu import lower + + modified = sorted(modified, key=lower) + if not question_dialog(self.gui, _('Some books changed'), '<p>'+ + _('The metadata for some books in your library has' + ' changed since you started the download. If you' + ' proceed, some of those changes may be overwritten. ' + 'Click "Show details" to see the list of changed books. ' + 'Do you want to proceed?'), det_msg='\n'.join(modified)): + return + + self.apply_metadata_changes(id_map) + + # }}} def edit_metadata(self, checked, bulk=None): ''' @@ -133,8 +162,6 @@ class EditMetadataAction(InterfaceAction): row_list = [r.row() for r in rows] current_row = 0 - changed = set([]) - db = self.gui.library_view.model().db if len(row_list) == 1: cr = row_list[0] @@ -142,39 +169,44 @@ class EditMetadataAction(InterfaceAction): list(range(self.gui.library_view.model().rowCount(QModelIndex()))) current_row = row_list.index(cr) - while True: - prev = next_ = None - if current_row > 0: - prev = db.title(row_list[current_row-1]) - if current_row < len(row_list) - 1: - next_ = db.title(row_list[current_row+1]) + changed, rows_to_refresh = self.do_edit_metadata(row_list, current_row) - d = MetadataSingleDialog(self.gui, row_list[current_row], db, - prev=prev, next_=next_) - d.view_format.connect(lambda - fmt:self.gui.iactions['View'].view_format(row_list[current_row], - fmt)) - ret = d.exec_() - d.break_cycles() - if ret != d.Accepted: - break - - changed.add(d.id) - self.gui.library_view.model().refresh_ids(list(d.books_to_refresh)) - if d.row_delta == 0: - break - current_row += d.row_delta + m = self.gui.library_view.model() + if rows_to_refresh: + m.refresh_rows(rows_to_refresh) if changed: - self.gui.library_view.model().refresh_ids(list(changed)) + m.refresh_ids(list(changed)) current = self.gui.library_view.currentIndex() - m = self.gui.library_view.model() if self.gui.cover_flow: self.gui.cover_flow.dataChanged() m.current_changed(current, previous) self.gui.tags_view.recount() + def do_edit_metadata(self, row_list, current_row): + from calibre.gui2.metadata.single import edit_metadata + db = self.gui.library_view.model().db + changed, rows_to_refresh = edit_metadata(db, row_list, current_row, + parent=self.gui, view_slot=self.view_format_callback, + set_current_callback=self.set_current_callback) + return changed, rows_to_refresh + + def set_current_callback(self, id_): + db = self.gui.library_view.model().db + current_row = db.row(id_) + self.gui.library_view.set_current_row(current_row) + self.gui.library_view.scroll_to_row(current_row) + + def view_format_callback(self, id_, fmt): + view = self.gui.iactions['View'] + if id_ is None: + view._view_file(fmt) + else: + db = self.gui.library_view.model().db + view.view_format(db.row(id_), fmt) + + def edit_bulk_metadata(self, checked): ''' Edit metadata of selected books in library in bulk. @@ -406,4 +438,104 @@ class EditMetadataAction(InterfaceAction): self.gui.upload_collections(model.db, view=view, oncard=oncard) view.reset() + # Apply bulk metadata changes {{{ + def apply_metadata_changes(self, id_map, title=None, msg='', callback=None): + ''' + Apply the metadata changes in id_map to the database synchronously + id_map must be a mapping of ids to Metadata objects. Set any fields you + do not want updated in the Metadata object to null. An easy way to do + that is to create a metadata object as Metadata(_('Unknown')) and then + only set the fields you want changed on this object. + + callback can be either None or a function accepting a single argument, + in which case it is called after applying is complete with the list of + changed ids. + ''' + if title is None: + title = _('Applying changed metadata') + self.apply_id_map = list(id_map.iteritems()) + self.apply_current_idx = 0 + self.apply_failures = [] + self.applied_ids = [] + self.apply_pd = None + self.apply_callback = callback + if len(self.apply_id_map) > 1: + from calibre.gui2.dialogs.progress import ProgressDialog + self.apply_pd = ProgressDialog(title, msg, min=0, + max=len(self.apply_id_map)-1, parent=self.gui, + cancelable=False) + self.apply_pd.setModal(True) + self.apply_pd.show() + self.do_one_apply() + + + def do_one_apply(self): + if self.apply_current_idx >= len(self.apply_id_map): + return self.finalize_apply() + + i, mi = self.apply_id_map[self.apply_current_idx] + db = self.gui.current_db + try: + set_title = not mi.is_null('title') + set_authors = not mi.is_null('authors') + idents = db.get_identifiers(i, index_is_id=True) + if mi.identifiers: + idents.update(mi.identifiers) + mi.identifiers = idents + db.set_metadata(i, mi, commit=False, set_title=set_title, + set_authors=set_authors, notify=False) + self.applied_ids.append(i) + except: + import traceback + self.apply_failures.append((i, traceback.format_exc())) + + try: + if mi.cover: + os.remove(mi.cover) + except: + pass + + self.apply_current_idx += 1 + if self.apply_pd is not None: + self.apply_pd.value += 1 + QTimer.singleShot(50, self.do_one_apply) + + def finalize_apply(self): + db = self.gui.current_db + db.commit() + + if self.apply_pd is not None: + self.apply_pd.hide() + + if self.apply_failures: + msg = [] + for i, tb in self.apply_failures: + title = db.title(i, index_is_id=True) + authors = db.authors(i, index_is_id=True) + if authors: + authors = [x.replace('|', ',') for x in authors.split(',')] + title += ' - ' + authors_to_string(authors) + msg.append(title+'\n\n'+tb+'\n'+('*'*80)) + + error_dialog(self.gui, _('Some failures'), + _('Failed to apply updated metadata for some books' + ' in your library. Click "Show Details" to see ' + 'details.'), det_msg='\n\n'.join(msg), show=True) + if self.applied_ids: + cr = self.gui.library_view.currentIndex().row() + self.gui.library_view.model().refresh_ids( + self.applied_ids, cr) + if self.gui.cover_flow: + self.gui.cover_flow.dataChanged() + self.gui.tags_view.recount() + + self.apply_id_map = [] + self.apply_pd = None + try: + if callable(self.apply_callback): + self.apply_callback(self.applied_ids) + finally: + self.apply_callback = None + + # }}} diff --git a/src/calibre/gui2/actions/fetch_news.py b/src/calibre/gui2/actions/fetch_news.py index 5c2a5e9663..f94dfbc88c 100644 --- a/src/calibre/gui2/actions/fetch_news.py +++ b/src/calibre/gui2/actions/fetch_news.py @@ -5,6 +5,8 @@ __license__ = 'GPL v3' __copyright__ = '2010, Kovid Goyal <kovid@kovidgoyal.net>' __docformat__ = 'restructuredtext en' +import gc + from PyQt4.Qt import Qt from calibre.gui2 import Dispatcher @@ -53,11 +55,26 @@ class FetchNewsAction(InterfaceAction): def scheduled_recipe_fetched(self, job): temp_files, fmt, arg = self.conversion_jobs.pop(job) - pt = temp_files[0] + fname = temp_files[0].name if job.failed: self.scheduler.recipe_download_failed(arg) return self.gui.job_exception(job) - id = self.gui.library_view.model().add_news(pt.name, arg) + id = self.gui.library_view.model().add_news(fname, arg) + + # Arg may contain a "keep_issues" variable. If it is non-zero, + # delete all but newest x issues. + try: + keep_issues = int(arg['keep_issues']) + except: + keep_issues = 0 + if keep_issues > 0: + ids_with_tag = list(sorted(self.gui.library_view.model(). + db.tags_older_than(arg['title'], + None, must_have_tag=_('News')), reverse=True)) + ids_to_delete = ids_with_tag[keep_issues:] + if ids_to_delete: + self.gui.library_view.model().delete_books_by_id(ids_to_delete) + self.gui.library_view.model().reset() sync = self.gui.news_to_be_synced sync.add(id) @@ -66,5 +83,6 @@ class FetchNewsAction(InterfaceAction): self.gui.status_bar.show_message(arg['title'] + _(' fetched.'), 3000) self.gui.email_news(id) self.gui.sync_news() + gc.collect() diff --git a/src/calibre/gui2/actions/help.py b/src/calibre/gui2/actions/help.py index 2294daf4bb..7d5851c83d 100644 --- a/src/calibre/gui2/actions/help.py +++ b/src/calibre/gui2/actions/help.py @@ -19,7 +19,7 @@ class HelpAction(InterfaceAction): self.qaction.triggered.connect(self.show_help) def show_help(self, *args): - open_url(QUrl('http://calibre-ebook.com/user_manual')) + open_url(QUrl('http://manual.calibre-ebook.com')) diff --git a/src/calibre/gui2/actions/next_match.py b/src/calibre/gui2/actions/next_match.py index 1c74719674..8e076655a9 100644 --- a/src/calibre/gui2/actions/next_match.py +++ b/src/calibre/gui2/actions/next_match.py @@ -11,7 +11,7 @@ class NextMatchAction(InterfaceAction): name = 'Move to next highlighted book' action_spec = (_('Move to next match'), 'arrow-down.png', _('Move to next highlighted match'), [_('N'), _('F3')]) - dont_add_to = frozenset(['toolbar-device', 'context-menu-device']) + dont_add_to = frozenset(['menubar-device', 'toolbar-device', 'context-menu-device']) action_type = 'current' def genesis(self): diff --git a/src/calibre/gui2/actions/open.py b/src/calibre/gui2/actions/open.py index 141ff01a66..a66f68eee5 100644 --- a/src/calibre/gui2/actions/open.py +++ b/src/calibre/gui2/actions/open.py @@ -13,7 +13,7 @@ class OpenFolderAction(InterfaceAction): name = 'Open Folder' action_spec = (_('Open containing folder'), 'document_open.png', None, _('O')) - dont_add_to = frozenset(['toolbar-device', 'context-menu-device']) + dont_add_to = frozenset(['menubar-device', 'toolbar-device', 'context-menu-device']) action_type = 'current' def genesis(self): diff --git a/src/calibre/gui2/actions/preferences.py b/src/calibre/gui2/actions/preferences.py index ee52f06aac..1ebd4ea6ba 100644 --- a/src/calibre/gui2/actions/preferences.py +++ b/src/calibre/gui2/actions/preferences.py @@ -10,23 +10,27 @@ from PyQt4.Qt import QIcon, QMenu, Qt from calibre.gui2.actions import InterfaceAction from calibre.gui2.preferences.main import Preferences from calibre.gui2 import error_dialog -from calibre.constants import DEBUG +from calibre.constants import DEBUG, isosx class PreferencesAction(InterfaceAction): name = 'Preferences' action_spec = (_('Preferences'), 'config.png', None, _('Ctrl+P')) - dont_remove_from = frozenset(['toolbar']) def genesis(self): pm = QMenu() pm.addAction(QIcon(I('config.png')), _('Preferences'), self.do_config) + if isosx: + pm.addAction(QIcon(I('config.png')), _('Change calibre behavior'), self.do_config) pm.addAction(QIcon(I('wizard.png')), _('Run welcome wizard'), self.gui.run_wizard) if not DEBUG: pm.addSeparator() - pm.addAction(QIcon(I('debug.png')), _('Restart in debug mode'), + ac = pm.addAction(QIcon(I('debug.png')), _('Restart in debug mode'), self.debug_restart) + ac.setShortcut('Ctrl+Shift+R') + self.gui.addAction(ac) + self.qaction.setMenu(pm) self.preferences_menu = pm for x in (self.gui.preferences_action, self.qaction): diff --git a/src/calibre/gui2/actions/show_book_details.py b/src/calibre/gui2/actions/show_book_details.py index 67903a7d58..1c28a08a79 100644 --- a/src/calibre/gui2/actions/show_book_details.py +++ b/src/calibre/gui2/actions/show_book_details.py @@ -15,7 +15,7 @@ class ShowBookDetailsAction(InterfaceAction): name = 'Show Book Details' action_spec = (_('Show book details'), 'dialog_information.png', None, _('I')) - dont_add_to = frozenset(['toolbar-device', 'context-menu-device']) + dont_add_to = frozenset(['menubar-device', 'toolbar-device', 'context-menu-device']) action_type = 'current' def genesis(self): @@ -30,5 +30,5 @@ class ShowBookDetailsAction(InterfaceAction): index = self.gui.library_view.currentIndex() if index.isValid(): BookInfo(self.gui, self.gui.library_view, index, - self.gui.iactions['View'].view_format_by_id).show() + self.gui.book_details.handle_click).show() diff --git a/src/calibre/gui2/actions/store.py b/src/calibre/gui2/actions/store.py new file mode 100644 index 0000000000..7f9b538bcf --- /dev/null +++ b/src/calibre/gui2/actions/store.py @@ -0,0 +1,150 @@ +# -*- coding: utf-8 -*- + +from __future__ import (unicode_literals, division, absolute_import, print_function) + +__license__ = 'GPL 3' +__copyright__ = '2011, John Schember <john@nachtimwald.com>' +__docformat__ = 'restructuredtext en' + +from functools import partial + +from PyQt4.Qt import QMenu, QIcon, QSize + +from calibre.gui2 import error_dialog +from calibre.gui2.actions import InterfaceAction +from calibre.gui2.dialogs.confirm_delete import confirm + +class StoreAction(InterfaceAction): + + name = 'Store' + action_spec = (_('Get books'), 'store.png', None, None) + + def genesis(self): + self.qaction.triggered.connect(self.do_search) + self.store_menu = QMenu() + self.load_menu() + + def load_menu(self): + self.store_menu.clear() + self.store_menu.addAction(_('Search for ebooks'), self.search) + self.store_menu.addAction(_('Search for this author'), self.search_author) + self.store_menu.addAction(_('Search for this title'), self.search_title) + self.store_menu.addAction(_('Search for this book'), self.search_author_title) + self.store_menu.addSeparator() + self.store_list_menu = self.store_menu.addMenu(_('Stores')) + icon = QIcon() + icon.addFile(I('donate.png'), QSize(16, 16)) + for n, p in sorted(self.gui.istores.items(), key=lambda x: x[0].lower()): + if p.base_plugin.affiliate: + self.store_list_menu.addAction(icon, n, partial(self.open_store, p)) + else: + self.store_list_menu.addAction(n, partial(self.open_store, p)) + self.store_menu.addSeparator() + self.store_menu.addAction(_('Choose stores'), self.choose) + self.qaction.setMenu(self.store_menu) + + def do_search(self): + return self.search() + + def search(self, query=''): + self.show_disclaimer() + from calibre.gui2.store.search.search import SearchDialog + sd = SearchDialog(self.gui, self.gui, query) + sd.exec_() + + def _get_selected_row(self): + rows = self.gui.current_view().selectionModel().selectedRows() + if not rows or len(rows) == 0: + return None + return rows[0].row() + + def _get_author(self, row): + authors = [] + + if self.gui.current_view() is self.gui.library_view: + a = self.gui.library_view.model().authors(row) + authors = a.split(',') + else: + mi = self.gui.current_view().model().get_book_display_info(row) + authors = mi.authors + + corrected_authors = [] + for x in authors: + a = x.split('|') + a.reverse() + a = ' '.join(a) + corrected_authors.append(a) + + return ' & '.join(corrected_authors).strip() + + def search_author(self): + row = self._get_selected_row() + if row == None: + error_dialog(self.gui, _('Cannot search'), _('No book selected'), show=True) + return + + query = 'author:"%s"' % self._get_author(row) + self.search(query) + + def _get_title(self, row): + title = '' + if self.gui.current_view() is self.gui.library_view: + title = self.gui.library_view.model().title(row) + else: + mi = self.gui.current_view().model().get_book_display_info(row) + title = mi.title + + return title.strip() + + def search_title(self): + row = self._get_selected_row() + if row == None: + error_dialog(self.gui, _('Cannot search'), _('No book selected'), show=True) + return + + query = 'title:"%s"' % self._get_title(row) + self.search(query) + + def search_author_title(self): + row = self._get_selected_row() + if row == None: + error_dialog(self.gui, _('Cannot search'), _('No book selected'), show=True) + return + + query = 'author:"%s" title:"%s"' % (self._get_author(row), self._get_title(row)) + self.search(query) + + def choose(self): + from calibre.gui2.store.config.chooser.chooser_dialog import StoreChooserDialog + d = StoreChooserDialog(self.gui) + d.exec_() + self.gui.load_store_plugins() + self.load_menu() + + def open_store(self, store_plugin): + self.show_disclaimer() + store_plugin.open(self.gui) + + def show_disclaimer(self): + confirm(('<p>' + + _('Calibre helps you find the ebooks you want by searching ' + 'the websites of various commercial and public domain ' + 'book sources for you.') + + '<p>' + + _('Using the integrated search you can easily find which ' + 'store has the book you are looking for, at the best price. ' + 'You also get DRM status and other useful information.') + + '<p>' + + _('All transactions (paid or otherwise) are handled between ' + 'you and the book seller. ' + 'Calibre is not part of this process and any issues related ' + 'to a purchase should be directed to the website you are ' + 'buying from. Be sure to double check that any books you get ' + 'will work with your e-book reader, especially if the book you ' + 'are buying has ' + '<a href="http://drmfree.calibre-ebook.com/about#drm">DRM</a>.' + )), 'about_get_books_msg', + parent=self.gui, show_cancel_button=False, + confirm_msg=_('Show this message again'), + pixmap='dialog_information.png', title=_('About Get Books')) + diff --git a/src/calibre/gui2/actions/tweak_epub.py b/src/calibre/gui2/actions/tweak_epub.py index 212aff8019..c9f2d7a8c6 100755 --- a/src/calibre/gui2/actions/tweak_epub.py +++ b/src/calibre/gui2/actions/tweak_epub.py @@ -15,7 +15,7 @@ class TweakEpubAction(InterfaceAction): action_spec = (_('Tweak ePub'), 'trim.png', _('Make small changes to ePub format books'), _('T')) - dont_add_to = frozenset(['toolbar-device', 'context-menu-device']) + dont_add_to = frozenset(['menubar-device', 'toolbar-device', 'context-menu-device']) action_type = 'current' def genesis(self): diff --git a/src/calibre/gui2/actions/view.py b/src/calibre/gui2/actions/view.py index 7b14de8176..6cf5c5d5af 100644 --- a/src/calibre/gui2/actions/view.py +++ b/src/calibre/gui2/actions/view.py @@ -6,9 +6,8 @@ __copyright__ = '2010, Kovid Goyal <kovid@kovidgoyal.net>' __docformat__ = 'restructuredtext en' import os, time -from functools import partial -from PyQt4.Qt import Qt, QMenu +from PyQt4.Qt import Qt, QMenu, QAction, pyqtSignal from calibre.constants import isosx from calibre.gui2 import error_dialog, Dispatcher, question_dialog, config, \ @@ -18,6 +17,19 @@ from calibre.utils.config import prefs from calibre.ptempfile import PersistentTemporaryFile from calibre.gui2.actions import InterfaceAction +class HistoryAction(QAction): + + view_historical = pyqtSignal(object) + + def __init__(self, id_, title, parent): + QAction.__init__(self, title, parent) + self.id = id_ + self.triggered.connect(self._triggered) + + def _triggered(self): + self.view_historical.emit(self.id) + + class ViewAction(InterfaceAction): name = 'View' @@ -28,11 +40,51 @@ class ViewAction(InterfaceAction): self.persistent_files = [] self.qaction.triggered.connect(self.view_book) self.view_menu = QMenu() - self.view_menu.addAction(_('View'), partial(self.view_book, False)) - ac = self.view_menu.addAction(_('View specific format')) - ac.setShortcut((Qt.ControlModifier if isosx else Qt.AltModifier)+Qt.Key_V) + ac = self.view_specific_action = QAction(_('View specific format'), + self.gui) self.qaction.setMenu(self.view_menu) + ac.setShortcut(Qt.AltModifier+Qt.Key_V) ac.triggered.connect(self.view_specific_format, type=Qt.QueuedConnection) + ac = self.view_action = QAction(self.qaction.icon(), + self.qaction.text(), self.gui) + ac.triggered.connect(self.view_book) + ac = self.create_action(spec=(_('Read a random book'), 'catalog.png', + None, None), attr='action_pick_random') + ac.triggered.connect(self.view_random) + ac = self.clear_history_action = QAction( + _('Clear recently viewed list'), self.gui) + ac.triggered.connect(self.clear_history) + + def initialization_complete(self): + self.build_menus(self.gui.current_db) + + def build_menus(self, db): + self.view_menu.clear() + self.view_menu.addAction(self.view_action) + self.view_menu.addAction(self.view_specific_action) + self.view_menu.addSeparator() + self.view_menu.addAction(self.action_pick_random) + self.history_actions = [] + history = db.prefs.get('gui_view_history', []) + if history: + self.view_menu.addSeparator() + for id_, title in history: + ac = HistoryAction(id_, title, self.view_menu) + self.view_menu.addAction(ac) + ac.view_historical.connect(self.view_historical) + self.view_menu.addSeparator() + self.view_menu.addAction(self.clear_history_action) + + def clear_history(self): + db = self.gui.current_db + db.prefs['gui_view_history'] = [] + self.build_menus(db) + + def view_historical(self, id_): + self._view_calibre_books([id_]) + + def library_changed(self, db): + self.build_menus(db) def location_selected(self, loc): enabled = loc == 'library' @@ -40,15 +92,17 @@ class ViewAction(InterfaceAction): action.setEnabled(enabled) def view_format(self, row, format): - fmt_path = self.gui.library_view.model().db.format_abspath(row, format) - if fmt_path: - self._view_file(fmt_path) + id_ = self.gui.library_view.model().id(row) + self.view_format_by_id(id_, format) def view_format_by_id(self, id_, format): - fmt_path = self.gui.library_view.model().db.format_abspath(id_, format, + db = self.gui.current_db + fmt_path = db.format_abspath(id_, format, index_is_id=True) if fmt_path: + title = db.title(id_, index_is_id=True) self._view_file(fmt_path) + self.update_history([(id_, title)]) def book_downloaded_for_viewing(self, job): if job.failed: @@ -151,6 +205,58 @@ class ViewAction(InterfaceAction): def view_specific_book(self, index): self._view_books([index]) + def view_random(self, *args): + self.gui.iactions['Choose Library'].pick_random() + self._view_books([self.gui.library_view.currentIndex()]) + + def _view_calibre_books(self, ids): + db = self.gui.current_db + views = [] + for id_ in ids: + try: + formats = db.formats(id_, index_is_id=True) + except: + error_dialog(self.gui, _('Cannot view'), + _('This book no longer exists in your library'), show=True) + self.update_history([], remove=set([id_])) + continue + + title = db.title(id_, index_is_id=True) + if not formats: + error_dialog(self.gui, _('Cannot view'), + _('%s has no available formats.')%(title,), show=True) + continue + + formats = formats.upper().split(',') + + fmt = formats[0] + for format in prefs['input_format_order']: + if format in formats: + fmt = format + break + views.append((id_, title)) + self.view_format_by_id(id_, fmt) + + self.update_history(views) + + def update_history(self, views, remove=frozenset()): + db = self.gui.current_db + if views: + seen = set() + history = [] + for id_, title in views + db.prefs.get('gui_view_history', []): + if title not in seen: + seen.add(title) + history.append((id_, title)) + + db.prefs['gui_view_history'] = history[:10] + self.build_menus(db) + if remove: + history = db.prefs.get('gui_view_history', []) + history = [x for x in history if x[0] not in remove] + db.prefs['gui_view_history'] = history[:10] + self.build_menus(db) + def _view_books(self, rows): if not rows or len(rows) == 0: self._launch_viewer() @@ -160,28 +266,8 @@ class ViewAction(InterfaceAction): return if self.gui.current_view() is self.gui.library_view: - for row in rows: - if hasattr(row, 'row'): - row = row.row() - - formats = self.gui.library_view.model().db.formats(row) - title = self.gui.library_view.model().db.title(row) - if not formats: - error_dialog(self.gui, _('Cannot view'), - _('%s has no available formats.')%(title,), show=True) - continue - - formats = formats.upper().split(',') - - - in_prefs = False - for format in prefs['input_format_order']: - if format in formats: - in_prefs = True - self.view_format(row, format) - break - if not in_prefs: - self.view_format(row, formats[0]) + ids = list(map(self.gui.library_view.model().id, rows)) + self._view_calibre_books(ids) else: paths = self.gui.current_view().model().paths(rows) for path in paths: diff --git a/src/calibre/gui2/add.py b/src/calibre/gui2/add.py index f40cf0ff75..44b5bb446b 100644 --- a/src/calibre/gui2/add.py +++ b/src/calibre/gui2/add.py @@ -78,7 +78,7 @@ class RecursiveFind(QThread): # {{{ if isinstance(root, unicode): root = root.encode(filesystem_encoding) self.walk(root) - except Exception, err: + except Exception as err: import traceback traceback.print_exc() try: diff --git a/src/calibre/gui2/add_wizard/__init__.py b/src/calibre/gui2/add_wizard/__init__.py index da1879ae97..5df3f601a0 100644 --- a/src/calibre/gui2/add_wizard/__init__.py +++ b/src/calibre/gui2/add_wizard/__init__.py @@ -11,7 +11,6 @@ from PyQt4.Qt import QWizard, QWizardPage, QIcon, QPixmap, Qt, QThread, \ pyqtSignal from calibre.gui2 import error_dialog, choose_dir, gprefs -from calibre.constants import filesystem_encoding from calibre.library.add_to_library import find_folders_under, \ find_books_in_folder, hash_merge_format_collections @@ -122,20 +121,19 @@ class WelcomePage(WizardPage, WelcomeWidget): x = unicode(self.opt_root_folder.text()).strip() if not x: return None - return os.path.abspath(x.encode(filesystem_encoding)) + return os.path.abspath(x) def get_one_per_folder(self): return self.opt_one_per_folder.isChecked() def validatePage(self): x = self.get_root_folder() - xu = x.decode(filesystem_encoding) if x and os.access(x, os.R_OK) and os.path.isdir(x): - gprefs['add wizard root folder'] = xu + gprefs['add wizard root folder'] = x gprefs['add wizard one per folder'] = self.get_one_per_folder() return True error_dialog(self, _('Invalid root folder'), - xu + _('is not a valid root folder'), show=True) + x + _('is not a valid root folder'), show=True) return False # }}} diff --git a/src/calibre/gui2/bars.py b/src/calibre/gui2/bars.py new file mode 100644 index 0000000000..7dc0567d95 --- /dev/null +++ b/src/calibre/gui2/bars.py @@ -0,0 +1,316 @@ +#!/usr/bin/env python +# vim:fileencoding=UTF-8:ts=4:sw=4:sta:et:sts=4:ai +from __future__ import (unicode_literals, division, absolute_import, + print_function) + +__license__ = 'GPL v3' +__copyright__ = '2011, Kovid Goyal <kovid@kovidgoyal.net>' +__docformat__ = 'restructuredtext en' + + +from PyQt4.Qt import (QObject, QToolBar, Qt, QSize, QToolButton, QVBoxLayout, + QLabel, QWidget, QAction, QMenuBar, QMenu) + +from calibre.constants import isosx +from calibre.gui2 import gprefs + +class ToolBar(QToolBar): # {{{ + + def __init__(self, donate, location_manager, parent): + QToolBar.__init__(self, parent) + self.setContextMenuPolicy(Qt.PreventContextMenu) + self.setMovable(False) + self.setFloatable(False) + self.setOrientation(Qt.Horizontal) + self.setAllowedAreas(Qt.TopToolBarArea|Qt.BottomToolBarArea) + self.setStyleSheet('QToolButton:checked { font-weight: bold }') + self.preferred_width = self.sizeHint().width() + self.gui = parent + self.donate_button = donate + self.added_actions = [] + + self.location_manager = location_manager + donate.setAutoRaise(True) + donate.setCursor(Qt.PointingHandCursor) + self.setAcceptDrops(True) + self.showing_donate = False + + def resizeEvent(self, ev): + QToolBar.resizeEvent(self, ev) + style = self.get_text_style() + self.setToolButtonStyle(style) + if hasattr(self, 'd_widget') and hasattr(self.d_widget, 'filler'): + self.d_widget.filler.setVisible(style != Qt.ToolButtonIconOnly) + + def get_text_style(self): + style = Qt.ToolButtonTextUnderIcon + s = gprefs['toolbar_icon_size'] + if s != 'off': + p = gprefs['toolbar_text'] + if p == 'never': + style = Qt.ToolButtonIconOnly + elif p == 'auto' and self.preferred_width > self.width()+35: + style = Qt.ToolButtonIconOnly + return style + + def contextMenuEvent(self, *args): + pass + + def update_lm_actions(self): + for ac in self.added_actions: + if ac in self.location_manager.all_actions: + ac.setVisible(ac in self.location_manager.available_actions) + + def init_bar(self, actions): + self.showing_donate = False + for ac in self.added_actions: + m = ac.menu() + if m is not None: + m.setVisible(False) + + self.clear() + self.added_actions = [] + + bar = self + + for what in actions: + if what is None: + bar.addSeparator() + elif what == 'Location Manager': + for ac in self.location_manager.all_actions: + bar.addAction(ac) + bar.added_actions.append(ac) + bar.setup_tool_button(bar, ac, QToolButton.MenuButtonPopup) + ac.setVisible(False) + elif what == 'Donate': + self.d_widget = QWidget() + self.d_widget.setLayout(QVBoxLayout()) + self.d_widget.layout().addWidget(self.donate_button) + if isosx: + self.d_widget.setStyleSheet('QWidget, QToolButton {background-color: none; border: none; }') + self.d_widget.layout().setContentsMargins(0,0,0,0) + self.d_widget.setContentsMargins(0,0,0,0) + self.d_widget.filler = QLabel(u'\u00a0') + self.d_widget.layout().addWidget(self.d_widget.filler) + bar.addWidget(self.d_widget) + self.showing_donate = True + elif what in self.gui.iactions: + action = self.gui.iactions[what] + bar.addAction(action.qaction) + self.added_actions.append(action.qaction) + self.setup_tool_button(bar, action.qaction, action.popup_type) + self.preferred_width = self.sizeHint().width() + + def setup_tool_button(self, bar, ac, menu_mode=None): + ch = bar.widgetForAction(ac) + if ch is None: + ch = self.child_bar.widgetForAction(ac) + ch.setCursor(Qt.PointingHandCursor) + ch.setAutoRaise(True) + if ac.menu() is not None and menu_mode is not None: + ch.setPopupMode(menu_mode) + return ch + + #support drag&drop from/to library from/to reader/card + def dragEnterEvent(self, event): + md = event.mimeData() + if md.hasFormat("application/calibre+from_library") or \ + md.hasFormat("application/calibre+from_device"): + event.setDropAction(Qt.CopyAction) + event.accept() + else: + event.ignore() + + def dragMoveEvent(self, event): + allowed = False + md = event.mimeData() + #Drop is only allowed in the location manager widget's different from the selected one + for ac in self.location_manager.available_actions: + w = self.widgetForAction(ac) + if w is not None: + if ( md.hasFormat("application/calibre+from_library") or \ + md.hasFormat("application/calibre+from_device") ) and \ + w.geometry().contains(event.pos()) and \ + isinstance(w, QToolButton) and not w.isChecked(): + allowed = True + break + if allowed: + event.acceptProposedAction() + else: + event.ignore() + + def dropEvent(self, event): + data = event.mimeData() + + mime = 'application/calibre+from_library' + if data.hasFormat(mime): + ids = list(map(int, str(data.data(mime)).split())) + tgt = None + for ac in self.location_manager.available_actions: + w = self.widgetForAction(ac) + if w is not None and w.geometry().contains(event.pos()): + tgt = ac.calibre_name + if tgt is not None: + if tgt == 'main': + tgt = None + self.gui.sync_to_device(tgt, False, send_ids=ids) + event.accept() + + mime = 'application/calibre+from_device' + if data.hasFormat(mime): + paths = [unicode(u.toLocalFile()) for u in data.urls()] + if paths: + self.gui.iactions['Add Books'].add_books_from_device( + self.gui.current_view(), paths=paths) + event.accept() + +# }}} + +class MenuAction(QAction): # {{{ + + def __init__(self, clone, parent): + QAction.__init__(self, clone.text(), parent) + self.clone = clone + clone.changed.connect(self.clone_changed) + + def clone_changed(self): + self.setText(self.clone.text()) +# }}} + +class MenuBar(QMenuBar): # {{{ + + def __init__(self, location_manager, parent): + QMenuBar.__init__(self, parent) + self.gui = parent + self.setNativeMenuBar(True) + + self.location_manager = location_manager + self.added_actions = [] + + self.donate_action = QAction(_('Donate'), self) + self.donate_menu = QMenu() + self.donate_menu.addAction(self.gui.donate_action) + self.donate_action.setMenu(self.donate_menu) + + def update_lm_actions(self): + for ac in self.added_actions: + if ac in self.location_manager.all_actions: + ac.setVisible(ac in self.location_manager.available_actions) + + def init_bar(self, actions): + for ac in self.added_actions: + m = ac.menu() + if m is not None: + m.setVisible(False) + + self.clear() + self.added_actions = [] + + for what in actions: + if what is None: + continue + elif what == 'Location Manager': + for ac in self.location_manager.all_actions: + ac = self.build_menu(ac) + self.addAction(ac) + self.added_actions.append(ac) + ac.setVisible(False) + elif what == 'Donate': + self.addAction(self.donate_action) + elif what in self.gui.iactions: + action = self.gui.iactions[what] + ac = self.build_menu(action.qaction) + self.addAction(ac) + self.added_actions.append(ac) + + def build_menu(self, action): + m = action.menu() + ac = MenuAction(action, self) + if m is None: + m = QMenu() + m.addAction(action) + ac.setMenu(m) + return ac + +# }}} + +class BarsManager(QObject): + + def __init__(self, donate_button, location_manager, parent): + QObject.__init__(self, parent) + self.donate_button, self.location_manager = (donate_button, + location_manager) + + bars = [ToolBar(donate_button, location_manager, parent) for i in + range(3)] + self.main_bars = tuple(bars[:2]) + self.child_bars = tuple(bars[2:]) + + self.menu_bar = MenuBar(self.location_manager, self.parent()) + self.parent().setMenuBar(self.menu_bar) + + self.apply_settings() + self.init_bars() + + def database_changed(self, db): + pass + + @property + def bars(self): + for x in self.main_bars + self.child_bars: + yield x + + @property + def showing_donate(self): + for b in self.bars: + if b.isVisible() and b.showing_donate: + return True + return False + + def init_bars(self): + self.bar_actions = tuple( + [gprefs['action-layout-toolbar'+x] for x in ('', '-device')] + + [gprefs['action-layout-toolbar-child']] + + [gprefs['action-layout-menubar']] + + [gprefs['action-layout-menubar-device']] + ) + + for bar, actions in zip(self.bars, self.bar_actions[:3]): + bar.init_bar(actions) + + def update_bars(self): + ''' + This shows the correct main toolbar and rebuilds the menubar based on + whether a device is connected or not. Note that the toolbars are + explicitly not rebuilt, this is to workaround a Qt limitation iwth + QToolButton's popup menus and modal dialogs. If you want the toolbars + rebuilt, call init_bars(). + ''' + showing_device = self.location_manager.has_device + main_bar = self.main_bars[1 if showing_device else 0] + child_bar = self.child_bars[0] + for bar in self.bars: + bar.setVisible(False) + bar.update_lm_actions() + if main_bar.added_actions: + main_bar.setVisible(True) + if child_bar.added_actions: + child_bar.setVisible(True) + + self.menu_bar.init_bar(self.bar_actions[4 if showing_device else 3]) + self.menu_bar.update_lm_actions() + self.menu_bar.setVisible(bool(self.menu_bar.added_actions)) + + def apply_settings(self): + sz = gprefs['toolbar_icon_size'] + sz = {'off':0, 'small':24, 'medium':48, 'large':64}[sz] + style = Qt.ToolButtonTextUnderIcon + if sz > 0 and gprefs['toolbar_text'] == 'never': + style = Qt.ToolButtonIconOnly + + for bar in self.bars: + bar.setIconSize(QSize(sz, sz)) + bar.setToolButtonStyle(style) + self.donate_button.set_normal_icon_size(sz, sz) + + diff --git a/src/calibre/gui2/book_details.py b/src/calibre/gui2/book_details.py index 2f7892692c..f94e179166 100644 --- a/src/calibre/gui2/book_details.py +++ b/src/calibre/gui2/book_details.py @@ -5,71 +5,161 @@ __license__ = 'GPL v3' __copyright__ = '2010, Kovid Goyal <kovid@kovidgoyal.net>' __docformat__ = 'restructuredtext en' -import os, collections, sys -from Queue import Queue -from PyQt4.Qt import QPixmap, QSize, QWidget, Qt, pyqtSignal, QUrl, \ - QPropertyAnimation, QEasingCurve, QThread, QApplication, QFontInfo, \ - QSizePolicy, QPainter, QRect, pyqtProperty, QLayout, QPalette +from PyQt4.Qt import (QPixmap, QSize, QWidget, Qt, pyqtSignal, QUrl, + QPropertyAnimation, QEasingCurve, QApplication, QFontInfo, + QSizePolicy, QPainter, QRect, pyqtProperty, QLayout, QPalette, QMenu) from PyQt4.QtWebKit import QWebView -from calibre import fit_image, prepare_string_for_xml -from calibre.gui2.widgets import IMAGE_EXTENSIONS +from calibre import fit_image, force_unicode, prepare_string_for_xml +from calibre.gui2.dnd import (dnd_has_image, dnd_get_image, dnd_get_files, + IMAGE_EXTENSIONS, dnd_has_extension) from calibre.ebooks import BOOK_EXTENSIONS -from calibre.constants import preferred_encoding +from calibre.ebooks.metadata.book.base import (field_metadata, Metadata) +from calibre.ebooks.metadata import fmt_sidx +from calibre.ebooks.metadata.sources.identify import urls_from_identifiers +from calibre.constants import filesystem_encoding from calibre.library.comments import comments_to_html -from calibre.gui2 import config, open_local_file, open_url +from calibre.gui2 import (config, open_local_file, open_url, pixmap_to_data, + gprefs) from calibre.utils.icu import sort_key -# render_rows(data) {{{ -WEIGHTS = collections.defaultdict(lambda : 100) -WEIGHTS[_('Path')] = 5 -WEIGHTS[_('Formats')] = 1 -WEIGHTS[_('Collections')] = 2 -WEIGHTS[_('Series')] = 3 -WEIGHTS[_('Tags')] = 4 +def render_html(mi, css, vertical, widget, all_fields=False): # {{{ + table = render_data(mi, all_fields=all_fields, + use_roman_numbers=config['use_roman_numerals_for_series_number']) -def render_rows(data): - keys = data.keys() - # First sort by name. The WEIGHTS sort will preserve this sub-order - keys.sort(key=sort_key) - keys.sort(key=lambda x: WEIGHTS[x]) - rows = [] - for key in keys: - txt = data[key] - if key in ('id', _('Comments')) or not hasattr(txt, 'strip') or not txt.strip() or \ - txt == 'None': + def color_to_string(col): + ans = '#000000' + if col.isValid(): + col = col.toRgb() + if col.isValid(): + ans = unicode(col.name()) + return ans + + f = QFontInfo(QApplication.font(widget)).pixelSize() + c = color_to_string(QApplication.palette().color(QPalette.Normal, + QPalette.WindowText)) + templ = u'''\ + <html> + <head> + <style type="text/css"> + body, td {background-color: transparent; font-size: %dpx; color: %s } + </style> + <style type="text/css"> + %s + </style> + </head> + <body> + %%s + </body> + <html> + '''%(f, c, css) + comments = u'' + if mi.comments: + comments = comments_to_html(force_unicode(mi.comments)) + right_pane = u'<div id="comments" class="comments">%s</div>'%comments + + if vertical: + ans = templ%(table+right_pane) + else: + ans = templ%(u'<table><tr><td valign="top" ' + 'style="padding-right:2em; width:40%%">%s</td><td valign="top">%s</td></tr></table>' + % (table, right_pane)) + return ans + +def get_field_list(fm, use_defaults=False): + src = gprefs.defaults if use_defaults else gprefs + fieldlist = list(src['book_display_fields']) + names = frozenset([x[0] for x in fieldlist]) + for field in fm.displayable_field_keys(): + if field not in names: + fieldlist.append((field, True)) + return fieldlist + +def render_data(mi, use_roman_numbers=True, all_fields=False): + ans = [] + isdevice = not hasattr(mi, 'id') + fm = getattr(mi, 'field_metadata', field_metadata) + + for field, display in get_field_list(fm): + metadata = fm.get(field, None) + if all_fields: + display = True + if (not display or not metadata or mi.is_null(field) or + field == 'comments'): continue - if isinstance(key, str): - key = key.decode(preferred_encoding, 'replace') - if isinstance(txt, str): - txt = txt.decode(preferred_encoding, 'replace') - if key.endswith(u':html'): - key = key[:-5] - txt = comments_to_html(txt) - elif '</font>' not in txt: - txt = prepare_string_for_xml(txt) - if 'id' in data: - if key == _('Path'): - txt = u'<a href="path:%s" title="%s">%s</a>'%(data['id'], - txt, _('Click to open')) - if key == _('Formats') and txt and txt != _('None'): - fmts = [x.strip() for x in txt.split(',')] - fmts = [u'<a href="format:%s:%s">%s</a>' % (data['id'], x, x) for x - in fmts] - txt = ', '.join(fmts) + name = metadata['name'] + if not name: + name = field + name += ':' + if metadata['datatype'] == 'comments': + val = getattr(mi, field) + if val: + val = force_unicode(val) + ans.append((field, + u'<td class="comments" colspan="2">%s</td>'%comments_to_html(val))) + elif field == 'path': + if mi.path: + path = force_unicode(mi.path, filesystem_encoding) + scheme = u'devpath' if isdevice else u'path' + url = prepare_string_for_xml(path if isdevice else + unicode(mi.id), True) + link = u'<a href="%s:%s" title="%s">%s</a>' % (scheme, url, + prepare_string_for_xml(path, True), _('Click to open')) + ans.append((field, u'<td class="title">%s</td><td>%s</td>'%(name, link))) + elif field == 'formats': + if isdevice: continue + fmts = [u'<a href="format:%s:%s">%s</a>' % (mi.id, x, x) for x + in mi.formats] + ans.append((field, u'<td class="title">%s</td><td>%s</td>'%(name, + u', '.join(fmts)))) + elif field == 'identifiers': + urls = urls_from_identifiers(mi.identifiers) + links = [u'<a href="%s" title="%s:%s">%s</a>' % (url, id_typ, id_val, name) + for name, id_typ, id_val, url in urls] + links = u', '.join(links) + if links: + ans.append((field, u'<td class="title">%s</td><td>%s</td>'%( + _('Ids')+':', links))) else: - if key == _('Path'): - txt = u'<a href="devpath:%s">%s</a>'%(txt, - _('Click to open')) + val = mi.format_field(field)[-1] + if val is None: + continue + val = prepare_string_for_xml(val) + if metadata['datatype'] == 'series': + sidx = mi.get(field+'_index') + if sidx is None: + sidx = 1.0 + val = _('Book %s of <span class="series_name">%s</span>')%(fmt_sidx(sidx, + use_roman=use_roman_numbers), + prepare_string_for_xml(getattr(mi, field))) - rows.append((key, txt)) - return rows + ans.append((field, u'<td class="title">%s</td><td>%s</td>'%(name, val))) + + dc = getattr(mi, 'device_collections', []) + if dc: + dc = u', '.join(sorted(dc, key=sort_key)) + ans.append(('device_collections', + u'<td class="title">%s</td><td>%s</td>'%( + _('Collections')+':', dc))) + + def classname(field): + try: + dt = fm[field]['datatype'] + except: + dt = 'text' + return 'datatype_%s'%dt + + ans = [u'<tr id="%s" class="%s">%s</tr>'%(field.replace('#', '_'), + classname(field), html) for field, html in ans] + # print '\n'.join(ans) + return u'<table class="fields">%s</table>'%(u'\n'.join(ans)) # }}} class CoverView(QWidget): # {{{ + cover_changed = pyqtSignal(object, object) def __init__(self, vertical, parent=None): QWidget.__init__(self, parent) @@ -115,10 +205,10 @@ class CoverView(QWidget): # {{{ def show_data(self, data): self.animation.stop() - same_item = data.get('id', True) == self.data.get('id', False) + same_item = getattr(data, 'id', True) == self.data.get('id', False) self.data = {'id':data.get('id', None)} - if data.has_key('cover'): - self.pixmap = QPixmap.fromImage(data.pop('cover')) + if data.cover_data[1]: + self.pixmap = QPixmap.fromImage(data.cover_data[1]) if self.pixmap.isNull() or self.pixmap.width() < 5 or \ self.pixmap.height() < 5: self.pixmap = self.default_pixmap @@ -151,37 +241,41 @@ class CoverView(QWidget): # {{{ fset=setCurrentPixmapSize ) + def contextMenuEvent(self, ev): + cm = QMenu(self) + paste = cm.addAction(_('Paste Cover')) + copy = cm.addAction(_('Copy Cover')) + if not QApplication.instance().clipboard().mimeData().hasImage(): + paste.setEnabled(False) + copy.triggered.connect(self.copy_to_clipboard) + paste.triggered.connect(self.paste_from_clipboard) + cm.exec_(ev.globalPos()) + + def copy_to_clipboard(self): + QApplication.instance().clipboard().setPixmap(self.pixmap) + + def paste_from_clipboard(self, pmap=None): + if not isinstance(pmap, QPixmap): + cb = QApplication.instance().clipboard() + pmap = cb.pixmap() + if pmap.isNull() and cb.supportsSelection(): + pmap = cb.pixmap(cb.Selection) + if not pmap.isNull(): + self.pixmap = pmap + self.do_layout() + self.update() + if not config['disable_animations']: + self.animation.start() + id_ = self.data.get('id', None) + if id_ is not None: + self.cover_changed.emit(id_, + pixmap_to_data(pmap)) + # }}} # Book Info {{{ -class RenderComments(QThread): - - rdone = pyqtSignal(object, object) - - def __init__(self, parent): - QThread.__init__(self, parent) - self.queue = Queue() - self.start() - - def run(self): - while True: - try: - rows, comments = self.queue.get() - except: - break - import time - time.sleep(0.001) - oint = sys.getcheckinterval() - sys.setcheckinterval(5) - try: - self.rdone.emit(rows, comments_to_html(comments)) - except: - pass - sys.setcheckinterval(oint) - - class BookInfo(QWebView): link_clicked = pyqtSignal(object) @@ -189,71 +283,29 @@ class BookInfo(QWebView): def __init__(self, vertical, parent=None): QWebView.__init__(self, parent) self.vertical = vertical - self.renderer = RenderComments(self) - self.renderer.rdone.connect(self._show_data, type=Qt.QueuedConnection) self.page().setLinkDelegationPolicy(self.page().DelegateAllLinks) self.linkClicked.connect(self.link_activated) self._link_clicked = False self.setAttribute(Qt.WA_OpaquePaintEvent, False) palette = self.palette() + self.setAcceptDrops(False) palette.setBrush(QPalette.Base, Qt.transparent) self.page().setPalette(palette) + self.css = P('templates/book_details.css', data=True).decode('utf-8') def link_activated(self, link): self._link_clicked = True + if unicode(link.scheme()) in ('http', 'https'): + return open_url(link) link = unicode(link.toString()) self.link_clicked.emit(link) def turnoff_scrollbar(self, *args): self.page().mainFrame().setScrollBarPolicy(Qt.Horizontal, Qt.ScrollBarAlwaysOff) - def show_data(self, data): - rows = render_rows(data) - rows = u'\n'.join([u'<tr><td valign="top"><b>%s:</b></td><td valign="top">%s</td></tr>'%(k,t) for - k, t in rows]) - comments = data.get(_('Comments'), '') - if not comments or comments == u'None': - comments = '' - self.renderer.queue.put((rows, comments)) - self._show_data(rows, '') - - - def _show_data(self, rows, comments): - - def color_to_string(col): - ans = '#000000' - if col.isValid(): - col = col.toRgb() - if col.isValid(): - ans = unicode(col.name()) - return ans - - f = QFontInfo(QApplication.font(self.parent())).pixelSize() - c = color_to_string(QApplication.palette().color(QPalette.Normal, - QPalette.WindowText)) - templ = u'''\ - <html> - <head> - <style type="text/css"> - body, td {background-color: transparent; font-size: %dpx; color: %s } - a { text-decoration: none; color: blue } - </style> - </head> - <body> - %%s - </body> - <html> - '''%(f, c) - if self.vertical: - if comments: - rows += u'<tr><td colspan="2">%s</td></tr>'%comments - self.setHtml(templ%(u'<table>%s</table>'%rows)) - else: - left_pane = u'<table>%s</table>'%rows - right_pane = u'<div>%s</div>'%comments - self.setHtml(templ%(u'<table><tr><td valign="top" ' - 'style="padding-right:2em; width:40%%">%s</td><td valign="top">%s</td></tr></table>' - % (left_pane, right_pane))) + def show_data(self, mi): + html = render_html(mi, self.css, self.vertical, self.parent()) + self.setHtml(html) def mouseDoubleClickEvent(self, ev): swidth = self.page().mainFrame().scrollBarGeometry(Qt.Vertical).width() @@ -358,34 +410,51 @@ class BookDetails(QWidget): # {{{ show_book_info = pyqtSignal() open_containing_folder = pyqtSignal(int) view_specific_format = pyqtSignal(int, object) + remote_file_dropped = pyqtSignal(object, object) + files_dropped = pyqtSignal(object, object) + cover_changed = pyqtSignal(object, object) # Drag 'n drop {{{ DROPABBLE_EXTENSIONS = IMAGE_EXTENSIONS+BOOK_EXTENSIONS - files_dropped = pyqtSignal(object, object) - - @classmethod - def paths_from_event(cls, event): - ''' - Accept a drop event and return a list of paths that can be read from - and represent files with extensions. - ''' - if event.mimeData().hasFormat('text/uri-list'): - urls = [unicode(u.toLocalFile()) for u in event.mimeData().urls()] - urls = [u for u in urls if os.path.splitext(u)[1] and os.access(u, os.R_OK)] - return [u for u in urls if os.path.splitext(u)[1][1:].lower() in cls.DROPABBLE_EXTENSIONS] def dragEnterEvent(self, event): - if int(event.possibleActions() & Qt.CopyAction) + \ - int(event.possibleActions() & Qt.MoveAction) == 0: - return - paths = self.paths_from_event(event) - if paths: + md = event.mimeData() + if dnd_has_extension(md, self.DROPABBLE_EXTENSIONS) or \ + dnd_has_image(md): event.acceptProposedAction() def dropEvent(self, event): - paths = self.paths_from_event(event) event.setDropAction(Qt.CopyAction) - self.files_dropped.emit(event, paths) + md = event.mimeData() + + x, y = dnd_get_image(md) + if x is not None: + # We have an image, set cover + event.accept() + if y is None: + # Local image + self.cover_view.paste_from_clipboard(x) + self.update_layout() + else: + self.remote_file_dropped.emit(x, y) + # We do not support setting cover *and* adding formats for + # a remote drop, anyway, so return + return + + # Now look for ebook files + urls, filenames = dnd_get_files(md, BOOK_EXTENSIONS) + if not urls: + # Nothing found + return + + if not filenames: + # Local files + self.files_dropped.emit(event, urls) + else: + # Remote files, use the first file + self.remote_file_dropped.emit(urls[0], filenames[0]) + event.accept() + def dragMoveEvent(self, event): event.acceptProposedAction() @@ -397,15 +466,17 @@ class BookDetails(QWidget): # {{{ self.setAcceptDrops(True) self._layout = DetailsLayout(vertical, self) self.setLayout(self._layout) + self.current_path = '' self.cover_view = CoverView(vertical, self) + self.cover_view.cover_changed.connect(self.cover_changed.emit) self._layout.addWidget(self.cover_view) self.book_info = BookInfo(vertical, self) self._layout.addWidget(self.book_info) - self.book_info.link_clicked.connect(self._link_clicked) + self.book_info.link_clicked.connect(self.handle_click) self.setCursor(Qt.PointingHandCursor) - def _link_clicked(self, link): + def handle_click(self, link): typ, _, val = link.partition(':') if typ == 'path': self.open_containing_folder.emit(int(val)) @@ -429,12 +500,23 @@ class BookDetails(QWidget): # {{{ def show_data(self, data): self.book_info.show_data(data) self.cover_view.show_data(data) + self.current_path = getattr(data, u'path', u'') + self.update_layout() + + def update_layout(self): self._layout.do_layout(self.rect()) - self.setToolTip('<p>'+_('Double-click to open Book Details window') + - '<br><br>' + _('Path') + ': ' + data.get(_('Path'), '')) + try: + sz = self.cover_view.pixmap.size() + except: + sz = QSize(0, 0) + self.setToolTip( + '<p>'+_('Double-click to open Book Details window') + + '<br><br>' + _('Path') + ': ' + self.current_path + + '<br><br>' + _('Cover size: %dx%d')%(sz.width(), sz.height()) + ) def reset_info(self): - self.show_data({}) + self.show_data(Metadata(_('Unknown'))) # }}} diff --git a/src/calibre/gui2/catalog/catalog_bibtex.py b/src/calibre/gui2/catalog/catalog_bibtex.py index ebfcc6e546..0fe641403b 100644 --- a/src/calibre/gui2/catalog/catalog_bibtex.py +++ b/src/calibre/gui2/catalog/catalog_bibtex.py @@ -46,6 +46,13 @@ class PluginWidget(QWidget, Ui_Form): for x in xrange(self.db_fields.count()): item = self.db_fields.item(x) item.setSelected(unicode(item.text()) in fields) + self.bibfile_enc.clear() + self.bibfile_enc.addItems(['utf-8', 'cp1252', 'ascii/LaTeX']) + self.bibfile_enctag.clear() + self.bibfile_enctag.addItems(['strict', 'replace', 'ignore', + 'backslashreplace']) + self.bib_entry.clear() + self.bib_entry.addItems(['mixed', 'misc', 'book']) # Update dialog fields from stored options for opt in self.OPTION_FIELDS: opt_value = gprefs.get(self.name + '_' + opt[0], opt[1]) diff --git a/src/calibre/gui2/catalog/catalog_bibtex.ui b/src/calibre/gui2/catalog/catalog_bibtex.ui index 8712d40148..5b41e96267 100644 --- a/src/calibre/gui2/catalog/catalog_bibtex.ui +++ b/src/calibre/gui2/catalog/catalog_bibtex.ui @@ -29,23 +29,7 @@ </widget> </item> <item row="1" column="0"> - <widget class="QComboBox" name="bibfile_enc"> - <item> - <property name="text"> - <string notr="true">utf-8</string> - </property> - </item> - <item> - <property name="text"> - <string notr="true">cp1252</string> - </property> - </item> - <item> - <property name="text"> - <string>ascii/LaTeX</string> - </property> - </item> - </widget> + <widget class="QComboBox" name="bibfile_enc"/> </item> <item row="1" column="1" rowspan="11"> <widget class="QListWidget" name="db_fields"> @@ -71,28 +55,7 @@ </widget> </item> <item row="3" column="0"> - <widget class="QComboBox" name="bibfile_enctag"> - <item> - <property name="text"> - <string>strict</string> - </property> - </item> - <item> - <property name="text"> - <string>replace</string> - </property> - </item> - <item> - <property name="text"> - <string>ignore</string> - </property> - </item> - <item> - <property name="text"> - <string>backslashreplace</string> - </property> - </item> - </widget> + <widget class="QComboBox" name="bibfile_enctag"/> </item> <item row="4" column="0"> <spacer name="verticalSpacer"> @@ -115,23 +78,7 @@ </widget> </item> <item row="6" column="0"> - <widget class="QComboBox" name="bib_entry"> - <item> - <property name="text"> - <string>mixed</string> - </property> - </item> - <item> - <property name="text"> - <string>misc</string> - </property> - </item> - <item> - <property name="text"> - <string>book</string> - </property> - </item> - </widget> + <widget class="QComboBox" name="bib_entry"/> </item> <item row="7" column="0"> <widget class="QCheckBox" name="impcit"> diff --git a/src/calibre/gui2/catalog/catalog_epub_mobi.py b/src/calibre/gui2/catalog/catalog_epub_mobi.py index d5149569be..8af72e51c0 100644 --- a/src/calibre/gui2/catalog/catalog_epub_mobi.py +++ b/src/calibre/gui2/catalog/catalog_epub_mobi.py @@ -193,7 +193,10 @@ class PluginWidget(QWidget,Ui_Form): opts_dict['header_note_source_field'] = self.header_note_source_field_name # Append the output profile - opts_dict['output_profile'] = [load_defaults('page_setup')['output_profile']] + try: + opts_dict['output_profile'] = [load_defaults('page_setup')['output_profile']] + except: + opts_dict['output_profile'] = ['default'] if False: print "opts_dict" for opt in sorted(opts_dict.keys()): diff --git a/src/calibre/gui2/convert/__init__.py b/src/calibre/gui2/convert/__init__.py index 925fecd693..bdcf9ede05 100644 --- a/src/calibre/gui2/convert/__init__.py +++ b/src/calibre/gui2/convert/__init__.py @@ -6,7 +6,7 @@ __license__ = 'GPL v3' __copyright__ = '2009, Kovid Goyal <kovid@kovidgoyal.net>' __docformat__ = 'restructuredtext en' -import textwrap, codecs +import textwrap, codecs, importlib from functools import partial from PyQt4.Qt import QWidget, QSpinBox, QDoubleSpinBox, QLineEdit, QTextEdit, \ @@ -22,8 +22,8 @@ from calibre.customize.ui import plugin_for_input_format def config_widget_for_input_plugin(plugin): name = plugin.name.lower().replace(' ', '_') try: - return __import__('calibre.gui2.convert.'+name, - fromlist=[1]).PluginWidget + return importlib.import_module( + 'calibre.gui2.convert.'+name).PluginWidget except ImportError: pass diff --git a/src/calibre/gui2/convert/bulk.py b/src/calibre/gui2/convert/bulk.py index 349f39ac76..576b3ca3e7 100644 --- a/src/calibre/gui2/convert/bulk.py +++ b/src/calibre/gui2/convert/bulk.py @@ -4,7 +4,7 @@ __license__ = 'GPL 3' __copyright__ = '2009, John Schember <john@nachtimwald.com>' __docformat__ = 'restructuredtext en' -import shutil +import shutil, importlib from PyQt4.Qt import QString, SIGNAL @@ -82,8 +82,8 @@ class BulkConfig(Config): output_widget = None name = self.plumber.output_plugin.name.lower().replace(' ', '_') try: - output_widget = __import__('calibre.gui2.convert.'+name, - fromlist=[1]) + output_widget = importlib.import_module( + 'calibre.gui2.convert.'+name) pw = output_widget.PluginWidget pw.ICON = I('back.png') pw.HELP = _('Options specific to the output format.') diff --git a/src/calibre/gui2/convert/fb2_output.py b/src/calibre/gui2/convert/fb2_output.py index 66296ee666..19d0995fc1 100644 --- a/src/calibre/gui2/convert/fb2_output.py +++ b/src/calibre/gui2/convert/fb2_output.py @@ -17,8 +17,10 @@ class PluginWidget(Widget, Ui_Form): ICON = I('mimetypes/fb2.png') def __init__(self, parent, get_option, get_help, db=None, book_id=None): - Widget.__init__(self, parent, ['sectionize']) + Widget.__init__(self, parent, ['sectionize', 'fb2_genre']) self.db, self.book_id = db, book_id for x in ('toc', 'files', 'nothing'): self.opt_sectionize.addItem(x) + for x in get_option('fb2_genre').option.choices: + self.opt_fb2_genre.addItem(x) self.initialize_options(get_option, get_help, db, book_id) diff --git a/src/calibre/gui2/convert/fb2_output.ui b/src/calibre/gui2/convert/fb2_output.ui index 891aa29857..c8d4230a99 100644 --- a/src/calibre/gui2/convert/fb2_output.ui +++ b/src/calibre/gui2/convert/fb2_output.ui @@ -14,7 +14,7 @@ <string>Form</string> </property> <layout class="QGridLayout" name="gridLayout"> - <item row="1" column="0"> + <item row="2" column="0"> <spacer name="verticalSpacer"> <property name="orientation"> <enum>Qt::Vertical</enum> @@ -29,21 +29,31 @@ </item> <item row="0" column="0"> <widget class="QLabel" name="label"> - <property name="text"> - <string>Sectionize:</string> - </property> - <property name="buddy"> - <cstring>opt_sectionize</cstring> - </property> - </widget> - </item> - <item row="0" column="1"> - <widget class="QComboBox" name="opt_sectionize"> - <property name="minimumContentsLength"> - <number>20</number> + <property name="text"> + <string>Sectionize:</string> + </property> + <property name="buddy"> + <cstring>opt_sectionize</cstring> </property> </widget> </item> + <item row="0" column="1"> + <widget class="QComboBox" name="opt_sectionize"> + <property name="minimumContentsLength"> + <number>20</number> + </property> + </widget> + </item> + <item row="1" column="0"> + <widget class="QLabel" name="label_2"> + <property name="text"> + <string>Genre</string> + </property> + </widget> + </item> + <item row="1" column="1"> + <widget class="QComboBox" name="opt_fb2_genre"/> + </item> </layout> </widget> <resources/> diff --git a/src/calibre/gui2/convert/font_key.ui b/src/calibre/gui2/convert/font_key.ui index 6c8ad2be01..07e0cb0f11 100644 --- a/src/calibre/gui2/convert/font_key.ui +++ b/src/calibre/gui2/convert/font_key.ui @@ -33,7 +33,7 @@ <property name="text"> <string><p>This wizard will help you choose an appropriate font size key for your needs. Just enter the base font size of the input document and then enter an input font size. The wizard will display what font size it will be mapped to, by the font rescaling algorithm. You can adjust the algorithm by adjusting the output base font size and font key below. When you find values suitable for you, click OK.</p> <p>By default, if the output base font size is zero and/or no font size key is specified, calibre will use the values from the current Output Profile. </p> -<p>See the <a href="http://calibre-ebook.com/user_manual/conversion.html#font-size-rescaling">User Manual</a> for a discussion of how font size rescaling works.</p></string> +<p>See the <a href="http://manual.calibre-ebook.com/conversion.html#font-size-rescaling">User Manual</a> for a discussion of how font size rescaling works.</p></string> </property> <property name="wordWrap"> <bool>true</bool> diff --git a/src/calibre/gui2/convert/heuristics.ui b/src/calibre/gui2/convert/heuristics.ui index 46d62061af..403321494a 100644 --- a/src/calibre/gui2/convert/heuristics.ui +++ b/src/calibre/gui2/convert/heuristics.ui @@ -17,7 +17,7 @@ <item> <widget class="QLabel" name="label"> <property name="text"> - <string><b>Heuristic processing</b> means that calibre will scan your book for common patterns and fix them. As the name implies, this involves guesswork, which means that it could end up worsening the result of a conversion, if calibre guesses wrong. Therefore, it is disabled by default. Often, if a conversion does not turn out as you expect, turning on heuristics can improve matters. Read more about the various heuristic processing options in the <a href="http://calibre-ebook.com/user_manual/conversion.html#heuristic-processing">User Manual</a>.</string> + <string><b>Heuristic processing</b> means that calibre will scan your book for common patterns and fix them. As the name implies, this involves guesswork, which means that it could end up worsening the result of a conversion, if calibre guesses wrong. Therefore, it is disabled by default. Often, if a conversion does not turn out as you expect, turning on heuristics can improve matters. Read more about the various heuristic processing options in the <a href="http://manual.calibre-ebook.com/conversion.html#heuristic-processing">User Manual</a>.</string> </property> <property name="wordWrap"> <bool>true</bool> diff --git a/src/calibre/gui2/convert/htmlz_output.py b/src/calibre/gui2/convert/htmlz_output.py new file mode 100644 index 0000000000..6e9f5bd68c --- /dev/null +++ b/src/calibre/gui2/convert/htmlz_output.py @@ -0,0 +1,26 @@ +# -*- coding: utf-8 -*- + +__license__ = 'GPL 3' +__copyright__ = '2009, John Schember <john@nachtimwald.com>' +__docformat__ = 'restructuredtext en' + +from calibre.gui2.convert.htmlz_output_ui import Ui_Form +from calibre.gui2.convert import Widget + +format_model = None + +class PluginWidget(Widget, Ui_Form): + + TITLE = _('HTMLZ Output') + HELP = _('Options specific to')+' HTMLZ '+_('output') + COMMIT_NAME = 'htmlz_output' + ICON = I('mimetypes/html.png') + + def __init__(self, parent, get_option, get_help, db=None, book_id=None): + Widget.__init__(self, parent, ['htmlz_css_type', 'htmlz_class_style']) + self.db, self.book_id = db, book_id + for x in get_option('htmlz_css_type').option.choices: + self.opt_htmlz_css_type.addItem(x) + for x in get_option('htmlz_class_style').option.choices: + self.opt_htmlz_class_style.addItem(x) + self.initialize_options(get_option, get_help, db, book_id) diff --git a/src/calibre/gui2/convert/htmlz_output.ui b/src/calibre/gui2/convert/htmlz_output.ui new file mode 100644 index 0000000000..7593300653 --- /dev/null +++ b/src/calibre/gui2/convert/htmlz_output.ui @@ -0,0 +1,61 @@ +<?xml version="1.0" encoding="UTF-8"?> +<ui version="4.0"> + <class>Form</class> + <widget class="QWidget" name="Form"> + <property name="geometry"> + <rect> + <x>0</x> + <y>0</y> + <width>438</width> + <height>300</height> + </rect> + </property> + <property name="windowTitle"> + <string>Form</string> + </property> + <layout class="QGridLayout" name="gridLayout"> + <item row="2" column="0"> + <spacer name="verticalSpacer"> + <property name="orientation"> + <enum>Qt::Vertical</enum> + </property> + <property name="sizeHint" stdset="0"> + <size> + <width>20</width> + <height>246</height> + </size> + </property> + </spacer> + </item> + <item row="0" column="0"> + <widget class="QLabel" name="label"> + <property name="text"> + <string>How to handle CSS</string> + </property> + <property name="buddy"> + <cstring>opt_htmlz_css_type</cstring> + </property> + </widget> + </item> + <item row="0" column="1"> + <widget class="QComboBox" name="opt_htmlz_css_type"> + <property name="minimumContentsLength"> + <number>20</number> + </property> + </widget> + </item> + <item row="1" column="0"> + <widget class="QLabel" name="label_2"> + <property name="text"> + <string>How to handle class based CSS</string> + </property> + </widget> + </item> + <item row="1" column="1"> + <widget class="QComboBox" name="opt_htmlz_class_style"/> + </item> + </layout> + </widget> + <resources/> + <connections/> +</ui> diff --git a/src/calibre/gui2/convert/metadata.py b/src/calibre/gui2/convert/metadata.py index 95dd7623c9..80311502e8 100644 --- a/src/calibre/gui2/convert/metadata.py +++ b/src/calibre/gui2/convert/metadata.py @@ -192,7 +192,7 @@ class MetadataWidget(Widget, Ui_Form): try: cf = open(_file, "rb") cover = cf.read() - except IOError, e: + except IOError as e: d = error_dialog(self.parent(), _('Error reading file'), _("<p>There was an error reading from file: <br /><b>") + _file + "</b></p><br />"+str(e)) d.exec_() diff --git a/src/calibre/gui2/convert/search_and_replace.py b/src/calibre/gui2/convert/search_and_replace.py index 88446344ec..407e7922e7 100644 --- a/src/calibre/gui2/convert/search_and_replace.py +++ b/src/calibre/gui2/convert/search_and_replace.py @@ -6,6 +6,8 @@ __docformat__ = 'restructuredtext en' import re +from PyQt4.Qt import QLineEdit, QTextEdit + from calibre.gui2.convert.search_and_replace_ui import Ui_Form from calibre.gui2.convert import Widget from calibre.gui2 import error_dialog @@ -67,8 +69,18 @@ class SearchAndReplaceWidget(Widget, Ui_Form): try: pat = unicode(x.regex) re.compile(pat) - except Exception, err: + except Exception as err: error_dialog(self, _('Invalid regular expression'), _('Invalid regular expression: %s')%err, show=True) return False return True + + def get_vaule(self, g): + if isinstance(g, (QLineEdit, QTextEdit)): + func = getattr(g, 'toPlainText', getattr(g, 'text', None))() + ans = unicode(func) + if not ans: + ans = None + return ans + else: + return Widget.get_value(self, g) diff --git a/src/calibre/gui2/convert/search_and_replace.ui b/src/calibre/gui2/convert/search_and_replace.ui index b7447f8feb..03a74b5ebd 100644 --- a/src/calibre/gui2/convert/search_and_replace.ui +++ b/src/calibre/gui2/convert/search_and_replace.ui @@ -188,7 +188,7 @@ <item row="0" column="0"> <widget class="QLabel" name="label"> <property name="text"> - <string><p>Search and replace uses <i>regular expressions</i>. See the <a href="http://calibre-ebook.com/user_manual/regexp.html">regular expressions tutorial</a> to get started with regular expressions. Also clicking the wizard buttons below will allow you to test your regular expression against the current input document.</string> + <string><p>Search and replace uses <i>regular expressions</i>. See the <a href="http://manual.calibre-ebook.com/regexp.html">regular expressions tutorial</a> to get started with regular expressions. Also clicking the wizard buttons below will allow you to test your regular expression against the current input document.</string> </property> <property name="wordWrap"> <bool>true</bool> diff --git a/src/calibre/gui2/convert/single.py b/src/calibre/gui2/convert/single.py index 59fcbb65ad..3575fb5ffb 100644 --- a/src/calibre/gui2/convert/single.py +++ b/src/calibre/gui2/convert/single.py @@ -6,7 +6,7 @@ __license__ = 'GPL v3' __copyright__ = '2009, Kovid Goyal <kovid@kovidgoyal.net>' __docformat__ = 'restructuredtext en' -import sys, cPickle, shutil +import sys, cPickle, shutil, importlib from PyQt4.Qt import QString, SIGNAL, QAbstractListModel, Qt, QVariant, QFont @@ -182,8 +182,8 @@ class Config(ResizableDialog, Ui_Dialog): output_widget = None name = self.plumber.output_plugin.name.lower().replace(' ', '_') try: - output_widget = __import__('calibre.gui2.convert.'+name, - fromlist=[1]) + output_widget = importlib.import_module( + 'calibre.gui2.convert.'+name) pw = output_widget.PluginWidget pw.ICON = I('back.png') pw.HELP = _('Options specific to the output format.') @@ -193,8 +193,8 @@ class Config(ResizableDialog, Ui_Dialog): input_widget = None name = self.plumber.input_plugin.name.lower().replace(' ', '_') try: - input_widget = __import__('calibre.gui2.convert.'+name, - fromlist=[1]) + input_widget = importlib.import_module( + 'calibre.gui2.convert.'+name) pw = input_widget.PluginWidget pw.ICON = I('forward.png') pw.HELP = _('Options specific to the input format.') diff --git a/src/calibre/gui2/convert/structure_detection.py b/src/calibre/gui2/convert/structure_detection.py index d8e2f4f122..b58c473bd4 100644 --- a/src/calibre/gui2/convert/structure_detection.py +++ b/src/calibre/gui2/convert/structure_detection.py @@ -21,7 +21,7 @@ class StructureDetectionWidget(Widget, Ui_Form): def __init__(self, parent, get_option, get_help, db=None, book_id=None): Widget.__init__(self, parent, ['chapter', 'chapter_mark', - 'remove_first_image', + 'remove_first_image', 'remove_fake_margins', 'insert_metadata', 'page_breaks_before'] ) self.db, self.book_id = db, book_id diff --git a/src/calibre/gui2/convert/structure_detection.ui b/src/calibre/gui2/convert/structure_detection.ui index f80e6f8182..4ba90c1c2c 100644 --- a/src/calibre/gui2/convert/structure_detection.ui +++ b/src/calibre/gui2/convert/structure_detection.ui @@ -48,10 +48,10 @@ </property> </widget> </item> - <item row="6" column="0" colspan="3"> + <item row="7" column="0" colspan="3"> <widget class="XPathEdit" name="opt_page_breaks_before" native="true"/> </item> - <item row="7" column="0" colspan="3"> + <item row="8" column="0" colspan="3"> <spacer name="verticalSpacer"> <property name="orientation"> <enum>Qt::Vertical</enum> @@ -77,7 +77,7 @@ </property> </spacer> </item> - <item row="4" column="0" colspan="3"> + <item row="5" column="0" colspan="3"> <widget class="QLabel" name="label_2"> <property name="text"> <string>The header and footer removal options have been replaced by the Search & Replace options. Click the Search & Replace category in the bar to the left to use these options. Leave the replace field blank and enter your header/footer removal regexps into the search field.</string> @@ -87,6 +87,13 @@ </property> </widget> </item> + <item row="2" column="2"> + <widget class="QCheckBox" name="opt_remove_fake_margins"> + <property name="text"> + <string>Remove &fake margins</string> + </property> + </widget> + </item> </layout> </widget> <customwidgets> diff --git a/src/calibre/gui2/convert/txt_output.py b/src/calibre/gui2/convert/txt_output.py index 8427f83824..816e8d7785 100644 --- a/src/calibre/gui2/convert/txt_output.py +++ b/src/calibre/gui2/convert/txt_output.py @@ -19,7 +19,7 @@ class PluginWidget(Widget, Ui_Form): Widget.__init__(self, parent, ['newline', 'max_line_length', 'force_max_line_length', 'inline_toc', 'txt_output_formatting', 'keep_links', 'keep_image_references', - 'txt_output_encoding']) + 'keep_color', 'txt_output_encoding']) self.db, self.book_id = db, book_id for x in get_option('newline').option.choices: self.opt_newline.addItem(x) diff --git a/src/calibre/gui2/convert/txt_output.ui b/src/calibre/gui2/convert/txt_output.ui index 1ef9e6e6b9..3a62643551 100644 --- a/src/calibre/gui2/convert/txt_output.ui +++ b/src/calibre/gui2/convert/txt_output.ui @@ -122,6 +122,13 @@ </property> </widget> </item> + <item> + <widget class="QCheckBox" name="opt_keep_color"> + <property name="text"> + <string>Keep text color, when possible</string> + </property> + </widget> + </item> </layout> </widget> </item> diff --git a/src/calibre/gui2/convert/xexp_edit.ui b/src/calibre/gui2/convert/xexp_edit.ui index 18b7c39b52..68c0c8c98e 100644 --- a/src/calibre/gui2/convert/xexp_edit.ui +++ b/src/calibre/gui2/convert/xexp_edit.ui @@ -43,6 +43,9 @@ <height>0</height> </size> </property> + <property name="sizeAdjustPolicy"> + <enum>QComboBox::AdjustToMinimumContentsLengthWithIcon</enum> + </property> <property name="minimumContentsLength"> <number>30</number> </property> diff --git a/src/calibre/gui2/convert/xpath_wizard.ui b/src/calibre/gui2/convert/xpath_wizard.ui index 52985653fe..5e112e1fcd 100644 --- a/src/calibre/gui2/convert/xpath_wizard.ui +++ b/src/calibre/gui2/convert/xpath_wizard.ui @@ -127,7 +127,7 @@ <item> <widget class="QLabel" name="label_5"> <property name="text"> - <string><p>For example, to match all h2 tags that have class="chapter", set tag to <i>h2</i>, attribute to <i>class</i> and value to <i>chapter</i>.</p><p>Leaving attribute blank will match any attribute and leaving value blank will match any value. Setting tag to * will match any tag.</p><p>To learn more advanced usage of XPath see the <a href="http://calibre-ebook.com/user_manual/xpath.html">XPath Tutorial</a>.</string> + <string><p>For example, to match all h2 tags that have class="chapter", set tag to <i>h2</i>, attribute to <i>class</i> and value to <i>chapter</i>.</p><p>Leaving attribute blank will match any attribute and leaving value blank will match any value. Setting tag to * will match any tag.</p><p>To learn more advanced usage of XPath see the <a href="http://manual.calibre-ebook.com/xpath.html">XPath Tutorial</a>.</string> </property> <property name="wordWrap"> <bool>true</bool> diff --git a/src/calibre/gui2/cover_flow.py b/src/calibre/gui2/cover_flow.py index cb951b09be..1d79d93bb2 100644 --- a/src/calibre/gui2/cover_flow.py +++ b/src/calibre/gui2/cover_flow.py @@ -53,7 +53,7 @@ if pictureflow is not None: def __init__(self, model, buffer=20): pictureflow.FlowImages.__init__(self) self.model = model - self.model.modelReset.connect(self.reset) + self.model.modelReset.connect(self.reset, type=Qt.QueuedConnection) def count(self): return self.model.count() @@ -83,6 +83,8 @@ if pictureflow is not None: class CoverFlow(pictureflow.PictureFlow): + dc_signal = pyqtSignal() + def __init__(self, parent=None): pictureflow.PictureFlow.__init__(self, parent, config['cover_flow_queue_length']+1) @@ -90,6 +92,8 @@ if pictureflow is not None: self.setFocusPolicy(Qt.WheelFocus) self.setSizePolicy(QSizePolicy(QSizePolicy.Expanding, QSizePolicy.Expanding)) + self.dc_signal.connect(self._data_changed, + type=Qt.QueuedConnection) def sizeHint(self): return self.minimumSize() @@ -101,6 +105,12 @@ if pictureflow is not None: elif ev.delta() > 0: self.showPrevious() + def dataChanged(self): + self.dc_signal.emit() + + def _data_changed(self): + pictureflow.PictureFlow.dataChanged(self) + else: CoverFlow = None @@ -135,8 +145,7 @@ class CoverFlowMixin(object): self.cover_flow = None if CoverFlow is not None: self.cf_last_updated_at = None - self.cover_flow_sync_timer = QTimer(self) - self.cover_flow_sync_timer.timeout.connect(self.cover_flow_do_sync) + self.cover_flow_syncing_enabled = False self.cover_flow_sync_flag = True self.cover_flow = CoverFlow(parent=self) self.cover_flow.currentChanged.connect(self.sync_listview_to_cf) @@ -179,14 +188,15 @@ class CoverFlowMixin(object): self.cover_flow.setFocus(Qt.OtherFocusReason) if CoverFlow is not None: self.cover_flow.setCurrentSlide(self.library_view.currentIndex().row()) - self.cover_flow_sync_timer.start(500) + self.cover_flow_syncing_enabled = True + QTimer.singleShot(500, self.cover_flow_do_sync) self.library_view.setCurrentIndex( self.library_view.currentIndex()) self.library_view.scroll_to_row(self.library_view.currentIndex().row()) def cover_browser_hidden(self): if CoverFlow is not None: - self.cover_flow_sync_timer.stop() + self.cover_flow_syncing_enabled = False idx = self.library_view.model().index(self.cover_flow.currentSlide(), 0) if idx.isValid(): sm = self.library_view.selectionModel() @@ -242,6 +252,8 @@ class CoverFlowMixin(object): except: import traceback traceback.print_exc() + if self.cover_flow_syncing_enabled: + QTimer.singleShot(500, self.cover_flow_do_sync) def sync_listview_to_cf(self, row): self.cf_last_updated_at = time.time() diff --git a/src/calibre/gui2/custom_column_widgets.py b/src/calibre/gui2/custom_column_widgets.py index fa7ba3c56d..c94913ea2c 100644 --- a/src/calibre/gui2/custom_column_widgets.py +++ b/src/calibre/gui2/custom_column_widgets.py @@ -5,7 +5,6 @@ __license__ = 'GPL v3' __copyright__ = '2010, Kovid Goyal <kovid@kovidgoyal.net>' __docformat__ = 'restructuredtext en' -import sys from functools import partial from PyQt4.Qt import QComboBox, QLabel, QSpinBox, QDoubleSpinBox, QDateEdit, \ @@ -63,7 +62,7 @@ class Bool(Base): w = self.widgets[1] items = [_('Yes'), _('No'), _('Undefined')] icons = [I('ok.png'), I('list_remove.png'), I('blank.png')] - if tweaks['bool_custom_columns_are_tristate'] == 'no': + if not self.db.prefs.get('bools_are_tristate'): items = items[:-1] icons = icons[:-1] for icon, text in zip(icons, items): @@ -71,7 +70,7 @@ class Bool(Base): def setter(self, val): val = {None: 2, False: 1, True: 0}[val] - if tweaks['bool_custom_columns_are_tristate'] == 'no' and val == 2: + if not self.db.prefs.get('bools_are_tristate') and val == 2: val = 1 self.widgets[1].setCurrentIndex(val) @@ -85,7 +84,7 @@ class Int(Base): self.widgets = [QLabel('&'+self.col_metadata['name']+':', parent), QSpinBox(parent)] w = self.widgets[1] - w.setRange(-100, sys.maxint) + w.setRange(-100, 100000000) w.setSpecialValueText(_('Undefined')) w.setSingleStep(1) @@ -108,7 +107,7 @@ class Float(Int): self.widgets = [QLabel('&'+self.col_metadata['name']+':', parent), QDoubleSpinBox(parent)] w = self.widgets[1] - w.setRange(-100., float(sys.maxint)) + w.setRange(-100., float(100000000)) w.setDecimals(2) w.setSpecialValueText(_('Undefined')) w.setSingleStep(1) @@ -227,10 +226,18 @@ class Comments(Base): class Text(Base): def setup_ui(self, parent): + if self.col_metadata['display'].get('is_names', False): + self.sep = u' & ' + else: + self.sep = u', ' values = self.all_values = list(self.db.all_custom(num=self.col_id)) values.sort(key=sort_key) if self.col_metadata['is_multiple']: w = MultiCompleteLineEdit(parent) + w.set_separator(self.sep.strip()) + if self.sep == u' & ': + w.set_space_before_sep(True) + w.set_add_separator(tweaks['authors_completer_append_separator']) w.update_items_cache(values) w.setSizePolicy(QSizePolicy.Minimum, QSizePolicy.Preferred) else: @@ -262,12 +269,12 @@ class Text(Base): if self.col_metadata['is_multiple']: if not val: val = [] - self.widgets[1].setText(u', '.join(val)) + self.widgets[1].setText(self.sep.join(val)) def getter(self): if self.col_metadata['is_multiple']: val = unicode(self.widgets[1].text()).strip() - ans = [x.strip() for x in val.split(',') if x.strip()] + ans = [x.strip() for x in val.split(self.sep.strip()) if x.strip()] if not ans: ans = None return ans @@ -282,6 +289,7 @@ class Series(Base): values = self.all_values = list(self.db.all_custom(num=self.col_id)) values.sort(key=sort_key) w = MultiCompleteComboBox(parent) + w.set_separator(None) w.setSizeAdjustPolicy(w.AdjustToMinimumContentsLengthWithIcon) w.setMinimumContentsLength(25) self.name_widget = w @@ -289,7 +297,7 @@ class Series(Base): self.widgets.append(QLabel('&'+self.col_metadata['name']+_(' index:'), parent)) w = QDoubleSpinBox(parent) - w.setRange(-100., float(sys.maxint)) + w.setRange(-100., float(100000000)) w.setDecimals(2) w.setSpecialValueText(_('Undefined')) w.setSingleStep(1) @@ -431,6 +439,7 @@ def populate_metadata_page(layout, db, book_id, bulk=False, two_column=False, pa w = widget_factory(dt, col) ans.append(w) for c in range(0, len(w.widgets), 2): + w.widgets[c].setWordWrap(True) w.widgets[c].setBuddy(w.widgets[c+1]) layout.addWidget(w.widgets[c], row, column) layout.addWidget(w.widgets[c+1], row, column+1) @@ -542,7 +551,7 @@ class BulkBool(BulkBase, Bool): value = None for book_id in book_ids: val = self.db.get_custom(book_id, num=self.col_id, index_is_id=True) - if tweaks['bool_custom_columns_are_tristate'] == 'no' and val is None: + if not self.db.prefs.get('bools_are_tristate') and val is None: val = False if value is not None and value != val: return None @@ -552,7 +561,7 @@ class BulkBool(BulkBase, Bool): def setup_ui(self, parent): self.make_widgets(parent, QComboBox) items = [_('Yes'), _('No')] - if tweaks['bool_custom_columns_are_tristate'] == 'no': + if not self.db.prefs.get('bools_are_tristate'): items.append('') else: items.append(_('Undefined')) @@ -564,7 +573,7 @@ class BulkBool(BulkBase, Bool): def getter(self): val = self.main_widget.currentIndex() - if tweaks['bool_custom_columns_are_tristate'] == 'no': + if not self.db.prefs.get('bools_are_tristate'): return {2: False, 1: False, 0: True}[val] else: return {2: None, 1: False, 0: True}[val] @@ -579,13 +588,13 @@ class BulkBool(BulkBase, Bool): return val = self.gui_val val = self.normalize_ui_val(val) - if tweaks['bool_custom_columns_are_tristate'] == 'no' and val is None: + if not self.db.prefs.get('bools_are_tristate') and val is None: val = False self.db.set_custom_bulk(book_ids, val, num=self.col_id, notify=notify) def a_c_checkbox_changed(self): if not self.ignore_change_signals: - if tweaks['bool_custom_columns_are_tristate'] == 'no' and \ + if not self.db.prefs.get('bools_are_tristate') and \ self.main_widget.currentIndex() == 2: self.a_c_checkbox.setChecked(False) else: @@ -595,7 +604,7 @@ class BulkInt(BulkBase): def setup_ui(self, parent): self.make_widgets(parent, QSpinBox) - self.main_widget.setRange(-100, sys.maxint) + self.main_widget.setRange(-100, 100000000) self.main_widget.setSpecialValueText(_('Undefined')) self.main_widget.setSingleStep(1) @@ -617,7 +626,7 @@ class BulkFloat(BulkInt): def setup_ui(self, parent): self.make_widgets(parent, QDoubleSpinBox) - self.main_widget.setRange(-100., float(sys.maxint)) + self.main_widget.setRange(-100., float(100000000)) self.main_widget.setDecimals(2) self.main_widget.setSpecialValueText(_('Undefined')) self.main_widget.setSingleStep(1) @@ -795,6 +804,7 @@ class BulkEnumeration(BulkBase, Enumeration): return value def setup_ui(self, parent): + self.parent = parent self.make_widgets(parent, QComboBox) vals = self.col_metadata['display']['enum_values'] self.main_widget.blockSignals(True) @@ -847,13 +857,20 @@ class BulkText(BulkBase): self.main_widget.setSizePolicy(QSizePolicy.Minimum, QSizePolicy.Preferred) self.adding_widget = self.main_widget - w = RemoveTags(parent, values) - self.widgets.append(QLabel('&'+self.col_metadata['name']+': ' + - _('tags to remove'), parent)) - self.widgets.append(w) - self.removing_widget = w - w.tags_box.textChanged.connect(self.a_c_checkbox_changed) - w.checkbox.stateChanged.connect(self.a_c_checkbox_changed) + if not self.col_metadata['display'].get('is_names', False): + w = RemoveTags(parent, values) + self.widgets.append(QLabel('&'+self.col_metadata['name']+': ' + + _('tags to remove'), parent)) + self.widgets.append(w) + self.removing_widget = w + self.main_widget.set_separator(',') + w.tags_box.textChanged.connect(self.a_c_checkbox_changed) + w.checkbox.stateChanged.connect(self.a_c_checkbox_changed) + else: + self.main_widget.set_separator('&') + self.main_widget.set_space_before_sep(True) + self.main_widget.set_add_separator( + tweaks['authors_completer_append_separator']) else: self.make_widgets(parent, MultiCompleteComboBox) self.main_widget.set_separator(None) @@ -882,21 +899,26 @@ class BulkText(BulkBase): if not self.a_c_checkbox.isChecked(): return if self.col_metadata['is_multiple']: - remove_all, adding, rtext = self.gui_val - remove = set() - if remove_all: - remove = set(self.db.all_custom(num=self.col_id)) + if self.col_metadata['display'].get('is_names', False): + val = self.gui_val + add = [v.strip() for v in val.split('&') if v.strip()] + self.db.set_custom_bulk(book_ids, add, num=self.col_id) else: - txt = rtext + remove_all, adding, rtext = self.gui_val + remove = set() + if remove_all: + remove = set(self.db.all_custom(num=self.col_id)) + else: + txt = rtext + if txt: + remove = set([v.strip() for v in txt.split(',')]) + txt = adding if txt: - remove = set([v.strip() for v in txt.split(',')]) - txt = adding - if txt: - add = set([v.strip() for v in txt.split(',')]) - else: - add = set() - self.db.set_custom_bulk_multiple(book_ids, add=add, remove=remove, - num=self.col_id) + add = set([v.strip() for v in txt.split(',')]) + else: + add = set() + self.db.set_custom_bulk_multiple(book_ids, add=add, + remove=remove, num=self.col_id) else: val = self.gui_val val = self.normalize_ui_val(val) @@ -905,10 +927,11 @@ class BulkText(BulkBase): def getter(self): if self.col_metadata['is_multiple']: - return self.removing_widget.checkbox.isChecked(), \ - unicode(self.adding_widget.text()), \ - unicode(self.removing_widget.tags_box.text()) - + if not self.col_metadata['display'].get('is_names', False): + return self.removing_widget.checkbox.isChecked(), \ + unicode(self.adding_widget.text()), \ + unicode(self.removing_widget.tags_box.text()) + return unicode(self.adding_widget.text()) val = unicode(self.main_widget.currentText()).strip() if not val: val = None diff --git a/src/calibre/gui2/device.py b/src/calibre/gui2/device.py index e4096f5761..dd9d7aaa50 100644 --- a/src/calibre/gui2/device.py +++ b/src/calibre/gui2/device.py @@ -7,7 +7,7 @@ import os, traceback, Queue, time, cStringIO, re, sys from threading import Thread from PyQt4.Qt import QMenu, QAction, QActionGroup, QIcon, SIGNAL, \ - Qt, pyqtSignal, QDialog + Qt, pyqtSignal, QDialog, QObject from calibre.customize.ui import available_input_formats, available_output_formats, \ device_plugins @@ -25,12 +25,10 @@ from calibre.devices.errors import FreeSpaceError from calibre.devices.apple.driver import ITUNES_ASYNC from calibre.devices.folder_device.driver import FOLDER_DEVICE from calibre.devices.bambook.driver import BAMBOOK, BAMBOOKWifi -from calibre.ebooks.metadata.meta import set_metadata from calibre.constants import DEBUG from calibre.utils.config import prefs, tweaks from calibre.utils.magick.draw import thumbnail -from calibre.library.save_to_disk import plugboard_any_device_value, \ - plugboard_any_format_value +from calibre.library.save_to_disk import find_plugboard # }}} class DeviceJob(BaseJob): # {{{ @@ -64,7 +62,7 @@ class DeviceJob(BaseJob): # {{{ self.result = self.func(*self.args, **self.kwargs) if self._aborted: return - except (Exception, SystemExit), err: + except (Exception, SystemExit) as err: if self._aborted: return self.failed = True @@ -93,23 +91,6 @@ class DeviceJob(BaseJob): # {{{ # }}} -def find_plugboard(device_name, format, plugboards): - cpb = None - if format in plugboards: - cpb = plugboards[format] - elif plugboard_any_format_value in plugboards: - cpb = plugboards[plugboard_any_format_value] - if cpb is not None: - if device_name in cpb: - cpb = cpb[device_name] - elif plugboard_any_device_value in cpb: - cpb = cpb[plugboard_any_device_value] - else: - cpb = None - if DEBUG: - prints('Device using plugboard', format, device_name, cpb) - return cpb - def device_name_for_plugboards(device_class): if hasattr(device_class, 'DEVICE_PLUGBOARD_NAME'): return device_class.DEVICE_PLUGBOARD_NAME @@ -140,6 +121,8 @@ class DeviceManager(Thread): # {{{ self.mount_connection_requests = Queue.Queue(0) self.open_feedback_slot = open_feedback_slot self.open_feedback_msg = open_feedback_msg + self._device_information = None + self.current_library_uuid = None def report_progress(self, *args): pass @@ -159,10 +142,10 @@ class DeviceManager(Thread): # {{{ try: dev.reset(detected_device=detected_device, report_progress=self.report_progress) - dev.open() - except OpenFeedback, e: + dev.open(self.current_library_uuid) + except OpenFeedback as e: if dev not in self.ejected_devices: - self.open_feedback_msg(dev.get_gui_name(), e.feedback_msg) + self.open_feedback_msg(dev.get_gui_name(), e) self.ejected_devices.add(dev) continue except: @@ -194,6 +177,7 @@ class DeviceManager(Thread): # {{{ else: self.connected_slot(False, self.connected_device_kind) self.connected_device = None + self._device_information = None def detect_device(self): self.scanner.scan() @@ -292,9 +276,13 @@ class DeviceManager(Thread): # {{{ def _get_device_information(self): info = self.device.get_device_information(end_session=False) - info = [i.replace('\x00', '').replace('\x01', '') for i in info] + if len(info) < 5: + info = tuple(list(info) + [{}]) + info = [i.replace('\x00', '').replace('\x01', '') if isinstance(i, basestring) else i + for i in info] cp = self.device.card_prefix(end_session=False) fs = self.device.free_space() + self._device_information = {'info': info, 'prefixes': cp, 'freespace': fs} return info, cp, fs def get_device_information(self, done): @@ -302,6 +290,9 @@ class DeviceManager(Thread): # {{{ return self.create_job(self._get_device_information, done, description=_('Get device information')) + def get_current_device_information(self): + return self._device_information + def _books(self): '''Get metadata from device''' mainlist = self.device.books(oncard=None, end_session=False) @@ -342,6 +333,7 @@ class DeviceManager(Thread): # {{{ def _upload_books(self, files, names, on_card=None, metadata=None, plugboards=None): '''Upload books to device: ''' + from calibre.ebooks.metadata.meta import set_metadata if hasattr(self.connected_device, 'set_plugboards') and \ callable(self.connected_device.set_plugboards): self.connected_device.set_plugboards(plugboards, find_plugboard) @@ -417,6 +409,13 @@ class DeviceManager(Thread): # {{{ return self.create_job(self._view_book, done, args=[path, target], description=_('View book on device')) + def set_current_library_uuid(self, uuid): + self.current_library_uuid = uuid + + def set_driveinfo_name(self, location_code, name): + if self.connected_device: + self.connected_device.set_driveinfo_name(location_code, name) + # }}} class DeviceAction(QAction): # {{{ @@ -588,6 +587,24 @@ class DeviceMenu(QMenu): # {{{ # }}} +class DeviceSignals(QObject): + #: This signal is emitted once, after metadata is downloaded from the + #: connected device. + #: The sequence: gui.device_manager.is_device_connected will become True, + #: and the device_connection_changed signal will be emitted, + #: then sometime later gui.device_metadata_available will be signaled. + #: This does not mean that there are no more jobs running. Automatic metadata + #: management might have kicked off a sync_booklists to write new metadata onto + #: the device, and that job might still be running when the signal is emitted. + device_metadata_available = pyqtSignal() + + #: This signal is emitted once when the device is detected and once when + #: it is disconnected. If the parameter is True, then it is a connection, + #: otherwise a disconnection. + device_connection_changed = pyqtSignal(object) + +device_signals = DeviceSignals() + class DeviceMixin(object): # {{{ def __init__(self): @@ -601,8 +618,11 @@ class DeviceMixin(object): # {{{ if tweaks['auto_connect_to_folder']: self.connect_to_folder_named(tweaks['auto_connect_to_folder']) - def show_open_feedback(self, devname, msg): - self.__of_dev_mem__ = d = info_dialog(self, devname, msg) + def show_open_feedback(self, devname, e): + try: + self.__of_dev_mem__ = d = e.custom_dialog(self) + except NotImplementedError: + self.__of_dev_mem__ = d = info_dialog(self, devname, e.feedback_msg) d.show() def auto_convert_question(self, msg, autos): @@ -731,8 +751,10 @@ class DeviceMixin(object): # {{{ if self.current_view() != self.library_view: self.book_details.reset_info() self.location_manager.update_devices() + self.bars_manager.update_bars() self.library_view.set_device_connected(self.device_connected) self.refresh_ondevice() + device_signals.device_connection_changed.emit(connected) def info_read(self, job): ''' @@ -743,6 +765,7 @@ class DeviceMixin(object): # {{{ info, cp, fs = job.result self.location_manager.update_devices(cp, fs, self.device_manager.device.icon) + self.bars_manager.update_bars() self.status_bar.device_connected(info[0]) self.device_manager.books(Dispatcher(self.metadata_downloaded)) @@ -771,6 +794,7 @@ class DeviceMixin(object): # {{{ self.sync_news() self.sync_catalogs() self.refresh_ondevice() + device_signals.device_metadata_available.emit() def refresh_ondevice(self, reset_only = False): ''' @@ -867,9 +891,14 @@ class DeviceMixin(object): # {{{ on_card = dest self.sync_to_device(on_card, delete, fmt) elif dest == 'mail': - to, fmts = sub_dest.split(';') + sub_dest_parts = sub_dest.split(';') + while len(sub_dest_parts) < 3: + sub_dest_parts.append('') + to = sub_dest_parts[0] + fmts = sub_dest_parts[1] + subject = ';'.join(sub_dest_parts[2:]) fmts = [x.strip().lower() for x in fmts.split(',')] - self.send_by_mail(to, fmts, delete) + self.send_by_mail(to, fmts, delete, subject=subject) def cover_to_thumbnail(self, data): if self.device_manager.device and \ @@ -1035,11 +1064,13 @@ class DeviceMixin(object): # {{{ except: pass total_size = self.location_manager.free[0] - if self.location_manager.free[0] > total_size + (1024**2): + loc = tweaks['send_news_to_device_location'] + loc_index = {"carda": 1, "cardb": 2}.get(loc, 0) + if self.location_manager.free[loc_index] > total_size + (1024**2): # Send news to main memory if enough space available # as some devices like the Nook Color cannot handle # periodicals on SD cards properly - on_card = None + on_card = loc if loc in ('carda', 'cardb') else None self.upload_books(files, names, metadata, on_card=on_card, memory=[files, remove]) @@ -1143,6 +1174,14 @@ class DeviceMixin(object): # {{{ ), bad) d.exec_() + def upload_dirtied_booklists(self): + ''' + Upload metadata to device. + ''' + plugboards = self.library_view.model().db.prefs.get('plugboards', {}) + self.device_manager.sync_booklists(Dispatcher(lambda x: x), + self.booklists(), plugboards) + def upload_booklists(self): ''' Upload metadata to device. @@ -1255,7 +1294,8 @@ class DeviceMixin(object): # {{{ self.book_db_uuid_path_map = None return - if not hasattr(self, 'db_book_uuid_cache'): + if not self.device_manager.is_device_connected or \ + not hasattr(self, 'db_book_uuid_cache'): return loc if self.book_db_id_cache is None: diff --git a/src/calibre/gui2/device_drivers/configwidget.py b/src/calibre/gui2/device_drivers/configwidget.py index 97c492b550..e55a0c6dda 100644 --- a/src/calibre/gui2/device_drivers/configwidget.py +++ b/src/calibre/gui2/device_drivers/configwidget.py @@ -62,8 +62,18 @@ class ConfigWidget(QWidget, Ui_ConfigWidget): if isinstance(extra_customization_message, list): self.opt_extra_customization = [] + if len(extra_customization_message) > 6: + row_func = lambda x, y: ((x/2) * 2) + y + col_func = lambda x: x%2 + else: + row_func = lambda x, y: x*2 + y + col_func = lambda x: 0 + for i, m in enumerate(extra_customization_message): label_text, tt = parse_msg(m) + if not label_text: + self.opt_extra_customization.append(None) + continue if isinstance(settings.extra_customization[i], bool): self.opt_extra_customization.append(QCheckBox(label_text)) self.opt_extra_customization[-1].setToolTip(tt) @@ -75,8 +85,9 @@ class ConfigWidget(QWidget, Ui_ConfigWidget): l.setBuddy(self.opt_extra_customization[i]) l.setWordWrap(True) self.opt_extra_customization[i].setText(settings.extra_customization[i]) - self.extra_layout.addWidget(l) - self.extra_layout.addWidget(self.opt_extra_customization[i]) + self.extra_layout.addWidget(l, row_func(i, 0), col_func(i)) + self.extra_layout.addWidget(self.opt_extra_customization[i], + row_func(i, 1), col_func(i)) else: self.opt_extra_customization = QLineEdit() label_text, tt = parse_msg(extra_customization_message) @@ -86,8 +97,8 @@ class ConfigWidget(QWidget, Ui_ConfigWidget): l.setWordWrap(True) if settings.extra_customization: self.opt_extra_customization.setText(settings.extra_customization) - self.extra_layout.addWidget(l) - self.extra_layout.addWidget(self.opt_extra_customization) + self.extra_layout.addWidget(l, 0, 0) + self.extra_layout.addWidget(self.opt_extra_customization, 1, 0) self.opt_save_template.setText(settings.save_template) @@ -133,7 +144,7 @@ class ConfigWidget(QWidget, Ui_ConfigWidget): try: validation_formatter.validate(tmpl) return True - except Exception, err: + except Exception as err: error_dialog(self, _('Invalid template'), '<p>'+_('The template %s is invalid:')%tmpl + \ '<br>'+unicode(err), show=True) diff --git a/src/calibre/gui2/device_drivers/configwidget.ui b/src/calibre/gui2/device_drivers/configwidget.ui index 619d7052e8..92324fd1a7 100644 --- a/src/calibre/gui2/device_drivers/configwidget.ui +++ b/src/calibre/gui2/device_drivers/configwidget.ui @@ -101,7 +101,7 @@ </widget> </item> <item row="6" column="0"> - <layout class="QVBoxLayout" name="extra_layout"/> + <layout class="QGridLayout" name="extra_layout"/> </item> <item row="4" column="0"> <widget class="QLabel" name="label"> diff --git a/src/calibre/gui2/dialogs/book_info.py b/src/calibre/gui2/dialogs/book_info.py index 4da897920c..4036e71a38 100644 --- a/src/calibre/gui2/dialogs/book_info.py +++ b/src/calibre/gui2/dialogs/book_info.py @@ -3,28 +3,33 @@ __license__ = 'GPL v3' __copyright__ = '2008, Kovid Goyal kovid@kovidgoyal.net' __docformat__ = 'restructuredtext en' -import textwrap, os, re -from PyQt4.Qt import QCoreApplication, SIGNAL, QModelIndex, QTimer, Qt, \ - QDialog, QPixmap, QGraphicsScene, QIcon, QSize +from PyQt4.Qt import (QCoreApplication, SIGNAL, QModelIndex, QTimer, Qt, + QDialog, QPixmap, QIcon, QSize, QPalette) from calibre.gui2.dialogs.book_info_ui import Ui_BookInfo -from calibre.gui2 import dynamic, open_local_file, open_url +from calibre.gui2 import dynamic from calibre import fit_image -from calibre.library.comments import comments_to_html -from calibre.utils.icu import sort_key +from calibre.gui2.book_details import render_html class BookInfo(QDialog, Ui_BookInfo): - def __init__(self, parent, view, row, view_func): + def __init__(self, parent, view, row, link_delegate): QDialog.__init__(self, parent) Ui_BookInfo.__init__(self) self.setupUi(self) + self.gui = parent self.cover_pixmap = None - self.comments.sizeHint = self.comments_size_hint - self.comments.page().setLinkDelegationPolicy(self.comments.page().DelegateAllLinks) - self.comments.linkClicked.connect(self.link_clicked) - self.view_func = view_func + self.details.sizeHint = self.details_size_hint + self.details.page().setLinkDelegationPolicy(self.details.page().DelegateAllLinks) + self.details.linkClicked.connect(self.link_clicked) + self.css = P('templates/book_details.css', data=True).decode('utf-8') + self.link_delegate = link_delegate + self.details.setAttribute(Qt.WA_OpaquePaintEvent, False) + palette = self.details.palette() + self.details.setAcceptDrops(False) + palette.setBrush(QPalette.Base, Qt.transparent) + self.details.page().setPalette(palette) self.view = view @@ -35,19 +40,34 @@ class BookInfo(QDialog, Ui_BookInfo): self.connect(self.view.selectionModel(), SIGNAL('currentChanged(QModelIndex,QModelIndex)'), self.slave) self.connect(self.next_button, SIGNAL('clicked()'), self.next) self.connect(self.previous_button, SIGNAL('clicked()'), self.previous) - self.connect(self.text, SIGNAL('linkActivated(QString)'), self.open_book_path) self.fit_cover.stateChanged.connect(self.toggle_cover_fit) self.cover.resizeEvent = self.cover_view_resized + self.cover.cover_changed.connect(self.cover_changed) desktop = QCoreApplication.instance().desktop() screen_height = desktop.availableGeometry().height() - 100 self.resize(self.size().width(), screen_height) - def link_clicked(self, url): - open_url(url) + def link_clicked(self, qurl): + link = unicode(qurl.toString()) + self.link_delegate(link) - def comments_size_hint(self): - return QSize(350, 250) + def cover_changed(self, data): + if self.current_row is not None: + id_ = self.view.model().id(self.current_row) + self.view.model().db.set_cover(id_, data) + if self.gui.cover_flow: + self.gui.cover_flow.dataChanged() + ci = self.view.currentIndex() + if ci.isValid(): + self.view.model().current_changed(ci, ci) + self.cover_pixmap = QPixmap() + self.cover_pixmap.loadFromData(data) + if self.fit_cover.isChecked(): + self.resize_cover() + + def details_size_hint(self): + return QSize(350, 550) def toggle_cover_fit(self, state): dynamic.set('book_info_dialog_fit_cover', self.fit_cover.isChecked()) @@ -60,13 +80,6 @@ class BookInfo(QDialog, Ui_BookInfo): row = current.row() self.refresh(row) - def open_book_path(self, path): - path = unicode(path) - if os.sep in path: - open_local_file(path) - else: - self.view_func(self.view.model().id(self.current_row), path) - def next(self): row = self.view.currentIndex().row() ni = self.view.model().index(row+1, 0) @@ -83,7 +96,6 @@ class BookInfo(QDialog, Ui_BookInfo): if self.cover_pixmap is None: return self.setWindowIcon(QIcon(self.cover_pixmap)) - self.scene = QGraphicsScene() pixmap = self.cover_pixmap if self.fit_cover.isChecked(): scaled, new_width, new_height = fit_image(pixmap.width(), @@ -92,16 +104,17 @@ class BookInfo(QDialog, Ui_BookInfo): if scaled: pixmap = pixmap.scaled(new_width, new_height, Qt.KeepAspectRatio, Qt.SmoothTransformation) - self.scene.addPixmap(pixmap) - self.cover.setScene(self.scene) + self.cover.set_pixmap(pixmap) + sz = pixmap.size() + self.cover.setToolTip(_('Cover size: %dx%d')%(sz.width(), sz.height())) def refresh(self, row): if isinstance(row, QModelIndex): row = row.row() if row == self.current_row: return - info = self.view.model().get_book_info(row) - if info is None: + mi = self.view.model().get_book_display_info(row) + if mi is None: # Indicates books was deleted from library, or row numbers have # changed return @@ -109,40 +122,11 @@ class BookInfo(QDialog, Ui_BookInfo): self.previous_button.setEnabled(False if row == 0 else True) self.next_button.setEnabled(False if row == self.view.model().rowCount(QModelIndex())-1 else True) self.current_row = row - self.setWindowTitle(info[_('Title')]) - self.title.setText('<b>'+info.pop(_('Title'))) - comments = info.pop(_('Comments'), '') - if comments: - comments = comments_to_html(comments) - if re.search(r'<[a-zA-Z]+>', comments) is None: - lines = comments.splitlines() - lines = [x if x.strip() else '<br><br>' for x in lines] - comments = '\n'.join(lines) - self.comments.setHtml('<div>%s</div>' % comments) - self.comments.page().setLinkDelegationPolicy(self.comments.page().DelegateAllLinks) - cdata = info.pop('cover', '') - self.cover_pixmap = QPixmap.fromImage(cdata) + self.setWindowTitle(mi.title) + self.title.setText('<b>'+mi.title) + mi.title = _('Unknown') + self.cover_pixmap = QPixmap.fromImage(mi.cover_data[1]) self.resize_cover() + html = render_html(mi, self.css, True, self, all_fields=True) + self.details.setHtml(html) - rows = u'' - self.text.setText('') - self.data = info - if _('Path') in info.keys(): - p = info[_('Path')] - info[_('Path')] = '<a href="%s">%s</a>'%(p, p) - if _('Formats') in info.keys(): - formats = info[_('Formats')].split(',') - info[_('Formats')] = '' - for f in formats: - f = f.strip() - info[_('Formats')] += '<a href="%s">%s</a>, '%(f,f) - for key in sorted(info.keys(), key=sort_key): - if key == 'id': continue - txt = info[key] - if key.endswith(':html'): - key = key[:-5] - txt = comments_to_html(txt) - if key != _('Path'): - txt = u'<br />\n'.join(textwrap.wrap(txt, 120)) - rows += u'<tr><td><b>%s:</b></td><td>%s</td></tr>'%(key, txt) - self.text.setText(u'<table>'+rows+'</table>') diff --git a/src/calibre/gui2/dialogs/book_info.ui b/src/calibre/gui2/dialogs/book_info.ui index 2902a2c917..44fd1adf22 100644 --- a/src/calibre/gui2/dialogs/book_info.ui +++ b/src/calibre/gui2/dialogs/book_info.ui @@ -7,15 +7,31 @@ <x>0</x> <y>0</y> <width>917</width> - <height>480</height> + <height>492</height> </rect> </property> <property name="windowTitle"> <string>Dialog</string> </property> + <property name="windowIcon"> + <iconset resource="../../../../resources/images.qrc"> + <normaloff>:/images/metadata.png</normaloff>:/images/metadata.png</iconset> + </property> <layout class="QGridLayout" name="gridLayout"> <item row="0" column="0" colspan="2"> <widget class="QLabel" name="title"> + <property name="sizePolicy"> + <sizepolicy hsizetype="Preferred" vsizetype="Maximum"> + <horstretch>0</horstretch> + <verstretch>0</verstretch> + </sizepolicy> + </property> + <property name="font"> + <font> + <weight>75</weight> + <bold>true</bold> + </font> + </property> <property name="text"> <string>TextLabel</string> </property> @@ -24,89 +40,51 @@ </property> </widget> </item> - <item row="1" column="0"> - <widget class="QGraphicsView" name="cover"/> + <item row="2" column="0" rowspan="3"> + <widget class="CoverView" name="cover"/> </item> - <item row="1" column="1"> - <layout class="QVBoxLayout" name="verticalLayout"> + <item row="3" column="1"> + <widget class="QCheckBox" name="fit_cover"> + <property name="text"> + <string>Fit &cover within view</string> + </property> + </widget> + </item> + <item row="4" column="1"> + <layout class="QHBoxLayout" name="horizontalLayout"> <item> - <widget class="QLabel" name="text"> + <widget class="QPushButton" name="previous_button"> <property name="text"> - <string>TextLabel</string> + <string>&Previous</string> </property> - <property name="alignment"> - <set>Qt::AlignLeading|Qt::AlignLeft|Qt::AlignTop</set> - </property> - <property name="wordWrap"> - <bool>true</bool> + <property name="icon"> + <iconset resource="../../../../resources/images.qrc"> + <normaloff>:/images/previous.png</normaloff>:/images/previous.png</iconset> </property> </widget> </item> <item> - <widget class="QGroupBox" name="groupBox"> - <property name="title"> - <string>Comments</string> - </property> - <layout class="QVBoxLayout" name="verticalLayout_2"> - <item> - <widget class="QWebView" name="comments"> - <property name="sizePolicy"> - <sizepolicy hsizetype="Preferred" vsizetype="Expanding"> - <horstretch>0</horstretch> - <verstretch>0</verstretch> - </sizepolicy> - </property> - <property name="maximumSize"> - <size> - <width>350</width> - <height>16777215</height> - </size> - </property> - <property name="url"> - <url> - <string>about:blank</string> - </url> - </property> - </widget> - </item> - </layout> - </widget> - </item> - <item> - <widget class="QCheckBox" name="fit_cover"> + <widget class="QPushButton" name="next_button"> <property name="text"> - <string>Fit &cover within view</string> + <string>&Next</string> + </property> + <property name="icon"> + <iconset resource="../../../../resources/images.qrc"> + <normaloff>:/images/next.png</normaloff>:/images/next.png</iconset> </property> </widget> </item> - <item> - <layout class="QHBoxLayout" name="horizontalLayout"> - <item> - <widget class="QPushButton" name="previous_button"> - <property name="text"> - <string>&Previous</string> - </property> - <property name="icon"> - <iconset resource="../../../../resources/images.qrc"> - <normaloff>:/images/previous.png</normaloff>:/images/previous.png</iconset> - </property> - </widget> - </item> - <item> - <widget class="QPushButton" name="next_button"> - <property name="text"> - <string>&Next</string> - </property> - <property name="icon"> - <iconset resource="../../../../resources/images.qrc"> - <normaloff>:/images/next.png</normaloff>:/images/next.png</iconset> - </property> - </widget> - </item> - </layout> - </item> </layout> </item> + <item row="2" column="1"> + <widget class="QWebView" name="details"> + <property name="url"> + <url> + <string>about:blank</string> + </url> + </property> + </widget> + </item> </layout> </widget> <customwidgets> @@ -115,6 +93,11 @@ <extends>QWidget</extends> <header>QtWebKit/QWebView</header> </customwidget> + <customwidget> + <class>CoverView</class> + <extends>QGraphicsView</extends> + <header>calibre/gui2/widgets.h</header> + </customwidget> </customwidgets> <resources> <include location="../../../../resources/images.qrc"/> diff --git a/src/calibre/gui2/dialogs/catalog.py b/src/calibre/gui2/dialogs/catalog.py index ebca7235eb..a8f7ed160f 100644 --- a/src/calibre/gui2/dialogs/catalog.py +++ b/src/calibre/gui2/dialogs/catalog.py @@ -6,7 +6,7 @@ __license__ = 'GPL v3' __copyright__ = '2010, Kovid Goyal <kovid@kovidgoyal.net>' __docformat__ = 'restructuredtext en' -import os, sys +import os, sys, importlib from calibre.customize.ui import config from calibre.gui2.dialogs.catalog_ui import Ui_Dialog @@ -43,8 +43,7 @@ class Catalog(ResizableDialog, Ui_Dialog): name = plugin.name.lower().replace(' ', '_') if type(plugin) in builtin_plugins: try: - catalog_widget = __import__('calibre.gui2.catalog.'+name, - fromlist=[1]) + catalog_widget = importlib.import_module('calibre.gui2.catalog.'+name) pw = catalog_widget.PluginWidget() pw.initialize(name, db) pw.ICON = I('forward.png') @@ -75,7 +74,7 @@ class Catalog(ResizableDialog, Ui_Dialog): # Import the dynamic PluginWidget() from .py file provided in plugin.zip try: sys.path.insert(0, plugin.resources_path) - catalog_widget = __import__(name, fromlist=[1]) + catalog_widget = importlib.import_module(name) pw = catalog_widget.PluginWidget() pw.initialize(name) pw.ICON = I('forward.png') diff --git a/src/calibre/gui2/dialogs/check_library.py b/src/calibre/gui2/dialogs/check_library.py index f9db87b087..95f99d4034 100644 --- a/src/calibre/gui2/dialogs/check_library.py +++ b/src/calibre/gui2/dialogs/check_library.py @@ -68,7 +68,7 @@ class DBCheck(QDialog): # {{{ self.start_load() return QTimer.singleShot(0, self.do_one_dump) - except Exception, e: + except Exception as e: import traceback self.error = (as_unicode(e), traceback.format_exc()) self.reject() @@ -90,7 +90,7 @@ class DBCheck(QDialog): # {{{ self.conn.commit() QTimer.singleShot(0, self.do_one_load) - except Exception, e: + except Exception as e: import traceback self.error = (as_unicode(e), traceback.format_exc()) self.reject() @@ -111,7 +111,7 @@ class DBCheck(QDialog): # {{{ self.pb.setValue(self.pb.value() + 1) self.count -= 1 QTimer.singleShot(0, self.do_one_load) - except Exception, e: + except Exception as e: import traceback self.error = (as_unicode(e), traceback.format_exc()) self.reject() @@ -202,13 +202,19 @@ class CheckLibraryDialog(QDialog): <p><i>Delete marked</i> is used to remove extra files/folders/covers that have no entries in the database. Check the box next to the item you want to delete. Use with caution.</p> - <p><i>Fix marked</i> is applicable only to covers (the two lines marked - 'fixable'). In the case of missing cover files, checking the fixable - box and pushing this button will remove the cover mark from the - database for all the files in that category. In the case of extra - cover files, checking the fixable box and pushing this button will - add the cover mark to the database for all the files in that - category.</p> + + <p><i>Fix marked</i> is applicable only to covers and missing formats + (the three lines marked 'fixable'). In the case of missing cover files, + checking the fixable box and pushing this button will tell calibre that + there is no cover for all of the books listed. Use this option if you + are not going to restore the covers from a backup. In the case of extra + cover files, checking the fixable box and pushing this button will tell + calibre that the cover files it found are correct for all the books + listed. Use this when you are not going to delete the file(s). In the + case of missing formats, checking the fixable box and pushing this + button will tell calibre that the formats are really gone. Use this if + you are not going to restore the formats from a backup.</p> + ''')) self.log = QTreeWidget(self) @@ -381,6 +387,19 @@ class CheckLibraryDialog(QDialog): unicode(it.text(1)))) self.run_the_check() + def fix_missing_formats(self): + tl = self.top_level_items['missing_formats'] + child_count = tl.childCount() + for i in range(0, child_count): + item = tl.child(i); + id = item.data(0, Qt.UserRole).toInt()[0] + all = self.db.formats(id, index_is_id=True, verify_formats=False) + all = set([f.strip() for f in all.split(',')]) if all else set() + valid = self.db.formats(id, index_is_id=True, verify_formats=True) + valid = set([f.strip() for f in valid.split(',')]) if valid else set() + for fmt in all-valid: + self.db.remove_format(id, fmt, index_is_id=True, db_only=True) + def fix_missing_covers(self): tl = self.top_level_items['missing_covers'] child_count = tl.childCount() diff --git a/src/calibre/gui2/dialogs/choose_plugin_toolbars.py b/src/calibre/gui2/dialogs/choose_plugin_toolbars.py new file mode 100644 index 0000000000..ddf8e162e8 --- /dev/null +++ b/src/calibre/gui2/dialogs/choose_plugin_toolbars.py @@ -0,0 +1,61 @@ +#!/usr/bin/env python +# vim:fileencoding=UTF-8:ts=4:sw=4:sta:et:sts=4:ai + +from __future__ import (unicode_literals, division, absolute_import, + print_function) + +__copyright__ = '2008, Kovid Goyal kovid@kovidgoyal.net' +__docformat__ = 'restructuredtext en' +__license__ = 'GPL v3' + + +from PyQt4.Qt import QDialog, QVBoxLayout, QLabel, QDialogButtonBox, \ + QListWidget, QAbstractItemView +from PyQt4 import QtGui + +class ChoosePluginToolbarsDialog(QDialog): + + def __init__(self, parent, plugin, locations): + QDialog.__init__(self, parent) + self.locations = locations + + self.setWindowTitle( + _('Add "%s" to toolbars or menus')%plugin.name) + + self._layout = QVBoxLayout(self) + self.setLayout(self._layout) + + self._header_label = QLabel( + _('Select the toolbars and/or menus to add <b>%s</b> to:') % + plugin.name) + self._layout.addWidget(self._header_label) + + self._locations_list = QListWidget(self) + self._locations_list.setSelectionMode(QAbstractItemView.MultiSelection) + sizePolicy = QtGui.QSizePolicy(QtGui.QSizePolicy.Preferred, + QtGui.QSizePolicy.Minimum) + sizePolicy.setHorizontalStretch(0) + sizePolicy.setVerticalStretch(0) + self._locations_list.setSizePolicy(sizePolicy) + for key, text in locations: + self._locations_list.addItem(text) + self._layout.addWidget(self._locations_list) + + self._footer_label = QLabel( + _('You can also customise the plugin locations ' + 'using <b>Preferences -> Customise the toolbar</b>')) + self._layout.addWidget(self._footer_label) + + button_box = QDialogButtonBox(QDialogButtonBox.Ok | + QDialogButtonBox.Cancel) + button_box.accepted.connect(self.accept) + button_box.rejected.connect(self.reject) + self._layout.addWidget(button_box) + self.resize(self.sizeHint()) + + def selected_locations(self): + selected = [] + for row in self._locations_list.selectionModel().selectedRows(): + selected.append(self.locations[row.row()]) + return selected + diff --git a/src/calibre/gui2/dialogs/confirm_delete.py b/src/calibre/gui2/dialogs/confirm_delete.py index 9cdd46712f..664afd507b 100644 --- a/src/calibre/gui2/dialogs/confirm_delete.py +++ b/src/calibre/gui2/dialogs/confirm_delete.py @@ -3,12 +3,11 @@ __license__ = 'GPL v3' __copyright__ = '2008, Kovid Goyal kovid@kovidgoyal.net' __docformat__ = 'restructuredtext en' -from calibre.gui2 import dynamic -from calibre.gui2.dialogs.confirm_delete_ui import Ui_Dialog from PyQt4.Qt import QDialog, Qt, QPixmap, QIcon -def _config_name(name): - return name + '_again' +from calibre import confirm_config_name +from calibre.gui2 import dynamic +from calibre.gui2.dialogs.confirm_delete_ui import Ui_Dialog class Dialog(QDialog, Ui_Dialog): @@ -22,14 +21,21 @@ class Dialog(QDialog, Ui_Dialog): self.buttonBox.setFocus(Qt.OtherFocusReason) def toggle(self, *args): - dynamic[_config_name(self.name)] = self.again.isChecked() + dynamic[confirm_config_name(self.name)] = self.again.isChecked() -def confirm(msg, name, parent=None, pixmap='dialog_warning.png'): - if not dynamic.get(_config_name(name), True): +def confirm(msg, name, parent=None, pixmap='dialog_warning.png', title=None, + show_cancel_button=True, confirm_msg=None): + if not dynamic.get(confirm_config_name(name), True): return True d = Dialog(msg, name, parent) d.label.setPixmap(QPixmap(I(pixmap))) d.setWindowIcon(QIcon(I(pixmap))) + if title is not None: + d.setWindowTitle(title) + if not show_cancel_button: + d.buttonBox.button(d.buttonBox.Cancel).setVisible(False) + if confirm_msg is not None: + d.again.setText(confirm_msg) d.resize(d.sizeHint()) return d.exec_() == d.Accepted diff --git a/src/calibre/gui2/dialogs/confirm_delete_location.ui b/src/calibre/gui2/dialogs/confirm_delete_location.ui index 9d70388627..212d96584f 100644 --- a/src/calibre/gui2/dialogs/confirm_delete_location.ui +++ b/src/calibre/gui2/dialogs/confirm_delete_location.ui @@ -22,6 +22,12 @@ <layout class="QHBoxLayout" name="horizontalLayout"> <item> <widget class="QLabel" name="label"> + <property name="sizePolicy"> + <sizepolicy hsizetype="Maximum" vsizetype="Preferred"> + <horstretch>0</horstretch> + <verstretch>0</verstretch> + </sizepolicy> + </property> <property name="pixmap"> <pixmap resource="../../../../resources/images.qrc">:/images/dialog_warning.png</pixmap> </property> @@ -46,6 +52,10 @@ <property name="text"> <string>Library</string> </property> + <property name="icon"> + <iconset resource="../../../../resources/images.qrc"> + <normaloff>:/images/library.png</normaloff>:/images/library.png</iconset> + </property> </widget> </item> <item> @@ -53,6 +63,10 @@ <property name="text"> <string>Device</string> </property> + <property name="icon"> + <iconset resource="../../../../resources/images.qrc"> + <normaloff>:/images/reader.png</normaloff>:/images/reader.png</iconset> + </property> </widget> </item> <item> @@ -60,6 +74,10 @@ <property name="text"> <string>Library and Device</string> </property> + <property name="icon"> + <iconset resource="../../../../resources/images.qrc"> + <normaloff>:/images/trash.png</normaloff>:/images/trash.png</iconset> + </property> </widget> </item> <item> diff --git a/src/calibre/gui2/dialogs/edit_authors_dialog.py b/src/calibre/gui2/dialogs/edit_authors_dialog.py index 62721f4c33..a791551d27 100644 --- a/src/calibre/gui2/dialogs/edit_authors_dialog.py +++ b/src/calibre/gui2/dialogs/edit_authors_dialog.py @@ -3,12 +3,13 @@ __copyright__ = '2008, Kovid Goyal kovid@kovidgoyal.net' __docformat__ = 'restructuredtext en' __license__ = 'GPL v3' -from PyQt4.Qt import Qt, QDialog, QTableWidgetItem, QAbstractItemView +from PyQt4.Qt import (Qt, QDialog, QTableWidgetItem, QAbstractItemView, QIcon, + QDialogButtonBox, QFrame, QLabel, QTimer, QMenu, QApplication) from calibre.ebooks.metadata import author_to_author_sort from calibre.gui2 import error_dialog from calibre.gui2.dialogs.edit_authors_dialog_ui import Ui_EditAuthorsDialog -from calibre.utils.icu import sort_key, strcmp +from calibre.utils.icu import sort_key class tableItem(QTableWidgetItem): def __ge__(self, other): @@ -19,7 +20,7 @@ class tableItem(QTableWidgetItem): class EditAuthorsDialog(QDialog, Ui_EditAuthorsDialog): - def __init__(self, parent, db, id_to_select): + def __init__(self, parent, db, id_to_select, select_sort): QDialog.__init__(self, parent) Ui_EditAuthorsDialog.__init__(self) self.setupUi(self) @@ -30,14 +31,23 @@ class EditAuthorsDialog(QDialog, Ui_EditAuthorsDialog): self.buttonBox.accepted.connect(self.accepted) + # Set up the column headings self.table.setSelectionMode(QAbstractItemView.SingleSelection) self.table.setColumnCount(2) - self.table.setHorizontalHeaderLabels([_('Author'), _('Author sort')]) + self.down_arrow_icon = QIcon(I('arrow-down.png')) + self.up_arrow_icon = QIcon(I('arrow-up.png')) + self.blank_icon = QIcon(I('blank.png')) + self.auth_col = QTableWidgetItem(_('Author')) + self.table.setHorizontalHeaderItem(0, self.auth_col) + self.auth_col.setIcon(self.blank_icon) + self.aus_col = QTableWidgetItem(_('Author sort')) + self.table.setHorizontalHeaderItem(1, self.aus_col) + self.aus_col.setIcon(self.up_arrow_icon) + # Add the data self.authors = {} auts = db.get_authors_with_ids() self.table.setRowCount(len(auts)) - setattr(self.table, '__lt__', lambda x, y: True if strcmp(x, y) < 0 else False) select_item = None for row, (id, author, sort) in enumerate(auts): author = author.replace('|', ',') @@ -48,7 +58,10 @@ class EditAuthorsDialog(QDialog, Ui_EditAuthorsDialog): self.table.setItem(row, 0, aut) self.table.setItem(row, 1, sort) if id == id_to_select: - select_item = sort + if select_sort: + select_item = sort + else: + select_item = aut self.table.resizeColumnsToContents() # set up the cellChanged signal only after the table is filled @@ -66,26 +79,156 @@ class EditAuthorsDialog(QDialog, Ui_EditAuthorsDialog): self.sort_by_author_sort.setChecked(True) self.author_sort_order = 1 - # set up author sort calc button self.recalc_author_sort.clicked.connect(self.do_recalc_author_sort) + self.auth_sort_to_author.clicked.connect(self.do_auth_sort_to_author) + # Position on the desired item if select_item is not None: self.table.setCurrentItem(select_item) self.table.editItem(select_item) + self.start_find_pos = select_item.row() * 2 + select_item.column() else: self.table.setCurrentCell(0, 0) + self.start_find_pos = -1 + + # set up the search box + self.find_box.initialize('manage_authors_search') + self.find_box.lineEdit().returnPressed.connect(self.do_find) + self.find_box.editTextChanged.connect(self.find_text_changed) + self.find_button.clicked.connect(self.do_find) + + l = QLabel(self.table) + self.not_found_label = l + l.setFrameStyle(QFrame.StyledPanel) + l.setAutoFillBackground(True) + l.setText(_('No matches found')) + l.setAlignment(Qt.AlignVCenter) + l.resize(l.sizeHint()) + l.move(10,20) + l.setVisible(False) + self.not_found_label.move(40, 40) + self.not_found_label_timer = QTimer() + self.not_found_label_timer.setSingleShot(True) + self.not_found_label_timer.timeout.connect( + self.not_found_label_timer_event, type=Qt.QueuedConnection) + + self.table.setContextMenuPolicy(Qt.CustomContextMenu) + self.table.customContextMenuRequested .connect(self.show_context_menu) + + def show_context_menu(self, point): + self.context_item = self.table.itemAt(point) + case_menu = QMenu(_('Change Case')) + action_upper_case = case_menu.addAction(_('Upper Case')) + action_lower_case = case_menu.addAction(_('Lower Case')) + action_swap_case = case_menu.addAction(_('Swap Case')) + action_title_case = case_menu.addAction(_('Title Case')) + action_capitalize = case_menu.addAction(_('Capitalize')) + + action_upper_case.triggered.connect(self.upper_case) + action_lower_case.triggered.connect(self.lower_case) + action_swap_case.triggered.connect(self.swap_case) + action_title_case.triggered.connect(self.title_case) + action_capitalize.triggered.connect(self.capitalize) + + m = self.au_context_menu = QMenu() + ca = m.addAction(_('Copy')) + ca.triggered.connect(self.copy_to_clipboard) + ca = m.addAction(_('Paste')) + ca.triggered.connect(self.paste_from_clipboard) + m.addSeparator() + + if self.context_item.column() == 0: + ca = m.addAction(_('Copy to author sort')) + ca.triggered.connect(self.copy_au_to_aus) + else: + ca = m.addAction(_('Copy to author')) + ca.triggered.connect(self.copy_aus_to_au) + m.addSeparator() + m.addMenu(case_menu) + m.exec_(self.table.mapToGlobal(point)) + + def copy_to_clipboard(self): + cb = QApplication.clipboard() + cb.setText(unicode(self.context_item.text())) + + def paste_from_clipboard(self): + cb = QApplication.clipboard() + self.context_item.setText(cb.text()) + + def upper_case(self): + self.context_item.setText(icu_upper(unicode(self.context_item.text()))) + + def lower_case(self): + self.context_item.setText(icu_lower(unicode(self.context_item.text()))) + + def swap_case(self): + self.context_item.setText(unicode(self.context_item.text()).swapcase()) + + def title_case(self): + from calibre.utils.titlecase import titlecase + self.context_item.setText(titlecase(unicode(self.context_item.text()))) + + def capitalize(self): + from calibre.utils.icu import capitalize + self.context_item.setText(capitalize(unicode(self.context_item.text()))) + + def copy_aus_to_au(self): + row = self.context_item.row() + dest = self.table.item(row, 0) + dest.setText(self.context_item.text()) + + def copy_au_to_aus(self): + row = self.context_item.row() + dest = self.table.item(row, 1) + dest.setText(self.context_item.text()) + + def not_found_label_timer_event(self): + self.not_found_label.setVisible(False) + + def find_text_changed(self): + self.start_find_pos = -1 + + def do_find(self): + self.not_found_label.setVisible(False) + # For some reason the button box keeps stealing the RETURN shortcut. + # Steal it back + self.buttonBox.button(QDialogButtonBox.Ok).setDefault(False) + self.buttonBox.button(QDialogButtonBox.Ok).setAutoDefault(False) + self.buttonBox.button(QDialogButtonBox.Cancel).setDefault(False) + self.buttonBox.button(QDialogButtonBox.Cancel).setAutoDefault(False) + st = icu_lower(unicode(self.find_box.currentText())) + + for i in range(0, self.table.rowCount()*2): + self.start_find_pos = (self.start_find_pos + 1) % (self.table.rowCount()*2) + r = (self.start_find_pos/2)%self.table.rowCount() + c = self.start_find_pos % 2 + item = self.table.item(r, c) + text = icu_lower(unicode(item.text())) + if st in text: + self.table.setCurrentItem(item) + self.table.setFocus(True) + return + # Nothing found. Pop up the little dialog for 1.5 seconds + self.not_found_label.setVisible(True) + self.not_found_label_timer.start(1500) def do_sort_by_author(self): self.author_order = 1 if self.author_order == 0 else 0 self.table.sortByColumn(0, self.author_order) self.sort_by_author.setChecked(True) self.sort_by_author_sort.setChecked(False) + self.auth_col.setIcon(self.down_arrow_icon if self.author_order + else self.up_arrow_icon) + self.aus_col.setIcon(self.blank_icon) def do_sort_by_author_sort(self): self.author_sort_order = 1 if self.author_sort_order == 0 else 0 self.table.sortByColumn(1, self.author_sort_order) self.sort_by_author.setChecked(False) self.sort_by_author_sort.setChecked(True) + self.aus_col.setIcon(self.down_arrow_icon if self.author_sort_order + else self.up_arrow_icon) + self.auth_col.setIcon(self.blank_icon) def accepted(self): self.result = [] @@ -108,6 +251,17 @@ class EditAuthorsDialog(QDialog, Ui_EditAuthorsDialog): self.table.setFocus(Qt.OtherFocusReason) self.table.cellChanged.connect(self.cell_changed) + def do_auth_sort_to_author(self): + self.table.cellChanged.disconnect() + for row in range(0,self.table.rowCount()): + item = self.table.item(row, 1) + aus = unicode(item.text()).strip() + c = self.table.item(row, 0) + # Sometimes trailing commas are left by changing between copy algs + c.setText(aus) + self.table.setFocus(Qt.OtherFocusReason) + self.table.cellChanged.connect(self.cell_changed) + def cell_changed(self, row, col): if col == 0: item = self.table.item(row, 0) diff --git a/src/calibre/gui2/dialogs/edit_authors_dialog.ui b/src/calibre/gui2/dialogs/edit_authors_dialog.ui index 6518e6a1b0..7fe965cc23 100644 --- a/src/calibre/gui2/dialogs/edit_authors_dialog.ui +++ b/src/calibre/gui2/dialogs/edit_authors_dialog.ui @@ -20,6 +20,50 @@ <string>Manage authors</string> </property> <layout class="QVBoxLayout" name="verticalLayout"> + <item> + <layout class="QHBoxLayout" name=""> + <item> + <widget class="QLabel"> + <property name="text"> + <string>&Search for:</string> + </property> + <property name="buddy"> + <cstring>find_box</cstring> + </property> + </widget> + </item> + <item> + <widget class="HistoryLineEdit" name="find_box"> + <property name="minimumSize"> + <size> + <width>200</width> + <height>0</height> + </size> + </property> + </widget> + </item> + <item> + <widget class="QPushButton" name="find_button"> + <property name="text"> + <string>F&ind</string> + </property> + </widget> + </item> + <item> + <spacer> + <property name="orientation"> + <enum>Qt::Horizontal</enum> + </property> + <property name="sizeHint" stdset="0"> + <size> + <width>40</width> + <height>20</height> + </size> + </property> + </spacer> + </item> + </layout> + </item> <item> <widget class="QTableWidget" name="table"> <property name="sizePolicy"> @@ -34,45 +78,45 @@ </widget> </item> <item> - <layout class="QHBoxLayout" name="horizontalLayout"> - <item> + <layout class="QGridLayout"> + <item row="0" column="0"> <widget class="QPushButton" name="sort_by_author"> <property name="text"> <string>Sort by author</string> </property> </widget> </item> - <item> + <item row="0" column="1"> <widget class="QPushButton" name="sort_by_author_sort"> <property name="text"> <string>Sort by author sort</string> </property> </widget> </item> - <item> + <item row="1" column="0"> <widget class="QPushButton" name="recalc_author_sort"> <property name="toolTip"> - <string>Reset all the author sort values to a value automatically generated from the author. Exactly how this value is automatically generated can be controlled via Preferences->Advanced->Tweaks</string> + <string>Reset all the author sort values to a value automatically +generated from the author. Exactly how this value is automatically +generated can be controlled via Preferences->Advanced->Tweaks</string> </property> <property name="text"> <string>Recalculate all author sort values</string> </property> </widget> </item> - <item> - <spacer name="horizontalSpacer_3"> - <property name="orientation"> - <enum>Qt::Horizontal</enum> + <item row="1" column="1"> + <widget class="QPushButton" name="auth_sort_to_author"> + <property name="toolTip"> + <string>Copy author sort to author for every author. You typically use this button +after changing Preferences->Advanced->Tweaks->Author sort name algorithm</string> </property> - <property name="sizeHint" stdset="0"> - <size> - <width>40</width> - <height>20</height> - </size> + <property name="text"> + <string>Copy all author sort values to author</string> </property> - </spacer> + </widget> </item> - <item> + <item row="1" column="2"> <widget class="QDialogButtonBox" name="buttonBox"> <property name="sizePolicy"> <sizepolicy hsizetype="Minimum" vsizetype="Fixed"> @@ -130,4 +174,11 @@ </hints> </connection> </connections> + <customwidgets> + <customwidget> + <class>HistoryLineEdit</class> + <extends>QComboBox</extends> + <header>calibre/gui2/widgets.h</header> + </customwidget> + </customwidgets> </ui> diff --git a/src/calibre/gui2/dialogs/fetch_metadata.py b/src/calibre/gui2/dialogs/fetch_metadata.py deleted file mode 100644 index 426c7b1d60..0000000000 --- a/src/calibre/gui2/dialogs/fetch_metadata.py +++ /dev/null @@ -1,271 +0,0 @@ -__license__ = 'GPL v3' -__copyright__ = '2008, Kovid Goyal <kovid at kovidgoyal.net>' -''' -GUI for fetching metadata from servers. -''' - -import time -from threading import Thread - -from PyQt4.QtCore import Qt, QObject, SIGNAL, QVariant, pyqtSignal, \ - QAbstractTableModel, QCoreApplication, QTimer -from PyQt4.QtGui import QDialog, QItemSelectionModel, QIcon - -from calibre.gui2.dialogs.fetch_metadata_ui import Ui_FetchMetadata -from calibre.gui2 import error_dialog, NONE, info_dialog, config -from calibre.gui2.widgets import ProgressIndicator -from calibre import strftime, force_unicode -from calibre.customize.ui import get_isbndb_key, set_isbndb_key -from calibre.utils.icu import sort_key - -_hung_fetchers = set([]) - -class Fetcher(Thread): - - def __init__(self, title, author, publisher, isbn, key): - Thread.__init__(self) - self.daemon = True - self.title = title - self.author = author - self.publisher = publisher - self.isbn = isbn - self.key = key - self.results, self.exceptions = [], [] - - def run(self): - from calibre.ebooks.metadata.fetch import search - self.results, self.exceptions = search(self.title, self.author, - self.publisher, self.isbn, - self.key if self.key else None) - - -class Matches(QAbstractTableModel): - - def __init__(self, matches): - self.matches = matches - self.yes_icon = QVariant(QIcon(I('ok.png'))) - QAbstractTableModel.__init__(self) - - def rowCount(self, *args): - return len(self.matches) - - def columnCount(self, *args): - return 8 - - def headerData(self, section, orientation, role): - if role != Qt.DisplayRole: - return NONE - text = "" - if orientation == Qt.Horizontal: - if section == 0: text = _("Title") - elif section == 1: text = _("Author(s)") - elif section == 2: text = _("Author Sort") - elif section == 3: text = _("Publisher") - elif section == 4: text = _("ISBN") - elif section == 5: text = _("Published") - elif section == 6: text = _("Has Cover") - elif section == 7: text = _("Has Summary") - - return QVariant(text) - else: - return QVariant(section+1) - - def summary(self, row): - return self.matches[row].comments - - def data_as_text(self, book, col): - if col == 0 and book.title is not None: - return book.title - elif col == 1: - return ', '.join(book.authors) - elif col == 2 and book.author_sort is not None: - return book.author_sort - elif col == 3 and book.publisher is not None: - return book.publisher - elif col == 4 and book.isbn is not None: - return book.isbn - elif col == 5 and hasattr(book.pubdate, 'timetuple'): - return strftime('%b %Y', book.pubdate.timetuple()) - elif col == 6 and book.has_cover: - return 'y' - elif col == 7 and book.comments: - return 'y' - return '' - - def data(self, index, role): - row, col = index.row(), index.column() - book = self.matches[row] - if role == Qt.DisplayRole: - res = self.data_as_text(book, col) - if col <= 5 and res: - return QVariant(res) - return NONE - elif role == Qt.DecorationRole: - if col == 6 and book.has_cover: - return self.yes_icon - if col == 7 and book.comments: - return self.yes_icon - return NONE - - def sort(self, col, order, reset=True): - if not self.matches: - return - descending = order == Qt.DescendingOrder - self.matches.sort(None, - lambda x: sort_key(unicode(force_unicode(self.data_as_text(x, col)))), - descending) - if reset: - self.reset() - -class FetchMetadata(QDialog, Ui_FetchMetadata): - - HANG_TIME = 75 #seconds - - queue_reject = pyqtSignal() - - def __init__(self, parent, isbn, title, author, publisher, timeout): - QDialog.__init__(self, parent) - Ui_FetchMetadata.__init__(self) - self.setupUi(self) - - for fetcher in list(_hung_fetchers): - if not fetcher.is_alive(): - _hung_fetchers.remove(fetcher) - - self.pi = ProgressIndicator(self) - self.timeout = timeout - QObject.connect(self.fetch, SIGNAL('clicked()'), self.fetch_metadata) - self.queue_reject.connect(self.reject, Qt.QueuedConnection) - - isbndb_key = get_isbndb_key() - if not isbndb_key: - isbndb_key = '' - self.key.setText(isbndb_key) - - self.setWindowTitle(title if title else _('Unknown')) - self.isbn = isbn - self.title = title - self.author = author.strip() - self.publisher = publisher - self.previous_row = None - self.warning.setVisible(False) - self.connect(self.matches, SIGNAL('activated(QModelIndex)'), self.chosen) - self.connect(self.matches, SIGNAL('entered(QModelIndex)'), - self.show_summary) - self.matches.setMouseTracking(True) - # Enabling sorting and setting a sort column will not change the initial - # order of the results, as they are filled in later - self.matches.setSortingEnabled(True) - self.matches.horizontalHeader().sectionClicked.connect(self.show_sort_indicator) - self.matches.horizontalHeader().setSortIndicatorShown(False) - self.fetch_metadata() - self.opt_get_social_metadata.setChecked(config['get_social_metadata']) - self.opt_overwrite_author_title_metadata.setChecked(config['overwrite_author_title_metadata']) - self.opt_auto_download_cover.setChecked(config['auto_download_cover']) - - def show_summary(self, current, *args): - row = current.row() - if row != self.previous_row: - summ = self.model.summary(row) - self.summary.setText(summ if summ else '') - self.previous_row = row - - def fetch_metadata(self): - self.warning.setVisible(False) - key = str(self.key.text()) - if key: - set_isbndb_key(key) - else: - key = None - title = author = publisher = isbn = None - if self.isbn: - isbn = self.isbn - if self.title: - title = self.title - if self.author and not self.author == _('Unknown'): - author = self.author - self.fetch.setEnabled(False) - self.setCursor(Qt.WaitCursor) - QCoreApplication.instance().processEvents() - self.fetcher = Fetcher(title, author, publisher, isbn, key) - self.fetcher.start() - self.pi.start(_('Finding metadata...')) - self._hangcheck = QTimer(self) - self.connect(self._hangcheck, SIGNAL('timeout()'), self.hangcheck, - Qt.QueuedConnection) - self.start_time = time.time() - self._hangcheck.start(100) - - def hangcheck(self): - if self.fetcher.is_alive() and \ - time.time() - self.start_time < self.HANG_TIME: - return - self._hangcheck.stop() - try: - if self.fetcher.is_alive(): - error_dialog(self, _('Could not find metadata'), - _('The metadata download seems to have stalled. ' - 'Try again later.')).exec_() - self.terminate() - return self.queue_reject.emit() - self.model = Matches(self.fetcher.results) - warnings = [(x[0], force_unicode(x[1])) for x in \ - self.fetcher.exceptions if x[1] is not None] - if warnings: - warnings='<br>'.join(['<b>%s</b>: %s'%(name, exc) for name,exc in warnings]) - self.warning.setText('<p><b>'+ _('Warning')+':</b>'+\ - _('Could not fetch metadata from:')+\ - '<br>'+warnings+'</p>') - self.warning.setVisible(True) - if self.model.rowCount() < 1: - info_dialog(self, _('No metadata found'), - _('No metadata found, try adjusting the title and author ' - 'and/or removing the ISBN.')).exec_() - self.reject() - return - - self.matches.setModel(self.model) - QObject.connect(self.matches.selectionModel(), - SIGNAL('currentRowChanged(QModelIndex, QModelIndex)'), - self.show_summary) - self.model.reset() - self.matches.selectionModel().select(self.model.index(0, 0), - QItemSelectionModel.Select | QItemSelectionModel.Rows) - self.matches.setCurrentIndex(self.model.index(0, 0)) - finally: - self.fetch.setEnabled(True) - self.unsetCursor() - self.matches.resizeColumnsToContents() - self.pi.stop() - - def terminate(self): - if hasattr(self, 'fetcher') and self.fetcher.is_alive(): - _hung_fetchers.add(self.fetcher) - if hasattr(self, '_hangcheck') and self._hangcheck.isActive(): - self._hangcheck.stop() - # Save value of auto_download_cover, since this is the only place it can - # be set. The values of the other options can be set in - # Preferences->Behavior and should not be set here as they affect bulk - # downloading as well. - if self.opt_auto_download_cover.isChecked() != config['auto_download_cover']: - config.set('auto_download_cover', self.opt_auto_download_cover.isChecked()) - - def __enter__(self, *args): - return self - - def __exit__(self, *args): - self.terminate() - - def selected_book(self): - try: - return self.matches.model().matches[self.matches.currentIndex().row()] - except: - return None - - def chosen(self, index): - self.matches.setCurrentIndex(index) - self.accept() - - def show_sort_indicator(self, *args): - self.matches.horizontalHeader().setSortIndicatorShown(True) - diff --git a/src/calibre/gui2/dialogs/fetch_metadata.ui b/src/calibre/gui2/dialogs/fetch_metadata.ui deleted file mode 100644 index b140fa158d..0000000000 --- a/src/calibre/gui2/dialogs/fetch_metadata.ui +++ /dev/null @@ -1,179 +0,0 @@ -<?xml version="1.0" encoding="UTF-8"?> -<ui version="4.0"> - <class>FetchMetadata</class> - <widget class="QDialog" name="FetchMetadata"> - <property name="windowModality"> - <enum>Qt::WindowModal</enum> - </property> - <property name="geometry"> - <rect> - <x>0</x> - <y>0</y> - <width>890</width> - <height>642</height> - </rect> - </property> - <property name="windowTitle"> - <string>Fetch metadata</string> - </property> - <property name="windowIcon"> - <iconset resource="../../../../resources/images.qrc"> - <normaloff>:/images/metadata.png</normaloff>:/images/metadata.png</iconset> - </property> - <layout class="QVBoxLayout"> - <item> - <widget class="QLabel" name="tlabel"> - <property name="text"> - <string><p>calibre can find metadata for your books from two locations: <b>Google Books</b> and <b>isbndb.com</b>. <p>To use isbndb.com you must sign up for a <a href="http://www.isbndb.com">free account</a> and enter your access key below.</string> - </property> - <property name="alignment"> - <set>Qt::AlignCenter</set> - </property> - <property name="wordWrap"> - <bool>true</bool> - </property> - <property name="openExternalLinks"> - <bool>true</bool> - </property> - </widget> - </item> - <item> - <layout class="QHBoxLayout"> - <item> - <widget class="QLabel" name="label_2"> - <property name="text"> - <string>&Access Key:</string> - </property> - <property name="buddy"> - <cstring>key</cstring> - </property> - </widget> - </item> - <item> - <widget class="QLineEdit" name="key"/> - </item> - <item> - <widget class="QPushButton" name="fetch"> - <property name="text"> - <string>Fetch</string> - </property> - </widget> - </item> - </layout> - </item> - <item> - <widget class="QLabel" name="warning"> - <property name="text"> - <string/> - </property> - <property name="wordWrap"> - <bool>true</bool> - </property> - </widget> - </item> - <item> - <widget class="QGroupBox" name="groupBox"> - <property name="title"> - <string>Matches</string> - </property> - <layout class="QVBoxLayout"> - <item> - <widget class="QLabel" name="label_3"> - <property name="text"> - <string>Select the book that most closely matches your copy from the list below</string> - </property> - </widget> - </item> - <item> - <widget class="QTableView" name="matches"> - <property name="sizePolicy"> - <sizepolicy hsizetype="Expanding" vsizetype="Expanding"> - <horstretch>0</horstretch> - <verstretch>1</verstretch> - </sizepolicy> - </property> - <property name="alternatingRowColors"> - <bool>true</bool> - </property> - <property name="selectionMode"> - <enum>QAbstractItemView::SingleSelection</enum> - </property> - <property name="selectionBehavior"> - <enum>QAbstractItemView::SelectRows</enum> - </property> - </widget> - </item> - <item> - <widget class="QTextBrowser" name="summary"/> - </item> - </layout> - </widget> - </item> - <item> - <widget class="QCheckBox" name="opt_overwrite_author_title_metadata"> - <property name="text"> - <string>Overwrite author and title with author and title of selected book</string> - </property> - </widget> - </item> - <item> - <widget class="QCheckBox" name="opt_get_social_metadata"> - <property name="text"> - <string>Download &social metadata (tags/rating/etc.) for the selected book</string> - </property> - </widget> - </item> - <item> - <widget class="QCheckBox" name="opt_auto_download_cover"> - <property name="text"> - <string>Automatically download the cover, if available</string> - </property> - </widget> - </item> - <item> - <widget class="QDialogButtonBox" name="buttonBox"> - <property name="standardButtons"> - <set>QDialogButtonBox::Cancel|QDialogButtonBox::Ok</set> - </property> - </widget> - </item> - </layout> - </widget> - <resources> - <include location="../../../../resources/images.qrc"/> - </resources> - <connections> - <connection> - <sender>buttonBox</sender> - <signal>accepted()</signal> - <receiver>FetchMetadata</receiver> - <slot>accept()</slot> - <hints> - <hint type="sourcelabel"> - <x>460</x> - <y>599</y> - </hint> - <hint type="destinationlabel"> - <x>657</x> - <y>530</y> - </hint> - </hints> - </connection> - <connection> - <sender>buttonBox</sender> - <signal>rejected()</signal> - <receiver>FetchMetadata</receiver> - <slot>reject()</slot> - <hints> - <hint type="sourcelabel"> - <x>417</x> - <y>599</y> - </hint> - <hint type="destinationlabel"> - <x>0</x> - <y>491</y> - </hint> - </hints> - </connection> - </connections> -</ui> diff --git a/src/calibre/gui2/dialogs/job_view.ui b/src/calibre/gui2/dialogs/job_view.ui index 8b54c23573..1e854c0f29 100644 --- a/src/calibre/gui2/dialogs/job_view.ui +++ b/src/calibre/gui2/dialogs/job_view.ui @@ -1,7 +1,8 @@ -<ui version="4.0" > +<?xml version="1.0" encoding="UTF-8"?> +<ui version="4.0"> <class>Dialog</class> - <widget class="QDialog" name="Dialog" > - <property name="geometry" > + <widget class="QDialog" name="Dialog"> + <property name="geometry"> <rect> <x>0</x> <y>0</y> @@ -9,38 +10,41 @@ <height>462</height> </rect> </property> - <property name="windowTitle" > + <property name="windowTitle"> <string>Details of job</string> </property> - <property name="windowIcon" > - <iconset resource="../../../../resources/images.qrc" > + <property name="windowIcon"> + <iconset resource="../../../../resources/images.qrc"> <normaloff>:/images/view.png</normaloff>:/images/view.png</iconset> </property> - <layout class="QGridLayout" name="gridLayout" > - <item row="0" column="0" > - <widget class="QPlainTextEdit" name="log" > - <property name="undoRedoEnabled" > + <layout class="QGridLayout" name="gridLayout"> + <item row="0" column="0"> + <widget class="QPlainTextEdit" name="log"> + <property name="undoRedoEnabled"> <bool>false</bool> </property> - <property name="lineWrapMode" > + <property name="lineWrapMode"> <enum>QPlainTextEdit::NoWrap</enum> </property> - <property name="readOnly" > + <property name="readOnly"> <bool>true</bool> </property> </widget> </item> - <item row="1" column="0" > - <widget class="QDialogButtonBox" name="buttonBox" > - <property name="standardButtons" > + <item row="2" column="0"> + <widget class="QDialogButtonBox" name="buttonBox"> + <property name="standardButtons"> <set>QDialogButtonBox::Ok</set> </property> </widget> </item> + <item row="1" column="0"> + <widget class="QTextBrowser" name="tb"/> + </item> </layout> </widget> <resources> - <include location="../../../../resources/images.qrc" /> + <include location="../../../../resources/images.qrc"/> </resources> <connections> <connection> @@ -49,11 +53,11 @@ <receiver>Dialog</receiver> <slot>accept()</slot> <hints> - <hint type="sourcelabel" > + <hint type="sourcelabel"> <x>617</x> <y>442</y> </hint> - <hint type="destinationlabel" > + <hint type="destinationlabel"> <x>206</x> <y>-5</y> </hint> diff --git a/src/calibre/gui2/dialogs/message_box.py b/src/calibre/gui2/dialogs/message_box.py index 945d50de4e..fdec19dc69 100644 --- a/src/calibre/gui2/dialogs/message_box.py +++ b/src/calibre/gui2/dialogs/message_box.py @@ -6,30 +6,36 @@ __copyright__ = '2011, Kovid Goyal <kovid@kovidgoyal.net>' __docformat__ = 'restructuredtext en' -from PyQt4.Qt import QDialog, QIcon, QApplication, QSize, QKeySequence, \ - QAction, Qt +from PyQt4.Qt import (QDialog, QIcon, QApplication, QSize, QKeySequence, + QAction, Qt, QTextBrowser, QDialogButtonBox, QVBoxLayout) from calibre.constants import __version__ from calibre.gui2.dialogs.message_box_ui import Ui_Dialog -class MessageBox(QDialog, Ui_Dialog): +class MessageBox(QDialog, Ui_Dialog): # {{{ ERROR = 0 WARNING = 1 INFO = 2 QUESTION = 3 - def __init__(self, type_, title, msg, det_msg='', show_copy_button=True, - parent=None): + def __init__(self, type_, title, msg, + det_msg='', + q_icon=None, + show_copy_button=True, + parent=None): QDialog.__init__(self, parent) - icon = { - self.ERROR : 'error', - self.WARNING: 'warning', - self.INFO: 'information', - self.QUESTION: 'question', - }[type_] - icon = 'dialog_%s.png'%icon - self.icon = QIcon(I(icon)) + if q_icon is None: + icon = { + self.ERROR : 'error', + self.WARNING: 'warning', + self.INFO: 'information', + self.QUESTION: 'question', + }[type_] + icon = 'dialog_%s.png'%icon + self.icon = QIcon(I(icon)) + else: + self.icon = q_icon self.setupUi(self) self.setWindowTitle(title) @@ -44,7 +50,6 @@ class MessageBox(QDialog, Ui_Dialog): self.bb.ActionRole) self.ctc_button.clicked.connect(self.copy_to_clipboard) - self.show_det_msg = _('Show &details') self.hide_det_msg = _('Hide &details') self.det_msg_toggle = self.bb.addButton(self.show_det_msg, self.bb.ActionRole) @@ -111,6 +116,90 @@ class MessageBox(QDialog, Ui_Dialog): self.det_msg_toggle.setVisible(bool(msg)) self.det_msg.setVisible(False) self.do_resize() +# }}} + +class ViewLog(QDialog): # {{{ + + def __init__(self, title, html, parent=None): + QDialog.__init__(self, parent) + self.l = l = QVBoxLayout() + self.setLayout(l) + + self.tb = QTextBrowser(self) + self.tb.setHtml('<pre style="font-family: monospace">%s</pre>' % html) + l.addWidget(self.tb) + + self.bb = QDialogButtonBox(QDialogButtonBox.Ok) + self.bb.accepted.connect(self.accept) + self.bb.rejected.connect(self.reject) + self.copy_button = self.bb.addButton(_('Copy to clipboard'), + self.bb.ActionRole) + self.copy_button.setIcon(QIcon(I('edit-copy.png'))) + self.copy_button.clicked.connect(self.copy_to_clipboard) + l.addWidget(self.bb) + self.setModal(False) + self.resize(QSize(700, 500)) + self.setWindowTitle(title) + self.setWindowIcon(QIcon(I('debug.png'))) + self.show() + + def copy_to_clipboard(self): + txt = self.tb.toPlainText() + QApplication.clipboard().setText(txt) +# }}} + + +_proceed_memory = [] + +class ProceedNotification(MessageBox): # {{{ + + def __init__(self, callback, payload, html_log, log_viewer_title, title, msg, + det_msg='', show_copy_button=False, parent=None): + ''' + A non modal popup that notifies the user that a background task has + been completed. + + :param callback: A callable that is called with payload if the user + asks to proceed. Note that this is always called in the GUI thread + :param payload: Arbitrary object, passed to callback + :param html_log: An HTML or plain text log + :param log_viewer_title: The title for the log viewer window + :param title: The title fo rthis popup + :param msg: The msg to display + :param det_msg: Detailed message + ''' + MessageBox.__init__(self, MessageBox.QUESTION, title, msg, + det_msg=det_msg, show_copy_button=show_copy_button, + parent=parent) + self.payload = payload + self.html_log = html_log + self.log_viewer_title = log_viewer_title + self.finished.connect(self.do_proceed, type=Qt.QueuedConnection) + + self.vlb = self.bb.addButton(_('View log'), self.bb.ActionRole) + self.vlb.setIcon(QIcon(I('debug.png'))) + self.vlb.clicked.connect(self.show_log) + self.det_msg_toggle.setVisible(bool(det_msg)) + self.setModal(False) + self.callback = callback + _proceed_memory.append(self) + + def show_log(self): + self.log_viewer = ViewLog(self.log_viewer_title, self.html_log, + parent=self) + + def do_proceed(self, result): + try: + if result == self.Accepted: + self.callback(self.payload) + finally: + # Ensure this notification is garbage collected + self.callback = None + self.setParent(None) + self.finished.disconnect() + self.vlb.clicked.disconnect() + _proceed_memory.remove(self) +# }}} if __name__ == '__main__': app = QApplication([]) diff --git a/src/calibre/gui2/dialogs/metadata_bulk.py b/src/calibre/gui2/dialogs/metadata_bulk.py index c1627d7e12..66cf55a9b2 100644 --- a/src/calibre/gui2/dialogs/metadata_bulk.py +++ b/src/calibre/gui2/dialogs/metadata_bulk.py @@ -7,13 +7,12 @@ import re, os, inspect from PyQt4.Qt import Qt, QDialog, QGridLayout, QVBoxLayout, QFont, QLabel, \ pyqtSignal, QDialogButtonBox, QInputDialog, QLineEdit, \ - QDate + QDate, QCompleter from calibre.gui2.dialogs.metadata_bulk_ui import Ui_MetadataBulkDialog from calibre.gui2.dialogs.tag_editor import TagEditor from calibre.ebooks.metadata import string_to_authors, authors_to_string, title_sort from calibre.ebooks.metadata.book.base import composite_formatter -from calibre.ebooks.metadata.meta import get_metadata from calibre.gui2.custom_column_widgets import populate_metadata_page from calibre.gui2 import error_dialog, ResizableDialog, UNDEFINED_QDATE, \ gprefs, question_dialog @@ -26,6 +25,7 @@ from calibre.utils.magick.draw import identify_data from calibre.utils.date import qt_to_dt def get_cover_data(path): # {{{ + from calibre.ebooks.metadata.meta import get_metadata old = prefs['read_file_metadata'] if not old: prefs['read_file_metadata'] = True @@ -120,7 +120,7 @@ class MyBlockingBusy(QDialog): # {{{ self.msg.setText(self.msg_text.format(self.phases[self.current_phase], percent)) self.do_one(id) - except Exception, err: + except Exception as err: import traceback try: err = unicode(err) @@ -364,7 +364,8 @@ class MetadataBulkDialog(ResizableDialog, Ui_MetadataBulkDialog): (fm[f]['datatype'] in ['text', 'series', 'enumeration'] and fm[f].get('search_terms', None) and f not in ['formats', 'ondevice']) or - fm[f]['datatype'] in ['int', 'float', 'bool'] ): + (fm[f]['datatype'] in ['int', 'float', 'bool'] and + f not in ['id'])): self.all_fields.append(f) self.writable_fields.append(f) if fm[f]['datatype'] == 'composite': @@ -393,6 +394,14 @@ class MetadataBulkDialog(ResizableDialog, Ui_MetadataBulkDialog): self.book_1_text.setObjectName(name) self.testgrid.addWidget(w, i+offset, 2, 1, 1) + ident_types = sorted(self.db.get_all_identifier_types(), key=sort_key) + self.s_r_dst_ident.setCompleter(QCompleter(ident_types)) + try: + self.s_r_dst_ident.setPlaceholderText(_('Enter an identifier type')) + except: + pass + self.s_r_src_ident.addItems(ident_types) + self.main_heading = _( '<b>You can destroy your library using this feature.</b> ' 'Changes are permanent. There is no undo function. ' @@ -449,6 +458,8 @@ class MetadataBulkDialog(ResizableDialog, Ui_MetadataBulkDialog): self.test_text.editTextChanged[str].connect(self.s_r_paint_results) self.comma_separated.stateChanged.connect(self.s_r_paint_results) self.case_sensitive.stateChanged.connect(self.s_r_paint_results) + self.s_r_src_ident.currentIndexChanged[int].connect(self.s_r_paint_results) + self.s_r_dst_ident.textChanged.connect(self.s_r_paint_results) self.s_r_template.lost_focus.connect(self.s_r_template_changed) self.central_widget.setCurrentIndex(0) @@ -471,6 +482,8 @@ class MetadataBulkDialog(ResizableDialog, Ui_MetadataBulkDialog): self.query_field.addItems(sorted([q for q in self.queries], key=sort_key)) self.query_field.currentIndexChanged[str].connect(self.s_r_query_change) self.query_field.setCurrentIndex(0) + self.search_field.setCurrentIndex(0) + self.s_r_search_field_changed(0) def s_r_sf_itemdata(self, idx): if idx is None: @@ -495,10 +508,19 @@ class MetadataBulkDialog(ResizableDialog, Ui_MetadataBulkDialog): val = mi.get(field, None) if isinstance(val, (int, float, bool)): val = str(val) + elif fm['is_csp']: + # convert the csp dict into a list + id_type = unicode(self.s_r_src_ident.currentText()) + if id_type: + val = [val.get(id_type, '')] + else: + val = [u'%s:%s'%(t[0], t[1]) for t in val.iteritems()] if val is None: val = [] if fm['is_multiple'] else [''] elif not fm['is_multiple']: val = [val] + elif fm['datatype'] == 'composite': + val = [v.strip() for v in val.split(fm['is_multiple'])] elif field == 'authors': val = [v.replace('|', ',') for v in val] else: @@ -512,12 +534,17 @@ class MetadataBulkDialog(ResizableDialog, Ui_MetadataBulkDialog): self.s_r_search_field_changed(self.search_field.currentIndex()) def s_r_search_field_changed(self, idx): - if self.search_mode.currentIndex() != 0 and idx == 1: # Template + self.s_r_template.setVisible(False) + self.template_label.setVisible(False) + self.s_r_src_ident_label.setVisible(False) + self.s_r_src_ident.setVisible(False) + if idx == 1: # Template self.s_r_template.setVisible(True) self.template_label.setVisible(True) - else: - self.s_r_template.setVisible(False) - self.template_label.setVisible(False) + elif self.s_r_sf_itemdata(idx) == 'identifiers': + self.s_r_src_ident_label.setVisible(True) + self.s_r_src_ident.setVisible(True) + for i in range(0, self.s_r_number_of_books): w = getattr(self, 'book_%d_text'%(i+1)) mi = self.db.get_metadata(self.ids[i], index_is_id=True) @@ -535,10 +562,15 @@ class MetadataBulkDialog(ResizableDialog, Ui_MetadataBulkDialog): self.s_r_paint_results(None) def s_r_destination_field_changed(self, idx): + self.s_r_dst_ident_label.setVisible(False) + self.s_r_dst_ident.setVisible(False) txt = self.s_r_df_itemdata(idx) if not txt: txt = self.s_r_sf_itemdata(None) if txt and txt in self.writable_fields: + if txt == 'identifiers': + self.s_r_dst_ident_label.setVisible(True) + self.s_r_dst_ident.setVisible(True) self.destination_field_fm = self.db.metadata_for_field(txt) self.s_r_paint_results(None) @@ -617,9 +649,16 @@ class MetadataBulkDialog(ResizableDialog, Ui_MetadataBulkDialog): dest = src dest_mode = self.replace_mode.currentIndex() + if self.destination_field_fm['is_csp']: + if not unicode(self.s_r_dst_ident.text()): + raise Exception(_('You must specify a destination identifier type')) + if self.destination_field_fm['is_multiple']: if self.comma_separated.isChecked(): - if dest == 'authors': + if dest == 'authors' or \ + (self.destination_field_fm['is_custom'] and + self.destination_field_fm['datatype'] == 'text' and + self.destination_field_fm['display'].get('is_names', False)): splitter = ' & ' else: splitter = ',' @@ -635,6 +674,13 @@ class MetadataBulkDialog(ResizableDialog, Ui_MetadataBulkDialog): if dest_mode != 0: dest_val = mi.get(dest, '') + if self.db.metadata_for_field(dest)['is_csp']: + dst_id_type = unicode(self.s_r_dst_ident.text()) + if dst_id_type: + dest_val = [dest_val.get(dst_id_type, '')] + else: + # convert the csp dict into a list + dest_val = [u'%s:%s'%(t[0], t[1]) for t in dest_val.iteritems()] if dest_val is None: dest_val = [] elif not isinstance(dest_val, list): @@ -717,6 +763,17 @@ class MetadataBulkDialog(ResizableDialog, Ui_MetadataBulkDialog): 'Book title %s not processed')%mi.title, show=True) return + # convert the colon-separated pair strings back into a dict, which + # is what set_identifiers wants + if dfm['is_csp']: + dst_id_type = unicode(self.s_r_dst_ident.text()) + if dst_id_type: + v = ''.join(val) + ids = mi.get(dest) + ids[dst_id_type] = v + val = ids + else: + val = dict([(t.split(':')) for t in val]) else: val = self.s_r_replace_mode_separator().join(val) if dest == 'title' and len(val) == 0: @@ -731,6 +788,12 @@ class MetadataBulkDialog(ResizableDialog, Ui_MetadataBulkDialog): books_to_refresh = self.db.set_custom(id, val, label=dfm['label'], extra=extra, commit=False, allow_case_change=True) + elif dest.startswith('#') and dest.endswith('_index'): + label = self.db.field_metadata[dest[:-6]]['label'] + series = self.db.get_custom(id, label=label, index_is_id=True) + books_to_refresh = self.db.set_custom(id, series, label=label, + extra=val, commit=False, + allow_case_change=True) else: if dest == 'comments': setter = self.db.set_comment @@ -961,11 +1024,13 @@ class MetadataBulkDialog(ResizableDialog, Ui_MetadataBulkDialog): query['search_field'] = unicode(self.search_field.currentText()) query['search_mode'] = unicode(self.search_mode.currentText()) query['s_r_template'] = unicode(self.s_r_template.text()) + query['s_r_src_ident'] = unicode(self.s_r_src_ident.currentText()) query['search_for'] = unicode(self.search_for.text()) query['case_sensitive'] = self.case_sensitive.isChecked() query['replace_with'] = unicode(self.replace_with.text()) query['replace_func'] = unicode(self.replace_func.currentText()) query['destination_field'] = unicode(self.destination_field.currentText()) + query['s_r_dst_ident'] = unicode(self.s_r_dst_ident.text()) query['replace_mode'] = unicode(self.replace_mode.currentText()) query['comma_separated'] = self.comma_separated.isChecked() query['results_count'] = self.results_count.value() @@ -992,37 +1057,61 @@ class MetadataBulkDialog(ResizableDialog, Ui_MetadataBulkDialog): self.s_r_reset_query_fields() return - def set_index(attr, txt): + def set_text(attr, key): try: - attr.setCurrentIndex(attr.findText(txt)) + attr.setText(item[key]) + except: + pass + + def set_checked(attr, key): + try: + attr.setChecked(item[key]) + except: + attr.setChecked(False) + + def set_value(attr, key): + try: + attr.setValue(int(item[key])) + except: + attr.setValue(0) + + def set_index(attr, key): + try: + attr.setCurrentIndex(attr.findText(item[key])) except: attr.setCurrentIndex(0) - set_index(self.search_mode, item['search_mode']) - set_index(self.search_field, item['search_field']) - self.s_r_template.setText(item['s_r_template']) + set_index(self.search_mode, 'search_mode') + set_index(self.search_field, 'search_field') + set_text(self.s_r_template, 's_r_template') + self.s_r_template_changed() #simulate gain/loss of focus - self.search_for.setText(item['search_for']) - self.case_sensitive.setChecked(item['case_sensitive']) - self.replace_with.setText(item['replace_with']) - set_index(self.replace_func, item['replace_func']) - set_index(self.destination_field, item['destination_field']) - set_index(self.replace_mode, item['replace_mode']) - self.comma_separated.setChecked(item['comma_separated']) - self.results_count.setValue(int(item['results_count'])) - self.starting_from.setValue(int(item['starting_from'])) - self.multiple_separator.setText(item['multiple_separator']) + + set_index(self.s_r_src_ident, 's_r_src_ident'); + set_text(self.s_r_dst_ident, 's_r_dst_ident') + set_text(self.search_for, 'search_for') + set_checked(self.case_sensitive, 'case_sensitive') + set_text(self.replace_with, 'replace_with') + set_index(self.replace_func, 'replace_func') + set_index(self.destination_field, 'destination_field') + set_index(self.replace_mode, 'replace_mode') + set_checked(self.comma_separated, 'comma_separated') + set_value(self.results_count, 'results_count') + set_value(self.starting_from, 'starting_from') + set_text(self.multiple_separator, 'multiple_separator') def s_r_reset_query_fields(self): # Don't reset the search mode. The user will probably want to use it # as it was self.search_field.setCurrentIndex(0) + self.s_r_src_ident.setCurrentIndex(0) self.s_r_template.setText("") self.search_for.setText("") self.case_sensitive.setChecked(False) self.replace_with.setText("") self.replace_func.setCurrentIndex(0) self.destination_field.setCurrentIndex(0) + self.s_r_dst_ident.setText('') self.replace_mode.setCurrentIndex(0) self.comma_separated.setChecked(True) self.results_count.setValue(999) diff --git a/src/calibre/gui2/dialogs/metadata_bulk.ui b/src/calibre/gui2/dialogs/metadata_bulk.ui index ae3445998b..59a68d6514 100644 --- a/src/calibre/gui2/dialogs/metadata_bulk.ui +++ b/src/calibre/gui2/dialogs/metadata_bulk.ui @@ -341,7 +341,7 @@ from the value in the box</string> <number>1</number> </property> <property name="maximum"> - <number>990000</number> + <number>99000000</number> </property> <property name="value"> <number>1</number> @@ -732,6 +732,29 @@ Future conversion of these books will use the default settings.</string> </item> </layout> </item> + <item row="5" column="0"> + <widget class="QLabel" name="s_r_src_ident_label"> + <property name="text"> + <string>Identifier type:</string> + </property> + <property name="buddy"> + <cstring>s_r_src_ident</cstring> + </property> + </widget> + </item> + <item row="5" column="1"> + <widget class="QComboBox" name="s_r_src_ident"> + <property name="sizePolicy"> + <sizepolicy hsizetype="Expanding" vsizetype="Fixed"> + <horstretch>100</horstretch> + <verstretch>0</verstretch> + </sizepolicy> + </property> + <property name="toolTip"> + <string>Choose which identifier type to operate upon</string> + </property> + </widget> + </item> <item row="5" column="0"> <widget class="QLabel" name="template_label"> <property name="text"> @@ -910,7 +933,30 @@ not multiple and the destination field is multiple</string> </item> </layout> </item> - <item row="9" column="1" colspan="2"> + <item row="9" column="0"> + <widget class="QLabel" name="s_r_dst_ident_label"> + <property name="text"> + <string>Identifier type:</string> + </property> + <property name="buddy"> + <cstring>s_r_dst_ident</cstring> + </property> + </widget> + </item> + <item row="9" column="1"> + <widget class="QLineEdit" name="s_r_dst_ident"> + <property name="sizePolicy"> + <sizepolicy hsizetype="Expanding" vsizetype="Fixed"> + <horstretch>100</horstretch> + <verstretch>0</verstretch> + </sizepolicy> + </property> + <property name="toolTip"> + <string>Choose which identifier type to operate upon</string> + </property> + </widget> + </item> + <item row="10" column="1" colspan="2"> <layout class="QHBoxLayout" name="horizontalLayout_21"> <item> <spacer name="HSpacer_347"> @@ -996,7 +1042,7 @@ not multiple and the destination field is multiple</string> </item> </layout> </item> - <item row="10" column="0" colspan="4"> + <item row="11" column="0" colspan="4"> <widget class="QScrollArea" name="scrollArea11"> <property name="frameShape"> <enum>QFrame::NoFrame</enum> @@ -1120,6 +1166,7 @@ not multiple and the destination field is multiple</string> <tabstop>remove_button</tabstop> <tabstop>search_field</tabstop> <tabstop>search_mode</tabstop> + <tabstop>s_r_src_ident</tabstop> <tabstop>s_r_template</tabstop> <tabstop>search_for</tabstop> <tabstop>case_sensitive</tabstop> @@ -1128,6 +1175,7 @@ not multiple and the destination field is multiple</string> <tabstop>destination_field</tabstop> <tabstop>replace_mode</tabstop> <tabstop>comma_separated</tabstop> + <tabstop>s_r_dst_ident</tabstop> <tabstop>results_count</tabstop> <tabstop>starting_from</tabstop> <tabstop>multiple_separator</tabstop> diff --git a/src/calibre/gui2/dialogs/metadata_single.py b/src/calibre/gui2/dialogs/metadata_single.py deleted file mode 100644 index d95c905f42..0000000000 --- a/src/calibre/gui2/dialogs/metadata_single.py +++ /dev/null @@ -1,1029 +0,0 @@ -__license__ = 'GPL v3' -__copyright__ = '2008, Kovid Goyal <kovid at kovidgoyal.net>' - -''' -The dialog used to edit meta information for a book as well as -add/remove formats -''' - -import os, re, time, traceback, textwrap -from functools import partial -from threading import Thread - -from PyQt4.Qt import SIGNAL, QObject, Qt, QTimer, QDate, \ - QPixmap, QListWidgetItem, QDialog, pyqtSignal, QIcon, \ - QPushButton - -from calibre.gui2 import error_dialog, file_icon_provider, dynamic, \ - choose_files, choose_images, ResizableDialog, \ - warning_dialog, question_dialog, UNDEFINED_QDATE -from calibre.gui2.dialogs.metadata_single_ui import Ui_MetadataSingleDialog -from calibre.gui2.dialogs.fetch_metadata import FetchMetadata -from calibre.gui2.dialogs.tag_editor import TagEditor -from calibre.gui2.widgets import ProgressIndicator -from calibre.ebooks import BOOK_EXTENSIONS -from calibre.ebooks.metadata import string_to_authors, \ - authors_to_string, check_isbn, title_sort -from calibre.ebooks.metadata.covers import download_cover -from calibre.ebooks.metadata.meta import get_metadata -from calibre.ebooks.metadata import MetaInformation -from calibre.utils.config import prefs, tweaks -from calibre.utils.date import qt_to_dt, local_tz, utcfromtimestamp -from calibre.utils.icu import sort_key -from calibre.customize.ui import run_plugins_on_import, get_isbndb_key -from calibre.gui2.preferences.social import SocialMetadata -from calibre.gui2.custom_column_widgets import populate_metadata_page -from calibre import strftime -from calibre.library.comments import comments_to_html - -class CoverFetcher(Thread): # {{{ - - def __init__(self, username, password, isbn, timeout, title, author): - Thread.__init__(self) - self.daemon = True - - self.username = username.strip() if username else username - self.password = password.strip() if password else password - self.timeout = timeout - self.isbn = isbn - self.title = title - self.needs_isbn = False - self.author = author - self.exception = self.traceback = self.cover_data = self.errors = None - - def run(self): - try: - au = self.author if self.author else None - mi = MetaInformation(self.title, [au]) - if not self.isbn: - from calibre.ebooks.metadata.fetch import search - if not self.title: - self.needs_isbn = True - return - key = get_isbndb_key() - if not key: - key = None - results = search(title=self.title, author=au, - isbndb_key=key)[0] - results = sorted([x.isbn for x in results if x.isbn], - cmp=lambda x,y:cmp(len(x),len(y)), reverse=True) - if not results: - self.needs_isbn = True - return - self.isbn = results[0] - - mi.isbn = self.isbn - - self.cover_data, self.errors = download_cover(mi, - timeout=self.timeout) - except Exception, e: - self.exception = e - self.traceback = traceback.format_exc() - print self.traceback - -# }}} - -class Format(QListWidgetItem): # {{{ - - def __init__(self, parent, ext, size, path=None, timestamp=None): - self.path = path - self.ext = ext - self.size = float(size)/(1024*1024) - text = '%s (%.2f MB)'%(self.ext.upper(), self.size) - QListWidgetItem.__init__(self, file_icon_provider().icon_from_ext(ext), - text, parent, QListWidgetItem.UserType) - if timestamp is not None: - ts = timestamp.astimezone(local_tz) - t = strftime('%a, %d %b %Y [%H:%M:%S]', ts.timetuple()) - text = _('Last modified: %s')%t - self.setToolTip(text) - self.setStatusTip(text) - -# }}} - - -class MetadataSingleDialog(ResizableDialog, Ui_MetadataSingleDialog): - - COVER_FETCH_TIMEOUT = 240 # seconds - view_format = pyqtSignal(object) - - # Cover processing {{{ - - def set_cover(self): - mi, ext = self.get_selected_format_metadata() - if mi is None: - return - cdata = None - if mi.cover and os.access(mi.cover, os.R_OK): - cdata = open(mi.cover).read() - elif mi.cover_data[1] is not None: - cdata = mi.cover_data[1] - if cdata is None: - error_dialog(self, _('Could not read cover'), - _('Could not read cover from %s format')%ext).exec_() - return - pix = QPixmap() - pix.loadFromData(cdata) - if pix.isNull(): - error_dialog(self, _('Could not read cover'), - _('The cover in the %s format is invalid')%ext).exec_() - return - self.cover.setPixmap(pix) - self.update_cover_tooltip() - self.cover_changed = True - self.cpixmap = pix - self.cover_data = cdata - - def trim_cover(self, *args): - from calibre.utils.magick import Image - cdata = self.cover_data - if not cdata: - return - im = Image() - im.load(cdata) - im.trim(10) - cdata = im.export('png') - pix = QPixmap() - pix.loadFromData(cdata) - self.cover.setPixmap(pix) - self.update_cover_tooltip() - self.cover_changed = True - self.cpixmap = pix - self.cover_data = cdata - - - - def update_cover_tooltip(self): - p = self.cover.pixmap() - self.cover.setToolTip(_('Cover size: %dx%d pixels') % - (p.width(), p.height())) - - - def do_reset_cover(self, *args): - pix = QPixmap(I('default_cover.png')) - self.cover.setPixmap(pix) - self.update_cover_tooltip() - self.cover_changed = True - self.cover_data = None - - def select_cover(self, checked): - files = choose_images(self, 'change cover dialog', - _('Choose cover for ') + unicode(self.title.text())) - if not files: - return - _file = files[0] - if _file: - _file = os.path.abspath(_file) - if not os.access(_file, os.R_OK): - d = error_dialog(self, _('Cannot read'), - _('You do not have permission to read the file: ') + _file) - d.exec_() - return - cf, cover = None, None - try: - cf = open(_file, "rb") - cover = cf.read() - except IOError, e: - d = error_dialog(self, _('Error reading file'), - _("<p>There was an error reading from file: <br /><b>") + _file + "</b></p><br />"+str(e)) - d.exec_() - if cover: - pix = QPixmap() - pix.loadFromData(cover) - if pix.isNull(): - d = error_dialog(self, - _("Not a valid picture"), - _file + _(" is not a valid picture")) - d.exec_() - else: - self.cover.setPixmap(pix) - self.update_cover_tooltip() - self.cover_changed = True - self.cpixmap = pix - self.cover_data = cover - - def generate_cover(self, *args): - from calibre.ebooks import calibre_cover - from calibre.ebooks.metadata import fmt_sidx - from calibre.gui2 import config - title = unicode(self.title.text()).strip() - author = unicode(self.authors.text()).strip() - if author.endswith('&'): - author = author[:-1].strip() - if not title or not author: - return error_dialog(self, _('Specify title and author'), - _('You must specify a title and author before generating ' - 'a cover'), show=True) - series = unicode(self.series.text()).strip() - series_string = None - if series: - series_string = _('Book %s of %s')%( - fmt_sidx(self.series_index.value(), - use_roman=config['use_roman_numerals_for_series_number']), series) - self.cover_data = calibre_cover(title, author, - series_string=series_string) - pix = QPixmap() - pix.loadFromData(self.cover_data) - self.cover.setPixmap(pix) - self.update_cover_tooltip() - self.cover_changed = True - self.cpixmap = pix - - def cover_dropped(self, cover_data): - self.cover_changed = True - self.cover_data = cover_data - self.update_cover_tooltip() - - def fetch_cover(self): - isbn = re.sub(r'[^0-9a-zA-Z]', '', unicode(self.isbn.text())).strip() - self.fetch_cover_button.setEnabled(False) - self.setCursor(Qt.WaitCursor) - title, author = map(unicode, (self.title.text(), self.authors.text())) - self.cover_fetcher = CoverFetcher(None, None, isbn, - self.timeout, title, author) - self.cover_fetcher.start() - self.cf_start_time = time.time() - self.pi.start(_('Downloading cover...')) - QTimer.singleShot(100, self.hangcheck) - - def hangcheck(self): - cf = self.cover_fetcher - if cf is None: - # Called after dialog closed - return - - if cf.is_alive() and \ - time.time()-self.cf_start_time < self.COVER_FETCH_TIMEOUT: - QTimer.singleShot(100, self.hangcheck) - return - - try: - if cf.is_alive(): - error_dialog(self, _('Cannot fetch cover'), - _('<b>Could not fetch cover.</b><br/>')+ - _('The download timed out.')).exec_() - return - if cf.needs_isbn: - error_dialog(self, _('Cannot fetch cover'), - _('Could not find cover for this book. Try ' - 'specifying the ISBN first.')).exec_() - return - if cf.exception is not None: - err = cf.exception - error_dialog(self, _('Cannot fetch cover'), - _('<b>Could not fetch cover.</b><br/>')+unicode(err)).exec_() - return - if cf.errors and cf.cover_data is None: - details = u'\n\n'.join([e[-1] + ': ' + e[1] for e in cf.errors]) - error_dialog(self, _('Cannot fetch cover'), - _('<b>Could not fetch cover.</b><br/>') + - _('For the error message from each cover source, ' - 'click Show details below.'), det_msg=details, show=True) - return - - pix = QPixmap() - pix.loadFromData(cf.cover_data) - if pix.isNull(): - error_dialog(self, _('Bad cover'), - _('The cover is not a valid picture')).exec_() - else: - self.cover.setPixmap(pix) - self.update_cover_tooltip() - self.cover_changed = True - self.cpixmap = pix - self.cover_data = cf.cover_data - finally: - self.fetch_cover_button.setEnabled(True) - self.unsetCursor() - if self.pi is not None: - self.pi.stop() - - - # }}} - - # Formats processing {{{ - def add_format(self, x): - files = choose_files(self, 'add formats dialog', - _("Choose formats for ") + unicode((self.title.text())), - [(_('Books'), BOOK_EXTENSIONS)]) - self._add_formats(files) - - def _add_formats(self, paths): - added = False - if not paths: - return added - bad_perms = [] - for _file in paths: - _file = os.path.abspath(_file) - if not os.access(_file, os.R_OK): - bad_perms.append(_file) - continue - - nfile = run_plugins_on_import(_file) - if nfile is not None: - _file = nfile - stat = os.stat(_file) - size = stat.st_size - ext = os.path.splitext(_file)[1].lower().replace('.', '') - timestamp = utcfromtimestamp(stat.st_mtime) - for row in range(self.formats.count()): - fmt = self.formats.item(row) - if fmt.ext.lower() == ext: - self.formats.takeItem(row) - break - Format(self.formats, ext, size, path=_file, timestamp=timestamp) - self.formats_changed = True - added = True - if bad_perms: - error_dialog(self, _('No permission'), - _('You do not have ' - 'permission to read the following files:'), - det_msg='\n'.join(bad_perms), show=True) - - return added - - def formats_dropped(self, event, paths): - if self._add_formats(paths): - event.accept() - - def remove_format(self, *args): - rows = self.formats.selectionModel().selectedRows(0) - for row in rows: - self.formats.takeItem(row.row()) - self.formats_changed = True - - def get_selected_format_metadata(self): - old = prefs['read_file_metadata'] - if not old: - prefs['read_file_metadata'] = True - try: - row = self.formats.currentRow() - fmt = self.formats.item(row) - if fmt is None: - if self.formats.count() == 1: - fmt = self.formats.item(0) - if fmt is None: - error_dialog(self, _('No format selected'), - _('No format selected')).exec_() - return None, None - ext = fmt.ext.lower() - if fmt.path is None: - stream = self.db.format(self.row, ext, as_file=True) - else: - stream = open(fmt.path, 'r+b') - try: - mi = get_metadata(stream, ext) - return mi, ext - except: - error_dialog(self, _('Could not read metadata'), - _('Could not read metadata from %s format')%ext).exec_() - return None, None - finally: - if old != prefs['read_file_metadata']: - prefs['read_file_metadata'] = old - - def set_metadata_from_format(self): - mi, ext = self.get_selected_format_metadata() - if mi is None: - return - if mi.title: - self.title.setText(mi.title) - if mi.authors: - self.authors.setEditText(authors_to_string(mi.authors)) - if mi.author_sort: - self.author_sort.setText(mi.author_sort) - if mi.rating is not None: - try: - self.rating.setValue(mi.rating) - except: - pass - if mi.publisher: - self.publisher.setEditText(mi.publisher) - if mi.tags: - self.tags.setText(', '.join(mi.tags)) - if mi.isbn: - self.isbn.setText(mi.isbn) - if mi.pubdate: - self.pubdate.setDate(QDate(mi.pubdate.year, mi.pubdate.month, - mi.pubdate.day)) - if mi.series and mi.series.strip(): - self.series.setEditText(mi.series) - if mi.series_index is not None: - self.series_index.setValue(float(mi.series_index)) - if mi.comments and mi.comments.strip(): - comments = comments_to_html(mi.comments) - self.comments.html = comments - - - def sync_formats(self): - old_extensions, new_extensions, paths = set(), set(), {} - for row in range(self.formats.count()): - fmt = self.formats.item(row) - ext, path = fmt.ext.lower(), fmt.path - if 'unknown' in ext.lower(): - ext = None - if path: - new_extensions.add(ext) - paths[ext] = path - else: - old_extensions.add(ext) - for ext in new_extensions: - self.db.add_format(self.row, ext, open(paths[ext], 'rb'), notify=False) - dbfmts = self.db.formats(self.row) - db_extensions = set([f.lower() for f in (dbfmts.split(',') if dbfmts - else [])]) - extensions = new_extensions.union(old_extensions) - for ext in db_extensions: - if ext not in extensions and ext in self.original_formats: - self.db.remove_format(self.row, ext, notify=False) - - def show_format(self, item, *args): - fmt = item.ext - self.view_format.emit(fmt) - - # }}} - - def __init__(self, window, row, db, prev=None, - next_=None): - ResizableDialog.__init__(self, window) - self.cover_fetcher = None - self.bc_box.layout().setAlignment(self.cover, Qt.AlignCenter|Qt.AlignHCenter) - base = unicode(self.author_sort.toolTip()) - ok_tooltip = '<p>' + textwrap.fill(base+'<br><br>'+ - _(' The green color indicates that the current ' - 'author sort matches the current author')) - bad_tooltip = '<p>'+textwrap.fill(base + '<br><br>'+ - _(' The red color indicates that the current ' - 'author sort does not match the current author. ' - 'No action is required if this is what you want.')) - self.aus_tooltips = (ok_tooltip, bad_tooltip) - - base = unicode(self.title_sort.toolTip()) - ok_tooltip = '<p>' + textwrap.fill(base+'<br><br>'+ - _(' The green color indicates that the current ' - 'title sort matches the current title')) - bad_tooltip = '<p>'+textwrap.fill(base + '<br><br>'+ - _(' The red color warns that the current ' - 'title sort does not match the current title. ' - 'No action is required if this is what you want.')) - self.ts_tooltips = (ok_tooltip, bad_tooltip) - self.row_delta = 0 - if prev: - self.prev_button = QPushButton(QIcon(I('back.png')), _('Previous'), - self) - self.button_box.addButton(self.prev_button, self.button_box.ActionRole) - tip = _('Save changes and edit the metadata of %s')%prev - self.prev_button.setToolTip(tip) - self.prev_button.clicked.connect(partial(self.next_triggered, - -1)) - if next_: - self.next_button = QPushButton(QIcon(I('forward.png')), _('Next'), - self) - self.button_box.addButton(self.next_button, self.button_box.ActionRole) - tip = _('Save changes and edit the metadata of %s')%next_ - self.next_button.setToolTip(tip) - self.next_button.clicked.connect(partial(self.next_triggered, 1)) - - self.splitter.setStretchFactor(100, 1) - self.read_state() - self.db = db - self.pi = ProgressIndicator(self) - self.id = db.id(row) - self.row = row - self.cover_data = None - self.formats_changed = False - self.formats.setAcceptDrops(True) - self.cover_changed = False - self.cpixmap = None - self.pubdate.setMinimumDate(UNDEFINED_QDATE) - pubdate_format = tweaks['gui_pubdate_display_format'] - if pubdate_format is not None: - self.pubdate.setDisplayFormat(pubdate_format) - self.date.setMinimumDate(UNDEFINED_QDATE) - self.pubdate.setSpecialValueText(_('Undefined')) - self.date.setSpecialValueText(_('Undefined')) - self.clear_pubdate_button.clicked.connect(self.clear_pubdate) - - - self.connect(self.cover, SIGNAL('cover_changed(PyQt_PyObject)'), self.cover_dropped) - QObject.connect(self.cover_button, SIGNAL("clicked(bool)"), \ - self.select_cover) - QObject.connect(self.add_format_button, SIGNAL("clicked(bool)"), \ - self.add_format) - self.connect(self.formats, - SIGNAL('formats_dropped(PyQt_PyObject,PyQt_PyObject)'), - self.formats_dropped) - QObject.connect(self.remove_format_button, SIGNAL("clicked(bool)"), \ - self.remove_format) - QObject.connect(self.fetch_metadata_button, SIGNAL('clicked()'), - self.fetch_metadata) - - QObject.connect(self.fetch_cover_button, SIGNAL('clicked()'), - self.fetch_cover) - QObject.connect(self.tag_editor_button, SIGNAL('clicked()'), - self.edit_tags) - QObject.connect(self.remove_series_button, SIGNAL('clicked()'), - self.remove_unused_series) - QObject.connect(self.auto_author_sort, SIGNAL('clicked()'), - self.deduce_author_sort) - QObject.connect(self.auto_title_sort, SIGNAL('clicked()'), - self.deduce_title_sort) - self.trim_cover_button.clicked.connect(self.trim_cover) - self.connect(self.title_sort, SIGNAL('textChanged(const QString&)'), - self.title_sort_box_changed) - self.connect(self.title, SIGNAL('textChanged(const QString&)'), - self.title_box_changed) - self.connect(self.author_sort, SIGNAL('textChanged(const QString&)'), - self.author_sort_box_changed) - self.connect(self.authors, SIGNAL('editTextChanged(const QString&)'), - self.authors_box_changed) - self.connect(self.formats, SIGNAL('itemDoubleClicked(QListWidgetItem*)'), - self.show_format) - self.connect(self.formats, SIGNAL('delete_format()'), self.remove_format) - self.connect(self.button_set_cover, SIGNAL('clicked()'), self.set_cover) - self.connect(self.button_set_metadata, SIGNAL('clicked()'), - self.set_metadata_from_format) - self.connect(self.reset_cover, SIGNAL('clicked()'), self.do_reset_cover) - self.connect(self.swap_button, SIGNAL('clicked()'), self.swap_title_author) - self.timeout = float(prefs['network_timeout']) - - - self.title.setText(db.title(row)) - self.title_sort.setText(db.title_sort(row)) - isbn = db.isbn(self.id, index_is_id=True) - if not isbn: - isbn = '' - self.isbn.textChanged.connect(self.validate_isbn) - self.isbn.setText(isbn) - aus = self.db.author_sort(row) - self.author_sort.setText(aus if aus else '') - tags = self.db.tags(row) - self.original_tags = ', '.join(tags.split(',')) if tags else '' - self.tags.setText(self.original_tags) - self.tags.update_items_cache(self.db.all_tags()) - rating = self.db.rating(row) - if rating > 0: - self.rating.setValue(int(rating/2.)) - comments = self.db.comments(row) - if comments and comments.strip(): - comments = comments_to_html(comments) - self.comments.html = comments - cover = self.db.cover(row) - pubdate = db.pubdate(self.id, index_is_id=True) - self.pubdate.setDate(QDate(pubdate.year, pubdate.month, - pubdate.day)) - timestamp = db.timestamp(self.id, index_is_id=True) - self.date.setDate(QDate(timestamp.year, timestamp.month, - timestamp.day)) - self.orig_date = qt_to_dt(self.date.date()) - - exts = self.db.formats(row) - self.original_formats = [] - if exts: - exts = exts.split(',') - for ext in exts: - if not ext: - ext = '' - size = self.db.sizeof_format(row, ext) - timestamp = self.db.format_last_modified(self.id, ext) - if size is None: - continue - Format(self.formats, ext, size, timestamp=timestamp) - self.original_formats.append(ext.lower()) - - - self.initialize_combos() - si = self.db.series_index(row) - if si is None: - si = 1.0 - try: - self.series_index.setValue(float(si)) - except: - self.series_index.setValue(1.0) - QObject.connect(self.series, SIGNAL('currentIndexChanged(int)'), self.enable_series_index) - QObject.connect(self.series, SIGNAL('editTextChanged(QString)'), self.enable_series_index) - self.series.lineEdit().editingFinished.connect(self.increment_series_index) - - pm = QPixmap() - if cover: - pm.loadFromData(cover) - if pm.isNull(): - pm = QPixmap(I('default_cover.png')) - else: - self.cover_data = cover - self.cover.setPixmap(pm) - self.update_cover_tooltip() - self.original_series_name = unicode(self.series.text()).strip() - if len(db.custom_column_label_map) == 0: - self.central_widget.tabBar().setVisible(False) - self.central_widget.setTabEnabled(1, False) - else: - self.create_custom_column_editors() - self.generate_cover_button.clicked.connect(self.generate_cover) - - self.original_author = unicode(self.authors.text()).strip() - self.original_title = unicode(self.title.text()).strip() - self.books_to_refresh = set() - - self.show() - - def clear_pubdate(self, *args): - self.pubdate.setDate(UNDEFINED_QDATE) - - def create_custom_column_editors(self): - w = self.central_widget.widget(1) - layout = w.layout() - self.custom_column_widgets, self.__cc_spacers = \ - populate_metadata_page(layout, self.db, self.id, parent=w, bulk=False, - two_column=tweaks['metadata_single_use_2_cols_for_custom_fields']) - self.__custom_col_layouts = [layout] - ans = self.custom_column_widgets - for i in range(len(ans)-1): - if len(ans[i+1].widgets) == 2: - w.setTabOrder(ans[i].widgets[-1], ans[i+1].widgets[1]) - else: - w.setTabOrder(ans[i].widgets[-1], ans[i+1].widgets[0]) - for c in range(2, len(ans[i].widgets), 2): - w.setTabOrder(ans[i].widgets[c-1], ans[i].widgets[c+1]) - - def title_box_changed(self, txt): - ts = unicode(txt) - ts = title_sort(ts) - self.mark_box_as_ok(control = self.title_sort, tt=self.ts_tooltips, - normal=(unicode(self.title_sort.text()) == ts)) - - def title_sort_box_changed(self, txt): - ts = unicode(txt) - self.mark_box_as_ok(control = self.title_sort, tt=self.ts_tooltips, - normal=(title_sort(unicode(self.title.text())) == ts)) - - def authors_box_changed(self, txt): - aus = unicode(txt) - aus = re.sub(r'\s+et al\.$', '', aus) - aus = self.db.author_sort_from_authors(string_to_authors(aus)) - self.mark_box_as_ok(control = self.author_sort, tt=self.aus_tooltips, - normal=(unicode(self.author_sort.text()) == aus)) - - def author_sort_box_changed(self, txt): - au = unicode(self.authors.text()) - au = re.sub(r'\s+et al\.$', '', au) - au = self.db.author_sort_from_authors(string_to_authors(au)) - self.mark_box_as_ok(control = self.author_sort, tt=self.aus_tooltips, - normal=(au == txt)) - - def mark_box_as_ok(self, control, tt, normal=True): - if normal: - col = 'rgb(0, 255, 0, 20%)' - else: - col = 'rgb(255, 0, 0, 20%)' - control.setStyleSheet('QLineEdit { color: black; ' - 'background-color: %s; }'%col) - tt = tt[0] if normal else tt[1] - control.setToolTip(tt) - - def validate_isbn(self, isbn): - isbn = unicode(isbn).strip() - if not isbn: - self.isbn.setStyleSheet('QLineEdit { background-color: rgba(0,255,0,0%) }') - self.isbn.setToolTip(_('This ISBN number is valid')) - return - - if check_isbn(isbn): - self.isbn.setStyleSheet('QLineEdit { background-color: rgba(0,255,0,20%) }') - self.isbn.setToolTip(_('This ISBN number is valid')) - else: - self.isbn.setStyleSheet('QLineEdit { background-color: rgba(255,0,0,20%) }') - self.isbn.setToolTip(_('This ISBN number is invalid')) - - def deduce_author_sort(self): - au = unicode(self.authors.text()) - au = re.sub(r'\s+et al\.$', '', au) - authors = string_to_authors(au) - self.author_sort.setText(self.db.author_sort_from_authors(authors)) - - def deduce_title_sort(self): - ts = unicode(self.title.text()) - self.title_sort.setText(title_sort(ts)) - - def swap_title_author(self): - title = self.title.text() - self.title.setText(self.authors.text()) - self.authors.setText(title) - self.deduce_author_sort() - self.deduce_title_sort() - - def initialize_combos(self): - self.initalize_authors() - self.initialize_series() - self.initialize_publisher() - - self.layout().activate() - - def initalize_authors(self): - all_authors = self.db.all_authors() - all_authors.sort(key=lambda x : sort_key(x[1])) - for i in all_authors: - id, name = i - name = [name.strip().replace('|', ',') for n in name.split(',')] - self.authors.addItem(authors_to_string(name)) - - au = self.db.authors(self.row) - if not au: - au = _('Unknown') - au = ' & '.join([a.strip().replace('|', ',') for a in au.split(',')]) - self.authors.setEditText(au) - - self.authors.set_separator('&') - self.authors.set_space_before_sep(True) - self.authors.set_add_separator(tweaks['authors_completer_append_separator']) - self.authors.update_items_cache(self.db.all_author_names()) - - def initialize_series(self): - self.series.setSizeAdjustPolicy(self.series.AdjustToContentsOnFirstShow) - all_series = self.db.all_series() - all_series.sort(key=lambda x : sort_key(x[1])) - self.series.set_separator(None) - self.series.update_items_cache([x[1] for x in all_series]) - series_id = self.db.series_id(self.row) - idx, c = None, 0 - for i in all_series: - id, name = i - if id == series_id: - idx = c - self.series.addItem(name) - c += 1 - - self.series.lineEdit().setText('') - if idx is not None: - self.series.setCurrentIndex(idx) - self.enable_series_index() - - def initialize_publisher(self): - all_publishers = self.db.all_publishers() - all_publishers.sort(key=lambda x : sort_key(x[1])) - self.publisher.set_separator(None) - self.publisher.update_items_cache([x[1] for x in all_publishers]) - publisher_id = self.db.publisher_id(self.row) - idx, c = None, 0 - for i in all_publishers: - id, name = i - if id == publisher_id: - idx = c - self.publisher.addItem(name) - c += 1 - - self.publisher.setEditText('') - if idx is not None: - self.publisher.setCurrentIndex(idx) - - def edit_tags(self): - if self.tags.text() != self.original_tags: - if question_dialog(self, _('Tags changed'), - _('You have changed the tags. In order to use the tags' - ' editor, you must either discard or apply these ' - 'changes. Apply changes?'), show_copy_button=False): - self.books_to_refresh |= self.apply_tags(commit=True, - notify=True) - self.original_tags = unicode(self.tags.text()) - else: - self.tags.setText(self.original_tags) - d = TagEditor(self, self.db, self.id) - d.exec_() - if d.result() == QDialog.Accepted: - tag_string = ', '.join(d.tags) - self.tags.setText(tag_string) - self.tags.update_items_cache(self.db.all_tags()) - - - def fetch_metadata(self): - isbn = re.sub(r'[^0-9a-zA-Z]', '', unicode(self.isbn.text())) - title = unicode(self.title.text()) - try: - author = string_to_authors(unicode(self.authors.text()))[0] - except: - author = '' - publisher = unicode(self.publisher.currentText()) - if isbn or title or author or publisher: - d = FetchMetadata(self, isbn, title, author, publisher, self.timeout) - self._fetch_metadata_scope = d - with d: - if d.exec_() == QDialog.Accepted: - book = d.selected_book() - if book: - if d.opt_get_social_metadata.isChecked(): - d2 = SocialMetadata(book, self) - d2.exec_() - if d2.timed_out: - warning_dialog(self, _('Timed out'), - _('The download of social' - ' metadata timed out, the servers are' - ' probably busy. Try again later.'), - show=True) - elif d2.exceptions: - det = '\n'.join([x[0]+'\n\n'+x[-1]+'\n\n\n' for - x in d2.exceptions]) - warning_dialog(self, _('There were errors'), - _('There were errors downloading social metadata'), - det_msg=det, show=True) - else: - book.tags = [] - if d.opt_overwrite_author_title_metadata.isChecked(): - self.title.setText(book.title) - self.authors.setText(authors_to_string(book.authors)) - if book.author_sort: self.author_sort.setText(book.author_sort) - if book.publisher: self.publisher.setEditText(book.publisher) - if book.isbn: self.isbn.setText(book.isbn) - if book.pubdate: - dt = book.pubdate - self.pubdate.setDate(QDate(dt.year, dt.month, dt.day)) - summ = book.comments - if summ: - prefix = self.comments.html - if prefix: - prefix += '\n' - self.comments.html = prefix + comments_to_html(summ) - if book.rating is not None: - self.rating.setValue(int(book.rating)) - if book.tags: - self.tags.setText(', '.join(book.tags)) - if book.series is not None: - if self.series.text() is None or self.series.text() == '': - self.series.setText(book.series) - if book.series_index is not None: - self.series_index.setValue(book.series_index) - if book.has_cover: - if d.opt_auto_download_cover.isChecked(): - self.fetch_cover() - else: - self.fetch_cover_button.setFocus(Qt.OtherFocusReason) - else: - error_dialog(self, _('Cannot fetch metadata'), - _('You must specify at least one of ISBN, Title, ' - 'Authors or Publisher'), show=True) - self.title.setFocus(Qt.OtherFocusReason) - - def enable_series_index(self, *args): - self.series_index.setEnabled(True) - - def increment_series_index(self): - if self.db is not None: - try: - series = unicode(self.series.text()).strip() - if series and series != self.original_series_name: - ns = 1 - if tweaks['series_index_auto_increment'] != 'const': - ns = self.db.get_next_series_num_for(series) - self.series_index.setValue(ns) - self.original_series_name = series - except: - traceback.print_exc() - - def remove_unused_series(self): - self.db.remove_unused_series() - idx = unicode(self.series.currentText()) - self.series.clear() - self.initialize_series() - if idx: - for i in range(self.series.count()): - if unicode(self.series.itemText(i)) == idx: - self.series.setCurrentIndex(i) - break - - def apply_tags(self, commit=False, notify=False): - return self.db.set_tags(self.id, [x.strip() for x in - unicode(self.tags.text()).split(',')], - notify=notify, commit=commit, allow_case_change=True) - - def next_triggered(self, row_delta, *args): - self.row_delta = row_delta - self.accept() - - def accept(self): - try: - if self.formats_changed: - self.sync_formats() - title = unicode(self.title.text()).strip() - if title != self.original_title: - self.db.set_title(self.id, title, notify=False) - # This must be after setting the title because of the DB update trigger - ts = unicode(self.title_sort.text()).strip() - if ts: - self.db.set_title_sort(self.id, ts, notify=False, commit=False) - au = unicode(self.authors.text()).strip() - if au and au != self.original_author: - self.books_to_refresh |= self.db.set_authors(self.id, - string_to_authors(au), - notify=False, - allow_case_change=True) - aus = unicode(self.author_sort.text()).strip() - if aus: - self.db.set_author_sort(self.id, aus, notify=False, commit=False) - self.db.set_isbn(self.id, - re.sub(r'[^0-9a-zA-Z]', '', - unicode(self.isbn.text()).strip()), - notify=False, commit=False) - self.db.set_rating(self.id, 2*self.rating.value(), notify=False, - commit=False) - self.books_to_refresh |= self.apply_tags() - self.books_to_refresh |= self.db.set_publisher(self.id, - unicode(self.publisher.currentText()).strip(), - notify=False, commit=False, allow_case_change=True) - self.books_to_refresh |= self.db.set_series(self.id, - unicode(self.series.currentText()).strip(), notify=False, - commit=False, allow_case_change=True) - self.db.set_series_index(self.id, self.series_index.value(), - notify=False, commit=False) - self.db.set_comment(self.id, - self.comments.html, - notify=False, commit=False) - d = self.pubdate.date() - d = qt_to_dt(d) - self.db.set_pubdate(self.id, d, notify=False, commit=False) - d = self.date.date() - d = qt_to_dt(d) - if d != self.orig_date: - self.db.set_timestamp(self.id, d, notify=False, commit=False) - self.db.commit() - - if self.cover_changed: - if self.cover_data is not None: - self.db.set_cover(self.id, self.cover_data) - else: - self.db.remove_cover(self.id) - for w in getattr(self, 'custom_column_widgets', []): - self.books_to_refresh |= w.commit(self.id) - self.db.commit() - except (IOError, OSError) as err: - if getattr(err, 'errno', -1) == 13: # Permission denied - fname = err.filename if err.filename else 'file' - return error_dialog(self, _('Permission denied'), - _('Could not open %s. Is it being used by another' - ' program?')%fname, det_msg=traceback.format_exc(), - show=True) - raise - self.save_state() - self.cover_fetcher = None - QDialog.accept(self) - - def reject(self, *args): - self.save_state() - self.cover_fetcher = None - QDialog.reject(self, *args) - - def read_state(self): - wg = dynamic.get('metasingle_window_geometry2', None) - ss = dynamic.get('metasingle_splitter_state2', None) - if wg is not None: - self.restoreGeometry(wg) - if ss is not None: - self.splitter.restoreState(ss) - - def save_state(self): - dynamic.set('metasingle_window_geometry2', bytes(self.saveGeometry())) - dynamic.set('metasingle_splitter_state2', - bytes(self.splitter.saveState())) - - def break_cycles(self): - # Break any reference cycles that could prevent python - # from garbage collecting this dialog - def disconnect(signal): - try: - signal.disconnect() - except: - pass # Fails if view format was never connected - disconnect(self.view_format) - for b in ('next_button', 'prev_button'): - x = getattr(self, b, None) - if x is not None: - disconnect(x.clicked) - -if __name__ == '__main__': - from calibre.library import db - from PyQt4.Qt import QApplication - from calibre.utils.mem import memory - import gc - - - app = QApplication([]) - db = db() - - # Initialize all Qt Objects once - d = MetadataSingleDialog(None, 4, db) - d.break_cycles() - d.reject() - del d - - for i in range(5): - gc.collect() - before = memory() - - d = MetadataSingleDialog(None, 4, db) - d.reject() - d.break_cycles() - del d - - for i in range(5): - gc.collect() - print 'Used memory:', memory(before)/1024.**2, 'MB' - - diff --git a/src/calibre/gui2/dialogs/metadata_single.ui b/src/calibre/gui2/dialogs/metadata_single.ui deleted file mode 100644 index 5bcf268aaa..0000000000 --- a/src/calibre/gui2/dialogs/metadata_single.ui +++ /dev/null @@ -1,937 +0,0 @@ -<?xml version="1.0" encoding="UTF-8"?> -<ui version="4.0"> - <class>MetadataSingleDialog</class> - <widget class="QDialog" name="MetadataSingleDialog"> - <property name="geometry"> - <rect> - <x>0</x> - <y>0</y> - <width>994</width> - <height>716</height> - </rect> - </property> - <property name="sizePolicy"> - <sizepolicy hsizetype="MinimumExpanding" vsizetype="MinimumExpanding"> - <horstretch>0</horstretch> - <verstretch>0</verstretch> - </sizepolicy> - </property> - <property name="windowTitle"> - <string>Edit Meta Information</string> - </property> - <property name="windowIcon"> - <iconset resource="../../../../resources/images.qrc"> - <normaloff>:/images/edit_input.png</normaloff>:/images/edit_input.png</iconset> - </property> - <property name="sizeGripEnabled"> - <bool>true</bool> - </property> - <property name="modal"> - <bool>true</bool> - </property> - <layout class="QVBoxLayout" name="verticalLayout_6"> - <item> - <widget class="QScrollArea" name="scrollArea"> - <property name="frameShape"> - <enum>QFrame::NoFrame</enum> - </property> - <property name="widgetResizable"> - <bool>true</bool> - </property> - <widget class="QWidget" name="scrollAreaWidgetContents"> - <property name="geometry"> - <rect> - <x>0</x> - <y>0</y> - <width>986</width> - <height>677</height> - </rect> - </property> - <layout class="QVBoxLayout" name="verticalLayout_5"> - <property name="margin"> - <number>0</number> - </property> - <item> - <widget class="QTabWidget" name="central_widget"> - <property name="minimumSize"> - <size> - <width>800</width> - <height>665</height> - </size> - </property> - <property name="currentIndex"> - <number>0</number> - </property> - <widget class="QWidget" name="central_tabWidgetPage1"> - <attribute name="title"> - <string>&Basic metadata</string> - </attribute> - <layout class="QGridLayout" name="gridLayout_5"> - <item row="0" column="0"> - <widget class="QSplitter" name="splitter"> - <property name="orientation"> - <enum>Qt::Horizontal</enum> - </property> - <widget class="QWidget" name="layoutWidget"> - <layout class="QVBoxLayout"> - <item> - <widget class="QGroupBox" name="meta_box"> - <property name="title"> - <string>Meta information</string> - </property> - <layout class="QGridLayout" name="gridLayout_3"> - <item row="0" column="0"> - <widget class="QLabel" name="label"> - <property name="text"> - <string>&Title: </string> - </property> - <property name="alignment"> - <set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set> - </property> - <property name="buddy"> - <cstring>title</cstring> - </property> - </widget> - </item> - <item row="0" column="1"> - <widget class="EnLineEdit" name="title"> - <property name="toolTip"> - <string>Change the title of this book</string> - </property> - </widget> - </item> - <item row="0" column="2" rowspan="4"> - <layout class="QVBoxLayout" name="verticalLayout_7"> - <item> - <spacer name="verticalSpacer_3"> - <property name="orientation"> - <enum>Qt::Vertical</enum> - </property> - <property name="sizeHint" stdset="0"> - <size> - <width>20</width> - <height>40</height> - </size> - </property> - </spacer> - </item> - <item> - <widget class="QToolButton" name="auto_title_sort"> - <property name="toolTip"> - <string>Automatically create the title sort entry based on the current title entry. -Using this button to create title sort will change title sort from red to green.</string> - </property> - <property name="text"> - <string>...</string> - </property> - <property name="icon"> - <iconset resource="../../../../resources/images.qrc"> - <normaloff>:/images/auto_author_sort.png</normaloff>:/images/auto_author_sort.png</iconset> - </property> - </widget> - </item> - <item> - <spacer name="verticalSpacer"> - <property name="orientation"> - <enum>Qt::Vertical</enum> - </property> - <property name="sizeHint" stdset="0"> - <size> - <width>20</width> - <height>40</height> - </size> - </property> - </spacer> - </item> - <item> - <widget class="QToolButton" name="swap_button"> - <property name="toolTip"> - <string>Swap the author and title</string> - </property> - <property name="text"> - <string>...</string> - </property> - <property name="icon"> - <iconset resource="../../../../resources/images.qrc"> - <normaloff>:/images/swap.png</normaloff>:/images/swap.png</iconset> - </property> - <property name="iconSize"> - <size> - <width>16</width> - <height>16</height> - </size> - </property> - </widget> - </item> - <item> - <spacer name="verticalSpacer_2"> - <property name="orientation"> - <enum>Qt::Vertical</enum> - </property> - <property name="sizeHint" stdset="0"> - <size> - <width>20</width> - <height>40</height> - </size> - </property> - </spacer> - </item> - <item> - <widget class="QToolButton" name="auto_author_sort"> - <property name="toolTip"> - <string>Automatically create the author sort entry based on the current author entry. -Using this button to create author sort will change author sort from red to green.</string> - </property> - <property name="text"> - <string>...</string> - </property> - <property name="icon"> - <iconset resource="../../../../resources/images.qrc"> - <normaloff>:/images/auto_author_sort.png</normaloff>:/images/auto_author_sort.png</iconset> - </property> - </widget> - </item> - <item> - <spacer name="verticalSpacer_4"> - <property name="orientation"> - <enum>Qt::Vertical</enum> - </property> - <property name="sizeHint" stdset="0"> - <size> - <width>20</width> - <height>40</height> - </size> - </property> - </spacer> - </item> - </layout> - </item> - <item row="1" column="0"> - <widget class="QLabel" name="label"> - <property name="text"> - <string>Title &sort: </string> - </property> - <property name="alignment"> - <set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set> - </property> - <property name="buddy"> - <cstring>title_sort</cstring> - </property> - </widget> - </item> - <item row="1" column="1"> - <widget class="EnLineEdit" name="title_sort"> - <property name="toolTip"> - <string>Specify how this book should be sorted when by title. For example, The Exorcist might be sorted as Exorcist, The.</string> - </property> - </widget> - </item> - <item row="2" column="0"> - <widget class="QLabel" name="label_2"> - <property name="text"> - <string>&Author(s): </string> - </property> - <property name="alignment"> - <set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set> - </property> - <property name="buddy"> - <cstring>authors</cstring> - </property> - </widget> - </item> - <item row="2" column="1"> - <widget class="MultiCompleteComboBox" name="authors"> - <property name="editable"> - <bool>true</bool> - </property> - </widget> - </item> - <item row="3" column="0"> - <widget class="QLabel" name="label_8"> - <property name="text"> - <string>Author S&ort: </string> - </property> - <property name="alignment"> - <set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set> - </property> - <property name="buddy"> - <cstring>author_sort</cstring> - </property> - </widget> - </item> - <item row="3" column="1"> - <widget class="EnLineEdit" name="author_sort"> - <property name="toolTip"> - <string>Specify how the author(s) of this book should be sorted. For example Charles Dickens should be sorted as Dickens, Charles. -If the box is colored green, then text matches the individual author's sort strings. If it is colored red, then the authors and this text do not match.</string> - </property> - </widget> - </item> - <item row="4" column="0"> - <widget class="QLabel" name="label_6"> - <property name="text"> - <string>&Rating:</string> - </property> - <property name="alignment"> - <set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set> - </property> - <property name="buddy"> - <cstring>rating</cstring> - </property> - </widget> - </item> - <item row="4" column="1" colspan="2"> - <widget class="QSpinBox" name="rating"> - <property name="toolTip"> - <string>Rating of this book. 0-5 stars</string> - </property> - <property name="whatsThis"> - <string>Rating of this book. 0-5 stars</string> - </property> - <property name="buttonSymbols"> - <enum>QAbstractSpinBox::PlusMinus</enum> - </property> - <property name="suffix"> - <string> stars</string> - </property> - <property name="maximum"> - <number>5</number> - </property> - </widget> - </item> - <item row="5" column="0"> - <widget class="QLabel" name="label_3"> - <property name="text"> - <string>&Publisher: </string> - </property> - <property name="alignment"> - <set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set> - </property> - <property name="buddy"> - <cstring>publisher</cstring> - </property> - </widget> - </item> - <item row="5" column="1" colspan="2"> - <widget class="MultiCompleteComboBox" name="publisher"> - <property name="editable"> - <bool>true</bool> - </property> - </widget> - </item> - <item row="6" column="0"> - <widget class="QLabel" name="label_4"> - <property name="text"> - <string>Ta&gs: </string> - </property> - <property name="alignment"> - <set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set> - </property> - <property name="buddy"> - <cstring>tags</cstring> - </property> - </widget> - </item> - <item row="6" column="1"> - <layout class="QHBoxLayout" name="_2"> - <item> - <widget class="MultiCompleteLineEdit" name="tags"> - <property name="toolTip"> - <string>Tags categorize the book. This is particularly useful while searching. <br><br>They can be any words or phrases, separated by commas.</string> - </property> - </widget> - </item> - </layout> - </item> - <item row="6" column="2"> - <widget class="QToolButton" name="tag_editor_button"> - <property name="toolTip"> - <string>Open Tag Editor</string> - </property> - <property name="text"> - <string>Open Tag Editor</string> - </property> - <property name="icon"> - <iconset resource="../../../../resources/images.qrc"> - <normaloff>:/images/chapters.png</normaloff>:/images/chapters.png</iconset> - </property> - </widget> - </item> - <item row="7" column="0"> - <widget class="QLabel" name="label_7"> - <property name="text"> - <string>&Series:</string> - </property> - <property name="textFormat"> - <enum>Qt::PlainText</enum> - </property> - <property name="alignment"> - <set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set> - </property> - <property name="buddy"> - <cstring>series</cstring> - </property> - </widget> - </item> - <item row="7" column="1"> - <layout class="QHBoxLayout" name="_3"> - <property name="spacing"> - <number>5</number> - </property> - <item> - <widget class="MultiCompleteComboBox" name="series"> - <property name="toolTip"> - <string>List of known series. You can add new series.</string> - </property> - <property name="whatsThis"> - <string>List of known series. You can add new series.</string> - </property> - <property name="editable"> - <bool>true</bool> - </property> - <property name="insertPolicy"> - <enum>QComboBox::InsertAlphabetically</enum> - </property> - </widget> - </item> - </layout> - </item> - <item row="7" column="2"> - <widget class="QToolButton" name="remove_series_button"> - <property name="toolTip"> - <string>Remove unused series (Series that have no books)</string> - </property> - <property name="text"> - <string>...</string> - </property> - <property name="icon"> - <iconset resource="../../../../resources/images.qrc"> - <normaloff>:/images/trash.png</normaloff>:/images/trash.png</iconset> - </property> - </widget> - </item> - <item row="8" column="1" colspan="2"> - <widget class="QDoubleSpinBox" name="series_index"> - <property name="enabled"> - <bool>false</bool> - </property> - <property name="prefix"> - <string>Book </string> - </property> - <property name="maximum"> - <double>9999.989999999999782</double> - </property> - </widget> - </item> - <item row="9" column="0"> - <widget class="QLabel" name="label_9"> - <property name="text"> - <string>IS&BN:</string> - </property> - <property name="alignment"> - <set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set> - </property> - <property name="buddy"> - <cstring>isbn</cstring> - </property> - </widget> - </item> - <item row="9" column="1" colspan="2"> - <widget class="QLineEdit" name="isbn"/> - </item> - <item row="10" column="0"> - <widget class="QLabel" name="label_11"> - <property name="text"> - <string>&Date:</string> - </property> - <property name="alignment"> - <set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set> - </property> - <property name="buddy"> - <cstring>date</cstring> - </property> - </widget> - </item> - <item row="10" column="1" colspan="2"> - <widget class="QDateEdit" name="date"> - <property name="displayFormat"> - <string>dd MMM yyyy</string> - </property> - <property name="calendarPopup"> - <bool>true</bool> - </property> - </widget> - </item> - <item row="11" column="0"> - <widget class="QLabel" name="label_10"> - <property name="text"> - <string>Publishe&d:</string> - </property> - <property name="alignment"> - <set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set> - </property> - <property name="buddy"> - <cstring>pubdate</cstring> - </property> - </widget> - </item> - <item row="11" column="1"> - <widget class="QDateEdit" name="pubdate"> - <property name="displayFormat"> - <string>MMM yyyy</string> - </property> - <property name="calendarPopup"> - <bool>true</bool> - </property> - </widget> - </item> - <item row="11" column="2"> - <widget class="QToolButton" name="clear_pubdate_button"> - <property name="toolTip"> - <string>Clear published date</string> - </property> - <property name="icon"> - <iconset resource="../../../../resources/images.qrc"> - <normaloff>:/images/trash.png</normaloff>:/images/trash.png</iconset> - </property> - </widget> - </item> - </layout> - </widget> - </item> - <item> - <widget class="QPushButton" name="fetch_metadata_button"> - <property name="text"> - <string>&Fetch metadata from server</string> - </property> - </widget> - </item> - <item> - <spacer name="verticalSpacer_5"> - <property name="orientation"> - <enum>Qt::Vertical</enum> - </property> - <property name="sizeType"> - <enum>QSizePolicy::Fixed</enum> - </property> - <property name="sizeHint" stdset="0"> - <size> - <width>20</width> - <height>40</height> - </size> - </property> - </spacer> - </item> - </layout> - </widget> - <widget class="QWidget" name="layoutWidget_2"> - <layout class="QVBoxLayout" name="verticalLayout_2"> - <item> - <widget class="QGroupBox" name="bc_box"> - <property name="sizePolicy"> - <sizepolicy hsizetype="Preferred" vsizetype="Expanding"> - <horstretch>0</horstretch> - <verstretch>10</verstretch> - </sizepolicy> - </property> - <property name="title"> - <string>Book Cover</string> - </property> - <layout class="QVBoxLayout" name="verticalLayout_4"> - <item> - <widget class="ImageView" name="cover" native="true"> - <property name="sizePolicy"> - <sizepolicy hsizetype="Expanding" vsizetype="Expanding"> - <horstretch>0</horstretch> - <verstretch>100</verstretch> - </sizepolicy> - </property> - </widget> - </item> - <item> - <layout class="QVBoxLayout" name="_4"> - <property name="spacing"> - <number>6</number> - </property> - <property name="sizeConstraint"> - <enum>QLayout::SetMaximumSize</enum> - </property> - <property name="margin"> - <number>0</number> - </property> - <item> - <widget class="QLabel" name="label_5"> - <property name="text"> - <string>Change &cover image:</string> - </property> - <property name="buddy"> - <cstring>cover_button</cstring> - </property> - </widget> - </item> - <item> - <layout class="QHBoxLayout" name="_5"> - <property name="spacing"> - <number>6</number> - </property> - <property name="margin"> - <number>0</number> - </property> - <item> - <widget class="QPushButton" name="cover_button"> - <property name="text"> - <string>&Browse</string> - </property> - <property name="icon"> - <iconset resource="../../../../resources/images.qrc"> - <normaloff>:/images/document_open.png</normaloff>:/images/document_open.png</iconset> - </property> - </widget> - </item> - <item> - <widget class="QPushButton" name="trim_cover_button"> - <property name="toolTip"> - <string>Remove border (if any) from cover</string> - </property> - <property name="text"> - <string>T&rim</string> - </property> - <property name="icon"> - <iconset resource="../../../../resources/images.qrc"> - <normaloff>:/images/trim.png</normaloff>:/images/trim.png</iconset> - </property> - </widget> - </item> - <item> - <widget class="QPushButton" name="reset_cover"> - <property name="toolTip"> - <string>Reset cover to default</string> - </property> - <property name="text"> - <string>&Remove</string> - </property> - <property name="icon"> - <iconset resource="../../../../resources/images.qrc"> - <normaloff>:/images/trash.png</normaloff>:/images/trash.png</iconset> - </property> - </widget> - </item> - </layout> - </item> - </layout> - </item> - <item> - <layout class="QHBoxLayout" name="_6"> - <item> - <widget class="QPushButton" name="fetch_cover_button"> - <property name="text"> - <string>Download co&ver</string> - </property> - </widget> - </item> - <item> - <widget class="QPushButton" name="generate_cover_button"> - <property name="toolTip"> - <string>Generate a default cover based on the title and author</string> - </property> - <property name="text"> - <string>&Generate cover</string> - </property> - </widget> - </item> - </layout> - </item> - </layout> - </widget> - </item> - </layout> - </widget> - <widget class="QWidget" name="layoutWidget"> - <layout class="QVBoxLayout" name="verticalLayout_3"> - <item> - <widget class="QGroupBox" name="af_group_box"> - <property name="sizePolicy"> - <sizepolicy hsizetype="Preferred" vsizetype="Minimum"> - <horstretch>0</horstretch> - <verstretch>0</verstretch> - </sizepolicy> - </property> - <property name="title"> - <string>Available Formats</string> - </property> - <layout class="QVBoxLayout" name="verticalLayout"> - <item> - <layout class="QGridLayout" name="gridLayout"> - <item row="0" column="1" rowspan="3"> - <widget class="FormatList" name="formats"> - <property name="sizePolicy"> - <sizepolicy hsizetype="Minimum" vsizetype="Minimum"> - <horstretch>0</horstretch> - <verstretch>0</verstretch> - </sizepolicy> - </property> - <property name="maximumSize"> - <size> - <width>16777215</width> - <height>140</height> - </size> - </property> - <property name="baseSize"> - <size> - <width>100</width> - <height>0</height> - </size> - </property> - <property name="dragDropMode"> - <enum>QAbstractItemView::DropOnly</enum> - </property> - <property name="iconSize"> - <size> - <width>64</width> - <height>64</height> - </size> - </property> - </widget> - </item> - <item row="0" column="2"> - <widget class="QToolButton" name="add_format_button"> - <property name="toolTip"> - <string>Add a new format for this book to the database</string> - </property> - <property name="text"> - <string>...</string> - </property> - <property name="icon"> - <iconset resource="../../../../resources/images.qrc"> - <normaloff>:/images/add_book.png</normaloff>:/images/add_book.png</iconset> - </property> - <property name="iconSize"> - <size> - <width>32</width> - <height>32</height> - </size> - </property> - </widget> - </item> - <item row="2" column="2"> - <widget class="QToolButton" name="remove_format_button"> - <property name="toolTip"> - <string>Remove the selected formats for this book from the database.</string> - </property> - <property name="text"> - <string>...</string> - </property> - <property name="icon"> - <iconset resource="../../../../resources/images.qrc"> - <normaloff>:/images/trash.png</normaloff>:/images/trash.png</iconset> - </property> - <property name="iconSize"> - <size> - <width>32</width> - <height>32</height> - </size> - </property> - </widget> - </item> - <item row="0" column="0"> - <widget class="QToolButton" name="button_set_cover"> - <property name="toolTip"> - <string>Set the cover for the book from the selected format</string> - </property> - <property name="text"> - <string>...</string> - </property> - <property name="icon"> - <iconset resource="../../../../resources/images.qrc"> - <normaloff>:/images/book.png</normaloff>:/images/book.png</iconset> - </property> - <property name="iconSize"> - <size> - <width>32</width> - <height>32</height> - </size> - </property> - </widget> - </item> - <item row="2" column="0"> - <widget class="QToolButton" name="button_set_metadata"> - <property name="toolTip"> - <string>Update metadata from the metadata in the selected format</string> - </property> - <property name="text"> - <string/> - </property> - <property name="icon"> - <iconset resource="../../../../resources/images.qrc"> - <normaloff>:/images/edit_input.png</normaloff>:/images/edit_input.png</iconset> - </property> - <property name="iconSize"> - <size> - <width>32</width> - <height>32</height> - </size> - </property> - </widget> - </item> - </layout> - </item> - </layout> - <zorder></zorder> - </widget> - </item> - <item> - <widget class="QGroupBox" name="groupBox"> - <property name="sizePolicy"> - <sizepolicy hsizetype="Preferred" vsizetype="Preferred"> - <horstretch>0</horstretch> - <verstretch>10</verstretch> - </sizepolicy> - </property> - <property name="title"> - <string>&Comments</string> - </property> - <layout class="QVBoxLayout" name="verticalLayout_8"> - <property name="margin"> - <number>0</number> - </property> - <item> - <widget class="Editor" name="comments" native="true"/> - </item> - </layout> - </widget> - </item> - </layout> - </widget> - </widget> - </item> - </layout> - </widget> - <widget class="QWidget" name="tab"> - <attribute name="title"> - <string>&Custom metadata</string> - </attribute> - <layout class="QGridLayout" name="gridLayout_2"/> - </widget> - </widget> - </item> - </layout> - </widget> - </widget> - </item> - <item> - <widget class="QDialogButtonBox" name="button_box"> - <property name="orientation"> - <enum>Qt::Horizontal</enum> - </property> - <property name="standardButtons"> - <set>QDialogButtonBox::Cancel|QDialogButtonBox::Ok</set> - </property> - </widget> - </item> - </layout> - </widget> - <customwidgets> - <customwidget> - <class>EnLineEdit</class> - <extends>QLineEdit</extends> - <header>widgets.h</header> - </customwidget> - <customwidget> - <class>MultiCompleteLineEdit</class> - <extends>QLineEdit</extends> - <header>calibre/gui2/complete.h</header> - </customwidget> - <customwidget> - <class>MultiCompleteComboBox</class> - <extends>QComboBox</extends> - <header>calibre/gui2/complete.h</header> - </customwidget> - <customwidget> - <class>FormatList</class> - <extends>QListWidget</extends> - <header location="global">calibre/gui2/widgets.h</header> - </customwidget> - <customwidget> - <class>ImageView</class> - <extends>QWidget</extends> - <header>calibre/gui2/widgets.h</header> - <container>1</container> - </customwidget> - <customwidget> - <class>Editor</class> - <extends>QWidget</extends> - <header location="global">calibre/gui2/comments_editor.h</header> - <container>1</container> - </customwidget> - </customwidgets> - <tabstops> - <tabstop>title</tabstop> - <tabstop>auto_title_sort</tabstop> - <tabstop>title_sort</tabstop> - <tabstop>swap_button</tabstop> - <tabstop>authors</tabstop> - <tabstop>auto_author_sort</tabstop> - <tabstop>author_sort</tabstop> - <tabstop>rating</tabstop> - <tabstop>publisher</tabstop> - <tabstop>tags</tabstop> - <tabstop>tag_editor_button</tabstop> - <tabstop>series</tabstop> - <tabstop>remove_series_button</tabstop> - <tabstop>series_index</tabstop> - <tabstop>isbn</tabstop> - <tabstop>date</tabstop> - <tabstop>pubdate</tabstop> - <tabstop>fetch_metadata_button</tabstop> - <tabstop>button_set_cover</tabstop> - <tabstop>button_set_metadata</tabstop> - <tabstop>formats</tabstop> - <tabstop>add_format_button</tabstop> - <tabstop>remove_format_button</tabstop> - <tabstop>cover_button</tabstop> - <tabstop>trim_cover_button</tabstop> - <tabstop>reset_cover</tabstop> - <tabstop>fetch_cover_button</tabstop> - <tabstop>generate_cover_button</tabstop> - <tabstop>button_box</tabstop> - <tabstop>scrollArea</tabstop> - <tabstop>central_widget</tabstop> - </tabstops> - <resources> - <include location="../../../../resources/images.qrc"/> - </resources> - <connections> - <connection> - <sender>button_box</sender> - <signal>accepted()</signal> - <receiver>MetadataSingleDialog</receiver> - <slot>accept()</slot> - <hints> - <hint type="sourcelabel"> - <x>261</x> - <y>710</y> - </hint> - <hint type="destinationlabel"> - <x>157</x> - <y>274</y> - </hint> - </hints> - </connection> - <connection> - <sender>button_box</sender> - <signal>rejected()</signal> - <receiver>MetadataSingleDialog</receiver> - <slot>reject()</slot> - <hints> - <hint type="sourcelabel"> - <x>329</x> - <y>710</y> - </hint> - <hint type="destinationlabel"> - <x>286</x> - <y>274</y> - </hint> - </hints> - </connection> - </connections> -</ui> diff --git a/src/calibre/gui2/dialogs/saved_search_editor.py b/src/calibre/gui2/dialogs/saved_search_editor.py index 1143a6f06a..c9f843109a 100644 --- a/src/calibre/gui2/dialogs/saved_search_editor.py +++ b/src/calibre/gui2/dialogs/saved_search_editor.py @@ -9,12 +9,13 @@ from PyQt4.QtGui import QDialog from calibre.gui2.dialogs.saved_search_editor_ui import Ui_SavedSearchEditor from calibre.utils.search_query_parser import saved_searches from calibre.utils.icu import sort_key +from calibre.gui2 import error_dialog from calibre.gui2.dialogs.confirm_delete import confirm class SavedSearchEditor(QDialog, Ui_SavedSearchEditor): - def __init__(self, window, initial_search=None): - QDialog.__init__(self, window) + def __init__(self, parent, initial_search=None): + QDialog.__init__(self, parent) Ui_SavedSearchEditor.__init__(self) self.setupUi(self) @@ -22,12 +23,13 @@ class SavedSearchEditor(QDialog, Ui_SavedSearchEditor): self.connect(self.search_name_box, SIGNAL('currentIndexChanged(int)'), self.current_index_changed) self.connect(self.delete_search_button, SIGNAL('clicked()'), self.del_search) + self.rename_button.clicked.connect(self.rename_search) self.current_search_name = None self.searches = {} - self.searches_to_delete = [] for name in saved_searches().names(): self.searches[name] = saved_searches().lookup(name) + self.search_names = set([icu_lower(n) for n in saved_searches().names()]) self.populate_search_list() if initial_search is not None and initial_search in self.searches: @@ -42,6 +44,11 @@ class SavedSearchEditor(QDialog, Ui_SavedSearchEditor): search_name = unicode(self.input_box.text()).strip() if search_name == '': return False + if icu_lower(search_name) in self.search_names: + error_dialog(self, _('Saved search already exists'), + _('The saved search %s already exists, perhaps with ' + 'different case')%search_name).exec_() + return False if search_name not in self.searches: self.searches[search_name] = '' self.populate_search_list() @@ -57,10 +64,25 @@ class SavedSearchEditor(QDialog, Ui_SavedSearchEditor): +'</p>', 'saved_search_editor_delete', self): return del self.searches[self.current_search_name] - self.searches_to_delete.append(self.current_search_name) self.current_search_name = None self.search_name_box.removeItem(self.search_name_box.currentIndex()) + def rename_search(self): + new_search_name = unicode(self.input_box.text()).strip() + if new_search_name == '': + return False + if icu_lower(new_search_name) in self.search_names: + error_dialog(self, _('Saved search already exists'), + _('The saved search %s already exists, perhaps with ' + 'different case')%new_search_name).exec_() + return False + if self.current_search_name in self.searches: + self.searches[new_search_name] = self.searches[self.current_search_name] + del self.searches[self.current_search_name] + self.populate_search_list() + self.select_search(new_search_name) + return True + def select_search(self, name): self.search_name_box.setCurrentIndex(self.search_name_box.findText(name)) @@ -78,7 +100,7 @@ class SavedSearchEditor(QDialog, Ui_SavedSearchEditor): def accept(self): if self.current_search_name: self.searches[self.current_search_name] = unicode(self.search_text.toPlainText()) - for name in self.searches_to_delete: + for name in saved_searches().names(): saved_searches().delete(name) for name in self.searches: saved_searches().add(name, self.searches[name]) diff --git a/src/calibre/gui2/dialogs/saved_search_editor.ui b/src/calibre/gui2/dialogs/saved_search_editor.ui index 3ba37bdf10..af6d6f4d55 100644 --- a/src/calibre/gui2/dialogs/saved_search_editor.ui +++ b/src/calibre/gui2/dialogs/saved_search_editor.ui @@ -90,7 +90,7 @@ </property> <property name="icon"> <iconset> - <normaloff>:/images/minus.png</normaloff>:/images/minus.png</iconset> + <normaloff>:/images/trash.png</normaloff>:/images/trash.png</iconset> </property> </widget> </item> @@ -134,6 +134,20 @@ </property> </widget> </item> + <item row="0" column="6"> + <widget class="QToolButton" name="rename_button"> + <property name="toolTip"> + <string>Rename the current search to what is in the box</string> + </property> + <property name="text"> + <string>...</string> + </property> + <property name="icon"> + <iconset> + <normaloff>:/images/edit-undo.png</normaloff>:/images/edit-undo.png</iconset> + </property> + </widget> + </item> </layout> </item> <item row="1" column="0"> diff --git a/src/calibre/gui2/dialogs/scheduler.py b/src/calibre/gui2/dialogs/scheduler.py index b6a3bed3eb..7d1d87b472 100644 --- a/src/calibre/gui2/dialogs/scheduler.py +++ b/src/calibre/gui2/dialogs/scheduler.py @@ -8,72 +8,253 @@ Scheduler for automated recipe downloads ''' from datetime import timedelta +import calendar, textwrap +from collections import OrderedDict -from PyQt4.Qt import QDialog, SIGNAL, Qt, QTime, QObject, QMenu, \ - QAction, QIcon, QMutex, QTimer, pyqtSignal, QWidget, QHBoxLayout, \ - QLabel +from PyQt4.Qt import QDialog, Qt, QTime, QObject, QMenu, QHBoxLayout, \ + QAction, QIcon, QMutex, QTimer, pyqtSignal, QWidget, QGridLayout, \ + QCheckBox, QTimeEdit, QLabel, QLineEdit, QDoubleSpinBox from calibre.gui2.dialogs.scheduler_ui import Ui_Dialog -from calibre.gui2.search_box import SearchBox2 from calibre.gui2 import config as gconf, error_dialog from calibre.web.feeds.recipes.model import RecipeModel from calibre.ptempfile import PersistentTemporaryFile from calibre.utils.date import utcnow from calibre.utils.network import internet_connected +from calibre import force_unicode + +def convert_day_time_schedule(val): + day_of_week, hour, minute = val + if day_of_week == -1: + return (tuple(xrange(7)), hour, minute) + return ((day_of_week,), hour, minute) + +class Base(QWidget): + + def __init__(self, parent=None): + QWidget.__init__(self, parent) + self.l = QGridLayout() + self.setLayout(self.l) + self.setToolTip(textwrap.dedent(self.HELP)) + +class DaysOfWeek(Base): + + HELP = _('''\ + Download this periodical every week on the specified days after + the specified time. For example, if you choose: Monday after + 9:00 AM, then the periodical will be download every Monday as + soon after 9:00 AM as possible. + ''') + + def __init__(self, parent=None): + Base.__init__(self, parent) + self.days = [QCheckBox(force_unicode(calendar.day_abbr[d]), + self) for d in xrange(7)] + for i, cb in enumerate(self.days): + row = i % 2 + col = i // 2 + self.l.addWidget(cb, row, col, 1, 1) + + self.time = QTimeEdit(self) + self.time.setDisplayFormat('hh:mm AP') + self.hl = QHBoxLayout() + self.l1 = QLabel(_('&Download after:')) + self.l1.setBuddy(self.time) + self.hl.addWidget(self.l1) + self.hl.addWidget(self.time) + self.l.addLayout(self.hl, 1, 3, 1, 1) + self.initialize() + + def initialize(self, typ=None, val=None): + if typ is None: + typ = 'day/time' + val = (-1, 6, 0) + if typ == 'day/time': + val = convert_day_time_schedule(val) + + days_of_week, hour, minute = val + for i, d in enumerate(self.days): + d.setChecked(i in days_of_week) + + self.time.setTime(QTime(hour, minute)) + + @property + def schedule(self): + days_of_week = tuple([i for i, d in enumerate(self.days) if + d.isChecked()]) + t = self.time.time() + hour, minute = t.hour(), t.minute() + return 'days_of_week', (days_of_week, int(hour), int(minute)) + +class DaysOfMonth(Base): + + HELP = _('''\ + Download this periodical every month, on the specified days. + The download will happen as soon after the specified time as + possible on the specified days of each month. For example, + if you choose the 1st and the 15th after 9:00 AM, the + periodical will be downloaded on the 1st and 15th of every + month, as soon after 9:00 AM as possible. + ''') + + def __init__(self, parent=None): + Base.__init__(self, parent) + + self.l1 = QLabel(_('&Days of the month:')) + self.days = QLineEdit(self) + self.days.setToolTip(_('Comma separated list of days of the month.' + ' For example: 1, 15')) + self.l1.setBuddy(self.days) + + self.l2 = QLabel(_('Download &after:')) + self.time = QTimeEdit(self) + self.time.setDisplayFormat('hh:mm AP') + self.l2.setBuddy(self.time) + + self.l.addWidget(self.l1, 0, 0, 1, 1) + self.l.addWidget(self.days, 0, 1, 1, 1) + self.l.addWidget(self.l2, 1, 0, 1, 1) + self.l.addWidget(self.time, 1, 1, 1, 1) + + def initialize(self, typ=None, val=None): + if val is None: + val = ((1,), 6, 0) + days_of_month, hour, minute = val + self.days.setText(', '.join(map(str, map(int, days_of_month)))) + self.time.setTime(QTime(hour, minute)) + + @property + def schedule(self): + parts = [x.strip() for x in unicode(self.days.text()).split(',') if + x.strip()] + try: + days_of_month = tuple(map(int, parts)) + except: + days_of_month = (1,) + if not days_of_month: + days_of_month = (1,) + t = self.time.time() + hour, minute = t.hour(), t.minute() + return 'days_of_month', (days_of_month, int(hour), int(minute)) + +class EveryXDays(Base): + + HELP = _('''\ + Download this periodical every x days. For example, if you + choose 30 days, the periodical will be downloaded every 30 + days. Note that you can set periods of less than a day, like + 0.1 days to download a periodical more than once a day. + ''') + + def __init__(self, parent=None): + Base.__init__(self, parent) + self.l1 = QLabel(_('&Download every:')) + self.interval = QDoubleSpinBox(self) + self.interval.setMinimum(0.04) + self.interval.setSpecialValueText(_('every hour')) + self.interval.setMaximum(1000.0) + self.interval.setValue(31.0) + self.interval.setSuffix(' ' + _('days')) + self.interval.setSingleStep(1.0) + self.interval.setDecimals(2) + self.l1.setBuddy(self.interval) + self.l2 = QLabel(_('Note: You can set intervals of less than a day,' + ' by typing the value manually.')) + self.l2.setWordWrap(True) + + self.l.addWidget(self.l1, 0, 0, 1, 1) + self.l.addWidget(self.interval, 0, 1, 1, 1) + self.l.addWidget(self.l2, 1, 0, 1, -1) + + def initialize(self, typ=None, val=None): + if val is None: + val = 31.0 + self.interval.setValue(val) + + @property + def schedule(self): + schedule = self.interval.value() + return 'interval', schedule + class SchedulerDialog(QDialog, Ui_Dialog): + SCHEDULE_TYPES = OrderedDict([ + ('days_of_week', DaysOfWeek), + ('days_of_month', DaysOfMonth), + ('every_x_days', EveryXDays), + ]) + + download = pyqtSignal(object) + def __init__(self, recipe_model, parent=None): QDialog.__init__(self, parent) self.setupUi(self) self.recipe_model = recipe_model self.recipe_model.do_refresh() + self.count_label.setText( + _('%s news sources') % + self.recipe_model.showing_count) + + self.schedule_widgets = [] + for key in reversed(self.SCHEDULE_TYPES): + self.schedule_widgets.insert(0, self.SCHEDULE_TYPES[key](self)) + self.schedule_stack.insertWidget(0, self.schedule_widgets[0]) - self._cont = QWidget(self) - self._cont.l = QHBoxLayout() - self._cont.setLayout(self._cont.l) - self._cont.la = QLabel(_('&Search:')) - self._cont.l.addWidget(self._cont.la, 1) - self.search = SearchBox2(self) - self._cont.l.addWidget(self.search, 100) - self._cont.la.setBuddy(self.search) - self.search.setMinimumContentsLength(25) self.search.initialize('scheduler_search_history') - self.recipe_box.layout().insertWidget(0, self._cont) + self.search.setMinimumContentsLength(15) self.search.search.connect(self.recipe_model.search) self.recipe_model.searched.connect(self.search.search_done, type=Qt.QueuedConnection) self.recipe_model.searched.connect(self.search_done) - self.search.setFocus(Qt.OtherFocusReason) + self.recipes.setFocus(Qt.OtherFocusReason) self.commit_on_change = True + self.previous_urn = None self.recipes.setModel(self.recipe_model) self.detail_box.setVisible(False) self.download_button.setVisible(False) self.recipes.currentChanged = self.current_changed - self.interval_button.setChecked(True) + for b, c in self.SCHEDULE_TYPES.iteritems(): + b = getattr(self, b) + b.toggled.connect(self.schedule_type_selected) + b.setToolTip(textwrap.dedent(c.HELP)) + self.days_of_week.setChecked(True) - self.connect(self.schedule, SIGNAL('stateChanged(int)'), - self.toggle_schedule_info) - self.connect(self.show_password, SIGNAL('stateChanged(int)'), - lambda state: self.password.setEchoMode(self.password.Normal if state == Qt.Checked else self.password.Password)) - self.connect(self.download_button, SIGNAL('clicked()'), - self.download_clicked) - self.connect(self.download_all_button, SIGNAL('clicked()'), + self.schedule.stateChanged[int].connect(self.toggle_schedule_info) + self.show_password.stateChanged[int].connect(self.set_pw_echo_mode) + self.download_button.clicked.connect(self.download_clicked) + self.download_all_button.clicked.connect( self.download_all_clicked) self.old_news.setValue(gconf['oldest_news']) + self.go_button.clicked.connect(self.search.do_search) + self.clear_search_button.clicked.connect(self.search.clear_clicked) + + def set_pw_echo_mode(self, state): + self.password.setEchoMode(self.password.Normal + if state == Qt.Checked else self.password.Password) + + + def schedule_type_selected(self, *args): + for i, st in enumerate(self.SCHEDULE_TYPES): + if getattr(self, st).isChecked(): + self.schedule_stack.setCurrentIndex(i) + break + def keyPressEvent(self, ev): if ev.key() not in (Qt.Key_Enter, Qt.Key_Return): return QDialog.keyPressEvent(self, ev) def break_cycles(self): - self.disconnect(self.recipe_model, SIGNAL('searched(PyQt_PyObject)'), - self.search_done) - self.disconnect(self.recipe_model, SIGNAL('searched(PyQt_PyObject)'), - self.search.search_done) - self.search.search.disconnect() + try: + self.recipe_model.searched.disconnect(self.search_done) + self.recipe_model.searched.disconnect(self.search.search_done) + self.search.search.disconnect() + self.download.disconnect() + except: + pass self.recipe_model = None def search_done(self, *args): @@ -82,19 +263,15 @@ class SchedulerDialog(QDialog, Ui_Dialog): def toggle_schedule_info(self, *args): enabled = self.schedule.isChecked() - for x in ('daily_button', 'day', 'time', 'interval_button', 'interval'): + for x in self.SCHEDULE_TYPES: getattr(self, x).setEnabled(enabled) + self.schedule_stack.setEnabled(enabled) self.last_downloaded.setVisible(enabled) def current_changed(self, current, previous): - if self.commit_on_change: - if previous.isValid(): - if not self.commit(urn=getattr(previous.internalPointer(), - 'urn', None)): - self.commit_on_change = False - self.recipes.setCurrentIndex(previous) - else: - self.commit_on_change = True + if self.previous_urn is not None: + self.commit(urn=self.previous_urn) + self.previous_urn = None urn = self.current_urn if urn is not None: @@ -105,14 +282,14 @@ class SchedulerDialog(QDialog, Ui_Dialog): return False return QDialog.accept(self) - def download_clicked(self): + def download_clicked(self, *args): self.commit() if self.commit() and self.current_urn: - self.emit(SIGNAL('download(PyQt_PyObject)'), self.current_urn) + self.download.emit(self.current_urn) - def download_all_clicked(self): + def download_all_clicked(self, *args): if self.commit() and self.commit(): - self.emit(SIGNAL('download(PyQt_PyObject)'), None) + self.download.emit(None) @property def current_urn(self): @@ -138,27 +315,23 @@ class SchedulerDialog(QDialog, Ui_Dialog): self.recipe_model.set_account_info(urn, un, pw) if self.schedule.isChecked(): - schedule_type = 'interval' if self.interval_button.isChecked() else 'day/time' - if schedule_type == 'interval': - schedule = self.interval.value() - if schedule < 0.1: - schedule = 1./24. - else: - day_of_week = self.day.currentIndex() - 1 - t = self.time.time() - hour, minute = t.hour(), t.minute() - schedule = (day_of_week, hour, minute) + schedule_type, schedule = \ + self.schedule_stack.currentWidget().schedule self.recipe_model.schedule_recipe(urn, schedule_type, schedule) else: self.recipe_model.un_schedule_recipe(urn) add_title_tag = self.add_title_tag.isChecked() + keep_issues = u'0' + if self.keep_issues.isEnabled(): + keep_issues = unicode(self.keep_issues.value()) custom_tags = unicode(self.custom_tags.text()).strip() custom_tags = [x.strip() for x in custom_tags.split(',')] - self.recipe_model.customize_recipe(urn, add_title_tag, custom_tags) + self.recipe_model.customize_recipe(urn, add_title_tag, custom_tags, keep_issues) return True def initialize_detail_box(self, urn): + self.previous_urn = urn self.detail_box.setVisible(True) self.download_button.setVisible(True) self.detail_box.setCurrentIndex(0) @@ -197,27 +370,34 @@ class SchedulerDialog(QDialog, Ui_Dialog): self.schedule.setChecked(scheduled) self.toggle_schedule_info() self.last_downloaded.setText(_('Last downloaded: never')) + ld_text = _('never') if scheduled: typ, sch, last_downloaded = schedule_info - if typ == 'interval': - self.interval_button.setChecked(True) - self.interval.setValue(sch) - elif typ == 'day/time': - self.daily_button.setChecked(True) - day, hour, minute = sch - self.day.setCurrentIndex(day+1) - self.time.setTime(QTime(hour, minute)) - d = utcnow() - last_downloaded def hm(x): return (x-x%3600)//3600, (x%3600 - (x%3600)%60)//60 hours, minutes = hm(d.seconds) tm = _('%d days, %d hours and %d minutes ago')%(d.days, hours, minutes) if d < timedelta(days=366): - self.last_downloaded.setText(_('Last downloaded')+': '+tm) - - add_title_tag, custom_tags = customize_info + ld_text = tm + else: + typ, sch = 'day/time', (-1, 6, 0) + sch_widget = {'day/time': 0, 'days_of_week': 0, 'days_of_month':1, + 'interval':2}[typ] + rb = getattr(self, list(self.SCHEDULE_TYPES)[sch_widget]) + rb.setChecked(True) + self.schedule_stack.setCurrentIndex(sch_widget) + self.schedule_stack.currentWidget().initialize(typ, sch) + add_title_tag, custom_tags, keep_issues = customize_info self.add_title_tag.setChecked(add_title_tag) self.custom_tags.setText(u', '.join(custom_tags)) + self.last_downloaded.setText(_('Last downloaded:') + ' ' + ld_text) + try: + keep_issues = int(keep_issues) + except: + keep_issues = 0 + self.keep_issues.setValue(keep_issues) + self.keep_issues.setEnabled(self.add_title_tag.isChecked()) + class Scheduler(QObject): @@ -231,7 +411,8 @@ class Scheduler(QObject): QObject.__init__(self, parent) self.internet_connection_failed = False self._parent = parent - self.recipe_model = RecipeModel(db) + self.recipe_model = RecipeModel() + self.db = db self.lock = QMutex(QMutex.Recursive) self.download_queue = set([]) @@ -239,9 +420,9 @@ class Scheduler(QObject): self.news_icon = QIcon(I('news.png')) self.scheduler_action = QAction(QIcon(I('scheduler.png')), _('Schedule news download'), self) self.news_menu.addAction(self.scheduler_action) - self.connect(self.scheduler_action, SIGNAL('triggered(bool)'), self.show_dialog) + self.scheduler_action.triggered[bool].connect(self.show_dialog) self.cac = QAction(QIcon(I('user_profile.png')), _('Add a custom news source'), self) - self.connect(self.cac, SIGNAL('triggered(bool)'), self.customize_feeds) + self.cac.triggered[bool].connect(self.customize_feeds) self.news_menu.addAction(self.cac) self.news_menu.addSeparator() self.all_action = self.news_menu.addAction( @@ -250,22 +431,22 @@ class Scheduler(QObject): self.timer = QTimer(self) self.timer.start(int(self.INTERVAL * 60 * 1000)) - self.connect(self.timer, SIGNAL('timeout()'), self.check) + self.timer.timeout.connect(self.check) self.oldest = gconf['oldest_news'] QTimer.singleShot(5 * 1000, self.oldest_check) - self.database_changed = self.recipe_model.database_changed + + def database_changed(self, db): + self.db = db def oldest_check(self): if self.oldest > 0: delta = timedelta(days=self.oldest) try: - ids = list(self.recipe_model.db.tags_older_than(_('News'), + ids = list(self.db.tags_older_than(_('News'), delta)) except: - # Should never happen + # Happens if library is being switched ids = [] - import traceback - traceback.print_exc() if ids: if ids: self.delete_old_news.emit(ids) @@ -276,8 +457,7 @@ class Scheduler(QObject): self.lock.lock() try: d = SchedulerDialog(self.recipe_model) - self.connect(d, SIGNAL('download(PyQt_PyObject)'), - self.download_clicked) + d.download.connect(self.download_clicked) d.exec_() gconf['oldest_news'] = self.oldest = d.old_news.value() d.break_cycles() @@ -299,7 +479,7 @@ class Scheduler(QObject): un = pw = None if account_info is not None: un, pw = account_info - add_title_tag, custom_tags = customize_info + add_title_tag, custom_tags, keep_issues = customize_info script = self.recipe_model.get_recipe(urn) pt = PersistentTemporaryFile('_builtin.recipe') pt.write(script) @@ -312,6 +492,7 @@ class Scheduler(QObject): 'recipe':pt.name, 'title':recipe.get('title',''), 'urn':urn, + 'keep_issues':keep_issues } self.download_queue.add(urn) self.start_recipe_fetch.emit(arg) @@ -371,7 +552,6 @@ class Scheduler(QObject): if __name__ == '__main__': from calibre.gui2 import is_ok_to_use_qt is_ok_to_use_qt() - from calibre.library.database2 import LibraryDatabase2 - d = SchedulerDialog(RecipeModel(LibraryDatabase2('/home/kovid/documents/library'))) + d = SchedulerDialog(RecipeModel()) d.exec_() diff --git a/src/calibre/gui2/dialogs/scheduler.ui b/src/calibre/gui2/dialogs/scheduler.ui index 8e6ab37162..6acbb01dd8 100644 --- a/src/calibre/gui2/dialogs/scheduler.ui +++ b/src/calibre/gui2/dialogs/scheduler.ui @@ -6,7 +6,7 @@ <rect> <x>0</x> <y>0</y> - <width>767</width> + <width>792</width> <height>575</height> </rect> </property> @@ -14,358 +14,402 @@ <string>Schedule news download</string> </property> <property name="windowIcon"> - <iconset> + <iconset resource="../../../../resources/images.qrc"> <normaloff>:/images/scheduler.png</normaloff>:/images/scheduler.png</iconset> </property> <layout class="QGridLayout" name="gridLayout"> - <item row="0" column="0" rowspan="3"> - <widget class="QGroupBox" name="recipe_box"> - <property name="title"> - <string>Recipes</string> - </property> - <layout class="QVBoxLayout" name="verticalLayout"> - <item> - <widget class="QTreeView" name="recipes"> - <property name="showDropIndicator" stdset="0"> - <bool>false</bool> - </property> - <property name="iconSize"> - <size> - <width>16</width> - <height>16</height> - </size> - </property> - <property name="animated"> - <bool>true</bool> - </property> - <property name="headerHidden"> - <bool>true</bool> - </property> - </widget> - </item> - <item> - <widget class="QPushButton" name="download_all_button"> - <property name="toolTip"> - <string>Download all scheduled recipes at once</string> - </property> - <property name="text"> - <string>Download &all scheduled</string> - </property> - </widget> - </item> - <item> - <widget class="QLabel" name="rnumber"> - <property name="text"> - <string/> - </property> - </widget> - </item> - </layout> - </widget> - </item> - <item row="0" column="1"> - <layout class="QVBoxLayout" name="verticalLayout_3"> + <item row="0" column="0"> + <layout class="QHBoxLayout" name="horizontalLayout_2"> <item> - <widget class="QScrollArea" name="scrollArea"> - <property name="frameShape"> - <enum>QFrame::NoFrame</enum> + <widget class="SearchBox2" name="search"/> + </item> + <item> + <widget class="QToolButton" name="go_button"> + <property name="text"> + <string>Go</string> </property> - <property name="widgetResizable"> - <bool>true</bool> + </widget> + </item> + <item> + <widget class="QToolButton" name="clear_search_button"> + <property name="icon"> + <iconset resource="../../../../resources/images.qrc"> + <normaloff>:/images/clear_left.png</normaloff>:/images/clear_left.png</iconset> </property> - <widget class="QWidget" name="scrollAreaWidgetContents"> - <property name="geometry"> - <rect> - <x>0</x> - <y>0</y> - <width>375</width> - <height>502</height> - </rect> - </property> - <layout class="QVBoxLayout" name="verticalLayout_5"> - <property name="margin"> - <number>0</number> - </property> - <item> - <widget class="QTabWidget" name="detail_box"> - <property name="sizePolicy"> - <sizepolicy hsizetype="Expanding" vsizetype="Expanding"> - <horstretch>0</horstretch> - <verstretch>100</verstretch> - </sizepolicy> - </property> - <property name="currentIndex"> - <number>0</number> - </property> - <widget class="QWidget" name="tab"> - <attribute name="title"> - <string>&Schedule</string> - </attribute> - <layout class="QVBoxLayout" name="verticalLayout_2"> - <item> - <widget class="QLabel" name="blurb"> - <property name="text"> - <string>blurb</string> - </property> - <property name="textFormat"> - <enum>Qt::RichText</enum> - </property> - <property name="wordWrap"> - <bool>true</bool> - </property> - <property name="openExternalLinks"> - <bool>true</bool> - </property> - </widget> - </item> - <item> - <widget class="QCheckBox" name="schedule"> - <property name="text"> - <string>&Schedule for download:</string> - </property> - </widget> - </item> - <item> - <layout class="QHBoxLayout" name="horizontalLayout_2"> - <item> - <widget class="QRadioButton" name="daily_button"> - <property name="text"> - <string>Every </string> - </property> - </widget> - </item> - <item> - <widget class="QComboBox" name="day"> - <item> - <property name="text"> - <string>day</string> - </property> - </item> - <item> - <property name="text"> - <string>Monday</string> - </property> - </item> - <item> - <property name="text"> - <string>Tuesday</string> - </property> - </item> - <item> - <property name="text"> - <string>Wednesday</string> - </property> - </item> - <item> - <property name="text"> - <string>Thursday</string> - </property> - </item> - <item> - <property name="text"> - <string>Friday</string> - </property> - </item> - <item> - <property name="text"> - <string>Saturday</string> - </property> - </item> - <item> - <property name="text"> - <string>Sunday</string> - </property> - </item> - </widget> - </item> - <item> - <widget class="QLabel" name="label_4"> - <property name="text"> - <string>at</string> - </property> - </widget> - </item> - <item> - <widget class="QTimeEdit" name="time"/> - </item> - <item> - <spacer name="horizontalSpacer"> - <property name="orientation"> - <enum>Qt::Horizontal</enum> - </property> - <property name="sizeHint" stdset="0"> - <size> - <width>40</width> - <height>20</height> - </size> - </property> - </spacer> - </item> - </layout> - </item> - <item> - <layout class="QHBoxLayout" name="horizontalLayout"> - <item> - <widget class="QRadioButton" name="interval_button"> - <property name="text"> - <string>Every </string> - </property> - </widget> - </item> - <item> - <widget class="QDoubleSpinBox" name="interval"> - <property name="sizePolicy"> - <sizepolicy hsizetype="Expanding" vsizetype="Fixed"> - <horstretch>0</horstretch> - <verstretch>0</verstretch> - </sizepolicy> - </property> - <property name="toolTip"> - <string>Interval at which to download this recipe. A value of zero means that the recipe will be downloaded every hour.</string> - </property> - <property name="suffix"> - <string> days</string> - </property> - <property name="decimals"> - <number>1</number> - </property> - <property name="minimum"> - <double>0.000000000000000</double> - </property> - <property name="maximum"> - <double>365.100000000000023</double> - </property> - <property name="singleStep"> - <double>1.000000000000000</double> - </property> - <property name="value"> - <double>1.000000000000000</double> - </property> - </widget> - </item> - </layout> - </item> - <item> - <widget class="QLabel" name="last_downloaded"> - <property name="text"> - <string/> - </property> - </widget> - </item> - <item> - <widget class="QGroupBox" name="account"> - <property name="title"> - <string>&Account</string> - </property> - <layout class="QGridLayout" name="gridLayout_2"> - <item row="0" column="1"> - <widget class="QLineEdit" name="username"/> - </item> - <item row="0" column="0"> - <widget class="QLabel" name="label_2"> - <property name="text"> - <string>&Username:</string> - </property> - <property name="buddy"> - <cstring>username</cstring> - </property> - </widget> - </item> - <item row="1" column="0"> - <widget class="QLabel" name="label_3"> - <property name="text"> - <string>&Password:</string> - </property> - <property name="buddy"> - <cstring>password</cstring> - </property> - </widget> - </item> - <item row="1" column="1"> - <widget class="QLineEdit" name="password"> - <property name="echoMode"> - <enum>QLineEdit::Password</enum> - </property> - </widget> - </item> - <item row="2" column="0"> - <widget class="QCheckBox" name="show_password"> - <property name="text"> - <string>&Show password</string> - </property> - </widget> - </item> - </layout> - </widget> - </item> - <item> - <widget class="QLabel" name="label"> - <property name="text"> - <string>For the scheduling to work, you must leave calibre running.</string> - </property> - <property name="wordWrap"> - <bool>true</bool> - </property> - </widget> - </item> - </layout> - </widget> - <widget class="QWidget" name="tab_2"> - <attribute name="title"> - <string>&Advanced</string> - </attribute> - <layout class="QVBoxLayout" name="verticalLayout_4"> - <item> - <widget class="QCheckBox" name="add_title_tag"> - <property name="text"> - <string>Add &title as tag</string> - </property> - </widget> - </item> - <item> - <widget class="QLabel" name="label_5"> - <property name="text"> - <string>&Extra tags:</string> - </property> - <property name="buddy"> - <cstring>custom_tags</cstring> - </property> - </widget> - </item> - <item> - <widget class="QLineEdit" name="custom_tags"/> - </item> - <item> - <spacer name="verticalSpacer"> - <property name="orientation"> - <enum>Qt::Vertical</enum> - </property> - <property name="sizeHint" stdset="0"> - <size> - <width>20</width> - <height>40</height> - </size> - </property> - </spacer> - </item> - </layout> - </widget> - </widget> - </item> - <item> - <widget class="QPushButton" name="download_button"> - <property name="text"> - <string>&Download now</string> - </property> - </widget> - </item> - </layout> - </widget> </widget> </item> </layout> </item> + <item row="0" column="1" rowspan="2"> + <widget class="QScrollArea" name="scrollArea"> + <property name="frameShape"> + <enum>QFrame::NoFrame</enum> + </property> + <property name="widgetResizable"> + <bool>true</bool> + </property> + <widget class="QWidget" name="scrollAreaWidgetContents"> + <property name="geometry"> + <rect> + <x>0</x> + <y>0</y> + <width>524</width> + <height>504</height> + </rect> + </property> + <layout class="QVBoxLayout" name="verticalLayout_5"> + <property name="margin"> + <number>0</number> + </property> + <item> + <widget class="QTabWidget" name="detail_box"> + <property name="currentIndex"> + <number>0</number> + </property> + <widget class="QWidget" name="tab"> + <attribute name="title"> + <string>&Schedule</string> + </attribute> + <layout class="QVBoxLayout" name="verticalLayout"> + <item> + <widget class="QLabel" name="blurb"> + <property name="text"> + <string>blurb</string> + </property> + <property name="wordWrap"> + <bool>true</bool> + </property> + <property name="openExternalLinks"> + <bool>true</bool> + </property> + </widget> + </item> + <item> + <spacer name="verticalSpacer_3"> + <property name="orientation"> + <enum>Qt::Vertical</enum> + </property> + <property name="sizeHint" stdset="0"> + <size> + <width>20</width> + <height>40</height> + </size> + </property> + </spacer> + </item> + <item> + <widget class="QFrame" name="frame"> + <property name="frameShape"> + <enum>QFrame::StyledPanel</enum> + </property> + <property name="frameShadow"> + <enum>QFrame::Raised</enum> + </property> + <layout class="QVBoxLayout" name="verticalLayout_2"> + <item> + <widget class="QCheckBox" name="schedule"> + <property name="text"> + <string>&Schedule for download:</string> + </property> + </widget> + </item> + <item> + <layout class="QHBoxLayout" name="horizontalLayout"> + <item> + <widget class="QRadioButton" name="days_of_week"> + <property name="text"> + <string>Days of week</string> + </property> + </widget> + </item> + <item> + <widget class="QRadioButton" name="days_of_month"> + <property name="text"> + <string>Days of month</string> + </property> + </widget> + </item> + <item> + <widget class="QRadioButton" name="every_x_days"> + <property name="text"> + <string>Every x days</string> + </property> + </widget> + </item> + </layout> + </item> + <item> + <widget class="QStackedWidget" name="schedule_stack"> + <property name="maximumSize"> + <size> + <width>16777215</width> + <height>75</height> + </size> + </property> + </widget> + </item> + <item> + <widget class="QLabel" name="last_downloaded"> + <property name="text"> + <string/> + </property> + <property name="wordWrap"> + <bool>true</bool> + </property> + </widget> + </item> + </layout> + </widget> + </item> + <item> + <spacer name="verticalSpacer_4"> + <property name="orientation"> + <enum>Qt::Vertical</enum> + </property> + <property name="sizeHint" stdset="0"> + <size> + <width>20</width> + <height>40</height> + </size> + </property> + </spacer> + </item> + <item> + <widget class="QGroupBox" name="account"> + <property name="title"> + <string>&Account</string> + </property> + <layout class="QGridLayout" name="gridLayout_2"> + <item row="0" column="1"> + <widget class="QLineEdit" name="username"/> + </item> + <item row="0" column="0"> + <widget class="QLabel" name="label_2"> + <property name="text"> + <string>&Username:</string> + </property> + <property name="buddy"> + <cstring>username</cstring> + </property> + </widget> + </item> + <item row="1" column="0"> + <widget class="QLabel" name="label_3"> + <property name="text"> + <string>&Password:</string> + </property> + <property name="buddy"> + <cstring>password</cstring> + </property> + </widget> + </item> + <item row="1" column="1"> + <widget class="QLineEdit" name="password"> + <property name="echoMode"> + <enum>QLineEdit::Password</enum> + </property> + </widget> + </item> + <item row="2" column="0"> + <widget class="QCheckBox" name="show_password"> + <property name="text"> + <string>&Show password</string> + </property> + </widget> + </item> + </layout> + </widget> + </item> + <item> + <widget class="QLabel" name="label"> + <property name="text"> + <string>For the scheduling to work, you must leave calibre running.</string> + </property> + <property name="wordWrap"> + <bool>true</bool> + </property> + </widget> + </item> + </layout> + </widget> + <widget class="QWidget" name="tab_2"> + <attribute name="title"> + <string>&Advanced</string> + </attribute> + <layout class="QGridLayout" name="gridLayout_3"> + <item row="0" column="0"> + <widget class="QCheckBox" name="add_title_tag"> + <property name="text"> + <string>Add &title as tag</string> + </property> + </widget> + </item> + <item row="1" column="0"> + <widget class="QLabel" name="label_5"> + <property name="text"> + <string>&Extra tags:</string> + </property> + <property name="buddy"> + <cstring>custom_tags</cstring> + </property> + </widget> + </item> + <item row="3" column="0" colspan="2"> + <widget class="QLabel" name="label_6"> + <property name="toolTip"> + <string>Maximum number of copies (issues) of this recipe to keep. Set to 0 to keep all (disable).</string> + </property> + <property name="text"> + <string>&Keep at most:</string> + </property> + <property name="buddy"> + <cstring>keep_issues</cstring> + </property> + </widget> + </item> + <item row="3" column="2"> + <widget class="QSpinBox" name="keep_issues"> + <property name="toolTip"> + <string><p>When set, this option will cause calibre to keep, at most, the specified number of issues of this periodical. Every time a new issue is downloaded, the oldest one is deleted, if the total is larger than this number. +<p>Note that this feature only works if you have the option to add the title as tag checked, above. +<p>Also, the setting for deleting periodicals older than a number of days, below, takes priority over this setting.</string> + </property> + <property name="specialValueText"> + <string>all issues</string> + </property> + <property name="suffix"> + <string> issues</string> + </property> + <property name="maximum"> + <number>100000</number> + </property> + </widget> + </item> + <item row="4" column="1"> + <spacer name="verticalSpacer"> + <property name="orientation"> + <enum>Qt::Vertical</enum> + </property> + <property name="sizeHint" stdset="0"> + <size> + <width>20</width> + <height>40</height> + </size> + </property> + </spacer> + </item> + <item row="1" column="2"> + <widget class="QLineEdit" name="custom_tags"/> + </item> + </layout> + </widget> + </widget> + </item> + <item> + <spacer name="verticalSpacer_2"> + <property name="orientation"> + <enum>Qt::Vertical</enum> + </property> + <property name="sizeHint" stdset="0"> + <size> + <width>20</width> + <height>40</height> + </size> + </property> + </spacer> + </item> + <item> + <widget class="QPushButton" name="download_button"> + <property name="text"> + <string>&Download now</string> + </property> + </widget> + </item> + </layout> + </widget> + </widget> + </item> + <item row="1" column="0"> + <widget class="QTreeView" name="recipes"> + <property name="sizePolicy"> + <sizepolicy hsizetype="Minimum" vsizetype="Expanding"> + <horstretch>0</horstretch> + <verstretch>0</verstretch> + </sizepolicy> + </property> + <property name="showDropIndicator" stdset="0"> + <bool>false</bool> + </property> + <property name="iconSize"> + <size> + <width>16</width> + <height>16</height> + </size> + </property> + <property name="animated"> + <bool>true</bool> + </property> + <property name="headerHidden"> + <bool>true</bool> + </property> + </widget> + </item> + <item row="2" column="0"> + <widget class="QLabel" name="count_label"> + <property name="text"> + <string/> + </property> + <property name="alignment"> + <set>Qt::AlignCenter</set> + </property> + </widget> + </item> <item row="2" column="1"> + <layout class="QHBoxLayout" name="horizontalLayout_3"> + <item> + <widget class="QLabel" name="label_7"> + <property name="text"> + <string>&Delete downloaded news older than:</string> + </property> + <property name="buddy"> + <cstring>old_news</cstring> + </property> + </widget> + </item> + <item> + <widget class="QSpinBox" name="old_news"> + <property name="toolTip"> + <string><p>Delete downloaded news older than the specified number of days. Set to zero to disable. +<p>You can also control the maximum number of issues of a specific periodical that are kept by clicking the Advanced tab for that periodical above.</string> + </property> + <property name="specialValueText"> + <string>never delete</string> + </property> + <property name="suffix"> + <string> days</string> + </property> + <property name="maximum"> + <number>1000</number> + </property> + </widget> + </item> + </layout> + </item> + <item row="3" column="0"> + <widget class="QPushButton" name="download_all_button"> + <property name="toolTip"> + <string>Download all scheduled news sources at once</string> + </property> + <property name="text"> + <string>Download &all scheduled</string> + </property> + <property name="icon"> + <iconset resource="../../../../resources/images.qrc"> + <normaloff>:/images/news.png</normaloff>:/images/news.png</iconset> + </property> + </widget> + </item> + <item row="3" column="1"> <widget class="QDialogButtonBox" name="buttonBox"> <property name="orientation"> <enum>Qt::Horizontal</enum> @@ -375,24 +419,15 @@ </property> </widget> </item> - <item row="1" column="1"> - <widget class="QSpinBox" name="old_news"> - <property name="toolTip"> - <string>Delete downloaded news older than the specified number of days. Set to zero to disable.</string> - </property> - <property name="suffix"> - <string> days</string> - </property> - <property name="prefix"> - <string>Delete downloaded news older than </string> - </property> - <property name="maximum"> - <number>1000</number> - </property> - </widget> - </item> </layout> </widget> + <customwidgets> + <customwidget> + <class>SearchBox2</class> + <extends>QComboBox</extends> + <header>calibre/gui2/search_box.h</header> + </customwidget> + </customwidgets> <resources> <include location="../../../../resources/images.qrc"/> </resources> @@ -430,50 +465,18 @@ </hints> </connection> <connection> - <sender>daily_button</sender> + <sender>add_title_tag</sender> <signal>toggled(bool)</signal> - <receiver>day</receiver> + <receiver>keep_issues</receiver> <slot>setEnabled(bool)</slot> <hints> <hint type="sourcelabel"> - <x>456</x> - <y>173</y> + <x>508</x> + <y>42</y> </hint> <hint type="destinationlabel"> - <x>537</x> - <y>176</y> - </hint> - </hints> - </connection> - <connection> - <sender>daily_button</sender> - <signal>toggled(bool)</signal> - <receiver>time</receiver> - <slot>setEnabled(bool)</slot> - <hints> - <hint type="sourcelabel"> - <x>456</x> - <y>173</y> - </hint> - <hint type="destinationlabel"> - <x>647</x> - <y>176</y> - </hint> - </hints> - </connection> - <connection> - <sender>interval_button</sender> - <signal>toggled(bool)</signal> - <receiver>interval</receiver> - <slot>setEnabled(bool)</slot> - <hints> - <hint type="sourcelabel"> - <x>456</x> - <y>239</y> - </hint> - <hint type="destinationlabel"> - <x>495</x> - <y>218</y> + <x>577</x> + <y>108</y> </hint> </hints> </connection> diff --git a/src/calibre/gui2/dialogs/search.ui b/src/calibre/gui2/dialogs/search.ui index eb6fffdb60..f3f96547bd 100644 --- a/src/calibre/gui2/dialogs/search.ui +++ b/src/calibre/gui2/dialogs/search.ui @@ -177,7 +177,7 @@ </size> </property> <property name="text"> - <string>See the <a href="http://calibre-ebook.com/user_manual/gui.html#the-search-interface">User Manual</a> for more help</string> + <string>See the <a href="http://manual.calibre-ebook.com/gui.html#the-search-interface">User Manual</a> for more help</string> </property> <property name="openExternalLinks"> <bool>true</bool> diff --git a/src/calibre/gui2/dialogs/select_formats.py b/src/calibre/gui2/dialogs/select_formats.py index 5934c8c0f9..aea56ad196 100644 --- a/src/calibre/gui2/dialogs/select_formats.py +++ b/src/calibre/gui2/dialogs/select_formats.py @@ -44,7 +44,7 @@ class SelectFormats(QDialog): self.setLayout(self._l) self.setWindowTitle(_('Choose formats')) self._m = QLabel(msg) - self._m.setWordWrap = True + self._m.setWordWrap(True) self._l.addWidget(self._m) self.formats = Formats(fmt_list) self.fview = QListView(self) diff --git a/src/calibre/gui2/dialogs/tag_categories.py b/src/calibre/gui2/dialogs/tag_categories.py index 9bddb817cf..49f8fc6457 100644 --- a/src/calibre/gui2/dialogs/tag_categories.py +++ b/src/calibre/gui2/dialogs/tag_categories.py @@ -59,14 +59,24 @@ class TagCategories(QDialog, Ui_TagCategories): ] category_names = ['', _('Authors'), _('Series'), _('Publishers'), _('Tags')] - cc_map = self.db.custom_column_label_map - for cc in cc_map: - if cc_map[cc]['datatype'] in ['text', 'series']: - self.category_labels.append(db.field_metadata.label_to_key(cc)) + cvals = {} + for key,cc in self.db.custom_field_metadata().iteritems(): + if cc['datatype'] in ['text', 'series', 'enumeration']: + self.category_labels.append(key) category_icons.append(cc_icon) - category_values.append(lambda col=cc: self.db.all_custom(label=col)) - category_names.append(cc_map[cc]['name']) - + category_values.append(lambda col=cc['label']: self.db.all_custom(label=col)) + category_names.append(cc['name']) + elif cc['datatype'] == 'composite' and \ + cc['display'].get('make_category', False): + self.category_labels.append(key) + category_icons.append(cc_icon) + category_names.append(cc['name']) + dex = cc['rec_index'] + cvals = set() + for book in db.data.iterall(): + if book[dex]: + cvals.add(book[dex]) + category_values.append(lambda s=list(cvals): s) self.all_items = [] self.all_items_dict = {} for idx,label in enumerate(self.category_labels): @@ -88,7 +98,8 @@ class TagCategories(QDialog, Ui_TagCategories): if l[1] in self.category_labels: if t is None: t = Item(name=l[0], label=l[1], index=len(self.all_items), - icon=category_icons[self.category_labels.index(l[1])], exists=False) + icon=category_icons[self.category_labels.index(l[1])], + exists=False) self.all_items.append(t) self.all_items_dict[key] = t l[2] = t.index @@ -108,13 +119,16 @@ class TagCategories(QDialog, Ui_TagCategories): self.add_category_button.clicked.connect(self.add_category) self.rename_category_button.clicked.connect(self.rename_category) self.category_box.currentIndexChanged[int].connect(self.select_category) - self.category_filter_box.currentIndexChanged[int].connect(self.display_filtered_categories) + self.category_filter_box.currentIndexChanged[int].connect( + self.display_filtered_categories) self.delete_category_button.clicked.connect(self.del_category) if islinux: self.available_items_box.itemDoubleClicked.connect(self.apply_tags) else: - self.connect(self.available_items_box, SIGNAL('itemActivated(QListWidgetItem*)'), self.apply_tags) - self.connect(self.applied_items_box, SIGNAL('itemActivated(QListWidgetItem*)'), self.unapply_tags) + self.connect(self.available_items_box, + SIGNAL('itemActivated(QListWidgetItem*)'), self.apply_tags) + self.connect(self.applied_items_box, + SIGNAL('itemActivated(QListWidgetItem*)'), self.unapply_tags) self.populate_category_list() if on_category is not None: @@ -129,6 +143,7 @@ class TagCategories(QDialog, Ui_TagCategories): n = item.name if item.exists else item.name + _(' (not on any book)') w = QListWidgetItem(item.icon, n) w.setData(Qt.UserRole, item.index) + w.setToolTip(_('Category lookup name: ') + item.label) return w def display_filtered_categories(self, idx): @@ -178,8 +193,10 @@ class TagCategories(QDialog, Ui_TagCategories): 'multiple periods in a row or spaces before ' 'or after periods.')).exec_() return False - for c in self.categories: - if strcmp(c, cat_name) == 0: + for c in sorted(self.categories.keys(), key=sort_key): + if strcmp(c, cat_name) == 0 or \ + (icu_lower(cat_name).startswith(icu_lower(c) + '.') and\ + not cat_name.startswith(c + '.')): error_dialog(self, _('Name already used'), _('That name is already used, perhaps with different case.')).exec_() return False diff --git a/src/calibre/gui2/dialogs/tag_categories.ui b/src/calibre/gui2/dialogs/tag_categories.ui index 0b17ccac05..e6fedf9bde 100644 --- a/src/calibre/gui2/dialogs/tag_categories.ui +++ b/src/calibre/gui2/dialogs/tag_categories.ui @@ -79,7 +79,7 @@ </property> <property name="icon"> <iconset> - <normaloff>:/images/minus.png</normaloff>:/images/minus.png</iconset> + <normaloff>:/images/trash.png</normaloff>:/images/trash.png</iconset> </property> </widget> </item> diff --git a/src/calibre/gui2/dialogs/tag_editor.py b/src/calibre/gui2/dialogs/tag_editor.py index 6bd8eb7dbe..bf3bb9fd4e 100644 --- a/src/calibre/gui2/dialogs/tag_editor.py +++ b/src/calibre/gui2/dialogs/tag_editor.py @@ -122,6 +122,8 @@ class TagEditor(QDialog, Ui_TagEditor): tags = unicode(self.add_tag_input.text()).split(',') for tag in tags: tag = tag.strip() + if not tag: + continue for item in self.available_tags.findItems(tag, Qt.MatchFixedString): self.available_tags.takeItem(self.available_tags.row(item)) if tag not in self.tags: diff --git a/src/calibre/gui2/dialogs/template_dialog.py b/src/calibre/gui2/dialogs/template_dialog.py index 174056ef80..083dacbf00 100644 --- a/src/calibre/gui2/dialogs/template_dialog.py +++ b/src/calibre/gui2/dialogs/template_dialog.py @@ -5,21 +5,213 @@ __license__ = 'GPL v3' import json -from PyQt4.Qt import Qt, QDialog, QDialogButtonBox +from PyQt4.Qt import (Qt, QDialog, QDialogButtonBox, QSyntaxHighlighter, + QRegExp, QApplication, + QTextCharFormat, QFont, QColor, QCursor) + from calibre.gui2.dialogs.template_dialog_ui import Ui_TemplateDialog from calibre.utils.formatter_functions import formatter_functions +from calibre.ebooks.metadata.book.base import composite_formatter + +class ParenPosition: + + def __init__(self, block, pos, paren): + self.block = block + self.pos = pos + self.paren = paren + self.highlight = False + + def set_highlight(self, to_what): + self.highlight = to_what + +class TemplateHighlighter(QSyntaxHighlighter): + + Config = {} + Rules = [] + Formats = {} + BN_FACTOR = 1000 + + KEYWORDS = ["program"] + + def __init__(self, parent=None): + super(TemplateHighlighter, self).__init__(parent) + + self.initializeFormats() + + TemplateHighlighter.Rules.append((QRegExp( + "|".join([r"\b%s\b" % keyword for keyword in self.KEYWORDS])), + "keyword")) + TemplateHighlighter.Rules.append((QRegExp( + "|".join([r"\b%s\b" % builtin for builtin in + formatter_functions.get_builtins()])), + "builtin")) + + TemplateHighlighter.Rules.append((QRegExp( + r"\b[+-]?[0-9]+[lL]?\b" + r"|\b[+-]?0[xX][0-9A-Fa-f]+[lL]?\b" + r"|\b[+-]?[0-9]+(?:\.[0-9]+)?(?:[eE][+-]?[0-9]+)?\b"), + "number")) + + stringRe = QRegExp(r"""(?:[^:]'[^']*'|"[^"]*")""") + stringRe.setMinimal(True) + TemplateHighlighter.Rules.append((stringRe, "string")) + + lparenRe = QRegExp(r'\(') + lparenRe.setMinimal(True) + TemplateHighlighter.Rules.append((lparenRe, "lparen")) + rparenRe = QRegExp(r'\)') + rparenRe.setMinimal(True) + TemplateHighlighter.Rules.append((rparenRe, "rparen")) + + self.regenerate_paren_positions() + self.highlighted_paren = False + + def initializeFormats(self): + Config = self.Config + Config["fontfamily"] = "monospace" + #Config["fontsize"] = 10 + for name, color, bold, italic in ( + ("normal", "#000000", False, False), + ("keyword", "#000080", True, False), + ("builtin", "#0000A0", False, False), + ("comment", "#007F00", False, True), + ("string", "#808000", False, False), + ("number", "#924900", False, False), + ("lparen", "#000000", True, True), + ("rparen", "#000000", True, True)): + Config["%sfontcolor" % name] = color + Config["%sfontbold" % name] = bold + Config["%sfontitalic" % name] = italic + + baseFormat = QTextCharFormat() + baseFormat.setFontFamily(Config["fontfamily"]) + #baseFormat.setFontPointSize(Config["fontsize"]) + + for name in ("normal", "keyword", "builtin", "comment", + "string", "number", "lparen", "rparen"): + format = QTextCharFormat(baseFormat) + format.setForeground(QColor(Config["%sfontcolor" % name])) + if Config["%sfontbold" % name]: + format.setFontWeight(QFont.Bold) + format.setFontItalic(Config["%sfontitalic" % name]) + self.Formats[name] = format + + def find_paren(self, bn, pos): + dex = bn * self.BN_FACTOR + pos + return self.paren_pos_map.get(dex, None) + + def highlightBlock(self, text): + bn = self.currentBlock().blockNumber() + textLength = text.length() + + self.setFormat(0, textLength, self.Formats["normal"]) + + if text.isEmpty(): + pass + elif text[0] == "#": + self.setFormat(0, text.length(), self.Formats["comment"]) + return + + for regex, format in TemplateHighlighter.Rules: + i = regex.indexIn(text) + while i >= 0: + length = regex.matchedLength() + if format in ['lparen', 'rparen']: + pp = self.find_paren(bn, i) + if pp and pp.highlight: + self.setFormat(i, length, self.Formats[format]) + else: + self.setFormat(i, length, self.Formats[format]) + i = regex.indexIn(text, i + length) + + if self.generate_paren_positions: + t = unicode(text) + i = 0 + foundQuote = False + while i < len(t): + c = t[i] + if c == ':': + # Deal with the funky syntax of template program mode. + # This won't work if there are more than one template + # expression in the document. + if not foundQuote and i+1 < len(t) and t[i+1] == "'": + i += 2 + elif c in ["'", '"']: + foundQuote = True + i += 1 + j = t[i:].find(c) + if j < 0: + i = len(t) + else: + i = i + j + elif c in ['(', ')']: + pp = ParenPosition(bn, i, c) + self.paren_positions.append(pp) + self.paren_pos_map[bn*self.BN_FACTOR+i] = pp + i += 1 + + def rehighlight(self): + QApplication.setOverrideCursor(QCursor(Qt.WaitCursor)) + QSyntaxHighlighter.rehighlight(self) + QApplication.restoreOverrideCursor() + + def check_cursor_pos(self, chr, block, pos_in_block): + found_pp = -1 + for i, pp in enumerate(self.paren_positions): + pp.set_highlight(False) + if pp.block == block and pp.pos == pos_in_block: + found_pp = i + + if chr not in ['(', ')']: + if self.highlighted_paren: + self.rehighlight() + self.highlighted_paren = False + return + + if found_pp >= 0: + stack = 0 + if chr == '(': + list = self.paren_positions[found_pp+1:] + else: + list = reversed(self.paren_positions[0:found_pp]) + for pp in list: + if pp.paren == chr: + stack += 1; + elif stack: + stack -= 1 + else: + pp.set_highlight(True) + self.paren_positions[found_pp].set_highlight(True) + break + self.highlighted_paren = True + self.rehighlight() + + def regenerate_paren_positions(self): + self.generate_paren_positions = True + self.paren_positions = [] + self.paren_pos_map = {} + self.rehighlight() + self.generate_paren_positions = False class TemplateDialog(QDialog, Ui_TemplateDialog): - def __init__(self, parent, text): + def __init__(self, parent, text, mi): QDialog.__init__(self, parent) Ui_TemplateDialog.__init__(self) self.setupUi(self) + + self.mi = mi + # Remove help icon on title bar icon = self.windowIcon() self.setWindowFlags(self.windowFlags()&(~Qt.WindowContextHelpButtonHint)) self.setWindowIcon(icon) + self.last_text = '' + self.highlighter = TemplateHighlighter(self.textbox.document()) + self.textbox.cursorPositionChanged.connect(self.text_cursor_changed) + self.textbox.textChanged.connect(self.textbox_changed) + self.textbox.setTabStopWidth(10) self.source_code.setTabStopWidth(10) self.documentation.setReadOnly(True) @@ -45,6 +237,26 @@ class TemplateDialog(QDialog, Ui_TemplateDialog): self.function.addItems(func_names) self.function.setCurrentIndex(0) self.function.currentIndexChanged[str].connect(self.function_changed) + self.textbox_changed() + + def textbox_changed(self): + cur_text = unicode(self.textbox.toPlainText()) + if self.last_text != cur_text: + self.last_text = cur_text + self.highlighter.regenerate_paren_positions() + self.template_value.setText( + composite_formatter.safe_format(cur_text, self.mi, + _('EXCEPTION: '), self.mi)) + + def text_cursor_changed(self): + cursor = self.textbox.textCursor() + block_number = cursor.blockNumber() + pos_in_block = cursor.positionInBlock() + position = cursor.position() + t = unicode(self.textbox.toPlainText()) + if position < len(t): + self.highlighter.check_cursor_pos(t[position], block_number, + pos_in_block) def function_changed(self, toWhat): name = unicode(toWhat) diff --git a/src/calibre/gui2/dialogs/template_dialog.ui b/src/calibre/gui2/dialogs/template_dialog.ui index dd8fb7bd88..d36cbbd3d4 100644 --- a/src/calibre/gui2/dialogs/template_dialog.ui +++ b/src/calibre/gui2/dialogs/template_dialog.ui @@ -23,19 +23,39 @@ <item> <widget class="QPlainTextEdit" name="textbox"/> </item> - <item> - <widget class="QDialogButtonBox" name="buttonBox"> - <property name="orientation"> - <enum>Qt::Horizontal</enum> - </property> - <property name="standardButtons"> - <set>QDialogButtonBox::Cancel|QDialogButtonBox::Ok</set> - </property> - </widget> - </item> <item> <layout class="QGridLayout" name="gridLayout"> <item row="0" column="0"> + <widget class="QLabel"> + <property name="text"> + <string>Template value:</string> + </property> + <property name="buddy"> + <cstring>template_value</cstring> + </property> + <property name="toolTip"> + <string>The value the of the template using the current book in the library view</string> + </property> + </widget> + </item> + <item row="0" column="1"> + <widget class="QLineEdit" name="template_value"> + <property name="readOnly"> + <bool>true</bool> + </property> + </widget> + </item> + <item row="1" column="1"> + <widget class="QDialogButtonBox" name="buttonBox"> + <property name="orientation"> + <enum>Qt::Horizontal</enum> + </property> + <property name="standardButtons"> + <set>QDialogButtonBox::Cancel|QDialogButtonBox::Ok</set> + </property> + </widget> + </item> + <item row="2" column="0"> <widget class="QLabel" name="label"> <property name="text"> <string>Function &name:</string> @@ -45,10 +65,10 @@ </property> </widget> </item> - <item row="0" column="1"> + <item row="2" column="1"> <widget class="QComboBox" name="function"/> </item> - <item row="1" column="0"> + <item row="3" column="0"> <widget class="QLabel" name="label_2"> <property name="text"> <string>&Documentation:</string> @@ -61,7 +81,7 @@ </property> </widget> </item> - <item row="2" column="0"> + <item row="4" column="0"> <widget class="QLabel" name="label_3"> <property name="text"> <string>Python &code:</string> @@ -74,7 +94,7 @@ </property> </widget> </item> - <item row="1" column="1"> + <item row="3" column="1"> <widget class="QPlainTextEdit" name="documentation"> <property name="maximumSize"> <size> @@ -84,7 +104,7 @@ </property> </widget> </item> - <item row="2" column="1"> + <item row="4" column="1"> <widget class="QPlainTextEdit" name="source_code"/> </item> </layout> diff --git a/src/calibre/gui2/dialogs/template_line_editor.py b/src/calibre/gui2/dialogs/template_line_editor.py new file mode 100644 index 0000000000..6a0b07200e --- /dev/null +++ b/src/calibre/gui2/dialogs/template_line_editor.py @@ -0,0 +1,487 @@ +#!/usr/bin/env python +# vim:fileencoding=UTF-8:ts=4:sw=4:sta:et:sts=4:ai + +__license__ = 'GPL v3' +__copyright__ = '2010, Kovid Goyal <kovid@kovidgoyal.net>' +__docformat__ = 'restructuredtext en' + +from functools import partial +from collections import defaultdict + +from PyQt4.Qt import (Qt, QLineEdit, QDialog, QGridLayout, QLabel, QCheckBox, + QIcon, QDialogButtonBox, QColor, QComboBox, QPushButton) + +from calibre.ebooks.metadata.book.base import composite_formatter +from calibre.gui2.dialogs.template_dialog import TemplateDialog +from calibre.gui2.complete import MultiCompleteLineEdit +from calibre.gui2 import error_dialog +from calibre.utils.icu import sort_key + +class TemplateLineEditor(QLineEdit): + + ''' + Extend the context menu of a QLineEdit to include more actions. + ''' + + def __init__(self, parent): + QLineEdit.__init__(self, parent) + self.tags = None + self.mi = None + self.txt = None + + def set_mi(self, mi): + self.mi = mi + + def set_db(self, db): + self.db = db + + def contextMenuEvent(self, event): + menu = self.createStandardContextMenu() + menu.addSeparator() + + action_clear_field = menu.addAction(_('Remove any template from the box')) + action_clear_field.triggered.connect(self.clear_field) + action_open_editor = menu.addAction(_('Open Template Editor')) + action_open_editor.triggered.connect(self.open_editor) + menu.exec_(event.globalPos()) + + def clear_field(self): + self.txt = None + self.setText('') + self.setReadOnly(False) + self.setStyleSheet('TemplateLineEditor { color: black }') + + def open_editor(self): + if self.txt: + t = TemplateDialog(self, self.txt, self.mi) + else: + t = TemplateDialog(self, self.text(), self.mi) + t.setWindowTitle(_('Edit template')) + if t.exec_(): + self.txt = None + self.setText(t.textbox.toPlainText()) + + def enable_wizard_button(self, txt): + if not txt or txt.startswith('program:\n#tag wizard'): + return True + return False + + def setText(self, txt): + txt = unicode(txt) + if txt and txt.startswith('program:\n#tag wizard'): + self.txt = txt + self.setReadOnly(True) + QLineEdit.setText(self, '') + QLineEdit.setText(self, _('Template generated by the wizard')) + self.setStyleSheet('TemplateLineEditor { color: gray }') + else: + QLineEdit.setText(self, txt) + + def tag_wizard(self): + txt = unicode(self.text()) + if txt and not self.txt: + error_dialog(self, _('Invalid text'), + _('The text in the box was not generated by this wizard'), + show=True, show_copy_button=False) + return + d = TagWizard(self, self.db, unicode(self.txt), self.mi) + if d.exec_(): + self.setText(d.template) + + def text(self): + if self.txt: + return self.txt + return QLineEdit.text(self) + +class TagWizard(QDialog): + + text_template = " strcmp(field('{f}'), '{v}', '{fv}', '{tv}', '{fv}')" + text_empty_template = " test(field('{f}'), '{fv}', '{tv}')" + text_re_template = " contains(field('{f}'), '{v}', '{tv}', '{fv}')" + + templates = { + 'text.mult' : " str_in_list(field('{f}'), '{mult}', '{v}', '{tv}', '{fv}')", + 'text.mult.re' : " in_list(field('{f}'), '{mult}', '^{v}$', '{tv}', '{fv}')", + 'text.mult.empty' : " test(field('{f}'), '{fv}', '{tv}')", + 'text' : text_template, + 'text.re' : text_re_template, + 'text.empty' : text_empty_template, + 'rating' : " cmp(field('{f}'), '{v}', '{fv}', '{tv}', '{fv}')", + 'rating.empty' : text_empty_template, + 'int' : " cmp(field('{f}'), '{v}', '{fv}', '{tv}', '{fv}')", + 'int.empty' : text_empty_template, + 'float' : " cmp(field('{f}'), '{v}', '{fv}', '{tv}', '{fv}')", + 'float.empty' : text_empty_template, + 'bool' : " strcmp(field('{f}'), '{v}', '{fv}', '{tv}', '{fv}')", + 'bool.empty' : text_empty_template, + 'series' : text_template, + 'series.re' : text_re_template, + 'series.empty' : text_empty_template, + 'composite' : text_template, + 'composite.re' : text_re_template, + 'composite.empty' : text_empty_template, + 'enumeration' : text_template, + 'enumeration.re' : text_re_template, + 'enumeration.empty' : text_empty_template, + 'comments' : text_template, + 'comments.re' : text_re_template, + 'comments.empty' : text_empty_template, + } + + def __init__(self, parent, db, txt, mi): + QDialog.__init__(self, parent) + self.setWindowTitle(_('Coloring Wizard')) + self.setWindowIcon(QIcon(I('wizard.png'))) + + self.mi = mi + + self.columns = [] + self.completion_values = defaultdict(dict) + for k in db.all_field_keys(): + m = db.metadata_for_field(k) + if k.endswith('_index') or ( + m['kind'] == 'field' and m['name'] and + k not in ('ondevice', 'path', 'size', 'sort') and + m['datatype'] not in ('datetime')): + self.columns.append(k) + self.completion_values[k]['dt'] = m['datatype'] + if m['is_custom']: + if m['datatype'] in ('int', 'float'): + self.completion_values[k]['v'] = [] + elif m['datatype'] == 'bool': + self.completion_values[k]['v'] = [_('Yes'), _('No')] + else: + self.completion_values[k]['v'] = db.all_custom(m['label']) + elif k == 'tags': + self.completion_values[k]['v'] = db.all_tags() + elif k == 'formats': + self.completion_values[k]['v'] = db.all_formats() + else: + if k in ('publisher'): + ck = k + 's' + else: + ck = k + f = getattr(db, 'all_' + ck, None) + if f: + if k == 'authors': + self.completion_values[k]['v'] = [v[1].\ + replace('|', ',') for v in f()] + else: + self.completion_values[k]['v'] = [v[1] for v in f()] + else: + self.completion_values[k]['v'] = [] + + if k in self.completion_values: + if k == 'authors': + mult = '&' + else: + mult = ',' if m['is_multiple'] == '|' else m['is_multiple'] + self.completion_values[k]['m'] = mult + + self.columns.sort(key=sort_key) + self.columns.insert(0, '') + + l = QGridLayout() + self.setLayout(l) + l.setColumnStretch(2, 10) + l.setColumnMinimumWidth(5, 300) + + h = QLabel(_('And')) + h.setToolTip('<p>' + + _('Set this box to indicate that the two conditions must both ' + 'be true to use the color. For example, you ' + 'can check if two tags are present, if the book has a tag ' + 'and a #read custom column is checked, or if a book has ' + 'some tag and has a particular format.')) + l.addWidget(h, 0, 0, 1, 1) + + h = QLabel(_('Column')) + h.setAlignment(Qt.AlignCenter) + l.addWidget(h, 0, 1, 1, 1) + + h = QLabel(_('is')) + h.setAlignment(Qt.AlignCenter) + l.addWidget(h, 0, 2, 1, 1) + + h = QLabel(_('not')) + h.setToolTip('<p>' + + _('Check this box to indicate that the value must <b>not</b> match ' + 'to use the color. For example, you can check if a tag does ' + 'not exist by entering that tag and checking this box.') + '</p>') + h.setAlignment(Qt.AlignCenter) + l.addWidget(h, 0, 3, 1, 1) + + c = QLabel(_('empty')) + c.setToolTip('<p>' + + _('Check this box to check if the column is empty') + '</p>') + l.addWidget(c, 0, 4, 1, 1) + + h = QLabel(_('Values')) + h.setAlignment(Qt.AlignCenter) + h.setToolTip('<p>' + + _('You can enter more than one value per box, separated by commas. ' + 'The comparison ignores letter case. Special note: authors are ' + 'separated by ampersands (&).<br>' + 'A value can be a regular expression. Check the box to turn ' + 'them on. When using regular expressions, note that the wizard ' + 'puts anchors (^ and $) around the expression, so you ' + 'must ensure your expression matches from the beginning ' + 'to the end of the column/value you are checking.<br>' + 'Regular expression examples:') + '<ul>' + + _('<li><code><b>.*</b></code> matches anything in the column.</li>' + '<li><code><b>A.*</b></code> matches anything beginning with A</li>' + '<li><code><b>.*mystery.*</b></code> matches anything containing ' + 'the word "mystery"</li>') + '</ul></p>') + l.addWidget(h , 0, 5, 1, 1) + + c = QLabel(_('is RE')) + c.setToolTip('<p>' + + _('Check this box if the values box contains regular expressions') + '</p>') + l.addWidget(c, 0, 6, 1, 1) + + c = QLabel(_('color')) + c.setAlignment(Qt.AlignCenter) + c.setToolTip('<p>' + + _('Use this color if the column matches the tests.') + '</p>') + l.addWidget(c, 0, 7, 1, 1) + + self.andboxes = [] + self.notboxes = [] + self.tagboxes = [] + self.colorboxes = [] + self.reboxes = [] + self.colboxes = [] + self.emptyboxes = [] + + self.colors = [unicode(s) for s in list(QColor.colorNames())] + self.colors.insert(0, '') + + def create_widget(klass, box, layout, row, col, items, + align=Qt.AlignCenter, rowspan=False): + w = klass(self) + if box is not None: + box.append(w) + if rowspan: + layout.addWidget(w, row, col, 2, 1, alignment=Qt.Alignment(align)) + else: + layout.addWidget(w, row, col, 1, 1, alignment=Qt.Alignment(align)) + if items: + w.addItems(items) + return w + + maxlines = 10 + for i in range(1, maxlines+1): + w = create_widget(QCheckBox, self.andboxes, l, i, 0, None, rowspan=True) + w.stateChanged.connect(partial(self.and_box_changed, line=i-1)) + if i == maxlines: + # last box is invisible + w.setVisible(False) + + w = create_widget(QComboBox, self.colboxes, l, i, 1, self.columns) + w.currentIndexChanged[str].connect(partial(self.column_changed, line=i-1)) + + w = QLabel(self) + w.setText(_('is')) + l.addWidget(w, i, 2, 1, 1) + + create_widget(QCheckBox, self.notboxes, l, i, 3, None) + + w = create_widget(QCheckBox, self.emptyboxes, l, i, 4, None) + w.stateChanged.connect(partial(self.empty_box_changed, line=i-1)) + + create_widget(MultiCompleteLineEdit, self.tagboxes, l, i, 5, None, align=0) + create_widget(QCheckBox, self.reboxes, l, i, 6, None) + create_widget(QComboBox, self.colorboxes, l, i, 7, self.colors) + + w = create_widget(QLabel, None, l, maxlines+1, 5, None) + w.setText(_('If none of the tests match, set the color to')) + self.elsebox = create_widget(QComboBox, None, l, maxlines+1, 7, self.colors) + self.elsebox.setToolTip('<p>' + + _('If this box contains a color, it will be used if none ' + 'of the above rules match.') + '</p>') + + if txt: + lines = txt.split('\n')[3:] + i = 0 + for line in lines: + if line.startswith('#'): + vals = line[1:].split(':|:') + if len(vals) == 1 and line.startswith('#else:'): + try: + self.elsebox.setCurrentIndex(self.elsebox.findText(line[6:])) + except: + pass + continue + if len(vals) == 2: + t, c = vals + f = 'tags' + a = n = e = re = False + else: + t,c,f,re,a,n,e = vals + try: + self.colboxes[i].setCurrentIndex(self.colboxes[i].findText(f)) + self.colorboxes[i].setCurrentIndex( + self.colorboxes[i].findText(c)) + self.tagboxes[i].setText(t) + self.reboxes[i].setChecked(re == '2') + self.andboxes[i].setChecked(a == '2') + self.notboxes[i].setChecked(n == '2') + self.emptyboxes[i].setChecked(e == '2') + i += 1 + except: + pass + + w = QLabel(_('Preview')) + l.addWidget(w, 99, 1, 1, 1) + w = self.test_box = QLineEdit(self) + w.setReadOnly(True) + l.addWidget(w, 99, 2, 1, 5) + w = QPushButton(_('Test')) + w.setToolTip('<p>' + + _('Press this button to see what color this template will ' + 'produce for the book that was selected when you ' + 'entered the preferences dialog.')) + l.addWidget(w, 99, 7, 1, 1) + w.clicked.connect(self.preview) + + bb = QDialogButtonBox(QDialogButtonBox.Ok|QDialogButtonBox.Cancel, parent=self) + l.addWidget(bb, 100, 5, 1, 3) + bb.accepted.connect(self.accepted) + bb.rejected.connect(self.reject) + self.template = '' + + def preview(self): + if not self.generate_program(): + return + t = composite_formatter.safe_format(self.template, self.mi, + _('EXCEPTION'), self.mi) + self.test_box.setText(t) + + def column_changed(self, s, line=None): + k = unicode(s) + if k in self.completion_values: + valbox = self.tagboxes[line] + valbox.update_items_cache(self.completion_values[k]['v']) + if self.completion_values[k]['m']: + valbox.set_separator(', ') + else: + valbox.set_separator(None) + + dt = self.completion_values[k]['dt'] + if dt in ('int', 'float', 'rating', 'bool'): + self.reboxes[line].setChecked(0) + self.reboxes[line].setEnabled(False) + else: + self.reboxes[line].setEnabled(True) + else: + valbox.update_items_cache([]) + valbox.set_separator(None) + + def generate_program(self): + res = ("program:\n#tag wizard -- do not directly edit\n" + " first_non_empty(\n") + lines = [] + was_and = had_line = False + + line = 0 + for tb, cb, fb, reb, ab, nb, eb in zip( + self.tagboxes, self.colorboxes, self.colboxes, + self.reboxes, self.andboxes, self.notboxes, self.emptyboxes): + f = unicode(fb.currentText()) + if not f: + continue + m = self.completion_values[f]['m'] + dt = self.completion_values[f]['dt'] + c = unicode(cb.currentText()).strip() + re = reb.checkState() + a = ab.checkState() + n = nb.checkState() + e = eb.checkState() + line += 1 + + tval = '' if n == 2 else '1' + fval = '1' if n == 2 else '' + + if m: + tags = [t.strip() for t in unicode(tb.text()).split(m) if t.strip()] + if re == 2: + tags = '$|^'.join(tags) + else: + tags = m.join(tags) + if m == '&': + tags = tags.replace(',', '|') + else: + tags = unicode(tb.text()).strip() + + if (tags or f) and not ((tags or e) and f and (a == 2 or c)): + error_dialog(self, _('Invalid line'), + _('Line number {0} is not valid').format(line), + show=True, show_copy_button=False) + return False + + if not was_and: + if had_line: + lines[-1] += ',' + had_line = True + lines.append(" test(and(") + else: + lines[-1] += ',' + + key = dt + ('.mult' if m else '') + ('.empty' if e else '') + ('.re' if re else '') + template = self.templates[key] + lines.append(template.format(v=tags, f=f, tv=tval, fv=fval, mult=m)) + + if a == 2: + was_and = True + else: + was_and = False + lines.append(" ), '{0}', '')".format(c)) + + res += '\n'.join(lines) + else_txt = unicode(self.elsebox.currentText()) + if else_txt: + res += ",\n '" + else_txt + "'" + res += ')\n' + self.template = res + res = '' + for tb, cb, fb, reb, ab, nb, eb in zip( + self.tagboxes, self.colorboxes, self.colboxes, + self.reboxes, self.andboxes, self.notboxes, self.emptyboxes): + t = unicode(tb.text()).strip() + if t.endswith(','): + t = t[:-1] + c = unicode(cb.currentText()).strip() + f = unicode(fb.currentText()) + re = unicode(reb.checkState()) + a = unicode(ab.checkState()) + n = unicode(nb.checkState()) + e = unicode(eb.checkState()) + if f and (t or e) and (a == '2' or c): + res += '#' + t + ':|:' + c + ':|:' + f + ':|:' + re + ':|:' + \ + a + ':|:' + n + ':|:' + e + '\n' + res += '#else:' + else_txt + '\n' + self.template += res + return True + + def empty_box_changed(self, state, line=None): + if state == 2: + self.tagboxes[line].setText('') + self.tagboxes[line].setEnabled(False) + self.reboxes[line].setChecked(0) + self.reboxes[line].setEnabled(False) + else: + self.reboxes[line].setEnabled(True) + self.tagboxes[line].setEnabled(True) + + def and_box_changed(self, state, line=None): + if state == 2: + self.colorboxes[line].setCurrentIndex(0) + self.colorboxes[line].setEnabled(False) + else: + self.colorboxes[line].setEnabled(True) + + def accepted(self): + if self.generate_program(): + self.accept() + else: + self.template = '' diff --git a/src/calibre/gui2/dialogs/tweak_epub.py b/src/calibre/gui2/dialogs/tweak_epub.py index db6e93fd7a..732d74b77d 100755 --- a/src/calibre/gui2/dialogs/tweak_epub.py +++ b/src/calibre/gui2/dialogs/tweak_epub.py @@ -7,15 +7,16 @@ __copyright__ = '2010, Kovid Goyal <kovid@kovidgoyal.net>' __docformat__ = 'restructuredtext en' import os, shutil -from contextlib import closing from zipfile import ZipFile, ZIP_DEFLATED, ZIP_STORED from PyQt4.Qt import QDialog -from calibre.gui2 import open_local_file +from calibre.constants import isosx +from calibre.gui2 import open_local_file, error_dialog from calibre.gui2.dialogs.tweak_epub_ui import Ui_Dialog from calibre.libunzip import extract as zipextract -from calibre.ptempfile import PersistentTemporaryDirectory +from calibre.ptempfile import (PersistentTemporaryDirectory, + PersistentTemporaryFile) class TweakEpub(QDialog, Ui_Dialog): ''' @@ -36,16 +37,33 @@ class TweakEpub(QDialog, Ui_Dialog): self.cancel_button.clicked.connect(self.reject) self.explode_button.clicked.connect(self.explode) self.rebuild_button.clicked.connect(self.rebuild) + self.preview_button.clicked.connect(self.preview) # Position update dialog overlaying top left of app window parent_loc = parent.pos() self.move(parent_loc.x(),parent_loc.y()) + self.gui = parent + self._preview_files = [] + def cleanup(self): + if isosx: + try: + import appscript + self.finder = appscript.app('Finder') + self.finder.Finder_windows[os.path.basename(self._exploded)].close() + except: + # appscript fails to load on 10.4 + pass + # Delete directory containing exploded ePub if self._exploded is not None: shutil.rmtree(self._exploded, ignore_errors=True) - + for x in self._preview_files: + try: + os.remove(x) + except: + pass def display_exploded(self): ''' @@ -62,9 +80,8 @@ class TweakEpub(QDialog, Ui_Dialog): self.rebuild_button.setEnabled(True) self.explode_button.setEnabled(False) - def rebuild(self, *args): - self._output = os.path.join(self._exploded, 'rebuilt.epub') - with closing(ZipFile(self._output, 'w', compression=ZIP_DEFLATED)) as zf: + def do_rebuild(self, src): + with ZipFile(src, 'w', compression=ZIP_DEFLATED) as zf: # Write mimetype zf.write(os.path.join(self._exploded,'mimetype'), 'mimetype', compress_type=ZIP_STORED) # Write everything else @@ -77,5 +94,23 @@ class TweakEpub(QDialog, Ui_Dialog): zfn = os.path.relpath(absfn, self._exploded).replace(os.sep, '/') zf.write(absfn, zfn) + + def preview(self): + if not self._exploded: + return error_dialog(self, _('Cannot preview'), + _('You must first explode the epub before previewing.'), + show=True) + + tf = PersistentTemporaryFile('.epub') + tf.close() + self._preview_files.append(tf.name) + + self.do_rebuild(tf.name) + + self.gui.iactions['View']._view_file(tf.name) + + def rebuild(self, *args): + self._output = os.path.join(self._exploded, 'rebuilt.epub') + self.do_rebuild(self._output) return QDialog.accept(self) diff --git a/src/calibre/gui2/dialogs/tweak_epub.ui b/src/calibre/gui2/dialogs/tweak_epub.ui index fc6f24675f..a59af4fde1 100644 --- a/src/calibre/gui2/dialogs/tweak_epub.ui +++ b/src/calibre/gui2/dialogs/tweak_epub.ui @@ -23,6 +23,16 @@ <bool>false</bool> </property> <layout class="QGridLayout" name="gridLayout"> + <item row="0" column="0" colspan="2"> + <widget class="QLabel" name="label"> + <property name="text"> + <string><p>Explode the ePub to display contents in a file browser window. To tweak individual files, right-click, then 'Open with...' your editor of choice. When tweaks are complete, close the file browser window <b>and the editor windows you used to edit files in the epub</b>.</p><p>Rebuild the ePub, updating your calibre library.</p></string> + </property> + <property name="wordWrap"> + <bool>true</bool> + </property> + </widget> + </item> <item row="1" column="0"> <widget class="QPushButton" name="explode_button"> <property name="statusTip"> @@ -37,23 +47,6 @@ </property> </widget> </item> - <item row="2" column="0"> - <widget class="QPushButton" name="rebuild_button"> - <property name="enabled"> - <bool>false</bool> - </property> - <property name="statusTip"> - <string>Rebuild ePub from exploded contents</string> - </property> - <property name="text"> - <string>&Rebuild ePub</string> - </property> - <property name="icon"> - <iconset resource="../../../../resources/images.qrc"> - <normaloff>:/images/exec.png</normaloff>:/images/exec.png</iconset> - </property> - </widget> - </item> <item row="3" column="0"> <widget class="QPushButton" name="cancel_button"> <property name="statusTip"> @@ -68,13 +61,31 @@ </property> </widget> </item> - <item row="0" column="0"> - <widget class="QLabel" name="label"> - <property name="text"> - <string><p>Explode the ePub to display contents in a file browser window. To tweak individual files, right-click, then 'Open with...' your editor of choice. When tweaks are complete, close the file browser window <b>and the editor windows you used to edit files in the epub</b>.</p><p>Rebuild the ePub, updating your calibre library.</p></string> + <item row="3" column="1"> + <widget class="QPushButton" name="rebuild_button"> + <property name="enabled"> + <bool>false</bool> </property> - <property name="wordWrap"> - <bool>true</bool> + <property name="statusTip"> + <string>Rebuild ePub from exploded contents</string> + </property> + <property name="text"> + <string>&Rebuild ePub</string> + </property> + <property name="icon"> + <iconset resource="../../../../resources/images.qrc"> + <normaloff>:/images/exec.png</normaloff>:/images/exec.png</iconset> + </property> + </widget> + </item> + <item row="1" column="1"> + <widget class="QPushButton" name="preview_button"> + <property name="text"> + <string>&Preview ePub</string> + </property> + <property name="icon"> + <iconset resource="../../../../resources/images.qrc"> + <normaloff>:/images/view.png</normaloff>:/images/view.png</iconset> </property> </widget> </item> diff --git a/src/calibre/gui2/dialogs/user_profiles.py b/src/calibre/gui2/dialogs/user_profiles.py index fe64deb430..d66d02d211 100644 --- a/src/calibre/gui2/dialogs/user_profiles.py +++ b/src/calibre/gui2/dialogs/user_profiles.py @@ -4,13 +4,13 @@ __copyright__ = '2008, Kovid Goyal <kovid at kovidgoyal.net>' import time, os from PyQt4.Qt import SIGNAL, QUrl, QAbstractListModel, Qt, \ - QVariant + QVariant, QFont -from calibre.web.feeds.recipes import compile_recipe +from calibre.web.feeds.recipes import compile_recipe, custom_recipes from calibre.web.feeds.news import AutomaticNewsRecipe from calibre.gui2.dialogs.user_profiles_ui import Ui_Dialog from calibre.gui2 import error_dialog, question_dialog, open_url, \ - choose_files, ResizableDialog, NONE + choose_files, ResizableDialog, NONE, open_local_file from calibre.gui2.widgets import PythonHighlighter from calibre.ptempfile import PersistentTemporaryFile from calibre.utils.icu import sort_key @@ -83,6 +83,9 @@ class UserProfiles(ResizableDialog, Ui_Dialog): self._model = self.model = CustomRecipeModel(recipe_model) self.available_profiles.setModel(self._model) self.available_profiles.currentChanged = self.current_changed + f = QFont() + f.setStyleHint(f.Monospace) + self.source_code.setFont(f) self.connect(self.remove_feed_button, SIGNAL('clicked(bool)'), self.added_feeds.remove_selected_items) @@ -93,6 +96,7 @@ class UserProfiles(ResizableDialog, Ui_Dialog): self.connect(self.load_button, SIGNAL('clicked()'), self.load) self.connect(self.builtin_recipe_button, SIGNAL('clicked()'), self.add_builtin_recipe) self.connect(self.share_button, SIGNAL('clicked()'), self.share) + self.show_recipe_files_button.clicked.connect(self.show_recipe_files) self.connect(self.down_button, SIGNAL('clicked()'), self.down) self.connect(self.up_button, SIGNAL('clicked()'), self.up) self.connect(self.add_profile_button, SIGNAL('clicked(bool)'), @@ -102,6 +106,10 @@ class UserProfiles(ResizableDialog, Ui_Dialog): self.connect(self.toggle_mode_button, SIGNAL('clicked(bool)'), self.toggle_mode) self.clear() + def show_recipe_files(self, *args): + bdir = os.path.dirname(custom_recipes.file_path) + open_local_file(bdir) + def break_cycles(self): self.recipe_model = self._model.recipe_model = None self.available_profiles = None @@ -229,7 +237,7 @@ class %(classname)s(%(base_class)s): try: compile_recipe(src) - except Exception, err: + except Exception as err: error_dialog(self, _('Invalid input'), _('<p>Could not create recipe. Error:<br>%s')%str(err)).exec_() return @@ -238,7 +246,7 @@ class %(classname)s(%(base_class)s): src = unicode(self.source_code.toPlainText()) try: title = compile_recipe(src).title - except Exception, err: + except Exception as err: error_dialog(self, _('Invalid input'), _('<p>Could not create recipe. Error:<br>%s')%str(err)).exec_() return @@ -325,7 +333,7 @@ class %(classname)s(%(base_class)s): try: profile = open(file, 'rb').read().decode('utf-8') title = compile_recipe(profile).title - except Exception, err: + except Exception as err: error_dialog(self, _('Invalid input'), _('<p>Could not create recipe. Error:<br>%s')%str(err)).exec_() return @@ -366,8 +374,7 @@ class %(classname)s(%(base_class)s): if __name__ == '__main__': from calibre.gui2 import is_ok_to_use_qt is_ok_to_use_qt() - from calibre.library.database2 import LibraryDatabase2 from calibre.web.feeds.recipes.model import RecipeModel - d=UserProfiles(None, RecipeModel(LibraryDatabase2('/home/kovid/documents/library'))) + d=UserProfiles(None, RecipeModel()) d.exec_() diff --git a/src/calibre/gui2/dialogs/user_profiles.ui b/src/calibre/gui2/dialogs/user_profiles.ui index 4ca47539d1..6493846c2b 100644 --- a/src/calibre/gui2/dialogs/user_profiles.ui +++ b/src/calibre/gui2/dialogs/user_profiles.ui @@ -35,7 +35,7 @@ <x>0</x> <y>0</y> <width>730</width> - <height>600</height> + <height>601</height> </rect> </property> <layout class="QVBoxLayout" name="verticalLayout_3"> @@ -102,6 +102,17 @@ </property> </widget> </item> + <item> + <widget class="QPushButton" name="show_recipe_files_button"> + <property name="text"> + <string>S&how recipe files</string> + </property> + <property name="icon"> + <iconset resource="../../../../resources/images.qrc"> + <normaloff>:/images/document_open.png</normaloff>:/images/document_open.png</iconset> + </property> + </widget> + </item> <item> <widget class="QPushButton" name="builtin_recipe_button"> <property name="text"> @@ -375,7 +386,7 @@ p, li { white-space: pre-wrap; } <item> <widget class="QLabel" name="label_8"> <property name="text"> - <string>For help with writing advanced news recipes, please visit <a href="http://__appname__-ebook.com/user_manual/news.html">User Recipes</a></string> + <string>For help with writing advanced news recipes, please visit <a href="http://manual.__appname__-ebook.com/news.html">User Recipes</a></string> </property> <property name="wordWrap"> <bool>true</bool> @@ -399,11 +410,6 @@ p, li { white-space: pre-wrap; } <verstretch>0</verstretch> </sizepolicy> </property> - <property name="font"> - <font> - <family>DejaVu Sans Mono</family> - </font> - </property> <property name="lineWrapMode"> <enum>QTextEdit::NoWrap</enum> </property> diff --git a/src/calibre/gui2/dnd.py b/src/calibre/gui2/dnd.py new file mode 100644 index 0000000000..b602e4112a --- /dev/null +++ b/src/calibre/gui2/dnd.py @@ -0,0 +1,333 @@ +#!/usr/bin/env python +# vim:fileencoding=UTF-8:ts=4:sw=4:sta:et:sts=4:ai +from __future__ import (unicode_literals, division, absolute_import, + print_function) + +__license__ = 'GPL v3' +__copyright__ = '2011, Kovid Goyal <kovid@kovidgoyal.net>' +__docformat__ = 'restructuredtext en' + +import posixpath, os, urllib, re +from urlparse import urlparse, urlunparse +from threading import Thread +from Queue import Queue, Empty + +from PyQt4.Qt import QPixmap, Qt, QDialog, QLabel, QVBoxLayout, \ + QDialogButtonBox, QProgressBar, QTimer + +from calibre.constants import DEBUG, iswindows +from calibre.ptempfile import PersistentTemporaryFile +from calibre import browser, as_unicode, prints +from calibre.gui2 import error_dialog + +IMAGE_EXTENSIONS = ['jpg', 'jpeg', 'gif', 'png', 'bmp'] + +class Worker(Thread): # {{{ + + def __init__(self, url, fpath, rq): + Thread.__init__(self) + self.url, self.fpath = url, fpath + self.daemon = True + self.rq = rq + self.err = self.tb = None + + def run(self): + try: + br = browser() + br.retrieve(self.url, self.fpath, self.callback) + except Exception as e: + self.err = as_unicode(e) + import traceback + self.tb = traceback.format_exc() + + def callback(self, a, b, c): + self.rq.put((a, b, c)) +# }}} + +class DownloadDialog(QDialog): # {{{ + + def __init__(self, url, fname, parent): + QDialog.__init__(self, parent) + self.setWindowTitle(_('Download %s')%fname) + self.l = QVBoxLayout(self) + self.purl = urlparse(url) + self.msg = QLabel(_('Downloading <b>%s</b> from %s')%(fname, + self.purl.netloc)) + self.msg.setWordWrap(True) + self.l.addWidget(self.msg) + self.pb = QProgressBar(self) + self.pb.setMinimum(0) + self.pb.setMaximum(0) + self.l.addWidget(self.pb) + self.bb = QDialogButtonBox(QDialogButtonBox.Cancel, Qt.Horizontal, self) + self.l.addWidget(self.bb) + self.bb.rejected.connect(self.reject) + sz = self.sizeHint() + self.resize(max(sz.width(), 400), sz.height()) + + fpath = PersistentTemporaryFile(os.path.splitext(fname)[1]) + fpath.close() + self.fpath = fpath.name + + self.worker = Worker(url, self.fpath, Queue()) + self.rejected = False + + def reject(self): + self.rejected = True + QDialog.reject(self) + + def start_download(self): + self.worker.start() + QTimer.singleShot(50, self.update) + self.exec_() + if self.worker.err is not None: + error_dialog(self.parent(), _('Download failed'), + _('Failed to download from %r with error: %s')%( + self.worker.url, self.worker.err), + det_msg=self.worker.tb, show=True) + + def update(self): + if self.rejected: + return + + try: + progress = self.worker.rq.get_nowait() + except Empty: + pass + else: + self.update_pb(progress) + + if not self.worker.is_alive(): + return self.accept() + QTimer.singleShot(50, self.update) + + def update_pb(self, progress): + transferred, block_size, total = progress + if total == -1: + self.pb.setMaximum(0) + self.pb.setMinimum(0) + self.pb.setValue(0) + else: + so_far = transferred * block_size + self.pb.setMaximum(max(total, so_far)) + self.pb.setValue(so_far) + + @property + def err(self): + return self.worker.err + +# }}} + +def dnd_has_image(md): + return md.hasImage() + +def data_as_string(f, md): + raw = bytes(md.data(f)) + if '/x-moz' in f: + try: + raw = raw.decode('utf-16') + except: + pass + return raw + +def dnd_has_extension(md, extensions): + if DEBUG: + prints('Debugging DND event') + for f in md.formats(): + f = unicode(f) + prints(f, repr(data_as_string(f, md))[:300], '\n') + print () + if has_firefox_ext(md, extensions): + return True + if md.hasUrls(): + urls = [unicode(u.toString()) for u in + md.urls()] + purls = [urlparse(u) for u in urls] + paths = [u2p(x) for x in purls] + if DEBUG: + prints('URLS:', urls) + prints('Paths:', paths) + + exts = frozenset([posixpath.splitext(u)[1][1:].lower() for u in + paths]) + return bool(exts.intersection(frozenset(extensions))) + return False + +def _u2p(raw): + path = raw + if iswindows and path.startswith('/'): + path = path[1:] + return path.replace('/', os.sep) + +def u2p(url): + path = url.path + ans = _u2p(path) + if not os.path.exists(ans): + ans = _u2p(url.path + '#' + url.fragment) + if os.path.exists(ans): + return ans + # Try unquoting the URL + return urllib.unquote(ans) + +def dnd_get_image(md, image_exts=IMAGE_EXTENSIONS): + ''' + Get the image in the QMimeData object md. + + :return: None, None if no image is found + QPixmap, None if an image is found, the pixmap is guaranteed not + null + url, filename if a URL that points to an image is found + ''' + if dnd_has_image(md): + for x in md.formats(): + x = unicode(x) + if x.startswith('image/'): + cdata = bytes(md.data(x)) + pmap = QPixmap() + pmap.loadFromData(cdata) + if not pmap.isNull(): + return pmap, None + break + + # No image, look for a URL pointing to an image + if md.hasUrls(): + urls = [unicode(u.toString()) for u in + md.urls()] + purls = [urlparse(u) for u in urls] + # First look for a local file + images = [u2p(x) for x in purls if x.scheme in ('', 'file')] + images = [x for x in images if + posixpath.splitext(urllib.unquote(x))[1][1:].lower() in + image_exts] + images = [x for x in images if os.path.exists(x)] + p = QPixmap() + for path in images: + try: + with open(path, 'rb') as f: + p.loadFromData(f.read()) + except: + continue + if not p.isNull(): + return p, None + + # No local images, look for remote ones + + # First, see if this is from Firefox + rurl, fname = get_firefox_rurl(md, image_exts) + + if rurl and fname: + return rurl, fname + # Look through all remaining URLs + remote_urls = [x for x in purls if x.scheme in ('http', 'https', + 'ftp') and posixpath.splitext(x.path)[1][1:].lower() in image_exts] + if remote_urls: + rurl = remote_urls[0] + fname = posixpath.basename(urllib.unquote(rurl.path)) + return urlunparse(rurl), fname + + return None, None + +def dnd_get_files(md, exts): + ''' + Get the file in the QMimeData object md with an extension that is one of + the extensions in exts. + + :return: None, None if no file is found + [paths], None if a local file is found + [urls], [filenames] if URLs that point to a files are found + ''' + # Look for a URL pointing to a file + if md.hasUrls(): + urls = [unicode(u.toString()) for u in + md.urls()] + purls = [urlparse(u) for u in urls] + # First look for a local file + local_files = [u2p(x) for x in purls if x.scheme in ('', 'file')] + local_files = [ p for p in local_files if + posixpath.splitext(urllib.unquote(p))[1][1:].lower() in + exts] + local_files = [x for x in local_files if os.path.exists(x)] + if local_files: + return local_files, None + + # No local files, look for remote ones + + # First, see if this is from Firefox + rurl, fname = get_firefox_rurl(md, exts) + if rurl and fname: + return [rurl], [fname] + + # Look through all remaining URLs + remote_urls = [x for x in purls if x.scheme in ('http', 'https', + 'ftp') and posixpath.splitext(x.path)[1][1:].lower() in exts] + if remote_urls: + filenames = [posixpath.basename(urllib.unquote(rurl.path)) for rurl in + remote_urls] + return [urlunparse(x) for x in remote_urls], filenames + + return None, None + +def _get_firefox_pair(md, exts, url, fname): + url = bytes(md.data(url)).decode('utf-16') + fname = bytes(md.data(fname)).decode('utf-16') + while url.endswith('\x00'): + url = url[:-1] + while fname.endswith('\x00'): + fname = fname[:-1] + if not url or not fname: + return None, None + ext = posixpath.splitext(fname)[1][1:].lower() + # Weird firefox bug on linux + ext = {'jpe':'jpg', 'epu':'epub', 'mob':'mobi'}.get(ext, ext) + fname = os.path.splitext(fname)[0] + '.' + ext + if DEBUG: + prints('Firefox file promise:', url, fname) + if ext not in exts: + fname = url = None + return url, fname + + +def get_firefox_rurl(md, exts): + formats = frozenset([unicode(x) for x in md.formats()]) + url = fname = None + if 'application/x-moz-file-promise-url' in formats and \ + 'application/x-moz-file-promise-dest-filename' in formats: + try: + url, fname = _get_firefox_pair(md, exts, + 'application/x-moz-file-promise-url', + 'application/x-moz-file-promise-dest-filename') + except: + if DEBUG: + import traceback + traceback.print_exc() + if url is None and 'text/x-moz-url-data' in formats and \ + 'text/x-moz-url-desc' in formats: + try: + url, fname = _get_firefox_pair(md, exts, + 'text/x-moz-url-data', 'text/x-moz-url-desc') + except: + if DEBUG: + import traceback + traceback.print_exc() + + if url is None and '_NETSCAPE_URL' in formats: + try: + raw = bytes(md.data('_NETSCAPE_URL')) + raw = raw.decode('utf-8') + lines = raw.splitlines() + if len(lines) > 1 and re.match(r'[a-z]+://', lines[1]) is None: + url, fname = lines[:2] + ext = posixpath.splitext(fname)[1][1:].lower() + if ext not in exts: + fname = url = None + except: + if DEBUG: + import traceback + traceback.print_exc() + if DEBUG: + prints('Firefox rurl:', url, fname) + return url, fname + +def has_firefox_ext(md, exts): + return bool(get_firefox_rurl(md, exts)[0]) + diff --git a/src/calibre/gui2/ebook_download.py b/src/calibre/gui2/ebook_download.py new file mode 100644 index 0000000000..2fea67b9f0 --- /dev/null +++ b/src/calibre/gui2/ebook_download.py @@ -0,0 +1,106 @@ +# -*- coding: utf-8 -*- + +from __future__ import (unicode_literals, division, absolute_import, print_function) + +__license__ = 'GPL 3' +__copyright__ = '2011, John Schember <john@nachtimwald.com>' +__docformat__ = 'restructuredtext en' + +import os +import shutil +from contextlib import closing +from mechanize import MozillaCookieJar + +from calibre import browser, get_download_filename +from calibre.ebooks import BOOK_EXTENSIONS +from calibre.gui2 import Dispatcher +from calibre.gui2.threaded_jobs import ThreadedJob +from calibre.ptempfile import PersistentTemporaryFile + +class EbookDownload(object): + + def __call__(self, gui, cookie_file=None, url='', filename='', save_loc='', add_to_lib=True, tags=[], log=None, abort=None, notifications=None): + dfilename = '' + try: + dfilename = self._download(cookie_file, url, filename, save_loc, add_to_lib) + self._add(dfilename, gui, add_to_lib, tags) + self._save_as(dfilename, save_loc) + except Exception as e: + raise e + finally: + try: + if dfilename: + os.remove(dfilename) + except: + pass + + def _download(self, cookie_file, url, filename, save_loc, add_to_lib): + dfilename = '' + + if not url: + raise Exception(_('No file specified to download.')) + if not save_loc and not add_to_lib: + # Nothing to do. + return dfilename + + if not filename: + filename = get_download_filename(url, cookie_file) + + br = browser() + if cookie_file: + cj = MozillaCookieJar() + cj.load(cookie_file) + br.set_cookiejar(cj) + with closing(br.open(url)) as r: + tf = PersistentTemporaryFile(suffix=filename) + tf.write(r.read()) + dfilename = tf.name + + return dfilename + + def _add(self, filename, gui, add_to_lib, tags): + if not add_to_lib or not filename: + return + ext = os.path.splitext(filename)[1][1:].lower() + if ext not in BOOK_EXTENSIONS: + raise Exception(_('Not a support ebook format.')) + + from calibre.ebooks.metadata.meta import get_metadata + with open(filename) as f: + mi = get_metadata(f, ext) + mi.tags.extend(tags) + + id = gui.library_view.model().db.create_book_entry(mi) + gui.library_view.model().db.add_format_with_hooks(id, ext.upper(), filename, index_is_id=True) + gui.library_view.model().books_added(1) + gui.library_view.model().count_changed() + + def _save_as(self, dfilename, save_loc): + if not save_loc or not dfilename: + return + shutil.copy(dfilename, save_loc) + + +gui_ebook_download = EbookDownload() + +def start_ebook_download(callback, job_manager, gui, cookie_file=None, url='', filename='', save_loc='', add_to_lib=True, tags=[]): + description = _('Downloading %s') % filename if filename else url + job = ThreadedJob('ebook_download', description, gui_ebook_download, (gui, cookie_file, url, filename, save_loc, add_to_lib, tags), {}, callback, max_concurrent_count=2, killable=False) + job_manager.run_threaded_job(job) + + +class EbookDownloadMixin(object): + + def download_ebook(self, url='', cookie_file=None, filename='', save_loc='', add_to_lib=True, tags=[]): + if tags: + if isinstance(tags, basestring): + tags = tags.split(',') + start_ebook_download(Dispatcher(self.downloaded_ebook), self.job_manager, self, cookie_file, url, filename, save_loc, add_to_lib, tags) + self.status_bar.show_message(_('Downloading') + ' ' + filename if filename else url, 3000) + + def downloaded_ebook(self, job): + if job.failed: + self.job_exception(job, dialog_title=_('Failed to download ebook')) + return + + self.status_bar.show_message(job.description + ' ' + _('finished'), 5000) diff --git a/src/calibre/gui2/email.py b/src/calibre/gui2/email.py index c84b3180f7..c8adeb7d31 100644 --- a/src/calibre/gui2/email.py +++ b/src/calibre/gui2/email.py @@ -6,9 +6,7 @@ __license__ = 'GPL v3' __copyright__ = '2010, Kovid Goyal <kovid@kovidgoyal.net>' __docformat__ = 'restructuredtext en' -import os, socket, time, cStringIO -from threading import Thread -from Queue import Queue +import os, socket, time from binascii import unhexlify from functools import partial from itertools import repeat @@ -16,66 +14,20 @@ from itertools import repeat from calibre.utils.smtp import compose_mail, sendmail, extract_email_address, \ config as email_config from calibre.utils.filenames import ascii_filename -from calibre.utils.ipc.job import BaseJob -from calibre.ptempfile import PersistentTemporaryFile from calibre.customize.ui import available_input_formats, available_output_formats from calibre.ebooks.metadata import authors_to_string from calibre.constants import preferred_encoding from calibre.gui2 import config, Dispatcher, warning_dialog +from calibre.library.save_to_disk import get_components from calibre.utils.config import tweaks +from calibre.gui2.threaded_jobs import ThreadedJob -class EmailJob(BaseJob): # {{{ - - def __init__(self, callback, description, attachment, aname, to, subject, text, job_manager): - BaseJob.__init__(self, description) - self.exception = None - self.job_manager = job_manager - self.email_args = (attachment, aname, to, subject, text) - self.email_sent_callback = callback - self.log_path = None - self._log_file = cStringIO.StringIO() - self._log_file.write(self.description.encode('utf-8') + '\n') - - @property - def log_file(self): - if self.log_path is not None: - return open(self.log_path, 'rb') - return cStringIO.StringIO(self._log_file.getvalue()) - - def start_work(self): - self.start_time = time.time() - self.job_manager.changed_queue.put(self) - - def job_done(self): - self.duration = time.time() - self.start_time - self.percent = 1 - # Dump log onto disk - lf = PersistentTemporaryFile('email_log') - lf.write(self._log_file.getvalue()) - lf.close() - self.log_path = lf.name - self._log_file.close() - self._log_file = None - - self.job_manager.changed_queue.put(self) - - def log_write(self, what): - self._log_file.write(what) - -# }}} - -class Emailer(Thread): # {{{ +class Sendmail(object): MAX_RETRIES = 1 - def __init__(self, job_manager): - Thread.__init__(self) - self.daemon = True - self.jobs = Queue() - self.job_manager = job_manager - self._run = True + def __init__(self): self.calculate_rate_limit() - self.last_send_time = time.time() - self.rate_limit def calculate_rate_limit(self): @@ -86,70 +38,28 @@ class Emailer(Thread): # {{{ 'gmail.com' in rh or 'live.com' in rh): self.rate_limit = tweaks['public_smtp_relay_delay'] - def stop(self): - self._run = False - self.jobs.put(None) + def __call__(self, attachment, aname, to, subject, text, log=None, + abort=None, notifications=None): - def run(self): - while self._run: + try_count = 0 + while try_count <= self.MAX_RETRIES: + if try_count > 0: + log('\nRetrying in %d seconds...\n' % + self.rate_limit) try: - job = self.jobs.get() + self.sendmail(attachment, aname, to, subject, text, log) + try_count = self.MAX_RETRIES + log('Email successfully sent') except: - break - if job is None or not self._run: - break - try_count = 0 - failed, exc = False, None - job.start_work() - if job.kill_on_start: - job.log_write('Aborted\n') - job.failed = failed - job.killed = True - job.job_done() - continue + if abort.is_set(): + return + if try_count == self.MAX_RETRIES: + raise + log.exception('\nSending failed...\n') - while try_count <= self.MAX_RETRIES: - failed = False - if try_count > 0: - job.log_write('\nRetrying in %d seconds...\n' % - self.rate_limit) - try: - self.sendmail(job) - break - except Exception, e: - if not self._run: - return - import traceback - failed = True - exc = e - job.log_write('\nSending failed...\n') - job.log_write(traceback.format_exc()) + try_count += 1 - try_count += 1 - - if not self._run: - break - - job.failed = failed - job.exception = exc - job.job_done() - try: - job.email_sent_callback(job) - except: - import traceback - traceback.print_exc() - - def send_mails(self, jobnames, callback, attachments, to_s, subjects, - texts, attachment_names): - for name, attachment, to, subject, text, aname in zip(jobnames, - attachments, to_s, subjects, texts, attachment_names): - description = _('Email %s to %s') % (name, to) - job = EmailJob(callback, description, attachment, aname, to, - subject, text, self.job_manager) - self.job_manager.add_job(job) - self.jobs.put(job) - - def sendmail(self, job): + def sendmail(self, attachment, aname, to, subject, text, log): while time.time() - self.last_send_time <= self.rate_limit: time.sleep(1) try: @@ -157,7 +67,6 @@ class Emailer(Thread): # {{{ from_ = opts.from_ if not from_: from_ = 'calibre <calibre@'+socket.getfqdn()+'>' - attachment, aname, to, subject, text = job.email_args msg = compose_mail(from_, to, text, subject, open(attachment, 'rb'), aname) efrom, eto = map(extract_email_address, (from_, to)) @@ -168,49 +77,57 @@ class Emailer(Thread): # {{{ username=opts.relay_username, password=unhexlify(opts.relay_password), port=opts.relay_port, encryption=opts.encryption, - debug_output=partial(print, file=job._log_file)) + debug_output=log.debug) finally: self.last_send_time = time.time() - def email_news(self, mi, remove, get_fmts, done): - opts = email_config().parse() - accounts = [(account, [x.strip().lower() for x in x[0].split(',')]) - for account, x in opts.accounts.items() if x[1]] - sent_mails = [] - for i, x in enumerate(accounts): - account, fmts = x - files = get_fmts(fmts) - files = [f for f in files if f is not None] - if not files: - continue - attachment = files[0] - to_s = [account] - subjects = [_('News:')+' '+mi.title] - texts = [ - _('Attached is the %s periodical downloaded by calibre.') - % (mi.title,) - ] - attachment_names = [ascii_filename(mi.title)+os.path.splitext(attachment)[1]] - attachments = [attachment] - jobnames = [mi.title] - do_remove = [] - if i == len(accounts) - 1: - do_remove = remove - self.send_mails(jobnames, - Dispatcher(partial(done, remove=do_remove)), - attachments, to_s, subjects, texts, attachment_names) - sent_mails.append(to_s[0]) - return sent_mails +gui_sendmail = Sendmail() -# }}} +def send_mails(jobnames, callback, attachments, to_s, subjects, + texts, attachment_names, job_manager): + for name, attachment, to, subject, text, aname in zip(jobnames, + attachments, to_s, subjects, texts, attachment_names): + description = _('Email %s to %s') % (name, to) + job = ThreadedJob('email', description, gui_sendmail, (attachment, aname, to, + subject, text), {}, callback, killable=False) + job_manager.run_threaded_job(job) + + +def email_news(mi, remove, get_fmts, done, job_manager): + opts = email_config().parse() + accounts = [(account, [x.strip().lower() for x in x[0].split(',')]) + for account, x in opts.accounts.items() if x[1]] + sent_mails = [] + for i, x in enumerate(accounts): + account, fmts = x + files = get_fmts(fmts) + files = [f for f in files if f is not None] + if not files: + continue + attachment = files[0] + to_s = [account] + subjects = [_('News:')+' '+mi.title] + texts = [ + _('Attached is the %s periodical downloaded by calibre.') + % (mi.title,) + ] + attachment_names = [ascii_filename(mi.title)+os.path.splitext(attachment)[1]] + attachments = [attachment] + jobnames = [mi.title] + do_remove = [] + if i == len(accounts) - 1: + do_remove = remove + send_mails(jobnames, + Dispatcher(partial(done, remove=do_remove)), + attachments, to_s, subjects, texts, attachment_names, + job_manager) + sent_mails.append(to_s[0]) + return sent_mails class EmailMixin(object): # {{{ - def __init__(self): - self.emailer = Emailer(self.job_manager) - - def send_by_mail(self, to, fmts, delete_from_library, send_ids=None, + def send_by_mail(self, to, fmts, delete_from_library, subject='', send_ids=None, do_auto_convert=True, specific_format=None): ids = [self.library_view.model().id(r) for r in self.library_view.selectionModel().selectedRows()] if send_ids is None else send_ids if not ids or len(ids) == 0: @@ -239,7 +156,13 @@ class EmailMixin(object): # {{{ remove_ids.append(id) jobnames.append(t) attachments.append(f) - subjects.append(_('E-book:')+ ' '+t) + if not subject: + subjects.append(_('E-book:')+ ' '+t) + else: + components = get_components(subject, mi, id) + if not components: + components = [mi.title] + subjects.append(os.path.join(*components)) a = authors_to_string(mi.authors if mi.authors else \ [_('Unknown')]) texts.append(_('Attached, you will find the e-book') + \ @@ -254,11 +177,10 @@ class EmailMixin(object): # {{{ to_s = list(repeat(to, len(attachments))) if attachments: - if not self.emailer.is_alive(): - self.emailer.start() - self.emailer.send_mails(jobnames, + send_mails(jobnames, Dispatcher(partial(self.email_sent, remove=remove)), - attachments, to_s, subjects, texts, attachment_names) + attachments, to_s, subjects, texts, attachment_names, + self.job_manager) self.status_bar.show_message(_('Sending email to')+' '+to, 3000) auto = [] @@ -292,7 +214,7 @@ class EmailMixin(object): # {{{ if self.auto_convert_question( _('Auto convert the following books before sending via ' 'email?'), autos): - self.iactions['Convert Books'].auto_convert_mail(to, fmts, delete_from_library, auto, format) + self.iactions['Convert Books'].auto_convert_mail(to, fmts, delete_from_library, auto, format, subject) if bad: bad = '\n'.join('%s'%(i,) for i in bad) @@ -326,10 +248,8 @@ class EmailMixin(object): # {{{ files, auto = self.library_view.model().\ get_preferred_formats_from_ids([id_], fmts) return files - if not self.emailer.is_alive(): - self.emailer.start() - sent_mails = self.emailer.email_news(mi, remove, - get_fmts, self.email_sent) + sent_mails = email_news(mi, remove, + get_fmts, self.email_sent, self.job_manager) if sent_mails: self.status_bar.show_message(_('Sent news to')+' '+\ ', '.join(sent_mails), 3000) diff --git a/src/calibre/gui2/filename_pattern.ui b/src/calibre/gui2/filename_pattern.ui index 68b3108e06..b86db0fa75 100644 --- a/src/calibre/gui2/filename_pattern.ui +++ b/src/calibre/gui2/filename_pattern.ui @@ -19,7 +19,7 @@ <property name="text"> <string><div style="font-size:10pt;"> <p>Set a regular expression pattern to use when trying to guess ebook metadata from filenames. </p> -<p>A <a href="http://calibre-ebook.com/user_manual/regexp.html">tutorial</a> on using regular expressions is available.</p> +<p>A <a href="http://manual.calibre-ebook.com/regexp.html">tutorial</a> on using regular expressions is available.</p> <p>Use the <b>Test</b> functionality below to test your regular expression on a few sample filenames (remember to include the file extension). The group names for the various metadata entries are documented in tooltips.</p></div></string> </property> <property name="textFormat"> @@ -206,6 +206,46 @@ </property> </widget> </item> + <item row="5" column="0"> + <widget class="QLabel" name="label_8"> + <property name="text"> + <string>Publisher:</string> + </property> + </widget> + </item> + <item row="5" column="1"> + <widget class="QLineEdit" name="publisher"> + <property name="toolTip"> + <string>Regular expression (?P<publisher>)</string> + </property> + <property name="text"> + <string>No match</string> + </property> + <property name="readOnly"> + <bool>true</bool> + </property> + </widget> + </item> + <item row="6" column="0"> + <widget class="QLabel" name="label_9"> + <property name="text"> + <string>Published:</string> + </property> + </widget> + </item> + <item row="6" column="1"> + <widget class="QLineEdit" name="pubdate"> + <property name="toolTip"> + <string>Regular expression (?P<published>)</string> + </property> + <property name="text"> + <string>No match</string> + </property> + <property name="readOnly"> + <bool>true</bool> + </property> + </widget> + </item> </layout> </widget> </widget> diff --git a/src/calibre/gui2/init.py b/src/calibre/gui2/init.py index 0ca58582b6..a75ff01b21 100644 --- a/src/calibre/gui2/init.py +++ b/src/calibre/gui2/init.py @@ -44,13 +44,13 @@ class LibraryViewMixin(object): # {{{ for view in (self.library_view, self.memory_view, self.card_a_view, self.card_b_view): getattr(view, func)(*args) - self.memory_view.connect_dirtied_signal(self.upload_booklists) + self.memory_view.connect_dirtied_signal(self.upload_dirtied_booklists) self.memory_view.connect_upload_collections_signal( func=self.upload_collections, oncard=None) - self.card_a_view.connect_dirtied_signal(self.upload_booklists) + self.card_a_view.connect_dirtied_signal(self.upload_dirtied_booklists) self.card_a_view.connect_upload_collections_signal( func=self.upload_collections, oncard='carda') - self.card_b_view.connect_dirtied_signal(self.upload_booklists) + self.card_b_view.connect_dirtied_signal(self.upload_dirtied_booklists) self.card_b_view.connect_upload_collections_signal( func=self.upload_collections, oncard='cardb') self.book_on_device(None, reset=True) @@ -141,6 +141,15 @@ class Stack(QStackedWidget): # {{{ # }}} +class UpdateLabel(QLabel): # {{{ + + def __init__(self, *args, **kwargs): + QLabel.__init__(self, *args, **kwargs) + + def contextMenuEvent(self, e): + pass +# }}} + class StatusBar(QStatusBar): # {{{ def __init__(self, parent=None): @@ -148,7 +157,7 @@ class StatusBar(QStatusBar): # {{{ self.default_message = __appname__ + ' ' + _('version') + ' ' + \ self.get_version() + ' ' + _('created by Kovid Goyal') self.device_string = '' - self.update_label = QLabel('') + self.update_label = UpdateLabel('') self.addPermanentWidget(self.update_label) self.update_label.setVisible(False) self._font = QFont() @@ -238,6 +247,11 @@ class LayoutMixin(object): # {{{ for x in ('cb', 'tb', 'bd'): button = getattr(self, x+'_splitter').button button.setIconSize(QSize(24, 24)) + if isosx: + button.setStyleSheet(''' + QToolButton { background: none; border:none; padding: 0px; } + QToolButton:checked { background: rgba(0, 0, 0, 25%); } + ''') self.status_bar.addPermanentWidget(button) self.status_bar.addPermanentWidget(self.jobs_button) self.setStatusBar(self.status_bar) @@ -253,6 +267,11 @@ class LayoutMixin(object): # {{{ self.status_bar.initialize(self.system_tray_icon) self.book_details.show_book_info.connect(self.iactions['Show Book Details'].show_book_info) self.book_details.files_dropped.connect(self.iactions['Add Books'].files_dropped_on_book) + self.book_details.cover_changed.connect(self.bd_cover_changed, + type=Qt.QueuedConnection) + self.book_details.remote_file_dropped.connect( + self.iactions['Add Books'].remote_file_dropped_on_book, + type=Qt.QueuedConnection) self.book_details.open_containing_folder.connect(self.iactions['View'].view_folder_for_id) self.book_details.view_specific_format.connect(self.iactions['View'].view_format_by_id) @@ -263,6 +282,10 @@ class LayoutMixin(object): # {{{ self.library_view.currentIndex()) self.library_view.setFocus(Qt.OtherFocusReason) + def bd_cover_changed(self, id_, cdata): + self.library_view.model().db.set_cover(id_, cdata) + if self.cover_flow: + self.cover_flow.dataChanged() def save_layout_state(self): for x in ('library', 'memory', 'card_a', 'card_b'): diff --git a/src/calibre/gui2/jobs.py b/src/calibre/gui2/jobs.py index dbde030e81..51c54843a4 100644 --- a/src/calibre/gui2/jobs.py +++ b/src/calibre/gui2/jobs.py @@ -8,14 +8,13 @@ Job management. ''' import re - from Queue import Empty, Queue -from PyQt4.Qt import QAbstractTableModel, QVariant, QModelIndex, Qt, \ - QTimer, pyqtSignal, QIcon, QDialog, QAbstractItemDelegate, QApplication, \ - QSize, QStyleOptionProgressBarV2, QString, QStyle, QToolTip, QFrame, \ - QHBoxLayout, QVBoxLayout, QSizePolicy, QLabel, QCoreApplication, QAction, \ - QByteArray +from PyQt4.Qt import (QAbstractTableModel, QVariant, QModelIndex, Qt, + QTimer, pyqtSignal, QIcon, QDialog, QAbstractItemDelegate, QApplication, + QSize, QStyleOptionProgressBarV2, QString, QStyle, QToolTip, QFrame, + QHBoxLayout, QVBoxLayout, QSizePolicy, QLabel, QCoreApplication, QAction, + QByteArray) from calibre.utils.ipc.server import Server from calibre.utils.ipc.job import ParallelJob @@ -25,8 +24,9 @@ from calibre.gui2.dialogs.jobs_ui import Ui_JobsDialog from calibre import __appname__ from calibre.gui2.dialogs.job_view_ui import Ui_Dialog from calibre.gui2.progress_indicator import ProgressIndicator +from calibre.gui2.threaded_jobs import ThreadedJobServer, ThreadedJob -class JobManager(QAbstractTableModel): +class JobManager(QAbstractTableModel): # {{{ job_added = pyqtSignal(int) job_done = pyqtSignal(int) @@ -42,6 +42,7 @@ class JobManager(QAbstractTableModel): self.add_job = Dispatcher(self._add_job) self.server = Server(limit=int(config['worker_limit']/2.0), enforce_cpu_limit=config['enforce_cpu_limit']) + self.threaded_server = ThreadedJobServer() self.changed_queue = Queue() self.timer = QTimer(self) @@ -146,12 +147,21 @@ class JobManager(QAbstractTableModel): jobs.add(self.server.changed_jobs_queue.get_nowait()) except Empty: break + + # Update device jobs while True: try: jobs.add(self.changed_queue.get_nowait()) except Empty: break + # Update threaded jobs + while True: + try: + jobs.add(self.threaded_server.changed_jobs.get_nowait()) + except Empty: + break + if jobs: needs_reset = False for job in jobs: @@ -159,11 +169,11 @@ class JobManager(QAbstractTableModel): job.update() if orig_state != job.run_state: needs_reset = True + if job.is_finished: + self.job_done.emit(len(self.unfinished_jobs())) if needs_reset: self.jobs.sort() self.reset() - if job.is_finished: - self.job_done.emit(len(self.unfinished_jobs())) else: for job in jobs: idx = self.jobs.index(job) @@ -207,11 +217,22 @@ class JobManager(QAbstractTableModel): self.server.add_job(job) return job + def run_threaded_job(self, job): + self.add_job(job) + self.threaded_server.add_job(job) + def launch_gui_app(self, name, args=[], kwargs={}, description=''): job = ParallelJob(name, description, lambda x: x, args=args, kwargs=kwargs) self.server.run_job(job, gui=True, redirect_output=False) + def _kill_job(self, job): + if isinstance(job, ParallelJob): + self.server.kill_job(job) + elif isinstance(job, ThreadedJob): + self.threaded_server.kill_job(job) + else: + job.kill_on_start = True def kill_job(self, row, view): job = self.jobs[row] @@ -221,29 +242,29 @@ class JobManager(QAbstractTableModel): if job.duration is not None: return error_dialog(view, _('Cannot kill job'), _('Job has already run')).exec_() - if isinstance(job, ParallelJob): - self.server.kill_job(job) - else: - job.kill_on_start = True + if not getattr(job, 'killable', True): + return error_dialog(view, _('Cannot kill job'), + _('This job cannot be stopped'), show=True) + self._kill_job(job) def kill_all_jobs(self): for job in self.jobs: - if isinstance(job, DeviceJob) or job.duration is not None: + if (isinstance(job, DeviceJob) or job.duration is not None or + not getattr(job, 'killable', True)): continue - if isinstance(job, ParallelJob): - self.server.kill_job(job) - else: - job.kill_on_start = True + self._kill_job(job) def terminate_all_jobs(self): self.server.killall() for job in self.jobs: - if isinstance(job, DeviceJob) or job.duration is not None: + if (isinstance(job, DeviceJob) or job.duration is not None or + not getattr(job, 'killable', True)): continue if not isinstance(job, ParallelJob): - job.kill_on_start = True - + self._kill_job(job) +# }}} +# Jobs UI {{{ class ProgressBarDelegate(QAbstractItemDelegate): def sizeHint(self, option, index): @@ -269,6 +290,11 @@ class DetailView(QDialog, Ui_Dialog): self.setupUi(self) self.setWindowTitle(job.description) self.job = job + self.html_view = hasattr(job, 'html_details') + if self.html_view: + self.log.setVisible(False) + else: + self.tb.setVisible(False) self.next_pos = 0 self.update() self.timer = QTimer(self) @@ -277,12 +303,19 @@ class DetailView(QDialog, Ui_Dialog): def update(self): - f = self.job.log_file - f.seek(self.next_pos) - more = f.read() - self.next_pos = f.tell() - if more: - self.log.appendPlainText(more.decode('utf-8', 'replace')) + if self.html_view: + html = self.job.html_details + if len(html) > self.next_pos: + self.next_pos = len(html) + self.tb.setHtml( + '<pre style="font-family:monospace">%s</pre>'%html) + else: + f = self.job.log_file + f.seek(self.next_pos) + more = f.read() + self.next_pos = f.tell() + if more: + self.log.appendPlainText(more.decode('utf-8', 'replace')) class JobsButton(QFrame): @@ -441,3 +474,5 @@ class JobsDialog(QDialog, Ui_JobsDialog): def hide(self, *args): self.save_state() return QDialog.hide(self, *args) +# }}} + diff --git a/src/calibre/gui2/layout.py b/src/calibre/gui2/layout.py index e8a4e79384..76b9f5f9a2 100644 --- a/src/calibre/gui2/layout.py +++ b/src/calibre/gui2/layout.py @@ -7,16 +7,17 @@ __docformat__ = 'restructuredtext en' from functools import partial -from PyQt4.Qt import QIcon, Qt, QWidget, QToolBar, QSize, \ - pyqtSignal, QToolButton, QMenu, \ - QObject, QVBoxLayout, QSizePolicy, QLabel, QHBoxLayout, QActionGroup +from PyQt4.Qt import (QIcon, Qt, QWidget, QSize, + pyqtSignal, QToolButton, QMenu, + QObject, QVBoxLayout, QSizePolicy, QLabel, QHBoxLayout, QActionGroup) from calibre.constants import __appname__ from calibre.gui2.search_box import SearchBox2, SavedSearchBox from calibre.gui2.throbber import ThrobbingButton -from calibre.gui2 import gprefs +from calibre.gui2.bars import BarsManager from calibre.gui2.widgets import ComboBoxWithHelp +from calibre.utils.config_base import tweaks from calibre import human_readable class LocationManager(QObject): # {{{ @@ -35,6 +36,8 @@ class LocationManager(QObject): # {{{ self._mem = [] self.tooltips = {} + self.all_actions = [] + def ac(name, text, icon, tooltip): icon = QIcon(I(icon)) ac = self.location_actions.addAction(icon, text) @@ -44,20 +47,22 @@ class LocationManager(QObject): # {{{ receiver = partial(self._location_selected, name) ac.triggered.connect(receiver) self.tooltips[name] = tooltip + + m = QMenu(parent) + self._mem.append(m) + a = m.addAction(icon, tooltip) + a.triggered.connect(receiver) if name != 'library': - m = QMenu(parent) - self._mem.append(m) - a = m.addAction(icon, tooltip) - a.triggered.connect(receiver) self._mem.append(a) a = m.addAction(QIcon(I('eject.png')), _('Eject this device')) a.triggered.connect(self._eject_requested) - ac.setMenu(m) self._mem.append(a) else: ac.setToolTip(tooltip) + ac.setMenu(m) ac.calibre_name = name + self.all_actions.append(ac) return ac self.library_action = ac('library', _('Library'), 'lt.png', @@ -71,7 +76,12 @@ class LocationManager(QObject): # {{{ def set_switch_actions(self, quick_actions, rename_actions, delete_actions, switch_actions, choose_action): - self.switch_menu = QMenu() + self.switch_menu = self.library_action.menu() + if self.switch_menu: + self.switch_menu.addSeparator() + else: + self.switch_menu = QMenu() + self.switch_menu.addAction(choose_action) self.cs_menus = [] for t, acs in [(_('Quick switch'), quick_actions), @@ -85,7 +95,9 @@ class LocationManager(QObject): # {{{ self.switch_menu.addSeparator() for ac in switch_actions: self.switch_menu.addAction(ac) - self.library_action.setMenu(self.switch_menu) + + if self.switch_menu != self.library_action.menu(): + self.library_action.setMenu(self.switch_menu) def _location_selected(self, location, *args): if location != self.current_location and hasattr(self, @@ -156,8 +168,6 @@ class SearchBar(QWidget): # {{{ x = ComboBoxWithHelp(self) x.setMaximumSize(QSize(150, 16777215)) x.setObjectName("search_restriction") - x.setToolTip(_('Books display will be restricted to those matching the ' - 'selected saved search')) l.addWidget(x) parent.search_restriction = x @@ -196,11 +206,9 @@ class SearchBar(QWidget): # {{{ l.addWidget(x) x.setToolTip(_("Reset Quick Search")) - x = parent.search_options_button = QToolButton(self) - x.setIcon(QIcon(I('config.png'))) - x.setObjectName("search_option_button") + x = parent.highlight_only_button = QToolButton(self) + x.setIcon(QIcon(I('arrow-down.png'))) l.addWidget(x) - x.setToolTip(_("Change the way searching for books works")) x = parent.saved_search = SavedSearchBox(self) x.setMaximumSize(QSize(150, 16777215)) @@ -218,14 +226,6 @@ class SearchBar(QWidget): # {{{ x.setIcon(QIcon(I("search_add_saved.png"))) x.setObjectName("save_search_button") l.addWidget(x) - x.setToolTip(_("Save current search under the name shown in the box")) - - x = parent.delete_search_button = QToolButton(self) - x.setIcon(QIcon(I("search_delete_saved.png"))) - x.setObjectName("delete_search_button") - l.addWidget(x) - x.setToolTip(_("Delete current saved search")) - # }}} @@ -238,176 +238,6 @@ class Spacer(QWidget): # {{{ self.l.addStretch(10) # }}} -class ToolBar(QToolBar): # {{{ - - def __init__(self, donate, location_manager, child_bar, parent): - QToolBar.__init__(self, parent) - self.gui = parent - self.child_bar = child_bar - self.setContextMenuPolicy(Qt.PreventContextMenu) - self.setMovable(False) - self.setFloatable(False) - self.setOrientation(Qt.Horizontal) - self.setAllowedAreas(Qt.TopToolBarArea|Qt.BottomToolBarArea) - self.setStyleSheet('QToolButton:checked { font-weight: bold }') - self.donate_button = donate - self.apply_settings() - - self.location_manager = location_manager - self.location_manager.locations_changed.connect(self.build_bar) - donate.setAutoRaise(True) - donate.setCursor(Qt.PointingHandCursor) - self.added_actions = [] - self.build_bar() - self.preferred_width = self.sizeHint().width() - self.setAcceptDrops(True) - - def apply_settings(self): - sz = gprefs['toolbar_icon_size'] - sz = {'small':24, 'medium':48, 'large':64}[sz] - self.setIconSize(QSize(sz, sz)) - self.child_bar.setIconSize(QSize(sz, sz)) - style = Qt.ToolButtonTextUnderIcon - if gprefs['toolbar_text'] == 'never': - style = Qt.ToolButtonIconOnly - self.setToolButtonStyle(style) - self.child_bar.setToolButtonStyle(style) - self.donate_button.set_normal_icon_size(sz, sz) - - def contextMenuEvent(self, *args): - pass - - def build_bar(self): - self.child_bar.setVisible(gprefs['show_child_bar']) - self.showing_donate = False - showing_device = self.location_manager.has_device - actions = '-device' if showing_device else '' - actions = gprefs['action-layout-toolbar'+actions] - - for ac in self.added_actions: - m = ac.menu() - if m is not None: - m.setVisible(False) - - self.clear() - self.child_bar.clear() - self.added_actions = [] - self.spacers = [Spacer(self.child_bar), Spacer(self.child_bar), - Spacer(self), Spacer(self)] - self.child_bar.addWidget(self.spacers[0]) - if gprefs['show_child_bar']: - self.addWidget(self.spacers[2]) - - for what in actions: - if what is None and not gprefs['show_child_bar']: - self.addSeparator() - elif what == 'Location Manager': - for ac in self.location_manager.available_actions: - self.addAction(ac) - self.added_actions.append(ac) - self.setup_tool_button(ac, QToolButton.MenuButtonPopup) - elif what == 'Donate': - self.d_widget = QWidget() - self.d_widget.setLayout(QVBoxLayout()) - self.d_widget.layout().addWidget(self.donate_button) - self.addWidget(self.d_widget) - self.showing_donate = True - elif what in self.gui.iactions: - action = self.gui.iactions[what] - bar = self - if action.action_type == 'current' and gprefs['show_child_bar']: - bar = self.child_bar - bar.addAction(action.qaction) - self.added_actions.append(action.qaction) - self.setup_tool_button(action.qaction, action.popup_type) - - self.child_bar.addWidget(self.spacers[1]) - if gprefs['show_child_bar']: - self.addWidget(self.spacers[3]) - else: - for s in self.spacers[2:]: - s.setVisible(False) - - def setup_tool_button(self, ac, menu_mode=None): - ch = self.widgetForAction(ac) - if ch is None: - ch = self.child_bar.widgetForAction(ac) - ch.setCursor(Qt.PointingHandCursor) - ch.setAutoRaise(True) - if ac.menu() is not None and menu_mode is not None: - ch.setPopupMode(menu_mode) - - def resizeEvent(self, ev): - QToolBar.resizeEvent(self, ev) - style = Qt.ToolButtonTextUnderIcon - p = gprefs['toolbar_text'] - if p == 'never': - style = Qt.ToolButtonIconOnly - - if p == 'auto' and self.preferred_width > self.width()+35 and \ - not gprefs['show_child_bar']: - style = Qt.ToolButtonIconOnly - - self.setToolButtonStyle(style) - - def database_changed(self, db): - pass - - #support drag&drop from/to library from/to reader/card - def dragEnterEvent(self, event): - md = event.mimeData() - if md.hasFormat("application/calibre+from_library") or \ - md.hasFormat("application/calibre+from_device"): - event.setDropAction(Qt.CopyAction) - event.accept() - else: - event.ignore() - - def dragMoveEvent(self, event): - allowed = False - md = event.mimeData() - #Drop is only allowed in the location manager widget's different from the selected one - for ac in self.location_manager.available_actions: - w = self.widgetForAction(ac) - if w is not None: - if ( md.hasFormat("application/calibre+from_library") or \ - md.hasFormat("application/calibre+from_device") ) and \ - w.geometry().contains(event.pos()) and \ - isinstance(w, QToolButton) and not w.isChecked(): - allowed = True - break - if allowed: - event.acceptProposedAction() - else: - event.ignore() - - def dropEvent(self, event): - data = event.mimeData() - - mime = 'application/calibre+from_library' - if data.hasFormat(mime): - ids = list(map(int, str(data.data(mime)).split())) - tgt = None - for ac in self.location_manager.available_actions: - w = self.widgetForAction(ac) - if w is not None and w.geometry().contains(event.pos()): - tgt = ac.calibre_name - if tgt is not None: - if tgt == 'main': - tgt = None - self.gui.sync_to_device(tgt, False, send_ids=ids) - event.accept() - - mime = 'application/calibre+from_device' - if data.hasFormat(mime): - paths = [unicode(u.toLocalFile()) for u in data.urls()] - if paths: - self.gui.iactions['Add Books'].add_books_from_device( - self.gui.current_view(), paths=paths) - event.accept() - -# }}} - class MainWindowMixin(object): # {{{ def __init__(self, db): @@ -427,11 +257,17 @@ class MainWindowMixin(object): # {{{ self.iactions['Fetch News'].init_scheduler(db) self.search_bar = SearchBar(self) - self.child_bar = QToolBar(self) - self.tool_bar = ToolBar(self.donate_button, - self.location_manager, self.child_bar, self) - self.addToolBar(Qt.TopToolBarArea, self.tool_bar) - self.addToolBar(Qt.BottomToolBarArea, self.child_bar) + self.bars_manager = BarsManager(self.donate_button, + self.location_manager, self) + for bar in self.bars_manager.main_bars: + self.addToolBar(Qt.TopToolBarArea, bar) + for bar in self.bars_manager.child_bars: + self.addToolBar(Qt.BottomToolBarArea, bar) + self.bars_manager.update_bars() + # This is disabled because it introduces various toolbar related bugs + # The width of the toolbar becomes the sum of both toolbars + if tweaks['unified_title_toolbar_on_osx']: + self.setUnifiedTitleAndToolBarOnMac(True) l = self.centralwidget.layout() l.addWidget(self.search_bar) diff --git a/src/calibre/gui2/library/delegates.py b/src/calibre/gui2/library/delegates.py index 87da6818eb..94c3deb403 100644 --- a/src/calibre/gui2/library/delegates.py +++ b/src/calibre/gui2/library/delegates.py @@ -5,14 +5,14 @@ __license__ = 'GPL v3' __copyright__ = '2010, Kovid Goyal <kovid@kovidgoyal.net>' __docformat__ = 'restructuredtext en' -import sys from math import cos, sin, pi -from PyQt4.Qt import QColor, Qt, QModelIndex, QSize, \ - QPainterPath, QLinearGradient, QBrush, \ - QPen, QStyle, QPainter, QStyleOptionViewItemV4, \ - QIcon, QDoubleSpinBox, QVariant, QSpinBox, \ - QStyledItemDelegate, QComboBox, QTextDocument +from PyQt4.Qt import (QColor, Qt, QModelIndex, QSize, QApplication, + QPainterPath, QLinearGradient, QBrush, + QPen, QStyle, QPainter, QStyleOptionViewItemV4, + QIcon, QDoubleSpinBox, QVariant, QSpinBox, + QStyledItemDelegate, QComboBox, QTextDocument, + QAbstractTextDocumentLayout) from calibre.gui2 import UNDEFINED_QDATE, error_dialog from calibre.gui2.widgets import EnLineEdit @@ -28,7 +28,6 @@ from calibre.gui2.dialogs.template_dialog import TemplateDialog class RatingDelegate(QStyledItemDelegate): # {{{ COLOR = QColor("blue") SIZE = 16 - PEN = QPen(COLOR, 1, Qt.SolidLine, Qt.RoundCap, Qt.RoundJoin) def __init__(self, parent): QStyledItemDelegate.__init__(self, parent) @@ -41,10 +40,7 @@ class RatingDelegate(QStyledItemDelegate): # {{{ 50 + 40 * sin(0.8 * i * pi)) self.star_path.closeSubpath() self.star_path.setFillRule(Qt.WindingFill) - gradient = QLinearGradient(0, 0, 0, 100) - gradient.setColorAt(0.0, self.COLOR) - gradient.setColorAt(1.0, self.COLOR) - self.brush = QBrush(gradient) + self.gradient = QLinearGradient(0, 0, 0, 100) self.factor = self.SIZE/100. def sizeHint(self, option, index): @@ -54,7 +50,8 @@ class RatingDelegate(QStyledItemDelegate): # {{{ def paint(self, painter, option, index): style = self._parent.style() option = QStyleOptionViewItemV4(option) - self.initStyleOption(option, self.dummy) + self.initStyleOption(option, index) + option.text = u'' num = index.model().data(index, Qt.DisplayRole).toInt()[0] def draw_star(): painter.save() @@ -71,13 +68,23 @@ class RatingDelegate(QStyledItemDelegate): # {{{ painter, self._parent) elif option.state & QStyle.State_Selected: painter.fillRect(option.rect, option.palette.highlight()) + else: + painter.fillRect(option.rect, option.backgroundBrush) + try: painter.setRenderHint(QPainter.Antialiasing) painter.setClipRect(option.rect) y = option.rect.center().y()-self.SIZE/2. x = option.rect.left() - painter.setPen(self.PEN) - painter.setBrush(self.brush) + color = index.data(Qt.ForegroundRole) + if color.isNull() or not color.isValid(): + color = self.COLOR + else: + color = QColor(color) + painter.setPen(QPen(color, 1, Qt.SolidLine, Qt.RoundCap, Qt.RoundJoin)) + self.gradient.setColorAt(0.0, color) + self.gradient.setColorAt(1.0, color) + painter.setBrush(QBrush(self.gradient)) painter.translate(x, y) i = 0 while i < num: @@ -98,18 +105,25 @@ class RatingDelegate(QStyledItemDelegate): # {{{ class DateDelegate(QStyledItemDelegate): # {{{ + def __init__(self, parent, tweak_name='gui_timestamp_display_format', + default_format='dd MMM yyyy', editor_format='dd MMM yyyy'): + QStyledItemDelegate.__init__(self, parent) + self.tweak_name = tweak_name + self.default_format = default_format + self.editor_format = editor_format + def displayText(self, val, locale): d = val.toDate() if d <= UNDEFINED_QDATE: return '' - format = tweaks['gui_timestamp_display_format'] + format = tweaks[self.tweak_name] if format is None: - format = 'dd MMM yyyy' + format = self.default_format return format_date(d.toPyDate(), format) def createEditor(self, parent, option, index): qde = QStyledItemDelegate.createEditor(self, parent, option, index) - qde.setDisplayFormat('dd MMM yyyy') + qde.setDisplayFormat(self.editor_format) qde.setMinimumDate(UNDEFINED_QDATE) qde.setSpecialValueText(_('Undefined')) qde.setCalendarPopup(True) @@ -235,6 +249,23 @@ class CcDateDelegate(QStyledItemDelegate): # {{{ # }}} class CcTextDelegate(QStyledItemDelegate): # {{{ + ''' + Delegate for text data. + ''' + + def createEditor(self, parent, option, index): + m = index.model() + col = m.column_map[index.column()] + editor = MultiCompleteLineEdit(parent) + editor.set_separator(None) + complete_items = sorted(list(m.db.all_custom(label=m.db.field_metadata.key_to_label(col))), + key=sort_key) + editor.update_items_cache(complete_items) + return editor + +# }}} + +class CcNumberDelegate(QStyledItemDelegate): # {{{ ''' Delegate for text/int/float data. ''' @@ -242,25 +273,23 @@ class CcTextDelegate(QStyledItemDelegate): # {{{ def createEditor(self, parent, option, index): m = index.model() col = m.column_map[index.column()] - typ = m.custom_columns[col]['datatype'] - if typ == 'int': + if m.custom_columns[col]['datatype'] == 'int': editor = QSpinBox(parent) - editor.setRange(-100, sys.maxint) + editor.setRange(-100, 100000000) editor.setSpecialValueText(_('Undefined')) editor.setSingleStep(1) - elif typ == 'float': + else: editor = QDoubleSpinBox(parent) editor.setSpecialValueText(_('Undefined')) - editor.setRange(-100., float(sys.maxint)) + editor.setRange(-100., 100000000) editor.setDecimals(2) - else: - editor = MultiCompleteLineEdit(parent) - editor.set_separator(None) - complete_items = sorted(list(m.db.all_custom(label=m.db.field_metadata.key_to_label(col))), - key=sort_key) - editor.update_items_cache(complete_items) return editor + def setEditorData(self, editor, index): + m = index.model() + val = m.db.data[index.row()][m.custom_columns[m.column_map[index.column()]]['rec_index']] + editor.setValue(val) + # }}} class CcEnumDelegate(QStyledItemDelegate): # {{{ @@ -305,18 +334,22 @@ class CcCommentsDelegate(QStyledItemDelegate): # {{{ self.document = QTextDocument() def paint(self, painter, option, index): - style = self.parent().style() - self.document.setHtml(index.data(Qt.DisplayRole).toString()) - painter.save() + self.initStyleOption(option, index) + style = QApplication.style() if option.widget is None \ + else option.widget.style() + self.document.setHtml(option.text) + option.text = u'' if hasattr(QStyle, 'CE_ItemViewItem'): - style.drawControl(QStyle.CE_ItemViewItem, option, - painter, self.parent()) - elif option.state & QStyle.State_Selected: - painter.fillRect(option.rect, option.palette.highlight()) - painter.setClipRect(option.rect) - painter.translate(option.rect.topLeft()) - self.document.drawContents(painter) - painter.restore() + style.drawControl(QStyle.CE_ItemViewItem, option, painter) + ctx = QAbstractTextDocumentLayout.PaintContext() + ctx.palette = option.palette #.setColor(QPalette.Text, QColor("red")); + if hasattr(QStyle, 'SE_ItemViewItemText'): + textRect = style.subElementRect(QStyle.SE_ItemViewItemText, option) + painter.save() + painter.translate(textRect.topLeft()) + painter.setClipRect(textRect.translated(-textRect.topLeft())) + self.document.documentLayout().draw(painter, ctx) + painter.restore() def createEditor(self, parent, option, index): m = index.model() @@ -354,7 +387,7 @@ class CcBoolDelegate(QStyledItemDelegate): # {{{ editor = DelegateCB(parent) items = [_('Y'), _('N'), ' '] icons = [I('ok.png'), I('list_remove.png'), I('blank.png')] - if tweaks['bool_custom_columns_are_tristate'] == 'no': + if not index.model().db.prefs.get('bools_are_tristate'): items = items[:-1] icons = icons[:-1] for icon, text in zip(icons, items): @@ -368,7 +401,7 @@ class CcBoolDelegate(QStyledItemDelegate): # {{{ def setEditorData(self, editor, index): m = index.model() val = m.db.data[index.row()][m.custom_columns[m.column_map[index.column()]]['rec_index']] - if tweaks['bool_custom_columns_are_tristate'] == 'no': + if not m.db.prefs.get('bools_are_tristate'): val = 1 if not val else 0 else: val = 2 if val is None else 1 if not val else 0 @@ -385,8 +418,9 @@ class CcTemplateDelegate(QStyledItemDelegate): # {{{ def createEditor(self, parent, option, index): m = index.model() + mi = m.db.get_metadata(index.row(), index_is_id=False) text = m.custom_columns[m.column_map[index.column()]]['display']['composite_template'] - editor = TemplateDialog(parent, text) + editor = TemplateDialog(parent, text, mi) editor.setWindowTitle(_("Edit template")) editor.textbox.setTabChangesFocus(False) editor.textbox.setTabStopWidth(20) @@ -399,7 +433,7 @@ class CcTemplateDelegate(QStyledItemDelegate): # {{{ val = unicode(editor.textbox.toPlainText()) try: validation_formatter.validate(val) - except Exception, err: + except Exception as err: error_dialog(self.parent(), _('Invalid template'), '<p>'+_('The template %s is invalid:')%val + \ '<br>'+str(err), show=True) diff --git a/src/calibre/gui2/library/models.py b/src/calibre/gui2/library/models.py index a5e68ab6a6..d79c92befa 100644 --- a/src/calibre/gui2/library/models.py +++ b/src/calibre/gui2/library/models.py @@ -7,23 +7,23 @@ __docformat__ = 'restructuredtext en' import shutil, functools, re, os, traceback from contextlib import closing -from operator import attrgetter +from collections import defaultdict -from PyQt4.Qt import QAbstractTableModel, Qt, pyqtSignal, QIcon, QImage, \ - QModelIndex, QVariant, QDate, QColor +from PyQt4.Qt import (QAbstractTableModel, Qt, pyqtSignal, QIcon, QImage, + QModelIndex, QVariant, QDate, QColor) -from calibre.gui2 import NONE, config, UNDEFINED_QDATE +from calibre.gui2 import NONE, UNDEFINED_QDATE from calibre.utils.pyparsing import ParseException from calibre.ebooks.metadata import fmt_sidx, authors_to_string, string_to_authors +from calibre.ebooks.metadata.book.base import composite_formatter from calibre.ptempfile import PersistentTemporaryFile from calibre.utils.config import tweaks, prefs -from calibre.utils.date import dt_factory, qt_to_dt, isoformat -from calibre.utils.icu import sort_key, strcmp as icu_strcmp -from calibre.ebooks.metadata.meta import set_metadata as _set_metadata +from calibre.utils.date import dt_factory, qt_to_dt +from calibre.utils.icu import sort_key from calibre.utils.search_query_parser import SearchQueryParser -from calibre.library.caches import _match, CONTAINS_MATCH, EQUALS_MATCH, \ - REGEXP_MATCH, MetadataBackup -from calibre import strftime, isbytestring, prepare_string_for_xml +from calibre.library.caches import (_match, CONTAINS_MATCH, EQUALS_MATCH, + REGEXP_MATCH, MetadataBackup, force_to_bool) +from calibre import strftime, isbytestring from calibre.constants import filesystem_encoding, DEBUG from calibre.gui2.library import DEFAULT_SORT @@ -72,6 +72,7 @@ class BooksModel(QAbstractTableModel): # {{{ 'publisher' : _("Publisher"), 'tags' : _("Tags"), 'series' : _("Series"), + 'last_modified' : _('Modified'), } def __init__(self, parent=None, buffer=40): @@ -87,6 +88,7 @@ class BooksModel(QAbstractTableModel): # {{{ self.column_map = [] self.headers = {} self.alignment_map = {} + self.color_cache = defaultdict(dict) self.buffer_size = buffer self.metadata_backup = None self.bool_yes_icon = QIcon(I('ok.png')) @@ -97,6 +99,8 @@ class BooksModel(QAbstractTableModel): # {{{ self.ids_to_highlight_set = set() self.current_highlighted_idx = None self.highlight_only = False + self.column_color_list = [] + self.colors = [unicode(c) for c in QColor.colorNames()] self.read_config() def change_alignment(self, colname, alignment): @@ -116,7 +120,7 @@ class BooksModel(QAbstractTableModel): # {{{ return cc_label in self.custom_columns def read_config(self): - self.use_roman_numbers = config['use_roman_numerals_for_series_number'] + pass def set_device_connected(self, is_connected): self.device_connected = is_connected @@ -152,6 +156,7 @@ class BooksModel(QAbstractTableModel): # {{{ self.headers[col] = self.custom_columns[col]['name'] self.build_data_convertors() + self.set_color_templates(reset=False) self.reset() self.database_changed.emit(db) self.stop_metadata_backup() @@ -169,11 +174,13 @@ class BooksModel(QAbstractTableModel): # {{{ def refresh_ids(self, ids, current_row=-1): + self.color_cache = defaultdict(dict) 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) for row in rows: if row == current_row: self.new_bookdisplay_data.emit( @@ -203,6 +210,7 @@ class BooksModel(QAbstractTableModel): # {{{ return ret def count_changed(self, *args): + self.color_cache = defaultdict(dict) self.count_changed_signal.emit(self.db.count()) def row_indices(self, index): @@ -268,6 +276,15 @@ class BooksModel(QAbstractTableModel): # {{{ return None return self.get_current_highlighted_id() + def highlight_ids(self, ids_to_highlight): + self.ids_to_highlight = ids_to_highlight + self.ids_to_highlight_set = set(self.ids_to_highlight) + if self.ids_to_highlight: + self.current_highlighted_idx = 0 + else: + self.current_highlighted_idx = None + self.reset() + def search(self, text, reset=True): try: if self.highlight_only: @@ -302,10 +319,17 @@ class BooksModel(QAbstractTableModel): # {{{ def sort(self, col, order, reset=True): if not self.db: return - self.about_to_be_sorted.emit(self.db.id) if not isinstance(order, bool): order = order == Qt.AscendingOrder label = self.column_map[col] + self._sort(label, order, reset) + + def sort_by_named_field(self, field, order, reset=True): + if field in self.db.field_metadata.keys(): + self._sort(field, order, reset) + + def _sort(self, label, order, reset): + self.about_to_be_sorted.emit(self.db.id) self.db.sort(label, order) if reset: self.reset() @@ -317,6 +341,10 @@ class BooksModel(QAbstractTableModel): # {{{ self.db.refresh(field=None) self.resort(reset=reset) + def reset(self): + self.color_cache = defaultdict(dict) + QAbstractTableModel.reset(self) + def resort(self, reset=True): if not self.db: return @@ -341,63 +369,13 @@ class BooksModel(QAbstractTableModel): # {{{ return self.rowCount(None) def get_book_display_info(self, idx): - def custom_keys_to_display(): - ans = getattr(self, '_custom_fields_in_book_info', None) - if ans is None: - cfkeys = set(self.db.custom_field_keys()) - yes_fields = set(tweaks['book_details_will_display']) - no_fields = set(tweaks['book_details_wont_display']) - if '*' in yes_fields: - yes_fields = cfkeys - if '*' in no_fields: - no_fields = cfkeys - ans = frozenset(yes_fields - no_fields) - setattr(self, '_custom_fields_in_book_info', ans) - return ans - - data = {} - cdata = self.cover(idx) - if cdata: - data['cover'] = cdata - tags = list(self.db.get_tags(self.db.id(idx))) - if tags: - tags.sort(key=sort_key) - tags = ', '.join(tags) - else: - tags = _('None') - data[_('Tags')] = tags - formats = self.db.formats(idx) - if formats: - formats = formats.replace(',', ', ') - else: - formats = _('None') - data[_('Formats')] = formats - data[_('Path')] = self.db.abspath(idx) - data['id'] = self.id(idx) - comments = self.db.comments(idx) - if not comments: - comments = _('None') - data[_('Comments')] = comments - series = self.db.series(idx) - if series: - sidx = self.db.series_index(idx) - sidx = fmt_sidx(sidx, use_roman = self.use_roman_numbers) - data[_('Series')] = \ - _('Book %s of %s.')%\ - (sidx, prepare_string_for_xml(series)) mi = self.db.get_metadata(idx) - cf_to_display = custom_keys_to_display() - for key in mi.custom_field_keys(): - if key not in cf_to_display: - continue - name, val = mi.format_field(key) - if mi.metadata_for_field(key)['datatype'] == 'comments': - name += ':html' - if val and name not in data: - data[name] = val - - return data - + mi.size = mi.book_size + mi.cover_data = ('jpg', self.cover(idx)) + mi.id = self.db.id(idx) + mi.field_metadata = self.db.field_metadata + mi.path = self.db.abspath(idx, create_dirs=False) + return mi def current_changed(self, current, previous, emit_signal=True): if current.isValid(): @@ -411,16 +389,8 @@ class BooksModel(QAbstractTableModel): # {{{ def get_book_info(self, index): if isinstance(index, int): index = self.index(index, 0) + # If index is not valid returns None data = self.current_changed(index, None, False) - if data is None: - return data - row = index.row() - data[_('Title')] = self.db.title(row) - au = self.db.authors(row) - if not au: - au = _('Unknown') - au = ', '.join([a.strip() for a in au.split(',')]) - data[_('Author(s)')] = au return data def metadata_for(self, ids): @@ -463,6 +433,7 @@ class BooksModel(QAbstractTableModel): # {{{ def get_preferred_formats_from_ids(self, ids, formats, set_metadata=False, specific_format=None, exclude_auto=False, mode='r+b'): + from calibre.ebooks.metadata.meta import set_metadata as _set_metadata ans = [] need_auto = [] if specific_format is not None: @@ -511,6 +482,7 @@ class BooksModel(QAbstractTableModel): # {{{ def get_preferred_formats(self, rows, formats, paths=False, set_metadata=False, specific_format=None, exclude_auto=False): + from calibre.ebooks.metadata.meta import set_metadata as _set_metadata ans = [] need_auto = [] if specific_format is not None: @@ -547,6 +519,9 @@ class BooksModel(QAbstractTableModel): # {{{ def id(self, row): return self.db.id(getattr(row, 'row', lambda:row)()) + def authors(self, row_number): + return self.db.authors(row_number) + def title(self, row_number): return self.db.title(row_number) @@ -570,6 +545,15 @@ class BooksModel(QAbstractTableModel): # {{{ img = self.default_image return img + def set_color_templates(self, reset=True): + self.column_color_list = [] + for i in range(1,self.db.column_color_count+1): + name = self.db.prefs.get('column_color_name_'+str(i)) + if name: + self.column_color_list.append((name, + self.db.prefs.get('column_color_template_'+str(i)))) + if reset: + self.reset() def build_data_convertors(self): def authors(r, idx=-1): @@ -596,7 +580,10 @@ class BooksModel(QAbstractTableModel): # {{{ def size(r, idx=-1): size = self.db.data[r][idx] if size: - return QVariant('%.1f'%(float(size)/(1024*1024))) + ans = '%.1f'%(float(size)/(1024*1024)) + if size > 0 and ans == '0.0': + ans = '<0.1' + return QVariant(ans) return None def rating_type(r, idx=-1): @@ -615,7 +602,7 @@ class BooksModel(QAbstractTableModel): # {{{ return None # displayed using a decorator def bool_type_decorator(r, idx=-1, bool_cols_are_tristate=True): - val = self.db.data[r][idx] + val = force_to_bool(self.db.data[r][idx]) if not bool_cols_are_tristate: if val is None or not val: return self.bool_no_icon @@ -631,18 +618,31 @@ class BooksModel(QAbstractTableModel): # {{{ return self.bool_yes_icon return self.bool_blank_icon - def text_type(r, mult=False, idx=-1): + def text_type(r, mult=None, idx=-1): text = self.db.data[r][idx] - if text and mult: - return QVariant(', '.join(sorted(text.split('|'),key=sort_key))) + if text and mult is not None: + if mult: + return QVariant(u' & '.join(text.split('|'))) + return QVariant(u', '.join(sorted(text.split('|'),key=sort_key))) return QVariant(text) - def number_type(r, idx=-1): + def decorated_text_type(r, idx=-1): + text = self.db.data[r][idx] + if force_to_bool(text) is not None: + return None + return QVariant(text) + + def number_type(r, idx=-1, fmt=None): + if fmt is not None: + try: + return QVariant(fmt.format(self.db.data[r][idx])) + except: + pass return QVariant(self.db.data[r][idx]) self.dc = { 'title' : functools.partial(text_type, - idx=self.db.field_metadata['title']['rec_index'], mult=False), + idx=self.db.field_metadata['title']['rec_index'], mult=None), 'authors' : functools.partial(authors, idx=self.db.field_metadata['authors']['rec_index']), 'size' : functools.partial(size, @@ -651,17 +651,19 @@ class BooksModel(QAbstractTableModel): # {{{ idx=self.db.field_metadata['timestamp']['rec_index']), 'pubdate' : functools.partial(datetime_type, idx=self.db.field_metadata['pubdate']['rec_index']), + 'last_modified': functools.partial(datetime_type, + idx=self.db.field_metadata['last_modified']['rec_index']), 'rating' : functools.partial(rating_type, idx=self.db.field_metadata['rating']['rec_index']), 'publisher': functools.partial(text_type, - idx=self.db.field_metadata['publisher']['rec_index'], mult=False), + idx=self.db.field_metadata['publisher']['rec_index'], mult=None), 'tags' : functools.partial(tags, idx=self.db.field_metadata['tags']['rec_index']), 'series' : functools.partial(series_type, idx=self.db.field_metadata['series']['rec_index'], siix=self.db.field_metadata['series_index']['rec_index']), 'ondevice' : functools.partial(text_type, - idx=self.db.field_metadata['ondevice']['rec_index'], mult=False), + idx=self.db.field_metadata['ondevice']['rec_index'], mult=None), } self.dc_decorator = { @@ -674,17 +676,28 @@ class BooksModel(QAbstractTableModel): # {{{ idx = self.custom_columns[col]['rec_index'] datatype = self.custom_columns[col]['datatype'] if datatype in ('text', 'comments', 'composite', 'enumeration'): - self.dc[col] = functools.partial(text_type, idx=idx, - mult=self.custom_columns[col]['is_multiple']) + mult=self.custom_columns[col]['is_multiple'] + if mult is not None: + mult = self.custom_columns[col]['display'].get('is_names', False) + self.dc[col] = functools.partial(text_type, idx=idx, mult=mult) + if datatype in ['text', 'composite', 'enumeration'] and not mult: + if self.custom_columns[col]['display'].get('use_decorations', False): + self.dc[col] = functools.partial(decorated_text_type, idx=idx) + self.dc_decorator[col] = functools.partial( + bool_type_decorator, idx=idx, + bool_cols_are_tristate= + self.db.prefs.get('bools_are_tristate')) elif datatype in ('int', 'float'): - self.dc[col] = functools.partial(number_type, idx=idx) + fmt = self.custom_columns[col]['display'].get('number_format', None) + self.dc[col] = functools.partial(number_type, idx=idx, fmt=fmt) elif datatype == 'datetime': self.dc[col] = functools.partial(datetime_type, idx=idx) elif datatype == 'bool': self.dc[col] = functools.partial(bool_type, idx=idx) self.dc_decorator[col] = functools.partial( bool_type_decorator, idx=idx, - bool_cols_are_tristate=tweaks['bool_custom_columns_are_tristate'] != 'no') + bool_cols_are_tristate= + self.db.prefs.get('bools_are_tristate')) elif datatype == 'rating': self.dc[col] = functools.partial(rating_type, idx=idx) elif datatype == 'series': @@ -708,9 +721,43 @@ class BooksModel(QAbstractTableModel): # {{{ return NONE if role in (Qt.DisplayRole, Qt.EditRole): return self.column_to_dc_map[col](index.row()) - elif role == Qt.BackgroundColorRole: + elif role == Qt.BackgroundRole: if self.id(index) in self.ids_to_highlight_set: - return QColor('lightgreen') + return QVariant(QColor('lightgreen')) + elif role == Qt.ForegroundRole: + key = self.column_map[col] + for k,fmt in self.column_color_list: + 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] + mi = self.db.get_metadata(self.id(index), index_is_id=True) + try: + color = composite_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: + return NONE + if self.is_custom_column(key) and \ + self.custom_columns[key]['datatype'] == 'enumeration': + cc = self.custom_columns[self.column_map[col]]['display'] + colors = cc.get('enum_colors', []) + values = cc.get('enum_values', []) + txt = unicode(index.data(Qt.DisplayRole).toString()) + if len(colors) > 0 and txt in values: + try: + color = QColor(colors[values.index(txt)]) + if color.isValid(): + return QVariant(color) + except: + pass + return NONE elif role == Qt.DecorationRole: if self.column_to_dc_decorator_map[col] is not None: return self.column_to_dc_decorator_map[index.column()](index.row()) @@ -957,6 +1004,21 @@ class OnDeviceSearch(SearchQueryParser): # {{{ # }}} +class DeviceDBSortKeyGen(object): # {{{ + + def __init__(self, attr, keyfunc, db): + self.attr = attr + self.db = db + self.keyfunc = keyfunc + + def __call__(self, x): + try: + ans = self.keyfunc(getattr(self.db[x], self.attr)) + except: + ans = None + return ans +# }}} + class DeviceBooksModel(BooksModel): # {{{ booklist_dirtied = pyqtSignal() @@ -1062,59 +1124,40 @@ class DeviceBooksModel(BooksModel): # {{{ def sort(self, col, order, reset=True): descending = order != Qt.AscendingOrder - def strcmp(attr): - ag = attrgetter(attr) - def _strcmp(x, y): - x = ag(self.db[x]) - y = ag(self.db[y]) - if x == None: - x = '' - if y == None: - y = '' - return icu_strcmp(x.strip(), y.strip()) - return _strcmp - def datecmp(x, y): - x = self.db[x].datetime - y = self.db[y].datetime - return cmp(dt_factory(x, assume_utc=True), dt_factory(y, - assume_utc=True)) - def sizecmp(x, y): - x, y = int(self.db[x].size), int(self.db[y].size) - return cmp(x, y) - def tagscmp(x, y): - x = ','.join(sorted(getattr(self.db[x], 'device_collections', []),key=sort_key)) - y = ','.join(sorted(getattr(self.db[y], 'device_collections', []),key=sort_key)) - return cmp(x, y) - def libcmp(x, y): - x, y = self.db[x].in_library, self.db[y].in_library - return cmp(x, y) - def authorcmp(x, y): - ax = getattr(self.db[x], 'author_sort', None) - ay = getattr(self.db[y], 'author_sort', None) - if ax and ay: - x = ax - y = ay - else: - x, y = authors_to_string(self.db[x].authors), \ - authors_to_string(self.db[y].authors) - return cmp(x, y) cname = self.column_map[col] - fcmp = { - 'title': strcmp('title_sorter'), - 'authors' : authorcmp, - 'size' : sizecmp, - 'timestamp': datecmp, - 'collections': tagscmp, - 'inlibrary': libcmp, + def author_key(x): + try: + ax = self.db[x].author_sort + if not ax: + raise Exception('') + except: + try: + ax = authors_to_string(self.db[x].authors) + except: + ax = '' + return ax + + keygen = { + 'title': ('title_sorter', lambda x: sort_key(x) if x else ''), + 'authors' : author_key, + 'size' : ('size', int), + 'timestamp': ('datetime', functools.partial(dt_factory, assume_utc=True)), + 'collections': ('device_collections', lambda x:sorted(x, + key=sort_key)), + 'inlibrary': ('in_library', lambda x: x), }[cname] - self.map.sort(cmp=fcmp, reverse=descending) + keygen = keygen if callable(keygen) else DeviceDBSortKeyGen( + keygen[0], keygen[1], self.db) + self.map.sort(key=keygen, reverse=descending) if len(self.map) == len(self.db): self.sorted_map = list(self.map) else: self.sorted_map = list(range(len(self.db))) - self.sorted_map.sort(cmp=fcmp, reverse=descending) + self.sorted_map.sort(key=keygen, reverse=descending) self.sorted_on = (self.column_map[col], order) self.sort_history.insert(0, self.sorted_on) + if hasattr(keygen, 'db'): + keygen.db = None if reset: self.reset() @@ -1156,39 +1199,46 @@ class DeviceBooksModel(BooksModel): # {{{ img = self.default_image return img - def current_changed(self, current, previous): - data = {} - item = self.db[self.map[current.row()]] - cover = self.cover(current.row()) - if cover is not self.default_image: - data['cover'] = cover - type = _('Unknown') + def get_book_display_info(self, idx): + from calibre.ebooks.metadata.book.base import Metadata + item = self.db[self.map[idx]] + cover = self.cover(idx) + if cover is self.default_image: + cover = None + title = item.title + if not title: + title = _('Unknown') + au = item.authors + if not au: + au = [_('Unknown')] + mi = Metadata(title, au) + mi.cover_data = ('jpg', cover) + fmt = _('Unknown') ext = os.path.splitext(item.path)[1] if ext: - type = ext[1:].lower() - data[_('Format')] = type - data[_('Path')] = item.path + fmt = ext[1:].lower() + mi.formats = [fmt] + mi.path = (item.path if item.path else None) dt = dt_factory(item.datetime, assume_utc=True) - data[_('Timestamp')] = isoformat(dt, sep=' ', as_utc=False) - data[_('Collections')] = ', '.join(item.device_collections) - - tags = getattr(item, 'tags', None) - if tags: - tags = u', '.join(tags) - else: - tags = _('None') - data[_('Tags')] = tags - comments = getattr(item, 'comments', None) - if not comments: - comments = _('None') - data[_('Comments')] = comments + mi.timestamp = dt + mi.device_collections = list(item.device_collections) + mi.tags = list(getattr(item, 'tags', [])) + mi.comments = getattr(item, 'comments', None) series = getattr(item, 'series', None) if series: sidx = getattr(item, 'series_index', 0) - sidx = fmt_sidx(sidx, use_roman = self.use_roman_numbers) - data[_('Series')] = _('Book <font face="serif">%s</font> of %s.')%(sidx, series) + mi.series = series + mi.series_index = sidx + return mi - self.new_bookdisplay_data.emit(data) + def current_changed(self, current, previous, emit_signal=True): + if current.isValid(): + idx = current.row() + data = self.get_book_display_info(idx) + if emit_signal: + self.new_bookdisplay_data.emit(data) + else: + return data def paths(self, rows): return [self.db[self.map[r.row()]].path for r in rows ] @@ -1248,7 +1298,7 @@ class DeviceBooksModel(BooksModel): # {{{ elif cname == 'authors': au = self.db[self.map[row]].authors if not au: - au = self.unknown + au = [_('Unknown')] return QVariant(authors_to_string(au)) elif cname == 'size': size = self.db[self.map[row]].size diff --git a/src/calibre/gui2/library/views.py b/src/calibre/gui2/library/views.py index c62936a46f..f59473851f 100644 --- a/src/calibre/gui2/library/views.py +++ b/src/calibre/gui2/library/views.py @@ -15,7 +15,7 @@ from PyQt4.Qt import QTableView, Qt, QAbstractItemView, QMenu, pyqtSignal, \ from calibre.gui2.library.delegates import RatingDelegate, PubDateDelegate, \ TextDelegate, DateDelegate, CompleteDelegate, CcTextDelegate, \ CcBoolDelegate, CcCommentsDelegate, CcDateDelegate, CcTemplateDelegate, \ - CcEnumDelegate + CcEnumDelegate, CcNumberDelegate from calibre.gui2.library.models import BooksModel, DeviceBooksModel from calibre.utils.config import tweaks, prefs from calibre.gui2 import error_dialog, gprefs @@ -76,8 +76,11 @@ class BooksView(QTableView): # {{{ self.rating_delegate = RatingDelegate(self) self.timestamp_delegate = DateDelegate(self) self.pubdate_delegate = PubDateDelegate(self) + self.last_modified_delegate = DateDelegate(self, + tweak_name='gui_last_modified_display_format') self.tags_delegate = CompleteDelegate(self, ',', 'all_tags') self.authors_delegate = CompleteDelegate(self, '&', 'all_author_names', True) + self.cc_names_delegate = CompleteDelegate(self, '&', 'all_custom', True) self.series_delegate = TextDelegate(self) self.publisher_delegate = TextDelegate(self) self.text_delegate = TextDelegate(self) @@ -86,6 +89,7 @@ class BooksView(QTableView): # {{{ self.cc_bool_delegate = CcBoolDelegate(self) self.cc_comments_delegate = CcCommentsDelegate(self) self.cc_template_delegate = CcTemplateDelegate(self) + self.cc_number_delegate = CcNumberDelegate(self) self.display_parent = parent self._model = modelcls(self) self.setModel(self._model) @@ -235,6 +239,46 @@ class BooksView(QTableView): # {{{ sm.select(idx, sm.Select|sm.Rows) self.scroll_to_row(indices[0].row()) self.selected_ids = [] + + def sort_by_named_field(self, field, order, reset=True): + if field in self.column_map: + idx = self.column_map.index(field) + if order: + self.sortByColumn(idx, Qt.AscendingOrder) + else: + self.sortByColumn(idx, Qt.DescendingOrder) + else: + self._model.sort_by_named_field(field, order, reset) + + def multisort(self, fields, reset=True, only_if_different=False): + if len(fields) == 0: + return + sh = self.cleanup_sort_history(self._model.sort_history, + ignore_column_map=True) + if only_if_different and len(sh) >= len(fields): + ret=True + for i,t in enumerate(fields): + if t[0] != sh[i][0]: + ret = False + break + if ret: + return + + for n,d in reversed(fields): + if n in self._model.db.field_metadata.keys(): + sh.insert(0, (n, d)) + sh = self.cleanup_sort_history(sh, ignore_column_map=True) + self._model.sort_history = [tuple(x) for x in sh] + self._model.resort(reset=reset) + col = fields[0][0] + dir = Qt.AscendingOrder if fields[0][1] else Qt.DescendingOrder + if col in self.column_map: + col = self.column_map.index(col) + hdrs = self.horizontalHeader() + try: + hdrs.setSortIndicator(col, dir) + except: + pass # }}} # Ondevice column {{{ @@ -255,6 +299,7 @@ class BooksView(QTableView): # {{{ state = {} state['hidden_columns'] = [cm[i] for i in range(h.count()) if h.isSectionHidden(i) and cm[i] != 'ondevice'] + state['last_modified_injected'] = True state['sort_history'] = \ self.cleanup_sort_history(self.model().sort_history) state['column_positions'] = {} @@ -279,14 +324,14 @@ class BooksView(QTableView): # {{{ state = self.get_state() self.write_state(state) - def cleanup_sort_history(self, sort_history): + def cleanup_sort_history(self, sort_history, ignore_column_map=False): history = [] for col, order in sort_history: if not isinstance(order, bool): continue if col == 'date': col = 'timestamp' - if col in self.column_map: + if ignore_column_map or col in self.column_map: if (not history or history[-1][0] != col): history.append([col, order]) return history @@ -339,7 +384,7 @@ class BooksView(QTableView): # {{{ def get_default_state(self): old_state = { - 'hidden_columns': [], + 'hidden_columns': ['last_modified'], 'sort_history':[DEFAULT_SORT], 'column_positions': {}, 'column_sizes': {}, @@ -347,6 +392,7 @@ class BooksView(QTableView): # {{{ 'size':'center', 'timestamp':'center', 'pubdate':'center'}, + 'last_modified_injected': True, } h = self.column_header cm = self.column_map @@ -357,7 +403,7 @@ class BooksView(QTableView): # {{{ old_state['column_sizes'][name] = \ min(350, max(self.sizeHintForColumn(i), h.sectionSizeHint(i))) - if name == 'timestamp': + if name in ('timestamp', 'last_modified'): old_state['column_sizes'][name] += 12 return old_state @@ -377,6 +423,13 @@ class BooksView(QTableView): # {{{ pass if ans is not None: db.prefs[name] = ans + else: + if not ans.get('last_modified_injected', False): + ans['last_modified_injected'] = True + hc = ans.get('hidden_columns', []) + if 'last_modified' not in hc: + hc.append('last_modified') + db.prefs[name] = ans return ans @@ -387,10 +440,16 @@ class BooksView(QTableView): # {{{ if tweaks['sort_columns_at_startup'] is not None: sh = [] - for c,d in tweaks['sort_columns_at_startup']: - if not isinstance(d, bool): - d = True if d == 0 else False - sh.append((c, d)) + try: + for c,d in tweaks['sort_columns_at_startup']: + if not isinstance(d, bool): + d = True if d == 0 else False + sh.append((c, d)) + except: + # Ignore invalid tweak values as users seem to often get them + # wrong + import traceback + traceback.print_exc() old_state['sort_history'] = sh self.apply_state(old_state) @@ -410,6 +469,7 @@ class BooksView(QTableView): # {{{ self.save_state() self._model.set_database(db) self.tags_delegate.set_database(db) + self.cc_names_delegate.set_database(db) self.authors_delegate.set_database(db) self.series_delegate.set_auto_complete_function(db.all_series) self.publisher_delegate.set_auto_complete_function(db.all_publishers) @@ -417,7 +477,8 @@ class BooksView(QTableView): # {{{ def database_changed(self, db): for i in range(self.model().columnCount(None)): if self.itemDelegateForColumn(i) in (self.rating_delegate, - self.timestamp_delegate, self.pubdate_delegate): + self.timestamp_delegate, self.pubdate_delegate, + self.last_modified_delegate): self.setItemDelegateForColumn(i, self.itemDelegate()) cm = self.column_map @@ -431,13 +492,20 @@ class BooksView(QTableView): # {{{ self.setItemDelegateForColumn(cm.index(colhead), delegate) elif cc['datatype'] == 'comments': self.setItemDelegateForColumn(cm.index(colhead), self.cc_comments_delegate) - elif cc['datatype'] in ('text', 'series'): + elif cc['datatype'] == 'text': if cc['is_multiple']: - self.setItemDelegateForColumn(cm.index(colhead), self.tags_delegate) + if cc['display'].get('is_names', False): + self.setItemDelegateForColumn(cm.index(colhead), + self.cc_names_delegate) + else: + self.setItemDelegateForColumn(cm.index(colhead), + self.tags_delegate) else: self.setItemDelegateForColumn(cm.index(colhead), self.cc_text_delegate) - elif cc['datatype'] in ('int', 'float'): + elif cc['datatype'] == 'series': self.setItemDelegateForColumn(cm.index(colhead), self.cc_text_delegate) + elif cc['datatype'] in ('int', 'float'): + self.setItemDelegateForColumn(cm.index(colhead), self.cc_number_delegate) elif cc['datatype'] == 'bool': self.setItemDelegateForColumn(cm.index(colhead), self.cc_bool_delegate) elif cc['datatype'] == 'rating': @@ -603,6 +671,11 @@ class BooksView(QTableView): # {{{ def column_map(self): return self._model.column_map + def refresh_book_details(self): + idx = self.currentIndex() + if idx.isValid(): + self._model.current_changed(idx, idx) + def scrollContentsBy(self, dx, dy): # Needed as Qt bug causes headerview to not always update when scrolling QTableView.scrollContentsBy(self, dx, dy) @@ -614,7 +687,7 @@ class BooksView(QTableView): # {{{ h = self.horizontalHeader() for i in range(h.count()): if not h.isSectionHidden(i) and h.sectionViewportPosition(i) >= 0: - self.scrollTo(self.model().index(row, i)) + self.scrollTo(self.model().index(row, i), self.PositionAtCenter) break def set_current_row(self, row, select=True): @@ -696,6 +769,8 @@ class BooksView(QTableView): # {{{ id_to_select = self._model.get_current_highlighted_id() if id_to_select is not None: self.select_rows([id_to_select], using_ids=True) + elif self._model.highlight_only: + self.clearSelection() self.setFocus(Qt.OtherFocusReason) def connect_to_search_box(self, sb, search_done): diff --git a/src/calibre/gui2/lrf_renderer/main.py b/src/calibre/gui2/lrf_renderer/main.py index 2acfd3c9a7..0575d106f4 100644 --- a/src/calibre/gui2/lrf_renderer/main.py +++ b/src/calibre/gui2/lrf_renderer/main.py @@ -5,7 +5,7 @@ import sys, logging, os, traceback, time from PyQt4.QtGui import QKeySequence, QPainter, QDialog, QSpinBox, QSlider, QIcon from PyQt4.QtCore import Qt, QObject, SIGNAL, QCoreApplication, QThread -from calibre import __appname__, setup_cli_handlers, islinux, isfreebsd +from calibre import __appname__, setup_cli_handlers, islinux, isbsd from calibre.ebooks.lrf.lrfparser import LRFDocument from calibre.gui2 import ORG_NAME, APP_UID, error_dialog, \ @@ -35,7 +35,7 @@ class RenderWorker(QThread): self.stream = None if self.aborted: self.lrf = None - except Exception, err: + except Exception as err: self.lrf, self.stream = None, None self.exception = err self.formatted_traceback = traceback.format_exc() @@ -258,7 +258,7 @@ def file_renderer(stream, opts, parent=None, logger=None): level = logging.DEBUG if opts.verbose else logging.INFO logger = logging.getLogger('lrfviewer') setup_cli_handlers(logger, level) - if islinux or isfreebsd: + if islinux or isbsd: try: # Set lrfviewer as the default for LRF files for this user from subprocess import call call('xdg-mime default calibre-lrfviewer.desktop application/lrf', shell=True) @@ -307,7 +307,7 @@ def main(args=sys.argv, logger=None): if hasattr(opts, 'help'): parser.print_help() return 1 - pid = os.fork() if (islinux or isfreebsd) else -1 + pid = os.fork() if (islinux or isbsd) else -1 if pid <= 0: app = Application(args) app.setWindowIcon(QIcon(I('viewer.png'))) diff --git a/src/calibre/gui2/main.py b/src/calibre/gui2/main.py index 976b679726..645ce3b228 100644 --- a/src/calibre/gui2/main.py +++ b/src/calibre/gui2/main.py @@ -19,6 +19,9 @@ from calibre.utils.config import prefs, dynamic from calibre.library.database2 import LibraryDatabase2 from calibre.library.sqlite import sqlite, DatabaseException +if iswindows: + winutil = plugins['winutil'][0] + def option_parser(): parser = _option_parser('''\ %prog [opts] [path_to_ebook] @@ -37,6 +40,11 @@ path_to_ebook to the database. parser.add_option('--ignore-plugins', default=False, action='store_true', help=_('Ignore custom plugins, useful if you installed a plugin' ' that is preventing calibre from starting')) + parser.add_option('-s', '--shutdown-running-calibre', default=False, + action='store_true', + help=_('Cause a running calibre instance, if any, to be' + ' shutdown. Note that if there are running jobs, they ' + 'will be silently aborted, so use with care.')) return parser def init_qt(args): @@ -80,8 +88,7 @@ def get_library_path(parent=None): if library_path is None: # Need to migrate to new database layout base = os.path.expanduser('~') if iswindows: - base = plugins['winutil'][0].special_folder_path( - plugins['winutil'][0].CSIDL_PERSONAL) + base = winutil.special_folder_path(winutil.CSIDL_PERSONAL) if not base or not os.path.exists(base): from PyQt4.Qt import QDir base = unicode(QDir.homePath()).replace('/', os.sep) @@ -292,13 +299,13 @@ def run_gui(opts, args, actions, listener, app, gui_debug=None): if getattr(runner.main, 'debug_on_restart', False): run_in_debug_mode() else: + import subprocess print 'Restarting with:', e, sys.argv if hasattr(sys, 'frameworks_dir'): app = os.path.dirname(os.path.dirname(sys.frameworks_dir)) - import subprocess subprocess.Popen('sleep 3s; open '+app, shell=True) else: - os.execvp(e, sys.argv) + subprocess.Popen([e] + sys.argv[1:]) else: if iswindows: try: @@ -337,7 +344,7 @@ def cant_start(msg=_('If you are sure it is not running')+', ', raise SystemExit(1) -def communicate(args): +def communicate(opts, args): t = RC() t.start() time.sleep(3) @@ -346,9 +353,12 @@ def communicate(args): cant_start(what=_('try deleting the file')+': '+f) raise SystemExit(1) - if len(args) > 1: - args[1] = os.path.abspath(args[1]) - t.conn.send('launched:'+repr(args)) + if opts.shutdown_running_calibre: + t.conn.send('shutdown:') + else: + if len(args) > 1: + args[1] = os.path.abspath(args[1]) + t.conn.send('launched:'+repr(args)) t.conn.close() raise SystemExit(0) @@ -363,6 +373,8 @@ def main(args=sys.argv): from calibre.utils.lock import singleinstance from multiprocessing.connection import Listener si = singleinstance('calibre GUI') + if si and opts.shutdown_running_calibre: + return 0 if si: try: listener = Listener(address=ADDRESS) @@ -388,10 +400,10 @@ def main(args=sys.argv): else: # On windows only singleinstance can be trusted otherinstance = True if iswindows else False - if not otherinstance: + if not otherinstance and not opts.shutdown_running_calibre: return run_gui(opts, args, actions, listener, app, gui_debug=gui_debug) - communicate(args) + communicate(opts, args) return 0 @@ -399,7 +411,7 @@ def main(args=sys.argv): if __name__ == '__main__': try: sys.exit(main()) - except Exception, err: + except Exception as err: if not iswindows: raise tb = traceback.format_exc() from PyQt4.QtGui import QErrorMessage diff --git a/src/calibre/gui2/main_window.py b/src/calibre/gui2/main_window.py index e068e851c2..134aae3ad1 100644 --- a/src/calibre/gui2/main_window.py +++ b/src/calibre/gui2/main_window.py @@ -1,10 +1,14 @@ +from __future__ import (unicode_literals, division, absolute_import, + print_function) + __license__ = 'GPL v3' __copyright__ = '2008, Kovid Goyal <kovid at kovidgoyal.net>' -import StringIO, traceback, sys -from PyQt4.Qt import QMainWindow, QString, Qt, QFont, QCoreApplication, SIGNAL,\ - QAction, QMenu, QMenuBar, QIcon, pyqtSignal +import StringIO, traceback, sys, gc + +from PyQt4.Qt import QMainWindow, QString, Qt, QFont, QTimer, \ + QAction, QMenu, QMenuBar, QIcon, pyqtSignal, QObject from calibre.gui2.dialogs.conversion_error import ConversionErrorDialog from calibre.utils.config import OptionParser from calibre.gui2 import error_dialog @@ -16,7 +20,8 @@ Usage: %prog [options] Launch the Graphical User Interface '''): parser = OptionParser(usage) - parser.add_option('--redirect-console-output', default=False, action='store_true', dest='redirect', + # The b is required because of a regression in optparse.py in python 2.7.0 + parser.add_option(b'--redirect-console-output', default=False, action='store_true', dest='redirect', help=_('Redirect console output to a dialog window (both stdout and stderr). Useful on windows where GUI apps do not have a output streams.')) return parser @@ -35,6 +40,53 @@ class DebugWindow(ConversionErrorDialog): def flush(self): pass +class GarbageCollector(QObject): + + ''' + Disable automatic garbage collection and instead collect manually + every INTERVAL milliseconds. + + This is done to ensure that garbage collection only happens in the GUI + thread, as otherwise Qt can crash. + ''' + + INTERVAL = 5000 + + def __init__(self, parent, debug=False): + QObject.__init__(self, parent) + self.debug = debug + + self.timer = QTimer(self) + self.timer.timeout.connect(self.check) + + self.threshold = gc.get_threshold() + gc.disable() + self.timer.start(self.INTERVAL) + #gc.set_debug(gc.DEBUG_SAVEALL) + + def check(self): + #return self.debug_cycles() + l0, l1, l2 = gc.get_count() + if self.debug: + print ('gc_check called:', l0, l1, l2) + if l0 > self.threshold[0]: + num = gc.collect(0) + if self.debug: + print ('collecting gen 0, found:', num, 'unreachable') + if l1 > self.threshold[1]: + num = gc.collect(1) + if self.debug: + print ('collecting gen 1, found:', num, 'unreachable') + if l2 > self.threshold[2]: + num = gc.collect(2) + if self.debug: + print ('collecting gen 2, found:', num, 'unreachable') + + def debug_cycles(self): + gc.collect() + for obj in gc.garbage: + print (obj, repr(obj), type(obj)) + class MainWindow(QMainWindow): ___menu_bar = None @@ -64,19 +116,15 @@ class MainWindow(QMainWindow): quit_action.setMenuRole(QAction.QuitRole) return preferences_action, quit_action - def __init__(self, opts, parent=None): + def __init__(self, opts, parent=None, disable_automatic_gc=False): QMainWindow.__init__(self, parent) - app = QCoreApplication.instance() - if app is not None: - self.connect(app, SIGNAL('unixSignal(int)'), self.unix_signal) + if disable_automatic_gc: + self._gc = GarbageCollector(self, debug=False) if getattr(opts, 'redirect', False): self.__console_redirect = DebugWindow(self) sys.stdout = sys.stderr = self.__console_redirect self.__console_redirect.show() - def unix_signal(self, signal): - print 'Received signal:', repr(signal) - def unhandled_exception(self, type, value, tb): if type == KeyboardInterrupt: self.keyboard_interrupt.emit() diff --git a/src/calibre/gui2/metadata/basic_widgets.py b/src/calibre/gui2/metadata/basic_widgets.py index a135176daf..d58ac4a379 100644 --- a/src/calibre/gui2/metadata/basic_widgets.py +++ b/src/calibre/gui2/metadata/basic_widgets.py @@ -1,5 +1,7 @@ #!/usr/bin/env python # vim:fileencoding=UTF-8:ts=4:sw=4:sta:et:sts=4:ai +from __future__ import (unicode_literals, division, absolute_import, + print_function) __license__ = 'GPL v3' __copyright__ = '2011, Kovid Goyal <kovid@kovidgoyal.net>' @@ -7,20 +9,20 @@ __docformat__ = 'restructuredtext en' import textwrap, re, os -from PyQt4.Qt import Qt, QDateEdit, QDate, \ - QIcon, QToolButton, QWidget, QLabel, QGridLayout, \ - QDoubleSpinBox, QListWidgetItem, QSize, QPixmap, \ - QPushButton, QSpinBox, QLineEdit +from PyQt4.Qt import (Qt, QDateEdit, QDate, pyqtSignal, QMessageBox, + QIcon, QToolButton, QWidget, QLabel, QGridLayout, QApplication, + QDoubleSpinBox, QListWidgetItem, QSize, QPixmap, + QPushButton, QSpinBox, QLineEdit, QSizePolicy) from calibre.gui2.widgets import EnLineEdit, FormatList, ImageView from calibre.gui2.complete import MultiCompleteLineEdit, MultiCompleteComboBox from calibre.utils.icu import sort_key from calibre.utils.config import tweaks, prefs -from calibre.ebooks.metadata import title_sort, authors_to_string, \ - string_to_authors, check_isbn +from calibre.ebooks.metadata import (title_sort, authors_to_string, + string_to_authors, check_isbn, authors_to_sort_string) from calibre.ebooks.metadata.meta import get_metadata -from calibre.gui2 import file_icon_provider, UNDEFINED_QDATE, UNDEFINED_DATE, \ - choose_files, error_dialog, choose_images, question_dialog +from calibre.gui2 import (file_icon_provider, UNDEFINED_QDATE, UNDEFINED_DATE, + choose_files, error_dialog, choose_images) from calibre.utils.date import local_tz, qt_to_dt from calibre import strftime from calibre.ebooks import BOOK_EXTENSIONS @@ -29,6 +31,16 @@ from calibre.utils.date import utcfromtimestamp from calibre.gui2.comments_editor import Editor from calibre.library.comments import comments_to_html from calibre.gui2.dialogs.tag_editor import TagEditor +from calibre.utils.icu import strcmp + +def save_dialog(parent, title, msg, det_msg=''): + d = QMessageBox(parent) + d.setWindowTitle(title) + d.setText(msg) + d.setStandardButtons(QMessageBox.Yes | QMessageBox.No | QMessageBox.Cancel) + return d.exec_() + + ''' The interface common to all widgets used to set basic metadata @@ -76,11 +88,22 @@ class TitleEdit(EnLineEdit): def commit(self, db, id_): title = self.current_val - if self.COMMIT: - getattr(db, 'set_'+ self.TITLE_ATTR)(id_, title, notify=False) - else: - getattr(db, 'set_'+ self.TITLE_ATTR)(id_, title, notify=False, - commit=False) + try: + if self.COMMIT: + getattr(db, 'set_'+ self.TITLE_ATTR)(id_, title, notify=False) + else: + getattr(db, 'set_'+ self.TITLE_ATTR)(id_, title, notify=False, + commit=False) + except (IOError, OSError) as err: + if getattr(err, 'errno', -1) == 13: # Permission denied + import traceback + fname = err.filename if err.filename else 'file' + error_dialog(self, _('Permission denied'), + _('Could not open %s. Is it being used by another' + ' program?')%fname, det_msg=traceback.format_exc(), + show=True) + return False + raise return True @dynamic_property @@ -154,7 +177,7 @@ class AuthorsEdit(MultiCompleteComboBox): TOOLTIP = '' LABEL = _('&Author(s):') - def __init__(self, parent): + def __init__(self, parent, manage_authors): self.dialog = parent self.books_to_refresh = set([]) MultiCompleteComboBox.__init__(self, parent) @@ -162,6 +185,28 @@ class AuthorsEdit(MultiCompleteComboBox): self.setWhatsThis(self.TOOLTIP) self.setEditable(True) self.setSizeAdjustPolicy(self.AdjustToMinimumContentsLengthWithIcon) + manage_authors.triggered.connect(self.manage_authors) + + def manage_authors(self): + if self.original_val != self.current_val: + d = save_dialog(self, _('Authors changed'), + _('You have changed the authors for this book. You must save ' + 'these changes before you can use Manage authors. Do you ' + 'want to save these changes?')) + if d == QMessageBox.Cancel: + return + if d == QMessageBox.Yes: + self.commit(self.db, self.id_) + self.db.commit() + self.original_val = self.current_val + else: + self.current_val = self.original_val + first_author = self.current_val[0] if len(self.current_val) else None + first_author_id = self.db.get_author_id(first_author) if first_author else None + self.dialog.parent().do_author_sort_edit(self, first_author_id, + select_sort=False) + self.initialize(self.db, self.id_) + self.dialog.author_sort.initialize(self.db, self.id_) def get_default(self): return _('Unknown') @@ -170,10 +215,11 @@ class AuthorsEdit(MultiCompleteComboBox): self.books_to_refresh = set([]) all_authors = db.all_authors() all_authors.sort(key=lambda x : sort_key(x[1])) + self.clear() for i in all_authors: id, name = i - name = [name.strip().replace('|', ',') for n in name.split(',')] - self.addItem(authors_to_string(name)) + name = name.strip().replace('|', ',') + self.addItem(name) self.set_separator('&') self.set_space_before_sep(True) @@ -185,11 +231,24 @@ class AuthorsEdit(MultiCompleteComboBox): au = _('Unknown') self.current_val = [a.strip().replace('|', ',') for a in au.split(',')] self.original_val = self.current_val + self.id_ = id_ + self.db = db def commit(self, db, id_): authors = self.current_val - self.books_to_refresh |= db.set_authors(id_, authors, notify=False, + try: + self.books_to_refresh |= db.set_authors(id_, authors, notify=False, allow_case_change=True) + except (IOError, OSError) as err: + if getattr(err, 'errno', -1) == 13: # Permission denied + import traceback + fname = err.filename if err.filename else 'file' + error_dialog(self, _('Permission denied'), + _('Could not open %s. Is it being used by another' + ' program?')%fname, det_msg=traceback.format_exc(), + show=True) + return False + raise return True @dynamic_property @@ -219,7 +278,8 @@ class AuthorSortEdit(EnLineEdit): 'red, then the authors and this text do not match.') LABEL = _('Author s&ort:') - def __init__(self, parent, authors_edit, autogen_button, db): + def __init__(self, parent, authors_edit, autogen_button, db, + copy_a_to_as_action, copy_as_to_a_action): EnLineEdit.__init__(self, parent) self.authors_edit = authors_edit self.db = db @@ -234,10 +294,12 @@ class AuthorSortEdit(EnLineEdit): 'No action is required if this is what you want.')) self.tooltips = (ok_tooltip, bad_tooltip) - self.authors_edit.editTextChanged.connect(self.update_state) + self.authors_edit.editTextChanged.connect(self.update_state_and_val) self.textChanged.connect(self.update_state) autogen_button.clicked.connect(self.auto_generate) + copy_a_to_as_action.triggered.connect(self.auto_generate) + copy_as_to_a_action.triggered.connect(self.copy_to_authors) self.update_state() @dynamic_property @@ -254,12 +316,19 @@ class AuthorSortEdit(EnLineEdit): return property(fget=fget, fset=fset) + def update_state_and_val(self): + # Handle case change if the authors box changed + aus = authors_to_sort_string(self.authors_edit.current_val) + if strcmp(aus, self.current_val) == 0: + self.current_val = aus + self.update_state() + def update_state(self, *args): au = unicode(self.authors_edit.text()) au = re.sub(r'\s+et al\.$', '', au) au = self.db.author_sort_from_authors(string_to_authors(au)) - normal = au == self.current_val + normal = strcmp(au, self.current_val) == 0 if normal: col = 'rgb(0, 255, 0, 20%)' else: @@ -270,6 +339,21 @@ class AuthorSortEdit(EnLineEdit): self.setToolTip(tt) self.setWhatsThis(tt) + def copy_to_authors(self): + aus = self.current_val + meth = tweaks['author_sort_copy_method'] + if aus: + ans = [] + for one in [a.strip() for a in aus.split('&')]: + if not one: + continue + ln, _, rest = one.partition(',') + if rest: + if meth in ('invert', 'nocomma', 'comma'): + one = rest.strip() + ' ' + ln.strip() + ans.append(one) + self.authors_edit.current_val = ans + def auto_generate(self, *args): au = unicode(self.authors_edit.text()) au = re.sub(r'\s+et al\.$', '', au) @@ -313,7 +397,7 @@ class SeriesEdit(MultiCompleteComboBox): if not val: val = '' self.setEditText(val.strip()) - self.setCursorPosition(0) + self.lineEdit().setCursorPosition(0) return property(fget=fget, fset=fset) @@ -324,6 +408,7 @@ class SeriesEdit(MultiCompleteComboBox): self.update_items_cache([x[1] for x in all_series]) series_id = db.series_id(id_, index_is_id=True) idx, c = None, 0 + self.clear() for i in all_series: id, name = i if id == series_id: @@ -351,7 +436,7 @@ class SeriesIndexEdit(QDoubleSpinBox): QDoubleSpinBox.__init__(self, parent) self.dialog = parent self.db = self.original_series_name = None - self.setMaximum(1000000) + self.setMaximum(10000000) self.series_edit = series_edit series_edit.currentIndexChanged.connect(self.enable) series_edit.editTextChanged.connect(self.enable) @@ -426,7 +511,7 @@ class Format(QListWidgetItem): # {{{ if timestamp is not None: ts = timestamp.astimezone(local_tz) t = strftime('%a, %d %b %Y [%H:%M:%S]', ts.timetuple()) - text = _('Last modified: %s')%t + text = _('Last modified: %s\n\nDouble click to view')%t self.setToolTip(text) self.setStatusTip(text) @@ -450,16 +535,22 @@ class FormatsManager(QWidget): # {{{ self.metadata_from_format_button = QToolButton(self) self.metadata_from_format_button.setIcon(QIcon(I('edit_input.png'))) self.metadata_from_format_button.setIconSize(QSize(32, 32)) + self.metadata_from_format_button.setToolTip( + _('Set metadata for the book from the selected format')) self.add_format_button = QToolButton(self) self.add_format_button.setIcon(QIcon(I('add_book.png'))) self.add_format_button.setIconSize(QSize(32, 32)) self.add_format_button.clicked.connect(self.add_format) + self.add_format_button.setToolTip( + _('Add a format to this book')) self.remove_format_button = QToolButton(self) self.remove_format_button.setIcon(QIcon(I('trash.png'))) self.remove_format_button.setIconSize(QSize(32, 32)) self.remove_format_button.clicked.connect(self.remove_format) + self.remove_format_button.setToolTip( + _('Remove the selected format from this book')) self.formats = FormatList(self) self.formats.setAcceptDrops(True) @@ -480,6 +571,7 @@ class FormatsManager(QWidget): # {{{ def initialize(self, db, id_): self.changed = False + self.formats.clear() exts = db.formats(id_, index_is_id=True) self.original_val = set([]) if exts: @@ -574,8 +666,7 @@ class FormatsManager(QWidget): # {{{ self.changed = True def show_format(self, item, *args): - fmt = item.ext - self.dialog.view_format.emit(fmt) + self.dialog.do_view_format(item.path, item.ext) def get_selected_format_metadata(self, db, id_): old = prefs['read_file_metadata'] @@ -611,6 +702,8 @@ class FormatsManager(QWidget): # {{{ class Cover(ImageView): # {{{ + download_cover = pyqtSignal() + def __init__(self, parent): ImageView.__init__(self, parent) self.dialog = parent @@ -638,6 +731,18 @@ class Cover(ImageView): # {{{ self.trim_cover_button, self.download_cover_button, self.generate_cover_button] + self.frame_size = (300, 400) + self.setSizePolicy(QSizePolicy(QSizePolicy.Preferred, + QSizePolicy.Preferred)) + + def frame_resized(self, ev): + sz = ev.size() + self.frame_size = (sz.width()//3, sz.height()) + + def sizeHint(self): + sz = QSize(self.frame_size[0], self.frame_size[1]) + return sz + def select_cover(self, *args): files = choose_images(self, 'change cover dialog', _('Choose cover for ') + @@ -656,7 +761,7 @@ class Cover(ImageView): # {{{ try: cf = open(_file, "rb") cover = cf.read() - except IOError, e: + except IOError as e: d = error_dialog(self, _('Error reading file'), _("<p>There was an error reading from file: <br /><b>") + _file + "</b></p><br />"+str(e)) @@ -684,9 +789,6 @@ class Cover(ImageView): # {{{ cdata = im.export('png') self.current_val = cdata - def download_cover(self, *args): - pass # TODO: Implement this - def generate_cover(self, *args): from calibre.ebooks import calibre_cover from calibre.ebooks.metadata import fmt_sidx @@ -771,6 +873,7 @@ class CommentsEdit(Editor): # {{{ else: val = comments_to_html(val) self.html = val + self.wyswyg_dirtied() return property(fget=fget, fset=fset) def initialize(self, db, id_): @@ -826,7 +929,7 @@ class RatingEdit(QSpinBox): # {{{ class TagsEdit(MultiCompleteLineEdit): # {{{ LABEL = _('Ta&gs:') TOOLTIP = '<p>'+_('Tags categorize the book. This is particularly ' - 'useful while searching. <br><br>They can be any words' + 'useful while searching. <br><br>They can be any words ' 'or phrases, separated by commas.') def __init__(self, parent): @@ -843,6 +946,7 @@ class TagsEdit(MultiCompleteLineEdit): # {{{ if not val: val = [] self.setText(', '.join([x.strip() for x in val])) + self.setCursorPosition(0) return property(fget=fget, fset=fset) def initialize(self, db, id_): @@ -859,10 +963,13 @@ class TagsEdit(MultiCompleteLineEdit): # {{{ def edit(self, db, id_): if self.changed: - if question_dialog(self, _('Tags changed'), + d = save_dialog(self, _('Tags changed'), _('You have changed the tags. In order to use the tags' ' editor, you must either discard or apply these ' - 'changes. Apply changes?'), show_copy_button=False): + 'changes. Apply changes?')) + if d == QMessageBox.Cancel: + return + if d == QMessageBox.Yes: self.commit(db, id_) db.commit() self.original_val = self.current_val @@ -882,8 +989,11 @@ class TagsEdit(MultiCompleteLineEdit): # {{{ # }}} -class ISBNEdit(QLineEdit): # {{{ - LABEL = _('IS&BN:') +class IdentifiersEdit(QLineEdit): # {{{ + LABEL = _('I&ds:') + BASE_TT = _('Edit the identifiers for this book. ' + 'For example: \n\n%s')%( + 'isbn:1565927249, doi:10.1000/182, amazon:1565927249') def __init__(self, parent): QLineEdit.__init__(self, parent) @@ -893,34 +1003,69 @@ class ISBNEdit(QLineEdit): # {{{ @dynamic_property def current_val(self): def fget(self): - return self.pat.sub('', unicode(self.text()).strip()) + raw = unicode(self.text()).strip() + parts = [x.strip() for x in raw.split(',')] + ans = {} + for x in parts: + c = x.split(':') + if len(c) > 1: + if c[0] == 'isbn': + v = check_isbn(c[1]) + if v is not None: + c[1] = v + ans[c[0]] = c[1] + return ans def fset(self, val): if not val: - val = '' - self.setText(val.strip()) + val = {} + def keygen(x): + x = x[0] + if x == 'isbn': + x = '00isbn' + return x + for k in list(val): + if k == 'isbn': + v = check_isbn(k) + if v is not None: + val[k] = v + ids = sorted(val.iteritems(), key=keygen) + txt = ', '.join(['%s:%s'%(k, v) for k, v in ids]) + self.setText(txt.strip()) + self.setCursorPosition(0) return property(fget=fget, fset=fset) def initialize(self, db, id_): - self.current_val = db.isbn(id_, index_is_id=True) - self.original_val = self.current_val + self.original_val = db.get_identifiers(id_, index_is_id=True) + self.current_val = self.original_val def commit(self, db, id_): - db.set_isbn(id_, self.current_val, notify=False, commit=False) + if self.original_val != self.current_val: + db.set_identifiers(id_, self.current_val, notify=False, commit=False) return True def validate(self, *args): - isbn = self.current_val - tt = _('This ISBN number is valid') + identifiers = self.current_val + isbn = identifiers.get('isbn', '') + tt = self.BASE_TT + extra = '' if not isbn: - col = 'rgba(0,255,0,0%)' + col = 'none' elif check_isbn(isbn) is not None: col = 'rgba(0,255,0,20%)' + extra = '\n\n'+_('This ISBN number is valid') else: col = 'rgba(255,0,0,20%)' - tt = _('This ISBN number is invalid') - self.setToolTip(tt) + extra = '\n\n' + _('This ISBN number is invalid') + self.setToolTip(tt+extra) self.setStyleSheet('QLineEdit { background-color: %s }'%col) + def paste_isbn(self): + text = unicode(QApplication.clipboard().text()).strip() + if text: + vals = self.current_val + vals['isbn'] = text + self.current_val = vals + # }}} class PublisherEdit(MultiCompleteComboBox): # {{{ @@ -943,7 +1088,7 @@ class PublisherEdit(MultiCompleteComboBox): # {{{ if not val: val = '' self.setEditText(val.strip()) - self.setCursorPosition(0) + self.lineEdit().setCursorPosition(0) return property(fget=fget, fset=fset) @@ -953,13 +1098,13 @@ class PublisherEdit(MultiCompleteComboBox): # {{{ all_publishers.sort(key=lambda x : sort_key(x[1])) self.update_items_cache([x[1] for x in all_publishers]) publisher_id = db.publisher_id(id_, index_is_id=True) - idx, c = None, 0 - for i in all_publishers: - id, name = i - if id == publisher_id: - idx = c + idx = None + self.clear() + for i, x in enumerate(all_publishers): + id_, name = x + if id_ == publisher_id: + idx = i self.addItem(name) - c += 1 self.setEditText('') if idx is not None: @@ -1003,7 +1148,7 @@ class DateEdit(QDateEdit): # {{{ @dynamic_property def current_val(self): def fget(self): - return qt_to_dt(self.date()) + return qt_to_dt(self.date(), as_utc=False) def fset(self, val): if val is None: val = UNDEFINED_DATE diff --git a/src/calibre/gui2/metadata/bulk_download.py b/src/calibre/gui2/metadata/bulk_download.py index 461f56b60c..2a307fc902 100644 --- a/src/calibre/gui2/metadata/bulk_download.py +++ b/src/calibre/gui2/metadata/bulk_download.py @@ -1,308 +1,195 @@ #!/usr/bin/env python # vim:fileencoding=UTF-8:ts=4:sw=4:sta:et:sts=4:ai -from __future__ import with_statement +from __future__ import (unicode_literals, division, absolute_import, + print_function) __license__ = 'GPL v3' -__copyright__ = '2009, Kovid Goyal <kovid@kovidgoyal.net>' +__copyright__ = '2011, Kovid Goyal <kovid@kovidgoyal.net>' __docformat__ = 'restructuredtext en' -import traceback -from threading import Thread -from Queue import Queue, Empty from functools import partial +from itertools import izip +from threading import Event -from PyQt4.Qt import QObject, QTimer, QDialog, \ - QVBoxLayout, QTextBrowser, QLabel, QGroupBox, QDialogButtonBox +from PyQt4.Qt import (QIcon, QDialog, + QDialogButtonBox, QLabel, QGridLayout, QPixmap, Qt) -from calibre.ebooks.metadata.fetch import search, get_social_metadata -from calibre.gui2 import config, error_dialog -from calibre.gui2.dialogs.progress import ProgressDialog -from calibre.ebooks.metadata.covers import download_cover -from calibre.customize.ui import get_isbndb_key +from calibre.gui2.threaded_jobs import ThreadedJob +from calibre.ebooks.metadata.sources.identify import identify, msprefs +from calibre.ebooks.metadata.sources.covers import download_cover +from calibre.ebooks.metadata.book.base import Metadata +from calibre.customize.ui import metadata_plugins +from calibre.ptempfile import PersistentTemporaryFile -class Worker(Thread): - 'Cover downloader' +# Start download {{{ +def show_config(gui, parent): + from calibre.gui2.preferences import show_config_widget + show_config_widget('Sharing', 'Metadata download', parent=parent, + gui=gui, never_shutdown=True) - def __init__(self): - Thread.__init__(self) - self.daemon = True - self.jobs = Queue() - self.results = Queue() +class ConfirmDialog(QDialog): - def run(self): - while True: - id, mi = self.jobs.get() - if not getattr(mi, 'isbn', False): - break + def __init__(self, ids, parent): + QDialog.__init__(self, parent) + self.setWindowTitle(_('Schedule download?')) + self.setWindowIcon(QIcon(I('dialog_question.png'))) + + l = self.l = QGridLayout() + self.setLayout(l) + + i = QLabel(self) + i.setPixmap(QPixmap(I('dialog_question.png'))) + l.addWidget(i, 0, 0) + + t = QLabel( + '<p>'+_('The download of metadata for the <b>%d selected book(s)</b> will' + ' run in the background. Proceed?')%len(ids) + + '<p>'+_('You can monitor the progress of the download ' + 'by clicking the rotating spinner in the bottom right ' + 'corner.') + + '<p>'+_('When the download completes you will be asked for' + ' confirmation before calibre applies the downloaded metadata.') + ) + t.setWordWrap(True) + l.addWidget(t, 0, 1) + l.setColumnStretch(0, 1) + l.setColumnStretch(1, 100) + + self.identify = self.covers = True + self.bb = QDialogButtonBox(QDialogButtonBox.Cancel) + self.bb.rejected.connect(self.reject) + b = self.bb.addButton(_('Download only &metadata'), + self.bb.AcceptRole) + b.clicked.connect(self.only_metadata) + b.setIcon(QIcon(I('edit_input.png'))) + b = self.bb.addButton(_('Download only &covers'), + self.bb.AcceptRole) + b.clicked.connect(self.only_covers) + b.setIcon(QIcon(I('default_cover.png'))) + b = self.b = self.bb.addButton(_('&Configure download'), self.bb.ActionRole) + b.setIcon(QIcon(I('config.png'))) + b.clicked.connect(partial(show_config, parent, self)) + l.addWidget(self.bb, 1, 0, 1, 2) + b = self.bb.addButton(_('Download &both'), + self.bb.AcceptRole) + b.clicked.connect(self.accept) + b.setDefault(True) + b.setAutoDefault(True) + b.setIcon(QIcon(I('ok.png'))) + + self.resize(self.sizeHint()) + b.setFocus(Qt.OtherFocusReason) + + def only_metadata(self): + self.covers = False + self.accept() + + def only_covers(self): + self.identify = False + self.accept() + +def start_download(gui, ids, callback): + d = ConfirmDialog(ids, gui) + ret = d.exec_() + d.b.clicked.disconnect() + if ret != d.Accepted: + return + + job = ThreadedJob('metadata bulk download', + _('Download metadata for %d books')%len(ids), + download, (ids, gui.current_db, d.identify, d.covers), {}, callback) + gui.job_manager.run_threaded_job(job) + gui.status_bar.show_message(_('Metadata download started'), 3000) +# }}} + +def get_job_details(job): + id_map, failed_ids, failed_covers, title_map, all_failed = job.result + det_msg = [] + for i in failed_ids | failed_covers: + title = title_map[i] + if i in failed_ids: + title += (' ' + _('(Failed metadata)')) + if i in failed_covers: + title += (' ' + _('(Failed cover)')) + det_msg.append(title) + det_msg = '\n'.join(det_msg) + return id_map, failed_ids, failed_covers, all_failed, det_msg + +def merge_result(oldmi, newmi): + dummy = Metadata(_('Unknown')) + for f in msprefs['ignore_fields']: + if ':' not in f: + setattr(newmi, f, getattr(dummy, f)) + fields = set() + for plugin in metadata_plugins(['identify']): + fields |= plugin.touched_fields + + for f in fields: + # Optimize so that set_metadata does not have to do extra work later + if not f.startswith('identifier:'): + if (not newmi.is_null(f) and getattr(newmi, f) == getattr(oldmi, f)): + setattr(newmi, f, getattr(dummy, f)) + + newmi.last_modified = oldmi.last_modified + + return newmi + +def download(ids, db, do_identify, covers, + log=None, abort=None, notifications=None): + ids = list(ids) + metadata = [db.get_metadata(i, index_is_id=True, get_user_categories=False) + for i in ids] + failed_ids = set() + failed_covers = set() + title_map = {} + ans = {} + count = 0 + all_failed = True + ''' + # Test apply dialog + all_failed = do_identify = covers = False + ''' + for i, mi in izip(ids, metadata): + if abort.is_set(): + log.error('Aborting...') + break + title, authors, identifiers = mi.title, mi.authors, mi.identifiers + title_map[i] = title + if do_identify: + results = [] try: - cdata, errors = download_cover(mi) - if cdata: - self.results.put((id, mi, True, cdata)) - else: - msg = [] - for e in errors: - if not e[0]: - msg.append(e[-1] + ' - ' + e[1]) - self.results.put((id, mi, False, '\n'.join(msg))) + results = identify(log, Event(), title=title, authors=authors, + identifiers=identifiers) except: - self.results.put((id, mi, False, traceback.format_exc())) - - def __enter__(self): - self.start() - return self - - def __exit__(self, *args): - self.jobs.put((False, False)) - - -class DownloadMetadata(Thread): - 'Metadata downloader' - - def __init__(self, db, ids, get_covers, set_metadata=True, - get_social_metadata=True): - Thread.__init__(self) - self.daemon = True - self.metadata = {} - self.covers = {} - self.set_metadata = set_metadata - self.get_social_metadata = get_social_metadata - self.social_metadata_exceptions = [] - self.db = db - self.updated = set([]) - self.get_covers = get_covers - self.worker = Worker() - self.results = Queue() - self.keep_going = True - for id in ids: - self.metadata[id] = db.get_metadata(id, index_is_id=True) - self.metadata[id].rating = None - self.total = len(ids) - if self.get_covers: - self.total += len(ids) - self.fetched_metadata = {} - self.fetched_covers = {} - self.failures = {} - self.cover_failures = {} - self.exception = self.tb = None - - def run(self): - try: - self._run() - except Exception, e: - self.exception = e - self.tb = traceback.format_exc() - - def _run(self): - self.key = get_isbndb_key() - if not self.key: - self.key = None - with self.worker: - for id, mi in self.metadata.items(): - if not self.keep_going: - break - args = {} - if mi.isbn: - args['isbn'] = mi.isbn - else: - if mi.is_null('title'): - self.failures[id] = \ - _('Book has neither title nor ISBN') - continue - args['title'] = mi.title - if mi.authors and mi.authors[0] != _('Unknown'): - args['author'] = mi.authors[0] - if self.key: - args['isbndb_key'] = self.key - results, exceptions = search(**args) - if results: - fmi = results[0] - self.fetched_metadata[id] = fmi - if self.get_covers: - if fmi.isbn: - self.worker.jobs.put((id, fmi)) - else: - self.results.put((id, 'cover', False, mi.title)) - if (not config['overwrite_author_title_metadata']): - fmi.authors = mi.authors - fmi.author_sort = mi.author_sort - fmi.title = mi.title - mi.smart_update(fmi) - if mi.isbn and self.get_social_metadata: - self.social_metadata_exceptions = get_social_metadata(mi) - if mi.rating: - mi.rating *= 2 - if not self.get_social_metadata: - mi.tags = [] - self.results.put((id, 'metadata', True, mi.title)) - else: - self.failures[id] = _('No matches found for this book') - self.results.put((id, 'metadata', False, mi.title)) - self.results.put((id, 'cover', False, mi.title)) - self.commit_covers() - - self.commit_covers(True) - - def commit_covers(self, all=False): - if all: - self.worker.jobs.put((False, False)) - while True: - try: - id, fmi, ok, cdata = self.worker.results.get_nowait() - if ok: - self.fetched_covers[id] = cdata - self.results.put((id, 'cover', ok, fmi.title)) - else: - self.results.put((id, 'cover', ok, fmi.title)) - try: - self.cover_failures[id] = unicode(cdata) - except: - self.cover_failures[id] = repr(cdata) - except Empty: - if not all or not self.worker.is_alive(): - return - -class DoDownload(QObject): - - def __init__(self, parent, title, db, ids, get_covers, set_metadata=True, - get_social_metadata=True): - QObject.__init__(self, parent) - self.pd = ProgressDialog(title, min=0, max=0, parent=parent) - self.pd.canceled_signal.connect(self.cancel) - self.downloader = None - self.create = partial(DownloadMetadata, db, ids, get_covers, - set_metadata=set_metadata, - get_social_metadata=get_social_metadata) - self.get_covers = get_covers - self.db = db - self.updated = set([]) - self.total = len(ids) - self.keep_going = True - - def exec_(self): - QTimer.singleShot(50, self.do_one) - ret = self.pd.exec_() - if getattr(self.downloader, 'exception', None) is not None and \ - ret == self.pd.Accepted: - error_dialog(self.parent(), _('Failed'), - _('Failed to download metadata'), show=True) - else: - self.show_report() - return ret - - def cancel(self, *args): - self.keep_going = False - self.downloader.keep_going = False - self.pd.reject() - - def do_one(self): - try: - if not self.keep_going: - return - if self.downloader is None: - self.downloader = self.create() - self.downloader.start() - self.pd.set_min(0) - self.pd.set_max(self.downloader.total) - try: - r = self.downloader.results.get_nowait() - self.handle_result(r) - except Empty: pass - if not self.downloader.is_alive(): - while True: - try: - r = self.downloader.results.get_nowait() - self.handle_result(r) - except Empty: - break - self.pd.accept() - return - except: - self.cancel() - raise - QTimer.singleShot(50, self.do_one) - - def handle_result(self, r): - id_, typ, ok, title = r - what = _('cover') if typ == 'cover' else _('metadata') - which = _('Downloaded') if ok else _('Failed to get') - if self.get_covers or typ != 'cover' or ok: - # Do not show message when cover fetch fails if user didn't ask to - # download covers - self.pd.set_msg(_('%s %s for: %s') % (which, what, title)) - self.pd.value += 1 - if ok: - self.updated.add(id_) - if typ == 'cover': - try: - self.db.set_cover(id_, - self.downloader.fetched_covers.pop(id_)) - except: - self.downloader.cover_failures[id_] = \ - traceback.format_exc() + if results: + all_failed = False + mi = merge_result(mi, results[0]) + identifiers = mi.identifiers + if not mi.is_null('rating'): + # set_metadata expects a rating out of 10 + mi.rating *= 2 else: - try: - self.set_metadata(id_) - except: - self.downloader.failures[id_] = \ - traceback.format_exc() - - def set_metadata(self, id_): - mi = self.downloader.metadata[id_] - if self.downloader.set_metadata: - self.db.set_metadata(id_, mi) - if not self.downloader.set_metadata and self.downloader.get_social_metadata: - if mi.rating: - self.db.set_rating(id_, mi.rating) - if mi.tags: - self.db.set_tags(id_, mi.tags) - if mi.comments: - self.db.set_comment(id_, mi.comments) - if mi.series: - self.db.set_series(id_, mi.series) - if mi.series_index is not None: - self.db.set_series_index(id_, mi.series_index) - - def show_report(self): - f, cf = self.downloader.failures, self.downloader.cover_failures - report = [] - if f: - report.append( - '<h3>Failed to download metadata for the following:</h3><ol>') - for id_, err in f.items(): - mi = self.downloader.metadata[id_] - report.append('<li><b>%s</b><pre>%s</pre></li>' % (mi.title, - unicode(err))) - report.append('</ol>') - if cf: - report.append( - '<h3>Failed to download cover for the following:</h3><ol>') - for id_, err in cf.items(): - mi = self.downloader.metadata[id_] - report.append('<li><b>%s</b><pre>%s</pre></li>' % (mi.title, - unicode(err))) - report.append('</ol>') - - if len(self.updated) != self.total or report: - d = QDialog(self.parent()) - bb = QDialogButtonBox(QDialogButtonBox.Ok, parent=d) - v1 = QVBoxLayout() - d.setLayout(v1) - d.setWindowTitle(_('Done')) - v1.addWidget(QLabel(_('Successfully downloaded metadata for %d out of %d books') % - (len(self.updated), self.total))) - gb = QGroupBox(_('Details'), self.parent()) - v2 = QVBoxLayout() - gb.setLayout(v2) - b = QTextBrowser(self.parent()) - v2.addWidget(b) - b.setHtml('\n'.join(report)) - v1.addWidget(gb) - v1.addWidget(bb) - bb.accepted.connect(d.accept) - d.resize(800, 600) - d.exec_() - + log.error('Failed to download metadata for', title) + failed_ids.add(i) + # We don't want set_metadata operating on anything but covers + mi = merge_result(mi, mi) + if covers: + cdata = download_cover(log, title=title, authors=authors, + identifiers=identifiers) + if cdata is not None: + with PersistentTemporaryFile('.jpg', 'downloaded-cover-') as f: + f.write(cdata[-1]) + mi.cover = f.name + all_failed = False + else: + failed_covers.add(i) + ans[i] = mi + count += 1 + notifications.put((count/len(ids), + _('Downloaded %d of %d')%(count, len(ids)))) + log('Download complete, with %d failures'%len(failed_ids)) + return (ans, failed_ids, failed_covers, title_map, all_failed) diff --git a/src/calibre/gui2/metadata/config.py b/src/calibre/gui2/metadata/config.py new file mode 100644 index 0000000000..6138f27090 --- /dev/null +++ b/src/calibre/gui2/metadata/config.py @@ -0,0 +1,131 @@ +#!/usr/bin/env python +# vim:fileencoding=UTF-8:ts=4:sw=4:sta:et:sts=4:ai +from __future__ import (unicode_literals, division, absolute_import, + print_function) + +__license__ = 'GPL v3' +__copyright__ = '2011, Kovid Goyal <kovid@kovidgoyal.net>' +__docformat__ = 'restructuredtext en' + +import textwrap + +from PyQt4.Qt import (QWidget, QGridLayout, QGroupBox, QListView, Qt, QSpinBox, + QDoubleSpinBox, QCheckBox, QLineEdit, QComboBox, QLabel, QVariant) + +from calibre.gui2.preferences.metadata_sources import FieldsModel as FM + +class FieldsModel(FM): # {{{ + + def __init__(self, plugin): + FM.__init__(self) + self.plugin = plugin + self.exclude = frozenset(['title', 'authors']) | self.exclude + self.prefs = self.plugin.prefs + + def initialize(self): + fields = self.plugin.touched_fields + self.fields = [] + for x in fields: + if not x.startswith('identifier:') and x not in self.exclude: + self.fields.append(x) + self.fields.sort(key=lambda x:self.descs.get(x, x)) + self.reset() + + def state(self, field, defaults=False): + src = self.prefs.defaults if defaults else self.prefs + return (Qt.Unchecked if field in src['ignore_fields'] + else Qt.Checked) + + def restore_defaults(self): + self.overrides = dict([(f, self.state(f, True)) for f in self.fields]) + self.reset() + + def commit(self): + ignored_fields = set([x for x in self.prefs['ignore_fields'] if x not in + self.overrides]) + changed = set([k for k, v in self.overrides.iteritems() if v == + Qt.Unchecked]) + self.prefs['ignore_fields'] = list(ignored_fields.union(changed)) + +# }}} + +class ConfigWidget(QWidget): + + def __init__(self, plugin): + QWidget.__init__(self) + self.plugin = plugin + + self.l = l = QGridLayout() + self.setLayout(l) + + self.gb = QGroupBox(_('Downloaded metadata fields'), self) + if plugin.config_help_message: + self.pchm = QLabel(plugin.config_help_message) + self.pchm.setWordWrap(True) + self.pchm.setOpenExternalLinks(True) + l.addWidget(self.pchm, 0, 0, 1, 2) + l.addWidget(self.gb, l.rowCount(), 0, 1, 2) + self.gb.l = QGridLayout() + self.gb.setLayout(self.gb.l) + self.fields_view = v = QListView(self) + self.gb.l.addWidget(v, 0, 0) + v.setFlow(v.LeftToRight) + v.setWrapping(True) + v.setResizeMode(v.Adjust) + self.fields_model = FieldsModel(self.plugin) + self.fields_model.initialize() + v.setModel(self.fields_model) + + self.memory = [] + self.widgets = [] + for opt in plugin.options: + self.create_widgets(opt) + + def create_widgets(self, opt): + val = self.plugin.prefs[opt.name] + if opt.type == 'number': + c = QSpinBox if isinstance(opt.default, int) else QDoubleSpinBox + widget = c(self) + widget.setValue(val) + elif opt.type == 'string': + widget = QLineEdit(self) + widget.setText(val if val else '') + elif opt.type == 'bool': + widget = QCheckBox(opt.label, self) + widget.setChecked(bool(val)) + elif opt.type == 'choices': + widget = QComboBox(self) + for key, label in opt.choices.iteritems(): + widget.addItem(label, QVariant(key)) + idx = widget.findData(QVariant(val)) + widget.setCurrentIndex(idx) + widget.opt = opt + widget.setToolTip(textwrap.fill(opt.desc)) + self.widgets.append(widget) + r = self.l.rowCount() + if opt.type == 'bool': + self.l.addWidget(widget, r, 0, 1, self.l.columnCount()) + else: + l = QLabel(opt.label) + l.setToolTip(widget.toolTip()) + self.memory.append(l) + l.setBuddy(widget) + self.l.addWidget(l, r, 0, 1, 1) + self.l.addWidget(widget, r, 1, 1, 1) + + + def commit(self): + self.fields_model.commit() + for w in self.widgets: + if isinstance(w, (QSpinBox, QDoubleSpinBox)): + val = w.value() + elif isinstance(w, QLineEdit): + val = unicode(w.text()) + elif isinstance(w, QCheckBox): + val = w.isChecked() + elif isinstance(w, QComboBox): + idx = w.currentIndex() + val = unicode(w.itemData(idx).toString()) + self.plugin.prefs[w.opt.name] = val + + diff --git a/src/calibre/gui2/metadata/single.py b/src/calibre/gui2/metadata/single.py index 0fa5c746e7..099831ccba 100644 --- a/src/calibre/gui2/metadata/single.py +++ b/src/calibre/gui2/metadata/single.py @@ -1,5 +1,7 @@ #!/usr/bin/env python # vim:fileencoding=UTF-8:ts=4:sw=4:sta:et:sts=4:ai +from __future__ import (unicode_literals, division, absolute_import, + print_function) __license__ = 'GPL v3' __copyright__ = '2011, Kovid Goyal <kovid@kovidgoyal.net>' @@ -8,31 +10,34 @@ __docformat__ = 'restructuredtext en' import os from functools import partial -from PyQt4.Qt import Qt, QVBoxLayout, QHBoxLayout, QWidget, QPushButton, \ - QGridLayout, pyqtSignal, QDialogButtonBox, QScrollArea, QFont, \ - QTabWidget, QIcon, QToolButton, QSplitter, QGroupBox, QSpacerItem, \ - QSizePolicy, QPalette, QFrame, QSize +from PyQt4.Qt import (Qt, QVBoxLayout, QHBoxLayout, QWidget, QPushButton, + QGridLayout, pyqtSignal, QDialogButtonBox, QScrollArea, QFont, + QTabWidget, QIcon, QToolButton, QSplitter, QGroupBox, QSpacerItem, + QSizePolicy, QPalette, QFrame, QSize, QKeySequence, QMenu) from calibre.ebooks.metadata import authors_to_string, string_to_authors -from calibre.gui2 import ResizableDialog, error_dialog, gprefs -from calibre.gui2.metadata.basic_widgets import TitleEdit, AuthorsEdit, \ - AuthorSortEdit, TitleSortEdit, SeriesEdit, SeriesIndexEdit, ISBNEdit, \ - RatingEdit, PublisherEdit, TagsEdit, FormatsManager, Cover, CommentsEdit, \ - BuddyLabel, DateEdit, PubdateEdit +from calibre.gui2 import ResizableDialog, error_dialog, gprefs, pixmap_to_data +from calibre.gui2.metadata.basic_widgets import (TitleEdit, AuthorsEdit, + AuthorSortEdit, TitleSortEdit, SeriesEdit, SeriesIndexEdit, IdentifiersEdit, + RatingEdit, PublisherEdit, TagsEdit, FormatsManager, Cover, CommentsEdit, + BuddyLabel, DateEdit, PubdateEdit) +from calibre.gui2.metadata.single_download import FullFetch from calibre.gui2.custom_column_widgets import populate_metadata_page from calibre.utils.config import tweaks +from calibre.ebooks.metadata.book.base import Metadata class MetadataSingleDialogBase(ResizableDialog): - view_format = pyqtSignal(object) + view_format = pyqtSignal(object, object) cc_two_column = tweaks['metadata_single_use_2_cols_for_custom_fields'] one_line_comments_toolbar = False + use_toolbutton_for_config_metadata = True def __init__(self, db, parent=None): self.db = db - self.changed = set([]) - self.books_to_refresh = set([]) - self.rows_to_refresh = set([]) + self.changed = set() + self.books_to_refresh = set() + self.rows_to_refresh = set() ResizableDialog.__init__(self, parent) def setupUi(self, *args): # {{{ @@ -45,9 +50,12 @@ class MetadataSingleDialogBase(ResizableDialog): self.button_box.rejected.connect(self.reject) self.next_button = QPushButton(QIcon(I('forward.png')), _('Next'), self) + self.next_button.setShortcut(QKeySequence('Alt+Right')) self.next_button.clicked.connect(partial(self.do_one, delta=1)) self.prev_button = QPushButton(QIcon(I('back.png')), _('Previous'), self) + self.prev_button.setShortcut(QKeySequence('Alt+Left')) + self.button_box.addButton(self.prev_button, self.button_box.ActionRole) self.button_box.addButton(self.next_button, self.button_box.ActionRole) self.prev_button.clicked.connect(partial(self.do_one, delta=-1)) @@ -62,7 +70,11 @@ class MetadataSingleDialogBase(ResizableDialog): self.setLayout(self.l) self.l.setMargin(0) self.l.addWidget(self.scroll_area) - self.l.addWidget(self.button_box) + ll = self.button_box_layout = QHBoxLayout() + self.l.addLayout(ll) + ll.addSpacing(10) + ll.addWidget(self.button_box) + ll.addSpacing(10) self.setWindowIcon(QIcon(I('edit_input.png'))) self.setWindowTitle(_('Edit Metadata')) @@ -95,15 +107,21 @@ class MetadataSingleDialogBase(ResizableDialog): self.deduce_title_sort_button) self.basic_metadata_widgets.extend([self.title, self.title_sort]) - self.authors = AuthorsEdit(self) - self.deduce_author_sort_button = QToolButton(self) - self.deduce_author_sort_button.setToolTip(_( - 'Automatically create the author sort entry based on the current' - ' author entry.\n' - 'Using this button to create author sort will change author sort from' - ' red to green.')) - self.author_sort = AuthorSortEdit(self, self.authors, - self.deduce_author_sort_button, self.db) + self.deduce_author_sort_button = b = QToolButton(self) + b.setToolTip('<p>' + + _('Automatically create the author sort entry based on the current ' + 'author entry. Using this button to create author sort will ' + 'change author sort from red to green. There is a menu of ' + 'functions available under this button. Click and hold ' + 'on the button to see it.') + '</p>') + b.m = m = QMenu() + ac = m.addAction(QIcon(I('forward.png')), _('Set author sort from author')) + ac2 = m.addAction(QIcon(I('back.png')), _('Set author from author sort')) + ac3 = m.addAction(QIcon(I('user_profile.png')), _('Manage authors')) + b.setMenu(m) + self.authors = AuthorsEdit(self, ac3) + self.author_sort = AuthorSortEdit(self, self.authors, b, self.db, ac, + ac2) self.basic_metadata_widgets.extend([self.authors, self.author_sort]) self.swap_title_author_button = QToolButton(self) @@ -112,6 +130,13 @@ class MetadataSingleDialogBase(ResizableDialog): 'Swap the author and title')) self.swap_title_author_button.clicked.connect(self.swap_title_author) + self.manage_authors_button = QToolButton(self) + self.manage_authors_button.setIcon(QIcon(I('user_profile.png'))) + self.manage_authors_button.setToolTip('<p>' + _( + 'Manage authors. Use to rename authors and correct ' + 'individual author\'s sort values') + '</p>') + self.manage_authors_button.clicked.connect(self.authors.manage_authors) + self.series = SeriesEdit(self) self.remove_unused_series_button = QToolButton(self) self.remove_unused_series_button.setToolTip( @@ -127,6 +152,7 @@ class MetadataSingleDialogBase(ResizableDialog): self.formats_manager.cover_from_format_button.clicked.connect( self.cover_from_format) self.cover = Cover(self) + self.cover.download_cover.connect(self.download_cover) self.basic_metadata_widgets.append(self.cover) self.comments = CommentsEdit(self, self.one_line_comments_toolbar) @@ -142,8 +168,17 @@ class MetadataSingleDialogBase(ResizableDialog): self.tags_editor_button.clicked.connect(self.tags_editor) self.basic_metadata_widgets.append(self.tags) - self.isbn = ISBNEdit(self) - self.basic_metadata_widgets.append(self.isbn) + self.identifiers = IdentifiersEdit(self) + self.basic_metadata_widgets.append(self.identifiers) + self.clear_identifiers_button = QToolButton(self) + self.clear_identifiers_button.setIcon(QIcon(I('trash.png'))) + self.clear_identifiers_button.clicked.connect(self.identifiers.clear) + self.paste_isbn_button = QToolButton(self) + self.paste_isbn_button.setToolTip('<p>' + + _('Paste the contents of the clipboard into the ' + 'identifiers box prefixed with isbn:') + '</p>') + self.paste_isbn_button.setIcon(QIcon(I('edit-paste.png'))) + self.paste_isbn_button.clicked.connect(self.identifiers.paste_isbn) self.publisher = PublisherEdit(self) self.basic_metadata_widgets.append(self.publisher) @@ -153,12 +188,22 @@ class MetadataSingleDialogBase(ResizableDialog): self.basic_metadata_widgets.extend([self.timestamp, self.pubdate]) self.fetch_metadata_button = QPushButton( - _('&Fetch metadata from server'), self) + _('&Download metadata'), self) self.fetch_metadata_button.clicked.connect(self.fetch_metadata) font = self.fmb_font = QFont() font.setBold(True) self.fetch_metadata_button.setFont(font) + if self.use_toolbutton_for_config_metadata: + self.config_metadata_button = QToolButton(self) + self.config_metadata_button.setIcon(QIcon(I('config.png'))) + else: + self.config_metadata_button = QPushButton(self) + self.config_metadata_button.setText(_('Configure download metadata')) + self.config_metadata_button.setIcon(QIcon(I('config.png'))) + self.config_metadata_button.clicked.connect(self.configure_metadata) + self.config_metadata_button.setToolTip( + _('Change how calibre downloads metadata')) # }}} @@ -178,7 +223,7 @@ class MetadataSingleDialogBase(ResizableDialog): ans = self.custom_metadata_widgets for i in range(len(ans)-1): if before is not None and i == 0: - pass# Do something + pass if len(ans[i+1].widgets) == 2: sto(ans[i].widgets[-1], ans[i+1].widgets[1]) else: @@ -186,9 +231,16 @@ class MetadataSingleDialogBase(ResizableDialog): for c in range(2, len(ans[i].widgets), 2): sto(ans[i].widgets[c-1], ans[i].widgets[c+1]) if after is not None: - pass # Do something + pass # }}} + def do_view_format(self, path, fmt): + if path: + self.view_format.emit(None, path) + else: + self.view_format.emit(self.book_id, fmt) + + def do_layout(self): raise NotImplementedError() @@ -199,6 +251,8 @@ class MetadataSingleDialogBase(ResizableDialog): widget.initialize(self.db, id_) for widget in getattr(self, 'custom_metadata_widgets', []): widget.initialize(id_) + if callable(self.set_current_callback): + self.set_current_callback(id_) # Commented out as it doesn't play nice with Next, Prev buttons #self.fetch_metadata_button.setFocus(Qt.OtherFocusReason) @@ -261,13 +315,17 @@ class MetadataSingleDialogBase(ResizableDialog): show=True) return - def update_from_mi(self, mi): + def update_from_mi(self, mi, update_sorts=True): if not mi.is_null('title'): self.title.current_val = mi.title + if update_sorts: + self.title_sort.auto_generate() if not mi.is_null('authors'): self.authors.current_val = mi.authors if not mi.is_null('author_sort'): self.author_sort.current_val = mi.author_sort + elif update_sorts: + self.author_sort.auto_generate() if not mi.is_null('rating'): try: self.rating.current_val = mi.rating @@ -277,8 +335,10 @@ class MetadataSingleDialogBase(ResizableDialog): self.publisher.current_val = mi.publisher if not mi.is_null('tags'): self.tags.current_val = mi.tags - if not mi.is_null('isbn'): - self.isbn.current_val = mi.isbn + if not mi.is_null('identifiers'): + current = self.identifiers.current_val + current.update(mi.identifiers) + self.identifiers.current_val = current if not mi.is_null('pubdate'): self.pubdate.current_val = mi.pubdate if not mi.is_null('series') and mi.series.strip(): @@ -289,7 +349,37 @@ class MetadataSingleDialogBase(ResizableDialog): self.comments.current_val = mi.comments def fetch_metadata(self, *args): - pass # TODO: fetch metadata + d = FullFetch(self.cover.pixmap(), self) + ret = d.start(title=self.title.current_val, authors=self.authors.current_val, + identifiers=self.identifiers.current_val) + if ret == d.Accepted: + from calibre.ebooks.metadata.sources.base import msprefs + mi = d.book + dummy = Metadata(_('Unknown')) + for f in msprefs['ignore_fields']: + if ':' not in f: + setattr(mi, f, getattr(dummy, f)) + if mi is not None: + self.update_from_mi(mi) + if d.cover_pixmap is not None: + self.cover.current_val = pixmap_to_data(d.cover_pixmap) + + def configure_metadata(self): + from calibre.gui2.preferences import show_config_widget + gui = self.parent() + show_config_widget('Sharing', 'Metadata download', parent=self, + gui=gui, never_shutdown=True) + + def download_cover(self, *args): + from calibre.gui2.metadata.single_download import CoverFetch + d = CoverFetch(self.cover.pixmap(), self) + ret = d.start(self.title.current_val, self.authors.current_val, + self.identifiers.current_val) + if ret == d.Accepted: + if d.cover_pixmap is not None: + self.cover.current_val = pixmap_to_data(d.cover_pixmap) + + # }}} def apply_changes(self): @@ -300,7 +390,7 @@ class MetadataSingleDialogBase(ResizableDialog): return False self.books_to_refresh |= getattr(widget, 'books_to_refresh', set([])) - except IOError, err: + except IOError as err: if err.errno == 13: # Permission denied import traceback fname = err.filename if err.filename else 'file' @@ -334,11 +424,13 @@ class MetadataSingleDialogBase(ResizableDialog): gprefs['metasingle_window_geometry3'] = bytearray(self.saveGeometry()) # Dialog use methods {{{ - def start(self, row_list, current_row, view_slot=None): + def start(self, row_list, current_row, view_slot=None, + set_current_callback=None): self.row_list = row_list self.current_row = current_row if view_slot is not None: self.view_format.connect(view_slot) + self.set_current_callback = set_current_callback self.do_one(apply_changes=False) ret = self.exec_() self.break_cycles() @@ -355,11 +447,13 @@ class MetadataSingleDialogBase(ResizableDialog): next_ = self.db.title(self.row_list[self.current_row+1]) if next_ is not None: - tip = _('Save changes and edit the metadata of %s')%next_ + tip = (_('Save changes and edit the metadata of %s')+ + ' [Alt+Right]')%next_ self.next_button.setToolTip(tip) self.next_button.setVisible(next_ is not None) if prev is not None: - tip = _('Save changes and edit the metadata of %s')%prev + tip = (_('Save changes and edit the metadata of %s')+ + ' [Alt+Left]')%prev self.prev_button.setToolTip(tip) self.prev_button.setVisible(prev is not None) self(self.db.id(self.row_list[self.current_row])) @@ -368,6 +462,7 @@ class MetadataSingleDialogBase(ResizableDialog): def break_cycles(self): # Break any reference cycles that could prevent python # from garbage collecting this dialog + self.set_current_callback = self.db = None def disconnect(signal): try: signal.disconnect() @@ -380,6 +475,14 @@ class MetadataSingleDialogBase(ResizableDialog): disconnect(x.clicked) # }}} +class Splitter(QSplitter): + + frame_resized = pyqtSignal(object) + + def resizeEvent(self, ev): + self.frame_resized.emit(ev) + return QSplitter.resizeEvent(self, ev) + class MetadataSingleDialog(MetadataSingleDialogBase): # {{{ def do_layout(self): @@ -403,7 +506,8 @@ class MetadataSingleDialog(MetadataSingleDialogBase): # {{{ sto = QWidget.setTabOrder sto(self.button_box, self.fetch_metadata_button) - sto(self.fetch_metadata_button, self.title) + sto(self.fetch_metadata_button, self.config_metadata_button) + sto(self.config_metadata_button, self.title) def create_row(row, one, two, three, col=1, icon='forward.png'): ql = BuddyLabel(one) @@ -420,7 +524,8 @@ class MetadataSingleDialog(MetadataSingleDialogBase): # {{{ sto(one, two) sto(two, three) - tl.addWidget(self.swap_title_author_button, 0, 0, 2, 1) + tl.addWidget(self.swap_title_author_button, 0, 0, 1, 1) + tl.addWidget(self.manage_authors_button, 1, 0, 1, 1) create_row(0, self.title, self.deduce_title_sort_button, self.title_sort) sto(self.title_sort, self.authors) @@ -429,16 +534,18 @@ class MetadataSingleDialog(MetadataSingleDialogBase): # {{{ create_row(2, self.series, self.remove_unused_series_button, self.series_index, icon='trash.png') sto(self.series_index, self.swap_title_author_button) + sto(self.swap_title_author_button, self.manage_authors_button) tl.addWidget(self.formats_manager, 0, 6, 3, 1) - self.splitter = QSplitter(Qt.Horizontal, self) + self.splitter = Splitter(Qt.Horizontal, self) self.splitter.addWidget(self.cover) + self.splitter.frame_resized.connect(self.cover.frame_resized) l.addWidget(self.splitter) self.tabs[0].gb = gb = QGroupBox(_('Change cover'), self) gb.l = l = QGridLayout() gb.setLayout(l) - sto(self.swap_title_author_button, self.cover.buttons[0]) + sto(self.manage_authors_button, self.cover.buttons[0]) for i, b in enumerate(self.cover.buttons[:3]): l.addWidget(b, 0, i, 1, 1) sto(b, self.cover.buttons[i+1]) @@ -452,10 +559,16 @@ class MetadataSingleDialog(MetadataSingleDialogBase): # {{{ w.setLayout(w.l) l.setMargin(0) self.splitter.addWidget(w) - def create_row2(row, widget, button=None): + def create_row2(row, widget, button=None, front_button=None): row += 1 ql = BuddyLabel(widget) - l.addWidget(ql, row, 0, 1, 1) + if front_button: + ltl = QHBoxLayout() + ltl.addWidget(front_button) + ltl.addWidget(ql) + l.addLayout(ltl, row, 0, 1, 1) + else: + l.addWidget(ql, row, 0, 1, 1) l.addWidget(widget, row, 1, 1, 2 if button is None else 1) if button is not None: l.addWidget(button, row, 2, 1, 1) @@ -470,9 +583,11 @@ class MetadataSingleDialog(MetadataSingleDialogBase): # {{{ create_row2(1, self.rating) sto(self.rating, self.tags) create_row2(2, self.tags, self.tags_editor_button) - sto(self.tags_editor_button, self.isbn) - create_row2(3, self.isbn) - sto(self.isbn, self.timestamp) + sto(self.tags_editor_button, self.paste_isbn_button) + sto(self.paste_isbn_button, self.identifiers) + create_row2(3, self.identifiers, self.clear_identifiers_button, + front_button=self.paste_isbn_button) + sto(self.clear_identifiers_button, self.timestamp) create_row2(4, self.timestamp, self.timestamp.clear_button) sto(self.timestamp.clear_button, self.pubdate) create_row2(5, self.pubdate, self.pubdate.clear_button) @@ -481,7 +596,8 @@ class MetadataSingleDialog(MetadataSingleDialogBase): # {{{ self.tabs[0].spc_two = QSpacerItem(10, 10, QSizePolicy.Expanding, QSizePolicy.Expanding) l.addItem(self.tabs[0].spc_two, 8, 0, 1, 3) - l.addWidget(self.fetch_metadata_button, 9, 0, 1, 3) + l.addWidget(self.fetch_metadata_button, 9, 0, 1, 2) + l.addWidget(self.config_metadata_button, 9, 2, 1, 1) self.tabs[0].gb2 = gb = QGroupBox(_('Co&mments'), self) gb.l = l = QVBoxLayout() @@ -493,10 +609,27 @@ class MetadataSingleDialog(MetadataSingleDialogBase): # {{{ # }}} -class MetadataSingleDialogAlt(MetadataSingleDialogBase): # {{{ +class DragTrackingWidget(QWidget): # {{{ + + def __init__(self, parent, on_drag_enter): + QWidget.__init__(self, parent) + self.on_drag_enter = on_drag_enter + + def dragEnterEvent(self, ev): + self.on_drag_enter.emit() + +# }}} + +class MetadataSingleDialogAlt1(MetadataSingleDialogBase): # {{{ cc_two_column = False one_line_comments_toolbar = True + use_toolbutton_for_config_metadata = False + + on_drag_enter = pyqtSignal() + + def handle_drag_enter(self): + self.central_widget.setCurrentIndex(1) def do_layout(self): self.central_widget.clear() @@ -504,7 +637,8 @@ class MetadataSingleDialogAlt(MetadataSingleDialogBase): # {{{ self.labels = [] sto = QWidget.setTabOrder - self.tabs.append(QWidget(self)) + self.on_drag_enter.connect(self.handle_drag_enter) + self.tabs.append(DragTrackingWidget(self, self.on_drag_enter)) self.central_widget.addTab(self.tabs[0], _("&Metadata")) self.tabs[0].l = QGridLayout() self.tabs[0].setLayout(self.tabs[0].l) @@ -514,6 +648,10 @@ class MetadataSingleDialogAlt(MetadataSingleDialogBase): # {{{ self.tabs[1].l = QGridLayout() self.tabs[1].setLayout(self.tabs[1].l) + # accept drop events so we can automatically switch to the second tab to + # drop covers and formats + self.tabs[0].setAcceptDrops(True) + # Tab 0 tab0 = self.tabs[0] @@ -522,7 +660,11 @@ class MetadataSingleDialogAlt(MetadataSingleDialogBase): # {{{ self.tabs[0].l.addWidget(gb, 0, 0, 1, 1) gb.setLayout(tl) - sto(self.button_box, self.title) + self.button_box_layout.insertWidget(1, self.fetch_metadata_button) + self.button_box_layout.insertWidget(2, self.config_metadata_button) + sto(self.button_box, self.fetch_metadata_button) + sto(self.fetch_metadata_button, self.config_metadata_button) + sto(self.config_metadata_button, self.title) def create_row(row, widget, tab_to, button=None, icon=None, span=1): ql = BuddyLabel(widget) @@ -540,6 +682,8 @@ class MetadataSingleDialogAlt(MetadataSingleDialogBase): # {{{ sto(widget, tab_to) tl.addWidget(self.swap_title_author_button, 0, 0, 2, 1) + tl.addWidget(self.manage_authors_button, 2, 0, 1, 1) + tl.addWidget(self.paste_isbn_button, 11, 0, 1, 1) create_row(0, self.title, self.title_sort, button=self.deduce_title_sort_button, span=2, @@ -557,9 +701,13 @@ class MetadataSingleDialogAlt(MetadataSingleDialogBase): # {{{ create_row(8, self.pubdate, self.publisher, button=self.pubdate.clear_button, icon='trash.png') create_row(9, self.publisher, self.timestamp) - create_row(10, self.timestamp, self.isbn, + create_row(10, self.timestamp, self.identifiers, button=self.timestamp.clear_button, icon='trash.png') - create_row(11, self.isbn, self.comments) + create_row(11, self.identifiers, self.comments, + button=self.clear_identifiers_button, icon='trash.png') + sto(self.clear_identifiers_button, self.swap_title_author_button) + sto(self.swap_title_author_button, self.manage_authors_button) + sto(self.manage_authors_button, self.paste_isbn_button) tl.addItem(QSpacerItem(1, 1, QSizePolicy.Fixed, QSizePolicy.Expanding), 12, 1, 1 ,1) @@ -575,7 +723,7 @@ class MetadataSingleDialogAlt(MetadataSingleDialogBase): # {{{ sr.setWidget(w) gbl.addWidget(sr) self.tabs[0].l.addWidget(gb, 0, 1, 1, 1) - sto(self.isbn, gb) + sto(self.identifiers, gb) w = QGroupBox(_('&Comments'), tab0) sp = QSizePolicy() @@ -599,7 +747,6 @@ class MetadataSingleDialogAlt(MetadataSingleDialogBase): # {{{ gb = QGroupBox(_('Change cover'), tab1) l = QGridLayout() gb.setLayout(l) - sto(self.swap_title_author_button, self.cover.buttons[0]) for i, b in enumerate(self.cover.buttons[:3]): l.addWidget(b, 0, i, 1, 1) sto(b, self.cover.buttons[i+1]) @@ -611,7 +758,6 @@ class MetadataSingleDialogAlt(MetadataSingleDialogBase): # {{{ wgl.addWidget(gb) wgl.addItem(QSpacerItem(10, 10, QSizePolicy.Expanding, QSizePolicy.Expanding)) - wgl.addWidget(self.fetch_metadata_button) wgl.addItem(QSpacerItem(10, 10, QSizePolicy.Expanding, QSizePolicy.Expanding)) wgl.addWidget(self.formats_manager) @@ -626,10 +772,148 @@ class MetadataSingleDialogAlt(MetadataSingleDialogBase): # {{{ # }}} +class MetadataSingleDialogAlt2(MetadataSingleDialogBase): # {{{ -def edit_metadata(db, row_list, current_row, parent=None, view_slot=None): - d = MetadataSingleDialog(db, parent) - d.start(row_list, current_row, view_slot=view_slot) + cc_two_column = False + one_line_comments_toolbar = True + use_toolbutton_for_config_metadata = False + + def do_layout(self): + self.central_widget.clear() + self.labels = [] + sto = QWidget.setTabOrder + + self.central_widget.tabBar().setVisible(False) + tab0 = QWidget(self) + self.central_widget.addTab(tab0, _("&Metadata")) + l = QGridLayout() + tab0.setLayout(l) + + # Basic metadata in col 0 + tl = QGridLayout() + gb = QGroupBox(_('Basic metadata'), tab0) + l.addWidget(gb, 0, 0, 1, 1) + gb.setLayout(tl) + + self.button_box_layout.insertWidget(1, self.fetch_metadata_button) + self.button_box_layout.insertWidget(2, self.config_metadata_button) + sto(self.button_box, self.fetch_metadata_button) + sto(self.fetch_metadata_button, self.config_metadata_button) + sto(self.config_metadata_button, self.title) + + def create_row(row, widget, tab_to, button=None, icon=None, span=1): + ql = BuddyLabel(widget) + tl.addWidget(ql, row, 1, 1, 1) + tl.addWidget(widget, row, 2, 1, 1) + if button is not None: + tl.addWidget(button, row, 3, span, 1) + if icon is not None: + button.setIcon(QIcon(I(icon))) + if tab_to is not None: + if button is not None: + sto(widget, button) + sto(button, tab_to) + else: + sto(widget, tab_to) + + tl.addWidget(self.swap_title_author_button, 0, 0, 2, 1) + tl.addWidget(self.manage_authors_button, 2, 0, 2, 1) + tl.addWidget(self.paste_isbn_button, 11, 0, 1, 1) + + create_row(0, self.title, self.title_sort, + button=self.deduce_title_sort_button, span=2, + icon='auto_author_sort.png') + create_row(1, self.title_sort, self.authors) + create_row(2, self.authors, self.author_sort, + button=self.deduce_author_sort_button, + span=2, icon='auto_author_sort.png') + create_row(3, self.author_sort, self.series) + create_row(4, self.series, self.series_index, + button=self.remove_unused_series_button, icon='trash.png') + create_row(5, self.series_index, self.tags) + create_row(6, self.tags, self.rating, button=self.tags_editor_button) + create_row(7, self.rating, self.pubdate) + create_row(8, self.pubdate, self.publisher, + button=self.pubdate.clear_button, icon='trash.png') + create_row(9, self.publisher, self.timestamp) + create_row(10, self.timestamp, self.identifiers, + button=self.timestamp.clear_button, icon='trash.png') + create_row(11, self.identifiers, self.comments, + button=self.clear_identifiers_button, icon='trash.png') + sto(self.clear_identifiers_button, self.swap_title_author_button) + sto(self.swap_title_author_button, self.manage_authors_button) + sto(self.manage_authors_button, self.paste_isbn_button) + tl.addItem(QSpacerItem(1, 1, QSizePolicy.Fixed, QSizePolicy.Expanding), + 12, 1, 1 ,1) + + # Custom metadata in col 1 + w = getattr(self, 'custom_metadata_widgets_parent', None) + if w is not None: + gb = QGroupBox(_('Custom metadata'), tab0) + gbl = QVBoxLayout() + gb.setLayout(gbl) + sr = QScrollArea(gb) + sr.setHorizontalScrollBarPolicy(Qt.ScrollBarAlwaysOff) + sr.setWidgetResizable(True) + sr.setBackgroundRole(QPalette.Base) + sr.setFrameStyle(QFrame.NoFrame) + sr.setWidget(w) + gbl.addWidget(sr) + l.addWidget(gb, 0, 1, 1, 1) + sp = QSizePolicy() + sp.setVerticalStretch(10) + sp.setHorizontalPolicy(QSizePolicy.Minimum) + sp.setVerticalPolicy(QSizePolicy.Expanding) + gb.setSizePolicy(sp) + self.set_custom_metadata_tab_order() + + # comments span col 0 & 1 + w = QGroupBox(_('Comments'), tab0) + sp = QSizePolicy() + sp.setVerticalStretch(10) + sp.setHorizontalPolicy(QSizePolicy.Expanding) + sp.setVerticalPolicy(QSizePolicy.Expanding) + w.setSizePolicy(sp) + lb = QHBoxLayout() + w.setLayout(lb) + lb.addWidget(self.comments) + l.addWidget(w, 1, 0, 1, 2) + + # Cover & formats in col 3 + gb = QGroupBox(_('Cover'), tab0) + lb = QGridLayout() + gb.setLayout(lb) + lb.addWidget(self.cover, 0, 0, 1, 3, alignment=Qt.AlignCenter) + sto(self.manage_authors_button, self.cover.buttons[0]) + for i, b in enumerate(self.cover.buttons[:3]): + lb.addWidget(b, 1, i, 1, 1) + sto(b, self.cover.buttons[i+1]) + hl = QHBoxLayout() + for b in self.cover.buttons[3:]: + hl.addWidget(b) + sto(self.cover.buttons[-2], self.cover.buttons[-1]) + lb.addLayout(hl, 2, 0, 1, 3) + l.addWidget(gb, 0, 2, 1, 1) + l.addWidget(self.formats_manager, 1, 2, 1, 1) + sto(self.cover.buttons[-1], self.formats_manager) + + self.formats_manager.formats.setMaximumWidth(10000) + self.formats_manager.formats.setIconSize(QSize(32, 32)) + +# }}} + + +editors = {'default': MetadataSingleDialog, 'alt1': MetadataSingleDialogAlt1, + 'alt2': MetadataSingleDialogAlt2} + +def edit_metadata(db, row_list, current_row, parent=None, view_slot=None, + set_current_callback=None): + cls = gprefs.get('edit_metadata_single_layout', '') + if cls not in editors: + cls = 'default' + d = editors[cls](db, parent) + d.start(row_list, current_row, view_slot=view_slot, + set_current_callback=set_current_callback) return d.changed, d.rows_to_refresh if __name__ == '__main__': diff --git a/src/calibre/gui2/metadata/single_download.py b/src/calibre/gui2/metadata/single_download.py new file mode 100644 index 0000000000..cc89ef2259 --- /dev/null +++ b/src/calibre/gui2/metadata/single_download.py @@ -0,0 +1,956 @@ +#!/usr/bin/env python +# vim:fileencoding=UTF-8:ts=4:sw=4:sta:et:sts=4:ai +from __future__ import (unicode_literals, division, absolute_import, + print_function) + +__license__ = 'GPL v3' +__copyright__ = '2011, Kovid Goyal <kovid@kovidgoyal.net>' +__docformat__ = 'restructuredtext en' + +DEBUG_DIALOG = False + +# Imports {{{ +from threading import Thread, Event +from operator import attrgetter +from Queue import Queue, Empty + +from PyQt4.Qt import (QStyledItemDelegate, QTextDocument, QRectF, QIcon, Qt, + QApplication, QDialog, QVBoxLayout, QLabel, QDialogButtonBox, + QStackedWidget, QWidget, QTableView, QGridLayout, QFontInfo, QPalette, + QTimer, pyqtSignal, QAbstractTableModel, QVariant, QSize, QListView, + QPixmap, QAbstractListModel, QColor, QRect, QTextBrowser, QModelIndex) +from PyQt4.QtWebKit import QWebView + +from calibre.customize.ui import metadata_plugins +from calibre.ebooks.metadata import authors_to_string +from calibre.utils.logging import GUILog as Log +from calibre.ebooks.metadata.sources.identify import (identify, + urls_from_identifiers) +from calibre.ebooks.metadata.book.base import Metadata +from calibre.gui2 import error_dialog, NONE +from calibre.utils.date import utcnow, fromordinal, format_date +from calibre.library.comments import comments_to_html +from calibre import force_unicode +# }}} + +class RichTextDelegate(QStyledItemDelegate): # {{{ + + def __init__(self, parent=None): + QStyledItemDelegate.__init__(self, parent) + + def to_doc(self, index): + doc = QTextDocument() + doc.setHtml(index.data().toString()) + return doc + + def sizeHint(self, option, index): + doc = self.to_doc(index) + ans = doc.size().toSize() + if ans.width() > 150: + ans.setWidth(160) + ans.setHeight(ans.height()+10) + return ans + + def paint(self, painter, option, index): + QStyledItemDelegate.paint(self, painter, option, QModelIndex()) + painter.save() + painter.setClipRect(QRectF(option.rect)) + painter.translate(option.rect.topLeft()) + self.to_doc(index).drawContents(painter) + painter.restore() +# }}} + +class CoverDelegate(QStyledItemDelegate): # {{{ + + needs_redraw = pyqtSignal() + + def __init__(self, parent): + QStyledItemDelegate.__init__(self, parent) + + self.angle = 0 + self.timer = QTimer(self) + self.timer.timeout.connect(self.frame_changed) + self.color = parent.palette().color(QPalette.WindowText) + self.spinner_width = 64 + + def frame_changed(self, *args): + self.angle = (self.angle+30)%360 + self.needs_redraw.emit() + + def start_animation(self): + self.angle = 0 + self.timer.start(200) + + def stop_animation(self): + self.timer.stop() + + def draw_spinner(self, painter, rect): + width = rect.width() + + outer_radius = (width-1)*0.5 + inner_radius = (width-1)*0.5*0.38 + + capsule_height = outer_radius - inner_radius + capsule_width = int(capsule_height * (0.23 if width > 32 else 0.35)) + capsule_radius = capsule_width//2 + + painter.save() + painter.setRenderHint(painter.Antialiasing) + + for i in xrange(12): + color = QColor(self.color) + color.setAlphaF(1.0 - (i/12.0)) + painter.setPen(Qt.NoPen) + painter.setBrush(color) + painter.save() + painter.translate(rect.center()) + painter.rotate(self.angle - i*30.0) + painter.drawRoundedRect(-capsule_width*0.5, + -(inner_radius+capsule_height), capsule_width, + capsule_height, capsule_radius, capsule_radius) + painter.restore() + painter.restore() + + def paint(self, painter, option, index): + QStyledItemDelegate.paint(self, painter, option, index) + style = QApplication.style() + waiting = self.timer.isActive() and index.data(Qt.UserRole).toBool() + if waiting: + rect = QRect(0, 0, self.spinner_width, self.spinner_width) + rect.moveCenter(option.rect.center()) + self.draw_spinner(painter, rect) + else: + # Ensure the cover is rendered over any selection rect + style.drawItemPixmap(painter, option.rect, Qt.AlignTop|Qt.AlignHCenter, + QPixmap(index.data(Qt.DecorationRole))) + +# }}} + +class ResultsModel(QAbstractTableModel): # {{{ + + COLUMNS = ( + '#', _('Title'), _('Published'), _('Has cover'), _('Has summary') + ) + HTML_COLS = (1, 2) + ICON_COLS = (3, 4) + + def __init__(self, results, parent=None): + QAbstractTableModel.__init__(self, parent) + self.results = results + self.yes_icon = QVariant(QIcon(I('ok.png'))) + + def rowCount(self, parent=None): + return len(self.results) + + def columnCount(self, parent=None): + return len(self.COLUMNS) + + def headerData(self, section, orientation, role): + if orientation == Qt.Horizontal and role == Qt.DisplayRole: + try: + return QVariant(self.COLUMNS[section]) + except: + return NONE + return NONE + + def data_as_text(self, book, col): + if col == 0: + return unicode(book.gui_rank+1) + if col == 1: + t = book.title if book.title else _('Unknown') + a = authors_to_string(book.authors) if book.authors else '' + return '<b>%s</b><br><i>%s</i>' % (t, a) + if col == 2: + d = format_date(book.pubdate, 'yyyy') if book.pubdate else _('Unknown') + p = book.publisher if book.publisher else '' + return '<b>%s</b><br><i>%s</i>' % (d, p) + + + def data(self, index, role): + row, col = index.row(), index.column() + try: + book = self.results[row] + except: + return NONE + if role == Qt.DisplayRole and col not in self.ICON_COLS: + res = self.data_as_text(book, col) + if res: + return QVariant(res) + return NONE + elif role == Qt.DecorationRole and col in self.ICON_COLS: + if col == 3 and getattr(book, 'has_cached_cover_url', False): + return self.yes_icon + if col == 4 and book.comments: + return self.yes_icon + elif role == Qt.UserRole: + return book + elif role == Qt.ToolTipRole and col == 3: + return QVariant( + _('The has cover indication is not fully\n' + 'reliable. Sometimes results marked as not\n' + 'having a cover will find a cover in the download\n' + 'cover stage, and vice versa.')) + + return NONE + + def sort(self, col, order=Qt.AscendingOrder): + key = lambda x: x + if col == 0: + key = attrgetter('gui_rank') + elif col == 1: + key = attrgetter('title') + elif col == 2: + key = attrgetter('pubdate') + elif col == 3: + key = attrgetter('has_cached_cover_url') + elif key == 4: + key = lambda x: bool(x.comments) + + self.results.sort(key=key, reverse=order==Qt.AscendingOrder) + self.reset() + +# }}} + +class ResultsView(QTableView): # {{{ + + show_details_signal = pyqtSignal(object) + book_selected = pyqtSignal(object) + + def __init__(self, parent=None): + QTableView.__init__(self, parent) + self.rt_delegate = RichTextDelegate(self) + self.setSelectionMode(self.SingleSelection) + self.setAlternatingRowColors(True) + self.setSelectionBehavior(self.SelectRows) + self.setIconSize(QSize(24, 24)) + self.clicked.connect(self.show_details) + self.doubleClicked.connect(self.select_index) + self.setSortingEnabled(True) + + def show_results(self, results): + self._model = ResultsModel(results, self) + self.setModel(self._model) + for i in self._model.HTML_COLS: + self.setItemDelegateForColumn(i, self.rt_delegate) + self.resizeRowsToContents() + self.resizeColumnsToContents() + self.setFocus(Qt.OtherFocusReason) + + def currentChanged(self, current, previous): + ret = QTableView.currentChanged(self, current, previous) + self.show_details(current) + return ret + + def show_details(self, index): + book = self.model().data(index, Qt.UserRole) + parts = [ + '<center>', + '<h2>%s</h2>'%book.title, + '<div><i>%s</i></div>'%authors_to_string(book.authors), + ] + if not book.is_null('rating'): + parts.append('<div>%s</div>'%('\u2605'*int(book.rating))) + parts.append('</center>') + if book.identifiers: + urls = urls_from_identifiers(book.identifiers) + ids = ['<a href="%s">%s</a>'%(url, name) for name, ign, ign, url in urls] + if ids: + parts.append('<div><b>%s:</b> %s</div><br>'%(_('See at'), ', '.join(ids))) + if book.tags: + parts.append('<div>%s</div><div>\u00a0</div>'%', '.join(book.tags)) + if book.comments: + parts.append(comments_to_html(book.comments)) + + self.show_details_signal.emit(''.join(parts)) + + def select_index(self, index): + if not index.isValid(): + index = self.model().index(0, 0) + book = self.model().data(index, Qt.UserRole) + self.book_selected.emit(book) + + def get_result(self): + self.select_index(self.currentIndex()) + +# }}} + +class Comments(QWebView): # {{{ + + def __init__(self, parent=None): + QWebView.__init__(self, parent) + self.setAcceptDrops(False) + self.setMaximumWidth(300) + self.setMinimumWidth(300) + + palette = self.palette() + palette.setBrush(QPalette.Base, Qt.transparent) + self.page().setPalette(palette) + self.setAttribute(Qt.WA_OpaquePaintEvent, False) + + self.page().setLinkDelegationPolicy(self.page().DelegateAllLinks) + self.linkClicked.connect(self.link_clicked) + + def link_clicked(self, url): + from calibre.gui2 import open_url + if unicode(url.toString()).startswith('http://'): + open_url(url) + + def turnoff_scrollbar(self, *args): + self.page().mainFrame().setScrollBarPolicy(Qt.Horizontal, Qt.ScrollBarAlwaysOff) + + def show_data(self, html): + def color_to_string(col): + ans = '#000000' + if col.isValid(): + col = col.toRgb() + if col.isValid(): + ans = unicode(col.name()) + return ans + + f = QFontInfo(QApplication.font(self.parent())).pixelSize() + c = color_to_string(QApplication.palette().color(QPalette.Normal, + QPalette.WindowText)) + templ = '''\ + <html> + <head> + <style type="text/css"> + body, td {background-color: transparent; font-size: %dpx; color: %s } + a { text-decoration: none; color: blue } + div.description { margin-top: 0; padding-top: 0; text-indent: 0 } + table { margin-bottom: 0; padding-bottom: 0; } + </style> + </head> + <body> + <div class="description"> + %%s + </div> + </body> + <html> + '''%(f, c) + self.setHtml(templ%html) +# }}} + +class IdentifyWorker(Thread): # {{{ + + def __init__(self, log, abort, title, authors, identifiers): + Thread.__init__(self) + self.daemon = True + + self.log, self.abort = log, abort + self.title, self.authors, self.identifiers = (title, authors, + identifiers) + + self.results = [] + self.error = None + + def sample_results(self): + m1 = Metadata('The Great Gatsby', ['Francis Scott Fitzgerald']) + m2 = Metadata('The Great Gatsby', ['F. Scott Fitzgerald']) + m1.has_cached_cover_url = True + m2.has_cached_cover_url = False + m1.comments = 'Some comments '*10 + m1.tags = ['tag%d'%i for i in range(20)] + m1.rating = 4.4 + m1.language = 'en' + m2.language = 'fr' + m1.pubdate = utcnow() + m2.pubdate = fromordinal(1000000) + m1.publisher = 'Publisher 1' + m2.publisher = 'Publisher 2' + + return [m1, m2] + + def run(self): + try: + if DEBUG_DIALOG: + self.results = self.sample_results() + else: + self.results = identify(self.log, self.abort, title=self.title, + authors=self.authors, identifiers=self.identifiers) + for i, result in enumerate(self.results): + result.gui_rank = i + except: + import traceback + self.error = force_unicode(traceback.format_exc()) +# }}} + +class IdentifyWidget(QWidget): # {{{ + + rejected = pyqtSignal() + results_found = pyqtSignal() + book_selected = pyqtSignal(object) + + def __init__(self, log, parent=None): + QWidget.__init__(self, parent) + self.log = log + self.abort = Event() + + self.l = l = QGridLayout() + self.setLayout(l) + + names = ['<b>'+p.name+'</b>' for p in metadata_plugins(['identify']) if + p.is_configured()] + self.top = QLabel('<p>'+_('calibre is downloading metadata from: ') + + ', '.join(names)) + self.top.setWordWrap(True) + l.addWidget(self.top, 0, 0) + + self.results_view = ResultsView(self) + self.results_view.book_selected.connect(self.book_selected.emit) + self.get_result = self.results_view.get_result + l.addWidget(self.results_view, 1, 0) + + self.comments_view = Comments(self) + l.addWidget(self.comments_view, 1, 1) + + self.results_view.show_details_signal.connect(self.comments_view.show_data) + + self.query = QLabel('download starting...') + f = self.query.font() + f.setPointSize(f.pointSize()-2) + self.query.setFont(f) + self.query.setWordWrap(True) + l.addWidget(self.query, 2, 0, 1, 2) + + self.comments_view.show_data('<h2>'+_('Please wait')+ + '<br><span id="dots">.</span></h2>'+ + ''' + <script type="text/javascript"> + window.onload=function(){ + var dotspan = document.getElementById('dots'); + window.setInterval(function(){ + if(dotspan.textContent == '............'){ + dotspan.textContent = '.'; + } + else{ + dotspan.textContent += '.'; + } + }, 400); + } + </script> + ''') + + def start(self, title=None, authors=None, identifiers={}): + self.log.clear() + self.log('Starting download') + parts = [] + if title: + parts.append('title:'+title) + if authors: + parts.append('authors:'+authors_to_string(authors)) + if identifiers: + x = ', '.join('%s:%s'%(k, v) for k, v in identifiers.iteritems()) + parts.append(x) + self.query.setText(_('Query: ')+'; '.join(parts)) + self.log(unicode(self.query.text())) + + self.worker = IdentifyWorker(self.log, self.abort, title, + authors, identifiers) + + self.worker.start() + + QTimer.singleShot(50, self.update) + + def update(self): + if self.worker.is_alive(): + QTimer.singleShot(50, self.update) + else: + self.process_results() + + def process_results(self): + if self.worker.error is not None: + error_dialog(self, _('Download failed'), + _('Failed to download metadata. Click ' + 'Show Details to see details'), + show=True, det_msg=self.worker.error) + self.rejected.emit() + return + + if not self.worker.results: + log = ''.join(self.log.plain_text) + error_dialog(self, _('No matches found'), '<p>' + + _('Failed to find any books that ' + 'match your search. Try making the search <b>less ' + 'specific</b>. For example, use only the author\'s ' + 'last name and a single distinctive word from ' + 'the title.<p>To see the full log, click Show Details.'), + show=True, det_msg=log) + self.rejected.emit() + return + + self.results_view.show_results(self.worker.results) + + self.comments_view.show_data(''' + <div style="margin-bottom:2ex">Found <b>%d</b> results</div> + <div>To see <b>details</b>, click on any result</div>''' % + len(self.worker.results)) + + self.results_found.emit() + + + def cancel(self): + self.abort.set() +# }}} + +class CoverWorker(Thread): # {{{ + + def __init__(self, log, abort, title, authors, identifiers): + Thread.__init__(self) + self.daemon = True + + self.log, self.abort = log, abort + self.title, self.authors, self.identifiers = (title, authors, + identifiers) + + self.rq = Queue() + self.error = None + + def fake_run(self): + images = ['donate.png', 'config.png', 'column.png', 'eject.png', ] + import time + time.sleep(2) + for pl, im in zip(metadata_plugins(['cover']), images): + self.rq.put((pl, 1, 1, 'png', I(im, data=True))) + + def run(self): + try: + if DEBUG_DIALOG: + self.fake_run() + else: + from calibre.ebooks.metadata.sources.covers import run_download + run_download(self.log, self.rq, self.abort, title=self.title, + authors=self.authors, identifiers=self.identifiers) + except: + import traceback + self.error = force_unicode(traceback.format_exc()) +# }}} + +class CoversModel(QAbstractListModel): # {{{ + + def __init__(self, current_cover, parent=None): + QAbstractListModel.__init__(self, parent) + + if current_cover is None: + current_cover = QPixmap(I('default_cover.png')) + + self.blank = QPixmap(I('blank.png')).scaled(150, 200) + + self.covers = [self.get_item(_('Current cover'), current_cover)] + self.plugin_map = {} + for i, plugin in enumerate(metadata_plugins(['cover'])): + self.covers.append((plugin.name+'\n'+_('Searching...'), + QVariant(self.blank), None, True)) + self.plugin_map[plugin] = i+1 + + def get_item(self, src, pmap, waiting=False): + sz = '%dx%d'%(pmap.width(), pmap.height()) + text = QVariant(src + '\n' + sz) + scaled = pmap.scaled(150, 200, Qt.IgnoreAspectRatio, + Qt.SmoothTransformation) + return (text, QVariant(scaled), pmap, waiting) + + def rowCount(self, parent=None): + return len(self.covers) + + def data(self, index, role): + try: + text, pmap, cover, waiting = self.covers[index.row()] + except: + return NONE + if role == Qt.DecorationRole: + return pmap + if role == Qt.DisplayRole: + return text + if role == Qt.UserRole: + return waiting + return NONE + + def plugin_for_index(self, index): + row = index.row() if hasattr(index, 'row') else index + for k, v in self.plugin_map.iteritems(): + if v == row: + return k + + def cover_keygen(self, x): + pmap = x[2] + if pmap is None: + return 1 + return pmap.width()*pmap.height() + + + def clear_failed(self): + good = [] + pmap = {} + dcovers = sorted(self.covers[1:], key=self.cover_keygen, reverse=True) + for i, x in enumerate(self.covers[0:1] + dcovers): + if not x[-1]: + good.append(x) + if i > 0: + plugin = self.plugin_for_index(i) + pmap[plugin] = len(good) - 1 + self.covers = good + self.plugin_map = pmap + self.reset() + + def index_for_plugin(self, plugin): + idx = self.plugin_map.get(plugin, 0) + return self.index(idx) + + def update_result(self, plugin, width, height, data): + try: + idx = self.plugin_map[plugin] + except: + return + pmap = QPixmap() + pmap.loadFromData(data) + if pmap.isNull(): + return + self.covers[idx] = self.get_item(plugin.name, pmap, waiting=False) + self.dataChanged.emit(self.index(idx), self.index(idx)) + + def cover_pixmap(self, index): + row = index.row() + if row > 0 and row < len(self.covers): + pmap = self.covers[row][2] + if pmap is not None and not pmap.isNull(): + return pmap + +# }}} + +class CoversView(QListView): # {{{ + + chosen = pyqtSignal() + + def __init__(self, current_cover, parent=None): + QListView.__init__(self, parent) + self.m = CoversModel(current_cover, self) + self.setModel(self.m) + + self.setFlow(self.LeftToRight) + self.setWrapping(True) + self.setResizeMode(self.Adjust) + self.setGridSize(QSize(190, 260)) + self.setIconSize(QSize(150, 200)) + self.setSelectionMode(self.SingleSelection) + self.setViewMode(self.IconMode) + + self.delegate = CoverDelegate(self) + self.setItemDelegate(self.delegate) + self.delegate.needs_redraw.connect(self.viewport().update, + type=Qt.QueuedConnection) + + self.doubleClicked.connect(self.chosen, type=Qt.QueuedConnection) + + def select(self, num): + current = self.model().index(num) + sm = self.selectionModel() + sm.select(current, sm.SelectCurrent) + + def start(self): + self.select(0) + self.delegate.start_animation() + + def clear_failed(self): + plugin = self.m.plugin_for_index(self.currentIndex()) + self.m.clear_failed() + self.select(self.m.index_for_plugin(plugin).row()) + +# }}} + +class CoversWidget(QWidget): # {{{ + + chosen = pyqtSignal() + finished = pyqtSignal() + + def __init__(self, log, current_cover, parent=None): + QWidget.__init__(self, parent) + self.log = log + self.abort = Event() + + self.l = l = QGridLayout() + self.setLayout(l) + + self.msg = QLabel() + self.msg.setWordWrap(True) + l.addWidget(self.msg, 0, 0) + + self.covers_view = CoversView(current_cover, self) + self.covers_view.chosen.connect(self.chosen) + l.addWidget(self.covers_view, 1, 0) + self.continue_processing = True + + def start(self, book, current_cover, title, authors): + self.book, self.current_cover = book, current_cover + self.title, self.authors = title, authors + self.log('Starting cover download for:', book.title) + self.log('Query:', title, authors, self.book.identifiers) + self.msg.setText('<p>'+_('Downloading covers for <b>%s</b>, please wait...')%book.title) + self.covers_view.start() + + self.worker = CoverWorker(self.log, self.abort, self.title, + self.authors, book.identifiers) + self.worker.start() + QTimer.singleShot(50, self.check) + self.covers_view.setFocus(Qt.OtherFocusReason) + + def check(self): + if self.worker.is_alive() and not self.abort.is_set(): + QTimer.singleShot(50, self.check) + try: + self.process_result(self.worker.rq.get_nowait()) + except Empty: + pass + else: + self.process_results() + + def process_results(self): + while self.continue_processing: + try: + self.process_result(self.worker.rq.get_nowait()) + except Empty: + break + + self.covers_view.clear_failed() + + if self.worker.error is not None: + error_dialog(self, _('Download failed'), + _('Failed to download any covers, click' + ' "Show details" for details.'), + det_msg=self.worker.error, show=True) + + num = self.covers_view.model().rowCount() + if num < 2: + txt = _('Could not find any covers for <b>%s</b>')%self.book.title + else: + txt = _('Found <b>%d</b> covers of %s. Pick the one you like' + ' best.')%(num-1, self.title) + self.msg.setText(txt) + + self.finished.emit() + + def process_result(self, result): + if not self.continue_processing: + return + plugin, width, height, fmt, data = result + self.covers_view.model().update_result(plugin, width, height, data) + + def cleanup(self): + self.covers_view.delegate.stop_animation() + self.continue_processing = False + + def cancel(self): + self.continue_processing = False + self.abort.set() + + def cover_pixmap(self): + idx = None + for i in self.covers_view.selectionModel().selectedIndexes(): + if i.isValid(): + idx = i + break + if idx is None: + idx = self.covers_view.currentIndex() + return self.covers_view.model().cover_pixmap(idx) + +# }}} + +class LogViewer(QDialog): # {{{ + + def __init__(self, log, parent=None): + QDialog.__init__(self, parent) + self.log = log + self.l = l = QVBoxLayout() + self.setLayout(l) + + self.tb = QTextBrowser(self) + l.addWidget(self.tb) + + self.bb = QDialogButtonBox(QDialogButtonBox.Close) + l.addWidget(self.bb) + self.copy_button = self.bb.addButton(_('Copy to clipboard'), + self.bb.ActionRole) + self.copy_button.clicked.connect(self.copy_to_clipboard) + self.copy_button.setIcon(QIcon(I('edit-copy.png'))) + self.bb.rejected.connect(self.reject) + self.bb.accepted.connect(self.accept) + + self.setWindowTitle(_('Download log')) + self.setWindowIcon(QIcon(I('debug.png'))) + self.resize(QSize(800, 400)) + + self.keep_updating = True + self.last_html = None + self.finished.connect(self.stop) + QTimer.singleShot(100, self.update_log) + + self.show() + + def copy_to_clipboard(self): + QApplication.clipboard().setText(''.join(self.log.plain_text)) + + def stop(self, *args): + self.keep_updating = False + + def update_log(self): + if not self.keep_updating: + return + html = self.log.html + if html != self.last_html: + self.last_html = html + self.tb.setHtml('<pre style="font-family:monospace">%s</pre>'%html) + QTimer.singleShot(1000, self.update_log) + +# }}} + +class FullFetch(QDialog): # {{{ + + def __init__(self, current_cover=None, parent=None): + QDialog.__init__(self, parent) + self.current_cover = current_cover + self.log = Log() + self.book = self.cover_pixmap = None + + self.setWindowTitle(_('Downloading metadata...')) + self.setWindowIcon(QIcon(I('metadata.png'))) + + self.stack = QStackedWidget() + self.l = l = QVBoxLayout() + self.setLayout(l) + l.addWidget(self.stack) + + self.bb = QDialogButtonBox(QDialogButtonBox.Cancel|QDialogButtonBox.Ok) + l.addWidget(self.bb) + self.bb.rejected.connect(self.reject) + self.next_button = self.bb.addButton(_('Next'), self.bb.AcceptRole) + self.next_button.setDefault(True) + self.next_button.setEnabled(False) + self.next_button.setIcon(QIcon(I('ok.png'))) + self.next_button.clicked.connect(self.next_clicked) + self.ok_button = self.bb.button(self.bb.Ok) + self.ok_button.clicked.connect(self.ok_clicked) + self.log_button = self.bb.addButton(_('View log'), self.bb.ActionRole) + self.log_button.clicked.connect(self.view_log) + self.log_button.setIcon(QIcon(I('debug.png'))) + self.ok_button.setVisible(False) + + self.identify_widget = IdentifyWidget(self.log, self) + self.identify_widget.rejected.connect(self.reject) + self.identify_widget.results_found.connect(self.identify_results_found) + self.identify_widget.book_selected.connect(self.book_selected) + self.stack.addWidget(self.identify_widget) + + self.covers_widget = CoversWidget(self.log, self.current_cover, parent=self) + self.covers_widget.chosen.connect(self.ok_clicked) + self.stack.addWidget(self.covers_widget) + + self.resize(850, 550) + + self.finished.connect(self.cleanup) + + def view_log(self): + self._lv = LogViewer(self.log, self) + + def book_selected(self, book): + self.next_button.setVisible(False) + self.ok_button.setVisible(True) + self.book = book + self.stack.setCurrentIndex(1) + self.log('\n\n') + self.covers_widget.start(book, self.current_cover, + self.title, self.authors) + + def accept(self): + # Prevent the usual dialog accept mechanisms from working + pass + + def reject(self): + self.identify_widget.cancel() + self.covers_widget.cancel() + return QDialog.reject(self) + + def cleanup(self): + self.covers_widget.cleanup() + + def identify_results_found(self): + self.next_button.setEnabled(True) + + def next_clicked(self, *args): + self.identify_widget.get_result() + + def ok_clicked(self, *args): + self.cover_pixmap = self.covers_widget.cover_pixmap() + if DEBUG_DIALOG: + if self.cover_pixmap is not None: + self.w = QLabel() + self.w.setPixmap(self.cover_pixmap) + self.stack.addWidget(self.w) + self.stack.setCurrentIndex(2) + else: + QDialog.accept(self) + + def start(self, title=None, authors=None, identifiers={}): + self.title, self.authors = title, authors + self.identify_widget.start(title=title, authors=authors, + identifiers=identifiers) + return self.exec_() +# }}} + +class CoverFetch(QDialog): # {{{ + + def __init__(self, current_cover=None, parent=None): + QDialog.__init__(self, parent) + self.current_cover = current_cover + self.log = Log() + self.cover_pixmap = None + + self.setWindowTitle(_('Downloading cover...')) + self.setWindowIcon(QIcon(I('book.png'))) + + self.l = l = QVBoxLayout() + self.setLayout(l) + + self.covers_widget = CoversWidget(self.log, self.current_cover, parent=self) + self.covers_widget.chosen.connect(self.accept) + l.addWidget(self.covers_widget) + + self.resize(850, 550) + + self.finished.connect(self.cleanup) + + self.bb = QDialogButtonBox(QDialogButtonBox.Cancel|QDialogButtonBox.Ok) + l.addWidget(self.bb) + self.log_button = self.bb.addButton(_('View log'), self.bb.ActionRole) + self.log_button.clicked.connect(self.view_log) + self.log_button.setIcon(QIcon(I('debug.png'))) + self.bb.rejected.connect(self.reject) + self.bb.accepted.connect(self.accept) + + def cleanup(self): + self.covers_widget.cleanup() + + def reject(self): + self.covers_widget.cancel() + return QDialog.reject(self) + + def accept(self, *args): + self.cover_pixmap = self.covers_widget.cover_pixmap() + QDialog.accept(self) + + def start(self, title, authors, identifiers): + book = Metadata(title, authors) + book.identifiers = identifiers + self.covers_widget.start(book, self.current_cover, + title, authors) + return self.exec_() + + def view_log(self): + self._lv = LogViewer(self.log, self) + +# }}} + +if __name__ == '__main__': + DEBUG_DIALOG = True + app = QApplication([]) + d = FullFetch() + d.start(title='great gatsby', authors=['fitzgerald']) + diff --git a/src/calibre/gui2/notify.py b/src/calibre/gui2/notify.py index 501f7007eb..947d98f1a4 100644 --- a/src/calibre/gui2/notify.py +++ b/src/calibre/gui2/notify.py @@ -34,7 +34,7 @@ class DBUSNotifier(Notifier): import dbus self.dbus = dbus self._notify = dbus.Interface(dbus.SessionBus().get_object(server, path), interface) - except Exception, err: + except Exception as err: self.ok = False self.err = str(err) diff --git a/src/calibre/gui2/pictureflow/pictureflow.cpp b/src/calibre/gui2/pictureflow/pictureflow.cpp index 1c63ec410c..1d671154ae 100644 --- a/src/calibre/gui2/pictureflow/pictureflow.cpp +++ b/src/calibre/gui2/pictureflow/pictureflow.cpp @@ -439,7 +439,8 @@ void PictureFlowPrivate::setImages(FlowImages *images) QObject::disconnect(slideImages, SIGNAL(dataChanged()), widget, SLOT(dataChanged())); slideImages = images; dataChanged(); - QObject::connect(slideImages, SIGNAL(dataChanged()), widget, SLOT(dataChanged())); + QObject::connect(slideImages, SIGNAL(dataChanged()), widget, SLOT(dataChanged()), + Qt::QueuedConnection); } int PictureFlowPrivate::slideCount() const diff --git a/src/calibre/gui2/preferences/__init__.py b/src/calibre/gui2/preferences/__init__.py index 7267716ea8..5b0a05ba40 100644 --- a/src/calibre/gui2/preferences/__init__.py +++ b/src/calibre/gui2/preferences/__init__.py @@ -7,8 +7,9 @@ __docformat__ = 'restructuredtext en' import textwrap -from PyQt4.Qt import QWidget, pyqtSignal, QCheckBox, QAbstractSpinBox, \ - QLineEdit, QComboBox, QVariant +from PyQt4.Qt import (QWidget, pyqtSignal, QCheckBox, QAbstractSpinBox, + QLineEdit, QComboBox, QVariant, Qt, QIcon, QDialog, QVBoxLayout, + QDialogButtonBox) from calibre.customize.ui import preferences_plugins from calibre.utils.config import ConfigProxy @@ -21,7 +22,7 @@ class ConfigWidgetInterface(object): ''' This class defines the interface that all widgets displayed in the Preferences dialog must implement. See :class:`ConfigWidgetBase` for - a base class that implements this interface and defines various conveninece + a base class that implements this interface and defines various convenience methods as well. ''' @@ -82,6 +83,8 @@ class ConfigWidgetInterface(object): class Setting(object): + CHOICES_SEARCH_FLAGS = Qt.MatchExactly | Qt.MatchCaseSensitive + def __init__(self, name, config_obj, widget, gui_name=None, empty_string_is_None=True, choices=None, restart_required=False): self.name, self.gui_name = name, gui_name @@ -168,7 +171,8 @@ class Setting(object): elif self.datatype == 'string': self.gui_obj.setText(val if val else '') elif self.datatype == 'choice': - idx = self.gui_obj.findData(QVariant(val)) + idx = self.gui_obj.findData(QVariant(val), role=Qt.UserRole, + flags=self.CHOICES_SEARCH_FLAGS) if idx == -1: idx = 0 self.gui_obj.setCurrentIndex(idx) @@ -281,7 +285,14 @@ def get_plugin(category, name): 'No Preferences Plugin with category: %s and name: %s found' % (category, name)) -# Testing {{{ +class ConfigDialog(QDialog): + def set_widget(self, w): self.w = w + def accept(self): + try: + self.restart_required = self.w.commit() + except AbortCommit: + return + QDialog.accept(self) def init_gui(): from calibre.gui2.ui import Main @@ -295,21 +306,27 @@ def init_gui(): gui.initialize(db.library_path, db, None, actions, show_gui=False) return gui -def test_widget(category, name, gui=None): - from PyQt4.Qt import QDialog, QVBoxLayout, QDialogButtonBox - class Dialog(QDialog): - def set_widget(self, w): self.w = w - def accept(self): - try: - self.restart_required = self.w.commit() - except AbortCommit: - return - QDialog.accept(self) +def show_config_widget(category, name, gui=None, show_restart_msg=False, + parent=None, never_shutdown=False): + ''' + Show the preferences plugin identified by category and name + :param gui: gui instance, if None a hidden gui is created + :param show_restart_msg: If True and the preferences plugin indicates a + restart is required, show a message box telling the user to restart + :param parent: The parent of the displayed dialog + + :return: True iff a restart is required for the changes made by the user to + take effect + ''' + from calibre.gui2 import gprefs pl = get_plugin(category, name) - d = Dialog() + d = ConfigDialog(parent) d.resize(750, 550) - d.setWindowTitle(category + " - " + name) + conf_name = 'config_widget_dialog_geometry_%s_%s'%(category, name) + geom = gprefs.get(conf_name, None) + d.setWindowTitle(_('Configure ') + name) + d.setWindowIcon(QIcon(I('config.png'))) bb = QDialogButtonBox(d) bb.setStandardButtons(bb.Apply|bb.Cancel|bb.RestoreDefaults) bb.accepted.connect(d.accept) @@ -320,7 +337,13 @@ def test_widget(category, name, gui=None): bb.button(bb.RestoreDefaults).setEnabled(w.supports_restoring_to_defaults) bb.button(bb.Apply).setEnabled(False) bb.button(bb.Apply).clicked.connect(d.accept) - w.changed_signal.connect(lambda : bb.button(bb.Apply).setEnabled(True)) + def onchange(): + b = bb.button(bb.Apply) + b.setEnabled(True) + b.setDefault(True) + b.setAutoDefault(True) + w.changed_signal.connect(onchange) + bb.button(bb.Cancel).setFocus(True) l = QVBoxLayout() d.setLayout(l) l.addWidget(w) @@ -331,12 +354,23 @@ def test_widget(category, name, gui=None): mygui = True w.genesis(gui) w.initialize() + if geom is not None: + d.restoreGeometry(geom) d.exec_() - if getattr(d, 'restart_required', False): + geom = bytearray(d.saveGeometry()) + gprefs[conf_name] = geom + rr = getattr(d, 'restart_required', False) + if show_restart_msg and rr: from calibre.gui2 import warning_dialog warning_dialog(gui, 'Restart required', 'Restart required', show=True) - if mygui: + if mygui and not never_shutdown: gui.shutdown() + return rr + +# Testing {{{ + +def test_widget(category, name, gui=None): + show_config_widget(category, name, gui=gui, show_restart_msg=True) def test_all(): from PyQt4.Qt import QApplication diff --git a/src/calibre/gui2/preferences/behavior.py b/src/calibre/gui2/preferences/behavior.py index aeee6e5064..1247c54ec9 100644 --- a/src/calibre/gui2/preferences/behavior.py +++ b/src/calibre/gui2/preferences/behavior.py @@ -9,9 +9,9 @@ import re from PyQt4.Qt import Qt, QVariant, QListWidgetItem -from calibre.gui2.preferences import ConfigWidgetBase, test_widget +from calibre.gui2.preferences import ConfigWidgetBase, test_widget, Setting from calibre.gui2.preferences.behavior_ui import Ui_Form -from calibre.gui2 import config, info_dialog, dynamic +from calibre.gui2 import config, info_dialog, dynamic, gprefs from calibre.utils.config import prefs from calibre.customize.ui import available_output_formats, all_input_formats from calibre.utils.search_query_parser import saved_searches @@ -20,6 +20,10 @@ from calibre.ebooks.oeb.iterator import is_supported from calibre.constants import iswindows from calibre.utils.icu import sort_key +class OutputFormatSetting(Setting): + + CHOICES_SEARCH_FLAGS = Qt.MatchFixedString + class ConfigWidget(ConfigWidgetBase, Ui_Form): def genesis(self, gui): @@ -27,15 +31,14 @@ class ConfigWidget(ConfigWidgetBase, Ui_Form): db = gui.library_view.model().db r = self.register - - r('worker_process_priority', prefs, choices= - [(_('Low'), 'low'), (_('Normal'), 'normal'), (_('High'), 'high')]) + choices = [(_('Low'), 'low'), (_('Normal'), 'normal'), (_('High'), + 'high')] if iswindows else \ + [(_('Normal'), 'normal'), (_('Low'), 'low'), (_('Very low'), + 'high')] + r('worker_process_priority', prefs, choices=choices) r('network_timeout', prefs) - - r('overwrite_author_title_metadata', config) - r('get_social_metadata', config) r('new_version_notification', config) r('upload_news_to_device', config) r('delete_news_from_library_on_upload', config) @@ -43,7 +46,7 @@ class ConfigWidget(ConfigWidgetBase, Ui_Form): output_formats = list(sorted(available_output_formats())) output_formats.remove('oeb') choices = [(x.upper(), x) for x in output_formats] - r('output_format', prefs, choices=choices) + r('output_format', prefs, choices=choices, setting=OutputFormatSetting) restrictions = sorted(saved_searches().names(), key=sort_key) choices = [('', '')] + [(x, x) for x in restrictions] @@ -56,9 +59,11 @@ class ConfigWidget(ConfigWidgetBase, Ui_Form): signal = getattr(self.opt_internally_viewed_formats, 'item'+signal) signal.connect(self.internally_viewed_formats_changed) - self.settings['worker_process_priority'].gui_obj.setVisible(iswindows) - self.priority_label.setVisible(iswindows) - + r('bools_are_tristate', db.prefs, restart_required=True) + r = self.register + choices = [(_('Default'), 'default'), (_('Compact Metadata'), 'alt1'), + (_('All on 1 tab'), 'alt2')] + r('edit_metadata_single_layout', gprefs, choices=choices) def initialize(self): ConfigWidgetBase.initialize(self) diff --git a/src/calibre/gui2/preferences/behavior.ui b/src/calibre/gui2/preferences/behavior.ui index 0f35d28cd5..ffd59d72bb 100644 --- a/src/calibre/gui2/preferences/behavior.ui +++ b/src/calibre/gui2/preferences/behavior.ui @@ -6,7 +6,7 @@ <rect> <x>0</x> <y>0</y> - <width>672</width> + <width>941</width> <height>563</height> </rect> </property> @@ -14,44 +14,65 @@ <string>Form</string> </property> <layout class="QGridLayout" name="gridLayout"> - <item row="0" column="0" colspan="2"> - <widget class="QCheckBox" name="opt_overwrite_author_title_metadata"> - <property name="text"> - <string>&Overwrite author and title by default when fetching metadata</string> - </property> - </widget> - </item> - <item row="1" column="0" colspan="2"> - <widget class="QCheckBox" name="opt_get_social_metadata"> - <property name="text"> - <string>Download &social metadata (tags/ratings/etc.) by default</string> - </property> - </widget> - </item> - <item row="2" column="0" colspan="2"> + <item row="1" column="0"> <widget class="QCheckBox" name="opt_new_version_notification"> <property name="text"> <string>Show notification when &new version is available</string> </property> </widget> </item> - <item row="3" column="0" colspan="2"> + <item row="1" column="1"> + <widget class="QCheckBox" name="opt_bools_are_tristate"> + <property name="toolTip"> + <string>If checked, Yes/No custom columns values can be Yes, No, or Unknown. +If not checked, the values can be Yes or No.</string> + </property> + <property name="text"> + <string>Yes/No columns have three values (Requires restart)</string> + </property> + </widget> + </item> + <item row="3" column="0"> <widget class="QCheckBox" name="opt_upload_news_to_device"> <property name="text"> <string>Automatically send downloaded &news to ebook reader</string> </property> </widget> </item> - <item row="4" column="0" colspan="2"> + <item row="3" column="1"> <widget class="QCheckBox" name="opt_delete_news_from_library_on_upload"> <property name="text"> <string>&Delete news from library when it is automatically sent to reader</string> </property> </widget> </item> - <item row="5" column="0" colspan="2"> - <layout class="QGridLayout" name="gridLayout_2"> - <item row="1" column="0"> + <item row="5" column="0"> + <layout class="QHBoxLayout"> + <item> + <widget class="QLabel" name="label_23"> + <property name="text"> + <string>Preferred &output format:</string> + </property> + <property name="buddy"> + <cstring>opt_output_format</cstring> + </property> + </widget> + </item> + <item> + <widget class="QComboBox" name="opt_output_format"> + <property name="sizeAdjustPolicy"> + <enum>QComboBox::AdjustToMinimumContentsLengthWithIcon</enum> + </property> + <property name="minimumContentsLength"> + <number>10</number> + </property> + </widget> + </item> + </layout> + </item> + <item row="5" column="1"> + <layout class="QHBoxLayout"> + <item> <widget class="QLabel" name="label_2"> <property name="text"> <string>Default network &timeout:</string> @@ -61,7 +82,7 @@ </property> </widget> </item> - <item row="1" column="1"> + <item> <widget class="QSpinBox" name="opt_network_timeout"> <property name="toolTip"> <string>Set the default timeout for network fetches (i.e. anytime we go out to the internet to get information)</string> @@ -80,7 +101,21 @@ </property> </widget> </item> - <item row="2" column="1"> + </layout> + </item> + <item row="7" column="0"> + <layout class="QHBoxLayout"> + <item> + <widget class="QLabel" name="priority_label"> + <property name="text"> + <string>Job &priority:</string> + </property> + <property name="buddy"> + <cstring>opt_worker_process_priority</cstring> + </property> + </widget> + </item> + <item> <widget class="QComboBox" name="opt_worker_process_priority"> <property name="sizeAdjustPolicy"> <enum>QComboBox::AdjustToMinimumContentsLengthWithIcon</enum> @@ -105,37 +140,11 @@ </item> </widget> </item> - <item row="2" column="0"> - <widget class="QLabel" name="priority_label"> - <property name="text"> - <string>Job &priority:</string> - </property> - <property name="buddy"> - <cstring>opt_worker_process_priority</cstring> - </property> - </widget> - </item> - <item row="0" column="0"> - <widget class="QLabel" name="label_23"> - <property name="text"> - <string>Preferred &output format:</string> - </property> - <property name="buddy"> - <cstring>opt_output_format</cstring> - </property> - </widget> - </item> - <item row="0" column="1"> - <widget class="QComboBox" name="opt_output_format"> - <property name="sizeAdjustPolicy"> - <enum>QComboBox::AdjustToMinimumContentsLengthWithIcon</enum> - </property> - <property name="minimumContentsLength"> - <number>10</number> - </property> - </widget> - </item> - <item row="3" column="0"> + </layout> + </item> + <item row="7" column="1"> + <layout class="QHBoxLayout"> + <item> <widget class="QLabel" name="label_170"> <property name="text"> <string>Restriction to apply when the current library is opened:</string> @@ -145,7 +154,7 @@ </property> </widget> </item> - <item row="3" column="1"> + <item> <widget class="QComboBox" name="opt_gui_restriction"> <property name="maximumSize"> <size> @@ -166,14 +175,28 @@ </item> </layout> </item> - <item row="6" column="0" colspan="2"> - <widget class="QPushButton" name="reset_confirmation_button"> - <property name="text"> - <string>Reset all disabled &confirmation dialogs</string> - </property> - </widget> + <item row="8" column="0"> + <layout class="QHBoxLayout"> + <item> + <widget class="QLabel" name="edit_metadata_single_label"> + <property name="text"> + <string>Edit metadata (single) layout:</string> + </property> + <property name="buddy"> + <cstring>opt_edit_metadata_single_layout</cstring> + </property> + </widget> + </item> + <item> + <widget class="QComboBox" name="opt_edit_metadata_single_layout"> + <property name="toolTip"> + <string>Choose a different layout for the Edit Metadata dialog. The compact metadata layout favors editing custom metadata over changing covers and formats.</string> + </property> + </widget> + </item> + </layout> </item> - <item row="7" column="0"> + <item row="19" column="0"> <widget class="QGroupBox" name="groupBox_5"> <property name="title"> <string>Preferred &input format order:</string> @@ -235,7 +258,7 @@ </layout> </widget> </item> - <item row="7" column="1"> + <item row="19" column="1"> <widget class="QGroupBox" name="groupBox_3"> <property name="title"> <string>Use internal &viewer for:</string> @@ -254,6 +277,13 @@ </layout> </widget> </item> + <item row="8" column="1"> + <widget class="QPushButton" name="reset_confirmation_button"> + <property name="text"> + <string>Reset all disabled &confirmation dialogs</string> + </property> + </widget> + </item> </layout> </widget> <resources> diff --git a/src/calibre/gui2/preferences/columns.py b/src/calibre/gui2/preferences/columns.py index 03a50e6f3a..92aafccce0 100644 --- a/src/calibre/gui2/preferences/columns.py +++ b/src/calibre/gui2/preferences/columns.py @@ -163,8 +163,9 @@ class ConfigWidget(ConfigWidgetBase, Ui_Form): elif '*edited' in self.custcols[c]: cc = self.custcols[c] db.set_custom_column_metadata(cc['colnum'], name=cc['name'], - label=cc['label'], - display = self.custcols[c]['display']) + label=cc['label'], + display = self.custcols[c]['display'], + notify=False) if '*must_restart' in self.custcols[c]: must_restart = True return must_restart diff --git a/src/calibre/gui2/preferences/columns.ui b/src/calibre/gui2/preferences/columns.ui index b5dc9b8c90..423d5dd106 100644 --- a/src/calibre/gui2/preferences/columns.ui +++ b/src/calibre/gui2/preferences/columns.ui @@ -38,6 +38,9 @@ <layout class="QVBoxLayout" name="verticalLayout_3"> <item> <widget class="QToolButton" name="column_up"> + <property name="toolTip"> + <string>Move column up</string> + </property> <property name="text"> <string>...</string> </property> @@ -45,6 +48,12 @@ <iconset resource="../../../../resources/images.qrc"> <normaloff>:/images/arrow-up.png</normaloff>:/images/arrow-up.png</iconset> </property> + <property name="iconSize"> + <size> + <width>32</width> + <height>32</height> + </size> + </property> </widget> </item> <item> @@ -70,7 +79,13 @@ </property> <property name="icon"> <iconset resource="../../../../resources/images.qrc"> - <normaloff>:/images/minus.png</normaloff>:/images/minus.png</iconset> + <normaloff>:/images/trash.png</normaloff>:/images/trash.png</iconset> + </property> + <property name="iconSize"> + <size> + <width>32</width> + <height>32</height> + </size> </property> </widget> </item> @@ -99,6 +114,12 @@ <iconset resource="../../../../resources/images.qrc"> <normaloff>:/images/plus.png</normaloff>:/images/plus.png</iconset> </property> + <property name="iconSize"> + <size> + <width>32</width> + <height>32</height> + </size> + </property> </widget> </item> <item> @@ -126,6 +147,12 @@ <iconset resource="../../../../resources/images.qrc"> <normaloff>:/images/edit_input.png</normaloff>:/images/edit_input.png</iconset> </property> + <property name="iconSize"> + <size> + <width>32</width> + <height>32</height> + </size> + </property> </widget> </item> <item> @@ -143,6 +170,9 @@ </item> <item> <widget class="QToolButton" name="column_down"> + <property name="toolTip"> + <string>Move column down</string> + </property> <property name="text"> <string>...</string> </property> @@ -150,6 +180,12 @@ <iconset resource="../../../../resources/images.qrc"> <normaloff>:/images/arrow-down.png</normaloff>:/images/arrow-down.png</iconset> </property> + <property name="iconSize"> + <size> + <width>32</width> + <height>32</height> + </size> + </property> </widget> </item> </layout> diff --git a/src/calibre/gui2/preferences/conversion.py b/src/calibre/gui2/preferences/conversion.py index 8de9ee1661..b5240227d3 100644 --- a/src/calibre/gui2/preferences/conversion.py +++ b/src/calibre/gui2/preferences/conversion.py @@ -5,6 +5,8 @@ __license__ = 'GPL v3' __copyright__ = '2010, Kovid Goyal <kovid@kovidgoyal.net>' __docformat__ = 'restructuredtext en' +import importlib + from PyQt4.Qt import QIcon, Qt, QStringListModel, QVariant from calibre.gui2.preferences import ConfigWidgetBase, test_widget, AbortCommit @@ -104,8 +106,8 @@ class OutputOptions(Base): for plugin in output_format_plugins(): name = plugin.name.lower().replace(' ', '_') try: - output_widget = __import__('calibre.gui2.convert.'+name, - fromlist=[1]) + output_widget = importlib.import_module( + 'calibre.gui2.convert.'+name) pw = output_widget.PluginWidget self.conversion_widgets.append(pw) except ImportError: diff --git a/src/calibre/gui2/preferences/create_custom_column.py b/src/calibre/gui2/preferences/create_custom_column.py index 9974de472f..8eaa2dd7d9 100644 --- a/src/calibre/gui2/preferences/create_custom_column.py +++ b/src/calibre/gui2/preferences/create_custom_column.py @@ -6,8 +6,7 @@ __copyright__ = '2010, Kovid Goyal <kovid at kovidgoyal.net>' import re from functools import partial -from PyQt4.QtCore import SIGNAL -from PyQt4.Qt import QDialog, Qt, QListWidgetItem, QVariant +from PyQt4.Qt import QDialog, Qt, QListWidgetItem, QVariant, QColor from calibre.gui2.preferences.create_custom_column_ui import Ui_QCreateCustomColumn from calibre.gui2 import error_dialog @@ -42,12 +41,16 @@ class CreateCustomColumn(QDialog, Ui_QCreateCustomColumn): 'text':_('Yes/No'), 'is_multiple':False}, 10:{'datatype':'composite', 'text':_('Column built from other columns'), 'is_multiple':False}, + 11:{'datatype':'*composite', + 'text':_('Column built from other columns, behaves like tags'), 'is_multiple':True}, } def __init__(self, parent, editing, standard_colheads, standard_colnames): QDialog.__init__(self, parent) Ui_QCreateCustomColumn.__init__(self) self.setupUi(self) + self.setWindowTitle(_('Create a custom column')) + self.heading_label.setText(_('Create a custom column')) # Remove help icon on title bar icon = self.windowIcon() self.setWindowFlags(self.windowFlags()&(~Qt.WindowContextHelpButtonHint)) @@ -55,8 +58,21 @@ class CreateCustomColumn(QDialog, Ui_QCreateCustomColumn): self.simple_error = partial(error_dialog, self, show=True, show_copy_button=False) - self.connect(self.button_box, SIGNAL("accepted()"), self.accept) - self.connect(self.button_box, SIGNAL("rejected()"), self.reject) + self.button_box.accepted.connect(self.accept) + self.button_box.rejected.connect(self.reject) + self.shortcuts.linkActivated.connect(self.shortcut_activated) + text = '<p>'+_('Quick create:') + for col, name in [('isbn', _('ISBN')), ('formats', _('Formats')), + ('yesno', _('Yes/No')), + ('tags', _('Tags')), ('series', _('Series')), ('rating', + _('Rating')), ('people', _("People's names"))]: + text += ' <a href="col:%s">%s</a>,'%(col, name) + text = text[:-1] + self.shortcuts.setText(text) + + for sort_by in [_('Text'), _('Number'), _('Date'), _('Yes/No')]: + self.composite_sort_by.addItem(sort_by) + self.parent = parent self.editing_col = editing self.standard_colheads = standard_colheads @@ -69,6 +85,9 @@ class CreateCustomColumn(QDialog, Ui_QCreateCustomColumn): self.datatype_changed() self.exec_() return + self.setWindowTitle(_('Edit a custom column')) + self.heading_label.setText(_('Edit a custom column')) + self.shortcuts.setVisible(False) idx = parent.opt_columns.currentRow() if idx < 0: self.simple_error(_('No column selected'), @@ -82,7 +101,9 @@ class CreateCustomColumn(QDialog, Ui_QCreateCustomColumn): c = parent.custcols[col] self.column_name_box.setText(c['label']) self.column_heading_box.setText(c['name']) - ct = c['datatype'] if not c['is_multiple'] else '*text' + ct = c['datatype'] + if c['is_multiple']: + ct = '*' + ct self.orig_column_number = c['colnum'] self.orig_column_name = col column_numbers = dict(map(lambda x:(self.column_types[x]['datatype'], x), @@ -92,13 +113,60 @@ class CreateCustomColumn(QDialog, Ui_QCreateCustomColumn): if ct == 'datetime': if c['display'].get('date_format', None): self.date_format_box.setText(c['display'].get('date_format', '')) - elif ct == 'composite': + elif ct in ['composite', '*composite']: self.composite_box.setText(c['display'].get('composite_template', '')) + sb = c['display'].get('composite_sort', 'text') + vals = ['text', 'number', 'date', 'bool'] + if sb in vals: + sb = vals.index(sb) + else: + sb = 0 + self.composite_sort_by.setCurrentIndex(sb) + self.composite_make_category.setChecked( + c['display'].get('make_category', False)) elif ct == 'enumeration': self.enum_box.setText(','.join(c['display'].get('enum_values', []))) + self.enum_colors.setText(','.join(c['display'].get('enum_colors', []))) + elif ct in ['int', 'float']: + if c['display'].get('number_format', None): + self.number_format_box.setText(c['display'].get('number_format', '')) self.datatype_changed() + if ct in ['text', 'composite', 'enumeration']: + self.use_decorations.setChecked(c['display'].get('use_decorations', False)) + elif ct == '*text': + self.is_names.setChecked(c['display'].get('is_names', False)) + + all_colors = [unicode(s) for s in list(QColor.colorNames())] + self.enum_colors_label.setToolTip('<p>' + ', '.join(all_colors) + '</p>') self.exec_() + def shortcut_activated(self, url): + which = unicode(url).split(':')[-1] + self.column_type_box.setCurrentIndex({ + 'yesno': 9, + 'tags' : 1, + 'series': 3, + 'rating': 8, + 'people': 1, + }.get(which, 10)) + self.column_name_box.setText(which) + self.column_heading_box.setText({ + 'isbn':'ISBN', + 'formats':_('Formats'), + 'yesno':_('Yes/No'), + 'tags': _('My Tags'), + 'series': _('My Series'), + 'rating': _('My Rating'), + 'people': _('People')}[which]) + self.is_names.setChecked(which == 'people') + if self.composite_box.isVisible(): + self.composite_box.setText( + { + 'isbn': '{identifiers:select(isbn)}', + 'formats': '{formats}', + }[which]) + self.composite_sort_by.setCurrentIndex(0) + def datatype_changed(self, *args): try: col_type = self.column_types[self.column_type_box.currentIndex()]['datatype'] @@ -106,15 +174,33 @@ class CreateCustomColumn(QDialog, Ui_QCreateCustomColumn): col_type = None for x in ('box', 'default_label', 'label'): getattr(self, 'date_format_'+x).setVisible(col_type == 'datetime') - for x in ('box', 'default_label', 'label'): - getattr(self, 'composite_'+x).setVisible(col_type == 'composite') - for x in ('box', 'default_label', 'label'): + getattr(self, 'number_format_'+x).setVisible(col_type in ['int', 'float']) + for x in ('box', 'default_label', 'label', 'sort_by', 'sort_by_label', + 'make_category'): + getattr(self, 'composite_'+x).setVisible(col_type in ['composite', '*composite']) + for x in ('box', 'default_label', 'label', 'colors', 'colors_label'): getattr(self, 'enum_'+x).setVisible(col_type == 'enumeration') + self.use_decorations.setVisible(col_type in ['text', 'composite', 'enumeration']) + self.is_names.setVisible(col_type == '*text') + if col_type == 'int': + self.number_format_box.setToolTip('<p>' + + _('Examples: The format <code>{0:0>4d}</code> ' + 'gives a 4-digit number with leading zeros. The format ' + '<code>{0:d} days</code> prints the number then the word "days"')+ '</p>') + elif col_type == 'float': + self.number_format_box.setToolTip('<p>' + + _('Examples: The format <code>{0:.1f}</code> gives a floating ' + 'point number with 1 digit after the decimal point. The format ' + '<code>Price: $ {0:,.2f}</code> prints ' + '"Price $ " then displays the number with 2 digits ' + 'after the decimal point and thousands separated by commas.') + '</p>') def accept(self): col = unicode(self.column_name_box.text()).strip() if not col: return self.simple_error('', _('No lookup name was provided')) + if col.startswith('#'): + col = col[1:] if re.match('^\w*$', col) is None or not col[0].isalpha() or col.lower() != col: return self.simple_error('', _('The lookup name must contain only ' 'lower case letters, digits and underscores, and start with a letter')) @@ -123,17 +209,20 @@ class CreateCustomColumn(QDialog, Ui_QCreateCustomColumn): 'because these names are reserved for the index of a series column.')) col_heading = unicode(self.column_heading_box.text()).strip() col_type = self.column_types[self.column_type_box.currentIndex()]['datatype'] - if col_type == '*text': - col_type='text' + if col_type[0] == '*': + col_type = col_type[1:] is_multiple = True else: is_multiple = False if not col_heading: return self.simple_error('', _('No column heading was provided')) + + db = self.parent.gui.library_view.model().db + key = db.field_metadata.custom_field_prefix+col bad_col = False - if col in self.parent.custcols: + if key in self.parent.custcols: if not self.editing_col or \ - self.parent.custcols[col]['colnum'] != self.orig_column_number: + self.parent.custcols[key]['colnum'] != self.orig_column_number: bad_col = True if bad_col: return self.simple_error('', _('The lookup name %s is already used')%col) @@ -161,7 +250,11 @@ class CreateCustomColumn(QDialog, Ui_QCreateCustomColumn): if not unicode(self.composite_box.text()).strip(): return self.simple_error('', _('You must enter a template for' ' composite columns')) - display_dict = {'composite_template':unicode(self.composite_box.text()).strip()} + display_dict = {'composite_template':unicode(self.composite_box.text()).strip(), + 'composite_sort': ['text', 'number', 'date', 'bool'] + [self.composite_sort_by.currentIndex()], + 'make_category': self.composite_make_category.isChecked(), + } elif col_type == 'enumeration': if not unicode(self.enum_box.text()).strip(): return self.simple_error('', _('You must enter at least one' @@ -174,12 +267,32 @@ class CreateCustomColumn(QDialog, Ui_QCreateCustomColumn): if l[i] in l[i+1:]: return self.simple_error('', _('The value "{0}" is in the ' 'list more than once').format(l[i])) - display_dict = {'enum_values': l} + c = unicode(self.enum_colors.text()) + if c: + c = [v.strip() for v in unicode(self.enum_colors.text()).split(',')] + else: + c = [] + if len(c) != 0 and len(c) != len(l): + return self.simple_error('', _('The colors box must be empty or ' + 'contain the same number of items as the value box')) + for tc in c: + if tc not in QColor.colorNames(): + return self.simple_error('', + _('The color {0} is unknown').format(tc)) + + display_dict = {'enum_values': l, 'enum_colors': c} + elif col_type == 'text' and is_multiple: + display_dict = {'is_names': self.is_names.isChecked()} + elif col_type in ['int', 'float']: + if unicode(self.number_format_box.text()).strip(): + display_dict = {'number_format':unicode(self.number_format_box.text()).strip()} + else: + display_dict = {'number_format': None} + + if col_type in ['text', 'composite', 'enumeration'] and not is_multiple: + display_dict['use_decorations'] = self.use_decorations.checkState() - db = self.parent.gui.library_view.model().db - key = db.field_metadata.custom_field_prefix+col if not self.editing_col: - db.field_metadata self.parent.custcols[key] = { 'label':col, 'name':col_heading, diff --git a/src/calibre/gui2/preferences/create_custom_column.ui b/src/calibre/gui2/preferences/create_custom_column.ui index d4e85a24c9..cedbfd72b8 100644 --- a/src/calibre/gui2/preferences/create_custom_column.ui +++ b/src/calibre/gui2/preferences/create_custom_column.ui @@ -9,8 +9,8 @@ <rect> <x>0</x> <y>0</y> - <width>528</width> - <height>212</height> + <width>831</width> + <height>344</height> </rect> </property> <property name="sizePolicy"> @@ -19,19 +19,20 @@ <verstretch>0</verstretch> </sizepolicy> </property> - <property name="windowTitle"> - <string>Create or edit custom columns</string> + <property name="windowIcon"> + <iconset resource="../../../../resources/images.qrc"> + <normaloff>:/images/column.png</normaloff>:/images/column.png</iconset> </property> <layout class="QVBoxLayout" name="verticalLayout"> <item> - <layout class="QGridLayout" name="gridLayout_2" rowstretch="0,0,0,0,0,0,0,0,0,0,0,0"> + <layout class="QGridLayout" name="gridLayout_2" rowstretch="0,0,0,0,0,0,0,0,0,0,0,0,0,0,0"> <property name="sizeConstraint"> <enum>QLayout::SetDefaultConstraint</enum> </property> <property name="margin"> <number>5</number> </property> - <item row="2" column="0"> + <item row="5" column="0"> <layout class="QGridLayout" name="gridLayout"> <property name="margin"> <number>0</number> @@ -79,7 +80,7 @@ <item row="2" column="0"> <widget class="QLabel" name="label_3"> <property name="text"> - <string>Column &type</string> + <string>&Column type</string> </property> <property name="buddy"> <cstring>column_type_box</cstring> @@ -87,23 +88,68 @@ </widget> </item> <item row="2" column="2"> - <widget class="QComboBox" name="column_type_box"> - <property name="sizePolicy"> - <sizepolicy hsizetype="Maximum" vsizetype="Fixed"> - <horstretch>0</horstretch> - <verstretch>0</verstretch> - </sizepolicy> - </property> - <property name="minimumSize"> - <size> - <width>70</width> - <height>0</height> - </size> - </property> - <property name="toolTip"> - <string>What kind of information will be kept in the column.</string> - </property> - </widget> + <layout class="QHBoxLayout" name="horizontalLayout_3"> + <item> + <widget class="QComboBox" name="column_type_box"> + <property name="sizePolicy"> + <sizepolicy hsizetype="Maximum" vsizetype="Fixed"> + <horstretch>0</horstretch> + <verstretch>0</verstretch> + </sizepolicy> + </property> + <property name="minimumSize"> + <size> + <width>70</width> + <height>0</height> + </size> + </property> + <property name="toolTip"> + <string>What kind of information will be kept in the column.</string> + </property> + </widget> + </item> + <item> + <widget class="QCheckBox" name="use_decorations"> + <property name="toolTip"> + <string>Show check marks in the GUI. Values of 'yes', 'checked', and 'true' +will show a green check. Values of 'no', 'unchecked', and 'false' will show a red X. +Everything else will show nothing.</string> + </property> + <property name="text"> + <string>Show checkmarks</string> + </property> + </widget> + </item> + <item> + <widget class="QCheckBox" name="is_names"> + <property name="toolTip"> + <string>Check this box if this column contains names, like the authors column.</string> + </property> + <property name="text"> + <string>Contains names</string> + </property> + </widget> + </item> + <item> + <spacer name="horizontalSpacer_27"> + <property name="sizePolicy"> + <sizepolicy hsizetype="Expanding" vsizetype="Fixed"> + <horstretch>10</horstretch> + <verstretch>0</verstretch> + </sizepolicy> + </property> + <property name="orientation"> + <enum>Qt::Horizontal</enum> + </property> + <property name="sizeHint" stdset="0"> + <size> + <width>20</width> + <height>0</height> + </size> + </property> + </spacer> + </item> + </layout> </item> <item row="4" column="2"> <layout class="QHBoxLayout" name="horizontalLayout_3"> @@ -125,6 +171,16 @@ </property> </widget> </item> + <item> + <widget class="QLineEdit" name="number_format_box"> + <property name="sizePolicy"> + <sizepolicy hsizetype="Maximum" vsizetype="Fixed"> + <horstretch>0</horstretch> + <verstretch>0</verstretch> + </sizepolicy> + </property> + </widget> + </item> <item> <widget class="QLabel" name="date_format_default_label"> <property name="toolTip"> @@ -135,6 +191,21 @@ </property> </widget> </item> + <item> + <widget class="QLabel" name="number_format_default_label"> + <property name="toolTip"> + <string><p>The format specifier must begin with <code>{0:</code> +and end with <code>}</code> You can have text before and after the format specifier. + </string> + </property> + <property name="text"> + <string><p>Default: Not formatted. For format language details see <a href="http://docs.python.org/library/string.html#format-string-syntax">the python documentation</a></string> + </property> + <property name="openExternalLinks"> + <bool>true</bool> + </property> + </widget> + </item> </layout> </item> <item row="4" column="0"> @@ -147,6 +218,26 @@ </property> </widget> </item> + <item row="4" column="0"> + <widget class="QLabel" name="number_format_label"> + <property name="text"> + <string>Format for &numbers</string> + </property> + <property name="buddy"> + <cstring>number_format_box</cstring> + </property> + </widget> + </item> + <item row="5" column="0"> + <widget class="QLabel" name="composite_label"> + <property name="text"> + <string>&Template</string> + </property> + <property name="buddy"> + <cstring>composite_box</cstring> + </property> + </widget> + </item> <item row="5" column="2"> <layout class="QHBoxLayout" name="horizontalLayout_4"> <item> @@ -174,15 +265,55 @@ </item> </layout> </item> - <item row="5" column="0"> - <widget class="QLabel" name="composite_label"> - <property name="text"> - <string>&Template</string> - </property> - <property name="buddy"> - <cstring>composite_box</cstring> - </property> - </widget> + <item row="6" column="2"> + <layout class="QHBoxLayout" name="composite_layout"> + <item> + <widget class="QLabel" name="composite_sort_by_label"> + <property name="text"> + <string>&Sort/search column by</string> + </property> + <property name="buddy"> + <cstring>composite_sort_by</cstring> + </property> + </widget> + </item> + <item> + <widget class="QComboBox" name="composite_sort_by"> + <property name="toolTip"> + <string>How this column should handled in the GUI when sorting and searching</string> + </property> + </widget> + </item> + <item> + <widget class="QCheckBox" name="composite_make_category"> + <property name="toolTip"> + <string>If checked, this column will appear in the tags browser as a category</string> + </property> + <property name="text"> + <string>Show in tags browser</string> + </property> + </widget> + </item> + <item> + <spacer name="horizontalSpacer_24"> + <property name="sizePolicy"> + <sizepolicy hsizetype="Expanding" vsizetype="Fixed"> + <horstretch>10</horstretch> + <verstretch>0</verstretch> + </sizepolicy> + </property> + <property name="orientation"> + <enum>Qt::Horizontal</enum> + </property> + <property name="sizeHint" stdset="0"> + <size> + <width>20</width> + <height>0</height> + </size> + </property> + </spacer> + </item> + </layout> </item> <item row="11" column="0" colspan="4"> <spacer name="verticalSpacer_2"> @@ -208,8 +339,8 @@ </widget> </item> <item row="6" column="2"> - <layout class="QHBoxLayout" name="horizontalLayout_2"> - <item> + <layout class="QGridLayout" name="horizontalLayout_2"> + <item row="0" column="0"> <widget class="QLineEdit" name="enum_box"> <property name="sizePolicy"> <sizepolicy hsizetype="Expanding" vsizetype="Fixed"> @@ -224,13 +355,34 @@ four values, the first of them being the empty value.</string> </property> </widget> </item> - <item> + <item row="0" column="1"> <widget class="QLabel" name="enum_default_label"> <property name="toolTip"> <string>The empty string is always the first value</string> </property> <property name="text"> - <string>Default: (nothing)</string> + <string>Values</string> + </property> + </widget> + </item> + <item row="1" column="0"> + <widget class="QLineEdit" name="enum_colors"> + <property name="sizePolicy"> + <sizepolicy hsizetype="Expanding" vsizetype="Fixed"> + <horstretch>0</horstretch> + <verstretch>0</verstretch> + </sizepolicy> + </property> + <property name="toolTip"> + <string>A list of color names to use when displaying an item. The +list must be empty or contain a color for each value.</string> + </property> + </widget> + </item> + <item row="1" column="1"> + <widget class="QLabel" name="enum_colors_label"> + <property name="text"> + <string>Colors</string> </property> </widget> </item> @@ -238,7 +390,7 @@ four values, the first of them being the empty value.</string> </item> </layout> </item> - <item row="11" column="0"> + <item row="14" column="0"> <widget class="QDialogButtonBox" name="button_box"> <property name="orientation"> <enum>Qt::Horizontal</enum> @@ -252,7 +404,7 @@ four values, the first of them being the empty value.</string> </widget> </item> <item row="1" column="0"> - <widget class="QLabel" name="label_6"> + <widget class="QLabel" name="heading_label"> <property name="font"> <font> <weight>75</weight> @@ -260,7 +412,31 @@ four values, the first of them being the empty value.</string> </font> </property> <property name="text"> - <string>Create or edit custom columns</string> + <string/> + </property> + </widget> + </item> + <item row="3" column="0"> + <widget class="QLabel" name="shortcuts"> + <property name="text"> + <string/> + </property> + <property name="wordWrap"> + <bool>true</bool> + </property> + </widget> + </item> + <item row="2" column="0"> + <widget class="Line" name="line"> + <property name="orientation"> + <enum>Qt::Vertical</enum> + </property> + </widget> + </item> + <item row="4" column="0"> + <widget class="Line" name="line_2"> + <property name="orientation"> + <enum>Qt::Vertical</enum> </property> </widget> </item> @@ -276,6 +452,8 @@ four values, the first of them being the empty value.</string> <tabstop>composite_box</tabstop> <tabstop>button_box</tabstop> </tabstops> - <resources/> + <resources> + <include location="../../../../resources/images.qrc"/> + </resources> <connections/> </ui> diff --git a/src/calibre/gui2/preferences/device_user_defined.py b/src/calibre/gui2/preferences/device_user_defined.py new file mode 100644 index 0000000000..13721689f1 --- /dev/null +++ b/src/calibre/gui2/preferences/device_user_defined.py @@ -0,0 +1,105 @@ +#!/usr/bin/env python +# vim:fileencoding=UTF-8:ts=4:sw=4:sta:et:sts=4:ai +from __future__ import with_statement + +__license__ = 'GPL v3' +__copyright__ = '2009, Kovid Goyal <kovid@kovidgoyal.net>' +__docformat__ = 'restructuredtext en' + + +from PyQt4.Qt import QDialog, QVBoxLayout, QPlainTextEdit, QTimer, \ + QDialogButtonBox, QPushButton, QApplication, QIcon, QMessageBox + +from calibre.constants import iswindows + +def step_dialog(parent, title, msg, det_msg=''): + d = QMessageBox(parent) + d.setWindowTitle(title) + d.setText(msg) + d.setStandardButtons(QMessageBox.Ok | QMessageBox.Cancel) + return d.exec_() & QMessageBox.Cancel + + +class UserDefinedDevice(QDialog): + + def __init__(self, parent=None): + QDialog.__init__(self, parent) + self._layout = QVBoxLayout(self) + self.setLayout(self._layout) + self.log = QPlainTextEdit(self) + self._layout.addWidget(self.log) + self.log.setPlainText(_('Getting device information')+'...') + self.copy = QPushButton(_('Copy to &clipboard')) + self.copy.setDefault(True) + self.setWindowTitle(_('User-defined device information')) + self.setWindowIcon(QIcon(I('debug.png'))) + self.copy.clicked.connect(self.copy_to_clipboard) + self.ok = QPushButton('&OK') + self.ok.setAutoDefault(False) + self.ok.clicked.connect(self.accept) + self.bbox = QDialogButtonBox(self) + self.bbox.addButton(self.copy, QDialogButtonBox.ActionRole) + self.bbox.addButton(self.ok, QDialogButtonBox.AcceptRole) + self._layout.addWidget(self.bbox) + self.resize(750, 500) + self.bbox.setEnabled(False) + QTimer.singleShot(1000, self.device_info) + + def device_info(self): + try: + from calibre.devices import device_info + r = step_dialog(self.parent(), _('Device Detection'), + _('Ensure your device is disconnected, then press OK')) + if r: + self.close() + return + before = device_info() + r = step_dialog(self.parent(), _('Device Detection'), + _('Ensure your device is connected, then press OK')) + if r: + self.close() + return + after = device_info() + new_drives = after['drive_set'] - before['drive_set'] + new_devices = after['device_set'] - before['device_set'] + res = '' + if (not iswindows or len(new_drives)) and len(new_devices) == 1: + for d in new_devices: + res = _('USB Vendor ID (in hex)') + ': 0x' + \ + after['device_details'][d][0] + '\n' + res += _('USB Product ID (in hex)') + ': 0x' + \ + after['device_details'][d][1] + '\n' + res += _('USB Revision ID (in hex)') + ': 0x' + \ + after['device_details'][d][2] + '\n' + if iswindows: + # sort the drives by the order number + for i,d in enumerate(sorted(new_drives, + key=lambda x: after['drive_details'][x][0])): + if i == 0: + res += _('Windows main memory vendor string') + ': ' + \ + after['drive_details'][d][1] + '\n' + res += _('Windows main memory ID string') + ': ' + \ + after['drive_details'][d][2] + '\n' + else: + res += _('Windows card A vendor string') + ': ' + \ + after['drive_details'][d][1] + '\n' + res += _('Windows card A ID string') + ': ' + \ + after['drive_details'][d][2] + '\n' + trailer = _( + 'Copy these values to the clipboard, paste them into an ' + 'editor, then enter them into the USER_DEVICE by ' + 'customizing the device plugin in Preferences->Plugins. ' + 'Remember to also enter the folders where you want the books to ' + 'be put. You must restart calibre for your changes ' + 'to take effect.\n') + self.log.setPlainText(res + '\n\n' + trailer) + finally: + self.bbox.setEnabled(True) + + def copy_to_clipboard(self): + QApplication.clipboard().setText(self.log.toPlainText()) + +if __name__ == '__main__': + app = QApplication([]) + d = UserDefinedDevice() + d.exec_() diff --git a/src/calibre/gui2/preferences/emailp.py b/src/calibre/gui2/preferences/emailp.py index 19007dfcf1..1256691c22 100644 --- a/src/calibre/gui2/preferences/emailp.py +++ b/src/calibre/gui2/preferences/emailp.py @@ -5,6 +5,8 @@ __license__ = 'GPL v3' __copyright__ = '2010, Kovid Goyal <kovid@kovidgoyal.net>' __docformat__ = 'restructuredtext en' +import textwrap + from PyQt4.Qt import QAbstractTableModel, QVariant, QFont, Qt @@ -17,25 +19,30 @@ from calibre.utils.smtp import config as smtp_prefs class EmailAccounts(QAbstractTableModel): # {{{ - def __init__(self, accounts): + def __init__(self, accounts, subjects): QAbstractTableModel.__init__(self) self.accounts = accounts + self.subjects = subjects self.account_order = sorted(self.accounts.keys()) - self.headers = map(QVariant, [_('Email'), _('Formats'), _('Auto send')]) + self.headers = map(QVariant, [_('Email'), _('Formats'), _('Subject'), _('Auto send')]) self.default_font = QFont() self.default_font.setBold(True) self.default_font = QVariant(self.default_font) - self.tooltips =[NONE] + map(QVariant, + self.tooltips =[NONE] + list(map(QVariant, map(textwrap.fill, [_('Formats to email. The first matching format will be sent.'), + _('Subject of the email to use when sending. When left blank ' + 'the title will be used for the subject. Also, the same ' + 'templates used for "Save to disk" such as {title} and ' + '{author_sort} can be used here.'), '<p>'+_('If checked, downloaded news will be automatically ' 'mailed <br>to this email address ' - '(provided it is in one of the listed formats).')]) + '(provided it is in one of the listed formats).')]))) def rowCount(self, *args): return len(self.account_order) def columnCount(self, *args): - return 3 + return len(self.headers) def headerData(self, section, orientation, role): if role == Qt.DisplayRole and orientation == Qt.Horizontal: @@ -56,14 +63,16 @@ class EmailAccounts(QAbstractTableModel): # {{{ return QVariant(account) if col == 1: return QVariant(self.accounts[account][0]) + if col == 2: + return QVariant(self.subjects.get(account, '')) if role == Qt.FontRole and self.accounts[account][2]: return self.default_font - if role == Qt.CheckStateRole and col == 2: + if role == Qt.CheckStateRole and col == 3: return QVariant(Qt.Checked if self.accounts[account][1] else Qt.Unchecked) return NONE def flags(self, index): - if index.column() == 2: + if index.column() == 3: return QAbstractTableModel.flags(self, index)|Qt.ItemIsUserCheckable else: return QAbstractTableModel.flags(self, index)|Qt.ItemIsEditable @@ -73,11 +82,13 @@ class EmailAccounts(QAbstractTableModel): # {{{ return False row, col = index.row(), index.column() account = self.account_order[row] - if col == 2: + if col == 3: self.accounts[account][1] ^= True + elif col == 2: + self.subjects[account] = unicode(value.toString()) elif col == 1: self.accounts[account][0] = unicode(value.toString()).upper() - else: + elif col == 0: na = unicode(value.toString()) from email.utils import parseaddr addr = parseaddr(na)[-1] @@ -89,7 +100,7 @@ class EmailAccounts(QAbstractTableModel): # {{{ self.accounts[na][0] = 'AZW, MOBI, TPZ, PRC, AZW1' self.dataChanged.emit( - self.index(index.row(), 0), self.index(index.row(), 2)) + self.index(index.row(), 0), self.index(index.row(), 3)) return True def make_default(self, index): @@ -143,7 +154,7 @@ class ConfigWidget(ConfigWidgetBase, Ui_Form): self.send_email_widget.initialize(self.preferred_to_address) self.send_email_widget.changed_signal.connect(self.changed_signal.emit) opts = self.send_email_widget.smtp_opts - self._email_accounts = EmailAccounts(opts.accounts) + self._email_accounts = EmailAccounts(opts.accounts, opts.subjects) self._email_accounts.dataChanged.connect(lambda x,y: self.changed_signal.emit()) self.email_view.setModel(self._email_accounts) @@ -170,6 +181,7 @@ class ConfigWidget(ConfigWidgetBase, Ui_Form): if not self.send_email_widget.set_email_settings(to_set): raise AbortCommit('abort') self.proxy['accounts'] = self._email_accounts.accounts + self.proxy['subjects'] = self._email_accounts.subjects return ConfigWidgetBase.commit(self) @@ -190,7 +202,8 @@ class ConfigWidget(ConfigWidgetBase, Ui_Form): self.changed_signal.emit() def refresh_gui(self, gui): - gui.emailer.calculate_rate_limit() + from calibre.gui2.email import gui_sendmail + gui_sendmail.calculate_rate_limit() if __name__ == '__main__': diff --git a/src/calibre/gui2/preferences/look_feel.py b/src/calibre/gui2/preferences/look_feel.py index f7d76f2b70..7a8c1fb69c 100644 --- a/src/calibre/gui2/preferences/look_feel.py +++ b/src/calibre/gui2/preferences/look_feel.py @@ -5,15 +5,94 @@ __license__ = 'GPL v3' __copyright__ = '2010, Kovid Goyal <kovid@kovidgoyal.net>' __docformat__ = 'restructuredtext en' -from PyQt4.Qt import QApplication, QFont, QFontInfo, QFontDialog +from functools import partial + +from PyQt4.Qt import (QApplication, QFont, QFontInfo, QFontDialog, + QAbstractListModel, Qt, QColor, QIcon, QToolButton, QComboBox) from calibre.gui2.preferences import ConfigWidgetBase, test_widget, CommaSeparatedList from calibre.gui2.preferences.look_feel_ui import Ui_Form from calibre.gui2 import config, gprefs, qt_app -from calibre.utils.localization import available_translations, \ - get_language, get_lang +from calibre.gui2.dialogs.template_line_editor import TemplateLineEditor +from calibre.utils.localization import (available_translations, + get_language, get_lang) from calibre.utils.config import prefs from calibre.utils.icu import sort_key +from calibre.gui2 import NONE +from calibre.gui2.book_details import get_field_list + +class DisplayedFields(QAbstractListModel): # {{{ + + def __init__(self, db, parent=None): + QAbstractListModel.__init__(self, parent) + + self.fields = [] + self.db = db + self.changed = False + + def initialize(self, use_defaults=False): + self.fields = [[x[0], x[1]] for x in + get_field_list(self.db.field_metadata, + use_defaults=use_defaults)] + self.reset() + self.changed = True + + def rowCount(self, *args): + return len(self.fields) + + def data(self, index, role): + try: + field, visible = self.fields[index.row()] + except: + return NONE + if role == Qt.DisplayRole: + name = field + try: + name = self.db.field_metadata[field]['name'] + except: + pass + if not name: + name = field + return name + if role == Qt.CheckStateRole: + return Qt.Checked if visible else Qt.Unchecked + return NONE + + def flags(self, index): + ans = QAbstractListModel.flags(self, index) + return ans | Qt.ItemIsUserCheckable + + def setData(self, index, val, role): + ret = False + if role == Qt.CheckStateRole: + val, ok = val.toInt() + if ok: + self.fields[index.row()][1] = bool(val) + self.changed = True + ret = True + self.dataChanged.emit(index, index) + return ret + + def restore_defaults(self): + self.initialize(use_defaults=True) + + def commit(self): + if self.changed: + gprefs['book_display_fields'] = self.fields + + def move(self, idx, delta): + row = idx.row() + delta + if row >= 0 and row < len(self.fields): + t = self.fields[row] + self.fields[row] = self.fields[row-delta] + self.fields[row-delta] = t + self.dataChanged.emit(idx, idx) + idx = self.index(row) + self.dataChanged.emit(idx, idx) + self.changed = True + return idx + +# }}} class ConfigWidget(ConfigWidgetBase, Ui_Form): @@ -48,13 +127,12 @@ class ConfigWidget(ConfigWidgetBase, Ui_Form): r('disable_tray_notification', config) r('use_roman_numerals_for_series_number', config) r('separate_cover_flow', config, restart_required=True) - r('show_child_bar', gprefs) - choices = [(_('Small'), 'small'), (_('Medium'), 'medium'), - (_('Large'), 'large')] + choices = [(_('Off'), 'off'), (_('Small'), 'small'), + (_('Medium'), 'medium'), (_('Large'), 'large')] r('toolbar_icon_size', gprefs, choices=choices) - choices = [(_('Automatic'), 'auto'), (_('Always'), 'always'), + choices = [(_('If there is enough room'), 'auto'), (_('Always'), 'always'), (_('Never'), 'never')] r('toolbar_text', gprefs, choices=choices) @@ -64,22 +142,147 @@ class ConfigWidget(ConfigWidgetBase, Ui_Form): r('tags_browser_collapse_at', gprefs) choices = set([k for k in db.field_metadata.all_field_keys() - if db.field_metadata[k]['is_category'] and - db.field_metadata[k]['datatype'] in ['text', 'series', 'enumeration']]) - choices -= set(['authors', 'publisher', 'formats', 'news']) + if db.field_metadata[k]['is_category'] and + (db.field_metadata[k]['datatype'] in ['text', 'series', 'enumeration']) and + not db.field_metadata[k]['display'].get('is_names', False)]) + choices -= set(['authors', 'publisher', 'formats', 'news', 'identifiers']) + choices |= set(['search']) self.opt_categories_using_hierarchy.update_items_cache(choices) r('categories_using_hierarchy', db.prefs, setting=CommaSeparatedList, choices=sorted(list(choices), key=sort_key)) - self.current_font = None + self.current_font = self.initial_font = None self.change_font_button.clicked.connect(self.change_font) + self.display_model = DisplayedFields(self.gui.current_db, + self.field_display_order) + self.display_model.dataChanged.connect(self.changed_signal) + self.field_display_order.setModel(self.display_model) + self.df_up_button.clicked.connect(self.move_df_up) + self.df_down_button.clicked.connect(self.move_df_down) + + self.color_help_text.setText('<p>' + + _('Here you can specify coloring rules for columns shown in the ' + 'library view. Choose the column you wish to color, then ' + 'supply a template that specifies the color to use based on ' + 'the values in the column. There is a ' + '<a href="http://manual.calibre-ebook.com/template_lang.html">' + 'tutorial</a> on using templates.') + + '</p><p>' + + _('If you want to color a field based on contents of columns, ' + 'then click the button next to an empty line to open the wizard. ' + 'It will build a template for you. You can later edit that ' + 'template with the same wizard. This is by far the easiest ' + 'way to specify a template.') + + '</p><p>' + + _('If you manually construct a template, then the template must ' + 'evaluate to a valid color name shown in the color names box.' + 'You can use any legal template expression. ' + 'For example, you can set the title to always display in ' + 'green using the template "green" (without the quotes). ' + 'To show the title in the color named in the custom column ' + '#column, use "{#column}". To show the title in blue if the ' + 'custom column #column contains the value "foo", in red if the ' + 'column contains the value "bar", otherwise in black, use ' + '<pre>{#column:switch(foo,blue,bar,red,black)}</pre>' + 'To show the title in blue if the book has the exact tag ' + '"Science Fiction", red if the book has the exact tag ' + '"Mystery", or black if the book has neither tag, use' + "<pre>program: \n" + " t = field('tags'); \n" + " first_non_empty(\n" + " in_list(t, ',', '^Science Fiction$', 'blue', ''), \n" + " in_list(t, ',', '^Mystery$', 'red', 'black'))</pre>" + 'To show the title in green if it has one format, blue if it ' + 'two formats, and red if more, use' + "<pre>program:cmp(count(field('formats'),','), 2, 'green', 'blue', 'red')</pre>") + + '</p><p>' + + _('You can access a multi-line template editor from the ' + 'context menu (right-click).') + '</p><p>' + + _('<b>Note:</b> if you want to color a "custom column with a fixed set ' + 'of values", it is often easier to specify the ' + 'colors in the column definition dialog. There you can ' + 'provide a color for each value without using a template.')+ '</p>') + self.color_help_scrollArea.setVisible(False) + self.color_help_button.clicked.connect(self.change_help_text) + self.colors_scrollArea.setVisible(False) + self.colors_label.setVisible(False) + self.colors_button.clicked.connect(self.change_colors_text) + + choices = db.field_metadata.displayable_field_keys() + choices.sort(key=sort_key) + choices.insert(0, '') + self.column_color_count = db.column_color_count+1 + + mi=None + try: + idx = gui.library_view.currentIndex().row() + mi = db.get_metadata(idx, index_is_id=False) + except: + pass + + l = self.column_color_layout + for i in range(1, self.column_color_count): + ccn = QComboBox(parent=self) + setattr(self, 'opt_column_color_name_'+str(i), ccn) + l.addWidget(ccn, i, 0, 1, 1) + + wtb = QToolButton(parent=self) + setattr(self, 'opt_column_color_wizard_'+str(i), wtb) + wtb.setIcon(QIcon(I('wizard.png'))) + l.addWidget(wtb, i, 1, 1, 1) + + ttb = QToolButton(parent=self) + setattr(self, 'opt_column_color_tpledit_'+str(i), ttb) + ttb.setIcon(QIcon(I('edit_input.png'))) + l.addWidget(ttb, i, 2, 1, 1) + + tpl = TemplateLineEditor(parent=self) + setattr(self, 'opt_column_color_template_'+str(i), tpl) + tpl.textChanged.connect(partial(self.tpl_edit_text_changed, ctrl=i)) + tpl.set_db(db) + tpl.set_mi(mi) + l.addWidget(tpl, i, 3, 1, 1) + + wtb.clicked.connect(tpl.tag_wizard) + ttb.clicked.connect(tpl.open_editor) + + r('column_color_name_'+str(i), db.prefs, choices=choices) + r('column_color_template_'+str(i), db.prefs) + txt = db.prefs.get('column_color_template_'+str(i), None) + + wtb.setEnabled(tpl.enable_wizard_button(txt)) + ttb.setEnabled(not tpl.enable_wizard_button(txt) or not txt) + + all_colors = [unicode(s) for s in list(QColor.colorNames())] + self.colors_box.setText(', '.join(all_colors)) + + def change_help_text(self): + self.color_help_scrollArea.setVisible(not self.color_help_scrollArea.isVisible()) + + def change_colors_text(self): + self.colors_scrollArea.setVisible(not self.colors_scrollArea.isVisible()) + self.colors_label.setVisible(not self.colors_label.isVisible()) + + def tpl_edit_text_changed(self, ign, ctrl=None): + tpl = getattr(self, 'opt_column_color_template_'+str(ctrl)) + txt = unicode(tpl.text()) + wtb = getattr(self, 'opt_column_color_wizard_'+str(ctrl)) + ttb = getattr(self, 'opt_column_color_tpledit_'+str(ctrl)) + wtb.setEnabled(tpl.enable_wizard_button(txt)) + ttb.setEnabled(not tpl.enable_wizard_button(txt) or not txt) + tpl.setFocus() def initialize(self): ConfigWidgetBase.initialize(self) - self.current_font = gprefs['font'] + font = gprefs['font'] + if font is not None: + font = list(font) + font.append(gprefs.get('font_stretch', QFont.Unstretched)) + self.current_font = self.initial_font = font self.update_font_display() + self.display_model.initialize() def restore_defaults(self): ConfigWidgetBase.restore_defaults(self) @@ -88,11 +291,14 @@ class ConfigWidget(ConfigWidgetBase, Ui_Form): if ofont is not None: self.changed_signal.emit() self.update_font_display() + self.display_model.restore_defaults() + self.changed_signal.emit() def build_font_obj(self): font_info = self.current_font if font_info is not None: - font = QFont(*font_info) + font = QFont(*(font_info[:4])) + font.setStretch(font_info[4]) else: font = qt_app.original_font return font @@ -106,30 +312,60 @@ class ConfigWidget(ConfigWidgetBase, Ui_Form): self.font_display.setText(name + ' [%dpt]'%fi.pointSize()) + def move_df_up(self): + idx = self.field_display_order.currentIndex() + if idx.isValid(): + idx = self.display_model.move(idx, -1) + if idx is not None: + sm = self.field_display_order.selectionModel() + sm.select(idx, sm.ClearAndSelect) + self.field_display_order.setCurrentIndex(idx) + + def move_df_down(self): + idx = self.field_display_order.currentIndex() + if idx.isValid(): + idx = self.display_model.move(idx, 1) + if idx is not None: + sm = self.field_display_order.selectionModel() + sm.select(idx, sm.ClearAndSelect) + self.field_display_order.setCurrentIndex(idx) + def change_font(self, *args): fd = QFontDialog(self.build_font_obj(), self) if fd.exec_() == fd.Accepted: font = fd.selectedFont() fi = QFontInfo(font) - self.current_font = (unicode(fi.family()), fi.pointSize(), - fi.weight(), fi.italic()) + self.current_font = [unicode(fi.family()), fi.pointSize(), + fi.weight(), fi.italic(), font.stretch()] self.update_font_display() self.changed_signal.emit() def commit(self, *args): + for i in range(1, self.column_color_count): + col = getattr(self, 'opt_column_color_name_'+str(i)) + tpl = getattr(self, 'opt_column_color_template_'+str(i)) + if not col.currentIndex() or not unicode(tpl.text()).strip(): + col.setCurrentIndex(0) + tpl.setText('') rr = ConfigWidgetBase.commit(self, *args) - if self.current_font != gprefs['font']: - gprefs['font'] = self.current_font + if self.current_font != self.initial_font: + gprefs['font'] = (self.current_font[:4] if self.current_font else + None) + gprefs['font_stretch'] = (self.current_font[4] if self.current_font + is not None else QFont.Unstretched) QApplication.setFont(self.font_display.font()) rr = True + self.display_model.commit() return rr - def refresh_gui(self, gui): + gui.library_view.model().set_color_templates() self.update_font_display() gui.tags_view.reread_collapse_parameters() + gui.library_view.refresh_book_details() if __name__ == '__main__': - app = QApplication([]) + from calibre.gui2 import Application + app = Application([]) test_widget('Interface', 'Look & Feel') diff --git a/src/calibre/gui2/preferences/look_feel.ui b/src/calibre/gui2/preferences/look_feel.ui index bc965b89fa..def1bdd41c 100644 --- a/src/calibre/gui2/preferences/look_feel.ui +++ b/src/calibre/gui2/preferences/look_feel.ui @@ -6,8 +6,8 @@ <rect> <x>0</x> <y>0</y> - <width>670</width> - <height>422</height> + <width>717</width> + <height>519</height> </rect> </property> <property name="windowTitle"> @@ -15,286 +15,555 @@ </property> <layout class="QGridLayout" name="gridLayout_2"> <item row="0" column="0"> - <widget class="QLabel" name="label_17"> - <property name="text"> - <string>User Interface &layout (needs restart):</string> + <widget class="QTabWidget" name="tabWidget"> + <property name="currentIndex"> + <number>0</number> </property> - <property name="buddy"> - <cstring>opt_gui_layout</cstring> - </property> - </widget> - </item> - <item row="0" column="1"> - <widget class="QComboBox" name="opt_gui_layout"> - <property name="maximumSize"> - <size> - <width>250</width> - <height>16777215</height> - </size> - </property> - <property name="sizeAdjustPolicy"> - <enum>QComboBox::AdjustToMinimumContentsLengthWithIcon</enum> - </property> - <property name="minimumContentsLength"> - <number>20</number> - </property> - </widget> - </item> - <item row="1" column="0"> - <widget class="QLabel" name="label_6"> - <property name="text"> - <string>&Number of covers to show in browse mode (needs restart):</string> - </property> - <property name="buddy"> - <cstring>opt_cover_flow_queue_length</cstring> - </property> - </widget> - </item> - <item row="1" column="1"> - <widget class="QSpinBox" name="opt_cover_flow_queue_length"/> - </item> - <item row="2" column="0"> - <widget class="QLabel" name="label_7"> - <property name="text"> - <string>Choose &language (requires restart):</string> - </property> - <property name="buddy"> - <cstring>opt_language</cstring> - </property> - </widget> - </item> - <item row="2" column="1"> - <widget class="QComboBox" name="opt_language"> - <property name="sizeAdjustPolicy"> - <enum>QComboBox::AdjustToMinimumContentsLengthWithIcon</enum> - </property> - <property name="minimumContentsLength"> - <number>20</number> - </property> - </widget> - </item> - <item row="3" column="0"> - <widget class="QCheckBox" name="opt_show_avg_rating"> - <property name="text"> - <string>Show &average ratings in the tags browser</string> - </property> - <property name="checked"> - <bool>true</bool> - </property> - </widget> - </item> - <item row="3" column="1"> - <widget class="QCheckBox" name="opt_disable_animations"> - <property name="toolTip"> - <string>Disable all animations. Useful if you have a slow/old computer.</string> - </property> - <property name="text"> - <string>Disable &animations</string> - </property> - </widget> - </item> - <item row="4" column="0"> - <widget class="QCheckBox" name="opt_systray_icon"> - <property name="text"> - <string>Enable system &tray icon (needs restart)</string> - </property> - </widget> - </item> - <item row="4" column="1"> - <widget class="QCheckBox" name="opt_show_splash_screen"> - <property name="text"> - <string>Show &splash screen at startup</string> - </property> - </widget> - </item> - <item row="5" column="0"> - <widget class="QCheckBox" name="opt_disable_tray_notification"> - <property name="text"> - <string>Disable &notifications in system tray</string> - </property> - </widget> - </item> - <item row="5" column="1"> - <widget class="QCheckBox" name="opt_use_roman_numerals_for_series_number"> - <property name="text"> - <string>Use &Roman numerals for series</string> - </property> - <property name="checked"> - <bool>true</bool> - </property> - </widget> - </item> - <item row="6" column="0" colspan="2"> - <widget class="QCheckBox" name="opt_separate_cover_flow"> - <property name="text"> - <string>Show cover &browser in a separate window (needs restart)</string> - </property> - </widget> - </item> - <item row="7" column="0" colspan="2"> - <layout class="QHBoxLayout"> - <item> - <widget class="QLabel" name="label_6"> - <property name="text"> - <string>Tags browser category &partitioning method:</string> - </property> - <property name="buddy"> - <cstring>opt_tags_browser_partition_method</cstring> - </property> - </widget> - </item> - <item> - <widget class="QComboBox" name="opt_tags_browser_partition_method"> - <property name="toolTip"> - <string>Choose how tag browser subcategories are displayed when + <widget class="QWidget" name="tab"> + <attribute name="icon"> + <iconset resource="../../../../resources/images.qrc"> + <normaloff>:/images/lt.png</normaloff>:/images/lt.png</iconset> + </attribute> + <attribute name="title"> + <string>Main Interface</string> + </attribute> + <layout class="QGridLayout" name="gridLayout_9"> + <item row="0" column="0"> + <widget class="QLabel" name="label_17"> + <property name="text"> + <string>User Interface &layout (needs restart):</string> + </property> + <property name="buddy"> + <cstring>opt_gui_layout</cstring> + </property> + </widget> + </item> + <item row="0" column="1"> + <widget class="QComboBox" name="opt_gui_layout"> + <property name="maximumSize"> + <size> + <width>250</width> + <height>16777215</height> + </size> + </property> + <property name="sizeAdjustPolicy"> + <enum>QComboBox::AdjustToMinimumContentsLengthWithIcon</enum> + </property> + <property name="minimumContentsLength"> + <number>20</number> + </property> + </widget> + </item> + <item row="1" column="0"> + <widget class="QLabel" name="label_7"> + <property name="text"> + <string>Choose &language (requires restart):</string> + </property> + <property name="buddy"> + <cstring>opt_language</cstring> + </property> + </widget> + </item> + <item row="1" column="1"> + <widget class="QComboBox" name="opt_language"> + <property name="sizeAdjustPolicy"> + <enum>QComboBox::AdjustToMinimumContentsLengthWithIcon</enum> + </property> + <property name="minimumContentsLength"> + <number>20</number> + </property> + </widget> + </item> + <item row="2" column="0"> + <widget class="QCheckBox" name="opt_systray_icon"> + <property name="text"> + <string>Enable system &tray icon (needs restart)</string> + </property> + </widget> + </item> + <item row="2" column="1"> + <widget class="QCheckBox" name="opt_disable_animations"> + <property name="toolTip"> + <string>Disable all animations. Useful if you have a slow/old computer.</string> + </property> + <property name="text"> + <string>Disable &animations</string> + </property> + </widget> + </item> + <item row="3" column="0"> + <widget class="QCheckBox" name="opt_disable_tray_notification"> + <property name="text"> + <string>Disable &notifications in system tray</string> + </property> + </widget> + </item> + <item row="3" column="1"> + <widget class="QCheckBox" name="opt_show_splash_screen"> + <property name="text"> + <string>Show &splash screen at startup</string> + </property> + </widget> + </item> + <item row="5" column="0" colspan="2"> + <widget class="QGroupBox" name="groupBox_2"> + <property name="title"> + <string>&Toolbar</string> + </property> + <layout class="QGridLayout" name="gridLayout_8"> + <item row="0" column="1"> + <widget class="QComboBox" name="opt_toolbar_icon_size"/> + </item> + <item row="0" column="0"> + <widget class="QLabel" name="label_5"> + <property name="text"> + <string>&Icon size:</string> + </property> + <property name="buddy"> + <cstring>opt_toolbar_icon_size</cstring> + </property> + </widget> + </item> + <item row="1" column="1"> + <widget class="QComboBox" name="opt_toolbar_text"/> + </item> + <item row="1" column="0"> + <widget class="QLabel" name="label_8"> + <property name="text"> + <string>Show &text under icons:</string> + </property> + <property name="buddy"> + <cstring>opt_toolbar_text</cstring> + </property> + </widget> + </item> + </layout> + </widget> + </item> + <item row="4" column="0"> + <layout class="QHBoxLayout" name="horizontalLayout"> + <item> + <widget class="QLabel" name="label_2"> + <property name="text"> + <string>Interface font:</string> + </property> + <property name="buddy"> + <cstring>font_display</cstring> + </property> + </widget> + </item> + <item> + <widget class="QLineEdit" name="font_display"> + <property name="readOnly"> + <bool>true</bool> + </property> + </widget> + </item> + </layout> + </item> + <item row="4" column="1"> + <widget class="QPushButton" name="change_font_button"> + <property name="text"> + <string>Change &font (needs restart)</string> + </property> + </widget> + </item> + <item row="6" column="0"> + <spacer name="verticalSpacer_3"> + <property name="orientation"> + <enum>Qt::Vertical</enum> + </property> + <property name="sizeHint" stdset="0"> + <size> + <width>20</width> + <height>40</height> + </size> + </property> + </spacer> + </item> + </layout> + </widget> + <widget class="QWidget" name="tab_4"> + <attribute name="icon"> + <iconset resource="../../../../resources/images.qrc"> + <normaloff>:/images/book.png</normaloff>:/images/book.png</iconset> + </attribute> + <attribute name="title"> + <string>Book Details</string> + </attribute> + <layout class="QGridLayout" name="gridLayout_12"> + <item row="0" column="0" rowspan="2"> + <widget class="QGroupBox" name="groupBox"> + <property name="title"> + <string>Select displayed metadata</string> + </property> + <layout class="QGridLayout" name="gridLayout_3"> + <item row="0" column="0" rowspan="3"> + <widget class="QListView" name="field_display_order"> + <property name="alternatingRowColors"> + <bool>true</bool> + </property> + </widget> + </item> + <item row="0" column="1"> + <widget class="QToolButton" name="df_up_button"> + <property name="toolTip"> + <string>Move up</string> + </property> + <property name="icon"> + <iconset resource="../../../../resources/images.qrc"> + <normaloff>:/images/arrow-up.png</normaloff>:/images/arrow-up.png</iconset> + </property> + </widget> + </item> + <item row="2" column="1"> + <widget class="QToolButton" name="df_down_button"> + <property name="toolTip"> + <string>Move down</string> + </property> + <property name="icon"> + <iconset resource="../../../../resources/images.qrc"> + <normaloff>:/images/arrow-down.png</normaloff>:/images/arrow-down.png</iconset> + </property> + </widget> + </item> + <item row="1" column="1"> + <spacer name="verticalSpacer_5"> + <property name="orientation"> + <enum>Qt::Vertical</enum> + </property> + <property name="sizeHint" stdset="0"> + <size> + <width>20</width> + <height>40</height> + </size> + </property> + </spacer> + </item> + </layout> + </widget> + </item> + <item row="0" column="1"> + <widget class="QCheckBox" name="opt_use_roman_numerals_for_series_number"> + <property name="text"> + <string>Use &Roman numerals for series</string> + </property> + <property name="checked"> + <bool>true</bool> + </property> + </widget> + </item> + <item row="1" column="1"> + <widget class="QLabel" name="label_3"> + <property name="text"> + <string>Note that <b>comments</b> will always be displayed at the end, regardless of the position you assign here.</string> + </property> + <property name="wordWrap"> + <bool>true</bool> + </property> + </widget> + </item> + </layout> + </widget> + <widget class="QWidget" name="tab_2"> + <attribute name="icon"> + <iconset resource="../../../../resources/images.qrc"> + <normaloff>:/images/tags.png</normaloff>:/images/tags.png</iconset> + </attribute> + <attribute name="title"> + <string>Tag Browser</string> + </attribute> + <layout class="QGridLayout" name="gridLayout_10"> + <item row="0" column="0" colspan="2"> + <widget class="QLabel" name="label_9"> + <property name="text"> + <string>Tags browser category &partitioning method:</string> + </property> + <property name="buddy"> + <cstring>opt_tags_browser_partition_method</cstring> + </property> + </widget> + </item> + <item row="0" column="2"> + <widget class="QComboBox" name="opt_tags_browser_partition_method"> + <property name="toolTip"> + <string>Choose how tag browser subcategories are displayed when there are more items than the limit. Select by first letter to see an A, B, C list. Choose partitioned to have a list of fixed-sized groups. Set to disabled if you never want subcategories</string> - </property> - </widget> - </item> - <item> - <widget class="QLabel" name="label_6"> - <property name="text"> - <string>&Collapse when more items than:</string> - </property> - <property name="buddy"> - <cstring>opt_tags_browser_collapse_at</cstring> - </property> - </widget> - </item> - <item> - <widget class="QSpinBox" name="opt_tags_browser_collapse_at"> - <property name="toolTip"> - <string>If a Tag Browser category has more than this number of items, it is divided + </property> + </widget> + </item> + <item row="0" column="3"> + <widget class="QLabel" name="label_10"> + <property name="text"> + <string>&Collapse when more items than:</string> + </property> + <property name="buddy"> + <cstring>opt_tags_browser_collapse_at</cstring> + </property> + </widget> + </item> + <item row="0" column="4"> + <widget class="QSpinBox" name="opt_tags_browser_collapse_at"> + <property name="toolTip"> + <string>If a Tag Browser category has more than this number of items, it is divided up into sub-categories. If the partition method is set to disable, this value is ignored.</string> - </property> - <property name="maximum"> - <number>10000</number> - </property> - </widget> - </item> - <item> - <spacer name="verticalSpacer"> - <property name="orientation"> - <enum>Qt::Horizontal</enum> - </property> - <property name="sizeHint" stdset="0"> - <size> - <width>20</width> - <height>5</height> - </size> - </property> - </spacer> - </item> - </layout> - </item> - <item row="8" column="0"> - <widget class="QLabel" name="label_81"> - <property name="text"> - <string>Categories with &hierarchical items:</string> - </property> - <property name="buddy"> - <cstring>opt_categories_using_hierarchy</cstring> - </property> - </widget> - </item> - <item row="8" column="1"> - <widget class="MultiCompleteLineEdit" name="opt_categories_using_hierarchy"> - <property name="toolTip"> - <string>A comma-separated list of columns in which items containing + </property> + <property name="maximum"> + <number>10000</number> + </property> + </widget> + </item> + <item row="1" column="0" colspan="5"> + <widget class="QCheckBox" name="opt_show_avg_rating"> + <property name="text"> + <string>Show &average ratings in the tags browser</string> + </property> + <property name="checked"> + <bool>true</bool> + </property> + </widget> + </item> + <item row="2" column="0"> + <widget class="QLabel" name="label_81"> + <property name="text"> + <string>Categories with &hierarchical items:</string> + </property> + <property name="buddy"> + <cstring>opt_categories_using_hierarchy</cstring> + </property> + </widget> + </item> + <item row="3" column="0" colspan="5"> + <spacer name="verticalSpacer_2"> + <property name="orientation"> + <enum>Qt::Vertical</enum> + </property> + <property name="sizeHint" stdset="0"> + <size> + <width>690</width> + <height>252</height> + </size> + </property> + </spacer> + </item> + <item row="2" column="2" colspan="3"> + <widget class="MultiCompleteLineEdit" name="opt_categories_using_hierarchy"> + <property name="toolTip"> + <string>A comma-separated list of columns in which items containing periods are displayed in the tag browser trees. For example, if this box contains 'tags' then tags of the form 'Mystery.English' and 'Mystery.Thriller' will be displayed with English and Thriller both under 'Mystery'. If 'tags' is not in this box, then the tags will be displayed each on their own line.</string> - </property> + </property> + </widget> + </item> + </layout> + </widget> + <widget class="QWidget" name="tab_3"> + <attribute name="icon"> + <iconset resource="../../../../resources/images.qrc"> + <normaloff>:/images/cover_flow.png</normaloff>:/images/cover_flow.png</iconset> + </attribute> + <attribute name="title"> + <string>Cover Browser</string> + </attribute> + <layout class="QGridLayout" name="gridLayout_11"> + <item row="0" column="0" colspan="2"> + <widget class="QCheckBox" name="opt_separate_cover_flow"> + <property name="text"> + <string>Show cover &browser in a separate window (needs restart)</string> + </property> + </widget> + </item> + <item row="1" column="0"> + <widget class="QLabel" name="label_6"> + <property name="text"> + <string>&Number of covers to show in browse mode (needs restart):</string> + </property> + <property name="buddy"> + <cstring>opt_cover_flow_queue_length</cstring> + </property> + </widget> + </item> + <item row="1" column="1"> + <widget class="QSpinBox" name="opt_cover_flow_queue_length"/> + </item> + <item row="2" column="0" colspan="2"> + <spacer name="verticalSpacer_4"> + <property name="orientation"> + <enum>Qt::Vertical</enum> + </property> + <property name="sizeHint" stdset="0"> + <size> + <width>690</width> + <height>283</height> + </size> + </property> + </spacer> + </item> + </layout> + </widget> + <widget class="QWidget" name="tab_5"> + <attribute name="icon"> + <iconset resource="../../../../resources/images.qrc"> + <normaloff>:/images/format-fill-color.png</normaloff>:/images/format-fill-color.png</iconset> + </attribute> + <attribute name="title"> + <string>Column Coloring</string> + </attribute> + <layout class="QGridLayout" name="column_color_layout"> + <item row="0" column="0"> + <widget class="QLabel" name="label"> + <property name="text"> + <string>Column to color</string> + </property> + </widget> + </item> + <item row="0" column="3"> + <layout class="QHBoxLayout"> + <item> + <widget class="QLabel" name="label"> + <property name="text"> + <string>Color selection template</string> + </property> + <property name="sizePolicy"> + <sizepolicy hsizetype="Expanding" vsizetype="Fixed"> + <horstretch>10</horstretch> + <verstretch>0</verstretch> + </sizepolicy> + </property> + </widget> + </item> + <item> + <widget class="QLabel" name="label"> + <property name="text"> + <string>The template wizard is easiest to use</string> + </property> + </widget> + </item> + <item> + <widget class="QPushButton" name="color_help_button"> + <property name="text"> + <string>Show/hide help text</string> + </property> + </widget> + </item> + <item> + <widget class="QPushButton" name="colors_button"> + <property name="text"> + <string>Show/hide colors</string> + </property> + </widget> + </item> + </layout> + </item> + <item row="20" column="0"> + <widget class="QLabel" name="colors_label"> + <property name="text"> + <string>Color names</string> + </property> + </widget> + </item> + <item row="21" column="0" colspan="8"> + <widget class="QScrollArea" name="colors_scrollArea"> + <property name="maximumSize"> + <size> + <width>16777215</width> + <height>300</height> + </size> + </property> + <property name="widgetResizable"> + <bool>true</bool> + </property> + <widget class="QWidget" name="scrollAreaWidgetContents_2"> + <property name="geometry"> + <rect> + <x>0</x> + <y>0</y> + <width>687</width> + <height>61</height> + </rect> + </property> + <layout class="QVBoxLayout" name="verticalLayout_2"> + <item> + <widget class="QLabel" name="colors_box"> + <property name="wordWrap"> + <bool>true</bool> + </property> + <property name="alignment"> + <set>Qt::AlignLeft|Qt::AlignTop</set> + </property> + </widget> + </item> + </layout> + </widget> + </widget> + </item> + <item row="30" column="0" colspan="8"> + <widget class="QScrollArea" name="color_help_scrollArea"> + <property name="minimumSize"> + <size> + <width>0</width> + <height>200</height> + </size> + </property> + <property name="widgetResizable"> + <bool>true</bool> + </property> + <property name="alignment"> + <set>Qt::AlignLeft|Qt::AlignTop</set> + </property> + <widget class="QWidget" name="scrollAreaWidgetContents"> + <property name="geometry"> + <rect> + <x>0</x> + <y>0</y> + <width>687</width> + <height>194</height> + </rect> + </property> + <layout class="QVBoxLayout" name="verticalLayout"> + <item> + <widget class="QLabel" name="color_help_text"> + <property name="wordWrap"> + <bool>true</bool> + </property> + <property name="openExternalLinks"> + <bool>true</bool> + </property> + </widget> + </item> + </layout> + </widget> + </widget> + </item> + <item row="40" column="0"> + <spacer> + <property name="sizePolicy"> + <sizepolicy vsizetype="Expanding" hsizetype="Fixed"> + <horstretch>0</horstretch> + <verstretch>10</verstretch> + </sizepolicy> + </property> + <property name="orientation"> + <enum>Qt::Vertical</enum> + </property> + <property name="sizeHint" stdset="0"> + <size> + <width>0</width> + <height>0</height> + </size> + </property> + </spacer> + </item> + </layout> + </widget> </widget> </item> - <item row="15" column="0" colspan="2"> - <widget class="QGroupBox" name="groupBox_2"> - <property name="title"> - <string>&Toolbar</string> - </property> - <layout class="QGridLayout" name="gridLayout"> - <item row="0" column="1"> - <widget class="QComboBox" name="opt_toolbar_icon_size"/> - </item> - <item row="0" column="0"> - <widget class="QLabel" name="label"> - <property name="text"> - <string>&Icon size:</string> - </property> - <property name="buddy"> - <cstring>opt_toolbar_icon_size</cstring> - </property> - </widget> - </item> - <item row="1" column="1"> - <widget class="QComboBox" name="opt_toolbar_text"/> - </item> - <item row="1" column="0"> - <widget class="QLabel" name="label_4"> - <property name="text"> - <string>Show &text under icons:</string> - </property> - <property name="buddy"> - <cstring>opt_toolbar_text</cstring> - </property> - </widget> - </item> - <item row="2" column="0" colspan="2"> - <widget class="QCheckBox" name="opt_show_child_bar"> - <property name="text"> - <string>&Split the toolbar into two toolbars</string> - </property> - </widget> - </item> - </layout> - </widget> - </item> - <item row="16" column="0"> - <layout class="QHBoxLayout" name="horizontalLayout"> - <item> - <widget class="QLabel" name="label_2"> - <property name="text"> - <string>Interface font:</string> - </property> - <property name="buddy"> - <cstring>font_display</cstring> - </property> - </widget> - </item> - <item> - <widget class="QLineEdit" name="font_display"> - <property name="readOnly"> - <bool>true</bool> - </property> - </widget> - </item> - </layout> - </item> - <item row="16" column="1"> - <widget class="QPushButton" name="change_font_button"> - <property name="text"> - <string>Change &font (needs restart)</string> - </property> - </widget> - </item> - <item row="17" column="0" colspan="2"> - <spacer name="verticalSpacer"> - <property name="orientation"> - <enum>Qt::Vertical</enum> - </property> - <property name="sizeHint" stdset="0"> - <size> - <width>20</width> - <height>40</height> - </size> - </property> - </spacer> - </item> </layout> </widget> <customwidgets> @@ -303,7 +572,14 @@ then the tags will be displayed each on their own line.</string> <extends>QLineEdit</extends> <header>calibre/gui2/complete.h</header> </customwidget> + <customwidget> + <class>TemplateLineEditor</class> + <extends>QLineEdit</extends> + <header>calibre/gui2/dialogs/template_line_editor.h</header> + </customwidget> </customwidgets> - <resources/> + <resources> + <include location="../../../../resources/images.qrc"/> + </resources> <connections/> </ui> diff --git a/src/calibre/gui2/preferences/main.py b/src/calibre/gui2/preferences/main.py index f25cc85dce..85a5fc018c 100644 --- a/src/calibre/gui2/preferences/main.py +++ b/src/calibre/gui2/preferences/main.py @@ -7,6 +7,7 @@ __docformat__ = 'restructuredtext en' import textwrap from functools import partial +from collections import OrderedDict from PyQt4.Qt import QMainWindow, Qt, QIcon, QStatusBar, QFont, QWidget, \ QScrollArea, QStackedWidget, QVBoxLayout, QLabel, QFrame, QKeySequence, \ @@ -18,7 +19,6 @@ from calibre.gui2 import gprefs, min_available_height, available_width, \ warning_dialog from calibre.gui2.preferences import init_gui, AbortCommit, get_plugin from calibre.customize.ui import preferences_plugins -from calibre.utils.ordered_dict import OrderedDict ICON_SIZE = 32 @@ -71,11 +71,11 @@ class Category(QWidget): # {{{ plugin_activated = pyqtSignal(object) - def __init__(self, name, plugins, parent=None): + def __init__(self, name, plugins, gui_name, parent=None): QWidget.__init__(self, parent) self._layout = QVBoxLayout() self.setLayout(self._layout) - self.label = QLabel(name) + self.label = QLabel(gui_name) self.sep = QFrame(self) self.bf = QFont() self.bf.setBold(True) @@ -87,7 +87,9 @@ class Category(QWidget): # {{{ self.plugins = plugins self.bar = QToolBar(self) - self.bar.setIconSize(QSize(48, 48)) + self.bar.setStyleSheet( + 'QToolBar { border: none; background: none }') + self.bar.setIconSize(QSize(32, 32)) self.bar.setMovable(False) self.bar.setFloatable(False) self.bar.setToolButtonStyle(Qt.ToolButtonTextUnderIcon) @@ -118,12 +120,17 @@ class Browser(QScrollArea): # {{{ QScrollArea.__init__(self, parent) self.setWidgetResizable(True) - category_map = {} + category_map, category_names = {}, {} for plugin in preferences_plugins(): if plugin.category not in category_map: category_map[plugin.category] = plugin.category_order if category_map[plugin.category] < plugin.category_order: category_map[plugin.category] = plugin.category_order + if plugin.category not in category_names: + category_names[plugin.category] = (plugin.gui_category if + plugin.gui_category else plugin.category) + + self.category_names = category_names categories = list(category_map.keys()) categories.sort(cmp=lambda x, y: cmp(category_map[x], category_map[y])) @@ -145,7 +152,7 @@ class Browser(QScrollArea): # {{{ self.setWidget(self.container) for name, plugins in self.category_map.items(): - w = Category(name, plugins, self) + w = Category(name, plugins, self.category_names[name], parent=self) self.widgets.append(w) self._layout.addWidget(w) w.plugin_activated.connect(self.show_plugin.emit) @@ -354,9 +361,9 @@ class Preferences(QMainWindow): self.gui.tags_view.recount() self.gui.create_device_menu() self.gui.set_device_menu_items_state(bool(self.gui.device_connected)) - self.gui.tool_bar.build_bar() + self.gui.bars_manager.apply_settings() + self.gui.bars_manager.update_bars() self.gui.build_context_menus() - self.gui.tool_bar.apply_settings() return QMainWindow.closeEvent(self, *args) diff --git a/src/calibre/gui2/preferences/metadata_sources.py b/src/calibre/gui2/preferences/metadata_sources.py new file mode 100644 index 0000000000..f7465fb0ee --- /dev/null +++ b/src/calibre/gui2/preferences/metadata_sources.py @@ -0,0 +1,332 @@ +#!/usr/bin/env python +# vim:fileencoding=UTF-8:ts=4:sw=4:sta:et:sts=4:ai +from __future__ import (unicode_literals, division, absolute_import, + print_function) + +__license__ = 'GPL v3' +__copyright__ = '2011, Kovid Goyal <kovid@kovidgoyal.net>' +__docformat__ = 'restructuredtext en' + +from operator import attrgetter + +from PyQt4.Qt import (QAbstractTableModel, Qt, QAbstractListModel, QWidget, + pyqtSignal, QVBoxLayout, QDialogButtonBox, QFrame, QLabel, QIcon) + +from calibre.gui2.preferences import ConfigWidgetBase, test_widget +from calibre.gui2.preferences.metadata_sources_ui import Ui_Form +from calibre.ebooks.metadata.sources.base import msprefs +from calibre.customize.ui import (all_metadata_plugins, is_disabled, + enable_plugin, disable_plugin, default_disabled_plugins) +from calibre.gui2 import NONE, error_dialog + +class SourcesModel(QAbstractTableModel): # {{{ + + def __init__(self, parent=None): + QAbstractTableModel.__init__(self, parent) + + self.plugins = [] + self.enabled_overrides = {} + self.cover_overrides = {} + + def initialize(self): + self.plugins = list(all_metadata_plugins()) + self.plugins.sort(key=attrgetter('name')) + self.enabled_overrides = {} + self.cover_overrides = {} + self.reset() + + def rowCount(self, parent=None): + return len(self.plugins) + + def columnCount(self, parent=None): + return 2 + + def headerData(self, section, orientation, role): + if orientation == Qt.Horizontal and role == Qt.DisplayRole: + if section == 0: + return _('Source') + if section == 1: + return _('Cover priority') + return NONE + + def data(self, index, role): + try: + plugin = self.plugins[index.row()] + except: + return NONE + col = index.column() + + if role == Qt.DisplayRole: + if col == 0: + return plugin.name + elif col == 1: + orig = msprefs['cover_priorities'].get(plugin.name, 1) + return self.cover_overrides.get(plugin, orig) + elif role == Qt.CheckStateRole and col == 0: + orig = Qt.Unchecked if is_disabled(plugin) else Qt.Checked + return self.enabled_overrides.get(plugin, orig) + elif role == Qt.UserRole: + return plugin + elif (role == Qt.DecorationRole and col == 0 and not + plugin.is_configured()): + return QIcon(I('list_remove.png')) + elif role == Qt.ToolTipRole: + base = plugin.description + '\n\n' + if plugin.is_configured(): + return base + _('This source is configured and ready to go') + return base + _('This source needs configuration') + return NONE + + def setData(self, index, val, role): + try: + plugin = self.plugins[index.row()] + except: + return False + col = index.column() + ret = False + if col == 0 and role == Qt.CheckStateRole: + val, ok = val.toInt() + if ok: + self.enabled_overrides[plugin] = val + ret = True + if col == 1 and role == Qt.EditRole: + val, ok = val.toInt() + if ok: + self.cover_overrides[plugin] = val + ret = True + if ret: + self.dataChanged.emit(index, index) + return ret + + + def flags(self, index): + col = index.column() + ans = QAbstractTableModel.flags(self, index) + if col == 0: + return ans | Qt.ItemIsUserCheckable + return Qt.ItemIsEditable | ans + + def commit(self): + for plugin, val in self.enabled_overrides.iteritems(): + if val == Qt.Checked: + enable_plugin(plugin) + elif val == Qt.Unchecked: + disable_plugin(plugin) + + if self.cover_overrides: + cp = msprefs['cover_priorities'] + for plugin, val in self.cover_overrides.iteritems(): + if val == 1: + cp.pop(plugin.name, None) + else: + cp[plugin.name] = val + msprefs['cover_priorities'] = cp + + self.enabled_overrides = {} + self.cover_overrides = {} + + def restore_defaults(self): + self.enabled_overrides = dict([(p, (Qt.Unchecked if p.name in + default_disabled_plugins else Qt.Checked)) for p in self.plugins]) + self.cover_overrides = dict([(p, + msprefs.defaults['cover_priorities'].get(p.name, 1)) + for p in self.plugins]) + self.reset() + +# }}} + +class FieldsModel(QAbstractListModel): # {{{ + + + def __init__(self, parent=None): + QAbstractTableModel.__init__(self, parent) + + self.fields = [] + self.descs = { + 'authors': _('Authors'), + 'comments': _('Comments'), + 'pubdate': _('Published date'), + 'publisher': _('Publisher'), + 'rating' : _('Rating'), + 'tags' : _('Tags'), + 'title': _('Title'), + 'series': _('Series'), + 'language': _('Language'), + } + self.overrides = {} + self.exclude = frozenset(['series_index']) + + def rowCount(self, parent=None): + return len(self.fields) + + def initialize(self): + fields = set() + for p in all_metadata_plugins(): + fields |= p.touched_fields + self.fields = [] + for x in fields: + if not x.startswith('identifier:') and x not in self.exclude: + self.fields.append(x) + self.fields.sort(key=lambda x:self.descs.get(x, x)) + self.reset() + + def state(self, field, defaults=False): + src = msprefs.defaults if defaults else msprefs + return (Qt.Unchecked if field in src['ignore_fields'] + else Qt.Checked) + + def data(self, index, role): + try: + field = self.fields[index.row()] + except: + return None + if role == Qt.DisplayRole: + return self.descs.get(field, field) + if role == Qt.CheckStateRole: + return self.overrides.get(field, self.state(field)) + return NONE + + def flags(self, index): + ans = QAbstractTableModel.flags(self, index) + return ans | Qt.ItemIsUserCheckable + + def restore_defaults(self): + self.overrides = dict([(f, self.state(f, Qt.Checked)) for f in self.fields]) + self.reset() + + def select_all(self): + self.overrides = dict([(f, Qt.Checked) for f in self.fields]) + self.reset() + + def clear_all(self): + self.overrides = dict([(f, Qt.Unchecked) for f in self.fields]) + self.reset() + + def setData(self, index, val, role): + try: + field = self.fields[index.row()] + except: + return False + ret = False + if role == Qt.CheckStateRole: + val, ok = val.toInt() + if ok: + self.overrides[field] = val + ret = True + if ret: + self.dataChanged.emit(index, index) + return ret + + def commit(self): + ignored_fields = set([x for x in msprefs['ignore_fields'] if x not in + self.overrides]) + changed = set([k for k, v in self.overrides.iteritems() if v == + Qt.Unchecked]) + msprefs['ignore_fields'] = list(ignored_fields.union(changed)) + + +# }}} + +class PluginConfig(QWidget): # {{{ + + finished = pyqtSignal() + + def __init__(self, plugin, parent): + QWidget.__init__(self, parent) + + self.plugin = plugin + + self.l = l = QVBoxLayout() + self.setLayout(l) + self.c = c = QLabel(_('<b>Configure %s</b><br>%s') % (plugin.name, + plugin.description)) + c.setAlignment(Qt.AlignHCenter) + l.addWidget(c) + + self.config_widget = plugin.config_widget() + self.l.addWidget(self.config_widget) + + self.bb = QDialogButtonBox( + QDialogButtonBox.Save|QDialogButtonBox.Cancel, + parent=self) + self.bb.accepted.connect(self.finished) + self.bb.rejected.connect(self.finished) + self.bb.accepted.connect(self.commit) + l.addWidget(self.bb) + + self.f = QFrame(self) + self.f.setFrameShape(QFrame.HLine) + l.addWidget(self.f) + + def commit(self): + self.plugin.save_settings(self.config_widget) +# }}} + +class ConfigWidget(ConfigWidgetBase, Ui_Form): + + def genesis(self, gui): + r = self.register + r('txt_comments', msprefs) + r('max_tags', msprefs) + r('wait_after_first_identify_result', msprefs) + r('wait_after_first_cover_result', msprefs) + r('swap_author_names', msprefs) + r('fewer_tags', msprefs) + + self.configure_plugin_button.clicked.connect(self.configure_plugin) + self.sources_model = SourcesModel(self) + self.sources_view.setModel(self.sources_model) + self.sources_model.dataChanged.connect(self.changed_signal) + + self.fields_model = FieldsModel(self) + self.fields_view.setModel(self.fields_model) + self.fields_model.dataChanged.connect(self.changed_signal) + + self.select_all_button.clicked.connect(self.fields_model.select_all) + self.clear_all_button.clicked.connect(self.fields_model.clear_all) + + def configure_plugin(self): + for index in self.sources_view.selectionModel().selectedRows(): + plugin = self.sources_model.data(index, Qt.UserRole) + if plugin is not NONE: + return self.do_config(plugin) + error_dialog(self, _('No source selected'), + _('No source selected, cannot configure.'), show=True) + + def do_config(self, plugin): + self.pc = PluginConfig(plugin, self) + self.stack.insertWidget(1, self.pc) + self.stack.setCurrentIndex(1) + self.pc.finished.connect(self.pc_finished) + + def pc_finished(self): + try: + self.pc.finished.diconnect() + except: + pass + self.stack.setCurrentIndex(0) + self.stack.removeWidget(self.pc) + self.pc = None + + def initialize(self): + ConfigWidgetBase.initialize(self) + self.sources_model.initialize() + self.sources_view.resizeColumnsToContents() + self.fields_model.initialize() + + def restore_defaults(self): + ConfigWidgetBase.restore_defaults(self) + self.sources_model.restore_defaults() + self.fields_model.restore_defaults() + self.changed_signal.emit() + + def commit(self): + self.sources_model.commit() + self.fields_model.commit() + return ConfigWidgetBase.commit(self) + +if __name__ == '__main__': + from PyQt4.Qt import QApplication + app = QApplication([]) + test_widget('Sharing', 'Metadata download') + diff --git a/src/calibre/gui2/preferences/metadata_sources.ui b/src/calibre/gui2/preferences/metadata_sources.ui new file mode 100644 index 0000000000..ff161654dd --- /dev/null +++ b/src/calibre/gui2/preferences/metadata_sources.ui @@ -0,0 +1,191 @@ +<?xml version="1.0" encoding="UTF-8"?> +<ui version="4.0"> + <class>Form</class> + <widget class="QWidget" name="Form"> + <property name="geometry"> + <rect> + <x>0</x> + <y>0</y> + <width>781</width> + <height>394</height> + </rect> + </property> + <property name="windowTitle"> + <string>Form</string> + </property> + <layout class="QVBoxLayout" name="verticalLayout"> + <property name="margin"> + <number>0</number> + </property> + <item> + <widget class="QStackedWidget" name="stack"> + <widget class="QWidget" name="page"> + <layout class="QGridLayout" name="gridLayout"> + <item row="0" column="0" rowspan="7"> + <widget class="QGroupBox" name="groupBox"> + <property name="title"> + <string>Metadata sources</string> + </property> + <layout class="QVBoxLayout" name="verticalLayout_3"> + <item> + <widget class="QLabel" name="label"> + <property name="text"> + <string>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 (smaller) priority will be preferred when bulk downloading metadata. +</string> + </property> + <property name="wordWrap"> + <bool>true</bool> + </property> + </widget> + </item> + <item> + <widget class="QTableView" name="sources_view"> + <property name="selectionMode"> + <enum>QAbstractItemView::SingleSelection</enum> + </property> + <property name="selectionBehavior"> + <enum>QAbstractItemView::SelectRows</enum> + </property> + </widget> + </item> + <item> + <widget class="QLabel" name="label_5"> + <property name="text"> + <string>Sources with a red X next to their names must be configured before they will be used. </string> + </property> + <property name="wordWrap"> + <bool>true</bool> + </property> + </widget> + </item> + <item> + <widget class="QPushButton" name="configure_plugin_button"> + <property name="text"> + <string>Configure selected source</string> + </property> + <property name="icon"> + <iconset resource="../../../../resources/images.qrc"> + <normaloff>:/images/plugins.png</normaloff>:/images/plugins.png</iconset> + </property> + </widget> + </item> + </layout> + </widget> + </item> + <item row="0" column="1" colspan="2"> + <widget class="QGroupBox" name="groupBox_2"> + <property name="title"> + <string>Downloaded metadata fields</string> + </property> + <layout class="QGridLayout" name="gridLayout_2"> + <item row="0" column="0" colspan="2"> + <widget class="QListView" name="fields_view"> + <property name="toolTip"> + <string>If you uncheck any fields, metadata for those fields will not be downloaded</string> + </property> + <property name="selectionMode"> + <enum>QAbstractItemView::NoSelection</enum> + </property> + </widget> + </item> + <item row="1" column="0"> + <widget class="QPushButton" name="select_all_button"> + <property name="text"> + <string>&Select all</string> + </property> + </widget> + </item> + <item row="1" column="1"> + <widget class="QPushButton" name="clear_all_button"> + <property name="text"> + <string>&Clear all</string> + </property> + </widget> + </item> + </layout> + </widget> + </item> + <item row="1" column="1" colspan="2"> + <widget class="QCheckBox" name="opt_txt_comments"> + <property name="text"> + <string>Convert all downloaded comments to plain &text</string> + </property> + </widget> + </item> + <item row="2" column="1" colspan="2"> + <widget class="QCheckBox" name="opt_swap_author_names"> + <property name="text"> + <string>Swap author names from FN LN to LN, FN</string> + </property> + </widget> + </item> + <item row="4" column="1"> + <widget class="QLabel" name="label_2"> + <property name="text"> + <string>Max. number of &tags to download:</string> + </property> + <property name="buddy"> + <cstring>opt_max_tags</cstring> + </property> + </widget> + </item> + <item row="4" column="2"> + <widget class="QSpinBox" name="opt_max_tags"/> + </item> + <item row="5" column="1"> + <widget class="QLabel" name="label_3"> + <property name="text"> + <string>Max. &time to wait after first match is found:</string> + </property> + <property name="buddy"> + <cstring>opt_wait_after_first_identify_result</cstring> + </property> + </widget> + </item> + <item row="5" column="2"> + <widget class="QSpinBox" name="opt_wait_after_first_identify_result"> + <property name="suffix"> + <string> secs</string> + </property> + </widget> + </item> + <item row="6" column="1"> + <widget class="QLabel" name="label_4"> + <property name="text"> + <string>Max. time to wait after first &cover is found:</string> + </property> + <property name="buddy"> + <cstring>opt_wait_after_first_cover_result</cstring> + </property> + </widget> + </item> + <item row="6" column="2"> + <widget class="QSpinBox" name="opt_wait_after_first_cover_result"> + <property name="suffix"> + <string> secs</string> + </property> + </widget> + </item> + <item row="3" column="1" colspan="2"> + <widget class="QCheckBox" name="opt_fewer_tags"> + <property name="toolTip"> + <string><p>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 tend to be more like genres, while the larger tag sets tend to describe the books content. +<p>Note that this option will only make a practical difference if one of the metadata sources has a genre like tag set for the book you are searching for. Most often, they all have large tag sets.</string> + </property> + <property name="text"> + <string>Prefer &fewer tags</string> + </property> + </widget> + </item> + </layout> + </widget> + <widget class="QWidget" name="page_2"/> + </widget> + </item> + </layout> + </widget> + <resources> + <include location="../../../../resources/images.qrc"/> + </resources> + <connections/> +</ui> diff --git a/src/calibre/gui2/preferences/misc.py b/src/calibre/gui2/preferences/misc.py index 330332a716..80bfdffcd8 100644 --- a/src/calibre/gui2/preferences/misc.py +++ b/src/calibre/gui2/preferences/misc.py @@ -6,22 +6,31 @@ __copyright__ = '2010, Kovid Goyal <kovid@kovidgoyal.net>' __docformat__ = 'restructuredtext en' -from calibre.gui2.preferences import ConfigWidgetBase, test_widget +from calibre.gui2.preferences import ConfigWidgetBase, test_widget, Setting from calibre.gui2.preferences.misc_ui import Ui_Form from calibre.gui2 import error_dialog, config, open_local_file, info_dialog from calibre.constants import isosx -# Check Integrity {{{ +class WorkersSetting(Setting): + + def set_gui_val(self, val): + val = val//2 + Setting.set_gui_val(self, val) + + def get_gui_val(self): + val = Setting.get_gui_val(self) + return val * 2 class ConfigWidget(ConfigWidgetBase, Ui_Form): def genesis(self, gui): self.gui = gui r = self.register - r('worker_limit', config, restart_required=True) + r('worker_limit', config, restart_required=True, setting=WorkersSetting) r('enforce_cpu_limit', config, restart_required=True) self.device_detection_button.clicked.connect(self.debug_device_detection) self.button_open_config_dir.clicked.connect(self.open_config_dir) + self.user_defined_device_button.clicked.connect(self.user_defined_device) self.button_osx_symlinks.clicked.connect(self.create_symlinks) self.button_osx_symlinks.setVisible(isosx) @@ -30,6 +39,11 @@ class ConfigWidget(ConfigWidgetBase, Ui_Form): d = DebugDevice(self) d.exec_() + def user_defined_device(self, *args): + from calibre.gui2.preferences.device_user_defined import UserDefinedDevice + d = UserDefinedDevice(self) + d.exec_() + def open_config_dir(self, *args): from calibre.utils.config import config_dir open_local_file(config_dir) diff --git a/src/calibre/gui2/preferences/misc.ui b/src/calibre/gui2/preferences/misc.ui index c036cb971b..843f0f01b7 100644 --- a/src/calibre/gui2/preferences/misc.ui +++ b/src/calibre/gui2/preferences/misc.ui @@ -17,7 +17,7 @@ <item row="0" column="0"> <widget class="QLabel" name="label_5"> <property name="text"> - <string>&Maximum number of waiting worker processes (needs restart):</string> + <string>Max. simultaneous conversion/news download jobs:</string> </property> <property name="buddy"> <cstring>opt_worker_limit</cstring> @@ -27,13 +27,7 @@ <item row="0" column="1"> <widget class="QSpinBox" name="opt_worker_limit"> <property name="minimum"> - <number>2</number> - </property> - <property name="maximum"> - <number>10000</number> - </property> - <property name="singleStep"> - <number>2</number> + <number>1</number> </property> </widget> </item> @@ -64,7 +58,14 @@ </property> </widget> </item> - <item row="4" column="0"> + <item row="4" column="0" colspan="2"> + <widget class="QPushButton" name="user_defined_device_button"> + <property name="text"> + <string>Get information to setup the &user defined device</string> + </property> + </widget> + </item> + <item row="5" column="0"> <spacer name="verticalSpacer_6"> <property name="orientation"> <enum>Qt::Vertical</enum> diff --git a/src/calibre/gui2/preferences/plugboard.py b/src/calibre/gui2/preferences/plugboard.py index e1dc6b03bd..cf632c04c0 100644 --- a/src/calibre/gui2/preferences/plugboard.py +++ b/src/calibre/gui2/preferences/plugboard.py @@ -5,47 +5,30 @@ __license__ = 'GPL v3' __copyright__ = '2010, Kovid Goyal <kovid@kovidgoyal.net>' __docformat__ = 'restructuredtext en' -from PyQt4.Qt import Qt, QLineEdit, QComboBox, SIGNAL, QListWidgetItem +import copy -from calibre.gui2 import error_dialog +from PyQt4.Qt import Qt, QComboBox, QListWidgetItem + +from calibre.customize.ui import is_disabled +from calibre.gui2 import error_dialog, question_dialog from calibre.gui2.device import device_name_for_plugboards -from calibre.gui2.dialogs.template_dialog import TemplateDialog +from calibre.gui2.dialogs.template_line_editor import TemplateLineEditor from calibre.gui2.preferences import ConfigWidgetBase, test_widget from calibre.gui2.preferences.plugboard_ui import Ui_Form from calibre.customize.ui import metadata_writers, device_plugins from calibre.library.save_to_disk import plugboard_any_format_value, \ - plugboard_any_device_value, plugboard_save_to_disk_value + plugboard_any_device_value, plugboard_save_to_disk_value, \ + find_plugboard +from calibre.library.server.content import plugboard_content_server_value, \ + plugboard_content_server_formats from calibre.utils.formatter import validation_formatter -class LineEditWithTextBox(QLineEdit): - - ''' - Extend the context menu of a QLineEdit to include more actions. - ''' - - def contextMenuEvent(self, event): - menu = self.createStandardContextMenu() - menu.addSeparator() - - action_open_editor = menu.addAction(_('Open Editor')) - - self.connect(action_open_editor, SIGNAL('triggered()'), self.open_editor) - menu.exec_(event.globalPos()) - - def open_editor(self): - t = TemplateDialog(self, self.text()) - if t.exec_(): - self.setText(t.textbox.toPlainText()) - class ConfigWidget(ConfigWidgetBase, Ui_Form): def genesis(self, gui): self.gui = gui self.db = gui.library_view.model().db - self.current_plugboards = self.db.prefs.get('plugboards',{}) - self.current_device = None - self.current_format = None def initialize(self): def field_cmp(x, y): @@ -61,6 +44,10 @@ class ConfigWidget(ConfigWidgetBase, Ui_Form): ConfigWidgetBase.initialize(self) + self.current_plugboards = copy.deepcopy(self.db.prefs.get('plugboards',{})) + self.current_device = None + self.current_format = None + if self.gui.device_manager.connected_device is not None: self.device_label.setText(_('Device currently connected: ') + self.gui.device_manager.connected_device.__class__.__name__) @@ -68,19 +55,26 @@ class ConfigWidget(ConfigWidgetBase, Ui_Form): self.device_label.setText(_('Device currently connected: None')) self.devices = ['', 'APPLE', 'FOLDER_DEVICE'] + self.device_to_formats_map = {} for device in device_plugins(): n = device_name_for_plugboards(device) + self.device_to_formats_map[n] = device.FORMATS if n not in self.devices: self.devices.append(n) self.devices.sort(cmp=lambda x, y: cmp(x.lower(), y.lower())) self.devices.insert(1, plugboard_save_to_disk_value) - self.devices.insert(2, plugboard_any_device_value) + self.devices.insert(1, plugboard_content_server_value) + self.device_to_formats_map[plugboard_content_server_value] = \ + plugboard_content_server_formats + self.devices.insert(1, plugboard_any_device_value) self.new_device.addItems(self.devices) self.formats = [''] for w in metadata_writers(): - for f in w.file_types: - self.formats.append(f) + if not is_disabled(w): + for f in w.file_types: + if not f in self.formats: + self.formats.append(f) self.formats.append('device_db') self.formats.sort() self.formats.insert(1, plugboard_any_format_value) @@ -93,7 +87,7 @@ class ConfigWidget(ConfigWidgetBase, Ui_Form): self.source_widgets = [] self.dest_widgets = [] for i in range(0, len(self.dest_fields)-1): - w = LineEditWithTextBox(self) + w = TemplateLineEditor(self) self.source_widgets.append(w) self.fields_layout.addWidget(w, 5+i, 0, 1, 1) w = QComboBox(self) @@ -186,50 +180,74 @@ class ConfigWidget(ConfigWidgetBase, Ui_Form): return self.clear_fields(edit_boxes=True) self.current_device = unicode(txt) - error = False - if self.current_format == plugboard_any_format_value: - # user specified any format. - for f in self.current_plugboards: - devs = set(self.current_plugboards[f]) - if self.current_device != plugboard_save_to_disk_value and \ - plugboard_any_device_value in devs: - # specific format/any device in list. conflict. - # note: any device does not match save_to_disk - error = True - break - if self.current_device in devs: - # specific format/current device in list. conflict - error = True - break - if self.current_device == plugboard_any_device_value: - # any device and a specific device already there. conflict - error = True - break - else: - # user specified specific format. - for f in self.current_plugboards: - devs = set(self.current_plugboards[f]) - if f == plugboard_any_format_value and \ - self.current_device in devs: - # any format/same device in list. conflict. - error = True - break - if f == self.current_format and self.current_device in devs: - # current format/current device in list. conflict - error = True - break - if f == self.current_format and plugboard_any_device_value in devs: - # current format/any device in list. conflict - error = True - break - if error: + if self.current_format in self.current_plugboards and \ + self.current_device in self.current_plugboards[self.current_format]: error_dialog(self, '', - _('That format and device already has a plugboard or ' - 'conflicts with another plugboard.'), + _('That format and device already has a plugboard.'), show=True) self.new_device.setCurrentIndex(0) return + + # If we have a specific format/device combination, check if a more + # general combination matches. + if self.current_format != plugboard_any_format_value and \ + self.current_device != plugboard_any_device_value: + if find_plugboard(self.current_device, self.current_format, + self.current_plugboards): + if not question_dialog(self.gui, + _('Possibly override plugboard?'), + _('A more general plugboard already exists for ' + 'that format and device. ' + 'Are you sure you want to add the new plugboard?')): + self.new_device.setCurrentIndex(0) + return + + # If we have a specific format, check if we are adding a possibly- + # covered plugboard + if self.current_format != plugboard_any_format_value: + if self.current_format in self.current_plugboards: + if self.current_device == plugboard_any_device_value: + if not question_dialog(self.gui, + _('Add possibly overridden plugboard?'), + _('More specific device plugboards exist for ' + 'that format. ' + 'Are you sure you want to add the new plugboard?')): + self.new_device.setCurrentIndex(0) + return + # We are adding an 'any format' entry. Check if we are adding a specific + # device and if so, does some other plugboard match that device. + elif self.current_device != plugboard_any_device_value: + for fmt in self.current_plugboards: + if find_plugboard(self.current_device, fmt, self.current_plugboards): + if not question_dialog(self.gui, + _('Really add plugboard?'), + _('A different plugboard matches that format and ' + 'device combination. ' + 'Are you sure you want to add the new plugboard?')): + self.new_device.setCurrentIndex(0) + return + # We are adding an any format/any device entry, which will be overridden + # by any other entry. Ask if such entries exist. + elif len(self.current_plugboards): + if not question_dialog(self.gui, + _('Add possibly overridden plugboard?'), + _('More specific format and device plugboards ' + 'already exist. ' + 'Are you sure you want to add the new plugboard?')): + self.new_device.setCurrentIndex(0) + return + + if self.current_format != plugboard_any_format_value and \ + self.current_device in self.device_to_formats_map: + allowable_formats = self.device_to_formats_map[self.current_device] + if self.current_format not in allowable_formats: + error_dialog(self, '', + _('The {0} device does not support the {1} format.'). + format(self.current_device, self.current_format), + show=True) + self.new_device.setCurrentIndex(0) + return self.set_fields() def new_format_changed(self, txt): @@ -251,7 +269,7 @@ class ConfigWidget(ConfigWidgetBase, Ui_Form): if d != 0: try: validation_formatter.validate(s) - except Exception, err: + except Exception as err: error_dialog(self, _('Invalid template'), '<p>'+_('The template %s is invalid:')%s + \ '<br>'+str(err), show=True) diff --git a/src/calibre/gui2/preferences/plugins.py b/src/calibre/gui2/preferences/plugins.py index acf42fee16..4f88e5aa1d 100644 --- a/src/calibre/gui2/preferences/plugins.py +++ b/src/calibre/gui2/preferences/plugins.py @@ -6,17 +6,18 @@ __copyright__ = '2010, Kovid Goyal <kovid@kovidgoyal.net>' __docformat__ = 'restructuredtext en' import textwrap, os +from collections import OrderedDict from PyQt4.Qt import Qt, QModelIndex, QAbstractItemModel, QVariant, QIcon, \ QBrush from calibre.gui2.preferences import ConfigWidgetBase, test_widget from calibre.gui2.preferences.plugins_ui import Ui_Form -from calibre.customize.ui import initialized_plugins, is_disabled, enable_plugin, \ - disable_plugin, plugin_customization, add_plugin, \ - remove_plugin +from calibre.customize.ui import (initialized_plugins, is_disabled, enable_plugin, + disable_plugin, plugin_customization, add_plugin, + remove_plugin, NameConflict) from calibre.gui2 import NONE, error_dialog, info_dialog, choose_files, \ - question_dialog + question_dialog, gprefs from calibre.utils.search_query_parser import SearchQueryParser from calibre.utils.icu import lower @@ -74,6 +75,8 @@ class PluginModel(QAbstractItemModel, SearchQueryParser): # {{{ def find(self, query): query = query.strip() + if not query: + return QModelIndex() matches = self.parse(query) if not matches: return QModelIndex() @@ -86,6 +89,8 @@ class PluginModel(QAbstractItemModel, SearchQueryParser): # {{{ def find_next(self, idx, query, backwards=False): query = query.strip() + if not query: + return idx matches = self.parse(query) if not matches: return idx @@ -217,6 +222,7 @@ class ConfigWidget(ConfigWidgetBase, Ui_Form): self.search.search.connect(self.find) self.next_button.clicked.connect(self.find_next) self.previous_button.clicked.connect(self.find_previous) + self.changed_signal.connect(self.reload_store_plugins) def find(self, query): idx = self._plugin_model.find(query) @@ -277,10 +283,15 @@ class ConfigWidget(ConfigWidgetBase, Ui_Form): ' Are you sure you want to proceed?'), show_copy_button=False): return - plugin = add_plugin(path) + try: + plugin = add_plugin(path) + except NameConflict as e: + return error_dialog(self, _('Already exists'), + unicode(e), show=True) self._plugin_model.populate() self._plugin_model.reset() self.changed_signal.emit() + self.check_for_add_to_toolbars(plugin) info_dialog(self, _('Success'), _('Plugin <b>{0}</b> successfully installed under <b>' ' {1} plugins</b>. You may have to restart calibre ' @@ -342,6 +353,43 @@ class ConfigWidget(ConfigWidgetBase, Ui_Form): plugin.name + _(' cannot be removed. It is a ' 'builtin plugin. Try disabling it instead.')).exec_() + def reload_store_plugins(self): + self.gui.load_store_plugins() + if self.gui.iactions.has_key('Store'): + self.gui.iactions['Store'].load_menu() + + def check_for_add_to_toolbars(self, plugin): + from calibre.gui2.preferences.toolbar import ConfigWidget + from calibre.customize import InterfaceActionBase + + if not isinstance(plugin, InterfaceActionBase): + return + + all_locations = OrderedDict(ConfigWidget.LOCATIONS) + plugin_action = plugin.load_actual_plugin(self.gui) + installed_actions = OrderedDict([ + (key, list(gprefs.get('action-layout-'+key, []))) + for key in all_locations]) + + # If already installed in a GUI container, do nothing + for action_names in installed_actions.itervalues(): + if plugin_action.name in action_names: + return + + allowed_locations = [(key, text) for key, text in + all_locations.iteritems() if key + not in plugin_action.dont_add_to] + if not allowed_locations: + return # This plugin doesn't want to live in the GUI + + from calibre.gui2.dialogs.choose_plugin_toolbars import ChoosePluginToolbarsDialog + d = ChoosePluginToolbarsDialog(self, plugin_action, allowed_locations) + if d.exec_() == d.Accepted: + for key, text in d.selected_locations(): + installed_actions = list(gprefs.get('action-layout-'+key, [])) + installed_actions.append(plugin_action.name) + gprefs['action-layout-'+key] = tuple(installed_actions) + if __name__ == '__main__': from PyQt4.Qt import QApplication diff --git a/src/calibre/gui2/preferences/save_template.py b/src/calibre/gui2/preferences/save_template.py index 4c00a14c0f..96ca8c8945 100644 --- a/src/calibre/gui2/preferences/save_template.py +++ b/src/calibre/gui2/preferences/save_template.py @@ -57,7 +57,7 @@ class SaveTemplate(QWidget, Ui_Form): return question_dialog(self, _('Constant template'), _('The template contains no {fields}, so all ' 'books will have the same name. Is this OK?')) - except Exception, err: + except Exception as err: error_dialog(self, _('Invalid template'), '<p>'+_('The template %s is invalid:')%tmpl + \ '<br>'+str(err), show=True) diff --git a/src/calibre/gui2/preferences/search.py b/src/calibre/gui2/preferences/search.py index db93cbd525..7bdb12ec55 100644 --- a/src/calibre/gui2/preferences/search.py +++ b/src/calibre/gui2/preferences/search.py @@ -171,10 +171,10 @@ class ConfigWidget(ConfigWidgetBase, Ui_Form): return ConfigWidgetBase.commit(self) def refresh_gui(self, gui): + gui.set_highlight_only_button_icon() if self.muc_changed: gui.tags_view.set_new_model() gui.search.search_as_you_type(config['search_as_you_type']) - gui.library_view.model().set_highlight_only(config['highlight_search_matches']) gui.search.do_search() def clear_histories(self, *args): diff --git a/src/calibre/gui2/preferences/server.py b/src/calibre/gui2/preferences/server.py index 82519f17cd..f4a00c0932 100644 --- a/src/calibre/gui2/preferences/server.py +++ b/src/calibre/gui2/preferences/server.py @@ -18,6 +18,7 @@ from calibre.utils.config import ConfigProxy from calibre.gui2 import error_dialog, config, open_url, warning_dialog, \ Dispatcher, info_dialog from calibre import as_unicode +from calibre.utils.icu import sort_key class ConfigWidget(ConfigWidgetBase, Ui_Form): @@ -42,8 +43,11 @@ class ConfigWidget(ConfigWidgetBase, Ui_Form): else self.opt_password.Password)) self.opt_password.setEchoMode(self.opt_password.Password) - restrictions = sorted(saved_searches().names(), - cmp=lambda x,y: cmp(x.lower(), y.lower())) + restrictions = sorted(saved_searches().names(), key=sort_key) + # verify that the current restriction still exists. If not, clear it. + csr = db.prefs.get('cs_restriction', None) + if csr and csr not in restrictions: + db.prefs.set('cs_restriction', '') choices = [('', '')] + [(x, x) for x in restrictions] r('cs_restriction', db.prefs, choices=choices) @@ -57,17 +61,8 @@ class ConfigWidget(ConfigWidgetBase, Ui_Form): r('autolaunch_server', config) - def set_server_options(self): - c = self.proxy - c.set('port', self.opt_port.value()) - c.set('username', unicode(self.opt_username.text()).strip()) - p = unicode(self.opt_password.text()).strip() - if not p: - p = None - c.set('password', p) - def start_server(self): - self.set_server_options() + ConfigWidgetBase.commit(self) self.gui.start_content_server(check_started=False) while not self.gui.content_server.is_running and self.gui.content_server.exception is None: time.sleep(1) diff --git a/src/calibre/gui2/preferences/social.py b/src/calibre/gui2/preferences/social.py deleted file mode 100644 index a22bcce091..0000000000 --- a/src/calibre/gui2/preferences/social.py +++ /dev/null @@ -1,79 +0,0 @@ -#!/usr/bin/env python -# vim:fileencoding=UTF-8:ts=4:sw=4:sta:et:sts=4:ai -from __future__ import with_statement - -__license__ = 'GPL v3' -__copyright__ = '2009, Kovid Goyal <kovid@kovidgoyal.net>' -__docformat__ = 'restructuredtext en' - -import time -from threading import Thread - -from PyQt4.Qt import QDialog, QDialogButtonBox, Qt, QLabel, QVBoxLayout, \ - QTimer - -from calibre.ebooks.metadata import MetaInformation - -class Worker(Thread): - - def __init__(self, mi): - Thread.__init__(self) - self.daemon = True - self.mi = MetaInformation(mi) - self.exceptions = [] - - def run(self): - from calibre.ebooks.metadata.fetch import get_social_metadata - self.exceptions = get_social_metadata(self.mi) - -class SocialMetadata(QDialog): - - TIMEOUT = 300 # seconds - - def __init__(self, mi, parent): - QDialog.__init__(self, parent) - - self.bbox = QDialogButtonBox(QDialogButtonBox.Cancel, Qt.Horizontal, self) - self.mi = mi - self.layout = QVBoxLayout(self) - self.label = QLabel(_('Downloading social metadata, please wait...'), self) - self.label.setWordWrap(True) - self.layout.addWidget(self.label) - self.layout.addWidget(self.bbox) - - self.worker = Worker(mi) - self.bbox.rejected.connect(self.reject) - self.worker.start() - self.start_time = time.time() - self.timed_out = False - self.rejected = False - QTimer.singleShot(50, self.update) - - def reject(self): - self.rejected = True - QDialog.reject(self) - - def update(self): - if self.rejected: - return - if time.time() - self.start_time > self.TIMEOUT: - self.timed_out = True - self.reject() - return - if not self.worker.is_alive(): - self.accept() - return - QTimer.singleShot(50, self.update) - - def accept(self): - self.mi.tags = self.worker.mi.tags - self.mi.rating = self.worker.mi.rating - self.mi.comments = self.worker.mi.comments - if self.worker.mi.series: - self.mi.series = self.worker.mi.series - self.mi.series_index = self.worker.mi.series_index - QDialog.accept(self) - - @property - def exceptions(self): - return self.worker.exceptions diff --git a/src/calibre/gui2/preferences/template_functions.py b/src/calibre/gui2/preferences/template_functions.py index 8ffd65b2b5..fcb4c87372 100644 --- a/src/calibre/gui2/preferences/template_functions.py +++ b/src/calibre/gui2/preferences/template_functions.py @@ -74,9 +74,10 @@ class ConfigWidget(ConfigWidgetBase, Ui_Form): def initialize(self): try: - with open(P('template-functions.json'), 'rb') as f: - self.builtin_source_dict = json.load(f, encoding='utf-8') + self.builtin_source_dict = json.loads(P('template-functions.json', data=True, + allow_user_override=False).decode('utf-8')) except: + traceback.print_exc() self.builtin_source_dict = {} self.funcs = formatter_functions.get_functions() @@ -186,7 +187,7 @@ class ConfigWidget(ConfigWidgetBase, Ui_Form): self.argument_count.setValue(func.arg_count) self.documentation.setText(func.doc) if txt in self.builtins: - if hasattr(func, 'program_text'): + if hasattr(func, 'program_text') and func.program_text: self.program.setPlainText(func.program_text) elif txt in self.builtin_source_dict: self.program.setPlainText(self.builtin_source_dict[txt]) diff --git a/src/calibre/gui2/preferences/toolbar.py b/src/calibre/gui2/preferences/toolbar.py index 26cdea19d3..6ed9b32ff1 100644 --- a/src/calibre/gui2/preferences/toolbar.py +++ b/src/calibre/gui2/preferences/toolbar.py @@ -34,9 +34,12 @@ class BaseModel(QAbstractListModel): if name == 'Location Manager': return FakeAction(name, None, _('Switch between library and device views'), - dont_remove_from=set(['toolbar-device'])) + dont_add_to=frozenset(['menubar', 'toolbar', + 'toolbar-child', 'context-menu', + 'context-menu-device'])) if name is None: - return FakeAction('--- '+_('Separator')+' ---', None) + return FakeAction('--- '+_('Separator')+' ---', None, + dont_add_to=frozenset(['menubar', 'menubar-device'])) try: return gui.iactions[name] except: @@ -55,6 +58,10 @@ class BaseModel(QAbstractListModel): text = _('Choose library') return QVariant(text) if role == Qt.DecorationRole: + if hasattr(self._data[row], 'qaction'): + icon = self._data[row].qaction.icon() + if not icon.isNull(): + return QVariant(icon) ic = action[1] if ic is None: ic = 'blank.png' @@ -73,6 +80,12 @@ class BaseModel(QAbstractListModel): ans.append(n) return ans + def has_action(self, name): + for a in self._data: + if a.name == name: + return True + return False + class AllModel(BaseModel): @@ -85,7 +98,7 @@ class AllModel(BaseModel): self._data = self.get_all_actions(current) def get_all_actions(self, current): - all = list(self.gui.iactions.keys()) + ['Donate'] + all = list(self.gui.iactions.keys()) + ['Donate', 'Location Manager'] all = [x for x in all if x not in current] + [None] all = [self.name_to_action(x, self.gui) for x in all] all = [x for x in all if self.key not in x.dont_add_to] @@ -205,10 +218,13 @@ class ConfigWidget(ConfigWidgetBase, Ui_Form): LOCATIONS = [ ('toolbar', _('The main toolbar')), ('toolbar-device', _('The main toolbar when a device is connected')), + ('toolbar-child', _('The optional second toolbar')), + ('menubar', _('The menubar')), + ('menubar-device', _('The menubar when a device is connected')), ('context-menu', _('The context menu for the books in the ' 'calibre library')), ('context-menu-device', _('The context menu for the books on ' - 'the device')) + 'the device')), ] def genesis(self, gui): @@ -279,6 +295,18 @@ class ConfigWidget(ConfigWidgetBase, Ui_Form): self.changed_signal.emit() def commit(self): + # Ensure preferences are showing in either the toolbar or + # the menubar. + pref_in_toolbar = self.models['toolbar'][1].has_action('Preferences') + pref_in_menubar = self.models['menubar'][1].has_action('Preferences') + lm_in_toolbar = self.models['toolbar-device'][1].has_action('Location Manager') + lm_in_menubar = self.models['menubar-device'][1].has_action('Location Manager') + if not pref_in_toolbar and not pref_in_menubar: + self.models['menubar'][1].add(['Preferences']) + if not lm_in_toolbar and not lm_in_menubar: + self.models['menubar-device'][1].add(['Location Manager']) + + # Save data. for am, cm in self.models.values(): cm.commit() return False @@ -289,6 +317,10 @@ class ConfigWidget(ConfigWidgetBase, Ui_Form): am.restore_defaults() self.changed_signal.emit() + def refresh_gui(self, gui): + gui.bars_manager.init_bars() + gui.bars_manager.update_bars() + if __name__ == '__main__': from PyQt4.Qt import QApplication diff --git a/src/calibre/gui2/preferences/tweaks.py b/src/calibre/gui2/preferences/tweaks.py index 9d0097ac35..a1756bf1ba 100644 --- a/src/calibre/gui2/preferences/tweaks.py +++ b/src/calibre/gui2/preferences/tweaks.py @@ -14,9 +14,9 @@ from calibre.utils.config import read_raw_tweaks, write_tweaks from calibre.gui2.widgets import PythonHighlighter from calibre import isbytestring -from PyQt4.Qt import QAbstractListModel, Qt, QStyledItemDelegate, QStyle, \ - QStyleOptionViewItem, QFont, QDialogButtonBox, QDialog, \ - QVBoxLayout, QPlainTextEdit, QLabel +from PyQt4.Qt import (QAbstractListModel, Qt, QStyledItemDelegate, QStyle, + QStyleOptionViewItem, QFont, QDialogButtonBox, QDialog, + QVBoxLayout, QPlainTextEdit, QLabel) class Delegate(QStyledItemDelegate): # {{{ def __init__(self, view): @@ -35,8 +35,9 @@ class Delegate(QStyledItemDelegate): # {{{ class Tweak(object): # {{{ def __init__(self, name, doc, var_names, defaults, custom): - self.name = name - self.doc = doc.strip() + translate = __builtins__['_'] + self.name = translate(name) + self.doc = translate(doc.strip()) self.var_names = var_names self.default_values = {} for x in var_names: diff --git a/src/calibre/gui2/search_box.py b/src/calibre/gui2/search_box.py index 34be6cd276..19cfb7417e 100644 --- a/src/calibre/gui2/search_box.py +++ b/src/calibre/gui2/search_box.py @@ -7,12 +7,14 @@ __copyright__ = '2009, Kovid Goyal <kovid@kovidgoyal.net>' __docformat__ = 'restructuredtext en' import re +from functools import partial + from PyQt4.Qt import QComboBox, Qt, QLineEdit, QStringList, pyqtSlot, QDialog, \ pyqtSignal, QCompleter, QAction, QKeySequence, QTimer, \ - QString + QString, QIcon, QMenu -from calibre.gui2 import config +from calibre.gui2 import config, error_dialog from calibre.gui2.dialogs.confirm_delete import confirm from calibre.gui2.dialogs.saved_search_editor import SavedSearchEditor from calibre.gui2.dialogs.search import SearchDialog @@ -109,7 +111,7 @@ class SearchBox2(QComboBox): # {{{ def normalize_state(self): self.setToolTip(self.tool_tip_text) self.line_edit.setStyleSheet( - 'QLineEdit{color:black;background-color:%s;}' % self.normal_background) + 'QLineEdit{color:none;background-color:%s;}' % self.normal_background) def text(self): return self.currentText() @@ -217,11 +219,15 @@ class SearchBox2(QComboBox): # {{{ self.clear() else: self.normalize_state() + self.lineEdit().setCompleter(None) self.setEditText(txt) self.line_edit.end(False) if emit_changed: self.changed.emit() self._do_search(store_in_history=store_in_history) + c = QCompleter() + self.lineEdit().setCompleter(c) + c.setCompletionMode(c.PopupCompletion) self.focus_to_library.emit() finally: if not store_in_history: @@ -312,23 +318,6 @@ class SavedSearchBox(QComboBox): # {{{ self.addItems(qnames) self.setCurrentIndex(-1) - # SIGNALed from the main UI - def delete_search_button_clicked(self): - if not confirm('<p>'+_('The selected search will be ' - '<b>permanently deleted</b>. Are you sure?') - +'</p>', 'saved_search_delete', self): - return - idx = self.currentIndex - if idx < 0: - return - ss = saved_searches().lookup(unicode(self.currentText())) - if ss is None: - return - saved_searches().delete(unicode(self.currentText())) - self.clear() - self.search_box.clear() - self.changed.emit() - # SIGNALed from the main UI def save_search_button_clicked(self): name = unicode(self.currentText()) @@ -344,6 +333,24 @@ class SavedSearchBox(QComboBox): # {{{ self.saved_search_selected (name) self.changed.emit() + def delete_current_search(self): + idx = self.currentIndex() + if idx <= 0: + error_dialog(self, _('Delete current search'), + _('No search is selected'), show=True) + return + if not confirm('<p>'+_('The selected search will be ' + '<b>permanently deleted</b>. Are you sure?') + +'</p>', 'saved_search_delete', self): + return + ss = saved_searches().lookup(unicode(self.currentText())) + if ss is None: + return + saved_searches().delete(unicode(self.currentText())) + self.clear() + self.search_box.clear() + self.changed.emit() + # SIGNALed from the main UI def copy_search_button_clicked (self): idx = self.currentIndex(); @@ -378,7 +385,24 @@ class SearchBoxMixin(object): # {{{ unicode(self.search.toolTip()))) self.advanced_search_button.setStatusTip(self.advanced_search_button.toolTip()) self.clear_button.setStatusTip(self.clear_button.toolTip()) - self.search_options_button.clicked.connect(self.search_options_button_clicked) + self.set_highlight_only_button_icon() + self.highlight_only_button.clicked.connect(self.highlight_only_clicked) + tt = _('Enable or disable search highlighting.') + '<br><br>' + tt += config.help('highlight_search_matches') + self.highlight_only_button.setToolTip(tt) + + def highlight_only_clicked(self, state): + config['highlight_search_matches'] = not config['highlight_search_matches'] + self.set_highlight_only_button_icon() + self.search.do_search() + self.focus_to_library() + + def set_highlight_only_button_icon(self): + if config['highlight_search_matches']: + self.highlight_only_button.setIcon(QIcon(I('highlight_only_on.png'))) + else: + self.highlight_only_button.setIcon(QIcon(I('highlight_only_off.png'))) + self.library_view.model().set_highlight_only(config['highlight_search_matches']) def focus_search_box(self, *args): self.search.setFocus(Qt.OtherFocusReason) @@ -402,10 +426,6 @@ class SearchBoxMixin(object): # {{{ self.search.do_search() self.focus_to_library() - def search_options_button_clicked(self): - self.iactions['Preferences'].do_config(initial_plugin=('Interface', - 'Search'), close_after_initial=True) - def focus_to_library(self): self.current_view().setFocus(Qt.OtherFocusReason) @@ -418,8 +438,6 @@ class SavedSearchBoxMixin(object): # {{{ self.clear_button.clicked.connect(self.saved_search.clear) self.save_search_button.clicked.connect( self.saved_search.save_search_button_clicked) - self.delete_search_button.clicked.connect( - self.saved_search.delete_search_button_clicked) self.copy_search_button.clicked.connect( self.saved_search.copy_search_button_clicked) self.saved_searches_changed() @@ -428,28 +446,50 @@ class SavedSearchBoxMixin(object): # {{{ self.saved_search.setToolTip( _('Choose saved search or enter name for new saved search')) self.saved_search.setStatusTip(self.saved_search.toolTip()) - for x in ('copy', 'save', 'delete'): + for x in ('copy', 'save'): b = getattr(self, x+'_search_button') b.setStatusTip(b.toolTip()) + self.save_search_button.setToolTip('<p>' + + _("Save current search under the name shown in the box. " + "Press and hold for a pop-up options menu.") + '</p>') + self.save_search_button.setMenu(QMenu()) + self.save_search_button.menu().addAction( + QIcon(I('plus.png')), + _('Create saved search'), + self.saved_search.save_search_button_clicked) + self.save_search_button.menu().addAction( + QIcon(I('trash.png')), + _('Delete saved search'), + self.saved_search.delete_current_search) + self.save_search_button.menu().addAction( + QIcon(I('search.png')), + _('Manage saved searches'), + partial(self.do_saved_search_edit, None)) - def saved_searches_changed(self): + def saved_searches_changed(self, set_restriction=None, recount=True): p = sorted(saved_searches().names(), key=sort_key) - t = unicode(self.search_restriction.currentText()) + if set_restriction is None: + set_restriction = unicode(self.search_restriction.currentText()) # rebuild the restrictions combobox using current saved searches self.search_restriction.clear() self.search_restriction.addItem('') - self.tags_view.recount() + self.search_restriction.addItem(_('*Current search')) + if recount: + self.tags_view.recount() for s in p: self.search_restriction.addItem(s) - if t: # redo the search restriction if there was one - self.apply_named_search_restriction(t) + if set_restriction: # redo the search restriction if there was one + self.apply_named_search_restriction(set_restriction) def do_saved_search_edit(self, search): d = SavedSearchEditor(self, search) d.exec_() if d.result() == d.Accepted: - self.saved_searches_changed() - self.saved_search.clear() + self.do_rebuild_saved_searches() + + def do_rebuild_saved_searches(self): + self.saved_searches_changed() + self.saved_search.clear() # }}} diff --git a/src/calibre/gui2/search_restriction_mixin.py b/src/calibre/gui2/search_restriction_mixin.py index 73c191101c..ffebc9e131 100644 --- a/src/calibre/gui2/search_restriction_mixin.py +++ b/src/calibre/gui2/search_restriction_mixin.py @@ -17,6 +17,10 @@ class SearchRestrictionMixin(object): self.search_restriction.setMinimumContentsLength(10) self.search_restriction.setStatusTip(self.search_restriction.toolTip()) self.search_count.setText(_("(all books)")) + self.search_restriction_tooltip = \ + _('Books display will be restricted to those matching a ' + 'selected saved search') + self.search_restriction.setToolTip(self.search_restriction_tooltip) def apply_named_search_restriction(self, name): if not name: @@ -25,16 +29,45 @@ class SearchRestrictionMixin(object): r = self.search_restriction.findText(name) if r < 0: r = 0 - self.search_restriction.setCurrentIndex(r) - self.apply_search_restriction(r) + if r != self.search_restriction.currentIndex(): + self.search_restriction.setCurrentIndex(r) + self.apply_search_restriction(r) + + def apply_text_search_restriction(self, search): + search = unicode(search) + if not search: + self.search_restriction.setCurrentIndex(0) + else: + s = '*' + search + if self.search_restriction.count() > 1: + txt = unicode(self.search_restriction.itemText(2)) + if txt.startswith('*'): + self.search_restriction.setItemText(2, s) + else: + self.search_restriction.insertItem(2, s) + else: + self.search_restriction.insertItem(2, s) + self.search_restriction.setCurrentIndex(2) + self.search_restriction.setToolTip('<p>' + + self.search_restriction_tooltip + + _(' or the search ') + "'" + search + "'</p>") + self._apply_search_restriction(search) def apply_search_restriction(self, i): - r = unicode(self.search_restriction.currentText()) - if r is not None and r != '': - restriction = 'search:"%s"'%(r) + if i == 1: + self.apply_text_search_restriction(unicode(self.search.currentText())) + elif i == 2 and unicode(self.search_restriction.currentText()).startswith('*'): + self.apply_text_search_restriction( + unicode(self.search_restriction.currentText())[1:]) else: - restriction = '' + r = unicode(self.search_restriction.currentText()) + if r is not None and r != '': + restriction = 'search:"%s"'%(r) + else: + restriction = '' + self._apply_search_restriction(restriction) + def _apply_search_restriction(self, restriction): self.saved_search.clear() # The order below is important. Set the restriction, force a '' search # to apply it, reset the tag browser to take it into account, then set diff --git a/src/calibre/gui2/shortcuts.py b/src/calibre/gui2/shortcuts.py index 5e56435e10..55ff625fdc 100644 --- a/src/calibre/gui2/shortcuts.py +++ b/src/calibre/gui2/shortcuts.py @@ -71,7 +71,7 @@ class Customize(QFrame, Ui_Frame): button = getattr(self, 'button%d'%which) font = QFont() button.setFont(font) - sequence = QKeySequence(code|int(ev.modifiers())) + sequence = QKeySequence(code|(int(ev.modifiers())&~Qt.KeypadModifier)) button.setText(sequence.toString()) self.capture = 0 setattr(self, 'shortcut%d'%which, sequence) @@ -195,7 +195,7 @@ class Shortcuts(QAbstractListModel): def get_match(self, event_or_sequence, ignore=tuple()): q = event_or_sequence if isinstance(q, QKeyEvent): - q = QKeySequence(q.key()|int(q.modifiers())) + q = QKeySequence(q.key()|(int(q.modifiers())&~Qt.KeypadModifier)) for key in self.order: if key not in ignore: for seq in self.get_sequences(key): diff --git a/src/calibre/gui2/store/__init__.py b/src/calibre/gui2/store/__init__.py new file mode 100644 index 0000000000..d58ccbda84 --- /dev/null +++ b/src/calibre/gui2/store/__init__.py @@ -0,0 +1,193 @@ +# -*- coding: utf-8 -*- + +from __future__ import (unicode_literals, division, absolute_import, print_function) + +__license__ = 'GPL 3' +__copyright__ = '2011, John Schember <john@nachtimwald.com>' +__docformat__ = 'restructuredtext en' + +class StorePlugin(object): # {{{ + ''' + A plugin representing an online ebook repository (store). The store can + be a commercial store that sells ebooks or a source of free downloadable + ebooks. + + Note that this class is the base class for these plugins, however, to + integrate the plugin with calibre's plugin system, you have to make a + wrapper class that references the actual plugin. See the + :mod:`calibre.customize.builtins` module for examples. + + If two :class:`StorePlugin` objects have the same name, the one with higher + priority takes precedence. + + Sub-classes must implement :meth:`open`, and :meth:`search`. + + Regarding :meth:`open`. Most stores only make themselves available + though a web site thus most store plugins will open using + :class:`calibre.gui2.store.web_store_dialog.WebStoreDialog`. This will + open a modal window and display the store website in a QWebView. + + Sub-classes should implement and use the :meth:`genesis` if they require + plugin specific initialization. They should not override or otherwise + reimplement :meth:`__init__`. + + Once initialized, this plugin has access to the main calibre GUI via the + :attr:`gui` member. You can access other plugins by name, for example:: + + self.gui.istores['Amazon Kindle'] + + Plugin authors can use affiliate programs within their plugin. The + distribution of money earned from a store plugin is 70/30. 70% going + to the pluin author / maintainer and 30% going to the calibre project. + + The easiest way to handle affiliate money payouts is to randomly select + between the author's affiliate id and calibre's affiliate id so that + 70% of the time the author's id is used. + + 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 + + self.gui = gui + self.name = name + self.base_plugin = None + self.config = JSONConfig('store/stores/' + self.name) + + def open(self, gui, parent=None, detail_item=None, external=False): + ''' + Open the store. + + :param gui: The main GUI. This will be used to have the job + system start downloading an item from the store. + + :param parent: The parent of the store dialog. This is used + to create modal dialogs. + + :param detail_item: A plugin specific reference to an item + in the store that the user should be shown. + + :param external: When False open an internal dialog with the + store. When True open the users default browser to the store's + web site. :param:`detail_item` should still be respected when external + is True. + ''' + raise NotImplementedError() + + def search(self, query, max_results=10, timeout=60): + ''' + Searches the store for items matching query. This should + return items as a generator. + + Don't be lazy with the search! Load as much data as possible in the + :class:`calibre.gui2.store.search_result.SearchResult` object. + However, if data (such as cover_url) + isn't available because the store does not display cover images then it's okay to + ignore it. + + At the very least a :class:`calibre.gui2.store.search_result.SearchResult` + returned by this function must have the title, author and id. + + If you have to parse multiple pages to get all of the data then implement + :meth:`get_deatils` for retrieving additional information. + + Also, by default search results can only include ebooks. A plugin can offer users + an option to include physical books in the search results but this must be + disabled by default. + + If a store doesn't provide search on it's own use something like a site specific + google search to get search results for this funtion. + + :param query: The string query search with. + :param max_results: The maximum number of results to return. + :param timeout: The maximum amount of time in seconds to spend downloading data for search results. + + :return: :class:`calibre.gui2.store.search_result.SearchResult` objects + item_data is plugin specific and is used in :meth:`open` to open to a specifc place in the store. + ''' + raise NotImplementedError() + + def get_details(self, search_result, timeout=60): + ''' + Delayed search for information about specific search items. + + Typically, this will be used when certain information such as + formats, drm status, cover url are not part of the main search + results and the information is on another web page. + + Using this function allows for the main information (title, author) + to be displayed in the search results while other information can + take extra time to load. Splitting retrieving data that takes longer + to load into a separate function will give the illusion of the search + being faster. + + :param search_result: A search result that need details set. + :param timeout: The maximum amount of time in seconds to spend downloading details. + + :return: True if the search_result was modified otherwise False + ''' + return False + + def update_cache(self, parent=None, timeout=60, force=False, suppress_progress=False): + ''' + Some plugins need to keep an local cache of available books. This function + is called to update the caches. It is recommended to call this function + from :meth:`open`. Especially if :meth:`open` does anything other than + open a web page. + + This function can be called at any time. It is up to the plugin to determine + if the cache really does need updating. Unless :param:`force` is True, then + the plugin must update the cache. The only time force should be True is if + this function is called by the plugin's configuration dialog. + + if :param:`suppress_progress` is False it is safe to assume that this function + is being called from the main GUI thread so it is safe and recommended to use + a QProgressDialog to display what is happening and allow the user to cancel + the operation. if :param:`suppress_progress` is True then run the update + silently. In this case there is no guarantee what thread is calling this + function so no Qt related functionality that requires being run in the main + GUI thread should be run. E.G. Open a QProgressDialog. + + :param parent: The parent object to be used by an GUI dialogs. + + :param timeout: The maximum amount of time that should be spent in + any given network connection. + + :param force: Force updating the cache even if the plugin has determined + it is not necessary. + + :param suppress_progress: Should a progress indicator be shown. + + :return: True if the cache was updated, False otherwise. + ''' + return False + + def do_genesis(self): + self.genesis() + + def genesis(self): + ''' + Plugin specific initialization. + ''' + pass + + def config_widget(self): + ''' + See :class:`calibre.customize.Plugin` for details. + ''' + raise NotImplementedError() + + def save_settings(self, config_widget): + ''' + See :class:`calibre.customize.Plugin` for details. + ''' + raise NotImplementedError() + + def customization_help(self, gui=False): + ''' + See :class:`calibre.customize.Plugin` for details. + ''' + raise NotImplementedError() + +# }}} diff --git a/src/calibre/gui2/store/amazon_de_plugin.py b/src/calibre/gui2/store/amazon_de_plugin.py new file mode 100644 index 0000000000..f7b17a2e83 --- /dev/null +++ b/src/calibre/gui2/store/amazon_de_plugin.py @@ -0,0 +1,34 @@ +# -*- coding: utf-8 -*- + +from __future__ import (unicode_literals, division, absolute_import, print_function) + +__license__ = 'GPL 3' +__copyright__ = '2011, John Schember <john@nachtimwald.com>' +__docformat__ = 'restructuredtext en' + +from PyQt4.Qt import QUrl + +from calibre.gui2 import open_url +from calibre.gui2.store.amazon_plugin import AmazonKindleStore + +class AmazonDEKindleStore(AmazonKindleStore): + ''' + For comments on the implementation, please see amazon_plugin.py + ''' + + search_url = 'http://www.amazon.de/s/?url=search-alias%3Ddigital-text&field-keywords=' + details_url = 'http://amazon.de/dp/' + drm_search_text = u'Gleichzeitige Verwendung von Geräten' + drm_free_text = u'Keine Einschränkung' + + def open(self, parent=None, detail_item=None, external=False): + aff_id = {'tag': 'charhale0a-21'} + store_link = ('http://www.amazon.de/gp/redirect.html?ie=UTF8&site-redirect=de' + '&tag=%(tag)s&linkCode=ur2&camp=1638&creative=19454' + '&location=http://www.amazon.de/ebooks-kindle/b?node=530886031') % aff_id + if detail_item: + aff_id['asin'] = detail_item + store_link = ('http://www.amazon.de/gp/redirect.html?ie=UTF8' + '&location=http://www.amazon.de/dp/%(asin)s&site-redirect=de' + '&tag=%(tag)s&linkCode=ur2&camp=1638&creative=6742') % aff_id + open_url(QUrl(store_link)) diff --git a/src/calibre/gui2/store/amazon_plugin.py b/src/calibre/gui2/store/amazon_plugin.py new file mode 100644 index 0000000000..b70d03ad0a --- /dev/null +++ b/src/calibre/gui2/store/amazon_plugin.py @@ -0,0 +1,210 @@ +# -*- coding: utf-8 -*- + +from __future__ import (unicode_literals, division, absolute_import, print_function) + +__license__ = 'GPL 3' +__copyright__ = '2011, John Schember <john@nachtimwald.com>' +__docformat__ = 'restructuredtext en' + +import random +import re +import urllib +from contextlib import closing + +from lxml import html + +from PyQt4.Qt import QUrl + +from calibre import browser +from calibre.gui2 import open_url +from calibre.gui2.store import StorePlugin +from calibre.gui2.store.search_result import SearchResult + +class AmazonKindleStore(StorePlugin): + + search_url = 'http://www.amazon.com/s/?url=search-alias%3Ddigital-text&field-keywords=' + details_url = 'http://amazon.com/dp/' + drm_search_text = u'Simultaneous Device Usage' + drm_free_text = u'Unlimited' + + def open(self, parent=None, detail_item=None, external=False): + ''' + Amazon comes with a number of difficulties. + + QWebView has major issues with Amazon.com. The largest of + issues is it simply doesn't work on a number of pages. + + When connecting to a number parts of Amazon.com (Kindle library + for instance) QNetworkAccessManager fails to connect with a + NetworkError of 399 - ProtocolFailure. The strange thing is, + when I check QNetworkRequest.HttpStatusCodeAttribute when the + 399 error is returned the status code is 200 (Ok). However, once + the QNetworkAccessManager decides there was a NetworkError it + does not download the page from Amazon. So I can't even set the + HTML in the QWebView myself. + + There is http://bugreports.qt.nokia.com/browse/QTWEBKIT-259 an + open bug about the issue but it is not correct. We can set the + useragent (Arora does) to something else and the above issue + will persist. This http://developer.qt.nokia.com/forums/viewthread/793 + gives a bit more information about the issue but as of now (27/Feb/2011) + there is no solution or work around. + + We cannot change the The linkDelegationPolicy to allow us to avoid + QNetworkAccessManager because it only works links. Forms aren't + included so the same issue persists on any part of the site (login) + that use a form to load a new page. + + Using an aStore was evaluated but I've decided against using it. + There are three major issues with an aStore. Because checkout is + handled by sending the user to Amazon we can't put it in a QWebView. + If we're sending the user to Amazon sending them there directly is + nicer. Also, we cannot put the aStore in a QWebView and let it open the + redirection the users default browser because the cookies with the + shopping cart won't transfer. + + Another issue with the aStore is how it handles the referral. It only + counts the referral for the items in the shopping card / the item + that directed the user to Amazon. Kindle books do not use the shopping + cart and send the user directly to Amazon for the purchase. In this + instance we would only get referral credit for the one book that the + aStore directs to Amazon that the user buys. Any other purchases we + won't get credit for. + + The last issue with the aStore is performance. Even though it's an + Amazon site it's alow. So much slower than Amazon.com that it makes + me not want to browse books using it. The look and feel are lesser + issues. So is the fact that it almost seems like the purchase is + with calibre. This can cause some support issues because we can't + do much for issues with Amazon.com purchase hiccups. + + Another option that was evaluated was the Product Advertising API. + The reasons against this are complexity. It would take a lot of work + to basically re-create Amazon.com within calibre. The Product + Advertising API is also designed with being run on a server not + in an app. The signing keys would have to be made avaliable to ever + calibre user which means bad things could be done with our account. + + The Product Advertising API also assumes the same browser for easy + shopping cart transfer to Amazon. With QWebView not working and there + not being an easy way to transfer cookies between a QWebView and the + users default browser this won't work well. + + We could create our own website on the calibre server and create an + Amazon Product Advertising API store. However, this goes back to the + complexity argument. Why spend the time recreating Amazon.com + + The final and largest issue against using the Product Advertising API + is the Efficiency Guidelines: + + "Each account used to access the Product Advertising API will be allowed + an initial usage limit of 2,000 requests per hour. Each account will + receive an additional 500 requests per hour (up to a maximum of 25,000 + requests per hour) for every $1 of shipped item revenue driven per hour + in a trailing 30-day period. Usage thresholds are recalculated daily based + on revenue performance." + + With over two million users a limit of 2,000 request per hour could + render our store unusable for no other reason than Amazon rate + limiting our traffic. + + The best (I use the term lightly here) solution is to open Amazon.com + in the users default browser and set the affiliate id as part of the url. + ''' + aff_id = {'tag': 'josbl0e-cpb-20'} + # Use Kovid's affiliate id 30% of the time. + if random.randint(1, 10) in (1, 2, 3): + aff_id['tag'] = 'calibrebs-20' + store_link = 'http://www.amazon.com/Kindle-eBooks/b/?ie=UTF&node=1286228011&ref_=%(tag)s&ref=%(tag)s&tag=%(tag)s&linkCode=ur2&camp=1789&creative=390957' % aff_id + if detail_item: + aff_id['asin'] = detail_item + store_link = 'http://www.amazon.com/dp/%(asin)s/?tag=%(tag)s' % aff_id + open_url(QUrl(store_link)) + + def search(self, query, max_results=10, timeout=60): + url = self.search_url + urllib.quote_plus(query) + br = browser() + + counter = max_results + with closing(br.open(url, timeout=timeout)) as f: + doc = html.fromstring(f.read()) + + # Amazon has two results pages. + is_shot = doc.xpath('boolean(//div[@id="shotgunMainResults"])') + # Horizontal grid of books. + if is_shot: + data_xpath = '//div[contains(@class, "result")]' + format_xpath = './/div[@class="productTitle"]/text()' + cover_xpath = './/div[@class="productTitle"]//img/@src' + # Vertical list of books. + else: + data_xpath = '//div[@class="productData"]' + format_xpath = './/span[@class="format"]/text()' + cover_xpath = '../div[@class="productImage"]/a/img/@src' + + 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_href = None + asin_a = data.xpath('.//div[@class="productTitle"]/a[1]') + if asin_a: + asin_href = asin_a[0].get('href', '') + m = re.search(r'/dp/(?P<asin>.+?)(/|$)', asin_href) + if m: + asin = m.group('asin') + else: + continue + else: + continue + + cover_url = ''.join(data.xpath(cover_xpath)) + + title = ''.join(data.xpath('.//div[@class="productTitle"]/a/text()')) + price = ''.join(data.xpath('.//div[@class="newPrice"]/span/text()')) + + if is_shot: + author = format.split(' by ')[-1] + else: + author = ''.join(data.xpath('.//div[@class="productTitle"]/span[@class="ptBrand"]/text()')) + author = author.split(' by ')[-1] + + 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.formats = 'Kindle' + + yield s + + def get_details(self, search_result, timeout): + url = self.details_url + + br = browser() + with closing(br.open(url + search_result.detail_item, timeout=timeout)) as nf: + idata = html.fromstring(nf.read()) + if idata.xpath('boolean(//div[@class="content"]//li/b[contains(text(), "' + + self.drm_search_text + '")])'): + if idata.xpath('boolean(//div[@class="content"]//li[contains(., "' + + self.drm_free_text + '") and contains(b, "' + + self.drm_search_text + '")])'): + search_result.drm = SearchResult.DRM_UNLOCKED + else: + search_result.drm = SearchResult.DRM_UNKNOWN + else: + search_result.drm = SearchResult.DRM_LOCKED + return True diff --git a/src/calibre/gui2/store/amazon_uk_plugin.py b/src/calibre/gui2/store/amazon_uk_plugin.py new file mode 100644 index 0000000000..1448e1548a --- /dev/null +++ b/src/calibre/gui2/store/amazon_uk_plugin.py @@ -0,0 +1,114 @@ +# -*- coding: utf-8 -*- + +from __future__ import (unicode_literals, division, absolute_import, print_function) + +__license__ = 'GPL 3' +__copyright__ = '2011, John Schember <john@nachtimwald.com>' +__docformat__ = 'restructuredtext en' + +import urllib +from contextlib import closing + +from lxml import html + +from PyQt4.Qt import QUrl + +from calibre import browser +from calibre.gui2 import open_url +from calibre.gui2.store.amazon_plugin import AmazonKindleStore +from calibre.gui2.store.search_result import SearchResult + +class AmazonUKKindleStore(AmazonKindleStore): + ''' + For comments on the implementation, please see amazon_plugin.py + ''' + + search_url = 'http://www.amazon.co.uk/s/?url=search-alias%3Ddigital-text&field-keywords=' + details_url = 'http://amazon.co.uk/dp/' + + def open(self, parent=None, detail_item=None, external=False): + 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' % aff_id + + if detail_item: + aff_id['asin'] = detail_item + store_link = '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' % aff_id + open_url(QUrl(store_link)) + + def search(self, query, max_results=10, timeout=60): + url = self.search_url + urllib.quote_plus(query) + br = browser() + + counter = max_results + with closing(br.open(url, timeout=timeout)) as f: + doc = html.fromstring(f.read()) + + # Amazon has two results pages. + is_shot = doc.xpath('boolean(//div[@id="shotgunMainResults"])') + # Horizontal grid of books. + if is_shot: + data_xpath = '//div[contains(@class, "result")]' + cover_xpath = './/div[@class="productTitle"]//img/@src' + # Vertical list of books. + else: + data_xpath = '//div[contains(@class, "product")]' + cover_xpath = './div[@class="productImage"]/a/img/@src' + + for data in doc.xpath(data_xpath): + if counter <= 0: + break + + # We must have an asin otherwise we can't easily reference the + # book later. + asin = ''.join(data.xpath('./@name')) + if not asin: + continue + cover_url = ''.join(data.xpath(cover_xpath)) + + title = ''.join(data.xpath('.//div[@class="productTitle"]/a/text()')) + price = ''.join(data.xpath('.//div[@class="newPrice"]/span/text()')) + + counter -= 1 + + s = SearchResult() + s.cover_url = cover_url.strip() + s.title = title.strip() + s.price = price.strip() + s.detail_item = asin.strip() + s.formats = 'Kindle' + + if is_shot: + # Amazon UK does not include the author on the grid layout + s.author = '' + self.get_details(s, timeout) + else: + author = ''.join(data.xpath('.//div[@class="productTitle"]/span[@class="ptBrand"]/text()')) + s.author = author.split(' by ')[-1].strip() + + yield s + + def get_details(self, search_result, timeout): + # We might already have been called. + if search_result.drm: + return + + url = self.details_url + + br = browser() + with closing(br.open(url + search_result.detail_item, timeout=timeout)) as nf: + idata = html.fromstring(nf.read()) + if not search_result.author: + search_result.author = ''.join(idata.xpath('//div[@class="buying" and contains(., "Author")]/a/text()')) + if idata.xpath('boolean(//div[@class="content"]//li/b[contains(text(), "' + + self.drm_search_text + '")])'): + if idata.xpath('boolean(//div[@class="content"]//li[contains(., "' + + self.drm_free_text + '") and contains(b, "' + + self.drm_search_text + '")])'): + search_result.drm = SearchResult.DRM_UNLOCKED + else: + search_result.drm = SearchResult.DRM_UNKNOWN + else: + search_result.drm = SearchResult.DRM_LOCKED + return True + + diff --git a/src/calibre/gui2/store/archive_org_plugin.py b/src/calibre/gui2/store/archive_org_plugin.py new file mode 100644 index 0000000000..e8e96b3839 --- /dev/null +++ b/src/calibre/gui2/store/archive_org_plugin.py @@ -0,0 +1,89 @@ +# -*- coding: utf-8 -*- + +from __future__ import (unicode_literals, division, absolute_import, print_function) + +__license__ = 'GPL 3' +__copyright__ = '2011, John Schember <john@nachtimwald.com>' +__docformat__ = 'restructuredtext en' + +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 ArchiveOrgStore(BasicStoreConfig, StorePlugin): + + def open(self, parent=None, detail_item=None, external=False): + url = 'http://www.archive.org/details/texts' + + if detail_item: + detail_item = url_slash_cleaner('http://www.archive.org' + detail_item) + + 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): + query = query + ' AND mediatype:texts' + url = 'http://www.archive.org/search.php?query=' + urllib.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('//td[@class="hitCell"]'): + if counter <= 0: + break + + id = ''.join(data.xpath('.//a[@class="titleLink"]/@href')) + if not id: + continue + + title = ''.join(data.xpath('.//a[@class="titleLink"]//text()')) + authors = data.xpath('.//text()') + if not authors: + continue + author = None + for a in authors: + if '-' in a: + author = a.replace('-', ' ').strip() + if author: + break + if not author: + continue + + counter -= 1 + + s = SearchResult() + s.title = title.strip() + s.author = author.strip() + s.price = '$0.00' + s.detail_item = id.strip() + s.drm = SearchResult.DRM_UNLOCKED + + yield s + + def get_details(self, search_result, timeout): + url = url_slash_cleaner('http://www.archive.org' + search_result.detail_item) + + br = browser() + with closing(br.open(url, timeout=timeout)) as nf: + idata = html.fromstring(nf.read()) + formats = ', '.join(idata.xpath('//p[@id="dl" and @class="content"]//a/text()')) + search_result.formats = formats.upper() + + return True diff --git a/src/calibre/gui2/store/baen_webscription_plugin.py b/src/calibre/gui2/store/baen_webscription_plugin.py new file mode 100644 index 0000000000..5be7e9c161 --- /dev/null +++ b/src/calibre/gui2/store/baen_webscription_plugin.py @@ -0,0 +1,90 @@ +# -*- coding: utf-8 -*- + +from __future__ import (unicode_literals, division, absolute_import, print_function) + +__license__ = 'GPL 3' +__copyright__ = '2011, John Schember <john@nachtimwald.com>' +__docformat__ = 'restructuredtext en' + +import re +import urllib2 +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 BaenWebScriptionStore(BasicStoreConfig, StorePlugin): + + def open(self, parent=None, detail_item=None, external=False): + url = 'http://www.webscription.net/' + + if external or self.config.get('open_external', False): + if detail_item: + url = url + detail_item + open_url(QUrl(url_slash_cleaner(url))) + else: + detail_url = None + if detail_item: + detail_url = url + detail_item + d = WebStoreDialog(self.gui, url, parent, detail_url) + d.setWindowTitle(self.name) + d.set_tags(self.config.get('tags', '')) + d.exec_() + + def search(self, query, max_results=10, timeout=60): + url = 'http://www.webscription.net/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/tr/td/img[@src="skins/Skin_1/images/matchingproducts.gif"]/..//tr'): + if counter <= 0: + break + + id = ''.join(data.xpath('./td[1]/a/@href')) + if not id: + continue + + title = ''.join(data.xpath('./td[1]/a/text()')) + + author = '' + cover_url = '' + price = '' + + with closing(br.open('http://www.webscription.net/' + id.strip(), timeout=timeout/4)) as nf: + idata = html.fromstring(nf.read()) + author = ''.join(idata.xpath('//span[@class="ProductNameText"]/../b/text()')) + author = author.split('by ')[-1] + price = ''.join(idata.xpath('//span[@class="variantprice"]/text()')) + a, b, price = price.partition('$') + price = b + price + + pnum = '' + mo = re.search(r'p-(?P<num>\d+)-', id.strip()) + if mo: + pnum = mo.group('num') + if pnum: + cover_url = 'http://www.webscription.net/' + ''.join(idata.xpath('//img[@id="ProductPic%s"]/@src' % pnum)) + + counter -= 1 + + s = SearchResult() + s.cover_url = cover_url + s.title = title.strip() + s.author = author.strip() + s.price = price + 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/basic_config.py b/src/calibre/gui2/store/basic_config.py new file mode 100644 index 0000000000..5e59b63694 --- /dev/null +++ b/src/calibre/gui2/store/basic_config.py @@ -0,0 +1,40 @@ +# -*- coding: utf-8 -*- + +from __future__ import (unicode_literals, division, absolute_import, print_function) + +__license__ = 'GPL 3' +__copyright__ = '2011, John Schember <john@nachtimwald.com>' +__docformat__ = 'restructuredtext en' + +from PyQt4.Qt import QWidget + +from calibre.gui2.store.basic_config_widget_ui import Ui_Form + +class BasicStoreConfigWidget(QWidget, Ui_Form): + + def __init__(self, store): + QWidget.__init__(self) + self.setupUi(self) + + self.store = store + + self.load_setings() + + def load_setings(self): + config = self.store.config + + self.open_external.setChecked(config.get('open_external', False)) + self.tags.setText(config.get('tags', '')) + +class BasicStoreConfig(object): + + def customization_help(self, gui=False): + return 'Customize the behavior of this store.' + + def config_widget(self): + return BasicStoreConfigWidget(self) + + def save_settings(self, config_widget): + self.config['open_external'] = config_widget.open_external.isChecked() + tags = unicode(config_widget.tags.text()) + self.config['tags'] = tags diff --git a/src/calibre/gui2/store/basic_config_widget.ui b/src/calibre/gui2/store/basic_config_widget.ui new file mode 100644 index 0000000000..cadf7813d6 --- /dev/null +++ b/src/calibre/gui2/store/basic_config_widget.ui @@ -0,0 +1,38 @@ +<?xml version="1.0" encoding="UTF-8"?> +<ui version="4.0"> + <class>Form</class> + <widget class="QWidget" name="Form"> + <property name="geometry"> + <rect> + <x>0</x> + <y>0</y> + <width>460</width> + <height>69</height> + </rect> + </property> + <property name="windowTitle"> + <string>Form</string> + </property> + <layout class="QGridLayout" name="gridLayout"> + <item row="1" column="0"> + <widget class="QLabel" name="label"> + <property name="text"> + <string>Added Tags:</string> + </property> + </widget> + </item> + <item row="1" column="1"> + <widget class="QLineEdit" name="tags"/> + </item> + <item row="0" column="0" colspan="2"> + <widget class="QCheckBox" name="open_external"> + <property name="text"> + <string>Open store in external web browswer</string> + </property> + </widget> + </item> + </layout> + </widget> + <resources/> + <connections/> +</ui> diff --git a/src/calibre/gui2/store/beam_ebooks_de_plugin.py b/src/calibre/gui2/store/beam_ebooks_de_plugin.py new file mode 100644 index 0000000000..b589a8c310 --- /dev/null +++ b/src/calibre/gui2/store/beam_ebooks_de_plugin.py @@ -0,0 +1,92 @@ +# -*- coding: utf-8 -*- + +from __future__ import (unicode_literals, division, absolute_import, print_function) + +__license__ = 'GPL 3' +__copyright__ = '2011, John Schember <john@nachtimwald.com>' +__docformat__ = 'restructuredtext en' + +import urllib2 +from contextlib import closing + +from lxml import html + +from PyQt4.Qt import QUrl + +from calibre import browser +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 BeamEBooksDEStore(BasicStoreConfig, StorePlugin): + + def open(self, parent=None, detail_item=None, external=False): + url = 'http://klick.affiliwelt.net/klick.php?bannerid=10072&pid=32307&prid=908' + url_details = ('http://klick.affiliwelt.net/klick.php?' + 'bannerid=10730&pid=32307&prid=908&prodid={0}') + + if external or self.config.get('open_external', False): + if detail_item: + url = url_details.format(detail_item) + open_url(QUrl(url)) + else: + detail_url = None + if detail_item: + detail_url = url_details.format(detail_item) + d = WebStoreDialog(self.gui, url, parent, detail_url) + d.setWindowTitle(self.name) + d.set_tags(self.config.get('tags', '')) + d.exec_() + + def search(self, query, max_results=10, timeout=60): + url = 'http://www.beam-ebooks.de/suchergebnis.php?Type=&sw=' + 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[tr/td/div[@class="stil2"]]'): + if counter <= 0: + break + + id = ''.join(data.xpath('./tr/td/div[@class="stil2"]/a/@href')).strip() + if not id: + continue + id = id[7:] + cover_url = ''.join(data.xpath('./tr/td[1]/a/img/@src')) + if cover_url: + cover_url = 'http://www.beam-ebooks.de' + cover_url + title = ''.join(data.xpath('./tr/td/div[@class="stil2"]/a/b/text()')) + author = ' '.join(data.xpath('./tr/td/div[@class="stil2"]/' + 'child::b/text()' + '|' + './tr/td/div[@class="stil2"]/' + 'child::strong/text()')) + price = ''.join(data.xpath('./tr/td[3]/text()')) + pdf = data.xpath( + 'boolean(./tr/td[3]/a/img[contains(@alt, "PDF")]/@alt)') + epub = data.xpath( + 'boolean(./tr/td[3]/a/img[contains(@alt, "ePub")]/@alt)') + mobi = data.xpath( + 'boolean(./tr/td[3]/a/img[contains(@alt, "Mobipocket")]/@alt)') + counter -= 1 + + s = SearchResult() + s.cover_url = cover_url + s.title = title.strip() + s.author = author.strip() + s.price = price + s.drm = SearchResult.DRM_UNLOCKED + s.detail_item = id + formats = [] + if epub: + formats.append('ePub') + if pdf: + formats.append('PDF') + if mobi: + formats.append('MOBI') + s.formats = ', '.join(formats) + + yield s diff --git a/src/calibre/gui2/store/bewrite_plugin.py b/src/calibre/gui2/store/bewrite_plugin.py new file mode 100644 index 0000000000..bfd543db49 --- /dev/null +++ b/src/calibre/gui2/store/bewrite_plugin.py @@ -0,0 +1,97 @@ +# -*- coding: utf-8 -*- + +from __future__ import (unicode_literals, division, absolute_import, print_function) + +__license__ = 'GPL 3' +__copyright__ = '2011, John Schember <john@nachtimwald.com>' +__docformat__ = 'restructuredtext en' + +import urllib2 +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 BeWriteStore(BasicStoreConfig, StorePlugin): + + def open(self, parent=None, detail_item=None, external=False): + url = 'http://www.bewrite.net/mm5/merchant.mvc?Screen=SFNT' + + 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 = 'http://www.bewrite.net/mm5/merchant.mvc?Search_Code=B&Screen=SRCH&Search=' + 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('//div[@id="content"]//table/tr[position() > 1]'): + if counter <= 0: + break + + id = ''.join(data.xpath('.//a/@href')) + if not id: + continue + + heading = ''.join(data.xpath('./td[2]//text()')) + title, q, author = heading.partition('by ') + cover_url = '' + price = '' + + 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 = id.strip() + s.drm = SearchResult.DRM_UNLOCKED + + yield s + + def get_details(self, search_result, timeout): + br = browser() + + 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()')) + if not price: + 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[1]/@src') + if cover_img: + cover_url = 'http://www.bewrite.net/mm5/' + cover_img[0] + search_result.cover_url = cover_url.strip() + + formats = set([]) + if idata.xpath('boolean(//div[@id="content"]//td[contains(text(), "ePub")])'): + formats.add('EPUB') + if idata.xpath('boolean(//div[@id="content"]//td[contains(text(), "PDF")])'): + formats.add('PDF') + if idata.xpath('boolean(//div[@id="content"]//td[contains(text(), "MOBI")])'): + formats.add('MOBI') + search_result.formats = ', '.join(list(formats)) + + return True diff --git a/src/calibre/gui2/store/bn_plugin.py b/src/calibre/gui2/store/bn_plugin.py new file mode 100644 index 0000000000..62826e825d --- /dev/null +++ b/src/calibre/gui2/store/bn_plugin.py @@ -0,0 +1,82 @@ +# -*- coding: utf-8 -*- + +from __future__ import (unicode_literals, division, absolute_import, print_function) + +__license__ = 'GPL 3' +__copyright__ = '2011, John Schember <john@nachtimwald.com>' +__docformat__ = 'restructuredtext en' + +import random +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 BNStore(BasicStoreConfig, StorePlugin): + + def open(self, parent=None, detail_item=None, external=False): + pub_id = '21000000000352219' + # Use Kovid's affiliate id 30% of the time. + if random.randint(1, 10) in (1, 2, 3): + pub_id = '21000000000352583' + + url = 'http://gan.doubleclick.net/gan_click?lid=41000000028437369&pubid=' + pub_id + + if detail_item: + mo = re.search(r'(?<=/)(?P<isbn>\d+)(?=/|$)', detail_item) + if mo: + isbn = mo.group('isbn') + detail_item = 'http://gan.doubleclick.net/gan_click?lid=41000000012871747&pid=' + isbn + '&adurl=' + detail_item + '&pubid=' + pub_id + + 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 = 'http://productsearch.barnesandnoble.com/search/results.aspx?STORE=EBOOK&SZE=%s&WRD=' % max_results + url += 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('//ul[contains(@class, "wgt-search-results-display")]/li[contains(@class, "search-result-item") and contains(@class, "nook-result-item")]'): + if counter <= 0: + break + + id = ''.join(data.xpath('.//div[contains(@class, "wgt-product-image-module")]/a/@href')) + if not id: + continue + cover_url = ''.join(data.xpath('.//div[contains(@class, "wgt-product-image-module")]/a/img/@src')) + + title = ''.join(data.xpath('.//span[@class="product-title"]/a/text()')) + author = ', '.join(data.xpath('.//span[@class="contributers-line"]/a/text()')) + price = ''.join(data.xpath('.//span[contains(@class, "onlinePriceValue2")]/text()')) + + counter -= 1 + + s = SearchResult() + s.cover_url = cover_url + s.title = title.strip() + s.author = author.strip() + s.price = price + s.detail_item = id.strip() + s.drm = SearchResult.DRM_UNKNOWN + s.formats = 'Nook' + + yield s diff --git a/src/calibre/gui2/store/config/__init__.py b/src/calibre/gui2/store/config/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/src/calibre/gui2/store/config/chooser/__init__.py b/src/calibre/gui2/store/config/chooser/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/src/calibre/gui2/store/config/chooser/adv_search_builder.py b/src/calibre/gui2/store/config/chooser/adv_search_builder.py new file mode 100644 index 0000000000..d22554b111 --- /dev/null +++ b/src/calibre/gui2/store/config/chooser/adv_search_builder.py @@ -0,0 +1,135 @@ +# -*- coding: utf-8 -*- + +from __future__ import (unicode_literals, division, absolute_import, print_function) + +__license__ = 'GPL 3' +__copyright__ = '2011, John Schember <john@nachtimwald.com>' +__docformat__ = 'restructuredtext en' + +import re + +from PyQt4.Qt import (QDialog, QDialogButtonBox) + +from calibre.gui2.store.config.chooser.adv_search_builder_ui import Ui_Dialog +from calibre.library.caches import CONTAINS_MATCH, EQUALS_MATCH + +class AdvSearchBuilderDialog(QDialog, Ui_Dialog): + + def __init__(self, parent): + QDialog.__init__(self, parent) + self.setupUi(self) + + self.buttonBox.accepted.connect(self.advanced_search_button_pushed) + self.tab_2_button_box.accepted.connect(self.accept) + self.tab_2_button_box.rejected.connect(self.reject) + self.clear_button.clicked.connect(self.clear_button_pushed) + self.adv_search_used = False + self.mc = '' + + self.tabWidget.setCurrentIndex(0) + self.tabWidget.currentChanged[int].connect(self.tab_changed) + self.tab_changed(0) + + def tab_changed(self, idx): + if idx == 1: + self.tab_2_button_box.button(QDialogButtonBox.Ok).setDefault(True) + else: + self.buttonBox.button(QDialogButtonBox.Ok).setDefault(True) + + def advanced_search_button_pushed(self): + self.adv_search_used = True + self.accept() + + def clear_button_pushed(self): + self.name_box.setText('') + self.description_box.setText('') + self.headquarters_box.setText('') + self.format_box.setText('') + self.enabled_combo.setCurrentIndex(0) + self.drm_combo.setCurrentIndex(0) + self.affiliate_combo.setCurrentIndex(0) + + def tokens(self, raw): + phrases = re.findall(r'\s*".*?"\s*', raw) + for f in phrases: + raw = raw.replace(f, ' ') + phrases = [t.strip('" ') for t in phrases] + return ['"' + self.mc + t + '"' for t in phrases + [r.strip() for r in raw.split()]] + + def search_string(self): + if self.adv_search_used: + return self.adv_search_string() + else: + return self.box_search_string() + + def adv_search_string(self): + mk = self.matchkind.currentIndex() + if mk == CONTAINS_MATCH: + self.mc = '' + elif mk == EQUALS_MATCH: + self.mc = '=' + else: + self.mc = '~' + all, any, phrase, none = map(lambda x: unicode(x.text()), + (self.all, self.any, self.phrase, self.none)) + all, any, none = map(self.tokens, (all, any, none)) + phrase = phrase.strip() + all = ' and '.join(all) + any = ' or '.join(any) + none = ' and not '.join(none) + ans = '' + if phrase: + ans += '"%s"'%phrase + if all: + ans += (' and ' if ans else '') + all + if none: + ans += (' and not ' if ans else 'not ') + none + if any: + ans += (' or ' if ans else '') + any + return ans + + def token(self): + txt = unicode(self.text.text()).strip() + if txt: + if self.negate.isChecked(): + txt = '!'+txt + tok = self.FIELDS[unicode(self.field.currentText())]+txt + if re.search(r'\s', tok): + tok = '"%s"'%tok + return tok + + def box_search_string(self): + mk = self.matchkind.currentIndex() + if mk == CONTAINS_MATCH: + self.mc = '' + elif mk == EQUALS_MATCH: + self.mc = '=' + else: + self.mc = '~' + + ans = [] + self.box_last_values = {} + name = unicode(self.name_box.text()).strip() + if name: + ans.append('name:"' + self.mc + name + '"') + description = unicode(self.description_box.text()).strip() + if description: + ans.append('description:"' + self.mc + description + '"') + headquarters = unicode(self.headquarters_box.text()).strip() + if headquarters: + ans.append('headquarters:"' + self.mc + headquarters + '"') + format = unicode(self.format_box.text()).strip() + if format: + ans.append('format:"' + self.mc + format + '"') + enabled = unicode(self.enabled_combo.currentText()).strip() + if enabled: + ans.append('enabled:' + enabled) + drm = unicode(self.drm_combo.currentText()).strip() + if drm: + ans.append('drm:' + drm) + affiliate = unicode(self.affiliate_combo.currentText()).strip() + if affiliate: + ans.append('affiliate:' + affiliate) + if ans: + return ' and '.join(ans) + return '' diff --git a/src/calibre/gui2/store/config/chooser/adv_search_builder.ui b/src/calibre/gui2/store/config/chooser/adv_search_builder.ui new file mode 100644 index 0000000000..63cf596bea --- /dev/null +++ b/src/calibre/gui2/store/config/chooser/adv_search_builder.ui @@ -0,0 +1,442 @@ +<?xml version="1.0" encoding="UTF-8"?> +<ui version="4.0"> + <class>Dialog</class> + <widget class="QDialog" name="Dialog"> + <property name="geometry"> + <rect> + <x>0</x> + <y>0</y> + <width>752</width> + <height>472</height> + </rect> + </property> + <property name="windowTitle"> + <string>Advanced Search</string> + </property> + <property name="windowIcon"> + <iconset> + <normaloff>:/images/search.png</normaloff>:/images/search.png</iconset> + </property> + <layout class="QGridLayout" name="gridLayout_2"> + <item row="0" column="0"> + <widget class="QLabel" name="label_5"> + <property name="text"> + <string>&What kind of match to use:</string> + </property> + <property name="buddy"> + <cstring>matchkind</cstring> + </property> + </widget> + </item> + <item row="0" column="1"> + <widget class="QComboBox" name="matchkind"> + <item> + <property name="text"> + <string>Contains: the word or phrase matches anywhere in the metadata field</string> + </property> + </item> + <item> + <property name="text"> + <string>Equals: the word or phrase must match the entire metadata field</string> + </property> + </item> + <item> + <property name="text"> + <string>Regular expression: the expression must match anywhere in the metadata field</string> + </property> + </item> + </widget> + </item> + <item row="2" column="0" colspan="2"> + <widget class="QTabWidget" name="tabWidget"> + <property name="currentIndex"> + <number>0</number> + </property> + <widget class="QWidget" name="tab"> + <attribute name="title"> + <string>A&dvanced Search</string> + </attribute> + <layout class="QGridLayout" name="gridLayout_3"> + <item row="0" column="0"> + <widget class="QGroupBox" name="groupBox"> + <property name="title"> + <string>Find entries that have...</string> + </property> + <layout class="QVBoxLayout" name="verticalLayout"> + <item> + <layout class="QHBoxLayout" name="horizontalLayout"> + <item> + <widget class="QLabel" name="label"> + <property name="text"> + <string>&All these words:</string> + </property> + <property name="buddy"> + <cstring>all</cstring> + </property> + </widget> + </item> + <item> + <widget class="QLineEdit" name="all"/> + </item> + </layout> + </item> + <item> + <layout class="QHBoxLayout" name="horizontalLayout_2"> + <item> + <widget class="QLabel" name="label_2"> + <property name="text"> + <string>This exact &phrase:</string> + </property> + <property name="buddy"> + <cstring>all</cstring> + </property> + </widget> + </item> + <item> + <widget class="QLineEdit" name="phrase"/> + </item> + </layout> + </item> + <item> + <layout class="QHBoxLayout" name="horizontalLayout_3"> + <item> + <widget class="QLabel" name="label_3"> + <property name="text"> + <string>&One or more of these words:</string> + </property> + <property name="buddy"> + <cstring>all</cstring> + </property> + </widget> + </item> + <item> + <widget class="QLineEdit" name="any"/> + </item> + </layout> + </item> + </layout> + </widget> + </item> + <item row="1" column="0"> + <widget class="QGroupBox" name="groupBox_2"> + <property name="title"> + <string>But dont show entries that have...</string> + </property> + <layout class="QVBoxLayout" name="verticalLayout_2"> + <item> + <layout class="QHBoxLayout" name="horizontalLayout_4"> + <item> + <widget class="QLabel" name="label_4"> + <property name="text"> + <string>Any of these &unwanted words:</string> + </property> + <property name="buddy"> + <cstring>all</cstring> + </property> + </widget> + </item> + <item> + <widget class="QLineEdit" name="none"/> + </item> + </layout> + </item> + <item> + <widget class="QLabel" name="label_6"> + <property name="maximumSize"> + <size> + <width>16777215</width> + <height>30</height> + </size> + </property> + <property name="text"> + <string>See the <a href="http://manual.calibre-ebook.com/gui.html#the-search-interface">User Manual</a> for more help</string> + </property> + <property name="openExternalLinks"> + <bool>true</bool> + </property> + </widget> + </item> + </layout> + </widget> + </item> + <item row="2" column="0"> + <spacer name="verticalSpacer_2"> + <property name="orientation"> + <enum>Qt::Vertical</enum> + </property> + <property name="sizeHint" stdset="0"> + <size> + <width>20</width> + <height>40</height> + </size> + </property> + </spacer> + </item> + <item row="3" column="0"> + <widget class="QDialogButtonBox" name="buttonBox"> + <property name="orientation"> + <enum>Qt::Horizontal</enum> + </property> + <property name="standardButtons"> + <set>QDialogButtonBox::Cancel|QDialogButtonBox::Ok</set> + </property> + </widget> + </item> + </layout> + </widget> + <widget class="QWidget" name="tab_2"> + <attribute name="title"> + <string>Nam&e/Description ...</string> + </attribute> + <layout class="QGridLayout" name="gridLayout"> + <item row="1" column="0"> + <widget class="QLabel" name="label_7"> + <property name="text"> + <string>&Name:</string> + </property> + <property name="buddy"> + <cstring>name_box</cstring> + </property> + </widget> + </item> + <item row="1" column="1"> + <widget class="EnLineEdit" name="name_box"> + <property name="toolTip"> + <string>Enter the title.</string> + </property> + </widget> + </item> + <item row="2" column="0"> + <widget class="QLabel" name="label_8"> + <property name="text"> + <string>&Description:</string> + </property> + <property name="buddy"> + <cstring>description_box</cstring> + </property> + </widget> + </item> + <item row="3" column="0"> + <widget class="QLabel" name="price_label"> + <property name="text"> + <string>&Headquarters:</string> + </property> + <property name="buddy"> + <cstring>headquarters_box</cstring> + </property> + </widget> + </item> + <item row="9" column="0" colspan="2"> + <layout class="QHBoxLayout" name="horizontalLayout_6"> + <item> + <widget class="QPushButton" name="clear_button"> + <property name="text"> + <string>&Clear</string> + </property> + </widget> + </item> + <item> + <widget class="QDialogButtonBox" name="tab_2_button_box"> + <property name="standardButtons"> + <set>QDialogButtonBox::Cancel|QDialogButtonBox::Ok</set> + </property> + </widget> + </item> + </layout> + </item> + <item row="8" column="1"> + <spacer name="verticalSpacer"> + <property name="orientation"> + <enum>Qt::Vertical</enum> + </property> + <property name="sizeHint" stdset="0"> + <size> + <width>20</width> + <height>40</height> + </size> + </property> + </spacer> + </item> + <item row="0" column="0" colspan="2"> + <widget class="QLabel" name="label_11"> + <property name="text"> + <string>Search only in specific fields:</string> + </property> + </widget> + </item> + <item row="2" column="1"> + <widget class="EnLineEdit" name="description_box"/> + </item> + <item row="4" column="1"> + <widget class="QLineEdit" name="format_box"/> + </item> + <item row="4" column="0"> + <widget class="QLabel" name="label_10"> + <property name="text"> + <string>&Format:</string> + </property> + <property name="buddy"> + <cstring>format_box</cstring> + </property> + </widget> + </item> + <item row="3" column="1"> + <widget class="EnLineEdit" name="headquarters_box"/> + </item> + <item row="5" column="0"> + <widget class="QLabel" name="label_9"> + <property name="text"> + <string>Enabled:</string> + </property> + </widget> + </item> + <item row="6" column="0"> + <widget class="QLabel" name="label_12"> + <property name="text"> + <string>DRM:</string> + </property> + </widget> + </item> + <item row="5" column="1"> + <widget class="QComboBox" name="enabled_combo"> + <item> + <property name="text"> + <string/> + </property> + </item> + <item> + <property name="text"> + <string>true</string> + </property> + </item> + <item> + <property name="text"> + <string>false</string> + </property> + </item> + </widget> + </item> + <item row="6" column="1"> + <widget class="QComboBox" name="drm_combo"> + <item> + <property name="text"> + <string/> + </property> + </item> + <item> + <property name="text"> + <string>true</string> + </property> + </item> + <item> + <property name="text"> + <string>false</string> + </property> + </item> + </widget> + </item> + <item row="7" column="0"> + <widget class="QLabel" name="label_13"> + <property name="text"> + <string>Affiliate:</string> + </property> + </widget> + </item> + <item row="7" column="1"> + <widget class="QComboBox" name="affiliate_combo"> + <item> + <property name="text"> + <string/> + </property> + </item> + <item> + <property name="text"> + <string>true</string> + </property> + </item> + <item> + <property name="text"> + <string>false</string> + </property> + </item> + </widget> + </item> + </layout> + </widget> + </widget> + </item> + <item row="1" column="1"> + <spacer name="verticalSpacer_3"> + <property name="orientation"> + <enum>Qt::Vertical</enum> + </property> + <property name="sizeHint" stdset="0"> + <size> + <width>20</width> + <height>40</height> + </size> + </property> + </spacer> + </item> + </layout> + </widget> + <customwidgets> + <customwidget> + <class>EnLineEdit</class> + <extends>QLineEdit</extends> + <header>widgets.h</header> + </customwidget> + </customwidgets> + <tabstops> + <tabstop>all</tabstop> + <tabstop>phrase</tabstop> + <tabstop>any</tabstop> + <tabstop>none</tabstop> + <tabstop>buttonBox</tabstop> + <tabstop>name_box</tabstop> + <tabstop>description_box</tabstop> + <tabstop>headquarters_box</tabstop> + <tabstop>format_box</tabstop> + <tabstop>clear_button</tabstop> + <tabstop>tab_2_button_box</tabstop> + <tabstop>tabWidget</tabstop> + <tabstop>matchkind</tabstop> + </tabstops> + <resources> + <include location="../../../../resources/images.qrc"/> + </resources> + <connections> + <connection> + <sender>buttonBox</sender> + <signal>accepted()</signal> + <receiver>Dialog</receiver> + <slot>accept()</slot> + <hints> + <hint type="sourcelabel"> + <x>248</x> + <y>254</y> + </hint> + <hint type="destinationlabel"> + <x>157</x> + <y>274</y> + </hint> + </hints> + </connection> + <connection> + <sender>buttonBox</sender> + <signal>rejected()</signal> + <receiver>Dialog</receiver> + <slot>reject()</slot> + <hints> + <hint type="sourcelabel"> + <x>316</x> + <y>260</y> + </hint> + <hint type="destinationlabel"> + <x>286</x> + <y>274</y> + </hint> + </hints> + </connection> + </connections> +</ui> diff --git a/src/calibre/gui2/store/config/chooser/chooser_dialog.py b/src/calibre/gui2/store/config/chooser/chooser_dialog.py new file mode 100644 index 0000000000..c94796dc11 --- /dev/null +++ b/src/calibre/gui2/store/config/chooser/chooser_dialog.py @@ -0,0 +1,28 @@ +# -*- coding: utf-8 -*- + +from __future__ import (unicode_literals, division, absolute_import, print_function) + +__license__ = 'GPL 3' +__copyright__ = '2011, John Schember <john@nachtimwald.com>' +__docformat__ = 'restructuredtext en' + +from PyQt4.Qt import (QDialog, QDialogButtonBox, QVBoxLayout) + +from calibre.gui2.store.config.chooser.chooser_widget import StoreChooserWidget + +class StoreChooserDialog(QDialog): + + def __init__(self, parent): + QDialog.__init__(self, parent) + + self.setWindowTitle(_('Choose stores')) + + button_box = QDialogButtonBox(QDialogButtonBox.Close) + button_box.accepted.connect(self.accept) + button_box.rejected.connect(self.reject) + v = QVBoxLayout(self) + self.config_widget = StoreChooserWidget() + v.addWidget(self.config_widget) + v.addWidget(button_box) + + self.resize(800, 600) diff --git a/src/calibre/gui2/store/config/chooser/chooser_widget.py b/src/calibre/gui2/store/config/chooser/chooser_widget.py new file mode 100644 index 0000000000..a9399028f8 --- /dev/null +++ b/src/calibre/gui2/store/config/chooser/chooser_widget.py @@ -0,0 +1,37 @@ +# -*- coding: utf-8 -*- + +from __future__ import (unicode_literals, division, absolute_import, print_function) + +__license__ = 'GPL 3' +__copyright__ = '2011, John Schember <john@nachtimwald.com>' +__docformat__ = 'restructuredtext en' + +from PyQt4.Qt import (QWidget, QIcon, QDialog) + +from calibre.gui2.store.config.chooser.adv_search_builder import AdvSearchBuilderDialog +from calibre.gui2.store.config.chooser.chooser_widget_ui import Ui_Form + +class StoreChooserWidget(QWidget, Ui_Form): + + def __init__(self): + QWidget.__init__(self) + self.setupUi(self) + + self.query.initialize('store_config_chooser_query') + + self.adv_search_builder.setIcon(QIcon(I('search.png'))) + + self.search.clicked.connect(self.do_search) + self.adv_search_builder.clicked.connect(self.build_adv_search) + self.enable_all.clicked.connect(self.results_view.model().enable_all) + self.enable_none.clicked.connect(self.results_view.model().enable_none) + self.enable_invert.clicked.connect(self.results_view.model().enable_invert) + self.results_view.activated.connect(self.results_view.model().toggle_plugin) + + def do_search(self): + self.results_view.model().search(unicode(self.query.text())) + + def build_adv_search(self): + adv = AdvSearchBuilderDialog(self) + if adv.exec_() == QDialog.Accepted: + self.query.setText(adv.search_string()) diff --git a/src/calibre/gui2/store/config/chooser/chooser_widget.ui b/src/calibre/gui2/store/config/chooser/chooser_widget.ui new file mode 100644 index 0000000000..7513cdd752 --- /dev/null +++ b/src/calibre/gui2/store/config/chooser/chooser_widget.ui @@ -0,0 +1,144 @@ +<?xml version="1.0" encoding="UTF-8"?> +<ui version="4.0"> + <class>Form</class> + <widget class="QWidget" name="Form"> + <property name="geometry"> + <rect> + <x>0</x> + <y>0</y> + <width>610</width> + <height>553</height> + </rect> + </property> + <property name="windowTitle"> + <string>Form</string> + </property> + <layout class="QVBoxLayout" name="verticalLayout"> + <item> + <layout class="QHBoxLayout" name="horizontalLayout"> + <item> + <widget class="QLabel" name="label"> + <property name="text"> + <string>Query:</string> + </property> + </widget> + </item> + <item> + <widget class="QToolButton" name="adv_search_builder"> + <property name="text"> + <string>...</string> + </property> + </widget> + </item> + <item> + <widget class="HistoryLineEdit" name="query"> + <property name="sizePolicy"> + <sizepolicy hsizetype="Expanding" vsizetype="Fixed"> + <horstretch>0</horstretch> + <verstretch>0</verstretch> + </sizepolicy> + </property> + </widget> + </item> + <item> + <widget class="QPushButton" name="search"> + <property name="text"> + <string>Search</string> + </property> + </widget> + </item> + </layout> + </item> + <item> + <widget class="ResultsView" name="results_view"> + <property name="alternatingRowColors"> + <bool>true</bool> + </property> + <property name="selectionMode"> + <enum>QAbstractItemView::SingleSelection</enum> + </property> + <property name="selectionBehavior"> + <enum>QAbstractItemView::SelectRows</enum> + </property> + <property name="rootIsDecorated"> + <bool>false</bool> + </property> + <property name="uniformRowHeights"> + <bool>true</bool> + </property> + <property name="itemsExpandable"> + <bool>false</bool> + </property> + <property name="sortingEnabled"> + <bool>true</bool> + </property> + <property name="expandsOnDoubleClick"> + <bool>false</bool> + </property> + <attribute name="headerStretchLastSection"> + <bool>false</bool> + </attribute> + </widget> + </item> + <item> + <layout class="QHBoxLayout" name="horizontalLayout_2"> + <item> + <widget class="QLabel" name="label_2"> + <property name="text"> + <string>Enable</string> + </property> + </widget> + </item> + <item> + <widget class="QPushButton" name="enable_all"> + <property name="text"> + <string>All</string> + </property> + </widget> + </item> + <item> + <widget class="QPushButton" name="enable_none"> + <property name="text"> + <string>None</string> + </property> + </widget> + </item> + <item> + <widget class="QPushButton" name="enable_invert"> + <property name="text"> + <string>Invert</string> + </property> + </widget> + </item> + <item> + <spacer name="horizontalSpacer"> + <property name="orientation"> + <enum>Qt::Horizontal</enum> + </property> + <property name="sizeHint" stdset="0"> + <size> + <width>40</width> + <height>20</height> + </size> + </property> + </spacer> + </item> + </layout> + </item> + </layout> + </widget> + <customwidgets> + <customwidget> + <class>ResultsView</class> + <extends>QTreeView</extends> + <header>results_view.h</header> + </customwidget> + <customwidget> + <class>HistoryLineEdit</class> + <extends>QLineEdit</extends> + <header>widgets.h</header> + </customwidget> + </customwidgets> + <resources/> + <connections/> +</ui> diff --git a/src/calibre/gui2/store/config/chooser/models.py b/src/calibre/gui2/store/config/chooser/models.py new file mode 100644 index 0000000000..dbda367fae --- /dev/null +++ b/src/calibre/gui2/store/config/chooser/models.py @@ -0,0 +1,285 @@ +# -*- coding: utf-8 -*- + +from __future__ import (unicode_literals, division, absolute_import, print_function) + +__license__ = 'GPL 3' +__copyright__ = '2011, John Schember <john@nachtimwald.com>' +__docformat__ = 'restructuredtext en' + +from PyQt4.Qt import (Qt, QAbstractItemModel, QIcon, QVariant, QModelIndex, QSize) + +from calibre.gui2 import NONE +from calibre.customize.ui import is_disabled, disable_plugin, enable_plugin +from calibre.library.caches import _match, CONTAINS_MATCH, EQUALS_MATCH, \ + REGEXP_MATCH +from calibre.utils.icu import sort_key +from calibre.utils.search_query_parser import SearchQueryParser + + +class Matches(QAbstractItemModel): + + HEADERS = [_('Enabled'), _('Name'), _('No DRM'), _('Headquarters'), _('Affiliate'), _('Formats')] + HTML_COLS = [1] + + def __init__(self, plugins): + QAbstractItemModel.__init__(self) + + self.NO_DRM_ICON = QIcon(I('ok.png')) + self.DONATE_ICON = QIcon() + self.DONATE_ICON.addFile(I('donate.png'), QSize(16, 16)) + + self.all_matches = plugins + self.matches = plugins + self.filter = '' + self.search_filter = SearchFilter(self.all_matches) + + self.sort_col = 1 + self.sort_order = Qt.AscendingOrder + + def get_plugin(self, index): + row = index.row() + if row < len(self.matches): + return self.matches[row] + else: + return None + + def search(self, filter): + self.filter = filter.strip() + if not self.filter: + self.matches = self.all_matches + else: + try: + self.matches = list(self.search_filter.parse(self.filter)) + except: + self.matches = self.all_matches + self.layoutChanged.emit() + self.sort(self.sort_col, self.sort_order) + + def enable_all(self): + for i in xrange(len(self.matches)): + index = self.createIndex(i, 0) + data = QVariant(True) + self.setData(index, data, Qt.CheckStateRole) + + def enable_none(self): + for i in xrange(len(self.matches)): + index = self.createIndex(i, 0) + data = QVariant(False) + self.setData(index, data, Qt.CheckStateRole) + + def enable_invert(self): + for i in xrange(len(self.matches)): + self.toggle_plugin(self.createIndex(i, 0)) + + def toggle_plugin(self, index): + new_index = self.createIndex(index.row(), 0) + data = QVariant(is_disabled(self.get_plugin(index))) + self.setData(new_index, data, Qt.CheckStateRole) + + def index(self, row, column, parent=QModelIndex()): + return self.createIndex(row, column) + + def parent(self, index): + if not index.isValid() or index.internalId() == 0: + return QModelIndex() + return self.createIndex(0, 0) + + def rowCount(self, *args): + return len(self.matches) + + def columnCount(self, *args): + return len(self.HEADERS) + + def headerData(self, section, orientation, role): + if role != Qt.DisplayRole: + return NONE + text = '' + if orientation == Qt.Horizontal: + if section < len(self.HEADERS): + text = self.HEADERS[section] + return QVariant(text) + else: + return QVariant(section+1) + + def data(self, index, role): + row, col = index.row(), index.column() + result = self.matches[row] + if role in (Qt.DisplayRole, Qt.EditRole): + if col == 1: + return QVariant('<b>%s</b><br><i>%s</i>' % (result.name, result.description)) + elif col == 3: + return QVariant(result.headquarters) + elif col == 5: + return QVariant(', '.join(result.formats).upper()) + elif role == Qt.DecorationRole: + if col == 2: + if result.drm_free_only: + return QVariant(self.NO_DRM_ICON) + if col == 4: + if result.affiliate: + return QVariant(self.DONATE_ICON) + elif role == Qt.CheckStateRole: + if col == 0: + if is_disabled(result): + return Qt.Unchecked + return Qt.Checked + elif role == Qt.ToolTipRole: + if col == 0: + if is_disabled(result): + return QVariant('<p>' + _('This store is currently diabled and cannot be used in other parts of calibre.') + '</p>') + else: + return QVariant('<p>' + _('This store is currently enabled and can be used in other parts of calibre.') + '</p>') + elif col == 1: + return QVariant('<p>%s</p>' % result.description) + elif col == 2: + if result.drm_free_only: + return QVariant('<p>' + _('This store only distributes ebooks with DRM.') + '</p>') + else: + return QVariant('<p>' + _('This store distributes ebooks with DRM. It may have some titles without DRM, but you will need to check on a per title basis.') + '</p>') + elif col == 3: + return QVariant('<p>' + _('This store is headquartered in %s. This is a good indication of what market the store caters to. However, this does not necessarily mean that the store is limited to that market only.') % result.headquarters + '</p>') + elif col == 4: + if result.affiliate: + return QVariant('<p>' + _('Buying from this store supports the calibre developer: %s.') % result.author + '</p>') + elif col == 5: + return QVariant('<p>' + _('This store distributes ebooks in the following formats: %s') % ', '.join(result.formats) + '</p>') + return NONE + + def setData(self, index, data, role): + if not index.isValid(): + return False + row, col = index.row(), index.column() + if col == 0: + if data.toBool(): + enable_plugin(self.get_plugin(index)) + else: + disable_plugin(self.get_plugin(index)) + self.dataChanged.emit(self.index(index.row(), 0), self.index(index.row(), self.columnCount() - 1)) + return True + + def flags(self, index): + if index.column() == 0: + return QAbstractItemModel.flags(self, index) | Qt.ItemIsUserCheckable + return QAbstractItemModel.flags(self, index) + + def data_as_text(self, match, col): + text = '' + if col == 0: + text = 'b' if is_disabled(match) else 'a' + elif col == 1: + text = match.name + elif col == 2: + text = 'a' if getattr(match, 'drm_free_only', True) else 'b' + elif col == 3: + text = getattr(match, 'headquarters', '') + elif col == 4: + text = 'a' if getattr(match, 'affiliate', False) else 'b' + return text + + def sort(self, col, order, reset=True): + self.sort_col = col + self.sort_order = order + if not self.matches: + return + descending = order == Qt.DescendingOrder + self.matches.sort(None, + lambda x: sort_key(unicode(self.data_as_text(x, col))), + descending) + if reset: + self.reset() + + +class SearchFilter(SearchQueryParser): + + USABLE_LOCATIONS = [ + 'all', + 'affiliate', + 'description', + 'drm', + 'enabled', + 'format', + 'formats', + 'headquarters', + 'name', + ] + + def __init__(self, all_plugins=[]): + SearchQueryParser.__init__(self, locations=self.USABLE_LOCATIONS) + self.srs = set(all_plugins) + + def universal_set(self): + return self.srs + + def get_matches(self, location, query): + location = location.lower().strip() + if location == 'formats': + location = 'format' + + matchkind = CONTAINS_MATCH + if len(query) > 1: + if query.startswith('\\'): + query = query[1:] + elif query.startswith('='): + matchkind = EQUALS_MATCH + query = query[1:] + elif query.startswith('~'): + matchkind = REGEXP_MATCH + query = query[1:] + if matchkind != REGEXP_MATCH: ### leave case in regexps because it can be significant e.g. \S \W \D + query = query.lower() + + if location not in self.USABLE_LOCATIONS: + return set([]) + matches = set([]) + all_locs = set(self.USABLE_LOCATIONS) - set(['all']) + locations = all_locs if location == 'all' else [location] + q = { + 'affiliate': lambda x: x.affiliate, + 'description': lambda x: x.description.lower(), + 'drm': lambda x: not x.drm_free_only, + 'enabled': lambda x: not is_disabled(x), + 'format': lambda x: ','.join(x.formats).lower(), + 'headquarters': lambda x: x.headquarters.lower(), + 'name': lambda x : x.name.lower(), + } + q['formats'] = q['format'] + for sr in self.srs: + for locvalue in locations: + accessor = q[locvalue] + if query == 'true': + if locvalue in ('affiliate', 'drm', 'enabled'): + if accessor(sr) == True: + matches.add(sr) + elif accessor(sr) is not None: + matches.add(sr) + continue + if query == 'false': + if locvalue in ('affiliate', 'drm', 'enabled'): + if accessor(sr) == False: + matches.add(sr) + elif accessor(sr) is None: + matches.add(sr) + continue + # this is bool, so can't match below + if locvalue in ('affiliate', 'drm', 'enabled'): + continue + try: + ### Can't separate authors because comma is used for name sep and author sep + ### Exact match might not get what you want. For that reason, turn author + ### exactmatch searches into contains searches. + if locvalue == 'name' and matchkind == EQUALS_MATCH: + m = CONTAINS_MATCH + else: + m = matchkind + + if locvalue == 'format': + vals = accessor(sr).split(',') + else: + vals = [accessor(sr)] + if _match(query, vals, m): + matches.add(sr) + break + except ValueError: # Unicode errors + import traceback + traceback.print_exc() + return matches diff --git a/src/calibre/gui2/store/config/chooser/results_view.py b/src/calibre/gui2/store/config/chooser/results_view.py new file mode 100644 index 0000000000..10dff4bcdb --- /dev/null +++ b/src/calibre/gui2/store/config/chooser/results_view.py @@ -0,0 +1,53 @@ +# -*- coding: utf-8 -*- + +from __future__ import (unicode_literals, division, absolute_import, print_function) + +__license__ = 'GPL 3' +__copyright__ = '2011, John Schember <john@nachtimwald.com>' +__docformat__ = 'restructuredtext en' + +from functools import partial + +from PyQt4.Qt import (Qt, QTreeView, QSize, QMenu) + +from calibre.customize.ui import store_plugins +from calibre.gui2.metadata.single_download import RichTextDelegate +from calibre.gui2.store.config.chooser.models import Matches + +class ResultsView(QTreeView): + + def __init__(self, *args): + QTreeView.__init__(self,*args) + + self._model = Matches([p for p in store_plugins()]) + self.setModel(self._model) + + self.setIconSize(QSize(24, 24)) + + self.rt_delegate = RichTextDelegate(self) + + for i in self._model.HTML_COLS: + self.setItemDelegateForColumn(i, self.rt_delegate) + + for i in xrange(self._model.columnCount()): + self.resizeColumnToContents(i) + + self.model().sort(1, Qt.AscendingOrder) + self.header().setSortIndicator(self.model().sort_col, self.model().sort_order) + + def contextMenuEvent(self, event): + index = self.indexAt(event.pos()) + + if not index.isValid(): + return + + plugin = self.model().get_plugin(index) + + menu = QMenu() + ca = menu.addAction(_('Configure...'), partial(self.configure_plugin, plugin)) + if not plugin.is_customizable(): + ca.setEnabled(False) + menu.exec_(event.globalPos()) + + def configure_plugin(self, plugin): + plugin.do_user_config(self) diff --git a/src/calibre/gui2/store/config/search/__init__.py b/src/calibre/gui2/store/config/search/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/src/calibre/gui2/store/config/search/search_widget.py b/src/calibre/gui2/store/config/search/search_widget.py new file mode 100644 index 0000000000..b2e55d2ad1 --- /dev/null +++ b/src/calibre/gui2/store/config/search/search_widget.py @@ -0,0 +1,45 @@ +# -*- coding: utf-8 -*- + +from __future__ import (unicode_literals, division, absolute_import, print_function) + +__license__ = 'GPL 3' +__copyright__ = '2011, John Schember <john@nachtimwald.com>' +__docformat__ = 'restructuredtext en' + +from PyQt4.Qt import QWidget + +from calibre.gui2 import JSONConfig +from calibre.gui2.store.config.search.search_widget_ui import Ui_Form + +class StoreConfigWidget(QWidget, Ui_Form): + + def __init__(self, config=None): + QWidget.__init__(self) + self.setupUi(self) + + self.config = JSONConfig('store/search') if not config else config + + # These default values should be the same as in + # calibre.gui2.store.search.search:SearchDialog.load_settings + # Seconds + self.opt_timeout.setValue(self.config.get('timeout', 75)) + self.opt_hang_time.setValue(self.config.get('hang_time', 75)) + + self.opt_max_results.setValue(self.config.get('max_results', 10)) + self.opt_open_external.setChecked(self.config.get('open_external', True)) + + # Number of threads to run for each type of operation + self.opt_search_thread_count.setValue(self.config.get('search_thread_count', 4)) + self.opt_cache_thread_count.setValue(self.config.get('cache_thread_count', 2)) + self.opt_cover_thread_count.setValue(self.config.get('cover_thread_count', 2)) + self.opt_details_thread_count.setValue(self.config.get('details_thread_count', 4)) + + def save_settings(self): + self.config['timeout'] = self.opt_timeout.value() + self.config['hang_time'] = self.opt_hang_time.value() + self.config['max_results'] = self.opt_max_results.value() + self.config['open_external'] = self.opt_open_external.isChecked() + self.config['search_thread_count'] = self.opt_search_thread_count.value() + self.config['cache_thread_count'] = self.opt_cache_thread_count.value() + self.config['cover_thread_count'] = self.opt_cover_thread_count.value() + self.config['details_thread_count'] = self.opt_details_thread_count.value() diff --git a/src/calibre/gui2/store/config/search/search_widget.ui b/src/calibre/gui2/store/config/search/search_widget.ui new file mode 100644 index 0000000000..a73aae3ea5 --- /dev/null +++ b/src/calibre/gui2/store/config/search/search_widget.ui @@ -0,0 +1,162 @@ +<?xml version="1.0" encoding="UTF-8"?> +<ui version="4.0"> + <class>Form</class> + <widget class="QWidget" name="Form"> + <property name="geometry"> + <rect> + <x>0</x> + <y>0</y> + <width>465</width> + <height>396</height> + </rect> + </property> + <property name="windowTitle"> + <string>Form</string> + </property> + <layout class="QVBoxLayout" name="verticalLayout"> + <item> + <widget class="QGroupBox" name="groupBox"> + <property name="title"> + <string>Time</string> + </property> + <layout class="QFormLayout" name="formLayout"> + <item row="0" column="0"> + <widget class="QLabel" name="label"> + <property name="text"> + <string>Number of seconds to wait for a store to respond</string> + </property> + </widget> + </item> + <item row="0" column="1"> + <widget class="QSpinBox" name="opt_timeout"> + <property name="minimum"> + <number>1</number> + </property> + </widget> + </item> + <item row="1" column="0"> + <widget class="QLabel" name="label_2"> + <property name="text"> + <string>Number of seconds to let a store process results</string> + </property> + </widget> + </item> + <item row="1" column="1"> + <widget class="QSpinBox" name="opt_hang_time"> + <property name="minimum"> + <number>1</number> + </property> + <property name="maximum"> + <number>99</number> + </property> + <property name="singleStep"> + <number>1</number> + </property> + <property name="value"> + <number>1</number> + </property> + </widget> + </item> + </layout> + </widget> + </item> + <item> + <widget class="QGroupBox" name="groupBox_2"> + <property name="title"> + <string>Display</string> + </property> + <layout class="QFormLayout" name="formLayout_2"> + <item row="0" column="0"> + <widget class="QLabel" name="label_3"> + <property name="text"> + <string>Maximum number of results to show per store</string> + </property> + </widget> + </item> + <item row="0" column="1"> + <widget class="QSpinBox" name="opt_max_results"> + <property name="minimum"> + <number>1</number> + </property> + </widget> + </item> + <item row="1" column="0"> + <widget class="QCheckBox" name="opt_open_external"> + <property name="text"> + <string>Open search result in system browser</string> + </property> + </widget> + </item> + </layout> + </widget> + </item> + <item> + <widget class="QGroupBox" name="groupBox_3"> + <property name="title"> + <string>Threads</string> + </property> + <layout class="QFormLayout" name="formLayout_3"> + <item row="0" column="0"> + <widget class="QLabel" name="label_4"> + <property name="text"> + <string>Number of search threads to use</string> + </property> + </widget> + </item> + <item row="0" column="1"> + <widget class="QSpinBox" name="opt_search_thread_count"> + <property name="minimum"> + <number>1</number> + </property> + </widget> + </item> + <item row="1" column="0"> + <widget class="QLabel" name="label_5"> + <property name="text"> + <string>Number of cache update threads to use</string> + </property> + </widget> + </item> + <item row="1" column="1"> + <widget class="QSpinBox" name="opt_cache_thread_count"> + <property name="minimum"> + <number>1</number> + </property> + </widget> + </item> + <item row="2" column="0"> + <widget class="QLabel" name="label_6"> + <property name="text"> + <string>Number of conver download threads to use</string> + </property> + </widget> + </item> + <item row="2" column="1"> + <widget class="QSpinBox" name="opt_cover_thread_count"> + <property name="minimum"> + <number>1</number> + </property> + </widget> + </item> + <item row="3" column="0"> + <widget class="QLabel" name="label_7"> + <property name="text"> + <string>Number of details threads to use</string> + </property> + </widget> + </item> + <item row="3" column="1"> + <widget class="QSpinBox" name="opt_details_thread_count"> + <property name="minimum"> + <number>1</number> + </property> + </widget> + </item> + </layout> + </widget> + </item> + </layout> + </widget> + <resources/> + <connections/> +</ui> diff --git a/src/calibre/gui2/store/config/store.py b/src/calibre/gui2/store/config/store.py new file mode 100644 index 0000000000..852f602d08 --- /dev/null +++ b/src/calibre/gui2/store/config/store.py @@ -0,0 +1,18 @@ +# -*- coding: utf-8 -*- + +from __future__ import (unicode_literals, division, absolute_import, print_function) + +__license__ = 'GPL 3' +__copyright__ = '2011, John Schember <john@nachtimwald.com>' +__docformat__ = 'restructuredtext en' + +''' +Config widget access functions for configuring the store action. +''' + +def config_widget(): + from calibre.gui2.store.config.search.search_widget import StoreConfigWidget + return StoreConfigWidget() + +def save_settings(config_widget): + config_widget.save_settings() diff --git a/src/calibre/gui2/store/declined.txt b/src/calibre/gui2/store/declined.txt new file mode 100644 index 0000000000..2186303d4b --- /dev/null +++ b/src/calibre/gui2/store/declined.txt @@ -0,0 +1,8 @@ +This is a list of stores that objected, declined +or asked not to be included in the store integration. + +* Borders (http://www.borders.com/) +* WH Smith (http://www.whsmith.co.uk/) + Refused to permit signing up for the affiliate program +* Libraria Rizzoli (http://libreriarizzoli.corriere.it/). + No reply with two attempts over 2 weeks \ No newline at end of file diff --git a/src/calibre/gui2/store/diesel_ebooks_plugin.py b/src/calibre/gui2/store/diesel_ebooks_plugin.py new file mode 100644 index 0000000000..a21d6943d7 --- /dev/null +++ b/src/calibre/gui2/store/diesel_ebooks_plugin.py @@ -0,0 +1,101 @@ +# -*- coding: utf-8 -*- + +from __future__ import (unicode_literals, division, absolute_import, print_function) + +__license__ = 'GPL 3' +__copyright__ = '2011, John Schember <john@nachtimwald.com>' +__docformat__ = 'restructuredtext en' + +import random +import urllib2 +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 DieselEbooksStore(BasicStoreConfig, StorePlugin): + + def open(self, parent=None, detail_item=None, external=False): + url = 'http://www.diesel-ebooks.com/' + + aff_id = '?aid=2049' + # Use Kovid's affiliate id 30% of the time. + if random.randint(1, 10) in (1, 2, 3): + aff_id = '?aid=2053' + + detail_url = None + 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: + d = WebStoreDialog(self.gui, url, parent, detail_url) + d.setWindowTitle(self.name) + d.set_tags(self.config.get('tags', '')) + d.exec_() + + def search(self, query, max_results=10, timeout=60): + url = 'http://www.diesel-ebooks.com/index.php?page=seek&id[m]=&id[c]=scope%253Dinventory&id[q]=' + 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('//div[@class="item clearfix"]'): + data = html.fromstring(html.tostring(data)) + if counter <= 0: + break + + id = ''.join(data.xpath('div[@class="cover"]/a/@href')) + if not id or '/item/' not in id: + continue + a, b, id = id.partition('/item/') + + cover_url = ''.join(data.xpath('div[@class="cover"]//img/@src')) + if cover_url.startswith('/'): + cover_url = cover_url[1:] + cover_url = 'http://www.diesel-ebooks.com/' + cover_url + + title = ''.join(data.xpath('.//div[@class="content"]//h2/text()')) + author = ''.join(data.xpath('//div[@class="content"]//div[@class="author"]/a/text()')) + price = '' + price_elem = data.xpath('//td[@class="price"]/text()') + if price_elem: + price = price_elem[0] + + formats = ', '.join(data.xpath('.//td[@class="format"]/text()')) + + counter -= 1 + + s = SearchResult() + s.cover_url = cover_url + s.title = title.strip() + s.author = author.strip() + s.price = price.strip() + s.detail_item = '/item/' + id.strip() + s.formats = formats + + yield s + + def get_details(self, search_result, timeout): + url = 'http://www.diesel-ebooks.com/item/' + + br = browser() + with closing(br.open(url + search_result.detail_item, timeout=timeout)) as nf: + idata = html.fromstring(nf.read()) + if idata.xpath('boolean(//table[@class="format-info"]//tr[contains(th, "DRM") and contains(td, "No")])'): + search_result.drm = SearchResult.DRM_UNLOCKED + else: + search_result.drm = SearchResult.DRM_LOCKED + return True diff --git a/src/calibre/gui2/store/ebooks_com_plugin.py b/src/calibre/gui2/store/ebooks_com_plugin.py new file mode 100644 index 0000000000..341d08abac --- /dev/null +++ b/src/calibre/gui2/store/ebooks_com_plugin.py @@ -0,0 +1,119 @@ +# -*- coding: utf-8 -*- + +from __future__ import (unicode_literals, division, absolute_import, print_function) + +__license__ = 'GPL 3' +__copyright__ = '2011, John Schember <john@nachtimwald.com>' +__docformat__ = 'restructuredtext en' + +import random +import re +import urllib2 +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 EbookscomStore(BasicStoreConfig, StorePlugin): + + def open(self, parent=None, detail_item=None, external=False): + m_url = 'http://www.dpbolvw.net/' + h_click = 'click-4879827-10364500' + d_click = 'click-4879827-10281551' + # Use Kovid's affiliate id 30% of the time. + if random.randint(1, 10) in (1, 2, 3): + h_click = 'click-4913808-10364500' + d_click = 'click-4913808-10281551' + + url = m_url + h_click + detail_url = None + if detail_item: + detail_url = m_url + d_click + detail_item + + 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_() + + def search(self, query, max_results=10, timeout=60): + url = 'http://www.ebooks.com/SearchApp/SearchResults.net?term=' + 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('//div[@class="book_a" or @class="book_b"]'): + if counter <= 0: + break + + id = ''.join(data.xpath('.//a[1]/@href')) + mo = re.search('\d+', id) + if not mo: + continue + id = mo.group() + + cover_url = ''.join(data.xpath('.//img[1]/@src')) + + title = '' + author = '' + heading_a = data.xpath('.//a[1]/text()') + if heading_a: + title = heading_a[0] + if len(heading_a) >= 2: + author = heading_a[1] + + counter -= 1 + + s = SearchResult() + s.cover_url = cover_url + s.title = title.strip() + s.author = author.strip() + s.detail_item = '?url=http://www.ebooks.com/cj.asp?IID=' + id.strip() + '&cjsku=' + id.strip() + + yield s + + def get_details(self, search_result, timeout): + url = 'http://www.ebooks.com/ebooks/book_display.asp?IID=' + + mo = re.search(r'\?IID=(?P<id>\d+)', search_result.detail_item) + if mo: + id = mo.group('id') + if not id: + return + + price = _('Not Available') + br = browser() + with closing(br.open(url + id, timeout=timeout)) as nf: + pdoc = html.fromstring(nf.read()) + + pdata = pdoc.xpath('//table[@class="price"]/tr/td/text()') + if len(pdata) >= 2: + price = pdata[1] + + search_result.drm = SearchResult.DRM_UNLOCKED + for sec in ('Printing', 'Copying', 'Lending'): + if pdoc.xpath('boolean(//div[@class="formatTableInner"]//table//tr[contains(th, "%s") and contains(td, "Off")])' % sec): + search_result.drm = SearchResult.DRM_LOCKED + break + + fdata = ', '.join(pdoc.xpath('//table[@class="price"]//tr//td[1]/text()')) + fdata = fdata.replace(':', '') + fdata = re.sub(r'\s{2,}', ' ', fdata) + fdata = fdata.replace(' ,', ',') + fdata = fdata.strip() + search_result.formats = fdata + + search_result.price = price.strip() + return True diff --git a/src/calibre/gui2/store/ebookshoppe_uk_plugin.py b/src/calibre/gui2/store/ebookshoppe_uk_plugin.py new file mode 100644 index 0000000000..5db03ab383 --- /dev/null +++ b/src/calibre/gui2/store/ebookshoppe_uk_plugin.py @@ -0,0 +1,87 @@ +# -*- coding: utf-8 -*- + +from __future__ import (unicode_literals, division, absolute_import, print_function) + +__license__ = 'GPL 3' +__copyright__ = '2011, John Schember <john@nachtimwald.com>' +__docformat__ = 'restructuredtext en' + +import urllib2 +from contextlib import closing + +from lxml import html + +from PyQt4.Qt import QUrl + +from calibre import browser +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 EBookShoppeUKStore(BasicStoreConfig, StorePlugin): + + def open(self, parent=None, detail_item=None, external=False): + url_details = 'http://www.awin1.com/cread.php?awinmid=1414&awinaffid=120917&clickref=&p={0}' + url = 'http://www.awin1.com/awclick.php?mid=2666&id=120917' + + if external or self.config.get('open_external', False): + if detail_item: + url = url_details.format(detail_item) + open_url(QUrl(url)) + else: + detail_url = None + if detail_item: + detail_url = url_details.format(detail_item) + d = WebStoreDialog(self.gui, url, parent, detail_url) + d.setWindowTitle(self.name) + d.set_tags(self.config.get('tags', '')) + d.exec_() + + def search(self, query, max_results=10, timeout=60): + url = 'http://www.ebookshoppe.com/search.php?search_query=' + 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('//ul[@class="ProductList"]/li'): + if counter <= 0: + break + + id = ''.join(data.xpath('./div[@class="ProductDetails"]/' + 'strong/a/@href')).strip() + if not id: + continue + cover_url = ''.join(data.xpath('./div[@class="ProductImage"]/a/img/@src')) + title = ''.join(data.xpath('./div[@class="ProductDetails"]/strong/a/text()')) + price = ''.join(data.xpath('./div[@class="ProductPriceRating"]/em/text()')) + counter -= 1 + + s = SearchResult() + s.cover_url = cover_url + s.title = title.strip() + s.price = price + s.drm = SearchResult.DRM_UNLOCKED + s.detail_item = id + + self.get_author_and_formats(s, timeout) + if not s.author: + continue + + yield s + + def get_author_and_formats(self, search_result, timeout): + br = browser() + with closing(br.open(search_result.detail_item, timeout=timeout)) as nf: + idata = html.fromstring(nf.read()) + author = ''.join(idata.xpath('//div[@id="ProductOtherDetails"]/dl/dd[1]/text()')) + if author: + search_result.author = author + formats = idata.xpath('//dl[@class="ProductAddToCart"]/dd/' + 'ul[@class="ProductOptionList"]/li/label/text()') + if formats: + search_result.formats = ', '.join(formats) + search_result.drm = SearchResult.DRM_UNKNOWN + return True diff --git a/src/calibre/gui2/store/eharlequin_plugin.py b/src/calibre/gui2/store/eharlequin_plugin.py new file mode 100644 index 0000000000..daa67e801c --- /dev/null +++ b/src/calibre/gui2/store/eharlequin_plugin.py @@ -0,0 +1,102 @@ +# -*- coding: utf-8 -*- + +from __future__ import (unicode_literals, division, absolute_import, print_function) + +__license__ = 'GPL 3' +__copyright__ = '2011, John Schember <john@nachtimwald.com>' +__docformat__ = 'restructuredtext en' + +import random +import re +import urllib2 +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 EHarlequinStore(BasicStoreConfig, StorePlugin): + + def open(self, parent=None, detail_item=None, external=False): + m_url = 'http://www.dpbolvw.net/' + h_click = 'click-4879827-534091' + d_click = 'click-4879827-10375439' + # Use Kovid's affiliate id 30% of the time. + if random.randint(1, 10) in (1, 2, 3): + h_click = 'click-4913808-534091' + d_click = 'click-4913808-10375439' + + url = m_url + h_click + detail_url = None + if detail_item: + detail_url = m_url + d_click + detail_item + + 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_() + + 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()) + for data in doc.xpath('//table[not(.//@class="sidelink")]/tr[.//ul[@id="details"]]'): + if counter <= 0: + break + + id = ''.join(data.xpath('.//ul[@id="details"]/li[@id="title-results"]/a/@href')) + if not id: + continue + + title = ''.join(data.xpath('.//ul[@id="details"]/li[@id="title-results"]/a/text()')) + author = ''.join(data.xpath('.//ul[@id="details"]/li[@id="author"][1]//a/text()')) + price = ''.join(data.xpath('.//div[@class="ourprice"]/font/text()')) + cover_url = ''.join(data.xpath('.//a[@href="%s"]/img/@src' % id)) + + counter -= 1 + + s = SearchResult() + s.cover_url = cover_url + s.title = title.strip() + s.author = author.strip() + s.price = price.strip() + s.detail_item = '?url=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<id>.+)', 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()) + drm = SearchResult.DRM_UNKNOWN + if idata.xpath('boolean(//div[@class="drm_head"])'): + if idata.xpath('boolean(//td[contains(., "Copy") and contains(., "not")])'): + drm = SearchResult.DRM_LOCKED + else: + drm = SearchResult.DRM_UNLOCKED + search_result.drm = drm + return True diff --git a/src/calibre/gui2/store/epubbuy_de_plugin.py b/src/calibre/gui2/store/epubbuy_de_plugin.py new file mode 100644 index 0000000000..242ef76793 --- /dev/null +++ b/src/calibre/gui2/store/epubbuy_de_plugin.py @@ -0,0 +1,80 @@ +# -*- coding: utf-8 -*- + +from __future__ import (unicode_literals, division, absolute_import, print_function) + +__license__ = 'GPL 3' +__copyright__ = '2011, John Schember <john@nachtimwald.com>' +__docformat__ = 'restructuredtext en' + +import urllib2 +from contextlib import closing + +from lxml import html + +from PyQt4.Qt import QUrl + +from calibre import browser +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 EPubBuyDEStore(BasicStoreConfig, StorePlugin): + + def open(self, parent=None, detail_item=None, external=False): + url = 'http://klick.affiliwelt.net/klick.php?bannerid=47653&pid=32307&prid=2627' + url_details = ('http://klick.affiliwelt.net/klick.php?bannerid=47653' + '&pid=32307&prid=2627&prodid={0}') + + if external or self.config.get('open_external', False): + if detail_item: + url = url_details.format(detail_item) + open_url(QUrl(url)) + else: + detail_url = None + if detail_item: + detail_url = url_details.format(detail_item) + d = WebStoreDialog(self.gui, url, parent, detail_url) + d.setWindowTitle(self.name) + d.set_tags(self.config.get('tags', '')) + d.exec_() + + def search(self, query, max_results=10, timeout=60): + url = 'http://www.epubbuy.com/search.php?search_query=' + 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('//li[contains(@class, "ajax_block_product")]'): + if counter <= 0: + break + + id = ''.join(data.xpath('./div[@class="center_block"]' + '/p[contains(text(), "artnr:")]/text()')).strip() + if not id: + continue + id = id[6:].strip() + if not id: + continue + cover_url = ''.join(data.xpath('./div[@class="center_block"]' + '/a[@class="product_img_link"]/img/@src')) + if cover_url: + cover_url = 'http://www.epubbuy.com' + cover_url + title = ''.join(data.xpath('./div[@class="center_block"]' + '/a[@class="product_img_link"]/@title')) + author = ''.join(data.xpath('./div[@class="center_block"]/a[2]/text()')) + price = ''.join(data.xpath('.//span[@class="price"]/text()')) + counter -= 1 + + s = SearchResult() + s.cover_url = cover_url + s.title = title.strip() + s.author = author.strip() + s.price = price + s.drm = SearchResult.DRM_UNLOCKED + s.detail_item = id + s.formats = 'ePub' + + yield s diff --git a/src/calibre/gui2/store/feedbooks_plugin.py b/src/calibre/gui2/store/feedbooks_plugin.py new file mode 100644 index 0000000000..9b1f7f6574 --- /dev/null +++ b/src/calibre/gui2/store/feedbooks_plugin.py @@ -0,0 +1,106 @@ +# -*- coding: utf-8 -*- + +from __future__ import (unicode_literals, division, absolute_import, print_function) + +__license__ = 'GPL 3' +__copyright__ = '2011, John Schember <john@nachtimwald.com>' +__docformat__ = 'restructuredtext en' + +import urllib2 +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 FeedbooksStore(BasicStoreConfig, StorePlugin): + + def open(self, parent=None, detail_item=None, external=False): + url = 'http://m.feedbooks.com/' + ext_url = 'http://feedbooks.com/' + + if external or self.config.get('open_external', False): + if detail_item: + ext_url = ext_url + detail_item + open_url(QUrl(url_slash_cleaner(ext_url))) + else: + detail_url = None + if detail_item: + detail_url = url + detail_item + d = WebStoreDialog(self.gui, url, parent, detail_url) + d.setWindowTitle(self.name) + d.set_tags(self.config.get('tags', '')) + d.exec_() + + def search(self, query, max_results=10, timeout=60): + url = 'http://m.feedbooks.com/search?query=' + 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('//ul[@class="m-list"]//li'): + if counter <= 0: + break + data = html.fromstring(html.tostring(data)) + + id = '' + id_a = data.xpath('//a[@class="buy"]') + if id_a: + id = id_a[0].get('href', None) + id = id.split('/')[-2] + id = '/item/' + id + else: + id_a = data.xpath('//a[@class="download"]') + if id_a: + id = id_a[0].get('href', None) + id = id.split('/')[-1] + id = id.split('.')[0] + id = '/book/' + id + if not id: + continue + + title = ''.join(data.xpath('//h5//a/text()')) + author = ''.join(data.xpath('//h6//a/text()')) + price = ''.join(data.xpath('//a[@class="buy"]/text()')) + formats = 'EPUB' + if not price: + price = '$0.00' + formats = 'EPUB, MOBI, PDF' + cover_url = '' + cover_url_img = data.xpath('//img') + if cover_url_img: + cover_url = cover_url_img[0].get('src') + cover_url.split('?')[0] + + counter -= 1 + + s = SearchResult() + s.cover_url = cover_url + s.title = title.strip() + s.author = author.strip() + s.price = price.replace(' ', '').strip() + s.detail_item = id.strip() + s.formats = formats + + yield s + + def get_details(self, search_result, timeout): + url = 'http://m.feedbooks.com/' + + br = browser() + with closing(br.open(url_slash_cleaner(url + search_result.detail_item), timeout=timeout)) as nf: + idata = html.fromstring(nf.read()) + if idata.xpath('boolean(//div[contains(@class, "m-description-long")]//p[contains(., "DRM") or contains(b, "Protection")])'): + search_result.drm = SearchResult.DRM_LOCKED + else: + search_result.drm = SearchResult.DRM_UNLOCKED + return True diff --git a/src/calibre/gui2/store/foyles_uk_plugin.py b/src/calibre/gui2/store/foyles_uk_plugin.py new file mode 100644 index 0000000000..fd670d2d85 --- /dev/null +++ b/src/calibre/gui2/store/foyles_uk_plugin.py @@ -0,0 +1,83 @@ +# -*- coding: utf-8 -*- + +from __future__ import (unicode_literals, division, absolute_import, print_function) + +__license__ = 'GPL 3' +__copyright__ = '2011, John Schember <john@nachtimwald.com>' +__docformat__ = 'restructuredtext en' + +import urllib2 +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 FoylesUKStore(BasicStoreConfig, StorePlugin): + + def open(self, parent=None, detail_item=None, external=False): + url = 'http://www.awin1.com/awclick.php?mid=1414&id=120917' + detail_url = 'http://www.awin1.com/cread.php?awinmid=1414&awinaffid=120917&clickref=&p=' + url_redirect = 'http://www.foyles.co.uk' + + if external or self.config.get('open_external', False): + if detail_item: + url = detail_url + url_redirect + detail_item + open_url(QUrl(url_slash_cleaner(url))) + else: + detail_url = None + if detail_item: + detail_url = url + url_redirect + detail_item + d = WebStoreDialog(self.gui, url, parent, detail_url) + d.setWindowTitle(self.name) + d.set_tags(self.config.get('tags', '')) + d.exec_() + + def search(self, query, max_results=10, timeout=60): + url = 'http://www.foyles.co.uk/Public/Shop/Search.aspx?fFacetId=1015&searchBy=1&quick=true&term=' + 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[contains(@id, "MainContent")]/tr/td/div[contains(@class, "Item")]'): + if counter <= 0: + break + id = ''.join(data.xpath('.//a[@class="Title"]/@href')).strip() + if not id: + continue + + # filter out the audio books + if not data.xpath('boolean(.//div[@class="Relative"]/ul/li[contains(text(), "ePub")])'): + continue + + cover_url = ''.join(data.xpath('.//a[@class="Jacket"]/img/@src')) + if cover_url: + cover_url = 'http://www.foyles.co.uk' + cover_url + #print(cover_url) + + title = ''.join(data.xpath('.//a[@class="Title"]/text()')) + author = ', '.join(data.xpath('.//span[@class="Author"]/text()')) + price = ''.join(data.xpath('./ul/li[@class="Strong"]/text()')) + price = price[price.rfind(' '):] + + counter -= 1 + + s = SearchResult() + s.cover_url = cover_url + s.title = title.strip() + s.author = author.strip() + s.price = price + s.detail_item = id + s.drm = SearchResult.DRM_LOCKED + s.formats = 'ePub' + + yield s diff --git a/src/calibre/gui2/store/gandalf_plugin.py b/src/calibre/gui2/store/gandalf_plugin.py new file mode 100644 index 0000000000..4bd8e9e747 --- /dev/null +++ b/src/calibre/gui2/store/gandalf_plugin.py @@ -0,0 +1,77 @@ +# -*- coding: utf-8 -*- + +from __future__ import (unicode_literals, division, absolute_import, print_function) + +__license__ = 'GPL 3' +__copyright__ = '2011, Tomasz Długosz <tomek3d@gmail.com>' +__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 GandalfStore(BasicStoreConfig, StorePlugin): + + def open(self, parent=None, detail_item=None, external=False): + url = 'http://www.gandalf.com.pl/ebooks/' + + 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 = 'http://www.gandalf.com.pl/s/' + values={ + 'search': query.encode('iso8859_2'), + 'dzialx':'11' + } + + br = browser() + + counter = max_results + with closing(br.open(url, data=urllib.urlencode(values), timeout=timeout)) as f: + doc = html.fromstring(f.read()) + for data in doc.xpath('//div[@class="box"]'): + if counter <= 0: + break + + id = ''.join(data.xpath('.//div[@class="info"]/h3/a/@href')) + if not id: + continue + + cover_url = ''.join(data.xpath('.//img/@src')) + title = ''.join(data.xpath('.//div[@class="info"]/h3/a/@title')) + formats = title.split() + formats = formats[-1] + author = ''.join(data.xpath('.//div[@class="info"]/h4/text() | .//div[@class="info"]/h4/span/text()')) + price = ''.join(data.xpath('.//h3[@class="promocja"]/text()')) + price = re.sub('PLN', 'zł', price) + price = re.sub('\.', ',', price) + + counter -= 1 + + s = SearchResult() + s.cover_url = cover_url + s.title = title.strip() + s.author = author.strip() + s.price = price + s.detail_item = id.strip() + s.drm = SearchResult.DRM_UNKNOWN + s.formats = formats.upper().strip() + + yield s diff --git a/src/calibre/gui2/store/google_books_plugin.py b/src/calibre/gui2/store/google_books_plugin.py new file mode 100644 index 0000000000..938ca70664 --- /dev/null +++ b/src/calibre/gui2/store/google_books_plugin.py @@ -0,0 +1,93 @@ +# -*- coding: utf-8 -*- + +from __future__ import (unicode_literals, division, absolute_import, print_function) + +__license__ = 'GPL 3' +__copyright__ = '2011, John Schember <john@nachtimwald.com>' +__docformat__ = 'restructuredtext en' + +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 GoogleBooksStore(BasicStoreConfig, StorePlugin): + + def open(self, parent=None, detail_item=None, external=False): + url = 'http://books.google.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 = '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'): + if counter <= 0: + break + + id = ''.join(data.xpath('.//h3/a/@href')) + if not id: + continue + + title = ''.join(data.xpath('.//h3/a//text()')) + authors = data.xpath('.//span[@class="gl"]//a//text()') + if authors[-1].strip().lower() in ('preview', 'read'): + authors = authors[:-1] + else: + continue + 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[@class="buy-price-container"]/span[contains(@class, "buy-price")]/text()')) + # Try to get the price inside of a buy button. + if not price.strip(): + price = ''.join(doc.xpath('//div[@class="buy-container"]/a/text()')) + price = price.split('-')[-1] + # No price set for this book. + if not price.strip(): + price = '$0.00' + 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/gutenberg_plugin.py b/src/calibre/gui2/store/gutenberg_plugin.py new file mode 100644 index 0000000000..d820a44f8d --- /dev/null +++ b/src/calibre/gui2/store/gutenberg_plugin.py @@ -0,0 +1,92 @@ +# -*- coding: utf-8 -*- + +from __future__ import (unicode_literals, division, absolute_import, print_function) + +__license__ = 'GPL 3' +__copyright__ = '2011, John Schember <john@nachtimwald.com>' +__docformat__ = 'restructuredtext en' + +import urllib2 +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 GutenbergStore(BasicStoreConfig, StorePlugin): + + def open(self, parent=None, detail_item=None, external=False): + url = 'http://m.gutenberg.org/' + ext_url = 'http://gutenberg.org/' + + if external or self.config.get('open_external', False): + if detail_item: + ext_url = ext_url + detail_item + open_url(QUrl(url_slash_cleaner(ext_url))) + else: + detail_url = None + if detail_item: + detail_url = url + detail_item + d = WebStoreDialog(self.gui, url, parent, detail_url) + d.setWindowTitle(self.name) + d.set_tags(self.config.get('tags', '')) + d.exec_() + + def search(self, query, max_results=10, timeout=60): + # Gutenberg's website does not allow searching both author and title. + # Using a google search so we can search on both fields at once. + url = 'http://www.google.com/xhtml?q=site:gutenberg.org+' + 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('//div[@class="edewpi"]//div[@class="r ld"]'): + if counter <= 0: + break + + url = '' + url_a = data.xpath('div[@class="jd"]/a') + if url_a: + url_a = url_a[0] + url = url_a.get('href', None) + if url: + url = url.split('u=')[-1].split('&')[0] + if '/ebooks/' not in url: + continue + id = url.split('/')[-1] + + url_a = html.fromstring(html.tostring(url_a)) + heading = ''.join(url_a.xpath('//text()')) + title, _, author = heading.rpartition('by ') + author = author.split('-')[0] + price = '$0.00' + + counter -= 1 + + s = SearchResult() + s.cover_url = '' + s.title = title.strip() + s.author = author.strip() + s.price = price.strip() + s.detail_item = '/ebooks/' + id.strip() + s.drm = SearchResult.DRM_UNLOCKED + + yield s + + def get_details(self, search_result, timeout): + url = 'http://m.gutenberg.org/' + + br = browser() + with closing(br.open(url + search_result.detail_item, timeout=timeout)) as nf: + idata = html.fromstring(nf.read()) + search_result.formats = ', '.join(idata.xpath('//a[@type!="application/atom+xml"]//span[@class="title"]/text()')) + return True \ No newline at end of file diff --git a/src/calibre/gui2/store/kobo_plugin.py b/src/calibre/gui2/store/kobo_plugin.py new file mode 100644 index 0000000000..9ec0e4b786 --- /dev/null +++ b/src/calibre/gui2/store/kobo_plugin.py @@ -0,0 +1,85 @@ +# -*- coding: utf-8 -*- + +from __future__ import (unicode_literals, division, absolute_import, print_function) + +__license__ = 'GPL 3' +__copyright__ = '2011, John Schember <john@nachtimwald.com>' +__docformat__ = 'restructuredtext en' + +import random +import urllib2 +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 KoboStore(BasicStoreConfig, StorePlugin): + + def open(self, parent=None, detail_item=None, external=False): + m_url = 'http://www.dpbolvw.net/' + h_click = 'click-4879827-10762497' + d_click = 'click-4879827-10772898' + # Use Kovid's affiliate id 30% of the time. + if random.randint(1, 10) in (1, 2, 3): + h_click = 'click-4913808-10762497' + d_click = 'click-4913808-10772898' + + url = m_url + h_click + detail_url = None + if detail_item: + detail_url = m_url + d_click + detail_item + + 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_() + + def search(self, query, max_results=10, timeout=60): + url = 'http://www.kobobooks.com/search/search.html?q=' + 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('//ul[@class="SCShortCoverList"]/li'): + if counter <= 0: + break + + id = ''.join(data.xpath('.//div[@class="SearchImageContainer"]/a[1]/@href')) + if not id: + continue + + price = ''.join(data.xpath('.//li[@class="OurPrice"]/strong/text()')) + if not price: + price = '$0.00' + + cover_url = ''.join(data.xpath('.//div[@class="SearchImageContainer"]//img[1]/@src')) + + title = ''.join(data.xpath('.//div[@class="SCItemHeader"]/h1/a[1]/text()')) + author = ''.join(data.xpath('.//div[@class="SCItemSummary"]/span/a[1]/text()')) + drm = data.xpath('boolean(.//span[@class="SCAvailibilityFormatsText" and contains(text(), "DRM")])') + + counter -= 1 + + s = SearchResult() + s.cover_url = cover_url + s.title = title.strip() + s.author = author.strip() + s.price = price.strip() + s.detail_item = '?url=http://www.kobobooks.com/' + id.strip() + s.drm = SearchResult.DRM_LOCKED if drm else SearchResult.DRM_UNLOCKED + s.formats = 'EPUB' + + yield s diff --git a/src/calibre/gui2/store/legimi_plugin.py b/src/calibre/gui2/store/legimi_plugin.py new file mode 100644 index 0000000000..7212f0f394 --- /dev/null +++ b/src/calibre/gui2/store/legimi_plugin.py @@ -0,0 +1,75 @@ +# -*- coding: utf-8 -*- + +from __future__ import (unicode_literals, division, absolute_import, print_function) + +__license__ = 'GPL 3' +__copyright__ = '2011, Tomasz Długosz <tomek3d@gmail.com>' +__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 LegimiStore(BasicStoreConfig, StorePlugin): + + def open(self, parent=None, detail_item=None, external=False): + + url = 'http://www.legimi.com/pl/ebooks/?price=any' + detail_url = None + + if detail_item: + detail_url = detail_item + + 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_() + + def search(self, query, max_results=10, timeout=60): + url = 'http://www.legimi.com/pl/ebooks/?price=any&lang=pl&search=' + urllib.quote_plus(query.encode('utf-8')) + '&sort=relevance' + + br = browser() + + counter = max_results + with closing(br.open(url, timeout=timeout)) as f: + doc = html.fromstring(f.read()) + for data in doc.xpath('//div[@class="list"]/ul/li'): + if counter <= 0: + break + + id = ''.join(data.xpath('.//div[@class="item_cover_container"]/a[1]/@href')) + if not id: + continue + + cover_url = ''.join(data.xpath('.//div[@class="item_cover_container"]/a/img/@src')) + title = ''.join(data.xpath('.//div[@class="item_entries"]/h2/a/text()')) + author = ''.join(data.xpath('.//div[@class="item_entries"]/span[1]/a/text()')) + price = ''.join(data.xpath('.//div[@class="item_entries"]/span[3]/text()')) + price = re.sub(r'[^0-9,]*','',price) + ' zł' + + counter -= 1 + + s = SearchResult() + s.cover_url = 'http://www.legimi.com/' + cover_url + s.title = title.strip() + s.author = author.strip() + s.price = price + s.detail_item = 'http://www.legimi.com/' + id.strip() + s.drm = SearchResult.DRM_LOCKED + s.formats = 'EPUB' + + yield s diff --git a/src/calibre/gui2/store/manybooks_plugin.py b/src/calibre/gui2/store/manybooks_plugin.py new file mode 100644 index 0000000000..e990accc86 --- /dev/null +++ b/src/calibre/gui2/store/manybooks_plugin.py @@ -0,0 +1,95 @@ +# -*- coding: utf-8 -*- + +from __future__ import (unicode_literals, division, absolute_import, print_function) + +__license__ = 'GPL 3' +__copyright__ = '2011, John Schember <john@nachtimwald.com>' +__docformat__ = 'restructuredtext en' + +import re +import urllib2 +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 ManyBooksStore(BasicStoreConfig, StorePlugin): + + def open(self, parent=None, detail_item=None, external=False): + url = 'http://manybooks.net/' + + detail_url = None + if detail_item: + detail_url = url + detail_item + + 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_() + + def search(self, query, max_results=10, timeout=60): + # ManyBooks website separates results for title and author. + # It also doesn't do a clear job of references authors and + # secondary titles. Google is also faster. + # Using a google search so we can search on both fields at once. + url = 'http://www.google.com/xhtml?q=site:manybooks.net+' + 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('//div[@class="edewpi"]//div[@class="r ld"]'): + if counter <= 0: + break + + url = '' + url_a = data.xpath('div[@class="jd"]/a') + if url_a: + url_a = url_a[0] + url = url_a.get('href', None) + if url: + url = url.split('u=')[-1][:-2] + if '/titles/' not in url: + continue + id = url.split('/')[-1] + id = id.strip() + + url_a = html.fromstring(html.tostring(url_a)) + heading = ''.join(url_a.xpath('//text()')) + title, _, author = heading.rpartition('by ') + author = author.split('-')[0] + price = '$0.00' + + cover_url = '' + mo = re.match('^\D+', id) + if mo: + cover_name = mo.group() + cover_name = cover_name.replace('etext', '') + cover_id = id.split('.')[0] + cover_url = 'http://www.manybooks.net/images/' + id[0] + '/' + cover_name + '/' + cover_id + '-thumb.jpg' + print(cover_url) + + counter -= 1 + + s = SearchResult() + s.cover_url = cover_url + s.title = title.strip() + s.author = author.strip() + s.price = price.strip() + s.detail_item = '/titles/' + id + s.drm = SearchResult.DRM_UNLOCKED + s.formts = 'EPUB, PDB (eReader, PalmDoc, zTXT, Plucker, iSilo), FB2, ZIP, AZW, MOBI, PRC, LIT, PKG, PDF, TXT, RB, RTF, LRF, TCR, JAR' + + yield s diff --git a/src/calibre/gui2/store/mobileread/__init__.py b/src/calibre/gui2/store/mobileread/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/src/calibre/gui2/store/mobileread/adv_search_builder.py b/src/calibre/gui2/store/mobileread/adv_search_builder.py new file mode 100644 index 0000000000..8c41f1924b --- /dev/null +++ b/src/calibre/gui2/store/mobileread/adv_search_builder.py @@ -0,0 +1,119 @@ +# -*- coding: utf-8 -*- + +from __future__ import (unicode_literals, division, absolute_import, print_function) + +__license__ = 'GPL 3' +__copyright__ = '2011, John Schember <john@nachtimwald.com>' +__docformat__ = 'restructuredtext en' + +import re + +from PyQt4.Qt import (QDialog, QDialogButtonBox) + +from calibre.gui2.store.mobileread.adv_search_builder_ui import Ui_Dialog +from calibre.library.caches import CONTAINS_MATCH, EQUALS_MATCH + +class AdvSearchBuilderDialog(QDialog, Ui_Dialog): + + def __init__(self, parent): + QDialog.__init__(self, parent) + self.setupUi(self) + + self.buttonBox.accepted.connect(self.advanced_search_button_pushed) + self.tab_2_button_box.accepted.connect(self.accept) + self.tab_2_button_box.rejected.connect(self.reject) + self.clear_button.clicked.connect(self.clear_button_pushed) + self.adv_search_used = False + self.mc = '' + + self.tabWidget.setCurrentIndex(0) + self.tabWidget.currentChanged[int].connect(self.tab_changed) + self.tab_changed(0) + + def tab_changed(self, idx): + if idx == 1: + self.tab_2_button_box.button(QDialogButtonBox.Ok).setDefault(True) + else: + self.buttonBox.button(QDialogButtonBox.Ok).setDefault(True) + + def advanced_search_button_pushed(self): + self.adv_search_used = True + self.accept() + + def clear_button_pushed(self): + self.title_box.setText('') + self.author_box.setText('') + self.format_box.setText('') + + def tokens(self, raw): + phrases = re.findall(r'\s*".*?"\s*', raw) + for f in phrases: + raw = raw.replace(f, ' ') + phrases = [t.strip('" ') for t in phrases] + return ['"' + self.mc + t + '"' for t in phrases + [r.strip() for r in raw.split()]] + + def search_string(self): + if self.adv_search_used: + return self.adv_search_string() + else: + return self.box_search_string() + + def adv_search_string(self): + mk = self.matchkind.currentIndex() + if mk == CONTAINS_MATCH: + self.mc = '' + elif mk == EQUALS_MATCH: + self.mc = '=' + else: + self.mc = '~' + all, any, phrase, none = map(lambda x: unicode(x.text()), + (self.all, self.any, self.phrase, self.none)) + all, any, none = map(self.tokens, (all, any, none)) + phrase = phrase.strip() + all = ' and '.join(all) + any = ' or '.join(any) + none = ' and not '.join(none) + ans = '' + if phrase: + ans += '"%s"'%phrase + if all: + ans += (' and ' if ans else '') + all + if none: + ans += (' and not ' if ans else 'not ') + none + if any: + ans += (' or ' if ans else '') + any + return ans + + def token(self): + txt = unicode(self.text.text()).strip() + if txt: + if self.negate.isChecked(): + txt = '!'+txt + tok = self.FIELDS[unicode(self.field.currentText())]+txt + if re.search(r'\s', tok): + tok = '"%s"'%tok + return tok + + def box_search_string(self): + mk = self.matchkind.currentIndex() + if mk == CONTAINS_MATCH: + self.mc = '' + elif mk == EQUALS_MATCH: + self.mc = '=' + else: + self.mc = '~' + + ans = [] + self.box_last_values = {} + title = unicode(self.title_box.text()).strip() + if title: + ans.append('title:"' + self.mc + title + '"') + author = unicode(self.author_box.text()).strip() + if author: + ans.append('author:"' + self.mc + author + '"') + format = unicode(self.format_box.text()).strip() + if format: + ans.append('format:"' + self.mc + format + '"') + if ans: + return ' and '.join(ans) + return '' diff --git a/src/calibre/gui2/store/mobileread/adv_search_builder.ui b/src/calibre/gui2/store/mobileread/adv_search_builder.ui new file mode 100644 index 0000000000..7742ccbd97 --- /dev/null +++ b/src/calibre/gui2/store/mobileread/adv_search_builder.ui @@ -0,0 +1,350 @@ +<?xml version="1.0" encoding="UTF-8"?> +<ui version="4.0"> + <class>Dialog</class> + <widget class="QDialog" name="Dialog"> + <property name="geometry"> + <rect> + <x>0</x> + <y>0</y> + <width>752</width> + <height>472</height> + </rect> + </property> + <property name="windowTitle"> + <string>Advanced Search</string> + </property> + <property name="windowIcon"> + <iconset> + <normaloff>:/images/search.png</normaloff>:/images/search.png</iconset> + </property> + <layout class="QGridLayout" name="gridLayout_2"> + <item row="0" column="0"> + <widget class="QLabel" name="label_5"> + <property name="text"> + <string>&What kind of match to use:</string> + </property> + <property name="buddy"> + <cstring>matchkind</cstring> + </property> + </widget> + </item> + <item row="0" column="1"> + <widget class="QComboBox" name="matchkind"> + <item> + <property name="text"> + <string>Contains: the word or phrase matches anywhere in the metadata field</string> + </property> + </item> + <item> + <property name="text"> + <string>Equals: the word or phrase must match the entire metadata field</string> + </property> + </item> + <item> + <property name="text"> + <string>Regular expression: the expression must match anywhere in the metadata field</string> + </property> + </item> + </widget> + </item> + <item row="2" column="0" colspan="2"> + <widget class="QTabWidget" name="tabWidget"> + <property name="currentIndex"> + <number>0</number> + </property> + <widget class="QWidget" name="tab"> + <attribute name="title"> + <string>A&dvanced Search</string> + </attribute> + <layout class="QGridLayout" name="gridLayout_3"> + <item row="0" column="0"> + <widget class="QGroupBox" name="groupBox"> + <property name="title"> + <string>Find entries that have...</string> + </property> + <layout class="QVBoxLayout" name="verticalLayout"> + <item> + <layout class="QHBoxLayout" name="horizontalLayout"> + <item> + <widget class="QLabel" name="label"> + <property name="text"> + <string>&All these words:</string> + </property> + <property name="buddy"> + <cstring>all</cstring> + </property> + </widget> + </item> + <item> + <widget class="QLineEdit" name="all"/> + </item> + </layout> + </item> + <item> + <layout class="QHBoxLayout" name="horizontalLayout_2"> + <item> + <widget class="QLabel" name="label_2"> + <property name="text"> + <string>This exact &phrase:</string> + </property> + <property name="buddy"> + <cstring>all</cstring> + </property> + </widget> + </item> + <item> + <widget class="QLineEdit" name="phrase"/> + </item> + </layout> + </item> + <item> + <layout class="QHBoxLayout" name="horizontalLayout_3"> + <item> + <widget class="QLabel" name="label_3"> + <property name="text"> + <string>&One or more of these words:</string> + </property> + <property name="buddy"> + <cstring>all</cstring> + </property> + </widget> + </item> + <item> + <widget class="QLineEdit" name="any"/> + </item> + </layout> + </item> + </layout> + </widget> + </item> + <item row="1" column="0"> + <widget class="QGroupBox" name="groupBox_2"> + <property name="title"> + <string>But dont show entries that have...</string> + </property> + <layout class="QVBoxLayout" name="verticalLayout_2"> + <item> + <layout class="QHBoxLayout" name="horizontalLayout_4"> + <item> + <widget class="QLabel" name="label_4"> + <property name="text"> + <string>Any of these &unwanted words:</string> + </property> + <property name="buddy"> + <cstring>all</cstring> + </property> + </widget> + </item> + <item> + <widget class="QLineEdit" name="none"/> + </item> + </layout> + </item> + <item> + <widget class="QLabel" name="label_6"> + <property name="maximumSize"> + <size> + <width>16777215</width> + <height>30</height> + </size> + </property> + <property name="text"> + <string>See the <a href="http://calibre-ebook.com/user_manual/gui.html#the-search-interface">User Manual</a> for more help</string> + </property> + <property name="openExternalLinks"> + <bool>true</bool> + </property> + </widget> + </item> + </layout> + </widget> + </item> + <item row="2" column="0"> + <spacer name="verticalSpacer_2"> + <property name="orientation"> + <enum>Qt::Vertical</enum> + </property> + <property name="sizeHint" stdset="0"> + <size> + <width>20</width> + <height>40</height> + </size> + </property> + </spacer> + </item> + <item row="3" column="0"> + <widget class="QDialogButtonBox" name="buttonBox"> + <property name="orientation"> + <enum>Qt::Horizontal</enum> + </property> + <property name="standardButtons"> + <set>QDialogButtonBox::Cancel|QDialogButtonBox::Ok</set> + </property> + </widget> + </item> + </layout> + </widget> + <widget class="QWidget" name="tab_2"> + <attribute name="title"> + <string>Titl&e/Author/Price ...</string> + </attribute> + <layout class="QGridLayout" name="gridLayout"> + <item row="1" column="0"> + <widget class="QLabel" name="label_7"> + <property name="text"> + <string>&Title:</string> + </property> + <property name="buddy"> + <cstring>title_box</cstring> + </property> + </widget> + </item> + <item row="1" column="1"> + <widget class="EnLineEdit" name="title_box"> + <property name="toolTip"> + <string>Enter the title.</string> + </property> + </widget> + </item> + <item row="2" column="0"> + <widget class="QLabel" name="label_8"> + <property name="text"> + <string>&Author:</string> + </property> + <property name="buddy"> + <cstring>author_box</cstring> + </property> + </widget> + </item> + <item row="5" column="0" colspan="2"> + <layout class="QHBoxLayout" name="horizontalLayout_6"> + <item> + <widget class="QPushButton" name="clear_button"> + <property name="text"> + <string>&Clear</string> + </property> + </widget> + </item> + <item> + <widget class="QDialogButtonBox" name="tab_2_button_box"> + <property name="standardButtons"> + <set>QDialogButtonBox::Cancel|QDialogButtonBox::Ok</set> + </property> + </widget> + </item> + </layout> + </item> + <item row="4" column="1"> + <spacer name="verticalSpacer"> + <property name="orientation"> + <enum>Qt::Vertical</enum> + </property> + <property name="sizeHint" stdset="0"> + <size> + <width>20</width> + <height>40</height> + </size> + </property> + </spacer> + </item> + <item row="0" column="0" colspan="2"> + <widget class="QLabel" name="label_11"> + <property name="text"> + <string>Search only in specific fields:</string> + </property> + </widget> + </item> + <item row="2" column="1"> + <widget class="EnLineEdit" name="author_box"/> + </item> + <item row="3" column="1"> + <widget class="QLineEdit" name="format_box"/> + </item> + <item row="3" column="0"> + <widget class="QLabel" name="label_10"> + <property name="text"> + <string>&Format:</string> + </property> + <property name="buddy"> + <cstring>format_box</cstring> + </property> + </widget> + </item> + </layout> + </widget> + </widget> + </item> + <item row="1" column="1"> + <spacer name="verticalSpacer_3"> + <property name="orientation"> + <enum>Qt::Vertical</enum> + </property> + <property name="sizeHint" stdset="0"> + <size> + <width>20</width> + <height>40</height> + </size> + </property> + </spacer> + </item> + </layout> + </widget> + <customwidgets> + <customwidget> + <class>EnLineEdit</class> + <extends>QLineEdit</extends> + <header>widgets.h</header> + </customwidget> + </customwidgets> + <tabstops> + <tabstop>all</tabstop> + <tabstop>phrase</tabstop> + <tabstop>any</tabstop> + <tabstop>none</tabstop> + <tabstop>buttonBox</tabstop> + <tabstop>title_box</tabstop> + <tabstop>author_box</tabstop> + <tabstop>format_box</tabstop> + <tabstop>clear_button</tabstop> + <tabstop>tab_2_button_box</tabstop> + <tabstop>tabWidget</tabstop> + <tabstop>matchkind</tabstop> + </tabstops> + <resources> + <include location="../../../../resources/images.qrc"/> + </resources> + <connections> + <connection> + <sender>buttonBox</sender> + <signal>accepted()</signal> + <receiver>Dialog</receiver> + <slot>accept()</slot> + <hints> + <hint type="sourcelabel"> + <x>248</x> + <y>254</y> + </hint> + <hint type="destinationlabel"> + <x>157</x> + <y>274</y> + </hint> + </hints> + </connection> + <connection> + <sender>buttonBox</sender> + <signal>rejected()</signal> + <receiver>Dialog</receiver> + <slot>reject()</slot> + <hints> + <hint type="sourcelabel"> + <x>316</x> + <y>260</y> + </hint> + <hint type="destinationlabel"> + <x>286</x> + <y>274</y> + </hint> + </hints> + </connection> + </connections> +</ui> diff --git a/src/calibre/gui2/store/mobileread/cache_progress_dialog.py b/src/calibre/gui2/store/mobileread/cache_progress_dialog.py new file mode 100644 index 0000000000..71416d8680 --- /dev/null +++ b/src/calibre/gui2/store/mobileread/cache_progress_dialog.py @@ -0,0 +1,62 @@ +# -*- coding: utf-8 -*- + +from __future__ import (unicode_literals, division, absolute_import, print_function) + +__license__ = 'GPL 3' +__copyright__ = '2011, John Schember <john@nachtimwald.com>' +__docformat__ = 'restructuredtext en' + +from PyQt4.Qt import QDialog + +from calibre.gui2.store.mobileread.cache_progress_dialog_ui import Ui_Dialog + +class CacheProgressDialog(QDialog, Ui_Dialog): + + def __init__(self, parent=None, total=None): + QDialog.__init__(self, parent) + self.setupUi(self) + + self.completed = 0 + self.canceled = False + + self.progress.setValue(0) + self.progress.setMinimum(0) + self.progress.setMaximum(total if total else 0) + + def exec_(self): + self.completed = 0 + self.canceled = False + QDialog.exec_(self) + + def open(self): + self.completed = 0 + self.canceled = False + QDialog.open(self) + + def reject(self): + self.canceled = True + QDialog.reject(self) + + def update_progress(self): + ''' + completed is an int from 0 to total representing the number + records that have bee completed. + ''' + self.set_progress(self.completed + 1) + + def set_message(self, msg): + self.message.setText(msg) + + def set_details(self, msg): + self.details.setText(msg) + + def set_progress(self, completed): + ''' + completed is an int from 0 to total representing the number + records that have bee completed. + ''' + self.completed = completed + self.progress.setValue(self.completed) + + def set_total(self, total): + self.progress.setMaximum(total) diff --git a/src/calibre/gui2/store/mobileread/cache_progress_dialog.ui b/src/calibre/gui2/store/mobileread/cache_progress_dialog.ui new file mode 100644 index 0000000000..4690f14e7f --- /dev/null +++ b/src/calibre/gui2/store/mobileread/cache_progress_dialog.ui @@ -0,0 +1,104 @@ +<?xml version="1.0" encoding="UTF-8"?> +<ui version="4.0"> + <class>Dialog</class> + <widget class="QDialog" name="Dialog"> + <property name="geometry"> + <rect> + <x>0</x> + <y>0</y> + <width>402</width> + <height>138</height> + </rect> + </property> + <property name="windowTitle"> + <string>Dialog</string> + </property> + <layout class="QVBoxLayout" name="verticalLayout"> + <item> + <widget class="QLabel" name="message"> + <property name="text"> + <string>Updating book cache</string> + </property> + <property name="alignment"> + <set>Qt::AlignCenter</set> + </property> + </widget> + </item> + <item> + <widget class="QProgressBar" name="progress"> + <property name="value"> + <number>24</number> + </property> + </widget> + </item> + <item> + <widget class="QLabel" name="details"> + <property name="text"> + <string/> + </property> + <property name="alignment"> + <set>Qt::AlignCenter</set> + </property> + </widget> + </item> + <item> + <spacer name="verticalSpacer"> + <property name="orientation"> + <enum>Qt::Vertical</enum> + </property> + <property name="sizeHint" stdset="0"> + <size> + <width>20</width> + <height>40</height> + </size> + </property> + </spacer> + </item> + <item> + <widget class="QDialogButtonBox" name="buttonBox"> + <property name="orientation"> + <enum>Qt::Horizontal</enum> + </property> + <property name="standardButtons"> + <set>QDialogButtonBox::Cancel</set> + </property> + </widget> + </item> + </layout> + </widget> + <resources/> + <connections> + <connection> + <sender>buttonBox</sender> + <signal>accepted()</signal> + <receiver>Dialog</receiver> + <slot>accept()</slot> + <hints> + <hint type="sourcelabel"> + <x>248</x> + <y>254</y> + </hint> + <hint type="destinationlabel"> + <x>157</x> + <y>274</y> + </hint> + </hints> + </connection> + <connection> + <sender>buttonBox</sender> + <signal>rejected()</signal> + <receiver>Dialog</receiver> + <slot>reject()</slot> + <hints> + <hint type="sourcelabel"> + <x>316</x> + <y>260</y> + </hint> + <hint type="destinationlabel"> + <x>286</x> + <y>274</y> + </hint> + </hints> + </connection> + </connections> +</ui> diff --git a/src/calibre/gui2/store/mobileread/cache_update_thread.py b/src/calibre/gui2/store/mobileread/cache_update_thread.py new file mode 100644 index 0000000000..f81e7951d4 --- /dev/null +++ b/src/calibre/gui2/store/mobileread/cache_update_thread.py @@ -0,0 +1,94 @@ +# -*- coding: utf-8 -*- + +from __future__ import (unicode_literals, division, absolute_import, print_function) + +__license__ = 'GPL 3' +__copyright__ = '2011, John Schember <john@nachtimwald.com>' +__docformat__ = 'restructuredtext en' + +import time +from contextlib import closing +from threading import Thread + +from lxml import html + +from PyQt4.Qt import (pyqtSignal, QObject) + +from calibre import browser +from calibre.gui2.store.search_result import SearchResult + +class CacheUpdateThread(Thread, QObject): + + total_changed = pyqtSignal(int) + update_progress = pyqtSignal(int) + update_details = pyqtSignal(unicode) + + def __init__(self, config, seralize_books_function, timeout): + Thread.__init__(self) + QObject.__init__(self) + + self.daemon = True + self.config = config + self.seralize_books = seralize_books_function + self.timeout = timeout + self._run = True + + def abort(self): + self._run = False + + def run(self): + url = 'http://www.mobileread.com/forums/ebooks.php?do=getlist&type=html' + + self.update_details.emit(_('Checking last download date.')) + last_download = self.config.get('last_download', None) + # Don't update the book list if our cache is less than one week old. + if last_download and (time.time() - last_download) < 604800: + return + + self.update_details.emit(_('Downloading book list from MobileRead.')) + # Download the book list HTML file from MobileRead. + br = browser() + raw_data = None + try: + with closing(br.open(url, timeout=self.timeout)) as f: + raw_data = f.read() + except: + return + + if not raw_data or not self._run: + return + + self.update_details.emit(_('Processing books.')) + # Turn books listed in the HTML file into SearchResults's. + books = [] + try: + data = html.fromstring(raw_data) + raw_books = data.xpath('//ul/li') + self.total_changed.emit(len(raw_books)) + + for i, book_data in enumerate(raw_books): + self.update_details.emit(_('%s of %s books processed.') % (i, len(raw_books))) + book = SearchResult() + book.detail_item = ''.join(book_data.xpath('.//a/@href')) + book.formats = ''.join(book_data.xpath('.//i/text()')) + book.formats = book.formats.strip() + + text = ''.join(book_data.xpath('.//a/text()')) + if ':' in text: + book.author, q, text = text.partition(':') + book.author = book.author.strip() + book.title = text.strip() + books.append(book) + + if not self._run: + books = [] + break + else: + self.update_progress.emit(i) + except: + pass + + # Save the book list and it's create time. + if books: + self.config['book_list'] = self.seralize_books(books) + self.config['last_download'] = time.time() diff --git a/src/calibre/gui2/store/mobileread/mobileread_plugin.py b/src/calibre/gui2/store/mobileread/mobileread_plugin.py new file mode 100644 index 0000000000..4e11d62bbd --- /dev/null +++ b/src/calibre/gui2/store/mobileread/mobileread_plugin.py @@ -0,0 +1,105 @@ +# -*- coding: utf-8 -*- + +from __future__ import (unicode_literals, division, absolute_import, print_function) + +__license__ = 'GPL 3' +__copyright__ = '2011, John Schember <john@nachtimwald.com>' +__docformat__ = 'restructuredtext en' + +from threading import Lock + +from PyQt4.Qt import (QUrl, QCoreApplication) + +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 +from calibre.gui2.store.mobileread.models import SearchFilter +from calibre.gui2.store.mobileread.cache_progress_dialog import CacheProgressDialog +from calibre.gui2.store.mobileread.cache_update_thread import CacheUpdateThread +from calibre.gui2.store.mobileread.store_dialog import MobileReadStoreDialog + +class MobileReadStore(BasicStoreConfig, StorePlugin): + + def genesis(self): + self.lock = Lock() + + def open(self, parent=None, detail_item=None, external=False): + url = 'http://www.mobileread.com/' + + if external or self.config.get('open_external', False): + open_url(QUrl(detail_item if detail_item else url)) + else: + if detail_item: + d = WebStoreDialog(self.gui, url, parent, detail_item) + d.setWindowTitle(self.name) + d.set_tags(self.config.get('tags', '')) + d.exec_() + else: + self.update_cache(parent, 30) + d = MobileReadStoreDialog(self, parent) + d.setWindowTitle(self.name) + d.exec_() + + def search(self, query, max_results=10, timeout=60): + books = self.get_book_list() + + sf = SearchFilter(books) + matches = sf.parse(query) + + for book in matches: + book.price = '$0.00' + book.drm = SearchResult.DRM_UNLOCKED + yield book + + def update_cache(self, parent=None, timeout=10, force=False, suppress_progress=False): + if self.lock.acquire(False): + try: + update_thread = CacheUpdateThread(self.config, self.seralize_books, timeout) + if not suppress_progress: + progress = CacheProgressDialog(parent) + progress.set_message(_('Updating MobileRead book cache...')) + + update_thread.total_changed.connect(progress.set_total) + update_thread.update_progress.connect(progress.set_progress) + update_thread.update_details.connect(progress.set_details) + progress.rejected.connect(update_thread.abort) + + progress.open() + update_thread.start() + while update_thread.is_alive() and not progress.canceled: + QCoreApplication.processEvents() + + if progress.isVisible(): + progress.accept() + return not progress.canceled + else: + update_thread.start() + finally: + self.lock.release() + + def get_book_list(self): + return self.deseralize_books(self.config.get('book_list', [])) + + def seralize_books(self, books): + sbooks = [] + for b in books: + data = {} + data['author'] = b.author + data['title'] = b.title + data['detail_item'] = b.detail_item + data['formats'] = b.formats + sbooks.append(data) + return sbooks + + def deseralize_books(self, sbooks): + books = [] + for s in sbooks: + b = SearchResult() + b.author = s.get('author', '') + b.title = s.get('title', '') + b.detail_item = s.get('detail_item', '') + b.formats = s.get('formats', '') + books.append(b) + return books diff --git a/src/calibre/gui2/store/mobileread/models.py b/src/calibre/gui2/store/mobileread/models.py new file mode 100644 index 0000000000..297707e248 --- /dev/null +++ b/src/calibre/gui2/store/mobileread/models.py @@ -0,0 +1,191 @@ +# -*- coding: utf-8 -*- + +from __future__ import (unicode_literals, division, absolute_import, print_function) + +__license__ = 'GPL 3' +__copyright__ = '2011, John Schember <john@nachtimwald.com>' +__docformat__ = 'restructuredtext en' + +from operator import attrgetter + +from PyQt4.Qt import (Qt, QAbstractItemModel, QModelIndex, QVariant, pyqtSignal) + +from calibre.gui2 import NONE +from calibre.library.caches import _match, CONTAINS_MATCH, EQUALS_MATCH, \ + REGEXP_MATCH +from calibre.utils.icu import sort_key +from calibre.utils.search_query_parser import SearchQueryParser + +class BooksModel(QAbstractItemModel): + + total_changed = pyqtSignal(int) + + HEADERS = [_('Title'), _('Author(s)'), _('Format')] + + def __init__(self, all_books): + QAbstractItemModel.__init__(self) + self.books = all_books + self.all_books = all_books + self.filter = '' + self.search_filter = SearchFilter(all_books) + self.sort_col = 0 + self.sort_order = Qt.AscendingOrder + + def get_book(self, index): + row = index.row() + if row < len(self.books): + return self.books[row] + else: + return None + + def search(self, filter): + self.filter = filter.strip() + if not self.filter: + self.books = self.all_books + else: + try: + self.books = list(self.search_filter.parse(self.filter)) + except: + self.books = self.all_books + self.layoutChanged.emit() + self.sort(self.sort_col, self.sort_order) + self.total_changed.emit(self.rowCount()) + + def index(self, row, column, parent=QModelIndex()): + return self.createIndex(row, column) + + def parent(self, index): + if not index.isValid() or index.internalId() == 0: + return QModelIndex() + return self.createIndex(0, 0) + + def rowCount(self, *args): + return len(self.books) + + def columnCount(self, *args): + return len(self.HEADERS) + + def headerData(self, section, orientation, role): + if role != Qt.DisplayRole: + return NONE + text = '' + if orientation == Qt.Horizontal: + if section < len(self.HEADERS): + text = self.HEADERS[section] + return QVariant(text) + else: + return QVariant(section+1) + + def data(self, index, role): + row, col = index.row(), index.column() + result = self.books[row] + if role == Qt.DisplayRole: + if col == 0: + return QVariant(result.title) + elif col == 1: + return QVariant(result.author) + elif col == 2: + return QVariant(result.formats) + return NONE + + def data_as_text(self, result, col): + text = '' + if col == 0: + text = result.title + elif col == 1: + text = result.author + elif col == 2: + text = result.formats + return text + + def sort(self, col, order, reset=True): + self.sort_col = col + self.sort_order = order + if not self.books: + return + descending = order == Qt.DescendingOrder + self.books.sort(None, + lambda x: sort_key(unicode(self.data_as_text(x, col))), + descending) + if reset: + self.reset() + + +class SearchFilter(SearchQueryParser): + + USABLE_LOCATIONS = [ + 'all', + 'author', + 'authors', + 'format', + 'formats', + 'title', + ] + + def __init__(self, all_books=[]): + SearchQueryParser.__init__(self, locations=self.USABLE_LOCATIONS) + self.srs = set(all_books) + + def universal_set(self): + return self.srs + + def get_matches(self, location, query): + location = location.lower().strip() + if location == 'authors': + location = 'author' + elif location == 'formats': + location = 'format' + + matchkind = CONTAINS_MATCH + if len(query) > 1: + if query.startswith('\\'): + query = query[1:] + elif query.startswith('='): + matchkind = EQUALS_MATCH + query = query[1:] + elif query.startswith('~'): + matchkind = REGEXP_MATCH + query = query[1:] + if matchkind != REGEXP_MATCH: ### leave case in regexps because it can be significant e.g. \S \W \D + query = query.lower() + + if location not in self.USABLE_LOCATIONS: + return set([]) + matches = set([]) + all_locs = set(self.USABLE_LOCATIONS) - set(['all']) + locations = all_locs if location == 'all' else [location] + q = { + 'author': lambda x: x.author.lower(), + 'format': attrgetter('formats'), + 'title': lambda x: x.title.lower(), + } + for x in ('author', 'format'): + q[x+'s'] = q[x] + for sr in self.srs: + for locvalue in locations: + accessor = q[locvalue] + if query == 'true': + if accessor(sr) is not None: + matches.add(sr) + continue + if query == 'false': + if accessor(sr) is None: + matches.add(sr) + continue + try: + ### Can't separate authors because comma is used for name sep and author sep + ### Exact match might not get what you want. For that reason, turn author + ### exactmatch searches into contains searches. + if locvalue == 'author' and matchkind == EQUALS_MATCH: + m = CONTAINS_MATCH + else: + m = matchkind + + vals = [accessor(sr)] + if _match(query, vals, m): + matches.add(sr) + break + except ValueError: # Unicode errors + import traceback + traceback.print_exc() + return matches diff --git a/src/calibre/gui2/store/mobileread/store_dialog.py b/src/calibre/gui2/store/mobileread/store_dialog.py new file mode 100644 index 0000000000..8908c9bb68 --- /dev/null +++ b/src/calibre/gui2/store/mobileread/store_dialog.py @@ -0,0 +1,82 @@ +# -*- coding: utf-8 -*- + +from __future__ import (unicode_literals, division, absolute_import, print_function) + +__license__ = 'GPL 3' +__copyright__ = '2011, John Schember <john@nachtimwald.com>' +__docformat__ = 'restructuredtext en' + + +from PyQt4.Qt import (Qt, QDialog, QIcon) + +from calibre.gui2.store.mobileread.adv_search_builder import AdvSearchBuilderDialog +from calibre.gui2.store.mobileread.models import BooksModel +from calibre.gui2.store.mobileread.store_dialog_ui import Ui_Dialog + +class MobileReadStoreDialog(QDialog, Ui_Dialog): + + def __init__(self, plugin, *args): + QDialog.__init__(self, *args) + self.setupUi(self) + + self.plugin = plugin + self.search_query.initialize('store_mobileread_search') + + self.adv_search_button.setIcon(QIcon(I('search.png'))) + + self._model = BooksModel(self.plugin.get_book_list()) + self.results_view.setModel(self._model) + self.total.setText('%s' % self.results_view.model().rowCount()) + + self.search_button.clicked.connect(self.do_search) + self.adv_search_button.clicked.connect(self.build_adv_search) + self.results_view.activated.connect(self.open_store) + self.results_view.model().total_changed.connect(self.update_book_total) + self.finished.connect(self.dialog_closed) + + self.restore_state() + + def do_search(self): + self.results_view.model().search(unicode(self.search_query.text())) + + def open_store(self, index): + result = self.results_view.model().get_book(index) + if result: + self.plugin.open(self, result.detail_item) + + def update_book_total(self, total): + self.total.setText('%s' % total) + + def build_adv_search(self): + adv = AdvSearchBuilderDialog(self) + if adv.exec_() == QDialog.Accepted: + self.search_query.setText(adv.search_string()) + + def restore_state(self): + geometry = self.plugin.config.get('dialog_geometry', None) + if geometry: + self.restoreGeometry(geometry) + + results_cwidth = self.plugin.config.get('dialog_results_view_column_width') + if results_cwidth: + for i, x in enumerate(results_cwidth): + if i >= self.results_view.model().columnCount(): + break + self.results_view.setColumnWidth(i, x) + else: + for i in xrange(self.results_view.model().columnCount()): + self.results_view.resizeColumnToContents(i) + + self.results_view.model().sort_col = self.plugin.config.get('dialog_sort_col', 0) + self.results_view.model().sort_order = self.plugin.config.get('dialog_sort_order', Qt.AscendingOrder) + self.results_view.model().sort(self.results_view.model().sort_col, self.results_view.model().sort_order) + self.results_view.header().setSortIndicator(self.results_view.model().sort_col, self.results_view.model().sort_order) + + def save_state(self): + self.plugin.config['dialog_geometry'] = bytearray(self.saveGeometry()) + self.plugin.config['dialog_results_view_column_width'] = [self.results_view.columnWidth(i) for i in range(self.results_view.model().columnCount())] + self.plugin.config['dialog_sort_col'] = self.results_view.model().sort_col + self.plugin.config['dialog_sort_order'] = self.results_view.model().sort_order + + def dialog_closed(self, result): + self.save_state() diff --git a/src/calibre/gui2/store/mobileread/store_dialog.ui b/src/calibre/gui2/store/mobileread/store_dialog.ui new file mode 100644 index 0000000000..b698352dfd --- /dev/null +++ b/src/calibre/gui2/store/mobileread/store_dialog.ui @@ -0,0 +1,143 @@ +<?xml version="1.0" encoding="UTF-8"?> +<ui version="4.0"> + <class>Dialog</class> + <widget class="QDialog" name="Dialog"> + <property name="geometry"> + <rect> + <x>0</x> + <y>0</y> + <width>691</width> + <height>614</height> + </rect> + </property> + <property name="windowTitle"> + <string>Dialog</string> + </property> + <layout class="QVBoxLayout" name="verticalLayout"> + <item> + <layout class="QHBoxLayout" name="horizontalLayout_2"> + <item> + <widget class="QLabel" name="label"> + <property name="text"> + <string>&Query:</string> + </property> + <property name="buddy"> + <cstring>search_query</cstring> + </property> + </widget> + </item> + <item> + <widget class="QToolButton" name="adv_search_button"> + <property name="text"> + <string>...</string> + </property> + </widget> + </item> + <item> + <widget class="HistoryLineEdit" name="search_query"> + <property name="sizePolicy"> + <sizepolicy hsizetype="Expanding" vsizetype="Fixed"> + <horstretch>0</horstretch> + <verstretch>0</verstretch> + </sizepolicy> + </property> + </widget> + </item> + <item> + <widget class="QPushButton" name="search_button"> + <property name="text"> + <string>Search</string> + </property> + </widget> + </item> + </layout> + </item> + <item> + <widget class="QTreeView" name="results_view"> + <property name="alternatingRowColors"> + <bool>true</bool> + </property> + <property name="rootIsDecorated"> + <bool>false</bool> + </property> + <property name="itemsExpandable"> + <bool>false</bool> + </property> + <property name="sortingEnabled"> + <bool>true</bool> + </property> + <property name="expandsOnDoubleClick"> + <bool>false</bool> + </property> + <attribute name="headerCascadingSectionResizes"> + <bool>false</bool> + </attribute> + </widget> + </item> + <item> + <layout class="QHBoxLayout" name="horizontalLayout"> + <item> + <widget class="QLabel" name="label_2"> + <property name="text"> + <string>Books:</string> + </property> + </widget> + </item> + <item> + <widget class="QLabel" name="total"> + <property name="text"> + <string>0</string> + </property> + </widget> + </item> + <item> + <spacer name="horizontalSpacer"> + <property name="orientation"> + <enum>Qt::Horizontal</enum> + </property> + <property name="sizeHint" stdset="0"> + <size> + <width>308</width> + <height>20</height> + </size> + </property> + </spacer> + </item> + <item> + <widget class="QPushButton" name="close_button"> + <property name="text"> + <string>Close</string> + </property> + </widget> + </item> + </layout> + </item> + </layout> + </widget> + <customwidgets> + <customwidget> + <class>HistoryLineEdit</class> + <extends>QLineEdit</extends> + <header>widgets.h</header> + </customwidget> + </customwidgets> + <resources/> + <connections> + <connection> + <sender>close_button</sender> + <signal>clicked()</signal> + <receiver>Dialog</receiver> + <slot>accept()</slot> + <hints> + <hint type="sourcelabel"> + <x>440</x> + <y>432</y> + </hint> + <hint type="destinationlabel"> + <x>245</x> + <y>230</y> + </hint> + </hints> + </connection> + </connections> +</ui> diff --git a/src/calibre/gui2/store/nexto_plugin.py b/src/calibre/gui2/store/nexto_plugin.py new file mode 100644 index 0000000000..0009f39b1b --- /dev/null +++ b/src/calibre/gui2/store/nexto_plugin.py @@ -0,0 +1,87 @@ +# -*- coding: utf-8 -*- + +from __future__ import (unicode_literals, division, absolute_import, print_function) + +__license__ = 'GPL 3' +__copyright__ = '2011, Tomasz Długosz <tomek3d@gmail.com>' +__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 NextoStore(BasicStoreConfig, StorePlugin): + + def open(self, parent=None, detail_item=None, external=False): + pid = '155711' + + url = 'http://www.nexto.pl/ebooki_c1015.xml' + detail_url = None + + if detail_item: + book_id = re.search(r'p[0-9]*\.xml\Z', detail_item) + book_id = book_id.group(0).replace('.xml','').replace('p','') + if book_id: + detail_url = 'http://www.nexto.pl/rf/pr?p=' + book_id + '&pid=' + pid + + 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_() + + def search(self, query, max_results=10, timeout=60): + url = 'http://www.nexto.pl/szukaj.xml?search-clause=' + urllib.quote_plus(query.encode('utf-8')) + '&scid=1015' + + br = browser() + + counter = max_results + with closing(br.open(url, timeout=timeout)) as f: + doc = html.fromstring(f.read()) + for data in doc.xpath('//ul[@class="productslist"]/li'): + if counter <= 0: + break + + id = ''.join(data.xpath('.//div[@class="cover_container"]/a[1]/@href')) + if not id: + continue + + price = ''.join(data.xpath('.//strong[@class="nprice"]/text()')) + + cover_url = ''.join(data.xpath('.//img[@class="cover"]/@src')) + title = ''.join(data.xpath('.//a[@class="title"]/text()')) + title = re.sub(r' - ebook$', '', title) + formats = ', '.join(data.xpath('.//ul[@class="formats_available"]/li//b/text()')) + DrmFree = re.search(r'bez.DRM', formats) + formats = re.sub(r'\(.+\)', '', formats) + + author = '' + with closing(br.open('http://www.nexto.pl/' + id.strip(), timeout=timeout/4)) as nf: + idata = html.fromstring(nf.read()) + author = ''.join(idata.xpath('//div[@class="basic_data"]/p[1]/b/a/text()')) + + counter -= 1 + + s = SearchResult() + s.cover_url = cover_url + s.title = title.strip() + s.author = author.strip() + s.price = price + s.detail_item = id.strip() + s.drm = SearchResult.DRM_UNLOCKED if DrmFree else SearchResult.DRM_LOCKED + s.formats = formats.upper().strip() + + yield s diff --git a/src/calibre/gui2/store/open_library_plugin.py b/src/calibre/gui2/store/open_library_plugin.py new file mode 100644 index 0000000000..b95f1bf930 --- /dev/null +++ b/src/calibre/gui2/store/open_library_plugin.py @@ -0,0 +1,84 @@ +# -*- coding: utf-8 -*- + +from __future__ import (unicode_literals, division, absolute_import, print_function) + +__license__ = 'GPL 3' +__copyright__ = '2011, John Schember <john@nachtimwald.com>' +__docformat__ = 'restructuredtext en' + +import urllib2 +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 OpenLibraryStore(BasicStoreConfig, StorePlugin): + + def open(self, parent=None, detail_item=None, external=False): + url = 'http://openlibrary.org/' + + if external or self.config.get('open_external', False): + if detail_item: + url = url + detail_item + open_url(QUrl(url_slash_cleaner(url))) + else: + detail_url = None + if detail_item: + detail_url = url + detail_item + d = WebStoreDialog(self.gui, url, parent, detail_url) + d.setWindowTitle(self.name) + d.set_tags(self.config.get('tags', '')) + d.exec_() + + def search(self, query, max_results=10, timeout=60): + url = 'http://openlibrary.org/search?q=' + urllib2.quote(query) + '&has_fulltext=true' + + br = browser() + + counter = max_results + with closing(br.open(url, timeout=timeout)) as f: + doc = html.fromstring(f.read()) + for data in doc.xpath('//div[@id="searchResults"]/ul[@id="siteSearch"]/li'): + if counter <= 0: + break + + # Don't include books that don't have downloadable files. + if not data.xpath('boolean(./span[@class="actions"]//span[@class="label" and contains(text(), "Read")])'): + continue + id = ''.join(data.xpath('./span[@class="bookcover"]/a/@href')) + if not id: + continue + cover_url = ''.join(data.xpath('./span[@class="bookcover"]/a/img/@src')) + + title = ''.join(data.xpath('.//h3[@class="booktitle"]/a[@class="results"]/text()')) + author = ''.join(data.xpath('.//span[@class="bookauthor"]/a/text()')) + price = '$0.00' + + counter -= 1 + + s = SearchResult() + s.cover_url = cover_url + s.title = title.strip() + s.author = author.strip() + s.price = price + s.detail_item = id.strip() + s.drm = SearchResult.DRM_UNLOCKED + + yield s + + def get_details(self, search_result, timeout): + url = 'http://openlibrary.org/' + + br = browser() + with closing(br.open(url_slash_cleaner(url + search_result.detail_item), timeout=timeout)) as nf: + idata = html.fromstring(nf.read()) + search_result.formats = ', '.join(list(set(idata.xpath('//a[contains(@title, "Download")]/text()')))) + return True diff --git a/src/calibre/gui2/store/oreilly_plugin.py b/src/calibre/gui2/store/oreilly_plugin.py new file mode 100644 index 0000000000..602a98c68e --- /dev/null +++ b/src/calibre/gui2/store/oreilly_plugin.py @@ -0,0 +1,87 @@ +# -*- coding: utf-8 -*- + +from __future__ import (unicode_literals, division, absolute_import, print_function) + +__license__ = 'GPL 3' +__copyright__ = '2011, John Schember <john@nachtimwald.com>' +__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 OReillyStore(BasicStoreConfig, StorePlugin): + + def open(self, parent=None, detail_item=None, external=False): + url = 'http://oreilly.com/ebooks/' + + if detail_item: + detail_item = 'https://epoch.oreilly.com/shop/cart.orm?prod=%s.EBOOK&p=CALIBRE' % detail_item + + 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 = 'http://search.oreilly.com/?t1=Books&t2=Format&t3=Ebook&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('//div[@id="results"]/div[@class="result"]'): + if counter <= 0: + break + + full_id = ''.join(data.xpath('.//div[@class="title"]/a/@href')) + mo = re.search('\d+', full_id) + if not mo: + continue + id = mo.group() + + cover_url = ''.join(data.xpath('.//div[@class="bigCover"]//img/@src')) + + title = ''.join(data.xpath('.//div[@class="title"]/a/text()')) + author = ''.join(data.xpath('.//div[@class="author"]/text()')) + author = author.split('By ')[-1].strip() + + # Get the detail here because we need to get the ebook id for the detail_item. + with closing(br.open(full_id, timeout=timeout)) as nf: + idoc = html.fromstring(nf.read()) + + price = ''.join(idoc.xpath('(//span[@class="price"])[1]/span//text()')) + formats = ', '.join(idoc.xpath('//div[@class="ebook_formats"]//a/text()')) + + eid = ''.join(idoc.xpath('(//a[@class="product_buy_link" and contains(@href, ".EBOOK")])[1]/@href')).strip() + mo = re.search('\d+', eid) + if mo: + id = mo.group() + + counter -= 1 + + s = SearchResult() + s.cover_url = cover_url.strip() + s.title = title.strip() + s.author = author.strip() + s.detail_item = id.strip() + s.price = price.strip() + s.drm = SearchResult.DRM_UNLOCKED + s.formats = formats.upper() + + yield s diff --git a/src/calibre/gui2/store/pragmatic_bookshelf_plugin.py b/src/calibre/gui2/store/pragmatic_bookshelf_plugin.py new file mode 100644 index 0000000000..f3803bbcea --- /dev/null +++ b/src/calibre/gui2/store/pragmatic_bookshelf_plugin.py @@ -0,0 +1,84 @@ +# -*- coding: utf-8 -*- + +from __future__ import (unicode_literals, division, absolute_import, print_function) + +__license__ = 'GPL 3' +__copyright__ = '2011, John Schember <john@nachtimwald.com>' +__docformat__ = 'restructuredtext en' + +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 PragmaticBookshelfStore(BasicStoreConfig, StorePlugin): + + def open(self, parent=None, detail_item=None, external=False): + url = 'http://pragprog.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): + ''' + OPDS based search. + + We really should get the catelog from http://pragprog.com/catalog.opds + and look for the application/opensearchdescription+xml entry. + Then get the opensearch description to get the search url and + format. However, we are going to be lazy and hard code it. + ''' + url = 'http://pragprog.com/catalog/search?q=' + urllib.quote_plus(query) + + br = browser() + + counter = max_results + with closing(br.open(url, timeout=timeout)) as f: + # Use html instead of etree as html allows us + # to ignore the namespace easily. + doc = html.fromstring(f.read()) + for data in doc.xpath('//entry'): + if counter <= 0: + break + + id = ''.join(data.xpath('.//link[@rel="http://opds-spec.org/acquisition/buy"]/@href')) + if not id: + continue + + price = ''.join(data.xpath('.//price/@currencycode')).strip() + price += ' ' + price += ''.join(data.xpath('.//price/text()')).strip() + if not price.strip(): + continue + + cover_url = ''.join(data.xpath('.//link[@rel="http://opds-spec.org/cover"]/@href')) + + title = ''.join(data.xpath('.//title/text()')) + author = ''.join(data.xpath('.//author//text()')) + + counter -= 1 + + s = SearchResult() + s.cover_url = cover_url + s.title = title.strip() + s.author = author.strip() + s.price = price.strip() + s.detail_item = id.strip() + s.drm = SearchResult.DRM_UNLOCKED + s.formats = 'EPUB, PDF, MOBI' + + yield s diff --git a/src/calibre/gui2/store/search/__init__.py b/src/calibre/gui2/store/search/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/src/calibre/gui2/store/search/adv_search_builder.py b/src/calibre/gui2/store/search/adv_search_builder.py new file mode 100644 index 0000000000..cc89ca4eb7 --- /dev/null +++ b/src/calibre/gui2/store/search/adv_search_builder.py @@ -0,0 +1,127 @@ +# -*- coding: utf-8 -*- + +from __future__ import (unicode_literals, division, absolute_import, print_function) + +__license__ = 'GPL 3' +__copyright__ = '2011, John Schember <john@nachtimwald.com>' +__docformat__ = 'restructuredtext en' + +import re + +from PyQt4.Qt import (QDialog, QDialogButtonBox) + +from calibre.gui2.store.search.adv_search_builder_ui import Ui_Dialog +from calibre.library.caches import CONTAINS_MATCH, EQUALS_MATCH + +class AdvSearchBuilderDialog(QDialog, Ui_Dialog): + + def __init__(self, parent): + QDialog.__init__(self, parent) + self.setupUi(self) + + self.buttonBox.accepted.connect(self.advanced_search_button_pushed) + self.tab_2_button_box.accepted.connect(self.accept) + self.tab_2_button_box.rejected.connect(self.reject) + self.clear_button.clicked.connect(self.clear_button_pushed) + self.adv_search_used = False + self.mc = '' + + self.tabWidget.setCurrentIndex(0) + self.tabWidget.currentChanged[int].connect(self.tab_changed) + self.tab_changed(0) + + def tab_changed(self, idx): + if idx == 1: + self.tab_2_button_box.button(QDialogButtonBox.Ok).setDefault(True) + else: + self.buttonBox.button(QDialogButtonBox.Ok).setDefault(True) + + def advanced_search_button_pushed(self): + self.adv_search_used = True + self.accept() + + def clear_button_pushed(self): + self.title_box.setText('') + self.author_box.setText('') + self.price_box.setText('') + self.format_box.setText('') + self.affiliate_combo.setCurrentIndex(0) + + def tokens(self, raw): + phrases = re.findall(r'\s*".*?"\s*', raw) + for f in phrases: + raw = raw.replace(f, ' ') + phrases = [t.strip('" ') for t in phrases] + return ['"' + self.mc + t + '"' for t in phrases + [r.strip() for r in raw.split()]] + + def search_string(self): + if self.adv_search_used: + return self.adv_search_string() + else: + return self.box_search_string() + + def adv_search_string(self): + mk = self.matchkind.currentIndex() + if mk == CONTAINS_MATCH: + self.mc = '' + elif mk == EQUALS_MATCH: + self.mc = '=' + else: + self.mc = '~' + all, any, phrase, none = map(lambda x: unicode(x.text()), + (self.all, self.any, self.phrase, self.none)) + all, any, none = map(self.tokens, (all, any, none)) + phrase = phrase.strip() + all = ' and '.join(all) + any = ' or '.join(any) + none = ' and not '.join(none) + ans = '' + if phrase: + ans += '"%s"'%phrase + if all: + ans += (' and ' if ans else '') + all + if none: + ans += (' and not ' if ans else 'not ') + none + if any: + ans += (' or ' if ans else '') + any + return ans + + def token(self): + txt = unicode(self.text.text()).strip() + if txt: + if self.negate.isChecked(): + txt = '!'+txt + tok = self.FIELDS[unicode(self.field.currentText())]+txt + if re.search(r'\s', tok): + tok = '"%s"'%tok + return tok + + def box_search_string(self): + mk = self.matchkind.currentIndex() + if mk == CONTAINS_MATCH: + self.mc = '' + elif mk == EQUALS_MATCH: + self.mc = '=' + else: + self.mc = '~' + + ans = [] + self.box_last_values = {} + title = unicode(self.title_box.text()).strip() + if title: + ans.append('title:"' + self.mc + title + '"') + author = unicode(self.author_box.text()).strip() + if author: + ans.append('author:"' + self.mc + author + '"') + price = unicode(self.price_box.text()).strip() + if price: + ans.append('price:"' + self.mc + price + '"') + format = unicode(self.format_box.text()).strip() + if format: + ans.append('format:"' + self.mc + format + '"') + affiliate = unicode(self.affiliate_combo.currentText()).strip() + if affiliate: + ans.append('affiliate:' + affiliate) + if ans: + return ' and '.join(ans) + return '' diff --git a/src/calibre/gui2/store/search/adv_search_builder.ui b/src/calibre/gui2/store/search/adv_search_builder.ui new file mode 100644 index 0000000000..ab12dbbc00 --- /dev/null +++ b/src/calibre/gui2/store/search/adv_search_builder.ui @@ -0,0 +1,390 @@ +<?xml version="1.0" encoding="UTF-8"?> +<ui version="4.0"> + <class>Dialog</class> + <widget class="QDialog" name="Dialog"> + <property name="geometry"> + <rect> + <x>0</x> + <y>0</y> + <width>752</width> + <height>472</height> + </rect> + </property> + <property name="windowTitle"> + <string>Advanced Search</string> + </property> + <property name="windowIcon"> + <iconset> + <normaloff>:/images/search.png</normaloff>:/images/search.png</iconset> + </property> + <layout class="QGridLayout" name="gridLayout_2"> + <item row="0" column="0"> + <widget class="QLabel" name="label_5"> + <property name="text"> + <string>&What kind of match to use:</string> + </property> + <property name="buddy"> + <cstring>matchkind</cstring> + </property> + </widget> + </item> + <item row="0" column="1"> + <widget class="QComboBox" name="matchkind"> + <item> + <property name="text"> + <string>Contains: the word or phrase matches anywhere in the metadata field</string> + </property> + </item> + <item> + <property name="text"> + <string>Equals: the word or phrase must match the entire metadata field</string> + </property> + </item> + <item> + <property name="text"> + <string>Regular expression: the expression must match anywhere in the metadata field</string> + </property> + </item> + </widget> + </item> + <item row="2" column="0" colspan="2"> + <widget class="QTabWidget" name="tabWidget"> + <property name="currentIndex"> + <number>0</number> + </property> + <widget class="QWidget" name="tab"> + <attribute name="title"> + <string>A&dvanced Search</string> + </attribute> + <layout class="QGridLayout" name="gridLayout_3"> + <item row="0" column="0"> + <widget class="QGroupBox" name="groupBox"> + <property name="title"> + <string>Find entries that have...</string> + </property> + <layout class="QVBoxLayout" name="verticalLayout"> + <item> + <layout class="QHBoxLayout" name="horizontalLayout"> + <item> + <widget class="QLabel" name="label"> + <property name="text"> + <string>&All these words:</string> + </property> + <property name="buddy"> + <cstring>all</cstring> + </property> + </widget> + </item> + <item> + <widget class="QLineEdit" name="all"/> + </item> + </layout> + </item> + <item> + <layout class="QHBoxLayout" name="horizontalLayout_2"> + <item> + <widget class="QLabel" name="label_2"> + <property name="text"> + <string>This exact &phrase:</string> + </property> + <property name="buddy"> + <cstring>all</cstring> + </property> + </widget> + </item> + <item> + <widget class="QLineEdit" name="phrase"/> + </item> + </layout> + </item> + <item> + <layout class="QHBoxLayout" name="horizontalLayout_3"> + <item> + <widget class="QLabel" name="label_3"> + <property name="text"> + <string>&One or more of these words:</string> + </property> + <property name="buddy"> + <cstring>all</cstring> + </property> + </widget> + </item> + <item> + <widget class="QLineEdit" name="any"/> + </item> + </layout> + </item> + </layout> + </widget> + </item> + <item row="1" column="0"> + <widget class="QGroupBox" name="groupBox_2"> + <property name="title"> + <string>But dont show entries that have...</string> + </property> + <layout class="QVBoxLayout" name="verticalLayout_2"> + <item> + <layout class="QHBoxLayout" name="horizontalLayout_4"> + <item> + <widget class="QLabel" name="label_4"> + <property name="text"> + <string>Any of these &unwanted words:</string> + </property> + <property name="buddy"> + <cstring>all</cstring> + </property> + </widget> + </item> + <item> + <widget class="QLineEdit" name="none"/> + </item> + </layout> + </item> + <item> + <widget class="QLabel" name="label_6"> + <property name="maximumSize"> + <size> + <width>16777215</width> + <height>30</height> + </size> + </property> + <property name="text"> + <string>See the <a href="http://manual.calibre-ebook.com/gui.html#the-search-interface">User Manual</a> for more help</string> + </property> + <property name="openExternalLinks"> + <bool>true</bool> + </property> + </widget> + </item> + </layout> + </widget> + </item> + <item row="2" column="0"> + <spacer name="verticalSpacer_2"> + <property name="orientation"> + <enum>Qt::Vertical</enum> + </property> + <property name="sizeHint" stdset="0"> + <size> + <width>20</width> + <height>40</height> + </size> + </property> + </spacer> + </item> + <item row="3" column="0"> + <widget class="QDialogButtonBox" name="buttonBox"> + <property name="orientation"> + <enum>Qt::Horizontal</enum> + </property> + <property name="standardButtons"> + <set>QDialogButtonBox::Cancel|QDialogButtonBox::Ok</set> + </property> + </widget> + </item> + </layout> + </widget> + <widget class="QWidget" name="tab_2"> + <attribute name="title"> + <string>Titl&e/Author/Price ...</string> + </attribute> + <layout class="QGridLayout" name="gridLayout"> + <item row="1" column="0"> + <widget class="QLabel" name="label_7"> + <property name="text"> + <string>&Title:</string> + </property> + <property name="buddy"> + <cstring>title_box</cstring> + </property> + </widget> + </item> + <item row="1" column="1"> + <widget class="EnLineEdit" name="title_box"> + <property name="toolTip"> + <string>Enter the title.</string> + </property> + </widget> + </item> + <item row="2" column="0"> + <widget class="QLabel" name="label_8"> + <property name="text"> + <string>&Author:</string> + </property> + <property name="buddy"> + <cstring>author_box</cstring> + </property> + </widget> + </item> + <item row="3" column="0"> + <widget class="QLabel" name="price_label"> + <property name="text"> + <string>&Price:</string> + </property> + <property name="buddy"> + <cstring>price_box</cstring> + </property> + </widget> + </item> + <item row="7" column="0" colspan="2"> + <layout class="QHBoxLayout" name="horizontalLayout_6"> + <item> + <widget class="QPushButton" name="clear_button"> + <property name="text"> + <string>&Clear</string> + </property> + </widget> + </item> + <item> + <widget class="QDialogButtonBox" name="tab_2_button_box"> + <property name="standardButtons"> + <set>QDialogButtonBox::Cancel|QDialogButtonBox::Ok</set> + </property> + </widget> + </item> + </layout> + </item> + <item row="6" column="1"> + <spacer name="verticalSpacer"> + <property name="orientation"> + <enum>Qt::Vertical</enum> + </property> + <property name="sizeHint" stdset="0"> + <size> + <width>20</width> + <height>40</height> + </size> + </property> + </spacer> + </item> + <item row="0" column="0" colspan="2"> + <widget class="QLabel" name="label_11"> + <property name="text"> + <string>Search only in specific fields:</string> + </property> + </widget> + </item> + <item row="2" column="1"> + <widget class="EnLineEdit" name="author_box"/> + </item> + <item row="4" column="1"> + <widget class="QLineEdit" name="format_box"/> + </item> + <item row="4" column="0"> + <widget class="QLabel" name="label_10"> + <property name="text"> + <string>&Format:</string> + </property> + <property name="buddy"> + <cstring>format_box</cstring> + </property> + </widget> + </item> + <item row="3" column="1"> + <widget class="EnLineEdit" name="price_box"/> + </item> + <item row="5" column="0"> + <widget class="QLabel" name="label_9"> + <property name="text"> + <string>Affiliate:</string> + </property> + </widget> + </item> + <item row="5" column="1"> + <widget class="QComboBox" name="affiliate_combo"> + <item> + <property name="text"> + <string/> + </property> + </item> + <item> + <property name="text"> + <string>true</string> + </property> + </item> + <item> + <property name="text"> + <string>false</string> + </property> + </item> + </widget> + </item> + </layout> + </widget> + </widget> + </item> + <item row="1" column="1"> + <spacer name="verticalSpacer_3"> + <property name="orientation"> + <enum>Qt::Vertical</enum> + </property> + <property name="sizeHint" stdset="0"> + <size> + <width>20</width> + <height>40</height> + </size> + </property> + </spacer> + </item> + </layout> + </widget> + <customwidgets> + <customwidget> + <class>EnLineEdit</class> + <extends>QLineEdit</extends> + <header>widgets.h</header> + </customwidget> + </customwidgets> + <tabstops> + <tabstop>all</tabstop> + <tabstop>phrase</tabstop> + <tabstop>any</tabstop> + <tabstop>none</tabstop> + <tabstop>buttonBox</tabstop> + <tabstop>title_box</tabstop> + <tabstop>author_box</tabstop> + <tabstop>price_box</tabstop> + <tabstop>format_box</tabstop> + <tabstop>clear_button</tabstop> + <tabstop>tab_2_button_box</tabstop> + <tabstop>tabWidget</tabstop> + <tabstop>matchkind</tabstop> + </tabstops> + <resources> + <include location="../../../../resources/images.qrc"/> + </resources> + <connections> + <connection> + <sender>buttonBox</sender> + <signal>accepted()</signal> + <receiver>Dialog</receiver> + <slot>accept()</slot> + <hints> + <hint type="sourcelabel"> + <x>248</x> + <y>254</y> + </hint> + <hint type="destinationlabel"> + <x>157</x> + <y>274</y> + </hint> + </hints> + </connection> + <connection> + <sender>buttonBox</sender> + <signal>rejected()</signal> + <receiver>Dialog</receiver> + <slot>reject()</slot> + <hints> + <hint type="sourcelabel"> + <x>316</x> + <y>260</y> + </hint> + <hint type="destinationlabel"> + <x>286</x> + <y>274</y> + </hint> + </hints> + </connection> + </connections> +</ui> diff --git a/src/calibre/gui2/store/search/download_thread.py b/src/calibre/gui2/store/search/download_thread.py new file mode 100644 index 0000000000..c55c487b5f --- /dev/null +++ b/src/calibre/gui2/store/search/download_thread.py @@ -0,0 +1,234 @@ +# -*- coding: utf-8 -*- + +from __future__ import (unicode_literals, division, absolute_import, print_function) + +__license__ = 'GPL 3' +__copyright__ = '2011, John Schember <john@nachtimwald.com>' +__docformat__ = 'restructuredtext en' + +import traceback +from contextlib import closing +from threading import Thread +from Queue import Queue + +from calibre import browser +from calibre.constants import DEBUG +from calibre.utils.magick.draw import thumbnail + +class GenericDownloadThreadPool(object): + ''' + add_task must be implemented in a subclass and must + GenericDownloadThreadPool.add_task must be called + at the end of the function. + ''' + + def __init__(self, thread_type, thread_count=1): + self.thread_type = thread_type + self.thread_count = thread_count + + self.tasks = Queue() + self.results = Queue() + self.threads = [] + + def set_thread_count(self, thread_count): + self.thread_count = thread_count + + def add_task(self): + ''' + This must be implemented in a sub class and this function + must be called at the end of the add_task function in + the sub class. + + The implementation of this function (in this base class) + starts any threads necessary to fill the pool if it is + not already full. + ''' + for i in xrange(self.thread_count - self.running_threads_count()): + t = self.thread_type(self.tasks, self.results) + self.threads.append(t) + t.start() + + def abort(self): + self.tasks = Queue() + self.results = Queue() + for t in self.threads: + t.abort() + self.threads = [] + + def has_tasks(self): + return not self.tasks.empty() + + def get_result(self): + return self.results.get() + + def get_result_no_wait(self): + return self.results.get_nowait() + + def result_count(self): + return len(self.results) + + def has_results(self): + return not self.results.empty() + + def threads_running(self): + return self.running_threads_count() > 0 + + def running_threads_count(self): + count = 0 + for t in self.threads: + if t.is_alive(): + count += 1 + return count + + +class SearchThreadPool(GenericDownloadThreadPool): + ''' + Threads will run until there is no work or + abort is called. Create and start new threads + using start_threads(). Reset by calling abort(). + + Example: + sp = SearchThreadPool(3) + sp.add_task(...) + ''' + + def __init__(self, thread_count): + GenericDownloadThreadPool.__init__(self, SearchThread, thread_count) + + def add_task(self, query, store_name, store_plugin, max_results, timeout): + self.tasks.put((query, store_name, store_plugin, max_results, timeout)) + GenericDownloadThreadPool.add_task(self) + + +class SearchThread(Thread): + + def __init__(self, tasks, results): + Thread.__init__(self) + self.daemon = True + self.tasks = tasks + self.results = results + self._run = True + + def abort(self): + self._run = False + + def run(self): + while self._run and not self.tasks.empty(): + try: + query, store_name, store_plugin, max_results, timeout = self.tasks.get() + for res in store_plugin.search(query, max_results=max_results, timeout=timeout): + if not self._run: + return + res.store_name = store_name + res.affiliate = store_plugin.base_plugin.affiliate + res.plugin_author = store_plugin.base_plugin.author + self.results.put((res, store_plugin)) + self.tasks.task_done() + except: + if DEBUG: + traceback.print_exc() + + +class CoverThreadPool(GenericDownloadThreadPool): + + def __init__(self, thread_count): + GenericDownloadThreadPool.__init__(self, CoverThread, thread_count) + + def add_task(self, search_result, update_callback, timeout=5): + self.tasks.put((search_result, update_callback, timeout)) + GenericDownloadThreadPool.add_task(self) + + +class CoverThread(Thread): + + def __init__(self, tasks, results): + Thread.__init__(self) + self.daemon = True + self.tasks = tasks + self.results = results + self._run = True + + self.br = browser() + + def abort(self): + self._run = False + + def run(self): + while self._run and not self.tasks.empty(): + try: + result, callback, timeout = self.tasks.get() + if result and result.cover_url: + with closing(self.br.open(result.cover_url, timeout=timeout)) as f: + result.cover_data = f.read() + result.cover_data = thumbnail(result.cover_data, 64, 64)[2] + callback() + self.tasks.task_done() + except: + if DEBUG: + traceback.print_exc() + + +class DetailsThreadPool(GenericDownloadThreadPool): + + def __init__(self, thread_count): + GenericDownloadThreadPool.__init__(self, DetailsThread, thread_count) + + def add_task(self, search_result, store_plugin, update_callback, timeout=10): + self.tasks.put((search_result, store_plugin, update_callback, timeout)) + GenericDownloadThreadPool.add_task(self) + + +class DetailsThread(Thread): + + def __init__(self, tasks, results): + Thread.__init__(self) + self.daemon = True + self.tasks = tasks + self.results = results + self._run = True + + def abort(self): + self._run = False + + def run(self): + while self._run and not self.tasks.empty(): + try: + result, store_plugin, callback, timeout = self.tasks.get() + if result: + store_plugin.get_details(result, timeout) + callback(result) + self.tasks.task_done() + except: + if DEBUG: + traceback.print_exc() + + +class CacheUpdateThreadPool(GenericDownloadThreadPool): + + def __init__(self, thread_count): + GenericDownloadThreadPool.__init__(self, CacheUpdateThread, thread_count) + + def add_task(self, store_plugin, timeout=10): + self.tasks.put((store_plugin, timeout)) + GenericDownloadThreadPool.add_task(self) + + +class CacheUpdateThread(Thread): + + def __init__(self, tasks, results): + Thread.__init__(self) + self.daemon = True + self.tasks = tasks + self._run = True + + def abort(self): + self._run = False + + def run(self): + while self._run and not self.tasks.empty(): + try: + store_plugin, timeout = self.tasks.get() + store_plugin.update_cache(timeout=timeout, suppress_progress=True) + except: + if DEBUG: + traceback.print_exc() diff --git a/src/calibre/gui2/store/search/models.py b/src/calibre/gui2/store/search/models.py new file mode 100644 index 0000000000..89c11445b3 --- /dev/null +++ b/src/calibre/gui2/store/search/models.py @@ -0,0 +1,371 @@ +# -*- coding: utf-8 -*- + +from __future__ import (unicode_literals, division, absolute_import, print_function) + +__license__ = 'GPL 3' +__copyright__ = '2011, John Schember <john@nachtimwald.com>' +__docformat__ = 'restructuredtext en' + +import re +from operator import attrgetter + +from PyQt4.Qt import (Qt, QAbstractItemModel, QVariant, QPixmap, QModelIndex, QSize, + pyqtSignal) + +from calibre.gui2 import NONE, FunctionDispatcher +from calibre.gui2.store.search_result import SearchResult +from calibre.gui2.store.search.download_thread import DetailsThreadPool, \ + CoverThreadPool +from calibre.library.caches import _match, CONTAINS_MATCH, EQUALS_MATCH, \ + REGEXP_MATCH +from calibre.utils.icu import sort_key +from calibre.utils.search_query_parser import SearchQueryParser + +def comparable_price(text): + if len(text) < 3 or text[-3] not in ('.', ','): + text += '00' + text = re.sub(r'\D', '', text) + text = text.rjust(6, '0') + return text + + +class Matches(QAbstractItemModel): + + total_changed = pyqtSignal(int) + + HEADERS = [_('Cover'), _('Title'), _('Price'), _('DRM'), _('Store'), _('')] + HTML_COLS = (1, 4) + + def __init__(self, cover_thread_count=2, detail_thread_count=4): + QAbstractItemModel.__init__(self) + + self.DRM_LOCKED_ICON = QPixmap(I('drm-locked.png')).scaledToHeight(64, + Qt.SmoothTransformation) + self.DRM_UNLOCKED_ICON = QPixmap(I('drm-unlocked.png')).scaledToHeight(64, + Qt.SmoothTransformation) + self.DRM_UNKNOWN_ICON = QPixmap(I('dialog_question.png')).scaledToHeight(64, + Qt.SmoothTransformation) + self.DONATE_ICON = QPixmap(I('donate.png')).scaledToHeight(16, + Qt.SmoothTransformation) + + # All matches. Used to determine the order to display + # self.matches because the SearchFilter returns + # matches unordered. + self.all_matches = [] + # Only the showing matches. + self.matches = [] + self.query = '' + self.search_filter = SearchFilter() + self.cover_pool = CoverThreadPool(cover_thread_count) + self.details_pool = DetailsThreadPool(detail_thread_count) + + self.filter_results_dispatcher = FunctionDispatcher(self.filter_results) + self.got_result_details_dispatcher = FunctionDispatcher(self.got_result_details) + + self.sort_col = 2 + self.sort_order = Qt.AscendingOrder + + def closing(self): + self.cover_pool.abort() + self.details_pool.abort() + + def clear_results(self): + self.all_matches = [] + self.matches = [] + self.all_matches = [] + self.search_filter.clear_search_results() + self.query = '' + self.cover_pool.abort() + self.details_pool.abort() + self.total_changed.emit(self.rowCount()) + self.reset() + + def add_result(self, result, store_plugin): + if result not in self.all_matches: + self.layoutAboutToBeChanged.emit() + self.all_matches.append(result) + self.search_filter.add_search_result(result) + if result.cover_url: + result.cover_queued = True + self.cover_pool.add_task(result, self.filter_results_dispatcher) + else: + result.cover_queued = False + self.details_pool.add_task(result, store_plugin, self.got_result_details_dispatcher) + self.filter_results() + self.layoutChanged.emit() + + def get_result(self, index): + row = index.row() + if row < len(self.matches): + return self.matches[row] + else: + return None + + def has_results(self): + return len(self.matches) > 0 + + def filter_results(self): + self.layoutAboutToBeChanged.emit() + if self.query: + self.matches = list(self.search_filter.parse(self.query)) + else: + self.matches = list(self.search_filter.universal_set()) + self.total_changed.emit(self.rowCount()) + self.sort(self.sort_col, self.sort_order, False) + self.layoutChanged.emit() + + def got_result_details(self, result): + if not result.cover_queued and result.cover_url: + result.cover_queued = True + self.cover_pool.add_task(result, self.filter_results_dispatcher) + if result in self.matches: + row = self.matches.index(result) + self.dataChanged.emit(self.index(row, 0), self.index(row, self.columnCount() - 1)) + if result.drm not in (SearchResult.DRM_LOCKED, SearchResult.DRM_UNLOCKED, SearchResult.DRM_UNKNOWN): + result.drm = SearchResult.DRM_UNKNOWN + self.filter_results() + + def set_query(self, query): + self.query = query + + def index(self, row, column, parent=QModelIndex()): + return self.createIndex(row, column) + + def parent(self, index): + if not index.isValid() or index.internalId() == 0: + return QModelIndex() + return self.createIndex(0, 0) + + def rowCount(self, *args): + return len(self.matches) + + def columnCount(self, *args): + return len(self.HEADERS) + + def headerData(self, section, orientation, role): + if role != Qt.DisplayRole: + return NONE + text = '' + if orientation == Qt.Horizontal: + if section < len(self.HEADERS): + text = self.HEADERS[section] + return QVariant(text) + else: + return QVariant(section+1) + + def data(self, index, role): + row, col = index.row(), index.column() + if row >= len(self.matches): + return NONE + result = self.matches[row] + if role == Qt.DisplayRole: + if col == 1: + t = result.title if result.title else _('Unknown') + a = result.author if result.author else '' + return QVariant('<b>%s</b><br><i>%s</i>' % (t, a)) + elif col == 2: + return QVariant(result.price) + elif col == 4: + return QVariant('%s<br>%s' % (result.store_name, result.formats)) + return NONE + elif role == Qt.DecorationRole: + if col == 0 and result.cover_data: + p = QPixmap() + p.loadFromData(result.cover_data) + return QVariant(p) + if col == 3: + if result.drm == SearchResult.DRM_LOCKED: + return QVariant(self.DRM_LOCKED_ICON) + elif result.drm == SearchResult.DRM_UNLOCKED: + return QVariant(self.DRM_UNLOCKED_ICON) + elif result.drm == SearchResult.DRM_UNKNOWN: + return QVariant(self.DRM_UNKNOWN_ICON) + if col == 5: + if result.affiliate: + return QVariant(self.DONATE_ICON) + return NONE + elif role == Qt.ToolTipRole: + if col == 1: + return QVariant('<p>%s</p>' % result.title) + elif col == 2: + return QVariant('<p>' + _('Detected price as: %s. Check with the store before making a purchase to verify this price is correct. This price often does not include promotions the store may be running.') % result.price + '</p>') + elif col == 3: + if result.drm == SearchResult.DRM_LOCKED: + return QVariant('<p>' + _('This book as been detected as having DRM restrictions. This book may not work with your reader and you will have limitations placed upon you as to what you can do with this book. Check with the store before making any purchases to ensure you can actually read this book.') + '</p>') + elif result.drm == SearchResult.DRM_UNLOCKED: + return QVariant('<p>' + _('This book has been detected as being DRM Free. You should be able to use this book on any device provided it is in a format calibre supports for conversion. However, before making a purchase double check the DRM status with the store. The store may not be disclosing the use of DRM.') + '</p>') + else: + return QVariant('<p>' + _('The DRM status of this book could not be determined. There is a very high likelihood that this book is actually DRM restricted.') + '</p>') + elif col == 4: + return QVariant('<p>%s</p>' % result.formats) + elif col == 5: + if result.affiliate: + return QVariant('<p>' + _('Buying from this store supports the calibre developer: %s.') % result.plugin_author + '</p>') + elif role == Qt.SizeHintRole: + return QSize(64, 64) + return NONE + + def data_as_text(self, result, col): + text = '' + if col == 1: + text = result.title + elif col == 2: + text = comparable_price(result.price) + elif col == 3: + if result.drm == SearchResult.DRM_UNLOCKED: + text = 'a' + if result.drm == SearchResult.DRM_LOCKED: + text = 'b' + else: + text = 'c' + elif col == 4: + text = result.store_name + elif col == 5: + if result.affiliate: + text = 'a' + else: + text = 'b' + return text + + def sort(self, col, order, reset=True): + self.sort_col = col + self.sort_order = order + if not self.matches: + return + descending = order == Qt.DescendingOrder + self.all_matches.sort(None, + lambda x: sort_key(unicode(self.data_as_text(x, col))), + descending) + self.reorder_matches() + if reset: + self.reset() + + def reorder_matches(self): + def keygen(x): + try: + return self.all_matches.index(x) + except: + return 100000 + self.matches = sorted(self.matches, key=keygen) + + +class SearchFilter(SearchQueryParser): + + USABLE_LOCATIONS = [ + 'all', + 'affiliate', + 'author', + 'authors', + 'cover', + 'drm', + 'format', + 'formats', + 'price', + 'title', + 'store', + ] + + def __init__(self): + SearchQueryParser.__init__(self, locations=self.USABLE_LOCATIONS) + self.srs = set([]) + + def add_search_result(self, search_result): + self.srs.add(search_result) + + def clear_search_results(self): + self.srs = set([]) + + def universal_set(self): + return self.srs + + def get_matches(self, location, query): + location = location.lower().strip() + if location == 'authors': + location = 'author' + elif location == 'formats': + location = 'format' + + matchkind = CONTAINS_MATCH + if len(query) > 1: + if query.startswith('\\'): + query = query[1:] + elif query.startswith('='): + matchkind = EQUALS_MATCH + query = query[1:] + elif query.startswith('~'): + matchkind = REGEXP_MATCH + query = query[1:] + if matchkind != REGEXP_MATCH: ### leave case in regexps because it can be significant e.g. \S \W \D + query = query.lower() + + if location not in self.USABLE_LOCATIONS: + return set([]) + matches = set([]) + all_locs = set(self.USABLE_LOCATIONS) - set(['all']) + locations = all_locs if location == 'all' else [location] + q = { + 'affiliate': attrgetter('affiliate'), + 'author': lambda x: x.author.lower(), + 'cover': attrgetter('cover_url'), + 'drm': attrgetter('drm'), + 'format': attrgetter('formats'), + 'price': lambda x: comparable_price(x.price), + 'store': lambda x: x.store_name.lower(), + 'title': lambda x: x.title.lower(), + } + for x in ('author', 'format'): + q[x+'s'] = q[x] + for sr in self.srs: + for locvalue in locations: + accessor = q[locvalue] + if query == 'true': + # True/False. + if locvalue == 'affiliate': + if accessor(sr): + matches.add(sr) + # Special that are treated as True/False. + elif locvalue == 'drm': + if accessor(sr) == SearchResult.DRM_LOCKED: + matches.add(sr) + # Testing for something or nothing. + else: + if accessor(sr) is not None: + matches.add(sr) + continue + if query == 'false': + # True/False. + if locvalue == 'affiliate': + if not accessor(sr): + matches.add(sr) + # Special that are treated as True/False. + elif locvalue == 'drm': + if accessor(sr) == SearchResult.DRM_UNLOCKED: + matches.add(sr) + # Testing for something or nothing. + else: + if accessor(sr) is None: + matches.add(sr) + continue + # this is bool or treated as bool, so can't match below. + if locvalue in ('affiliate', 'drm'): + continue + try: + ### Can't separate authors because comma is used for name sep and author sep + ### Exact match might not get what you want. For that reason, turn author + ### exactmatch searches into contains searches. + if locvalue == 'author' and matchkind == EQUALS_MATCH: + m = CONTAINS_MATCH + else: + m = matchkind + + if locvalue == 'format': + vals = accessor(sr).split(',') + else: + vals = [accessor(sr)] + if _match(query, vals, m): + matches.add(sr) + break + except ValueError: # Unicode errors + import traceback + traceback.print_exc() + return matches diff --git a/src/calibre/gui2/store/search/results_view.py b/src/calibre/gui2/store/search/results_view.py new file mode 100644 index 0000000000..91c067006e --- /dev/null +++ b/src/calibre/gui2/store/search/results_view.py @@ -0,0 +1,26 @@ +# -*- coding: utf-8 -*- + +from __future__ import (unicode_literals, division, absolute_import, print_function) + +__license__ = 'GPL 3' +__copyright__ = '2011, John Schember <john@nachtimwald.com>' +__docformat__ = 'restructuredtext en' + +from PyQt4.Qt import (QTreeView) + +from calibre.gui2.metadata.single_download import RichTextDelegate +from calibre.gui2.store.search.models import Matches + +class ResultsView(QTreeView): + + def __init__(self, *args): + QTreeView.__init__(self,*args) + + self._model = Matches() + self.setModel(self._model) + + self.rt_delegate = RichTextDelegate(self) + + for i in self._model.HTML_COLS: + self.setItemDelegateForColumn(i, self.rt_delegate) + diff --git a/src/calibre/gui2/store/search/search.py b/src/calibre/gui2/store/search/search.py new file mode 100644 index 0000000000..e1ad24943d --- /dev/null +++ b/src/calibre/gui2/store/search/search.py @@ -0,0 +1,359 @@ +# -*- coding: utf-8 -*- + +from __future__ import (unicode_literals, division, absolute_import, print_function) + +__license__ = 'GPL 3' +__copyright__ = '2011, John Schember <john@nachtimwald.com>' +__docformat__ = 'restructuredtext en' + +import re +from random import shuffle + +from PyQt4.Qt import (Qt, QDialog, QDialogButtonBox, QTimer, QCheckBox, QLabel, + QVBoxLayout, QIcon, QWidget, QTabWidget, QGridLayout) + +from calibre.gui2 import JSONConfig, info_dialog +from calibre.gui2.progress_indicator import ProgressIndicator +from calibre.gui2.store.config.chooser.chooser_widget import StoreChooserWidget +from calibre.gui2.store.config.search.search_widget import StoreConfigWidget +from calibre.gui2.store.search.adv_search_builder import AdvSearchBuilderDialog +from calibre.gui2.store.search.download_thread import SearchThreadPool, \ + CacheUpdateThreadPool +from calibre.gui2.store.search.search_ui import Ui_Dialog + +class SearchDialog(QDialog, Ui_Dialog): + + def __init__(self, gui, parent=None, query=''): + QDialog.__init__(self, parent) + self.setupUi(self) + + self.config = JSONConfig('store/search') + self.search_edit.initialize('store_search_search') + + # Loads variables that store various settings. + # This needs to be called soon in __init__ because + # the variables it sets up are used later. + self.load_settings() + + self.gui = gui + + # Setup our worker threads. + self.search_pool = SearchThreadPool(self.search_thread_count) + self.cache_pool = CacheUpdateThreadPool(self.cache_thread_count) + self.results_view.model().cover_pool.set_thread_count(self.cover_thread_count) + self.results_view.model().details_pool.set_thread_count(self.details_thread_count) + + # Check for results and hung threads. + self.checker = QTimer() + self.progress_checker = QTimer() + self.hang_check = 0 + + # Update store caches silently. + for p in self.gui.istores.values(): + self.cache_pool.add_task(p, self.timeout) + + self.store_checks = {} + self.setup_store_checks() + + # Set the search query + self.search_edit.setText(query) + + # Create and add the progress indicator + self.pi = ProgressIndicator(self, 24) + self.top_layout.addWidget(self.pi) + + self.adv_search_button.setIcon(QIcon(I('search.png'))) + self.configure.setIcon(QIcon(I('config.png'))) + + self.adv_search_button.clicked.connect(self.build_adv_search) + self.search.clicked.connect(self.do_search) + self.checker.timeout.connect(self.get_results) + self.progress_checker.timeout.connect(self.check_progress) + self.results_view.activated.connect(self.open_store) + self.results_view.model().total_changed.connect(self.update_book_total) + self.select_all_stores.clicked.connect(self.stores_select_all) + self.select_invert_stores.clicked.connect(self.stores_select_invert) + self.select_none_stores.clicked.connect(self.stores_select_none) + self.configure.clicked.connect(self.do_config) + self.finished.connect(self.dialog_closed) + + self.progress_checker.start(100) + + self.restore_state() + + def setup_store_checks(self): + # Add check boxes for each store so the user + # can disable searching specific stores on a + # per search basis. + existing = {} + for n in self.store_checks: + existing[n] = self.store_checks[n].isChecked() + + self.store_checks = {} + + stores_check_widget = QWidget() + store_list_layout = QGridLayout() + stores_check_widget.setLayout(store_list_layout) + + icon = QIcon(I('donate.png')) + for i, x in enumerate(sorted(self.gui.istores.keys(), key=lambda x: x.lower())): + cbox = QCheckBox(x) + cbox.setChecked(existing.get(x, False)) + store_list_layout.addWidget(cbox, i, 0, 1, 1) + if self.gui.istores[x].base_plugin.affiliate: + iw = QLabel(self) + iw.setToolTip('<p>' + _('Buying from this store supports the calibre developer: %s</p>') % self.gui.istores[x].base_plugin.author + '</p>') + iw.setPixmap(icon.pixmap(16, 16)) + store_list_layout.addWidget(iw, i, 1, 1, 1) + self.store_checks[x] = cbox + store_list_layout.setRowStretch(store_list_layout.rowCount(), 10) + self.store_list.setWidget(stores_check_widget) + + def build_adv_search(self): + adv = AdvSearchBuilderDialog(self) + if adv.exec_() == QDialog.Accepted: + self.search_edit.setText(adv.search_string()) + + def resize_columns(self): + total = 600 + # Cover + self.results_view.setColumnWidth(0, 85) + total = total - 85 + # Title / Author + self.results_view.setColumnWidth(1,int(total*.40)) + # Price + self.results_view.setColumnWidth(2,int(total*.20)) + # DRM + self.results_view.setColumnWidth(3, int(total*.15)) + # Store / Formats + self.results_view.setColumnWidth(4, int(total*.25)) + + def do_search(self): + # Stop all running threads. + self.checker.stop() + self.search_pool.abort() + # Clear the visible results. + self.results_view.model().clear_results() + + # Don't start a search if there is nothing to search for. + query = unicode(self.search_edit.text()) + if not query.strip(): + return + # Give the query to the results model so it can do + # futher filtering. + self.results_view.model().set_query(query) + + # Plugins are in random order that does not change. + # Randomize the ord of the plugin names every time + # there is a search. This way plugins closer + # to a don't have an unfair advantage over + # plugins further from a. + store_names = self.store_checks.keys() + if not store_names: + return + # Remove all of our internal filtering logic from the query. + query = self.clean_query(query) + shuffle(store_names) + # Add plugins that the user has checked to the search pool's work queue. + 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) + self.hang_check = 0 + self.checker.start(100) + self.pi.startAnimation() + + def clean_query(self, query): + query = query.lower() + # Remove control modifiers. + query = query.replace('\\', '') + query = query.replace('!', '') + query = query.replace('=', '') + query = query.replace('~', '') + query = query.replace('>', '') + query = query.replace('<', '') + # Remove the prefix. + for loc in ('all', 'author', 'authors', 'title'): + query = re.sub(r'%s:"(?P<a>[^\s"]+)"' % loc, '\g<a>', query) + query = query.replace('%s:' % loc, '') + # Remove the prefix and search text. + for loc in ('cover', 'drm', 'format', 'formats', 'price', 'store'): + query = re.sub(r'%s:"[^"]"' % loc, '', query) + query = re.sub(r'%s:[^\s]*' % loc, '', query) + # Remove logic. + query = re.sub(r'(^|\s)(and|not|or|a|the|is|of)(\s|$)', ' ', query) + # Remove " + query = query.replace('"', '') + # Remove excess whitespace. + query = re.sub(r'\s{2,}', ' ', query) + query = query.strip() + return query + + def save_state(self): + self.config['geometry'] = bytearray(self.saveGeometry()) + self.config['store_splitter_state'] = bytearray(self.store_splitter.saveState()) + self.config['results_view_column_width'] = [self.results_view.columnWidth(i) for i in range(self.results_view.model().columnCount())] + self.config['sort_col'] = self.results_view.model().sort_col + self.config['sort_order'] = self.results_view.model().sort_order + self.config['open_external'] = self.open_external.isChecked() + + store_check = {} + for k, v in self.store_checks.items(): + store_check[k] = v.isChecked() + self.config['store_checked'] = store_check + + def restore_state(self): + geometry = self.config.get('geometry', None) + if geometry: + self.restoreGeometry(geometry) + + splitter_state = self.config.get('store_splitter_state', None) + if splitter_state: + self.store_splitter.restoreState(splitter_state) + + results_cwidth = self.config.get('results_view_column_width', None) + if results_cwidth: + for i, x in enumerate(results_cwidth): + if i >= self.results_view.model().columnCount(): + break + self.results_view.setColumnWidth(i, x) + else: + self.resize_columns() + + self.open_external.setChecked(self.should_open_external) + + store_check = self.config.get('store_checked', None) + if store_check: + for n in store_check: + if n in self.store_checks: + self.store_checks[n].setChecked(store_check[n]) + + self.results_view.model().sort_col = self.config.get('sort_col', 2) + self.results_view.model().sort_order = self.config.get('sort_order', Qt.AscendingOrder) + self.results_view.header().setSortIndicator(self.results_view.model().sort_col, self.results_view.model().sort_order) + + def load_settings(self): + # Seconds + self.timeout = self.config.get('timeout', 75) + # Milliseconds + self.hang_time = self.config.get('hang_time', 75) * 1000 + + self.max_results = self.config.get('max_results', 10) + self.should_open_external = self.config.get('open_external', True) + + # Number of threads to run for each type of operation + self.search_thread_count = self.config.get('search_thread_count', 4) + self.cache_thread_count = self.config.get('cache_thread_count', 2) + self.cover_thread_count = self.config.get('cover_thread_count', 2) + self.details_thread_count = self.config.get('details_thread_count', 4) + + def do_config(self): + # Save values that need to be synced between the dialog and the + # search widget. + self.config['open_external'] = self.open_external.isChecked() + + # Create the config dialog. It's going to put two config widgets + # into a QTabWidget for displaying all of the settings. + d = QDialog(self) + button_box = QDialogButtonBox(QDialogButtonBox.Close) + v = QVBoxLayout(d) + button_box.accepted.connect(d.accept) + button_box.rejected.connect(d.reject) + d.setWindowTitle(_('Customize get books search')) + + tab_widget = QTabWidget(d) + v.addWidget(tab_widget) + v.addWidget(button_box) + + chooser_config_widget = StoreChooserWidget() + search_config_widget = StoreConfigWidget(self.config) + + tab_widget.addTab(chooser_config_widget, _('Choose stores')) + tab_widget.addTab(search_config_widget, _('Configure search')) + + # Restore dialog state. + geometry = self.config.get('config_dialog_geometry', None) + if geometry: + d.restoreGeometry(geometry) + else: + d.resize(800, 600) + tab_index = self.config.get('config_dialog_tab_index', 0) + tab_index = min(tab_index, tab_widget.count() - 1) + tab_widget.setCurrentIndex(tab_index) + + d.exec_() + + # Save dialog state. + self.config['config_dialog_geometry'] = bytearray(d.saveGeometry()) + self.config['config_dialog_tab_index'] = tab_widget.currentIndex() + + search_config_widget.save_settings() + self.config_changed() + self.gui.load_store_plugins() + self.setup_store_checks() + + def config_changed(self): + self.load_settings() + + self.open_external.setChecked(self.should_open_external) + self.search_pool.set_thread_count(self.search_thread_count) + self.cache_pool.set_thread_count(self.cache_thread_count) + self.results_view.model().cover_pool.set_thread_count(self.cover_thread_count) + self.results_view.model().details_pool.set_thread_count(self.details_thread_count) + + def get_results(self): + # We only want the search plugins to run + # a maximum set amount of time before giving up. + self.hang_check += 1 + if self.hang_check >= self.hang_time: + self.search_pool.abort() + self.checker.stop() + else: + # Stop the checker if not threads are running. + if not self.search_pool.threads_running() and not self.search_pool.has_tasks(): + self.checker.stop() + + while self.search_pool.has_results(): + res, store_plugin = self.search_pool.get_result() + if res: + self.results_view.model().add_result(res, store_plugin) + + if not self.search_pool.threads_running() and not self.results_view.model().has_results(): + info_dialog(self, _('No matches'), _('Couldn\'t find any books matching your query.'), show=True, show_copy_button=False) + + def update_book_total(self, total): + self.total.setText('%s' % total) + + def open_store(self, index): + result = self.results_view.model().get_result(index) + self.gui.istores[result.store_name].open(self, result.detail_item, self.open_external.isChecked()) + + def check_progress(self): + if not self.search_pool.threads_running() and not self.results_view.model().cover_pool.threads_running() and not self.results_view.model().details_pool.threads_running(): + self.pi.stopAnimation() + else: + if not self.pi.isAnimated(): + self.pi.startAnimation() + + def stores_select_all(self): + for check in self.store_checks.values(): + check.setChecked(True) + + def stores_select_invert(self): + for check in self.store_checks.values(): + check.setChecked(not check.isChecked()) + + def stores_select_none(self): + for check in self.store_checks.values(): + check.setChecked(False) + + def dialog_closed(self, result): + self.results_view.model().closing() + self.search_pool.abort() + self.cache_pool.abort() + self.save_state() + + def exec_(self): + if unicode(self.search_edit.text()).strip(): + self.do_search() + return QDialog.exec_(self) + diff --git a/src/calibre/gui2/store/search/search.ui b/src/calibre/gui2/store/search/search.ui new file mode 100644 index 0000000000..1451aa09f1 --- /dev/null +++ b/src/calibre/gui2/store/search/search.ui @@ -0,0 +1,278 @@ +<?xml version="1.0" encoding="UTF-8"?> +<ui version="4.0"> + <class>Dialog</class> + <widget class="QDialog" name="Dialog"> + <property name="geometry"> + <rect> + <x>0</x> + <y>0</y> + <width>584</width> + <height>533</height> + </rect> + </property> + <property name="windowTitle"> + <string>Get Books</string> + </property> + <property name="windowIcon"> + <iconset> + <normaloff>:/images/store.png</normaloff>:/images/store.png</iconset> + </property> + <property name="sizeGripEnabled"> + <bool>true</bool> + </property> + <layout class="QVBoxLayout" name="verticalLayout_5"> + <item> + <layout class="QHBoxLayout" name="top_layout"> + <item> + <widget class="QLabel" name="label"> + <property name="text"> + <string>Query:</string> + </property> + </widget> + </item> + <item> + <widget class="QToolButton" name="adv_search_button"> + <property name="text"> + <string>...</string> + </property> + </widget> + </item> + <item> + <widget class="HistoryLineEdit" name="search_edit"> + <property name="sizePolicy"> + <sizepolicy hsizetype="Expanding" vsizetype="Fixed"> + <horstretch>0</horstretch> + <verstretch>0</verstretch> + </sizepolicy> + </property> + </widget> + </item> + <item> + <widget class="QPushButton" name="search"> + <property name="text"> + <string>Search</string> + </property> + </widget> + </item> + </layout> + </item> + <item> + <widget class="QSplitter" name="store_splitter"> + <property name="orientation"> + <enum>Qt::Horizontal</enum> + </property> + <widget class="QGroupBox" name="groupBox"> + <property name="title"> + <string>Stores</string> + </property> + <layout class="QVBoxLayout" name="verticalLayout_2"> + <property name="spacing"> + <number>0</number> + </property> + <property name="margin"> + <number>0</number> + </property> + <item> + <widget class="QScrollArea" name="store_list"> + <property name="widgetResizable"> + <bool>true</bool> + </property> + <widget class="QWidget" name="scrollAreaWidgetContents"> + <property name="geometry"> + <rect> + <x>0</x> + <y>0</y> + <width>125</width> + <height>127</height> + </rect> + </property> + </widget> + </widget> + </item> + <item> + <layout class="QVBoxLayout" name="verticalLayout"> + <property name="spacing"> + <number>0</number> + </property> + <item> + <widget class="QPushButton" name="select_all_stores"> + <property name="text"> + <string>All</string> + </property> + </widget> + </item> + <item> + <widget class="QPushButton" name="select_invert_stores"> + <property name="text"> + <string>Invert</string> + </property> + </widget> + </item> + <item> + <widget class="QPushButton" name="select_none_stores"> + <property name="text"> + <string>None</string> + </property> + </widget> + </item> + </layout> + </item> + </layout> + </widget> + <widget class="QWidget" name="verticalLayoutWidget"> + <layout class="QVBoxLayout" name="verticalLayout_4"> + <item> + <widget class="ResultsView" name="results_view"> + <property name="sizePolicy"> + <sizepolicy hsizetype="Expanding" vsizetype="Expanding"> + <horstretch>1</horstretch> + <verstretch>0</verstretch> + </sizepolicy> + </property> + <property name="minimumSize"> + <size> + <width>0</width> + <height>0</height> + </size> + </property> + <property name="alternatingRowColors"> + <bool>true</bool> + </property> + <property name="iconSize"> + <size> + <width>32</width> + <height>32</height> + </size> + </property> + <property name="rootIsDecorated"> + <bool>false</bool> + </property> + <property name="uniformRowHeights"> + <bool>false</bool> + </property> + <property name="itemsExpandable"> + <bool>false</bool> + </property> + <property name="sortingEnabled"> + <bool>true</bool> + </property> + <property name="expandsOnDoubleClick"> + <bool>false</bool> + </property> + <attribute name="headerStretchLastSection"> + <bool>false</bool> + </attribute> + </widget> + </item> + <item> + <layout class="QHBoxLayout" name="horizontalLayout"> + <item> + <widget class="QToolButton" name="configure"> + <property name="text"> + <string>...</string> + </property> + </widget> + </item> + <item> + <widget class="QCheckBox" name="open_external"> + <property name="toolTip"> + <string>Open a selected book in the system's web browser</string> + </property> + <property name="text"> + <string>Open in &external browser</string> + </property> + </widget> + </item> + <item> + <spacer name="horizontalSpacer_2"> + <property name="orientation"> + <enum>Qt::Horizontal</enum> + </property> + <property name="sizeHint" stdset="0"> + <size> + <width>40</width> + <height>20</height> + </size> + </property> + </spacer> + </item> + </layout> + </item> + </layout> + </widget> + </widget> + </item> + <item> + <layout class="QHBoxLayout" name="bottom_layout"> + <item> + <widget class="QLabel" name="label_2"> + <property name="text"> + <string>Books:</string> + </property> + </widget> + </item> + <item> + <widget class="QLabel" name="total"> + <property name="text"> + <string>0</string> + </property> + </widget> + </item> + <item> + <spacer name="horizontalSpacer"> + <property name="orientation"> + <enum>Qt::Horizontal</enum> + </property> + <property name="sizeHint" stdset="0"> + <size> + <width>40</width> + <height>20</height> + </size> + </property> + </spacer> + </item> + <item> + <widget class="QPushButton" name="close"> + <property name="text"> + <string>Close</string> + </property> + </widget> + </item> + </layout> + </item> + </layout> + </widget> + <customwidgets> + <customwidget> + <class>ResultsView</class> + <extends>QTreeView</extends> + <header>results_view.h</header> + </customwidget> + <customwidget> + <class>HistoryLineEdit</class> + <extends>QLineEdit</extends> + <header>widgets.h</header> + </customwidget> + </customwidgets> + <resources> + <include location="../../../../resources/images.qrc"/> + </resources> + <connections> + <connection> + <sender>close</sender> + <signal>clicked()</signal> + <receiver>Dialog</receiver> + <slot>accept()</slot> + <hints> + <hint type="sourcelabel"> + <x>526</x> + <y>525</y> + </hint> + <hint type="destinationlabel"> + <x>307</x> + <y>272</y> + </hint> + </hints> + </connection> + </connections> +</ui> diff --git a/src/calibre/gui2/store/search_result.py b/src/calibre/gui2/store/search_result.py new file mode 100644 index 0000000000..7d6ac5acad --- /dev/null +++ b/src/calibre/gui2/store/search_result.py @@ -0,0 +1,29 @@ +# -*- coding: utf-8 -*- + +from __future__ import (unicode_literals, division, absolute_import, print_function) + +__license__ = 'GPL 3' +__copyright__ = '2011, John Schember <john@nachtimwald.com>' +__docformat__ = 'restructuredtext en' + +class SearchResult(object): + + DRM_LOCKED = 1 + DRM_UNLOCKED = 2 + DRM_UNKNOWN = 3 + + def __init__(self): + self.store_name = '' + self.cover_url = '' + self.cover_data = None + self.title = '' + self.author = '' + self.price = '' + self.detail_item = '' + self.drm = None + self.formats = '' + self.affiliate = False + self.plugin_author = '' + + def __eq__(self, other): + return self.title == other.title and self.author == other.author and self.store_name == other.store_name diff --git a/src/calibre/gui2/store/smashwords_plugin.py b/src/calibre/gui2/store/smashwords_plugin.py new file mode 100644 index 0000000000..73700ed546 --- /dev/null +++ b/src/calibre/gui2/store/smashwords_plugin.py @@ -0,0 +1,103 @@ +# -*- coding: utf-8 -*- + +from __future__ import (unicode_literals, division, absolute_import, print_function) + +__license__ = 'GPL 3' +__copyright__ = '2011, John Schember <john@nachtimwald.com>' +__docformat__ = 'restructuredtext en' + +import random +import re +import urllib2 +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 SmashwordsStore(BasicStoreConfig, StorePlugin): + + def open(self, parent=None, detail_item=None, external=False): + url = 'http://www.smashwords.com/' + + aff_id = '?ref=usernone' + # Use Kovid's affiliate id 30% of the time. + if random.randint(1, 10) in (1, 2, 3): + aff_id = '?ref=kovidgoyal' + + detail_url = None + 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: + d = WebStoreDialog(self.gui, url, parent, detail_url) + d.setWindowTitle(self.name) + d.set_tags(self.config.get('tags', '')) + d.exec_() + + 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()) + for data in doc.xpath('//div[@id="pageCenterContent2"]//div[@class="bookCoverImg"]'): + if counter <= 0: + break + data = html.fromstring(html.tostring(data)) + + id = None + id_a = data.xpath('//a[@class="bookTitle"]') + if id_a: + id = id_a[0].get('href', None) + if id: + 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()')) + price = subnote.partition('$')[2] + price = price.split(u'\xa0')[0] + price = '$' + 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 = '/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()) + search_result.formats = ', '.join(list(set(idata.xpath('//td//b//text()')))) + return True diff --git a/src/calibre/gui2/store/virtualo_plugin.py b/src/calibre/gui2/store/virtualo_plugin.py new file mode 100644 index 0000000000..c6d6fc70d8 --- /dev/null +++ b/src/calibre/gui2/store/virtualo_plugin.py @@ -0,0 +1,71 @@ +# -*- coding: utf-8 -*- + +from __future__ import (unicode_literals, division, absolute_import, print_function) + +__license__ = 'GPL 3' +__copyright__ = '2011, Tomasz Długosz <tomek3d@gmail.com>' +__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 VirtualoStore(BasicStoreConfig, StorePlugin): + + def open(self, parent=None, detail_item=None, external=False): + url = 'http://virtualo.pl/ebook/c2/' + + 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 = 'http://virtualo.pl/c2/?q=' + urllib.quote(query.encode('utf-8')) + + br = browser() + + counter = max_results + with closing(br.open(url, timeout=timeout)) as f: + doc = html.fromstring(f.read()) + for data in doc.xpath('//div[@id="product_list"]/div/div[@class="column"]'): + if counter <= 0: + break + + id = ''.join(data.xpath('.//table/tr[2]/td[1]/a/@href')) + if not id: + continue + + price = ''.join(data.xpath('.//span[@class="price"]/text() | .//span[@class="price abbr"]/text()')) + cover_url = ''.join(data.xpath('.//table/tr[2]/td[1]/a/img/@src')) + title = ''.join(data.xpath('.//div[@class="title"]/a/text()')) + author = ', '.join(data.xpath('.//div[@class="authors"]/a/text()')) + formats = ', '.join(data.xpath('.//span[@class="format"]/a/text()')) + formats = re.sub(r'(, )?ONLINE(, )?', '', formats) + + counter -= 1 + + s = SearchResult() + s.cover_url = cover_url + s.title = title.strip() + ' ' + formats + s.author = author.strip() + s.price = price + ' zł' + s.detail_item = 'http://virtualo.pl' + id.strip() + s.formats = formats.upper().strip() + s.drm = SearchResult.DRM_UNKNOWN + + yield s diff --git a/src/calibre/gui2/store/waterstones_uk_plugin.py b/src/calibre/gui2/store/waterstones_uk_plugin.py new file mode 100644 index 0000000000..a5065128ba --- /dev/null +++ b/src/calibre/gui2/store/waterstones_uk_plugin.py @@ -0,0 +1,84 @@ +# -*- coding: utf-8 -*- + +from __future__ import (unicode_literals, division, absolute_import, print_function) + +__license__ = 'GPL 3' +__copyright__ = '2011, John Schember <john@nachtimwald.com>' +__docformat__ = 'restructuredtext en' + +import urllib2 +from contextlib import closing + +from lxml import html + +from PyQt4.Qt import QUrl + +from calibre import browser +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 WaterstonesUKStore(BasicStoreConfig, StorePlugin): + + def open(self, parent=None, detail_item=None, external=False): + url = 'http://clkuk.tradedoubler.com/click?p=51196&a=1951604&g=19333484' + url_details = 'http://clkuk.tradedoubler.com/click?p(51196)a(1951604)g(16460516)url({0})' + + if external or self.config.get('open_external', False): + if detail_item: + url = url_details.format(detail_item) + open_url(QUrl(url)) + else: + detail_url = None + if detail_item: + detail_url = url_details.format(detail_item) + d = WebStoreDialog(self.gui, url, parent, detail_url) + d.setWindowTitle(self.name) + d.set_tags(self.config.get('tags', '')) + 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) + + br = browser() + + counter = max_results + with closing(br.open(url, timeout=timeout)) as f: + doc = html.fromstring(f.read()) + for data in doc.xpath('//div[contains(@class, "results-pane")]'): + if counter <= 0: + break + + id = ''.join(data.xpath('./div/div/h2/a/@href')).strip() + if not id: + continue + cover_url = ''.join(data.xpath('.//div[@class="image"]/a/img/@src')) + 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="priceStandard"]/text()')) + drm = data.xpath('boolean(.//td[@headers="productFormat" and contains(., "DRM")])') + pdf = data.xpath('boolean(.//td[@headers="productFormat" and contains(., "PDF")])') + epub = data.xpath('boolean(.//td[@headers="productFormat" and contains(., "EPUB")])') + + counter -= 1 + + s = SearchResult() + s.cover_url = cover_url + s.title = title.strip() + s.author = author.strip() + s.price = price + if drm: + s.drm = SearchResult.DRM_LOCKED + else: + s.drm = SearchResult.DRM_UNKNOWN + s.detail_item = id + formats = [] + if epub: + formats.append('ePub') + if pdf: + formats.append('PDF') + s.formats = ', '.join(formats) + + yield s diff --git a/src/calibre/gui2/store/web_control.py b/src/calibre/gui2/store/web_control.py new file mode 100644 index 0000000000..17b42c5643 --- /dev/null +++ b/src/calibre/gui2/store/web_control.py @@ -0,0 +1,129 @@ +# -*- coding: utf-8 -*- + +from __future__ import (unicode_literals, division, absolute_import, print_function) + +__license__ = 'GPL 3' +__copyright__ = '2011, John Schember <john@nachtimwald.com>' +__docformat__ = 'restructuredtext en' + +import os +from urlparse import urlparse + +from PyQt4.Qt import QNetworkCookieJar, QFileDialog, QNetworkProxy +from PyQt4.QtWebKit import QWebView, QWebPage + +from calibre import USER_AGENT, get_proxies, get_download_filename +from calibre.ebooks import BOOK_EXTENSIONS +from calibre.ptempfile import PersistentTemporaryFile + +class NPWebView(QWebView): + + def __init__(self, *args): + QWebView.__init__(self, *args) + self.gui = None + self.tags = '' + + self.setPage(NPWebPage()) + self.page().networkAccessManager().setCookieJar(QNetworkCookieJar()) + + http_proxy = get_proxies().get('http', None) + if http_proxy: + proxy_parts = urlparse(http_proxy) + proxy = QNetworkProxy() + proxy.setType(QNetworkProxy.HttpProxy) + if proxy_parts.username: + proxy.setUser(proxy_parts.username) + if proxy_parts.password: + proxy.setPassword(proxy_parts.password) + if proxy_parts.hostname: + proxy.setHostName(proxy_parts.hostname) + if proxy_parts.port: + proxy.setPort(proxy_parts.port) + self.page().networkAccessManager().setProxy(proxy) + + self.page().setForwardUnsupportedContent(True) + self.page().unsupportedContent.connect(self.start_download) + self.page().downloadRequested.connect(self.start_download) + self.page().networkAccessManager().sslErrors.connect(self.ignore_ssl_errors) + + def createWindow(self, type): + if type == QWebPage.WebBrowserWindow: + return self + else: + return None + + def set_gui(self, gui): + self.gui = gui + + def set_tags(self, tags): + self.tags = tags + + def start_download(self, request): + if not self.gui: + return + + url = unicode(request.url().toString()) + cf = self.get_cookies() + + filename = get_download_filename(url, cf) + ext = os.path.splitext(filename)[1][1:].lower() + if ext not in BOOK_EXTENSIONS: + if ext == 'acsm': + from calibre.gui2.dialogs.confirm_delete import confirm + if not confirm('<p>' + _('This ebook is a DRMed EPUB file. ' + 'You will be prompted to save this file to your ' + 'computer. Once it is saved, open it with ' + '<a href="http://www.adobe.com/products/digitaleditions/">' + 'Adobe Digital Editions</a> (ADE).<p>ADE, in turn ' + 'will download the actual ebook, which will be a ' + '.epub file. You can add this book to calibre ' + 'using "Add Books" and selecting the file from ' + 'the ADE library folder.'), + 'acsm_download', self): + return + home = os.path.expanduser('~') + name = QFileDialog.getSaveFileName(self, + _('File is not a supported ebook type. Save to disk?'), + os.path.join(home, filename), + '*.*') + if name: + self.gui.download_ebook(url, cf, name, name, False) + else: + self.gui.download_ebook(url, cf, filename, tags=self.tags) + + def ignore_ssl_errors(self, reply, errors): + reply.ignoreSslErrors(errors) + + def get_cookies(self): + ''' + Writes QNetworkCookies to Mozilla cookie .txt file. + + :return: The file path to the cookie file. + ''' + cf = PersistentTemporaryFile(suffix='.txt') + + cf.write('# Netscape HTTP Cookie File\n\n') + + for c in self.page().networkAccessManager().cookieJar().allCookies(): + cookie = [] + domain = unicode(c.domain()) + + cookie.append(domain) + cookie.append('TRUE' if domain.startswith('.') else 'FALSE') + cookie.append(unicode(c.path())) + cookie.append('TRUE' if c.isSecure() else 'FALSE') + cookie.append(unicode(c.expirationDate().toTime_t())) + cookie.append(unicode(c.name())) + cookie.append(unicode(c.value())) + + cf.write('\t'.join(cookie)) + cf.write('\n') + + cf.close() + return cf.name + + +class NPWebPage(QWebPage): + + def userAgentForUrl(self, url): + return USER_AGENT diff --git a/src/calibre/gui2/store/web_store_dialog.py b/src/calibre/gui2/store/web_store_dialog.py new file mode 100644 index 0000000000..20fb016b6b --- /dev/null +++ b/src/calibre/gui2/store/web_store_dialog.py @@ -0,0 +1,55 @@ +# -*- coding: utf-8 -*- + +from __future__ import (unicode_literals, division, absolute_import, print_function) + +__license__ = 'GPL 3' +__copyright__ = '2011, John Schember <john@nachtimwald.com>' +__docformat__ = 'restructuredtext en' + +from PyQt4.Qt import QDialog, QUrl + +from calibre import url_slash_cleaner +from calibre.gui2.store.web_store_dialog_ui import Ui_Dialog + +class WebStoreDialog(QDialog, Ui_Dialog): + + def __init__(self, gui, base_url, parent=None, detail_url=None): + QDialog.__init__(self, parent=parent) + self.setupUi(self) + + self.gui = gui + self.base_url = base_url + + self.view.set_gui(self.gui) + self.view.loadStarted.connect(self.load_started) + self.view.loadProgress.connect(self.load_progress) + self.view.loadFinished.connect(self.load_finished) + self.home.clicked.connect(self.go_home) + self.reload.clicked.connect(self.view.reload) + self.back.clicked.connect(self.view.back) + + self.go_home(detail_url=detail_url) + + def set_tags(self, tags): + self.view.set_tags(tags) + + def load_started(self): + self.progress.setValue(0) + + def load_progress(self, val): + self.progress.setValue(val) + + def load_finished(self, ok=True): + self.progress.setValue(100) + + def go_home(self, checked=False, detail_url=None): + if detail_url: + url = detail_url + else: + url = self.base_url + + # Reduce redundant /'s because some stores + # (Feedbooks) and server frameworks (cherrypy) + # choke on them. + url = url_slash_cleaner(url) + self.view.load(QUrl(url)) diff --git a/src/calibre/gui2/store/web_store_dialog.ui b/src/calibre/gui2/store/web_store_dialog.ui new file mode 100644 index 0000000000..b89b9305be --- /dev/null +++ b/src/calibre/gui2/store/web_store_dialog.ui @@ -0,0 +1,115 @@ +<?xml version="1.0" encoding="UTF-8"?> +<ui version="4.0"> + <class>Dialog</class> + <widget class="QDialog" name="Dialog"> + <property name="geometry"> + <rect> + <x>0</x> + <y>0</y> + <width>962</width> + <height>656</height> + </rect> + </property> + <property name="windowTitle"> + <string/> + </property> + <property name="sizeGripEnabled"> + <bool>true</bool> + </property> + <layout class="QGridLayout" name="gridLayout"> + <item row="0" column="0" colspan="5"> + <widget class="QFrame" name="frame"> + <property name="frameShape"> + <enum>QFrame::StyledPanel</enum> + </property> + <property name="frameShadow"> + <enum>QFrame::Raised</enum> + </property> + <layout class="QVBoxLayout" name="verticalLayout"> + <property name="margin"> + <number>0</number> + </property> + <item> + <widget class="NPWebView" name="view"> + <property name="url"> + <url> + <string>about:blank</string> + </url> + </property> + </widget> + </item> + </layout> + </widget> + </item> + <item row="1" column="0"> + <widget class="QPushButton" name="home"> + <property name="text"> + <string>Home</string> + </property> + </widget> + </item> + <item row="1" column="1"> + <widget class="QPushButton" name="reload"> + <property name="text"> + <string>Reload</string> + </property> + </widget> + </item> + <item row="1" column="3"> + <widget class="QProgressBar" name="progress"> + <property name="value"> + <number>0</number> + </property> + <property name="format"> + <string>%p%</string> + </property> + </widget> + </item> + <item row="1" column="2"> + <widget class="QPushButton" name="back"> + <property name="text"> + <string>Back</string> + </property> + </widget> + </item> + <item row="1" column="4"> + <widget class="QPushButton" name="close"> + <property name="text"> + <string>Close</string> + </property> + </widget> + </item> + </layout> + </widget> + <customwidgets> + <customwidget> + <class>QWebView</class> + <extends>QWidget</extends> + <header>QtWebKit/QWebView</header> + </customwidget> + <customwidget> + <class>NPWebView</class> + <extends>QWebView</extends> + <header>web_control.h</header> + </customwidget> + </customwidgets> + <resources/> + <connections> + <connection> + <sender>close</sender> + <signal>clicked()</signal> + <receiver>Dialog</receiver> + <slot>accept()</slot> + <hints> + <hint type="sourcelabel"> + <x>917</x> + <y>635</y> + </hint> + <hint type="destinationlabel"> + <x>480</x> + <y>327</y> + </hint> + </hints> + </connection> + </connections> +</ui> diff --git a/src/calibre/gui2/store/weightless_books_plugin.py b/src/calibre/gui2/store/weightless_books_plugin.py new file mode 100644 index 0000000000..3fa1c76851 --- /dev/null +++ b/src/calibre/gui2/store/weightless_books_plugin.py @@ -0,0 +1,76 @@ +# -*- coding: utf-8 -*- + +from __future__ import (unicode_literals, division, absolute_import, print_function) + +__license__ = 'GPL 3' +__copyright__ = '2011, John Schember <john@nachtimwald.com>' +__docformat__ = 'restructuredtext en' + +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 WeightlessBooksStore(BasicStoreConfig, StorePlugin): + + def open(self, parent=None, detail_item=None, external=False): + url = 'http://weightlessbooks.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 = '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()) + for data in doc.xpath('//li[@id="product"]'): + if counter <= 0: + break + + id = ''.join(data.xpath('.//div[@class="cover"]/a/@href')) + if not id: + 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() + s.author = author.strip() + s.price = price.strip() + s.detail_item = id.strip() + s.drm = SearchResult.DRM_UNLOCKED + s.formats = formats + + yield s diff --git a/src/calibre/gui2/store/whsmith_uk_plugin.py b/src/calibre/gui2/store/whsmith_uk_plugin.py new file mode 100644 index 0000000000..66d81258f7 --- /dev/null +++ b/src/calibre/gui2/store/whsmith_uk_plugin.py @@ -0,0 +1,83 @@ +# -*- coding: utf-8 -*- + +from __future__ import (unicode_literals, division, absolute_import, print_function) + +__license__ = 'GPL 3' +__copyright__ = '2011, John Schember <john@nachtimwald.com>' +__docformat__ = 'restructuredtext en' + +import urllib2 +from contextlib import closing + +from lxml import html + +from PyQt4.Qt import QUrl + +from calibre import browser +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 WHSmithUKStore(BasicStoreConfig, StorePlugin): + + def open(self, parent=None, detail_item=None, external=False): + url = 'http://www.whsmith.co.uk/' + url_details = '' + + if external or self.config.get('open_external', False): + if detail_item: + url = url_details + detail_item + open_url(QUrl(url)) + else: + detail_url = None + if detail_item: + detail_url = url_details + detail_item + d = WebStoreDialog(self.gui, url, parent, detail_url) + d.setWindowTitle(self.name) + d.set_tags(self.config.get('tags', '')) + d.exec_() + + def search(self, query, max_results=10, timeout=60): + url = ('http://www.whsmith.co.uk/CatalogAndSearch/SearchWithinCategory.aspx' + '?cat=\Books\eb_eBooks&gq=' + 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('//div[@class="product-search"]/' + 'div[contains(@id, "whsSearchResultItem")]'): + if counter <= 0: + break + + id = ''.join(data.xpath('.//a[contains(@id, "labelProductTitle")]/@href')) + if not id: + continue + cover_url = ''.join(data.xpath('.//a[contains(@id, "hlinkProductImage")]/img/@src')) + title = ''.join(data.xpath('.//a[contains(@id, "labelProductTitle")]/text()')) + author = ', '.join(data.xpath('.//div[@class="author"]/h3/span/text()')) + price = ''.join(data.xpath('.//span[contains(@id, "labelProductPrice")]/text()')) + pdf = data.xpath('boolean(.//span[contains(@id, "labelFormatText") and ' + 'contains(., "PDF")])') + epub = data.xpath('boolean(.//span[contains(@id, "labelFormatText") and ' + 'contains(., "ePub")])') + counter -= 1 + + s = SearchResult() + s.cover_url = cover_url + s.title = title.strip() + s.author = author.strip() + s.price = price + s.drm = SearchResult.DRM_LOCKED + s.detail_item = id + formats = [] + if epub: + formats.append('ePub') + if pdf: + formats.append('PDF') + s.formats = ', '.join(formats) + + yield s diff --git a/src/calibre/gui2/store/wizards_tower_books_plugin.py b/src/calibre/gui2/store/wizards_tower_books_plugin.py new file mode 100644 index 0000000000..90966fc06a --- /dev/null +++ b/src/calibre/gui2/store/wizards_tower_books_plugin.py @@ -0,0 +1,118 @@ +# -*- coding: utf-8 -*- + +from __future__ import (unicode_literals, division, absolute_import, print_function) + +__license__ = 'GPL 3' +__copyright__ = '2011, John Schember <john@nachtimwald.com>' +__docformat__ = 'restructuredtext en' + +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 WizardsTowerBooksStore(BasicStoreConfig, StorePlugin): + + url = 'http://www.wizardstowerbooks.com/' + + def open(self, parent=None, detail_item=None, external=False): + if detail_item: + detail_item = self.url + detail_item + + if external or self.config.get('open_external', False): + open_url(QUrl(url_slash_cleaner(detail_item))) + else: + d = WebStoreDialog(self.gui, self.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 = 'http://www.wizardstowerbooks.com/search.html?for=' + urllib.quote(query) + + br = browser() + + counter = max_results + with closing(br.open(url, timeout=timeout)) as f: + doc = html.fromstring(f.read()) + if 'search.html' in f.geturl(): + for data in doc.xpath('//table[@class="gridp"]//td'): + if counter <= 0: + break + + id = ''.join(data.xpath('.//span[@class="prti"]/a/@href')) + id = id.strip() + if not id: + continue + + cover_url = ''.join(data.xpath('.//div[@class="prim"]/a/img/@src')) + cover_url = url_slash_cleaner(self.url + cover_url.strip()) + + price = ''.join(data.xpath('.//font[@class="selling_price"]//text()')) + price = price.strip() + if not price: + continue + + title = ''.join(data.xpath('.//span[@class="prti"]/a/b/text()')) + author = ''.join(data.xpath('.//p[@class="last"]/text()')) + a, b, author = author.partition(' by ') + + counter -= 1 + + s = SearchResult() + s.cover_url = cover_url + s.title = title.strip() + s.author = author.strip() + s.price = price.strip() + s.detail_item = id.strip() + s.drm = SearchResult.DRM_UNLOCKED + + yield s + # Exact match brought us to the books detail page. + else: + s = SearchResult() + + cover_url = ''.join(doc.xpath('//div[@id="image"]/a/img[@title="Zoom"]/@src')).strip() + s.cover_url = url_slash_cleaner(self.url + cover_url.strip()) + + s.title = ''.join(doc.xpath('//form[@name="details"]/h1/text()')).strip() + + authors = doc.xpath('//p[contains(., "Author:")]//text()') + author_index = None + for i, a in enumerate(authors): + if 'author' in a.lower(): + author_index = i + 1 + break + if author_index is not None and len(authors) > author_index: + a = authors[author_index] + a = a.replace(u'\xa0', '') + s.author = a.strip() + + s.price = ''.join(doc.xpath('//span[@id="price_selling"]//text()')).strip() + s.detail_item = f.geturl().replace(self.url, '').strip() + s.formats = ', '.join(doc.xpath('//select[@id="N1_"]//option//text()')) + s.drm = SearchResult.DRM_UNLOCKED + + yield s + + def get_details(self, search_result, timeout): + if search_result.formats: + return False + + br = browser() + with closing(br.open(url_slash_cleaner(self.url + search_result.detail_item), timeout=timeout)) as nf: + idata = html.fromstring(nf.read()) + + formats = ', '.join(idata.xpath('//select[@id="N1_"]//option//text()')) + search_result.formats = formats.upper() + + return True diff --git a/src/calibre/gui2/store/woblink_plugin.py b/src/calibre/gui2/store/woblink_plugin.py new file mode 100644 index 0000000000..69be8f2e94 --- /dev/null +++ b/src/calibre/gui2/store/woblink_plugin.py @@ -0,0 +1,76 @@ +# -*- coding: utf-8 -*- + +from __future__ import (unicode_literals, division, absolute_import, print_function) + +__license__ = 'GPL 3' +__copyright__ = '2011, Tomasz Długosz <tomek3d@gmail.com>' +__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 WoblinkStore(BasicStoreConfig, StorePlugin): + + def open(self, parent=None, detail_item=None, external=False): + + url = 'http://woblink.com/publication' + detail_url = None + + if detail_item: + detail_url = 'http://woblink.com' + detail_item + + 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_() + + def search(self, query, max_results=10, timeout=60): + url = 'http://woblink.com/publication?query=' + urllib.quote_plus(query.encode('utf-8')) + + br = browser() + + counter = max_results + with closing(br.open(url, timeout=timeout)) as f: + doc = html.fromstring(f.read()) + for data in doc.xpath('//div[@class="book-item"]'): + if counter <= 0: + break + + id = ''.join(data.xpath('.//td[@class="w10 va-t"]/a[1]/@href')) + if not id: + continue + + cover_url = ''.join(data.xpath('.//td[@class="w10 va-t"]/a[1]/img/@src')) + title = ''.join(data.xpath('.//h3[@class="title"]/a[1]/text()')) + author = ''.join(data.xpath('.//p[@class="author"]/a[1]/text()')) + price = ''.join(data.xpath('.//div[@class="prices"]/p[1]/span/text()')) + price = re.sub('PLN', ' zł', price) + price = re.sub('\.', ',', price) + + counter -= 1 + + s = SearchResult() + s.cover_url = 'http://woblink.com' + cover_url + s.title = title.strip() + s.author = author.strip() + s.price = price + s.detail_item = id.strip() + s.drm = SearchResult.DRM_LOCKED + s.formats = 'EPUB' + + yield s diff --git a/src/calibre/gui2/tag_view.py b/src/calibre/gui2/tag_view.py index 2693ba8ed6..da5029bab3 100644 --- a/src/calibre/gui2/tag_view.py +++ b/src/calibre/gui2/tag_view.py @@ -12,15 +12,16 @@ import traceback, copy, cPickle from itertools import izip, repeat from functools import partial -from PyQt4.Qt import Qt, QTreeView, QApplication, pyqtSignal, QFont, QSize, \ - QIcon, QPoint, QVBoxLayout, QHBoxLayout, QComboBox, QTimer,\ - QAbstractItemModel, QVariant, QModelIndex, QMenu, QFrame,\ - QPushButton, QWidget, QItemDelegate, QString, QLabel, \ - QShortcut, QKeySequence, SIGNAL, QMimeData +from PyQt4.Qt import (Qt, QTreeView, QApplication, pyqtSignal, QFont, QSize, + QIcon, QPoint, QVBoxLayout, QHBoxLayout, QComboBox, QTimer, + QAbstractItemModel, QVariant, QModelIndex, QMenu, QFrame, + QWidget, QItemDelegate, QString, QLabel, QPushButton, + QShortcut, QKeySequence, SIGNAL, QMimeData, QToolButton) from calibre.ebooks.metadata import title_sort from calibre.gui2 import config, NONE, gprefs from calibre.library.field_metadata import TagsIcons, category_icon_map +from calibre.library.database2 import Tag from calibre.utils.config import tweaks from calibre.utils.icu import sort_key, lower, strcmp from calibre.utils.search_query_parser import saved_searches @@ -32,9 +33,6 @@ from calibre.gui2.dialogs.tag_list_editor import TagListEditor from calibre.gui2.dialogs.edit_authors_dialog import EditAuthorsDialog from calibre.gui2.widgets import HistoryLineEdit -def original_name(t): - return getattr(t, 'original_name', t.name) - class TagDelegate(QItemDelegate): # {{{ def paint(self, painter, option, index): @@ -69,7 +67,8 @@ class TagDelegate(QItemDelegate): # {{{ # }}} -TAG_SEARCH_STATES = {'clear': 0, 'mark_plus': 1, 'mark_minus': 2} +TAG_SEARCH_STATES = {'clear': 0, 'mark_plus': 1, 'mark_plusplus': 2, + 'mark_minus': 3, 'mark_minusminus': 4} class TagsView(QTreeView): # {{{ @@ -82,10 +81,12 @@ class TagsView(QTreeView): # {{{ add_subcategory = pyqtSignal(object) tag_list_edit = pyqtSignal(object, object) saved_search_edit = pyqtSignal(object) + rebuild_saved_searches = pyqtSignal() author_sort_edit = pyqtSignal(object, object) tag_item_renamed = pyqtSignal() search_item_renamed = pyqtSignal() drag_drop_finished = pyqtSignal(object) + restriction_error = pyqtSignal() def __init__(self, parent=None): QTreeView.__init__(self, parent=None) @@ -112,6 +113,8 @@ class TagsView(QTreeView): # {{{ self.collapse_model = gprefs['tags_browser_partition_method'] self.search_icon = QIcon(I('search.png')) self.user_category_icon = QIcon(I('tb_folder.png')) + self.delete_icon = QIcon(I('list_remove.png')) + self.rename_icon = QIcon(I('edit-undo.png')) def set_pane_is_visible(self, to_what): pv = self.pane_is_visible @@ -127,13 +130,17 @@ class TagsView(QTreeView): # {{{ self.set_new_model(self._model.get_filter_categories_by()) def set_database(self, db, tag_match, sort_by): - self.hidden_categories = db.prefs.get('tag_browser_hidden_categories', None) + hidden_cats = db.prefs.get('tag_browser_hidden_categories', None) + self.hidden_categories = [] # migrate from config to db prefs - if self.hidden_categories is None: - self.hidden_categories = config['tag_browser_hidden_categories'] - db.prefs.set('tag_browser_hidden_categories', list(self.hidden_categories)) - else: - self.hidden_categories = set(self.hidden_categories) + if hidden_cats is None: + hidden_cats = config['tag_browser_hidden_categories'] + # strip out any non-existence field keys + for cat in hidden_cats: + if cat in db.field_metadata: + self.hidden_categories.append(cat) + db.prefs.set('tag_browser_hidden_categories', list(self.hidden_categories)) + self.hidden_categories = set(self.hidden_categories) old = getattr(self, '_model', None) if old is not None: @@ -152,11 +159,17 @@ class TagsView(QTreeView): # {{{ self.setContextMenuPolicy(Qt.CustomContextMenu) pop = config['sort_tags_by'] self.sort_by.setCurrentIndex(self.db.CATEGORY_SORTS.index(pop)) + try: + match_pop = self.db.MATCH_TYPE.index(config['match_tags_type']) + except ValueError: + match_pop = 0 + self.tag_match.setCurrentIndex(match_pop) if not self.made_connections: self.clicked.connect(self.toggle) self.customContextMenuRequested.connect(self.show_context_menu) self.refresh_required.connect(self.recount, type=Qt.QueuedConnection) self.sort_by.currentIndexChanged.connect(self.sort_changed) + self.tag_match.currentIndexChanged.connect(self.match_changed) self.made_connections = True self.refresh_signal_processed = True db.add_listener(self.database_changed) @@ -174,6 +187,12 @@ class TagsView(QTreeView): # {{{ config.set('sort_tags_by', self.db.CATEGORY_SORTS[pop]) self.recount() + def match_changed(self, pop): + try: + config.set('match_tags_type', self.db.MATCH_TYPE[pop]) + except: + pass + def set_search_restriction(self, s): if s: self.search_restriction = s @@ -234,27 +253,31 @@ class TagsView(QTreeView): # {{{ tag = index.tag if len(index.children) > 0: for c in index.children: - self.add_item_to_user_cat.emit(category, original_name(c.tag), + self.add_item_to_user_cat.emit(category, c.tag.original_name, c.tag.category) - self.add_item_to_user_cat.emit(category, original_name(tag), + self.add_item_to_user_cat.emit(category, tag.original_name, tag.category) return if action == 'add_subcategory': self.add_subcategory.emit(key) return if action == 'search_category': - self.tags_marked.emit(key + ':' + search_state) + self._toggle(index, set_to=search_state) return if action == 'delete_user_category': self.delete_user_category.emit(key) return + if action == 'delete_search': + saved_searches().delete(key) + self.rebuild_saved_searches.emit() + return if action == 'delete_item_from_user_category': tag = index.tag if len(index.children) > 0: for c in index.children: - self.del_item_from_user_cat.emit(key, original_name(c.tag), + self.del_item_from_user_cat.emit(key, c.tag.original_name, c.tag.category) - self.del_item_from_user_cat.emit(key, original_name(tag), tag.category) + self.del_item_from_user_cat.emit(key, tag.original_name, tag.category) return if action == 'manage_searches': self.saved_search_edit.emit(category) @@ -281,6 +304,14 @@ class TagsView(QTreeView): # {{{ return def show_context_menu(self, point): + def display_name( tag): + if tag.category == 'search': + n = tag.name + if len(n) > 45: + n = n[:45] + '...' + return "'" + n + "'" + return tag.name + index = self.indexAt(point) self.context_menu = QMenu(self) @@ -310,15 +341,19 @@ class TagsView(QTreeView): # {{{ # the possibility of renaming that item. if tag.is_editable: # Add the 'rename' items - self.context_menu.addAction(_('Rename %s')%tag.name, + self.context_menu.addAction(self.rename_icon, + _('Rename %s')%display_name(tag), partial(self.context_menu_handler, action='edit_item', index=index)) if key == 'authors': - self.context_menu.addAction(_('Edit sort for %s')%tag.name, + self.context_menu.addAction(_('Edit sort for %s')%display_name(tag), partial(self.context_menu_handler, action='edit_author_sort', index=tag.id)) + + # is_editable is also overloaded to mean 'can be added + # to a user category' m = self.context_menu.addMenu(self.user_category_icon, - _('Add %s to user category')%tag.name) + _('Add %s to user category')%display_name(tag)) nt = self.model().category_node_tree def add_node_tree(tree_dict, m, path): p = path[:] @@ -335,28 +370,37 @@ class TagsView(QTreeView): # {{{ add_node_tree(tree_dict[k], tm, p) p.pop() add_node_tree(nt, m, []) - + elif key == 'search': + self.context_menu.addAction(self.rename_icon, + _('Rename %s')%display_name(tag), + partial(self.context_menu_handler, action='edit_item', + index=index)) + self.context_menu.addAction(self.delete_icon, + _('Delete search %s')%display_name(tag), + partial(self.context_menu_handler, + action='delete_search', key=tag.name)) if key.startswith('@') and not item.is_gst: self.context_menu.addAction(self.user_category_icon, - _('Remove %s from category %s')%(tag.name, item.py_name), + _('Remove %s from category %s')% + (display_name(tag), item.py_name), partial(self.context_menu_handler, action='delete_item_from_user_category', key = key, index = tag_item)) - # Add the search for value items + # Add the search for value items. All leaf nodes are searchable self.context_menu.addAction(self.search_icon, - _('Search for %s')%tag.name, + _('Search for %s')%display_name(tag), partial(self.context_menu_handler, action='search', search_state=TAG_SEARCH_STATES['mark_plus'], index=index)) self.context_menu.addAction(self.search_icon, - _('Search for everything but %s')%tag.name, + _('Search for everything but %s')%display_name(tag), partial(self.context_menu_handler, action='search', search_state=TAG_SEARCH_STATES['mark_minus'], index=index)) self.context_menu.addSeparator() elif key.startswith('@') and not item.is_gst: if item.can_be_edited: - self.context_menu.addAction(self.user_category_icon, + self.context_menu.addAction(self.rename_icon, _('Rename %s')%item.py_name, partial(self.context_menu_handler, action='edit_item', index=index)) @@ -364,39 +408,44 @@ class TagsView(QTreeView): # {{{ _('Add sub-category to %s')%item.py_name, partial(self.context_menu_handler, action='add_subcategory', key=key)) - self.context_menu.addAction(self.user_category_icon, + self.context_menu.addAction(self.delete_icon, _('Delete user category %s')%item.py_name, partial(self.context_menu_handler, action='delete_user_category', key=key)) self.context_menu.addSeparator() # Hide/Show/Restore categories - if not key.startswith('@') or key.find('.') < 0: - self.context_menu.addAction(_('Hide category %s') % category, - partial(self.context_menu_handler, action='hide', - category=category)) + self.context_menu.addAction(_('Hide category %s') % category, + partial(self.context_menu_handler, action='hide', + category=key)) if self.hidden_categories: m = self.context_menu.addMenu(_('Show category')) - for col in sorted(self.hidden_categories, key=sort_key): - m.addAction(col, + for col in sorted(self.hidden_categories, + key=lambda x: sort_key(self.db.field_metadata[x]['name'])): + m.addAction(self.db.field_metadata[col]['name'], partial(self.context_menu_handler, action='show', category=col)) - # search by category - if key != 'search': + # search by category. Some categories are not searchable, such + # as search and news + if item.tag.is_searchable: self.context_menu.addAction(self.search_icon, _('Search for books in category %s')%category, - partial(self.context_menu_handler, action='search_category', - key=key, search_state='true')) + partial(self.context_menu_handler, + action='search_category', + index=self._model.createIndex(item.row(), 0, item), + search_state=TAG_SEARCH_STATES['mark_plus'])) self.context_menu.addAction(self.search_icon, _('Search for books not in category %s')%category, - partial(self.context_menu_handler, action='search_category', - key=key, search_state='false')) + partial(self.context_menu_handler, + action='search_category', + index=self._model.createIndex(item.row(), 0, item), + search_state=TAG_SEARCH_STATES['mark_minus'])) # Offer specific editors for tags/series/publishers/saved searches self.context_menu.addSeparator() if key in ['tags', 'publisher', 'series'] or \ self.db.field_metadata[key]['is_custom']: self.context_menu.addAction(_('Manage %s')%category, partial(self.context_menu_handler, action='open_editor', - category=original_name(tag) if tag else None, + category=tag.original_name if tag else None, key=key)) elif key == 'authors': self.context_menu.addAction(_('Manage %s')%category, @@ -474,9 +523,11 @@ class TagsView(QTreeView): # {{{ if hasattr(md, 'column_name'): fm_src = self.db.metadata_for_field(md.column_name) if md.column_name in ['authors', 'publisher', 'series'] or \ - (fm_src['is_custom'] and - fm_src['datatype'] in ['series', 'text'] and - not fm_src['is_multiple']): + (fm_src['is_custom'] and ( + (fm_src['datatype'] in ['series', 'text', 'enumeration'] and + not fm_src['is_multiple']) or + (fm_src['datatype'] == 'composite' and + fm_src['display'].get('make_category', False)))): self.setDropIndicatorShown(True) def clear(self): @@ -522,7 +573,9 @@ class TagsView(QTreeView): # {{{ self.setModel(self._model) except: # The DB must be gone. Set the model to None and hope that someone - # will call set_database later. I don't know if this in fact works + # will call set_database later. I don't know if this in fact works. + # But perhaps a Bad Thing Happened, so print the exception + traceback.print_exc() self._model = None self.setModel(None) # }}} @@ -540,6 +593,7 @@ class TagTreeItem(object): # {{{ self.id_set = set() self.is_gst = False self.boxed = False + self.icon_state_map = list(map(QVariant, icon_map)) if self.parent is not None: self.parent.append(self) if data is None: @@ -554,14 +608,27 @@ class TagTreeItem(object): # {{{ self.bold_font = QVariant(self.bold_font) self.category_key = category_key self.temporary = temporary + self.tag = Tag(data, category=category_key, + is_editable=category_key not in ['news', 'search', 'identifiers'], + is_searchable=category_key not in ['news', 'search']) + elif self.type == self.TAG: - icon_map[0] = data.icon - self.tag, self.icon_state_map = data, list(map(QVariant, icon_map)) + self.icon_state_map[0] = QVariant(data.icon) + self.tag = data if tooltip: self.tooltip = tooltip + ' ' else: self.tooltip = '' + def break_cycles(self): + for x in self.children: + try: + x.break_cycles() + except: + pass + self.parent = self.icon_state_map = self.bold_font = self.tag = \ + self.icon = self.children = None + def __str__(self): if self.type == self.ROOT: return 'ROOT' @@ -593,6 +660,8 @@ class TagTreeItem(object): # {{{ if role == Qt.EditRole: return QVariant(self.py_name) if role == Qt.DecorationRole: + if self.tag.state: + return self.icon_state_map[self.tag.state] return self.icon if role == Qt.FontRole: return self.bold_font @@ -602,8 +671,7 @@ class TagTreeItem(object): # {{{ def tag_data(self, role): tag = self.tag - if tag.category == 'authors' and \ - tweaks['categories_use_field_for_author_name'] == 'author_sort': + if tag.use_sort_as_name: name = tag.sort tt_author = True else: @@ -611,7 +679,7 @@ class TagTreeItem(object): # {{{ while p.parent.type != self.ROOT: p = p.parent if not tag.is_hierarchical: - name = original_name(tag) + name = tag.original_name else: name = tag.name tt_author = False @@ -623,7 +691,7 @@ class TagTreeItem(object): # {{{ else: return QVariant('[%d] %s'%(count, name)) if role == Qt.EditRole: - return QVariant(original_name(tag)) + return QVariant(tag.original_name) if role == Qt.DecorationRole: return self.icon_state_map[tag.state] if role == Qt.ToolTipRole: @@ -642,11 +710,22 @@ class TagTreeItem(object): # {{{ ''' set_to: None => advance the state, otherwise a value from TAG_SEARCH_STATES ''' - if self.type == self.TAG: - if set_to is None: - self.tag.state = (self.tag.state + 1)%3 - else: - self.tag.state = set_to + if set_to is None: + while True: + self.tag.state = (self.tag.state + 1)%5 + if self.tag.state == TAG_SEARCH_STATES['mark_plus'] or \ + self.tag.state == TAG_SEARCH_STATES['mark_minus']: + if self.tag.is_searchable: + break + elif self.tag.state == TAG_SEARCH_STATES['mark_plusplus'] or\ + self.tag.state == TAG_SEARCH_STATES['mark_minusminus']: + if self.tag.is_searchable and len(self.children) and \ + self.tag.is_hierarchical == '5state': + break + else: + break + else: + self.tag.state = set_to def child_tags(self): res = [] @@ -677,7 +756,8 @@ class TagsModel(QAbstractItemModel): # {{{ self.categories_with_ratings = ['authors', 'series', 'publisher', 'tags'] self.drag_drop_finished = drag_drop_finished - self.icon_state_map = [None, QIcon(I('plus.png')), QIcon(I('minus.png'))] + self.icon_state_map = [None, QIcon(I('plus.png')), QIcon(I('plusplus.png')), + QIcon(I('minus.png')), QIcon(I('minusminus.png'))] self.db = db self.tags_view = parent self.hidden_categories = hidden_categories @@ -691,26 +771,33 @@ class TagsModel(QAbstractItemModel): # {{{ data = self.get_node_tree(config['sort_tags_by']) gst = db.prefs.get('grouped_search_terms', {}) - self.root_item = TagTreeItem() + self.root_item = TagTreeItem(icon_map=self.icon_state_map) self.category_nodes = [] last_category_node = None category_node_map = {} self.category_node_tree = {} - for i, r in enumerate(self.row_map): - if self.hidden_categories and self.categories[i] in self.hidden_categories: - continue + for i, key in enumerate(self.row_map): + if self.hidden_categories: + if key in self.hidden_categories: + continue + found = False + for cat in self.hidden_categories: + if cat.startswith('@') and key.startswith(cat + '.'): + found = True + if found: + continue is_gst = False - if r.startswith('@') and r[1:] in gst: - tt = _(u'The grouped search term name is "{0}"').format(r[1:]) + if key.startswith('@') and key[1:] in gst: + tt = _(u'The grouped search term name is "{0}"').format(key[1:]) is_gst = True - elif r == 'news': + elif key == 'news': tt = '' else: - tt = _(u'The lookup/search name is "{0}"').format(r) + tt = _(u'The lookup/search name is "{0}"').format(key) - if r.startswith('@'): - path_parts = [p for p in r.split('.')] + if key.startswith('@'): + path_parts = [p for p in key.split('.')] path = '' last_category_node = self.root_item tree_root = self.category_node_tree @@ -719,14 +806,17 @@ class TagsModel(QAbstractItemModel): # {{{ if path not in category_node_map: node = TagTreeItem(parent=last_category_node, data=p[1:] if i == 0 else p, - category_icon=self.category_icon_map[r], - tooltip=tt if path == r else path, - category_key=path) + category_icon=self.category_icon_map[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) node.can_be_edited = (not is_gst) and (i == (len(path_parts)-1)) node.is_gst = is_gst + if not is_gst: + node.tag.is_hierarchical = '5state' if not is_gst: tree_root[p] = {} tree_root = tree_root[p] @@ -736,16 +826,18 @@ class TagsModel(QAbstractItemModel): # {{{ path += '.' else: node = TagTreeItem(parent=self.root_item, - data=self.categories[i], - category_icon=self.category_icon_map[r], - tooltip=tt, category_key=r) + data=self.categories[key], + category_icon=self.category_icon_map[key], + tooltip=tt, category_key=key, + icon_map=self.icon_state_map) node.is_gst = False - category_node_map[r] = node + category_node_map[key] = node last_category_node = node self.category_nodes.append(node) self.refresh(data=data) def break_cycles(self): + self.root_item.break_cycles() self.db = self.root_item = None def mimeTypes(self): @@ -766,7 +858,7 @@ class TagsModel(QAbstractItemModel): # {{{ p = node while p.type != TagTreeItem.CATEGORY: p = p.parent - d = (node.type, p.category_key, p.is_gst, original_name(t), + d = (node.type, p.category_key, p.is_gst, t.original_name, t.category, path) data.append(d) else: @@ -817,7 +909,7 @@ class TagsModel(QAbstractItemModel): # {{{ Copy/move an item and all its children to the destination ''' copied = False - src_name = original_name(node.tag) + src_name = node.tag.original_name src_cat = node.tag.category # delete the item if the source is a user category and action is move if is_uc and not src_parent_is_gst and src_parent in user_cats and \ @@ -894,13 +986,17 @@ class TagsModel(QAbstractItemModel): # {{{ def do_drop_from_library(self, md, action, row, column, parent): idx = parent if idx.isValid(): + self.tags_view.setCurrentIndex(idx) node = self.data(idx, Qt.UserRole) if node.type == TagTreeItem.TAG: fm = self.db.metadata_for_field(node.tag.category) if node.tag.category in \ ('tags', 'series', 'authors', 'rating', 'publisher') or \ - (fm['is_custom'] and \ - fm['datatype'] in ['text', 'rating', 'series']): + (fm['is_custom'] and ( + fm['datatype'] in ['text', 'rating', 'series', + 'enumeration'] or + (fm['datatype'] == 'composite' and + fm['display'].get('make_category', False)))): mime = 'application/calibre+from_library' ids = list(map(int, str(md.data(mime)).split())) self.handle_drop(node, ids) @@ -910,9 +1006,11 @@ class TagsModel(QAbstractItemModel): # {{{ if fm_dest['kind'] == 'user': fm_src = self.db.metadata_for_field(md.column_name) if md.column_name in ['authors', 'publisher', 'series'] or \ - (fm_src['is_custom'] and - fm_src['datatype'] in ['series', 'text'] and - not fm_src['is_multiple']): + (fm_src['is_custom'] and ( + (fm_src['datatype'] in ['series', 'text', 'enumeration'] and + not fm_src['is_multiple']))or + (fm_src['datatype'] == 'composite' and + fm_src['display'].get('make_category', False))): mime = 'application/calibre+from_library' ids = list(map(int, str(md.data(mime)).split())) self.handle_user_category_drop(node, ids, md.column_name) @@ -926,7 +1024,6 @@ class TagsModel(QAbstractItemModel): # {{{ return fm_src = self.db.metadata_for_field(column) for id in ids: - vmap = {} label = fm_src['label'] if not fm_src['is_custom']: if label == 'authors': @@ -942,19 +1039,21 @@ class TagsModel(QAbstractItemModel): # {{{ value = self.db.series(id, index_is_id=True) else: items = self.db.get_custom_items_with_ids(label=label) - value = self.db.get_custom(id, label=label, index_is_id=True) + if fm_src['datatype'] != 'composite': + value = self.db.get_custom(id, label=label, index_is_id=True) + else: + value = self.db.get_property(id, loc=fm_src['rec_index'], + index_is_id=True) if value is None: return if not isinstance(value, list): value = [value] - for v in items: - vmap[v[1]] = v[0] for val in value: for (v, c, id) in category: if v == val and c == column: break else: - category.append([val, column, vmap[val]]) + category.append([val, column, 0]) categories[on_node.category_key[1:]] = category self.db.prefs.set('user_categories', categories) self.tags_view.recount() @@ -965,17 +1064,17 @@ class TagsModel(QAbstractItemModel): # {{{ if (key == 'authors' and len(ids) >= 5): if not confirm('<p>'+_('Changing the authors for several books can ' 'take a while. Are you sure?') - +'</p>', 'tag_browser_drop_authors', self.parent()): + +'</p>', 'tag_browser_drop_authors', self.tags_view): return elif len(ids) > 15: if not confirm('<p>'+_('Changing the metadata for that many books ' 'can take a while. Are you sure?') - +'</p>', 'tag_browser_many_changes', self.parent()): + +'</p>', 'tag_browser_many_changes', self.tags_view): return fm = self.db.metadata_for_field(key) is_multiple = fm['is_multiple'] - val = original_name(on_node.tag) + val = on_node.tag.original_name for id in ids: mi = self.db.get_metadata(id, index_is_id=True) @@ -1015,13 +1114,17 @@ class TagsModel(QAbstractItemModel): # {{{ def get_node_tree(self, sort): old_row_map = self.row_map[:] self.row_map = [] - self.categories = [] + self.categories = {} # Get the categories if self.search_restriction: - data = self.db.get_categories(sort=sort, + try: + data = self.db.get_categories(sort=sort, icon_map=self.category_icon_map, ids=self.db.search('', return_matches=True)) + except: + data = self.db.get_categories(sort=sort, icon_map=self.category_icon_map) + self.tags_view.restriction_error.emit() else: data = self.db.get_categories(sort=sort, icon_map=self.category_icon_map) @@ -1062,7 +1165,7 @@ class TagsModel(QAbstractItemModel): # {{{ for category in tb_categories: if category in data: # The search category can come and go self.row_map.append(category) - self.categories.append(tb_categories[category]['name']) + self.categories[category] = tb_categories[category]['name'] if len(old_row_map) != 0 and len(old_row_map) != len(self.row_map): # A category has been added or removed. We must force a rebuild of @@ -1091,7 +1194,7 @@ class TagsModel(QAbstractItemModel): # {{{ collapse_model = 'partition' collapse_template = tweaks['categories_collapsed_popularity_template'] - def process_one_node(category, state_map): + def process_one_node(category, state_map): # {{{ collapse_letter = None category_index = self.createIndex(category.row(), 0, category) category_node = category_index.internalPointer() @@ -1108,7 +1211,8 @@ class TagsModel(QAbstractItemModel): # {{{ not fm['is_custom'] and \ not fm['kind'] == 'user' \ else False - tt = key if fm['kind'] == 'user' else None + in_uc = fm['kind'] == 'user' + tt = key if in_uc else None if collapse_model == 'first letter': # Build a list of 'equal' first letters by looking for @@ -1159,11 +1263,12 @@ class TagsModel(QAbstractItemModel): # {{{ d['last'] = data[key][cat_len-1] name = eval_formatter.safe_format(collapse_template, d, 'TAG_VIEW', None) - self.beginInsertRows(category_index, 999999, 1) #len(data[key])-1) + self.beginInsertRows(category_index, 999998, 999999) #len(data[key])-1) sub_cat = TagTreeItem(parent=category, data = name, tooltip = None, temporary=True, category_icon = category_node.icon, - category_key=category_node.category_key) + category_key=category_node.category_key, + icon_map=self.icon_state_map) self.endInsertRows() else: # by 'first letter' cl = cl_list[idx] @@ -1173,7 +1278,8 @@ class TagsModel(QAbstractItemModel): # {{{ data = collapse_letter, category_icon = category_node.icon, tooltip = None, temporary=True, - category_key=category_node.category_key) + category_key=category_node.category_key, + icon_map=self.icon_state_map) node_parent = sub_cat else: node_parent = category @@ -1181,25 +1287,22 @@ class TagsModel(QAbstractItemModel): # {{{ # category display order is important here. The following works # only of all the non-user categories are displayed before the # user categories - components = [t.strip() for t in original_name(tag).split('.') + components = [t.strip() for t in tag.original_name.split('.') if t.strip()] - if len(components) == 0 or '.'.join(components) != original_name(tag): - components = [original_name(tag)] - in_uc = fm['kind'] == 'user' + if len(components) == 0 or '.'.join(components) != tag.original_name: + components = [tag.original_name] if (not tag.is_hierarchical) and (in_uc or + (fm['is_custom'] and fm['display'].get('is_names', False)) or key in ['authors', 'publisher', 'news', 'formats', 'rating'] or key not in self.db.prefs.get('categories_using_hierarchy', []) or len(components) == 1): - self.beginInsertRows(category_index, 999999, 1) + self.beginInsertRows(category_index, 999998, 999999) n = TagTreeItem(parent=node_parent, data=tag, tooltip=tt, icon_map=self.icon_state_map) if tag.id_set is not None: n.id_set |= tag.id_set category_child_map[tag.name, tag.category] = n self.endInsertRows() - tag.is_editable = key != 'formats' and (key == 'news' or \ - self.db.field_metadata[tag.category]['datatype'] in \ - ['text', 'series', 'enumeration']) else: for i,comp in enumerate(components): if i == 0: @@ -1210,20 +1313,26 @@ class TagsModel(QAbstractItemModel): # {{{ if t.type != TagTreeItem.CATEGORY]) if (comp,tag.category) in child_map: node_parent = child_map[(comp,tag.category)] - node_parent.tag.is_hierarchical = True + node_parent.tag.is_hierarchical = \ + '5state' if tag.category != 'search' else '3state' else: if i < len(components)-1: t = copy.copy(tag) t.original_name = '.'.join(components[:i+1]) - t.is_editable = False + if key != 'search': + # This 'manufactured' intermediate node can + # be searched, but cannot be edited. + t.is_editable = False + else: + t.is_searchable = t.is_editable = False else: t = tag if not in_uc: t.original_name = t.name - t.is_editable = True - t.is_hierarchical = True + t.is_hierarchical = \ + '5state' if t.category != 'search' else '3state' t.name = comp - self.beginInsertRows(category_index, 999999, 1) + self.beginInsertRows(category_index, 999998, 999999) node_parent = TagTreeItem(parent=node_parent, data=t, tooltip=tt, icon_map=self.icon_state_map) child_map[(comp,tag.category)] = node_parent @@ -1231,6 +1340,7 @@ class TagsModel(QAbstractItemModel): # {{{ # This id_set must not be None node_parent.id_set |= tag.id_set return + # }}} for category in self.category_nodes: if len(category.children) > 0: @@ -1284,16 +1394,20 @@ class TagsModel(QAbstractItemModel): # {{{ return False user_cats = self.db.prefs.get('user_categories', {}) + user_cat_keys_lower = [icu_lower(k) for k in user_cats] ckey = item.category_key[1:] + ckey_lower = icu_lower(ckey) dotpos = ckey.rfind('.') if dotpos < 0: nkey = val else: nkey = ckey[:dotpos+1] + val - for c in user_cats: - if c.startswith(ckey): + nkey_lower = icu_lower(nkey) + for c in sorted(user_cats.keys(), key=sort_key): + if icu_lower(c).startswith(ckey_lower): if len(c) == len(ckey): - if nkey in user_cats: + if strcmp(ckey, nkey) != 0 and \ + nkey_lower in user_cat_keys_lower: error_dialog(self.tags_view, _('Rename user category'), _('The name %s is already used')%nkey, show=True) return False @@ -1301,7 +1415,8 @@ class TagsModel(QAbstractItemModel): # {{{ del user_cats[ckey] elif c[len(ckey)] == '.': rest = c[len(ckey):] - if (nkey + rest) in user_cats: + if strcmp(ckey, nkey) != 0 and \ + icu_lower(nkey + rest) in user_cat_keys_lower: error_dialog(self.tags_view, _('Rename user category'), _('The name %s is already used')%(nkey+rest), show=True) return False @@ -1315,7 +1430,7 @@ class TagsModel(QAbstractItemModel): # {{{ return True key = item.tag.category - name = original_name(item.tag) + name = item.tag.original_name # make certain we know about the item's category if key not in self.db.field_metadata: return False @@ -1408,7 +1523,7 @@ class TagsModel(QAbstractItemModel): # {{{ if node.tag.category in \ ('tags', 'series', 'authors', 'rating', 'publisher') or \ (fm['is_custom'] and \ - fm['datatype'] in ['text', 'rating', 'series']): + fm['datatype'] in ['text', 'rating', 'series', 'enumeration']): ans |= Qt.ItemIsDropEnabled else: ans |= Qt.ItemIsDropEnabled @@ -1477,16 +1592,15 @@ class TagsModel(QAbstractItemModel): # {{{ def reset_all_states(self, except_=None): update_list = [] def process_tag(tag_item): - if tag_item.type != TagTreeItem.CATEGORY: - tag = tag_item.tag - if tag is except_: - tag_index = self.createIndex(tag_item.row(), 0, tag_item) - self.dataChanged.emit(tag_index, tag_index) - elif tag.state != 0 or tag in update_list: - tag_index = self.createIndex(tag_item.row(), 0, tag_item) - tag.state = 0 - update_list.append(tag) - self.dataChanged.emit(tag_index, tag_index) + tag = tag_item.tag + if tag is except_: + tag_index = self.createIndex(tag_item.row(), 0, tag_item) + self.dataChanged.emit(tag_index, tag_index) + elif tag.state != 0 or tag in update_list: + tag_index = self.createIndex(tag_item.row(), 0, tag_item) + tag.state = 0 + update_list.append(tag) + self.dataChanged.emit(tag_index, tag_index) for t in tag_item.children: process_tag(t) @@ -1503,13 +1617,11 @@ class TagsModel(QAbstractItemModel): # {{{ ''' if not index.isValid(): return False item = index.internalPointer() - if item.type == TagTreeItem.TAG: - item.toggle(set_to=set_to) - if exclusive: - self.reset_all_states(except_=item.tag) - self.dataChanged.emit(index, index) - return True - return False + item.toggle(set_to=set_to) + if exclusive: + self.reset_all_states(except_=item.tag) + self.dataChanged.emit(index, index) + return True def tokens(self): ans = [] @@ -1523,19 +1635,35 @@ class TagsModel(QAbstractItemModel): # {{{ # into the search string only once. The nodes_seen set helps us do that nodes_seen = set() + node_searches = {TAG_SEARCH_STATES['mark_plus'] : 'true', + TAG_SEARCH_STATES['mark_plusplus'] : '.true', + TAG_SEARCH_STATES['mark_minus'] : 'false', + TAG_SEARCH_STATES['mark_minusminus'] : '.false'} + for node in self.category_nodes: + if node.tag.state: + ans.append('%s:%s'%(node.category_key, node_searches[node.tag.state])) + key = node.category_key for tag_item in node.child_tags(): tag = tag_item.tag if tag.state != TAG_SEARCH_STATES['clear']: - prefix = ' not ' if tag.state == TAG_SEARCH_STATES['mark_minus'] \ - else '' + if tag.state == TAG_SEARCH_STATES['mark_minus'] or \ + tag.state == TAG_SEARCH_STATES['mark_minusminus']: + prefix = ' not ' + else: + prefix = '' category = tag.category if key != 'news' else 'tag' + add_colon = False + if self.db.field_metadata[tag.category]['is_csp']: + add_colon = True + if tag.name and tag.name[0] == u'\u2605': # char is a star. Assume rating ans.append('%s%s:%s'%(prefix, category, len(tag.name))) else: - name = original_name(tag) - use_prefix = tag.is_hierarchical + name = tag.original_name + use_prefix = tag.state in [TAG_SEARCH_STATES['mark_plusplus'], + TAG_SEARCH_STATES['mark_minusminus']] if category == 'tags': if name in tags_seen: continue @@ -1543,9 +1671,12 @@ class TagsModel(QAbstractItemModel): # {{{ if tag in nodes_seen: continue nodes_seen.add(tag) - ans.append('%s%s:"=%s%s"'%(prefix, category, - '.' if use_prefix else '', - name.replace(r'"', r'\"'))) + n = name.replace(r'"', r'\"') + if name.startswith('.'): + n = '.' + n + ans.append('%s%s:"=%s%s%s"'%(prefix, category, + '.' if use_prefix else '', n, + ':' if add_colon else '')) return ans def find_item_node(self, key, txt, start_path, equals_match=False): @@ -1573,7 +1704,7 @@ class TagsModel(QAbstractItemModel): # {{{ tag = tag_item.tag if tag is None: return False - name = original_name(tag) + name = tag.original_name if (equals_match and strcmp(name, txt) == 0) or \ (not equals_match and lower(name).find(txt) >= 0): self.path_found = path @@ -1691,12 +1822,35 @@ class TagBrowserMixin(object): # {{{ self.tags_view.add_subcategory.connect(self.do_add_subcategory) self.tags_view.add_item_to_user_cat.connect(self.do_add_item_to_user_cat) self.tags_view.saved_search_edit.connect(self.do_saved_search_edit) + self.tags_view.rebuild_saved_searches.connect(self.do_rebuild_saved_searches) self.tags_view.author_sort_edit.connect(self.do_author_sort_edit) self.tags_view.tag_item_renamed.connect(self.do_tag_item_renamed) self.tags_view.search_item_renamed.connect(self.saved_searches_changed) self.tags_view.drag_drop_finished.connect(self.drag_drop_finished) - self.edit_categories.clicked.connect(lambda x: - self.do_edit_user_categories()) + self.tags_view.restriction_error.connect(self.do_restriction_error, + type=Qt.QueuedConnection) + + for text, func, args, cat_name in ( + (_('Manage Authors'), + self.do_author_sort_edit, (self, None), 'authors'), + (_('Manage Series'), + self.do_tags_list_edit, (None, 'series'), 'series'), + (_('Manage Publishers'), + self.do_tags_list_edit, (None, 'publisher'), 'publisher'), + (_('Manage Tags'), + self.do_tags_list_edit, (None, 'tags'), 'tags'), + (_('Manage User Categories'), + self.do_edit_user_categories, (None,), 'user:'), + (_('Manage Saved Searches'), + self.do_saved_search_edit, (None,), 'search') + ): + self.manage_items_button.menu().addAction( + QIcon(I(category_icon_map[cat_name])), + text, partial(func, *args)) + + def do_restriction_error(self): + error_dialog(self.tags_view, _('Invalid search restriction'), + _('The current search restriction is invalid'), show=True) def do_add_subcategory(self, on_category_key, new_category_name=None): ''' @@ -1894,12 +2048,12 @@ class TagBrowserMixin(object): # {{{ self.library_view.select_rows(ids) # refreshing the tags view happens at the emit()/call() site - def do_author_sort_edit(self, parent, id): + def do_author_sort_edit(self, parent, id, select_sort=True): ''' Open the manage authors dialog ''' db = self.library_view.model().db - editor = EditAuthorsDialog(parent, db, id) + editor = EditAuthorsDialog(parent, db, id, select_sort) d = editor.exec_() if d: for (id, old_author, new_author, new_sort) in editor.result: @@ -1947,17 +2101,18 @@ class TagBrowserWidget(QWidget): # {{{ sc = QShortcut(QKeySequence(_('ALT+f')), parent) sc.connect(sc, SIGNAL('activated()'), self.set_focus_to_find_box) - self.search_button = QPushButton() + self.search_button = QToolButton() self.search_button.setText(_('F&ind')) self.search_button.setToolTip(_('Find the first/next matching item')) - self.search_button.setFixedWidth(40) search_layout.addWidget(self.search_button) - self.expand_button = QPushButton() + self.expand_button = QToolButton() self.expand_button.setText('-') - self.expand_button.setFixedWidth(20) self.expand_button.setToolTip(_('Collapse all categories')) search_layout.addWidget(self.expand_button) + search_layout.setStretch(0, 10) + search_layout.setStretch(1, 1) + search_layout.setStretch(2, 1) self.current_find_position = None self.search_button.clicked.connect(self.find) @@ -1999,6 +2154,7 @@ class TagBrowserWidget(QWidget): # {{{ parent.sort_by.setCurrentIndex(0) self._layout.addWidget(parent.sort_by) + # Must be in the same order as db2.MATCH_TYPE parent.tag_match = QComboBox(parent) for x in (_('Match any'), _('Match all')): parent.tag_match.addItem(x) @@ -2009,11 +2165,19 @@ class TagBrowserWidget(QWidget): # {{{ 'match any or all of them')) parent.tag_match.setStatusTip(parent.tag_match.toolTip()) - parent.edit_categories = QPushButton(_('Manage &user categories'), parent) - self._layout.addWidget(parent.edit_categories) - parent.edit_categories.setToolTip( - _('Add your own categories to the Tag Browser')) - parent.edit_categories.setStatusTip(parent.edit_categories.toolTip()) + + l = parent.manage_items_button = QPushButton(self) + l.setStyleSheet('QPushButton {text-align: left; }') + l.setText(_('Manage authors, tags, etc')) + l.setToolTip(_('All of these category_managers are available by right-clicking ' + 'on items in the tag browser above')) + l.m = QMenu() + l.setMenu(l.m) + self._layout.addWidget(l) + + # self.leak_test_timer = QTimer(self) + # self.leak_test_timer.timeout.connect(self.test_for_leak) + # self.leak_test_timer.start(5000) def set_pane_is_visible(self, to_what): self.tags_view.set_pane_is_visible(to_what) @@ -2076,5 +2240,13 @@ class TagBrowserWidget(QWidget): # {{{ def not_found_label_timer_event(self): self.not_found_label.setVisible(False) + def test_for_leak(self): + from calibre.utils.mem import memory + import gc + before = memory() + self.tags_view.recount() + for i in xrange(3): gc.collect() + print 'Used memory:', memory(before)/(1024.), 'KB' + # }}} diff --git a/src/calibre/gui2/threaded_jobs.py b/src/calibre/gui2/threaded_jobs.py new file mode 100644 index 0000000000..9e16f88a1a --- /dev/null +++ b/src/calibre/gui2/threaded_jobs.py @@ -0,0 +1,246 @@ +#!/usr/bin/env python +# vim:fileencoding=UTF-8:ts=4:sw=4:sta:et:sts=4:ai +from __future__ import (unicode_literals, division, absolute_import, + print_function) + +__license__ = 'GPL v3' +__copyright__ = '2011, Kovid Goyal <kovid@kovidgoyal.net>' +__docformat__ = 'restructuredtext en' + +import os, time, tempfile, json +from threading import Thread, RLock, Event +from Queue import Queue + +from calibre.utils.ipc.job import BaseJob +from calibre.utils.logging import GUILog +from calibre.ptempfile import base_dir + +class ThreadedJob(BaseJob): + + def __init__(self, + type_, description, + + func, args, kwargs, + + callback, + + max_concurrent_count=1, + killable=True, + log=None): + ''' + A job that is run in its own thread in the calibre main process + + :param type_: The type of this job (a string). The type is used in + conjunction with max_concurrent_count to prevent too many jobs of the + same type from running + + :param description: A user viewable job description + + :func: The function that actually does the work. This function *must* + accept at least three keyword arguments: abort, log and notifications. abort is + An Event object. func should periodically check abort.is_set() and if + it is True, it should stop processing as soon as possible. notifications + is a Queue. func should put progress notifications into it in the form + of a tuple (frac, msg). frac is a number between 0 and 1 indicating + progress and msg is a string describing the progress. log is a Log + object which func should use for all debugging output. func should + raise an Exception to indicate failure. This exception is stored in + job.exception and can thus be used to pass arbitrary information to + callback. + + :param args,kwargs: These are passed to func when it is called + + :param callback: A callable that is called on completion of this job. + Note that it is not called if the user kills the job. Check job.failed + to see if the job succeeded or not. And use job.log to get the job log. + + :param killable: If False the GUI wont let the user kill this job + + :param log: Must be a subclass of GUILog or None. If None a default + GUILog is created. + ''' + BaseJob.__init__(self, description) + + self.type = type_ + self.max_concurrent_count = max_concurrent_count + self.killable = killable + self.callback = callback + self.abort = Event() + self.exception = None + + kwargs['notifications'] = self.notifications + kwargs['abort'] = self.abort + self.log = GUILog() if log is None else log + kwargs['log'] = self.log + + self.func, self.args, self.kwargs = func, args, kwargs + self.consolidated_log = None + + def start_work(self): + self.start_time = time.time() + self.log('Starting job:', self.description) + try: + self.result = self.func(*self.args, **self.kwargs) + except Exception as e: + self.exception = e + self.failed = True + self.log.exception('Job: "%s" failed with error:'%self.description) + self.log.debug('Called with args:', self.args, self.kwargs) + + self.duration = time.time() - self.start_time + try: + self.callback(self) + except: + import traceback + traceback.print_exc() + self._cleanup() + + def _cleanup(self): + + try: + self.consolidate_log() + except: + if self.log is not None: + self.log.exception('Log consolidation failed') + + # No need to keep references to these around anymore + self.func = self.args = self.kwargs = self.notifications = None + # We can't delete self.callback as it might be a Dispatch object and if + # it is garbage collected it won't work + + def kill(self): + if self.start_time is None: + self.start_time = time.time() + self.duration = 0.0001 + else: + self.duration = time.time() - self.start_time + self.abort.set() + + self.log('Aborted job:', self.description) + self.killed = True + self.failed = True + self._cleanup() + + def consolidate_log(self): + logs = [self.log.html, self.log.plain_text] + bdir = base_dir() + log_dir = os.path.join(bdir, 'threaded_job_logs') + if not os.path.exists(log_dir): + os.makedirs(log_dir) + fd, path = tempfile.mkstemp(suffix='.json', prefix='log-', dir=log_dir) + with os.fdopen(fd, 'wb') as f: + f.write(json.dumps(logs, ensure_ascii=False, + indent=2).encode('utf-8')) + self.consolidated_log = path + self.log = None + + def read_consolidated_log(self): + with open(self.consolidated_log, 'rb') as f: + return json.loads(f.read().decode('utf-8')) + + @property + def details(self): + if self.consolidated_log is None: + return self.log.plain_text + return self.read_consolidated_log()[1] + + @property + def html_details(self): + if self.consolidated_log is None: + return self.log.html + return self.read_consolidated_log()[0] + +class ThreadedJobWorker(Thread): + + def __init__(self, job): + Thread.__init__(self) + self.daemon = True + self.job = job + + def run(self): + try: + self.job.start_work() + except: + import traceback + from calibre import prints + prints('Job had unhandled exception:', self.job.description) + traceback.print_exc() + +class ThreadedJobServer(Thread): + + def __init__(self): + Thread.__init__(self) + self.daemon = True + self.lock = RLock() + + self.queued_jobs = [] + self.running_jobs = set() + self.changed_jobs = Queue() + self.keep_going = True + + def close(self): + self.keep_going = False + + def add_job(self, job): + with self.lock: + self.queued_jobs.append(job) + + if not self.is_alive(): + self.start() + + def run(self): + while self.keep_going: + try: + self.run_once() + except: + import traceback + traceback.print_exc() + time.sleep(0.1) + + def run_once(self): + with self.lock: + remove = set() + for worker in self.running_jobs: + if worker.is_alive(): + # Get progress notifications + if worker.job.consume_notifications(): + self.changed_jobs.put(worker.job) + else: + remove.add(worker) + self.changed_jobs.put(worker.job) + + for worker in remove: + self.running_jobs.remove(worker) + + jobs = self.get_startable_jobs() + for job in jobs: + w = ThreadedJobWorker(job) + w.start() + self.running_jobs.add(w) + self.changed_jobs.put(job) + self.queued_jobs.remove(job) + + def kill_job(self, job): + with self.lock: + if job in self.queued_jobs: + self.queued_jobs.remove(job) + elif job in self.running_jobs: + self.running_jobs.remove(job) + job.kill() + self.changed_jobs.put(job) + + def running_jobs_of_type(self, type_): + return len([w for w in self.running_jobs if w.job.type == type_]) + + def get_startable_jobs(self): + queued_types = [] + ans = [] + for job in self.queued_jobs: + num = self.running_jobs_of_type(job.type) + num += queued_types.count(job.type) + if num < job.max_concurrent_count: + queued_types.append(job.type) + ans.append(job) + return ans + + diff --git a/src/calibre/gui2/ui.py b/src/calibre/gui2/ui.py index 8844446de6..cf9f6ee610 100644 --- a/src/calibre/gui2/ui.py +++ b/src/calibre/gui2/ui.py @@ -9,21 +9,22 @@ __docformat__ = 'restructuredtext en' '''The main GUI''' -import collections, os, sys, textwrap, time +import collections, os, sys, textwrap, time, gc from Queue import Queue, Empty from threading import Thread -from PyQt4.Qt import Qt, SIGNAL, QTimer, QHelpEvent, QAction, \ - QMenu, QIcon, pyqtSignal, \ - QDialog, QSystemTrayIcon, QApplication, QKeySequence +from collections import OrderedDict + +from PyQt4.Qt import (Qt, SIGNAL, QTimer, QHelpEvent, QAction, + QMenu, QIcon, pyqtSignal, QUrl, + QDialog, QSystemTrayIcon, QApplication, QKeySequence) from calibre import prints from calibre.constants import __appname__, isosx -from calibre.ptempfile import PersistentTemporaryFile from calibre.utils.config import prefs, dynamic from calibre.utils.ipc.server import Server from calibre.library.database2 import LibraryDatabase2 -from calibre.customize.ui import interface_actions -from calibre.gui2 import error_dialog, GetMetadata, open_local_file, \ +from calibre.customize.ui import interface_actions, available_store_plugins +from calibre.gui2 import error_dialog, GetMetadata, open_url, \ gprefs, max_available_height, config, info_dialog, Dispatcher, \ question_dialog from calibre.gui2.cover_flow import CoverFlowMixin @@ -33,12 +34,12 @@ from calibre.gui2.main_window import MainWindow from calibre.gui2.layout import MainWindowMixin from calibre.gui2.device import DeviceMixin from calibre.gui2.email import EmailMixin +from calibre.gui2.ebook_download import EbookDownloadMixin from calibre.gui2.jobs import JobManager, JobsDialog, JobsButton from calibre.gui2.init import LibraryViewMixin, LayoutMixin from calibre.gui2.search_box import SearchBoxMixin, SavedSearchBoxMixin from calibre.gui2.search_restriction_mixin import SearchRestrictionMixin from calibre.gui2.tag_view import TagBrowserMixin -from calibre.utils.ordered_dict import OrderedDict class Listener(Thread): # {{{ @@ -87,19 +88,28 @@ class SystemTrayIcon(QSystemTrayIcon): # {{{ # }}} +_gui = None + +def get_gui(): + return _gui + class Main(MainWindow, MainWindowMixin, DeviceMixin, EmailMixin, # {{{ TagBrowserMixin, CoverFlowMixin, LibraryViewMixin, SearchBoxMixin, - SavedSearchBoxMixin, SearchRestrictionMixin, LayoutMixin, UpdateMixin + SavedSearchBoxMixin, SearchRestrictionMixin, LayoutMixin, UpdateMixin, + EbookDownloadMixin ): 'The main GUI' def __init__(self, opts, parent=None, gui_debug=None): - MainWindow.__init__(self, opts, parent) + global _gui + MainWindow.__init__(self, opts, parent=parent, disable_automatic_gc=True) + _gui = self self.opts = opts self.device_connected = None self.gui_debug = gui_debug self.iactions = OrderedDict() + # Actions for action in interface_actions(): if opts.ignore_plugins and action.plugin_path is not None: continue @@ -112,11 +122,10 @@ class Main(MainWindow, MainWindowMixin, DeviceMixin, EmailMixin, # {{{ if action.plugin_path is None: raise continue - ac.plugin_path = action.plugin_path ac.interface_action_base_plugin = action - self.add_iaction(ac) + self.load_store_plugins() def init_iaction(self, action): ac = action.load_actual_plugin(self) @@ -133,6 +142,37 @@ class Main(MainWindow, MainWindowMixin, DeviceMixin, EmailMixin, # {{{ else: acmap[ac.name] = ac + def load_store_plugins(self): + self.istores = OrderedDict() + for store in available_store_plugins(): + if self.opts.ignore_plugins and store.plugin_path is not None: + continue + try: + st = self.init_istore(store) + self.add_istore(st) + except: + # Ignore errors in loading user supplied plugins + import traceback + traceback.print_exc() + if store.plugin_path is None: + raise + continue + + def init_istore(self, store): + st = store.load_actual_plugin(self) + st.plugin_path = store.plugin_path + st.base_plugin = store + store.actual_istore_plugin_loaded = True + return st + + def add_istore(self, st): + stmap = self.istores + if st.name in stmap: + if st.priority >= stmap[st.name].priority: + stmap[st.name] = st + else: + stmap[st.name] = st + def initialize(self, library_path, db, listener, actions, show_gui=True): opts = self.opts @@ -153,6 +193,9 @@ class Main(MainWindow, MainWindowMixin, DeviceMixin, EmailMixin, # {{{ for ac in self.iactions.values(): ac.do_genesis() + self.donate_action = QAction(QIcon(I('donate.png')), _('&Donate to support calibre'), self) + for st in self.istores.values(): + st.do_genesis() MainWindowMixin.__init__(self, db) # Jobs Button {{{ @@ -164,6 +207,7 @@ class Main(MainWindow, MainWindowMixin, DeviceMixin, EmailMixin, # {{{ LayoutMixin.__init__(self) EmailMixin.__init__(self) + EbookDownloadMixin.__init__(self) DeviceMixin.__init__(self) self.progress_indicator = ProgressIndicator(self) @@ -186,8 +230,7 @@ class Main(MainWindow, MainWindowMixin, DeviceMixin, EmailMixin, # {{{ self.system_tray_menu = QMenu(self) self.restore_action = self.system_tray_menu.addAction( QIcon(I('page.png')), _('&Restore')) - self.donate_action = self.system_tray_menu.addAction( - QIcon(I('donate.png')), _('&Donate to support calibre')) + self.system_tray_menu.addAction(self.donate_action) self.donate_button.setDefaultAction(self.donate_action) self.donate_button.setStatusTip(self.donate_button.toolTip()) self.eject_action = self.system_tray_menu.addAction( @@ -235,11 +278,8 @@ class Main(MainWindow, MainWindowMixin, DeviceMixin, EmailMixin, # {{{ self.library_view.model().count_changed_signal.connect( self.iactions['Choose Library'].count_changed) if not gprefs.get('quick_start_guide_added', False): - from calibre.ebooks.metadata import MetaInformation - mi = MetaInformation(_('Calibre Quick Start Guide'), ['John Schember']) - mi.author_sort = 'Schember, John' - mi.comments = "A guide to get you up and running with calibre" - mi.publisher = 'calibre' + from calibre.ebooks.metadata.meta import get_metadata + mi = get_metadata(open(P('quick_start.epub'), 'rb'), 'epub') self.library_view.model().add_books([P('quick_start.epub')], ['epub'], [mi]) gprefs['quick_start_guide_added'] = True @@ -248,8 +288,8 @@ class Main(MainWindow, MainWindowMixin, DeviceMixin, EmailMixin, # {{{ self.db_images.reset() self.library_view.model().count_changed() - self.tool_bar.database_changed(self.library_view.model().db) - self.library_view.model().database_changed.connect(self.tool_bar.database_changed, + self.bars_manager.database_changed(self.library_view.model().db) + self.library_view.model().database_changed.connect(self.bars_manager.database_changed, type=Qt.QueuedConnection) ########################### Tags Browser ############################## @@ -284,7 +324,7 @@ class Main(MainWindow, MainWindowMixin, DeviceMixin, EmailMixin, # {{{ self.read_settings() self.finalize_layout() - if self.tool_bar.showing_donate: + if self.bars_manager.showing_donate: self.donate_button.start_animation() self.set_window_title() @@ -296,6 +336,10 @@ class Main(MainWindow, MainWindowMixin, DeviceMixin, EmailMixin, # {{{ traceback.print_exc() if ac.plugin_path is None: raise + self.device_manager.set_current_library_uuid(db.library_id) + + # Collect cycles now + gc.collect() if show_gui and self.gui_debug is not None: info_dialog(self, _('Debug mode'), '<p>' + @@ -378,6 +422,9 @@ class Main(MainWindow, MainWindowMixin, DeviceMixin, EmailMixin, # {{{ error_dialog(self, _('Failed to start content server'), unicode(self.content_server.exception)).exec_() + @property + def current_db(self): + return self.library_view.model().db def another_instance_wants_to_talk(self): try: @@ -398,6 +445,9 @@ class Main(MainWindow, MainWindowMixin, DeviceMixin, EmailMixin, # {{{ elif msg.startswith('refreshdb:'): self.library_view.model().refresh() self.library_view.model().research() + self.tags_view.recount() + elif msg.startswith('shutdown:'): + self.quit(confirm_quit=False) else: print msg @@ -438,15 +488,16 @@ class Main(MainWindow, MainWindowMixin, DeviceMixin, EmailMixin, # {{{ self.search.clear() self.saved_search.clear() self.book_details.reset_info() - self.library_view.model().count_changed() prefs['library_path'] = self.library_path + #self.library_view.model().count_changed() db = self.library_view.model().db - for action in self.iactions.values(): - action.library_changed(db) + self.iactions['Choose Library'].count_changed(db.count()) self.set_window_title() self.apply_named_search_restriction('') # reset restriction to null - self.saved_searches_changed() # reload the search restrictions combo box + self.saved_searches_changed(recount=False) # reload the search restrictions combo box self.apply_named_search_restriction(db.prefs['gui_restriction']) + for action in self.iactions.values(): + action.library_changed(db) if olddb is not None: try: if call_close: @@ -461,6 +512,10 @@ class Main(MainWindow, MainWindowMixin, DeviceMixin, EmailMixin, # {{{ self.memory_view.reset() self.card_a_view.reset() self.card_b_view.reset() + self.device_manager.set_current_library_uuid(db.library_id) + # Run a garbage collection now so that it does not freeze the + # interface later + gc.collect() def set_window_title(self): @@ -480,10 +535,10 @@ class Main(MainWindow, MainWindowMixin, DeviceMixin, EmailMixin, # {{{ action.location_selected(location) if location == 'library': self.search_restriction.setEnabled(True) - self.search_options_button.setEnabled(True) + self.highlight_only_button.setEnabled(True) else: self.search_restriction.setEnabled(False) - self.search_options_button.setEnabled(False) + self.highlight_only_button.setEnabled(False) # Reset the view in case something changed while it was invisible self.current_view().reset() self.set_number_of_books_shown() @@ -546,8 +601,9 @@ class Main(MainWindow, MainWindowMixin, DeviceMixin, EmailMixin, # {{{ dynamic.set('sort_history', self.library_view.model().sort_history) self.save_layout_state() - def quit(self, checked=True, restart=False, debug_on_restart=False): - if not self.confirm_quit(): + def quit(self, checked=True, restart=False, debug_on_restart=False, + confirm_quit=True): + if confirm_quit and not self.confirm_quit(): return try: self.shutdown() @@ -558,37 +614,7 @@ class Main(MainWindow, MainWindowMixin, DeviceMixin, EmailMixin, # {{{ QApplication.instance().quit() def donate(self, *args): - BUTTON = ''' - <form action="https://www.paypal.com/cgi-bin/webscr" method="post"> - <input type="hidden" name="cmd" value="_s-xclick" /> - <input type="hidden" name="hosted_button_id" value="3029467" /> - <input type="image" src="https://www.paypal.com/en_US/i/btn/btn_donateCC_LG.gif" border="0" name="submit" alt="Donate to support calibre development" /> - <img alt="" border="0" src="https://www.paypal.com/en_US/i/scr/pixel.gif" width="1" height="1" /> - </form> - ''' - MSG = _('is the result of the efforts of many volunteers from all ' - 'over the world. If you find it useful, please consider ' - 'donating to support its development. Your donation helps ' - 'keep calibre development going.') - HTML = u''' - <html> - <head> - <meta http-equiv="Content-Type" content="text/html;charset=utf-8" /> - <title>Donate to support calibre - - -
    calibre
    -

    Calibre %s

    - %s - - - '''%(P('content_server/calibre_banner.png').replace(os.sep, '/'), MSG, BUTTON) - pt = PersistentTemporaryFile('_donate.htm') - pt.write(HTML.encode('utf-8')) - pt.close() - open_local_file(pt.name) - + open_url(QUrl('http://calibre-ebook.com/donate')) def confirm_quit(self): if self.job_manager.has_jobs(): @@ -625,6 +651,7 @@ class Main(MainWindow, MainWindowMixin, DeviceMixin, EmailMixin, # {{{ self.update_checker.terminate() self.listener.close() self.job_manager.server.close() + self.job_manager.threaded_server.close() while self.spare_servers: self.spare_servers.pop().close() self.device_manager.keep_going = False @@ -633,8 +660,6 @@ class Main(MainWindow, MainWindowMixin, DeviceMixin, EmailMixin, # {{{ mb.stop() self.hide_windows() - if self.emailer.is_alive(): - self.emailer.stop() try: try: if self.content_server is not None: @@ -647,6 +672,8 @@ class Main(MainWindow, MainWindowMixin, DeviceMixin, EmailMixin, # {{{ pass time.sleep(2) self.hide_windows() + # Do not report any errors that happen after the shutdown + sys.excepthook = sys.__excepthook__ return True def run_wizard(self, *args): diff --git a/src/calibre/gui2/update.py b/src/calibre/gui2/update.py index 9929d50a7e..9aae245d98 100644 --- a/src/calibre/gui2/update.py +++ b/src/calibre/gui2/update.py @@ -49,10 +49,12 @@ class UpdateNotification(QDialog): self.logo.setMaximumWidth(110) self.logo.setPixmap(QPixmap(I('lt.png')).scaled(100, 100, Qt.IgnoreAspectRatio, Qt.SmoothTransformation)) - self.label = QLabel('

    '+ + self.label = QLabel(('

    '+ _('%s has been updated to version %s. ' 'See the new features.')%(__appname__, version)) + '">new features.') + '

    '+_('Update only if one of the ' + 'new features or bug fixes is important to you. ' + 'If the current version works well for you, do not update.'))%(__appname__, version)) self.label.setOpenExternalLinks(True) self.label.setWordWrap(True) self.setWindowTitle(_('Update available!')) diff --git a/src/calibre/gui2/viewer/dictionary.py b/src/calibre/gui2/viewer/dictionary.py index dad8d1821c..d5dd4d0a86 100644 --- a/src/calibre/gui2/viewer/dictionary.py +++ b/src/calibre/gui2/viewer/dictionary.py @@ -36,7 +36,7 @@ class Lookup(QThread): def run(self): try: self.define() - except Exception, e: + except Exception as e: import traceback self.exception = e self.traceback = traceback.format_exc() diff --git a/src/calibre/gui2/viewer/documentview.py b/src/calibre/gui2/viewer/documentview.py index 4102aea412..808a764196 100644 --- a/src/calibre/gui2/viewer/documentview.py +++ b/src/calibre/gui2/viewer/documentview.py @@ -171,10 +171,11 @@ class Document(QWebPage): # {{{ self.misc_config() self.after_load() - def __init__(self, shortcuts, parent=None): + def __init__(self, shortcuts, parent=None, resize_callback=lambda: None): QWebPage.__init__(self, parent) self.setObjectName("py_bridge") self.debug_javascript = False + self.resize_callback = resize_callback self.current_language = None self.loaded_javascript = False @@ -237,6 +238,12 @@ class Document(QWebPage): # {{{ if self.loaded_javascript: return self.loaded_javascript = True + self.javascript( + ''' + window.onresize = function(event) { + window.py_bridge.window_resized(); + } + ''') if jquery is None: jquery = P('content_server/jquery.js', data=True) self.javascript(jquery) @@ -298,6 +305,10 @@ class Document(QWebPage): # {{{ def debug(self, msg): prints(msg) + @pyqtSignature('') + def window_resized(self): + self.resize_callback() + def reference_mode(self, enable): self.javascript(('enter' if enable else 'leave')+'_reference_mode()') @@ -424,12 +435,19 @@ class Document(QWebPage): # {{{ def xpos(self): return self.mainFrame().scrollPosition().x() - @property + @dynamic_property def scroll_fraction(self): - try: - return float(self.ypos)/(self.height-self.window_height) - except ZeroDivisionError: - return 0. + def fget(self): + try: + return float(self.ypos)/(self.height-self.window_height) + except ZeroDivisionError: + return 0. + def fset(self, val): + npos = val * (self.height - self.window_height) + if npos < 0: + npos = 0 + self.scroll_to(x=self.xpos, y=npos) + return property(fget=fget, fset=fset) @property def hscroll_fraction(self): @@ -493,7 +511,8 @@ class DocumentView(QWebView): # {{{ self._size_hint = QSize(510, 680) self.initial_pos = 0.0 self.to_bottom = False - self.document = Document(self.shortcuts, parent=self) + self.document = Document(self.shortcuts, parent=self, + resize_callback=self.viewport_resized) self.setPage(self.document) self.manager = None self._reference_mode = False @@ -515,6 +534,7 @@ class DocumentView(QWebView): # {{{ _('&Lookup in dictionary'), self) self.dictionary_action.setShortcut(Qt.CTRL+Qt.Key_L) self.dictionary_action.triggered.connect(self.lookup) + self.addAction(self.dictionary_action) self.goto_location_action = QAction(_('Go to...'), self) self.goto_location_menu = m = QMenu(self) self.goto_location_actions = a = { @@ -630,9 +650,13 @@ class DocumentView(QWebView): # {{{ def sizeHint(self): return self._size_hint - @property + @dynamic_property def scroll_fraction(self): - return self.document.scroll_fraction + def fget(self): + return self.document.scroll_fraction + def fset(self, val): + self.document.scroll_fraction = float(val) + return property(fget=fget, fset=fset) @property def hscroll_fraction(self): @@ -968,9 +992,11 @@ class DocumentView(QWebView): # {{{ def resizeEvent(self, event): ret = QWebView.resizeEvent(self, event) QTimer.singleShot(10, self.initialize_scrollbar) + return ret + + def viewport_resized(self): if self.manager is not None: self.manager.viewport_resized(self.scroll_fraction) - return ret def event(self, ev): typ = ev.type() diff --git a/src/calibre/gui2/viewer/main.py b/src/calibre/gui2/viewer/main.py index de0f83a5b2..e25d59c5ad 100644 --- a/src/calibre/gui2/viewer/main.py +++ b/src/calibre/gui2/viewer/main.py @@ -17,17 +17,19 @@ from calibre.gui2.viewer.bookmarkmanager import BookmarkManager from calibre.gui2.widgets import ProgressIndicator from calibre.gui2.main_window import MainWindow from calibre.gui2 import Application, ORG_NAME, APP_UID, choose_files, \ - info_dialog, error_dialog, open_url, available_height, gprefs + info_dialog, error_dialog, open_url, available_height from calibre.ebooks.oeb.iterator import EbookIterator from calibre.ebooks import DRMError -from calibre.constants import islinux, isfreebsd, isosx, filesystem_encoding -from calibre.utils.config import Config, StringConfig, dynamic +from calibre.constants import islinux, isbsd, isosx, filesystem_encoding +from calibre.utils.config import Config, StringConfig, JSONConfig from calibre.gui2.search_box import SearchBox2 from calibre.ebooks.metadata import MetaInformation from calibre.customize.ui import available_input_formats from calibre.gui2.viewer.dictionary import Lookup from calibre import as_unicode, force_unicode, isbytestring +vprefs = JSONConfig('viewer') + class TOCItem(QStandardItem): def __init__(self, toc): @@ -223,6 +225,12 @@ class EbookViewer(MainWindow, Ui_EbookViewer): self.action_quit.setShortcuts(qs) self.connect(self.action_quit, SIGNAL('triggered(bool)'), lambda x:QApplication.instance().quit()) + self.action_focus_search = QAction(self) + self.addAction(self.action_focus_search) + self.action_focus_search.setShortcuts([Qt.Key_Slash, + QKeySequence(QKeySequence.Find)]) + self.action_focus_search.triggered.connect(lambda x: + self.search.setFocus(Qt.OtherFocusReason)) self.action_copy.setDisabled(True) self.action_metadata.setCheckable(True) self.action_metadata.setShortcut(Qt.CTRL+Qt.Key_I) @@ -232,7 +240,7 @@ class EbookViewer(MainWindow, Ui_EbookViewer): self.connect(self.action_reference_mode, SIGNAL('triggered(bool)'), lambda x: self.view.reference_mode(x)) self.connect(self.action_metadata, SIGNAL('triggered(bool)'), lambda x:self.metadata.setVisible(x)) - self.connect(self.action_table_of_contents, SIGNAL('toggled(bool)'), lambda x:self.toc.setVisible(x)) + self.action_table_of_contents.toggled[bool].connect(self.set_toc_visible) self.connect(self.action_copy, SIGNAL('triggered(bool)'), self.copy) self.connect(self.action_font_size_larger, SIGNAL('triggered(bool)'), self.font_size_larger) @@ -291,6 +299,9 @@ class EbookViewer(MainWindow, Ui_EbookViewer): ca.setShortcut(QKeySequence.Copy) self.addAction(ca) self.open_history_menu = QMenu() + self.clear_recent_history_action = QAction( + _('Clear list of recently opened books'), self) + self.clear_recent_history_action.triggered.connect(self.clear_recent_history) self.build_recent_menu() self.action_open_ebook.setMenu(self.open_history_menu) self.open_history_menu.triggered[QAction].connect(self.open_recent) @@ -299,11 +310,22 @@ class EbookViewer(MainWindow, Ui_EbookViewer): self.restore_state() + def set_toc_visible(self, yes): + self.toc.setVisible(yes) + + def clear_recent_history(self, *args): + vprefs.set('viewer_open_history', []) + self.build_recent_menu() + def build_recent_menu(self): m = self.open_history_menu m.clear() + recent = vprefs.get('viewer_open_history', []) + if recent: + m.addAction(self.clear_recent_history_action) + m.addSeparator() count = 0 - for path in gprefs.get('viewer_open_history', []): + for path in recent: if count > 9: break if os.path.exists(path): @@ -315,17 +337,17 @@ class EbookViewer(MainWindow, Ui_EbookViewer): return MainWindow.closeEvent(self, e) def save_state(self): - state = str(self.saveState(self.STATE_VERSION)) - dynamic['viewer_toolbar_state'] = state - dynamic.set('viewer_window_geometry', self.saveGeometry()) + state = bytearray(self.saveState(self.STATE_VERSION)) + vprefs['viewer_toolbar_state'] = state + vprefs.set('viewer_window_geometry', bytearray(self.saveGeometry())) if self.current_book_has_toc: - dynamic.set('viewer_toc_isvisible', bool(self.toc.isVisible())) + vprefs.set('viewer_toc_isvisible', bool(self.toc.isVisible())) if self.toc.isVisible(): - dynamic.set('viewer_splitter_state', + vprefs.set('viewer_splitter_state', bytearray(self.splitter.saveState())) def restore_state(self): - state = dynamic.get('viewer_toolbar_state', None) + state = vprefs.get('viewer_toolbar_state', None) if state is not None: try: state = QByteArray(state) @@ -492,12 +514,6 @@ class EbookViewer(MainWindow, Ui_EbookViewer): if self.view.search(text, backwards=backwards): self.scrolled(self.view.scroll_fraction) - def keyPressEvent(self, event): - if event.key() == Qt.Key_Slash: - self.search.setFocus(Qt.OtherFocusReason) - else: - return MainWindow.keyPressEvent(self, event) - def internal_link_clicked(self, frac): self.history.add(self.pos.value()) @@ -676,13 +692,13 @@ class EbookViewer(MainWindow, Ui_EbookViewer): self.action_table_of_contents.setChecked(False) if isbytestring(pathtoebook): pathtoebook = force_unicode(pathtoebook, filesystem_encoding) - vh = gprefs.get('viewer_open_history', []) + vh = vprefs.get('viewer_open_history', []) try: vh.remove(pathtoebook) except: pass vh.insert(0, pathtoebook) - gprefs.set('viewer_open_history', vh[:50]) + vprefs.set('viewer_open_history', vh[:50]) self.build_recent_menu() self.action_table_of_contents.setDisabled(not self.iterator.toc) @@ -739,13 +755,13 @@ class EbookViewer(MainWindow, Ui_EbookViewer): c = config().parse() self.splitter.setSizes([1, 300]) if c.remember_window_size: - wg = dynamic.get('viewer_window_geometry', None) + wg = vprefs.get('viewer_window_geometry', None) if wg is not None: self.restoreGeometry(wg) - ss = dynamic.get('viewer_splitter_state', None) + ss = vprefs.get('viewer_splitter_state', None) if ss is not None: self.splitter.restoreState(ss) - self.show_toc_on_open = dynamic.get('viewer_toc_isvisible', False) + self.show_toc_on_open = vprefs.get('viewer_toc_isvisible', False) av = available_height() - 30 if self.height() > av: self.resize(self.width(), av) @@ -785,7 +801,7 @@ def main(args=sys.argv): parser = option_parser() opts, args = parser.parse_args(args) - pid = os.fork() if False and (islinux or isfreebsd) else -1 + pid = os.fork() if False and (islinux or isbsd) else -1 if pid <= 0: app = Application(args) app.setWindowIcon(QIcon(I('viewer.png'))) diff --git a/src/calibre/gui2/viewer/main.ui b/src/calibre/gui2/viewer/main.ui index d470a386c6..3137ad2e07 100644 --- a/src/calibre/gui2/viewer/main.ui +++ b/src/calibre/gui2/viewer/main.ui @@ -33,24 +33,21 @@ QFrame::Raised - - - - + Qt::Vertical - + Qt::Horizontal - + QFrame::StyledPanel @@ -91,6 +88,9 @@ + + + @@ -108,7 +108,7 @@ - Qt::LeftToolBarArea + LeftToolBarArea false @@ -121,7 +121,7 @@ - + @@ -130,13 +130,13 @@ - + - Qt::TopToolBarArea + TopToolBarArea false @@ -316,6 +316,12 @@ QWidget

    QtWebKit/QWebView
    + + DocumentView + QWidget +
    calibre/gui2/viewer/documentview.h
    + 1 +
    diff --git a/src/calibre/gui2/widgets.py b/src/calibre/gui2/widgets.py index f6c4cce3ef..dd8d876005 100644 --- a/src/calibre/gui2/widgets.py +++ b/src/calibre/gui2/widgets.py @@ -3,7 +3,7 @@ __copyright__ = '2008, Kovid Goyal ' ''' Miscellaneous widgets used in the GUI ''' -import re, os, traceback +import re, traceback from PyQt4.Qt import QIcon, QFont, QLabel, QListWidget, QAction, \ QListWidgetItem, QTextCharFormat, QApplication, \ @@ -11,17 +11,18 @@ from PyQt4.Qt import QIcon, QFont, QLabel, QListWidget, QAction, \ QPixmap, QSplitterHandle, QToolButton, \ QAbstractListModel, QVariant, Qt, SIGNAL, pyqtSignal, \ QRegExp, QSettings, QSize, QSplitter, \ - QPainter, QLineEdit, QComboBox, QPen, \ + QPainter, QLineEdit, QComboBox, QPen, QGraphicsScene, \ QMenu, QStringListModel, QCompleter, QStringList, \ - QTimer, QRect + QTimer, QRect, QFontDatabase, QGraphicsView from calibre.gui2 import NONE, error_dialog, pixmap_to_data, gprefs from calibre.gui2.filename_pattern_ui import Ui_Form from calibre import fit_image from calibre.ebooks import BOOK_EXTENSIONS -from calibre.ebooks.metadata.meta import metadata_from_filename from calibre.utils.config import prefs, XMLConfig, tweaks from calibre.gui2.progress_indicator import ProgressIndicator as _ProgressIndicator +from calibre.gui2.dnd import dnd_has_image, dnd_get_image, dnd_get_files, \ + IMAGE_EXTENSIONS, dnd_has_extension, DownloadDialog history = XMLConfig('history') @@ -93,9 +94,10 @@ class FilenamePattern(QWidget, Ui_Form): self.re.setCurrentIndex(0) def do_test(self): + from calibre.ebooks.metadata.meta import metadata_from_filename try: pat = self.pattern() - except Exception, err: + except Exception as err: error_dialog(self, _('Invalid regular expression'), _('Invalid regular expression: %s')%err).exec_() return @@ -119,6 +121,12 @@ class FilenamePattern(QWidget, Ui_Form): else: self.series_index.setText(_('No match')) + if mi.publisher: + self.publisher.setText(mi.publisher) + + if mi.pubdate: + self.pubdate.setText(mi.pubdate.strftime('%Y-%m-%d')) + self.isbn.setText(_('No match') if mi.isbn is None else str(mi.isbn)) @@ -141,36 +149,35 @@ class FilenamePattern(QWidget, Ui_Form): return pat -IMAGE_EXTENSIONS = ['jpg', 'jpeg', 'gif', 'png', 'bmp'] - class FormatList(QListWidget): DROPABBLE_EXTENSIONS = BOOK_EXTENSIONS formats_dropped = pyqtSignal(object, object) delete_format = pyqtSignal() - @classmethod - def paths_from_event(cls, event): - ''' - Accept a drop event and return a list of paths that can be read from - and represent files with extensions. - ''' - if event.mimeData().hasFormat('text/uri-list'): - urls = [unicode(u.toLocalFile()) for u in event.mimeData().urls()] - urls = [u for u in urls if os.path.splitext(u)[1] and os.access(u, os.R_OK)] - return [u for u in urls if os.path.splitext(u)[1][1:].lower() in cls.DROPABBLE_EXTENSIONS] - def dragEnterEvent(self, event): - if int(event.possibleActions() & Qt.CopyAction) + \ - int(event.possibleActions() & Qt.MoveAction) == 0: - return - paths = self.paths_from_event(event) - if paths: + md = event.mimeData() + if dnd_has_extension(md, self.DROPABBLE_EXTENSIONS): event.acceptProposedAction() def dropEvent(self, event): - paths = self.paths_from_event(event) event.setDropAction(Qt.CopyAction) - self.formats_dropped.emit(event, paths) + md = event.mimeData() + # Now look for ebook files + urls, filenames = dnd_get_files(md, self.DROPABBLE_EXTENSIONS) + if not urls: + # Nothing found + return + + if not filenames: + # Local files + self.formats_dropped.emit(event, urls) + else: + # Remote files, use the first file + d = DownloadDialog(urls[0], filenames[0], self) + d.start_download() + if d.err is None: + self.formats_dropped.emit(event, [d.fpath]) + def dragMoveEvent(self, event): event.acceptProposedAction() @@ -181,8 +188,81 @@ class FormatList(QListWidget): else: return QListWidget.keyPressEvent(self, event) +class ImageDropMixin(object): # {{{ + ''' + Adds support for dropping images onto widgets and a context menu for + copy/pasting images. + ''' + DROPABBLE_EXTENSIONS = IMAGE_EXTENSIONS -class ImageView(QWidget): + def __init__(self): + self.setAcceptDrops(True) + + def dragEnterEvent(self, event): + md = event.mimeData() + if dnd_has_extension(md, self.DROPABBLE_EXTENSIONS) or \ + dnd_has_image(md): + event.acceptProposedAction() + + def dropEvent(self, event): + event.setDropAction(Qt.CopyAction) + md = event.mimeData() + + x, y = dnd_get_image(md) + if x is not None: + # We have an image, set cover + event.accept() + if y is None: + # Local image + self.handle_image_drop(x) + else: + # Remote files, use the first file + d = DownloadDialog(x, y, self) + d.start_download() + if d.err is None: + pmap = QPixmap() + pmap.loadFromData(open(d.fpath, 'rb').read()) + if not pmap.isNull(): + self.handle_image_drop(pmap) + + def handle_image_drop(self, pmap): + self.set_pixmap(pmap) + self.cover_changed.emit(pixmap_to_data(pmap)) + + def dragMoveEvent(self, event): + event.acceptProposedAction() + + def get_pixmap(self): + return self.pixmap() + + def set_pixmap(self, pmap): + self.setPixmap(pmap) + + def contextMenuEvent(self, ev): + cm = QMenu(self) + paste = cm.addAction(_('Paste Cover')) + copy = cm.addAction(_('Copy Cover')) + if not QApplication.instance().clipboard().mimeData().hasImage(): + paste.setEnabled(False) + copy.triggered.connect(self.copy_to_clipboard) + paste.triggered.connect(self.paste_from_clipboard) + cm.exec_(ev.globalPos()) + + def copy_to_clipboard(self): + QApplication.instance().clipboard().setPixmap(self.get_pixmap()) + + def paste_from_clipboard(self): + cb = QApplication.instance().clipboard() + pmap = cb.pixmap() + if pmap.isNull() and cb.supportsSelection(): + pmap = cb.pixmap(cb.Selection) + if not pmap.isNull(): + self.set_pixmap(pmap) + self.cover_changed.emit( + pixmap_to_data(pmap)) +# }}} + +class ImageView(QWidget, ImageDropMixin): BORDER_WIDTH = 1 cover_changed = pyqtSignal(object) @@ -191,47 +271,9 @@ class ImageView(QWidget): QWidget.__init__(self, parent) self._pixmap = QPixmap(self) self.setMinimumSize(QSize(150, 200)) - self.setAcceptDrops(True) + ImageDropMixin.__init__(self) self.draw_border = True - # Drag 'n drop {{{ - DROPABBLE_EXTENSIONS = IMAGE_EXTENSIONS - - @classmethod - def paths_from_event(cls, event): - ''' - Accept a drop event and return a list of paths that can be read from - and represent files with extensions. - ''' - if event.mimeData().hasFormat('text/uri-list'): - urls = [unicode(u.toLocalFile()) for u in event.mimeData().urls()] - urls = [u for u in urls if os.path.splitext(u)[1] and os.access(u, os.R_OK)] - return [u for u in urls if os.path.splitext(u)[1][1:].lower() in cls.DROPABBLE_EXTENSIONS] - - def dragEnterEvent(self, event): - if int(event.possibleActions() & Qt.CopyAction) + \ - int(event.possibleActions() & Qt.MoveAction) == 0: - return - paths = self.paths_from_event(event) - if paths: - event.acceptProposedAction() - - def dropEvent(self, event): - paths = self.paths_from_event(event) - event.setDropAction(Qt.CopyAction) - for path in paths: - pmap = QPixmap() - pmap.load(path) - if not pmap.isNull(): - self.setPixmap(pmap) - event.accept() - self.cover_changed.emit(open(path, 'rb').read()) - break - - def dragMoveEvent(self, event): - event.acceptProposedAction() - # }}} - def setPixmap(self, pixmap): if not isinstance(pixmap, QPixmap): raise TypeError('Must use a QPixmap') @@ -270,36 +312,26 @@ class ImageView(QWidget): p.setPen(pen) if self.draw_border: p.drawRect(target) + #p.drawRect(self.rect()) p.end() +class CoverView(QGraphicsView, ImageDropMixin): - # Clipboard copy/paste # {{{ - def contextMenuEvent(self, ev): - cm = QMenu(self) - copy = cm.addAction(_('Copy Image')) - paste = cm.addAction(_('Paste Image')) - if not QApplication.instance().clipboard().mimeData().hasImage(): - paste.setEnabled(False) - copy.triggered.connect(self.copy_to_clipboard) - paste.triggered.connect(self.paste_from_clipboard) - cm.exec_(ev.globalPos()) - - def copy_to_clipboard(self): - QApplication.instance().clipboard().setPixmap(self.pixmap()) - - def paste_from_clipboard(self): - cb = QApplication.instance().clipboard() - pmap = cb.pixmap() - if pmap.isNull() and cb.supportsSelection(): - pmap = cb.pixmap(cb.Selection) - if not pmap.isNull(): - self.setPixmap(pmap) - self.cover_changed.emit( - pixmap_to_data(pmap)) - # }}} + cover_changed = pyqtSignal(object) + def __init__(self, *args, **kwargs): + QGraphicsView.__init__(self, *args, **kwargs) + ImageDropMixin.__init__(self) + def get_pixmap(self): + for item in self.scene.items(): + if hasattr(item, 'pixmap'): + return item.pixmap() + def set_pixmap(self, pmap): + self.scene = QGraphicsScene() + self.scene.addPixmap(pmap) + self.setScene(self.scene) class FontFamilyModel(QAbstractListModel): @@ -312,8 +344,12 @@ class FontFamilyModel(QAbstractListModel): self.families = [] print 'WARNING: Could not load fonts' traceback.print_exc() + # Restrict to Qt families as Qt tends to crash + qt_families = set([unicode(x) for x in QFontDatabase().families()]) + self.families = list(qt_families.intersection(set(self.families))) self.families.sort() self.families[:0] = [_('None')] + self.font = QFont('sansserif') def rowCount(self, *args): return len(self.families) @@ -326,10 +362,11 @@ class FontFamilyModel(QAbstractListModel): return NONE if role == Qt.DisplayRole: return QVariant(family) - if False and role == Qt.FontRole: - # Causes a Qt crash with some fonts - # so disabled. - return QVariant(QFont(family)) + if role == Qt.FontRole: + # If a user chooses some non standard font as the interface font, + # rendering some font names causes Qt to crash, so return what is + # hopefully a "safe" font + return QVariant(self.font) return NONE def index_of(self, family): @@ -796,7 +833,7 @@ class PythonHighlighter(QSyntaxHighlighter): Config["tabwidth"] = settings.value("tabwidth", QVariant(4)).toInt()[0] Config["fontfamily"] = settings.value("fontfamily", - QVariant("Bitstream Vera Sans Mono")).toString() + QVariant("monospace")).toString() Config["fontsize"] = settings.value("fontsize", QVariant(10)).toInt()[0] for name, color, bold, italic in ( diff --git a/src/calibre/gui2/wizard/__init__.py b/src/calibre/gui2/wizard/__init__.py index 5f9f1828fa..5875373dfe 100644 --- a/src/calibre/gui2/wizard/__init__.py +++ b/src/calibre/gui2/wizard/__init__.py @@ -51,7 +51,7 @@ class Device(object): @classmethod def set_output_format(cls): if cls.output_format: - prefs.set('output_format', cls.output_format) + prefs.set('output_format', cls.output_format.lower()) @classmethod def commit(cls): @@ -164,7 +164,7 @@ class Sony900(Sony505): class Nook(Sony505): id = 'nook' - name = 'Nook' + name = 'Nook and Nook Simple Reader' manufacturer = 'Barnes & Noble' output_profile = 'nook' @@ -435,7 +435,7 @@ class DevicePage(QWizardPage, DeviceUI): self.registerField("device", self.device_view) def initializePage(self): - self.label.setText(_('Choose you e-book device. If your device is' + self.label.setText(_('Choose your e-book device. If your device is' ' not in the list, choose a "%s" device.')%Device.manufacturer) self.man_model = ManufacturerModel() self.manufacturer_view.setModel(self.man_model) @@ -565,7 +565,7 @@ def move_library(oldloc, newloc, parent, callback_on_complete): # Try to load existing library at new location try: LibraryDatabase2(newloc) - except Exception, err: + except Exception as err: det = traceback.format_exc() error_dialog(parent, _('Invalid database'), _('

    An invalid library already exists at ' @@ -577,7 +577,7 @@ def move_library(oldloc, newloc, parent, callback_on_complete): else: callback(newloc) return - except Exception, err: + except Exception as err: det = traceback.format_exc() error_dialog(parent, _('Could not move library'), unicode(err), det, show=True) @@ -633,8 +633,8 @@ class LibraryPage(QWizardPage, LibraryUI): try: lang = prefs['language'].lower()[:2] metadata_plugins = { - 'zh' : ('Douban Books', 'Douban.com covers'), - 'fr' : ('Nicebooks', 'Nicebooks covers'), + 'zh' : ('Douban Books',), + 'fr' : ('Nicebooks',), }.get(lang, []) from calibre.customize.ui import enable_plugin for name in metadata_plugins: diff --git a/src/calibre/gui2/wizard/finish.ui b/src/calibre/gui2/wizard/finish.ui index d637aa350a..28972d864e 100644 --- a/src/calibre/gui2/wizard/finish.ui +++ b/src/calibre/gui2/wizard/finish.ui @@ -62,7 +62,7 @@ - <h2>User Manual</h2>A User Manual is also available <a href="http://calibre-ebook.com/user_manual">online</a>. + <h2>User Manual</h2>A User Manual is also available <a href="http://manual.calibre-ebook.com">online</a>. true diff --git a/src/calibre/gui2/wizard/send_email.py b/src/calibre/gui2/wizard/send_email.py index 5785f52276..4337e558eb 100644 --- a/src/calibre/gui2/wizard/send_email.py +++ b/src/calibre/gui2/wizard/send_email.py @@ -46,6 +46,64 @@ class TestEmail(QDialog, TE_Dialog): finally: self.test_button.setEnabled(True) +class RelaySetup(QDialog): + + def __init__(self, service, parent): + QDialog.__init__(self, parent) + + self.l = l = QGridLayout() + self.setLayout(l) + self.bb = bb = QDialogButtonBox(QDialogButtonBox.Ok|QDialogButtonBox.Cancel) + bb.accepted.connect(self.accept) + bb.rejected.connect(self.reject) + self.tl = QLabel(('

    '+_('Setup sending email using') + + ' {name}

    ' + + _('If you don\'t have an account, you can sign up for a free {name} email ' + 'account at http://{url}. {extra}')).format( + **service)) + l.addWidget(self.tl, 0, 0, 3, 0) + self.tl.setWordWrap(True) + self.tl.setOpenExternalLinks(True) + for name, label in ( + ['from_', _('Your %s &email address:')], + ['username', _('Your %s &username:')], + ['password', _('Your %s &password:')], + ): + la = QLabel(label%service['name']) + le = QLineEdit(self) + setattr(self, name, le) + setattr(self, name+'_label', la) + r = l.rowCount() + l.addWidget(la, r, 0) + l.addWidget(le, r, 1) + la.setBuddy(le) + if name == 'password': + self.ptoggle = QCheckBox(_('&Show password'), self) + l.addWidget(self.ptoggle, r, 2) + self.ptoggle.stateChanged.connect( + lambda s: self.password.setEchoMode(self.password.Normal if s + == Qt.Checked else self.password.Password)) + self.username.setText(service['username']) + self.password.setEchoMode(self.password.Password) + self.bl = QLabel('

    ' + _( + 'If you plan to use email to send books to your Kindle, remember to' + ' add the your %s email address to the allowed email addresses in your ' + 'Amazon.com Kindle management page.')%service['name']) + self.bl.setWordWrap(True) + l.addWidget(self.bl, l.rowCount(), 0, 3, 0) + l.addWidget(bb, l.rowCount(), 0, 3, 0) + self.setWindowTitle(_('Setup') + ' ' + service['name']) + self.resize(self.sizeHint()) + self.service = service + + def accept(self): + un = unicode(self.username.text()) + if self.service.get('at_in_username', False) and '@' not in un: + return error_dialog(self, _('Incorrect username'), + _('%s needs the full email address as your username') % + self.service['name'], show=True) + QDialog.accept(self) + class SendEmail(QWidget, Ui_Form): @@ -92,7 +150,8 @@ class SendEmail(QWidget, Ui_Form): pa = self.preferred_to_address() to_set = pa is not None if self.set_email_settings(to_set): - if question_dialog(self, _('OK to proceed?'), + opts = smtp_prefs().parse() + if not opts.relay_password or question_dialog(self, _('OK to proceed?'), _('This will display your email password on the screen' '. Is it OK to proceed?'), show_copy_button=False): TestEmail(pa, self).exec_() @@ -128,7 +187,8 @@ class SendEmail(QWidget, Ui_Form): 'port': 587, 'username': '@gmail.com', 'url': 'www.gmail.com', - 'extra': '' + 'extra': '', + 'at_in_username': True, }, 'hotmail': { 'name': 'Hotmail', @@ -137,55 +197,15 @@ class SendEmail(QWidget, Ui_Form): 'username': '', 'url': 'www.hotmail.com', 'extra': _('If you are setting up a new' - ' hotmail account, you must log in to it ' - ' once before you will be able to send mails.'), + ' hotmail account, Microsoft requires that you ' + ' verify your account periodically, before it' + ' will let calibre send email. In this case, I' + ' strongly suggest you setup a free gmail account' + ' instead.'), + 'at_in_username': True, } }[service] - d = QDialog(self) - l = QGridLayout() - d.setLayout(l) - bb = QDialogButtonBox(QDialogButtonBox.Ok|QDialogButtonBox.Cancel) - bb.accepted.connect(d.accept) - bb.rejected.connect(d.reject) - d.tl = QLabel(('

    '+_('Setup sending email using') + - ' {name}

    ' + - _('If you don\'t have an account, you can sign up for a free {name} email ' - 'account at http://{url}. {extra}')).format( - **service)) - l.addWidget(d.tl, 0, 0, 3, 0) - d.tl.setWordWrap(True) - d.tl.setOpenExternalLinks(True) - for name, label in ( - ['from_', _('Your %s &email address:')], - ['username', _('Your %s &username:')], - ['password', _('Your %s &password:')], - ): - la = QLabel(label%service['name']) - le = QLineEdit(d) - setattr(d, name, le) - setattr(d, name+'_label', la) - r = l.rowCount() - l.addWidget(la, r, 0) - l.addWidget(le, r, 1) - la.setBuddy(le) - if name == 'password': - d.ptoggle = QCheckBox(_('&Show password'), d) - l.addWidget(d.ptoggle, r, 2) - d.ptoggle.stateChanged.connect( - lambda s: d.password.setEchoMode(d.password.Normal if s - == Qt.Checked else d.password.Password)) - d.username.setText(service['username']) - d.password.setEchoMode(d.password.Password) - d.bl = QLabel('

    ' + _( - 'If you plan to use email to send books to your Kindle, remember to' - ' add the your %s email address to the allowed email addresses in your ' - 'Amazon.com Kindle management page.')%service['name']) - d.bl.setWordWrap(True) - l.addWidget(d.bl, l.rowCount(), 0, 3, 0) - l.addWidget(bb, l.rowCount(), 0, 3, 0) - d.setWindowTitle(_('Setup') + ' ' + service['name']) - d.resize(d.sizeHint()) - bb.setVisible(True) + d = RelaySetup(service, self) if d.exec_() != d.Accepted: return self.relay_username.setText(d.username.text()) @@ -204,19 +224,32 @@ class SendEmail(QWidget, Ui_Form): username = unicode(self.relay_username.text()).strip() password = unicode(self.relay_password.text()).strip() host = unicode(self.relay_host.text()).strip() - if host and not (username and password): - error_dialog(self, _('Bad configuration'), - _('You must set the username and password for ' - 'the mail server.')).exec_() - return False + enc_method = ('TLS' if self.relay_tls.isChecked() else 'SSL' + if self.relay_ssl.isChecked() else 'NONE') + if host: + # Validate input + if ((username and not password) or (not username and password)): + error_dialog(self, _('Bad configuration'), + _('You must either set both the username and password for ' + 'the mail server or no username and no password at all.')).exec_() + return False + if not username and not password and enc_method != 'NONE': + error_dialog(self, _('Bad configuration'), + _('Please enter a username and password or set' + ' encryption to None ')).exec_() + return False + if not (username and password) and not question_dialog(self, + _('Are you sure?'), + _('No username and password set for mailserver. Most ' + ' mailservers need a username and password. Are you sure?')): + return False conf = smtp_prefs() conf.set('from_', from_) conf.set('relay_host', host if host else None) conf.set('relay_port', self.relay_port.value()) conf.set('relay_username', username if username else None) conf.set('relay_password', hexlify(password)) - conf.set('encryption', 'TLS' if self.relay_tls.isChecked() else 'SSL' - if self.relay_ssl.isChecked() else 'NONE') + conf.set('encryption', enc_method) return True diff --git a/src/calibre/library/add_to_library.py b/src/calibre/library/add_to_library.py index 8451241e3c..a453423a3c 100644 --- a/src/calibre/library/add_to_library.py +++ b/src/calibre/library/add_to_library.py @@ -8,7 +8,6 @@ __docformat__ = 'restructuredtext en' import os from hashlib import sha1 -from calibre.constants import filesystem_encoding from calibre.ebooks import BOOK_EXTENSIONS def find_folders_under(root, db, add_root=True, # {{{ @@ -17,21 +16,13 @@ def find_folders_under(root, db, add_root=True, # {{{ Find all folders under the specified root path, ignoring any folders under the library path of db - root must be a bytestring in filesystem_encoding - If follow_links is True, follow symbolic links. WARNING; this can lead to infinite recursion. cancel_callback must be a no argument callable that returns True to cancel the search ''' - assert not isinstance(root, unicode) # root must be in filesystem encoding lp = db.library_path - if isinstance(lp, unicode): - try: - lp = lp.encode(filesystem_encoding) - except: - lp = None if lp: lp = os.path.abspath(lp) diff --git a/src/calibre/library/caches.py b/src/calibre/library/caches.py index e626d446d2..470bbcdfa8 100644 --- a/src/calibre/library/caches.py +++ b/src/calibre/library/caches.py @@ -7,7 +7,7 @@ __copyright__ = '2010, Kovid Goyal ' __docformat__ = 'restructuredtext en' import re, itertools, time, traceback -from itertools import repeat +from itertools import repeat, izip, imap from datetime import timedelta from threading import Thread @@ -15,7 +15,7 @@ from calibre.utils.config import tweaks, prefs from calibre.utils.date import parse_date, now, UNDEFINED_DATE from calibre.utils.search_query_parser import SearchQueryParser from calibre.utils.pyparsing import ParseException -from calibre.ebooks.metadata import title_sort +from calibre.ebooks.metadata import title_sort, author_to_author_sort from calibre.ebooks.metadata.opf2 import metadata_to_opf from calibre import prints @@ -121,11 +121,24 @@ CONTAINS_MATCH = 0 EQUALS_MATCH = 1 REGEXP_MATCH = 2 def _match(query, value, matchkind): + if query.startswith('..'): + query = query[1:] + sq = query[1:] + internal_match_ok = True + else: + internal_match_ok = False for t in value: t = icu_lower(t) try: ### ignore regexp exceptions, required because search-ahead tries before typing is finished if (matchkind == EQUALS_MATCH): - if query[0] == '.': + if internal_match_ok: + if query == t: + return True + comps = [c.strip() for c in t.split('.') if c.strip()] + for comp in comps: + if sq == comp: + return True + elif query[0] == '.': if t.startswith(query[1:]): ql = len(query) - 1 if (len(t) == ql) or (t[ql:ql+1] == '.'): @@ -139,6 +152,22 @@ def _match(query, value, matchkind): pass return False +def force_to_bool(val): + if isinstance(val, (str, unicode)): + try: + val = icu_lower(val) + if not val: + val = None + elif val in [_('yes'), _('checked'), 'true']: + val = True + elif val in [_('no'), _('unchecked'), 'false']: + val = False + else: + val = bool(int(val)) + except: + val = None + return val + class CacheRow(list): # {{{ def __init__(self, db, composites, val): @@ -162,7 +191,8 @@ class CacheRow(list): # {{{ if is_comp: id = list.__getitem__(self, 0) self._must_do = False - mi = self.db.get_metadata(id, index_is_id=True) + mi = self.db.get_metadata(id, index_is_id=True, + get_user_categories=False) for c in self._composites: self[c] = mi.get(self._composites[c]) return list.__getitem__(self, col) @@ -170,6 +200,11 @@ class CacheRow(list): # {{{ def __getslice__(self, i, j): return self.__getitem__(slice(i, j)) + def refresh_composites(self): + for c in self._composites: + self[c] = None + self._must_do = True + # }}} class ResultCache(SearchQueryParser): # {{{ @@ -189,6 +224,7 @@ class ResultCache(SearchQueryParser): # {{{ self.first_sort = True self.search_restriction = '' self.search_restriction_book_count = 0 + self.marked_ids_dict = {} self.field_metadata = field_metadata self.all_search_locations = field_metadata.get_search_terms() SearchQueryParser.__init__(self, self.all_search_locations, optimize=True) @@ -297,14 +333,20 @@ class ResultCache(SearchQueryParser): # {{{ for id_ in candidates: item = self._data[id_] if item is None: continue - if item[loc] is None or item[loc] <= UNDEFINED_DATE: + v = item[loc] + if isinstance(v, (str, unicode)): + v = parse_date(v) + if v is None or v <= UNDEFINED_DATE: matches.add(item[0]) return matches if query == 'true': for id_ in candidates: item = self._data[id_] if item is None: continue - if item[loc] is not None and item[loc] > UNDEFINED_DATE: + v = item[loc] + if isinstance(v, (str, unicode)): + v = parse_date(v) + if v is not None and v > UNDEFINED_DATE: matches.add(item[0]) return matches @@ -344,106 +386,211 @@ class ResultCache(SearchQueryParser): # {{{ for id_ in candidates: item = self._data[id_] if item is None or item[loc] is None: continue - if relop(item[loc], qd, field_count): + v = item[loc] + if isinstance(v, (str, unicode)): + v = parse_date(v) + if relop(v, qd, field_count): matches.add(item[0]) return matches def build_numeric_relop_dict(self): self.numeric_search_relops = { '=':[1, lambda r, q: r == q], - '>':[1, lambda r, q: r > q], - '<':[1, lambda r, q: r < q], + '>':[1, lambda r, q: r is not None and r > q], + '<':[1, lambda r, q: r is not None and r < q], '!=':[2, lambda r, q: r != q], - '>=':[2, lambda r, q: r >= q], - '<=':[2, lambda r, q: r <= q] + '>=':[2, lambda r, q: r is not None and r >= q], + '<=':[2, lambda r, q: r is not None and r <= q] } def get_numeric_matches(self, location, query, candidates, val_func = None): matches = set([]) if len(query) == 0: return matches - if query == 'false': - query = '0' - elif query == 'true': - query = '!=0' - relop = None - for k in self.numeric_search_relops.keys(): - if query.startswith(k): - (p, relop) = self.numeric_search_relops[k] - query = query[p:] - if relop is None: - (p, relop) = self.numeric_search_relops['='] if val_func is None: loc = self.field_metadata[location]['rec_index'] val_func = lambda item, loc=loc: item[loc] - + q = '' + cast = adjust = lambda x: x dt = self.field_metadata[location]['datatype'] - if dt == 'int': - cast = (lambda x: int (x)) - adjust = lambda x: x - elif dt == 'rating': - cast = (lambda x: int (x)) - adjust = lambda x: x/2 - elif dt == 'float': - cast = lambda x : float (x) - adjust = lambda x: x - else: # count operation - cast = (lambda x: int (x)) - adjust = lambda x: x - if len(query) > 1: - mult = query[-1:].lower() - mult = {'k':1024.,'m': 1024.**2, 'g': 1024.**3}.get(mult, 1.0) - if mult != 1.0: - query = query[:-1] + if query == 'false': + if dt == 'rating' or location == 'cover': + relop = lambda x,y: not bool(x) + else: + relop = lambda x,y: x is None + elif query == 'true': + if dt == 'rating' or location == 'cover': + relop = lambda x,y: bool(x) + else: + relop = lambda x,y: x is not None else: - mult = 1.0 - try: - q = cast(query) * mult - except: - return matches + relop = None + for k in self.numeric_search_relops.keys(): + if query.startswith(k): + (p, relop) = self.numeric_search_relops[k] + query = query[p:] + if relop is None: + (p, relop) = self.numeric_search_relops['='] + + if dt == 'int': + cast = lambda x: int (x) + elif dt == 'rating': + cast = lambda x: 0 if x is None else int (x) + adjust = lambda x: x/2 + elif dt in ('float', 'composite'): + cast = lambda x : float (x) + else: # count operation + cast = (lambda x: int (x)) + + if len(query) > 1: + mult = query[-1:].lower() + mult = {'k':1024.,'m': 1024.**2, 'g': 1024.**3}.get(mult, 1.0) + if mult != 1.0: + query = query[:-1] + else: + mult = 1.0 + try: + q = cast(query) * mult + except: + raise ParseException(query, len(query), + 'Non-numeric value in query', self) for id_ in candidates: item = self._data[id_] if item is None: continue - v = val_func(item) - if not v: - i = 0 - else: - i = adjust(v) - if relop(i, q): + try: + v = cast(val_func(item)) + except: + v = None + if v: + v = adjust(v) + if relop(v, q): matches.add(item[0]) return matches def get_user_category_matches(self, location, query, candidates): - res = set([]) - if self.db_prefs is None: - return res + matches = set([]) + if self.db_prefs is None or len(query) < 2: + return matches user_cats = self.db_prefs.get('user_categories', []) c = set(candidates) - l = location.rfind('.') - if l > 0: - alt_loc = location[0:l] - alt_item = location[l+1:] + + if query.startswith('.'): + check_subcats = True + query = query[1:] else: - alt_loc = None + check_subcats = False + for key in user_cats: - if key == location or key.startswith(location + '.'): + if key == location or (check_subcats and key.startswith(location + '.')): for (item, category, ign) in user_cats[key]: s = self.get_matches(category, '=' + item, candidates=c) c -= s - res |= s - elif key == alt_loc: - for (item, category, ign) in user_cats[key]: - if item == alt_item: - s = self.get_matches(category, '=' + item, candidates=c) - c -= s - res |= s + matches |= s if query == 'false': - return candidates - res - return res + return candidates - matches + return matches + + def get_keypair_matches(self, location, query, candidates): + matches = set([]) + if query.find(':') >= 0: + q = [q.strip() for q in query.split(':')] + if len(q) != 2: + raise ParseException(query, len(query), + 'Invalid query format for colon-separated search', self) + (keyq, valq) = q + keyq_mkind, keyq = self._matchkind(keyq) + valq_mkind, valq = self._matchkind(valq) + else: + keyq = keyq_mkind = '' + valq_mkind, valq = self._matchkind(query) + + loc = self.field_metadata[location]['rec_index'] + split_char = self.field_metadata[location]['is_multiple'] + for id_ in candidates: + item = self._data[id_] + if item is None: + continue + + if item[loc] is None: + if valq == 'false': + matches.add(id_) + continue + + pairs = [p.strip() for p in item[loc].split(split_char)] + for pair in pairs: + parts = pair.split(':') + if len(parts) != 2: + continue + k = parts[:1] + v = parts[1:] + if keyq and not _match(keyq, k, keyq_mkind): + continue + if valq: + if valq == 'true': + if not v: + continue + elif valq == 'false': + if v: + continue + elif not _match(valq, v, valq_mkind): + continue + matches.add(id_) + return matches + + def _matchkind(self, query): + matchkind = CONTAINS_MATCH + if (len(query) > 1): + if query.startswith('\\'): + query = query[1:] + elif query.startswith('='): + matchkind = EQUALS_MATCH + query = query[1:] + elif query.startswith('~'): + matchkind = REGEXP_MATCH + query = query[1:] + + if matchkind != REGEXP_MATCH: + # leave case in regexps because it can be significant e.g. \S \W \D + query = icu_lower(query) + return matchkind, query + + def get_bool_matches(self, location, query, candidates): + bools_are_tristate = self.db_prefs.get('bools_are_tristate') + loc = self.field_metadata[location]['rec_index'] + matches = set() + query = icu_lower(query) + if query not in (_('no'), _('unchecked'), '_no', 'false', + _('yes'), _('checked'), '_yes', 'true', + _('empty'), _('blank'), '_empty'): + raise ParseException(_('Invalid boolean query "{0}"').format(query)) + for id_ in candidates: + item = self._data[id_] + if item is None: + continue + + val = force_to_bool(item[loc]) + if not bools_are_tristate: + if val is None or not val: # item is None or set to false + if query in [_('no'), _('unchecked'), '_no', 'false']: + matches.add(item[0]) + else: # item is explicitly set to true + if query in [_('yes'), _('checked'), '_yes', 'true']: + matches.add(item[0]) + else: + if val is None: + if query in [_('empty'), _('blank'), '_empty', 'false']: + matches.add(item[0]) + elif not val: # is not None and false + if query in [_('no'), _('unchecked'), '_no', 'true']: + matches.add(item[0]) + else: # item is not None and true + if query in [_('yes'), _('checked'), '_yes', 'true']: + matches.add(item[0]) + return matches def get_matches(self, location, query, candidates=None, allow_recursion=True): @@ -452,6 +599,8 @@ class ResultCache(SearchQueryParser): # {{{ candidates = self.universal_set() if len(candidates) == 0: return matches + if location not in self.all_search_locations: + return matches if len(location) > 2 and location.startswith('@') and \ location[1:] in self.db_prefs['grouped_search_terms']: @@ -460,6 +609,7 @@ class ResultCache(SearchQueryParser): # {{{ if query and query.strip(): # get metadata key associated with the search term. Eliminates # dealing with plurals and other aliases + original_location = location location = self.field_metadata.search_term_to_field_key(icu_lower(location.strip())) # grouped search terms if isinstance(location, list): @@ -487,20 +637,30 @@ class ResultCache(SearchQueryParser): # {{{ terms.add(l) if terms: for l in terms: - matches |= self.get_matches(l, query, - candidates=candidates, allow_recursion=allow_recursion) + try: + matches |= self.get_matches(l, query, + candidates=candidates, allow_recursion=allow_recursion) + except: + pass return matches if location in self.field_metadata: fm = self.field_metadata[location] # take care of dates special case - if fm['datatype'] == 'datetime': + if fm['datatype'] == 'datetime' or \ + (fm['datatype'] == 'composite' and + fm['display'].get('composite_sort', '') == 'date'): return self.get_dates_matches(location, query.lower(), candidates) # take care of numbers special case - if fm['datatype'] in ('rating', 'int', 'float'): + if fm['datatype'] in ('rating', 'int', 'float') or \ + (fm['datatype'] == 'composite' and + fm['display'].get('composite_sort', '') == 'number'): return self.get_numeric_matches(location, query.lower(), candidates) + if fm['datatype'] == 'bool': + return self.get_bool_matches(location, query, candidates) + # take care of the 'count' operator for is_multiples if fm['is_multiple'] and \ len(query) > 1 and query.startswith('#') and \ @@ -510,24 +670,20 @@ class ResultCache(SearchQueryParser): # {{{ return self.get_numeric_matches(location, query[1:], candidates, val_func=vf) + # special case: colon-separated fields such as identifiers. isbn + # is a special case within the case + if fm.get('is_csp', False): + if location == 'identifiers' and original_location == 'isbn': + return self.get_keypair_matches('identifiers', + '=isbn:'+query, candidates) + return self.get_keypair_matches(location, query, candidates) + # check for user categories if len(location) >= 2 and location.startswith('@'): return self.get_user_category_matches(location[1:], query.lower(), candidates) # everything else, or 'all' matches - matchkind = CONTAINS_MATCH - if (len(query) > 1): - if query.startswith('\\'): - query = query[1:] - elif query.startswith('='): - matchkind = EQUALS_MATCH - query = query[1:] - elif query.startswith('~'): - matchkind = REGEXP_MATCH - query = query[1:] - if matchkind != REGEXP_MATCH: - # leave case in regexps because it can be significant e.g. \S \W \D - query = icu_lower(query) + matchkind, query = self._matchkind(query) if not isinstance(query, unicode): query = query.decode('utf-8') @@ -558,13 +714,13 @@ class ResultCache(SearchQueryParser): # {{{ for i, loc in enumerate(location): location[i] = db_col[loc] - # get the tweak here so that the string lookup and compare aren't in the loop - bools_are_tristate = tweaks['bool_custom_columns_are_tristate'] != 'no' - for loc in location: # location is now an array of field indices if loc == db_col['authors']: ### DB stores authors with commas changed to bars, so change query - q = query.replace(',', '|'); + if matchkind == REGEXP_MATCH: + q = query.replace(',', r'\|'); + else: + q = query.replace(',', '|'); else: q = query @@ -572,35 +728,15 @@ class ResultCache(SearchQueryParser): # {{{ item = self._data[id_] if item is None: continue - if col_datatype[loc] == 'bool': # complexity caused by the two-/three-value tweak - v = item[loc] - if not bools_are_tristate: - if v is None or not v: # item is None or set to false - if q in [_('no'), _('unchecked'), 'false']: - matches.add(item[0]) - else: # item is explicitly set to true - if q in [_('yes'), _('checked'), 'true']: - matches.add(item[0]) - else: - if v is None: - if q in [_('empty'), _('blank'), 'false']: - matches.add(item[0]) - elif not v: # is not None and false - if q in [_('no'), _('unchecked'), 'true']: - matches.add(item[0]) - else: # item is not None and true - if q in [_('yes'), _('checked'), 'true']: - matches.add(item[0]) - continue - if not item[loc]: - if q == 'false': + if q == 'false' and matchkind == CONTAINS_MATCH: matches.add(item[0]) continue # item is empty. No possible matches below - if q == 'false': # Field has something in it, so a false query does not match + if q == 'false'and matchkind == CONTAINS_MATCH: + # Field has something in it, so a false query does not match continue - if q == 'true': + if q == 'true' and matchkind == CONTAINS_MATCH: if isinstance(item[loc], basestring): if item[loc].strip() == '': continue @@ -629,7 +765,7 @@ class ResultCache(SearchQueryParser): # {{{ if loc not in exclude_fields: # time for text matching if is_multiple_cols[loc] is not None: - vals = item[loc].split(is_multiple_cols[loc]) + vals = [v.strip() for v in item[loc].split(is_multiple_cols[loc])] else: vals = [item[loc]] ### make into list to make _match happy if _match(q, vals, matchkind): @@ -652,7 +788,7 @@ class ResultCache(SearchQueryParser): # {{{ else: q = query if search_restriction: - q = u'%s (%s)' % (search_restriction, query) + q = u'(%s) and (%s)' % (search_restriction, query) if not q: if set_restriction_count: self.search_restriction_book_count = len(self._map) @@ -675,6 +811,36 @@ class ResultCache(SearchQueryParser): # {{{ def get_search_restriction_book_count(self): return self.search_restriction_book_count + def set_marked_ids(self, id_dict): + ''' + ids in id_dict are "marked". They can be searched for by + using the search term ``marked:true``. Pass in an empty dictionary or + set to clear marked ids. + + :param id_dict: Either a dictionary mapping ids to values or a set + of ids. In the latter case, the value is set to 'true' for all ids. If + a mapping is provided, then the search can be used to search for + particular values: ``marked:value`` + ''' + if not hasattr(id_dict, 'items'): + # Simple list. Make it a dict of string 'true' + self.marked_ids_dict = dict.fromkeys(id_dict, u'true') + else: + # Ensure that all the items in the dict are text + self.marked_ids_dict = dict(izip(id_dict.iterkeys(), imap(unicode, + id_dict.itervalues()))) + + # Set the values in the cache + marked_col = self.FIELD_MAP['marked'] + for r in self.iterall(): + r[marked_col] = None + + for id_, val in self.marked_ids_dict.iteritems(): + try: + self._data[id_][marked_col] = val + except: + pass + # }}} def remove(self, id): @@ -724,6 +890,7 @@ class ResultCache(SearchQueryParser): # {{{ self._data[id] = CacheRow(db, self.composites, db.conn.get('SELECT * from meta2 WHERE id=?', (id,))[0]) self._data[id].append(db.book_on_device_string(id)) + self._data[id].append(self.marked_ids_dict.get(id, None)) except IndexError: return None try: @@ -740,6 +907,7 @@ class ResultCache(SearchQueryParser): # {{{ self._data[id] = CacheRow(db, self.composites, db.conn.get('SELECT * from meta2 WHERE id=?', (id,))[0]) self._data[id].append(db.book_on_device_string(id)) + self._data[id].append(self.marked_ids_dict.get(id, None)) self._map[0:0] = ids self._map_filtered[0:0] = ids @@ -755,6 +923,7 @@ class ResultCache(SearchQueryParser): # {{{ for item in self._data: if item is not None: item[ondevice_col] = db.book_on_device_string(item[0]) + item.refresh_composites() def refresh(self, db, field=None, ascending=True): temp = db.conn.get('SELECT * FROM meta2') @@ -764,6 +933,15 @@ class ResultCache(SearchQueryParser): # {{{ for item in self._data: if item is not None: item.append(db.book_on_device_string(item[0])) + item.append(None) + + marked_col = self.FIELD_MAP['marked'] + for id_,val in self.marked_ids_dict.iteritems(): + try: + self._data[id_][marked_col] = val + except: + pass + self._map = [i[0] for i in self._data if i is not None] if field is not None: self.sort(field, ascending) @@ -792,7 +970,7 @@ class ResultCache(SearchQueryParser): # {{{ if not fields: fields = [('timestamp', False)] - keyg = SortKeyGenerator(fields, self.field_metadata, self._data) + keyg = SortKeyGenerator(fields, self.field_metadata, self._data, self.db_prefs) self._map.sort(key=keyg) tmap = list(itertools.repeat(False, len(self._data))) @@ -815,9 +993,10 @@ class SortKey(object): class SortKeyGenerator(object): - def __init__(self, fields, field_metadata, data): + def __init__(self, fields, field_metadata, data, db_prefs): from calibre.utils.icu import sort_key self.field_metadata = field_metadata + self.db_prefs = db_prefs self.orders = [1 if x[1] else -1 for x in fields] self.entries = [(x[0], field_metadata[x[0]]) for x in fields] self.library_order = tweaks['title_series_sorting'] == 'library_order' @@ -832,6 +1011,23 @@ class SortKeyGenerator(object): for name, fm in self.entries: dt = fm['datatype'] val = record[fm['rec_index']] + if dt == 'composite': + sb = fm['display'].get('composite_sort', 'text') + if sb == 'date': + try: + val = parse_date(val) + except: + val = UNDEFINED_DATE + dt = 'datetime' + elif sb == 'number': + try: + val = float(val) + except: + val = 0.0 + dt = 'float' + elif sb == 'bool': + val = force_to_bool(val) + dt = 'bool' if dt == 'datetime': if val is None: @@ -851,12 +1047,16 @@ class SortKeyGenerator(object): if val: sep = fm['is_multiple'] if sep: - val = sep.join(sorted(val.split(sep), + if fm['display'].get('is_names', False): + val = sep.join( + [author_to_author_sort(v) for v in val.split(sep)]) + else: + val = sep.join(sorted(val.split(sep), key=self.string_sort_key)) val = self.string_sort_key(val) elif dt == 'bool': - if tweaks['bool_custom_columns_are_tristate'] == 'no': + if not self.db_prefs.get('bools_are_tristate'): val = {True: 1, False: 2, None: 2}.get(val, 2) else: val = {True: 1, False: 2, None: 3}.get(val, 3) diff --git a/src/calibre/library/catalog.py b/src/calibre/library/catalog.py index f3640af4f0..a19534191b 100644 --- a/src/calibre/library/catalog.py +++ b/src/calibre/library/catalog.py @@ -8,6 +8,7 @@ from collections import namedtuple from copy import deepcopy from xml.sax.saxutils import escape from lxml import etree +from types import StringType, UnicodeType from calibre import prints, prepare_string_for_xml, strftime from calibre.constants import preferred_encoding, DEBUG @@ -15,16 +16,18 @@ from calibre.customize import CatalogPlugin from calibre.customize.conversion import OptionRecommendation, DummyReporter from calibre.ebooks.BeautifulSoup import BeautifulSoup, BeautifulStoneSoup, Tag, NavigableString from calibre.ebooks.chardet import substitute_entites -from calibre.ebooks.oeb.base import XHTML_NS +from calibre.library.save_to_disk import preprocess_template from calibre.ptempfile import PersistentTemporaryDirectory +from calibre.utils.bibtex import BibTeX from calibre.utils.config import config_dir from calibre.utils.date import format_date, isoformat, is_date_undefined, now as nowf +from calibre.utils.html2text import html2text from calibre.utils.icu import capitalize from calibre.utils.logging import default_log as log -from calibre.utils.zipfile import ZipFile, ZipInfo from calibre.utils.magick.draw import thumbnail +from calibre.utils.zipfile import ZipFile, ZipInfo -FIELDS = ['all', 'title', 'author_sort', 'authors', 'comments', +FIELDS = ['all', 'title', 'title_sort', 'author_sort', 'authors', 'comments', 'cover', 'formats','id', 'isbn', 'ondevice', 'pubdate', 'publisher', 'rating', 'series_index', 'series', 'size', 'tags', 'timestamp', 'uuid'] @@ -63,7 +66,7 @@ class CSV_XML(CatalogPlugin): # {{{ dest = 'sort_by', action = None, help = _('Output field to sort on.\n' - 'Available fields: author_sort, id, rating, size, timestamp, title.\n' + 'Available fields: author_sort, id, rating, size, timestamp, title_sort\n' "Default: '%default'\n" "Applies to: CSV, XML output formats"))] @@ -73,7 +76,7 @@ class CSV_XML(CatalogPlugin): # {{{ if opts.verbose: opts_dict = vars(opts) - log("%s(): Generating %s" % (self.name,self.fmt)) + log("%s(): Generating %s" % (self.name,self.fmt.upper())) if opts.connected_device['is_device_connected']: log(" connected_device: %s" % opts.connected_device['name']) if opts_dict['search_text']: @@ -123,8 +126,11 @@ class CSV_XML(CatalogPlugin): # {{{ for field in fields: if field.startswith('#'): item = db.get_field(entry['id'],field,index_is_id=True) + elif field == 'title_sort': + item = entry['sort'] else: item = entry[field] + if item is None: outstr.append('""') continue @@ -164,7 +170,7 @@ class CSV_XML(CatalogPlugin): # {{{ item = getattr(E, field.replace('#','_'))(val) record.append(item) - for field in ('id', 'uuid', 'title', 'publisher', 'rating', 'size', + for field in ('id', 'uuid', 'publisher', 'rating', 'size', 'isbn','ondevice'): if field in fields: val = r[field] @@ -175,6 +181,10 @@ class CSV_XML(CatalogPlugin): # {{{ item = getattr(E, field)(val) record.append(item) + if 'title' in fields: + title = E.title(r['title'], sort=r['sort']) + record.append(title) + if 'authors' in fields: aus = E.authors(sort=r['author_sort']) for au in r['authors']: @@ -304,12 +314,6 @@ class BIBTEX(CatalogPlugin): # {{{ def run(self, path_to_output, opts, db, notification=DummyReporter()): - from types import StringType, UnicodeType - - from calibre.library.save_to_disk import preprocess_template - #Bibtex functions - from calibre.utils.bibtex import BibTeX - def create_bibtex_entry(entry, fields, mode, template_citation, bibtexdict, citation_bibtex=True, calibre_files=True): @@ -366,6 +370,11 @@ class BIBTEX(CatalogPlugin): # {{{ #\n removal item = item.replace(u'\r\n',u' ') item = item.replace(u'\n',u' ') + #html to text + try: + item = html2text(item) + except: + log.warn("Failed to convert comments to text") bibtex_entry.append(u'note = "%s"' % bibtexdict.utf8ToBibtex(item)) elif field == 'isbn' : @@ -942,6 +951,7 @@ class EPUB_MOBI(CatalogPlugin): catalog.createDirectoryStructure() catalog.copyResources() catalog.buildSources() + Options managed in gui2.catalog.catalog_epub_mobi.py ''' # A single number creates 'Last x days' only. @@ -4322,6 +4332,8 @@ Author '{0}': ''' Generate description header from template ''' + from calibre.ebooks.oeb.base import XHTML_NS + def generate_html(): args = dict( author=author, @@ -5103,6 +5115,19 @@ Author '{0}': recommendations.append(('book_producer',opts.output_profile, OptionRecommendation.HIGH)) + # If cover exists, use it + try: + search_text = 'title:"%s" author:%s' % ( + opts.catalog_title.replace('"', '\\"'), 'calibre') + matches = db.search(search_text, return_matches=True) + if matches: + cpath = db.cover(matches[0], index_is_id=True, as_path=True) + if cpath and os.path.exists(cpath): + recommendations.append(('cover', cpath, + OptionRecommendation.HIGH)) + except: + pass + # Run ebook-convert from calibre.ebooks.conversion.plumber import Plumber plumber = Plumber(os.path.join(catalog.catalogPath, diff --git a/src/calibre/library/check_library.py b/src/calibre/library/check_library.py index 19ecb97308..6013c76b9c 100644 --- a/src/calibre/library/check_library.py +++ b/src/calibre/library/check_library.py @@ -27,7 +27,7 @@ CHECKS = [('invalid_titles', _('Invalid titles'), True, False), ('extra_titles', _('Extra titles'), True, False), ('invalid_authors', _('Invalid authors'), True, False), ('extra_authors', _('Extra authors'), True, False), - ('missing_formats', _('Missing book formats'), False, False), + ('missing_formats', _('Missing book formats'), False, True), ('extra_formats', _('Extra book formats'), True, False), ('extra_files', _('Unknown files in books'), True, False), ('missing_covers', _('Missing covers files'), False, True), diff --git a/src/calibre/library/cli.py b/src/calibre/library/cli.py index e93be187f9..61e7ec334d 100644 --- a/src/calibre/library/cli.py +++ b/src/calibre/library/cli.py @@ -10,8 +10,7 @@ Command line interface to the calibre database. import sys, os, cStringIO, re from textwrap import TextWrapper -from calibre import terminal_controller, preferred_encoding, prints, \ - isbytestring +from calibre import preferred_encoding, prints, isbytestring from calibre.utils.config import OptionParser, prefs, tweaks from calibre.ebooks.metadata.meta import get_metadata from calibre.library.database2 import LibraryDatabase2 @@ -20,7 +19,8 @@ from calibre.utils.date import isoformat FIELDS = set(['title', 'authors', 'author_sort', 'publisher', 'rating', 'timestamp', 'size', 'tags', 'comments', 'series', 'series_index', - 'formats', 'isbn', 'uuid', 'pubdate', 'cover']) + 'formats', 'isbn', 'uuid', 'pubdate', 'cover', 'last_modified', + 'identifiers']) def send_message(msg=''): prints('Notifying calibre of the change') @@ -52,6 +52,8 @@ def get_db(dbpath, options): def do_list(db, fields, afields, sort_by, ascending, search_text, line_width, separator, prefix, subtitle='Books in the calibre database'): + from calibre.constants import terminal_controller as tc + terminal_controller = tc() if sort_by: db.sort(sort_by, ascending) if search_text: @@ -425,7 +427,7 @@ def do_show_metadata(db, id, as_opf): mi = OPFCreator(os.getcwd(), mi) mi.render(sys.stdout) else: - print unicode(mi).encode(preferred_encoding) + prints(unicode(mi)) def show_metadata_option_parser(): parser = get_parser(_( @@ -1086,6 +1088,9 @@ def command_list_categories(args, dbpath): fields = ['category', 'tag_name', 'count', 'rating'] def do_list(): + from calibre.constants import terminal_controller as tc + terminal_controller = tc() + separator = ' ' widths = list(map(lambda x : 0, fields)) for i in data: diff --git a/src/calibre/library/custom_columns.py b/src/calibre/library/custom_columns.py index 358daf9de6..187d718a39 100644 --- a/src/calibre/library/custom_columns.py +++ b/src/calibre/library/custom_columns.py @@ -76,6 +76,8 @@ class CustomColumns(object): 'num':record[6], 'is_multiple':record[7], } + if data['display'] is None: + data['display'] = {} table, lt = self.custom_table_names(data['num']) if table not in custom_tables or (data['normalized'] and lt not in custom_tables): @@ -117,7 +119,7 @@ class CustomColumns(object): if x is None: return [] if isinstance(x, (str, unicode, bytes)): - x = x.split(',') + x = x.split('&' if d['display'].get('is_names', False) else',') x = [y.strip() for y in x if y.strip()] x = [y.decode(preferred_encoding, 'replace') if not isinstance(y, unicode) else y for y in x] @@ -180,7 +182,7 @@ class CustomColumns(object): else: is_category = False if v['is_multiple']: - is_m = '|' + is_m = ',' if v['datatype'] == 'composite' else '|' else: is_m = None tn = 'custom_column_{0}'.format(v['num']) @@ -188,7 +190,7 @@ class CustomColumns(object): table=tn, column='value', datatype=v['datatype'], colnum=v['num'], name=v['name'], display=v['display'], is_multiple=is_m, is_category=is_category, - is_editable=v['editable']) + is_editable=v['editable'], is_csp=False) def get_custom(self, idx, label=None, num=None, index_is_id=False): if label is not None: @@ -316,7 +318,7 @@ class CustomColumns(object): self.conn.commit() def set_custom_column_metadata(self, num, name=None, label=None, - is_editable=None, display=None): + is_editable=None, display=None, notify=True): changed = False if name is not None: self.conn.execute('UPDATE custom_columns SET name=? WHERE id=?', @@ -338,6 +340,9 @@ class CustomColumns(object): if changed: self.conn.commit() + if notify: + self.notify('metadata', []) + return changed def set_custom_bulk_multiple(self, ids, add=[], remove=[], @@ -482,8 +487,11 @@ class CustomColumns(object): set_val = val if data['is_multiple'] else [val] existing = getter() if not existing: - existing = [] - for x in set(set_val) - set(existing): + existing = set([]) + else: + existing = set(existing) + # preserve the order in set_val + for x in [v for v in set_val if v not in existing]: # normalized types are text and ratings, so we can do this check # to see if we need to re-add the value if not x: @@ -590,7 +598,7 @@ class CustomColumns(object): raise ValueError('%r is not a supported data type'%datatype) normalized = datatype not in ('datetime', 'comments', 'int', 'bool', 'float', 'composite') - is_multiple = is_multiple and datatype in ('text',) + is_multiple = is_multiple and datatype in ('text', 'composite') num = self.conn.execute( ('INSERT INTO ' 'custom_columns(label,name,datatype,is_multiple,editable,display,normalized)' diff --git a/src/calibre/library/database2.py b/src/calibre/library/database2.py index 4be2ba4340..df465c919e 100644 --- a/src/calibre/library/database2.py +++ b/src/calibre/library/database2.py @@ -6,7 +6,8 @@ __docformat__ = 'restructuredtext en' ''' The database used to store ebook metadata ''' -import os, sys, shutil, cStringIO, glob, time, functools, traceback, re, json +import os, sys, shutil, cStringIO, glob, time, functools, traceback, re, \ + json, uuid import threading, random from itertools import repeat from math import ceil @@ -14,7 +15,8 @@ from math import ceil from PyQt4.QtGui import QImage from calibre import prints -from calibre.ebooks.metadata import title_sort, author_to_author_sort +from calibre.ebooks.metadata import (title_sort, author_to_author_sort, + string_to_authors, authors_to_string) from calibre.ebooks.metadata.opf2 import metadata_to_opf from calibre.library.database import LibraryDatabase from calibre.library.field_metadata import FieldMetadata, TagsIcons @@ -23,9 +25,7 @@ from calibre.library.caches import ResultCache from calibre.library.custom_columns import CustomColumns from calibre.library.sqlite import connect, IntegrityError from calibre.library.prefs import DBPrefs -from calibre.ebooks.metadata import string_to_authors, authors_to_string from calibre.ebooks.metadata.book.base import Metadata -from calibre.ebooks.metadata.meta import get_metadata, metadata_from_formats from calibre.constants import preferred_encoding, iswindows, isosx, filesystem_encoding from calibre.ptempfile import PersistentTemporaryFile from calibre.customize.ui import run_plugins_on_import @@ -33,29 +33,31 @@ from calibre import isbytestring from calibre.utils.filenames import ascii_filename from calibre.utils.date import utcnow, now as nowf, utcfromtimestamp from calibre.utils.config import prefs, tweaks, from_json, to_json -from calibre.utils.icu import sort_key +from calibre.utils.icu import sort_key, strcmp from calibre.utils.search_query_parser import saved_searches, set_saved_searches from calibre.ebooks import BOOK_EXTENSIONS, check_ebook_format from calibre.utils.magick.draw import save_cover_data_to from calibre.utils.recycle_bin import delete_file, delete_tree from calibre.utils.formatter_functions import load_user_template_functions - copyfile = os.link if hasattr(os, 'link') else shutil.copyfile class Tag(object): def __init__(self, name, id=None, count=0, state=0, avg=0, sort=None, - tooltip=None, icon=None, category=None, id_set=None): - self.name = name + tooltip=None, icon=None, category=None, id_set=None, + is_editable = True, is_searchable=True, use_sort_as_name=False): + self.name = self.original_name = name self.id = id self.count = count self.state = state - self.is_hierarchical = False - self.is_editable = True - self.id_set = id_set + self.is_hierarchical = '' + self.is_editable = is_editable + self.is_searchable = is_searchable + self.id_set = id_set if id_set is not None else set([]) self.avg_rating = avg/2.0 if avg is not None else 0 self.sort = sort + self.use_sort_as_name = use_sort_as_name if self.avg_rating > 0: if tooltip: tooltip = tooltip + ': ' @@ -94,6 +96,31 @@ class LibraryDatabase2(LibraryDatabase, SchemaUpgrade, CustomColumns): return property(doc=doc, fget=fget, fset=fset) + @dynamic_property + def library_id(self): + doc = ('The UUID for this library. As long as the user only operates' + ' on libraries with calibre, it will be unique') + + def fget(self): + if self._library_id_ is None: + ans = self.conn.get('SELECT uuid FROM library_id', all=False) + if ans is None: + ans = str(uuid.uuid4()) + self.library_id = ans + else: + self._library_id_ = ans + return self._library_id_ + + def fset(self, val): + self._library_id_ = unicode(val) + self.conn.executescript(''' + DELETE FROM library_id; + INSERT INTO library_id (uuid) VALUES ("%s"); + '''%self._library_id_) + self.conn.commit() + + return property(doc=doc, fget=fget, fset=fset) + def connect(self): if 'win32' in sys.platform and len(self.library_path) + 4*self.PATH_LIMIT + 10 > 259: raise ValueError('Path to library too long. Must be less than %d characters.'%(259-4*self.PATH_LIMIT-10)) @@ -119,7 +146,13 @@ class LibraryDatabase2(LibraryDatabase, SchemaUpgrade, CustomColumns): def __init__(self, library_path, row_factory=False, default_prefs=None, read_only=False): + try: + if isbytestring(library_path): + library_path = library_path.decode(filesystem_encoding) + except: + traceback.print_exc() self.field_metadata = FieldMetadata() + self._library_id_ = None # Create the lock to be used to guard access to the metadata writer # queues. This must be an RLock, not a Lock self.dirtied_lock = threading.RLock() @@ -131,8 +164,6 @@ class LibraryDatabase2(LibraryDatabase, SchemaUpgrade, CustomColumns): self.dbpath = os.path.join(library_path, 'metadata.db') self.dbpath = os.environ.get('CALIBRE_OVERRIDE_DATABASE_PATH', self.dbpath) - if isinstance(self.dbpath, unicode) and not iswindows: - self.dbpath = self.dbpath.encode(filesystem_encoding) if read_only and os.path.exists(self.dbpath): # Work on only a copy of metadata.db to ensure that @@ -148,6 +179,8 @@ class LibraryDatabase2(LibraryDatabase, SchemaUpgrade, CustomColumns): self.is_case_sensitive = not iswindows and not isosx and \ not os.path.exists(self.dbpath.replace('metadata.db', 'MeTAdAtA.dB')) SchemaUpgrade.__init__(self) + # Guarantee that the library_id is set + self.library_id # if we are to copy the prefs and structure from some other DB, then # we need to do it before we call initialize_dynamic @@ -178,6 +211,16 @@ class LibraryDatabase2(LibraryDatabase, SchemaUpgrade, CustomColumns): defs = self.prefs.defaults defs['gui_restriction'] = defs['cs_restriction'] = '' defs['categories_using_hierarchy'] = [] + self.column_color_count = 5 + for i in range(1,self.column_color_count+1): + defs['column_color_name_'+str(i)] = '' + defs['column_color_template_'+str(i)] = '' + + # Migrate the bool tristate tweak + defs['bools_are_tristate'] = \ + tweaks.get('bool_custom_columns_are_tristate', 'yes') == 'yes' + if self.prefs.get('bools_are_tristate') is None: + self.prefs.set('bools_are_tristate', defs['bools_are_tristate']) # Migrate saved search and user categories to db preference scheme def migrate_preference(key, default): @@ -293,14 +336,14 @@ class LibraryDatabase2(LibraryDatabase, SchemaUpgrade, CustomColumns): 'sort', 'author_sort', '(SELECT group_concat(format) FROM data WHERE data.book=books.id) formats', - 'isbn', 'path', - 'lccn', 'pubdate', - 'flags', 'uuid', 'has_cover', - ('au_map', 'authors', 'author', 'aum_sortconcat(link.id, authors.name, authors.sort)') + ('au_map', 'authors', 'author', + 'aum_sortconcat(link.id, authors.name, authors.sort)'), + 'last_modified', + '(SELECT identifiers_concat(type, val) FROM identifiers WHERE identifiers.book=books.id) identifiers', ] lines = [] for col in columns: @@ -318,8 +361,8 @@ class LibraryDatabase2(LibraryDatabase, SchemaUpgrade, CustomColumns): self.FIELD_MAP = {'id':0, 'title':1, 'authors':2, 'timestamp':3, 'size':4, 'rating':5, 'tags':6, 'comments':7, 'series':8, 'publisher':9, 'series_index':10, 'sort':11, 'author_sort':12, - 'formats':13, 'isbn':14, 'path':15, 'lccn':16, 'pubdate':17, - 'flags':18, 'uuid':19, 'cover':20, 'au_map':21} + 'formats':13, 'path':14, 'pubdate':15, 'uuid':16, 'cover':17, + 'au_map':18, 'last_modified':19, 'identifiers':20} for k,v in self.FIELD_MAP.iteritems(): self.field_metadata.set_field_record_index(k, v, prefer_custom=False) @@ -343,6 +386,8 @@ class LibraryDatabase2(LibraryDatabase, SchemaUpgrade, CustomColumns): self.FIELD_MAP['ondevice'] = base = base+1 self.field_metadata.set_field_record_index('ondevice', base, prefer_custom=False) + self.FIELD_MAP['marked'] = base = base+1 + self.field_metadata.set_field_record_index('marked', base, prefer_custom=False) script = ''' DROP VIEW IF EXISTS meta2; @@ -390,12 +435,18 @@ class LibraryDatabase2(LibraryDatabase, SchemaUpgrade, CustomColumns): self.row = self.data.row self.has_id = self.data.has_id self.count = self.data.count + self.set_marked_ids = self.data.set_marked_ids - for prop in ('author_sort', 'authors', 'comment', 'comments', 'isbn', - 'publisher', 'rating', 'series', 'series_index', 'tags', - 'title', 'timestamp', 'uuid', 'pubdate', 'ondevice'): + for prop in ( + 'author_sort', 'authors', 'comment', 'comments', + 'publisher', 'rating', 'series', 'series_index', 'tags', + 'title', 'timestamp', 'uuid', 'pubdate', 'ondevice', + 'metadata_last_modified', + ): + fm = {'comment':'comments', 'metadata_last_modified': + 'last_modified'}.get(prop, prop) setattr(self, prop, functools.partial(self.get_property, - loc=self.FIELD_MAP['comments' if prop == 'comment' else prop])) + loc=self.FIELD_MAP[fm])) setattr(self, 'title_sort', functools.partial(self.get_property, loc=self.FIELD_MAP['sort'])) @@ -417,9 +468,12 @@ class LibraryDatabase2(LibraryDatabase, SchemaUpgrade, CustomColumns): self.refresh_ondevice = None def initialize_database(self): - metadata_sqlite = open(P('metadata_sqlite.sql'), 'rb').read() + metadata_sqlite = P('metadata_sqlite.sql', data=True, + allow_user_override=False).decode('utf-8') self.conn.executescript(metadata_sqlite) - self.user_version = 1 + self.conn.commit() + if self.user_version == 0: + self.user_version = 1 def last_modified(self): ''' Return last modified time as a UTC datetime object''' @@ -450,12 +504,15 @@ class LibraryDatabase2(LibraryDatabase, SchemaUpgrade, CustomColumns): authors = self.authors(id, index_is_id=True) if not authors: authors = _('Unknown') - author = ascii_filename(authors.split(',')[0])[:self.PATH_LIMIT].decode(filesystem_encoding, 'replace') - title = ascii_filename(self.title(id, index_is_id=True))[:self.PATH_LIMIT].decode(filesystem_encoding, 'replace') + author = ascii_filename(authors.split(',')[0] + )[:self.PATH_LIMIT].decode('ascii', 'replace') + title = ascii_filename(self.title(id, index_is_id=True) + )[:self.PATH_LIMIT].decode('ascii', 'replace') while author[-1] in (' ', '.'): author = author[:-1] if not author: - author = ascii_filename(_('Unknown')).decode(filesystem_encoding, 'replace') + author = ascii_filename(_('Unknown')).decode( + 'ascii', 'replace') path = author + '/' + title + ' (%d)'%id return path @@ -466,8 +523,10 @@ class LibraryDatabase2(LibraryDatabase, SchemaUpgrade, CustomColumns): authors = self.authors(id, index_is_id=True) if not authors: authors = _('Unknown') - author = ascii_filename(authors.split(',')[0])[:self.PATH_LIMIT].decode(filesystem_encoding, 'replace') - title = ascii_filename(self.title(id, index_is_id=True))[:self.PATH_LIMIT].decode(filesystem_encoding, 'replace') + author = ascii_filename(authors.split(',')[0] + )[:self.PATH_LIMIT].decode('ascii', 'replace') + title = ascii_filename(self.title(id, index_is_id=True) + )[:self.PATH_LIMIT].decode('ascii', 'replace') name = title + ' - ' + author while name.endswith('.'): name = name[:-1] @@ -681,8 +740,20 @@ class LibraryDatabase2(LibraryDatabase, SchemaUpgrade, CustomColumns): if commit: self.conn.commit() + def update_last_modified(self, book_ids, commit=False, now=None): + if now is None: + now = nowf() + if book_ids: + self.conn.executemany( + 'UPDATE books SET last_modified=? WHERE id=?', + [(now, book) for book in book_ids]) + for book_id in book_ids: + self.data.set(book_id, self.FIELD_MAP['last_modified'], now, row_is_id=True) + if commit: + self.conn.commit() + def dirtied(self, book_ids, commit=True): - changed = False + self.update_last_modified(book_ids) for book in book_ids: with self.dirtied_lock: # print 'dirtied: check id', book @@ -691,21 +762,18 @@ class LibraryDatabase2(LibraryDatabase, SchemaUpgrade, CustomColumns): self.dirtied_sequence += 1 continue # print 'book not already dirty' - try: - self.conn.execute( - 'INSERT INTO metadata_dirtied (book) VALUES (?)', - (book,)) - changed = True - except IntegrityError: - # Already in table - pass + + self.conn.execute( + 'INSERT OR IGNORE INTO metadata_dirtied (book) VALUES (?)', + (book,)) self.dirtied_cache[book] = self.dirtied_sequence self.dirtied_sequence += 1 + # If the commit doesn't happen, then the DB table will be wrong. This # could lead to a problem because on restart, we won't put the book back # into the dirtied_cache. We deal with this by writing the dirtied_cache # back to the table on GUI exit. Not perfect, but probably OK - if commit and changed: + if book_ids and commit: self.conn.commit() def get_a_dirtied_book(self): @@ -761,7 +829,8 @@ class LibraryDatabase2(LibraryDatabase, SchemaUpgrade, CustomColumns): pass return (path, mi, sequence) - def get_metadata(self, idx, index_is_id=False, get_cover=False): + def get_metadata(self, idx, index_is_id=False, get_cover=False, + get_user_categories=True): ''' Convenience method to return metadata as a :class:`Metadata` object. Note that the list of formats is not verified. @@ -790,6 +859,9 @@ class LibraryDatabase2(LibraryDatabase, SchemaUpgrade, CustomColumns): mi.pubdate = row[fm['pubdate']] mi.uuid = row[fm['uuid']] mi.title_sort = row[fm['sort']] + mi.book_size = row[fm['size']] + mi.ondevice_col= row[fm['ondevice']] + mi.last_modified = row[fm['last_modified']] formats = row[fm['formats']] if not formats: formats = None @@ -803,8 +875,8 @@ class LibraryDatabase2(LibraryDatabase, SchemaUpgrade, CustomColumns): if mi.series: mi.series_index = row[fm['series_index']] mi.rating = row[fm['rating']] - mi.isbn = row[fm['isbn']] id = idx if index_is_id else self.id(idx) + mi.set_identifiers(self.get_identifiers(id, index_is_id=True)) mi.application_id = id mi.id = id for key, meta in self.field_metadata.custom_iteritems(): @@ -819,16 +891,17 @@ class LibraryDatabase2(LibraryDatabase, SchemaUpgrade, CustomColumns): user_cats = self.prefs['user_categories'] user_cat_vals = {} - for ucat in user_cats: - res = [] - for name,cat,ign in user_cats[ucat]: - v = mi.get(cat, None) - if isinstance(v, list): - if name in v: + if get_user_categories: + for ucat in user_cats: + res = [] + for name,cat,ign in user_cats[ucat]: + v = mi.get(cat, None) + if isinstance(v, list): + if name in v: + res.append([name,cat]) + elif name == v: res.append([name,cat]) - elif name == v: - res.append([name,cat]) - user_cat_vals[ucat] = res + user_cat_vals[ucat] = res mi.user_categories = user_cat_vals if get_cover: @@ -911,10 +984,14 @@ class LibraryDatabase2(LibraryDatabase, SchemaUpgrade, CustomColumns): except (IOError, OSError): time.sleep(0.2) save_cover_data_to(data, path) - self.conn.execute('UPDATE books SET has_cover=1 WHERE id=?', (id,)) + now = nowf() + self.conn.execute( + 'UPDATE books SET has_cover=1,last_modified=? WHERE id=?', + (now, id)) if commit: self.conn.commit() self.data.set(id, self.FIELD_MAP['cover'], True, row_is_id=True) + self.data.set(id, self.FIELD_MAP['last_modified'], now, row_is_id=True) if notify: self.notify('cover', [id]) @@ -923,8 +1000,12 @@ class LibraryDatabase2(LibraryDatabase, SchemaUpgrade, CustomColumns): def set_has_cover(self, id, val): dval = 1 if val else 0 - self.conn.execute('UPDATE books SET has_cover=? WHERE id=?', (dval, id,)) + now = nowf() + self.conn.execute( + 'UPDATE books SET has_cover=?,last_modified=? WHERE id=?', + (dval, now, id)) self.data.set(id, self.FIELD_MAP['cover'], val, row_is_id=True) + self.data.set(id, self.FIELD_MAP['last_modified'], now, row_is_id=True) def book_on_device(self, id): if callable(self.book_on_device_func): @@ -1055,8 +1136,10 @@ class LibraryDatabase2(LibraryDatabase, SchemaUpgrade, CustomColumns): pdir = os.path.dirname(dest) if not os.path.exists(pdir): os.makedirs(pdir) - with lopen(dest, 'wb') as f: - shutil.copyfileobj(stream, f) + if not getattr(stream, 'name', False) or \ + os.path.abspath(dest) != os.path.abspath(stream.name): + with lopen(dest, 'wb') as f: + shutil.copyfileobj(stream, f) stream.seek(0, 2) size=stream.tell() self.conn.execute('INSERT INTO data (book,format,uncompressed_size,name) VALUES (?,?,?,?)', @@ -1067,7 +1150,7 @@ class LibraryDatabase2(LibraryDatabase, SchemaUpgrade, CustomColumns): self.notify('metadata', [id]) return True - def delete_book(self, id, notify=True, commit=True): + def delete_book(self, id, notify=True, commit=True, permanent=False): ''' Removes book from the result cache and the underlying database. If you set commit to False, you must call clean() manually afterwards @@ -1077,10 +1160,10 @@ class LibraryDatabase2(LibraryDatabase, SchemaUpgrade, CustomColumns): except: path = None if path and os.path.exists(path): - self.rmtree(path) + self.rmtree(path, permanent=permanent) parent = os.path.dirname(path) if len(os.listdir(parent)) == 0: - self.rmtree(parent) + self.rmtree(parent, permanent=permanent) self.conn.execute('DELETE FROM books WHERE id=?', (id,)) if commit: self.conn.commit() @@ -1089,15 +1172,18 @@ class LibraryDatabase2(LibraryDatabase, SchemaUpgrade, CustomColumns): if notify: self.notify('delete', [id]) - def remove_format(self, index, format, index_is_id=False, notify=True, commit=True): + def remove_format(self, index, format, index_is_id=False, notify=True, + commit=True, db_only=False): id = index if index_is_id else self.id(index) name = self.conn.get('SELECT name FROM data WHERE book=? AND format=?', (id, format), all=False) if name: - path = self.format_abspath(id, format, index_is_id=True) - try: - delete_file(path) - except: - traceback.print_exc() + if not db_only: + try: + path = self.format_abspath(id, format, index_is_id=True) + if path: + delete_file(path) + except: + traceback.print_exc() self.conn.execute('DELETE FROM data WHERE book=? AND format=?', (id, format.upper())) if commit: self.conn.commit() @@ -1135,12 +1221,6 @@ class LibraryDatabase2(LibraryDatabase, SchemaUpgrade, CustomColumns): self.clean_custom() self.conn.commit() - def get_recipes(self): - return self.conn.get('SELECT id, script FROM feeds') - - def get_recipe(self, id): - return self.conn.get('SELECT script FROM feeds WHERE id=?', (id,), all=False) - def get_books_for_category(self, category, id_): ans = set([]) @@ -1148,6 +1228,18 @@ class LibraryDatabase2(LibraryDatabase, SchemaUpgrade, CustomColumns): return ans field = self.field_metadata[category] + if field['datatype'] == 'composite': + dex = field['rec_index'] + for book in self.data.iterall(): + if field['is_multiple']: + vals = [v.strip() for v in book[dex].split(field['is_multiple']) + if v.strip()] + if id_ in vals: + ans.add(book[0]) + elif book[dex] == id_: + ans.add(book[0]) + return ans + ans = self.conn.get( 'SELECT book FROM books_{tn}_link WHERE {col}=?'.format( tn=field['table'], col=field['link_column']), (id_,)) @@ -1156,6 +1248,7 @@ class LibraryDatabase2(LibraryDatabase, SchemaUpgrade, CustomColumns): ########## data structures for get_categories CATEGORY_SORTS = ('name', 'popularity', 'rating') + MATCH_TYPE = ('any', 'all') class TCat_Tag(object): @@ -1195,7 +1288,11 @@ class LibraryDatabase2(LibraryDatabase, SchemaUpgrade, CustomColumns): i += 1 else: new_cats['.'.join(comps)] = user_cats[k] - self.prefs.set('user_categories', new_cats) + try: + if new_cats != user_cats: + self.prefs.set('user_categories', new_cats) + except: + pass return new_cats def get_categories(self, sort='name', ids=None, icon_map=None): @@ -1215,10 +1312,11 @@ class LibraryDatabase2(LibraryDatabase, SchemaUpgrade, CustomColumns): # First, build the maps. We need a category->items map and an # item -> (item_id, sort_val) map to use in the books loop - for category in tb_cats.keys(): + for category in tb_cats.iterkeys(): cat = tb_cats[category] if not cat['is_category'] or cat['kind'] in ['user', 'search'] \ - or category in ['news', 'formats']: + or category in ['news', 'formats'] or cat.get('is_csp', + False): continue # Get the ids for the item values if not cat['is_custom']: @@ -1249,6 +1347,11 @@ class LibraryDatabase2(LibraryDatabase, SchemaUpgrade, CustomColumns): for l in list: (id, val) = (l[0], l[1]) tids[category][val] = (id, '{0:05.2f}'.format(val)) + elif cat['datatype'] == 'text' and cat['is_multiple'] and \ + cat['display'].get('is_names', False): + for l in list: + (id, val) = (l[0], l[1]) + tids[category][val] = (id, author_to_author_sort(val)) else: for l in list: (id, val) = (l[0], l[1]) @@ -1257,8 +1360,16 @@ class LibraryDatabase2(LibraryDatabase, SchemaUpgrade, CustomColumns): tcategories[category] = {} # create a list of category/field_index for the books scan to use. # This saves iterating through field_metadata for each book - md.append((category, cat['rec_index'], cat['is_multiple'])) + md.append((category, cat['rec_index'], cat['is_multiple'], False)) + for category in tb_cats.iterkeys(): + cat = tb_cats[category] + if cat['datatype'] == 'composite' and \ + cat['display'].get('make_category', False): + tids[category] = {} + tcategories[category] = {} + md.append((category, cat['rec_index'], cat['is_multiple'], + cat['datatype'] == 'composite')) #print 'end phase "collection":', time.clock() - last, 'seconds' #last = time.clock() @@ -1272,17 +1383,30 @@ class LibraryDatabase2(LibraryDatabase, SchemaUpgrade, CustomColumns): continue rating = book[rating_dex] # We kept track of all possible category field_map positions above - for (cat, dex, mult) in md: - if book[dex] is None: + for (cat, dex, mult, is_comp) in md: + if not book[dex]: continue + tid_cat = tids[cat] + tcats_cat = tcategories[cat] if not mult: val = book[dex] + if is_comp: + item = tcats_cat.get(val, None) + if not item: + item = tag_class(val, val) + tcats_cat[val] = item + item.c += 1 + item.id = val + if rating > 0: + item.rt += rating + item.rc += 1 + continue try: - (item_id, sort_val) = tids[cat][val] # let exceptions fly - item = tcategories[cat].get(val, None) + (item_id, sort_val) = tid_cat[val] # let exceptions fly + item = tcats_cat.get(val, None) if not item: item = tag_class(val, sort_val) - tcategories[cat][val] = item + tcats_cat[val] = item item.c += 1 item.id_set.add(book[0]) item.id = item_id @@ -1293,14 +1417,18 @@ class LibraryDatabase2(LibraryDatabase, SchemaUpgrade, CustomColumns): prints('get_categories: item', val, 'is not in', cat, 'list!') else: vals = book[dex].split(mult) + if is_comp: + vals = [v.strip() for v in vals if v.strip()] + for val in vals: + if val not in tid_cat: + tid_cat[val] = (val, val) for val in vals: - if not val: continue try: - (item_id, sort_val) = tids[cat][val] # let exceptions fly - item = tcategories[cat].get(val, None) + (item_id, sort_val) = tid_cat[val] # let exceptions fly + item = tcats_cat.get(val, None) if not item: item = tag_class(val, sort_val) - tcategories[cat][val] = item + tcats_cat[val] = item item.c += 1 item.id_set.add(book[0]) item.id = item_id @@ -1338,7 +1466,7 @@ class LibraryDatabase2(LibraryDatabase, SchemaUpgrade, CustomColumns): # and building the Tag instances. categories = {} tag_class = Tag - for category in tb_cats.keys(): + for category in tb_cats.iterkeys(): if category not in tcategories: continue cat = tb_cats[category] @@ -1388,10 +1516,20 @@ class LibraryDatabase2(LibraryDatabase, SchemaUpgrade, CustomColumns): reverse=True items.sort(key=kf, reverse=reverse) + if tweaks['categories_use_field_for_author_name'] == 'author_sort' and\ + (category == 'authors' or + (cat['display'].get('is_names', False) and + cat['is_custom'] and cat['is_multiple'] and + cat['datatype'] == 'text')): + use_sort_as_name = True + else: + use_sort_as_name = False + is_editable = category not in ['news', 'rating'] categories[category] = [tag_class(formatter(r.n), count=r.c, id=r.id, avg=avgr(r), sort=r.s, icon=icon, tooltip=tooltip, category=category, - id_set=r.id_set) + id_set=r.id_set, is_editable=is_editable, + use_sort_as_name=use_sort_as_name) for r in items] #print 'end phase "tags list":', time.clock() - last, 'seconds' @@ -1428,7 +1566,7 @@ class LibraryDatabase2(LibraryDatabase, SchemaUpgrade, CustomColumns): all=False) if count > 0: categories['formats'].append(Tag(fmt, count=count, icon=icon, - category='formats')) + category='formats', is_editable=False)) if sort == 'popularity': categories['formats'].sort(key=lambda x: x.count, reverse=True) @@ -1436,6 +1574,35 @@ class LibraryDatabase2(LibraryDatabase, SchemaUpgrade, CustomColumns): # No need for ICU here. categories['formats'].sort(key = lambda x:x.name) + # Now do identifiers. This works like formats + categories['identifiers'] = [] + icon = None + if icon_map and 'identifiers' in icon_map: + icon = icon_map['identifiers'] + for ident in self.conn.get('SELECT DISTINCT type FROM identifiers'): + ident = ident[0] + if ids is not None: + count = self.conn.get('''SELECT COUNT(book) + FROM identifiers + WHERE type="%s" AND + books_list_filter(book)'''%ident, + all=False) + else: + count = self.conn.get('''SELECT COUNT(id) + FROM identifiers + WHERE type="%s"'''%ident, + all=False) + if count > 0: + categories['identifiers'].append(Tag(ident, count=count, icon=icon, + category='identifiers', + is_editable=False)) + + if sort == 'popularity': + categories['identifiers'].sort(key=lambda x: x.count, reverse=True) + else: # no ratings exist to sort on + # No need for ICU here. + categories['identifiers'].sort(key = lambda x:x.name) + #### Now do the user-defined categories. #### user_categories = dict.copy(self.clean_user_categories()) @@ -1487,7 +1654,8 @@ class LibraryDatabase2(LibraryDatabase, SchemaUpgrade, CustomColumns): icon = icon_map['search'] for srch in saved_searches().names(): items.append(Tag(srch, tooltip=saved_searches().lookup(srch), - sort=srch, icon=icon, category='search')) + sort=srch, icon=icon, category='search', + is_editable=False)) if len(items): if icon_map is not None: icon_map['search'] = icon_map['search'] @@ -1500,18 +1668,30 @@ class LibraryDatabase2(LibraryDatabase, SchemaUpgrade, CustomColumns): ############# End get_categories - def tags_older_than(self, tag, delta): + def tags_older_than(self, tag, delta, must_have_tag=None): + ''' + Return the ids of all books having the tag ``tag`` that are older than + than the specified time. tag comparison is case insensitive. + + :param delta: A timedelta object or None. If None, then all ids with + the tag are returned. + :param must_have_tag: If not None the list of matches will be + restricted to books that have this tag + ''' tag = tag.lower().strip() + mht = must_have_tag.lower().strip() if must_have_tag else None now = nowf() tindex = self.FIELD_MAP['timestamp'] gindex = self.FIELD_MAP['tags'] + iindex = self.FIELD_MAP['id'] for r in self.data._data: if r is not None: - if (now - r[tindex]) > delta: + if delta is None or (now - r[tindex]) > delta: tags = r[gindex] - if tags and tag in [x.strip() for x in - tags.lower().split(',')]: - yield r[self.FIELD_MAP['id']] + if tags: + tags = [x.strip() for x in tags.lower().split(',')] + if tag in tags and (mht is None or mht in tags): + yield r[iindex] def get_next_series_num_for(self, series): series_id = self.conn.get('SELECT id from series WHERE name=?', @@ -1583,10 +1763,21 @@ class LibraryDatabase2(LibraryDatabase, SchemaUpgrade, CustomColumns): self.notify('metadata', [id]) return books_to_refresh - def set_metadata(self, id, mi, ignore_errors=False, - set_title=True, set_authors=True, commit=True): + def set_metadata(self, id, mi, ignore_errors=False, set_title=True, + set_authors=True, commit=True, force_changes=False, + notify=True): ''' Set metadata for the book `id` from the `Metadata` object `mi` + + Setting force_changes=True will force set_metadata to update fields even + if mi contains empty values. In this case, 'None' is distinguished from + 'empty'. If mi.XXX is None, the XXX is not replaced, otherwise it is. + The tags, identifiers, and cover attributes are special cases. Tags and + identifiers cannot be set to None so then will always be replaced if + force_changes is true. You must ensure that mi contains the values you + want the book to have. Covers are always changed if a new cover is + provided, but are never deleted. Also note that force_changes has no + effect on setting title or authors. ''' if callable(getattr(mi, 'to_book_metadata', None)): # Handle code passing in a OPF object instead of a Metadata object @@ -1600,13 +1791,18 @@ class LibraryDatabase2(LibraryDatabase, SchemaUpgrade, CustomColumns): traceback.print_exc() else: raise + + def should_replace_field(attr): + return (force_changes and (mi.get(attr, None) is not None)) or \ + not mi.is_null(attr) + path_changed = False if set_title and mi.title: self._set_title(id, mi.title) path_changed = True if set_authors: if not mi.authors: - mi.authors = [_('Unknown')] + mi.authors = [_('Unknown')] authors = [] for a in mi.authors: authors += string_to_authors(a) @@ -1614,16 +1810,21 @@ class LibraryDatabase2(LibraryDatabase, SchemaUpgrade, CustomColumns): path_changed = True if path_changed: self.set_path(id, index_is_id=True) - if mi.author_sort: + + if should_replace_field('author_sort'): doit(self.set_author_sort, id, mi.author_sort, notify=False, commit=False) - if mi.publisher: + if should_replace_field('publisher'): doit(self.set_publisher, id, mi.publisher, notify=False, commit=False) - if mi.rating: + + # Setting rating to zero is acceptable. + if mi.rating is not None: doit(self.set_rating, id, mi.rating, notify=False, commit=False) - if mi.series: + if should_replace_field('series'): doit(self.set_series, id, mi.series, notify=False, commit=False) + + # force_changes has no effect on cover manipulation if mi.cover_data[1] is not None: doit(self.set_cover, id, mi.cover_data[1], commit=False) elif mi.cover is not None: @@ -1632,32 +1833,49 @@ class LibraryDatabase2(LibraryDatabase, SchemaUpgrade, CustomColumns): raw = f.read() if raw: doit(self.set_cover, id, raw, commit=False) - if mi.tags: + + # if force_changes is true, tags are always replaced because the + # attribute cannot be set to None. + if should_replace_field('tags'): doit(self.set_tags, id, mi.tags, notify=False, commit=False) - if mi.comments: + + if should_replace_field('comments'): doit(self.set_comment, id, mi.comments, notify=False, commit=False) - if mi.isbn and mi.isbn.strip(): - doit(self.set_isbn, id, mi.isbn, notify=False, commit=False) - if mi.series_index: + + # Setting series_index to zero is acceptable + if mi.series_index is not None: doit(self.set_series_index, id, mi.series_index, notify=False, commit=False) - if mi.pubdate: + if should_replace_field('pubdate'): doit(self.set_pubdate, id, mi.pubdate, notify=False, commit=False) if getattr(mi, 'timestamp', None) is not None: doit(self.set_timestamp, id, mi.timestamp, notify=False, commit=False) + # identifiers will always be replaced if force_changes is True + mi_idents = mi.get_identifiers() + if force_changes: + self.set_identifiers(id, mi_idents, notify=False, commit=False) + elif mi_idents: + identifiers = self.get_identifiers(id, index_is_id=True) + for key, val in mi_idents.iteritems(): + if val and val.strip(): # Don't delete an existing identifier + identifiers[icu_lower(key)] = val + self.set_identifiers(id, identifiers, notify=False, commit=False) + + user_mi = mi.get_all_user_metadata(make_copy=False) for key in user_mi.iterkeys(): if key in self.field_metadata and \ user_mi[key]['datatype'] == self.field_metadata[key]['datatype']: - doit(self.set_custom, id, - val=mi.get(key), - extra=mi.get_extra(key), - label=user_mi[key]['label'], commit=False) + val = mi.get(key, None) + if force_changes or val is not None: + doit(self.set_custom, id, val=val, extra=mi.get_extra(key), + label=user_mi[key]['label'], commit=False) if commit: self.conn.commit() - self.notify('metadata', [id]) + if notify: + self.notify('metadata', [id]) def authors_sort_strings(self, id, index_is_id=False): ''' @@ -1707,6 +1925,18 @@ class LibraryDatabase2(LibraryDatabase, SchemaUpgrade, CustomColumns): result.append(r) return ' & '.join(result).replace('|', ',') + def _update_author_in_cache(self, id_, ss, final_authors): + self.conn.execute('UPDATE books SET author_sort=? WHERE id=?', (ss, id_)) + self.data.set(id_, self.FIELD_MAP['authors'], + ','.join([a.replace(',', '|') for a in final_authors]), + row_is_id=True) + self.data.set(id_, self.FIELD_MAP['author_sort'], ss, row_is_id=True) + + aum = self.authors_with_sort_strings(id_, index_is_id=True) + self.data.set(id_, self.FIELD_MAP['au_map'], + ':#:'.join([':::'.join((au.replace(',', '|'), aus)) for (au, aus) in aum]), + row_is_id=True) + def _set_authors(self, id, authors, allow_case_change=False): if not authors: authors = [_('Unknown')] @@ -1720,14 +1950,17 @@ class LibraryDatabase2(LibraryDatabase, SchemaUpgrade, CustomColumns): a = a.strip().replace(',', '|') if not isinstance(a, unicode): a = a.decode(preferred_encoding, 'replace') - aus = self.conn.get('SELECT id, name FROM authors WHERE name=?', (a,)) + aus = self.conn.get('SELECT id, name, sort FROM authors WHERE name=?', (a,)) if aus: - aid, name = aus[0] + aid, name, sort = aus[0] # Handle change of case if name != a: if allow_case_change: - self.conn.execute('''UPDATE authors - SET name=? WHERE id=?''', (a, aid)) + ns = author_to_author_sort(a.replace('|', ',')) + if strcmp(sort, ns) == 0: + sort = ns + self.conn.execute('''UPDATE authors SET name=?, sort=? + WHERE id=?''', (a, sort, aid)) case_change = True else: a = name @@ -1744,17 +1977,14 @@ class LibraryDatabase2(LibraryDatabase, SchemaUpgrade, CustomColumns): bks = self.conn.get('''SELECT book FROM books_authors_link WHERE author=?''', (aid,)) books_to_refresh |= set([bk[0] for bk in bks]) + for bk in books_to_refresh: + ss = self.author_sort_from_book(id, index_is_id=True) + aus = self.author_sort(bk, index_is_id=True) + if strcmp(aus, ss) == 0: + self._update_author_in_cache(bk, ss, final_authors) + # This can repeat what was done above in rare cases. Let it. ss = self.author_sort_from_book(id, index_is_id=True) - self.conn.execute('UPDATE books SET author_sort=? WHERE id=?', - (ss, id)) - self.data.set(id, self.FIELD_MAP['authors'], - ','.join([a.replace(',', '|') for a in final_authors]), - row_is_id=True) - self.data.set(id, self.FIELD_MAP['author_sort'], ss, row_is_id=True) - aum = self.authors_with_sort_strings(id, index_is_id=True) - self.data.set(id, self.FIELD_MAP['au_map'], - ':#:'.join([':::'.join((au.replace(',', '|'), aus)) for (au, aus) in aum]), - row_is_id=True) + self._update_author_in_cache(id, ss, final_authors) return books_to_refresh def set_authors(self, id, authors, notify=True, commit=True, @@ -1794,8 +2024,16 @@ class LibraryDatabase2(LibraryDatabase, SchemaUpgrade, CustomColumns): return False if isbytestring(title): title = title.decode(preferred_encoding, 'replace') + old_title = self.title(id, index_is_id=True) + # We cannot check if old_title == title as previous code might have + # already updated the cache + only_case_change = icu_lower(old_title) == icu_lower(title) self.conn.execute('UPDATE books SET title=? WHERE id=?', (title, id)) self.data.set(id, self.FIELD_MAP['title'], title, row_is_id=True) + if only_case_change: + # SQLite update trigger will not update sort on a case change + self.conn.execute('UPDATE books SET sort=? WHERE id=?', + (title_sort(title), id)) ts = self.conn.get('SELECT sort FROM books WHERE id=?', (id,), all=False) if ts: @@ -2052,6 +2290,12 @@ class LibraryDatabase2(LibraryDatabase, SchemaUpgrade, CustomColumns): return [] return result + def get_author_id(self, author): + author = author.replace(',', '|') + result = self.conn.get('SELECT id FROM authors WHERE name=?', + (author,), all=False) + return result + def set_sort_field_for_author(self, old_id, new_sort, commit=True, notify=False): self.conn.execute('UPDATE authors SET sort=? WHERE id=?', \ (new_sort.strip(), old_id)) @@ -2244,6 +2488,8 @@ class LibraryDatabase2(LibraryDatabase, SchemaUpgrade, CustomColumns): @param tags: list of strings @param append: If True existing tags are not removed ''' + if not tags: + tags = [] if not append: self.conn.execute('DELETE FROM books_tags_link WHERE book=?', (id,)) self.conn.execute('''DELETE FROM tags WHERE (SELECT COUNT(id) @@ -2394,6 +2640,8 @@ class LibraryDatabase2(LibraryDatabase, SchemaUpgrade, CustomColumns): self.notify('metadata', [id]) def set_rating(self, id, rating, notify=True, commit=True): + if not rating: + rating = 0 rating = int(rating) self.conn.execute('DELETE FROM books_ratings_link WHERE book=?',(id,)) rat = self.conn.get('SELECT id FROM ratings WHERE rating=?', (rating,), all=False) @@ -2408,7 +2656,10 @@ class LibraryDatabase2(LibraryDatabase, SchemaUpgrade, CustomColumns): def set_comment(self, id, text, notify=True, commit=True): self.conn.execute('DELETE FROM comments WHERE book=?', (id,)) - self.conn.execute('INSERT INTO comments(book,text) VALUES (?,?)', (id, text)) + if text: + self.conn.execute('INSERT INTO comments(book,text) VALUES (?,?)', (id, text)) + else: + text = '' if commit: self.conn.commit() self.data.set(id, self.FIELD_MAP['comments'], text, row_is_id=True) @@ -2417,6 +2668,8 @@ class LibraryDatabase2(LibraryDatabase, SchemaUpgrade, CustomColumns): self.notify('metadata', [id]) def set_author_sort(self, id, sort, notify=True, commit=True): + if not sort: + sort = '' self.conn.execute('UPDATE books SET author_sort=? WHERE id=?', (sort, id)) self.dirtied([id], commit=False) if commit: @@ -2425,16 +2678,94 @@ class LibraryDatabase2(LibraryDatabase, SchemaUpgrade, CustomColumns): if notify: self.notify('metadata', [id]) - def set_isbn(self, id, isbn, notify=True, commit=True): - self.conn.execute('UPDATE books SET isbn=? WHERE id=?', (isbn, id)) - self.dirtied([id], commit=False) + def isbn(self, idx, index_is_id=False): + row = self.data._data[idx] if index_is_id else self.data[idx] + if row is not None: + raw = row[self.FIELD_MAP['identifiers']] + if raw: + for x in raw.split(','): + if x.startswith('isbn:'): + return x[5:].strip() + + def get_identifiers(self, idx, index_is_id=False): + ans = {} + row = self.data._data[idx] if index_is_id else self.data[idx] + if row is not None: + raw = row[self.FIELD_MAP['identifiers']] + if raw: + for x in raw.split(','): + key, _, val = x.partition(':') + key, val = key.strip(), val.strip() + if key and val: + ans[key] = val + + return ans + + def get_all_identifier_types(self): + idents = self.conn.get('SELECT DISTINCT type FROM identifiers') + return [ident[0] for ident in idents] + + def _clean_identifier(self, typ, val): + typ = icu_lower(typ).strip().replace(':', '').replace(',', '') + val = val.strip().replace(',', '|').replace(':', '|') + return typ, val + + def set_identifier(self, id_, typ, val, notify=True, commit=True): + 'If val is empty, deletes identifier of type typ' + typ, val = self._clean_identifier(typ, val) + identifiers = self.get_identifiers(id_, index_is_id=True) + if not typ: + return + changed = False + if not val and typ in identifiers: + identifiers.pop(typ) + changed = True + self.conn.execute( + 'DELETE from identifiers WHERE book=? AND type=?', + (id_, typ)) + if val and identifiers.get(typ, None) != val: + changed = True + identifiers[typ] = val + self.conn.execute( + 'INSERT OR REPLACE INTO identifiers (book, type, val) VALUES (?, ?, ?)', + (id_, typ, val)) + if changed: + raw = ','.join(['%s:%s'%(k, v) for k, v in + identifiers.iteritems()]) + self.data.set(id_, self.FIELD_MAP['identifiers'], raw, + row_is_id=True) + if commit: + self.conn.commit() + if notify: + self.notify('metadata', [id_]) + + def set_identifiers(self, id_, identifiers, notify=True, commit=True): + cleaned = {} + if not identifiers: + identifiers = {} + for typ, val in identifiers.iteritems(): + typ, val = self._clean_identifier(typ, val) + if val: + cleaned[typ] = val + self.conn.execute('DELETE FROM identifiers WHERE book=?', (id_,)) + self.conn.executemany( + 'INSERT INTO identifiers (book, type, val) VALUES (?, ?, ?)', + [(id_, k, v) for k, v in cleaned.iteritems()]) + raw = ','.join(['%s:%s'%(k, v) for k, v in + cleaned.iteritems()]) + self.data.set(id_, self.FIELD_MAP['identifiers'], raw, + row_is_id=True) if commit: self.conn.commit() - self.data.set(id, self.FIELD_MAP['isbn'], isbn, row_is_id=True) if notify: - self.notify('metadata', [id]) + self.notify('metadata', [id_]) + + def set_isbn(self, id_, isbn, notify=True, commit=True): + self.set_identifier(id_, 'isbn', isbn, notify=notify, commit=commit) def add_catalog(self, path, title): + from calibre.ebooks.metadata.meta import get_metadata + format = os.path.splitext(path)[1][1:].lower() with lopen(path, 'rb') as stream: matches = self.data.get_matches('title', '='+title) @@ -2470,6 +2801,8 @@ class LibraryDatabase2(LibraryDatabase, SchemaUpgrade, CustomColumns): def add_news(self, path, arg): + from calibre.ebooks.metadata.meta import get_metadata + format = os.path.splitext(path)[1][1:].lower() stream = path if hasattr(path, 'read') else lopen(path, 'rb') stream.seek(0) @@ -2728,9 +3061,9 @@ class LibraryDatabase2(LibraryDatabase, SchemaUpgrade, CustomColumns): ''' if prefix is None: prefix = self.library_path - FIELDS = set(['title', 'authors', 'author_sort', 'publisher', 'rating', + FIELDS = set(['title', 'sort', 'authors', 'author_sort', 'publisher', 'rating', 'timestamp', 'size', 'tags', 'comments', 'series', 'series_index', - 'isbn', 'uuid', 'pubdate']) + 'uuid', 'pubdate', 'last_modified', 'identifiers']) for x in self.custom_column_num_map: FIELDS.add(x) data = [] @@ -2745,6 +3078,8 @@ class LibraryDatabase2(LibraryDatabase, SchemaUpgrade, CustomColumns): data.append(x) x['id'] = db_id x['formats'] = [] + isbn = self.isbn(db_id, index_is_id=True) + x['isbn'] = isbn if isbn else '' if not x['authors']: x['authors'] = _('Unknown') x['authors'] = [i.replace('|', ',') for i in x['authors'].split(',')] @@ -2861,6 +3196,8 @@ books_series_link feeds yield formats def import_book_directory_multiple(self, dirpath, callback=None): + from calibre.ebooks.metadata.meta import metadata_from_formats + duplicates = [] for formats in self.find_books_in_directory(dirpath, False): mi = metadata_from_formats(formats) @@ -2876,6 +3213,7 @@ books_series_link feeds return duplicates def import_book_directory(self, dirpath, callback=None): + from calibre.ebooks.metadata.meta import metadata_from_formats dirpath = os.path.abspath(dirpath) formats = self.find_books_in_directory(dirpath, True) formats = list(formats)[0] @@ -2902,7 +3240,6 @@ books_series_link feeds if callable(callback): if callback(''): break - return duplicates def add_custom_book_data(self, book_id, name, val): @@ -2911,12 +3248,19 @@ books_series_link feeds raise ValueError('add_custom_book_data: no such book_id %d'%book_id) # Do the json encode first, in case it throws an exception s = json.dumps(val, default=to_json) - self.conn.execute('DELETE FROM books_plugin_data WHERE book=? AND name=?', - (book_id, name)) - self.conn.execute('''INSERT INTO books_plugin_data(book, name, val) + self.conn.execute('''INSERT OR REPLACE INTO books_plugin_data(book, name, val) VALUES(?, ?, ?)''', (book_id, name, s)) self.commit() + def add_multiple_custom_book_data(self, name, vals, delete_first=False): + if delete_first: + self.conn.execute('DELETE FROM books_plugin_data WHERE name=?', (name, )) + self.conn.executemany( + 'INSERT OR REPLACE INTO books_plugin_data (book, name, val) VALUES (?, ?, ?)', + [(book_id, name, json.dumps(val, default=to_json)) + for book_id, val in vals.iteritems()]) + self.commit() + def get_custom_book_data(self, book_id, name, default=None): try: s = self.conn.get('''select val FROM books_plugin_data @@ -2928,17 +3272,31 @@ books_series_link feeds pass return default + def get_all_custom_book_data(self, name, default=None): + try: + s = self.conn.get('''select book, val FROM books_plugin_data + WHERE name=?''', (name,)) + if s is None: + return default + res = {} + for r in s: + res[r[0]] = json.loads(r[1], object_hook=from_json) + return res + except: + pass + return default + def delete_custom_book_data(self, book_id, name): self.conn.execute('DELETE FROM books_plugin_data WHERE book=? AND name=?', (book_id, name)) self.commit() + def delete_all_custom_book_data(self, name): + self.conn.execute('DELETE FROM books_plugin_data WHERE name=?', (name, )) + self.commit() + def get_ids_for_custom_book_data(self, name): s = self.conn.get('''SELECT book FROM books_plugin_data WHERE name=?''', (name,)) return [x[0] for x in s] - def get_custom_recipes(self): - for id, title, script in self.conn.get('SELECT id,title,script FROM feeds'): - yield id, title, script - diff --git a/src/calibre/library/db/__init__.py b/src/calibre/library/db/__init__.py deleted file mode 100644 index 0080175bfa..0000000000 --- a/src/calibre/library/db/__init__.py +++ /dev/null @@ -1,9 +0,0 @@ -#!/usr/bin/env python -# vim:fileencoding=UTF-8:ts=4:sw=4:sta:et:sts=4:ai - -__license__ = 'GPL v3' -__copyright__ = '2010, Kovid Goyal ' -__docformat__ = 'restructuredtext en' - - - diff --git a/src/calibre/library/db/base.py b/src/calibre/library/db/base.py deleted file mode 100644 index a2374583eb..0000000000 --- a/src/calibre/library/db/base.py +++ /dev/null @@ -1,37 +0,0 @@ -#!/usr/bin/env python -# vim:fileencoding=UTF-8:ts=4:sw=4:sta:et:sts=4:ai - -__license__ = 'GPL v3' -__copyright__ = '2010, Kovid Goyal ' -__docformat__ = 'restructuredtext en' - - -''' Design documentation {{{ - - Storage paradigm {{{ - * Agnostic to storage paradigm (i.e. no book per folder assumptions) - * Two separate concepts: A store and collection - A store is a backend, like a sqlite database associated with a path on - the local filesystem, or a cloud based storage solution. - A collection is a user defined group of stores. Most of the logic for - data manipulation sorting/searching/restrictions should be in the collection - class. The collection class should transparently handle the - conversion from store name + id to row number in the collection. - * Not sure how feasible it is to allow many-many maps between stores - and collections. - }}} - - Event system {{{ - * Comprehensive event system that other components can subscribe to - * Subscribers should be able to temporarily block receiving events - * Should event dispatch be asynchronous? - * Track last modified time for metadata and each format - }}} -}}}''' - -# Imports {{{ -# }}} - - - - diff --git a/src/calibre/library/field_metadata.py b/src/calibre/library/field_metadata.py index aff2803452..979e98a819 100644 --- a/src/calibre/library/field_metadata.py +++ b/src/calibre/library/field_metadata.py @@ -4,8 +4,8 @@ Created on 25 May 2010 @author: charles ''' import copy, traceback +from collections import OrderedDict -from calibre.utils.ordered_dict import OrderedDict from calibre.utils.config import tweaks class TagsIcons(dict): @@ -16,7 +16,8 @@ class TagsIcons(dict): ''' category_icons = ['authors', 'series', 'formats', 'publisher', 'rating', - 'news', 'tags', 'custom:', 'user:', 'search',] + 'news', 'tags', 'custom:', 'user:', 'search', + 'identifiers'] def __init__(self, icon_dict): for a in self.category_icons: if a not in icon_dict: @@ -24,16 +25,17 @@ class TagsIcons(dict): self[a] = icon_dict[a] category_icon_map = { - 'authors' : 'user_profile.png', - 'series' : 'series.png', - 'formats' : 'book.png', - 'publisher' : 'publisher.png', - 'rating' : 'rating.png', - 'news' : 'news.png', - 'tags' : 'tags.png', - 'custom:' : 'column.png', - 'user:' : 'tb_folder.png', - 'search' : 'search.png' + 'authors' : 'user_profile.png', + 'series' : 'series.png', + 'formats' : 'book.png', + 'publisher' : 'publisher.png', + 'rating' : 'rating.png', + 'news' : 'news.png', + 'tags' : 'tags.png', + 'custom:' : 'column.png', + 'user:' : 'tb_folder.png', + 'search' : 'search.png', + 'identifiers': 'identifiers.png' } @@ -80,6 +82,8 @@ class FieldMetadata(dict): rec_index: the index of the field in the db metadata record. + is_csp: field contains colon-separated pairs. Must also be text, is_multiple + ''' VALID_DATA_TYPES = frozenset([None, 'rating', 'text', 'comments', 'datetime', @@ -98,7 +102,8 @@ class FieldMetadata(dict): 'name':_('Authors'), 'search_terms':['authors', 'author'], 'is_custom':False, - 'is_category':True}), + 'is_category':True, + 'is_csp': False}), ('series', {'table':'series', 'column':'name', 'link_column':'series', @@ -109,7 +114,8 @@ class FieldMetadata(dict): 'name':_('Series'), 'search_terms':['series'], 'is_custom':False, - 'is_category':True}), + 'is_category':True, + 'is_csp': False}), ('formats', {'table':None, 'column':None, 'datatype':'text', @@ -118,7 +124,8 @@ class FieldMetadata(dict): 'name':_('Formats'), 'search_terms':['formats', 'format'], 'is_custom':False, - 'is_category':True}), + 'is_category':True, + 'is_csp': False}), ('publisher', {'table':'publishers', 'column':'name', 'link_column':'publisher', @@ -129,7 +136,8 @@ class FieldMetadata(dict): 'name':_('Publishers'), 'search_terms':['publisher'], 'is_custom':False, - 'is_category':True}), + 'is_category':True, + 'is_csp': False}), ('rating', {'table':'ratings', 'column':'rating', 'link_column':'rating', @@ -140,7 +148,8 @@ class FieldMetadata(dict): 'name':_('Ratings'), 'search_terms':['rating'], 'is_custom':False, - 'is_category':True}), + 'is_category':True, + 'is_csp': False}), ('news', {'table':'news', 'column':'name', 'category_sort':'name', @@ -150,7 +159,8 @@ class FieldMetadata(dict): 'name':_('News'), 'search_terms':[], 'is_custom':False, - 'is_category':True}), + 'is_category':True, + 'is_csp': False}), ('tags', {'table':'tags', 'column':'name', 'link_column': 'tag', @@ -161,16 +171,28 @@ class FieldMetadata(dict): 'name':_('Tags'), 'search_terms':['tags', 'tag'], 'is_custom':False, - 'is_category':True}), + 'is_category':True, + 'is_csp': False}), + ('identifiers', {'table':None, + 'column':None, + 'datatype':'text', + 'is_multiple':',', + 'kind':'field', + 'name':_('Identifiers'), + 'search_terms':['identifiers', 'identifier', 'isbn'], + 'is_custom':False, + 'is_category':True, + 'is_csp': True}), ('author_sort',{'table':None, 'column':None, 'datatype':'text', 'is_multiple':None, 'kind':'field', - 'name':None, + 'name':_('Author Sort'), 'search_terms':['author_sort'], 'is_custom':False, - 'is_category':False}), + 'is_category':False, + 'is_csp': False}), ('au_map', {'table':None, 'column':None, 'datatype':'text', @@ -179,7 +201,8 @@ class FieldMetadata(dict): 'name':None, 'search_terms':[], 'is_custom':False, - 'is_category':False}), + 'is_category':False, + 'is_csp': False}), ('comments', {'table':None, 'column':None, 'datatype':'text', @@ -187,7 +210,9 @@ class FieldMetadata(dict): 'kind':'field', 'name':_('Comments'), 'search_terms':['comments', 'comment'], - 'is_custom':False, 'is_category':False}), + 'is_custom':False, + 'is_category':False, + 'is_csp': False}), ('cover', {'table':None, 'column':None, 'datatype':'int', @@ -196,16 +221,8 @@ class FieldMetadata(dict): 'name':None, 'search_terms':['cover'], 'is_custom':False, - 'is_category':False}), - ('flags', {'table':None, - 'column':None, - 'datatype':'text', - 'is_multiple':None, - 'kind':'field', - 'name':None, - 'search_terms':[], - 'is_custom':False, - 'is_category':False}), + 'is_category':False, + 'is_csp': False}), ('id', {'table':None, 'column':None, 'datatype':'int', @@ -214,25 +231,18 @@ class FieldMetadata(dict): 'name':None, 'search_terms':[], 'is_custom':False, - 'is_category':False}), - ('isbn', {'table':None, + 'is_category':False, + 'is_csp': False}), + ('last_modified', {'table':None, 'column':None, - 'datatype':'text', + 'datatype':'datetime', 'is_multiple':None, 'kind':'field', - 'name':None, - 'search_terms':['isbn'], + 'name':_('Modified'), + 'search_terms':['last_modified'], 'is_custom':False, - 'is_category':False}), - ('lccn', {'table':None, - 'column':None, - 'datatype':'text', - 'is_multiple':None, - 'kind':'field', - 'name':None, - 'search_terms':[], - 'is_custom':False, - 'is_category':False}), + 'is_category':False, + 'is_csp': False}), ('ondevice', {'table':None, 'column':None, 'datatype':'text', @@ -241,16 +251,18 @@ class FieldMetadata(dict): 'name':_('On Device'), 'search_terms':['ondevice'], 'is_custom':False, - 'is_category':False}), + 'is_category':False, + 'is_csp': False}), ('path', {'table':None, 'column':None, 'datatype':'text', 'is_multiple':None, 'kind':'field', - 'name':None, + 'name':_('Path'), 'search_terms':[], 'is_custom':False, - 'is_category':False}), + 'is_category':False, + 'is_csp': False}), ('pubdate', {'table':None, 'column':None, 'datatype':'datetime', @@ -259,7 +271,18 @@ class FieldMetadata(dict): 'name':_('Published'), 'search_terms':['pubdate'], 'is_custom':False, - 'is_category':False}), + 'is_category':False, + 'is_csp': False}), + ('marked', {'table':None, + 'column':None, + 'datatype':'text', + 'is_multiple':None, + 'kind':'field', + 'name': None, + 'search_terms':['marked'], + 'is_custom':False, + 'is_category':False, + 'is_csp': False}), ('series_index',{'table':None, 'column':None, 'datatype':'float', @@ -268,7 +291,8 @@ class FieldMetadata(dict): 'name':None, 'search_terms':['series_index'], 'is_custom':False, - 'is_category':False}), + 'is_category':False, + 'is_csp': False}), ('sort', {'table':None, 'column':None, 'datatype':'text', @@ -277,16 +301,18 @@ class FieldMetadata(dict): 'name':_('Title Sort'), 'search_terms':['title_sort'], 'is_custom':False, - 'is_category':False}), + 'is_category':False, + 'is_csp': False}), ('size', {'table':None, 'column':None, 'datatype':'float', 'is_multiple':None, 'kind':'field', - 'name':_('Size (MB)'), + 'name':_('Size'), 'search_terms':['size'], 'is_custom':False, - 'is_category':False}), + 'is_category':False, + 'is_csp': False}), ('timestamp', {'table':None, 'column':None, 'datatype':'datetime', @@ -295,7 +321,8 @@ class FieldMetadata(dict): 'name':_('Date'), 'search_terms':['date'], 'is_custom':False, - 'is_category':False}), + 'is_category':False, + 'is_csp': False}), ('title', {'table':None, 'column':None, 'datatype':'text', @@ -304,7 +331,8 @@ class FieldMetadata(dict): 'name':_('Title'), 'search_terms':['title'], 'is_custom':False, - 'is_category':False}), + 'is_category':False, + 'is_csp': False}), ('uuid', {'table':None, 'column':None, 'datatype':'text', @@ -313,7 +341,8 @@ class FieldMetadata(dict): 'name':None, 'search_terms':[], 'is_custom':False, - 'is_category':False}), + 'is_category':False, + 'is_csp': False}), ] # }}} @@ -339,6 +368,8 @@ class FieldMetadata(dict): 'date_format': tweaks['gui_timestamp_display_format']} self._tb_cats['pubdate']['display'] = { 'date_format': tweaks['gui_pubdate_display_format']} + self._tb_cats['last_modified']['display'] = { + 'date_format': tweaks['gui_last_modified_display_format']} self.custom_field_prefix = '#' self.get = self._tb_cats.get @@ -369,6 +400,13 @@ class FieldMetadata(dict): if self._tb_cats[k]['kind']=='field' and self._tb_cats[k]['datatype'] is not None] + def displayable_field_keys(self): + return [k for k in self._tb_cats.keys() + if self._tb_cats[k]['kind']=='field' and + self._tb_cats[k]['datatype'] is not None and + k not in ('au_map', 'marked', 'ondevice', 'cover') and + not self.is_series_index(k)] + def standard_field_keys(self): return [k for k in self._tb_cats.keys() if self._tb_cats[k]['kind']=='field' and @@ -412,6 +450,11 @@ class FieldMetadata(dict): def is_custom_field(self, key): return key.startswith(self.custom_field_prefix) + def is_series_index(self, key): + m = self[key] + return (m['datatype'] == 'float' and key.endswith('_index') and + key[:-6] in self) + def key_to_label(self, key): if 'label' not in self._tb_cats[key]: return key @@ -441,7 +484,8 @@ class FieldMetadata(dict): return l def add_custom_field(self, label, table, column, datatype, colnum, name, - display, is_editable, is_multiple, is_category): + display, is_editable, is_multiple, is_category, + is_csp=False): key = self.custom_field_prefix + label if key in self._tb_cats: raise ValueError('Duplicate custom field [%s]'%(label)) @@ -454,7 +498,7 @@ class FieldMetadata(dict): 'colnum':colnum, 'display':display, 'is_custom':True, 'is_category':is_category, 'link_column':'value','category_sort':'value', - 'is_editable': is_editable,} + 'is_csp' : is_csp, 'is_editable': is_editable,} self._add_search_terms_to_map(key, [key]) self.custom_label_to_key_map[label] = key if datatype == 'series': @@ -466,7 +510,7 @@ class FieldMetadata(dict): 'colnum':None, 'display':{}, 'is_custom':False, 'is_category':False, 'link_column':None, 'category_sort':None, - 'is_editable': False,} + 'is_editable': False, 'is_csp': False} self._add_search_terms_to_map(key, [key]) self.custom_label_to_key_map[label+'_index'] = key @@ -515,7 +559,7 @@ class FieldMetadata(dict): 'datatype':None, 'is_multiple':None, 'kind':'user', 'name':name, 'search_terms':st, 'is_custom':False, - 'is_category':True} + 'is_category':True, 'is_csp': False} self._add_search_terms_to_map(label, st) def add_search_category(self, label, name): @@ -524,8 +568,8 @@ class FieldMetadata(dict): self._tb_cats[label] = {'table':None, 'column':None, 'datatype':None, 'is_multiple':None, 'kind':'search', 'name':name, - 'search_terms':[], 'is_custom':False, - 'is_category':True} + 'search_terms':[], 'is_custom':False, + 'is_category':True, 'is_csp': False} def set_field_record_index(self, label, index, prefer_custom=False): if prefer_custom: diff --git a/src/calibre/library/prefs.py b/src/calibre/library/prefs.py index 233c717897..4ef1dcb35a 100644 --- a/src/calibre/library/prefs.py +++ b/src/calibre/library/prefs.py @@ -49,8 +49,7 @@ class DBPrefs(dict): if self.disable_setting: return raw = self.to_raw(val) - self.db.conn.execute('DELETE FROM preferences WHERE key=?', (key,)) - self.db.conn.execute('INSERT INTO preferences (key,val) VALUES (?,?)', (key, + self.db.conn.execute('INSERT OR REPLACE INTO preferences (key,val) VALUES (?,?)', (key, raw)) self.db.conn.commit() dict.__setitem__(self, key, val) diff --git a/src/calibre/library/restore.py b/src/calibre/library/restore.py index 76f3c0333d..e03edd449a 100644 --- a/src/calibre/library/restore.py +++ b/src/calibre/library/restore.py @@ -13,6 +13,7 @@ from calibre.ptempfile import TemporaryDirectory from calibre.ebooks.metadata.opf2 import OPF from calibre.library.database2 import LibraryDatabase2 from calibre.constants import filesystem_encoding +from calibre.utils.date import utcfromtimestamp from calibre import isbytestring NON_EBOOK_EXTENSIONS = frozenset([ @@ -211,8 +212,8 @@ class Restore(Thread): force_id=book['id']) if book['mi'].uuid: db.set_uuid(book['id'], book['mi'].uuid, commit=False, notify=False) - db.conn.execute('UPDATE books SET path=? WHERE id=?', (book['path'], - book['id'])) + db.conn.execute('UPDATE books SET path=?,last_modified=? WHERE id=?', (book['path'], + utcfromtimestamp(book['timestamp']), book['id'])) for fmt, size, name in book['formats']: db.conn.execute(''' diff --git a/src/calibre/library/save_to_disk.py b/src/calibre/library/save_to_disk.py index de586048b7..5f49833564 100644 --- a/src/calibre/library/save_to_disk.py +++ b/src/calibre/library/save_to_disk.py @@ -12,13 +12,13 @@ from calibre.constants import DEBUG from calibre.utils.config import Config, StringConfig, tweaks from calibre.utils.formatter import TemplateFormatter from calibre.utils.filenames import shorten_components_to, supports_long_names, \ - ascii_filename, sanitize_file_name + ascii_filename from calibre.ebooks.metadata.opf2 import metadata_to_opf -from calibre.ebooks.metadata.meta import set_metadata -from calibre.constants import preferred_encoding, filesystem_encoding +from calibre.constants import preferred_encoding from calibre.ebooks.metadata import fmt_sidx from calibre.ebooks.metadata import title_sort -from calibre import strftime, prints +from calibre.utils.date import parse_date +from calibre import strftime, prints, sanitize_file_name_unicode plugboard_any_device_value = 'any device' plugboard_any_format_value = 'any format' @@ -43,6 +43,8 @@ FORMAT_ARG_DESCS = dict( publisher=_('The publisher'), timestamp=_('The date'), pubdate=_('The published date'), + last_modified=_('The date when the metadata for this book record' + ' was last modified'), id=_('The calibre internal id') ) @@ -51,6 +53,24 @@ for x in FORMAT_ARG_DESCS: FORMAT_ARGS[x] = '' +def find_plugboard(device_name, format, plugboards): + cpb = None + if format in plugboards: + pb = plugboards[format] + if device_name in pb: + cpb = pb[device_name] + elif plugboard_any_device_value in pb: + cpb = pb[plugboard_any_device_value] + if not cpb and plugboard_any_format_value in plugboards: + pb = plugboards[plugboard_any_format_value] + if device_name in pb: + cpb = pb[device_name] + elif plugboard_any_device_value in pb: + cpb = pb[plugboard_any_device_value] + if DEBUG: + prints('Device using plugboard', format, device_name, cpb) + return cpb + def config(defaults=None): if defaults is None: c = Config('save_to_disk', _('Options to control saving to disk')) @@ -114,6 +134,8 @@ class SafeFormat(TemplateFormatter): ''' def get_value(self, key, args, kwargs): + if key == '': + return '' try: key = key.lower() try: @@ -170,18 +192,20 @@ def get_components(template, mi, id, timefmt='%b %Y', length=250, else: template = re.sub(r'\{series_index[^}]*?\}', '', template) if mi.rating is not None: - format_args['rating'] = mi.format_rating() + format_args['rating'] = mi.format_rating(divide_by=2.0) if hasattr(mi.timestamp, 'timetuple'): format_args['timestamp'] = strftime(timefmt, mi.timestamp.timetuple()) if hasattr(mi.pubdate, 'timetuple'): format_args['pubdate'] = strftime(timefmt, mi.pubdate.timetuple()) + if hasattr(mi, 'last_modified') and hasattr(mi.last_modified, 'timetuple'): + format_args['last_modified'] = strftime(timefmt, mi.last_modified.timetuple()) + format_args['id'] = str(id) # Now format the custom fields custom_metadata = mi.get_all_user_metadata(make_copy=False) for key in custom_metadata: if key in format_args: cm = custom_metadata[key] - ## TODO: NEWMETA: should ratings be divided by 2? The standard rating isn't... if cm['datatype'] == 'series': format_args[key] = title_sort(format_args[key], order=tsorder) if key+'_index' in format_args: @@ -190,6 +214,9 @@ def get_components(template, mi, id, timefmt='%b %Y', length=250, format_args[key] = strftime(timefmt, format_args[key].timetuple()) elif cm['datatype'] == 'bool': format_args[key] = _('yes') if format_args[key] else _('no') + elif cm['datatype'] == 'rating': + format_args[key] = mi.format_rating(format_args[key], + divide_by=2.0) elif cm['datatype'] in ['int', 'float']: if format_args[key] != 0: format_args[key] = unicode(format_args[key]) @@ -197,12 +224,10 @@ def get_components(template, mi, id, timefmt='%b %Y', length=250, format_args[key] = '' components = SafeFormat().safe_format(template, format_args, 'G_C-EXCEPTION!', mi) - components = [x.strip() for x in components.split('/') if x.strip()] + components = [x.strip() for x in components.split('/')] components = [sanitize_func(x) for x in components if x] if not components: components = [str(id)] - components = [x.encode(filesystem_encoding, 'replace') if isinstance(x, - unicode) else x for x in components] if to_lowercase: components = [x.lower() for x in components] if replace_whitespace: @@ -237,6 +262,7 @@ def save_book_to_disk(id_, db, root, opts, length): def do_save_book_to_disk(id_, mi, cover, plugboards, format_map, root, opts, length): + from calibre.ebooks.metadata.meta import set_metadata available_formats = [x.lower().strip() for x in format_map.keys()] if opts.formats == 'all': asked_formats = available_formats @@ -247,7 +273,7 @@ def do_save_book_to_disk(id_, mi, cover, plugboards, return True, id_, mi.title components = get_components(opts.template, mi, id_, opts.timefmt, length, - ascii_filename if opts.asciiize else sanitize_file_name, + ascii_filename if opts.asciiize else sanitize_file_name_unicode, to_lowercase=opts.to_lowercase, replace_whitespace=opts.replace_whitespace) base_path = os.path.join(root, *components) @@ -281,20 +307,7 @@ def do_save_book_to_disk(id_, mi, cover, plugboards, written = False for fmt in formats: global plugboard_save_to_disk_value, plugboard_any_format_value - dev_name = plugboard_save_to_disk_value - cpb = None - if fmt in plugboards: - cpb = plugboards[fmt] - if dev_name in cpb: - cpb = cpb[dev_name] - else: - cpb = None - if cpb is None and plugboard_any_format_value in plugboards: - cpb = plugboards[plugboard_any_format_value] - if dev_name in cpb: - cpb = cpb[dev_name] - else: - cpb = None + cpb = find_plugboard(plugboard_save_to_disk_value, fmt, plugboards) # Leave this here for a while, in case problems arise. if cpb is not None: prints('Save-to-disk using plugboard:', fmt, cpb) @@ -329,8 +342,6 @@ def do_save_book_to_disk(id_, mi, cover, plugboards, def _sanitize_args(root, opts): if opts is None: opts = config().parse() - if isinstance(root, unicode): - root = root.encode(filesystem_encoding) root = os.path.abspath(root) opts.template = preprocess_template(opts.template) @@ -374,10 +385,14 @@ def save_serialized_to_disk(ids, data, plugboards, root, opts, callback): root, opts, length = _sanitize_args(root, opts) failures = [] for x in ids: - opf, cover, format_map = data[x] + opf, cover, format_map, last_modified = data[x] if isinstance(opf, unicode): opf = opf.encode('utf-8') mi = OPF(cStringIO.StringIO(opf)).to_book_metadata() + try: + mi.last_modified = parse_date(last_modified) + except: + pass tb = '' try: failed, id, title = do_save_book_to_disk(x, mi, cover, plugboards, diff --git a/src/calibre/library/schema_upgrades.py b/src/calibre/library/schema_upgrades.py index 0b7a3f5350..3fc9a2368a 100644 --- a/src/calibre/library/schema_upgrades.py +++ b/src/calibre/library/schema_upgrades.py @@ -8,6 +8,8 @@ __docformat__ = 'restructuredtext en' import os +from calibre.utils.date import isoformat, DEFAULT_DATE + class SchemaUpgrade(object): def __init__(self): @@ -468,4 +470,134 @@ class SchemaUpgrade(object): ''' self.conn.executescript(script) + def upgrade_version_18(self): + ''' + Add a library UUID. + Add an identifiers table. + Add a languages table. + Add a last_modified column. + NOTE: You cannot downgrade after this update, if you do + any changes you make to book isbns will be lost. + ''' + script = ''' + DROP TABLE IF EXISTS library_id; + CREATE TABLE library_id ( id INTEGER PRIMARY KEY, + uuid TEXT NOT NULL, + UNIQUE(uuid) + ); + + DROP TABLE IF EXISTS identifiers; + CREATE TABLE identifiers ( id INTEGER PRIMARY KEY, + book INTEGER NON NULL, + type TEXT NON NULL DEFAULT "isbn" COLLATE NOCASE, + val TEXT NON NULL COLLATE NOCASE, + UNIQUE(book, type) + ); + + DROP TABLE IF EXISTS languages; + CREATE TABLE languages ( id INTEGER PRIMARY KEY, + lang_code TEXT NON NULL COLLATE NOCASE, + UNIQUE(lang_code) + ); + + DROP TABLE IF EXISTS books_languages_link; + CREATE TABLE books_languages_link ( id INTEGER PRIMARY KEY, + book INTEGER NOT NULL, + lang_code INTEGER NOT NULL, + item_order INTEGER NOT NULL DEFAULT 0, + UNIQUE(book, lang_code) + ); + + DROP TRIGGER IF EXISTS fkc_delete_on_languages; + CREATE TRIGGER fkc_delete_on_languages + BEFORE DELETE ON languages + BEGIN + SELECT CASE + WHEN (SELECT COUNT(id) FROM books_languages_link WHERE lang_code=OLD.id) > 0 + THEN RAISE(ABORT, 'Foreign key violation: language is still referenced') + END; + END; + + DROP TRIGGER IF EXISTS fkc_delete_on_languages_link; + CREATE TRIGGER fkc_delete_on_languages_link + BEFORE INSERT ON books_languages_link + BEGIN + SELECT CASE + WHEN (SELECT id from books WHERE id=NEW.book) IS NULL + THEN RAISE(ABORT, 'Foreign key violation: book not in books') + WHEN (SELECT id from languages WHERE id=NEW.lang_code) IS NULL + THEN RAISE(ABORT, 'Foreign key violation: lang_code not in languages') + END; + END; + + DROP TRIGGER IF EXISTS fkc_update_books_languages_link_a; + CREATE TRIGGER fkc_update_books_languages_link_a + BEFORE UPDATE OF book ON books_languages_link + BEGIN + SELECT CASE + WHEN (SELECT id from books WHERE id=NEW.book) IS NULL + THEN RAISE(ABORT, 'Foreign key violation: book not in books') + END; + END; + DROP TRIGGER IF EXISTS fkc_update_books_languages_link_b; + CREATE TRIGGER fkc_update_books_languages_link_b + BEFORE UPDATE OF lang_code ON books_languages_link + BEGIN + SELECT CASE + WHEN (SELECT id from languages WHERE id=NEW.lang_code) IS NULL + THEN RAISE(ABORT, 'Foreign key violation: lang_code not in languages') + END; + END; + + DROP INDEX IF EXISTS books_languages_link_aidx; + CREATE INDEX books_languages_link_aidx ON books_languages_link (lang_code); + DROP INDEX IF EXISTS books_languages_link_bidx; + CREATE INDEX books_languages_link_bidx ON books_languages_link (book); + DROP INDEX IF EXISTS languages_idx; + CREATE INDEX languages_idx ON languages (lang_code COLLATE NOCASE); + + DROP TRIGGER IF EXISTS books_delete_trg; + CREATE TRIGGER books_delete_trg + AFTER DELETE ON books + BEGIN + DELETE FROM books_authors_link WHERE book=OLD.id; + DELETE FROM books_publishers_link WHERE book=OLD.id; + DELETE FROM books_ratings_link WHERE book=OLD.id; + DELETE FROM books_series_link WHERE book=OLD.id; + DELETE FROM books_tags_link WHERE book=OLD.id; + DELETE FROM books_languages_link WHERE book=OLD.id; + DELETE FROM data WHERE book=OLD.id; + DELETE FROM comments WHERE book=OLD.id; + DELETE FROM conversion_options WHERE book=OLD.id; + DELETE FROM books_plugin_data WHERE book=OLD.id; + DELETE FROM identifiers WHERE book=OLD.id; + END; + + INSERT INTO identifiers (book, val) SELECT id,isbn FROM books WHERE isbn; + + ALTER TABLE books ADD COLUMN last_modified TIMESTAMP NOT NULL DEFAULT "%s"; + + '''%isoformat(DEFAULT_DATE, sep=' ') + # Sqlite does not support non constant default values in alter + # statements + self.conn.executescript(script) + + def upgrade_version_19(self): + recipes = self.conn.get('SELECT id,title,script FROM feeds') + if recipes: + from calibre.web.feeds.recipes import custom_recipes, \ + custom_recipe_filename + bdir = os.path.dirname(custom_recipes.file_path) + for id_, title, script in recipes: + existing = frozenset(map(int, custom_recipes.iterkeys())) + if id_ in existing: + id_ = max(existing) + 1000 + id_ = str(id_) + fname = custom_recipe_filename(id_, title) + custom_recipes[id_] = (title, fname) + if isinstance(script, unicode): + script = script.encode('utf-8') + with open(os.path.join(bdir, fname), 'wb') as f: + f.write(script) + diff --git a/src/calibre/library/server/__init__.py b/src/calibre/library/server/__init__.py index 244669f50a..950c881d8d 100644 --- a/src/calibre/library/server/__init__.py +++ b/src/calibre/library/server/__init__.py @@ -10,7 +10,7 @@ import os from calibre.utils.config import Config, StringConfig, config_dir, tweaks -listen_on = '0.0.0.0' +listen_on = tweaks['server_listen_on'] log_access_file = os.path.join(config_dir, 'server_access_log.txt') diff --git a/src/calibre/library/server/base.py b/src/calibre/library/server/base.py index 83d395dec5..319feefa44 100644 --- a/src/calibre/library/server/base.py +++ b/src/calibre/library/server/base.py @@ -24,6 +24,8 @@ from calibre.library.server.xml import XMLServer from calibre.library.server.opds import OPDSServer from calibre.library.server.cache import Cache from calibre.library.server.browse import BrowseServer +from calibre.utils.search_query_parser import saved_searches +from calibre import prints class DispatchController(object): # {{{ @@ -178,7 +180,12 @@ class LibraryServer(ContentServer, MobileServer, XMLServer, OPDSServer, Cache, def set_search_restriction(self, restriction): self.search_restriction_name = restriction if restriction: - self.search_restriction = 'search:"%s"'%restriction + if restriction not in saved_searches().names(): + prints('WARNING: Content server: search restriction ', + restriction, ' does not exist') + self.search_restriction = '' + else: + self.search_restriction = 'search:"%s"'%restriction else: self.search_restriction = '' self.reset_caches() @@ -211,10 +218,15 @@ class LibraryServer(ContentServer, MobileServer, XMLServer, OPDSServer, Cache, cherrypy.engine.start() except: ip = get_external_ip() - if not ip or ip == '127.0.0.1': + if not ip or ip.startswith('127.'): raise cherrypy.log('Trying to bind to single interface: '+ip) + # Change the host we listen on cherrypy.config.update({'server.socket_host' : ip}) + # This ensures that the change is actually applied + cherrypy.server.socket_host = ip + cherrypy.server.httpserver = cherrypy.server.instance = None + cherrypy.engine.start() self.is_running = True @@ -222,8 +234,10 @@ class LibraryServer(ContentServer, MobileServer, XMLServer, OPDSServer, Cache, # cherrypy.engine.signal_handler.subscribe() cherrypy.engine.block() - except Exception, e: + except Exception as e: self.exception = e + import traceback + traceback.print_exc() finally: self.is_running = False try: diff --git a/src/calibre/library/server/browse.py b/src/calibre/library/server/browse.py index 7dfedcb6ff..139e2b3ef9 100644 --- a/src/calibre/library/server/browse.py +++ b/src/calibre/library/server/browse.py @@ -7,15 +7,15 @@ __docformat__ = 'restructuredtext en' import operator, os, json, re from binascii import hexlify, unhexlify +from collections import OrderedDict import cherrypy from calibre.constants import filesystem_encoding from calibre import isbytestring, force_unicode, fit_image, \ - prepare_string_for_xml as xml -from calibre.utils.ordered_dict import OrderedDict + prepare_string_for_xml from calibre.utils.filenames import ascii_filename -from calibre.utils.config import prefs, tweaks +from calibre.utils.config import prefs from calibre.utils.icu import sort_key from calibre.utils.magick import Image from calibre.library.comments import comments_to_html @@ -23,6 +23,10 @@ from calibre.library.server import custom_fields_to_display from calibre.library.field_metadata import category_icon_map from calibre.library.server.utils import quote, unquote +def xml(*args, **kwargs): + ans = prepare_string_for_xml(*args, **kwargs) + return ans.replace(''', ''') + def render_book_list(ids, prefix, suffix=''): # {{{ pages = [] num = len(ids) @@ -151,8 +155,7 @@ def get_category_items(category, items, restriction, datatype, prefix): # {{{ '

    {1}
    ' '
    {2}
    ') rating, rstring = render_rating(i.avg_rating, prefix) - if i.category == 'authors' and \ - tweaks['categories_use_field_for_author_name'] == 'author_sort': + if i.use_sort_as_name: name = xml(i.sort) else: name = xml(i.name) @@ -346,7 +349,7 @@ class BrowseServer(object): for category in sorted(categories, key=lambda x: sort_key(getter(x))): if len(categories[category]) == 0: continue - if category == 'formats': + if category in ('formats', 'identifiers'): continue meta = category_meta.get(category, None) if meta is None: @@ -626,6 +629,8 @@ class BrowseServer(object): elif category == 'allbooks': ids = all_ids else: + if fm.get(category, {'datatype':None})['datatype'] == 'composite': + cid = cid.decode('utf-8') q = category if q == 'news': q = 'tags' @@ -666,7 +671,7 @@ class BrowseServer(object): if add_category_links: added_key = False fm = mi.metadata_for_field(key) - if val and fm and fm['is_category'] and \ + if val and fm and fm['is_category'] and not fm['is_csp'] and\ key != 'formats' and fm['datatype'] not in ['rating']: categories = mi.get(key) if isinstance(categories, basestring): @@ -690,7 +695,10 @@ class BrowseServer(object): xml(href, True), xml(val if len(dbtags) == 1 else tag.name), xml(key, True))) - join = ' & ' if key == 'authors' else ', ' + join = ' & ' if key == 'authors' or \ + (fm['is_custom'] and + fm['display'].get('is_names', False)) \ + else ', ' args[key] = join.join(vals) added_key = True if not added_key: diff --git a/src/calibre/library/server/cache.py b/src/calibre/library/server/cache.py index cc4f7a3886..2ad7b543cb 100644 --- a/src/calibre/library/server/cache.py +++ b/src/calibre/library/server/cache.py @@ -5,8 +5,9 @@ __license__ = 'GPL v3' __copyright__ = '2010, Kovid Goyal ' __docformat__ = 'restructuredtext en' +from collections import OrderedDict + from calibre.utils.date import utcnow -from calibre.utils.ordered_dict import OrderedDict class Cache(object): diff --git a/src/calibre/library/server/content.py b/src/calibre/library/server/content.py index 11ea2b951e..08de4faecd 100644 --- a/src/calibre/library/server/content.py +++ b/src/calibre/library/server/content.py @@ -12,13 +12,18 @@ import cherrypy from calibre import fit_image, guess_type from calibre.utils.date import fromtimestamp from calibre.library.caches import SortKeyGenerator +from calibre.library.save_to_disk import find_plugboard + from calibre.utils.magick.draw import save_cover_data_to, Image, \ thumbnail as generate_thumbnail +plugboard_content_server_value = 'content_server' +plugboard_content_server_formats = ['epub'] + class CSSortKeyGenerator(SortKeyGenerator): - def __init__(self, fields, fm): - SortKeyGenerator.__init__(self, fields, fm, None) + def __init__(self, fields, fm, db_prefs): + SortKeyGenerator.__init__(self, fields, fm, None, db_prefs) def __call__(self, record): return self.itervals(record).next() @@ -56,7 +61,8 @@ class ContentServer(object): field = self.db.data.sanitize_sort_field_name(field) if field not in self.db.field_metadata.sortable_field_keys(): raise cherrypy.HTTPError(400, '%s is not a valid sort field'%field) - keyg = CSSortKeyGenerator([(field, order)], self.db.field_metadata) + keyg = CSSortKeyGenerator([(field, order)], self.db.field_metadata, + self.db.prefs) items.sort(key=keyg, reverse=not order) # }}} @@ -169,7 +175,7 @@ class ContentServer(object): return cover return save_cover_data_to(img, 'img.jpg', return_data=True, resize_to=(width, height)) - except Exception, err: + except Exception as err: import traceback cherrypy.log.error('Failed to generate cover:') cherrypy.log.error(traceback.print_exc()) @@ -182,16 +188,30 @@ class ContentServer(object): if fmt is None: raise cherrypy.HTTPError(404, 'book: %d does not have format: %s'%(id, format)) if format == 'EPUB': + # Get the original metadata + mi = self.db.get_metadata(id, index_is_id=True) + + # Get any EPUB plugboards for the content server + plugboards = self.db.prefs.get('plugboards', {}) + cpb = find_plugboard(plugboard_content_server_value, + 'epub', plugboards) + if cpb: + # Transform the metadata via the plugboard + newmi = mi.deepcopy_metadata() + newmi.template_to_attribute(mi, cpb) + else: + newmi = mi + + # Write the updated file from tempfile import TemporaryFile from calibre.ebooks.metadata.meta import set_metadata raw = fmt.read() fmt = TemporaryFile() fmt.write(raw) fmt.seek(0) - set_metadata(fmt, self.db.get_metadata(id, index_is_id=True, - get_cover=True), - 'epub') + set_metadata(fmt, newmi, 'epub') fmt.seek(0) + mt = guess_type('dummy.'+format.lower())[0] if mt is None: mt = 'application/octet-stream' diff --git a/src/calibre/library/server/main.py b/src/calibre/library/server/main.py index e4de710c6a..3a6f918022 100644 --- a/src/calibre/library/server/main.py +++ b/src/calibre/library/server/main.py @@ -69,7 +69,7 @@ def daemonize(stdin='/dev/null', stdout='/dev/null', stderr='/dev/null'): if pid > 0: # exit first parent sys.exit(0) - except OSError, e: + except OSError as e: print >>sys.stderr, "fork #1 failed: %d (%s)" % (e.errno, e.strerror) sys.exit(1) @@ -84,7 +84,7 @@ def daemonize(stdin='/dev/null', stdout='/dev/null', stderr='/dev/null'): if pid > 0: # exit from second parent sys.exit(0) - except OSError, e: + except OSError as e: print >>sys.stderr, "fork #2 failed: %d (%s)" % (e.errno, e.strerror) sys.exit(1) diff --git a/src/calibre/library/server/opds.py b/src/calibre/library/server/opds.py index 72a802eaa9..5f6180e68a 100644 --- a/src/calibre/library/server/opds.py +++ b/src/calibre/library/server/opds.py @@ -8,6 +8,7 @@ __docformat__ = 'restructuredtext en' import hashlib, binascii from functools import partial from itertools import repeat +from collections import OrderedDict from lxml import etree, html from lxml.builder import ElementMaker @@ -21,8 +22,6 @@ from calibre.library.server import custom_fields_to_display from calibre.library.server.utils import format_tag_string, Offsets from calibre import guess_type, prepare_string_for_xml as xml from calibre.utils.icu import sort_key -from calibre.utils.ordered_dict import OrderedDict -from calibre.utils.config import tweaks BASE_HREFS = { 0 : '/stanza', @@ -126,8 +125,7 @@ def CATALOG_ENTRY(item, item_kind, base_href, version, updated, count = (_('%d books') if item.count > 1 else _('%d book'))%item.count if ignore_count: count = '' - if item.category == 'authors' and \ - tweaks['categories_use_field_for_author_name'] == 'author_sort': + if item.use_sort_as_name: name = item.sort else: name = item.name @@ -580,7 +578,7 @@ class OPDSServer(object): for category in sorted(categories, key=lambda x: sort_key(getter(x))): if len(categories[category]) == 0: continue - if category == 'formats': + if category in ('formats', 'identifiers'): continue meta = category_meta.get(category, None) if meta is None: diff --git a/src/calibre/library/server/xml.py b/src/calibre/library/server/xml.py index efbceb9771..14955dc541 100644 --- a/src/calibre/library/server/xml.py +++ b/src/calibre/library/server/xml.py @@ -89,13 +89,16 @@ class XMLServer(object): for x in ('id', 'title', 'sort', 'author_sort', 'rating', 'size'): kwargs[x] = serialize(record[FM[x]]) - for x in ('isbn', 'formats', 'series', 'tags', 'publisher', - 'comments'): + for x in ('formats', 'series', 'tags', 'publisher', + 'comments', 'identifiers'): y = record[FM[x]] if x == 'tags': y = format_tag_string(y, ',', ignore_max=True) kwargs[x] = serialize(y) if y else '' + isbn = self.db.isbn(record[FM['id']], index_is_id=True) + kwargs['isbn'] = serialize(isbn if isbn else '') + kwargs['safe_title'] = ascii_filename(kwargs['title']) c = kwargs.pop('comments') diff --git a/src/calibre/library/sqlite.py b/src/calibre/library/sqlite.py index 622d6b8459..511106fe7b 100644 --- a/src/calibre/library/sqlite.py +++ b/src/calibre/library/sqlite.py @@ -8,6 +8,7 @@ Wrapper for multi-threaded access to a single sqlite database connection. Serial all calls. ''' import sqlite3 as sqlite, traceback, time, uuid, sys, os +import repr as reprlib from sqlite3 import IntegrityError, OperationalError from threading import Thread from Queue import Queue @@ -16,18 +17,54 @@ from datetime import datetime from functools import partial from calibre.ebooks.metadata import title_sort, author_to_author_sort -from calibre.utils.date import parse_date, isoformat +from calibre.utils.date import parse_date, isoformat, local_tz from calibre import isbytestring, force_unicode -from calibre.constants import iswindows, DEBUG +from calibre.constants import iswindows, DEBUG, plugins from calibre.utils.icu import strcmp +from calibre import prints + +from dateutil.tz import tzoffset global_lock = RLock() -def convert_timestamp(val): +_c_speedup = plugins['speedup'][0] + +def _c_convert_timestamp(val): + if not val: + return None + try: + ret = _c_speedup.parse_date(val.strip()) + except: + ret = None + if ret is None: + return parse_date(val, as_utc=False) + year, month, day, hour, minutes, seconds, tzsecs = ret + return datetime(year, month, day, hour, minutes, seconds, + tzinfo=tzoffset(None, tzsecs)).astimezone(local_tz) + +def _py_convert_timestamp(val): if val: + tzsecs = 0 + try: + sign = {'+':1, '-':-1}.get(val[-6], None) + if sign is not None: + tzsecs = 60*((int(val[-5:-3])*60 + int(val[-2:])) * sign) + year = int(val[0:4]) + month = int(val[5:7]) + day = int(val[8:10]) + hour = int(val[11:13]) + min = int(val[14:16]) + sec = int(val[17:19]) + return datetime(year, month, day, hour, min, sec, + tzinfo=tzoffset(None, tzsecs)) + except: + pass return parse_date(val, as_utc=False) return None +convert_timestamp = _py_convert_timestamp if _c_speedup is None else \ + _c_convert_timestamp + def adapt_datetime(dt): return isoformat(dt, sep=' ') @@ -87,6 +124,18 @@ class SortedConcatenate(object): class SafeSortedConcatenate(SortedConcatenate): sep = '|' +class IdentifiersConcat(object): + '''String concatenation aggregator for the identifiers map''' + def __init__(self): + self.ans = [] + + def step(self, key, val): + self.ans.append(u'%s:%s'%(key, val)) + + def finalize(self): + return ','.join(self.ans) + + class AumSortedConcatenate(object): '''String concatenation aggregator for the author sort map''' def __init__(self): @@ -144,7 +193,7 @@ def load_c_extensions(conn, debug=DEBUG): conn.load_extension(ext_path) conn.enable_load_extension(False) return True - except Exception, e: + except Exception as e: if debug: print 'Failed to load high performance sqlite C extension' print e @@ -170,13 +219,13 @@ class DBThread(Thread): detect_types=sqlite.PARSE_DECLTYPES|sqlite.PARSE_COLNAMES) self.conn.execute('pragma cache_size=5000') encoding = self.conn.execute('pragma encoding').fetchone()[0] - c_ext_loaded = load_c_extensions(self.conn) + self.conn.create_aggregate('sortconcat', 2, SortedConcatenate) + self.conn.create_aggregate('sort_concat', 2, SafeSortedConcatenate) + self.conn.create_aggregate('identifiers_concat', 2, IdentifiersConcat) + load_c_extensions(self.conn) self.conn.row_factory = sqlite.Row if self.row_factory else lambda cursor, row : list(row) self.conn.create_aggregate('concat', 1, Concatenate) self.conn.create_aggregate('aum_sortconcat', 3, AumSortedConcatenate) - if not c_ext_loaded: - self.conn.create_aggregate('sortconcat', 2, SortedConcatenate) - self.conn.create_aggregate('sort_concat', 2, SafeSortedConcatenate) self.conn.create_collation('PYNOCASE', partial(pynocase, encoding=encoding)) self.conn.create_function('title_sort', 1, title_sort) @@ -198,32 +247,36 @@ class DBThread(Thread): if func == 'dump': try: ok, res = True, tuple(self.conn.iterdump()) - except Exception, err: + except Exception as err: ok, res = False, (err, traceback.format_exc()) elif func == 'create_dynamic_filter': try: f = DynamicFilter(args[0]) self.conn.create_function(args[0], 1, f) ok, res = True, f - except Exception, err: + except Exception as err: ok, res = False, (err, traceback.format_exc()) else: - func = getattr(self.conn, func) + bfunc = getattr(self.conn, func) try: for i in range(3): try: - ok, res = True, func(*args, **kwargs) + ok, res = True, bfunc(*args, **kwargs) break - except OperationalError, err: + except OperationalError as err: # Retry if unable to open db file - if 'unable to open' not in str(err) or i == 2: + e = str(err) + if 'unable to open' not in e or i == 2: + if 'unable to open' in e: + prints('Unable to open database for func', + func, reprlib.repr(args), + reprlib.repr(kwargs)) raise - traceback.print_exc() time.sleep(0.5) - except Exception, err: + except Exception as err: ok, res = False, (err, traceback.format_exc()) self.results.put((ok, res)) - except Exception, err: + except Exception as err: self.unhandled_error = (err, traceback.format_exc()) class DatabaseException(Exception): diff --git a/src/calibre/library/sqlite_custom.c b/src/calibre/library/sqlite_custom.c index 650c474c2c..dee17c79d4 100644 --- a/src/calibre/library/sqlite_custom.c +++ b/src/calibre/library/sqlite_custom.c @@ -77,6 +77,7 @@ static void sort_concat_free(SortConcatList *list) { free(list->vals[i]->val); free(list->vals[i]); } + free(list->vals); } static int sort_concat_cmp(const void *a_, const void *b_) { @@ -142,11 +143,102 @@ static void sort_concat_finalize2(sqlite3_context *context) { // }}} +// identifiers_concat {{{ + +typedef struct { + char *val; + size_t length; +} IdentifiersConcatItem; + +typedef struct { + IdentifiersConcatItem **vals; + size_t count; + size_t length; +} IdentifiersConcatList; + +static void identifiers_concat_step(sqlite3_context *context, int argc, sqlite3_value **argv) { + const char *key, *val; + size_t len = 0; + IdentifiersConcatList *list; + + assert(argc == 2); + + list = (IdentifiersConcatList*) sqlite3_aggregate_context(context, sizeof(*list)); + if (list == NULL) return; + + if (list->vals == NULL) { + list->vals = (IdentifiersConcatItem**)calloc(100, sizeof(IdentifiersConcatItem*)); + if (list->vals == NULL) return; + list->length = 100; + list->count = 0; + } + + if (list->count == list->length) { + list->vals = (IdentifiersConcatItem**)realloc(list->vals, list->length + 100); + if (list->vals == NULL) return; + list->length = list->length + 100; + } + + list->vals[list->count] = (IdentifiersConcatItem*)calloc(1, sizeof(IdentifiersConcatItem)); + if (list->vals[list->count] == NULL) return; + + key = (char*) sqlite3_value_text(argv[0]); + val = (char*) sqlite3_value_text(argv[1]); + if (key == NULL || val == NULL) {return;} + len = strlen(key) + strlen(val) + 1; + + list->vals[list->count]->val = (char*)calloc(len+1, sizeof(char)); + if (list->vals[list->count]->val == NULL) return; + snprintf(list->vals[list->count]->val, len+1, "%s:%s", key, val); + list->vals[list->count]->length = len; + + list->count = list->count + 1; + +} + + +static void identifiers_concat_finalize(sqlite3_context *context) { + IdentifiersConcatList *list; + IdentifiersConcatItem *item; + char *ans, *pos; + size_t sz = 0, i; + + list = (IdentifiersConcatList*) sqlite3_aggregate_context(context, sizeof(*list)); + if (list == NULL || list->vals == NULL || list->count < 1) return; + + for (i = 0; i < list->count; i++) { + sz += list->vals[i]->length; + } + sz += list->count; // Space for commas + ans = (char*)calloc(sz+2, sizeof(char)); + if (ans == NULL) return; + + pos = ans; + + for (i = 0; i < list->count; i++) { + item = list->vals[i]; + if (item == NULL || item->val == NULL) continue; + memcpy(pos, item->val, item->length); + pos += item->length; + *pos = ','; + pos += 1; + free(item->val); + free(item); + } + *(pos-1) = 0; // Remove trailing comma + sqlite3_result_text(context, ans, -1, SQLITE_TRANSIENT); + free(ans); + free(list->vals); +} + +// }}} + MYEXPORT int sqlite3_extension_init( sqlite3 *db, char **pzErrMsg, const sqlite3_api_routines *pApi){ SQLITE_EXTENSION_INIT2(pApi); sqlite3_create_function(db, "sortconcat", 2, SQLITE_UTF8, NULL, NULL, sort_concat_step, sort_concat_finalize); sqlite3_create_function(db, "sort_concat", 2, SQLITE_UTF8, NULL, NULL, sort_concat_step, sort_concat_finalize2); + sqlite3_create_function(db, "identifiers_concat", 2, SQLITE_UTF8, NULL, NULL, identifiers_concat_step, identifiers_concat_finalize); return 0; } diff --git a/src/calibre/linux.py b/src/calibre/linux.py index 22f8af56c2..9e58d4f638 100644 --- a/src/calibre/linux.py +++ b/src/calibre/linux.py @@ -3,11 +3,11 @@ __copyright__ = '2008, Kovid Goyal ' ''' Post installation script for linux ''' -import sys, os, cPickle, textwrap, stat +import sys, os, cPickle, textwrap, stat, importlib from subprocess import check_call from calibre import __appname__, prints, guess_type -from calibre.constants import islinux, isfreebsd +from calibre.constants import islinux, isnetbsd, isbsd from calibre.customize.ui import all_input_formats from calibre.ptempfile import TemporaryDirectory from calibre import CurrentDir @@ -30,7 +30,7 @@ entry_points = { 'calibre-customize = calibre.customize.ui:main', 'calibre-complete = calibre.utils.complete:main', 'pdfmanipulate = calibre.ebooks.pdf.manipulate.cli:main', - 'fetch-ebook-metadata = calibre.ebooks.metadata.fetch:main', + 'fetch-ebook-metadata = calibre.ebooks.metadata.sources.cli:main', 'epub-fix = calibre.ebooks.epub.fix.main:main', 'calibre-smtp = calibre.utils.smtp:main', ], @@ -59,7 +59,7 @@ for x in {manifest!r}: shutil.rmtree(x) else: os.unlink(x) - except Exception, e: + except Exception as e: print 'Failed to delete', x print '\t', e @@ -136,20 +136,21 @@ class PostInstall: self.icon_resources = [] self.menu_resources = [] self.mime_resources = [] - if islinux: + if islinux or isbsd: self.setup_completion() self.install_man_pages() - if islinux: + if islinux or isbsd: self.setup_desktop_integration() self.create_uninstaller() from calibre.utils.config import config_dir if os.path.exists(config_dir): os.chdir(config_dir) - if islinux: + if islinux or isbsd: for f in os.listdir('.'): if os.stat(f).st_uid == 0: - os.rmdir(f) if os.path.isdir(f) else os.unlink(f) + import shutil + shutil.rmtree(f) if os.path.isdir(f) else os.unlink(f) if os.stat(config_dir).st_uid == 0: os.rmdir(config_dir) @@ -183,7 +184,7 @@ class PostInstall: from calibre.ebooks.lrf.lrfparser import option_parser as lrf2lrsop from calibre.gui2.lrf_renderer.main import option_parser as lrfviewerop from calibre.gui2.viewer.main import option_parser as viewer_op - from calibre.ebooks.metadata.fetch import option_parser as fem_op + from calibre.ebooks.metadata.sources.cli import option_parser as fem_op from calibre.gui2.main import option_parser as guiop from calibre.utils.smtp import option_parser as smtp_op from calibre.library.server.main import option_parser as serv_op @@ -195,7 +196,10 @@ class PostInstall: if os.path.exists(bc): f = os.path.join(bc, 'calibre') else: - f = os.path.join(self.opts.staging_etc, 'bash_completion.d/calibre') + if isnetbsd: + f = os.path.join(self.opts.staging_root, 'share/bash_completion.d/calibre') + else: + f = os.path.join(self.opts.staging_etc, 'bash_completion.d/calibre') if not os.path.exists(os.path.dirname(f)): os.makedirs(os.path.dirname(f)) self.manifest.append(f) @@ -285,7 +289,7 @@ class PostInstall: complete -o nospace -C calibre-complete ebook-convert ''')) - except TypeError, err: + except TypeError as err: if 'resolve_entities' in str(err): print 'You need python-lxml >= 2.0.5 for calibre' sys.exit(1) @@ -299,7 +303,7 @@ class PostInstall: def install_man_pages(self): # {{{ try: from calibre.utils.help2man import create_man_page - if isfreebsd: + if isbsd: manpath = os.path.join(self.opts.staging_root, 'man/man1') else: manpath = os.path.join(self.opts.staging_sharedir, 'man/man1') @@ -309,13 +313,13 @@ class PostInstall: for src in entry_points['console_scripts']: prog, right = src.split('=') prog = prog.strip() - module = __import__(right.split(':')[0].strip(), fromlist=['a']) + module = importlib.import_module(right.split(':')[0].strip()) parser = getattr(module, 'option_parser', None) if parser is None: continue parser = parser() raw = create_man_page(prog, parser) - if isfreebsd: + if isbsd: manfile = os.path.join(manpath, prog+'.1') else: manfile = os.path.join(manpath, prog+'.1'+__appname__+'.bz2') @@ -352,7 +356,7 @@ class PostInstall: mimetypes = set([]) for x in all_input_formats(): mt = guess_type('dummy.'+x)[0] - if mt and 'chemical' not in mt: + if mt and 'chemical' not in mt and 'ctc-posml' not in mt: mimetypes.add(mt) def write_mimetypes(f): @@ -372,11 +376,10 @@ class PostInstall: des = ('calibre-gui.desktop', 'calibre-lrfviewer.desktop', 'calibre-ebook-viewer.desktop') for x in des: - cmd = ['xdg-desktop-menu', 'install', './'+x] - if x != des[-1]: - cmd.insert(2, '--noupdate') + cmd = ['xdg-desktop-menu', 'install', '--noupdate', './'+x] check_call(' '.join(cmd), shell=True) self.menu_resources.append(x) + check_call(['xdg-desktop-menu', 'forceupdate']) f = open('calibre-mimetypes', 'wb') f.write(MIME) f.close() diff --git a/src/calibre/manual/conf.py b/src/calibre/manual/conf.py index fc8962bcfd..91a4395007 100644 --- a/src/calibre/manual/conf.py +++ b/src/calibre/manual/conf.py @@ -43,7 +43,7 @@ language = 'en' # General substitutions. project = __appname__ -copyright = '2008, Kovid Goyal' +copyright = 'Kovid Goyal' # The default replacements for |version| and |release|, also used in various # other places throughout the built documents. @@ -86,11 +86,19 @@ pygments_style = 'sphinx' # given in html_static_path. html_theme = 'default' html_theme_options = {'stickysidebar':'true', 'relbarbgcolor':'black'} +# Put the quick search box on top +html_sidebars = { + '**' : ['searchbox.html', 'localtoc.html', 'relations.html', + 'sourcelink.html'], +} + +# The favicon +html_favicon = 'favicon.ico' # Add any paths that contain custom static files (such as style sheets) here, # relative to this directory. They are copied after the builtin static files, # so a file named "default.css" will overwrite the builtin "default.css". -html_static_path = ['resources'] +html_static_path = ['resources', '../../../icons/favicon.ico'] # If not '', a 'Last updated on:' timestamp is inserted at every page bottom, # using the given strftime format. @@ -108,7 +116,7 @@ html_logo = 'resources/logo.png' epub_author = 'Kovid Goyal' epub_cover = 'epub_cover.jpg' epub_publisher = 'Kovid Goyal' -epub_identifier = 'http://calibre-ebook.com/user_manual' +epub_identifier = 'http://manual.calibre-ebook.com' epub_scheme = 'url' epub_uid = 'S54a88f8e9d42455e9c6db000e989225f' epub_tocdepth = 4 @@ -126,12 +134,12 @@ html_use_modindex = False html_use_index = False # If true, the reST sources are included in the HTML build as _sources/. -html_copy_source = False +html_copy_source = True # Output file base name for HTML help builder. htmlhelp_basename = 'calibredoc' -html_use_opensearch = 'http://calibre-ebook.com/user_manual' +html_use_opensearch = 'http://manual.calibre-ebook.com' html_show_sphinx = False diff --git a/src/calibre/manual/conversion.rst b/src/calibre/manual/conversion.rst index 73358e0f72..540da0fc9a 100644 --- a/src/calibre/manual/conversion.rst +++ b/src/calibre/manual/conversion.rst @@ -655,6 +655,7 @@ Some limitations of PDF input are: * Some PDFs use special glyphs to represent ll or ff or fi, etc. Conversion of these may or may not work depending on just how they are represented internally in the PDF. * Some PDFs store their images upside down with a rotation instruction, |app| currently doesn't support that instruction, so the images will be rotated in the output as well. * Links and Tables of Contents are not supported + * PDFs that use embedded non-unicode fonts to represent non-English characters will result in garbled output for those characters To re-iterate **PDF is a really, really bad** format to use as input. If you absolutely must use PDF, then be prepared for an output ranging anywhere from decent to unusable, depending on the input PDF. diff --git a/src/calibre/manual/creating_plugins.rst b/src/calibre/manual/creating_plugins.rst new file mode 100644 index 0000000000..4a69cc8753 --- /dev/null +++ b/src/calibre/manual/creating_plugins.rst @@ -0,0 +1,213 @@ + +.. include:: global.rst + +.. _pluginstutorial: + +Writing your own plugins to extend |app|'s functionality +==================================================================== + +|app| has a very modular design. Almost all functionality in |app| comes in the form of plugins. Plugins are used for conversion, for downloading news (though these are called recipes), for various components of the user interface, to connect to different devices, to process files when adding them to |app| and so on. You can get a complete list of all the builtin plugins in |app| by going to :guilabel:`Preferences->Plugins`. + +Here, we will teach you how to create your own plugins to add new features to |app|. + + +.. contents:: Contents + :depth: 2 + :local: + +.. note:: This only applies to calibre releases >= 0.7.53 + +Anatomy of a |app| plugin +--------------------------- + +A |app| plugin is very simple, it's just a zip file that contains some python code +and any other resources like image files needed by the plugin. Without further ado, +let's see a basic example. + +Suppose you have an installation of |app| that you are using to self publish various e-documents in EPUB and MOBI +formats. You would like all files generated by |app| to have their publisher set as "Hello world", here's how to do it. +Create a file named :file:`__init__.py` (this is a special name and must always be used for the main file of your plugin) +and enter the following Python code into it: + +.. literalinclude:: plugin_examples/helloworld/__init__.py + :lines: 10- + +That's all. To add this code to |app| as a plugin, simply create a zip file with:: + + zip plugin.zip __init__.py + +Add this plugin to |app| via :guilabel:`Preferences->Plugins`. + +You can download the Hello World plugin from +`helloworld_plugin.zip `_. + +Every time you use calibre to convert a book, the plugin's :meth:`run` method will be called and the +converted book will have its publisher set to "Hello World". This is a trivial plugin, lets move on to +a more complex example that actually adds a component to the user interface. + +A User Interface plugin +------------------------- + +This plugin will be spread over a few files (to keep the code clean). It will show you how to get resources +(images or data files) from the plugin zip file, allow users to configure your plugin, +how to create elements in the |app| user interface and how to access +and query the books database in |app|. + +You can download this plugin from `interface_demo_plugin.zip `_ + +The first thing to note is that this zip file has a lot more files in it, explained below, pay particular attention to +``plugin-import-name-interface_demo.txt``. + + **plugin-import-name-interface_demo.txt** + An empty text file used to enable the multi-file plugin magic. This file must be present in all plugins that use + more than one .py file. It should be empty and its filename must be of the form: plugin-import-name-**some_name**.txt + The presence of this file allows you to import code from the .py files present inside the zip file, using a statement like:: + + from calibre_plugins.some_name.some_module import some_object + + The prefix ``calibre_plugins`` must always be present. ``some_name`` comes from the filename of the empty text file. + ``some_module`` refers to :file:`some_module.py` file inside the zip file. Note that this importing is just as + powerful as regular python imports. You can create packages and subpackages of .py modules inside the zip file, + just like you would normally (by defining __init__.py in each sub directory), and everything should Just Work. + + The name you use for ``some_name`` enters a global namespace shared by all plugins, **so make it as unique as possible**. + But remember that it must be a valid python identifier (only alphabets, numbers and the underscore). + + **__init__.py** + As before, the file that defines the plugin class + + **main.py** + This file contains the actual code that does something useful + + **ui.py** + This file defines the interface part of the plugin + + **images/icon.png** + The icon for this plugin + + **about.txt** + A text file with information about the plugin + +Now let's look at the code. + +__init__.py +^^^^^^^^^^^^^ + +First, the obligatory ``__init__.py`` to define the plugin metadata: + +.. literalinclude:: plugin_examples/interface_demo/__init__.py + :lines: 10- + +The only noteworthy feature is the field :attr:`actual_plugin`. Since |app| has both command line and GUI interfaces, +GUI plugins like this one should not load any GUI libraries in __init__.py. The actual_plugin field does this for you, +by telling |app| that the actual plugin is to be found in another file inside your zip archive, which will only be loaded +in a GUI context. + +Remember that for this to work, you must have a plugin-import-name-some_name.txt file in your plugin zip file, +as discussed above. + +Also there are a couple of methods for enabling user configuration of the plugin. These are discussed below. + +ui.py +^^^^^^^^ + +Now let's look at ui.py which defines the actual GUI plugin. The source code is heavily commented and should be self explanatory: + +.. literalinclude:: plugin_examples/interface_demo/ui.py + :lines: 16- + +main.py +^^^^^^^^^ + +The actual logic to implement the Interface Plugin Demo dialog. + +.. literalinclude:: plugin_examples/interface_demo/main.py + :lines: 16- + +Getting resources from the plugin zip file +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +|app|'s plugin loading system defines a couple of builtin functions that allow you to conveniently get files from the plugin zip file. + + **get_resources(name_or_list_of_names)** + This function should be called with a list of paths to files inside the zip file. For example to access the file icon.png in + the directory images in the zip file, you would use: ``images/icon.png``. Always use a forward slash as the path separator, + even on windows. When you pass in a single name, the function will return the raw bytes of that file or None if the name + was not found in the zip file. If you pass in more than one name then it returns a dict mapping the names to bytes. + If a name is not found, it will not be present in the returned dict. + + **get_icons(name_or_list_of_names)** + A convenience wrapper for get_resources() that creates QIcon objects from the raw bytes returned by get_resources. + If a name is not found in the zip file the corresponding QIcon will be null. + +Enabling user configuration of your plugin +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +To allow users to configure your plugin, you must define three methods in your base plugin class, '**is_customizable**, **config_widget** and **save_settings** as shown below: + +.. literalinclude:: plugin_examples/interface_demo/__init__.py + :pyobject: InterfacePluginDemo.is_customizable + +.. literalinclude:: plugin_examples/interface_demo/__init__.py + :pyobject: InterfacePluginDemo.config_widget + +.. literalinclude:: plugin_examples/interface_demo/__init__.py + :pyobject: InterfacePluginDemo.save_settings + +|app| has many different ways to store configuration data (a legacy of its long history). The recommended way is to use the **JSONConfig** class, which stores your configuration information in a .json file. + +The code to manage configuration data in the demo plugin is in config.py: + +.. literalinclude:: plugin_examples/interface_demo/config.py + :lines: 10- + +The ``prefs`` object is now available throughout the plugin code by a simple:: + + from calibre_plugins.interface_demo.config import prefs + + +You can see the ``prefs`` object being used in main.py: + +.. literalinclude:: plugin_examples/interface_demo/main.py + :pyobject: DemoDialog.config + + +The different types of plugins +-------------------------------- + +As you may have noticed above, a plugin in |app| is a class. There are different classes for the different types of plugins in |app|. +Details on each class, including the base class of all plugins can be found in :ref:`plugins`. + +Debugging plugins +------------------- + +The first, most important step is to run |app| in debug mode. You can do this from the command line with:: + + calibre-debug -g + +Or from within calibre by clicking the arrow next to the preferences button or using the `Ctrl+Shift+R` keyboard shortcut. + +When running from the command line, debug output will be printed to the console, when running from within |app| the output will go to a txt file. + +You can insert print statements anywhere in your plugin code, they will be output in debug mode. Remember, this is python, you really shouldn't need anything more than print statements to debug ;) I developed all of |app| using just this debugging technique. + +It can get tiresome to keep re-adding a plugin to calibre to test small changes. The plugin zip files are stored in the calibre config directory in plugins/ (goto Preferences->Misc and click open config directory to see the config directory). + +Once you've located the zip file of your plugin you can then directly update it with your changes instead of re-adding it each time. To do so from the command line, in the directory that contains your plugin source code, use:: + + calibre -s; sleep 4s; zip -R /path/to/plugin/zip/file.zip *; calibre + +This will shutdown a running calibre. Wait for the shutdown to complete, then update your plugin files and relaunch calibre. +It relies on the freely available zip command line tool. + +More plugin examples +---------------------- + +You can find a list of many, sophisticated |app| plugins `here `_. + +Sharing your plugins with others +---------------------------------- + +If you would like to share the plugins you have created with other users of |app|, post your plugin in a new thread in the +`calibre plugins forum `_. + diff --git a/src/calibre/manual/customize.rst b/src/calibre/manual/customize.rst index 6218bf8112..fe33100576 100644 --- a/src/calibre/manual/customize.rst +++ b/src/calibre/manual/customize.rst @@ -17,6 +17,11 @@ use *plugins* to add functionality to |app|. :depth: 2 :local: +.. toctree:: + :hidden: + + plugins + Environment variables ----------------------- @@ -53,148 +58,10 @@ You should not change the files in this resources folder, as your changes will g For example, if you wanted to change the icon for the :guilabel:`Remove books` action, you would first look in the builtin resources folder and see that the relevant file is :file:`resources/images/trash.svg`. Assuming you have an alternate icon in svg format called :file:`mytrash.svg` you would save it in the configuration directory as :file:`resources/images/trash.svg`. All the icons used by the calibre user interface are in :file:`resources/images` and its sub-folders. -A Hello World plugin ------------------------- +Customizing |app| with plugins +-------------------------------- -Suppose you have an installation of |app| that you are using to self publish various e-documents in EPUB and LRF -format. You would like all file generated by |app| to have their publisher set as "Hello world", here's how to do it. -Create a file name :file:`my_plugin.py` (the file name must end with plugin.py) and enter the following Python code into it: +|app| has a very modular design. Almost all functionality in |app| comes in the form of plugins. Plugins are used for conversion, for downloading news (though these are called recipes), for various components of the user interface, to connect to different devices, to process files when adding them to |app| and so on. You can get a complete list of all the builtin plugins in |app| by going to :guilabel:`Preferences->Plugins`. -.. code-block:: python +You can write your own plugins to customize and extend the behavior of |app|. The plugin architecture in |app| is very simple, see the tutorial :ref:`pluginstutorial`. - import os - from calibre.customize import FileTypePlugin - - class HelloWorld(FileTypePlugin): - - name = 'Hello World Plugin' # Name of the plugin - description = 'Set the publisher to Hello World for all new conversions' - supported_platforms = ['windows', 'osx', 'linux'] # Platforms this plugin will run on - author = 'Acme Inc.' # The author of this plugin - version = (1, 0, 0) # The version number of this plugin - file_types = set(['epub', 'lrf']) # The file types that this plugin will be applied to - on_postprocess = True # Run this plugin after conversion is complete - - def run(self, path_to_ebook): - from calibre.ebooks.metadata.meta import get_metadata, set_metadata - file = open(path_to_ebook, 'r+b') - ext = os.path.splitext(path_to_ebook)[-1][1:].lower() - mi = get_metadata(file, ext) - mi.publisher = 'Hello World' - set_metadata(file, mi, ext) - return path_to_ebook - -That's all. To add this code to |app| as a plugin, simply create a zip file with:: - - zip plugin.zip my_plugin.py - -You can download the Hello World plugin from -`helloworld_plugin.zip `_. -Now either use the configuration dialog in |app| GUI to add this zip file as a plugin, or -use the command:: - - calibre-customize -a plugin.zip - -Every time you use calibre to convert a book, the plugin's :meth:`run` method will be called and the -converted book will have its publisher set to "Hello World". For more information about -|app|'s plugin system, read on... - - -A Hello World GUI plugin ---------------------------- - -Here's a simple Hello World plugin for the |app| GUI. It will cause a box to popup with the message "Hellooo World!" when you press Ctrl+Shift+H - -.. note:: Only available in calibre versions ``>= 0.7.32``. - -.. code-block:: python - - from calibre.customize import InterfaceActionBase - - class HelloWorldBase(InterfaceActionBase): - - name = 'Hello World GUI' - author = 'The little green man' - - def load_actual_plugin(self, gui): - from calibre.gui2.actions import InterfaceAction - - class HelloWorld(InterfaceAction): - name = 'Hello World GUI' - action_spec = ('Hello World!', 'add_book.png', None, - _('Ctrl+Shift+H')) - - def genesis(self): - self.qaction.triggered.connect(self.hello_world) - - def hello_world(self, *args): - from calibre.gui2 import info_dialog - info_dialog(self.gui, 'Hello World!', 'Hellooo World!', - show=True) - - return HelloWorld(gui, self.site_customization) - -You can also have it show up in the toolbars/context menu by going to Preferences->Toolbars and adding this plugin to the locations you want it to be in. - -While this plugin is utterly useless, note that all calibre GUI actions like adding/saving/removing/viewing/etc. are implemented as plugins, so there is no limit to what you can achieve. The key thing to remember is that the plugin has access to the full |app| GUI via ``self.gui``. - - -The Plugin base class ------------------------- - -As you may have noticed above, all |app| plugins are classes. The Plugin classes are organized in a hierarchy at the top of which -is :class:`calibre.customize.Plugin`. The has excellent in source documentation for its various features, here I will discuss a -few of the important ones. - -First, all plugins must supply a list of platforms they have been tested on by setting the ``supported_platforms`` member as in the -example above. - -If the plugin needs to do any initialization, it should implement the :meth:`initialize` method. The path to the plugin zip file -is available as ``self.plugin_path``. The initialization method could be used to load any needed resources from the zip file. - -If the plugin needs to be customized (i.e. it needs some information from the user), it should implement the :meth:`customization_help` -method, to indicate to |app| that it needs user input. This can be useful, for example, to ask the user to input the path to a needed system -binary or the URL of a website, etc. When |app| asks the user for the customization information, the string retuned by the :meth:`customization_help` -method is used as help text to le thte user know what information is needed. - -Another useful method is :meth:`temporary_file`, which returns a file handle to an opened temporary file. If your plugin needs to make use -of temporary files, it should use this method. Temporary file cleanup is then taken care of automatically. - -In addition, whenever plugins are run, their zip files are automatically added to the start of ``sys.path``, so you can directly import -any python files you bundle in the zip files. Note that this is not available when the plugin is being initialized, only when it is being run. - -Finally, plugins can have a priority (a positive integer). Higher priority plugins are run in preference tolower priority ones in a given context. -By default all plugins have priority 1. You can change that by setting the member :attr:'priority` in your subclass. - -See :ref:`pluginsPlugin` for details. - -File type plugins -------------------- - -File type plugins are intended to be associated with specific file types (as identified by extension). They can be run on several different occassions. - - * When books/formats are added ot the |app| database (if :attr:`on_import` is set to True). - * Just before an any2whatever converter is run on an input file (if :attr:`on_preprocess` is set to True). - * After an any2whatever converter has run, on the output file (if :attr:`on_postprocess` is set to True). - -File type plugins specify which file types they are associated with by specifying the :attr:`file_types` member as in the above example. -the actual work should be done in the :meth:`run` method, which must return the path to the modified ebook (it can be the same as the original -if the modifcations are done in place). - -See :ref:`pluginsFTPlugin` for details. - -Metadata plugins -------------------- - -Metadata plugins add the ability to read/write metadata from ebook files to |app|. See :ref:`pluginsMetadataPlugin` for details. - -.. toctree:: - :hidden: - - plugins - -Metadata download plugins ----------------------------- - -Metadata download plugins add various sources that |app| uses to download metadata based on title/author/isbn etc. See :ref:`pluginsMetadataSource` -for details. diff --git a/src/calibre/manual/develop.rst b/src/calibre/manual/develop.rst index f95d51bfca..c49176ceb2 100644 --- a/src/calibre/manual/develop.rst +++ b/src/calibre/manual/develop.rst @@ -65,7 +65,7 @@ this, make your changes, then run:: bzr send -o my-changes This will create a :file:`my-changes` file in the current directory, -simply attach that to a ticket on the |app| `bug tracker `_. +simply attach that to a ticket on the |app| `bug tracker `_. If you plan to do a lot of development on |app|, then the best method is to create a `Launchpad `_ account. Once you have the account, you can use it to register diff --git a/src/calibre/manual/faq.rst b/src/calibre/manual/faq.rst index 8a78815751..99c53e5a37 100644 --- a/src/calibre/manual/faq.rst +++ b/src/calibre/manual/faq.rst @@ -20,17 +20,20 @@ What formats does |app| support conversion to/from? |app| supports the conversion of many input formats to many output formats. It can convert every input format in the following list, to every output format. -*Input Formats:* CBZ, CBR, CBC, CHM, EPUB, FB2, HTML, LIT, LRF, MOBI, ODT, PDF, PRC**, PDB, PML, RB, RTF, SNB, TCR, TXT +*Input Formats:* CBZ, CBR, CBC, CHM, EPUB, FB2, HTML, HTMLZ, LIT, LRF, MOBI, ODT, PDF, PRC, PDB, PML, RB, RTF, SNB, TCR, TXT, TXTZ -*Output Formats:* EPUB, FB2, OEB, LIT, LRF, MOBI, PDB, PML, RB, PDF, SNB, TCR, TXT +*Output Formats:* EPUB, FB2, OEB, LIT, LRF, MOBI, HTMLZ, PDB, PML, RB, PDF, RTF, SNB, TCR, TXT, TXTZ -** PRC is a generic format, |app| supports PRC files with TextRead and MOBIBook headers +.. note :: + + PRC is a generic format, |app| supports PRC files with TextRead and MOBIBook headers. + PDB is also a generic format. |app| supports eReder, Plucker, PML and zTxt PDB files. .. _best-source-formats: What are the best source formats to convert? ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -In order of decreasing preference: LIT, MOBI, EPUB, HTML, PRC, RTF, PDB, TXT, PDF +In order of decreasing preference: LIT, MOBI, EPUB, FB2, HTML, PRC, RTF, PDB, TXT, PDF Why does the PDF conversion lose some images/tables? ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ @@ -65,6 +68,22 @@ There are two aspects to this problem: 2. When adding HTML files to |app|, you may need to tell |app| what encoding the files are in. To do this go to :guilabel:`Preferences->Advanced->Plugins->File Type plugins` and customize the HTML2Zip plugin, telling it what encoding your HTML files are in. Now when you add HTML files to |app| they will be correctly processed. HTML files from different sources often have different encodings, so you may have to change this setting repeatedly. A common encoding for many files from the web is ``cp1252`` and I would suggest you try that first. Note that when converting HTML files, leave the input encoding setting mentioned above blank. This is because the HTML2ZIP plugin automatically converts the HTML files to a standard encoding (utf-8). 3. Embedding fonts: If you are generating an LRF file to read on your SONY Reader, you are limited by the fact that the Reader only supports a few non-English characters in the fonts it comes pre-loaded with. You can work around this problem by embedding a unicode-aware font that supports the character set your file uses into the LRF file. You should embed atleast a serif and a sans-serif font. Be aware that embedding fonts significantly slows down page-turn speed on the reader. +What's the deal with Table of Contents in MOBI files? +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +The first thing to realize is that most ebooks have two tables of contents. One is the traditional Table of Contents, like the TOC you find in paper books. This Table of Contents is part of the main document flow and can be styled however you like. This TOC is called the *content TOC*. + +Then there is the *metadata TOC*. A metadata TOC is a TOC that is not part of the book text and is typically accessed by some special button on a reader. For example, in the calibre viewer, you use the Show Table of Contents button to see this TOC. This TOC cannot be styled by the book creator. How it is represented is up to the viewer program. + +In the MOBI format, the situation is a little confused. This is because the MOBI format, alone amongst mainstream ebook formats, *does not* have decent support for a metadata TOC. A MOBI book simulates the presence of a metadata TOC by putting an *extra* content TOC at the end of the book. When you click Goto Table of Contents on your Kindle, it is to this extra content TOC that the Kindle takes you. + +Now it might well seem to you that the MOBI book has two identical TOCs. Remember that one is semantically a content TOC and the other is a metadata TOC, even though both might have exactly the same entries and look the same. One can be accessed directly from the Kindle's menus, the other cannot. + +When converting to MOBI, calibre detects the *metadata TOC* in the input document and generates an end-of-file TOC in the output MOBI file. You can turn this off by an option in the MOBI Output settings. You cannot control where this generated TOC will go. Remember this TOC is semantically a *metadata TOC*, in any format other than MOBI it *cannot not be part of the text*. The fact that it is part of the text in MOBI is an accident caused by the limitations of MOBI. If you want a TOC at a particular location in your document text, create one by hand. + +If you have a hand edited TOC in the input document, you can use the TOC detection options in calibre to automatically generate the metadata TOC from it. See the conversion section of the User Manual for more details on how to use these options. + +Finally, I encourage you to ditch the content TOC and only have a metadata TOC in your ebooks. Metadata TOCs will give the people reading your ebooks a much superior navigation experience (except on the Kindle, where they are essentially the same as a content TOC). How do I use some of the advanced features of the conversion tools? ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ @@ -81,7 +100,9 @@ Device Integration What devices does |app| support? ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -At the moment |app| has full support for the SONY PRS line, Barnes & Noble Nook line, Cybook Gen 3/Opus, Amazon Kindle line, Entourage Edge, Longshine ShineBook, Ectaco Jetbook, BeBook/BeBook Mini, Irex Illiad/DR1000, Foxit eSlick, PocketBook line, Italica, eClicto, Iriver Story, Airis dBook, Hanvon N515, Binatone Readme, Teclast K3 and clones, SpringDesign Alex, Kobo Reader, various Android phones and the iPhone/iPad. In addition, using the :guilabel:`Connect to folder` function you can use it with any ebook reader that exports itself as a USB disk. +At the moment |app| has full support for the SONY PRS line, Barnes & Noble Nook line, Cybook Gen 3/Opus, Amazon Kindle line, Entourage Edge, Longshine ShineBook, Ectaco Jetbook, BeBook/BeBook Mini, Irex Illiad/DR1000, Foxit eSlick, PocketBook line, Italica, eClicto, Iriver Story, Airis dBook, Hanvon N515, Binatone Readme, Teclast K3 and clones, SpringDesign Alex, Kobo Reader, various Android phones and the iPhone/iPad. In addition, using the :guilabel:`Connect to folder` function you can use it with any ebook reader that exports itself as a USB disk. + +There is also a special ``User Defined`` device plugin that can be used to connect to arbitrary devices that present their memory as disk drives. See the device plugin ``Preferences -> Plugins -> Device Plugins -> User Defined`` and ``Preferences -> Miscelleaneous -> Get information to setup the user defined device`` for more information. How can I help get my device supported in |app|? ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ @@ -99,7 +120,8 @@ We just need some information from you: device. Once you send us the output for a particular operating system, support for the device in that operating system -will appear in the next release of |app|. +will appear in the next release of |app|. To send us the output, open a bug report and attach the output to it. +See `calibre bugs `_. My device is not being detected by |app|? ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ @@ -113,6 +135,11 @@ Follow these steps to find the problem: * In calibre, 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. * 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? +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +In addition to the :guilabel:`Connect to Folder` function found under the Connect/Share button, |app| provides a ``User Defined`` device plugin that can be used to connect to any USB device that shows up as a disk drive in your operating system. Note: on windows, the device must have a drive letter for calibre to use it. See the device plugin ``Preferences -> Plugins -> Device Plugins -> User Defined`` and ``Preferences -> Miscellaneous -> Get information to setup the user defined device`` for more information. + How does |app| manage collections on my SONY reader? ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ @@ -327,10 +354,24 @@ Now coming to author name sorting: * When recalculating the author sort values for books, |app| uses the author sort values for each individual author. Therefore, ensure that the individual author sort values are correct before recalculating the books' author sort values. * You can control whether the Tag Browser display authors using their names or their sort values by setting the :guilabel:`categories_use_field_for_author_name` tweak in Preferences->Tweaks -With all this flexibility, it is possible to have |app| manage your author names however you like. For example, one common request is to have |app| display author names LN, FN. To do this first set the ``author_sort_copy_method`` to ``copy``. Then change all author names to LN, FN via the Manage authors dialog. Then have |app| recalculate author sort values for both authors and books as described above. - Note that you can set an individual author's sort value to whatever you want using :guilabel:`Manage authors`. This is useful when dealing with names that |app| will not get right, such as complex multi-part names like Miguel de Cervantes Saavedra or when dealing with Asian names like Sun Tzu. +With all this flexibility, it is possible to have |app| manage your author names however you like. For example, one common request is to have |app| display author names LN, FN. To do this, and if the note below does not apply to you, then: + * Set the ``author_sort_copy_method`` tweak to ``copy`` as described above. + * Restart calibre. Do not change any book metadata before doing the remaining steps. + * Change all author names to LN, FN using the Manage authors dialog. + * After you have changed all the authors, press the `Recalculate all author sort values` button. + * Press OK, at which point |app| will change the authors in all your books. This can take a while. + +.. note:: + + When changing from FN LN to LN, FN, it is often the case that the values in author_sort are already in LN, FN format. If this is your case, then do the following: + * set the ``author_sort_copy_method`` tweak to ``copy`` as described above. + * restart calibre. Do not change any book metadata before doing the remaining steps. + * open the Manage authors dialog. Press the ``copy all author sort values to author`` button. + * Check through the authors to be sure you are happy. You can still press Cancel to abandon the changes. Once you press OK, there is no undo. + * Press OK, at which point |app| will change the authors in all your books. This can take a while. + Why doesn't |app| let me store books in my own directory structure? ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ @@ -350,7 +391,7 @@ Why doesn't |app| have a column for foo? |app| is designed to have columns for the most frequently and widely used fields. In addition, you can add any columns you like. Columns can be added via :guilabel:`Preferences->Interface->Add your own columns`. Watch the tutorial `UI Power tips `_ to learn how to create your own columns. -You can also create "virtual columns" that contain combinations of the metadata from other columns. In the add column dialog choose the option "Column from other columns" and in the template enter the other column names. For example to create a virtual column containing formats or ISBN, enter ``{formats}`` for formats or ``{isbn}`` for ISBN. For more details, see :ref:`templatelangcalibre`. +You can also create "virtual columns" that contain combinations of the metadata from other columns. In the add column dialog use the :guilabel:`Quick create` links to easily create columns to show the book ISBN, formats or the time the book was last modified. For more details, see :ref:`templatelangcalibre`. Can I have a column showing the formats or the ISBN? @@ -434,6 +475,18 @@ If it still wont launch, start a command prompt (press the windows key and R; th Post any output you see in a help message on the `Forum `_. +|app| freezes when I click on anything? +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +There are three possible things I know of, that can cause this: + + * You recently connected an external monitor or TV to your computer. In this case, whenever |app| opens a new window like the edit metadata window or the conversion dialog, it appears on the second monitor where you dont notice it and so you think |app| has frozen. Disconnect your second monitor and restart calibre. + + * You are using a Wacom branded mouse. There is an incompatibility between Wacom mice and the graphics toolkit |app| uses. Try using a non-Wacom mouse. + + * You have invalid files in your fonts folder. If this is the case, start |app| in debug mode as desribed in the previous answer and you will get messages about invalid files in :file:`C:\\Windows\\fonts`. Delete these files and you will be fine. + + |app| is not starting on OS X? ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ @@ -456,7 +509,13 @@ menu, choose "Validate fonts". I downloaded the installer, but it is not working? ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -Downloading from the internet can sometimes result in a corrupted download. If the |app| installer you downloaded is not opening, try downloading it again. If re-downloading it does not work, download it from `an alternate location `_. If the installer still doesn't work, then something on your computer is preventing it from running. Try rebooting your computer and running a registry cleaner like `Wise registry cleaner `_. Best place to ask for more help is in the `forums `_. +Downloading from the internet can sometimes result in a corrupted download. If the |app| installer you downloaded is not opening, try downloading it again. If re-downloading it does not work, download it from `an alternate location `_. If the installer still doesn't work, then something on your computer is preventing it from running. + + * Try temporarily disabling your antivirus program (Microsoft Security Essentials, or Kaspersky or Norton or McAfee or whatever). This is most likely the culprit if the upgrade process is hanging in the middle. + * Try rebooting your computer and running a registry cleaner like `Wise registry cleaner `_. + * Try downloading the installer with an alternate browser. For example if you are using Internet Explorer, try using Firefox or Chrome instead. + +Best place to ask for more help is in the `forums `_. My antivirus program claims |app| is a virus/trojan? ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ @@ -479,7 +538,16 @@ Most purchased EPUB books have `DRM `_. Thi I am getting a "Permission Denied" error? ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -A permission denied error can occur because of many possible reasons, none of them having anything to do with |app|. You can get permission denied errors if you are using an SD card with write protect enabled. Or if you, or some program you used changed the file permissions of the files in question to read only. Or if there is a filesystem error on the device which caused your operating system to mount the filesystem in read only mode or mark a particular file as read only pending recovery. Or if the files have their owner set to a user other than you. Or if your file is open in another program. You will need to fix the underlying cause of the permissions error before resuming to use |app|. Read the error message carefully, see what file it points to and fix the permissions on that file. +A permission denied error can occur because of many possible reasons, none of them having anything to do with |app|. + + * You can get permission denied errors if you are using an SD card with write protect enabled. + * If you, or some program you used changed the file permissions of the files in question to read only. + * If there is a filesystem error on the device which caused your operating system to mount the filesystem in read only mode or mark a particular file as read only pending recovery. + * If the files have their owner set to a user other than you. + * If your file is open in another program. + * If the file resides on a device, you may have reached the limit of a maximum of 256 files in the root of the device. In this case you need to reformat the device/sd card referered to in the error message with a FAT32 filesystem, or delete some files from the SD card/device memory. + +You will need to fix the underlying cause of the permissions error before resuming to use |app|. Read the error message carefully, see what file it points to and fix the permissions on that file. Can I have the comment metadata show up on my reader? ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ @@ -492,28 +560,17 @@ I want some feature added to |app|. What can I do? ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ You have two choices: 1. Create a patch by hacking on |app| and send it to me for review and inclusion. See `Development `_. - 2. `Open a ticket `_ (you have to register and login first). Remember that |app| development is done by volunteers, so if you get no response to your feature request, it means no one feels like implementing it. + 2. `Open a ticket `_ (you have to register and login first). Remember that |app| development is done by volunteers, so if you get no response to your feature request, it means no one feels like implementing it. -Can I include |app| on a CD to be distributed with my product/magazine? +How is |app| licensed? ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -|app| is licensed under the GNU General Public License v3 (an open source license). This means that you are free to redistribute |app| as long as you make the source code available. So if you want to put |app| on a CD with your product, you must also put the |app| source code on the CD. The source code is available for download `from googlecode `_. +|app| is licensed under the GNU General Public License v3 (an open source license). This means that you are free to redistribute |app| as long as you make the source code available. So if you want to put |app| on a CD with your product, you must also put the |app| source code on the CD. The source code is available for download `from googlecode `_. You are free to use the results of conversions from |app| however you want. You cannot use code, libraries from |app| in your software without making your software open source. For details, see `The GNU GPL v3 `_. How do I run calibre from my USB stick? ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ A portable version of calibre is available at: `portableapps.com `_. However, this is usually out of date. You can also setup your own portable calibre install by following :ref:`these instructions `. -Why are there so many calibre-parallel processes on my system? -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -|app| maintains two separate worker process pools. One is used for adding books/saving to disk and the other for conversions. You can control the number of worker processes via :guilabel:`Preferences->Advanced->Miscellaneous`. So if you set it to 6 that means a maximum of 3 conversions will run simultaneously. And that is why you will see the number of worker processes changes by two when you use the up and down arrows. On windows, you can set the priority that these processes run with. This can be useful on older, single CPU machines, if you find them slowing down to a crawl when conversions are running. - -In addition to this some conversion plugins run tasks in their own pool of processes, so for example if you bulk convert comics, each comic conversion will use three separate processes to render the images. The job manager knows this so it will run only a single comic conversion simultaneously. - -And since I'm sure someone will ask: The reason adding/saving books are in separate processes is because of PDF. PDF processing libraries can crash on reading PDFs and I dont want the crash to take down all of calibre. Also when adding EPUB books, in order to extract the cover you have to sometimes render the HTML of the first page, which means that it either has to run in the GUI thread of the main process or in a separate process. - -Finally, the reason calibre keep workers alive and idle instead of launching on demand is to workaround the slow startup time of python processes. - How do I run parts of |app| like news download and the content server on my own linux server? ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ @@ -530,7 +587,7 @@ You can download news and convert it into an ebook with the command:: /opt/calibre/ebook-convert "Title of news source.recipe" outputfile.epub -If you want to generate MOBI, use outputfile.mobi instead. +If you want to generate MOBI, use outputfile.mobi instead and use ``--output-profile kindle``. You can email downloaded news with the command:: diff --git a/src/calibre/manual/gui.rst b/src/calibre/manual/gui.rst index 882e3d5921..e2758bc257 100644 --- a/src/calibre/manual/gui.rst +++ b/src/calibre/manual/gui.rst @@ -12,7 +12,7 @@ for using |app| is to first add books to the library from your hard disk. to its internal database. Once they are in the database, you can perform a various :ref:`actions` on them that include conversion from one format to another, transfer to the reading device, viewing on your computer, editing metadata, including covers, etc. -Note that |app| creates copies of the files you add to it, your original files are left untouched. +Note that |app| creates copies of the files you add to it, your original files are left untouched. The interface is divided into various sections: @@ -51,10 +51,12 @@ Add books 3. **Add books directories, including sub-directories (Multiple books per directory, assumes every ebook file is a different book)**: Allows you to choose a directory. The directory and all its sub-directories are scanned recursively and any ebooks found are added to the library. The algorithm assumes that each directory contains many books. All ebook files with the same name in a directory are assumed to be the same book in different formats. Ebooks with different names are added as different books. This action is the inverse of the :ref:`Save to disk ` action, i.e. you can :guilabel:`Save to disk`, delete the books and re-add them with no lost information (except date). - 4. **Add empty book. (Book Entry with blank formats)**: Allows you to create a blank book record. This can be used to then manually fill out the information about a book that you may not have yet in your collection. - - 5. **Add by ISBN**: Allows you to add one or more books by entering just their ISBN into a list or pasting the list of ISBNs from your clipboard. - + 4. **Add empty book. (Book Entry with blank formats)**: Allows you to create a blank book record. This can be used to then manually fill out the information about a book that you may not have yet in your collection. + + 5. **Add by ISBN**: Allows you to add one or more books by entering just their ISBN into a list or pasting the list of ISBNs from your clipboard. + + 6. **Add files to selected book records**: Allows you to add or update the files associated with an existing book in your library. + The :guilabel:`Add books` action can read metadata from a wide variety of e-book formats. In addition it tries to guess metadata from the filename. See the :ref:`config_filename_metadata` section, to learn how to configure this. @@ -69,7 +71,7 @@ Edit metadata |emii| The :guilabel:`Edit metadata` action has six variations, which can be accessed by clicking the down arrow on the right side of the button. - 1. **Edit metadata individually**: This allows you to edit the metadata of books one-by-one, with the option of fetching metadata, including covers from the internet. It also allows you to add/remove particular ebook formats from a book. For more detail see :ref:`metadata`. + 1. **Edit metadata individually**: This allows you to edit the metadata of books one-by-one, with the option of fetching metadata, including covers from the internet. It also allows you to add/remove particular ebook formats from a book. 2. **Edit metadata in bulk**: This allows you to edit common metadata fields for large numbers of books simulataneously. It operates on all the books you have selected in the :ref:`Library view `. 3. **Download metadata and covers**: Downloads metadata and covers (if available), for the books that are selected in the book list. 4. **Download only metadata**: Downloads only metadata (if available), for the books that are selected in the book list. @@ -77,7 +79,8 @@ Edit metadata 6. **Download only social metadata**: Downloads only social metadata such as tags and reviews (if available), for the books that are selected in the book list. 7. **Merge Book Records**: Gives you the capability of merging the metadata and formats of two or more book records together. You can choose to either delete or keep the records that were not clicked first. - +For more details see :ref:`metadata`. + .. _convert_ebooks: Convert e-books @@ -91,13 +94,13 @@ Note that ebooks you purchase will typically have `Digital Rights Management Behavior. If a book has more than one format, you can view a particular format by clicking the down arrow on the right of the :guilabel:`View` button. - + .. _send_to_device: Send to device @@ -138,7 +141,7 @@ Send to device You can control the file name and folder structure of files sent to the device by setting up a template in :guilabel:`Preferences->Import/Export->Sending books to devices`. Also see :ref:`templatelangcalibre`. - + .. _fetch_news: Fetch news @@ -147,11 +150,11 @@ Fetch news :class: float-right-img |fni| The :guilabel:`Fetch news` action downloads news from various websites and converts it into an ebook that can be read on your ebook reader. Normally, the newly created ebook is added to your ebook library, but if an ebook reader is connected at the time the download finishes, the news is also uploaded to the reader automatically. - + The :guilabel:`Fetch news` action uses simple recipes (10-15 lines of code) for each news site. To learn how to create recipes for your own news sources, see :ref:`news`. The :guilabel:`Fetch news` action has three variations, accessed by clicking the down arrow on the right of the button. - + 1. **Schedule news download**: This action allows you to schedule the download of of your selected news sources from a list of hundreds of available. Scheduling can be set individually for each news source you select and the scheduling is flexible allowing you to select specific days of the week or a frequency of days between downloads. 2. **Add a custom news service**: This action allows you to create a simple recipe for downloading news from a custom news site that you wish to access. Creating the recipe can be as simple as specifying an RSS news feed URL, or you can be more prescriptive by creating python based code for the task, see :ref:`news`. 3. **Download all scheduled news sources**: This action causes |app| to immediately begin to download all news sources that you have previously scheduled. @@ -164,7 +167,7 @@ Library .. |lii| image:: images/library.png :class: float-right-img -|lii| The :guilabel: `Library` action allows you to create, switch between, rename or delete a Library. |app| allows you to create as many libraries as you wish. You coudl for instance create a fiction library, a non fiction library, a foreign language library a project library, basically any structure that suits your needs. Libraries are the highest organizational structure within |app|, each library has its own set of books, tags, categories and base storage location. +|lii| The :guilabel: `Library` action allows you to create, switch between, rename or delete a Library. |app| allows you to create as many libraries as you wish. You could for instance create a fiction library, a non fiction library, a foreign language library a project library, basically any structure that suits your needs. Libraries are the highest organizational structure within |app|, each library has its own set of books, tags, categories and base storage location. 1. **Switch\Create library..**: This action allows you to; a) connect to a pre-existing |app| library at another location from your currently open library, b) Create and empty library at a nw location or, c) Move the current Library to a newly specified location. 2. **Quick Switch>**: This action allows you to switch between libraries that have been registered or created within |app|. @@ -180,9 +183,9 @@ Device :class: float-right-img |dvi| The :guilabel:`Device` action allows you to view the books in the main memory or storage cards of your device, or to eject the device (detach it from |app|). -This icon shows up automatically on the main |app| toolbar when you connect a supported device. You can click on it to see the books on your device. You can also drag and drop books from your |app| library onto the icon to transfer them to your device. Conversely, you can drag and drop books from your device onto the |app| icon on the toolbar to transfer books from your device to the |app| library. +This icon shows up automatically on the main |app| toolbar when you connect a supported device. You can click on it to see the books on your device. You can also drag and drop books from your |app| library onto the icon to transfer them to your device. Conversely, you can drag and drop books from your device onto the |app| icon on the toolbar to transfer books from your device to the |app| library. + - .. _save_to_disk: Save to disk @@ -199,14 +202,14 @@ Save to disk Author_(sort) Title Book Files - + You can control the file name and folder structure of files saved to disk by setting up a template in :guilabel:`Preferences->Import/Export->Saving books to disk`. Also see :ref:`templatelangcalibre`. - + .. _save_to_disk_single: 2. **Save to disk in a single directory**: The selected books are saved to disk in a single directory. - + For 1. and 2. All available formats as well as metadata is stored to disk for each selected book. Metadata is stored in an OPF file. Saved books can be re-imported to the library without any loss of information by using the :ref:`Add books ` action. @@ -227,14 +230,14 @@ Connect/Share |csi| The :guilabel:`Connect/Share` action allows you to manually connect to a device or folder on your computer, it also allows you to set up you |app| library for access via a web browser, or email. The :guilabel:`Connect/Share` action has four variations, accessed by clicking the down arrow on the right of the button. - + 1. **Connect to folder**: This action allows you to connect to any folder on your computer as though it were a device and use all the facilities |app| has for devices with that folder. Useful if your device cannot be supported by |app| but is available as a USB disk. - - 2. **Connect to iTunes**: Allows you to connect to your iTunes books database as though it were a device. Once the books are sent to iTunes, you can then use iTunes to make them available on your various iDevices. Useful if you would rather not have |app| send books to your iDevice directly. - + + 2. **Connect to iTunes**: Allows you to connect to your iTunes books database as though it were a device. Once the books are sent to iTunes, you can then use iTunes to make them available on your various iDevices. Useful if you would rather not have |app| send books to your iDevice directly. + 3. **Start Content Server**: This action causes |app| to start up its built-in web server. When this is started, your |app| library will be accessible via a web browser from the internet (if you choose). You can configure how the web server is accessed by setting preferences at :guilabel:`Preferences->Sharing->Sharing over the net` - - 4. **Setup email based sharing of books**: This action allows you to setup |app| to share books (and news feeds) by email. After setting up email addresses for this option |app| will send news updates and book updates to the entered email addresses. You can configure how the |app| sends email by setting preferences at :guilabel:`Preferences->Sharing->Sharing books by email`. Once you have setup one or more email addresses, this menu entry get replaced by menu entries to send books to the setup email addresses. + + 4. **Setup email based sharing of books**: This action allows you to setup |app| to share books (and news feeds) by email. After setting up email addresses for this option |app| will send news updates and book updates to the entered email addresses. You can configure how the |app| sends email by setting preferences at :guilabel:`Preferences->Sharing->Sharing books by email`. Once you have setup one or more email addresses, this menu entry get replaced by menu entries to send books to the setup email addresses. .. _remove_books: @@ -245,14 +248,14 @@ Remove books |rbi| The :guilabel:`Remove books` action **deletes books permanently**, so use it with care. It is *context sensitive*, i.e. it depends on which :ref:`catalog ` you have selected. If you have selected the :guilabel:`Library`, books will be removed from the library. If you have selected the ebook reader device, the books will be removed from the device. To remove only a particular format for a given book use the :ref:`edit_meta_information` action. Remove books also has five variations which can be accessed by clicking the down arrow on the right side of the button. - 1. **Remove Selected Books**: Allows you to **permanently** remove all books that are selected in the book list. - + 1. **Remove Selected Books**: Allows you to **permanently** remove all books that are selected in the book list. + 2. **Remove files of a specified format from selected books..**: Allows you to **permanently** remove ebook files of a specified format, from books that are selected in the book list. - + 3. **Remove all files of a specified format, except..**: Allows you to **permanently** remove ebook files of a multiple formats except a given format, from books that are selected in the book list. - + 4. **Remove covers from selected books**: Allows you to **permanently** remove cover images files, from books that are selected in the book list. - + 5. **Remove matching books from device**: Allows you to remove ebook files from a connected device, that match the books that are selected in the book list. .. note:: @@ -265,7 +268,7 @@ Preferences .. |cbi| image:: images/preferences.png The Preferences Action allows you to change the way various aspects of |app| work. To access it, click the |cbi|. - + .. _catalogs: Catalogs @@ -274,9 +277,9 @@ Catalogs :align: center A *catalog* is a collection of books. |app| can manage two types of different catalogs: - + 1. **Library**: This is a collection of books stored in your |app| library on your computer - + 2. **Device**: This is a collection of books stored in the main memory of your ebook reader. It will be available when you connect the reader to your computer. - In addition, you can see the books on the storage card (if any) in your reader device. @@ -292,17 +295,17 @@ Search & Sort The Search & Sort section allows you to perform several powerful actions on your book collections. * You can sort them by title, author, date, rating etc. by clicking on the column titles. You can also sub-sort (i.e. sort on multiple columns). For example, if you click on the title column and then the author column, the book will be sorted by author and then all the entries for the same author will be sorted by title. - + * You can search for a particular book or set of books using the search bar. More on that below. - + * You can quickly and conveniently edit metadata by double-clicking the entry you want changed in the list. - + * You can perform :ref:`actions` on sets to books. To select multiple books you can either: - + - Keep the :kbd:`Ctrl` key pressed and click on the books you want selected. - + - Keep the :kbd:`Shift` key pressed and click on the starting and ending book of arange of books you want selected. - + * You can configure which fields you want displayed by using the :ref:`configuration` dialog. .. _search_interface: @@ -310,10 +313,10 @@ The Search & Sort section allows you to perform several powerful actions on your The Search Interface --------------------- You can search all the metadata by entering search terms in the search bar. Searches are case insensitive. For example:: - + Asimov Foundation format:lrf -This will match all books in your library that have ``Asimov`` and ``Foundation`` in their metadata and +This will match all books in your library that have ``Asimov`` and ``Foundation`` in their metadata and are available in the LRF format. Some more examples:: author:Asimov and not series:Foundation @@ -327,20 +330,18 @@ Equality searches are indicated by prefixing the search string with an equals si ``tag:"=science"`` will match "science", but not "science fiction" or "hard science". Regular expression searches are indicated by prefixing the search string with a tilde (~). Any python-compatible regular expression can be used. Regular expression searches are contains searches unless the expression contains anchors. -Should you need to search for a string with a leading equals or tilde, prefix the string with a backslash. +Should you need to search for a string with a leading equals or tilde, prefix the string with a backslash. Enclose search strings with quotes (") if the string contains parenthesis or spaces. For example, to search for the tag ``Science Fiction``, you would need to search for ``tag:"=science fiction"``. If you search for ``tag:=science fiction``, you will find all books with the tag 'science' and containing the word 'fiction' in any metadata. -You can build advanced search queries easily using the :guilabel:`Advanced Search Dialog`, accessed by +You can build advanced search queries easily using the :guilabel:`Advanced Search Dialog`, accessed by clicking the button |sbi|. Available fields for searching are: ``tag, title, author, publisher, series, series_index, rating, cover, -comments, format, isbn, date, pubdate, search, size`` and custom columns. If a device is plugged in, the -``ondevice`` field becomes available. To find the search name for a custom column, hover your mouse over the -column header. +comments, format, identifiers, date, pubdate, search, size`` and custom columns. If a device is plugged in, the ``ondevice`` field becomes available. To find the search name (actually called the `lookup name`) for a custom column, hover your mouse over the column header in the library view. The syntax for searching for dates is:: @@ -351,7 +352,7 @@ The syntax for searching for dates is:: If the date is ambiguous, the current locale is used for date comparison. For example, in an mm/dd/yyyy locale, 2/1/2009 is interpreted as 1 Feb 2009. In a dd/mm/yyyy locale, it is interpreted as 2 Jan 2009. Some special date strings are available. The string ``today`` translates to today's date, whatever it is. The -strings `yesterday`` and ``thismonth`` also work. In addition, the string ``daysago`` can be used to compare +strings ``yesterday`` and ``thismonth`` also work. In addition, the string ``daysago`` can be used to compare to a date some number of days ago, for example: date:>10daysago, date:<=45daysago. You can search for books that have a format of a certain size like this:: @@ -364,6 +365,8 @@ Dates and numeric fields support the relational operators ``=`` (equals), ``>`` Rating fields are considered to be numeric. For example, the search ``rating:>=3`` will find all books rated 3 or higher. +You can search for the number of items in multiple-valued fields such as tags). These searches begin with the character ``#``, then use the same syntax as numeric fields. For example, to find all books with more than 4 tags, use ``tags:#>4``. To find all books with exactly 10 tags, use ``tags:#=10``. + Series indices are searchable. For the standard series, the search name is 'series_index'. For custom series columns, use the column search name followed by _index. For example, to search the indices for a custom series column named ``#my_series``, you would use the search name ``#my_series_index``. @@ -385,18 +388,33 @@ with undefined values in the column. Searching for ``true`` will find all books values in the column. Searching for ``yes`` or ``checked`` will find all books with ``Yes`` in the column. Searching for ``no`` or ``unchecked`` will find all books with ``No`` in the column. +Hierarchical items (e.g. A.B.C) use an extended syntax to match initial parts of the hierarchy. This is done by adding a period between the exact match indicator (=) and the text. For example, the query ``tags:=.A`` will find the tags `A` and `A.B`, but will not find the tags `AA` or `AA.B`. The query ``tags:=.A.B`` will find the tags `A.B` and `A.C`, but not the tag `A`. + +Identifiers (e.g., isbn, doi, lccn etc) also use an extended syntax. First, note that an identifier has the form ``type:value``, as in ``isbn:123456789``. The extended syntax permits you to specify independently which type and value to search for. Both the type and the value parts of the query can use `equality`, `contains`, or `regular expression` matches. Examples: + + * ``identifiers:true`` will find books with any identifier. + * ``identifiers:false`` will find books with no identifier. + * ``identifiers:123`` will search for books with any type having a value containing `123`. + * ``identifiers:=123456789`` will search for books with any type having a value equal to `123456789`. + * ``identifiers:=isbn:`` and ``identifiers:isbn:true`` will find books with a type equal to isbn having any value + * ``identifiers:=isbn:false`` will find books with no type equal to isbn. + * ``identifiers:=isbn:123`` will find books with a type equal to isbn having a value containing `123`. + * ``identifiers:=isbn:=123456789`` will find books with a type equal to isbn having a value equal to `123456789`. + * ``identifiers:i:1`` will find books with a type containing an `i` having a value containing a `1`. + + .. |sbi| image:: images/search_button.png :align: middle .. figure:: images/search.png :align: center - + :guilabel:`Advanced Search Dialog` Saving searches ----------------- -|app| has a useful feature, it allows you to save a search you use frequently under a special name and then re-use that search with a single click. To do this, create your search, either by typing it in the search bar, or using the Tag Browser. Then, type the name you would like to give to the search in the Saved Searches box next to the search bar and click the plus icon next to the saved searches box to save the search. +|app| has a useful feature, it allows you to save a search you use frequently under a special name and then re-use that search with a single click. To do this, create your search, either by typing it in the search bar, or using the Tag Browser. Then, type the name you would like to give to the search in the Saved Searches box next to the search bar and click the plus icon next to the saved searches box to save the search. Now, you can access your saved search in the Tag Browser under "Searches". A single click will allow you to re-use any arbitrarily complex search easily, without needing to re-create it. @@ -436,25 +454,26 @@ Tag Browser .. image:: images/tag_browser.png :class: float-left-img -The Tag Browser allows you to easily browse your collection by Author/Tags/Series/etc. If you click on any Item in the Tag Browser, for example, the Author name, Isaac Asimov, then the list of books to the right is restricted to books by that author. Clicking once again on Isaac Asimov will restrict the list of books to books not by Isaac Asimov. A third click will remove the restriction. If you hold down the Ctrl or Shift keys and click on multiple items, then restrictions based on multiple items are created. For example you could Hold Ctrl and click on the tags History and Europe for find books on European history. The Tag Browser works by constructing search expressions that are automatically entered into the Search bar. It is a good way to learn how to construct basic search expressions. +The Tag Browser allows you to easily browse your collection by Author/Tags/Series/etc. If you click on any item in the Tag Browser, for example the author name Isaac Asimov, then the list of books to the right is restricted to showing books by that author. You can click on category names as well. For example, clicking on "Series" will show you all books in any series. -There is a search bar at the top of the Tag Browser that allows you to easily find any item in the Tag Browser. In addition, you can right click on any item and choose to hide it or rename it or open a "Manage x" dialog that allows you to manage items of that kind. For example the "Manage Authors" dialog allows you to rename authors and control how their names are sorted. +The first click on an item will restrict the list of books to those that contain/match the item. Continuing the above example, clicking on Isaac Asimov will show books by that author. Clicking again on the item will change what is shown, depending on whether the item has children (see sub-categories and hierarchical items below). Continuing the Isaac Asimov example, clicking again on Isaac Asimov will restrict the list of books to those not by Isaac Asimov. A third click will remove the restriction, showing all books. If you hold down the Ctrl or Shift keys and click on multiple items, then restrictions based on multiple items are created. For example you could hold Ctrl and click on the tags History and Europe for find books on European history. The Tag Browser works by constructing search expressions that are automatically entered into the Search bar. Looking at what the Tag Browser generates is a good way to learn how to construct basic search expressions. Items in the Tag browser have their icons partially colored. The amount of color depends on the average rating of the books in that category. So for example if the books by Isaac Asimov have an average of four stars, the icon for Isaac Asimov in the Tag Browser will be 4/5th colored. You can hover your mouse over the icon to see the average rating. -For convenience, you can drag and drop books from the book list to items in the Tag Browser and that item will be automatically applied to the dropped books. For example, dragging a book to Isaac Asimov will set the author of that book to Isaac Asimov or dragging it to the tag History will add the tag History to its tags. +The outer-level items in the tag browser such as Authors and Series are called categories. You can create your own categories, called User Categories, which are useful for organizing items. For example, you can use the User Categories Editor (push the Manage User Categories button) to create a user category called Favorite Authors, then put the items for your favorites into the category. User categories can have sub-categories. For example, the user category Favorites.Authors is a sub-category of Favorites. You might also have Favorites.Series, in which case there will be two sub-categories under Favorites. Sub-categories can be created by right-clicking on a user category, choosing "Add sub-category to ...", and entering the sub-category name; or by using the User Categories Editor by entering names like the Favorites example above. -The outer-level items in the tag browser such as Authors and Series are called categories. You can create your own categories, called User Categories, which are useful for organizing items. For example, you can use the user categories editor (push the Manage User Categories button) to create a user category called Favorite Authors, then put the items for your favorites into the category. User categories act like built-in categories; you can click on items to search for them. You can search for all items in a category by right-clicking on the category name and choosing "Search for books in ...". +You can search user categories in the same way as built-in categories, by clicking on them. There are four different searches cycled through by clicking: "everything matching an item in the category" indicated by a single green plus sign, "everything matching an item in the category or its sub-categories" indicated by two green plus signs, "everything not matching an item in the category" shown by a single red minus sign, and "everything not matching an item in the category or its sub-categories" shown by two red minus signs. -User categories can have sub-categories. For example, the user category Favorites.Authors is a sub-category of Favorites. You might also have Favorites.Series, in which case there will be two sub-categories under Favorites. Sub-categories can be created using Manage User Categories by entering names like the Favorites example. They can also be created by right-clicking on a user category, choosing "Add sub-category to ...", and entering the category name. +It is also possible to create hierarchies inside some of the text categories such as tags, series, and custom columns. These hierarchies show with the small triangle, permitting the sub-items to be hidden. To use hierarchies of items in a category, you must first go to Preferences / Look & Feel and enter the category name(s) into the "Categories with hierarchical items" box. Once this is done, items in that category that contain periods will be shown using the small triangle. For example, assume you create a custom column called "Genre" and indicate that it contains hierarchical items. Once done, items such as Mystery.Thriller and Mystery.English will display as Mystery with the small triangle next to it. Clicking on the triangle will show Thriller and English as sub-items. See :ref:`Managing subgroups of books, for example "genre" ` for more information. -It is also possible to create hierarchies inside some of the built-in categories (the text categories). These hierarchies show with the small triangle permitting the sub-items to be hidden. To use hierarchies in a category, you must first go to Preferences / Look & Feel and enter the category name(s) into the "Categories with hierarchical items" box. Once this is done, items in that category that contain periods will be shown using the small triangle. For example, assume you create a custom column called "Genre" and indicate that it contains hierarchical items. Once done, items such as Mystery.Thriller and Mystery.English will display as Mystery with the small triangle next to it. Clicking on the triangle will show Thriller and English as sub-items. +Hierarchical items (items with children) use the same four 'click-on' searches as user categories. Items that do not have children use two of the searches: "everything matching" and "everything not matching". -You can drag and drop items in the Tag browser onto user categories to add them to that category. +You can drag and drop items in the Tag browser onto user categories to add them to that category. If the source is a user category, holding the shift key while dragging will move the item to the new category. You can also drag and drop books from the book list onto items in the Tag Browser; dropping a book on an item causes that item to be automatically applied to the dropped books. For example, dragging a book onto Isaac Asimov will set the author of that book to Isaac Asimov. Dropping it onto the tag History will add the tag History to the book's tags. + +There is a search bar at the top of the Tag Browser that allows you to easily find any item in the Tag Browser. In addition, you can right click on any item and choose one of several operations. Some examples are to hide the it, rename it, or open a "Manage x" dialog that allows you to manage items of that kind. For example, the "Manage Authors" dialog allows you to rename authors and control how their names are sorted. You can control how items are sorted in the Tag browser via the box at the bottom of the Tag Browser. You can choose to sort by name, average rating or popularity (popularity is the number of books with an item in your library; for example; the popularity of Isaac Asimov is the number of book sin your library by Isaac Asimov). - Jobs ----- .. image:: images/jobs.png @@ -479,45 +498,47 @@ Calibre has several keyboard shortcuts to save you time and mouse movement. Thes - Action * - :kbd:`F2 (Enter in OS X)` - Edit the metadata of the currently selected field in the book list. - * - :kbd:`A` + * - :kbd:`A` - Add Books - * - :kbd:`C` + * - :kbd:`Shift+A` + - Add Formats to the selected books + * - :kbd:`C` - Convert selected Books - * - :kbd:`D` + * - :kbd:`D` - Send to device - * - :kbd:`Del` + * - :kbd:`Del` - Remove selected Books - * - :kbd:`E` + * - :kbd:`E` - Edit metadata of selected books - * - :kbd:`I` + * - :kbd:`I` - Show book details - * - :kbd:`M` + * - :kbd:`M` - Merge selected records - * - :kbd:`Alt+M` + * - :kbd:`Alt+M` - Merge selected records, keeping originals - * - :kbd:`O` + * - :kbd:`O` - Open containing folder - * - :kbd:`S` + * - :kbd:`S` - Save to Disk - * - :kbd:`V` + * - :kbd:`V` - View - * - :kbd:`Alt+V/Cmd+V in OS X` + * - :kbd:`Alt+V/Cmd+V in OS X` - View specific format - * - :kbd:`Alt+Shift+J` + * - :kbd:`Alt+Shift+J` - Toggle jobs list - * - :kbd:`Alt+Shift+B` + * - :kbd:`Alt+Shift+B` - Toggle Cover Browser - * - :kbd:`Alt+Shift+T` + * - :kbd:`Alt+Shift+T` - Toggle Tag Browser - * - :kbd:`Alt+A` + * - :kbd:`Alt+A` - Show books by the Same author as the current book - * - :kbd:`Alt+T` + * - :kbd:`Alt+T` - Show books with the same tags as current book - * - :kbd:`Alt+P` + * - :kbd:`Alt+P` - Show books by the same publisher as current book - * - :kbd:`Alt+Shift+S` + * - :kbd:`Alt+Shift+S` - Show books in the same series as current book - * - :kbd:`/, Ctrl+F` + * - :kbd:`/, Ctrl+F` - Focus the search bar * - :kbd:`Shift+Ctrl+F` - Open the advanced search dialog @@ -527,13 +548,15 @@ Calibre has several keyboard shortcuts to save you time and mouse movement. Thes - Find the next book that matches the current search (only works if the highlight checkbox next to the search bar is checked) * - :kbd:`Shift+N or Shift+F3` - Find the next book that matches the current search (only works if the highlight checkbox next to the search bar is checked) - * - :kbd:`Ctrl+D` + * - :kbd:`Ctrl+D` - Download metadata and shortcuts - * - :kbd:`Ctrl+R` + * - :kbd:`Ctrl+R` - Restart calibre + * - :kbd:`Ctrl+Shift+R` + - Restart calibre in debug mode * - :kbd:`Shift+Ctrl+E` - Add empty books to calibre - * - :kbd:`Ctrl+Q` + * - :kbd:`Ctrl+Q` - Quit calibre diff --git a/src/calibre/manual/images/sg_cc.jpg b/src/calibre/manual/images/sg_cc.jpg new file mode 100644 index 0000000000..c6be070ea5 Binary files /dev/null and b/src/calibre/manual/images/sg_cc.jpg differ diff --git a/src/calibre/manual/images/sg_genre.jpg b/src/calibre/manual/images/sg_genre.jpg new file mode 100644 index 0000000000..5d9658b14d Binary files /dev/null and b/src/calibre/manual/images/sg_genre.jpg differ diff --git a/src/calibre/manual/images/sg_pref.jpg b/src/calibre/manual/images/sg_pref.jpg new file mode 100644 index 0000000000..8bab672c25 Binary files /dev/null and b/src/calibre/manual/images/sg_pref.jpg differ diff --git a/src/calibre/manual/images/sg_restrict.jpg b/src/calibre/manual/images/sg_restrict.jpg new file mode 100644 index 0000000000..0400a27b30 Binary files /dev/null and b/src/calibre/manual/images/sg_restrict.jpg differ diff --git a/src/calibre/manual/images/sg_restrict2.jpg b/src/calibre/manual/images/sg_restrict2.jpg new file mode 100644 index 0000000000..1be7dea603 Binary files /dev/null and b/src/calibre/manual/images/sg_restrict2.jpg differ diff --git a/src/calibre/manual/images/sg_search.jpg b/src/calibre/manual/images/sg_search.jpg new file mode 100644 index 0000000000..fb23b129ad Binary files /dev/null and b/src/calibre/manual/images/sg_search.jpg differ diff --git a/src/calibre/manual/images/sg_tb.jpg b/src/calibre/manual/images/sg_tb.jpg new file mode 100644 index 0000000000..63610845db Binary files /dev/null and b/src/calibre/manual/images/sg_tb.jpg differ diff --git a/src/calibre/manual/images/sg_tree.jpg b/src/calibre/manual/images/sg_tree.jpg new file mode 100644 index 0000000000..c116f2f46a Binary files /dev/null and b/src/calibre/manual/images/sg_tree.jpg differ diff --git a/src/calibre/manual/index.rst b/src/calibre/manual/index.rst index bc8e8a97c2..e54882dda0 100644 --- a/src/calibre/manual/index.rst +++ b/src/calibre/manual/index.rst @@ -40,3 +40,84 @@ Sections glossary +The main |app| user interface +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +.. toctree:: + :maxdepth: 2 + + gui + +Adding your favorite news website to |app| +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +.. toctree:: + :maxdepth: 2 + + news + +The |app| e-book viewer +^^^^^^^^^^^^^^^^^^^^^^^^^^ + +.. toctree:: + :maxdepth: 2 + + viewer + +Customizing |app|'s e-book conversion +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +.. toctree:: + :maxdepth: 2 + + conversion + +Editing e-book metadata +^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +.. toctree:: + :maxdepth: 2 + + metadata + +Frequently Asked Questions +^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +.. toctree:: + :maxdepth: 2 + + faq + +Tutorials +^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +.. toctree:: + :maxdepth: 2 + + tutorials + +Customizing |app| +^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +.. toctree:: + :maxdepth: 2 + + customize + +The Command Line Interface +^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +.. toctree:: + :maxdepth: 2 + + cli/cli-index + +Setting up a |app| development environment +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +.. toctree:: + :maxdepth: 2 + + develop + + diff --git a/src/calibre/manual/metadata.rst b/src/calibre/manual/metadata.rst index ec3dbb08bf..72ae2b8250 100644 --- a/src/calibre/manual/metadata.rst +++ b/src/calibre/manual/metadata.rst @@ -19,7 +19,7 @@ Editing the metadata of one book at a time Click the book you want to edit and then click the :guilabel:`Edit metadata` button or press the ``E`` key. A dialog opens that allows you to edit all aspects of the metadata. It has various features to make editing faster and more efficient. A list of the commonly used tips: * You can click the button in between title and authors to swap them automatically. - * You can click the button next to author sort to automatically to have |app| automatically fill it from the author name. + * You can click the button next to author sort to have |app| automatically fill it in using the sort values stored with each author. Use the :guilabel:`Manage authors` dialog to see and change the authors' sort values. This dialog can be opened by clicking and holding the button next to author sort. * You can click the button next to tags to use the Tag Editor to manage the tags associated with the book. * The ISBN box will have a red background if you enter an invalid ISBN. It will be green for valid ISBNs * The author sort box will be red if the author sort value differs from what |app| thinks it should be. diff --git a/src/calibre/manual/news.rst b/src/calibre/manual/news.rst index d0838ccb0f..52dc5a7166 100644 --- a/src/calibre/manual/news.rst +++ b/src/calibre/manual/news.rst @@ -137,7 +137,7 @@ to the recipe. Finally, lets replace some of the :term:`CSS` that we disabled ea With these additions, our recipe has become "production quality", indeed it is very close to the actual recipe used by |app| for the *BBC*, shown below: -.. literalinclude:: ../../../resources/recipes/bbc.recipe +.. literalinclude:: ../../../recipes/bbc.recipe This :term:`recipe` explores only the tip of the iceberg when it comes to the power of |app|. To explore more of the abilities of |app| we'll examine a more complex real life example in the next section. @@ -263,20 +263,18 @@ Tips for developing new recipes The best way to develop new recipes is to use the command line interface. Create the recipe using your favorite python editor and save it to a file say :file:`myrecipe.recipe`. The `.recipe` extension is required. You can download content using this recipe with the command:: - ebook-convert myrecipe.recipe output_dir --test -vv + ebook-convert myrecipe.recipe .epub --test -vv --debug-pipeline debug -The :command:`ebook-convert` will download all the webpages and save them to the directory :file:`output_dir`, creating it if necessary. The :option:`-vv` makes ebook-convert spit out a lot of information about what it is doing. The :option:`--test` makes it download only a couple of articles from at most two feeds. +The command :command:`ebook-convert` will download all the webpages and save them to the EPUB file :file:`myrecipe.epub`. The :option:`-vv` makes ebook-convert spit out a lot of information about what it is doing. The :option:`--test` makes it download only a couple of articles from at most two feeds. In addition, ebook-convert will put the downloaded HTML into the ``debug/input`` directory, where ``debug`` is the directory you specified in the :option:`--debug-pipeline` option. -Once the download is complete, you can look at the downloaded :term:`HTML` by opening the file :file:`index.html` in a browser. Once you're satisfied that the download and preprocessing is happening correctly, you can generate ebooks in different formats as shown below:: +Once the download is complete, you can look at the downloaded :term:`HTML` by opening the file :file:`debug/input/index.html` in a browser. Once you're satisfied that the download and preprocessing is happening correctly, you can generate ebooks in different formats as shown below:: ebook-convert myrecipe.recipe myrecipe.epub ebook-convert myrecipe.recipe myrecipe.mobi ... -If you're satisfied with your recipe, and you feel there is enough demand to justify its inclusion into the set of built-in recipes, add a comment to the ticket http://bugs.calibre-ebook.com/ticket/405 - -Alternatively, you could just post your recipe in the calibre forum at http://www.mobileread.com/forums/forumdisplay.php?f=166 to share it with other calibre users. +If you're satisfied with your recipe, and you feel there is enough demand to justify its inclusion into the set of built-in recipes, post your recipe in the `calibre recipes forum `_ to share it with other calibre users. .. seealso:: diff --git a/src/calibre/manual/plugin_examples/helloworld/__init__.py b/src/calibre/manual/plugin_examples/helloworld/__init__.py new file mode 100644 index 0000000000..3a49123e07 --- /dev/null +++ b/src/calibre/manual/plugin_examples/helloworld/__init__.py @@ -0,0 +1,33 @@ +#!/usr/bin/env python +# vim:fileencoding=UTF-8:ts=4:sw=4:sta:et:sts=4:ai +from __future__ import (unicode_literals, division, absolute_import, + print_function) + +__license__ = 'GPL v3' +__copyright__ = '2011, Kovid Goyal ' +__docformat__ = 'restructuredtext en' + +import os +from calibre.customize import FileTypePlugin + +class HelloWorld(FileTypePlugin): + + name = 'Hello World Plugin' # Name of the plugin + description = 'Set the publisher to Hello World for all new conversions' + supported_platforms = ['windows', 'osx', 'linux'] # Platforms this plugin will run on + author = 'Acme Inc.' # The author of this plugin + version = (1, 0, 0) # The version number of this plugin + file_types = set(['epub', 'mobi']) # The file types that this plugin will be applied to + on_postprocess = True # Run this plugin after conversion is complete + minimum_calibre_version = (0, 7, 53) + + def run(self, path_to_ebook): + from calibre.ebooks.metadata.meta import get_metadata, set_metadata + file = open(path_to_ebook, 'r+b') + ext = os.path.splitext(path_to_ebook)[-1][1:].lower() + mi = get_metadata(file, ext) + mi.publisher = 'Hello World' + set_metadata(file, mi, ext) + return path_to_ebook + + diff --git a/src/calibre/manual/plugin_examples/interface_demo/__init__.py b/src/calibre/manual/plugin_examples/interface_demo/__init__.py new file mode 100644 index 0000000000..ac7d9c6ec1 --- /dev/null +++ b/src/calibre/manual/plugin_examples/interface_demo/__init__.py @@ -0,0 +1,80 @@ +#!/usr/bin/env python +# vim:fileencoding=UTF-8:ts=4:sw=4:sta:et:sts=4:ai +from __future__ import (unicode_literals, division, absolute_import, + print_function) + +__license__ = 'GPL v3' +__copyright__ = '2011, Kovid Goyal ' +__docformat__ = 'restructuredtext en' + +# The class that all Interface Action plugin wrappers must inherit from +from calibre.customize import InterfaceActionBase + +class InterfacePluginDemo(InterfaceActionBase): + ''' + This class is a simple wrapper that provides information about the actual + plugin class. The actual interface plugin class is called InterfacePlugin + and is defined in the ui.py file, as specified in the actual_plugin field + below. + + The reason for having two classes is that it allows the command line + calibre utilities to run without needing to load the GUI libraries. + ''' + name = 'Interface Plugin Demo' + description = 'An advanced plugin demo' + supported_platforms = ['windows', 'osx', 'linux'] + author = 'Kovid Goyal' + version = (1, 0, 0) + minimum_calibre_version = (0, 7, 53) + + #: This field defines the GUI plugin class that contains all the code + #: that actually does something. Its format is module_path:class_name + #: The specified class must be defined in the specified module. + actual_plugin = 'calibre_plugins.interface_demo.ui:InterfacePlugin' + + def is_customizable(self): + ''' + This method must return True to enable customization via + Preferences->Plugins + ''' + return True + + def config_widget(self): + ''' + Implement this method and :meth:`save_settings` in your plugin to + use a custom configuration dialog. + + This method, if implemented, must return a QWidget. The widget can have + an optional method validate() that takes no arguments and is called + immediately after the user clicks OK. Changes are applied if and only + if the method returns True. + + If for some reason you cannot perform the configuration at this time, + return a tuple of two strings (message, details), these will be + displayed as a warning dialog to the user and the process will be + aborted. + + The base class implementation of this method raises NotImplementedError + so by default no user configuration is possible. + ''' + # It is important to put this import statement here rather than at the + # top of the module as importing the config class will also cause the + # GUI libraries to be loaded, which we do not want when using calibre + # from the command line + from calibre_plugins.interface_demo.config import ConfigWidget + return ConfigWidget() + + def save_settings(self, config_widget): + ''' + Save the settings specified by the user with config_widget. + + :param config_widget: The widget returned by :meth:`config_widget`. + ''' + config_widget.save_settings() + + # Apply the changes + ac = self.actual_plugin_ + if ac is not None: + ac.apply_settings() + + diff --git a/src/calibre/manual/plugin_examples/interface_demo/about.txt b/src/calibre/manual/plugin_examples/interface_demo/about.txt new file mode 100644 index 0000000000..f35b1e7196 --- /dev/null +++ b/src/calibre/manual/plugin_examples/interface_demo/about.txt @@ -0,0 +1,7 @@ +The Interface Plugin Demo +=========================== + +Created by Kovid Goyal + +Requires calibre >= 0.7.53 + diff --git a/src/calibre/manual/plugin_examples/interface_demo/config.py b/src/calibre/manual/plugin_examples/interface_demo/config.py new file mode 100644 index 0000000000..fd391ce944 --- /dev/null +++ b/src/calibre/manual/plugin_examples/interface_demo/config.py @@ -0,0 +1,41 @@ +#!/usr/bin/env python +# vim:fileencoding=UTF-8:ts=4:sw=4:sta:et:sts=4:ai +from __future__ import (unicode_literals, division, absolute_import, + print_function) + +__license__ = 'GPL v3' +__copyright__ = '2011, Kovid Goyal ' +__docformat__ = 'restructuredtext en' + +from PyQt4.Qt import QWidget, QHBoxLayout, QLabel, QLineEdit + +from calibre.utils.config import JSONConfig + +# This is where all preferences for this plugin will be stored +# Remember that this name (i.e. plugins/interface_demo) is also +# in a global namespace, so make it as unique as possible. +# You should always prefix your config file name with plugins/, +# so as to ensure you dont accidentally clobber a calibre config file +prefs = JSONConfig('plugins/interface_demo') + +# Set defaults +prefs.defaults['hello_world_msg'] = 'Hello, World!' + +class ConfigWidget(QWidget): + + def __init__(self): + QWidget.__init__(self) + self.l = QHBoxLayout() + self.setLayout(self.l) + + self.label = QLabel('Hello world &message:') + self.l.addWidget(self.label) + + self.msg = QLineEdit(self) + self.msg.setText(prefs['hello_world_msg']) + self.l.addWidget(self.msg) + self.label.setBuddy(self.msg) + + def save_settings(self): + prefs['hello_world_msg'] = unicode(self.msg.text()) + diff --git a/src/calibre/manual/plugin_examples/interface_demo/images/icon.png b/src/calibre/manual/plugin_examples/interface_demo/images/icon.png new file mode 100644 index 0000000000..ad823e2ff4 Binary files /dev/null and b/src/calibre/manual/plugin_examples/interface_demo/images/icon.png differ diff --git a/src/calibre/manual/plugin_examples/interface_demo/main.py b/src/calibre/manual/plugin_examples/interface_demo/main.py new file mode 100644 index 0000000000..f23664b1de --- /dev/null +++ b/src/calibre/manual/plugin_examples/interface_demo/main.py @@ -0,0 +1,116 @@ +#!/usr/bin/env python +# vim:fileencoding=UTF-8:ts=4:sw=4:sta:et:sts=4:ai +from __future__ import (unicode_literals, division, absolute_import, + print_function) + +__license__ = 'GPL v3' +__copyright__ = '2011, Kovid Goyal ' +__docformat__ = 'restructuredtext en' + +if False: + # This is here to keep my python error checker from complaining about + # the builtin functions that will be defined by the plugin loading system + # You do not need this code in your plugins + get_icons = get_resources = None + +from PyQt4.Qt import QDialog, QVBoxLayout, QPushButton, QMessageBox, QLabel + +from calibre_plugins.interface_demo.config import prefs + +class DemoDialog(QDialog): + + def __init__(self, gui, icon, do_user_config): + QDialog.__init__(self, gui) + self.gui = gui + self.do_user_config = do_user_config + + # The current database shown in the GUI + # db is an instance of the class LibraryDatabase2 from database.py + # This class has many, many methods that allow you to do a lot of + # things. + self.db = gui.current_db + + self.l = QVBoxLayout() + self.setLayout(self.l) + + self.label = QLabel(prefs['hello_world_msg']) + self.l.addWidget(self.label) + + self.setWindowTitle('Interface Plugin Demo') + self.setWindowIcon(icon) + + self.about_button = QPushButton('About', self) + self.about_button.clicked.connect(self.about) + self.l.addWidget(self.about_button) + + self.marked_button = QPushButton( + 'Show books with only one format in the calibre GUI', self) + self.marked_button.clicked.connect(self.marked) + self.l.addWidget(self.marked_button) + + self.view_button = QPushButton( + 'View the most recently added book', self) + self.view_button.clicked.connect(self.view) + self.l.addWidget(self.view_button) + + self.conf_button = QPushButton( + 'Configure this plugin', self) + self.conf_button.clicked.connect(self.config) + self.l.addWidget(self.conf_button) + + self.resize(self.sizeHint()) + + def about(self): + # Get the about text from a file inside the plugin zip file + # The get_resources function is a builtin function defined for all your + # plugin code. It loads files from the plugin zip file. It returns + # the bytes from the specified file. + # + # Note that if you are loading more than one file, for performance, you + # should pass a list of names to get_resources. In this case, + # get_resources will return a dictionary mapping names to bytes. Names that + # are not found in the zip file will not be in the returned dictionary. + text = get_resources('about.txt') + QMessageBox.about(self, 'About the Interface Plugin Demo', + text.decode('utf-8')) + + def marked(self): + fmt_idx = self.db.FIELD_MAP['formats'] + matched_ids = set() + for record in self.db.data.iterall(): + # Iterate over all records + fmts = record[fmt_idx] + # fmts is either None or a comma separated list of formats + if fmts and ',' not in fmts: + matched_ids.add(record[0]) + # Mark the records with the matching ids + self.db.set_marked_ids(matched_ids) + + # Tell the GUI to search for all marked records + self.gui.search.setEditText('marked:true') + self.gui.search.do_search() + + def view(self): + most_recent = most_recent_id = None + timestamp_idx = self.db.FIELD_MAP['timestamp'] + + for record in self.db.data: + # Iterate over all currently showing records + timestamp = record[timestamp_idx] + if most_recent is None or timestamp > most_recent: + most_recent = timestamp + most_recent_id = record[0] + + if most_recent_id is not None: + # Get the row number of the id as shown in the GUI + row_number = self.db.row(most_recent_id) + # Get a reference to the View plugin + view_plugin = self.gui.iactions['View'] + # Ask the view plugin to launch the viewer for row_number + view_plugin._view_books([row_number]) + + def config(self): + self.do_user_config(parent=self) + # Apply the changes + self.label.setText(prefs['hello_world_msg']) + diff --git a/src/calibre/manual/plugin_examples/interface_demo/plugin-import-name-interface_demo.txt b/src/calibre/manual/plugin_examples/interface_demo/plugin-import-name-interface_demo.txt new file mode 100644 index 0000000000..e69de29bb2 diff --git a/src/calibre/manual/plugin_examples/interface_demo/ui.py b/src/calibre/manual/plugin_examples/interface_demo/ui.py new file mode 100644 index 0000000000..5026269b60 --- /dev/null +++ b/src/calibre/manual/plugin_examples/interface_demo/ui.py @@ -0,0 +1,71 @@ +#!/usr/bin/env python +# vim:fileencoding=UTF-8:ts=4:sw=4:sta:et:sts=4:ai +from __future__ import (unicode_literals, division, absolute_import, + print_function) + +__license__ = 'GPL v3' +__copyright__ = '2011, Kovid Goyal ' +__docformat__ = 'restructuredtext en' + +if False: + # This is here to keep my python error checker from complaining about + # the builtin functions that will be defined by the plugin loading system + # You do not need this code in your plugins + get_icons = get_resources = None + +# The class that all interface action plugins must inherit from +from calibre.gui2.actions import InterfaceAction +from calibre_plugins.interface_demo.main import DemoDialog + +class InterfacePlugin(InterfaceAction): + + name = 'Interface Plugin Demo' + + # Declare the main action associated with this plugin + # The keyboard shortcut can be None if you dont want to use a keyboard + # shortcut. Remember that currently calibre has no central management for + # keyboard shortcuts, so try to use an unusual/unused shortcut. + action_spec = ('Interface Plugin Demo', None, + 'Run the Interface Plugin Demo', 'Ctrl+Shift+F1') + + def genesis(self): + # This method is called once per plugin, do initial setup here + + # Set the icon for this interface action + # The get_icons function is a builtin function defined for all your + # plugin code. It loads icons from the plugin zip file. It returns + # QIcon objects, if you want the actual data, use the analogous + # get_resources builtin function. + # + # Note that if you are loading more than one icon, for performance, you + # should pass a list of names to get_icons. In this case, get_icons + # will return a dictionary mapping names to QIcons. Names that + # are not found in the zip file will result in null QIcons. + icon = get_icons('images/icon.png') + + # The qaction is automatically created from the action_spec defined + # above + self.qaction.setIcon(icon) + self.qaction.triggered.connect(self.show_dialog) + + def show_dialog(self): + # The base plugin object defined in __init__.py + base_plugin_object = self.interface_action_base_plugin + # Show the config dialog + # The config dialog can also be shown from within + # Preferences->Plugins, which is why the do_user_config + # method is defined on the base plugin class + do_user_config = base_plugin_object.do_user_config + + # self.gui is the main calibre GUI. It acts as the gateway to access + # all the elements of the calibre user interface, it should also be the + # parent of the dialog + d = DemoDialog(self.gui, self.qaction.icon(), do_user_config) + d.show() + + def apply_settings(self): + from calibre_plugins.interface_demo.config import prefs + # In an actual non trivial plugin, you would probably need to + # do something based on the settings in prefs + prefs + diff --git a/src/calibre/manual/plugins.rst b/src/calibre/manual/plugins.rst index 0a62218fb9..1ebb180d46 100644 --- a/src/calibre/manual/plugins.rst +++ b/src/calibre/manual/plugins.rst @@ -65,17 +65,14 @@ Catalog plugins Metadata download plugins -------------------------- -.. module:: calibre.ebooks.metadata.fetch +.. module:: calibre.ebooks.metadata.sources.base -.. autoclass:: MetadataSource +.. autoclass:: Source :show-inheritance: :members: :member-order: bysource -.. autoclass:: calibre.ebooks.metadata.covers.CoverDownload - :show-inheritance: - :members: - :member-order: bysource +.. autoclass:: InternalMetadataCompareKeyGen Conversion plugins -------------------- diff --git a/src/calibre/manual/regexp.rst b/src/calibre/manual/regexp.rst index 776141b113..7c879fa2e2 100644 --- a/src/calibre/manual/regexp.rst +++ b/src/calibre/manual/regexp.rst @@ -133,4 +133,5 @@ Thanks for helping with tips, corrections and such: * kacir * Starson17 +For more about regexps see `The Python User Manual `_. diff --git a/src/calibre/manual/server.rst b/src/calibre/manual/server.rst index 6d1adc88cd..aa98ba57df 100644 --- a/src/calibre/manual/server.rst +++ b/src/calibre/manual/server.rst @@ -16,12 +16,14 @@ Here, we will show you how to integrate the |app| content server into another se Using a reverse proxy ----------------------- -This is the simplest approach as it allows you to use the binary calibre install with no external dependencies/system integration requirements. +A reverse proxy is when your normal server accepts incoming requests and passes them onto the calibre server. It then reads the response from the calibre server and forwards it to the client. This means that you can simply run the calibre server as normal without trying to integrate it closely with your main server, and you can take advantage of whatever authentication systems you main server has in place. This is the simplest approach as it allows you to use the binary calibre install with no external dependencies/system integration requirements. Below, is an example of how to achieve this with Apache as your main server, but it will work with any server that supports Reverse Proxies. First start the |app| content server as shown below:: calibre-server --url-prefix /calibre --port 8080 +The key parameter here is ``--url-prefix /calibre``. This causes the content server to serve all URLs prefixed by calibre. To see this in action, visit ``http://localhost:8080/calibre`` in your browser. You should see the normal content server website, but now it will run under /calibre. + Now suppose you are using Apache as your main server. First enable the proxy modules in apache, by adding the following to :file:`httpd.conf`:: LoadModule proxy_module modules/mod_proxy.so @@ -33,7 +35,7 @@ The exact technique for enabling the proxy modules will vary depending on your A RewriteRule ^/calibre/(.*) http://localhost:8080/calibre/$1 [proxy] RewriteRule ^/calibre http://localhost:8080 [proxy] -That's all, you will now be able to access the |app| Content Server under the /calibre URL in your apache server. +That's all, you will now be able to access the |app| Content Server under the /calibre URL in your apache server. The above rules pass all requests under /calibre to the calibre server running on port 8080 and thanks to the --url-prefix option above, the calibre server handles them transparently. .. note:: If you are willing to devote an entire VirtualHost to the content server, then there is no need to use --url-prefix and RewriteRule, instead just use the ProxyPass directive. diff --git a/src/calibre/manual/sub_groups.rst b/src/calibre/manual/sub_groups.rst new file mode 100644 index 0000000000..e5a433dce9 --- /dev/null +++ b/src/calibre/manual/sub_groups.rst @@ -0,0 +1,117 @@ + +.. include:: global.rst + +.. _subgroups-tutorial: + +Managing subgroups of books, for example "genre" +================================================== + +Some people wish to organize the books in their library into subgroups, similar to subfolders. The most commonly provided reason is to create genre hierarchies, but there are many others. One user asked for a way to organize textbooks by subject and course number. Another wanted to keep track of gifts by subject and recipient. This tutorial will use the genre example for the rest of this post. + +Before going on, please note that we are not talking about folders on the hard disk. Subgroups are not file folders. Books will not be copied anywhere. Calibre's library file structure is not affected. Instead, we are presenting a way to organize and display subgroups of books within a |app| library. + +.. contents:: + :depth: 1 + :local: + +.. |sgtree| image:: images/sg_tree.jpg + :class: float-right-img + + +The commonly-provided requirements for subgroups such as genres are: + + * A subgroup (e.g., a genre) must contain (point to) books, not categories of books. This is what distinguishes subgroups from |app| user categories. + * A book can be in multiple subgroups (genres). This distinguishes subgroups from physical file folders. + * Subgroups (genres) must form a hierarchy; subgroups can contain subgroups. + +Tags give you the first two. If you tag a book with the genre then you can use the tag browser (or search) for find the books with that genre, giving you the first. Many books can have the same tag(s), giving you the second. The problem is that tags don't satisfy the third requirement. They don't provide a hierarchy. + +|sgtree| Calibre's hierarchy feature gives you the third, the ability to see the genres in a 'tree' and the ability to easily search for books in genre or sub-genre. For example, assume that your genre structure is similar to the following:: + + Genre + . History + .. Japanese + .. Military + .. Roman + . Mysteries + .. English + .. Vampire + . Science Fiction + .. Alternate History + .. Military + .. Space Opera + . Thrillers + .. Crime + .. Horror + etc. + +By using the hierarchy feature, you can see these genres in the tag browser in tree form, as shown in the screen image. In this example the outermost level (Genre) is a custom column that contains the genres. Genres containing sub-genres appear with a small triangle next to them. Clicking on that triangle will open the item and show the sub-genres, as you can see with History and Science Fiction. + +Clicking on a genre can search for all books with that genre or children of that genre. For example, clicking on Science Fiction can give all three of the child genres, Alternate History, Military, and Space Opera. Clicking on Alternate History will give books in that genre, ignoring those in Military and Space Opera. Of course, a book can have multiple genres. If a book has both Space Opera and Military genres, then you will see that book if you click on either genre. Searching is discussed in more detail below. + +Another thing you can see from the image is that the genre Military appears twice, once under History and once under Science Fiction. Because the genres are in a hierarchy, these are two separate genres. A book can be in one, the other, or (doubtfully in this case) both. For example, the books in Winston Churchill's "The Second World War" could be in "History.Military". David Weber's Honor Harrington books could be in "Science Fiction.Military", and for that matter also in "Science Fiction.Space Opera." + +Once a genre exists, that is at least one book has that genre, you can easily apply it to other books by dragging the books from the library view onto the genre you want the books to have. You can also apply genres in the metadata editors; more on this below. + +Setup +---------------------------------------- + +By now, your question might be "How was all of this up?" There are three steps: 1) create the custom column, 2) tell |app| that the new column is to be treated as a hierarchy, and 3) add genres. + +You create the custom column in the usual way, using Preferences -> Add your own columns. This example uses "#genre" as the lookup name and "Genre" as the column heading. The column type is "Comma-separated text, like tags, shown in the tag browser." + +.. image:: images/sg_cc.jpg + :align: center + +Then after restarting |app|, you must tell |app| that the column is to be treated as a hierarchy. Go to Preferences -> Look and Feel and enter the lookup name "#genre" into the "Categories with hierarchical items" box. Press Apply, and you are done with setting up. + +.. image:: images/sg_pref.jpg + :align: center + +At the point there are no genres in the column. We are left with the last step: how to apply a genre to a book. A genre does not exist in |app| until it appears on at least one book. To learn how to apply a genre for the first time, we must go into some detail about what a genre looks like in the metadata for a book. + +A hierarchy of 'things' is built by creating an item consisting of phrases separated by periods. Continuing the genre example, these items would "History.Military", "Mysteries.Vampire", "Science Fiction.Space Opera", etc. Thus to create a new genre, you pick a book that should have that genre, edit its metadata, and enter the new genre into the column you created. Continuing our example, if you want to assign a new genre "Comics" with a sub-genre "Superheroes" to a book, you would 'edit metadata' for that (comic) book, choose the Custom metadata tab, and then enter "Comics.Superheroes" as shown in the following (ignore the other custom columns): + +.. image:: images/sg_genre.jpg + :align: center + +After doing the above, you see in the tag browser: + +.. image:: images/sg_tb.jpg + :align: center + +From here on, to apply this new genre to a book (a comic book, presumably), you can either drag the book onto the genre, or add it to the book using edit metadata in exactly the same way as done above. + +Searching +--------------- + +.. image:: images/sg_search.jpg + :align: center + +The easiest way to search for genres is using the tag browser, clicking on the genre you wish to see. Clicking on a genre with children will show you books with that genre and all child genres. However, this might bring up a question. Just because a genre has children doesn't mean that it isn't a genre in its own right. For example, a book can have the genre "History" but not "History.Military". How do you search for books with only "History"? + +The tag browser search mechanism knows if an item has children. If it does, clicking on the item cycles through 5 searches instead of the normal three. The first is the normal green plus, which shows you books with that genre only (e.g., History). The second is a doubled plus (shown above), which shows you books with that genre and all sub-genres (e.g., History and History.Military). The third is the normal red minus, which shows you books without that exact genre. The fourth is a doubled minus, which shows you books without that genre or sub-genres. The fifth is back to the beginning, no mark, meaning no search. + +Restrictions +--------------- + +If you search for a genre then create a saved search for it, you can use the 'restrict to' box to create a virtual library of books with that genre. This is useful if you want to do other searches within the genre or to manage/update metadata for books in the genre. Continuing our example, you can create a saved search named 'History.Japanese' by first clicking on the genre Japanese in the tag browser to get a search into the search box, entering History.Japanese into the saved search box, then pushing the "save search" button (the green box with the white plus, on the right-hand side). + +.. image:: images/sg_restrict.jpg + :align: center + +After creating the saved search, you can use it as a restriction. + +.. image:: images/sg_restrict2.jpg + :align: center + +Useful Template Functions +------------------------- + + You might want to use the genre information in a template, such as with save to disk or send to device. The question might then be "How do I get the outermost genre name or names?" An |app| template function, subitems, is provided to make doing this easier. + + For example, assume you want to add the outermost genre level to the save-to-disk template to make genre folders, as in "History/The Gathering Storm - Churchill, Winston". To do this, you must extract the first level of the hierarchy and add it to the front along with a slash to indicate that it should make a folder. The template below accomplishes this:: + + {#genre:subitems(0,1)||/}{title} - {authors} + +See :ref:`The |app| template language ` for more information templates and the subitem function. diff --git a/src/calibre/manual/template_lang.rst b/src/calibre/manual/template_lang.rst index f63a7c4e95..079af59286 100644 --- a/src/calibre/manual/template_lang.rst +++ b/src/calibre/manual/template_lang.rst @@ -30,7 +30,7 @@ You can use all the various metadata fields available in calibre in a template, In addition to the column based fields, you also can use:: {formats} - A list of formats available in the calibre library for a book - {isbn} - The ISBN number of the book + {identifiers:select(isbn)} - The ISBN number of the book If a particular book does not have a particular piece of metadata, the field in the template is automatically removed for that book. Consider, for example:: @@ -95,7 +95,7 @@ Advanced features Using templates in custom columns ---------------------------------- -There are sometimes cases where you want to display metadata that |app| does not normally display, or to display data in a way different from how |app| normally does. For example, you might want to display the ISBN, a field that |app| does not display. You can use custom columns for this by creating a column with the type 'column built from other columns' (hereafter called composite columns), and entering a template. Result: |app| will display a column showing the result of evaluating that template. To display the ISBN, create the column and enter ``{isbn}`` into the template box. To display a column containing the values of two series custom columns separated by a comma, use ``{#series1:||,}{#series2}``. +There are sometimes cases where you want to display metadata that |app| does not normally display, or to display data in a way different from how |app| normally does. For example, you might want to display the ISBN, a field that |app| does not display. You can use custom columns for this by creating a column with the type 'column built from other columns' (hereafter called composite columns), and entering a template. Result: |app| will display a column showing the result of evaluating that template. To display the ISBN, create the column and enter ``{identifiers:select(isbn)}`` into the template box. To display a column containing the values of two series custom columns separated by a comma, use ``{#series1:||,}{#series2}``. Composite columns can use any template option, including formatting. @@ -112,24 +112,46 @@ Functions are always applied before format specifications. See further down for The syntax for using functions is ``{field:function(arguments)}``, or ``{field:function(arguments)|prefix|suffix}``. Arguments are separated by commas. Commas inside arguments must be preceeded by a backslash ( '\\' ). The last (or only) argument cannot contain a closing parenthesis ( ')' ). Functions return the value of the field used in the template, suitably modified. +If you have programming experience, please note that the syntax in this mode (single function) is not what you might expect. Strings are not quoted. Spaces are significant. All arguments must be constants; there is no sub-evaluation. Use :ref:`template program mode ` and :ref:`general program mode ` to avoid these differences. + +Many functions use regular expressions. In all cases, regular expression matching is case-insensitive. + The functions available are: * ``lowercase()`` -- return value of the field in lower case. * ``uppercase()`` -- return the value of the field in upper case. * ``titlecase()`` -- return the value of the field in title case. * ``capitalize()`` -- return the value with the first letter upper case and the rest lower case. - * ``contains(pattern, text if match, text if not match`` -- checks if field contains matches for the regular expression `pattern`. Returns `text if match` if matches are found, otherwise it returns `text if no match`. + * ``contains(pattern, text if match, text if not match)`` -- checks if field contains matches for the regular expression `pattern`. Returns `text if match` if matches are found, otherwise it returns `text if no match`. * ``count(separator)`` -- interprets the value as a list of items separated by `separator`, returning the number of items in the list. Most lists use a comma as the separator, but authors uses an ampersand. Examples: `{tags:count(,)}`, `{authors:count(&)}` * ``ifempty(text)`` -- if the field is not empty, return the value of the field. Otherwise return `text`. - * ``list_item(index, separator)`` -- interpret the value as a list of items separated by `separator`, returning the `index`th item. The first item is number zero. The last item can be returned using `list_item(-1,separator)`. If the item is not in the list, then the empty value is returned. The separator has the same meaning as in the `count` function. - * ``lookup(pattern, field, pattern, field, ..., else_field)`` -- like switch, except the arguments are field (metadata) names, not text. The value of the appropriate field will be fetched and used. Note that because composite columns are fields, you can use this function in one composite field to use the value of some other composite field. This is extremely useful when constructing variable save paths (more later). + * ``in_list(separator, pattern, found_val, not_found_val)`` -- interpret the field as a list of items separated by `separator`, comparing the `pattern` against each value in the list. If the pattern matches a value, return `found_val`, otherwise return `not_found_val`. + * ``list_item(index, separator)`` -- interpret the field as a list of items separated by `separator`, returning the `index`th item. The first item is number zero. The last item can be returned using `list_item(-1,separator)`. If the item is not in the list, then the empty value is returned. The separator has the same meaning as in the `count` function. * ``re(pattern, replacement)`` -- return the field after applying the regular expression. All instances of `pattern` are replaced with `replacement`. As in all of |app|, these are python-compatible regular expressions. * ``shorten(left chars, middle text, right chars)`` -- Return a shortened version of the field, consisting of `left chars` characters from the beginning of the field, followed by `middle text`, followed by `right chars` characters from the end of the string. `Left chars` and `right chars` must be integers. For example, assume the title of the book is `Ancient English Laws in the Times of Ivanhoe`, and you want it to fit in a space of at most 15 characters. If you use ``{title:shorten(9,-,5)}``, the result will be `Ancient E-nhoe`. If the field's length is less than ``left chars`` + ``right chars`` + the length of ``middle text``, then the field will be used intact. For example, the title `The Dome` would not be changed. * ``switch(pattern, value, pattern, value, ..., else_value)`` -- for each ``pattern, value`` pair, checks if the field matches the regular expression ``pattern`` and if so, returns that ``value``. If no ``pattern`` matches, then ``else_value`` is returned. You can have as many ``pattern, value`` pairs as you want. + * ``lookup(pattern, field, pattern, field, ..., else_field)`` -- like switch, except the arguments are field (metadata) names, not text. The value of the appropriate field will be fetched and used. Note that because composite columns are fields, you can use this function in one composite field to use the value of some other composite field. This is extremely useful when constructing variable save paths (more later). + * ``select(key)`` -- interpret the field as a comma-separated list of items, with the items being of the form "id:value". Find the pair with the id equal to key, and return the corresponding value. This function is particularly useful for extracting a value such as an isbn from the set of identifiers for a book. + * ``str_in_list(val, separator, string, found_val, not_found_val)`` -- treat val as a list of items separated by separator, comparing the string against each value in the list. If the string matches a value, return found_val, otherwise return not_found_val. If the string contains separators, then it is also treated as a list and each value is checked. + * ``subitems(val, start_index, end_index)`` -- This function is used to break apart lists of tag-like hierarchical items such as genres. It interprets the value as a comma-separated list of tag-like items, where each item is a period-separated list. Returns a new list made by first finding all the period-separated tag-like items, then for each such item extracting the components from `start_index` to `end_index`, then combining the results back together. The first component in a period-separated list has an index of zero. If an index is negative, then it counts from the end of the list. As a special case, an end_index of zero is assumed to be the length of the list. Examples:: + + Assuming a #genre column containing "A.B.C": + {#genre:subitems(0,1)} returns "A" + {#genre:subitems(0,2)} returns "A.B" + {#genre:subitems(1,0)} returns "B.C" + Assuming a #genre column containing "A.B.C, D.E": + {#genre:subitems(0,1)} returns "A, D" + {#genre:subitems(0,2)} returns "A.B, D.E" + + * ``sublist(val, start_index, end_index, separator)`` -- interpret the value as a list of items separated by `separator`, returning a new list made from the items from `start_index`to `end_index`. The first item is number zero. If an index is negative, then it counts from the end of the list. As a special case, an end_index of zero is assumed to be the length of the list. Examples assuming that the tags column (which is comma-separated) contains "A, B ,C":: + + {tags:sublist(0,1,\,)} returns "A" + {tags:sublist(-1,0,\,)} returns "C" + {tags:sublist(0,-1,\,)} returns "A, B" + * ``test(text if not empty, text if empty)`` -- return `text if not empty` if the field is not empty, otherwise return `text if empty`. - -Now, about using functions and formatting in the same field. Suppose you have an integer custom column called ``#myint`` that you want to see with leading zeros, as in ``003``. To do this, you would use a format of ``0>3s``. However, by default, if a number (integer or float) equals zero then the field produces the empty value, so zero values will produce nothing, not ``000``. If you really want to see ``000`` values, then you use both the format string and the ``ifempty`` function to change the empty value back to a zero. The field reference would be:: +Now, what about using functions and formatting in the same field. Suppose you have an integer custom column called ``#myint`` that you want to see with leading zeros, as in ``003``. To do this, you would use a format of ``0>3s``. However, by default, if a number (integer or float) equals zero then the field produces the empty value, so zero values will produce nothing, not ``000``. If you really want to see ``000`` values, then you use both the format string and the ``ifempty`` function to change the empty value back to a zero. The field reference would be:: {#myint:0>3s:ifempty(0)} @@ -137,11 +159,14 @@ Note that you can use the prefix and suffix as well. If you want the number to a {#myint:0>3s:ifempty(0)|[|]} +.. _template_mode: Using functions in templates - template program mode ---------------------------------------------------- -The template language program mode differs from single-function mode in that it permits you to write template expressions that refer to other metadata fields, modify values, and do arithmetic. It is a reasonably complete programming language. +The template language program mode differs from single-function mode in that it permits you to write template expressions that refer to other metadata fields, modify values, and do arithmetic. It is a reasonably complete programming language. + +You can use the functions documented above in template program mode. See below for details. Beginning with an example, assume that you want your template to show the series for a book if it has one, otherwise show the value of a custom field #genre. You cannot do this in the basic language because you cannot make reference to another metadata field within a template expression. In program mode, you can. The following expression works:: @@ -203,23 +228,56 @@ For various values of series_index, the program returns: * series_index == 2, result = ``prefix 2->eq suffix`` * series_index == 3, result = ``prefix 3->gt suffix`` -All the functions listed under single-function mode can be used in program mode, noting that unlike the functions described below you must supply a first parameter providing the value the function is to act upon. +**All the functions listed under single-function mode can be used in program mode**. To do so, you must supply the value that the function is to act upon as the first parameter, in addition to the parameters documented above. For example, in program mode the parameters of the `test` function are ``test(x, text_if_not_empty, text_if_empty)``. The `x` parameter, which is the value to be tested, will almost always be a variable or a function call, often `field()`. The following functions are available in addition to those described in single-function mode. Remember from the example above that the single-function mode functions require an additional first parameter specifying the field to operate on. With the exception of the ``id`` parameter of assign, all parameters can be statements (sequences of expressions): + * ``and(value, value, ...)`` -- returns the string "1" if all values are not empty, otherwise returns the empty string. This function works well with test or first_non_empty. You can have as many values as you want. * ``add(x, y)`` -- returns x + y. Throws an exception if either x or y are not numbers. * ``assign(id, val)`` -- assigns val to id, then returns val. id must be an identifier, not an expression + * ``booksize()`` -- returns the value of the |app| 'size' field. Returns '' if there are no formats. * ``cmp(x, y, lt, eq, gt)`` -- compares x and y after converting both to numbers. Returns ``lt`` if x < y. Returns ``eq`` if x == y. Otherwise returns ``gt``. * ``divide(x, y)`` -- returns x / y. Throws an exception if either x or y are not numbers. * ``field(name)`` -- returns the metadata field named by ``name``. + * ``first_non_empty(value, value, ...)`` -- returns the first value that is not empty. If all values are empty, then the empty value is returned. You can have as many values as you want. + * ``format_date(x, date_format)`` -- format_date(val, format_string) -- format the value, which must be a date field, using the format_string, returning a string. The formatting codes are:: + + d : the day as number without a leading zero (1 to 31) + dd : the day as number with a leading zero (01 to 31) + ddd : the abbreviated localized day name (e.g. "Mon" to "Sun"). + dddd : the long localized day name (e.g. "Monday" to "Sunday"). + M : the month as number without a leading zero (1 to 12). + MM : the month as number with a leading zero (01 to 12) + MMM : the abbreviated localized month name (e.g. "Jan" to "Dec"). + MMMM : the long localized month name (e.g. "January" to "December"). + yy : the year as two digit number (00 to 99). + yyyy : the year as four digit number. + iso : the date with time and timezone. Must be the only format present. + * ``eval(string)`` -- evaluates the string as a program, passing the local variables (those ``assign`` ed to). This permits using the template processor to construct complex results from local variables. + * ``not(value)`` -- returns the string "1" if the value is empty, otherwise returns the empty string. This function works well with test or first_non_empty. You can have as many values as you want. + * ``merge_lists(list1, list2, separator)`` -- return a list made by merging the items in list1 and list2, removing duplicate items using a case-insensitive compare. If items differ in case, the one in list1 is used. The items in list1 and list2 are separated by separator, as are the items in the returned list. * ``multiply(x, y)`` -- returns x * y. Throws an exception if either x or y are not numbers. + * ``ondevice()`` -- return the string "Yes" if ondevice is set, otherwise return the empty string + * ``or(value, value, ...)`` -- returns the string "1" if any value is not empty, otherwise returns the empty string. This function works well with test or first_non_empty. You can have as many values as you want. * ``print(a, b, ...)`` -- prints the arguments to standard output. Unless you start calibre from the command line (``calibre-debug -g``), the output will go to a black hole. + * ``raw_field(name)`` -- returns the metadata field named by name without applying any formatting. * ``strcat(a, b, ...)`` -- can take any number of arguments. Returns a string formed by concatenating all the arguments. * ``strcmp(x, y, lt, eq, gt)`` -- does a case-insensitive comparison x and y as strings. Returns ``lt`` if x < y. Returns ``eq`` if x == y. Otherwise returns ``gt``. * ``substr(str, start, end)`` -- returns the ``start``'th through the ``end``'th characters of ``str``. The first character in ``str`` is the zero'th character. If end is negative, then it indicates that many characters counting from the right. If end is zero, then it indicates the last character. For example, ``substr('12345', 1, 0)`` returns ``'2345'``, and ``substr('12345', 1, -1)`` returns ``'234'``. * ``subtract(x, y)`` -- returns x - y. Throws an exception if either x or y are not numbers. * ``template(x)`` -- evaluates x as a template. The evaluation is done in its own context, meaning that variables are not shared between the caller and the template evaluation. Because the `{` and `}` characters are special, you must use `[[` for the `{` character and `]]` for the '}' character; they are converted automatically. For example, ``template('[[title_sort]]') will evaluate the template ``{title_sort}`` and return its value. + +Function classification +--------------------------- + +.. toctree:: + :maxdepth: 3 + + template_ref + + +.. _general_mode: Using general program mode ----------------------------------- @@ -335,13 +393,18 @@ To accomplish this, we: Templates and Plugboards ------------------------ -Plugboards are used for changing the metadata written into books during send-to-device and save-to-disk operations. A plugboard permits you to specify a template to provide the data to write into the book's metadata. You can use plugboards to modify the following fields: authors, author_sort, language, publisher, tags, title, title_sort. This feature should help those of you who want to use different metadata in your books on devices to solve sorting or display issues. +Plugboards are used for changing the metadata written into books during send-to-device and save-to-disk operations. A plugboard permits you to specify a template to provide the data to write into the book's metadata. You can use plugboards to modify the following fields: authors, author_sort, language, publisher, tags, title, title_sort. This feature helps people who want to use different metadata in books on devices to solve sorting or display issues. -When you create a plugboard, you specify the format and device for which the plugboard is to be used. A special device is provided, save_to_disk, that is used when saving formats (as opposed to sending them to a device). Once you have chosen the format and device, you choose the metadata fields to change, providing templates to supply the new values. These templates are `connected` to their destination fields, hence the name `plugboards`. You can, of course, use composite columns in these templates. +When you create a plugboard, you specify the format and device for which the plugboard is to be used. A special device is provided, save_to_disk, that is used when saving formats (as opposed to sending them to a device). Once you have chosen the format and device, you choose the metadata fields to change, providing templates to supply the new values. These templates are `connected` to their destination fields, hence the name `plugboards`. You can, of course, use composite columns in these templates. -The tags and authors fields have special treatment, because both of these fields can hold more than one item. After all, book can have many tags and many authors. When you specify that one of these two fields is to be changed, the result of evaluating the template is examined to see if more than one item is there. +When a plugboard might apply (content server, save to disk, or send to device), |app| searches the defined plugboards to choose the correct one for the given format and device. For example, to find the appropriate plugboard for an EPUB book being sent to an ANDROID device, |app| searches the plugboards using the following search order: -For tags, the result cut apart whereever |app| finds a comma. For example, if the template produces the value ``Thriller, Horror``, then the result will be two tags, ``Thriller`` and ``Horror``. There is no way to put a comma in the middle of a tag. + * a plugboard with an exact match on format and device, e.g., ``EPUB`` and ``ANDROID`` + * a plugboard with an exact match on format and the special ``any device`` choice, e.g., ``EPUB`` and ``any device`` + * a plugboard with the special ``any format`` choice and an exact match on device, e.g., ``any format`` and ``ANDROID`` + * a plugboard with ``any format`` and ``any device`` + +The tags and authors fields have special treatment, because both of these fields can hold more than one item. A book can have many tags and many authors. When you specify that one of these two fields is to be changed, the template's result is examined to see if more than one item is there. For tags, the result is cut apart wherever |app| finds a comma. For example, if the template produces the value ``Thriller, Horror``, then the result will be two tags, ``Thriller`` and ``Horror``. There is no way to put a comma in the middle of a tag. The same thing happens for authors, but using a different character for the cut, a `&` (ampersand) instead of a comma. For example, if the template produces the value ``Blogs, Joe&Posts, Susan``, then the book will end up with two authors, ``Blogs, Joe`` and ``Posts, Susan``. If the template produces the value ``Blogs, Joe;Posts, Susan``, then the book will have one author with a rather strange name. @@ -356,4 +419,9 @@ You might find the following tips useful. * Templates can use other templates by referencing a composite custom column. * In a plugboard, you can set a field to empty (or whatever is equivalent to empty) by using the special template ``{null}``. This template will always evaluate to an empty string. * The technique described above to show numbers even if they have a zero value works with the standard field series_index. - + +.. toctree:: + :hidden: + + template_ref + diff --git a/src/calibre/manual/template_ref.rst b/src/calibre/manual/template_ref.rst new file mode 100644 index 0000000000..670a7ba791 --- /dev/null +++ b/src/calibre/manual/template_ref.rst @@ -0,0 +1,266 @@ +.. include:: global.rst + +.. _templaterefcalibre: + +Reference for all builtin template language functions +======================================================== + +Here, we document all the builtin functions available in the |app| template language. Every function is implemented as a class in python and you can click the source links to see the source code, in case the documentation is insufficient. The functions are arranged in logical groups by type. + +.. contents:: + :depth: 2 + :local: + +.. module:: calibre.utils.formatter_functions + +Get values from metadata +-------------------------- + +field(name) +^^^^^^^^^^^^^^ + +.. autoclass:: BuiltinField + +raw_field(name) +^^^^^^^^^^^^^^^^ + +.. autoclass:: BuiltinRaw_field + +booksize() +^^^^^^^^^^^^ + +.. autoclass:: BuiltinBooksize + +format_date(val, format_string) +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +.. autoclass:: BuiltinFormat_date + +ondevice() +^^^^^^^^^^^ + +.. autoclass:: BuiltinOndevice + +Arithmetic +------------- + +add(x, y) +^^^^^^^^^^^^^ +.. autoclass:: BuiltinAdd + +subtract(x, y) +^^^^^^^^^^^^^^^^ + +.. autoclass:: BuiltinSubtract + +multiply(x, y) +^^^^^^^^^^^^^^^^ + +.. autoclass:: BuiltinMultiply + +divide(x, y) +^^^^^^^^^^^^^^^ + +.. autoclass:: BuiltinDivide + +Boolean +------------ + +and(value1, value2, ...) +^^^^^^^^^^^^^^^^^^^^^^^^^^ + +.. autoclass:: BuiltinAnd + +or(value1, value2, ...) +^^^^^^^^^^^^^^^^^^^^^^^^^ + +.. autoclass:: BuiltinOr + +not(value) +^^^^^^^^^^^^^ + +.. autoclass:: BuiltinNot + +If-then-else +----------------- + +contains(val, pattern, text if match, text if not match) +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +.. autoclass:: BuiltinContains + +test(val, text if not empty, text if empty) +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +.. autoclass:: BuiltinTest + +ifempty(val, text if empty) +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +.. autoclass:: BuiltinIfempty + +Iterating over values +------------------------ + +first_non_empty(value, value, ...) +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +.. autoclass:: BuiltinFirstNonEmpty + +lookup(val, pattern, field, pattern, field, ..., else_field) +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +.. autoclass:: BuiltinLookup + +switch(val, pattern, value, pattern, value, ..., else_value) +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +.. autoclass:: BuiltinSwitch + +List Lookup +--------------- + +in_list(val, separator, pattern, found_val, not_found_val) +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +.. autoclass:: BuiltinInList + +str_in_list(val, separator, string, found_val, not_found_val) +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +.. autoclass:: BuiltinStrInList + +list_item(val, index, separator) +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +.. autoclass:: BuiltinListitem + +select(val, key) +^^^^^^^^^^^^^^^^^ + +.. autoclass:: BuiltinSelect + + +List Manipulation +------------------- + +count(val, separator) +^^^^^^^^^^^^^^^^^^^^^^^^ + +.. autoclass:: BuiltinCount + +merge_lists(list1, list2, separator) +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +.. autoclass:: BuiltinMergeLists + +sublist(val, start_index, end_index, separator) +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +.. autoclass:: BuiltinSublist + +subitems(val, start_index, end_index) +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +.. autoclass:: BuiltinSubitems + +Recursion +------------- + +eval(template) +^^^^^^^^^^^^^^^^ + +.. autoclass:: BuiltinEval + +template(x) +^^^^^^^^^^^^ + +.. autoclass:: BuiltinTemplate + +Relational +----------- + +cmp(x, y, lt, eq, gt) +^^^^^^^^^^^^^^^^^^^^^^^ + +.. autoclass:: BuiltinCmp + +strcmp(x, y, lt, eq, gt) +^^^^^^^^^^^^^^^^^^^^^^^^^ + +.. autoclass:: BuiltinStrcmp + +String case changes +--------------------- + +lowercase(val) +^^^^^^^^^^^^^^^^ + +.. autoclass:: BuiltinLowercase + +uppercase(val) +^^^^^^^^^^^^^^^ + +.. autoclass:: BuiltinUppercase + +titlecase(val) +^^^^^^^^^^^^^^^ + +.. autoclass:: BuiltinTitlecase + +capitalize(val) +^^^^^^^^^^^^^^^^ + +.. autoclass:: BuiltinCapitalize + +String Manipulation +--------------------- + +re(val, pattern, replacement) +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +.. autoclass:: BuiltinRe + +shorten(val, left chars, middle text, right chars) +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +.. autoclass:: BuiltinShorten + +substr(str, start, end) +^^^^^^^^^^^^^^^^^^^^^^^^ + +.. autoclass:: BuiltinSubstr + + +Other +-------- + +assign(id, val) +^^^^^^^^^^^^^^^^^ + +.. autoclass:: BuiltinAssign + +print(a, b, ...) +^^^^^^^^^^^^^^^^^ + +.. autoclass:: BuiltinPrint + + +API of the Metadata objects +---------------------------- + +The python implementation of the template functions is passed in a Metadata object. Knowing it's API is useful if you want to define your own template functions. + +.. module:: calibre.ebooks.metadata.book.base + +.. autoclass:: Metadata + :members: + :member-order: bysource + +.. data:: STANDARD_METADATA_FIELDS + + The set of standard metadata fields. + +.. literalinclude:: ../ebooks/metadata/book/__init__.py + :lines: 7- + diff --git a/src/calibre/manual/templates/layout.html b/src/calibre/manual/templates/layout.html index b427482947..8f35a9a6c5 100644 --- a/src/calibre/manual/templates/layout.html +++ b/src/calibre/manual/templates/layout.html @@ -1,23 +1,6 @@ {% extends "!layout.html" %} {% block extrahead %} - {% if not embedded %} - - {% endif %} - \n" +"

    Create a basic news " +"recipe, by adding RSS feeds to it.
    For most feeds, you will have to " +"use the \"Advanced mode\" to further customize the fetch " +"process.

    " +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/user_profiles_ui.py:270 +msgid "Recipe &title:" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/user_profiles_ui.py:271 +msgid "&Oldest article:" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/user_profiles_ui.py:272 +msgid "The oldest article to download" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/user_profiles_ui.py:274 +msgid "&Max. number of articles per feed:" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/user_profiles_ui.py:275 +msgid "Maximum number of articles to download per feed." +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/user_profiles_ui.py:276 +msgid "Feeds in recipe" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/user_profiles_ui.py:278 +msgid "Remove feed from recipe" +msgstr "" + +#: /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 "" + +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/user_profiles_ui.py:282 +msgid "&Feed title:" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/user_profiles_ui.py:283 +msgid "Feed &URL:" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/user_profiles_ui.py:285 +msgid "&Add feed" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/user_profiles_ui.py:286 +msgid "" +"For help with writing advanced news recipes, please visit User Recipes" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/user_profiles_ui.py:287 +msgid "Recipe source code (python)" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/dnd.py:51 +msgid "Download %s" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/dnd.py:54 +msgid "Downloading %s from %s" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/dnd.py:85 +msgid "Failed to download from %r with error: %s" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/ebook_download.py:41 +msgid "No file specified to download." +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/ebook_download.py:66 +msgid "Not a support ebook format." +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/ebook_download.py:87 +msgid "Downloading %s" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/ebook_download.py:99 +msgid "Downloading" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/ebook_download.py:103 +msgid "Failed to download ebook" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/email.py:91 +msgid "Email %s to %s" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/email.py:110 +msgid "News:" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/email.py:112 +msgid "Attached is the %s periodical downloaded by calibre." +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/email.py:160 +msgid "E-book:" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/email.py:168 +msgid "Attached, you will find the e-book" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/email.py:169 +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/plugins.py:189 +msgid "by" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/email.py:170 +msgid "in the %s format." +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/email.py:184 +msgid "Sending email to" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/email.py:215 +msgid "Auto convert the following books before sending via email?" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/email.py:222 +msgid "" +"Could not email the following books as no suitable formats were found:" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/email.py:228 +msgid "Failed to email book" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/email.py:231 +msgid "sent" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/email.py:254 +msgid "Sent news to" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/filename_pattern_ui.py:129 +msgid "" +"
    \n" +"

    Set a regular expression pattern to use when trying to guess ebook " +"metadata from filenames.

    \n" +"

    A tutorial on using regular " +"expressions is available.

    \n" +"

    Use the Test functionality below to test your regular expression " +"on a few sample filenames (remember to include the file extension). The " +"group names for the various metadata entries are documented in " +"tooltips.

    " +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/filename_pattern_ui.py:133 +msgid "Regular &expression" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/filename_pattern_ui.py:135 +msgid "File &name:" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/filename_pattern_ui.py:137 +msgid "Title:" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/filename_pattern_ui.py:138 +msgid "Regular expression (?P<title>)" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/filename_pattern_ui.py:139 +#: /home/kovid/work/calibre/src/calibre/gui2/filename_pattern_ui.py:142 +#: /home/kovid/work/calibre/src/calibre/gui2/filename_pattern_ui.py:145 +#: /home/kovid/work/calibre/src/calibre/gui2/filename_pattern_ui.py:148 +#: /home/kovid/work/calibre/src/calibre/gui2/filename_pattern_ui.py:151 +#: /home/kovid/work/calibre/src/calibre/gui2/filename_pattern_ui.py:154 +#: /home/kovid/work/calibre/src/calibre/gui2/filename_pattern_ui.py:157 +#: /home/kovid/work/calibre/src/calibre/gui2/widgets.py:108 +#: /home/kovid/work/calibre/src/calibre/gui2/widgets.py:112 +#: /home/kovid/work/calibre/src/calibre/gui2/widgets.py:117 +#: /home/kovid/work/calibre/src/calibre/gui2/widgets.py:122 +#: /home/kovid/work/calibre/src/calibre/gui2/widgets.py:130 +msgid "No match" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/filename_pattern_ui.py:140 +msgid "Authors:" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/filename_pattern_ui.py:141 +msgid "Regular expression (?P)" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/filename_pattern_ui.py:143 +msgid "Series:" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/filename_pattern_ui.py:144 +msgid "Regular expression (?P)" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/filename_pattern_ui.py:146 +msgid "Series index:" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/filename_pattern_ui.py:147 +msgid "Regular expression (?P)" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/filename_pattern_ui.py:149 +msgid "ISBN:" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/filename_pattern_ui.py:150 +msgid "Regular expression (?P)" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/filename_pattern_ui.py:152 +msgid "Publisher:" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/filename_pattern_ui.py:153 +msgid "Regular expression (?P)" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/filename_pattern_ui.py:155 +msgid "Published:" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/filename_pattern_ui.py:156 +msgid "Regular expression (?P)" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/init.py:105 +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/look_feel_ui.py:237 +msgid "Cover Browser" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/init.py:110 +msgid "Shift+Alt+B" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/init.py:124 +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/look_feel_ui.py:234 +msgid "Tag Browser" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/init.py:126 +msgid "Shift+Alt+T" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/init.py:157 +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/main.py:29 +msgid "version" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/init.py:158 +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/main.py:30 +msgid "created by Kovid Goyal" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/init.py:175 +msgid "Connected " +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/init.py:188 +msgid "Update found" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/init.py:223 +#: /home/kovid/work/calibre/src/calibre/gui2/init.py:233 +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/look_feel_ui.py:216 +msgid "Book Details" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/init.py:225 +msgid "Alt+D" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/init.py:235 +msgid "Shift+Alt+D" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/jobs.py:62 +msgid "Job" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/jobs.py:63 +msgid "Status" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/jobs.py:64 +msgid "Progress" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/jobs.py:65 +msgid "Running time" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/jobs.py:77 +msgid "There are %d running jobs:" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/jobs.py:81 +#: /home/kovid/work/calibre/src/calibre/gui2/jobs.py:88 +#: /home/kovid/work/calibre/src/calibre/gui2/jobs.py:103 +msgid "Unknown job" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/jobs.py:84 +msgid "There are %d waiting jobs:" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/jobs.py:240 +#: /home/kovid/work/calibre/src/calibre/gui2/jobs.py:243 +#: /home/kovid/work/calibre/src/calibre/gui2/jobs.py:246 +msgid "Cannot kill job" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/jobs.py:241 +msgid "Cannot kill jobs that communicate with the device" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/jobs.py:244 +msgid "Job has already run" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/jobs.py:247 +msgid "This job cannot be stopped" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/jobs.py:283 +msgid "Unavailable" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/jobs.py:327 +msgid "Jobs:" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/jobs.py:329 +msgid "Shift+Alt+J" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/jobs.py:346 +msgid "Click to see list of jobs" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/jobs.py:415 +msgid " - Jobs" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/jobs.py:457 +msgid "Do you really want to stop the selected job?" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/jobs.py:463 +msgid "Do you really want to stop all non-device jobs?" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/layout.py:57 +msgid "Eject this device" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/layout.py:69 +msgid "Show books in calibre library" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/layout.py:71 +msgid "Show books in the main memory of the device" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/layout.py:72 +#: /home/kovid/work/calibre/src/calibre/library/database2.py:1019 +msgid "Card A" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/layout.py:73 +msgid "Show books in storage card A" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/layout.py:74 +#: /home/kovid/work/calibre/src/calibre/library/database2.py:1021 +msgid "Card B" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/layout.py:75 +msgid "Show books in storage card B" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/layout.py:140 +msgid "available" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/layout.py:181 +msgid "Shift+Ctrl+F" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/layout.py:184 +msgid "Advanced search" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/layout.py:189 +msgid "" +"

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

    Words separated by spaces are ANDed" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/layout.py:195 +msgid "&Go!" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/layout.py:201 +msgid "Do Quick Search (you can also press the Enter key)" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/layout.py:207 +msgid "Reset Quick Search" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/layout.py:223 +msgid "Copy current search text (instead of search name)" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/library/delegates.py:361 +msgid "Y" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/library/delegates.py:396 +msgid "Edit template" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/library/models.py:64 +#: /home/kovid/work/calibre/src/calibre/library/field_metadata.py:251 +msgid "On Device" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/library/models.py:66 +msgid "Size (MB)" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/library/models.py:73 +#: /home/kovid/work/calibre/src/calibre/library/field_metadata.py:241 +msgid "Modified" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/library/models.py:720 +#: /home/kovid/work/calibre/src/calibre/gui2/library/models.py:1277 +#: /home/kovid/work/calibre/src/calibre/gui2/tag_view.py:797 +msgid "The lookup/search name is \"{0}\"" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/library/models.py:726 +#: /home/kovid/work/calibre/src/calibre/gui2/library/models.py:1279 +msgid "This book's UUID is \"{0}\"" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/library/models.py:976 +msgid "In Library" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/library/models.py:980 +#: /home/kovid/work/calibre/src/calibre/library/field_metadata.py:311 +msgid "Size" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/library/models.py:1257 +msgid "Marked for deletion" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/library/models.py:1260 +msgid "Double click to edit me

    " +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/library/views.py:158 +msgid "Hide column %s" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/library/views.py:163 +msgid "Sort on %s" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/library/views.py:164 +msgid "Ascending" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/library/views.py:167 +msgid "Descending" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/library/views.py:179 +msgid "Change text alignment for %s" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/library/views.py:181 +msgid "Left" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/library/views.py:181 +msgid "Right" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/library/views.py:182 +msgid "Center" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/library/views.py:201 +msgid "Show column" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/library/views.py:213 +msgid "Restore default layout" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/library/views.py:855 +msgid "" +"Dropping onto a device is not supported. First add the book to the calibre " +"library." +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/lrf_renderer/config_ui.py:52 +msgid "Configure Viewer" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/lrf_renderer/config_ui.py:53 +msgid "Use white background" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/lrf_renderer/config_ui.py:54 +msgid "Hyphenate" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/lrf_renderer/config_ui.py:55 +msgid "Changes will only take effect after a restart." +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/lrf_renderer/main.py:70 +msgid " - LRF Viewer" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/lrf_renderer/main.py:160 +msgid "No matches for the search phrase %s were found." +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/lrf_renderer/main_ui.py:128 +msgid "LRF Viewer" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/lrf_renderer/main_ui.py:129 +msgid "Parsing LRF file" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/lrf_renderer/main_ui.py:130 +msgid "LRF Viewer toolbar" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/lrf_renderer/main_ui.py:131 +#: /home/kovid/work/calibre/src/calibre/gui2/viewer/documentview.py:559 +msgid "Next Page" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/lrf_renderer/main_ui.py:132 +#: /home/kovid/work/calibre/src/calibre/gui2/viewer/documentview.py:560 +msgid "Previous Page" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/lrf_renderer/main_ui.py:133 +#: /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:193 +msgid "Back" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/lrf_renderer/main_ui.py:134 +#: /home/kovid/work/calibre/src/calibre/gui2/viewer/main_ui.py:194 +msgid "Forward" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/lrf_renderer/main_ui.py:135 +msgid "Next match" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/lrf_renderer/main_ui.py:136 +#: /home/kovid/work/calibre/src/calibre/gui2/viewer/main_ui.py:201 +msgid "Open ebook" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/lrf_renderer/main_ui.py:137 +msgid "Configure" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/main.py:33 +msgid "Use the library located at the specified path." +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/main.py:35 +msgid "Start minimized to system tray." +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/main.py:37 +msgid "Log debugging information to console" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/main.py:39 +msgid "Do not check for updates" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/main.py:41 +msgid "" +"Ignore custom plugins, useful if you installed a plugin that is preventing " +"calibre from starting" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/main.py:45 +msgid "" +"Cause a running calibre instance, if any, to be shutdown. Note that if there " +"are running jobs, they will be silently aborted, so use with care." +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/main.py:69 +#: /home/kovid/work/calibre/src/calibre/gui2/wizard/__init__.py:678 +msgid "Calibre Library" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/main.py:96 +msgid "Choose a location for your calibre e-book library" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/main.py:105 +msgid "Failed to create library" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/main.py:106 +msgid "Failed to create calibre library at: %r." +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/main.py:109 +#: /home/kovid/work/calibre/src/calibre/gui2/main.py:195 +msgid "Choose a location for your new calibre e-book library" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/main.py:164 +msgid "Initializing user interface..." +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/main.py:189 +msgid "Repairing failed" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/main.py:190 +msgid "The database repair failed. Starting with a new empty library." +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/main.py:204 +#: /home/kovid/work/calibre/src/calibre/gui2/main.py:236 +msgid "Bad database location" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/main.py:205 +msgid "Bad database location %r. calibre will now quit." +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/main.py:217 +msgid "Corrupted database" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/main.py:218 +msgid "" +"Your calibre database appears to be corrupted. Do you want calibre to try " +"and repair it automatically? If you say No, a new empty calibre library will " +"be created." +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/main.py:224 +msgid "" +"Repairing database. This can take a very long time for a large collection" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/main.py:237 +msgid "" +"Bad database location %r. Will start with a new, empty calibre library" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/main.py:247 +msgid "Starting %s: Loading books..." +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/main.py:327 +msgid "If you are sure it is not running" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/main.py:330 +msgid "may be running in the system tray, in the" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/main.py:332 +msgid "upper right region of the screen." +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/main.py:334 +msgid "lower right region of the screen." +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/main.py:337 +msgid "try rebooting your computer." +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/main.py:339 +#: /home/kovid/work/calibre/src/calibre/gui2/main.py:353 +msgid "try deleting the file" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/main.py:342 +msgid "Cannot Start " +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/main.py:343 +msgid "%s is already running." +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/main_window.py:25 +msgid "" +"Redirect console output to a dialog window (both stdout and stderr). Useful " +"on windows where GUI apps do not have a output streams." +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/main_window.py:113 +msgid "&Preferences" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/main_window.py:114 +msgid "&Quit" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/main_window.py:138 +msgid "Unhandled exception" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/metadata/basic_widgets.py:121 +msgid "" +"Specify how this book should be sorted when by title. For example, The " +"Exorcist might be sorted as Exorcist, The." +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/metadata/basic_widgets.py:123 +msgid "Title &sort:" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/metadata/basic_widgets.py:131 +msgid "" +" The green color indicates that the current title sort matches the current " +"title" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/metadata/basic_widgets.py:134 +msgid "" +" The red color warns that the current title sort does not match the current " +"title. No action is required if this is what you want." +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/metadata/basic_widgets.py:181 +msgid "Authors changed" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/metadata/basic_widgets.py:182 +msgid "" +"You have changed the authors for this book. You must save these changes " +"before you can use Manage authors. Do you want to save these changes?" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/metadata/basic_widgets.py:252 +msgid "" +"Specify how the author(s) of this book should be sorted. For example Charles " +"Dickens should be sorted as Dickens, Charles.\n" +"If the box is colored green, then text matches the individual author's sort " +"strings. If it is colored red, then the authors and this text do not match." +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/metadata/basic_widgets.py:257 +msgid "Author s&ort:" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/metadata/basic_widgets.py:267 +msgid "" +" The green color indicates that the current author sort matches the current " +"author" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/metadata/basic_widgets.py:270 +msgid "" +" The red color indicates that the current author sort does not match the " +"current author. No action is required if this is what you want." +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/metadata/basic_widgets.py:411 +msgid "&Number:" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/metadata/basic_widgets.py:492 +msgid "" +"Last modified: %s\n" +"\n" +"Double click to view" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/metadata/basic_widgets.py:509 +msgid "Set the cover for the book from the selected format" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/metadata/basic_widgets.py:517 +msgid "Set metadata for the book from the selected format" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/metadata/basic_widgets.py:524 +msgid "Add a format to this book" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/metadata/basic_widgets.py:531 +msgid "Remove the selected format from this book" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/metadata/basic_widgets.py:597 +msgid "Choose formats for " +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/metadata/basic_widgets.py:629 +msgid "No permission" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/metadata/basic_widgets.py:630 +msgid "You do not have permission to read the following files:" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/metadata/basic_widgets.py:660 +#: /home/kovid/work/calibre/src/calibre/gui2/metadata/basic_widgets.py:661 +msgid "No format selected" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/metadata/basic_widgets.py:672 +msgid "Could not read metadata" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/metadata/basic_widgets.py:673 +msgid "Could not read metadata from %s format" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/metadata/basic_widgets.py:692 +msgid "&Browse" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/metadata/basic_widgets.py:694 +msgid "T&rim" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/metadata/basic_widgets.py:696 +msgid "&Remove" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/metadata/basic_widgets.py:702 +msgid "Download co&ver" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/metadata/basic_widgets.py:703 +msgid "&Generate cover" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/metadata/basic_widgets.py:753 +msgid "Not a valid picture" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/metadata/basic_widgets.py:777 +msgid "Specify title and author" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/metadata/basic_widgets.py:778 +msgid "You must specify a title and author before generating a cover" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/metadata/basic_widgets.py:796 +msgid "Invalid cover" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/metadata/basic_widgets.py:797 +msgid "Could not change cover as the image is invalid." +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/metadata/basic_widgets.py:824 +msgid "This book has no cover" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/metadata/basic_widgets.py:826 +msgid "Cover size: %dx%d pixels" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/metadata/basic_widgets.py:875 +msgid "stars" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/metadata/basic_widgets.py:944 +msgid "Tags changed" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/metadata/basic_widgets.py:945 +msgid "" +"You have changed the tags. In order to use the tags editor, you must either " +"discard or apply these changes. Apply changes?" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/metadata/basic_widgets.py:971 +msgid "I&ds:" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/metadata/basic_widgets.py:972 +msgid "" +"Edit the identifiers for this book. For example: \n" +"\n" +"%s" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/metadata/basic_widgets.py:1033 +msgid "This ISBN number is valid" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/metadata/basic_widgets.py:1036 +msgid "This ISBN number is invalid" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/metadata/basic_widgets.py:1050 +msgid "&Publisher:" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/metadata/basic_widgets.py:1120 +msgid "Clear date" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/metadata/basic_widgets.py:1152 +msgid "Publishe&d:" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/metadata/bulk_download.py:34 +msgid "Schedule download?" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/metadata/bulk_download.py:45 +msgid "" +"The download of metadata for the %d selected book(s) will run in the " +"background. Proceed?" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/metadata/bulk_download.py:47 +msgid "" +"You can monitor the progress of the download by clicking the rotating " +"spinner in the bottom right corner." +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/metadata/bulk_download.py:50 +msgid "" +"When the download completes you will be asked for confirmation before " +"calibre applies the downloaded metadata." +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/metadata/bulk_download.py:61 +msgid "Download only &metadata" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/metadata/bulk_download.py:65 +msgid "Download only &covers" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/metadata/bulk_download.py:69 +msgid "&Configure download" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/metadata/bulk_download.py:73 +msgid "Download &both" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/metadata/bulk_download.py:99 +msgid "Download metadata for %d books" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/metadata/bulk_download.py:102 +msgid "Metadata download started" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/metadata/bulk_download.py:111 +msgid "(Failed metadata)" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/metadata/bulk_download.py:113 +msgid "(Failed cover)" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/metadata/bulk_download.py:190 +msgid "Downloaded %d of %d" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/metadata/config.py:61 +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/metadata_sources_ui.py:115 +msgid "Downloaded metadata fields" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/metadata/single.py:51 +#: /home/kovid/work/calibre/src/calibre/gui2/metadata/single_download.py:824 +#: /home/kovid/work/calibre/src/calibre/library/server/browse.py:107 +#: /home/kovid/work/calibre/src/calibre/web/feeds/templates.py:211 +#: /home/kovid/work/calibre/src/calibre/web/feeds/templates.py:401 +msgid "Next" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/metadata/single.py:55 +#: /home/kovid/work/calibre/src/calibre/library/server/browse.py:106 +#: /home/kovid/work/calibre/src/calibre/web/feeds/templates.py:221 +#: /home/kovid/work/calibre/src/calibre/web/feeds/templates.py:384 +msgid "Previous" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/metadata/single.py:80 +#: /home/kovid/work/calibre/src/calibre/gui2/metadata/single.py:265 +msgid "Edit Metadata" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/metadata/single.py:101 +msgid "" +"Automatically create the title sort entry based on the current title entry.\n" +"Using this button to create title sort will change title sort from red to " +"green." +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/metadata/single.py:112 +msgid "" +"Automatically create the author sort entry based on the current author " +"entry. Using this button to create author sort will change author sort from " +"red to green. There is a menu of functions available under this button. " +"Click and hold on the button to see it." +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/metadata/single.py:118 +msgid "Set author sort from author" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/metadata/single.py:119 +msgid "Set author from author sort" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/metadata/single.py:129 +msgid "Swap the author and title" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/metadata/single.py:135 +msgid "" +"Manage authors. Use to rename authors and correct individual author's sort " +"values" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/metadata/single.py:143 +msgid "Remove unused series (Series that have no books)" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/metadata/single.py:178 +msgid "" +"Paste the contents of the clipboard into the identifiers box prefixed with " +"isbn:" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/metadata/single.py:191 +msgid "&Download metadata" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/metadata/single.py:202 +msgid "Configure download metadata" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/metadata/single.py:206 +msgid "Change how calibre downloads metadata" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/metadata/single.py:306 +#: /home/kovid/work/calibre/src/calibre/gui2/metadata/single.py:313 +msgid "Could not read cover" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/metadata/single.py:307 +msgid "Could not read cover from %s format" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/metadata/single.py:314 +msgid "The cover in the %s format is invalid" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/metadata/single.py:397 +msgid "Permission denied" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/metadata/single.py:398 +msgid "Could not open %s. Is it being used by another program?" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/metadata/single.py:450 +#: /home/kovid/work/calibre/src/calibre/gui2/metadata/single.py:455 +msgid "Save changes and edit the metadata of %s" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/metadata/single.py:545 +#: /home/kovid/work/calibre/src/calibre/gui2/metadata/single.py:747 +msgid "Change cover" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/metadata/single.py:602 +msgid "Co&mments" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/metadata/single.py:642 +#: /home/kovid/work/calibre/src/calibre/gui2/metadata/single.py:788 +msgid "&Metadata" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/metadata/single.py:647 +msgid "&Cover and formats" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/metadata/single.py:716 +msgid "C&ustom metadata" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/metadata/single.py:728 +msgid "&Comments" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/metadata/single.py:794 +msgid "Basic metadata" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/metadata/single_download.py:132 +msgid "Has cover" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/metadata/single_download.py:132 +msgid "Has summary" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/metadata/single_download.py:189 +msgid "" +"The has cover indication is not fully\n" +"reliable. Sometimes results marked as not\n" +"having a cover will find a cover in the download\n" +"cover stage, and vice versa." +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/metadata/single_download.py:258 +msgid "See at" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/metadata/single_download.py:393 +msgid "calibre is downloading metadata from: " +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/metadata/single_download.py:415 +msgid "Please wait" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/metadata/single_download.py:444 +msgid "Query: " +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/metadata/single_download.py:463 +msgid "Failed to download metadata. Click Show Details to see details" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/metadata/single_download.py:472 +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 " +"single distinctive word from the title.

    To see the full log, click Show " +"Details." +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/metadata/single_download.py:538 +msgid "Current cover" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/metadata/single_download.py:541 +msgid "Searching..." +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/metadata/single_download.py:687 +msgid "Downloading covers for %s, please wait..." +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/metadata/single_download.py:717 +msgid "Failed to download any covers, click \"Show details\" for details." +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/metadata/single_download.py:723 +msgid "Could not find any covers for %s" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/metadata/single_download.py:725 +msgid "Found %d covers of %s. Pick the one you like best." +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/metadata/single_download.py:813 +msgid "Downloading metadata..." +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/metadata/single_download.py:906 +msgid "Downloading cover..." +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/__init__.py:37 +msgid "" +"Restore settings to default values. You have to click Apply to actually save " +"the default settings." +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/__init__.py:328 +msgid "Configure " +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/adding.py:28 +msgid "Ignore duplicate incoming formats" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/adding.py:29 +msgid "Overwrite existing duplicate formats" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/adding.py:30 +msgid "Create new record for each duplicate format" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/adding_ui.py:65 +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 " +"from the filename." +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/adding_ui.py:66 +msgid "Read &metadata from &file contents rather than file name" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/adding_ui.py:67 +msgid "" +"Swap the firstname and lastname of the author. This affects only metadata " +"read from file names." +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/adding_ui.py:68 +msgid "&Swap author firstname and lastname" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/adding_ui.py:69 +msgid "" +"Automerge: If books with similar titles and authors found, merge the " +"incoming formats automatically into\n" +"existing book records. The box to the right controls what happens when an " +"existing record already has\n" +"the incoming format. Note that this option also affects the Copy to library " +"action.\n" +"\n" +"Title match ignores leading indefinite articles (\"the\", \"a\", \"an\"), " +"punctuation, case, etc. Author match is exact." +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/adding_ui.py:74 +msgid "&Automerge added books if they already exist in the calibre library:" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/adding_ui.py:75 +msgid "" +"Automerge: If books with similar titles and authors found, merge the " +"incoming formats automatically into\n" +"existing book records. This box controls what happens when an existing " +"record already has\n" +"the incoming format: \n" +"\n" +"Ignore duplicate incoming files - means that existing files in your calibre " +"library will not be replaced\n" +"Overwrite existing duplicate files - means that existing files in your " +"calibre library will be replaced\n" +"Create new record for each duplicate file - means that a new book entry will " +"be created for each duplicate file\n" +"\n" +"Title matching ignores leading indefinite articles (\"the\", \"a\", \"an\"), " +"punctuation, case, etc.\n" +"Author matching is exact." +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/adding_ui.py:85 +msgid "&Tags to apply when adding a book:" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/adding_ui.py:86 +msgid "" +"A comma-separated list of tags that will be applied to books added to the " +"library" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/adding_ui.py:87 +msgid "&Configure metadata from file name" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/behavior.py:34 +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/behavior.py:36 +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/behavior_ui.py:160 +msgid "Low" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/behavior.py:34 +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/behavior_ui.py:159 +msgid "High" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/behavior.py:36 +msgid "Very low" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/behavior.py:64 +msgid "Compact Metadata" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/behavior.py:65 +msgid "All on 1 tab" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/behavior.py:165 +msgid "Done" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/behavior.py:166 +msgid "Confirmation dialogs have all been reset" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/behavior_ui.py:147 +msgid "Show notification when &new version is available" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/behavior_ui.py:148 +msgid "" +"If checked, Yes/No custom columns values can be Yes, No, or Unknown.\n" +"If not checked, the values can be Yes or No." +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/behavior_ui.py:150 +msgid "Yes/No columns have three values (Requires restart)" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/behavior_ui.py:151 +msgid "Automatically send downloaded &news to ebook reader" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/behavior_ui.py:152 +msgid "&Delete news from library when it is automatically sent to reader" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/behavior_ui.py:153 +msgid "Preferred &output format:" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/behavior_ui.py:154 +msgid "Default network &timeout:" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/behavior_ui.py:155 +msgid "" +"Set the default timeout for network fetches (i.e. anytime we go out to the " +"internet to get information)" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/behavior_ui.py:156 +msgid " seconds" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/behavior_ui.py:157 +msgid "Job &priority:" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/behavior_ui.py:161 +msgid "Restriction to apply when the current library is opened:" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/behavior_ui.py:162 +msgid "" +"Apply this restriction on calibre startup if the current library is being " +"used. Also applied when switching to this library. Note that this setting is " +"per library. " +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/behavior_ui.py:163 +msgid "Edit metadata (single) layout:" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/behavior_ui.py:164 +msgid "" +"Choose a different layout for the Edit Metadata dialog. The compact metadata " +"layout favors editing custom metadata over changing covers and formats." +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/behavior_ui.py:165 +msgid "Preferred &input format order:" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/behavior_ui.py:168 +msgid "Use internal &viewer for:" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/behavior_ui.py:169 +msgid "Reset all disabled &confirmation dialogs" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/columns.py:96 +msgid "You must select a column to delete it" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/columns.py:101 +msgid "The selected column is not a custom column" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/columns.py:103 +msgid "Do you really want to delete column %s and all its data?" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/columns_ui.py:87 +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/custom_columns_ui.py:82 +msgid "" +"Here you can re-arrange the layout of the columns in the calibre library " +"book list. You can hide columns by unchecking them. You can also create your " +"own, custom columns." +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/columns_ui.py:88 +msgid "Move column up" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/columns_ui.py:90 +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/custom_columns_ui.py:84 +msgid "Remove a user-defined column" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/columns_ui.py:92 +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/custom_columns_ui.py:86 +msgid "Add a user-defined column" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/columns_ui.py:94 +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/custom_columns_ui.py:88 +msgid "Edit settings of a user-defined column" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/columns_ui.py:96 +msgid "Move column down" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/columns_ui.py:98 +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/custom_columns_ui.py:91 +msgid "Add &custom column" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/conversion.py:41 +msgid "" +"Restore settings to default values. Only settings for the currently selected " +"section are restored." +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/create_custom_column.py:18 +msgid "Text, column shown in the tag browser" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/create_custom_column.py:21 +msgid "Comma separated text, like tags, shown in the tag browser" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/create_custom_column.py:24 +msgid "Long text, like comments, not shown in the tag browser" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/create_custom_column.py:27 +msgid "Text column for keeping series-like information" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/create_custom_column.py:30 +msgid "Text, but with a fixed set of permitted values" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/create_custom_column.py:34 +msgid "Floating point numbers" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/create_custom_column.py:36 +msgid "Integers" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/create_custom_column.py:38 +msgid "Ratings, shown with stars" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/create_custom_column.py:41 +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/create_custom_column.py:66 +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/create_custom_column.py:73 +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/create_custom_column.py:149 +msgid "Yes/No" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/create_custom_column.py:43 +msgid "Column built from other columns" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/create_custom_column.py:45 +msgid "Column built from other columns, behaves like tags" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/create_custom_column.py:52 +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/create_custom_column.py:53 +msgid "Create a custom column" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/create_custom_column.py:64 +msgid "Quick create:" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/create_custom_column.py:65 +msgid "ISBN" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/create_custom_column.py:65 +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/create_custom_column.py:148 +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/emailp.py:27 +#: /home/kovid/work/calibre/src/calibre/library/field_metadata.py:124 +msgid "Formats" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/create_custom_column.py:68 +msgid "People's names" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/create_custom_column.py:73 +msgid "Number" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/create_custom_column.py:73 +msgid "Text" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/create_custom_column.py:88 +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/create_custom_column.py:89 +msgid "Edit a custom column" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/create_custom_column.py:93 +msgid "No column selected" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/create_custom_column.py:94 +msgid "No column has been selected" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/create_custom_column.py:98 +msgid "Selected column is not a user-defined column" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/create_custom_column.py:150 +msgid "My Tags" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/create_custom_column.py:151 +msgid "My Series" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/create_custom_column.py:152 +msgid "My Rating" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/create_custom_column.py:153 +msgid "People" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/create_custom_column.py:181 +msgid "No lookup name was provided" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/create_custom_column.py:185 +msgid "" +"The lookup name must contain only lower case letters, digits and " +"underscores, and start with a letter" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/create_custom_column.py:188 +msgid "" +"Lookup names cannot end with _index, because these names are reserved for " +"the index of a series column." +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/create_custom_column.py:198 +msgid "No column heading was provided" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/create_custom_column.py:208 +msgid "The lookup name %s is already used" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/create_custom_column.py:220 +msgid "The heading %s is already used" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/create_custom_column.py:231 +msgid "You must enter a template for composite columns" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/create_custom_column.py:240 +msgid "You must enter at least one value for enumeration columns" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/create_custom_column.py:244 +msgid "You cannot provide the empty value, as it is included by default" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/create_custom_column.py:248 +msgid "The value \"{0}\" is in the list more than once" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/create_custom_column_ui.py:190 +msgid "&Lookup name" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/create_custom_column_ui.py:191 +msgid "Column &heading" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/create_custom_column_ui.py:192 +msgid "" +"Used for searching the column. Must contain only digits and lower case " +"letters." +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/create_custom_column_ui.py:193 +msgid "" +"Column heading in the library view and category name in the tag browser" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/create_custom_column_ui.py:194 +msgid "&Column type" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/create_custom_column_ui.py:195 +msgid "What kind of information will be kept in the column." +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/create_custom_column_ui.py:196 +msgid "" +"Show check marks in the GUI. Values of 'yes', 'checked', and 'true'\n" +"will show a green check. Values of 'no', 'unchecked', and 'false' will show " +"a red X.\n" +"Everything else will show nothing." +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/create_custom_column_ui.py:199 +msgid "Show checkmarks" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/create_custom_column_ui.py:200 +msgid "" +"Check this box if this column contains names, like the authors column." +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/create_custom_column_ui.py:201 +msgid "Contains names" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/create_custom_column_ui.py:202 +msgid "" +"

    Date format. Use 1-4 'd's for day, 1-4 'M's for month, and 2 or 4 'y's " +"for year.

    \n" +"

    For example:\n" +"

      \n" +"
    • ddd, d MMM yyyy gives Mon, 5 Jan 2010
    • \n" +"
    • dd MMMM yy gives 05 January 10
    • \n" +"
    " +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/create_custom_column_ui.py:208 +msgid "Use MMM yyyy for month + year, yyyy for year only" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/create_custom_column_ui.py:209 +msgid "Default: dd MMM yyyy." +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/create_custom_column_ui.py:210 +msgid "Format for &dates" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/create_custom_column_ui.py:211 +msgid "&Template" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/create_custom_column_ui.py:212 +msgid "Field template. Uses the same syntax as save templates." +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/create_custom_column_ui.py:213 +msgid "Similar to save templates. For example, {title} {isbn}" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/create_custom_column_ui.py:214 +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/create_custom_column_ui.py:224 +msgid "Default: (nothing)" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/create_custom_column_ui.py:215 +msgid "&Sort/search column by" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/create_custom_column_ui.py:216 +msgid "How this column should handled in the GUI when sorting and searching" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/create_custom_column_ui.py:217 +msgid "If checked, this column will appear in the tags browser as a category" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/create_custom_column_ui.py:218 +msgid "Show in tags browser" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/create_custom_column_ui.py:219 +msgid "Values" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/create_custom_column_ui.py:220 +msgid "" +"A comma-separated list of permitted values. The empty value is always\n" +"included, and is the default. For example, the list 'one,two,three' has\n" +"four values, the first of them being the empty value." +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/create_custom_column_ui.py:223 +msgid "The empty string is always the first value" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/device_debug.py:21 +msgid "Getting debug information" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/device_debug.py:22 +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/device_user_defined.py:32 +msgid "Copy to &clipboard" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/device_debug.py:24 +msgid "Debug device detection" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/device_user_defined.py:31 +msgid "Getting device information" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/device_user_defined.py:34 +msgid "User-defined device information" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/device_user_defined.py:51 +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/device_user_defined.py:57 +msgid "Device Detection" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/device_user_defined.py:52 +msgid "Ensure your device is disconnected, then press OK" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/device_user_defined.py:58 +msgid "Ensure your device is connected, then press OK" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/device_user_defined.py:88 +msgid "" +"Copy these values to the clipboard, paste them into an editor, then enter " +"them into the USER_DEVICE by customizing the device plugin in Preferences-" +">Plugins. Remember to also enter the folders where you want the books to be " +"put. You must restart calibre for your changes to take effect.\n" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/email_ui.py:66 +msgid "" +"calibre can send your books to you (or your reader) by email. Emails will be " +"automatically sent for downloaded news to all email addresses that have Auto-" +"send checked." +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/email_ui.py:67 +msgid "Add an email address to which to send books" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/email_ui.py:68 +msgid "&Add email" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/email_ui.py:69 +msgid "Make &default" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/email_ui.py:70 +msgid "&Remove email" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/emailp.py:27 +msgid "Auto send" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/emailp.py:27 +msgid "Email" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/emailp.py:32 +msgid "Formats to email. The first matching format will be sent." +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/emailp.py:33 +msgid "" +"Subject of the email to use when sending. When left blank the title will be " +"used for the subject. Also, the same templates used for \"Save to disk\" " +"such as {title} and {author_sort} can be used here." +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/emailp.py:37 +msgid "" +"If checked, downloaded news will be automatically mailed
    to this email " +"address (provided it is in one of the listed formats)." +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/emailp.py:115 +msgid "new email address" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/look_feel.py:103 +msgid "Narrow" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/look_feel.py:103 +msgid "Wide" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/look_feel.py:128 +msgid "Off" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/look_feel.py:128 +msgid "Small" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/look_feel.py:129 +msgid "Large" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/look_feel.py:129 +msgid "Medium" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/look_feel.py:132 +msgid "Always" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/look_feel.py:132 +msgid "Automatic" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/look_feel.py:133 +msgid "Never" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/look_feel.py:136 +msgid "By first letter" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/look_feel.py:136 +msgid "Disabled" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/look_feel.py:137 +msgid "Partitioned" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/look_feel_ui.py:198 +msgid "User Interface &layout (needs restart):" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/look_feel_ui.py:199 +msgid "Choose &language (requires restart):" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/look_feel_ui.py:200 +msgid "Enable system &tray icon (needs restart)" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/look_feel_ui.py:201 +msgid "Disable all animations. Useful if you have a slow/old computer." +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/look_feel_ui.py:202 +msgid "Disable &animations" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/look_feel_ui.py:203 +msgid "Disable ¬ifications in system tray" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/look_feel_ui.py:204 +msgid "Show &splash screen at startup" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/look_feel_ui.py:205 +msgid "&Toolbar" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/look_feel_ui.py:206 +msgid "&Icon size:" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/look_feel_ui.py:207 +msgid "Show &text under icons:" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/look_feel_ui.py:208 +msgid "Interface font:" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/look_feel_ui.py:209 +msgid "Change &font (needs restart)" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/look_feel_ui.py:210 +msgid "Main Interface" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/look_feel_ui.py:211 +msgid "Select displayed metadata" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/look_feel_ui.py:212 +msgid "Move up" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/look_feel_ui.py:213 +msgid "Move down" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/look_feel_ui.py:214 +msgid "Use &Roman numerals for series" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/look_feel_ui.py:215 +msgid "" +"Note that comments will always be displayed at the end, regardless of " +"the position you assign here." +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/look_feel_ui.py:217 +msgid "Tags browser category &partitioning method:" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/look_feel_ui.py:218 +msgid "" +"Choose how tag browser subcategories are displayed when\n" +"there are more items than the limit. Select by first\n" +"letter to see an A, B, C list. Choose partitioned to\n" +"have a list of fixed-sized groups. Set to disabled\n" +"if you never want subcategories" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/look_feel_ui.py:223 +msgid "&Collapse when more items than:" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/look_feel_ui.py:224 +msgid "" +"If a Tag Browser category has more than this number of items, it is divided\n" +"up into sub-categories. If the partition method is set to disable, this " +"value is ignored." +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/look_feel_ui.py:226 +msgid "Show &average ratings in the tags browser" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/look_feel_ui.py:227 +msgid "Categories with &hierarchical items:" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/look_feel_ui.py:228 +msgid "" +"A comma-separated list of columns in which items containing\n" +"periods are displayed in the tag browser trees. For example, if\n" +"this box contains 'tags' then tags of the form 'Mystery.English'\n" +"and 'Mystery.Thriller' will be displayed with English and Thriller\n" +"both under 'Mystery'. If 'tags' is not in this box,\n" +"then the tags will be displayed each on their own line." +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/look_feel_ui.py:235 +msgid "Show cover &browser in a separate window (needs restart)" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/look_feel_ui.py:236 +msgid "&Number of covers to show in browse mode (needs restart):" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/main.py:230 +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/tweaks_ui.py:96 +msgid "&Apply" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/main.py:237 +msgid "Restore &defaults" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/main.py:238 +msgid "Save changes" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/main.py:239 +msgid "Cancel and return to overview" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/main.py:295 +msgid "Restoring to defaults not supported for" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/main.py:330 +msgid "" +"Some of the changes you made require a restart. Please restart calibre as " +"soon as possible." +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/main.py:333 +msgid "" +"The changes you have made require calibre be restarted immediately. You will " +"not be allowed set any more preferences, until you restart." +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/main.py:338 +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/server.py:127 +msgid "Restart needed" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/metadata_sources.py:47 +msgid "Source" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/metadata_sources.py:49 +msgid "Cover priority" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/metadata_sources.py:76 +msgid "This source is configured and ready to go" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/metadata_sources.py:77 +msgid "This source needs configuration" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/metadata_sources.py:148 +msgid "Published date" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/metadata_sources.py:241 +msgid "Configure %s
    %s" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/metadata_sources.py:293 +msgid "No source selected" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/metadata_sources.py:294 +msgid "No source selected, cannot configure." +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/metadata_sources_ui.py:110 +msgid "Metadata sources" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/metadata_sources_ui.py:111 +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 " +"(smaller) priority will be preferred when bulk downloading metadata.\n" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/metadata_sources_ui.py:113 +msgid "" +"Sources with a red X next to their names must be configured before they will " +"be used. " +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/metadata_sources_ui.py:114 +msgid "Configure selected source" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/metadata_sources_ui.py:116 +msgid "" +"If you uncheck any fields, metadata for those fields will not be downloaded" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/metadata_sources_ui.py:117 +msgid "&Select all" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/metadata_sources_ui.py:118 +msgid "&Clear all" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/metadata_sources_ui.py:119 +msgid "Convert all downloaded comments to plain &text" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/metadata_sources_ui.py:120 +msgid "Swap author names from FN LN to LN, FN" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/metadata_sources_ui.py:121 +msgid "Max. number of &tags to download:" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/metadata_sources_ui.py:122 +msgid "Max. &time to wait after first match is found:" +msgstr "" + +#: /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:125 +#: /home/kovid/work/calibre/src/calibre/gui2/viewer/config_ui.py:199 +msgid " secs" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/metadata_sources_ui.py:124 +msgid "Max. time to wait after first &cover is found:" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/metadata_sources_ui.py:126 +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 " +"tend to be more like genres, while the larger tag sets tend to describe the " +"books content.\n" +"

    Note that this option will only make a practical difference if one of the " +"metadata sources has a genre like tag set for the book you are searching " +"for. Most often, they all have large tag sets." +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/metadata_sources_ui.py:128 +msgid "Prefer &fewer tags" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/misc.py:56 +msgid "Failed to install command line tools." +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/misc.py:59 +msgid "Command line tools installed" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/misc.py:60 +msgid "Command line tools installed in" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/misc.py:61 +msgid "" +"If you move calibre.app, you have to re-install the command line tools." +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/misc_ui.py:62 +msgid "Max. simultaneous conversion/news download jobs:" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/misc_ui.py:63 +msgid "Limit the max. simultaneous jobs to the available CPU &cores" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/misc_ui.py:64 +msgid "Debug &device detection" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/misc_ui.py:65 +msgid "Get information to setup the &user defined device" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/misc_ui.py:66 +msgid "Open calibre &configuration directory" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/misc_ui.py:67 +msgid "&Install command line tools" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/plugboard.py:37 +msgid "Open Editor" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/plugboard.py:72 +msgid "Device currently connected: " +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/plugboard.py:75 +msgid "Device currently connected: None" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/plugboard.py:207 +msgid "That format and device already has a plugboard." +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/plugboard.py:219 +msgid "Possibly override plugboard?" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/plugboard.py:220 +msgid "" +"A more general plugboard already exists for that format and device. Are you " +"sure you want to add the new plugboard?" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/plugboard.py:232 +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/plugboard.py:254 +msgid "Add possibly overridden plugboard?" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/plugboard.py:233 +msgid "" +"More specific device plugboards exist for that format. Are you sure you want " +"to add the new plugboard?" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/plugboard.py:244 +msgid "Really add plugboard?" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/plugboard.py:245 +msgid "" +"A different plugboard matches that format and device combination. Are you " +"sure you want to add the new plugboard?" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/plugboard.py:255 +msgid "" +"More specific format and device plugboards already exist. Are you sure you " +"want to add the new plugboard?" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/plugboard.py:266 +msgid "The {0} device does not support the {1} format." +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/plugboard.py:299 +msgid "Invalid destination" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/plugboard.py:300 +msgid "The destination field cannot be blank" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/plugboard_ui.py:114 +msgid "" +"Here you can change the metadata calibre uses to update a book when saving " +"to disk or sending to device.\n" +"\n" +"Use this dialog to define a 'plugboard' for a format (or all formats) and a " +"device (or all devices). The plugboard specifies what template is connected " +"to what field. The template is used to compute a value, and that value is " +"assigned to the connected field.\n" +"\n" +"Often templates will contain simple references to composite columns, but " +"this is not necessary. You can use any template in a source box that you can " +"use elsewhere in calibre.\n" +"\n" +"One possible use for a plugboard is to alter the title to contain series " +"information. Another would be to change the author sort, something that mobi " +"users might do to force it to use the ';' that the kindle requires. A third " +"would be to specify the language." +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/plugboard_ui.py:121 +msgid "Format (choose first)" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/plugboard_ui.py:122 +msgid "Device (choose second)" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/plugboard_ui.py:123 +msgid "Add new plugboard" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/plugboard_ui.py:124 +msgid "Edit existing plugboard" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/plugboard_ui.py:125 +msgid "Existing plugboards" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/plugboard_ui.py:126 +msgid "Source template" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/plugboard_ui.py:127 +msgid "Destination field" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/plugboard_ui.py:128 +msgid "Save plugboard" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/plugboard_ui.py:129 +msgid "Delete plugboard" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/plugins.py:182 +msgid "%(plugin_type)s %(plugins)s" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/plugins.py:183 +msgid "plugins" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/plugins.py:192 +msgid "" +"\n" +"Customization: " +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/plugins.py:221 +msgid "Search for plugin" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/plugins.py:230 +#: /home/kovid/work/calibre/src/calibre/gui2/store/search/search.py:223 +msgid "No matches" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/plugins.py:231 +msgid "Could not find any matching plugins" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/plugins.py:272 +msgid "Add plugin" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/plugins.py:280 +msgid "" +"Installing plugins is a security risk. Plugins can contain a " +"virus/malware. Only install it if you got it from a trusted source. Are you " +"sure you want to proceed?" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/plugins.py:296 +msgid "" +"Plugin {0} successfully installed under {1} plugins. You may " +"have to restart calibre for the plugin to take effect." +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/plugins.py:304 +msgid "No valid plugin path" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/plugins.py:305 +msgid "%s is not a valid plugin path" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/plugins.py:314 +msgid "Select an actual plugin under %s to customize" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/plugins.py:320 +msgid "Plugin cannot be disabled" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/plugins.py:321 +msgid "The plugin: %s cannot be disabled" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/plugins.py:331 +msgid "Plugin not customizable" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/plugins.py:332 +msgid "Plugin: %s does not need customization" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/plugins.py:338 +msgid "Must restart" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/plugins.py:339 +msgid "" +"You must restart calibre before you can configure the %s plugin" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/plugins.py:344 +msgid "Plugin {0} successfully removed" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/plugins.py:352 +msgid "Cannot remove builtin plugin" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/plugins.py:353 +msgid " cannot be removed. It is a builtin plugin. Try disabling it instead." +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/plugins_ui.py:87 +msgid "" +"Here you can customize the behavior of Calibre by controlling what plugins " +"it uses." +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/plugins_ui.py:90 +msgid "Enable/&Disable plugin" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/plugins_ui.py:91 +msgid "&Customize plugin" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/plugins_ui.py:92 +msgid "&Remove plugin" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/plugins_ui.py:93 +msgid "&Add a new plugin" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/save_template.py:33 +msgid "Any custom field" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/save_template.py:34 +msgid "The lookup name of any custom field. These names begin with \"#\")" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/save_template.py:57 +msgid "Constant template" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/save_template.py:58 +msgid "" +"The template contains no {fields}, so all books will have the same name. Is " +"this OK?" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/save_template_ui.py:47 +msgid "Save &template" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/save_template_ui.py:48 +msgid "" +"By adjusting the template below, you can control what folders the files are " +"saved in and what filenames they are given. You can use the / character to " +"indicate sub-folders. Available metadata variables are described below. If a " +"particular book does not have some metadata, the variable will be replaced " +"by the empty string." +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/save_template_ui.py:49 +msgid "Available variables:" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/saving_ui.py:68 +msgid "" +"Here you can control how calibre will save your books when you click the " +"Save to Disk button:" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/saving_ui.py:69 +msgid "Save &cover separately" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/saving_ui.py:70 +msgid "Replace space with &underscores" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/saving_ui.py:71 +msgid "Update &metadata in saved copies" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/saving_ui.py:72 +msgid "Change paths to &lowercase" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/saving_ui.py:73 +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/sending_ui.py:76 +msgid "Format &dates as:" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/saving_ui.py:74 +msgid "File &formats to save:" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/saving_ui.py:75 +msgid "Convert non-English characters to &English equivalents" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/saving_ui.py:76 +msgid "Save metadata in &OPF file" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/search.py:33 +msgid "" +"Grouped search terms are search names that permit a query to " +"automatically search across more than one column. For example, if you create " +"a grouped search term allseries with the value series, " +"#myseries, #myseries2, then the query allseries:adhoc " +"will find 'adhoc' in any of the columns series, " +"#myseries, and #myseries2.

    Enter the name of " +"the grouped search term in the drop-down box, enter the list of columns to " +"search in the value box, then push the Save button.

    Note: Search terms " +"are forced to lower case; MySearch and mysearch " +"are the same term.

    You can have your grouped search term show up as user " +"categories in the Tag Browser. Just add the grouped search term names to " +"the Make user categories from box. You can add multiple terms separated by " +"commas. The new user category will be automatically populated with all the " +"items in the categories included in the grouped search term.

    Automatic " +"user categories permit you to see easily all the category items that are in " +"the columns contained in the grouped search term. Using the above " +"allseries example, the automatically-generated user category " +"will contain all the series mentioned in series, " +"#myseries, and #myseries2. This can be useful to " +"check for duplicates, to find which column contains a particular item, or to " +"have hierarchical categories (categories that contain categories)." +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/search.py:96 +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/search.py:106 +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/search.py:110 +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/search.py:116 +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/search.py:128 +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/search_ui.py:119 +msgid "Grouped Search Terms" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/search.py:97 +msgid "The search term cannot be blank" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/search.py:107 +msgid "That name is already used for a column or grouped search term" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/search.py:111 +msgid "That name is already used for user category" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/search.py:117 +msgid "The value box cannot be empty" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/search.py:129 +msgid "The empty grouped search term cannot be deleted" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/search_ui.py:110 +msgid "Search as you &type" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/search_ui.py:111 +msgid "" +"&Highlight search results instead of restricting the book list to the results" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/search_ui.py:112 +msgid "What to search by default" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/search_ui.py:113 +msgid "" +"When you enter a search term without a prefix, by default calibre will " +"search all metadata for matches. For example, entering, \"asimov\" will " +"search not just authors but title/tags/series/comments/etc. Use these " +"options if you would like to change this behavior." +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/search_ui.py:114 +msgid "&Limit the searched metadata" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/search_ui.py:115 +msgid "&Columns that non-prefixed searches are limited to:" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/search_ui.py:116 +msgid "" +"Note that this option affects all searches, including saved searches and " +"restrictions. Therefore, if you use this option, it is best to ensure that " +"you always use prefixes in your saved searches. For example, use " +"\"series:Foundation\" rather than just \"Foundation\" in a saved search" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/search_ui.py:117 +msgid "" +"Clear search histories from all over calibre. Including the book list, e-" +"book viewer, fetch news dialog, etc." +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/search_ui.py:118 +msgid "Clear search &histories" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/search_ui.py:120 +msgid "&Names:" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/search_ui.py:121 +msgid "" +"Contains the names of the currently-defined group search terms.\n" +"Create a new name by entering it into the empty box, then\n" +"pressing Save. Rename a search term by selecting it then\n" +"changing the name and pressing Save. Change the value of\n" +"a search term by changing the value box then pressing Save." +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/search_ui.py:126 +msgid "Delete the current search term" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/search_ui.py:128 +msgid "" +"Save the current search term. You can rename a search term by\n" +"changing the name then pressing Save. You can change the value\n" +"of a search term by changing the value box then pressing Save." +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/search_ui.py:131 +msgid "&Save" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/search_ui.py:132 +msgid "Make &user categories from:" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/search_ui.py:133 +msgid "" +"Enter the names of any grouped search terms you wish\n" +"to be shown as user categories" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/sending.py:28 +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/sending_ui.py:70 +msgid "Manual management" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/sending.py:29 +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/sending_ui.py:71 +msgid "Only on send" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/sending.py:30 +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/sending_ui.py:72 +msgid "Automatic management" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/sending_ui.py:69 +msgid "Metadata &management:" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/sending_ui.py:73 +msgid "" +"

  • Manual management: Calibre updates the metadata and adds " +"collections only when a book is sent. With this option, calibre will never " +"remove a collection.
  • \n" +"
  • Only on send: Calibre updates metadata and adds/removes " +"collections for a book only when it is sent to the device.
  • \n" +"
  • Automatic management: Calibre automatically keeps metadata on the " +"device in sync with the calibre library, on every connect
  • " +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/sending_ui.py:77 +msgid "" +"Here you can control how calibre will save your books when you click the " +"Send to Device button. This setting can be overriden for individual devices " +"by customizing the device interface plugins in Preferences->Advanced->Plugins" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/server.py:70 +#: /home/kovid/work/calibre/src/calibre/gui2/ui.py:422 +msgid "Failed to start content server" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/server.py:106 +msgid "Error log:" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/server.py:113 +msgid "Access log:" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/server.py:128 +msgid "You need to restart the server for changes to take effect" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/server_ui.py:124 +msgid "Server &port:" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/server_ui.py:127 +msgid "" +"If you leave the password blank, anyone will be able to access your book " +"collection using the web interface." +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/server_ui.py:128 +msgid "" +"The maximum size (widthxheight) for displayed covers. Larger covers are " +"resized. " +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/server_ui.py:129 +msgid "Max. &cover size:" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/server_ui.py:131 +msgid "Max. &OPDS items per query:" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/server_ui.py:132 +msgid "Max. OPDS &ungrouped items:" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/server_ui.py:133 +msgid "Restriction (saved search) to apply:" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/server_ui.py:134 +msgid "" +"This restriction (based on a saved search) will restrict the books the " +"content server makes available to those matching the search. This setting is " +"per library (i.e. you can have a different restriction per library)." +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/server_ui.py:135 +msgid "&Start Server" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/server_ui.py:136 +msgid "St&op Server" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/server_ui.py:137 +msgid "&Test Server" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/server_ui.py:138 +msgid "" +"calibre contains a network server that allows you to access your book " +"collection using a browser from anywhere in the world. Any changes to the " +"settings will only take effect after a server restart." +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/server_ui.py:139 +msgid "Run server &automatically on startup" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/server_ui.py:140 +msgid "View &server logs" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/server_ui.py:141 +#: /home/kovid/work/calibre/src/calibre/gui2/wizard/stanza_ui.py:51 +msgid "" +"

    Remember to leave calibre running as the server only runs as long as " +"calibre is running.\n" +"

    Stanza should see your calibre collection automatically. If not, try " +"adding the URL http://myhostname:8080 as a new catalog in the Stanza reader " +"on your iPhone. Here myhostname should be the fully qualified hostname or " +"the IP address of the computer calibre is running on." +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/template_functions.py:23 +msgid "" +"\n" +"

    Here you can add and remove functions used in template " +"processing. A\n" +" template function is written in python. It takes information from " +"the\n" +" book, processes it in some way, then returns a string result. " +"Functions\n" +" defined here are usable in templates in the same way that builtin\n" +" functions are usable. The function must be named evaluate, " +"and\n" +" must have the signature shown below.

    \n" +"

    evaluate(self, formatter, kwargs, mi, locals, your " +"parameters)\n" +" → returning a unicode string

    \n" +"

    The parameters of the evaluate function are:\n" +"

      \n" +"
    • formatter: the instance of the formatter being used to\n" +" evaluate the current template. You can use this to do recursive\n" +" template evaluation.
    • \n" +"
    • kwargs: a dictionary of metadata. Field values are in " +"this\n" +" dictionary.\n" +"
    • mi: a Metadata instance. Used to get field information.\n" +" This parameter can be None in some cases, such as when evaluating\n" +" non-book templates.
    • \n" +"
    • locals: the local variables assigned to by the current\n" +" template program.
    • \n" +"
    • your parameters: You must supply one or more formal\n" +" parameters. The number must match the arg count box, unless arg " +"count is\n" +" -1 (variable number or arguments), in which case the last argument " +"must\n" +" be *args. At least one argument is required, and is usually the " +"value of\n" +" the field being operated upon. Note that when writing in basic " +"template\n" +" mode, the user does not provide this first argument. Instead it is\n" +" supplied by the formatter.
    • \n" +"

    \n" +"

    \n" +" The following example function checks the value of the field. If " +"the\n" +" field is not empty, the field's value is returned, otherwise the " +"value\n" +" EMPTY is returned.\n" +"

    \n"
    +"        name: my_ifempty\n"
    +"        arg count: 1\n"
    +"        doc: my_ifempty(val) -- return val if it is not empty, otherwise the "
    +"string 'EMPTY'\n"
    +"        program code:\n"
    +"        def evaluate(self, formatter, kwargs, mi, locals, val):\n"
    +"            if val:\n"
    +"                return val\n"
    +"            else:\n"
    +"                return 'EMPTY'
    \n" +" This function can be called in any of the three template program " +"modes:\n" +"
      \n" +"
    • single-function mode: {tags:my_ifempty()}
    • \n" +"
    • template program mode: {tags:'my_ifempty($)'}
    • \n" +"
    • general program mode: program: my_ifempty(field('tags'))
    • \n" +"

      \n" +" " +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/template_functions.py:134 +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/template_functions.py:144 +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/template_functions.py:151 +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/template_functions.py:155 +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/template_functions.py:166 +msgid "Template functions" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/template_functions.py:135 +msgid "You cannot delete a built-in function" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/template_functions.py:145 +msgid "Function not defined" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/template_functions.py:156 +msgid "Argument count must be -1 or greater than zero" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/template_functions.py:167 +msgid "Exception while compiling function" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/template_functions.py:195 +msgid "function source code not available" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/template_functions_ui.py:96 +msgid "&Function:" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/template_functions_ui.py:97 +msgid "Enter the name of the function to create." +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/template_functions_ui.py:98 +msgid "Arg &count:" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/template_functions_ui.py:99 +msgid "Set this to -1 if the function takes a variable number of arguments" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/template_functions_ui.py:102 +msgid "&Delete" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/template_functions_ui.py:103 +msgid "&Replace" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/template_functions_ui.py:104 +msgid "C&reate" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/template_functions_ui.py:105 +msgid "&Program Code: (be sure to follow python indenting rules)" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/toolbar.py:36 +msgid "Switch between library and device views" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/toolbar.py:41 +msgid "Separator" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/toolbar.py:58 +msgid "Choose library" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/toolbar.py:219 +msgid "The main toolbar" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/toolbar.py:220 +msgid "The main toolbar when a device is connected" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/toolbar.py:221 +msgid "The optional second toolbar" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/toolbar.py:222 +msgid "The menubar" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/toolbar.py:223 +msgid "The menubar when a device is connected" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/toolbar.py:224 +msgid "The context menu for the books in the calibre library" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/toolbar.py:226 +msgid "The context menu for the books on the device" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/toolbar.py:260 +msgid "Cannot add" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/toolbar.py:261 +msgid "Cannot add the actions %s to this location" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/toolbar.py:279 +msgid "Cannot remove" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/toolbar.py:280 +msgid "Cannot remove the actions %s from this location" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/toolbar_ui.py:99 +msgid "Customize the actions in:" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/toolbar_ui.py:100 +msgid "A&vailable actions" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/toolbar_ui.py:101 +msgid "&Current actions" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/toolbar_ui.py:102 +msgid "Move selected action up" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/toolbar_ui.py:104 +msgid "Move selected action down" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/toolbar_ui.py:107 +msgid "Add selected actions to toolbar" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/toolbar_ui.py:109 +msgid "Remove selected actions from toolbar" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/tweaks.py:114 +msgid "This tweak has it default value" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/tweaks.py:116 +msgid "This tweak has been customized" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/tweaks.py:237 +msgid "" +"Add/edit tweaks for any custom plugins you have installed. Documentation for " +"these tweaks should be available on the website from where you downloaded " +"the plugins." +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/tweaks.py:278 +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/tweaks.py:318 +msgid "" +"There was a syntax error in your tweak. Click the show details button for " +"details." +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/tweaks.py:331 +msgid "Invalid tweaks" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/tweaks.py:332 +msgid "" +"The tweaks you entered are invalid, try resetting the tweaks to default and " +"changing them one by one until you find the invalid setting." +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/tweaks_ui.py:88 +msgid "" +"Values for the tweaks are shown below. Edit them to change the behavior of " +"calibre. Your changes will only take effect after a restart of " +"calibre." +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/tweaks_ui.py:89 +msgid "Edit tweaks for any custom plugins you have installed" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/tweaks_ui.py:90 +msgid "&Plugin tweaks" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/tweaks_ui.py:92 +msgid "Edit tweak" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/tweaks_ui.py:93 +msgid "Restore this tweak to its default value" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/tweaks_ui.py:94 +msgid "Restore &default" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/tweaks_ui.py:95 +msgid "Apply any changes you made to this tweak" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/search_box.py:95 +#: /home/kovid/work/calibre/src/calibre/gui2/search_box.py:279 +#: /home/kovid/work/calibre/src/calibre/gui2/store/mobileread/store_dialog_ui.py:76 +#: /home/kovid/work/calibre/src/calibre/gui2/store/search/search_ui.py:121 +#: /home/kovid/work/calibre/src/calibre/gui2/store/search_ui.py:109 +#: /home/kovid/work/calibre/src/calibre/gui2/widgets.py:653 +#: /home/kovid/work/calibre/src/calibre/library/server/browse.py:280 +msgid "Search" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/search_box.py:339 +msgid "Delete current search" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/search_box.py:340 +msgid "No search is selected" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/search_box.py:342 +msgid "The selected search will be permanently deleted. Are you sure?" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/search_box.py:367 +msgid "Search (For Advanced Search click the button to the left)" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/search_box.py:390 +msgid "Enable or disable search highlighting." +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/search_box.py:445 +msgid "Saved Searches" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/search_box.py:447 +msgid "Choose saved search or enter name for new saved search" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/search_box.py:453 +msgid "" +"Save current search under the name shown in the box. Press and hold for a " +"pop-up options menu." +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/search_box.py:458 +msgid "Create saved search" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/search_box.py:462 +msgid "Delete saved search" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/search_box.py:466 +msgid "Manage saved searches" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/search_box.py:476 +msgid "*Current search" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/search_restriction_mixin.py:12 +msgid "Restrict to" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/search_restriction_mixin.py:19 +#: /home/kovid/work/calibre/src/calibre/gui2/search_restriction_mixin.py:92 +msgid "(all books)" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/search_restriction_mixin.py:21 +msgid "" +"Books display will be restricted to those matching a selected saved search" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/search_restriction_mixin.py:53 +msgid " or the search " +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/search_restriction_mixin.py:87 +msgid "({0} of {1})" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/search_restriction_mixin.py:94 +msgid "({0} of all)" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/shortcuts.py:48 +#: /home/kovid/work/calibre/src/calibre/gui2/shortcuts_ui.py:78 +#: /home/kovid/work/calibre/src/calibre/gui2/shortcuts_ui.py:83 +#: /home/kovid/work/calibre/src/calibre/gui2/store/search/search_ui.py:125 +#: /home/kovid/work/calibre/src/calibre/gui2/store/search_ui.py:113 +#: /home/kovid/work/calibre/src/calibre/gui2/widgets.py:351 +msgid "None" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/shortcuts.py:59 +msgid "Press a key..." +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/shortcuts.py:80 +msgid "Already assigned" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/shortcuts.py:82 +msgid "already assigned to" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/shortcuts.py:132 +#: /home/kovid/work/calibre/src/calibre/gui2/shortcuts.py:223 +msgid " or " +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/shortcuts.py:134 +#: /home/kovid/work/calibre/src/calibre/gui2/shortcuts_ui.py:74 +msgid "&Default" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/shortcuts.py:136 +msgid "Customize shortcuts for" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/shortcuts.py:223 +msgid "Keys" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/shortcuts.py:225 +msgid "Double click to change" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/shortcuts_ui.py:73 +msgid "Frame" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/shortcuts_ui.py:75 +msgid "&Custom" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/shortcuts_ui.py:76 +msgid "&Shortcut:" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/shortcuts_ui.py:77 +#: /home/kovid/work/calibre/src/calibre/gui2/shortcuts_ui.py:82 +msgid "Click to change" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/shortcuts_ui.py:81 +msgid "&Alternate shortcut:" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/store/basic_config_widget_ui.py:38 +msgid "Added Tags:" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/store/basic_config_widget_ui.py:39 +msgid "Open store in external web browswer" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/store/ebooks_com_plugin.py:96 +msgid "Not Available" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/store/mobileread/cache_progress_dialog_ui.py:51 +msgid "Updating book cache" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/store/mobileread/cache_update_thread.py:42 +msgid "Checking last download date." +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/store/mobileread/cache_update_thread.py:48 +msgid "Downloading book list from MobileRead." +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/store/mobileread/cache_update_thread.py:61 +msgid "Processing books." +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/store/mobileread/cache_update_thread.py:70 +msgid "%s of %s books processed." +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/store/mobileread/mobileread_plugin.py:62 +msgid "Updating MobileRead book cache..." +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/store/mobileread/store_dialog_ui.py:74 +msgid "&Query:" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/store/mobileread/store_dialog_ui.py:77 +#: /home/kovid/work/calibre/src/calibre/gui2/store/mobileread_store_dialog_ui.py:63 +msgid "Books:" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/store/mobileread/store_dialog_ui.py:79 +#: /home/kovid/work/calibre/src/calibre/gui2/store/mobileread_store_dialog_ui.py:65 +#: /home/kovid/work/calibre/src/calibre/gui2/store/search/search_ui.py:128 +#: /home/kovid/work/calibre/src/calibre/gui2/store/search_ui.py:114 +#: /home/kovid/work/calibre/src/calibre/gui2/store/web_store_dialog_ui.py:63 +msgid "Close" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/store/mobileread_store_dialog_ui.py:62 +msgid "Search:" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/store/search/adv_search_builder_ui.py:192 +msgid "&Price:" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/store/search/adv_search_builder_ui.py:196 +msgid "Titl&e/Author/Price ..." +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/store/search/models.py:33 +msgid "DRM" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/store/search/models.py:33 +msgid "Price" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/store/search/models.py:175 +msgid "" +"Detected price as: %s. Check with the store before making a purchase to " +"verify this price is correct. This price often does not include promotions " +"the store may be running." +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/store/search/models.py:178 +msgid "" +"This book as been detected as having DRM restrictions. This book may not " +"work with your reader and you will have limitations placed upon you as to " +"what you can do with this book. Check with the store before making any " +"purchases to ensure you can actually read this book." +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/store/search/models.py:180 +msgid "" +"This book has been detected as being DRM Free. You should be able to use " +"this book on any device provided it is in a format calibre supports for " +"conversion. However, before making a purchase double check the DRM status " +"with the store. The store may not be disclosing the use of DRM." +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/store/search/models.py:182 +msgid "" +"The DRM status of this book could not be determined. There is a very high " +"likelihood that this book is actually DRM restricted." +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/store/search/search.py:223 +msgid "Couldn't find any books matching your query." +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/store/search/search_ui.py:118 +#: /home/kovid/work/calibre/src/calibre/gui2/store/search_ui.py:107 +msgid "Get Books" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/store/search/search_ui.py:119 +#: /home/kovid/work/calibre/src/calibre/gui2/store/search_ui.py:108 +msgid "Query:" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/store/search/search_ui.py:123 +#: /home/kovid/work/calibre/src/calibre/gui2/store/search_ui.py:111 +msgid "All" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/store/search/search_ui.py:124 +#: /home/kovid/work/calibre/src/calibre/gui2/store/search_ui.py:112 +msgid "Invert" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/store/search/search_ui.py:126 +msgid "Open a selected book in the system's web browser" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/store/search/search_ui.py:127 +msgid "Open in &external browser" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/store/web_control.py:73 +msgid "" +"This ebook is a DRMed EPUB file. You will be prompted to save this file to " +"your computer. Once it is saved, open it with Adobe Digital " +"Editions (ADE).

      ADE, in turn will download the actual ebook, which " +"will be a .epub file. You can add this book to calibre using \"Add Books\" " +"and selecting the file from the ADE library folder." +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/store/web_control.py:86 +msgid "File is not a supported ebook type. Save to disk?" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/store/web_store_dialog_ui.py:59 +msgid "Home" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/store/web_store_dialog_ui.py:60 +msgid "Reload" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/store/web_store_dialog_ui.py:61 +msgid "%p%" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/tag_view.py:345 +#: /home/kovid/work/calibre/src/calibre/gui2/tag_view.py:375 +#: /home/kovid/work/calibre/src/calibre/gui2/tag_view.py:404 +msgid "Rename %s" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/tag_view.py:349 +msgid "Edit sort for %s" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/tag_view.py:356 +msgid "Add %s to user category" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/tag_view.py:369 +msgid "Children of %s" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/tag_view.py:379 +msgid "Delete search %s" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/tag_view.py:384 +msgid "Remove %s from category %s" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/tag_view.py:391 +msgid "Search for %s" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/tag_view.py:396 +msgid "Search for everything but %s" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/tag_view.py:408 +msgid "Add sub-category to %s" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/tag_view.py:412 +msgid "Delete user category %s" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/tag_view.py:417 +msgid "Hide category %s" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/tag_view.py:421 +msgid "Show category" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/tag_view.py:431 +msgid "Search for books in category %s" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/tag_view.py:437 +msgid "Search for books not in category %s" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/tag_view.py:446 +#: /home/kovid/work/calibre/src/calibre/gui2/tag_view.py:451 +msgid "Manage %s" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/tag_view.py:454 +#: /home/kovid/work/calibre/src/calibre/gui2/tag_view.py:1844 +msgid "Manage Saved Searches" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/tag_view.py:462 +#: /home/kovid/work/calibre/src/calibre/gui2/tag_view.py:466 +#: /home/kovid/work/calibre/src/calibre/gui2/tag_view.py:1842 +msgid "Manage User Categories" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/tag_view.py:473 +msgid "Show all categories" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/tag_view.py:476 +msgid "Change sub-categorization scheme" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/tag_view.py:792 +msgid "The grouped search term name is \"{0}\"" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/tag_view.py:1065 +msgid "" +"Changing the authors for several books can take a while. Are you sure?" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/tag_view.py:1070 +msgid "" +"Changing the metadata for that many books can take a while. Are you sure?" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/tag_view.py:1157 +#: /home/kovid/work/calibre/src/calibre/library/database2.py:417 +msgid "Searches" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/tag_view.py:1391 +#: /home/kovid/work/calibre/src/calibre/gui2/tag_view.py:1411 +#: /home/kovid/work/calibre/src/calibre/gui2/tag_view.py:1420 +msgid "Rename user category" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/tag_view.py:1392 +msgid "You cannot use periods in the name when renaming user categories" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/tag_view.py:1412 +#: /home/kovid/work/calibre/src/calibre/gui2/tag_view.py:1421 +msgid "The name %s is already used" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/tag_view.py:1444 +msgid "Duplicate search name" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/tag_view.py:1445 +msgid "The saved search name %s is already used." +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/tag_view.py:1834 +msgid "Manage Authors" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/tag_view.py:1836 +msgid "Manage Series" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/tag_view.py:1838 +msgid "Manage Publishers" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/tag_view.py:1840 +msgid "Manage Tags" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/tag_view.py:1852 +msgid "Invalid search restriction" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/tag_view.py:1853 +msgid "The current search restriction is invalid" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/tag_view.py:1869 +msgid "New Category" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/tag_view.py:1920 +#: /home/kovid/work/calibre/src/calibre/gui2/tag_view.py:1923 +msgid "Delete user category" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/tag_view.py:1921 +msgid "%s is not a user category" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/tag_view.py:1924 +msgid "%s contains items. Do you really want to delete it?" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/tag_view.py:1945 +msgid "Remove category" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/tag_view.py:1946 +msgid "User category %s does not exist" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/tag_view.py:1965 +msgid "Add to user category" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/tag_view.py:1966 +msgid "A user category %s does not exist" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/tag_view.py:2089 +msgid "Find item in tag browser" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/tag_view.py:2092 +msgid "" +"Search for items. This is a \"contains\" search; items containing the\n" +"text anywhere in the name will be found. You can limit the search\n" +"to particular categories using syntax similar to search. For example,\n" +"tags:foo will find foo in any tag, but not in authors etc. Entering\n" +"*foo will filter all categories at once, showing only those items\n" +"containing the text \"foo\"" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/tag_view.py:2101 +msgid "ALT+f" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/tag_view.py:2106 +msgid "Find the first/next matching item" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/tag_view.py:2111 +msgid "Collapse all categories" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/tag_view.py:2135 +msgid "No More Matches.

      Click Find again to go to first match" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/tag_view.py:2148 +msgid "Sort by name" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/tag_view.py:2148 +msgid "Sort by popularity" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/tag_view.py:2149 +msgid "Sort by average rating" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/tag_view.py:2152 +msgid "Set the sort order for entries in the Tag Browser" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/tag_view.py:2159 +msgid "Match all" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/tag_view.py:2159 +msgid "Match any" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/tag_view.py:2164 +msgid "" +"When selecting multiple entries in the Tag Browser match any or all of them" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/tag_view.py:2171 +msgid "Manage authors, tags, etc" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/tag_view.py:2172 +msgid "" +"All of these category_managers are available by right-clicking on items in " +"the tag browser above" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/tools.py:65 +msgid "Convert book %(num)d of %(total)d (%(title)s)" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/tools.py:93 +#: /home/kovid/work/calibre/src/calibre/gui2/tools.py:205 +msgid "Could not convert some books" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/tools.py:94 +#: /home/kovid/work/calibre/src/calibre/gui2/tools.py:206 +msgid "" +"Could not convert %d of %d books, because no suitable source format was " +"found." +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/tools.py:125 +msgid "Queueing books for bulk conversion" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/tools.py:183 +msgid "Queueing " +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/tools.py:184 +msgid "Convert book %d of %d (%s)" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/tools.py:254 +msgid "Fetch news from " +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/tools.py:327 +msgid "Convert existing" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/tools.py:328 +msgid "" +"The following books have already been converted to %s format. Do you wish to " +"reconvert them?" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/ui.py:196 +msgid "&Donate to support calibre" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/ui.py:232 +msgid "&Restore" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/ui.py:237 +msgid "&Eject connected device" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/ui.py:345 +msgid "Debug mode" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/ui.py:346 +msgid "" +"You have started calibre in debug mode. After you quit calibre, the debug " +"log will be available in the file: %s

      The log will be displayed " +"automatically." +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/ui.py:548 +msgid "Conversion Error" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/ui.py:571 +msgid "Recipe Disabled" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/ui.py:587 +msgid "Failed" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/ui.py:621 +msgid "There are active jobs. Are you sure you want to quit?" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/ui.py:624 +msgid "" +" is communicating with the device!
      \n" +" Quitting may cause corruption on the device.
      \n" +" Are you sure you want to quit?" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/ui.py:628 +msgid "Active jobs" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/ui.py:696 +msgid "" +"will keep running in the system tray. To close it, choose Quit in the " +"context menu of the system tray." +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/update.py:53 +msgid "" +"%s has been updated to version %s. See the new features." +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/update.py:58 +msgid "Update available!" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/update.py:63 +msgid "Show this notification for future updates" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/update.py:68 +msgid "&Get update" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/viewer/bookmarkmanager.py:43 +msgid "Edit bookmark" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/viewer/bookmarkmanager.py:43 +msgid "New title for bookmark:" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/viewer/bookmarkmanager.py:52 +msgid "Export Bookmarks" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/viewer/bookmarkmanager.py:54 +msgid "Saved Bookmarks (*.pickle)" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/viewer/bookmarkmanager.py:62 +msgid "Import Bookmarks" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/viewer/bookmarkmanager.py:62 +msgid "Pickled Bookmarks (*.pickle)" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/viewer/bookmarkmanager_ui.py:61 +msgid "Bookmark Manager" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/viewer/bookmarkmanager_ui.py:62 +msgid "Actions" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/viewer/bookmarkmanager_ui.py:63 +msgid "Edit" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/viewer/bookmarkmanager_ui.py:65 +msgid "Reset" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/viewer/bookmarkmanager_ui.py:66 +msgid "Export" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/viewer/bookmarkmanager_ui.py:67 +msgid "Import" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/viewer/config_ui.py:178 +msgid "Configure Ebook viewer" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/viewer/config_ui.py:179 +msgid "&Font options" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/viewer/config_ui.py:180 +msgid "Se&rif family:" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/viewer/config_ui.py:181 +msgid "&Sans family:" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/viewer/config_ui.py:182 +msgid "&Monospace family:" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/viewer/config_ui.py:183 +msgid "&Default font size:" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/viewer/config_ui.py:184 +#: /home/kovid/work/calibre/src/calibre/gui2/viewer/config_ui.py:186 +#: /home/kovid/work/calibre/src/calibre/gui2/viewer/config_ui.py:201 +msgid " px" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/viewer/config_ui.py:185 +msgid "Monospace &font size:" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/viewer/config_ui.py:187 +msgid "S&tandard font:" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/viewer/config_ui.py:188 +msgid "Serif" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/viewer/config_ui.py:189 +msgid "Sans-serif" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/viewer/config_ui.py:190 +msgid "Monospace" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/viewer/config_ui.py:191 +msgid "Remember last used &window size" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/viewer/config_ui.py:192 +msgid "Remember the ¤t page when quitting" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/viewer/config_ui.py:193 +msgid "H&yphenate (break line in the middle of large words)" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/viewer/config_ui.py:194 +msgid "" +"The default language to use for hyphenation rules. If the book does not " +"specify a language, this will be used." +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/viewer/config_ui.py:195 +msgid "Default &language for hyphenation:" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/viewer/config_ui.py:196 +msgid "&Resize images larger than the viewer window (needs restart)" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/viewer/config_ui.py:197 +msgid "Page flip &duration:" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/viewer/config_ui.py:198 +msgid "disabled" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/viewer/config_ui.py:200 +msgid "Mouse &wheel flips pages" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/viewer/config_ui.py:202 +msgid "Maximum &view width:" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/viewer/config_ui.py:203 +msgid "&General" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/viewer/config_ui.py:204 +msgid "Double click to change a keyboard shortcut" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/viewer/config_ui.py:205 +msgid "&Keyboard shortcuts" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/viewer/config_ui.py:206 +msgid "" +"

      A CSS stylesheet that can be used to control the look and feel of books. " +"For examples, click here." +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/viewer/config_ui.py:207 +msgid "User &Stylesheet" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/viewer/dictionary.py:53 +msgid "No results found for:" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/viewer/documentview.py:40 +msgid "Options to customize the ebook viewer" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/viewer/documentview.py:47 +#: /home/kovid/work/calibre/src/calibre/gui2/viewer/main.py:783 +msgid "Remember last used window size" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/viewer/documentview.py:49 +#: /home/kovid/work/calibre/src/calibre/gui2/viewer/documentview.py:96 +msgid "" +"Set the user CSS stylesheet. This can be used to customize the look of all " +"books." +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/viewer/documentview.py:51 +msgid "Maximum width of the viewer window, in pixels." +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/viewer/documentview.py:53 +msgid "Resize images larger than the viewer window to fit inside it" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/viewer/documentview.py:54 +msgid "Hyphenate text" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/viewer/documentview.py:56 +msgid "Default language for hyphenation rules" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/viewer/documentview.py:58 +msgid "Save the current position in the document, when quitting" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/viewer/documentview.py:60 +msgid "Have the mouse wheel turn pages" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/viewer/documentview.py:62 +msgid "" +"The time, in seconds, for the page flip animation. Default is half a second." +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/viewer/documentview.py:65 +msgid "Font options" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/viewer/documentview.py:67 +msgid "The serif font family" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/viewer/documentview.py:69 +msgid "The sans-serif font family" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/viewer/documentview.py:71 +msgid "The monospaced font family" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/viewer/documentview.py:72 +msgid "The standard font size in px" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/viewer/documentview.py:73 +msgid "The monospaced font size in px" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/viewer/documentview.py:74 +msgid "The standard font type" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/viewer/documentview.py:125 +msgid "Still editing" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/viewer/documentview.py:126 +msgid "" +"You are in the middle of editing a keyboard shortcut first complete that, by " +"clicking outside the shortcut editing box." +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/viewer/documentview.py:534 +msgid "&Lookup in dictionary" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/viewer/documentview.py:538 +#: /home/kovid/work/calibre/src/calibre/gui2/viewer/main.py:141 +msgid "Go to..." +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/viewer/documentview.py:550 +msgid "Next Section" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/viewer/documentview.py:551 +msgid "Previous Section" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/viewer/documentview.py:553 +msgid "Document Start" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/viewer/documentview.py:554 +msgid "Document End" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/viewer/documentview.py:556 +msgid "Section Start" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/viewer/documentview.py:557 +msgid "Section End" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/viewer/keys.py:12 +msgid "Scroll to the next page" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/viewer/keys.py:15 +msgid "Scroll to the previous page" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/viewer/keys.py:18 +msgid "Scroll to the next section" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/viewer/keys.py:21 +msgid "Scroll to the previous section" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/viewer/keys.py:24 +msgid "Scroll to the bottom of the section" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/viewer/keys.py:27 +msgid "Scroll to the top of the section" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/viewer/keys.py:30 +msgid "Scroll to the end of the document" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/viewer/keys.py:33 +msgid "Scroll to the start of the document" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/viewer/keys.py:36 +msgid "Scroll down" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/viewer/keys.py:39 +msgid "Scroll up" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/viewer/keys.py:42 +msgid "Scroll left" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/viewer/keys.py:45 +msgid "Scroll right" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/viewer/main.py:118 +msgid "Book format" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/viewer/main.py:198 +msgid "Position in book" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/viewer/main.py:205 +msgid "Go to a reference. To get reference numbers, use the reference mode." +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/viewer/main.py:213 +msgid "Search for text in book" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/viewer/main.py:292 +msgid "Print Preview" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/viewer/main.py:303 +msgid "Clear list of recently opened books" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/viewer/main.py:361 +msgid "Connecting to dict.org to lookup: %s…" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/viewer/main.py:467 +msgid "Choose ebook" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/viewer/main.py:468 +msgid "Ebooks" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/viewer/main.py:504 +msgid "No matches found for: %s" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/viewer/main.py:541 +msgid "Loading flow..." +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/viewer/main.py:579 +msgid "Laying out %s" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/viewer/main.py:610 +msgid "Bookmark #%d" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/viewer/main.py:614 +msgid "Add bookmark" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/viewer/main.py:615 +msgid "Enter title for bookmark:" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/viewer/main.py:625 +msgid "Manage Bookmarks" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/viewer/main.py:665 +msgid "Loading ebook..." +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/viewer/main.py:677 +msgid "Could not open ebook" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/viewer/main.py:770 +msgid "Options to control the ebook viewer" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/viewer/main.py:777 +msgid "" +"If specified, viewer window will try to come to the front when started." +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/viewer/main.py:780 +msgid "" +"If specified, viewer window will try to open full screen when started." +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/viewer/main.py:785 +msgid "Print javascript alert and console messages to the console" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/viewer/main.py:791 +msgid "" +"%prog [options] file\n" +"\n" +"View an ebook.\n" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/viewer/main_ui.py:189 +msgid "E-book Viewer" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/viewer/main_ui.py:190 +msgid "Close dictionary" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/viewer/main_ui.py:192 +msgid "toolBar" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/viewer/main_ui.py:195 +msgid "Next page" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/viewer/main_ui.py:196 +msgid "Previous page" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/viewer/main_ui.py:197 +msgid "Font size larger" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/viewer/main_ui.py:198 +msgid "Font size smaller" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/viewer/main_ui.py:202 +msgid "Find next" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/viewer/main_ui.py:203 +msgid "Find next occurrence" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/viewer/main_ui.py:207 +msgid "Reference Mode" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/viewer/main_ui.py:208 +msgid "Bookmark" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/viewer/main_ui.py:209 +msgid "Toggle full screen" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/viewer/main_ui.py:210 +msgid "Print" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/viewer/main_ui.py:211 +msgid "Find previous" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/viewer/main_ui.py:212 +msgid "Find previous occurrence" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/viewer/printing.py:114 +msgid "Print eBook" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/widgets.py:962 +msgid "Drag to resize" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/widgets.py:997 +msgid "Show" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/widgets.py:1004 +msgid "Hide" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/widgets.py:1041 +msgid "Toggle" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/wizard/__init__.py:438 +msgid "" +"Choose your e-book device. If your device is not in the list, choose a " +"\"%s\" device." +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/wizard/__init__.py:499 +msgid "Moving library..." +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/wizard/__init__.py:515 +#: /home/kovid/work/calibre/src/calibre/gui2/wizard/__init__.py:516 +msgid "Failed to move library" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/wizard/__init__.py:570 +msgid "Invalid database" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/wizard/__init__.py:571 +msgid "" +"

      An invalid library already exists at %s, delete it before trying to move " +"the existing library.
      Error: %s" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/wizard/__init__.py:582 +msgid "Could not move library" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/wizard/__init__.py:657 +msgid "Select location for books" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/wizard/__init__.py:671 +msgid "" +"You must choose an empty folder for the calibre library. %s is not empty." +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/wizard/__init__.py:745 +msgid "welcome wizard" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/wizard/device_ui.py:54 +#: /home/kovid/work/calibre/src/calibre/gui2/wizard/device_ui.py:55 +#: /home/kovid/work/calibre/src/calibre/gui2/wizard/finish_ui.py:47 +#: /home/kovid/work/calibre/src/calibre/gui2/wizard/kindle_ui.py:47 +#: /home/kovid/work/calibre/src/calibre/gui2/wizard/library_ui.py:55 +#: /home/kovid/work/calibre/src/calibre/gui2/wizard/stanza_ui.py:47 +msgid "Welcome to calibre" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/wizard/device_ui.py:56 +#: /home/kovid/work/calibre/src/calibre/gui2/wizard/finish_ui.py:48 +#: /home/kovid/work/calibre/src/calibre/gui2/wizard/kindle_ui.py:48 +#: /home/kovid/work/calibre/src/calibre/gui2/wizard/library_ui.py:56 +#: /home/kovid/work/calibre/src/calibre/gui2/wizard/stanza_ui.py:48 +msgid "The one stop solution to all your e-book needs." +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/wizard/device_ui.py:57 +msgid "&Manufacturers" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/wizard/device_ui.py:58 +msgid "&Devices" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/wizard/finish_ui.py:49 +msgid "" +"

      Congratulations!

      You have successfully setup calibre. Press the %s " +"button to apply your settings." +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/wizard/finish_ui.py:50 +msgid "" +"

      Demo videos

      Videos demonstrating the various features of calibre are " +"available online." +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/wizard/finish_ui.py:51 +msgid "" +"

      User Manual

      A User Manual is also available online." +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/wizard/kindle_ui.py:49 +msgid "" +"

      calibre can automatically send books by email to your Kindle. To do that " +"you have to setup email delivery below. The easiest way is to setup a free " +"gmail account and click the Use gmail " +"button below. You will also have to register your gmail address in your " +"Amazon account." +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/wizard/kindle_ui.py:50 +msgid "&Kindle email:" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/wizard/library_ui.py:57 +msgid "Choose your &language:" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/wizard/library_ui.py:58 +msgid "" +"

      Choose a location for your books. When you add books to calibre, they " +"will be copied here. Use an empty folder for a new calibre library:" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/wizard/library_ui.py:59 +msgid "&Change" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/wizard/library_ui.py:60 +msgid "" +"If you have an existing calibre library, it will be copied to the new " +"location. If a calibre library already exists at the new location, calibre " +"will switch to using it." +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/wizard/send_email.py:34 +msgid "Using: %s:%s@%s:%s and %s encryption" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/wizard/send_email.py:39 +msgid "Sending..." +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/wizard/send_email.py:44 +msgid "Mail successfully sent" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/wizard/send_email.py:96 +msgid "OK to proceed?" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/wizard/send_email.py:97 +msgid "" +"This will display your email password on the screen. Is it OK to proceed?" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/wizard/send_email.py:140 +msgid "" +"If you are setting up a new hotmail account, you must log in to it once " +"before you will be able to send mails." +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/wizard/send_email.py:151 +msgid "Setup sending email using" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/wizard/send_email.py:153 +msgid "" +"If you don't have an account, you can sign up for a free {name} email " +"account at http://{url}. {extra}" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/wizard/send_email.py:160 +msgid "Your %s &email address:" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/wizard/send_email.py:161 +msgid "Your %s &username:" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/wizard/send_email.py:162 +msgid "Your %s &password:" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/wizard/send_email.py:180 +msgid "" +"If you plan to use email to send books to your Kindle, remember to add the " +"your %s email address to the allowed email addresses in your Amazon.com " +"Kindle management page." +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/wizard/send_email.py:187 +msgid "Setup" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/wizard/send_email.py:202 +#: /home/kovid/work/calibre/src/calibre/gui2/wizard/send_email.py:213 +#: /home/kovid/work/calibre/src/calibre/gui2/wizard/send_email.py:218 +msgid "Bad configuration" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/wizard/send_email.py:203 +msgid "You must set the From email address" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/wizard/send_email.py:214 +msgid "" +"You must either set both the username and password for the mail " +"server or no username and no password at all." +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/wizard/send_email.py:219 +msgid "Please enter a username and password or set encryption to None " +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/wizard/send_email.py:224 +msgid "" +"No username and password set for mailserver. Most mailservers need a " +"username and password. Are you sure?" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/wizard/send_email_ui.py:124 +msgid "Send email &from:" +msgstr "" + +#: /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 "" + +#: /home/kovid/work/calibre/src/calibre/gui2/wizard/send_email_ui.py:126 +msgid "" +"

      A mail server is useful if the service you are sending mail to only " +"accepts email from well know mail services." +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/wizard/send_email_ui.py:127 +msgid "Mail &Server" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/wizard/send_email_ui.py:128 +msgid "calibre can optionally use a server to send mail" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/wizard/send_email_ui.py:129 +msgid "&Hostname:" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/wizard/send_email_ui.py:130 +msgid "The hostname of your mail server. For e.g. smtp.gmail.com" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/wizard/send_email_ui.py:131 +msgid "&Port:" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/wizard/send_email_ui.py:132 +msgid "" +"The port your mail server listens for connections on. The default is 25" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/wizard/send_email_ui.py:134 +msgid "Your username on the mail server" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/wizard/send_email_ui.py:136 +msgid "Your password on the mail server" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/wizard/send_email_ui.py:137 +msgid "&Show" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/wizard/send_email_ui.py:138 +msgid "&Encryption:" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/wizard/send_email_ui.py:139 +msgid "" +"Use TLS encryption when connecting to the mail server. This is the most " +"common." +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/wizard/send_email_ui.py:140 +msgid "&TLS" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/wizard/send_email_ui.py:141 +msgid "Use SSL encryption when connecting to the mail server." +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/wizard/send_email_ui.py:142 +msgid "&SSL" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/wizard/send_email_ui.py:143 +msgid "WARNING: Using no encryption is highly insecure" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/wizard/send_email_ui.py:144 +msgid "&None" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/wizard/send_email_ui.py:145 +msgid "Use Gmail" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/wizard/send_email_ui.py:146 +msgid "Use Hotmail" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/wizard/send_email_ui.py:147 +msgid "&Test email" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/wizard/stanza_ui.py:49 +msgid "" +"

      If you use the Stanza e-" +"book app on your iPhone/iTouch, you can access your calibre book collection " +"directly on the device. To do this you have to turn on the calibre content " +"server." +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/wizard/stanza_ui.py:50 +msgid "Turn on the &content server" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/library/caches.py:161 +#: /home/kovid/work/calibre/src/calibre/library/caches.py:562 +#: /home/kovid/work/calibre/src/calibre/library/caches.py:576 +#: /home/kovid/work/calibre/src/calibre/library/caches.py:586 +msgid "checked" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/library/caches.py:161 +#: /home/kovid/work/calibre/src/calibre/library/caches.py:562 +#: /home/kovid/work/calibre/src/calibre/library/caches.py:576 +#: /home/kovid/work/calibre/src/calibre/library/caches.py:586 +#: /home/kovid/work/calibre/src/calibre/library/save_to_disk.py:214 +msgid "yes" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/library/caches.py:163 +#: /home/kovid/work/calibre/src/calibre/library/caches.py:561 +#: /home/kovid/work/calibre/src/calibre/library/caches.py:573 +#: /home/kovid/work/calibre/src/calibre/library/caches.py:583 +msgid "unchecked" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/library/caches.py:163 +#: /home/kovid/work/calibre/src/calibre/library/caches.py:561 +#: /home/kovid/work/calibre/src/calibre/library/caches.py:573 +#: /home/kovid/work/calibre/src/calibre/library/caches.py:583 +#: /home/kovid/work/calibre/src/calibre/library/save_to_disk.py:214 +msgid "no" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/library/caches.py:356 +msgid "today" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/library/caches.py:359 +msgid "yesterday" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/library/caches.py:362 +msgid "thismonth" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/library/caches.py:365 +#: /home/kovid/work/calibre/src/calibre/library/caches.py:366 +msgid "daysago" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/library/caches.py:563 +#: /home/kovid/work/calibre/src/calibre/library/caches.py:580 +msgid "blank" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/library/caches.py:563 +#: /home/kovid/work/calibre/src/calibre/library/caches.py:580 +msgid "empty" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/library/caches.py:564 +msgid "Invalid boolean query \"{0}\"" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/library/catalog.py:55 +msgid "" +"The fields to output when cataloging books in the database. Should be a " +"comma-separated list of fields.\n" +"Available fields: %s,\n" +"plus user-created custom fields.\n" +"Example: %s=title,authors,tags\n" +"Default: '%%default'\n" +"Applies to: CSV, XML output formats" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/library/catalog.py:68 +msgid "" +"Output field to sort on.\n" +"Available fields: author_sort, id, rating, size, timestamp, title_sort\n" +"Default: '%default'\n" +"Applies to: CSV, XML output formats" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/library/catalog.py:242 +msgid "" +"The fields to output when cataloging books in the database. Should be a " +"comma-separated list of fields.\n" +"Available fields: %s.\n" +"plus user-created custom fields.\n" +"Example: %s=title,authors,tags\n" +"Default: '%%default'\n" +"Applies to: BIBTEX output format" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/library/catalog.py:255 +msgid "" +"Output field to sort on.\n" +"Available fields: author_sort, id, rating, size, timestamp, title.\n" +"Default: '%default'\n" +"Applies to: BIBTEX output format" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/library/catalog.py:264 +msgid "" +"Create a citation for BibTeX entries.\n" +"Boolean value: True, False\n" +"Default: '%default'\n" +"Applies to: BIBTEX output format" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/library/catalog.py:273 +msgid "" +"Create a file entry if formats is selected for BibTeX entries.\n" +"Boolean value: True, False\n" +"Default: '%default'\n" +"Applies to: BIBTEX output format" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/library/catalog.py:282 +msgid "" +"The template for citation creation from database fields.\n" +"Should be a template with {} enclosed fields.\n" +"Available fields: %s.\n" +"Default: '%%default'\n" +"Applies to: BIBTEX output format" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/library/catalog.py:292 +msgid "" +"BibTeX file encoding output.\n" +"Available types: utf8, cp1252, ascii.\n" +"Default: '%default'\n" +"Applies to: BIBTEX output format" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/library/catalog.py:301 +msgid "" +"BibTeX file encoding flag.\n" +"Available types: strict, replace, ignore, backslashreplace.\n" +"Default: '%default'\n" +"Applies to: BIBTEX output format" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/library/catalog.py:310 +msgid "" +"Entry type for BibTeX catalog.\n" +"Available types: book, misc, mixed.\n" +"Default: '%default'\n" +"Applies to: BIBTEX output format" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/library/catalog.py:616 +msgid "" +"Title of generated catalog used as title in metadata.\n" +"Default: '%default'\n" +"Applies to: ePub, MOBI output formats" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/library/catalog.py:623 +msgid "" +"Save the output from different stages of the conversion pipeline to the " +"specified directory. Useful if you are unsure at which stage of the " +"conversion process a bug is occurring.\n" +"Default: '%default'\n" +"Applies to: ePub, MOBI output formats" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/library/catalog.py:633 +msgid "" +"field:pattern specifying custom field/contents indicating book should be " +"excluded.\n" +"Default: '%default'\n" +"Applies to ePub, MOBI output formats" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/library/catalog.py:640 +msgid "" +"Regex describing tags to exclude as genres.\n" +"Default: '%default' excludes bracketed tags, e.g. '[]'\n" +"Applies to: ePub, MOBI output formats" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/library/catalog.py:646 +msgid "" +"Comma-separated list of tag words indicating book should be excluded from " +"output.For example: 'skip' will match 'skip this book' and 'Skip will like " +"this'.Default: '%default'\n" +"Applies to: ePub, MOBI output formats" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/library/catalog.py:654 +msgid "" +"Include 'Authors' section in catalog.\n" +"Default: '%default'\n" +"Applies to: ePub, MOBI output formats" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/library/catalog.py:661 +msgid "" +"Include 'Descriptions' section in catalog.\n" +"Default: '%default'\n" +"Applies to: ePub, MOBI output formats" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/library/catalog.py:668 +msgid "" +"Include 'Genres' section in catalog.\n" +"Default: '%default'\n" +"Applies to: ePub, MOBI output formats" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/library/catalog.py:675 +msgid "" +"Include 'Titles' section in catalog.\n" +"Default: '%default'\n" +"Applies to: ePub, MOBI output formats" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/library/catalog.py:682 +msgid "" +"Include 'Series' section in catalog.\n" +"Default: '%default'\n" +"Applies to: ePub, MOBI output formats" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/library/catalog.py:689 +msgid "" +"Include 'Recently Added' section in catalog.\n" +"Default: '%default'\n" +"Applies to: ePub, MOBI output formats" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/library/catalog.py:696 +msgid "" +"Custom field containing note text to insert in Description header.\n" +"Default: '%default'\n" +"Applies to: ePub, MOBI output formats" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/library/catalog.py:703 +msgid "" +":[before|after]:[True|False] specifying:\n" +" Custom field containing notes to merge with Comments\n" +" [before|after] Placement of notes with respect to Comments\n" +" [True|False] - A horizontal rule is inserted between notes and Comments\n" +"Default: '%default'\n" +"Applies to ePub, MOBI output formats" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/library/catalog.py:713 +msgid "" +"Specifies the output profile. In some cases, an output profile is required " +"to optimize the catalog for the device. For example, 'kindle' or " +"'kindle_dx' creates a structured Table of Contents with Sections and " +"Articles.\n" +"Default: '%default'\n" +"Applies to: ePub, MOBI output formats" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/library/catalog.py:720 +msgid "" +"field:pattern indicating book has been read.\n" +"Default: '%default'\n" +"Applies to ePub, MOBI output formats" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/library/catalog.py:726 +msgid "" +"Size hint (in inches) for book covers in catalog.\n" +"Range: 1.0 - 2.0\n" +"Default: '%default'\n" +"Applies to ePub, MOBI output formats" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/library/catalog.py:734 +msgid "" +"Tag indicating book to be displayed as wishlist item.\n" +"Default: '%default'\n" +"Applies to: ePub, MOBI output formats" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/library/catalog.py:1418 +msgid "No enabled genres found to catalog.\n" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/library/catalog.py:1422 +msgid "No books available to catalog" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/library/catalog.py:1497 +msgid "" +"Inconsistent Author Sort values for\n" +"Author '{0}':\n" +"'{1}' <> '{2}'\n" +"Unable to build MOBI catalog.\n" +"\n" +"Select all books by '{0}', apply correct Author Sort value in Edit Metadata " +"dialog, then rebuild the catalog.\n" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/library/catalog.py:1514 +msgid "" +"Warning: inconsistent Author Sort values for\n" +"Author '{0}':\n" +"'{1}' <> '{2}'\n" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/library/catalog.py:1710 +msgid "" +"No books found to catalog.\n" +"Check 'Excluded books' criteria in E-book options.\n" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/library/catalog.py:1712 +msgid "No books available to include in catalog" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/library/catalog.py:5042 +msgid "" +"\n" +"*** Adding 'By Authors' Section required for MOBI output ***" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/library/check_library.py:26 +msgid "Invalid titles" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/library/check_library.py:27 +msgid "Extra titles" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/library/check_library.py:28 +msgid "Invalid authors" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/library/check_library.py:29 +msgid "Extra authors" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/library/check_library.py:30 +msgid "Missing book formats" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/library/check_library.py:31 +msgid "Extra book formats" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/library/check_library.py:32 +msgid "Unknown files in books" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/library/check_library.py:33 +msgid "Missing covers files" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/library/check_library.py:34 +msgid "Cover files not in database" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/library/check_library.py:35 +msgid "Folders raising exception" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/library/cli.py:43 +msgid "" +"Path to the calibre library. Default is to use the path stored in the " +"settings." +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/library/cli.py:124 +msgid "" +"%prog list [options]\n" +"\n" +"List the books available in the calibre database.\n" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/library/cli.py:132 +msgid "" +"The fields to display when listing books in the database. Should be a comma " +"separated list of fields.\n" +"Available fields: %s\n" +"Default: %%default. The special field \"all\" can be used to select all " +"fields. Only has effect in the text output format." +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/library/cli.py:139 +msgid "" +"The field by which to sort the results.\n" +"Available fields: %s\n" +"Default: %%default" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/library/cli.py:141 +msgid "Sort results in ascending order" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/library/cli.py:143 +msgid "" +"Filter the results by the search query. For the format of the search query, " +"please see the search related documentation in the User Manual. Default is " +"to do no filtering." +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/library/cli.py:145 +#: /home/kovid/work/calibre/src/calibre/library/cli.py:1044 +msgid "" +"The maximum width of a single line in the output. Defaults to detecting " +"screen size." +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/library/cli.py:146 +msgid "The string used to separate fields. Default is a space." +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/library/cli.py:147 +msgid "" +"The prefix for all file paths. Default is the absolute path to the library " +"folder." +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/library/cli.py:169 +msgid "Invalid fields. Available fields:" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/library/cli.py:176 +msgid "Invalid sort field. Available fields:" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/library/cli.py:248 +msgid "" +"The following books were not added as they already exist in the database " +"(see --duplicates option):" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/library/cli.py:272 +msgid "" +"%prog add [options] file1 file2 file3 ...\n" +"\n" +"Add the specified files as books to the database. You can also specify " +"directories, see\n" +"the directory related options below.\n" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/library/cli.py:281 +msgid "" +"Assume that each directory has only a single logical book and that all files " +"in it are different e-book formats of that book" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/library/cli.py:283 +msgid "Process directories recursively" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/library/cli.py:285 +msgid "" +"Add books to database even if they already exist. Comparison is done based " +"on book titles." +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/library/cli.py:287 +msgid "Add an empty book (a book with no formats)" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/library/cli.py:289 +msgid "Set the title of the added empty book" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/library/cli.py:291 +msgid "Set the authors of the added empty book" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/library/cli.py:293 +msgid "Set the ISBN of the added empty book" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/library/cli.py:319 +msgid "You must specify at least one file to add" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/library/cli.py:336 +msgid "" +"%prog remove ids\n" +"\n" +"Remove the books identified by ids from the database. ids should be a comma " +"separated list of id numbers (you can get id numbers by using the list " +"command). For example, 23,34,57-85\n" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/library/cli.py:351 +msgid "You must specify at least one book to remove" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/library/cli.py:370 +msgid "" +"%prog add_format [options] id ebook_file\n" +"\n" +"Add the ebook in ebook_file to the available formats for the logical book " +"identified by id. You can get id by using the list command. If the format " +"already exists, it is replaced.\n" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/library/cli.py:385 +msgid "You must specify an id and an ebook file" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/library/cli.py:390 +msgid "ebook file must have an extension" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/library/cli.py:398 +msgid "" +"\n" +"%prog remove_format [options] id fmt\n" +"\n" +"Remove the format fmt from the logical book identified by id. You can get id " +"by using the list command. fmt should be a file extension like LRF or TXT or " +"EPUB. If the logical book does not have fmt available, do nothing.\n" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/library/cli.py:415 +msgid "You must specify an id and a format" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/library/cli.py:433 +msgid "" +"\n" +"%prog show_metadata [options] id\n" +"\n" +"Show the metadata stored in the calibre database for the book identified by " +"id.\n" +"id is an id number from the list command.\n" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/library/cli.py:441 +msgid "Print metadata in OPF form (XML)" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/library/cli.py:450 +msgid "You must specify an id" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/library/cli.py:465 +msgid "" +"\n" +"%prog set_metadata [options] id /path/to/metadata.opf\n" +"\n" +"Set the metadata stored in the calibre database for the book identified by " +"id\n" +"from the OPF file metadata.opf. id is an id number from the list command. " +"You\n" +"can get a quick feel for the OPF format by using the --as-opf switch to the\n" +"show_metadata command.\n" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/library/cli.py:481 +msgid "You must specify an id and a metadata file" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/library/cli.py:501 +msgid "" +"%prog export [options] ids\n" +"\n" +"Export the books specified by ids (a comma separated list) to the " +"filesystem.\n" +"The export operation saves all formats of the book, its cover and metadata " +"(in\n" +"an opf file). You can get id numbers from the list command.\n" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/library/cli.py:509 +msgid "Export all books in database, ignoring the list of ids." +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/library/cli.py:511 +msgid "Export books to the specified directory. Default is" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/library/cli.py:513 +msgid "Export all books into a single directory" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/library/cli.py:520 +msgid "Specifying this switch will turn this behavior off." +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/library/cli.py:543 +msgid "You must specify some ids or the %s option" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/library/cli.py:556 +msgid "" +"%prog add_custom_column [options] label name datatype\n" +"\n" +"Create a custom column. label is the machine friendly name of the column. " +"Should\n" +"not contain spaces or colons. name is the human friendly name of the " +"column.\n" +"datatype is one of: {0}\n" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/library/cli.py:565 +msgid "" +"This column stores tag like data (i.e. multiple comma separated values). " +"Only applies if datatype is text." +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/library/cli.py:569 +msgid "" +"A dictionary of options to customize how the data in this column will be " +"interpreted. This is a JSON string. For enumeration columns, use --" +"display='{\"enum_values\":[\"val1\", \"val2\"]}'" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/library/cli.py:583 +msgid "You must specify label, name and datatype" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/library/cli.py:644 +msgid "" +"\n" +" %prog catalog /path/to/destination.(CSV|EPUB|MOBI|XML ...) [options]\n" +"\n" +" Export a catalog in format specified by path/to/destination extension.\n" +" Options control how entries are displayed in the generated catalog " +"ouput.\n" +" " +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/library/cli.py:658 +msgid "" +"Comma-separated list of database IDs to catalog.\n" +"If declared, --search is ignored.\n" +"Default: all" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/library/cli.py:662 +msgid "" +"Filter the results by the search query. For the format of the search query, " +"please see the search-related documentation in the User Manual.\n" +"Default: no filtering" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/library/cli.py:668 +#: /home/kovid/work/calibre/src/calibre/web/fetch/simple.py:499 +msgid "Show detailed output information. Useful for debugging" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/library/cli.py:681 +msgid "Error: You must specify a catalog output file" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/library/cli.py:727 +msgid "" +"\n" +" %prog set_custom [options] column id value\n" +"\n" +" Set the value of a custom column for the book identified by id.\n" +" You can get a list of ids using the list command.\n" +" You can get a list of custom column names using the custom_columns\n" +" command.\n" +" " +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/library/cli.py:738 +msgid "" +"If the column stores multiple values, append the specified values to the " +"existing ones, instead of replacing them." +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/library/cli.py:749 +msgid "Error: You must specify a field name, id and value" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/library/cli.py:768 +msgid "" +"\n" +" %prog custom_columns [options]\n" +"\n" +" List available custom columns. Shows column labels and ids.\n" +" " +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/library/cli.py:775 +msgid "Show details for each column." +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/library/cli.py:787 +msgid "You will lose all data in the column: %r. Are you sure (y/n)? " +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/library/cli.py:789 +msgid "y" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/library/cli.py:795 +msgid "" +"\n" +" %prog remove_custom_column [options] label\n" +"\n" +" Remove the custom column identified by label. You can see available\n" +" columns with the custom_columns command.\n" +" " +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/library/cli.py:803 +msgid "Do not ask for confirmation" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/library/cli.py:813 +msgid "Error: You must specify a column label" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/library/cli.py:823 +msgid "" +"\n" +" %prog saved_searches [options] list\n" +" %prog saved_searches add name search\n" +" %prog saved_searches remove name\n" +"\n" +" Manage the saved searches stored in this database.\n" +" If you try to add a query with a name that already exists, it will be\n" +" replaced.\n" +" " +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/library/cli.py:841 +msgid "Error: You must specify an action (add|remove|list)" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/library/cli.py:849 +msgid "Name:" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/library/cli.py:850 +msgid "Search string:" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/library/cli.py:856 +msgid "Error: You must specify a name and a search string" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/library/cli.py:859 +msgid "added" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/library/cli.py:864 +msgid "Error: You must specify a name" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/library/cli.py:867 +msgid "removed" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/library/cli.py:871 +msgid "Error: Action %s not recognized, must be one of: (add|remove|list)" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/library/cli.py:879 +msgid "" +"%prog check_library [options]\n" +"\n" +"Perform some checks on the filesystem representing a library. Reports are " +"{0}\n" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/library/cli.py:886 +#: /home/kovid/work/calibre/src/calibre/library/cli.py:1036 +msgid "Output in CSV" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/library/cli.py:889 +msgid "" +"Comma-separated list of reports.\n" +"Default: all" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/library/cli.py:893 +msgid "" +"Comma-separated list of extensions to ignore.\n" +"Default: all" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/library/cli.py:897 +msgid "" +"Comma-separated list of names to ignore.\n" +"Default: all" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/library/cli.py:927 +msgid "Unknown report check" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/library/cli.py:960 +msgid "" +"%prog restore_database [options]\n" +"\n" +"Restore this database from the metadata stored in OPF files in each\n" +"directory of the calibre library. This is useful if your metadata.db file\n" +"has been corrupted.\n" +"\n" +"WARNING: This command completely regenerates your database. You will lose\n" +"all saved searches, user categories, plugboards, stored per-book conversion\n" +"settings, and custom recipes. Restored metadata will only be as accurate as\n" +"what is found in the OPF files.\n" +" " +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/library/cli.py:975 +msgid "" +"Really do the recovery. The command will not run unless this option is " +"specified." +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/library/cli.py:988 +msgid "You must provide the %s option to do a recovery" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/library/cli.py:1025 +msgid "" +"%prog list_categories [options]\n" +"\n" +"Produce a report of the category information in the database. The\n" +"information is the equivalent of what is shown in the tags pane.\n" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/library/cli.py:1033 +msgid "" +"Output only the number of items in a category instead of the counts per item " +"within the category" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/library/cli.py:1038 +msgid "" +"The character to put around the category value in CSV mode. Default is " +"quotes (\")." +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/library/cli.py:1041 +msgid "" +"Comma-separated list of category lookup names.\n" +"Default: all" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/library/cli.py:1047 +msgid "The string used to separate fields in CSV mode. Default is a comma." +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/library/cli.py:1085 +msgid "CATEGORY ITEMS" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/library/cli.py:1157 +msgid "" +"%%prog command [options] [arguments]\n" +"\n" +"%%prog is the command line interface to the calibre books database.\n" +"\n" +"command is one of:\n" +" %s\n" +"\n" +"For help on an individual command: %%prog command --help\n" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/library/custom_columns.py:594 +msgid "No label was provided" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/library/custom_columns.py:596 +msgid "" +"The label must contain only lower case letters, digits and underscores, and " +"start with a letter" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/library/database2.py:64 +msgid "%sAverage rating is %3.1f" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/library/database2.py:1017 +msgid "Main" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/library/database2.py:3105 +msgid "

      Migrating old database to ebook library in %s

      " +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/library/database2.py:3134 +msgid "Copying %s" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/library/database2.py:3151 +msgid "Compacting database" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/library/field_metadata.py:148 +msgid "Ratings" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/library/field_metadata.py:181 +msgid "Identifiers" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/library/field_metadata.py:191 +msgid "Author Sort" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/library/field_metadata.py:301 +msgid "Title Sort" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/library/restore.py:126 +msgid "Processed" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/library/restore.py:192 +msgid "creating custom column " +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/library/save_to_disk.py:32 +msgid "The title" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/library/save_to_disk.py:33 +msgid "The authors" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/library/save_to_disk.py:34 +msgid "" +"The author sort string. To use only the first letter of the name use " +"{author_sort[0]}" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/library/save_to_disk.py:36 +msgid "The tags" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/library/save_to_disk.py:37 +msgid "The series" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/library/save_to_disk.py:38 +msgid "" +"The series number. To get leading zeros use {series_index:0>3s} or " +"{series_index:>3s} for leading spaces" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/library/save_to_disk.py:41 +msgid "The rating" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/library/save_to_disk.py:42 +msgid "The ISBN" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/library/save_to_disk.py:43 +msgid "The publisher" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/library/save_to_disk.py:44 +msgid "The date" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/library/save_to_disk.py:45 +msgid "The published date" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/library/save_to_disk.py:46 +msgid "The date when the metadata for this book record was last modified" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/library/save_to_disk.py:48 +msgid "The calibre internal id" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/library/save_to_disk.py:76 +msgid "Options to control saving to disk" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/library/save_to_disk.py:82 +msgid "" +"Normally, calibre will update the metadata in the saved files from what is " +"in the calibre library. Makes saving to disk slower." +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/library/save_to_disk.py:85 +msgid "" +"Normally, calibre will write the metadata into a separate OPF file along " +"with the actual e-book files." +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/library/save_to_disk.py:88 +msgid "" +"Normally, calibre will save the cover in a separate file along with the " +"actual e-book file(s)." +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/library/save_to_disk.py:91 +msgid "" +"Comma separated list of formats to save for each book. By default all " +"available formats are saved." +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/library/save_to_disk.py:94 +msgid "" +"The template to control the filename and directory structure of the saved " +"files. Default is \"%s\" which will save books into a per-author " +"subdirectory with filenames containing title and author. Available controls " +"are: {%s}" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/library/save_to_disk.py:99 +msgid "" +"The template to control the filename and directory structure of files sent " +"to the device. Default is \"%s\" which will save books into a per-author " +"directory with filenames containing title and author. Available controls " +"are: {%s}" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/library/save_to_disk.py:106 +msgid "" +"Normally, calibre will convert all non English characters into English " +"equivalents for the file names. WARNING: If you turn this off, you may " +"experience errors when saving, depending on how well the filesystem you are " +"saving to supports unicode." +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/library/save_to_disk.py:112 +#: /home/kovid/work/calibre/src/calibre/library/save_to_disk.py:115 +msgid "" +"The format in which to display dates. %d - day, %b - month, %Y - year. " +"Default is: %b, %Y" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/library/save_to_disk.py:118 +msgid "Convert paths to lowercase." +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/library/save_to_disk.py:120 +msgid "Replace whitespace with underscores." +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/library/save_to_disk.py:370 +#: /home/kovid/work/calibre/src/calibre/library/save_to_disk.py:398 +msgid "Requested formats not available" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/library/server/__init__.py:21 +msgid "Settings to control the calibre content server" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/library/server/__init__.py:25 +msgid "The port on which to listen. Default is %default" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/library/server/__init__.py:27 +msgid "The server timeout in seconds. Default is %default" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/library/server/__init__.py:29 +msgid "The max number of worker threads to use. Default is %default" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/library/server/__init__.py:31 +msgid "Set a password to restrict access. By default access is unrestricted." +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/library/server/__init__.py:33 +msgid "Username for access. By default, it is: %default" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/library/server/__init__.py:37 +msgid "The maximum size for displayed covers. Default is %default." +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/library/server/__init__.py:39 +msgid "" +"The maximum number of matches to return per OPDS query. This affects Stanza, " +"WordPlayer, etc. integration." +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/library/server/__init__.py:43 +msgid "" +"Group items in categories such as author/tags by first letter when there are " +"more than this number of items. Default: %default. Set to a large number to " +"disable grouping." +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/library/server/__init__.py:48 +msgid "" +"Prefix to prepend to all URLs. Useful for reverseproxying to this server " +"from Apache/nginx/etc." +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/library/server/browse.py:64 +#: /home/kovid/work/calibre/src/calibre/library/server/browse.py:498 +msgid "Loading, please wait" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/library/server/browse.py:90 +#: /home/kovid/work/calibre/src/calibre/library/server/browse.py:111 +msgid "Go to" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/library/server/browse.py:106 +msgid "First" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/library/server/browse.py:106 +msgid "Last" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/library/server/browse.py:109 +msgid "Browsing %d books" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/library/server/browse.py:126 +#: /home/kovid/work/calibre/src/calibre/library/server/browse.py:255 +msgid "Average rating" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/library/server/browse.py:127 +msgid "%s: %.1f stars" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/library/server/browse.py:163 +msgid "%d stars" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/library/server/browse.py:256 +msgid "Popularity" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/library/server/browse.py:275 +msgid "Sort by" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/library/server/browse.py:278 +msgid "library" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/library/server/browse.py:279 +msgid "home" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/library/server/browse.py:340 +#: /home/kovid/work/calibre/src/calibre/library/server/browse.py:612 +#: /home/kovid/work/calibre/src/calibre/library/server/opds.py:573 +msgid "Newest" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/library/server/browse.py:341 +#: /home/kovid/work/calibre/src/calibre/library/server/browse.py:613 +msgid "All books" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/library/server/browse.py:386 +#: /home/kovid/work/calibre/src/calibre/library/server/browse.py:451 +msgid "Browse books by" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/library/server/browse.py:391 +msgid "Choose a category to browse by:" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/library/server/browse.py:521 +msgid "Browsing by" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/library/server/browse.py:522 +msgid "Up" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/library/server/browse.py:648 +msgid "in" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/library/server/browse.py:651 +msgid "Books in" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/library/server/browse.py:740 +msgid "Other formats" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/library/server/browse.py:747 +msgid "Read %(title)s in the %(fmt)s format" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/library/server/browse.py:752 +msgid "Get" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/library/server/browse.py:765 +msgid "Details" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/library/server/browse.py:767 +msgid "Permalink" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/library/server/browse.py:768 +msgid "A permanent link to this book" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/library/server/browse.py:779 +msgid "This book has been deleted" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/library/server/browse.py:865 +msgid "in search" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/library/server/browse.py:867 +msgid "Matching books" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/library/server/main.py:39 +msgid "" +"[options]\n" +"\n" +"Start the calibre content server. The calibre content server\n" +"exposes your calibre library over the internet. The default interface\n" +"allows you to browse you calibre library by categories. You can also\n" +"access an interface optimized for mobile browsers at /mobile and an\n" +"OPDS based interface for use with reading applications at /opds.\n" +"\n" +"The OPDS interface is advertised via BonJour automatically.\n" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/library/server/main.py:52 +msgid "Path to the library folder to serve with the content server" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/library/server/main.py:54 +msgid "Write process PID to the specified file" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/library/server/main.py:58 +msgid "" +"Specifies a restriction to be used for this invocation. This option " +"overrides any per-library settings specified in the GUI" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/library/server/main.py:62 +msgid "" +"Auto reload server when source code changes. May not work in all " +"environments." +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/library/server/opds.py:125 +msgid "%d book" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/library/server/opds.py:148 +msgid "%d items" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/library/server/opds.py:166 +msgid "RATING: %s
      " +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/library/server/opds.py:169 +msgid "TAGS: %s
      " +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/library/server/opds.py:174 +msgid "SERIES: %s [%s]
      " +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/library/server/opds.py:267 +msgid "Books in your library" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/library/server/opds.py:273 +msgid "By " +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/library/server/opds.py:274 +msgid "Books sorted by " +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/utils/config.py:34 +msgid "%sUsage%s: %s\n" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/utils/config.py:85 +msgid "Created by " +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/utils/config.py:86 +msgid "" +"Whenever you pass arguments to %prog that have spaces in them, enclose the " +"arguments in quotation marks." +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/utils/config_base.py:375 +msgid "Path to the database in which books are stored" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/utils/config_base.py:377 +msgid "Pattern to guess metadata from filenames" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/utils/config_base.py:379 +msgid "Access key for isbndb.com" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/utils/config_base.py:381 +msgid "Default timeout for network operations (seconds)" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/utils/config_base.py:383 +msgid "Path to directory in which your library of books is stored" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/utils/config_base.py:385 +msgid "The language in which to display the user interface" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/utils/config_base.py:387 +msgid "The default output format for ebook conversions." +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/utils/config_base.py:391 +msgid "Ordered list of formats to prefer for input." +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/utils/config_base.py:393 +msgid "Read metadata from files" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/utils/config_base.py:395 +msgid "" +"The priority of worker processes. A higher priority means they run faster " +"and consume more resources. Most tasks like conversion/news download/adding " +"books/etc. are affected by this setting." +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/utils/config_base.py:400 +msgid "Swap author first and last names when reading metadata" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/utils/config_base.py:402 +msgid "Add new formats to existing book records" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/utils/config_base.py:404 +msgid "Tags to apply to books added to the library" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/utils/config_base.py:408 +msgid "List of named saved searches" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/utils/config_base.py:409 +msgid "User-created tag browser categories" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/utils/config_base.py:411 +msgid "How and when calibre updates metadata on the device." +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/utils/config_base.py:413 +msgid "" +"When searching for text without using lookup prefixes, as for example, Red " +"instead of title:Red, limit the columns searched to those named below." +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/utils/config_base.py:418 +msgid "" +"Choose columns to be searched when not using prefixes, as for example, when " +"searching for Redd instead of title:Red. Enter a list of search/lookup names " +"separated by commas. Only takes effect if you set the option to limit search " +"columns above." +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/utils/formatter.py:27 +msgid "failed to scan program. Invalid input {0}" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/utils/formatter.py:32 +msgid " near " +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/utils/formatter.py:38 +msgid "end of program" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/utils/formatter.py:75 +msgid "syntax error - program ends before EOF" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/utils/formatter.py:101 +msgid "unknown id " +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/utils/formatter.py:107 +msgid "unknown function {0}" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/utils/formatter.py:126 +msgid "missing closing parenthesis" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/utils/formatter.py:145 +msgid "expression is not function or constant" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/utils/formatter.py:179 +msgid "format: type {0} requires an integer value, got {1}" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/utils/formatter.py:185 +msgid "format: type {0} requires a decimal (float) value, got {1}" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/utils/formatter.py:296 +msgid "%s: unknown function" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/utils/formatter.py:343 +msgid "No such variable " +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/utils/formatter_functions.py:58 +msgid "No documentation provided" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/utils/formatter_functions.py:79 +msgid "Exception " +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/utils/formatter_functions.py:97 +msgid "" +"strcmp(x, y, lt, eq, gt) -- does a case-insensitive comparison of x and y as " +"strings. Returns lt if x < y. Returns eq if x == y. Otherwise returns gt." +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/utils/formatter_functions.py:112 +msgid "" +"cmp(x, y, lt, eq, gt) -- compares x and y after converting both to numbers. " +"Returns lt if x < y. Returns eq if x == y. Otherwise returns gt." +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/utils/formatter_functions.py:127 +msgid "" +"strcat(a, b, ...) -- can take any number of arguments. Returns a string " +"formed by concatenating all the arguments" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/utils/formatter_functions.py:140 +msgid "" +"add(x, y) -- returns x + y. Throws an exception if either x or y are not " +"numbers." +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/utils/formatter_functions.py:150 +msgid "" +"subtract(x, y) -- returns x - y. Throws an exception if either x or y are " +"not numbers." +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/utils/formatter_functions.py:160 +msgid "" +"multiply(x, y) -- returns x * y. Throws an exception if either x or y are " +"not numbers." +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/utils/formatter_functions.py:170 +msgid "" +"divide(x, y) -- returns x / y. Throws an exception if either x or y are not " +"numbers." +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/utils/formatter_functions.py:180 +msgid "" +"template(x) -- evaluates x as a template. The evaluation is done in its own " +"context, meaning that variables are not shared between the caller and the " +"template evaluation. Because the { and } characters are special, you must " +"use [[ for the { character and ]] for the } character; they are converted " +"automatically. For example, template('[[title_sort]]') will evaluate the " +"template {title_sort} and return its value." +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/utils/formatter_functions.py:195 +msgid "" +"eval(template) -- evaluates the template, passing the local variables (those " +"'assign'ed to) instead of the book metadata. This permits using the " +"template processor to construct complex results from local variables." +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/utils/formatter_functions.py:208 +msgid "" +"assign(id, val) -- assigns val to id, then returns val. id must be an " +"identifier, not an expression" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/utils/formatter_functions.py:218 +msgid "" +"print(a, b, ...) -- prints the arguments to standard output. Unless you " +"start calibre from the command line (calibre-debug -g), the output will go " +"to a black hole." +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/utils/formatter_functions.py:229 +msgid "field(name) -- returns the metadata field named by name" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/utils/formatter_functions.py:237 +msgid "" +"raw_field(name) -- returns the metadata field named by name without applying " +"any formatting." +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/utils/formatter_functions.py:246 +msgid "" +"substr(str, start, end) -- returns the start'th through the end'th " +"characters of str. The first character in str is the zero'th character. If " +"end is negative, then it indicates that many characters counting from the " +"right. If end is zero, then it indicates the last character. For example, " +"substr('12345', 1, 0) returns '2345', and substr('12345', 1, -1) returns " +"'234'." +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/utils/formatter_functions.py:259 +msgid "" +"lookup(val, pattern, field, pattern, field, ..., else_field) -- like switch, " +"except the arguments are field (metadata) names, not text. The value of the " +"appropriate field will be fetched and used. Note that because composite " +"columns are fields, you can use this function in one composite field to use " +"the value of some other composite field. This is extremely useful when " +"constructing variable save paths" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/utils/formatter_functions.py:274 +msgid "lookup requires either 2 or an odd number of arguments" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/utils/formatter_functions.py:286 +msgid "" +"test(val, text if not empty, text if empty) -- return `text if not empty` if " +"the field is not empty, otherwise return `text if empty`" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/utils/formatter_functions.py:298 +msgid "" +"contains(val, pattern, text if match, text if not match) -- checks if field " +"contains matches for the regular expression `pattern`. Returns `text if " +"match` if matches are found, otherwise it returns `text if no match`" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/utils/formatter_functions.py:313 +msgid "" +"switch(val, pattern, value, pattern, value, ..., else_value) -- for each " +"`pattern, value` pair, checks if the field matches the regular expression " +"`pattern` and if so, returns that `value`. If no pattern matches, then " +"else_value is returned. You can have as many `pattern, value` pairs as you " +"want" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/utils/formatter_functions.py:321 +msgid "switch requires an odd number of arguments" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/utils/formatter_functions.py:333 +msgid "" +"re(val, pattern, replacement) -- return the field after applying the regular " +"expression. All instances of `pattern` are replaced with `replacement`. As " +"in all of calibre, these are python-compatible regular expressions" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/utils/formatter_functions.py:344 +msgid "" +"ifempty(val, text if empty) -- return val if val is not empty, otherwise " +"return `text if empty`" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/utils/formatter_functions.py:356 +msgid "" +"shorten(val, left chars, middle text, right chars) -- Return a shortened " +"version of the field, consisting of `left chars` characters from the " +"beginning of the field, followed by `middle text`, followed by `right chars` " +"characters from the end of the string. `Left chars` and `right chars` must " +"be integers. For example, assume the title of the book is `Ancient English " +"Laws in the Times of Ivanhoe`, and you want it to fit in a space of at most " +"15 characters. If you use {title:shorten(9,-,5)}, the result will be " +"`Ancient E-nhoe`. If the field's length is less than left chars + right " +"chars + the length of `middle text`, then the field will be used intact. For " +"example, the title `The Dome` would not be changed." +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/utils/formatter_functions.py:381 +msgid "" +"count(val, separator) -- interprets the value as a list of items separated " +"by `separator`, returning the number of items in the list. Most lists use a " +"comma as the separator, but authors uses an ampersand. Examples: " +"{tags:count(,)}, {authors:count(&)}" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/utils/formatter_functions.py:392 +msgid "" +"list_item(val, index, separator) -- interpret the value as a list of items " +"separated by `separator`, returning the `index`th item. The first item is " +"number zero. The last item can be returned using `list_item(-1,separator)`. " +"If the item is not in the list, then the empty value is returned. The " +"separator has the same meaning as in the count function." +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/utils/formatter_functions.py:412 +msgid "" +"select(val, key) -- interpret the value as a comma-separated list of items, " +"with the items being \"id:value\". Find the pair with theid equal to key, " +"and return the corresponding value." +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/utils/formatter_functions.py:429 +msgid "" +"sublist(val, start_index, end_index, separator) -- interpret the value as a " +"list of items separated by `separator`, returning a new list made from the " +"`start_index`th to the `end_index`th item. The first item is number zero. If " +"an index is negative, then it counts from the end of the list. As a special " +"case, an end_index of zero is assumed to be the length of the list. Examples " +"using basic template mode and assuming that the tags column (which is comma-" +"separated) contains \"A, B, C\": {tags:sublist(0,1,\\,)} returns \"A\". " +"{tags:sublist(-1,0,\\,)} returns \"C\". {tags:sublist(0,-1,\\,)} returns " +"\"A, B\"." +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/utils/formatter_functions.py:458 +msgid "" +"subitems(val, start_index, end_index) -- This function is used to break " +"apart lists of items such as genres. It interprets the value as a comma-" +"separated list of items, where each item is a period-separated list. Returns " +"a new list made by first finding all the period-separated items, then for " +"each such item extracting the start_index`th to the `end_index`th " +"components, then combining the results back together. The first component in " +"a period-separated list has an index of zero. If an index is negative, then " +"it counts from the end of the list. As a special case, an end_index of zero " +"is assumed to be the length of the list. Example using basic template mode " +"and assuming a #genre value of \"A.B.C\": {#genre:subitems(0,1)} returns " +"\"A\". {#genre:subitems(0,2)} returns \"A.B\". {#genre:subitems(1,0)} " +"returns \"B.C\". Assuming a #genre value of \"A.B.C, D.E.F\", " +"{#genre:subitems(0,1)} returns \"A, D\". {#genre:subitems(0,2)} returns " +"\"A.B, D.E\"" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/utils/formatter_functions.py:495 +msgid "" +"format_date(val, format_string) -- format the value, which must be a date " +"field, using the format_string, returning a string. The formatting codes " +"are: d : the day as number without a leading zero (1 to 31) dd : the " +"day as number with a leading zero (01 to 31) ddd : the abbreviated " +"localized day name (e.g. \"Mon\" to \"Sun\"). dddd : the long localized day " +"name (e.g. \"Monday\" to \"Sunday\"). M : the month as number without a " +"leading zero (1 to 12). MM : the month as number with a leading zero (01 " +"to 12) MMM : the abbreviated localized month name (e.g. \"Jan\" to " +"\"Dec\"). MMMM : the long localized month name (e.g. \"January\" to " +"\"December\"). yy : the year as two digit number (00 to 99). yyyy : the " +"year as four digit number. iso : the date with time and timezone. Must be " +"the only format present" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/utils/formatter_functions.py:523 +msgid "uppercase(val) -- return value of the field in upper case" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/utils/formatter_functions.py:531 +msgid "lowercase(val) -- return value of the field in lower case" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/utils/formatter_functions.py:539 +msgid "titlecase(val) -- return value of the field in title case" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/utils/formatter_functions.py:547 +msgid "capitalize(val) -- return value of the field capitalized" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/utils/formatter_functions.py:555 +msgid "booksize() -- return value of the field capitalized" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/utils/ipc/job.py:43 +msgid "Waiting..." +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/utils/ipc/job.py:52 +msgid "Stopped" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/utils/ipc/job.py:54 +msgid "Finished" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/utils/ipc/job.py:76 +msgid "Working..." +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/utils/localization.py:95 +msgid "Brazilian Portuguese" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/utils/localization.py:96 +msgid "English (UK)" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/utils/localization.py:97 +msgid "Simplified Chinese" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/utils/localization.py:98 +msgid "Chinese (HK)" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/utils/localization.py:99 +msgid "Traditional Chinese" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/utils/localization.py:100 +msgid "English" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/utils/localization.py:101 +msgid "English (Australia)" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/utils/localization.py:102 +msgid "English (New Zealand)" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/utils/localization.py:103 +msgid "English (Canada)" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/utils/localization.py:104 +msgid "English (India)" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/utils/localization.py:105 +msgid "English (Thailand)" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/utils/localization.py:106 +msgid "English (Cyprus)" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/utils/localization.py:107 +msgid "English (Czechoslovakia)" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/utils/localization.py:108 +msgid "English (Pakistan)" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/utils/localization.py:109 +msgid "English (Croatia)" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/utils/localization.py:110 +msgid "English (Indonesia)" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/utils/localization.py:111 +msgid "English (Israel)" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/utils/localization.py:112 +msgid "English (Singapore)" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/utils/localization.py:113 +msgid "English (Yemen)" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/utils/localization.py:114 +msgid "English (Ireland)" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/utils/localization.py:115 +msgid "English (China)" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/utils/localization.py:116 +msgid "Spanish (Paraguay)" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/utils/localization.py:117 +msgid "Spanish (Uruguay)" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/utils/localization.py:118 +msgid "Spanish (Argentina)" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/utils/localization.py:119 +msgid "Spanish (Mexico)" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/utils/localization.py:120 +msgid "Spanish (Cuba)" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/utils/localization.py:121 +msgid "Spanish (Chile)" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/utils/localization.py:122 +msgid "Spanish (Ecuador)" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/utils/localization.py:123 +msgid "Spanish (Honduras)" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/utils/localization.py:124 +msgid "Spanish (Venezuela)" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/utils/localization.py:125 +msgid "Spanish (Bolivia)" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/utils/localization.py:126 +msgid "Spanish (Nicaragua)" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/utils/localization.py:127 +msgid "German (AT)" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/utils/localization.py:128 +msgid "French (BE)" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/utils/localization.py:129 +msgid "Dutch (NL)" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/utils/localization.py:130 +msgid "Dutch (BE)" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/utils/pyconsole/console.py:56 +msgid "Choose theme (needs restart)" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/utils/pyconsole/console.py:109 +msgid "ERROR: Unhandled exception" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/utils/pyconsole/console.py:188 +msgid "No interpreter" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/utils/pyconsole/console.py:189 +msgid "No active interpreter found. Try restarting the console" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/utils/pyconsole/console.py:203 +msgid "Interpreter died" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/utils/pyconsole/console.py:204 +msgid "" +"Interpreter dies while excuting a command. To see the command, click Show " +"details" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/utils/pyconsole/main.py:20 +msgid "Welcome to" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/utils/pyconsole/main.py:41 +msgid " console " +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/utils/pyconsole/main.py:51 +msgid "Code is running" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/utils/pyconsole/main.py:58 +msgid "Restart console" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/utils/sftp.py:53 +msgid "URL must have the scheme sftp" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/utils/sftp.py:57 +msgid "host must be of the form user@hostname" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/utils/sftp.py:68 +msgid "Failed to negotiate SSH session: " +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/utils/sftp.py:71 +msgid "Failed to authenticate with server: %s" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/utils/smtp.py:249 +msgid "Control email delivery" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/web/feeds/__init__.py:120 +msgid "Unknown section" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/web/feeds/__init__.py:142 +msgid "Unknown feed" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/web/feeds/__init__.py:160 +#: /home/kovid/work/calibre/src/calibre/web/feeds/__init__.py:187 +msgid "Untitled article" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/web/feeds/input.py:22 +msgid "Download periodical content from the internet" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/web/feeds/input.py:37 +msgid "" +"Useful for recipe development. Forces max_articles_per_feed to 2 and " +"downloads at most 2 feeds." +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/web/feeds/input.py:40 +msgid "Username for sites that require a login to access content." +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/web/feeds/input.py:43 +msgid "Password for sites that require a login to access content." +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/web/feeds/input.py:47 +msgid "" +"Do not download latest version of builtin recipes from the calibre server" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/web/feeds/news.py:47 +msgid "Unknown News Source" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/web/feeds/news.py:630 +msgid "The \"%s\" recipe needs a username and password." +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/web/feeds/news.py:736 +msgid "Download finished" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/web/feeds/news.py:738 +msgid "Failed to download the following articles:" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/web/feeds/news.py:744 +msgid "Failed to download parts of the following articles:" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/web/feeds/news.py:746 +msgid " from " +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/web/feeds/news.py:748 +msgid "\tFailed links:" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/web/feeds/news.py:843 +msgid "Could not fetch article." +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/web/feeds/news.py:845 +msgid "The debug traceback is available earlier in this log" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/web/feeds/news.py:847 +msgid "Run with -vv to see the reason" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/web/feeds/news.py:870 +msgid "Fetching feeds..." +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/web/feeds/news.py:875 +msgid "Got feeds from index page" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/web/feeds/news.py:884 +msgid "Trying to download cover..." +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/web/feeds/news.py:886 +msgid "Generating masthead..." +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/web/feeds/news.py:966 +msgid "Starting download [%d thread(s)]..." +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/web/feeds/news.py:982 +msgid "Feeds downloaded to %s" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/web/feeds/news.py:991 +msgid "Could not download cover: %s" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/web/feeds/news.py:1000 +msgid "Downloading cover from %s" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/web/feeds/news.py:1046 +msgid "Masthead image downloaded" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/web/feeds/news.py:1214 +msgid "Untitled Article" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/web/feeds/news.py:1285 +msgid "Article downloaded: %s" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/web/feeds/news.py:1296 +msgid "Article download failed: %s" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/web/feeds/news.py:1313 +msgid "Fetching feed" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/web/feeds/news.py:1460 +msgid "" +"Failed to log in, check your username and password for the calibre " +"Periodicals service." +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/web/feeds/news.py:1475 +msgid "" +"You do not have permission to download this issue. Either your subscription " +"has expired or you have exceeded the maximum allowed downloads for today." +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/web/feeds/recipes/collection.py:46 +msgid "You" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/web/feeds/recipes/model.py:75 +#: /home/kovid/work/calibre/src/calibre/web/feeds/recipes/model.py:84 +#: /home/kovid/work/calibre/src/calibre/web/feeds/recipes/model.py:202 +msgid "Scheduled" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/web/feeds/recipes/model.py:86 +#: /home/kovid/work/calibre/src/calibre/web/feeds/recipes/model.py:203 +msgid "Custom" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/web/feeds/templates.py:118 +msgid "Next section" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/web/feeds/templates.py:121 +msgid "Main menu" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/web/feeds/templates.py:125 +msgid "Previous section" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/web/feeds/templates.py:214 +msgid "Section Menu" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/web/feeds/templates.py:217 +msgid "Main Menu" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/web/feeds/templates.py:303 +#: /home/kovid/work/calibre/src/calibre/web/feeds/templates.py:393 +msgid "Sections" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/web/feeds/templates.py:390 +msgid "Articles" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/web/fetch/simple.py:476 +msgid "" +"%prog URL\n" +"\n" +"Where URL is for example http://google.com" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/web/fetch/simple.py:479 +msgid "Base directory into which URL is saved. Default is %default" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/web/fetch/simple.py:482 +msgid "" +"Timeout in seconds to wait for a response from the server. Default: %default " +"s" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/web/fetch/simple.py:485 +msgid "" +"Maximum number of levels to recurse i.e. depth of links to follow. Default " +"%default" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/web/fetch/simple.py:488 +msgid "" +"The maximum number of files to download. This only applies to files from tags. Default is %default" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/web/fetch/simple.py:490 +msgid "" +"Minimum interval in seconds between consecutive fetches. Default is %default " +"s" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/web/fetch/simple.py:492 +msgid "" +"The character encoding for the websites you are trying to download. The " +"default is to try and guess the encoding." +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/web/fetch/simple.py:494 +msgid "" +"Only links that match this regular expression will be followed. This option " +"can be specified multiple times, in which case as long as a link matches any " +"one regexp, it will be followed. By default all links are followed." +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/web/fetch/simple.py:496 +msgid "" +"Any link that matches this regular expression will be ignored. This option " +"can be specified multiple times, in which case as long as any regexp matches " +"a link, it will be ignored.By default, no links are ignored. If both filter " +"regexp and match regexp are specified, then filter regexp is applied first." +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/web/fetch/simple.py:498 +msgid "Do not download CSS stylesheets." +msgstr "" + +#: /home/kovid/work/calibre/resources/default_tweaks.py:12 +msgid "Auto increment series index" +msgstr "" + +#: /home/kovid/work/calibre/resources/default_tweaks.py:13 +msgid "" +"The algorithm used to assign a new book in an existing series a series " +"number.\n" +"New series numbers assigned using this tweak are always integer values, " +"except\n" +"if a constant non-integer is specified.\n" +"Possible values are:\n" +"next - First available integer larger than the largest existing number\n" +"first_free - First available integer larger than 0\n" +"next_free - First available integer larger than the smallest existing " +"number\n" +"last_free - First available integer smaller than the largest existing " +"number\n" +"Return largest existing + 1 if no free number is found\n" +"const - Assign the number 1 always\n" +"a number - Assign that number always. The number is not in quotes. Note " +"that\n" +"0.0 can be used here.\n" +"Examples:\n" +"series_index_auto_increment = 'next'\n" +"series_index_auto_increment = 'next_free'\n" +"series_index_auto_increment = 16.5" +msgstr "" + +#: /home/kovid/work/calibre/resources/default_tweaks.py:31 +msgid "Add separator after completing an author name" +msgstr "" + +#: /home/kovid/work/calibre/resources/default_tweaks.py:32 +msgid "" +"Should the completion separator be append\n" +"to the end of the completed text to\n" +"automatically begin a new completion operation\n" +"for authors.\n" +"Can be either True or False" +msgstr "" + +#: /home/kovid/work/calibre/resources/default_tweaks.py:40 +msgid "Author sort name algorithm" +msgstr "" + +#: /home/kovid/work/calibre/resources/default_tweaks.py:41 +msgid "" +"The algorithm used to copy author to author_sort\n" +"Possible values are:\n" +"invert: use \"fn ln\" -> \"ln, fn\"\n" +"copy : copy author to author_sort without modification\n" +"comma : use 'copy' if there is a ',' in the name, otherwise use 'invert'\n" +"nocomma : \"fn ln\" -> \"ln fn\" (without the comma)\n" +"When this tweak is changed, the author_sort values stored with each author\n" +"must be recomputed by right-clicking on an author in the left-hand tags " +"pane,\n" +"selecting 'manage authors', and pressing 'Recalculate all author sort " +"values'.\n" +"The author name suffixes are words that are ignored when they occur at the\n" +"end of an author name. The case of the suffix is ignored and trailing\n" +"periods are automatically handled." +msgstr "" + +#: /home/kovid/work/calibre/resources/default_tweaks.py:58 +msgid "Use author sort in Tag Browser" +msgstr "" + +#: /home/kovid/work/calibre/resources/default_tweaks.py:59 +msgid "" +"Set which author field to display in the tags pane (the list of authors,\n" +"series, publishers etc on the left hand side). The choices are author and\n" +"author_sort. This tweak affects only what is displayed under the authors\n" +"category in the tags pane and content server. Please note that if you set " +"this\n" +"to author_sort, it is very possible to see duplicate names in the list " +"because\n" +"although it is guaranteed that author names are unique, there is no such\n" +"guarantee for author_sort values. Showing duplicates won't break anything, " +"but\n" +"it could lead to some confusion. When using 'author_sort', the tooltip will\n" +"show the author's name.\n" +"Examples:\n" +"categories_use_field_for_author_name = 'author'\n" +"categories_use_field_for_author_name = 'author_sort'" +msgstr "" + +#: /home/kovid/work/calibre/resources/default_tweaks.py:73 +msgid "Control partitioning of Tag Browser" +msgstr "" + +#: /home/kovid/work/calibre/resources/default_tweaks.py:74 +msgid "" +"When partitioning the tags browser, the format of the subcategory label is\n" +"controlled by a template: categories_collapsed_name_template if sorting by\n" +"name, categories_collapsed_rating_template if sorting by average rating, " +"and\n" +"categories_collapsed_popularity_template if sorting by popularity. There " +"are\n" +"two variables available to the template: first and last. The variable " +"'first'\n" +"is the initial item in the subcategory, and the variable 'last' is the " +"final\n" +"item in the subcategory. Both variables are 'objects'; they each have " +"multiple\n" +"values that are obtained by using a suffix. For example, first.name for an\n" +"author category will be the name of the author. The sub-values available " +"are:\n" +"name: the printable name of the item\n" +"count: the number of books that references this item\n" +"avg_rating: the average rating of all the books referencing this item\n" +"sort: the sort value. For authors, this is the author_sort for that author\n" +"category: the category (e.g., authors, series) that the item is in.\n" +"Note that the \"r'\" in front of the { is necessary if there are " +"backslashes\n" +"(\\ characters) in the template. It doesn't hurt anything to leave it there\n" +"even if there aren't any backslashes." +msgstr "" + +#: /home/kovid/work/calibre/resources/default_tweaks.py:96 +msgid "Specify columns to sort the booklist by on startup" +msgstr "" + +#: /home/kovid/work/calibre/resources/default_tweaks.py:97 +msgid "" +"Provide a set of columns to be sorted on when calibre starts\n" +"The argument is None if saved sort history is to be used\n" +"otherwise it is a list of column,order pairs. Column is the\n" +"lookup/search name, found using the tooltip for the column\n" +"Order is 0 for ascending, 1 for descending\n" +"For example, set it to [('authors',0),('title',0)] to sort by\n" +"title within authors." +msgstr "" + +#: /home/kovid/work/calibre/resources/default_tweaks.py:106 +msgid "Control how dates are displayed" +msgstr "" + +#: /home/kovid/work/calibre/resources/default_tweaks.py:107 +msgid "" +"Format to be used for publication date and the timestamp (date).\n" +"A string controlling how the publication date is displayed in the GUI\n" +"d the day as number without a leading zero (1 to 31)\n" +"dd the day as number with a leading zero (01 to 31)\n" +"ddd the abbreviated localized day name (e.g. 'Mon' to 'Sun').\n" +"dddd the long localized day name (e.g. 'Monday' to 'Qt::Sunday').\n" +"M the month as number without a leading zero (1-12)\n" +"MM the month as number with a leading zero (01-12)\n" +"MMM the abbreviated localized month name (e.g. 'Jan' to 'Dec').\n" +"MMMM the long localized month name (e.g. 'January' to 'December').\n" +"yy the year as two digit number (00-99)\n" +"yyyy the year as four digit number\n" +"For example, given the date of 9 Jan 2010, the following formats show\n" +"MMM yyyy ==> Jan 2010 yyyy ==> 2010 dd MMM yyyy ==> 09 Jan 2010\n" +"MM/yyyy ==> 01/2010 d/M/yy ==> 9/1/10 yy ==> 10\n" +"publication default if not set: MMM yyyy\n" +"timestamp default if not set: dd MMM yyyy" +msgstr "" + +#: /home/kovid/work/calibre/resources/default_tweaks.py:128 +msgid "Control sorting of titles and series in the library display" +msgstr "" + +#: /home/kovid/work/calibre/resources/default_tweaks.py:129 +msgid "" +"Control title and series sorting in the library view. If set to\n" +"'library_order', the title sort field will be used instead of the title.\n" +"Unless you have manually edited the title sort field, leading articles such " +"as\n" +"The and A will be ignored. If set to 'strictly_alphabetic', the titles will " +"be\n" +"sorted as-is (sort by title instead of title sort). For example, with\n" +"library_order, The Client will sort under 'C'. With strictly_alphabetic, " +"the\n" +"book will sort under 'T'.\n" +"This flag affects Calibre's library display. It has no effect on devices. " +"In\n" +"addition, titles for books added before changing the flag will retain their\n" +"order until the title is edited. Double-clicking on a title and hitting " +"return\n" +"without changing anything is sufficient to change the sort." +msgstr "" + +#: /home/kovid/work/calibre/resources/default_tweaks.py:142 +msgid "Control formatting of title and series when used in templates" +msgstr "" + +#: /home/kovid/work/calibre/resources/default_tweaks.py:143 +msgid "" +"Control how title and series names are formatted when saving to " +"disk/sending\n" +"to device. The behavior depends on the field being processed. If processing\n" +"title, then if this tweak is set to 'library_order', the title will be\n" +"replaced with title_sort. If it is set to 'strictly_alphabetic', then the\n" +"title will not be changed. If processing series, then if set to\n" +"'library_order', articles such as 'The' and 'An' will be moved to the end. " +"If\n" +"set to 'strictly_alphabetic', the series will be sent without change.\n" +"For example, if the tweak is set to library_order, \"The Lord of the " +"Rings\"\n" +"will become \"Lord of the Rings, The\". If the tweak is set to\n" +"strictly_alphabetic, it would remain \"The Lord of the Rings\"." +msgstr "" + +#: /home/kovid/work/calibre/resources/default_tweaks.py:155 +msgid "Set the list of words considered to be \"articles\" for sort strings" +msgstr "" + +#: /home/kovid/work/calibre/resources/default_tweaks.py:156 +msgid "" +"Set the list of words that are to be considered 'articles' when computing " +"the\n" +"title sort strings. The list is a regular expression, with the articles\n" +"separated by 'or' bars. Comparisons are case insensitive, and that cannot " +"be\n" +"changed. Changes to this tweak won't have an effect until the book is " +"modified\n" +"in some way. If you enter an invalid pattern, it is silently ignored.\n" +"To disable use the expression: '^$'\n" +"Default: '^(A|The|An)\\s+'" +msgstr "" + +#: /home/kovid/work/calibre/resources/default_tweaks.py:165 +msgid "Specify a folder calibre should connect to at startup" +msgstr "" + +#: /home/kovid/work/calibre/resources/default_tweaks.py:166 +msgid "" +"Specify a folder that calibre should connect to at startup using\n" +"connect_to_folder. This must be a full path to the folder. If the folder " +"does\n" +"not exist when calibre starts, it is ignored. If there are '\\' characters " +"in\n" +"the path (such as in Windows paths), you must double them.\n" +"Examples:\n" +"auto_connect_to_folder = 'C:\\\\Users\\\\someone\\\\Desktop\\\\testlib'\n" +"auto_connect_to_folder = '/home/dropbox/My Dropbox/someone/library'" +msgstr "" + +#: /home/kovid/work/calibre/resources/default_tweaks.py:175 +msgid "Specify renaming rules for SONY collections" +msgstr "" + +#: /home/kovid/work/calibre/resources/default_tweaks.py:176 +msgid "" +"Specify renaming rules for sony collections. This tweak is only applicable " +"if\n" +"metadata management is set to automatic. Collections on Sonys are named\n" +"depending upon whether the field is standard or custom. A collection " +"derived\n" +"from a standard field is named for the value in that field. For example, if\n" +"the standard 'series' column contains the value 'Darkover', then the\n" +"collection name is 'Darkover'. A collection derived from a custom field " +"will\n" +"have the name of the field added to the value. For example, if a custom " +"series\n" +"column named 'My Series' contains the name 'Darkover', then the collection\n" +"will by default be named 'Darkover (My Series)'. For purposes of this\n" +"documentation, 'Darkover' is called the value and 'My Series' is called the\n" +"category. If two books have fields that generate the same collection name,\n" +"then both books will be in that collection.\n" +"This set of tweaks lets you specify for a standard or custom field how\n" +"the collections are to be named. You can use it to add a description to a\n" +"standard field, for example 'Foo (Tag)' instead of the 'Foo'. You can also " +"use\n" +"it to force multiple fields to end up in the same collection. For example, " +"you\n" +"could force the values in 'series', '#my_series_1', and '#my_series_2' to\n" +"appear in collections named 'some_value (Series)', thereby merging all of " +"the\n" +"fields into one set of collections.\n" +"There are two related tweaks. The first determines the category name to use\n" +"for a metadata field. The second is a template, used to determines how the\n" +"value and category are combined to create the collection name.\n" +"The syntax of the first tweak, sony_collection_renaming_rules, is:\n" +"{'field_lookup_name':'category_name_to_use', 'lookup_name':'name', ...}\n" +"The second tweak, sony_collection_name_template, is a template. It uses the\n" +"same template language as plugboards and save templates. This tweak " +"controls\n" +"how the value and category are combined together to make the collection " +"name.\n" +"The only two fields available are {category} and {value}. The {value} field " +"is\n" +"never empty. The {category} field can be empty. The default is to put the\n" +"value first, then the category enclosed in parentheses, it is isn't empty:\n" +"'{value} {category:|(|)}'\n" +"Examples: The first three examples assume that the second tweak\n" +"has not been changed.\n" +"1: I want three series columns to be merged into one set of collections. " +"The\n" +"column lookup names are 'series', '#series_1' and '#series_2'. I want " +"nothing\n" +"in the parenthesis. The value to use in the tweak value would be:\n" +"sony_collection_renaming_rules={'series':'', '#series_1':'', " +"'#series_2':''}\n" +"2: I want the word '(Series)' to appear on collections made from series, " +"and\n" +"the word '(Tag)' to appear on collections made from tags. Use:\n" +"sony_collection_renaming_rules={'series':'Series', 'tags':'Tag'}\n" +"3: I want 'series' and '#myseries' to be merged, and for the collection " +"name\n" +"to have '(Series)' appended. The renaming rule is:\n" +"sony_collection_renaming_rules={'series':'Series', '#myseries':'Series'}\n" +"4: Same as example 2, but instead of having the category name in " +"parentheses\n" +"and appended to the value, I want it prepended and separated by a colon, " +"such\n" +"as in Series: Darkover. I must change the template used to format the " +"category name\n" +"The resulting two tweaks are:\n" +"sony_collection_renaming_rules={'series':'Series', 'tags':'Tag'}\n" +"sony_collection_name_template='{category:||: }{value}'" +msgstr "" + +#: /home/kovid/work/calibre/resources/default_tweaks.py:228 +msgid "Specify how SONY collections are sorted" +msgstr "" + +#: /home/kovid/work/calibre/resources/default_tweaks.py:229 +msgid "" +"Specify how sony collections are sorted. This tweak is only applicable if\n" +"metadata management is set to automatic. You can indicate which metadata is " +"to\n" +"be used to sort on a collection-by-collection basis. The format of the " +"tweak\n" +"is a list of metadata fields from which collections are made, followed by " +"the\n" +"name of the metadata field containing the sort value.\n" +"Example: The following indicates that collections built from pubdate and " +"tags\n" +"are to be sorted by the value in the custom column '#mydate', that " +"collections\n" +"built from 'series' are to be sorted by 'series_index', and that all other\n" +"collections are to be sorted by title. If a collection metadata field is " +"not\n" +"named, then if it is a series- based collection it is sorted by series " +"order,\n" +"otherwise it is sorted by title order.\n" +"[(['pubdate', 'tags'],'#mydate'), (['series'],'series_index'), (['*'], " +"'title')]\n" +"Note that the bracketing and parentheses are required. The syntax is\n" +"[ ( [list of fields], sort field ) , ( [ list of fields ] , sort field ) ]\n" +"Default: empty (no rules), so no collection attributes are named." +msgstr "" + +#: /home/kovid/work/calibre/resources/default_tweaks.py:247 +msgid "Control how tags are applied when copying books to another library" +msgstr "" + +#: /home/kovid/work/calibre/resources/default_tweaks.py:248 +msgid "" +"Set this to True to ensure that tags in 'Tags to add when adding\n" +"a book' are added when copying books to another library" +msgstr "" + +#: /home/kovid/work/calibre/resources/default_tweaks.py:253 +msgid "Set the maximum number of tags to show per book in the content server" +msgstr "" + +#: /home/kovid/work/calibre/resources/default_tweaks.py:257 +msgid "" +"Set custom metadata fields that the content server will or will not display." +msgstr "" + +#: /home/kovid/work/calibre/resources/default_tweaks.py:258 +msgid "" +"content_server_will_display is a list of custom fields to be displayed.\n" +"content_server_wont_display is a list of custom fields not to be displayed.\n" +"wont_display has priority over will_display.\n" +"The special value '*' means all custom fields. The value [] means no " +"entries.\n" +"Defaults:\n" +"content_server_will_display = ['*']\n" +"content_server_wont_display = []\n" +"Examples:\n" +"To display only the custom fields #mytags and #genre:\n" +"content_server_will_display = ['#mytags', '#genre']\n" +"content_server_wont_display = []\n" +"To display all fields except #mycomments:\n" +"content_server_will_display = ['*']\n" +"content_server_wont_display['#mycomments']" +msgstr "" + +#: /home/kovid/work/calibre/resources/default_tweaks.py:275 +msgid "Set the maximum number of sort 'levels'" +msgstr "" + +#: /home/kovid/work/calibre/resources/default_tweaks.py:276 +msgid "" +"Set the maximum number of sort 'levels' that calibre will use to resort the\n" +"library after certain operations such as searches or device insertion. Each\n" +"sort level adds a performance penalty. If the database is large (thousands " +"of\n" +"books) the penalty might be noticeable. If you are not concerned about multi-" +"\n" +"level sorts, and if you are seeing a slowdown, reduce the value of this " +"tweak." +msgstr "" + +#: /home/kovid/work/calibre/resources/default_tweaks.py:283 +msgid "Specify which font to use when generating a default cover" +msgstr "" + +#: /home/kovid/work/calibre/resources/default_tweaks.py:284 +msgid "" +"Absolute path to .ttf font files to use as the fonts for the title, author\n" +"and footer when generating a default cover. Useful if the default font " +"(Liberation\n" +"Serif) does not contain glyphs for the language of the books in your library." +msgstr "" + +#: /home/kovid/work/calibre/resources/default_tweaks.py:290 +msgid "Control behavior of double clicks on the book list" +msgstr "" + +#: /home/kovid/work/calibre/resources/default_tweaks.py:291 +msgid "" +"Behavior of doubleclick on the books list. Choices: open_viewer, " +"do_nothing,\n" +"edit_cell, edit_metadata. Selecting edit_metadata has the side effect of\n" +"disabling editing a field using a single click.\n" +"Default: open_viewer.\n" +"Example: doubleclick_on_library_view = 'do_nothing'" +msgstr "" + +#: /home/kovid/work/calibre/resources/default_tweaks.py:299 +msgid "Language to use when sorting." +msgstr "" + +#: /home/kovid/work/calibre/resources/default_tweaks.py:300 +msgid "" +"Setting this tweak will force sorting to use the\n" +"collating order for the specified language. This might be useful if you run\n" +"calibre in English but want sorting to work in the language where you live.\n" +"Set the tweak to the desired ISO 639-1 language code, in lower case.\n" +"You can find the list of supported locales at\n" +"http://publib.boulder.ibm.com/infocenter/iseries/v5r3/topic/nls/rbagsicusorts" +"equencetables.htm\n" +"Default: locale_for_sorting = '' -- use the language calibre displays in\n" +"Example: locale_for_sorting = 'fr' -- sort using French rules.\n" +"Example: locale_for_sorting = 'nb' -- sort using Norwegian rules." +msgstr "" + +#: /home/kovid/work/calibre/resources/default_tweaks.py:311 +msgid "Number of columns for custom metadata in the edit metadata dialog" +msgstr "" + +#: /home/kovid/work/calibre/resources/default_tweaks.py:312 +msgid "" +"Set whether to use one or two columns for custom metadata when editing\n" +"metadata one book at a time. If True, then the fields are laid out using " +"two\n" +"columns. If False, one column is used." +msgstr "" + +#: /home/kovid/work/calibre/resources/default_tweaks.py:317 +msgid "The number of seconds to wait before sending emails" +msgstr "" + +#: /home/kovid/work/calibre/resources/default_tweaks.py:318 +msgid "" +"The number of seconds to wait before sending emails when using a\n" +"public email server like gmail or hotmail. Default is: 5 minutes\n" +"Setting it to lower may cause the server's SPAM controls to kick in,\n" +"making email sending fail. Changes will take effect only after a restart of\n" +"calibre." +msgstr "" + +#: /home/kovid/work/calibre/resources/default_tweaks.py:325 +msgid "Remove the bright yellow lines at the edges of the book list" +msgstr "" + +#: /home/kovid/work/calibre/resources/default_tweaks.py:326 +msgid "" +"Control whether the bright yellow lines at the edges of book list are drawn\n" +"when a section of the user interface is hidden. Changes will take effect\n" +"after a restart of calibre." +msgstr "" + +#: /home/kovid/work/calibre/resources/default_tweaks.py:331 +msgid "The maximum width and height for covers saved in the calibre library" +msgstr "" + +#: /home/kovid/work/calibre/resources/default_tweaks.py:332 +msgid "" +"All covers in the calibre library will be resized, preserving aspect ratio,\n" +"to fit within this size. This is to prevent slowdowns caused by extremely\n" +"large covers" +msgstr "" + +#: /home/kovid/work/calibre/resources/default_tweaks.py:337 +msgid "Where to send downloaded news" +msgstr "" + +#: /home/kovid/work/calibre/resources/default_tweaks.py:338 +msgid "" +"When automatically sending downloaded news to a connected device, calibre\n" +"will by default send it to the main memory. By changing this tweak, you can\n" +"control where it is sent. Valid values are \"main\", \"carda\", \"cardb\". " +"Note\n" +"that if there isn't enough free space available on the location you choose,\n" +"the files will be sent to the location with the most free space." +msgstr "" + +#: /home/kovid/work/calibre/resources/default_tweaks.py:345 +msgid "What interfaces should the content server listen on" +msgstr "" + +#: /home/kovid/work/calibre/resources/default_tweaks.py:346 +msgid "" +"By default, the calibre content server listens on '0.0.0.0' which means that " +"it\n" +"accepts IPv4 connections on all interfaces. You can change this to, for\n" +"example, '127.0.0.1' to only listen for connections from the local machine, " +"or\n" +"to '::' to listen to all incoming IPv6 and IPv4 connections (this may not\n" +"work on all operating systems)" +msgstr "" + +#: /home/kovid/work/calibre/resources/default_tweaks.py:353 +msgid "Unified toolbar on OS X" +msgstr "" + +#: /home/kovid/work/calibre/resources/default_tweaks.py:354 +msgid "" +"If you enable this option and restart calibre, the toolbar will be " +"'unified'\n" +"with the titlebar as is normal for OS X applications. However, doing this " +"has\n" +"various bugs, for instance the minimum width of the toolbar becomes twice\n" +"what it should be and it causes other random bugs on some systems, so turn " +"it\n" +"on at your own risk!" +msgstr "" diff --git a/src/calibre/translations/eu.po b/src/calibre/translations/eu.po index 9dac21302b..69ab1efa93 100644 --- a/src/calibre/translations/eu.po +++ b/src/calibre/translations/eu.po @@ -7,88 +7,97 @@ msgid "" msgstr "" "Project-Id-Version: calibre\n" "Report-Msgid-Bugs-To: FULL NAME \n" -"POT-Creation-Date: 2011-02-18 21:06+0000\n" -"PO-Revision-Date: 2011-02-07 14:39+0000\n" -"Last-Translator: gorkaazk \n" +"POT-Creation-Date: 2011-05-20 18:12+0000\n" +"PO-Revision-Date: 2011-05-08 17:11+0000\n" +"Last-Translator: Oier Mees \n" "Language-Team: Basque \n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" -"X-Launchpad-Export-Date: 2011-02-19 04:48+0000\n" -"X-Generator: Launchpad (build 12351)\n" +"X-Launchpad-Export-Date: 2011-05-21 04:43+0000\n" +"X-Generator: Launchpad (build 12959)\n" -#: /home/kovid/work/calibre/src/calibre/customize/__init__.py:43 +#: /home/kovid/work/calibre/src/calibre/customize/__init__.py:56 msgid "Does absolutely nothing" msgstr "Ez du ezer egiten" -#: /home/kovid/work/calibre/src/calibre/customize/__init__.py:46 +#: /home/kovid/work/calibre/src/calibre/customize/__init__.py:59 #: /home/kovid/work/calibre/src/calibre/devices/hanvon/driver.py:87 #: /home/kovid/work/calibre/src/calibre/devices/hanvon/driver.py:88 #: /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/books.py:24 -#: /home/kovid/work/calibre/src/calibre/devices/kobo/driver.py:482 +#: /home/kovid/work/calibre/src/calibre/devices/kobo/driver.py:488 #: /home/kovid/work/calibre/src/calibre/devices/nook/driver.py:70 #: /home/kovid/work/calibre/src/calibre/devices/nook/driver.py:71 #: /home/kovid/work/calibre/src/calibre/devices/prs500/books.py:267 #: /home/kovid/work/calibre/src/calibre/devices/prs505/sony_cache.py:660 -#: /home/kovid/work/calibre/src/calibre/devices/usbms/driver.py:403 -#: /home/kovid/work/calibre/src/calibre/ebooks/chm/input.py:97 -#: /home/kovid/work/calibre/src/calibre/ebooks/chm/input.py:100 +#: /home/kovid/work/calibre/src/calibre/devices/usbms/driver.py:467 +#: /home/kovid/work/calibre/src/calibre/ebooks/chm/input.py:106 +#: /home/kovid/work/calibre/src/calibre/ebooks/chm/input.py:109 #: /home/kovid/work/calibre/src/calibre/ebooks/chm/metadata.py:56 -#: /home/kovid/work/calibre/src/calibre/ebooks/comic/input.py:419 +#: /home/kovid/work/calibre/src/calibre/ebooks/comic/input.py:435 #: /home/kovid/work/calibre/src/calibre/ebooks/epub/periodical.py:127 #: /home/kovid/work/calibre/src/calibre/ebooks/fb2/input.py:100 #: /home/kovid/work/calibre/src/calibre/ebooks/fb2/input.py:102 -#: /home/kovid/work/calibre/src/calibre/ebooks/html/input.py:331 -#: /home/kovid/work/calibre/src/calibre/ebooks/html/input.py:334 +#: /home/kovid/work/calibre/src/calibre/ebooks/html/input.py:332 +#: /home/kovid/work/calibre/src/calibre/ebooks/html/input.py:335 #: /home/kovid/work/calibre/src/calibre/ebooks/lrf/html/convert_from.py:1894 #: /home/kovid/work/calibre/src/calibre/ebooks/lrf/html/convert_from.py:1896 #: /home/kovid/work/calibre/src/calibre/ebooks/lrf/output.py:24 -#: /home/kovid/work/calibre/src/calibre/ebooks/metadata/__init__.py:236 -#: /home/kovid/work/calibre/src/calibre/ebooks/metadata/book/base.py:31 -#: /home/kovid/work/calibre/src/calibre/ebooks/metadata/book/base.py:32 -#: /home/kovid/work/calibre/src/calibre/ebooks/metadata/book/base.py:73 -#: /home/kovid/work/calibre/src/calibre/ebooks/metadata/book/base.py:379 -#: /home/kovid/work/calibre/src/calibre/ebooks/metadata/book/base.py:384 -#: /home/kovid/work/calibre/src/calibre/ebooks/metadata/book/base.py:616 +#: /home/kovid/work/calibre/src/calibre/ebooks/metadata/__init__.py:253 +#: /home/kovid/work/calibre/src/calibre/ebooks/metadata/book/base.py:34 +#: /home/kovid/work/calibre/src/calibre/ebooks/metadata/book/base.py:35 +#: /home/kovid/work/calibre/src/calibre/ebooks/metadata/book/base.py:89 +#: /home/kovid/work/calibre/src/calibre/ebooks/metadata/book/base.py:455 +#: /home/kovid/work/calibre/src/calibre/ebooks/metadata/book/base.py:460 +#: /home/kovid/work/calibre/src/calibre/ebooks/metadata/book/base.py:724 #: /home/kovid/work/calibre/src/calibre/ebooks/metadata/ereader.py:36 #: /home/kovid/work/calibre/src/calibre/ebooks/metadata/ereader.py:61 -#: /home/kovid/work/calibre/src/calibre/ebooks/metadata/fb2.py:54 -#: /home/kovid/work/calibre/src/calibre/ebooks/metadata/fetch.py:359 +#: /home/kovid/work/calibre/src/calibre/ebooks/metadata/extz.py:23 +#: /home/kovid/work/calibre/src/calibre/ebooks/metadata/fb2.py:55 #: /home/kovid/work/calibre/src/calibre/ebooks/metadata/meta.py:36 #: /home/kovid/work/calibre/src/calibre/ebooks/metadata/meta.py:64 #: /home/kovid/work/calibre/src/calibre/ebooks/metadata/meta.py:66 #: /home/kovid/work/calibre/src/calibre/ebooks/metadata/meta.py:124 #: /home/kovid/work/calibre/src/calibre/ebooks/metadata/meta.py:126 -#: /home/kovid/work/calibre/src/calibre/ebooks/metadata/opf2.py:1026 -#: /home/kovid/work/calibre/src/calibre/ebooks/metadata/opf2.py:1136 -#: /home/kovid/work/calibre/src/calibre/ebooks/metadata/pdb.py:39 +#: /home/kovid/work/calibre/src/calibre/ebooks/metadata/opf2.py:1066 +#: /home/kovid/work/calibre/src/calibre/ebooks/metadata/opf2.py:1176 +#: /home/kovid/work/calibre/src/calibre/ebooks/metadata/pdb.py:41 #: /home/kovid/work/calibre/src/calibre/ebooks/metadata/pdf.py:29 +#: /home/kovid/work/calibre/src/calibre/ebooks/metadata/plucker.py:25 #: /home/kovid/work/calibre/src/calibre/ebooks/metadata/pml.py:23 #: /home/kovid/work/calibre/src/calibre/ebooks/metadata/pml.py:49 #: /home/kovid/work/calibre/src/calibre/ebooks/metadata/rtf.py:88 #: /home/kovid/work/calibre/src/calibre/ebooks/metadata/rtf.py:91 #: /home/kovid/work/calibre/src/calibre/ebooks/metadata/rtf.py:101 #: /home/kovid/work/calibre/src/calibre/ebooks/metadata/snb.py:16 -#: /home/kovid/work/calibre/src/calibre/ebooks/metadata/sources/google.py:75 +#: /home/kovid/work/calibre/src/calibre/ebooks/metadata/sources/base.py:48 +#: /home/kovid/work/calibre/src/calibre/ebooks/metadata/sources/base.py:298 +#: /home/kovid/work/calibre/src/calibre/ebooks/metadata/sources/covers.py:79 +#: /home/kovid/work/calibre/src/calibre/ebooks/metadata/sources/covers.py:81 +#: /home/kovid/work/calibre/src/calibre/ebooks/metadata/sources/douban.py:78 +#: /home/kovid/work/calibre/src/calibre/ebooks/metadata/sources/google.py:81 +#: /home/kovid/work/calibre/src/calibre/ebooks/metadata/sources/identify.py:208 +#: /home/kovid/work/calibre/src/calibre/ebooks/metadata/sources/identify.py:303 +#: /home/kovid/work/calibre/src/calibre/ebooks/metadata/sources/identify.py:305 +#: /home/kovid/work/calibre/src/calibre/ebooks/metadata/sources/identify.py:406 #: /home/kovid/work/calibre/src/calibre/ebooks/metadata/txt.py:18 -#: /home/kovid/work/calibre/src/calibre/ebooks/metadata/txtz.py:23 -#: /home/kovid/work/calibre/src/calibre/ebooks/mobi/reader.py:42 -#: /home/kovid/work/calibre/src/calibre/ebooks/mobi/reader.py:68 -#: /home/kovid/work/calibre/src/calibre/ebooks/mobi/reader.py:81 -#: /home/kovid/work/calibre/src/calibre/ebooks/mobi/reader.py:124 -#: /home/kovid/work/calibre/src/calibre/ebooks/mobi/reader.py:158 -#: /home/kovid/work/calibre/src/calibre/ebooks/mobi/reader.py:663 -#: /home/kovid/work/calibre/src/calibre/ebooks/mobi/reader.py:878 -#: /home/kovid/work/calibre/src/calibre/ebooks/mobi/reader.py:880 -#: /home/kovid/work/calibre/src/calibre/ebooks/odt/input.py:49 -#: /home/kovid/work/calibre/src/calibre/ebooks/odt/input.py:51 -#: /home/kovid/work/calibre/src/calibre/ebooks/oeb/base.py:958 -#: /home/kovid/work/calibre/src/calibre/ebooks/oeb/base.py:963 -#: /home/kovid/work/calibre/src/calibre/ebooks/oeb/base.py:1029 -#: /home/kovid/work/calibre/src/calibre/ebooks/oeb/reader.py:145 -#: /home/kovid/work/calibre/src/calibre/ebooks/oeb/reader.py:152 +#: /home/kovid/work/calibre/src/calibre/ebooks/mobi/reader.py:43 +#: /home/kovid/work/calibre/src/calibre/ebooks/mobi/reader.py:69 +#: /home/kovid/work/calibre/src/calibre/ebooks/mobi/reader.py:82 +#: /home/kovid/work/calibre/src/calibre/ebooks/mobi/reader.py:125 +#: /home/kovid/work/calibre/src/calibre/ebooks/mobi/reader.py:159 +#: /home/kovid/work/calibre/src/calibre/ebooks/mobi/reader.py:714 +#: /home/kovid/work/calibre/src/calibre/ebooks/mobi/reader.py:961 +#: /home/kovid/work/calibre/src/calibre/ebooks/mobi/reader.py:963 +#: /home/kovid/work/calibre/src/calibre/ebooks/odt/input.py:99 +#: /home/kovid/work/calibre/src/calibre/ebooks/odt/input.py:101 +#: /home/kovid/work/calibre/src/calibre/ebooks/oeb/base.py:1001 +#: /home/kovid/work/calibre/src/calibre/ebooks/oeb/base.py:1006 +#: /home/kovid/work/calibre/src/calibre/ebooks/oeb/base.py:1072 +#: /home/kovid/work/calibre/src/calibre/ebooks/oeb/reader.py:144 +#: /home/kovid/work/calibre/src/calibre/ebooks/oeb/reader.py:151 #: /home/kovid/work/calibre/src/calibre/ebooks/oeb/transforms/jacket.py:65 #: /home/kovid/work/calibre/src/calibre/ebooks/oeb/transforms/jacket.py:112 #: /home/kovid/work/calibre/src/calibre/ebooks/oeb/transforms/jacket.py:119 @@ -112,108 +121,119 @@ msgstr "Ez du ezer egiten" #: /home/kovid/work/calibre/src/calibre/ebooks/pdf/manipulate/rotate.py:63 #: /home/kovid/work/calibre/src/calibre/ebooks/pdf/manipulate/split.py:81 #: /home/kovid/work/calibre/src/calibre/ebooks/pdf/manipulate/split.py:82 -#: /home/kovid/work/calibre/src/calibre/ebooks/pdf/writer.py:100 #: /home/kovid/work/calibre/src/calibre/ebooks/pdf/writer.py:101 -#: /home/kovid/work/calibre/src/calibre/ebooks/rtf/input.py:312 -#: /home/kovid/work/calibre/src/calibre/ebooks/rtf/input.py:314 -#: /home/kovid/work/calibre/src/calibre/gui2/__init__.py:308 -#: /home/kovid/work/calibre/src/calibre/gui2/__init__.py:315 -#: /home/kovid/work/calibre/src/calibre/gui2/actions/add.py:101 -#: /home/kovid/work/calibre/src/calibre/gui2/actions/edit_metadata.py:332 -#: /home/kovid/work/calibre/src/calibre/gui2/actions/edit_metadata.py:335 +#: /home/kovid/work/calibre/src/calibre/ebooks/pdf/writer.py:102 +#: /home/kovid/work/calibre/src/calibre/ebooks/rtf/input.py:313 +#: /home/kovid/work/calibre/src/calibre/ebooks/rtf/input.py:315 +#: /home/kovid/work/calibre/src/calibre/gui2/__init__.py:347 +#: /home/kovid/work/calibre/src/calibre/gui2/__init__.py:355 +#: /home/kovid/work/calibre/src/calibre/gui2/actions/add.py:156 +#: /home/kovid/work/calibre/src/calibre/gui2/actions/edit_metadata.py:364 +#: /home/kovid/work/calibre/src/calibre/gui2/actions/edit_metadata.py:367 #: /home/kovid/work/calibre/src/calibre/gui2/add.py:160 #: /home/kovid/work/calibre/src/calibre/gui2/add.py:167 +#: /home/kovid/work/calibre/src/calibre/gui2/book_details.py:519 #: /home/kovid/work/calibre/src/calibre/gui2/convert/__init__.py:42 #: /home/kovid/work/calibre/src/calibre/gui2/convert/metadata.py:122 #: /home/kovid/work/calibre/src/calibre/gui2/convert/metadata.py:151 #: /home/kovid/work/calibre/src/calibre/gui2/convert/metadata.py:153 -#: /home/kovid/work/calibre/src/calibre/gui2/device.py:1089 -#: /home/kovid/work/calibre/src/calibre/gui2/device.py:1092 +#: /home/kovid/work/calibre/src/calibre/gui2/device.py:1120 +#: /home/kovid/work/calibre/src/calibre/gui2/device.py:1123 #: /home/kovid/work/calibre/src/calibre/gui2/dialogs/add_empty_book.py:56 #: /home/kovid/work/calibre/src/calibre/gui2/dialogs/add_empty_book.py:68 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/book_info.py:127 #: /home/kovid/work/calibre/src/calibre/gui2/dialogs/comicconf.py:47 -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/fetch_metadata.py:145 -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/fetch_metadata.py:185 -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single.py:732 -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/scheduler.py:193 -#: /home/kovid/work/calibre/src/calibre/gui2/email.py:236 -#: /home/kovid/work/calibre/src/calibre/gui2/email.py:245 -#: /home/kovid/work/calibre/src/calibre/gui2/library/models.py:421 -#: /home/kovid/work/calibre/src/calibre/gui2/library/models.py:440 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/scheduler.py:366 +#: /home/kovid/work/calibre/src/calibre/gui2/email.py:152 +#: /home/kovid/work/calibre/src/calibre/gui2/email.py:167 +#: /home/kovid/work/calibre/src/calibre/gui2/library/models.py:397 #: /home/kovid/work/calibre/src/calibre/gui2/library/models.py:972 -#: /home/kovid/work/calibre/src/calibre/gui2/library/models.py:1165 -#: /home/kovid/work/calibre/src/calibre/gui2/metadata/basic_widgets.py:70 -#: /home/kovid/work/calibre/src/calibre/gui2/metadata/basic_widgets.py:167 -#: /home/kovid/work/calibre/src/calibre/gui2/metadata/basic_widgets.py:185 -#: /home/kovid/work/calibre/src/calibre/gui2/metadata/bulk_download.py:112 -#: /home/kovid/work/calibre/src/calibre/gui2/viewer/main.py:197 -#: /home/kovid/work/calibre/src/calibre/library/cli.py:215 +#: /home/kovid/work/calibre/src/calibre/gui2/library/models.py:1148 +#: /home/kovid/work/calibre/src/calibre/gui2/library/models.py:1151 +#: /home/kovid/work/calibre/src/calibre/gui2/library/models.py:1154 +#: /home/kovid/work/calibre/src/calibre/gui2/library/models.py:1239 +#: /home/kovid/work/calibre/src/calibre/gui2/metadata/basic_widgets.py:82 +#: /home/kovid/work/calibre/src/calibre/gui2/metadata/basic_widgets.py:201 +#: /home/kovid/work/calibre/src/calibre/gui2/metadata/basic_widgets.py:220 +#: /home/kovid/work/calibre/src/calibre/gui2/metadata/bulk_download.py:119 +#: /home/kovid/work/calibre/src/calibre/gui2/metadata/single.py:358 +#: /home/kovid/work/calibre/src/calibre/gui2/metadata/single_download.py:160 +#: /home/kovid/work/calibre/src/calibre/gui2/metadata/single_download.py:164 +#: /home/kovid/work/calibre/src/calibre/gui2/store/google_books_plugin.py:90 +#: /home/kovid/work/calibre/src/calibre/gui2/store/search/models.py:151 +#: /home/kovid/work/calibre/src/calibre/gui2/viewer/main.py:199 +#: /home/kovid/work/calibre/src/calibre/library/cli.py:217 #: /home/kovid/work/calibre/src/calibre/library/database.py:914 -#: /home/kovid/work/calibre/src/calibre/library/database2.py:448 -#: /home/kovid/work/calibre/src/calibre/library/database2.py:454 -#: /home/kovid/work/calibre/src/calibre/library/database2.py:464 -#: /home/kovid/work/calibre/src/calibre/library/database2.py:1568 -#: /home/kovid/work/calibre/src/calibre/library/database2.py:1671 -#: /home/kovid/work/calibre/src/calibre/library/database2.py:2574 -#: /home/kovid/work/calibre/src/calibre/library/database2.py:2576 -#: /home/kovid/work/calibre/src/calibre/library/database2.py:2707 +#: /home/kovid/work/calibre/src/calibre/library/database2.py:502 +#: /home/kovid/work/calibre/src/calibre/library/database2.py:510 +#: /home/kovid/work/calibre/src/calibre/library/database2.py:521 +#: /home/kovid/work/calibre/src/calibre/library/database2.py:1800 +#: /home/kovid/work/calibre/src/calibre/library/database2.py:1937 +#: /home/kovid/work/calibre/src/calibre/library/database2.py:2944 +#: /home/kovid/work/calibre/src/calibre/library/database2.py:2946 +#: /home/kovid/work/calibre/src/calibre/library/database2.py:3079 #: /home/kovid/work/calibre/src/calibre/library/server/mobile.py:233 -#: /home/kovid/work/calibre/src/calibre/library/server/opds.py:158 -#: /home/kovid/work/calibre/src/calibre/library/server/opds.py:161 +#: /home/kovid/work/calibre/src/calibre/library/server/opds.py:156 +#: /home/kovid/work/calibre/src/calibre/library/server/opds.py:159 #: /home/kovid/work/calibre/src/calibre/library/server/xml.py:79 #: /home/kovid/work/calibre/src/calibre/utils/localization.py:131 #: /home/kovid/work/calibre/src/calibre/utils/podofo/__init__.py:46 #: /home/kovid/work/calibre/src/calibre/utils/podofo/__init__.py:64 #: /home/kovid/work/calibre/src/calibre/utils/podofo/__init__.py:78 -#: /home/kovid/work/calibre/src/calibre/web/feeds/recipes/collection.py:47 -#: /home/kovid/work/calibre/src/calibre/web/feeds/recipes/collection.py:55 +#: /home/kovid/work/calibre/src/calibre/web/feeds/recipes/collection.py:46 +#: /home/kovid/work/calibre/src/calibre/web/feeds/recipes/collection.py:54 msgid "Unknown" msgstr "Ezezaguna" -#: /home/kovid/work/calibre/src/calibre/customize/__init__.py:64 +#: /home/kovid/work/calibre/src/calibre/customize/__init__.py:77 msgid "Base" msgstr "Oinarria" -#: /home/kovid/work/calibre/src/calibre/customize/__init__.py:135 +#: /home/kovid/work/calibre/src/calibre/customize/__init__.py:148 msgid "Customize" msgstr "Pertsonalizatu" -#: /home/kovid/work/calibre/src/calibre/customize/__init__.py:143 -#: /home/kovid/work/calibre/src/calibre/gui2/actions/preferences.py:39 -#: /home/kovid/work/calibre/src/calibre/gui2/actions/preferences.py:44 +#: /home/kovid/work/calibre/src/calibre/customize/__init__.py:156 +#: /home/kovid/work/calibre/src/calibre/gui2/actions/preferences.py:43 +#: /home/kovid/work/calibre/src/calibre/gui2/actions/preferences.py:48 msgid "Cannot configure" msgstr "Cannot configure" -#: /home/kovid/work/calibre/src/calibre/customize/__init__.py:305 +#: /home/kovid/work/calibre/src/calibre/customize/__init__.py:318 msgid "File type" msgstr "Fitxategi-mota" -#: /home/kovid/work/calibre/src/calibre/customize/__init__.py:341 +#: /home/kovid/work/calibre/src/calibre/customize/__init__.py:354 msgid "Metadata reader" msgstr "Metadatuen irakurlea" -#: /home/kovid/work/calibre/src/calibre/customize/__init__.py:371 +#: /home/kovid/work/calibre/src/calibre/customize/__init__.py:384 msgid "Metadata writer" msgstr "Metadatuen idazlea" -#: /home/kovid/work/calibre/src/calibre/customize/__init__.py:401 +#: /home/kovid/work/calibre/src/calibre/customize/__init__.py:414 msgid "Catalog generator" msgstr "Katalogo-sortzailea" -#: /home/kovid/work/calibre/src/calibre/customize/__init__.py:510 +#: /home/kovid/work/calibre/src/calibre/customize/__init__.py:523 msgid "User Interface Action" msgstr "Erabiltzailearen interfaze ekintza" -#: /home/kovid/work/calibre/src/calibre/customize/__init__.py:536 +#: /home/kovid/work/calibre/src/calibre/customize/__init__.py:557 #: /home/kovid/work/calibre/src/calibre/gui2/actions/preferences.py:18 -#: /home/kovid/work/calibre/src/calibre/gui2/actions/preferences.py:23 -#: /home/kovid/work/calibre/src/calibre/gui2/preferences/main.py:190 -#: /home/kovid/work/calibre/src/calibre/gui2/preferences/main.py:280 -#: /home/kovid/work/calibre/src/calibre/gui2/preferences/main.py:302 +#: /home/kovid/work/calibre/src/calibre/gui2/actions/preferences.py:22 +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/main.py:197 +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/main.py:287 +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/main.py:309 #: /home/kovid/work/calibre/src/calibre/gui2/viewer/main_ui.py:206 msgid "Preferences" msgstr "Hobespenak" +#: /home/kovid/work/calibre/src/calibre/customize/__init__.py:609 +#: /home/kovid/work/calibre/src/calibre/gui2/store/search/models.py:33 +msgid "Store" +msgstr "" + #: /home/kovid/work/calibre/src/calibre/customize/builtins.py:18 msgid "" "Follow all local links in an HTML file and create a ZIP file containing all " @@ -249,95 +269,97 @@ msgid "" "are added to the archive." msgstr "" -#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:166 +#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:168 msgid "Extract cover from comic files" msgstr "Erauzi ezazu liburu-azala komiki fitxategietatik" -#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:195 -#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:206 -#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:218 +#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:205 +#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:216 #: /home/kovid/work/calibre/src/calibre/customize/builtins.py:228 #: /home/kovid/work/calibre/src/calibre/customize/builtins.py:238 -#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:249 +#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:248 #: /home/kovid/work/calibre/src/calibre/customize/builtins.py:259 -#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:269 -#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:279 -#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:289 -#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:299 -#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:309 +#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:270 +#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:280 +#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:290 +#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:300 +#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:310 #: /home/kovid/work/calibre/src/calibre/customize/builtins.py:320 -#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:332 +#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:330 +#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:341 #: /home/kovid/work/calibre/src/calibre/customize/builtins.py:353 -#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:364 #: /home/kovid/work/calibre/src/calibre/customize/builtins.py:374 #: /home/kovid/work/calibre/src/calibre/customize/builtins.py:385 #: /home/kovid/work/calibre/src/calibre/customize/builtins.py:395 #: /home/kovid/work/calibre/src/calibre/customize/builtins.py:406 +#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:416 +#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:427 msgid "Read metadata from %s files" msgstr "Irakur itzazu metadatuak %s fitxategietatik" -#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:343 +#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:364 msgid "Read metadata from ebooks in RAR archives" msgstr "Irakur itzazu metadatuak liburu elektronikoetatik RAR fitxategietan" -#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:417 +#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:438 msgid "Read metadata from ebooks in ZIP archives" msgstr "Irakur itzazu metadatuak liburu elektronikoetatik ZIP fitxategietan" -#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:430 -#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:440 -#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:450 +#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:451 #: /home/kovid/work/calibre/src/calibre/customize/builtins.py:472 -#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:483 -#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:493 +#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:482 +#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:504 +#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:515 +#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:525 msgid "Set metadata in %s files" msgstr "Ezarri metadatuak %s fitxategietan" #: /home/kovid/work/calibre/src/calibre/customize/builtins.py:461 -#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:504 +#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:493 +#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:536 msgid "Set metadata from %s files" msgstr "Ezarri metadatuak %s fitxategietatik" -#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:824 +#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:873 msgid "Look and Feel" msgstr "Itxura eta izaera" -#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:826 -#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:838 -#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:849 -#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:860 -#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:872 +#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:875 +#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:887 +#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:898 +#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:909 +#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:921 msgid "Interface" msgstr "Interfazea" -#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:830 +#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:879 msgid "Adjust the look and feel of the calibre interface to suit your tastes" msgstr "Doi ezazu calibreren interfazearen itxura zure gustuen arabera" -#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:836 +#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:885 msgid "Behavior" msgstr "Jokabidea" -#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:842 +#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:891 msgid "Change the way calibre behaves" msgstr "Alda ezazu calibreren jokatzeko era" -#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:847 -#: /home/kovid/work/calibre/src/calibre/gui2/library/views.py:217 +#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:896 +#: /home/kovid/work/calibre/src/calibre/gui2/library/views.py:220 msgid "Add your own columns" msgstr "Gehi itzazu zureak diren zutabeak" -#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:853 +#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:902 msgid "Add/remove your own columns to the calibre book list" msgstr "" "Gehitu/ezabatu itzazu zuk egindako zure zutabeak calibreren liburu " "zerrendara/zerrendatik" -#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:858 -msgid "Customize the toolbar" -msgstr "Pertsonalizatu tresna-barra" +#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:907 +msgid "Toolbar" +msgstr "" -#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:864 +#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:913 msgid "" "Customize the toolbars and context menus, changing which actions are " "available in each" @@ -345,66 +367,66 @@ msgstr "" "Pertsonalizatu tresna-barra eta testuinguruaren araberako menuak, bakoitzean " "eskuragarri agertuko diren ekintzekin aldatuz." -#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:870 -msgid "Customize searching" -msgstr "" +#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:919 +msgid "Searching" +msgstr "Bilatzen" -#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:876 +#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:925 msgid "Customize the way searching for books works in calibre" -msgstr "" +msgstr "Pertsonalizatu nola bilatu liburuak calibren" -#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:881 +#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:930 msgid "Input Options" msgstr "Sorburu aukerak" -#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:883 -#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:894 -#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:905 +#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:932 +#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:943 +#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:954 msgid "Conversion" msgstr "Bihurketa" -#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:887 +#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:936 msgid "Set conversion options specific to each input format" msgstr "Ezarri itzazu sorburu formatu bakoitzeko bihurketa aukera zehatzak" -#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:892 +#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:941 msgid "Common Options" msgstr "Aukera komunak" -#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:898 +#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:947 msgid "Set conversion options common to all formats" msgstr "Ezarri itzazu formatu guztietarako komunak diren bihurketa aukerak" -#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:903 +#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:952 msgid "Output Options" msgstr "Helburu aukerak" -#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:909 +#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:958 msgid "Set conversion options specific to each output format" msgstr "Ezarri itzazu helburu formatu bakoitzeko bihurketa aukera zehatzak" -#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:914 +#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:963 msgid "Adding books" msgstr "Liburuak gehitzen" -#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:916 -#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:928 -#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:940 -#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:952 +#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:965 +#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:977 +#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:989 +#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:1001 msgid "Import/Export" msgstr "Inportatu/Esportatu" -#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:920 +#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:969 msgid "Control how calibre reads metadata from files when adding books" msgstr "" "Kontrola ezazu calibrek nola irakurtzen dituen metadatuak fitxategietatik " "liburuak gehitzerakoan" -#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:926 +#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:975 msgid "Saving books to disk" msgstr "Liburuak diskan gordetzen" -#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:932 +#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:981 msgid "" "Control how calibre exports files from its database to disk when using Save " "to disk" @@ -412,49 +434,50 @@ msgstr "" "Kontrola ezazu calibrek nola esportatzen dituen fitxategiak bere datu " "basetik diskora \"Diskoan gorde\" aukera erabiltzen denean." -#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:938 +#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:987 msgid "Sending books to devices" msgstr "Bidaltzen liburuak gailuetara" -#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:944 +#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:993 msgid "Control how calibre transfers files to your ebook reader" msgstr "" "Kontrola ezazu ea calibrek nola bidaltzen dituen fitxategiak zure liburu " "elektronikoetara" -#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:950 +#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:999 msgid "Metadata plugboards" msgstr "Metadatuen konektore-txartela" -#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:956 +#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:1005 msgid "Change metadata fields before saving/sending" msgstr "Aldatu metadatu eremuak gorde/igorri baino lehenago" -#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:961 +#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:1010 msgid "Template Functions" msgstr "Txantiloi funtzioak" -#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:963 -#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:999 -#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:1011 -#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:1022 +#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:1012 +#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:1059 +#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:1071 +#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:1082 msgid "Advanced" msgstr "Aurreratua" -#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:967 +#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:1016 msgid "Create your own template functions" msgstr "Sortu txantiloi funtzio berriak" -#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:972 +#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:1021 msgid "Sharing books by email" msgstr "Liburuak e-posta bidez partekatzen" -#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:974 -#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:986 +#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:1023 +#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:1035 +#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:1048 msgid "Sharing" msgstr "Partekatzen" -#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:978 +#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:1027 msgid "" "Setup sharing of books via email. Can be used for automatic sending of " "downloaded news to your devices" @@ -462,11 +485,11 @@ msgstr "" "Antolatu liburuen elkarbanatzea e-postaren bidez. Saretik deskargatutako " "albisteak norbere gailuetara automatikoki bidaltzeko erabil daiteke" -#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:984 +#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:1033 msgid "Sharing over the net" msgstr "Sarean zehar elkarbanatzen" -#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:990 +#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:1039 msgid "" "Setup the calibre Content Server which will give you access to your calibre " "library from anywhere, on any device, over the internet" @@ -475,33 +498,147 @@ msgstr "" "interneten bidezko sarbidea emango dizun edozein lekutan eta edozein " "gailuren bidez" -#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:997 -#: /home/kovid/work/calibre/src/calibre/gui2/preferences/plugins.py:267 +#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:1046 +msgid "Metadata download" +msgstr "Metadatuak deskargatu" + +#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:1052 +msgid "Control how calibre downloads ebook metadata from the net" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:1057 +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/plugins.py:273 msgid "Plugins" msgstr "Pluginak" -#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:1003 +#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:1063 msgid "Add/remove/customize various bits of calibre functionality" msgstr "Gehitu/ezabatu/pertsonalizatu calibreren zenbait aukera" -#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:1009 +#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:1069 msgid "Tweaks" msgstr "Doikuntzak" -#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:1015 +#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:1075 msgid "Fine tune how calibre behaves in various contexts" msgstr "" "Afina ezazu zehaztasun handiz nola jokatuko duen calibrek hainbat " "testuingurutan" -#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:1020 +#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:1080 msgid "Miscellaneous" msgstr "Denetarik" -#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:1026 +#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:1086 msgid "Miscellaneous advanced configuration" msgstr "Hainbat gauzetarako ezarpen aurreratuak" +#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:1098 +msgid "Kindle books from Amazon." +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:1103 +msgid "Kindle books from Amazon.de." +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:1108 +msgid "Kindle books from Amazon.uk." +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:1113 +msgid "" +"Free Books : Download & Streaming : Ebook and Texts Archive : Internet " +"Archive." +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:1119 +msgid "Ebooks for readers." +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:1124 +msgid "Books, Textbooks, eBooks, Toys, Games and More." +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:1129 +msgid "Der eBook Shop." +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:1134 +msgid "Publishers of fine books." +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:1139 +msgid "World Famous eBook Store." +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:1144 +msgid "The digital bookstore." +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:1149 +msgid "EPUBReaders eBook Shop." +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:1154 +msgid "Entertain, enrich, inspire." +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:1159 +msgid "Read anywhere." +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:1164 +msgid "Foyles of London, online." +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:1170 +msgid "Zaczarowany świat książek" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:1175 +msgid "Google Books" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:1180 +msgid "The first producer of free ebooks." +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:1185 +msgid "eReading: anytime. anyplace." +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:1190 +msgid "The best ebooks at the best price: free!" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:1195 +msgid "Ebooks handcrafted with the utmost care." +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:1201 +msgid "Audiobooki mp3, ebooki, prasa - księgarnia internetowa." +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:1206 +msgid "One web page for every book." +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:1211 +msgid "DRM-Free tech ebooks." +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:1216 +msgid "The Pragmatic Bookshelf" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:1221 +msgid "Your ebook. Your way." +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:1226 +msgid "Feel every word." +msgstr "" + #: /home/kovid/work/calibre/src/calibre/customize/conversion.py:102 msgid "Conversion Input" msgstr "Bihurketa-sarrera" @@ -544,7 +681,7 @@ msgstr "" "da sarrerako dokumentuari buruz ezer ez dakizunean." #: /home/kovid/work/calibre/src/calibre/customize/profiles.py:61 -#: /home/kovid/work/calibre/src/calibre/customize/profiles.py:453 +#: /home/kovid/work/calibre/src/calibre/customize/profiles.py:454 msgid "" "This profile is intended for the SONY PRS line. The 500/505/600/700 etc." msgstr "" @@ -556,62 +693,62 @@ msgid "This profile is intended for the SONY PRS 300." msgstr "Profil hau \"SONY PRS 300\" horretara zuzendurik dago." #: /home/kovid/work/calibre/src/calibre/customize/profiles.py:82 -#: /home/kovid/work/calibre/src/calibre/customize/profiles.py:493 +#: /home/kovid/work/calibre/src/calibre/customize/profiles.py:494 msgid "This profile is intended for the SONY PRS-900." msgstr "Profil hau \"SONY PRS 900\" horretara zuzendurik dago." #: /home/kovid/work/calibre/src/calibre/customize/profiles.py:90 -#: /home/kovid/work/calibre/src/calibre/customize/profiles.py:538 +#: /home/kovid/work/calibre/src/calibre/customize/profiles.py:539 msgid "This profile is intended for the Microsoft Reader." msgstr "Profil hau Microsoft Reader-arentzat zuzendua dago." #: /home/kovid/work/calibre/src/calibre/customize/profiles.py:101 -#: /home/kovid/work/calibre/src/calibre/customize/profiles.py:549 +#: /home/kovid/work/calibre/src/calibre/customize/profiles.py:550 msgid "This profile is intended for the Mobipocket books." msgstr "Profil hau Mobipocket liburuentzat zuzendua dago." #: /home/kovid/work/calibre/src/calibre/customize/profiles.py:114 -#: /home/kovid/work/calibre/src/calibre/customize/profiles.py:562 +#: /home/kovid/work/calibre/src/calibre/customize/profiles.py:563 msgid "This profile is intended for the Hanlin V3 and its clones." msgstr "Profil hau Hanlin V3 eta bere klonentzat zuzendua dago." #: /home/kovid/work/calibre/src/calibre/customize/profiles.py:126 -#: /home/kovid/work/calibre/src/calibre/customize/profiles.py:574 +#: /home/kovid/work/calibre/src/calibre/customize/profiles.py:575 msgid "This profile is intended for the Hanlin V5 and its clones." msgstr "Profil hau \"Hanlin V5\" eta bere klonetara zuzendurik dago." #: /home/kovid/work/calibre/src/calibre/customize/profiles.py:136 -#: /home/kovid/work/calibre/src/calibre/customize/profiles.py:582 +#: /home/kovid/work/calibre/src/calibre/customize/profiles.py:583 msgid "This profile is intended for the Cybook G3." msgstr "Profil hau Cybook G3-arentzat zuzendua dago." #: /home/kovid/work/calibre/src/calibre/customize/profiles.py:149 -#: /home/kovid/work/calibre/src/calibre/customize/profiles.py:596 +#: /home/kovid/work/calibre/src/calibre/customize/profiles.py:597 msgid "This profile is intended for the Cybook Opus." msgstr "Profil hau Cybook Opus-arentzat zuzendua dago." #: /home/kovid/work/calibre/src/calibre/customize/profiles.py:161 -#: /home/kovid/work/calibre/src/calibre/customize/profiles.py:609 +#: /home/kovid/work/calibre/src/calibre/customize/profiles.py:610 msgid "This profile is intended for the Amazon Kindle." msgstr "Profil hau Amazon Kindle gailuarentzat dago prestaturik." #: /home/kovid/work/calibre/src/calibre/customize/profiles.py:173 -#: /home/kovid/work/calibre/src/calibre/customize/profiles.py:659 +#: /home/kovid/work/calibre/src/calibre/customize/profiles.py:660 msgid "This profile is intended for the Irex Illiad." msgstr "Profil hau Irex Illiad-arentzat zuzendua dago." #: /home/kovid/work/calibre/src/calibre/customize/profiles.py:185 -#: /home/kovid/work/calibre/src/calibre/customize/profiles.py:672 +#: /home/kovid/work/calibre/src/calibre/customize/profiles.py:673 msgid "This profile is intended for the IRex Digital Reader 1000." msgstr "Profil hau IRex Digital Reader 1000-rentzat zuzendua dago." #: /home/kovid/work/calibre/src/calibre/customize/profiles.py:198 -#: /home/kovid/work/calibre/src/calibre/customize/profiles.py:686 +#: /home/kovid/work/calibre/src/calibre/customize/profiles.py:687 msgid "This profile is intended for the IRex Digital Reader 800." msgstr "Profil hau \"IRex Digital Reader 800\" horrentzat dago prestaturik." #: /home/kovid/work/calibre/src/calibre/customize/profiles.py:210 -#: /home/kovid/work/calibre/src/calibre/customize/profiles.py:700 +#: /home/kovid/work/calibre/src/calibre/customize/profiles.py:701 msgid "This profile is intended for the B&N Nook." msgstr "Profil hau \"B&N Nook\" horrentzat dago prestaturik." @@ -636,13 +773,13 @@ msgstr "" "iPad eta antzeko trepetetarako zuzendurik, 768x1024 bereizmena duten " "trepetetarako." -#: /home/kovid/work/calibre/src/calibre/customize/profiles.py:437 +#: /home/kovid/work/calibre/src/calibre/customize/profiles.py:438 msgid "Intended for generic tablet devices, does no resizing of images" msgstr "" "Edozein tablet gailutan erabiltzeko asmoz, ez ditu irudien neurriak modu " "automatikoan aldatuko" -#: /home/kovid/work/calibre/src/calibre/customize/profiles.py:445 +#: /home/kovid/work/calibre/src/calibre/customize/profiles.py:446 msgid "" "Intended for the Samsung Galaxy and similar tablet devices with a resolution " "of 600x1280" @@ -650,29 +787,29 @@ msgstr "" "Samsung Galaxy eta antzeko tablet gailuentzat prestatua, 600x1280 " "bereizmenarekin." -#: /home/kovid/work/calibre/src/calibre/customize/profiles.py:471 +#: /home/kovid/work/calibre/src/calibre/customize/profiles.py:472 msgid "This profile is intended for the Kobo Reader." msgstr "Profil hau \"Kobo Reader\" horietara zuzendurik dago." -#: /home/kovid/work/calibre/src/calibre/customize/profiles.py:484 +#: /home/kovid/work/calibre/src/calibre/customize/profiles.py:485 msgid "This profile is intended for the SONY PRS-300." msgstr "Profil hau \"SONY PRS 300\" horretara zuzendurik dago." -#: /home/kovid/work/calibre/src/calibre/customize/profiles.py:502 +#: /home/kovid/work/calibre/src/calibre/customize/profiles.py:503 msgid "Suitable for use with any e-ink device" msgstr "Tinta elektronikoa darabilen edozein gailurekin erabil daiteke" -#: /home/kovid/work/calibre/src/calibre/customize/profiles.py:509 +#: /home/kovid/work/calibre/src/calibre/customize/profiles.py:510 msgid "Suitable for use with any large screen e-ink device" msgstr "" "Tinta elektronikoa darabilen eta pantaila handia duen edozein gailurekin " "erabil daiteke" -#: /home/kovid/work/calibre/src/calibre/customize/profiles.py:518 +#: /home/kovid/work/calibre/src/calibre/customize/profiles.py:519 msgid "This profile is intended for the 5-inch JetBook." msgstr "Profil hau 5 hazbeteko JetBook gailuarentzat dago prestaturik." -#: /home/kovid/work/calibre/src/calibre/customize/profiles.py:527 +#: /home/kovid/work/calibre/src/calibre/customize/profiles.py:528 msgid "" "This profile is intended for the SONY PRS line. The 500/505/700 etc, in " "landscape mode. Mainly useful for comics." @@ -681,48 +818,44 @@ msgstr "" "500/505/600/700 eta abar orrialdea horizontal moduan erakutsita. Batez ere " "komikietan erabilgarria." -#: /home/kovid/work/calibre/src/calibre/customize/profiles.py:635 +#: /home/kovid/work/calibre/src/calibre/customize/profiles.py:636 msgid "This profile is intended for the Amazon Kindle DX." msgstr "Profil hau Amazon Kindle DX-arentzat zuzendua dago." -#: /home/kovid/work/calibre/src/calibre/customize/profiles.py:712 +#: /home/kovid/work/calibre/src/calibre/customize/profiles.py:713 msgid "This profile is intended for the B&N Nook Color." msgstr "Profil hau B&N Nook Color horrentzat sortu da." -#: /home/kovid/work/calibre/src/calibre/customize/profiles.py:723 +#: /home/kovid/work/calibre/src/calibre/customize/profiles.py:724 msgid "This profile is intended for the Sanda Bambook." msgstr "Sanda Bambook horrentzat egindako profila." -#: /home/kovid/work/calibre/src/calibre/customize/ui.py:35 +#: /home/kovid/work/calibre/src/calibre/customize/ui.py:31 msgid "Installed plugins" msgstr "Instalaturiko gehigarriak" -#: /home/kovid/work/calibre/src/calibre/customize/ui.py:36 +#: /home/kovid/work/calibre/src/calibre/customize/ui.py:32 msgid "Mapping for filetype plugins" msgstr "Mapaketa fitxategi-mota gehigarrientzat" -#: /home/kovid/work/calibre/src/calibre/customize/ui.py:37 +#: /home/kovid/work/calibre/src/calibre/customize/ui.py:33 msgid "Local plugin customization" msgstr "Lokal gehigarrien pertsonalizazioa" -#: /home/kovid/work/calibre/src/calibre/customize/ui.py:38 +#: /home/kovid/work/calibre/src/calibre/customize/ui.py:34 msgid "Disabled plugins" msgstr "Desgaitutako gehigarriak" -#: /home/kovid/work/calibre/src/calibre/customize/ui.py:39 +#: /home/kovid/work/calibre/src/calibre/customize/ui.py:35 msgid "Enabled plugins" msgstr "Gehigarri gaituak" -#: /home/kovid/work/calibre/src/calibre/customize/ui.py:94 -msgid "No valid plugin found in " -msgstr "Baliogabeko gehigarria aurkitu da hemen: " - -#: /home/kovid/work/calibre/src/calibre/customize/ui.py:520 +#: /home/kovid/work/calibre/src/calibre/customize/ui.py:470 msgid "Initialization of plugin %s failed with traceback:" msgstr "" "%s gehigarriaren abiarazteak huts egin du eta ondoko aztarna utzi du:" -#: /home/kovid/work/calibre/src/calibre/customize/ui.py:553 +#: /home/kovid/work/calibre/src/calibre/customize/ui.py:508 msgid "" " %prog options\n" "\n" @@ -734,18 +867,18 @@ msgstr "" " Pertsonalizatu calibre kanpoko gehigarriak kargatuz.\n" " " -#: /home/kovid/work/calibre/src/calibre/customize/ui.py:559 +#: /home/kovid/work/calibre/src/calibre/customize/ui.py:514 msgid "Add a plugin by specifying the path to the zip file containing it." msgstr "" "Gehigarria gehitu bere barnean duen ZIP fitxategiaren bidea adieraziz." -#: /home/kovid/work/calibre/src/calibre/customize/ui.py:561 +#: /home/kovid/work/calibre/src/calibre/customize/ui.py:516 msgid "Remove a custom plugin by name. Has no effect on builtin plugins" msgstr "" "Pertsonalizatutako gehigarria izenez kendu. Ez du efekturik izango " "\"builtin\" gehigarrietan, \"Nola eraiki zen\" gehigarrietan." -#: /home/kovid/work/calibre/src/calibre/customize/ui.py:563 +#: /home/kovid/work/calibre/src/calibre/customize/ui.py:518 msgid "" "Customize plugin. Specify name of plugin and customization string separated " "by a comma." @@ -753,19 +886,19 @@ msgstr "" "Pertsonalizatu gehigarria. Adierazi gehigarriaren izena eta " "pertsonalizaturiko katearena komaren bidez bereizirik." -#: /home/kovid/work/calibre/src/calibre/customize/ui.py:565 +#: /home/kovid/work/calibre/src/calibre/customize/ui.py:520 msgid "List all installed plugins" msgstr "Zerrendatu instalatutako gehigarri guztiak" -#: /home/kovid/work/calibre/src/calibre/customize/ui.py:567 +#: /home/kovid/work/calibre/src/calibre/customize/ui.py:522 msgid "Enable the named plugin" msgstr "Gaitu izendaturiko gehigarria." -#: /home/kovid/work/calibre/src/calibre/customize/ui.py:569 +#: /home/kovid/work/calibre/src/calibre/customize/ui.py:524 msgid "Disable the named plugin" msgstr "Desgaitu izendaturiko gehigarria." -#: /home/kovid/work/calibre/src/calibre/debug.py:150 +#: /home/kovid/work/calibre/src/calibre/debug.py:152 msgid "Debug log" msgstr "Araztu saioa" @@ -773,7 +906,7 @@ msgstr "Araztu saioa" msgid "Communicate with Android phones." msgstr "Adroid telefonoekin komunikatu." -#: /home/kovid/work/calibre/src/calibre/devices/android/driver.py:74 +#: /home/kovid/work/calibre/src/calibre/devices/android/driver.py:96 msgid "" "Comma separated list of directories to send e-books to on the device. The " "first one that exists will be used" @@ -781,25 +914,63 @@ msgstr "" "Gailuan dagoen komen bitartez bereizitako direktorioen zerrenda, liburu " "elektronikoak hara igortzeko. Existitzen den lehena erabiliko da." -#: /home/kovid/work/calibre/src/calibre/devices/android/driver.py:121 +#: /home/kovid/work/calibre/src/calibre/devices/android/driver.py:146 msgid "Communicate with S60 phones." msgstr "S60 telefonoekin komunikatu." -#: /home/kovid/work/calibre/src/calibre/devices/apple/driver.py:92 +#: /home/kovid/work/calibre/src/calibre/devices/apple/driver.py:47 +msgid "" +"

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

      To " +"transfer books to your iDevice, click Disable Apple Driver, then use " +"the 'Connect to iTunes' method recommended in the Calibre + " +"iDevices FAQ, using the Connect/Share|Connect to " +"iTunes menu item.

      Enabling the Apple driver for direct connection " +"to iDevices is an unsupported advanced user mode.

      " +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/devices/apple/driver.py:65 +msgid "Disable Apple driver" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/devices/apple/driver.py:69 +msgid "Enable Apple driver" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/devices/apple/driver.py:117 +msgid "Use Series as Category in iTunes/iBooks" +msgstr "" +"iTunes/iBooks horietan serieak erabiltzen ditu kategoriak izango balira " +"bezala." + +#: /home/kovid/work/calibre/src/calibre/devices/apple/driver.py:118 +msgid "Enable to use the series name as the iTunes Genre, iBooks Category" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/devices/apple/driver.py:120 +msgid "Cache covers from iTunes/iBooks" +msgstr "iTunes/iBooks horietatik cache-azalak" + +#: /home/kovid/work/calibre/src/calibre/devices/apple/driver.py:122 +msgid "Enable to cache and display covers from iTunes/iBooks" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/devices/apple/driver.py:178 msgid "Apple device" msgstr "Apple markako gailua" -#: /home/kovid/work/calibre/src/calibre/devices/apple/driver.py:94 +#: /home/kovid/work/calibre/src/calibre/devices/apple/driver.py:180 msgid "Communicate with iTunes/iBooks." msgstr "iTunes/iBooks horiekin komunikatu." -#: /home/kovid/work/calibre/src/calibre/devices/apple/driver.py:100 +#: /home/kovid/work/calibre/src/calibre/devices/apple/driver.py:192 msgid "Apple device detected, launching iTunes, please wait ..." msgstr "" "Apple enpresako gailua detektatu egin da, iTunes-en nabigatzen ari da, " "mesedez itxaron..." -#: /home/kovid/work/calibre/src/calibre/devices/apple/driver.py:102 +#: /home/kovid/work/calibre/src/calibre/devices/apple/driver.py:194 msgid "" "Cannot copy books directly from iDevice. Drag from iTunes Library to " "desktop, then add to calibre's Library window." @@ -807,37 +978,28 @@ msgstr "" "Ezin dira liburuak zuzenean gailu elektronikotik kopiatu. Herrestan eraman " "iTunes Liburutegitik mahaigainera, gero itsatsi calibre liburutegiko leihoan." -#: /home/kovid/work/calibre/src/calibre/devices/apple/driver.py:262 -#: /home/kovid/work/calibre/src/calibre/devices/apple/driver.py:265 +#: /home/kovid/work/calibre/src/calibre/devices/apple/driver.py:357 +#: /home/kovid/work/calibre/src/calibre/devices/apple/driver.py:360 msgid "Updating device metadata listing..." msgstr "Irakurgailuaren zerrendatze metadatuak eguneratzen..." -#: /home/kovid/work/calibre/src/calibre/devices/apple/driver.py:341 -#: /home/kovid/work/calibre/src/calibre/devices/apple/driver.py:380 -#: /home/kovid/work/calibre/src/calibre/devices/apple/driver.py:949 -#: /home/kovid/work/calibre/src/calibre/devices/apple/driver.py:989 -#: /home/kovid/work/calibre/src/calibre/devices/apple/driver.py:2971 -#: /home/kovid/work/calibre/src/calibre/devices/apple/driver.py:3011 +#: /home/kovid/work/calibre/src/calibre/devices/apple/driver.py:436 +#: /home/kovid/work/calibre/src/calibre/devices/apple/driver.py:475 +#: /home/kovid/work/calibre/src/calibre/devices/apple/driver.py:1057 +#: /home/kovid/work/calibre/src/calibre/devices/apple/driver.py:1101 +#: /home/kovid/work/calibre/src/calibre/devices/apple/driver.py:3097 +#: /home/kovid/work/calibre/src/calibre/devices/apple/driver.py:3137 msgid "%d of %d" msgstr "%d-tik %d" -#: /home/kovid/work/calibre/src/calibre/devices/apple/driver.py:387 -#: /home/kovid/work/calibre/src/calibre/devices/apple/driver.py:994 -#: /home/kovid/work/calibre/src/calibre/devices/apple/driver.py:3017 +#: /home/kovid/work/calibre/src/calibre/devices/apple/driver.py:482 +#: /home/kovid/work/calibre/src/calibre/devices/apple/driver.py:1106 +#: /home/kovid/work/calibre/src/calibre/devices/apple/driver.py:3143 +#: /home/kovid/work/calibre/src/calibre/gui2/ebook_download.py:106 msgid "finished" msgstr "amaiturik" -#: /home/kovid/work/calibre/src/calibre/devices/apple/driver.py:562 -msgid "Use Series as Category in iTunes/iBooks" -msgstr "" -"iTunes/iBooks horietan serieak erabiltzen ditu kategoriak izango balira " -"bezala." - -#: /home/kovid/work/calibre/src/calibre/devices/apple/driver.py:564 -msgid "Cache covers from iTunes/iBooks" -msgstr "iTunes/iBooks horietatik cache-azalak" - -#: /home/kovid/work/calibre/src/calibre/devices/apple/driver.py:576 +#: /home/kovid/work/calibre/src/calibre/devices/apple/driver.py:667 msgid "" "Some books not found in iTunes database.\n" "Delete using the iBooks app.\n" @@ -847,7 +1009,7 @@ msgstr "" "Ezabatu \"iBooks app\" erabiliz.\n" "Egin ezazu klik 'Zehaztasunak erakutsi' zerrenda ikusteko." -#: /home/kovid/work/calibre/src/calibre/devices/apple/driver.py:913 +#: /home/kovid/work/calibre/src/calibre/devices/apple/driver.py:1018 msgid "" "Some cover art could not be converted.\n" "Click 'Show Details' for a list." @@ -855,30 +1017,31 @@ msgstr "" "Azalaren arte lan batzuk ezin izan dira bihurtu.\n" "Egin ezazu klik 'Zehaztasunak erakutsi' zerrenda ikusteko." -#: /home/kovid/work/calibre/src/calibre/devices/apple/driver.py:2552 +#: /home/kovid/work/calibre/src/calibre/devices/apple/driver.py:2668 #: /home/kovid/work/calibre/src/calibre/devices/nook/driver.py:100 #: /home/kovid/work/calibre/src/calibre/devices/prs505/sony_cache.py:447 #: /home/kovid/work/calibre/src/calibre/devices/prs505/sony_cache.py:470 -#: /home/kovid/work/calibre/src/calibre/devices/usbms/device.py:908 -#: /home/kovid/work/calibre/src/calibre/devices/usbms/device.py:914 -#: /home/kovid/work/calibre/src/calibre/devices/usbms/device.py:944 -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/scheduler.py:262 -#: /home/kovid/work/calibre/src/calibre/library/database2.py:255 -#: /home/kovid/work/calibre/src/calibre/library/database2.py:268 -#: /home/kovid/work/calibre/src/calibre/library/database2.py:2438 -#: /home/kovid/work/calibre/src/calibre/library/field_metadata.py:150 +#: /home/kovid/work/calibre/src/calibre/devices/usbms/device.py:909 +#: /home/kovid/work/calibre/src/calibre/devices/usbms/device.py:915 +#: /home/kovid/work/calibre/src/calibre/devices/usbms/device.py:945 +#: /home/kovid/work/calibre/src/calibre/gui2/actions/fetch_news.py:73 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/scheduler.py:445 +#: /home/kovid/work/calibre/src/calibre/library/database2.py:298 +#: /home/kovid/work/calibre/src/calibre/library/database2.py:311 +#: /home/kovid/work/calibre/src/calibre/library/database2.py:2808 +#: /home/kovid/work/calibre/src/calibre/library/field_metadata.py:159 msgid "News" msgstr "Albisteak" -#: /home/kovid/work/calibre/src/calibre/devices/apple/driver.py:2553 +#: /home/kovid/work/calibre/src/calibre/devices/apple/driver.py:2669 #: /home/kovid/work/calibre/src/calibre/gui2/catalog/catalog_epub_mobi.py:65 -#: /home/kovid/work/calibre/src/calibre/library/catalog.py:634 -#: /home/kovid/work/calibre/src/calibre/library/database2.py:2401 -#: /home/kovid/work/calibre/src/calibre/library/database2.py:2419 +#: /home/kovid/work/calibre/src/calibre/library/catalog.py:643 +#: /home/kovid/work/calibre/src/calibre/library/database2.py:2768 +#: /home/kovid/work/calibre/src/calibre/library/database2.py:2786 msgid "Catalog" msgstr "Katalogoa" -#: /home/kovid/work/calibre/src/calibre/devices/apple/driver.py:2875 +#: /home/kovid/work/calibre/src/calibre/devices/apple/driver.py:3001 msgid "Communicate with iTunes." msgstr "Komunikatu iTunes horrekin ." @@ -906,7 +1069,7 @@ msgstr "" msgid "" "Unable to connect to Bambook, you need to install Bambook library first." msgstr "" -"Ezin izan da Bambook horrekin konektatu, lehenago instalatu beharko duzu " +"Ezin izan da Bambook-arekin konektatu, lehenago instalatu beharko duzu " "Bambook liburutegia." #: /home/kovid/work/calibre/src/calibre/devices/bambook/driver.py:75 @@ -915,8 +1078,8 @@ msgid "" "If you are trying to connect via Wi-Fi, please make sure the IP address of " "Bambook has been correctly configured." msgstr "" -"Ezin izan da Bambook horrekin konektatu. \n" -"Wi-Fi-aren bidez saiatzen ari bazara, konprobatu Bambook horren IP helbidea " +"Ezin izan da Bambook-arekin konektatu. \n" +"Wi-Fi-aren bidez saiatzen ari bazara, konprobatu Bambook-aren IP helbidea " "behar bezala zehaztu duzula." #: /home/kovid/work/calibre/src/calibre/devices/bambook/driver.py:112 @@ -928,30 +1091,30 @@ msgstr "Bambook" #: /home/kovid/work/calibre/src/calibre/devices/kobo/driver.py:67 #: /home/kovid/work/calibre/src/calibre/devices/kobo/driver.py:70 #: /home/kovid/work/calibre/src/calibre/devices/kobo/driver.py:73 -#: /home/kovid/work/calibre/src/calibre/devices/kobo/driver.py:226 -#: /home/kovid/work/calibre/src/calibre/devices/usbms/driver.py:68 -#: /home/kovid/work/calibre/src/calibre/devices/usbms/driver.py:71 -#: /home/kovid/work/calibre/src/calibre/devices/usbms/driver.py:74 -#: /home/kovid/work/calibre/src/calibre/devices/usbms/driver.py:138 -#: /home/kovid/work/calibre/src/calibre/devices/usbms/driver.py:145 -#: /home/kovid/work/calibre/src/calibre/devices/usbms/driver.py:168 +#: /home/kovid/work/calibre/src/calibre/devices/kobo/driver.py:232 +#: /home/kovid/work/calibre/src/calibre/devices/usbms/driver.py:122 +#: /home/kovid/work/calibre/src/calibre/devices/usbms/driver.py:125 +#: /home/kovid/work/calibre/src/calibre/devices/usbms/driver.py:128 +#: /home/kovid/work/calibre/src/calibre/devices/usbms/driver.py:196 +#: /home/kovid/work/calibre/src/calibre/devices/usbms/driver.py:203 +#: /home/kovid/work/calibre/src/calibre/devices/usbms/driver.py:226 msgid "Getting list of books on device..." msgstr "Liburu zerrenda gailutik eskuratzen..." #: /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:279 -#: /home/kovid/work/calibre/src/calibre/devices/usbms/driver.py:197 -#: /home/kovid/work/calibre/src/calibre/devices/usbms/driver.py:199 +#: /home/kovid/work/calibre/src/calibre/devices/usbms/driver.py:255 +#: /home/kovid/work/calibre/src/calibre/devices/usbms/driver.py:257 msgid "Transferring books to device..." msgstr "Liburuak gailura transferitzen..." #: /home/kovid/work/calibre/src/calibre/devices/bambook/driver.py:285 #: /home/kovid/work/calibre/src/calibre/devices/bambook/driver.py:299 -#: /home/kovid/work/calibre/src/calibre/devices/kobo/driver.py:343 -#: /home/kovid/work/calibre/src/calibre/devices/kobo/driver.py:378 -#: /home/kovid/work/calibre/src/calibre/devices/usbms/driver.py:221 -#: /home/kovid/work/calibre/src/calibre/devices/usbms/driver.py:252 +#: /home/kovid/work/calibre/src/calibre/devices/kobo/driver.py:349 +#: /home/kovid/work/calibre/src/calibre/devices/kobo/driver.py:384 +#: /home/kovid/work/calibre/src/calibre/devices/usbms/driver.py:279 +#: /home/kovid/work/calibre/src/calibre/devices/usbms/driver.py:310 msgid "Adding books to device metadata listing..." msgstr "Gailuaren metadatu zerrendara liburuak gehitzen..." @@ -959,28 +1122,28 @@ msgstr "Gailuaren metadatu zerrendara liburuak gehitzen..." #: /home/kovid/work/calibre/src/calibre/devices/bambook/driver.py:309 #: /home/kovid/work/calibre/src/calibre/devices/hanvon/driver.py:102 #: /home/kovid/work/calibre/src/calibre/devices/hanvon/driver.py:113 -#: /home/kovid/work/calibre/src/calibre/devices/kobo/driver.py:295 -#: /home/kovid/work/calibre/src/calibre/devices/kobo/driver.py:327 -#: /home/kovid/work/calibre/src/calibre/devices/usbms/driver.py:258 -#: /home/kovid/work/calibre/src/calibre/devices/usbms/driver.py:276 +#: /home/kovid/work/calibre/src/calibre/devices/kobo/driver.py:301 +#: /home/kovid/work/calibre/src/calibre/devices/kobo/driver.py:333 +#: /home/kovid/work/calibre/src/calibre/devices/usbms/driver.py:316 +#: /home/kovid/work/calibre/src/calibre/devices/usbms/driver.py:334 msgid "Removing books from device..." msgstr "Gailutik liburuak ezabatzen..." #: /home/kovid/work/calibre/src/calibre/devices/bambook/driver.py:324 #: /home/kovid/work/calibre/src/calibre/devices/bambook/driver.py:329 -#: /home/kovid/work/calibre/src/calibre/devices/kobo/driver.py:331 -#: /home/kovid/work/calibre/src/calibre/devices/kobo/driver.py:338 -#: /home/kovid/work/calibre/src/calibre/devices/usbms/driver.py:283 -#: /home/kovid/work/calibre/src/calibre/devices/usbms/driver.py:288 +#: /home/kovid/work/calibre/src/calibre/devices/kobo/driver.py:337 +#: /home/kovid/work/calibre/src/calibre/devices/kobo/driver.py:344 +#: /home/kovid/work/calibre/src/calibre/devices/usbms/driver.py:341 +#: /home/kovid/work/calibre/src/calibre/devices/usbms/driver.py:346 msgid "Removing books from device metadata listing..." msgstr "Gailuaren metadatu zerrendatik liburuak kentzen..." #: /home/kovid/work/calibre/src/calibre/devices/bambook/driver.py:397 -#: /home/kovid/work/calibre/src/calibre/devices/usbms/driver.py:318 +#: /home/kovid/work/calibre/src/calibre/devices/usbms/driver.py:376 msgid "Sending metadata to device..." msgstr "Metadatuak gailura bidaltzen..." -#: /home/kovid/work/calibre/src/calibre/devices/bambook/libbambookcore.py:132 +#: /home/kovid/work/calibre/src/calibre/devices/bambook/libbambookcore.py:129 msgid "Bambook SDK has not been installed." msgstr "Bambook SDK ez da instalatu." @@ -993,12 +1156,20 @@ msgid "Communicate with the Blackberry smart phone." msgstr "Komunikatu Blackberry smart telefonoarekin." #: /home/kovid/work/calibre/src/calibre/devices/blackberry/driver.py:14 -#: /home/kovid/work/calibre/src/calibre/devices/eb600/driver.py:253 +#: /home/kovid/work/calibre/src/calibre/devices/eb600/driver.py:254 #: /home/kovid/work/calibre/src/calibre/devices/nuut2/driver.py:18 #: /home/kovid/work/calibre/src/calibre/devices/prs500/driver.py:90 msgid "Kovid Goyal" msgstr "Kovid Goyal" +#: /home/kovid/work/calibre/src/calibre/devices/boeye/driver.py:14 +msgid "Communicate with BOEYE BEX Serial eBook readers." +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/devices/boeye/driver.py:35 +msgid "Communicate with BOEYE BDX serial eBook readers." +msgstr "" + #: /home/kovid/work/calibre/src/calibre/devices/cybook/driver.py:22 msgid "Communicate with the Cybook Gen 3 / Opus eBook reader." msgstr "Komunikatu \"Cybook Gen 3 / Opus eBook reader\" horrekin." @@ -1023,7 +1194,7 @@ msgstr "Komunikatu \"PocketBook 301 reader\" horrekin." msgid "Communicate with the PocketBook 602/603/902/903 reader." msgstr "Kontaktatu PocketBook 602/603/902/903 reader horrekin." -#: /home/kovid/work/calibre/src/calibre/devices/eb600/driver.py:252 +#: /home/kovid/work/calibre/src/calibre/devices/eb600/driver.py:253 msgid "Communicate with the PocketBook 701" msgstr "Komunikatu \"PocketBook 701\" enpresakoekin" @@ -1061,7 +1232,7 @@ msgstr "Komunikatu \"Hanlin V3 eBook readers\" horiekin." msgid "Communicate with Hanlin V5 eBook readers." msgstr "Komunikatu \"Hanlin V5 eBook readers\" horiekin." -#: /home/kovid/work/calibre/src/calibre/devices/hanlin/driver.py:115 +#: /home/kovid/work/calibre/src/calibre/devices/hanlin/driver.py:114 msgid "Communicate with the BOOX eBook reader." msgstr "Komunikatu \"BOOX eBook reader\" horrekin." @@ -1099,7 +1270,7 @@ msgstr "Komunikatu IRex Iliad eBook irakurgailuarekin." #: /home/kovid/work/calibre/src/calibre/devices/iliad/driver.py:17 #: /home/kovid/work/calibre/src/calibre/devices/irexdr/driver.py:18 -#: /home/kovid/work/calibre/src/calibre/devices/usbms/driver.py:42 +#: /home/kovid/work/calibre/src/calibre/devices/usbms/driver.py:43 msgid "John Schember" msgstr "John Schember" @@ -1136,10 +1307,12 @@ msgstr "Komunikatu \"JetBook Mini reader\" enpresakoekin." #: /home/kovid/work/calibre/src/calibre/devices/kindle/apnx.py:28 msgid "Not a valid MOBI file. Reports identity of %s" msgstr "" +"Ez da MOBI fitxategi baliagarria. Eman %s horren identitatearen abisua, " +"mesedez." #: /home/kovid/work/calibre/src/calibre/devices/kindle/apnx.py:44 msgid "Could not generate page mapping." -msgstr "" +msgstr "Ezin izan da orriaren mapa sortu." #: /home/kovid/work/calibre/src/calibre/devices/kindle/driver.py:44 msgid "Communicate with the Kindle eBook reader." @@ -1151,7 +1324,7 @@ msgstr "Komunikatu \"Kindle 2/3 eBook reader\" enpresakoekin." #: /home/kovid/work/calibre/src/calibre/devices/kindle/driver.py:180 msgid "Send page number information when sending books" -msgstr "" +msgstr "Bidali orri kopuruaren informazioa liburuak bidaltzerakoan" #: /home/kovid/work/calibre/src/calibre/devices/kindle/driver.py:182 msgid "" @@ -1164,6 +1337,8 @@ msgstr "" #: /home/kovid/work/calibre/src/calibre/devices/kindle/driver.py:187 msgid "Use slower but more accurate page number generation" msgstr "" +"Erabil ezazu orrialdeen zenbakiak sortzeko beste sistema motelago baina " +"zehatzago bat" #: /home/kovid/work/calibre/src/calibre/devices/kindle/driver.py:189 msgid "" @@ -1189,12 +1364,12 @@ msgstr "" "The Kobo horrek bilduma bati bakarrik ematen dio sostengua: \"Im_Reading\" " "zerrendari. Sor ezazu izena hori, \"Im_Reading\" izena, duen etiketa bat. " -#: /home/kovid/work/calibre/src/calibre/devices/kobo/driver.py:462 -#: /home/kovid/work/calibre/src/calibre/gui2/actions/add.py:315 +#: /home/kovid/work/calibre/src/calibre/devices/kobo/driver.py:468 +#: /home/kovid/work/calibre/src/calibre/gui2/actions/add.py:388 msgid "Not Implemented" msgstr "Abiarazi gabea, inplementatu gabea" -#: /home/kovid/work/calibre/src/calibre/devices/kobo/driver.py:463 +#: /home/kovid/work/calibre/src/calibre/devices/kobo/driver.py:469 msgid "" "\".kobo\" files do not exist on the device as books instead, they are rows " "in the sqlite database. Currently they cannot be exported or viewed." @@ -1203,59 +1378,50 @@ msgstr "" "\".kobo\" fitxategi horiek SQLite datu base bateko lerroak dira. Une honetan " "ezin dira ez ikusi ez esportatu." -#: /home/kovid/work/calibre/src/calibre/devices/misc.py:17 +#: /home/kovid/work/calibre/src/calibre/devices/misc.py:19 msgid "Communicate with the Palm Pre" msgstr "" "Komunikatu \"Palm Pre\" horrekin laguntzen duten enpresako adituekin." -#: /home/kovid/work/calibre/src/calibre/devices/misc.py:37 +#: /home/kovid/work/calibre/src/calibre/devices/misc.py:39 msgid "Communicate with the Bq Avant" msgstr "Jar zaitez Bq Avant horiekin kontaktuan" -#: /home/kovid/work/calibre/src/calibre/devices/misc.py:58 -msgid "Communicate with the Sweex MM300" -msgstr "" -"Komunikatu \"Sweex MM300\" horrekin laguntzen duten enpresako adituekin." +#: /home/kovid/work/calibre/src/calibre/devices/misc.py:60 +msgid "Communicate with the Sweex/Kogan/Q600/Wink" +msgstr "Kontaktatu Sweex/Kogan/Q600/Wink horrekin" -#: /home/kovid/work/calibre/src/calibre/devices/misc.py:79 -msgid "Communicate with the Digma Q600" -msgstr "Komunikatu \"Digma Q600\" enpresakoekin" - -#: /home/kovid/work/calibre/src/calibre/devices/misc.py:88 -msgid "Communicate with the Kogan" -msgstr "Jar zaitez Koganekin harremanetan" - -#: /home/kovid/work/calibre/src/calibre/devices/misc.py:96 -#: /home/kovid/work/calibre/src/calibre/devices/misc.py:123 +#: /home/kovid/work/calibre/src/calibre/devices/misc.py:81 +#: /home/kovid/work/calibre/src/calibre/devices/misc.py:108 msgid "Communicate with the Pandigital Novel" msgstr "" "Komunikatu \"Pandigital Novel\" horrekin laguntzen duten enpresako adituekin." -#: /home/kovid/work/calibre/src/calibre/devices/misc.py:142 +#: /home/kovid/work/calibre/src/calibre/devices/misc.py:127 msgid "Communicate with the VelocityMicro" msgstr "Komunikatu \"VelocityMicro\" enpresarekin" -#: /home/kovid/work/calibre/src/calibre/devices/misc.py:160 +#: /home/kovid/work/calibre/src/calibre/devices/misc.py:145 msgid "Communicate with the GM2000" msgstr "Komunikatu \"GM2000\" enpresako horiekin" -#: /home/kovid/work/calibre/src/calibre/devices/misc.py:180 +#: /home/kovid/work/calibre/src/calibre/devices/misc.py:165 msgid "Communicate with the Acer Lumiread" msgstr "Komunikatu \"Acer Lumiread\" enpresakoekin." -#: /home/kovid/work/calibre/src/calibre/devices/misc.py:214 +#: /home/kovid/work/calibre/src/calibre/devices/misc.py:199 msgid "Communicate with the Aluratek Color" msgstr "Komunikatu \"Aluratek Color\" enpresakoekin." -#: /home/kovid/work/calibre/src/calibre/devices/misc.py:234 +#: /home/kovid/work/calibre/src/calibre/devices/misc.py:219 msgid "Communicate with the Trekstor" msgstr "Komunikatu \"Trekstor\" enpresakoekin." -#: /home/kovid/work/calibre/src/calibre/devices/misc.py:254 +#: /home/kovid/work/calibre/src/calibre/devices/misc.py:239 msgid "Communicate with the EEE Reader" msgstr "Kontaktatu EEE Reader horrekin" -#: /home/kovid/work/calibre/src/calibre/devices/misc.py:274 +#: /home/kovid/work/calibre/src/calibre/devices/misc.py:259 msgid "Communicate with the Nextbook Reader" msgstr "Kontaktatu Nextbook Reader horrekin" @@ -1306,15 +1472,15 @@ msgstr "" "Komunikatu \"Sony eBook readers\" horrekin laguntzen duten enpresako " "adituekin." -#: /home/kovid/work/calibre/src/calibre/devices/prs505/driver.py:61 +#: /home/kovid/work/calibre/src/calibre/devices/prs505/driver.py:62 msgid "All by title" msgstr "Guztiak izenburuaren arabera" -#: /home/kovid/work/calibre/src/calibre/devices/prs505/driver.py:62 +#: /home/kovid/work/calibre/src/calibre/devices/prs505/driver.py:63 msgid "All by author" msgstr "Guztiak egilearen arabera" -#: /home/kovid/work/calibre/src/calibre/devices/prs505/driver.py:65 +#: /home/kovid/work/calibre/src/calibre/devices/prs505/driver.py:66 msgid "" "Comma separated list of metadata fields to turn into collections on the " "device. Possibilities include: " @@ -1322,7 +1488,7 @@ msgstr "" "Komez bereiziriko meta-datuen eremuen zerrenda gailuan bildumak bihurtzeko " "modukoak. Aukeren artean hauek: " -#: /home/kovid/work/calibre/src/calibre/devices/prs505/driver.py:68 +#: /home/kovid/work/calibre/src/calibre/devices/prs505/driver.py:69 msgid "" ". Two special collections are available: %s:%s and %s:%s. Add these values " "to the list to enable them. The collections will be given the name provided " @@ -1332,12 +1498,12 @@ msgstr "" "zerrendara balioei bidea emateko . Bilduma hauei \":\" karakterearen osteko " "izena emango zaie." -#: /home/kovid/work/calibre/src/calibre/devices/prs505/driver.py:72 +#: /home/kovid/work/calibre/src/calibre/devices/prs505/driver.py:73 msgid "Upload separate cover thumbnails for books (newer readers)" msgstr "" "Kargatu liburu-azalen argazki txikiak (irakurgailu berrienekin egin daiteke)" -#: /home/kovid/work/calibre/src/calibre/devices/prs505/driver.py:73 +#: /home/kovid/work/calibre/src/calibre/devices/prs505/driver.py:74 msgid "" "Normally, the SONY readers get the cover image from the ebook file itself. " "With this option, calibre will send a separate cover image to the reader, " @@ -1352,35 +1518,46 @@ msgstr "" "reader gailu berri-berriekin bakarrik erabil daiteke, hau da: SONY 350, 650, " "950 eta geroagokoekin." -#: /home/kovid/work/calibre/src/calibre/devices/prs505/driver.py:79 +#: /home/kovid/work/calibre/src/calibre/devices/prs505/driver.py:80 msgid "" "Refresh separate covers when using automatic management (newer readers)" msgstr "" "Eguneratu liburu-azalak bereizirik kudeaketa automatikoa erabiltzerakoan " "(irakurle berriak)" -#: /home/kovid/work/calibre/src/calibre/devices/prs505/driver.py:81 +#: /home/kovid/work/calibre/src/calibre/devices/prs505/driver.py:82 msgid "" "Set this option to have separate book covers uploaded every time you connect " "your device. Unset this option if you have so many books on the reader that " "performance is unacceptable." msgstr "" -#: /home/kovid/work/calibre/src/calibre/devices/prs505/driver.py:85 +#: /home/kovid/work/calibre/src/calibre/devices/prs505/driver.py:86 msgid "Preserve cover aspect ratio when building thumbnails" msgstr "" "Aldaketarik ez ezarri liburu-azalen tamainen proportzioetan koadro txikiak " "sortzerakoan" -#: /home/kovid/work/calibre/src/calibre/devices/prs505/driver.py:87 +#: /home/kovid/work/calibre/src/calibre/devices/prs505/driver.py:88 msgid "" "Set this option if you want the cover thumbnails to have the same aspect " "ratio (width to height) as the cover. Unset it if you want the thumbnail to " "be the maximum size, ignoring aspect ratio." msgstr "" +#: /home/kovid/work/calibre/src/calibre/devices/prs505/driver.py:92 +msgid "Search for books in all folders" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/devices/prs505/driver.py:94 +msgid "" +"Setting this option tells calibre to look for books in all folders on the " +"device and its cards. This permits calibre to find books put on the device " +"by other software and by wireless download." +msgstr "" + #: /home/kovid/work/calibre/src/calibre/devices/prs505/sony_cache.py:190 -#: /home/kovid/work/calibre/src/calibre/ebooks/oeb/transforms/structure.py:68 +#: /home/kovid/work/calibre/src/calibre/ebooks/oeb/transforms/structure.py:69 msgid "Unnamed" msgstr "Identifikatu gabea" @@ -1422,7 +1599,11 @@ msgstr "Komunikatu \"Sunstech EB700 reader\" enpresakoekin." #: /home/kovid/work/calibre/src/calibre/devices/teclast/driver.py:99 msgid "Communicate with the Stash W950 reader." -msgstr "" +msgstr "Kontaktatu Stash W950 reader enpresakoekin." + +#: /home/kovid/work/calibre/src/calibre/devices/teclast/driver.py:111 +msgid "Communicate with the Wexler reader." +msgstr "Kontaktatu with the Wexler reader enpresakoekin." #: /home/kovid/work/calibre/src/calibre/devices/usbms/device.py:282 msgid "Unable to detect the %s disk drive. Try rebooting." @@ -1461,21 +1642,21 @@ msgstr "" "%s memoria nagusia irakurtzeko memoria da soilik. Hau normalean fitxategi " "sistemen akatsengatik gertatu ohi da." -#: /home/kovid/work/calibre/src/calibre/devices/usbms/device.py:841 -#: /home/kovid/work/calibre/src/calibre/devices/usbms/device.py:843 +#: /home/kovid/work/calibre/src/calibre/devices/usbms/device.py:842 +#: /home/kovid/work/calibre/src/calibre/devices/usbms/device.py:844 msgid "The reader has no storage card in this slot." msgstr "Irakurgailuak ez du memori-txartelik slot honetan." -#: /home/kovid/work/calibre/src/calibre/devices/usbms/device.py:845 +#: /home/kovid/work/calibre/src/calibre/devices/usbms/device.py:846 msgid "Selected slot: %s is not supported." msgstr "Aukeratutako slot-a: %s ez-onartua." -#: /home/kovid/work/calibre/src/calibre/devices/usbms/device.py:874 +#: /home/kovid/work/calibre/src/calibre/devices/usbms/device.py:875 msgid "There is insufficient free space in main memory" msgstr "Ez dago espazio libre nahikorik memoria nagusian." -#: /home/kovid/work/calibre/src/calibre/devices/usbms/device.py:876 -#: /home/kovid/work/calibre/src/calibre/devices/usbms/device.py:878 +#: /home/kovid/work/calibre/src/calibre/devices/usbms/device.py:877 +#: /home/kovid/work/calibre/src/calibre/devices/usbms/device.py:879 msgid "There is insufficient free space on the storage card" msgstr "Ez dago espazio libre nahikorik memoria-txartelean." @@ -1515,23 +1696,90 @@ msgstr "" msgid "Extra customization" msgstr "Gainerako pertsonalizazioa." -#: /home/kovid/work/calibre/src/calibre/devices/usbms/driver.py:41 +#: /home/kovid/work/calibre/src/calibre/devices/usbms/driver.py:42 msgid "Communicate with an eBook reader." msgstr "Komunikatu eBook irakurgailu batekin." -#: /home/kovid/work/calibre/src/calibre/devices/usbms/driver.py:57 +#: /home/kovid/work/calibre/src/calibre/devices/usbms/driver.py:94 msgid "Get device information..." msgstr "Eskuratu gailuaren informazioa." -#: /home/kovid/work/calibre/src/calibre/ebooks/comic/input.py:190 +#: /home/kovid/work/calibre/src/calibre/devices/user_defined/driver.py:37 +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/device_user_defined.py:68 +msgid "USB Vendor ID (in hex)" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/devices/user_defined/driver.py:38 +#: /home/kovid/work/calibre/src/calibre/devices/user_defined/driver.py:41 +#: /home/kovid/work/calibre/src/calibre/devices/user_defined/driver.py:44 +msgid "" +"Get this ID using Preferences -> Misc -> Get information to set up the user-" +"defined device" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/devices/user_defined/driver.py:40 +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/device_user_defined.py:70 +msgid "USB Product ID (in hex)" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/devices/user_defined/driver.py:43 +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/device_user_defined.py:72 +msgid "USB Revision ID (in hex)" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/devices/user_defined/driver.py:47 +msgid "Windows main memory vendor string" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/devices/user_defined/driver.py:48 +#: /home/kovid/work/calibre/src/calibre/devices/user_defined/driver.py:52 +#: /home/kovid/work/calibre/src/calibre/devices/user_defined/driver.py:56 +#: /home/kovid/work/calibre/src/calibre/devices/user_defined/driver.py:60 +msgid "" +"This field is used only on windows. Get this ID using Preferences -> Misc -> " +"Get information to set up the user-defined device" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/devices/user_defined/driver.py:51 +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/device_user_defined.py:79 +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/device_user_defined.py:81 +msgid "Windows main memory ID string" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/devices/user_defined/driver.py:55 +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/device_user_defined.py:84 +msgid "Windows card A vendor string" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/devices/user_defined/driver.py:59 +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/device_user_defined.py:86 +msgid "Windows card A ID string" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/devices/user_defined/driver.py:63 +msgid "Main memory folder" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/devices/user_defined/driver.py:64 +#: /home/kovid/work/calibre/src/calibre/devices/user_defined/driver.py:67 +msgid "" +"Enter the folder where the books are to be stored. This folder is prepended " +"to any send_to_device template" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/devices/user_defined/driver.py:66 +msgid "Card A folder" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/ebooks/comic/input.py:202 msgid "Rendered %s" msgstr "Prozesatua %s" -#: /home/kovid/work/calibre/src/calibre/ebooks/comic/input.py:193 +#: /home/kovid/work/calibre/src/calibre/ebooks/comic/input.py:205 msgid "Failed %s" msgstr "Huts egin du: %s" -#: /home/kovid/work/calibre/src/calibre/ebooks/comic/input.py:247 +#: /home/kovid/work/calibre/src/calibre/ebooks/comic/input.py:259 msgid "" "Failed to process comic: \n" "\n" @@ -1541,7 +1789,7 @@ msgstr "" "\n" "%s" -#: /home/kovid/work/calibre/src/calibre/ebooks/comic/input.py:266 +#: /home/kovid/work/calibre/src/calibre/ebooks/comic/input.py:278 msgid "" "Number of colors for grayscale image conversion. Default: %default. Values " "of less than 256 may result in blurred text on your device if you are " @@ -1552,24 +1800,24 @@ msgstr "" "erakuts ditzakete zure irakurgailuan zure komikiak ePUB formatuan sortzen " "bazabiltza." -#: /home/kovid/work/calibre/src/calibre/ebooks/comic/input.py:270 +#: /home/kovid/work/calibre/src/calibre/ebooks/comic/input.py:282 msgid "" "Disable normalize (improve contrast) color range for pictures. Default: False" msgstr "" "Desgaitu irudientzako kolore tartearen normalizatzea (kontrastea " "hobetzeko).. Lehenetsia: Faltsu, oker" -#: /home/kovid/work/calibre/src/calibre/ebooks/comic/input.py:273 +#: /home/kovid/work/calibre/src/calibre/ebooks/comic/input.py:285 msgid "Maintain picture aspect ratio. Default is to fill the screen." msgstr "" "Mantendu irudiaren aspektu-proportzioa. Lehenetsia dagoen balioa, pantaila " "guztia betetzea." -#: /home/kovid/work/calibre/src/calibre/ebooks/comic/input.py:275 +#: /home/kovid/work/calibre/src/calibre/ebooks/comic/input.py:287 msgid "Disable sharpening." msgstr "Desgaitu irudiaren fokatze doiketa." -#: /home/kovid/work/calibre/src/calibre/ebooks/comic/input.py:277 +#: /home/kovid/work/calibre/src/calibre/ebooks/comic/input.py:289 msgid "" "Disable trimming of comic pages. For some comics, trimming might remove " "content as well as borders." @@ -1577,13 +1825,13 @@ msgstr "" "Desgaitu komiki orrialdeen doiketa. Komiki batzuetan, doiketak edukiren bat " "edo bazterren bat ezaba dezake." -#: /home/kovid/work/calibre/src/calibre/ebooks/comic/input.py:280 +#: /home/kovid/work/calibre/src/calibre/ebooks/comic/input.py:292 msgid "Don't split landscape images into two portrait images" msgstr "" "Ez itzazu zatitu horizontalean zeuden irudi zabalak bakoitza bi irudi " "bertikal txikiagoetan" -#: /home/kovid/work/calibre/src/calibre/ebooks/comic/input.py:282 +#: /home/kovid/work/calibre/src/calibre/ebooks/comic/input.py:294 msgid "" "Keep aspect ratio and scale image using screen height as image width for " "viewing in landscape mode." @@ -1592,7 +1840,7 @@ msgstr "" "altuera irudiaren zabalera legez erabilita, horrela modu horizontalean " "ikusteko aukerari eusteko." -#: /home/kovid/work/calibre/src/calibre/ebooks/comic/input.py:285 +#: /home/kovid/work/calibre/src/calibre/ebooks/comic/input.py:297 msgid "" "Used for right-to-left publications like manga. Causes landscape pages to be " "split into portrait pages from right to left." @@ -1601,7 +1849,7 @@ msgstr "" "bezalakoetarako. Honek eragiten du horizontal zeuden orrialdeak bertikal " "jartzea, eskuinetik ezkerretara." -#: /home/kovid/work/calibre/src/calibre/ebooks/comic/input.py:289 +#: /home/kovid/work/calibre/src/calibre/ebooks/comic/input.py:301 msgid "" "Enable Despeckle. Reduces speckle noise. May greatly increase processing " "time." @@ -1609,7 +1857,7 @@ msgstr "" "Gaitu Despeckle ('parasitoak erauzi'). Zarata desatsegin batzuk " "gutxiagotuko ditu. Prozesaketa denbora asko luza dezake." -#: /home/kovid/work/calibre/src/calibre/ebooks/comic/input.py:292 +#: /home/kovid/work/calibre/src/calibre/ebooks/comic/input.py:304 msgid "" "Don't sort the files found in the comic alphabetically by name. Instead use " "the order they were added to the comic." @@ -1617,7 +1865,7 @@ msgstr "" "Ez sailkatu izenez alfabetikoki komikian aurkitutako fitxategiak. Horrela " "egin beharrean, sailkatu fitxategiak komikira gehitu ziren ordenaren arabera." -#: /home/kovid/work/calibre/src/calibre/ebooks/comic/input.py:296 +#: /home/kovid/work/calibre/src/calibre/ebooks/comic/input.py:308 msgid "" "The format that images in the created ebook are converted to. You can " "experiment to see which format gives you optimal size and look on your " @@ -1628,22 +1876,28 @@ msgstr "" "egokitzen zaizun hoberen zure irakurtzeko gailuan, ea zeinek daukan " "neurririk eta itxurarik egokiena zure irakurgailuan." -#: /home/kovid/work/calibre/src/calibre/ebooks/comic/input.py:300 +#: /home/kovid/work/calibre/src/calibre/ebooks/comic/input.py:312 msgid "Apply no processing to the image" msgstr "Ez egin irudiari inolako prozesaketarik" -#: /home/kovid/work/calibre/src/calibre/ebooks/comic/input.py:302 +#: /home/kovid/work/calibre/src/calibre/ebooks/comic/input.py:314 msgid "Do not convert the image to grayscale (black and white)" msgstr "Ez bihurtu irudia gris-eskalara (ez bilakatu zuri-beltzezko irudia)" -#: /home/kovid/work/calibre/src/calibre/ebooks/comic/input.py:304 +#: /home/kovid/work/calibre/src/calibre/ebooks/comic/input.py:316 msgid "" "Specify the image size as widthxheight pixels. Normally, an image size is " "automatically calculated from the output profile, this option overrides it." msgstr "" -#: /home/kovid/work/calibre/src/calibre/ebooks/comic/input.py:443 -#: /home/kovid/work/calibre/src/calibre/ebooks/comic/input.py:454 +#: /home/kovid/work/calibre/src/calibre/ebooks/comic/input.py:320 +msgid "" +"When converting a CBC do not add links to each page to the TOC. Note this " +"only applies if the TOC has more than one section" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/ebooks/comic/input.py:459 +#: /home/kovid/work/calibre/src/calibre/ebooks/comic/input.py:471 msgid "Page" msgstr "Orrialdea" @@ -1703,43 +1957,43 @@ msgstr "" "\n" "Bihurketa sistemei buruzko dokumentazio osoa ikusi ahal izateko ikus ezazu\n" -#: /home/kovid/work/calibre/src/calibre/ebooks/conversion/cli.py:106 +#: /home/kovid/work/calibre/src/calibre/ebooks/conversion/cli.py:109 msgid "INPUT OPTIONS" msgstr "INPUT (SORBURU) AUKERAK" -#: /home/kovid/work/calibre/src/calibre/ebooks/conversion/cli.py:107 +#: /home/kovid/work/calibre/src/calibre/ebooks/conversion/cli.py:110 msgid "Options to control the processing of the input %s file" msgstr "Input %s fitxategiaren prozesamendua kontrolatzeko aukerak" -#: /home/kovid/work/calibre/src/calibre/ebooks/conversion/cli.py:113 +#: /home/kovid/work/calibre/src/calibre/ebooks/conversion/cli.py:116 msgid "OUTPUT OPTIONS" msgstr "OUTPUT (HELBURU) AUKERAK" -#: /home/kovid/work/calibre/src/calibre/ebooks/conversion/cli.py:114 +#: /home/kovid/work/calibre/src/calibre/ebooks/conversion/cli.py:117 msgid "Options to control the processing of the output %s" msgstr "Output %s fitxategiaren prozesamendua kontrolatzeko aukerak" -#: /home/kovid/work/calibre/src/calibre/ebooks/conversion/cli.py:128 +#: /home/kovid/work/calibre/src/calibre/ebooks/conversion/cli.py:131 msgid "Options to control the look and feel of the output" msgstr "Outputaren, irteerako emaitzaren, itxura osoa kontrolatzeko aukerak" -#: /home/kovid/work/calibre/src/calibre/ebooks/conversion/cli.py:143 +#: /home/kovid/work/calibre/src/calibre/ebooks/conversion/cli.py:146 msgid "" "Modify the document text and structure using common patterns. Disabled by " "default. Use %s to enable. Individual actions can be disabled with the %s " "options." msgstr "" -#: /home/kovid/work/calibre/src/calibre/ebooks/conversion/cli.py:151 -#: /home/kovid/work/calibre/src/calibre/gui2/convert/search_and_replace.py:16 +#: /home/kovid/work/calibre/src/calibre/ebooks/conversion/cli.py:154 +#: /home/kovid/work/calibre/src/calibre/gui2/convert/search_and_replace.py:18 msgid "Modify the document text and structure using user defined patterns." msgstr "Aldatu dokumentuaren testua eta estruktura patroi zehatzak erabiliz." -#: /home/kovid/work/calibre/src/calibre/ebooks/conversion/cli.py:160 +#: /home/kovid/work/calibre/src/calibre/ebooks/conversion/cli.py:163 msgid "Control auto-detection of document structure." msgstr "Dokumentuaren estrukturaren detektatze automatikoaren kontrola." -#: /home/kovid/work/calibre/src/calibre/ebooks/conversion/cli.py:169 +#: /home/kovid/work/calibre/src/calibre/ebooks/conversion/cli.py:173 msgid "" "Control the automatic generation of a Table of Contents. By default, if the " "source file has a Table of Contents, it will be used in preference to the " @@ -1749,29 +2003,29 @@ msgstr "" "sorburu fitxategiak dagoeneko badu aurkibidea, orduan horixe erabiliko da " "automatikoki sor zitekeenaren ordez." -#: /home/kovid/work/calibre/src/calibre/ebooks/conversion/cli.py:179 +#: /home/kovid/work/calibre/src/calibre/ebooks/conversion/cli.py:183 msgid "Options to set metadata in the output" msgstr "Metadatuak outputean, helburuan, ezartzeko aukerak" -#: /home/kovid/work/calibre/src/calibre/ebooks/conversion/cli.py:182 +#: /home/kovid/work/calibre/src/calibre/ebooks/conversion/cli.py:186 msgid "Options to help with debugging the conversion" msgstr "Bihurketaren arazketarekin laguntzeko aukerak" -#: /home/kovid/work/calibre/src/calibre/ebooks/conversion/cli.py:208 +#: /home/kovid/work/calibre/src/calibre/ebooks/conversion/cli.py:212 msgid "List builtin recipes" msgstr "\"Nola eraiki zen\", 'builtin', formulen edo errezeten zerrenda" -#: /home/kovid/work/calibre/src/calibre/ebooks/conversion/cli.py:281 +#: /home/kovid/work/calibre/src/calibre/ebooks/conversion/cli.py:285 msgid "Output saved to" msgstr "Output horrela gordeta" -#: /home/kovid/work/calibre/src/calibre/ebooks/conversion/plumber.py:102 +#: /home/kovid/work/calibre/src/calibre/ebooks/conversion/plumber.py:103 msgid "Level of verbosity. Specify multiple times for greater verbosity." msgstr "" "Hitz-jario maila, berritsukeria maila. Zehaztu hamaika aldiz hitzontzikeria " "maila altuagoa lortzeko." -#: /home/kovid/work/calibre/src/calibre/ebooks/conversion/plumber.py:109 +#: /home/kovid/work/calibre/src/calibre/ebooks/conversion/plumber.py:110 msgid "" "Save the output from different stages of the conversion pipeline to the " "specified directory. Useful if you are unsure at which stage of the " @@ -1781,7 +2035,7 @@ msgstr "" "outputak, irteera-emaitzak. Erabilgarria ez badakizu prozesuaren zein unetan " "gertatu egiten den errorea." -#: /home/kovid/work/calibre/src/calibre/ebooks/conversion/plumber.py:118 +#: /home/kovid/work/calibre/src/calibre/ebooks/conversion/plumber.py:119 msgid "" "Specify the input profile. The input profile gives the conversion system " "information on how to interpret various information in the input document. " @@ -1793,7 +2047,7 @@ msgstr "" "dokumentuan. Adibidez neurriaren araberako erresoluzioa (pixeletan neurtua). " "Aukerak hauek dira:" -#: /home/kovid/work/calibre/src/calibre/ebooks/conversion/plumber.py:129 +#: /home/kovid/work/calibre/src/calibre/ebooks/conversion/plumber.py:130 msgid "" "Specify the output profile. The output profile tells the conversion system " "how to optimize the created document for the specified device. In some " @@ -1806,7 +2060,7 @@ msgstr "" "diren dokumentuak ekoizteko. Esate baterako SONY reader-ek ePUB formatua " "hobesten du. Aukerak hauek dira:" -#: /home/kovid/work/calibre/src/calibre/ebooks/conversion/plumber.py:140 +#: /home/kovid/work/calibre/src/calibre/ebooks/conversion/plumber.py:141 msgid "" "The base font size in pts. All font sizes in the produced book will be " "rescaled based on this size. By choosing a larger size you can make the " @@ -1820,7 +2074,7 @@ msgstr "" "Lehenetsita zera dago, oinarrizko letra-tipoaren neurria berez aukeratu " "egingo da zuk aukeratu duzun irteera profilaren arabera." -#: /home/kovid/work/calibre/src/calibre/ebooks/conversion/plumber.py:150 +#: /home/kovid/work/calibre/src/calibre/ebooks/conversion/plumber.py:151 msgid "" "Mapping from CSS font names to font sizes in pts. An example setting is " "12,12,14,16,18,20,22,24. These are the mappings for the sizes xx-small to xx-" @@ -1837,11 +2091,11 @@ msgstr "" "Lehenetsita dagoen aukera hauxe da, erabiltzen da doitze sistema bat zuk " "aukeratutako irteera profilaren araberakoa." -#: /home/kovid/work/calibre/src/calibre/ebooks/conversion/plumber.py:162 +#: /home/kovid/work/calibre/src/calibre/ebooks/conversion/plumber.py:163 msgid "Disable all rescaling of font sizes." msgstr "Desgaituta dago edozein letra-tipo tamaina berri batera aldatzea" -#: /home/kovid/work/calibre/src/calibre/ebooks/conversion/plumber.py:168 +#: /home/kovid/work/calibre/src/calibre/ebooks/conversion/plumber.py:169 msgid "" "The minimum line height, as a percentage of the element's calculated font " "size. calibre will ensure that every element has a line height of at least " @@ -1859,7 +2113,7 @@ msgstr "" "Esate baterako, lerroen arteko espazio bikoitza erraz lor dezakezu 240 " "balioa hautatuz." -#: /home/kovid/work/calibre/src/calibre/ebooks/conversion/plumber.py:183 +#: /home/kovid/work/calibre/src/calibre/ebooks/conversion/plumber.py:184 msgid "" "The line height in pts. Controls spacing between consecutive lines of text. " "Only applies to elements that do not define their own line height. In most " @@ -1872,7 +2126,7 @@ msgstr "" "erabilgarriena. Lehenetsita hauxe: ez da da lerroen altuera aldaketarik " "egingo." -#: /home/kovid/work/calibre/src/calibre/ebooks/conversion/plumber.py:194 +#: /home/kovid/work/calibre/src/calibre/ebooks/conversion/plumber.py:195 msgid "" "Some badly designed documents use tables to control the layout of text on " "the page. When converted these documents often have text that runs off the " @@ -1885,7 +2139,7 @@ msgstr "" "eta antzeko problemak. Aukera honek testuaren edukia aterako du tauletatik " "eta aurkeztuko ditu eduki horiek modu lineal batean." -#: /home/kovid/work/calibre/src/calibre/ebooks/conversion/plumber.py:204 +#: /home/kovid/work/calibre/src/calibre/ebooks/conversion/plumber.py:205 msgid "" "XPath expression that specifies all tags that should be added to the Table " "of Contents at level one. If this is specified, it takes precedence over " @@ -1896,7 +2150,7 @@ msgstr "" "zehaztuz gero, honek beste auto-detekzio mota batzuen aurretik lehenetsiko " "da." -#: /home/kovid/work/calibre/src/calibre/ebooks/conversion/plumber.py:213 +#: /home/kovid/work/calibre/src/calibre/ebooks/conversion/plumber.py:214 msgid "" "XPath expression that specifies all tags that should be added to the Table " "of Contents at level two. Each entry is added under the previous level one " @@ -1906,7 +2160,7 @@ msgstr "" "aurkibidearen bigarren mailara gehitu egin beharko liratekeela. Sarrera " "bakoitza gehitu egiten da aurreko lehen mailaren baitan." -#: /home/kovid/work/calibre/src/calibre/ebooks/conversion/plumber.py:221 +#: /home/kovid/work/calibre/src/calibre/ebooks/conversion/plumber.py:222 msgid "" "XPath expression that specifies all tags that should be added to the Table " "of Contents at level three. Each entry is added under the previous level two " @@ -1916,7 +2170,7 @@ msgstr "" "aurkibidearen hirugarren mailara gehitu egin beharko liratekeela. Sarrera " "bakoitza gehitu egiten da aurreko bigarren mailaren baitan." -#: /home/kovid/work/calibre/src/calibre/ebooks/conversion/plumber.py:229 +#: /home/kovid/work/calibre/src/calibre/ebooks/conversion/plumber.py:230 msgid "" "Normally, if the source file already has a Table of Contents, it is used in " "preference to the auto-generated one. With this option, the auto-generated " @@ -1926,11 +2180,11 @@ msgstr "" "hori erabiliko da berez sistemak sor dezakeenaren aurretik. Aukera honekin, " "ordea, sistemak berez sortuko duen aurkibidea ezarriko da beti." -#: /home/kovid/work/calibre/src/calibre/ebooks/conversion/plumber.py:237 +#: /home/kovid/work/calibre/src/calibre/ebooks/conversion/plumber.py:238 msgid "Don't add auto-detected chapters to the Table of Contents." msgstr "Aurkibidean ez gehitu berez detektatu diren kapituluak ." -#: /home/kovid/work/calibre/src/calibre/ebooks/conversion/plumber.py:244 +#: /home/kovid/work/calibre/src/calibre/ebooks/conversion/plumber.py:245 msgid "" "If fewer than this number of chapters is detected, then links are added to " "the Table of Contents. Default: %default" @@ -1938,7 +2192,7 @@ msgstr "" "Kapitulu kopuru hau baino txikiagoa den kopurua detektatu egiten bada, " "orduan esteka batzuk gehituko dira aurkibidera. Lehenetsita: %default" -#: /home/kovid/work/calibre/src/calibre/ebooks/conversion/plumber.py:251 +#: /home/kovid/work/calibre/src/calibre/ebooks/conversion/plumber.py:252 msgid "" "Maximum number of links to insert into the TOC. Set to 0 to disable. Default " "is: %default. Links are only added to the TOC if less than the threshold " @@ -1949,7 +2203,7 @@ msgstr "" "baldin eta muga legez ezarri den kopurutik behera gabiltzala detektatzen " "bada." -#: /home/kovid/work/calibre/src/calibre/ebooks/conversion/plumber.py:259 +#: /home/kovid/work/calibre/src/calibre/ebooks/conversion/plumber.py:260 msgid "" "Remove entries from the Table of Contents whose titles match the specified " "regular expression. Matching entries and all their children are removed." @@ -1958,7 +2212,7 @@ msgstr "" "adierazpen arruntek bat egiten badute. Bat egiten duten aurkibideko sarrera " "guztiak eta sarrera horien adar guztiak ezabatu egin dira." -#: /home/kovid/work/calibre/src/calibre/ebooks/conversion/plumber.py:270 +#: /home/kovid/work/calibre/src/calibre/ebooks/conversion/plumber.py:271 msgid "" "An XPath expression to detect chapter titles. The default is to consider " "

      or

      tags that contain the words \"chapter\",\"book\",\"section\" or " @@ -1977,7 +2231,7 @@ msgstr "" "Ikus ezazu XPath Tutoriala calibre Erabiltzailearen Eskuliburuaren barruan " "laguntza osotuago eskura izateko eginbide hau erabiltzerakoan." -#: /home/kovid/work/calibre/src/calibre/ebooks/conversion/plumber.py:284 +#: /home/kovid/work/calibre/src/calibre/ebooks/conversion/plumber.py:285 msgid "" "Specify how to mark detected chapters. A value of \"pagebreak\" will insert " "page breaks before chapters. A value of \"rule\" will insert a line before " @@ -1992,7 +2246,7 @@ msgstr "" "da \"biak batera\" adierazpenak, aldi berean orrialde jauzia eta lerroak " "erabiliko ditu kapituluak markatzeko." -#: /home/kovid/work/calibre/src/calibre/ebooks/conversion/plumber.py:294 +#: /home/kovid/work/calibre/src/calibre/ebooks/conversion/plumber.py:295 msgid "" "Either the path to a CSS stylesheet or raw CSS. This CSS will be appended to " "the style rules from the source file, so it can be used to override those " @@ -2002,14 +2256,22 @@ msgstr "" "fitxategiaren estilo arauen gainean erantsiko da, beraz, erabil daiteke arau " "horiek baliogabetzeko." -#: /home/kovid/work/calibre/src/calibre/ebooks/conversion/plumber.py:303 +#: /home/kovid/work/calibre/src/calibre/ebooks/conversion/plumber.py:304 msgid "" "An XPath expression. Page breaks are inserted before the specified elements." msgstr "" "XPath adierazpena. Orrialde jauziak txertatuko dira zehaztutako elementuen " "aurrean." -#: /home/kovid/work/calibre/src/calibre/ebooks/conversion/plumber.py:309 +#: /home/kovid/work/calibre/src/calibre/ebooks/conversion/plumber.py:310 +msgid "" +"Some documents specify page margins by specifying a left and right margin on " +"each individual paragraph. calibre will try to detect and remove these " +"margins. Sometimes, this can cause the removal of margins that should not " +"have been removed. In this case you can disable the removal." +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/ebooks/conversion/plumber.py:321 msgid "" "Set the top margin in pts. Default is %default. Note: 72 pts equals 1 inch" msgstr "" @@ -2017,28 +2279,28 @@ msgstr "" "hauxe: %default. Oharra: 72 puntu hazbete baten pareko, hau da 2,54 " "zentimetro." -#: /home/kovid/work/calibre/src/calibre/ebooks/conversion/plumber.py:314 +#: /home/kovid/work/calibre/src/calibre/ebooks/conversion/plumber.py:326 msgid "" "Set the bottom margin in pts. Default is %default. Note: 72 pts equals 1 inch" msgstr "" "Ezarri ezazu beheko bazterra puntuetan. Lehenetsita hauxe: %default. Oharra: " "72 puntu hazbete baten pareko, hau da 2,54 zentimetro." -#: /home/kovid/work/calibre/src/calibre/ebooks/conversion/plumber.py:319 +#: /home/kovid/work/calibre/src/calibre/ebooks/conversion/plumber.py:331 msgid "" "Set the left margin in pts. Default is %default. Note: 72 pts equals 1 inch" msgstr "" "Ezarri ezkerretako bazterra puntuetan. Lehenetsita hauxe: %default. Oharra: " "72 puntu hazbete baten pareko, hau da 2,54 zentimetro." -#: /home/kovid/work/calibre/src/calibre/ebooks/conversion/plumber.py:324 +#: /home/kovid/work/calibre/src/calibre/ebooks/conversion/plumber.py:336 msgid "" "Set the right margin in pts. Default is %default. Note: 72 pts equals 1 inch" msgstr "" "Ezarri eskuinetako bazterra puntuetan. Lehenetsita hauxe: %default. Oharra: " "72 puntu hazbete baten pareko, hau da 2,54 zentimetro." -#: /home/kovid/work/calibre/src/calibre/ebooks/conversion/plumber.py:330 +#: /home/kovid/work/calibre/src/calibre/ebooks/conversion/plumber.py:342 msgid "" "Change text justification. A value of \"left\" converts all justified text " "in the source to left aligned (i.e. unjustified) text. A value of " @@ -2055,7 +2317,7 @@ msgstr "" "aldaketarik gabe. Oharra: irteera formatu batzuk bakarrik eusten diote " "justifikazioari." -#: /home/kovid/work/calibre/src/calibre/ebooks/conversion/plumber.py:340 +#: /home/kovid/work/calibre/src/calibre/ebooks/conversion/plumber.py:352 msgid "" "Remove spacing between paragraphs. Also sets an indent on paragraphs of " "1.5em. Spacing removal will not work if the source file does not use " @@ -2066,7 +2328,7 @@ msgstr "" "sorburu fitxategiek ez badute paragraforik erabiltzen (

      edo

      " "etiketak)." -#: /home/kovid/work/calibre/src/calibre/ebooks/conversion/plumber.py:347 +#: /home/kovid/work/calibre/src/calibre/ebooks/conversion/plumber.py:359 msgid "" "When calibre removes inter paragraph spacing, it automatically sets a " "paragraph indent, to ensure that paragraphs can be easily distinguished. " @@ -2076,7 +2338,7 @@ msgstr "" "paragrafoko koska txertatzen du ziurtatzeko paragrafoen artean nahasketarik " "ez dela egongo. Aukera honek koskaren zabalera kontrolatzeko da." -#: /home/kovid/work/calibre/src/calibre/ebooks/conversion/plumber.py:354 +#: /home/kovid/work/calibre/src/calibre/ebooks/conversion/plumber.py:366 msgid "" "Use the cover detected from the source file in preference to the specified " "cover." @@ -2084,7 +2346,7 @@ msgstr "" "Erabil ezazu sorburu fitxategitik detektatu den liburu-azala, zehaztutako " "liburu-azala erabili beharrean." -#: /home/kovid/work/calibre/src/calibre/ebooks/conversion/plumber.py:360 +#: /home/kovid/work/calibre/src/calibre/ebooks/conversion/plumber.py:372 msgid "" "Insert a blank line between paragraphs. Will not work if the source file " "does not use paragraphs (

      or

      tags)." @@ -2093,7 +2355,7 @@ msgstr "" "baldin eta sorburu fitxategiek ez badute paragraforik erabiltzen (

      edo " "

      etiketak)." -#: /home/kovid/work/calibre/src/calibre/ebooks/conversion/plumber.py:367 +#: /home/kovid/work/calibre/src/calibre/ebooks/conversion/plumber.py:379 msgid "" "Remove the first image from the input ebook. Useful if the first image in " "the source file is a cover and you are specifying an external cover." @@ -2102,7 +2364,7 @@ msgstr "" "erabilgarria izango zaizu baldin eta jatorrizko fitxategiaren lehen irudia " "liburuaren azala da eta zu kanpoko liburu-azala erabili nahi baduzu." -#: /home/kovid/work/calibre/src/calibre/ebooks/conversion/plumber.py:375 +#: /home/kovid/work/calibre/src/calibre/ebooks/conversion/plumber.py:387 msgid "" "Insert the book metadata at the start of the book. This is useful if your " "ebook reader does not support displaying/searching metadata directly." @@ -2112,7 +2374,7 @@ msgstr "" "metadatuen erakusten/bilatzen aukerari eusten, ez badauka " "erakusten/bilatzen aukera, metadatuak zuzenean bilatzeko eta erakusteko." -#: /home/kovid/work/calibre/src/calibre/ebooks/conversion/plumber.py:383 +#: /home/kovid/work/calibre/src/calibre/ebooks/conversion/plumber.py:395 msgid "" "Convert plain quotes, dashes and ellipsis to their typographically correct " "equivalents. For details, see http://daringfireball.net/projects/smartypants" @@ -2121,7 +2383,7 @@ msgstr "" "bihurtzen ditu. Gehiago jakiteko, ikus: " "http://daringfireball.net/projects/smartypants" -#: /home/kovid/work/calibre/src/calibre/ebooks/conversion/plumber.py:392 +#: /home/kovid/work/calibre/src/calibre/ebooks/conversion/plumber.py:404 msgid "" "Read metadata from the specified OPF file. Metadata read from this file will " "override any metadata in the source file." @@ -2130,7 +2392,7 @@ msgstr "" "irakurritako edozein metadatu sorburu fitxategiko edozein metadaturen " "gainetik gailenduko dira." -#: /home/kovid/work/calibre/src/calibre/ebooks/conversion/plumber.py:399 +#: /home/kovid/work/calibre/src/calibre/ebooks/conversion/plumber.py:411 msgid "" "Transliterate unicode characters to an ASCII representation. Use with care " "because this will replace unicode characters with ASCII. For instance it " @@ -2140,7 +2402,7 @@ msgid "" "current calibre interface language will be used." msgstr "" -#: /home/kovid/work/calibre/src/calibre/ebooks/conversion/plumber.py:414 +#: /home/kovid/work/calibre/src/calibre/ebooks/conversion/plumber.py:426 msgid "" "Preserve ligatures present in the input document. A ligature is a special " "rendering of a pair of characters like ff, fi, fl et cetera. Most readers do " @@ -2158,91 +2420,91 @@ msgstr "" "bakartzat hartu beharrean bi karaktere lokabetzat hartuko ditu. Baina aukera " "hau lehenetsiko bazenu letra-lotura horiek ondo babestuko zenituzke." -#: /home/kovid/work/calibre/src/calibre/ebooks/conversion/plumber.py:426 +#: /home/kovid/work/calibre/src/calibre/ebooks/conversion/plumber.py:438 #: /home/kovid/work/calibre/src/calibre/ebooks/metadata/cli.py:38 msgid "Set the title." msgstr "Izenburua ezarri." -#: /home/kovid/work/calibre/src/calibre/ebooks/conversion/plumber.py:430 +#: /home/kovid/work/calibre/src/calibre/ebooks/conversion/plumber.py:442 msgid "Set the authors. Multiple authors should be separated by ampersands." msgstr "" "Egileak ezarri. Hainbat egile. Bere izenak ampersand ikurrarekin ( hau da & " "ikurrarekin) bereiziko dira." -#: /home/kovid/work/calibre/src/calibre/ebooks/conversion/plumber.py:435 +#: /home/kovid/work/calibre/src/calibre/ebooks/conversion/plumber.py:447 msgid "The version of the title to be used for sorting. " msgstr "Sailkatzeko erabiliko den liburuaren izenburuaren bertsioa. " -#: /home/kovid/work/calibre/src/calibre/ebooks/conversion/plumber.py:439 +#: /home/kovid/work/calibre/src/calibre/ebooks/conversion/plumber.py:451 msgid "String to be used when sorting by author. " msgstr "" "Egilearen izenaren arabera sailkatzen denean erabiliko den testu-katea. " -#: /home/kovid/work/calibre/src/calibre/ebooks/conversion/plumber.py:443 +#: /home/kovid/work/calibre/src/calibre/ebooks/conversion/plumber.py:455 msgid "Set the cover to the specified file or URL" msgstr "" "ezarri iezaiozu liburu-azala zehaztutako fitxategiari edo zehaztutako URL-" "ari." -#: /home/kovid/work/calibre/src/calibre/ebooks/conversion/plumber.py:447 +#: /home/kovid/work/calibre/src/calibre/ebooks/conversion/plumber.py:459 #: /home/kovid/work/calibre/src/calibre/ebooks/metadata/cli.py:54 msgid "Set the ebook description." msgstr "Liburu elektronikoaren deskripzioa jarri." -#: /home/kovid/work/calibre/src/calibre/ebooks/conversion/plumber.py:451 +#: /home/kovid/work/calibre/src/calibre/ebooks/conversion/plumber.py:463 #: /home/kovid/work/calibre/src/calibre/ebooks/metadata/cli.py:56 msgid "Set the ebook publisher." msgstr "Liburu elektronikoaren argitaratzailea jarri." -#: /home/kovid/work/calibre/src/calibre/ebooks/conversion/plumber.py:455 +#: /home/kovid/work/calibre/src/calibre/ebooks/conversion/plumber.py:467 #: /home/kovid/work/calibre/src/calibre/ebooks/metadata/cli.py:60 msgid "Set the series this ebook belongs to." msgstr "" "Ezarri ezazu liburu elektroniko hau zein serietan sailkatuko zenukeen." -#: /home/kovid/work/calibre/src/calibre/ebooks/conversion/plumber.py:459 +#: /home/kovid/work/calibre/src/calibre/ebooks/conversion/plumber.py:471 #: /home/kovid/work/calibre/src/calibre/ebooks/metadata/cli.py:62 msgid "Set the index of the book in this series." msgstr "Serie hauetan ezarri ezazu liburuaren aurkibidea." -#: /home/kovid/work/calibre/src/calibre/ebooks/conversion/plumber.py:463 +#: /home/kovid/work/calibre/src/calibre/ebooks/conversion/plumber.py:475 #: /home/kovid/work/calibre/src/calibre/ebooks/metadata/cli.py:64 msgid "Set the rating. Should be a number between 1 and 5." msgstr "Ezarri balorazioa. 1 eta 5 artean dagoen zenbakia izan beharko." -#: /home/kovid/work/calibre/src/calibre/ebooks/conversion/plumber.py:467 +#: /home/kovid/work/calibre/src/calibre/ebooks/conversion/plumber.py:479 #: /home/kovid/work/calibre/src/calibre/ebooks/metadata/cli.py:66 msgid "Set the ISBN of the book." msgstr "Liburuaren ISBN-a jarri." -#: /home/kovid/work/calibre/src/calibre/ebooks/conversion/plumber.py:471 +#: /home/kovid/work/calibre/src/calibre/ebooks/conversion/plumber.py:483 #: /home/kovid/work/calibre/src/calibre/ebooks/metadata/cli.py:68 msgid "Set the tags for the book. Should be a comma separated list." msgstr "" "Ezarri liburuaren etiketak. Komen bidez bereizitako elementuen zerrenda bat " "izan beharko luke." -#: /home/kovid/work/calibre/src/calibre/ebooks/conversion/plumber.py:475 +#: /home/kovid/work/calibre/src/calibre/ebooks/conversion/plumber.py:487 #: /home/kovid/work/calibre/src/calibre/ebooks/metadata/cli.py:70 msgid "Set the book producer." msgstr "Ezarri ezazu liburuaren ekoizlea." -#: /home/kovid/work/calibre/src/calibre/ebooks/conversion/plumber.py:479 +#: /home/kovid/work/calibre/src/calibre/ebooks/conversion/plumber.py:491 #: /home/kovid/work/calibre/src/calibre/ebooks/metadata/cli.py:72 msgid "Set the language." msgstr "Ezarri hizkuntza." -#: /home/kovid/work/calibre/src/calibre/ebooks/conversion/plumber.py:483 +#: /home/kovid/work/calibre/src/calibre/ebooks/conversion/plumber.py:495 msgid "Set the publication date." msgstr "Ezarri ezazu liburuaren ekoizpen-data. Noiz argitaratu zen." -#: /home/kovid/work/calibre/src/calibre/ebooks/conversion/plumber.py:487 +#: /home/kovid/work/calibre/src/calibre/ebooks/conversion/plumber.py:499 msgid "Set the book timestamp (used by the date column in calibre)." msgstr "" "Ezarri ezazu liburuaren denbora-zigilua (calibre programa honetan data " "zutabean erabilia)." -#: /home/kovid/work/calibre/src/calibre/ebooks/conversion/plumber.py:491 +#: /home/kovid/work/calibre/src/calibre/ebooks/conversion/plumber.py:503 msgid "" "Enable heuristic processing. This option must be set for any heuristic " "processing to take place." @@ -2250,27 +2512,27 @@ msgstr "" "Baimendu prozesamendu heuristikoa. Aukera hau ezarri beharko da edozein " "prezesamendu heuristiko bideratzeko." -#: /home/kovid/work/calibre/src/calibre/ebooks/conversion/plumber.py:496 +#: /home/kovid/work/calibre/src/calibre/ebooks/conversion/plumber.py:508 msgid "" "Detect unformatted chapter headings and sub headings. Change them to h2 and " "h3 tags. This setting will not create a TOC, but can be used in conjunction " "with structure detection to create one." msgstr "" -#: /home/kovid/work/calibre/src/calibre/ebooks/conversion/plumber.py:503 +#: /home/kovid/work/calibre/src/calibre/ebooks/conversion/plumber.py:515 msgid "" "Look for common words and patterns that denote italics and italicize them." msgstr "" "Bilatu normalean letra etzanarekin dauden hitzak eta patroiak. Gero, jar " "itzazu hitz horiek letra etzanez." -#: /home/kovid/work/calibre/src/calibre/ebooks/conversion/plumber.py:508 +#: /home/kovid/work/calibre/src/calibre/ebooks/conversion/plumber.py:520 msgid "" "Turn indentation created from multiple non-breaking space entities into CSS " "indents." msgstr "Bihurtu espazio zurrun anitzetako koskak CSS koska." -#: /home/kovid/work/calibre/src/calibre/ebooks/conversion/plumber.py:513 +#: /home/kovid/work/calibre/src/calibre/ebooks/conversion/plumber.py:525 msgid "" "Scale used to determine the length at which a line should be unwrapped. " "Valid values are a decimal between 0 and 1. The default is 0.4, just below " @@ -2278,11 +2540,11 @@ msgid "" "unwrapping this value should be reduced" msgstr "" -#: /home/kovid/work/calibre/src/calibre/ebooks/conversion/plumber.py:521 +#: /home/kovid/work/calibre/src/calibre/ebooks/conversion/plumber.py:533 msgid "Unwrap lines using punctuation and other formatting clues." msgstr "Batu lerroak puntuazio eta formatu aztarnei esker." -#: /home/kovid/work/calibre/src/calibre/ebooks/conversion/plumber.py:525 +#: /home/kovid/work/calibre/src/calibre/ebooks/conversion/plumber.py:537 msgid "" "Remove empty paragraphs from the document when they exist between every " "other paragraph" @@ -2290,19 +2552,19 @@ msgstr "" "Ezabatu dokumentutik hutsik dauden paragrafoak baldin badaude beste " "paragrafoen artean." -#: /home/kovid/work/calibre/src/calibre/ebooks/conversion/plumber.py:530 +#: /home/kovid/work/calibre/src/calibre/ebooks/conversion/plumber.py:542 msgid "" "Left aligned scene break markers are center aligned. Replace soft scene " "breaks that use multiple blank lines withhorizontal rules." msgstr "" -#: /home/kovid/work/calibre/src/calibre/ebooks/conversion/plumber.py:536 +#: /home/kovid/work/calibre/src/calibre/ebooks/conversion/plumber.py:548 msgid "" "Replace scene breaks with the specified text. By default, the text from the " "input document is used." msgstr "" -#: /home/kovid/work/calibre/src/calibre/ebooks/conversion/plumber.py:541 +#: /home/kovid/work/calibre/src/calibre/ebooks/conversion/plumber.py:553 msgid "" "Analyze hyphenated words throughout the document. The document itself is " "used as a dictionary to determine whether hyphens should be retained or " @@ -2312,61 +2574,61 @@ msgstr "" "erabiliko da erreferentzia hiztegi bezala erabakitzeko ea gidoiak mantenduko " "diren edo ezabatuko diren." -#: /home/kovid/work/calibre/src/calibre/ebooks/conversion/plumber.py:547 +#: /home/kovid/work/calibre/src/calibre/ebooks/conversion/plumber.py:559 msgid "" "Looks for occurrences of sequential

      or

      tags. The tags are " "renumbered to prevent splitting in the middle of chapter headings." msgstr "" -#: /home/kovid/work/calibre/src/calibre/ebooks/conversion/plumber.py:553 +#: /home/kovid/work/calibre/src/calibre/ebooks/conversion/plumber.py:565 msgid "Search pattern (regular expression) to be replaced with sr1-replace." msgstr "" "sr1-replace-rekin ordezkatuko den bilaketa patroia (adierazpen erregularra)." -#: /home/kovid/work/calibre/src/calibre/ebooks/conversion/plumber.py:558 +#: /home/kovid/work/calibre/src/calibre/ebooks/conversion/plumber.py:570 msgid "Replacement to replace the text found with sr1-search." msgstr "" -#: /home/kovid/work/calibre/src/calibre/ebooks/conversion/plumber.py:562 +#: /home/kovid/work/calibre/src/calibre/ebooks/conversion/plumber.py:574 msgid "Search pattern (regular expression) to be replaced with sr2-replace." msgstr "" -#: /home/kovid/work/calibre/src/calibre/ebooks/conversion/plumber.py:567 +#: /home/kovid/work/calibre/src/calibre/ebooks/conversion/plumber.py:579 msgid "Replacement to replace the text found with sr2-search." msgstr "" -#: /home/kovid/work/calibre/src/calibre/ebooks/conversion/plumber.py:571 +#: /home/kovid/work/calibre/src/calibre/ebooks/conversion/plumber.py:583 msgid "Search pattern (regular expression) to be replaced with sr3-replace." msgstr "" -#: /home/kovid/work/calibre/src/calibre/ebooks/conversion/plumber.py:576 +#: /home/kovid/work/calibre/src/calibre/ebooks/conversion/plumber.py:588 msgid "Replacement to replace the text found with sr3-search." msgstr "" -#: /home/kovid/work/calibre/src/calibre/ebooks/conversion/plumber.py:678 +#: /home/kovid/work/calibre/src/calibre/ebooks/conversion/plumber.py:690 msgid "Could not find an ebook inside the archive" msgstr "Ezin izan da fitxategi horretan liburu elektronikorik aurkitu" -#: /home/kovid/work/calibre/src/calibre/ebooks/conversion/plumber.py:736 +#: /home/kovid/work/calibre/src/calibre/ebooks/conversion/plumber.py:748 msgid "Values of series index and rating must be numbers. Ignoring" msgstr "" "Aurkibideko serieen balioak eta puntuazioa, zenbakiak izan beharko dira. Ez " "ikusiarena egiten" -#: /home/kovid/work/calibre/src/calibre/ebooks/conversion/plumber.py:743 +#: /home/kovid/work/calibre/src/calibre/ebooks/conversion/plumber.py:755 msgid "Failed to parse date/time" msgstr "Huts egin du data/orduaren analisiak" -#: /home/kovid/work/calibre/src/calibre/ebooks/conversion/plumber.py:898 +#: /home/kovid/work/calibre/src/calibre/ebooks/conversion/plumber.py:914 msgid "Converting input to HTML..." msgstr "Sorburukoa HTML horretara bihurtzen..." -#: /home/kovid/work/calibre/src/calibre/ebooks/conversion/plumber.py:925 +#: /home/kovid/work/calibre/src/calibre/ebooks/conversion/plumber.py:941 msgid "Running transforms on ebook..." msgstr "" "Liburu elektronikoan une honetan ari dira bihurtze aldaketak gertatzen..." -#: /home/kovid/work/calibre/src/calibre/ebooks/conversion/plumber.py:1013 +#: /home/kovid/work/calibre/src/calibre/ebooks/conversion/plumber.py:1037 msgid "Creating" msgstr "Sortzen" @@ -2533,7 +2795,7 @@ msgstr "Hasi" msgid "Do not insert a Table of Contents at the beginning of the book." msgstr "Ez txertatu aurkibidea liburuaren hasieran." -#: /home/kovid/work/calibre/src/calibre/ebooks/fb2/output.py:22 +#: /home/kovid/work/calibre/src/calibre/ebooks/fb2/output.py:149 msgid "" "Specify the sectionization of elements. A value of \"nothing\" turns the " "book into a single section. A value of \"files\" turns each file into a " @@ -2552,6 +2814,17 @@ msgstr "" "detekzioa\" edota \"Aurkibidea\" ezarpenak (piztu \"Behartu automatikoki " "sortutako aurkibidea\")." +#: /home/kovid/work/calibre/src/calibre/ebooks/fb2/output.py:158 +msgid "" +"Genre for the book. Choices: %s\n" +"\n" +" See: " +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/ebooks/fb2/output.py:159 +msgid "for a complete list with descriptions." +msgstr "" + #: /home/kovid/work/calibre/src/calibre/ebooks/html/input.py:248 msgid "" "Traverse links in HTML files breadth first. Normally, they are traversed " @@ -2582,13 +2855,13 @@ msgstr "" "hau bakarrik ondo dakizunean zertaz ari zaren. Aukera honek gustuko ez diren " "ondorio batzuk bihurketaren beste eremu batzuetara ekar ditzake." -#: /home/kovid/work/calibre/src/calibre/ebooks/html/output.py:33 +#: /home/kovid/work/calibre/src/calibre/ebooks/html/output.py:32 msgid "CSS file used for the output instead of the default file" msgstr "" "Helburu fitxategia izateko erabili izan den CSS fitxategia, lehenetsitako " "fitxategia erabili beharrean" -#: /home/kovid/work/calibre/src/calibre/ebooks/html/output.py:36 +#: /home/kovid/work/calibre/src/calibre/ebooks/html/output.py:35 msgid "" "Template used for generation of the html index file instead of the default " "file" @@ -2596,7 +2869,7 @@ msgstr "" "Indize fitxategia sortzeko erabili izan den html txantiloia, lehenetsitako " "fitxategia erabili beharrean" -#: /home/kovid/work/calibre/src/calibre/ebooks/html/output.py:39 +#: /home/kovid/work/calibre/src/calibre/ebooks/html/output.py:38 msgid "" "Template used for the generation of the html contents of the book instead of " "the default file" @@ -2604,7 +2877,7 @@ msgstr "" "Liburaren edukiak sortzeko erabili izan den html txantiloia, lehenetsitako " "fitxategia erabili beharrean" -#: /home/kovid/work/calibre/src/calibre/ebooks/html/output.py:42 +#: /home/kovid/work/calibre/src/calibre/ebooks/html/output.py:41 msgid "" "Extract the contents of the generated ZIP file to the specified directory. " "WARNING: The contents of the directory will be deleted." @@ -2612,6 +2885,22 @@ msgstr "" "Erauzi sortu den ZIP fitxategiko edukiak zehaztutako direktoriora. KONTUZ: " "direktorioko edukiak ezabatu egingo dira." +#: /home/kovid/work/calibre/src/calibre/ebooks/htmlz/output.py:30 +msgid "" +"Specify the handling of CSS. Default is class.\n" +"class: Use CSS classes and have elements reference them.\n" +"inline: Write the CSS as an inline style attribute.\n" +"tag: Turn as many CSS styles as possible into HTML tags." +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/ebooks/htmlz/output.py:38 +msgid "" +"How to handle the CSS when using css-type = 'class'.\n" +"Default is external.\n" +"external: Use an external CSS file that is linked in the document.\n" +"inline: Place the CSS in the head section of the document." +msgstr "" + #: /home/kovid/work/calibre/src/calibre/ebooks/lit/from_any.py:47 msgid "Creating LIT file from EPUB..." msgstr "" @@ -2760,7 +3049,6 @@ msgid "Path to output file" msgstr "Helburu fitxategirako bidea" #: /home/kovid/work/calibre/src/calibre/ebooks/lrf/lrs/convert_from.py:290 -#: /home/kovid/work/calibre/src/calibre/ebooks/metadata/isbndb.py:126 msgid "Verbose processing" msgstr "Hitz-jarioa prozesatzen" @@ -2926,49 +3214,6 @@ msgstr "Monospace letra-mota multzoa kapsulatua txertatzeko" msgid "Comic" msgstr "Komikia" -#: /home/kovid/work/calibre/src/calibre/ebooks/metadata/amazonfr.py:26 -msgid "Downloads metadata from amazon.fr" -msgstr "Deskargatzen ditu metadatuak amazon.fr gunetik" - -#: /home/kovid/work/calibre/src/calibre/ebooks/metadata/amazonfr.py:43 -msgid "Downloads metadata from amazon.com in spanish" -msgstr "Deskargatzen ditu metadatuak amazon.com gaztelaniazko gunetik" - -#: /home/kovid/work/calibre/src/calibre/ebooks/metadata/amazonfr.py:60 -msgid "Downloads metadata from amazon.com in english" -msgstr "Deskargatzen ditu metadatuak amazon.com ingelesezko gunetik" - -#: /home/kovid/work/calibre/src/calibre/ebooks/metadata/amazonfr.py:77 -msgid "Downloads metadata from amazon.de" -msgstr "Deskargatzen ditu metadatuak amazon.de gunetik" - -#: /home/kovid/work/calibre/src/calibre/ebooks/metadata/amazonfr.py:94 -msgid "Downloads metadata from amazon.com" -msgstr "Deskargatzen ditu metadatuak amazon.com gunetik" - -#: /home/kovid/work/calibre/src/calibre/ebooks/metadata/amazonfr.py:474 -msgid "" -" %prog [options]\n" -"\n" -" Fetch book metadata from Amazon. You must specify one of title, " -"author,\n" -" ISBN, publisher or keywords. Will fetch a maximum of 10 matches,\n" -" so you should make your query as specific as possible.\n" -" You can chose the language for metadata retrieval:\n" -" All & english & french & german & spanish\n" -" " -msgstr "" -" %prog [options]\n" -"\n" -" Lortu liburuaren metadatuak Amazon dendatik. Zehaztu egin beharko " -"hauetako bat, izenburua, egilea,\n" -" ISBNa, argitaletxea edo gako-hitzen bat. Hamar bat erkaketa besterik " -"ez dira egingo, 10 maximo,\n" -" beraz ahalik eta zehatzen egin ezazu galdera.\n" -" Metadatuak eskuratzerakoan hizkuntza hauen artean aukera dezakezu:\n" -" Guztiak & ingelesa & frantsesa & alemana & gaztelania\n" -" " - #: /home/kovid/work/calibre/src/calibre/ebooks/metadata/archive.py:41 msgid "" "Extract common e-book formats from archives (zip/rar) files. Also try to " @@ -2978,107 +3223,100 @@ msgstr "" "beretik, saia zaitez modu automatikoan detektatzen ea CBZ/CBR fitxategiak " "ote diren." -#: /home/kovid/work/calibre/src/calibre/ebooks/metadata/book/base.py:116 +#: /home/kovid/work/calibre/src/calibre/ebooks/metadata/book/base.py:145 msgid "TEMPLATE ERROR" msgstr "AKATSA TXANTILOIAN" -#: /home/kovid/work/calibre/src/calibre/ebooks/metadata/book/base.py:541 -#: /home/kovid/work/calibre/src/calibre/gui2/custom_column_widgets.py:64 -#: /home/kovid/work/calibre/src/calibre/gui2/custom_column_widgets.py:554 +#: /home/kovid/work/calibre/src/calibre/ebooks/metadata/book/base.py:628 +#: /home/kovid/work/calibre/src/calibre/gui2/custom_column_widgets.py:63 +#: /home/kovid/work/calibre/src/calibre/gui2/custom_column_widgets.py:563 msgid "No" msgstr "Ez" -#: /home/kovid/work/calibre/src/calibre/ebooks/metadata/book/base.py:541 -#: /home/kovid/work/calibre/src/calibre/gui2/custom_column_widgets.py:64 -#: /home/kovid/work/calibre/src/calibre/gui2/custom_column_widgets.py:554 +#: /home/kovid/work/calibre/src/calibre/ebooks/metadata/book/base.py:628 +#: /home/kovid/work/calibre/src/calibre/gui2/custom_column_widgets.py:63 +#: /home/kovid/work/calibre/src/calibre/gui2/custom_column_widgets.py:563 msgid "Yes" msgstr "Bai" -#: /home/kovid/work/calibre/src/calibre/ebooks/metadata/book/base.py:615 +#: /home/kovid/work/calibre/src/calibre/ebooks/metadata/book/base.py:723 #: /home/kovid/work/calibre/src/calibre/ebooks/pdf/manipulate/info.py:45 -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/book_info.py:112 -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/book_info.py:113 #: /home/kovid/work/calibre/src/calibre/gui2/dialogs/delete_matching_from_device.py:75 -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/fetch_metadata.py:60 -#: /home/kovid/work/calibre/src/calibre/gui2/library/models.py:65 -#: /home/kovid/work/calibre/src/calibre/gui2/library/models.py:418 +#: /home/kovid/work/calibre/src/calibre/gui2/library/models.py:63 #: /home/kovid/work/calibre/src/calibre/gui2/library/models.py:977 -#: /home/kovid/work/calibre/src/calibre/library/field_metadata.py:304 -#: /home/kovid/work/calibre/src/calibre/library/server/opds.py:590 +#: /home/kovid/work/calibre/src/calibre/gui2/metadata/single_download.py:132 +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/metadata_sources.py:152 +#: /home/kovid/work/calibre/src/calibre/gui2/store/mobileread/models.py:23 +#: /home/kovid/work/calibre/src/calibre/gui2/store/search/models.py:33 +#: /home/kovid/work/calibre/src/calibre/library/field_metadata.py:331 +#: /home/kovid/work/calibre/src/calibre/library/server/opds.py:574 msgid "Title" msgstr "Izenburua" -#: /home/kovid/work/calibre/src/calibre/ebooks/metadata/book/base.py:616 -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/fetch_metadata.py:61 -#: /home/kovid/work/calibre/src/calibre/gui2/library/models.py:67 -#: /home/kovid/work/calibre/src/calibre/gui2/library/models.py:423 +#: /home/kovid/work/calibre/src/calibre/ebooks/metadata/book/base.py:724 +#: /home/kovid/work/calibre/src/calibre/gui2/library/models.py:65 #: /home/kovid/work/calibre/src/calibre/gui2/library/models.py:978 +#: /home/kovid/work/calibre/src/calibre/gui2/store/mobileread/models.py:23 msgid "Author(s)" msgstr "Egilea(k)" -#: /home/kovid/work/calibre/src/calibre/ebooks/metadata/book/base.py:617 -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/fetch_metadata.py:63 -#: /home/kovid/work/calibre/src/calibre/gui2/library/models.py:72 +#: /home/kovid/work/calibre/src/calibre/ebooks/metadata/book/base.py:725 +#: /home/kovid/work/calibre/src/calibre/gui2/library/models.py:70 +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/metadata_sources.py:149 msgid "Publisher" msgstr "Argitaratzailea" -#: /home/kovid/work/calibre/src/calibre/ebooks/metadata/book/base.py:618 +#: /home/kovid/work/calibre/src/calibre/ebooks/metadata/book/base.py:726 #: /home/kovid/work/calibre/src/calibre/ebooks/pdf/manipulate/info.py:49 msgid "Producer" msgstr "Ekoizlea" -#: /home/kovid/work/calibre/src/calibre/ebooks/metadata/book/base.py:619 -#: /home/kovid/work/calibre/src/calibre/gui2/book_details.py:40 -#: /home/kovid/work/calibre/src/calibre/gui2/book_details.py:214 -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/book_info.py:114 -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/book_info_ui.py:79 -#: /home/kovid/work/calibre/src/calibre/gui2/library/models.py:380 -#: /home/kovid/work/calibre/src/calibre/gui2/library/models.py:1184 -#: /home/kovid/work/calibre/src/calibre/library/field_metadata.py:188 +#: /home/kovid/work/calibre/src/calibre/ebooks/metadata/book/base.py:727 +#: /home/kovid/work/calibre/src/calibre/gui2/metadata/single.py:871 +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/metadata_sources.py:147 +#: /home/kovid/work/calibre/src/calibre/library/field_metadata.py:211 msgid "Comments" msgstr "Iruzkinak" -#: /home/kovid/work/calibre/src/calibre/ebooks/metadata/book/base.py:621 +#: /home/kovid/work/calibre/src/calibre/ebooks/metadata/book/base.py:729 #: /home/kovid/work/calibre/src/calibre/ebooks/oeb/transforms/jacket.py:170 -#: /home/kovid/work/calibre/src/calibre/gui2/book_details.py:30 #: /home/kovid/work/calibre/src/calibre/gui2/dialogs/tag_categories.py:60 -#: /home/kovid/work/calibre/src/calibre/gui2/library/models.py:73 -#: /home/kovid/work/calibre/src/calibre/gui2/library/models.py:368 -#: /home/kovid/work/calibre/src/calibre/gui2/library/models.py:1180 -#: /home/kovid/work/calibre/src/calibre/library/field_metadata.py:161 -#: /home/kovid/work/calibre/src/calibre/library/server/browse.py:691 +#: /home/kovid/work/calibre/src/calibre/gui2/library/models.py:71 +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/create_custom_column.py:67 +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/metadata_sources.py:151 +#: /home/kovid/work/calibre/src/calibre/library/field_metadata.py:171 +#: /home/kovid/work/calibre/src/calibre/library/server/browse.py:761 msgid "Tags" msgstr "Etiketak" -#: /home/kovid/work/calibre/src/calibre/ebooks/metadata/book/base.py:623 +#: /home/kovid/work/calibre/src/calibre/ebooks/metadata/book/base.py:731 #: /home/kovid/work/calibre/src/calibre/ebooks/oeb/transforms/jacket.py:168 -#: /home/kovid/work/calibre/src/calibre/gui2/book_details.py:29 #: /home/kovid/work/calibre/src/calibre/gui2/dialogs/tag_categories.py:60 -#: /home/kovid/work/calibre/src/calibre/gui2/library/models.py:74 -#: /home/kovid/work/calibre/src/calibre/gui2/library/models.py:385 -#: /home/kovid/work/calibre/src/calibre/gui2/library/models.py:1189 -#: /home/kovid/work/calibre/src/calibre/library/field_metadata.py:109 +#: /home/kovid/work/calibre/src/calibre/gui2/library/models.py:72 +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/create_custom_column.py:67 +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/metadata_sources.py:153 +#: /home/kovid/work/calibre/src/calibre/library/field_metadata.py:114 msgid "Series" msgstr "Serieak" -#: /home/kovid/work/calibre/src/calibre/ebooks/metadata/book/base.py:624 +#: /home/kovid/work/calibre/src/calibre/ebooks/metadata/book/base.py:732 +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/metadata_sources.py:154 msgid "Language" msgstr "Hizkuntza" -#: /home/kovid/work/calibre/src/calibre/ebooks/metadata/book/base.py:626 -#: /home/kovid/work/calibre/src/calibre/gui2/library/models.py:1172 +#: /home/kovid/work/calibre/src/calibre/ebooks/metadata/book/base.py:734 msgid "Timestamp" msgstr "Dataren zigilua (noizkoa)" -#: /home/kovid/work/calibre/src/calibre/ebooks/metadata/book/base.py:628 +#: /home/kovid/work/calibre/src/calibre/ebooks/metadata/book/base.py:736 #: /home/kovid/work/calibre/src/calibre/ebooks/oeb/transforms/jacket.py:167 -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/fetch_metadata.py:65 -#: /home/kovid/work/calibre/src/calibre/gui2/library/models.py:70 -#: /home/kovid/work/calibre/src/calibre/library/field_metadata.py:259 +#: /home/kovid/work/calibre/src/calibre/gui2/library/models.py:68 +#: /home/kovid/work/calibre/src/calibre/gui2/metadata/single_download.py:132 +#: /home/kovid/work/calibre/src/calibre/library/field_metadata.py:271 msgid "Published" msgstr "Argitaratua" -#: /home/kovid/work/calibre/src/calibre/ebooks/metadata/book/base.py:630 +#: /home/kovid/work/calibre/src/calibre/ebooks/metadata/book/base.py:738 msgid "Rights" msgstr "Eskubideak" @@ -3200,215 +3438,7 @@ msgstr "Liburu-azala gorde da hemen" msgid "No cover found" msgstr "Ez da liburu-azalik aurkitu" -#: /home/kovid/work/calibre/src/calibre/ebooks/metadata/covers.py:27 -#: /home/kovid/work/calibre/src/calibre/ebooks/metadata/nicebooks.py:45 -msgid "Cover download" -msgstr "Deskargatu liburu-azala" - -#: /home/kovid/work/calibre/src/calibre/ebooks/metadata/covers.py:79 -msgid "Download covers from openlibrary.org" -msgstr "Deskargatu liburu-azalak hemendik: openlibrary.org" - -#: /home/kovid/work/calibre/src/calibre/ebooks/metadata/covers.py:107 -msgid "ISBN: %s not found" -msgstr "ISBN: %s ez da aurkitu" - -#: /home/kovid/work/calibre/src/calibre/ebooks/metadata/covers.py:117 -msgid "Download covers from amazon.com" -msgstr "" - -#: /home/kovid/work/calibre/src/calibre/ebooks/metadata/covers.py:205 -msgid "Download covers from Douban.com" -msgstr "Deskargatu liburu-azalak hemendik: Douban.com" - -#: /home/kovid/work/calibre/src/calibre/ebooks/metadata/covers.py:214 -msgid "Douban.com API timed out. Try again later." -msgstr "Douban.com API, denbora iraungita. Saia zaitez berriro beranduago." - -#: /home/kovid/work/calibre/src/calibre/ebooks/metadata/douban.py:42 -msgid "Downloads metadata from Douban.com" -msgstr "Deskargatu metadatuak hemendik: Douban.com" - -#: /home/kovid/work/calibre/src/calibre/ebooks/metadata/fetch.py:57 -msgid "Metadata download" -msgstr "Metadatuak deskargatu" - -#: /home/kovid/work/calibre/src/calibre/ebooks/metadata/fetch.py:141 -msgid "ratings" -msgstr "balorazioak" - -#: /home/kovid/work/calibre/src/calibre/ebooks/metadata/fetch.py:141 -msgid "tags" -msgstr "etiketak" - -#: /home/kovid/work/calibre/src/calibre/ebooks/metadata/fetch.py:142 -msgid "description/reviews" -msgstr "deskripzioa/aipamenak edo kritikak" - -#: /home/kovid/work/calibre/src/calibre/ebooks/metadata/fetch.py:143 -msgid "Download %s from %s" -msgstr "Deskargatu %s hemendik: %s" - -#: /home/kovid/work/calibre/src/calibre/ebooks/metadata/fetch.py:150 -msgid "Convert comments downloaded from %s to plain text" -msgstr "Bihurtu deskargatutako iruzkinak %s horretatik testu sinplera" - -#: /home/kovid/work/calibre/src/calibre/ebooks/metadata/fetch.py:178 -#: /home/kovid/work/calibre/src/calibre/ebooks/metadata/sources/google.py:159 -msgid "Downloads metadata from Google Books" -msgstr "Deskargatu metadatatuak Google Books gunetik" - -#: /home/kovid/work/calibre/src/calibre/ebooks/metadata/fetch.py:195 -msgid "Downloads metadata from isbndb.com" -msgstr "Deskargatu datuak isbndb.com gunetik" - -#: /home/kovid/work/calibre/src/calibre/ebooks/metadata/fetch.py:223 -msgid "" -"To use isbndb.com you must sign up for a %sfree account%s and enter your " -"access key below." -msgstr "" -"Erabiltzeko isbndb.com izena eman beharko duzu %sfree account%s doaneko " -"kontua izateko. Gero zure pasahitza erabili beharko duzu." - -#: /home/kovid/work/calibre/src/calibre/ebooks/metadata/fetch.py:233 -msgid "Downloads social metadata from amazon.com" -msgstr "Deskargatu gizarte mailako metadatuak amazon.com gunetik" - -#: /home/kovid/work/calibre/src/calibre/ebooks/metadata/fetch.py:254 -msgid "Downloads series information from ww2.kdl.org" -msgstr "" - -#: /home/kovid/work/calibre/src/calibre/ebooks/metadata/fictionwise.py:25 -msgid "Downloads metadata from Fictionwise" -msgstr "Deskargatzen ditu metadatuak Fictionwise gunetik" - -#: /home/kovid/work/calibre/src/calibre/ebooks/metadata/fictionwise.py:90 -#: /home/kovid/work/calibre/src/calibre/ebooks/metadata/nicebooks.py:108 -msgid "Query: %s" -msgstr "Zalantza: %s" - -#: /home/kovid/work/calibre/src/calibre/ebooks/metadata/fictionwise.py:100 -#: /home/kovid/work/calibre/src/calibre/ebooks/metadata/fictionwise.py:285 -msgid "Fictionwise timed out. Try again later." -msgstr "" -"Fictionwise horretan denbora gehiegi alferrik. Saia zaitez berriro " -"beranduago." - -#: /home/kovid/work/calibre/src/calibre/ebooks/metadata/fictionwise.py:101 -#: /home/kovid/work/calibre/src/calibre/ebooks/metadata/fictionwise.py:286 -msgid "Fictionwise encountered an error." -msgstr "Fictionwise errorea aurkitu da." - -#: /home/kovid/work/calibre/src/calibre/ebooks/metadata/fictionwise.py:219 -msgid "" -"SUMMARY:\n" -" %s" -msgstr "" -"LABURPENA:\n" -" %s" - -#: /home/kovid/work/calibre/src/calibre/ebooks/metadata/fictionwise.py:316 -#: /home/kovid/work/calibre/src/calibre/ebooks/metadata/fictionwise.py:333 -msgid "Failed to get all details for an entry" -msgstr "Sartutakoaren zehaztasun guztiak eskuratzen akatsa gertatu da" - -#: /home/kovid/work/calibre/src/calibre/ebooks/metadata/fictionwise.py:354 -msgid "" -" %prog [options]\n" -"\n" -" Fetch book metadata from Fictionwise. You must specify one of title, " -"author,\n" -" or keywords. No ISBN specification possible. Will fetch a maximum of " -"20 matches,\n" -" so you should make your query as specific as possible.\n" -" " -msgstr "" -" %prog [options]\n" -"\n" -" Eskuratu liburuen metadatuak Fictionwise horretatik. Zehaztu beharko " -"duzu egilea, izenburua,\n" -" edo gako-hitzen bat. Ezin ISBNa erabili. 20 aldiz saia zaitezke, ez " -"gehiagotan,\n" -" beraz egin ezazu zure galdera ahalik eta zehatzen.\n" -" " - -#: /home/kovid/work/calibre/src/calibre/ebooks/metadata/fictionwise.py:362 -#: /home/kovid/work/calibre/src/calibre/ebooks/metadata/nicebooks.py:363 -msgid "Book title" -msgstr "Liburuaren izenburua" - -#: /home/kovid/work/calibre/src/calibre/ebooks/metadata/fictionwise.py:363 -#: /home/kovid/work/calibre/src/calibre/ebooks/metadata/nicebooks.py:364 -msgid "Book author(s)" -msgstr "Liburuaren egilea(k)" - -#: /home/kovid/work/calibre/src/calibre/ebooks/metadata/fictionwise.py:364 -#: /home/kovid/work/calibre/src/calibre/ebooks/metadata/nicebooks.py:365 -msgid "Book publisher" -msgstr "Liburuaren editorea" - -#: /home/kovid/work/calibre/src/calibre/ebooks/metadata/fictionwise.py:365 -#: /home/kovid/work/calibre/src/calibre/ebooks/metadata/nicebooks.py:367 -msgid "Keywords" -msgstr "Gako-hitzak" - -#: /home/kovid/work/calibre/src/calibre/ebooks/metadata/fictionwise.py:367 -#: /home/kovid/work/calibre/src/calibre/ebooks/metadata/nicebooks.py:373 -msgid "Maximum number of results to fetch" -msgstr "Lortzeko dauden emaitza kopuru maximoa" - -#: /home/kovid/work/calibre/src/calibre/ebooks/metadata/fictionwise.py:369 -#: /home/kovid/work/calibre/src/calibre/ebooks/metadata/nicebooks.py:375 -msgid "Be more verbose about errors" -msgstr "Akatsei buruz berri gehago mesedez" - -#: /home/kovid/work/calibre/src/calibre/ebooks/metadata/fictionwise.py:383 -#: /home/kovid/work/calibre/src/calibre/ebooks/metadata/nicebooks.py:390 -msgid "No result found for this search!" -msgstr "Bilaketa honetarako emaitzik ez!" - -#: /home/kovid/work/calibre/src/calibre/ebooks/metadata/isbndb.py:107 -msgid "" -"\n" -"%prog [options] key\n" -"\n" -"Fetch metadata for books from isndb.com. You can specify either the\n" -"books ISBN ID or its title and author. If you specify the title and author,\n" -"then more than one book may be returned.\n" -"\n" -"key is the account key you generate after signing up for a free account from " -"isbndb.com.\n" -"\n" -msgstr "" -"\n" -"%prog [aukerak] key\n" -"\n" -"Eskura ezazu liburu elektronikoei buruzko metadatuak hemendik: isndb.com. " -"Zehaztu ahal dituzu bai\n" -"liburuen ISBN ID, bai egilea, bai izenburua. Zehazten badituzu izenburua eta " -"egilea, hala eta guztiz ere, liburu bat baino gehiago egon daiteke\n" -"\n" -"pasahitza zera da, isbndb.com horretan zeuk sortu duzun pasahitza, hau da, " -"doaneko kontu batean izena ematen duzunean sortzen duzun pasahitza.\n" -"\n" - -#: /home/kovid/work/calibre/src/calibre/ebooks/metadata/isbndb.py:118 -msgid "The ISBN ID of the book you want metadata for." -msgstr "Nahi dituzun metadatuen liburuaren ISBN ID." - -#: /home/kovid/work/calibre/src/calibre/ebooks/metadata/isbndb.py:120 -msgid "The author whose book to search for." -msgstr "Bilatzen ari zaren liburuaren egilea." - -#: /home/kovid/work/calibre/src/calibre/ebooks/metadata/isbndb.py:122 -msgid "The title of the book to search for." -msgstr "Bilatzen ari zaren liburuaren izenburua." - -#: /home/kovid/work/calibre/src/calibre/ebooks/metadata/isbndb.py:124 -msgid "The publisher of the book to search for." -msgstr "Bilatzen ari zaren liburuaren argitaleytxea." - -#: /home/kovid/work/calibre/src/calibre/ebooks/metadata/library_thing.py:75 +#: /home/kovid/work/calibre/src/calibre/ebooks/metadata/library_thing.py:64 msgid "" "\n" "%prog [options] ISBN\n" @@ -3422,92 +3452,108 @@ msgstr "" "Eskura ezazu liburu azal bat irudi/gizarte metadatuekin bere ISBN-ren bidez " "hemendik: LibraryThing.com\n" -#: /home/kovid/work/calibre/src/calibre/ebooks/metadata/nicebooks.py:26 -msgid "Downloads metadata from french Nicebooks" -msgstr "Deskargatzen ditu metadatuak Nicebooks frantziarretik" - -#: /home/kovid/work/calibre/src/calibre/ebooks/metadata/nicebooks.py:42 -msgid "Downloads covers from french Nicebooks" -msgstr "Deskargatzen ditu liburu-azalak Nicebooks frantziarretik" - -#: /home/kovid/work/calibre/src/calibre/ebooks/metadata/nicebooks.py:118 -#: /home/kovid/work/calibre/src/calibre/ebooks/metadata/nicebooks.py:242 -#: /home/kovid/work/calibre/src/calibre/ebooks/metadata/nicebooks.py:320 -msgid "Nicebooks timed out. Try again later." -msgstr "" -"Nicebooks horretan egoteko denbora agorturik. Saia zaitez berriro geroago." - -#: /home/kovid/work/calibre/src/calibre/ebooks/metadata/nicebooks.py:119 -#: /home/kovid/work/calibre/src/calibre/ebooks/metadata/nicebooks.py:243 -msgid "Nicebooks encountered an error." -msgstr "Nicebooks horrek akatsa topatu du." - -#: /home/kovid/work/calibre/src/calibre/ebooks/metadata/nicebooks.py:323 -msgid "ISBN: %s not found." -msgstr "ISBN: %s ez da aurkitu." - -#: /home/kovid/work/calibre/src/calibre/ebooks/metadata/nicebooks.py:324 -msgid "An errror occured with Nicebooks cover fetcher" -msgstr "Akats bat gertatu da Nicebooks-en liburu-azalen eskuratzailearekin" - -#: /home/kovid/work/calibre/src/calibre/ebooks/metadata/nicebooks.py:354 -msgid "" -" %prog [options]\n" -"\n" -" Fetch book metadata from Nicebooks. You must specify one of title, " -"author,\n" -" ISBN, publisher or keywords. Will fetch a maximum of 20 matches,\n" -" so you should make your query as specific as possible.\n" -" It can also get covers if the option is activated.\n" -" " -msgstr "" -" %prog [options]\n" -"\n" -" Lor itzazu liburuaren metadatuak Nicebooks horretatik. Zehaztu " -"beharko duzu izenburua, egilea,\n" -" ISBNa, argitaletxea edo gako-hitzak. Hogei bat 20 bilaketa, askoz " -"jota, egingo dira,\n" -" beraz zure galdera ahalik eta zehatzen egin beharko duzu beti.\n" -" liburu-azalak ere lor daitezke aukera hori lehenetsita izanez gero.\n" -" " - -#: /home/kovid/work/calibre/src/calibre/ebooks/metadata/nicebooks.py:366 -msgid "Book ISBN" -msgstr "Liburuaren ISBN" - -#: /home/kovid/work/calibre/src/calibre/ebooks/metadata/nicebooks.py:369 -msgid "Covers: 1-Check/ 2-Download" -msgstr "Liburu-azalak: 1-Bilatuk/ 2-Deskargatu" - -#: /home/kovid/work/calibre/src/calibre/ebooks/metadata/nicebooks.py:371 -msgid "Covers files path" -msgstr "Liburu-azalen fitxategietarako bidea" - -#: /home/kovid/work/calibre/src/calibre/ebooks/metadata/nicebooks.py:396 -msgid "No cover found!" -msgstr "Ez da aurkitu liburu-azalik!" - -#: /home/kovid/work/calibre/src/calibre/ebooks/metadata/nicebooks.py:398 -msgid "A cover was found for this book" -msgstr "Liburu azal bat topatu egin da" - -#: /home/kovid/work/calibre/src/calibre/ebooks/metadata/nicebooks.py:407 -msgid "Cover saved to file " -msgstr "Liburu-azala fitxategi honetan gorde egin da: " - -#: /home/kovid/work/calibre/src/calibre/ebooks/metadata/opf2.py:1312 -#: /home/kovid/work/calibre/src/calibre/ebooks/oeb/base.py:1448 +#: /home/kovid/work/calibre/src/calibre/ebooks/metadata/opf2.py:1358 +#: /home/kovid/work/calibre/src/calibre/ebooks/oeb/base.py:1493 +#: /home/kovid/work/calibre/src/calibre/gui2/metadata/single.py:883 +#: /home/kovid/work/calibre/src/calibre/gui2/store/search/models.py:33 msgid "Cover" msgstr "Liburu-azala" -#: /home/kovid/work/calibre/src/calibre/ebooks/metadata/sources/amazon.py:16 -msgid "Downloads metadata from Amazon" +#: /home/kovid/work/calibre/src/calibre/ebooks/metadata/sources/amazon.py:384 +msgid "Downloads metadata and covers from Amazon" msgstr "" -#: /home/kovid/work/calibre/src/calibre/ebooks/metadata/sources/base.py:22 +#: /home/kovid/work/calibre/src/calibre/ebooks/metadata/sources/amazon.py:394 +msgid "US" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/ebooks/metadata/sources/amazon.py:395 +msgid "France" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/ebooks/metadata/sources/amazon.py:396 +msgid "Germany" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/ebooks/metadata/sources/amazon.py:397 +msgid "UK" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/ebooks/metadata/sources/amazon.py:398 +msgid "Italy" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/ebooks/metadata/sources/amazon.py:402 +msgid "Amazon website to use:" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/ebooks/metadata/sources/amazon.py:403 +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:508 +msgid "Amazon timed out. Try again later." +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/ebooks/metadata/sources/base.py:159 msgid "Metadata source" msgstr "" +#: /home/kovid/work/calibre/src/calibre/ebooks/metadata/sources/douban.py:154 +msgid "Downloads metadata and covers from Douban.com" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/ebooks/metadata/sources/google.py:160 +msgid "Downloads metadata and covers from Google Books" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/ebooks/metadata/sources/isbndb.py:27 +msgid "Downloads metadata from isbndb.com" +msgstr "Deskargatu datuak isbndb.com gunetik" + +#: /home/kovid/work/calibre/src/calibre/ebooks/metadata/sources/isbndb.py:37 +msgid "IsbnDB key:" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/ebooks/metadata/sources/isbndb.py:38 +msgid "" +"To use isbndb.com you have to sign up for a free accountat isbndb.com and " +"get an access key." +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/ebooks/metadata/sources/isbndb.py:42 +msgid "" +"To use metadata from isbndb.com you must sign up for a free account and get " +"an isbndb key and enter it below. Instructions to get the key are here." +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/ebooks/metadata/sources/openlibrary.py:15 +msgid "Downloads covers from The Open Library" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/ebooks/metadata/sources/overdrive.py:33 +msgid "Downloads metadata and covers from Overdrive's Content Reserve" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/ebooks/metadata/sources/overdrive.py:45 +msgid "Download all metadata (slow)" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/ebooks/metadata/sources/overdrive.py:46 +msgid "Enable this option to gather all metadata available from Overdrive." +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/ebooks/metadata/sources/overdrive.py:49 +msgid "" +"Additional metadata can be taken from Overdrive's book detail page. This " +"includes a limited set of tags used by libraries, comments, language, and " +"the ebook ISBN. Collecting this data is disabled by default due to the extra " +"time required. Check the download all metadata option below to enable " +"downloading this data." +msgstr "" + #: /home/kovid/work/calibre/src/calibre/ebooks/mobi/output.py:22 msgid "Modify images to meet Palm device size limitations." msgstr "" @@ -3557,74 +3603,74 @@ msgstr "" msgid "All articles" msgstr "Artikulu guztiak" -#: /home/kovid/work/calibre/src/calibre/ebooks/mobi/reader.py:267 +#: /home/kovid/work/calibre/src/calibre/ebooks/mobi/reader.py:274 msgid "This is an Amazon Topaz book. It cannot be processed." msgstr "Hauxe Amazon Topaz liburua da. Ezin da prozesatu." -#: /home/kovid/work/calibre/src/calibre/ebooks/oeb/base.py:1449 +#: /home/kovid/work/calibre/src/calibre/ebooks/oeb/base.py:1494 msgid "Title Page" msgstr "Orriaren Izenburua" -#: /home/kovid/work/calibre/src/calibre/ebooks/oeb/base.py:1450 +#: /home/kovid/work/calibre/src/calibre/ebooks/oeb/base.py:1495 #: /home/kovid/work/calibre/src/calibre/ebooks/oeb/transforms/htmltoc.py:15 -#: /home/kovid/work/calibre/src/calibre/gui2/viewer/main.py:54 +#: /home/kovid/work/calibre/src/calibre/gui2/viewer/main.py:56 #: /home/kovid/work/calibre/src/calibre/gui2/viewer/main_ui.py:199 msgid "Table of Contents" msgstr "Aurkibidea" -#: /home/kovid/work/calibre/src/calibre/ebooks/oeb/base.py:1451 +#: /home/kovid/work/calibre/src/calibre/ebooks/oeb/base.py:1496 msgid "Index" msgstr "Indizea" -#: /home/kovid/work/calibre/src/calibre/ebooks/oeb/base.py:1452 +#: /home/kovid/work/calibre/src/calibre/ebooks/oeb/base.py:1497 msgid "Glossary" msgstr "Glosarioa" -#: /home/kovid/work/calibre/src/calibre/ebooks/oeb/base.py:1453 +#: /home/kovid/work/calibre/src/calibre/ebooks/oeb/base.py:1498 msgid "Acknowledgements" msgstr "Aipamenak" -#: /home/kovid/work/calibre/src/calibre/ebooks/oeb/base.py:1454 +#: /home/kovid/work/calibre/src/calibre/ebooks/oeb/base.py:1499 msgid "Bibliography" msgstr "Bibliografia" -#: /home/kovid/work/calibre/src/calibre/ebooks/oeb/base.py:1455 +#: /home/kovid/work/calibre/src/calibre/ebooks/oeb/base.py:1500 msgid "Colophon" msgstr "Azken oharra" -#: /home/kovid/work/calibre/src/calibre/ebooks/oeb/base.py:1456 +#: /home/kovid/work/calibre/src/calibre/ebooks/oeb/base.py:1501 msgid "Copyright" msgstr "Copyright-a" -#: /home/kovid/work/calibre/src/calibre/ebooks/oeb/base.py:1457 +#: /home/kovid/work/calibre/src/calibre/ebooks/oeb/base.py:1502 msgid "Dedication" msgstr "Eskaintza" -#: /home/kovid/work/calibre/src/calibre/ebooks/oeb/base.py:1458 +#: /home/kovid/work/calibre/src/calibre/ebooks/oeb/base.py:1503 msgid "Epigraph" msgstr "Epigrafea" -#: /home/kovid/work/calibre/src/calibre/ebooks/oeb/base.py:1459 +#: /home/kovid/work/calibre/src/calibre/ebooks/oeb/base.py:1504 msgid "Foreword" msgstr "Sarrera" -#: /home/kovid/work/calibre/src/calibre/ebooks/oeb/base.py:1460 +#: /home/kovid/work/calibre/src/calibre/ebooks/oeb/base.py:1505 msgid "List of Illustrations" msgstr "Irudien zerrenda" -#: /home/kovid/work/calibre/src/calibre/ebooks/oeb/base.py:1461 +#: /home/kovid/work/calibre/src/calibre/ebooks/oeb/base.py:1506 msgid "List of Tables" msgstr "Taulen zerrenda" -#: /home/kovid/work/calibre/src/calibre/ebooks/oeb/base.py:1462 +#: /home/kovid/work/calibre/src/calibre/ebooks/oeb/base.py:1507 msgid "Notes" msgstr "Oharrak" -#: /home/kovid/work/calibre/src/calibre/ebooks/oeb/base.py:1463 +#: /home/kovid/work/calibre/src/calibre/ebooks/oeb/base.py:1508 msgid "Preface" msgstr "Aitzinsolasa" -#: /home/kovid/work/calibre/src/calibre/ebooks/oeb/base.py:1464 +#: /home/kovid/work/calibre/src/calibre/ebooks/oeb/base.py:1509 msgid "Main Text" msgstr "Testu nagusia" @@ -3634,8 +3680,7 @@ msgstr "%s liburuen formatuekin ezin. Oraingoz sostengurik ez" #: /home/kovid/work/calibre/src/calibre/ebooks/oeb/transforms/cover.py:98 #: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk.py:176 -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single.py:220 -#: /home/kovid/work/calibre/src/calibre/gui2/metadata/basic_widgets.py:703 +#: /home/kovid/work/calibre/src/calibre/gui2/metadata/basic_widgets.py:783 msgid "Book %s of %s" msgstr "%s liburu %s-etatik" @@ -3644,8 +3689,10 @@ msgid "HTML TOC generation options." msgstr "HTML aurkibideak sortzeko aukerak." #: /home/kovid/work/calibre/src/calibre/ebooks/oeb/transforms/jacket.py:169 -#: /home/kovid/work/calibre/src/calibre/gui2/library/models.py:71 -#: /home/kovid/work/calibre/src/calibre/library/server/browse.py:689 +#: /home/kovid/work/calibre/src/calibre/gui2/library/models.py:69 +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/create_custom_column.py:68 +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/metadata_sources.py:150 +#: /home/kovid/work/calibre/src/calibre/library/server/browse.py:759 msgid "Rating" msgstr "Balorazioa" @@ -3675,7 +3722,7 @@ msgstr "" msgid "Footnotes" msgstr "Oin-oharrak" -#: /home/kovid/work/calibre/src/calibre/ebooks/pdb/ereader/reader132.py:135 +#: /home/kovid/work/calibre/src/calibre/ebooks/pdb/ereader/reader132.py:139 msgid "Sidebar" msgstr "Alboko barra" @@ -3692,9 +3739,9 @@ msgstr "" "Oharra: aukera hau ez dute formatu guztiek onartuko." #: /home/kovid/work/calibre/src/calibre/ebooks/pdb/output.py:32 -#: /home/kovid/work/calibre/src/calibre/ebooks/pml/output.py:37 +#: /home/kovid/work/calibre/src/calibre/ebooks/pml/output.py:36 #: /home/kovid/work/calibre/src/calibre/ebooks/rb/output.py:21 -#: /home/kovid/work/calibre/src/calibre/ebooks/txt/output.py:41 +#: /home/kovid/work/calibre/src/calibre/ebooks/txt/output.py:40 msgid "Add Table of Contents to beginning of the book." msgstr "Gehi ezazu aurkibidea liburuaren hasieran." @@ -3838,11 +3885,12 @@ msgstr "" #: /home/kovid/work/calibre/src/calibre/ebooks/pdf/manipulate/info.py:46 #: /home/kovid/work/calibre/src/calibre/gui2/dialogs/delete_matching_from_device.py:75 -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/edit_authors_dialog.py:35 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/edit_authors_dialog.py:40 msgid "Author" msgstr "Egilea" #: /home/kovid/work/calibre/src/calibre/ebooks/pdf/manipulate/info.py:47 +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/emailp.py:27 msgid "Subject" msgstr "Gaia" @@ -3973,19 +4021,19 @@ msgid "" "full first page of the generated pdf." msgstr "" -#: /home/kovid/work/calibre/src/calibre/ebooks/pdf/pdftohtml.py:55 +#: /home/kovid/work/calibre/src/calibre/ebooks/pdf/pdftohtml.py:57 msgid "Could not find pdftohtml, check it is in your PATH" msgstr "" "Ezin izan da aurkitu pdftohtml programarik pdf formatutik html formatura " "egiteko, zoaz ikustera zure PATH horretan, zure BIDE horretan" -#: /home/kovid/work/calibre/src/calibre/ebooks/pml/output.py:33 +#: /home/kovid/work/calibre/src/calibre/ebooks/pml/output.py:32 msgid "" "Specify the character encoding of the output document. The default is cp1252." msgstr "" "Zehaztu karaktereen kodea helburu dokumentuan. Lehenetsita hauxe: cp1252." -#: /home/kovid/work/calibre/src/calibre/ebooks/pml/output.py:40 +#: /home/kovid/work/calibre/src/calibre/ebooks/pml/output.py:39 msgid "" "Do not reduce the size or bit depth of images. Images have their size and " "depth reduced by default to accommodate applications that can not convert " @@ -4001,7 +4049,7 @@ msgstr "" msgid "Table of Contents:" msgstr "Aurkibidea:" -#: /home/kovid/work/calibre/src/calibre/ebooks/rtf/input.py:271 +#: /home/kovid/work/calibre/src/calibre/ebooks/rtf/input.py:272 msgid "" "This RTF file has a feature calibre does not support. Convert it to HTML " "first and then try it.\n" @@ -4017,14 +4065,14 @@ msgstr "" #: /home/kovid/work/calibre/src/calibre/ebooks/snb/output.py:25 #: /home/kovid/work/calibre/src/calibre/ebooks/tcr/output.py:23 -#: /home/kovid/work/calibre/src/calibre/ebooks/txt/output.py:37 +#: /home/kovid/work/calibre/src/calibre/ebooks/txt/output.py:36 msgid "" "Specify the character encoding of the output document. The default is utf-8." msgstr "" "Zehaztu karaktereen kodea helburu dokumentuan. Lehenetsita hauxe: utf-8." #: /home/kovid/work/calibre/src/calibre/ebooks/snb/output.py:29 -#: /home/kovid/work/calibre/src/calibre/ebooks/txt/output.py:44 +#: /home/kovid/work/calibre/src/calibre/ebooks/txt/output.py:43 msgid "" "The maximum number of characters per line. This splits on the first space " "before the specified value. If no space is found the line will be broken at " @@ -4125,7 +4173,7 @@ msgstr "" msgid "Do not insert a Table of Contents into the output text." msgstr "Ez txertatu aurkibiderik helburu testuan." -#: /home/kovid/work/calibre/src/calibre/ebooks/txt/output.py:31 +#: /home/kovid/work/calibre/src/calibre/ebooks/txt/output.py:30 msgid "" "Type of newline to use. Options are %s. Default is 'system'. Use 'old_mac' " "for compatibility with Mac OS 9 and earlier. For Mac OS X use 'unix'. " @@ -4136,7 +4184,7 @@ msgstr "" "Mac OS X horrekin erabili 'unix'. 'system' horrek lehenetsi egingo du OS " "honek erabiltzen duen lerro-berri mota." -#: /home/kovid/work/calibre/src/calibre/ebooks/txt/output.py:51 +#: /home/kovid/work/calibre/src/calibre/ebooks/txt/output.py:50 msgid "" "Force splitting on the max-line-length value when no space is present. Also " "allows max-line-length to be below the minimum" @@ -4145,7 +4193,7 @@ msgstr "" "espaziorik ez dagoenean. Onartzen du, baita, lerro-luzera-maximoa minomoaren " "azpian egotea." -#: /home/kovid/work/calibre/src/calibre/ebooks/txt/output.py:56 +#: /home/kovid/work/calibre/src/calibre/ebooks/txt/output.py:55 msgid "" "Formatting used within the document.\n" "* plain: Produce plain text.\n" @@ -4153,89 +4201,102 @@ msgid "" "* textile: Produce Textile formatted text." msgstr "" -#: /home/kovid/work/calibre/src/calibre/ebooks/txt/output.py:62 +#: /home/kovid/work/calibre/src/calibre/ebooks/txt/output.py:61 msgid "" "Do not remove links within the document. This is only useful when paired " "with a txt-output-formatting option that is not none because links are " "always removed with plain text output." msgstr "" -#: /home/kovid/work/calibre/src/calibre/ebooks/txt/output.py:67 +#: /home/kovid/work/calibre/src/calibre/ebooks/txt/output.py:66 msgid "" "Do not remove image references within the document. This is only useful when " "paired with a txt-output-formatting option that is not none because links " "are always removed with plain text output." msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/__init__.py:71 +#: /home/kovid/work/calibre/src/calibre/ebooks/txt/output.py:71 +msgid "" +"Do not remove font color from output. This is only useful when txt-output-" +"formatting is set to textile. Textile is the only formatting that supports " +"setting font color. If this option is not specified font color will not be " +"set and default to the color displayed by the reader (generally this is " +"black)." +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/__init__.py:103 msgid "Send file to storage card instead of main memory by default" msgstr "" "Lehenetsita: bidali fitxategia memoria-txartelara trepetaren memoria " "nagusira bidali beharrean" -#: /home/kovid/work/calibre/src/calibre/gui2/__init__.py:73 +#: /home/kovid/work/calibre/src/calibre/gui2/__init__.py:105 msgid "Confirm before deleting" msgstr "Baieztatu ezabatu baino lehen" -#: /home/kovid/work/calibre/src/calibre/gui2/__init__.py:75 +#: /home/kovid/work/calibre/src/calibre/gui2/__init__.py:107 msgid "Main window geometry" msgstr "Leiho nagusiaren geometria" -#: /home/kovid/work/calibre/src/calibre/gui2/__init__.py:77 +#: /home/kovid/work/calibre/src/calibre/gui2/__init__.py:109 msgid "Notify when a new version is available" msgstr "Oharra bidali bertsio berri bat eskuragarri dagoen bakoitzean" -#: /home/kovid/work/calibre/src/calibre/gui2/__init__.py:79 +#: /home/kovid/work/calibre/src/calibre/gui2/__init__.py:111 msgid "Use Roman numerals for series number" msgstr "Erabili zenbaki erromatarrak zenbaki segidetarako" -#: /home/kovid/work/calibre/src/calibre/gui2/__init__.py:81 +#: /home/kovid/work/calibre/src/calibre/gui2/__init__.py:113 msgid "Sort tags list by name, popularity, or rating" msgstr "" "Sailkatu etiketa zerrendak, izenen arabera, ospearen arabera edo balorazioen " "arabera" -#: /home/kovid/work/calibre/src/calibre/gui2/__init__.py:83 +#: /home/kovid/work/calibre/src/calibre/gui2/__init__.py:115 +msgid "Match tags by any or all." +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/__init__.py:117 msgid "Number of covers to show in the cover browsing mode" msgstr "Erakutsiko den liburu-azal kopurua, liburu-azal-arakatzaile moduan" -#: /home/kovid/work/calibre/src/calibre/gui2/__init__.py:85 +#: /home/kovid/work/calibre/src/calibre/gui2/__init__.py:119 msgid "Defaults for conversion to LRF" msgstr "Lehenetsitako balioak LRF formatura bihurtzeko" -#: /home/kovid/work/calibre/src/calibre/gui2/__init__.py:87 +#: /home/kovid/work/calibre/src/calibre/gui2/__init__.py:121 msgid "Options for the LRF ebook viewer" msgstr "Aukerak LRF liburu-e irakurgailuarentzat" -#: /home/kovid/work/calibre/src/calibre/gui2/__init__.py:90 +#: /home/kovid/work/calibre/src/calibre/gui2/__init__.py:124 msgid "Formats that are viewed using the internal viewer" msgstr "Barneko irakurtzeko sistema erabilita ikus daitezkeen formatuak" -#: /home/kovid/work/calibre/src/calibre/gui2/__init__.py:92 +#: /home/kovid/work/calibre/src/calibre/gui2/__init__.py:126 msgid "Columns to be displayed in the book list" msgstr "Liburu zerrenda zenbat zutabetan erakutsiko" -#: /home/kovid/work/calibre/src/calibre/gui2/__init__.py:93 +#: /home/kovid/work/calibre/src/calibre/gui2/__init__.py:127 msgid "Automatically launch content server on application startup" msgstr "Automatikoki abiaraziko du zerbitzariko edukia hasi aplikazioan" -#: /home/kovid/work/calibre/src/calibre/gui2/__init__.py:94 +#: /home/kovid/work/calibre/src/calibre/gui2/__init__.py:128 msgid "Oldest news kept in database" msgstr "Albiste zaharragoak datu basean gordeta" -#: /home/kovid/work/calibre/src/calibre/gui2/__init__.py:95 +#: /home/kovid/work/calibre/src/calibre/gui2/__init__.py:129 msgid "Show system tray icon" msgstr "Erakutsi sistemako erretiluaren ikonoa" -#: /home/kovid/work/calibre/src/calibre/gui2/__init__.py:97 +#: /home/kovid/work/calibre/src/calibre/gui2/__init__.py:131 msgid "Upload downloaded news to device" msgstr "Karga itzazu irakurgailuan deskargaturiko albisteak" -#: /home/kovid/work/calibre/src/calibre/gui2/__init__.py:99 +#: /home/kovid/work/calibre/src/calibre/gui2/__init__.py:133 msgid "Delete books from library after uploading to device" msgstr "Ezabatu liburuak liburutegitik irakurgailura kargatu eta gero" -#: /home/kovid/work/calibre/src/calibre/gui2/__init__.py:101 +#: /home/kovid/work/calibre/src/calibre/gui2/__init__.py:135 msgid "" "Show the cover flow in a separate window instead of in the main calibre " "window" @@ -4243,94 +4304,144 @@ msgstr "" "Erakutsi Cover Flow, (liburu-azal nabigazioa), berariazko leiho batean eta " "ez calibreren leiho nagusian" -#: /home/kovid/work/calibre/src/calibre/gui2/__init__.py:103 +#: /home/kovid/work/calibre/src/calibre/gui2/__init__.py:137 msgid "Disable notifications from the system tray icon" msgstr "Desgaitu abisuak sistemaren erretilu ikonotik" -#: /home/kovid/work/calibre/src/calibre/gui2/__init__.py:105 +#: /home/kovid/work/calibre/src/calibre/gui2/__init__.py:139 msgid "Default action to perform when send to device button is clicked" msgstr "" "Lehenetsitako egiteko ekintza klik egiten duzunean \"bidali irakurgailura\" " "botoian" -#: /home/kovid/work/calibre/src/calibre/gui2/__init__.py:110 +#: /home/kovid/work/calibre/src/calibre/gui2/__init__.py:144 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:113 +#: /home/kovid/work/calibre/src/calibre/gui2/__init__.py:147 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:131 -msgid "Maximum number of waiting worker processes" -msgstr "\"Langilearen zain\" prozesuen gehienezko kopurua" +#: /home/kovid/work/calibre/src/calibre/gui2/__init__.py:165 +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:133 +#: /home/kovid/work/calibre/src/calibre/gui2/__init__.py:169 msgid "Download social metadata (tags/rating/etc.)" msgstr "" "Deskargatu gizarte mailako metadatuak (etiketak/balorazioak/eta abar.)" -#: /home/kovid/work/calibre/src/calibre/gui2/__init__.py:135 +#: /home/kovid/work/calibre/src/calibre/gui2/__init__.py:171 msgid "Overwrite author and title with new metadata" msgstr "" "Gainetik idatzi egilearen izena eta testuaren izenburua metadatu berriekin" -#: /home/kovid/work/calibre/src/calibre/gui2/__init__.py:137 -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/fetch_metadata_ui.py:101 +#: /home/kovid/work/calibre/src/calibre/gui2/__init__.py:173 msgid "Automatically download the cover, if available" msgstr "Modu automatikoan deskargatu liburu-azala, eskura baldin badago." -#: /home/kovid/work/calibre/src/calibre/gui2/__init__.py:139 +#: /home/kovid/work/calibre/src/calibre/gui2/__init__.py:175 msgid "Limit max simultaneous jobs to number of CPUs" msgstr "" "Murriztu itzazu aldi bereko gehienezko egitekoen kopurua dauden CPU-en " "arabera" -#: /home/kovid/work/calibre/src/calibre/gui2/__init__.py:141 +#: /home/kovid/work/calibre/src/calibre/gui2/__init__.py:177 msgid "The layout of the user interface" msgstr "Erabiltzailearen interfazearen itxura" -#: /home/kovid/work/calibre/src/calibre/gui2/__init__.py:143 +#: /home/kovid/work/calibre/src/calibre/gui2/__init__.py:179 msgid "Show the average rating per item indication in the tag browser" msgstr "" "Erakutsi batez besteko balorazioa kontu bakoitzeko etiketen arakatzailean" -#: /home/kovid/work/calibre/src/calibre/gui2/__init__.py:145 +#: /home/kovid/work/calibre/src/calibre/gui2/__init__.py:181 msgid "Disable UI animations" msgstr "Desgaitu EI (erabiltzailearen interfazearen) animazioak" -#: /home/kovid/work/calibre/src/calibre/gui2/__init__.py:150 +#: /home/kovid/work/calibre/src/calibre/gui2/__init__.py:186 msgid "tag browser categories not to display" msgstr "etiketatu arakatzailearen kategoriak ez erakusteko moduan" -#: /home/kovid/work/calibre/src/calibre/gui2/__init__.py:419 +#: /home/kovid/work/calibre/src/calibre/gui2/__init__.py:461 msgid "Choose Files" msgstr "Aukeratu fitxategiak" #: /home/kovid/work/calibre/src/calibre/gui2/actions/add.py:29 +#: /home/kovid/work/calibre/src/calibre/gui2/metadata/basic_widgets.py:599 +msgid "Books" +msgstr "Liburuak" + +#: /home/kovid/work/calibre/src/calibre/gui2/actions/add.py:30 +msgid "EPUB Books" +msgstr "EPUB liburuak" + +#: /home/kovid/work/calibre/src/calibre/gui2/actions/add.py:31 +msgid "LRF Books" +msgstr "LRF liburuak" + +#: /home/kovid/work/calibre/src/calibre/gui2/actions/add.py:32 +msgid "HTML Books" +msgstr "HTML liburuak" + +#: /home/kovid/work/calibre/src/calibre/gui2/actions/add.py:33 +msgid "LIT Books" +msgstr "LIT liburuak" + +#: /home/kovid/work/calibre/src/calibre/gui2/actions/add.py:34 +msgid "MOBI Books" +msgstr "MOBI liburuak" + +#: /home/kovid/work/calibre/src/calibre/gui2/actions/add.py:35 +msgid "Topaz books" +msgstr "Topaz liburuak" + +#: /home/kovid/work/calibre/src/calibre/gui2/actions/add.py:36 +msgid "Text books" +msgstr "Text liburuak" + +#: /home/kovid/work/calibre/src/calibre/gui2/actions/add.py:37 +msgid "PDF Books" +msgstr "PDF liburuak" + +#: /home/kovid/work/calibre/src/calibre/gui2/actions/add.py:38 +msgid "SNB Books" +msgstr "SNB Liburuak" + +#: /home/kovid/work/calibre/src/calibre/gui2/actions/add.py:39 +msgid "Comics" +msgstr "Komikiak" + +#: /home/kovid/work/calibre/src/calibre/gui2/actions/add.py:40 +msgid "Archives" +msgstr "Artxiboak" + +#: /home/kovid/work/calibre/src/calibre/gui2/actions/add.py:47 msgid "Add books" msgstr "Gehitu liburuak" -#: /home/kovid/work/calibre/src/calibre/gui2/actions/add.py:30 +#: /home/kovid/work/calibre/src/calibre/gui2/actions/add.py:48 msgid "Add books to the calibre library/device from files on your computer" msgstr "" "Gehitu liburuak calibre liburutegira/irakurgailura zure ordenagailuko " "fitxategietatik" -#: /home/kovid/work/calibre/src/calibre/gui2/actions/add.py:31 +#: /home/kovid/work/calibre/src/calibre/gui2/actions/add.py:49 msgid "A" msgstr "G" -#: /home/kovid/work/calibre/src/calibre/gui2/actions/add.py:37 +#: /home/kovid/work/calibre/src/calibre/gui2/actions/add.py:55 msgid "Add books from a single directory" msgstr "Gehitu liburuak direktoriko bakar batetik" -#: /home/kovid/work/calibre/src/calibre/gui2/actions/add.py:39 +#: /home/kovid/work/calibre/src/calibre/gui2/actions/add.py:57 msgid "" "Add books from directories, including sub-directories (One book per " "directory, assumes every ebook file is the same book in a different format)" @@ -4339,7 +4450,7 @@ msgstr "" "direktorioko, ebook fitxategi guztiak liburu berdina direla formatu " "ezberdinetan suposatuz)" -#: /home/kovid/work/calibre/src/calibre/gui2/actions/add.py:43 +#: /home/kovid/work/calibre/src/calibre/gui2/actions/add.py:61 msgid "" "Add books from directories, including sub directories (Multiple books per " "directory, assumes every ebook file is a different book)" @@ -4347,125 +4458,104 @@ msgstr "" "Liburuak gehitu direktorioetatik, azpi-direktorioak barne (Liburu anitz " "direktorioko, ebook fitxategi bakoitza liburu ezbedina dela suposatuz)" -#: /home/kovid/work/calibre/src/calibre/gui2/actions/add.py:47 +#: /home/kovid/work/calibre/src/calibre/gui2/actions/add.py:65 msgid "Add Empty book. (Book entry with no formats)" msgstr "Liburu hutsa gehitu. (Liburu sarrera formaturik gabe)" -#: /home/kovid/work/calibre/src/calibre/gui2/actions/add.py:48 +#: /home/kovid/work/calibre/src/calibre/gui2/actions/add.py:66 msgid "Shift+Ctrl+E" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/actions/add.py:49 +#: /home/kovid/work/calibre/src/calibre/gui2/actions/add.py:67 msgid "Add from ISBN" msgstr "Gehitu hemendik: ISBN" -#: /home/kovid/work/calibre/src/calibre/gui2/actions/add.py:113 +#: /home/kovid/work/calibre/src/calibre/gui2/actions/add.py:69 +msgid "Add files to selected book records" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/actions/add.py:70 +msgid "Shift+A" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/actions/add.py:90 +msgid "Are you sure" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/actions/add.py:91 +msgid "" +"Are you sure you want to add the same files to all %d books? If the " +"formatalready exists for a book, it will be replaced." +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/actions/add.py:97 +msgid "Select book files" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/actions/add.py:168 msgid "Adding" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/actions/add.py:114 +#: /home/kovid/work/calibre/src/calibre/gui2/actions/add.py:169 msgid "Creating book records from ISBNs" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/actions/add.py:194 -#: /home/kovid/work/calibre/src/calibre/gui2/actions/add.py:256 +#: /home/kovid/work/calibre/src/calibre/gui2/actions/add.py:268 +#: /home/kovid/work/calibre/src/calibre/gui2/actions/add.py:317 msgid "Uploading books to device." msgstr "Kargatzen liburuak irakurgailuan." -#: /home/kovid/work/calibre/src/calibre/gui2/actions/add.py:211 -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single.py:308 -#: /home/kovid/work/calibre/src/calibre/gui2/metadata/basic_widgets.py:529 -msgid "Books" -msgstr "Liburuak" - -#: /home/kovid/work/calibre/src/calibre/gui2/actions/add.py:212 -msgid "EPUB Books" -msgstr "EPUB liburuak" - -#: /home/kovid/work/calibre/src/calibre/gui2/actions/add.py:213 -msgid "LRF Books" -msgstr "LRF liburuak" - -#: /home/kovid/work/calibre/src/calibre/gui2/actions/add.py:214 -msgid "HTML Books" -msgstr "HTML liburuak" - -#: /home/kovid/work/calibre/src/calibre/gui2/actions/add.py:215 -msgid "LIT Books" -msgstr "LIT liburuak" - -#: /home/kovid/work/calibre/src/calibre/gui2/actions/add.py:216 -msgid "MOBI Books" -msgstr "MOBI liburuak" - -#: /home/kovid/work/calibre/src/calibre/gui2/actions/add.py:217 -msgid "Topaz books" -msgstr "Topaz liburuak" - -#: /home/kovid/work/calibre/src/calibre/gui2/actions/add.py:218 -msgid "Text books" -msgstr "Text liburuak" - -#: /home/kovid/work/calibre/src/calibre/gui2/actions/add.py:219 -msgid "PDF Books" -msgstr "PDF liburuak" - -#: /home/kovid/work/calibre/src/calibre/gui2/actions/add.py:220 -msgid "SNB Books" -msgstr "SNB Liburuak" - -#: /home/kovid/work/calibre/src/calibre/gui2/actions/add.py:221 -msgid "Comics" -msgstr "Komikiak" - -#: /home/kovid/work/calibre/src/calibre/gui2/actions/add.py:222 -msgid "Archives" -msgstr "Artxiboak" - -#: /home/kovid/work/calibre/src/calibre/gui2/actions/add.py:227 +#: /home/kovid/work/calibre/src/calibre/gui2/actions/add.py:288 msgid "Supported books" msgstr "Onartzen diren liburuak (formatu hauei eusten zaie)" -#: /home/kovid/work/calibre/src/calibre/gui2/actions/add.py:266 +#: /home/kovid/work/calibre/src/calibre/gui2/actions/add.py:291 +msgid "Select books" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/actions/add.py:328 msgid "Merged some books" msgstr "Bateratu liburu batzuk" -#: /home/kovid/work/calibre/src/calibre/gui2/actions/add.py:267 +#: /home/kovid/work/calibre/src/calibre/gui2/actions/add.py:329 msgid "" "The following duplicate books were found and incoming book formats were " "processed and merged into your Calibre database according to your automerge " "settings:" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/actions/add.py:276 +#: /home/kovid/work/calibre/src/calibre/gui2/actions/add.py:349 msgid "Failed to read metadata" msgstr "Metadatuak irakurtzen huts egin du" -#: /home/kovid/work/calibre/src/calibre/gui2/actions/add.py:277 +#: /home/kovid/work/calibre/src/calibre/gui2/actions/add.py:350 msgid "Failed to read metadata from the following" msgstr "Huts egin du metadatuak hemendik irakurtzen" -#: /home/kovid/work/calibre/src/calibre/gui2/actions/add.py:298 -#: /home/kovid/work/calibre/src/calibre/gui2/actions/add.py:303 -#: /home/kovid/work/calibre/src/calibre/gui2/actions/add.py:322 +#: /home/kovid/work/calibre/src/calibre/gui2/actions/add.py:371 +#: /home/kovid/work/calibre/src/calibre/gui2/actions/add.py:376 +#: /home/kovid/work/calibre/src/calibre/gui2/actions/add.py:395 msgid "Add to library" msgstr "Gehitu liburutegira" -#: /home/kovid/work/calibre/src/calibre/gui2/actions/add.py:303 -#: /home/kovid/work/calibre/src/calibre/gui2/actions/delete.py:116 +#: /home/kovid/work/calibre/src/calibre/gui2/actions/add.py:376 +#: /home/kovid/work/calibre/src/calibre/gui2/actions/delete.py:127 +#: /home/kovid/work/calibre/src/calibre/gui2/actions/store.py:76 +#: /home/kovid/work/calibre/src/calibre/gui2/actions/store.py:95 +#: /home/kovid/work/calibre/src/calibre/gui2/actions/store.py:104 #: /home/kovid/work/calibre/src/calibre/gui2/actions/tweak_epub.py:28 -#: /home/kovid/work/calibre/src/calibre/gui2/actions/view.py:85 -#: /home/kovid/work/calibre/src/calibre/gui2/actions/view.py:131 +#: /home/kovid/work/calibre/src/calibre/gui2/actions/view.py:139 +#: /home/kovid/work/calibre/src/calibre/gui2/actions/view.py:185 msgid "No book selected" msgstr "Hautaturiko libururik ez dago" -#: /home/kovid/work/calibre/src/calibre/gui2/actions/add.py:316 +#: /home/kovid/work/calibre/src/calibre/gui2/actions/add.py:389 msgid "" "The following books are virtual and cannot be added to the calibre library:" msgstr "" "Hurrengo liburuak birtualak dira eta ezin dira calibre liburutegira gehitu:" -#: /home/kovid/work/calibre/src/calibre/gui2/actions/add.py:322 +#: /home/kovid/work/calibre/src/calibre/gui2/actions/add.py:395 msgid "No book files found" msgstr "Liburu fitxategirik ez da aurkitu" @@ -4478,57 +4568,65 @@ msgid "Add books to your calibre library from the connected device" msgstr "Gehitu liburuak zure calibre liburutegira konektatutako gailutik" #: /home/kovid/work/calibre/src/calibre/gui2/actions/annotate.py:20 -#: /home/kovid/work/calibre/src/calibre/gui2/device.py:544 +#: /home/kovid/work/calibre/src/calibre/gui2/device.py:543 msgid "Fetch annotations (experimental)" msgstr "Berreskura itzazu zure ohar eta zirriborroak (esperimentala)" #: /home/kovid/work/calibre/src/calibre/gui2/actions/annotate.py:56 -#: /home/kovid/work/calibre/src/calibre/gui2/actions/annotate.py:240 +msgid "Not supported" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/actions/annotate.py:57 +msgid "Fetching annotations is not supported for this device" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/actions/annotate.py:61 +#: /home/kovid/work/calibre/src/calibre/gui2/actions/annotate.py:245 msgid "Use library only" msgstr "Erabil ezazu bakarrik liburutegia" -#: /home/kovid/work/calibre/src/calibre/gui2/actions/annotate.py:57 -#: /home/kovid/work/calibre/src/calibre/gui2/actions/annotate.py:241 +#: /home/kovid/work/calibre/src/calibre/gui2/actions/annotate.py:62 +#: /home/kovid/work/calibre/src/calibre/gui2/actions/annotate.py:246 msgid "User annotations generated from main library only" msgstr "Liburutegi nagusian erabiltzaileak sortu dituen oharrak bakarrik" -#: /home/kovid/work/calibre/src/calibre/gui2/actions/annotate.py:64 -#: /home/kovid/work/calibre/src/calibre/gui2/actions/catalog.py:30 +#: /home/kovid/work/calibre/src/calibre/gui2/actions/annotate.py:69 +#: /home/kovid/work/calibre/src/calibre/gui2/actions/catalog.py:33 #: /home/kovid/work/calibre/src/calibre/gui2/actions/convert.py:87 #: /home/kovid/work/calibre/src/calibre/gui2/actions/copy_to_library.py:127 -#: /home/kovid/work/calibre/src/calibre/gui2/actions/edit_metadata.py:80 -#: /home/kovid/work/calibre/src/calibre/gui2/actions/edit_metadata.py:127 -#: /home/kovid/work/calibre/src/calibre/gui2/actions/edit_metadata.py:188 -#: /home/kovid/work/calibre/src/calibre/gui2/actions/edit_metadata.py:225 +#: /home/kovid/work/calibre/src/calibre/gui2/actions/edit_metadata.py:72 +#: /home/kovid/work/calibre/src/calibre/gui2/actions/edit_metadata.py:156 +#: /home/kovid/work/calibre/src/calibre/gui2/actions/edit_metadata.py:220 +#: /home/kovid/work/calibre/src/calibre/gui2/actions/edit_metadata.py:257 #: /home/kovid/work/calibre/src/calibre/gui2/actions/save_to_disk.py:92 msgid "No books selected" msgstr "Libururik ez da hautatu" -#: /home/kovid/work/calibre/src/calibre/gui2/actions/annotate.py:65 +#: /home/kovid/work/calibre/src/calibre/gui2/actions/annotate.py:70 msgid "No books selected to fetch annotations from" msgstr "Libururik ez da hautatu oharrak handik berreskuratzeko" -#: /home/kovid/work/calibre/src/calibre/gui2/actions/annotate.py:90 +#: /home/kovid/work/calibre/src/calibre/gui2/actions/annotate.py:95 msgid "Merging user annotations into database" msgstr "Erabiltzailearen oharrak datu basearekin bateratzen" -#: /home/kovid/work/calibre/src/calibre/gui2/actions/annotate.py:118 +#: /home/kovid/work/calibre/src/calibre/gui2/actions/annotate.py:123 msgid "%s
      Last Page Read: %d (%d%%)" msgstr "%s
      Irakurritako azken orrialdea: %d (%d%%)" -#: /home/kovid/work/calibre/src/calibre/gui2/actions/annotate.py:124 +#: /home/kovid/work/calibre/src/calibre/gui2/actions/annotate.py:129 msgid "%s
      Last Page Read: Location %d (%d%%)" msgstr "%s
      Irakurritako azken orrialdea non %d (%d%%)" -#: /home/kovid/work/calibre/src/calibre/gui2/actions/annotate.py:143 +#: /home/kovid/work/calibre/src/calibre/gui2/actions/annotate.py:148 msgid "Location %d • %s
      %s
      " msgstr "Non %d • %s
      %s
      " -#: /home/kovid/work/calibre/src/calibre/gui2/actions/annotate.py:152 +#: /home/kovid/work/calibre/src/calibre/gui2/actions/annotate.py:157 msgid "Page %d • %s
      " msgstr "Orrialdea %d • %s
      " -#: /home/kovid/work/calibre/src/calibre/gui2/actions/annotate.py:157 +#: /home/kovid/work/calibre/src/calibre/gui2/actions/annotate.py:162 msgid "Location %d • %s
      " msgstr "Non %d • %s
      " @@ -4537,30 +4635,30 @@ msgstr "Non %d • %s
      " msgid "Create a catalog of the books in your calibre library" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/actions/catalog.py:31 +#: /home/kovid/work/calibre/src/calibre/gui2/actions/catalog.py:34 msgid "No books selected for catalog generation" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/actions/catalog.py:54 +#: /home/kovid/work/calibre/src/calibre/gui2/actions/catalog.py:57 msgid "Generating %s catalog..." msgstr "Sortzen %s katalogoa..." -#: /home/kovid/work/calibre/src/calibre/gui2/actions/catalog.py:78 +#: /home/kovid/work/calibre/src/calibre/gui2/actions/catalog.py:81 msgid "Catalog generated." msgstr "Katalogoa sortu egin da." -#: /home/kovid/work/calibre/src/calibre/gui2/actions/catalog.py:81 +#: /home/kovid/work/calibre/src/calibre/gui2/actions/catalog.py:84 msgid "Export Catalog Directory" msgstr "Esportatu katalogoaren direktorioa" -#: /home/kovid/work/calibre/src/calibre/gui2/actions/catalog.py:82 +#: /home/kovid/work/calibre/src/calibre/gui2/actions/catalog.py:85 msgid "Select destination for %s.%s" msgstr "Aukeratu helburua honentzat: %s.%s" #: /home/kovid/work/calibre/src/calibre/gui2/actions/choose_library.py:81 -#: /home/kovid/work/calibre/src/calibre/gui2/preferences/toolbar.py:54 -#: /home/kovid/work/calibre/src/calibre/library/server/browse.py:167 -#: /home/kovid/work/calibre/src/calibre/library/server/opds.py:126 +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/toolbar.py:57 +#: /home/kovid/work/calibre/src/calibre/library/server/browse.py:170 +#: /home/kovid/work/calibre/src/calibre/library/server/opds.py:125 msgid "%d books" msgstr "%d books" @@ -4568,76 +4666,77 @@ msgstr "%d books" msgid "Choose calibre library to work with" msgstr "Choose calibre library to work with" -#: /home/kovid/work/calibre/src/calibre/gui2/actions/choose_library.py:91 +#: /home/kovid/work/calibre/src/calibre/gui2/actions/choose_library.py:94 msgid "Switch/create library..." msgstr "Aldatu/sortu liburutegia..." -#: /home/kovid/work/calibre/src/calibre/gui2/actions/choose_library.py:102 -#: /home/kovid/work/calibre/src/calibre/gui2/layout.py:77 +#: /home/kovid/work/calibre/src/calibre/gui2/actions/choose_library.py:105 +#: /home/kovid/work/calibre/src/calibre/gui2/layout.py:87 msgid "Quick switch" msgstr "Aldaketa azkarra" -#: /home/kovid/work/calibre/src/calibre/gui2/actions/choose_library.py:104 -#: /home/kovid/work/calibre/src/calibre/gui2/layout.py:78 +#: /home/kovid/work/calibre/src/calibre/gui2/actions/choose_library.py:107 +#: /home/kovid/work/calibre/src/calibre/gui2/layout.py:88 msgid "Rename library" msgstr "Liburutegiari izena aldatu" -#: /home/kovid/work/calibre/src/calibre/gui2/actions/choose_library.py:106 -#: /home/kovid/work/calibre/src/calibre/gui2/layout.py:79 +#: /home/kovid/work/calibre/src/calibre/gui2/actions/choose_library.py:109 +#: /home/kovid/work/calibre/src/calibre/gui2/layout.py:89 msgid "Delete library" msgstr "Ezabatu liburutegia" -#: /home/kovid/work/calibre/src/calibre/gui2/actions/choose_library.py:109 +#: /home/kovid/work/calibre/src/calibre/gui2/actions/choose_library.py:112 msgid "Pick a random book" msgstr "Hartu liburu bat ausaz" -#: /home/kovid/work/calibre/src/calibre/gui2/actions/choose_library.py:128 +#: /home/kovid/work/calibre/src/calibre/gui2/actions/choose_library.py:132 msgid "Library Maintenance" msgstr "Liburutetiaren mantentzea" -#: /home/kovid/work/calibre/src/calibre/gui2/actions/choose_library.py:129 +#: /home/kovid/work/calibre/src/calibre/gui2/actions/choose_library.py:133 msgid "Library metadata backup status" msgstr "Liburutegiaren metadatuen babes kopiaren egoera" -#: /home/kovid/work/calibre/src/calibre/gui2/actions/choose_library.py:133 +#: /home/kovid/work/calibre/src/calibre/gui2/actions/choose_library.py:137 msgid "Start backing up metadata of all books" msgstr "Hasi liburu guztien metadatuen babes kopia egiten" -#: /home/kovid/work/calibre/src/calibre/gui2/actions/choose_library.py:137 +#: /home/kovid/work/calibre/src/calibre/gui2/actions/choose_library.py:141 msgid "Check library" msgstr "Ikuskatu liburutegia" -#: /home/kovid/work/calibre/src/calibre/gui2/actions/choose_library.py:141 +#: /home/kovid/work/calibre/src/calibre/gui2/actions/choose_library.py:145 msgid "Restore database" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/actions/choose_library.py:216 +#: /home/kovid/work/calibre/src/calibre/gui2/actions/choose_library.py:220 msgid "Rename" msgstr "Aldatu izena" -#: /home/kovid/work/calibre/src/calibre/gui2/actions/choose_library.py:217 +#: /home/kovid/work/calibre/src/calibre/gui2/actions/choose_library.py:221 msgid "Choose a new name for the library %s. " msgstr "Aukeratu liburutegirako izen berria %s. " -#: /home/kovid/work/calibre/src/calibre/gui2/actions/choose_library.py:218 +#: /home/kovid/work/calibre/src/calibre/gui2/actions/choose_library.py:222 msgid "Note that the actual library folder will be renamed." msgstr "" "Kontuan izan oraingo liburutegi karpetari izena aldatu egingo zaiola." -#: /home/kovid/work/calibre/src/calibre/gui2/actions/choose_library.py:225 -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/user_profiles.py:191 +#: /home/kovid/work/calibre/src/calibre/gui2/actions/choose_library.py:229 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/user_profiles.py:199 +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/plugins.py:289 msgid "Already exists" msgstr "Hori badago dagoeneko" -#: /home/kovid/work/calibre/src/calibre/gui2/actions/choose_library.py:226 +#: /home/kovid/work/calibre/src/calibre/gui2/actions/choose_library.py:230 msgid "The folder %s already exists. Delete it first." msgstr "Dagoeneko badago %s izeneko karpeta. Ezaba ezazu lehen eta behin." -#: /home/kovid/work/calibre/src/calibre/gui2/actions/choose_library.py:232 +#: /home/kovid/work/calibre/src/calibre/gui2/actions/choose_library.py:236 msgid "Rename failed" msgstr "Huts egin du berrizendatzerakoan" -#: /home/kovid/work/calibre/src/calibre/gui2/actions/choose_library.py:233 +#: /home/kovid/work/calibre/src/calibre/gui2/actions/choose_library.py:237 msgid "" "Failed to rename the library at %s. The most common cause for this is if one " "of the files in the library is open in another program." @@ -4646,86 +4745,88 @@ msgstr "" "denean gehienetan izaten da liburutegiko fitxategi bat dagoeneko zabalik " "egoten delako beste programa baten menpe." -#: /home/kovid/work/calibre/src/calibre/gui2/actions/choose_library.py:244 +#: /home/kovid/work/calibre/src/calibre/gui2/actions/choose_library.py:248 +#: /home/kovid/work/calibre/src/calibre/gui2/actions/delete.py:30 #: /home/kovid/work/calibre/src/calibre/gui2/dialogs/confirm_delete_ui.py:53 #: /home/kovid/work/calibre/src/calibre/gui2/dialogs/restore_library.py:78 -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/user_profiles.py:360 -#: /home/kovid/work/calibre/src/calibre/gui2/jobs.py:424 -#: /home/kovid/work/calibre/src/calibre/gui2/jobs.py:430 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/user_profiles.py:368 +#: /home/kovid/work/calibre/src/calibre/gui2/jobs.py:457 +#: /home/kovid/work/calibre/src/calibre/gui2/jobs.py:463 #: /home/kovid/work/calibre/src/calibre/gui2/preferences/columns.py:102 -#: /home/kovid/work/calibre/src/calibre/gui2/preferences/plugins.py:273 +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/plugins.py:279 +#: /home/kovid/work/calibre/src/calibre/gui2/wizard/send_email.py:223 msgid "Are you sure?" msgstr "Ziur zaude?" -#: /home/kovid/work/calibre/src/calibre/gui2/actions/choose_library.py:245 -msgid "All files from %s will be permanently deleted. Are you sure?" +#: /home/kovid/work/calibre/src/calibre/gui2/actions/choose_library.py:249 +msgid "" +"All files (not just ebooks) from " +"

      %s

      will be permanently deleted. Are you sure?" msgstr "" -"%s horretako fitxategi guztiak betiko ezabatu egingo dira. Ziur zaude?" -#: /home/kovid/work/calibre/src/calibre/gui2/actions/choose_library.py:265 +#: /home/kovid/work/calibre/src/calibre/gui2/actions/choose_library.py:270 msgid "none" msgstr "ezer ere ez" -#: /home/kovid/work/calibre/src/calibre/gui2/actions/choose_library.py:266 +#: /home/kovid/work/calibre/src/calibre/gui2/actions/choose_library.py:271 msgid "Backup status" msgstr "Babes-kopiaren egoera" -#: /home/kovid/work/calibre/src/calibre/gui2/actions/choose_library.py:267 +#: /home/kovid/work/calibre/src/calibre/gui2/actions/choose_library.py:272 msgid "Book metadata files remaining to be written: %s" msgstr "Idazteko geratzen diren liburuen metadatu fitxategiak : %s" -#: /home/kovid/work/calibre/src/calibre/gui2/actions/choose_library.py:273 +#: /home/kovid/work/calibre/src/calibre/gui2/actions/choose_library.py:278 msgid "Backup metadata" msgstr "Metadatuen babes kopia" -#: /home/kovid/work/calibre/src/calibre/gui2/actions/choose_library.py:274 +#: /home/kovid/work/calibre/src/calibre/gui2/actions/choose_library.py:279 msgid "" "Metadata will be backed up while calibre is running, at the rate of " "approximately 1 book every three seconds." msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/actions/choose_library.py:306 +#: /home/kovid/work/calibre/src/calibre/gui2/actions/choose_library.py:311 #: /home/kovid/work/calibre/src/calibre/gui2/dialogs/restore_library.py:106 #: /home/kovid/work/calibre/src/calibre/gui2/dialogs/restore_library.py:111 -#: /home/kovid/work/calibre/src/calibre/gui2/preferences/plugins.py:284 -#: /home/kovid/work/calibre/src/calibre/gui2/preferences/plugins.py:338 +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/plugins.py:295 +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/plugins.py:349 msgid "Success" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/actions/choose_library.py:307 +#: /home/kovid/work/calibre/src/calibre/gui2/actions/choose_library.py:312 msgid "" "Found no errors in your calibre library database. Do you want calibre to " "check if the files in your library match the information in the database?" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/actions/choose_library.py:312 +#: /home/kovid/work/calibre/src/calibre/gui2/actions/choose_library.py:317 #: /home/kovid/work/calibre/src/calibre/gui2/actions/copy_to_library.py:150 -#: /home/kovid/work/calibre/src/calibre/gui2/device.py:672 -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk.py:911 +#: /home/kovid/work/calibre/src/calibre/gui2/device.py:692 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk.py:974 #: /home/kovid/work/calibre/src/calibre/gui2/dialogs/restore_library.py:101 -#: /home/kovid/work/calibre/src/calibre/gui2/metadata/bulk_download.py:186 -#: /home/kovid/work/calibre/src/calibre/gui2/preferences/tweaks.py:276 -#: /home/kovid/work/calibre/src/calibre/gui2/preferences/tweaks.py:316 +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/tweaks.py:277 +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/tweaks.py:317 msgid "Failed" msgstr "Huts egin du" -#: /home/kovid/work/calibre/src/calibre/gui2/actions/choose_library.py:313 +#: /home/kovid/work/calibre/src/calibre/gui2/actions/choose_library.py:318 msgid "Database integrity check failed, click Show details for details." msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/actions/choose_library.py:318 +#: /home/kovid/work/calibre/src/calibre/gui2/actions/choose_library.py:323 msgid "No problems found" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/actions/choose_library.py:319 +#: /home/kovid/work/calibre/src/calibre/gui2/actions/choose_library.py:324 msgid "The files in your library match the information in the database." msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/actions/choose_library.py:328 +#: /home/kovid/work/calibre/src/calibre/gui2/actions/choose_library.py:333 msgid "No library found" msgstr "Ez da liburutegirik aurkitu" -#: /home/kovid/work/calibre/src/calibre/gui2/actions/choose_library.py:329 +#: /home/kovid/work/calibre/src/calibre/gui2/actions/choose_library.py:334 msgid "" "No existing calibre library was found at %s. It will be removed from the " "list of known libraries." @@ -4733,15 +4834,15 @@ msgstr "" "Ez da %s horretan liburutegirik topatu. Ezagutzen diren liburutegien " "zerrendatik ezabatu egingo da." -#: /home/kovid/work/calibre/src/calibre/gui2/actions/choose_library.py:394 -#: /home/kovid/work/calibre/src/calibre/gui2/actions/choose_library.py:399 +#: /home/kovid/work/calibre/src/calibre/gui2/actions/choose_library.py:400 +#: /home/kovid/work/calibre/src/calibre/gui2/actions/choose_library.py:405 #: /home/kovid/work/calibre/src/calibre/gui2/actions/copy_to_library.py:167 #: /home/kovid/work/calibre/src/calibre/gui2/actions/save_to_disk.py:101 -#: /home/kovid/work/calibre/src/calibre/gui2/library/views.py:782 +#: /home/kovid/work/calibre/src/calibre/gui2/library/views.py:854 msgid "Not allowed" msgstr "Not allowed" -#: /home/kovid/work/calibre/src/calibre/gui2/actions/choose_library.py:395 +#: /home/kovid/work/calibre/src/calibre/gui2/actions/choose_library.py:401 msgid "" "You cannot change libraries while using the environment variable " "CALIBRE_OVERRIDE_DATABASE_PATH." @@ -4749,7 +4850,7 @@ msgstr "" "Ezin dituzu liburutegiak aldatu CALIBRE_OVERRIDE_DATABASE_PATH ingurugiro " "aldakorra erabiltzen ari zaren bitartean." -#: /home/kovid/work/calibre/src/calibre/gui2/actions/choose_library.py:400 +#: /home/kovid/work/calibre/src/calibre/gui2/actions/choose_library.py:406 msgid "You cannot change libraries while jobs are running." msgstr "" "Ezin dezakezu liburutegiak aldatu lanak exekutatzen dauden bitartean." @@ -4764,14 +4865,14 @@ msgstr "Bihurtu liburuak" #: /home/kovid/work/calibre/src/calibre/gui2/actions/convert.py:28 msgid "Convert individually" -msgstr "Convert individually" +msgstr "Bihurtu banaka" #: /home/kovid/work/calibre/src/calibre/gui2/actions/convert.py:30 msgid "Bulk convert" -msgstr "Bulk convert" +msgstr "Bihurtu multzoka" #: /home/kovid/work/calibre/src/calibre/gui2/actions/convert.py:86 -#: /home/kovid/work/calibre/src/calibre/gui2/ui.py:505 +#: /home/kovid/work/calibre/src/calibre/gui2/ui.py:560 msgid "Cannot convert" msgstr "Ezin da bihurtu" @@ -4779,7 +4880,7 @@ msgstr "Ezin da bihurtu" msgid "Starting conversion of %d book(s)" msgstr "%d liburu(ar)en bihurketa abiatzen" -#: /home/kovid/work/calibre/src/calibre/gui2/actions/convert.py:170 +#: /home/kovid/work/calibre/src/calibre/gui2/actions/convert.py:171 msgid "Empty output file, probably the conversion process crashed" msgstr "" "Helburu fitxategia hutsik. Ziur aski bihurketa prozesuak huts egin du" @@ -4829,103 +4930,110 @@ msgstr "" "Ezin dituzu beste liburutegi batzuk erabili CALIBRE_OVERRIDE_DATABASE_PATH " "ingurugiro aldakorra erabiltzen ari zaren bitartean." -#: /home/kovid/work/calibre/src/calibre/gui2/actions/delete.py:32 +#: /home/kovid/work/calibre/src/calibre/gui2/actions/delete.py:31 +msgid "" +"You are trying to delete %d books. Sending so many files to the Recycle Bin " +"can be slow. Should calibre skip the Recycle Bin? If you click Yes " +"the files will be permanently deleted." +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/actions/delete.py:42 msgid "Deleting..." msgstr "Ezabatzen..." -#: /home/kovid/work/calibre/src/calibre/gui2/actions/delete.py:54 +#: /home/kovid/work/calibre/src/calibre/gui2/actions/delete.py:65 msgid "Deleted" msgstr "Ezabaturik" -#: /home/kovid/work/calibre/src/calibre/gui2/actions/delete.py:66 +#: /home/kovid/work/calibre/src/calibre/gui2/actions/delete.py:77 msgid "Failed to delete" msgstr "Ezin izan da ezabatu" -#: /home/kovid/work/calibre/src/calibre/gui2/actions/delete.py:67 +#: /home/kovid/work/calibre/src/calibre/gui2/actions/delete.py:78 msgid "" "Failed to delete some books, click the Show Details button for details." msgstr "" "Ezin izan dira liburu batzuk ezabatu, gehiago jakiteko egin ezazu klik " "\"Zehaztasunak erakutsi\" botoian." -#: /home/kovid/work/calibre/src/calibre/gui2/actions/delete.py:73 +#: /home/kovid/work/calibre/src/calibre/gui2/actions/delete.py:84 msgid "Del" msgstr "Ezabatu" -#: /home/kovid/work/calibre/src/calibre/gui2/actions/delete.py:73 +#: /home/kovid/work/calibre/src/calibre/gui2/actions/delete.py:84 msgid "Remove books" msgstr "Ezabatu liburuak" -#: /home/kovid/work/calibre/src/calibre/gui2/actions/delete.py:79 +#: /home/kovid/work/calibre/src/calibre/gui2/actions/delete.py:90 msgid "Remove selected books" -msgstr "Remove selected books" +msgstr "Ezabatu hautatutako liburuak" -#: /home/kovid/work/calibre/src/calibre/gui2/actions/delete.py:81 +#: /home/kovid/work/calibre/src/calibre/gui2/actions/delete.py:92 msgid "Remove files of a specific format from selected books.." msgstr "Remove files of a specific format from selected books.." -#: /home/kovid/work/calibre/src/calibre/gui2/actions/delete.py:84 +#: /home/kovid/work/calibre/src/calibre/gui2/actions/delete.py:95 msgid "Remove all formats from selected books, except..." msgstr "Remove all formats from selected books, except..." -#: /home/kovid/work/calibre/src/calibre/gui2/actions/delete.py:87 +#: /home/kovid/work/calibre/src/calibre/gui2/actions/delete.py:98 msgid "Remove covers from selected books" -msgstr "Remove covers from selected books" +msgstr "Ezabatu hautatutako liburuetako azalak" -#: /home/kovid/work/calibre/src/calibre/gui2/actions/delete.py:90 +#: /home/kovid/work/calibre/src/calibre/gui2/actions/delete.py:101 msgid "Remove matching books from device" msgstr "Remove matching books from device" -#: /home/kovid/work/calibre/src/calibre/gui2/actions/delete.py:113 +#: /home/kovid/work/calibre/src/calibre/gui2/actions/delete.py:124 msgid "Cannot delete" msgstr "Ezin ezabatu" -#: /home/kovid/work/calibre/src/calibre/gui2/actions/delete.py:126 +#: /home/kovid/work/calibre/src/calibre/gui2/actions/delete.py:137 msgid "Choose formats to be deleted" msgstr "Aukeratu ezabatzeko formatuak" -#: /home/kovid/work/calibre/src/calibre/gui2/actions/delete.py:144 +#: /home/kovid/work/calibre/src/calibre/gui2/actions/delete.py:155 msgid "Choose formats not to be deleted" msgstr "Aukeratu formatuak ez ezabatzeko" -#: /home/kovid/work/calibre/src/calibre/gui2/actions/delete.py:164 +#: /home/kovid/work/calibre/src/calibre/gui2/actions/delete.py:175 msgid "Cannot delete books" msgstr "Ezin liburuak ezabatu" -#: /home/kovid/work/calibre/src/calibre/gui2/actions/delete.py:165 +#: /home/kovid/work/calibre/src/calibre/gui2/actions/delete.py:176 msgid "No device is connected" msgstr "Ez dago konektaturik inolako irakurgailurik" -#: /home/kovid/work/calibre/src/calibre/gui2/actions/delete.py:175 +#: /home/kovid/work/calibre/src/calibre/gui2/actions/delete.py:186 msgid "Main memory" msgstr "Memoria nagusia" -#: /home/kovid/work/calibre/src/calibre/gui2/actions/delete.py:176 -#: /home/kovid/work/calibre/src/calibre/gui2/device.py:469 -#: /home/kovid/work/calibre/src/calibre/gui2/device.py:478 +#: /home/kovid/work/calibre/src/calibre/gui2/actions/delete.py:187 +#: /home/kovid/work/calibre/src/calibre/gui2/device.py:468 +#: /home/kovid/work/calibre/src/calibre/gui2/device.py:477 msgid "Storage Card A" msgstr "Memoria-txartela A" -#: /home/kovid/work/calibre/src/calibre/gui2/actions/delete.py:177 -#: /home/kovid/work/calibre/src/calibre/gui2/device.py:471 -#: /home/kovid/work/calibre/src/calibre/gui2/device.py:480 +#: /home/kovid/work/calibre/src/calibre/gui2/actions/delete.py:188 +#: /home/kovid/work/calibre/src/calibre/gui2/device.py:470 +#: /home/kovid/work/calibre/src/calibre/gui2/device.py:479 msgid "Storage Card B" msgstr "Memoria-txartela B" -#: /home/kovid/work/calibre/src/calibre/gui2/actions/delete.py:182 +#: /home/kovid/work/calibre/src/calibre/gui2/actions/delete.py:193 msgid "No books to delete" msgstr "Ez dago ezabatzeko libururik" -#: /home/kovid/work/calibre/src/calibre/gui2/actions/delete.py:183 +#: /home/kovid/work/calibre/src/calibre/gui2/actions/delete.py:194 msgid "None of the selected books are on the device" msgstr "Hautatutako liburuak ez daude irakurgailuan, ezta bakar bat ere" -#: /home/kovid/work/calibre/src/calibre/gui2/actions/delete.py:200 -#: /home/kovid/work/calibre/src/calibre/gui2/actions/delete.py:290 +#: /home/kovid/work/calibre/src/calibre/gui2/actions/delete.py:211 +#: /home/kovid/work/calibre/src/calibre/gui2/actions/delete.py:302 msgid "Deleting books from device." msgstr "Ezabatzen liburuak irakurgailutik." -#: /home/kovid/work/calibre/src/calibre/gui2/actions/delete.py:245 +#: /home/kovid/work/calibre/src/calibre/gui2/actions/delete.py:257 msgid "" "Some of the selected books are on the attached device. Where do you " "want the selected files deleted from?" @@ -4933,7 +5041,7 @@ msgstr "" "Hautatutako liburuetako batzuk erantsitako gailuan daude. Nondik nahi " "duzu ezabatu hautatutako fitxategiak?" -#: /home/kovid/work/calibre/src/calibre/gui2/actions/delete.py:257 +#: /home/kovid/work/calibre/src/calibre/gui2/actions/delete.py:269 msgid "" "The selected books will be permanently deleted and the files removed " "from your calibre library. Are you sure?" @@ -4941,7 +5049,7 @@ msgstr "" "Aukeratutako liburuak betiko ezabatu egingo dira zure calibre " "liburutegitik eta fitxategiak ezabatu egingo dira betiko. Ziur zaude?" -#: /home/kovid/work/calibre/src/calibre/gui2/actions/delete.py:282 +#: /home/kovid/work/calibre/src/calibre/gui2/actions/delete.py:294 msgid "" "The selected books will be permanently deleted from your device. Are " "you sure?" @@ -4951,15 +5059,15 @@ msgstr "" #: /home/kovid/work/calibre/src/calibre/gui2/actions/device.py:31 msgid "Connect to folder" -msgstr "Connect to folder" +msgstr "Konektatu karpetara" #: /home/kovid/work/calibre/src/calibre/gui2/actions/device.py:36 msgid "Connect to iTunes" -msgstr "Connect to iTunes" +msgstr "Konektatu iTunes-era" #: /home/kovid/work/calibre/src/calibre/gui2/actions/device.py:42 msgid "Connect to Bambook" -msgstr "Konektatu Bambook horrekin" +msgstr "Konektatu Bambook-era" #: /home/kovid/work/calibre/src/calibre/gui2/actions/device.py:56 #: /home/kovid/work/calibre/src/calibre/gui2/actions/device.py:64 @@ -4971,7 +5079,7 @@ msgid "Stop Content Server" msgstr "Stop Content Server" #: /home/kovid/work/calibre/src/calibre/gui2/actions/device.py:77 -#: /home/kovid/work/calibre/src/calibre/gui2/actions/device.py:95 +#: /home/kovid/work/calibre/src/calibre/gui2/actions/device.py:96 msgid "Email to" msgstr "Email to" @@ -4979,19 +5087,19 @@ msgstr "Email to" msgid "Email to and delete from library" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/actions/device.py:89 +#: /home/kovid/work/calibre/src/calibre/gui2/actions/device.py:90 msgid "(delete from library)" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/actions/device.py:104 +#: /home/kovid/work/calibre/src/calibre/gui2/actions/device.py:105 msgid "Setup email based sharing of books" msgstr "Prestatu e-postaan oinarritutako liburuen partekatzea" -#: /home/kovid/work/calibre/src/calibre/gui2/actions/device.py:122 +#: /home/kovid/work/calibre/src/calibre/gui2/actions/device.py:123 msgid "D" msgstr "D" -#: /home/kovid/work/calibre/src/calibre/gui2/actions/device.py:122 +#: /home/kovid/work/calibre/src/calibre/gui2/actions/device.py:123 msgid "Send to device" msgstr "Bidali irakurgailura" @@ -4999,13 +5107,13 @@ msgstr "Bidali irakurgailura" msgid "Connect/share" msgstr "Konektatu/Konpartitu" -#: /home/kovid/work/calibre/src/calibre/gui2/actions/device.py:174 -#: /home/kovid/work/calibre/src/calibre/gui2/preferences/server.py:84 +#: /home/kovid/work/calibre/src/calibre/gui2/actions/device.py:178 +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/server.py:79 msgid "Stopping" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/actions/device.py:175 -#: /home/kovid/work/calibre/src/calibre/gui2/preferences/server.py:85 +#: /home/kovid/work/calibre/src/calibre/gui2/actions/device.py:179 +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/server.py:80 msgid "Stopping server, this could take upto a minute, please wait..." msgstr "" @@ -5039,73 +5147,96 @@ msgstr "Editatu metadatuak banan-banan" #: /home/kovid/work/calibre/src/calibre/gui2/actions/edit_metadata.py:34 msgid "Edit metadata in bulk" -msgstr "Edit metadatuak multzoka" +msgstr "Editatu metadatuak multzoka" #: /home/kovid/work/calibre/src/calibre/gui2/actions/edit_metadata.py:37 msgid "Download metadata and covers" msgstr "Deskargatu metadatuak eta liburu azalak" -#: /home/kovid/work/calibre/src/calibre/gui2/actions/edit_metadata.py:40 -msgid "Download only metadata" -msgstr "Deskargatu metadatuak besterik ez" - #: /home/kovid/work/calibre/src/calibre/gui2/actions/edit_metadata.py:42 -msgid "Download only covers" -msgstr "Deskargatu liburu-azalak besterik ez" - -#: /home/kovid/work/calibre/src/calibre/gui2/actions/edit_metadata.py:45 -msgid "Download only social metadata" -msgstr "Deskargatu gizarte mailako metadatuak besterik ez" - -#: /home/kovid/work/calibre/src/calibre/gui2/actions/edit_metadata.py:51 msgid "Merge into first selected book - delete others" msgstr "Bateratu hautatutako lehen liburuan - ezabatu besteak" -#: /home/kovid/work/calibre/src/calibre/gui2/actions/edit_metadata.py:54 +#: /home/kovid/work/calibre/src/calibre/gui2/actions/edit_metadata.py:45 msgid "Merge into first selected book - keep others" msgstr "Bateratu hautatutako lehen liburuan - gorde besteak" -#: /home/kovid/work/calibre/src/calibre/gui2/actions/edit_metadata.py:58 +#: /home/kovid/work/calibre/src/calibre/gui2/actions/edit_metadata.py:49 msgid "Merge only formats into first selected book - delete others" msgstr "" "Bateratu formatuak hautatutako lehen liburuak besterik ez - ezabatu besteak" -#: /home/kovid/work/calibre/src/calibre/gui2/actions/edit_metadata.py:79 +#: /home/kovid/work/calibre/src/calibre/gui2/actions/edit_metadata.py:71 msgid "Cannot download metadata" msgstr "Ezin izan dira metadatuak deskargatu" -#: /home/kovid/work/calibre/src/calibre/gui2/actions/edit_metadata.py:99 -msgid "social metadata" -msgstr "gizarte mailako metadatuak" +#: /home/kovid/work/calibre/src/calibre/gui2/actions/edit_metadata.py:81 +msgid "Failed to download metadata" +msgstr "Kale egin du metadatuak deskargatzen" -#: /home/kovid/work/calibre/src/calibre/gui2/actions/edit_metadata.py:101 -msgid "covers" -msgstr "liburu-azalak" - -#: /home/kovid/work/calibre/src/calibre/gui2/actions/edit_metadata.py:101 -#: /home/kovid/work/calibre/src/calibre/gui2/metadata/bulk_download.py:227 -msgid "metadata" -msgstr "metadatuak" - -#: /home/kovid/work/calibre/src/calibre/gui2/actions/edit_metadata.py:102 -msgid "Downloading {0} for {1} book(s)" +#: /home/kovid/work/calibre/src/calibre/gui2/actions/edit_metadata.py:87 +#: /home/kovid/work/calibre/src/calibre/gui2/dnd.py:84 +#: /home/kovid/work/calibre/src/calibre/gui2/metadata/single_download.py:462 +#: /home/kovid/work/calibre/src/calibre/gui2/metadata/single_download.py:716 +msgid "Download failed" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/actions/edit_metadata.py:126 -#: /home/kovid/work/calibre/src/calibre/gui2/actions/edit_metadata.py:187 +#: /home/kovid/work/calibre/src/calibre/gui2/actions/edit_metadata.py:88 +msgid "Failed to download metadata or covers for any of the %d book(s)." +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/actions/edit_metadata.py:91 +msgid "Metadata download completed" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/actions/edit_metadata.py:93 +msgid "" +"Finished downloading metadata for %d book(s). Proceed with updating " +"the metadata in your library?" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/actions/edit_metadata.py:100 +msgid "" +"Could not download metadata and/or covers for %d of the books. Click \"Show " +"details\" to see which books." +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/actions/edit_metadata.py:107 +msgid "Download complete" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/actions/edit_metadata.py:107 +#: /home/kovid/work/calibre/src/calibre/gui2/metadata/single_download.py:777 +msgid "Download log" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/actions/edit_metadata.py:136 +msgid "Some books changed" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/actions/edit_metadata.py:137 +msgid "" +"The metadata for some books in your library has changed since you started " +"the download. If you proceed, some of those changes may be overwritten. " +"Click \"Show details\" to see the list of changed books. Do you want to " +"proceed?" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/actions/edit_metadata.py:155 +#: /home/kovid/work/calibre/src/calibre/gui2/actions/edit_metadata.py:219 msgid "Cannot edit metadata" msgstr "Ezin izan dira metadatuak editatu" -#: /home/kovid/work/calibre/src/calibre/gui2/actions/edit_metadata.py:224 -#: /home/kovid/work/calibre/src/calibre/gui2/actions/edit_metadata.py:227 +#: /home/kovid/work/calibre/src/calibre/gui2/actions/edit_metadata.py:256 +#: /home/kovid/work/calibre/src/calibre/gui2/actions/edit_metadata.py:259 msgid "Cannot merge books" msgstr "Ezin izan dira liburuak bateratu" -#: /home/kovid/work/calibre/src/calibre/gui2/actions/edit_metadata.py:228 +#: /home/kovid/work/calibre/src/calibre/gui2/actions/edit_metadata.py:260 msgid "At least two books must be selected for merging" msgstr "Gutxienez bi liburu hautatu beharko dira haiekin bakarra egiteko" -#: /home/kovid/work/calibre/src/calibre/gui2/actions/edit_metadata.py:231 +#: /home/kovid/work/calibre/src/calibre/gui2/actions/edit_metadata.py:263 msgid "" "You are about to merge more than 5 books. Are you sure you want to " "proceed?" @@ -5113,7 +5244,7 @@ msgstr "" "Bost liburu baino gehiagorekin liburu bakarra egiteko prest zaude. Benetan " "ziur zaude? Aurrera egingo?" -#: /home/kovid/work/calibre/src/calibre/gui2/actions/edit_metadata.py:239 +#: /home/kovid/work/calibre/src/calibre/gui2/actions/edit_metadata.py:271 msgid "" "Book formats and metadata from the selected books will be added to the " "first selected book (%s). ISBN will not be merged.

      The " @@ -5126,7 +5257,7 @@ msgstr "" "liburuak ez dira ezabatuko ezta aldatuko ere.

      Mesedez, baieztatu " "aurrera egin nahi duzula." -#: /home/kovid/work/calibre/src/calibre/gui2/actions/edit_metadata.py:251 +#: /home/kovid/work/calibre/src/calibre/gui2/actions/edit_metadata.py:283 msgid "" "Book formats from the selected books will be merged into the first " "selected book (%s). Metadata in the first selected book will not be " @@ -5147,7 +5278,7 @@ msgstr "" "dira zure calibre liburutegitik.

      Ziur zaude? Benetan aurrera " "egin nahi duzu?" -#: /home/kovid/work/calibre/src/calibre/gui2/actions/edit_metadata.py:267 +#: /home/kovid/work/calibre/src/calibre/gui2/actions/edit_metadata.py:299 msgid "" "Book formats and metadata from the selected books will be merged into the " "first selected book (%s). ISBN will not be " @@ -5165,19 +5296,33 @@ msgstr "" "bikoiztutako formatu guztiak ezabatu egingo dira betiko zure calibre " "liburutegitik.

      Ziur zaude? Benetan aurrera egin nahi duzu?" -#: /home/kovid/work/calibre/src/calibre/gui2/actions/fetch_news.py:17 +#: /home/kovid/work/calibre/src/calibre/gui2/actions/edit_metadata.py:455 +msgid "Applying changed metadata" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/actions/edit_metadata.py:520 +msgid "Some failures" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/actions/edit_metadata.py:521 +msgid "" +"Failed to apply updated metadata for some books in your library. Click " +"\"Show Details\" to see details." +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/actions/fetch_news.py:19 msgid "F" msgstr "Es" -#: /home/kovid/work/calibre/src/calibre/gui2/actions/fetch_news.py:17 +#: /home/kovid/work/calibre/src/calibre/gui2/actions/fetch_news.py:19 msgid "Fetch news" msgstr "Eskuratu berriak" -#: /home/kovid/work/calibre/src/calibre/gui2/actions/fetch_news.py:52 +#: /home/kovid/work/calibre/src/calibre/gui2/actions/fetch_news.py:54 msgid "Fetching news from " msgstr "Albisteak eskuratzen hemendik: " -#: /home/kovid/work/calibre/src/calibre/gui2/actions/fetch_news.py:66 +#: /home/kovid/work/calibre/src/calibre/gui2/actions/fetch_news.py:83 msgid " fetched." msgstr " eskuratuta." @@ -5203,7 +5348,7 @@ msgid "Move to next highlighted match" msgstr "" #: /home/kovid/work/calibre/src/calibre/gui2/actions/next_match.py:13 -#: /home/kovid/work/calibre/src/calibre/gui2/library/delegates.py:355 +#: /home/kovid/work/calibre/src/calibre/gui2/library/delegates.py:361 msgid "N" msgstr "E" @@ -5242,19 +5387,23 @@ msgid "Ctrl+P" msgstr "Ctrl+P" #: /home/kovid/work/calibre/src/calibre/gui2/actions/preferences.py:24 -#: /home/kovid/work/calibre/src/calibre/gui2/preferences/main.py:201 +msgid "Change calibre behavior" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/actions/preferences.py:25 +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/main.py:208 msgid "Run welcome wizard" msgstr "Run welcome wizard" -#: /home/kovid/work/calibre/src/calibre/gui2/actions/preferences.py:28 +#: /home/kovid/work/calibre/src/calibre/gui2/actions/preferences.py:29 msgid "Restart in debug mode" msgstr "Berrabiarazi araztaile moduan (debug mode)" -#: /home/kovid/work/calibre/src/calibre/gui2/actions/preferences.py:40 +#: /home/kovid/work/calibre/src/calibre/gui2/actions/preferences.py:44 msgid "Cannot configure while there are running jobs." msgstr "Cannot configure while there are running jobs." -#: /home/kovid/work/calibre/src/calibre/gui2/actions/preferences.py:45 +#: /home/kovid/work/calibre/src/calibre/gui2/actions/preferences.py:49 msgid "Cannot configure before calibre is restarted." msgstr "Cannot configure before calibre is restarted." @@ -5331,9 +5480,9 @@ msgstr "" "Egin klik zehaztasunak erakutsi botoian ea zeintzuk izan diren ikusteko." #: /home/kovid/work/calibre/src/calibre/gui2/actions/show_book_details.py:16 -#: /home/kovid/work/calibre/src/calibre/library/server/browse.py:696 +#: /home/kovid/work/calibre/src/calibre/library/server/browse.py:766 msgid "Show book details" -msgstr "Show book details" +msgstr "Erakutsi liburuaren xehetasunak" #: /home/kovid/work/calibre/src/calibre/gui2/actions/show_book_details.py:17 msgid "I" @@ -5349,7 +5498,7 @@ msgstr "No detailed information is available for books on the device." #: /home/kovid/work/calibre/src/calibre/gui2/actions/similar_books.py:17 msgid "Similar books..." -msgstr "Similar books..." +msgstr "Antzeko liburuak..." #: /home/kovid/work/calibre/src/calibre/gui2/actions/similar_books.py:24 msgid "Alt+A" @@ -5383,8 +5532,71 @@ msgstr "Alt+T" msgid "Books with the same tags" msgstr "Books with the same tags" +#: /home/kovid/work/calibre/src/calibre/gui2/actions/store.py:20 +msgid "Get books" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/actions/store.py:29 +msgid "Search for ebooks" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/actions/store.py:30 +msgid "Search for this author" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/actions/store.py:31 +msgid "Search for this title" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/actions/store.py:32 +msgid "Search for this book" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/actions/store.py:34 +#: /home/kovid/work/calibre/src/calibre/gui2/store/search/search_ui.py:122 +#: /home/kovid/work/calibre/src/calibre/gui2/store/search_ui.py:110 +msgid "Stores" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/actions/store.py:76 +#: /home/kovid/work/calibre/src/calibre/gui2/actions/store.py:95 +#: /home/kovid/work/calibre/src/calibre/gui2/actions/store.py:104 +msgid "Cannot search" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/actions/store.py:116 +msgid "" +"Calibre helps you find the ebooks you want by searching the websites of " +"various commercial and public domain book sources for you." +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/actions/store.py:120 +msgid "" +"Using the integrated search you can easily find which store has the book you " +"are looking for, at the best price. You also get DRM status and other useful " +"information." +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/actions/store.py:124 +msgid "" +"All transactions (paid or otherwise) are handled between you and the book " +"seller. Calibre is not part of this process and any issues related to a " +"purchase should be directed to the website you are buying from. Be sure to " +"double check that any books you get will work with your e-book reader, " +"especially if the book you are buying has DRM." +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/actions/store.py:134 +msgid "Show this message again" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/actions/store.py:135 +msgid "About Get Books" +msgstr "" + #: /home/kovid/work/calibre/src/calibre/gui2/actions/tweak_epub.py:15 -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/tweak_epub_ui.py:54 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/tweak_epub_ui.py:60 msgid "Tweak ePub" msgstr "ePub berrukitu" @@ -5407,39 +5619,47 @@ msgstr "" "Ez dago ePub eskuragarririk. Lehen eta behin, bihur ezazu liburua ePub " "formatura." -#: /home/kovid/work/calibre/src/calibre/gui2/actions/view.py:24 +#: /home/kovid/work/calibre/src/calibre/gui2/actions/view.py:36 msgid "V" msgstr "I" -#: /home/kovid/work/calibre/src/calibre/gui2/actions/view.py:24 -#: /home/kovid/work/calibre/src/calibre/gui2/actions/view.py:31 +#: /home/kovid/work/calibre/src/calibre/gui2/actions/view.py:36 msgid "View" msgstr "Ikusi" -#: /home/kovid/work/calibre/src/calibre/gui2/actions/view.py:32 +#: /home/kovid/work/calibre/src/calibre/gui2/actions/view.py:43 msgid "View specific format" msgstr "View specific format" -#: /home/kovid/work/calibre/src/calibre/gui2/actions/view.py:85 -#: /home/kovid/work/calibre/src/calibre/gui2/actions/view.py:170 +#: /home/kovid/work/calibre/src/calibre/gui2/actions/view.py:51 +msgid "Read a random book" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/actions/view.py:55 +msgid "Clear recently viewed list" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/actions/view.py:139 +#: /home/kovid/work/calibre/src/calibre/gui2/actions/view.py:219 +#: /home/kovid/work/calibre/src/calibre/gui2/actions/view.py:226 msgid "Cannot view" msgstr "Ezin da ikusi" -#: /home/kovid/work/calibre/src/calibre/gui2/actions/view.py:98 -#: /home/kovid/work/calibre/src/calibre/gui2/actions/view.py:112 +#: /home/kovid/work/calibre/src/calibre/gui2/actions/view.py:152 +#: /home/kovid/work/calibre/src/calibre/gui2/actions/view.py:166 msgid "Format unavailable" msgstr "Formatu hori ez dago eskuragarri" -#: /home/kovid/work/calibre/src/calibre/gui2/actions/view.py:99 +#: /home/kovid/work/calibre/src/calibre/gui2/actions/view.py:153 msgid "Selected books have no formats" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/actions/view.py:101 +#: /home/kovid/work/calibre/src/calibre/gui2/actions/view.py:155 #: /home/kovid/work/calibre/src/calibre/gui2/convert/regex_builder.py:127 msgid "Choose the format to view" msgstr "Aukeratu ikusteko formatua" -#: /home/kovid/work/calibre/src/calibre/gui2/actions/view.py:113 +#: /home/kovid/work/calibre/src/calibre/gui2/actions/view.py:167 msgid "" "Not all the selected books were available in the %s format. You should " "convert them first." @@ -5447,11 +5667,11 @@ msgstr "" "Hautatutako liburu guztiak ez zeuden eskuragarri %s formatuan. Lehenengo " "formatuz aldatu, bihurtu egin beharko dituzu horietako batzuk." -#: /home/kovid/work/calibre/src/calibre/gui2/actions/view.py:120 +#: /home/kovid/work/calibre/src/calibre/gui2/actions/view.py:174 msgid "Multiple Books Selected" msgstr "Liburu anitz hautatuak" -#: /home/kovid/work/calibre/src/calibre/gui2/actions/view.py:121 +#: /home/kovid/work/calibre/src/calibre/gui2/actions/view.py:175 msgid "" "You are attempting to open %d books. Opening too many books at once can be " "slow and have a negative effect on the responsiveness of your computer. Once " @@ -5463,11 +5683,15 @@ msgstr "" "denboran, oro har, eragin txarra izan dezake. Prozesua behin hasiz gero, " "ezin da eten burutu arte. Aurrera egin nahi duzu?" -#: /home/kovid/work/calibre/src/calibre/gui2/actions/view.py:130 +#: /home/kovid/work/calibre/src/calibre/gui2/actions/view.py:184 msgid "Cannot open folder" msgstr "Ezin da karpeta zabaldu" -#: /home/kovid/work/calibre/src/calibre/gui2/actions/view.py:171 +#: /home/kovid/work/calibre/src/calibre/gui2/actions/view.py:220 +msgid "This book no longer exists in your library" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/actions/view.py:227 msgid "%s has no available formats." msgstr "%s-k ez du formatu eskuragarririk." @@ -5492,7 +5716,7 @@ msgid "The specified directory could not be processed." msgstr "Zehaztutako direktorioa ezin izan da prozesatu." #: /home/kovid/work/calibre/src/calibre/gui2/add.py:274 -#: /home/kovid/work/calibre/src/calibre/gui2/device.py:821 +#: /home/kovid/work/calibre/src/calibre/gui2/device.py:845 msgid "No books" msgstr "Libururik ez" @@ -5541,33 +5765,33 @@ msgstr "Gordetzen..." msgid "Saved" msgstr "Gordeta" -#: /home/kovid/work/calibre/src/calibre/gui2/add_wizard/__init__.py:57 +#: /home/kovid/work/calibre/src/calibre/gui2/add_wizard/__init__.py:56 msgid "Searching for sub-folders" msgstr "Azpi-karpeten bila, arakatzen" -#: /home/kovid/work/calibre/src/calibre/gui2/add_wizard/__init__.py:62 +#: /home/kovid/work/calibre/src/calibre/gui2/add_wizard/__init__.py:61 msgid "Searching for books" msgstr "Liburuen bila, arakatzen" -#: /home/kovid/work/calibre/src/calibre/gui2/add_wizard/__init__.py:74 +#: /home/kovid/work/calibre/src/calibre/gui2/add_wizard/__init__.py:73 msgid "Looking for duplicates based on file hash" msgstr "" "\"Hash-fitxategi-bilaketan\" oinarrituta, bikoiztutakoen bila, arakatzen" -#: /home/kovid/work/calibre/src/calibre/gui2/add_wizard/__init__.py:109 +#: /home/kovid/work/calibre/src/calibre/gui2/add_wizard/__init__.py:108 #: /home/kovid/work/calibre/src/calibre/gui2/add_wizard/welcome_ui.py:70 msgid "Choose root folder" msgstr "Aukeratu \"root karpeta\", erroko karpeta" -#: /home/kovid/work/calibre/src/calibre/gui2/add_wizard/__init__.py:137 +#: /home/kovid/work/calibre/src/calibre/gui2/add_wizard/__init__.py:135 msgid "Invalid root folder" msgstr "Erroko karpeta horrek, \"root karpeta\" horrek, ez du balio" -#: /home/kovid/work/calibre/src/calibre/gui2/add_wizard/__init__.py:138 +#: /home/kovid/work/calibre/src/calibre/gui2/add_wizard/__init__.py:136 msgid "is not a valid root folder" msgstr "balio ez duen \"root\" erroko karpeta" -#: /home/kovid/work/calibre/src/calibre/gui2/add_wizard/__init__.py:148 +#: /home/kovid/work/calibre/src/calibre/gui2/add_wizard/__init__.py:146 msgid "Add books to calibre" msgstr "Gehitu liburuak calibre aplikaziora" @@ -5634,21 +5858,15 @@ msgstr "" #: /home/kovid/work/calibre/src/calibre/gui2/convert/debug_ui.py:58 #: /home/kovid/work/calibre/src/calibre/gui2/convert/look_and_feel_ui.py:143 #: /home/kovid/work/calibre/src/calibre/gui2/convert/metadata_ui.py:162 -#: /home/kovid/work/calibre/src/calibre/gui2/convert/xexp_edit_ui.py:56 +#: /home/kovid/work/calibre/src/calibre/gui2/convert/xexp_edit_ui.py:57 #: /home/kovid/work/calibre/src/calibre/gui2/device_drivers/configwidget_ui.py:79 #: /home/kovid/work/calibre/src/calibre/gui2/device_drivers/configwidget_ui.py:80 #: /home/kovid/work/calibre/src/calibre/gui2/dialogs/choose_library_ui.py:86 -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:534 -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:539 -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single_ui.py:412 -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single_ui.py:414 -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single_ui.py:417 -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single_ui.py:437 -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single_ui.py:458 -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single_ui.py:460 -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single_ui.py:462 -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/saved_search_editor_ui.py:92 -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/saved_search_editor_ui.py:95 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:560 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:565 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/saved_search_editor_ui.py:98 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/saved_search_editor_ui.py:101 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/saved_search_editor_ui.py:103 #: /home/kovid/work/calibre/src/calibre/gui2/dialogs/tag_categories_ui.py:170 #: /home/kovid/work/calibre/src/calibre/gui2/dialogs/tag_categories_ui.py:173 #: /home/kovid/work/calibre/src/calibre/gui2/dialogs/tag_categories_ui.py:175 @@ -5660,16 +5878,16 @@ 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:80 #: /home/kovid/work/calibre/src/calibre/gui2/dialogs/tag_list_editor_ui.py:82 -#: /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:274 -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/user_profiles_ui.py:275 -#: /home/kovid/work/calibre/src/calibre/gui2/preferences/behavior_ui.py:148 -#: /home/kovid/work/calibre/src/calibre/gui2/preferences/behavior_ui.py:149 -#: /home/kovid/work/calibre/src/calibre/gui2/preferences/columns_ui.py:83 -#: /home/kovid/work/calibre/src/calibre/gui2/preferences/columns_ui.py:85 -#: /home/kovid/work/calibre/src/calibre/gui2/preferences/columns_ui.py:87 +#: /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/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 -#: /home/kovid/work/calibre/src/calibre/gui2/preferences/columns_ui.py:90 +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/columns_ui.py:91 +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/columns_ui.py:93 +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/columns_ui.py:95 +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/columns_ui.py:97 #: /home/kovid/work/calibre/src/calibre/gui2/preferences/custom_columns_ui.py:83 #: /home/kovid/work/calibre/src/calibre/gui2/preferences/custom_columns_ui.py:85 #: /home/kovid/work/calibre/src/calibre/gui2/preferences/custom_columns_ui.py:87 @@ -5682,6 +5900,8 @@ msgstr "" #: /home/kovid/work/calibre/src/calibre/gui2/preferences/toolbar_ui.py:110 #: /home/kovid/work/calibre/src/calibre/gui2/shortcuts_ui.py:80 #: /home/kovid/work/calibre/src/calibre/gui2/shortcuts_ui.py:85 +#: /home/kovid/work/calibre/src/calibre/gui2/store/mobileread/store_dialog_ui.py:75 +#: /home/kovid/work/calibre/src/calibre/gui2/store/search/search_ui.py:120 #: /home/kovid/work/calibre/src/calibre/gui2/viewer/main_ui.py:191 msgid "..." msgstr "..." @@ -5706,60 +5926,52 @@ msgstr "" "&Liburu anitz karpeta bakoitzeko, honek bere gain hartzen du fitxategi " "bakoitza liburu elektroniko bakar bati dagokiola" -#: /home/kovid/work/calibre/src/calibre/gui2/book_details.py:26 -#: /home/kovid/work/calibre/src/calibre/gui2/book_details.py:53 -#: /home/kovid/work/calibre/src/calibre/gui2/book_details.py:62 -#: /home/kovid/work/calibre/src/calibre/gui2/book_details.py:434 -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/book_info.py:130 -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/book_info.py:131 -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/book_info.py:132 -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/book_info.py:145 -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/delete_matching_from_device.py:76 -#: /home/kovid/work/calibre/src/calibre/gui2/library/models.py:375 -#: /home/kovid/work/calibre/src/calibre/gui2/library/models.py:1170 -msgid "Path" -msgstr "Bidea (Path)" +#: /home/kovid/work/calibre/src/calibre/gui2/bars.py:190 +msgid "Donate" +msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/book_details.py:27 -#: /home/kovid/work/calibre/src/calibre/gui2/book_details.py:56 -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/book_info.py:133 -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/book_info.py:134 -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/book_info.py:135 -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/book_info.py:138 -#: /home/kovid/work/calibre/src/calibre/gui2/library/models.py:374 -#: /home/kovid/work/calibre/src/calibre/gui2/preferences/emailp.py:24 -#: /home/kovid/work/calibre/src/calibre/library/field_metadata.py:118 -msgid "Formats" -msgstr "Formatuak" - -#: /home/kovid/work/calibre/src/calibre/gui2/book_details.py:28 -#: /home/kovid/work/calibre/src/calibre/gui2/library/models.py:981 -#: /home/kovid/work/calibre/src/calibre/gui2/library/models.py:1173 -msgid "Collections" -msgstr "Bildumak" - -#: /home/kovid/work/calibre/src/calibre/gui2/book_details.py:55 -#: /home/kovid/work/calibre/src/calibre/gui2/book_details.py:64 +#: /home/kovid/work/calibre/src/calibre/gui2/book_details.py:108 msgid "Click to open" msgstr "Egin klik zabaltzeko" -#: /home/kovid/work/calibre/src/calibre/gui2/book_details.py:56 -#: /home/kovid/work/calibre/src/calibre/gui2/library/models.py:367 -#: /home/kovid/work/calibre/src/calibre/gui2/library/models.py:373 -#: /home/kovid/work/calibre/src/calibre/gui2/library/models.py:379 -#: /home/kovid/work/calibre/src/calibre/gui2/library/models.py:1179 -#: /home/kovid/work/calibre/src/calibre/gui2/library/models.py:1183 -#: /home/kovid/work/calibre/src/calibre/gui2/shortcuts.py:48 -#: /home/kovid/work/calibre/src/calibre/gui2/shortcuts_ui.py:78 -#: /home/kovid/work/calibre/src/calibre/gui2/shortcuts_ui.py:83 -#: /home/kovid/work/calibre/src/calibre/gui2/widgets.py:316 -msgid "None" -msgstr "Bat ere ez" +#: /home/kovid/work/calibre/src/calibre/gui2/book_details.py:123 +msgid "Ids" +msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/book_details.py:433 +#: /home/kovid/work/calibre/src/calibre/gui2/book_details.py:133 +msgid "Book %s of %s" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/book_details.py:144 +#: /home/kovid/work/calibre/src/calibre/gui2/library/models.py:981 +msgid "Collections" +msgstr "Bildumak" + +#: /home/kovid/work/calibre/src/calibre/gui2/book_details.py:246 +#: /home/kovid/work/calibre/src/calibre/gui2/widgets.py:243 +msgid "Paste Cover" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/book_details.py:247 +#: /home/kovid/work/calibre/src/calibre/gui2/widgets.py:244 +msgid "Copy Cover" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/book_details.py:513 msgid "Double-click to open Book Details window" msgstr "Egin klik birritan liburuaren zehaztasunen leihoa ikusteko" +#: /home/kovid/work/calibre/src/calibre/gui2/book_details.py:514 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/delete_matching_from_device.py:76 +#: /home/kovid/work/calibre/src/calibre/library/field_metadata.py:261 +msgid "Path" +msgstr "Bidea (Path)" + +#: /home/kovid/work/calibre/src/calibre/gui2/book_details.py:515 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/book_info.py:109 +msgid "Cover size: %dx%d" +msgstr "" + #: /home/kovid/work/calibre/src/calibre/gui2/catalog/catalog_bibtex.py:16 msgid "BibTeX Options" msgstr "BibTeX Aukerak" @@ -5771,6 +5983,7 @@ msgstr "BibTeX Aukerak" #: /home/kovid/work/calibre/src/calibre/gui2/convert/epub_output.py:16 #: /home/kovid/work/calibre/src/calibre/gui2/convert/fb2_input.py:13 #: /home/kovid/work/calibre/src/calibre/gui2/convert/fb2_output.py:15 +#: /home/kovid/work/calibre/src/calibre/gui2/convert/htmlz_output.py:15 #: /home/kovid/work/calibre/src/calibre/gui2/convert/lrf_output.py:20 #: /home/kovid/work/calibre/src/calibre/gui2/convert/mobi_output.py:20 #: /home/kovid/work/calibre/src/calibre/gui2/convert/pdb_output.py:15 @@ -5790,6 +6003,7 @@ msgstr "Aukerak honetako propio:" #: /home/kovid/work/calibre/src/calibre/gui2/catalog/catalog_epub_mobi.py:19 #: /home/kovid/work/calibre/src/calibre/gui2/convert/epub_output.py:16 #: /home/kovid/work/calibre/src/calibre/gui2/convert/fb2_output.py:15 +#: /home/kovid/work/calibre/src/calibre/gui2/convert/htmlz_output.py:15 #: /home/kovid/work/calibre/src/calibre/gui2/convert/lrf_output.py:20 #: /home/kovid/work/calibre/src/calibre/gui2/convert/mobi_output.py:20 #: /home/kovid/work/calibre/src/calibre/gui2/convert/pdb_output.py:15 @@ -5802,16 +6016,17 @@ msgstr "Aukerak honetako propio:" msgid "output" msgstr "outputa, helburua" -#: /home/kovid/work/calibre/src/calibre/gui2/catalog/catalog_bibtex_ui.py:89 +#: /home/kovid/work/calibre/src/calibre/gui2/catalog/catalog_bibtex_ui.py:77 #: /home/kovid/work/calibre/src/calibre/gui2/catalog/catalog_csv_xml_ui.py:42 #: /home/kovid/work/calibre/src/calibre/gui2/catalog/catalog_epub_mobi_ui.py:295 #: /home/kovid/work/calibre/src/calibre/gui2/catalog/catalog_tab_template_ui.py:32 -#: /home/kovid/work/calibre/src/calibre/gui2/convert/comic_input_ui.py:100 +#: /home/kovid/work/calibre/src/calibre/gui2/convert/comic_input_ui.py:103 #: /home/kovid/work/calibre/src/calibre/gui2/convert/debug_ui.py:54 #: /home/kovid/work/calibre/src/calibre/gui2/convert/epub_output_ui.py:56 #: /home/kovid/work/calibre/src/calibre/gui2/convert/fb2_input_ui.py:33 -#: /home/kovid/work/calibre/src/calibre/gui2/convert/fb2_output_ui.py:38 +#: /home/kovid/work/calibre/src/calibre/gui2/convert/fb2_output_ui.py:44 #: /home/kovid/work/calibre/src/calibre/gui2/convert/heuristics_ui.py:112 +#: /home/kovid/work/calibre/src/calibre/gui2/convert/htmlz_output_ui.py:44 #: /home/kovid/work/calibre/src/calibre/gui2/convert/look_and_feel_ui.py:137 #: /home/kovid/work/calibre/src/calibre/gui2/convert/lrf_output_ui.py:120 #: /home/kovid/work/calibre/src/calibre/gui2/convert/metadata_ui.py:158 @@ -5825,23 +6040,24 @@ msgstr "outputa, helburua" #: /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:147 #: /home/kovid/work/calibre/src/calibre/gui2/convert/snb_output_ui.py:42 -#: /home/kovid/work/calibre/src/calibre/gui2/convert/structure_detection_ui.py:56 +#: /home/kovid/work/calibre/src/calibre/gui2/convert/structure_detection_ui.py:59 #: /home/kovid/work/calibre/src/calibre/gui2/convert/toc_ui.py:67 #: /home/kovid/work/calibre/src/calibre/gui2/convert/txt_input_ui.py:91 -#: /home/kovid/work/calibre/src/calibre/gui2/convert/txt_output_ui.py:84 -#: /home/kovid/work/calibre/src/calibre/gui2/convert/xexp_edit_ui.py:53 +#: /home/kovid/work/calibre/src/calibre/gui2/convert/txt_output_ui.py:87 +#: /home/kovid/work/calibre/src/calibre/gui2/convert/xexp_edit_ui.py:54 #: /home/kovid/work/calibre/src/calibre/gui2/convert/xpath_wizard_ui.py:72 #: /home/kovid/work/calibre/src/calibre/gui2/device_drivers/configwidget_ui.py:77 #: /home/kovid/work/calibre/src/calibre/gui2/dialogs/search_item_ui.py:40 -#: /home/kovid/work/calibre/src/calibre/gui2/filename_pattern_ui.py:114 +#: /home/kovid/work/calibre/src/calibre/gui2/filename_pattern_ui.py:128 #: /home/kovid/work/calibre/src/calibre/gui2/preferences/adding_ui.py:64 -#: /home/kovid/work/calibre/src/calibre/gui2/preferences/behavior_ui.py:130 -#: /home/kovid/work/calibre/src/calibre/gui2/preferences/columns_ui.py:81 +#: /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/custom_columns_ui.py:81 #: /home/kovid/work/calibre/src/calibre/gui2/preferences/email_ui.py:65 -#: /home/kovid/work/calibre/src/calibre/gui2/preferences/look_feel_ui.py:135 -#: /home/kovid/work/calibre/src/calibre/gui2/preferences/misc_ui.py:60 +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/look_feel_ui.py:197 +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/metadata_sources_ui.py:109 +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/misc_ui.py:61 #: /home/kovid/work/calibre/src/calibre/gui2/preferences/plugboard_ui.py:113 #: /home/kovid/work/calibre/src/calibre/gui2/preferences/plugins_ui.py:86 #: /home/kovid/work/calibre/src/calibre/gui2/preferences/save_template_ui.py:46 @@ -5852,72 +6068,41 @@ msgstr "outputa, helburua" #: /home/kovid/work/calibre/src/calibre/gui2/preferences/template_functions_ui.py:95 #: /home/kovid/work/calibre/src/calibre/gui2/preferences/toolbar_ui.py:98 #: /home/kovid/work/calibre/src/calibre/gui2/preferences/tweaks_ui.py:87 +#: /home/kovid/work/calibre/src/calibre/gui2/store/basic_config_widget_ui.py:37 #: /home/kovid/work/calibre/src/calibre/gui2/wizard/send_email_ui.py:123 msgid "Form" msgstr "Inprimakia" -#: /home/kovid/work/calibre/src/calibre/gui2/catalog/catalog_bibtex_ui.py:90 +#: /home/kovid/work/calibre/src/calibre/gui2/catalog/catalog_bibtex_ui.py:78 msgid "Bib file encoding:" msgstr "Bib formatuko fitxategia kodetzen:" -#: /home/kovid/work/calibre/src/calibre/gui2/catalog/catalog_bibtex_ui.py:91 +#: /home/kovid/work/calibre/src/calibre/gui2/catalog/catalog_bibtex_ui.py:79 #: /home/kovid/work/calibre/src/calibre/gui2/catalog/catalog_csv_xml_ui.py:43 msgid "Fields to include in output:" msgstr "Outputean, helburuan sartzeko eremuak:" -#: /home/kovid/work/calibre/src/calibre/gui2/catalog/catalog_bibtex_ui.py:92 -msgid "ascii/LaTeX" -msgstr "ascii/LaTeX" - -#: /home/kovid/work/calibre/src/calibre/gui2/catalog/catalog_bibtex_ui.py:93 +#: /home/kovid/work/calibre/src/calibre/gui2/catalog/catalog_bibtex_ui.py:80 msgid "Encoding configuration (change if you have errors) :" msgstr "Konfigurazioa kodetzen (alda ezazu akatsak baldin badituzu):" -#: /home/kovid/work/calibre/src/calibre/gui2/catalog/catalog_bibtex_ui.py:94 -msgid "strict" -msgstr "zehatz" - -#: /home/kovid/work/calibre/src/calibre/gui2/catalog/catalog_bibtex_ui.py:95 -msgid "replace" -msgstr "ordeztu" - -#: /home/kovid/work/calibre/src/calibre/gui2/catalog/catalog_bibtex_ui.py:96 -msgid "ignore" -msgstr "baztertu" - -#: /home/kovid/work/calibre/src/calibre/gui2/catalog/catalog_bibtex_ui.py:97 -msgid "backslashreplace" -msgstr "backslashreplace" - -#: /home/kovid/work/calibre/src/calibre/gui2/catalog/catalog_bibtex_ui.py:98 +#: /home/kovid/work/calibre/src/calibre/gui2/catalog/catalog_bibtex_ui.py:81 msgid "BibTeX entry type:" msgstr "BibTeX mota sartu:" -#: /home/kovid/work/calibre/src/calibre/gui2/catalog/catalog_bibtex_ui.py:99 -msgid "mixed" -msgstr "nahasia" - -#: /home/kovid/work/calibre/src/calibre/gui2/catalog/catalog_bibtex_ui.py:100 -msgid "misc" -msgstr "nahas-mahas" - -#: /home/kovid/work/calibre/src/calibre/gui2/catalog/catalog_bibtex_ui.py:101 -msgid "book" -msgstr "liburua" - -#: /home/kovid/work/calibre/src/calibre/gui2/catalog/catalog_bibtex_ui.py:102 +#: /home/kovid/work/calibre/src/calibre/gui2/catalog/catalog_bibtex_ui.py:82 msgid "Create a citation tag?" msgstr "Aipu-etiketarik sortu nahi?" -#: /home/kovid/work/calibre/src/calibre/gui2/catalog/catalog_bibtex_ui.py:103 +#: /home/kovid/work/calibre/src/calibre/gui2/catalog/catalog_bibtex_ui.py:83 msgid "Add files path with formats?" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/catalog/catalog_bibtex_ui.py:104 +#: /home/kovid/work/calibre/src/calibre/gui2/catalog/catalog_bibtex_ui.py:84 msgid "Expression to form the BibTeX citation tag:" msgstr "BibTeX aipu-etiketarako adierazpena:" -#: /home/kovid/work/calibre/src/calibre/gui2/catalog/catalog_bibtex_ui.py:105 +#: /home/kovid/work/calibre/src/calibre/gui2/catalog/catalog_bibtex_ui.py:85 msgid "" "Some explanation about this template:\n" " -The fields availables are 'author_sort', 'authors', 'id',\n" @@ -6170,10 +6355,12 @@ msgid "Remove formatting" msgstr "Kendu formateatzea" #: /home/kovid/work/calibre/src/calibre/gui2/comments_editor.py:96 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/edit_authors_dialog.py:134 msgid "Copy" msgstr "Kopiatu" #: /home/kovid/work/calibre/src/calibre/gui2/comments_editor.py:97 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/edit_authors_dialog.py:136 msgid "Paste" msgstr "Itsatsi" @@ -6212,8 +6399,9 @@ msgid "Style the selected text block" msgstr "Hautatutako testu blokearen estiloa aldatu" #: /home/kovid/work/calibre/src/calibre/gui2/comments_editor.py:125 -#: /home/kovid/work/calibre/src/calibre/gui2/preferences/behavior.py:32 -#: /home/kovid/work/calibre/src/calibre/gui2/preferences/behavior_ui.py:139 +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/behavior.py:34 +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/behavior.py:36 +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/behavior_ui.py:158 msgid "Normal" msgstr "Normala" @@ -6264,11 +6452,11 @@ msgstr "Sortu esteka" msgid "Enter URL" msgstr "Sartu URL" -#: /home/kovid/work/calibre/src/calibre/gui2/comments_editor.py:522 +#: /home/kovid/work/calibre/src/calibre/gui2/comments_editor.py:523 msgid "Normal view" msgstr "Ikuspegi arrunta" -#: /home/kovid/work/calibre/src/calibre/gui2/comments_editor.py:523 +#: /home/kovid/work/calibre/src/calibre/gui2/comments_editor.py:524 msgid "HTML Source" msgstr "HTML iturburua" @@ -6284,7 +6472,7 @@ msgstr "" #: /home/kovid/work/calibre/src/calibre/gui2/convert/bulk.py:74 msgid "Bulk Convert" -msgstr "Karga handiko bihurketa" +msgstr "Bihurtu multzoka" #: /home/kovid/work/calibre/src/calibre/gui2/convert/bulk.py:89 #: /home/kovid/work/calibre/src/calibre/gui2/convert/single.py:189 @@ -6302,73 +6490,77 @@ msgstr "Sorburu komikia" msgid "input" msgstr "sorburua" -#: /home/kovid/work/calibre/src/calibre/gui2/convert/comic_input_ui.py:101 +#: /home/kovid/work/calibre/src/calibre/gui2/convert/comic_input_ui.py:104 #: /home/kovid/work/calibre/src/calibre/gui2/dialogs/comicconf_ui.py:99 msgid "&Number of Colors:" msgstr "Kolore &Kopurua:" -#: /home/kovid/work/calibre/src/calibre/gui2/convert/comic_input_ui.py:102 +#: /home/kovid/work/calibre/src/calibre/gui2/convert/comic_input_ui.py:105 #: /home/kovid/work/calibre/src/calibre/gui2/dialogs/comicconf_ui.py:101 msgid "Disable &normalize" msgstr "Desgaitu &normaldu" -#: /home/kovid/work/calibre/src/calibre/gui2/convert/comic_input_ui.py:103 +#: /home/kovid/work/calibre/src/calibre/gui2/convert/comic_input_ui.py:106 #: /home/kovid/work/calibre/src/calibre/gui2/dialogs/comicconf_ui.py:102 msgid "Keep &aspect ratio" msgstr "Gorde &aspektu proportzio" -#: /home/kovid/work/calibre/src/calibre/gui2/convert/comic_input_ui.py:104 +#: /home/kovid/work/calibre/src/calibre/gui2/convert/comic_input_ui.py:107 #: /home/kovid/work/calibre/src/calibre/gui2/dialogs/comicconf_ui.py:103 msgid "Disable &Sharpening" msgstr "Desgaitu &Zorrozketa" -#: /home/kovid/work/calibre/src/calibre/gui2/convert/comic_input_ui.py:105 +#: /home/kovid/work/calibre/src/calibre/gui2/convert/comic_input_ui.py:108 #: /home/kovid/work/calibre/src/calibre/gui2/dialogs/comicconf_ui.py:109 msgid "Disable &Trimming" msgstr "Desgaitu &Apainketa" -#: /home/kovid/work/calibre/src/calibre/gui2/convert/comic_input_ui.py:106 +#: /home/kovid/work/calibre/src/calibre/gui2/convert/comic_input_ui.py:109 #: /home/kovid/work/calibre/src/calibre/gui2/dialogs/comicconf_ui.py:108 msgid "&Wide" msgstr "&Zabal" -#: /home/kovid/work/calibre/src/calibre/gui2/convert/comic_input_ui.py:107 +#: /home/kovid/work/calibre/src/calibre/gui2/convert/comic_input_ui.py:110 #: /home/kovid/work/calibre/src/calibre/gui2/dialogs/comicconf_ui.py:104 msgid "&Landscape" msgstr "&Horizontal" -#: /home/kovid/work/calibre/src/calibre/gui2/convert/comic_input_ui.py:108 +#: /home/kovid/work/calibre/src/calibre/gui2/convert/comic_input_ui.py:111 #: /home/kovid/work/calibre/src/calibre/gui2/dialogs/comicconf_ui.py:106 msgid "&Right to left" msgstr "&Eskuinetatik ezkerretara" -#: /home/kovid/work/calibre/src/calibre/gui2/convert/comic_input_ui.py:109 +#: /home/kovid/work/calibre/src/calibre/gui2/convert/comic_input_ui.py:112 #: /home/kovid/work/calibre/src/calibre/gui2/dialogs/comicconf_ui.py:105 msgid "Don't so&rt" msgstr "Ez ezazu sail&katu" -#: /home/kovid/work/calibre/src/calibre/gui2/convert/comic_input_ui.py:110 +#: /home/kovid/work/calibre/src/calibre/gui2/convert/comic_input_ui.py:113 #: /home/kovid/work/calibre/src/calibre/gui2/dialogs/comicconf_ui.py:107 msgid "De&speckle" msgstr "De&speckle \"parasitoak erauzi\"" -#: /home/kovid/work/calibre/src/calibre/gui2/convert/comic_input_ui.py:111 +#: /home/kovid/work/calibre/src/calibre/gui2/convert/comic_input_ui.py:114 msgid "&Disable comic processing" msgstr "&Desgaitu komikiaren prozesaketa" -#: /home/kovid/work/calibre/src/calibre/gui2/convert/comic_input_ui.py:112 +#: /home/kovid/work/calibre/src/calibre/gui2/convert/comic_input_ui.py:115 #: /home/kovid/work/calibre/src/calibre/gui2/convert/single_ui.py:120 msgid "&Output format:" msgstr "&Helburu formatua:" -#: /home/kovid/work/calibre/src/calibre/gui2/convert/comic_input_ui.py:113 +#: /home/kovid/work/calibre/src/calibre/gui2/convert/comic_input_ui.py:116 msgid "Disable conversion of images to &black and white" msgstr "Desgaitu irudiak grisetara bihurtzea &zuri-beltzean" -#: /home/kovid/work/calibre/src/calibre/gui2/convert/comic_input_ui.py:114 +#: /home/kovid/work/calibre/src/calibre/gui2/convert/comic_input_ui.py:117 msgid "Override image &size:" msgstr "" +#: /home/kovid/work/calibre/src/calibre/gui2/convert/comic_input_ui.py:118 +msgid "Don't add links to &pages to the Table of Contents for CBC files" +msgstr "" + #: /home/kovid/work/calibre/src/calibre/gui2/convert/debug.py:19 msgid "Debug" msgstr "Akasgabetu" @@ -6457,10 +6649,14 @@ msgstr "Ez ezazu txertatu &Aurkibidea liburuaren hasieran." msgid "FB2 Output" msgstr "FB2 outputa" -#: /home/kovid/work/calibre/src/calibre/gui2/convert/fb2_output_ui.py:39 +#: /home/kovid/work/calibre/src/calibre/gui2/convert/fb2_output_ui.py:45 msgid "Sectionize:" msgstr "Moldatu ataletan:" +#: /home/kovid/work/calibre/src/calibre/gui2/convert/fb2_output_ui.py:46 +msgid "Genre" +msgstr "" + #: /home/kovid/work/calibre/src/calibre/gui2/convert/font_key_ui.py:104 msgid "Font rescaling wizard" msgstr "Letra-tipoak berreskalatzeko laguntzaile-magialaria" @@ -6614,6 +6810,18 @@ msgstr "" msgid "Replace entity indents with CSS indents" msgstr "" +#: /home/kovid/work/calibre/src/calibre/gui2/convert/htmlz_output.py:14 +msgid "HTMLZ Output" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/convert/htmlz_output_ui.py:45 +msgid "How to handle CSS" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/convert/htmlz_output_ui.py:46 +msgid "How to handle class based CSS" +msgstr "" + #: /home/kovid/work/calibre/src/calibre/gui2/convert/look_and_feel.py:16 msgid "Look & Feel" msgstr "Diseinua & Itxura" @@ -6768,7 +6976,7 @@ msgid "&Monospaced font family:" msgstr "&Monospaced letra-tipo familia:" #: /home/kovid/work/calibre/src/calibre/gui2/convert/metadata.py:47 -#: /home/kovid/work/calibre/src/calibre/gui2/viewer/main.py:115 +#: /home/kovid/work/calibre/src/calibre/gui2/viewer/main.py:117 #: /home/kovid/work/calibre/src/calibre/gui2/viewer/main_ui.py:200 msgid "Metadata" msgstr "Metadatuak" @@ -6782,49 +6990,41 @@ msgstr "" "metadatu gehien gordeko ditu." #: /home/kovid/work/calibre/src/calibre/gui2/convert/metadata.py:180 -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single.py:171 -#: /home/kovid/work/calibre/src/calibre/gui2/metadata/basic_widgets.py:643 +#: /home/kovid/work/calibre/src/calibre/gui2/metadata/basic_widgets.py:726 msgid "Choose cover for " msgstr "Aukeratu liburu-azala honentzat: " #: /home/kovid/work/calibre/src/calibre/gui2/convert/metadata.py:187 -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single.py:178 -#: /home/kovid/work/calibre/src/calibre/gui2/metadata/basic_widgets.py:651 +#: /home/kovid/work/calibre/src/calibre/gui2/metadata/basic_widgets.py:734 msgid "Cannot read" msgstr "Ezin irakurri" #: /home/kovid/work/calibre/src/calibre/gui2/convert/metadata.py:188 -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single.py:179 -#: /home/kovid/work/calibre/src/calibre/gui2/metadata/basic_widgets.py:652 +#: /home/kovid/work/calibre/src/calibre/gui2/metadata/basic_widgets.py:735 msgid "You do not have permission to read the file: " msgstr "Ez duzu fitxategi hau irakurtzeko baimenik: " #: /home/kovid/work/calibre/src/calibre/gui2/convert/metadata.py:196 #: /home/kovid/work/calibre/src/calibre/gui2/convert/metadata.py:203 -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single.py:187 -#: /home/kovid/work/calibre/src/calibre/gui2/metadata/basic_widgets.py:660 +#: /home/kovid/work/calibre/src/calibre/gui2/metadata/basic_widgets.py:743 msgid "Error reading file" msgstr "Huts egin du fitxategia irakurtzerakoan" #: /home/kovid/work/calibre/src/calibre/gui2/convert/metadata.py:197 -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single.py:188 -#: /home/kovid/work/calibre/src/calibre/gui2/metadata/basic_widgets.py:661 +#: /home/kovid/work/calibre/src/calibre/gui2/metadata/basic_widgets.py:744 msgid "

      There was an error reading from file:
      " msgstr "

      Akats bat egon da fitxategitik irakurtzerakoan:
      " #: /home/kovid/work/calibre/src/calibre/gui2/convert/metadata.py:204 -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single.py:196 -#: /home/kovid/work/calibre/src/calibre/gui2/metadata/basic_widgets.py:671 +#: /home/kovid/work/calibre/src/calibre/gui2/metadata/basic_widgets.py:754 msgid " is not a valid picture" msgstr " ez da irudi baliogarria" #: /home/kovid/work/calibre/src/calibre/gui2/convert/metadata_ui.py:159 -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single_ui.py:446 msgid "Book Cover" msgstr "Liburuaren azala" #: /home/kovid/work/calibre/src/calibre/gui2/convert/metadata_ui.py:160 -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single_ui.py:447 msgid "Change &cover image:" msgstr "Aldatu &azaleko irudia:" @@ -6837,19 +7037,16 @@ msgid "Use cover from &source file" msgstr "Erabil ezazu liburu-azala &sorburu fitxategitik" #: /home/kovid/work/calibre/src/calibre/gui2/convert/metadata_ui.py:164 -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single_ui.py:408 msgid "&Title: " msgstr "&Izenburua: " #: /home/kovid/work/calibre/src/calibre/gui2/convert/metadata_ui.py:165 -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single_ui.py:409 -#: /home/kovid/work/calibre/src/calibre/gui2/metadata/basic_widgets.py:60 +#: /home/kovid/work/calibre/src/calibre/gui2/metadata/basic_widgets.py:72 msgid "Change the title of this book" msgstr "Aldatu liburu honen izenburua" #: /home/kovid/work/calibre/src/calibre/gui2/convert/metadata_ui.py:166 -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:499 -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single_ui.py:420 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:525 msgid "&Author(s): " msgstr "&Egilea(k):s " @@ -6866,19 +7063,17 @@ msgstr "" "beharko lirateke." #: /home/kovid/work/calibre/src/calibre/gui2/convert/metadata_ui.py:169 -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:509 -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single_ui.py:428 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:535 msgid "&Publisher: " msgstr "&Argitaratzailea: " #: /home/kovid/work/calibre/src/calibre/gui2/convert/metadata_ui.py:170 -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single_ui.py:429 msgid "Ta&gs: " msgstr "Etike&tak: " #: /home/kovid/work/calibre/src/calibre/gui2/convert/metadata_ui.py:171 -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:511 -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single_ui.py:430 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:537 +#: /home/kovid/work/calibre/src/calibre/gui2/metadata/basic_widgets.py:909 msgid "" "Tags categorize the book. This is particularly useful while searching. " "

      They can be any words or phrases, separated by commas." @@ -6888,25 +7083,21 @@ msgstr "" "etiketa, komekin bereiziak." #: /home/kovid/work/calibre/src/calibre/gui2/convert/metadata_ui.py:172 -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:518 -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single_ui.py:433 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:544 #: /home/kovid/work/calibre/src/calibre/gui2/dialogs/search_ui.py:214 -#: /home/kovid/work/calibre/src/calibre/gui2/metadata/basic_widgets.py:293 +#: /home/kovid/work/calibre/src/calibre/gui2/metadata/basic_widgets.py:355 msgid "&Series:" msgstr "&Sailak:" #: /home/kovid/work/calibre/src/calibre/gui2/convert/metadata_ui.py:173 #: /home/kovid/work/calibre/src/calibre/gui2/convert/metadata_ui.py:174 -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:519 -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:520 -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single_ui.py:434 -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single_ui.py:435 -#: /home/kovid/work/calibre/src/calibre/gui2/metadata/basic_widgets.py:292 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:545 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:546 +#: /home/kovid/work/calibre/src/calibre/gui2/metadata/basic_widgets.py:354 msgid "List of known series. You can add new series." msgstr "Ezagunak diren sailen zerrenda. Sail berria gehi dezakezu." #: /home/kovid/work/calibre/src/calibre/gui2/convert/metadata_ui.py:175 -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single_ui.py:438 msgid "Book " msgstr "Liburua " @@ -6915,6 +7106,7 @@ msgid "MOBI Output" msgstr "MOBI formatuko outputa" #: /home/kovid/work/calibre/src/calibre/gui2/convert/mobi_output.py:44 +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/behavior.py:64 msgid "Default" msgstr "Lehenetsia" @@ -7003,13 +7195,14 @@ msgid "PDB Output" msgstr "PDB outputa" #: /home/kovid/work/calibre/src/calibre/gui2/convert/pdb_output_ui.py:48 +#: /home/kovid/work/calibre/src/calibre/gui2/store/search/adv_search_builder_ui.py:195 msgid "&Format:" msgstr "&Formatua:" #: /home/kovid/work/calibre/src/calibre/gui2/convert/pdb_output_ui.py:49 #: /home/kovid/work/calibre/src/calibre/gui2/convert/pmlz_output_ui.py:47 #: /home/kovid/work/calibre/src/calibre/gui2/convert/rb_output_ui.py:34 -#: /home/kovid/work/calibre/src/calibre/gui2/convert/txt_output_ui.py:92 +#: /home/kovid/work/calibre/src/calibre/gui2/convert/txt_output_ui.py:95 msgid "&Inline TOC" msgstr "&Inline aurkibidea" @@ -7081,7 +7274,7 @@ msgid "Regex:" msgstr "Regex (ohiko adierazpen):" #: /home/kovid/work/calibre/src/calibre/gui2/convert/regex_builder_ui.py:92 -#: /home/kovid/work/calibre/src/calibre/gui2/filename_pattern_ui.py:122 +#: /home/kovid/work/calibre/src/calibre/gui2/filename_pattern_ui.py:136 msgid "Test" msgstr "Proba egin" @@ -7090,6 +7283,8 @@ msgid "Occurrences:" msgstr "" #: /home/kovid/work/calibre/src/calibre/gui2/convert/regex_builder_ui.py:94 +#: /home/kovid/work/calibre/src/calibre/gui2/store/mobileread/store_dialog_ui.py:78 +#: /home/kovid/work/calibre/src/calibre/gui2/store/mobileread_store_dialog_ui.py:64 msgid "0" msgstr "" @@ -7098,13 +7293,13 @@ msgid "Goto:" msgstr "" #: /home/kovid/work/calibre/src/calibre/gui2/convert/regex_builder_ui.py:96 -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/book_info_ui.py:81 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/book_info_ui.py:72 #: /home/kovid/work/calibre/src/calibre/gui2/preferences/plugins_ui.py:89 msgid "&Previous" msgstr "&Aurrekoa" #: /home/kovid/work/calibre/src/calibre/gui2/convert/regex_builder_ui.py:97 -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/book_info_ui.py:82 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/book_info_ui.py:73 #: /home/kovid/work/calibre/src/calibre/gui2/preferences/plugins_ui.py:88 msgid "&Next" msgstr "&Hurrengoa" @@ -7113,26 +7308,26 @@ msgstr "&Hurrengoa" msgid "Preview" msgstr "Aurrebista" -#: /home/kovid/work/calibre/src/calibre/gui2/convert/search_and_replace.py:15 +#: /home/kovid/work/calibre/src/calibre/gui2/convert/search_and_replace.py:17 msgid "" "Search\n" "&\n" "Replace" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/convert/search_and_replace.py:28 -#: /home/kovid/work/calibre/src/calibre/gui2/convert/search_and_replace.py:31 -#: /home/kovid/work/calibre/src/calibre/gui2/convert/search_and_replace.py:34 +#: /home/kovid/work/calibre/src/calibre/gui2/convert/search_and_replace.py:30 +#: /home/kovid/work/calibre/src/calibre/gui2/convert/search_and_replace.py:33 +#: /home/kovid/work/calibre/src/calibre/gui2/convert/search_and_replace.py:36 msgid "&Search Regular Expression" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/convert/search_and_replace.py:71 -#: /home/kovid/work/calibre/src/calibre/gui2/widgets.py:99 +#: /home/kovid/work/calibre/src/calibre/gui2/convert/search_and_replace.py:73 +#: /home/kovid/work/calibre/src/calibre/gui2/widgets.py:101 msgid "Invalid regular expression" msgstr "Baliorik gabeko ohiko adierazpena" -#: /home/kovid/work/calibre/src/calibre/gui2/convert/search_and_replace.py:72 -#: /home/kovid/work/calibre/src/calibre/gui2/widgets.py:100 +#: /home/kovid/work/calibre/src/calibre/gui2/convert/search_and_replace.py:74 +#: /home/kovid/work/calibre/src/calibre/gui2/widgets.py:102 msgid "Invalid regular expression: %s" msgstr "Baliorik gabeko ohiko adierazpena: %s" @@ -7172,10 +7367,13 @@ msgid "Options specific to the input format." msgstr "Berariazko aukerak sorburu formatuarentzat." #: /home/kovid/work/calibre/src/calibre/gui2/convert/single_ui.py:117 -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/book_info_ui.py:76 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/book_info_ui.py:69 #: /home/kovid/work/calibre/src/calibre/gui2/dialogs/comicconf_ui.py:96 #: /home/kovid/work/calibre/src/calibre/gui2/dialogs/message_box_ui.py:52 #: /home/kovid/work/calibre/src/calibre/gui2/dialogs/progress_ui.py:53 +#: /home/kovid/work/calibre/src/calibre/gui2/store/mobileread/cache_progress_dialog_ui.py:50 +#: /home/kovid/work/calibre/src/calibre/gui2/store/mobileread/store_dialog_ui.py:73 +#: /home/kovid/work/calibre/src/calibre/gui2/store/mobileread_store_dialog_ui.py:61 msgid "Dialog" msgstr "Elkarrizketa" @@ -7240,19 +7438,19 @@ msgstr "Baliorik gabeko XPath" msgid "The XPath expression %s is invalid." msgstr "XPath %s adierazpena baliorik gabekoa da." -#: /home/kovid/work/calibre/src/calibre/gui2/convert/structure_detection_ui.py:57 +#: /home/kovid/work/calibre/src/calibre/gui2/convert/structure_detection_ui.py:60 msgid "Chapter &mark:" msgstr "Kapituluaren &marka:" -#: /home/kovid/work/calibre/src/calibre/gui2/convert/structure_detection_ui.py:58 +#: /home/kovid/work/calibre/src/calibre/gui2/convert/structure_detection_ui.py:61 msgid "Remove first &image" msgstr "Lehena ezabatu &irudia" -#: /home/kovid/work/calibre/src/calibre/gui2/convert/structure_detection_ui.py:59 +#: /home/kovid/work/calibre/src/calibre/gui2/convert/structure_detection_ui.py:62 msgid "Insert &metadata as page at start of book" msgstr "Txertatu &metadatuak orrialde bezala liburuaren hasieran" -#: /home/kovid/work/calibre/src/calibre/gui2/convert/structure_detection_ui.py:60 +#: /home/kovid/work/calibre/src/calibre/gui2/convert/structure_detection_ui.py:63 msgid "" "The header and footer removal options have been replaced by the Search & " "Replace options. Click the Search & Replace category in the bar to the left " @@ -7260,6 +7458,10 @@ msgid "" "header/footer removal regexps into the search field." msgstr "" +#: /home/kovid/work/calibre/src/calibre/gui2/convert/structure_detection_ui.py:64 +msgid "Remove &fake margins" +msgstr "" + #: /home/kovid/work/calibre/src/calibre/gui2/convert/toc.py:16 msgid "" "Table of\n" @@ -7356,53 +7558,56 @@ msgstr "" msgid "TXT Output" msgstr "TXT outputa" -#: /home/kovid/work/calibre/src/calibre/gui2/convert/txt_output_ui.py:85 +#: /home/kovid/work/calibre/src/calibre/gui2/convert/txt_output_ui.py:88 msgid "General" msgstr "Orokorra" -#: /home/kovid/work/calibre/src/calibre/gui2/convert/txt_output_ui.py:86 +#: /home/kovid/work/calibre/src/calibre/gui2/convert/txt_output_ui.py:89 msgid "Output &Encoding:" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/convert/txt_output_ui.py:87 +#: /home/kovid/work/calibre/src/calibre/gui2/convert/txt_output_ui.py:90 msgid "&Line ending style:" msgstr "&Lerroa bukatzeko estiloa:" -#: /home/kovid/work/calibre/src/calibre/gui2/convert/txt_output_ui.py:88 +#: /home/kovid/work/calibre/src/calibre/gui2/convert/txt_output_ui.py:91 msgid "&Formatting:" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/convert/txt_output_ui.py:89 +#: /home/kovid/work/calibre/src/calibre/gui2/convert/txt_output_ui.py:92 msgid "Plain" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/convert/txt_output_ui.py:90 +#: /home/kovid/work/calibre/src/calibre/gui2/convert/txt_output_ui.py:93 msgid "&Maximum line length:" msgstr "Lerroaren luzera &Maximoa:" -#: /home/kovid/work/calibre/src/calibre/gui2/convert/txt_output_ui.py:91 +#: /home/kovid/work/calibre/src/calibre/gui2/convert/txt_output_ui.py:94 msgid "Force maximum line length" msgstr "Lerroaren luzeerarik handienera behartu" -#: /home/kovid/work/calibre/src/calibre/gui2/convert/txt_output_ui.py:93 +#: /home/kovid/work/calibre/src/calibre/gui2/convert/txt_output_ui.py:96 msgid "Markdown, Textile" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/convert/txt_output_ui.py:94 +#: /home/kovid/work/calibre/src/calibre/gui2/convert/txt_output_ui.py:97 msgid "Do not remove links ( tags) before processing" msgstr "Ez ezabatu estekak ( etiketak) prozesatu baino lehen" -#: /home/kovid/work/calibre/src/calibre/gui2/convert/txt_output_ui.py:95 +#: /home/kovid/work/calibre/src/calibre/gui2/convert/txt_output_ui.py:98 msgid "Do not remove image references before processing" msgstr "Ez ezabatu irudien erreferentziak prozesatu baino lehen" +#: /home/kovid/work/calibre/src/calibre/gui2/convert/txt_output_ui.py:99 +msgid "Keep text color, when possible" +msgstr "" + #: /home/kovid/work/calibre/src/calibre/gui2/convert/txtz_output.py:12 msgid "TXTZ Output" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/convert/xexp_edit_ui.py:54 -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/book_info_ui.py:77 -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/book_info_ui.py:78 +#: /home/kovid/work/calibre/src/calibre/gui2/convert/xexp_edit_ui.py:55 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/book_info_ui.py:70 #: /home/kovid/work/calibre/src/calibre/gui2/dialogs/choose_format_ui.py:46 #: /home/kovid/work/calibre/src/calibre/gui2/dialogs/confirm_delete_ui.py:54 #: /home/kovid/work/calibre/src/calibre/gui2/dialogs/password_ui.py:62 @@ -7411,7 +7616,7 @@ msgstr "" msgid "TextLabel" msgstr "TestuEtiketa" -#: /home/kovid/work/calibre/src/calibre/gui2/convert/xexp_edit_ui.py:55 +#: /home/kovid/work/calibre/src/calibre/gui2/convert/xexp_edit_ui.py:56 msgid "Use a wizard to help construct the Regular expression" msgstr "" @@ -7497,62 +7702,60 @@ msgstr "" "erabiltzen, ikus ezazu XPath Tutoriala." -#: /home/kovid/work/calibre/src/calibre/gui2/cover_flow.py:118 +#: /home/kovid/work/calibre/src/calibre/gui2/cover_flow.py:128 msgid "Browse by covers" msgstr "Liburu-azaletan zehar arakatu" -#: /home/kovid/work/calibre/src/calibre/gui2/cover_flow.py:149 +#: /home/kovid/work/calibre/src/calibre/gui2/cover_flow.py:158 msgid "Cover browser could not be loaded" msgstr "Liburu-azalen arakatzailea ezin izan da kargatu" -#: /home/kovid/work/calibre/src/calibre/gui2/custom_column_widgets.py:64 -#: /home/kovid/work/calibre/src/calibre/gui2/custom_column_widgets.py:89 -#: /home/kovid/work/calibre/src/calibre/gui2/custom_column_widgets.py:113 -#: /home/kovid/work/calibre/src/calibre/gui2/custom_column_widgets.py:150 -#: /home/kovid/work/calibre/src/calibre/gui2/custom_column_widgets.py:184 -#: /home/kovid/work/calibre/src/calibre/gui2/custom_column_widgets.py:294 -#: /home/kovid/work/calibre/src/calibre/gui2/custom_column_widgets.py:558 -#: /home/kovid/work/calibre/src/calibre/gui2/custom_column_widgets.py:599 -#: /home/kovid/work/calibre/src/calibre/gui2/custom_column_widgets.py:622 -#: /home/kovid/work/calibre/src/calibre/gui2/custom_column_widgets.py:673 +#: /home/kovid/work/calibre/src/calibre/gui2/custom_column_widgets.py:63 +#: /home/kovid/work/calibre/src/calibre/gui2/custom_column_widgets.py:88 +#: /home/kovid/work/calibre/src/calibre/gui2/custom_column_widgets.py:112 +#: /home/kovid/work/calibre/src/calibre/gui2/custom_column_widgets.py:149 +#: /home/kovid/work/calibre/src/calibre/gui2/custom_column_widgets.py:183 +#: /home/kovid/work/calibre/src/calibre/gui2/custom_column_widgets.py:302 +#: /home/kovid/work/calibre/src/calibre/gui2/custom_column_widgets.py:567 +#: /home/kovid/work/calibre/src/calibre/gui2/custom_column_widgets.py:608 +#: /home/kovid/work/calibre/src/calibre/gui2/custom_column_widgets.py:631 +#: /home/kovid/work/calibre/src/calibre/gui2/custom_column_widgets.py:682 #: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk.py:306 #: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk.py:311 -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single.py:503 -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single.py:504 -#: /home/kovid/work/calibre/src/calibre/gui2/library/delegates.py:114 -#: /home/kovid/work/calibre/src/calibre/gui2/library/delegates.py:134 -#: /home/kovid/work/calibre/src/calibre/gui2/library/delegates.py:216 -#: /home/kovid/work/calibre/src/calibre/gui2/library/delegates.py:249 -#: /home/kovid/work/calibre/src/calibre/gui2/library/delegates.py:253 -#: /home/kovid/work/calibre/src/calibre/gui2/metadata/basic_widgets.py:994 +#: /home/kovid/work/calibre/src/calibre/gui2/library/delegates.py:120 +#: /home/kovid/work/calibre/src/calibre/gui2/library/delegates.py:140 +#: /home/kovid/work/calibre/src/calibre/gui2/library/delegates.py:222 +#: /home/kovid/work/calibre/src/calibre/gui2/library/delegates.py:255 +#: /home/kovid/work/calibre/src/calibre/gui2/library/delegates.py:259 +#: /home/kovid/work/calibre/src/calibre/gui2/metadata/basic_widgets.py:1117 msgid "Undefined" msgstr "Definitu gabea" -#: /home/kovid/work/calibre/src/calibre/gui2/custom_column_widgets.py:127 -#: /home/kovid/work/calibre/src/calibre/gui2/custom_column_widgets.py:630 +#: /home/kovid/work/calibre/src/calibre/gui2/custom_column_widgets.py:126 +#: /home/kovid/work/calibre/src/calibre/gui2/custom_column_widgets.py:639 msgid "star(s)" msgstr "izarra(k)" -#: /home/kovid/work/calibre/src/calibre/gui2/custom_column_widgets.py:128 -#: /home/kovid/work/calibre/src/calibre/gui2/custom_column_widgets.py:631 +#: /home/kovid/work/calibre/src/calibre/gui2/custom_column_widgets.py:127 +#: /home/kovid/work/calibre/src/calibre/gui2/custom_column_widgets.py:640 msgid "Unrated" msgstr "Baloraziorik gabea" -#: /home/kovid/work/calibre/src/calibre/gui2/custom_column_widgets.py:171 -#: /home/kovid/work/calibre/src/calibre/gui2/custom_column_widgets.py:660 +#: /home/kovid/work/calibre/src/calibre/gui2/custom_column_widgets.py:170 +#: /home/kovid/work/calibre/src/calibre/gui2/custom_column_widgets.py:669 msgid "Set '%s' to today" msgstr "Ezarri '%s' gaurko eguna adierazteko" -#: /home/kovid/work/calibre/src/calibre/gui2/custom_column_widgets.py:173 -#: /home/kovid/work/calibre/src/calibre/gui2/custom_column_widgets.py:662 +#: /home/kovid/work/calibre/src/calibre/gui2/custom_column_widgets.py:172 +#: /home/kovid/work/calibre/src/calibre/gui2/custom_column_widgets.py:671 msgid "Clear '%s'" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/custom_column_widgets.py:290 +#: /home/kovid/work/calibre/src/calibre/gui2/custom_column_widgets.py:298 msgid " index:" msgstr " aurkibidea:" -#: /home/kovid/work/calibre/src/calibre/gui2/custom_column_widgets.py:359 +#: /home/kovid/work/calibre/src/calibre/gui2/custom_column_widgets.py:367 msgid "" "The enumeration \"{0}\" contains an invalid value that will be set to the " "default" @@ -7560,23 +7763,23 @@ msgstr "" "\"{0}\" zerrendatzeak badu balio ez duen datu bat eta lehenetsitako baliora " "aldatuko da" -#: /home/kovid/work/calibre/src/calibre/gui2/custom_column_widgets.py:513 +#: /home/kovid/work/calibre/src/calibre/gui2/custom_column_widgets.py:522 msgid "Apply changes" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/custom_column_widgets.py:706 +#: /home/kovid/work/calibre/src/calibre/gui2/custom_column_widgets.py:715 msgid "Remove series" msgstr "Ezabatu serieak" -#: /home/kovid/work/calibre/src/calibre/gui2/custom_column_widgets.py:709 +#: /home/kovid/work/calibre/src/calibre/gui2/custom_column_widgets.py:718 msgid "Automatically number books" msgstr "Liburuen zenbaketa automatikoa" -#: /home/kovid/work/calibre/src/calibre/gui2/custom_column_widgets.py:712 +#: /home/kovid/work/calibre/src/calibre/gui2/custom_column_widgets.py:721 msgid "Force numbers to start with " msgstr "behartu zenbakia honekin hasten " -#: /home/kovid/work/calibre/src/calibre/gui2/custom_column_widgets.py:783 +#: /home/kovid/work/calibre/src/calibre/gui2/custom_column_widgets.py:792 msgid "" "The enumeration \"{0}\" contains invalid values that will not appear in the " "list" @@ -7584,122 +7787,122 @@ msgstr "" "\"{0}\" zerrendatze horrek baditu balio ez duen daturik eta horrelakorik ez " "da zerrendan agertuko" -#: /home/kovid/work/calibre/src/calibre/gui2/custom_column_widgets.py:826 +#: /home/kovid/work/calibre/src/calibre/gui2/custom_column_widgets.py:836 msgid "Remove all tags" msgstr "Ezabatu etiketa guztiak" -#: /home/kovid/work/calibre/src/calibre/gui2/custom_column_widgets.py:846 +#: /home/kovid/work/calibre/src/calibre/gui2/custom_column_widgets.py:856 msgid "tags to add" msgstr "gehitzeko etiketak" -#: /home/kovid/work/calibre/src/calibre/gui2/custom_column_widgets.py:852 +#: /home/kovid/work/calibre/src/calibre/gui2/custom_column_widgets.py:863 msgid "tags to remove" msgstr "ezabatzeko etiketak" -#: /home/kovid/work/calibre/src/calibre/gui2/device.py:45 -#: /home/kovid/work/calibre/src/calibre/utils/ipc/job.py:136 +#: /home/kovid/work/calibre/src/calibre/gui2/device.py:43 +#: /home/kovid/work/calibre/src/calibre/utils/ipc/job.py:144 msgid "No details available." msgstr "Ez dago zehaztasunik eskura." -#: /home/kovid/work/calibre/src/calibre/gui2/device.py:185 +#: /home/kovid/work/calibre/src/calibre/gui2/device.py:168 msgid "Device no longer connected." msgstr "Irakurgailua dagoeneko ez dago konektaturik." -#: /home/kovid/work/calibre/src/calibre/gui2/device.py:303 +#: /home/kovid/work/calibre/src/calibre/gui2/device.py:291 msgid "Get device information" msgstr "Lortu irakurgailutik informazioa" -#: /home/kovid/work/calibre/src/calibre/gui2/device.py:314 +#: /home/kovid/work/calibre/src/calibre/gui2/device.py:305 msgid "Get list of books on device" msgstr "Lortu liburu zerrenda irakurgailutik" -#: /home/kovid/work/calibre/src/calibre/gui2/device.py:324 +#: /home/kovid/work/calibre/src/calibre/gui2/device.py:315 msgid "Get annotations from device" msgstr "Lortu zirriborroak eta oharrak irakurgailutik" -#: /home/kovid/work/calibre/src/calibre/gui2/device.py:336 +#: /home/kovid/work/calibre/src/calibre/gui2/device.py:327 msgid "Send metadata to device" msgstr "Igorri irakurgailura metadatuak" -#: /home/kovid/work/calibre/src/calibre/gui2/device.py:341 +#: /home/kovid/work/calibre/src/calibre/gui2/device.py:332 msgid "Send collections to device" msgstr "Igorri irakurgailura bildumak" -#: /home/kovid/work/calibre/src/calibre/gui2/device.py:376 +#: /home/kovid/work/calibre/src/calibre/gui2/device.py:368 msgid "Upload %d books to device" msgstr "Kargatu %d liburuak irakurgailuan" -#: /home/kovid/work/calibre/src/calibre/gui2/device.py:391 +#: /home/kovid/work/calibre/src/calibre/gui2/device.py:383 msgid "Delete books from device" msgstr "Ezabatu liburuak irakurgailutik" -#: /home/kovid/work/calibre/src/calibre/gui2/device.py:408 +#: /home/kovid/work/calibre/src/calibre/gui2/device.py:400 msgid "Download books from device" msgstr "Deskargatu liburuak irakurgailutik" -#: /home/kovid/work/calibre/src/calibre/gui2/device.py:418 +#: /home/kovid/work/calibre/src/calibre/gui2/device.py:410 msgid "View book on device" msgstr "Ikusi liburua irakurgailuan" -#: /home/kovid/work/calibre/src/calibre/gui2/device.py:452 +#: /home/kovid/work/calibre/src/calibre/gui2/device.py:451 msgid "Set default send to device action" msgstr "Ezarri lehenetsia bezala \"irakurgailura bidali\" ekintza" -#: /home/kovid/work/calibre/src/calibre/gui2/device.py:458 +#: /home/kovid/work/calibre/src/calibre/gui2/device.py:457 msgid "Send to main memory" msgstr "Bidali memoria nagusira" -#: /home/kovid/work/calibre/src/calibre/gui2/device.py:460 +#: /home/kovid/work/calibre/src/calibre/gui2/device.py:459 msgid "Send to storage card A" msgstr "Bidali A memoria-txartelera" -#: /home/kovid/work/calibre/src/calibre/gui2/device.py:462 +#: /home/kovid/work/calibre/src/calibre/gui2/device.py:461 msgid "Send to storage card B" msgstr "Bidali B memoria-txartelera" -#: /home/kovid/work/calibre/src/calibre/gui2/device.py:467 -#: /home/kovid/work/calibre/src/calibre/gui2/device.py:476 +#: /home/kovid/work/calibre/src/calibre/gui2/device.py:466 +#: /home/kovid/work/calibre/src/calibre/gui2/device.py:475 msgid "Main Memory" msgstr "Memoria nagusia" -#: /home/kovid/work/calibre/src/calibre/gui2/device.py:488 +#: /home/kovid/work/calibre/src/calibre/gui2/device.py:487 msgid "Send specific format to" msgstr "Bidali formatu zehatza hona" -#: /home/kovid/work/calibre/src/calibre/gui2/device.py:489 +#: /home/kovid/work/calibre/src/calibre/gui2/device.py:488 msgid "Send and delete from library" msgstr "Bidali eta ezabatu liburutegitik" -#: /home/kovid/work/calibre/src/calibre/gui2/device.py:532 +#: /home/kovid/work/calibre/src/calibre/gui2/device.py:531 msgid "Eject device" msgstr "Egotzi irakurgailua (Eject)" -#: /home/kovid/work/calibre/src/calibre/gui2/device.py:594 -#: /home/kovid/work/calibre/src/calibre/gui2/preferences/misc.py:41 -#: /home/kovid/work/calibre/src/calibre/gui2/preferences/plugins.py:302 +#: /home/kovid/work/calibre/src/calibre/gui2/device.py:611 +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/misc.py:55 +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/plugins.py:313 #: /home/kovid/work/calibre/src/calibre/utils/ipc/job.py:54 msgid "Error" msgstr "Errorea" -#: /home/kovid/work/calibre/src/calibre/gui2/device.py:595 +#: /home/kovid/work/calibre/src/calibre/gui2/device.py:612 msgid "Error communicating with device" msgstr "Irakurgailuarekin komunikatzeko saioak huts egin du" -#: /home/kovid/work/calibre/src/calibre/gui2/device.py:611 -#: /home/kovid/work/calibre/src/calibre/gui2/device.py:1139 -#: /home/kovid/work/calibre/src/calibre/gui2/email.py:298 +#: /home/kovid/work/calibre/src/calibre/gui2/device.py:631 +#: /home/kovid/work/calibre/src/calibre/gui2/device.py:1170 +#: /home/kovid/work/calibre/src/calibre/gui2/email.py:221 msgid "No suitable formats" msgstr "Ez dago formatu egokirik" -#: /home/kovid/work/calibre/src/calibre/gui2/device.py:627 +#: /home/kovid/work/calibre/src/calibre/gui2/device.py:647 msgid "Select folder to open as device" msgstr "Aukeratu karpeta bat irakurgailua izango balitz bezala zabaltzeko" -#: /home/kovid/work/calibre/src/calibre/gui2/device.py:678 +#: /home/kovid/work/calibre/src/calibre/gui2/device.py:698 msgid "Error talking to device" msgstr "Akatsen bat irakurgailuarekin komunikatzerakoan" -#: /home/kovid/work/calibre/src/calibre/gui2/device.py:679 +#: /home/kovid/work/calibre/src/calibre/gui2/device.py:699 msgid "" "There was a temporary error talking to the device. Please unplug and " "reconnect the device and or reboot." @@ -7707,68 +7910,68 @@ msgstr "" "Behin-behineko huts egite bat egon da irakurgailuarekin komunikatzerakoan. " "Mesedez, deskonektatu eta konektatu berriro gailua, edo berrabiarazi." -#: /home/kovid/work/calibre/src/calibre/gui2/device.py:722 +#: /home/kovid/work/calibre/src/calibre/gui2/device.py:742 msgid "Device: " msgstr "Gailua: " -#: /home/kovid/work/calibre/src/calibre/gui2/device.py:724 +#: /home/kovid/work/calibre/src/calibre/gui2/device.py:744 msgid " detected." msgstr " detektaturik." -#: /home/kovid/work/calibre/src/calibre/gui2/device.py:822 +#: /home/kovid/work/calibre/src/calibre/gui2/device.py:846 msgid "selected to send" msgstr "hautatua bidaltzeko" -#: /home/kovid/work/calibre/src/calibre/gui2/device.py:841 +#: /home/kovid/work/calibre/src/calibre/gui2/device.py:865 msgid "%i of %i Books" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/device.py:844 +#: /home/kovid/work/calibre/src/calibre/gui2/device.py:868 msgid "0 of %i Books" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/device.py:845 +#: /home/kovid/work/calibre/src/calibre/gui2/device.py:869 msgid "Choose format to send to device" msgstr "Aukeratu irakurgailura bidaltzeko formatua" -#: /home/kovid/work/calibre/src/calibre/gui2/device.py:853 +#: /home/kovid/work/calibre/src/calibre/gui2/device.py:877 msgid "No device" msgstr "Gailurik ez dago" -#: /home/kovid/work/calibre/src/calibre/gui2/device.py:854 +#: /home/kovid/work/calibre/src/calibre/gui2/device.py:878 msgid "Cannot send: No device is connected" msgstr "Ezin izan da igorri: ez dago inolako gailurik konektatua" -#: /home/kovid/work/calibre/src/calibre/gui2/device.py:857 -#: /home/kovid/work/calibre/src/calibre/gui2/device.py:861 +#: /home/kovid/work/calibre/src/calibre/gui2/device.py:881 +#: /home/kovid/work/calibre/src/calibre/gui2/device.py:885 msgid "No card" msgstr "Txartelik ez dago" -#: /home/kovid/work/calibre/src/calibre/gui2/device.py:858 -#: /home/kovid/work/calibre/src/calibre/gui2/device.py:862 +#: /home/kovid/work/calibre/src/calibre/gui2/device.py:882 +#: /home/kovid/work/calibre/src/calibre/gui2/device.py:886 msgid "Cannot send: Device has no storage card" msgstr "Ezin bidali: gailuak ez dauka memoria-txartelik" -#: /home/kovid/work/calibre/src/calibre/gui2/device.py:918 -#: /home/kovid/work/calibre/src/calibre/gui2/device.py:1001 -#: /home/kovid/work/calibre/src/calibre/gui2/device.py:1133 +#: /home/kovid/work/calibre/src/calibre/gui2/device.py:947 +#: /home/kovid/work/calibre/src/calibre/gui2/device.py:1030 +#: /home/kovid/work/calibre/src/calibre/gui2/device.py:1164 msgid "Auto convert the following books before uploading to the device?" msgstr "" "Bihurtu modu automatikoan hurrengo liburuak irakurgailuan kargatu aurretik?" -#: /home/kovid/work/calibre/src/calibre/gui2/device.py:947 +#: /home/kovid/work/calibre/src/calibre/gui2/device.py:976 msgid "Sending catalogs to device." msgstr "Katalogoak irakurgailura bidaltzen." -#: /home/kovid/work/calibre/src/calibre/gui2/device.py:1046 +#: /home/kovid/work/calibre/src/calibre/gui2/device.py:1077 msgid "Sending news to device." msgstr "Albisteak irakurgailura bidaltzen." -#: /home/kovid/work/calibre/src/calibre/gui2/device.py:1100 +#: /home/kovid/work/calibre/src/calibre/gui2/device.py:1131 msgid "Sending books to device." msgstr "Liburuak irakurgailura bidaltzen." -#: /home/kovid/work/calibre/src/calibre/gui2/device.py:1140 +#: /home/kovid/work/calibre/src/calibre/gui2/device.py:1171 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." @@ -7777,37 +7980,37 @@ msgstr "" "topatu egin ez delako. Lehenengo eta behin, bihurtu liburua(k) zure " "irakurgailuak onartzen duen formaturen batean." -#: /home/kovid/work/calibre/src/calibre/gui2/device.py:1204 +#: /home/kovid/work/calibre/src/calibre/gui2/device.py:1243 msgid "No space on device" msgstr "Lekurik ez irakurgailuan" -#: /home/kovid/work/calibre/src/calibre/gui2/device.py:1205 +#: /home/kovid/work/calibre/src/calibre/gui2/device.py:1244 msgid "" "

      Cannot upload books to device there is no more free space available " msgstr "" "

      Ezin da libururik kargatu irakurgailuan, ez dago leku libre nahikorik eta " -#: /home/kovid/work/calibre/src/calibre/gui2/device_drivers/configwidget.py:124 +#: /home/kovid/work/calibre/src/calibre/gui2/device_drivers/configwidget.py:135 msgid "Unknown formats" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/device_drivers/configwidget.py:125 +#: /home/kovid/work/calibre/src/calibre/gui2/device_drivers/configwidget.py:136 msgid "" "You have enabled the {0} formats for your {1}. The {1} may not " "support them. If you send these formats to your {1} they may not work. Are " "you sure?" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/device_drivers/configwidget.py:137 -#: /home/kovid/work/calibre/src/calibre/gui2/library/delegates.py:403 -#: /home/kovid/work/calibre/src/calibre/gui2/preferences/plugboard.py:255 +#: /home/kovid/work/calibre/src/calibre/gui2/device_drivers/configwidget.py:148 +#: /home/kovid/work/calibre/src/calibre/gui2/library/delegates.py:409 +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/plugboard.py:293 #: /home/kovid/work/calibre/src/calibre/gui2/preferences/save_template.py:61 msgid "Invalid template" msgstr "Baliorik ez duen txantiloia" -#: /home/kovid/work/calibre/src/calibre/gui2/device_drivers/configwidget.py:138 -#: /home/kovid/work/calibre/src/calibre/gui2/library/delegates.py:404 -#: /home/kovid/work/calibre/src/calibre/gui2/preferences/plugboard.py:256 +#: /home/kovid/work/calibre/src/calibre/gui2/device_drivers/configwidget.py:149 +#: /home/kovid/work/calibre/src/calibre/gui2/library/delegates.py:410 +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/plugboard.py:294 #: /home/kovid/work/calibre/src/calibre/gui2/preferences/save_template.py:62 msgid "The template %s is invalid:" msgstr "%s txantiloiak ez du balio:" @@ -7883,7 +8086,7 @@ msgstr "" msgid "&Tags to set on created book entries:" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/book_info_ui.py:80 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/book_info_ui.py:71 msgid "Fit &cover within view" msgstr "Bateratu &liburu-azala bistaratzen denarekin" @@ -7993,50 +8196,62 @@ msgid "" " have no entries in the database. Check the box next to the item you " "want\n" " to delete. Use with caution.

      \n" -"

      Fix marked is applicable only to covers (the two lines " -"marked\n" -" 'fixable'). In the case of missing cover files, checking the " -"fixable\n" -" box and pushing this button will remove the cover mark from the\n" -" database for all the files in that category. In the case of extra\n" -" cover files, checking the fixable box and pushing this button will\n" -" add the cover mark to the database for all the files in that\n" -" category.

      \n" +"\n" +"

      Fix marked is applicable only to covers and missing " +"formats\n" +" (the three lines marked 'fixable'). In the case of missing cover " +"files,\n" +" checking the fixable box and pushing this button will tell calibre " +"that\n" +" there is no cover for all of the books listed. Use this option if " +"you\n" +" are not going to restore the covers from a backup. In the case of " +"extra\n" +" cover files, checking the fixable box and pushing this button will " +"tell\n" +" calibre that the cover files it found are correct for all the books\n" +" listed. Use this when you are not going to delete the file(s). In " +"the\n" +" case of missing formats, checking the fixable box and pushing this\n" +" button will tell calibre that the formats are really gone. Use this " +"if\n" +" you are not going to restore the formats from a backup.

      \n" +"\n" " " msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/check_library.py:220 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/check_library.py:226 msgid "&Run the check again" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/check_library.py:223 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/check_library.py:229 msgid "Copy &to clipboard" msgstr "Kopiatu &arbelean" -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/check_library.py:230 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/check_library.py:236 msgid "Delete marked files (checked subitems)" msgstr "Ezabatu markatutako fitxategiak (berrikusitako batzuk)" -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/check_library.py:236 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/check_library.py:242 msgid "Fix marked sections (checked fixable items)" msgstr "Konpondu markatutako atalak (konpontzeko moduko bilatu diren gauzak)" -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/check_library.py:246 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/check_library.py:252 msgid "Names to ignore:" msgstr "Kontuan hartu behar ez diren izenak:" -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/check_library.py:251 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/check_library.py:257 msgid "" "Enter comma-separated standard file name wildcards, such as synctoy*.dat" msgstr "" "Sar itzazu komen bidez bereiziriko fitxategien joker-karten izen " "estandarrak, synctoy*.dat bezalakoak" -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/check_library.py:254 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/check_library.py:260 msgid "Extensions to ignore" msgstr "Kontuan hartu behar ez diren luzapenak" -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/check_library.py:259 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/check_library.py:265 msgid "" "Enter comma-separated extensions without a leading dot. Used only in book " "folders" @@ -8044,21 +8259,21 @@ msgstr "" "Sar itzazu komen bidez bereiziriko luzapenak gidatze punturik gabe. " "Liburuen karpetetan bakarrik erabilia" -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/check_library.py:308 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/check_library.py:314 msgid "(fixable)" msgstr "(doigarria)" -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/check_library.py:331 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/check_library.py:337 msgid "Path from library" msgstr "Liburutegitiko laster-bidea" -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/check_library.py:331 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/check_library.py:337 #: /home/kovid/work/calibre/src/calibre/gui2/viewer/bookmarkmanager.py:89 -#: /home/kovid/work/calibre/src/calibre/library/server/browse.py:253 +#: /home/kovid/work/calibre/src/calibre/library/server/browse.py:256 msgid "Name" msgstr "Izena" -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/check_library.py:360 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/check_library.py:366 msgid "" "The marked files and folders will be permanently deleted. Are you " "sure?" @@ -8073,7 +8288,7 @@ msgstr "Aukeratu formatua" #: /home/kovid/work/calibre/src/calibre/gui2/dialogs/choose_format_device_ui.py:49 #: /home/kovid/work/calibre/src/calibre/gui2/dialogs/delete_matching_from_device.py:76 -#: /home/kovid/work/calibre/src/calibre/gui2/library/models.py:1169 +#: /home/kovid/work/calibre/src/calibre/gui2/store/mobileread/models.py:23 msgid "Format" msgstr "Formatua" @@ -8169,6 +8384,20 @@ msgstr "" msgid "&Move current library to new location" msgstr "&Mugitu oraingo liburutegia kokapen berri batera" +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/choose_plugin_toolbars.py:23 +msgid "Add \"%s\" to toolbars or menus" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/choose_plugin_toolbars.py:29 +msgid "Select the toolbars and/or menus to add %s to:" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/choose_plugin_toolbars.py:45 +msgid "" +"You can also customise the plugin locations using Preferences -> " +"Customise the toolbar" +msgstr "" + #: /home/kovid/work/calibre/src/calibre/gui2/dialogs/comicconf.py:33 msgid "Set defaults for conversion of comics (CBR/CBZ files)" msgstr "" @@ -8181,12 +8410,13 @@ msgstr "Ezarri aukera bihurtzeko %s" #: /home/kovid/work/calibre/src/calibre/gui2/dialogs/comicconf_ui.py:97 #: /home/kovid/work/calibre/src/calibre/gui2/dialogs/search_ui.py:211 -#: /home/kovid/work/calibre/src/calibre/gui2/metadata/basic_widgets.py:61 +#: /home/kovid/work/calibre/src/calibre/gui2/metadata/basic_widgets.py:73 +#: /home/kovid/work/calibre/src/calibre/gui2/store/search/adv_search_builder_ui.py:189 msgid "&Title:" msgstr "&Izenburua:" #: /home/kovid/work/calibre/src/calibre/gui2/dialogs/comicconf_ui.py:98 -#: /home/kovid/work/calibre/src/calibre/gui2/metadata/basic_widgets.py:155 +#: /home/kovid/work/calibre/src/calibre/gui2/metadata/basic_widgets.py:167 msgid "&Author(s):" msgstr "&Egilea(k):s" @@ -8201,8 +8431,8 @@ msgstr "&Ados" #: /home/kovid/work/calibre/src/calibre/gui2/dialogs/comments_dialog.py:25 #: /home/kovid/work/calibre/src/calibre/gui2/dialogs/template_dialog.py:31 -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/tweak_epub_ui.py:60 -#: /home/kovid/work/calibre/src/calibre/gui2/preferences/main.py:226 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/tweak_epub_ui.py:65 +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/main.py:233 msgid "&Cancel" msgstr "&Bertan behera utzi" @@ -8211,22 +8441,22 @@ msgstr "&Bertan behera utzi" msgid "Edit Comments" msgstr "Iruzkinak editatu" -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/confirm_delete_location_ui.py:62 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/confirm_delete_location_ui.py:76 msgid "Where do you want to delete from?" msgstr "Nondik ezabatu nahi duzu?" -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/confirm_delete_location_ui.py:63 -#: /home/kovid/work/calibre/src/calibre/gui2/layout.py:63 -#: /home/kovid/work/calibre/src/calibre/library/server/opds.py:230 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/confirm_delete_location_ui.py:77 +#: /home/kovid/work/calibre/src/calibre/gui2/layout.py:68 +#: /home/kovid/work/calibre/src/calibre/library/server/opds.py:228 msgid "Library" msgstr "Liburutegia" -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/confirm_delete_location_ui.py:64 -#: /home/kovid/work/calibre/src/calibre/gui2/layout.py:65 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/confirm_delete_location_ui.py:78 +#: /home/kovid/work/calibre/src/calibre/gui2/layout.py:70 msgid "Device" msgstr "Gailua" -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/confirm_delete_location_ui.py:65 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/confirm_delete_location_ui.py:79 msgid "Library and Device" msgstr "Liburutegia eta irakurgailua" @@ -8251,11 +8481,12 @@ msgid "Location" msgstr "Kokalekua" #: /home/kovid/work/calibre/src/calibre/gui2/dialogs/delete_matching_from_device.py:76 -#: /home/kovid/work/calibre/src/calibre/gui2/library/models.py:69 +#: /home/kovid/work/calibre/src/calibre/gui2/library/models.py:67 #: /home/kovid/work/calibre/src/calibre/gui2/library/models.py:979 -#: /home/kovid/work/calibre/src/calibre/gui2/preferences/create_custom_column.py:33 -#: /home/kovid/work/calibre/src/calibre/library/field_metadata.py:295 -#: /home/kovid/work/calibre/src/calibre/library/server/opds.py:589 +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/create_custom_column.py:32 +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/create_custom_column.py:73 +#: /home/kovid/work/calibre/src/calibre/library/field_metadata.py:321 +#: /home/kovid/work/calibre/src/calibre/library/server/opds.py:573 msgid "Date" msgstr "Data" @@ -8271,149 +8502,120 @@ msgstr "" msgid "" "

      This book is locked by DRM. To learn more about DRM and why you " "cannot read or convert this book in calibre, \n" -"click here." +" click " +"here.

      A large number of recent, DRM free releases are \n" +" available at Open " +"Books." msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/edit_authors_dialog.py:35 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/edit_authors_dialog.py:43 msgid "Author sort" msgstr "Egile izenaren araberako sailkapena" -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/edit_authors_dialog.py:117 -#: /home/kovid/work/calibre/src/calibre/gui2/tag_view.py:916 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/edit_authors_dialog.py:104 +#: /home/kovid/work/calibre/src/calibre/gui2/lrf_renderer/main.py:160 +#: /home/kovid/work/calibre/src/calibre/gui2/metadata/single_download.py:471 +#: /home/kovid/work/calibre/src/calibre/gui2/viewer/main.py:503 +msgid "No matches found" +msgstr "Ez da bat datorrenik aurkitu" + +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/edit_authors_dialog.py:120 +#: /home/kovid/work/calibre/src/calibre/gui2/widgets.py:419 +msgid "Change Case" +msgstr "Letra larriak/xeheak giltza aldatu" + +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/edit_authors_dialog.py:121 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk.py:261 +#: /home/kovid/work/calibre/src/calibre/gui2/widgets.py:420 +msgid "Upper Case" +msgstr "Letra larriak" + +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/edit_authors_dialog.py:122 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk.py:260 +#: /home/kovid/work/calibre/src/calibre/gui2/widgets.py:421 +msgid "Lower Case" +msgstr "Letra xeheak" + +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/edit_authors_dialog.py:123 +#: /home/kovid/work/calibre/src/calibre/gui2/widgets.py:422 +msgid "Swap Case" +msgstr "Trukatu leta xehe/larri" + +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/edit_authors_dialog.py:124 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk.py:262 +#: /home/kovid/work/calibre/src/calibre/gui2/widgets.py:423 +msgid "Title Case" +msgstr "Izenburuaren letra mota (xehe/larri)" + +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/edit_authors_dialog.py:125 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk.py:263 +#: /home/kovid/work/calibre/src/calibre/gui2/widgets.py:424 +msgid "Capitalize" +msgstr "Kapitalizatu" + +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/edit_authors_dialog.py:141 +msgid "Copy to author sort" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/edit_authors_dialog.py:144 +msgid "Copy to author" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/edit_authors_dialog.py:271 +#: /home/kovid/work/calibre/src/calibre/gui2/tag_view.py:1439 msgid "Invalid author name" msgstr "Baliogarria ez den egile izena" -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/edit_authors_dialog.py:118 -#: /home/kovid/work/calibre/src/calibre/gui2/tag_view.py:917 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/edit_authors_dialog.py:272 +#: /home/kovid/work/calibre/src/calibre/gui2/tag_view.py:1440 msgid "Author names cannot contain & characters." msgstr "Egile izenek ezin dute & karakterea eduki." -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/edit_authors_dialog_ui.py:71 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/edit_authors_dialog_ui.py:88 +#: /home/kovid/work/calibre/src/calibre/gui2/metadata/single.py:120 msgid "Manage authors" msgstr "Erabili egileen izenak" -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/edit_authors_dialog_ui.py:72 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/edit_authors_dialog_ui.py:89 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:597 +msgid "&Search for:" +msgstr "&Bilatu hau:" + +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/edit_authors_dialog_ui.py:90 +#: /home/kovid/work/calibre/src/calibre/gui2/tag_view.py:2105 +msgid "F&ind" +msgstr "B&ilatu" + +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/edit_authors_dialog_ui.py:91 msgid "Sort by author" msgstr "Egileen izenen arabera sailkatu" -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/edit_authors_dialog_ui.py:73 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/edit_authors_dialog_ui.py:92 msgid "Sort by author sort" msgstr "Egileen sailkapenen arabera sailkatu" -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/edit_authors_dialog_ui.py:74 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/edit_authors_dialog_ui.py:93 msgid "" -"Reset all the author sort values to a value automatically generated from the " -"author. Exactly how this value is automatically generated can be controlled " -"via Preferences->Advanced->Tweaks" +"Reset all the author sort values to a value automatically\n" +"generated from the author. Exactly how this value is automatically\n" +"generated can be controlled via Preferences->Advanced->Tweaks" msgstr "" -"Berrezarri egile sailkapen guztien balioak modu automatikoan egile-izen " -"batetik sortutako balio batera. Zehatz esanda, balio hau nola sor daitekeen " -"modu automatikoan honen bidez kontrola daiteke\r\n" -"Hobespenak->Aurreratua->Azken ukituak" -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/edit_authors_dialog_ui.py:75 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/edit_authors_dialog_ui.py:96 msgid "Recalculate all author sort values" msgstr "Kalkulatu berriro egile guzien sailakapenen balioak" -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/fetch_metadata.py:62 -msgid "Author Sort" -msgstr "Egile izenen araberako sailkapena" - -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/fetch_metadata.py:64 -msgid "ISBN" -msgstr "ISBNa" - -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/fetch_metadata.py:66 -msgid "Has Cover" -msgstr "Badu liburu-azalik" - -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/fetch_metadata.py:67 -msgid "Has Summary" -msgstr "Badu hasierako laburpenik" - -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/fetch_metadata.py:192 -msgid "Finding metadata..." -msgstr "Bilatzen metadatuak..." - -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/fetch_metadata.py:206 -msgid "Could not find metadata" -msgstr "Ezin izan da metadaturik aurkitu" - -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/fetch_metadata.py:207 -msgid "The metadata download seems to have stalled. Try again later." -msgstr "" -"Badirudi metadatuen deskarga luzerako gelditu egin dela. Saia zaitez " -"geroago, mesedez." - -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/fetch_metadata.py:216 -msgid "Warning" -msgstr "Abisua" - -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/fetch_metadata.py:217 -msgid "Could not fetch metadata from:" -msgstr "Ezin izan dira metadatuak hemendik eskuratu:" - -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/fetch_metadata.py:221 -msgid "No metadata found" -msgstr "Ez da metadaturik topatu" - -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/fetch_metadata.py:222 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/edit_authors_dialog_ui.py:97 msgid "" -"No metadata found, try adjusting the title and author and/or removing the " -"ISBN." +"Copy author sort to author for every author. You typically use this button\n" +"after changing Preferences->Advanced->Tweaks->Author sort name algorithm" msgstr "" -"Ez da metadaturik aurkitu, saia zaitez testuaren izenburua eta egilearen " -"izena doitzen edota ISBNa ezabatzen." -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/fetch_metadata_ui.py:93 -msgid "Fetch metadata" -msgstr "Eskuratu metadatuak" - -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/fetch_metadata_ui.py:94 -msgid "" -"

      calibre can find metadata for your books from two locations: Google " -"Books and isbndb.com.

      To use isbndb.com you must sign up for a " -"free account and enter your access key " -"below." +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/edit_authors_dialog_ui.py:99 +msgid "Copy all author sort values to author" msgstr "" -"

      calibrek zure liburuetarako metadatuak bi helbideetatik eskura ditzake: " -"Google Books-etik eta isbndb.com-etik.

      isbndb.com " -"erabiltzeko izena eman beharko duzu, sign up, doaneko kontua izateko eta gero pasahitza " -"sartu beharko duzu." -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/fetch_metadata_ui.py:95 -msgid "&Access Key:" -msgstr "&Pasahitza:" - -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/fetch_metadata_ui.py:96 -msgid "Fetch" -msgstr "Eskuratu" - -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/fetch_metadata_ui.py:97 -msgid "Matches" -msgstr "Bat-etortzeak" - -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/fetch_metadata_ui.py:98 -msgid "" -"Select the book that most closely matches your copy from the list below" -msgstr "" -"Aukera ezazu beheko zerrendatik zure kopiarekin bat egiten duten " -"liburuetatik hurbilena" - -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/fetch_metadata_ui.py:99 -msgid "Overwrite author and title with author and title of selected book" -msgstr "" -"Gainean idatzi egilearen izena eta izenburua aukeratu duzun liburuaren " -"egilearen izenarekin eta izenburuarekin" - -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/fetch_metadata_ui.py:100 -msgid "Download &social metadata (tags/rating/etc.) for the selected book" -msgstr "" -"Deskargatu &gizarte mailako metadatuak (etiketak/balorazioak/eta abar) " -"aukeratu duzun liburuarentzat" - -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/job_view_ui.py:42 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/job_view_ui.py:45 msgid "Details of job" msgstr "Laneko zehaztasunak" @@ -8433,27 +8635,39 @@ msgstr "Erakutsi lana &zehaztasunak" msgid "Stop &all non device jobs" msgstr "Stop, gelditu, &lan guztiak (irakurgailutik kanpokoak)" -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/message_box.py:43 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/message_box.py:49 msgid "&Copy to clipboard" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/message_box.py:48 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/message_box.py:53 msgid "Show &details" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/message_box.py:49 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/message_box.py:54 msgid "Hide &details" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/message_box.py:53 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/message_box.py:58 msgid "Show detailed information about this error" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/message_box.py:93 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/message_box.py:98 #: /home/kovid/work/calibre/src/calibre/gui2/wizard/__init__.py:525 msgid "Copied" msgstr "Kopiaturik" +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/message_box.py:135 +#: /home/kovid/work/calibre/src/calibre/gui2/metadata/single_download.py:770 +#: /home/kovid/work/calibre/src/calibre/gui2/viewer/main_ui.py:205 +msgid "Copy to clipboard" +msgstr "Kopiatu arbelean" + +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/message_box.py:179 +#: /home/kovid/work/calibre/src/calibre/gui2/metadata/single_download.py:831 +#: /home/kovid/work/calibre/src/calibre/gui2/metadata/single_download.py:922 +msgid "View log" +msgstr "" + #: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk.py:58 msgid "Title/Author" msgstr "Izenburua/Egilea" @@ -8463,6 +8677,7 @@ msgid "Standard metadata" msgstr "Metadatu estandarrak" #: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk.py:60 +#: /home/kovid/work/calibre/src/calibre/gui2/metadata/single.py:852 msgid "Custom metadata" msgstr "Metadatu pertsonalizatuak" @@ -8475,26 +8690,6 @@ msgstr "Bilatu/Ordeztu" msgid "Working" msgstr "Lanean" -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk.py:260 -#: /home/kovid/work/calibre/src/calibre/gui2/widgets.py:384 -msgid "Lower Case" -msgstr "Letra xeheak" - -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk.py:261 -#: /home/kovid/work/calibre/src/calibre/gui2/widgets.py:383 -msgid "Upper Case" -msgstr "Letra larriak" - -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk.py:262 -#: /home/kovid/work/calibre/src/calibre/gui2/widgets.py:386 -msgid "Title Case" -msgstr "Izenburuaren letra mota (xehe/larri)" - -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk.py:263 -#: /home/kovid/work/calibre/src/calibre/gui2/widgets.py:387 -msgid "Capitalize" -msgstr "Kapitalizatu" - #: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk.py:266 msgid "Character match" msgstr "Karaktereek bat egin" @@ -8527,11 +8722,15 @@ msgstr "" "Egin itzazu aldaketa guztiak bat-batean elkarrizketa leihoa itxi gabe. " "Eragiketa hau ezin da bertan behera utzi edo atzera bota" -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk.py:381 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk.py:382 msgid "Book %d:" msgstr "Liburua %d:" -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk.py:396 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk.py:400 +msgid "Enter an identifier type" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk.py:405 msgid "" "You can destroy your library using this feature. Changes are " "permanent. There is no undo function. You are strongly encouraged to back up " @@ -8543,7 +8742,7 @@ msgstr "" "babes-kopia egitea zeharo gomendagarria da. Bilatu eta ordeztu eremuak " "testuan adierazpen erregularrak edo irizpide-karaktereak erabiliz. " -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk.py:404 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk.py:413 msgid "" "In character mode, the field is searched for the entered search text. The " "text is replaced by the specified replacement text everywhere it is found in " @@ -8560,7 +8759,7 @@ msgstr "" "bada ondo zehazten, bilaketa testua bai letra larriekin bai letra xeheekin " "egingo da." -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk.py:415 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk.py:424 msgid "" "In regular expression mode, the search text is an arbitrary python-" "compatible regular expression. The replacement text can contain " @@ -8586,38 +8785,42 @@ msgstr "" "erreferentzia python adierazpen arruntei buruz gehiago irakurtzeko eta " "begiratu batez ere 'sub' funtzioa." -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk.py:489 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk.py:502 msgid "S/R TEMPLATE ERROR" msgstr "S/R TXANTILOI ERROREA" -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk.py:616 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk.py:648 msgid "You must specify a destination when source is a composite field" msgstr "Zehaztu beha duzu helburu bat sorburua eremu mistoa denean" -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk.py:715 -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk.py:723 -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk.py:844 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk.py:654 +msgid "You must specify a destination identifier type" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk.py:761 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk.py:780 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk.py:907 msgid "Search/replace invalid" msgstr "Bilatu/ordeztu ez dabil" -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk.py:716 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk.py:762 msgid "" "Authors cannot be set to the empty string. Book title %s not processed" msgstr "" "Egileak ezin dira hutsik dagoen kate batean ezarri. Liburuaren izenburua %s " "ez da prozesatu" -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk.py:724 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk.py:781 msgid "Title cannot be set to the empty string. Book title %s not processed" msgstr "" "Izenburuak ezin dira hutsik dagoen kate batean ezarri. Liburuaren izenburua " "%s ez da prozesatu" -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk.py:845 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk.py:908 msgid "Search pattern is invalid: %s" msgstr "Bilatzeko patroaia ez dabil: %s" -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk.py:897 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk.py:960 msgid "" "Applying changes to %d books.\n" "Phase {0} {1}%%." @@ -8625,47 +8828,47 @@ msgstr "" "Aldaketak ezartzen %d liburuetara.\n" "Fase {0} {1}%%." -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk.py:927 -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:561 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk.py:990 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:587 msgid "Delete saved search/replace" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk.py:928 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk.py:991 msgid "The selected saved search/replace will be deleted. Are you sure?" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk.py:945 -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk.py:953 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk.py:1008 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk.py:1016 msgid "Save search/replace" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk.py:946 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk.py:1009 msgid "Search/replace name:" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk.py:954 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk.py:1017 msgid "" "That saved search/replace already exists and will be overwritten. Are you " "sure?" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:498 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:524 msgid "Edit Meta information" msgstr "Editatu meta informazioa" -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:500 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:526 msgid "A&utomatically set author sort" msgstr "A&utomatikoki ezarri egile izenaren araberako sailkapena" -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:501 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:527 msgid "&Swap title and author" msgstr "&Trukatu haien artean izenburua eta egilearen izena" -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:502 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:528 msgid "Author s&ort: " msgstr "Egile izenaren araberako s&ailkapena: " -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:503 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:529 msgid "" "Specify how the author(s) of this book should be sorted. For example Charles " "Dickens should be sorted as Dickens, Charles." @@ -8673,66 +8876,60 @@ msgstr "" "Zehaztu ea nola sailkatuko d(ir)en liburu honen egile izena(k) Adibidez " "Charles Dickens honela sailkatuko da; Dickens, Charles." -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:504 -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single_ui.py:424 -#: /home/kovid/work/calibre/src/calibre/gui2/metadata/basic_widgets.py:786 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:530 +#: /home/kovid/work/calibre/src/calibre/gui2/metadata/basic_widgets.py:867 msgid "&Rating:" msgstr "&Balorazioa:" -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:505 -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:506 -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single_ui.py:425 -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single_ui.py:426 -#: /home/kovid/work/calibre/src/calibre/gui2/metadata/basic_widgets.py:787 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:531 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:532 +#: /home/kovid/work/calibre/src/calibre/gui2/metadata/basic_widgets.py:868 msgid "Rating of this book. 0-5 stars" msgstr "Liburu honen balorazioa. 0-5 izar" -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:507 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:533 msgid "No change" msgstr "Aldaketarik ez" -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:508 -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single_ui.py:427 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:534 msgid " stars" msgstr " izarrak" -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:510 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:536 msgid "Add ta&gs: " msgstr "Gehitu eti&ketak: " -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:512 -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:513 -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single_ui.py:431 -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single_ui.py:432 -#: /home/kovid/work/calibre/src/calibre/gui2/metadata/single.py:140 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:538 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:539 +#: /home/kovid/work/calibre/src/calibre/gui2/metadata/single.py:166 msgid "Open Tag Editor" msgstr "Zabaldu etiketen editorea" -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:514 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:540 msgid "&Remove tags:" msgstr "&Ezabatu etiketak:" -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:515 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:541 msgid "Comma separated list of tags to remove from the books. " msgstr "Liburuetatik ezabatzeko komen bidez bereiziriko etiketen zerrenda. " -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:516 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:542 msgid "Check this box to remove all tags from the books." msgstr "Aztertu kutxatila hau liburuetako etiketa guztiak ezabatzeko" -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:517 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:543 msgid "Remove &all" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:521 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:547 msgid "If checked, the series will be cleared" msgstr "Arakatuz gero, seriak ezabatu egingo dira" -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:522 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:548 msgid "&Clear series" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:523 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:549 msgid "" "If not checked, the series number for the books will be set to 1.\n" "If checked, selected books will be automatically numbered, in the order\n" @@ -8747,11 +8944,11 @@ msgstr "" "A Liburuak 1 serie zenbakia izango du eta B Liburuak 2 serie zenbakia izango " "du." -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:527 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:553 msgid "&Automatically number books in this series" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:528 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:554 msgid "" "Series will normally be renumbered from the highest number in the database\n" "for that series. Checking this box will tell calibre to start numbering\n" @@ -8763,39 +8960,37 @@ msgstr "" "zenbaketarekin hasteko\n" "kutxan dagoen zenbakitik hasita" -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:531 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:557 msgid "&Force numbers to start with:" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:532 -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single_ui.py:440 -#: /home/kovid/work/calibre/src/calibre/gui2/metadata/basic_widgets.py:978 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:558 +#: /home/kovid/work/calibre/src/calibre/gui2/metadata/basic_widgets.py:1101 msgid "&Date:" msgstr "&Data:" -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:533 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:559 msgid "d MMM yyyy" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:535 -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:540 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:561 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:566 msgid "&Apply date" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:536 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:562 msgid "&Published:" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:538 -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single_ui.py:444 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:564 msgid "Clear published date" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:541 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:567 msgid "Remove &format:" msgstr "Ezabatu &formatua:" -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:542 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:568 msgid "" "Force the title to be in title case. If both this and swap authors are " "checked,\n" @@ -8805,21 +9000,21 @@ msgstr "" "bai egile azterketan\n" "egile eta izenburua aztertuko dira izenburu letra tipoa ezarri baino lehen." -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:544 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:570 msgid "Change title to title &case" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:545 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:571 msgid "" "Update title sort based on the current title. This will be applied only " "after other changes to title." msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:546 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:572 msgid "Update &title sort" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:547 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:573 msgid "" "Remove stored conversion settings for the selected books.\n" "\n" @@ -8830,73 +9025,71 @@ msgstr "" "Etorkizunean liburu hauen bihurketek lehenetsitako ezarpenak erabiliko " "dituzte." -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:550 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:576 msgid "Remove &stored conversion settings for the selected books" msgstr "Ezabatu &gordetako bihurketa ezarpenak hautatutako liburuentzat" -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:551 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:577 msgid "Change &cover" msgstr "Aldatu &liburu-azala" -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:552 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:578 msgid "&Generate default cover" msgstr "&Sortu lehenetsitako liburu-azala" -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:553 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:579 msgid "&Remove cover" msgstr "&Ezabatu liburu-azala" -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:554 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:580 msgid "Set from &ebook file(s)" msgstr "Ezarri &e-liburu(eta)ko fitxategi(eta)tik" -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:555 -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single_ui.py:465 -#: /home/kovid/work/calibre/src/calibre/gui2/metadata/single.py:392 -#: /home/kovid/work/calibre/src/calibre/gui2/metadata/single.py:521 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:581 +#: /home/kovid/work/calibre/src/calibre/gui2/metadata/single.py:495 +#: /home/kovid/work/calibre/src/calibre/gui2/metadata/single.py:659 msgid "&Basic metadata" msgstr "&Oinarrizko metadatuak" -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:556 -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single_ui.py:466 -#: /home/kovid/work/calibre/src/calibre/gui2/metadata/single.py:399 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:582 +#: /home/kovid/work/calibre/src/calibre/gui2/metadata/single.py:502 msgid "&Custom metadata" msgstr "&Pertsonalizaturiko metadatuak" -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:557 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:583 msgid "Load searc&h/replace:" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:558 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:584 msgid "Select saved search/replace to load." msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:559 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:585 msgid "Save current search/replace" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:560 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:586 msgid "Sa&ve" 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:588 #: /home/kovid/work/calibre/src/calibre/gui2/viewer/bookmarkmanager_ui.py:64 msgid "Delete" msgstr "Ezabatu" -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:563 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:589 msgid "Search &field:" msgstr "Search &eremua:" -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:564 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:590 msgid "The name of the field that you want to search" msgstr "Aztertu nahi duzun eremuaren izena" -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:565 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:591 msgid "Search &mode:" msgstr "Bilatze &modua:" -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:566 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:592 msgid "" "Choose whether to use basic text matching or advanced regular expression " "matching" @@ -8904,19 +9097,25 @@ msgstr "" "Aukeratu ea oinarrizko testuaren bilaketa egin edo adierazpen arrunten " "bilaketa aurreratua egin" -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:567 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:593 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:615 +msgid "Identifier type:" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:594 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:616 +msgid "Choose which identifier type to operate upon" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:595 msgid "Te&mplate:" msgstr "Txa&ntiloia:" -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:568 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:596 msgid "Enter a template to be used as the source for the search/replace" msgstr "Sartu bilatu/ordeztu horretarako iturburua izango den txantiloia" -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:569 -msgid "&Search for:" -msgstr "&Bilatu hau:" - -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:570 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:598 msgid "" "Enter the what you are looking for, either plain text or a regular " "expression, depending on the mode" @@ -8924,7 +9123,7 @@ msgstr "" "Sartu bilatzen ari zaren hori, bai testu hutsa bai adierazpen arrunta, " "aukeratu duzun moduaren arabera" -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:571 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:599 msgid "" "Check this box if the search string must match exactly upper and lower case. " "Uncheck it if case is to be ignored" @@ -8933,26 +9132,26 @@ msgstr "" "letra larri eta letra xeheekin. Ez arakatu kutxa honetan baldin eta letra " "tipoari ez badiozu arreta eskaini nahi." -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:572 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:600 msgid "Cas&e sensitive" msgstr "&Letra larri-xehe bereizi" -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:573 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:601 msgid "&Replace with:" msgstr "&Ordeztu honekin:" -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:574 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:602 msgid "" "The replacement text. The matched search text will be replaced with this " "string" msgstr "" "Ordezkatze testua. Bat egiten duen testua kate honekin ordezkatu egingo da" -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:575 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:603 msgid "&Apply function after replace:" msgstr "&Aplikatu funtzioa ordeztu eta gero:" -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:576 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:604 msgid "" "Specify how the text is to be processed after matching and replacement. In " "character mode, the entire\n" @@ -8964,11 +9163,11 @@ msgstr "" "eremu osoa prozesatu egingo da. Adierazpen erregular moduan, bakarrik " "prozesatuko da bilatu eta bat egiten duen testua" -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:578 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:606 msgid "&Destination field:" msgstr "&Helburu eremua:" -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:579 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:607 msgid "" "The field that the text will be put into after all replacements.\n" "If blank, the source field is used if the field is modifiable" @@ -8976,15 +9175,15 @@ msgstr "" "Eremua. Ordezkapen guztien ostean non sartuko den testua jakiteko eremua.\n" "Zuriz utziz gero, sorburuko eremua erabiliko da aldatzeko aukerarik badago." -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:581 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:609 msgid "M&ode:" msgstr "M&odua:" -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:582 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:610 msgid "Specify how the text should be copied into the destination." msgstr "Zehaztu nola kopiatuko den testua helburura." -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:583 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:611 msgid "" "Specifies whether result items should be split into multiple values or\n" "left as single values. This option has the most effect when the source field " @@ -8996,23 +9195,23 @@ msgstr "" "sorburuko eremua\n" "anizkuna ez denean eta helburu eremua anizkuna denean" -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:586 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:614 msgid "Split &result" msgstr "Zatitu e&maitza" -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:587 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:617 msgid "For multiple-valued fields, sho&w" msgstr "Hainbat balio anizkun eremutarako, era&kutsi" -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:588 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:618 msgid "values starting a&t" msgstr "&honela hasten diren balioak" -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:589 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:619 msgid "with values separated b&y" msgstr "honen &bidez bereizitako balioekin" -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:590 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:620 msgid "" "Used when displaying test results to separate values in multiple-valued " "fields" @@ -9020,441 +9219,44 @@ msgstr "" "Erabilia proben emaitzak erakusterakoan, balioak hainbat balio-anizkun " "eremuetan bereizteko" -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:591 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:621 msgid "Test text" msgstr "Probatu testua" -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:592 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:622 msgid "Test result" msgstr "Probaren emaitza" -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:593 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:623 msgid "Your test:" msgstr "Zure testua:" -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:594 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:624 msgid "&Search and replace" msgstr "Bilatu eta ordeztu" -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single.py:98 -#: /home/kovid/work/calibre/src/calibre/gui2/metadata/basic_widgets.py:429 -msgid "Last modified: %s" -msgstr "Azken aldaketa: %s" - -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single.py:122 -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single.py:128 -#: /home/kovid/work/calibre/src/calibre/gui2/metadata/single.py:252 -#: /home/kovid/work/calibre/src/calibre/gui2/metadata/single.py:259 -msgid "Could not read cover" -msgstr "Ezin izan da liburu-azala irakurri" - -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single.py:123 -#: /home/kovid/work/calibre/src/calibre/gui2/metadata/single.py:253 -msgid "Could not read cover from %s format" -msgstr "Ezin izan da %s formatutik irakurri" - -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single.py:129 -#: /home/kovid/work/calibre/src/calibre/gui2/metadata/single.py:260 -msgid "The cover in the %s format is invalid" -msgstr "Liburu-azala %s formatuan ez du balio" - -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single.py:158 -#: /home/kovid/work/calibre/src/calibre/gui2/metadata/basic_widgets.py:746 -msgid "Cover size: %dx%d pixels" -msgstr "Liburu-azalaren tamaina: %dx%d pixels" - -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single.py:195 -#: /home/kovid/work/calibre/src/calibre/gui2/metadata/basic_widgets.py:670 -msgid "Not a valid picture" -msgstr "Ez da irudi baliogarria" - -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single.py:214 -#: /home/kovid/work/calibre/src/calibre/gui2/metadata/basic_widgets.py:697 -msgid "Specify title and author" -msgstr "Zehaztu egilea eta izenburua" - -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single.py:215 -#: /home/kovid/work/calibre/src/calibre/gui2/metadata/basic_widgets.py:698 -msgid "You must specify a title and author before generating a cover" -msgstr "" -"Azala sortu baino lehenago egilea eta izenburua zehaztu beharko dituzu" - -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single.py:246 -msgid "Downloading cover..." -msgstr "Deskargatzen liburu-azala..." - -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single.py:262 -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single.py:267 -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single.py:273 -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single.py:278 -msgid "Cannot fetch cover" -msgstr "Ezin da liburu-azala eskuratu" - -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single.py:263 -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single.py:274 -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single.py:279 -msgid "Could not fetch cover.
      " -msgstr "Ezin izan da liburu-azalik eskuratu.
      " - -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single.py:264 -msgid "The download timed out." -msgstr "Deskargatzeko denbora gainditu egin da." - -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single.py:268 -msgid "Could not find cover for this book. Try specifying the ISBN first." -msgstr "" -"Ezin izan da liburu honetarako liburu-azalik aurkitu. Saia zaitez, lehenengo " -"eta behin, ISBN-arekin." - -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single.py:280 -msgid "" -"For the error message from each cover source, click Show details below." -msgstr "" -"Liburu-azaletako sorburuen errore mezurik balego, egin klik Erakutsi " -"zehaztasunak behean." - -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single.py:287 -msgid "Bad cover" -msgstr "Liburu-azal okerra" - -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single.py:288 -msgid "The cover is not a valid picture" -msgstr "Liburu azala ez da irudi baliogarria" - -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single.py:307 -#: /home/kovid/work/calibre/src/calibre/gui2/metadata/basic_widgets.py:527 -msgid "Choose formats for " -msgstr "Aukeratu formatuak honentzat " - -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single.py:338 -#: /home/kovid/work/calibre/src/calibre/gui2/metadata/basic_widgets.py:559 -msgid "No permission" -msgstr "Baimenik ez" - -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single.py:339 -#: /home/kovid/work/calibre/src/calibre/gui2/metadata/basic_widgets.py:560 -msgid "You do not have permission to read the following files:" -msgstr "Ez daukazu honako fitxategiak irakurtzeko baimenik:" - -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single.py:366 -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single.py:367 -#: /home/kovid/work/calibre/src/calibre/gui2/metadata/basic_widgets.py:591 -#: /home/kovid/work/calibre/src/calibre/gui2/metadata/basic_widgets.py:592 -msgid "No format selected" -msgstr "Formaturik ez da aukeratu" - -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single.py:378 -#: /home/kovid/work/calibre/src/calibre/gui2/metadata/basic_widgets.py:603 -msgid "Could not read metadata" -msgstr "Ezin izan dira metadatuak irakurri" - -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single.py:379 -#: /home/kovid/work/calibre/src/calibre/gui2/metadata/basic_widgets.py:604 -msgid "Could not read metadata from %s format" -msgstr "Ezin izan dira metadatuak irakurri %s formatutik" - -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single.py:453 -#: /home/kovid/work/calibre/src/calibre/gui2/metadata/basic_widgets.py:229 -msgid "" -" The green color indicates that the current author sort matches the current " -"author" -msgstr "" -" Kolore berdeak adierazten du erabiltzen ari garen egile mota honek bat " -"egiten duela erabiltzen ari garen egilearekin" - -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single.py:456 -#: /home/kovid/work/calibre/src/calibre/gui2/metadata/basic_widgets.py:232 -msgid "" -" The red color indicates that the current author sort does not match the " -"current author. No action is required if this is what you want." -msgstr "" -" Gorri koloreak erakusten du oraingo egile klaseak ez duela bat egiten " -"oraingo egilearekin. Hau baldin bada zeuk nahi duzuna, ez duzu ezer egin " -"behar." - -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single.py:463 -#: /home/kovid/work/calibre/src/calibre/gui2/metadata/basic_widgets.py:119 -msgid "" -" The green color indicates that the current title sort matches the current " -"title" -msgstr "" -" Kolore berdeak adierazten du oraingo izenburu mota horrek bat egiten duela " -"oraingo izenburuarekin" - -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single.py:466 -#: /home/kovid/work/calibre/src/calibre/gui2/metadata/basic_widgets.py:122 -msgid "" -" The red color warns that the current title sort does not match the current " -"title. No action is required if this is what you want." -msgstr "" -" Kolore gorriak abisua ematen du esateko oraingo titulu mota horrek ez duela " -"bat egiten oraingo tituluarekin. Horixe bada nahi duzuna, jakina, ez duzu " -"ezer egin behar." - -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single.py:472 -#: /home/kovid/work/calibre/src/calibre/gui2/metadata/single.py:49 -#: /home/kovid/work/calibre/src/calibre/library/server/browse.py:102 -#: /home/kovid/work/calibre/src/calibre/web/feeds/templates.py:221 -#: /home/kovid/work/calibre/src/calibre/web/feeds/templates.py:384 -msgid "Previous" -msgstr "Aurrekoa" - -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single.py:475 -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single.py:483 -#: /home/kovid/work/calibre/src/calibre/gui2/metadata/single.py:358 -#: /home/kovid/work/calibre/src/calibre/gui2/metadata/single.py:362 -msgid "Save changes and edit the metadata of %s" -msgstr "Gorde aldaketak eta editatu %s horren metadatuak" - -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single.py:480 -#: /home/kovid/work/calibre/src/calibre/gui2/metadata/single.py:46 -#: /home/kovid/work/calibre/src/calibre/library/server/browse.py:103 -#: /home/kovid/work/calibre/src/calibre/web/feeds/templates.py:211 -#: /home/kovid/work/calibre/src/calibre/web/feeds/templates.py:401 -msgid "Next" -msgstr "Hurrengoa" - -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single.py:688 -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single.py:693 -#: /home/kovid/work/calibre/src/calibre/gui2/metadata/basic_widgets.py:913 -msgid "This ISBN number is valid" -msgstr "ISBN zenbaki baliogarria" - -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single.py:696 -#: /home/kovid/work/calibre/src/calibre/gui2/metadata/basic_widgets.py:920 -msgid "This ISBN number is invalid" -msgstr "ISBN zenbaki balio gabea" - -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single.py:781 -#: /home/kovid/work/calibre/src/calibre/gui2/metadata/basic_widgets.py:862 -msgid "Tags changed" -msgstr "Aldatu diren etiketak" - -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single.py:782 -#: /home/kovid/work/calibre/src/calibre/gui2/metadata/basic_widgets.py:863 -msgid "" -"You have changed the tags. In order to use the tags editor, you must either " -"discard or apply these changes. Apply changes?" -msgstr "" - -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single.py:817 -msgid "Timed out" -msgstr "" - -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single.py:818 -msgid "" -"The download of social metadata timed out, the servers are probably busy. " -"Try again later." -msgstr "" - -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single.py:825 -msgid "There were errors" -msgstr "Akatsak egon dira" - -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single.py:826 -msgid "There were errors downloading social metadata" -msgstr "Akatsak egon dira gizarte mailako metadatuak deskargatzerakoan" - -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single.py:860 -msgid "Cannot fetch metadata" -msgstr "Ezin dira metadatuak eskuratu" - -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single.py:861 -msgid "You must specify at least one of ISBN, Title, Authors or Publisher" -msgstr "" -"Gutxienez hauetako bat zehaztu beharko duzu: ISBNa, izenburua, egilea(k) " -"edo argitaletxea" - -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single.py:959 -#: /home/kovid/work/calibre/src/calibre/gui2/metadata/single.py:307 -msgid "Permission denied" -msgstr "Ez zaizu baimenik eman" - -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single.py:960 -#: /home/kovid/work/calibre/src/calibre/gui2/metadata/single.py:308 -msgid "Could not open %s. Is it being used by another program?" -msgstr "Ezin izan da %s zabaldu. Beste programa bat erabiltzen ari?" - -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single_ui.py:406 -msgid "Edit Meta Information" -msgstr "Editatu meta informazioa" - -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single_ui.py:407 -msgid "Meta information" -msgstr "Meta informazioa" - -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single_ui.py:410 -#: /home/kovid/work/calibre/src/calibre/gui2/metadata/single.py:89 -msgid "" -"Automatically create the title sort entry based on the current title entry.\n" -"Using this button to create title sort will change title sort from red to " -"green." -msgstr "" -"Sortu automatikoki izenburuen klasea izango den sarrera bat oraingo izenburu " -"sarreran oinarrituta.\n" -"Botoi hau erabiliz gero izenburuen klasea sortzeko, izenburuaren klasearen " -"kolorea aldatuko da berdetik gorrira." - -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single_ui.py:413 -#: /home/kovid/work/calibre/src/calibre/gui2/metadata/single.py:111 -msgid "Swap the author and title" -msgstr "Trukatu haien artean zenburua eta egilearen izena" - -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single_ui.py:415 -#: /home/kovid/work/calibre/src/calibre/gui2/metadata/single.py:100 -msgid "" -"Automatically create the author sort entry based on the current author " -"entry.\n" -"Using this button to create author sort will change author sort from red to " -"green." -msgstr "" -"Sortu modu automatikoan egile motaren sarrera orain erabiltzen ari garen " -"egile izenean oinarriturik.\n" -"Egile mota berria sortzeko botoi hau erabiliz gero, egile mota gorritik " -"berdera aldatzea lortuko dugu." - -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single_ui.py:418 -msgid "Title &sort: " -msgstr "Izenburu &mota: " - -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single_ui.py:419 -#: /home/kovid/work/calibre/src/calibre/gui2/metadata/basic_widgets.py:109 -msgid "" -"Specify how this book should be sorted when by title. For example, The " -"Exorcist might be sorted as Exorcist, The." -msgstr "" -"Zehaztu hola sailkatu beharko litzateke liburu hau izenburuaren arabera. " -"Esate baterako, El Quijote horrela sailka daiteke: Quijote, El." - -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single_ui.py:421 -msgid "Author S&ort: " -msgstr "Egile izenaren araberako S&ailkapena: " - -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single_ui.py:422 -#: /home/kovid/work/calibre/src/calibre/gui2/metadata/basic_widgets.py:215 -msgid "" -"Specify how the author(s) of this book should be sorted. For example Charles " -"Dickens should be sorted as Dickens, Charles.\n" -"If the box is colored green, then text matches the individual author's sort " -"strings. If it is colored red, then the authors and this text do not match." -msgstr "" -"Zehaztu ea nola ordenatuko d(ir)en liburu honen egilea(k). Esate baterako, " -"Charles Dickens horrela sailka daiteke: Dickens, Charles.\n" -"Kutxatila berde baldin badago, orduan testuak bat egiten du egile izen mota " -"batekin. Kutxatila gorri baldin badago, orduan egileak edo egileek eta " -"testuek ez dute bat egin." - -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single_ui.py:436 -#: /home/kovid/work/calibre/src/calibre/gui2/metadata/single.py:118 -msgid "Remove unused series (Series that have no books)" -msgstr "Ezabatu erabiltzen ez diren serieak (libururik ez daukaten serieak)" - -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single_ui.py:439 -#: /home/kovid/work/calibre/src/calibre/gui2/metadata/basic_widgets.py:886 -msgid "IS&BN:" -msgstr "IS&BNa:" - -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single_ui.py:441 -msgid "dd MMM yyyy" -msgstr "dd MMM yyyy (AEBetako ohitura)" - -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single_ui.py:442 -#: /home/kovid/work/calibre/src/calibre/gui2/metadata/basic_widgets.py:1029 -msgid "Publishe&d:" -msgstr "Argitaratu&rik:" - -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single_ui.py:445 -#: /home/kovid/work/calibre/src/calibre/gui2/metadata/single.py:156 -msgid "&Fetch metadata from server" -msgstr "&Eskuratu metadatuak zerbitzaritik" - -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single_ui.py:448 -#: /home/kovid/work/calibre/src/calibre/gui2/metadata/basic_widgets.py:621 -msgid "&Browse" -msgstr "&Arakatu" - -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single_ui.py:449 -msgid "Remove border (if any) from cover" -msgstr "Ezabatu ertza (ertzik baldin badago) liburu-azaletik" - -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single_ui.py:450 -#: /home/kovid/work/calibre/src/calibre/gui2/metadata/basic_widgets.py:623 -msgid "T&rim" -msgstr "M&oztu" - -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single_ui.py:451 -msgid "Reset cover to default" -msgstr "Berrezarri lehenetsitako liburu-azala" - -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single_ui.py:452 -#: /home/kovid/work/calibre/src/calibre/gui2/metadata/basic_widgets.py:625 -msgid "&Remove" -msgstr "&Kendu" - -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single_ui.py:453 -#: /home/kovid/work/calibre/src/calibre/gui2/metadata/basic_widgets.py:631 -msgid "Download co&ver" -msgstr "Deskargatu liburu&azala" - -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single_ui.py:454 -msgid "Generate a default cover based on the title and author" -msgstr "" -"Sortu lehenetsitako liburu-azal oinarrizko bat egilearen izenarekin eta " -"izenbuarekin" - -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single_ui.py:455 -#: /home/kovid/work/calibre/src/calibre/gui2/metadata/basic_widgets.py:632 -msgid "&Generate cover" -msgstr "&Sortu liburu-azala" - -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single_ui.py:456 -msgid "Available Formats" -msgstr "Eskuragarri dauden formatuak" - -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single_ui.py:457 -msgid "Add a new format for this book to the database" -msgstr "Gehi ezazu datu basera liburu honetarako formatu berri bat" - -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single_ui.py:459 -msgid "Remove the selected formats for this book from the database." -msgstr "Ezaba ezazu datu basetik liburu honetarako hautatutako formatuak" - -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single_ui.py:461 -#: /home/kovid/work/calibre/src/calibre/gui2/metadata/basic_widgets.py:446 -msgid "Set the cover for the book from the selected format" -msgstr "Ezarri ezazu liburu honen azala hautatutako formatutik" - -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single_ui.py:463 -msgid "Update metadata from the metadata in the selected format" -msgstr "Egunera itzazu metadatuak hautatutako formatuaren metadatuetatik" - -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single_ui.py:464 -#: /home/kovid/work/calibre/src/calibre/gui2/metadata/single.py:580 -msgid "&Comments" -msgstr "&Iruzkinak" - #: /home/kovid/work/calibre/src/calibre/gui2/dialogs/password_ui.py:61 msgid "Password needed" msgstr "Pasahitza beharko" #: /home/kovid/work/calibre/src/calibre/gui2/dialogs/password_ui.py:63 -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/scheduler_ui.py:217 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/scheduler_ui.py:205 #: /home/kovid/work/calibre/src/calibre/gui2/preferences/server_ui.py:125 #: /home/kovid/work/calibre/src/calibre/gui2/wizard/send_email_ui.py:133 msgid "&Username:" msgstr "&Erabiltzaile-izena:" #: /home/kovid/work/calibre/src/calibre/gui2/dialogs/password_ui.py:64 -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/scheduler_ui.py:218 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/scheduler_ui.py:206 #: /home/kovid/work/calibre/src/calibre/gui2/preferences/server_ui.py:126 #: /home/kovid/work/calibre/src/calibre/gui2/wizard/send_email_ui.py:135 msgid "&Password:" msgstr "&Pasahitza:" #: /home/kovid/work/calibre/src/calibre/gui2/dialogs/password_ui.py:65 -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/scheduler_ui.py:219 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/scheduler_ui.py:207 #: /home/kovid/work/calibre/src/calibre/gui2/preferences/server_ui.py:130 -#: /home/kovid/work/calibre/src/calibre/gui2/wizard/send_email.py:172 +#: /home/kovid/work/calibre/src/calibre/gui2/wizard/send_email.py:173 msgid "&Show password" msgstr "&Erakutsi pasahitza" @@ -9497,218 +9299,299 @@ msgstr "" msgid "Restoring database was successful" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/saved_search_editor.py:55 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/saved_search_editor.py:48 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/saved_search_editor.py:75 +msgid "Saved search already exists" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/saved_search_editor.py:49 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/saved_search_editor.py:76 +msgid "The saved search %s already exists, perhaps with different case" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/saved_search_editor.py:62 msgid "" "The current saved search will be permanently deleted. Are you sure?" msgstr "" "Orain gordetako bilaketa betiko ezabatu egingo da. Ziur zaude?" -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/saved_search_editor_ui.py:88 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/saved_search_editor_ui.py:94 msgid "Saved Search Editor" msgstr "Gorde egin da bilaketa editorea" -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/saved_search_editor_ui.py:89 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/saved_search_editor_ui.py:95 msgid "Saved Search: " -msgstr "Bilaketa gordeta: " +msgstr "Gordetako bilaketa: " -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/saved_search_editor_ui.py:90 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/saved_search_editor_ui.py:96 msgid "Select a saved search to edit" msgstr "Hautatu gordetako bilaketa bat editatzeko" -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/saved_search_editor_ui.py:91 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/saved_search_editor_ui.py:97 msgid "Delete this selected saved search" msgstr "Ezabatu hautatu egin den gordetako bilaketa hau" -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/saved_search_editor_ui.py:93 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/saved_search_editor_ui.py:99 msgid "Enter a new saved search name." msgstr "Sartu gordetako bilaketa izen berri bat." -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/saved_search_editor_ui.py:94 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/saved_search_editor_ui.py:100 msgid "Add the new saved search" msgstr "Gehitu gordetako bilaketa berria" -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/saved_search_editor_ui.py:96 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/saved_search_editor_ui.py:102 +msgid "Rename the current search to what is in the box" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/saved_search_editor_ui.py:104 msgid "Change the contents of the saved search" msgstr "Aldatu gordetako bilaketaren edukia" -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/scheduler.py:35 -msgid "&Search:" -msgstr "&Bilatu:" +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/scheduler.py:42 +msgid "" +" Download this periodical every week on the specified days " +"after\n" +" the specified time. For example, if you choose: Monday " +"after\n" +" 9:00 AM, then the periodical will be download every Monday " +"as\n" +" soon after 9:00 AM as possible.\n" +" " +msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/scheduler.py:133 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/scheduler.py:61 +msgid "&Download after:" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/scheduler.py:91 +msgid "" +" Download this periodical every month, on the specified " +"days.\n" +" The download will happen as soon after the specified time " +"as\n" +" possible on the specified days of each month. For example,\n" +" if you choose the 1st and the 15th after 9:00 AM, the\n" +" periodical will be downloaded on the 1st and 15th of every\n" +" month, as soon after 9:00 AM as possible.\n" +" " +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/scheduler.py:103 +msgid "&Days of the month:" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/scheduler.py:105 +msgid "Comma separated list of days of the month. For example: 1, 15" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/scheduler.py:109 +msgid "Download &after:" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/scheduler.py:142 +msgid "" +" Download this periodical every x days. For example, if you\n" +" choose 30 days, the periodical will be downloaded every 30\n" +" days. Note that you can set periods of less than a day, " +"like\n" +" 0.1 days to download a periodical more than once a day.\n" +" " +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/scheduler.py:151 +msgid "&Download every:" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/scheduler.py:154 +msgid "every hour" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/scheduler.py:157 +msgid "days" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/scheduler.py:161 +msgid "" +"Note: You can set intervals of less than a day, by typing the value manually." +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/scheduler.py:196 +msgid "%s news sources" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/scheduler.py:311 msgid "Need username and password" msgstr "Erabiltzaile izena eta pasahitza beharko" -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/scheduler.py:134 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/scheduler.py:312 msgid "You must provide a username and/or password to use this news source." msgstr "" "Eman beharko duzu erabiltzaile izena edota pasahitza albiste iturri hau " "erabiltzeko" -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/scheduler.py:173 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/scheduler.py:346 msgid "Account" msgstr "Kontua" -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/scheduler.py:174 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/scheduler.py:347 msgid "(optional)" msgstr "(hautazkoa)" -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/scheduler.py:175 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/scheduler.py:348 msgid "(required)" msgstr "(beharrezkoa)" -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/scheduler.py:192 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/scheduler.py:365 msgid "Created by: " msgstr "Sortzailea: " -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/scheduler.py:199 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/scheduler.py:372 msgid "Last downloaded: never" msgstr "Azken deskarga: inoiz ez" -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/scheduler.py:214 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/scheduler.py:373 +msgid "never" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/scheduler.py:379 msgid "%d days, %d hours and %d minutes ago" msgstr "duela %d egun, %d ordu eta %d minutu" -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/scheduler.py:216 -msgid "Last downloaded" -msgstr "Azken deskarga" +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/scheduler.py:393 +msgid "Last downloaded:" +msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/scheduler.py:240 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/scheduler.py:421 #: /home/kovid/work/calibre/src/calibre/gui2/dialogs/scheduler_ui.py:197 msgid "Schedule news download" msgstr "Albisteen deskargaren planifikazioa" -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/scheduler.py:243 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/scheduler.py:424 msgid "Add a custom news source" msgstr "Gehitu pertsonalizatutako albiste iturri berri bat" -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/scheduler.py:248 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/scheduler.py:429 msgid "Download all scheduled new sources" msgstr "Deskargatu planifikatutako albiste iturri guztiak" -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/scheduler.py:353 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/scheduler.py:534 msgid "No internet connection" msgstr "Ez dago internet konexiorik" -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/scheduler.py:354 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/scheduler.py:535 msgid "Cannot download news as no internet connection is active" msgstr "Ezin da albisterik deskargatu interneteko konexioa ez baitabil" #: /home/kovid/work/calibre/src/calibre/gui2/dialogs/scheduler_ui.py:198 -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/user_profiles.py:321 -msgid "Recipes" -msgstr "Formulak edo errezetak" +msgid "&Search:" +msgstr "&Bilatu:" #: /home/kovid/work/calibre/src/calibre/gui2/dialogs/scheduler_ui.py:199 -msgid "Download all scheduled recipes at once" -msgstr "Deskargatu planifikazio formula guztiak aldi berean" - -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/scheduler_ui.py:200 -msgid "Download &all scheduled" -msgstr "Deskargatu planifikatutako &dena" - -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/scheduler_ui.py:201 msgid "blurb" msgstr "publizitate ohartxoa" -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/scheduler_ui.py:202 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/scheduler_ui.py:200 msgid "&Schedule for download:" msgstr "&Planifikatu deskargatzeko:" +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/scheduler_ui.py:201 +msgid "Days of week" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/scheduler_ui.py:202 +msgid "Days of month" +msgstr "" + #: /home/kovid/work/calibre/src/calibre/gui2/dialogs/scheduler_ui.py:203 -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/scheduler_ui.py:213 -msgid "Every " -msgstr "Noizero " +msgid "Every x days" +msgstr "" #: /home/kovid/work/calibre/src/calibre/gui2/dialogs/scheduler_ui.py:204 -msgid "day" -msgstr "eguna" - -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/scheduler_ui.py:205 -msgid "Monday" -msgstr "astelehena" - -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/scheduler_ui.py:206 -msgid "Tuesday" -msgstr "asteartea" - -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/scheduler_ui.py:207 -msgid "Wednesday" -msgstr "asteazkena" - -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/scheduler_ui.py:208 -msgid "Thursday" -msgstr "osteguna" - -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/scheduler_ui.py:209 -msgid "Friday" -msgstr "ostirala" - -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/scheduler_ui.py:210 -msgid "Saturday" -msgstr "larunbata" - -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/scheduler_ui.py:211 -msgid "Sunday" -msgstr "igandea" - -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/scheduler_ui.py:212 -msgid "at" -msgstr "ordua:" - -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/scheduler_ui.py:214 -msgid "" -"Interval at which to download this recipe. A value of zero means that the " -"recipe will be downloaded every hour." -msgstr "" -"Formula hau zein tartetan deskargatu. Zero balioak esan nahi du formula edo " -"errezeta orduro deskargatu egingo dela." - -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/scheduler_ui.py:215 -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/scheduler_ui.py:227 -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/user_profiles_ui.py:268 -msgid " days" -msgstr " egunak" - -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/scheduler_ui.py:216 msgid "&Account" msgstr "&Kontua" -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/scheduler_ui.py:220 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/scheduler_ui.py:208 msgid "For the scheduling to work, you must leave calibre running." msgstr "Lana planifikatzeko, calibre lanean utzi beharko duzu." -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/scheduler_ui.py:221 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/scheduler_ui.py:209 msgid "&Schedule" msgstr "&Planifikazioa" -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/scheduler_ui.py:222 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/scheduler_ui.py:210 msgid "Add &title as tag" msgstr "Gehitu &izenburua etiketa bezala" -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/scheduler_ui.py:223 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/scheduler_ui.py:211 msgid "&Extra tags:" msgstr "&Estra etiketak:" -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/scheduler_ui.py:224 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/scheduler_ui.py:212 +msgid "" +"Maximum number of copies (issues) of this recipe to keep. Set to 0 to keep " +"all (disable)." +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/scheduler_ui.py:213 +msgid "&Keep at most:" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/scheduler_ui.py:214 +msgid "" +"

      When set, this option will cause calibre to keep, at most, the specified " +"number of issues of this periodical. Every time a new issue is downloaded, " +"the oldest one is deleted, if the total is larger than this number.\n" +"

      Note that this feature only works if you have the option to add the title " +"as tag checked, above.\n" +"

      Also, the setting for deleting periodicals older than a number of days, " +"below, takes priority over this setting." +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/scheduler_ui.py:217 +msgid "all issues" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/scheduler_ui.py:218 +msgid " issues" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/scheduler_ui.py:219 msgid "&Advanced" msgstr "&Aurreratua" -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/scheduler_ui.py:225 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/scheduler_ui.py:220 msgid "&Download now" msgstr "&Deskargatu orain" -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/scheduler_ui.py:226 -msgid "" -"Delete downloaded news older than the specified number of days. Set to zero " -"to disable." +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/scheduler_ui.py:221 +msgid "&Delete downloaded news older than:" msgstr "" -"Ezabatu aurretik zehaztu den egun kopuru batetik gora deskargatu diren " -"berriak. Ezarri zero desgaitzeko." -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/scheduler_ui.py:228 -msgid "Delete downloaded news older than " -msgstr "Ezabatu hau baino zaharragoak diren albisteak " +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/scheduler_ui.py:222 +msgid "" +"

      Delete downloaded news older than the specified number of days. Set to " +"zero to disable.\n" +"

      You can also control the maximum number of issues of a specific " +"periodical that are kept by clicking the Advanced tab for that periodical " +"above." +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/scheduler_ui.py:224 +msgid "never delete" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/scheduler_ui.py:225 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/user_profiles_ui.py:273 +msgid " days" +msgstr " egunak" + +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/scheduler_ui.py:226 +msgid "Download all scheduled news sources at once" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/scheduler_ui.py:227 +msgid "Download &all scheduled" +msgstr "Deskargatu planifikatutako &dena" #: /home/kovid/work/calibre/src/calibre/gui2/dialogs/search_item_ui.py:41 msgid "contains" @@ -9731,23 +9614,28 @@ msgid "Negate" msgstr "Ezeztatu" #: /home/kovid/work/calibre/src/calibre/gui2/dialogs/search_ui.py:198 +#: /home/kovid/work/calibre/src/calibre/gui2/store/search/adv_search_builder_ui.py:176 msgid "Advanced Search" msgstr "Bilaketa aurreratua" #: /home/kovid/work/calibre/src/calibre/gui2/dialogs/search_ui.py:199 +#: /home/kovid/work/calibre/src/calibre/gui2/store/search/adv_search_builder_ui.py:177 msgid "&What kind of match to use:" msgstr "&Zein parekotasun erabili:" #: /home/kovid/work/calibre/src/calibre/gui2/dialogs/search_ui.py:200 +#: /home/kovid/work/calibre/src/calibre/gui2/store/search/adv_search_builder_ui.py:178 msgid "Contains: the word or phrase matches anywhere in the metadata field" msgstr "" "Badauka: hitza edo esaldia metadatu eremuko edozein tokitan bat dator" #: /home/kovid/work/calibre/src/calibre/gui2/dialogs/search_ui.py:201 +#: /home/kovid/work/calibre/src/calibre/gui2/store/search/adv_search_builder_ui.py:179 msgid "Equals: the word or phrase must match the entire metadata field" msgstr "Berdinak: hitza edo esaldia metadatu eremu osoan bat etorri behar du" #: /home/kovid/work/calibre/src/calibre/gui2/dialogs/search_ui.py:202 +#: /home/kovid/work/calibre/src/calibre/gui2/store/search/adv_search_builder_ui.py:180 msgid "" "Regular expression: the expression must match anywhere in the metadata field" msgstr "" @@ -9755,30 +9643,37 @@ msgstr "" "etorri behar" #: /home/kovid/work/calibre/src/calibre/gui2/dialogs/search_ui.py:203 +#: /home/kovid/work/calibre/src/calibre/gui2/store/search/adv_search_builder_ui.py:181 msgid "Find entries that have..." msgstr "Bilatu hauxe duten sarrerak..." #: /home/kovid/work/calibre/src/calibre/gui2/dialogs/search_ui.py:204 +#: /home/kovid/work/calibre/src/calibre/gui2/store/search/adv_search_builder_ui.py:182 msgid "&All these words:" msgstr "Hitz &hauek guztiak:" #: /home/kovid/work/calibre/src/calibre/gui2/dialogs/search_ui.py:205 +#: /home/kovid/work/calibre/src/calibre/gui2/store/search/adv_search_builder_ui.py:183 msgid "This exact &phrase:" msgstr "Zehatz eta mehatz &esaldi hau:" #: /home/kovid/work/calibre/src/calibre/gui2/dialogs/search_ui.py:206 +#: /home/kovid/work/calibre/src/calibre/gui2/store/search/adv_search_builder_ui.py:184 msgid "&One or more of these words:" msgstr "&Bat edo gehiago hitz hauetatik:" #: /home/kovid/work/calibre/src/calibre/gui2/dialogs/search_ui.py:207 +#: /home/kovid/work/calibre/src/calibre/gui2/store/search/adv_search_builder_ui.py:185 msgid "But dont show entries that have..." msgstr "Baina ez erakutsi honako hauxe daukaten sarrerak..." #: /home/kovid/work/calibre/src/calibre/gui2/dialogs/search_ui.py:208 +#: /home/kovid/work/calibre/src/calibre/gui2/store/search/adv_search_builder_ui.py:186 msgid "Any of these &unwanted words:" msgstr "&nahi-ez-den hitz hauetako bat:" #: /home/kovid/work/calibre/src/calibre/gui2/dialogs/search_ui.py:209 +#: /home/kovid/work/calibre/src/calibre/gui2/store/search/adv_search_builder_ui.py:187 msgid "" "See the User Manual for more help" @@ -9787,19 +9682,22 @@ msgstr "" "interface\">Erabiltzailearen eskuliburua laguntza gehiagorako" #: /home/kovid/work/calibre/src/calibre/gui2/dialogs/search_ui.py:210 +#: /home/kovid/work/calibre/src/calibre/gui2/store/search/adv_search_builder_ui.py:188 msgid "A&dvanced Search" msgstr "Bilaketa Aurreratua" #: /home/kovid/work/calibre/src/calibre/gui2/dialogs/search_ui.py:212 +#: /home/kovid/work/calibre/src/calibre/gui2/store/search/adv_search_builder_ui.py:190 msgid "Enter the title." msgstr "Idatzi izenburua." #: /home/kovid/work/calibre/src/calibre/gui2/dialogs/search_ui.py:213 +#: /home/kovid/work/calibre/src/calibre/gui2/store/search/adv_search_builder_ui.py:191 msgid "&Author:" msgstr "Egile&a:" #: /home/kovid/work/calibre/src/calibre/gui2/dialogs/search_ui.py:215 -#: /home/kovid/work/calibre/src/calibre/gui2/metadata/basic_widgets.py:827 +#: /home/kovid/work/calibre/src/calibre/gui2/metadata/basic_widgets.py:908 msgid "Ta&gs:" msgstr "Etiketak:" @@ -9816,14 +9714,16 @@ msgstr "" #: /home/kovid/work/calibre/src/calibre/gui2/dialogs/search_ui.py:218 msgid "Enter tags separated by spaces" -msgstr "Sartu etiketak Zuriuneekin bereizturik" +msgstr "Sartu zuriuneekin bereizitako etiketak" #: /home/kovid/work/calibre/src/calibre/gui2/dialogs/search_ui.py:219 #: /home/kovid/work/calibre/src/calibre/gui2/preferences/template_functions_ui.py:101 +#: /home/kovid/work/calibre/src/calibre/gui2/store/search/adv_search_builder_ui.py:193 msgid "&Clear" -msgstr "Garbitu" +msgstr "&Garbitu" #: /home/kovid/work/calibre/src/calibre/gui2/dialogs/search_ui.py:220 +#: /home/kovid/work/calibre/src/calibre/gui2/store/search/adv_search_builder_ui.py:194 msgid "Search only in specific fields:" msgstr "Bilatu bakarrik adierazitako eremuetan:" @@ -9836,31 +9736,48 @@ msgid "Choose formats" msgstr "Hautatu formatuak" #: /home/kovid/work/calibre/src/calibre/gui2/dialogs/tag_categories.py:60 -#: /home/kovid/work/calibre/src/calibre/library/field_metadata.py:98 +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/metadata_sources.py:146 +#: /home/kovid/work/calibre/src/calibre/library/field_metadata.py:102 msgid "Authors" msgstr "Egileak" #: /home/kovid/work/calibre/src/calibre/gui2/dialogs/tag_categories.py:60 -#: /home/kovid/work/calibre/src/calibre/library/field_metadata.py:129 +#: /home/kovid/work/calibre/src/calibre/library/field_metadata.py:136 msgid "Publishers" msgstr "Argitaletxeak" -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/tag_categories.py:128 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/tag_categories.py:143 msgid " (not on any book)" msgstr " (inongo liburutan ez)" -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/tag_categories.py:175 -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/tag_categories.py:197 -#: /home/kovid/work/calibre/src/calibre/gui2/preferences/template_functions.py:151 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/tag_categories.py:146 +msgid "Category lookup name: " +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/tag_categories.py:191 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/tag_categories.py:222 +msgid "Invalid name" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/tag_categories.py:192 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/tag_categories.py:223 +msgid "" +"That name contains leading or trailing periods, multiple periods in a row or " +"spaces before or after periods." +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/tag_categories.py:200 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/tag_categories.py:230 +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/template_functions.py:152 msgid "Name already used" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/tag_categories.py:176 -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/tag_categories.py:198 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/tag_categories.py:201 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/tag_categories.py:231 msgid "That name is already used, perhaps with different case." msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/tag_categories.py:211 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/tag_categories.py:244 msgid "" "The current tag category will be permanently deleted. Are you sure?" msgstr "Oraingo kategoria hau betiko ezabatu egingo da. Ziur zaude?" @@ -9918,7 +9835,7 @@ msgid "Unapply (remove) tag from current tag category" msgstr "Ez erantsi (ezabatu) etiketa oraingo etiketen kategoriatik" #: /home/kovid/work/calibre/src/calibre/gui2/dialogs/tag_editor.py:70 -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/tag_list_editor.py:109 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/tag_list_editor.py:111 msgid "Are your sure?" msgstr "Ziur zaude?" @@ -9980,35 +9897,35 @@ msgstr "" msgid "%s (was %s)" msgstr "%s (%s izan da)" -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/tag_list_editor.py:83 -#: /home/kovid/work/calibre/src/calibre/gui2/tag_view.py:906 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/tag_list_editor.py:85 +#: /home/kovid/work/calibre/src/calibre/gui2/tag_view.py:1385 msgid "Item is blank" msgstr "Elementua zuriz dago" -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/tag_list_editor.py:84 -#: /home/kovid/work/calibre/src/calibre/gui2/tag_view.py:907 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/tag_list_editor.py:86 +#: /home/kovid/work/calibre/src/calibre/gui2/tag_view.py:1386 msgid "An item cannot be set to nothing. Delete it instead." msgstr "Elementu bat ezin zaio ezerezari gehitu. Horren ordez, ezaba ezazu." -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/tag_list_editor.py:97 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/tag_list_editor.py:99 msgid "No item selected" msgstr "Ez dago ezer hautatuta" -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/tag_list_editor.py:98 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/tag_list_editor.py:100 msgid "You must select one item from the list of Available items." msgstr "" "Hautatu beharko duzu elementu bat eskuragarri daudenenen elementuen " "zerrendatik." -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/tag_list_editor.py:105 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/tag_list_editor.py:107 msgid "No items selected" msgstr "Ez dago elementurik hautatuta" -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/tag_list_editor.py:106 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/tag_list_editor.py:108 msgid "You must select at least one items from the list." msgstr "Gutxienez zerrendako elementu bat hautatu beharko duzu." -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/tag_list_editor.py:110 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/tag_list_editor.py:112 msgid "Are you certain you want to delete the following items?" msgstr "" "Erabat ziur zaude? Benetan ezabatu nahi dituzu honako elementu hauek?" @@ -10060,29 +9977,17 @@ msgid "Send test mail from %s to:" msgstr "Igorri aztertzeko e-posta bat hemendik %s horra:" #: /home/kovid/work/calibre/src/calibre/gui2/dialogs/test_email_ui.py:58 -#: /home/kovid/work/calibre/src/calibre/gui2/filename_pattern_ui.py:120 +#: /home/kovid/work/calibre/src/calibre/gui2/filename_pattern_ui.py:134 msgid "&Test" msgstr "&Aztertu" -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/tweak_epub_ui.py:55 -msgid "Display contents of exploded ePub" -msgstr "Erakutsi suntsitutako ePub horren edukiak" +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/tweak_epub.py:100 +msgid "Cannot preview" +msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/tweak_epub_ui.py:56 -msgid "&Explode ePub" -msgstr "&Suntsitu ePub" - -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/tweak_epub_ui.py:57 -msgid "Rebuild ePub from exploded contents" -msgstr "Berreraiki ePub suntsitutako edukietatik" - -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/tweak_epub_ui.py:58 -msgid "&Rebuild ePub" -msgstr "&Berreraiki ePub" - -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/tweak_epub_ui.py:59 -msgid "Discard changes" -msgstr "Baztertu aldaketak" +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/tweak_epub.py:101 +msgid "You must first explode the epub before previewing." +msgstr "" #: /home/kovid/work/calibre/src/calibre/gui2/dialogs/tweak_epub_ui.py:61 msgid "" @@ -10099,118 +10004,150 @@ msgstr "" "dituzun editore leihoak ere bai.

      Berreraiki ePub fitxategia zure " "calibre liburutegia eguneratuz.

      " -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/user_profiles.py:133 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/tweak_epub_ui.py:62 +msgid "Display contents of exploded ePub" +msgstr "Erakutsi suntsitutako ePub horren edukiak" + +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/tweak_epub_ui.py:63 +msgid "&Explode ePub" +msgstr "&Suntsitu ePub" + +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/tweak_epub_ui.py:64 +msgid "Discard changes" +msgstr "Baztertu aldaketak" + +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/tweak_epub_ui.py:66 +msgid "Rebuild ePub from exploded contents" +msgstr "Berreraiki ePub suntsitutako edukietatik" + +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/tweak_epub_ui.py:67 +msgid "&Rebuild ePub" +msgstr "&Berreraiki ePub" + +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/tweak_epub_ui.py:68 +msgid "&Preview ePub" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/user_profiles.py:141 msgid "No recipe selected" msgstr "Ez da formula edo errezetarik hautatu" -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/user_profiles.py:138 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/user_profiles.py:146 msgid "The attached file: %s is a recipe to download %s." msgstr "Erantsitako fitxategia: %s hori %s deskargatzeko formula da." -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/user_profiles.py:139 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/user_profiles.py:147 msgid "Recipe for " msgstr "Honetarako formula " -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/user_profiles.py:156 -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/user_profiles.py:167 -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/user_profiles_ui.py:260 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/user_profiles.py:164 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/user_profiles.py:175 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/user_profiles_ui.py:265 msgid "Switch to Advanced mode" msgstr "Aldatu modu aurreratura" -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/user_profiles.py:162 #: /home/kovid/work/calibre/src/calibre/gui2/dialogs/user_profiles.py:170 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/user_profiles.py:178 msgid "Switch to Basic mode" msgstr "Aldatu modu oinarrizkora" -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/user_profiles.py:180 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/user_profiles.py:188 msgid "Feed must have a title" msgstr "Jario horrek izenburu bat beharko du" -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/user_profiles.py:181 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/user_profiles.py:189 msgid "The feed must have a title" msgstr "Jario horrek izenburu bat beharko du" -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/user_profiles.py:185 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/user_profiles.py:193 msgid "Feed must have a URL" msgstr "Jario horrek URL bat beharko du" -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/user_profiles.py:186 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/user_profiles.py:194 msgid "The feed %s must have a URL" msgstr "%s jario horrek URL bat beharko du" -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/user_profiles.py:192 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/user_profiles.py:200 msgid "This feed has already been added to the recipe" msgstr "Jario hori formulara gehitu egin da dagoeneko" -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/user_profiles.py:233 -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/user_profiles.py:242 -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/user_profiles.py:329 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/user_profiles.py:241 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/user_profiles.py:250 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/user_profiles.py:337 msgid "Invalid input" msgstr "Sarrera baliogabea" -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/user_profiles.py:234 -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/user_profiles.py:243 -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/user_profiles.py:330 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/user_profiles.py:242 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/user_profiles.py:251 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/user_profiles.py:338 msgid "

      Could not create recipe. Error:
      %s" msgstr "

      Ezin izan da formula bat sortu. Errorea:
      %s" -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/user_profiles.py:247 -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/user_profiles.py:306 -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/user_profiles.py:333 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/user_profiles.py:255 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/user_profiles.py:314 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/user_profiles.py:341 msgid "Replace recipe?" msgstr "Ordeztu formula?" -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/user_profiles.py:248 -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/user_profiles.py:307 -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/user_profiles.py:334 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/user_profiles.py:256 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/user_profiles.py:315 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/user_profiles.py:342 msgid "A custom recipe named %s already exists. Do you want to replace it?" msgstr "" "Pertsonalizatutako formula bat %s izenarekin egon badago dagoeneko. Ordeztu " "nahi duzu?" -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/user_profiles.py:274 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/user_profiles.py:282 msgid "Choose builtin recipe" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/user_profiles.py:320 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/user_profiles.py:328 msgid "Choose a recipe file" msgstr "Aukeratu formula-fitxategi bat" -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/user_profiles.py:361 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/user_profiles.py:329 +msgid "Recipes" +msgstr "Formulak edo errezetak" + +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/user_profiles.py:369 msgid "" "You will lose any unsaved changes. To save your changes, click the " "Add/Update recipe button. Continue?" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/user_profiles_ui.py:253 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/user_profiles_ui.py:257 msgid "Add custom news source" msgstr "Gehitu albiste iturri berria" -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/user_profiles_ui.py:254 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/user_profiles_ui.py:258 msgid "Available user recipes" msgstr "Erabiltzaile formulak erabilgarri" -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/user_profiles_ui.py:255 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/user_profiles_ui.py:259 msgid "Add/Update &recipe" msgstr "Gehitu/Eguneratu &formula" -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/user_profiles_ui.py:256 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/user_profiles_ui.py:260 msgid "&Remove recipe" msgstr "&Ezabatu formula" -#: /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:261 msgid "&Share recipe" msgstr "&Partekatu formula" -#: /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:262 +msgid "S&how recipe files" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/user_profiles_ui.py:263 msgid "Customize &builtin recipe" msgstr "Pertsonalizatu &builtin formula" -#: /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:264 msgid "&Load recipe from file" msgstr "&Kargatu formula fitxategitik" -#: /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:266 msgid "" "\n" +"

      Create a basic news " +"recipe, by adding RSS feeds to it.
      For most feeds, you will have to " +"use the \"Advanced mode\" to further customize the fetch " +"process.

      " +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/user_profiles_ui.py:270 +msgid "Recipe &title:" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/user_profiles_ui.py:271 +msgid "&Oldest article:" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/user_profiles_ui.py:272 +msgid "The oldest article to download" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/user_profiles_ui.py:274 +msgid "&Max. number of articles per feed:" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/user_profiles_ui.py:275 +msgid "Maximum number of articles to download per feed." +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/user_profiles_ui.py:276 +msgid "Feeds in recipe" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/user_profiles_ui.py:278 +msgid "Remove feed from recipe" +msgstr "" + +#: /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 "" + +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/user_profiles_ui.py:282 +msgid "&Feed title:" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/user_profiles_ui.py:283 +msgid "Feed &URL:" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/user_profiles_ui.py:285 +msgid "&Add feed" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/user_profiles_ui.py:286 +msgid "" +"For help with writing advanced news recipes, please visit User Recipes" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/user_profiles_ui.py:287 +msgid "Recipe source code (python)" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/dnd.py:51 +msgid "Download %s" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/dnd.py:54 +msgid "Downloading %s from %s" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/dnd.py:85 +msgid "Failed to download from %r with error: %s" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/ebook_download.py:41 +msgid "No file specified to download." +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/ebook_download.py:66 +msgid "Not a support ebook format." +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/ebook_download.py:87 +msgid "Downloading %s" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/ebook_download.py:99 +msgid "Downloading" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/ebook_download.py:103 +msgid "Failed to download ebook" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/email.py:91 +msgid "Email %s to %s" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/email.py:110 +msgid "News:" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/email.py:112 +msgid "Attached is the %s periodical downloaded by calibre." +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/email.py:160 +msgid "E-book:" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/email.py:168 +msgid "Attached, you will find the e-book" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/email.py:169 +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/plugins.py:189 +msgid "by" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/email.py:170 +msgid "in the %s format." +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/email.py:184 +msgid "Sending email to" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/email.py:215 +msgid "Auto convert the following books before sending via email?" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/email.py:222 +msgid "" +"Could not email the following books as no suitable formats were found:" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/email.py:228 +msgid "Failed to email book" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/email.py:231 +msgid "sent" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/email.py:254 +msgid "Sent news to" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/filename_pattern_ui.py:129 +msgid "" +"
      \n" +"

      Set a regular expression pattern to use when trying to guess ebook " +"metadata from filenames.

      \n" +"

      A tutorial on using regular " +"expressions is available.

      \n" +"

      Use the Test functionality below to test your regular expression " +"on a few sample filenames (remember to include the file extension). The " +"group names for the various metadata entries are documented in " +"tooltips.

      " +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/filename_pattern_ui.py:133 +msgid "Regular &expression" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/filename_pattern_ui.py:135 +msgid "File &name:" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/filename_pattern_ui.py:137 +msgid "Title:" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/filename_pattern_ui.py:138 +msgid "Regular expression (?P<title>)" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/filename_pattern_ui.py:139 +#: /home/kovid/work/calibre/src/calibre/gui2/filename_pattern_ui.py:142 +#: /home/kovid/work/calibre/src/calibre/gui2/filename_pattern_ui.py:145 +#: /home/kovid/work/calibre/src/calibre/gui2/filename_pattern_ui.py:148 +#: /home/kovid/work/calibre/src/calibre/gui2/filename_pattern_ui.py:151 +#: /home/kovid/work/calibre/src/calibre/gui2/filename_pattern_ui.py:154 +#: /home/kovid/work/calibre/src/calibre/gui2/filename_pattern_ui.py:157 +#: /home/kovid/work/calibre/src/calibre/gui2/widgets.py:108 +#: /home/kovid/work/calibre/src/calibre/gui2/widgets.py:112 +#: /home/kovid/work/calibre/src/calibre/gui2/widgets.py:117 +#: /home/kovid/work/calibre/src/calibre/gui2/widgets.py:122 +#: /home/kovid/work/calibre/src/calibre/gui2/widgets.py:130 +msgid "No match" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/filename_pattern_ui.py:140 +msgid "Authors:" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/filename_pattern_ui.py:141 +msgid "Regular expression (?P)" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/filename_pattern_ui.py:143 +msgid "Series:" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/filename_pattern_ui.py:144 +msgid "Regular expression (?P)" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/filename_pattern_ui.py:146 +msgid "Series index:" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/filename_pattern_ui.py:147 +msgid "Regular expression (?P)" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/filename_pattern_ui.py:149 +msgid "ISBN:" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/filename_pattern_ui.py:150 +msgid "Regular expression (?P)" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/filename_pattern_ui.py:152 +msgid "Publisher:" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/filename_pattern_ui.py:153 +msgid "Regular expression (?P)" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/filename_pattern_ui.py:155 +msgid "Published:" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/filename_pattern_ui.py:156 +msgid "Regular expression (?P)" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/init.py:105 +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/look_feel_ui.py:237 +msgid "Cover Browser" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/init.py:110 +msgid "Shift+Alt+B" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/init.py:124 +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/look_feel_ui.py:234 +msgid "Tag Browser" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/init.py:126 +msgid "Shift+Alt+T" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/init.py:157 +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/main.py:29 +msgid "version" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/init.py:158 +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/main.py:30 +msgid "created by Kovid Goyal" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/init.py:175 +msgid "Connected " +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/init.py:188 +msgid "Update found" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/init.py:223 +#: /home/kovid/work/calibre/src/calibre/gui2/init.py:233 +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/look_feel_ui.py:216 +msgid "Book Details" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/init.py:225 +msgid "Alt+D" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/init.py:235 +msgid "Shift+Alt+D" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/jobs.py:62 +msgid "Job" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/jobs.py:63 +msgid "Status" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/jobs.py:64 +msgid "Progress" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/jobs.py:65 +msgid "Running time" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/jobs.py:77 +msgid "There are %d running jobs:" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/jobs.py:81 +#: /home/kovid/work/calibre/src/calibre/gui2/jobs.py:88 +#: /home/kovid/work/calibre/src/calibre/gui2/jobs.py:103 +msgid "Unknown job" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/jobs.py:84 +msgid "There are %d waiting jobs:" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/jobs.py:240 +#: /home/kovid/work/calibre/src/calibre/gui2/jobs.py:243 +#: /home/kovid/work/calibre/src/calibre/gui2/jobs.py:246 +msgid "Cannot kill job" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/jobs.py:241 +msgid "Cannot kill jobs that communicate with the device" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/jobs.py:244 +msgid "Job has already run" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/jobs.py:247 +msgid "This job cannot be stopped" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/jobs.py:283 +msgid "Unavailable" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/jobs.py:327 +msgid "Jobs:" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/jobs.py:329 +msgid "Shift+Alt+J" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/jobs.py:346 +msgid "Click to see list of jobs" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/jobs.py:415 +msgid " - Jobs" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/jobs.py:457 +msgid "Do you really want to stop the selected job?" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/jobs.py:463 +msgid "Do you really want to stop all non-device jobs?" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/layout.py:57 +msgid "Eject this device" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/layout.py:69 +msgid "Show books in calibre library" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/layout.py:71 +msgid "Show books in the main memory of the device" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/layout.py:72 +#: /home/kovid/work/calibre/src/calibre/library/database2.py:1019 +msgid "Card A" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/layout.py:73 +msgid "Show books in storage card A" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/layout.py:74 +#: /home/kovid/work/calibre/src/calibre/library/database2.py:1021 +msgid "Card B" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/layout.py:75 +msgid "Show books in storage card B" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/layout.py:140 +msgid "available" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/layout.py:181 +msgid "Shift+Ctrl+F" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/layout.py:184 +msgid "Advanced search" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/layout.py:189 +msgid "" +"

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

      Words separated by spaces are ANDed" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/layout.py:195 +msgid "&Go!" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/layout.py:201 +msgid "Do Quick Search (you can also press the Enter key)" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/layout.py:207 +msgid "Reset Quick Search" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/layout.py:223 +msgid "Copy current search text (instead of search name)" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/library/delegates.py:361 +msgid "Y" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/library/delegates.py:396 +msgid "Edit template" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/library/models.py:64 +#: /home/kovid/work/calibre/src/calibre/library/field_metadata.py:251 +msgid "On Device" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/library/models.py:66 +msgid "Size (MB)" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/library/models.py:73 +#: /home/kovid/work/calibre/src/calibre/library/field_metadata.py:241 +msgid "Modified" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/library/models.py:720 +#: /home/kovid/work/calibre/src/calibre/gui2/library/models.py:1277 +#: /home/kovid/work/calibre/src/calibre/gui2/tag_view.py:797 +msgid "The lookup/search name is \"{0}\"" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/library/models.py:726 +#: /home/kovid/work/calibre/src/calibre/gui2/library/models.py:1279 +msgid "This book's UUID is \"{0}\"" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/library/models.py:976 +msgid "In Library" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/library/models.py:980 +#: /home/kovid/work/calibre/src/calibre/library/field_metadata.py:311 +msgid "Size" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/library/models.py:1257 +msgid "Marked for deletion" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/library/models.py:1260 +msgid "Double click to edit me

      " +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/library/views.py:158 +msgid "Hide column %s" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/library/views.py:163 +msgid "Sort on %s" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/library/views.py:164 +msgid "Ascending" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/library/views.py:167 +msgid "Descending" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/library/views.py:179 +msgid "Change text alignment for %s" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/library/views.py:181 +msgid "Left" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/library/views.py:181 +msgid "Right" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/library/views.py:182 +msgid "Center" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/library/views.py:201 +msgid "Show column" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/library/views.py:213 +msgid "Restore default layout" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/library/views.py:855 +msgid "" +"Dropping onto a device is not supported. First add the book to the calibre " +"library." +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/lrf_renderer/config_ui.py:52 +msgid "Configure Viewer" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/lrf_renderer/config_ui.py:53 +msgid "Use white background" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/lrf_renderer/config_ui.py:54 +msgid "Hyphenate" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/lrf_renderer/config_ui.py:55 +msgid "Changes will only take effect after a restart." +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/lrf_renderer/main.py:70 +msgid " - LRF Viewer" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/lrf_renderer/main.py:160 +msgid "No matches for the search phrase %s were found." +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/lrf_renderer/main_ui.py:128 +msgid "LRF Viewer" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/lrf_renderer/main_ui.py:129 +msgid "Parsing LRF file" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/lrf_renderer/main_ui.py:130 +msgid "LRF Viewer toolbar" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/lrf_renderer/main_ui.py:131 +#: /home/kovid/work/calibre/src/calibre/gui2/viewer/documentview.py:559 +msgid "Next Page" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/lrf_renderer/main_ui.py:132 +#: /home/kovid/work/calibre/src/calibre/gui2/viewer/documentview.py:560 +msgid "Previous Page" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/lrf_renderer/main_ui.py:133 +#: /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:193 +msgid "Back" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/lrf_renderer/main_ui.py:134 +#: /home/kovid/work/calibre/src/calibre/gui2/viewer/main_ui.py:194 +msgid "Forward" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/lrf_renderer/main_ui.py:135 +msgid "Next match" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/lrf_renderer/main_ui.py:136 +#: /home/kovid/work/calibre/src/calibre/gui2/viewer/main_ui.py:201 +msgid "Open ebook" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/lrf_renderer/main_ui.py:137 +msgid "Configure" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/main.py:33 +msgid "Use the library located at the specified path." +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/main.py:35 +msgid "Start minimized to system tray." +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/main.py:37 +msgid "Log debugging information to console" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/main.py:39 +msgid "Do not check for updates" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/main.py:41 +msgid "" +"Ignore custom plugins, useful if you installed a plugin that is preventing " +"calibre from starting" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/main.py:45 +msgid "" +"Cause a running calibre instance, if any, to be shutdown. Note that if there " +"are running jobs, they will be silently aborted, so use with care." +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/main.py:69 +#: /home/kovid/work/calibre/src/calibre/gui2/wizard/__init__.py:678 +msgid "Calibre Library" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/main.py:96 +msgid "Choose a location for your calibre e-book library" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/main.py:105 +msgid "Failed to create library" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/main.py:106 +msgid "Failed to create calibre library at: %r." +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/main.py:109 +#: /home/kovid/work/calibre/src/calibre/gui2/main.py:195 +msgid "Choose a location for your new calibre e-book library" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/main.py:164 +msgid "Initializing user interface..." +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/main.py:189 +msgid "Repairing failed" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/main.py:190 +msgid "The database repair failed. Starting with a new empty library." +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/main.py:204 +#: /home/kovid/work/calibre/src/calibre/gui2/main.py:236 +msgid "Bad database location" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/main.py:205 +msgid "Bad database location %r. calibre will now quit." +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/main.py:217 +msgid "Corrupted database" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/main.py:218 +msgid "" +"Your calibre database appears to be corrupted. Do you want calibre to try " +"and repair it automatically? If you say No, a new empty calibre library will " +"be created." +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/main.py:224 +msgid "" +"Repairing database. This can take a very long time for a large collection" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/main.py:237 +msgid "" +"Bad database location %r. Will start with a new, empty calibre library" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/main.py:247 +msgid "Starting %s: Loading books..." +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/main.py:327 +msgid "If you are sure it is not running" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/main.py:330 +msgid "may be running in the system tray, in the" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/main.py:332 +msgid "upper right region of the screen." +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/main.py:334 +msgid "lower right region of the screen." +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/main.py:337 +msgid "try rebooting your computer." +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/main.py:339 +#: /home/kovid/work/calibre/src/calibre/gui2/main.py:353 +msgid "try deleting the file" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/main.py:342 +msgid "Cannot Start " +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/main.py:343 +msgid "%s is already running." +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/main_window.py:25 +msgid "" +"Redirect console output to a dialog window (both stdout and stderr). Useful " +"on windows where GUI apps do not have a output streams." +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/main_window.py:113 +msgid "&Preferences" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/main_window.py:114 +msgid "&Quit" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/main_window.py:138 +msgid "Unhandled exception" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/metadata/basic_widgets.py:121 +msgid "" +"Specify how this book should be sorted when by title. For example, The " +"Exorcist might be sorted as Exorcist, The." +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/metadata/basic_widgets.py:123 +msgid "Title &sort:" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/metadata/basic_widgets.py:131 +msgid "" +" The green color indicates that the current title sort matches the current " +"title" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/metadata/basic_widgets.py:134 +msgid "" +" The red color warns that the current title sort does not match the current " +"title. No action is required if this is what you want." +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/metadata/basic_widgets.py:181 +msgid "Authors changed" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/metadata/basic_widgets.py:182 +msgid "" +"You have changed the authors for this book. You must save these changes " +"before you can use Manage authors. Do you want to save these changes?" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/metadata/basic_widgets.py:252 +msgid "" +"Specify how the author(s) of this book should be sorted. For example Charles " +"Dickens should be sorted as Dickens, Charles.\n" +"If the box is colored green, then text matches the individual author's sort " +"strings. If it is colored red, then the authors and this text do not match." +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/metadata/basic_widgets.py:257 +msgid "Author s&ort:" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/metadata/basic_widgets.py:267 +msgid "" +" The green color indicates that the current author sort matches the current " +"author" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/metadata/basic_widgets.py:270 +msgid "" +" The red color indicates that the current author sort does not match the " +"current author. No action is required if this is what you want." +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/metadata/basic_widgets.py:411 +msgid "&Number:" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/metadata/basic_widgets.py:492 +msgid "" +"Last modified: %s\n" +"\n" +"Double click to view" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/metadata/basic_widgets.py:509 +msgid "Set the cover for the book from the selected format" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/metadata/basic_widgets.py:517 +msgid "Set metadata for the book from the selected format" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/metadata/basic_widgets.py:524 +msgid "Add a format to this book" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/metadata/basic_widgets.py:531 +msgid "Remove the selected format from this book" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/metadata/basic_widgets.py:597 +msgid "Choose formats for " +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/metadata/basic_widgets.py:629 +msgid "No permission" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/metadata/basic_widgets.py:630 +msgid "You do not have permission to read the following files:" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/metadata/basic_widgets.py:660 +#: /home/kovid/work/calibre/src/calibre/gui2/metadata/basic_widgets.py:661 +msgid "No format selected" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/metadata/basic_widgets.py:672 +msgid "Could not read metadata" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/metadata/basic_widgets.py:673 +msgid "Could not read metadata from %s format" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/metadata/basic_widgets.py:692 +msgid "&Browse" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/metadata/basic_widgets.py:694 +msgid "T&rim" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/metadata/basic_widgets.py:696 +msgid "&Remove" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/metadata/basic_widgets.py:702 +msgid "Download co&ver" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/metadata/basic_widgets.py:703 +msgid "&Generate cover" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/metadata/basic_widgets.py:753 +msgid "Not a valid picture" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/metadata/basic_widgets.py:777 +msgid "Specify title and author" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/metadata/basic_widgets.py:778 +msgid "You must specify a title and author before generating a cover" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/metadata/basic_widgets.py:796 +msgid "Invalid cover" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/metadata/basic_widgets.py:797 +msgid "Could not change cover as the image is invalid." +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/metadata/basic_widgets.py:824 +msgid "This book has no cover" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/metadata/basic_widgets.py:826 +msgid "Cover size: %dx%d pixels" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/metadata/basic_widgets.py:875 +msgid "stars" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/metadata/basic_widgets.py:944 +msgid "Tags changed" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/metadata/basic_widgets.py:945 +msgid "" +"You have changed the tags. In order to use the tags editor, you must either " +"discard or apply these changes. Apply changes?" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/metadata/basic_widgets.py:971 +msgid "I&ds:" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/metadata/basic_widgets.py:972 +msgid "" +"Edit the identifiers for this book. For example: \n" +"\n" +"%s" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/metadata/basic_widgets.py:1033 +msgid "This ISBN number is valid" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/metadata/basic_widgets.py:1036 +msgid "This ISBN number is invalid" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/metadata/basic_widgets.py:1050 +msgid "&Publisher:" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/metadata/basic_widgets.py:1120 +msgid "Clear date" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/metadata/basic_widgets.py:1152 +msgid "Publishe&d:" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/metadata/bulk_download.py:34 +msgid "Schedule download?" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/metadata/bulk_download.py:45 +msgid "" +"The download of metadata for the %d selected book(s) will run in the " +"background. Proceed?" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/metadata/bulk_download.py:47 +msgid "" +"You can monitor the progress of the download by clicking the rotating " +"spinner in the bottom right corner." +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/metadata/bulk_download.py:50 +msgid "" +"When the download completes you will be asked for confirmation before " +"calibre applies the downloaded metadata." +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/metadata/bulk_download.py:61 +msgid "Download only &metadata" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/metadata/bulk_download.py:65 +msgid "Download only &covers" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/metadata/bulk_download.py:69 +msgid "&Configure download" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/metadata/bulk_download.py:73 +msgid "Download &both" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/metadata/bulk_download.py:99 +msgid "Download metadata for %d books" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/metadata/bulk_download.py:102 +msgid "Metadata download started" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/metadata/bulk_download.py:111 +msgid "(Failed metadata)" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/metadata/bulk_download.py:113 +msgid "(Failed cover)" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/metadata/bulk_download.py:190 +msgid "Downloaded %d of %d" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/metadata/config.py:61 +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/metadata_sources_ui.py:115 +msgid "Downloaded metadata fields" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/metadata/single.py:51 +#: /home/kovid/work/calibre/src/calibre/gui2/metadata/single_download.py:824 +#: /home/kovid/work/calibre/src/calibre/library/server/browse.py:107 +#: /home/kovid/work/calibre/src/calibre/web/feeds/templates.py:211 +#: /home/kovid/work/calibre/src/calibre/web/feeds/templates.py:401 +msgid "Next" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/metadata/single.py:55 +#: /home/kovid/work/calibre/src/calibre/library/server/browse.py:106 +#: /home/kovid/work/calibre/src/calibre/web/feeds/templates.py:221 +#: /home/kovid/work/calibre/src/calibre/web/feeds/templates.py:384 +msgid "Previous" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/metadata/single.py:80 +#: /home/kovid/work/calibre/src/calibre/gui2/metadata/single.py:265 +msgid "Edit Metadata" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/metadata/single.py:101 +msgid "" +"Automatically create the title sort entry based on the current title entry.\n" +"Using this button to create title sort will change title sort from red to " +"green." +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/metadata/single.py:112 +msgid "" +"Automatically create the author sort entry based on the current author " +"entry. Using this button to create author sort will change author sort from " +"red to green. There is a menu of functions available under this button. " +"Click and hold on the button to see it." +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/metadata/single.py:118 +msgid "Set author sort from author" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/metadata/single.py:119 +msgid "Set author from author sort" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/metadata/single.py:129 +msgid "Swap the author and title" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/metadata/single.py:135 +msgid "" +"Manage authors. Use to rename authors and correct individual author's sort " +"values" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/metadata/single.py:143 +msgid "Remove unused series (Series that have no books)" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/metadata/single.py:178 +msgid "" +"Paste the contents of the clipboard into the identifiers box prefixed with " +"isbn:" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/metadata/single.py:191 +msgid "&Download metadata" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/metadata/single.py:202 +msgid "Configure download metadata" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/metadata/single.py:206 +msgid "Change how calibre downloads metadata" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/metadata/single.py:306 +#: /home/kovid/work/calibre/src/calibre/gui2/metadata/single.py:313 +msgid "Could not read cover" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/metadata/single.py:307 +msgid "Could not read cover from %s format" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/metadata/single.py:314 +msgid "The cover in the %s format is invalid" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/metadata/single.py:397 +msgid "Permission denied" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/metadata/single.py:398 +msgid "Could not open %s. Is it being used by another program?" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/metadata/single.py:450 +#: /home/kovid/work/calibre/src/calibre/gui2/metadata/single.py:455 +msgid "Save changes and edit the metadata of %s" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/metadata/single.py:545 +#: /home/kovid/work/calibre/src/calibre/gui2/metadata/single.py:747 +msgid "Change cover" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/metadata/single.py:602 +msgid "Co&mments" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/metadata/single.py:642 +#: /home/kovid/work/calibre/src/calibre/gui2/metadata/single.py:788 +msgid "&Metadata" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/metadata/single.py:647 +msgid "&Cover and formats" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/metadata/single.py:716 +msgid "C&ustom metadata" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/metadata/single.py:728 +msgid "&Comments" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/metadata/single.py:794 +msgid "Basic metadata" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/metadata/single_download.py:132 +msgid "Has cover" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/metadata/single_download.py:132 +msgid "Has summary" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/metadata/single_download.py:189 +msgid "" +"The has cover indication is not fully\n" +"reliable. Sometimes results marked as not\n" +"having a cover will find a cover in the download\n" +"cover stage, and vice versa." +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/metadata/single_download.py:258 +msgid "See at" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/metadata/single_download.py:393 +msgid "calibre is downloading metadata from: " +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/metadata/single_download.py:415 +msgid "Please wait" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/metadata/single_download.py:444 +msgid "Query: " +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/metadata/single_download.py:463 +msgid "Failed to download metadata. Click Show Details to see details" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/metadata/single_download.py:472 +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 " +"single distinctive word from the title.

      To see the full log, click Show " +"Details." +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/metadata/single_download.py:538 +msgid "Current cover" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/metadata/single_download.py:541 +msgid "Searching..." +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/metadata/single_download.py:687 +msgid "Downloading covers for %s, please wait..." +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/metadata/single_download.py:717 +msgid "Failed to download any covers, click \"Show details\" for details." +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/metadata/single_download.py:723 +msgid "Could not find any covers for %s" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/metadata/single_download.py:725 +msgid "Found %d covers of %s. Pick the one you like best." +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/metadata/single_download.py:813 +msgid "Downloading metadata..." +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/metadata/single_download.py:906 +msgid "Downloading cover..." +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/__init__.py:37 +msgid "" +"Restore settings to default values. You have to click Apply to actually save " +"the default settings." +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/__init__.py:328 +msgid "Configure " +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/adding.py:28 +msgid "Ignore duplicate incoming formats" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/adding.py:29 +msgid "Overwrite existing duplicate formats" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/adding.py:30 +msgid "Create new record for each duplicate format" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/adding_ui.py:65 +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 " +"from the filename." +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/adding_ui.py:66 +msgid "Read &metadata from &file contents rather than file name" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/adding_ui.py:67 +msgid "" +"Swap the firstname and lastname of the author. This affects only metadata " +"read from file names." +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/adding_ui.py:68 +msgid "&Swap author firstname and lastname" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/adding_ui.py:69 +msgid "" +"Automerge: If books with similar titles and authors found, merge the " +"incoming formats automatically into\n" +"existing book records. The box to the right controls what happens when an " +"existing record already has\n" +"the incoming format. Note that this option also affects the Copy to library " +"action.\n" +"\n" +"Title match ignores leading indefinite articles (\"the\", \"a\", \"an\"), " +"punctuation, case, etc. Author match is exact." +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/adding_ui.py:74 +msgid "&Automerge added books if they already exist in the calibre library:" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/adding_ui.py:75 +msgid "" +"Automerge: If books with similar titles and authors found, merge the " +"incoming formats automatically into\n" +"existing book records. This box controls what happens when an existing " +"record already has\n" +"the incoming format: \n" +"\n" +"Ignore duplicate incoming files - means that existing files in your calibre " +"library will not be replaced\n" +"Overwrite existing duplicate files - means that existing files in your " +"calibre library will be replaced\n" +"Create new record for each duplicate file - means that a new book entry will " +"be created for each duplicate file\n" +"\n" +"Title matching ignores leading indefinite articles (\"the\", \"a\", \"an\"), " +"punctuation, case, etc.\n" +"Author matching is exact." +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/adding_ui.py:85 +msgid "&Tags to apply when adding a book:" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/adding_ui.py:86 +msgid "" +"A comma-separated list of tags that will be applied to books added to the " +"library" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/adding_ui.py:87 +msgid "&Configure metadata from file name" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/behavior.py:34 +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/behavior.py:36 +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/behavior_ui.py:160 +msgid "Low" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/behavior.py:34 +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/behavior_ui.py:159 +msgid "High" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/behavior.py:36 +msgid "Very low" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/behavior.py:64 +msgid "Compact Metadata" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/behavior.py:65 +msgid "All on 1 tab" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/behavior.py:165 +msgid "Done" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/behavior.py:166 +msgid "Confirmation dialogs have all been reset" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/behavior_ui.py:147 +msgid "Show notification when &new version is available" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/behavior_ui.py:148 +msgid "" +"If checked, Yes/No custom columns values can be Yes, No, or Unknown.\n" +"If not checked, the values can be Yes or No." +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/behavior_ui.py:150 +msgid "Yes/No columns have three values (Requires restart)" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/behavior_ui.py:151 +msgid "Automatically send downloaded &news to ebook reader" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/behavior_ui.py:152 +msgid "&Delete news from library when it is automatically sent to reader" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/behavior_ui.py:153 +msgid "Preferred &output format:" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/behavior_ui.py:154 +msgid "Default network &timeout:" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/behavior_ui.py:155 +msgid "" +"Set the default timeout for network fetches (i.e. anytime we go out to the " +"internet to get information)" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/behavior_ui.py:156 +msgid " seconds" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/behavior_ui.py:157 +msgid "Job &priority:" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/behavior_ui.py:161 +msgid "Restriction to apply when the current library is opened:" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/behavior_ui.py:162 +msgid "" +"Apply this restriction on calibre startup if the current library is being " +"used. Also applied when switching to this library. Note that this setting is " +"per library. " +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/behavior_ui.py:163 +msgid "Edit metadata (single) layout:" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/behavior_ui.py:164 +msgid "" +"Choose a different layout for the Edit Metadata dialog. The compact metadata " +"layout favors editing custom metadata over changing covers and formats." +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/behavior_ui.py:165 +msgid "Preferred &input format order:" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/behavior_ui.py:168 +msgid "Use internal &viewer for:" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/behavior_ui.py:169 +msgid "Reset all disabled &confirmation dialogs" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/columns.py:96 +msgid "You must select a column to delete it" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/columns.py:101 +msgid "The selected column is not a custom column" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/columns.py:103 +msgid "Do you really want to delete column %s and all its data?" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/columns_ui.py:87 +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/custom_columns_ui.py:82 +msgid "" +"Here you can re-arrange the layout of the columns in the calibre library " +"book list. You can hide columns by unchecking them. You can also create your " +"own, custom columns." +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/columns_ui.py:88 +msgid "Move column up" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/columns_ui.py:90 +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/custom_columns_ui.py:84 +msgid "Remove a user-defined column" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/columns_ui.py:92 +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/custom_columns_ui.py:86 +msgid "Add a user-defined column" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/columns_ui.py:94 +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/custom_columns_ui.py:88 +msgid "Edit settings of a user-defined column" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/columns_ui.py:96 +msgid "Move column down" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/columns_ui.py:98 +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/custom_columns_ui.py:91 +msgid "Add &custom column" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/conversion.py:41 +msgid "" +"Restore settings to default values. Only settings for the currently selected " +"section are restored." +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/create_custom_column.py:18 +msgid "Text, column shown in the tag browser" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/create_custom_column.py:21 +msgid "Comma separated text, like tags, shown in the tag browser" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/create_custom_column.py:24 +msgid "Long text, like comments, not shown in the tag browser" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/create_custom_column.py:27 +msgid "Text column for keeping series-like information" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/create_custom_column.py:30 +msgid "Text, but with a fixed set of permitted values" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/create_custom_column.py:34 +msgid "Floating point numbers" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/create_custom_column.py:36 +msgid "Integers" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/create_custom_column.py:38 +msgid "Ratings, shown with stars" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/create_custom_column.py:41 +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/create_custom_column.py:66 +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/create_custom_column.py:73 +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/create_custom_column.py:149 +msgid "Yes/No" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/create_custom_column.py:43 +msgid "Column built from other columns" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/create_custom_column.py:45 +msgid "Column built from other columns, behaves like tags" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/create_custom_column.py:52 +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/create_custom_column.py:53 +msgid "Create a custom column" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/create_custom_column.py:64 +msgid "Quick create:" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/create_custom_column.py:65 +msgid "ISBN" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/create_custom_column.py:65 +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/create_custom_column.py:148 +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/emailp.py:27 +#: /home/kovid/work/calibre/src/calibre/library/field_metadata.py:124 +msgid "Formats" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/create_custom_column.py:68 +msgid "People's names" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/create_custom_column.py:73 +msgid "Number" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/create_custom_column.py:73 +msgid "Text" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/create_custom_column.py:88 +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/create_custom_column.py:89 +msgid "Edit a custom column" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/create_custom_column.py:93 +msgid "No column selected" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/create_custom_column.py:94 +msgid "No column has been selected" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/create_custom_column.py:98 +msgid "Selected column is not a user-defined column" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/create_custom_column.py:150 +msgid "My Tags" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/create_custom_column.py:151 +msgid "My Series" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/create_custom_column.py:152 +msgid "My Rating" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/create_custom_column.py:153 +msgid "People" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/create_custom_column.py:181 +msgid "No lookup name was provided" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/create_custom_column.py:185 +msgid "" +"The lookup name must contain only lower case letters, digits and " +"underscores, and start with a letter" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/create_custom_column.py:188 +msgid "" +"Lookup names cannot end with _index, because these names are reserved for " +"the index of a series column." +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/create_custom_column.py:198 +msgid "No column heading was provided" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/create_custom_column.py:208 +msgid "The lookup name %s is already used" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/create_custom_column.py:220 +msgid "The heading %s is already used" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/create_custom_column.py:231 +msgid "You must enter a template for composite columns" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/create_custom_column.py:240 +msgid "You must enter at least one value for enumeration columns" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/create_custom_column.py:244 +msgid "You cannot provide the empty value, as it is included by default" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/create_custom_column.py:248 +msgid "The value \"{0}\" is in the list more than once" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/create_custom_column_ui.py:190 +msgid "&Lookup name" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/create_custom_column_ui.py:191 +msgid "Column &heading" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/create_custom_column_ui.py:192 +msgid "" +"Used for searching the column. Must contain only digits and lower case " +"letters." +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/create_custom_column_ui.py:193 +msgid "" +"Column heading in the library view and category name in the tag browser" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/create_custom_column_ui.py:194 +msgid "&Column type" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/create_custom_column_ui.py:195 +msgid "What kind of information will be kept in the column." +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/create_custom_column_ui.py:196 +msgid "" +"Show check marks in the GUI. Values of 'yes', 'checked', and 'true'\n" +"will show a green check. Values of 'no', 'unchecked', and 'false' will show " +"a red X.\n" +"Everything else will show nothing." +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/create_custom_column_ui.py:199 +msgid "Show checkmarks" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/create_custom_column_ui.py:200 +msgid "" +"Check this box if this column contains names, like the authors column." +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/create_custom_column_ui.py:201 +msgid "Contains names" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/create_custom_column_ui.py:202 +msgid "" +"

      Date format. Use 1-4 'd's for day, 1-4 'M's for month, and 2 or 4 'y's " +"for year.

      \n" +"

      For example:\n" +"

        \n" +"
      • ddd, d MMM yyyy gives Mon, 5 Jan 2010
      • \n" +"
      • dd MMMM yy gives 05 January 10
      • \n" +"
      " +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/create_custom_column_ui.py:208 +msgid "Use MMM yyyy for month + year, yyyy for year only" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/create_custom_column_ui.py:209 +msgid "Default: dd MMM yyyy." +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/create_custom_column_ui.py:210 +msgid "Format for &dates" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/create_custom_column_ui.py:211 +msgid "&Template" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/create_custom_column_ui.py:212 +msgid "Field template. Uses the same syntax as save templates." +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/create_custom_column_ui.py:213 +msgid "Similar to save templates. For example, {title} {isbn}" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/create_custom_column_ui.py:214 +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/create_custom_column_ui.py:224 +msgid "Default: (nothing)" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/create_custom_column_ui.py:215 +msgid "&Sort/search column by" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/create_custom_column_ui.py:216 +msgid "How this column should handled in the GUI when sorting and searching" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/create_custom_column_ui.py:217 +msgid "If checked, this column will appear in the tags browser as a category" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/create_custom_column_ui.py:218 +msgid "Show in tags browser" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/create_custom_column_ui.py:219 +msgid "Values" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/create_custom_column_ui.py:220 +msgid "" +"A comma-separated list of permitted values. The empty value is always\n" +"included, and is the default. For example, the list 'one,two,three' has\n" +"four values, the first of them being the empty value." +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/create_custom_column_ui.py:223 +msgid "The empty string is always the first value" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/device_debug.py:21 +msgid "Getting debug information" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/device_debug.py:22 +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/device_user_defined.py:32 +msgid "Copy to &clipboard" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/device_debug.py:24 +msgid "Debug device detection" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/device_user_defined.py:31 +msgid "Getting device information" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/device_user_defined.py:34 +msgid "User-defined device information" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/device_user_defined.py:51 +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/device_user_defined.py:57 +msgid "Device Detection" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/device_user_defined.py:52 +msgid "Ensure your device is disconnected, then press OK" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/device_user_defined.py:58 +msgid "Ensure your device is connected, then press OK" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/device_user_defined.py:88 +msgid "" +"Copy these values to the clipboard, paste them into an editor, then enter " +"them into the USER_DEVICE by customizing the device plugin in Preferences-" +">Plugins. Remember to also enter the folders where you want the books to be " +"put. You must restart calibre for your changes to take effect.\n" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/email_ui.py:66 +msgid "" +"calibre can send your books to you (or your reader) by email. Emails will be " +"automatically sent for downloaded news to all email addresses that have Auto-" +"send checked." +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/email_ui.py:67 +msgid "Add an email address to which to send books" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/email_ui.py:68 +msgid "&Add email" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/email_ui.py:69 +msgid "Make &default" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/email_ui.py:70 +msgid "&Remove email" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/emailp.py:27 +msgid "Auto send" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/emailp.py:27 +msgid "Email" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/emailp.py:32 +msgid "Formats to email. The first matching format will be sent." +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/emailp.py:33 +msgid "" +"Subject of the email to use when sending. When left blank the title will be " +"used for the subject. Also, the same templates used for \"Save to disk\" " +"such as {title} and {author_sort} can be used here." +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/emailp.py:37 +msgid "" +"If checked, downloaded news will be automatically mailed
      to this email " +"address (provided it is in one of the listed formats)." +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/emailp.py:115 +msgid "new email address" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/look_feel.py:103 +msgid "Narrow" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/look_feel.py:103 +msgid "Wide" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/look_feel.py:128 +msgid "Off" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/look_feel.py:128 +msgid "Small" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/look_feel.py:129 +msgid "Large" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/look_feel.py:129 +msgid "Medium" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/look_feel.py:132 +msgid "Always" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/look_feel.py:132 +msgid "Automatic" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/look_feel.py:133 +msgid "Never" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/look_feel.py:136 +msgid "By first letter" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/look_feel.py:136 +msgid "Disabled" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/look_feel.py:137 +msgid "Partitioned" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/look_feel_ui.py:198 +msgid "User Interface &layout (needs restart):" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/look_feel_ui.py:199 +msgid "Choose &language (requires restart):" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/look_feel_ui.py:200 +msgid "Enable system &tray icon (needs restart)" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/look_feel_ui.py:201 +msgid "Disable all animations. Useful if you have a slow/old computer." +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/look_feel_ui.py:202 +msgid "Disable &animations" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/look_feel_ui.py:203 +msgid "Disable ¬ifications in system tray" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/look_feel_ui.py:204 +msgid "Show &splash screen at startup" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/look_feel_ui.py:205 +msgid "&Toolbar" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/look_feel_ui.py:206 +msgid "&Icon size:" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/look_feel_ui.py:207 +msgid "Show &text under icons:" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/look_feel_ui.py:208 +msgid "Interface font:" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/look_feel_ui.py:209 +msgid "Change &font (needs restart)" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/look_feel_ui.py:210 +msgid "Main Interface" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/look_feel_ui.py:211 +msgid "Select displayed metadata" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/look_feel_ui.py:212 +msgid "Move up" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/look_feel_ui.py:213 +msgid "Move down" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/look_feel_ui.py:214 +msgid "Use &Roman numerals for series" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/look_feel_ui.py:215 +msgid "" +"Note that comments will always be displayed at the end, regardless of " +"the position you assign here." +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/look_feel_ui.py:217 +msgid "Tags browser category &partitioning method:" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/look_feel_ui.py:218 +msgid "" +"Choose how tag browser subcategories are displayed when\n" +"there are more items than the limit. Select by first\n" +"letter to see an A, B, C list. Choose partitioned to\n" +"have a list of fixed-sized groups. Set to disabled\n" +"if you never want subcategories" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/look_feel_ui.py:223 +msgid "&Collapse when more items than:" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/look_feel_ui.py:224 +msgid "" +"If a Tag Browser category has more than this number of items, it is divided\n" +"up into sub-categories. If the partition method is set to disable, this " +"value is ignored." +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/look_feel_ui.py:226 +msgid "Show &average ratings in the tags browser" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/look_feel_ui.py:227 +msgid "Categories with &hierarchical items:" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/look_feel_ui.py:228 +msgid "" +"A comma-separated list of columns in which items containing\n" +"periods are displayed in the tag browser trees. For example, if\n" +"this box contains 'tags' then tags of the form 'Mystery.English'\n" +"and 'Mystery.Thriller' will be displayed with English and Thriller\n" +"both under 'Mystery'. If 'tags' is not in this box,\n" +"then the tags will be displayed each on their own line." +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/look_feel_ui.py:235 +msgid "Show cover &browser in a separate window (needs restart)" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/look_feel_ui.py:236 +msgid "&Number of covers to show in browse mode (needs restart):" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/main.py:230 +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/tweaks_ui.py:96 +msgid "&Apply" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/main.py:237 +msgid "Restore &defaults" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/main.py:238 +msgid "Save changes" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/main.py:239 +msgid "Cancel and return to overview" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/main.py:295 +msgid "Restoring to defaults not supported for" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/main.py:330 +msgid "" +"Some of the changes you made require a restart. Please restart calibre as " +"soon as possible." +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/main.py:333 +msgid "" +"The changes you have made require calibre be restarted immediately. You will " +"not be allowed set any more preferences, until you restart." +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/main.py:338 +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/server.py:127 +msgid "Restart needed" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/metadata_sources.py:47 +msgid "Source" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/metadata_sources.py:49 +msgid "Cover priority" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/metadata_sources.py:76 +msgid "This source is configured and ready to go" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/metadata_sources.py:77 +msgid "This source needs configuration" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/metadata_sources.py:148 +msgid "Published date" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/metadata_sources.py:241 +msgid "Configure %s
      %s" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/metadata_sources.py:293 +msgid "No source selected" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/metadata_sources.py:294 +msgid "No source selected, cannot configure." +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/metadata_sources_ui.py:110 +msgid "Metadata sources" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/metadata_sources_ui.py:111 +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 " +"(smaller) priority will be preferred when bulk downloading metadata.\n" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/metadata_sources_ui.py:113 +msgid "" +"Sources with a red X next to their names must be configured before they will " +"be used. " +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/metadata_sources_ui.py:114 +msgid "Configure selected source" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/metadata_sources_ui.py:116 +msgid "" +"If you uncheck any fields, metadata for those fields will not be downloaded" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/metadata_sources_ui.py:117 +msgid "&Select all" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/metadata_sources_ui.py:118 +msgid "&Clear all" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/metadata_sources_ui.py:119 +msgid "Convert all downloaded comments to plain &text" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/metadata_sources_ui.py:120 +msgid "Swap author names from FN LN to LN, FN" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/metadata_sources_ui.py:121 +msgid "Max. number of &tags to download:" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/metadata_sources_ui.py:122 +msgid "Max. &time to wait after first match is found:" +msgstr "" + +#: /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:125 +#: /home/kovid/work/calibre/src/calibre/gui2/viewer/config_ui.py:199 +msgid " secs" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/metadata_sources_ui.py:124 +msgid "Max. time to wait after first &cover is found:" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/metadata_sources_ui.py:126 +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 " +"tend to be more like genres, while the larger tag sets tend to describe the " +"books content.\n" +"

      Note that this option will only make a practical difference if one of the " +"metadata sources has a genre like tag set for the book you are searching " +"for. Most often, they all have large tag sets." +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/metadata_sources_ui.py:128 +msgid "Prefer &fewer tags" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/misc.py:56 +msgid "Failed to install command line tools." +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/misc.py:59 +msgid "Command line tools installed" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/misc.py:60 +msgid "Command line tools installed in" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/misc.py:61 +msgid "" +"If you move calibre.app, you have to re-install the command line tools." +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/misc_ui.py:62 +msgid "Max. simultaneous conversion/news download jobs:" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/misc_ui.py:63 +msgid "Limit the max. simultaneous jobs to the available CPU &cores" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/misc_ui.py:64 +msgid "Debug &device detection" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/misc_ui.py:65 +msgid "Get information to setup the &user defined device" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/misc_ui.py:66 +msgid "Open calibre &configuration directory" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/misc_ui.py:67 +msgid "&Install command line tools" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/plugboard.py:37 +msgid "Open Editor" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/plugboard.py:72 +msgid "Device currently connected: " +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/plugboard.py:75 +msgid "Device currently connected: None" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/plugboard.py:207 +msgid "That format and device already has a plugboard." +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/plugboard.py:219 +msgid "Possibly override plugboard?" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/plugboard.py:220 +msgid "" +"A more general plugboard already exists for that format and device. Are you " +"sure you want to add the new plugboard?" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/plugboard.py:232 +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/plugboard.py:254 +msgid "Add possibly overridden plugboard?" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/plugboard.py:233 +msgid "" +"More specific device plugboards exist for that format. Are you sure you want " +"to add the new plugboard?" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/plugboard.py:244 +msgid "Really add plugboard?" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/plugboard.py:245 +msgid "" +"A different plugboard matches that format and device combination. Are you " +"sure you want to add the new plugboard?" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/plugboard.py:255 +msgid "" +"More specific format and device plugboards already exist. Are you sure you " +"want to add the new plugboard?" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/plugboard.py:266 +msgid "The {0} device does not support the {1} format." +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/plugboard.py:299 +msgid "Invalid destination" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/plugboard.py:300 +msgid "The destination field cannot be blank" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/plugboard_ui.py:114 +msgid "" +"Here you can change the metadata calibre uses to update a book when saving " +"to disk or sending to device.\n" +"\n" +"Use this dialog to define a 'plugboard' for a format (or all formats) and a " +"device (or all devices). The plugboard specifies what template is connected " +"to what field. The template is used to compute a value, and that value is " +"assigned to the connected field.\n" +"\n" +"Often templates will contain simple references to composite columns, but " +"this is not necessary. You can use any template in a source box that you can " +"use elsewhere in calibre.\n" +"\n" +"One possible use for a plugboard is to alter the title to contain series " +"information. Another would be to change the author sort, something that mobi " +"users might do to force it to use the ';' that the kindle requires. A third " +"would be to specify the language." +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/plugboard_ui.py:121 +msgid "Format (choose first)" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/plugboard_ui.py:122 +msgid "Device (choose second)" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/plugboard_ui.py:123 +msgid "Add new plugboard" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/plugboard_ui.py:124 +msgid "Edit existing plugboard" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/plugboard_ui.py:125 +msgid "Existing plugboards" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/plugboard_ui.py:126 +msgid "Source template" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/plugboard_ui.py:127 +msgid "Destination field" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/plugboard_ui.py:128 +msgid "Save plugboard" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/plugboard_ui.py:129 +msgid "Delete plugboard" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/plugins.py:182 +msgid "%(plugin_type)s %(plugins)s" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/plugins.py:183 +msgid "plugins" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/plugins.py:192 +msgid "" +"\n" +"Customization: " +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/plugins.py:221 +msgid "Search for plugin" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/plugins.py:230 +#: /home/kovid/work/calibre/src/calibre/gui2/store/search/search.py:223 +msgid "No matches" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/plugins.py:231 +msgid "Could not find any matching plugins" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/plugins.py:272 +msgid "Add plugin" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/plugins.py:280 +msgid "" +"Installing plugins is a security risk. Plugins can contain a " +"virus/malware. Only install it if you got it from a trusted source. Are you " +"sure you want to proceed?" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/plugins.py:296 +msgid "" +"Plugin {0} successfully installed under {1} plugins. You may " +"have to restart calibre for the plugin to take effect." +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/plugins.py:304 +msgid "No valid plugin path" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/plugins.py:305 +msgid "%s is not a valid plugin path" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/plugins.py:314 +msgid "Select an actual plugin under %s to customize" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/plugins.py:320 +msgid "Plugin cannot be disabled" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/plugins.py:321 +msgid "The plugin: %s cannot be disabled" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/plugins.py:331 +msgid "Plugin not customizable" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/plugins.py:332 +msgid "Plugin: %s does not need customization" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/plugins.py:338 +msgid "Must restart" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/plugins.py:339 +msgid "" +"You must restart calibre before you can configure the %s plugin" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/plugins.py:344 +msgid "Plugin {0} successfully removed" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/plugins.py:352 +msgid "Cannot remove builtin plugin" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/plugins.py:353 +msgid " cannot be removed. It is a builtin plugin. Try disabling it instead." +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/plugins_ui.py:87 +msgid "" +"Here you can customize the behavior of Calibre by controlling what plugins " +"it uses." +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/plugins_ui.py:90 +msgid "Enable/&Disable plugin" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/plugins_ui.py:91 +msgid "&Customize plugin" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/plugins_ui.py:92 +msgid "&Remove plugin" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/plugins_ui.py:93 +msgid "&Add a new plugin" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/save_template.py:33 +msgid "Any custom field" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/save_template.py:34 +msgid "The lookup name of any custom field. These names begin with \"#\")" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/save_template.py:57 +msgid "Constant template" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/save_template.py:58 +msgid "" +"The template contains no {fields}, so all books will have the same name. Is " +"this OK?" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/save_template_ui.py:47 +msgid "Save &template" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/save_template_ui.py:48 +msgid "" +"By adjusting the template below, you can control what folders the files are " +"saved in and what filenames they are given. You can use the / character to " +"indicate sub-folders. Available metadata variables are described below. If a " +"particular book does not have some metadata, the variable will be replaced " +"by the empty string." +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/save_template_ui.py:49 +msgid "Available variables:" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/saving_ui.py:68 +msgid "" +"Here you can control how calibre will save your books when you click the " +"Save to Disk button:" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/saving_ui.py:69 +msgid "Save &cover separately" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/saving_ui.py:70 +msgid "Replace space with &underscores" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/saving_ui.py:71 +msgid "Update &metadata in saved copies" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/saving_ui.py:72 +msgid "Change paths to &lowercase" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/saving_ui.py:73 +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/sending_ui.py:76 +msgid "Format &dates as:" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/saving_ui.py:74 +msgid "File &formats to save:" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/saving_ui.py:75 +msgid "Convert non-English characters to &English equivalents" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/saving_ui.py:76 +msgid "Save metadata in &OPF file" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/search.py:33 +msgid "" +"Grouped search terms are search names that permit a query to " +"automatically search across more than one column. For example, if you create " +"a grouped search term allseries with the value series, " +"#myseries, #myseries2, then the query allseries:adhoc " +"will find 'adhoc' in any of the columns series, " +"#myseries, and #myseries2.

      Enter the name of " +"the grouped search term in the drop-down box, enter the list of columns to " +"search in the value box, then push the Save button.

      Note: Search terms " +"are forced to lower case; MySearch and mysearch " +"are the same term.

      You can have your grouped search term show up as user " +"categories in the Tag Browser. Just add the grouped search term names to " +"the Make user categories from box. You can add multiple terms separated by " +"commas. The new user category will be automatically populated with all the " +"items in the categories included in the grouped search term.

      Automatic " +"user categories permit you to see easily all the category items that are in " +"the columns contained in the grouped search term. Using the above " +"allseries example, the automatically-generated user category " +"will contain all the series mentioned in series, " +"#myseries, and #myseries2. This can be useful to " +"check for duplicates, to find which column contains a particular item, or to " +"have hierarchical categories (categories that contain categories)." +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/search.py:96 +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/search.py:106 +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/search.py:110 +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/search.py:116 +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/search.py:128 +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/search_ui.py:119 +msgid "Grouped Search Terms" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/search.py:97 +msgid "The search term cannot be blank" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/search.py:107 +msgid "That name is already used for a column or grouped search term" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/search.py:111 +msgid "That name is already used for user category" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/search.py:117 +msgid "The value box cannot be empty" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/search.py:129 +msgid "The empty grouped search term cannot be deleted" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/search_ui.py:110 +msgid "Search as you &type" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/search_ui.py:111 +msgid "" +"&Highlight search results instead of restricting the book list to the results" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/search_ui.py:112 +msgid "What to search by default" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/search_ui.py:113 +msgid "" +"When you enter a search term without a prefix, by default calibre will " +"search all metadata for matches. For example, entering, \"asimov\" will " +"search not just authors but title/tags/series/comments/etc. Use these " +"options if you would like to change this behavior." +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/search_ui.py:114 +msgid "&Limit the searched metadata" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/search_ui.py:115 +msgid "&Columns that non-prefixed searches are limited to:" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/search_ui.py:116 +msgid "" +"Note that this option affects all searches, including saved searches and " +"restrictions. Therefore, if you use this option, it is best to ensure that " +"you always use prefixes in your saved searches. For example, use " +"\"series:Foundation\" rather than just \"Foundation\" in a saved search" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/search_ui.py:117 +msgid "" +"Clear search histories from all over calibre. Including the book list, e-" +"book viewer, fetch news dialog, etc." +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/search_ui.py:118 +msgid "Clear search &histories" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/search_ui.py:120 +msgid "&Names:" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/search_ui.py:121 +msgid "" +"Contains the names of the currently-defined group search terms.\n" +"Create a new name by entering it into the empty box, then\n" +"pressing Save. Rename a search term by selecting it then\n" +"changing the name and pressing Save. Change the value of\n" +"a search term by changing the value box then pressing Save." +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/search_ui.py:126 +msgid "Delete the current search term" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/search_ui.py:128 +msgid "" +"Save the current search term. You can rename a search term by\n" +"changing the name then pressing Save. You can change the value\n" +"of a search term by changing the value box then pressing Save." +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/search_ui.py:131 +msgid "&Save" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/search_ui.py:132 +msgid "Make &user categories from:" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/search_ui.py:133 +msgid "" +"Enter the names of any grouped search terms you wish\n" +"to be shown as user categories" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/sending.py:28 +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/sending_ui.py:70 +msgid "Manual management" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/sending.py:29 +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/sending_ui.py:71 +msgid "Only on send" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/sending.py:30 +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/sending_ui.py:72 +msgid "Automatic management" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/sending_ui.py:69 +msgid "Metadata &management:" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/sending_ui.py:73 +msgid "" +"

    • Manual management: Calibre updates the metadata and adds " +"collections only when a book is sent. With this option, calibre will never " +"remove a collection.
    • \n" +"
    • Only on send: Calibre updates metadata and adds/removes " +"collections for a book only when it is sent to the device.
    • \n" +"
    • Automatic management: Calibre automatically keeps metadata on the " +"device in sync with the calibre library, on every connect
    " +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/sending_ui.py:77 +msgid "" +"Here you can control how calibre will save your books when you click the " +"Send to Device button. This setting can be overriden for individual devices " +"by customizing the device interface plugins in Preferences->Advanced->Plugins" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/server.py:70 +#: /home/kovid/work/calibre/src/calibre/gui2/ui.py:422 +msgid "Failed to start content server" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/server.py:106 +msgid "Error log:" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/server.py:113 +msgid "Access log:" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/server.py:128 +msgid "You need to restart the server for changes to take effect" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/server_ui.py:124 +msgid "Server &port:" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/server_ui.py:127 +msgid "" +"If you leave the password blank, anyone will be able to access your book " +"collection using the web interface." +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/server_ui.py:128 +msgid "" +"The maximum size (widthxheight) for displayed covers. Larger covers are " +"resized. " +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/server_ui.py:129 +msgid "Max. &cover size:" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/server_ui.py:131 +msgid "Max. &OPDS items per query:" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/server_ui.py:132 +msgid "Max. OPDS &ungrouped items:" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/server_ui.py:133 +msgid "Restriction (saved search) to apply:" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/server_ui.py:134 +msgid "" +"This restriction (based on a saved search) will restrict the books the " +"content server makes available to those matching the search. This setting is " +"per library (i.e. you can have a different restriction per library)." +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/server_ui.py:135 +msgid "&Start Server" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/server_ui.py:136 +msgid "St&op Server" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/server_ui.py:137 +msgid "&Test Server" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/server_ui.py:138 +msgid "" +"calibre contains a network server that allows you to access your book " +"collection using a browser from anywhere in the world. Any changes to the " +"settings will only take effect after a server restart." +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/server_ui.py:139 +msgid "Run server &automatically on startup" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/server_ui.py:140 +msgid "View &server logs" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/server_ui.py:141 +#: /home/kovid/work/calibre/src/calibre/gui2/wizard/stanza_ui.py:51 +msgid "" +"

    Remember to leave calibre running as the server only runs as long as " +"calibre is running.\n" +"

    Stanza should see your calibre collection automatically. If not, try " +"adding the URL http://myhostname:8080 as a new catalog in the Stanza reader " +"on your iPhone. Here myhostname should be the fully qualified hostname or " +"the IP address of the computer calibre is running on." +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/template_functions.py:23 +msgid "" +"\n" +"

    Here you can add and remove functions used in template " +"processing. A\n" +" template function is written in python. It takes information from " +"the\n" +" book, processes it in some way, then returns a string result. " +"Functions\n" +" defined here are usable in templates in the same way that builtin\n" +" functions are usable. The function must be named evaluate, " +"and\n" +" must have the signature shown below.

    \n" +"

    evaluate(self, formatter, kwargs, mi, locals, your " +"parameters)\n" +" → returning a unicode string

    \n" +"

    The parameters of the evaluate function are:\n" +"

      \n" +"
    • formatter: the instance of the formatter being used to\n" +" evaluate the current template. You can use this to do recursive\n" +" template evaluation.
    • \n" +"
    • kwargs: a dictionary of metadata. Field values are in " +"this\n" +" dictionary.\n" +"
    • mi: a Metadata instance. Used to get field information.\n" +" This parameter can be None in some cases, such as when evaluating\n" +" non-book templates.
    • \n" +"
    • locals: the local variables assigned to by the current\n" +" template program.
    • \n" +"
    • your parameters: You must supply one or more formal\n" +" parameters. The number must match the arg count box, unless arg " +"count is\n" +" -1 (variable number or arguments), in which case the last argument " +"must\n" +" be *args. At least one argument is required, and is usually the " +"value of\n" +" the field being operated upon. Note that when writing in basic " +"template\n" +" mode, the user does not provide this first argument. Instead it is\n" +" supplied by the formatter.
    • \n" +"

    \n" +"

    \n" +" The following example function checks the value of the field. If " +"the\n" +" field is not empty, the field's value is returned, otherwise the " +"value\n" +" EMPTY is returned.\n" +"

    \n"
    +"        name: my_ifempty\n"
    +"        arg count: 1\n"
    +"        doc: my_ifempty(val) -- return val if it is not empty, otherwise the "
    +"string 'EMPTY'\n"
    +"        program code:\n"
    +"        def evaluate(self, formatter, kwargs, mi, locals, val):\n"
    +"            if val:\n"
    +"                return val\n"
    +"            else:\n"
    +"                return 'EMPTY'
    \n" +" This function can be called in any of the three template program " +"modes:\n" +"
      \n" +"
    • single-function mode: {tags:my_ifempty()}
    • \n" +"
    • template program mode: {tags:'my_ifempty($)'}
    • \n" +"
    • general program mode: program: my_ifempty(field('tags'))
    • \n" +"

      \n" +" " +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/template_functions.py:134 +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/template_functions.py:144 +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/template_functions.py:151 +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/template_functions.py:155 +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/template_functions.py:166 +msgid "Template functions" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/template_functions.py:135 +msgid "You cannot delete a built-in function" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/template_functions.py:145 +msgid "Function not defined" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/template_functions.py:156 +msgid "Argument count must be -1 or greater than zero" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/template_functions.py:167 +msgid "Exception while compiling function" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/template_functions.py:195 +msgid "function source code not available" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/template_functions_ui.py:96 +msgid "&Function:" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/template_functions_ui.py:97 +msgid "Enter the name of the function to create." +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/template_functions_ui.py:98 +msgid "Arg &count:" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/template_functions_ui.py:99 +msgid "Set this to -1 if the function takes a variable number of arguments" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/template_functions_ui.py:102 +msgid "&Delete" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/template_functions_ui.py:103 +msgid "&Replace" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/template_functions_ui.py:104 +msgid "C&reate" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/template_functions_ui.py:105 +msgid "&Program Code: (be sure to follow python indenting rules)" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/toolbar.py:36 +msgid "Switch between library and device views" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/toolbar.py:41 +msgid "Separator" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/toolbar.py:58 +msgid "Choose library" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/toolbar.py:219 +msgid "The main toolbar" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/toolbar.py:220 +msgid "The main toolbar when a device is connected" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/toolbar.py:221 +msgid "The optional second toolbar" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/toolbar.py:222 +msgid "The menubar" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/toolbar.py:223 +msgid "The menubar when a device is connected" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/toolbar.py:224 +msgid "The context menu for the books in the calibre library" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/toolbar.py:226 +msgid "The context menu for the books on the device" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/toolbar.py:260 +msgid "Cannot add" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/toolbar.py:261 +msgid "Cannot add the actions %s to this location" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/toolbar.py:279 +msgid "Cannot remove" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/toolbar.py:280 +msgid "Cannot remove the actions %s from this location" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/toolbar_ui.py:99 +msgid "Customize the actions in:" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/toolbar_ui.py:100 +msgid "A&vailable actions" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/toolbar_ui.py:101 +msgid "&Current actions" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/toolbar_ui.py:102 +msgid "Move selected action up" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/toolbar_ui.py:104 +msgid "Move selected action down" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/toolbar_ui.py:107 +msgid "Add selected actions to toolbar" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/toolbar_ui.py:109 +msgid "Remove selected actions from toolbar" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/tweaks.py:114 +msgid "This tweak has it default value" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/tweaks.py:116 +msgid "This tweak has been customized" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/tweaks.py:237 +msgid "" +"Add/edit tweaks for any custom plugins you have installed. Documentation for " +"these tweaks should be available on the website from where you downloaded " +"the plugins." +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/tweaks.py:278 +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/tweaks.py:318 +msgid "" +"There was a syntax error in your tweak. Click the show details button for " +"details." +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/tweaks.py:331 +msgid "Invalid tweaks" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/tweaks.py:332 +msgid "" +"The tweaks you entered are invalid, try resetting the tweaks to default and " +"changing them one by one until you find the invalid setting." +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/tweaks_ui.py:88 +msgid "" +"Values for the tweaks are shown below. Edit them to change the behavior of " +"calibre. Your changes will only take effect after a restart of " +"calibre." +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/tweaks_ui.py:89 +msgid "Edit tweaks for any custom plugins you have installed" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/tweaks_ui.py:90 +msgid "&Plugin tweaks" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/tweaks_ui.py:92 +msgid "Edit tweak" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/tweaks_ui.py:93 +msgid "Restore this tweak to its default value" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/tweaks_ui.py:94 +msgid "Restore &default" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/tweaks_ui.py:95 +msgid "Apply any changes you made to this tweak" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/search_box.py:95 +#: /home/kovid/work/calibre/src/calibre/gui2/search_box.py:279 +#: /home/kovid/work/calibre/src/calibre/gui2/store/mobileread/store_dialog_ui.py:76 +#: /home/kovid/work/calibre/src/calibre/gui2/store/search/search_ui.py:121 +#: /home/kovid/work/calibre/src/calibre/gui2/store/search_ui.py:109 +#: /home/kovid/work/calibre/src/calibre/gui2/widgets.py:653 +#: /home/kovid/work/calibre/src/calibre/library/server/browse.py:280 +msgid "Search" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/search_box.py:339 +msgid "Delete current search" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/search_box.py:340 +msgid "No search is selected" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/search_box.py:342 +msgid "The selected search will be permanently deleted. Are you sure?" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/search_box.py:367 +msgid "Search (For Advanced Search click the button to the left)" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/search_box.py:390 +msgid "Enable or disable search highlighting." +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/search_box.py:445 +msgid "Saved Searches" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/search_box.py:447 +msgid "Choose saved search or enter name for new saved search" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/search_box.py:453 +msgid "" +"Save current search under the name shown in the box. Press and hold for a " +"pop-up options menu." +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/search_box.py:458 +msgid "Create saved search" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/search_box.py:462 +msgid "Delete saved search" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/search_box.py:466 +msgid "Manage saved searches" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/search_box.py:476 +msgid "*Current search" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/search_restriction_mixin.py:12 +msgid "Restrict to" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/search_restriction_mixin.py:19 +#: /home/kovid/work/calibre/src/calibre/gui2/search_restriction_mixin.py:92 +msgid "(all books)" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/search_restriction_mixin.py:21 +msgid "" +"Books display will be restricted to those matching a selected saved search" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/search_restriction_mixin.py:53 +msgid " or the search " +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/search_restriction_mixin.py:87 +msgid "({0} of {1})" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/search_restriction_mixin.py:94 +msgid "({0} of all)" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/shortcuts.py:48 +#: /home/kovid/work/calibre/src/calibre/gui2/shortcuts_ui.py:78 +#: /home/kovid/work/calibre/src/calibre/gui2/shortcuts_ui.py:83 +#: /home/kovid/work/calibre/src/calibre/gui2/store/search/search_ui.py:125 +#: /home/kovid/work/calibre/src/calibre/gui2/store/search_ui.py:113 +#: /home/kovid/work/calibre/src/calibre/gui2/widgets.py:351 +msgid "None" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/shortcuts.py:59 +msgid "Press a key..." +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/shortcuts.py:80 +msgid "Already assigned" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/shortcuts.py:82 +msgid "already assigned to" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/shortcuts.py:132 +#: /home/kovid/work/calibre/src/calibre/gui2/shortcuts.py:223 +msgid " or " +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/shortcuts.py:134 +#: /home/kovid/work/calibre/src/calibre/gui2/shortcuts_ui.py:74 +msgid "&Default" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/shortcuts.py:136 +msgid "Customize shortcuts for" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/shortcuts.py:223 +msgid "Keys" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/shortcuts.py:225 +msgid "Double click to change" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/shortcuts_ui.py:73 +msgid "Frame" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/shortcuts_ui.py:75 +msgid "&Custom" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/shortcuts_ui.py:76 +msgid "&Shortcut:" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/shortcuts_ui.py:77 +#: /home/kovid/work/calibre/src/calibre/gui2/shortcuts_ui.py:82 +msgid "Click to change" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/shortcuts_ui.py:81 +msgid "&Alternate shortcut:" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/store/basic_config_widget_ui.py:38 +msgid "Added Tags:" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/store/basic_config_widget_ui.py:39 +msgid "Open store in external web browswer" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/store/ebooks_com_plugin.py:96 +msgid "Not Available" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/store/mobileread/cache_progress_dialog_ui.py:51 +msgid "Updating book cache" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/store/mobileread/cache_update_thread.py:42 +msgid "Checking last download date." +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/store/mobileread/cache_update_thread.py:48 +msgid "Downloading book list from MobileRead." +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/store/mobileread/cache_update_thread.py:61 +msgid "Processing books." +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/store/mobileread/cache_update_thread.py:70 +msgid "%s of %s books processed." +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/store/mobileread/mobileread_plugin.py:62 +msgid "Updating MobileRead book cache..." +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/store/mobileread/store_dialog_ui.py:74 +msgid "&Query:" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/store/mobileread/store_dialog_ui.py:77 +#: /home/kovid/work/calibre/src/calibre/gui2/store/mobileread_store_dialog_ui.py:63 +msgid "Books:" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/store/mobileread/store_dialog_ui.py:79 +#: /home/kovid/work/calibre/src/calibre/gui2/store/mobileread_store_dialog_ui.py:65 +#: /home/kovid/work/calibre/src/calibre/gui2/store/search/search_ui.py:128 +#: /home/kovid/work/calibre/src/calibre/gui2/store/search_ui.py:114 +#: /home/kovid/work/calibre/src/calibre/gui2/store/web_store_dialog_ui.py:63 +msgid "Close" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/store/mobileread_store_dialog_ui.py:62 +msgid "Search:" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/store/search/adv_search_builder_ui.py:192 +msgid "&Price:" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/store/search/adv_search_builder_ui.py:196 +msgid "Titl&e/Author/Price ..." +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/store/search/models.py:33 +msgid "DRM" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/store/search/models.py:33 +msgid "Price" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/store/search/models.py:175 +msgid "" +"Detected price as: %s. Check with the store before making a purchase to " +"verify this price is correct. This price often does not include promotions " +"the store may be running." +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/store/search/models.py:178 +msgid "" +"This book as been detected as having DRM restrictions. This book may not " +"work with your reader and you will have limitations placed upon you as to " +"what you can do with this book. Check with the store before making any " +"purchases to ensure you can actually read this book." +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/store/search/models.py:180 +msgid "" +"This book has been detected as being DRM Free. You should be able to use " +"this book on any device provided it is in a format calibre supports for " +"conversion. However, before making a purchase double check the DRM status " +"with the store. The store may not be disclosing the use of DRM." +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/store/search/models.py:182 +msgid "" +"The DRM status of this book could not be determined. There is a very high " +"likelihood that this book is actually DRM restricted." +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/store/search/search.py:223 +msgid "Couldn't find any books matching your query." +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/store/search/search_ui.py:118 +#: /home/kovid/work/calibre/src/calibre/gui2/store/search_ui.py:107 +msgid "Get Books" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/store/search/search_ui.py:119 +#: /home/kovid/work/calibre/src/calibre/gui2/store/search_ui.py:108 +msgid "Query:" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/store/search/search_ui.py:123 +#: /home/kovid/work/calibre/src/calibre/gui2/store/search_ui.py:111 +msgid "All" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/store/search/search_ui.py:124 +#: /home/kovid/work/calibre/src/calibre/gui2/store/search_ui.py:112 +msgid "Invert" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/store/search/search_ui.py:126 +msgid "Open a selected book in the system's web browser" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/store/search/search_ui.py:127 +msgid "Open in &external browser" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/store/web_control.py:73 +msgid "" +"This ebook is a DRMed EPUB file. You will be prompted to save this file to " +"your computer. Once it is saved, open it with Adobe Digital " +"Editions (ADE).

      ADE, in turn will download the actual ebook, which " +"will be a .epub file. You can add this book to calibre using \"Add Books\" " +"and selecting the file from the ADE library folder." +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/store/web_control.py:86 +msgid "File is not a supported ebook type. Save to disk?" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/store/web_store_dialog_ui.py:59 +msgid "Home" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/store/web_store_dialog_ui.py:60 +msgid "Reload" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/store/web_store_dialog_ui.py:61 +msgid "%p%" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/tag_view.py:345 +#: /home/kovid/work/calibre/src/calibre/gui2/tag_view.py:375 +#: /home/kovid/work/calibre/src/calibre/gui2/tag_view.py:404 +msgid "Rename %s" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/tag_view.py:349 +msgid "Edit sort for %s" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/tag_view.py:356 +msgid "Add %s to user category" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/tag_view.py:369 +msgid "Children of %s" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/tag_view.py:379 +msgid "Delete search %s" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/tag_view.py:384 +msgid "Remove %s from category %s" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/tag_view.py:391 +msgid "Search for %s" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/tag_view.py:396 +msgid "Search for everything but %s" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/tag_view.py:408 +msgid "Add sub-category to %s" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/tag_view.py:412 +msgid "Delete user category %s" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/tag_view.py:417 +msgid "Hide category %s" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/tag_view.py:421 +msgid "Show category" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/tag_view.py:431 +msgid "Search for books in category %s" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/tag_view.py:437 +msgid "Search for books not in category %s" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/tag_view.py:446 +#: /home/kovid/work/calibre/src/calibre/gui2/tag_view.py:451 +msgid "Manage %s" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/tag_view.py:454 +#: /home/kovid/work/calibre/src/calibre/gui2/tag_view.py:1844 +msgid "Manage Saved Searches" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/tag_view.py:462 +#: /home/kovid/work/calibre/src/calibre/gui2/tag_view.py:466 +#: /home/kovid/work/calibre/src/calibre/gui2/tag_view.py:1842 +msgid "Manage User Categories" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/tag_view.py:473 +msgid "Show all categories" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/tag_view.py:476 +msgid "Change sub-categorization scheme" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/tag_view.py:792 +msgid "The grouped search term name is \"{0}\"" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/tag_view.py:1065 +msgid "" +"Changing the authors for several books can take a while. Are you sure?" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/tag_view.py:1070 +msgid "" +"Changing the metadata for that many books can take a while. Are you sure?" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/tag_view.py:1157 +#: /home/kovid/work/calibre/src/calibre/library/database2.py:417 +msgid "Searches" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/tag_view.py:1391 +#: /home/kovid/work/calibre/src/calibre/gui2/tag_view.py:1411 +#: /home/kovid/work/calibre/src/calibre/gui2/tag_view.py:1420 +msgid "Rename user category" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/tag_view.py:1392 +msgid "You cannot use periods in the name when renaming user categories" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/tag_view.py:1412 +#: /home/kovid/work/calibre/src/calibre/gui2/tag_view.py:1421 +msgid "The name %s is already used" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/tag_view.py:1444 +msgid "Duplicate search name" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/tag_view.py:1445 +msgid "The saved search name %s is already used." +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/tag_view.py:1834 +msgid "Manage Authors" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/tag_view.py:1836 +msgid "Manage Series" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/tag_view.py:1838 +msgid "Manage Publishers" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/tag_view.py:1840 +msgid "Manage Tags" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/tag_view.py:1852 +msgid "Invalid search restriction" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/tag_view.py:1853 +msgid "The current search restriction is invalid" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/tag_view.py:1869 +msgid "New Category" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/tag_view.py:1920 +#: /home/kovid/work/calibre/src/calibre/gui2/tag_view.py:1923 +msgid "Delete user category" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/tag_view.py:1921 +msgid "%s is not a user category" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/tag_view.py:1924 +msgid "%s contains items. Do you really want to delete it?" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/tag_view.py:1945 +msgid "Remove category" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/tag_view.py:1946 +msgid "User category %s does not exist" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/tag_view.py:1965 +msgid "Add to user category" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/tag_view.py:1966 +msgid "A user category %s does not exist" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/tag_view.py:2089 +msgid "Find item in tag browser" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/tag_view.py:2092 +msgid "" +"Search for items. This is a \"contains\" search; items containing the\n" +"text anywhere in the name will be found. You can limit the search\n" +"to particular categories using syntax similar to search. For example,\n" +"tags:foo will find foo in any tag, but not in authors etc. Entering\n" +"*foo will filter all categories at once, showing only those items\n" +"containing the text \"foo\"" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/tag_view.py:2101 +msgid "ALT+f" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/tag_view.py:2106 +msgid "Find the first/next matching item" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/tag_view.py:2111 +msgid "Collapse all categories" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/tag_view.py:2135 +msgid "No More Matches.

      Click Find again to go to first match" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/tag_view.py:2148 +msgid "Sort by name" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/tag_view.py:2148 +msgid "Sort by popularity" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/tag_view.py:2149 +msgid "Sort by average rating" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/tag_view.py:2152 +msgid "Set the sort order for entries in the Tag Browser" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/tag_view.py:2159 +msgid "Match all" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/tag_view.py:2159 +msgid "Match any" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/tag_view.py:2164 +msgid "" +"When selecting multiple entries in the Tag Browser match any or all of them" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/tag_view.py:2171 +msgid "Manage authors, tags, etc" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/tag_view.py:2172 +msgid "" +"All of these category_managers are available by right-clicking on items in " +"the tag browser above" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/tools.py:65 +msgid "Convert book %(num)d of %(total)d (%(title)s)" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/tools.py:93 +#: /home/kovid/work/calibre/src/calibre/gui2/tools.py:205 +msgid "Could not convert some books" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/tools.py:94 +#: /home/kovid/work/calibre/src/calibre/gui2/tools.py:206 +msgid "" +"Could not convert %d of %d books, because no suitable source format was " +"found." +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/tools.py:125 +msgid "Queueing books for bulk conversion" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/tools.py:183 +msgid "Queueing " +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/tools.py:184 +msgid "Convert book %d of %d (%s)" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/tools.py:254 +msgid "Fetch news from " +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/tools.py:327 +msgid "Convert existing" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/tools.py:328 +msgid "" +"The following books have already been converted to %s format. Do you wish to " +"reconvert them?" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/ui.py:196 +msgid "&Donate to support calibre" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/ui.py:232 +msgid "&Restore" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/ui.py:237 +msgid "&Eject connected device" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/ui.py:345 +msgid "Debug mode" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/ui.py:346 +msgid "" +"You have started calibre in debug mode. After you quit calibre, the debug " +"log will be available in the file: %s

      The log will be displayed " +"automatically." +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/ui.py:548 +msgid "Conversion Error" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/ui.py:571 +msgid "Recipe Disabled" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/ui.py:587 +msgid "Failed" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/ui.py:621 +msgid "There are active jobs. Are you sure you want to quit?" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/ui.py:624 +msgid "" +" is communicating with the device!
      \n" +" Quitting may cause corruption on the device.
      \n" +" Are you sure you want to quit?" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/ui.py:628 +msgid "Active jobs" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/ui.py:696 +msgid "" +"will keep running in the system tray. To close it, choose Quit in the " +"context menu of the system tray." +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/update.py:53 +msgid "" +"%s has been updated to version %s. See the new features." +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/update.py:58 +msgid "Update available!" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/update.py:63 +msgid "Show this notification for future updates" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/update.py:68 +msgid "&Get update" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/viewer/bookmarkmanager.py:43 +msgid "Edit bookmark" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/viewer/bookmarkmanager.py:43 +msgid "New title for bookmark:" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/viewer/bookmarkmanager.py:52 +msgid "Export Bookmarks" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/viewer/bookmarkmanager.py:54 +msgid "Saved Bookmarks (*.pickle)" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/viewer/bookmarkmanager.py:62 +msgid "Import Bookmarks" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/viewer/bookmarkmanager.py:62 +msgid "Pickled Bookmarks (*.pickle)" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/viewer/bookmarkmanager_ui.py:61 +msgid "Bookmark Manager" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/viewer/bookmarkmanager_ui.py:62 +msgid "Actions" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/viewer/bookmarkmanager_ui.py:63 +msgid "Edit" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/viewer/bookmarkmanager_ui.py:65 +msgid "Reset" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/viewer/bookmarkmanager_ui.py:66 +msgid "Export" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/viewer/bookmarkmanager_ui.py:67 +msgid "Import" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/viewer/config_ui.py:178 +msgid "Configure Ebook viewer" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/viewer/config_ui.py:179 +msgid "&Font options" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/viewer/config_ui.py:180 +msgid "Se&rif family:" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/viewer/config_ui.py:181 +msgid "&Sans family:" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/viewer/config_ui.py:182 +msgid "&Monospace family:" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/viewer/config_ui.py:183 +msgid "&Default font size:" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/viewer/config_ui.py:184 +#: /home/kovid/work/calibre/src/calibre/gui2/viewer/config_ui.py:186 +#: /home/kovid/work/calibre/src/calibre/gui2/viewer/config_ui.py:201 +msgid " px" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/viewer/config_ui.py:185 +msgid "Monospace &font size:" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/viewer/config_ui.py:187 +msgid "S&tandard font:" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/viewer/config_ui.py:188 +msgid "Serif" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/viewer/config_ui.py:189 +msgid "Sans-serif" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/viewer/config_ui.py:190 +msgid "Monospace" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/viewer/config_ui.py:191 +msgid "Remember last used &window size" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/viewer/config_ui.py:192 +msgid "Remember the ¤t page when quitting" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/viewer/config_ui.py:193 +msgid "H&yphenate (break line in the middle of large words)" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/viewer/config_ui.py:194 +msgid "" +"The default language to use for hyphenation rules. If the book does not " +"specify a language, this will be used." +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/viewer/config_ui.py:195 +msgid "Default &language for hyphenation:" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/viewer/config_ui.py:196 +msgid "&Resize images larger than the viewer window (needs restart)" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/viewer/config_ui.py:197 +msgid "Page flip &duration:" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/viewer/config_ui.py:198 +msgid "disabled" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/viewer/config_ui.py:200 +msgid "Mouse &wheel flips pages" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/viewer/config_ui.py:202 +msgid "Maximum &view width:" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/viewer/config_ui.py:203 +msgid "&General" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/viewer/config_ui.py:204 +msgid "Double click to change a keyboard shortcut" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/viewer/config_ui.py:205 +msgid "&Keyboard shortcuts" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/viewer/config_ui.py:206 +msgid "" +"

      A CSS stylesheet that can be used to control the look and feel of books. " +"For examples, click here." +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/viewer/config_ui.py:207 +msgid "User &Stylesheet" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/viewer/dictionary.py:53 +msgid "No results found for:" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/viewer/documentview.py:40 +msgid "Options to customize the ebook viewer" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/viewer/documentview.py:47 +#: /home/kovid/work/calibre/src/calibre/gui2/viewer/main.py:783 +msgid "Remember last used window size" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/viewer/documentview.py:49 +#: /home/kovid/work/calibre/src/calibre/gui2/viewer/documentview.py:96 +msgid "" +"Set the user CSS stylesheet. This can be used to customize the look of all " +"books." +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/viewer/documentview.py:51 +msgid "Maximum width of the viewer window, in pixels." +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/viewer/documentview.py:53 +msgid "Resize images larger than the viewer window to fit inside it" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/viewer/documentview.py:54 +msgid "Hyphenate text" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/viewer/documentview.py:56 +msgid "Default language for hyphenation rules" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/viewer/documentview.py:58 +msgid "Save the current position in the document, when quitting" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/viewer/documentview.py:60 +msgid "Have the mouse wheel turn pages" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/viewer/documentview.py:62 +msgid "" +"The time, in seconds, for the page flip animation. Default is half a second." +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/viewer/documentview.py:65 +msgid "Font options" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/viewer/documentview.py:67 +msgid "The serif font family" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/viewer/documentview.py:69 +msgid "The sans-serif font family" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/viewer/documentview.py:71 +msgid "The monospaced font family" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/viewer/documentview.py:72 +msgid "The standard font size in px" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/viewer/documentview.py:73 +msgid "The monospaced font size in px" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/viewer/documentview.py:74 +msgid "The standard font type" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/viewer/documentview.py:125 +msgid "Still editing" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/viewer/documentview.py:126 +msgid "" +"You are in the middle of editing a keyboard shortcut first complete that, by " +"clicking outside the shortcut editing box." +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/viewer/documentview.py:534 +msgid "&Lookup in dictionary" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/viewer/documentview.py:538 +#: /home/kovid/work/calibre/src/calibre/gui2/viewer/main.py:141 +msgid "Go to..." +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/viewer/documentview.py:550 +msgid "Next Section" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/viewer/documentview.py:551 +msgid "Previous Section" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/viewer/documentview.py:553 +msgid "Document Start" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/viewer/documentview.py:554 +msgid "Document End" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/viewer/documentview.py:556 +msgid "Section Start" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/viewer/documentview.py:557 +msgid "Section End" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/viewer/keys.py:12 +msgid "Scroll to the next page" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/viewer/keys.py:15 +msgid "Scroll to the previous page" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/viewer/keys.py:18 +msgid "Scroll to the next section" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/viewer/keys.py:21 +msgid "Scroll to the previous section" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/viewer/keys.py:24 +msgid "Scroll to the bottom of the section" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/viewer/keys.py:27 +msgid "Scroll to the top of the section" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/viewer/keys.py:30 +msgid "Scroll to the end of the document" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/viewer/keys.py:33 +msgid "Scroll to the start of the document" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/viewer/keys.py:36 +msgid "Scroll down" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/viewer/keys.py:39 +msgid "Scroll up" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/viewer/keys.py:42 +msgid "Scroll left" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/viewer/keys.py:45 +msgid "Scroll right" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/viewer/main.py:118 +msgid "Book format" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/viewer/main.py:198 +msgid "Position in book" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/viewer/main.py:205 +msgid "Go to a reference. To get reference numbers, use the reference mode." +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/viewer/main.py:213 +msgid "Search for text in book" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/viewer/main.py:292 +msgid "Print Preview" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/viewer/main.py:303 +msgid "Clear list of recently opened books" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/viewer/main.py:361 +msgid "Connecting to dict.org to lookup: %s…" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/viewer/main.py:467 +msgid "Choose ebook" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/viewer/main.py:468 +msgid "Ebooks" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/viewer/main.py:504 +msgid "No matches found for: %s" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/viewer/main.py:541 +msgid "Loading flow..." +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/viewer/main.py:579 +msgid "Laying out %s" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/viewer/main.py:610 +msgid "Bookmark #%d" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/viewer/main.py:614 +msgid "Add bookmark" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/viewer/main.py:615 +msgid "Enter title for bookmark:" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/viewer/main.py:625 +msgid "Manage Bookmarks" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/viewer/main.py:665 +msgid "Loading ebook..." +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/viewer/main.py:677 +msgid "Could not open ebook" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/viewer/main.py:770 +msgid "Options to control the ebook viewer" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/viewer/main.py:777 +msgid "" +"If specified, viewer window will try to come to the front when started." +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/viewer/main.py:780 +msgid "" +"If specified, viewer window will try to open full screen when started." +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/viewer/main.py:785 +msgid "Print javascript alert and console messages to the console" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/viewer/main.py:791 +msgid "" +"%prog [options] file\n" +"\n" +"View an ebook.\n" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/viewer/main_ui.py:189 +msgid "E-book Viewer" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/viewer/main_ui.py:190 +msgid "Close dictionary" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/viewer/main_ui.py:192 +msgid "toolBar" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/viewer/main_ui.py:195 +msgid "Next page" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/viewer/main_ui.py:196 +msgid "Previous page" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/viewer/main_ui.py:197 +msgid "Font size larger" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/viewer/main_ui.py:198 +msgid "Font size smaller" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/viewer/main_ui.py:202 +msgid "Find next" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/viewer/main_ui.py:203 +msgid "Find next occurrence" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/viewer/main_ui.py:207 +msgid "Reference Mode" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/viewer/main_ui.py:208 +msgid "Bookmark" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/viewer/main_ui.py:209 +msgid "Toggle full screen" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/viewer/main_ui.py:210 +msgid "Print" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/viewer/main_ui.py:211 +msgid "Find previous" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/viewer/main_ui.py:212 +msgid "Find previous occurrence" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/viewer/printing.py:114 +msgid "Print eBook" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/widgets.py:962 +msgid "Drag to resize" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/widgets.py:997 +msgid "Show" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/widgets.py:1004 +msgid "Hide" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/widgets.py:1041 +msgid "Toggle" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/wizard/__init__.py:438 +msgid "" +"Choose your e-book device. If your device is not in the list, choose a " +"\"%s\" device." +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/wizard/__init__.py:499 +msgid "Moving library..." +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/wizard/__init__.py:515 +#: /home/kovid/work/calibre/src/calibre/gui2/wizard/__init__.py:516 +msgid "Failed to move library" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/wizard/__init__.py:570 +msgid "Invalid database" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/wizard/__init__.py:571 +msgid "" +"

      An invalid library already exists at %s, delete it before trying to move " +"the existing library.
      Error: %s" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/wizard/__init__.py:582 +msgid "Could not move library" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/wizard/__init__.py:657 +msgid "Select location for books" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/wizard/__init__.py:671 +msgid "" +"You must choose an empty folder for the calibre library. %s is not empty." +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/wizard/__init__.py:745 +msgid "welcome wizard" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/wizard/device_ui.py:54 +#: /home/kovid/work/calibre/src/calibre/gui2/wizard/device_ui.py:55 +#: /home/kovid/work/calibre/src/calibre/gui2/wizard/finish_ui.py:47 +#: /home/kovid/work/calibre/src/calibre/gui2/wizard/kindle_ui.py:47 +#: /home/kovid/work/calibre/src/calibre/gui2/wizard/library_ui.py:55 +#: /home/kovid/work/calibre/src/calibre/gui2/wizard/stanza_ui.py:47 +msgid "Welcome to calibre" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/wizard/device_ui.py:56 +#: /home/kovid/work/calibre/src/calibre/gui2/wizard/finish_ui.py:48 +#: /home/kovid/work/calibre/src/calibre/gui2/wizard/kindle_ui.py:48 +#: /home/kovid/work/calibre/src/calibre/gui2/wizard/library_ui.py:56 +#: /home/kovid/work/calibre/src/calibre/gui2/wizard/stanza_ui.py:48 +msgid "The one stop solution to all your e-book needs." +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/wizard/device_ui.py:57 +msgid "&Manufacturers" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/wizard/device_ui.py:58 +msgid "&Devices" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/wizard/finish_ui.py:49 +msgid "" +"

      Congratulations!

      You have successfully setup calibre. Press the %s " +"button to apply your settings." +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/wizard/finish_ui.py:50 +msgid "" +"

      Demo videos

      Videos demonstrating the various features of calibre are " +"available online." +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/wizard/finish_ui.py:51 +msgid "" +"

      User Manual

      A User Manual is also available online." +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/wizard/kindle_ui.py:49 +msgid "" +"

      calibre can automatically send books by email to your Kindle. To do that " +"you have to setup email delivery below. The easiest way is to setup a free " +"gmail account and click the Use gmail " +"button below. You will also have to register your gmail address in your " +"Amazon account." +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/wizard/kindle_ui.py:50 +msgid "&Kindle email:" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/wizard/library_ui.py:57 +msgid "Choose your &language:" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/wizard/library_ui.py:58 +msgid "" +"

      Choose a location for your books. When you add books to calibre, they " +"will be copied here. Use an empty folder for a new calibre library:" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/wizard/library_ui.py:59 +msgid "&Change" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/wizard/library_ui.py:60 +msgid "" +"If you have an existing calibre library, it will be copied to the new " +"location. If a calibre library already exists at the new location, calibre " +"will switch to using it." +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/wizard/send_email.py:34 +msgid "Using: %s:%s@%s:%s and %s encryption" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/wizard/send_email.py:39 +msgid "Sending..." +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/wizard/send_email.py:44 +msgid "Mail successfully sent" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/wizard/send_email.py:96 +msgid "OK to proceed?" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/wizard/send_email.py:97 +msgid "" +"This will display your email password on the screen. Is it OK to proceed?" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/wizard/send_email.py:140 +msgid "" +"If you are setting up a new hotmail account, you must log in to it once " +"before you will be able to send mails." +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/wizard/send_email.py:151 +msgid "Setup sending email using" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/wizard/send_email.py:153 +msgid "" +"If you don't have an account, you can sign up for a free {name} email " +"account at http://{url}. {extra}" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/wizard/send_email.py:160 +msgid "Your %s &email address:" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/wizard/send_email.py:161 +msgid "Your %s &username:" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/wizard/send_email.py:162 +msgid "Your %s &password:" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/wizard/send_email.py:180 +msgid "" +"If you plan to use email to send books to your Kindle, remember to add the " +"your %s email address to the allowed email addresses in your Amazon.com " +"Kindle management page." +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/wizard/send_email.py:187 +msgid "Setup" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/wizard/send_email.py:202 +#: /home/kovid/work/calibre/src/calibre/gui2/wizard/send_email.py:213 +#: /home/kovid/work/calibre/src/calibre/gui2/wizard/send_email.py:218 +msgid "Bad configuration" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/wizard/send_email.py:203 +msgid "You must set the From email address" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/wizard/send_email.py:214 +msgid "" +"You must either set both the username and password for the mail " +"server or no username and no password at all." +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/wizard/send_email.py:219 +msgid "Please enter a username and password or set encryption to None " +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/wizard/send_email.py:224 +msgid "" +"No username and password set for mailserver. Most mailservers need a " +"username and password. Are you sure?" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/wizard/send_email_ui.py:124 +msgid "Send email &from:" +msgstr "" + +#: /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 "" + +#: /home/kovid/work/calibre/src/calibre/gui2/wizard/send_email_ui.py:126 +msgid "" +"

      A mail server is useful if the service you are sending mail to only " +"accepts email from well know mail services." +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/wizard/send_email_ui.py:127 +msgid "Mail &Server" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/wizard/send_email_ui.py:128 +msgid "calibre can optionally use a server to send mail" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/wizard/send_email_ui.py:129 +msgid "&Hostname:" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/wizard/send_email_ui.py:130 +msgid "The hostname of your mail server. For e.g. smtp.gmail.com" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/wizard/send_email_ui.py:131 +msgid "&Port:" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/wizard/send_email_ui.py:132 +msgid "" +"The port your mail server listens for connections on. The default is 25" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/wizard/send_email_ui.py:134 +msgid "Your username on the mail server" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/wizard/send_email_ui.py:136 +msgid "Your password on the mail server" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/wizard/send_email_ui.py:137 +msgid "&Show" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/wizard/send_email_ui.py:138 +msgid "&Encryption:" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/wizard/send_email_ui.py:139 +msgid "" +"Use TLS encryption when connecting to the mail server. This is the most " +"common." +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/wizard/send_email_ui.py:140 +msgid "&TLS" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/wizard/send_email_ui.py:141 +msgid "Use SSL encryption when connecting to the mail server." +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/wizard/send_email_ui.py:142 +msgid "&SSL" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/wizard/send_email_ui.py:143 +msgid "WARNING: Using no encryption is highly insecure" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/wizard/send_email_ui.py:144 +msgid "&None" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/wizard/send_email_ui.py:145 +msgid "Use Gmail" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/wizard/send_email_ui.py:146 +msgid "Use Hotmail" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/wizard/send_email_ui.py:147 +msgid "&Test email" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/wizard/stanza_ui.py:49 +msgid "" +"

      If you use the Stanza e-" +"book app on your iPhone/iTouch, you can access your calibre book collection " +"directly on the device. To do this you have to turn on the calibre content " +"server." +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/wizard/stanza_ui.py:50 +msgid "Turn on the &content server" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/library/caches.py:161 +#: /home/kovid/work/calibre/src/calibre/library/caches.py:562 +#: /home/kovid/work/calibre/src/calibre/library/caches.py:576 +#: /home/kovid/work/calibre/src/calibre/library/caches.py:586 +msgid "checked" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/library/caches.py:161 +#: /home/kovid/work/calibre/src/calibre/library/caches.py:562 +#: /home/kovid/work/calibre/src/calibre/library/caches.py:576 +#: /home/kovid/work/calibre/src/calibre/library/caches.py:586 +#: /home/kovid/work/calibre/src/calibre/library/save_to_disk.py:214 +msgid "yes" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/library/caches.py:163 +#: /home/kovid/work/calibre/src/calibre/library/caches.py:561 +#: /home/kovid/work/calibre/src/calibre/library/caches.py:573 +#: /home/kovid/work/calibre/src/calibre/library/caches.py:583 +msgid "unchecked" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/library/caches.py:163 +#: /home/kovid/work/calibre/src/calibre/library/caches.py:561 +#: /home/kovid/work/calibre/src/calibre/library/caches.py:573 +#: /home/kovid/work/calibre/src/calibre/library/caches.py:583 +#: /home/kovid/work/calibre/src/calibre/library/save_to_disk.py:214 +msgid "no" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/library/caches.py:356 +msgid "today" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/library/caches.py:359 +msgid "yesterday" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/library/caches.py:362 +msgid "thismonth" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/library/caches.py:365 +#: /home/kovid/work/calibre/src/calibre/library/caches.py:366 +msgid "daysago" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/library/caches.py:563 +#: /home/kovid/work/calibre/src/calibre/library/caches.py:580 +msgid "blank" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/library/caches.py:563 +#: /home/kovid/work/calibre/src/calibre/library/caches.py:580 +msgid "empty" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/library/caches.py:564 +msgid "Invalid boolean query \"{0}\"" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/library/catalog.py:55 +msgid "" +"The fields to output when cataloging books in the database. Should be a " +"comma-separated list of fields.\n" +"Available fields: %s,\n" +"plus user-created custom fields.\n" +"Example: %s=title,authors,tags\n" +"Default: '%%default'\n" +"Applies to: CSV, XML output formats" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/library/catalog.py:68 +msgid "" +"Output field to sort on.\n" +"Available fields: author_sort, id, rating, size, timestamp, title_sort\n" +"Default: '%default'\n" +"Applies to: CSV, XML output formats" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/library/catalog.py:242 +msgid "" +"The fields to output when cataloging books in the database. Should be a " +"comma-separated list of fields.\n" +"Available fields: %s.\n" +"plus user-created custom fields.\n" +"Example: %s=title,authors,tags\n" +"Default: '%%default'\n" +"Applies to: BIBTEX output format" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/library/catalog.py:255 +msgid "" +"Output field to sort on.\n" +"Available fields: author_sort, id, rating, size, timestamp, title.\n" +"Default: '%default'\n" +"Applies to: BIBTEX output format" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/library/catalog.py:264 +msgid "" +"Create a citation for BibTeX entries.\n" +"Boolean value: True, False\n" +"Default: '%default'\n" +"Applies to: BIBTEX output format" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/library/catalog.py:273 +msgid "" +"Create a file entry if formats is selected for BibTeX entries.\n" +"Boolean value: True, False\n" +"Default: '%default'\n" +"Applies to: BIBTEX output format" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/library/catalog.py:282 +msgid "" +"The template for citation creation from database fields.\n" +"Should be a template with {} enclosed fields.\n" +"Available fields: %s.\n" +"Default: '%%default'\n" +"Applies to: BIBTEX output format" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/library/catalog.py:292 +msgid "" +"BibTeX file encoding output.\n" +"Available types: utf8, cp1252, ascii.\n" +"Default: '%default'\n" +"Applies to: BIBTEX output format" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/library/catalog.py:301 +msgid "" +"BibTeX file encoding flag.\n" +"Available types: strict, replace, ignore, backslashreplace.\n" +"Default: '%default'\n" +"Applies to: BIBTEX output format" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/library/catalog.py:310 +msgid "" +"Entry type for BibTeX catalog.\n" +"Available types: book, misc, mixed.\n" +"Default: '%default'\n" +"Applies to: BIBTEX output format" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/library/catalog.py:616 +msgid "" +"Title of generated catalog used as title in metadata.\n" +"Default: '%default'\n" +"Applies to: ePub, MOBI output formats" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/library/catalog.py:623 +msgid "" +"Save the output from different stages of the conversion pipeline to the " +"specified directory. Useful if you are unsure at which stage of the " +"conversion process a bug is occurring.\n" +"Default: '%default'\n" +"Applies to: ePub, MOBI output formats" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/library/catalog.py:633 +msgid "" +"field:pattern specifying custom field/contents indicating book should be " +"excluded.\n" +"Default: '%default'\n" +"Applies to ePub, MOBI output formats" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/library/catalog.py:640 +msgid "" +"Regex describing tags to exclude as genres.\n" +"Default: '%default' excludes bracketed tags, e.g. '[]'\n" +"Applies to: ePub, MOBI output formats" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/library/catalog.py:646 +msgid "" +"Comma-separated list of tag words indicating book should be excluded from " +"output.For example: 'skip' will match 'skip this book' and 'Skip will like " +"this'.Default: '%default'\n" +"Applies to: ePub, MOBI output formats" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/library/catalog.py:654 +msgid "" +"Include 'Authors' section in catalog.\n" +"Default: '%default'\n" +"Applies to: ePub, MOBI output formats" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/library/catalog.py:661 +msgid "" +"Include 'Descriptions' section in catalog.\n" +"Default: '%default'\n" +"Applies to: ePub, MOBI output formats" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/library/catalog.py:668 +msgid "" +"Include 'Genres' section in catalog.\n" +"Default: '%default'\n" +"Applies to: ePub, MOBI output formats" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/library/catalog.py:675 +msgid "" +"Include 'Titles' section in catalog.\n" +"Default: '%default'\n" +"Applies to: ePub, MOBI output formats" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/library/catalog.py:682 +msgid "" +"Include 'Series' section in catalog.\n" +"Default: '%default'\n" +"Applies to: ePub, MOBI output formats" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/library/catalog.py:689 +msgid "" +"Include 'Recently Added' section in catalog.\n" +"Default: '%default'\n" +"Applies to: ePub, MOBI output formats" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/library/catalog.py:696 +msgid "" +"Custom field containing note text to insert in Description header.\n" +"Default: '%default'\n" +"Applies to: ePub, MOBI output formats" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/library/catalog.py:703 +msgid "" +":[before|after]:[True|False] specifying:\n" +" Custom field containing notes to merge with Comments\n" +" [before|after] Placement of notes with respect to Comments\n" +" [True|False] - A horizontal rule is inserted between notes and Comments\n" +"Default: '%default'\n" +"Applies to ePub, MOBI output formats" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/library/catalog.py:713 +msgid "" +"Specifies the output profile. In some cases, an output profile is required " +"to optimize the catalog for the device. For example, 'kindle' or " +"'kindle_dx' creates a structured Table of Contents with Sections and " +"Articles.\n" +"Default: '%default'\n" +"Applies to: ePub, MOBI output formats" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/library/catalog.py:720 +msgid "" +"field:pattern indicating book has been read.\n" +"Default: '%default'\n" +"Applies to ePub, MOBI output formats" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/library/catalog.py:726 +msgid "" +"Size hint (in inches) for book covers in catalog.\n" +"Range: 1.0 - 2.0\n" +"Default: '%default'\n" +"Applies to ePub, MOBI output formats" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/library/catalog.py:734 +msgid "" +"Tag indicating book to be displayed as wishlist item.\n" +"Default: '%default'\n" +"Applies to: ePub, MOBI output formats" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/library/catalog.py:1418 +msgid "No enabled genres found to catalog.\n" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/library/catalog.py:1422 +msgid "No books available to catalog" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/library/catalog.py:1497 +msgid "" +"Inconsistent Author Sort values for\n" +"Author '{0}':\n" +"'{1}' <> '{2}'\n" +"Unable to build MOBI catalog.\n" +"\n" +"Select all books by '{0}', apply correct Author Sort value in Edit Metadata " +"dialog, then rebuild the catalog.\n" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/library/catalog.py:1514 +msgid "" +"Warning: inconsistent Author Sort values for\n" +"Author '{0}':\n" +"'{1}' <> '{2}'\n" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/library/catalog.py:1710 +msgid "" +"No books found to catalog.\n" +"Check 'Excluded books' criteria in E-book options.\n" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/library/catalog.py:1712 +msgid "No books available to include in catalog" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/library/catalog.py:5042 +msgid "" +"\n" +"*** Adding 'By Authors' Section required for MOBI output ***" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/library/check_library.py:26 +msgid "Invalid titles" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/library/check_library.py:27 +msgid "Extra titles" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/library/check_library.py:28 +msgid "Invalid authors" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/library/check_library.py:29 +msgid "Extra authors" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/library/check_library.py:30 +msgid "Missing book formats" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/library/check_library.py:31 +msgid "Extra book formats" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/library/check_library.py:32 +msgid "Unknown files in books" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/library/check_library.py:33 +msgid "Missing covers files" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/library/check_library.py:34 +msgid "Cover files not in database" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/library/check_library.py:35 +msgid "Folders raising exception" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/library/cli.py:43 +msgid "" +"Path to the calibre library. Default is to use the path stored in the " +"settings." +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/library/cli.py:124 +msgid "" +"%prog list [options]\n" +"\n" +"List the books available in the calibre database.\n" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/library/cli.py:132 +msgid "" +"The fields to display when listing books in the database. Should be a comma " +"separated list of fields.\n" +"Available fields: %s\n" +"Default: %%default. The special field \"all\" can be used to select all " +"fields. Only has effect in the text output format." +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/library/cli.py:139 +msgid "" +"The field by which to sort the results.\n" +"Available fields: %s\n" +"Default: %%default" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/library/cli.py:141 +msgid "Sort results in ascending order" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/library/cli.py:143 +msgid "" +"Filter the results by the search query. For the format of the search query, " +"please see the search related documentation in the User Manual. Default is " +"to do no filtering." +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/library/cli.py:145 +#: /home/kovid/work/calibre/src/calibre/library/cli.py:1044 +msgid "" +"The maximum width of a single line in the output. Defaults to detecting " +"screen size." +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/library/cli.py:146 +msgid "The string used to separate fields. Default is a space." +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/library/cli.py:147 +msgid "" +"The prefix for all file paths. Default is the absolute path to the library " +"folder." +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/library/cli.py:169 +msgid "Invalid fields. Available fields:" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/library/cli.py:176 +msgid "Invalid sort field. Available fields:" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/library/cli.py:248 +msgid "" +"The following books were not added as they already exist in the database " +"(see --duplicates option):" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/library/cli.py:272 +msgid "" +"%prog add [options] file1 file2 file3 ...\n" +"\n" +"Add the specified files as books to the database. You can also specify " +"directories, see\n" +"the directory related options below.\n" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/library/cli.py:281 +msgid "" +"Assume that each directory has only a single logical book and that all files " +"in it are different e-book formats of that book" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/library/cli.py:283 +msgid "Process directories recursively" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/library/cli.py:285 +msgid "" +"Add books to database even if they already exist. Comparison is done based " +"on book titles." +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/library/cli.py:287 +msgid "Add an empty book (a book with no formats)" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/library/cli.py:289 +msgid "Set the title of the added empty book" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/library/cli.py:291 +msgid "Set the authors of the added empty book" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/library/cli.py:293 +msgid "Set the ISBN of the added empty book" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/library/cli.py:319 +msgid "You must specify at least one file to add" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/library/cli.py:336 +msgid "" +"%prog remove ids\n" +"\n" +"Remove the books identified by ids from the database. ids should be a comma " +"separated list of id numbers (you can get id numbers by using the list " +"command). For example, 23,34,57-85\n" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/library/cli.py:351 +msgid "You must specify at least one book to remove" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/library/cli.py:370 +msgid "" +"%prog add_format [options] id ebook_file\n" +"\n" +"Add the ebook in ebook_file to the available formats for the logical book " +"identified by id. You can get id by using the list command. If the format " +"already exists, it is replaced.\n" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/library/cli.py:385 +msgid "You must specify an id and an ebook file" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/library/cli.py:390 +msgid "ebook file must have an extension" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/library/cli.py:398 +msgid "" +"\n" +"%prog remove_format [options] id fmt\n" +"\n" +"Remove the format fmt from the logical book identified by id. You can get id " +"by using the list command. fmt should be a file extension like LRF or TXT or " +"EPUB. If the logical book does not have fmt available, do nothing.\n" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/library/cli.py:415 +msgid "You must specify an id and a format" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/library/cli.py:433 +msgid "" +"\n" +"%prog show_metadata [options] id\n" +"\n" +"Show the metadata stored in the calibre database for the book identified by " +"id.\n" +"id is an id number from the list command.\n" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/library/cli.py:441 +msgid "Print metadata in OPF form (XML)" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/library/cli.py:450 +msgid "You must specify an id" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/library/cli.py:465 +msgid "" +"\n" +"%prog set_metadata [options] id /path/to/metadata.opf\n" +"\n" +"Set the metadata stored in the calibre database for the book identified by " +"id\n" +"from the OPF file metadata.opf. id is an id number from the list command. " +"You\n" +"can get a quick feel for the OPF format by using the --as-opf switch to the\n" +"show_metadata command.\n" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/library/cli.py:481 +msgid "You must specify an id and a metadata file" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/library/cli.py:501 +msgid "" +"%prog export [options] ids\n" +"\n" +"Export the books specified by ids (a comma separated list) to the " +"filesystem.\n" +"The export operation saves all formats of the book, its cover and metadata " +"(in\n" +"an opf file). You can get id numbers from the list command.\n" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/library/cli.py:509 +msgid "Export all books in database, ignoring the list of ids." +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/library/cli.py:511 +msgid "Export books to the specified directory. Default is" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/library/cli.py:513 +msgid "Export all books into a single directory" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/library/cli.py:520 +msgid "Specifying this switch will turn this behavior off." +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/library/cli.py:543 +msgid "You must specify some ids or the %s option" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/library/cli.py:556 +msgid "" +"%prog add_custom_column [options] label name datatype\n" +"\n" +"Create a custom column. label is the machine friendly name of the column. " +"Should\n" +"not contain spaces or colons. name is the human friendly name of the " +"column.\n" +"datatype is one of: {0}\n" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/library/cli.py:565 +msgid "" +"This column stores tag like data (i.e. multiple comma separated values). " +"Only applies if datatype is text." +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/library/cli.py:569 +msgid "" +"A dictionary of options to customize how the data in this column will be " +"interpreted. This is a JSON string. For enumeration columns, use --" +"display='{\"enum_values\":[\"val1\", \"val2\"]}'" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/library/cli.py:583 +msgid "You must specify label, name and datatype" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/library/cli.py:644 +msgid "" +"\n" +" %prog catalog /path/to/destination.(CSV|EPUB|MOBI|XML ...) [options]\n" +"\n" +" Export a catalog in format specified by path/to/destination extension.\n" +" Options control how entries are displayed in the generated catalog " +"ouput.\n" +" " +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/library/cli.py:658 +msgid "" +"Comma-separated list of database IDs to catalog.\n" +"If declared, --search is ignored.\n" +"Default: all" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/library/cli.py:662 +msgid "" +"Filter the results by the search query. For the format of the search query, " +"please see the search-related documentation in the User Manual.\n" +"Default: no filtering" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/library/cli.py:668 +#: /home/kovid/work/calibre/src/calibre/web/fetch/simple.py:499 +msgid "Show detailed output information. Useful for debugging" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/library/cli.py:681 +msgid "Error: You must specify a catalog output file" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/library/cli.py:727 +msgid "" +"\n" +" %prog set_custom [options] column id value\n" +"\n" +" Set the value of a custom column for the book identified by id.\n" +" You can get a list of ids using the list command.\n" +" You can get a list of custom column names using the custom_columns\n" +" command.\n" +" " +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/library/cli.py:738 +msgid "" +"If the column stores multiple values, append the specified values to the " +"existing ones, instead of replacing them." +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/library/cli.py:749 +msgid "Error: You must specify a field name, id and value" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/library/cli.py:768 +msgid "" +"\n" +" %prog custom_columns [options]\n" +"\n" +" List available custom columns. Shows column labels and ids.\n" +" " +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/library/cli.py:775 +msgid "Show details for each column." +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/library/cli.py:787 +msgid "You will lose all data in the column: %r. Are you sure (y/n)? " +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/library/cli.py:789 +msgid "y" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/library/cli.py:795 +msgid "" +"\n" +" %prog remove_custom_column [options] label\n" +"\n" +" Remove the custom column identified by label. You can see available\n" +" columns with the custom_columns command.\n" +" " +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/library/cli.py:803 +msgid "Do not ask for confirmation" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/library/cli.py:813 +msgid "Error: You must specify a column label" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/library/cli.py:823 +msgid "" +"\n" +" %prog saved_searches [options] list\n" +" %prog saved_searches add name search\n" +" %prog saved_searches remove name\n" +"\n" +" Manage the saved searches stored in this database.\n" +" If you try to add a query with a name that already exists, it will be\n" +" replaced.\n" +" " +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/library/cli.py:841 +msgid "Error: You must specify an action (add|remove|list)" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/library/cli.py:849 +msgid "Name:" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/library/cli.py:850 +msgid "Search string:" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/library/cli.py:856 +msgid "Error: You must specify a name and a search string" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/library/cli.py:859 +msgid "added" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/library/cli.py:864 +msgid "Error: You must specify a name" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/library/cli.py:867 +msgid "removed" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/library/cli.py:871 +msgid "Error: Action %s not recognized, must be one of: (add|remove|list)" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/library/cli.py:879 +msgid "" +"%prog check_library [options]\n" +"\n" +"Perform some checks on the filesystem representing a library. Reports are " +"{0}\n" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/library/cli.py:886 +#: /home/kovid/work/calibre/src/calibre/library/cli.py:1036 +msgid "Output in CSV" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/library/cli.py:889 +msgid "" +"Comma-separated list of reports.\n" +"Default: all" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/library/cli.py:893 +msgid "" +"Comma-separated list of extensions to ignore.\n" +"Default: all" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/library/cli.py:897 +msgid "" +"Comma-separated list of names to ignore.\n" +"Default: all" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/library/cli.py:927 +msgid "Unknown report check" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/library/cli.py:960 +msgid "" +"%prog restore_database [options]\n" +"\n" +"Restore this database from the metadata stored in OPF files in each\n" +"directory of the calibre library. This is useful if your metadata.db file\n" +"has been corrupted.\n" +"\n" +"WARNING: This command completely regenerates your database. You will lose\n" +"all saved searches, user categories, plugboards, stored per-book conversion\n" +"settings, and custom recipes. Restored metadata will only be as accurate as\n" +"what is found in the OPF files.\n" +" " +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/library/cli.py:975 +msgid "" +"Really do the recovery. The command will not run unless this option is " +"specified." +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/library/cli.py:988 +msgid "You must provide the %s option to do a recovery" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/library/cli.py:1025 +msgid "" +"%prog list_categories [options]\n" +"\n" +"Produce a report of the category information in the database. The\n" +"information is the equivalent of what is shown in the tags pane.\n" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/library/cli.py:1033 +msgid "" +"Output only the number of items in a category instead of the counts per item " +"within the category" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/library/cli.py:1038 +msgid "" +"The character to put around the category value in CSV mode. Default is " +"quotes (\")." +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/library/cli.py:1041 +msgid "" +"Comma-separated list of category lookup names.\n" +"Default: all" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/library/cli.py:1047 +msgid "The string used to separate fields in CSV mode. Default is a comma." +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/library/cli.py:1085 +msgid "CATEGORY ITEMS" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/library/cli.py:1157 +msgid "" +"%%prog command [options] [arguments]\n" +"\n" +"%%prog is the command line interface to the calibre books database.\n" +"\n" +"command is one of:\n" +" %s\n" +"\n" +"For help on an individual command: %%prog command --help\n" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/library/custom_columns.py:594 +msgid "No label was provided" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/library/custom_columns.py:596 +msgid "" +"The label must contain only lower case letters, digits and underscores, and " +"start with a letter" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/library/database2.py:64 +msgid "%sAverage rating is %3.1f" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/library/database2.py:1017 +msgid "Main" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/library/database2.py:3105 +msgid "

      Migrating old database to ebook library in %s

      " +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/library/database2.py:3134 +msgid "Copying %s" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/library/database2.py:3151 +msgid "Compacting database" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/library/field_metadata.py:148 +msgid "Ratings" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/library/field_metadata.py:181 +msgid "Identifiers" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/library/field_metadata.py:191 +msgid "Author Sort" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/library/field_metadata.py:301 +msgid "Title Sort" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/library/restore.py:126 +msgid "Processed" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/library/restore.py:192 +msgid "creating custom column " +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/library/save_to_disk.py:32 +msgid "The title" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/library/save_to_disk.py:33 +msgid "The authors" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/library/save_to_disk.py:34 +msgid "" +"The author sort string. To use only the first letter of the name use " +"{author_sort[0]}" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/library/save_to_disk.py:36 +msgid "The tags" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/library/save_to_disk.py:37 +msgid "The series" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/library/save_to_disk.py:38 +msgid "" +"The series number. To get leading zeros use {series_index:0>3s} or " +"{series_index:>3s} for leading spaces" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/library/save_to_disk.py:41 +msgid "The rating" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/library/save_to_disk.py:42 +msgid "The ISBN" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/library/save_to_disk.py:43 +msgid "The publisher" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/library/save_to_disk.py:44 +msgid "The date" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/library/save_to_disk.py:45 +msgid "The published date" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/library/save_to_disk.py:46 +msgid "The date when the metadata for this book record was last modified" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/library/save_to_disk.py:48 +msgid "The calibre internal id" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/library/save_to_disk.py:76 +msgid "Options to control saving to disk" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/library/save_to_disk.py:82 +msgid "" +"Normally, calibre will update the metadata in the saved files from what is " +"in the calibre library. Makes saving to disk slower." +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/library/save_to_disk.py:85 +msgid "" +"Normally, calibre will write the metadata into a separate OPF file along " +"with the actual e-book files." +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/library/save_to_disk.py:88 +msgid "" +"Normally, calibre will save the cover in a separate file along with the " +"actual e-book file(s)." +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/library/save_to_disk.py:91 +msgid "" +"Comma separated list of formats to save for each book. By default all " +"available formats are saved." +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/library/save_to_disk.py:94 +msgid "" +"The template to control the filename and directory structure of the saved " +"files. Default is \"%s\" which will save books into a per-author " +"subdirectory with filenames containing title and author. Available controls " +"are: {%s}" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/library/save_to_disk.py:99 +msgid "" +"The template to control the filename and directory structure of files sent " +"to the device. Default is \"%s\" which will save books into a per-author " +"directory with filenames containing title and author. Available controls " +"are: {%s}" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/library/save_to_disk.py:106 +msgid "" +"Normally, calibre will convert all non English characters into English " +"equivalents for the file names. WARNING: If you turn this off, you may " +"experience errors when saving, depending on how well the filesystem you are " +"saving to supports unicode." +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/library/save_to_disk.py:112 +#: /home/kovid/work/calibre/src/calibre/library/save_to_disk.py:115 +msgid "" +"The format in which to display dates. %d - day, %b - month, %Y - year. " +"Default is: %b, %Y" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/library/save_to_disk.py:118 +msgid "Convert paths to lowercase." +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/library/save_to_disk.py:120 +msgid "Replace whitespace with underscores." +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/library/save_to_disk.py:370 +#: /home/kovid/work/calibre/src/calibre/library/save_to_disk.py:398 +msgid "Requested formats not available" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/library/server/__init__.py:21 +msgid "Settings to control the calibre content server" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/library/server/__init__.py:25 +msgid "The port on which to listen. Default is %default" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/library/server/__init__.py:27 +msgid "The server timeout in seconds. Default is %default" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/library/server/__init__.py:29 +msgid "The max number of worker threads to use. Default is %default" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/library/server/__init__.py:31 +msgid "Set a password to restrict access. By default access is unrestricted." +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/library/server/__init__.py:33 +msgid "Username for access. By default, it is: %default" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/library/server/__init__.py:37 +msgid "The maximum size for displayed covers. Default is %default." +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/library/server/__init__.py:39 +msgid "" +"The maximum number of matches to return per OPDS query. This affects Stanza, " +"WordPlayer, etc. integration." +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/library/server/__init__.py:43 +msgid "" +"Group items in categories such as author/tags by first letter when there are " +"more than this number of items. Default: %default. Set to a large number to " +"disable grouping." +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/library/server/__init__.py:48 +msgid "" +"Prefix to prepend to all URLs. Useful for reverseproxying to this server " +"from Apache/nginx/etc." +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/library/server/browse.py:64 +#: /home/kovid/work/calibre/src/calibre/library/server/browse.py:498 +msgid "Loading, please wait" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/library/server/browse.py:90 +#: /home/kovid/work/calibre/src/calibre/library/server/browse.py:111 +msgid "Go to" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/library/server/browse.py:106 +msgid "First" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/library/server/browse.py:106 +msgid "Last" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/library/server/browse.py:109 +msgid "Browsing %d books" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/library/server/browse.py:126 +#: /home/kovid/work/calibre/src/calibre/library/server/browse.py:255 +msgid "Average rating" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/library/server/browse.py:127 +msgid "%s: %.1f stars" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/library/server/browse.py:163 +msgid "%d stars" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/library/server/browse.py:256 +msgid "Popularity" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/library/server/browse.py:275 +msgid "Sort by" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/library/server/browse.py:278 +msgid "library" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/library/server/browse.py:279 +msgid "home" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/library/server/browse.py:340 +#: /home/kovid/work/calibre/src/calibre/library/server/browse.py:612 +#: /home/kovid/work/calibre/src/calibre/library/server/opds.py:573 +msgid "Newest" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/library/server/browse.py:341 +#: /home/kovid/work/calibre/src/calibre/library/server/browse.py:613 +msgid "All books" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/library/server/browse.py:386 +#: /home/kovid/work/calibre/src/calibre/library/server/browse.py:451 +msgid "Browse books by" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/library/server/browse.py:391 +msgid "Choose a category to browse by:" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/library/server/browse.py:521 +msgid "Browsing by" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/library/server/browse.py:522 +msgid "Up" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/library/server/browse.py:648 +msgid "in" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/library/server/browse.py:651 +msgid "Books in" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/library/server/browse.py:740 +msgid "Other formats" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/library/server/browse.py:747 +msgid "Read %(title)s in the %(fmt)s format" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/library/server/browse.py:752 +msgid "Get" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/library/server/browse.py:765 +msgid "Details" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/library/server/browse.py:767 +msgid "Permalink" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/library/server/browse.py:768 +msgid "A permanent link to this book" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/library/server/browse.py:779 +msgid "This book has been deleted" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/library/server/browse.py:865 +msgid "in search" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/library/server/browse.py:867 +msgid "Matching books" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/library/server/main.py:39 +msgid "" +"[options]\n" +"\n" +"Start the calibre content server. The calibre content server\n" +"exposes your calibre library over the internet. The default interface\n" +"allows you to browse you calibre library by categories. You can also\n" +"access an interface optimized for mobile browsers at /mobile and an\n" +"OPDS based interface for use with reading applications at /opds.\n" +"\n" +"The OPDS interface is advertised via BonJour automatically.\n" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/library/server/main.py:52 +msgid "Path to the library folder to serve with the content server" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/library/server/main.py:54 +msgid "Write process PID to the specified file" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/library/server/main.py:58 +msgid "" +"Specifies a restriction to be used for this invocation. This option " +"overrides any per-library settings specified in the GUI" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/library/server/main.py:62 +msgid "" +"Auto reload server when source code changes. May not work in all " +"environments." +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/library/server/opds.py:125 +msgid "%d book" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/library/server/opds.py:148 +msgid "%d items" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/library/server/opds.py:166 +msgid "RATING: %s
      " +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/library/server/opds.py:169 +msgid "TAGS: %s
      " +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/library/server/opds.py:174 +msgid "SERIES: %s [%s]
      " +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/library/server/opds.py:267 +msgid "Books in your library" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/library/server/opds.py:273 +msgid "By " +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/library/server/opds.py:274 +msgid "Books sorted by " +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/utils/config.py:34 +msgid "%sUsage%s: %s\n" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/utils/config.py:85 +msgid "Created by " +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/utils/config.py:86 +msgid "" +"Whenever you pass arguments to %prog that have spaces in them, enclose the " +"arguments in quotation marks." +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/utils/config_base.py:375 +msgid "Path to the database in which books are stored" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/utils/config_base.py:377 +msgid "Pattern to guess metadata from filenames" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/utils/config_base.py:379 +msgid "Access key for isbndb.com" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/utils/config_base.py:381 +msgid "Default timeout for network operations (seconds)" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/utils/config_base.py:383 +msgid "Path to directory in which your library of books is stored" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/utils/config_base.py:385 +msgid "The language in which to display the user interface" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/utils/config_base.py:387 +msgid "The default output format for ebook conversions." +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/utils/config_base.py:391 +msgid "Ordered list of formats to prefer for input." +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/utils/config_base.py:393 +msgid "Read metadata from files" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/utils/config_base.py:395 +msgid "" +"The priority of worker processes. A higher priority means they run faster " +"and consume more resources. Most tasks like conversion/news download/adding " +"books/etc. are affected by this setting." +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/utils/config_base.py:400 +msgid "Swap author first and last names when reading metadata" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/utils/config_base.py:402 +msgid "Add new formats to existing book records" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/utils/config_base.py:404 +msgid "Tags to apply to books added to the library" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/utils/config_base.py:408 +msgid "List of named saved searches" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/utils/config_base.py:409 +msgid "User-created tag browser categories" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/utils/config_base.py:411 +msgid "How and when calibre updates metadata on the device." +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/utils/config_base.py:413 +msgid "" +"When searching for text without using lookup prefixes, as for example, Red " +"instead of title:Red, limit the columns searched to those named below." +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/utils/config_base.py:418 +msgid "" +"Choose columns to be searched when not using prefixes, as for example, when " +"searching for Redd instead of title:Red. Enter a list of search/lookup names " +"separated by commas. Only takes effect if you set the option to limit search " +"columns above." +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/utils/formatter.py:27 +msgid "failed to scan program. Invalid input {0}" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/utils/formatter.py:32 +msgid " near " +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/utils/formatter.py:38 +msgid "end of program" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/utils/formatter.py:75 +msgid "syntax error - program ends before EOF" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/utils/formatter.py:101 +msgid "unknown id " +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/utils/formatter.py:107 +msgid "unknown function {0}" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/utils/formatter.py:126 +msgid "missing closing parenthesis" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/utils/formatter.py:145 +msgid "expression is not function or constant" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/utils/formatter.py:179 +msgid "format: type {0} requires an integer value, got {1}" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/utils/formatter.py:185 +msgid "format: type {0} requires a decimal (float) value, got {1}" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/utils/formatter.py:296 +msgid "%s: unknown function" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/utils/formatter.py:343 +msgid "No such variable " +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/utils/formatter_functions.py:58 +msgid "No documentation provided" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/utils/formatter_functions.py:79 +msgid "Exception " +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/utils/formatter_functions.py:97 +msgid "" +"strcmp(x, y, lt, eq, gt) -- does a case-insensitive comparison of x and y as " +"strings. Returns lt if x < y. Returns eq if x == y. Otherwise returns gt." +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/utils/formatter_functions.py:112 +msgid "" +"cmp(x, y, lt, eq, gt) -- compares x and y after converting both to numbers. " +"Returns lt if x < y. Returns eq if x == y. Otherwise returns gt." +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/utils/formatter_functions.py:127 +msgid "" +"strcat(a, b, ...) -- can take any number of arguments. Returns a string " +"formed by concatenating all the arguments" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/utils/formatter_functions.py:140 +msgid "" +"add(x, y) -- returns x + y. Throws an exception if either x or y are not " +"numbers." +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/utils/formatter_functions.py:150 +msgid "" +"subtract(x, y) -- returns x - y. Throws an exception if either x or y are " +"not numbers." +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/utils/formatter_functions.py:160 +msgid "" +"multiply(x, y) -- returns x * y. Throws an exception if either x or y are " +"not numbers." +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/utils/formatter_functions.py:170 +msgid "" +"divide(x, y) -- returns x / y. Throws an exception if either x or y are not " +"numbers." +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/utils/formatter_functions.py:180 +msgid "" +"template(x) -- evaluates x as a template. The evaluation is done in its own " +"context, meaning that variables are not shared between the caller and the " +"template evaluation. Because the { and } characters are special, you must " +"use [[ for the { character and ]] for the } character; they are converted " +"automatically. For example, template('[[title_sort]]') will evaluate the " +"template {title_sort} and return its value." +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/utils/formatter_functions.py:195 +msgid "" +"eval(template) -- evaluates the template, passing the local variables (those " +"'assign'ed to) instead of the book metadata. This permits using the " +"template processor to construct complex results from local variables." +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/utils/formatter_functions.py:208 +msgid "" +"assign(id, val) -- assigns val to id, then returns val. id must be an " +"identifier, not an expression" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/utils/formatter_functions.py:218 +msgid "" +"print(a, b, ...) -- prints the arguments to standard output. Unless you " +"start calibre from the command line (calibre-debug -g), the output will go " +"to a black hole." +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/utils/formatter_functions.py:229 +msgid "field(name) -- returns the metadata field named by name" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/utils/formatter_functions.py:237 +msgid "" +"raw_field(name) -- returns the metadata field named by name without applying " +"any formatting." +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/utils/formatter_functions.py:246 +msgid "" +"substr(str, start, end) -- returns the start'th through the end'th " +"characters of str. The first character in str is the zero'th character. If " +"end is negative, then it indicates that many characters counting from the " +"right. If end is zero, then it indicates the last character. For example, " +"substr('12345', 1, 0) returns '2345', and substr('12345', 1, -1) returns " +"'234'." +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/utils/formatter_functions.py:259 +msgid "" +"lookup(val, pattern, field, pattern, field, ..., else_field) -- like switch, " +"except the arguments are field (metadata) names, not text. The value of the " +"appropriate field will be fetched and used. Note that because composite " +"columns are fields, you can use this function in one composite field to use " +"the value of some other composite field. This is extremely useful when " +"constructing variable save paths" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/utils/formatter_functions.py:274 +msgid "lookup requires either 2 or an odd number of arguments" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/utils/formatter_functions.py:286 +msgid "" +"test(val, text if not empty, text if empty) -- return `text if not empty` if " +"the field is not empty, otherwise return `text if empty`" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/utils/formatter_functions.py:298 +msgid "" +"contains(val, pattern, text if match, text if not match) -- checks if field " +"contains matches for the regular expression `pattern`. Returns `text if " +"match` if matches are found, otherwise it returns `text if no match`" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/utils/formatter_functions.py:313 +msgid "" +"switch(val, pattern, value, pattern, value, ..., else_value) -- for each " +"`pattern, value` pair, checks if the field matches the regular expression " +"`pattern` and if so, returns that `value`. If no pattern matches, then " +"else_value is returned. You can have as many `pattern, value` pairs as you " +"want" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/utils/formatter_functions.py:321 +msgid "switch requires an odd number of arguments" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/utils/formatter_functions.py:333 +msgid "" +"re(val, pattern, replacement) -- return the field after applying the regular " +"expression. All instances of `pattern` are replaced with `replacement`. As " +"in all of calibre, these are python-compatible regular expressions" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/utils/formatter_functions.py:344 +msgid "" +"ifempty(val, text if empty) -- return val if val is not empty, otherwise " +"return `text if empty`" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/utils/formatter_functions.py:356 +msgid "" +"shorten(val, left chars, middle text, right chars) -- Return a shortened " +"version of the field, consisting of `left chars` characters from the " +"beginning of the field, followed by `middle text`, followed by `right chars` " +"characters from the end of the string. `Left chars` and `right chars` must " +"be integers. For example, assume the title of the book is `Ancient English " +"Laws in the Times of Ivanhoe`, and you want it to fit in a space of at most " +"15 characters. If you use {title:shorten(9,-,5)}, the result will be " +"`Ancient E-nhoe`. If the field's length is less than left chars + right " +"chars + the length of `middle text`, then the field will be used intact. For " +"example, the title `The Dome` would not be changed." +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/utils/formatter_functions.py:381 +msgid "" +"count(val, separator) -- interprets the value as a list of items separated " +"by `separator`, returning the number of items in the list. Most lists use a " +"comma as the separator, but authors uses an ampersand. Examples: " +"{tags:count(,)}, {authors:count(&)}" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/utils/formatter_functions.py:392 +msgid "" +"list_item(val, index, separator) -- interpret the value as a list of items " +"separated by `separator`, returning the `index`th item. The first item is " +"number zero. The last item can be returned using `list_item(-1,separator)`. " +"If the item is not in the list, then the empty value is returned. The " +"separator has the same meaning as in the count function." +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/utils/formatter_functions.py:412 +msgid "" +"select(val, key) -- interpret the value as a comma-separated list of items, " +"with the items being \"id:value\". Find the pair with theid equal to key, " +"and return the corresponding value." +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/utils/formatter_functions.py:429 +msgid "" +"sublist(val, start_index, end_index, separator) -- interpret the value as a " +"list of items separated by `separator`, returning a new list made from the " +"`start_index`th to the `end_index`th item. The first item is number zero. If " +"an index is negative, then it counts from the end of the list. As a special " +"case, an end_index of zero is assumed to be the length of the list. Examples " +"using basic template mode and assuming that the tags column (which is comma-" +"separated) contains \"A, B, C\": {tags:sublist(0,1,\\,)} returns \"A\". " +"{tags:sublist(-1,0,\\,)} returns \"C\". {tags:sublist(0,-1,\\,)} returns " +"\"A, B\"." +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/utils/formatter_functions.py:458 +msgid "" +"subitems(val, start_index, end_index) -- This function is used to break " +"apart lists of items such as genres. It interprets the value as a comma-" +"separated list of items, where each item is a period-separated list. Returns " +"a new list made by first finding all the period-separated items, then for " +"each such item extracting the start_index`th to the `end_index`th " +"components, then combining the results back together. The first component in " +"a period-separated list has an index of zero. If an index is negative, then " +"it counts from the end of the list. As a special case, an end_index of zero " +"is assumed to be the length of the list. Example using basic template mode " +"and assuming a #genre value of \"A.B.C\": {#genre:subitems(0,1)} returns " +"\"A\". {#genre:subitems(0,2)} returns \"A.B\". {#genre:subitems(1,0)} " +"returns \"B.C\". Assuming a #genre value of \"A.B.C, D.E.F\", " +"{#genre:subitems(0,1)} returns \"A, D\". {#genre:subitems(0,2)} returns " +"\"A.B, D.E\"" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/utils/formatter_functions.py:495 +msgid "" +"format_date(val, format_string) -- format the value, which must be a date " +"field, using the format_string, returning a string. The formatting codes " +"are: d : the day as number without a leading zero (1 to 31) dd : the " +"day as number with a leading zero (01 to 31) ddd : the abbreviated " +"localized day name (e.g. \"Mon\" to \"Sun\"). dddd : the long localized day " +"name (e.g. \"Monday\" to \"Sunday\"). M : the month as number without a " +"leading zero (1 to 12). MM : the month as number with a leading zero (01 " +"to 12) MMM : the abbreviated localized month name (e.g. \"Jan\" to " +"\"Dec\"). MMMM : the long localized month name (e.g. \"January\" to " +"\"December\"). yy : the year as two digit number (00 to 99). yyyy : the " +"year as four digit number. iso : the date with time and timezone. Must be " +"the only format present" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/utils/formatter_functions.py:523 +msgid "uppercase(val) -- return value of the field in upper case" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/utils/formatter_functions.py:531 +msgid "lowercase(val) -- return value of the field in lower case" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/utils/formatter_functions.py:539 +msgid "titlecase(val) -- return value of the field in title case" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/utils/formatter_functions.py:547 +msgid "capitalize(val) -- return value of the field capitalized" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/utils/formatter_functions.py:555 +msgid "booksize() -- return value of the field capitalized" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/utils/ipc/job.py:43 +msgid "Waiting..." +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/utils/ipc/job.py:52 +msgid "Stopped" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/utils/ipc/job.py:54 +msgid "Finished" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/utils/ipc/job.py:76 +msgid "Working..." +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/utils/localization.py:95 +msgid "Brazilian Portuguese" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/utils/localization.py:96 +msgid "English (UK)" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/utils/localization.py:97 +msgid "Simplified Chinese" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/utils/localization.py:98 +msgid "Chinese (HK)" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/utils/localization.py:99 +msgid "Traditional Chinese" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/utils/localization.py:100 +msgid "English" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/utils/localization.py:101 +msgid "English (Australia)" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/utils/localization.py:102 +msgid "English (New Zealand)" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/utils/localization.py:103 +msgid "English (Canada)" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/utils/localization.py:104 +msgid "English (India)" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/utils/localization.py:105 +msgid "English (Thailand)" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/utils/localization.py:106 +msgid "English (Cyprus)" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/utils/localization.py:107 +msgid "English (Czechoslovakia)" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/utils/localization.py:108 +msgid "English (Pakistan)" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/utils/localization.py:109 +msgid "English (Croatia)" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/utils/localization.py:110 +msgid "English (Indonesia)" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/utils/localization.py:111 +msgid "English (Israel)" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/utils/localization.py:112 +msgid "English (Singapore)" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/utils/localization.py:113 +msgid "English (Yemen)" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/utils/localization.py:114 +msgid "English (Ireland)" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/utils/localization.py:115 +msgid "English (China)" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/utils/localization.py:116 +msgid "Spanish (Paraguay)" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/utils/localization.py:117 +msgid "Spanish (Uruguay)" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/utils/localization.py:118 +msgid "Spanish (Argentina)" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/utils/localization.py:119 +msgid "Spanish (Mexico)" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/utils/localization.py:120 +msgid "Spanish (Cuba)" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/utils/localization.py:121 +msgid "Spanish (Chile)" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/utils/localization.py:122 +msgid "Spanish (Ecuador)" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/utils/localization.py:123 +msgid "Spanish (Honduras)" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/utils/localization.py:124 +msgid "Spanish (Venezuela)" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/utils/localization.py:125 +msgid "Spanish (Bolivia)" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/utils/localization.py:126 +msgid "Spanish (Nicaragua)" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/utils/localization.py:127 +msgid "German (AT)" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/utils/localization.py:128 +msgid "French (BE)" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/utils/localization.py:129 +msgid "Dutch (NL)" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/utils/localization.py:130 +msgid "Dutch (BE)" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/utils/pyconsole/console.py:56 +msgid "Choose theme (needs restart)" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/utils/pyconsole/console.py:109 +msgid "ERROR: Unhandled exception" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/utils/pyconsole/console.py:188 +msgid "No interpreter" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/utils/pyconsole/console.py:189 +msgid "No active interpreter found. Try restarting the console" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/utils/pyconsole/console.py:203 +msgid "Interpreter died" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/utils/pyconsole/console.py:204 +msgid "" +"Interpreter dies while excuting a command. To see the command, click Show " +"details" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/utils/pyconsole/main.py:20 +msgid "Welcome to" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/utils/pyconsole/main.py:41 +msgid " console " +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/utils/pyconsole/main.py:51 +msgid "Code is running" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/utils/pyconsole/main.py:58 +msgid "Restart console" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/utils/sftp.py:53 +msgid "URL must have the scheme sftp" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/utils/sftp.py:57 +msgid "host must be of the form user@hostname" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/utils/sftp.py:68 +msgid "Failed to negotiate SSH session: " +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/utils/sftp.py:71 +msgid "Failed to authenticate with server: %s" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/utils/smtp.py:249 +msgid "Control email delivery" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/web/feeds/__init__.py:120 +msgid "Unknown section" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/web/feeds/__init__.py:142 +msgid "Unknown feed" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/web/feeds/__init__.py:160 +#: /home/kovid/work/calibre/src/calibre/web/feeds/__init__.py:187 +msgid "Untitled article" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/web/feeds/input.py:22 +msgid "Download periodical content from the internet" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/web/feeds/input.py:37 +msgid "" +"Useful for recipe development. Forces max_articles_per_feed to 2 and " +"downloads at most 2 feeds." +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/web/feeds/input.py:40 +msgid "Username for sites that require a login to access content." +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/web/feeds/input.py:43 +msgid "Password for sites that require a login to access content." +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/web/feeds/input.py:47 +msgid "" +"Do not download latest version of builtin recipes from the calibre server" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/web/feeds/news.py:47 +msgid "Unknown News Source" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/web/feeds/news.py:630 +msgid "The \"%s\" recipe needs a username and password." +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/web/feeds/news.py:736 +msgid "Download finished" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/web/feeds/news.py:738 +msgid "Failed to download the following articles:" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/web/feeds/news.py:744 +msgid "Failed to download parts of the following articles:" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/web/feeds/news.py:746 +msgid " from " +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/web/feeds/news.py:748 +msgid "\tFailed links:" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/web/feeds/news.py:843 +msgid "Could not fetch article." +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/web/feeds/news.py:845 +msgid "The debug traceback is available earlier in this log" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/web/feeds/news.py:847 +msgid "Run with -vv to see the reason" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/web/feeds/news.py:870 +msgid "Fetching feeds..." +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/web/feeds/news.py:875 +msgid "Got feeds from index page" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/web/feeds/news.py:884 +msgid "Trying to download cover..." +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/web/feeds/news.py:886 +msgid "Generating masthead..." +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/web/feeds/news.py:966 +msgid "Starting download [%d thread(s)]..." +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/web/feeds/news.py:982 +msgid "Feeds downloaded to %s" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/web/feeds/news.py:991 +msgid "Could not download cover: %s" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/web/feeds/news.py:1000 +msgid "Downloading cover from %s" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/web/feeds/news.py:1046 +msgid "Masthead image downloaded" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/web/feeds/news.py:1214 +msgid "Untitled Article" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/web/feeds/news.py:1285 +msgid "Article downloaded: %s" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/web/feeds/news.py:1296 +msgid "Article download failed: %s" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/web/feeds/news.py:1313 +msgid "Fetching feed" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/web/feeds/news.py:1460 +msgid "" +"Failed to log in, check your username and password for the calibre " +"Periodicals service." +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/web/feeds/news.py:1475 +msgid "" +"You do not have permission to download this issue. Either your subscription " +"has expired or you have exceeded the maximum allowed downloads for today." +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/web/feeds/recipes/collection.py:46 +msgid "You" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/web/feeds/recipes/model.py:75 +#: /home/kovid/work/calibre/src/calibre/web/feeds/recipes/model.py:84 +#: /home/kovid/work/calibre/src/calibre/web/feeds/recipes/model.py:202 +msgid "Scheduled" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/web/feeds/recipes/model.py:86 +#: /home/kovid/work/calibre/src/calibre/web/feeds/recipes/model.py:203 +msgid "Custom" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/web/feeds/templates.py:118 +msgid "Next section" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/web/feeds/templates.py:121 +msgid "Main menu" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/web/feeds/templates.py:125 +msgid "Previous section" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/web/feeds/templates.py:214 +msgid "Section Menu" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/web/feeds/templates.py:217 +msgid "Main Menu" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/web/feeds/templates.py:303 +#: /home/kovid/work/calibre/src/calibre/web/feeds/templates.py:393 +msgid "Sections" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/web/feeds/templates.py:390 +msgid "Articles" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/web/fetch/simple.py:476 +msgid "" +"%prog URL\n" +"\n" +"Where URL is for example http://google.com" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/web/fetch/simple.py:479 +msgid "Base directory into which URL is saved. Default is %default" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/web/fetch/simple.py:482 +msgid "" +"Timeout in seconds to wait for a response from the server. Default: %default " +"s" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/web/fetch/simple.py:485 +msgid "" +"Maximum number of levels to recurse i.e. depth of links to follow. Default " +"%default" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/web/fetch/simple.py:488 +msgid "" +"The maximum number of files to download. This only applies to files from tags. Default is %default" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/web/fetch/simple.py:490 +msgid "" +"Minimum interval in seconds between consecutive fetches. Default is %default " +"s" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/web/fetch/simple.py:492 +msgid "" +"The character encoding for the websites you are trying to download. The " +"default is to try and guess the encoding." +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/web/fetch/simple.py:494 +msgid "" +"Only links that match this regular expression will be followed. This option " +"can be specified multiple times, in which case as long as a link matches any " +"one regexp, it will be followed. By default all links are followed." +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/web/fetch/simple.py:496 +msgid "" +"Any link that matches this regular expression will be ignored. This option " +"can be specified multiple times, in which case as long as any regexp matches " +"a link, it will be ignored.By default, no links are ignored. If both filter " +"regexp and match regexp are specified, then filter regexp is applied first." +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/web/fetch/simple.py:498 +msgid "Do not download CSS stylesheets." +msgstr "" + +#: /home/kovid/work/calibre/resources/default_tweaks.py:12 +msgid "Auto increment series index" +msgstr "" + +#: /home/kovid/work/calibre/resources/default_tweaks.py:13 +msgid "" +"The algorithm used to assign a new book in an existing series a series " +"number.\n" +"New series numbers assigned using this tweak are always integer values, " +"except\n" +"if a constant non-integer is specified.\n" +"Possible values are:\n" +"next - First available integer larger than the largest existing number\n" +"first_free - First available integer larger than 0\n" +"next_free - First available integer larger than the smallest existing " +"number\n" +"last_free - First available integer smaller than the largest existing " +"number\n" +"Return largest existing + 1 if no free number is found\n" +"const - Assign the number 1 always\n" +"a number - Assign that number always. The number is not in quotes. Note " +"that\n" +"0.0 can be used here.\n" +"Examples:\n" +"series_index_auto_increment = 'next'\n" +"series_index_auto_increment = 'next_free'\n" +"series_index_auto_increment = 16.5" +msgstr "" + +#: /home/kovid/work/calibre/resources/default_tweaks.py:31 +msgid "Add separator after completing an author name" +msgstr "" + +#: /home/kovid/work/calibre/resources/default_tweaks.py:32 +msgid "" +"Should the completion separator be append\n" +"to the end of the completed text to\n" +"automatically begin a new completion operation\n" +"for authors.\n" +"Can be either True or False" +msgstr "" + +#: /home/kovid/work/calibre/resources/default_tweaks.py:40 +msgid "Author sort name algorithm" +msgstr "" + +#: /home/kovid/work/calibre/resources/default_tweaks.py:41 +msgid "" +"The algorithm used to copy author to author_sort\n" +"Possible values are:\n" +"invert: use \"fn ln\" -> \"ln, fn\"\n" +"copy : copy author to author_sort without modification\n" +"comma : use 'copy' if there is a ',' in the name, otherwise use 'invert'\n" +"nocomma : \"fn ln\" -> \"ln fn\" (without the comma)\n" +"When this tweak is changed, the author_sort values stored with each author\n" +"must be recomputed by right-clicking on an author in the left-hand tags " +"pane,\n" +"selecting 'manage authors', and pressing 'Recalculate all author sort " +"values'.\n" +"The author name suffixes are words that are ignored when they occur at the\n" +"end of an author name. The case of the suffix is ignored and trailing\n" +"periods are automatically handled." +msgstr "" + +#: /home/kovid/work/calibre/resources/default_tweaks.py:58 +msgid "Use author sort in Tag Browser" +msgstr "" + +#: /home/kovid/work/calibre/resources/default_tweaks.py:59 +msgid "" +"Set which author field to display in the tags pane (the list of authors,\n" +"series, publishers etc on the left hand side). The choices are author and\n" +"author_sort. This tweak affects only what is displayed under the authors\n" +"category in the tags pane and content server. Please note that if you set " +"this\n" +"to author_sort, it is very possible to see duplicate names in the list " +"because\n" +"although it is guaranteed that author names are unique, there is no such\n" +"guarantee for author_sort values. Showing duplicates won't break anything, " +"but\n" +"it could lead to some confusion. When using 'author_sort', the tooltip will\n" +"show the author's name.\n" +"Examples:\n" +"categories_use_field_for_author_name = 'author'\n" +"categories_use_field_for_author_name = 'author_sort'" +msgstr "" + +#: /home/kovid/work/calibre/resources/default_tweaks.py:73 +msgid "Control partitioning of Tag Browser" +msgstr "" + +#: /home/kovid/work/calibre/resources/default_tweaks.py:74 +msgid "" +"When partitioning the tags browser, the format of the subcategory label is\n" +"controlled by a template: categories_collapsed_name_template if sorting by\n" +"name, categories_collapsed_rating_template if sorting by average rating, " +"and\n" +"categories_collapsed_popularity_template if sorting by popularity. There " +"are\n" +"two variables available to the template: first and last. The variable " +"'first'\n" +"is the initial item in the subcategory, and the variable 'last' is the " +"final\n" +"item in the subcategory. Both variables are 'objects'; they each have " +"multiple\n" +"values that are obtained by using a suffix. For example, first.name for an\n" +"author category will be the name of the author. The sub-values available " +"are:\n" +"name: the printable name of the item\n" +"count: the number of books that references this item\n" +"avg_rating: the average rating of all the books referencing this item\n" +"sort: the sort value. For authors, this is the author_sort for that author\n" +"category: the category (e.g., authors, series) that the item is in.\n" +"Note that the \"r'\" in front of the { is necessary if there are " +"backslashes\n" +"(\\ characters) in the template. It doesn't hurt anything to leave it there\n" +"even if there aren't any backslashes." +msgstr "" + +#: /home/kovid/work/calibre/resources/default_tweaks.py:96 +msgid "Specify columns to sort the booklist by on startup" +msgstr "" + +#: /home/kovid/work/calibre/resources/default_tweaks.py:97 +msgid "" +"Provide a set of columns to be sorted on when calibre starts\n" +"The argument is None if saved sort history is to be used\n" +"otherwise it is a list of column,order pairs. Column is the\n" +"lookup/search name, found using the tooltip for the column\n" +"Order is 0 for ascending, 1 for descending\n" +"For example, set it to [('authors',0),('title',0)] to sort by\n" +"title within authors." +msgstr "" + +#: /home/kovid/work/calibre/resources/default_tweaks.py:106 +msgid "Control how dates are displayed" +msgstr "" + +#: /home/kovid/work/calibre/resources/default_tweaks.py:107 +msgid "" +"Format to be used for publication date and the timestamp (date).\n" +"A string controlling how the publication date is displayed in the GUI\n" +"d the day as number without a leading zero (1 to 31)\n" +"dd the day as number with a leading zero (01 to 31)\n" +"ddd the abbreviated localized day name (e.g. 'Mon' to 'Sun').\n" +"dddd the long localized day name (e.g. 'Monday' to 'Qt::Sunday').\n" +"M the month as number without a leading zero (1-12)\n" +"MM the month as number with a leading zero (01-12)\n" +"MMM the abbreviated localized month name (e.g. 'Jan' to 'Dec').\n" +"MMMM the long localized month name (e.g. 'January' to 'December').\n" +"yy the year as two digit number (00-99)\n" +"yyyy the year as four digit number\n" +"For example, given the date of 9 Jan 2010, the following formats show\n" +"MMM yyyy ==> Jan 2010 yyyy ==> 2010 dd MMM yyyy ==> 09 Jan 2010\n" +"MM/yyyy ==> 01/2010 d/M/yy ==> 9/1/10 yy ==> 10\n" +"publication default if not set: MMM yyyy\n" +"timestamp default if not set: dd MMM yyyy" +msgstr "" + +#: /home/kovid/work/calibre/resources/default_tweaks.py:128 +msgid "Control sorting of titles and series in the library display" +msgstr "" + +#: /home/kovid/work/calibre/resources/default_tweaks.py:129 +msgid "" +"Control title and series sorting in the library view. If set to\n" +"'library_order', the title sort field will be used instead of the title.\n" +"Unless you have manually edited the title sort field, leading articles such " +"as\n" +"The and A will be ignored. If set to 'strictly_alphabetic', the titles will " +"be\n" +"sorted as-is (sort by title instead of title sort). For example, with\n" +"library_order, The Client will sort under 'C'. With strictly_alphabetic, " +"the\n" +"book will sort under 'T'.\n" +"This flag affects Calibre's library display. It has no effect on devices. " +"In\n" +"addition, titles for books added before changing the flag will retain their\n" +"order until the title is edited. Double-clicking on a title and hitting " +"return\n" +"without changing anything is sufficient to change the sort." +msgstr "" + +#: /home/kovid/work/calibre/resources/default_tweaks.py:142 +msgid "Control formatting of title and series when used in templates" +msgstr "" + +#: /home/kovid/work/calibre/resources/default_tweaks.py:143 +msgid "" +"Control how title and series names are formatted when saving to " +"disk/sending\n" +"to device. The behavior depends on the field being processed. If processing\n" +"title, then if this tweak is set to 'library_order', the title will be\n" +"replaced with title_sort. If it is set to 'strictly_alphabetic', then the\n" +"title will not be changed. If processing series, then if set to\n" +"'library_order', articles such as 'The' and 'An' will be moved to the end. " +"If\n" +"set to 'strictly_alphabetic', the series will be sent without change.\n" +"For example, if the tweak is set to library_order, \"The Lord of the " +"Rings\"\n" +"will become \"Lord of the Rings, The\". If the tweak is set to\n" +"strictly_alphabetic, it would remain \"The Lord of the Rings\"." +msgstr "" + +#: /home/kovid/work/calibre/resources/default_tweaks.py:155 +msgid "Set the list of words considered to be \"articles\" for sort strings" +msgstr "" + +#: /home/kovid/work/calibre/resources/default_tweaks.py:156 +msgid "" +"Set the list of words that are to be considered 'articles' when computing " +"the\n" +"title sort strings. The list is a regular expression, with the articles\n" +"separated by 'or' bars. Comparisons are case insensitive, and that cannot " +"be\n" +"changed. Changes to this tweak won't have an effect until the book is " +"modified\n" +"in some way. If you enter an invalid pattern, it is silently ignored.\n" +"To disable use the expression: '^$'\n" +"Default: '^(A|The|An)\\s+'" +msgstr "" + +#: /home/kovid/work/calibre/resources/default_tweaks.py:165 +msgid "Specify a folder calibre should connect to at startup" +msgstr "" + +#: /home/kovid/work/calibre/resources/default_tweaks.py:166 +msgid "" +"Specify a folder that calibre should connect to at startup using\n" +"connect_to_folder. This must be a full path to the folder. If the folder " +"does\n" +"not exist when calibre starts, it is ignored. If there are '\\' characters " +"in\n" +"the path (such as in Windows paths), you must double them.\n" +"Examples:\n" +"auto_connect_to_folder = 'C:\\\\Users\\\\someone\\\\Desktop\\\\testlib'\n" +"auto_connect_to_folder = '/home/dropbox/My Dropbox/someone/library'" +msgstr "" + +#: /home/kovid/work/calibre/resources/default_tweaks.py:175 +msgid "Specify renaming rules for SONY collections" +msgstr "" + +#: /home/kovid/work/calibre/resources/default_tweaks.py:176 +msgid "" +"Specify renaming rules for sony collections. This tweak is only applicable " +"if\n" +"metadata management is set to automatic. Collections on Sonys are named\n" +"depending upon whether the field is standard or custom. A collection " +"derived\n" +"from a standard field is named for the value in that field. For example, if\n" +"the standard 'series' column contains the value 'Darkover', then the\n" +"collection name is 'Darkover'. A collection derived from a custom field " +"will\n" +"have the name of the field added to the value. For example, if a custom " +"series\n" +"column named 'My Series' contains the name 'Darkover', then the collection\n" +"will by default be named 'Darkover (My Series)'. For purposes of this\n" +"documentation, 'Darkover' is called the value and 'My Series' is called the\n" +"category. If two books have fields that generate the same collection name,\n" +"then both books will be in that collection.\n" +"This set of tweaks lets you specify for a standard or custom field how\n" +"the collections are to be named. You can use it to add a description to a\n" +"standard field, for example 'Foo (Tag)' instead of the 'Foo'. You can also " +"use\n" +"it to force multiple fields to end up in the same collection. For example, " +"you\n" +"could force the values in 'series', '#my_series_1', and '#my_series_2' to\n" +"appear in collections named 'some_value (Series)', thereby merging all of " +"the\n" +"fields into one set of collections.\n" +"There are two related tweaks. The first determines the category name to use\n" +"for a metadata field. The second is a template, used to determines how the\n" +"value and category are combined to create the collection name.\n" +"The syntax of the first tweak, sony_collection_renaming_rules, is:\n" +"{'field_lookup_name':'category_name_to_use', 'lookup_name':'name', ...}\n" +"The second tweak, sony_collection_name_template, is a template. It uses the\n" +"same template language as plugboards and save templates. This tweak " +"controls\n" +"how the value and category are combined together to make the collection " +"name.\n" +"The only two fields available are {category} and {value}. The {value} field " +"is\n" +"never empty. The {category} field can be empty. The default is to put the\n" +"value first, then the category enclosed in parentheses, it is isn't empty:\n" +"'{value} {category:|(|)}'\n" +"Examples: The first three examples assume that the second tweak\n" +"has not been changed.\n" +"1: I want three series columns to be merged into one set of collections. " +"The\n" +"column lookup names are 'series', '#series_1' and '#series_2'. I want " +"nothing\n" +"in the parenthesis. The value to use in the tweak value would be:\n" +"sony_collection_renaming_rules={'series':'', '#series_1':'', " +"'#series_2':''}\n" +"2: I want the word '(Series)' to appear on collections made from series, " +"and\n" +"the word '(Tag)' to appear on collections made from tags. Use:\n" +"sony_collection_renaming_rules={'series':'Series', 'tags':'Tag'}\n" +"3: I want 'series' and '#myseries' to be merged, and for the collection " +"name\n" +"to have '(Series)' appended. The renaming rule is:\n" +"sony_collection_renaming_rules={'series':'Series', '#myseries':'Series'}\n" +"4: Same as example 2, but instead of having the category name in " +"parentheses\n" +"and appended to the value, I want it prepended and separated by a colon, " +"such\n" +"as in Series: Darkover. I must change the template used to format the " +"category name\n" +"The resulting two tweaks are:\n" +"sony_collection_renaming_rules={'series':'Series', 'tags':'Tag'}\n" +"sony_collection_name_template='{category:||: }{value}'" +msgstr "" + +#: /home/kovid/work/calibre/resources/default_tweaks.py:228 +msgid "Specify how SONY collections are sorted" +msgstr "" + +#: /home/kovid/work/calibre/resources/default_tweaks.py:229 +msgid "" +"Specify how sony collections are sorted. This tweak is only applicable if\n" +"metadata management is set to automatic. You can indicate which metadata is " +"to\n" +"be used to sort on a collection-by-collection basis. The format of the " +"tweak\n" +"is a list of metadata fields from which collections are made, followed by " +"the\n" +"name of the metadata field containing the sort value.\n" +"Example: The following indicates that collections built from pubdate and " +"tags\n" +"are to be sorted by the value in the custom column '#mydate', that " +"collections\n" +"built from 'series' are to be sorted by 'series_index', and that all other\n" +"collections are to be sorted by title. If a collection metadata field is " +"not\n" +"named, then if it is a series- based collection it is sorted by series " +"order,\n" +"otherwise it is sorted by title order.\n" +"[(['pubdate', 'tags'],'#mydate'), (['series'],'series_index'), (['*'], " +"'title')]\n" +"Note that the bracketing and parentheses are required. The syntax is\n" +"[ ( [list of fields], sort field ) , ( [ list of fields ] , sort field ) ]\n" +"Default: empty (no rules), so no collection attributes are named." +msgstr "" + +#: /home/kovid/work/calibre/resources/default_tweaks.py:247 +msgid "Control how tags are applied when copying books to another library" +msgstr "" + +#: /home/kovid/work/calibre/resources/default_tweaks.py:248 +msgid "" +"Set this to True to ensure that tags in 'Tags to add when adding\n" +"a book' are added when copying books to another library" +msgstr "" + +#: /home/kovid/work/calibre/resources/default_tweaks.py:253 +msgid "Set the maximum number of tags to show per book in the content server" +msgstr "" + +#: /home/kovid/work/calibre/resources/default_tweaks.py:257 +msgid "" +"Set custom metadata fields that the content server will or will not display." +msgstr "" + +#: /home/kovid/work/calibre/resources/default_tweaks.py:258 +msgid "" +"content_server_will_display is a list of custom fields to be displayed.\n" +"content_server_wont_display is a list of custom fields not to be displayed.\n" +"wont_display has priority over will_display.\n" +"The special value '*' means all custom fields. The value [] means no " +"entries.\n" +"Defaults:\n" +"content_server_will_display = ['*']\n" +"content_server_wont_display = []\n" +"Examples:\n" +"To display only the custom fields #mytags and #genre:\n" +"content_server_will_display = ['#mytags', '#genre']\n" +"content_server_wont_display = []\n" +"To display all fields except #mycomments:\n" +"content_server_will_display = ['*']\n" +"content_server_wont_display['#mycomments']" +msgstr "" + +#: /home/kovid/work/calibre/resources/default_tweaks.py:275 +msgid "Set the maximum number of sort 'levels'" +msgstr "" + +#: /home/kovid/work/calibre/resources/default_tweaks.py:276 +msgid "" +"Set the maximum number of sort 'levels' that calibre will use to resort the\n" +"library after certain operations such as searches or device insertion. Each\n" +"sort level adds a performance penalty. If the database is large (thousands " +"of\n" +"books) the penalty might be noticeable. If you are not concerned about multi-" +"\n" +"level sorts, and if you are seeing a slowdown, reduce the value of this " +"tweak." +msgstr "" + +#: /home/kovid/work/calibre/resources/default_tweaks.py:283 +msgid "Specify which font to use when generating a default cover" +msgstr "" + +#: /home/kovid/work/calibre/resources/default_tweaks.py:284 +msgid "" +"Absolute path to .ttf font files to use as the fonts for the title, author\n" +"and footer when generating a default cover. Useful if the default font " +"(Liberation\n" +"Serif) does not contain glyphs for the language of the books in your library." +msgstr "" + +#: /home/kovid/work/calibre/resources/default_tweaks.py:290 +msgid "Control behavior of double clicks on the book list" +msgstr "" + +#: /home/kovid/work/calibre/resources/default_tweaks.py:291 +msgid "" +"Behavior of doubleclick on the books list. Choices: open_viewer, " +"do_nothing,\n" +"edit_cell, edit_metadata. Selecting edit_metadata has the side effect of\n" +"disabling editing a field using a single click.\n" +"Default: open_viewer.\n" +"Example: doubleclick_on_library_view = 'do_nothing'" +msgstr "" + +#: /home/kovid/work/calibre/resources/default_tweaks.py:299 +msgid "Language to use when sorting." +msgstr "" + +#: /home/kovid/work/calibre/resources/default_tweaks.py:300 +msgid "" +"Setting this tweak will force sorting to use the\n" +"collating order for the specified language. This might be useful if you run\n" +"calibre in English but want sorting to work in the language where you live.\n" +"Set the tweak to the desired ISO 639-1 language code, in lower case.\n" +"You can find the list of supported locales at\n" +"http://publib.boulder.ibm.com/infocenter/iseries/v5r3/topic/nls/rbagsicusorts" +"equencetables.htm\n" +"Default: locale_for_sorting = '' -- use the language calibre displays in\n" +"Example: locale_for_sorting = 'fr' -- sort using French rules.\n" +"Example: locale_for_sorting = 'nb' -- sort using Norwegian rules." +msgstr "" + +#: /home/kovid/work/calibre/resources/default_tweaks.py:311 +msgid "Number of columns for custom metadata in the edit metadata dialog" +msgstr "" + +#: /home/kovid/work/calibre/resources/default_tweaks.py:312 +msgid "" +"Set whether to use one or two columns for custom metadata when editing\n" +"metadata one book at a time. If True, then the fields are laid out using " +"two\n" +"columns. If False, one column is used." +msgstr "" + +#: /home/kovid/work/calibre/resources/default_tweaks.py:317 +msgid "The number of seconds to wait before sending emails" +msgstr "" + +#: /home/kovid/work/calibre/resources/default_tweaks.py:318 +msgid "" +"The number of seconds to wait before sending emails when using a\n" +"public email server like gmail or hotmail. Default is: 5 minutes\n" +"Setting it to lower may cause the server's SPAM controls to kick in,\n" +"making email sending fail. Changes will take effect only after a restart of\n" +"calibre." +msgstr "" + +#: /home/kovid/work/calibre/resources/default_tweaks.py:325 +msgid "Remove the bright yellow lines at the edges of the book list" +msgstr "" + +#: /home/kovid/work/calibre/resources/default_tweaks.py:326 +msgid "" +"Control whether the bright yellow lines at the edges of book list are drawn\n" +"when a section of the user interface is hidden. Changes will take effect\n" +"after a restart of calibre." +msgstr "" + +#: /home/kovid/work/calibre/resources/default_tweaks.py:331 +msgid "The maximum width and height for covers saved in the calibre library" +msgstr "" + +#: /home/kovid/work/calibre/resources/default_tweaks.py:332 +msgid "" +"All covers in the calibre library will be resized, preserving aspect ratio,\n" +"to fit within this size. This is to prevent slowdowns caused by extremely\n" +"large covers" +msgstr "" + +#: /home/kovid/work/calibre/resources/default_tweaks.py:337 +msgid "Where to send downloaded news" +msgstr "" + +#: /home/kovid/work/calibre/resources/default_tweaks.py:338 +msgid "" +"When automatically sending downloaded news to a connected device, calibre\n" +"will by default send it to the main memory. By changing this tweak, you can\n" +"control where it is sent. Valid values are \"main\", \"carda\", \"cardb\". " +"Note\n" +"that if there isn't enough free space available on the location you choose,\n" +"the files will be sent to the location with the most free space." +msgstr "" + +#: /home/kovid/work/calibre/resources/default_tweaks.py:345 +msgid "What interfaces should the content server listen on" +msgstr "" + +#: /home/kovid/work/calibre/resources/default_tweaks.py:346 +msgid "" +"By default, the calibre content server listens on '0.0.0.0' which means that " +"it\n" +"accepts IPv4 connections on all interfaces. You can change this to, for\n" +"example, '127.0.0.1' to only listen for connections from the local machine, " +"or\n" +"to '::' to listen to all incoming IPv6 and IPv4 connections (this may not\n" +"work on all operating systems)" +msgstr "" + +#: /home/kovid/work/calibre/resources/default_tweaks.py:353 +msgid "Unified toolbar on OS X" +msgstr "" + +#: /home/kovid/work/calibre/resources/default_tweaks.py:354 +msgid "" +"If you enable this option and restart calibre, the toolbar will be " +"'unified'\n" +"with the titlebar as is normal for OS X applications. However, doing this " +"has\n" +"various bugs, for instance the minimum width of the toolbar becomes twice\n" +"what it should be and it causes other random bugs on some systems, so turn " +"it\n" +"on at your own risk!" +msgstr "" diff --git a/src/calibre/translations/lv.po b/src/calibre/translations/lv.po index 84230ad4e9..fe22012307 100644 --- a/src/calibre/translations/lv.po +++ b/src/calibre/translations/lv.po @@ -7,90 +7,99 @@ msgid "" msgstr "" "Project-Id-Version: calibre\n" "Report-Msgid-Bugs-To: FULL NAME \n" -"POT-Creation-Date: 2011-02-18 21:06+0000\n" -"PO-Revision-Date: 2010-12-18 16:39+0000\n" -"Last-Translator: Vladimirs Kuzmins \n" +"POT-Creation-Date: 2011-05-20 18:12+0000\n" +"PO-Revision-Date: 2011-05-17 11:56+0000\n" +"Last-Translator: uGGa \n" "Language-Team: Latvian \n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" -"X-Launchpad-Export-Date: 2011-02-19 04:54+0000\n" -"X-Generator: Launchpad (build 12351)\n" +"X-Launchpad-Export-Date: 2011-05-21 04:50+0000\n" +"X-Generator: Launchpad (build 12959)\n" "X-Poedit-Country: LATVIA\n" "X-Poedit-Language: Latvian\n" -#: /home/kovid/work/calibre/src/calibre/customize/__init__.py:43 +#: /home/kovid/work/calibre/src/calibre/customize/__init__.py:56 msgid "Does absolutely nothing" msgstr "Pilnīgi neko nedara" -#: /home/kovid/work/calibre/src/calibre/customize/__init__.py:46 +#: /home/kovid/work/calibre/src/calibre/customize/__init__.py:59 #: /home/kovid/work/calibre/src/calibre/devices/hanvon/driver.py:87 #: /home/kovid/work/calibre/src/calibre/devices/hanvon/driver.py:88 #: /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/books.py:24 -#: /home/kovid/work/calibre/src/calibre/devices/kobo/driver.py:482 +#: /home/kovid/work/calibre/src/calibre/devices/kobo/driver.py:488 #: /home/kovid/work/calibre/src/calibre/devices/nook/driver.py:70 #: /home/kovid/work/calibre/src/calibre/devices/nook/driver.py:71 #: /home/kovid/work/calibre/src/calibre/devices/prs500/books.py:267 #: /home/kovid/work/calibre/src/calibre/devices/prs505/sony_cache.py:660 -#: /home/kovid/work/calibre/src/calibre/devices/usbms/driver.py:403 -#: /home/kovid/work/calibre/src/calibre/ebooks/chm/input.py:97 -#: /home/kovid/work/calibre/src/calibre/ebooks/chm/input.py:100 +#: /home/kovid/work/calibre/src/calibre/devices/usbms/driver.py:467 +#: /home/kovid/work/calibre/src/calibre/ebooks/chm/input.py:106 +#: /home/kovid/work/calibre/src/calibre/ebooks/chm/input.py:109 #: /home/kovid/work/calibre/src/calibre/ebooks/chm/metadata.py:56 -#: /home/kovid/work/calibre/src/calibre/ebooks/comic/input.py:419 +#: /home/kovid/work/calibre/src/calibre/ebooks/comic/input.py:435 #: /home/kovid/work/calibre/src/calibre/ebooks/epub/periodical.py:127 #: /home/kovid/work/calibre/src/calibre/ebooks/fb2/input.py:100 #: /home/kovid/work/calibre/src/calibre/ebooks/fb2/input.py:102 -#: /home/kovid/work/calibre/src/calibre/ebooks/html/input.py:331 -#: /home/kovid/work/calibre/src/calibre/ebooks/html/input.py:334 +#: /home/kovid/work/calibre/src/calibre/ebooks/html/input.py:332 +#: /home/kovid/work/calibre/src/calibre/ebooks/html/input.py:335 #: /home/kovid/work/calibre/src/calibre/ebooks/lrf/html/convert_from.py:1894 #: /home/kovid/work/calibre/src/calibre/ebooks/lrf/html/convert_from.py:1896 #: /home/kovid/work/calibre/src/calibre/ebooks/lrf/output.py:24 -#: /home/kovid/work/calibre/src/calibre/ebooks/metadata/__init__.py:236 -#: /home/kovid/work/calibre/src/calibre/ebooks/metadata/book/base.py:31 -#: /home/kovid/work/calibre/src/calibre/ebooks/metadata/book/base.py:32 -#: /home/kovid/work/calibre/src/calibre/ebooks/metadata/book/base.py:73 -#: /home/kovid/work/calibre/src/calibre/ebooks/metadata/book/base.py:379 -#: /home/kovid/work/calibre/src/calibre/ebooks/metadata/book/base.py:384 -#: /home/kovid/work/calibre/src/calibre/ebooks/metadata/book/base.py:616 +#: /home/kovid/work/calibre/src/calibre/ebooks/metadata/__init__.py:253 +#: /home/kovid/work/calibre/src/calibre/ebooks/metadata/book/base.py:34 +#: /home/kovid/work/calibre/src/calibre/ebooks/metadata/book/base.py:35 +#: /home/kovid/work/calibre/src/calibre/ebooks/metadata/book/base.py:89 +#: /home/kovid/work/calibre/src/calibre/ebooks/metadata/book/base.py:455 +#: /home/kovid/work/calibre/src/calibre/ebooks/metadata/book/base.py:460 +#: /home/kovid/work/calibre/src/calibre/ebooks/metadata/book/base.py:724 #: /home/kovid/work/calibre/src/calibre/ebooks/metadata/ereader.py:36 #: /home/kovid/work/calibre/src/calibre/ebooks/metadata/ereader.py:61 -#: /home/kovid/work/calibre/src/calibre/ebooks/metadata/fb2.py:54 -#: /home/kovid/work/calibre/src/calibre/ebooks/metadata/fetch.py:359 +#: /home/kovid/work/calibre/src/calibre/ebooks/metadata/extz.py:23 +#: /home/kovid/work/calibre/src/calibre/ebooks/metadata/fb2.py:55 #: /home/kovid/work/calibre/src/calibre/ebooks/metadata/meta.py:36 #: /home/kovid/work/calibre/src/calibre/ebooks/metadata/meta.py:64 #: /home/kovid/work/calibre/src/calibre/ebooks/metadata/meta.py:66 #: /home/kovid/work/calibre/src/calibre/ebooks/metadata/meta.py:124 #: /home/kovid/work/calibre/src/calibre/ebooks/metadata/meta.py:126 -#: /home/kovid/work/calibre/src/calibre/ebooks/metadata/opf2.py:1026 -#: /home/kovid/work/calibre/src/calibre/ebooks/metadata/opf2.py:1136 -#: /home/kovid/work/calibre/src/calibre/ebooks/metadata/pdb.py:39 +#: /home/kovid/work/calibre/src/calibre/ebooks/metadata/opf2.py:1066 +#: /home/kovid/work/calibre/src/calibre/ebooks/metadata/opf2.py:1176 +#: /home/kovid/work/calibre/src/calibre/ebooks/metadata/pdb.py:41 #: /home/kovid/work/calibre/src/calibre/ebooks/metadata/pdf.py:29 +#: /home/kovid/work/calibre/src/calibre/ebooks/metadata/plucker.py:25 #: /home/kovid/work/calibre/src/calibre/ebooks/metadata/pml.py:23 #: /home/kovid/work/calibre/src/calibre/ebooks/metadata/pml.py:49 #: /home/kovid/work/calibre/src/calibre/ebooks/metadata/rtf.py:88 #: /home/kovid/work/calibre/src/calibre/ebooks/metadata/rtf.py:91 #: /home/kovid/work/calibre/src/calibre/ebooks/metadata/rtf.py:101 #: /home/kovid/work/calibre/src/calibre/ebooks/metadata/snb.py:16 -#: /home/kovid/work/calibre/src/calibre/ebooks/metadata/sources/google.py:75 +#: /home/kovid/work/calibre/src/calibre/ebooks/metadata/sources/base.py:48 +#: /home/kovid/work/calibre/src/calibre/ebooks/metadata/sources/base.py:298 +#: /home/kovid/work/calibre/src/calibre/ebooks/metadata/sources/covers.py:79 +#: /home/kovid/work/calibre/src/calibre/ebooks/metadata/sources/covers.py:81 +#: /home/kovid/work/calibre/src/calibre/ebooks/metadata/sources/douban.py:78 +#: /home/kovid/work/calibre/src/calibre/ebooks/metadata/sources/google.py:81 +#: /home/kovid/work/calibre/src/calibre/ebooks/metadata/sources/identify.py:208 +#: /home/kovid/work/calibre/src/calibre/ebooks/metadata/sources/identify.py:303 +#: /home/kovid/work/calibre/src/calibre/ebooks/metadata/sources/identify.py:305 +#: /home/kovid/work/calibre/src/calibre/ebooks/metadata/sources/identify.py:406 #: /home/kovid/work/calibre/src/calibre/ebooks/metadata/txt.py:18 -#: /home/kovid/work/calibre/src/calibre/ebooks/metadata/txtz.py:23 -#: /home/kovid/work/calibre/src/calibre/ebooks/mobi/reader.py:42 -#: /home/kovid/work/calibre/src/calibre/ebooks/mobi/reader.py:68 -#: /home/kovid/work/calibre/src/calibre/ebooks/mobi/reader.py:81 -#: /home/kovid/work/calibre/src/calibre/ebooks/mobi/reader.py:124 -#: /home/kovid/work/calibre/src/calibre/ebooks/mobi/reader.py:158 -#: /home/kovid/work/calibre/src/calibre/ebooks/mobi/reader.py:663 -#: /home/kovid/work/calibre/src/calibre/ebooks/mobi/reader.py:878 -#: /home/kovid/work/calibre/src/calibre/ebooks/mobi/reader.py:880 -#: /home/kovid/work/calibre/src/calibre/ebooks/odt/input.py:49 -#: /home/kovid/work/calibre/src/calibre/ebooks/odt/input.py:51 -#: /home/kovid/work/calibre/src/calibre/ebooks/oeb/base.py:958 -#: /home/kovid/work/calibre/src/calibre/ebooks/oeb/base.py:963 -#: /home/kovid/work/calibre/src/calibre/ebooks/oeb/base.py:1029 -#: /home/kovid/work/calibre/src/calibre/ebooks/oeb/reader.py:145 -#: /home/kovid/work/calibre/src/calibre/ebooks/oeb/reader.py:152 +#: /home/kovid/work/calibre/src/calibre/ebooks/mobi/reader.py:43 +#: /home/kovid/work/calibre/src/calibre/ebooks/mobi/reader.py:69 +#: /home/kovid/work/calibre/src/calibre/ebooks/mobi/reader.py:82 +#: /home/kovid/work/calibre/src/calibre/ebooks/mobi/reader.py:125 +#: /home/kovid/work/calibre/src/calibre/ebooks/mobi/reader.py:159 +#: /home/kovid/work/calibre/src/calibre/ebooks/mobi/reader.py:714 +#: /home/kovid/work/calibre/src/calibre/ebooks/mobi/reader.py:961 +#: /home/kovid/work/calibre/src/calibre/ebooks/mobi/reader.py:963 +#: /home/kovid/work/calibre/src/calibre/ebooks/odt/input.py:99 +#: /home/kovid/work/calibre/src/calibre/ebooks/odt/input.py:101 +#: /home/kovid/work/calibre/src/calibre/ebooks/oeb/base.py:1001 +#: /home/kovid/work/calibre/src/calibre/ebooks/oeb/base.py:1006 +#: /home/kovid/work/calibre/src/calibre/ebooks/oeb/base.py:1072 +#: /home/kovid/work/calibre/src/calibre/ebooks/oeb/reader.py:144 +#: /home/kovid/work/calibre/src/calibre/ebooks/oeb/reader.py:151 #: /home/kovid/work/calibre/src/calibre/ebooks/oeb/transforms/jacket.py:65 #: /home/kovid/work/calibre/src/calibre/ebooks/oeb/transforms/jacket.py:112 #: /home/kovid/work/calibre/src/calibre/ebooks/oeb/transforms/jacket.py:119 @@ -114,108 +123,119 @@ msgstr "Pilnīgi neko nedara" #: /home/kovid/work/calibre/src/calibre/ebooks/pdf/manipulate/rotate.py:63 #: /home/kovid/work/calibre/src/calibre/ebooks/pdf/manipulate/split.py:81 #: /home/kovid/work/calibre/src/calibre/ebooks/pdf/manipulate/split.py:82 -#: /home/kovid/work/calibre/src/calibre/ebooks/pdf/writer.py:100 #: /home/kovid/work/calibre/src/calibre/ebooks/pdf/writer.py:101 -#: /home/kovid/work/calibre/src/calibre/ebooks/rtf/input.py:312 -#: /home/kovid/work/calibre/src/calibre/ebooks/rtf/input.py:314 -#: /home/kovid/work/calibre/src/calibre/gui2/__init__.py:308 -#: /home/kovid/work/calibre/src/calibre/gui2/__init__.py:315 -#: /home/kovid/work/calibre/src/calibre/gui2/actions/add.py:101 -#: /home/kovid/work/calibre/src/calibre/gui2/actions/edit_metadata.py:332 -#: /home/kovid/work/calibre/src/calibre/gui2/actions/edit_metadata.py:335 +#: /home/kovid/work/calibre/src/calibre/ebooks/pdf/writer.py:102 +#: /home/kovid/work/calibre/src/calibre/ebooks/rtf/input.py:313 +#: /home/kovid/work/calibre/src/calibre/ebooks/rtf/input.py:315 +#: /home/kovid/work/calibre/src/calibre/gui2/__init__.py:347 +#: /home/kovid/work/calibre/src/calibre/gui2/__init__.py:355 +#: /home/kovid/work/calibre/src/calibre/gui2/actions/add.py:156 +#: /home/kovid/work/calibre/src/calibre/gui2/actions/edit_metadata.py:364 +#: /home/kovid/work/calibre/src/calibre/gui2/actions/edit_metadata.py:367 #: /home/kovid/work/calibre/src/calibre/gui2/add.py:160 #: /home/kovid/work/calibre/src/calibre/gui2/add.py:167 +#: /home/kovid/work/calibre/src/calibre/gui2/book_details.py:519 #: /home/kovid/work/calibre/src/calibre/gui2/convert/__init__.py:42 #: /home/kovid/work/calibre/src/calibre/gui2/convert/metadata.py:122 #: /home/kovid/work/calibre/src/calibre/gui2/convert/metadata.py:151 #: /home/kovid/work/calibre/src/calibre/gui2/convert/metadata.py:153 -#: /home/kovid/work/calibre/src/calibre/gui2/device.py:1089 -#: /home/kovid/work/calibre/src/calibre/gui2/device.py:1092 +#: /home/kovid/work/calibre/src/calibre/gui2/device.py:1120 +#: /home/kovid/work/calibre/src/calibre/gui2/device.py:1123 #: /home/kovid/work/calibre/src/calibre/gui2/dialogs/add_empty_book.py:56 #: /home/kovid/work/calibre/src/calibre/gui2/dialogs/add_empty_book.py:68 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/book_info.py:127 #: /home/kovid/work/calibre/src/calibre/gui2/dialogs/comicconf.py:47 -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/fetch_metadata.py:145 -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/fetch_metadata.py:185 -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single.py:732 -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/scheduler.py:193 -#: /home/kovid/work/calibre/src/calibre/gui2/email.py:236 -#: /home/kovid/work/calibre/src/calibre/gui2/email.py:245 -#: /home/kovid/work/calibre/src/calibre/gui2/library/models.py:421 -#: /home/kovid/work/calibre/src/calibre/gui2/library/models.py:440 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/scheduler.py:366 +#: /home/kovid/work/calibre/src/calibre/gui2/email.py:152 +#: /home/kovid/work/calibre/src/calibre/gui2/email.py:167 +#: /home/kovid/work/calibre/src/calibre/gui2/library/models.py:397 #: /home/kovid/work/calibre/src/calibre/gui2/library/models.py:972 -#: /home/kovid/work/calibre/src/calibre/gui2/library/models.py:1165 -#: /home/kovid/work/calibre/src/calibre/gui2/metadata/basic_widgets.py:70 -#: /home/kovid/work/calibre/src/calibre/gui2/metadata/basic_widgets.py:167 -#: /home/kovid/work/calibre/src/calibre/gui2/metadata/basic_widgets.py:185 -#: /home/kovid/work/calibre/src/calibre/gui2/metadata/bulk_download.py:112 -#: /home/kovid/work/calibre/src/calibre/gui2/viewer/main.py:197 -#: /home/kovid/work/calibre/src/calibre/library/cli.py:215 +#: /home/kovid/work/calibre/src/calibre/gui2/library/models.py:1148 +#: /home/kovid/work/calibre/src/calibre/gui2/library/models.py:1151 +#: /home/kovid/work/calibre/src/calibre/gui2/library/models.py:1154 +#: /home/kovid/work/calibre/src/calibre/gui2/library/models.py:1239 +#: /home/kovid/work/calibre/src/calibre/gui2/metadata/basic_widgets.py:82 +#: /home/kovid/work/calibre/src/calibre/gui2/metadata/basic_widgets.py:201 +#: /home/kovid/work/calibre/src/calibre/gui2/metadata/basic_widgets.py:220 +#: /home/kovid/work/calibre/src/calibre/gui2/metadata/bulk_download.py:119 +#: /home/kovid/work/calibre/src/calibre/gui2/metadata/single.py:358 +#: /home/kovid/work/calibre/src/calibre/gui2/metadata/single_download.py:160 +#: /home/kovid/work/calibre/src/calibre/gui2/metadata/single_download.py:164 +#: /home/kovid/work/calibre/src/calibre/gui2/store/google_books_plugin.py:90 +#: /home/kovid/work/calibre/src/calibre/gui2/store/search/models.py:151 +#: /home/kovid/work/calibre/src/calibre/gui2/viewer/main.py:199 +#: /home/kovid/work/calibre/src/calibre/library/cli.py:217 #: /home/kovid/work/calibre/src/calibre/library/database.py:914 -#: /home/kovid/work/calibre/src/calibre/library/database2.py:448 -#: /home/kovid/work/calibre/src/calibre/library/database2.py:454 -#: /home/kovid/work/calibre/src/calibre/library/database2.py:464 -#: /home/kovid/work/calibre/src/calibre/library/database2.py:1568 -#: /home/kovid/work/calibre/src/calibre/library/database2.py:1671 -#: /home/kovid/work/calibre/src/calibre/library/database2.py:2574 -#: /home/kovid/work/calibre/src/calibre/library/database2.py:2576 -#: /home/kovid/work/calibre/src/calibre/library/database2.py:2707 +#: /home/kovid/work/calibre/src/calibre/library/database2.py:502 +#: /home/kovid/work/calibre/src/calibre/library/database2.py:510 +#: /home/kovid/work/calibre/src/calibre/library/database2.py:521 +#: /home/kovid/work/calibre/src/calibre/library/database2.py:1800 +#: /home/kovid/work/calibre/src/calibre/library/database2.py:1937 +#: /home/kovid/work/calibre/src/calibre/library/database2.py:2944 +#: /home/kovid/work/calibre/src/calibre/library/database2.py:2946 +#: /home/kovid/work/calibre/src/calibre/library/database2.py:3079 #: /home/kovid/work/calibre/src/calibre/library/server/mobile.py:233 -#: /home/kovid/work/calibre/src/calibre/library/server/opds.py:158 -#: /home/kovid/work/calibre/src/calibre/library/server/opds.py:161 +#: /home/kovid/work/calibre/src/calibre/library/server/opds.py:156 +#: /home/kovid/work/calibre/src/calibre/library/server/opds.py:159 #: /home/kovid/work/calibre/src/calibre/library/server/xml.py:79 #: /home/kovid/work/calibre/src/calibre/utils/localization.py:131 #: /home/kovid/work/calibre/src/calibre/utils/podofo/__init__.py:46 #: /home/kovid/work/calibre/src/calibre/utils/podofo/__init__.py:64 #: /home/kovid/work/calibre/src/calibre/utils/podofo/__init__.py:78 -#: /home/kovid/work/calibre/src/calibre/web/feeds/recipes/collection.py:47 -#: /home/kovid/work/calibre/src/calibre/web/feeds/recipes/collection.py:55 +#: /home/kovid/work/calibre/src/calibre/web/feeds/recipes/collection.py:46 +#: /home/kovid/work/calibre/src/calibre/web/feeds/recipes/collection.py:54 msgid "Unknown" msgstr "Nav zināms" -#: /home/kovid/work/calibre/src/calibre/customize/__init__.py:64 +#: /home/kovid/work/calibre/src/calibre/customize/__init__.py:77 msgid "Base" msgstr "Bāze" -#: /home/kovid/work/calibre/src/calibre/customize/__init__.py:135 +#: /home/kovid/work/calibre/src/calibre/customize/__init__.py:148 msgid "Customize" -msgstr "" +msgstr "Pielāgot" -#: /home/kovid/work/calibre/src/calibre/customize/__init__.py:143 -#: /home/kovid/work/calibre/src/calibre/gui2/actions/preferences.py:39 -#: /home/kovid/work/calibre/src/calibre/gui2/actions/preferences.py:44 +#: /home/kovid/work/calibre/src/calibre/customize/__init__.py:156 +#: /home/kovid/work/calibre/src/calibre/gui2/actions/preferences.py:43 +#: /home/kovid/work/calibre/src/calibre/gui2/actions/preferences.py:48 msgid "Cannot configure" -msgstr "" +msgstr "Nevar pielāgot" -#: /home/kovid/work/calibre/src/calibre/customize/__init__.py:305 +#: /home/kovid/work/calibre/src/calibre/customize/__init__.py:318 msgid "File type" msgstr "Faila tips" -#: /home/kovid/work/calibre/src/calibre/customize/__init__.py:341 +#: /home/kovid/work/calibre/src/calibre/customize/__init__.py:354 msgid "Metadata reader" msgstr "Metadatu lasītājs" -#: /home/kovid/work/calibre/src/calibre/customize/__init__.py:371 +#: /home/kovid/work/calibre/src/calibre/customize/__init__.py:384 msgid "Metadata writer" msgstr "Metadatu rakstītājs" -#: /home/kovid/work/calibre/src/calibre/customize/__init__.py:401 +#: /home/kovid/work/calibre/src/calibre/customize/__init__.py:414 msgid "Catalog generator" -msgstr "" +msgstr "Kataloga ģenerators" -#: /home/kovid/work/calibre/src/calibre/customize/__init__.py:510 +#: /home/kovid/work/calibre/src/calibre/customize/__init__.py:523 msgid "User Interface Action" msgstr "" -#: /home/kovid/work/calibre/src/calibre/customize/__init__.py:536 +#: /home/kovid/work/calibre/src/calibre/customize/__init__.py:557 #: /home/kovid/work/calibre/src/calibre/gui2/actions/preferences.py:18 -#: /home/kovid/work/calibre/src/calibre/gui2/actions/preferences.py:23 -#: /home/kovid/work/calibre/src/calibre/gui2/preferences/main.py:190 -#: /home/kovid/work/calibre/src/calibre/gui2/preferences/main.py:280 -#: /home/kovid/work/calibre/src/calibre/gui2/preferences/main.py:302 +#: /home/kovid/work/calibre/src/calibre/gui2/actions/preferences.py:22 +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/main.py:197 +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/main.py:287 +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/main.py:309 #: /home/kovid/work/calibre/src/calibre/gui2/viewer/main_ui.py:206 msgid "Preferences" msgstr "Iestatījumi" +#: /home/kovid/work/calibre/src/calibre/customize/__init__.py:609 +#: /home/kovid/work/calibre/src/calibre/gui2/store/search/models.py:33 +msgid "Store" +msgstr "" + #: /home/kovid/work/calibre/src/calibre/customize/builtins.py:18 msgid "" "Follow all local links in an HTML file and create a ZIP file containing all " @@ -245,242 +265,359 @@ msgid "" "are added to the archive." msgstr "" -#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:166 +#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:168 msgid "Extract cover from comic files" msgstr "Izvilkt vāku no komiksu failiem" -#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:195 -#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:206 -#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:218 +#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:205 +#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:216 #: /home/kovid/work/calibre/src/calibre/customize/builtins.py:228 #: /home/kovid/work/calibre/src/calibre/customize/builtins.py:238 -#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:249 +#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:248 #: /home/kovid/work/calibre/src/calibre/customize/builtins.py:259 -#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:269 -#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:279 -#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:289 -#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:299 -#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:309 +#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:270 +#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:280 +#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:290 +#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:300 +#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:310 #: /home/kovid/work/calibre/src/calibre/customize/builtins.py:320 -#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:332 +#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:330 +#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:341 #: /home/kovid/work/calibre/src/calibre/customize/builtins.py:353 -#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:364 #: /home/kovid/work/calibre/src/calibre/customize/builtins.py:374 #: /home/kovid/work/calibre/src/calibre/customize/builtins.py:385 #: /home/kovid/work/calibre/src/calibre/customize/builtins.py:395 #: /home/kovid/work/calibre/src/calibre/customize/builtins.py:406 +#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:416 +#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:427 msgid "Read metadata from %s files" msgstr "Lasīt metadatus no %s failiem" -#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:343 +#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:364 msgid "Read metadata from ebooks in RAR archives" msgstr "Lasīt metadatus no e-grāmatām RAR arhīvos" -#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:417 +#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:438 msgid "Read metadata from ebooks in ZIP archives" msgstr "Lasīt metadatus no e-grāmatām ZIP arhīvos" -#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:430 -#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:440 -#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:450 +#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:451 #: /home/kovid/work/calibre/src/calibre/customize/builtins.py:472 -#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:483 -#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:493 +#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:482 +#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:504 +#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:515 +#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:525 msgid "Set metadata in %s files" msgstr "Ierakstīti metadati %s failos" #: /home/kovid/work/calibre/src/calibre/customize/builtins.py:461 -#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:504 +#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:493 +#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:536 msgid "Set metadata from %s files" msgstr "" -#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:824 +#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:873 msgid "Look and Feel" msgstr "Izskats un sajūta" -#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:826 -#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:838 -#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:849 -#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:860 -#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:872 +#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:875 +#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:887 +#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:898 +#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:909 +#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:921 msgid "Interface" msgstr "Saskarne" -#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:830 +#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:879 msgid "Adjust the look and feel of the calibre interface to suit your tastes" msgstr "" -#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:836 +#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:885 msgid "Behavior" msgstr "Uzvedība" -#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:842 +#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:891 msgid "Change the way calibre behaves" msgstr "" -#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:847 -#: /home/kovid/work/calibre/src/calibre/gui2/library/views.py:217 +#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:896 +#: /home/kovid/work/calibre/src/calibre/gui2/library/views.py:220 msgid "Add your own columns" -msgstr "" +msgstr "Pievienot savas kolonnas" -#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:853 +#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:902 msgid "Add/remove your own columns to the calibre book list" msgstr "" -#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:858 -msgid "Customize the toolbar" -msgstr "" +#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:907 +msgid "Toolbar" +msgstr "Rīkjosla" -#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:864 +#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:913 msgid "" "Customize the toolbars and context menus, changing which actions are " "available in each" msgstr "" -#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:870 -msgid "Customize searching" -msgstr "" +#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:919 +msgid "Searching" +msgstr "Meklēšana" -#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:876 +#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:925 msgid "Customize the way searching for books works in calibre" msgstr "" -#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:881 +#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:930 msgid "Input Options" msgstr "" -#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:883 -#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:894 -#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:905 +#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:932 +#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:943 +#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:954 msgid "Conversion" msgstr "Pārveidošana" -#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:887 +#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:936 msgid "Set conversion options specific to each input format" msgstr "" -#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:892 +#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:941 msgid "Common Options" -msgstr "" +msgstr "Kopīgas opcijas" -#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:898 +#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:947 msgid "Set conversion options common to all formats" -msgstr "" +msgstr "Uzstādīt visiem formātiem kopīgas konvertēšanas opcijas" -#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:903 +#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:952 msgid "Output Options" msgstr "Izvades opcijas" -#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:909 +#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:958 msgid "Set conversion options specific to each output format" msgstr "" -#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:914 +#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:963 msgid "Adding books" msgstr "Pievieno grāmatas" -#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:916 -#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:928 -#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:940 -#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:952 +#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:965 +#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:977 +#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:989 +#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:1001 msgid "Import/Export" msgstr "Importēt/Eksportēt" -#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:920 +#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:969 msgid "Control how calibre reads metadata from files when adding books" msgstr "" -#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:926 +#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:975 msgid "Saving books to disk" msgstr "Saglabā grāmatas diskā" -#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:932 +#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:981 msgid "" "Control how calibre exports files from its database to disk when using Save " "to disk" msgstr "" -#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:938 +#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:987 msgid "Sending books to devices" msgstr "Sūta grāmatas ierīcēm" -#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:944 +#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:993 msgid "Control how calibre transfers files to your ebook reader" msgstr "" -#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:950 +#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:999 msgid "Metadata plugboards" msgstr "" -#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:956 +#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:1005 msgid "Change metadata fields before saving/sending" msgstr "" -#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:961 +#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:1010 msgid "Template Functions" msgstr "" -#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:963 -#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:999 -#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:1011 -#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:1022 +#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:1012 +#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:1059 +#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:1071 +#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:1082 msgid "Advanced" msgstr "Paplašināti" -#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:967 +#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:1016 msgid "Create your own template functions" msgstr "" -#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:972 +#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:1021 msgid "Sharing books by email" msgstr "" -#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:974 -#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:986 +#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:1023 +#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:1035 +#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:1048 msgid "Sharing" msgstr "Koplietošana" -#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:978 +#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:1027 msgid "" "Setup sharing of books via email. Can be used for automatic sending of " "downloaded news to your devices" msgstr "" -#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:984 +#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:1033 msgid "Sharing over the net" msgstr "" -#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:990 +#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:1039 msgid "" "Setup the calibre Content Server which will give you access to your calibre " "library from anywhere, on any device, over the internet" msgstr "" -#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:997 -#: /home/kovid/work/calibre/src/calibre/gui2/preferences/plugins.py:267 +#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:1046 +msgid "Metadata download" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:1052 +msgid "Control how calibre downloads ebook metadata from the net" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:1057 +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/plugins.py:273 msgid "Plugins" msgstr "Spraudņi" -#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:1003 +#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:1063 msgid "Add/remove/customize various bits of calibre functionality" msgstr "" -#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:1009 +#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:1069 msgid "Tweaks" msgstr "" -#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:1015 +#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:1075 msgid "Fine tune how calibre behaves in various contexts" msgstr "" -#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:1020 +#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:1080 msgid "Miscellaneous" msgstr "Dažādi" -#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:1026 +#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:1086 msgid "Miscellaneous advanced configuration" msgstr "" +#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:1098 +msgid "Kindle books from Amazon." +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:1103 +msgid "Kindle books from Amazon.de." +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:1108 +msgid "Kindle books from Amazon.uk." +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:1113 +msgid "" +"Free Books : Download & Streaming : Ebook and Texts Archive : Internet " +"Archive." +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:1119 +msgid "Ebooks for readers." +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:1124 +msgid "Books, Textbooks, eBooks, Toys, Games and More." +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:1129 +msgid "Der eBook Shop." +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:1134 +msgid "Publishers of fine books." +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:1139 +msgid "World Famous eBook Store." +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:1144 +msgid "The digital bookstore." +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:1149 +msgid "EPUBReaders eBook Shop." +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:1154 +msgid "Entertain, enrich, inspire." +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:1159 +msgid "Read anywhere." +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:1164 +msgid "Foyles of London, online." +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:1170 +msgid "Zaczarowany świat książek" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:1175 +msgid "Google Books" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:1180 +msgid "The first producer of free ebooks." +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:1185 +msgid "eReading: anytime. anyplace." +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:1190 +msgid "The best ebooks at the best price: free!" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:1195 +msgid "Ebooks handcrafted with the utmost care." +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:1201 +msgid "Audiobooki mp3, ebooki, prasa - księgarnia internetowa." +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:1206 +msgid "One web page for every book." +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:1211 +msgid "DRM-Free tech ebooks." +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:1216 +msgid "The Pragmatic Bookshelf" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:1221 +msgid "Your ebook. Your way." +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:1226 +msgid "Feel every word." +msgstr "" + #: /home/kovid/work/calibre/src/calibre/customize/conversion.py:102 msgid "Conversion Input" msgstr "" @@ -514,7 +651,7 @@ msgid "" msgstr "" #: /home/kovid/work/calibre/src/calibre/customize/profiles.py:61 -#: /home/kovid/work/calibre/src/calibre/customize/profiles.py:453 +#: /home/kovid/work/calibre/src/calibre/customize/profiles.py:454 msgid "" "This profile is intended for the SONY PRS line. The 500/505/600/700 etc." msgstr "" @@ -524,62 +661,62 @@ msgid "This profile is intended for the SONY PRS 300." msgstr "" #: /home/kovid/work/calibre/src/calibre/customize/profiles.py:82 -#: /home/kovid/work/calibre/src/calibre/customize/profiles.py:493 +#: /home/kovid/work/calibre/src/calibre/customize/profiles.py:494 msgid "This profile is intended for the SONY PRS-900." msgstr "" #: /home/kovid/work/calibre/src/calibre/customize/profiles.py:90 -#: /home/kovid/work/calibre/src/calibre/customize/profiles.py:538 +#: /home/kovid/work/calibre/src/calibre/customize/profiles.py:539 msgid "This profile is intended for the Microsoft Reader." msgstr "Šis profils ir paredzēts Microsoft Reader." #: /home/kovid/work/calibre/src/calibre/customize/profiles.py:101 -#: /home/kovid/work/calibre/src/calibre/customize/profiles.py:549 +#: /home/kovid/work/calibre/src/calibre/customize/profiles.py:550 msgid "This profile is intended for the Mobipocket books." msgstr "Šis profils ir paredzēts Mobipocket grāmatām" #: /home/kovid/work/calibre/src/calibre/customize/profiles.py:114 -#: /home/kovid/work/calibre/src/calibre/customize/profiles.py:562 +#: /home/kovid/work/calibre/src/calibre/customize/profiles.py:563 msgid "This profile is intended for the Hanlin V3 and its clones." msgstr "Šis profils ir paredzēts Hanlin V3 un tā atdarinājumiem." #: /home/kovid/work/calibre/src/calibre/customize/profiles.py:126 -#: /home/kovid/work/calibre/src/calibre/customize/profiles.py:574 +#: /home/kovid/work/calibre/src/calibre/customize/profiles.py:575 msgid "This profile is intended for the Hanlin V5 and its clones." msgstr "" #: /home/kovid/work/calibre/src/calibre/customize/profiles.py:136 -#: /home/kovid/work/calibre/src/calibre/customize/profiles.py:582 +#: /home/kovid/work/calibre/src/calibre/customize/profiles.py:583 msgid "This profile is intended for the Cybook G3." msgstr "Šis profils ir paredzēts Cybook G3." #: /home/kovid/work/calibre/src/calibre/customize/profiles.py:149 -#: /home/kovid/work/calibre/src/calibre/customize/profiles.py:596 +#: /home/kovid/work/calibre/src/calibre/customize/profiles.py:597 msgid "This profile is intended for the Cybook Opus." msgstr "Šis profils ir paredzēts Cybook Opus." #: /home/kovid/work/calibre/src/calibre/customize/profiles.py:161 -#: /home/kovid/work/calibre/src/calibre/customize/profiles.py:609 +#: /home/kovid/work/calibre/src/calibre/customize/profiles.py:610 msgid "This profile is intended for the Amazon Kindle." msgstr "Šis profils ir paredzēts Amazon Kindle." #: /home/kovid/work/calibre/src/calibre/customize/profiles.py:173 -#: /home/kovid/work/calibre/src/calibre/customize/profiles.py:659 +#: /home/kovid/work/calibre/src/calibre/customize/profiles.py:660 msgid "This profile is intended for the Irex Illiad." msgstr "Šis profils ir paredzēts Irex Illiad." #: /home/kovid/work/calibre/src/calibre/customize/profiles.py:185 -#: /home/kovid/work/calibre/src/calibre/customize/profiles.py:672 +#: /home/kovid/work/calibre/src/calibre/customize/profiles.py:673 msgid "This profile is intended for the IRex Digital Reader 1000." msgstr "Šis profils ir paredzēts IRex Digital Reader 1000." #: /home/kovid/work/calibre/src/calibre/customize/profiles.py:198 -#: /home/kovid/work/calibre/src/calibre/customize/profiles.py:686 +#: /home/kovid/work/calibre/src/calibre/customize/profiles.py:687 msgid "This profile is intended for the IRex Digital Reader 800." msgstr "" #: /home/kovid/work/calibre/src/calibre/customize/profiles.py:210 -#: /home/kovid/work/calibre/src/calibre/customize/profiles.py:700 +#: /home/kovid/work/calibre/src/calibre/customize/profiles.py:701 msgid "This profile is intended for the B&N Nook." msgstr "" @@ -599,83 +736,79 @@ msgid "" "Intended for the iPad and similar devices with a resolution of 768x1024" msgstr "" -#: /home/kovid/work/calibre/src/calibre/customize/profiles.py:437 +#: /home/kovid/work/calibre/src/calibre/customize/profiles.py:438 msgid "Intended for generic tablet devices, does no resizing of images" msgstr "" -#: /home/kovid/work/calibre/src/calibre/customize/profiles.py:445 +#: /home/kovid/work/calibre/src/calibre/customize/profiles.py:446 msgid "" "Intended for the Samsung Galaxy and similar tablet devices with a resolution " "of 600x1280" msgstr "" -#: /home/kovid/work/calibre/src/calibre/customize/profiles.py:471 +#: /home/kovid/work/calibre/src/calibre/customize/profiles.py:472 msgid "This profile is intended for the Kobo Reader." msgstr "" -#: /home/kovid/work/calibre/src/calibre/customize/profiles.py:484 +#: /home/kovid/work/calibre/src/calibre/customize/profiles.py:485 msgid "This profile is intended for the SONY PRS-300." msgstr "" -#: /home/kovid/work/calibre/src/calibre/customize/profiles.py:502 +#: /home/kovid/work/calibre/src/calibre/customize/profiles.py:503 msgid "Suitable for use with any e-ink device" msgstr "" -#: /home/kovid/work/calibre/src/calibre/customize/profiles.py:509 +#: /home/kovid/work/calibre/src/calibre/customize/profiles.py:510 msgid "Suitable for use with any large screen e-ink device" msgstr "" -#: /home/kovid/work/calibre/src/calibre/customize/profiles.py:518 +#: /home/kovid/work/calibre/src/calibre/customize/profiles.py:519 msgid "This profile is intended for the 5-inch JetBook." msgstr "" -#: /home/kovid/work/calibre/src/calibre/customize/profiles.py:527 +#: /home/kovid/work/calibre/src/calibre/customize/profiles.py:528 msgid "" "This profile is intended for the SONY PRS line. The 500/505/700 etc, in " "landscape mode. Mainly useful for comics." msgstr "" -#: /home/kovid/work/calibre/src/calibre/customize/profiles.py:635 +#: /home/kovid/work/calibre/src/calibre/customize/profiles.py:636 msgid "This profile is intended for the Amazon Kindle DX." msgstr "Šis profils ir paredzēts Amazon Kindle DX." -#: /home/kovid/work/calibre/src/calibre/customize/profiles.py:712 +#: /home/kovid/work/calibre/src/calibre/customize/profiles.py:713 msgid "This profile is intended for the B&N Nook Color." msgstr "" -#: /home/kovid/work/calibre/src/calibre/customize/profiles.py:723 +#: /home/kovid/work/calibre/src/calibre/customize/profiles.py:724 msgid "This profile is intended for the Sanda Bambook." msgstr "Šis profils ir paredzēts Sanda Bambook." -#: /home/kovid/work/calibre/src/calibre/customize/ui.py:35 +#: /home/kovid/work/calibre/src/calibre/customize/ui.py:31 msgid "Installed plugins" msgstr "Uzstādītie spraudņi" -#: /home/kovid/work/calibre/src/calibre/customize/ui.py:36 +#: /home/kovid/work/calibre/src/calibre/customize/ui.py:32 msgid "Mapping for filetype plugins" msgstr "" -#: /home/kovid/work/calibre/src/calibre/customize/ui.py:37 +#: /home/kovid/work/calibre/src/calibre/customize/ui.py:33 msgid "Local plugin customization" msgstr "" -#: /home/kovid/work/calibre/src/calibre/customize/ui.py:38 +#: /home/kovid/work/calibre/src/calibre/customize/ui.py:34 msgid "Disabled plugins" msgstr "Atspējotie spraudņi" -#: /home/kovid/work/calibre/src/calibre/customize/ui.py:39 +#: /home/kovid/work/calibre/src/calibre/customize/ui.py:35 msgid "Enabled plugins" msgstr "Iespējotie spraudņi" -#: /home/kovid/work/calibre/src/calibre/customize/ui.py:94 -msgid "No valid plugin found in " -msgstr "" - -#: /home/kovid/work/calibre/src/calibre/customize/ui.py:520 +#: /home/kovid/work/calibre/src/calibre/customize/ui.py:470 msgid "Initialization of plugin %s failed with traceback:" msgstr "" -#: /home/kovid/work/calibre/src/calibre/customize/ui.py:553 +#: /home/kovid/work/calibre/src/calibre/customize/ui.py:508 msgid "" " %prog options\n" "\n" @@ -683,33 +816,33 @@ msgid "" " " msgstr "" -#: /home/kovid/work/calibre/src/calibre/customize/ui.py:559 +#: /home/kovid/work/calibre/src/calibre/customize/ui.py:514 msgid "Add a plugin by specifying the path to the zip file containing it." msgstr "Pievienot spraudni, norādot zip failu, kurš to satur." -#: /home/kovid/work/calibre/src/calibre/customize/ui.py:561 +#: /home/kovid/work/calibre/src/calibre/customize/ui.py:516 msgid "Remove a custom plugin by name. Has no effect on builtin plugins" msgstr "" -#: /home/kovid/work/calibre/src/calibre/customize/ui.py:563 +#: /home/kovid/work/calibre/src/calibre/customize/ui.py:518 msgid "" "Customize plugin. Specify name of plugin and customization string separated " "by a comma." msgstr "" -#: /home/kovid/work/calibre/src/calibre/customize/ui.py:565 +#: /home/kovid/work/calibre/src/calibre/customize/ui.py:520 msgid "List all installed plugins" msgstr "" -#: /home/kovid/work/calibre/src/calibre/customize/ui.py:567 +#: /home/kovid/work/calibre/src/calibre/customize/ui.py:522 msgid "Enable the named plugin" msgstr "" -#: /home/kovid/work/calibre/src/calibre/customize/ui.py:569 +#: /home/kovid/work/calibre/src/calibre/customize/ui.py:524 msgid "Disable the named plugin" msgstr "" -#: /home/kovid/work/calibre/src/calibre/debug.py:150 +#: /home/kovid/work/calibre/src/calibre/debug.py:152 msgid "Debug log" msgstr "" @@ -717,99 +850,129 @@ msgstr "" msgid "Communicate with Android phones." msgstr "Komunicē ar Android telefoniem." -#: /home/kovid/work/calibre/src/calibre/devices/android/driver.py:74 +#: /home/kovid/work/calibre/src/calibre/devices/android/driver.py:96 msgid "" "Comma separated list of directories to send e-books to on the device. The " "first one that exists will be used" msgstr "" -#: /home/kovid/work/calibre/src/calibre/devices/android/driver.py:121 +#: /home/kovid/work/calibre/src/calibre/devices/android/driver.py:146 msgid "Communicate with S60 phones." msgstr "" -#: /home/kovid/work/calibre/src/calibre/devices/apple/driver.py:92 +#: /home/kovid/work/calibre/src/calibre/devices/apple/driver.py:47 +msgid "" +"

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

      To " +"transfer books to your iDevice, click Disable Apple Driver, then use " +"the 'Connect to iTunes' method recommended in the Calibre + " +"iDevices FAQ, using the Connect/Share|Connect to " +"iTunes menu item.

      Enabling the Apple driver for direct connection " +"to iDevices is an unsupported advanced user mode.

      " +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/devices/apple/driver.py:65 +msgid "Disable Apple driver" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/devices/apple/driver.py:69 +msgid "Enable Apple driver" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/devices/apple/driver.py:117 +msgid "Use Series as Category in iTunes/iBooks" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/devices/apple/driver.py:118 +msgid "Enable to use the series name as the iTunes Genre, iBooks Category" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/devices/apple/driver.py:120 +msgid "Cache covers from iTunes/iBooks" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/devices/apple/driver.py:122 +msgid "Enable to cache and display covers from iTunes/iBooks" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/devices/apple/driver.py:178 msgid "Apple device" msgstr "" -#: /home/kovid/work/calibre/src/calibre/devices/apple/driver.py:94 +#: /home/kovid/work/calibre/src/calibre/devices/apple/driver.py:180 msgid "Communicate with iTunes/iBooks." msgstr "" -#: /home/kovid/work/calibre/src/calibre/devices/apple/driver.py:100 +#: /home/kovid/work/calibre/src/calibre/devices/apple/driver.py:192 msgid "Apple device detected, launching iTunes, please wait ..." msgstr "" -#: /home/kovid/work/calibre/src/calibre/devices/apple/driver.py:102 +#: /home/kovid/work/calibre/src/calibre/devices/apple/driver.py:194 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:262 -#: /home/kovid/work/calibre/src/calibre/devices/apple/driver.py:265 +#: /home/kovid/work/calibre/src/calibre/devices/apple/driver.py:357 +#: /home/kovid/work/calibre/src/calibre/devices/apple/driver.py:360 msgid "Updating device metadata listing..." msgstr "" -#: /home/kovid/work/calibre/src/calibre/devices/apple/driver.py:341 -#: /home/kovid/work/calibre/src/calibre/devices/apple/driver.py:380 -#: /home/kovid/work/calibre/src/calibre/devices/apple/driver.py:949 -#: /home/kovid/work/calibre/src/calibre/devices/apple/driver.py:989 -#: /home/kovid/work/calibre/src/calibre/devices/apple/driver.py:2971 -#: /home/kovid/work/calibre/src/calibre/devices/apple/driver.py:3011 +#: /home/kovid/work/calibre/src/calibre/devices/apple/driver.py:436 +#: /home/kovid/work/calibre/src/calibre/devices/apple/driver.py:475 +#: /home/kovid/work/calibre/src/calibre/devices/apple/driver.py:1057 +#: /home/kovid/work/calibre/src/calibre/devices/apple/driver.py:1101 +#: /home/kovid/work/calibre/src/calibre/devices/apple/driver.py:3097 +#: /home/kovid/work/calibre/src/calibre/devices/apple/driver.py:3137 msgid "%d of %d" msgstr "%d no %d" -#: /home/kovid/work/calibre/src/calibre/devices/apple/driver.py:387 -#: /home/kovid/work/calibre/src/calibre/devices/apple/driver.py:994 -#: /home/kovid/work/calibre/src/calibre/devices/apple/driver.py:3017 +#: /home/kovid/work/calibre/src/calibre/devices/apple/driver.py:482 +#: /home/kovid/work/calibre/src/calibre/devices/apple/driver.py:1106 +#: /home/kovid/work/calibre/src/calibre/devices/apple/driver.py:3143 +#: /home/kovid/work/calibre/src/calibre/gui2/ebook_download.py:106 msgid "finished" msgstr "pabeigts" -#: /home/kovid/work/calibre/src/calibre/devices/apple/driver.py:562 -msgid "Use Series as Category in iTunes/iBooks" -msgstr "" - -#: /home/kovid/work/calibre/src/calibre/devices/apple/driver.py:564 -msgid "Cache covers from iTunes/iBooks" -msgstr "" - -#: /home/kovid/work/calibre/src/calibre/devices/apple/driver.py:576 +#: /home/kovid/work/calibre/src/calibre/devices/apple/driver.py:667 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:913 +#: /home/kovid/work/calibre/src/calibre/devices/apple/driver.py:1018 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:2552 +#: /home/kovid/work/calibre/src/calibre/devices/apple/driver.py:2668 #: /home/kovid/work/calibre/src/calibre/devices/nook/driver.py:100 #: /home/kovid/work/calibre/src/calibre/devices/prs505/sony_cache.py:447 #: /home/kovid/work/calibre/src/calibre/devices/prs505/sony_cache.py:470 -#: /home/kovid/work/calibre/src/calibre/devices/usbms/device.py:908 -#: /home/kovid/work/calibre/src/calibre/devices/usbms/device.py:914 -#: /home/kovid/work/calibre/src/calibre/devices/usbms/device.py:944 -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/scheduler.py:262 -#: /home/kovid/work/calibre/src/calibre/library/database2.py:255 -#: /home/kovid/work/calibre/src/calibre/library/database2.py:268 -#: /home/kovid/work/calibre/src/calibre/library/database2.py:2438 -#: /home/kovid/work/calibre/src/calibre/library/field_metadata.py:150 +#: /home/kovid/work/calibre/src/calibre/devices/usbms/device.py:909 +#: /home/kovid/work/calibre/src/calibre/devices/usbms/device.py:915 +#: /home/kovid/work/calibre/src/calibre/devices/usbms/device.py:945 +#: /home/kovid/work/calibre/src/calibre/gui2/actions/fetch_news.py:73 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/scheduler.py:445 +#: /home/kovid/work/calibre/src/calibre/library/database2.py:298 +#: /home/kovid/work/calibre/src/calibre/library/database2.py:311 +#: /home/kovid/work/calibre/src/calibre/library/database2.py:2808 +#: /home/kovid/work/calibre/src/calibre/library/field_metadata.py:159 msgid "News" msgstr "Ziņas" -#: /home/kovid/work/calibre/src/calibre/devices/apple/driver.py:2553 +#: /home/kovid/work/calibre/src/calibre/devices/apple/driver.py:2669 #: /home/kovid/work/calibre/src/calibre/gui2/catalog/catalog_epub_mobi.py:65 -#: /home/kovid/work/calibre/src/calibre/library/catalog.py:634 -#: /home/kovid/work/calibre/src/calibre/library/database2.py:2401 -#: /home/kovid/work/calibre/src/calibre/library/database2.py:2419 +#: /home/kovid/work/calibre/src/calibre/library/catalog.py:643 +#: /home/kovid/work/calibre/src/calibre/library/database2.py:2768 +#: /home/kovid/work/calibre/src/calibre/library/database2.py:2786 msgid "Catalog" msgstr "Katalogs" -#: /home/kovid/work/calibre/src/calibre/devices/apple/driver.py:2875 +#: /home/kovid/work/calibre/src/calibre/devices/apple/driver.py:3001 msgid "Communicate with iTunes." msgstr "" @@ -852,30 +1015,30 @@ msgstr "" #: /home/kovid/work/calibre/src/calibre/devices/kobo/driver.py:67 #: /home/kovid/work/calibre/src/calibre/devices/kobo/driver.py:70 #: /home/kovid/work/calibre/src/calibre/devices/kobo/driver.py:73 -#: /home/kovid/work/calibre/src/calibre/devices/kobo/driver.py:226 -#: /home/kovid/work/calibre/src/calibre/devices/usbms/driver.py:68 -#: /home/kovid/work/calibre/src/calibre/devices/usbms/driver.py:71 -#: /home/kovid/work/calibre/src/calibre/devices/usbms/driver.py:74 -#: /home/kovid/work/calibre/src/calibre/devices/usbms/driver.py:138 -#: /home/kovid/work/calibre/src/calibre/devices/usbms/driver.py:145 -#: /home/kovid/work/calibre/src/calibre/devices/usbms/driver.py:168 +#: /home/kovid/work/calibre/src/calibre/devices/kobo/driver.py:232 +#: /home/kovid/work/calibre/src/calibre/devices/usbms/driver.py:122 +#: /home/kovid/work/calibre/src/calibre/devices/usbms/driver.py:125 +#: /home/kovid/work/calibre/src/calibre/devices/usbms/driver.py:128 +#: /home/kovid/work/calibre/src/calibre/devices/usbms/driver.py:196 +#: /home/kovid/work/calibre/src/calibre/devices/usbms/driver.py:203 +#: /home/kovid/work/calibre/src/calibre/devices/usbms/driver.py:226 msgid "Getting list of books on device..." 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:279 -#: /home/kovid/work/calibre/src/calibre/devices/usbms/driver.py:197 -#: /home/kovid/work/calibre/src/calibre/devices/usbms/driver.py:199 +#: /home/kovid/work/calibre/src/calibre/devices/usbms/driver.py:255 +#: /home/kovid/work/calibre/src/calibre/devices/usbms/driver.py:257 msgid "Transferring books to device..." msgstr "" #: /home/kovid/work/calibre/src/calibre/devices/bambook/driver.py:285 #: /home/kovid/work/calibre/src/calibre/devices/bambook/driver.py:299 -#: /home/kovid/work/calibre/src/calibre/devices/kobo/driver.py:343 -#: /home/kovid/work/calibre/src/calibre/devices/kobo/driver.py:378 -#: /home/kovid/work/calibre/src/calibre/devices/usbms/driver.py:221 -#: /home/kovid/work/calibre/src/calibre/devices/usbms/driver.py:252 +#: /home/kovid/work/calibre/src/calibre/devices/kobo/driver.py:349 +#: /home/kovid/work/calibre/src/calibre/devices/kobo/driver.py:384 +#: /home/kovid/work/calibre/src/calibre/devices/usbms/driver.py:279 +#: /home/kovid/work/calibre/src/calibre/devices/usbms/driver.py:310 msgid "Adding books to device metadata listing..." msgstr "" @@ -883,28 +1046,28 @@ msgstr "" #: /home/kovid/work/calibre/src/calibre/devices/bambook/driver.py:309 #: /home/kovid/work/calibre/src/calibre/devices/hanvon/driver.py:102 #: /home/kovid/work/calibre/src/calibre/devices/hanvon/driver.py:113 -#: /home/kovid/work/calibre/src/calibre/devices/kobo/driver.py:295 -#: /home/kovid/work/calibre/src/calibre/devices/kobo/driver.py:327 -#: /home/kovid/work/calibre/src/calibre/devices/usbms/driver.py:258 -#: /home/kovid/work/calibre/src/calibre/devices/usbms/driver.py:276 +#: /home/kovid/work/calibre/src/calibre/devices/kobo/driver.py:301 +#: /home/kovid/work/calibre/src/calibre/devices/kobo/driver.py:333 +#: /home/kovid/work/calibre/src/calibre/devices/usbms/driver.py:316 +#: /home/kovid/work/calibre/src/calibre/devices/usbms/driver.py:334 msgid "Removing books from device..." msgstr "Dzēš grāmatas no ierīces..." #: /home/kovid/work/calibre/src/calibre/devices/bambook/driver.py:324 #: /home/kovid/work/calibre/src/calibre/devices/bambook/driver.py:329 -#: /home/kovid/work/calibre/src/calibre/devices/kobo/driver.py:331 -#: /home/kovid/work/calibre/src/calibre/devices/kobo/driver.py:338 -#: /home/kovid/work/calibre/src/calibre/devices/usbms/driver.py:283 -#: /home/kovid/work/calibre/src/calibre/devices/usbms/driver.py:288 +#: /home/kovid/work/calibre/src/calibre/devices/kobo/driver.py:337 +#: /home/kovid/work/calibre/src/calibre/devices/kobo/driver.py:344 +#: /home/kovid/work/calibre/src/calibre/devices/usbms/driver.py:341 +#: /home/kovid/work/calibre/src/calibre/devices/usbms/driver.py:346 msgid "Removing books from device metadata listing..." msgstr "" #: /home/kovid/work/calibre/src/calibre/devices/bambook/driver.py:397 -#: /home/kovid/work/calibre/src/calibre/devices/usbms/driver.py:318 +#: /home/kovid/work/calibre/src/calibre/devices/usbms/driver.py:376 msgid "Sending metadata to device..." msgstr "Sūta ierīcei grāmatu metadatus..." -#: /home/kovid/work/calibre/src/calibre/devices/bambook/libbambookcore.py:132 +#: /home/kovid/work/calibre/src/calibre/devices/bambook/libbambookcore.py:129 msgid "Bambook SDK has not been installed." msgstr "" @@ -917,12 +1080,20 @@ msgid "Communicate with the Blackberry smart phone." msgstr "Komunicē ar Blackberry viedtelefonu." #: /home/kovid/work/calibre/src/calibre/devices/blackberry/driver.py:14 -#: /home/kovid/work/calibre/src/calibre/devices/eb600/driver.py:253 +#: /home/kovid/work/calibre/src/calibre/devices/eb600/driver.py:254 #: /home/kovid/work/calibre/src/calibre/devices/nuut2/driver.py:18 #: /home/kovid/work/calibre/src/calibre/devices/prs500/driver.py:90 msgid "Kovid Goyal" msgstr "Kovid Goyal" +#: /home/kovid/work/calibre/src/calibre/devices/boeye/driver.py:14 +msgid "Communicate with BOEYE BEX Serial eBook readers." +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/devices/boeye/driver.py:35 +msgid "Communicate with BOEYE BDX serial eBook readers." +msgstr "" + #: /home/kovid/work/calibre/src/calibre/devices/cybook/driver.py:22 msgid "Communicate with the Cybook Gen 3 / Opus eBook reader." msgstr "" @@ -947,7 +1118,7 @@ msgstr "" msgid "Communicate with the PocketBook 602/603/902/903 reader." msgstr "" -#: /home/kovid/work/calibre/src/calibre/devices/eb600/driver.py:252 +#: /home/kovid/work/calibre/src/calibre/devices/eb600/driver.py:253 msgid "Communicate with the PocketBook 701" msgstr "" @@ -985,7 +1156,7 @@ msgstr "" msgid "Communicate with Hanlin V5 eBook readers." msgstr "" -#: /home/kovid/work/calibre/src/calibre/devices/hanlin/driver.py:115 +#: /home/kovid/work/calibre/src/calibre/devices/hanlin/driver.py:114 msgid "Communicate with the BOOX eBook reader." msgstr "" @@ -1021,7 +1192,7 @@ msgstr "Komunicē arIRex Iliad e-grāmatu lasītāju." #: /home/kovid/work/calibre/src/calibre/devices/iliad/driver.py:17 #: /home/kovid/work/calibre/src/calibre/devices/irexdr/driver.py:18 -#: /home/kovid/work/calibre/src/calibre/devices/usbms/driver.py:42 +#: /home/kovid/work/calibre/src/calibre/devices/usbms/driver.py:43 msgid "John Schember" msgstr "John Schember" @@ -1107,67 +1278,59 @@ msgid "" "Create a tag called \"Im_Reading\" " msgstr "" -#: /home/kovid/work/calibre/src/calibre/devices/kobo/driver.py:462 -#: /home/kovid/work/calibre/src/calibre/gui2/actions/add.py:315 +#: /home/kovid/work/calibre/src/calibre/devices/kobo/driver.py:468 +#: /home/kovid/work/calibre/src/calibre/gui2/actions/add.py:388 msgid "Not Implemented" msgstr "" -#: /home/kovid/work/calibre/src/calibre/devices/kobo/driver.py:463 +#: /home/kovid/work/calibre/src/calibre/devices/kobo/driver.py:469 msgid "" "\".kobo\" files do not exist on the device as books instead, they are rows " "in the sqlite database. Currently they cannot be exported or viewed." msgstr "" -#: /home/kovid/work/calibre/src/calibre/devices/misc.py:17 +#: /home/kovid/work/calibre/src/calibre/devices/misc.py:19 msgid "Communicate with the Palm Pre" msgstr "" -#: /home/kovid/work/calibre/src/calibre/devices/misc.py:37 +#: /home/kovid/work/calibre/src/calibre/devices/misc.py:39 msgid "Communicate with the Bq Avant" msgstr "" -#: /home/kovid/work/calibre/src/calibre/devices/misc.py:58 -msgid "Communicate with the Sweex MM300" +#: /home/kovid/work/calibre/src/calibre/devices/misc.py:60 +msgid "Communicate with the Sweex/Kogan/Q600/Wink" msgstr "" -#: /home/kovid/work/calibre/src/calibre/devices/misc.py:79 -msgid "Communicate with the Digma Q600" -msgstr "" - -#: /home/kovid/work/calibre/src/calibre/devices/misc.py:88 -msgid "Communicate with the Kogan" -msgstr "" - -#: /home/kovid/work/calibre/src/calibre/devices/misc.py:96 -#: /home/kovid/work/calibre/src/calibre/devices/misc.py:123 +#: /home/kovid/work/calibre/src/calibre/devices/misc.py:81 +#: /home/kovid/work/calibre/src/calibre/devices/misc.py:108 msgid "Communicate with the Pandigital Novel" msgstr "" -#: /home/kovid/work/calibre/src/calibre/devices/misc.py:142 +#: /home/kovid/work/calibre/src/calibre/devices/misc.py:127 msgid "Communicate with the VelocityMicro" msgstr "" -#: /home/kovid/work/calibre/src/calibre/devices/misc.py:160 +#: /home/kovid/work/calibre/src/calibre/devices/misc.py:145 msgid "Communicate with the GM2000" msgstr "" -#: /home/kovid/work/calibre/src/calibre/devices/misc.py:180 +#: /home/kovid/work/calibre/src/calibre/devices/misc.py:165 msgid "Communicate with the Acer Lumiread" msgstr "" -#: /home/kovid/work/calibre/src/calibre/devices/misc.py:214 +#: /home/kovid/work/calibre/src/calibre/devices/misc.py:199 msgid "Communicate with the Aluratek Color" msgstr "" -#: /home/kovid/work/calibre/src/calibre/devices/misc.py:234 +#: /home/kovid/work/calibre/src/calibre/devices/misc.py:219 msgid "Communicate with the Trekstor" msgstr "" -#: /home/kovid/work/calibre/src/calibre/devices/misc.py:254 +#: /home/kovid/work/calibre/src/calibre/devices/misc.py:239 msgid "Communicate with the EEE Reader" msgstr "" -#: /home/kovid/work/calibre/src/calibre/devices/misc.py:274 +#: /home/kovid/work/calibre/src/calibre/devices/misc.py:259 msgid "Communicate with the Nextbook Reader" msgstr "" @@ -1211,32 +1374,32 @@ msgstr "Komunicē ar Sony PRS-500 e-grāmatu lasītāju." msgid "Communicate with all the Sony eBook readers." msgstr "" -#: /home/kovid/work/calibre/src/calibre/devices/prs505/driver.py:61 +#: /home/kovid/work/calibre/src/calibre/devices/prs505/driver.py:62 msgid "All by title" msgstr "" -#: /home/kovid/work/calibre/src/calibre/devices/prs505/driver.py:62 +#: /home/kovid/work/calibre/src/calibre/devices/prs505/driver.py:63 msgid "All by author" msgstr "" -#: /home/kovid/work/calibre/src/calibre/devices/prs505/driver.py:65 +#: /home/kovid/work/calibre/src/calibre/devices/prs505/driver.py:66 msgid "" "Comma separated list of metadata fields to turn into collections on the " "device. Possibilities include: " msgstr "" -#: /home/kovid/work/calibre/src/calibre/devices/prs505/driver.py:68 +#: /home/kovid/work/calibre/src/calibre/devices/prs505/driver.py:69 msgid "" ". Two special collections are available: %s:%s and %s:%s. Add these values " "to the list to enable them. The collections will be given the name provided " "after the \":\" character." msgstr "" -#: /home/kovid/work/calibre/src/calibre/devices/prs505/driver.py:72 +#: /home/kovid/work/calibre/src/calibre/devices/prs505/driver.py:73 msgid "Upload separate cover thumbnails for books (newer readers)" msgstr "" -#: /home/kovid/work/calibre/src/calibre/devices/prs505/driver.py:73 +#: /home/kovid/work/calibre/src/calibre/devices/prs505/driver.py:74 msgid "" "Normally, the SONY readers get the cover image from the ebook file itself. " "With this option, calibre will send a separate cover image to the reader, " @@ -1245,31 +1408,42 @@ msgid "" "950 and newer." msgstr "" -#: /home/kovid/work/calibre/src/calibre/devices/prs505/driver.py:79 +#: /home/kovid/work/calibre/src/calibre/devices/prs505/driver.py:80 msgid "" "Refresh separate covers when using automatic management (newer readers)" msgstr "" -#: /home/kovid/work/calibre/src/calibre/devices/prs505/driver.py:81 +#: /home/kovid/work/calibre/src/calibre/devices/prs505/driver.py:82 msgid "" "Set this option to have separate book covers uploaded every time you connect " "your device. Unset this option if you have so many books on the reader that " "performance is unacceptable." msgstr "" -#: /home/kovid/work/calibre/src/calibre/devices/prs505/driver.py:85 +#: /home/kovid/work/calibre/src/calibre/devices/prs505/driver.py:86 msgid "Preserve cover aspect ratio when building thumbnails" msgstr "" -#: /home/kovid/work/calibre/src/calibre/devices/prs505/driver.py:87 +#: /home/kovid/work/calibre/src/calibre/devices/prs505/driver.py:88 msgid "" "Set this option if you want the cover thumbnails to have the same aspect " "ratio (width to height) as the cover. Unset it if you want the thumbnail to " "be the maximum size, ignoring aspect ratio." msgstr "" +#: /home/kovid/work/calibre/src/calibre/devices/prs505/driver.py:92 +msgid "Search for books in all folders" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/devices/prs505/driver.py:94 +msgid "" +"Setting this option tells calibre to look for books in all folders on the " +"device and its cards. This permits calibre to find books put on the device " +"by other software and by wireless download." +msgstr "" + #: /home/kovid/work/calibre/src/calibre/devices/prs505/sony_cache.py:190 -#: /home/kovid/work/calibre/src/calibre/ebooks/oeb/transforms/structure.py:68 +#: /home/kovid/work/calibre/src/calibre/ebooks/oeb/transforms/structure.py:69 msgid "Unnamed" msgstr "" @@ -1309,6 +1483,10 @@ msgstr "" msgid "Communicate with the Stash W950 reader." msgstr "" +#: /home/kovid/work/calibre/src/calibre/devices/teclast/driver.py:111 +msgid "Communicate with the Wexler reader." +msgstr "" + #: /home/kovid/work/calibre/src/calibre/devices/usbms/device.py:282 msgid "Unable to detect the %s disk drive. Try rebooting." msgstr "" @@ -1341,21 +1519,21 @@ msgid "" "system errors." msgstr "" -#: /home/kovid/work/calibre/src/calibre/devices/usbms/device.py:841 -#: /home/kovid/work/calibre/src/calibre/devices/usbms/device.py:843 +#: /home/kovid/work/calibre/src/calibre/devices/usbms/device.py:842 +#: /home/kovid/work/calibre/src/calibre/devices/usbms/device.py:844 msgid "The reader has no storage card in this slot." msgstr "" -#: /home/kovid/work/calibre/src/calibre/devices/usbms/device.py:845 +#: /home/kovid/work/calibre/src/calibre/devices/usbms/device.py:846 msgid "Selected slot: %s is not supported." msgstr "" -#: /home/kovid/work/calibre/src/calibre/devices/usbms/device.py:874 +#: /home/kovid/work/calibre/src/calibre/devices/usbms/device.py:875 msgid "There is insufficient free space in main memory" msgstr "" -#: /home/kovid/work/calibre/src/calibre/devices/usbms/device.py:876 -#: /home/kovid/work/calibre/src/calibre/devices/usbms/device.py:878 +#: /home/kovid/work/calibre/src/calibre/devices/usbms/device.py:877 +#: /home/kovid/work/calibre/src/calibre/devices/usbms/device.py:879 msgid "There is insufficient free space on the storage card" msgstr "" @@ -1392,106 +1570,179 @@ msgstr "" msgid "Extra customization" msgstr "" -#: /home/kovid/work/calibre/src/calibre/devices/usbms/driver.py:41 +#: /home/kovid/work/calibre/src/calibre/devices/usbms/driver.py:42 msgid "Communicate with an eBook reader." msgstr "" -#: /home/kovid/work/calibre/src/calibre/devices/usbms/driver.py:57 +#: /home/kovid/work/calibre/src/calibre/devices/usbms/driver.py:94 msgid "Get device information..." msgstr "" -#: /home/kovid/work/calibre/src/calibre/ebooks/comic/input.py:190 +#: /home/kovid/work/calibre/src/calibre/devices/user_defined/driver.py:37 +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/device_user_defined.py:68 +msgid "USB Vendor ID (in hex)" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/devices/user_defined/driver.py:38 +#: /home/kovid/work/calibre/src/calibre/devices/user_defined/driver.py:41 +#: /home/kovid/work/calibre/src/calibre/devices/user_defined/driver.py:44 +msgid "" +"Get this ID using Preferences -> Misc -> Get information to set up the user-" +"defined device" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/devices/user_defined/driver.py:40 +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/device_user_defined.py:70 +msgid "USB Product ID (in hex)" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/devices/user_defined/driver.py:43 +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/device_user_defined.py:72 +msgid "USB Revision ID (in hex)" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/devices/user_defined/driver.py:47 +msgid "Windows main memory vendor string" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/devices/user_defined/driver.py:48 +#: /home/kovid/work/calibre/src/calibre/devices/user_defined/driver.py:52 +#: /home/kovid/work/calibre/src/calibre/devices/user_defined/driver.py:56 +#: /home/kovid/work/calibre/src/calibre/devices/user_defined/driver.py:60 +msgid "" +"This field is used only on windows. Get this ID using Preferences -> Misc -> " +"Get information to set up the user-defined device" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/devices/user_defined/driver.py:51 +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/device_user_defined.py:79 +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/device_user_defined.py:81 +msgid "Windows main memory ID string" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/devices/user_defined/driver.py:55 +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/device_user_defined.py:84 +msgid "Windows card A vendor string" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/devices/user_defined/driver.py:59 +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/device_user_defined.py:86 +msgid "Windows card A ID string" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/devices/user_defined/driver.py:63 +msgid "Main memory folder" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/devices/user_defined/driver.py:64 +#: /home/kovid/work/calibre/src/calibre/devices/user_defined/driver.py:67 +msgid "" +"Enter the folder where the books are to be stored. This folder is prepended " +"to any send_to_device template" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/devices/user_defined/driver.py:66 +msgid "Card A folder" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/ebooks/comic/input.py:202 msgid "Rendered %s" msgstr "" -#: /home/kovid/work/calibre/src/calibre/ebooks/comic/input.py:193 +#: /home/kovid/work/calibre/src/calibre/ebooks/comic/input.py:205 msgid "Failed %s" msgstr "" -#: /home/kovid/work/calibre/src/calibre/ebooks/comic/input.py:247 +#: /home/kovid/work/calibre/src/calibre/ebooks/comic/input.py:259 msgid "" "Failed to process comic: \n" "\n" "%s" msgstr "" -#: /home/kovid/work/calibre/src/calibre/ebooks/comic/input.py:266 +#: /home/kovid/work/calibre/src/calibre/ebooks/comic/input.py:278 msgid "" "Number of colors for grayscale image conversion. Default: %default. Values " "of less than 256 may result in blurred text on your device if you are " "creating your comics in EPUB format." msgstr "" -#: /home/kovid/work/calibre/src/calibre/ebooks/comic/input.py:270 +#: /home/kovid/work/calibre/src/calibre/ebooks/comic/input.py:282 msgid "" "Disable normalize (improve contrast) color range for pictures. Default: False" msgstr "" -#: /home/kovid/work/calibre/src/calibre/ebooks/comic/input.py:273 +#: /home/kovid/work/calibre/src/calibre/ebooks/comic/input.py:285 msgid "Maintain picture aspect ratio. Default is to fill the screen." msgstr "" -#: /home/kovid/work/calibre/src/calibre/ebooks/comic/input.py:275 +#: /home/kovid/work/calibre/src/calibre/ebooks/comic/input.py:287 msgid "Disable sharpening." msgstr "" -#: /home/kovid/work/calibre/src/calibre/ebooks/comic/input.py:277 +#: /home/kovid/work/calibre/src/calibre/ebooks/comic/input.py:289 msgid "" "Disable trimming of comic pages. For some comics, trimming might remove " "content as well as borders." msgstr "" -#: /home/kovid/work/calibre/src/calibre/ebooks/comic/input.py:280 +#: /home/kovid/work/calibre/src/calibre/ebooks/comic/input.py:292 msgid "Don't split landscape images into two portrait images" msgstr "" -#: /home/kovid/work/calibre/src/calibre/ebooks/comic/input.py:282 +#: /home/kovid/work/calibre/src/calibre/ebooks/comic/input.py:294 msgid "" "Keep aspect ratio and scale image using screen height as image width for " "viewing in landscape mode." msgstr "" -#: /home/kovid/work/calibre/src/calibre/ebooks/comic/input.py:285 +#: /home/kovid/work/calibre/src/calibre/ebooks/comic/input.py:297 msgid "" "Used for right-to-left publications like manga. Causes landscape pages to be " "split into portrait pages from right to left." msgstr "" -#: /home/kovid/work/calibre/src/calibre/ebooks/comic/input.py:289 +#: /home/kovid/work/calibre/src/calibre/ebooks/comic/input.py:301 msgid "" "Enable Despeckle. Reduces speckle noise. May greatly increase processing " "time." msgstr "" -#: /home/kovid/work/calibre/src/calibre/ebooks/comic/input.py:292 +#: /home/kovid/work/calibre/src/calibre/ebooks/comic/input.py:304 msgid "" "Don't sort the files found in the comic alphabetically by name. Instead use " "the order they were added to the comic." msgstr "" -#: /home/kovid/work/calibre/src/calibre/ebooks/comic/input.py:296 +#: /home/kovid/work/calibre/src/calibre/ebooks/comic/input.py:308 msgid "" "The format that images in the created ebook are converted to. You can " "experiment to see which format gives you optimal size and look on your " "device." msgstr "" -#: /home/kovid/work/calibre/src/calibre/ebooks/comic/input.py:300 +#: /home/kovid/work/calibre/src/calibre/ebooks/comic/input.py:312 msgid "Apply no processing to the image" msgstr "" -#: /home/kovid/work/calibre/src/calibre/ebooks/comic/input.py:302 +#: /home/kovid/work/calibre/src/calibre/ebooks/comic/input.py:314 msgid "Do not convert the image to grayscale (black and white)" msgstr "" -#: /home/kovid/work/calibre/src/calibre/ebooks/comic/input.py:304 +#: /home/kovid/work/calibre/src/calibre/ebooks/comic/input.py:316 msgid "" "Specify the image size as widthxheight pixels. Normally, an image size is " "automatically calculated from the output profile, this option overrides it." msgstr "" -#: /home/kovid/work/calibre/src/calibre/ebooks/comic/input.py:443 -#: /home/kovid/work/calibre/src/calibre/ebooks/comic/input.py:454 +#: /home/kovid/work/calibre/src/calibre/ebooks/comic/input.py:320 +msgid "" +"When converting a CBC do not add links to each page to the TOC. Note this " +"only applies if the TOC has more than one section" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/ebooks/comic/input.py:459 +#: /home/kovid/work/calibre/src/calibre/ebooks/comic/input.py:471 msgid "Page" msgstr "Lapa" @@ -1521,77 +1772,77 @@ msgid "" "For full documentation of the conversion system see\n" msgstr "" -#: /home/kovid/work/calibre/src/calibre/ebooks/conversion/cli.py:106 +#: /home/kovid/work/calibre/src/calibre/ebooks/conversion/cli.py:109 msgid "INPUT OPTIONS" msgstr "" -#: /home/kovid/work/calibre/src/calibre/ebooks/conversion/cli.py:107 +#: /home/kovid/work/calibre/src/calibre/ebooks/conversion/cli.py:110 msgid "Options to control the processing of the input %s file" msgstr "" -#: /home/kovid/work/calibre/src/calibre/ebooks/conversion/cli.py:113 +#: /home/kovid/work/calibre/src/calibre/ebooks/conversion/cli.py:116 msgid "OUTPUT OPTIONS" msgstr "" -#: /home/kovid/work/calibre/src/calibre/ebooks/conversion/cli.py:114 +#: /home/kovid/work/calibre/src/calibre/ebooks/conversion/cli.py:117 msgid "Options to control the processing of the output %s" msgstr "" -#: /home/kovid/work/calibre/src/calibre/ebooks/conversion/cli.py:128 +#: /home/kovid/work/calibre/src/calibre/ebooks/conversion/cli.py:131 msgid "Options to control the look and feel of the output" msgstr "" -#: /home/kovid/work/calibre/src/calibre/ebooks/conversion/cli.py:143 +#: /home/kovid/work/calibre/src/calibre/ebooks/conversion/cli.py:146 msgid "" "Modify the document text and structure using common patterns. Disabled by " "default. Use %s to enable. Individual actions can be disabled with the %s " "options." msgstr "" -#: /home/kovid/work/calibre/src/calibre/ebooks/conversion/cli.py:151 -#: /home/kovid/work/calibre/src/calibre/gui2/convert/search_and_replace.py:16 +#: /home/kovid/work/calibre/src/calibre/ebooks/conversion/cli.py:154 +#: /home/kovid/work/calibre/src/calibre/gui2/convert/search_and_replace.py:18 msgid "Modify the document text and structure using user defined patterns." msgstr "" -#: /home/kovid/work/calibre/src/calibre/ebooks/conversion/cli.py:160 +#: /home/kovid/work/calibre/src/calibre/ebooks/conversion/cli.py:163 msgid "Control auto-detection of document structure." msgstr "" -#: /home/kovid/work/calibre/src/calibre/ebooks/conversion/cli.py:169 +#: /home/kovid/work/calibre/src/calibre/ebooks/conversion/cli.py:173 msgid "" "Control the automatic generation of a Table of Contents. By default, if the " "source file has a Table of Contents, it will be used in preference to the " "automatically generated one." msgstr "" -#: /home/kovid/work/calibre/src/calibre/ebooks/conversion/cli.py:179 +#: /home/kovid/work/calibre/src/calibre/ebooks/conversion/cli.py:183 msgid "Options to set metadata in the output" msgstr "" -#: /home/kovid/work/calibre/src/calibre/ebooks/conversion/cli.py:182 +#: /home/kovid/work/calibre/src/calibre/ebooks/conversion/cli.py:186 msgid "Options to help with debugging the conversion" msgstr "" -#: /home/kovid/work/calibre/src/calibre/ebooks/conversion/cli.py:208 +#: /home/kovid/work/calibre/src/calibre/ebooks/conversion/cli.py:212 msgid "List builtin recipes" msgstr "Parādīt iebūvētās receptes" -#: /home/kovid/work/calibre/src/calibre/ebooks/conversion/cli.py:281 +#: /home/kovid/work/calibre/src/calibre/ebooks/conversion/cli.py:285 msgid "Output saved to" msgstr "" -#: /home/kovid/work/calibre/src/calibre/ebooks/conversion/plumber.py:102 +#: /home/kovid/work/calibre/src/calibre/ebooks/conversion/plumber.py:103 msgid "Level of verbosity. Specify multiple times for greater verbosity." msgstr "" -#: /home/kovid/work/calibre/src/calibre/ebooks/conversion/plumber.py:109 +#: /home/kovid/work/calibre/src/calibre/ebooks/conversion/plumber.py:110 msgid "" "Save the output from different stages of the conversion pipeline to the " "specified directory. Useful if you are unsure at which stage of the " "conversion process a bug is occurring." msgstr "" -#: /home/kovid/work/calibre/src/calibre/ebooks/conversion/plumber.py:118 +#: /home/kovid/work/calibre/src/calibre/ebooks/conversion/plumber.py:119 msgid "" "Specify the input profile. The input profile gives the conversion system " "information on how to interpret various information in the input document. " @@ -1599,7 +1850,7 @@ msgid "" "are:" msgstr "" -#: /home/kovid/work/calibre/src/calibre/ebooks/conversion/plumber.py:129 +#: /home/kovid/work/calibre/src/calibre/ebooks/conversion/plumber.py:130 msgid "" "Specify the output profile. The output profile tells the conversion system " "how to optimize the created document for the specified device. In some " @@ -1607,7 +1858,7 @@ msgid "" "a device. For example EPUB on the SONY reader. Choices are:" msgstr "" -#: /home/kovid/work/calibre/src/calibre/ebooks/conversion/plumber.py:140 +#: /home/kovid/work/calibre/src/calibre/ebooks/conversion/plumber.py:141 msgid "" "The base font size in pts. All font sizes in the produced book will be " "rescaled based on this size. By choosing a larger size you can make the " @@ -1615,7 +1866,7 @@ msgid "" "chosen based on the output profile you chose." msgstr "" -#: /home/kovid/work/calibre/src/calibre/ebooks/conversion/plumber.py:150 +#: /home/kovid/work/calibre/src/calibre/ebooks/conversion/plumber.py:151 msgid "" "Mapping from CSS font names to font sizes in pts. An example setting is " "12,12,14,16,18,20,22,24. These are the mappings for the sizes xx-small to xx-" @@ -1624,11 +1875,11 @@ msgid "" "use a mapping based on the output profile you chose." msgstr "" -#: /home/kovid/work/calibre/src/calibre/ebooks/conversion/plumber.py:162 +#: /home/kovid/work/calibre/src/calibre/ebooks/conversion/plumber.py:163 msgid "Disable all rescaling of font sizes." msgstr "" -#: /home/kovid/work/calibre/src/calibre/ebooks/conversion/plumber.py:168 +#: /home/kovid/work/calibre/src/calibre/ebooks/conversion/plumber.py:169 msgid "" "The minimum line height, as a percentage of the element's calculated font " "size. calibre will ensure that every element has a line height of at least " @@ -1638,7 +1889,7 @@ msgid "" "you can achieve \"double spaced\" text by setting this to 240." msgstr "" -#: /home/kovid/work/calibre/src/calibre/ebooks/conversion/plumber.py:183 +#: /home/kovid/work/calibre/src/calibre/ebooks/conversion/plumber.py:184 msgid "" "The line height in pts. Controls spacing between consecutive lines of text. " "Only applies to elements that do not define their own line height. In most " @@ -1646,7 +1897,7 @@ msgid "" "height manipulation is performed." msgstr "" -#: /home/kovid/work/calibre/src/calibre/ebooks/conversion/plumber.py:194 +#: /home/kovid/work/calibre/src/calibre/ebooks/conversion/plumber.py:195 msgid "" "Some badly designed documents use tables to control the layout of text on " "the page. When converted these documents often have text that runs off the " @@ -1654,58 +1905,58 @@ msgid "" "tables and present it in a linear fashion." msgstr "" -#: /home/kovid/work/calibre/src/calibre/ebooks/conversion/plumber.py:204 +#: /home/kovid/work/calibre/src/calibre/ebooks/conversion/plumber.py:205 msgid "" "XPath expression that specifies all tags that should be added to the Table " "of Contents at level one. If this is specified, it takes precedence over " "other forms of auto-detection." msgstr "" -#: /home/kovid/work/calibre/src/calibre/ebooks/conversion/plumber.py:213 +#: /home/kovid/work/calibre/src/calibre/ebooks/conversion/plumber.py:214 msgid "" "XPath expression that specifies all tags that should be added to the Table " "of Contents at level two. Each entry is added under the previous level one " "entry." msgstr "" -#: /home/kovid/work/calibre/src/calibre/ebooks/conversion/plumber.py:221 +#: /home/kovid/work/calibre/src/calibre/ebooks/conversion/plumber.py:222 msgid "" "XPath expression that specifies all tags that should be added to the Table " "of Contents at level three. Each entry is added under the previous level two " "entry." msgstr "" -#: /home/kovid/work/calibre/src/calibre/ebooks/conversion/plumber.py:229 +#: /home/kovid/work/calibre/src/calibre/ebooks/conversion/plumber.py:230 msgid "" "Normally, if the source file already has a Table of Contents, it is used in " "preference to the auto-generated one. With this option, the auto-generated " "one is always used." msgstr "" -#: /home/kovid/work/calibre/src/calibre/ebooks/conversion/plumber.py:237 +#: /home/kovid/work/calibre/src/calibre/ebooks/conversion/plumber.py:238 msgid "Don't add auto-detected chapters to the Table of Contents." msgstr "" -#: /home/kovid/work/calibre/src/calibre/ebooks/conversion/plumber.py:244 +#: /home/kovid/work/calibre/src/calibre/ebooks/conversion/plumber.py:245 msgid "" "If fewer than this number of chapters is detected, then links are added to " "the Table of Contents. Default: %default" msgstr "" -#: /home/kovid/work/calibre/src/calibre/ebooks/conversion/plumber.py:251 +#: /home/kovid/work/calibre/src/calibre/ebooks/conversion/plumber.py:252 msgid "" "Maximum number of links to insert into the TOC. Set to 0 to disable. Default " "is: %default. Links are only added to the TOC if less than the threshold " "number of chapters were detected." msgstr "" -#: /home/kovid/work/calibre/src/calibre/ebooks/conversion/plumber.py:259 +#: /home/kovid/work/calibre/src/calibre/ebooks/conversion/plumber.py:260 msgid "" "Remove entries from the Table of Contents whose titles match the specified " "regular expression. Matching entries and all their children are removed." msgstr "" -#: /home/kovid/work/calibre/src/calibre/ebooks/conversion/plumber.py:270 +#: /home/kovid/work/calibre/src/calibre/ebooks/conversion/plumber.py:271 msgid "" "An XPath expression to detect chapter titles. The default is to consider " "

      or

      tags that contain the words \"chapter\",\"book\",\"section\" or " @@ -1715,7 +1966,7 @@ msgid "" "User Manual for further help on using this feature." msgstr "" -#: /home/kovid/work/calibre/src/calibre/ebooks/conversion/plumber.py:284 +#: /home/kovid/work/calibre/src/calibre/ebooks/conversion/plumber.py:285 msgid "" "Specify how to mark detected chapters. A value of \"pagebreak\" will insert " "page breaks before chapters. A value of \"rule\" will insert a line before " @@ -1723,39 +1974,47 @@ msgid "" "\"both\" will use both page breaks and lines to mark chapters." msgstr "" -#: /home/kovid/work/calibre/src/calibre/ebooks/conversion/plumber.py:294 +#: /home/kovid/work/calibre/src/calibre/ebooks/conversion/plumber.py:295 msgid "" "Either the path to a CSS stylesheet or raw CSS. This CSS will be appended to " "the style rules from the source file, so it can be used to override those " "rules." msgstr "" -#: /home/kovid/work/calibre/src/calibre/ebooks/conversion/plumber.py:303 +#: /home/kovid/work/calibre/src/calibre/ebooks/conversion/plumber.py:304 msgid "" "An XPath expression. Page breaks are inserted before the specified elements." msgstr "" -#: /home/kovid/work/calibre/src/calibre/ebooks/conversion/plumber.py:309 +#: /home/kovid/work/calibre/src/calibre/ebooks/conversion/plumber.py:310 +msgid "" +"Some documents specify page margins by specifying a left and right margin on " +"each individual paragraph. calibre will try to detect and remove these " +"margins. Sometimes, this can cause the removal of margins that should not " +"have been removed. In this case you can disable the removal." +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/ebooks/conversion/plumber.py:321 msgid "" "Set the top margin in pts. Default is %default. Note: 72 pts equals 1 inch" msgstr "" -#: /home/kovid/work/calibre/src/calibre/ebooks/conversion/plumber.py:314 +#: /home/kovid/work/calibre/src/calibre/ebooks/conversion/plumber.py:326 msgid "" "Set the bottom margin in pts. Default is %default. Note: 72 pts equals 1 inch" msgstr "" -#: /home/kovid/work/calibre/src/calibre/ebooks/conversion/plumber.py:319 +#: /home/kovid/work/calibre/src/calibre/ebooks/conversion/plumber.py:331 msgid "" "Set the left margin in pts. Default is %default. Note: 72 pts equals 1 inch" msgstr "" -#: /home/kovid/work/calibre/src/calibre/ebooks/conversion/plumber.py:324 +#: /home/kovid/work/calibre/src/calibre/ebooks/conversion/plumber.py:336 msgid "" "Set the right margin in pts. Default is %default. Note: 72 pts equals 1 inch" msgstr "" -#: /home/kovid/work/calibre/src/calibre/ebooks/conversion/plumber.py:330 +#: /home/kovid/work/calibre/src/calibre/ebooks/conversion/plumber.py:342 msgid "" "Change text justification. A value of \"left\" converts all justified text " "in the source to left aligned (i.e. unjustified) text. A value of " @@ -1764,57 +2023,57 @@ msgid "" "Note that only some output formats support justification." msgstr "" -#: /home/kovid/work/calibre/src/calibre/ebooks/conversion/plumber.py:340 +#: /home/kovid/work/calibre/src/calibre/ebooks/conversion/plumber.py:352 msgid "" "Remove spacing between paragraphs. Also sets an indent on paragraphs of " "1.5em. Spacing removal will not work if the source file does not use " "paragraphs (

      or

      tags)." msgstr "" -#: /home/kovid/work/calibre/src/calibre/ebooks/conversion/plumber.py:347 +#: /home/kovid/work/calibre/src/calibre/ebooks/conversion/plumber.py:359 msgid "" "When calibre removes inter paragraph spacing, it automatically sets a " "paragraph indent, to ensure that paragraphs can be easily distinguished. " "This option controls the width of that indent." msgstr "" -#: /home/kovid/work/calibre/src/calibre/ebooks/conversion/plumber.py:354 +#: /home/kovid/work/calibre/src/calibre/ebooks/conversion/plumber.py:366 msgid "" "Use the cover detected from the source file in preference to the specified " "cover." msgstr "" -#: /home/kovid/work/calibre/src/calibre/ebooks/conversion/plumber.py:360 +#: /home/kovid/work/calibre/src/calibre/ebooks/conversion/plumber.py:372 msgid "" "Insert a blank line between paragraphs. Will not work if the source file " "does not use paragraphs (

      or

      tags)." msgstr "" -#: /home/kovid/work/calibre/src/calibre/ebooks/conversion/plumber.py:367 +#: /home/kovid/work/calibre/src/calibre/ebooks/conversion/plumber.py:379 msgid "" "Remove the first image from the input ebook. Useful if the first image in " "the source file is a cover and you are specifying an external cover." msgstr "" -#: /home/kovid/work/calibre/src/calibre/ebooks/conversion/plumber.py:375 +#: /home/kovid/work/calibre/src/calibre/ebooks/conversion/plumber.py:387 msgid "" "Insert the book metadata at the start of the book. This is useful if your " "ebook reader does not support displaying/searching metadata directly." msgstr "" -#: /home/kovid/work/calibre/src/calibre/ebooks/conversion/plumber.py:383 +#: /home/kovid/work/calibre/src/calibre/ebooks/conversion/plumber.py:395 msgid "" "Convert plain quotes, dashes and ellipsis to their typographically correct " "equivalents. For details, see http://daringfireball.net/projects/smartypants" msgstr "" -#: /home/kovid/work/calibre/src/calibre/ebooks/conversion/plumber.py:392 +#: /home/kovid/work/calibre/src/calibre/ebooks/conversion/plumber.py:404 msgid "" "Read metadata from the specified OPF file. Metadata read from this file will " "override any metadata in the source file." msgstr "" -#: /home/kovid/work/calibre/src/calibre/ebooks/conversion/plumber.py:399 +#: /home/kovid/work/calibre/src/calibre/ebooks/conversion/plumber.py:411 msgid "" "Transliterate unicode characters to an ASCII representation. Use with care " "because this will replace unicode characters with ASCII. For instance it " @@ -1824,7 +2083,7 @@ msgid "" "current calibre interface language will be used." msgstr "" -#: /home/kovid/work/calibre/src/calibre/ebooks/conversion/plumber.py:414 +#: /home/kovid/work/calibre/src/calibre/ebooks/conversion/plumber.py:426 msgid "" "Preserve ligatures present in the input document. A ligature is a special " "rendering of a pair of characters like ff, fi, fl et cetera. Most readers do " @@ -1834,105 +2093,105 @@ msgid "" "instead." msgstr "" -#: /home/kovid/work/calibre/src/calibre/ebooks/conversion/plumber.py:426 +#: /home/kovid/work/calibre/src/calibre/ebooks/conversion/plumber.py:438 #: /home/kovid/work/calibre/src/calibre/ebooks/metadata/cli.py:38 msgid "Set the title." msgstr "" -#: /home/kovid/work/calibre/src/calibre/ebooks/conversion/plumber.py:430 +#: /home/kovid/work/calibre/src/calibre/ebooks/conversion/plumber.py:442 msgid "Set the authors. Multiple authors should be separated by ampersands." msgstr "" -#: /home/kovid/work/calibre/src/calibre/ebooks/conversion/plumber.py:435 +#: /home/kovid/work/calibre/src/calibre/ebooks/conversion/plumber.py:447 msgid "The version of the title to be used for sorting. " msgstr "" -#: /home/kovid/work/calibre/src/calibre/ebooks/conversion/plumber.py:439 +#: /home/kovid/work/calibre/src/calibre/ebooks/conversion/plumber.py:451 msgid "String to be used when sorting by author. " msgstr "" -#: /home/kovid/work/calibre/src/calibre/ebooks/conversion/plumber.py:443 +#: /home/kovid/work/calibre/src/calibre/ebooks/conversion/plumber.py:455 msgid "Set the cover to the specified file or URL" msgstr "" -#: /home/kovid/work/calibre/src/calibre/ebooks/conversion/plumber.py:447 +#: /home/kovid/work/calibre/src/calibre/ebooks/conversion/plumber.py:459 #: /home/kovid/work/calibre/src/calibre/ebooks/metadata/cli.py:54 msgid "Set the ebook description." msgstr "" -#: /home/kovid/work/calibre/src/calibre/ebooks/conversion/plumber.py:451 +#: /home/kovid/work/calibre/src/calibre/ebooks/conversion/plumber.py:463 #: /home/kovid/work/calibre/src/calibre/ebooks/metadata/cli.py:56 msgid "Set the ebook publisher." msgstr "" -#: /home/kovid/work/calibre/src/calibre/ebooks/conversion/plumber.py:455 +#: /home/kovid/work/calibre/src/calibre/ebooks/conversion/plumber.py:467 #: /home/kovid/work/calibre/src/calibre/ebooks/metadata/cli.py:60 msgid "Set the series this ebook belongs to." msgstr "" -#: /home/kovid/work/calibre/src/calibre/ebooks/conversion/plumber.py:459 +#: /home/kovid/work/calibre/src/calibre/ebooks/conversion/plumber.py:471 #: /home/kovid/work/calibre/src/calibre/ebooks/metadata/cli.py:62 msgid "Set the index of the book in this series." msgstr "" -#: /home/kovid/work/calibre/src/calibre/ebooks/conversion/plumber.py:463 +#: /home/kovid/work/calibre/src/calibre/ebooks/conversion/plumber.py:475 #: /home/kovid/work/calibre/src/calibre/ebooks/metadata/cli.py:64 msgid "Set the rating. Should be a number between 1 and 5." msgstr "" -#: /home/kovid/work/calibre/src/calibre/ebooks/conversion/plumber.py:467 +#: /home/kovid/work/calibre/src/calibre/ebooks/conversion/plumber.py:479 #: /home/kovid/work/calibre/src/calibre/ebooks/metadata/cli.py:66 msgid "Set the ISBN of the book." msgstr "" -#: /home/kovid/work/calibre/src/calibre/ebooks/conversion/plumber.py:471 +#: /home/kovid/work/calibre/src/calibre/ebooks/conversion/plumber.py:483 #: /home/kovid/work/calibre/src/calibre/ebooks/metadata/cli.py:68 msgid "Set the tags for the book. Should be a comma separated list." msgstr "" -#: /home/kovid/work/calibre/src/calibre/ebooks/conversion/plumber.py:475 +#: /home/kovid/work/calibre/src/calibre/ebooks/conversion/plumber.py:487 #: /home/kovid/work/calibre/src/calibre/ebooks/metadata/cli.py:70 msgid "Set the book producer." msgstr "" -#: /home/kovid/work/calibre/src/calibre/ebooks/conversion/plumber.py:479 +#: /home/kovid/work/calibre/src/calibre/ebooks/conversion/plumber.py:491 #: /home/kovid/work/calibre/src/calibre/ebooks/metadata/cli.py:72 msgid "Set the language." msgstr "" -#: /home/kovid/work/calibre/src/calibre/ebooks/conversion/plumber.py:483 +#: /home/kovid/work/calibre/src/calibre/ebooks/conversion/plumber.py:495 msgid "Set the publication date." msgstr "" -#: /home/kovid/work/calibre/src/calibre/ebooks/conversion/plumber.py:487 +#: /home/kovid/work/calibre/src/calibre/ebooks/conversion/plumber.py:499 msgid "Set the book timestamp (used by the date column in calibre)." msgstr "" -#: /home/kovid/work/calibre/src/calibre/ebooks/conversion/plumber.py:491 +#: /home/kovid/work/calibre/src/calibre/ebooks/conversion/plumber.py:503 msgid "" "Enable heuristic processing. This option must be set for any heuristic " "processing to take place." msgstr "" -#: /home/kovid/work/calibre/src/calibre/ebooks/conversion/plumber.py:496 +#: /home/kovid/work/calibre/src/calibre/ebooks/conversion/plumber.py:508 msgid "" "Detect unformatted chapter headings and sub headings. Change them to h2 and " "h3 tags. This setting will not create a TOC, but can be used in conjunction " "with structure detection to create one." msgstr "" -#: /home/kovid/work/calibre/src/calibre/ebooks/conversion/plumber.py:503 +#: /home/kovid/work/calibre/src/calibre/ebooks/conversion/plumber.py:515 msgid "" "Look for common words and patterns that denote italics and italicize them." msgstr "" -#: /home/kovid/work/calibre/src/calibre/ebooks/conversion/plumber.py:508 +#: /home/kovid/work/calibre/src/calibre/ebooks/conversion/plumber.py:520 msgid "" "Turn indentation created from multiple non-breaking space entities into CSS " "indents." msgstr "" -#: /home/kovid/work/calibre/src/calibre/ebooks/conversion/plumber.py:513 +#: /home/kovid/work/calibre/src/calibre/ebooks/conversion/plumber.py:525 msgid "" "Scale used to determine the length at which a line should be unwrapped. " "Valid values are a decimal between 0 and 1. The default is 0.4, just below " @@ -1940,86 +2199,86 @@ msgid "" "unwrapping this value should be reduced" msgstr "" -#: /home/kovid/work/calibre/src/calibre/ebooks/conversion/plumber.py:521 +#: /home/kovid/work/calibre/src/calibre/ebooks/conversion/plumber.py:533 msgid "Unwrap lines using punctuation and other formatting clues." msgstr "" -#: /home/kovid/work/calibre/src/calibre/ebooks/conversion/plumber.py:525 +#: /home/kovid/work/calibre/src/calibre/ebooks/conversion/plumber.py:537 msgid "" "Remove empty paragraphs from the document when they exist between every " "other paragraph" msgstr "" -#: /home/kovid/work/calibre/src/calibre/ebooks/conversion/plumber.py:530 +#: /home/kovid/work/calibre/src/calibre/ebooks/conversion/plumber.py:542 msgid "" "Left aligned scene break markers are center aligned. Replace soft scene " "breaks that use multiple blank lines withhorizontal rules." msgstr "" -#: /home/kovid/work/calibre/src/calibre/ebooks/conversion/plumber.py:536 +#: /home/kovid/work/calibre/src/calibre/ebooks/conversion/plumber.py:548 msgid "" "Replace scene breaks with the specified text. By default, the text from the " "input document is used." msgstr "" -#: /home/kovid/work/calibre/src/calibre/ebooks/conversion/plumber.py:541 +#: /home/kovid/work/calibre/src/calibre/ebooks/conversion/plumber.py:553 msgid "" "Analyze hyphenated words throughout the document. The document itself is " "used as a dictionary to determine whether hyphens should be retained or " "removed." msgstr "" -#: /home/kovid/work/calibre/src/calibre/ebooks/conversion/plumber.py:547 +#: /home/kovid/work/calibre/src/calibre/ebooks/conversion/plumber.py:559 msgid "" "Looks for occurrences of sequential

      or

      tags. The tags are " "renumbered to prevent splitting in the middle of chapter headings." msgstr "" -#: /home/kovid/work/calibre/src/calibre/ebooks/conversion/plumber.py:553 +#: /home/kovid/work/calibre/src/calibre/ebooks/conversion/plumber.py:565 msgid "Search pattern (regular expression) to be replaced with sr1-replace." msgstr "" -#: /home/kovid/work/calibre/src/calibre/ebooks/conversion/plumber.py:558 +#: /home/kovid/work/calibre/src/calibre/ebooks/conversion/plumber.py:570 msgid "Replacement to replace the text found with sr1-search." msgstr "" -#: /home/kovid/work/calibre/src/calibre/ebooks/conversion/plumber.py:562 +#: /home/kovid/work/calibre/src/calibre/ebooks/conversion/plumber.py:574 msgid "Search pattern (regular expression) to be replaced with sr2-replace." msgstr "" -#: /home/kovid/work/calibre/src/calibre/ebooks/conversion/plumber.py:567 +#: /home/kovid/work/calibre/src/calibre/ebooks/conversion/plumber.py:579 msgid "Replacement to replace the text found with sr2-search." msgstr "" -#: /home/kovid/work/calibre/src/calibre/ebooks/conversion/plumber.py:571 +#: /home/kovid/work/calibre/src/calibre/ebooks/conversion/plumber.py:583 msgid "Search pattern (regular expression) to be replaced with sr3-replace." msgstr "" -#: /home/kovid/work/calibre/src/calibre/ebooks/conversion/plumber.py:576 +#: /home/kovid/work/calibre/src/calibre/ebooks/conversion/plumber.py:588 msgid "Replacement to replace the text found with sr3-search." msgstr "" -#: /home/kovid/work/calibre/src/calibre/ebooks/conversion/plumber.py:678 +#: /home/kovid/work/calibre/src/calibre/ebooks/conversion/plumber.py:690 msgid "Could not find an ebook inside the archive" msgstr "" -#: /home/kovid/work/calibre/src/calibre/ebooks/conversion/plumber.py:736 +#: /home/kovid/work/calibre/src/calibre/ebooks/conversion/plumber.py:748 msgid "Values of series index and rating must be numbers. Ignoring" msgstr "" -#: /home/kovid/work/calibre/src/calibre/ebooks/conversion/plumber.py:743 +#: /home/kovid/work/calibre/src/calibre/ebooks/conversion/plumber.py:755 msgid "Failed to parse date/time" msgstr "" -#: /home/kovid/work/calibre/src/calibre/ebooks/conversion/plumber.py:898 +#: /home/kovid/work/calibre/src/calibre/ebooks/conversion/plumber.py:914 msgid "Converting input to HTML..." msgstr "" -#: /home/kovid/work/calibre/src/calibre/ebooks/conversion/plumber.py:925 +#: /home/kovid/work/calibre/src/calibre/ebooks/conversion/plumber.py:941 msgid "Running transforms on ebook..." msgstr "" -#: /home/kovid/work/calibre/src/calibre/ebooks/conversion/plumber.py:1013 +#: /home/kovid/work/calibre/src/calibre/ebooks/conversion/plumber.py:1037 msgid "Creating" msgstr "" @@ -2134,7 +2393,7 @@ msgstr "" msgid "Do not insert a Table of Contents at the beginning of the book." msgstr "" -#: /home/kovid/work/calibre/src/calibre/ebooks/fb2/output.py:22 +#: /home/kovid/work/calibre/src/calibre/ebooks/fb2/output.py:149 msgid "" "Specify the sectionization of elements. A value of \"nothing\" turns the " "book into a single section. A value of \"files\" turns each file into a " @@ -2145,6 +2404,17 @@ msgid "" "of Contents)." msgstr "" +#: /home/kovid/work/calibre/src/calibre/ebooks/fb2/output.py:158 +msgid "" +"Genre for the book. Choices: %s\n" +"\n" +" See: " +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/ebooks/fb2/output.py:159 +msgid "for a complete list with descriptions." +msgstr "" + #: /home/kovid/work/calibre/src/calibre/ebooks/html/input.py:248 msgid "" "Traverse links in HTML files breadth first. Normally, they are traversed " @@ -2166,28 +2436,44 @@ msgid "" "pipeline." msgstr "" -#: /home/kovid/work/calibre/src/calibre/ebooks/html/output.py:33 +#: /home/kovid/work/calibre/src/calibre/ebooks/html/output.py:32 msgid "CSS file used for the output instead of the default file" msgstr "" -#: /home/kovid/work/calibre/src/calibre/ebooks/html/output.py:36 +#: /home/kovid/work/calibre/src/calibre/ebooks/html/output.py:35 msgid "" "Template used for generation of the html index file instead of the default " "file" msgstr "" -#: /home/kovid/work/calibre/src/calibre/ebooks/html/output.py:39 +#: /home/kovid/work/calibre/src/calibre/ebooks/html/output.py:38 msgid "" "Template used for the generation of the html contents of the book instead of " "the default file" msgstr "" -#: /home/kovid/work/calibre/src/calibre/ebooks/html/output.py:42 +#: /home/kovid/work/calibre/src/calibre/ebooks/html/output.py:41 msgid "" "Extract the contents of the generated ZIP file to the specified directory. " "WARNING: The contents of the directory will be deleted." msgstr "" +#: /home/kovid/work/calibre/src/calibre/ebooks/htmlz/output.py:30 +msgid "" +"Specify the handling of CSS. Default is class.\n" +"class: Use CSS classes and have elements reference them.\n" +"inline: Write the CSS as an inline style attribute.\n" +"tag: Turn as many CSS styles as possible into HTML tags." +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/ebooks/htmlz/output.py:38 +msgid "" +"How to handle the CSS when using css-type = 'class'.\n" +"Default is external.\n" +"external: Use an external CSS file that is linked in the document.\n" +"inline: Place the CSS in the head section of the document." +msgstr "" + #: /home/kovid/work/calibre/src/calibre/ebooks/lit/from_any.py:47 msgid "Creating LIT file from EPUB..." msgstr "Veido LIT failu no EPUB..." @@ -2315,7 +2601,6 @@ msgid "Path to output file" msgstr "" #: /home/kovid/work/calibre/src/calibre/ebooks/lrf/lrs/convert_from.py:290 -#: /home/kovid/work/calibre/src/calibre/ebooks/metadata/isbndb.py:126 msgid "Verbose processing" msgstr "" @@ -2451,146 +2736,106 @@ msgstr "" msgid "Comic" msgstr "" -#: /home/kovid/work/calibre/src/calibre/ebooks/metadata/amazonfr.py:26 -msgid "Downloads metadata from amazon.fr" -msgstr "" - -#: /home/kovid/work/calibre/src/calibre/ebooks/metadata/amazonfr.py:43 -msgid "Downloads metadata from amazon.com in spanish" -msgstr "" - -#: /home/kovid/work/calibre/src/calibre/ebooks/metadata/amazonfr.py:60 -msgid "Downloads metadata from amazon.com in english" -msgstr "" - -#: /home/kovid/work/calibre/src/calibre/ebooks/metadata/amazonfr.py:77 -msgid "Downloads metadata from amazon.de" -msgstr "" - -#: /home/kovid/work/calibre/src/calibre/ebooks/metadata/amazonfr.py:94 -msgid "Downloads metadata from amazon.com" -msgstr "" - -#: /home/kovid/work/calibre/src/calibre/ebooks/metadata/amazonfr.py:474 -msgid "" -" %prog [options]\n" -"\n" -" Fetch book metadata from Amazon. You must specify one of title, " -"author,\n" -" ISBN, publisher or keywords. Will fetch a maximum of 10 matches,\n" -" so you should make your query as specific as possible.\n" -" You can chose the language for metadata retrieval:\n" -" All & english & french & german & spanish\n" -" " -msgstr "" - #: /home/kovid/work/calibre/src/calibre/ebooks/metadata/archive.py:41 msgid "" "Extract common e-book formats from archives (zip/rar) files. Also try to " "autodetect if they are actually cbz/cbr files." msgstr "" -#: /home/kovid/work/calibre/src/calibre/ebooks/metadata/book/base.py:116 +#: /home/kovid/work/calibre/src/calibre/ebooks/metadata/book/base.py:145 msgid "TEMPLATE ERROR" msgstr "" -#: /home/kovid/work/calibre/src/calibre/ebooks/metadata/book/base.py:541 -#: /home/kovid/work/calibre/src/calibre/gui2/custom_column_widgets.py:64 -#: /home/kovid/work/calibre/src/calibre/gui2/custom_column_widgets.py:554 +#: /home/kovid/work/calibre/src/calibre/ebooks/metadata/book/base.py:628 +#: /home/kovid/work/calibre/src/calibre/gui2/custom_column_widgets.py:63 +#: /home/kovid/work/calibre/src/calibre/gui2/custom_column_widgets.py:563 msgid "No" msgstr "" -#: /home/kovid/work/calibre/src/calibre/ebooks/metadata/book/base.py:541 -#: /home/kovid/work/calibre/src/calibre/gui2/custom_column_widgets.py:64 -#: /home/kovid/work/calibre/src/calibre/gui2/custom_column_widgets.py:554 +#: /home/kovid/work/calibre/src/calibre/ebooks/metadata/book/base.py:628 +#: /home/kovid/work/calibre/src/calibre/gui2/custom_column_widgets.py:63 +#: /home/kovid/work/calibre/src/calibre/gui2/custom_column_widgets.py:563 msgid "Yes" msgstr "" -#: /home/kovid/work/calibre/src/calibre/ebooks/metadata/book/base.py:615 +#: /home/kovid/work/calibre/src/calibre/ebooks/metadata/book/base.py:723 #: /home/kovid/work/calibre/src/calibre/ebooks/pdf/manipulate/info.py:45 -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/book_info.py:112 -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/book_info.py:113 #: /home/kovid/work/calibre/src/calibre/gui2/dialogs/delete_matching_from_device.py:75 -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/fetch_metadata.py:60 -#: /home/kovid/work/calibre/src/calibre/gui2/library/models.py:65 -#: /home/kovid/work/calibre/src/calibre/gui2/library/models.py:418 +#: /home/kovid/work/calibre/src/calibre/gui2/library/models.py:63 #: /home/kovid/work/calibre/src/calibre/gui2/library/models.py:977 -#: /home/kovid/work/calibre/src/calibre/library/field_metadata.py:304 -#: /home/kovid/work/calibre/src/calibre/library/server/opds.py:590 +#: /home/kovid/work/calibre/src/calibre/gui2/metadata/single_download.py:132 +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/metadata_sources.py:152 +#: /home/kovid/work/calibre/src/calibre/gui2/store/mobileread/models.py:23 +#: /home/kovid/work/calibre/src/calibre/gui2/store/search/models.py:33 +#: /home/kovid/work/calibre/src/calibre/library/field_metadata.py:331 +#: /home/kovid/work/calibre/src/calibre/library/server/opds.py:574 msgid "Title" msgstr "Nosaukums" -#: /home/kovid/work/calibre/src/calibre/ebooks/metadata/book/base.py:616 -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/fetch_metadata.py:61 -#: /home/kovid/work/calibre/src/calibre/gui2/library/models.py:67 -#: /home/kovid/work/calibre/src/calibre/gui2/library/models.py:423 +#: /home/kovid/work/calibre/src/calibre/ebooks/metadata/book/base.py:724 +#: /home/kovid/work/calibre/src/calibre/gui2/library/models.py:65 #: /home/kovid/work/calibre/src/calibre/gui2/library/models.py:978 +#: /home/kovid/work/calibre/src/calibre/gui2/store/mobileread/models.py:23 msgid "Author(s)" msgstr "Autors(i)" -#: /home/kovid/work/calibre/src/calibre/ebooks/metadata/book/base.py:617 -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/fetch_metadata.py:63 -#: /home/kovid/work/calibre/src/calibre/gui2/library/models.py:72 +#: /home/kovid/work/calibre/src/calibre/ebooks/metadata/book/base.py:725 +#: /home/kovid/work/calibre/src/calibre/gui2/library/models.py:70 +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/metadata_sources.py:149 msgid "Publisher" msgstr "Izdevējs" -#: /home/kovid/work/calibre/src/calibre/ebooks/metadata/book/base.py:618 +#: /home/kovid/work/calibre/src/calibre/ebooks/metadata/book/base.py:726 #: /home/kovid/work/calibre/src/calibre/ebooks/pdf/manipulate/info.py:49 msgid "Producer" msgstr "" -#: /home/kovid/work/calibre/src/calibre/ebooks/metadata/book/base.py:619 -#: /home/kovid/work/calibre/src/calibre/gui2/book_details.py:40 -#: /home/kovid/work/calibre/src/calibre/gui2/book_details.py:214 -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/book_info.py:114 -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/book_info_ui.py:79 -#: /home/kovid/work/calibre/src/calibre/gui2/library/models.py:380 -#: /home/kovid/work/calibre/src/calibre/gui2/library/models.py:1184 -#: /home/kovid/work/calibre/src/calibre/library/field_metadata.py:188 +#: /home/kovid/work/calibre/src/calibre/ebooks/metadata/book/base.py:727 +#: /home/kovid/work/calibre/src/calibre/gui2/metadata/single.py:871 +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/metadata_sources.py:147 +#: /home/kovid/work/calibre/src/calibre/library/field_metadata.py:211 msgid "Comments" msgstr "Komentāri" -#: /home/kovid/work/calibre/src/calibre/ebooks/metadata/book/base.py:621 +#: /home/kovid/work/calibre/src/calibre/ebooks/metadata/book/base.py:729 #: /home/kovid/work/calibre/src/calibre/ebooks/oeb/transforms/jacket.py:170 -#: /home/kovid/work/calibre/src/calibre/gui2/book_details.py:30 #: /home/kovid/work/calibre/src/calibre/gui2/dialogs/tag_categories.py:60 -#: /home/kovid/work/calibre/src/calibre/gui2/library/models.py:73 -#: /home/kovid/work/calibre/src/calibre/gui2/library/models.py:368 -#: /home/kovid/work/calibre/src/calibre/gui2/library/models.py:1180 -#: /home/kovid/work/calibre/src/calibre/library/field_metadata.py:161 -#: /home/kovid/work/calibre/src/calibre/library/server/browse.py:691 +#: /home/kovid/work/calibre/src/calibre/gui2/library/models.py:71 +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/create_custom_column.py:67 +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/metadata_sources.py:151 +#: /home/kovid/work/calibre/src/calibre/library/field_metadata.py:171 +#: /home/kovid/work/calibre/src/calibre/library/server/browse.py:761 msgid "Tags" msgstr "Birkas" -#: /home/kovid/work/calibre/src/calibre/ebooks/metadata/book/base.py:623 +#: /home/kovid/work/calibre/src/calibre/ebooks/metadata/book/base.py:731 #: /home/kovid/work/calibre/src/calibre/ebooks/oeb/transforms/jacket.py:168 -#: /home/kovid/work/calibre/src/calibre/gui2/book_details.py:29 #: /home/kovid/work/calibre/src/calibre/gui2/dialogs/tag_categories.py:60 -#: /home/kovid/work/calibre/src/calibre/gui2/library/models.py:74 -#: /home/kovid/work/calibre/src/calibre/gui2/library/models.py:385 -#: /home/kovid/work/calibre/src/calibre/gui2/library/models.py:1189 -#: /home/kovid/work/calibre/src/calibre/library/field_metadata.py:109 +#: /home/kovid/work/calibre/src/calibre/gui2/library/models.py:72 +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/create_custom_column.py:67 +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/metadata_sources.py:153 +#: /home/kovid/work/calibre/src/calibre/library/field_metadata.py:114 msgid "Series" msgstr "Sērija" -#: /home/kovid/work/calibre/src/calibre/ebooks/metadata/book/base.py:624 +#: /home/kovid/work/calibre/src/calibre/ebooks/metadata/book/base.py:732 +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/metadata_sources.py:154 msgid "Language" msgstr "Valoda" -#: /home/kovid/work/calibre/src/calibre/ebooks/metadata/book/base.py:626 -#: /home/kovid/work/calibre/src/calibre/gui2/library/models.py:1172 +#: /home/kovid/work/calibre/src/calibre/ebooks/metadata/book/base.py:734 msgid "Timestamp" msgstr "" -#: /home/kovid/work/calibre/src/calibre/ebooks/metadata/book/base.py:628 +#: /home/kovid/work/calibre/src/calibre/ebooks/metadata/book/base.py:736 #: /home/kovid/work/calibre/src/calibre/ebooks/oeb/transforms/jacket.py:167 -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/fetch_metadata.py:65 -#: /home/kovid/work/calibre/src/calibre/gui2/library/models.py:70 -#: /home/kovid/work/calibre/src/calibre/library/field_metadata.py:259 +#: /home/kovid/work/calibre/src/calibre/gui2/library/models.py:68 +#: /home/kovid/work/calibre/src/calibre/gui2/metadata/single_download.py:132 +#: /home/kovid/work/calibre/src/calibre/library/field_metadata.py:271 msgid "Published" msgstr "Publicēts" -#: /home/kovid/work/calibre/src/calibre/ebooks/metadata/book/base.py:630 +#: /home/kovid/work/calibre/src/calibre/ebooks/metadata/book/base.py:738 msgid "Rights" msgstr "" @@ -2687,190 +2932,7 @@ msgstr "Vāks saglabāts" msgid "No cover found" msgstr "" -#: /home/kovid/work/calibre/src/calibre/ebooks/metadata/covers.py:27 -#: /home/kovid/work/calibre/src/calibre/ebooks/metadata/nicebooks.py:45 -msgid "Cover download" -msgstr "" - -#: /home/kovid/work/calibre/src/calibre/ebooks/metadata/covers.py:79 -msgid "Download covers from openlibrary.org" -msgstr "" - -#: /home/kovid/work/calibre/src/calibre/ebooks/metadata/covers.py:107 -msgid "ISBN: %s not found" -msgstr "" - -#: /home/kovid/work/calibre/src/calibre/ebooks/metadata/covers.py:117 -msgid "Download covers from amazon.com" -msgstr "" - -#: /home/kovid/work/calibre/src/calibre/ebooks/metadata/covers.py:205 -msgid "Download covers from Douban.com" -msgstr "" - -#: /home/kovid/work/calibre/src/calibre/ebooks/metadata/covers.py:214 -msgid "Douban.com API timed out. Try again later." -msgstr "" - -#: /home/kovid/work/calibre/src/calibre/ebooks/metadata/douban.py:42 -msgid "Downloads metadata from Douban.com" -msgstr "" - -#: /home/kovid/work/calibre/src/calibre/ebooks/metadata/fetch.py:57 -msgid "Metadata download" -msgstr "" - -#: /home/kovid/work/calibre/src/calibre/ebooks/metadata/fetch.py:141 -msgid "ratings" -msgstr "vērtējumi" - -#: /home/kovid/work/calibre/src/calibre/ebooks/metadata/fetch.py:141 -msgid "tags" -msgstr "" - -#: /home/kovid/work/calibre/src/calibre/ebooks/metadata/fetch.py:142 -msgid "description/reviews" -msgstr "" - -#: /home/kovid/work/calibre/src/calibre/ebooks/metadata/fetch.py:143 -msgid "Download %s from %s" -msgstr "" - -#: /home/kovid/work/calibre/src/calibre/ebooks/metadata/fetch.py:150 -msgid "Convert comments downloaded from %s to plain text" -msgstr "" - -#: /home/kovid/work/calibre/src/calibre/ebooks/metadata/fetch.py:178 -#: /home/kovid/work/calibre/src/calibre/ebooks/metadata/sources/google.py:159 -msgid "Downloads metadata from Google Books" -msgstr "" - -#: /home/kovid/work/calibre/src/calibre/ebooks/metadata/fetch.py:195 -msgid "Downloads metadata from isbndb.com" -msgstr "" - -#: /home/kovid/work/calibre/src/calibre/ebooks/metadata/fetch.py:223 -msgid "" -"To use isbndb.com you must sign up for a %sfree account%s and enter your " -"access key below." -msgstr "" - -#: /home/kovid/work/calibre/src/calibre/ebooks/metadata/fetch.py:233 -msgid "Downloads social metadata from amazon.com" -msgstr "" - -#: /home/kovid/work/calibre/src/calibre/ebooks/metadata/fetch.py:254 -msgid "Downloads series information from ww2.kdl.org" -msgstr "" - -#: /home/kovid/work/calibre/src/calibre/ebooks/metadata/fictionwise.py:25 -msgid "Downloads metadata from Fictionwise" -msgstr "" - -#: /home/kovid/work/calibre/src/calibre/ebooks/metadata/fictionwise.py:90 -#: /home/kovid/work/calibre/src/calibre/ebooks/metadata/nicebooks.py:108 -msgid "Query: %s" -msgstr "" - -#: /home/kovid/work/calibre/src/calibre/ebooks/metadata/fictionwise.py:100 -#: /home/kovid/work/calibre/src/calibre/ebooks/metadata/fictionwise.py:285 -msgid "Fictionwise timed out. Try again later." -msgstr "" - -#: /home/kovid/work/calibre/src/calibre/ebooks/metadata/fictionwise.py:101 -#: /home/kovid/work/calibre/src/calibre/ebooks/metadata/fictionwise.py:286 -msgid "Fictionwise encountered an error." -msgstr "" - -#: /home/kovid/work/calibre/src/calibre/ebooks/metadata/fictionwise.py:219 -msgid "" -"SUMMARY:\n" -" %s" -msgstr "" - -#: /home/kovid/work/calibre/src/calibre/ebooks/metadata/fictionwise.py:316 -#: /home/kovid/work/calibre/src/calibre/ebooks/metadata/fictionwise.py:333 -msgid "Failed to get all details for an entry" -msgstr "" - -#: /home/kovid/work/calibre/src/calibre/ebooks/metadata/fictionwise.py:354 -msgid "" -" %prog [options]\n" -"\n" -" Fetch book metadata from Fictionwise. You must specify one of title, " -"author,\n" -" or keywords. No ISBN specification possible. Will fetch a maximum of " -"20 matches,\n" -" so you should make your query as specific as possible.\n" -" " -msgstr "" - -#: /home/kovid/work/calibre/src/calibre/ebooks/metadata/fictionwise.py:362 -#: /home/kovid/work/calibre/src/calibre/ebooks/metadata/nicebooks.py:363 -msgid "Book title" -msgstr "" - -#: /home/kovid/work/calibre/src/calibre/ebooks/metadata/fictionwise.py:363 -#: /home/kovid/work/calibre/src/calibre/ebooks/metadata/nicebooks.py:364 -msgid "Book author(s)" -msgstr "" - -#: /home/kovid/work/calibre/src/calibre/ebooks/metadata/fictionwise.py:364 -#: /home/kovid/work/calibre/src/calibre/ebooks/metadata/nicebooks.py:365 -msgid "Book publisher" -msgstr "" - -#: /home/kovid/work/calibre/src/calibre/ebooks/metadata/fictionwise.py:365 -#: /home/kovid/work/calibre/src/calibre/ebooks/metadata/nicebooks.py:367 -msgid "Keywords" -msgstr "" - -#: /home/kovid/work/calibre/src/calibre/ebooks/metadata/fictionwise.py:367 -#: /home/kovid/work/calibre/src/calibre/ebooks/metadata/nicebooks.py:373 -msgid "Maximum number of results to fetch" -msgstr "" - -#: /home/kovid/work/calibre/src/calibre/ebooks/metadata/fictionwise.py:369 -#: /home/kovid/work/calibre/src/calibre/ebooks/metadata/nicebooks.py:375 -msgid "Be more verbose about errors" -msgstr "" - -#: /home/kovid/work/calibre/src/calibre/ebooks/metadata/fictionwise.py:383 -#: /home/kovid/work/calibre/src/calibre/ebooks/metadata/nicebooks.py:390 -msgid "No result found for this search!" -msgstr "" - -#: /home/kovid/work/calibre/src/calibre/ebooks/metadata/isbndb.py:107 -msgid "" -"\n" -"%prog [options] key\n" -"\n" -"Fetch metadata for books from isndb.com. You can specify either the\n" -"books ISBN ID or its title and author. If you specify the title and author,\n" -"then more than one book may be returned.\n" -"\n" -"key is the account key you generate after signing up for a free account from " -"isbndb.com.\n" -"\n" -msgstr "" - -#: /home/kovid/work/calibre/src/calibre/ebooks/metadata/isbndb.py:118 -msgid "The ISBN ID of the book you want metadata for." -msgstr "" - -#: /home/kovid/work/calibre/src/calibre/ebooks/metadata/isbndb.py:120 -msgid "The author whose book to search for." -msgstr "Meklējamās grāmatas autors." - -#: /home/kovid/work/calibre/src/calibre/ebooks/metadata/isbndb.py:122 -msgid "The title of the book to search for." -msgstr "Meklējamās grāmatas nosaukums." - -#: /home/kovid/work/calibre/src/calibre/ebooks/metadata/isbndb.py:124 -msgid "The publisher of the book to search for." -msgstr "Meklējamās grāmatas izdevējs." - -#: /home/kovid/work/calibre/src/calibre/ebooks/metadata/library_thing.py:75 +#: /home/kovid/work/calibre/src/calibre/ebooks/metadata/library_thing.py:64 msgid "" "\n" "%prog [options] ISBN\n" @@ -2879,82 +2941,108 @@ msgid "" "LibraryThing.com\n" msgstr "" -#: /home/kovid/work/calibre/src/calibre/ebooks/metadata/nicebooks.py:26 -msgid "Downloads metadata from french Nicebooks" -msgstr "" - -#: /home/kovid/work/calibre/src/calibre/ebooks/metadata/nicebooks.py:42 -msgid "Downloads covers from french Nicebooks" -msgstr "" - -#: /home/kovid/work/calibre/src/calibre/ebooks/metadata/nicebooks.py:118 -#: /home/kovid/work/calibre/src/calibre/ebooks/metadata/nicebooks.py:242 -#: /home/kovid/work/calibre/src/calibre/ebooks/metadata/nicebooks.py:320 -msgid "Nicebooks timed out. Try again later." -msgstr "" - -#: /home/kovid/work/calibre/src/calibre/ebooks/metadata/nicebooks.py:119 -#: /home/kovid/work/calibre/src/calibre/ebooks/metadata/nicebooks.py:243 -msgid "Nicebooks encountered an error." -msgstr "" - -#: /home/kovid/work/calibre/src/calibre/ebooks/metadata/nicebooks.py:323 -msgid "ISBN: %s not found." -msgstr "" - -#: /home/kovid/work/calibre/src/calibre/ebooks/metadata/nicebooks.py:324 -msgid "An errror occured with Nicebooks cover fetcher" -msgstr "" - -#: /home/kovid/work/calibre/src/calibre/ebooks/metadata/nicebooks.py:354 -msgid "" -" %prog [options]\n" -"\n" -" Fetch book metadata from Nicebooks. You must specify one of title, " -"author,\n" -" ISBN, publisher or keywords. Will fetch a maximum of 20 matches,\n" -" so you should make your query as specific as possible.\n" -" It can also get covers if the option is activated.\n" -" " -msgstr "" - -#: /home/kovid/work/calibre/src/calibre/ebooks/metadata/nicebooks.py:366 -msgid "Book ISBN" -msgstr "" - -#: /home/kovid/work/calibre/src/calibre/ebooks/metadata/nicebooks.py:369 -msgid "Covers: 1-Check/ 2-Download" -msgstr "" - -#: /home/kovid/work/calibre/src/calibre/ebooks/metadata/nicebooks.py:371 -msgid "Covers files path" -msgstr "" - -#: /home/kovid/work/calibre/src/calibre/ebooks/metadata/nicebooks.py:396 -msgid "No cover found!" -msgstr "" - -#: /home/kovid/work/calibre/src/calibre/ebooks/metadata/nicebooks.py:398 -msgid "A cover was found for this book" -msgstr "" - -#: /home/kovid/work/calibre/src/calibre/ebooks/metadata/nicebooks.py:407 -msgid "Cover saved to file " -msgstr "" - -#: /home/kovid/work/calibre/src/calibre/ebooks/metadata/opf2.py:1312 -#: /home/kovid/work/calibre/src/calibre/ebooks/oeb/base.py:1448 +#: /home/kovid/work/calibre/src/calibre/ebooks/metadata/opf2.py:1358 +#: /home/kovid/work/calibre/src/calibre/ebooks/oeb/base.py:1493 +#: /home/kovid/work/calibre/src/calibre/gui2/metadata/single.py:883 +#: /home/kovid/work/calibre/src/calibre/gui2/store/search/models.py:33 msgid "Cover" msgstr "Vāks" -#: /home/kovid/work/calibre/src/calibre/ebooks/metadata/sources/amazon.py:16 -msgid "Downloads metadata from Amazon" +#: /home/kovid/work/calibre/src/calibre/ebooks/metadata/sources/amazon.py:384 +msgid "Downloads metadata and covers from Amazon" msgstr "" -#: /home/kovid/work/calibre/src/calibre/ebooks/metadata/sources/base.py:22 +#: /home/kovid/work/calibre/src/calibre/ebooks/metadata/sources/amazon.py:394 +msgid "US" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/ebooks/metadata/sources/amazon.py:395 +msgid "France" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/ebooks/metadata/sources/amazon.py:396 +msgid "Germany" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/ebooks/metadata/sources/amazon.py:397 +msgid "UK" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/ebooks/metadata/sources/amazon.py:398 +msgid "Italy" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/ebooks/metadata/sources/amazon.py:402 +msgid "Amazon website to use:" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/ebooks/metadata/sources/amazon.py:403 +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:508 +msgid "Amazon timed out. Try again later." +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/ebooks/metadata/sources/base.py:159 msgid "Metadata source" msgstr "" +#: /home/kovid/work/calibre/src/calibre/ebooks/metadata/sources/douban.py:154 +msgid "Downloads metadata and covers from Douban.com" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/ebooks/metadata/sources/google.py:160 +msgid "Downloads metadata and covers from Google Books" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/ebooks/metadata/sources/isbndb.py:27 +msgid "Downloads metadata from isbndb.com" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/ebooks/metadata/sources/isbndb.py:37 +msgid "IsbnDB key:" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/ebooks/metadata/sources/isbndb.py:38 +msgid "" +"To use isbndb.com you have to sign up for a free accountat isbndb.com and " +"get an access key." +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/ebooks/metadata/sources/isbndb.py:42 +msgid "" +"To use metadata from isbndb.com you must sign up for a free account and get " +"an isbndb key and enter it below. Instructions to get the key are here." +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/ebooks/metadata/sources/openlibrary.py:15 +msgid "Downloads covers from The Open Library" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/ebooks/metadata/sources/overdrive.py:33 +msgid "Downloads metadata and covers from Overdrive's Content Reserve" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/ebooks/metadata/sources/overdrive.py:45 +msgid "Download all metadata (slow)" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/ebooks/metadata/sources/overdrive.py:46 +msgid "Enable this option to gather all metadata available from Overdrive." +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/ebooks/metadata/sources/overdrive.py:49 +msgid "" +"Additional metadata can be taken from Overdrive's book detail page. This " +"includes a limited set of tags used by libraries, comments, language, and " +"the ebook ISBN. Collecting this data is disabled by default due to the extra " +"time required. Check the download all metadata option below to enable " +"downloading this data." +msgstr "" + #: /home/kovid/work/calibre/src/calibre/ebooks/mobi/output.py:22 msgid "Modify images to meet Palm device size limitations." msgstr "" @@ -2993,74 +3081,74 @@ msgstr "" msgid "All articles" msgstr "Visi raksti" -#: /home/kovid/work/calibre/src/calibre/ebooks/mobi/reader.py:267 +#: /home/kovid/work/calibre/src/calibre/ebooks/mobi/reader.py:274 msgid "This is an Amazon Topaz book. It cannot be processed." msgstr "" -#: /home/kovid/work/calibre/src/calibre/ebooks/oeb/base.py:1449 +#: /home/kovid/work/calibre/src/calibre/ebooks/oeb/base.py:1494 msgid "Title Page" msgstr "Titullapa" -#: /home/kovid/work/calibre/src/calibre/ebooks/oeb/base.py:1450 +#: /home/kovid/work/calibre/src/calibre/ebooks/oeb/base.py:1495 #: /home/kovid/work/calibre/src/calibre/ebooks/oeb/transforms/htmltoc.py:15 -#: /home/kovid/work/calibre/src/calibre/gui2/viewer/main.py:54 +#: /home/kovid/work/calibre/src/calibre/gui2/viewer/main.py:56 #: /home/kovid/work/calibre/src/calibre/gui2/viewer/main_ui.py:199 msgid "Table of Contents" msgstr "Satura rādītājs" -#: /home/kovid/work/calibre/src/calibre/ebooks/oeb/base.py:1451 +#: /home/kovid/work/calibre/src/calibre/ebooks/oeb/base.py:1496 msgid "Index" msgstr "" -#: /home/kovid/work/calibre/src/calibre/ebooks/oeb/base.py:1452 +#: /home/kovid/work/calibre/src/calibre/ebooks/oeb/base.py:1497 msgid "Glossary" msgstr "" -#: /home/kovid/work/calibre/src/calibre/ebooks/oeb/base.py:1453 +#: /home/kovid/work/calibre/src/calibre/ebooks/oeb/base.py:1498 msgid "Acknowledgements" msgstr "" -#: /home/kovid/work/calibre/src/calibre/ebooks/oeb/base.py:1454 +#: /home/kovid/work/calibre/src/calibre/ebooks/oeb/base.py:1499 msgid "Bibliography" msgstr "Bibliogrāfija" -#: /home/kovid/work/calibre/src/calibre/ebooks/oeb/base.py:1455 +#: /home/kovid/work/calibre/src/calibre/ebooks/oeb/base.py:1500 msgid "Colophon" msgstr "" -#: /home/kovid/work/calibre/src/calibre/ebooks/oeb/base.py:1456 +#: /home/kovid/work/calibre/src/calibre/ebooks/oeb/base.py:1501 msgid "Copyright" msgstr "" -#: /home/kovid/work/calibre/src/calibre/ebooks/oeb/base.py:1457 +#: /home/kovid/work/calibre/src/calibre/ebooks/oeb/base.py:1502 msgid "Dedication" msgstr "" -#: /home/kovid/work/calibre/src/calibre/ebooks/oeb/base.py:1458 +#: /home/kovid/work/calibre/src/calibre/ebooks/oeb/base.py:1503 msgid "Epigraph" msgstr "" -#: /home/kovid/work/calibre/src/calibre/ebooks/oeb/base.py:1459 +#: /home/kovid/work/calibre/src/calibre/ebooks/oeb/base.py:1504 msgid "Foreword" msgstr "Priekšvārds" -#: /home/kovid/work/calibre/src/calibre/ebooks/oeb/base.py:1460 +#: /home/kovid/work/calibre/src/calibre/ebooks/oeb/base.py:1505 msgid "List of Illustrations" msgstr "Ilustrāciju saraksts" -#: /home/kovid/work/calibre/src/calibre/ebooks/oeb/base.py:1461 +#: /home/kovid/work/calibre/src/calibre/ebooks/oeb/base.py:1506 msgid "List of Tables" msgstr "Tabulu saraksts" -#: /home/kovid/work/calibre/src/calibre/ebooks/oeb/base.py:1462 +#: /home/kovid/work/calibre/src/calibre/ebooks/oeb/base.py:1507 msgid "Notes" msgstr "Piezīmes" -#: /home/kovid/work/calibre/src/calibre/ebooks/oeb/base.py:1463 +#: /home/kovid/work/calibre/src/calibre/ebooks/oeb/base.py:1508 msgid "Preface" msgstr "Priekšvārds" -#: /home/kovid/work/calibre/src/calibre/ebooks/oeb/base.py:1464 +#: /home/kovid/work/calibre/src/calibre/ebooks/oeb/base.py:1509 msgid "Main Text" msgstr "Pamatteksts" @@ -3070,8 +3158,7 @@ msgstr "" #: /home/kovid/work/calibre/src/calibre/ebooks/oeb/transforms/cover.py:98 #: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk.py:176 -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single.py:220 -#: /home/kovid/work/calibre/src/calibre/gui2/metadata/basic_widgets.py:703 +#: /home/kovid/work/calibre/src/calibre/gui2/metadata/basic_widgets.py:783 msgid "Book %s of %s" msgstr "" @@ -3080,8 +3167,10 @@ msgid "HTML TOC generation options." msgstr "Iestatījumi HTML satura rādītāja ģenerēšanai." #: /home/kovid/work/calibre/src/calibre/ebooks/oeb/transforms/jacket.py:169 -#: /home/kovid/work/calibre/src/calibre/gui2/library/models.py:71 -#: /home/kovid/work/calibre/src/calibre/library/server/browse.py:689 +#: /home/kovid/work/calibre/src/calibre/gui2/library/models.py:69 +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/create_custom_column.py:68 +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/metadata_sources.py:150 +#: /home/kovid/work/calibre/src/calibre/library/server/browse.py:759 msgid "Rating" msgstr "Vērtējums" @@ -3107,7 +3196,7 @@ msgstr "" msgid "Footnotes" msgstr "" -#: /home/kovid/work/calibre/src/calibre/ebooks/pdb/ereader/reader132.py:135 +#: /home/kovid/work/calibre/src/calibre/ebooks/pdb/ereader/reader132.py:139 msgid "Sidebar" msgstr "" @@ -3122,9 +3211,9 @@ msgid "" msgstr "" #: /home/kovid/work/calibre/src/calibre/ebooks/pdb/output.py:32 -#: /home/kovid/work/calibre/src/calibre/ebooks/pml/output.py:37 +#: /home/kovid/work/calibre/src/calibre/ebooks/pml/output.py:36 #: /home/kovid/work/calibre/src/calibre/ebooks/rb/output.py:21 -#: /home/kovid/work/calibre/src/calibre/ebooks/txt/output.py:41 +#: /home/kovid/work/calibre/src/calibre/ebooks/txt/output.py:40 msgid "Add Table of Contents to beginning of the book." msgstr "" @@ -3240,11 +3329,12 @@ msgstr "" #: /home/kovid/work/calibre/src/calibre/ebooks/pdf/manipulate/info.py:46 #: /home/kovid/work/calibre/src/calibre/gui2/dialogs/delete_matching_from_device.py:75 -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/edit_authors_dialog.py:35 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/edit_authors_dialog.py:40 msgid "Author" msgstr "Autors" #: /home/kovid/work/calibre/src/calibre/ebooks/pdf/manipulate/info.py:47 +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/emailp.py:27 msgid "Subject" msgstr "" @@ -3348,16 +3438,16 @@ msgid "" "full first page of the generated pdf." msgstr "" -#: /home/kovid/work/calibre/src/calibre/ebooks/pdf/pdftohtml.py:55 +#: /home/kovid/work/calibre/src/calibre/ebooks/pdf/pdftohtml.py:57 msgid "Could not find pdftohtml, check it is in your PATH" msgstr "" -#: /home/kovid/work/calibre/src/calibre/ebooks/pml/output.py:33 +#: /home/kovid/work/calibre/src/calibre/ebooks/pml/output.py:32 msgid "" "Specify the character encoding of the output document. The default is cp1252." msgstr "" -#: /home/kovid/work/calibre/src/calibre/ebooks/pml/output.py:40 +#: /home/kovid/work/calibre/src/calibre/ebooks/pml/output.py:39 msgid "" "Do not reduce the size or bit depth of images. Images have their size and " "depth reduced by default to accommodate applications that can not convert " @@ -3369,7 +3459,7 @@ msgstr "" msgid "Table of Contents:" msgstr "" -#: /home/kovid/work/calibre/src/calibre/ebooks/rtf/input.py:271 +#: /home/kovid/work/calibre/src/calibre/ebooks/rtf/input.py:272 msgid "" "This RTF file has a feature calibre does not support. Convert it to HTML " "first and then try it.\n" @@ -3382,13 +3472,13 @@ msgstr "" #: /home/kovid/work/calibre/src/calibre/ebooks/snb/output.py:25 #: /home/kovid/work/calibre/src/calibre/ebooks/tcr/output.py:23 -#: /home/kovid/work/calibre/src/calibre/ebooks/txt/output.py:37 +#: /home/kovid/work/calibre/src/calibre/ebooks/txt/output.py:36 msgid "" "Specify the character encoding of the output document. The default is utf-8." msgstr "" #: /home/kovid/work/calibre/src/calibre/ebooks/snb/output.py:29 -#: /home/kovid/work/calibre/src/calibre/ebooks/txt/output.py:44 +#: /home/kovid/work/calibre/src/calibre/ebooks/txt/output.py:43 msgid "" "The maximum number of characters per line. This splits on the first space " "before the specified value. If no space is found the line will be broken at " @@ -3476,20 +3566,20 @@ msgstr "" msgid "Do not insert a Table of Contents into the output text." msgstr "" -#: /home/kovid/work/calibre/src/calibre/ebooks/txt/output.py:31 +#: /home/kovid/work/calibre/src/calibre/ebooks/txt/output.py:30 msgid "" "Type of newline to use. Options are %s. Default is 'system'. Use 'old_mac' " "for compatibility with Mac OS 9 and earlier. For Mac OS X use 'unix'. " "'system' will default to the newline type used by this OS." msgstr "" -#: /home/kovid/work/calibre/src/calibre/ebooks/txt/output.py:51 +#: /home/kovid/work/calibre/src/calibre/ebooks/txt/output.py:50 msgid "" "Force splitting on the max-line-length value when no space is present. Also " "allows max-line-length to be below the minimum" msgstr "" -#: /home/kovid/work/calibre/src/calibre/ebooks/txt/output.py:56 +#: /home/kovid/work/calibre/src/calibre/ebooks/txt/output.py:55 msgid "" "Formatting used within the document.\n" "* plain: Produce plain text.\n" @@ -3497,169 +3587,232 @@ msgid "" "* textile: Produce Textile formatted text." msgstr "" -#: /home/kovid/work/calibre/src/calibre/ebooks/txt/output.py:62 +#: /home/kovid/work/calibre/src/calibre/ebooks/txt/output.py:61 msgid "" "Do not remove links within the document. This is only useful when paired " "with a txt-output-formatting option that is not none because links are " "always removed with plain text output." msgstr "" -#: /home/kovid/work/calibre/src/calibre/ebooks/txt/output.py:67 +#: /home/kovid/work/calibre/src/calibre/ebooks/txt/output.py:66 msgid "" "Do not remove image references within the document. This is only useful when " "paired with a txt-output-formatting option that is not none because links " "are always removed with plain text output." msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/__init__.py:71 +#: /home/kovid/work/calibre/src/calibre/ebooks/txt/output.py:71 +msgid "" +"Do not remove font color from output. This is only useful when txt-output-" +"formatting is set to textile. Textile is the only formatting that supports " +"setting font color. If this option is not specified font color will not be " +"set and default to the color displayed by the reader (generally this is " +"black)." +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/__init__.py:103 msgid "Send file to storage card instead of main memory by default" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/__init__.py:73 +#: /home/kovid/work/calibre/src/calibre/gui2/__init__.py:105 msgid "Confirm before deleting" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/__init__.py:75 +#: /home/kovid/work/calibre/src/calibre/gui2/__init__.py:107 msgid "Main window geometry" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/__init__.py:77 +#: /home/kovid/work/calibre/src/calibre/gui2/__init__.py:109 msgid "Notify when a new version is available" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/__init__.py:79 +#: /home/kovid/work/calibre/src/calibre/gui2/__init__.py:111 msgid "Use Roman numerals for series number" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/__init__.py:81 +#: /home/kovid/work/calibre/src/calibre/gui2/__init__.py:113 msgid "Sort tags list by name, popularity, or rating" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/__init__.py:83 +#: /home/kovid/work/calibre/src/calibre/gui2/__init__.py:115 +msgid "Match tags by any or all." +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/__init__.py:117 msgid "Number of covers to show in the cover browsing mode" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/__init__.py:85 +#: /home/kovid/work/calibre/src/calibre/gui2/__init__.py:119 msgid "Defaults for conversion to LRF" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/__init__.py:87 +#: /home/kovid/work/calibre/src/calibre/gui2/__init__.py:121 msgid "Options for the LRF ebook viewer" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/__init__.py:90 +#: /home/kovid/work/calibre/src/calibre/gui2/__init__.py:124 msgid "Formats that are viewed using the internal viewer" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/__init__.py:92 +#: /home/kovid/work/calibre/src/calibre/gui2/__init__.py:126 msgid "Columns to be displayed in the book list" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/__init__.py:93 +#: /home/kovid/work/calibre/src/calibre/gui2/__init__.py:127 msgid "Automatically launch content server on application startup" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/__init__.py:94 +#: /home/kovid/work/calibre/src/calibre/gui2/__init__.py:128 msgid "Oldest news kept in database" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/__init__.py:95 +#: /home/kovid/work/calibre/src/calibre/gui2/__init__.py:129 msgid "Show system tray icon" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/__init__.py:97 +#: /home/kovid/work/calibre/src/calibre/gui2/__init__.py:131 msgid "Upload downloaded news to device" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/__init__.py:99 +#: /home/kovid/work/calibre/src/calibre/gui2/__init__.py:133 msgid "Delete books from library after uploading to device" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/__init__.py:101 +#: /home/kovid/work/calibre/src/calibre/gui2/__init__.py:135 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:103 +#: /home/kovid/work/calibre/src/calibre/gui2/__init__.py:137 msgid "Disable notifications from the system tray icon" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/__init__.py:105 +#: /home/kovid/work/calibre/src/calibre/gui2/__init__.py:139 msgid "Default action to perform when send to device button is clicked" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/__init__.py:110 +#: /home/kovid/work/calibre/src/calibre/gui2/__init__.py:144 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:113 +#: /home/kovid/work/calibre/src/calibre/gui2/__init__.py:147 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:131 -msgid "Maximum number of waiting worker processes" +#: /home/kovid/work/calibre/src/calibre/gui2/__init__.py:165 +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:133 +#: /home/kovid/work/calibre/src/calibre/gui2/__init__.py:169 msgid "Download social metadata (tags/rating/etc.)" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/__init__.py:135 +#: /home/kovid/work/calibre/src/calibre/gui2/__init__.py:171 msgid "Overwrite author and title with new metadata" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/__init__.py:137 -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/fetch_metadata_ui.py:101 +#: /home/kovid/work/calibre/src/calibre/gui2/__init__.py:173 msgid "Automatically download the cover, if available" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/__init__.py:139 +#: /home/kovid/work/calibre/src/calibre/gui2/__init__.py:175 msgid "Limit max simultaneous jobs to number of CPUs" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/__init__.py:141 +#: /home/kovid/work/calibre/src/calibre/gui2/__init__.py:177 msgid "The layout of the user interface" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/__init__.py:143 +#: /home/kovid/work/calibre/src/calibre/gui2/__init__.py:179 msgid "Show the average rating per item indication in the tag browser" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/__init__.py:145 +#: /home/kovid/work/calibre/src/calibre/gui2/__init__.py:181 msgid "Disable UI animations" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/__init__.py:150 +#: /home/kovid/work/calibre/src/calibre/gui2/__init__.py:186 msgid "tag browser categories not to display" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/__init__.py:419 +#: /home/kovid/work/calibre/src/calibre/gui2/__init__.py:461 msgid "Choose Files" msgstr "" #: /home/kovid/work/calibre/src/calibre/gui2/actions/add.py:29 -msgid "Add books" -msgstr "Pievienot grāmatas" +#: /home/kovid/work/calibre/src/calibre/gui2/metadata/basic_widgets.py:599 +msgid "Books" +msgstr "Grāmatas" #: /home/kovid/work/calibre/src/calibre/gui2/actions/add.py:30 -msgid "Add books to the calibre library/device from files on your computer" -msgstr "" +msgid "EPUB Books" +msgstr "EPUB grāmatas" #: /home/kovid/work/calibre/src/calibre/gui2/actions/add.py:31 -msgid "A" +msgid "LRF Books" +msgstr "LRF grāmatas" + +#: /home/kovid/work/calibre/src/calibre/gui2/actions/add.py:32 +msgid "HTML Books" +msgstr "HTML grāmatas" + +#: /home/kovid/work/calibre/src/calibre/gui2/actions/add.py:33 +msgid "LIT Books" +msgstr "LIT grāmatas" + +#: /home/kovid/work/calibre/src/calibre/gui2/actions/add.py:34 +msgid "MOBI Books" +msgstr "MOBI grāmatas" + +#: /home/kovid/work/calibre/src/calibre/gui2/actions/add.py:35 +msgid "Topaz books" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/actions/add.py:36 +msgid "Text books" msgstr "" #: /home/kovid/work/calibre/src/calibre/gui2/actions/add.py:37 +msgid "PDF Books" +msgstr "PDF grāmatas" + +#: /home/kovid/work/calibre/src/calibre/gui2/actions/add.py:38 +msgid "SNB Books" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/actions/add.py:39 +msgid "Comics" +msgstr "Komiki" + +#: /home/kovid/work/calibre/src/calibre/gui2/actions/add.py:40 +msgid "Archives" +msgstr "Arhīvi" + +#: /home/kovid/work/calibre/src/calibre/gui2/actions/add.py:47 +msgid "Add books" +msgstr "Pievienot grāmatas" + +#: /home/kovid/work/calibre/src/calibre/gui2/actions/add.py:48 +msgid "Add books to the calibre library/device from files on your computer" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/actions/add.py:49 +msgid "A" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/actions/add.py:55 msgid "Add books from a single directory" msgstr "Pievienot vienā katalogā esošas grāmatas" -#: /home/kovid/work/calibre/src/calibre/gui2/actions/add.py:39 +#: /home/kovid/work/calibre/src/calibre/gui2/actions/add.py:57 msgid "" "Add books from directories, including sub-directories (One book per " "directory, assumes every ebook file is the same book in a different format)" @@ -3668,7 +3821,7 @@ msgstr "" "(Katalogā viena grāmata, katrs e-grāmatu fails tiek uzskatītas par vienu un " "to pašu grāmatu dažādos formātos)" -#: /home/kovid/work/calibre/src/calibre/gui2/actions/add.py:43 +#: /home/kovid/work/calibre/src/calibre/gui2/actions/add.py:61 msgid "" "Add books from directories, including sub directories (Multiple books per " "directory, assumes every ebook file is a different book)" @@ -3677,124 +3830,103 @@ msgstr "" "(Vairākas grāmatas katalogā, atsevišķi e-grāmatu faili tiek uzskatīti par " "dažādām grāmatām)" -#: /home/kovid/work/calibre/src/calibre/gui2/actions/add.py:47 +#: /home/kovid/work/calibre/src/calibre/gui2/actions/add.py:65 msgid "Add Empty book. (Book entry with no formats)" msgstr "Pievienot tukšu grāmatu. (Grāmatas ierakstu bez formātiem)" -#: /home/kovid/work/calibre/src/calibre/gui2/actions/add.py:48 +#: /home/kovid/work/calibre/src/calibre/gui2/actions/add.py:66 msgid "Shift+Ctrl+E" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/actions/add.py:49 +#: /home/kovid/work/calibre/src/calibre/gui2/actions/add.py:67 msgid "Add from ISBN" msgstr "Pievienot no ISBN" -#: /home/kovid/work/calibre/src/calibre/gui2/actions/add.py:113 +#: /home/kovid/work/calibre/src/calibre/gui2/actions/add.py:69 +msgid "Add files to selected book records" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/actions/add.py:70 +msgid "Shift+A" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/actions/add.py:90 +msgid "Are you sure" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/actions/add.py:91 +msgid "" +"Are you sure you want to add the same files to all %d books? If the " +"formatalready exists for a book, it will be replaced." +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/actions/add.py:97 +msgid "Select book files" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/actions/add.py:168 msgid "Adding" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/actions/add.py:114 +#: /home/kovid/work/calibre/src/calibre/gui2/actions/add.py:169 msgid "Creating book records from ISBNs" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/actions/add.py:194 -#: /home/kovid/work/calibre/src/calibre/gui2/actions/add.py:256 +#: /home/kovid/work/calibre/src/calibre/gui2/actions/add.py:268 +#: /home/kovid/work/calibre/src/calibre/gui2/actions/add.py:317 msgid "Uploading books to device." msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/actions/add.py:211 -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single.py:308 -#: /home/kovid/work/calibre/src/calibre/gui2/metadata/basic_widgets.py:529 -msgid "Books" -msgstr "Grāmatas" - -#: /home/kovid/work/calibre/src/calibre/gui2/actions/add.py:212 -msgid "EPUB Books" -msgstr "EPUB grāmatas" - -#: /home/kovid/work/calibre/src/calibre/gui2/actions/add.py:213 -msgid "LRF Books" -msgstr "LRF grāmatas" - -#: /home/kovid/work/calibre/src/calibre/gui2/actions/add.py:214 -msgid "HTML Books" -msgstr "HTML grāmatas" - -#: /home/kovid/work/calibre/src/calibre/gui2/actions/add.py:215 -msgid "LIT Books" -msgstr "LIT grāmatas" - -#: /home/kovid/work/calibre/src/calibre/gui2/actions/add.py:216 -msgid "MOBI Books" -msgstr "MOBI grāmatas" - -#: /home/kovid/work/calibre/src/calibre/gui2/actions/add.py:217 -msgid "Topaz books" -msgstr "" - -#: /home/kovid/work/calibre/src/calibre/gui2/actions/add.py:218 -msgid "Text books" -msgstr "" - -#: /home/kovid/work/calibre/src/calibre/gui2/actions/add.py:219 -msgid "PDF Books" -msgstr "PDF grāmatas" - -#: /home/kovid/work/calibre/src/calibre/gui2/actions/add.py:220 -msgid "SNB Books" -msgstr "" - -#: /home/kovid/work/calibre/src/calibre/gui2/actions/add.py:221 -msgid "Comics" -msgstr "Komiki" - -#: /home/kovid/work/calibre/src/calibre/gui2/actions/add.py:222 -msgid "Archives" -msgstr "Arhīvi" - -#: /home/kovid/work/calibre/src/calibre/gui2/actions/add.py:227 +#: /home/kovid/work/calibre/src/calibre/gui2/actions/add.py:288 msgid "Supported books" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/actions/add.py:266 +#: /home/kovid/work/calibre/src/calibre/gui2/actions/add.py:291 +msgid "Select books" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/actions/add.py:328 msgid "Merged some books" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/actions/add.py:267 +#: /home/kovid/work/calibre/src/calibre/gui2/actions/add.py:329 msgid "" "The following duplicate books were found and incoming book formats were " "processed and merged into your Calibre database according to your automerge " "settings:" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/actions/add.py:276 +#: /home/kovid/work/calibre/src/calibre/gui2/actions/add.py:349 msgid "Failed to read metadata" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/actions/add.py:277 +#: /home/kovid/work/calibre/src/calibre/gui2/actions/add.py:350 msgid "Failed to read metadata from the following" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/actions/add.py:298 -#: /home/kovid/work/calibre/src/calibre/gui2/actions/add.py:303 -#: /home/kovid/work/calibre/src/calibre/gui2/actions/add.py:322 +#: /home/kovid/work/calibre/src/calibre/gui2/actions/add.py:371 +#: /home/kovid/work/calibre/src/calibre/gui2/actions/add.py:376 +#: /home/kovid/work/calibre/src/calibre/gui2/actions/add.py:395 msgid "Add to library" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/actions/add.py:303 -#: /home/kovid/work/calibre/src/calibre/gui2/actions/delete.py:116 +#: /home/kovid/work/calibre/src/calibre/gui2/actions/add.py:376 +#: /home/kovid/work/calibre/src/calibre/gui2/actions/delete.py:127 +#: /home/kovid/work/calibre/src/calibre/gui2/actions/store.py:76 +#: /home/kovid/work/calibre/src/calibre/gui2/actions/store.py:95 +#: /home/kovid/work/calibre/src/calibre/gui2/actions/store.py:104 #: /home/kovid/work/calibre/src/calibre/gui2/actions/tweak_epub.py:28 -#: /home/kovid/work/calibre/src/calibre/gui2/actions/view.py:85 -#: /home/kovid/work/calibre/src/calibre/gui2/actions/view.py:131 +#: /home/kovid/work/calibre/src/calibre/gui2/actions/view.py:139 +#: /home/kovid/work/calibre/src/calibre/gui2/actions/view.py:185 msgid "No book selected" msgstr "Nav izvēlēta grāmata" -#: /home/kovid/work/calibre/src/calibre/gui2/actions/add.py:316 +#: /home/kovid/work/calibre/src/calibre/gui2/actions/add.py:389 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:322 +#: /home/kovid/work/calibre/src/calibre/gui2/actions/add.py:395 msgid "No book files found" msgstr "" @@ -3807,57 +3939,65 @@ msgid "Add books to your calibre library from the connected device" msgstr "" #: /home/kovid/work/calibre/src/calibre/gui2/actions/annotate.py:20 -#: /home/kovid/work/calibre/src/calibre/gui2/device.py:544 +#: /home/kovid/work/calibre/src/calibre/gui2/device.py:543 msgid "Fetch annotations (experimental)" msgstr "" #: /home/kovid/work/calibre/src/calibre/gui2/actions/annotate.py:56 -#: /home/kovid/work/calibre/src/calibre/gui2/actions/annotate.py:240 -msgid "Use library only" +msgid "Not supported" msgstr "" #: /home/kovid/work/calibre/src/calibre/gui2/actions/annotate.py:57 -#: /home/kovid/work/calibre/src/calibre/gui2/actions/annotate.py:241 +msgid "Fetching annotations is not supported for this device" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/actions/annotate.py:61 +#: /home/kovid/work/calibre/src/calibre/gui2/actions/annotate.py:245 +msgid "Use library only" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/actions/annotate.py:62 +#: /home/kovid/work/calibre/src/calibre/gui2/actions/annotate.py:246 msgid "User annotations generated from main library only" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/actions/annotate.py:64 -#: /home/kovid/work/calibre/src/calibre/gui2/actions/catalog.py:30 +#: /home/kovid/work/calibre/src/calibre/gui2/actions/annotate.py:69 +#: /home/kovid/work/calibre/src/calibre/gui2/actions/catalog.py:33 #: /home/kovid/work/calibre/src/calibre/gui2/actions/convert.py:87 #: /home/kovid/work/calibre/src/calibre/gui2/actions/copy_to_library.py:127 -#: /home/kovid/work/calibre/src/calibre/gui2/actions/edit_metadata.py:80 -#: /home/kovid/work/calibre/src/calibre/gui2/actions/edit_metadata.py:127 -#: /home/kovid/work/calibre/src/calibre/gui2/actions/edit_metadata.py:188 -#: /home/kovid/work/calibre/src/calibre/gui2/actions/edit_metadata.py:225 +#: /home/kovid/work/calibre/src/calibre/gui2/actions/edit_metadata.py:72 +#: /home/kovid/work/calibre/src/calibre/gui2/actions/edit_metadata.py:156 +#: /home/kovid/work/calibre/src/calibre/gui2/actions/edit_metadata.py:220 +#: /home/kovid/work/calibre/src/calibre/gui2/actions/edit_metadata.py:257 #: /home/kovid/work/calibre/src/calibre/gui2/actions/save_to_disk.py:92 msgid "No books selected" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/actions/annotate.py:65 +#: /home/kovid/work/calibre/src/calibre/gui2/actions/annotate.py:70 msgid "No books selected to fetch annotations from" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/actions/annotate.py:90 +#: /home/kovid/work/calibre/src/calibre/gui2/actions/annotate.py:95 msgid "Merging user annotations into database" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/actions/annotate.py:118 +#: /home/kovid/work/calibre/src/calibre/gui2/actions/annotate.py:123 msgid "%s
      Last Page Read: %d (%d%%)" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/actions/annotate.py:124 +#: /home/kovid/work/calibre/src/calibre/gui2/actions/annotate.py:129 msgid "%s
      Last Page Read: Location %d (%d%%)" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/actions/annotate.py:143 +#: /home/kovid/work/calibre/src/calibre/gui2/actions/annotate.py:148 msgid "Location %d • %s
      %s
      " msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/actions/annotate.py:152 +#: /home/kovid/work/calibre/src/calibre/gui2/actions/annotate.py:157 msgid "Page %d • %s
      " msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/actions/annotate.py:157 +#: /home/kovid/work/calibre/src/calibre/gui2/actions/annotate.py:162 msgid "Location %d • %s
      " msgstr "" @@ -3866,30 +4006,30 @@ msgstr "" msgid "Create a catalog of the books in your calibre library" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/actions/catalog.py:31 +#: /home/kovid/work/calibre/src/calibre/gui2/actions/catalog.py:34 msgid "No books selected for catalog generation" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/actions/catalog.py:54 +#: /home/kovid/work/calibre/src/calibre/gui2/actions/catalog.py:57 msgid "Generating %s catalog..." msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/actions/catalog.py:78 +#: /home/kovid/work/calibre/src/calibre/gui2/actions/catalog.py:81 msgid "Catalog generated." msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/actions/catalog.py:81 +#: /home/kovid/work/calibre/src/calibre/gui2/actions/catalog.py:84 msgid "Export Catalog Directory" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/actions/catalog.py:82 +#: /home/kovid/work/calibre/src/calibre/gui2/actions/catalog.py:85 msgid "Select destination for %s.%s" msgstr "" #: /home/kovid/work/calibre/src/calibre/gui2/actions/choose_library.py:81 -#: /home/kovid/work/calibre/src/calibre/gui2/preferences/toolbar.py:54 -#: /home/kovid/work/calibre/src/calibre/library/server/browse.py:167 -#: /home/kovid/work/calibre/src/calibre/library/server/opds.py:126 +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/toolbar.py:57 +#: /home/kovid/work/calibre/src/calibre/library/server/browse.py:170 +#: /home/kovid/work/calibre/src/calibre/library/server/opds.py:125 msgid "%d books" msgstr "" @@ -3897,179 +4037,183 @@ msgstr "" msgid "Choose calibre library to work with" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/actions/choose_library.py:91 +#: /home/kovid/work/calibre/src/calibre/gui2/actions/choose_library.py:94 msgid "Switch/create library..." msgstr "Pārslēgt/izveidot bibliotēku" -#: /home/kovid/work/calibre/src/calibre/gui2/actions/choose_library.py:102 -#: /home/kovid/work/calibre/src/calibre/gui2/layout.py:77 +#: /home/kovid/work/calibre/src/calibre/gui2/actions/choose_library.py:105 +#: /home/kovid/work/calibre/src/calibre/gui2/layout.py:87 msgid "Quick switch" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/actions/choose_library.py:104 -#: /home/kovid/work/calibre/src/calibre/gui2/layout.py:78 +#: /home/kovid/work/calibre/src/calibre/gui2/actions/choose_library.py:107 +#: /home/kovid/work/calibre/src/calibre/gui2/layout.py:88 msgid "Rename library" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/actions/choose_library.py:106 -#: /home/kovid/work/calibre/src/calibre/gui2/layout.py:79 +#: /home/kovid/work/calibre/src/calibre/gui2/actions/choose_library.py:109 +#: /home/kovid/work/calibre/src/calibre/gui2/layout.py:89 msgid "Delete library" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/actions/choose_library.py:109 +#: /home/kovid/work/calibre/src/calibre/gui2/actions/choose_library.py:112 msgid "Pick a random book" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/actions/choose_library.py:128 +#: /home/kovid/work/calibre/src/calibre/gui2/actions/choose_library.py:132 msgid "Library Maintenance" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/actions/choose_library.py:129 +#: /home/kovid/work/calibre/src/calibre/gui2/actions/choose_library.py:133 msgid "Library metadata backup status" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/actions/choose_library.py:133 +#: /home/kovid/work/calibre/src/calibre/gui2/actions/choose_library.py:137 msgid "Start backing up metadata of all books" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/actions/choose_library.py:137 +#: /home/kovid/work/calibre/src/calibre/gui2/actions/choose_library.py:141 msgid "Check library" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/actions/choose_library.py:141 +#: /home/kovid/work/calibre/src/calibre/gui2/actions/choose_library.py:145 msgid "Restore database" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/actions/choose_library.py:216 +#: /home/kovid/work/calibre/src/calibre/gui2/actions/choose_library.py:220 msgid "Rename" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/actions/choose_library.py:217 +#: /home/kovid/work/calibre/src/calibre/gui2/actions/choose_library.py:221 msgid "Choose a new name for the library %s. " msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/actions/choose_library.py:218 +#: /home/kovid/work/calibre/src/calibre/gui2/actions/choose_library.py:222 msgid "Note that the actual library folder will be renamed." msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/actions/choose_library.py:225 -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/user_profiles.py:191 +#: /home/kovid/work/calibre/src/calibre/gui2/actions/choose_library.py:229 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/user_profiles.py:199 +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/plugins.py:289 msgid "Already exists" msgstr "Jau eksistē" -#: /home/kovid/work/calibre/src/calibre/gui2/actions/choose_library.py:226 +#: /home/kovid/work/calibre/src/calibre/gui2/actions/choose_library.py:230 msgid "The folder %s already exists. Delete it first." msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/actions/choose_library.py:232 +#: /home/kovid/work/calibre/src/calibre/gui2/actions/choose_library.py:236 msgid "Rename failed" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/actions/choose_library.py:233 +#: /home/kovid/work/calibre/src/calibre/gui2/actions/choose_library.py:237 msgid "" "Failed to rename the library at %s. The most common cause for this is if one " "of the files in the library is open in another program." msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/actions/choose_library.py:244 +#: /home/kovid/work/calibre/src/calibre/gui2/actions/choose_library.py:248 +#: /home/kovid/work/calibre/src/calibre/gui2/actions/delete.py:30 #: /home/kovid/work/calibre/src/calibre/gui2/dialogs/confirm_delete_ui.py:53 #: /home/kovid/work/calibre/src/calibre/gui2/dialogs/restore_library.py:78 -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/user_profiles.py:360 -#: /home/kovid/work/calibre/src/calibre/gui2/jobs.py:424 -#: /home/kovid/work/calibre/src/calibre/gui2/jobs.py:430 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/user_profiles.py:368 +#: /home/kovid/work/calibre/src/calibre/gui2/jobs.py:457 +#: /home/kovid/work/calibre/src/calibre/gui2/jobs.py:463 #: /home/kovid/work/calibre/src/calibre/gui2/preferences/columns.py:102 -#: /home/kovid/work/calibre/src/calibre/gui2/preferences/plugins.py:273 +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/plugins.py:279 +#: /home/kovid/work/calibre/src/calibre/gui2/wizard/send_email.py:223 msgid "Are you sure?" msgstr "Vai esat pārliecināts?" -#: /home/kovid/work/calibre/src/calibre/gui2/actions/choose_library.py:245 -msgid "All files from %s will be permanently deleted. Are you sure?" +#: /home/kovid/work/calibre/src/calibre/gui2/actions/choose_library.py:249 +msgid "" +"All files (not just ebooks) from " +"

      %s

      will be permanently deleted. Are you sure?" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/actions/choose_library.py:265 +#: /home/kovid/work/calibre/src/calibre/gui2/actions/choose_library.py:270 msgid "none" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/actions/choose_library.py:266 +#: /home/kovid/work/calibre/src/calibre/gui2/actions/choose_library.py:271 msgid "Backup status" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/actions/choose_library.py:267 +#: /home/kovid/work/calibre/src/calibre/gui2/actions/choose_library.py:272 msgid "Book metadata files remaining to be written: %s" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/actions/choose_library.py:273 +#: /home/kovid/work/calibre/src/calibre/gui2/actions/choose_library.py:278 msgid "Backup metadata" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/actions/choose_library.py:274 +#: /home/kovid/work/calibre/src/calibre/gui2/actions/choose_library.py:279 msgid "" "Metadata will be backed up while calibre is running, at the rate of " "approximately 1 book every three seconds." msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/actions/choose_library.py:306 +#: /home/kovid/work/calibre/src/calibre/gui2/actions/choose_library.py:311 #: /home/kovid/work/calibre/src/calibre/gui2/dialogs/restore_library.py:106 #: /home/kovid/work/calibre/src/calibre/gui2/dialogs/restore_library.py:111 -#: /home/kovid/work/calibre/src/calibre/gui2/preferences/plugins.py:284 -#: /home/kovid/work/calibre/src/calibre/gui2/preferences/plugins.py:338 +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/plugins.py:295 +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/plugins.py:349 msgid "Success" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/actions/choose_library.py:307 +#: /home/kovid/work/calibre/src/calibre/gui2/actions/choose_library.py:312 msgid "" "Found no errors in your calibre library database. Do you want calibre to " "check if the files in your library match the information in the database?" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/actions/choose_library.py:312 +#: /home/kovid/work/calibre/src/calibre/gui2/actions/choose_library.py:317 #: /home/kovid/work/calibre/src/calibre/gui2/actions/copy_to_library.py:150 -#: /home/kovid/work/calibre/src/calibre/gui2/device.py:672 -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk.py:911 +#: /home/kovid/work/calibre/src/calibre/gui2/device.py:692 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk.py:974 #: /home/kovid/work/calibre/src/calibre/gui2/dialogs/restore_library.py:101 -#: /home/kovid/work/calibre/src/calibre/gui2/metadata/bulk_download.py:186 -#: /home/kovid/work/calibre/src/calibre/gui2/preferences/tweaks.py:276 -#: /home/kovid/work/calibre/src/calibre/gui2/preferences/tweaks.py:316 +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/tweaks.py:277 +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/tweaks.py:317 msgid "Failed" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/actions/choose_library.py:313 +#: /home/kovid/work/calibre/src/calibre/gui2/actions/choose_library.py:318 msgid "Database integrity check failed, click Show details for details." msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/actions/choose_library.py:318 +#: /home/kovid/work/calibre/src/calibre/gui2/actions/choose_library.py:323 msgid "No problems found" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/actions/choose_library.py:319 +#: /home/kovid/work/calibre/src/calibre/gui2/actions/choose_library.py:324 msgid "The files in your library match the information in the database." msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/actions/choose_library.py:328 +#: /home/kovid/work/calibre/src/calibre/gui2/actions/choose_library.py:333 msgid "No library found" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/actions/choose_library.py:329 +#: /home/kovid/work/calibre/src/calibre/gui2/actions/choose_library.py:334 msgid "" "No existing calibre library was found at %s. It will be removed from the " "list of known libraries." msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/actions/choose_library.py:394 -#: /home/kovid/work/calibre/src/calibre/gui2/actions/choose_library.py:399 +#: /home/kovid/work/calibre/src/calibre/gui2/actions/choose_library.py:400 +#: /home/kovid/work/calibre/src/calibre/gui2/actions/choose_library.py:405 #: /home/kovid/work/calibre/src/calibre/gui2/actions/copy_to_library.py:167 #: /home/kovid/work/calibre/src/calibre/gui2/actions/save_to_disk.py:101 -#: /home/kovid/work/calibre/src/calibre/gui2/library/views.py:782 +#: /home/kovid/work/calibre/src/calibre/gui2/library/views.py:854 msgid "Not allowed" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/actions/choose_library.py:395 +#: /home/kovid/work/calibre/src/calibre/gui2/actions/choose_library.py:401 msgid "" "You cannot change libraries while using the environment variable " "CALIBRE_OVERRIDE_DATABASE_PATH." msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/actions/choose_library.py:400 +#: /home/kovid/work/calibre/src/calibre/gui2/actions/choose_library.py:406 msgid "You cannot change libraries while jobs are running." msgstr "" @@ -4090,7 +4234,7 @@ msgid "Bulk convert" msgstr "Masveida pārveidošana" #: /home/kovid/work/calibre/src/calibre/gui2/actions/convert.py:86 -#: /home/kovid/work/calibre/src/calibre/gui2/ui.py:505 +#: /home/kovid/work/calibre/src/calibre/gui2/ui.py:560 msgid "Cannot convert" msgstr "Nav iespējams pārveidot" @@ -4098,7 +4242,7 @@ msgstr "Nav iespējams pārveidot" msgid "Starting conversion of %d book(s)" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/actions/convert.py:170 +#: /home/kovid/work/calibre/src/calibre/gui2/actions/convert.py:171 msgid "Empty output file, probably the conversion process crashed" msgstr "" @@ -4145,113 +4289,120 @@ msgid "" "CALIBRE_OVERRIDE_DATABASE_PATH." msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/actions/delete.py:32 +#: /home/kovid/work/calibre/src/calibre/gui2/actions/delete.py:31 +msgid "" +"You are trying to delete %d books. Sending so many files to the Recycle Bin " +"can be slow. Should calibre skip the Recycle Bin? If you click Yes " +"the files will be permanently deleted." +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/actions/delete.py:42 msgid "Deleting..." msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/actions/delete.py:54 +#: /home/kovid/work/calibre/src/calibre/gui2/actions/delete.py:65 msgid "Deleted" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/actions/delete.py:66 +#: /home/kovid/work/calibre/src/calibre/gui2/actions/delete.py:77 msgid "Failed to delete" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/actions/delete.py:67 +#: /home/kovid/work/calibre/src/calibre/gui2/actions/delete.py:78 msgid "" "Failed to delete some books, click the Show Details button for details." msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/actions/delete.py:73 +#: /home/kovid/work/calibre/src/calibre/gui2/actions/delete.py:84 msgid "Del" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/actions/delete.py:73 +#: /home/kovid/work/calibre/src/calibre/gui2/actions/delete.py:84 msgid "Remove books" msgstr "Dzēst grāmatas" -#: /home/kovid/work/calibre/src/calibre/gui2/actions/delete.py:79 +#: /home/kovid/work/calibre/src/calibre/gui2/actions/delete.py:90 msgid "Remove selected books" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/actions/delete.py:81 +#: /home/kovid/work/calibre/src/calibre/gui2/actions/delete.py:92 msgid "Remove files of a specific format from selected books.." msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/actions/delete.py:84 +#: /home/kovid/work/calibre/src/calibre/gui2/actions/delete.py:95 msgid "Remove all formats from selected books, except..." msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/actions/delete.py:87 +#: /home/kovid/work/calibre/src/calibre/gui2/actions/delete.py:98 msgid "Remove covers from selected books" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/actions/delete.py:90 +#: /home/kovid/work/calibre/src/calibre/gui2/actions/delete.py:101 msgid "Remove matching books from device" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/actions/delete.py:113 +#: /home/kovid/work/calibre/src/calibre/gui2/actions/delete.py:124 msgid "Cannot delete" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/actions/delete.py:126 +#: /home/kovid/work/calibre/src/calibre/gui2/actions/delete.py:137 msgid "Choose formats to be deleted" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/actions/delete.py:144 +#: /home/kovid/work/calibre/src/calibre/gui2/actions/delete.py:155 msgid "Choose formats not to be deleted" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/actions/delete.py:164 +#: /home/kovid/work/calibre/src/calibre/gui2/actions/delete.py:175 msgid "Cannot delete books" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/actions/delete.py:165 +#: /home/kovid/work/calibre/src/calibre/gui2/actions/delete.py:176 msgid "No device is connected" msgstr "Neviena ierīce nav savienota" -#: /home/kovid/work/calibre/src/calibre/gui2/actions/delete.py:175 +#: /home/kovid/work/calibre/src/calibre/gui2/actions/delete.py:186 msgid "Main memory" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/actions/delete.py:176 -#: /home/kovid/work/calibre/src/calibre/gui2/device.py:469 -#: /home/kovid/work/calibre/src/calibre/gui2/device.py:478 +#: /home/kovid/work/calibre/src/calibre/gui2/actions/delete.py:187 +#: /home/kovid/work/calibre/src/calibre/gui2/device.py:468 +#: /home/kovid/work/calibre/src/calibre/gui2/device.py:477 msgid "Storage Card A" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/actions/delete.py:177 -#: /home/kovid/work/calibre/src/calibre/gui2/device.py:471 -#: /home/kovid/work/calibre/src/calibre/gui2/device.py:480 +#: /home/kovid/work/calibre/src/calibre/gui2/actions/delete.py:188 +#: /home/kovid/work/calibre/src/calibre/gui2/device.py:470 +#: /home/kovid/work/calibre/src/calibre/gui2/device.py:479 msgid "Storage Card B" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/actions/delete.py:182 +#: /home/kovid/work/calibre/src/calibre/gui2/actions/delete.py:193 msgid "No books to delete" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/actions/delete.py:183 +#: /home/kovid/work/calibre/src/calibre/gui2/actions/delete.py:194 msgid "None of the selected books are on the device" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/actions/delete.py:200 -#: /home/kovid/work/calibre/src/calibre/gui2/actions/delete.py:290 +#: /home/kovid/work/calibre/src/calibre/gui2/actions/delete.py:211 +#: /home/kovid/work/calibre/src/calibre/gui2/actions/delete.py:302 msgid "Deleting books from device." msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/actions/delete.py:245 +#: /home/kovid/work/calibre/src/calibre/gui2/actions/delete.py:257 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:257 +#: /home/kovid/work/calibre/src/calibre/gui2/actions/delete.py:269 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:282 +#: /home/kovid/work/calibre/src/calibre/gui2/actions/delete.py:294 msgid "" "The selected books will be permanently deleted from your device. Are " "you sure?" @@ -4279,7 +4430,7 @@ msgid "Stop Content Server" msgstr "" #: /home/kovid/work/calibre/src/calibre/gui2/actions/device.py:77 -#: /home/kovid/work/calibre/src/calibre/gui2/actions/device.py:95 +#: /home/kovid/work/calibre/src/calibre/gui2/actions/device.py:96 msgid "Email to" msgstr "" @@ -4287,19 +4438,19 @@ msgstr "" msgid "Email to and delete from library" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/actions/device.py:89 +#: /home/kovid/work/calibre/src/calibre/gui2/actions/device.py:90 msgid "(delete from library)" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/actions/device.py:104 +#: /home/kovid/work/calibre/src/calibre/gui2/actions/device.py:105 msgid "Setup email based sharing of books" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/actions/device.py:122 +#: /home/kovid/work/calibre/src/calibre/gui2/actions/device.py:123 msgid "D" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/actions/device.py:122 +#: /home/kovid/work/calibre/src/calibre/gui2/actions/device.py:123 msgid "Send to device" msgstr "Sūtīt uz ierīci" @@ -4307,13 +4458,13 @@ msgstr "Sūtīt uz ierīci" msgid "Connect/share" msgstr "Savienoties/koplietot" -#: /home/kovid/work/calibre/src/calibre/gui2/actions/device.py:174 -#: /home/kovid/work/calibre/src/calibre/gui2/preferences/server.py:84 +#: /home/kovid/work/calibre/src/calibre/gui2/actions/device.py:178 +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/server.py:79 msgid "Stopping" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/actions/device.py:175 -#: /home/kovid/work/calibre/src/calibre/gui2/preferences/server.py:85 +#: /home/kovid/work/calibre/src/calibre/gui2/actions/device.py:179 +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/server.py:80 msgid "Stopping server, this could take upto a minute, please wait..." msgstr "" @@ -4353,72 +4504,95 @@ msgstr "Rediģēt metadatus masveidā" msgid "Download metadata and covers" msgstr "Lejupielādēt metadatus un vākus" -#: /home/kovid/work/calibre/src/calibre/gui2/actions/edit_metadata.py:40 -msgid "Download only metadata" -msgstr "Lejupielādēt tikai metadatus" - #: /home/kovid/work/calibre/src/calibre/gui2/actions/edit_metadata.py:42 -msgid "Download only covers" -msgstr "Lejupielādēt tikai vākus" - -#: /home/kovid/work/calibre/src/calibre/gui2/actions/edit_metadata.py:45 -msgid "Download only social metadata" -msgstr "" - -#: /home/kovid/work/calibre/src/calibre/gui2/actions/edit_metadata.py:51 msgid "Merge into first selected book - delete others" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/actions/edit_metadata.py:54 +#: /home/kovid/work/calibre/src/calibre/gui2/actions/edit_metadata.py:45 msgid "Merge into first selected book - keep others" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/actions/edit_metadata.py:58 +#: /home/kovid/work/calibre/src/calibre/gui2/actions/edit_metadata.py:49 msgid "Merge only formats into first selected book - delete others" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/actions/edit_metadata.py:79 +#: /home/kovid/work/calibre/src/calibre/gui2/actions/edit_metadata.py:71 msgid "Cannot download metadata" msgstr "Nav iespējams lejupielādēt metadatus" -#: /home/kovid/work/calibre/src/calibre/gui2/actions/edit_metadata.py:99 -msgid "social metadata" +#: /home/kovid/work/calibre/src/calibre/gui2/actions/edit_metadata.py:81 +msgid "Failed to download metadata" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/actions/edit_metadata.py:101 -msgid "covers" -msgstr "vāki" - -#: /home/kovid/work/calibre/src/calibre/gui2/actions/edit_metadata.py:101 -#: /home/kovid/work/calibre/src/calibre/gui2/metadata/bulk_download.py:227 -msgid "metadata" -msgstr "metadati" - -#: /home/kovid/work/calibre/src/calibre/gui2/actions/edit_metadata.py:102 -msgid "Downloading {0} for {1} book(s)" +#: /home/kovid/work/calibre/src/calibre/gui2/actions/edit_metadata.py:87 +#: /home/kovid/work/calibre/src/calibre/gui2/dnd.py:84 +#: /home/kovid/work/calibre/src/calibre/gui2/metadata/single_download.py:462 +#: /home/kovid/work/calibre/src/calibre/gui2/metadata/single_download.py:716 +msgid "Download failed" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/actions/edit_metadata.py:126 -#: /home/kovid/work/calibre/src/calibre/gui2/actions/edit_metadata.py:187 +#: /home/kovid/work/calibre/src/calibre/gui2/actions/edit_metadata.py:88 +msgid "Failed to download metadata or covers for any of the %d book(s)." +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/actions/edit_metadata.py:91 +msgid "Metadata download completed" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/actions/edit_metadata.py:93 +msgid "" +"Finished downloading metadata for %d book(s). Proceed with updating " +"the metadata in your library?" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/actions/edit_metadata.py:100 +msgid "" +"Could not download metadata and/or covers for %d of the books. Click \"Show " +"details\" to see which books." +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/actions/edit_metadata.py:107 +msgid "Download complete" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/actions/edit_metadata.py:107 +#: /home/kovid/work/calibre/src/calibre/gui2/metadata/single_download.py:777 +msgid "Download log" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/actions/edit_metadata.py:136 +msgid "Some books changed" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/actions/edit_metadata.py:137 +msgid "" +"The metadata for some books in your library has changed since you started " +"the download. If you proceed, some of those changes may be overwritten. " +"Click \"Show details\" to see the list of changed books. Do you want to " +"proceed?" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/actions/edit_metadata.py:155 +#: /home/kovid/work/calibre/src/calibre/gui2/actions/edit_metadata.py:219 msgid "Cannot edit metadata" msgstr "Nav iespējams rediģēt metadatus" -#: /home/kovid/work/calibre/src/calibre/gui2/actions/edit_metadata.py:224 -#: /home/kovid/work/calibre/src/calibre/gui2/actions/edit_metadata.py:227 +#: /home/kovid/work/calibre/src/calibre/gui2/actions/edit_metadata.py:256 +#: /home/kovid/work/calibre/src/calibre/gui2/actions/edit_metadata.py:259 msgid "Cannot merge books" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/actions/edit_metadata.py:228 +#: /home/kovid/work/calibre/src/calibre/gui2/actions/edit_metadata.py:260 msgid "At least two books must be selected for merging" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/actions/edit_metadata.py:231 +#: /home/kovid/work/calibre/src/calibre/gui2/actions/edit_metadata.py:263 msgid "" "You are about to merge more than 5 books. Are you sure you want to " "proceed?" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/actions/edit_metadata.py:239 +#: /home/kovid/work/calibre/src/calibre/gui2/actions/edit_metadata.py:271 msgid "" "Book formats and metadata from the selected books will be added to the " "first selected book (%s). ISBN will not be merged.

      The " @@ -4426,7 +4600,7 @@ msgid "" "changed.

      Please confirm you want to proceed." msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/actions/edit_metadata.py:251 +#: /home/kovid/work/calibre/src/calibre/gui2/actions/edit_metadata.py:283 msgid "" "Book formats from the selected books will be merged into the first " "selected book (%s). Metadata in the first selected book will not be " @@ -4438,7 +4612,7 @@ msgid "" "calibre library.

      Are you sure you want to proceed?" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/actions/edit_metadata.py:267 +#: /home/kovid/work/calibre/src/calibre/gui2/actions/edit_metadata.py:299 msgid "" "Book formats and metadata from the selected books will be merged into the " "first selected book (%s). ISBN will not be " @@ -4449,19 +4623,33 @@ msgid "" "Are you sure you want to proceed?" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/actions/fetch_news.py:17 +#: /home/kovid/work/calibre/src/calibre/gui2/actions/edit_metadata.py:455 +msgid "Applying changed metadata" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/actions/edit_metadata.py:520 +msgid "Some failures" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/actions/edit_metadata.py:521 +msgid "" +"Failed to apply updated metadata for some books in your library. Click " +"\"Show Details\" to see details." +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/actions/fetch_news.py:19 msgid "F" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/actions/fetch_news.py:17 +#: /home/kovid/work/calibre/src/calibre/gui2/actions/fetch_news.py:19 msgid "Fetch news" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/actions/fetch_news.py:52 +#: /home/kovid/work/calibre/src/calibre/gui2/actions/fetch_news.py:54 msgid "Fetching news from " msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/actions/fetch_news.py:66 +#: /home/kovid/work/calibre/src/calibre/gui2/actions/fetch_news.py:83 msgid " fetched." msgstr "" @@ -4487,7 +4675,7 @@ msgid "Move to next highlighted match" msgstr "" #: /home/kovid/work/calibre/src/calibre/gui2/actions/next_match.py:13 -#: /home/kovid/work/calibre/src/calibre/gui2/library/delegates.py:355 +#: /home/kovid/work/calibre/src/calibre/gui2/library/delegates.py:361 msgid "N" msgstr "" @@ -4526,19 +4714,23 @@ msgid "Ctrl+P" msgstr "" #: /home/kovid/work/calibre/src/calibre/gui2/actions/preferences.py:24 -#: /home/kovid/work/calibre/src/calibre/gui2/preferences/main.py:201 +msgid "Change calibre behavior" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/actions/preferences.py:25 +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/main.py:208 msgid "Run welcome wizard" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/actions/preferences.py:28 +#: /home/kovid/work/calibre/src/calibre/gui2/actions/preferences.py:29 msgid "Restart in debug mode" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/actions/preferences.py:40 +#: /home/kovid/work/calibre/src/calibre/gui2/actions/preferences.py:44 msgid "Cannot configure while there are running jobs." msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/actions/preferences.py:45 +#: /home/kovid/work/calibre/src/calibre/gui2/actions/preferences.py:49 msgid "Cannot configure before calibre is restarted." msgstr "" @@ -4611,7 +4803,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:696 +#: /home/kovid/work/calibre/src/calibre/library/server/browse.py:766 msgid "Show book details" msgstr "Parādīt grāmatas detaļas" @@ -4663,8 +4855,71 @@ msgstr "" msgid "Books with the same tags" msgstr "Grāmatas ar tādām pašām birkām" +#: /home/kovid/work/calibre/src/calibre/gui2/actions/store.py:20 +msgid "Get books" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/actions/store.py:29 +msgid "Search for ebooks" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/actions/store.py:30 +msgid "Search for this author" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/actions/store.py:31 +msgid "Search for this title" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/actions/store.py:32 +msgid "Search for this book" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/actions/store.py:34 +#: /home/kovid/work/calibre/src/calibre/gui2/store/search/search_ui.py:122 +#: /home/kovid/work/calibre/src/calibre/gui2/store/search_ui.py:110 +msgid "Stores" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/actions/store.py:76 +#: /home/kovid/work/calibre/src/calibre/gui2/actions/store.py:95 +#: /home/kovid/work/calibre/src/calibre/gui2/actions/store.py:104 +msgid "Cannot search" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/actions/store.py:116 +msgid "" +"Calibre helps you find the ebooks you want by searching the websites of " +"various commercial and public domain book sources for you." +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/actions/store.py:120 +msgid "" +"Using the integrated search you can easily find which store has the book you " +"are looking for, at the best price. You also get DRM status and other useful " +"information." +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/actions/store.py:124 +msgid "" +"All transactions (paid or otherwise) are handled between you and the book " +"seller. Calibre is not part of this process and any issues related to a " +"purchase should be directed to the website you are buying from. Be sure to " +"double check that any books you get will work with your e-book reader, " +"especially if the book you are buying has DRM." +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/actions/store.py:134 +msgid "Show this message again" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/actions/store.py:135 +msgid "About Get Books" +msgstr "" + #: /home/kovid/work/calibre/src/calibre/gui2/actions/tweak_epub.py:15 -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/tweak_epub_ui.py:54 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/tweak_epub_ui.py:60 msgid "Tweak ePub" msgstr "" @@ -4685,49 +4940,57 @@ msgstr "" msgid "No ePub available. First convert the book to ePub." msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/actions/view.py:24 +#: /home/kovid/work/calibre/src/calibre/gui2/actions/view.py:36 msgid "V" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/actions/view.py:24 -#: /home/kovid/work/calibre/src/calibre/gui2/actions/view.py:31 +#: /home/kovid/work/calibre/src/calibre/gui2/actions/view.py:36 msgid "View" msgstr "Skatīt" -#: /home/kovid/work/calibre/src/calibre/gui2/actions/view.py:32 +#: /home/kovid/work/calibre/src/calibre/gui2/actions/view.py:43 msgid "View specific format" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/actions/view.py:85 -#: /home/kovid/work/calibre/src/calibre/gui2/actions/view.py:170 +#: /home/kovid/work/calibre/src/calibre/gui2/actions/view.py:51 +msgid "Read a random book" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/actions/view.py:55 +msgid "Clear recently viewed list" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/actions/view.py:139 +#: /home/kovid/work/calibre/src/calibre/gui2/actions/view.py:219 +#: /home/kovid/work/calibre/src/calibre/gui2/actions/view.py:226 msgid "Cannot view" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/actions/view.py:98 -#: /home/kovid/work/calibre/src/calibre/gui2/actions/view.py:112 +#: /home/kovid/work/calibre/src/calibre/gui2/actions/view.py:152 +#: /home/kovid/work/calibre/src/calibre/gui2/actions/view.py:166 msgid "Format unavailable" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/actions/view.py:99 +#: /home/kovid/work/calibre/src/calibre/gui2/actions/view.py:153 msgid "Selected books have no formats" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/actions/view.py:101 +#: /home/kovid/work/calibre/src/calibre/gui2/actions/view.py:155 #: /home/kovid/work/calibre/src/calibre/gui2/convert/regex_builder.py:127 msgid "Choose the format to view" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/actions/view.py:113 +#: /home/kovid/work/calibre/src/calibre/gui2/actions/view.py:167 msgid "" "Not all the selected books were available in the %s format. You should " "convert them first." msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/actions/view.py:120 +#: /home/kovid/work/calibre/src/calibre/gui2/actions/view.py:174 msgid "Multiple Books Selected" msgstr "Izvēlētas vairākas grāmatas" -#: /home/kovid/work/calibre/src/calibre/gui2/actions/view.py:121 +#: /home/kovid/work/calibre/src/calibre/gui2/actions/view.py:175 msgid "" "You are attempting to open %d books. Opening too many books at once can be " "slow and have a negative effect on the responsiveness of your computer. Once " @@ -4735,11 +4998,15 @@ msgid "" "continue?" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/actions/view.py:130 +#: /home/kovid/work/calibre/src/calibre/gui2/actions/view.py:184 msgid "Cannot open folder" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/actions/view.py:171 +#: /home/kovid/work/calibre/src/calibre/gui2/actions/view.py:220 +msgid "This book no longer exists in your library" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/actions/view.py:227 msgid "%s has no available formats." msgstr "" @@ -4764,7 +5031,7 @@ msgid "The specified directory could not be processed." msgstr "" #: /home/kovid/work/calibre/src/calibre/gui2/add.py:274 -#: /home/kovid/work/calibre/src/calibre/gui2/device.py:821 +#: /home/kovid/work/calibre/src/calibre/gui2/device.py:845 msgid "No books" msgstr "" @@ -4808,32 +5075,32 @@ msgstr "Saglabā..." msgid "Saved" msgstr "Saglabāts" -#: /home/kovid/work/calibre/src/calibre/gui2/add_wizard/__init__.py:57 +#: /home/kovid/work/calibre/src/calibre/gui2/add_wizard/__init__.py:56 msgid "Searching for sub-folders" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/add_wizard/__init__.py:62 +#: /home/kovid/work/calibre/src/calibre/gui2/add_wizard/__init__.py:61 msgid "Searching for books" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/add_wizard/__init__.py:74 +#: /home/kovid/work/calibre/src/calibre/gui2/add_wizard/__init__.py:73 msgid "Looking for duplicates based on file hash" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/add_wizard/__init__.py:109 +#: /home/kovid/work/calibre/src/calibre/gui2/add_wizard/__init__.py:108 #: /home/kovid/work/calibre/src/calibre/gui2/add_wizard/welcome_ui.py:70 msgid "Choose root folder" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/add_wizard/__init__.py:137 +#: /home/kovid/work/calibre/src/calibre/gui2/add_wizard/__init__.py:135 msgid "Invalid root folder" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/add_wizard/__init__.py:138 +#: /home/kovid/work/calibre/src/calibre/gui2/add_wizard/__init__.py:136 msgid "is not a valid root folder" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/add_wizard/__init__.py:148 +#: /home/kovid/work/calibre/src/calibre/gui2/add_wizard/__init__.py:146 msgid "Add books to calibre" msgstr "" @@ -4889,21 +5156,15 @@ msgstr "" #: /home/kovid/work/calibre/src/calibre/gui2/convert/debug_ui.py:58 #: /home/kovid/work/calibre/src/calibre/gui2/convert/look_and_feel_ui.py:143 #: /home/kovid/work/calibre/src/calibre/gui2/convert/metadata_ui.py:162 -#: /home/kovid/work/calibre/src/calibre/gui2/convert/xexp_edit_ui.py:56 +#: /home/kovid/work/calibre/src/calibre/gui2/convert/xexp_edit_ui.py:57 #: /home/kovid/work/calibre/src/calibre/gui2/device_drivers/configwidget_ui.py:79 #: /home/kovid/work/calibre/src/calibre/gui2/device_drivers/configwidget_ui.py:80 #: /home/kovid/work/calibre/src/calibre/gui2/dialogs/choose_library_ui.py:86 -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:534 -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:539 -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single_ui.py:412 -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single_ui.py:414 -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single_ui.py:417 -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single_ui.py:437 -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single_ui.py:458 -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single_ui.py:460 -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single_ui.py:462 -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/saved_search_editor_ui.py:92 -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/saved_search_editor_ui.py:95 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:560 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:565 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/saved_search_editor_ui.py:98 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/saved_search_editor_ui.py:101 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/saved_search_editor_ui.py:103 #: /home/kovid/work/calibre/src/calibre/gui2/dialogs/tag_categories_ui.py:170 #: /home/kovid/work/calibre/src/calibre/gui2/dialogs/tag_categories_ui.py:173 #: /home/kovid/work/calibre/src/calibre/gui2/dialogs/tag_categories_ui.py:175 @@ -4915,16 +5176,16 @@ 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:80 #: /home/kovid/work/calibre/src/calibre/gui2/dialogs/tag_list_editor_ui.py:82 -#: /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:274 -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/user_profiles_ui.py:275 -#: /home/kovid/work/calibre/src/calibre/gui2/preferences/behavior_ui.py:148 -#: /home/kovid/work/calibre/src/calibre/gui2/preferences/behavior_ui.py:149 -#: /home/kovid/work/calibre/src/calibre/gui2/preferences/columns_ui.py:83 -#: /home/kovid/work/calibre/src/calibre/gui2/preferences/columns_ui.py:85 -#: /home/kovid/work/calibre/src/calibre/gui2/preferences/columns_ui.py:87 +#: /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/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 -#: /home/kovid/work/calibre/src/calibre/gui2/preferences/columns_ui.py:90 +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/columns_ui.py:91 +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/columns_ui.py:93 +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/columns_ui.py:95 +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/columns_ui.py:97 #: /home/kovid/work/calibre/src/calibre/gui2/preferences/custom_columns_ui.py:83 #: /home/kovid/work/calibre/src/calibre/gui2/preferences/custom_columns_ui.py:85 #: /home/kovid/work/calibre/src/calibre/gui2/preferences/custom_columns_ui.py:87 @@ -4937,6 +5198,8 @@ msgstr "" #: /home/kovid/work/calibre/src/calibre/gui2/preferences/toolbar_ui.py:110 #: /home/kovid/work/calibre/src/calibre/gui2/shortcuts_ui.py:80 #: /home/kovid/work/calibre/src/calibre/gui2/shortcuts_ui.py:85 +#: /home/kovid/work/calibre/src/calibre/gui2/store/mobileread/store_dialog_ui.py:75 +#: /home/kovid/work/calibre/src/calibre/gui2/store/search/search_ui.py:120 #: /home/kovid/work/calibre/src/calibre/gui2/viewer/main_ui.py:191 msgid "..." msgstr "" @@ -4956,60 +5219,52 @@ msgid "" "&Multiple books per folder, assumes every ebook file is a different book" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/book_details.py:26 -#: /home/kovid/work/calibre/src/calibre/gui2/book_details.py:53 -#: /home/kovid/work/calibre/src/calibre/gui2/book_details.py:62 -#: /home/kovid/work/calibre/src/calibre/gui2/book_details.py:434 -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/book_info.py:130 -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/book_info.py:131 -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/book_info.py:132 -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/book_info.py:145 -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/delete_matching_from_device.py:76 -#: /home/kovid/work/calibre/src/calibre/gui2/library/models.py:375 -#: /home/kovid/work/calibre/src/calibre/gui2/library/models.py:1170 -msgid "Path" -msgstr "Atrodas" - -#: /home/kovid/work/calibre/src/calibre/gui2/book_details.py:27 -#: /home/kovid/work/calibre/src/calibre/gui2/book_details.py:56 -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/book_info.py:133 -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/book_info.py:134 -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/book_info.py:135 -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/book_info.py:138 -#: /home/kovid/work/calibre/src/calibre/gui2/library/models.py:374 -#: /home/kovid/work/calibre/src/calibre/gui2/preferences/emailp.py:24 -#: /home/kovid/work/calibre/src/calibre/library/field_metadata.py:118 -msgid "Formats" -msgstr "Formāti" - -#: /home/kovid/work/calibre/src/calibre/gui2/book_details.py:28 -#: /home/kovid/work/calibre/src/calibre/gui2/library/models.py:981 -#: /home/kovid/work/calibre/src/calibre/gui2/library/models.py:1173 -msgid "Collections" +#: /home/kovid/work/calibre/src/calibre/gui2/bars.py:190 +msgid "Donate" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/book_details.py:55 -#: /home/kovid/work/calibre/src/calibre/gui2/book_details.py:64 +#: /home/kovid/work/calibre/src/calibre/gui2/book_details.py:108 msgid "Click to open" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/book_details.py:56 -#: /home/kovid/work/calibre/src/calibre/gui2/library/models.py:367 -#: /home/kovid/work/calibre/src/calibre/gui2/library/models.py:373 -#: /home/kovid/work/calibre/src/calibre/gui2/library/models.py:379 -#: /home/kovid/work/calibre/src/calibre/gui2/library/models.py:1179 -#: /home/kovid/work/calibre/src/calibre/gui2/library/models.py:1183 -#: /home/kovid/work/calibre/src/calibre/gui2/shortcuts.py:48 -#: /home/kovid/work/calibre/src/calibre/gui2/shortcuts_ui.py:78 -#: /home/kovid/work/calibre/src/calibre/gui2/shortcuts_ui.py:83 -#: /home/kovid/work/calibre/src/calibre/gui2/widgets.py:316 -msgid "None" -msgstr "Nav" +#: /home/kovid/work/calibre/src/calibre/gui2/book_details.py:123 +msgid "Ids" +msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/book_details.py:433 +#: /home/kovid/work/calibre/src/calibre/gui2/book_details.py:133 +msgid "Book %s of %s" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/book_details.py:144 +#: /home/kovid/work/calibre/src/calibre/gui2/library/models.py:981 +msgid "Collections" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/book_details.py:246 +#: /home/kovid/work/calibre/src/calibre/gui2/widgets.py:243 +msgid "Paste Cover" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/book_details.py:247 +#: /home/kovid/work/calibre/src/calibre/gui2/widgets.py:244 +msgid "Copy Cover" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/book_details.py:513 msgid "Double-click to open Book Details window" msgstr "" +#: /home/kovid/work/calibre/src/calibre/gui2/book_details.py:514 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/delete_matching_from_device.py:76 +#: /home/kovid/work/calibre/src/calibre/library/field_metadata.py:261 +msgid "Path" +msgstr "Atrodas" + +#: /home/kovid/work/calibre/src/calibre/gui2/book_details.py:515 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/book_info.py:109 +msgid "Cover size: %dx%d" +msgstr "" + #: /home/kovid/work/calibre/src/calibre/gui2/catalog/catalog_bibtex.py:16 msgid "BibTeX Options" msgstr "" @@ -5021,6 +5276,7 @@ msgstr "" #: /home/kovid/work/calibre/src/calibre/gui2/convert/epub_output.py:16 #: /home/kovid/work/calibre/src/calibre/gui2/convert/fb2_input.py:13 #: /home/kovid/work/calibre/src/calibre/gui2/convert/fb2_output.py:15 +#: /home/kovid/work/calibre/src/calibre/gui2/convert/htmlz_output.py:15 #: /home/kovid/work/calibre/src/calibre/gui2/convert/lrf_output.py:20 #: /home/kovid/work/calibre/src/calibre/gui2/convert/mobi_output.py:20 #: /home/kovid/work/calibre/src/calibre/gui2/convert/pdb_output.py:15 @@ -5040,6 +5296,7 @@ msgstr "" #: /home/kovid/work/calibre/src/calibre/gui2/catalog/catalog_epub_mobi.py:19 #: /home/kovid/work/calibre/src/calibre/gui2/convert/epub_output.py:16 #: /home/kovid/work/calibre/src/calibre/gui2/convert/fb2_output.py:15 +#: /home/kovid/work/calibre/src/calibre/gui2/convert/htmlz_output.py:15 #: /home/kovid/work/calibre/src/calibre/gui2/convert/lrf_output.py:20 #: /home/kovid/work/calibre/src/calibre/gui2/convert/mobi_output.py:20 #: /home/kovid/work/calibre/src/calibre/gui2/convert/pdb_output.py:15 @@ -5052,16 +5309,17 @@ msgstr "" msgid "output" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/catalog/catalog_bibtex_ui.py:89 +#: /home/kovid/work/calibre/src/calibre/gui2/catalog/catalog_bibtex_ui.py:77 #: /home/kovid/work/calibre/src/calibre/gui2/catalog/catalog_csv_xml_ui.py:42 #: /home/kovid/work/calibre/src/calibre/gui2/catalog/catalog_epub_mobi_ui.py:295 #: /home/kovid/work/calibre/src/calibre/gui2/catalog/catalog_tab_template_ui.py:32 -#: /home/kovid/work/calibre/src/calibre/gui2/convert/comic_input_ui.py:100 +#: /home/kovid/work/calibre/src/calibre/gui2/convert/comic_input_ui.py:103 #: /home/kovid/work/calibre/src/calibre/gui2/convert/debug_ui.py:54 #: /home/kovid/work/calibre/src/calibre/gui2/convert/epub_output_ui.py:56 #: /home/kovid/work/calibre/src/calibre/gui2/convert/fb2_input_ui.py:33 -#: /home/kovid/work/calibre/src/calibre/gui2/convert/fb2_output_ui.py:38 +#: /home/kovid/work/calibre/src/calibre/gui2/convert/fb2_output_ui.py:44 #: /home/kovid/work/calibre/src/calibre/gui2/convert/heuristics_ui.py:112 +#: /home/kovid/work/calibre/src/calibre/gui2/convert/htmlz_output_ui.py:44 #: /home/kovid/work/calibre/src/calibre/gui2/convert/look_and_feel_ui.py:137 #: /home/kovid/work/calibre/src/calibre/gui2/convert/lrf_output_ui.py:120 #: /home/kovid/work/calibre/src/calibre/gui2/convert/metadata_ui.py:158 @@ -5075,23 +5333,24 @@ msgstr "" #: /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:147 #: /home/kovid/work/calibre/src/calibre/gui2/convert/snb_output_ui.py:42 -#: /home/kovid/work/calibre/src/calibre/gui2/convert/structure_detection_ui.py:56 +#: /home/kovid/work/calibre/src/calibre/gui2/convert/structure_detection_ui.py:59 #: /home/kovid/work/calibre/src/calibre/gui2/convert/toc_ui.py:67 #: /home/kovid/work/calibre/src/calibre/gui2/convert/txt_input_ui.py:91 -#: /home/kovid/work/calibre/src/calibre/gui2/convert/txt_output_ui.py:84 -#: /home/kovid/work/calibre/src/calibre/gui2/convert/xexp_edit_ui.py:53 +#: /home/kovid/work/calibre/src/calibre/gui2/convert/txt_output_ui.py:87 +#: /home/kovid/work/calibre/src/calibre/gui2/convert/xexp_edit_ui.py:54 #: /home/kovid/work/calibre/src/calibre/gui2/convert/xpath_wizard_ui.py:72 #: /home/kovid/work/calibre/src/calibre/gui2/device_drivers/configwidget_ui.py:77 #: /home/kovid/work/calibre/src/calibre/gui2/dialogs/search_item_ui.py:40 -#: /home/kovid/work/calibre/src/calibre/gui2/filename_pattern_ui.py:114 +#: /home/kovid/work/calibre/src/calibre/gui2/filename_pattern_ui.py:128 #: /home/kovid/work/calibre/src/calibre/gui2/preferences/adding_ui.py:64 -#: /home/kovid/work/calibre/src/calibre/gui2/preferences/behavior_ui.py:130 -#: /home/kovid/work/calibre/src/calibre/gui2/preferences/columns_ui.py:81 +#: /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/custom_columns_ui.py:81 #: /home/kovid/work/calibre/src/calibre/gui2/preferences/email_ui.py:65 -#: /home/kovid/work/calibre/src/calibre/gui2/preferences/look_feel_ui.py:135 -#: /home/kovid/work/calibre/src/calibre/gui2/preferences/misc_ui.py:60 +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/look_feel_ui.py:197 +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/metadata_sources_ui.py:109 +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/misc_ui.py:61 #: /home/kovid/work/calibre/src/calibre/gui2/preferences/plugboard_ui.py:113 #: /home/kovid/work/calibre/src/calibre/gui2/preferences/plugins_ui.py:86 #: /home/kovid/work/calibre/src/calibre/gui2/preferences/save_template_ui.py:46 @@ -5102,72 +5361,41 @@ msgstr "" #: /home/kovid/work/calibre/src/calibre/gui2/preferences/template_functions_ui.py:95 #: /home/kovid/work/calibre/src/calibre/gui2/preferences/toolbar_ui.py:98 #: /home/kovid/work/calibre/src/calibre/gui2/preferences/tweaks_ui.py:87 +#: /home/kovid/work/calibre/src/calibre/gui2/store/basic_config_widget_ui.py:37 #: /home/kovid/work/calibre/src/calibre/gui2/wizard/send_email_ui.py:123 msgid "Form" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/catalog/catalog_bibtex_ui.py:90 +#: /home/kovid/work/calibre/src/calibre/gui2/catalog/catalog_bibtex_ui.py:78 msgid "Bib file encoding:" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/catalog/catalog_bibtex_ui.py:91 +#: /home/kovid/work/calibre/src/calibre/gui2/catalog/catalog_bibtex_ui.py:79 #: /home/kovid/work/calibre/src/calibre/gui2/catalog/catalog_csv_xml_ui.py:43 msgid "Fields to include in output:" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/catalog/catalog_bibtex_ui.py:92 -msgid "ascii/LaTeX" -msgstr "" - -#: /home/kovid/work/calibre/src/calibre/gui2/catalog/catalog_bibtex_ui.py:93 +#: /home/kovid/work/calibre/src/calibre/gui2/catalog/catalog_bibtex_ui.py:80 msgid "Encoding configuration (change if you have errors) :" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/catalog/catalog_bibtex_ui.py:94 -msgid "strict" -msgstr "" - -#: /home/kovid/work/calibre/src/calibre/gui2/catalog/catalog_bibtex_ui.py:95 -msgid "replace" -msgstr "" - -#: /home/kovid/work/calibre/src/calibre/gui2/catalog/catalog_bibtex_ui.py:96 -msgid "ignore" -msgstr "" - -#: /home/kovid/work/calibre/src/calibre/gui2/catalog/catalog_bibtex_ui.py:97 -msgid "backslashreplace" -msgstr "" - -#: /home/kovid/work/calibre/src/calibre/gui2/catalog/catalog_bibtex_ui.py:98 +#: /home/kovid/work/calibre/src/calibre/gui2/catalog/catalog_bibtex_ui.py:81 msgid "BibTeX entry type:" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/catalog/catalog_bibtex_ui.py:99 -msgid "mixed" -msgstr "" - -#: /home/kovid/work/calibre/src/calibre/gui2/catalog/catalog_bibtex_ui.py:100 -msgid "misc" -msgstr "" - -#: /home/kovid/work/calibre/src/calibre/gui2/catalog/catalog_bibtex_ui.py:101 -msgid "book" -msgstr "" - -#: /home/kovid/work/calibre/src/calibre/gui2/catalog/catalog_bibtex_ui.py:102 +#: /home/kovid/work/calibre/src/calibre/gui2/catalog/catalog_bibtex_ui.py:82 msgid "Create a citation tag?" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/catalog/catalog_bibtex_ui.py:103 +#: /home/kovid/work/calibre/src/calibre/gui2/catalog/catalog_bibtex_ui.py:83 msgid "Add files path with formats?" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/catalog/catalog_bibtex_ui.py:104 +#: /home/kovid/work/calibre/src/calibre/gui2/catalog/catalog_bibtex_ui.py:84 msgid "Expression to form the BibTeX citation tag:" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/catalog/catalog_bibtex_ui.py:105 +#: /home/kovid/work/calibre/src/calibre/gui2/catalog/catalog_bibtex_ui.py:85 msgid "" "Some explanation about this template:\n" " -The fields availables are 'author_sort', 'authors', 'id',\n" @@ -5408,10 +5636,12 @@ msgid "Remove formatting" msgstr "" #: /home/kovid/work/calibre/src/calibre/gui2/comments_editor.py:96 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/edit_authors_dialog.py:134 msgid "Copy" msgstr "Kopēt" #: /home/kovid/work/calibre/src/calibre/gui2/comments_editor.py:97 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/edit_authors_dialog.py:136 msgid "Paste" msgstr "" @@ -5448,8 +5678,9 @@ msgid "Style the selected text block" msgstr "" #: /home/kovid/work/calibre/src/calibre/gui2/comments_editor.py:125 -#: /home/kovid/work/calibre/src/calibre/gui2/preferences/behavior.py:32 -#: /home/kovid/work/calibre/src/calibre/gui2/preferences/behavior_ui.py:139 +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/behavior.py:34 +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/behavior.py:36 +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/behavior_ui.py:158 msgid "Normal" msgstr "Standarta" @@ -5500,11 +5731,11 @@ msgstr "" msgid "Enter URL" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/comments_editor.py:522 +#: /home/kovid/work/calibre/src/calibre/gui2/comments_editor.py:523 msgid "Normal view" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/comments_editor.py:523 +#: /home/kovid/work/calibre/src/calibre/gui2/comments_editor.py:524 msgid "HTML Source" msgstr "" @@ -5535,73 +5766,77 @@ msgstr "" msgid "input" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/convert/comic_input_ui.py:101 +#: /home/kovid/work/calibre/src/calibre/gui2/convert/comic_input_ui.py:104 #: /home/kovid/work/calibre/src/calibre/gui2/dialogs/comicconf_ui.py:99 msgid "&Number of Colors:" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/convert/comic_input_ui.py:102 +#: /home/kovid/work/calibre/src/calibre/gui2/convert/comic_input_ui.py:105 #: /home/kovid/work/calibre/src/calibre/gui2/dialogs/comicconf_ui.py:101 msgid "Disable &normalize" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/convert/comic_input_ui.py:103 +#: /home/kovid/work/calibre/src/calibre/gui2/convert/comic_input_ui.py:106 #: /home/kovid/work/calibre/src/calibre/gui2/dialogs/comicconf_ui.py:102 msgid "Keep &aspect ratio" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/convert/comic_input_ui.py:104 +#: /home/kovid/work/calibre/src/calibre/gui2/convert/comic_input_ui.py:107 #: /home/kovid/work/calibre/src/calibre/gui2/dialogs/comicconf_ui.py:103 msgid "Disable &Sharpening" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/convert/comic_input_ui.py:105 +#: /home/kovid/work/calibre/src/calibre/gui2/convert/comic_input_ui.py:108 #: /home/kovid/work/calibre/src/calibre/gui2/dialogs/comicconf_ui.py:109 msgid "Disable &Trimming" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/convert/comic_input_ui.py:106 +#: /home/kovid/work/calibre/src/calibre/gui2/convert/comic_input_ui.py:109 #: /home/kovid/work/calibre/src/calibre/gui2/dialogs/comicconf_ui.py:108 msgid "&Wide" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/convert/comic_input_ui.py:107 +#: /home/kovid/work/calibre/src/calibre/gui2/convert/comic_input_ui.py:110 #: /home/kovid/work/calibre/src/calibre/gui2/dialogs/comicconf_ui.py:104 msgid "&Landscape" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/convert/comic_input_ui.py:108 +#: /home/kovid/work/calibre/src/calibre/gui2/convert/comic_input_ui.py:111 #: /home/kovid/work/calibre/src/calibre/gui2/dialogs/comicconf_ui.py:106 msgid "&Right to left" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/convert/comic_input_ui.py:109 +#: /home/kovid/work/calibre/src/calibre/gui2/convert/comic_input_ui.py:112 #: /home/kovid/work/calibre/src/calibre/gui2/dialogs/comicconf_ui.py:105 msgid "Don't so&rt" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/convert/comic_input_ui.py:110 +#: /home/kovid/work/calibre/src/calibre/gui2/convert/comic_input_ui.py:113 #: /home/kovid/work/calibre/src/calibre/gui2/dialogs/comicconf_ui.py:107 msgid "De&speckle" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/convert/comic_input_ui.py:111 +#: /home/kovid/work/calibre/src/calibre/gui2/convert/comic_input_ui.py:114 msgid "&Disable comic processing" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/convert/comic_input_ui.py:112 +#: /home/kovid/work/calibre/src/calibre/gui2/convert/comic_input_ui.py:115 #: /home/kovid/work/calibre/src/calibre/gui2/convert/single_ui.py:120 msgid "&Output format:" msgstr "&Rezultāta formāts:" -#: /home/kovid/work/calibre/src/calibre/gui2/convert/comic_input_ui.py:113 +#: /home/kovid/work/calibre/src/calibre/gui2/convert/comic_input_ui.py:116 msgid "Disable conversion of images to &black and white" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/convert/comic_input_ui.py:114 +#: /home/kovid/work/calibre/src/calibre/gui2/convert/comic_input_ui.py:117 msgid "Override image &size:" msgstr "" +#: /home/kovid/work/calibre/src/calibre/gui2/convert/comic_input_ui.py:118 +msgid "Don't add links to &pages to the Table of Contents for CBC files" +msgstr "" + #: /home/kovid/work/calibre/src/calibre/gui2/convert/debug.py:19 msgid "Debug" msgstr "Atkļūdošana" @@ -5682,10 +5917,14 @@ msgstr "" msgid "FB2 Output" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/convert/fb2_output_ui.py:39 +#: /home/kovid/work/calibre/src/calibre/gui2/convert/fb2_output_ui.py:45 msgid "Sectionize:" msgstr "" +#: /home/kovid/work/calibre/src/calibre/gui2/convert/fb2_output_ui.py:46 +msgid "Genre" +msgstr "" + #: /home/kovid/work/calibre/src/calibre/gui2/convert/font_key_ui.py:104 msgid "Font rescaling wizard" msgstr "" @@ -5825,6 +6064,18 @@ msgstr "" msgid "Replace entity indents with CSS indents" msgstr "" +#: /home/kovid/work/calibre/src/calibre/gui2/convert/htmlz_output.py:14 +msgid "HTMLZ Output" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/convert/htmlz_output_ui.py:45 +msgid "How to handle CSS" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/convert/htmlz_output_ui.py:46 +msgid "How to handle class based CSS" +msgstr "" + #: /home/kovid/work/calibre/src/calibre/gui2/convert/look_and_feel.py:16 msgid "Look & Feel" msgstr "Izskats un sajūtas" @@ -5973,7 +6224,7 @@ msgid "&Monospaced font family:" msgstr "" #: /home/kovid/work/calibre/src/calibre/gui2/convert/metadata.py:47 -#: /home/kovid/work/calibre/src/calibre/gui2/viewer/main.py:115 +#: /home/kovid/work/calibre/src/calibre/gui2/viewer/main.py:117 #: /home/kovid/work/calibre/src/calibre/gui2/viewer/main_ui.py:200 msgid "Metadata" msgstr "Metadati" @@ -5985,49 +6236,41 @@ msgid "" msgstr "" #: /home/kovid/work/calibre/src/calibre/gui2/convert/metadata.py:180 -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single.py:171 -#: /home/kovid/work/calibre/src/calibre/gui2/metadata/basic_widgets.py:643 +#: /home/kovid/work/calibre/src/calibre/gui2/metadata/basic_widgets.py:726 msgid "Choose cover for " msgstr "Izvēlēties vāku " #: /home/kovid/work/calibre/src/calibre/gui2/convert/metadata.py:187 -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single.py:178 -#: /home/kovid/work/calibre/src/calibre/gui2/metadata/basic_widgets.py:651 +#: /home/kovid/work/calibre/src/calibre/gui2/metadata/basic_widgets.py:734 msgid "Cannot read" msgstr "" #: /home/kovid/work/calibre/src/calibre/gui2/convert/metadata.py:188 -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single.py:179 -#: /home/kovid/work/calibre/src/calibre/gui2/metadata/basic_widgets.py:652 +#: /home/kovid/work/calibre/src/calibre/gui2/metadata/basic_widgets.py:735 msgid "You do not have permission to read the file: " msgstr "" #: /home/kovid/work/calibre/src/calibre/gui2/convert/metadata.py:196 #: /home/kovid/work/calibre/src/calibre/gui2/convert/metadata.py:203 -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single.py:187 -#: /home/kovid/work/calibre/src/calibre/gui2/metadata/basic_widgets.py:660 +#: /home/kovid/work/calibre/src/calibre/gui2/metadata/basic_widgets.py:743 msgid "Error reading file" msgstr "Kļūda nolasot failu" #: /home/kovid/work/calibre/src/calibre/gui2/convert/metadata.py:197 -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single.py:188 -#: /home/kovid/work/calibre/src/calibre/gui2/metadata/basic_widgets.py:661 +#: /home/kovid/work/calibre/src/calibre/gui2/metadata/basic_widgets.py:744 msgid "

      There was an error reading from file:
      " msgstr "" #: /home/kovid/work/calibre/src/calibre/gui2/convert/metadata.py:204 -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single.py:196 -#: /home/kovid/work/calibre/src/calibre/gui2/metadata/basic_widgets.py:671 +#: /home/kovid/work/calibre/src/calibre/gui2/metadata/basic_widgets.py:754 msgid " is not a valid picture" msgstr "" #: /home/kovid/work/calibre/src/calibre/gui2/convert/metadata_ui.py:159 -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single_ui.py:446 msgid "Book Cover" msgstr "Grāmatas vāks" #: /home/kovid/work/calibre/src/calibre/gui2/convert/metadata_ui.py:160 -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single_ui.py:447 msgid "Change &cover image:" msgstr "Mainīt grāmatas &vāku:" @@ -6040,19 +6283,16 @@ msgid "Use cover from &source file" msgstr "" #: /home/kovid/work/calibre/src/calibre/gui2/convert/metadata_ui.py:164 -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single_ui.py:408 msgid "&Title: " msgstr "&Nosaukums " #: /home/kovid/work/calibre/src/calibre/gui2/convert/metadata_ui.py:165 -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single_ui.py:409 -#: /home/kovid/work/calibre/src/calibre/gui2/metadata/basic_widgets.py:60 +#: /home/kovid/work/calibre/src/calibre/gui2/metadata/basic_widgets.py:72 msgid "Change the title of this book" msgstr "Maina šīs grāmatas nosaukumu" #: /home/kovid/work/calibre/src/calibre/gui2/convert/metadata_ui.py:166 -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:499 -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single_ui.py:420 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:525 msgid "&Author(s): " msgstr "&Autors(i): " @@ -6068,44 +6308,38 @@ msgstr "" "Mainīt šīs grāmatas autoru(s). Ja autori ir vairāki, tie jāatdala ar komatu" #: /home/kovid/work/calibre/src/calibre/gui2/convert/metadata_ui.py:169 -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:509 -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single_ui.py:428 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:535 msgid "&Publisher: " msgstr "&Izdevniecība: " #: /home/kovid/work/calibre/src/calibre/gui2/convert/metadata_ui.py:170 -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single_ui.py:429 msgid "Ta&gs: " msgstr "&Birkas: " #: /home/kovid/work/calibre/src/calibre/gui2/convert/metadata_ui.py:171 -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:511 -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single_ui.py:430 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:537 +#: /home/kovid/work/calibre/src/calibre/gui2/metadata/basic_widgets.py:909 msgid "" "Tags categorize the book. This is particularly useful while searching. " "

      They can be any words or phrases, separated by commas." msgstr "" #: /home/kovid/work/calibre/src/calibre/gui2/convert/metadata_ui.py:172 -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:518 -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single_ui.py:433 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:544 #: /home/kovid/work/calibre/src/calibre/gui2/dialogs/search_ui.py:214 -#: /home/kovid/work/calibre/src/calibre/gui2/metadata/basic_widgets.py:293 +#: /home/kovid/work/calibre/src/calibre/gui2/metadata/basic_widgets.py:355 msgid "&Series:" msgstr "&Sērija:" #: /home/kovid/work/calibre/src/calibre/gui2/convert/metadata_ui.py:173 #: /home/kovid/work/calibre/src/calibre/gui2/convert/metadata_ui.py:174 -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:519 -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:520 -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single_ui.py:434 -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single_ui.py:435 -#: /home/kovid/work/calibre/src/calibre/gui2/metadata/basic_widgets.py:292 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:545 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:546 +#: /home/kovid/work/calibre/src/calibre/gui2/metadata/basic_widgets.py:354 msgid "List of known series. You can add new series." msgstr "Zināmo sēriju saraksts. Jūs varat pievienot jaunas sērijas." #: /home/kovid/work/calibre/src/calibre/gui2/convert/metadata_ui.py:175 -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single_ui.py:438 msgid "Book " msgstr "Grāmata " @@ -6114,6 +6348,7 @@ msgid "MOBI Output" msgstr "" #: /home/kovid/work/calibre/src/calibre/gui2/convert/mobi_output.py:44 +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/behavior.py:64 msgid "Default" msgstr "" @@ -6202,13 +6437,14 @@ msgid "PDB Output" msgstr "" #: /home/kovid/work/calibre/src/calibre/gui2/convert/pdb_output_ui.py:48 +#: /home/kovid/work/calibre/src/calibre/gui2/store/search/adv_search_builder_ui.py:195 msgid "&Format:" msgstr "" #: /home/kovid/work/calibre/src/calibre/gui2/convert/pdb_output_ui.py:49 #: /home/kovid/work/calibre/src/calibre/gui2/convert/pmlz_output_ui.py:47 #: /home/kovid/work/calibre/src/calibre/gui2/convert/rb_output_ui.py:34 -#: /home/kovid/work/calibre/src/calibre/gui2/convert/txt_output_ui.py:92 +#: /home/kovid/work/calibre/src/calibre/gui2/convert/txt_output_ui.py:95 msgid "&Inline TOC" msgstr "" @@ -6278,7 +6514,7 @@ msgid "Regex:" msgstr "" #: /home/kovid/work/calibre/src/calibre/gui2/convert/regex_builder_ui.py:92 -#: /home/kovid/work/calibre/src/calibre/gui2/filename_pattern_ui.py:122 +#: /home/kovid/work/calibre/src/calibre/gui2/filename_pattern_ui.py:136 msgid "Test" msgstr "" @@ -6287,6 +6523,8 @@ msgid "Occurrences:" msgstr "" #: /home/kovid/work/calibre/src/calibre/gui2/convert/regex_builder_ui.py:94 +#: /home/kovid/work/calibre/src/calibre/gui2/store/mobileread/store_dialog_ui.py:78 +#: /home/kovid/work/calibre/src/calibre/gui2/store/mobileread_store_dialog_ui.py:64 msgid "0" msgstr "" @@ -6295,13 +6533,13 @@ msgid "Goto:" msgstr "" #: /home/kovid/work/calibre/src/calibre/gui2/convert/regex_builder_ui.py:96 -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/book_info_ui.py:81 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/book_info_ui.py:72 #: /home/kovid/work/calibre/src/calibre/gui2/preferences/plugins_ui.py:89 msgid "&Previous" msgstr "" #: /home/kovid/work/calibre/src/calibre/gui2/convert/regex_builder_ui.py:97 -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/book_info_ui.py:82 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/book_info_ui.py:73 #: /home/kovid/work/calibre/src/calibre/gui2/preferences/plugins_ui.py:88 msgid "&Next" msgstr "&Nākošais" @@ -6310,26 +6548,26 @@ msgstr "&Nākošais" msgid "Preview" msgstr "Priekšskats" -#: /home/kovid/work/calibre/src/calibre/gui2/convert/search_and_replace.py:15 +#: /home/kovid/work/calibre/src/calibre/gui2/convert/search_and_replace.py:17 msgid "" "Search\n" "&\n" "Replace" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/convert/search_and_replace.py:28 -#: /home/kovid/work/calibre/src/calibre/gui2/convert/search_and_replace.py:31 -#: /home/kovid/work/calibre/src/calibre/gui2/convert/search_and_replace.py:34 +#: /home/kovid/work/calibre/src/calibre/gui2/convert/search_and_replace.py:30 +#: /home/kovid/work/calibre/src/calibre/gui2/convert/search_and_replace.py:33 +#: /home/kovid/work/calibre/src/calibre/gui2/convert/search_and_replace.py:36 msgid "&Search Regular Expression" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/convert/search_and_replace.py:71 -#: /home/kovid/work/calibre/src/calibre/gui2/widgets.py:99 +#: /home/kovid/work/calibre/src/calibre/gui2/convert/search_and_replace.py:73 +#: /home/kovid/work/calibre/src/calibre/gui2/widgets.py:101 msgid "Invalid regular expression" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/convert/search_and_replace.py:72 -#: /home/kovid/work/calibre/src/calibre/gui2/widgets.py:100 +#: /home/kovid/work/calibre/src/calibre/gui2/convert/search_and_replace.py:74 +#: /home/kovid/work/calibre/src/calibre/gui2/widgets.py:102 msgid "Invalid regular expression: %s" msgstr "" @@ -6369,10 +6607,13 @@ msgid "Options specific to the input format." msgstr "" #: /home/kovid/work/calibre/src/calibre/gui2/convert/single_ui.py:117 -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/book_info_ui.py:76 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/book_info_ui.py:69 #: /home/kovid/work/calibre/src/calibre/gui2/dialogs/comicconf_ui.py:96 #: /home/kovid/work/calibre/src/calibre/gui2/dialogs/message_box_ui.py:52 #: /home/kovid/work/calibre/src/calibre/gui2/dialogs/progress_ui.py:53 +#: /home/kovid/work/calibre/src/calibre/gui2/store/mobileread/cache_progress_dialog_ui.py:50 +#: /home/kovid/work/calibre/src/calibre/gui2/store/mobileread/store_dialog_ui.py:73 +#: /home/kovid/work/calibre/src/calibre/gui2/store/mobileread_store_dialog_ui.py:61 msgid "Dialog" msgstr "" @@ -6433,19 +6674,19 @@ msgstr "Nederīgs XPath" msgid "The XPath expression %s is invalid." msgstr "XPath izteiksme %s ir nekorekta" -#: /home/kovid/work/calibre/src/calibre/gui2/convert/structure_detection_ui.py:57 +#: /home/kovid/work/calibre/src/calibre/gui2/convert/structure_detection_ui.py:60 msgid "Chapter &mark:" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/convert/structure_detection_ui.py:58 +#: /home/kovid/work/calibre/src/calibre/gui2/convert/structure_detection_ui.py:61 msgid "Remove first &image" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/convert/structure_detection_ui.py:59 +#: /home/kovid/work/calibre/src/calibre/gui2/convert/structure_detection_ui.py:62 msgid "Insert &metadata as page at start of book" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/convert/structure_detection_ui.py:60 +#: /home/kovid/work/calibre/src/calibre/gui2/convert/structure_detection_ui.py:63 msgid "" "The header and footer removal options have been replaced by the Search & " "Replace options. Click the Search & Replace category in the bar to the left " @@ -6453,6 +6694,10 @@ msgid "" "header/footer removal regexps into the search field." msgstr "" +#: /home/kovid/work/calibre/src/calibre/gui2/convert/structure_detection_ui.py:64 +msgid "Remove &fake margins" +msgstr "" + #: /home/kovid/work/calibre/src/calibre/gui2/convert/toc.py:16 msgid "" "Table of\n" @@ -6544,53 +6789,56 @@ msgstr "" msgid "TXT Output" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/convert/txt_output_ui.py:85 +#: /home/kovid/work/calibre/src/calibre/gui2/convert/txt_output_ui.py:88 msgid "General" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/convert/txt_output_ui.py:86 +#: /home/kovid/work/calibre/src/calibre/gui2/convert/txt_output_ui.py:89 msgid "Output &Encoding:" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/convert/txt_output_ui.py:87 +#: /home/kovid/work/calibre/src/calibre/gui2/convert/txt_output_ui.py:90 msgid "&Line ending style:" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/convert/txt_output_ui.py:88 +#: /home/kovid/work/calibre/src/calibre/gui2/convert/txt_output_ui.py:91 msgid "&Formatting:" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/convert/txt_output_ui.py:89 +#: /home/kovid/work/calibre/src/calibre/gui2/convert/txt_output_ui.py:92 msgid "Plain" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/convert/txt_output_ui.py:90 +#: /home/kovid/work/calibre/src/calibre/gui2/convert/txt_output_ui.py:93 msgid "&Maximum line length:" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/convert/txt_output_ui.py:91 +#: /home/kovid/work/calibre/src/calibre/gui2/convert/txt_output_ui.py:94 msgid "Force maximum line length" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/convert/txt_output_ui.py:93 +#: /home/kovid/work/calibre/src/calibre/gui2/convert/txt_output_ui.py:96 msgid "Markdown, Textile" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/convert/txt_output_ui.py:94 +#: /home/kovid/work/calibre/src/calibre/gui2/convert/txt_output_ui.py:97 msgid "Do not remove links ( tags) before processing" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/convert/txt_output_ui.py:95 +#: /home/kovid/work/calibre/src/calibre/gui2/convert/txt_output_ui.py:98 msgid "Do not remove image references before processing" msgstr "" +#: /home/kovid/work/calibre/src/calibre/gui2/convert/txt_output_ui.py:99 +msgid "Keep text color, when possible" +msgstr "" + #: /home/kovid/work/calibre/src/calibre/gui2/convert/txtz_output.py:12 msgid "TXTZ Output" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/convert/xexp_edit_ui.py:54 -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/book_info_ui.py:77 -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/book_info_ui.py:78 +#: /home/kovid/work/calibre/src/calibre/gui2/convert/xexp_edit_ui.py:55 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/book_info_ui.py:70 #: /home/kovid/work/calibre/src/calibre/gui2/dialogs/choose_format_ui.py:46 #: /home/kovid/work/calibre/src/calibre/gui2/dialogs/confirm_delete_ui.py:54 #: /home/kovid/work/calibre/src/calibre/gui2/dialogs/password_ui.py:62 @@ -6599,7 +6847,7 @@ msgstr "" msgid "TextLabel" msgstr "TextLabel" -#: /home/kovid/work/calibre/src/calibre/gui2/convert/xexp_edit_ui.py:55 +#: /home/kovid/work/calibre/src/calibre/gui2/convert/xexp_edit_ui.py:56 msgid "Use a wizard to help construct the Regular expression" msgstr "" @@ -6677,306 +6925,304 @@ msgid "" "href=\"http://calibre-ebook.com/user_manual/xpath.html\">XPath Tutorial." msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/cover_flow.py:118 +#: /home/kovid/work/calibre/src/calibre/gui2/cover_flow.py:128 msgid "Browse by covers" msgstr "Pārlūkot vākus" -#: /home/kovid/work/calibre/src/calibre/gui2/cover_flow.py:149 +#: /home/kovid/work/calibre/src/calibre/gui2/cover_flow.py:158 msgid "Cover browser could not be loaded" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/custom_column_widgets.py:64 -#: /home/kovid/work/calibre/src/calibre/gui2/custom_column_widgets.py:89 -#: /home/kovid/work/calibre/src/calibre/gui2/custom_column_widgets.py:113 -#: /home/kovid/work/calibre/src/calibre/gui2/custom_column_widgets.py:150 -#: /home/kovid/work/calibre/src/calibre/gui2/custom_column_widgets.py:184 -#: /home/kovid/work/calibre/src/calibre/gui2/custom_column_widgets.py:294 -#: /home/kovid/work/calibre/src/calibre/gui2/custom_column_widgets.py:558 -#: /home/kovid/work/calibre/src/calibre/gui2/custom_column_widgets.py:599 -#: /home/kovid/work/calibre/src/calibre/gui2/custom_column_widgets.py:622 -#: /home/kovid/work/calibre/src/calibre/gui2/custom_column_widgets.py:673 +#: /home/kovid/work/calibre/src/calibre/gui2/custom_column_widgets.py:63 +#: /home/kovid/work/calibre/src/calibre/gui2/custom_column_widgets.py:88 +#: /home/kovid/work/calibre/src/calibre/gui2/custom_column_widgets.py:112 +#: /home/kovid/work/calibre/src/calibre/gui2/custom_column_widgets.py:149 +#: /home/kovid/work/calibre/src/calibre/gui2/custom_column_widgets.py:183 +#: /home/kovid/work/calibre/src/calibre/gui2/custom_column_widgets.py:302 +#: /home/kovid/work/calibre/src/calibre/gui2/custom_column_widgets.py:567 +#: /home/kovid/work/calibre/src/calibre/gui2/custom_column_widgets.py:608 +#: /home/kovid/work/calibre/src/calibre/gui2/custom_column_widgets.py:631 +#: /home/kovid/work/calibre/src/calibre/gui2/custom_column_widgets.py:682 #: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk.py:306 #: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk.py:311 -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single.py:503 -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single.py:504 -#: /home/kovid/work/calibre/src/calibre/gui2/library/delegates.py:114 -#: /home/kovid/work/calibre/src/calibre/gui2/library/delegates.py:134 -#: /home/kovid/work/calibre/src/calibre/gui2/library/delegates.py:216 -#: /home/kovid/work/calibre/src/calibre/gui2/library/delegates.py:249 -#: /home/kovid/work/calibre/src/calibre/gui2/library/delegates.py:253 -#: /home/kovid/work/calibre/src/calibre/gui2/metadata/basic_widgets.py:994 +#: /home/kovid/work/calibre/src/calibre/gui2/library/delegates.py:120 +#: /home/kovid/work/calibre/src/calibre/gui2/library/delegates.py:140 +#: /home/kovid/work/calibre/src/calibre/gui2/library/delegates.py:222 +#: /home/kovid/work/calibre/src/calibre/gui2/library/delegates.py:255 +#: /home/kovid/work/calibre/src/calibre/gui2/library/delegates.py:259 +#: /home/kovid/work/calibre/src/calibre/gui2/metadata/basic_widgets.py:1117 msgid "Undefined" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/custom_column_widgets.py:127 -#: /home/kovid/work/calibre/src/calibre/gui2/custom_column_widgets.py:630 +#: /home/kovid/work/calibre/src/calibre/gui2/custom_column_widgets.py:126 +#: /home/kovid/work/calibre/src/calibre/gui2/custom_column_widgets.py:639 msgid "star(s)" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/custom_column_widgets.py:128 -#: /home/kovid/work/calibre/src/calibre/gui2/custom_column_widgets.py:631 +#: /home/kovid/work/calibre/src/calibre/gui2/custom_column_widgets.py:127 +#: /home/kovid/work/calibre/src/calibre/gui2/custom_column_widgets.py:640 msgid "Unrated" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/custom_column_widgets.py:171 -#: /home/kovid/work/calibre/src/calibre/gui2/custom_column_widgets.py:660 +#: /home/kovid/work/calibre/src/calibre/gui2/custom_column_widgets.py:170 +#: /home/kovid/work/calibre/src/calibre/gui2/custom_column_widgets.py:669 msgid "Set '%s' to today" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/custom_column_widgets.py:173 -#: /home/kovid/work/calibre/src/calibre/gui2/custom_column_widgets.py:662 +#: /home/kovid/work/calibre/src/calibre/gui2/custom_column_widgets.py:172 +#: /home/kovid/work/calibre/src/calibre/gui2/custom_column_widgets.py:671 msgid "Clear '%s'" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/custom_column_widgets.py:290 +#: /home/kovid/work/calibre/src/calibre/gui2/custom_column_widgets.py:298 msgid " index:" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/custom_column_widgets.py:359 +#: /home/kovid/work/calibre/src/calibre/gui2/custom_column_widgets.py:367 msgid "" "The enumeration \"{0}\" contains an invalid value that will be set to the " "default" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/custom_column_widgets.py:513 +#: /home/kovid/work/calibre/src/calibre/gui2/custom_column_widgets.py:522 msgid "Apply changes" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/custom_column_widgets.py:706 +#: /home/kovid/work/calibre/src/calibre/gui2/custom_column_widgets.py:715 msgid "Remove series" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/custom_column_widgets.py:709 +#: /home/kovid/work/calibre/src/calibre/gui2/custom_column_widgets.py:718 msgid "Automatically number books" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/custom_column_widgets.py:712 +#: /home/kovid/work/calibre/src/calibre/gui2/custom_column_widgets.py:721 msgid "Force numbers to start with " msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/custom_column_widgets.py:783 +#: /home/kovid/work/calibre/src/calibre/gui2/custom_column_widgets.py:792 msgid "" "The enumeration \"{0}\" contains invalid values that will not appear in the " "list" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/custom_column_widgets.py:826 +#: /home/kovid/work/calibre/src/calibre/gui2/custom_column_widgets.py:836 msgid "Remove all tags" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/custom_column_widgets.py:846 +#: /home/kovid/work/calibre/src/calibre/gui2/custom_column_widgets.py:856 msgid "tags to add" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/custom_column_widgets.py:852 +#: /home/kovid/work/calibre/src/calibre/gui2/custom_column_widgets.py:863 msgid "tags to remove" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/device.py:45 -#: /home/kovid/work/calibre/src/calibre/utils/ipc/job.py:136 +#: /home/kovid/work/calibre/src/calibre/gui2/device.py:43 +#: /home/kovid/work/calibre/src/calibre/utils/ipc/job.py:144 msgid "No details available." msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/device.py:185 +#: /home/kovid/work/calibre/src/calibre/gui2/device.py:168 msgid "Device no longer connected." msgstr "Ierīce vairs nav savienota." -#: /home/kovid/work/calibre/src/calibre/gui2/device.py:303 +#: /home/kovid/work/calibre/src/calibre/gui2/device.py:291 msgid "Get device information" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/device.py:314 +#: /home/kovid/work/calibre/src/calibre/gui2/device.py:305 msgid "Get list of books on device" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/device.py:324 +#: /home/kovid/work/calibre/src/calibre/gui2/device.py:315 msgid "Get annotations from device" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/device.py:336 +#: /home/kovid/work/calibre/src/calibre/gui2/device.py:327 msgid "Send metadata to device" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/device.py:341 +#: /home/kovid/work/calibre/src/calibre/gui2/device.py:332 msgid "Send collections to device" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/device.py:376 +#: /home/kovid/work/calibre/src/calibre/gui2/device.py:368 msgid "Upload %d books to device" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/device.py:391 +#: /home/kovid/work/calibre/src/calibre/gui2/device.py:383 msgid "Delete books from device" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/device.py:408 +#: /home/kovid/work/calibre/src/calibre/gui2/device.py:400 msgid "Download books from device" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/device.py:418 +#: /home/kovid/work/calibre/src/calibre/gui2/device.py:410 msgid "View book on device" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/device.py:452 +#: /home/kovid/work/calibre/src/calibre/gui2/device.py:451 msgid "Set default send to device action" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/device.py:458 +#: /home/kovid/work/calibre/src/calibre/gui2/device.py:457 msgid "Send to main memory" msgstr "Sūtīt uz pamatatmiņu" -#: /home/kovid/work/calibre/src/calibre/gui2/device.py:460 +#: /home/kovid/work/calibre/src/calibre/gui2/device.py:459 msgid "Send to storage card A" msgstr "Sūtīt uz karti A" -#: /home/kovid/work/calibre/src/calibre/gui2/device.py:462 +#: /home/kovid/work/calibre/src/calibre/gui2/device.py:461 msgid "Send to storage card B" msgstr "Sūtīt uz karti B" -#: /home/kovid/work/calibre/src/calibre/gui2/device.py:467 -#: /home/kovid/work/calibre/src/calibre/gui2/device.py:476 +#: /home/kovid/work/calibre/src/calibre/gui2/device.py:466 +#: /home/kovid/work/calibre/src/calibre/gui2/device.py:475 msgid "Main Memory" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/device.py:488 +#: /home/kovid/work/calibre/src/calibre/gui2/device.py:487 msgid "Send specific format to" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/device.py:489 +#: /home/kovid/work/calibre/src/calibre/gui2/device.py:488 msgid "Send and delete from library" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/device.py:532 +#: /home/kovid/work/calibre/src/calibre/gui2/device.py:531 msgid "Eject device" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/device.py:594 -#: /home/kovid/work/calibre/src/calibre/gui2/preferences/misc.py:41 -#: /home/kovid/work/calibre/src/calibre/gui2/preferences/plugins.py:302 +#: /home/kovid/work/calibre/src/calibre/gui2/device.py:611 +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/misc.py:55 +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/plugins.py:313 #: /home/kovid/work/calibre/src/calibre/utils/ipc/job.py:54 msgid "Error" msgstr "Kļūda" -#: /home/kovid/work/calibre/src/calibre/gui2/device.py:595 +#: /home/kovid/work/calibre/src/calibre/gui2/device.py:612 msgid "Error communicating with device" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/device.py:611 -#: /home/kovid/work/calibre/src/calibre/gui2/device.py:1139 -#: /home/kovid/work/calibre/src/calibre/gui2/email.py:298 +#: /home/kovid/work/calibre/src/calibre/gui2/device.py:631 +#: /home/kovid/work/calibre/src/calibre/gui2/device.py:1170 +#: /home/kovid/work/calibre/src/calibre/gui2/email.py:221 msgid "No suitable formats" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/device.py:627 +#: /home/kovid/work/calibre/src/calibre/gui2/device.py:647 msgid "Select folder to open as device" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/device.py:678 +#: /home/kovid/work/calibre/src/calibre/gui2/device.py:698 msgid "Error talking to device" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/device.py:679 +#: /home/kovid/work/calibre/src/calibre/gui2/device.py:699 msgid "" "There was a temporary error talking to the device. Please unplug and " "reconnect the device and or reboot." msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/device.py:722 +#: /home/kovid/work/calibre/src/calibre/gui2/device.py:742 msgid "Device: " msgstr "Ierīce: " -#: /home/kovid/work/calibre/src/calibre/gui2/device.py:724 +#: /home/kovid/work/calibre/src/calibre/gui2/device.py:744 msgid " detected." msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/device.py:822 +#: /home/kovid/work/calibre/src/calibre/gui2/device.py:846 msgid "selected to send" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/device.py:841 +#: /home/kovid/work/calibre/src/calibre/gui2/device.py:865 msgid "%i of %i Books" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/device.py:844 +#: /home/kovid/work/calibre/src/calibre/gui2/device.py:868 msgid "0 of %i Books" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/device.py:845 +#: /home/kovid/work/calibre/src/calibre/gui2/device.py:869 msgid "Choose format to send to device" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/device.py:853 +#: /home/kovid/work/calibre/src/calibre/gui2/device.py:877 msgid "No device" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/device.py:854 +#: /home/kovid/work/calibre/src/calibre/gui2/device.py:878 msgid "Cannot send: No device is connected" msgstr "Nevar nosūtīt: Neviena ierīce nav savienota" -#: /home/kovid/work/calibre/src/calibre/gui2/device.py:857 -#: /home/kovid/work/calibre/src/calibre/gui2/device.py:861 +#: /home/kovid/work/calibre/src/calibre/gui2/device.py:881 +#: /home/kovid/work/calibre/src/calibre/gui2/device.py:885 msgid "No card" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/device.py:858 -#: /home/kovid/work/calibre/src/calibre/gui2/device.py:862 +#: /home/kovid/work/calibre/src/calibre/gui2/device.py:882 +#: /home/kovid/work/calibre/src/calibre/gui2/device.py:886 msgid "Cannot send: Device has no storage card" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/device.py:918 -#: /home/kovid/work/calibre/src/calibre/gui2/device.py:1001 -#: /home/kovid/work/calibre/src/calibre/gui2/device.py:1133 +#: /home/kovid/work/calibre/src/calibre/gui2/device.py:947 +#: /home/kovid/work/calibre/src/calibre/gui2/device.py:1030 +#: /home/kovid/work/calibre/src/calibre/gui2/device.py:1164 msgid "Auto convert the following books before uploading to the device?" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/device.py:947 +#: /home/kovid/work/calibre/src/calibre/gui2/device.py:976 msgid "Sending catalogs to device." msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/device.py:1046 +#: /home/kovid/work/calibre/src/calibre/gui2/device.py:1077 msgid "Sending news to device." msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/device.py:1100 +#: /home/kovid/work/calibre/src/calibre/gui2/device.py:1131 msgid "Sending books to device." msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/device.py:1140 +#: /home/kovid/work/calibre/src/calibre/gui2/device.py:1171 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:1204 +#: /home/kovid/work/calibre/src/calibre/gui2/device.py:1243 msgid "No space on device" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/device.py:1205 +#: /home/kovid/work/calibre/src/calibre/gui2/device.py:1244 msgid "" "

      Cannot upload books to device there is no more free space available " msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/device_drivers/configwidget.py:124 +#: /home/kovid/work/calibre/src/calibre/gui2/device_drivers/configwidget.py:135 msgid "Unknown formats" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/device_drivers/configwidget.py:125 +#: /home/kovid/work/calibre/src/calibre/gui2/device_drivers/configwidget.py:136 msgid "" "You have enabled the {0} formats for your {1}. The {1} may not " "support them. If you send these formats to your {1} they may not work. Are " "you sure?" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/device_drivers/configwidget.py:137 -#: /home/kovid/work/calibre/src/calibre/gui2/library/delegates.py:403 -#: /home/kovid/work/calibre/src/calibre/gui2/preferences/plugboard.py:255 +#: /home/kovid/work/calibre/src/calibre/gui2/device_drivers/configwidget.py:148 +#: /home/kovid/work/calibre/src/calibre/gui2/library/delegates.py:409 +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/plugboard.py:293 #: /home/kovid/work/calibre/src/calibre/gui2/preferences/save_template.py:61 msgid "Invalid template" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/device_drivers/configwidget.py:138 -#: /home/kovid/work/calibre/src/calibre/gui2/library/delegates.py:404 -#: /home/kovid/work/calibre/src/calibre/gui2/preferences/plugboard.py:256 +#: /home/kovid/work/calibre/src/calibre/gui2/device_drivers/configwidget.py:149 +#: /home/kovid/work/calibre/src/calibre/gui2/library/delegates.py:410 +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/plugboard.py:294 #: /home/kovid/work/calibre/src/calibre/gui2/preferences/save_template.py:62 msgid "The template %s is invalid:" msgstr "" @@ -7042,7 +7288,7 @@ msgstr "" msgid "&Tags to set on created book entries:" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/book_info_ui.py:80 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/book_info_ui.py:71 msgid "Fit &cover within view" msgstr "" @@ -7151,68 +7397,80 @@ msgid "" " have no entries in the database. Check the box next to the item you " "want\n" " to delete. Use with caution.

      \n" -"

      Fix marked is applicable only to covers (the two lines " -"marked\n" -" 'fixable'). In the case of missing cover files, checking the " -"fixable\n" -" box and pushing this button will remove the cover mark from the\n" -" database for all the files in that category. In the case of extra\n" -" cover files, checking the fixable box and pushing this button will\n" -" add the cover mark to the database for all the files in that\n" -" category.

      \n" +"\n" +"

      Fix marked is applicable only to covers and missing " +"formats\n" +" (the three lines marked 'fixable'). In the case of missing cover " +"files,\n" +" checking the fixable box and pushing this button will tell calibre " +"that\n" +" there is no cover for all of the books listed. Use this option if " +"you\n" +" are not going to restore the covers from a backup. In the case of " +"extra\n" +" cover files, checking the fixable box and pushing this button will " +"tell\n" +" calibre that the cover files it found are correct for all the books\n" +" listed. Use this when you are not going to delete the file(s). In " +"the\n" +" case of missing formats, checking the fixable box and pushing this\n" +" button will tell calibre that the formats are really gone. Use this " +"if\n" +" you are not going to restore the formats from a backup.

      \n" +"\n" " " msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/check_library.py:220 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/check_library.py:226 msgid "&Run the check again" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/check_library.py:223 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/check_library.py:229 msgid "Copy &to clipboard" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/check_library.py:230 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/check_library.py:236 msgid "Delete marked files (checked subitems)" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/check_library.py:236 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/check_library.py:242 msgid "Fix marked sections (checked fixable items)" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/check_library.py:246 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/check_library.py:252 msgid "Names to ignore:" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/check_library.py:251 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/check_library.py:257 msgid "" "Enter comma-separated standard file name wildcards, such as synctoy*.dat" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/check_library.py:254 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/check_library.py:260 msgid "Extensions to ignore" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/check_library.py:259 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/check_library.py:265 msgid "" "Enter comma-separated extensions without a leading dot. Used only in book " "folders" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/check_library.py:308 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/check_library.py:314 msgid "(fixable)" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/check_library.py:331 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/check_library.py:337 msgid "Path from library" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/check_library.py:331 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/check_library.py:337 #: /home/kovid/work/calibre/src/calibre/gui2/viewer/bookmarkmanager.py:89 -#: /home/kovid/work/calibre/src/calibre/library/server/browse.py:253 +#: /home/kovid/work/calibre/src/calibre/library/server/browse.py:256 msgid "Name" msgstr "Nosaukums" -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/check_library.py:360 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/check_library.py:366 msgid "" "The marked files and folders will be permanently deleted. Are you " "sure?" @@ -7225,7 +7483,7 @@ msgstr "Izvēlieties formātu" #: /home/kovid/work/calibre/src/calibre/gui2/dialogs/choose_format_device_ui.py:49 #: /home/kovid/work/calibre/src/calibre/gui2/dialogs/delete_matching_from_device.py:76 -#: /home/kovid/work/calibre/src/calibre/gui2/library/models.py:1169 +#: /home/kovid/work/calibre/src/calibre/gui2/store/mobileread/models.py:23 msgid "Format" msgstr "" @@ -7316,6 +7574,20 @@ msgstr "" msgid "&Move current library to new location" msgstr "" +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/choose_plugin_toolbars.py:23 +msgid "Add \"%s\" to toolbars or menus" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/choose_plugin_toolbars.py:29 +msgid "Select the toolbars and/or menus to add %s to:" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/choose_plugin_toolbars.py:45 +msgid "" +"You can also customise the plugin locations using Preferences -> " +"Customise the toolbar" +msgstr "" + #: /home/kovid/work/calibre/src/calibre/gui2/dialogs/comicconf.py:33 msgid "Set defaults for conversion of comics (CBR/CBZ files)" msgstr "" @@ -7326,12 +7598,13 @@ msgstr "" #: /home/kovid/work/calibre/src/calibre/gui2/dialogs/comicconf_ui.py:97 #: /home/kovid/work/calibre/src/calibre/gui2/dialogs/search_ui.py:211 -#: /home/kovid/work/calibre/src/calibre/gui2/metadata/basic_widgets.py:61 +#: /home/kovid/work/calibre/src/calibre/gui2/metadata/basic_widgets.py:73 +#: /home/kovid/work/calibre/src/calibre/gui2/store/search/adv_search_builder_ui.py:189 msgid "&Title:" msgstr "" #: /home/kovid/work/calibre/src/calibre/gui2/dialogs/comicconf_ui.py:98 -#: /home/kovid/work/calibre/src/calibre/gui2/metadata/basic_widgets.py:155 +#: /home/kovid/work/calibre/src/calibre/gui2/metadata/basic_widgets.py:167 msgid "&Author(s):" msgstr "" @@ -7346,8 +7619,8 @@ msgstr "" #: /home/kovid/work/calibre/src/calibre/gui2/dialogs/comments_dialog.py:25 #: /home/kovid/work/calibre/src/calibre/gui2/dialogs/template_dialog.py:31 -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/tweak_epub_ui.py:60 -#: /home/kovid/work/calibre/src/calibre/gui2/preferences/main.py:226 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/tweak_epub_ui.py:65 +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/main.py:233 msgid "&Cancel" msgstr "" @@ -7356,22 +7629,22 @@ msgstr "" msgid "Edit Comments" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/confirm_delete_location_ui.py:62 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/confirm_delete_location_ui.py:76 msgid "Where do you want to delete from?" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/confirm_delete_location_ui.py:63 -#: /home/kovid/work/calibre/src/calibre/gui2/layout.py:63 -#: /home/kovid/work/calibre/src/calibre/library/server/opds.py:230 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/confirm_delete_location_ui.py:77 +#: /home/kovid/work/calibre/src/calibre/gui2/layout.py:68 +#: /home/kovid/work/calibre/src/calibre/library/server/opds.py:228 msgid "Library" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/confirm_delete_location_ui.py:64 -#: /home/kovid/work/calibre/src/calibre/gui2/layout.py:65 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/confirm_delete_location_ui.py:78 +#: /home/kovid/work/calibre/src/calibre/gui2/layout.py:70 msgid "Device" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/confirm_delete_location_ui.py:65 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/confirm_delete_location_ui.py:79 msgid "Library and Device" msgstr "" @@ -7394,11 +7667,12 @@ msgid "Location" msgstr "" #: /home/kovid/work/calibre/src/calibre/gui2/dialogs/delete_matching_from_device.py:76 -#: /home/kovid/work/calibre/src/calibre/gui2/library/models.py:69 +#: /home/kovid/work/calibre/src/calibre/gui2/library/models.py:67 #: /home/kovid/work/calibre/src/calibre/gui2/library/models.py:979 -#: /home/kovid/work/calibre/src/calibre/gui2/preferences/create_custom_column.py:33 -#: /home/kovid/work/calibre/src/calibre/library/field_metadata.py:295 -#: /home/kovid/work/calibre/src/calibre/library/server/opds.py:589 +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/create_custom_column.py:32 +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/create_custom_column.py:73 +#: /home/kovid/work/calibre/src/calibre/library/field_metadata.py:321 +#: /home/kovid/work/calibre/src/calibre/library/server/opds.py:573 msgid "Date" msgstr "Datums" @@ -7414,136 +7688,120 @@ msgstr "" msgid "" "

      This book is locked by DRM. To learn more about DRM and why you " "cannot read or convert this book in calibre, \n" -"click here." +" click " +"here.

      A large number of recent, DRM free releases are \n" +" available at Open " +"Books." msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/edit_authors_dialog.py:35 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/edit_authors_dialog.py:43 msgid "Author sort" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/edit_authors_dialog.py:117 -#: /home/kovid/work/calibre/src/calibre/gui2/tag_view.py:916 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/edit_authors_dialog.py:104 +#: /home/kovid/work/calibre/src/calibre/gui2/lrf_renderer/main.py:160 +#: /home/kovid/work/calibre/src/calibre/gui2/metadata/single_download.py:471 +#: /home/kovid/work/calibre/src/calibre/gui2/viewer/main.py:503 +msgid "No matches found" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/edit_authors_dialog.py:120 +#: /home/kovid/work/calibre/src/calibre/gui2/widgets.py:419 +msgid "Change Case" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/edit_authors_dialog.py:121 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk.py:261 +#: /home/kovid/work/calibre/src/calibre/gui2/widgets.py:420 +msgid "Upper Case" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/edit_authors_dialog.py:122 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk.py:260 +#: /home/kovid/work/calibre/src/calibre/gui2/widgets.py:421 +msgid "Lower Case" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/edit_authors_dialog.py:123 +#: /home/kovid/work/calibre/src/calibre/gui2/widgets.py:422 +msgid "Swap Case" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/edit_authors_dialog.py:124 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk.py:262 +#: /home/kovid/work/calibre/src/calibre/gui2/widgets.py:423 +msgid "Title Case" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/edit_authors_dialog.py:125 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk.py:263 +#: /home/kovid/work/calibre/src/calibre/gui2/widgets.py:424 +msgid "Capitalize" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/edit_authors_dialog.py:141 +msgid "Copy to author sort" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/edit_authors_dialog.py:144 +msgid "Copy to author" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/edit_authors_dialog.py:271 +#: /home/kovid/work/calibre/src/calibre/gui2/tag_view.py:1439 msgid "Invalid author name" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/edit_authors_dialog.py:118 -#: /home/kovid/work/calibre/src/calibre/gui2/tag_view.py:917 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/edit_authors_dialog.py:272 +#: /home/kovid/work/calibre/src/calibre/gui2/tag_view.py:1440 msgid "Author names cannot contain & characters." msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/edit_authors_dialog_ui.py:71 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/edit_authors_dialog_ui.py:88 +#: /home/kovid/work/calibre/src/calibre/gui2/metadata/single.py:120 msgid "Manage authors" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/edit_authors_dialog_ui.py:72 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/edit_authors_dialog_ui.py:89 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:597 +msgid "&Search for:" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/edit_authors_dialog_ui.py:90 +#: /home/kovid/work/calibre/src/calibre/gui2/tag_view.py:2105 +msgid "F&ind" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/edit_authors_dialog_ui.py:91 msgid "Sort by author" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/edit_authors_dialog_ui.py:73 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/edit_authors_dialog_ui.py:92 msgid "Sort by author sort" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/edit_authors_dialog_ui.py:74 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/edit_authors_dialog_ui.py:93 msgid "" -"Reset all the author sort values to a value automatically generated from the " -"author. Exactly how this value is automatically generated can be controlled " -"via Preferences->Advanced->Tweaks" +"Reset all the author sort values to a value automatically\n" +"generated from the author. Exactly how this value is automatically\n" +"generated can be controlled via Preferences->Advanced->Tweaks" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/edit_authors_dialog_ui.py:75 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/edit_authors_dialog_ui.py:96 msgid "Recalculate all author sort values" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/fetch_metadata.py:62 -msgid "Author Sort" -msgstr "Autors kārtošanai" - -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/fetch_metadata.py:64 -msgid "ISBN" -msgstr "ISBN" - -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/fetch_metadata.py:66 -msgid "Has Cover" -msgstr "" - -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/fetch_metadata.py:67 -msgid "Has Summary" -msgstr "" - -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/fetch_metadata.py:192 -msgid "Finding metadata..." -msgstr "Meklē metadatus..." - -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/fetch_metadata.py:206 -msgid "Could not find metadata" -msgstr "Nav iespējams atrast metadatus" - -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/fetch_metadata.py:207 -msgid "The metadata download seems to have stalled. Try again later." -msgstr "" - -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/fetch_metadata.py:216 -msgid "Warning" -msgstr "Brīdinājums" - -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/fetch_metadata.py:217 -msgid "Could not fetch metadata from:" -msgstr "" - -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/fetch_metadata.py:221 -msgid "No metadata found" -msgstr "Metadati netika atrasti" - -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/fetch_metadata.py:222 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/edit_authors_dialog_ui.py:97 msgid "" -"No metadata found, try adjusting the title and author and/or removing the " -"ISBN." +"Copy author sort to author for every author. You typically use this button\n" +"after changing Preferences->Advanced->Tweaks->Author sort name algorithm" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/fetch_metadata_ui.py:93 -msgid "Fetch metadata" -msgstr "Lejupielādēt metadatus" - -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/fetch_metadata_ui.py:94 -msgid "" -"

      calibre can find metadata for your books from two locations: Google " -"Books and isbndb.com.

      To use isbndb.com you must sign up for a " -"free account and enter your access key " -"below." -msgstr "" -"

      calibre metadatus meklē Google Books un " -"isbndb.com.

      Lai izmantotu isbndb.com Jums nepieciešams zemāk " -"ievadīt pieejas kodu, kuru var iegūt reģistrējoties bez maksas.

      " - -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/fetch_metadata_ui.py:95 -msgid "&Access Key:" -msgstr "Pieejas &kods:" - -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/fetch_metadata_ui.py:96 -msgid "Fetch" -msgstr "Meklēt" - -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/fetch_metadata_ui.py:97 -msgid "Matches" -msgstr "Rezultāti" - -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/fetch_metadata_ui.py:98 -msgid "" -"Select the book that most closely matches your copy from the list below" -msgstr "" -"No zemāk esošā saraksta izvēlieties grāmatu, kura ir visvairāk atbilst " -"meklētajai." - -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/fetch_metadata_ui.py:99 -msgid "Overwrite author and title with author and title of selected book" +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/edit_authors_dialog_ui.py:99 +msgid "Copy all author sort values to author" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/fetch_metadata_ui.py:100 -msgid "Download &social metadata (tags/rating/etc.) for the selected book" -msgstr "" - -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/job_view_ui.py:42 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/job_view_ui.py:45 msgid "Details of job" msgstr "" @@ -7563,27 +7821,39 @@ msgstr "" msgid "Stop &all non device jobs" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/message_box.py:43 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/message_box.py:49 msgid "&Copy to clipboard" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/message_box.py:48 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/message_box.py:53 msgid "Show &details" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/message_box.py:49 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/message_box.py:54 msgid "Hide &details" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/message_box.py:53 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/message_box.py:58 msgid "Show detailed information about this error" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/message_box.py:93 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/message_box.py:98 #: /home/kovid/work/calibre/src/calibre/gui2/wizard/__init__.py:525 msgid "Copied" msgstr "Nokopēts" +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/message_box.py:135 +#: /home/kovid/work/calibre/src/calibre/gui2/metadata/single_download.py:770 +#: /home/kovid/work/calibre/src/calibre/gui2/viewer/main_ui.py:205 +msgid "Copy to clipboard" +msgstr "Kopēt uz starpliktuvi" + +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/message_box.py:179 +#: /home/kovid/work/calibre/src/calibre/gui2/metadata/single_download.py:831 +#: /home/kovid/work/calibre/src/calibre/gui2/metadata/single_download.py:922 +msgid "View log" +msgstr "" + #: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk.py:58 msgid "Title/Author" msgstr "" @@ -7593,6 +7863,7 @@ msgid "Standard metadata" msgstr "" #: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk.py:60 +#: /home/kovid/work/calibre/src/calibre/gui2/metadata/single.py:852 msgid "Custom metadata" msgstr "" @@ -7605,26 +7876,6 @@ msgstr "" msgid "Working" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk.py:260 -#: /home/kovid/work/calibre/src/calibre/gui2/widgets.py:384 -msgid "Lower Case" -msgstr "" - -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk.py:261 -#: /home/kovid/work/calibre/src/calibre/gui2/widgets.py:383 -msgid "Upper Case" -msgstr "" - -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk.py:262 -#: /home/kovid/work/calibre/src/calibre/gui2/widgets.py:386 -msgid "Title Case" -msgstr "" - -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk.py:263 -#: /home/kovid/work/calibre/src/calibre/gui2/widgets.py:387 -msgid "Capitalize" -msgstr "" - #: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk.py:266 msgid "Character match" msgstr "" @@ -7655,11 +7906,15 @@ msgid "" "cannot be canceled or undone" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk.py:381 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk.py:382 msgid "Book %d:" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk.py:396 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk.py:400 +msgid "Enter an identifier type" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk.py:405 msgid "" "You can destroy your library using this feature. Changes are " "permanent. There is no undo function. You are strongly encouraged to back up " @@ -7667,7 +7922,7 @@ msgid "" "character matching or regular expressions. " msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk.py:404 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk.py:413 msgid "" "In character mode, the field is searched for the entered search text. The " "text is replaced by the specified replacement text everywhere it is found in " @@ -7677,7 +7932,7 @@ msgid "" "text will match both upper- and lower-case letters" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk.py:415 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk.py:424 msgid "" "In regular expression mode, the search text is an arbitrary python-" "compatible regular expression. The replacement text can contain " @@ -7692,80 +7947,84 @@ msgid "" "function." msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk.py:489 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk.py:502 msgid "S/R TEMPLATE ERROR" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk.py:616 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk.py:648 msgid "You must specify a destination when source is a composite field" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk.py:715 -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk.py:723 -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk.py:844 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk.py:654 +msgid "You must specify a destination identifier type" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk.py:761 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk.py:780 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk.py:907 msgid "Search/replace invalid" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk.py:716 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk.py:762 msgid "" "Authors cannot be set to the empty string. Book title %s not processed" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk.py:724 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk.py:781 msgid "Title cannot be set to the empty string. Book title %s not processed" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk.py:845 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk.py:908 msgid "Search pattern is invalid: %s" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk.py:897 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk.py:960 msgid "" "Applying changes to %d books.\n" "Phase {0} {1}%%." msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk.py:927 -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:561 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk.py:990 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:587 msgid "Delete saved search/replace" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk.py:928 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk.py:991 msgid "The selected saved search/replace will be deleted. Are you sure?" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk.py:945 -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk.py:953 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk.py:1008 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk.py:1016 msgid "Save search/replace" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk.py:946 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk.py:1009 msgid "Search/replace name:" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk.py:954 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk.py:1017 msgid "" "That saved search/replace already exists and will be overwritten. Are you " "sure?" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:498 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:524 msgid "Edit Meta information" msgstr "Rediģēt metadatus" -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:500 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:526 msgid "A&utomatically set author sort" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:501 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:527 msgid "&Swap title and author" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:502 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:528 msgid "Author s&ort: " msgstr "Autors &kārtošanai: " -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:503 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:529 msgid "" "Specify how the author(s) of this book should be sorted. For example Charles " "Dickens should be sorted as Dickens, Charles." @@ -7773,66 +8032,60 @@ msgstr "" "Norāda grāmatas autora(u) kārtošanas secību. Piemēram, Čārlzs Dikenss būtu " "jākārto kā Dikenss, Čārlzs." -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:504 -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single_ui.py:424 -#: /home/kovid/work/calibre/src/calibre/gui2/metadata/basic_widgets.py:786 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:530 +#: /home/kovid/work/calibre/src/calibre/gui2/metadata/basic_widgets.py:867 msgid "&Rating:" msgstr "&Vērtējums:" -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:505 -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:506 -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single_ui.py:425 -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single_ui.py:426 -#: /home/kovid/work/calibre/src/calibre/gui2/metadata/basic_widgets.py:787 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:531 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:532 +#: /home/kovid/work/calibre/src/calibre/gui2/metadata/basic_widgets.py:868 msgid "Rating of this book. 0-5 stars" msgstr "Šīs grāmatas vērtējums (0 - 5 zvaigznes)" -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:507 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:533 msgid "No change" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:508 -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single_ui.py:427 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:534 msgid " stars" msgstr " zvaigznes" -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:510 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:536 msgid "Add ta&gs: " msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:512 -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:513 -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single_ui.py:431 -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single_ui.py:432 -#: /home/kovid/work/calibre/src/calibre/gui2/metadata/single.py:140 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:538 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:539 +#: /home/kovid/work/calibre/src/calibre/gui2/metadata/single.py:166 msgid "Open Tag Editor" msgstr "Atvērt birku redaktoru" -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:514 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:540 msgid "&Remove tags:" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:515 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:541 msgid "Comma separated list of tags to remove from the books. " msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:516 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:542 msgid "Check this box to remove all tags from the books." msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:517 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:543 msgid "Remove &all" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:521 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:547 msgid "If checked, the series will be cleared" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:522 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:548 msgid "&Clear series" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:523 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:549 msgid "" "If not checked, the series number for the books will be set to 1.\n" "If checked, selected books will be automatically numbered, in the order\n" @@ -7840,192 +8093,194 @@ msgid "" "Book A will have series number 1 and Book B series number 2." msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:527 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:553 msgid "&Automatically number books in this series" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:528 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:554 msgid "" "Series will normally be renumbered from the highest number in the database\n" "for that series. Checking this box will tell calibre to start numbering\n" "from the value in the box" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:531 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:557 msgid "&Force numbers to start with:" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:532 -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single_ui.py:440 -#: /home/kovid/work/calibre/src/calibre/gui2/metadata/basic_widgets.py:978 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:558 +#: /home/kovid/work/calibre/src/calibre/gui2/metadata/basic_widgets.py:1101 msgid "&Date:" msgstr "&Datums:" -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:533 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:559 msgid "d MMM yyyy" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:535 -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:540 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:561 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:566 msgid "&Apply date" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:536 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:562 msgid "&Published:" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:538 -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single_ui.py:444 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:564 msgid "Clear published date" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:541 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:567 msgid "Remove &format:" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:542 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:568 msgid "" "Force the title to be in title case. If both this and swap authors are " "checked,\n" "title and author are swapped before the title case is set" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:544 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:570 msgid "Change title to title &case" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:545 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:571 msgid "" "Update title sort based on the current title. This will be applied only " "after other changes to title." msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:546 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:572 msgid "Update &title sort" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:547 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:573 msgid "" "Remove stored conversion settings for the selected books.\n" "\n" "Future conversion of these books will use the default settings." msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:550 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:576 msgid "Remove &stored conversion settings for the selected books" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:551 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:577 msgid "Change &cover" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:552 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:578 msgid "&Generate default cover" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:553 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:579 msgid "&Remove cover" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:554 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:580 msgid "Set from &ebook file(s)" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:555 -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single_ui.py:465 -#: /home/kovid/work/calibre/src/calibre/gui2/metadata/single.py:392 -#: /home/kovid/work/calibre/src/calibre/gui2/metadata/single.py:521 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:581 +#: /home/kovid/work/calibre/src/calibre/gui2/metadata/single.py:495 +#: /home/kovid/work/calibre/src/calibre/gui2/metadata/single.py:659 msgid "&Basic metadata" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:556 -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single_ui.py:466 -#: /home/kovid/work/calibre/src/calibre/gui2/metadata/single.py:399 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:582 +#: /home/kovid/work/calibre/src/calibre/gui2/metadata/single.py:502 msgid "&Custom metadata" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:557 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:583 msgid "Load searc&h/replace:" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:558 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:584 msgid "Select saved search/replace to load." msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:559 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:585 msgid "Save current search/replace" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:560 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:586 msgid "Sa&ve" 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:588 #: /home/kovid/work/calibre/src/calibre/gui2/viewer/bookmarkmanager_ui.py:64 msgid "Delete" msgstr "Dzēst" -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:563 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:589 msgid "Search &field:" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:564 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:590 msgid "The name of the field that you want to search" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:565 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:591 msgid "Search &mode:" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:566 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:592 msgid "" "Choose whether to use basic text matching or advanced regular expression " "matching" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:567 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:593 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:615 +msgid "Identifier type:" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:594 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:616 +msgid "Choose which identifier type to operate upon" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:595 msgid "Te&mplate:" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:568 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:596 msgid "Enter a template to be used as the source for the search/replace" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:569 -msgid "&Search for:" -msgstr "" - -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:570 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:598 msgid "" "Enter the what you are looking for, either plain text or a regular " "expression, depending on the mode" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:571 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:599 msgid "" "Check this box if the search string must match exactly upper and lower case. " "Uncheck it if case is to be ignored" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:572 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:600 msgid "Cas&e sensitive" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:573 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:601 msgid "&Replace with:" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:574 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:602 msgid "" "The replacement text. The matched search text will be replaced with this " "string" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:575 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:603 msgid "&Apply function after replace:" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:576 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:604 msgid "" "Specify how the text is to be processed after matching and replacement. In " "character mode, the entire\n" @@ -8033,25 +8288,25 @@ msgid "" "processed" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:578 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:606 msgid "&Destination field:" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:579 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:607 msgid "" "The field that the text will be put into after all replacements.\n" "If blank, the source field is used if the field is modifiable" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:581 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:609 msgid "M&ode:" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:582 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:610 msgid "Specify how the text should be copied into the destination." msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:583 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:611 msgid "" "Specifies whether result items should be split into multiple values or\n" "left as single values. This option has the most effect when the source field " @@ -8059,429 +8314,66 @@ msgid "" "not multiple and the destination field is multiple" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:586 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:614 msgid "Split &result" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:587 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:617 msgid "For multiple-valued fields, sho&w" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:588 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:618 msgid "values starting a&t" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:589 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:619 msgid "with values separated b&y" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:590 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:620 msgid "" "Used when displaying test results to separate values in multiple-valued " "fields" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:591 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:621 msgid "Test text" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:592 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:622 msgid "Test result" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:593 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:623 msgid "Your test:" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:594 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:624 msgid "&Search and replace" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single.py:98 -#: /home/kovid/work/calibre/src/calibre/gui2/metadata/basic_widgets.py:429 -msgid "Last modified: %s" -msgstr "" - -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single.py:122 -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single.py:128 -#: /home/kovid/work/calibre/src/calibre/gui2/metadata/single.py:252 -#: /home/kovid/work/calibre/src/calibre/gui2/metadata/single.py:259 -msgid "Could not read cover" -msgstr "" - -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single.py:123 -#: /home/kovid/work/calibre/src/calibre/gui2/metadata/single.py:253 -msgid "Could not read cover from %s format" -msgstr "" - -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single.py:129 -#: /home/kovid/work/calibre/src/calibre/gui2/metadata/single.py:260 -msgid "The cover in the %s format is invalid" -msgstr "" - -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single.py:158 -#: /home/kovid/work/calibre/src/calibre/gui2/metadata/basic_widgets.py:746 -msgid "Cover size: %dx%d pixels" -msgstr "" - -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single.py:195 -#: /home/kovid/work/calibre/src/calibre/gui2/metadata/basic_widgets.py:670 -msgid "Not a valid picture" -msgstr "" - -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single.py:214 -#: /home/kovid/work/calibre/src/calibre/gui2/metadata/basic_widgets.py:697 -msgid "Specify title and author" -msgstr "" - -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single.py:215 -#: /home/kovid/work/calibre/src/calibre/gui2/metadata/basic_widgets.py:698 -msgid "You must specify a title and author before generating a cover" -msgstr "" - -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single.py:246 -msgid "Downloading cover..." -msgstr "Lejupielādē vāku..." - -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single.py:262 -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single.py:267 -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single.py:273 -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single.py:278 -msgid "Cannot fetch cover" -msgstr "" - -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single.py:263 -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single.py:274 -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single.py:279 -msgid "Could not fetch cover.
      " -msgstr "" - -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single.py:264 -msgid "The download timed out." -msgstr "Lejupielādes noildze." - -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single.py:268 -msgid "Could not find cover for this book. Try specifying the ISBN first." -msgstr "Šai grāmatai nevar atrast vāku. Mēģiniet norādīt ISBN." - -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single.py:280 -msgid "" -"For the error message from each cover source, click Show details below." -msgstr "" - -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single.py:287 -msgid "Bad cover" -msgstr "Slikts vāks" - -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single.py:288 -msgid "The cover is not a valid picture" -msgstr "" - -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single.py:307 -#: /home/kovid/work/calibre/src/calibre/gui2/metadata/basic_widgets.py:527 -msgid "Choose formats for " -msgstr "Izvēlieties formātus " - -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single.py:338 -#: /home/kovid/work/calibre/src/calibre/gui2/metadata/basic_widgets.py:559 -msgid "No permission" -msgstr "" - -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single.py:339 -#: /home/kovid/work/calibre/src/calibre/gui2/metadata/basic_widgets.py:560 -msgid "You do not have permission to read the following files:" -msgstr "" - -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single.py:366 -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single.py:367 -#: /home/kovid/work/calibre/src/calibre/gui2/metadata/basic_widgets.py:591 -#: /home/kovid/work/calibre/src/calibre/gui2/metadata/basic_widgets.py:592 -msgid "No format selected" -msgstr "" - -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single.py:378 -#: /home/kovid/work/calibre/src/calibre/gui2/metadata/basic_widgets.py:603 -msgid "Could not read metadata" -msgstr "" - -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single.py:379 -#: /home/kovid/work/calibre/src/calibre/gui2/metadata/basic_widgets.py:604 -msgid "Could not read metadata from %s format" -msgstr "" - -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single.py:453 -#: /home/kovid/work/calibre/src/calibre/gui2/metadata/basic_widgets.py:229 -msgid "" -" The green color indicates that the current author sort matches the current " -"author" -msgstr "" - -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single.py:456 -#: /home/kovid/work/calibre/src/calibre/gui2/metadata/basic_widgets.py:232 -msgid "" -" The red color indicates that the current author sort does not match the " -"current author. No action is required if this is what you want." -msgstr "" - -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single.py:463 -#: /home/kovid/work/calibre/src/calibre/gui2/metadata/basic_widgets.py:119 -msgid "" -" The green color indicates that the current title sort matches the current " -"title" -msgstr "" - -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single.py:466 -#: /home/kovid/work/calibre/src/calibre/gui2/metadata/basic_widgets.py:122 -msgid "" -" The red color warns that the current title sort does not match the current " -"title. No action is required if this is what you want." -msgstr "" - -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single.py:472 -#: /home/kovid/work/calibre/src/calibre/gui2/metadata/single.py:49 -#: /home/kovid/work/calibre/src/calibre/library/server/browse.py:102 -#: /home/kovid/work/calibre/src/calibre/web/feeds/templates.py:221 -#: /home/kovid/work/calibre/src/calibre/web/feeds/templates.py:384 -msgid "Previous" -msgstr "" - -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single.py:475 -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single.py:483 -#: /home/kovid/work/calibre/src/calibre/gui2/metadata/single.py:358 -#: /home/kovid/work/calibre/src/calibre/gui2/metadata/single.py:362 -msgid "Save changes and edit the metadata of %s" -msgstr "" - -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single.py:480 -#: /home/kovid/work/calibre/src/calibre/gui2/metadata/single.py:46 -#: /home/kovid/work/calibre/src/calibre/library/server/browse.py:103 -#: /home/kovid/work/calibre/src/calibre/web/feeds/templates.py:211 -#: /home/kovid/work/calibre/src/calibre/web/feeds/templates.py:401 -msgid "Next" -msgstr "Nākamais" - -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single.py:688 -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single.py:693 -#: /home/kovid/work/calibre/src/calibre/gui2/metadata/basic_widgets.py:913 -msgid "This ISBN number is valid" -msgstr "" - -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single.py:696 -#: /home/kovid/work/calibre/src/calibre/gui2/metadata/basic_widgets.py:920 -msgid "This ISBN number is invalid" -msgstr "" - -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single.py:781 -#: /home/kovid/work/calibre/src/calibre/gui2/metadata/basic_widgets.py:862 -msgid "Tags changed" -msgstr "" - -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single.py:782 -#: /home/kovid/work/calibre/src/calibre/gui2/metadata/basic_widgets.py:863 -msgid "" -"You have changed the tags. In order to use the tags editor, you must either " -"discard or apply these changes. Apply changes?" -msgstr "" - -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single.py:817 -msgid "Timed out" -msgstr "" - -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single.py:818 -msgid "" -"The download of social metadata timed out, the servers are probably busy. " -"Try again later." -msgstr "" - -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single.py:825 -msgid "There were errors" -msgstr "" - -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single.py:826 -msgid "There were errors downloading social metadata" -msgstr "" - -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single.py:860 -msgid "Cannot fetch metadata" -msgstr "" - -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single.py:861 -msgid "You must specify at least one of ISBN, Title, Authors or Publisher" -msgstr "" - -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single.py:959 -#: /home/kovid/work/calibre/src/calibre/gui2/metadata/single.py:307 -msgid "Permission denied" -msgstr "" - -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single.py:960 -#: /home/kovid/work/calibre/src/calibre/gui2/metadata/single.py:308 -msgid "Could not open %s. Is it being used by another program?" -msgstr "" - -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single_ui.py:406 -msgid "Edit Meta Information" -msgstr "Rediģēt metadatus" - -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single_ui.py:407 -msgid "Meta information" -msgstr "Metadati" - -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single_ui.py:410 -#: /home/kovid/work/calibre/src/calibre/gui2/metadata/single.py:89 -msgid "" -"Automatically create the title sort entry based on the current title entry.\n" -"Using this button to create title sort will change title sort from red to " -"green." -msgstr "" - -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single_ui.py:413 -#: /home/kovid/work/calibre/src/calibre/gui2/metadata/single.py:111 -msgid "Swap the author and title" -msgstr "Mainīt vietām autoru un nosaukumu" - -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single_ui.py:415 -#: /home/kovid/work/calibre/src/calibre/gui2/metadata/single.py:100 -msgid "" -"Automatically create the author sort entry based on the current author " -"entry.\n" -"Using this button to create author sort will change author sort from red to " -"green." -msgstr "" - -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single_ui.py:418 -msgid "Title &sort: " -msgstr "" - -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single_ui.py:419 -#: /home/kovid/work/calibre/src/calibre/gui2/metadata/basic_widgets.py:109 -msgid "" -"Specify how this book should be sorted when by title. For example, The " -"Exorcist might be sorted as Exorcist, The." -msgstr "" - -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single_ui.py:421 -msgid "Author S&ort: " -msgstr "" - -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single_ui.py:422 -#: /home/kovid/work/calibre/src/calibre/gui2/metadata/basic_widgets.py:215 -msgid "" -"Specify how the author(s) of this book should be sorted. For example Charles " -"Dickens should be sorted as Dickens, Charles.\n" -"If the box is colored green, then text matches the individual author's sort " -"strings. If it is colored red, then the authors and this text do not match." -msgstr "" - -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single_ui.py:436 -#: /home/kovid/work/calibre/src/calibre/gui2/metadata/single.py:118 -msgid "Remove unused series (Series that have no books)" -msgstr "" - -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single_ui.py:439 -#: /home/kovid/work/calibre/src/calibre/gui2/metadata/basic_widgets.py:886 -msgid "IS&BN:" -msgstr "IS&BN:" - -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single_ui.py:441 -msgid "dd MMM yyyy" -msgstr "yyyy.MM.dd" - -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single_ui.py:442 -#: /home/kovid/work/calibre/src/calibre/gui2/metadata/basic_widgets.py:1029 -msgid "Publishe&d:" -msgstr "I&zdota:" - -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single_ui.py:445 -#: /home/kovid/work/calibre/src/calibre/gui2/metadata/single.py:156 -msgid "&Fetch metadata from server" -msgstr "Lejupielādēt &metdatus" - -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single_ui.py:448 -#: /home/kovid/work/calibre/src/calibre/gui2/metadata/basic_widgets.py:621 -msgid "&Browse" -msgstr "&Pārlūkot" - -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single_ui.py:449 -msgid "Remove border (if any) from cover" -msgstr "" - -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single_ui.py:450 -#: /home/kovid/work/calibre/src/calibre/gui2/metadata/basic_widgets.py:623 -msgid "T&rim" -msgstr "" - -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single_ui.py:451 -msgid "Reset cover to default" -msgstr "" - -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single_ui.py:452 -#: /home/kovid/work/calibre/src/calibre/gui2/metadata/basic_widgets.py:625 -msgid "&Remove" -msgstr "" - -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single_ui.py:453 -#: /home/kovid/work/calibre/src/calibre/gui2/metadata/basic_widgets.py:631 -msgid "Download co&ver" -msgstr "" - -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single_ui.py:454 -msgid "Generate a default cover based on the title and author" -msgstr "" - -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single_ui.py:455 -#: /home/kovid/work/calibre/src/calibre/gui2/metadata/basic_widgets.py:632 -msgid "&Generate cover" -msgstr "" - -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single_ui.py:456 -msgid "Available Formats" -msgstr "Pieejamie formāti" - -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single_ui.py:457 -msgid "Add a new format for this book to the database" -msgstr "" - -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single_ui.py:459 -msgid "Remove the selected formats for this book from the database." -msgstr "" - -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single_ui.py:461 -#: /home/kovid/work/calibre/src/calibre/gui2/metadata/basic_widgets.py:446 -msgid "Set the cover for the book from the selected format" -msgstr "Uzstāda vāka attēlu izvēlētā formāta grāmatai" - -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single_ui.py:463 -msgid "Update metadata from the metadata in the selected format" -msgstr "" - -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single_ui.py:464 -#: /home/kovid/work/calibre/src/calibre/gui2/metadata/single.py:580 -msgid "&Comments" -msgstr "&Kometāri" - #: /home/kovid/work/calibre/src/calibre/gui2/dialogs/password_ui.py:61 msgid "Password needed" msgstr "Nepieciešama parole" #: /home/kovid/work/calibre/src/calibre/gui2/dialogs/password_ui.py:63 -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/scheduler_ui.py:217 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/scheduler_ui.py:205 #: /home/kovid/work/calibre/src/calibre/gui2/preferences/server_ui.py:125 #: /home/kovid/work/calibre/src/calibre/gui2/wizard/send_email_ui.py:133 msgid "&Username:" msgstr "" #: /home/kovid/work/calibre/src/calibre/gui2/dialogs/password_ui.py:64 -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/scheduler_ui.py:218 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/scheduler_ui.py:206 #: /home/kovid/work/calibre/src/calibre/gui2/preferences/server_ui.py:126 #: /home/kovid/work/calibre/src/calibre/gui2/wizard/send_email_ui.py:135 msgid "&Password:" msgstr "" #: /home/kovid/work/calibre/src/calibre/gui2/dialogs/password_ui.py:65 -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/scheduler_ui.py:219 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/scheduler_ui.py:207 #: /home/kovid/work/calibre/src/calibre/gui2/preferences/server_ui.py:130 -#: /home/kovid/work/calibre/src/calibre/gui2/wizard/send_email.py:172 +#: /home/kovid/work/calibre/src/calibre/gui2/wizard/send_email.py:173 msgid "&Show password" msgstr "" @@ -8524,210 +8416,295 @@ msgstr "" msgid "Restoring database was successful" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/saved_search_editor.py:55 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/saved_search_editor.py:48 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/saved_search_editor.py:75 +msgid "Saved search already exists" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/saved_search_editor.py:49 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/saved_search_editor.py:76 +msgid "The saved search %s already exists, perhaps with different case" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/saved_search_editor.py:62 msgid "" "The current saved search will be permanently deleted. Are you sure?" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/saved_search_editor_ui.py:88 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/saved_search_editor_ui.py:94 msgid "Saved Search Editor" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/saved_search_editor_ui.py:89 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/saved_search_editor_ui.py:95 msgid "Saved Search: " msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/saved_search_editor_ui.py:90 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/saved_search_editor_ui.py:96 msgid "Select a saved search to edit" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/saved_search_editor_ui.py:91 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/saved_search_editor_ui.py:97 msgid "Delete this selected saved search" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/saved_search_editor_ui.py:93 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/saved_search_editor_ui.py:99 msgid "Enter a new saved search name." msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/saved_search_editor_ui.py:94 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/saved_search_editor_ui.py:100 msgid "Add the new saved search" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/saved_search_editor_ui.py:96 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/saved_search_editor_ui.py:102 +msgid "Rename the current search to what is in the box" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/saved_search_editor_ui.py:104 msgid "Change the contents of the saved search" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/scheduler.py:35 -msgid "&Search:" -msgstr "&Meklēt:" +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/scheduler.py:42 +msgid "" +" Download this periodical every week on the specified days " +"after\n" +" the specified time. For example, if you choose: Monday " +"after\n" +" 9:00 AM, then the periodical will be download every Monday " +"as\n" +" soon after 9:00 AM as possible.\n" +" " +msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/scheduler.py:133 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/scheduler.py:61 +msgid "&Download after:" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/scheduler.py:91 +msgid "" +" Download this periodical every month, on the specified " +"days.\n" +" The download will happen as soon after the specified time " +"as\n" +" possible on the specified days of each month. For example,\n" +" if you choose the 1st and the 15th after 9:00 AM, the\n" +" periodical will be downloaded on the 1st and 15th of every\n" +" month, as soon after 9:00 AM as possible.\n" +" " +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/scheduler.py:103 +msgid "&Days of the month:" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/scheduler.py:105 +msgid "Comma separated list of days of the month. For example: 1, 15" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/scheduler.py:109 +msgid "Download &after:" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/scheduler.py:142 +msgid "" +" Download this periodical every x days. For example, if you\n" +" choose 30 days, the periodical will be downloaded every 30\n" +" days. Note that you can set periods of less than a day, " +"like\n" +" 0.1 days to download a periodical more than once a day.\n" +" " +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/scheduler.py:151 +msgid "&Download every:" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/scheduler.py:154 +msgid "every hour" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/scheduler.py:157 +msgid "days" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/scheduler.py:161 +msgid "" +"Note: You can set intervals of less than a day, by typing the value manually." +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/scheduler.py:196 +msgid "%s news sources" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/scheduler.py:311 msgid "Need username and password" msgstr "Nepieciešams lietotājvārds un parole" -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/scheduler.py:134 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/scheduler.py:312 msgid "You must provide a username and/or password to use this news source." msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/scheduler.py:173 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/scheduler.py:346 msgid "Account" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/scheduler.py:174 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/scheduler.py:347 msgid "(optional)" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/scheduler.py:175 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/scheduler.py:348 msgid "(required)" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/scheduler.py:192 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/scheduler.py:365 msgid "Created by: " msgstr "Izveidoja: " -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/scheduler.py:199 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/scheduler.py:372 msgid "Last downloaded: never" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/scheduler.py:214 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/scheduler.py:373 +msgid "never" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/scheduler.py:379 msgid "%d days, %d hours and %d minutes ago" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/scheduler.py:216 -msgid "Last downloaded" +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/scheduler.py:393 +msgid "Last downloaded:" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/scheduler.py:240 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/scheduler.py:421 #: /home/kovid/work/calibre/src/calibre/gui2/dialogs/scheduler_ui.py:197 msgid "Schedule news download" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/scheduler.py:243 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/scheduler.py:424 msgid "Add a custom news source" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/scheduler.py:248 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/scheduler.py:429 msgid "Download all scheduled new sources" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/scheduler.py:353 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/scheduler.py:534 msgid "No internet connection" msgstr "Nav interneta savienojuma" -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/scheduler.py:354 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/scheduler.py:535 msgid "Cannot download news as no internet connection is active" msgstr "" #: /home/kovid/work/calibre/src/calibre/gui2/dialogs/scheduler_ui.py:198 -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/user_profiles.py:321 -msgid "Recipes" -msgstr "Receptes" +msgid "&Search:" +msgstr "&Meklēt:" #: /home/kovid/work/calibre/src/calibre/gui2/dialogs/scheduler_ui.py:199 -msgid "Download all scheduled recipes at once" -msgstr "" - -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/scheduler_ui.py:200 -msgid "Download &all scheduled" -msgstr "" - -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/scheduler_ui.py:201 msgid "blurb" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/scheduler_ui.py:202 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/scheduler_ui.py:200 msgid "&Schedule for download:" msgstr "" +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/scheduler_ui.py:201 +msgid "Days of week" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/scheduler_ui.py:202 +msgid "Days of month" +msgstr "" + #: /home/kovid/work/calibre/src/calibre/gui2/dialogs/scheduler_ui.py:203 -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/scheduler_ui.py:213 -msgid "Every " -msgstr "Katru " +msgid "Every x days" +msgstr "" #: /home/kovid/work/calibre/src/calibre/gui2/dialogs/scheduler_ui.py:204 -msgid "day" -msgstr "dienu" - -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/scheduler_ui.py:205 -msgid "Monday" -msgstr "pirmdienu" - -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/scheduler_ui.py:206 -msgid "Tuesday" -msgstr "otrdienu" - -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/scheduler_ui.py:207 -msgid "Wednesday" -msgstr "trešdienu" +msgid "&Account" +msgstr "" #: /home/kovid/work/calibre/src/calibre/gui2/dialogs/scheduler_ui.py:208 -msgid "Thursday" -msgstr "ceturtdienu" +msgid "For the scheduling to work, you must leave calibre running." +msgstr "" #: /home/kovid/work/calibre/src/calibre/gui2/dialogs/scheduler_ui.py:209 -msgid "Friday" -msgstr "piektdienu" +msgid "&Schedule" +msgstr "" #: /home/kovid/work/calibre/src/calibre/gui2/dialogs/scheduler_ui.py:210 -msgid "Saturday" -msgstr "sestdienu" +msgid "Add &title as tag" +msgstr "" #: /home/kovid/work/calibre/src/calibre/gui2/dialogs/scheduler_ui.py:211 -msgid "Sunday" -msgstr "svētdienu" +msgid "&Extra tags:" +msgstr "" #: /home/kovid/work/calibre/src/calibre/gui2/dialogs/scheduler_ui.py:212 -msgid "at" +msgid "" +"Maximum number of copies (issues) of this recipe to keep. Set to 0 to keep " +"all (disable)." +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/scheduler_ui.py:213 +msgid "&Keep at most:" msgstr "" #: /home/kovid/work/calibre/src/calibre/gui2/dialogs/scheduler_ui.py:214 msgid "" -"Interval at which to download this recipe. A value of zero means that the " -"recipe will be downloaded every hour." +"

      When set, this option will cause calibre to keep, at most, the specified " +"number of issues of this periodical. Every time a new issue is downloaded, " +"the oldest one is deleted, if the total is larger than this number.\n" +"

      Note that this feature only works if you have the option to add the title " +"as tag checked, above.\n" +"

      Also, the setting for deleting periodicals older than a number of days, " +"below, takes priority over this setting." msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/scheduler_ui.py:215 -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/scheduler_ui.py:227 -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/user_profiles_ui.py:268 -msgid " days" +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/scheduler_ui.py:217 +msgid "all issues" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/scheduler_ui.py:216 -msgid "&Account" +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/scheduler_ui.py:218 +msgid " issues" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/scheduler_ui.py:220 -msgid "For the scheduling to work, you must leave calibre running." -msgstr "" - -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/scheduler_ui.py:221 -msgid "&Schedule" -msgstr "" - -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/scheduler_ui.py:222 -msgid "Add &title as tag" -msgstr "" - -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/scheduler_ui.py:223 -msgid "&Extra tags:" -msgstr "" - -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/scheduler_ui.py:224 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/scheduler_ui.py:219 msgid "&Advanced" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/scheduler_ui.py:225 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/scheduler_ui.py:220 msgid "&Download now" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/scheduler_ui.py:226 -msgid "" -"Delete downloaded news older than the specified number of days. Set to zero " -"to disable." +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/scheduler_ui.py:221 +msgid "&Delete downloaded news older than:" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/scheduler_ui.py:228 -msgid "Delete downloaded news older than " +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/scheduler_ui.py:222 +msgid "" +"

      Delete downloaded news older than the specified number of days. Set to " +"zero to disable.\n" +"

      You can also control the maximum number of issues of a specific " +"periodical that are kept by clicking the Advanced tab for that periodical " +"above." +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/scheduler_ui.py:224 +msgid "never delete" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/scheduler_ui.py:225 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/user_profiles_ui.py:273 +msgid " days" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/scheduler_ui.py:226 +msgid "Download all scheduled news sources at once" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/scheduler_ui.py:227 +msgid "Download &all scheduled" msgstr "" #: /home/kovid/work/calibre/src/calibre/gui2/dialogs/search_item_ui.py:41 @@ -8749,70 +8726,85 @@ msgid "Negate" msgstr "" #: /home/kovid/work/calibre/src/calibre/gui2/dialogs/search_ui.py:198 +#: /home/kovid/work/calibre/src/calibre/gui2/store/search/adv_search_builder_ui.py:176 msgid "Advanced Search" msgstr "Paplašinātā meklēšana" #: /home/kovid/work/calibre/src/calibre/gui2/dialogs/search_ui.py:199 +#: /home/kovid/work/calibre/src/calibre/gui2/store/search/adv_search_builder_ui.py:177 msgid "&What kind of match to use:" msgstr "" #: /home/kovid/work/calibre/src/calibre/gui2/dialogs/search_ui.py:200 +#: /home/kovid/work/calibre/src/calibre/gui2/store/search/adv_search_builder_ui.py:178 msgid "Contains: the word or phrase matches anywhere in the metadata field" msgstr "" #: /home/kovid/work/calibre/src/calibre/gui2/dialogs/search_ui.py:201 +#: /home/kovid/work/calibre/src/calibre/gui2/store/search/adv_search_builder_ui.py:179 msgid "Equals: the word or phrase must match the entire metadata field" msgstr "" #: /home/kovid/work/calibre/src/calibre/gui2/dialogs/search_ui.py:202 +#: /home/kovid/work/calibre/src/calibre/gui2/store/search/adv_search_builder_ui.py:180 msgid "" "Regular expression: the expression must match anywhere in the metadata field" msgstr "" #: /home/kovid/work/calibre/src/calibre/gui2/dialogs/search_ui.py:203 +#: /home/kovid/work/calibre/src/calibre/gui2/store/search/adv_search_builder_ui.py:181 msgid "Find entries that have..." msgstr "" #: /home/kovid/work/calibre/src/calibre/gui2/dialogs/search_ui.py:204 +#: /home/kovid/work/calibre/src/calibre/gui2/store/search/adv_search_builder_ui.py:182 msgid "&All these words:" msgstr "" #: /home/kovid/work/calibre/src/calibre/gui2/dialogs/search_ui.py:205 +#: /home/kovid/work/calibre/src/calibre/gui2/store/search/adv_search_builder_ui.py:183 msgid "This exact &phrase:" msgstr "" #: /home/kovid/work/calibre/src/calibre/gui2/dialogs/search_ui.py:206 +#: /home/kovid/work/calibre/src/calibre/gui2/store/search/adv_search_builder_ui.py:184 msgid "&One or more of these words:" msgstr "" #: /home/kovid/work/calibre/src/calibre/gui2/dialogs/search_ui.py:207 +#: /home/kovid/work/calibre/src/calibre/gui2/store/search/adv_search_builder_ui.py:185 msgid "But dont show entries that have..." msgstr "Bet nerādīt ierakstus, kuriem..." #: /home/kovid/work/calibre/src/calibre/gui2/dialogs/search_ui.py:208 +#: /home/kovid/work/calibre/src/calibre/gui2/store/search/adv_search_builder_ui.py:186 msgid "Any of these &unwanted words:" msgstr "" #: /home/kovid/work/calibre/src/calibre/gui2/dialogs/search_ui.py:209 +#: /home/kovid/work/calibre/src/calibre/gui2/store/search/adv_search_builder_ui.py:187 msgid "" "See the User Manual for more help" msgstr "" #: /home/kovid/work/calibre/src/calibre/gui2/dialogs/search_ui.py:210 +#: /home/kovid/work/calibre/src/calibre/gui2/store/search/adv_search_builder_ui.py:188 msgid "A&dvanced Search" msgstr "" #: /home/kovid/work/calibre/src/calibre/gui2/dialogs/search_ui.py:212 +#: /home/kovid/work/calibre/src/calibre/gui2/store/search/adv_search_builder_ui.py:190 msgid "Enter the title." msgstr "" #: /home/kovid/work/calibre/src/calibre/gui2/dialogs/search_ui.py:213 +#: /home/kovid/work/calibre/src/calibre/gui2/store/search/adv_search_builder_ui.py:191 msgid "&Author:" msgstr "" #: /home/kovid/work/calibre/src/calibre/gui2/dialogs/search_ui.py:215 -#: /home/kovid/work/calibre/src/calibre/gui2/metadata/basic_widgets.py:827 +#: /home/kovid/work/calibre/src/calibre/gui2/metadata/basic_widgets.py:908 msgid "Ta&gs:" msgstr "" @@ -8831,10 +8823,12 @@ msgstr "" #: /home/kovid/work/calibre/src/calibre/gui2/dialogs/search_ui.py:219 #: /home/kovid/work/calibre/src/calibre/gui2/preferences/template_functions_ui.py:101 +#: /home/kovid/work/calibre/src/calibre/gui2/store/search/adv_search_builder_ui.py:193 msgid "&Clear" msgstr "" #: /home/kovid/work/calibre/src/calibre/gui2/dialogs/search_ui.py:220 +#: /home/kovid/work/calibre/src/calibre/gui2/store/search/adv_search_builder_ui.py:194 msgid "Search only in specific fields:" msgstr "" @@ -8847,31 +8841,48 @@ msgid "Choose formats" msgstr "" #: /home/kovid/work/calibre/src/calibre/gui2/dialogs/tag_categories.py:60 -#: /home/kovid/work/calibre/src/calibre/library/field_metadata.py:98 +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/metadata_sources.py:146 +#: /home/kovid/work/calibre/src/calibre/library/field_metadata.py:102 msgid "Authors" msgstr "Autori" #: /home/kovid/work/calibre/src/calibre/gui2/dialogs/tag_categories.py:60 -#: /home/kovid/work/calibre/src/calibre/library/field_metadata.py:129 +#: /home/kovid/work/calibre/src/calibre/library/field_metadata.py:136 msgid "Publishers" msgstr "Izdevēji" -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/tag_categories.py:128 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/tag_categories.py:143 msgid " (not on any book)" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/tag_categories.py:175 -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/tag_categories.py:197 -#: /home/kovid/work/calibre/src/calibre/gui2/preferences/template_functions.py:151 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/tag_categories.py:146 +msgid "Category lookup name: " +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/tag_categories.py:191 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/tag_categories.py:222 +msgid "Invalid name" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/tag_categories.py:192 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/tag_categories.py:223 +msgid "" +"That name contains leading or trailing periods, multiple periods in a row or " +"spaces before or after periods." +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/tag_categories.py:200 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/tag_categories.py:230 +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/template_functions.py:152 msgid "Name already used" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/tag_categories.py:176 -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/tag_categories.py:198 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/tag_categories.py:201 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/tag_categories.py:231 msgid "That name is already used, perhaps with different case." msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/tag_categories.py:211 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/tag_categories.py:244 msgid "" "The current tag category will be permanently deleted. Are you sure?" msgstr "" @@ -8929,7 +8940,7 @@ msgid "Unapply (remove) tag from current tag category" msgstr "" #: /home/kovid/work/calibre/src/calibre/gui2/dialogs/tag_editor.py:70 -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/tag_list_editor.py:109 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/tag_list_editor.py:111 msgid "Are your sure?" msgstr "Vai esat pārliecināts?" @@ -8983,33 +8994,33 @@ msgstr "" msgid "%s (was %s)" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/tag_list_editor.py:83 -#: /home/kovid/work/calibre/src/calibre/gui2/tag_view.py:906 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/tag_list_editor.py:85 +#: /home/kovid/work/calibre/src/calibre/gui2/tag_view.py:1385 msgid "Item is blank" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/tag_list_editor.py:84 -#: /home/kovid/work/calibre/src/calibre/gui2/tag_view.py:907 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/tag_list_editor.py:86 +#: /home/kovid/work/calibre/src/calibre/gui2/tag_view.py:1386 msgid "An item cannot be set to nothing. Delete it instead." msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/tag_list_editor.py:97 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/tag_list_editor.py:99 msgid "No item selected" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/tag_list_editor.py:98 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/tag_list_editor.py:100 msgid "You must select one item from the list of Available items." msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/tag_list_editor.py:105 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/tag_list_editor.py:107 msgid "No items selected" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/tag_list_editor.py:106 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/tag_list_editor.py:108 msgid "You must select at least one items from the list." msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/tag_list_editor.py:110 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/tag_list_editor.py:112 msgid "Are you certain you want to delete the following items?" msgstr "" @@ -9058,28 +9069,16 @@ msgid "Send test mail from %s to:" msgstr "" #: /home/kovid/work/calibre/src/calibre/gui2/dialogs/test_email_ui.py:58 -#: /home/kovid/work/calibre/src/calibre/gui2/filename_pattern_ui.py:120 +#: /home/kovid/work/calibre/src/calibre/gui2/filename_pattern_ui.py:134 msgid "&Test" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/tweak_epub_ui.py:55 -msgid "Display contents of exploded ePub" +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/tweak_epub.py:100 +msgid "Cannot preview" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/tweak_epub_ui.py:56 -msgid "&Explode ePub" -msgstr "" - -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/tweak_epub_ui.py:57 -msgid "Rebuild ePub from exploded contents" -msgstr "" - -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/tweak_epub_ui.py:58 -msgid "&Rebuild ePub" -msgstr "" - -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/tweak_epub_ui.py:59 -msgid "Discard changes" +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/tweak_epub.py:101 +msgid "You must first explode the epub before previewing." msgstr "" #: /home/kovid/work/calibre/src/calibre/gui2/dialogs/tweak_epub_ui.py:61 @@ -9091,116 +9090,148 @@ msgid "" "updating your calibre library.

      " msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/user_profiles.py:133 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/tweak_epub_ui.py:62 +msgid "Display contents of exploded ePub" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/tweak_epub_ui.py:63 +msgid "&Explode ePub" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/tweak_epub_ui.py:64 +msgid "Discard changes" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/tweak_epub_ui.py:66 +msgid "Rebuild ePub from exploded contents" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/tweak_epub_ui.py:67 +msgid "&Rebuild ePub" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/tweak_epub_ui.py:68 +msgid "&Preview ePub" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/user_profiles.py:141 msgid "No recipe selected" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/user_profiles.py:138 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/user_profiles.py:146 msgid "The attached file: %s is a recipe to download %s." msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/user_profiles.py:139 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/user_profiles.py:147 msgid "Recipe for " msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/user_profiles.py:156 -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/user_profiles.py:167 -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/user_profiles_ui.py:260 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/user_profiles.py:164 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/user_profiles.py:175 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/user_profiles_ui.py:265 msgid "Switch to Advanced mode" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/user_profiles.py:162 #: /home/kovid/work/calibre/src/calibre/gui2/dialogs/user_profiles.py:170 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/user_profiles.py:178 msgid "Switch to Basic mode" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/user_profiles.py:180 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/user_profiles.py:188 msgid "Feed must have a title" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/user_profiles.py:181 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/user_profiles.py:189 msgid "The feed must have a title" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/user_profiles.py:185 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/user_profiles.py:193 msgid "Feed must have a URL" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/user_profiles.py:186 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/user_profiles.py:194 msgid "The feed %s must have a URL" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/user_profiles.py:192 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/user_profiles.py:200 msgid "This feed has already been added to the recipe" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/user_profiles.py:233 -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/user_profiles.py:242 -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/user_profiles.py:329 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/user_profiles.py:241 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/user_profiles.py:250 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/user_profiles.py:337 msgid "Invalid input" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/user_profiles.py:234 -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/user_profiles.py:243 -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/user_profiles.py:330 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/user_profiles.py:242 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/user_profiles.py:251 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/user_profiles.py:338 msgid "

      Could not create recipe. Error:
      %s" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/user_profiles.py:247 -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/user_profiles.py:306 -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/user_profiles.py:333 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/user_profiles.py:255 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/user_profiles.py:314 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/user_profiles.py:341 msgid "Replace recipe?" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/user_profiles.py:248 -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/user_profiles.py:307 -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/user_profiles.py:334 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/user_profiles.py:256 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/user_profiles.py:315 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/user_profiles.py:342 msgid "A custom recipe named %s already exists. Do you want to replace it?" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/user_profiles.py:274 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/user_profiles.py:282 msgid "Choose builtin recipe" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/user_profiles.py:320 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/user_profiles.py:328 msgid "Choose a recipe file" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/user_profiles.py:361 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/user_profiles.py:329 +msgid "Recipes" +msgstr "Receptes" + +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/user_profiles.py:369 msgid "" "You will lose any unsaved changes. To save your changes, click the " "Add/Update recipe button. Continue?" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/user_profiles_ui.py:253 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/user_profiles_ui.py:257 msgid "Add custom news source" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/user_profiles_ui.py:254 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/user_profiles_ui.py:258 msgid "Available user recipes" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/user_profiles_ui.py:255 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/user_profiles_ui.py:259 msgid "Add/Update &recipe" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/user_profiles_ui.py:256 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/user_profiles_ui.py:260 msgid "&Remove recipe" 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:261 msgid "&Share recipe" 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:262 +msgid "S&how recipe files" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/user_profiles_ui.py:263 msgid "Customize &builtin recipe" 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:264 msgid "&Load recipe from file" 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:266 msgid "" "\n" -#~ "

      Set a regular expression " -#~ "pattern to use when trying to guess ebook metadata from filenames.

      \n" -#~ "

      A reference on the syntax " -#~ "of regular expressions is available.

      \n" -#~ "

      Use the Test functionality below to test your regular " -#~ "expression on a few sample filenames. The group names for the various " -#~ "metadata entries are documented in tooltips.

      " -#~ msgstr "" -#~ "\n" -#~ "\n" -#~ "

      Geef een reguliere " -#~ "expressie patroon op om te gebruiken bij het raden van eboek metagegevens " -#~ "gebaseerd op bestandsnamen.

      \n" -#~ "

      Een referentie betreffende de " -#~ "syntax voor reguliere expressies is beschikbaar.

      \n" -#~ "

      Gebruik de Test functionaliteit hieronder om de reguliere " -#~ "expressie uit te proberen op een aantal test bestandsnamen. De groepnamen " -#~ "voor de diverse metadata velden zijn gedocumenteerd onder " -#~ "tooltips.

      " - #~ msgid "" #~ "The following books had formats listed in the database that are not actually " #~ "available. The entries for the formats have been removed. You should check " @@ -18704,23 +21128,1316 @@ msgstr "Download geen CSS stylesheets" #~ "verwijderd. Je zou ze handmatig moeten controleren. Dit kan ontstaan als je " #~ "bestanden wijzigt in de mappen zelf." +#~ msgid "Downloads metadata from french Nicebooks" +#~ msgstr "Download metagegevens van de franse Nicebooks" + +#~ msgid "Downloads covers from french Nicebooks" +#~ msgstr "Download omslagen van de franse Nicebooks" + #~ msgid "No details available" #~ msgstr "Geen details beschikbaar" #~ msgid "Communicate with the PocketBook 602 reader." #~ msgstr "Communiceer met de PocketBook 602 reader." +#~ msgid "Downloads metadata from amazon.fr" +#~ msgstr "Downloadt metagegevens van amazon.fr" + +#~ msgid "Downloads metadata from amazon.com in spanish" +#~ msgstr "Downloadt metagegevens van amazon.com in het Spaans" + +#~ msgid "Downloads metadata from amazon.com in english" +#~ msgstr "Downloadt metagegevens van amazon.com in het Engels" + +#~ msgid "Downloads metadata from amazon.com" +#~ msgstr "Downloadt metagegevens van amazon.com" + +#~ msgid "Downloads metadata from amazon.de" +#~ msgstr "Downloadt metagegevens van amazon.de" + +#~ msgid "Fictionwise encountered an error." +#~ msgstr "Fictionwise kwam een fout tegen." + +#~ msgid "Failed to get all details for an entry" +#~ msgstr "Niet gelukt om alle details te krijgen voor een opgave" + +#~ msgid "Downloads metadata from Fictionwise" +#~ msgstr "Download metagegevens van Fictionwise" + +#~ msgid "Query: %s" +#~ msgstr "Vraag: %s" + +#~ msgid "Keywords" +#~ msgstr "Sleutelwoorden" + +#~ msgid "Be more verbose about errors" +#~ msgstr "Wees duidelijker over fouten" + +#~ msgid "ISBN: %s not found." +#~ msgstr "ISBN: %s niet gevonden." + +#~ msgid "No result found for this search!" +#~ msgstr "Geen resultaten gevonden!" + #~ msgid "Unknown publisher" #~ msgstr "Onbekende uitgever" +#~ msgid "No cover found!" +#~ msgstr "Geen omslag gevonden!" + +#~ msgid "Covers: 1-Check/ 2-Download" +#~ msgstr "Omslagen: 1-Controleer/ 2-Download" + +#~ msgid "A cover was found for this book" +#~ msgstr "Er is een omslag gevonden voor dit boek" + +#~ msgid "Cover saved to file " +#~ msgstr "Omslag als bestand opgeslagen " + #~ msgid "Books marked as read:" #~ msgstr "Boeken gemarkeerd als gelezen:" #~ msgid "Do not change" #~ msgstr "Niet wijzigen" +#~ msgid "Has Cover" +#~ msgstr "Heeft Omslag" + #~ msgid "Missing covers in books" #~ msgstr "Mist omslagen in boeken" #~ msgid "Extra covers in books" #~ msgstr "Extra omslagen in boeken" + +#~ msgid "Customize the toolbar" +#~ msgstr "Gereedschapsbalk aanpassen" + +#~ msgid "Customize searching" +#~ msgstr "Zoekmethode aanpassen" + +#~ msgid "Download covers from amazon.com" +#~ msgstr "Download omslagen van amazon.com" + +#~ msgid "Downloads metadata from Amazon" +#~ msgstr "Download metadata van Amazon" + +#~ msgid "Downloading {0} for {1} book(s)" +#~ msgstr "Bezig met downloaden boek {0} van de {1} boek(en)" + +#~ msgid "No valid plugin found in " +#~ msgstr "Geen geldige plug-in gevonden in " + +#~ msgid "" +#~ " %prog [options]\n" +#~ "\n" +#~ " Fetch book metadata from Amazon. You must specify one of title, " +#~ "author,\n" +#~ " ISBN, publisher or keywords. Will fetch a maximum of 10 matches,\n" +#~ " so you should make your query as specific as possible.\n" +#~ " You can chose the language for metadata retrieval:\n" +#~ " All & english & french & german & spanish\n" +#~ " " +#~ msgstr "" +#~ " %prog [options]\n" +#~ "\n" +#~ " Haal metagegevens van Amazon. Je moet ofwel titel, auteur,\n" +#~ " ISBN, uitgever of sleutelwoorden opgeven. Zal een maximum van 10 " +#~ "treffers ophalen,\n" +#~ " dus je moet je zoeksleutel zo specifiek mogelijk maken.\n" +#~ " Je kunt de taal kiezen voor de metagegevens:\n" +#~ " Alle, & Engels & Frans & Duits & Spaans\n" +#~ " " + +#~ msgid "Cover download" +#~ msgstr "Download boekomslag" + +#~ msgid "tags" +#~ msgstr "labels" + +#~ msgid "" +#~ "To use isbndb.com you must sign up for a %sfree account%s and enter your " +#~ "access key below." +#~ msgstr "" +#~ "Om isbndb.com te gebruiken moet je je opgeven voor een %sgratis account%s, " +#~ "en je toegangssleutel hieronder invoeren." + +#~ msgid "" +#~ "Downloads series information from ww2.kdl.org. This website cannot handle " +#~ "large numbers of queries, so the plugin is disabled by default." +#~ msgstr "" +#~ "Download informatie over reeksen van ww2.kdl.org. Deze website kan geen " +#~ "grote hoeveelheden verkeer aan, en dus is de plug-in standaard uitgeschakeld." + +#~ msgid "Fictionwise timed out. Try again later." +#~ msgstr "Fictionwise time-out. Probeer later opnieuw." + +#~ msgid "" +#~ "SUMMARY:\n" +#~ " %s" +#~ msgstr "" +#~ "SAMENVATTING:\n" +#~ " %s" + +#~ msgid "" +#~ " %prog [options]\n" +#~ "\n" +#~ " Fetch book metadata from Fictionwise. You must specify one of title, " +#~ "author,\n" +#~ " or keywords. No ISBN specification possible. Will fetch a maximum of " +#~ "20 matches,\n" +#~ " so you should make your query as specific as possible.\n" +#~ " " +#~ msgstr "" +#~ " %prog [options]\n" +#~ "\n" +#~ " Haal metagegevens van Fictionwise. Je moet ofwel titel, auteur,\n" +#~ " uitgever of sleutelwoorden opgeven. Geen ISBN mogelijk. Zal een " +#~ "maximum van 20 treffers ophalen,\n" +#~ " dus je moet je zoeksleutel zo specifiek mogelijk maken.\n" +#~ " " + +#~ msgid "Book title" +#~ msgstr "Titel boek" + +#~ msgid "Book author(s)" +#~ msgstr "Auteur(s) boek" + +#~ msgid "Book publisher" +#~ msgstr "Uigever" + +#~ msgid "Maximum number of results to fetch" +#~ msgstr "Maximaal aantal op te halen resultaten" + +#~ msgid "" +#~ "\n" +#~ "%prog [options] key\n" +#~ "\n" +#~ "Fetch metadata for books from isndb.com. You can specify either the\n" +#~ "books ISBN ID or its title and author. If you specify the title and author,\n" +#~ "then more than one book may be returned.\n" +#~ "\n" +#~ "key is the account key you generate after signing up for a free account from " +#~ "isbndb.com.\n" +#~ "\n" +#~ msgstr "" +#~ "\n" +#~ "%prog [opties] sleutel\n" +#~ "\n" +#~ "Haal metagegevens voor boeken van isbndb.com. Je kunt ofwel het ISBN nummer " +#~ "ofwel titel en auteur opgeven.\n" +#~ "als je de titel en auteur opgeeft, dan is het mogelijk dat er meerdere " +#~ "resultaten zijn.\n" +#~ "\n" +#~ "Sleutel is de 'account key' die gegenereerd werd nadat je je inschreef voor " +#~ "een gratis account op isbndb.com.\n" +#~ "\n" + +#~ msgid "The ISBN ID of the book you want metadata for." +#~ msgstr "Het ISBN-nummer van het boek waarvoor je de metagegevens wilt." + +#~ msgid "The author whose book to search for." +#~ msgstr "De auteur van het gezochte boek." + +#~ msgid "The title of the book to search for." +#~ msgstr "De titel van het gezochte boek." + +#~ msgid "The publisher of the book to search for." +#~ msgstr "De uitgever van het gezochte boek." + +#~ msgid "Nicebooks timed out. Try again later." +#~ msgstr "Nicebooks time-out. Probeer later opnieuw." + +#~ msgid "Nicebooks encountered an error." +#~ msgstr "Nicebook is in de fout gegaan." + +#~ msgid "Book ISBN" +#~ msgstr "ISBN Boek" + +#~ msgid "Covers files path" +#~ msgstr "Bestandspad voor omslagen" + +#~ msgid "Maximum number of waiting worker processes" +#~ msgstr "Maximaal aantal wachtende werkprocessen" + +#~ msgid "Download only covers" +#~ msgstr "Download alleen omslagafbeeldingen" + +#~ msgid "covers" +#~ msgstr "Omslagafbeeldingen" + +#~ msgid "Third Expression" +#~ msgstr "Derde regexp" + +#~ msgid "Has Summary" +#~ msgstr "Heeft samenvatting" + +#~ msgid "" +#~ "Select the book that most closely matches your copy from the list below" +#~ msgstr "" +#~ "Selecteer het boek dat het best overeenkomt met dat van jou uit de lijst " +#~ "hieronder" + +#~ msgid "Overwrite author and title with author and title of selected book" +#~ msgstr "" +#~ "Overschrijf auteur en titel met de auteur en titel van het geselecteerde boek" + +#~ msgid "Last modified: %s" +#~ msgstr "Laatst aangepast op: %s" + +#~ msgid "Could not find cover for this book. Try specifying the ISBN first." +#~ msgstr "" +#~ "Kan geen omslagafbeelding voor dit boek vinden. Probeer eerst het ISBN-" +#~ "nummer op te geven." + +#~ msgid "Bad cover" +#~ msgstr "Ongeldige omslagafbeelding" + +#~ msgid "The cover is not a valid picture" +#~ msgstr "De omslag is geen geldige afbeelding" + +#~ msgid "" +#~ "The download of social metadata timed out, the servers are probably busy. " +#~ "Try again later." +#~ msgstr "" +#~ "Time-out bij het downloaden van sociale metagegevens; de servers zijn " +#~ "waarschijnlijk overbelast. Probeer later nog eens." + +#~ msgid "Edit Meta Information" +#~ msgstr "Verander Meta-informatie" + +#~ msgid "Meta information" +#~ msgstr "Meta-informatie" + +#~ msgid "Author S&ort: " +#~ msgstr "S&orteerauteur: " + +#~ msgid "Remove border (if any) from cover" +#~ msgstr "Verwijder rand (als die er is) van omslag" + +#~ msgid "Reset cover to default" +#~ msgstr "Vervang omslagafbeelding door de standaardafbeelding" + +#~ msgid "Generate a default cover based on the title and author" +#~ msgstr "Genereer een standaardomslag aan de hand van de titel en auteur" + +#~ msgid "Remove the selected formats for this book from the database." +#~ msgstr "Verwijder de geselecteerde formaten voor dit boek uit de database." + +#~ msgid "Update metadata from the metadata in the selected format" +#~ msgstr "" +#~ "Update metagegevens a.d.h.v. de metagegevens die in het geselecteerde " +#~ "formaat staan" + +#~ msgid "Days of week" +#~ msgstr "Dagen van de week" + +#~ msgid "" +#~ "Books display will be restricted to those matching the selected saved search" +#~ msgstr "" +#~ "Boekweergave zal beperkt worden tot die boeken die aan de criteria van de " +#~ "geselecteerde zoekopdracht voldoen" + +#~ msgid "Change the way searching for books works" +#~ msgstr "Verander de manier om boeken te zoeken" + +#~ msgid "" +#~ "Tags categorize the book. This is particularly useful while searching. " +#~ "

      They can be any wordsor phrases, separated by commas." +#~ msgstr "" +#~ "Labels geven eigenschappen van een boek. Dit is erg handig bij het " +#~ "zoeken.

      Ze bestaan uit een woord of frase en worden gescheiden door " +#~ "komma's." + +#~ msgid "Successfully downloaded metadata for %d out of %d books" +#~ msgstr "Downloaden metagegevens succesvol voor %d van %d boeken" + +#~ msgid "Download &social metadata (tags/ratings/etc.) by default" +#~ msgstr "Download standaard &sociale metadata (labels, waarderingen, etc)" + +#~ msgid "&Maximum number of waiting worker processes (needs restart):" +#~ msgstr "&Maximaal aantal wachten werkprocessen (herstart nodig)" + +#~ msgid "Downloading social metadata, please wait..." +#~ msgstr "Downloaden sociale metagegevens, even geduld...." + +#~ msgid "Add your own categories to the Tag Browser" +#~ msgstr "Voeg je eigen categorïen toe aan de labelbrowser" + +#~ msgid "Calibre Quick Start Guide" +#~ msgstr "Calibre snelstarthandboek" + +#~ msgid "Separate paragraphs by blank lines." +#~ msgstr "Scheid alinea's met lege regels." + +#~ msgid "" +#~ "Use the element from the OPF file to determine the order in which " +#~ "the HTML files are appended to the LRF. The .opf file must be in the same " +#~ "directory as the base HTML file." +#~ msgstr "" +#~ "Gebruik het element van het OPF-bestand om de volgorde te bepalen " +#~ "waarin de HTML bestanden aan de LRF worden toegevoegd. Het .OPF bestand moet " +#~ "zich in dezelfde map bevinden als het basis HTML bestand." + +#~ msgid "" +#~ "A regular expression. tags whose href matches will be ignored. Defaults " +#~ "to %default" +#~ msgstr "" +#~ "Een regexp. tags waar de href mee overeen komt worden genegeerd. " +#~ "Standaard: %default" + +#~ msgid "" +#~ "Force a page break before tags whose names match this regular expression." +#~ msgstr "" +#~ "Forceer een pagina scheiding voor tags wiens naam overeenkomen met deze " +#~ "regexp." + +#~ msgid "" +#~ "If html2lrf does not find any page breaks in the html file and cannot detect " +#~ "chapter headings, it will automatically insert page-breaks before the tags " +#~ "whose names match this regular expression. Defaults to %default. You can " +#~ "disable it by setting the regexp to \"$\". The purpose of this option is to " +#~ "try to ensure that there are no really long pages as this degrades the page " +#~ "turn performance of the LRF. Thus this option is ignored if the current page " +#~ "has only a few elements." +#~ msgstr "" +#~ "Als html2lrf geen pagina scheidingen in het HTML bestand kan vinden en er " +#~ "geen hoofdstukken worden herkend, dan zal het automatisch nieuwe pagina's " +#~ "beginnen voor de tags die overeenkomen met deze regexp. Standaard: %default. " +#~ " Je kunt dit uitschakelen door \"$\" op te geven als regexp. Het doel van " +#~ "deze optie is om te proberen ervoor te zorgen dat er geen zeer lange " +#~ "pagina's voorkomen, aangezien die veel meer tijd nodig hebben om om te slaan " +#~ "in LRF formaat. Deze optie zal worden genegeerd als de huidige pagina weinig " +#~ "elementen bevat." + +#~ msgid "" +#~ "The regular expression used to detect chapter titles. It is searched for in " +#~ "heading tags (h1-h6). Defaults to %default" +#~ msgstr "" +#~ "De regexp die wordt gebruikt om hoofdstukken te herkennen. Deze wordt " +#~ "gezocht in 'heading tags' (h1-h6). Standaard: %default" + +#~ msgid "" +#~ "Usage: %prog [options] mybook.html\n" +#~ "\n" +#~ "\n" +#~ "%prog converts mybook.html to mybook.lrf. \n" +#~ "%prog follows all links in mybook.html that point \n" +#~ "to local files recursively. Thus, you can use it to \n" +#~ "convert a whole tree of HTML files." +#~ msgstr "" +#~ "Gebruik: %prog [opties] mijnboek.html\n" +#~ "\n" +#~ "\n" +#~ "%prog converteert mijnbook.html naar mijnboek.lrf \n" +#~ "%prog volgt alle links in mijnboek.html die naar lokale \n" +#~ "bestanden wijzen recursief. Het kan dus worden \n" +#~ "gebruikt om hele mappen in een keer te converteren." + +#~ msgid "" +#~ "Path to output directory in which to create the HTML file. Defaults to " +#~ "current directory." +#~ msgstr "" +#~ "Pad naar output map waarin het HTML bestand word gemaakt. Standaard is dit " +#~ "de huidige map." + +#~ msgid "Usage: pdf-meta file.pdf" +#~ msgstr "Gebruik: pdf-metabestand.pdf" + +#~ msgid "" +#~ "\n" +#~ "%prog [options] ISBN\n" +#~ "\n" +#~ "Fetch a cover image for the book identified by ISBN from LibraryThing.com\n" +#~ msgstr "" +#~ "\n" +#~ "%prog [opties] ISBN\n" +#~ "\n" +#~ "Download een omslagafbeelding voor het boek geidentificeerd door ISBN via " +#~ "LibraryThing.com\n" + +#~ msgid "Output directory. Defaults to current directory." +#~ msgstr "Output map. Standaard is dit de huidige map." + +#~ msgid "
      Must be a directory." +#~ msgstr "
      Moet een map zijn." + +#~ msgid "Frequently used directories" +#~ msgstr "Vaak gebruikte mappen" + +#~ msgid "Remove a directory from the frequently used directories list" +#~ msgstr "Verwijder een map van de lijst met vaak gebruikte mappen" + +#~ msgid "Add a directory to the frequently used directories list" +#~ msgstr "Voeg een map toe aan de lijst met vaak gebruikte mappen" + +#~ msgid "Bulk convert ebooks to LRF" +#~ msgstr "Converteer meerdere e-boeken naar LRF" + +#~ msgid "Insert &blank lines between paragraphs" +#~ msgstr "Voeg &blanco regels to tussen alinea's" + +#~ msgid "&Regular expression:" +#~ msgstr "&Regexp:" + +#~ msgid "Edit meta information" +#~ msgstr "Wijzig meta-informatie" + +#~ msgid "" +#~ "The title for this recipe. Used as the title for any ebooks created from the " +#~ "downloaded feeds." +#~ msgstr "" +#~ "De titel voor dit recept. Dit word gebruikt als titel voor alle e-boeken die " +#~ "worden gemaakt van de gedownloade feeds." + +#~ msgid "" +#~ "The directory in which to store the downloaded feeds. Defaults to the " +#~ "current directory." +#~ msgstr "" +#~ "De map waarin de gedownloade feeds worden bewaards. Standaard: de huidige " +#~ "map." + +#~ msgid "" +#~ "Any link that matches this regular expression will be ignored. This option " +#~ "can be specified multiple times, in which case as long as any regexp matches " +#~ "a link, it will be ignored.By default, no links are ignored. If both --" +#~ "filter-regexp and --match-regexp are specified, then --filter-regexp is " +#~ "applied first." +#~ msgstr "" +#~ "Iedere link die overeenkomst met deze regexp zal worden overgeslagen. Deze " +#~ "optie kan meerdere keren worden opgegeven, en als een van de uitdrukkingen " +#~ "overeenkomst dan zal de link worden genegeerd. Standaard word geen enkele " +#~ "link overgeslagen. indien zowel --filter-regexp en --match-regexp worden " +#~ "gebruikt, dan zal --filter-regexp allereerst worden toegepast." + +#~ msgid "" +#~ "Specify the base font size in pts. All fonts are rescaled accordingly. This " +#~ "option obsoletes the --font-delta option and takes precedence over it. To " +#~ "use --font-delta, set this to 0. Default: %defaultpt" +#~ msgstr "" +#~ "Geet de basic lettergrootte in pts. Alls lettertypen worden overeenkomstig " +#~ "vergroot. Deze optie vervangt de --font-delta optie. Om --fonot-delta te " +#~ "gebruiken, zet het op 0. STandaard: %defaultpt" + +#~ msgid "The output directory. Default is the current directory." +#~ msgstr "De uitvoer map. Standaard is de huidige map." + +#~ msgid "" +#~ "Create the output in a zip file. If this option is specified, the --output " +#~ "should be the name of a file not a directory." +#~ msgstr "" +#~ "Genereer de uitvoer in een zip bestand. Als deze optie is gekozen, dan moet -" +#~ "-output de naam van een bestand zijn, niet de map." + +#~ msgid "Set metadata of the generated ebook" +#~ msgstr "Metadata van het gegenereerde e-boek" + +#~ msgid "" +#~ "Set the author in the metadata of the generated ebook. Default is %default" +#~ msgstr "" +#~ "De auteur in de metadata van het gegenereerde e-boek. Standaard is %default" + +#~ msgid "Title for generated ebook. Default is to use the filename." +#~ msgstr "" +#~ "Titel voor gegenereerd e-boek. Standaard word de bestandsnaam gebruikt." + +#~ msgid "" +#~ "Options to control the conversion of comics (CBR, CBZ) files into ebooks" +#~ msgstr "" +#~ "Opties voor de configuratie van de conversie van comics (CBR, CBZ) bestanden " +#~ "naar e-boek" + +#~ msgid "" +#~ "

      Could not convert %d of %d books, because no suitable source format was " +#~ "found.

        %s
      " +#~ msgstr "" +#~ "

      %d van %d boeken kon niet worden geconverteerd, omdat geen schikbaar " +#~ "bronformaat kon worden gevonden:

        %s
      " + +#~ msgid "Copying library to " +#~ msgstr "Kopieer bibliotheek naar " + +#~ msgid "Choose a location for your ebook library." +#~ msgstr "Kies een locatie voor de e-boek bibliotheek" + +#~ msgid "The regular expression to use to remove the footer." +#~ msgstr "De regexp te gebruiken om de voetnoot de verwijderen" + +#~ msgid "Use a regular expression to try and remove the footer." +#~ msgstr "" +#~ "Gebruik een regexp om te proberen de voetnoot te vinden en verwijderen." + +#~ msgid "" +#~ "Do not force text to be justified in output. Whether text is actually " +#~ "displayed justified or not depends on whether the ebook format and reading " +#~ "device support justification." +#~ msgstr "" +#~ "Forceer geen tekst uitlijning in de uitvoer. Of de tekst al dan niet wordt " +#~ "uitgelijnd hangt af van of het e-boek formaat en de gebruikte lees apparaat " +#~ "al dan niet ijtlijning ondersteunen." + +#~ msgid "The regular expression to use to remove the header." +#~ msgstr "De regexp die wordt gebruikt om de koptekst te verwijderen" + +#~ msgid "Use a regular expression to try and remove the header." +#~ msgstr "Gebruik een regexp om te proberen de koptekst te verwijderen" + +#~ msgid "Output file. Default is derived from input filename." +#~ msgstr "" +#~ "Uitvoerbestand. Standaard wordt deze afgeleidt uit de invoerbestandsnaam." + +#~ msgid "Usage: ebook-convert INFILE OUTFILE [OPTIONS..]" +#~ msgstr "Gebruik: e-boek-convert INVOER UITVOER [OPTIES..]" + +#~ msgid "Do not add a blank line between paragraphs." +#~ msgstr "Voeg geen blanco regel in tussen alinea's." + +#~ msgid "" +#~ "Specify the character encoding of the output document. The default is utf-8. " +#~ "Note: This option is not honored by all formats." +#~ msgstr "" +#~ "Specificeer de karakterkodering van het uitvoerdocument. De standaard is utf-" +#~ "8. Deze optie word niet door alle formaten nageleefd." + +#~ msgid "Header regular expression:" +#~ msgstr "Koptekst regexp:" + +#~ msgid "Footer regular expression:" +#~ msgstr "Voetnoot regexp:" + +#~ msgid "Use a wizard to help construct the XPath expression" +#~ msgstr "Gebruik een wizard om te helpen een XPath uitdrukking te construeren" + +#~ msgid "Send specific format to storage card B" +#~ msgstr "Stuur specifiek formaat naar opslag kaart B" + +#~ msgid "Send specific format to storage card A" +#~ msgstr "Stuur specifiek formaat naar opslag kaart A" + +#~ msgid "Send specific format to main memory" +#~ msgstr "Stuur specifiek formaat naar hoofdgeheugen" + +#~ msgid "Sent by email:" +#~ msgstr "Verstuurd via e-mail:" + +#~ msgid "Failed to email the following books:" +#~ msgstr "De volgende boeken konden niet worden ge-e-mailed:" + +#~ msgid "" +#~ "Automatically create the author sort entry based on the current author entry" +#~ msgstr "" +#~ "Creër automatisch de sorteerauteur gebaseerd op de huidige auteur ingave" + +#~ msgid "" +#~ "

      Could not convert: %s

      It is a DRMed book. You must " +#~ "first remove the DRM using third party tools." +#~ msgstr "" +#~ "

      Kon %s niet converteren.

      Het is een e-boek met DRM. " +#~ "Je moet eerst de DRM verwijderen met externe programma's." + +#~ msgid "" +#~ "If you use the WordPlayer e-book app on your Android phone, you can access " +#~ "your calibre book collection directly on the device. To do this you have to " +#~ "turn on the content server." +#~ msgstr "" +#~ "Als je de wordplayer e-boek applicatie gebruikt op je Android telefoon, dan " +#~ "kan je de calibre boekencollectie direct op je telefoon bekijken. Om dit te " +#~ "doen zul je de content server moeten inschakelen." + +#~ msgid "The author sort string" +#~ msgstr "De sorteerauteur sleutel" + +#~ msgid "The series number" +#~ msgstr "Het reeks nummer" + +#~ msgid "Password to access your calibre library. Username is " +#~ msgstr "" +#~ "Wachtwoord om toegang te verkrijgen tot je calibrebibliotheek. " +#~ "Gebruikersnaam is " + +#~ msgid "The priority of worker processes" +#~ msgstr "De prioriteit van werkprocessen" + +#~ msgid "Communicate with the Sony PRS-300/505/500 eBook reader." +#~ msgstr "Communiceer met de Sony PRS-300/505/500 e-boeklezer." + +#~ msgid "Communicate with the Sony PRS-600/700/900 eBook reader." +#~ msgstr "Communiceer met de Sony PRS-600/700/900 e-boeklezer." + +#~ msgid "" +#~ "Here you can control how calibre will save your books when you click the " +#~ "Send to Device button. This setting can be overriden for individual devices " +#~ "by customizing the device interface plugins in Preferences->Plugins" +#~ msgstr "" +#~ "Hier kan je instellen hoe calibre je boeken bewaard wanneer je op de " +#~ "verstuur naar apparaat knop drukt. Deze instelling kan worden genegeerd voor " +#~ "bepaalde apparaten door de apparaat interface plugins in te stellen onder " +#~ "voorkeuren -> plugins." + +#~ msgid "Tags to exclude as genres (regex):" +#~ msgstr "Labels te vermeiden als genres (regexp):" + +#~ msgid "Create catalog of books in your calibre library" +#~ msgstr "Maak catalogus van boeken in je calibrebibliotheek" + +#~ msgid "Automatically number books in this series" +#~ msgstr "Nummer de boeken in deze reeks automatisch" + +#~ msgid "Send specific format" +#~ msgstr "Verstuur specifiek formaat" + +#~ msgid "" +#~ "The value %d you have chosen for the content server port is a system " +#~ "port. Your operating system may not allow the server to run on this " +#~ "port. To be safe choose a port number larger than 1024." +#~ msgstr "" +#~ "De waarde %d die je hebt gekozen voor de server-poort is een systeem-" +#~ "poort. Uw besturingssysteem kan het gebruik van deze poort blokkeren en " +#~ "voorkomen dat de server op deze poort draait. Het is veiliger te kiezen voor " +#~ "een poortnummer groter dan 1024." + +#~ msgid "" +#~ "The changes you made require that Calibre be restarted. Please restart as " +#~ "soon as practical." +#~ msgstr "" +#~ "De door je aangebrachte wijzigingen vereisen dat Calibre opnieuw opgestart " +#~ "moet worden. Herstart zo spoedig mogelijk." + +#~ msgid "" +#~ "Selected books will be automatically numbered,\n" +#~ "in the order you selected them.\n" +#~ "So if you selected Book A and then Book B,\n" +#~ "Book A will have series number 1 and Book B series number 2." +#~ msgstr "" +#~ "Geselecteerde boeken worden automatisch genummerd,\n" +#~ "in de volgorde waarin u ze geselecteerd hebt.\n" +#~ "Dus als je u eerst boek A en daarna boek B selecteerd,\n" +#~ "dan zal Boek A nr 1 en boek B nr 2 in de reeks krijgen." + +#~ msgid "" +#~ "&Location of ebooks (The ebooks are stored in maps sorted by author and " +#~ "metadata is stored in the file metadata.db)" +#~ msgstr "" +#~ "&Locatie van de e-boeken. (De e-boeken zijn opgeslagen in de mappen " +#~ "gesorteerd op auteur, en metadata word bewaard in eht bestand metadata.db)" + +#~ msgid "" +#~ "No metadata found, try adjusting the title and author or the ISBN key." +#~ msgstr "" +#~ "Geen metagegevens gevonden, probeer een andere titel en auteur of een ander " +#~ "ISBN-nummer." + +#~ msgid "" +#~ "Regular expression: the expression must match anywhere in the metadata" +#~ msgstr "Regexp: de uitdrukking moet ergens in de metagegevens voorkomen" + +#~ msgid "" +#~ "If checked, collections will not be deleted even if a book with changed " +#~ "metadata is resent and the collection is not in the book's metadata. In " +#~ "addition, editing collections in the device view will be enabled. If " +#~ "unchecked, collections will be always reflect only the metadata in the " +#~ "calibre library." +#~ msgstr "" +#~ "Indien aangevinkt zal een collectie op het apparaat niet worden verwijderd, " +#~ "ook al is er een boek met gewijzigde metadata aanwezig en de collectie is " +#~ "daarin niet in aanwezig. Tevens zal de mogelijkheid om collecties aan te " +#~ "passen in de apparaat weergave worden mogelijk gemaakt. Indien niet " +#~ "aangevinkt zullen de collecties op het apparaat steeds overeenkomen met de " +#~ "metadata in de calibrebibliotheek." + +#~ msgid "" +#~ "

    • Manual Management: Calibre updates the metadata and adds " +#~ "collections only when a book is sent. With this option, calibre will never " +#~ "remove a collection.
    • \n" +#~ "
    • Only on send: Calibre updates metadata and adds/removes " +#~ "collections for a book only when it is sent to the device.
    • \n" +#~ "
    • Automatic management: Calibre automatically keeps metadata on the " +#~ "device in sync with the calibre library, on every connect
    " +#~ msgstr "" +#~ "
  • Handmatig beleid: Calibre past de metadata aan en voegt " +#~ "collecties toe enkel op het moment dat er een boek wordt verstuurd. Als deze " +#~ "optie aan staat zal calibre nooit een collectie verwijderen.
  • \n" +#~ "
  • Enkel versturen: Calibre past de metadata aan en voegt collecties " +#~ "toe of verwijderd ze alleen op het moment dat er een boek naar het apparaat " +#~ "wordt verstuurd.
  • \n" +#~ "
  • Automatisch beleid: Calibre syncroniseert metadata op het " +#~ "apparaat met de metadata in de calibrebibliotheek bij iedere connectie met " +#~ "het apparaat.
  • " + +#~ msgid "" +#~ "This command rebuilds your calibre database from the information stored by " +#~ "calibre in the OPF files.

    This function is not currently available in the " +#~ "GUI. You can recover your database using the 'calibredb restore_database' " +#~ "command line function." +#~ msgstr "" +#~ "Deze actie maakt de calibrebibliotheek opnieuw vanaf de informatie die is " +#~ "opgeslagen in de OPF bestanden.

    Deze functie is (nog) niet beschikbaar in " +#~ "de GUI. Je kunt de database herstellen door 'calibredb restore_database\" te " +#~ "gebruiken met de CLI." + +#~ msgid "" +#~ "You can destroy your library using this feature. Changes are " +#~ "permanent. There is no undo function. This feature is experimental, and " +#~ "there may be bugs. You are strongly encouraged to back up your library " +#~ "before proceeding.

    Search and replace in text fields using character " +#~ "matching or regular expressions. " +#~ msgstr "" +#~ "Je kunt je bibliotheek met deze functie kapot maken. Veranderingen " +#~ "zijn permanent. Je kunt niet ongedaan maken. Deze functie is experimenteel " +#~ "en er kunnen fouten optreden. Je wordt sterk aangeraden eerst een backup van " +#~ "je bibliotheek te maken voor je verder gaat.

    Zoek en vervang in " +#~ "tekstvelden met karaktercombinaties of regexps. " + +#~ msgid "Clear series" +#~ msgstr "Verwijder reeks" + +#~ msgid "Read %s in the %s format" +#~ msgstr "Lees %s in het %s-formaat" + +#~ msgid "" +#~ "Normally calibre treats blank lines as paragraph markers. With this option " +#~ "it will assume that every line starting with an indent (either a tab or 2+ " +#~ "spaces) represents a paragraph. Paragraphs end when the next line that " +#~ "starts with an indent is reached." +#~ msgstr "" +#~ "Normaal gesproken behandelt calibre blanco regels als paragraaf markering. " +#~ "Met deze optie zal het aannemen dat iedere regel die begint met een " +#~ "indentatie (een tab, of twee of meer spaties) een paragraaf voorstelt. " +#~ "alinea's eindigen wanneer de volgende lijn met een indentatie word bereikt." + +#~ msgid "" +#~ "\n" +#~ "\n" +#~ "

    Set a regular expression " +#~ "pattern to use when trying to guess ebook metadata from filenames.

    \n" +#~ "

    A reference on the syntax " +#~ "of regular expressions is available.

    \n" +#~ "

    Use the Test functionality below to test your regular " +#~ "expression on a few sample filenames. The group names for the various " +#~ "metadata entries are documented in tooltips.

    " +#~ msgstr "" +#~ "\n" +#~ "\n" +#~ "

    Geef een reguliere " +#~ "uitdrukking patroon op om te gebruiken bij het raden van e-boek " +#~ "metagegevens gebaseerd op bestandsnamen.

    \n" +#~ "

    Een referentie betreffende de " +#~ "syntax voor regexps is beschikbaar.

    \n" +#~ "

    Gebruik de Test functionaliteit hieronder om de reguliere " +#~ "uitdrukking uit te proberen op een aantal test bestandsnamen. De groepnamen " +#~ "voor de diverse metadata velden zijn gedocumenteerd onder " +#~ "tooltips.

    " + +#~ msgid "" +#~ "The following books had formats listed in the database that are not actually " +#~ "available. The entries for the formats have been removed. You should check " +#~ "them manually. This can happen if you manipulate the files in the library " +#~ "map directly." +#~ msgstr "" +#~ "De volgende boeken hadden formaten in de database die niet beschikbaar zijn. " +#~ "De opgaves voor deze formaten zijn verwijderd. Je zult deze handmatig moeten " +#~ "nakijken. Dit kan gebeuren als je de bestanden in de mappen zelf hebt " +#~ "aangepast." + +#~ msgid "" +#~ "The following books had formats or covers listed in the database that are " +#~ "not actually available. The entries for the formats/covers have been " +#~ "removed. You should check them manually. This can happen if you manipulate " +#~ "the files in the library map directly." +#~ msgstr "" +#~ "Deze boeken hadden formaten of omslagen opgeslagen in de database die niet " +#~ "echt beschikbaar zijn. De ingangen voor de formaten/omslagen zijn " +#~ "verwijderd. Je zou ze handmatig moeten controleren. Dit kan ontstaan als je " +#~ "bestanden wijzigt in de mappen zelf." + +#~ msgid "" +#~ " %prog [options]\n" +#~ "\n" +#~ " Fetch book metadata from Nicebooks. You must specify one of title, " +#~ "author,\n" +#~ " ISBN, publisher or keywords. Will fetch a maximum of 20 matches,\n" +#~ " so you should make your query as specific as possible.\n" +#~ " It can also get covers if the option is activated.\n" +#~ " " +#~ msgstr "" +#~ " %prog [options]\n" +#~ "\n" +#~ " Haal metagegevens van Nicebooks. Je moet ofwel titel, auteur,\n" +#~ " ISBN, uitgever of sleutelwoorden opgeven. Zal een maximum van 20 " +#~ "treffers ophalen,\n" +#~ " dus je moet je zoeksleutel zo specifiek mogelijk maken.\n" +#~ " Je kan ook omslagen ophalen als die optie is aangevinkt.\n" +#~ " " + +#~ msgid "An errror occured with Nicebooks cover fetcher" +#~ msgstr "Er is een fout opgetreden bij het ophalen van Nicebooks omslagen" + +#~ msgid "Downloads metadata from The Open Library" +#~ msgstr "Download metadata van The Open Library" + +#~ msgid "" +#~ "Automatically create the author sort entry based on the current author " +#~ "entry.\n" +#~ "Using this button to create author sort will change author sort from red to " +#~ "green." +#~ msgstr "" +#~ "Leid de sorteerauteur automatisch af uit de auteur. Gebruik van deze knop\n" +#~ "om de sorteerauteur te maken zal de sorteerauteur veranderen van rood in " +#~ "groen." + +#~ msgid "Modified Date" +#~ msgstr "Datum gewijzigd" + +#~ msgid "" +#~ "That format and device already has a plugboard or conflicts with another " +#~ "plugboard." +#~ msgstr "" +#~ "Dat formaat en apparaat hebben al een stekkersnoer of conflicteren met een " +#~ "ander stekkersnoer." + +#~ msgid "Downloads metadata from Overdrive's Content Reserve" +#~ msgstr "Download metadata van Overdrive's Content Reserve" + +#~ msgid "" +#~ "Set custom metadata fields that the book details panel will or will not " +#~ "display." +#~ msgstr "" +#~ "Stel persoonlijke metadata velden in die al dan niet worden weergegeven in " +#~ "het boeken-details paneel." + +#~ msgid "Configure metadata downloading" +#~ msgstr "Configureer metadata downloads" + +#~ msgid "" +#~ "For the error message from each cover source, click Show details below." +#~ msgstr "" +#~ "Klik hieronder op Toon details om de foutmeldingen van elke omslagbron in te " +#~ "zien" + +#~ msgid "" +#~ "

    calibre can find metadata for your books from two locations: Google " +#~ "Books and isbndb.com.

    To use isbndb.com you must sign up for a " +#~ "free account and enter your access key " +#~ "below." +#~ msgstr "" +#~ "

    calibre kan metagegevens voor je boeken in twee locaties vinden: " +#~ "Google Books en isbndb.com.

    Om isbndb.com te gebruiken zul " +#~ "je eerst een gratis account moeten " +#~ "aanvragen en daarna je wachtwoord hieronder opgeven." + +#~ msgid "Ebooks handcrafted with the utmost care" +#~ msgstr "E-boeken handgemaakt met de meeste zorg" + +#~ msgid "Kindle books from Amazon" +#~ msgstr "Kindle-boeken van Amazon" + +#~ msgid "Kindle eBooks" +#~ msgstr "Kindle E-boeken" + +#~ msgid "Feel every word" +#~ msgstr "Voel ieder woord" + +#~ msgid "Foyles of London, online" +#~ msgstr "Foyles uit London, online" + +#~ msgid "entertain, enrich, inspire." +#~ msgstr "Vermaak, verrijk, inspireer." + +#~ msgid "Title &sort: " +#~ msgstr "&Sorteertitel: " + +#~ msgid "&Access Key:" +#~ msgstr "Toeg&angssleutel:" + +#~ msgid "&Fetch metadata from server" +#~ msgstr "Download metagegevens van de server" + +#~ msgid "Select visible &columns in library view" +#~ msgstr "Sele&cteer zichtbare kolommen in bibliotheek weergave" + +#~ msgid "Minimum &indent:" +#~ msgstr "Minimum &indentatie:" + +#~ msgid "&Preprocess:" +#~ msgstr "Verwerk" + +#~ msgid "Ignore &colors" +#~ msgstr "Negeer kleuren" + +#~ msgid "&Show header" +#~ msgstr "Laat koptekst zien" + +#~ msgid "&Header format:" +#~ msgstr "Koptekst (&hoofd) formaat:" + +#~ msgid "&Top Margin:" +#~ msgstr "Boven Marge:" + +#~ msgid "&Bottom Margin:" +#~ msgstr "Onder Marge:" + +#~ msgid "Add &chapters to table of contents" +#~ msgstr "Hoofdstukken bes&chikbaar maken in inhoudsopgave" + +#~ msgid "Add Ta&gs: " +#~ msgstr "Voe&g labels toe: " + +#~ msgid "&Button size in toolbar" +#~ msgstr "Omvang knop(-af&beelding) in werkbalk" + +#~ msgid "Format for &single file save:" +#~ msgstr "Op&slagformaat voor enkel bestand:" + +#~ msgid "&Transliterate unicode characters to ASCII." +#~ msgstr "Conver&teer unicode karakters naar ASCII" + +#~ msgid "Remove F&ooter" +#~ msgstr "Verwijder V&oetnoot" + +#~ msgid "Remove H&eader" +#~ msgstr "Verwijder Kopt&ekst" + +#~ msgid "&Adding books" +#~ msgstr "Boeken toevoegen" + +#~ msgid "&Saving books" +#~ msgstr "Boeken op&slaan" + +#~ msgid "Search as you type" +#~ msgstr "Zoek tijdens typen" + +#~ msgid "&Add" +#~ msgstr "Toevoegen" + +#~ msgid "Plugin &file:" +#~ msgstr "Plugin bestand:" + +#~ msgid "Stop &all jobs" +#~ msgstr "Stop &alle taken" + +#~ msgid "Fit &cover to view" +#~ msgstr "S&chaal omslag naar schermgrootte" + +#~ msgid "Sending to &device" +#~ msgstr "Verstuur naar apparaat" + +#~ msgid "Read metadata only from &file name" +#~ msgstr "Lees metagegevens alleen uit de bestandsnaam" + +#~ msgid "&Restrict to:" +#~ msgstr "Bepe&rkt tot:" + +#~ msgid "&Miscellaneous" +#~ msgstr "Overige" + +#~ msgid "&Current tweaks" +#~ msgstr "Huidige aanpassingen" + +#~ msgid "&Tweaks" +#~ msgstr "&Tweaks" + +#~ msgid "&Restore to defaults" +#~ msgstr "He&rstel naar standaardwaarden" + +#~ msgid "Manage &user categories" +#~ msgstr "Beheer gebr&uikerscategorieën" + +#~ msgid "&Preprocess input file to possibly improve structure detection" +#~ msgstr "" +#~ "Invoerbestand voorbewerken (aan&passen) voor mogelijk betere structuur " +#~ "detectie" + +#~ msgid "Download &cover" +#~ msgstr "Download omslagafbeelding (indien bes&chikbaar)" + +#~ msgid "Kindle books from Amazon.uk" +#~ msgstr "Kindle-boeken van Amazon.uk" + +#~ msgid "der eBook Shop" +#~ msgstr "de E-boekwinkel" + +#~ msgid "EPUBReaders eBook Shop" +#~ msgstr "EPUBLezers E-boekwinkel" + +#~ msgid "" +#~ "The algorithm used to copy author to author_sort\n" +#~ "Possible values are:\n" +#~ "invert: use \"fn ln\" -> \"ln, fn\" (the default algorithm)\n" +#~ "copy : copy author to author_sort without modification\n" +#~ "comma : use 'copy' if there is a ',' in the name, otherwise use 'invert'\n" +#~ "nocomma : \"fn ln\" -> \"ln fn\" (without the comma)\n" +#~ "When this tweak is changed, the author_sort values stored with each author\n" +#~ "must be recomputed by right-clicking on an author in the left-hand tags " +#~ "pane,\n" +#~ "selecting 'manage authors', and pressing 'Recalculate all author sort " +#~ "values'." +#~ msgstr "" +#~ "Het algoritme gebruikt om het sorteerauteursveld af te leiden uit\n" +#~ "het auteursveld.\n" +#~ "Mogelijke waarden zijn:\n" +#~ "invert: gebruik \"vn an\" -> \"an, vn\" (het standaardalgoritme)\n" +#~ "copy : kopieer auteur naar sorteerauteur zonder wijziging\n" +#~ "comma : gebruik 'copy' als er een ',' in de naam staat, gebruik anders " +#~ "'invert'\n" +#~ "nocomma : \"vn an\" -> \"an vn\" (zonder de komma)\n" +#~ "Als deze tweak is gewijzigd, zullen de waarden voor sorteerauteur voor " +#~ "iedere auteur opnieuw berekend moeten worden door rechtsklikken op een " +#~ "auteur in het linkerpaneel, waarna 'Auteursbeheer' geselecteerd moet worden, " +#~ "gevolgd door 'Herbereken alle sorteerauteurwaarden'" + +#~ msgid "" +#~ "Choose you e-book device. If your device is not in the list, choose a \"%s\" " +#~ "device." +#~ msgstr "" +#~ "Kies je leesapparaat. Als het niet in de lijst staat, kies dan een \"%s\"-" +#~ "apparaat." + +#~ msgid "" +#~ "Specify the page settings like margins and the screen size of the target " +#~ "device." +#~ msgstr "" +#~ "Specificeer de pagina indeling zoals kantlijnen en de scherm grootte van het " +#~ "doel apparaat." + +#~ msgid "Toolbar icon size" +#~ msgstr "Werkbalk ikoon grootte" + +#~ msgid "This profile is intended for the SONY PRS line. The 500/505/700 etc." +#~ msgstr "Dit profiel is bedoeld voor de SONY PRS lijn. De 500/505/700 etc." + +#~ msgid "Force maximum line lenght" +#~ msgstr "Forceer maximum regel lengte" + +#~ msgid "Configure calibre" +#~ msgstr "Configueer calibre" + +#~ msgid "

    For help see the: User Manual
    " +#~ msgstr "

    Voor hulp, zie het: Gebruikers handboek
    " + +#~ msgid "Checked id" +#~ msgstr "Id gecontroleerd" + +#~ msgid "" +#~ "Tag indicating book has been read.\n" +#~ "Default: '%default'\n" +#~ "Applies to: ePub, MOBI output formats" +#~ msgstr "" +#~ "Label geeft aan dat boek gelezen is.\n" +#~ "Standaard: '%default'\n" +#~ "Van toepassing op: uitvoer naar ePub, MOBI" + +#~ msgid "set in ui.py" +#~ msgstr "ingesteld in ui.py" + +#~ msgid "" +#~ "Sort titles with leading numbers as text, e.g.,\n" +#~ "'2001: A Space Odyssey' sorts as \n" +#~ "'Two Thousand One: A Space Odyssey'.\n" +#~ "Default: '%default'\n" +#~ "Applies to: ePub, MOBI output formats" +#~ msgstr "" +#~ "Sorteer titels die beginnen met cijfers als tekst, bv.,\n" +#~ "'2001: A Space Odyssey' wordt gesorteerd als\n" +#~ "'Two Thousand One: A Space Odyssey'.\n" +#~ "Standaard: '%default'\n" +#~ "Van toepassing op: uitvoer naar ePub, MOBI" + +#~ msgid "Downloads metadata from Google Books" +#~ msgstr "Download metagegevens van Google Boeken" + +#~ msgid "misc" +#~ msgstr "diversen" + +#~ msgid "" +#~ "The fields to output when cataloging books in the database. Should be a " +#~ "comma-separated list of fields.\n" +#~ "Available fields: %s.\n" +#~ "Default: '%%default'\n" +#~ "Applies to: BIBTEX output format" +#~ msgstr "" +#~ "De velden die dienen weergegeven bij het catalogeren van boeken in de " +#~ "database. Dit zou een door komma's gescheiden lijst van velden moeten zijn.\n" +#~ "Beschikbare velden: %s.\n" +#~ "Standaard: '%%default'\n" +#~ "Van toepassing op: BIBTEX weergave formaat" + +#~ msgid "Communicate with the Promedia eBook reader" +#~ msgstr "Communicatie met de Promedia E-boek lezer" + +#~ msgid "" +#~ "Regex tips:\n" +#~ "- The default regex - \\[.+\\] - excludes genre tags of the form [tag], " +#~ "e.g., [Amazon Freebie]\n" +#~ "- A regex pattern of a single dot excludes all genre tags, generating no " +#~ "Genre Section" +#~ msgstr "" +#~ "Regex tips:\n" +#~ "- De standaard regex - \\[.+\\] - sluit genre labels uit van met de het " +#~ "formaat [tag], b.v., [Amazon Freebie]\n" +#~ "- Een regex met één . sluit alle genre labels uit, en genereert geen Genre " +#~ "Sectie" + +#~ msgid "" +#~ "The template for citation creation from database fields.\n" +#~ " Should be a template with {} enclosed fields.\n" +#~ "Available fields: %s.\n" +#~ "Default: '%%default'\n" +#~ "Applies to: BIBTEX output format" +#~ msgstr "" +#~ "Het sjabloon voor het maken van citaten gebaseerd op velden uit de " +#~ "database.\n" +#~ " Zou een sjabloon moeten zijn met velden ingesloten door {}.\n" +#~ "Beschikbare velden: %s.\n" +#~ "Standaard: '%%default'\n" +#~ "Van toepassing op: BIBTEX weergave formaat" + +#~ msgid "Timed out" +#~ msgstr "Verlopen" + +#~ msgid "" +#~ "Output field to sort on.\n" +#~ "Available fields: author_sort, id, rating, size, timestamp, title.\n" +#~ "Default: '%default'\n" +#~ "Applies to: CSV, XML output formats" +#~ msgstr "" +#~ "Uitvoerveld om op te sorteren.\n" +#~ "Beschikbare velden: author_sort, id, rating, size, timestamp, title.\n" +#~ "Standaard: '%default'\n" +#~ "Van toepassing op: uitvoer naar CSV en XMLBIBTEX" + +#~ msgid "Fine tune the detection of chapter and section headings." +#~ msgstr "Stel de detectie van hoofdstuk en paragraaf koppen in" + +#~ msgid "Character encoding for HTML files. Default is to auto detect." +#~ msgstr "Karaktercodering voor HTML bestanden. Standaard is auto detect." + +#~ msgid "Ebook Viewer" +#~ msgstr "E-boek Viewer" + +#~ msgid "" +#~ "The fields to output when cataloging books in the database. Should be a " +#~ "comma-separated list of fields.\n" +#~ "Available fields: all, author_sort, authors, comments, cover, formats, id, " +#~ "isbn, pubdate, publisher, rating, series_index, series, size, tags, " +#~ "timestamp, title, uuid.\n" +#~ "Default: '%default'\n" +#~ "Applies to: CSV, XML output formats" +#~ msgstr "" +#~ "De velden om uit te voeren wanneer boeken in de database worden geindexeerd. " +#~ "Komma gescheiden velden.\n" +#~ "Beschikbare velden: all, author_sort, authors, comments, cover, formats, id, " +#~ "isbn, pubdate, publisher, rating, series_index, series, size, tags, " +#~ "timestamp, title, uuid.\n" +#~ "Standaard: '%default'\n" +#~ "Van toepassing op: uitvoer naar CSV, XML" + +#~ msgid "" +#~ "The fields to output when cataloging books in the database. Should be a " +#~ "comma-separated list of fields.\n" +#~ "Available fields: %s.\n" +#~ "Default: '%%default'\n" +#~ "Applies to: CSV, XML output formats" +#~ msgstr "" +#~ "De velden voor de uitvoer van catalogus van boeken in de database. komma-" +#~ "gescheiden lijst met velden.\n" +#~ "Beschikbare velden: %s\n" +#~ "Standaard: '%%default'\n" +#~ "Van toepassing op: uitvoer naar CSV, XML" + +#~ msgid "" +#~ "Tag prefix for user notes, e.g. '*Jeff might enjoy reading this'.\n" +#~ "Default: '%default'\n" +#~ "Applies to: ePub, MOBI output formats" +#~ msgstr "" +#~ "Label prefix voor gebruiker notities, bv. '*Misschien vindt Jeff dit een " +#~ "leuk boek'\n" +#~ "Standaard: '%default'\n" +#~ "Van toepassing op: uitvoer naar ePub, MOBI" + +#~ msgid "" +#~ "Save the output from different stages of the conversion pipeline to the " +#~ "specified directory. Useful if you are unsure at which stage of the " +#~ "conversion process a bug is occurring.\n" +#~ "Default: '%default'None\n" +#~ "Applies to: ePub, MOBI output formats" +#~ msgstr "" +#~ "Bewaar de uitvoer van verschillende stadia van het conversie proces in de " +#~ "opgegeven map. Handig als je niet zeker weet wanneer fouten optreden tijdens " +#~ "de conversie.\n" +#~ "Standaard: '%default'None\n" +#~ "Van toepassing op: uitvoer naar ePub, MOBI" + +#~ msgid "" +#~ "Comma-separated list of tag words indicating book should be excluded from " +#~ "output. Case-insensitive.\n" +#~ "--exclude-tags=skip will match 'skip this book' and 'Skip will like this'.\n" +#~ "Default: '%default'\n" +#~ "Applies to: ePub, MOBI output formats" +#~ msgstr "" +#~ "Komma-gescheiden lijst van tag woorden die aangeeft dat het boek uit de " +#~ "uitvoer moet gehouden worden. Niet hoofdletter gevoelig.\n" +#~ "--exclude-tags=ben komt overeen met 'ben ik aan het lezen' en 'Ben zal dit " +#~ "leuk vinden'.\n" +#~ "Standaard: '%default'\n" +#~ "Van toepassing op: uitvoer naar ePub, MOBI" + +#~ msgid "Communicate with the BeBook Mini eBook reader." +#~ msgstr "Communiceer met de BeBook Mini E-boek lezer." + +#~ msgid "Communicate with the BeBook eBook reader." +#~ msgstr "Communiceer met de BeBook E-boek lezer." + +#~ msgid "Communicate with the Cybook Gen 3 eBook reader." +#~ msgstr "Communiceer met de Cybook Gen 3 E-boek lezer." + +#~ msgid "Communicate with the Cybook Opus eBook reader." +#~ msgstr "Communiceer met de Cybook Opus E-boek lezer." + +#~ msgid "Communicate with the Kindle 2 eBook reader." +#~ msgstr "Communiceer met de Kindle 2 E-boek lezer." + +#~ msgid "Communicate with the Sony PRS-600/700 eBook reader." +#~ msgstr "Communiceer met de Sony PRS-600/700 E-boek lezer." + +#~ msgid "Communicate with the Sony PRS-300/505 eBook reader." +#~ msgstr "Communiceer met de Sony PRS-300/505 E-boek lezer." + +#~ msgid "" +#~ "Adjust the look of the generated LRF file by specifying things like font " +#~ "sizes and the spacing between words." +#~ msgstr "" +#~ "Verander de weergave van het gegenereerde LRF bestand door de lettertype " +#~ "grootte en witruimte tussen woorden aan te passen." + +#~ msgid "" +#~ "Remove spacing between paragraphs. Also sets a indent on paragraphs of " +#~ "1.5em. You can override this by adding p {text-indent: 0cm} to --override-" +#~ "css. Spacing removal will not work if the source file forces inter-paragraph " +#~ "spacing." +#~ msgstr "" +#~ "Verwijderd witruimte tussen de alinea's. Voegt eveneens een inspringen van " +#~ "1,5em toe aan alinea's. je kan dit overschrijven door p {text-indent: 0cm} " +#~ "toe te voegen aan --override-css. Het verwijderen van witruimte tussen de " +#~ "alinea's zal niet werken indien het bronbestand witruimte afdwingt tussen " +#~ "alinea's." diff --git a/src/calibre/translations/oc.po b/src/calibre/translations/oc.po index 0f93d7b936..d6bc112e96 100644 --- a/src/calibre/translations/oc.po +++ b/src/calibre/translations/oc.po @@ -7,88 +7,97 @@ msgid "" msgstr "" "Project-Id-Version: calibre\n" "Report-Msgid-Bugs-To: FULL NAME \n" -"POT-Creation-Date: 2011-02-18 21:06+0000\n" +"POT-Creation-Date: 2011-05-20 18:12+0000\n" "PO-Revision-Date: 2010-05-21 07:14+0000\n" "Last-Translator: Cédric VALMARY (Tot en òc) \n" "Language-Team: Occitan (post 1500) \n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" -"X-Launchpad-Export-Date: 2011-02-19 04:56+0000\n" -"X-Generator: Launchpad (build 12351)\n" +"X-Launchpad-Export-Date: 2011-05-21 04:52+0000\n" +"X-Generator: Launchpad (build 12959)\n" -#: /home/kovid/work/calibre/src/calibre/customize/__init__.py:43 +#: /home/kovid/work/calibre/src/calibre/customize/__init__.py:56 msgid "Does absolutely nothing" msgstr "Fa estrictament pas res" -#: /home/kovid/work/calibre/src/calibre/customize/__init__.py:46 +#: /home/kovid/work/calibre/src/calibre/customize/__init__.py:59 #: /home/kovid/work/calibre/src/calibre/devices/hanvon/driver.py:87 #: /home/kovid/work/calibre/src/calibre/devices/hanvon/driver.py:88 #: /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/books.py:24 -#: /home/kovid/work/calibre/src/calibre/devices/kobo/driver.py:482 +#: /home/kovid/work/calibre/src/calibre/devices/kobo/driver.py:488 #: /home/kovid/work/calibre/src/calibre/devices/nook/driver.py:70 #: /home/kovid/work/calibre/src/calibre/devices/nook/driver.py:71 #: /home/kovid/work/calibre/src/calibre/devices/prs500/books.py:267 #: /home/kovid/work/calibre/src/calibre/devices/prs505/sony_cache.py:660 -#: /home/kovid/work/calibre/src/calibre/devices/usbms/driver.py:403 -#: /home/kovid/work/calibre/src/calibre/ebooks/chm/input.py:97 -#: /home/kovid/work/calibre/src/calibre/ebooks/chm/input.py:100 +#: /home/kovid/work/calibre/src/calibre/devices/usbms/driver.py:467 +#: /home/kovid/work/calibre/src/calibre/ebooks/chm/input.py:106 +#: /home/kovid/work/calibre/src/calibre/ebooks/chm/input.py:109 #: /home/kovid/work/calibre/src/calibre/ebooks/chm/metadata.py:56 -#: /home/kovid/work/calibre/src/calibre/ebooks/comic/input.py:419 +#: /home/kovid/work/calibre/src/calibre/ebooks/comic/input.py:435 #: /home/kovid/work/calibre/src/calibre/ebooks/epub/periodical.py:127 #: /home/kovid/work/calibre/src/calibre/ebooks/fb2/input.py:100 #: /home/kovid/work/calibre/src/calibre/ebooks/fb2/input.py:102 -#: /home/kovid/work/calibre/src/calibre/ebooks/html/input.py:331 -#: /home/kovid/work/calibre/src/calibre/ebooks/html/input.py:334 +#: /home/kovid/work/calibre/src/calibre/ebooks/html/input.py:332 +#: /home/kovid/work/calibre/src/calibre/ebooks/html/input.py:335 #: /home/kovid/work/calibre/src/calibre/ebooks/lrf/html/convert_from.py:1894 #: /home/kovid/work/calibre/src/calibre/ebooks/lrf/html/convert_from.py:1896 #: /home/kovid/work/calibre/src/calibre/ebooks/lrf/output.py:24 -#: /home/kovid/work/calibre/src/calibre/ebooks/metadata/__init__.py:236 -#: /home/kovid/work/calibre/src/calibre/ebooks/metadata/book/base.py:31 -#: /home/kovid/work/calibre/src/calibre/ebooks/metadata/book/base.py:32 -#: /home/kovid/work/calibre/src/calibre/ebooks/metadata/book/base.py:73 -#: /home/kovid/work/calibre/src/calibre/ebooks/metadata/book/base.py:379 -#: /home/kovid/work/calibre/src/calibre/ebooks/metadata/book/base.py:384 -#: /home/kovid/work/calibre/src/calibre/ebooks/metadata/book/base.py:616 +#: /home/kovid/work/calibre/src/calibre/ebooks/metadata/__init__.py:253 +#: /home/kovid/work/calibre/src/calibre/ebooks/metadata/book/base.py:34 +#: /home/kovid/work/calibre/src/calibre/ebooks/metadata/book/base.py:35 +#: /home/kovid/work/calibre/src/calibre/ebooks/metadata/book/base.py:89 +#: /home/kovid/work/calibre/src/calibre/ebooks/metadata/book/base.py:455 +#: /home/kovid/work/calibre/src/calibre/ebooks/metadata/book/base.py:460 +#: /home/kovid/work/calibre/src/calibre/ebooks/metadata/book/base.py:724 #: /home/kovid/work/calibre/src/calibre/ebooks/metadata/ereader.py:36 #: /home/kovid/work/calibre/src/calibre/ebooks/metadata/ereader.py:61 -#: /home/kovid/work/calibre/src/calibre/ebooks/metadata/fb2.py:54 -#: /home/kovid/work/calibre/src/calibre/ebooks/metadata/fetch.py:359 +#: /home/kovid/work/calibre/src/calibre/ebooks/metadata/extz.py:23 +#: /home/kovid/work/calibre/src/calibre/ebooks/metadata/fb2.py:55 #: /home/kovid/work/calibre/src/calibre/ebooks/metadata/meta.py:36 #: /home/kovid/work/calibre/src/calibre/ebooks/metadata/meta.py:64 #: /home/kovid/work/calibre/src/calibre/ebooks/metadata/meta.py:66 #: /home/kovid/work/calibre/src/calibre/ebooks/metadata/meta.py:124 #: /home/kovid/work/calibre/src/calibre/ebooks/metadata/meta.py:126 -#: /home/kovid/work/calibre/src/calibre/ebooks/metadata/opf2.py:1026 -#: /home/kovid/work/calibre/src/calibre/ebooks/metadata/opf2.py:1136 -#: /home/kovid/work/calibre/src/calibre/ebooks/metadata/pdb.py:39 +#: /home/kovid/work/calibre/src/calibre/ebooks/metadata/opf2.py:1066 +#: /home/kovid/work/calibre/src/calibre/ebooks/metadata/opf2.py:1176 +#: /home/kovid/work/calibre/src/calibre/ebooks/metadata/pdb.py:41 #: /home/kovid/work/calibre/src/calibre/ebooks/metadata/pdf.py:29 +#: /home/kovid/work/calibre/src/calibre/ebooks/metadata/plucker.py:25 #: /home/kovid/work/calibre/src/calibre/ebooks/metadata/pml.py:23 #: /home/kovid/work/calibre/src/calibre/ebooks/metadata/pml.py:49 #: /home/kovid/work/calibre/src/calibre/ebooks/metadata/rtf.py:88 #: /home/kovid/work/calibre/src/calibre/ebooks/metadata/rtf.py:91 #: /home/kovid/work/calibre/src/calibre/ebooks/metadata/rtf.py:101 #: /home/kovid/work/calibre/src/calibre/ebooks/metadata/snb.py:16 -#: /home/kovid/work/calibre/src/calibre/ebooks/metadata/sources/google.py:75 +#: /home/kovid/work/calibre/src/calibre/ebooks/metadata/sources/base.py:48 +#: /home/kovid/work/calibre/src/calibre/ebooks/metadata/sources/base.py:298 +#: /home/kovid/work/calibre/src/calibre/ebooks/metadata/sources/covers.py:79 +#: /home/kovid/work/calibre/src/calibre/ebooks/metadata/sources/covers.py:81 +#: /home/kovid/work/calibre/src/calibre/ebooks/metadata/sources/douban.py:78 +#: /home/kovid/work/calibre/src/calibre/ebooks/metadata/sources/google.py:81 +#: /home/kovid/work/calibre/src/calibre/ebooks/metadata/sources/identify.py:208 +#: /home/kovid/work/calibre/src/calibre/ebooks/metadata/sources/identify.py:303 +#: /home/kovid/work/calibre/src/calibre/ebooks/metadata/sources/identify.py:305 +#: /home/kovid/work/calibre/src/calibre/ebooks/metadata/sources/identify.py:406 #: /home/kovid/work/calibre/src/calibre/ebooks/metadata/txt.py:18 -#: /home/kovid/work/calibre/src/calibre/ebooks/metadata/txtz.py:23 -#: /home/kovid/work/calibre/src/calibre/ebooks/mobi/reader.py:42 -#: /home/kovid/work/calibre/src/calibre/ebooks/mobi/reader.py:68 -#: /home/kovid/work/calibre/src/calibre/ebooks/mobi/reader.py:81 -#: /home/kovid/work/calibre/src/calibre/ebooks/mobi/reader.py:124 -#: /home/kovid/work/calibre/src/calibre/ebooks/mobi/reader.py:158 -#: /home/kovid/work/calibre/src/calibre/ebooks/mobi/reader.py:663 -#: /home/kovid/work/calibre/src/calibre/ebooks/mobi/reader.py:878 -#: /home/kovid/work/calibre/src/calibre/ebooks/mobi/reader.py:880 -#: /home/kovid/work/calibre/src/calibre/ebooks/odt/input.py:49 -#: /home/kovid/work/calibre/src/calibre/ebooks/odt/input.py:51 -#: /home/kovid/work/calibre/src/calibre/ebooks/oeb/base.py:958 -#: /home/kovid/work/calibre/src/calibre/ebooks/oeb/base.py:963 -#: /home/kovid/work/calibre/src/calibre/ebooks/oeb/base.py:1029 -#: /home/kovid/work/calibre/src/calibre/ebooks/oeb/reader.py:145 -#: /home/kovid/work/calibre/src/calibre/ebooks/oeb/reader.py:152 +#: /home/kovid/work/calibre/src/calibre/ebooks/mobi/reader.py:43 +#: /home/kovid/work/calibre/src/calibre/ebooks/mobi/reader.py:69 +#: /home/kovid/work/calibre/src/calibre/ebooks/mobi/reader.py:82 +#: /home/kovid/work/calibre/src/calibre/ebooks/mobi/reader.py:125 +#: /home/kovid/work/calibre/src/calibre/ebooks/mobi/reader.py:159 +#: /home/kovid/work/calibre/src/calibre/ebooks/mobi/reader.py:714 +#: /home/kovid/work/calibre/src/calibre/ebooks/mobi/reader.py:961 +#: /home/kovid/work/calibre/src/calibre/ebooks/mobi/reader.py:963 +#: /home/kovid/work/calibre/src/calibre/ebooks/odt/input.py:99 +#: /home/kovid/work/calibre/src/calibre/ebooks/odt/input.py:101 +#: /home/kovid/work/calibre/src/calibre/ebooks/oeb/base.py:1001 +#: /home/kovid/work/calibre/src/calibre/ebooks/oeb/base.py:1006 +#: /home/kovid/work/calibre/src/calibre/ebooks/oeb/base.py:1072 +#: /home/kovid/work/calibre/src/calibre/ebooks/oeb/reader.py:144 +#: /home/kovid/work/calibre/src/calibre/ebooks/oeb/reader.py:151 #: /home/kovid/work/calibre/src/calibre/ebooks/oeb/transforms/jacket.py:65 #: /home/kovid/work/calibre/src/calibre/ebooks/oeb/transforms/jacket.py:112 #: /home/kovid/work/calibre/src/calibre/ebooks/oeb/transforms/jacket.py:119 @@ -112,108 +121,119 @@ msgstr "Fa estrictament pas res" #: /home/kovid/work/calibre/src/calibre/ebooks/pdf/manipulate/rotate.py:63 #: /home/kovid/work/calibre/src/calibre/ebooks/pdf/manipulate/split.py:81 #: /home/kovid/work/calibre/src/calibre/ebooks/pdf/manipulate/split.py:82 -#: /home/kovid/work/calibre/src/calibre/ebooks/pdf/writer.py:100 #: /home/kovid/work/calibre/src/calibre/ebooks/pdf/writer.py:101 -#: /home/kovid/work/calibre/src/calibre/ebooks/rtf/input.py:312 -#: /home/kovid/work/calibre/src/calibre/ebooks/rtf/input.py:314 -#: /home/kovid/work/calibre/src/calibre/gui2/__init__.py:308 -#: /home/kovid/work/calibre/src/calibre/gui2/__init__.py:315 -#: /home/kovid/work/calibre/src/calibre/gui2/actions/add.py:101 -#: /home/kovid/work/calibre/src/calibre/gui2/actions/edit_metadata.py:332 -#: /home/kovid/work/calibre/src/calibre/gui2/actions/edit_metadata.py:335 +#: /home/kovid/work/calibre/src/calibre/ebooks/pdf/writer.py:102 +#: /home/kovid/work/calibre/src/calibre/ebooks/rtf/input.py:313 +#: /home/kovid/work/calibre/src/calibre/ebooks/rtf/input.py:315 +#: /home/kovid/work/calibre/src/calibre/gui2/__init__.py:347 +#: /home/kovid/work/calibre/src/calibre/gui2/__init__.py:355 +#: /home/kovid/work/calibre/src/calibre/gui2/actions/add.py:156 +#: /home/kovid/work/calibre/src/calibre/gui2/actions/edit_metadata.py:364 +#: /home/kovid/work/calibre/src/calibre/gui2/actions/edit_metadata.py:367 #: /home/kovid/work/calibre/src/calibre/gui2/add.py:160 #: /home/kovid/work/calibre/src/calibre/gui2/add.py:167 +#: /home/kovid/work/calibre/src/calibre/gui2/book_details.py:519 #: /home/kovid/work/calibre/src/calibre/gui2/convert/__init__.py:42 #: /home/kovid/work/calibre/src/calibre/gui2/convert/metadata.py:122 #: /home/kovid/work/calibre/src/calibre/gui2/convert/metadata.py:151 #: /home/kovid/work/calibre/src/calibre/gui2/convert/metadata.py:153 -#: /home/kovid/work/calibre/src/calibre/gui2/device.py:1089 -#: /home/kovid/work/calibre/src/calibre/gui2/device.py:1092 +#: /home/kovid/work/calibre/src/calibre/gui2/device.py:1120 +#: /home/kovid/work/calibre/src/calibre/gui2/device.py:1123 #: /home/kovid/work/calibre/src/calibre/gui2/dialogs/add_empty_book.py:56 #: /home/kovid/work/calibre/src/calibre/gui2/dialogs/add_empty_book.py:68 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/book_info.py:127 #: /home/kovid/work/calibre/src/calibre/gui2/dialogs/comicconf.py:47 -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/fetch_metadata.py:145 -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/fetch_metadata.py:185 -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single.py:732 -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/scheduler.py:193 -#: /home/kovid/work/calibre/src/calibre/gui2/email.py:236 -#: /home/kovid/work/calibre/src/calibre/gui2/email.py:245 -#: /home/kovid/work/calibre/src/calibre/gui2/library/models.py:421 -#: /home/kovid/work/calibre/src/calibre/gui2/library/models.py:440 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/scheduler.py:366 +#: /home/kovid/work/calibre/src/calibre/gui2/email.py:152 +#: /home/kovid/work/calibre/src/calibre/gui2/email.py:167 +#: /home/kovid/work/calibre/src/calibre/gui2/library/models.py:397 #: /home/kovid/work/calibre/src/calibre/gui2/library/models.py:972 -#: /home/kovid/work/calibre/src/calibre/gui2/library/models.py:1165 -#: /home/kovid/work/calibre/src/calibre/gui2/metadata/basic_widgets.py:70 -#: /home/kovid/work/calibre/src/calibre/gui2/metadata/basic_widgets.py:167 -#: /home/kovid/work/calibre/src/calibre/gui2/metadata/basic_widgets.py:185 -#: /home/kovid/work/calibre/src/calibre/gui2/metadata/bulk_download.py:112 -#: /home/kovid/work/calibre/src/calibre/gui2/viewer/main.py:197 -#: /home/kovid/work/calibre/src/calibre/library/cli.py:215 +#: /home/kovid/work/calibre/src/calibre/gui2/library/models.py:1148 +#: /home/kovid/work/calibre/src/calibre/gui2/library/models.py:1151 +#: /home/kovid/work/calibre/src/calibre/gui2/library/models.py:1154 +#: /home/kovid/work/calibre/src/calibre/gui2/library/models.py:1239 +#: /home/kovid/work/calibre/src/calibre/gui2/metadata/basic_widgets.py:82 +#: /home/kovid/work/calibre/src/calibre/gui2/metadata/basic_widgets.py:201 +#: /home/kovid/work/calibre/src/calibre/gui2/metadata/basic_widgets.py:220 +#: /home/kovid/work/calibre/src/calibre/gui2/metadata/bulk_download.py:119 +#: /home/kovid/work/calibre/src/calibre/gui2/metadata/single.py:358 +#: /home/kovid/work/calibre/src/calibre/gui2/metadata/single_download.py:160 +#: /home/kovid/work/calibre/src/calibre/gui2/metadata/single_download.py:164 +#: /home/kovid/work/calibre/src/calibre/gui2/store/google_books_plugin.py:90 +#: /home/kovid/work/calibre/src/calibre/gui2/store/search/models.py:151 +#: /home/kovid/work/calibre/src/calibre/gui2/viewer/main.py:199 +#: /home/kovid/work/calibre/src/calibre/library/cli.py:217 #: /home/kovid/work/calibre/src/calibre/library/database.py:914 -#: /home/kovid/work/calibre/src/calibre/library/database2.py:448 -#: /home/kovid/work/calibre/src/calibre/library/database2.py:454 -#: /home/kovid/work/calibre/src/calibre/library/database2.py:464 -#: /home/kovid/work/calibre/src/calibre/library/database2.py:1568 -#: /home/kovid/work/calibre/src/calibre/library/database2.py:1671 -#: /home/kovid/work/calibre/src/calibre/library/database2.py:2574 -#: /home/kovid/work/calibre/src/calibre/library/database2.py:2576 -#: /home/kovid/work/calibre/src/calibre/library/database2.py:2707 +#: /home/kovid/work/calibre/src/calibre/library/database2.py:502 +#: /home/kovid/work/calibre/src/calibre/library/database2.py:510 +#: /home/kovid/work/calibre/src/calibre/library/database2.py:521 +#: /home/kovid/work/calibre/src/calibre/library/database2.py:1800 +#: /home/kovid/work/calibre/src/calibre/library/database2.py:1937 +#: /home/kovid/work/calibre/src/calibre/library/database2.py:2944 +#: /home/kovid/work/calibre/src/calibre/library/database2.py:2946 +#: /home/kovid/work/calibre/src/calibre/library/database2.py:3079 #: /home/kovid/work/calibre/src/calibre/library/server/mobile.py:233 -#: /home/kovid/work/calibre/src/calibre/library/server/opds.py:158 -#: /home/kovid/work/calibre/src/calibre/library/server/opds.py:161 +#: /home/kovid/work/calibre/src/calibre/library/server/opds.py:156 +#: /home/kovid/work/calibre/src/calibre/library/server/opds.py:159 #: /home/kovid/work/calibre/src/calibre/library/server/xml.py:79 #: /home/kovid/work/calibre/src/calibre/utils/localization.py:131 #: /home/kovid/work/calibre/src/calibre/utils/podofo/__init__.py:46 #: /home/kovid/work/calibre/src/calibre/utils/podofo/__init__.py:64 #: /home/kovid/work/calibre/src/calibre/utils/podofo/__init__.py:78 -#: /home/kovid/work/calibre/src/calibre/web/feeds/recipes/collection.py:47 -#: /home/kovid/work/calibre/src/calibre/web/feeds/recipes/collection.py:55 +#: /home/kovid/work/calibre/src/calibre/web/feeds/recipes/collection.py:46 +#: /home/kovid/work/calibre/src/calibre/web/feeds/recipes/collection.py:54 msgid "Unknown" msgstr "Desconegut" -#: /home/kovid/work/calibre/src/calibre/customize/__init__.py:64 +#: /home/kovid/work/calibre/src/calibre/customize/__init__.py:77 msgid "Base" msgstr "Basa" -#: /home/kovid/work/calibre/src/calibre/customize/__init__.py:135 +#: /home/kovid/work/calibre/src/calibre/customize/__init__.py:148 msgid "Customize" msgstr "" -#: /home/kovid/work/calibre/src/calibre/customize/__init__.py:143 -#: /home/kovid/work/calibre/src/calibre/gui2/actions/preferences.py:39 -#: /home/kovid/work/calibre/src/calibre/gui2/actions/preferences.py:44 +#: /home/kovid/work/calibre/src/calibre/customize/__init__.py:156 +#: /home/kovid/work/calibre/src/calibre/gui2/actions/preferences.py:43 +#: /home/kovid/work/calibre/src/calibre/gui2/actions/preferences.py:48 msgid "Cannot configure" msgstr "" -#: /home/kovid/work/calibre/src/calibre/customize/__init__.py:305 +#: /home/kovid/work/calibre/src/calibre/customize/__init__.py:318 msgid "File type" msgstr "Tipe de fichièr" -#: /home/kovid/work/calibre/src/calibre/customize/__init__.py:341 +#: /home/kovid/work/calibre/src/calibre/customize/__init__.py:354 msgid "Metadata reader" msgstr "Lector de metadonadas" -#: /home/kovid/work/calibre/src/calibre/customize/__init__.py:371 +#: /home/kovid/work/calibre/src/calibre/customize/__init__.py:384 msgid "Metadata writer" msgstr "Editor de metadonadas" -#: /home/kovid/work/calibre/src/calibre/customize/__init__.py:401 +#: /home/kovid/work/calibre/src/calibre/customize/__init__.py:414 msgid "Catalog generator" msgstr "Generador de catalòg" -#: /home/kovid/work/calibre/src/calibre/customize/__init__.py:510 +#: /home/kovid/work/calibre/src/calibre/customize/__init__.py:523 msgid "User Interface Action" msgstr "" -#: /home/kovid/work/calibre/src/calibre/customize/__init__.py:536 +#: /home/kovid/work/calibre/src/calibre/customize/__init__.py:557 #: /home/kovid/work/calibre/src/calibre/gui2/actions/preferences.py:18 -#: /home/kovid/work/calibre/src/calibre/gui2/actions/preferences.py:23 -#: /home/kovid/work/calibre/src/calibre/gui2/preferences/main.py:190 -#: /home/kovid/work/calibre/src/calibre/gui2/preferences/main.py:280 -#: /home/kovid/work/calibre/src/calibre/gui2/preferences/main.py:302 +#: /home/kovid/work/calibre/src/calibre/gui2/actions/preferences.py:22 +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/main.py:197 +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/main.py:287 +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/main.py:309 #: /home/kovid/work/calibre/src/calibre/gui2/viewer/main_ui.py:206 msgid "Preferences" msgstr "" +#: /home/kovid/work/calibre/src/calibre/customize/__init__.py:609 +#: /home/kovid/work/calibre/src/calibre/gui2/store/search/models.py:33 +msgid "Store" +msgstr "" + #: /home/kovid/work/calibre/src/calibre/customize/builtins.py:18 msgid "" "Follow all local links in an HTML file and create a ZIP file containing all " @@ -241,242 +261,359 @@ msgid "" "are added to the archive." msgstr "" -#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:166 +#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:168 msgid "Extract cover from comic files" msgstr "" -#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:195 -#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:206 -#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:218 +#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:205 +#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:216 #: /home/kovid/work/calibre/src/calibre/customize/builtins.py:228 #: /home/kovid/work/calibre/src/calibre/customize/builtins.py:238 -#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:249 +#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:248 #: /home/kovid/work/calibre/src/calibre/customize/builtins.py:259 -#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:269 -#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:279 -#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:289 -#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:299 -#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:309 +#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:270 +#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:280 +#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:290 +#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:300 +#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:310 #: /home/kovid/work/calibre/src/calibre/customize/builtins.py:320 -#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:332 +#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:330 +#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:341 #: /home/kovid/work/calibre/src/calibre/customize/builtins.py:353 -#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:364 #: /home/kovid/work/calibre/src/calibre/customize/builtins.py:374 #: /home/kovid/work/calibre/src/calibre/customize/builtins.py:385 #: /home/kovid/work/calibre/src/calibre/customize/builtins.py:395 #: /home/kovid/work/calibre/src/calibre/customize/builtins.py:406 +#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:416 +#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:427 msgid "Read metadata from %s files" msgstr "Lectura de las metadonadas dempuèi los fichièrs %s" -#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:343 +#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:364 msgid "Read metadata from ebooks in RAR archives" msgstr "" -#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:417 +#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:438 msgid "Read metadata from ebooks in ZIP archives" msgstr "" -#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:430 -#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:440 -#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:450 +#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:451 #: /home/kovid/work/calibre/src/calibre/customize/builtins.py:472 -#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:483 -#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:493 +#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:482 +#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:504 +#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:515 +#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:525 msgid "Set metadata in %s files" msgstr "" #: /home/kovid/work/calibre/src/calibre/customize/builtins.py:461 -#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:504 +#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:493 +#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:536 msgid "Set metadata from %s files" msgstr "" -#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:824 +#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:873 msgid "Look and Feel" msgstr "" -#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:826 -#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:838 -#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:849 -#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:860 -#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:872 +#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:875 +#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:887 +#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:898 +#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:909 +#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:921 msgid "Interface" msgstr "" -#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:830 +#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:879 msgid "Adjust the look and feel of the calibre interface to suit your tastes" msgstr "" -#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:836 +#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:885 msgid "Behavior" msgstr "" -#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:842 +#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:891 msgid "Change the way calibre behaves" msgstr "" -#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:847 -#: /home/kovid/work/calibre/src/calibre/gui2/library/views.py:217 +#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:896 +#: /home/kovid/work/calibre/src/calibre/gui2/library/views.py:220 msgid "Add your own columns" msgstr "" -#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:853 +#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:902 msgid "Add/remove your own columns to the calibre book list" msgstr "" -#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:858 -msgid "Customize the toolbar" +#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:907 +msgid "Toolbar" msgstr "" -#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:864 +#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:913 msgid "" "Customize the toolbars and context menus, changing which actions are " "available in each" msgstr "" -#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:870 -msgid "Customize searching" +#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:919 +msgid "Searching" msgstr "" -#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:876 +#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:925 msgid "Customize the way searching for books works in calibre" msgstr "" -#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:881 +#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:930 msgid "Input Options" msgstr "" -#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:883 -#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:894 -#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:905 +#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:932 +#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:943 +#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:954 msgid "Conversion" msgstr "" -#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:887 +#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:936 msgid "Set conversion options specific to each input format" msgstr "" -#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:892 +#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:941 msgid "Common Options" msgstr "" -#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:898 +#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:947 msgid "Set conversion options common to all formats" msgstr "" -#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:903 +#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:952 msgid "Output Options" msgstr "" -#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:909 +#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:958 msgid "Set conversion options specific to each output format" msgstr "" -#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:914 +#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:963 msgid "Adding books" msgstr "" -#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:916 -#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:928 -#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:940 -#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:952 +#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:965 +#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:977 +#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:989 +#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:1001 msgid "Import/Export" msgstr "" -#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:920 +#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:969 msgid "Control how calibre reads metadata from files when adding books" msgstr "" -#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:926 +#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:975 msgid "Saving books to disk" msgstr "" -#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:932 +#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:981 msgid "" "Control how calibre exports files from its database to disk when using Save " "to disk" msgstr "" -#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:938 +#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:987 msgid "Sending books to devices" msgstr "" -#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:944 +#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:993 msgid "Control how calibre transfers files to your ebook reader" msgstr "" -#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:950 +#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:999 msgid "Metadata plugboards" msgstr "" -#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:956 +#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:1005 msgid "Change metadata fields before saving/sending" msgstr "" -#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:961 +#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:1010 msgid "Template Functions" msgstr "" -#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:963 -#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:999 -#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:1011 -#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:1022 +#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:1012 +#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:1059 +#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:1071 +#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:1082 msgid "Advanced" msgstr "" -#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:967 +#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:1016 msgid "Create your own template functions" msgstr "" -#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:972 +#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:1021 msgid "Sharing books by email" msgstr "" -#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:974 -#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:986 +#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:1023 +#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:1035 +#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:1048 msgid "Sharing" msgstr "" -#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:978 +#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:1027 msgid "" "Setup sharing of books via email. Can be used for automatic sending of " "downloaded news to your devices" msgstr "" -#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:984 +#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:1033 msgid "Sharing over the net" msgstr "" -#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:990 +#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:1039 msgid "" "Setup the calibre Content Server which will give you access to your calibre " "library from anywhere, on any device, over the internet" msgstr "" -#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:997 -#: /home/kovid/work/calibre/src/calibre/gui2/preferences/plugins.py:267 +#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:1046 +msgid "Metadata download" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:1052 +msgid "Control how calibre downloads ebook metadata from the net" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:1057 +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/plugins.py:273 msgid "Plugins" msgstr "" -#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:1003 +#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:1063 msgid "Add/remove/customize various bits of calibre functionality" msgstr "" -#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:1009 +#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:1069 msgid "Tweaks" msgstr "" -#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:1015 +#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:1075 msgid "Fine tune how calibre behaves in various contexts" msgstr "" -#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:1020 +#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:1080 msgid "Miscellaneous" msgstr "" -#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:1026 +#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:1086 msgid "Miscellaneous advanced configuration" msgstr "" +#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:1098 +msgid "Kindle books from Amazon." +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:1103 +msgid "Kindle books from Amazon.de." +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:1108 +msgid "Kindle books from Amazon.uk." +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:1113 +msgid "" +"Free Books : Download & Streaming : Ebook and Texts Archive : Internet " +"Archive." +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:1119 +msgid "Ebooks for readers." +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:1124 +msgid "Books, Textbooks, eBooks, Toys, Games and More." +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:1129 +msgid "Der eBook Shop." +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:1134 +msgid "Publishers of fine books." +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:1139 +msgid "World Famous eBook Store." +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:1144 +msgid "The digital bookstore." +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:1149 +msgid "EPUBReaders eBook Shop." +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:1154 +msgid "Entertain, enrich, inspire." +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:1159 +msgid "Read anywhere." +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:1164 +msgid "Foyles of London, online." +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:1170 +msgid "Zaczarowany świat książek" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:1175 +msgid "Google Books" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:1180 +msgid "The first producer of free ebooks." +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:1185 +msgid "eReading: anytime. anyplace." +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:1190 +msgid "The best ebooks at the best price: free!" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:1195 +msgid "Ebooks handcrafted with the utmost care." +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:1201 +msgid "Audiobooki mp3, ebooki, prasa - księgarnia internetowa." +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:1206 +msgid "One web page for every book." +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:1211 +msgid "DRM-Free tech ebooks." +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:1216 +msgid "The Pragmatic Bookshelf" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:1221 +msgid "Your ebook. Your way." +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:1226 +msgid "Feel every word." +msgstr "" + #: /home/kovid/work/calibre/src/calibre/customize/conversion.py:102 msgid "Conversion Input" msgstr "Conversion (entrada)" @@ -510,7 +647,7 @@ msgid "" msgstr "" #: /home/kovid/work/calibre/src/calibre/customize/profiles.py:61 -#: /home/kovid/work/calibre/src/calibre/customize/profiles.py:453 +#: /home/kovid/work/calibre/src/calibre/customize/profiles.py:454 msgid "" "This profile is intended for the SONY PRS line. The 500/505/600/700 etc." msgstr "" @@ -520,62 +657,62 @@ msgid "This profile is intended for the SONY PRS 300." msgstr "" #: /home/kovid/work/calibre/src/calibre/customize/profiles.py:82 -#: /home/kovid/work/calibre/src/calibre/customize/profiles.py:493 +#: /home/kovid/work/calibre/src/calibre/customize/profiles.py:494 msgid "This profile is intended for the SONY PRS-900." msgstr "" #: /home/kovid/work/calibre/src/calibre/customize/profiles.py:90 -#: /home/kovid/work/calibre/src/calibre/customize/profiles.py:538 +#: /home/kovid/work/calibre/src/calibre/customize/profiles.py:539 msgid "This profile is intended for the Microsoft Reader." msgstr "" #: /home/kovid/work/calibre/src/calibre/customize/profiles.py:101 -#: /home/kovid/work/calibre/src/calibre/customize/profiles.py:549 +#: /home/kovid/work/calibre/src/calibre/customize/profiles.py:550 msgid "This profile is intended for the Mobipocket books." msgstr "" #: /home/kovid/work/calibre/src/calibre/customize/profiles.py:114 -#: /home/kovid/work/calibre/src/calibre/customize/profiles.py:562 +#: /home/kovid/work/calibre/src/calibre/customize/profiles.py:563 msgid "This profile is intended for the Hanlin V3 and its clones." msgstr "" #: /home/kovid/work/calibre/src/calibre/customize/profiles.py:126 -#: /home/kovid/work/calibre/src/calibre/customize/profiles.py:574 +#: /home/kovid/work/calibre/src/calibre/customize/profiles.py:575 msgid "This profile is intended for the Hanlin V5 and its clones." msgstr "" #: /home/kovid/work/calibre/src/calibre/customize/profiles.py:136 -#: /home/kovid/work/calibre/src/calibre/customize/profiles.py:582 +#: /home/kovid/work/calibre/src/calibre/customize/profiles.py:583 msgid "This profile is intended for the Cybook G3." msgstr "Aqueste perfil es previst pel Cybook G3." #: /home/kovid/work/calibre/src/calibre/customize/profiles.py:149 -#: /home/kovid/work/calibre/src/calibre/customize/profiles.py:596 +#: /home/kovid/work/calibre/src/calibre/customize/profiles.py:597 msgid "This profile is intended for the Cybook Opus." msgstr "Aqueste perfil es previst pel Cybook Opus." #: /home/kovid/work/calibre/src/calibre/customize/profiles.py:161 -#: /home/kovid/work/calibre/src/calibre/customize/profiles.py:609 +#: /home/kovid/work/calibre/src/calibre/customize/profiles.py:610 msgid "This profile is intended for the Amazon Kindle." msgstr "Aqueste perfil es previst pel Kindle d'Amazon." #: /home/kovid/work/calibre/src/calibre/customize/profiles.py:173 -#: /home/kovid/work/calibre/src/calibre/customize/profiles.py:659 +#: /home/kovid/work/calibre/src/calibre/customize/profiles.py:660 msgid "This profile is intended for the Irex Illiad." msgstr "Aqueste perfil es previst per l'Iliad Irex." #: /home/kovid/work/calibre/src/calibre/customize/profiles.py:185 -#: /home/kovid/work/calibre/src/calibre/customize/profiles.py:672 +#: /home/kovid/work/calibre/src/calibre/customize/profiles.py:673 msgid "This profile is intended for the IRex Digital Reader 1000." msgstr "Aqueste perfil es previst per l'IRex Digital Reader 1000." #: /home/kovid/work/calibre/src/calibre/customize/profiles.py:198 -#: /home/kovid/work/calibre/src/calibre/customize/profiles.py:686 +#: /home/kovid/work/calibre/src/calibre/customize/profiles.py:687 msgid "This profile is intended for the IRex Digital Reader 800." msgstr "Aqueste perfil es previst pel lector IRex Digital 800." #: /home/kovid/work/calibre/src/calibre/customize/profiles.py:210 -#: /home/kovid/work/calibre/src/calibre/customize/profiles.py:700 +#: /home/kovid/work/calibre/src/calibre/customize/profiles.py:701 msgid "This profile is intended for the B&N Nook." msgstr "Aqueste perfil es previst pel Nook B&N." @@ -595,83 +732,79 @@ msgid "" "Intended for the iPad and similar devices with a resolution of 768x1024" msgstr "" -#: /home/kovid/work/calibre/src/calibre/customize/profiles.py:437 +#: /home/kovid/work/calibre/src/calibre/customize/profiles.py:438 msgid "Intended for generic tablet devices, does no resizing of images" msgstr "" -#: /home/kovid/work/calibre/src/calibre/customize/profiles.py:445 +#: /home/kovid/work/calibre/src/calibre/customize/profiles.py:446 msgid "" "Intended for the Samsung Galaxy and similar tablet devices with a resolution " "of 600x1280" msgstr "" -#: /home/kovid/work/calibre/src/calibre/customize/profiles.py:471 +#: /home/kovid/work/calibre/src/calibre/customize/profiles.py:472 msgid "This profile is intended for the Kobo Reader." msgstr "" -#: /home/kovid/work/calibre/src/calibre/customize/profiles.py:484 +#: /home/kovid/work/calibre/src/calibre/customize/profiles.py:485 msgid "This profile is intended for the SONY PRS-300." msgstr "Aqueste perfil es previst pel SONY PRS-300." -#: /home/kovid/work/calibre/src/calibre/customize/profiles.py:502 +#: /home/kovid/work/calibre/src/calibre/customize/profiles.py:503 msgid "Suitable for use with any e-ink device" msgstr "" -#: /home/kovid/work/calibre/src/calibre/customize/profiles.py:509 +#: /home/kovid/work/calibre/src/calibre/customize/profiles.py:510 msgid "Suitable for use with any large screen e-ink device" msgstr "" -#: /home/kovid/work/calibre/src/calibre/customize/profiles.py:518 +#: /home/kovid/work/calibre/src/calibre/customize/profiles.py:519 msgid "This profile is intended for the 5-inch JetBook." msgstr "" -#: /home/kovid/work/calibre/src/calibre/customize/profiles.py:527 +#: /home/kovid/work/calibre/src/calibre/customize/profiles.py:528 msgid "" "This profile is intended for the SONY PRS line. The 500/505/700 etc, in " "landscape mode. Mainly useful for comics." msgstr "" -#: /home/kovid/work/calibre/src/calibre/customize/profiles.py:635 +#: /home/kovid/work/calibre/src/calibre/customize/profiles.py:636 msgid "This profile is intended for the Amazon Kindle DX." msgstr "Aqueste perfil es previst pel Kindle DX d'Amazon." -#: /home/kovid/work/calibre/src/calibre/customize/profiles.py:712 +#: /home/kovid/work/calibre/src/calibre/customize/profiles.py:713 msgid "This profile is intended for the B&N Nook Color." msgstr "" -#: /home/kovid/work/calibre/src/calibre/customize/profiles.py:723 +#: /home/kovid/work/calibre/src/calibre/customize/profiles.py:724 msgid "This profile is intended for the Sanda Bambook." msgstr "" -#: /home/kovid/work/calibre/src/calibre/customize/ui.py:35 +#: /home/kovid/work/calibre/src/calibre/customize/ui.py:31 msgid "Installed plugins" msgstr "" -#: /home/kovid/work/calibre/src/calibre/customize/ui.py:36 +#: /home/kovid/work/calibre/src/calibre/customize/ui.py:32 msgid "Mapping for filetype plugins" msgstr "" -#: /home/kovid/work/calibre/src/calibre/customize/ui.py:37 +#: /home/kovid/work/calibre/src/calibre/customize/ui.py:33 msgid "Local plugin customization" msgstr "" -#: /home/kovid/work/calibre/src/calibre/customize/ui.py:38 +#: /home/kovid/work/calibre/src/calibre/customize/ui.py:34 msgid "Disabled plugins" msgstr "Moduls extèrnes desactivats" -#: /home/kovid/work/calibre/src/calibre/customize/ui.py:39 +#: /home/kovid/work/calibre/src/calibre/customize/ui.py:35 msgid "Enabled plugins" msgstr "" -#: /home/kovid/work/calibre/src/calibre/customize/ui.py:94 -msgid "No valid plugin found in " -msgstr "" - -#: /home/kovid/work/calibre/src/calibre/customize/ui.py:520 +#: /home/kovid/work/calibre/src/calibre/customize/ui.py:470 msgid "Initialization of plugin %s failed with traceback:" msgstr "" -#: /home/kovid/work/calibre/src/calibre/customize/ui.py:553 +#: /home/kovid/work/calibre/src/calibre/customize/ui.py:508 msgid "" " %prog options\n" "\n" @@ -679,33 +812,33 @@ msgid "" " " msgstr "" -#: /home/kovid/work/calibre/src/calibre/customize/ui.py:559 +#: /home/kovid/work/calibre/src/calibre/customize/ui.py:514 msgid "Add a plugin by specifying the path to the zip file containing it." msgstr "" -#: /home/kovid/work/calibre/src/calibre/customize/ui.py:561 +#: /home/kovid/work/calibre/src/calibre/customize/ui.py:516 msgid "Remove a custom plugin by name. Has no effect on builtin plugins" msgstr "" -#: /home/kovid/work/calibre/src/calibre/customize/ui.py:563 +#: /home/kovid/work/calibre/src/calibre/customize/ui.py:518 msgid "" "Customize plugin. Specify name of plugin and customization string separated " "by a comma." msgstr "" -#: /home/kovid/work/calibre/src/calibre/customize/ui.py:565 +#: /home/kovid/work/calibre/src/calibre/customize/ui.py:520 msgid "List all installed plugins" msgstr "" -#: /home/kovid/work/calibre/src/calibre/customize/ui.py:567 +#: /home/kovid/work/calibre/src/calibre/customize/ui.py:522 msgid "Enable the named plugin" msgstr "" -#: /home/kovid/work/calibre/src/calibre/customize/ui.py:569 +#: /home/kovid/work/calibre/src/calibre/customize/ui.py:524 msgid "Disable the named plugin" msgstr "" -#: /home/kovid/work/calibre/src/calibre/debug.py:150 +#: /home/kovid/work/calibre/src/calibre/debug.py:152 msgid "Debug log" msgstr "" @@ -713,99 +846,129 @@ msgstr "" msgid "Communicate with Android phones." msgstr "" -#: /home/kovid/work/calibre/src/calibre/devices/android/driver.py:74 +#: /home/kovid/work/calibre/src/calibre/devices/android/driver.py:96 msgid "" "Comma separated list of directories to send e-books to on the device. The " "first one that exists will be used" msgstr "" -#: /home/kovid/work/calibre/src/calibre/devices/android/driver.py:121 +#: /home/kovid/work/calibre/src/calibre/devices/android/driver.py:146 msgid "Communicate with S60 phones." msgstr "" -#: /home/kovid/work/calibre/src/calibre/devices/apple/driver.py:92 +#: /home/kovid/work/calibre/src/calibre/devices/apple/driver.py:47 +msgid "" +"

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

    To " +"transfer books to your iDevice, click Disable Apple Driver, then use " +"the 'Connect to iTunes' method recommended in the Calibre + " +"iDevices FAQ, using the Connect/Share|Connect to " +"iTunes menu item.

    Enabling the Apple driver for direct connection " +"to iDevices is an unsupported advanced user mode.

    " +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/devices/apple/driver.py:65 +msgid "Disable Apple driver" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/devices/apple/driver.py:69 +msgid "Enable Apple driver" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/devices/apple/driver.py:117 +msgid "Use Series as Category in iTunes/iBooks" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/devices/apple/driver.py:118 +msgid "Enable to use the series name as the iTunes Genre, iBooks Category" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/devices/apple/driver.py:120 +msgid "Cache covers from iTunes/iBooks" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/devices/apple/driver.py:122 +msgid "Enable to cache and display covers from iTunes/iBooks" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/devices/apple/driver.py:178 msgid "Apple device" msgstr "" -#: /home/kovid/work/calibre/src/calibre/devices/apple/driver.py:94 +#: /home/kovid/work/calibre/src/calibre/devices/apple/driver.py:180 msgid "Communicate with iTunes/iBooks." msgstr "" -#: /home/kovid/work/calibre/src/calibre/devices/apple/driver.py:100 +#: /home/kovid/work/calibre/src/calibre/devices/apple/driver.py:192 msgid "Apple device detected, launching iTunes, please wait ..." msgstr "" -#: /home/kovid/work/calibre/src/calibre/devices/apple/driver.py:102 +#: /home/kovid/work/calibre/src/calibre/devices/apple/driver.py:194 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:262 -#: /home/kovid/work/calibre/src/calibre/devices/apple/driver.py:265 +#: /home/kovid/work/calibre/src/calibre/devices/apple/driver.py:357 +#: /home/kovid/work/calibre/src/calibre/devices/apple/driver.py:360 msgid "Updating device metadata listing..." msgstr "" -#: /home/kovid/work/calibre/src/calibre/devices/apple/driver.py:341 -#: /home/kovid/work/calibre/src/calibre/devices/apple/driver.py:380 -#: /home/kovid/work/calibre/src/calibre/devices/apple/driver.py:949 -#: /home/kovid/work/calibre/src/calibre/devices/apple/driver.py:989 -#: /home/kovid/work/calibre/src/calibre/devices/apple/driver.py:2971 -#: /home/kovid/work/calibre/src/calibre/devices/apple/driver.py:3011 +#: /home/kovid/work/calibre/src/calibre/devices/apple/driver.py:436 +#: /home/kovid/work/calibre/src/calibre/devices/apple/driver.py:475 +#: /home/kovid/work/calibre/src/calibre/devices/apple/driver.py:1057 +#: /home/kovid/work/calibre/src/calibre/devices/apple/driver.py:1101 +#: /home/kovid/work/calibre/src/calibre/devices/apple/driver.py:3097 +#: /home/kovid/work/calibre/src/calibre/devices/apple/driver.py:3137 msgid "%d of %d" msgstr "" -#: /home/kovid/work/calibre/src/calibre/devices/apple/driver.py:387 -#: /home/kovid/work/calibre/src/calibre/devices/apple/driver.py:994 -#: /home/kovid/work/calibre/src/calibre/devices/apple/driver.py:3017 +#: /home/kovid/work/calibre/src/calibre/devices/apple/driver.py:482 +#: /home/kovid/work/calibre/src/calibre/devices/apple/driver.py:1106 +#: /home/kovid/work/calibre/src/calibre/devices/apple/driver.py:3143 +#: /home/kovid/work/calibre/src/calibre/gui2/ebook_download.py:106 msgid "finished" msgstr "" -#: /home/kovid/work/calibre/src/calibre/devices/apple/driver.py:562 -msgid "Use Series as Category in iTunes/iBooks" -msgstr "" - -#: /home/kovid/work/calibre/src/calibre/devices/apple/driver.py:564 -msgid "Cache covers from iTunes/iBooks" -msgstr "" - -#: /home/kovid/work/calibre/src/calibre/devices/apple/driver.py:576 +#: /home/kovid/work/calibre/src/calibre/devices/apple/driver.py:667 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:913 +#: /home/kovid/work/calibre/src/calibre/devices/apple/driver.py:1018 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:2552 +#: /home/kovid/work/calibre/src/calibre/devices/apple/driver.py:2668 #: /home/kovid/work/calibre/src/calibre/devices/nook/driver.py:100 #: /home/kovid/work/calibre/src/calibre/devices/prs505/sony_cache.py:447 #: /home/kovid/work/calibre/src/calibre/devices/prs505/sony_cache.py:470 -#: /home/kovid/work/calibre/src/calibre/devices/usbms/device.py:908 -#: /home/kovid/work/calibre/src/calibre/devices/usbms/device.py:914 -#: /home/kovid/work/calibre/src/calibre/devices/usbms/device.py:944 -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/scheduler.py:262 -#: /home/kovid/work/calibre/src/calibre/library/database2.py:255 -#: /home/kovid/work/calibre/src/calibre/library/database2.py:268 -#: /home/kovid/work/calibre/src/calibre/library/database2.py:2438 -#: /home/kovid/work/calibre/src/calibre/library/field_metadata.py:150 +#: /home/kovid/work/calibre/src/calibre/devices/usbms/device.py:909 +#: /home/kovid/work/calibre/src/calibre/devices/usbms/device.py:915 +#: /home/kovid/work/calibre/src/calibre/devices/usbms/device.py:945 +#: /home/kovid/work/calibre/src/calibre/gui2/actions/fetch_news.py:73 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/scheduler.py:445 +#: /home/kovid/work/calibre/src/calibre/library/database2.py:298 +#: /home/kovid/work/calibre/src/calibre/library/database2.py:311 +#: /home/kovid/work/calibre/src/calibre/library/database2.py:2808 +#: /home/kovid/work/calibre/src/calibre/library/field_metadata.py:159 msgid "News" msgstr "Nòvas" -#: /home/kovid/work/calibre/src/calibre/devices/apple/driver.py:2553 +#: /home/kovid/work/calibre/src/calibre/devices/apple/driver.py:2669 #: /home/kovid/work/calibre/src/calibre/gui2/catalog/catalog_epub_mobi.py:65 -#: /home/kovid/work/calibre/src/calibre/library/catalog.py:634 -#: /home/kovid/work/calibre/src/calibre/library/database2.py:2401 -#: /home/kovid/work/calibre/src/calibre/library/database2.py:2419 +#: /home/kovid/work/calibre/src/calibre/library/catalog.py:643 +#: /home/kovid/work/calibre/src/calibre/library/database2.py:2768 +#: /home/kovid/work/calibre/src/calibre/library/database2.py:2786 msgid "Catalog" msgstr "" -#: /home/kovid/work/calibre/src/calibre/devices/apple/driver.py:2875 +#: /home/kovid/work/calibre/src/calibre/devices/apple/driver.py:3001 msgid "Communicate with iTunes." msgstr "" @@ -848,30 +1011,30 @@ msgstr "" #: /home/kovid/work/calibre/src/calibre/devices/kobo/driver.py:67 #: /home/kovid/work/calibre/src/calibre/devices/kobo/driver.py:70 #: /home/kovid/work/calibre/src/calibre/devices/kobo/driver.py:73 -#: /home/kovid/work/calibre/src/calibre/devices/kobo/driver.py:226 -#: /home/kovid/work/calibre/src/calibre/devices/usbms/driver.py:68 -#: /home/kovid/work/calibre/src/calibre/devices/usbms/driver.py:71 -#: /home/kovid/work/calibre/src/calibre/devices/usbms/driver.py:74 -#: /home/kovid/work/calibre/src/calibre/devices/usbms/driver.py:138 -#: /home/kovid/work/calibre/src/calibre/devices/usbms/driver.py:145 -#: /home/kovid/work/calibre/src/calibre/devices/usbms/driver.py:168 +#: /home/kovid/work/calibre/src/calibre/devices/kobo/driver.py:232 +#: /home/kovid/work/calibre/src/calibre/devices/usbms/driver.py:122 +#: /home/kovid/work/calibre/src/calibre/devices/usbms/driver.py:125 +#: /home/kovid/work/calibre/src/calibre/devices/usbms/driver.py:128 +#: /home/kovid/work/calibre/src/calibre/devices/usbms/driver.py:196 +#: /home/kovid/work/calibre/src/calibre/devices/usbms/driver.py:203 +#: /home/kovid/work/calibre/src/calibre/devices/usbms/driver.py:226 msgid "Getting list of books on device..." 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:279 -#: /home/kovid/work/calibre/src/calibre/devices/usbms/driver.py:197 -#: /home/kovid/work/calibre/src/calibre/devices/usbms/driver.py:199 +#: /home/kovid/work/calibre/src/calibre/devices/usbms/driver.py:255 +#: /home/kovid/work/calibre/src/calibre/devices/usbms/driver.py:257 msgid "Transferring books to device..." msgstr "" #: /home/kovid/work/calibre/src/calibre/devices/bambook/driver.py:285 #: /home/kovid/work/calibre/src/calibre/devices/bambook/driver.py:299 -#: /home/kovid/work/calibre/src/calibre/devices/kobo/driver.py:343 -#: /home/kovid/work/calibre/src/calibre/devices/kobo/driver.py:378 -#: /home/kovid/work/calibre/src/calibre/devices/usbms/driver.py:221 -#: /home/kovid/work/calibre/src/calibre/devices/usbms/driver.py:252 +#: /home/kovid/work/calibre/src/calibre/devices/kobo/driver.py:349 +#: /home/kovid/work/calibre/src/calibre/devices/kobo/driver.py:384 +#: /home/kovid/work/calibre/src/calibre/devices/usbms/driver.py:279 +#: /home/kovid/work/calibre/src/calibre/devices/usbms/driver.py:310 msgid "Adding books to device metadata listing..." msgstr "" @@ -879,28 +1042,28 @@ msgstr "" #: /home/kovid/work/calibre/src/calibre/devices/bambook/driver.py:309 #: /home/kovid/work/calibre/src/calibre/devices/hanvon/driver.py:102 #: /home/kovid/work/calibre/src/calibre/devices/hanvon/driver.py:113 -#: /home/kovid/work/calibre/src/calibre/devices/kobo/driver.py:295 -#: /home/kovid/work/calibre/src/calibre/devices/kobo/driver.py:327 -#: /home/kovid/work/calibre/src/calibre/devices/usbms/driver.py:258 -#: /home/kovid/work/calibre/src/calibre/devices/usbms/driver.py:276 +#: /home/kovid/work/calibre/src/calibre/devices/kobo/driver.py:301 +#: /home/kovid/work/calibre/src/calibre/devices/kobo/driver.py:333 +#: /home/kovid/work/calibre/src/calibre/devices/usbms/driver.py:316 +#: /home/kovid/work/calibre/src/calibre/devices/usbms/driver.py:334 msgid "Removing books from device..." msgstr "" #: /home/kovid/work/calibre/src/calibre/devices/bambook/driver.py:324 #: /home/kovid/work/calibre/src/calibre/devices/bambook/driver.py:329 -#: /home/kovid/work/calibre/src/calibre/devices/kobo/driver.py:331 -#: /home/kovid/work/calibre/src/calibre/devices/kobo/driver.py:338 -#: /home/kovid/work/calibre/src/calibre/devices/usbms/driver.py:283 -#: /home/kovid/work/calibre/src/calibre/devices/usbms/driver.py:288 +#: /home/kovid/work/calibre/src/calibre/devices/kobo/driver.py:337 +#: /home/kovid/work/calibre/src/calibre/devices/kobo/driver.py:344 +#: /home/kovid/work/calibre/src/calibre/devices/usbms/driver.py:341 +#: /home/kovid/work/calibre/src/calibre/devices/usbms/driver.py:346 msgid "Removing books from device metadata listing..." msgstr "" #: /home/kovid/work/calibre/src/calibre/devices/bambook/driver.py:397 -#: /home/kovid/work/calibre/src/calibre/devices/usbms/driver.py:318 +#: /home/kovid/work/calibre/src/calibre/devices/usbms/driver.py:376 msgid "Sending metadata to device..." msgstr "" -#: /home/kovid/work/calibre/src/calibre/devices/bambook/libbambookcore.py:132 +#: /home/kovid/work/calibre/src/calibre/devices/bambook/libbambookcore.py:129 msgid "Bambook SDK has not been installed." msgstr "" @@ -913,12 +1076,20 @@ msgid "Communicate with the Blackberry smart phone." msgstr "" #: /home/kovid/work/calibre/src/calibre/devices/blackberry/driver.py:14 -#: /home/kovid/work/calibre/src/calibre/devices/eb600/driver.py:253 +#: /home/kovid/work/calibre/src/calibre/devices/eb600/driver.py:254 #: /home/kovid/work/calibre/src/calibre/devices/nuut2/driver.py:18 #: /home/kovid/work/calibre/src/calibre/devices/prs500/driver.py:90 msgid "Kovid Goyal" msgstr "Kovid Goyal" +#: /home/kovid/work/calibre/src/calibre/devices/boeye/driver.py:14 +msgid "Communicate with BOEYE BEX Serial eBook readers." +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/devices/boeye/driver.py:35 +msgid "Communicate with BOEYE BDX serial eBook readers." +msgstr "" + #: /home/kovid/work/calibre/src/calibre/devices/cybook/driver.py:22 msgid "Communicate with the Cybook Gen 3 / Opus eBook reader." msgstr "" @@ -943,7 +1114,7 @@ msgstr "" msgid "Communicate with the PocketBook 602/603/902/903 reader." msgstr "" -#: /home/kovid/work/calibre/src/calibre/devices/eb600/driver.py:252 +#: /home/kovid/work/calibre/src/calibre/devices/eb600/driver.py:253 msgid "Communicate with the PocketBook 701" msgstr "" @@ -981,7 +1152,7 @@ msgstr "Comunica amb los lectors d'ebook Hanlin V3." msgid "Communicate with Hanlin V5 eBook readers." msgstr "Comunica amb los lectors d'ebook Hanlin V5." -#: /home/kovid/work/calibre/src/calibre/devices/hanlin/driver.py:115 +#: /home/kovid/work/calibre/src/calibre/devices/hanlin/driver.py:114 msgid "Communicate with the BOOX eBook reader." msgstr "Comunica amb lo lector d'ebook BOOX." @@ -1017,7 +1188,7 @@ msgstr "Comunica amb lo lector d'ebook Iliad IRex." #: /home/kovid/work/calibre/src/calibre/devices/iliad/driver.py:17 #: /home/kovid/work/calibre/src/calibre/devices/irexdr/driver.py:18 -#: /home/kovid/work/calibre/src/calibre/devices/usbms/driver.py:42 +#: /home/kovid/work/calibre/src/calibre/devices/usbms/driver.py:43 msgid "John Schember" msgstr "John Schember" @@ -1103,67 +1274,59 @@ msgid "" "Create a tag called \"Im_Reading\" " msgstr "" -#: /home/kovid/work/calibre/src/calibre/devices/kobo/driver.py:462 -#: /home/kovid/work/calibre/src/calibre/gui2/actions/add.py:315 +#: /home/kovid/work/calibre/src/calibre/devices/kobo/driver.py:468 +#: /home/kovid/work/calibre/src/calibre/gui2/actions/add.py:388 msgid "Not Implemented" msgstr "" -#: /home/kovid/work/calibre/src/calibre/devices/kobo/driver.py:463 +#: /home/kovid/work/calibre/src/calibre/devices/kobo/driver.py:469 msgid "" "\".kobo\" files do not exist on the device as books instead, they are rows " "in the sqlite database. Currently they cannot be exported or viewed." msgstr "" -#: /home/kovid/work/calibre/src/calibre/devices/misc.py:17 +#: /home/kovid/work/calibre/src/calibre/devices/misc.py:19 msgid "Communicate with the Palm Pre" msgstr "" -#: /home/kovid/work/calibre/src/calibre/devices/misc.py:37 +#: /home/kovid/work/calibre/src/calibre/devices/misc.py:39 msgid "Communicate with the Bq Avant" msgstr "" -#: /home/kovid/work/calibre/src/calibre/devices/misc.py:58 -msgid "Communicate with the Sweex MM300" +#: /home/kovid/work/calibre/src/calibre/devices/misc.py:60 +msgid "Communicate with the Sweex/Kogan/Q600/Wink" msgstr "" -#: /home/kovid/work/calibre/src/calibre/devices/misc.py:79 -msgid "Communicate with the Digma Q600" -msgstr "" - -#: /home/kovid/work/calibre/src/calibre/devices/misc.py:88 -msgid "Communicate with the Kogan" -msgstr "" - -#: /home/kovid/work/calibre/src/calibre/devices/misc.py:96 -#: /home/kovid/work/calibre/src/calibre/devices/misc.py:123 +#: /home/kovid/work/calibre/src/calibre/devices/misc.py:81 +#: /home/kovid/work/calibre/src/calibre/devices/misc.py:108 msgid "Communicate with the Pandigital Novel" msgstr "" -#: /home/kovid/work/calibre/src/calibre/devices/misc.py:142 +#: /home/kovid/work/calibre/src/calibre/devices/misc.py:127 msgid "Communicate with the VelocityMicro" msgstr "" -#: /home/kovid/work/calibre/src/calibre/devices/misc.py:160 +#: /home/kovid/work/calibre/src/calibre/devices/misc.py:145 msgid "Communicate with the GM2000" msgstr "" -#: /home/kovid/work/calibre/src/calibre/devices/misc.py:180 +#: /home/kovid/work/calibre/src/calibre/devices/misc.py:165 msgid "Communicate with the Acer Lumiread" msgstr "" -#: /home/kovid/work/calibre/src/calibre/devices/misc.py:214 +#: /home/kovid/work/calibre/src/calibre/devices/misc.py:199 msgid "Communicate with the Aluratek Color" msgstr "" -#: /home/kovid/work/calibre/src/calibre/devices/misc.py:234 +#: /home/kovid/work/calibre/src/calibre/devices/misc.py:219 msgid "Communicate with the Trekstor" msgstr "" -#: /home/kovid/work/calibre/src/calibre/devices/misc.py:254 +#: /home/kovid/work/calibre/src/calibre/devices/misc.py:239 msgid "Communicate with the EEE Reader" msgstr "" -#: /home/kovid/work/calibre/src/calibre/devices/misc.py:274 +#: /home/kovid/work/calibre/src/calibre/devices/misc.py:259 msgid "Communicate with the Nextbook Reader" msgstr "" @@ -1207,32 +1370,32 @@ msgstr "Comunica amb lo lector d'ebook Sony PRS-500." msgid "Communicate with all the Sony eBook readers." msgstr "" -#: /home/kovid/work/calibre/src/calibre/devices/prs505/driver.py:61 +#: /home/kovid/work/calibre/src/calibre/devices/prs505/driver.py:62 msgid "All by title" msgstr "" -#: /home/kovid/work/calibre/src/calibre/devices/prs505/driver.py:62 +#: /home/kovid/work/calibre/src/calibre/devices/prs505/driver.py:63 msgid "All by author" msgstr "" -#: /home/kovid/work/calibre/src/calibre/devices/prs505/driver.py:65 +#: /home/kovid/work/calibre/src/calibre/devices/prs505/driver.py:66 msgid "" "Comma separated list of metadata fields to turn into collections on the " "device. Possibilities include: " msgstr "" -#: /home/kovid/work/calibre/src/calibre/devices/prs505/driver.py:68 +#: /home/kovid/work/calibre/src/calibre/devices/prs505/driver.py:69 msgid "" ". Two special collections are available: %s:%s and %s:%s. Add these values " "to the list to enable them. The collections will be given the name provided " "after the \":\" character." msgstr "" -#: /home/kovid/work/calibre/src/calibre/devices/prs505/driver.py:72 +#: /home/kovid/work/calibre/src/calibre/devices/prs505/driver.py:73 msgid "Upload separate cover thumbnails for books (newer readers)" msgstr "" -#: /home/kovid/work/calibre/src/calibre/devices/prs505/driver.py:73 +#: /home/kovid/work/calibre/src/calibre/devices/prs505/driver.py:74 msgid "" "Normally, the SONY readers get the cover image from the ebook file itself. " "With this option, calibre will send a separate cover image to the reader, " @@ -1241,31 +1404,42 @@ msgid "" "950 and newer." msgstr "" -#: /home/kovid/work/calibre/src/calibre/devices/prs505/driver.py:79 +#: /home/kovid/work/calibre/src/calibre/devices/prs505/driver.py:80 msgid "" "Refresh separate covers when using automatic management (newer readers)" msgstr "" -#: /home/kovid/work/calibre/src/calibre/devices/prs505/driver.py:81 +#: /home/kovid/work/calibre/src/calibre/devices/prs505/driver.py:82 msgid "" "Set this option to have separate book covers uploaded every time you connect " "your device. Unset this option if you have so many books on the reader that " "performance is unacceptable." msgstr "" -#: /home/kovid/work/calibre/src/calibre/devices/prs505/driver.py:85 +#: /home/kovid/work/calibre/src/calibre/devices/prs505/driver.py:86 msgid "Preserve cover aspect ratio when building thumbnails" msgstr "" -#: /home/kovid/work/calibre/src/calibre/devices/prs505/driver.py:87 +#: /home/kovid/work/calibre/src/calibre/devices/prs505/driver.py:88 msgid "" "Set this option if you want the cover thumbnails to have the same aspect " "ratio (width to height) as the cover. Unset it if you want the thumbnail to " "be the maximum size, ignoring aspect ratio." msgstr "" +#: /home/kovid/work/calibre/src/calibre/devices/prs505/driver.py:92 +msgid "Search for books in all folders" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/devices/prs505/driver.py:94 +msgid "" +"Setting this option tells calibre to look for books in all folders on the " +"device and its cards. This permits calibre to find books put on the device " +"by other software and by wireless download." +msgstr "" + #: /home/kovid/work/calibre/src/calibre/devices/prs505/sony_cache.py:190 -#: /home/kovid/work/calibre/src/calibre/ebooks/oeb/transforms/structure.py:68 +#: /home/kovid/work/calibre/src/calibre/ebooks/oeb/transforms/structure.py:69 msgid "Unnamed" msgstr "" @@ -1305,6 +1479,10 @@ msgstr "" msgid "Communicate with the Stash W950 reader." msgstr "" +#: /home/kovid/work/calibre/src/calibre/devices/teclast/driver.py:111 +msgid "Communicate with the Wexler reader." +msgstr "" + #: /home/kovid/work/calibre/src/calibre/devices/usbms/device.py:282 msgid "Unable to detect the %s disk drive. Try rebooting." msgstr "" @@ -1337,21 +1515,21 @@ msgid "" "system errors." msgstr "" -#: /home/kovid/work/calibre/src/calibre/devices/usbms/device.py:841 -#: /home/kovid/work/calibre/src/calibre/devices/usbms/device.py:843 +#: /home/kovid/work/calibre/src/calibre/devices/usbms/device.py:842 +#: /home/kovid/work/calibre/src/calibre/devices/usbms/device.py:844 msgid "The reader has no storage card in this slot." msgstr "" -#: /home/kovid/work/calibre/src/calibre/devices/usbms/device.py:845 +#: /home/kovid/work/calibre/src/calibre/devices/usbms/device.py:846 msgid "Selected slot: %s is not supported." msgstr "" -#: /home/kovid/work/calibre/src/calibre/devices/usbms/device.py:874 +#: /home/kovid/work/calibre/src/calibre/devices/usbms/device.py:875 msgid "There is insufficient free space in main memory" msgstr "" -#: /home/kovid/work/calibre/src/calibre/devices/usbms/device.py:876 -#: /home/kovid/work/calibre/src/calibre/devices/usbms/device.py:878 +#: /home/kovid/work/calibre/src/calibre/devices/usbms/device.py:877 +#: /home/kovid/work/calibre/src/calibre/devices/usbms/device.py:879 msgid "There is insufficient free space on the storage card" msgstr "" @@ -1388,106 +1566,179 @@ msgstr "" msgid "Extra customization" msgstr "" -#: /home/kovid/work/calibre/src/calibre/devices/usbms/driver.py:41 +#: /home/kovid/work/calibre/src/calibre/devices/usbms/driver.py:42 msgid "Communicate with an eBook reader." msgstr "" -#: /home/kovid/work/calibre/src/calibre/devices/usbms/driver.py:57 +#: /home/kovid/work/calibre/src/calibre/devices/usbms/driver.py:94 msgid "Get device information..." msgstr "" -#: /home/kovid/work/calibre/src/calibre/ebooks/comic/input.py:190 +#: /home/kovid/work/calibre/src/calibre/devices/user_defined/driver.py:37 +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/device_user_defined.py:68 +msgid "USB Vendor ID (in hex)" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/devices/user_defined/driver.py:38 +#: /home/kovid/work/calibre/src/calibre/devices/user_defined/driver.py:41 +#: /home/kovid/work/calibre/src/calibre/devices/user_defined/driver.py:44 +msgid "" +"Get this ID using Preferences -> Misc -> Get information to set up the user-" +"defined device" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/devices/user_defined/driver.py:40 +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/device_user_defined.py:70 +msgid "USB Product ID (in hex)" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/devices/user_defined/driver.py:43 +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/device_user_defined.py:72 +msgid "USB Revision ID (in hex)" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/devices/user_defined/driver.py:47 +msgid "Windows main memory vendor string" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/devices/user_defined/driver.py:48 +#: /home/kovid/work/calibre/src/calibre/devices/user_defined/driver.py:52 +#: /home/kovid/work/calibre/src/calibre/devices/user_defined/driver.py:56 +#: /home/kovid/work/calibre/src/calibre/devices/user_defined/driver.py:60 +msgid "" +"This field is used only on windows. Get this ID using Preferences -> Misc -> " +"Get information to set up the user-defined device" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/devices/user_defined/driver.py:51 +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/device_user_defined.py:79 +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/device_user_defined.py:81 +msgid "Windows main memory ID string" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/devices/user_defined/driver.py:55 +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/device_user_defined.py:84 +msgid "Windows card A vendor string" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/devices/user_defined/driver.py:59 +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/device_user_defined.py:86 +msgid "Windows card A ID string" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/devices/user_defined/driver.py:63 +msgid "Main memory folder" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/devices/user_defined/driver.py:64 +#: /home/kovid/work/calibre/src/calibre/devices/user_defined/driver.py:67 +msgid "" +"Enter the folder where the books are to be stored. This folder is prepended " +"to any send_to_device template" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/devices/user_defined/driver.py:66 +msgid "Card A folder" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/ebooks/comic/input.py:202 msgid "Rendered %s" msgstr "" -#: /home/kovid/work/calibre/src/calibre/ebooks/comic/input.py:193 +#: /home/kovid/work/calibre/src/calibre/ebooks/comic/input.py:205 msgid "Failed %s" msgstr "" -#: /home/kovid/work/calibre/src/calibre/ebooks/comic/input.py:247 +#: /home/kovid/work/calibre/src/calibre/ebooks/comic/input.py:259 msgid "" "Failed to process comic: \n" "\n" "%s" msgstr "" -#: /home/kovid/work/calibre/src/calibre/ebooks/comic/input.py:266 +#: /home/kovid/work/calibre/src/calibre/ebooks/comic/input.py:278 msgid "" "Number of colors for grayscale image conversion. Default: %default. Values " "of less than 256 may result in blurred text on your device if you are " "creating your comics in EPUB format." msgstr "" -#: /home/kovid/work/calibre/src/calibre/ebooks/comic/input.py:270 +#: /home/kovid/work/calibre/src/calibre/ebooks/comic/input.py:282 msgid "" "Disable normalize (improve contrast) color range for pictures. Default: False" msgstr "" -#: /home/kovid/work/calibre/src/calibre/ebooks/comic/input.py:273 +#: /home/kovid/work/calibre/src/calibre/ebooks/comic/input.py:285 msgid "Maintain picture aspect ratio. Default is to fill the screen." msgstr "" -#: /home/kovid/work/calibre/src/calibre/ebooks/comic/input.py:275 +#: /home/kovid/work/calibre/src/calibre/ebooks/comic/input.py:287 msgid "Disable sharpening." msgstr "" -#: /home/kovid/work/calibre/src/calibre/ebooks/comic/input.py:277 +#: /home/kovid/work/calibre/src/calibre/ebooks/comic/input.py:289 msgid "" "Disable trimming of comic pages. For some comics, trimming might remove " "content as well as borders." msgstr "" -#: /home/kovid/work/calibre/src/calibre/ebooks/comic/input.py:280 +#: /home/kovid/work/calibre/src/calibre/ebooks/comic/input.py:292 msgid "Don't split landscape images into two portrait images" msgstr "" -#: /home/kovid/work/calibre/src/calibre/ebooks/comic/input.py:282 +#: /home/kovid/work/calibre/src/calibre/ebooks/comic/input.py:294 msgid "" "Keep aspect ratio and scale image using screen height as image width for " "viewing in landscape mode." msgstr "" -#: /home/kovid/work/calibre/src/calibre/ebooks/comic/input.py:285 +#: /home/kovid/work/calibre/src/calibre/ebooks/comic/input.py:297 msgid "" "Used for right-to-left publications like manga. Causes landscape pages to be " "split into portrait pages from right to left." msgstr "" -#: /home/kovid/work/calibre/src/calibre/ebooks/comic/input.py:289 +#: /home/kovid/work/calibre/src/calibre/ebooks/comic/input.py:301 msgid "" "Enable Despeckle. Reduces speckle noise. May greatly increase processing " "time." msgstr "" -#: /home/kovid/work/calibre/src/calibre/ebooks/comic/input.py:292 +#: /home/kovid/work/calibre/src/calibre/ebooks/comic/input.py:304 msgid "" "Don't sort the files found in the comic alphabetically by name. Instead use " "the order they were added to the comic." msgstr "" -#: /home/kovid/work/calibre/src/calibre/ebooks/comic/input.py:296 +#: /home/kovid/work/calibre/src/calibre/ebooks/comic/input.py:308 msgid "" "The format that images in the created ebook are converted to. You can " "experiment to see which format gives you optimal size and look on your " "device." msgstr "" -#: /home/kovid/work/calibre/src/calibre/ebooks/comic/input.py:300 +#: /home/kovid/work/calibre/src/calibre/ebooks/comic/input.py:312 msgid "Apply no processing to the image" msgstr "" -#: /home/kovid/work/calibre/src/calibre/ebooks/comic/input.py:302 +#: /home/kovid/work/calibre/src/calibre/ebooks/comic/input.py:314 msgid "Do not convert the image to grayscale (black and white)" msgstr "" -#: /home/kovid/work/calibre/src/calibre/ebooks/comic/input.py:304 +#: /home/kovid/work/calibre/src/calibre/ebooks/comic/input.py:316 msgid "" "Specify the image size as widthxheight pixels. Normally, an image size is " "automatically calculated from the output profile, this option overrides it." msgstr "" -#: /home/kovid/work/calibre/src/calibre/ebooks/comic/input.py:443 -#: /home/kovid/work/calibre/src/calibre/ebooks/comic/input.py:454 +#: /home/kovid/work/calibre/src/calibre/ebooks/comic/input.py:320 +msgid "" +"When converting a CBC do not add links to each page to the TOC. Note this " +"only applies if the TOC has more than one section" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/ebooks/comic/input.py:459 +#: /home/kovid/work/calibre/src/calibre/ebooks/comic/input.py:471 msgid "Page" msgstr "" @@ -1517,77 +1768,77 @@ msgid "" "For full documentation of the conversion system see\n" msgstr "" -#: /home/kovid/work/calibre/src/calibre/ebooks/conversion/cli.py:106 +#: /home/kovid/work/calibre/src/calibre/ebooks/conversion/cli.py:109 msgid "INPUT OPTIONS" msgstr "" -#: /home/kovid/work/calibre/src/calibre/ebooks/conversion/cli.py:107 +#: /home/kovid/work/calibre/src/calibre/ebooks/conversion/cli.py:110 msgid "Options to control the processing of the input %s file" msgstr "" -#: /home/kovid/work/calibre/src/calibre/ebooks/conversion/cli.py:113 +#: /home/kovid/work/calibre/src/calibre/ebooks/conversion/cli.py:116 msgid "OUTPUT OPTIONS" msgstr "" -#: /home/kovid/work/calibre/src/calibre/ebooks/conversion/cli.py:114 +#: /home/kovid/work/calibre/src/calibre/ebooks/conversion/cli.py:117 msgid "Options to control the processing of the output %s" msgstr "" -#: /home/kovid/work/calibre/src/calibre/ebooks/conversion/cli.py:128 +#: /home/kovid/work/calibre/src/calibre/ebooks/conversion/cli.py:131 msgid "Options to control the look and feel of the output" msgstr "" -#: /home/kovid/work/calibre/src/calibre/ebooks/conversion/cli.py:143 +#: /home/kovid/work/calibre/src/calibre/ebooks/conversion/cli.py:146 msgid "" "Modify the document text and structure using common patterns. Disabled by " "default. Use %s to enable. Individual actions can be disabled with the %s " "options." msgstr "" -#: /home/kovid/work/calibre/src/calibre/ebooks/conversion/cli.py:151 -#: /home/kovid/work/calibre/src/calibre/gui2/convert/search_and_replace.py:16 +#: /home/kovid/work/calibre/src/calibre/ebooks/conversion/cli.py:154 +#: /home/kovid/work/calibre/src/calibre/gui2/convert/search_and_replace.py:18 msgid "Modify the document text and structure using user defined patterns." msgstr "" -#: /home/kovid/work/calibre/src/calibre/ebooks/conversion/cli.py:160 +#: /home/kovid/work/calibre/src/calibre/ebooks/conversion/cli.py:163 msgid "Control auto-detection of document structure." msgstr "" -#: /home/kovid/work/calibre/src/calibre/ebooks/conversion/cli.py:169 +#: /home/kovid/work/calibre/src/calibre/ebooks/conversion/cli.py:173 msgid "" "Control the automatic generation of a Table of Contents. By default, if the " "source file has a Table of Contents, it will be used in preference to the " "automatically generated one." msgstr "" -#: /home/kovid/work/calibre/src/calibre/ebooks/conversion/cli.py:179 +#: /home/kovid/work/calibre/src/calibre/ebooks/conversion/cli.py:183 msgid "Options to set metadata in the output" msgstr "" -#: /home/kovid/work/calibre/src/calibre/ebooks/conversion/cli.py:182 +#: /home/kovid/work/calibre/src/calibre/ebooks/conversion/cli.py:186 msgid "Options to help with debugging the conversion" msgstr "" -#: /home/kovid/work/calibre/src/calibre/ebooks/conversion/cli.py:208 +#: /home/kovid/work/calibre/src/calibre/ebooks/conversion/cli.py:212 msgid "List builtin recipes" msgstr "" -#: /home/kovid/work/calibre/src/calibre/ebooks/conversion/cli.py:281 +#: /home/kovid/work/calibre/src/calibre/ebooks/conversion/cli.py:285 msgid "Output saved to" msgstr "" -#: /home/kovid/work/calibre/src/calibre/ebooks/conversion/plumber.py:102 +#: /home/kovid/work/calibre/src/calibre/ebooks/conversion/plumber.py:103 msgid "Level of verbosity. Specify multiple times for greater verbosity." msgstr "" -#: /home/kovid/work/calibre/src/calibre/ebooks/conversion/plumber.py:109 +#: /home/kovid/work/calibre/src/calibre/ebooks/conversion/plumber.py:110 msgid "" "Save the output from different stages of the conversion pipeline to the " "specified directory. Useful if you are unsure at which stage of the " "conversion process a bug is occurring." msgstr "" -#: /home/kovid/work/calibre/src/calibre/ebooks/conversion/plumber.py:118 +#: /home/kovid/work/calibre/src/calibre/ebooks/conversion/plumber.py:119 msgid "" "Specify the input profile. The input profile gives the conversion system " "information on how to interpret various information in the input document. " @@ -1595,7 +1846,7 @@ msgid "" "are:" msgstr "" -#: /home/kovid/work/calibre/src/calibre/ebooks/conversion/plumber.py:129 +#: /home/kovid/work/calibre/src/calibre/ebooks/conversion/plumber.py:130 msgid "" "Specify the output profile. The output profile tells the conversion system " "how to optimize the created document for the specified device. In some " @@ -1603,7 +1854,7 @@ msgid "" "a device. For example EPUB on the SONY reader. Choices are:" msgstr "" -#: /home/kovid/work/calibre/src/calibre/ebooks/conversion/plumber.py:140 +#: /home/kovid/work/calibre/src/calibre/ebooks/conversion/plumber.py:141 msgid "" "The base font size in pts. All font sizes in the produced book will be " "rescaled based on this size. By choosing a larger size you can make the " @@ -1611,7 +1862,7 @@ msgid "" "chosen based on the output profile you chose." msgstr "" -#: /home/kovid/work/calibre/src/calibre/ebooks/conversion/plumber.py:150 +#: /home/kovid/work/calibre/src/calibre/ebooks/conversion/plumber.py:151 msgid "" "Mapping from CSS font names to font sizes in pts. An example setting is " "12,12,14,16,18,20,22,24. These are the mappings for the sizes xx-small to xx-" @@ -1620,11 +1871,11 @@ msgid "" "use a mapping based on the output profile you chose." msgstr "" -#: /home/kovid/work/calibre/src/calibre/ebooks/conversion/plumber.py:162 +#: /home/kovid/work/calibre/src/calibre/ebooks/conversion/plumber.py:163 msgid "Disable all rescaling of font sizes." msgstr "" -#: /home/kovid/work/calibre/src/calibre/ebooks/conversion/plumber.py:168 +#: /home/kovid/work/calibre/src/calibre/ebooks/conversion/plumber.py:169 msgid "" "The minimum line height, as a percentage of the element's calculated font " "size. calibre will ensure that every element has a line height of at least " @@ -1634,7 +1885,7 @@ msgid "" "you can achieve \"double spaced\" text by setting this to 240." msgstr "" -#: /home/kovid/work/calibre/src/calibre/ebooks/conversion/plumber.py:183 +#: /home/kovid/work/calibre/src/calibre/ebooks/conversion/plumber.py:184 msgid "" "The line height in pts. Controls spacing between consecutive lines of text. " "Only applies to elements that do not define their own line height. In most " @@ -1642,7 +1893,7 @@ msgid "" "height manipulation is performed." msgstr "" -#: /home/kovid/work/calibre/src/calibre/ebooks/conversion/plumber.py:194 +#: /home/kovid/work/calibre/src/calibre/ebooks/conversion/plumber.py:195 msgid "" "Some badly designed documents use tables to control the layout of text on " "the page. When converted these documents often have text that runs off the " @@ -1650,58 +1901,58 @@ msgid "" "tables and present it in a linear fashion." msgstr "" -#: /home/kovid/work/calibre/src/calibre/ebooks/conversion/plumber.py:204 +#: /home/kovid/work/calibre/src/calibre/ebooks/conversion/plumber.py:205 msgid "" "XPath expression that specifies all tags that should be added to the Table " "of Contents at level one. If this is specified, it takes precedence over " "other forms of auto-detection." msgstr "" -#: /home/kovid/work/calibre/src/calibre/ebooks/conversion/plumber.py:213 +#: /home/kovid/work/calibre/src/calibre/ebooks/conversion/plumber.py:214 msgid "" "XPath expression that specifies all tags that should be added to the Table " "of Contents at level two. Each entry is added under the previous level one " "entry." msgstr "" -#: /home/kovid/work/calibre/src/calibre/ebooks/conversion/plumber.py:221 +#: /home/kovid/work/calibre/src/calibre/ebooks/conversion/plumber.py:222 msgid "" "XPath expression that specifies all tags that should be added to the Table " "of Contents at level three. Each entry is added under the previous level two " "entry." msgstr "" -#: /home/kovid/work/calibre/src/calibre/ebooks/conversion/plumber.py:229 +#: /home/kovid/work/calibre/src/calibre/ebooks/conversion/plumber.py:230 msgid "" "Normally, if the source file already has a Table of Contents, it is used in " "preference to the auto-generated one. With this option, the auto-generated " "one is always used." msgstr "" -#: /home/kovid/work/calibre/src/calibre/ebooks/conversion/plumber.py:237 +#: /home/kovid/work/calibre/src/calibre/ebooks/conversion/plumber.py:238 msgid "Don't add auto-detected chapters to the Table of Contents." msgstr "" -#: /home/kovid/work/calibre/src/calibre/ebooks/conversion/plumber.py:244 +#: /home/kovid/work/calibre/src/calibre/ebooks/conversion/plumber.py:245 msgid "" "If fewer than this number of chapters is detected, then links are added to " "the Table of Contents. Default: %default" msgstr "" -#: /home/kovid/work/calibre/src/calibre/ebooks/conversion/plumber.py:251 +#: /home/kovid/work/calibre/src/calibre/ebooks/conversion/plumber.py:252 msgid "" "Maximum number of links to insert into the TOC. Set to 0 to disable. Default " "is: %default. Links are only added to the TOC if less than the threshold " "number of chapters were detected." msgstr "" -#: /home/kovid/work/calibre/src/calibre/ebooks/conversion/plumber.py:259 +#: /home/kovid/work/calibre/src/calibre/ebooks/conversion/plumber.py:260 msgid "" "Remove entries from the Table of Contents whose titles match the specified " "regular expression. Matching entries and all their children are removed." msgstr "" -#: /home/kovid/work/calibre/src/calibre/ebooks/conversion/plumber.py:270 +#: /home/kovid/work/calibre/src/calibre/ebooks/conversion/plumber.py:271 msgid "" "An XPath expression to detect chapter titles. The default is to consider " "

    or

    tags that contain the words \"chapter\",\"book\",\"section\" or " @@ -1711,7 +1962,7 @@ msgid "" "User Manual for further help on using this feature." msgstr "" -#: /home/kovid/work/calibre/src/calibre/ebooks/conversion/plumber.py:284 +#: /home/kovid/work/calibre/src/calibre/ebooks/conversion/plumber.py:285 msgid "" "Specify how to mark detected chapters. A value of \"pagebreak\" will insert " "page breaks before chapters. A value of \"rule\" will insert a line before " @@ -1719,39 +1970,47 @@ msgid "" "\"both\" will use both page breaks and lines to mark chapters." msgstr "" -#: /home/kovid/work/calibre/src/calibre/ebooks/conversion/plumber.py:294 +#: /home/kovid/work/calibre/src/calibre/ebooks/conversion/plumber.py:295 msgid "" "Either the path to a CSS stylesheet or raw CSS. This CSS will be appended to " "the style rules from the source file, so it can be used to override those " "rules." msgstr "" -#: /home/kovid/work/calibre/src/calibre/ebooks/conversion/plumber.py:303 +#: /home/kovid/work/calibre/src/calibre/ebooks/conversion/plumber.py:304 msgid "" "An XPath expression. Page breaks are inserted before the specified elements." msgstr "" -#: /home/kovid/work/calibre/src/calibre/ebooks/conversion/plumber.py:309 +#: /home/kovid/work/calibre/src/calibre/ebooks/conversion/plumber.py:310 +msgid "" +"Some documents specify page margins by specifying a left and right margin on " +"each individual paragraph. calibre will try to detect and remove these " +"margins. Sometimes, this can cause the removal of margins that should not " +"have been removed. In this case you can disable the removal." +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/ebooks/conversion/plumber.py:321 msgid "" "Set the top margin in pts. Default is %default. Note: 72 pts equals 1 inch" msgstr "" -#: /home/kovid/work/calibre/src/calibre/ebooks/conversion/plumber.py:314 +#: /home/kovid/work/calibre/src/calibre/ebooks/conversion/plumber.py:326 msgid "" "Set the bottom margin in pts. Default is %default. Note: 72 pts equals 1 inch" msgstr "" -#: /home/kovid/work/calibre/src/calibre/ebooks/conversion/plumber.py:319 +#: /home/kovid/work/calibre/src/calibre/ebooks/conversion/plumber.py:331 msgid "" "Set the left margin in pts. Default is %default. Note: 72 pts equals 1 inch" msgstr "" -#: /home/kovid/work/calibre/src/calibre/ebooks/conversion/plumber.py:324 +#: /home/kovid/work/calibre/src/calibre/ebooks/conversion/plumber.py:336 msgid "" "Set the right margin in pts. Default is %default. Note: 72 pts equals 1 inch" msgstr "" -#: /home/kovid/work/calibre/src/calibre/ebooks/conversion/plumber.py:330 +#: /home/kovid/work/calibre/src/calibre/ebooks/conversion/plumber.py:342 msgid "" "Change text justification. A value of \"left\" converts all justified text " "in the source to left aligned (i.e. unjustified) text. A value of " @@ -1760,57 +2019,57 @@ msgid "" "Note that only some output formats support justification." msgstr "" -#: /home/kovid/work/calibre/src/calibre/ebooks/conversion/plumber.py:340 +#: /home/kovid/work/calibre/src/calibre/ebooks/conversion/plumber.py:352 msgid "" "Remove spacing between paragraphs. Also sets an indent on paragraphs of " "1.5em. Spacing removal will not work if the source file does not use " "paragraphs (

    or

    tags)." msgstr "" -#: /home/kovid/work/calibre/src/calibre/ebooks/conversion/plumber.py:347 +#: /home/kovid/work/calibre/src/calibre/ebooks/conversion/plumber.py:359 msgid "" "When calibre removes inter paragraph spacing, it automatically sets a " "paragraph indent, to ensure that paragraphs can be easily distinguished. " "This option controls the width of that indent." msgstr "" -#: /home/kovid/work/calibre/src/calibre/ebooks/conversion/plumber.py:354 +#: /home/kovid/work/calibre/src/calibre/ebooks/conversion/plumber.py:366 msgid "" "Use the cover detected from the source file in preference to the specified " "cover." msgstr "" -#: /home/kovid/work/calibre/src/calibre/ebooks/conversion/plumber.py:360 +#: /home/kovid/work/calibre/src/calibre/ebooks/conversion/plumber.py:372 msgid "" "Insert a blank line between paragraphs. Will not work if the source file " "does not use paragraphs (

    or

    tags)." msgstr "" -#: /home/kovid/work/calibre/src/calibre/ebooks/conversion/plumber.py:367 +#: /home/kovid/work/calibre/src/calibre/ebooks/conversion/plumber.py:379 msgid "" "Remove the first image from the input ebook. Useful if the first image in " "the source file is a cover and you are specifying an external cover." msgstr "" -#: /home/kovid/work/calibre/src/calibre/ebooks/conversion/plumber.py:375 +#: /home/kovid/work/calibre/src/calibre/ebooks/conversion/plumber.py:387 msgid "" "Insert the book metadata at the start of the book. This is useful if your " "ebook reader does not support displaying/searching metadata directly." msgstr "" -#: /home/kovid/work/calibre/src/calibre/ebooks/conversion/plumber.py:383 +#: /home/kovid/work/calibre/src/calibre/ebooks/conversion/plumber.py:395 msgid "" "Convert plain quotes, dashes and ellipsis to their typographically correct " "equivalents. For details, see http://daringfireball.net/projects/smartypants" msgstr "" -#: /home/kovid/work/calibre/src/calibre/ebooks/conversion/plumber.py:392 +#: /home/kovid/work/calibre/src/calibre/ebooks/conversion/plumber.py:404 msgid "" "Read metadata from the specified OPF file. Metadata read from this file will " "override any metadata in the source file." msgstr "" -#: /home/kovid/work/calibre/src/calibre/ebooks/conversion/plumber.py:399 +#: /home/kovid/work/calibre/src/calibre/ebooks/conversion/plumber.py:411 msgid "" "Transliterate unicode characters to an ASCII representation. Use with care " "because this will replace unicode characters with ASCII. For instance it " @@ -1820,7 +2079,7 @@ msgid "" "current calibre interface language will be used." msgstr "" -#: /home/kovid/work/calibre/src/calibre/ebooks/conversion/plumber.py:414 +#: /home/kovid/work/calibre/src/calibre/ebooks/conversion/plumber.py:426 msgid "" "Preserve ligatures present in the input document. A ligature is a special " "rendering of a pair of characters like ff, fi, fl et cetera. Most readers do " @@ -1830,105 +2089,105 @@ msgid "" "instead." msgstr "" -#: /home/kovid/work/calibre/src/calibre/ebooks/conversion/plumber.py:426 +#: /home/kovid/work/calibre/src/calibre/ebooks/conversion/plumber.py:438 #: /home/kovid/work/calibre/src/calibre/ebooks/metadata/cli.py:38 msgid "Set the title." msgstr "" -#: /home/kovid/work/calibre/src/calibre/ebooks/conversion/plumber.py:430 +#: /home/kovid/work/calibre/src/calibre/ebooks/conversion/plumber.py:442 msgid "Set the authors. Multiple authors should be separated by ampersands." msgstr "" -#: /home/kovid/work/calibre/src/calibre/ebooks/conversion/plumber.py:435 +#: /home/kovid/work/calibre/src/calibre/ebooks/conversion/plumber.py:447 msgid "The version of the title to be used for sorting. " msgstr "" -#: /home/kovid/work/calibre/src/calibre/ebooks/conversion/plumber.py:439 +#: /home/kovid/work/calibre/src/calibre/ebooks/conversion/plumber.py:451 msgid "String to be used when sorting by author. " msgstr "" -#: /home/kovid/work/calibre/src/calibre/ebooks/conversion/plumber.py:443 +#: /home/kovid/work/calibre/src/calibre/ebooks/conversion/plumber.py:455 msgid "Set the cover to the specified file or URL" msgstr "" -#: /home/kovid/work/calibre/src/calibre/ebooks/conversion/plumber.py:447 +#: /home/kovid/work/calibre/src/calibre/ebooks/conversion/plumber.py:459 #: /home/kovid/work/calibre/src/calibre/ebooks/metadata/cli.py:54 msgid "Set the ebook description." msgstr "" -#: /home/kovid/work/calibre/src/calibre/ebooks/conversion/plumber.py:451 +#: /home/kovid/work/calibre/src/calibre/ebooks/conversion/plumber.py:463 #: /home/kovid/work/calibre/src/calibre/ebooks/metadata/cli.py:56 msgid "Set the ebook publisher." msgstr "" -#: /home/kovid/work/calibre/src/calibre/ebooks/conversion/plumber.py:455 +#: /home/kovid/work/calibre/src/calibre/ebooks/conversion/plumber.py:467 #: /home/kovid/work/calibre/src/calibre/ebooks/metadata/cli.py:60 msgid "Set the series this ebook belongs to." msgstr "" -#: /home/kovid/work/calibre/src/calibre/ebooks/conversion/plumber.py:459 +#: /home/kovid/work/calibre/src/calibre/ebooks/conversion/plumber.py:471 #: /home/kovid/work/calibre/src/calibre/ebooks/metadata/cli.py:62 msgid "Set the index of the book in this series." msgstr "" -#: /home/kovid/work/calibre/src/calibre/ebooks/conversion/plumber.py:463 +#: /home/kovid/work/calibre/src/calibre/ebooks/conversion/plumber.py:475 #: /home/kovid/work/calibre/src/calibre/ebooks/metadata/cli.py:64 msgid "Set the rating. Should be a number between 1 and 5." msgstr "" -#: /home/kovid/work/calibre/src/calibre/ebooks/conversion/plumber.py:467 +#: /home/kovid/work/calibre/src/calibre/ebooks/conversion/plumber.py:479 #: /home/kovid/work/calibre/src/calibre/ebooks/metadata/cli.py:66 msgid "Set the ISBN of the book." msgstr "" -#: /home/kovid/work/calibre/src/calibre/ebooks/conversion/plumber.py:471 +#: /home/kovid/work/calibre/src/calibre/ebooks/conversion/plumber.py:483 #: /home/kovid/work/calibre/src/calibre/ebooks/metadata/cli.py:68 msgid "Set the tags for the book. Should be a comma separated list." msgstr "" -#: /home/kovid/work/calibre/src/calibre/ebooks/conversion/plumber.py:475 +#: /home/kovid/work/calibre/src/calibre/ebooks/conversion/plumber.py:487 #: /home/kovid/work/calibre/src/calibre/ebooks/metadata/cli.py:70 msgid "Set the book producer." msgstr "" -#: /home/kovid/work/calibre/src/calibre/ebooks/conversion/plumber.py:479 +#: /home/kovid/work/calibre/src/calibre/ebooks/conversion/plumber.py:491 #: /home/kovid/work/calibre/src/calibre/ebooks/metadata/cli.py:72 msgid "Set the language." msgstr "" -#: /home/kovid/work/calibre/src/calibre/ebooks/conversion/plumber.py:483 +#: /home/kovid/work/calibre/src/calibre/ebooks/conversion/plumber.py:495 msgid "Set the publication date." msgstr "" -#: /home/kovid/work/calibre/src/calibre/ebooks/conversion/plumber.py:487 +#: /home/kovid/work/calibre/src/calibre/ebooks/conversion/plumber.py:499 msgid "Set the book timestamp (used by the date column in calibre)." msgstr "" -#: /home/kovid/work/calibre/src/calibre/ebooks/conversion/plumber.py:491 +#: /home/kovid/work/calibre/src/calibre/ebooks/conversion/plumber.py:503 msgid "" "Enable heuristic processing. This option must be set for any heuristic " "processing to take place." msgstr "" -#: /home/kovid/work/calibre/src/calibre/ebooks/conversion/plumber.py:496 +#: /home/kovid/work/calibre/src/calibre/ebooks/conversion/plumber.py:508 msgid "" "Detect unformatted chapter headings and sub headings. Change them to h2 and " "h3 tags. This setting will not create a TOC, but can be used in conjunction " "with structure detection to create one." msgstr "" -#: /home/kovid/work/calibre/src/calibre/ebooks/conversion/plumber.py:503 +#: /home/kovid/work/calibre/src/calibre/ebooks/conversion/plumber.py:515 msgid "" "Look for common words and patterns that denote italics and italicize them." msgstr "" -#: /home/kovid/work/calibre/src/calibre/ebooks/conversion/plumber.py:508 +#: /home/kovid/work/calibre/src/calibre/ebooks/conversion/plumber.py:520 msgid "" "Turn indentation created from multiple non-breaking space entities into CSS " "indents." msgstr "" -#: /home/kovid/work/calibre/src/calibre/ebooks/conversion/plumber.py:513 +#: /home/kovid/work/calibre/src/calibre/ebooks/conversion/plumber.py:525 msgid "" "Scale used to determine the length at which a line should be unwrapped. " "Valid values are a decimal between 0 and 1. The default is 0.4, just below " @@ -1936,86 +2195,86 @@ msgid "" "unwrapping this value should be reduced" msgstr "" -#: /home/kovid/work/calibre/src/calibre/ebooks/conversion/plumber.py:521 +#: /home/kovid/work/calibre/src/calibre/ebooks/conversion/plumber.py:533 msgid "Unwrap lines using punctuation and other formatting clues." msgstr "" -#: /home/kovid/work/calibre/src/calibre/ebooks/conversion/plumber.py:525 +#: /home/kovid/work/calibre/src/calibre/ebooks/conversion/plumber.py:537 msgid "" "Remove empty paragraphs from the document when they exist between every " "other paragraph" msgstr "" -#: /home/kovid/work/calibre/src/calibre/ebooks/conversion/plumber.py:530 +#: /home/kovid/work/calibre/src/calibre/ebooks/conversion/plumber.py:542 msgid "" "Left aligned scene break markers are center aligned. Replace soft scene " "breaks that use multiple blank lines withhorizontal rules." msgstr "" -#: /home/kovid/work/calibre/src/calibre/ebooks/conversion/plumber.py:536 +#: /home/kovid/work/calibre/src/calibre/ebooks/conversion/plumber.py:548 msgid "" "Replace scene breaks with the specified text. By default, the text from the " "input document is used." msgstr "" -#: /home/kovid/work/calibre/src/calibre/ebooks/conversion/plumber.py:541 +#: /home/kovid/work/calibre/src/calibre/ebooks/conversion/plumber.py:553 msgid "" "Analyze hyphenated words throughout the document. The document itself is " "used as a dictionary to determine whether hyphens should be retained or " "removed." msgstr "" -#: /home/kovid/work/calibre/src/calibre/ebooks/conversion/plumber.py:547 +#: /home/kovid/work/calibre/src/calibre/ebooks/conversion/plumber.py:559 msgid "" "Looks for occurrences of sequential

    or

    tags. The tags are " "renumbered to prevent splitting in the middle of chapter headings." msgstr "" -#: /home/kovid/work/calibre/src/calibre/ebooks/conversion/plumber.py:553 +#: /home/kovid/work/calibre/src/calibre/ebooks/conversion/plumber.py:565 msgid "Search pattern (regular expression) to be replaced with sr1-replace." msgstr "" -#: /home/kovid/work/calibre/src/calibre/ebooks/conversion/plumber.py:558 +#: /home/kovid/work/calibre/src/calibre/ebooks/conversion/plumber.py:570 msgid "Replacement to replace the text found with sr1-search." msgstr "" -#: /home/kovid/work/calibre/src/calibre/ebooks/conversion/plumber.py:562 +#: /home/kovid/work/calibre/src/calibre/ebooks/conversion/plumber.py:574 msgid "Search pattern (regular expression) to be replaced with sr2-replace." msgstr "" -#: /home/kovid/work/calibre/src/calibre/ebooks/conversion/plumber.py:567 +#: /home/kovid/work/calibre/src/calibre/ebooks/conversion/plumber.py:579 msgid "Replacement to replace the text found with sr2-search." msgstr "" -#: /home/kovid/work/calibre/src/calibre/ebooks/conversion/plumber.py:571 +#: /home/kovid/work/calibre/src/calibre/ebooks/conversion/plumber.py:583 msgid "Search pattern (regular expression) to be replaced with sr3-replace." msgstr "" -#: /home/kovid/work/calibre/src/calibre/ebooks/conversion/plumber.py:576 +#: /home/kovid/work/calibre/src/calibre/ebooks/conversion/plumber.py:588 msgid "Replacement to replace the text found with sr3-search." msgstr "" -#: /home/kovid/work/calibre/src/calibre/ebooks/conversion/plumber.py:678 +#: /home/kovid/work/calibre/src/calibre/ebooks/conversion/plumber.py:690 msgid "Could not find an ebook inside the archive" msgstr "" -#: /home/kovid/work/calibre/src/calibre/ebooks/conversion/plumber.py:736 +#: /home/kovid/work/calibre/src/calibre/ebooks/conversion/plumber.py:748 msgid "Values of series index and rating must be numbers. Ignoring" msgstr "" -#: /home/kovid/work/calibre/src/calibre/ebooks/conversion/plumber.py:743 +#: /home/kovid/work/calibre/src/calibre/ebooks/conversion/plumber.py:755 msgid "Failed to parse date/time" msgstr "" -#: /home/kovid/work/calibre/src/calibre/ebooks/conversion/plumber.py:898 +#: /home/kovid/work/calibre/src/calibre/ebooks/conversion/plumber.py:914 msgid "Converting input to HTML..." msgstr "" -#: /home/kovid/work/calibre/src/calibre/ebooks/conversion/plumber.py:925 +#: /home/kovid/work/calibre/src/calibre/ebooks/conversion/plumber.py:941 msgid "Running transforms on ebook..." msgstr "" -#: /home/kovid/work/calibre/src/calibre/ebooks/conversion/plumber.py:1013 +#: /home/kovid/work/calibre/src/calibre/ebooks/conversion/plumber.py:1037 msgid "Creating" msgstr "" @@ -2130,7 +2389,7 @@ msgstr "" msgid "Do not insert a Table of Contents at the beginning of the book." msgstr "" -#: /home/kovid/work/calibre/src/calibre/ebooks/fb2/output.py:22 +#: /home/kovid/work/calibre/src/calibre/ebooks/fb2/output.py:149 msgid "" "Specify the sectionization of elements. A value of \"nothing\" turns the " "book into a single section. A value of \"files\" turns each file into a " @@ -2141,6 +2400,17 @@ msgid "" "of Contents)." msgstr "" +#: /home/kovid/work/calibre/src/calibre/ebooks/fb2/output.py:158 +msgid "" +"Genre for the book. Choices: %s\n" +"\n" +" See: " +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/ebooks/fb2/output.py:159 +msgid "for a complete list with descriptions." +msgstr "" + #: /home/kovid/work/calibre/src/calibre/ebooks/html/input.py:248 msgid "" "Traverse links in HTML files breadth first. Normally, they are traversed " @@ -2162,28 +2432,44 @@ msgid "" "pipeline." msgstr "" -#: /home/kovid/work/calibre/src/calibre/ebooks/html/output.py:33 +#: /home/kovid/work/calibre/src/calibre/ebooks/html/output.py:32 msgid "CSS file used for the output instead of the default file" msgstr "" -#: /home/kovid/work/calibre/src/calibre/ebooks/html/output.py:36 +#: /home/kovid/work/calibre/src/calibre/ebooks/html/output.py:35 msgid "" "Template used for generation of the html index file instead of the default " "file" msgstr "" -#: /home/kovid/work/calibre/src/calibre/ebooks/html/output.py:39 +#: /home/kovid/work/calibre/src/calibre/ebooks/html/output.py:38 msgid "" "Template used for the generation of the html contents of the book instead of " "the default file" msgstr "" -#: /home/kovid/work/calibre/src/calibre/ebooks/html/output.py:42 +#: /home/kovid/work/calibre/src/calibre/ebooks/html/output.py:41 msgid "" "Extract the contents of the generated ZIP file to the specified directory. " "WARNING: The contents of the directory will be deleted." msgstr "" +#: /home/kovid/work/calibre/src/calibre/ebooks/htmlz/output.py:30 +msgid "" +"Specify the handling of CSS. Default is class.\n" +"class: Use CSS classes and have elements reference them.\n" +"inline: Write the CSS as an inline style attribute.\n" +"tag: Turn as many CSS styles as possible into HTML tags." +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/ebooks/htmlz/output.py:38 +msgid "" +"How to handle the CSS when using css-type = 'class'.\n" +"Default is external.\n" +"external: Use an external CSS file that is linked in the document.\n" +"inline: Place the CSS in the head section of the document." +msgstr "" + #: /home/kovid/work/calibre/src/calibre/ebooks/lit/from_any.py:47 msgid "Creating LIT file from EPUB..." msgstr "" @@ -2311,7 +2597,6 @@ msgid "Path to output file" msgstr "" #: /home/kovid/work/calibre/src/calibre/ebooks/lrf/lrs/convert_from.py:290 -#: /home/kovid/work/calibre/src/calibre/ebooks/metadata/isbndb.py:126 msgid "Verbose processing" msgstr "" @@ -2447,146 +2732,106 @@ msgstr "" msgid "Comic" msgstr "" -#: /home/kovid/work/calibre/src/calibre/ebooks/metadata/amazonfr.py:26 -msgid "Downloads metadata from amazon.fr" -msgstr "" - -#: /home/kovid/work/calibre/src/calibre/ebooks/metadata/amazonfr.py:43 -msgid "Downloads metadata from amazon.com in spanish" -msgstr "" - -#: /home/kovid/work/calibre/src/calibre/ebooks/metadata/amazonfr.py:60 -msgid "Downloads metadata from amazon.com in english" -msgstr "" - -#: /home/kovid/work/calibre/src/calibre/ebooks/metadata/amazonfr.py:77 -msgid "Downloads metadata from amazon.de" -msgstr "" - -#: /home/kovid/work/calibre/src/calibre/ebooks/metadata/amazonfr.py:94 -msgid "Downloads metadata from amazon.com" -msgstr "" - -#: /home/kovid/work/calibre/src/calibre/ebooks/metadata/amazonfr.py:474 -msgid "" -" %prog [options]\n" -"\n" -" Fetch book metadata from Amazon. You must specify one of title, " -"author,\n" -" ISBN, publisher or keywords. Will fetch a maximum of 10 matches,\n" -" so you should make your query as specific as possible.\n" -" You can chose the language for metadata retrieval:\n" -" All & english & french & german & spanish\n" -" " -msgstr "" - #: /home/kovid/work/calibre/src/calibre/ebooks/metadata/archive.py:41 msgid "" "Extract common e-book formats from archives (zip/rar) files. Also try to " "autodetect if they are actually cbz/cbr files." msgstr "" -#: /home/kovid/work/calibre/src/calibre/ebooks/metadata/book/base.py:116 +#: /home/kovid/work/calibre/src/calibre/ebooks/metadata/book/base.py:145 msgid "TEMPLATE ERROR" msgstr "" -#: /home/kovid/work/calibre/src/calibre/ebooks/metadata/book/base.py:541 -#: /home/kovid/work/calibre/src/calibre/gui2/custom_column_widgets.py:64 -#: /home/kovid/work/calibre/src/calibre/gui2/custom_column_widgets.py:554 +#: /home/kovid/work/calibre/src/calibre/ebooks/metadata/book/base.py:628 +#: /home/kovid/work/calibre/src/calibre/gui2/custom_column_widgets.py:63 +#: /home/kovid/work/calibre/src/calibre/gui2/custom_column_widgets.py:563 msgid "No" msgstr "" -#: /home/kovid/work/calibre/src/calibre/ebooks/metadata/book/base.py:541 -#: /home/kovid/work/calibre/src/calibre/gui2/custom_column_widgets.py:64 -#: /home/kovid/work/calibre/src/calibre/gui2/custom_column_widgets.py:554 +#: /home/kovid/work/calibre/src/calibre/ebooks/metadata/book/base.py:628 +#: /home/kovid/work/calibre/src/calibre/gui2/custom_column_widgets.py:63 +#: /home/kovid/work/calibre/src/calibre/gui2/custom_column_widgets.py:563 msgid "Yes" msgstr "" -#: /home/kovid/work/calibre/src/calibre/ebooks/metadata/book/base.py:615 +#: /home/kovid/work/calibre/src/calibre/ebooks/metadata/book/base.py:723 #: /home/kovid/work/calibre/src/calibre/ebooks/pdf/manipulate/info.py:45 -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/book_info.py:112 -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/book_info.py:113 #: /home/kovid/work/calibre/src/calibre/gui2/dialogs/delete_matching_from_device.py:75 -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/fetch_metadata.py:60 -#: /home/kovid/work/calibre/src/calibre/gui2/library/models.py:65 -#: /home/kovid/work/calibre/src/calibre/gui2/library/models.py:418 +#: /home/kovid/work/calibre/src/calibre/gui2/library/models.py:63 #: /home/kovid/work/calibre/src/calibre/gui2/library/models.py:977 -#: /home/kovid/work/calibre/src/calibre/library/field_metadata.py:304 -#: /home/kovid/work/calibre/src/calibre/library/server/opds.py:590 +#: /home/kovid/work/calibre/src/calibre/gui2/metadata/single_download.py:132 +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/metadata_sources.py:152 +#: /home/kovid/work/calibre/src/calibre/gui2/store/mobileread/models.py:23 +#: /home/kovid/work/calibre/src/calibre/gui2/store/search/models.py:33 +#: /home/kovid/work/calibre/src/calibre/library/field_metadata.py:331 +#: /home/kovid/work/calibre/src/calibre/library/server/opds.py:574 msgid "Title" msgstr "" -#: /home/kovid/work/calibre/src/calibre/ebooks/metadata/book/base.py:616 -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/fetch_metadata.py:61 -#: /home/kovid/work/calibre/src/calibre/gui2/library/models.py:67 -#: /home/kovid/work/calibre/src/calibre/gui2/library/models.py:423 +#: /home/kovid/work/calibre/src/calibre/ebooks/metadata/book/base.py:724 +#: /home/kovid/work/calibre/src/calibre/gui2/library/models.py:65 #: /home/kovid/work/calibre/src/calibre/gui2/library/models.py:978 +#: /home/kovid/work/calibre/src/calibre/gui2/store/mobileread/models.py:23 msgid "Author(s)" msgstr "" -#: /home/kovid/work/calibre/src/calibre/ebooks/metadata/book/base.py:617 -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/fetch_metadata.py:63 -#: /home/kovid/work/calibre/src/calibre/gui2/library/models.py:72 +#: /home/kovid/work/calibre/src/calibre/ebooks/metadata/book/base.py:725 +#: /home/kovid/work/calibre/src/calibre/gui2/library/models.py:70 +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/metadata_sources.py:149 msgid "Publisher" msgstr "" -#: /home/kovid/work/calibre/src/calibre/ebooks/metadata/book/base.py:618 +#: /home/kovid/work/calibre/src/calibre/ebooks/metadata/book/base.py:726 #: /home/kovid/work/calibre/src/calibre/ebooks/pdf/manipulate/info.py:49 msgid "Producer" msgstr "" -#: /home/kovid/work/calibre/src/calibre/ebooks/metadata/book/base.py:619 -#: /home/kovid/work/calibre/src/calibre/gui2/book_details.py:40 -#: /home/kovid/work/calibre/src/calibre/gui2/book_details.py:214 -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/book_info.py:114 -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/book_info_ui.py:79 -#: /home/kovid/work/calibre/src/calibre/gui2/library/models.py:380 -#: /home/kovid/work/calibre/src/calibre/gui2/library/models.py:1184 -#: /home/kovid/work/calibre/src/calibre/library/field_metadata.py:188 +#: /home/kovid/work/calibre/src/calibre/ebooks/metadata/book/base.py:727 +#: /home/kovid/work/calibre/src/calibre/gui2/metadata/single.py:871 +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/metadata_sources.py:147 +#: /home/kovid/work/calibre/src/calibre/library/field_metadata.py:211 msgid "Comments" msgstr "" -#: /home/kovid/work/calibre/src/calibre/ebooks/metadata/book/base.py:621 +#: /home/kovid/work/calibre/src/calibre/ebooks/metadata/book/base.py:729 #: /home/kovid/work/calibre/src/calibre/ebooks/oeb/transforms/jacket.py:170 -#: /home/kovid/work/calibre/src/calibre/gui2/book_details.py:30 #: /home/kovid/work/calibre/src/calibre/gui2/dialogs/tag_categories.py:60 -#: /home/kovid/work/calibre/src/calibre/gui2/library/models.py:73 -#: /home/kovid/work/calibre/src/calibre/gui2/library/models.py:368 -#: /home/kovid/work/calibre/src/calibre/gui2/library/models.py:1180 -#: /home/kovid/work/calibre/src/calibre/library/field_metadata.py:161 -#: /home/kovid/work/calibre/src/calibre/library/server/browse.py:691 +#: /home/kovid/work/calibre/src/calibre/gui2/library/models.py:71 +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/create_custom_column.py:67 +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/metadata_sources.py:151 +#: /home/kovid/work/calibre/src/calibre/library/field_metadata.py:171 +#: /home/kovid/work/calibre/src/calibre/library/server/browse.py:761 msgid "Tags" msgstr "" -#: /home/kovid/work/calibre/src/calibre/ebooks/metadata/book/base.py:623 +#: /home/kovid/work/calibre/src/calibre/ebooks/metadata/book/base.py:731 #: /home/kovid/work/calibre/src/calibre/ebooks/oeb/transforms/jacket.py:168 -#: /home/kovid/work/calibre/src/calibre/gui2/book_details.py:29 #: /home/kovid/work/calibre/src/calibre/gui2/dialogs/tag_categories.py:60 -#: /home/kovid/work/calibre/src/calibre/gui2/library/models.py:74 -#: /home/kovid/work/calibre/src/calibre/gui2/library/models.py:385 -#: /home/kovid/work/calibre/src/calibre/gui2/library/models.py:1189 -#: /home/kovid/work/calibre/src/calibre/library/field_metadata.py:109 +#: /home/kovid/work/calibre/src/calibre/gui2/library/models.py:72 +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/create_custom_column.py:67 +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/metadata_sources.py:153 +#: /home/kovid/work/calibre/src/calibre/library/field_metadata.py:114 msgid "Series" msgstr "" -#: /home/kovid/work/calibre/src/calibre/ebooks/metadata/book/base.py:624 +#: /home/kovid/work/calibre/src/calibre/ebooks/metadata/book/base.py:732 +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/metadata_sources.py:154 msgid "Language" msgstr "" -#: /home/kovid/work/calibre/src/calibre/ebooks/metadata/book/base.py:626 -#: /home/kovid/work/calibre/src/calibre/gui2/library/models.py:1172 +#: /home/kovid/work/calibre/src/calibre/ebooks/metadata/book/base.py:734 msgid "Timestamp" msgstr "" -#: /home/kovid/work/calibre/src/calibre/ebooks/metadata/book/base.py:628 +#: /home/kovid/work/calibre/src/calibre/ebooks/metadata/book/base.py:736 #: /home/kovid/work/calibre/src/calibre/ebooks/oeb/transforms/jacket.py:167 -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/fetch_metadata.py:65 -#: /home/kovid/work/calibre/src/calibre/gui2/library/models.py:70 -#: /home/kovid/work/calibre/src/calibre/library/field_metadata.py:259 +#: /home/kovid/work/calibre/src/calibre/gui2/library/models.py:68 +#: /home/kovid/work/calibre/src/calibre/gui2/metadata/single_download.py:132 +#: /home/kovid/work/calibre/src/calibre/library/field_metadata.py:271 msgid "Published" msgstr "" -#: /home/kovid/work/calibre/src/calibre/ebooks/metadata/book/base.py:630 +#: /home/kovid/work/calibre/src/calibre/ebooks/metadata/book/base.py:738 msgid "Rights" msgstr "" @@ -2683,190 +2928,7 @@ msgstr "" msgid "No cover found" msgstr "" -#: /home/kovid/work/calibre/src/calibre/ebooks/metadata/covers.py:27 -#: /home/kovid/work/calibre/src/calibre/ebooks/metadata/nicebooks.py:45 -msgid "Cover download" -msgstr "" - -#: /home/kovid/work/calibre/src/calibre/ebooks/metadata/covers.py:79 -msgid "Download covers from openlibrary.org" -msgstr "" - -#: /home/kovid/work/calibre/src/calibre/ebooks/metadata/covers.py:107 -msgid "ISBN: %s not found" -msgstr "" - -#: /home/kovid/work/calibre/src/calibre/ebooks/metadata/covers.py:117 -msgid "Download covers from amazon.com" -msgstr "" - -#: /home/kovid/work/calibre/src/calibre/ebooks/metadata/covers.py:205 -msgid "Download covers from Douban.com" -msgstr "" - -#: /home/kovid/work/calibre/src/calibre/ebooks/metadata/covers.py:214 -msgid "Douban.com API timed out. Try again later." -msgstr "" - -#: /home/kovid/work/calibre/src/calibre/ebooks/metadata/douban.py:42 -msgid "Downloads metadata from Douban.com" -msgstr "" - -#: /home/kovid/work/calibre/src/calibre/ebooks/metadata/fetch.py:57 -msgid "Metadata download" -msgstr "" - -#: /home/kovid/work/calibre/src/calibre/ebooks/metadata/fetch.py:141 -msgid "ratings" -msgstr "" - -#: /home/kovid/work/calibre/src/calibre/ebooks/metadata/fetch.py:141 -msgid "tags" -msgstr "" - -#: /home/kovid/work/calibre/src/calibre/ebooks/metadata/fetch.py:142 -msgid "description/reviews" -msgstr "" - -#: /home/kovid/work/calibre/src/calibre/ebooks/metadata/fetch.py:143 -msgid "Download %s from %s" -msgstr "" - -#: /home/kovid/work/calibre/src/calibre/ebooks/metadata/fetch.py:150 -msgid "Convert comments downloaded from %s to plain text" -msgstr "" - -#: /home/kovid/work/calibre/src/calibre/ebooks/metadata/fetch.py:178 -#: /home/kovid/work/calibre/src/calibre/ebooks/metadata/sources/google.py:159 -msgid "Downloads metadata from Google Books" -msgstr "" - -#: /home/kovid/work/calibre/src/calibre/ebooks/metadata/fetch.py:195 -msgid "Downloads metadata from isbndb.com" -msgstr "" - -#: /home/kovid/work/calibre/src/calibre/ebooks/metadata/fetch.py:223 -msgid "" -"To use isbndb.com you must sign up for a %sfree account%s and enter your " -"access key below." -msgstr "" - -#: /home/kovid/work/calibre/src/calibre/ebooks/metadata/fetch.py:233 -msgid "Downloads social metadata from amazon.com" -msgstr "" - -#: /home/kovid/work/calibre/src/calibre/ebooks/metadata/fetch.py:254 -msgid "Downloads series information from ww2.kdl.org" -msgstr "" - -#: /home/kovid/work/calibre/src/calibre/ebooks/metadata/fictionwise.py:25 -msgid "Downloads metadata from Fictionwise" -msgstr "" - -#: /home/kovid/work/calibre/src/calibre/ebooks/metadata/fictionwise.py:90 -#: /home/kovid/work/calibre/src/calibre/ebooks/metadata/nicebooks.py:108 -msgid "Query: %s" -msgstr "" - -#: /home/kovid/work/calibre/src/calibre/ebooks/metadata/fictionwise.py:100 -#: /home/kovid/work/calibre/src/calibre/ebooks/metadata/fictionwise.py:285 -msgid "Fictionwise timed out. Try again later." -msgstr "" - -#: /home/kovid/work/calibre/src/calibre/ebooks/metadata/fictionwise.py:101 -#: /home/kovid/work/calibre/src/calibre/ebooks/metadata/fictionwise.py:286 -msgid "Fictionwise encountered an error." -msgstr "" - -#: /home/kovid/work/calibre/src/calibre/ebooks/metadata/fictionwise.py:219 -msgid "" -"SUMMARY:\n" -" %s" -msgstr "" - -#: /home/kovid/work/calibre/src/calibre/ebooks/metadata/fictionwise.py:316 -#: /home/kovid/work/calibre/src/calibre/ebooks/metadata/fictionwise.py:333 -msgid "Failed to get all details for an entry" -msgstr "" - -#: /home/kovid/work/calibre/src/calibre/ebooks/metadata/fictionwise.py:354 -msgid "" -" %prog [options]\n" -"\n" -" Fetch book metadata from Fictionwise. You must specify one of title, " -"author,\n" -" or keywords. No ISBN specification possible. Will fetch a maximum of " -"20 matches,\n" -" so you should make your query as specific as possible.\n" -" " -msgstr "" - -#: /home/kovid/work/calibre/src/calibre/ebooks/metadata/fictionwise.py:362 -#: /home/kovid/work/calibre/src/calibre/ebooks/metadata/nicebooks.py:363 -msgid "Book title" -msgstr "" - -#: /home/kovid/work/calibre/src/calibre/ebooks/metadata/fictionwise.py:363 -#: /home/kovid/work/calibre/src/calibre/ebooks/metadata/nicebooks.py:364 -msgid "Book author(s)" -msgstr "" - -#: /home/kovid/work/calibre/src/calibre/ebooks/metadata/fictionwise.py:364 -#: /home/kovid/work/calibre/src/calibre/ebooks/metadata/nicebooks.py:365 -msgid "Book publisher" -msgstr "" - -#: /home/kovid/work/calibre/src/calibre/ebooks/metadata/fictionwise.py:365 -#: /home/kovid/work/calibre/src/calibre/ebooks/metadata/nicebooks.py:367 -msgid "Keywords" -msgstr "" - -#: /home/kovid/work/calibre/src/calibre/ebooks/metadata/fictionwise.py:367 -#: /home/kovid/work/calibre/src/calibre/ebooks/metadata/nicebooks.py:373 -msgid "Maximum number of results to fetch" -msgstr "" - -#: /home/kovid/work/calibre/src/calibre/ebooks/metadata/fictionwise.py:369 -#: /home/kovid/work/calibre/src/calibre/ebooks/metadata/nicebooks.py:375 -msgid "Be more verbose about errors" -msgstr "" - -#: /home/kovid/work/calibre/src/calibre/ebooks/metadata/fictionwise.py:383 -#: /home/kovid/work/calibre/src/calibre/ebooks/metadata/nicebooks.py:390 -msgid "No result found for this search!" -msgstr "" - -#: /home/kovid/work/calibre/src/calibre/ebooks/metadata/isbndb.py:107 -msgid "" -"\n" -"%prog [options] key\n" -"\n" -"Fetch metadata for books from isndb.com. You can specify either the\n" -"books ISBN ID or its title and author. If you specify the title and author,\n" -"then more than one book may be returned.\n" -"\n" -"key is the account key you generate after signing up for a free account from " -"isbndb.com.\n" -"\n" -msgstr "" - -#: /home/kovid/work/calibre/src/calibre/ebooks/metadata/isbndb.py:118 -msgid "The ISBN ID of the book you want metadata for." -msgstr "" - -#: /home/kovid/work/calibre/src/calibre/ebooks/metadata/isbndb.py:120 -msgid "The author whose book to search for." -msgstr "" - -#: /home/kovid/work/calibre/src/calibre/ebooks/metadata/isbndb.py:122 -msgid "The title of the book to search for." -msgstr "" - -#: /home/kovid/work/calibre/src/calibre/ebooks/metadata/isbndb.py:124 -msgid "The publisher of the book to search for." -msgstr "" - -#: /home/kovid/work/calibre/src/calibre/ebooks/metadata/library_thing.py:75 +#: /home/kovid/work/calibre/src/calibre/ebooks/metadata/library_thing.py:64 msgid "" "\n" "%prog [options] ISBN\n" @@ -2875,82 +2937,108 @@ msgid "" "LibraryThing.com\n" msgstr "" -#: /home/kovid/work/calibre/src/calibre/ebooks/metadata/nicebooks.py:26 -msgid "Downloads metadata from french Nicebooks" -msgstr "" - -#: /home/kovid/work/calibre/src/calibre/ebooks/metadata/nicebooks.py:42 -msgid "Downloads covers from french Nicebooks" -msgstr "" - -#: /home/kovid/work/calibre/src/calibre/ebooks/metadata/nicebooks.py:118 -#: /home/kovid/work/calibre/src/calibre/ebooks/metadata/nicebooks.py:242 -#: /home/kovid/work/calibre/src/calibre/ebooks/metadata/nicebooks.py:320 -msgid "Nicebooks timed out. Try again later." -msgstr "" - -#: /home/kovid/work/calibre/src/calibre/ebooks/metadata/nicebooks.py:119 -#: /home/kovid/work/calibre/src/calibre/ebooks/metadata/nicebooks.py:243 -msgid "Nicebooks encountered an error." -msgstr "" - -#: /home/kovid/work/calibre/src/calibre/ebooks/metadata/nicebooks.py:323 -msgid "ISBN: %s not found." -msgstr "" - -#: /home/kovid/work/calibre/src/calibre/ebooks/metadata/nicebooks.py:324 -msgid "An errror occured with Nicebooks cover fetcher" -msgstr "" - -#: /home/kovid/work/calibre/src/calibre/ebooks/metadata/nicebooks.py:354 -msgid "" -" %prog [options]\n" -"\n" -" Fetch book metadata from Nicebooks. You must specify one of title, " -"author,\n" -" ISBN, publisher or keywords. Will fetch a maximum of 20 matches,\n" -" so you should make your query as specific as possible.\n" -" It can also get covers if the option is activated.\n" -" " -msgstr "" - -#: /home/kovid/work/calibre/src/calibre/ebooks/metadata/nicebooks.py:366 -msgid "Book ISBN" -msgstr "" - -#: /home/kovid/work/calibre/src/calibre/ebooks/metadata/nicebooks.py:369 -msgid "Covers: 1-Check/ 2-Download" -msgstr "" - -#: /home/kovid/work/calibre/src/calibre/ebooks/metadata/nicebooks.py:371 -msgid "Covers files path" -msgstr "" - -#: /home/kovid/work/calibre/src/calibre/ebooks/metadata/nicebooks.py:396 -msgid "No cover found!" -msgstr "" - -#: /home/kovid/work/calibre/src/calibre/ebooks/metadata/nicebooks.py:398 -msgid "A cover was found for this book" -msgstr "" - -#: /home/kovid/work/calibre/src/calibre/ebooks/metadata/nicebooks.py:407 -msgid "Cover saved to file " -msgstr "" - -#: /home/kovid/work/calibre/src/calibre/ebooks/metadata/opf2.py:1312 -#: /home/kovid/work/calibre/src/calibre/ebooks/oeb/base.py:1448 +#: /home/kovid/work/calibre/src/calibre/ebooks/metadata/opf2.py:1358 +#: /home/kovid/work/calibre/src/calibre/ebooks/oeb/base.py:1493 +#: /home/kovid/work/calibre/src/calibre/gui2/metadata/single.py:883 +#: /home/kovid/work/calibre/src/calibre/gui2/store/search/models.py:33 msgid "Cover" msgstr "" -#: /home/kovid/work/calibre/src/calibre/ebooks/metadata/sources/amazon.py:16 -msgid "Downloads metadata from Amazon" +#: /home/kovid/work/calibre/src/calibre/ebooks/metadata/sources/amazon.py:384 +msgid "Downloads metadata and covers from Amazon" msgstr "" -#: /home/kovid/work/calibre/src/calibre/ebooks/metadata/sources/base.py:22 +#: /home/kovid/work/calibre/src/calibre/ebooks/metadata/sources/amazon.py:394 +msgid "US" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/ebooks/metadata/sources/amazon.py:395 +msgid "France" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/ebooks/metadata/sources/amazon.py:396 +msgid "Germany" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/ebooks/metadata/sources/amazon.py:397 +msgid "UK" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/ebooks/metadata/sources/amazon.py:398 +msgid "Italy" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/ebooks/metadata/sources/amazon.py:402 +msgid "Amazon website to use:" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/ebooks/metadata/sources/amazon.py:403 +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:508 +msgid "Amazon timed out. Try again later." +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/ebooks/metadata/sources/base.py:159 msgid "Metadata source" msgstr "" +#: /home/kovid/work/calibre/src/calibre/ebooks/metadata/sources/douban.py:154 +msgid "Downloads metadata and covers from Douban.com" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/ebooks/metadata/sources/google.py:160 +msgid "Downloads metadata and covers from Google Books" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/ebooks/metadata/sources/isbndb.py:27 +msgid "Downloads metadata from isbndb.com" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/ebooks/metadata/sources/isbndb.py:37 +msgid "IsbnDB key:" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/ebooks/metadata/sources/isbndb.py:38 +msgid "" +"To use isbndb.com you have to sign up for a free accountat isbndb.com and " +"get an access key." +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/ebooks/metadata/sources/isbndb.py:42 +msgid "" +"To use metadata from isbndb.com you must sign up for a free account and get " +"an isbndb key and enter it below. Instructions to get the key are here." +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/ebooks/metadata/sources/openlibrary.py:15 +msgid "Downloads covers from The Open Library" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/ebooks/metadata/sources/overdrive.py:33 +msgid "Downloads metadata and covers from Overdrive's Content Reserve" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/ebooks/metadata/sources/overdrive.py:45 +msgid "Download all metadata (slow)" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/ebooks/metadata/sources/overdrive.py:46 +msgid "Enable this option to gather all metadata available from Overdrive." +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/ebooks/metadata/sources/overdrive.py:49 +msgid "" +"Additional metadata can be taken from Overdrive's book detail page. This " +"includes a limited set of tags used by libraries, comments, language, and " +"the ebook ISBN. Collecting this data is disabled by default due to the extra " +"time required. Check the download all metadata option below to enable " +"downloading this data." +msgstr "" + #: /home/kovid/work/calibre/src/calibre/ebooks/mobi/output.py:22 msgid "Modify images to meet Palm device size limitations." msgstr "" @@ -2989,74 +3077,74 @@ msgstr "" msgid "All articles" msgstr "" -#: /home/kovid/work/calibre/src/calibre/ebooks/mobi/reader.py:267 +#: /home/kovid/work/calibre/src/calibre/ebooks/mobi/reader.py:274 msgid "This is an Amazon Topaz book. It cannot be processed." msgstr "" -#: /home/kovid/work/calibre/src/calibre/ebooks/oeb/base.py:1449 +#: /home/kovid/work/calibre/src/calibre/ebooks/oeb/base.py:1494 msgid "Title Page" msgstr "" -#: /home/kovid/work/calibre/src/calibre/ebooks/oeb/base.py:1450 +#: /home/kovid/work/calibre/src/calibre/ebooks/oeb/base.py:1495 #: /home/kovid/work/calibre/src/calibre/ebooks/oeb/transforms/htmltoc.py:15 -#: /home/kovid/work/calibre/src/calibre/gui2/viewer/main.py:54 +#: /home/kovid/work/calibre/src/calibre/gui2/viewer/main.py:56 #: /home/kovid/work/calibre/src/calibre/gui2/viewer/main_ui.py:199 msgid "Table of Contents" msgstr "" -#: /home/kovid/work/calibre/src/calibre/ebooks/oeb/base.py:1451 +#: /home/kovid/work/calibre/src/calibre/ebooks/oeb/base.py:1496 msgid "Index" msgstr "" -#: /home/kovid/work/calibre/src/calibre/ebooks/oeb/base.py:1452 +#: /home/kovid/work/calibre/src/calibre/ebooks/oeb/base.py:1497 msgid "Glossary" msgstr "" -#: /home/kovid/work/calibre/src/calibre/ebooks/oeb/base.py:1453 +#: /home/kovid/work/calibre/src/calibre/ebooks/oeb/base.py:1498 msgid "Acknowledgements" msgstr "" -#: /home/kovid/work/calibre/src/calibre/ebooks/oeb/base.py:1454 +#: /home/kovid/work/calibre/src/calibre/ebooks/oeb/base.py:1499 msgid "Bibliography" msgstr "" -#: /home/kovid/work/calibre/src/calibre/ebooks/oeb/base.py:1455 +#: /home/kovid/work/calibre/src/calibre/ebooks/oeb/base.py:1500 msgid "Colophon" msgstr "" -#: /home/kovid/work/calibre/src/calibre/ebooks/oeb/base.py:1456 +#: /home/kovid/work/calibre/src/calibre/ebooks/oeb/base.py:1501 msgid "Copyright" msgstr "" -#: /home/kovid/work/calibre/src/calibre/ebooks/oeb/base.py:1457 +#: /home/kovid/work/calibre/src/calibre/ebooks/oeb/base.py:1502 msgid "Dedication" msgstr "" -#: /home/kovid/work/calibre/src/calibre/ebooks/oeb/base.py:1458 +#: /home/kovid/work/calibre/src/calibre/ebooks/oeb/base.py:1503 msgid "Epigraph" msgstr "" -#: /home/kovid/work/calibre/src/calibre/ebooks/oeb/base.py:1459 +#: /home/kovid/work/calibre/src/calibre/ebooks/oeb/base.py:1504 msgid "Foreword" msgstr "" -#: /home/kovid/work/calibre/src/calibre/ebooks/oeb/base.py:1460 +#: /home/kovid/work/calibre/src/calibre/ebooks/oeb/base.py:1505 msgid "List of Illustrations" msgstr "" -#: /home/kovid/work/calibre/src/calibre/ebooks/oeb/base.py:1461 +#: /home/kovid/work/calibre/src/calibre/ebooks/oeb/base.py:1506 msgid "List of Tables" msgstr "" -#: /home/kovid/work/calibre/src/calibre/ebooks/oeb/base.py:1462 +#: /home/kovid/work/calibre/src/calibre/ebooks/oeb/base.py:1507 msgid "Notes" msgstr "" -#: /home/kovid/work/calibre/src/calibre/ebooks/oeb/base.py:1463 +#: /home/kovid/work/calibre/src/calibre/ebooks/oeb/base.py:1508 msgid "Preface" msgstr "" -#: /home/kovid/work/calibre/src/calibre/ebooks/oeb/base.py:1464 +#: /home/kovid/work/calibre/src/calibre/ebooks/oeb/base.py:1509 msgid "Main Text" msgstr "" @@ -3066,8 +3154,7 @@ msgstr "" #: /home/kovid/work/calibre/src/calibre/ebooks/oeb/transforms/cover.py:98 #: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk.py:176 -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single.py:220 -#: /home/kovid/work/calibre/src/calibre/gui2/metadata/basic_widgets.py:703 +#: /home/kovid/work/calibre/src/calibre/gui2/metadata/basic_widgets.py:783 msgid "Book %s of %s" msgstr "" @@ -3076,8 +3163,10 @@ msgid "HTML TOC generation options." msgstr "" #: /home/kovid/work/calibre/src/calibre/ebooks/oeb/transforms/jacket.py:169 -#: /home/kovid/work/calibre/src/calibre/gui2/library/models.py:71 -#: /home/kovid/work/calibre/src/calibre/library/server/browse.py:689 +#: /home/kovid/work/calibre/src/calibre/gui2/library/models.py:69 +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/create_custom_column.py:68 +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/metadata_sources.py:150 +#: /home/kovid/work/calibre/src/calibre/library/server/browse.py:759 msgid "Rating" msgstr "" @@ -3103,7 +3192,7 @@ msgstr "" msgid "Footnotes" msgstr "" -#: /home/kovid/work/calibre/src/calibre/ebooks/pdb/ereader/reader132.py:135 +#: /home/kovid/work/calibre/src/calibre/ebooks/pdb/ereader/reader132.py:139 msgid "Sidebar" msgstr "" @@ -3118,9 +3207,9 @@ msgid "" msgstr "" #: /home/kovid/work/calibre/src/calibre/ebooks/pdb/output.py:32 -#: /home/kovid/work/calibre/src/calibre/ebooks/pml/output.py:37 +#: /home/kovid/work/calibre/src/calibre/ebooks/pml/output.py:36 #: /home/kovid/work/calibre/src/calibre/ebooks/rb/output.py:21 -#: /home/kovid/work/calibre/src/calibre/ebooks/txt/output.py:41 +#: /home/kovid/work/calibre/src/calibre/ebooks/txt/output.py:40 msgid "Add Table of Contents to beginning of the book." msgstr "" @@ -3236,11 +3325,12 @@ msgstr "" #: /home/kovid/work/calibre/src/calibre/ebooks/pdf/manipulate/info.py:46 #: /home/kovid/work/calibre/src/calibre/gui2/dialogs/delete_matching_from_device.py:75 -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/edit_authors_dialog.py:35 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/edit_authors_dialog.py:40 msgid "Author" msgstr "" #: /home/kovid/work/calibre/src/calibre/ebooks/pdf/manipulate/info.py:47 +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/emailp.py:27 msgid "Subject" msgstr "" @@ -3342,16 +3432,16 @@ msgid "" "full first page of the generated pdf." msgstr "" -#: /home/kovid/work/calibre/src/calibre/ebooks/pdf/pdftohtml.py:55 +#: /home/kovid/work/calibre/src/calibre/ebooks/pdf/pdftohtml.py:57 msgid "Could not find pdftohtml, check it is in your PATH" msgstr "" -#: /home/kovid/work/calibre/src/calibre/ebooks/pml/output.py:33 +#: /home/kovid/work/calibre/src/calibre/ebooks/pml/output.py:32 msgid "" "Specify the character encoding of the output document. The default is cp1252." msgstr "" -#: /home/kovid/work/calibre/src/calibre/ebooks/pml/output.py:40 +#: /home/kovid/work/calibre/src/calibre/ebooks/pml/output.py:39 msgid "" "Do not reduce the size or bit depth of images. Images have their size and " "depth reduced by default to accommodate applications that can not convert " @@ -3363,7 +3453,7 @@ msgstr "" msgid "Table of Contents:" msgstr "" -#: /home/kovid/work/calibre/src/calibre/ebooks/rtf/input.py:271 +#: /home/kovid/work/calibre/src/calibre/ebooks/rtf/input.py:272 msgid "" "This RTF file has a feature calibre does not support. Convert it to HTML " "first and then try it.\n" @@ -3376,13 +3466,13 @@ msgstr "" #: /home/kovid/work/calibre/src/calibre/ebooks/snb/output.py:25 #: /home/kovid/work/calibre/src/calibre/ebooks/tcr/output.py:23 -#: /home/kovid/work/calibre/src/calibre/ebooks/txt/output.py:37 +#: /home/kovid/work/calibre/src/calibre/ebooks/txt/output.py:36 msgid "" "Specify the character encoding of the output document. The default is utf-8." msgstr "" #: /home/kovid/work/calibre/src/calibre/ebooks/snb/output.py:29 -#: /home/kovid/work/calibre/src/calibre/ebooks/txt/output.py:44 +#: /home/kovid/work/calibre/src/calibre/ebooks/txt/output.py:43 msgid "" "The maximum number of characters per line. This splits on the first space " "before the specified value. If no space is found the line will be broken at " @@ -3470,20 +3560,20 @@ msgstr "" msgid "Do not insert a Table of Contents into the output text." msgstr "" -#: /home/kovid/work/calibre/src/calibre/ebooks/txt/output.py:31 +#: /home/kovid/work/calibre/src/calibre/ebooks/txt/output.py:30 msgid "" "Type of newline to use. Options are %s. Default is 'system'. Use 'old_mac' " "for compatibility with Mac OS 9 and earlier. For Mac OS X use 'unix'. " "'system' will default to the newline type used by this OS." msgstr "" -#: /home/kovid/work/calibre/src/calibre/ebooks/txt/output.py:51 +#: /home/kovid/work/calibre/src/calibre/ebooks/txt/output.py:50 msgid "" "Force splitting on the max-line-length value when no space is present. Also " "allows max-line-length to be below the minimum" msgstr "" -#: /home/kovid/work/calibre/src/calibre/ebooks/txt/output.py:56 +#: /home/kovid/work/calibre/src/calibre/ebooks/txt/output.py:55 msgid "" "Formatting used within the document.\n" "* plain: Produce plain text.\n" @@ -3491,298 +3581,340 @@ msgid "" "* textile: Produce Textile formatted text." msgstr "" -#: /home/kovid/work/calibre/src/calibre/ebooks/txt/output.py:62 +#: /home/kovid/work/calibre/src/calibre/ebooks/txt/output.py:61 msgid "" "Do not remove links within the document. This is only useful when paired " "with a txt-output-formatting option that is not none because links are " "always removed with plain text output." msgstr "" -#: /home/kovid/work/calibre/src/calibre/ebooks/txt/output.py:67 +#: /home/kovid/work/calibre/src/calibre/ebooks/txt/output.py:66 msgid "" "Do not remove image references within the document. This is only useful when " "paired with a txt-output-formatting option that is not none because links " "are always removed with plain text output." msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/__init__.py:71 +#: /home/kovid/work/calibre/src/calibre/ebooks/txt/output.py:71 +msgid "" +"Do not remove font color from output. This is only useful when txt-output-" +"formatting is set to textile. Textile is the only formatting that supports " +"setting font color. If this option is not specified font color will not be " +"set and default to the color displayed by the reader (generally this is " +"black)." +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/__init__.py:103 msgid "Send file to storage card instead of main memory by default" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/__init__.py:73 +#: /home/kovid/work/calibre/src/calibre/gui2/__init__.py:105 msgid "Confirm before deleting" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/__init__.py:75 +#: /home/kovid/work/calibre/src/calibre/gui2/__init__.py:107 msgid "Main window geometry" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/__init__.py:77 +#: /home/kovid/work/calibre/src/calibre/gui2/__init__.py:109 msgid "Notify when a new version is available" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/__init__.py:79 +#: /home/kovid/work/calibre/src/calibre/gui2/__init__.py:111 msgid "Use Roman numerals for series number" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/__init__.py:81 +#: /home/kovid/work/calibre/src/calibre/gui2/__init__.py:113 msgid "Sort tags list by name, popularity, or rating" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/__init__.py:83 +#: /home/kovid/work/calibre/src/calibre/gui2/__init__.py:115 +msgid "Match tags by any or all." +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/__init__.py:117 msgid "Number of covers to show in the cover browsing mode" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/__init__.py:85 +#: /home/kovid/work/calibre/src/calibre/gui2/__init__.py:119 msgid "Defaults for conversion to LRF" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/__init__.py:87 +#: /home/kovid/work/calibre/src/calibre/gui2/__init__.py:121 msgid "Options for the LRF ebook viewer" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/__init__.py:90 +#: /home/kovid/work/calibre/src/calibre/gui2/__init__.py:124 msgid "Formats that are viewed using the internal viewer" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/__init__.py:92 +#: /home/kovid/work/calibre/src/calibre/gui2/__init__.py:126 msgid "Columns to be displayed in the book list" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/__init__.py:93 +#: /home/kovid/work/calibre/src/calibre/gui2/__init__.py:127 msgid "Automatically launch content server on application startup" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/__init__.py:94 +#: /home/kovid/work/calibre/src/calibre/gui2/__init__.py:128 msgid "Oldest news kept in database" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/__init__.py:95 +#: /home/kovid/work/calibre/src/calibre/gui2/__init__.py:129 msgid "Show system tray icon" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/__init__.py:97 +#: /home/kovid/work/calibre/src/calibre/gui2/__init__.py:131 msgid "Upload downloaded news to device" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/__init__.py:99 +#: /home/kovid/work/calibre/src/calibre/gui2/__init__.py:133 msgid "Delete books from library after uploading to device" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/__init__.py:101 +#: /home/kovid/work/calibre/src/calibre/gui2/__init__.py:135 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:103 +#: /home/kovid/work/calibre/src/calibre/gui2/__init__.py:137 msgid "Disable notifications from the system tray icon" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/__init__.py:105 +#: /home/kovid/work/calibre/src/calibre/gui2/__init__.py:139 msgid "Default action to perform when send to device button is clicked" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/__init__.py:110 +#: /home/kovid/work/calibre/src/calibre/gui2/__init__.py:144 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:113 +#: /home/kovid/work/calibre/src/calibre/gui2/__init__.py:147 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:131 -msgid "Maximum number of waiting worker processes" +#: /home/kovid/work/calibre/src/calibre/gui2/__init__.py:165 +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:133 +#: /home/kovid/work/calibre/src/calibre/gui2/__init__.py:169 msgid "Download social metadata (tags/rating/etc.)" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/__init__.py:135 +#: /home/kovid/work/calibre/src/calibre/gui2/__init__.py:171 msgid "Overwrite author and title with new metadata" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/__init__.py:137 -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/fetch_metadata_ui.py:101 +#: /home/kovid/work/calibre/src/calibre/gui2/__init__.py:173 msgid "Automatically download the cover, if available" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/__init__.py:139 +#: /home/kovid/work/calibre/src/calibre/gui2/__init__.py:175 msgid "Limit max simultaneous jobs to number of CPUs" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/__init__.py:141 +#: /home/kovid/work/calibre/src/calibre/gui2/__init__.py:177 msgid "The layout of the user interface" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/__init__.py:143 +#: /home/kovid/work/calibre/src/calibre/gui2/__init__.py:179 msgid "Show the average rating per item indication in the tag browser" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/__init__.py:145 +#: /home/kovid/work/calibre/src/calibre/gui2/__init__.py:181 msgid "Disable UI animations" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/__init__.py:150 +#: /home/kovid/work/calibre/src/calibre/gui2/__init__.py:186 msgid "tag browser categories not to display" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/__init__.py:419 +#: /home/kovid/work/calibre/src/calibre/gui2/__init__.py:461 msgid "Choose Files" msgstr "" #: /home/kovid/work/calibre/src/calibre/gui2/actions/add.py:29 -msgid "Add books" +#: /home/kovid/work/calibre/src/calibre/gui2/metadata/basic_widgets.py:599 +msgid "Books" msgstr "" #: /home/kovid/work/calibre/src/calibre/gui2/actions/add.py:30 -msgid "Add books to the calibre library/device from files on your computer" +msgid "EPUB Books" msgstr "" #: /home/kovid/work/calibre/src/calibre/gui2/actions/add.py:31 -msgid "A" +msgid "LRF Books" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/actions/add.py:32 +msgid "HTML Books" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/actions/add.py:33 +msgid "LIT Books" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/actions/add.py:34 +msgid "MOBI Books" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/actions/add.py:35 +msgid "Topaz books" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/actions/add.py:36 +msgid "Text books" msgstr "" #: /home/kovid/work/calibre/src/calibre/gui2/actions/add.py:37 -msgid "Add books from a single directory" +msgid "PDF Books" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/actions/add.py:38 +msgid "SNB Books" msgstr "" #: /home/kovid/work/calibre/src/calibre/gui2/actions/add.py:39 +msgid "Comics" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/actions/add.py:40 +msgid "Archives" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/actions/add.py:47 +msgid "Add books" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/actions/add.py:48 +msgid "Add books to the calibre library/device from files on your computer" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/actions/add.py:49 +msgid "A" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/actions/add.py:55 +msgid "Add books from a single directory" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/actions/add.py:57 msgid "" "Add books from directories, including sub-directories (One book per " "directory, assumes every ebook file is the same book in a different format)" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/actions/add.py:43 +#: /home/kovid/work/calibre/src/calibre/gui2/actions/add.py:61 msgid "" "Add books from directories, including sub directories (Multiple books per " "directory, assumes every ebook file is a different book)" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/actions/add.py:47 +#: /home/kovid/work/calibre/src/calibre/gui2/actions/add.py:65 msgid "Add Empty book. (Book entry with no formats)" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/actions/add.py:48 +#: /home/kovid/work/calibre/src/calibre/gui2/actions/add.py:66 msgid "Shift+Ctrl+E" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/actions/add.py:49 +#: /home/kovid/work/calibre/src/calibre/gui2/actions/add.py:67 msgid "Add from ISBN" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/actions/add.py:113 +#: /home/kovid/work/calibre/src/calibre/gui2/actions/add.py:69 +msgid "Add files to selected book records" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/actions/add.py:70 +msgid "Shift+A" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/actions/add.py:90 +msgid "Are you sure" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/actions/add.py:91 +msgid "" +"Are you sure you want to add the same files to all %d books? If the " +"formatalready exists for a book, it will be replaced." +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/actions/add.py:97 +msgid "Select book files" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/actions/add.py:168 msgid "Adding" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/actions/add.py:114 +#: /home/kovid/work/calibre/src/calibre/gui2/actions/add.py:169 msgid "Creating book records from ISBNs" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/actions/add.py:194 -#: /home/kovid/work/calibre/src/calibre/gui2/actions/add.py:256 +#: /home/kovid/work/calibre/src/calibre/gui2/actions/add.py:268 +#: /home/kovid/work/calibre/src/calibre/gui2/actions/add.py:317 msgid "Uploading books to device." msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/actions/add.py:211 -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single.py:308 -#: /home/kovid/work/calibre/src/calibre/gui2/metadata/basic_widgets.py:529 -msgid "Books" -msgstr "" - -#: /home/kovid/work/calibre/src/calibre/gui2/actions/add.py:212 -msgid "EPUB Books" -msgstr "" - -#: /home/kovid/work/calibre/src/calibre/gui2/actions/add.py:213 -msgid "LRF Books" -msgstr "" - -#: /home/kovid/work/calibre/src/calibre/gui2/actions/add.py:214 -msgid "HTML Books" -msgstr "" - -#: /home/kovid/work/calibre/src/calibre/gui2/actions/add.py:215 -msgid "LIT Books" -msgstr "" - -#: /home/kovid/work/calibre/src/calibre/gui2/actions/add.py:216 -msgid "MOBI Books" -msgstr "" - -#: /home/kovid/work/calibre/src/calibre/gui2/actions/add.py:217 -msgid "Topaz books" -msgstr "" - -#: /home/kovid/work/calibre/src/calibre/gui2/actions/add.py:218 -msgid "Text books" -msgstr "" - -#: /home/kovid/work/calibre/src/calibre/gui2/actions/add.py:219 -msgid "PDF Books" -msgstr "" - -#: /home/kovid/work/calibre/src/calibre/gui2/actions/add.py:220 -msgid "SNB Books" -msgstr "" - -#: /home/kovid/work/calibre/src/calibre/gui2/actions/add.py:221 -msgid "Comics" -msgstr "" - -#: /home/kovid/work/calibre/src/calibre/gui2/actions/add.py:222 -msgid "Archives" -msgstr "" - -#: /home/kovid/work/calibre/src/calibre/gui2/actions/add.py:227 +#: /home/kovid/work/calibre/src/calibre/gui2/actions/add.py:288 msgid "Supported books" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/actions/add.py:266 +#: /home/kovid/work/calibre/src/calibre/gui2/actions/add.py:291 +msgid "Select books" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/actions/add.py:328 msgid "Merged some books" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/actions/add.py:267 +#: /home/kovid/work/calibre/src/calibre/gui2/actions/add.py:329 msgid "" "The following duplicate books were found and incoming book formats were " "processed and merged into your Calibre database according to your automerge " "settings:" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/actions/add.py:276 +#: /home/kovid/work/calibre/src/calibre/gui2/actions/add.py:349 msgid "Failed to read metadata" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/actions/add.py:277 +#: /home/kovid/work/calibre/src/calibre/gui2/actions/add.py:350 msgid "Failed to read metadata from the following" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/actions/add.py:298 -#: /home/kovid/work/calibre/src/calibre/gui2/actions/add.py:303 -#: /home/kovid/work/calibre/src/calibre/gui2/actions/add.py:322 +#: /home/kovid/work/calibre/src/calibre/gui2/actions/add.py:371 +#: /home/kovid/work/calibre/src/calibre/gui2/actions/add.py:376 +#: /home/kovid/work/calibre/src/calibre/gui2/actions/add.py:395 msgid "Add to library" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/actions/add.py:303 -#: /home/kovid/work/calibre/src/calibre/gui2/actions/delete.py:116 +#: /home/kovid/work/calibre/src/calibre/gui2/actions/add.py:376 +#: /home/kovid/work/calibre/src/calibre/gui2/actions/delete.py:127 +#: /home/kovid/work/calibre/src/calibre/gui2/actions/store.py:76 +#: /home/kovid/work/calibre/src/calibre/gui2/actions/store.py:95 +#: /home/kovid/work/calibre/src/calibre/gui2/actions/store.py:104 #: /home/kovid/work/calibre/src/calibre/gui2/actions/tweak_epub.py:28 -#: /home/kovid/work/calibre/src/calibre/gui2/actions/view.py:85 -#: /home/kovid/work/calibre/src/calibre/gui2/actions/view.py:131 +#: /home/kovid/work/calibre/src/calibre/gui2/actions/view.py:139 +#: /home/kovid/work/calibre/src/calibre/gui2/actions/view.py:185 msgid "No book selected" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/actions/add.py:316 +#: /home/kovid/work/calibre/src/calibre/gui2/actions/add.py:389 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:322 +#: /home/kovid/work/calibre/src/calibre/gui2/actions/add.py:395 msgid "No book files found" msgstr "" @@ -3795,57 +3927,65 @@ msgid "Add books to your calibre library from the connected device" msgstr "" #: /home/kovid/work/calibre/src/calibre/gui2/actions/annotate.py:20 -#: /home/kovid/work/calibre/src/calibre/gui2/device.py:544 +#: /home/kovid/work/calibre/src/calibre/gui2/device.py:543 msgid "Fetch annotations (experimental)" msgstr "" #: /home/kovid/work/calibre/src/calibre/gui2/actions/annotate.py:56 -#: /home/kovid/work/calibre/src/calibre/gui2/actions/annotate.py:240 -msgid "Use library only" +msgid "Not supported" msgstr "" #: /home/kovid/work/calibre/src/calibre/gui2/actions/annotate.py:57 -#: /home/kovid/work/calibre/src/calibre/gui2/actions/annotate.py:241 +msgid "Fetching annotations is not supported for this device" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/actions/annotate.py:61 +#: /home/kovid/work/calibre/src/calibre/gui2/actions/annotate.py:245 +msgid "Use library only" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/actions/annotate.py:62 +#: /home/kovid/work/calibre/src/calibre/gui2/actions/annotate.py:246 msgid "User annotations generated from main library only" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/actions/annotate.py:64 -#: /home/kovid/work/calibre/src/calibre/gui2/actions/catalog.py:30 +#: /home/kovid/work/calibre/src/calibre/gui2/actions/annotate.py:69 +#: /home/kovid/work/calibre/src/calibre/gui2/actions/catalog.py:33 #: /home/kovid/work/calibre/src/calibre/gui2/actions/convert.py:87 #: /home/kovid/work/calibre/src/calibre/gui2/actions/copy_to_library.py:127 -#: /home/kovid/work/calibre/src/calibre/gui2/actions/edit_metadata.py:80 -#: /home/kovid/work/calibre/src/calibre/gui2/actions/edit_metadata.py:127 -#: /home/kovid/work/calibre/src/calibre/gui2/actions/edit_metadata.py:188 -#: /home/kovid/work/calibre/src/calibre/gui2/actions/edit_metadata.py:225 +#: /home/kovid/work/calibre/src/calibre/gui2/actions/edit_metadata.py:72 +#: /home/kovid/work/calibre/src/calibre/gui2/actions/edit_metadata.py:156 +#: /home/kovid/work/calibre/src/calibre/gui2/actions/edit_metadata.py:220 +#: /home/kovid/work/calibre/src/calibre/gui2/actions/edit_metadata.py:257 #: /home/kovid/work/calibre/src/calibre/gui2/actions/save_to_disk.py:92 msgid "No books selected" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/actions/annotate.py:65 +#: /home/kovid/work/calibre/src/calibre/gui2/actions/annotate.py:70 msgid "No books selected to fetch annotations from" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/actions/annotate.py:90 +#: /home/kovid/work/calibre/src/calibre/gui2/actions/annotate.py:95 msgid "Merging user annotations into database" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/actions/annotate.py:118 +#: /home/kovid/work/calibre/src/calibre/gui2/actions/annotate.py:123 msgid "%s
    Last Page Read: %d (%d%%)" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/actions/annotate.py:124 +#: /home/kovid/work/calibre/src/calibre/gui2/actions/annotate.py:129 msgid "%s
    Last Page Read: Location %d (%d%%)" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/actions/annotate.py:143 +#: /home/kovid/work/calibre/src/calibre/gui2/actions/annotate.py:148 msgid "Location %d • %s
    %s
    " msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/actions/annotate.py:152 +#: /home/kovid/work/calibre/src/calibre/gui2/actions/annotate.py:157 msgid "Page %d • %s
    " msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/actions/annotate.py:157 +#: /home/kovid/work/calibre/src/calibre/gui2/actions/annotate.py:162 msgid "Location %d • %s
    " msgstr "" @@ -3854,30 +3994,30 @@ msgstr "" msgid "Create a catalog of the books in your calibre library" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/actions/catalog.py:31 +#: /home/kovid/work/calibre/src/calibre/gui2/actions/catalog.py:34 msgid "No books selected for catalog generation" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/actions/catalog.py:54 +#: /home/kovid/work/calibre/src/calibre/gui2/actions/catalog.py:57 msgid "Generating %s catalog..." msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/actions/catalog.py:78 +#: /home/kovid/work/calibre/src/calibre/gui2/actions/catalog.py:81 msgid "Catalog generated." msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/actions/catalog.py:81 +#: /home/kovid/work/calibre/src/calibre/gui2/actions/catalog.py:84 msgid "Export Catalog Directory" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/actions/catalog.py:82 +#: /home/kovid/work/calibre/src/calibre/gui2/actions/catalog.py:85 msgid "Select destination for %s.%s" msgstr "" #: /home/kovid/work/calibre/src/calibre/gui2/actions/choose_library.py:81 -#: /home/kovid/work/calibre/src/calibre/gui2/preferences/toolbar.py:54 -#: /home/kovid/work/calibre/src/calibre/library/server/browse.py:167 -#: /home/kovid/work/calibre/src/calibre/library/server/opds.py:126 +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/toolbar.py:57 +#: /home/kovid/work/calibre/src/calibre/library/server/browse.py:170 +#: /home/kovid/work/calibre/src/calibre/library/server/opds.py:125 msgid "%d books" msgstr "" @@ -3885,179 +4025,183 @@ msgstr "" msgid "Choose calibre library to work with" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/actions/choose_library.py:91 +#: /home/kovid/work/calibre/src/calibre/gui2/actions/choose_library.py:94 msgid "Switch/create library..." msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/actions/choose_library.py:102 -#: /home/kovid/work/calibre/src/calibre/gui2/layout.py:77 +#: /home/kovid/work/calibre/src/calibre/gui2/actions/choose_library.py:105 +#: /home/kovid/work/calibre/src/calibre/gui2/layout.py:87 msgid "Quick switch" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/actions/choose_library.py:104 -#: /home/kovid/work/calibre/src/calibre/gui2/layout.py:78 +#: /home/kovid/work/calibre/src/calibre/gui2/actions/choose_library.py:107 +#: /home/kovid/work/calibre/src/calibre/gui2/layout.py:88 msgid "Rename library" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/actions/choose_library.py:106 -#: /home/kovid/work/calibre/src/calibre/gui2/layout.py:79 +#: /home/kovid/work/calibre/src/calibre/gui2/actions/choose_library.py:109 +#: /home/kovid/work/calibre/src/calibre/gui2/layout.py:89 msgid "Delete library" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/actions/choose_library.py:109 +#: /home/kovid/work/calibre/src/calibre/gui2/actions/choose_library.py:112 msgid "Pick a random book" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/actions/choose_library.py:128 +#: /home/kovid/work/calibre/src/calibre/gui2/actions/choose_library.py:132 msgid "Library Maintenance" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/actions/choose_library.py:129 +#: /home/kovid/work/calibre/src/calibre/gui2/actions/choose_library.py:133 msgid "Library metadata backup status" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/actions/choose_library.py:133 +#: /home/kovid/work/calibre/src/calibre/gui2/actions/choose_library.py:137 msgid "Start backing up metadata of all books" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/actions/choose_library.py:137 +#: /home/kovid/work/calibre/src/calibre/gui2/actions/choose_library.py:141 msgid "Check library" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/actions/choose_library.py:141 +#: /home/kovid/work/calibre/src/calibre/gui2/actions/choose_library.py:145 msgid "Restore database" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/actions/choose_library.py:216 +#: /home/kovid/work/calibre/src/calibre/gui2/actions/choose_library.py:220 msgid "Rename" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/actions/choose_library.py:217 +#: /home/kovid/work/calibre/src/calibre/gui2/actions/choose_library.py:221 msgid "Choose a new name for the library %s. " msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/actions/choose_library.py:218 +#: /home/kovid/work/calibre/src/calibre/gui2/actions/choose_library.py:222 msgid "Note that the actual library folder will be renamed." msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/actions/choose_library.py:225 -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/user_profiles.py:191 +#: /home/kovid/work/calibre/src/calibre/gui2/actions/choose_library.py:229 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/user_profiles.py:199 +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/plugins.py:289 msgid "Already exists" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/actions/choose_library.py:226 +#: /home/kovid/work/calibre/src/calibre/gui2/actions/choose_library.py:230 msgid "The folder %s already exists. Delete it first." msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/actions/choose_library.py:232 +#: /home/kovid/work/calibre/src/calibre/gui2/actions/choose_library.py:236 msgid "Rename failed" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/actions/choose_library.py:233 +#: /home/kovid/work/calibre/src/calibre/gui2/actions/choose_library.py:237 msgid "" "Failed to rename the library at %s. The most common cause for this is if one " "of the files in the library is open in another program." msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/actions/choose_library.py:244 +#: /home/kovid/work/calibre/src/calibre/gui2/actions/choose_library.py:248 +#: /home/kovid/work/calibre/src/calibre/gui2/actions/delete.py:30 #: /home/kovid/work/calibre/src/calibre/gui2/dialogs/confirm_delete_ui.py:53 #: /home/kovid/work/calibre/src/calibre/gui2/dialogs/restore_library.py:78 -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/user_profiles.py:360 -#: /home/kovid/work/calibre/src/calibre/gui2/jobs.py:424 -#: /home/kovid/work/calibre/src/calibre/gui2/jobs.py:430 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/user_profiles.py:368 +#: /home/kovid/work/calibre/src/calibre/gui2/jobs.py:457 +#: /home/kovid/work/calibre/src/calibre/gui2/jobs.py:463 #: /home/kovid/work/calibre/src/calibre/gui2/preferences/columns.py:102 -#: /home/kovid/work/calibre/src/calibre/gui2/preferences/plugins.py:273 +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/plugins.py:279 +#: /home/kovid/work/calibre/src/calibre/gui2/wizard/send_email.py:223 msgid "Are you sure?" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/actions/choose_library.py:245 -msgid "All files from %s will be permanently deleted. Are you sure?" +#: /home/kovid/work/calibre/src/calibre/gui2/actions/choose_library.py:249 +msgid "" +"All files (not just ebooks) from " +"

    %s

    will be permanently deleted. Are you sure?" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/actions/choose_library.py:265 +#: /home/kovid/work/calibre/src/calibre/gui2/actions/choose_library.py:270 msgid "none" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/actions/choose_library.py:266 +#: /home/kovid/work/calibre/src/calibre/gui2/actions/choose_library.py:271 msgid "Backup status" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/actions/choose_library.py:267 +#: /home/kovid/work/calibre/src/calibre/gui2/actions/choose_library.py:272 msgid "Book metadata files remaining to be written: %s" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/actions/choose_library.py:273 +#: /home/kovid/work/calibre/src/calibre/gui2/actions/choose_library.py:278 msgid "Backup metadata" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/actions/choose_library.py:274 +#: /home/kovid/work/calibre/src/calibre/gui2/actions/choose_library.py:279 msgid "" "Metadata will be backed up while calibre is running, at the rate of " "approximately 1 book every three seconds." msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/actions/choose_library.py:306 +#: /home/kovid/work/calibre/src/calibre/gui2/actions/choose_library.py:311 #: /home/kovid/work/calibre/src/calibre/gui2/dialogs/restore_library.py:106 #: /home/kovid/work/calibre/src/calibre/gui2/dialogs/restore_library.py:111 -#: /home/kovid/work/calibre/src/calibre/gui2/preferences/plugins.py:284 -#: /home/kovid/work/calibre/src/calibre/gui2/preferences/plugins.py:338 +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/plugins.py:295 +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/plugins.py:349 msgid "Success" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/actions/choose_library.py:307 +#: /home/kovid/work/calibre/src/calibre/gui2/actions/choose_library.py:312 msgid "" "Found no errors in your calibre library database. Do you want calibre to " "check if the files in your library match the information in the database?" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/actions/choose_library.py:312 +#: /home/kovid/work/calibre/src/calibre/gui2/actions/choose_library.py:317 #: /home/kovid/work/calibre/src/calibre/gui2/actions/copy_to_library.py:150 -#: /home/kovid/work/calibre/src/calibre/gui2/device.py:672 -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk.py:911 +#: /home/kovid/work/calibre/src/calibre/gui2/device.py:692 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk.py:974 #: /home/kovid/work/calibre/src/calibre/gui2/dialogs/restore_library.py:101 -#: /home/kovid/work/calibre/src/calibre/gui2/metadata/bulk_download.py:186 -#: /home/kovid/work/calibre/src/calibre/gui2/preferences/tweaks.py:276 -#: /home/kovid/work/calibre/src/calibre/gui2/preferences/tweaks.py:316 +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/tweaks.py:277 +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/tweaks.py:317 msgid "Failed" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/actions/choose_library.py:313 +#: /home/kovid/work/calibre/src/calibre/gui2/actions/choose_library.py:318 msgid "Database integrity check failed, click Show details for details." msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/actions/choose_library.py:318 +#: /home/kovid/work/calibre/src/calibre/gui2/actions/choose_library.py:323 msgid "No problems found" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/actions/choose_library.py:319 +#: /home/kovid/work/calibre/src/calibre/gui2/actions/choose_library.py:324 msgid "The files in your library match the information in the database." msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/actions/choose_library.py:328 +#: /home/kovid/work/calibre/src/calibre/gui2/actions/choose_library.py:333 msgid "No library found" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/actions/choose_library.py:329 +#: /home/kovid/work/calibre/src/calibre/gui2/actions/choose_library.py:334 msgid "" "No existing calibre library was found at %s. It will be removed from the " "list of known libraries." msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/actions/choose_library.py:394 -#: /home/kovid/work/calibre/src/calibre/gui2/actions/choose_library.py:399 +#: /home/kovid/work/calibre/src/calibre/gui2/actions/choose_library.py:400 +#: /home/kovid/work/calibre/src/calibre/gui2/actions/choose_library.py:405 #: /home/kovid/work/calibre/src/calibre/gui2/actions/copy_to_library.py:167 #: /home/kovid/work/calibre/src/calibre/gui2/actions/save_to_disk.py:101 -#: /home/kovid/work/calibre/src/calibre/gui2/library/views.py:782 +#: /home/kovid/work/calibre/src/calibre/gui2/library/views.py:854 msgid "Not allowed" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/actions/choose_library.py:395 +#: /home/kovid/work/calibre/src/calibre/gui2/actions/choose_library.py:401 msgid "" "You cannot change libraries while using the environment variable " "CALIBRE_OVERRIDE_DATABASE_PATH." msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/actions/choose_library.py:400 +#: /home/kovid/work/calibre/src/calibre/gui2/actions/choose_library.py:406 msgid "You cannot change libraries while jobs are running." msgstr "" @@ -4078,7 +4222,7 @@ msgid "Bulk convert" msgstr "" #: /home/kovid/work/calibre/src/calibre/gui2/actions/convert.py:86 -#: /home/kovid/work/calibre/src/calibre/gui2/ui.py:505 +#: /home/kovid/work/calibre/src/calibre/gui2/ui.py:560 msgid "Cannot convert" msgstr "" @@ -4086,7 +4230,7 @@ msgstr "" msgid "Starting conversion of %d book(s)" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/actions/convert.py:170 +#: /home/kovid/work/calibre/src/calibre/gui2/actions/convert.py:171 msgid "Empty output file, probably the conversion process crashed" msgstr "" @@ -4133,113 +4277,120 @@ msgid "" "CALIBRE_OVERRIDE_DATABASE_PATH." msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/actions/delete.py:32 +#: /home/kovid/work/calibre/src/calibre/gui2/actions/delete.py:31 +msgid "" +"You are trying to delete %d books. Sending so many files to the Recycle Bin " +"can be slow. Should calibre skip the Recycle Bin? If you click Yes " +"the files will be permanently deleted." +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/actions/delete.py:42 msgid "Deleting..." msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/actions/delete.py:54 +#: /home/kovid/work/calibre/src/calibre/gui2/actions/delete.py:65 msgid "Deleted" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/actions/delete.py:66 +#: /home/kovid/work/calibre/src/calibre/gui2/actions/delete.py:77 msgid "Failed to delete" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/actions/delete.py:67 +#: /home/kovid/work/calibre/src/calibre/gui2/actions/delete.py:78 msgid "" "Failed to delete some books, click the Show Details button for details." msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/actions/delete.py:73 +#: /home/kovid/work/calibre/src/calibre/gui2/actions/delete.py:84 msgid "Del" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/actions/delete.py:73 +#: /home/kovid/work/calibre/src/calibre/gui2/actions/delete.py:84 msgid "Remove books" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/actions/delete.py:79 +#: /home/kovid/work/calibre/src/calibre/gui2/actions/delete.py:90 msgid "Remove selected books" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/actions/delete.py:81 +#: /home/kovid/work/calibre/src/calibre/gui2/actions/delete.py:92 msgid "Remove files of a specific format from selected books.." msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/actions/delete.py:84 +#: /home/kovid/work/calibre/src/calibre/gui2/actions/delete.py:95 msgid "Remove all formats from selected books, except..." msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/actions/delete.py:87 +#: /home/kovid/work/calibre/src/calibre/gui2/actions/delete.py:98 msgid "Remove covers from selected books" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/actions/delete.py:90 +#: /home/kovid/work/calibre/src/calibre/gui2/actions/delete.py:101 msgid "Remove matching books from device" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/actions/delete.py:113 +#: /home/kovid/work/calibre/src/calibre/gui2/actions/delete.py:124 msgid "Cannot delete" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/actions/delete.py:126 +#: /home/kovid/work/calibre/src/calibre/gui2/actions/delete.py:137 msgid "Choose formats to be deleted" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/actions/delete.py:144 +#: /home/kovid/work/calibre/src/calibre/gui2/actions/delete.py:155 msgid "Choose formats not to be deleted" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/actions/delete.py:164 +#: /home/kovid/work/calibre/src/calibre/gui2/actions/delete.py:175 msgid "Cannot delete books" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/actions/delete.py:165 +#: /home/kovid/work/calibre/src/calibre/gui2/actions/delete.py:176 msgid "No device is connected" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/actions/delete.py:175 +#: /home/kovid/work/calibre/src/calibre/gui2/actions/delete.py:186 msgid "Main memory" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/actions/delete.py:176 -#: /home/kovid/work/calibre/src/calibre/gui2/device.py:469 -#: /home/kovid/work/calibre/src/calibre/gui2/device.py:478 +#: /home/kovid/work/calibre/src/calibre/gui2/actions/delete.py:187 +#: /home/kovid/work/calibre/src/calibre/gui2/device.py:468 +#: /home/kovid/work/calibre/src/calibre/gui2/device.py:477 msgid "Storage Card A" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/actions/delete.py:177 -#: /home/kovid/work/calibre/src/calibre/gui2/device.py:471 -#: /home/kovid/work/calibre/src/calibre/gui2/device.py:480 +#: /home/kovid/work/calibre/src/calibre/gui2/actions/delete.py:188 +#: /home/kovid/work/calibre/src/calibre/gui2/device.py:470 +#: /home/kovid/work/calibre/src/calibre/gui2/device.py:479 msgid "Storage Card B" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/actions/delete.py:182 +#: /home/kovid/work/calibre/src/calibre/gui2/actions/delete.py:193 msgid "No books to delete" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/actions/delete.py:183 +#: /home/kovid/work/calibre/src/calibre/gui2/actions/delete.py:194 msgid "None of the selected books are on the device" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/actions/delete.py:200 -#: /home/kovid/work/calibre/src/calibre/gui2/actions/delete.py:290 +#: /home/kovid/work/calibre/src/calibre/gui2/actions/delete.py:211 +#: /home/kovid/work/calibre/src/calibre/gui2/actions/delete.py:302 msgid "Deleting books from device." msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/actions/delete.py:245 +#: /home/kovid/work/calibre/src/calibre/gui2/actions/delete.py:257 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:257 +#: /home/kovid/work/calibre/src/calibre/gui2/actions/delete.py:269 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:282 +#: /home/kovid/work/calibre/src/calibre/gui2/actions/delete.py:294 msgid "" "The selected books will be permanently deleted from your device. Are " "you sure?" @@ -4267,7 +4418,7 @@ msgid "Stop Content Server" msgstr "" #: /home/kovid/work/calibre/src/calibre/gui2/actions/device.py:77 -#: /home/kovid/work/calibre/src/calibre/gui2/actions/device.py:95 +#: /home/kovid/work/calibre/src/calibre/gui2/actions/device.py:96 msgid "Email to" msgstr "" @@ -4275,19 +4426,19 @@ msgstr "" msgid "Email to and delete from library" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/actions/device.py:89 +#: /home/kovid/work/calibre/src/calibre/gui2/actions/device.py:90 msgid "(delete from library)" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/actions/device.py:104 +#: /home/kovid/work/calibre/src/calibre/gui2/actions/device.py:105 msgid "Setup email based sharing of books" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/actions/device.py:122 +#: /home/kovid/work/calibre/src/calibre/gui2/actions/device.py:123 msgid "D" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/actions/device.py:122 +#: /home/kovid/work/calibre/src/calibre/gui2/actions/device.py:123 msgid "Send to device" msgstr "" @@ -4295,13 +4446,13 @@ msgstr "" msgid "Connect/share" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/actions/device.py:174 -#: /home/kovid/work/calibre/src/calibre/gui2/preferences/server.py:84 +#: /home/kovid/work/calibre/src/calibre/gui2/actions/device.py:178 +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/server.py:79 msgid "Stopping" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/actions/device.py:175 -#: /home/kovid/work/calibre/src/calibre/gui2/preferences/server.py:85 +#: /home/kovid/work/calibre/src/calibre/gui2/actions/device.py:179 +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/server.py:80 msgid "Stopping server, this could take upto a minute, please wait..." msgstr "" @@ -4341,72 +4492,95 @@ msgstr "" msgid "Download metadata and covers" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/actions/edit_metadata.py:40 -msgid "Download only metadata" -msgstr "" - #: /home/kovid/work/calibre/src/calibre/gui2/actions/edit_metadata.py:42 -msgid "Download only covers" -msgstr "" - -#: /home/kovid/work/calibre/src/calibre/gui2/actions/edit_metadata.py:45 -msgid "Download only social metadata" -msgstr "" - -#: /home/kovid/work/calibre/src/calibre/gui2/actions/edit_metadata.py:51 msgid "Merge into first selected book - delete others" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/actions/edit_metadata.py:54 +#: /home/kovid/work/calibre/src/calibre/gui2/actions/edit_metadata.py:45 msgid "Merge into first selected book - keep others" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/actions/edit_metadata.py:58 +#: /home/kovid/work/calibre/src/calibre/gui2/actions/edit_metadata.py:49 msgid "Merge only formats into first selected book - delete others" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/actions/edit_metadata.py:79 +#: /home/kovid/work/calibre/src/calibre/gui2/actions/edit_metadata.py:71 msgid "Cannot download metadata" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/actions/edit_metadata.py:99 -msgid "social metadata" +#: /home/kovid/work/calibre/src/calibre/gui2/actions/edit_metadata.py:81 +msgid "Failed to download metadata" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/actions/edit_metadata.py:101 -msgid "covers" +#: /home/kovid/work/calibre/src/calibre/gui2/actions/edit_metadata.py:87 +#: /home/kovid/work/calibre/src/calibre/gui2/dnd.py:84 +#: /home/kovid/work/calibre/src/calibre/gui2/metadata/single_download.py:462 +#: /home/kovid/work/calibre/src/calibre/gui2/metadata/single_download.py:716 +msgid "Download failed" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/actions/edit_metadata.py:101 -#: /home/kovid/work/calibre/src/calibre/gui2/metadata/bulk_download.py:227 -msgid "metadata" +#: /home/kovid/work/calibre/src/calibre/gui2/actions/edit_metadata.py:88 +msgid "Failed to download metadata or covers for any of the %d book(s)." msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/actions/edit_metadata.py:102 -msgid "Downloading {0} for {1} book(s)" +#: /home/kovid/work/calibre/src/calibre/gui2/actions/edit_metadata.py:91 +msgid "Metadata download completed" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/actions/edit_metadata.py:126 -#: /home/kovid/work/calibre/src/calibre/gui2/actions/edit_metadata.py:187 +#: /home/kovid/work/calibre/src/calibre/gui2/actions/edit_metadata.py:93 +msgid "" +"Finished downloading metadata for %d book(s). Proceed with updating " +"the metadata in your library?" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/actions/edit_metadata.py:100 +msgid "" +"Could not download metadata and/or covers for %d of the books. Click \"Show " +"details\" to see which books." +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/actions/edit_metadata.py:107 +msgid "Download complete" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/actions/edit_metadata.py:107 +#: /home/kovid/work/calibre/src/calibre/gui2/metadata/single_download.py:777 +msgid "Download log" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/actions/edit_metadata.py:136 +msgid "Some books changed" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/actions/edit_metadata.py:137 +msgid "" +"The metadata for some books in your library has changed since you started " +"the download. If you proceed, some of those changes may be overwritten. " +"Click \"Show details\" to see the list of changed books. Do you want to " +"proceed?" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/actions/edit_metadata.py:155 +#: /home/kovid/work/calibre/src/calibre/gui2/actions/edit_metadata.py:219 msgid "Cannot edit metadata" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/actions/edit_metadata.py:224 -#: /home/kovid/work/calibre/src/calibre/gui2/actions/edit_metadata.py:227 +#: /home/kovid/work/calibre/src/calibre/gui2/actions/edit_metadata.py:256 +#: /home/kovid/work/calibre/src/calibre/gui2/actions/edit_metadata.py:259 msgid "Cannot merge books" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/actions/edit_metadata.py:228 +#: /home/kovid/work/calibre/src/calibre/gui2/actions/edit_metadata.py:260 msgid "At least two books must be selected for merging" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/actions/edit_metadata.py:231 +#: /home/kovid/work/calibre/src/calibre/gui2/actions/edit_metadata.py:263 msgid "" "You are about to merge more than 5 books. Are you sure you want to " "proceed?" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/actions/edit_metadata.py:239 +#: /home/kovid/work/calibre/src/calibre/gui2/actions/edit_metadata.py:271 msgid "" "Book formats and metadata from the selected books will be added to the " "first selected book (%s). ISBN will not be merged.

    The " @@ -4414,7 +4588,7 @@ msgid "" "changed.

    Please confirm you want to proceed." msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/actions/edit_metadata.py:251 +#: /home/kovid/work/calibre/src/calibre/gui2/actions/edit_metadata.py:283 msgid "" "Book formats from the selected books will be merged into the first " "selected book (%s). Metadata in the first selected book will not be " @@ -4426,7 +4600,7 @@ msgid "" "calibre library.

    Are you sure you want to proceed?" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/actions/edit_metadata.py:267 +#: /home/kovid/work/calibre/src/calibre/gui2/actions/edit_metadata.py:299 msgid "" "Book formats and metadata from the selected books will be merged into the " "first selected book (%s). ISBN will not be " @@ -4437,19 +4611,33 @@ msgid "" "Are you sure you want to proceed?" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/actions/fetch_news.py:17 +#: /home/kovid/work/calibre/src/calibre/gui2/actions/edit_metadata.py:455 +msgid "Applying changed metadata" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/actions/edit_metadata.py:520 +msgid "Some failures" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/actions/edit_metadata.py:521 +msgid "" +"Failed to apply updated metadata for some books in your library. Click " +"\"Show Details\" to see details." +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/actions/fetch_news.py:19 msgid "F" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/actions/fetch_news.py:17 +#: /home/kovid/work/calibre/src/calibre/gui2/actions/fetch_news.py:19 msgid "Fetch news" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/actions/fetch_news.py:52 +#: /home/kovid/work/calibre/src/calibre/gui2/actions/fetch_news.py:54 msgid "Fetching news from " msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/actions/fetch_news.py:66 +#: /home/kovid/work/calibre/src/calibre/gui2/actions/fetch_news.py:83 msgid " fetched." msgstr "" @@ -4475,7 +4663,7 @@ msgid "Move to next highlighted match" msgstr "" #: /home/kovid/work/calibre/src/calibre/gui2/actions/next_match.py:13 -#: /home/kovid/work/calibre/src/calibre/gui2/library/delegates.py:355 +#: /home/kovid/work/calibre/src/calibre/gui2/library/delegates.py:361 msgid "N" msgstr "" @@ -4514,19 +4702,23 @@ msgid "Ctrl+P" msgstr "" #: /home/kovid/work/calibre/src/calibre/gui2/actions/preferences.py:24 -#: /home/kovid/work/calibre/src/calibre/gui2/preferences/main.py:201 +msgid "Change calibre behavior" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/actions/preferences.py:25 +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/main.py:208 msgid "Run welcome wizard" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/actions/preferences.py:28 +#: /home/kovid/work/calibre/src/calibre/gui2/actions/preferences.py:29 msgid "Restart in debug mode" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/actions/preferences.py:40 +#: /home/kovid/work/calibre/src/calibre/gui2/actions/preferences.py:44 msgid "Cannot configure while there are running jobs." msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/actions/preferences.py:45 +#: /home/kovid/work/calibre/src/calibre/gui2/actions/preferences.py:49 msgid "Cannot configure before calibre is restarted." msgstr "" @@ -4599,7 +4791,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:696 +#: /home/kovid/work/calibre/src/calibre/library/server/browse.py:766 msgid "Show book details" msgstr "" @@ -4651,8 +4843,71 @@ msgstr "" msgid "Books with the same tags" msgstr "" +#: /home/kovid/work/calibre/src/calibre/gui2/actions/store.py:20 +msgid "Get books" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/actions/store.py:29 +msgid "Search for ebooks" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/actions/store.py:30 +msgid "Search for this author" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/actions/store.py:31 +msgid "Search for this title" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/actions/store.py:32 +msgid "Search for this book" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/actions/store.py:34 +#: /home/kovid/work/calibre/src/calibre/gui2/store/search/search_ui.py:122 +#: /home/kovid/work/calibre/src/calibre/gui2/store/search_ui.py:110 +msgid "Stores" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/actions/store.py:76 +#: /home/kovid/work/calibre/src/calibre/gui2/actions/store.py:95 +#: /home/kovid/work/calibre/src/calibre/gui2/actions/store.py:104 +msgid "Cannot search" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/actions/store.py:116 +msgid "" +"Calibre helps you find the ebooks you want by searching the websites of " +"various commercial and public domain book sources for you." +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/actions/store.py:120 +msgid "" +"Using the integrated search you can easily find which store has the book you " +"are looking for, at the best price. You also get DRM status and other useful " +"information." +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/actions/store.py:124 +msgid "" +"All transactions (paid or otherwise) are handled between you and the book " +"seller. Calibre is not part of this process and any issues related to a " +"purchase should be directed to the website you are buying from. Be sure to " +"double check that any books you get will work with your e-book reader, " +"especially if the book you are buying has DRM." +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/actions/store.py:134 +msgid "Show this message again" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/actions/store.py:135 +msgid "About Get Books" +msgstr "" + #: /home/kovid/work/calibre/src/calibre/gui2/actions/tweak_epub.py:15 -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/tweak_epub_ui.py:54 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/tweak_epub_ui.py:60 msgid "Tweak ePub" msgstr "" @@ -4673,49 +4928,57 @@ msgstr "" msgid "No ePub available. First convert the book to ePub." msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/actions/view.py:24 +#: /home/kovid/work/calibre/src/calibre/gui2/actions/view.py:36 msgid "V" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/actions/view.py:24 -#: /home/kovid/work/calibre/src/calibre/gui2/actions/view.py:31 +#: /home/kovid/work/calibre/src/calibre/gui2/actions/view.py:36 msgid "View" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/actions/view.py:32 +#: /home/kovid/work/calibre/src/calibre/gui2/actions/view.py:43 msgid "View specific format" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/actions/view.py:85 -#: /home/kovid/work/calibre/src/calibre/gui2/actions/view.py:170 +#: /home/kovid/work/calibre/src/calibre/gui2/actions/view.py:51 +msgid "Read a random book" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/actions/view.py:55 +msgid "Clear recently viewed list" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/actions/view.py:139 +#: /home/kovid/work/calibre/src/calibre/gui2/actions/view.py:219 +#: /home/kovid/work/calibre/src/calibre/gui2/actions/view.py:226 msgid "Cannot view" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/actions/view.py:98 -#: /home/kovid/work/calibre/src/calibre/gui2/actions/view.py:112 +#: /home/kovid/work/calibre/src/calibre/gui2/actions/view.py:152 +#: /home/kovid/work/calibre/src/calibre/gui2/actions/view.py:166 msgid "Format unavailable" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/actions/view.py:99 +#: /home/kovid/work/calibre/src/calibre/gui2/actions/view.py:153 msgid "Selected books have no formats" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/actions/view.py:101 +#: /home/kovid/work/calibre/src/calibre/gui2/actions/view.py:155 #: /home/kovid/work/calibre/src/calibre/gui2/convert/regex_builder.py:127 msgid "Choose the format to view" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/actions/view.py:113 +#: /home/kovid/work/calibre/src/calibre/gui2/actions/view.py:167 msgid "" "Not all the selected books were available in the %s format. You should " "convert them first." msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/actions/view.py:120 +#: /home/kovid/work/calibre/src/calibre/gui2/actions/view.py:174 msgid "Multiple Books Selected" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/actions/view.py:121 +#: /home/kovid/work/calibre/src/calibre/gui2/actions/view.py:175 msgid "" "You are attempting to open %d books. Opening too many books at once can be " "slow and have a negative effect on the responsiveness of your computer. Once " @@ -4723,11 +4986,15 @@ msgid "" "continue?" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/actions/view.py:130 +#: /home/kovid/work/calibre/src/calibre/gui2/actions/view.py:184 msgid "Cannot open folder" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/actions/view.py:171 +#: /home/kovid/work/calibre/src/calibre/gui2/actions/view.py:220 +msgid "This book no longer exists in your library" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/actions/view.py:227 msgid "%s has no available formats." msgstr "" @@ -4752,7 +5019,7 @@ msgid "The specified directory could not be processed." msgstr "" #: /home/kovid/work/calibre/src/calibre/gui2/add.py:274 -#: /home/kovid/work/calibre/src/calibre/gui2/device.py:821 +#: /home/kovid/work/calibre/src/calibre/gui2/device.py:845 msgid "No books" msgstr "" @@ -4796,32 +5063,32 @@ msgstr "" msgid "Saved" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/add_wizard/__init__.py:57 +#: /home/kovid/work/calibre/src/calibre/gui2/add_wizard/__init__.py:56 msgid "Searching for sub-folders" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/add_wizard/__init__.py:62 +#: /home/kovid/work/calibre/src/calibre/gui2/add_wizard/__init__.py:61 msgid "Searching for books" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/add_wizard/__init__.py:74 +#: /home/kovid/work/calibre/src/calibre/gui2/add_wizard/__init__.py:73 msgid "Looking for duplicates based on file hash" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/add_wizard/__init__.py:109 +#: /home/kovid/work/calibre/src/calibre/gui2/add_wizard/__init__.py:108 #: /home/kovid/work/calibre/src/calibre/gui2/add_wizard/welcome_ui.py:70 msgid "Choose root folder" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/add_wizard/__init__.py:137 +#: /home/kovid/work/calibre/src/calibre/gui2/add_wizard/__init__.py:135 msgid "Invalid root folder" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/add_wizard/__init__.py:138 +#: /home/kovid/work/calibre/src/calibre/gui2/add_wizard/__init__.py:136 msgid "is not a valid root folder" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/add_wizard/__init__.py:148 +#: /home/kovid/work/calibre/src/calibre/gui2/add_wizard/__init__.py:146 msgid "Add books to calibre" msgstr "" @@ -4877,21 +5144,15 @@ msgstr "" #: /home/kovid/work/calibre/src/calibre/gui2/convert/debug_ui.py:58 #: /home/kovid/work/calibre/src/calibre/gui2/convert/look_and_feel_ui.py:143 #: /home/kovid/work/calibre/src/calibre/gui2/convert/metadata_ui.py:162 -#: /home/kovid/work/calibre/src/calibre/gui2/convert/xexp_edit_ui.py:56 +#: /home/kovid/work/calibre/src/calibre/gui2/convert/xexp_edit_ui.py:57 #: /home/kovid/work/calibre/src/calibre/gui2/device_drivers/configwidget_ui.py:79 #: /home/kovid/work/calibre/src/calibre/gui2/device_drivers/configwidget_ui.py:80 #: /home/kovid/work/calibre/src/calibre/gui2/dialogs/choose_library_ui.py:86 -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:534 -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:539 -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single_ui.py:412 -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single_ui.py:414 -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single_ui.py:417 -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single_ui.py:437 -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single_ui.py:458 -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single_ui.py:460 -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single_ui.py:462 -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/saved_search_editor_ui.py:92 -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/saved_search_editor_ui.py:95 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:560 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:565 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/saved_search_editor_ui.py:98 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/saved_search_editor_ui.py:101 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/saved_search_editor_ui.py:103 #: /home/kovid/work/calibre/src/calibre/gui2/dialogs/tag_categories_ui.py:170 #: /home/kovid/work/calibre/src/calibre/gui2/dialogs/tag_categories_ui.py:173 #: /home/kovid/work/calibre/src/calibre/gui2/dialogs/tag_categories_ui.py:175 @@ -4903,16 +5164,16 @@ 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:80 #: /home/kovid/work/calibre/src/calibre/gui2/dialogs/tag_list_editor_ui.py:82 -#: /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:274 -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/user_profiles_ui.py:275 -#: /home/kovid/work/calibre/src/calibre/gui2/preferences/behavior_ui.py:148 -#: /home/kovid/work/calibre/src/calibre/gui2/preferences/behavior_ui.py:149 -#: /home/kovid/work/calibre/src/calibre/gui2/preferences/columns_ui.py:83 -#: /home/kovid/work/calibre/src/calibre/gui2/preferences/columns_ui.py:85 -#: /home/kovid/work/calibre/src/calibre/gui2/preferences/columns_ui.py:87 +#: /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/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 -#: /home/kovid/work/calibre/src/calibre/gui2/preferences/columns_ui.py:90 +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/columns_ui.py:91 +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/columns_ui.py:93 +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/columns_ui.py:95 +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/columns_ui.py:97 #: /home/kovid/work/calibre/src/calibre/gui2/preferences/custom_columns_ui.py:83 #: /home/kovid/work/calibre/src/calibre/gui2/preferences/custom_columns_ui.py:85 #: /home/kovid/work/calibre/src/calibre/gui2/preferences/custom_columns_ui.py:87 @@ -4925,6 +5186,8 @@ msgstr "" #: /home/kovid/work/calibre/src/calibre/gui2/preferences/toolbar_ui.py:110 #: /home/kovid/work/calibre/src/calibre/gui2/shortcuts_ui.py:80 #: /home/kovid/work/calibre/src/calibre/gui2/shortcuts_ui.py:85 +#: /home/kovid/work/calibre/src/calibre/gui2/store/mobileread/store_dialog_ui.py:75 +#: /home/kovid/work/calibre/src/calibre/gui2/store/search/search_ui.py:120 #: /home/kovid/work/calibre/src/calibre/gui2/viewer/main_ui.py:191 msgid "..." msgstr "" @@ -4944,60 +5207,52 @@ msgid "" "&Multiple books per folder, assumes every ebook file is a different book" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/book_details.py:26 -#: /home/kovid/work/calibre/src/calibre/gui2/book_details.py:53 -#: /home/kovid/work/calibre/src/calibre/gui2/book_details.py:62 -#: /home/kovid/work/calibre/src/calibre/gui2/book_details.py:434 -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/book_info.py:130 -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/book_info.py:131 -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/book_info.py:132 -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/book_info.py:145 -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/delete_matching_from_device.py:76 -#: /home/kovid/work/calibre/src/calibre/gui2/library/models.py:375 -#: /home/kovid/work/calibre/src/calibre/gui2/library/models.py:1170 -msgid "Path" +#: /home/kovid/work/calibre/src/calibre/gui2/bars.py:190 +msgid "Donate" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/book_details.py:27 -#: /home/kovid/work/calibre/src/calibre/gui2/book_details.py:56 -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/book_info.py:133 -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/book_info.py:134 -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/book_info.py:135 -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/book_info.py:138 -#: /home/kovid/work/calibre/src/calibre/gui2/library/models.py:374 -#: /home/kovid/work/calibre/src/calibre/gui2/preferences/emailp.py:24 -#: /home/kovid/work/calibre/src/calibre/library/field_metadata.py:118 -msgid "Formats" -msgstr "" - -#: /home/kovid/work/calibre/src/calibre/gui2/book_details.py:28 -#: /home/kovid/work/calibre/src/calibre/gui2/library/models.py:981 -#: /home/kovid/work/calibre/src/calibre/gui2/library/models.py:1173 -msgid "Collections" -msgstr "" - -#: /home/kovid/work/calibre/src/calibre/gui2/book_details.py:55 -#: /home/kovid/work/calibre/src/calibre/gui2/book_details.py:64 +#: /home/kovid/work/calibre/src/calibre/gui2/book_details.py:108 msgid "Click to open" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/book_details.py:56 -#: /home/kovid/work/calibre/src/calibre/gui2/library/models.py:367 -#: /home/kovid/work/calibre/src/calibre/gui2/library/models.py:373 -#: /home/kovid/work/calibre/src/calibre/gui2/library/models.py:379 -#: /home/kovid/work/calibre/src/calibre/gui2/library/models.py:1179 -#: /home/kovid/work/calibre/src/calibre/gui2/library/models.py:1183 -#: /home/kovid/work/calibre/src/calibre/gui2/shortcuts.py:48 -#: /home/kovid/work/calibre/src/calibre/gui2/shortcuts_ui.py:78 -#: /home/kovid/work/calibre/src/calibre/gui2/shortcuts_ui.py:83 -#: /home/kovid/work/calibre/src/calibre/gui2/widgets.py:316 -msgid "None" +#: /home/kovid/work/calibre/src/calibre/gui2/book_details.py:123 +msgid "Ids" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/book_details.py:433 +#: /home/kovid/work/calibre/src/calibre/gui2/book_details.py:133 +msgid "Book %s of %s" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/book_details.py:144 +#: /home/kovid/work/calibre/src/calibre/gui2/library/models.py:981 +msgid "Collections" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/book_details.py:246 +#: /home/kovid/work/calibre/src/calibre/gui2/widgets.py:243 +msgid "Paste Cover" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/book_details.py:247 +#: /home/kovid/work/calibre/src/calibre/gui2/widgets.py:244 +msgid "Copy Cover" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/book_details.py:513 msgid "Double-click to open Book Details window" msgstr "" +#: /home/kovid/work/calibre/src/calibre/gui2/book_details.py:514 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/delete_matching_from_device.py:76 +#: /home/kovid/work/calibre/src/calibre/library/field_metadata.py:261 +msgid "Path" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/book_details.py:515 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/book_info.py:109 +msgid "Cover size: %dx%d" +msgstr "" + #: /home/kovid/work/calibre/src/calibre/gui2/catalog/catalog_bibtex.py:16 msgid "BibTeX Options" msgstr "" @@ -5009,6 +5264,7 @@ msgstr "" #: /home/kovid/work/calibre/src/calibre/gui2/convert/epub_output.py:16 #: /home/kovid/work/calibre/src/calibre/gui2/convert/fb2_input.py:13 #: /home/kovid/work/calibre/src/calibre/gui2/convert/fb2_output.py:15 +#: /home/kovid/work/calibre/src/calibre/gui2/convert/htmlz_output.py:15 #: /home/kovid/work/calibre/src/calibre/gui2/convert/lrf_output.py:20 #: /home/kovid/work/calibre/src/calibre/gui2/convert/mobi_output.py:20 #: /home/kovid/work/calibre/src/calibre/gui2/convert/pdb_output.py:15 @@ -5028,6 +5284,7 @@ msgstr "" #: /home/kovid/work/calibre/src/calibre/gui2/catalog/catalog_epub_mobi.py:19 #: /home/kovid/work/calibre/src/calibre/gui2/convert/epub_output.py:16 #: /home/kovid/work/calibre/src/calibre/gui2/convert/fb2_output.py:15 +#: /home/kovid/work/calibre/src/calibre/gui2/convert/htmlz_output.py:15 #: /home/kovid/work/calibre/src/calibre/gui2/convert/lrf_output.py:20 #: /home/kovid/work/calibre/src/calibre/gui2/convert/mobi_output.py:20 #: /home/kovid/work/calibre/src/calibre/gui2/convert/pdb_output.py:15 @@ -5040,16 +5297,17 @@ msgstr "" msgid "output" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/catalog/catalog_bibtex_ui.py:89 +#: /home/kovid/work/calibre/src/calibre/gui2/catalog/catalog_bibtex_ui.py:77 #: /home/kovid/work/calibre/src/calibre/gui2/catalog/catalog_csv_xml_ui.py:42 #: /home/kovid/work/calibre/src/calibre/gui2/catalog/catalog_epub_mobi_ui.py:295 #: /home/kovid/work/calibre/src/calibre/gui2/catalog/catalog_tab_template_ui.py:32 -#: /home/kovid/work/calibre/src/calibre/gui2/convert/comic_input_ui.py:100 +#: /home/kovid/work/calibre/src/calibre/gui2/convert/comic_input_ui.py:103 #: /home/kovid/work/calibre/src/calibre/gui2/convert/debug_ui.py:54 #: /home/kovid/work/calibre/src/calibre/gui2/convert/epub_output_ui.py:56 #: /home/kovid/work/calibre/src/calibre/gui2/convert/fb2_input_ui.py:33 -#: /home/kovid/work/calibre/src/calibre/gui2/convert/fb2_output_ui.py:38 +#: /home/kovid/work/calibre/src/calibre/gui2/convert/fb2_output_ui.py:44 #: /home/kovid/work/calibre/src/calibre/gui2/convert/heuristics_ui.py:112 +#: /home/kovid/work/calibre/src/calibre/gui2/convert/htmlz_output_ui.py:44 #: /home/kovid/work/calibre/src/calibre/gui2/convert/look_and_feel_ui.py:137 #: /home/kovid/work/calibre/src/calibre/gui2/convert/lrf_output_ui.py:120 #: /home/kovid/work/calibre/src/calibre/gui2/convert/metadata_ui.py:158 @@ -5063,23 +5321,24 @@ msgstr "" #: /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:147 #: /home/kovid/work/calibre/src/calibre/gui2/convert/snb_output_ui.py:42 -#: /home/kovid/work/calibre/src/calibre/gui2/convert/structure_detection_ui.py:56 +#: /home/kovid/work/calibre/src/calibre/gui2/convert/structure_detection_ui.py:59 #: /home/kovid/work/calibre/src/calibre/gui2/convert/toc_ui.py:67 #: /home/kovid/work/calibre/src/calibre/gui2/convert/txt_input_ui.py:91 -#: /home/kovid/work/calibre/src/calibre/gui2/convert/txt_output_ui.py:84 -#: /home/kovid/work/calibre/src/calibre/gui2/convert/xexp_edit_ui.py:53 +#: /home/kovid/work/calibre/src/calibre/gui2/convert/txt_output_ui.py:87 +#: /home/kovid/work/calibre/src/calibre/gui2/convert/xexp_edit_ui.py:54 #: /home/kovid/work/calibre/src/calibre/gui2/convert/xpath_wizard_ui.py:72 #: /home/kovid/work/calibre/src/calibre/gui2/device_drivers/configwidget_ui.py:77 #: /home/kovid/work/calibre/src/calibre/gui2/dialogs/search_item_ui.py:40 -#: /home/kovid/work/calibre/src/calibre/gui2/filename_pattern_ui.py:114 +#: /home/kovid/work/calibre/src/calibre/gui2/filename_pattern_ui.py:128 #: /home/kovid/work/calibre/src/calibre/gui2/preferences/adding_ui.py:64 -#: /home/kovid/work/calibre/src/calibre/gui2/preferences/behavior_ui.py:130 -#: /home/kovid/work/calibre/src/calibre/gui2/preferences/columns_ui.py:81 +#: /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/custom_columns_ui.py:81 #: /home/kovid/work/calibre/src/calibre/gui2/preferences/email_ui.py:65 -#: /home/kovid/work/calibre/src/calibre/gui2/preferences/look_feel_ui.py:135 -#: /home/kovid/work/calibre/src/calibre/gui2/preferences/misc_ui.py:60 +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/look_feel_ui.py:197 +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/metadata_sources_ui.py:109 +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/misc_ui.py:61 #: /home/kovid/work/calibre/src/calibre/gui2/preferences/plugboard_ui.py:113 #: /home/kovid/work/calibre/src/calibre/gui2/preferences/plugins_ui.py:86 #: /home/kovid/work/calibre/src/calibre/gui2/preferences/save_template_ui.py:46 @@ -5090,72 +5349,41 @@ msgstr "" #: /home/kovid/work/calibre/src/calibre/gui2/preferences/template_functions_ui.py:95 #: /home/kovid/work/calibre/src/calibre/gui2/preferences/toolbar_ui.py:98 #: /home/kovid/work/calibre/src/calibre/gui2/preferences/tweaks_ui.py:87 +#: /home/kovid/work/calibre/src/calibre/gui2/store/basic_config_widget_ui.py:37 #: /home/kovid/work/calibre/src/calibre/gui2/wizard/send_email_ui.py:123 msgid "Form" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/catalog/catalog_bibtex_ui.py:90 +#: /home/kovid/work/calibre/src/calibre/gui2/catalog/catalog_bibtex_ui.py:78 msgid "Bib file encoding:" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/catalog/catalog_bibtex_ui.py:91 +#: /home/kovid/work/calibre/src/calibre/gui2/catalog/catalog_bibtex_ui.py:79 #: /home/kovid/work/calibre/src/calibre/gui2/catalog/catalog_csv_xml_ui.py:43 msgid "Fields to include in output:" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/catalog/catalog_bibtex_ui.py:92 -msgid "ascii/LaTeX" -msgstr "" - -#: /home/kovid/work/calibre/src/calibre/gui2/catalog/catalog_bibtex_ui.py:93 +#: /home/kovid/work/calibre/src/calibre/gui2/catalog/catalog_bibtex_ui.py:80 msgid "Encoding configuration (change if you have errors) :" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/catalog/catalog_bibtex_ui.py:94 -msgid "strict" -msgstr "" - -#: /home/kovid/work/calibre/src/calibre/gui2/catalog/catalog_bibtex_ui.py:95 -msgid "replace" -msgstr "" - -#: /home/kovid/work/calibre/src/calibre/gui2/catalog/catalog_bibtex_ui.py:96 -msgid "ignore" -msgstr "" - -#: /home/kovid/work/calibre/src/calibre/gui2/catalog/catalog_bibtex_ui.py:97 -msgid "backslashreplace" -msgstr "" - -#: /home/kovid/work/calibre/src/calibre/gui2/catalog/catalog_bibtex_ui.py:98 +#: /home/kovid/work/calibre/src/calibre/gui2/catalog/catalog_bibtex_ui.py:81 msgid "BibTeX entry type:" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/catalog/catalog_bibtex_ui.py:99 -msgid "mixed" -msgstr "" - -#: /home/kovid/work/calibre/src/calibre/gui2/catalog/catalog_bibtex_ui.py:100 -msgid "misc" -msgstr "" - -#: /home/kovid/work/calibre/src/calibre/gui2/catalog/catalog_bibtex_ui.py:101 -msgid "book" -msgstr "" - -#: /home/kovid/work/calibre/src/calibre/gui2/catalog/catalog_bibtex_ui.py:102 +#: /home/kovid/work/calibre/src/calibre/gui2/catalog/catalog_bibtex_ui.py:82 msgid "Create a citation tag?" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/catalog/catalog_bibtex_ui.py:103 +#: /home/kovid/work/calibre/src/calibre/gui2/catalog/catalog_bibtex_ui.py:83 msgid "Add files path with formats?" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/catalog/catalog_bibtex_ui.py:104 +#: /home/kovid/work/calibre/src/calibre/gui2/catalog/catalog_bibtex_ui.py:84 msgid "Expression to form the BibTeX citation tag:" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/catalog/catalog_bibtex_ui.py:105 +#: /home/kovid/work/calibre/src/calibre/gui2/catalog/catalog_bibtex_ui.py:85 msgid "" "Some explanation about this template:\n" " -The fields availables are 'author_sort', 'authors', 'id',\n" @@ -5396,10 +5624,12 @@ msgid "Remove formatting" msgstr "" #: /home/kovid/work/calibre/src/calibre/gui2/comments_editor.py:96 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/edit_authors_dialog.py:134 msgid "Copy" msgstr "" #: /home/kovid/work/calibre/src/calibre/gui2/comments_editor.py:97 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/edit_authors_dialog.py:136 msgid "Paste" msgstr "" @@ -5436,8 +5666,9 @@ msgid "Style the selected text block" msgstr "" #: /home/kovid/work/calibre/src/calibre/gui2/comments_editor.py:125 -#: /home/kovid/work/calibre/src/calibre/gui2/preferences/behavior.py:32 -#: /home/kovid/work/calibre/src/calibre/gui2/preferences/behavior_ui.py:139 +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/behavior.py:34 +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/behavior.py:36 +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/behavior_ui.py:158 msgid "Normal" msgstr "" @@ -5488,11 +5719,11 @@ msgstr "" msgid "Enter URL" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/comments_editor.py:522 +#: /home/kovid/work/calibre/src/calibre/gui2/comments_editor.py:523 msgid "Normal view" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/comments_editor.py:523 +#: /home/kovid/work/calibre/src/calibre/gui2/comments_editor.py:524 msgid "HTML Source" msgstr "" @@ -5523,73 +5754,77 @@ msgstr "" msgid "input" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/convert/comic_input_ui.py:101 +#: /home/kovid/work/calibre/src/calibre/gui2/convert/comic_input_ui.py:104 #: /home/kovid/work/calibre/src/calibre/gui2/dialogs/comicconf_ui.py:99 msgid "&Number of Colors:" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/convert/comic_input_ui.py:102 +#: /home/kovid/work/calibre/src/calibre/gui2/convert/comic_input_ui.py:105 #: /home/kovid/work/calibre/src/calibre/gui2/dialogs/comicconf_ui.py:101 msgid "Disable &normalize" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/convert/comic_input_ui.py:103 +#: /home/kovid/work/calibre/src/calibre/gui2/convert/comic_input_ui.py:106 #: /home/kovid/work/calibre/src/calibre/gui2/dialogs/comicconf_ui.py:102 msgid "Keep &aspect ratio" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/convert/comic_input_ui.py:104 +#: /home/kovid/work/calibre/src/calibre/gui2/convert/comic_input_ui.py:107 #: /home/kovid/work/calibre/src/calibre/gui2/dialogs/comicconf_ui.py:103 msgid "Disable &Sharpening" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/convert/comic_input_ui.py:105 +#: /home/kovid/work/calibre/src/calibre/gui2/convert/comic_input_ui.py:108 #: /home/kovid/work/calibre/src/calibre/gui2/dialogs/comicconf_ui.py:109 msgid "Disable &Trimming" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/convert/comic_input_ui.py:106 +#: /home/kovid/work/calibre/src/calibre/gui2/convert/comic_input_ui.py:109 #: /home/kovid/work/calibre/src/calibre/gui2/dialogs/comicconf_ui.py:108 msgid "&Wide" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/convert/comic_input_ui.py:107 +#: /home/kovid/work/calibre/src/calibre/gui2/convert/comic_input_ui.py:110 #: /home/kovid/work/calibre/src/calibre/gui2/dialogs/comicconf_ui.py:104 msgid "&Landscape" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/convert/comic_input_ui.py:108 +#: /home/kovid/work/calibre/src/calibre/gui2/convert/comic_input_ui.py:111 #: /home/kovid/work/calibre/src/calibre/gui2/dialogs/comicconf_ui.py:106 msgid "&Right to left" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/convert/comic_input_ui.py:109 +#: /home/kovid/work/calibre/src/calibre/gui2/convert/comic_input_ui.py:112 #: /home/kovid/work/calibre/src/calibre/gui2/dialogs/comicconf_ui.py:105 msgid "Don't so&rt" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/convert/comic_input_ui.py:110 +#: /home/kovid/work/calibre/src/calibre/gui2/convert/comic_input_ui.py:113 #: /home/kovid/work/calibre/src/calibre/gui2/dialogs/comicconf_ui.py:107 msgid "De&speckle" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/convert/comic_input_ui.py:111 +#: /home/kovid/work/calibre/src/calibre/gui2/convert/comic_input_ui.py:114 msgid "&Disable comic processing" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/convert/comic_input_ui.py:112 +#: /home/kovid/work/calibre/src/calibre/gui2/convert/comic_input_ui.py:115 #: /home/kovid/work/calibre/src/calibre/gui2/convert/single_ui.py:120 msgid "&Output format:" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/convert/comic_input_ui.py:113 +#: /home/kovid/work/calibre/src/calibre/gui2/convert/comic_input_ui.py:116 msgid "Disable conversion of images to &black and white" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/convert/comic_input_ui.py:114 +#: /home/kovid/work/calibre/src/calibre/gui2/convert/comic_input_ui.py:117 msgid "Override image &size:" msgstr "" +#: /home/kovid/work/calibre/src/calibre/gui2/convert/comic_input_ui.py:118 +msgid "Don't add links to &pages to the Table of Contents for CBC files" +msgstr "" + #: /home/kovid/work/calibre/src/calibre/gui2/convert/debug.py:19 msgid "Debug" msgstr "" @@ -5670,10 +5905,14 @@ msgstr "" msgid "FB2 Output" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/convert/fb2_output_ui.py:39 +#: /home/kovid/work/calibre/src/calibre/gui2/convert/fb2_output_ui.py:45 msgid "Sectionize:" msgstr "" +#: /home/kovid/work/calibre/src/calibre/gui2/convert/fb2_output_ui.py:46 +msgid "Genre" +msgstr "" + #: /home/kovid/work/calibre/src/calibre/gui2/convert/font_key_ui.py:104 msgid "Font rescaling wizard" msgstr "" @@ -5813,6 +6052,18 @@ msgstr "" msgid "Replace entity indents with CSS indents" msgstr "" +#: /home/kovid/work/calibre/src/calibre/gui2/convert/htmlz_output.py:14 +msgid "HTMLZ Output" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/convert/htmlz_output_ui.py:45 +msgid "How to handle CSS" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/convert/htmlz_output_ui.py:46 +msgid "How to handle class based CSS" +msgstr "" + #: /home/kovid/work/calibre/src/calibre/gui2/convert/look_and_feel.py:16 msgid "Look & Feel" msgstr "" @@ -5961,7 +6212,7 @@ msgid "&Monospaced font family:" msgstr "" #: /home/kovid/work/calibre/src/calibre/gui2/convert/metadata.py:47 -#: /home/kovid/work/calibre/src/calibre/gui2/viewer/main.py:115 +#: /home/kovid/work/calibre/src/calibre/gui2/viewer/main.py:117 #: /home/kovid/work/calibre/src/calibre/gui2/viewer/main_ui.py:200 msgid "Metadata" msgstr "" @@ -5973,49 +6224,41 @@ msgid "" msgstr "" #: /home/kovid/work/calibre/src/calibre/gui2/convert/metadata.py:180 -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single.py:171 -#: /home/kovid/work/calibre/src/calibre/gui2/metadata/basic_widgets.py:643 +#: /home/kovid/work/calibre/src/calibre/gui2/metadata/basic_widgets.py:726 msgid "Choose cover for " msgstr "" #: /home/kovid/work/calibre/src/calibre/gui2/convert/metadata.py:187 -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single.py:178 -#: /home/kovid/work/calibre/src/calibre/gui2/metadata/basic_widgets.py:651 +#: /home/kovid/work/calibre/src/calibre/gui2/metadata/basic_widgets.py:734 msgid "Cannot read" msgstr "" #: /home/kovid/work/calibre/src/calibre/gui2/convert/metadata.py:188 -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single.py:179 -#: /home/kovid/work/calibre/src/calibre/gui2/metadata/basic_widgets.py:652 +#: /home/kovid/work/calibre/src/calibre/gui2/metadata/basic_widgets.py:735 msgid "You do not have permission to read the file: " msgstr "" #: /home/kovid/work/calibre/src/calibre/gui2/convert/metadata.py:196 #: /home/kovid/work/calibre/src/calibre/gui2/convert/metadata.py:203 -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single.py:187 -#: /home/kovid/work/calibre/src/calibre/gui2/metadata/basic_widgets.py:660 +#: /home/kovid/work/calibre/src/calibre/gui2/metadata/basic_widgets.py:743 msgid "Error reading file" msgstr "" #: /home/kovid/work/calibre/src/calibre/gui2/convert/metadata.py:197 -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single.py:188 -#: /home/kovid/work/calibre/src/calibre/gui2/metadata/basic_widgets.py:661 +#: /home/kovid/work/calibre/src/calibre/gui2/metadata/basic_widgets.py:744 msgid "

    There was an error reading from file:
    " msgstr "" #: /home/kovid/work/calibre/src/calibre/gui2/convert/metadata.py:204 -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single.py:196 -#: /home/kovid/work/calibre/src/calibre/gui2/metadata/basic_widgets.py:671 +#: /home/kovid/work/calibre/src/calibre/gui2/metadata/basic_widgets.py:754 msgid " is not a valid picture" msgstr "" #: /home/kovid/work/calibre/src/calibre/gui2/convert/metadata_ui.py:159 -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single_ui.py:446 msgid "Book Cover" msgstr "" #: /home/kovid/work/calibre/src/calibre/gui2/convert/metadata_ui.py:160 -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single_ui.py:447 msgid "Change &cover image:" msgstr "" @@ -6028,19 +6271,16 @@ msgid "Use cover from &source file" msgstr "" #: /home/kovid/work/calibre/src/calibre/gui2/convert/metadata_ui.py:164 -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single_ui.py:408 msgid "&Title: " msgstr "" #: /home/kovid/work/calibre/src/calibre/gui2/convert/metadata_ui.py:165 -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single_ui.py:409 -#: /home/kovid/work/calibre/src/calibre/gui2/metadata/basic_widgets.py:60 +#: /home/kovid/work/calibre/src/calibre/gui2/metadata/basic_widgets.py:72 msgid "Change the title of this book" msgstr "" #: /home/kovid/work/calibre/src/calibre/gui2/convert/metadata_ui.py:166 -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:499 -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single_ui.py:420 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:525 msgid "&Author(s): " msgstr "" @@ -6055,44 +6295,38 @@ msgid "" msgstr "" #: /home/kovid/work/calibre/src/calibre/gui2/convert/metadata_ui.py:169 -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:509 -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single_ui.py:428 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:535 msgid "&Publisher: " msgstr "" #: /home/kovid/work/calibre/src/calibre/gui2/convert/metadata_ui.py:170 -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single_ui.py:429 msgid "Ta&gs: " 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:511 -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single_ui.py:430 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:537 +#: /home/kovid/work/calibre/src/calibre/gui2/metadata/basic_widgets.py:909 msgid "" "Tags categorize the book. This is particularly useful while searching. " "

    They can be any words or phrases, separated by commas." msgstr "" #: /home/kovid/work/calibre/src/calibre/gui2/convert/metadata_ui.py:172 -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:518 -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single_ui.py:433 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:544 #: /home/kovid/work/calibre/src/calibre/gui2/dialogs/search_ui.py:214 -#: /home/kovid/work/calibre/src/calibre/gui2/metadata/basic_widgets.py:293 +#: /home/kovid/work/calibre/src/calibre/gui2/metadata/basic_widgets.py:355 msgid "&Series:" msgstr "" #: /home/kovid/work/calibre/src/calibre/gui2/convert/metadata_ui.py:173 #: /home/kovid/work/calibre/src/calibre/gui2/convert/metadata_ui.py:174 -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:519 -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:520 -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single_ui.py:434 -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single_ui.py:435 -#: /home/kovid/work/calibre/src/calibre/gui2/metadata/basic_widgets.py:292 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:545 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:546 +#: /home/kovid/work/calibre/src/calibre/gui2/metadata/basic_widgets.py:354 msgid "List of known series. You can add new series." msgstr "" #: /home/kovid/work/calibre/src/calibre/gui2/convert/metadata_ui.py:175 -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single_ui.py:438 msgid "Book " msgstr "" @@ -6101,6 +6335,7 @@ msgid "MOBI Output" msgstr "" #: /home/kovid/work/calibre/src/calibre/gui2/convert/mobi_output.py:44 +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/behavior.py:64 msgid "Default" msgstr "" @@ -6189,13 +6424,14 @@ msgid "PDB Output" msgstr "" #: /home/kovid/work/calibre/src/calibre/gui2/convert/pdb_output_ui.py:48 +#: /home/kovid/work/calibre/src/calibre/gui2/store/search/adv_search_builder_ui.py:195 msgid "&Format:" msgstr "" #: /home/kovid/work/calibre/src/calibre/gui2/convert/pdb_output_ui.py:49 #: /home/kovid/work/calibre/src/calibre/gui2/convert/pmlz_output_ui.py:47 #: /home/kovid/work/calibre/src/calibre/gui2/convert/rb_output_ui.py:34 -#: /home/kovid/work/calibre/src/calibre/gui2/convert/txt_output_ui.py:92 +#: /home/kovid/work/calibre/src/calibre/gui2/convert/txt_output_ui.py:95 msgid "&Inline TOC" msgstr "" @@ -6265,7 +6501,7 @@ msgid "Regex:" msgstr "" #: /home/kovid/work/calibre/src/calibre/gui2/convert/regex_builder_ui.py:92 -#: /home/kovid/work/calibre/src/calibre/gui2/filename_pattern_ui.py:122 +#: /home/kovid/work/calibre/src/calibre/gui2/filename_pattern_ui.py:136 msgid "Test" msgstr "" @@ -6274,6 +6510,8 @@ msgid "Occurrences:" msgstr "" #: /home/kovid/work/calibre/src/calibre/gui2/convert/regex_builder_ui.py:94 +#: /home/kovid/work/calibre/src/calibre/gui2/store/mobileread/store_dialog_ui.py:78 +#: /home/kovid/work/calibre/src/calibre/gui2/store/mobileread_store_dialog_ui.py:64 msgid "0" msgstr "" @@ -6282,13 +6520,13 @@ msgid "Goto:" msgstr "" #: /home/kovid/work/calibre/src/calibre/gui2/convert/regex_builder_ui.py:96 -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/book_info_ui.py:81 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/book_info_ui.py:72 #: /home/kovid/work/calibre/src/calibre/gui2/preferences/plugins_ui.py:89 msgid "&Previous" msgstr "" #: /home/kovid/work/calibre/src/calibre/gui2/convert/regex_builder_ui.py:97 -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/book_info_ui.py:82 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/book_info_ui.py:73 #: /home/kovid/work/calibre/src/calibre/gui2/preferences/plugins_ui.py:88 msgid "&Next" msgstr "" @@ -6297,26 +6535,26 @@ msgstr "" msgid "Preview" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/convert/search_and_replace.py:15 +#: /home/kovid/work/calibre/src/calibre/gui2/convert/search_and_replace.py:17 msgid "" "Search\n" "&\n" "Replace" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/convert/search_and_replace.py:28 -#: /home/kovid/work/calibre/src/calibre/gui2/convert/search_and_replace.py:31 -#: /home/kovid/work/calibre/src/calibre/gui2/convert/search_and_replace.py:34 +#: /home/kovid/work/calibre/src/calibre/gui2/convert/search_and_replace.py:30 +#: /home/kovid/work/calibre/src/calibre/gui2/convert/search_and_replace.py:33 +#: /home/kovid/work/calibre/src/calibre/gui2/convert/search_and_replace.py:36 msgid "&Search Regular Expression" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/convert/search_and_replace.py:71 -#: /home/kovid/work/calibre/src/calibre/gui2/widgets.py:99 +#: /home/kovid/work/calibre/src/calibre/gui2/convert/search_and_replace.py:73 +#: /home/kovid/work/calibre/src/calibre/gui2/widgets.py:101 msgid "Invalid regular expression" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/convert/search_and_replace.py:72 -#: /home/kovid/work/calibre/src/calibre/gui2/widgets.py:100 +#: /home/kovid/work/calibre/src/calibre/gui2/convert/search_and_replace.py:74 +#: /home/kovid/work/calibre/src/calibre/gui2/widgets.py:102 msgid "Invalid regular expression: %s" msgstr "" @@ -6356,10 +6594,13 @@ msgid "Options specific to the input format." msgstr "" #: /home/kovid/work/calibre/src/calibre/gui2/convert/single_ui.py:117 -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/book_info_ui.py:76 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/book_info_ui.py:69 #: /home/kovid/work/calibre/src/calibre/gui2/dialogs/comicconf_ui.py:96 #: /home/kovid/work/calibre/src/calibre/gui2/dialogs/message_box_ui.py:52 #: /home/kovid/work/calibre/src/calibre/gui2/dialogs/progress_ui.py:53 +#: /home/kovid/work/calibre/src/calibre/gui2/store/mobileread/cache_progress_dialog_ui.py:50 +#: /home/kovid/work/calibre/src/calibre/gui2/store/mobileread/store_dialog_ui.py:73 +#: /home/kovid/work/calibre/src/calibre/gui2/store/mobileread_store_dialog_ui.py:61 msgid "Dialog" msgstr "" @@ -6420,19 +6661,19 @@ msgstr "" msgid "The XPath expression %s is invalid." msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/convert/structure_detection_ui.py:57 +#: /home/kovid/work/calibre/src/calibre/gui2/convert/structure_detection_ui.py:60 msgid "Chapter &mark:" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/convert/structure_detection_ui.py:58 +#: /home/kovid/work/calibre/src/calibre/gui2/convert/structure_detection_ui.py:61 msgid "Remove first &image" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/convert/structure_detection_ui.py:59 +#: /home/kovid/work/calibre/src/calibre/gui2/convert/structure_detection_ui.py:62 msgid "Insert &metadata as page at start of book" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/convert/structure_detection_ui.py:60 +#: /home/kovid/work/calibre/src/calibre/gui2/convert/structure_detection_ui.py:63 msgid "" "The header and footer removal options have been replaced by the Search & " "Replace options. Click the Search & Replace category in the bar to the left " @@ -6440,6 +6681,10 @@ msgid "" "header/footer removal regexps into the search field." msgstr "" +#: /home/kovid/work/calibre/src/calibre/gui2/convert/structure_detection_ui.py:64 +msgid "Remove &fake margins" +msgstr "" + #: /home/kovid/work/calibre/src/calibre/gui2/convert/toc.py:16 msgid "" "Table of\n" @@ -6529,53 +6774,56 @@ msgstr "" msgid "TXT Output" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/convert/txt_output_ui.py:85 +#: /home/kovid/work/calibre/src/calibre/gui2/convert/txt_output_ui.py:88 msgid "General" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/convert/txt_output_ui.py:86 +#: /home/kovid/work/calibre/src/calibre/gui2/convert/txt_output_ui.py:89 msgid "Output &Encoding:" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/convert/txt_output_ui.py:87 +#: /home/kovid/work/calibre/src/calibre/gui2/convert/txt_output_ui.py:90 msgid "&Line ending style:" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/convert/txt_output_ui.py:88 +#: /home/kovid/work/calibre/src/calibre/gui2/convert/txt_output_ui.py:91 msgid "&Formatting:" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/convert/txt_output_ui.py:89 +#: /home/kovid/work/calibre/src/calibre/gui2/convert/txt_output_ui.py:92 msgid "Plain" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/convert/txt_output_ui.py:90 +#: /home/kovid/work/calibre/src/calibre/gui2/convert/txt_output_ui.py:93 msgid "&Maximum line length:" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/convert/txt_output_ui.py:91 +#: /home/kovid/work/calibre/src/calibre/gui2/convert/txt_output_ui.py:94 msgid "Force maximum line length" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/convert/txt_output_ui.py:93 +#: /home/kovid/work/calibre/src/calibre/gui2/convert/txt_output_ui.py:96 msgid "Markdown, Textile" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/convert/txt_output_ui.py:94 +#: /home/kovid/work/calibre/src/calibre/gui2/convert/txt_output_ui.py:97 msgid "Do not remove links ( tags) before processing" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/convert/txt_output_ui.py:95 +#: /home/kovid/work/calibre/src/calibre/gui2/convert/txt_output_ui.py:98 msgid "Do not remove image references before processing" msgstr "" +#: /home/kovid/work/calibre/src/calibre/gui2/convert/txt_output_ui.py:99 +msgid "Keep text color, when possible" +msgstr "" + #: /home/kovid/work/calibre/src/calibre/gui2/convert/txtz_output.py:12 msgid "TXTZ Output" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/convert/xexp_edit_ui.py:54 -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/book_info_ui.py:77 -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/book_info_ui.py:78 +#: /home/kovid/work/calibre/src/calibre/gui2/convert/xexp_edit_ui.py:55 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/book_info_ui.py:70 #: /home/kovid/work/calibre/src/calibre/gui2/dialogs/choose_format_ui.py:46 #: /home/kovid/work/calibre/src/calibre/gui2/dialogs/confirm_delete_ui.py:54 #: /home/kovid/work/calibre/src/calibre/gui2/dialogs/password_ui.py:62 @@ -6584,7 +6832,7 @@ msgstr "" msgid "TextLabel" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/convert/xexp_edit_ui.py:55 +#: /home/kovid/work/calibre/src/calibre/gui2/convert/xexp_edit_ui.py:56 msgid "Use a wizard to help construct the Regular expression" msgstr "" @@ -6662,306 +6910,304 @@ msgid "" "href=\"http://calibre-ebook.com/user_manual/xpath.html\">XPath Tutorial." msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/cover_flow.py:118 +#: /home/kovid/work/calibre/src/calibre/gui2/cover_flow.py:128 msgid "Browse by covers" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/cover_flow.py:149 +#: /home/kovid/work/calibre/src/calibre/gui2/cover_flow.py:158 msgid "Cover browser could not be loaded" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/custom_column_widgets.py:64 -#: /home/kovid/work/calibre/src/calibre/gui2/custom_column_widgets.py:89 -#: /home/kovid/work/calibre/src/calibre/gui2/custom_column_widgets.py:113 -#: /home/kovid/work/calibre/src/calibre/gui2/custom_column_widgets.py:150 -#: /home/kovid/work/calibre/src/calibre/gui2/custom_column_widgets.py:184 -#: /home/kovid/work/calibre/src/calibre/gui2/custom_column_widgets.py:294 -#: /home/kovid/work/calibre/src/calibre/gui2/custom_column_widgets.py:558 -#: /home/kovid/work/calibre/src/calibre/gui2/custom_column_widgets.py:599 -#: /home/kovid/work/calibre/src/calibre/gui2/custom_column_widgets.py:622 -#: /home/kovid/work/calibre/src/calibre/gui2/custom_column_widgets.py:673 +#: /home/kovid/work/calibre/src/calibre/gui2/custom_column_widgets.py:63 +#: /home/kovid/work/calibre/src/calibre/gui2/custom_column_widgets.py:88 +#: /home/kovid/work/calibre/src/calibre/gui2/custom_column_widgets.py:112 +#: /home/kovid/work/calibre/src/calibre/gui2/custom_column_widgets.py:149 +#: /home/kovid/work/calibre/src/calibre/gui2/custom_column_widgets.py:183 +#: /home/kovid/work/calibre/src/calibre/gui2/custom_column_widgets.py:302 +#: /home/kovid/work/calibre/src/calibre/gui2/custom_column_widgets.py:567 +#: /home/kovid/work/calibre/src/calibre/gui2/custom_column_widgets.py:608 +#: /home/kovid/work/calibre/src/calibre/gui2/custom_column_widgets.py:631 +#: /home/kovid/work/calibre/src/calibre/gui2/custom_column_widgets.py:682 #: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk.py:306 #: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk.py:311 -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single.py:503 -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single.py:504 -#: /home/kovid/work/calibre/src/calibre/gui2/library/delegates.py:114 -#: /home/kovid/work/calibre/src/calibre/gui2/library/delegates.py:134 -#: /home/kovid/work/calibre/src/calibre/gui2/library/delegates.py:216 -#: /home/kovid/work/calibre/src/calibre/gui2/library/delegates.py:249 -#: /home/kovid/work/calibre/src/calibre/gui2/library/delegates.py:253 -#: /home/kovid/work/calibre/src/calibre/gui2/metadata/basic_widgets.py:994 +#: /home/kovid/work/calibre/src/calibre/gui2/library/delegates.py:120 +#: /home/kovid/work/calibre/src/calibre/gui2/library/delegates.py:140 +#: /home/kovid/work/calibre/src/calibre/gui2/library/delegates.py:222 +#: /home/kovid/work/calibre/src/calibre/gui2/library/delegates.py:255 +#: /home/kovid/work/calibre/src/calibre/gui2/library/delegates.py:259 +#: /home/kovid/work/calibre/src/calibre/gui2/metadata/basic_widgets.py:1117 msgid "Undefined" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/custom_column_widgets.py:127 -#: /home/kovid/work/calibre/src/calibre/gui2/custom_column_widgets.py:630 +#: /home/kovid/work/calibre/src/calibre/gui2/custom_column_widgets.py:126 +#: /home/kovid/work/calibre/src/calibre/gui2/custom_column_widgets.py:639 msgid "star(s)" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/custom_column_widgets.py:128 -#: /home/kovid/work/calibre/src/calibre/gui2/custom_column_widgets.py:631 +#: /home/kovid/work/calibre/src/calibre/gui2/custom_column_widgets.py:127 +#: /home/kovid/work/calibre/src/calibre/gui2/custom_column_widgets.py:640 msgid "Unrated" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/custom_column_widgets.py:171 -#: /home/kovid/work/calibre/src/calibre/gui2/custom_column_widgets.py:660 +#: /home/kovid/work/calibre/src/calibre/gui2/custom_column_widgets.py:170 +#: /home/kovid/work/calibre/src/calibre/gui2/custom_column_widgets.py:669 msgid "Set '%s' to today" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/custom_column_widgets.py:173 -#: /home/kovid/work/calibre/src/calibre/gui2/custom_column_widgets.py:662 +#: /home/kovid/work/calibre/src/calibre/gui2/custom_column_widgets.py:172 +#: /home/kovid/work/calibre/src/calibre/gui2/custom_column_widgets.py:671 msgid "Clear '%s'" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/custom_column_widgets.py:290 +#: /home/kovid/work/calibre/src/calibre/gui2/custom_column_widgets.py:298 msgid " index:" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/custom_column_widgets.py:359 +#: /home/kovid/work/calibre/src/calibre/gui2/custom_column_widgets.py:367 msgid "" "The enumeration \"{0}\" contains an invalid value that will be set to the " "default" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/custom_column_widgets.py:513 +#: /home/kovid/work/calibre/src/calibre/gui2/custom_column_widgets.py:522 msgid "Apply changes" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/custom_column_widgets.py:706 +#: /home/kovid/work/calibre/src/calibre/gui2/custom_column_widgets.py:715 msgid "Remove series" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/custom_column_widgets.py:709 +#: /home/kovid/work/calibre/src/calibre/gui2/custom_column_widgets.py:718 msgid "Automatically number books" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/custom_column_widgets.py:712 +#: /home/kovid/work/calibre/src/calibre/gui2/custom_column_widgets.py:721 msgid "Force numbers to start with " msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/custom_column_widgets.py:783 +#: /home/kovid/work/calibre/src/calibre/gui2/custom_column_widgets.py:792 msgid "" "The enumeration \"{0}\" contains invalid values that will not appear in the " "list" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/custom_column_widgets.py:826 +#: /home/kovid/work/calibre/src/calibre/gui2/custom_column_widgets.py:836 msgid "Remove all tags" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/custom_column_widgets.py:846 +#: /home/kovid/work/calibre/src/calibre/gui2/custom_column_widgets.py:856 msgid "tags to add" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/custom_column_widgets.py:852 +#: /home/kovid/work/calibre/src/calibre/gui2/custom_column_widgets.py:863 msgid "tags to remove" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/device.py:45 -#: /home/kovid/work/calibre/src/calibre/utils/ipc/job.py:136 +#: /home/kovid/work/calibre/src/calibre/gui2/device.py:43 +#: /home/kovid/work/calibre/src/calibre/utils/ipc/job.py:144 msgid "No details available." msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/device.py:185 +#: /home/kovid/work/calibre/src/calibre/gui2/device.py:168 msgid "Device no longer connected." msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/device.py:303 +#: /home/kovid/work/calibre/src/calibre/gui2/device.py:291 msgid "Get device information" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/device.py:314 +#: /home/kovid/work/calibre/src/calibre/gui2/device.py:305 msgid "Get list of books on device" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/device.py:324 +#: /home/kovid/work/calibre/src/calibre/gui2/device.py:315 msgid "Get annotations from device" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/device.py:336 +#: /home/kovid/work/calibre/src/calibre/gui2/device.py:327 msgid "Send metadata to device" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/device.py:341 +#: /home/kovid/work/calibre/src/calibre/gui2/device.py:332 msgid "Send collections to device" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/device.py:376 +#: /home/kovid/work/calibre/src/calibre/gui2/device.py:368 msgid "Upload %d books to device" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/device.py:391 +#: /home/kovid/work/calibre/src/calibre/gui2/device.py:383 msgid "Delete books from device" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/device.py:408 +#: /home/kovid/work/calibre/src/calibre/gui2/device.py:400 msgid "Download books from device" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/device.py:418 +#: /home/kovid/work/calibre/src/calibre/gui2/device.py:410 msgid "View book on device" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/device.py:452 +#: /home/kovid/work/calibre/src/calibre/gui2/device.py:451 msgid "Set default send to device action" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/device.py:458 +#: /home/kovid/work/calibre/src/calibre/gui2/device.py:457 msgid "Send to main memory" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/device.py:460 +#: /home/kovid/work/calibre/src/calibre/gui2/device.py:459 msgid "Send to storage card A" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/device.py:462 +#: /home/kovid/work/calibre/src/calibre/gui2/device.py:461 msgid "Send to storage card B" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/device.py:467 -#: /home/kovid/work/calibre/src/calibre/gui2/device.py:476 +#: /home/kovid/work/calibre/src/calibre/gui2/device.py:466 +#: /home/kovid/work/calibre/src/calibre/gui2/device.py:475 msgid "Main Memory" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/device.py:488 +#: /home/kovid/work/calibre/src/calibre/gui2/device.py:487 msgid "Send specific format to" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/device.py:489 +#: /home/kovid/work/calibre/src/calibre/gui2/device.py:488 msgid "Send and delete from library" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/device.py:532 +#: /home/kovid/work/calibre/src/calibre/gui2/device.py:531 msgid "Eject device" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/device.py:594 -#: /home/kovid/work/calibre/src/calibre/gui2/preferences/misc.py:41 -#: /home/kovid/work/calibre/src/calibre/gui2/preferences/plugins.py:302 +#: /home/kovid/work/calibre/src/calibre/gui2/device.py:611 +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/misc.py:55 +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/plugins.py:313 #: /home/kovid/work/calibre/src/calibre/utils/ipc/job.py:54 msgid "Error" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/device.py:595 +#: /home/kovid/work/calibre/src/calibre/gui2/device.py:612 msgid "Error communicating with device" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/device.py:611 -#: /home/kovid/work/calibre/src/calibre/gui2/device.py:1139 -#: /home/kovid/work/calibre/src/calibre/gui2/email.py:298 +#: /home/kovid/work/calibre/src/calibre/gui2/device.py:631 +#: /home/kovid/work/calibre/src/calibre/gui2/device.py:1170 +#: /home/kovid/work/calibre/src/calibre/gui2/email.py:221 msgid "No suitable formats" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/device.py:627 +#: /home/kovid/work/calibre/src/calibre/gui2/device.py:647 msgid "Select folder to open as device" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/device.py:678 +#: /home/kovid/work/calibre/src/calibre/gui2/device.py:698 msgid "Error talking to device" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/device.py:679 +#: /home/kovid/work/calibre/src/calibre/gui2/device.py:699 msgid "" "There was a temporary error talking to the device. Please unplug and " "reconnect the device and or reboot." msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/device.py:722 +#: /home/kovid/work/calibre/src/calibre/gui2/device.py:742 msgid "Device: " msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/device.py:724 +#: /home/kovid/work/calibre/src/calibre/gui2/device.py:744 msgid " detected." msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/device.py:822 +#: /home/kovid/work/calibre/src/calibre/gui2/device.py:846 msgid "selected to send" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/device.py:841 +#: /home/kovid/work/calibre/src/calibre/gui2/device.py:865 msgid "%i of %i Books" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/device.py:844 +#: /home/kovid/work/calibre/src/calibre/gui2/device.py:868 msgid "0 of %i Books" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/device.py:845 +#: /home/kovid/work/calibre/src/calibre/gui2/device.py:869 msgid "Choose format to send to device" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/device.py:853 +#: /home/kovid/work/calibre/src/calibre/gui2/device.py:877 msgid "No device" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/device.py:854 +#: /home/kovid/work/calibre/src/calibre/gui2/device.py:878 msgid "Cannot send: No device is connected" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/device.py:857 -#: /home/kovid/work/calibre/src/calibre/gui2/device.py:861 +#: /home/kovid/work/calibre/src/calibre/gui2/device.py:881 +#: /home/kovid/work/calibre/src/calibre/gui2/device.py:885 msgid "No card" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/device.py:858 -#: /home/kovid/work/calibre/src/calibre/gui2/device.py:862 +#: /home/kovid/work/calibre/src/calibre/gui2/device.py:882 +#: /home/kovid/work/calibre/src/calibre/gui2/device.py:886 msgid "Cannot send: Device has no storage card" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/device.py:918 -#: /home/kovid/work/calibre/src/calibre/gui2/device.py:1001 -#: /home/kovid/work/calibre/src/calibre/gui2/device.py:1133 +#: /home/kovid/work/calibre/src/calibre/gui2/device.py:947 +#: /home/kovid/work/calibre/src/calibre/gui2/device.py:1030 +#: /home/kovid/work/calibre/src/calibre/gui2/device.py:1164 msgid "Auto convert the following books before uploading to the device?" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/device.py:947 +#: /home/kovid/work/calibre/src/calibre/gui2/device.py:976 msgid "Sending catalogs to device." msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/device.py:1046 +#: /home/kovid/work/calibre/src/calibre/gui2/device.py:1077 msgid "Sending news to device." msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/device.py:1100 +#: /home/kovid/work/calibre/src/calibre/gui2/device.py:1131 msgid "Sending books to device." msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/device.py:1140 +#: /home/kovid/work/calibre/src/calibre/gui2/device.py:1171 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:1204 +#: /home/kovid/work/calibre/src/calibre/gui2/device.py:1243 msgid "No space on device" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/device.py:1205 +#: /home/kovid/work/calibre/src/calibre/gui2/device.py:1244 msgid "" "

    Cannot upload books to device there is no more free space available " msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/device_drivers/configwidget.py:124 +#: /home/kovid/work/calibre/src/calibre/gui2/device_drivers/configwidget.py:135 msgid "Unknown formats" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/device_drivers/configwidget.py:125 +#: /home/kovid/work/calibre/src/calibre/gui2/device_drivers/configwidget.py:136 msgid "" "You have enabled the {0} formats for your {1}. The {1} may not " "support them. If you send these formats to your {1} they may not work. Are " "you sure?" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/device_drivers/configwidget.py:137 -#: /home/kovid/work/calibre/src/calibre/gui2/library/delegates.py:403 -#: /home/kovid/work/calibre/src/calibre/gui2/preferences/plugboard.py:255 +#: /home/kovid/work/calibre/src/calibre/gui2/device_drivers/configwidget.py:148 +#: /home/kovid/work/calibre/src/calibre/gui2/library/delegates.py:409 +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/plugboard.py:293 #: /home/kovid/work/calibre/src/calibre/gui2/preferences/save_template.py:61 msgid "Invalid template" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/device_drivers/configwidget.py:138 -#: /home/kovid/work/calibre/src/calibre/gui2/library/delegates.py:404 -#: /home/kovid/work/calibre/src/calibre/gui2/preferences/plugboard.py:256 +#: /home/kovid/work/calibre/src/calibre/gui2/device_drivers/configwidget.py:149 +#: /home/kovid/work/calibre/src/calibre/gui2/library/delegates.py:410 +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/plugboard.py:294 #: /home/kovid/work/calibre/src/calibre/gui2/preferences/save_template.py:62 msgid "The template %s is invalid:" msgstr "" @@ -7027,7 +7273,7 @@ msgstr "" msgid "&Tags to set on created book entries:" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/book_info_ui.py:80 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/book_info_ui.py:71 msgid "Fit &cover within view" msgstr "" @@ -7136,68 +7382,80 @@ msgid "" " have no entries in the database. Check the box next to the item you " "want\n" " to delete. Use with caution.

    \n" -"

    Fix marked is applicable only to covers (the two lines " -"marked\n" -" 'fixable'). In the case of missing cover files, checking the " -"fixable\n" -" box and pushing this button will remove the cover mark from the\n" -" database for all the files in that category. In the case of extra\n" -" cover files, checking the fixable box and pushing this button will\n" -" add the cover mark to the database for all the files in that\n" -" category.

    \n" +"\n" +"

    Fix marked is applicable only to covers and missing " +"formats\n" +" (the three lines marked 'fixable'). In the case of missing cover " +"files,\n" +" checking the fixable box and pushing this button will tell calibre " +"that\n" +" there is no cover for all of the books listed. Use this option if " +"you\n" +" are not going to restore the covers from a backup. In the case of " +"extra\n" +" cover files, checking the fixable box and pushing this button will " +"tell\n" +" calibre that the cover files it found are correct for all the books\n" +" listed. Use this when you are not going to delete the file(s). In " +"the\n" +" case of missing formats, checking the fixable box and pushing this\n" +" button will tell calibre that the formats are really gone. Use this " +"if\n" +" you are not going to restore the formats from a backup.

    \n" +"\n" " " msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/check_library.py:220 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/check_library.py:226 msgid "&Run the check again" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/check_library.py:223 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/check_library.py:229 msgid "Copy &to clipboard" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/check_library.py:230 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/check_library.py:236 msgid "Delete marked files (checked subitems)" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/check_library.py:236 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/check_library.py:242 msgid "Fix marked sections (checked fixable items)" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/check_library.py:246 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/check_library.py:252 msgid "Names to ignore:" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/check_library.py:251 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/check_library.py:257 msgid "" "Enter comma-separated standard file name wildcards, such as synctoy*.dat" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/check_library.py:254 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/check_library.py:260 msgid "Extensions to ignore" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/check_library.py:259 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/check_library.py:265 msgid "" "Enter comma-separated extensions without a leading dot. Used only in book " "folders" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/check_library.py:308 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/check_library.py:314 msgid "(fixable)" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/check_library.py:331 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/check_library.py:337 msgid "Path from library" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/check_library.py:331 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/check_library.py:337 #: /home/kovid/work/calibre/src/calibre/gui2/viewer/bookmarkmanager.py:89 -#: /home/kovid/work/calibre/src/calibre/library/server/browse.py:253 +#: /home/kovid/work/calibre/src/calibre/library/server/browse.py:256 msgid "Name" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/check_library.py:360 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/check_library.py:366 msgid "" "The marked files and folders will be permanently deleted. Are you " "sure?" @@ -7210,7 +7468,7 @@ msgstr "" #: /home/kovid/work/calibre/src/calibre/gui2/dialogs/choose_format_device_ui.py:49 #: /home/kovid/work/calibre/src/calibre/gui2/dialogs/delete_matching_from_device.py:76 -#: /home/kovid/work/calibre/src/calibre/gui2/library/models.py:1169 +#: /home/kovid/work/calibre/src/calibre/gui2/store/mobileread/models.py:23 msgid "Format" msgstr "" @@ -7301,6 +7559,20 @@ msgstr "" msgid "&Move current library to new location" msgstr "" +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/choose_plugin_toolbars.py:23 +msgid "Add \"%s\" to toolbars or menus" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/choose_plugin_toolbars.py:29 +msgid "Select the toolbars and/or menus to add %s to:" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/choose_plugin_toolbars.py:45 +msgid "" +"You can also customise the plugin locations using Preferences -> " +"Customise the toolbar" +msgstr "" + #: /home/kovid/work/calibre/src/calibre/gui2/dialogs/comicconf.py:33 msgid "Set defaults for conversion of comics (CBR/CBZ files)" msgstr "" @@ -7311,12 +7583,13 @@ msgstr "" #: /home/kovid/work/calibre/src/calibre/gui2/dialogs/comicconf_ui.py:97 #: /home/kovid/work/calibre/src/calibre/gui2/dialogs/search_ui.py:211 -#: /home/kovid/work/calibre/src/calibre/gui2/metadata/basic_widgets.py:61 +#: /home/kovid/work/calibre/src/calibre/gui2/metadata/basic_widgets.py:73 +#: /home/kovid/work/calibre/src/calibre/gui2/store/search/adv_search_builder_ui.py:189 msgid "&Title:" msgstr "" #: /home/kovid/work/calibre/src/calibre/gui2/dialogs/comicconf_ui.py:98 -#: /home/kovid/work/calibre/src/calibre/gui2/metadata/basic_widgets.py:155 +#: /home/kovid/work/calibre/src/calibre/gui2/metadata/basic_widgets.py:167 msgid "&Author(s):" msgstr "" @@ -7331,8 +7604,8 @@ msgstr "" #: /home/kovid/work/calibre/src/calibre/gui2/dialogs/comments_dialog.py:25 #: /home/kovid/work/calibre/src/calibre/gui2/dialogs/template_dialog.py:31 -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/tweak_epub_ui.py:60 -#: /home/kovid/work/calibre/src/calibre/gui2/preferences/main.py:226 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/tweak_epub_ui.py:65 +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/main.py:233 msgid "&Cancel" msgstr "" @@ -7341,22 +7614,22 @@ msgstr "" msgid "Edit Comments" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/confirm_delete_location_ui.py:62 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/confirm_delete_location_ui.py:76 msgid "Where do you want to delete from?" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/confirm_delete_location_ui.py:63 -#: /home/kovid/work/calibre/src/calibre/gui2/layout.py:63 -#: /home/kovid/work/calibre/src/calibre/library/server/opds.py:230 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/confirm_delete_location_ui.py:77 +#: /home/kovid/work/calibre/src/calibre/gui2/layout.py:68 +#: /home/kovid/work/calibre/src/calibre/library/server/opds.py:228 msgid "Library" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/confirm_delete_location_ui.py:64 -#: /home/kovid/work/calibre/src/calibre/gui2/layout.py:65 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/confirm_delete_location_ui.py:78 +#: /home/kovid/work/calibre/src/calibre/gui2/layout.py:70 msgid "Device" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/confirm_delete_location_ui.py:65 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/confirm_delete_location_ui.py:79 msgid "Library and Device" msgstr "" @@ -7379,11 +7652,12 @@ msgid "Location" msgstr "" #: /home/kovid/work/calibre/src/calibre/gui2/dialogs/delete_matching_from_device.py:76 -#: /home/kovid/work/calibre/src/calibre/gui2/library/models.py:69 +#: /home/kovid/work/calibre/src/calibre/gui2/library/models.py:67 #: /home/kovid/work/calibre/src/calibre/gui2/library/models.py:979 -#: /home/kovid/work/calibre/src/calibre/gui2/preferences/create_custom_column.py:33 -#: /home/kovid/work/calibre/src/calibre/library/field_metadata.py:295 -#: /home/kovid/work/calibre/src/calibre/library/server/opds.py:589 +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/create_custom_column.py:32 +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/create_custom_column.py:73 +#: /home/kovid/work/calibre/src/calibre/library/field_metadata.py:321 +#: /home/kovid/work/calibre/src/calibre/library/server/opds.py:573 msgid "Date" msgstr "" @@ -7399,130 +7673,120 @@ msgstr "" msgid "" "

    This book is locked by DRM. To learn more about DRM and why you " "cannot read or convert this book in calibre, \n" -"click here." +" click " +"here.

    A large number of recent, DRM free releases are \n" +" available at Open " +"Books." msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/edit_authors_dialog.py:35 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/edit_authors_dialog.py:43 msgid "Author sort" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/edit_authors_dialog.py:117 -#: /home/kovid/work/calibre/src/calibre/gui2/tag_view.py:916 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/edit_authors_dialog.py:104 +#: /home/kovid/work/calibre/src/calibre/gui2/lrf_renderer/main.py:160 +#: /home/kovid/work/calibre/src/calibre/gui2/metadata/single_download.py:471 +#: /home/kovid/work/calibre/src/calibre/gui2/viewer/main.py:503 +msgid "No matches found" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/edit_authors_dialog.py:120 +#: /home/kovid/work/calibre/src/calibre/gui2/widgets.py:419 +msgid "Change Case" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/edit_authors_dialog.py:121 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk.py:261 +#: /home/kovid/work/calibre/src/calibre/gui2/widgets.py:420 +msgid "Upper Case" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/edit_authors_dialog.py:122 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk.py:260 +#: /home/kovid/work/calibre/src/calibre/gui2/widgets.py:421 +msgid "Lower Case" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/edit_authors_dialog.py:123 +#: /home/kovid/work/calibre/src/calibre/gui2/widgets.py:422 +msgid "Swap Case" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/edit_authors_dialog.py:124 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk.py:262 +#: /home/kovid/work/calibre/src/calibre/gui2/widgets.py:423 +msgid "Title Case" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/edit_authors_dialog.py:125 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk.py:263 +#: /home/kovid/work/calibre/src/calibre/gui2/widgets.py:424 +msgid "Capitalize" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/edit_authors_dialog.py:141 +msgid "Copy to author sort" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/edit_authors_dialog.py:144 +msgid "Copy to author" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/edit_authors_dialog.py:271 +#: /home/kovid/work/calibre/src/calibre/gui2/tag_view.py:1439 msgid "Invalid author name" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/edit_authors_dialog.py:118 -#: /home/kovid/work/calibre/src/calibre/gui2/tag_view.py:917 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/edit_authors_dialog.py:272 +#: /home/kovid/work/calibre/src/calibre/gui2/tag_view.py:1440 msgid "Author names cannot contain & characters." msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/edit_authors_dialog_ui.py:71 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/edit_authors_dialog_ui.py:88 +#: /home/kovid/work/calibre/src/calibre/gui2/metadata/single.py:120 msgid "Manage authors" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/edit_authors_dialog_ui.py:72 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/edit_authors_dialog_ui.py:89 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:597 +msgid "&Search for:" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/edit_authors_dialog_ui.py:90 +#: /home/kovid/work/calibre/src/calibre/gui2/tag_view.py:2105 +msgid "F&ind" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/edit_authors_dialog_ui.py:91 msgid "Sort by author" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/edit_authors_dialog_ui.py:73 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/edit_authors_dialog_ui.py:92 msgid "Sort by author sort" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/edit_authors_dialog_ui.py:74 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/edit_authors_dialog_ui.py:93 msgid "" -"Reset all the author sort values to a value automatically generated from the " -"author. Exactly how this value is automatically generated can be controlled " -"via Preferences->Advanced->Tweaks" +"Reset all the author sort values to a value automatically\n" +"generated from the author. Exactly how this value is automatically\n" +"generated can be controlled via Preferences->Advanced->Tweaks" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/edit_authors_dialog_ui.py:75 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/edit_authors_dialog_ui.py:96 msgid "Recalculate all author sort values" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/fetch_metadata.py:62 -msgid "Author Sort" -msgstr "" - -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/fetch_metadata.py:64 -msgid "ISBN" -msgstr "" - -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/fetch_metadata.py:66 -msgid "Has Cover" -msgstr "" - -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/fetch_metadata.py:67 -msgid "Has Summary" -msgstr "" - -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/fetch_metadata.py:192 -msgid "Finding metadata..." -msgstr "" - -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/fetch_metadata.py:206 -msgid "Could not find metadata" -msgstr "" - -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/fetch_metadata.py:207 -msgid "The metadata download seems to have stalled. Try again later." -msgstr "" - -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/fetch_metadata.py:216 -msgid "Warning" -msgstr "" - -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/fetch_metadata.py:217 -msgid "Could not fetch metadata from:" -msgstr "" - -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/fetch_metadata.py:221 -msgid "No metadata found" -msgstr "" - -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/fetch_metadata.py:222 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/edit_authors_dialog_ui.py:97 msgid "" -"No metadata found, try adjusting the title and author and/or removing the " -"ISBN." +"Copy author sort to author for every author. You typically use this button\n" +"after changing Preferences->Advanced->Tweaks->Author sort name algorithm" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/fetch_metadata_ui.py:93 -msgid "Fetch metadata" +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/edit_authors_dialog_ui.py:99 +msgid "Copy all author sort values to author" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/fetch_metadata_ui.py:94 -msgid "" -"

    calibre can find metadata for your books from two locations: Google " -"Books and isbndb.com.

    To use isbndb.com you must sign up for a " -"free account and enter your access key " -"below." -msgstr "" - -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/fetch_metadata_ui.py:95 -msgid "&Access Key:" -msgstr "" - -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/fetch_metadata_ui.py:96 -msgid "Fetch" -msgstr "" - -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/fetch_metadata_ui.py:97 -msgid "Matches" -msgstr "" - -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/fetch_metadata_ui.py:98 -msgid "" -"Select the book that most closely matches your copy from the list below" -msgstr "" - -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/fetch_metadata_ui.py:99 -msgid "Overwrite author and title with author and title of selected book" -msgstr "" - -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/fetch_metadata_ui.py:100 -msgid "Download &social metadata (tags/rating/etc.) for the selected book" -msgstr "" - -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/job_view_ui.py:42 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/job_view_ui.py:45 msgid "Details of job" msgstr "" @@ -7542,27 +7806,39 @@ msgstr "" msgid "Stop &all non device jobs" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/message_box.py:43 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/message_box.py:49 msgid "&Copy to clipboard" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/message_box.py:48 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/message_box.py:53 msgid "Show &details" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/message_box.py:49 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/message_box.py:54 msgid "Hide &details" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/message_box.py:53 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/message_box.py:58 msgid "Show detailed information about this error" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/message_box.py:93 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/message_box.py:98 #: /home/kovid/work/calibre/src/calibre/gui2/wizard/__init__.py:525 msgid "Copied" msgstr "" +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/message_box.py:135 +#: /home/kovid/work/calibre/src/calibre/gui2/metadata/single_download.py:770 +#: /home/kovid/work/calibre/src/calibre/gui2/viewer/main_ui.py:205 +msgid "Copy to clipboard" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/message_box.py:179 +#: /home/kovid/work/calibre/src/calibre/gui2/metadata/single_download.py:831 +#: /home/kovid/work/calibre/src/calibre/gui2/metadata/single_download.py:922 +msgid "View log" +msgstr "" + #: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk.py:58 msgid "Title/Author" msgstr "" @@ -7572,6 +7848,7 @@ msgid "Standard metadata" msgstr "" #: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk.py:60 +#: /home/kovid/work/calibre/src/calibre/gui2/metadata/single.py:852 msgid "Custom metadata" msgstr "" @@ -7584,26 +7861,6 @@ msgstr "" msgid "Working" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk.py:260 -#: /home/kovid/work/calibre/src/calibre/gui2/widgets.py:384 -msgid "Lower Case" -msgstr "" - -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk.py:261 -#: /home/kovid/work/calibre/src/calibre/gui2/widgets.py:383 -msgid "Upper Case" -msgstr "" - -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk.py:262 -#: /home/kovid/work/calibre/src/calibre/gui2/widgets.py:386 -msgid "Title Case" -msgstr "" - -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk.py:263 -#: /home/kovid/work/calibre/src/calibre/gui2/widgets.py:387 -msgid "Capitalize" -msgstr "" - #: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk.py:266 msgid "Character match" msgstr "" @@ -7634,11 +7891,15 @@ msgid "" "cannot be canceled or undone" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk.py:381 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk.py:382 msgid "Book %d:" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk.py:396 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk.py:400 +msgid "Enter an identifier type" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk.py:405 msgid "" "You can destroy your library using this feature. Changes are " "permanent. There is no undo function. You are strongly encouraged to back up " @@ -7646,7 +7907,7 @@ msgid "" "character matching or regular expressions. " msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk.py:404 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk.py:413 msgid "" "In character mode, the field is searched for the entered search text. The " "text is replaced by the specified replacement text everywhere it is found in " @@ -7656,7 +7917,7 @@ msgid "" "text will match both upper- and lower-case letters" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk.py:415 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk.py:424 msgid "" "In regular expression mode, the search text is an arbitrary python-" "compatible regular expression. The replacement text can contain " @@ -7671,145 +7932,143 @@ msgid "" "function." msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk.py:489 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk.py:502 msgid "S/R TEMPLATE ERROR" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk.py:616 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk.py:648 msgid "You must specify a destination when source is a composite field" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk.py:715 -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk.py:723 -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk.py:844 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk.py:654 +msgid "You must specify a destination identifier type" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk.py:761 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk.py:780 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk.py:907 msgid "Search/replace invalid" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk.py:716 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk.py:762 msgid "" "Authors cannot be set to the empty string. Book title %s not processed" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk.py:724 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk.py:781 msgid "Title cannot be set to the empty string. Book title %s not processed" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk.py:845 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk.py:908 msgid "Search pattern is invalid: %s" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk.py:897 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk.py:960 msgid "" "Applying changes to %d books.\n" "Phase {0} {1}%%." msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk.py:927 -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:561 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk.py:990 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:587 msgid "Delete saved search/replace" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk.py:928 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk.py:991 msgid "The selected saved search/replace will be deleted. Are you sure?" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk.py:945 -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk.py:953 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk.py:1008 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk.py:1016 msgid "Save search/replace" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk.py:946 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk.py:1009 msgid "Search/replace name:" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk.py:954 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk.py:1017 msgid "" "That saved search/replace already exists and will be overwritten. Are you " "sure?" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:498 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:524 msgid "Edit Meta information" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:500 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:526 msgid "A&utomatically set author sort" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:501 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:527 msgid "&Swap title and author" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:502 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:528 msgid "Author s&ort: " msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:503 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:529 msgid "" "Specify how the author(s) of this book should be sorted. For example Charles " "Dickens should be sorted as Dickens, Charles." msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:504 -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single_ui.py:424 -#: /home/kovid/work/calibre/src/calibre/gui2/metadata/basic_widgets.py:786 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:530 +#: /home/kovid/work/calibre/src/calibre/gui2/metadata/basic_widgets.py:867 msgid "&Rating:" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:505 -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:506 -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single_ui.py:425 -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single_ui.py:426 -#: /home/kovid/work/calibre/src/calibre/gui2/metadata/basic_widgets.py:787 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:531 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:532 +#: /home/kovid/work/calibre/src/calibre/gui2/metadata/basic_widgets.py:868 msgid "Rating of this book. 0-5 stars" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:507 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:533 msgid "No change" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:508 -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single_ui.py:427 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:534 msgid " stars" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:510 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:536 msgid "Add ta&gs: " msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:512 -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:513 -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single_ui.py:431 -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single_ui.py:432 -#: /home/kovid/work/calibre/src/calibre/gui2/metadata/single.py:140 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:538 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:539 +#: /home/kovid/work/calibre/src/calibre/gui2/metadata/single.py:166 msgid "Open Tag Editor" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:514 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:540 msgid "&Remove tags:" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:515 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:541 msgid "Comma separated list of tags to remove from the books. " msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:516 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:542 msgid "Check this box to remove all tags from the books." msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:517 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:543 msgid "Remove &all" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:521 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:547 msgid "If checked, the series will be cleared" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:522 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:548 msgid "&Clear series" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:523 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:549 msgid "" "If not checked, the series number for the books will be set to 1.\n" "If checked, selected books will be automatically numbered, in the order\n" @@ -7817,192 +8076,194 @@ msgid "" "Book A will have series number 1 and Book B series number 2." msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:527 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:553 msgid "&Automatically number books in this series" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:528 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:554 msgid "" "Series will normally be renumbered from the highest number in the database\n" "for that series. Checking this box will tell calibre to start numbering\n" "from the value in the box" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:531 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:557 msgid "&Force numbers to start with:" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:532 -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single_ui.py:440 -#: /home/kovid/work/calibre/src/calibre/gui2/metadata/basic_widgets.py:978 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:558 +#: /home/kovid/work/calibre/src/calibre/gui2/metadata/basic_widgets.py:1101 msgid "&Date:" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:533 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:559 msgid "d MMM yyyy" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:535 -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:540 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:561 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:566 msgid "&Apply date" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:536 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:562 msgid "&Published:" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:538 -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single_ui.py:444 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:564 msgid "Clear published date" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:541 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:567 msgid "Remove &format:" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:542 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:568 msgid "" "Force the title to be in title case. If both this and swap authors are " "checked,\n" "title and author are swapped before the title case is set" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:544 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:570 msgid "Change title to title &case" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:545 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:571 msgid "" "Update title sort based on the current title. This will be applied only " "after other changes to title." msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:546 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:572 msgid "Update &title sort" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:547 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:573 msgid "" "Remove stored conversion settings for the selected books.\n" "\n" "Future conversion of these books will use the default settings." msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:550 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:576 msgid "Remove &stored conversion settings for the selected books" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:551 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:577 msgid "Change &cover" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:552 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:578 msgid "&Generate default cover" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:553 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:579 msgid "&Remove cover" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:554 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:580 msgid "Set from &ebook file(s)" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:555 -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single_ui.py:465 -#: /home/kovid/work/calibre/src/calibre/gui2/metadata/single.py:392 -#: /home/kovid/work/calibre/src/calibre/gui2/metadata/single.py:521 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:581 +#: /home/kovid/work/calibre/src/calibre/gui2/metadata/single.py:495 +#: /home/kovid/work/calibre/src/calibre/gui2/metadata/single.py:659 msgid "&Basic metadata" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:556 -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single_ui.py:466 -#: /home/kovid/work/calibre/src/calibre/gui2/metadata/single.py:399 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:582 +#: /home/kovid/work/calibre/src/calibre/gui2/metadata/single.py:502 msgid "&Custom metadata" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:557 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:583 msgid "Load searc&h/replace:" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:558 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:584 msgid "Select saved search/replace to load." msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:559 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:585 msgid "Save current search/replace" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:560 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:586 msgid "Sa&ve" 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:588 #: /home/kovid/work/calibre/src/calibre/gui2/viewer/bookmarkmanager_ui.py:64 msgid "Delete" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:563 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:589 msgid "Search &field:" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:564 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:590 msgid "The name of the field that you want to search" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:565 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:591 msgid "Search &mode:" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:566 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:592 msgid "" "Choose whether to use basic text matching or advanced regular expression " "matching" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:567 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:593 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:615 +msgid "Identifier type:" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:594 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:616 +msgid "Choose which identifier type to operate upon" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:595 msgid "Te&mplate:" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:568 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:596 msgid "Enter a template to be used as the source for the search/replace" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:569 -msgid "&Search for:" -msgstr "" - -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:570 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:598 msgid "" "Enter the what you are looking for, either plain text or a regular " "expression, depending on the mode" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:571 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:599 msgid "" "Check this box if the search string must match exactly upper and lower case. " "Uncheck it if case is to be ignored" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:572 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:600 msgid "Cas&e sensitive" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:573 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:601 msgid "&Replace with:" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:574 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:602 msgid "" "The replacement text. The matched search text will be replaced with this " "string" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:575 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:603 msgid "&Apply function after replace:" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:576 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:604 msgid "" "Specify how the text is to be processed after matching and replacement. In " "character mode, the entire\n" @@ -8010,25 +8271,25 @@ msgid "" "processed" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:578 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:606 msgid "&Destination field:" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:579 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:607 msgid "" "The field that the text will be put into after all replacements.\n" "If blank, the source field is used if the field is modifiable" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:581 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:609 msgid "M&ode:" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:582 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:610 msgid "Specify how the text should be copied into the destination." msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:583 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:611 msgid "" "Specifies whether result items should be split into multiple values or\n" "left as single values. This option has the most effect when the source field " @@ -8036,429 +8297,66 @@ msgid "" "not multiple and the destination field is multiple" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:586 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:614 msgid "Split &result" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:587 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:617 msgid "For multiple-valued fields, sho&w" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:588 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:618 msgid "values starting a&t" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:589 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:619 msgid "with values separated b&y" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:590 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:620 msgid "" "Used when displaying test results to separate values in multiple-valued " "fields" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:591 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:621 msgid "Test text" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:592 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:622 msgid "Test result" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:593 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:623 msgid "Your test:" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:594 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:624 msgid "&Search and replace" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single.py:98 -#: /home/kovid/work/calibre/src/calibre/gui2/metadata/basic_widgets.py:429 -msgid "Last modified: %s" -msgstr "" - -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single.py:122 -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single.py:128 -#: /home/kovid/work/calibre/src/calibre/gui2/metadata/single.py:252 -#: /home/kovid/work/calibre/src/calibre/gui2/metadata/single.py:259 -msgid "Could not read cover" -msgstr "" - -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single.py:123 -#: /home/kovid/work/calibre/src/calibre/gui2/metadata/single.py:253 -msgid "Could not read cover from %s format" -msgstr "" - -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single.py:129 -#: /home/kovid/work/calibre/src/calibre/gui2/metadata/single.py:260 -msgid "The cover in the %s format is invalid" -msgstr "" - -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single.py:158 -#: /home/kovid/work/calibre/src/calibre/gui2/metadata/basic_widgets.py:746 -msgid "Cover size: %dx%d pixels" -msgstr "" - -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single.py:195 -#: /home/kovid/work/calibre/src/calibre/gui2/metadata/basic_widgets.py:670 -msgid "Not a valid picture" -msgstr "" - -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single.py:214 -#: /home/kovid/work/calibre/src/calibre/gui2/metadata/basic_widgets.py:697 -msgid "Specify title and author" -msgstr "" - -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single.py:215 -#: /home/kovid/work/calibre/src/calibre/gui2/metadata/basic_widgets.py:698 -msgid "You must specify a title and author before generating a cover" -msgstr "" - -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single.py:246 -msgid "Downloading cover..." -msgstr "" - -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single.py:262 -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single.py:267 -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single.py:273 -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single.py:278 -msgid "Cannot fetch cover" -msgstr "" - -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single.py:263 -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single.py:274 -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single.py:279 -msgid "Could not fetch cover.
    " -msgstr "" - -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single.py:264 -msgid "The download timed out." -msgstr "" - -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single.py:268 -msgid "Could not find cover for this book. Try specifying the ISBN first." -msgstr "" - -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single.py:280 -msgid "" -"For the error message from each cover source, click Show details below." -msgstr "" - -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single.py:287 -msgid "Bad cover" -msgstr "" - -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single.py:288 -msgid "The cover is not a valid picture" -msgstr "" - -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single.py:307 -#: /home/kovid/work/calibre/src/calibre/gui2/metadata/basic_widgets.py:527 -msgid "Choose formats for " -msgstr "" - -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single.py:338 -#: /home/kovid/work/calibre/src/calibre/gui2/metadata/basic_widgets.py:559 -msgid "No permission" -msgstr "" - -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single.py:339 -#: /home/kovid/work/calibre/src/calibre/gui2/metadata/basic_widgets.py:560 -msgid "You do not have permission to read the following files:" -msgstr "" - -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single.py:366 -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single.py:367 -#: /home/kovid/work/calibre/src/calibre/gui2/metadata/basic_widgets.py:591 -#: /home/kovid/work/calibre/src/calibre/gui2/metadata/basic_widgets.py:592 -msgid "No format selected" -msgstr "" - -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single.py:378 -#: /home/kovid/work/calibre/src/calibre/gui2/metadata/basic_widgets.py:603 -msgid "Could not read metadata" -msgstr "" - -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single.py:379 -#: /home/kovid/work/calibre/src/calibre/gui2/metadata/basic_widgets.py:604 -msgid "Could not read metadata from %s format" -msgstr "" - -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single.py:453 -#: /home/kovid/work/calibre/src/calibre/gui2/metadata/basic_widgets.py:229 -msgid "" -" The green color indicates that the current author sort matches the current " -"author" -msgstr "" - -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single.py:456 -#: /home/kovid/work/calibre/src/calibre/gui2/metadata/basic_widgets.py:232 -msgid "" -" The red color indicates that the current author sort does not match the " -"current author. No action is required if this is what you want." -msgstr "" - -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single.py:463 -#: /home/kovid/work/calibre/src/calibre/gui2/metadata/basic_widgets.py:119 -msgid "" -" The green color indicates that the current title sort matches the current " -"title" -msgstr "" - -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single.py:466 -#: /home/kovid/work/calibre/src/calibre/gui2/metadata/basic_widgets.py:122 -msgid "" -" The red color warns that the current title sort does not match the current " -"title. No action is required if this is what you want." -msgstr "" - -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single.py:472 -#: /home/kovid/work/calibre/src/calibre/gui2/metadata/single.py:49 -#: /home/kovid/work/calibre/src/calibre/library/server/browse.py:102 -#: /home/kovid/work/calibre/src/calibre/web/feeds/templates.py:221 -#: /home/kovid/work/calibre/src/calibre/web/feeds/templates.py:384 -msgid "Previous" -msgstr "" - -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single.py:475 -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single.py:483 -#: /home/kovid/work/calibre/src/calibre/gui2/metadata/single.py:358 -#: /home/kovid/work/calibre/src/calibre/gui2/metadata/single.py:362 -msgid "Save changes and edit the metadata of %s" -msgstr "" - -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single.py:480 -#: /home/kovid/work/calibre/src/calibre/gui2/metadata/single.py:46 -#: /home/kovid/work/calibre/src/calibre/library/server/browse.py:103 -#: /home/kovid/work/calibre/src/calibre/web/feeds/templates.py:211 -#: /home/kovid/work/calibre/src/calibre/web/feeds/templates.py:401 -msgid "Next" -msgstr "" - -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single.py:688 -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single.py:693 -#: /home/kovid/work/calibre/src/calibre/gui2/metadata/basic_widgets.py:913 -msgid "This ISBN number is valid" -msgstr "" - -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single.py:696 -#: /home/kovid/work/calibre/src/calibre/gui2/metadata/basic_widgets.py:920 -msgid "This ISBN number is invalid" -msgstr "" - -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single.py:781 -#: /home/kovid/work/calibre/src/calibre/gui2/metadata/basic_widgets.py:862 -msgid "Tags changed" -msgstr "" - -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single.py:782 -#: /home/kovid/work/calibre/src/calibre/gui2/metadata/basic_widgets.py:863 -msgid "" -"You have changed the tags. In order to use the tags editor, you must either " -"discard or apply these changes. Apply changes?" -msgstr "" - -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single.py:817 -msgid "Timed out" -msgstr "" - -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single.py:818 -msgid "" -"The download of social metadata timed out, the servers are probably busy. " -"Try again later." -msgstr "" - -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single.py:825 -msgid "There were errors" -msgstr "" - -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single.py:826 -msgid "There were errors downloading social metadata" -msgstr "" - -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single.py:860 -msgid "Cannot fetch metadata" -msgstr "" - -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single.py:861 -msgid "You must specify at least one of ISBN, Title, Authors or Publisher" -msgstr "" - -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single.py:959 -#: /home/kovid/work/calibre/src/calibre/gui2/metadata/single.py:307 -msgid "Permission denied" -msgstr "" - -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single.py:960 -#: /home/kovid/work/calibre/src/calibre/gui2/metadata/single.py:308 -msgid "Could not open %s. Is it being used by another program?" -msgstr "" - -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single_ui.py:406 -msgid "Edit Meta Information" -msgstr "" - -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single_ui.py:407 -msgid "Meta information" -msgstr "" - -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single_ui.py:410 -#: /home/kovid/work/calibre/src/calibre/gui2/metadata/single.py:89 -msgid "" -"Automatically create the title sort entry based on the current title entry.\n" -"Using this button to create title sort will change title sort from red to " -"green." -msgstr "" - -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single_ui.py:413 -#: /home/kovid/work/calibre/src/calibre/gui2/metadata/single.py:111 -msgid "Swap the author and title" -msgstr "" - -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single_ui.py:415 -#: /home/kovid/work/calibre/src/calibre/gui2/metadata/single.py:100 -msgid "" -"Automatically create the author sort entry based on the current author " -"entry.\n" -"Using this button to create author sort will change author sort from red to " -"green." -msgstr "" - -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single_ui.py:418 -msgid "Title &sort: " -msgstr "" - -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single_ui.py:419 -#: /home/kovid/work/calibre/src/calibre/gui2/metadata/basic_widgets.py:109 -msgid "" -"Specify how this book should be sorted when by title. For example, The " -"Exorcist might be sorted as Exorcist, The." -msgstr "" - -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single_ui.py:421 -msgid "Author S&ort: " -msgstr "" - -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single_ui.py:422 -#: /home/kovid/work/calibre/src/calibre/gui2/metadata/basic_widgets.py:215 -msgid "" -"Specify how the author(s) of this book should be sorted. For example Charles " -"Dickens should be sorted as Dickens, Charles.\n" -"If the box is colored green, then text matches the individual author's sort " -"strings. If it is colored red, then the authors and this text do not match." -msgstr "" - -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single_ui.py:436 -#: /home/kovid/work/calibre/src/calibre/gui2/metadata/single.py:118 -msgid "Remove unused series (Series that have no books)" -msgstr "" - -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single_ui.py:439 -#: /home/kovid/work/calibre/src/calibre/gui2/metadata/basic_widgets.py:886 -msgid "IS&BN:" -msgstr "" - -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single_ui.py:441 -msgid "dd MMM yyyy" -msgstr "" - -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single_ui.py:442 -#: /home/kovid/work/calibre/src/calibre/gui2/metadata/basic_widgets.py:1029 -msgid "Publishe&d:" -msgstr "" - -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single_ui.py:445 -#: /home/kovid/work/calibre/src/calibre/gui2/metadata/single.py:156 -msgid "&Fetch metadata from server" -msgstr "" - -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single_ui.py:448 -#: /home/kovid/work/calibre/src/calibre/gui2/metadata/basic_widgets.py:621 -msgid "&Browse" -msgstr "" - -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single_ui.py:449 -msgid "Remove border (if any) from cover" -msgstr "" - -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single_ui.py:450 -#: /home/kovid/work/calibre/src/calibre/gui2/metadata/basic_widgets.py:623 -msgid "T&rim" -msgstr "" - -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single_ui.py:451 -msgid "Reset cover to default" -msgstr "" - -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single_ui.py:452 -#: /home/kovid/work/calibre/src/calibre/gui2/metadata/basic_widgets.py:625 -msgid "&Remove" -msgstr "" - -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single_ui.py:453 -#: /home/kovid/work/calibre/src/calibre/gui2/metadata/basic_widgets.py:631 -msgid "Download co&ver" -msgstr "" - -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single_ui.py:454 -msgid "Generate a default cover based on the title and author" -msgstr "" - -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single_ui.py:455 -#: /home/kovid/work/calibre/src/calibre/gui2/metadata/basic_widgets.py:632 -msgid "&Generate cover" -msgstr "" - -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single_ui.py:456 -msgid "Available Formats" -msgstr "" - -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single_ui.py:457 -msgid "Add a new format for this book to the database" -msgstr "" - -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single_ui.py:459 -msgid "Remove the selected formats for this book from the database." -msgstr "" - -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single_ui.py:461 -#: /home/kovid/work/calibre/src/calibre/gui2/metadata/basic_widgets.py:446 -msgid "Set the cover for the book from the selected format" -msgstr "" - -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single_ui.py:463 -msgid "Update metadata from the metadata in the selected format" -msgstr "" - -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single_ui.py:464 -#: /home/kovid/work/calibre/src/calibre/gui2/metadata/single.py:580 -msgid "&Comments" -msgstr "" - #: /home/kovid/work/calibre/src/calibre/gui2/dialogs/password_ui.py:61 msgid "Password needed" msgstr "" #: /home/kovid/work/calibre/src/calibre/gui2/dialogs/password_ui.py:63 -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/scheduler_ui.py:217 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/scheduler_ui.py:205 #: /home/kovid/work/calibre/src/calibre/gui2/preferences/server_ui.py:125 #: /home/kovid/work/calibre/src/calibre/gui2/wizard/send_email_ui.py:133 msgid "&Username:" msgstr "" #: /home/kovid/work/calibre/src/calibre/gui2/dialogs/password_ui.py:64 -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/scheduler_ui.py:218 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/scheduler_ui.py:206 #: /home/kovid/work/calibre/src/calibre/gui2/preferences/server_ui.py:126 #: /home/kovid/work/calibre/src/calibre/gui2/wizard/send_email_ui.py:135 msgid "&Password:" msgstr "" #: /home/kovid/work/calibre/src/calibre/gui2/dialogs/password_ui.py:65 -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/scheduler_ui.py:219 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/scheduler_ui.py:207 #: /home/kovid/work/calibre/src/calibre/gui2/preferences/server_ui.py:130 -#: /home/kovid/work/calibre/src/calibre/gui2/wizard/send_email.py:172 +#: /home/kovid/work/calibre/src/calibre/gui2/wizard/send_email.py:173 msgid "&Show password" msgstr "" @@ -8501,210 +8399,295 @@ msgstr "" msgid "Restoring database was successful" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/saved_search_editor.py:55 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/saved_search_editor.py:48 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/saved_search_editor.py:75 +msgid "Saved search already exists" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/saved_search_editor.py:49 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/saved_search_editor.py:76 +msgid "The saved search %s already exists, perhaps with different case" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/saved_search_editor.py:62 msgid "" "The current saved search will be permanently deleted. Are you sure?" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/saved_search_editor_ui.py:88 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/saved_search_editor_ui.py:94 msgid "Saved Search Editor" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/saved_search_editor_ui.py:89 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/saved_search_editor_ui.py:95 msgid "Saved Search: " msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/saved_search_editor_ui.py:90 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/saved_search_editor_ui.py:96 msgid "Select a saved search to edit" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/saved_search_editor_ui.py:91 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/saved_search_editor_ui.py:97 msgid "Delete this selected saved search" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/saved_search_editor_ui.py:93 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/saved_search_editor_ui.py:99 msgid "Enter a new saved search name." msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/saved_search_editor_ui.py:94 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/saved_search_editor_ui.py:100 msgid "Add the new saved search" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/saved_search_editor_ui.py:96 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/saved_search_editor_ui.py:102 +msgid "Rename the current search to what is in the box" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/saved_search_editor_ui.py:104 msgid "Change the contents of the saved search" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/scheduler.py:35 -msgid "&Search:" +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/scheduler.py:42 +msgid "" +" Download this periodical every week on the specified days " +"after\n" +" the specified time. For example, if you choose: Monday " +"after\n" +" 9:00 AM, then the periodical will be download every Monday " +"as\n" +" soon after 9:00 AM as possible.\n" +" " msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/scheduler.py:133 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/scheduler.py:61 +msgid "&Download after:" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/scheduler.py:91 +msgid "" +" Download this periodical every month, on the specified " +"days.\n" +" The download will happen as soon after the specified time " +"as\n" +" possible on the specified days of each month. For example,\n" +" if you choose the 1st and the 15th after 9:00 AM, the\n" +" periodical will be downloaded on the 1st and 15th of every\n" +" month, as soon after 9:00 AM as possible.\n" +" " +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/scheduler.py:103 +msgid "&Days of the month:" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/scheduler.py:105 +msgid "Comma separated list of days of the month. For example: 1, 15" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/scheduler.py:109 +msgid "Download &after:" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/scheduler.py:142 +msgid "" +" Download this periodical every x days. For example, if you\n" +" choose 30 days, the periodical will be downloaded every 30\n" +" days. Note that you can set periods of less than a day, " +"like\n" +" 0.1 days to download a periodical more than once a day.\n" +" " +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/scheduler.py:151 +msgid "&Download every:" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/scheduler.py:154 +msgid "every hour" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/scheduler.py:157 +msgid "days" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/scheduler.py:161 +msgid "" +"Note: You can set intervals of less than a day, by typing the value manually." +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/scheduler.py:196 +msgid "%s news sources" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/scheduler.py:311 msgid "Need username and password" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/scheduler.py:134 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/scheduler.py:312 msgid "You must provide a username and/or password to use this news source." msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/scheduler.py:173 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/scheduler.py:346 msgid "Account" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/scheduler.py:174 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/scheduler.py:347 msgid "(optional)" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/scheduler.py:175 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/scheduler.py:348 msgid "(required)" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/scheduler.py:192 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/scheduler.py:365 msgid "Created by: " msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/scheduler.py:199 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/scheduler.py:372 msgid "Last downloaded: never" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/scheduler.py:214 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/scheduler.py:373 +msgid "never" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/scheduler.py:379 msgid "%d days, %d hours and %d minutes ago" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/scheduler.py:216 -msgid "Last downloaded" +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/scheduler.py:393 +msgid "Last downloaded:" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/scheduler.py:240 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/scheduler.py:421 #: /home/kovid/work/calibre/src/calibre/gui2/dialogs/scheduler_ui.py:197 msgid "Schedule news download" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/scheduler.py:243 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/scheduler.py:424 msgid "Add a custom news source" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/scheduler.py:248 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/scheduler.py:429 msgid "Download all scheduled new sources" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/scheduler.py:353 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/scheduler.py:534 msgid "No internet connection" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/scheduler.py:354 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/scheduler.py:535 msgid "Cannot download news as no internet connection is active" msgstr "" #: /home/kovid/work/calibre/src/calibre/gui2/dialogs/scheduler_ui.py:198 -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/user_profiles.py:321 -msgid "Recipes" +msgid "&Search:" msgstr "" #: /home/kovid/work/calibre/src/calibre/gui2/dialogs/scheduler_ui.py:199 -msgid "Download all scheduled recipes at once" -msgstr "" - -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/scheduler_ui.py:200 -msgid "Download &all scheduled" -msgstr "" - -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/scheduler_ui.py:201 msgid "blurb" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/scheduler_ui.py:202 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/scheduler_ui.py:200 msgid "&Schedule for download:" msgstr "" +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/scheduler_ui.py:201 +msgid "Days of week" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/scheduler_ui.py:202 +msgid "Days of month" +msgstr "" + #: /home/kovid/work/calibre/src/calibre/gui2/dialogs/scheduler_ui.py:203 -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/scheduler_ui.py:213 -msgid "Every " +msgid "Every x days" msgstr "" #: /home/kovid/work/calibre/src/calibre/gui2/dialogs/scheduler_ui.py:204 -msgid "day" -msgstr "" - -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/scheduler_ui.py:205 -msgid "Monday" -msgstr "" - -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/scheduler_ui.py:206 -msgid "Tuesday" -msgstr "" - -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/scheduler_ui.py:207 -msgid "Wednesday" +msgid "&Account" msgstr "" #: /home/kovid/work/calibre/src/calibre/gui2/dialogs/scheduler_ui.py:208 -msgid "Thursday" +msgid "For the scheduling to work, you must leave calibre running." msgstr "" #: /home/kovid/work/calibre/src/calibre/gui2/dialogs/scheduler_ui.py:209 -msgid "Friday" +msgid "&Schedule" msgstr "" #: /home/kovid/work/calibre/src/calibre/gui2/dialogs/scheduler_ui.py:210 -msgid "Saturday" +msgid "Add &title as tag" msgstr "" #: /home/kovid/work/calibre/src/calibre/gui2/dialogs/scheduler_ui.py:211 -msgid "Sunday" +msgid "&Extra tags:" msgstr "" #: /home/kovid/work/calibre/src/calibre/gui2/dialogs/scheduler_ui.py:212 -msgid "at" +msgid "" +"Maximum number of copies (issues) of this recipe to keep. Set to 0 to keep " +"all (disable)." +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/scheduler_ui.py:213 +msgid "&Keep at most:" msgstr "" #: /home/kovid/work/calibre/src/calibre/gui2/dialogs/scheduler_ui.py:214 msgid "" -"Interval at which to download this recipe. A value of zero means that the " -"recipe will be downloaded every hour." +"

    When set, this option will cause calibre to keep, at most, the specified " +"number of issues of this periodical. Every time a new issue is downloaded, " +"the oldest one is deleted, if the total is larger than this number.\n" +"

    Note that this feature only works if you have the option to add the title " +"as tag checked, above.\n" +"

    Also, the setting for deleting periodicals older than a number of days, " +"below, takes priority over this setting." msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/scheduler_ui.py:215 -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/scheduler_ui.py:227 -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/user_profiles_ui.py:268 -msgid " days" +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/scheduler_ui.py:217 +msgid "all issues" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/scheduler_ui.py:216 -msgid "&Account" +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/scheduler_ui.py:218 +msgid " issues" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/scheduler_ui.py:220 -msgid "For the scheduling to work, you must leave calibre running." -msgstr "" - -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/scheduler_ui.py:221 -msgid "&Schedule" -msgstr "" - -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/scheduler_ui.py:222 -msgid "Add &title as tag" -msgstr "" - -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/scheduler_ui.py:223 -msgid "&Extra tags:" -msgstr "" - -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/scheduler_ui.py:224 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/scheduler_ui.py:219 msgid "&Advanced" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/scheduler_ui.py:225 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/scheduler_ui.py:220 msgid "&Download now" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/scheduler_ui.py:226 -msgid "" -"Delete downloaded news older than the specified number of days. Set to zero " -"to disable." +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/scheduler_ui.py:221 +msgid "&Delete downloaded news older than:" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/scheduler_ui.py:228 -msgid "Delete downloaded news older than " +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/scheduler_ui.py:222 +msgid "" +"

    Delete downloaded news older than the specified number of days. Set to " +"zero to disable.\n" +"

    You can also control the maximum number of issues of a specific " +"periodical that are kept by clicking the Advanced tab for that periodical " +"above." +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/scheduler_ui.py:224 +msgid "never delete" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/scheduler_ui.py:225 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/user_profiles_ui.py:273 +msgid " days" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/scheduler_ui.py:226 +msgid "Download all scheduled news sources at once" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/scheduler_ui.py:227 +msgid "Download &all scheduled" msgstr "" #: /home/kovid/work/calibre/src/calibre/gui2/dialogs/search_item_ui.py:41 @@ -8726,70 +8709,85 @@ msgid "Negate" msgstr "" #: /home/kovid/work/calibre/src/calibre/gui2/dialogs/search_ui.py:198 +#: /home/kovid/work/calibre/src/calibre/gui2/store/search/adv_search_builder_ui.py:176 msgid "Advanced Search" msgstr "" #: /home/kovid/work/calibre/src/calibre/gui2/dialogs/search_ui.py:199 +#: /home/kovid/work/calibre/src/calibre/gui2/store/search/adv_search_builder_ui.py:177 msgid "&What kind of match to use:" msgstr "" #: /home/kovid/work/calibre/src/calibre/gui2/dialogs/search_ui.py:200 +#: /home/kovid/work/calibre/src/calibre/gui2/store/search/adv_search_builder_ui.py:178 msgid "Contains: the word or phrase matches anywhere in the metadata field" msgstr "" #: /home/kovid/work/calibre/src/calibre/gui2/dialogs/search_ui.py:201 +#: /home/kovid/work/calibre/src/calibre/gui2/store/search/adv_search_builder_ui.py:179 msgid "Equals: the word or phrase must match the entire metadata field" msgstr "" #: /home/kovid/work/calibre/src/calibre/gui2/dialogs/search_ui.py:202 +#: /home/kovid/work/calibre/src/calibre/gui2/store/search/adv_search_builder_ui.py:180 msgid "" "Regular expression: the expression must match anywhere in the metadata field" msgstr "" #: /home/kovid/work/calibre/src/calibre/gui2/dialogs/search_ui.py:203 +#: /home/kovid/work/calibre/src/calibre/gui2/store/search/adv_search_builder_ui.py:181 msgid "Find entries that have..." msgstr "" #: /home/kovid/work/calibre/src/calibre/gui2/dialogs/search_ui.py:204 +#: /home/kovid/work/calibre/src/calibre/gui2/store/search/adv_search_builder_ui.py:182 msgid "&All these words:" msgstr "" #: /home/kovid/work/calibre/src/calibre/gui2/dialogs/search_ui.py:205 +#: /home/kovid/work/calibre/src/calibre/gui2/store/search/adv_search_builder_ui.py:183 msgid "This exact &phrase:" msgstr "" #: /home/kovid/work/calibre/src/calibre/gui2/dialogs/search_ui.py:206 +#: /home/kovid/work/calibre/src/calibre/gui2/store/search/adv_search_builder_ui.py:184 msgid "&One or more of these words:" msgstr "" #: /home/kovid/work/calibre/src/calibre/gui2/dialogs/search_ui.py:207 +#: /home/kovid/work/calibre/src/calibre/gui2/store/search/adv_search_builder_ui.py:185 msgid "But dont show entries that have..." msgstr "" #: /home/kovid/work/calibre/src/calibre/gui2/dialogs/search_ui.py:208 +#: /home/kovid/work/calibre/src/calibre/gui2/store/search/adv_search_builder_ui.py:186 msgid "Any of these &unwanted words:" msgstr "" #: /home/kovid/work/calibre/src/calibre/gui2/dialogs/search_ui.py:209 +#: /home/kovid/work/calibre/src/calibre/gui2/store/search/adv_search_builder_ui.py:187 msgid "" "See the User Manual for more help" msgstr "" #: /home/kovid/work/calibre/src/calibre/gui2/dialogs/search_ui.py:210 +#: /home/kovid/work/calibre/src/calibre/gui2/store/search/adv_search_builder_ui.py:188 msgid "A&dvanced Search" msgstr "" #: /home/kovid/work/calibre/src/calibre/gui2/dialogs/search_ui.py:212 +#: /home/kovid/work/calibre/src/calibre/gui2/store/search/adv_search_builder_ui.py:190 msgid "Enter the title." msgstr "" #: /home/kovid/work/calibre/src/calibre/gui2/dialogs/search_ui.py:213 +#: /home/kovid/work/calibre/src/calibre/gui2/store/search/adv_search_builder_ui.py:191 msgid "&Author:" msgstr "" #: /home/kovid/work/calibre/src/calibre/gui2/dialogs/search_ui.py:215 -#: /home/kovid/work/calibre/src/calibre/gui2/metadata/basic_widgets.py:827 +#: /home/kovid/work/calibre/src/calibre/gui2/metadata/basic_widgets.py:908 msgid "Ta&gs:" msgstr "" @@ -8808,10 +8806,12 @@ msgstr "" #: /home/kovid/work/calibre/src/calibre/gui2/dialogs/search_ui.py:219 #: /home/kovid/work/calibre/src/calibre/gui2/preferences/template_functions_ui.py:101 +#: /home/kovid/work/calibre/src/calibre/gui2/store/search/adv_search_builder_ui.py:193 msgid "&Clear" msgstr "" #: /home/kovid/work/calibre/src/calibre/gui2/dialogs/search_ui.py:220 +#: /home/kovid/work/calibre/src/calibre/gui2/store/search/adv_search_builder_ui.py:194 msgid "Search only in specific fields:" msgstr "" @@ -8824,31 +8824,48 @@ msgid "Choose formats" msgstr "" #: /home/kovid/work/calibre/src/calibre/gui2/dialogs/tag_categories.py:60 -#: /home/kovid/work/calibre/src/calibre/library/field_metadata.py:98 +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/metadata_sources.py:146 +#: /home/kovid/work/calibre/src/calibre/library/field_metadata.py:102 msgid "Authors" msgstr "" #: /home/kovid/work/calibre/src/calibre/gui2/dialogs/tag_categories.py:60 -#: /home/kovid/work/calibre/src/calibre/library/field_metadata.py:129 +#: /home/kovid/work/calibre/src/calibre/library/field_metadata.py:136 msgid "Publishers" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/tag_categories.py:128 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/tag_categories.py:143 msgid " (not on any book)" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/tag_categories.py:175 -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/tag_categories.py:197 -#: /home/kovid/work/calibre/src/calibre/gui2/preferences/template_functions.py:151 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/tag_categories.py:146 +msgid "Category lookup name: " +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/tag_categories.py:191 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/tag_categories.py:222 +msgid "Invalid name" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/tag_categories.py:192 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/tag_categories.py:223 +msgid "" +"That name contains leading or trailing periods, multiple periods in a row or " +"spaces before or after periods." +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/tag_categories.py:200 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/tag_categories.py:230 +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/template_functions.py:152 msgid "Name already used" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/tag_categories.py:176 -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/tag_categories.py:198 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/tag_categories.py:201 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/tag_categories.py:231 msgid "That name is already used, perhaps with different case." msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/tag_categories.py:211 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/tag_categories.py:244 msgid "" "The current tag category will be permanently deleted. Are you sure?" msgstr "" @@ -8906,7 +8923,7 @@ msgid "Unapply (remove) tag from current tag category" msgstr "" #: /home/kovid/work/calibre/src/calibre/gui2/dialogs/tag_editor.py:70 -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/tag_list_editor.py:109 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/tag_list_editor.py:111 msgid "Are your sure?" msgstr "" @@ -8960,33 +8977,33 @@ msgstr "" msgid "%s (was %s)" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/tag_list_editor.py:83 -#: /home/kovid/work/calibre/src/calibre/gui2/tag_view.py:906 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/tag_list_editor.py:85 +#: /home/kovid/work/calibre/src/calibre/gui2/tag_view.py:1385 msgid "Item is blank" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/tag_list_editor.py:84 -#: /home/kovid/work/calibre/src/calibre/gui2/tag_view.py:907 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/tag_list_editor.py:86 +#: /home/kovid/work/calibre/src/calibre/gui2/tag_view.py:1386 msgid "An item cannot be set to nothing. Delete it instead." msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/tag_list_editor.py:97 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/tag_list_editor.py:99 msgid "No item selected" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/tag_list_editor.py:98 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/tag_list_editor.py:100 msgid "You must select one item from the list of Available items." msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/tag_list_editor.py:105 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/tag_list_editor.py:107 msgid "No items selected" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/tag_list_editor.py:106 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/tag_list_editor.py:108 msgid "You must select at least one items from the list." msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/tag_list_editor.py:110 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/tag_list_editor.py:112 msgid "Are you certain you want to delete the following items?" msgstr "" @@ -9035,28 +9052,16 @@ msgid "Send test mail from %s to:" msgstr "" #: /home/kovid/work/calibre/src/calibre/gui2/dialogs/test_email_ui.py:58 -#: /home/kovid/work/calibre/src/calibre/gui2/filename_pattern_ui.py:120 +#: /home/kovid/work/calibre/src/calibre/gui2/filename_pattern_ui.py:134 msgid "&Test" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/tweak_epub_ui.py:55 -msgid "Display contents of exploded ePub" +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/tweak_epub.py:100 +msgid "Cannot preview" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/tweak_epub_ui.py:56 -msgid "&Explode ePub" -msgstr "" - -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/tweak_epub_ui.py:57 -msgid "Rebuild ePub from exploded contents" -msgstr "" - -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/tweak_epub_ui.py:58 -msgid "&Rebuild ePub" -msgstr "" - -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/tweak_epub_ui.py:59 -msgid "Discard changes" +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/tweak_epub.py:101 +msgid "You must first explode the epub before previewing." msgstr "" #: /home/kovid/work/calibre/src/calibre/gui2/dialogs/tweak_epub_ui.py:61 @@ -9068,116 +9073,148 @@ msgid "" "updating your calibre library.

    " msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/user_profiles.py:133 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/tweak_epub_ui.py:62 +msgid "Display contents of exploded ePub" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/tweak_epub_ui.py:63 +msgid "&Explode ePub" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/tweak_epub_ui.py:64 +msgid "Discard changes" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/tweak_epub_ui.py:66 +msgid "Rebuild ePub from exploded contents" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/tweak_epub_ui.py:67 +msgid "&Rebuild ePub" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/tweak_epub_ui.py:68 +msgid "&Preview ePub" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/user_profiles.py:141 msgid "No recipe selected" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/user_profiles.py:138 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/user_profiles.py:146 msgid "The attached file: %s is a recipe to download %s." msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/user_profiles.py:139 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/user_profiles.py:147 msgid "Recipe for " msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/user_profiles.py:156 -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/user_profiles.py:167 -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/user_profiles_ui.py:260 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/user_profiles.py:164 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/user_profiles.py:175 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/user_profiles_ui.py:265 msgid "Switch to Advanced mode" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/user_profiles.py:162 #: /home/kovid/work/calibre/src/calibre/gui2/dialogs/user_profiles.py:170 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/user_profiles.py:178 msgid "Switch to Basic mode" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/user_profiles.py:180 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/user_profiles.py:188 msgid "Feed must have a title" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/user_profiles.py:181 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/user_profiles.py:189 msgid "The feed must have a title" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/user_profiles.py:185 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/user_profiles.py:193 msgid "Feed must have a URL" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/user_profiles.py:186 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/user_profiles.py:194 msgid "The feed %s must have a URL" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/user_profiles.py:192 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/user_profiles.py:200 msgid "This feed has already been added to the recipe" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/user_profiles.py:233 -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/user_profiles.py:242 -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/user_profiles.py:329 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/user_profiles.py:241 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/user_profiles.py:250 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/user_profiles.py:337 msgid "Invalid input" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/user_profiles.py:234 -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/user_profiles.py:243 -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/user_profiles.py:330 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/user_profiles.py:242 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/user_profiles.py:251 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/user_profiles.py:338 msgid "

    Could not create recipe. Error:
    %s" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/user_profiles.py:247 -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/user_profiles.py:306 -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/user_profiles.py:333 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/user_profiles.py:255 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/user_profiles.py:314 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/user_profiles.py:341 msgid "Replace recipe?" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/user_profiles.py:248 -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/user_profiles.py:307 -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/user_profiles.py:334 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/user_profiles.py:256 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/user_profiles.py:315 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/user_profiles.py:342 msgid "A custom recipe named %s already exists. Do you want to replace it?" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/user_profiles.py:274 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/user_profiles.py:282 msgid "Choose builtin recipe" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/user_profiles.py:320 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/user_profiles.py:328 msgid "Choose a recipe file" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/user_profiles.py:361 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/user_profiles.py:329 +msgid "Recipes" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/user_profiles.py:369 msgid "" "You will lose any unsaved changes. To save your changes, click the " "Add/Update recipe button. Continue?" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/user_profiles_ui.py:253 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/user_profiles_ui.py:257 msgid "Add custom news source" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/user_profiles_ui.py:254 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/user_profiles_ui.py:258 msgid "Available user recipes" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/user_profiles_ui.py:255 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/user_profiles_ui.py:259 msgid "Add/Update &recipe" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/user_profiles_ui.py:256 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/user_profiles_ui.py:260 msgid "&Remove recipe" 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:261 msgid "&Share recipe" 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:262 +msgid "S&how recipe files" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/user_profiles_ui.py:263 msgid "Customize &builtin recipe" 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:264 msgid "&Load recipe from file" 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:266 msgid "" "\n" +"

    Stwórz proste źródło " +"newsów, dodając do niego strumień RSS.
    Dla większości strumieni, " +"będziesz musiał użyć \"Trybu zaawansowanego\" by bardziej dostosować proces " +"pobierania.

    " -#: /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:270 msgid "Recipe &title:" msgstr "&Tytuł źródła:" -#: /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:271 msgid "&Oldest article:" msgstr "&Najstarszy artykuł może mieć:" -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/user_profiles_ui.py:267 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/user_profiles_ui.py:272 msgid "The oldest article to download" msgstr "Najstarszy artykuł do pobrania" -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/user_profiles_ui.py:269 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/user_profiles_ui.py:274 msgid "&Max. number of articles per feed:" msgstr "&Maksymalna ilość artykułów na strumień:" -#: /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:275 msgid "Maximum number of articles to download per feed." msgstr "Maksymalna liczba artykułów do pobrania na strumień." -#: /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:276 msgid "Feeds in recipe" msgstr "Strumienie w źródle aktualności" -#: /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:278 msgid "Remove feed from recipe" msgstr "Usuń strumień ze źródła" -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/user_profiles_ui.py:276 -#: /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:281 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/user_profiles_ui.py:284 msgid "Add feed to recipe" msgstr "Dodaj strumień do źródła" -#: /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:282 msgid "&Feed title:" msgstr "&Tytuł strumienia:" -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/user_profiles_ui.py:278 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/user_profiles_ui.py:283 msgid "Feed &URL:" msgstr "Adres &URL strumienia:" -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/user_profiles_ui.py:280 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/user_profiles_ui.py:285 msgid "&Add feed" msgstr "&Dodaj strumień" -#: /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:286 msgid "" "For help with writing advanced news recipes, please visit User Recipes" msgstr "" +"Aby uzyskać pomoc przy tworzeniu zaawansowanych źródeł newsowych, odwiedź Źródła " +"Użytkownika" -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/user_profiles_ui.py:282 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/user_profiles_ui.py:287 msgid "Recipe source code (python)" msgstr "Kod źródłowy źródła aktualności (python)" -#: /home/kovid/work/calibre/src/calibre/gui2/email.py:146 -msgid "Email %s to %s" +#: /home/kovid/work/calibre/src/calibre/gui2/dnd.py:51 +msgid "Download %s" +msgstr "Pobierz %s" + +#: /home/kovid/work/calibre/src/calibre/gui2/dnd.py:54 +msgid "Downloading %s from %s" +msgstr "Pobieranie %s z %s" + +#: /home/kovid/work/calibre/src/calibre/gui2/dnd.py:85 +msgid "Failed to download from %r with error: %s" +msgstr "Nie powiodło się pobieranie %r z błędem: %s" + +#: /home/kovid/work/calibre/src/calibre/gui2/ebook_download.py:41 +msgid "No file specified to download." msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/email.py:188 +#: /home/kovid/work/calibre/src/calibre/gui2/ebook_download.py:66 +msgid "Not a support ebook format." +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/ebook_download.py:87 +msgid "Downloading %s" +msgstr "Pobieranie %s" + +#: /home/kovid/work/calibre/src/calibre/gui2/ebook_download.py:99 +msgid "Downloading" +msgstr "Pobieranie" + +#: /home/kovid/work/calibre/src/calibre/gui2/ebook_download.py:103 +msgid "Failed to download ebook" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/email.py:91 +msgid "Email %s to %s" +msgstr "Email %s do %s" + +#: /home/kovid/work/calibre/src/calibre/gui2/email.py:110 msgid "News:" msgstr "Newsy:" -#: /home/kovid/work/calibre/src/calibre/gui2/email.py:190 +#: /home/kovid/work/calibre/src/calibre/gui2/email.py:112 msgid "Attached is the %s periodical downloaded by calibre." -msgstr "" +msgstr "W załączeniu przesyłam %s czasopism pobranych przez calibre." -#: /home/kovid/work/calibre/src/calibre/gui2/email.py:243 +#: /home/kovid/work/calibre/src/calibre/gui2/email.py:160 msgid "E-book:" msgstr "E-book:" -#: /home/kovid/work/calibre/src/calibre/gui2/email.py:246 +#: /home/kovid/work/calibre/src/calibre/gui2/email.py:168 msgid "Attached, you will find the e-book" msgstr "W załączniku znajdziesz e-booka" -#: /home/kovid/work/calibre/src/calibre/gui2/email.py:247 -#: /home/kovid/work/calibre/src/calibre/gui2/preferences/plugins.py:184 +#: /home/kovid/work/calibre/src/calibre/gui2/email.py:169 +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/plugins.py:189 msgid "by" msgstr "przez" -#: /home/kovid/work/calibre/src/calibre/gui2/email.py:248 +#: /home/kovid/work/calibre/src/calibre/gui2/email.py:170 msgid "in the %s format." msgstr "w formacie %s." -#: /home/kovid/work/calibre/src/calibre/gui2/email.py:261 +#: /home/kovid/work/calibre/src/calibre/gui2/email.py:184 msgid "Sending email to" msgstr "Wysyłanie wiadomości do" -#: /home/kovid/work/calibre/src/calibre/gui2/email.py:292 +#: /home/kovid/work/calibre/src/calibre/gui2/email.py:215 msgid "Auto convert the following books before sending via email?" msgstr "" "Dokonać automatycznej konwersji następujących książek przed wysłaniem ich " -"poprzez email?" +"emailem?" -#: /home/kovid/work/calibre/src/calibre/gui2/email.py:299 +#: /home/kovid/work/calibre/src/calibre/gui2/email.py:222 msgid "" "Could not email the following books as no suitable formats were found:" msgstr "" -"Nie można było przesłać następujących książek poprzez email, ponieważ nie " +"Nie można było przesłać następujących książek emailem, ponieważ nie " "znaleziono odpowiednich formatów:" -#: /home/kovid/work/calibre/src/calibre/gui2/email.py:305 +#: /home/kovid/work/calibre/src/calibre/gui2/email.py:228 msgid "Failed to email book" -msgstr "" +msgstr "Nie udało się wysłać emailem książki" -#: /home/kovid/work/calibre/src/calibre/gui2/email.py:308 +#: /home/kovid/work/calibre/src/calibre/gui2/email.py:231 msgid "sent" -msgstr "" +msgstr "wysłano" -#: /home/kovid/work/calibre/src/calibre/gui2/email.py:331 +#: /home/kovid/work/calibre/src/calibre/gui2/email.py:254 msgid "Sent news to" msgstr "Wysyłaj newsy do" -#: /home/kovid/work/calibre/src/calibre/gui2/filename_pattern_ui.py:115 +#: /home/kovid/work/calibre/src/calibre/gui2/filename_pattern_ui.py:129 msgid "" "
    \n" "

    Set a regular expression pattern to use when trying to guess ebook " @@ -9763,363 +10475,381 @@ msgid "" "group names for the various metadata entries are documented in " "tooltips.

    " msgstr "" +"
    \n" +"

    Ustaw wzorzec regularnych wyrażeń dla próby odgadnięcia metadanych " +"książki z nazwy plików.

    \n" +"

    Dostępny jest tutorial o użyciu regularnych " +"wyrażeń.

    \n" +"

    Użyj poniższego testu funkcjonalności, by przetestować swoje " +"regularne wyrażenie na kilku przykładowych nazwach plków (pamiętaj, aby " +"dołączyć rozszerzenie pliku). Nazwy grup dla różnych wpisów metadanych są " +"udokumentowane we wskazówkach narzędziowych.

    " -#: /home/kovid/work/calibre/src/calibre/gui2/filename_pattern_ui.py:119 +#: /home/kovid/work/calibre/src/calibre/gui2/filename_pattern_ui.py:133 msgid "Regular &expression" msgstr "Wyrażenie ®ularne" -#: /home/kovid/work/calibre/src/calibre/gui2/filename_pattern_ui.py:121 +#: /home/kovid/work/calibre/src/calibre/gui2/filename_pattern_ui.py:135 msgid "File &name:" msgstr "&Nazwa pliku:" -#: /home/kovid/work/calibre/src/calibre/gui2/filename_pattern_ui.py:123 +#: /home/kovid/work/calibre/src/calibre/gui2/filename_pattern_ui.py:137 msgid "Title:" msgstr "Tytuł:" -#: /home/kovid/work/calibre/src/calibre/gui2/filename_pattern_ui.py:124 +#: /home/kovid/work/calibre/src/calibre/gui2/filename_pattern_ui.py:138 msgid "Regular expression (?P<title>)" msgstr "Wyrażenie regularne (?P<title>)" -#: /home/kovid/work/calibre/src/calibre/gui2/filename_pattern_ui.py:125 -#: /home/kovid/work/calibre/src/calibre/gui2/filename_pattern_ui.py:128 -#: /home/kovid/work/calibre/src/calibre/gui2/filename_pattern_ui.py:131 -#: /home/kovid/work/calibre/src/calibre/gui2/filename_pattern_ui.py:134 -#: /home/kovid/work/calibre/src/calibre/gui2/filename_pattern_ui.py:137 -#: /home/kovid/work/calibre/src/calibre/gui2/widgets.py:106 -#: /home/kovid/work/calibre/src/calibre/gui2/widgets.py:110 -#: /home/kovid/work/calibre/src/calibre/gui2/widgets.py:115 -#: /home/kovid/work/calibre/src/calibre/gui2/widgets.py:120 +#: /home/kovid/work/calibre/src/calibre/gui2/filename_pattern_ui.py:139 +#: /home/kovid/work/calibre/src/calibre/gui2/filename_pattern_ui.py:142 +#: /home/kovid/work/calibre/src/calibre/gui2/filename_pattern_ui.py:145 +#: /home/kovid/work/calibre/src/calibre/gui2/filename_pattern_ui.py:148 +#: /home/kovid/work/calibre/src/calibre/gui2/filename_pattern_ui.py:151 +#: /home/kovid/work/calibre/src/calibre/gui2/filename_pattern_ui.py:154 +#: /home/kovid/work/calibre/src/calibre/gui2/filename_pattern_ui.py:157 +#: /home/kovid/work/calibre/src/calibre/gui2/widgets.py:108 +#: /home/kovid/work/calibre/src/calibre/gui2/widgets.py:112 +#: /home/kovid/work/calibre/src/calibre/gui2/widgets.py:117 #: /home/kovid/work/calibre/src/calibre/gui2/widgets.py:122 +#: /home/kovid/work/calibre/src/calibre/gui2/widgets.py:130 msgid "No match" msgstr "Brak wyników" -#: /home/kovid/work/calibre/src/calibre/gui2/filename_pattern_ui.py:126 +#: /home/kovid/work/calibre/src/calibre/gui2/filename_pattern_ui.py:140 msgid "Authors:" msgstr "Autorzy:" -#: /home/kovid/work/calibre/src/calibre/gui2/filename_pattern_ui.py:127 +#: /home/kovid/work/calibre/src/calibre/gui2/filename_pattern_ui.py:141 msgid "Regular expression (?P)" msgstr "Wyrażenie regularne (?P)" -#: /home/kovid/work/calibre/src/calibre/gui2/filename_pattern_ui.py:129 +#: /home/kovid/work/calibre/src/calibre/gui2/filename_pattern_ui.py:143 msgid "Series:" msgstr "Cykl:" -#: /home/kovid/work/calibre/src/calibre/gui2/filename_pattern_ui.py:130 +#: /home/kovid/work/calibre/src/calibre/gui2/filename_pattern_ui.py:144 msgid "Regular expression (?P)" msgstr "Wyrażenie regularne (?P)" -#: /home/kovid/work/calibre/src/calibre/gui2/filename_pattern_ui.py:132 +#: /home/kovid/work/calibre/src/calibre/gui2/filename_pattern_ui.py:146 msgid "Series index:" msgstr "Spis cykli:" -#: /home/kovid/work/calibre/src/calibre/gui2/filename_pattern_ui.py:133 +#: /home/kovid/work/calibre/src/calibre/gui2/filename_pattern_ui.py:147 msgid "Regular expression (?P)" msgstr "Wyrażenie regularne (?P)" -#: /home/kovid/work/calibre/src/calibre/gui2/filename_pattern_ui.py:135 +#: /home/kovid/work/calibre/src/calibre/gui2/filename_pattern_ui.py:149 msgid "ISBN:" msgstr "ISBN:" -#: /home/kovid/work/calibre/src/calibre/gui2/filename_pattern_ui.py:136 +#: /home/kovid/work/calibre/src/calibre/gui2/filename_pattern_ui.py:150 msgid "Regular expression (?P)" msgstr "Wyrażenie regularne (?P)" +#: /home/kovid/work/calibre/src/calibre/gui2/filename_pattern_ui.py:152 +msgid "Publisher:" +msgstr "Wydawca:" + +#: /home/kovid/work/calibre/src/calibre/gui2/filename_pattern_ui.py:153 +msgid "Regular expression (?P)" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/filename_pattern_ui.py:155 +msgid "Published:" +msgstr "Wydano:" + +#: /home/kovid/work/calibre/src/calibre/gui2/filename_pattern_ui.py:156 +msgid "Regular expression (?P)" +msgstr "" + #: /home/kovid/work/calibre/src/calibre/gui2/init.py:105 +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/look_feel_ui.py:237 msgid "Cover Browser" msgstr "Przeglądarka okładek" #: /home/kovid/work/calibre/src/calibre/gui2/init.py:110 msgid "Shift+Alt+B" -msgstr "" +msgstr "Shift+Alt+B" #: /home/kovid/work/calibre/src/calibre/gui2/init.py:124 +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/look_feel_ui.py:234 msgid "Tag Browser" msgstr "Przeglądarka etykiet" #: /home/kovid/work/calibre/src/calibre/gui2/init.py:126 msgid "Shift+Alt+T" -msgstr "" +msgstr "Shift+Alt+T" -#: /home/kovid/work/calibre/src/calibre/gui2/init.py:148 +#: /home/kovid/work/calibre/src/calibre/gui2/init.py:157 #: /home/kovid/work/calibre/src/calibre/gui2/preferences/main.py:29 msgid "version" msgstr "wersja" -#: /home/kovid/work/calibre/src/calibre/gui2/init.py:149 +#: /home/kovid/work/calibre/src/calibre/gui2/init.py:158 #: /home/kovid/work/calibre/src/calibre/gui2/preferences/main.py:30 msgid "created by Kovid Goyal" msgstr "- autor: Kovid Goyal" -#: /home/kovid/work/calibre/src/calibre/gui2/init.py:166 +#: /home/kovid/work/calibre/src/calibre/gui2/init.py:175 msgid "Connected " msgstr "Połączone " -#: /home/kovid/work/calibre/src/calibre/gui2/init.py:179 +#: /home/kovid/work/calibre/src/calibre/gui2/init.py:188 msgid "Update found" msgstr "Znaleziono aktualizację" -#: /home/kovid/work/calibre/src/calibre/gui2/init.py:214 -#: /home/kovid/work/calibre/src/calibre/gui2/init.py:224 +#: /home/kovid/work/calibre/src/calibre/gui2/init.py:223 +#: /home/kovid/work/calibre/src/calibre/gui2/init.py:233 +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/look_feel_ui.py:216 msgid "Book Details" msgstr "O książce" -#: /home/kovid/work/calibre/src/calibre/gui2/init.py:216 +#: /home/kovid/work/calibre/src/calibre/gui2/init.py:225 msgid "Alt+D" -msgstr "" +msgstr "Alt+D" -#: /home/kovid/work/calibre/src/calibre/gui2/init.py:226 +#: /home/kovid/work/calibre/src/calibre/gui2/init.py:235 msgid "Shift+Alt+D" -msgstr "" +msgstr "Shift+Alt+D" -#: /home/kovid/work/calibre/src/calibre/gui2/jobs.py:61 +#: /home/kovid/work/calibre/src/calibre/gui2/jobs.py:62 msgid "Job" msgstr "Zadanie" -#: /home/kovid/work/calibre/src/calibre/gui2/jobs.py:62 +#: /home/kovid/work/calibre/src/calibre/gui2/jobs.py:63 msgid "Status" msgstr "Status" -#: /home/kovid/work/calibre/src/calibre/gui2/jobs.py:63 +#: /home/kovid/work/calibre/src/calibre/gui2/jobs.py:64 msgid "Progress" msgstr "Postęp" -#: /home/kovid/work/calibre/src/calibre/gui2/jobs.py:64 +#: /home/kovid/work/calibre/src/calibre/gui2/jobs.py:65 msgid "Running time" msgstr "Czas pracy" -#: /home/kovid/work/calibre/src/calibre/gui2/jobs.py:76 +#: /home/kovid/work/calibre/src/calibre/gui2/jobs.py:77 msgid "There are %d running jobs:" msgstr "Aktualnie jest przetwarzanych %d zadań:" -#: /home/kovid/work/calibre/src/calibre/gui2/jobs.py:80 -#: /home/kovid/work/calibre/src/calibre/gui2/jobs.py:87 -#: /home/kovid/work/calibre/src/calibre/gui2/jobs.py:102 +#: /home/kovid/work/calibre/src/calibre/gui2/jobs.py:81 +#: /home/kovid/work/calibre/src/calibre/gui2/jobs.py:88 +#: /home/kovid/work/calibre/src/calibre/gui2/jobs.py:103 msgid "Unknown job" msgstr "Nieznane zadanie" -#: /home/kovid/work/calibre/src/calibre/gui2/jobs.py:83 +#: /home/kovid/work/calibre/src/calibre/gui2/jobs.py:84 msgid "There are %d waiting jobs:" msgstr "Aktualnie jest %d zadań oczekujących:" -#: /home/kovid/work/calibre/src/calibre/gui2/jobs.py:219 -#: /home/kovid/work/calibre/src/calibre/gui2/jobs.py:222 +#: /home/kovid/work/calibre/src/calibre/gui2/jobs.py:240 +#: /home/kovid/work/calibre/src/calibre/gui2/jobs.py:243 +#: /home/kovid/work/calibre/src/calibre/gui2/jobs.py:246 msgid "Cannot kill job" msgstr "Nie można zatrzymać zadania" -#: /home/kovid/work/calibre/src/calibre/gui2/jobs.py:220 +#: /home/kovid/work/calibre/src/calibre/gui2/jobs.py:241 msgid "Cannot kill jobs that communicate with the device" msgstr "Nie można przerwać zadań, które komunikują się z urządzeniem" -#: /home/kovid/work/calibre/src/calibre/gui2/jobs.py:223 +#: /home/kovid/work/calibre/src/calibre/gui2/jobs.py:244 msgid "Job has already run" msgstr "Zadanie zostało już wykonane" -#: /home/kovid/work/calibre/src/calibre/gui2/jobs.py:262 +#: /home/kovid/work/calibre/src/calibre/gui2/jobs.py:247 +msgid "This job cannot be stopped" +msgstr "Nie można przerwać tego zadania" + +#: /home/kovid/work/calibre/src/calibre/gui2/jobs.py:283 msgid "Unavailable" msgstr "Niedostępne" -#: /home/kovid/work/calibre/src/calibre/gui2/jobs.py:294 +#: /home/kovid/work/calibre/src/calibre/gui2/jobs.py:327 msgid "Jobs:" msgstr "Zadań:" -#: /home/kovid/work/calibre/src/calibre/gui2/jobs.py:296 +#: /home/kovid/work/calibre/src/calibre/gui2/jobs.py:329 msgid "Shift+Alt+J" -msgstr "" +msgstr "Shift+Alt+J" -#: /home/kovid/work/calibre/src/calibre/gui2/jobs.py:313 +#: /home/kovid/work/calibre/src/calibre/gui2/jobs.py:346 msgid "Click to see list of jobs" msgstr "Kliknij, by zobaczyć kolejkę zadań" -#: /home/kovid/work/calibre/src/calibre/gui2/jobs.py:382 +#: /home/kovid/work/calibre/src/calibre/gui2/jobs.py:415 msgid " - Jobs" msgstr " - Zadania" -#: /home/kovid/work/calibre/src/calibre/gui2/jobs.py:424 +#: /home/kovid/work/calibre/src/calibre/gui2/jobs.py:457 msgid "Do you really want to stop the selected job?" -msgstr "" +msgstr "Naprawdę chcesz zatrzymać wybrane zadanie?" -#: /home/kovid/work/calibre/src/calibre/gui2/jobs.py:430 +#: /home/kovid/work/calibre/src/calibre/gui2/jobs.py:463 msgid "Do you really want to stop all non-device jobs?" msgstr "" +"Naprawdę chcesz zatrzymać wszystkie zadania nie związane z urządzeniem?" -#: /home/kovid/work/calibre/src/calibre/gui2/layout.py:53 +#: /home/kovid/work/calibre/src/calibre/gui2/layout.py:57 msgid "Eject this device" msgstr "Odłącz urządzenie" -#: /home/kovid/work/calibre/src/calibre/gui2/layout.py:64 +#: /home/kovid/work/calibre/src/calibre/gui2/layout.py:69 msgid "Show books in calibre library" msgstr "Pokaż książki w bibliotece calibre" -#: /home/kovid/work/calibre/src/calibre/gui2/layout.py:66 +#: /home/kovid/work/calibre/src/calibre/gui2/layout.py:71 msgid "Show books in the main memory of the device" -msgstr "Pokaż książki w pamięci głownej urządzenia" +msgstr "Pokaż książki w pamięci głównej urządzenia" -#: /home/kovid/work/calibre/src/calibre/gui2/layout.py:67 -#: /home/kovid/work/calibre/src/calibre/library/database2.py:924 +#: /home/kovid/work/calibre/src/calibre/gui2/layout.py:72 +#: /home/kovid/work/calibre/src/calibre/library/database2.py:1019 msgid "Card A" msgstr "Karta A" -#: /home/kovid/work/calibre/src/calibre/gui2/layout.py:68 +#: /home/kovid/work/calibre/src/calibre/gui2/layout.py:73 msgid "Show books in storage card A" msgstr "Pokaż książki na karcie pamięci A" -#: /home/kovid/work/calibre/src/calibre/gui2/layout.py:69 -#: /home/kovid/work/calibre/src/calibre/library/database2.py:926 +#: /home/kovid/work/calibre/src/calibre/gui2/layout.py:74 +#: /home/kovid/work/calibre/src/calibre/library/database2.py:1021 msgid "Card B" msgstr "Karta B" -#: /home/kovid/work/calibre/src/calibre/gui2/layout.py:70 +#: /home/kovid/work/calibre/src/calibre/gui2/layout.py:75 msgid "Show books in storage card B" msgstr "Pokaż książki na karcie pamięci B" -#: /home/kovid/work/calibre/src/calibre/gui2/layout.py:128 +#: /home/kovid/work/calibre/src/calibre/gui2/layout.py:140 msgid "available" msgstr "dostępne" -#: /home/kovid/work/calibre/src/calibre/gui2/layout.py:159 -msgid "" -"Books display will be restricted to those matching the selected saved search" -msgstr "" -"Wyświetlane książki będą zawężone do pozycji z wybranego zapisanego " -"wyszukania" - -#: /home/kovid/work/calibre/src/calibre/gui2/layout.py:171 +#: /home/kovid/work/calibre/src/calibre/gui2/layout.py:181 msgid "Shift+Ctrl+F" -msgstr "" +msgstr "Shift+Ctrl+F" -#: /home/kovid/work/calibre/src/calibre/gui2/layout.py:174 +#: /home/kovid/work/calibre/src/calibre/gui2/layout.py:184 msgid "Advanced search" msgstr "Wyszukiwanie zaawansowane" -#: /home/kovid/work/calibre/src/calibre/gui2/layout.py:179 +#: /home/kovid/work/calibre/src/calibre/gui2/layout.py:189 msgid "" "

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

    Words separated by spaces are ANDed" msgstr "" +"

    Wyszukaj listę książek po tytule, autorzem wydawcy, etykietach, " +"komentarzach, itd.

    Słowa rozdzielone spacją są traktowane jako " +"warunek ORAZ (AND)" -#: /home/kovid/work/calibre/src/calibre/gui2/layout.py:185 +#: /home/kovid/work/calibre/src/calibre/gui2/layout.py:195 msgid "&Go!" msgstr "Szukaj!" -#: /home/kovid/work/calibre/src/calibre/gui2/layout.py:191 +#: /home/kovid/work/calibre/src/calibre/gui2/layout.py:201 msgid "Do Quick Search (you can also press the Enter key)" -msgstr "" +msgstr "Wykonaj szybkie wyszukiwanie (możesz też nacisnąć Enter)" -#: /home/kovid/work/calibre/src/calibre/gui2/layout.py:197 +#: /home/kovid/work/calibre/src/calibre/gui2/layout.py:207 msgid "Reset Quick Search" msgstr "Wyczyść pasek wyszukiwania" -#: /home/kovid/work/calibre/src/calibre/gui2/layout.py:203 -msgid "Change the way searching for books works" -msgstr "" - -#: /home/kovid/work/calibre/src/calibre/gui2/layout.py:215 +#: /home/kovid/work/calibre/src/calibre/gui2/layout.py:223 msgid "Copy current search text (instead of search name)" msgstr "Skopiuj aktualny tekst wyszukiwania (zamiast nazwy wyszukiwania)" -#: /home/kovid/work/calibre/src/calibre/gui2/layout.py:221 -msgid "Save current search under the name shown in the box" -msgstr "Zapisz aktualne wyszukanie pod nazwą pokazaną w polu" - -#: /home/kovid/work/calibre/src/calibre/gui2/layout.py:227 -msgid "Delete current saved search" -msgstr "Usuń aktualne zapisane wyszukanie" - -#: /home/kovid/work/calibre/src/calibre/gui2/library/delegates.py:355 +#: /home/kovid/work/calibre/src/calibre/gui2/library/delegates.py:361 msgid "Y" -msgstr "" +msgstr "Y" -#: /home/kovid/work/calibre/src/calibre/gui2/library/delegates.py:390 +#: /home/kovid/work/calibre/src/calibre/gui2/library/delegates.py:396 msgid "Edit template" -msgstr "" +msgstr "Modyfikuj szablon" -#: /home/kovid/work/calibre/src/calibre/gui2/library/models.py:66 -#: /home/kovid/work/calibre/src/calibre/library/field_metadata.py:241 +#: /home/kovid/work/calibre/src/calibre/gui2/library/models.py:64 +#: /home/kovid/work/calibre/src/calibre/library/field_metadata.py:251 msgid "On Device" msgstr "Na urządzeniu" -#: /home/kovid/work/calibre/src/calibre/gui2/library/models.py:68 -#: /home/kovid/work/calibre/src/calibre/library/field_metadata.py:286 +#: /home/kovid/work/calibre/src/calibre/gui2/library/models.py:66 msgid "Size (MB)" msgstr "Rozmiar (MB)" -#: /home/kovid/work/calibre/src/calibre/gui2/library/models.py:386 -msgid "Book %s of %s." +#: /home/kovid/work/calibre/src/calibre/gui2/library/models.py:73 +#: /home/kovid/work/calibre/src/calibre/library/field_metadata.py:241 +msgid "Modified" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/library/models.py:735 -#: /home/kovid/work/calibre/src/calibre/gui2/library/models.py:1289 -#: /home/kovid/work/calibre/src/calibre/gui2/tag_view.py:601 +#: /home/kovid/work/calibre/src/calibre/gui2/library/models.py:720 +#: /home/kovid/work/calibre/src/calibre/gui2/library/models.py:1277 +#: /home/kovid/work/calibre/src/calibre/gui2/tag_view.py:797 msgid "The lookup/search name is \"{0}\"" -msgstr "" +msgstr "Poszukiwana nazwa to \"{0}\"" -#: /home/kovid/work/calibre/src/calibre/gui2/library/models.py:741 -#: /home/kovid/work/calibre/src/calibre/gui2/library/models.py:1291 +#: /home/kovid/work/calibre/src/calibre/gui2/library/models.py:726 +#: /home/kovid/work/calibre/src/calibre/gui2/library/models.py:1279 msgid "This book's UUID is \"{0}\"" -msgstr "" +msgstr "UUID tej książki to \"{0}\"" #: /home/kovid/work/calibre/src/calibre/gui2/library/models.py:976 msgid "In Library" msgstr "W bibliotece" #: /home/kovid/work/calibre/src/calibre/gui2/library/models.py:980 +#: /home/kovid/work/calibre/src/calibre/library/field_metadata.py:311 msgid "Size" msgstr "Rozmiar" -#: /home/kovid/work/calibre/src/calibre/gui2/library/models.py:1189 -msgid "Book %s of %s." -msgstr "Książka %s z %s." - -#: /home/kovid/work/calibre/src/calibre/gui2/library/models.py:1269 +#: /home/kovid/work/calibre/src/calibre/gui2/library/models.py:1257 msgid "Marked for deletion" msgstr "Oznaczone do usunięcia" -#: /home/kovid/work/calibre/src/calibre/gui2/library/models.py:1272 +#: /home/kovid/work/calibre/src/calibre/gui2/library/models.py:1260 msgid "Double click to edit me

    " msgstr "Kliknij dwa razy, aby rozpocząć edycję

    " -#: /home/kovid/work/calibre/src/calibre/gui2/library/views.py:155 +#: /home/kovid/work/calibre/src/calibre/gui2/library/views.py:158 msgid "Hide column %s" msgstr "Ukryj kolumnę %s" -#: /home/kovid/work/calibre/src/calibre/gui2/library/views.py:160 +#: /home/kovid/work/calibre/src/calibre/gui2/library/views.py:163 msgid "Sort on %s" msgstr "Posortuj po kolumnie %s" -#: /home/kovid/work/calibre/src/calibre/gui2/library/views.py:161 +#: /home/kovid/work/calibre/src/calibre/gui2/library/views.py:164 msgid "Ascending" msgstr "Rosnąco" -#: /home/kovid/work/calibre/src/calibre/gui2/library/views.py:164 +#: /home/kovid/work/calibre/src/calibre/gui2/library/views.py:167 msgid "Descending" msgstr "Malejąco" -#: /home/kovid/work/calibre/src/calibre/gui2/library/views.py:176 +#: /home/kovid/work/calibre/src/calibre/gui2/library/views.py:179 msgid "Change text alignment for %s" msgstr "Zmień wyrównanie tekstu dla %s" -#: /home/kovid/work/calibre/src/calibre/gui2/library/views.py:178 +#: /home/kovid/work/calibre/src/calibre/gui2/library/views.py:181 msgid "Left" msgstr "Do lewej" -#: /home/kovid/work/calibre/src/calibre/gui2/library/views.py:178 +#: /home/kovid/work/calibre/src/calibre/gui2/library/views.py:181 msgid "Right" msgstr "Do prawej" -#: /home/kovid/work/calibre/src/calibre/gui2/library/views.py:179 +#: /home/kovid/work/calibre/src/calibre/gui2/library/views.py:182 msgid "Center" msgstr "Wyśrodkuj" -#: /home/kovid/work/calibre/src/calibre/gui2/library/views.py:198 +#: /home/kovid/work/calibre/src/calibre/gui2/library/views.py:201 msgid "Show column" msgstr "Pokaż kolumnę" -#: /home/kovid/work/calibre/src/calibre/gui2/library/views.py:210 +#: /home/kovid/work/calibre/src/calibre/gui2/library/views.py:213 msgid "Restore default layout" msgstr "Przywróć domyślny układ" -#: /home/kovid/work/calibre/src/calibre/gui2/library/views.py:783 +#: /home/kovid/work/calibre/src/calibre/gui2/library/views.py:855 msgid "" "Dropping onto a device is not supported. First add the book to the calibre " "library." @@ -10151,11 +10881,6 @@ msgstr " - Przeglądarka LRF" msgid "No matches for the search phrase %s were found." msgstr "Nie znaleziono wyników dla szukanej frazy %s." -#: /home/kovid/work/calibre/src/calibre/gui2/lrf_renderer/main.py:160 -#: /home/kovid/work/calibre/src/calibre/gui2/viewer/main.py:481 -msgid "No matches found" -msgstr "Brak pasujących wyników" - #: /home/kovid/work/calibre/src/calibre/gui2/lrf_renderer/main_ui.py:128 msgid "LRF Viewer" msgstr "Przeglądarka LRF" @@ -10169,16 +10894,17 @@ msgid "LRF Viewer toolbar" msgstr "Pasek narzędzi przeglądarki LRF" #: /home/kovid/work/calibre/src/calibre/gui2/lrf_renderer/main_ui.py:131 -#: /home/kovid/work/calibre/src/calibre/gui2/viewer/documentview.py:539 +#: /home/kovid/work/calibre/src/calibre/gui2/viewer/documentview.py:559 msgid "Next Page" msgstr "Następna strona" #: /home/kovid/work/calibre/src/calibre/gui2/lrf_renderer/main_ui.py:132 -#: /home/kovid/work/calibre/src/calibre/gui2/viewer/documentview.py:540 +#: /home/kovid/work/calibre/src/calibre/gui2/viewer/documentview.py:560 msgid "Previous Page" msgstr "Poprzednia strona" #: /home/kovid/work/calibre/src/calibre/gui2/lrf_renderer/main_ui.py:133 +#: /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:193 msgid "Back" msgstr "Wstecz" @@ -10201,77 +10927,85 @@ msgstr "Otwórz e-book" msgid "Configure" msgstr "Ustawienia" -#: /home/kovid/work/calibre/src/calibre/gui2/main.py:30 +#: /home/kovid/work/calibre/src/calibre/gui2/main.py:33 msgid "Use the library located at the specified path." msgstr "Użyj biblioteki zlokalizowanej pod podaną ścieżką." -#: /home/kovid/work/calibre/src/calibre/gui2/main.py:32 +#: /home/kovid/work/calibre/src/calibre/gui2/main.py:35 msgid "Start minimized to system tray." msgstr "Uruchom zminimalizowany w zasobniku systemowym." -#: /home/kovid/work/calibre/src/calibre/gui2/main.py:34 +#: /home/kovid/work/calibre/src/calibre/gui2/main.py:37 msgid "Log debugging information to console" msgstr "Zapisuj informacje z debugowania do konsoli" -#: /home/kovid/work/calibre/src/calibre/gui2/main.py:36 +#: /home/kovid/work/calibre/src/calibre/gui2/main.py:39 msgid "Do not check for updates" msgstr "Nie sprawdzaj czy uaktualnienie jest dostępne" -#: /home/kovid/work/calibre/src/calibre/gui2/main.py:38 +#: /home/kovid/work/calibre/src/calibre/gui2/main.py:41 msgid "" "Ignore custom plugins, useful if you installed a plugin that is preventing " "calibre from starting" msgstr "" +"Ignoruj wtyczki niestandardowe, przydatne gdy zainstalujesz wtyczkę, która " +"zablokuje uruchomienie calibre" -#: /home/kovid/work/calibre/src/calibre/gui2/main.py:61 +#: /home/kovid/work/calibre/src/calibre/gui2/main.py:45 +msgid "" +"Cause a running calibre instance, if any, to be shutdown. Note that if there " +"are running jobs, they will be silently aborted, so use with care." +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/main.py:69 #: /home/kovid/work/calibre/src/calibre/gui2/wizard/__init__.py:678 msgid "Calibre Library" msgstr "Biblioteka calibre" -#: /home/kovid/work/calibre/src/calibre/gui2/main.py:89 +#: /home/kovid/work/calibre/src/calibre/gui2/main.py:96 msgid "Choose a location for your calibre e-book library" msgstr "Wybierz lokalizację dla twojej biblioteki książek calibre" -#: /home/kovid/work/calibre/src/calibre/gui2/main.py:98 +#: /home/kovid/work/calibre/src/calibre/gui2/main.py:105 msgid "Failed to create library" msgstr "Stworzenie biblioteki nie powiodło się" -#: /home/kovid/work/calibre/src/calibre/gui2/main.py:99 +#: /home/kovid/work/calibre/src/calibre/gui2/main.py:106 msgid "Failed to create calibre library at: %r." msgstr "Stworzenie biblioteki w %r nie powiodło się" -#: /home/kovid/work/calibre/src/calibre/gui2/main.py:102 -#: /home/kovid/work/calibre/src/calibre/gui2/main.py:188 +#: /home/kovid/work/calibre/src/calibre/gui2/main.py:109 +#: /home/kovid/work/calibre/src/calibre/gui2/main.py:195 msgid "Choose a location for your new calibre e-book library" msgstr "Wybierz lokalizację dla twojej nowej biblioteki książek calibre" -#: /home/kovid/work/calibre/src/calibre/gui2/main.py:157 +#: /home/kovid/work/calibre/src/calibre/gui2/main.py:164 msgid "Initializing user interface..." msgstr "Inicjalizacja interfejsu użytkownika..." -#: /home/kovid/work/calibre/src/calibre/gui2/main.py:182 +#: /home/kovid/work/calibre/src/calibre/gui2/main.py:189 msgid "Repairing failed" msgstr "Naprawianie nie powiodło się" -#: /home/kovid/work/calibre/src/calibre/gui2/main.py:183 +#: /home/kovid/work/calibre/src/calibre/gui2/main.py:190 msgid "The database repair failed. Starting with a new empty library." msgstr "" "Naprawa bazy danych nie powiodła się. Uruchamiam z nową, pustą biblioteką." -#: /home/kovid/work/calibre/src/calibre/gui2/main.py:197 -#: /home/kovid/work/calibre/src/calibre/gui2/main.py:229 +#: /home/kovid/work/calibre/src/calibre/gui2/main.py:204 +#: /home/kovid/work/calibre/src/calibre/gui2/main.py:236 msgid "Bad database location" msgstr "Zła lokalizacja bazy danych" -#: /home/kovid/work/calibre/src/calibre/gui2/main.py:198 +#: /home/kovid/work/calibre/src/calibre/gui2/main.py:205 msgid "Bad database location %r. calibre will now quit." -msgstr "Zła lokalizacja bazy danych %r. calibre zostanie teraz wyłączone" +msgstr "Zła lokalizacja bazy danych %r. calibre zostanie teraz wyłączone." -#: /home/kovid/work/calibre/src/calibre/gui2/main.py:210 +#: /home/kovid/work/calibre/src/calibre/gui2/main.py:217 msgid "Corrupted database" msgstr "Uszkodzona baza danych" -#: /home/kovid/work/calibre/src/calibre/gui2/main.py:211 +#: /home/kovid/work/calibre/src/calibre/gui2/main.py:218 msgid "" "Your calibre database appears to be corrupted. Do you want calibre to try " "and repair it automatically? If you say No, a new empty calibre library will " @@ -10281,200 +11015,601 @@ msgstr "" "ją automatycznie? Jeśli wybierzesz Nie zostanie stworzona nowa, pusta " "biblioteka." -#: /home/kovid/work/calibre/src/calibre/gui2/main.py:217 +#: /home/kovid/work/calibre/src/calibre/gui2/main.py:224 msgid "" "Repairing database. This can take a very long time for a large collection" msgstr "" "Naprawianie bazy danych. Może to zająć dużo czasu przy wielkiej kolekcji" -#: /home/kovid/work/calibre/src/calibre/gui2/main.py:230 +#: /home/kovid/work/calibre/src/calibre/gui2/main.py:237 msgid "" "Bad database location %r. Will start with a new, empty calibre library" msgstr "" "Błędna lokalizacja bazy danych: %r. Uruchamiam z nową, pustą biblioteką." -#: /home/kovid/work/calibre/src/calibre/gui2/main.py:240 +#: /home/kovid/work/calibre/src/calibre/gui2/main.py:247 msgid "Starting %s: Loading books..." msgstr "Uruchamiam %s: Ładuję książki..." -#: /home/kovid/work/calibre/src/calibre/gui2/main.py:320 +#: /home/kovid/work/calibre/src/calibre/gui2/main.py:327 msgid "If you are sure it is not running" msgstr "Jeśli jestes pewien, że nie jest uruchomione" -#: /home/kovid/work/calibre/src/calibre/gui2/main.py:323 +#: /home/kovid/work/calibre/src/calibre/gui2/main.py:330 msgid "may be running in the system tray, in the" msgstr "może być uruchomione i znajdować się w zasobniku systemowym" -#: /home/kovid/work/calibre/src/calibre/gui2/main.py:325 +#: /home/kovid/work/calibre/src/calibre/gui2/main.py:332 msgid "upper right region of the screen." msgstr "w prawym górnym obszarze wyświetlacza." -#: /home/kovid/work/calibre/src/calibre/gui2/main.py:327 +#: /home/kovid/work/calibre/src/calibre/gui2/main.py:334 msgid "lower right region of the screen." msgstr "w prawym donym obszarze wyświetlacza." -#: /home/kovid/work/calibre/src/calibre/gui2/main.py:330 +#: /home/kovid/work/calibre/src/calibre/gui2/main.py:337 msgid "try rebooting your computer." msgstr "spróbuj zrestartować komputer." -#: /home/kovid/work/calibre/src/calibre/gui2/main.py:332 -#: /home/kovid/work/calibre/src/calibre/gui2/main.py:346 +#: /home/kovid/work/calibre/src/calibre/gui2/main.py:339 +#: /home/kovid/work/calibre/src/calibre/gui2/main.py:353 msgid "try deleting the file" msgstr "spróbuj usunąć plik" -#: /home/kovid/work/calibre/src/calibre/gui2/main.py:335 +#: /home/kovid/work/calibre/src/calibre/gui2/main.py:342 msgid "Cannot Start " msgstr "Nie można uruchomić " -#: /home/kovid/work/calibre/src/calibre/gui2/main.py:336 +#: /home/kovid/work/calibre/src/calibre/gui2/main.py:343 msgid "%s is already running." msgstr "%s jest już uruchomiony." -#: /home/kovid/work/calibre/src/calibre/gui2/main_window.py:20 +#: /home/kovid/work/calibre/src/calibre/gui2/main_window.py:25 msgid "" "Redirect console output to a dialog window (both stdout and stderr). Useful " "on windows where GUI apps do not have a output streams." msgstr "" +"Przekieruj wynik konsoli na okno dialogowe (zarówno stdout i stderr). " +"Przydatne w oknach gdzie aplikacje GUI nie mają strumieni wynikowych." -#: /home/kovid/work/calibre/src/calibre/gui2/main_window.py:61 +#: /home/kovid/work/calibre/src/calibre/gui2/main_window.py:113 msgid "&Preferences" -msgstr "&Ustawienia" +msgstr "&Preferencje" -#: /home/kovid/work/calibre/src/calibre/gui2/main_window.py:62 +#: /home/kovid/work/calibre/src/calibre/gui2/main_window.py:114 msgid "&Quit" msgstr "&Zakończ" -#: /home/kovid/work/calibre/src/calibre/gui2/main_window.py:90 +#: /home/kovid/work/calibre/src/calibre/gui2/main_window.py:138 msgid "Unhandled exception" -msgstr "" +msgstr "Nieznany wyjątek" -#: /home/kovid/work/calibre/src/calibre/gui2/metadata/basic_widgets.py:111 -msgid "Title &sort:" -msgstr "" - -#: /home/kovid/work/calibre/src/calibre/gui2/metadata/basic_widgets.py:220 -msgid "Author s&ort:" -msgstr "" - -#: /home/kovid/work/calibre/src/calibre/gui2/metadata/basic_widgets.py:348 -msgid "&Number:" -msgstr "" - -#: /home/kovid/work/calibre/src/calibre/gui2/metadata/basic_widgets.py:716 -msgid "Invalid cover" -msgstr "" - -#: /home/kovid/work/calibre/src/calibre/gui2/metadata/basic_widgets.py:717 -msgid "Could not change cover as the image is invalid." -msgstr "" - -#: /home/kovid/work/calibre/src/calibre/gui2/metadata/basic_widgets.py:744 -msgid "This book has no cover" -msgstr "" - -#: /home/kovid/work/calibre/src/calibre/gui2/metadata/basic_widgets.py:794 -msgid "stars" -msgstr "" - -#: /home/kovid/work/calibre/src/calibre/gui2/metadata/basic_widgets.py:828 +#: /home/kovid/work/calibre/src/calibre/gui2/metadata/basic_widgets.py:121 msgid "" -"Tags categorize the book. This is particularly useful while searching. " -"

    They can be any wordsor phrases, separated by commas." +"Specify how this book should be sorted when by title. For example, The " +"Exorcist might be sorted as Exorcist, The." +msgstr "" +"Ustal, jak książki mają być sortowane po tytule. Przykładowo: The Exorcist " +"może być sortowana jako Exorcist, The" + +#: /home/kovid/work/calibre/src/calibre/gui2/metadata/basic_widgets.py:123 +msgid "Title &sort:" +msgstr "Sortowanie według &tytułu:" + +#: /home/kovid/work/calibre/src/calibre/gui2/metadata/basic_widgets.py:131 +msgid "" +" The green color indicates that the current title sort matches the current " +"title" +msgstr "" +" Zielony kolor wskazuje, że obecne sortowanie według tytułu pasuje do " +"obecnego tytułu" + +#: /home/kovid/work/calibre/src/calibre/gui2/metadata/basic_widgets.py:134 +msgid "" +" The red color warns that the current title sort does not match the current " +"title. No action is required if this is what you want." +msgstr "" +" Czerwony kolor wskazuje, że obecne sortowanie według tytułu nie pasuje do " +"obecnego tytułu. Nie jest wymagana żadna akcja, jeśli to spełnia twoje " +"oczekiwania." + +#: /home/kovid/work/calibre/src/calibre/gui2/metadata/basic_widgets.py:181 +msgid "Authors changed" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/metadata/basic_widgets.py:927 +#: /home/kovid/work/calibre/src/calibre/gui2/metadata/basic_widgets.py:182 +msgid "" +"You have changed the authors for this book. You must save these changes " +"before you can use Manage authors. Do you want to save these changes?" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/metadata/basic_widgets.py:252 +msgid "" +"Specify how the author(s) of this book should be sorted. For example Charles " +"Dickens should be sorted as Dickens, Charles.\n" +"If the box is colored green, then text matches the individual author's sort " +"strings. If it is colored red, then the authors and this text do not match." +msgstr "" +"Ustal, jak książka ma być sortowana według autora(rów). Na przykład Charles " +"Dickens powinien być sortowany jako Dickens, Charles.\n" +"Zielony kolor tła tego pola oznacza, że dane zgadzają się z danymi " +"poszczególnych autorów. Czerwne tło oznacza, że dane autorów nie zgadzają " +"się z tekstem wpisanym do tego pola." + +#: /home/kovid/work/calibre/src/calibre/gui2/metadata/basic_widgets.py:257 +msgid "Author s&ort:" +msgstr "S&ortowanie według autora:" + +#: /home/kovid/work/calibre/src/calibre/gui2/metadata/basic_widgets.py:267 +msgid "" +" The green color indicates that the current author sort matches the current " +"author" +msgstr "" +" Zielony kolor wskazuje, że obecne sortowanie według autora pasuje do " +"obecnego autora" + +#: /home/kovid/work/calibre/src/calibre/gui2/metadata/basic_widgets.py:270 +msgid "" +" The red color indicates that the current author sort does not match the " +"current author. No action is required if this is what you want." +msgstr "" +" Czerwony kolor wskazuje, że obecne sortowanie według autora nie pasuje do " +"obecnego autora. Nie jest wymagana żadna akcja, jeśli to spełnia twoje " +"oczekiwania." + +#: /home/kovid/work/calibre/src/calibre/gui2/metadata/basic_widgets.py:411 +msgid "&Number:" +msgstr "&Numer:" + +#: /home/kovid/work/calibre/src/calibre/gui2/metadata/basic_widgets.py:492 +msgid "" +"Last modified: %s\n" +"\n" +"Double click to view" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/metadata/basic_widgets.py:509 +msgid "Set the cover for the book from the selected format" +msgstr "Przypisz okładkę dla tej książki z wybranego formatu" + +#: /home/kovid/work/calibre/src/calibre/gui2/metadata/basic_widgets.py:517 +msgid "Set metadata for the book from the selected format" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/metadata/basic_widgets.py:524 +msgid "Add a format to this book" +msgstr "Dodaj kolej" + +#: /home/kovid/work/calibre/src/calibre/gui2/metadata/basic_widgets.py:531 +msgid "Remove the selected format from this book" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/metadata/basic_widgets.py:597 +msgid "Choose formats for " +msgstr "Wybierz formaty dla " + +#: /home/kovid/work/calibre/src/calibre/gui2/metadata/basic_widgets.py:629 +msgid "No permission" +msgstr "Brak uprawnień" + +#: /home/kovid/work/calibre/src/calibre/gui2/metadata/basic_widgets.py:630 +msgid "You do not have permission to read the following files:" +msgstr "Nie masz uprawnień do odczytu następujących plików:" + +#: /home/kovid/work/calibre/src/calibre/gui2/metadata/basic_widgets.py:660 +#: /home/kovid/work/calibre/src/calibre/gui2/metadata/basic_widgets.py:661 +msgid "No format selected" +msgstr "Nie wybrano formatu" + +#: /home/kovid/work/calibre/src/calibre/gui2/metadata/basic_widgets.py:672 +msgid "Could not read metadata" +msgstr "Nie można odczytać metadanych" + +#: /home/kovid/work/calibre/src/calibre/gui2/metadata/basic_widgets.py:673 +msgid "Could not read metadata from %s format" +msgstr "Nie można odczytać metadanych z formatu %s" + +#: /home/kovid/work/calibre/src/calibre/gui2/metadata/basic_widgets.py:692 +msgid "&Browse" +msgstr "&Przeglądaj" + +#: /home/kovid/work/calibre/src/calibre/gui2/metadata/basic_widgets.py:694 +msgid "T&rim" +msgstr "P&rzytnij" + +#: /home/kovid/work/calibre/src/calibre/gui2/metadata/basic_widgets.py:696 +msgid "&Remove" +msgstr "&Usuń" + +#: /home/kovid/work/calibre/src/calibre/gui2/metadata/basic_widgets.py:702 +msgid "Download co&ver" +msgstr "Pobierz o&kładkę" + +#: /home/kovid/work/calibre/src/calibre/gui2/metadata/basic_widgets.py:703 +msgid "&Generate cover" +msgstr "Wy&generuj okładkę" + +#: /home/kovid/work/calibre/src/calibre/gui2/metadata/basic_widgets.py:753 +msgid "Not a valid picture" +msgstr "To nie jest poprawny obrazek" + +#: /home/kovid/work/calibre/src/calibre/gui2/metadata/basic_widgets.py:777 +msgid "Specify title and author" +msgstr "Podaj tytuł i autora" + +#: /home/kovid/work/calibre/src/calibre/gui2/metadata/basic_widgets.py:778 +msgid "You must specify a title and author before generating a cover" +msgstr "Musisz podać tytuł i autora przed generacją okładki" + +#: /home/kovid/work/calibre/src/calibre/gui2/metadata/basic_widgets.py:796 +msgid "Invalid cover" +msgstr "Błędna okładka" + +#: /home/kovid/work/calibre/src/calibre/gui2/metadata/basic_widgets.py:797 +msgid "Could not change cover as the image is invalid." +msgstr "Nie zmieniłem okładki z powodu błednego obrazu" + +#: /home/kovid/work/calibre/src/calibre/gui2/metadata/basic_widgets.py:824 +msgid "This book has no cover" +msgstr "Ta książka nie ma okładki" + +#: /home/kovid/work/calibre/src/calibre/gui2/metadata/basic_widgets.py:826 +msgid "Cover size: %dx%d pixels" +msgstr "Rozmiar okładki: %dx%d pikseli" + +#: /home/kovid/work/calibre/src/calibre/gui2/metadata/basic_widgets.py:875 +msgid "stars" +msgstr "gwiazdek" + +#: /home/kovid/work/calibre/src/calibre/gui2/metadata/basic_widgets.py:944 +msgid "Tags changed" +msgstr "Etykiety zmienione" + +#: /home/kovid/work/calibre/src/calibre/gui2/metadata/basic_widgets.py:945 +msgid "" +"You have changed the tags. In order to use the tags editor, you must either " +"discard or apply these changes. Apply changes?" +msgstr "" +"Dokonałeś zmiany etykiet. Aby skorzystać z edytora etykiet, musisz albo " +"porzucić, albo zatwierdzić te zmiany. Zatwierdzić zmiany?" + +#: /home/kovid/work/calibre/src/calibre/gui2/metadata/basic_widgets.py:971 +msgid "I&ds:" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/metadata/basic_widgets.py:972 +msgid "" +"Edit the identifiers for this book. For example: \n" +"\n" +"%s" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/metadata/basic_widgets.py:1033 +msgid "This ISBN number is valid" +msgstr "Numer ISBN jest poprawny" + +#: /home/kovid/work/calibre/src/calibre/gui2/metadata/basic_widgets.py:1036 +msgid "This ISBN number is invalid" +msgstr "Numer ISBN nie jest poprawny" + +#: /home/kovid/work/calibre/src/calibre/gui2/metadata/basic_widgets.py:1050 msgid "&Publisher:" -msgstr "" +msgstr "&Wydawca:" -#: /home/kovid/work/calibre/src/calibre/gui2/metadata/basic_widgets.py:997 +#: /home/kovid/work/calibre/src/calibre/gui2/metadata/basic_widgets.py:1120 msgid "Clear date" +msgstr "Data wyczyszczenia" + +#: /home/kovid/work/calibre/src/calibre/gui2/metadata/basic_widgets.py:1152 +msgid "Publishe&d:" +msgstr "Wy&dana:" + +#: /home/kovid/work/calibre/src/calibre/gui2/metadata/bulk_download.py:34 +msgid "Schedule download?" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/metadata/bulk_download.py:109 -msgid "Book has neither title nor ISBN" -msgstr "Książka nie ma, ani tytułu, ani kodu ISBN" +#: /home/kovid/work/calibre/src/calibre/gui2/metadata/bulk_download.py:45 +msgid "" +"The download of metadata for the %d selected book(s) will run in the " +"background. Proceed?" +msgstr "" +"Pobieranie metadanych dla %d wybranych książek rozpocznie się w tle. " +"Kontynuować?" -#: /home/kovid/work/calibre/src/calibre/gui2/metadata/bulk_download.py:138 -msgid "No matches found for this book" -msgstr "Nie znaleziono wyników dla tej książki" +#: /home/kovid/work/calibre/src/calibre/gui2/metadata/bulk_download.py:47 +msgid "" +"You can monitor the progress of the download by clicking the rotating " +"spinner in the bottom right corner." +msgstr "" +"Możesz śledzić postęp pobierania, klikając kręcącą się ikonę w dolnym prawym " +"narożniku." -#: /home/kovid/work/calibre/src/calibre/gui2/metadata/bulk_download.py:187 -msgid "Failed to download metadata" +#: /home/kovid/work/calibre/src/calibre/gui2/metadata/bulk_download.py:50 +msgid "" +"When the download completes you will be asked for confirmation before " +"calibre applies the downloaded metadata." +msgstr "" +"Po zakończeniu pobierania, zostaniesz poproszony o potwierdzenie użycia " +"pobranych metadanych." + +#: /home/kovid/work/calibre/src/calibre/gui2/metadata/bulk_download.py:61 +msgid "Download only &metadata" +msgstr "Pobierz tylko &metadane" + +#: /home/kovid/work/calibre/src/calibre/gui2/metadata/bulk_download.py:65 +msgid "Download only &covers" +msgstr "Pobierz tylko &okładki" + +#: /home/kovid/work/calibre/src/calibre/gui2/metadata/bulk_download.py:69 +msgid "&Configure download" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/metadata/bulk_download.py:227 -msgid "cover" +#: /home/kovid/work/calibre/src/calibre/gui2/metadata/bulk_download.py:73 +msgid "Download &both" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/metadata/bulk_download.py:228 -msgid "Downloaded" +#: /home/kovid/work/calibre/src/calibre/gui2/metadata/bulk_download.py:99 +msgid "Download metadata for %d books" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/metadata/bulk_download.py:228 -msgid "Failed to get" +#: /home/kovid/work/calibre/src/calibre/gui2/metadata/bulk_download.py:102 +msgid "Metadata download started" +msgstr "Pobieranie metadanych rozpoczęte" + +#: /home/kovid/work/calibre/src/calibre/gui2/metadata/bulk_download.py:111 +msgid "(Failed metadata)" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/metadata/bulk_download.py:232 -msgid "%s %s for: %s" +#: /home/kovid/work/calibre/src/calibre/gui2/metadata/bulk_download.py:113 +msgid "(Failed cover)" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/metadata/bulk_download.py:291 -#: /home/kovid/work/calibre/src/calibre/gui2/preferences/behavior.py:160 -msgid "Done" -msgstr "Gotowe" - -#: /home/kovid/work/calibre/src/calibre/gui2/metadata/bulk_download.py:292 -msgid "Successfully downloaded metadata for %d out of %d books" +#: /home/kovid/work/calibre/src/calibre/gui2/metadata/bulk_download.py:190 +msgid "Downloaded %d of %d" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/metadata/bulk_download.py:294 -#: /home/kovid/work/calibre/src/calibre/library/server/browse.py:695 -msgid "Details" +#: /home/kovid/work/calibre/src/calibre/gui2/metadata/config.py:61 +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/metadata_sources_ui.py:115 +msgid "Downloaded metadata fields" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/metadata/single.py:68 -#: /home/kovid/work/calibre/src/calibre/gui2/metadata/single.py:211 +#: /home/kovid/work/calibre/src/calibre/gui2/metadata/single.py:51 +#: /home/kovid/work/calibre/src/calibre/gui2/metadata/single_download.py:824 +#: /home/kovid/work/calibre/src/calibre/library/server/browse.py:107 +#: /home/kovid/work/calibre/src/calibre/web/feeds/templates.py:211 +#: /home/kovid/work/calibre/src/calibre/web/feeds/templates.py:401 +msgid "Next" +msgstr "Następna" + +#: /home/kovid/work/calibre/src/calibre/gui2/metadata/single.py:55 +#: /home/kovid/work/calibre/src/calibre/library/server/browse.py:106 +#: /home/kovid/work/calibre/src/calibre/web/feeds/templates.py:221 +#: /home/kovid/work/calibre/src/calibre/web/feeds/templates.py:384 +msgid "Previous" +msgstr "Poprzednia" + +#: /home/kovid/work/calibre/src/calibre/gui2/metadata/single.py:80 +#: /home/kovid/work/calibre/src/calibre/gui2/metadata/single.py:265 msgid "Edit Metadata" +msgstr "Edycja metadanych" + +#: /home/kovid/work/calibre/src/calibre/gui2/metadata/single.py:101 +msgid "" +"Automatically create the title sort entry based on the current title entry.\n" +"Using this button to create title sort will change title sort from red to " +"green." +msgstr "" +"Automatycznie stwórz sortowanie według tytułu w oparciu o bieżący tytuł. " +"Używając tego\n" +"przycisku w celu stworzenia sortowania według tytułu zmieni sortowanie z " +"czerwonego na zielony." + +#: /home/kovid/work/calibre/src/calibre/gui2/metadata/single.py:112 +msgid "" +"Automatically create the author sort entry based on the current author " +"entry. Using this button to create author sort will change author sort from " +"red to green. There is a menu of functions available under this button. " +"Click and hold on the button to see it." msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/metadata/single.py:438 -#: /home/kovid/work/calibre/src/calibre/gui2/metadata/single.py:599 +#: /home/kovid/work/calibre/src/calibre/gui2/metadata/single.py:118 +msgid "Set author sort from author" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/metadata/single.py:119 +msgid "Set author from author sort" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/metadata/single.py:129 +msgid "Swap the author and title" +msgstr "Zamień autora z tytułem" + +#: /home/kovid/work/calibre/src/calibre/gui2/metadata/single.py:135 +msgid "" +"Manage authors. Use to rename authors and correct individual author's sort " +"values" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/metadata/single.py:143 +msgid "Remove unused series (Series that have no books)" +msgstr "" +"Usuń nieużywane cykle (cykle, które są nie przypisane do żadnej książki)" + +#: /home/kovid/work/calibre/src/calibre/gui2/metadata/single.py:178 +msgid "" +"Paste the contents of the clipboard into the identifiers box prefixed with " +"isbn:" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/metadata/single.py:191 +msgid "&Download metadata" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/metadata/single.py:202 +msgid "Configure download metadata" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/metadata/single.py:206 +msgid "Change how calibre downloads metadata" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/metadata/single.py:306 +#: /home/kovid/work/calibre/src/calibre/gui2/metadata/single.py:313 +msgid "Could not read cover" +msgstr "Nie udało się odczytać okładki" + +#: /home/kovid/work/calibre/src/calibre/gui2/metadata/single.py:307 +msgid "Could not read cover from %s format" +msgstr "Nie można odczytać okładki z formatu %s" + +#: /home/kovid/work/calibre/src/calibre/gui2/metadata/single.py:314 +msgid "The cover in the %s format is invalid" +msgstr "Okładka w formacie %s jest nieprawidłowa" + +#: /home/kovid/work/calibre/src/calibre/gui2/metadata/single.py:397 +msgid "Permission denied" +msgstr "Brak dostępu" + +#: /home/kovid/work/calibre/src/calibre/gui2/metadata/single.py:398 +msgid "Could not open %s. Is it being used by another program?" +msgstr "" +"Nie można otworzyć %s. Czy ten plik nie jest przypadkiem używany przez inny " +"program?" + +#: /home/kovid/work/calibre/src/calibre/gui2/metadata/single.py:450 +#: /home/kovid/work/calibre/src/calibre/gui2/metadata/single.py:455 +msgid "Save changes and edit the metadata of %s" +msgstr "Zapisz zmiany i edytuj metadane %s" + +#: /home/kovid/work/calibre/src/calibre/gui2/metadata/single.py:545 +#: /home/kovid/work/calibre/src/calibre/gui2/metadata/single.py:747 msgid "Change cover" -msgstr "" +msgstr "Zmień okładkę" -#: /home/kovid/work/calibre/src/calibre/gui2/metadata/single.py:486 +#: /home/kovid/work/calibre/src/calibre/gui2/metadata/single.py:602 msgid "Co&mments" -msgstr "" +msgstr "Ko&mentarze" -#: /home/kovid/work/calibre/src/calibre/gui2/metadata/single.py:508 +#: /home/kovid/work/calibre/src/calibre/gui2/metadata/single.py:642 +#: /home/kovid/work/calibre/src/calibre/gui2/metadata/single.py:788 msgid "&Metadata" -msgstr "" +msgstr "&Metadane" -#: /home/kovid/work/calibre/src/calibre/gui2/metadata/single.py:513 +#: /home/kovid/work/calibre/src/calibre/gui2/metadata/single.py:647 msgid "&Cover and formats" -msgstr "" +msgstr "Okładki i &formaty" -#: /home/kovid/work/calibre/src/calibre/gui2/metadata/single.py:568 +#: /home/kovid/work/calibre/src/calibre/gui2/metadata/single.py:716 msgid "C&ustom metadata" +msgstr "Własne metadane" + +#: /home/kovid/work/calibre/src/calibre/gui2/metadata/single.py:728 +msgid "&Comments" +msgstr "&Komentarze" + +#: /home/kovid/work/calibre/src/calibre/gui2/metadata/single.py:794 +msgid "Basic metadata" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/preferences/__init__.py:36 +#: /home/kovid/work/calibre/src/calibre/gui2/metadata/single_download.py:132 +msgid "Has cover" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/metadata/single_download.py:132 +msgid "Has summary" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/metadata/single_download.py:189 +msgid "" +"The has cover indication is not fully\n" +"reliable. Sometimes results marked as not\n" +"having a cover will find a cover in the download\n" +"cover stage, and vice versa." +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/metadata/single_download.py:258 +msgid "See at" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/metadata/single_download.py:393 +msgid "calibre is downloading metadata from: " +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/metadata/single_download.py:415 +msgid "Please wait" +msgstr "Proszę czekać" + +#: /home/kovid/work/calibre/src/calibre/gui2/metadata/single_download.py:444 +msgid "Query: " +msgstr "Zapytanie: " + +#: /home/kovid/work/calibre/src/calibre/gui2/metadata/single_download.py:463 +msgid "Failed to download metadata. Click Show Details to see details" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/metadata/single_download.py:472 +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 " +"single distinctive word from the title.

    To see the full log, click Show " +"Details." +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/metadata/single_download.py:538 +msgid "Current cover" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/metadata/single_download.py:541 +msgid "Searching..." +msgstr "Wyszukiwanie..." + +#: /home/kovid/work/calibre/src/calibre/gui2/metadata/single_download.py:687 +msgid "Downloading covers for %s, please wait..." +msgstr "Pobieranie okładek dla %s. Proszę czekać..." + +#: /home/kovid/work/calibre/src/calibre/gui2/metadata/single_download.py:717 +msgid "Failed to download any covers, click \"Show details\" for details." +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/metadata/single_download.py:723 +msgid "Could not find any covers for %s" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/metadata/single_download.py:725 +msgid "Found %d covers of %s. Pick the one you like best." +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/metadata/single_download.py:813 +msgid "Downloading metadata..." +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/metadata/single_download.py:906 +msgid "Downloading cover..." +msgstr "Pobieranie okładki..." + +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/__init__.py:37 msgid "" "Restore settings to default values. You have to click Apply to actually save " "the default settings." msgstr "" +"Przywróć ustawienia na domyślne wartości. Musisz kliknąć w Zastosuj, aby " +"zapisać ustawienia domyślne." + +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/__init__.py:328 +msgid "Configure " +msgstr "" #: /home/kovid/work/calibre/src/calibre/gui2/preferences/adding.py:28 msgid "Ignore duplicate incoming formats" -msgstr "" +msgstr "Ignoruj zduplikowane formaty" #: /home/kovid/work/calibre/src/calibre/gui2/preferences/adding.py:29 msgid "Overwrite existing duplicate formats" -msgstr "" +msgstr "Nadpisz istniejące zduplikowane formaty" #: /home/kovid/work/calibre/src/calibre/gui2/preferences/adding.py:30 msgid "Create new record for each duplicate format" -msgstr "" +msgstr "Stwórz nowy rekord dla każdego zduplikowatego formatu" #: /home/kovid/work/calibre/src/calibre/gui2/preferences/adding_ui.py:65 msgid "" @@ -10482,10 +11617,13 @@ msgid "" "to it. calibre can either read metadata from the contents of the file, or " "from the filename." msgstr "" +"Tutaj możesz ustawić w jaki sposób calibre będzie czytał metadane z plików " +"które dodasz do programu. calibre albo przeczyta metadane z zawartości pliku " +"albo z nazwy pliku." #: /home/kovid/work/calibre/src/calibre/gui2/preferences/adding_ui.py:66 msgid "Read &metadata from &file contents rather than file name" -msgstr "" +msgstr "Odczytaj &metadane prędzej z zawartości &pliku niż nazwy pliku" #: /home/kovid/work/calibre/src/calibre/gui2/preferences/adding_ui.py:67 msgid "" @@ -10511,10 +11649,20 @@ msgid "" "Title match ignores leading indefinite articles (\"the\", \"a\", \"an\"), " "punctuation, case, etc. Author match is exact." msgstr "" +"Autoscalanie: Jeśli znalezione książki mają identyczne tytuły i autorów, " +"połącz dołączane formaty do\n" +"istniejących rekordów książki. Opcja po prawej odpowiada za to co się stanie " +"gdy istniejący rekord ma już\n" +"dołączany format. Weź pod uwagę, że ta opcja ma również wpływ na akcję " +"Kopiuj do biblioteki.\n" +"\n" +"Porównanie tytułów ignoruje poprzedzające przedimki nieokreślone (\"the\", " +"\"a\", \"an\"), interpunkcję, duże, małe litery itd. Porównanie autora jest " +"dokładne." #: /home/kovid/work/calibre/src/calibre/gui2/preferences/adding_ui.py:74 msgid "&Automerge added books if they already exist in the calibre library:" -msgstr "" +msgstr "&Autoscal dodane książki jeśli już istnieją w bibliotece calibre:" #: /home/kovid/work/calibre/src/calibre/gui2/preferences/adding_ui.py:75 msgid "" @@ -10535,277 +11683,431 @@ msgid "" "punctuation, case, etc.\n" "Author matching is exact." msgstr "" +"Autoscalanie: Jeśli znalezione książki mają identyczne tytuły i autorów, " +"połącz automatycznie dołączane\n" +"formaty do istniejących rekordów książki. Opcja ta odpowiada za to co się " +"stanie gdy istniejący rekord ma już\n" +"dołączany format:\n" +"\n" +"Ignoruj zduplikowane dołączane pliki - oznacza, że istniejące pliki w twojej " +"bibliotece calibrenie zostaną zastąpione\n" +"Nadpisz zduplikowane pliki - oznacza, że istniejące pliki w twojej " +"bibliotece calibre zostaną zastąpione\n" +"Stwórz nowy rekord dla każdego zduplikowanego pliku - stworzony zostanie " +"nowy wpis książki dla każdego zduplikowanego pliku\n" +"\n" +"Porównanie tytułów ignoruje poprzedzające przedimki nieokreślone (\"the\", " +"\"a\", \"an\"), interpunkcję, duże, małe litery itd. Porównanie autora jest " +"dokładne." #: /home/kovid/work/calibre/src/calibre/gui2/preferences/adding_ui.py:85 msgid "&Tags to apply when adding a book:" -msgstr "" +msgstr "E&tykiety do zastosowanie przy dodawaniu książki:" #: /home/kovid/work/calibre/src/calibre/gui2/preferences/adding_ui.py:86 msgid "" "A comma-separated list of tags that will be applied to books added to the " "library" msgstr "" +"Lista etykiet oddzielonych przecinkiem, które zostaną zastosowane do książek " +"dodawanych do biblioteki" #: /home/kovid/work/calibre/src/calibre/gui2/preferences/adding_ui.py:87 msgid "&Configure metadata from file name" msgstr "&Konfiguruj metadane pobierane z nazwy pliku" -#: /home/kovid/work/calibre/src/calibre/gui2/preferences/behavior.py:32 -#: /home/kovid/work/calibre/src/calibre/gui2/preferences/behavior_ui.py:140 -msgid "High" -msgstr "Wysoki" - -#: /home/kovid/work/calibre/src/calibre/gui2/preferences/behavior.py:32 -#: /home/kovid/work/calibre/src/calibre/gui2/preferences/behavior_ui.py:141 +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/behavior.py:34 +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/behavior.py:36 +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/behavior_ui.py:160 msgid "Low" msgstr "Niski" -#: /home/kovid/work/calibre/src/calibre/gui2/preferences/behavior.py:161 -msgid "Confirmation dialogs have all been reset" +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/behavior.py:34 +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/behavior_ui.py:159 +msgid "High" +msgstr "Wysoki" + +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/behavior.py:36 +msgid "Very low" +msgstr "Bardzo niski" + +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/behavior.py:64 +msgid "Compact Metadata" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/preferences/behavior_ui.py:131 -msgid "&Overwrite author and title by default when fetching metadata" -msgstr "Domyślnie &nadpisuj autora i tytuł przy pobieraniu metadanych" +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/behavior.py:65 +msgid "All on 1 tab" +msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/preferences/behavior_ui.py:132 -msgid "Download &social metadata (tags/ratings/etc.) by default" -msgstr "Pobieraj domyślnie metadane &społecznościowe (etykiety/oceny/itd.)" +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/behavior.py:165 +msgid "Done" +msgstr "Gotowe" -#: /home/kovid/work/calibre/src/calibre/gui2/preferences/behavior_ui.py:133 +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/behavior.py:166 +msgid "Confirmation dialogs have all been reset" +msgstr "Wszystkie zapytania o potwierdzenie zostały zresetowane" + +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/behavior_ui.py:147 msgid "Show notification when &new version is available" msgstr "Pokaż powiadomienie, gdy &nowa wersja jest dostępna" -#: /home/kovid/work/calibre/src/calibre/gui2/preferences/behavior_ui.py:134 +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/behavior_ui.py:148 +msgid "" +"If checked, Yes/No custom columns values can be Yes, No, or Unknown.\n" +"If not checked, the values can be Yes or No." +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/behavior_ui.py:150 +msgid "Yes/No columns have three values (Requires restart)" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/behavior_ui.py:151 msgid "Automatically send downloaded &news to ebook reader" msgstr "Automatycznie przesyłaj pobrane newsy na czytnik" -#: /home/kovid/work/calibre/src/calibre/gui2/preferences/behavior_ui.py:135 +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/behavior_ui.py:152 msgid "&Delete news from library when it is automatically sent to reader" msgstr "" -"&Usuń wiadomości z biblioteki, gdy jest automatycznie wysyłane do czytnika" +"&Usuń wiadomości z biblioteki, gdy są automatycznie wysyłane do czytnika" -#: /home/kovid/work/calibre/src/calibre/gui2/preferences/behavior_ui.py:136 +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/behavior_ui.py:153 +msgid "Preferred &output format:" +msgstr "Preferowany format &docelowy:" + +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/behavior_ui.py:154 msgid "Default network &timeout:" msgstr "Domyślny sieciowy &czas oczekiwania:" -#: /home/kovid/work/calibre/src/calibre/gui2/preferences/behavior_ui.py:137 +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/behavior_ui.py:155 msgid "" "Set the default timeout for network fetches (i.e. anytime we go out to the " "internet to get information)" msgstr "" +"Wyznacz domyślny czas oczekiwania dla pobierania z sieci (np. za każdym " +"razem gdy włączamy internet w celu uzyskania informacji)" -#: /home/kovid/work/calibre/src/calibre/gui2/preferences/behavior_ui.py:138 +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/behavior_ui.py:156 msgid " seconds" msgstr " sekund" -#: /home/kovid/work/calibre/src/calibre/gui2/preferences/behavior_ui.py:142 +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/behavior_ui.py:157 msgid "Job &priority:" msgstr "&Priorytet zdań:" -#: /home/kovid/work/calibre/src/calibre/gui2/preferences/behavior_ui.py:143 -msgid "Preferred &output format:" -msgstr "Preferowany format &docelowy:" - -#: /home/kovid/work/calibre/src/calibre/gui2/preferences/behavior_ui.py:144 +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/behavior_ui.py:161 msgid "Restriction to apply when the current library is opened:" -msgstr "" +msgstr "Zastosowane ograniczenie gdy jest otwarta bieżąca biblioteka:" -#: /home/kovid/work/calibre/src/calibre/gui2/preferences/behavior_ui.py:145 +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/behavior_ui.py:162 msgid "" "Apply this restriction on calibre startup if the current library is being " "used. Also applied when switching to this library. Note that this setting is " "per library. " msgstr "" +"Zastosuj to ograniczenie podczas uruchamiania programu calibre jeśli bieżąca " +"biblioteka jest w użyciu. To ustawienie jest dla biblioteki. " -#: /home/kovid/work/calibre/src/calibre/gui2/preferences/behavior_ui.py:146 -msgid "Reset all disabled &confirmation dialogs" +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/behavior_ui.py:163 +msgid "Edit metadata (single) layout:" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/preferences/behavior_ui.py:147 +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/behavior_ui.py:164 +msgid "" +"Choose a different layout for the Edit Metadata dialog. The compact metadata " +"layout favors editing custom metadata over changing covers and formats." +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/behavior_ui.py:165 msgid "Preferred &input format order:" msgstr "Preferowany porządek formatów &źródłowych:" -#: /home/kovid/work/calibre/src/calibre/gui2/preferences/behavior_ui.py:150 +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/behavior_ui.py:168 msgid "Use internal &viewer for:" msgstr "Użyj &wewnętrzej przeglądarki dla:" +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/behavior_ui.py:169 +msgid "Reset all disabled &confirmation dialogs" +msgstr "Zresetuj wszystkie wyłączone zapytania o potwierdzenie" + #: /home/kovid/work/calibre/src/calibre/gui2/preferences/columns.py:96 msgid "You must select a column to delete it" msgstr "Musisz wybrać kolumnę, by móc ją usunąć" #: /home/kovid/work/calibre/src/calibre/gui2/preferences/columns.py:101 msgid "The selected column is not a custom column" -msgstr "" +msgstr "Wskazana kolumna nie jest kolumną dodatkową" #: /home/kovid/work/calibre/src/calibre/gui2/preferences/columns.py:103 msgid "Do you really want to delete column %s and all its data?" msgstr "Czy na pewno chcesz usunąć kolumnę %s i całą jej zawartość?" -#: /home/kovid/work/calibre/src/calibre/gui2/preferences/columns_ui.py:82 +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/columns_ui.py:87 #: /home/kovid/work/calibre/src/calibre/gui2/preferences/custom_columns_ui.py:82 msgid "" "Here you can re-arrange the layout of the columns in the calibre library " "book list. You can hide columns by unchecking them. You can also create your " "own, custom columns." msgstr "" +"Tutaj możesz zmienić układ kolumn w liście książek biblioteki calibre. " +"Możesz ukryć kolumny odznaczając je. Możesz również stworzyć własne, " +"dodatkowe kolumny." -#: /home/kovid/work/calibre/src/calibre/gui2/preferences/columns_ui.py:84 +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/columns_ui.py:88 +msgid "Move column up" +msgstr "Przenieś kolumnę w górę" + +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/columns_ui.py:90 #: /home/kovid/work/calibre/src/calibre/gui2/preferences/custom_columns_ui.py:84 msgid "Remove a user-defined column" msgstr "Usuń kolumnę użytkownika" -#: /home/kovid/work/calibre/src/calibre/gui2/preferences/columns_ui.py:86 +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/columns_ui.py:92 #: /home/kovid/work/calibre/src/calibre/gui2/preferences/custom_columns_ui.py:86 msgid "Add a user-defined column" msgstr "Dodaj kolumnę użytkownika" -#: /home/kovid/work/calibre/src/calibre/gui2/preferences/columns_ui.py:88 +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/columns_ui.py:94 #: /home/kovid/work/calibre/src/calibre/gui2/preferences/custom_columns_ui.py:88 msgid "Edit settings of a user-defined column" msgstr "Edytuj ustawienia kolumny użytkownika" -#: /home/kovid/work/calibre/src/calibre/gui2/preferences/columns_ui.py:91 +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/columns_ui.py:96 +msgid "Move column down" +msgstr "Przenieś kolumnę w dół" + +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/columns_ui.py:98 #: /home/kovid/work/calibre/src/calibre/gui2/preferences/custom_columns_ui.py:91 msgid "Add &custom column" -msgstr "" +msgstr "Dodaj dodatkową &kolumnę" -#: /home/kovid/work/calibre/src/calibre/gui2/preferences/conversion.py:39 +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/conversion.py:41 msgid "" "Restore settings to default values. Only settings for the currently selected " "section are restored." msgstr "" +"Przywróć ustawienia do wartości domyślnych. Tylko ustawienia dla obecnie " +"zaznaczonych sekcji zostaną przywrócone." -#: /home/kovid/work/calibre/src/calibre/gui2/preferences/create_custom_column.py:19 +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/create_custom_column.py:18 msgid "Text, column shown in the tag browser" -msgstr "" +msgstr "Tekst, kolumna pokazywana w przeglądarce etykiet" -#: /home/kovid/work/calibre/src/calibre/gui2/preferences/create_custom_column.py:22 +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/create_custom_column.py:21 msgid "Comma separated text, like tags, shown in the tag browser" msgstr "" +"Tekst rozdzielany przecinkiem, jak etykiety, pokazywany w przeglądarce " +"etykiet" -#: /home/kovid/work/calibre/src/calibre/gui2/preferences/create_custom_column.py:25 +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/create_custom_column.py:24 msgid "Long text, like comments, not shown in the tag browser" -msgstr "Długi tekst, jak komentarze, nie pokazywany w Przeglądarce etykiet" +msgstr "Długi tekst, jak komentarze, nie pokazywany w przeglądarce etykiet" -#: /home/kovid/work/calibre/src/calibre/gui2/preferences/create_custom_column.py:28 +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/create_custom_column.py:27 msgid "Text column for keeping series-like information" -msgstr "" +msgstr "Tekst kolumny dla utrzymania informacji cyklo-podobnych" -#: /home/kovid/work/calibre/src/calibre/gui2/preferences/create_custom_column.py:31 +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/create_custom_column.py:30 msgid "Text, but with a fixed set of permitted values" -msgstr "" +msgstr "Tekst, ale z ustalonym zestawem dozwolonych wartości" -#: /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:34 msgid "Floating point numbers" msgstr "Liczby wymierne" -#: /home/kovid/work/calibre/src/calibre/gui2/preferences/create_custom_column.py:37 +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/create_custom_column.py:36 msgid "Integers" msgstr "Liczby całkowite" -#: /home/kovid/work/calibre/src/calibre/gui2/preferences/create_custom_column.py:39 +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/create_custom_column.py:38 msgid "Ratings, shown with stars" -msgstr "Oceany pokazywane gwiazdkami" +msgstr "Oceny pokazywane gwiazdkami" -#: /home/kovid/work/calibre/src/calibre/gui2/preferences/create_custom_column.py:42 +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/create_custom_column.py:41 +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/create_custom_column.py:66 +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/create_custom_column.py:73 +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/create_custom_column.py:149 msgid "Yes/No" msgstr "Tak/Nie" -#: /home/kovid/work/calibre/src/calibre/gui2/preferences/create_custom_column.py:44 +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/create_custom_column.py:43 msgid "Column built from other columns" +msgstr "Kolumna składająca się z innych kolumn" + +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/create_custom_column.py:45 +msgid "Column built from other columns, behaves like tags" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/preferences/create_custom_column.py:74 +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/create_custom_column.py:52 +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/create_custom_column.py:53 +msgid "Create a custom column" +msgstr "Stwórz własną kolumnę" + +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/create_custom_column.py:64 +msgid "Quick create:" +msgstr "Szybkie tworzenie:" + +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/create_custom_column.py:65 +msgid "ISBN" +msgstr "ISBN" + +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/create_custom_column.py:65 +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/create_custom_column.py:148 +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/emailp.py:27 +#: /home/kovid/work/calibre/src/calibre/library/field_metadata.py:124 +msgid "Formats" +msgstr "Formaty" + +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/create_custom_column.py:68 +msgid "People's names" +msgstr "Nazwiska ludzi" + +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/create_custom_column.py:73 +msgid "Number" +msgstr "Liczba" + +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/create_custom_column.py:73 +msgid "Text" +msgstr "Tekst" + +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/create_custom_column.py:88 +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/create_custom_column.py:89 +msgid "Edit a custom column" +msgstr "Edycja własnej kolumny" + +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/create_custom_column.py:93 msgid "No column selected" msgstr "Nie wybrano kolumny" -#: /home/kovid/work/calibre/src/calibre/gui2/preferences/create_custom_column.py:75 +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/create_custom_column.py:94 msgid "No column has been selected" msgstr "Żadna kolumna nie została wybrana" -#: /home/kovid/work/calibre/src/calibre/gui2/preferences/create_custom_column.py:79 +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/create_custom_column.py:98 msgid "Selected column is not a user-defined column" msgstr "Wybrana kolumna nie jest kolumną użytkownika" -#: /home/kovid/work/calibre/src/calibre/gui2/preferences/create_custom_column.py:117 -msgid "No lookup name was provided" -msgstr "" +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/create_custom_column.py:150 +msgid "My Tags" +msgstr "Moje etykiety" -#: /home/kovid/work/calibre/src/calibre/gui2/preferences/create_custom_column.py:119 +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/create_custom_column.py:151 +msgid "My Series" +msgstr "Moje cykle" + +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/create_custom_column.py:152 +msgid "My Rating" +msgstr "Moje oceny" + +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/create_custom_column.py:153 +msgid "People" +msgstr "Ludzie" + +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/create_custom_column.py:181 +msgid "No lookup name was provided" +msgstr "Nie podano nazwy do wyszukania" + +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/create_custom_column.py:185 msgid "" "The lookup name must contain only lower case letters, digits and " "underscores, and start with a letter" msgstr "" +"Nazwy do wyszukania mogą zawierać tylko małe litery, cyfry i podkreślniki, i " +"powinny zaczynać się od litery" -#: /home/kovid/work/calibre/src/calibre/gui2/preferences/create_custom_column.py:122 +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/create_custom_column.py:188 msgid "" "Lookup names cannot end with _index, because these names are reserved for " "the index of a series column." msgstr "" +"Nazwy do wyszukania nie mogą się kończyć ciągiem _index, ponieważ te nazwy " +"są zarezerwowane dla indeksów kolumny cykli" -#: /home/kovid/work/calibre/src/calibre/gui2/preferences/create_custom_column.py:132 +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/create_custom_column.py:198 msgid "No column heading was provided" -msgstr "" +msgstr "Nie podano nagłówka kolumny" -#: /home/kovid/work/calibre/src/calibre/gui2/preferences/create_custom_column.py:139 +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/create_custom_column.py:208 msgid "The lookup name %s is already used" -msgstr "" +msgstr "Nazwa wyszukania %s już została wykorzystana" -#: /home/kovid/work/calibre/src/calibre/gui2/preferences/create_custom_column.py:151 +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/create_custom_column.py:220 msgid "The heading %s is already used" -msgstr "" +msgstr "Nagłówek %s już został wykorzystany" -#: /home/kovid/work/calibre/src/calibre/gui2/preferences/create_custom_column.py:162 +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/create_custom_column.py:231 msgid "You must enter a template for composite columns" -msgstr "" +msgstr "Musisz wprowadzić szablon dla kolumn złożonych" -#: /home/kovid/work/calibre/src/calibre/gui2/preferences/create_custom_column.py:167 +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/create_custom_column.py:240 msgid "You must enter at least one value for enumeration columns" msgstr "" +"Musisz wprowadzić przynajmniej jedną wartość dla kolumn przeliczających" -#: /home/kovid/work/calibre/src/calibre/gui2/preferences/create_custom_column.py:171 +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/create_custom_column.py:244 msgid "You cannot provide the empty value, as it is included by default" msgstr "" +"Nie możesz wpisać pustej wartości, tak jak to jest załączone domyślnie" -#: /home/kovid/work/calibre/src/calibre/gui2/preferences/create_custom_column.py:175 +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/create_custom_column.py:248 msgid "The value \"{0}\" is in the list more than once" -msgstr "" +msgstr "Wartość \"{0}\" występuje w liście więcej niż jeden raz" -#: /home/kovid/work/calibre/src/calibre/gui2/preferences/create_custom_column_ui.py:145 -#: /home/kovid/work/calibre/src/calibre/gui2/preferences/create_custom_column_ui.py:171 -msgid "Create or edit custom columns" -msgstr "" - -#: /home/kovid/work/calibre/src/calibre/gui2/preferences/create_custom_column_ui.py:146 +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/create_custom_column_ui.py:190 msgid "&Lookup name" -msgstr "" +msgstr "&Nazwa wyszukania" -#: /home/kovid/work/calibre/src/calibre/gui2/preferences/create_custom_column_ui.py:147 +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/create_custom_column_ui.py:191 msgid "Column &heading" msgstr "&Nagłówek kolumny" -#: /home/kovid/work/calibre/src/calibre/gui2/preferences/create_custom_column_ui.py:148 +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/create_custom_column_ui.py:192 msgid "" "Used for searching the column. Must contain only digits and lower case " "letters." msgstr "" "Używany do przeszukiwania kolumny. Może zawierać tylko cyfry i małe litery." -#: /home/kovid/work/calibre/src/calibre/gui2/preferences/create_custom_column_ui.py:149 +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/create_custom_column_ui.py:193 msgid "" "Column heading in the library view and category name in the tag browser" msgstr "" +"Nagłówek kolumny w widoku biblioteki i nazwa kategorii w przeglądarce etykiet" -#: /home/kovid/work/calibre/src/calibre/gui2/preferences/create_custom_column_ui.py:150 -msgid "Column &type" -msgstr "&Typ kolumny" +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/create_custom_column_ui.py:194 +msgid "&Column type" +msgstr "Typ &kolumny" -#: /home/kovid/work/calibre/src/calibre/gui2/preferences/create_custom_column_ui.py:151 +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/create_custom_column_ui.py:195 msgid "What kind of information will be kept in the column." msgstr "Jaki typ informacji będzie przechowywany w kolumnie." -#: /home/kovid/work/calibre/src/calibre/gui2/preferences/create_custom_column_ui.py:152 +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/create_custom_column_ui.py:196 +msgid "" +"Show check marks in the GUI. Values of 'yes', 'checked', and 'true'\n" +"will show a green check. Values of 'no', 'unchecked', and 'false' will show " +"a red X.\n" +"Everything else will show nothing." +msgstr "" +"Pokazuje znaczki potwierdzeń w interfejsie graficznym. Wartości 'tak', " +"'zaznaczony',\n" +"i 'prawda' będą oznaczone na zielono. Wartości 'nie', 'niezaznaczony', i " +"'fałsz'\n" +"będą oznaczone na czerwono. Pozostałe wartości nic nie pokażą." + +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/create_custom_column_ui.py:199 +msgid "Show checkmarks" +msgstr "Pokaż znaczki" + +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/create_custom_column_ui.py:200 +msgid "" +"Check this box if this column contains names, like the authors column." +msgstr "Zaznacz to jeśli ta kolumna zawiera nazwy, tak jak kolumna autorów" + +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/create_custom_column_ui.py:201 +msgid "Contains names" +msgstr "Zawiera nazwy" + +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/create_custom_column_ui.py:202 msgid "" "

    Date format. Use 1-4 'd's for day, 1-4 'M's for month, and 2 or 4 'y's " "for year.

    \n" @@ -10815,61 +12117,122 @@ msgid "" "
  • dd MMMM yy gives 05 January 10
  • \n" " " msgstr "" +"

    Format daty. Użyj do 1 do 4 'd' dla dnia, od 1 do 4 'M' dla miesiąca, i " +"od 2 do 4 'r' dla roku.

    \n" +"

    Na przykład:\n" +"

      \n" +"
    • ddd, d MMM rrrr wyświetli Pon, 5 Sty 2010
    • \n" +"
    • dd MMMM yy wyświetli 05 Stycznia 10
    • \n" +"
    " -#: /home/kovid/work/calibre/src/calibre/gui2/preferences/create_custom_column_ui.py:158 +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/create_custom_column_ui.py:208 msgid "Use MMM yyyy for month + year, yyyy for year only" -msgstr "" +msgstr "Użyj MMM rrrr dla miesiąca + roku, rrrr dla roku" -#: /home/kovid/work/calibre/src/calibre/gui2/preferences/create_custom_column_ui.py:159 +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/create_custom_column_ui.py:209 msgid "Default: dd MMM yyyy." -msgstr "Domyślnie: dd MMM yyyy." +msgstr "Domyślnie: dd MMM rrrr." -#: /home/kovid/work/calibre/src/calibre/gui2/preferences/create_custom_column_ui.py:160 +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/create_custom_column_ui.py:210 msgid "Format for &dates" msgstr "Format dla &dat" -#: /home/kovid/work/calibre/src/calibre/gui2/preferences/create_custom_column_ui.py:161 -msgid "Field template. Uses the same syntax as save templates." -msgstr "" - -#: /home/kovid/work/calibre/src/calibre/gui2/preferences/create_custom_column_ui.py:162 -msgid "Similar to save templates. For example, {title} {isbn}" -msgstr "" - -#: /home/kovid/work/calibre/src/calibre/gui2/preferences/create_custom_column_ui.py:163 -#: /home/kovid/work/calibre/src/calibre/gui2/preferences/create_custom_column_ui.py:170 -msgid "Default: (nothing)" -msgstr "" - -#: /home/kovid/work/calibre/src/calibre/gui2/preferences/create_custom_column_ui.py:164 +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/create_custom_column_ui.py:211 msgid "&Template" -msgstr "" +msgstr "&Szablon" -#: /home/kovid/work/calibre/src/calibre/gui2/preferences/create_custom_column_ui.py:165 +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/create_custom_column_ui.py:212 +msgid "Field template. Uses the same syntax as save templates." +msgstr "Szablona pola. Używa takiej samej składni jak zapisane szablony." + +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/create_custom_column_ui.py:213 +msgid "Similar to save templates. For example, {title} {isbn}" +msgstr "Podobny do zapisywania szablonu. Na przykład, {tytuł} {isbn}" + +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/create_custom_column_ui.py:214 +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/create_custom_column_ui.py:224 +msgid "Default: (nothing)" +msgstr "Domyślnie: (nic)" + +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/create_custom_column_ui.py:215 +msgid "&Sort/search column by" +msgstr "&Sortowanie/wyszukiwanie kolumny po" + +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/create_custom_column_ui.py:216 +msgid "How this column should handled in the GUI when sorting and searching" +msgstr "" +"Jak ta kolumna powinna się zachować w interfejsie graficznym podczas " +"sortowania i wyszukiwania" + +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/create_custom_column_ui.py:217 +msgid "If checked, this column will appear in the tags browser as a category" +msgstr "" +"Jeśli zaznaczone, ta kolumna pojawi się w przeglądarce etykiet jako kategoria" + +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/create_custom_column_ui.py:218 +msgid "Show in tags browser" +msgstr "Pokaż w przeglądarce etykiet" + +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/create_custom_column_ui.py:219 msgid "Values" -msgstr "" +msgstr "Wartości" -#: /home/kovid/work/calibre/src/calibre/gui2/preferences/create_custom_column_ui.py:166 +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/create_custom_column_ui.py:220 msgid "" "A comma-separated list of permitted values. The empty value is always\n" "included, and is the default. For example, the list 'one,two,three' has\n" "four values, the first of them being the empty value." msgstr "" +"Lista dozwolonych wartości oddzielonych przecinkiem. Pusta wartość jest " +"zawsze\n" +"załączona i jest domyślna. Na przykład, lista 'jeden,dwa,trzy' ma 4 " +"wartości,\n" +"pierwszą z nich jest pusta wartość." -#: /home/kovid/work/calibre/src/calibre/gui2/preferences/create_custom_column_ui.py:169 +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/create_custom_column_ui.py:223 msgid "The empty string is always the first value" -msgstr "" +msgstr "Pusty ciąg znaków zawsze jest pierwszą wartością" #: /home/kovid/work/calibre/src/calibre/gui2/preferences/device_debug.py:21 msgid "Getting debug information" -msgstr "" +msgstr "Uzyskuję informację do debugowania" #: /home/kovid/work/calibre/src/calibre/gui2/preferences/device_debug.py:22 +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/device_user_defined.py:32 msgid "Copy to &clipboard" msgstr "Kopiuj do &schowka" #: /home/kovid/work/calibre/src/calibre/gui2/preferences/device_debug.py:24 msgid "Debug device detection" +msgstr "Wykrycie urządzenia do debugowania" + +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/device_user_defined.py:31 +msgid "Getting device information" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/device_user_defined.py:34 +msgid "User-defined device information" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/device_user_defined.py:51 +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/device_user_defined.py:57 +msgid "Device Detection" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/device_user_defined.py:52 +msgid "Ensure your device is disconnected, then press OK" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/device_user_defined.py:58 +msgid "Ensure your device is connected, then press OK" +msgstr "Upewnij się, że urządzenie jest odłączone, a następnie wciśnij OK" + +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/device_user_defined.py:88 +msgid "" +"Copy these values to the clipboard, paste them into an editor, then enter " +"them into the USER_DEVICE by customizing the device plugin in Preferences-" +">Plugins. Remember to also enter the folders where you want the books to be " +"put. You must restart calibre for your changes to take effect.\n" msgstr "" #: /home/kovid/work/calibre/src/calibre/gui2/preferences/email_ui.py:66 @@ -10878,10 +12241,13 @@ msgid "" "automatically sent for downloaded news to all email addresses that have Auto-" "send checked." msgstr "" +"calibre może wysyłać twoje książki do ciebie (lub twojego czytnika) emailem. " +"Emaile będą wysyłane automatycznie dla pobranych newsów na wszystkie adresy " +"emailowe które mają zaznaczoną opcję auto-wysyłania." #: /home/kovid/work/calibre/src/calibre/gui2/preferences/email_ui.py:67 msgid "Add an email address to which to send books" -msgstr "Dodaj adres email, na którego chcesz wysłać ksiązki" +msgstr "Dodaj adres email, na którego chcesz wysłać książki" #: /home/kovid/work/calibre/src/calibre/gui2/preferences/email_ui.py:68 msgid "&Add email" @@ -10895,124 +12261,166 @@ msgstr "&Ustaw jako domyślny" msgid "&Remove email" msgstr "&Usuń email" -#: /home/kovid/work/calibre/src/calibre/gui2/preferences/emailp.py:24 +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/emailp.py:27 msgid "Auto send" msgstr "Wysyłaj automatycznie" -#: /home/kovid/work/calibre/src/calibre/gui2/preferences/emailp.py:24 +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/emailp.py:27 msgid "Email" msgstr "E-mail" -#: /home/kovid/work/calibre/src/calibre/gui2/preferences/emailp.py:29 +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/emailp.py:32 msgid "Formats to email. The first matching format will be sent." +msgstr "Formaty emaila. Pierwszy pasujący format zostanie wysłany." + +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/emailp.py:33 +msgid "" +"Subject of the email to use when sending. When left blank the title will be " +"used for the subject. Also, the same templates used for \"Save to disk\" " +"such as {title} and {author_sort} can be used here." msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/preferences/emailp.py:30 +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/emailp.py:37 msgid "" "If checked, downloaded news will be automatically mailed
    to this email " "address (provided it is in one of the listed formats)." msgstr "" +"Jeśli zaznaczone, pobrane newsy zostaną automatycznie wysłane
    na ten " +"adres (pod warunkiem że jest w jednym z wyświetlonych formatów)." -#: /home/kovid/work/calibre/src/calibre/gui2/preferences/emailp.py:104 +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/emailp.py:115 msgid "new email address" msgstr "nowy adres email" -#: /home/kovid/work/calibre/src/calibre/gui2/preferences/look_feel.py:25 +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/look_feel.py:103 msgid "Narrow" msgstr "Wąski" -#: /home/kovid/work/calibre/src/calibre/gui2/preferences/look_feel.py:25 +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/look_feel.py:103 msgid "Wide" msgstr "Szeroki" -#: /home/kovid/work/calibre/src/calibre/gui2/preferences/look_feel.py:51 -msgid "Medium" -msgstr "Średnie" +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/look_feel.py:128 +msgid "Off" +msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/preferences/look_feel.py:51 +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/look_feel.py:128 msgid "Small" msgstr "Małe" -#: /home/kovid/work/calibre/src/calibre/gui2/preferences/look_feel.py:52 +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/look_feel.py:129 msgid "Large" msgstr "Duże" -#: /home/kovid/work/calibre/src/calibre/gui2/preferences/look_feel.py:55 +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/look_feel.py:129 +msgid "Medium" +msgstr "Średnie" + +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/look_feel.py:132 msgid "Always" msgstr "Zawsze" -#: /home/kovid/work/calibre/src/calibre/gui2/preferences/look_feel.py:55 +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/look_feel.py:132 msgid "Automatic" msgstr "Automatycznie" -#: /home/kovid/work/calibre/src/calibre/gui2/preferences/look_feel.py:56 +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/look_feel.py:133 msgid "Never" msgstr "Nigdy" -#: /home/kovid/work/calibre/src/calibre/gui2/preferences/look_feel.py:59 +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/look_feel.py:136 msgid "By first letter" -msgstr "" +msgstr "Od pierwszej litery" -#: /home/kovid/work/calibre/src/calibre/gui2/preferences/look_feel.py:59 +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/look_feel.py:136 msgid "Disabled" -msgstr "" +msgstr "Wyłączone" -#: /home/kovid/work/calibre/src/calibre/gui2/preferences/look_feel.py:60 +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/look_feel.py:137 msgid "Partitioned" -msgstr "" +msgstr "Podzielone na partycje" -#: /home/kovid/work/calibre/src/calibre/gui2/preferences/look_feel_ui.py:136 +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/look_feel_ui.py:198 msgid "User Interface &layout (needs restart):" -msgstr "&Układ interfejsu użytkownika (wymaga restartu):" +msgstr "&Układ interfejsu użytkownika (wymaga ponownego uruchomienia):" -#: /home/kovid/work/calibre/src/calibre/gui2/preferences/look_feel_ui.py:137 -msgid "&Number of covers to show in browse mode (needs restart):" -msgstr "" -"&Liczba wyświetlanych okładek w trybie przeglądarki (wymaga restartu):" - -#: /home/kovid/work/calibre/src/calibre/gui2/preferences/look_feel_ui.py:138 +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/look_feel_ui.py:199 msgid "Choose &language (requires restart):" -msgstr "Wybierz &język (wymaga restartu):" +msgstr "Wybierz &język (wymaga ponownego uruchomienia):" -#: /home/kovid/work/calibre/src/calibre/gui2/preferences/look_feel_ui.py:139 -msgid "Show &average ratings in the tags browser" -msgstr "Pokaż &średnie oceny w Przeglądarce etykiet" - -#: /home/kovid/work/calibre/src/calibre/gui2/preferences/look_feel_ui.py:140 -msgid "Disable all animations. Useful if you have a slow/old computer." -msgstr "" -"Zablokuj wszystkie animacje. Przydatne, jeśli masz wolny/stary komputer." - -#: /home/kovid/work/calibre/src/calibre/gui2/preferences/look_feel_ui.py:141 -msgid "Disable &animations" -msgstr "Zablokuj &animacje" - -#: /home/kovid/work/calibre/src/calibre/gui2/preferences/look_feel_ui.py:142 +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/look_feel_ui.py:200 msgid "Enable system &tray icon (needs restart)" msgstr "" "Aktywuj ikonę w &zasobniku systemowym (wymaga ponownego uruchomienia)" -#: /home/kovid/work/calibre/src/calibre/gui2/preferences/look_feel_ui.py:143 +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/look_feel_ui.py:201 +msgid "Disable all animations. Useful if you have a slow/old computer." +msgstr "" +"Zablokuj wszystkie animacje. Przydatne, jeśli masz wolny/stary komputer." + +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/look_feel_ui.py:202 +msgid "Disable &animations" +msgstr "Zablokuj &animacje" + +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/look_feel_ui.py:203 +msgid "Disable ¬ifications in system tray" +msgstr "Wyłącz powiadomienia w zasob&niku systemowym" + +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/look_feel_ui.py:204 msgid "Show &splash screen at startup" msgstr "Pokaż planszę &startową podczas uruchomienia" -#: /home/kovid/work/calibre/src/calibre/gui2/preferences/look_feel_ui.py:144 -msgid "Disable ¬ifications in system tray" +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/look_feel_ui.py:205 +msgid "&Toolbar" +msgstr "&Pasek narzędzi" + +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/look_feel_ui.py:206 +msgid "&Icon size:" +msgstr "&Rozmiar ikon:" + +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/look_feel_ui.py:207 +msgid "Show &text under icons:" +msgstr "Pokaż &tekst pod ikonami:" + +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/look_feel_ui.py:208 +msgid "Interface font:" +msgstr "Czcionka interfejsu:" + +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/look_feel_ui.py:209 +msgid "Change &font (needs restart)" +msgstr "Zmień czcionkę (wymaga ponownego uruchomienia)" + +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/look_feel_ui.py:210 +msgid "Main Interface" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/preferences/look_feel_ui.py:145 +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/look_feel_ui.py:211 +msgid "Select displayed metadata" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/look_feel_ui.py:212 +msgid "Move up" +msgstr "Przenieś wyżej" + +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/look_feel_ui.py:213 +msgid "Move down" +msgstr "Przenieś niżej" + +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/look_feel_ui.py:214 msgid "Use &Roman numerals for series" +msgstr "Użyj numeracji rzymskiej dla cykli" + +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/look_feel_ui.py:215 +msgid "" +"Note that comments will always be displayed at the end, regardless of " +"the position you assign here." msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/preferences/look_feel_ui.py:146 -msgid "Show cover &browser in a separate window (needs restart)" -msgstr "Pokaż &przeglądarkę okładek w nowym oknie (wymaga restartu)" +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/look_feel_ui.py:217 +msgid "Tags browser category &partitioning method:" +msgstr "Metoda kategorii partycjonowania przeglądarki etykiet:" -#: /home/kovid/work/calibre/src/calibre/gui2/preferences/look_feel_ui.py:147 -msgid "Tags browser category partitioning method:" -msgstr "" - -#: /home/kovid/work/calibre/src/calibre/gui2/preferences/look_feel_ui.py:148 +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/look_feel_ui.py:218 msgid "" "Choose how tag browser subcategories are displayed when\n" "there are more items than the limit. Select by first\n" @@ -11020,145 +12428,322 @@ msgid "" "have a list of fixed-sized groups. Set to disabled\n" "if you never want subcategories" msgstr "" +"Wybierz jak podkategorie przeglądarki etykiet są wyświetlane\n" +"gdy jest więcej elementów niż wskazuje na to ograniczenie.\n" +"Wybierz od pierwszej litery, aby zobaczyć listę A, B, C. Wybierz\n" +"partycjonowane, aby uzyskać listę grup o określonym rozmiarze.\n" +"Wybierz wyłącz, aby nie oglądać podkategorii" -#: /home/kovid/work/calibre/src/calibre/gui2/preferences/look_feel_ui.py:153 -msgid "Collapse when more items than:" -msgstr "" +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/look_feel_ui.py:223 +msgid "&Collapse when more items than:" +msgstr "Schowaj gdy wystąpi więcej elementów niż:" -#: /home/kovid/work/calibre/src/calibre/gui2/preferences/look_feel_ui.py:154 +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/look_feel_ui.py:224 msgid "" "If a Tag Browser category has more than this number of items, it is divided\n" "up into sub-categories. If the partition method is set to disable, this " "value is ignored." msgstr "" +"Jeśli kategoria przeglądarki etykier ma większą ilość elementów niż " +"wskazana, jest dzielona\n" +"na podkategorie. Jeśli metoda partycjonowania jest wyłączona, ta wartość jes " +"ignorowana." -#: /home/kovid/work/calibre/src/calibre/gui2/preferences/look_feel_ui.py:156 -msgid "&Toolbar" -msgstr "&Pasek narzędzi" +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/look_feel_ui.py:226 +msgid "Show &average ratings in the tags browser" +msgstr "Pokaż &średnie oceny w przeglądarce etykiet" -#: /home/kovid/work/calibre/src/calibre/gui2/preferences/look_feel_ui.py:157 -msgid "&Icon size:" -msgstr "&Rozmiar ikon:" +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/look_feel_ui.py:227 +msgid "Categories with &hierarchical items:" +msgstr "Kategorie z elementami hierarchicznymi:" -#: /home/kovid/work/calibre/src/calibre/gui2/preferences/look_feel_ui.py:158 -msgid "Show &text under icons:" -msgstr "Pokaż &tekst pod ikonami:" - -#: /home/kovid/work/calibre/src/calibre/gui2/preferences/look_feel_ui.py:159 -msgid "&Split the toolbar into two toolbars" +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/look_feel_ui.py:228 +msgid "" +"A comma-separated list of columns in which items containing\n" +"periods are displayed in the tag browser trees. For example, if\n" +"this box contains 'tags' then tags of the form 'Mystery.English'\n" +"and 'Mystery.Thriller' will be displayed with English and Thriller\n" +"both under 'Mystery'. If 'tags' is not in this box,\n" +"then the tags will be displayed each on their own line." msgstr "" +"Lista kolumna oddzielana przecinkami, w których elementy zawierające\n" +"okresy są wyświetlane w drzewkach przeglądarki elementów. Na przykład,\n" +"jeśli to pole zawiera 'etykiety' wówczas etykiety z formularza " +"'Tajemnice.Angielski'\n" +"i 'Tajemnice.Thriller' zostaną wyświetlone zarówno z Angielski i Thriller " +"pod\n" +"'Tajemnice'. Jeśli 'etykeity' nie znajdą się w tym polu, wówczas etykiety\n" +"zostaną wyświetlone każda we własnym wierszu." -#: /home/kovid/work/calibre/src/calibre/gui2/preferences/look_feel_ui.py:160 -msgid "Interface font:" -msgstr "Czcionka interfejsu:" - -#: /home/kovid/work/calibre/src/calibre/gui2/preferences/look_feel_ui.py:161 -msgid "Change &font (needs restart)" +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/look_feel_ui.py:235 +msgid "Show cover &browser in a separate window (needs restart)" msgstr "" +"Pokaż &przeglądarkę okładek w nowym oknie (wymaga ponownego uruchomienia)" -#: /home/kovid/work/calibre/src/calibre/gui2/preferences/main.py:223 -#: /home/kovid/work/calibre/src/calibre/gui2/preferences/tweaks_ui.py:96 -msgid "&Apply" +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/look_feel_ui.py:236 +msgid "&Number of covers to show in browse mode (needs restart):" msgstr "" +"&Liczba wyświetlanych okładek w trybie przeglądarki (wymaga ponownego " +"uruchomienia):" #: /home/kovid/work/calibre/src/calibre/gui2/preferences/main.py:230 +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/tweaks_ui.py:96 +msgid "&Apply" +msgstr "&Zastosuj" + +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/main.py:237 msgid "Restore &defaults" -msgstr "" +msgstr "Przywróć ustawienia &domyślne" -#: /home/kovid/work/calibre/src/calibre/gui2/preferences/main.py:231 +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/main.py:238 msgid "Save changes" -msgstr "" +msgstr "Zapisz zmiany" -#: /home/kovid/work/calibre/src/calibre/gui2/preferences/main.py:232 +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/main.py:239 msgid "Cancel and return to overview" -msgstr "" +msgstr "Anuluj i wróć do omówienia" -#: /home/kovid/work/calibre/src/calibre/gui2/preferences/main.py:288 +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/main.py:295 msgid "Restoring to defaults not supported for" -msgstr "" +msgstr "Przywracanie wartości domyślnych nie jest wspierane dla" -#: /home/kovid/work/calibre/src/calibre/gui2/preferences/main.py:323 +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/main.py:330 msgid "" "Some of the changes you made require a restart. Please restart calibre as " "soon as possible." msgstr "" +"Niektóre ze zmian wymagają ponownego uruchomienia. Proszę jak najszybciej " +"ponownie uruchomić calibre." -#: /home/kovid/work/calibre/src/calibre/gui2/preferences/main.py:326 +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/main.py:333 msgid "" "The changes you have made require calibre be restarted immediately. You will " "not be allowed set any more preferences, until you restart." msgstr "" +"Zmiany, których dokonałeś wymagają natychmiastowego ponownego uruchomienia " +"programu calibre. Nie można zmieniać żadnych dalszych preferencji bez " +"uprzedniego ponownego uruchomienia." -#: /home/kovid/work/calibre/src/calibre/gui2/preferences/main.py:331 -#: /home/kovid/work/calibre/src/calibre/gui2/preferences/server.py:132 +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/main.py:338 +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/server.py:127 msgid "Restart needed" +msgstr "Wymagane ponowne uruchomienie" + +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/metadata_sources.py:47 +msgid "Source" +msgstr "Źródło" + +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/metadata_sources.py:49 +msgid "Cover priority" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/preferences/misc.py:42 -msgid "Failed to install command line tools." -msgstr "Nie powiodła się instalacja narzędzi linii poleceń." +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/metadata_sources.py:76 +msgid "This source is configured and ready to go" +msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/preferences/misc.py:45 +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/metadata_sources.py:77 +msgid "This source needs configuration" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/metadata_sources.py:148 +msgid "Published date" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/metadata_sources.py:241 +msgid "Configure %s
    %s" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/metadata_sources.py:293 +msgid "No source selected" +msgstr "Nie wybrano źródła" + +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/metadata_sources.py:294 +msgid "No source selected, cannot configure." +msgstr "Nie wybrano źródła, nie można skonfigurować." + +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/metadata_sources_ui.py:110 +msgid "Metadata sources" +msgstr "Żródła metadanych" + +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/metadata_sources_ui.py:111 +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 " +"(smaller) priority will be preferred when bulk downloading metadata.\n" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/metadata_sources_ui.py:113 +msgid "" +"Sources with a red X next to their names must be configured before they will " +"be used. " +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/metadata_sources_ui.py:114 +msgid "Configure selected source" +msgstr "Skonfiguruj wybrane źródło" + +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/metadata_sources_ui.py:116 +msgid "" +"If you uncheck any fields, metadata for those fields will not be downloaded" +msgstr "Jeśli odznaczysz pola, metadane dla tych pól nie zostaną pobrane" + +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/metadata_sources_ui.py:117 +msgid "&Select all" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/metadata_sources_ui.py:118 +msgid "&Clear all" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/metadata_sources_ui.py:119 +msgid "Convert all downloaded comments to plain &text" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/metadata_sources_ui.py:120 +msgid "Swap author names from FN LN to LN, FN" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/metadata_sources_ui.py:121 +msgid "Max. number of &tags to download:" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/metadata_sources_ui.py:122 +msgid "Max. &time to wait after first match is found:" +msgstr "" + +#: /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:125 +#: /home/kovid/work/calibre/src/calibre/gui2/viewer/config_ui.py:199 +msgid " secs" +msgstr " sekund" + +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/metadata_sources_ui.py:124 +msgid "Max. time to wait after first &cover is found:" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/metadata_sources_ui.py:126 +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 " +"tend to be more like genres, while the larger tag sets tend to describe the " +"books content.\n" +"

    Note that this option will only make a practical difference if one of the " +"metadata sources has a genre like tag set for the book you are searching " +"for. Most often, they all have large tag sets." +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/metadata_sources_ui.py:128 +msgid "Prefer &fewer tags" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/misc.py:56 +msgid "Failed to install command line tools." +msgstr "Instalacja narzędzi linii poleceń nie powiodła się." + +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/misc.py:59 msgid "Command line tools installed" msgstr "Narzędzia linii poleceń zostały zainstalowane" -#: /home/kovid/work/calibre/src/calibre/gui2/preferences/misc.py:46 +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/misc.py:60 msgid "Command line tools installed in" msgstr "Narzędzia linii poleceń zostały zainstalowane w" -#: /home/kovid/work/calibre/src/calibre/gui2/preferences/misc.py:47 +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/misc.py:61 msgid "" "If you move calibre.app, you have to re-install the command line tools." msgstr "" "Jeśli przeniesiesz calibre.app, będziesz musiał przeinstalować narzędzia " "linii poleceń." -#: /home/kovid/work/calibre/src/calibre/gui2/preferences/misc_ui.py:61 -msgid "&Maximum number of waiting worker processes (needs restart):" +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/misc_ui.py:62 +msgid "Max. simultaneous conversion/news download jobs:" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/preferences/misc_ui.py:62 +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/misc_ui.py:63 msgid "Limit the max. simultaneous jobs to the available CPU &cores" msgstr "" "Ogranicz liczbę jednocześnie wykonywanych zadań do ilości &rdzeni procesora" -#: /home/kovid/work/calibre/src/calibre/gui2/preferences/misc_ui.py:63 +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/misc_ui.py:64 msgid "Debug &device detection" +msgstr "Wykrycie urzą&dzenia debugującego" + +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/misc_ui.py:65 +msgid "Get information to setup the &user defined device" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/preferences/misc_ui.py:64 +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/misc_ui.py:66 msgid "Open calibre &configuration directory" msgstr "Otwórz katalog &konfiguracyjny calibre" -#: /home/kovid/work/calibre/src/calibre/gui2/preferences/misc_ui.py:65 +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/misc_ui.py:67 msgid "&Install command line tools" msgstr "Za&instaluj narzędzia linii komend" -#: /home/kovid/work/calibre/src/calibre/gui2/preferences/plugboard.py:31 +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/plugboard.py:37 msgid "Open Editor" -msgstr "" +msgstr "Otwórz edytor" -#: /home/kovid/work/calibre/src/calibre/gui2/preferences/plugboard.py:65 +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/plugboard.py:72 msgid "Device currently connected: " msgstr "Podłączone urządzenie: " -#: /home/kovid/work/calibre/src/calibre/gui2/preferences/plugboard.py:68 +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/plugboard.py:75 msgid "Device currently connected: None" +msgstr "Podłączone urządzenie: brak" + +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/plugboard.py:207 +msgid "That format and device already has a plugboard." msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/preferences/plugboard.py:228 +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/plugboard.py:219 +msgid "Possibly override plugboard?" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/plugboard.py:220 msgid "" -"That format and device already has a plugboard or conflicts with another " -"plugboard." +"A more general plugboard already exists for that format and device. Are you " +"sure you want to add the new plugboard?" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/preferences/plugboard.py:261 +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/plugboard.py:232 +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/plugboard.py:254 +msgid "Add possibly overridden plugboard?" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/plugboard.py:233 +msgid "" +"More specific device plugboards exist for that format. Are you sure you want " +"to add the new plugboard?" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/plugboard.py:244 +msgid "Really add plugboard?" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/plugboard.py:245 +msgid "" +"A different plugboard matches that format and device combination. Are you " +"sure you want to add the new plugboard?" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/plugboard.py:255 +msgid "" +"More specific format and device plugboards already exist. Are you sure you " +"want to add the new plugboard?" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/plugboard.py:266 +msgid "The {0} device does not support the {1} format." +msgstr "Urządzenie {0} nie wspiera formatu {1}." + +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/plugboard.py:299 msgid "Invalid destination" -msgstr "" +msgstr "Niepoprawne miejsce przeznaczenia" -#: /home/kovid/work/calibre/src/calibre/gui2/preferences/plugboard.py:262 +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/plugboard.py:300 msgid "The destination field cannot be blank" -msgstr "" +msgstr "Pole przeznaczenia nie może byc puste" #: /home/kovid/work/calibre/src/calibre/gui2/preferences/plugboard_ui.py:114 msgid "" @@ -11179,52 +12764,68 @@ msgid "" "users might do to force it to use the ';' that the kindle requires. A third " "would be to specify the language." msgstr "" +"Tutaj możesz zmienić metadane, których calibre używa do aktualizacji książki " +"podczas zapisywania na dysk lub wysyłania na urządzenie.\n" +"\n" +"Tutaj możesz zdefiniować 'wtyczkę' dla formatu (lub wszystkich formatów) i " +"urządzeń (lub wszystkich urządzeń). Wtyczka określa, jaki szablon jest " +"podłączony do jakiego pola. Szablon jest wykorzystany do obliczenia " +"wartości, a ta wartość jest przypisywana do podłączonego pola.\n" +"\n" +"Szablony często zawierają proste referencje do złożonych kolumn, ale nie " +"jest to wymagane. Możesz wykorzystać jakikolwiek szablon z pola źródłowego, " +"który możesz wykorzystać wszędzie w calibre.\n" +"\n" +"Wtyczki można użyć między innymi do zmiany tytułu, który ma zawierać " +"informacje o cyklu. Można także zmienić sortowanie według autora, " +"użytkownicy formatu mobi mogą wymusić używanie ';', czego wymaga kindle. " +"Trzecim wykorzystaniem może być określenie języka." #: /home/kovid/work/calibre/src/calibre/gui2/preferences/plugboard_ui.py:121 msgid "Format (choose first)" -msgstr "" +msgstr "Format (wybierz pierwszy)" #: /home/kovid/work/calibre/src/calibre/gui2/preferences/plugboard_ui.py:122 msgid "Device (choose second)" -msgstr "" +msgstr "Urządzenie (wybierz drugie)" #: /home/kovid/work/calibre/src/calibre/gui2/preferences/plugboard_ui.py:123 msgid "Add new plugboard" -msgstr "" +msgstr "Dodaj nową wtyczkę" #: /home/kovid/work/calibre/src/calibre/gui2/preferences/plugboard_ui.py:124 msgid "Edit existing plugboard" -msgstr "" +msgstr "Edytuj istniejącą wtyczkę" #: /home/kovid/work/calibre/src/calibre/gui2/preferences/plugboard_ui.py:125 msgid "Existing plugboards" -msgstr "" +msgstr "Istniejące wtyczki" #: /home/kovid/work/calibre/src/calibre/gui2/preferences/plugboard_ui.py:126 msgid "Source template" -msgstr "" +msgstr "Szablon źródłowy" #: /home/kovid/work/calibre/src/calibre/gui2/preferences/plugboard_ui.py:127 msgid "Destination field" -msgstr "" +msgstr "Pole miejsca przeznaczenia" #: /home/kovid/work/calibre/src/calibre/gui2/preferences/plugboard_ui.py:128 msgid "Save plugboard" -msgstr "" +msgstr "Zapisz wtyczkę" #: /home/kovid/work/calibre/src/calibre/gui2/preferences/plugboard_ui.py:129 msgid "Delete plugboard" -msgstr "" +msgstr "Skasuj wtyczkę" -#: /home/kovid/work/calibre/src/calibre/gui2/preferences/plugins.py:177 +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/plugins.py:182 msgid "%(plugin_type)s %(plugins)s" msgstr "%(plugins)s: %(plugin_type)s" -#: /home/kovid/work/calibre/src/calibre/gui2/preferences/plugins.py:178 +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/plugins.py:183 msgid "plugins" -msgstr "Wtyczki" +msgstr "wtyczki" -#: /home/kovid/work/calibre/src/calibre/gui2/preferences/plugins.py:187 +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/plugins.py:192 msgid "" "\n" "Customization: " @@ -11232,81 +12833,89 @@ msgstr "" "\n" "Dostosowywanie: " -#: /home/kovid/work/calibre/src/calibre/gui2/preferences/plugins.py:216 +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/plugins.py:221 msgid "Search for plugin" -msgstr "" +msgstr "Szukaj wtyczki" -#: /home/kovid/work/calibre/src/calibre/gui2/preferences/plugins.py:224 +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/plugins.py:230 +#: /home/kovid/work/calibre/src/calibre/gui2/store/search/search.py:223 msgid "No matches" -msgstr "" +msgstr "Brak trafień" -#: /home/kovid/work/calibre/src/calibre/gui2/preferences/plugins.py:225 +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/plugins.py:231 msgid "Could not find any matching plugins" -msgstr "" +msgstr "Nie znalazłem żadnych pasujących wtyczek" -#: /home/kovid/work/calibre/src/calibre/gui2/preferences/plugins.py:266 +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/plugins.py:272 msgid "Add plugin" -msgstr "" +msgstr "Dodaj wtyczkę" -#: /home/kovid/work/calibre/src/calibre/gui2/preferences/plugins.py:274 +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/plugins.py:280 msgid "" "Installing plugins is a security risk. Plugins can contain a " "virus/malware. Only install it if you got it from a trusted source. Are you " "sure you want to proceed?" msgstr "" +"Instalacja wtyczek niesie ze sobą ryzyko związane z bezpieczeństwem. " +"Wtyczki mogą zawierać wirusy/szkodliwe oprogramowanie. Instaluj tylko " +"wtyczki pochodzące z zaufanych źródeł. Jesteś pewien, że chcesz kontynuować?" -#: /home/kovid/work/calibre/src/calibre/gui2/preferences/plugins.py:285 +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/plugins.py:296 msgid "" "Plugin {0} successfully installed under {1} plugins. You may " "have to restart calibre for the plugin to take effect." msgstr "" +"Wtyczka {0} pomyślnie zainstalowana pod {1} wtyczkami. " +"Możliwe, że trzeba ponownie uruchomić calibre, aby wtyczka zaczęła działać." -#: /home/kovid/work/calibre/src/calibre/gui2/preferences/plugins.py:293 +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/plugins.py:304 msgid "No valid plugin path" msgstr "Niewłaściwa ścieżka do wtyczki" -#: /home/kovid/work/calibre/src/calibre/gui2/preferences/plugins.py:294 +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/plugins.py:305 msgid "%s is not a valid plugin path" msgstr "%s nie jest właściwą ścieżką do wtyczki" -#: /home/kovid/work/calibre/src/calibre/gui2/preferences/plugins.py:303 +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/plugins.py:314 msgid "Select an actual plugin under %s to customize" -msgstr "" +msgstr "Wskaż aktualną wtyczkę pod %s w celu dostosowania" -#: /home/kovid/work/calibre/src/calibre/gui2/preferences/plugins.py:309 +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/plugins.py:320 msgid "Plugin cannot be disabled" msgstr "Wtyczka nie może zostać wyłączona" -#: /home/kovid/work/calibre/src/calibre/gui2/preferences/plugins.py:310 +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/plugins.py:321 msgid "The plugin: %s cannot be disabled" msgstr "Wtyczka %s nie może zostać wyłączona" -#: /home/kovid/work/calibre/src/calibre/gui2/preferences/plugins.py:320 +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/plugins.py:331 msgid "Plugin not customizable" msgstr "Tej wtyczki nie można dostosowywać" -#: /home/kovid/work/calibre/src/calibre/gui2/preferences/plugins.py:321 +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/plugins.py:332 msgid "Plugin: %s does not need customization" msgstr "Wtyczka %s nie potrzebuje dodatkowego dostosowywania" -#: /home/kovid/work/calibre/src/calibre/gui2/preferences/plugins.py:327 +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/plugins.py:338 msgid "Must restart" -msgstr "Wymagany restart" +msgstr "Wymagane ponowne uruchomienie" -#: /home/kovid/work/calibre/src/calibre/gui2/preferences/plugins.py:328 +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/plugins.py:339 msgid "" "You must restart calibre before you can configure the %s plugin" msgstr "" +"Musisz ponownie uruchomić program calibre nim będziesz mógł skonfigurować " +"%s wtyczkę" -#: /home/kovid/work/calibre/src/calibre/gui2/preferences/plugins.py:333 +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/plugins.py:344 msgid "Plugin {0} successfully removed" -msgstr "" +msgstr "Wtyczka {0} została pomyślnie usunięta" -#: /home/kovid/work/calibre/src/calibre/gui2/preferences/plugins.py:341 +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/plugins.py:352 msgid "Cannot remove builtin plugin" msgstr "Nie można usunąć wbudowanej wtyczki" -#: /home/kovid/work/calibre/src/calibre/gui2/preferences/plugins.py:342 +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/plugins.py:353 msgid " cannot be removed. It is a builtin plugin. Try disabling it instead." msgstr "" " - ta wtyczka nie może zostać usunięta. Ta wtyczka jest wbudowana w program. " @@ -11317,7 +12926,7 @@ msgid "" "Here you can customize the behavior of Calibre by controlling what plugins " "it uses." msgstr "" -"Tutaj można dostosować zachowanie Calibre poprzez kontrolę wtyczek, których " +"Tutaj można dostosować zachowanie calibre poprzez kontrolę wtyczek, których " "ma używać." #: /home/kovid/work/calibre/src/calibre/gui2/preferences/plugins_ui.py:90 @@ -11334,25 +12943,29 @@ msgstr "&Usuń wtyczkę" #: /home/kovid/work/calibre/src/calibre/gui2/preferences/plugins_ui.py:93 msgid "&Add a new plugin" -msgstr "" +msgstr "Dod&aj nową wtyczkę" #: /home/kovid/work/calibre/src/calibre/gui2/preferences/save_template.py:33 msgid "Any custom field" -msgstr "" +msgstr "Jakiekolwiek pole dodatkowe" #: /home/kovid/work/calibre/src/calibre/gui2/preferences/save_template.py:34 msgid "The lookup name of any custom field. These names begin with \"#\")" msgstr "" +"Nazwa wyszukania jakiegokolwiek pola dodatkowego. Te nazwy zaczynają się od " +"znaku \"#\"." #: /home/kovid/work/calibre/src/calibre/gui2/preferences/save_template.py:57 msgid "Constant template" -msgstr "" +msgstr "Stały szablon" #: /home/kovid/work/calibre/src/calibre/gui2/preferences/save_template.py:58 msgid "" "The template contains no {fields}, so all books will have the same name. Is " "this OK?" msgstr "" +"Szablon nie zawiera żadnych {fields}, więc wszystkie książki będą miały tę " +"samą nazwę. Tak ma być?" #: /home/kovid/work/calibre/src/calibre/gui2/preferences/save_template_ui.py:47 msgid "Save &template" @@ -11366,6 +12979,11 @@ msgid "" "particular book does not have some metadata, the variable will be replaced " "by the empty string." msgstr "" +"Dostosowując poniższy szablon, może kontrolować, w których folderach pliki " +"są zapisywane i jakie mają nadawane nazwy. Możesz użyć znaku / by wskazać " +"podfoldery. Dostępne są wskazane poniżej zmienne metadanych. Gdy dana " +"książka nie posiada metadanych, zmienna zostanie zastąpiona pustym ciągiem " +"znaków." #: /home/kovid/work/calibre/src/calibre/gui2/preferences/save_template_ui.py:49 msgid "Available variables:" @@ -11437,6 +13055,30 @@ msgid "" "check for duplicates, to find which column contains a particular item, or to " "have hierarchical categories (categories that contain categories)." msgstr "" +"Zgrupowane wyszukiwanie są to wyszukania, które pozwalają zapytaniu " +"automatycznie wyszukiwać pośród więcej niż jednej kolumn. Na przykład, jeśli " +"stworzysz zgrupowane wyszukanie wszystkiecykle (allseries) z " +"wartością cykle, #mojecykle, #mojecykle2, wówczas zapytanie " +"wszystkiecykle:adhoc znajdzie 'adhoc' w każdej z kolumn " +"cykle, #mojecykle i " +"#mojecykle2.

    Wpisz nazwę zgrupowanego wyszukiwania w polu " +"rozwijanym, wpisz listę kolumn do wyszukania w polu wartości, a następnie " +"naciśnij Zapisz.

    Informacja: wyszukiwanie wymusza małe litery; " +"MojWyszukanie i mojewyszukanie oznacza ten sam " +"warunek.

    Możesz ustawić, aby zgrupowane wyszukiwanie pokazywało się jako " +"kategoria użytkownika w przeglądarce etykiet. Wytarczy, że dodasz nazwy " +"zgrupowanego wyszukiwania do pola Stwórz kategorie użytkownika. Możesz dodać " +"wiele warunków oddzielonych przecinkiem. Nowa kategoria użytkownika zostanie " +"automatycznie wstawiona z wszystkimi elementami w kategoriach zawartych w " +"zgrupowanym wyszukiwaniu.

    Automatyczne kategorie użytkownika pozwalają na " +"łatwe obejrzenie wszystkich elementów kategorii, które są w kolumnach " +"zawartych w zgrupowanym wyszukiwaniu. Używając powyższego " +"wszystkiecykle przykładu, automatycznie wygenerowana kategoria " +"użytkownika będzie zawierać wszystkie cykle zawarte w cykle, " +"#mojecykle i #mojecykle2. To może być pomocne przy " +"sprawdzaniu duplikatów, przy wyszukaniu kolumn zawierających określone " +"elementy lub do posiadania hierarchicznych kategorii (kategorii, które " +"zawierają kategorie)." #: /home/kovid/work/calibre/src/calibre/gui2/preferences/search.py:96 #: /home/kovid/work/calibre/src/calibre/gui2/preferences/search.py:106 @@ -11445,40 +13087,42 @@ msgstr "" #: /home/kovid/work/calibre/src/calibre/gui2/preferences/search.py:128 #: /home/kovid/work/calibre/src/calibre/gui2/preferences/search_ui.py:119 msgid "Grouped Search Terms" -msgstr "" +msgstr "Zgrupowane wyszukiwanie" #: /home/kovid/work/calibre/src/calibre/gui2/preferences/search.py:97 msgid "The search term cannot be blank" -msgstr "" +msgstr "Wyszukiwanie nie może być puste" #: /home/kovid/work/calibre/src/calibre/gui2/preferences/search.py:107 msgid "That name is already used for a column or grouped search term" msgstr "" +"Ta nazwa już jest wykorzystana dla kolumny lub zgrupowanego wyszukiwania" #: /home/kovid/work/calibre/src/calibre/gui2/preferences/search.py:111 msgid "That name is already used for user category" -msgstr "" +msgstr "Ta nazwa już jest wykorzystana dla kategorii użytkownika" #: /home/kovid/work/calibre/src/calibre/gui2/preferences/search.py:117 msgid "The value box cannot be empty" -msgstr "" +msgstr "Pole wartości nie może być puste" #: /home/kovid/work/calibre/src/calibre/gui2/preferences/search.py:129 msgid "The empty grouped search term cannot be deleted" -msgstr "" +msgstr "Puste zgrupowane wyszukiwanie nie może zostać skasowane" #: /home/kovid/work/calibre/src/calibre/gui2/preferences/search_ui.py:110 msgid "Search as you &type" -msgstr "" +msgstr "Szukaj w &trakcie pisania" #: /home/kovid/work/calibre/src/calibre/gui2/preferences/search_ui.py:111 msgid "" "&Highlight search results instead of restricting the book list to the results" msgstr "" +"&Podświetlaj wyniki wyszukania zamiast ograniczać listę książek do wyników" #: /home/kovid/work/calibre/src/calibre/gui2/preferences/search_ui.py:112 msgid "What to search by default" -msgstr "" +msgstr "Czego domyślnie szukać" #: /home/kovid/work/calibre/src/calibre/gui2/preferences/search_ui.py:113 msgid "" @@ -11487,14 +13131,19 @@ msgid "" "search not just authors but title/tags/series/comments/etc. Use these " "options if you would like to change this behavior." msgstr "" +"Gdy wpiszesz frazę wyszukania bez prefiksu, calibre domyślnie przeszuka " +"wszystkie metadane pod tym kątem. Na przykład wpisując \"asimov\" przeszuka " +"nie tylko według autora, ale także po " +"tytule/etykiecie/cyklu/komentarzach/itd. Użyj tych opcji jeśli chcesz " +"zmienić to zachowanie." #: /home/kovid/work/calibre/src/calibre/gui2/preferences/search_ui.py:114 msgid "&Limit the searched metadata" -msgstr "" +msgstr "&Ogranicz przeszukiwane metadane" #: /home/kovid/work/calibre/src/calibre/gui2/preferences/search_ui.py:115 msgid "&Columns that non-prefixed searches are limited to:" -msgstr "" +msgstr "&Kolumny których bezprefiksowe wyszukiwania są ograniczone do:" #: /home/kovid/work/calibre/src/calibre/gui2/preferences/search_ui.py:116 msgid "" @@ -11503,12 +13152,19 @@ msgid "" "you always use prefixes in your saved searches. For example, use " "\"series:Foundation\" rather than just \"Foundation\" in a saved search" msgstr "" +"Ta opcja ma wpływ na wszystkie wyszukiwania, włącznie z zapisanymi " +"wyszukiwaniami i ograniczeniami. Dlatego też, jeśli użyjesz tej opcji, " +"najlepiej będzie, jeśli się upewnisz, że zawsze używasz prefiksów w " +"zapisanych wyszukiwaniach. Na przykład użyj lepiej \"cykle:Fundacja\" niż " +"samo \"Fundacja\" w zapisanych wyszukiwaniach" #: /home/kovid/work/calibre/src/calibre/gui2/preferences/search_ui.py:117 msgid "" "Clear search histories from all over calibre. Including the book list, e-" "book viewer, fetch news dialog, etc." msgstr "" +"Wyczyść historię wyszukiwania w całym calibre. Włącznie z listą książek, " +"przeglądarką książek, pobieraniem newsów itd." #: /home/kovid/work/calibre/src/calibre/gui2/preferences/search_ui.py:118 msgid "Clear search &histories" @@ -11526,10 +13182,15 @@ msgid "" "changing the name and pressing Save. Change the value of\n" "a search term by changing the value box then pressing Save." msgstr "" +"Zawiera nazwy obecnie zdefiniowanych zgrupowanych wyszukiwań.\n" +"Stwórz nową nazwę wpisując ją w puste pole, a następnie\n" +"naciśnij Zapisz. Zmień nazwę wyszukiwania wskazując je, a potem\n" +"zmień nazwę i naciśnij Zapisz. Zmień wartość wyszukiwania\n" +"zmieniając wartość w polu, a następnie naciśnij Zapisz." #: /home/kovid/work/calibre/src/calibre/gui2/preferences/search_ui.py:126 msgid "Delete the current search term" -msgstr "" +msgstr "Skasuj bieżące wyszukiwanie" #: /home/kovid/work/calibre/src/calibre/gui2/preferences/search_ui.py:128 msgid "" @@ -11537,6 +13198,9 @@ msgid "" "changing the name then pressing Save. You can change the value\n" "of a search term by changing the value box then pressing Save." msgstr "" +"Zapisz bieżące wyszukiwanie. Możesz zmienić nazwę wyszukiwania\n" +"zmieniając nazwę i naciskając Zapisz. Możesz zmienić wartość\n" +"wyszukiwania zmieniając wartość pola, a następnie naciskając Zapisz." #: /home/kovid/work/calibre/src/calibre/gui2/preferences/search_ui.py:131 msgid "&Save" @@ -11544,13 +13208,15 @@ msgstr "&Zapisz" #: /home/kovid/work/calibre/src/calibre/gui2/preferences/search_ui.py:132 msgid "Make &user categories from:" -msgstr "" +msgstr "Utwórz kategorie użytkownika z:" #: /home/kovid/work/calibre/src/calibre/gui2/preferences/search_ui.py:133 msgid "" "Enter the names of any grouped search terms you wish\n" "to be shown as user categories" msgstr "" +"Wpisz nazwy jakiegokolwiek zgrupowanego wyszukiwania,\n" +"które chcesz wyświetlać w kategoriach użytkownika" #: /home/kovid/work/calibre/src/calibre/gui2/preferences/sending.py:28 #: /home/kovid/work/calibre/src/calibre/gui2/preferences/sending_ui.py:70 @@ -11581,6 +13247,14 @@ msgid "" "

  • Automatic management: Calibre automatically keeps metadata on the " "device in sync with the calibre library, on every connect
  • " msgstr "" +"
  • Manualne zarządzanie: Calibre aktualizuje metadane i dodaje " +"kolekcje tylko gdy książka jest wysyłana. Z tą opcją, calibre nigdy nie " +"usunie kolekcji.
  • \n" +"
  • Tylko przy wysyłaniu: Calibre aktualizuje metadane i dodaje/usuwa " +"kolekcje dla książki wtedy, gdy książka jest wysłana na urządzenie.
  • \n" +"
  • Automatyczne zarządzanie: Calibre automatycznie przechowuje " +"metadane na urządzeniu w synchronizacji z biblioteką calibre, przy każdym " +"połączeniu
  • " #: /home/kovid/work/calibre/src/calibre/gui2/preferences/sending_ui.py:77 msgid "" @@ -11588,23 +13262,27 @@ msgid "" "Send to Device button. This setting can be overriden for individual devices " "by customizing the device interface plugins in Preferences->Advanced->Plugins" msgstr "" +"Tutaj możesz ustalić zachowanie calibre odnośnie zapisywania twoich książek " +"gdy klikniesz w przycisk Wyślij na Urządzenie. Te ustawienia mogą zostać " +"uchylone dla indywidualnych urządzeń poprzez dostosowanie wtyczek interfejsu " +"urzadzenia w Preferencje->Zaawansowane->Wtyczki" -#: /home/kovid/work/calibre/src/calibre/gui2/preferences/server.py:75 -#: /home/kovid/work/calibre/src/calibre/gui2/ui.py:378 +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/server.py:70 +#: /home/kovid/work/calibre/src/calibre/gui2/ui.py:422 msgid "Failed to start content server" msgstr "Włączanie serwera zakończone niepowodzeniem" -#: /home/kovid/work/calibre/src/calibre/gui2/preferences/server.py:111 +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/server.py:106 msgid "Error log:" msgstr "Dziennik błędów:" -#: /home/kovid/work/calibre/src/calibre/gui2/preferences/server.py:118 +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/server.py:113 msgid "Access log:" msgstr "Dziennik dostępów:" -#: /home/kovid/work/calibre/src/calibre/gui2/preferences/server.py:133 +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/server.py:128 msgid "You need to restart the server for changes to take effect" -msgstr "Musisz zrestartować serwer, aby zmiany były widoczne" +msgstr "Musisz ponownie uruchomić serwer, aby zmiany były widoczne" #: /home/kovid/work/calibre/src/calibre/gui2/preferences/server_ui.py:124 msgid "Server &port:" @@ -11623,6 +13301,8 @@ msgid "" "The maximum size (widthxheight) for displayed covers. Larger covers are " "resized. " msgstr "" +"Maksymalny rozmiar (szerokośćxwysokość) dla wyświetlanych okładek. Większe " +"okładki zostaną zmniejszone. " #: /home/kovid/work/calibre/src/calibre/gui2/preferences/server_ui.py:129 msgid "Max. &cover size:" @@ -11630,15 +13310,15 @@ msgstr "Maks. rozmiar &okładki" #: /home/kovid/work/calibre/src/calibre/gui2/preferences/server_ui.py:131 msgid "Max. &OPDS items per query:" -msgstr "" +msgstr "Maks. elementy &OPDS dla zapytań:" #: /home/kovid/work/calibre/src/calibre/gui2/preferences/server_ui.py:132 msgid "Max. OPDS &ungrouped items:" -msgstr "" +msgstr "Maks. niezgr&upowane elementy OPDS:" #: /home/kovid/work/calibre/src/calibre/gui2/preferences/server_ui.py:133 msgid "Restriction (saved search) to apply:" -msgstr "" +msgstr "Ograniczenie (zapisane wyszukiwanie) do zastosowania:" #: /home/kovid/work/calibre/src/calibre/gui2/preferences/server_ui.py:134 msgid "" @@ -11646,6 +13326,10 @@ msgid "" "content server makes available to those matching the search. This setting is " "per library (i.e. you can have a different restriction per library)." msgstr "" +"To ograniczenie (oparte na zapisanym wyszukaniu) ograniczy ilość dostępnych " +"książek na serwerze zawartości do tych spełniających kryteria wyszukiwania. " +"To ustawienie dotyczy biblioteki (np. możesz mieć różne ograniczenia dla " +"każdej biblioteki)." #: /home/kovid/work/calibre/src/calibre/gui2/preferences/server_ui.py:135 msgid "&Start Server" @@ -11687,17 +13371,13 @@ msgid "" "on your iPhone. Here myhostname should be the fully qualified hostname or " "the IP address of the computer calibre is running on." msgstr "" -"

    Pamiętaj, by pozostawić calibre załączone, jako że serwer działa tylko " -"wtedy, gdy calibre pracuje.\n" +"

    Pamiętaj, by pozostawić włączony program calibre, gdyż serwer działa " +"tylko wtedy, gdy calibre pracuje.\n" "

    Stanza powinien automatycznie zobaczyć bibliotekę calibre. Jeśli nie, " "spróbuj dodać URL http://myhostname:8080 jako nowy katalog w czytniku Stanza " "na urządzeniu iPhone. myhostname powinien być poprawną nazwą hosta lub " "adresem IP komputera, na którym działa calibre." -#: /home/kovid/work/calibre/src/calibre/gui2/preferences/social.py:39 -msgid "Downloading social metadata, please wait..." -msgstr "Pobieranie szpołecznościowych metadanych, proszę czekać..." - #: /home/kovid/work/calibre/src/calibre/gui2/preferences/template_functions.py:23 msgid "" "\n" @@ -11765,34 +13445,99 @@ msgid "" "

    \n" " " msgstr "" - -#: /home/kovid/work/calibre/src/calibre/gui2/preferences/template_functions.py:133 -#: /home/kovid/work/calibre/src/calibre/gui2/preferences/template_functions.py:143 -#: /home/kovid/work/calibre/src/calibre/gui2/preferences/template_functions.py:150 -#: /home/kovid/work/calibre/src/calibre/gui2/preferences/template_functions.py:154 -#: /home/kovid/work/calibre/src/calibre/gui2/preferences/template_functions.py:165 -msgid "Template functions" -msgstr "" +"\n" +"

    Tutaj możesz dodawać lub usuwać funkcje używane w procesie " +"tworzenia\n" +" szablonu. Funkcja szablonu jest pisana w pythonie. Pobiera " +"informacje\n" +" z książki, przetwarza je po czy zwraca wynik w postaci ciągu znaków. " +"Funkcje\n" +" zdefiniowane tutaj można wykorzystać w szablonach w ten sam sposób " +"co\n" +" wbudowane funkcje. Funkcja musi zostać nazwana evaluate i " +"mieć\n" +" poniższy podpis.

    \n" +"

    evaluate(self, formatter, kwargs, mi, locals, twoje " +"parametry)\n" +" → zwracany ciąg unicode

    \n" +"

    Parametry funkcji evaluate:\n" +"

      \n" +"
    • formatter: the instance of the formatter being used to\n" +" evaluate the current template. You can use this to do recursive\n" +" template evaluation.
    • \n" +"
    • kwargs: a dictionary of metadata. Field values are in " +"this\n" +" dictionary.\n" +"
    • mi: a Metadata instance. Used to get field information.\n" +" This parameter can be None in some cases, such as when evaluating\n" +" non-book templates.
    • \n" +"
    • locals: the local variables assigned to by the current\n" +" template program.
    • \n" +"
    • your parameters: You must supply one or more formal\n" +" parameters. The number must match the arg count box, unless arg " +"count is\n" +" -1 (variable number or arguments), in which case the last argument " +"must\n" +" be *args. At least one argument is required, and is usually the " +"value of\n" +" the field being operated upon. Note that when writing in basic " +"template\n" +" mode, the user does not provide this first argument. Instead it is\n" +" supplied by the formatter.
    • \n" +"

    \n" +"

    \n" +" The following example function checks the value of the field. If " +"the\n" +" field is not empty, the field's value is returned, otherwise the " +"value\n" +" EMPTY is returned.\n" +"

    \n"
    +"        name: my_ifempty\n"
    +"        arg count: 1\n"
    +"        doc: my_ifempty(val) -- return val if it is not empty, otherwise the "
    +"string 'EMPTY'\n"
    +"        program code:\n"
    +"        def evaluate(self, formatter, kwargs, mi, locals, val):\n"
    +"            if val:\n"
    +"                return val\n"
    +"            else:\n"
    +"                return 'EMPTY'
    \n" +" This function can be called in any of the three template program " +"modes:\n" +"
      \n" +"
    • single-function mode: {tags:my_ifempty()}
    • \n" +"
    • template program mode: {tags:'my_ifempty($)'}
    • \n" +"
    • general program mode: program: my_ifempty(field('tags'))
    • \n" +"

      \n" +" " #: /home/kovid/work/calibre/src/calibre/gui2/preferences/template_functions.py:134 -msgid "You cannot delete a built-in function" -msgstr "" - #: /home/kovid/work/calibre/src/calibre/gui2/preferences/template_functions.py:144 -msgid "Function not defined" -msgstr "" - +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/template_functions.py:151 #: /home/kovid/work/calibre/src/calibre/gui2/preferences/template_functions.py:155 -msgid "Argument count must be -1 or greater than zero" -msgstr "" - #: /home/kovid/work/calibre/src/calibre/gui2/preferences/template_functions.py:166 -msgid "Exception while compiling function" -msgstr "" +msgid "Template functions" +msgstr "Funkcje szablonu" -#: /home/kovid/work/calibre/src/calibre/gui2/preferences/template_functions.py:194 +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/template_functions.py:135 +msgid "You cannot delete a built-in function" +msgstr "Nie możesz skasować wbudowanych funkcji" + +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/template_functions.py:145 +msgid "Function not defined" +msgstr "Niezdefiniowana funkcja" + +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/template_functions.py:156 +msgid "Argument count must be -1 or greater than zero" +msgstr "Suma argumentu musi wynosić -1 lub być większa od zera" + +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/template_functions.py:167 +msgid "Exception while compiling function" +msgstr "Podczas kompilowania funkcji nastąpił wyjątek" + +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/template_functions.py:195 msgid "function source code not available" -msgstr "" +msgstr "kod źródłowy funkcji niedostępny" #: /home/kovid/work/calibre/src/calibre/gui2/preferences/template_functions_ui.py:96 msgid "&Function:" @@ -11800,15 +13545,15 @@ msgstr "&Funkcja:" #: /home/kovid/work/calibre/src/calibre/gui2/preferences/template_functions_ui.py:97 msgid "Enter the name of the function to create." -msgstr "" +msgstr "Wpisz nazwę funkcji, którą chcesz stworzyć." #: /home/kovid/work/calibre/src/calibre/gui2/preferences/template_functions_ui.py:98 msgid "Arg &count:" -msgstr "" +msgstr "Suma argumentów:" #: /home/kovid/work/calibre/src/calibre/gui2/preferences/template_functions_ui.py:99 msgid "Set this to -1 if the function takes a variable number of arguments" -msgstr "" +msgstr "Ustaw to na -1 jeśli funkcja pobiera zmienną ilość argumentów" #: /home/kovid/work/calibre/src/calibre/gui2/preferences/template_functions_ui.py:102 msgid "&Delete" @@ -11820,115 +13565,135 @@ msgstr "Z&astąp" #: /home/kovid/work/calibre/src/calibre/gui2/preferences/template_functions_ui.py:104 msgid "C&reate" -msgstr "" +msgstr "Utwó&rz" #: /home/kovid/work/calibre/src/calibre/gui2/preferences/template_functions_ui.py:105 msgid "&Program Code: (be sure to follow python indenting rules)" -msgstr "" +msgstr "Kod &programu: (stosuj reguły wcięć z pythona)" #: /home/kovid/work/calibre/src/calibre/gui2/preferences/toolbar.py:36 msgid "Switch between library and device views" -msgstr "" +msgstr "Przełącz pomiędzy widokami biblioteki i urządzenia" -#: /home/kovid/work/calibre/src/calibre/gui2/preferences/toolbar.py:39 +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/toolbar.py:41 msgid "Separator" -msgstr "" +msgstr "Odstęp" -#: /home/kovid/work/calibre/src/calibre/gui2/preferences/toolbar.py:55 +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/toolbar.py:58 msgid "Choose library" -msgstr "" +msgstr "Wybierz bibliotekę" -#: /home/kovid/work/calibre/src/calibre/gui2/preferences/toolbar.py:206 +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/toolbar.py:219 msgid "The main toolbar" -msgstr "" +msgstr "Główny pasek narzędzi" -#: /home/kovid/work/calibre/src/calibre/gui2/preferences/toolbar.py:207 +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/toolbar.py:220 msgid "The main toolbar when a device is connected" -msgstr "" +msgstr "Główny pasek narzędzi gdy urządzenie jest podłączone" -#: /home/kovid/work/calibre/src/calibre/gui2/preferences/toolbar.py:208 +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/toolbar.py:221 +msgid "The optional second toolbar" +msgstr "Opcjonalny drugi pasek narzędzi" + +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/toolbar.py:222 +msgid "The menubar" +msgstr "Pasek menu" + +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/toolbar.py:223 +msgid "The menubar when a device is connected" +msgstr "Pasek menu przy podłączonym czytniku" + +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/toolbar.py:224 msgid "The context menu for the books in the calibre library" -msgstr "" +msgstr "Menu kontekstowe dla książek w bibliotece calibre" -#: /home/kovid/work/calibre/src/calibre/gui2/preferences/toolbar.py:210 +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/toolbar.py:226 msgid "The context menu for the books on the device" -msgstr "" +msgstr "Menu kontekstowe dla książek na urządzeniu" -#: /home/kovid/work/calibre/src/calibre/gui2/preferences/toolbar.py:244 +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/toolbar.py:260 msgid "Cannot add" -msgstr "" +msgstr "Nie można dodać" -#: /home/kovid/work/calibre/src/calibre/gui2/preferences/toolbar.py:245 +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/toolbar.py:261 msgid "Cannot add the actions %s to this location" -msgstr "" +msgstr "Nie można dodać akcji %s do tej lokalizacji" -#: /home/kovid/work/calibre/src/calibre/gui2/preferences/toolbar.py:263 +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/toolbar.py:279 msgid "Cannot remove" -msgstr "" +msgstr "Nie mogę usunąć" -#: /home/kovid/work/calibre/src/calibre/gui2/preferences/toolbar.py:264 +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/toolbar.py:280 msgid "Cannot remove the actions %s from this location" -msgstr "" +msgstr "Nie mogę usunąć akcji %s z tej lokalizacji" #: /home/kovid/work/calibre/src/calibre/gui2/preferences/toolbar_ui.py:99 msgid "Customize the actions in:" -msgstr "" +msgstr "Dostosuj akcje w:" #: /home/kovid/work/calibre/src/calibre/gui2/preferences/toolbar_ui.py:100 msgid "A&vailable actions" -msgstr "" +msgstr "Dostępne &akcje" #: /home/kovid/work/calibre/src/calibre/gui2/preferences/toolbar_ui.py:101 msgid "&Current actions" -msgstr "" +msgstr "Obe&cne akcje" #: /home/kovid/work/calibre/src/calibre/gui2/preferences/toolbar_ui.py:102 msgid "Move selected action up" -msgstr "" +msgstr "Przenieś wybraną akcję do góry" #: /home/kovid/work/calibre/src/calibre/gui2/preferences/toolbar_ui.py:104 msgid "Move selected action down" -msgstr "" +msgstr "Przenieś wybraną akcję w dół" #: /home/kovid/work/calibre/src/calibre/gui2/preferences/toolbar_ui.py:107 msgid "Add selected actions to toolbar" -msgstr "" +msgstr "Dodaj wybrane akcje do paska narzędzi" #: /home/kovid/work/calibre/src/calibre/gui2/preferences/toolbar_ui.py:109 msgid "Remove selected actions from toolbar" -msgstr "" +msgstr "Usuń wybrane akcje z paska narzędzi" -#: /home/kovid/work/calibre/src/calibre/gui2/preferences/tweaks.py:113 +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/tweaks.py:114 msgid "This tweak has it default value" -msgstr "" +msgstr "To ulepszenie ma domyślną wartość" -#: /home/kovid/work/calibre/src/calibre/gui2/preferences/tweaks.py:115 +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/tweaks.py:116 msgid "This tweak has been customized" -msgstr "" +msgstr "To ulepszenie zostało dostosowane" -#: /home/kovid/work/calibre/src/calibre/gui2/preferences/tweaks.py:236 +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/tweaks.py:237 msgid "" "Add/edit tweaks for any custom plugins you have installed. Documentation for " "these tweaks should be available on the website from where you downloaded " "the plugins." msgstr "" +"Dodaj/edytuj ulepszenia dla wszystkich dodatkowych wtyczek, które " +"zainstalowałeś. Dokumentacja dla tych ulepszeń powinna być dostępna na " +"stronie skąd pobrałeś wtyczki." -#: /home/kovid/work/calibre/src/calibre/gui2/preferences/tweaks.py:277 -#: /home/kovid/work/calibre/src/calibre/gui2/preferences/tweaks.py:317 +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/tweaks.py:278 +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/tweaks.py:318 msgid "" "There was a syntax error in your tweak. Click the show details button for " "details." msgstr "" - -#: /home/kovid/work/calibre/src/calibre/gui2/preferences/tweaks.py:330 -msgid "Invalid tweaks" -msgstr "" +"W twoim ulepszeniu jest błąd składni. Kliknij w pokaż szczegóły, aby uzyskać " +"więcej informacji" #: /home/kovid/work/calibre/src/calibre/gui2/preferences/tweaks.py:331 +msgid "Invalid tweaks" +msgstr "Błędne ulepszenia" + +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/tweaks.py:332 msgid "" "The tweaks you entered are invalid, try resetting the tweaks to default and " "changing them one by one until you find the invalid setting." msgstr "" +"Ulepszenia, które wprowadziłeś są błędne, spróbuj zresetować ulepszenia do " +"wartości domyślnych i zmieniaj je pojedynczo aż nie natrafisz na błedne " +"ustawienie." #: /home/kovid/work/calibre/src/calibre/gui2/preferences/tweaks_ui.py:88 msgid "" @@ -11936,80 +13701,138 @@ msgid "" "calibre. Your changes will only take effect after a restart of " "calibre." msgstr "" +"Wartości dla ulepszeń są przedstawione poniżej. Edytuj je, aby zmienić " +"zachowanie programu calibre. Twoje zmiany zostaną wprowadzone po ponownym " +"uruchomieniu programu calibre." #: /home/kovid/work/calibre/src/calibre/gui2/preferences/tweaks_ui.py:89 msgid "Edit tweaks for any custom plugins you have installed" -msgstr "" +msgstr "Edytuj ulepszenia dla dodatkowych wtyczek, które zainstalowałeś" #: /home/kovid/work/calibre/src/calibre/gui2/preferences/tweaks_ui.py:90 msgid "&Plugin tweaks" -msgstr "" +msgstr "Ule&pszenia wtyczek" #: /home/kovid/work/calibre/src/calibre/gui2/preferences/tweaks_ui.py:92 msgid "Edit tweak" -msgstr "" +msgstr "Edytuj ulepszenia" #: /home/kovid/work/calibre/src/calibre/gui2/preferences/tweaks_ui.py:93 msgid "Restore this tweak to its default value" -msgstr "" +msgstr "Przywróc to ulepszenie do wartości domyślnych" #: /home/kovid/work/calibre/src/calibre/gui2/preferences/tweaks_ui.py:94 msgid "Restore &default" -msgstr "" +msgstr "Przywróć &domyślne" #: /home/kovid/work/calibre/src/calibre/gui2/preferences/tweaks_ui.py:95 msgid "Apply any changes you made to this tweak" -msgstr "" +msgstr "Zastosuj wszystkie zmiany, których dokonałeś do tego ulepszenia" -#: /home/kovid/work/calibre/src/calibre/gui2/search_box.py:93 -#: /home/kovid/work/calibre/src/calibre/gui2/search_box.py:273 -#: /home/kovid/work/calibre/src/calibre/gui2/widgets.py:616 -#: /home/kovid/work/calibre/src/calibre/library/server/browse.py:277 +#: /home/kovid/work/calibre/src/calibre/gui2/search_box.py:95 +#: /home/kovid/work/calibre/src/calibre/gui2/search_box.py:279 +#: /home/kovid/work/calibre/src/calibre/gui2/store/mobileread/store_dialog_ui.py:76 +#: /home/kovid/work/calibre/src/calibre/gui2/store/search/search_ui.py:121 +#: /home/kovid/work/calibre/src/calibre/gui2/store/search_ui.py:109 +#: /home/kovid/work/calibre/src/calibre/gui2/widgets.py:653 +#: /home/kovid/work/calibre/src/calibre/library/server/browse.py:280 msgid "Search" msgstr "Szukaj" -#: /home/kovid/work/calibre/src/calibre/gui2/search_box.py:317 -msgid "The selected search will be permanently deleted. Are you sure?" -msgstr "Wybrane wyszukania będą trwale usunięte. Jesteś pewien?" +#: /home/kovid/work/calibre/src/calibre/gui2/search_box.py:339 +msgid "Delete current search" +msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/search_box.py:360 +#: /home/kovid/work/calibre/src/calibre/gui2/search_box.py:340 +msgid "No search is selected" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/search_box.py:342 +msgid "The selected search will be permanently deleted. Are you sure?" +msgstr "Wybrane wyszukania zostaną trwale usunięte. Jesteś pewien?" + +#: /home/kovid/work/calibre/src/calibre/gui2/search_box.py:367 msgid "Search (For Advanced Search click the button to the left)" msgstr "" "Szukaj (Aby użyć zaawansowanego wyszukiwania kliknij przycisk po lewej)" -#: /home/kovid/work/calibre/src/calibre/gui2/search_box.py:427 +#: /home/kovid/work/calibre/src/calibre/gui2/search_box.py:390 +msgid "Enable or disable search highlighting." +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/search_box.py:445 msgid "Saved Searches" msgstr "Zapisane wyszukania" -#: /home/kovid/work/calibre/src/calibre/gui2/search_box.py:429 +#: /home/kovid/work/calibre/src/calibre/gui2/search_box.py:447 msgid "Choose saved search or enter name for new saved search" msgstr "" "Wybierz zapisane wyszukanie lub wpisz nazwę dla nowego wyszukania do zapisu" +#: /home/kovid/work/calibre/src/calibre/gui2/search_box.py:453 +msgid "" +"Save current search under the name shown in the box. Press and hold for a " +"pop-up options menu." +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/search_box.py:458 +msgid "Create saved search" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/search_box.py:462 +msgid "Delete saved search" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/search_box.py:466 +msgid "Manage saved searches" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/search_box.py:476 +msgid "*Current search" +msgstr "" + #: /home/kovid/work/calibre/src/calibre/gui2/search_restriction_mixin.py:12 msgid "Restrict to" -msgstr "Zawęź do:" +msgstr "Ogranicz do:" #: /home/kovid/work/calibre/src/calibre/gui2/search_restriction_mixin.py:19 -#: /home/kovid/work/calibre/src/calibre/gui2/search_restriction_mixin.py:59 +#: /home/kovid/work/calibre/src/calibre/gui2/search_restriction_mixin.py:92 msgid "(all books)" msgstr "(wszystkie książki)" -#: /home/kovid/work/calibre/src/calibre/gui2/search_restriction_mixin.py:54 +#: /home/kovid/work/calibre/src/calibre/gui2/search_restriction_mixin.py:21 +msgid "" +"Books display will be restricted to those matching a selected saved search" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/search_restriction_mixin.py:53 +msgid " or the search " +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/search_restriction_mixin.py:87 msgid "({0} of {1})" msgstr "({0} z {1})" -#: /home/kovid/work/calibre/src/calibre/gui2/search_restriction_mixin.py:61 +#: /home/kovid/work/calibre/src/calibre/gui2/search_restriction_mixin.py:94 msgid "({0} of all)" msgstr "({0} ze wszystkich)" +#: /home/kovid/work/calibre/src/calibre/gui2/shortcuts.py:48 +#: /home/kovid/work/calibre/src/calibre/gui2/shortcuts_ui.py:78 +#: /home/kovid/work/calibre/src/calibre/gui2/shortcuts_ui.py:83 +#: /home/kovid/work/calibre/src/calibre/gui2/store/search/search_ui.py:125 +#: /home/kovid/work/calibre/src/calibre/gui2/store/search_ui.py:113 +#: /home/kovid/work/calibre/src/calibre/gui2/widgets.py:351 +msgid "None" +msgstr "Brak" + #: /home/kovid/work/calibre/src/calibre/gui2/shortcuts.py:59 msgid "Press a key..." msgstr "Wciśnij dowolny klawisz..." #: /home/kovid/work/calibre/src/calibre/gui2/shortcuts.py:80 msgid "Already assigned" -msgstr "Jest juz przypisany" +msgstr "Jest już przypisany" #: /home/kovid/work/calibre/src/calibre/gui2/shortcuts.py:82 msgid "already assigned to" @@ -12035,7 +13858,7 @@ msgstr "Klawisze" #: /home/kovid/work/calibre/src/calibre/gui2/shortcuts.py:225 msgid "Double click to change" -msgstr "Kliknij dwa razy any zmienić" +msgstr "Kliknij dwa razy aby zmienić" #: /home/kovid/work/calibre/src/calibre/gui2/shortcuts_ui.py:73 msgid "Frame" @@ -12058,92 +13881,371 @@ msgstr "Kliknij, by zmienić" msgid "&Alternate shortcut:" msgstr "&Alternatywny skrót:" -#: /home/kovid/work/calibre/src/calibre/gui2/tag_view.py:282 +#: /home/kovid/work/calibre/src/calibre/gui2/store/basic_config_widget_ui.py:38 +msgid "Added Tags:" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/store/basic_config_widget_ui.py:39 +msgid "Open store in external web browswer" +msgstr "Otwórz stronę sklepu w zewnętrznej przeglądarce" + +#: /home/kovid/work/calibre/src/calibre/gui2/store/ebooks_com_plugin.py:96 +msgid "Not Available" +msgstr "Niedostępne" + +#: /home/kovid/work/calibre/src/calibre/gui2/store/mobileread/cache_progress_dialog_ui.py:51 +msgid "Updating book cache" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/store/mobileread/cache_update_thread.py:42 +msgid "Checking last download date." +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/store/mobileread/cache_update_thread.py:48 +msgid "Downloading book list from MobileRead." +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/store/mobileread/cache_update_thread.py:61 +msgid "Processing books." +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/store/mobileread/cache_update_thread.py:70 +msgid "%s of %s books processed." +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/store/mobileread/mobileread_plugin.py:62 +msgid "Updating MobileRead book cache..." +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/store/mobileread/store_dialog_ui.py:74 +msgid "&Query:" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/store/mobileread/store_dialog_ui.py:77 +#: /home/kovid/work/calibre/src/calibre/gui2/store/mobileread_store_dialog_ui.py:63 +msgid "Books:" +msgstr "Książki:" + +#: /home/kovid/work/calibre/src/calibre/gui2/store/mobileread/store_dialog_ui.py:79 +#: /home/kovid/work/calibre/src/calibre/gui2/store/mobileread_store_dialog_ui.py:65 +#: /home/kovid/work/calibre/src/calibre/gui2/store/search/search_ui.py:128 +#: /home/kovid/work/calibre/src/calibre/gui2/store/search_ui.py:114 +#: /home/kovid/work/calibre/src/calibre/gui2/store/web_store_dialog_ui.py:63 +msgid "Close" +msgstr "Zamknij" + +#: /home/kovid/work/calibre/src/calibre/gui2/store/mobileread_store_dialog_ui.py:62 +msgid "Search:" +msgstr "Wyszukaj:" + +#: /home/kovid/work/calibre/src/calibre/gui2/store/search/adv_search_builder_ui.py:192 +msgid "&Price:" +msgstr "&Cena:" + +#: /home/kovid/work/calibre/src/calibre/gui2/store/search/adv_search_builder_ui.py:196 +msgid "Titl&e/Author/Price ..." +msgstr "T&ytuł/Autor/Cena ..." + +#: /home/kovid/work/calibre/src/calibre/gui2/store/search/models.py:33 +msgid "DRM" +msgstr "DRM" + +#: /home/kovid/work/calibre/src/calibre/gui2/store/search/models.py:33 +msgid "Price" +msgstr "Cena" + +#: /home/kovid/work/calibre/src/calibre/gui2/store/search/models.py:175 +msgid "" +"Detected price as: %s. Check with the store before making a purchase to " +"verify this price is correct. This price often does not include promotions " +"the store may be running." +msgstr "" +"Wykryto cenę: %s. Przed dokonaniem zakupu sprawdź sklep, by zweryfikować, " +"czy jest ona poprawna. Podana cena często nie uwzględnia promocji aktualnie " +"obowiązujących w danym sklepie." + +#: /home/kovid/work/calibre/src/calibre/gui2/store/search/models.py:178 +msgid "" +"This book as been detected as having DRM restrictions. This book may not " +"work with your reader and you will have limitations placed upon you as to " +"what you can do with this book. Check with the store before making any " +"purchases to ensure you can actually read this book." +msgstr "" +"Ta książka posiada zabezpieczenia DRM. Może ona nie działać na twoim " +"czytniku. Dodatkowo sposób jej użycia może być ograniczany. Przed zakupem " +"upewnij się, że będziesz miał możliwość przeczytać tę książkę." + +#: /home/kovid/work/calibre/src/calibre/gui2/store/search/models.py:180 +msgid "" +"This book has been detected as being DRM Free. You should be able to use " +"this book on any device provided it is in a format calibre supports for " +"conversion. However, before making a purchase double check the DRM status " +"with the store. The store may not be disclosing the use of DRM." +msgstr "" +"Ta książka jest wolna od zabezpiczeń DRM. Pownieneś móc otworzyć tę książkę " +"na każdym urządzeniu, które wspiera formaty do których calibre potrafi " +"konwertować. Jednak przed zakupem upewnij się co do braku zabezpieczeń - " +"niektóre sklepy nie publikują informacji o stosowanych zabezpieczeniach." + +#: /home/kovid/work/calibre/src/calibre/gui2/store/search/models.py:182 +msgid "" +"The DRM status of this book could not be determined. There is a very high " +"likelihood that this book is actually DRM restricted." +msgstr "" +"Nie udało się sprawdzić obecności zabezpieczeń DRM. Istnieje duże " +"prawdopodobieństwo, że ta książka posiada zabezpieczenia DRM." + +#: /home/kovid/work/calibre/src/calibre/gui2/store/search/search.py:223 +msgid "Couldn't find any books matching your query." +msgstr "Nie udało się znaleźć książek pasujących do zapytania." + +#: /home/kovid/work/calibre/src/calibre/gui2/store/search/search_ui.py:118 +#: /home/kovid/work/calibre/src/calibre/gui2/store/search_ui.py:107 +msgid "Get Books" +msgstr "Zdobądź książki" + +#: /home/kovid/work/calibre/src/calibre/gui2/store/search/search_ui.py:119 +#: /home/kovid/work/calibre/src/calibre/gui2/store/search_ui.py:108 +msgid "Query:" +msgstr "Zapytanie:" + +#: /home/kovid/work/calibre/src/calibre/gui2/store/search/search_ui.py:123 +#: /home/kovid/work/calibre/src/calibre/gui2/store/search_ui.py:111 +msgid "All" +msgstr "Wszystkie" + +#: /home/kovid/work/calibre/src/calibre/gui2/store/search/search_ui.py:124 +#: /home/kovid/work/calibre/src/calibre/gui2/store/search_ui.py:112 +msgid "Invert" +msgstr "Odwróć zaznaczenie" + +#: /home/kovid/work/calibre/src/calibre/gui2/store/search/search_ui.py:126 +msgid "Open a selected book in the system's web browser" +msgstr "Otwórz wybraną książkę w przeglądarce systemowej" + +#: /home/kovid/work/calibre/src/calibre/gui2/store/search/search_ui.py:127 +msgid "Open in &external browser" +msgstr "Otwórz w z&ewnętrznej przeglądarce" + +#: /home/kovid/work/calibre/src/calibre/gui2/store/web_control.py:73 +msgid "" +"This ebook is a DRMed EPUB file. You will be prompted to save this file to " +"your computer. Once it is saved, open it with Adobe Digital " +"Editions (ADE).

      ADE, in turn will download the actual ebook, which " +"will be a .epub file. You can add this book to calibre using \"Add Books\" " +"and selecting the file from the ADE library folder." +msgstr "" +"Ta książka to EPUB zabezpieczony DRM. Zostaniesz poproszony o zapisanie " +"pliku na komputerze. Po zapisaniu, otwórz go za pomocą Adobe Digital " +"Editions (ADE).

      Z kolei ADE pobierze właściwy plik EPUB. Możesz " +"następnie dodać tą książkę do calibre używająć przicusku \"Dodaj książki\" i " +"wybierając plik z katlogu z biblioteką ADE." + +#: /home/kovid/work/calibre/src/calibre/gui2/store/web_control.py:86 +msgid "File is not a supported ebook type. Save to disk?" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/store/web_store_dialog_ui.py:59 +msgid "Home" +msgstr "Strona główna" + +#: /home/kovid/work/calibre/src/calibre/gui2/store/web_store_dialog_ui.py:60 +msgid "Reload" +msgstr "Odśwież" + +#: /home/kovid/work/calibre/src/calibre/gui2/store/web_store_dialog_ui.py:61 +msgid "%p%" +msgstr "%p%" + +#: /home/kovid/work/calibre/src/calibre/gui2/tag_view.py:345 +#: /home/kovid/work/calibre/src/calibre/gui2/tag_view.py:375 +#: /home/kovid/work/calibre/src/calibre/gui2/tag_view.py:404 msgid "Rename %s" -msgstr "" +msgstr "Zmień nazwę %s" -#: /home/kovid/work/calibre/src/calibre/gui2/tag_view.py:286 +#: /home/kovid/work/calibre/src/calibre/gui2/tag_view.py:349 msgid "Edit sort for %s" -msgstr "" +msgstr "Edytuj sortowanie dla %s" -#: /home/kovid/work/calibre/src/calibre/gui2/tag_view.py:291 +#: /home/kovid/work/calibre/src/calibre/gui2/tag_view.py:356 +msgid "Add %s to user category" +msgstr "Dodaj %s do kategorii użytkownika" + +#: /home/kovid/work/calibre/src/calibre/gui2/tag_view.py:369 +msgid "Children of %s" +msgstr "Dzieci %s" + +#: /home/kovid/work/calibre/src/calibre/gui2/tag_view.py:379 +msgid "Delete search %s" +msgstr "Skasuj wyszukiwania %s" + +#: /home/kovid/work/calibre/src/calibre/gui2/tag_view.py:384 +msgid "Remove %s from category %s" +msgstr "Usuń %s z kategorii %s" + +#: /home/kovid/work/calibre/src/calibre/gui2/tag_view.py:391 msgid "Search for %s" -msgstr "" +msgstr "Szukaj %s" -#: /home/kovid/work/calibre/src/calibre/gui2/tag_view.py:296 +#: /home/kovid/work/calibre/src/calibre/gui2/tag_view.py:396 msgid "Search for everything but %s" -msgstr "" +msgstr "Szukaj wszystkiego oprócz %s" -#: /home/kovid/work/calibre/src/calibre/gui2/tag_view.py:302 +#: /home/kovid/work/calibre/src/calibre/gui2/tag_view.py:408 +msgid "Add sub-category to %s" +msgstr "Dodaj podkategorię do %s" + +#: /home/kovid/work/calibre/src/calibre/gui2/tag_view.py:412 +msgid "Delete user category %s" +msgstr "Skasuj kategorię użytkownika %s" + +#: /home/kovid/work/calibre/src/calibre/gui2/tag_view.py:417 msgid "Hide category %s" msgstr "Ukryj kategorię %s" -#: /home/kovid/work/calibre/src/calibre/gui2/tag_view.py:305 +#: /home/kovid/work/calibre/src/calibre/gui2/tag_view.py:421 msgid "Show category" msgstr "Wyświetl kategorię" -#: /home/kovid/work/calibre/src/calibre/gui2/tag_view.py:313 +#: /home/kovid/work/calibre/src/calibre/gui2/tag_view.py:431 msgid "Search for books in category %s" -msgstr "" +msgstr "Szukaj książek w kategorii %s" -#: /home/kovid/work/calibre/src/calibre/gui2/tag_view.py:317 +#: /home/kovid/work/calibre/src/calibre/gui2/tag_view.py:437 msgid "Search for books not in category %s" -msgstr "" +msgstr "Szukaj książek nie w kategorii %s" -#: /home/kovid/work/calibre/src/calibre/gui2/tag_view.py:324 -#: /home/kovid/work/calibre/src/calibre/gui2/tag_view.py:328 +#: /home/kovid/work/calibre/src/calibre/gui2/tag_view.py:446 +#: /home/kovid/work/calibre/src/calibre/gui2/tag_view.py:451 msgid "Manage %s" msgstr "Zarządzaj %s" -#: /home/kovid/work/calibre/src/calibre/gui2/tag_view.py:331 +#: /home/kovid/work/calibre/src/calibre/gui2/tag_view.py:454 +#: /home/kovid/work/calibre/src/calibre/gui2/tag_view.py:1844 msgid "Manage Saved Searches" msgstr "Zarządzaj zapisanymi wyszukaniami" -#: /home/kovid/work/calibre/src/calibre/gui2/tag_view.py:338 -#: /home/kovid/work/calibre/src/calibre/gui2/tag_view.py:342 +#: /home/kovid/work/calibre/src/calibre/gui2/tag_view.py:462 +#: /home/kovid/work/calibre/src/calibre/gui2/tag_view.py:466 +#: /home/kovid/work/calibre/src/calibre/gui2/tag_view.py:1842 msgid "Manage User Categories" msgstr "Zarządzaj kategoriami użytkownika" -#: /home/kovid/work/calibre/src/calibre/gui2/tag_view.py:349 +#: /home/kovid/work/calibre/src/calibre/gui2/tag_view.py:473 msgid "Show all categories" msgstr "Wyświetl wszystkie kategorie" -#: /home/kovid/work/calibre/src/calibre/gui2/tag_view.py:352 +#: /home/kovid/work/calibre/src/calibre/gui2/tag_view.py:476 msgid "Change sub-categorization scheme" -msgstr "" +msgstr "Zmień schemat pod-kategoryzacji" -#: /home/kovid/work/calibre/src/calibre/gui2/tag_view.py:597 +#: /home/kovid/work/calibre/src/calibre/gui2/tag_view.py:792 msgid "The grouped search term name is \"{0}\"" -msgstr "" +msgstr "Nazwa zgrupowanego wyszukiwania to \"{0}\"" -#: /home/kovid/work/calibre/src/calibre/gui2/tag_view.py:689 +#: /home/kovid/work/calibre/src/calibre/gui2/tag_view.py:1065 msgid "" "Changing the authors for several books can take a while. Are you sure?" -msgstr "" +msgstr "Zmiana autorów dla kilku książek może chwilę zająć. Jesteś pewien?" -#: /home/kovid/work/calibre/src/calibre/gui2/tag_view.py:694 +#: /home/kovid/work/calibre/src/calibre/gui2/tag_view.py:1070 msgid "" "Changing the metadata for that many books can take a while. Are you sure?" -msgstr "" +msgstr "Zmiana metadanych dla tylu książek może chwilę zająć. Jesteś pewien?" -#: /home/kovid/work/calibre/src/calibre/gui2/tag_view.py:772 -#: /home/kovid/work/calibre/src/calibre/library/database2.py:372 +#: /home/kovid/work/calibre/src/calibre/gui2/tag_view.py:1157 +#: /home/kovid/work/calibre/src/calibre/library/database2.py:417 msgid "Searches" msgstr "Wyszukiwania" -#: /home/kovid/work/calibre/src/calibre/gui2/tag_view.py:921 +#: /home/kovid/work/calibre/src/calibre/gui2/tag_view.py:1391 +#: /home/kovid/work/calibre/src/calibre/gui2/tag_view.py:1411 +#: /home/kovid/work/calibre/src/calibre/gui2/tag_view.py:1420 +msgid "Rename user category" +msgstr "Zmień kategorię użytkownika" + +#: /home/kovid/work/calibre/src/calibre/gui2/tag_view.py:1392 +msgid "You cannot use periods in the name when renaming user categories" +msgstr "" +"Nie możesz używać okresów w nazwie podczas zmiany nazwy kategorii użytkownika" + +#: /home/kovid/work/calibre/src/calibre/gui2/tag_view.py:1412 +#: /home/kovid/work/calibre/src/calibre/gui2/tag_view.py:1421 +msgid "The name %s is already used" +msgstr "Nazwa %s już jest w użyciu" + +#: /home/kovid/work/calibre/src/calibre/gui2/tag_view.py:1444 msgid "Duplicate search name" msgstr "Powtórzona nazwa wyszukania" -#: /home/kovid/work/calibre/src/calibre/gui2/tag_view.py:922 +#: /home/kovid/work/calibre/src/calibre/gui2/tag_view.py:1445 msgid "The saved search name %s is already used." msgstr "Nazwa zapisanego wyszukania %s jest już używana." -#: /home/kovid/work/calibre/src/calibre/gui2/tag_view.py:1343 -msgid "Find item in tag browser" +#: /home/kovid/work/calibre/src/calibre/gui2/tag_view.py:1834 +msgid "Manage Authors" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/tag_view.py:1346 +#: /home/kovid/work/calibre/src/calibre/gui2/tag_view.py:1836 +msgid "Manage Series" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/tag_view.py:1838 +msgid "Manage Publishers" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/tag_view.py:1840 +msgid "Manage Tags" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/tag_view.py:1852 +msgid "Invalid search restriction" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/tag_view.py:1853 +msgid "The current search restriction is invalid" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/tag_view.py:1869 +msgid "New Category" +msgstr "Nowa kategoria" + +#: /home/kovid/work/calibre/src/calibre/gui2/tag_view.py:1920 +#: /home/kovid/work/calibre/src/calibre/gui2/tag_view.py:1923 +msgid "Delete user category" +msgstr "Skasuj kategorię użytkownika" + +#: /home/kovid/work/calibre/src/calibre/gui2/tag_view.py:1921 +msgid "%s is not a user category" +msgstr "%s nie jest kategorią użytkownika" + +#: /home/kovid/work/calibre/src/calibre/gui2/tag_view.py:1924 +msgid "%s contains items. Do you really want to delete it?" +msgstr "%s zawiera elementy. Naprawdę chcesz skasować?" + +#: /home/kovid/work/calibre/src/calibre/gui2/tag_view.py:1945 +msgid "Remove category" +msgstr "Usuń kategorię" + +#: /home/kovid/work/calibre/src/calibre/gui2/tag_view.py:1946 +msgid "User category %s does not exist" +msgstr "Kategoria użytkownika %s nie istnieje" + +#: /home/kovid/work/calibre/src/calibre/gui2/tag_view.py:1965 +msgid "Add to user category" +msgstr "Dodaj do kategorii użytkownika" + +#: /home/kovid/work/calibre/src/calibre/gui2/tag_view.py:1966 +msgid "A user category %s does not exist" +msgstr "Kategoria użytkownika %s nie istnieje" + +#: /home/kovid/work/calibre/src/calibre/gui2/tag_view.py:2089 +msgid "Find item in tag browser" +msgstr "Znajdź element w przeglądarce elementów" + +#: /home/kovid/work/calibre/src/calibre/gui2/tag_view.py:2092 msgid "" "Search for items. This is a \"contains\" search; items containing the\n" "text anywhere in the name will be found. You can limit the search\n" @@ -12152,67 +14254,80 @@ msgid "" "*foo will filter all categories at once, showing only those items\n" "containing the text \"foo\"" msgstr "" +"Szukaj elementów. To jest wyszukiwanie typu \"zawiera\"; elementy " +"zawierające\n" +"tekst gdziekolwiek w nazwie zostaną znalezione. Możesz ograniczyć " +"wyszukiwanie\n" +"do określonych kategorii używając składni identycznej do wyszukiwania. Na " +"przykład,\n" +"etykiety:foo znajdzie foo w każdej etykiecie, ale nie w autorze itd. " +"Wpisując\n" +"*foo odfiltruje natychmiast wszystkie kategorie, pokazując tylko te " +"elementy\n" +"zawierające tekst \"foo\"" -#: /home/kovid/work/calibre/src/calibre/gui2/tag_view.py:1355 +#: /home/kovid/work/calibre/src/calibre/gui2/tag_view.py:2101 msgid "ALT+f" -msgstr "" +msgstr "ALT+f" -#: /home/kovid/work/calibre/src/calibre/gui2/tag_view.py:1359 -msgid "F&ind" -msgstr "" - -#: /home/kovid/work/calibre/src/calibre/gui2/tag_view.py:1360 +#: /home/kovid/work/calibre/src/calibre/gui2/tag_view.py:2106 msgid "Find the first/next matching item" msgstr "Znajdź pierwszą/kolejną pasującą pozycję" -#: /home/kovid/work/calibre/src/calibre/gui2/tag_view.py:1367 +#: /home/kovid/work/calibre/src/calibre/gui2/tag_view.py:2111 msgid "Collapse all categories" -msgstr "" +msgstr "Zwiń wszystkie kategorie" -#: /home/kovid/work/calibre/src/calibre/gui2/tag_view.py:1388 +#: /home/kovid/work/calibre/src/calibre/gui2/tag_view.py:2135 msgid "No More Matches.

      Click Find again to go to first match" msgstr "" +"Brak dalszych trafień.

      Kliknij Szukaj ponownie, aby znaleźć pierwsze " +"trafienie" -#: /home/kovid/work/calibre/src/calibre/gui2/tag_view.py:1401 +#: /home/kovid/work/calibre/src/calibre/gui2/tag_view.py:2148 msgid "Sort by name" msgstr "Sortuj wg nazwy" -#: /home/kovid/work/calibre/src/calibre/gui2/tag_view.py:1401 +#: /home/kovid/work/calibre/src/calibre/gui2/tag_view.py:2148 msgid "Sort by popularity" msgstr "Sortuj wg popularności" -#: /home/kovid/work/calibre/src/calibre/gui2/tag_view.py:1402 +#: /home/kovid/work/calibre/src/calibre/gui2/tag_view.py:2149 msgid "Sort by average rating" msgstr "Sortuj wg średniej oceny" -#: /home/kovid/work/calibre/src/calibre/gui2/tag_view.py:1405 +#: /home/kovid/work/calibre/src/calibre/gui2/tag_view.py:2152 msgid "Set the sort order for entries in the Tag Browser" -msgstr "Ustaw porządek sortowania dla wpisów w Przeglądarce etykiet" +msgstr "Ustaw porządek sortowania dla wpisów w przeglądarce etykiet" -#: /home/kovid/work/calibre/src/calibre/gui2/tag_view.py:1411 +#: /home/kovid/work/calibre/src/calibre/gui2/tag_view.py:2159 msgid "Match all" msgstr "Dopasuj wszystkie" -#: /home/kovid/work/calibre/src/calibre/gui2/tag_view.py:1411 +#: /home/kovid/work/calibre/src/calibre/gui2/tag_view.py:2159 msgid "Match any" msgstr "Dopasuj którykolwiek" -#: /home/kovid/work/calibre/src/calibre/gui2/tag_view.py:1416 +#: /home/kovid/work/calibre/src/calibre/gui2/tag_view.py:2164 msgid "" "When selecting multiple entries in the Tag Browser match any or all of them" msgstr "" +"Przy wybraniu wielu wpisów w przeglądarce etykiet dopasuj jakiekolwiek lub " +"wszystkie z nich" -#: /home/kovid/work/calibre/src/calibre/gui2/tag_view.py:1420 -msgid "Manage &user categories" -msgstr "Zarządzaj kategoriami &użytkownika" +#: /home/kovid/work/calibre/src/calibre/gui2/tag_view.py:2171 +msgid "Manage authors, tags, etc" +msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/tag_view.py:1423 -msgid "Add your own categories to the Tag Browser" -msgstr "Dodaj swoje własne kategorie do Przeglądarki etykiet" +#: /home/kovid/work/calibre/src/calibre/gui2/tag_view.py:2172 +msgid "" +"All of these category_managers are available by right-clicking on items in " +"the tag browser above" +msgstr "" #: /home/kovid/work/calibre/src/calibre/gui2/tools.py:65 msgid "Convert book %(num)d of %(total)d (%(title)s)" -msgstr "" +msgstr "Konwertuj książkę %(num)d z %(total)d (%(title)s)" #: /home/kovid/work/calibre/src/calibre/gui2/tools.py:93 #: /home/kovid/work/calibre/src/calibre/gui2/tools.py:205 @@ -12234,7 +14349,7 @@ msgstr "Kolejkowanie książek do masowej konwersji" #: /home/kovid/work/calibre/src/calibre/gui2/tools.py:183 msgid "Queueing " -msgstr "Kolejnkowanie " +msgstr "Kolejkowanie " #: /home/kovid/work/calibre/src/calibre/gui2/tools.py:184 msgid "Convert book %d of %d (%s)" @@ -12246,7 +14361,7 @@ msgstr "Pobierzy newsy z " #: /home/kovid/work/calibre/src/calibre/gui2/tools.py:327 msgid "Convert existing" -msgstr "Okładka istnieje" +msgstr "Konwertuj istniejące" #: /home/kovid/work/calibre/src/calibre/gui2/tools.py:328 msgid "" @@ -12256,61 +14371,50 @@ msgstr "" "Następujące książki zostały już skonwertowane do formatu %s. Czy chcesz, by " "zostały ponownie skonwertowane?" -#: /home/kovid/work/calibre/src/calibre/gui2/ui.py:188 -msgid "&Restore" -msgstr "&Przywróć" - -#: /home/kovid/work/calibre/src/calibre/gui2/ui.py:190 +#: /home/kovid/work/calibre/src/calibre/gui2/ui.py:196 msgid "&Donate to support calibre" msgstr "&Wpłać i wesprzyj rozwój calibre" -#: /home/kovid/work/calibre/src/calibre/gui2/ui.py:194 +#: /home/kovid/work/calibre/src/calibre/gui2/ui.py:232 +msgid "&Restore" +msgstr "&Przywróć" + +#: /home/kovid/work/calibre/src/calibre/gui2/ui.py:237 msgid "&Eject connected device" msgstr "&Odłącz połączone urządzenie" -#: /home/kovid/work/calibre/src/calibre/gui2/ui.py:239 -msgid "Calibre Quick Start Guide" -msgstr "Krótki przewodnik po calibre" - -#: /home/kovid/work/calibre/src/calibre/gui2/ui.py:301 +#: /home/kovid/work/calibre/src/calibre/gui2/ui.py:345 msgid "Debug mode" -msgstr "" +msgstr "Tryb debugowania" -#: /home/kovid/work/calibre/src/calibre/gui2/ui.py:302 +#: /home/kovid/work/calibre/src/calibre/gui2/ui.py:346 msgid "" "You have started calibre in debug mode. After you quit calibre, the debug " "log will be available in the file: %s

      The log will be displayed " "automatically." msgstr "" -"Uruchomiłeś calibre w trybie debugowania. Po opuszczeniu programu, log " -"debugowania będzie dostępny w pliku: %s

      Log zostanie automatycznie " +"Uruchomiłeś calibre w trybie debugowania. Po opuszczeniu programu, dziennik " +"debugowania będzie dostępny w pliku: %s

      Dziennik zostanie automatycznie " "wyświetlony." -#: /home/kovid/work/calibre/src/calibre/gui2/ui.py:493 +#: /home/kovid/work/calibre/src/calibre/gui2/ui.py:548 msgid "Conversion Error" msgstr "Błąd podczas konwersji" -#: /home/kovid/work/calibre/src/calibre/gui2/ui.py:516 +#: /home/kovid/work/calibre/src/calibre/gui2/ui.py:571 msgid "Recipe Disabled" msgstr "Źródło wyłączone" -#: /home/kovid/work/calibre/src/calibre/gui2/ui.py:532 +#: /home/kovid/work/calibre/src/calibre/gui2/ui.py:587 msgid "Failed" msgstr "Nie powiodło się" -#: /home/kovid/work/calibre/src/calibre/gui2/ui.py:569 -msgid "" -"is the result of the efforts of many volunteers from all over the world. If " -"you find it useful, please consider donating to support its development. " -"Your donation helps keep calibre development going." -msgstr "" - -#: /home/kovid/work/calibre/src/calibre/gui2/ui.py:595 +#: /home/kovid/work/calibre/src/calibre/gui2/ui.py:621 msgid "There are active jobs. Are you sure you want to quit?" msgstr "" -"Niektóre zadania są aktywne. Jesteś pewnien, że chcesz zamknąć program?" +"Niektóre zadania są aktywne. Jesteś pewien, że chcesz zamknąć program?" -#: /home/kovid/work/calibre/src/calibre/gui2/ui.py:598 +#: /home/kovid/work/calibre/src/calibre/gui2/ui.py:624 msgid "" " is communicating with the device!
      \n" " Quitting may cause corruption on the device.
      \n" @@ -12320,11 +14424,11 @@ msgstr "" " Wyjście może spowodować uszkodzenie urządzenia.
      \n" " Jesteś pewny, że chcesz wyjść?" -#: /home/kovid/work/calibre/src/calibre/gui2/ui.py:602 +#: /home/kovid/work/calibre/src/calibre/gui2/ui.py:628 msgid "Active jobs" -msgstr "" +msgstr "Aktywne zadania" -#: /home/kovid/work/calibre/src/calibre/gui2/ui.py:668 +#: /home/kovid/work/calibre/src/calibre/gui2/ui.py:696 msgid "" "will keep running in the system tray. To close it, choose Quit in the " "context menu of the system tray." @@ -12337,14 +14441,16 @@ msgid "" "%s has been updated to version %s. See the new features." msgstr "" +"%s został zaktualizowany do wersji %s. Zobacz nowe funkcje." #: /home/kovid/work/calibre/src/calibre/gui2/update.py:58 msgid "Update available!" -msgstr "" +msgstr "Jest dostępna aktualizacja!" #: /home/kovid/work/calibre/src/calibre/gui2/update.py:63 msgid "Show this notification for future updates" -msgstr "" +msgstr "Pokazuj to powiadomienie dla przyszłych aktualizacji" #: /home/kovid/work/calibre/src/calibre/gui2/update.py:68 msgid "&Get update" @@ -12372,11 +14478,11 @@ msgstr "Import zakładek" #: /home/kovid/work/calibre/src/calibre/gui2/viewer/bookmarkmanager.py:62 msgid "Pickled Bookmarks (*.pickle)" -msgstr "" +msgstr "Zapisane zakładki (*.pickle)" #: /home/kovid/work/calibre/src/calibre/gui2/viewer/bookmarkmanager_ui.py:61 msgid "Bookmark Manager" -msgstr "Menadżer zakładek" +msgstr "Menedżer zakładek" #: /home/kovid/work/calibre/src/calibre/gui2/viewer/bookmarkmanager_ui.py:62 msgid "Actions" @@ -12426,7 +14532,7 @@ msgstr "&Domyślny rozmiar czcionki:" #: /home/kovid/work/calibre/src/calibre/gui2/viewer/config_ui.py:186 #: /home/kovid/work/calibre/src/calibre/gui2/viewer/config_ui.py:201 msgid " px" -msgstr " px" +msgstr " piks." #: /home/kovid/work/calibre/src/calibre/gui2/viewer/config_ui.py:185 msgid "Monospace &font size:" @@ -12442,7 +14548,7 @@ msgstr "Szeryfowa" #: /home/kovid/work/calibre/src/calibre/gui2/viewer/config_ui.py:189 msgid "Sans-serif" -msgstr "Sans-serif" +msgstr "Bez-szeryfowa" #: /home/kovid/work/calibre/src/calibre/gui2/viewer/config_ui.py:190 msgid "Monospace" @@ -12484,10 +14590,6 @@ msgstr "&Czas przewracania stron" msgid "disabled" msgstr "wyłączony" -#: /home/kovid/work/calibre/src/calibre/gui2/viewer/config_ui.py:199 -msgid " secs" -msgstr " sekund" - #: /home/kovid/work/calibre/src/calibre/gui2/viewer/config_ui.py:200 msgid "Mouse &wheel flips pages" msgstr "Zmiana stron &kółkiem myszy" @@ -12514,10 +14616,13 @@ msgid "" "For examples, click here." msgstr "" +"

      Style CSS, który może być używany do kontrolowania wyglądu i stylu " +"książek. Dla przykładów kliknij tutaj." #: /home/kovid/work/calibre/src/calibre/gui2/viewer/config_ui.py:207 msgid "User &Stylesheet" -msgstr "" +msgstr "Style użytkownika" #: /home/kovid/work/calibre/src/calibre/gui2/viewer/dictionary.py:53 msgid "No results found for:" @@ -12528,7 +14633,7 @@ msgid "Options to customize the ebook viewer" msgstr "Opcje odpowiedzialne za personalizacje przeglądarki książek" #: /home/kovid/work/calibre/src/calibre/gui2/viewer/documentview.py:47 -#: /home/kovid/work/calibre/src/calibre/gui2/viewer/main.py:767 +#: /home/kovid/work/calibre/src/calibre/gui2/viewer/main.py:783 msgid "Remember last used window size" msgstr "Zapamiętaj ostatnio użyty rozmiar okienka" @@ -12571,6 +14676,8 @@ msgstr "Zmiana stron kółkiem myszy" msgid "" "The time, in seconds, for the page flip animation. Default is half a second." msgstr "" +"Czas, w sekundach, na animację przewrócenia strony. Domyślnie jest to pół " +"sekundy." #: /home/kovid/work/calibre/src/calibre/gui2/viewer/documentview.py:65 msgid "Font options" @@ -12582,7 +14689,7 @@ msgstr "Rodzina czcionek szeryfowych" #: /home/kovid/work/calibre/src/calibre/gui2/viewer/documentview.py:69 msgid "The sans-serif font family" -msgstr "Rodzina czcionek sans-serif" +msgstr "Rodzina czcionek bez-szeryfowych" #: /home/kovid/work/calibre/src/calibre/gui2/viewer/documentview.py:71 msgid "The monospaced font family" @@ -12602,44 +14709,46 @@ msgstr "Standardowy typ czcionki" #: /home/kovid/work/calibre/src/calibre/gui2/viewer/documentview.py:125 msgid "Still editing" -msgstr "" +msgstr "Wciąż wdytuję" #: /home/kovid/work/calibre/src/calibre/gui2/viewer/documentview.py:126 msgid "" "You are in the middle of editing a keyboard shortcut first complete that, by " "clicking outside the shortcut editing box." msgstr "" +"Jesteś w trakcie edycji skrótu klawiaturowego, zakończ to klikając poza pole " +"edycji skrótu." -#: /home/kovid/work/calibre/src/calibre/gui2/viewer/documentview.py:515 +#: /home/kovid/work/calibre/src/calibre/gui2/viewer/documentview.py:534 msgid "&Lookup in dictionary" msgstr "Sprawdź w słowniku" -#: /home/kovid/work/calibre/src/calibre/gui2/viewer/documentview.py:518 -#: /home/kovid/work/calibre/src/calibre/gui2/viewer/main.py:139 +#: /home/kovid/work/calibre/src/calibre/gui2/viewer/documentview.py:538 +#: /home/kovid/work/calibre/src/calibre/gui2/viewer/main.py:141 msgid "Go to..." msgstr "Przejdź do..." -#: /home/kovid/work/calibre/src/calibre/gui2/viewer/documentview.py:530 +#: /home/kovid/work/calibre/src/calibre/gui2/viewer/documentview.py:550 msgid "Next Section" msgstr "Następna sekcja" -#: /home/kovid/work/calibre/src/calibre/gui2/viewer/documentview.py:531 +#: /home/kovid/work/calibre/src/calibre/gui2/viewer/documentview.py:551 msgid "Previous Section" msgstr "Poprzednia sekcja" -#: /home/kovid/work/calibre/src/calibre/gui2/viewer/documentview.py:533 +#: /home/kovid/work/calibre/src/calibre/gui2/viewer/documentview.py:553 msgid "Document Start" msgstr "Początek dokumentu" -#: /home/kovid/work/calibre/src/calibre/gui2/viewer/documentview.py:534 +#: /home/kovid/work/calibre/src/calibre/gui2/viewer/documentview.py:554 msgid "Document End" msgstr "Koniec dokumentu" -#: /home/kovid/work/calibre/src/calibre/gui2/viewer/documentview.py:536 +#: /home/kovid/work/calibre/src/calibre/gui2/viewer/documentview.py:556 msgid "Section Start" msgstr "Początek sekcji" -#: /home/kovid/work/calibre/src/calibre/gui2/viewer/documentview.py:537 +#: /home/kovid/work/calibre/src/calibre/gui2/viewer/documentview.py:557 msgid "Section End" msgstr "Koniec sekcji" @@ -12691,102 +14800,114 @@ msgstr "Przewiń w lewo" msgid "Scroll right" msgstr "Przewiń w prawo" -#: /home/kovid/work/calibre/src/calibre/gui2/viewer/main.py:116 +#: /home/kovid/work/calibre/src/calibre/gui2/viewer/main.py:118 msgid "Book format" msgstr "Format książki" -#: /home/kovid/work/calibre/src/calibre/gui2/viewer/main.py:196 +#: /home/kovid/work/calibre/src/calibre/gui2/viewer/main.py:198 msgid "Position in book" msgstr "Pozycja w książce" -#: /home/kovid/work/calibre/src/calibre/gui2/viewer/main.py:203 +#: /home/kovid/work/calibre/src/calibre/gui2/viewer/main.py:205 msgid "Go to a reference. To get reference numbers, use the reference mode." msgstr "" +"Idź do referencji. Aby uzyskać numery referencji, użyj trybu referencji." -#: /home/kovid/work/calibre/src/calibre/gui2/viewer/main.py:211 +#: /home/kovid/work/calibre/src/calibre/gui2/viewer/main.py:213 msgid "Search for text in book" msgstr "Szukanie tekstu w książce" -#: /home/kovid/work/calibre/src/calibre/gui2/viewer/main.py:284 +#: /home/kovid/work/calibre/src/calibre/gui2/viewer/main.py:292 msgid "Print Preview" msgstr "Podgląd wydruku" -#: /home/kovid/work/calibre/src/calibre/gui2/viewer/main.py:339 +#: /home/kovid/work/calibre/src/calibre/gui2/viewer/main.py:303 +msgid "Clear list of recently opened books" +msgstr "Wyczyść listę ostatnio otwieranych książek" + +#: /home/kovid/work/calibre/src/calibre/gui2/viewer/main.py:361 msgid "Connecting to dict.org to lookup: %s…" msgstr "Łączenie z dict.org by sprawdzić: %s…" -#: /home/kovid/work/calibre/src/calibre/gui2/viewer/main.py:445 +#: /home/kovid/work/calibre/src/calibre/gui2/viewer/main.py:467 msgid "Choose ebook" -msgstr "Wybierz e-book" +msgstr "Wybierz książkę" -#: /home/kovid/work/calibre/src/calibre/gui2/viewer/main.py:446 +#: /home/kovid/work/calibre/src/calibre/gui2/viewer/main.py:468 msgid "Ebooks" -msgstr "E-booki" +msgstr "Książki" -#: /home/kovid/work/calibre/src/calibre/gui2/viewer/main.py:482 +#: /home/kovid/work/calibre/src/calibre/gui2/viewer/main.py:504 msgid "No matches found for: %s" msgstr "Nie znalezniono wyników dla: %s" -#: /home/kovid/work/calibre/src/calibre/gui2/viewer/main.py:525 +#: /home/kovid/work/calibre/src/calibre/gui2/viewer/main.py:541 msgid "Loading flow..." -msgstr "" +msgstr "Ładuję strumień..." -#: /home/kovid/work/calibre/src/calibre/gui2/viewer/main.py:563 +#: /home/kovid/work/calibre/src/calibre/gui2/viewer/main.py:579 msgid "Laying out %s" -msgstr "" +msgstr "Zestawiam %s" -#: /home/kovid/work/calibre/src/calibre/gui2/viewer/main.py:594 +#: /home/kovid/work/calibre/src/calibre/gui2/viewer/main.py:610 msgid "Bookmark #%d" msgstr "Zakładka #%d" -#: /home/kovid/work/calibre/src/calibre/gui2/viewer/main.py:598 +#: /home/kovid/work/calibre/src/calibre/gui2/viewer/main.py:614 msgid "Add bookmark" msgstr "Dodaj zakładkę" -#: /home/kovid/work/calibre/src/calibre/gui2/viewer/main.py:599 +#: /home/kovid/work/calibre/src/calibre/gui2/viewer/main.py:615 msgid "Enter title for bookmark:" -msgstr "Dodaj tytuł zakładki" +msgstr "Dodaj tytuł zakładki:" -#: /home/kovid/work/calibre/src/calibre/gui2/viewer/main.py:609 +#: /home/kovid/work/calibre/src/calibre/gui2/viewer/main.py:625 msgid "Manage Bookmarks" msgstr "Zarządzaj zakładkami" -#: /home/kovid/work/calibre/src/calibre/gui2/viewer/main.py:649 +#: /home/kovid/work/calibre/src/calibre/gui2/viewer/main.py:665 msgid "Loading ebook..." msgstr "Ładowanie książki..." -#: /home/kovid/work/calibre/src/calibre/gui2/viewer/main.py:661 +#: /home/kovid/work/calibre/src/calibre/gui2/viewer/main.py:677 msgid "Could not open ebook" msgstr "Nie można otworzyć książki" -#: /home/kovid/work/calibre/src/calibre/gui2/viewer/main.py:754 +#: /home/kovid/work/calibre/src/calibre/gui2/viewer/main.py:770 msgid "Options to control the ebook viewer" -msgstr "Opcje odpowiadające za kontolę nad przeglądarką książek" +msgstr "Opcje odpowiadające za kontrolę nad przeglądarką książek" -#: /home/kovid/work/calibre/src/calibre/gui2/viewer/main.py:761 +#: /home/kovid/work/calibre/src/calibre/gui2/viewer/main.py:777 msgid "" "If specified, viewer window will try to come to the front when started." msgstr "" +"Jeśli określone, okno przeglądarki spróbuje pokazać się na wierzchu podczas " +"startu." -#: /home/kovid/work/calibre/src/calibre/gui2/viewer/main.py:764 +#: /home/kovid/work/calibre/src/calibre/gui2/viewer/main.py:780 msgid "" "If specified, viewer window will try to open full screen when started." msgstr "" +"Jeśli określone, okno przeglądarki spróbuje otworzyć się na pełnym ekranie " +"podczas startu." -#: /home/kovid/work/calibre/src/calibre/gui2/viewer/main.py:769 +#: /home/kovid/work/calibre/src/calibre/gui2/viewer/main.py:785 msgid "Print javascript alert and console messages to the console" -msgstr "Wyświetlaj uwagi javascript'u i widomości konsolowe w konsoli" +msgstr "Wyświetlaj uwagi javascriptu i wiadomości konsolowe w konsoli" -#: /home/kovid/work/calibre/src/calibre/gui2/viewer/main.py:775 +#: /home/kovid/work/calibre/src/calibre/gui2/viewer/main.py:791 msgid "" "%prog [options] file\n" "\n" "View an ebook.\n" msgstr "" +"%prog [options] file\n" +"\n" +"Zobacz książkę.\n" #: /home/kovid/work/calibre/src/calibre/gui2/viewer/main_ui.py:189 msgid "E-book Viewer" -msgstr "Przeglądarka e-booków" +msgstr "Przeglądarka książek" #: /home/kovid/work/calibre/src/calibre/gui2/viewer/main_ui.py:190 msgid "Close dictionary" @@ -12820,10 +14941,6 @@ msgstr "Znajdź następną" msgid "Find next occurrence" msgstr "Znajdź następne wystąpienie" -#: /home/kovid/work/calibre/src/calibre/gui2/viewer/main_ui.py:205 -msgid "Copy to clipboard" -msgstr "Kopiuj do schowka" - #: /home/kovid/work/calibre/src/calibre/gui2/viewer/main_ui.py:207 msgid "Reference Mode" msgstr "Tryb odwołań" @@ -12850,47 +14967,30 @@ msgstr "Znajdź poprzednie wystąpienie" #: /home/kovid/work/calibre/src/calibre/gui2/viewer/printing.py:114 msgid "Print eBook" -msgstr "Wydrukuj e-książkę" +msgstr "Wydrukuj książkę" -#: /home/kovid/work/calibre/src/calibre/gui2/widgets.py:279 -msgid "Copy Image" -msgstr "Kopiuj grafikę" - -#: /home/kovid/work/calibre/src/calibre/gui2/widgets.py:280 -msgid "Paste Image" -msgstr "Wklej grafikę" - -#: /home/kovid/work/calibre/src/calibre/gui2/widgets.py:382 -msgid "Change Case" -msgstr "Zmień wielkość liter" - -#: /home/kovid/work/calibre/src/calibre/gui2/widgets.py:385 -msgid "Swap Case" -msgstr "Zamień wielkość liter" - -#: /home/kovid/work/calibre/src/calibre/gui2/widgets.py:925 +#: /home/kovid/work/calibre/src/calibre/gui2/widgets.py:962 msgid "Drag to resize" -msgstr "Przeciągnij aby zmienić wielkość" +msgstr "Przeciągnij, aby zmienić wielkość" -#: /home/kovid/work/calibre/src/calibre/gui2/widgets.py:960 +#: /home/kovid/work/calibre/src/calibre/gui2/widgets.py:997 msgid "Show" msgstr "Wyświetl" -#: /home/kovid/work/calibre/src/calibre/gui2/widgets.py:967 +#: /home/kovid/work/calibre/src/calibre/gui2/widgets.py:1004 msgid "Hide" msgstr "Ukryj" -#: /home/kovid/work/calibre/src/calibre/gui2/widgets.py:1004 +#: /home/kovid/work/calibre/src/calibre/gui2/widgets.py:1041 msgid "Toggle" msgstr "Przełącz" #: /home/kovid/work/calibre/src/calibre/gui2/wizard/__init__.py:438 msgid "" -"Choose you e-book device. If your device is not in the list, choose a \"%s\" " -"device." +"Choose your e-book device. If your device is not in the list, choose a " +"\"%s\" device." msgstr "" -"Wybierz swój czytnik książek, jeśli Twojego urządzenia nie ma na liście, " -"wybierz \"%s\" ." +"Wybierz swój czytnik. Jeśli nie ma go na liście, wybierz urządzenie \"%s\"." #: /home/kovid/work/calibre/src/calibre/gui2/wizard/__init__.py:499 msgid "Moving library..." @@ -12910,6 +15010,8 @@ msgid "" "

      An invalid library already exists at %s, delete it before trying to move " "the existing library.
      Error: %s" msgstr "" +"

      Nieprawidłowa biblioteka istnieje już w %s, skasuj ją przed " +"przeniesieniem istniejącej biblioteki.
      Błąd: %s" #: /home/kovid/work/calibre/src/calibre/gui2/wizard/__init__.py:582 msgid "Could not move library" @@ -12960,14 +15062,16 @@ msgid "" "

      Congratulations!

      You have successfully setup calibre. Press the %s " "button to apply your settings." msgstr "" -"

      Gratulacje!

      Udało Ci się skonfigurować calibre. Naciśnij %s aby " -"zastosować Twoje ustawienia." +"

      Gratulacje!

      Udało ci się skonfigurować calibre. Naciśnij %s aby " +"zastosować twoje ustawienia." #: /home/kovid/work/calibre/src/calibre/gui2/wizard/finish_ui.py:50 msgid "" "

      Demo videos

      Videos demonstrating the various features of calibre are " "available online." msgstr "" +"

      Filmy demonstracyjne

      Filmy pokazujące różne funkcje programu calibre " +"są dostępne w sieci." #: /home/kovid/work/calibre/src/calibre/gui2/wizard/finish_ui.py:51 msgid "" @@ -12975,7 +15079,7 @@ msgid "" "ebook.com/user_manual\">online." msgstr "" "

      Podręcznik użytkownika

      Podręcznik użytkownika dostępny jest również " -"w Internecie." +"w sieci." #: /home/kovid/work/calibre/src/calibre/gui2/wizard/kindle_ui.py:49 msgid "" @@ -12985,6 +15089,11 @@ msgid "" "button below. You will also have to register your gmail address in your " "Amazon account." msgstr "" +"

      calibre może automatycznie wysyłac książki emailem na twojego Kindle'a. " +"Aby to zrobić musisz wpisać dane konta email poniżej. Najłatwiej będzie " +"założyć darmowe konto gmail i kliknięcie w " +"klawisz Użyj gmaila poniżej. Musisz również zarejestrować ten adres emailowy " +"na koncie Amazon." #: /home/kovid/work/calibre/src/calibre/gui2/wizard/kindle_ui.py:50 msgid "&Kindle email:" @@ -12999,9 +15108,9 @@ msgid "" "

      Choose a location for your books. When you add books to calibre, they " "will be copied here. Use an empty folder for a new calibre library:" msgstr "" -"

      Wybierz lokalizację dla książek. Kiedy dodasz książkę do calibre, będzie " -"ona skopiowana tutaj. Użyj pustego folderu fdla nowej biblioteki " -"calibre:" +"

      Wybierz lokalizację dla książek. Kiedy dodasz książkę do calibre, " +"zostanie ona tutaj skopiowana. Użyj pustego folderu dla nowej " +"biblioteki calibre:" #: /home/kovid/work/calibre/src/calibre/gui2/wizard/library_ui.py:59 msgid "&Change" @@ -13029,72 +15138,90 @@ msgstr "Wysyłanie..." msgid "Mail successfully sent" msgstr "Email wysłany pomyślnie" -#: /home/kovid/work/calibre/src/calibre/gui2/wizard/send_email.py:95 +#: /home/kovid/work/calibre/src/calibre/gui2/wizard/send_email.py:96 msgid "OK to proceed?" msgstr "Kontynuować?" -#: /home/kovid/work/calibre/src/calibre/gui2/wizard/send_email.py:96 +#: /home/kovid/work/calibre/src/calibre/gui2/wizard/send_email.py:97 msgid "" "This will display your email password on the screen. Is it OK to proceed?" -msgstr "To wyświetli Twoje hasło do e-maila na ekranie. Czy kontynuować?" +msgstr "To wyświetli twoje hasło do e-maila na ekranie. Czy kontynuować?" -#: /home/kovid/work/calibre/src/calibre/gui2/wizard/send_email.py:139 +#: /home/kovid/work/calibre/src/calibre/gui2/wizard/send_email.py:140 msgid "" "If you are setting up a new hotmail account, you must log in to it once " "before you will be able to send mails." msgstr "" -"Jeśli ustawiasz nowego konto hotmail, musisz najpierw się na nie zalogować, " +"Jeśli ustawiasz nowe konto hotmail, musisz najpierw się na nie zalogować, " "zanim będziesz mógł wysyłać maile." -#: /home/kovid/work/calibre/src/calibre/gui2/wizard/send_email.py:150 +#: /home/kovid/work/calibre/src/calibre/gui2/wizard/send_email.py:151 msgid "Setup sending email using" msgstr "Ustawienie adresu email do wysyłania" -#: /home/kovid/work/calibre/src/calibre/gui2/wizard/send_email.py:152 +#: /home/kovid/work/calibre/src/calibre/gui2/wizard/send_email.py:153 msgid "" "If you don't have an account, you can sign up for a free {name} email " "account at http://{url}. {extra}" msgstr "" +"Jeśli nie masz konta, możesz zarejestrować za darmo {name} konto emailowe http://{url}. {extra}" -#: /home/kovid/work/calibre/src/calibre/gui2/wizard/send_email.py:159 +#: /home/kovid/work/calibre/src/calibre/gui2/wizard/send_email.py:160 msgid "Your %s &email address:" msgstr "Twój %s &adres e-mail:" -#: /home/kovid/work/calibre/src/calibre/gui2/wizard/send_email.py:160 +#: /home/kovid/work/calibre/src/calibre/gui2/wizard/send_email.py:161 msgid "Your %s &username:" msgstr "Twoja %s &nazwa użytkownika:" -#: /home/kovid/work/calibre/src/calibre/gui2/wizard/send_email.py:161 +#: /home/kovid/work/calibre/src/calibre/gui2/wizard/send_email.py:162 msgid "Your %s &password:" msgstr "Twoje %s &hasło:" -#: /home/kovid/work/calibre/src/calibre/gui2/wizard/send_email.py:179 +#: /home/kovid/work/calibre/src/calibre/gui2/wizard/send_email.py:180 msgid "" "If you plan to use email to send books to your Kindle, remember to add the " "your %s email address to the allowed email addresses in your Amazon.com " "Kindle management page." msgstr "" "Jeśli zamierzasz używać tego adresu e-mail do wysyłania książek na Kindle, " -"by dodać Twój %s adres email do dozwolonych adresów email na Twojej stronie " -"zarządzania Amazon.com Kindle." +"pamiętaj by dodać swój %s adres email do dozwolonych adresów email na twojej " +"stronie zarządzania Amazon.com Kindle." -#: /home/kovid/work/calibre/src/calibre/gui2/wizard/send_email.py:186 +#: /home/kovid/work/calibre/src/calibre/gui2/wizard/send_email.py:187 msgid "Setup" msgstr "Ustawienia" -#: /home/kovid/work/calibre/src/calibre/gui2/wizard/send_email.py:201 -#: /home/kovid/work/calibre/src/calibre/gui2/wizard/send_email.py:208 +#: /home/kovid/work/calibre/src/calibre/gui2/wizard/send_email.py:202 +#: /home/kovid/work/calibre/src/calibre/gui2/wizard/send_email.py:213 +#: /home/kovid/work/calibre/src/calibre/gui2/wizard/send_email.py:218 msgid "Bad configuration" msgstr "Zła konfiguracja" -#: /home/kovid/work/calibre/src/calibre/gui2/wizard/send_email.py:202 +#: /home/kovid/work/calibre/src/calibre/gui2/wizard/send_email.py:203 msgid "You must set the From email address" msgstr "Musisz ustawić adres Od" -#: /home/kovid/work/calibre/src/calibre/gui2/wizard/send_email.py:209 -msgid "You must set the username and password for the mail server." +#: /home/kovid/work/calibre/src/calibre/gui2/wizard/send_email.py:214 +msgid "" +"You must either set both the username and password for the mail " +"server or no username and no password at all." msgstr "" -"Musisz ustawić nazwe uzytkownika i hasło dla tego serweru pocztowego." +"Musisz ustawić zarówno użytkownika i hasło dla serwera pocztowego lub " +"brak użytkownika i brak hasła." + +#: /home/kovid/work/calibre/src/calibre/gui2/wizard/send_email.py:219 +msgid "Please enter a username and password or set encryption to None " +msgstr "Proszę wpisać użytkownika i hasło lub ustawić szyfrowanie na żadne " + +#: /home/kovid/work/calibre/src/calibre/gui2/wizard/send_email.py:224 +msgid "" +"No username and password set for mailserver. Most mailservers need a " +"username and password. Are you sure?" +msgstr "" +"Nie ustalono użytkownika i hasła dla serwera pocztowego. Większość serwerów " +"wymaga użytkownika i hasła. Jesteś pewien?" #: /home/kovid/work/calibre/src/calibre/gui2/wizard/send_email_ui.py:124 msgid "Send email &from:" @@ -13105,12 +15232,16 @@ msgid "" "

      This is what will be present in the From: field of emails sent by " "calibre.
      Set it to your email address" msgstr "" +"

      To będzie pokazane w polu Od: w emailach wysłanych przez calibre.
      " +"Wstaw tutaj swój adres emailowy" #: /home/kovid/work/calibre/src/calibre/gui2/wizard/send_email_ui.py:126 msgid "" "

      A mail server is useful if the service you are sending mail to only " "accepts email from well know mail services." msgstr "" +"

      Serwer pocztowy jest użyteczny gdy usługa, do której wysyłasz emaila " +"akceptuje emaile ze znanych usług emailowych." #: /home/kovid/work/calibre/src/calibre/gui2/wizard/send_email_ui.py:127 msgid "Mail &Server" @@ -13128,7 +15259,7 @@ msgstr "&Nazwa komputera:" #: /home/kovid/work/calibre/src/calibre/gui2/wizard/send_email_ui.py:130 msgid "The hostname of your mail server. For e.g. smtp.gmail.com" -msgstr "Nazwa hosta Twojego serwera poczty, np. smtp.gmail.com" +msgstr "Nazwa hosta twojego serwera poczty, np. smtp.gmail.com" #: /home/kovid/work/calibre/src/calibre/gui2/wizard/send_email_ui.py:131 msgid "&Port:" @@ -13138,7 +15269,7 @@ msgstr "&Port:" msgid "" "The port your mail server listens for connections on. The default is 25" msgstr "" -"Port, na którym Twój serwer poczty nasłuchuje połączeń. Domyślnie to 25" +"Port, na którym twój serwer poczty nasłuchuje połączeń. Domyślnie to 25" #: /home/kovid/work/calibre/src/calibre/gui2/wizard/send_email_ui.py:134 msgid "Your username on the mail server" @@ -13178,7 +15309,7 @@ msgstr "&SSL" #: /home/kovid/work/calibre/src/calibre/gui2/wizard/send_email_ui.py:143 msgid "WARNING: Using no encryption is highly insecure" -msgstr "Ostrzeżenie: Brak użycia kodowania jest wysoce niebezpieczny" +msgstr "OSTRZEŻENIE: Brak szyfrowania jest wysoce niebezpieczny" #: /home/kovid/work/calibre/src/calibre/gui2/wizard/send_email_ui.py:144 msgid "&None" @@ -13203,59 +15334,77 @@ msgid "" "directly on the device. To do this you have to turn on the calibre content " "server." msgstr "" +"

      Jeśli używasz aplikacji Stanza na twoim iPhone/iTouch, " +"możesz uzyskac dostęp do kolekcji książek calibre bezpośrednio na " +"urządzeniu. Aby to zrobić musisz włączyć serwer zawartości programu calibre." #: /home/kovid/work/calibre/src/calibre/gui2/wizard/stanza_ui.py:50 msgid "Turn on the &content server" msgstr "Włącz serwer &zawartości" -#: /home/kovid/work/calibre/src/calibre/library/caches.py:312 -msgid "today" -msgstr "dzisiaj" - -#: /home/kovid/work/calibre/src/calibre/library/caches.py:315 -msgid "yesterday" -msgstr "wczoraj" - -#: /home/kovid/work/calibre/src/calibre/library/caches.py:318 -msgid "thismonth" -msgstr "w tym miesiącu" - -#: /home/kovid/work/calibre/src/calibre/library/caches.py:321 -#: /home/kovid/work/calibre/src/calibre/library/caches.py:322 -msgid "daysago" -msgstr "dni temu" - -#: /home/kovid/work/calibre/src/calibre/library/caches.py:560 -#: /home/kovid/work/calibre/src/calibre/library/caches.py:570 -msgid "unchecked" -msgstr "odznaczony" - -#: /home/kovid/work/calibre/src/calibre/library/caches.py:560 -#: /home/kovid/work/calibre/src/calibre/library/caches.py:570 -#: /home/kovid/work/calibre/src/calibre/library/save_to_disk.py:192 -msgid "no" -msgstr "nie" - -#: /home/kovid/work/calibre/src/calibre/library/caches.py:563 -#: /home/kovid/work/calibre/src/calibre/library/caches.py:573 +#: /home/kovid/work/calibre/src/calibre/library/caches.py:161 +#: /home/kovid/work/calibre/src/calibre/library/caches.py:562 +#: /home/kovid/work/calibre/src/calibre/library/caches.py:576 +#: /home/kovid/work/calibre/src/calibre/library/caches.py:586 msgid "checked" msgstr "zaznaczony" -#: /home/kovid/work/calibre/src/calibre/library/caches.py:563 -#: /home/kovid/work/calibre/src/calibre/library/caches.py:573 -#: /home/kovid/work/calibre/src/calibre/library/save_to_disk.py:192 +#: /home/kovid/work/calibre/src/calibre/library/caches.py:161 +#: /home/kovid/work/calibre/src/calibre/library/caches.py:562 +#: /home/kovid/work/calibre/src/calibre/library/caches.py:576 +#: /home/kovid/work/calibre/src/calibre/library/caches.py:586 +#: /home/kovid/work/calibre/src/calibre/library/save_to_disk.py:214 msgid "yes" msgstr "tak" -#: /home/kovid/work/calibre/src/calibre/library/caches.py:567 +#: /home/kovid/work/calibre/src/calibre/library/caches.py:163 +#: /home/kovid/work/calibre/src/calibre/library/caches.py:561 +#: /home/kovid/work/calibre/src/calibre/library/caches.py:573 +#: /home/kovid/work/calibre/src/calibre/library/caches.py:583 +msgid "unchecked" +msgstr "odznaczony" + +#: /home/kovid/work/calibre/src/calibre/library/caches.py:163 +#: /home/kovid/work/calibre/src/calibre/library/caches.py:561 +#: /home/kovid/work/calibre/src/calibre/library/caches.py:573 +#: /home/kovid/work/calibre/src/calibre/library/caches.py:583 +#: /home/kovid/work/calibre/src/calibre/library/save_to_disk.py:214 +msgid "no" +msgstr "nie" + +#: /home/kovid/work/calibre/src/calibre/library/caches.py:356 +msgid "today" +msgstr "dzisiaj" + +#: /home/kovid/work/calibre/src/calibre/library/caches.py:359 +msgid "yesterday" +msgstr "wczoraj" + +#: /home/kovid/work/calibre/src/calibre/library/caches.py:362 +msgid "thismonth" +msgstr "w tym miesiącu" + +#: /home/kovid/work/calibre/src/calibre/library/caches.py:365 +#: /home/kovid/work/calibre/src/calibre/library/caches.py:366 +msgid "daysago" +msgstr "dni temu" + +#: /home/kovid/work/calibre/src/calibre/library/caches.py:563 +#: /home/kovid/work/calibre/src/calibre/library/caches.py:580 msgid "blank" msgstr "pusty" -#: /home/kovid/work/calibre/src/calibre/library/caches.py:567 +#: /home/kovid/work/calibre/src/calibre/library/caches.py:563 +#: /home/kovid/work/calibre/src/calibre/library/caches.py:580 msgid "empty" msgstr "pusty" -#: /home/kovid/work/calibre/src/calibre/library/catalog.py:52 +#: /home/kovid/work/calibre/src/calibre/library/caches.py:564 +msgid "Invalid boolean query \"{0}\"" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/library/catalog.py:55 msgid "" "The fields to output when cataloging books in the database. Should be a " "comma-separated list of fields.\n" @@ -13265,16 +15414,23 @@ msgid "" "Default: '%%default'\n" "Applies to: CSV, XML output formats" msgstr "" +"Pola wyjściowe przy katalogowaniu książek w bazie danych. Pola powinny być " +"oddzielone przecinkami.\n" +"Dostępne pola: %s,\n" +"plus stworzone przez użytkownika pola dodatkowe.\n" +"Przykład: %s=title,authors,tags\n" +"Domyślnie: '%%default'\n" +"Odnosi się do: formatów wyjściowych CSV, XML" -#: /home/kovid/work/calibre/src/calibre/library/catalog.py:65 +#: /home/kovid/work/calibre/src/calibre/library/catalog.py:68 msgid "" "Output field to sort on.\n" -"Available fields: author_sort, id, rating, size, timestamp, title.\n" +"Available fields: author_sort, id, rating, size, timestamp, title_sort\n" "Default: '%default'\n" "Applies to: CSV, XML output formats" msgstr "" -#: /home/kovid/work/calibre/src/calibre/library/catalog.py:232 +#: /home/kovid/work/calibre/src/calibre/library/catalog.py:242 msgid "" "The fields to output when cataloging books in the database. Should be a " "comma-separated list of fields.\n" @@ -13284,32 +15440,51 @@ msgid "" "Default: '%%default'\n" "Applies to: BIBTEX output format" msgstr "" +"Pola wyjściowe przy katalogowaniu książek w bazie danych. Pola powinny być " +"oddzielone przecinkami.\n" +"Dostępne pola: %s,\n" +"plus stworzone przez użytkownika pola dodatkowe.\n" +"Przykład: %s=title,authors,tags\n" +"Domyślnie: '%%default'\n" +"Odnosi się do: formatu wyjściowego BIBTEX" -#: /home/kovid/work/calibre/src/calibre/library/catalog.py:245 +#: /home/kovid/work/calibre/src/calibre/library/catalog.py:255 msgid "" "Output field to sort on.\n" "Available fields: author_sort, id, rating, size, timestamp, title.\n" "Default: '%default'\n" "Applies to: BIBTEX output format" msgstr "" +"Pole wyściowe do sortowania.\n" +"Dostępne pola: author_sort, id, rating, size, timestamp, title.\n" +"Domyślnie: '%default'\n" +"Odnosi się do: formatu wyjściowego BIBTEX" -#: /home/kovid/work/calibre/src/calibre/library/catalog.py:254 +#: /home/kovid/work/calibre/src/calibre/library/catalog.py:264 msgid "" "Create a citation for BibTeX entries.\n" "Boolean value: True, False\n" "Default: '%default'\n" "Applies to: BIBTEX output format" msgstr "" +"Stwórz cytaty dla wpisów BibTeX\n" +"Wartość zerojedynkowa: Prawda, Fałsz\n" +"Domyślnie: '%default'\n" +"Odnosi się do: formatu wyjściowego BIBTEX" -#: /home/kovid/work/calibre/src/calibre/library/catalog.py:263 +#: /home/kovid/work/calibre/src/calibre/library/catalog.py:273 msgid "" "Create a file entry if formats is selected for BibTeX entries.\n" "Boolean value: True, False\n" "Default: '%default'\n" "Applies to: BIBTEX output format" msgstr "" +"Stwórz wpis pliku jeśli formaty są wybranymi dla wpisów BibTeX.\n" +"Wartość zerojedynkowa: Prawda, Fałsz\n" +"Domyślnie: '%default'\n" +"Odnosi się do: formatu wyjściowego BIBTEX" -#: /home/kovid/work/calibre/src/calibre/library/catalog.py:272 +#: /home/kovid/work/calibre/src/calibre/library/catalog.py:282 msgid "" "The template for citation creation from database fields.\n" "Should be a template with {} enclosed fields.\n" @@ -13317,39 +15492,59 @@ msgid "" "Default: '%%default'\n" "Applies to: BIBTEX output format" msgstr "" +"Szablon dla tworzenia cytatów z pól bazy danych.\n" +"Powinien być szablon z {} zamkniętymi polami.\n" +"Dostępne pola: %s.\n" +"Domyślnie: '%%default'\n" +"Odnosi się do: formatu wyjściowego BIBTEX" -#: /home/kovid/work/calibre/src/calibre/library/catalog.py:282 +#: /home/kovid/work/calibre/src/calibre/library/catalog.py:292 msgid "" "BibTeX file encoding output.\n" "Available types: utf8, cp1252, ascii.\n" "Default: '%default'\n" "Applies to: BIBTEX output format" msgstr "" +"Kodowanie wyjściowe pliku BibTeX.\n" +"Dostępne typy: utf8, cp1252, ascii.\n" +"Domyślnie: '%default'\n" +"Odnosi się do: formatu wyjściowego BIBTEX" -#: /home/kovid/work/calibre/src/calibre/library/catalog.py:291 +#: /home/kovid/work/calibre/src/calibre/library/catalog.py:301 msgid "" "BibTeX file encoding flag.\n" "Available types: strict, replace, ignore, backslashreplace.\n" "Default: '%default'\n" "Applies to: BIBTEX output format" msgstr "" +"Flag kodowania pliku BibTeX.\n" +"Dostępne typy: strict, replace, ignore, backslashreplace.\n" +"Domyślnie: '%default'\n" +"Odnosi się do: formatu wyjściowego BIBTEX" -#: /home/kovid/work/calibre/src/calibre/library/catalog.py:300 +#: /home/kovid/work/calibre/src/calibre/library/catalog.py:310 msgid "" "Entry type for BibTeX catalog.\n" "Available types: book, misc, mixed.\n" "Default: '%default'\n" "Applies to: BIBTEX output format" msgstr "" +"Wpis wejściowy dla katalogu BibTeX.\n" +"Dostępne typy: book, misc, mixed.\n" +"Domyślnie: '%default'\n" +"Odnosi się do: formatu wyjściowego BIBTEX" -#: /home/kovid/work/calibre/src/calibre/library/catalog.py:607 +#: /home/kovid/work/calibre/src/calibre/library/catalog.py:616 msgid "" "Title of generated catalog used as title in metadata.\n" "Default: '%default'\n" "Applies to: ePub, MOBI output formats" msgstr "" +"Tytuł wygenerowanego katalogu użytego jako tytuł w metadanych.\n" +"Domyślnie: '%default'\n" +"Odnosi się do: formaty wyjściowe ePub, MOBI" -#: /home/kovid/work/calibre/src/calibre/library/catalog.py:614 +#: /home/kovid/work/calibre/src/calibre/library/catalog.py:623 msgid "" "Save the output from different stages of the conversion pipeline to the " "specified directory. Useful if you are unsure at which stage of the " @@ -13357,80 +15552,118 @@ msgid "" "Default: '%default'\n" "Applies to: ePub, MOBI output formats" msgstr "" +"Zapisz wynik z różnych etapów konwersji do określonego katalogu. Użyteczne " +"jeśli nie jesteś pewien który z etapów konwersji powoduje występowanie " +"błędu.\n" +"Domyślnie: '%default'\n" +"Odnosi się do: formaty wyjściowe ePub, MOBI" -#: /home/kovid/work/calibre/src/calibre/library/catalog.py:624 +#: /home/kovid/work/calibre/src/calibre/library/catalog.py:633 msgid "" "field:pattern specifying custom field/contents indicating book should be " "excluded.\n" "Default: '%default'\n" "Applies to ePub, MOBI output formats" msgstr "" +"pole:wzorzec określający pole użytkownika/zawartości wskazujące książkę " +"która powinna być pominięta.\n" +"Domyślnie: '%default'\n" +"Odnosi się do: formaty wyjściowe ePub, MOBI" -#: /home/kovid/work/calibre/src/calibre/library/catalog.py:631 +#: /home/kovid/work/calibre/src/calibre/library/catalog.py:640 msgid "" "Regex describing tags to exclude as genres.\n" "Default: '%default' excludes bracketed tags, e.g. '[]'\n" "Applies to: ePub, MOBI output formats" msgstr "" +"Etykiety opisujące regex do wykluczenia jako gatunki.\n" +"Domyślnie: '%default' wyklucza etykiety w nawiasach, np. '[]'\n" +"Odnosi się do: formaty wyjściowe ePub, MOBI" -#: /home/kovid/work/calibre/src/calibre/library/catalog.py:637 +#: /home/kovid/work/calibre/src/calibre/library/catalog.py:646 msgid "" "Comma-separated list of tag words indicating book should be excluded from " "output.For example: 'skip' will match 'skip this book' and 'Skip will like " "this'.Default: '%default'\n" "Applies to: ePub, MOBI output formats" msgstr "" +"Oddzielona przecinkami lista etykiet wskazująca książki, które mają być " +"wykluczone przy wyjściu. Na przykład: 'pomiń' będzie pasować do 'pomiń tę " +"książkę' and 'Pomińmy to milczeniem'.\n" +"Domyślnie: '%default'\n" +"Odnosi się do: formaty wyjściowe ePub, MOBI" -#: /home/kovid/work/calibre/src/calibre/library/catalog.py:645 +#: /home/kovid/work/calibre/src/calibre/library/catalog.py:654 msgid "" "Include 'Authors' section in catalog.\n" "Default: '%default'\n" "Applies to: ePub, MOBI output formats" msgstr "" +"Dołącz sekcję 'Autorzy' do katalogu.\n" +"Domyślnie: '%default'\n" +"Odnosi się do: formaty wyjściowe ePub, MOBI" -#: /home/kovid/work/calibre/src/calibre/library/catalog.py:652 +#: /home/kovid/work/calibre/src/calibre/library/catalog.py:661 msgid "" "Include 'Descriptions' section in catalog.\n" "Default: '%default'\n" "Applies to: ePub, MOBI output formats" msgstr "" +"Dołącz sekcję 'Opisy' do katalogu.\n" +"Domyślnie: '%default'\n" +"Odnosi się do: formaty wyjściowe ePub, MOBI" -#: /home/kovid/work/calibre/src/calibre/library/catalog.py:659 +#: /home/kovid/work/calibre/src/calibre/library/catalog.py:668 msgid "" "Include 'Genres' section in catalog.\n" "Default: '%default'\n" "Applies to: ePub, MOBI output formats" msgstr "" +"Dołącz sekcję 'Gatunki' do katalogu.\n" +"Domyślnie: '%default'\n" +"Odnosi się do: formaty wyjściowe ePub, MOBI" -#: /home/kovid/work/calibre/src/calibre/library/catalog.py:666 +#: /home/kovid/work/calibre/src/calibre/library/catalog.py:675 msgid "" "Include 'Titles' section in catalog.\n" "Default: '%default'\n" "Applies to: ePub, MOBI output formats" msgstr "" +"Dołącz sekcję 'Tytuły' do katalogu.\n" +"Domyślnie: '%default'\n" +"Odnosi się do: formaty wyjściowe ePub, MOBI" -#: /home/kovid/work/calibre/src/calibre/library/catalog.py:673 +#: /home/kovid/work/calibre/src/calibre/library/catalog.py:682 msgid "" "Include 'Series' section in catalog.\n" "Default: '%default'\n" "Applies to: ePub, MOBI output formats" msgstr "" +"Dołącz sekcję 'Cykle' do katalogu.\n" +"Domyślnie: '%default'\n" +"Odnosi się do: formaty wyjściowe ePub, MOBI" -#: /home/kovid/work/calibre/src/calibre/library/catalog.py:680 +#: /home/kovid/work/calibre/src/calibre/library/catalog.py:689 msgid "" "Include 'Recently Added' section in catalog.\n" "Default: '%default'\n" "Applies to: ePub, MOBI output formats" msgstr "" +"Dołącz sekcję 'Ostatnio dodane' do katalogu.\n" +"Domyślnie: '%default'\n" +"Odnosi się do: formaty wyjściowe ePub, MOBI" -#: /home/kovid/work/calibre/src/calibre/library/catalog.py:687 +#: /home/kovid/work/calibre/src/calibre/library/catalog.py:696 msgid "" "Custom field containing note text to insert in Description header.\n" "Default: '%default'\n" "Applies to: ePub, MOBI output formats" msgstr "" +"Pole użytkownika zawierające tekst do wprowadzenia w Nagłówku opisu.\n" +"Domyślnie: '%default'\n" +"Odnosi się do: formaty wyjściowe ePub, MOBI" -#: /home/kovid/work/calibre/src/calibre/library/catalog.py:694 +#: /home/kovid/work/calibre/src/calibre/library/catalog.py:703 msgid "" ":[before|after]:[True|False] specifying:\n" " Custom field containing notes to merge with Comments\n" @@ -13439,8 +15672,16 @@ msgid "" "Default: '%default'\n" "Applies to ePub, MOBI output formats" msgstr "" +":[before|after]:[True|False] określające:\n" +" Pole użytkownika zawierające informacje do złączenia z " +"Komentarzami\n" +" [before|after] Umiejscowienie informacji z odniesieniem do Komentarzy\n" +" [True|False] - Horyzontalna reguła jest wprowadzona pomiędzy tekstem i " +"Komentarzami\n" +"Domyślnie: '%default'\n" +"Odnosi się do: formaty wyjściowe ePub, MOBI" -#: /home/kovid/work/calibre/src/calibre/library/catalog.py:704 +#: /home/kovid/work/calibre/src/calibre/library/catalog.py:713 msgid "" "Specifies the output profile. In some cases, an output profile is required " "to optimize the catalog for the device. For example, 'kindle' or " @@ -13449,38 +15690,55 @@ msgid "" "Default: '%default'\n" "Applies to: ePub, MOBI output formats" msgstr "" +"Okresla profil wyjściowy. W niektórych przypadkach wymagany jest profil " +"wyjściwy w celu zoptymalizowania katalogu dla urządzenia. Na przykład, " +"'kindle' lub 'kindle_dx' tworzy zestrukturyzowany spis zawartości wraz z " +"sekcjami i paragrafami.\n" +"Domyślnie: '%default'\n" +"Odnosi się do: formaty wyjściowe ePub, MOBI" -#: /home/kovid/work/calibre/src/calibre/library/catalog.py:711 +#: /home/kovid/work/calibre/src/calibre/library/catalog.py:720 msgid "" "field:pattern indicating book has been read.\n" "Default: '%default'\n" "Applies to ePub, MOBI output formats" msgstr "" +"pole:wzorzec wskazujący, że książka była czytana.\n" +"Domyślnie: '%default'\n" +"Odnosi się do: formaty wyjściowe ePub, MOBI" -#: /home/kovid/work/calibre/src/calibre/library/catalog.py:717 +#: /home/kovid/work/calibre/src/calibre/library/catalog.py:726 msgid "" "Size hint (in inches) for book covers in catalog.\n" "Range: 1.0 - 2.0\n" "Default: '%default'\n" "Applies to ePub, MOBI output formats" msgstr "" +"Wskazówka rozmiaru (w calach) dla okładek książki w katalogu.\n" +"Zakres: 1.0 - 2.0\n" +"Domyślnie: '%default'\n" +"Odnosi się do: formaty wyjściowe ePub, MOBI" -#: /home/kovid/work/calibre/src/calibre/library/catalog.py:725 +#: /home/kovid/work/calibre/src/calibre/library/catalog.py:734 msgid "" "Tag indicating book to be displayed as wishlist item.\n" "Default: '%default'\n" "Applies to: ePub, MOBI output formats" msgstr "" +"Etykieta wskazująca czy ksiązka ma być wyświetlana jako pozycja listy " +"życzeń.\n" +"Domyślnie: '%default'\n" +"Odnosi się do: formaty wyjściowe ePub, MOBI" -#: /home/kovid/work/calibre/src/calibre/library/catalog.py:1408 +#: /home/kovid/work/calibre/src/calibre/library/catalog.py:1418 msgid "No enabled genres found to catalog.\n" -msgstr "" +msgstr "Nie znaleziono żadnych gatunków dla tego katalogu.\n" -#: /home/kovid/work/calibre/src/calibre/library/catalog.py:1412 +#: /home/kovid/work/calibre/src/calibre/library/catalog.py:1422 msgid "No books available to catalog" msgstr "Brak książek do skatalogowania" -#: /home/kovid/work/calibre/src/calibre/library/catalog.py:1487 +#: /home/kovid/work/calibre/src/calibre/library/catalog.py:1497 msgid "" "Inconsistent Author Sort values for\n" "Author '{0}':\n" @@ -13490,29 +15748,45 @@ msgid "" "Select all books by '{0}', apply correct Author Sort value in Edit Metadata " "dialog, then rebuild the catalog.\n" msgstr "" +"Sprzeczne wartości przy Sortowaniu Autora dla\n" +"Author '{0}':\n" +"'{1}' <> '{2}'\n" +"Nie można stworzyć katalogu MOBI.\n" +"\n" +"Wybierz wszystkie książki używając '{0}', zastosuj poprawną wartość " +"Sortowania Autora w oknie Edytuj Metadane, a następnie przebuduj ten " +"katalog.\n" -#: /home/kovid/work/calibre/src/calibre/library/catalog.py:1504 +#: /home/kovid/work/calibre/src/calibre/library/catalog.py:1514 msgid "" "Warning: inconsistent Author Sort values for\n" "Author '{0}':\n" "'{1}' <> '{2}'\n" msgstr "" +"Ostrzeżenie: sprzeczne wartości przy Sortowaniu Autora dla\n" +"Author '{0}':\n" +"'{1}' <> '{2}'\n" -#: /home/kovid/work/calibre/src/calibre/library/catalog.py:1700 +#: /home/kovid/work/calibre/src/calibre/library/catalog.py:1710 msgid "" "No books found to catalog.\n" "Check 'Excluded books' criteria in E-book options.\n" msgstr "" +"Nie znaleziono książek w katalogu.\n" +"Sprawdz kryteria 'Wykluczone książki' w opcjach książek.\n" -#: /home/kovid/work/calibre/src/calibre/library/catalog.py:1702 +#: /home/kovid/work/calibre/src/calibre/library/catalog.py:1712 msgid "No books available to include in catalog" msgstr "Brak dostępnych książek by dołączyć do katalogu" -#: /home/kovid/work/calibre/src/calibre/library/catalog.py:5030 +#: /home/kovid/work/calibre/src/calibre/library/catalog.py:5042 msgid "" "\n" "*** Adding 'By Authors' Section required for MOBI output ***" msgstr "" +"\n" +"*** Dodawanie Sekcji 'Według Autora' wymaganej przez profil wyjściowy MOBI " +"***" #: /home/kovid/work/calibre/src/calibre/library/check_library.py:26 msgid "Invalid titles" @@ -13544,15 +15818,15 @@ msgstr "Nieznane pliki w książkach" #: /home/kovid/work/calibre/src/calibre/library/check_library.py:33 msgid "Missing covers files" -msgstr "" +msgstr "Brak plików z okładkami" #: /home/kovid/work/calibre/src/calibre/library/check_library.py:34 msgid "Cover files not in database" -msgstr "" +msgstr "Pliki okładek nie figurują w bazie danych" #: /home/kovid/work/calibre/src/calibre/library/check_library.py:35 msgid "Folders raising exception" -msgstr "" +msgstr "Foldery powodujące wyjątek" #: /home/kovid/work/calibre/src/calibre/library/cli.py:43 msgid "" @@ -13562,14 +15836,17 @@ msgstr "" "Ścieżka do biblioteki calibre. Domyślnie używana jest ścieżka zapisana w " "ustawieniach." -#: /home/kovid/work/calibre/src/calibre/library/cli.py:122 +#: /home/kovid/work/calibre/src/calibre/library/cli.py:124 msgid "" "%prog list [options]\n" "\n" "List the books available in the calibre database.\n" msgstr "" +"%prog list [options]\n" +"\n" +"Wyświetl książki dostępne w bazie danych calibre.\n" -#: /home/kovid/work/calibre/src/calibre/library/cli.py:130 +#: /home/kovid/work/calibre/src/calibre/library/cli.py:132 msgid "" "The fields to display when listing books in the database. Should be a comma " "separated list of fields.\n" @@ -13577,27 +15854,39 @@ msgid "" "Default: %%default. The special field \"all\" can be used to select all " "fields. Only has effect in the text output format." msgstr "" +"Pola do wyświetlenia gdy wyświetlane są książki w bazie danych. Lista pól " +"powinna być oddzielona przecinkami.\n" +"Dostępne pola: %s\n" +"Domyślnie: %%default. Specjalne pole \"wszystkie\" (all) może zostać użyte w " +"celu wybrania wszystkich pól. Zastosować można tylko w tekstowym formacie " +"wyjściowym." -#: /home/kovid/work/calibre/src/calibre/library/cli.py:137 +#: /home/kovid/work/calibre/src/calibre/library/cli.py:139 msgid "" "The field by which to sort the results.\n" "Available fields: %s\n" "Default: %%default" msgstr "" +"Pole, po którym sortować wyniki.\n" +"Dostępne pola: %s\n" +"Domyślnie: %%default" -#: /home/kovid/work/calibre/src/calibre/library/cli.py:139 +#: /home/kovid/work/calibre/src/calibre/library/cli.py:141 msgid "Sort results in ascending order" msgstr "Posortuj wyniki rosnąco" -#: /home/kovid/work/calibre/src/calibre/library/cli.py:141 +#: /home/kovid/work/calibre/src/calibre/library/cli.py:143 msgid "" "Filter the results by the search query. For the format of the search query, " "please see the search related documentation in the User Manual. Default is " "to do no filtering." msgstr "" +"Filtruj wyniki po zapytaniu. Dla sformatowania zapytania, zobacz odpowiednią " +"dokumentację w Podręczniku Użytkownika. Domyślnie nie jest włączone żadne " +"filtrowanie." -#: /home/kovid/work/calibre/src/calibre/library/cli.py:143 -#: /home/kovid/work/calibre/src/calibre/library/cli.py:1042 +#: /home/kovid/work/calibre/src/calibre/library/cli.py:145 +#: /home/kovid/work/calibre/src/calibre/library/cli.py:1044 msgid "" "The maximum width of a single line in the output. Defaults to detecting " "screen size." @@ -13605,33 +15894,35 @@ msgstr "" "Maksymalna szerokość pojedynczej linii w pliku wynikowym. Domyślnie " "dostosowana do wykrytego rozmiaru ekranu." -#: /home/kovid/work/calibre/src/calibre/library/cli.py:144 +#: /home/kovid/work/calibre/src/calibre/library/cli.py:146 msgid "The string used to separate fields. Default is a space." -msgstr "Znak używany do oddzielania pól. Domyslny to spacja." +msgstr "Znak używany do oddzielania pól. Domyślny to spacja." -#: /home/kovid/work/calibre/src/calibre/library/cli.py:145 +#: /home/kovid/work/calibre/src/calibre/library/cli.py:147 msgid "" "The prefix for all file paths. Default is the absolute path to the library " "folder." msgstr "" +"Przedrostek dla wszystkich ścieżek do plików. Domyślnie jest to ścieżka " +"absolutna do folderu biblioteki." -#: /home/kovid/work/calibre/src/calibre/library/cli.py:167 +#: /home/kovid/work/calibre/src/calibre/library/cli.py:169 msgid "Invalid fields. Available fields:" msgstr "Niewłaściwe pola. Dostepne pola:" -#: /home/kovid/work/calibre/src/calibre/library/cli.py:174 +#: /home/kovid/work/calibre/src/calibre/library/cli.py:176 msgid "Invalid sort field. Available fields:" -msgstr "Niewłaściwe pole sortowania. Dostepnę pola:" +msgstr "Niewłaściwe pole sortowania. Dostępne pola:" -#: /home/kovid/work/calibre/src/calibre/library/cli.py:246 +#: /home/kovid/work/calibre/src/calibre/library/cli.py:248 msgid "" "The following books were not added as they already exist in the database " "(see --duplicates option):" msgstr "" -"Następujące książki nie zostały dodane ponieważ istnieją ją one w bazie " +"Następujące książki nie zostały dodane ponieważ istnieją już one w bazie " "danych (zobacz opcję --duplicates):" -#: /home/kovid/work/calibre/src/calibre/library/cli.py:270 +#: /home/kovid/work/calibre/src/calibre/library/cli.py:272 msgid "" "%prog add [options] file1 file2 file3 ...\n" "\n" @@ -13639,46 +15930,52 @@ msgid "" "directories, see\n" "the directory related options below.\n" msgstr "" +"%prog add [options] file1 file2 file3 ...\n" +"\n" +"Dodaje określone pliki jako książki do bazy danych. Możesz również określić " +"katalogi, zobacz opcje odnośnie katalogów poniżej.\n" -#: /home/kovid/work/calibre/src/calibre/library/cli.py:279 +#: /home/kovid/work/calibre/src/calibre/library/cli.py:281 msgid "" "Assume that each directory has only a single logical book and that all files " "in it are different e-book formats of that book" msgstr "" +"Zakładaj, że każdy katalog ma tylko jedną logiczną książkę i że wszystkie " +"pliki znajdujące się w nim są innymi formatami tej samej książki" -#: /home/kovid/work/calibre/src/calibre/library/cli.py:281 +#: /home/kovid/work/calibre/src/calibre/library/cli.py:283 msgid "Process directories recursively" msgstr "Przetwarzaj katalogi rekursywnie" -#: /home/kovid/work/calibre/src/calibre/library/cli.py:283 +#: /home/kovid/work/calibre/src/calibre/library/cli.py:285 msgid "" "Add books to database even if they already exist. Comparison is done based " "on book titles." msgstr "" -"Dodaj książki do bazy danych nawet jeśli już są w niej. Porównanie odbywa " +"Dodaj książki do bazy danych nawet jeśli są już w niej. Porównanie odbywa " "się na podstawie tytułów." -#: /home/kovid/work/calibre/src/calibre/library/cli.py:285 +#: /home/kovid/work/calibre/src/calibre/library/cli.py:287 msgid "Add an empty book (a book with no formats)" msgstr "Dodaj pustą książkę (książkę bez żadnych formatów)" -#: /home/kovid/work/calibre/src/calibre/library/cli.py:287 +#: /home/kovid/work/calibre/src/calibre/library/cli.py:289 msgid "Set the title of the added empty book" msgstr "Ustaw tytuł dodawanej pustej książki" -#: /home/kovid/work/calibre/src/calibre/library/cli.py:289 +#: /home/kovid/work/calibre/src/calibre/library/cli.py:291 msgid "Set the authors of the added empty book" msgstr "Ustaw autorów dodawanej pustej książki" -#: /home/kovid/work/calibre/src/calibre/library/cli.py:291 +#: /home/kovid/work/calibre/src/calibre/library/cli.py:293 msgid "Set the ISBN of the added empty book" msgstr "Wpisz ISBN dla dodanej pustej książki" -#: /home/kovid/work/calibre/src/calibre/library/cli.py:317 +#: /home/kovid/work/calibre/src/calibre/library/cli.py:319 msgid "You must specify at least one file to add" -msgstr "Musisz określić conajmniej jeden plik do dodania" +msgstr "Musisz określić co najmniej jeden plik do dodania" -#: /home/kovid/work/calibre/src/calibre/library/cli.py:334 +#: /home/kovid/work/calibre/src/calibre/library/cli.py:336 msgid "" "%prog remove ids\n" "\n" @@ -13686,12 +15983,17 @@ msgid "" "separated list of id numbers (you can get id numbers by using the list " "command). For example, 23,34,57-85\n" msgstr "" +"%prog remove ids\n" +"\n" +"Usuń książki określone przez id z bazy danych. id powinny być numerami id " +"oddzielone przecinkami (możesz uzyskać numery id używając listy komend). Na " +"przykład, 23,34,57-85\n" -#: /home/kovid/work/calibre/src/calibre/library/cli.py:349 +#: /home/kovid/work/calibre/src/calibre/library/cli.py:351 msgid "You must specify at least one book to remove" msgstr "Wybierz co najmniej jedną książkę do usunięcia" -#: /home/kovid/work/calibre/src/calibre/library/cli.py:368 +#: /home/kovid/work/calibre/src/calibre/library/cli.py:370 msgid "" "%prog add_format [options] id ebook_file\n" "\n" @@ -13699,16 +16001,21 @@ msgid "" "identified by id. You can get id by using the list command. If the format " "already exists, it is replaced.\n" msgstr "" +"%prog add_format [options] id ebook_file\n" +"\n" +"Dodaj książkę w pliku (ebook_file) do dostępnych formatów dla logicznej " +"książki określonej przez id. Możesz uzyskać id używając listy komend. Jeśli " +"format już istnieje, zostanie nadpisany.\n" -#: /home/kovid/work/calibre/src/calibre/library/cli.py:383 +#: /home/kovid/work/calibre/src/calibre/library/cli.py:385 msgid "You must specify an id and an ebook file" msgstr "Musisz podać identyfikator i plik książki" -#: /home/kovid/work/calibre/src/calibre/library/cli.py:388 +#: /home/kovid/work/calibre/src/calibre/library/cli.py:390 msgid "ebook file must have an extension" -msgstr "Plik e-książki musi mieć rozszerzenie" +msgstr "Plik książki musi mieć rozszerzenie" -#: /home/kovid/work/calibre/src/calibre/library/cli.py:396 +#: /home/kovid/work/calibre/src/calibre/library/cli.py:398 msgid "" "\n" "%prog remove_format [options] id fmt\n" @@ -13717,12 +16024,18 @@ msgid "" "by using the list command. fmt should be a file extension like LRF or TXT or " "EPUB. If the logical book does not have fmt available, do nothing.\n" msgstr "" +"\n" +"%prog remove_format [options] id fmt\n" +"\n" +"Usuń format fmt z logicznej książki określonej przez id. Możesz uzyskać id " +"używając listy komend. fmt powinien być rozszerzenie takim jak LRF lub TXT " +"lub EPUB. Jeśli logiczna książka nie ma dostępnego fmt, nie rób nic.\n" -#: /home/kovid/work/calibre/src/calibre/library/cli.py:413 +#: /home/kovid/work/calibre/src/calibre/library/cli.py:415 msgid "You must specify an id and a format" msgstr "Musisz podać identyfikator i format książki" -#: /home/kovid/work/calibre/src/calibre/library/cli.py:431 +#: /home/kovid/work/calibre/src/calibre/library/cli.py:433 msgid "" "\n" "%prog show_metadata [options] id\n" @@ -13731,16 +16044,22 @@ msgid "" "id.\n" "id is an id number from the list command.\n" msgstr "" +"\n" +"%prog show_metadata [options] id\n" +"\n" +"Pokazuje metadane przechowywane w bazie danych calibre dla książek " +"określonych przez id.\n" +"id is to numer id z listy komend.\n" -#: /home/kovid/work/calibre/src/calibre/library/cli.py:439 +#: /home/kovid/work/calibre/src/calibre/library/cli.py:441 msgid "Print metadata in OPF form (XML)" msgstr "Wydrukuj metadane w formacie OPF (XML)" -#: /home/kovid/work/calibre/src/calibre/library/cli.py:448 +#: /home/kovid/work/calibre/src/calibre/library/cli.py:450 msgid "You must specify an id" msgstr "Musisz podać identyfikator" -#: /home/kovid/work/calibre/src/calibre/library/cli.py:463 +#: /home/kovid/work/calibre/src/calibre/library/cli.py:465 msgid "" "\n" "%prog set_metadata [options] id /path/to/metadata.opf\n" @@ -13752,12 +16071,21 @@ msgid "" "can get a quick feel for the OPF format by using the --as-opf switch to the\n" "show_metadata command.\n" msgstr "" +"\n" +"%prog set_metadata [options] id /path/to/metadata.opf\n" +"\n" +"Określa metadane przechowywane w bazie danych calibre dla książki " +"określonej\n" +"przez id z pliku metadanych OPF metadata.opf. id to numer id z listy " +"komend.\n" +"Możesz wypróbować format OPF używając przełącznika --as-opf dla\n" +"komendy show_metadata.\n" -#: /home/kovid/work/calibre/src/calibre/library/cli.py:479 +#: /home/kovid/work/calibre/src/calibre/library/cli.py:481 msgid "You must specify an id and a metadata file" msgstr "Musisz podać identyfikator i plik z metadanymi" -#: /home/kovid/work/calibre/src/calibre/library/cli.py:499 +#: /home/kovid/work/calibre/src/calibre/library/cli.py:501 msgid "" "%prog export [options] ids\n" "\n" @@ -13767,28 +16095,35 @@ msgid "" "(in\n" "an opf file). You can get id numbers from the list command.\n" msgstr "" +"%prog export [options] ids\n" +"\n" +"Eksportuje książki określone przez id (lista oddzielona przecinkami) do " +"systemu plików.\n" +"Operacja eksportu zapisuje wszystkie formaty książek, jej okładkę i metadane " +"(w pliku\n" +"opf). Możesz uzyskać numeryid z listy komend.\n" -#: /home/kovid/work/calibre/src/calibre/library/cli.py:507 +#: /home/kovid/work/calibre/src/calibre/library/cli.py:509 msgid "Export all books in database, ignoring the list of ids." msgstr "Eksportuj wszystkie książki w bazie, ignoruj listę identyfikatorów." -#: /home/kovid/work/calibre/src/calibre/library/cli.py:509 +#: /home/kovid/work/calibre/src/calibre/library/cli.py:511 msgid "Export books to the specified directory. Default is" msgstr "Eksportuj książki do podanego katalogu. Domyślny to" -#: /home/kovid/work/calibre/src/calibre/library/cli.py:511 +#: /home/kovid/work/calibre/src/calibre/library/cli.py:513 msgid "Export all books into a single directory" msgstr "Eksportuj wszystkie książki do pojedynczego katalogu" -#: /home/kovid/work/calibre/src/calibre/library/cli.py:518 +#: /home/kovid/work/calibre/src/calibre/library/cli.py:520 msgid "Specifying this switch will turn this behavior off." -msgstr "" +msgstr "Określając ten przełącznik wyłączysz to zachowanie" -#: /home/kovid/work/calibre/src/calibre/library/cli.py:541 +#: /home/kovid/work/calibre/src/calibre/library/cli.py:543 msgid "You must specify some ids or the %s option" -msgstr "" +msgstr "Musisz określić jakieś id albo opcję %s" -#: /home/kovid/work/calibre/src/calibre/library/cli.py:554 +#: /home/kovid/work/calibre/src/calibre/library/cli.py:556 msgid "" "%prog add_custom_column [options] label name datatype\n" "\n" @@ -13798,25 +16133,35 @@ msgid "" "column.\n" "datatype is one of: {0}\n" msgstr "" +"%prog add_custom_column [options] label name datatype\n" +"\n" +"Stwórz własną kolumnę. label to nazwa kolumny w języku komputerowym. Nie\n" +"powinna zawierać spacji ani dwukropka. name to nazwa ludzka kolumny.\n" +"datatype to jeden z: {0}\n" -#: /home/kovid/work/calibre/src/calibre/library/cli.py:563 +#: /home/kovid/work/calibre/src/calibre/library/cli.py:565 msgid "" "This column stores tag like data (i.e. multiple comma separated values). " "Only applies if datatype is text." msgstr "" +"Ta kolumna przechowuje dane etykietopodobne (np. wielokrotne wartości " +"oddzielone przecinkami). Ma zastosowanie tylko jeśli typ danych to tekst." -#: /home/kovid/work/calibre/src/calibre/library/cli.py:567 +#: /home/kovid/work/calibre/src/calibre/library/cli.py:569 msgid "" "A dictionary of options to customize how the data in this column will be " "interpreted. This is a JSON string. For enumeration columns, use --" "display='{\"enum_values\":[\"val1\", \"val2\"]}'" msgstr "" +"Słownik opcji określających jak dane w tej kolumnie zostaną zinterpretowane. " +"To ciąg znaków JSON. Dla wyliczenia kolumn użyj --" +"display='{\"enum_values\":[\"val1\", \"val2\"]}'" -#: /home/kovid/work/calibre/src/calibre/library/cli.py:581 +#: /home/kovid/work/calibre/src/calibre/library/cli.py:583 msgid "You must specify label, name and datatype" -msgstr "" +msgstr "Musisz wskazać etykietę, nazwę i typ danych" -#: /home/kovid/work/calibre/src/calibre/library/cli.py:642 +#: /home/kovid/work/calibre/src/calibre/library/cli.py:644 msgid "" "\n" " %prog catalog /path/to/destination.(CSV|EPUB|MOBI|XML ...) [options]\n" @@ -13826,32 +16171,46 @@ msgid "" "ouput.\n" " " msgstr "" +"\n" +" %prog catalog /path/to/destination.(CSV|EPUB|MOBI|XML ...) [options]\n" +"\n" +" Eksportuje katalog w formacie określonym przez ścieżkę/do/miejsce " +"docelowe rozszerzenia.\n" +" Opcje kontrolują jak wpisy są wyświetlane w wygenerowanym katalogu " +"wynikowym.\n" +" " -#: /home/kovid/work/calibre/src/calibre/library/cli.py:656 +#: /home/kovid/work/calibre/src/calibre/library/cli.py:658 msgid "" "Comma-separated list of database IDs to catalog.\n" "If declared, --search is ignored.\n" "Default: all" msgstr "" +"Lista ID oddzielona przecnkami do katalogu.\n" +"Jeśli jest zadeklarowane, --search jest ignorowane.\n" +"Domyślnie: wszystkie (all)" -#: /home/kovid/work/calibre/src/calibre/library/cli.py:660 +#: /home/kovid/work/calibre/src/calibre/library/cli.py:662 msgid "" "Filter the results by the search query. For the format of the search query, " "please see the search-related documentation in the User Manual.\n" "Default: no filtering" msgstr "" +"Filtruj wyniki przez zapytanie. Dla formatu zapytania, zobacz odpowiednią " +"dokumentację w Podręczniku Użytkownika.\n" +"Domyślnie: brak filtrowania" -#: /home/kovid/work/calibre/src/calibre/library/cli.py:666 +#: /home/kovid/work/calibre/src/calibre/library/cli.py:668 #: /home/kovid/work/calibre/src/calibre/web/fetch/simple.py:499 msgid "Show detailed output information. Useful for debugging" msgstr "" "Pokazuj szczegółowową informację wyjściową. Przydatne przy debugowaniu." -#: /home/kovid/work/calibre/src/calibre/library/cli.py:679 +#: /home/kovid/work/calibre/src/calibre/library/cli.py:681 msgid "Error: You must specify a catalog output file" msgstr "Błąd: musisz podać plik wyjściowy katalogu" -#: /home/kovid/work/calibre/src/calibre/library/cli.py:725 +#: /home/kovid/work/calibre/src/calibre/library/cli.py:727 msgid "" "\n" " %prog set_custom [options] column id value\n" @@ -13862,18 +16221,28 @@ msgid "" " command.\n" " " msgstr "" +"\n" +" %prog set_custom [options] column id value\n" +"\n" +" Ustaw wartość kolumny dodatkowej dla książki określonej przez id.\n" +" Możesz uzyskać listę id używając listy komend.\n" +" Możesz uzyskać listę nazw dodatkowych kolumn używając komendy " +"custom_columns.\n" +" " -#: /home/kovid/work/calibre/src/calibre/library/cli.py:736 +#: /home/kovid/work/calibre/src/calibre/library/cli.py:738 msgid "" "If the column stores multiple values, append the specified values to the " "existing ones, instead of replacing them." msgstr "" +"Jeśli kolumna przechowuje wielokrotne wartości, dołącz określone wartości do " +"istniejących, zamiast je zastępować." -#: /home/kovid/work/calibre/src/calibre/library/cli.py:747 +#: /home/kovid/work/calibre/src/calibre/library/cli.py:749 msgid "Error: You must specify a field name, id and value" msgstr "Błąd: Musisz określić identyfikator, nazwę i wartość pola" -#: /home/kovid/work/calibre/src/calibre/library/cli.py:766 +#: /home/kovid/work/calibre/src/calibre/library/cli.py:768 msgid "" "\n" " %prog custom_columns [options]\n" @@ -13881,20 +16250,25 @@ msgid "" " List available custom columns. Shows column labels and ids.\n" " " msgstr "" +"\n" +" %prog custom_columns [options]\n" +"\n" +" Lista dostępnych dodatkowych kolumn. Pokazuje etykiety kolumn i id.\n" +" " -#: /home/kovid/work/calibre/src/calibre/library/cli.py:773 +#: /home/kovid/work/calibre/src/calibre/library/cli.py:775 msgid "Show details for each column." msgstr "Pokaż szczegóły dla każdej kolumny." -#: /home/kovid/work/calibre/src/calibre/library/cli.py:785 -msgid "You will lose all data in the column: %r. Are you sure (y/n)? " -msgstr "Utracisz wszystkie dane z kolumny: %r. Jesteś pewien (y/n)? " - #: /home/kovid/work/calibre/src/calibre/library/cli.py:787 +msgid "You will lose all data in the column: %r. Are you sure (y/n)? " +msgstr "Utracisz wszystkie dane z kolumny: %r. Jesteś pewien (t/n)? " + +#: /home/kovid/work/calibre/src/calibre/library/cli.py:789 msgid "y" msgstr "t" -#: /home/kovid/work/calibre/src/calibre/library/cli.py:793 +#: /home/kovid/work/calibre/src/calibre/library/cli.py:795 msgid "" "\n" " %prog remove_custom_column [options] label\n" @@ -13903,16 +16277,22 @@ msgid "" " columns with the custom_columns command.\n" " " msgstr "" +"\n" +" %prog remove_custom_column [options] label\n" +"\n" +" Usuń dodatkową kolumnę określoną przez label. Możesz zobaczyć dostępne\n" +" kolumny używając komendy custom_columns.\n" +" " -#: /home/kovid/work/calibre/src/calibre/library/cli.py:801 +#: /home/kovid/work/calibre/src/calibre/library/cli.py:803 msgid "Do not ask for confirmation" msgstr "Nie pytaj o potwierdzenie" -#: /home/kovid/work/calibre/src/calibre/library/cli.py:811 +#: /home/kovid/work/calibre/src/calibre/library/cli.py:813 msgid "Error: You must specify a column label" msgstr "Błąd: Musisz podać nagłówek kolumny" -#: /home/kovid/work/calibre/src/calibre/library/cli.py:821 +#: /home/kovid/work/calibre/src/calibre/library/cli.py:823 msgid "" "\n" " %prog saved_searches [options] list\n" @@ -13924,75 +16304,93 @@ msgid "" " replaced.\n" " " msgstr "" +"\n" +" %prog saved_searches [options] list\n" +" %prog saved_searches add name search\n" +" %prog saved_searches remove name\n" +"\n" +" Zarządzaj zapisanymi wyszukiwaniami zachowanymi w bazie danych.\n" +" Jeśli spróbujesz dodać zapytanie z nazwą, która juz istnieje, zostanie\n" +" nadpisane.\n" +" " -#: /home/kovid/work/calibre/src/calibre/library/cli.py:839 +#: /home/kovid/work/calibre/src/calibre/library/cli.py:841 msgid "Error: You must specify an action (add|remove|list)" msgstr "Błąd: Musisz wybrać akcję (dodaj|usuń|lista)" -#: /home/kovid/work/calibre/src/calibre/library/cli.py:847 +#: /home/kovid/work/calibre/src/calibre/library/cli.py:849 msgid "Name:" msgstr "Nazwa:" -#: /home/kovid/work/calibre/src/calibre/library/cli.py:848 +#: /home/kovid/work/calibre/src/calibre/library/cli.py:850 msgid "Search string:" -msgstr "" +msgstr "Wyszukanie:" -#: /home/kovid/work/calibre/src/calibre/library/cli.py:854 +#: /home/kovid/work/calibre/src/calibre/library/cli.py:856 msgid "Error: You must specify a name and a search string" -msgstr "" +msgstr "Błąd: Musisz wskazać nazwę i wyszukiwanie" -#: /home/kovid/work/calibre/src/calibre/library/cli.py:857 +#: /home/kovid/work/calibre/src/calibre/library/cli.py:859 msgid "added" msgstr "dodano" -#: /home/kovid/work/calibre/src/calibre/library/cli.py:862 +#: /home/kovid/work/calibre/src/calibre/library/cli.py:864 msgid "Error: You must specify a name" msgstr "Błąd: Musisz podać nazwę" -#: /home/kovid/work/calibre/src/calibre/library/cli.py:865 +#: /home/kovid/work/calibre/src/calibre/library/cli.py:867 msgid "removed" msgstr "usunięto" -#: /home/kovid/work/calibre/src/calibre/library/cli.py:869 +#: /home/kovid/work/calibre/src/calibre/library/cli.py:871 msgid "Error: Action %s not recognized, must be one of: (add|remove|list)" -msgstr "" +msgstr "Błąd: Akcja %s nierozpoznana, musi być jedno z: (dodaj|usuń|lista)" -#: /home/kovid/work/calibre/src/calibre/library/cli.py:877 +#: /home/kovid/work/calibre/src/calibre/library/cli.py:879 msgid "" "%prog check_library [options]\n" "\n" "Perform some checks on the filesystem representing a library. Reports are " "{0}\n" msgstr "" +"%prog check_library [options]\n" +"\n" +"Wykonaj sprawdzenie w systemie plików biblioteki. Zgłoszenia są {0}\n" -#: /home/kovid/work/calibre/src/calibre/library/cli.py:884 -#: /home/kovid/work/calibre/src/calibre/library/cli.py:1034 +#: /home/kovid/work/calibre/src/calibre/library/cli.py:886 +#: /home/kovid/work/calibre/src/calibre/library/cli.py:1036 msgid "Output in CSV" -msgstr "" +msgstr "Wynik w CSV" -#: /home/kovid/work/calibre/src/calibre/library/cli.py:887 +#: /home/kovid/work/calibre/src/calibre/library/cli.py:889 msgid "" "Comma-separated list of reports.\n" "Default: all" msgstr "" +"Lista raportów rozdzielona przecinkiem.\n" +"Domyślnie: wszystkie" -#: /home/kovid/work/calibre/src/calibre/library/cli.py:891 +#: /home/kovid/work/calibre/src/calibre/library/cli.py:893 msgid "" "Comma-separated list of extensions to ignore.\n" "Default: all" msgstr "" +"Lista rozszerzeń rozdzielona przecinkiem do zignorowania.\n" +"Domyślnie: wszystkie" -#: /home/kovid/work/calibre/src/calibre/library/cli.py:895 +#: /home/kovid/work/calibre/src/calibre/library/cli.py:897 msgid "" "Comma-separated list of names to ignore.\n" "Default: all" msgstr "" +"Lista nazw rozdzielona przecinkiem do zignorowania.\n" +"Domyślnie: wszystkie" -#: /home/kovid/work/calibre/src/calibre/library/cli.py:925 +#: /home/kovid/work/calibre/src/calibre/library/cli.py:927 msgid "Unknown report check" -msgstr "" +msgstr "Nieznany status raportu" -#: /home/kovid/work/calibre/src/calibre/library/cli.py:958 +#: /home/kovid/work/calibre/src/calibre/library/cli.py:960 msgid "" "%prog restore_database [options]\n" "\n" @@ -14006,52 +16404,79 @@ msgid "" "what is found in the OPF files.\n" " " msgstr "" +"%prog restore_database [options]\n" +"\n" +"Przywróć tę bazę danych z metadanych zachowanych w plikach OPF w każdym\n" +"katalogu biblioteki calibre. To jest użyteczne jeśli twój plik metadata.db\n" +"uległ uszkodzeniu.\n" +"\n" +"UWAGA: Ta komenda całkowicie zregeneruje twoją bazę danych. Stracisz\n" +"wszystkie zapisane wyszukiwanie, kategorie użytkowników, wtyczki, ustawienia " +"konwersji\n" +"i własne źródła. Przywrócone metadane będą tak dokładne jakie zostaną " +"znalezione\n" +"w plikach OPF.\n" +" " -#: /home/kovid/work/calibre/src/calibre/library/cli.py:973 +#: /home/kovid/work/calibre/src/calibre/library/cli.py:975 msgid "" "Really do the recovery. The command will not run unless this option is " "specified." msgstr "" +"Naprawdę dokonaj przywrócenia. Ta komenda nie uruchomi się dopóki ta opcja " +"nie zostanie określona." -#: /home/kovid/work/calibre/src/calibre/library/cli.py:986 +#: /home/kovid/work/calibre/src/calibre/library/cli.py:988 msgid "You must provide the %s option to do a recovery" -msgstr "" +msgstr "Musisz zaznaczyć opcję %s aby dokonać przywrócenia" -#: /home/kovid/work/calibre/src/calibre/library/cli.py:1023 +#: /home/kovid/work/calibre/src/calibre/library/cli.py:1025 msgid "" "%prog list_categories [options]\n" "\n" "Produce a report of the category information in the database. The\n" "information is the equivalent of what is shown in the tags pane.\n" msgstr "" +"%prog list_categories [options]\n" +"\n" +"Wygeneruj raport z informacji kategorii w bazie danych. Informacja\n" +"jest równoznaczna z tym co jest pokazane na ekranie etykiet.\n" -#: /home/kovid/work/calibre/src/calibre/library/cli.py:1031 +#: /home/kovid/work/calibre/src/calibre/library/cli.py:1033 msgid "" "Output only the number of items in a category instead of the counts per item " "within the category" msgstr "" +"Wyprowadź tylko taką ilość elementów w kategorii zamiast wyliczać na każdy " +"element wewnątrz kategorii" -#: /home/kovid/work/calibre/src/calibre/library/cli.py:1036 +#: /home/kovid/work/calibre/src/calibre/library/cli.py:1038 msgid "" "The character to put around the category value in CSV mode. Default is " "quotes (\")." msgstr "" +"Znak do ustawienia w wartości kategorii w trybie CSV. Domyślnie jest to " +"cudzysłów (\")." -#: /home/kovid/work/calibre/src/calibre/library/cli.py:1039 +#: /home/kovid/work/calibre/src/calibre/library/cli.py:1041 msgid "" "Comma-separated list of category lookup names.\n" "Default: all" msgstr "" +"Lista kategorii wyszukiwań nazw oddzielona przecinkami.\n" +"Domyślnie: wszystkie" -#: /home/kovid/work/calibre/src/calibre/library/cli.py:1045 +#: /home/kovid/work/calibre/src/calibre/library/cli.py:1047 msgid "The string used to separate fields in CSV mode. Default is a comma." msgstr "" +"Ciąg znaków użyty do oddzielenia pól w trybie CSV. Domyślnie jest to " +"przecinek." -#: /home/kovid/work/calibre/src/calibre/library/cli.py:1083 +#: /home/kovid/work/calibre/src/calibre/library/cli.py:1085 msgid "CATEGORY ITEMS" -msgstr "" +msgstr "ELEMENTY KATEGORII" -#: /home/kovid/work/calibre/src/calibre/library/cli.py:1152 +#: /home/kovid/work/calibre/src/calibre/library/cli.py:1157 msgid "" "%%prog command [options] [arguments]\n" "\n" @@ -14062,53 +16487,71 @@ msgid "" "\n" "For help on an individual command: %%prog command --help\n" msgstr "" +"%%prog command [options] [arguments]\n" +"\n" +"%%prog to komenda linii interfejsu dla bazy danych książek calibre.\n" +"\n" +"komendami mogą być:\n" +" %s\n" +"\n" +"Dla pomocy odnośnie każdej komendy wpisz: %%prog command --help\n" -#: /home/kovid/work/calibre/src/calibre/library/custom_columns.py:586 +#: /home/kovid/work/calibre/src/calibre/library/custom_columns.py:594 msgid "No label was provided" -msgstr "" +msgstr "Nie wskazano etykiety" -#: /home/kovid/work/calibre/src/calibre/library/custom_columns.py:588 +#: /home/kovid/work/calibre/src/calibre/library/custom_columns.py:596 msgid "" "The label must contain only lower case letters, digits and underscores, and " "start with a letter" msgstr "" +"Etykieta musi zawierać tylko małe litery, cyfry i podkreślniki oraz zaczynać " +"się od litery" -#: /home/kovid/work/calibre/src/calibre/library/database2.py:59 +#: /home/kovid/work/calibre/src/calibre/library/database2.py:64 msgid "%sAverage rating is %3.1f" -msgstr "" +msgstr "%sŚrednia ocena to %3.1f" -#: /home/kovid/work/calibre/src/calibre/library/database2.py:922 +#: /home/kovid/work/calibre/src/calibre/library/database2.py:1017 msgid "Main" msgstr "Główna" -#: /home/kovid/work/calibre/src/calibre/library/database2.py:2733 +#: /home/kovid/work/calibre/src/calibre/library/database2.py:3105 msgid "

      Migrating old database to ebook library in %s

      " msgstr "" "

      Przenoszenie starej bazy danych do biblioteki książek w %s

      " -#: /home/kovid/work/calibre/src/calibre/library/database2.py:2762 +#: /home/kovid/work/calibre/src/calibre/library/database2.py:3134 msgid "Copying %s" msgstr "Kopiowanie %s" -#: /home/kovid/work/calibre/src/calibre/library/database2.py:2779 +#: /home/kovid/work/calibre/src/calibre/library/database2.py:3151 msgid "Compacting database" msgstr "Kompaktowanie bazy danych" -#: /home/kovid/work/calibre/src/calibre/library/field_metadata.py:140 +#: /home/kovid/work/calibre/src/calibre/library/field_metadata.py:148 msgid "Ratings" msgstr "Oceny" -#: /home/kovid/work/calibre/src/calibre/library/field_metadata.py:277 +#: /home/kovid/work/calibre/src/calibre/library/field_metadata.py:181 +msgid "Identifiers" +msgstr "Identyikatory" + +#: /home/kovid/work/calibre/src/calibre/library/field_metadata.py:191 +msgid "Author Sort" +msgstr "Sortowanie po autorze" + +#: /home/kovid/work/calibre/src/calibre/library/field_metadata.py:301 msgid "Title Sort" msgstr "Tytuł sort." -#: /home/kovid/work/calibre/src/calibre/library/restore.py:125 +#: /home/kovid/work/calibre/src/calibre/library/restore.py:126 msgid "Processed" msgstr "Przetworzony" -#: /home/kovid/work/calibre/src/calibre/library/restore.py:191 +#: /home/kovid/work/calibre/src/calibre/library/restore.py:192 msgid "creating custom column " -msgstr "" +msgstr "tworzenie kolumny użytkownika " #: /home/kovid/work/calibre/src/calibre/library/save_to_disk.py:32 msgid "The title" @@ -14123,6 +16566,8 @@ msgid "" "The author sort string. To use only the first letter of the name use " "{author_sort[0]}" msgstr "" +"Ciąg sortowania według autora. By użyć tylko pierwszej litery nazwy użyj " +"{author_sort[0]}" #: /home/kovid/work/calibre/src/calibre/library/save_to_disk.py:36 msgid "The tags" @@ -14161,14 +16606,18 @@ msgid "The published date" msgstr "Data publikacji" #: /home/kovid/work/calibre/src/calibre/library/save_to_disk.py:46 +msgid "The date when the metadata for this book record was last modified" +msgstr "Czas ostatniej modyfikacji metadanych" + +#: /home/kovid/work/calibre/src/calibre/library/save_to_disk.py:48 msgid "The calibre internal id" msgstr "Wewnętrzny identyfikator calibre" -#: /home/kovid/work/calibre/src/calibre/library/save_to_disk.py:56 +#: /home/kovid/work/calibre/src/calibre/library/save_to_disk.py:76 msgid "Options to control saving to disk" msgstr "Opcje dotyczące zapisu na dysku" -#: /home/kovid/work/calibre/src/calibre/library/save_to_disk.py:62 +#: /home/kovid/work/calibre/src/calibre/library/save_to_disk.py:82 msgid "" "Normally, calibre will update the metadata in the saved files from what is " "in the calibre library. Makes saving to disk slower." @@ -14176,45 +16625,54 @@ msgstr "" "Normalnie calibre uaktualni metadane w zapisanych plikach tymi, które są w " "bibliotece calibre. Sprawia, że zapisywanie na dysku jest wolniejsze." -#: /home/kovid/work/calibre/src/calibre/library/save_to_disk.py:65 +#: /home/kovid/work/calibre/src/calibre/library/save_to_disk.py:85 msgid "" "Normally, calibre will write the metadata into a separate OPF file along " "with the actual e-book files." msgstr "" "Normalnie calibre zapisze metadane do osobnego pliku OPF obok właściwego " -"pliku z e-bookiem." +"pliku z książką." -#: /home/kovid/work/calibre/src/calibre/library/save_to_disk.py:68 +#: /home/kovid/work/calibre/src/calibre/library/save_to_disk.py:88 msgid "" "Normally, calibre will save the cover in a separate file along with the " "actual e-book file(s)." msgstr "" "Normalnie calibre zapisze okładkę do osobnego pliku obok właściwego pliku z " -"e-bookiem." +"książką." -#: /home/kovid/work/calibre/src/calibre/library/save_to_disk.py:71 +#: /home/kovid/work/calibre/src/calibre/library/save_to_disk.py:91 msgid "" "Comma separated list of formats to save for each book. By default all " "available formats are saved." msgstr "" +"Oddzielana przecinkami lista formatów do zapisania dla każdej książki. " +"Domyślnie zapisywane są wszystkie dostępne formaty." -#: /home/kovid/work/calibre/src/calibre/library/save_to_disk.py:74 +#: /home/kovid/work/calibre/src/calibre/library/save_to_disk.py:94 msgid "" "The template to control the filename and directory structure of the saved " "files. Default is \"%s\" which will save books into a per-author " "subdirectory with filenames containing title and author. Available controls " "are: {%s}" msgstr "" +"Szablon kontrolujący nazyw plików i strukturę katalogu zapisanych plików. " +"Domyślnie jest \"%s\" który zapisze książki w podkatalogach z nazwą autora z " +"nazwami plików zawierających tytuł i autora. Dostępne kontrole to:{%s}" -#: /home/kovid/work/calibre/src/calibre/library/save_to_disk.py:79 +#: /home/kovid/work/calibre/src/calibre/library/save_to_disk.py:99 msgid "" "The template to control the filename and directory structure of files sent " "to the device. Default is \"%s\" which will save books into a per-author " "directory with filenames containing title and author. Available controls " "are: {%s}" msgstr "" +"Szablon kontrolujący nazyw plików i strukturę katalogu wysłanych do " +"urzadzenia. Domyślnie jest \"%s\" który zapisze książki w podkatalogach z " +"nazwą autora z nazwami plików zawierających tytuł i autora. Dostępne " +"kontrole to:{%s}" -#: /home/kovid/work/calibre/src/calibre/library/save_to_disk.py:86 +#: /home/kovid/work/calibre/src/calibre/library/save_to_disk.py:106 msgid "" "Normally, calibre will convert all non English characters into English " "equivalents for the file names. WARNING: If you turn this off, you may " @@ -14224,10 +16682,10 @@ msgstr "" "Normalnie calibre skonwertuje wszystkie nie-angielskie znaki na ich " "angielskie odpowiedniki w nazwach plików. UWAGA: Jeżeli wyłączysz tę opcję, " "możesz doświadczyć błędów podczas zapisywania, w zależności od tego, jak " -"dobrze Twój system plików radzi sobie z Unicode." +"dobrze twój system plików radzi sobie z Unicode." -#: /home/kovid/work/calibre/src/calibre/library/save_to_disk.py:92 -#: /home/kovid/work/calibre/src/calibre/library/save_to_disk.py:95 +#: /home/kovid/work/calibre/src/calibre/library/save_to_disk.py:112 +#: /home/kovid/work/calibre/src/calibre/library/save_to_disk.py:115 msgid "" "The format in which to display dates. %d - day, %b - month, %Y - year. " "Default is: %b, %Y" @@ -14235,18 +16693,18 @@ msgstr "" "Format wyświetlania daty. %d - dzień, %b - miesiąc, %Y - rok. Domyślnie " "jest: %b, %Y" -#: /home/kovid/work/calibre/src/calibre/library/save_to_disk.py:98 +#: /home/kovid/work/calibre/src/calibre/library/save_to_disk.py:118 msgid "Convert paths to lowercase." msgstr "Przekształć scieżki na małe znaki." -#: /home/kovid/work/calibre/src/calibre/library/save_to_disk.py:100 +#: /home/kovid/work/calibre/src/calibre/library/save_to_disk.py:120 msgid "Replace whitespace with underscores." msgstr "Zastąp spacje podkreśleniami." -#: /home/kovid/work/calibre/src/calibre/library/save_to_disk.py:361 -#: /home/kovid/work/calibre/src/calibre/library/save_to_disk.py:385 +#: /home/kovid/work/calibre/src/calibre/library/save_to_disk.py:370 +#: /home/kovid/work/calibre/src/calibre/library/save_to_disk.py:398 msgid "Requested formats not available" -msgstr "Żądane formaty nie dostępne" +msgstr "Żądane formaty nie są dostępne" #: /home/kovid/work/calibre/src/calibre/library/server/__init__.py:21 msgid "Settings to control the calibre content server" @@ -14258,16 +16716,16 @@ msgstr "Port, na którym będziemy nasłuchiwać. Domyślnie to %default" #: /home/kovid/work/calibre/src/calibre/library/server/__init__.py:27 msgid "The server timeout in seconds. Default is %default" -msgstr "Czas oczekiwania serwera w sekundach. Domyslnie %default" +msgstr "Czas oczekiwania serwera w sekundach. Domyślnie %default" #: /home/kovid/work/calibre/src/calibre/library/server/__init__.py:29 msgid "The max number of worker threads to use. Default is %default" -msgstr "Ilość wątków pracy która ma być użyta. Domyślne jest %default" +msgstr "Ilość wątków pracy, która ma być użyta. Domyślne jest %default" #: /home/kovid/work/calibre/src/calibre/library/server/__init__.py:31 msgid "Set a password to restrict access. By default access is unrestricted." msgstr "" -"Ustaw hasło aby ograniczyć dostęp. Domyślnie dostęp jest nieograniczony." +"Ustaw hasło, aby ograniczyć dostęp. Domyślnie dostęp jest nieograniczony." #: /home/kovid/work/calibre/src/calibre/library/server/__init__.py:33 msgid "Username for access. By default, it is: %default" @@ -14282,6 +16740,8 @@ msgid "" "The maximum number of matches to return per OPDS query. This affects Stanza, " "WordPlayer, etc. integration." msgstr "" +"Maksymalna ilość trafień przy zapytaniu OPDS. Dotyczy integracji ze Stanzą, " +"WordPlayerem itd." #: /home/kovid/work/calibre/src/calibre/library/server/__init__.py:43 msgid "" @@ -14289,128 +16749,138 @@ msgid "" "more than this number of items. Default: %default. Set to a large number to " "disable grouping." msgstr "" +"Grupuj elementy w kategorie takie jak autor/etykiety po pierwszej literze " +"gdy zaistnieje więcej niż jeden element. Domyślnie: %default. Ustaw wyższą " +"wartość aby wyłączyć grupowanie." #: /home/kovid/work/calibre/src/calibre/library/server/__init__.py:48 msgid "" "Prefix to prepend to all URLs. Useful for reverseproxying to this server " "from Apache/nginx/etc." msgstr "" +"Przedrostek do dołączenia przed wszystkimi URL. Użyteczne przy " +"reverseproxying dla tego serwera z Apache/nginx/itd." -#: /home/kovid/work/calibre/src/calibre/library/server/browse.py:60 -#: /home/kovid/work/calibre/src/calibre/library/server/browse.py:436 +#: /home/kovid/work/calibre/src/calibre/library/server/browse.py:64 +#: /home/kovid/work/calibre/src/calibre/library/server/browse.py:498 msgid "Loading, please wait" -msgstr "Wszytywanie, proszę czekać" +msgstr "Wczytywanie, proszę czekać" -#: /home/kovid/work/calibre/src/calibre/library/server/browse.py:86 -#: /home/kovid/work/calibre/src/calibre/library/server/browse.py:107 +#: /home/kovid/work/calibre/src/calibre/library/server/browse.py:90 +#: /home/kovid/work/calibre/src/calibre/library/server/browse.py:111 msgid "Go to" msgstr "Idź do" -#: /home/kovid/work/calibre/src/calibre/library/server/browse.py:102 +#: /home/kovid/work/calibre/src/calibre/library/server/browse.py:106 msgid "First" msgstr "Pierwsze" -#: /home/kovid/work/calibre/src/calibre/library/server/browse.py:102 +#: /home/kovid/work/calibre/src/calibre/library/server/browse.py:106 msgid "Last" msgstr "Ostatnie" -#: /home/kovid/work/calibre/src/calibre/library/server/browse.py:105 +#: /home/kovid/work/calibre/src/calibre/library/server/browse.py:109 msgid "Browsing %d books" msgstr "Przeglądanie %d książek" -#: /home/kovid/work/calibre/src/calibre/library/server/browse.py:122 -#: /home/kovid/work/calibre/src/calibre/library/server/browse.py:252 +#: /home/kovid/work/calibre/src/calibre/library/server/browse.py:126 +#: /home/kovid/work/calibre/src/calibre/library/server/browse.py:255 msgid "Average rating" msgstr "Średnia ocen" -#: /home/kovid/work/calibre/src/calibre/library/server/browse.py:123 +#: /home/kovid/work/calibre/src/calibre/library/server/browse.py:127 msgid "%s: %.1f stars" -msgstr "" +msgstr "%s: %.1f gwiazdek" -#: /home/kovid/work/calibre/src/calibre/library/server/browse.py:160 +#: /home/kovid/work/calibre/src/calibre/library/server/browse.py:163 msgid "%d stars" msgstr "%d gwiazdki" -#: /home/kovid/work/calibre/src/calibre/library/server/browse.py:253 +#: /home/kovid/work/calibre/src/calibre/library/server/browse.py:256 msgid "Popularity" msgstr "Popularność" -#: /home/kovid/work/calibre/src/calibre/library/server/browse.py:272 +#: /home/kovid/work/calibre/src/calibre/library/server/browse.py:275 msgid "Sort by" msgstr "Sortuj według" -#: /home/kovid/work/calibre/src/calibre/library/server/browse.py:275 +#: /home/kovid/work/calibre/src/calibre/library/server/browse.py:278 msgid "library" msgstr "biblioteka" -#: /home/kovid/work/calibre/src/calibre/library/server/browse.py:276 +#: /home/kovid/work/calibre/src/calibre/library/server/browse.py:279 msgid "home" msgstr "strona główna" -#: /home/kovid/work/calibre/src/calibre/library/server/browse.py:337 -#: /home/kovid/work/calibre/src/calibre/library/server/browse.py:547 -#: /home/kovid/work/calibre/src/calibre/library/server/opds.py:589 +#: /home/kovid/work/calibre/src/calibre/library/server/browse.py:340 +#: /home/kovid/work/calibre/src/calibre/library/server/browse.py:612 +#: /home/kovid/work/calibre/src/calibre/library/server/opds.py:573 msgid "Newest" msgstr "Najnowsze" -#: /home/kovid/work/calibre/src/calibre/library/server/browse.py:338 -#: /home/kovid/work/calibre/src/calibre/library/server/browse.py:548 +#: /home/kovid/work/calibre/src/calibre/library/server/browse.py:341 +#: /home/kovid/work/calibre/src/calibre/library/server/browse.py:613 msgid "All books" msgstr "Wszystkie książki" -#: /home/kovid/work/calibre/src/calibre/library/server/browse.py:370 +#: /home/kovid/work/calibre/src/calibre/library/server/browse.py:386 +#: /home/kovid/work/calibre/src/calibre/library/server/browse.py:451 msgid "Browse books by" msgstr "Przeglądaj książki po" -#: /home/kovid/work/calibre/src/calibre/library/server/browse.py:375 +#: /home/kovid/work/calibre/src/calibre/library/server/browse.py:391 msgid "Choose a category to browse by:" msgstr "Wybierz kategorię do przeglądania:" -#: /home/kovid/work/calibre/src/calibre/library/server/browse.py:456 +#: /home/kovid/work/calibre/src/calibre/library/server/browse.py:521 msgid "Browsing by" msgstr "Przeglądanie po" -#: /home/kovid/work/calibre/src/calibre/library/server/browse.py:457 +#: /home/kovid/work/calibre/src/calibre/library/server/browse.py:522 msgid "Up" msgstr "Góra" -#: /home/kovid/work/calibre/src/calibre/library/server/browse.py:581 +#: /home/kovid/work/calibre/src/calibre/library/server/browse.py:648 msgid "in" msgstr "w" -#: /home/kovid/work/calibre/src/calibre/library/server/browse.py:584 +#: /home/kovid/work/calibre/src/calibre/library/server/browse.py:651 msgid "Books in" msgstr "Książki w" -#: /home/kovid/work/calibre/src/calibre/library/server/browse.py:670 +#: /home/kovid/work/calibre/src/calibre/library/server/browse.py:740 msgid "Other formats" msgstr "Inne formaty" -#: /home/kovid/work/calibre/src/calibre/library/server/browse.py:677 +#: /home/kovid/work/calibre/src/calibre/library/server/browse.py:747 msgid "Read %(title)s in the %(fmt)s format" -msgstr "" +msgstr "Czytaj %(title)s w formacie %(fmt)s" -#: /home/kovid/work/calibre/src/calibre/library/server/browse.py:682 +#: /home/kovid/work/calibre/src/calibre/library/server/browse.py:752 msgid "Get" msgstr "Uzyskaj" -#: /home/kovid/work/calibre/src/calibre/library/server/browse.py:697 +#: /home/kovid/work/calibre/src/calibre/library/server/browse.py:765 +msgid "Details" +msgstr "Szczegóły" + +#: /home/kovid/work/calibre/src/calibre/library/server/browse.py:767 msgid "Permalink" msgstr "Bezpośredni odnośnik" -#: /home/kovid/work/calibre/src/calibre/library/server/browse.py:698 +#: /home/kovid/work/calibre/src/calibre/library/server/browse.py:768 msgid "A permanent link to this book" msgstr "Stały link do tej książki" -#: /home/kovid/work/calibre/src/calibre/library/server/browse.py:709 +#: /home/kovid/work/calibre/src/calibre/library/server/browse.py:779 msgid "This book has been deleted" msgstr "Książka została skasowana" -#: /home/kovid/work/calibre/src/calibre/library/server/browse.py:795 +#: /home/kovid/work/calibre/src/calibre/library/server/browse.py:865 msgid "in search" -msgstr "W poszukiwaniu" +msgstr "w poszukiwaniu" -#: /home/kovid/work/calibre/src/calibre/library/server/browse.py:797 +#: /home/kovid/work/calibre/src/calibre/library/server/browse.py:867 msgid "Matching books" msgstr "Odpowiadające książki" @@ -14426,6 +16896,17 @@ msgid "" "\n" "The OPDS interface is advertised via BonJour automatically.\n" msgstr "" +"[options]\n" +"\n" +"Uruchom serwer zawartości calibre. Serwer zawartości calibre\n" +"wystawia twoją bibliotekę calibre w internecie. Domyślny interfejs\n" +"pozwala ci przeglądać bibliotekę calibre według kategorii. Możesz także\n" +"uzyskać dostęp do interfejsu zoptymalizowany dla przeglądarek przenośnych " +"używając\n" +"/mobile i interejsu opartego na OPDS z aplikacjami do czytania używając " +"/opds.\n" +"\n" +"Interfejs OPDS jest ogłaszany automatycznie poprzez BonJour.\n" #: /home/kovid/work/calibre/src/calibre/library/server/main.py:52 msgid "Path to the library folder to serve with the content server" @@ -14441,137 +16922,156 @@ msgid "" "Specifies a restriction to be used for this invocation. This option " "overrides any per-library settings specified in the GUI" msgstr "" +"Określa ograniczenia użyte w tej inwokacji. Ta opcja nadpisuje wszystkie " +"ustawienia dla biblioteki określone w interfejsie graficznym" #: /home/kovid/work/calibre/src/calibre/library/server/main.py:62 msgid "" "Auto reload server when source code changes. May not work in all " "environments." msgstr "" +"Automatycznie przeładuj serwer gdy kod źródłowy ulegnie zmianie. Może nie " +"działać we wszystkich środowiskach." -#: /home/kovid/work/calibre/src/calibre/library/server/opds.py:126 +#: /home/kovid/work/calibre/src/calibre/library/server/opds.py:125 msgid "%d book" -msgstr "" +msgstr "książka %d" -#: /home/kovid/work/calibre/src/calibre/library/server/opds.py:150 +#: /home/kovid/work/calibre/src/calibre/library/server/opds.py:148 msgid "%d items" msgstr "%d elementów" -#: /home/kovid/work/calibre/src/calibre/library/server/opds.py:168 +#: /home/kovid/work/calibre/src/calibre/library/server/opds.py:166 msgid "RATING: %s
      " msgstr "OCENA: %s
      " -#: /home/kovid/work/calibre/src/calibre/library/server/opds.py:171 +#: /home/kovid/work/calibre/src/calibre/library/server/opds.py:169 msgid "TAGS: %s
      " msgstr "ETYKIETY: %s
      " -#: /home/kovid/work/calibre/src/calibre/library/server/opds.py:176 +#: /home/kovid/work/calibre/src/calibre/library/server/opds.py:174 msgid "SERIES: %s [%s]
      " msgstr "CYKL: %s [%s]
      " -#: /home/kovid/work/calibre/src/calibre/library/server/opds.py:269 +#: /home/kovid/work/calibre/src/calibre/library/server/opds.py:267 msgid "Books in your library" msgstr "Książki w twojej bibliotece" -#: /home/kovid/work/calibre/src/calibre/library/server/opds.py:275 +#: /home/kovid/work/calibre/src/calibre/library/server/opds.py:273 msgid "By " -msgstr "" +msgstr "autorstwa " -#: /home/kovid/work/calibre/src/calibre/library/server/opds.py:276 +#: /home/kovid/work/calibre/src/calibre/library/server/opds.py:274 msgid "Books sorted by " msgstr "Książki sortowane według " -#: /home/kovid/work/calibre/src/calibre/utils/config.py:32 +#: /home/kovid/work/calibre/src/calibre/utils/config.py:34 msgid "%sUsage%s: %s\n" msgstr "%sUżycie%s: %s\n" -#: /home/kovid/work/calibre/src/calibre/utils/config.py:76 +#: /home/kovid/work/calibre/src/calibre/utils/config.py:85 msgid "Created by " msgstr "Stworzony przez " -#: /home/kovid/work/calibre/src/calibre/utils/config.py:77 +#: /home/kovid/work/calibre/src/calibre/utils/config.py:86 msgid "" "Whenever you pass arguments to %prog that have spaces in them, enclose the " "arguments in quotation marks." msgstr "" +"Za każdym razem, gdy przekazujesz argumenty do %prog które mają spacje, " +"dołącz argumenty w cudzysłowach." -#: /home/kovid/work/calibre/src/calibre/utils/config.py:697 +#: /home/kovid/work/calibre/src/calibre/utils/config_base.py:375 msgid "Path to the database in which books are stored" msgstr "Ścieżka do bazy danych, w której książki są zlokalizowane" -#: /home/kovid/work/calibre/src/calibre/utils/config.py:699 +#: /home/kovid/work/calibre/src/calibre/utils/config_base.py:377 msgid "Pattern to guess metadata from filenames" msgstr "Szablony odgadywania metadanych z nazw plików" -#: /home/kovid/work/calibre/src/calibre/utils/config.py:701 +#: /home/kovid/work/calibre/src/calibre/utils/config_base.py:379 msgid "Access key for isbndb.com" msgstr "Klucz dostępu do isbndb.com" -#: /home/kovid/work/calibre/src/calibre/utils/config.py:703 +#: /home/kovid/work/calibre/src/calibre/utils/config_base.py:381 msgid "Default timeout for network operations (seconds)" msgstr "Domyślny czas oczekiwania dla operacji sieciowych (w sekundach)" -#: /home/kovid/work/calibre/src/calibre/utils/config.py:705 +#: /home/kovid/work/calibre/src/calibre/utils/config_base.py:383 msgid "Path to directory in which your library of books is stored" msgstr "Ścieżka do katalogu w którym przechowywana jest biblioteka książek" -#: /home/kovid/work/calibre/src/calibre/utils/config.py:707 +#: /home/kovid/work/calibre/src/calibre/utils/config_base.py:385 msgid "The language in which to display the user interface" msgstr "Język wyświetlania interfejsu użytkownika" -#: /home/kovid/work/calibre/src/calibre/utils/config.py:709 +#: /home/kovid/work/calibre/src/calibre/utils/config_base.py:387 msgid "The default output format for ebook conversions." msgstr "Domyślny format wyjściowy dla konwersji książek" -#: /home/kovid/work/calibre/src/calibre/utils/config.py:713 +#: /home/kovid/work/calibre/src/calibre/utils/config_base.py:391 msgid "Ordered list of formats to prefer for input." msgstr "Uporządkowana lista preferowanych formatów źródłowych." -#: /home/kovid/work/calibre/src/calibre/utils/config.py:715 +#: /home/kovid/work/calibre/src/calibre/utils/config_base.py:393 msgid "Read metadata from files" msgstr "Wczytaj metadane z plików" -#: /home/kovid/work/calibre/src/calibre/utils/config.py:717 -msgid "The priority of worker processes" -msgstr "Priorytet zadań" +#: /home/kovid/work/calibre/src/calibre/utils/config_base.py:395 +msgid "" +"The priority of worker processes. A higher priority means they run faster " +"and consume more resources. Most tasks like conversion/news download/adding " +"books/etc. are affected by this setting." +msgstr "" +"Priorytet procesów roboczych. Wyższy priorytet oznacza, że szybciej się " +"wykonują i pobierają więcej zasobów. To ustawienie ma wpływ na większość " +"zadań takich jak konwersja/pobieranie newsów/dodawanie książek/itd." -#: /home/kovid/work/calibre/src/calibre/utils/config.py:719 +#: /home/kovid/work/calibre/src/calibre/utils/config_base.py:400 msgid "Swap author first and last names when reading metadata" msgstr "" "Zamień miejscami imię i nazwisko autora, podczas odczytywania metadanych" -#: /home/kovid/work/calibre/src/calibre/utils/config.py:721 +#: /home/kovid/work/calibre/src/calibre/utils/config_base.py:402 msgid "Add new formats to existing book records" msgstr "Dodaj nowy format do istniejącego wpisu książki" -#: /home/kovid/work/calibre/src/calibre/utils/config.py:723 +#: /home/kovid/work/calibre/src/calibre/utils/config_base.py:404 msgid "Tags to apply to books added to the library" msgstr "Tagi które mają być dodane do książek dodawanych do biblioteki" -#: /home/kovid/work/calibre/src/calibre/utils/config.py:727 +#: /home/kovid/work/calibre/src/calibre/utils/config_base.py:408 msgid "List of named saved searches" msgstr "Lista nazwanych zapisanych wyszukań" -#: /home/kovid/work/calibre/src/calibre/utils/config.py:728 +#: /home/kovid/work/calibre/src/calibre/utils/config_base.py:409 msgid "User-created tag browser categories" msgstr "Przeglądarka tagów użytkownika" -#: /home/kovid/work/calibre/src/calibre/utils/config.py:730 +#: /home/kovid/work/calibre/src/calibre/utils/config_base.py:411 msgid "How and when calibre updates metadata on the device." msgstr "Sposób i harmonogram uaktualniania metadanych na urządzeniu." -#: /home/kovid/work/calibre/src/calibre/utils/config.py:732 +#: /home/kovid/work/calibre/src/calibre/utils/config_base.py:413 msgid "" "When searching for text without using lookup prefixes, as for example, Red " "instead of title:Red, limit the columns searched to those named below." msgstr "" +"Podczas szukania tekstu bez przedrostków, jak na przykład, Czerwony zamiast " +"title:Czerwony, ogranicz przeszukiwanie kolumn tylko do tych wskazanych " +"poniżej." -#: /home/kovid/work/calibre/src/calibre/utils/config.py:737 +#: /home/kovid/work/calibre/src/calibre/utils/config_base.py:418 msgid "" "Choose columns to be searched when not using prefixes, as for example, when " "searching for Redd instead of title:Red. Enter a list of search/lookup names " "separated by commas. Only takes effect if you set the option to limit search " "columns above." msgstr "" +"Wybeirz kolumny do przeszukiwania gdy nie używasz przedrostków, na przykład, " +"podczas szukania Czerwony zamiast titel:Czerwony. Wprowadź listę nazw " +"wyszukiwania oddzielone przecinkami. Ma zastosowanie jeśli ustawisz opcję " +"ograniczenia przeszukiwanych kolumn powyżej." #: /home/kovid/work/calibre/src/calibre/utils/formatter.py:27 msgid "failed to scan program. Invalid input {0}" @@ -14579,7 +17079,7 @@ msgstr "Nieudane skanowanie programu. Nieprawidłowe dane na wejściu {0}" #: /home/kovid/work/calibre/src/calibre/utils/formatter.py:32 msgid " near " -msgstr "" +msgstr " blisko " #: /home/kovid/work/calibre/src/calibre/utils/formatter.py:38 msgid "end of program" @@ -14599,7 +17099,7 @@ msgstr "nieznana funkcja {0}" #: /home/kovid/work/calibre/src/calibre/utils/formatter.py:126 msgid "missing closing parenthesis" -msgstr "" +msgstr "brak zamkniętego nawiasu" #: /home/kovid/work/calibre/src/calibre/utils/formatter.py:145 msgid "expression is not function or constant" @@ -14607,11 +17107,11 @@ msgstr "wyrażenie nie jest funkcją czy stałą" #: /home/kovid/work/calibre/src/calibre/utils/formatter.py:179 msgid "format: type {0} requires an integer value, got {1}" -msgstr "" +msgstr "format: typ {0} wymaga wartości całkowitej, mam {1}" #: /home/kovid/work/calibre/src/calibre/utils/formatter.py:185 msgid "format: type {0} requires a decimal (float) value, got {1}" -msgstr "" +msgstr "format: typ {0} wymaga dziesiętnej wartości (float), mam {1}" #: /home/kovid/work/calibre/src/calibre/utils/formatter.py:296 msgid "%s: unknown function" @@ -14621,57 +17121,73 @@ msgstr "%s: nieznana funkcja" msgid "No such variable " msgstr "Nie ma takiej zmiennej " -#: /home/kovid/work/calibre/src/calibre/utils/formatter_functions.py:57 +#: /home/kovid/work/calibre/src/calibre/utils/formatter_functions.py:58 msgid "No documentation provided" msgstr "Nie dostarczono dokumentacji" -#: /home/kovid/work/calibre/src/calibre/utils/formatter_functions.py:78 +#: /home/kovid/work/calibre/src/calibre/utils/formatter_functions.py:79 msgid "Exception " msgstr "Wyjątki " -#: /home/kovid/work/calibre/src/calibre/utils/formatter_functions.py:96 +#: /home/kovid/work/calibre/src/calibre/utils/formatter_functions.py:97 msgid "" "strcmp(x, y, lt, eq, gt) -- does a case-insensitive comparison of x and y as " "strings. Returns lt if x < y. Returns eq if x == y. Otherwise returns gt." msgstr "" +"strcmp(x, y, lt, eq, gt) -- dokonuje porównania x i y jako ciągów (wielkość " +"liter ma znaczenie). Zwraca lt jeśli x < y. Zwraca eq jeśli x == y. W innym " +"wypadku zwraca gt." -#: /home/kovid/work/calibre/src/calibre/utils/formatter_functions.py:111 +#: /home/kovid/work/calibre/src/calibre/utils/formatter_functions.py:112 msgid "" "cmp(x, y, lt, eq, gt) -- compares x and y after converting both to numbers. " "Returns lt if x < y. Returns eq if x == y. Otherwise returns gt." msgstr "" +"cmp(x, y, lt, eq, gt) -- porównuje x i y po konwersji obu wartości do " +"numerów. Zwraca lt jeśli x < y. Zwraca eq jeśli x == y. W innym wypadku " +"zwraca gt." -#: /home/kovid/work/calibre/src/calibre/utils/formatter_functions.py:126 +#: /home/kovid/work/calibre/src/calibre/utils/formatter_functions.py:127 msgid "" "strcat(a, b, ...) -- can take any number of arguments. Returns a string " "formed by concatenating all the arguments" msgstr "" +"strcat(a, b, ...) -- może pobrać jakąkolwiek ilość argumentów. Zwraca ciąg " +"znaków połączonych z wszystkich argumentów" -#: /home/kovid/work/calibre/src/calibre/utils/formatter_functions.py:139 +#: /home/kovid/work/calibre/src/calibre/utils/formatter_functions.py:140 msgid "" "add(x, y) -- returns x + y. Throws an exception if either x or y are not " "numbers." msgstr "" +"add(x, y) -- zwraca x + y. Wyrzuca wyjątek jeśli albo x albo y nie są " +"numerami." -#: /home/kovid/work/calibre/src/calibre/utils/formatter_functions.py:149 +#: /home/kovid/work/calibre/src/calibre/utils/formatter_functions.py:150 msgid "" "subtract(x, y) -- returns x - y. Throws an exception if either x or y are " "not numbers." msgstr "" +"subtract(x, y) -- zwraca x - y. Wyrzuca wyjątek jeśli albo x albo y nie są " +"numerami." -#: /home/kovid/work/calibre/src/calibre/utils/formatter_functions.py:159 +#: /home/kovid/work/calibre/src/calibre/utils/formatter_functions.py:160 msgid "" "multiply(x, y) -- returns x * y. Throws an exception if either x or y are " "not numbers." msgstr "" +"multiply(x, y) -- zwraca x * y. Wyrzuca wyjątek jeśli albo x albo y nie są " +"numerami." -#: /home/kovid/work/calibre/src/calibre/utils/formatter_functions.py:169 +#: /home/kovid/work/calibre/src/calibre/utils/formatter_functions.py:170 msgid "" "divide(x, y) -- returns x / y. Throws an exception if either x or y are not " "numbers." msgstr "" +"divide(x, y) -- zwraca x / y. Wyrzuca wyjątek jeśli albo x albo y nie są " +"numerami." -#: /home/kovid/work/calibre/src/calibre/utils/formatter_functions.py:179 +#: /home/kovid/work/calibre/src/calibre/utils/formatter_functions.py:180 msgid "" "template(x) -- evaluates x as a template. The evaluation is done in its own " "context, meaning that variables are not shared between the caller and the " @@ -14680,32 +17196,54 @@ msgid "" "automatically. For example, template('[[title_sort]]') will evaluate the " "template {title_sort} and return its value." msgstr "" +"template(x) -- określa x jako szablon. Określanie dokonuje się we własnym " +"kontekście, co oznacza, że zmienne nie są oduostępniane pomiędzy wywołującym " +"a określoeniem szablonu. Ponieważ znaki { and } są specjalne, musisz użyć [[ " +"dla { znaków i ]] dla } znaków; są konwertowane automatycznie. Na przykład, " +"template('[[title_sort]]') określi wzorzec {title_sort} i zwróci jego " +"wartość." -#: /home/kovid/work/calibre/src/calibre/utils/formatter_functions.py:194 +#: /home/kovid/work/calibre/src/calibre/utils/formatter_functions.py:195 msgid "" "eval(template) -- evaluates the template, passing the local variables (those " "'assign'ed to) instead of the book metadata. This permits using the " "template processor to construct complex results from local variables." msgstr "" +"eval(template) -- określa szablon, przekazując lokalne zmienne (te " +"'przypisane' do) zamiast z książki metadanych. To pozwala wykorzystać " +"procesor szablonów do konstrukcji złożonych wyników z lokalnych zmiennych." -#: /home/kovid/work/calibre/src/calibre/utils/formatter_functions.py:207 +#: /home/kovid/work/calibre/src/calibre/utils/formatter_functions.py:208 msgid "" "assign(id, val) -- assigns val to id, then returns val. id must be an " "identifier, not an expression" msgstr "" +"assign(id, val) -- przypisuje val do id, a następnie zwraca val. id musi być " +"identyfikatorem, nie wyrażeniem" -#: /home/kovid/work/calibre/src/calibre/utils/formatter_functions.py:217 +#: /home/kovid/work/calibre/src/calibre/utils/formatter_functions.py:218 msgid "" "print(a, b, ...) -- prints the arguments to standard output. Unless you " "start calibre from the command line (calibre-debug -g), the output will go " "to a black hole." msgstr "" +"print(a, b, ...) -- drukuje argumenty do standardowego profilu wynikowego. " +"Jeśli nie uruchomisz calibre z linii komend (calibre-debug -g), wynik " +"pójdzie do czarnej dziury." -#: /home/kovid/work/calibre/src/calibre/utils/formatter_functions.py:228 +#: /home/kovid/work/calibre/src/calibre/utils/formatter_functions.py:229 msgid "field(name) -- returns the metadata field named by name" -msgstr "" +msgstr "field(name) -- zwraca pole metadanych nazwane po nazwie" -#: /home/kovid/work/calibre/src/calibre/utils/formatter_functions.py:236 +#: /home/kovid/work/calibre/src/calibre/utils/formatter_functions.py:237 +msgid "" +"raw_field(name) -- returns the metadata field named by name without applying " +"any formatting." +msgstr "" +"raw_field(name) -- zwraca metadane pola nazwanego po nazwie bez zastosowania " +"jakiegokolwiek formatowania." + +#: /home/kovid/work/calibre/src/calibre/utils/formatter_functions.py:246 msgid "" "substr(str, start, end) -- returns the start'th through the end'th " "characters of str. The first character in str is the zero'th character. If " @@ -14714,8 +17252,13 @@ msgid "" "substr('12345', 1, 0) returns '2345', and substr('12345', 1, -1) returns " "'234'." msgstr "" +"substr(str, startowy, końcowy) -- zwraca startowy poprzez końcowy znak " +"ciągu. Pierwszy znak str to zerowy znak. Jeśli końcowy jest ujemny, wówczas " +"wskazuje tylko znaków licząc od prawej. Jeśli końcowy jest zerem, wówczas " +"wskazuje ostatni znak. Na przykład, substr('12345', 1, 0) zwraca '2345', a " +"substr('12345', 1, -1) zwraca '234'." -#: /home/kovid/work/calibre/src/calibre/utils/formatter_functions.py:249 +#: /home/kovid/work/calibre/src/calibre/utils/formatter_functions.py:259 msgid "" "lookup(val, pattern, field, pattern, field, ..., else_field) -- like switch, " "except the arguments are field (metadata) names, not text. The value of the " @@ -14724,25 +17267,38 @@ msgid "" "the value of some other composite field. This is extremely useful when " "constructing variable save paths" msgstr "" +"lookup(val, pattern, field, pattern, field, ..., else_field) -- tak jak " +"switch, z wyjątkiem że argumentami są nazwy pola (metadane), nie tekst. " +"Wartość odpowiedniego pola zostanie pobrana i użyta. Zwróć uwagę, złożone " +"kolumny to pola, możesz użyć tej funkcji w jednym złożonym polu, aby użyć " +"wartości z jakiegoś innego złożonego pola. Jest to nadzwyczaj użyteczne " +"podczas konstruowania zmiennych ścieżek zapisu" -#: /home/kovid/work/calibre/src/calibre/utils/formatter_functions.py:264 +#: /home/kovid/work/calibre/src/calibre/utils/formatter_functions.py:274 msgid "lookup requires either 2 or an odd number of arguments" -msgstr "" +msgstr "lookup wymaga albo 2 albo nieparzystej liczby argumentów" -#: /home/kovid/work/calibre/src/calibre/utils/formatter_functions.py:276 +#: /home/kovid/work/calibre/src/calibre/utils/formatter_functions.py:286 msgid "" "test(val, text if not empty, text if empty) -- return `text if not empty` if " "the field is not empty, otherwise return `text if empty`" msgstr "" +"test(val, tekst gdy niepusty, tekst gdy pusty) -- zwraca `tekst gdy " +"niepusty` gdy pole nie jest puste, w przeciwnym wypadku zwraca `tekst gdy " +"pusty`" -#: /home/kovid/work/calibre/src/calibre/utils/formatter_functions.py:288 +#: /home/kovid/work/calibre/src/calibre/utils/formatter_functions.py:298 msgid "" "contains(val, pattern, text if match, text if not match) -- checks if field " "contains matches for the regular expression `pattern`. Returns `text if " "match` if matches are found, otherwise it returns `text if no match`" msgstr "" +"contains(val, pattern, tekst gdy pasuje, tekst gdy nie pasuje) -- sprawdza " +"czy pole zawiera dopasowania dla regularnego wyrażenia `pattern` (wzorzec). " +"Zwraca ` tekst gdy pasuje` jeśli znajdzie dopasowania, w przeciwnym wypadku " +"zwraca `tekst gdy nie pasuje`" -#: /home/kovid/work/calibre/src/calibre/utils/formatter_functions.py:303 +#: /home/kovid/work/calibre/src/calibre/utils/formatter_functions.py:313 msgid "" "switch(val, pattern, value, pattern, value, ..., else_value) -- for each " "`pattern, value` pair, checks if the field matches the regular expression " @@ -14750,25 +17306,35 @@ msgid "" "else_value is returned. You can have as many `pattern, value` pairs as you " "want" msgstr "" +"switch(val, pattern, value, pattern, value, ..., else_value) -- dla każdej " +"pary `pattern, value`, sprawdza czy pole pasuje do regularnego wyrażnenia " +"`pattern` (wzorzec) a jeśli tak, to zwraca ta `value` (wartość). Jeśli żaden " +"wzorzec nie pasuje, wówczas zwracana jest else_value. Możesz zadeklarować " +"tyle par `pattern, value` ile chcesz" -#: /home/kovid/work/calibre/src/calibre/utils/formatter_functions.py:311 +#: /home/kovid/work/calibre/src/calibre/utils/formatter_functions.py:321 msgid "switch requires an odd number of arguments" -msgstr "" +msgstr "switch wymaga nieparzystej ilości argumentów" -#: /home/kovid/work/calibre/src/calibre/utils/formatter_functions.py:323 +#: /home/kovid/work/calibre/src/calibre/utils/formatter_functions.py:333 msgid "" "re(val, pattern, replacement) -- return the field after applying the regular " "expression. All instances of `pattern` are replaced with `replacement`. As " "in all of calibre, these are python-compatible regular expressions" msgstr "" +"re(val, wzorzec, zamiana) -- zwraca pole po zastosowaniu regularnego " +"wyrażenia. Wszystkie wypadki `wzorca` są zastąpione `zamianą`. Tak jak w " +"całym calibre, są to kompatybilne z pythonem regularne wyrażenia" -#: /home/kovid/work/calibre/src/calibre/utils/formatter_functions.py:334 +#: /home/kovid/work/calibre/src/calibre/utils/formatter_functions.py:344 msgid "" "ifempty(val, text if empty) -- return val if val is not empty, otherwise " "return `text if empty`" msgstr "" +"ifempty(val, tekst jeśli pusty) -- zwraca val jeśli val nie jest pusty, w " +"przeciwnym wypadku zwraca `tekst jeśli pusty`" -#: /home/kovid/work/calibre/src/calibre/utils/formatter_functions.py:346 +#: /home/kovid/work/calibre/src/calibre/utils/formatter_functions.py:356 msgid "" "shorten(val, left chars, middle text, right chars) -- Return a shortened " "version of the field, consisting of `left chars` characters from the " @@ -14781,16 +17347,30 @@ msgid "" "chars + the length of `middle text`, then the field will be used intact. For " "example, the title `The Dome` would not be changed." msgstr "" +"shorten(val, left chars, middle text, right chars) -- zwraca skróconą wersję " +"pola, składająca się ze znaków `left chars` z początku pola, następnie " +"`middle text`, a następnie `right chars` z końca ciągu. `Left chars` i " +"`right chars` muszą być liczbami całkowitymi. Na przykład, załóżmy, że tytuł " +"książki to `Ancient English Laws in the Times of Ivanhoe`, a ty chcesz " +"zmieścić się w przestrzeni najwyżej 15 znaków. Jeśli użyjesz " +"{title:shorten(9,-,5)}, wynikiem będzie `Ancient E-nhoe`. Jeśli długośc pola " +"jest mniejsza niż left chars + right chars + długość `middle text`, wówczas " +"pole pozostanie nienaruszone. Na przykład tytuł `The Dome` nie ulegnie " +"zmianie." -#: /home/kovid/work/calibre/src/calibre/utils/formatter_functions.py:371 +#: /home/kovid/work/calibre/src/calibre/utils/formatter_functions.py:381 msgid "" "count(val, separator) -- interprets the value as a list of items separated " "by `separator`, returning the number of items in the list. Most lists use a " "comma as the separator, but authors uses an ampersand. Examples: " "{tags:count(,)}, {authors:count(&)}" msgstr "" +"count(val, separator) -- interpretuje wartość jako listę elementów " +"oddzielonych `separatorem`, zwraca liczbę elementów w liście. Większośc list " +"używa przecinka jako separatora, ale autorzy używa znak &. Przykłady: " +"{tags:count(,)}, {authors:count(&)}" -#: /home/kovid/work/calibre/src/calibre/utils/formatter_functions.py:382 +#: /home/kovid/work/calibre/src/calibre/utils/formatter_functions.py:392 msgid "" "list_item(val, index, separator) -- interpret the value as a list of items " "separated by `separator`, returning the `index`th item. The first item is " @@ -14798,21 +17378,113 @@ msgid "" "If the item is not in the list, then the empty value is returned. The " "separator has the same meaning as in the count function." msgstr "" +"list_item(val, index, separator) -- interpretuje wartość jako listę " +"oddzielaną `separatorem`, zwraca `index` elementu. Pierwszy element to numer " +"zero. Ostatni element może zostać zwrócony przy użyciu `list_item(-" +"1,separator)`. Jeśli element nie jest w liście, wówczas zwrócona zostaje " +"pusta wartość. Separator ma takie samo znaczenie jak w funkcji count." -#: /home/kovid/work/calibre/src/calibre/utils/formatter_functions.py:402 +#: /home/kovid/work/calibre/src/calibre/utils/formatter_functions.py:412 +msgid "" +"select(val, key) -- interpret the value as a comma-separated list of items, " +"with the items being \"id:value\". Find the pair with theid equal to key, " +"and return the corresponding value." +msgstr "" +"select(val, key) -- interpretuje wartość jako oddzielaną przecinkami listę " +"elementów, z elementami będącymi \"id:value\". Znajduje parę z id równym " +"key, a potem zwraca odpowiadającą jej wartość." + +#: /home/kovid/work/calibre/src/calibre/utils/formatter_functions.py:429 +msgid "" +"sublist(val, start_index, end_index, separator) -- interpret the value as a " +"list of items separated by `separator`, returning a new list made from the " +"`start_index`th to the `end_index`th item. The first item is number zero. If " +"an index is negative, then it counts from the end of the list. As a special " +"case, an end_index of zero is assumed to be the length of the list. Examples " +"using basic template mode and assuming that the tags column (which is comma-" +"separated) contains \"A, B, C\": {tags:sublist(0,1,\\,)} returns \"A\". " +"{tags:sublist(-1,0,\\,)} returns \"C\". {tags:sublist(0,-1,\\,)} returns " +"\"A, B\"." +msgstr "" +"sublist(val, start_index, end_index, separator) -- interpretuje wartość jako " +"listę elementów oddzielonych `separatorem`, zwracając nową listę stworzoną " +"od elementu `start_index` do elementu `end_index`. Pierwszy element jest " +"numerem zero. Jeśli indeks jest ujemny, wówczas liczy od końca listy. W " +"szczególnym wypadku, end_index zera zakłada się, że jest długością listy. " +"Przykłady wykorzystujące prosty tryb szablonu przy założeniu, że kolumny " +"etykiet (które sa oddzielane przecinkiem) zawierają \"A, B, C\": " +"{tags:sublist(0,1,\\,)} zwraca \"A\". {tags:sublist(-1,0,\\,)} zwraca \"C\". " +"{tags:sublist(0,-1,\\,)} zwraca \"A, B\"." + +#: /home/kovid/work/calibre/src/calibre/utils/formatter_functions.py:458 +msgid "" +"subitems(val, start_index, end_index) -- This function is used to break " +"apart lists of items such as genres. It interprets the value as a comma-" +"separated list of items, where each item is a period-separated list. Returns " +"a new list made by first finding all the period-separated items, then for " +"each such item extracting the start_index`th to the `end_index`th " +"components, then combining the results back together. The first component in " +"a period-separated list has an index of zero. If an index is negative, then " +"it counts from the end of the list. As a special case, an end_index of zero " +"is assumed to be the length of the list. Example using basic template mode " +"and assuming a #genre value of \"A.B.C\": {#genre:subitems(0,1)} returns " +"\"A\". {#genre:subitems(0,2)} returns \"A.B\". {#genre:subitems(1,0)} " +"returns \"B.C\". Assuming a #genre value of \"A.B.C, D.E.F\", " +"{#genre:subitems(0,1)} returns \"A, D\". {#genre:subitems(0,2)} returns " +"\"A.B, D.E\"" +msgstr "" +"subitems(val, start_index, end_index) -- Ta funkcja używana jest do " +"rozdzielania listy elementów takich jak gatunki. Interpretuje wartość jako " +"oddzielaną przecinkami listę elementów, gdzie każdy element jest listą " +"oddzielaną kropką. Zwraca nową listę stworzoną przez pierwsze znalezienie " +"wszystkich elementów oddzielanych kropką, wtedy dla każdego takiego elementu " +"wyciąga komponenty od `start_index` do `end_index`, potem łączy wyniki z " +"powrotem w całość. Pierwszy komponent w liście oddzielanej kropkami ma " +"indeks zero. Jeśli indeks jest ujemny, wówczas liczy od końca listy. W " +"szczególnym przypadku, end_index zera zakłada się, że jest długością " +"listy.Przykłady wykorzystujące prosty tryb szablonu przy założeniu, że " +"wartość #genre z \"A.B.C\": {#genre:subitems(0,1)} zwraca \"A\". " +"{#genre:subitems(0,2)} zwraca \"A.B\". {#genre:subitems(1,0)} zwraca " +"\"B.C\". Zakłądając, że wartość #genre z \"A.B.C, D.E.F\", " +"{#genre:subitems(0,1)} zwraca \"A, D\". {#genre:subitems(0,2)} zwraca \"A.B, " +"D.E\"" + +#: /home/kovid/work/calibre/src/calibre/utils/formatter_functions.py:495 +msgid "" +"format_date(val, format_string) -- format the value, which must be a date " +"field, using the format_string, returning a string. The formatting codes " +"are: d : the day as number without a leading zero (1 to 31) dd : the " +"day as number with a leading zero (01 to 31) ddd : the abbreviated " +"localized day name (e.g. \"Mon\" to \"Sun\"). dddd : the long localized day " +"name (e.g. \"Monday\" to \"Sunday\"). M : the month as number without a " +"leading zero (1 to 12). MM : the month as number with a leading zero (01 " +"to 12) MMM : the abbreviated localized month name (e.g. \"Jan\" to " +"\"Dec\"). MMMM : the long localized month name (e.g. \"January\" to " +"\"December\"). yy : the year as two digit number (00 to 99). yyyy : the " +"year as four digit number. iso : the date with time and timezone. Must be " +"the only format present" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/utils/formatter_functions.py:523 msgid "uppercase(val) -- return value of the field in upper case" -msgstr "" +msgstr "uppercase(val) -- zwraca wartość pola w dużych literach" -#: /home/kovid/work/calibre/src/calibre/utils/formatter_functions.py:410 +#: /home/kovid/work/calibre/src/calibre/utils/formatter_functions.py:531 msgid "lowercase(val) -- return value of the field in lower case" -msgstr "" +msgstr "lowercase(val) -- zwraca wartość pola w małych literach" -#: /home/kovid/work/calibre/src/calibre/utils/formatter_functions.py:418 +#: /home/kovid/work/calibre/src/calibre/utils/formatter_functions.py:539 msgid "titlecase(val) -- return value of the field in title case" msgstr "" +"titlecase(val) -- zwraca wartość pola w tytulikach (każdy wyraz z wielkiej " +"litery)" -#: /home/kovid/work/calibre/src/calibre/utils/formatter_functions.py:426 +#: /home/kovid/work/calibre/src/calibre/utils/formatter_functions.py:547 msgid "capitalize(val) -- return value of the field capitalized" +msgstr "capitalize(val) -- zwraca wartość pola napisaną dużymi literami" + +#: /home/kovid/work/calibre/src/calibre/utils/formatter_functions.py:555 +msgid "booksize() -- return value of the field capitalized" msgstr "" #: /home/kovid/work/calibre/src/calibre/utils/ipc/job.py:43 @@ -14837,7 +17509,7 @@ msgstr "brazylijsko-portugalski" #: /home/kovid/work/calibre/src/calibre/utils/localization.py:96 msgid "English (UK)" -msgstr "Angielski (Wielka Brytania)" +msgstr "angielski (Wielka Brytania)" #: /home/kovid/work/calibre/src/calibre/utils/localization.py:97 msgid "Simplified Chinese" @@ -14857,63 +17529,63 @@ msgstr "angielski" #: /home/kovid/work/calibre/src/calibre/utils/localization.py:101 msgid "English (Australia)" -msgstr "Angielski (Australia)" +msgstr "angielski (Australia)" #: /home/kovid/work/calibre/src/calibre/utils/localization.py:102 msgid "English (New Zealand)" -msgstr "Angielski (Nowa Zelandia)" +msgstr "angielski (Nowa Zelandia)" #: /home/kovid/work/calibre/src/calibre/utils/localization.py:103 msgid "English (Canada)" -msgstr "Angielski (Kanada)" +msgstr "angielski (Kanada)" #: /home/kovid/work/calibre/src/calibre/utils/localization.py:104 msgid "English (India)" -msgstr "Angielski (Indie)" +msgstr "angielski (Indie)" #: /home/kovid/work/calibre/src/calibre/utils/localization.py:105 msgid "English (Thailand)" -msgstr "Angielski (Tajlandia)" +msgstr "angielski (Tajlandia)" #: /home/kovid/work/calibre/src/calibre/utils/localization.py:106 msgid "English (Cyprus)" -msgstr "Angielski (Cypr)" +msgstr "angielski (Cypr)" #: /home/kovid/work/calibre/src/calibre/utils/localization.py:107 msgid "English (Czechoslovakia)" -msgstr "" +msgstr "angielski (Czechosłowacja)" #: /home/kovid/work/calibre/src/calibre/utils/localization.py:108 msgid "English (Pakistan)" -msgstr "Angielski (Pakistan)" +msgstr "angielski (Pakistan)" #: /home/kovid/work/calibre/src/calibre/utils/localization.py:109 msgid "English (Croatia)" -msgstr "" +msgstr "angielski (Chorwacja)" #: /home/kovid/work/calibre/src/calibre/utils/localization.py:110 msgid "English (Indonesia)" -msgstr "" +msgstr "angielski (Indonezja)" #: /home/kovid/work/calibre/src/calibre/utils/localization.py:111 msgid "English (Israel)" -msgstr "Angielski (Izrael)" +msgstr "angielski (Izrael)" #: /home/kovid/work/calibre/src/calibre/utils/localization.py:112 msgid "English (Singapore)" -msgstr "Angielski (Singapur)" +msgstr "angielski (Singapur)" #: /home/kovid/work/calibre/src/calibre/utils/localization.py:113 msgid "English (Yemen)" -msgstr "Angielski (Jemen)" +msgstr "angielski (Jemen)" #: /home/kovid/work/calibre/src/calibre/utils/localization.py:114 msgid "English (Ireland)" -msgstr "Angielski (Irlandia)" +msgstr "angielski (Irlandia)" #: /home/kovid/work/calibre/src/calibre/utils/localization.py:115 msgid "English (China)" -msgstr "Angielski (Chiny)" +msgstr "angielski (Chiny)" #: /home/kovid/work/calibre/src/calibre/utils/localization.py:116 msgid "Spanish (Paraguay)" @@ -14921,67 +17593,67 @@ msgstr "hiszpański (Paragwaj)" #: /home/kovid/work/calibre/src/calibre/utils/localization.py:117 msgid "Spanish (Uruguay)" -msgstr "" +msgstr "hiszpański (Urugwaj)" #: /home/kovid/work/calibre/src/calibre/utils/localization.py:118 msgid "Spanish (Argentina)" -msgstr "" +msgstr "hiszpański (Argentyna)" #: /home/kovid/work/calibre/src/calibre/utils/localization.py:119 msgid "Spanish (Mexico)" -msgstr "" +msgstr "hiszpański (Meksyk)" #: /home/kovid/work/calibre/src/calibre/utils/localization.py:120 msgid "Spanish (Cuba)" -msgstr "" +msgstr "hiszpański (Kuba)" #: /home/kovid/work/calibre/src/calibre/utils/localization.py:121 msgid "Spanish (Chile)" -msgstr "" +msgstr "hiszpański (Chile)" #: /home/kovid/work/calibre/src/calibre/utils/localization.py:122 msgid "Spanish (Ecuador)" -msgstr "" +msgstr "hiszpański (Ekwador)" #: /home/kovid/work/calibre/src/calibre/utils/localization.py:123 msgid "Spanish (Honduras)" -msgstr "" +msgstr "hiszpański (Honduras)" #: /home/kovid/work/calibre/src/calibre/utils/localization.py:124 msgid "Spanish (Venezuela)" -msgstr "" +msgstr "Hiszpański (Wenezuela)" #: /home/kovid/work/calibre/src/calibre/utils/localization.py:125 msgid "Spanish (Bolivia)" -msgstr "" +msgstr "hiszpański (Boliwia)" #: /home/kovid/work/calibre/src/calibre/utils/localization.py:126 msgid "Spanish (Nicaragua)" -msgstr "" +msgstr "Hiszpański (Nikaragua)" #: /home/kovid/work/calibre/src/calibre/utils/localization.py:127 msgid "German (AT)" -msgstr "Niemiecki (Austria)" +msgstr "niemiecki (Austria)" #: /home/kovid/work/calibre/src/calibre/utils/localization.py:128 msgid "French (BE)" -msgstr "" +msgstr "francuski (Belgia)" #: /home/kovid/work/calibre/src/calibre/utils/localization.py:129 msgid "Dutch (NL)" -msgstr "Niderlandzki (Holandia)" +msgstr "niderlandzki (Holandia)" #: /home/kovid/work/calibre/src/calibre/utils/localization.py:130 msgid "Dutch (BE)" -msgstr "Niderlandzi (Belgia)" +msgstr "niderlandzki (Belgia)" #: /home/kovid/work/calibre/src/calibre/utils/pyconsole/console.py:56 msgid "Choose theme (needs restart)" -msgstr "" +msgstr "Wybierz temat (wymaga ponownego uruchomienia)" #: /home/kovid/work/calibre/src/calibre/utils/pyconsole/console.py:109 msgid "ERROR: Unhandled exception" -msgstr "BŁĄD: Nieobsłuzony wyjątek" +msgstr "BŁĄD: Nieobsłużony wyjątek" #: /home/kovid/work/calibre/src/calibre/utils/pyconsole/console.py:188 msgid "No interpreter" @@ -14989,21 +17661,23 @@ msgstr "Brak interpretera" #: /home/kovid/work/calibre/src/calibre/utils/pyconsole/console.py:189 msgid "No active interpreter found. Try restarting the console" -msgstr "" +msgstr "Brak aktywnego interpretera. Spróbuj zrestartować konsolę" #: /home/kovid/work/calibre/src/calibre/utils/pyconsole/console.py:203 msgid "Interpreter died" -msgstr "" +msgstr "Interpreter umarł" #: /home/kovid/work/calibre/src/calibre/utils/pyconsole/console.py:204 msgid "" "Interpreter dies while excuting a command. To see the command, click Show " "details" msgstr "" +"Interpreter umiera w momencie wykonywania komendy. Aby zobaczyć komendę, " +"kliknij w Pokaż szczegóły" #: /home/kovid/work/calibre/src/calibre/utils/pyconsole/main.py:20 msgid "Welcome to" -msgstr "" +msgstr "Witaj w" #: /home/kovid/work/calibre/src/calibre/utils/pyconsole/main.py:41 msgid " console " @@ -15011,15 +17685,15 @@ msgstr " konsola " #: /home/kovid/work/calibre/src/calibre/utils/pyconsole/main.py:51 msgid "Code is running" -msgstr "" +msgstr "Trwa wykonywanie kodu" #: /home/kovid/work/calibre/src/calibre/utils/pyconsole/main.py:58 msgid "Restart console" -msgstr "Restartuj konsole" +msgstr "Uruchom ponownie konsolę" #: /home/kovid/work/calibre/src/calibre/utils/sftp.py:53 msgid "URL must have the scheme sftp" -msgstr "" +msgstr "URL musi mieć schemat sftp" #: /home/kovid/work/calibre/src/calibre/utils/sftp.py:57 msgid "host must be of the form user@hostname" @@ -15031,24 +17705,24 @@ msgstr "Nie udało się wynegocjować sesji SSH: " #: /home/kovid/work/calibre/src/calibre/utils/sftp.py:71 msgid "Failed to authenticate with server: %s" -msgstr "Nie mozna było uwierzytelnić z serwerem: %s" +msgstr "Nie można było uwierzytelnić z serwerem: %s" #: /home/kovid/work/calibre/src/calibre/utils/smtp.py:249 msgid "Control email delivery" msgstr "Kontroluj dostarczanie poczty email" -#: /home/kovid/work/calibre/src/calibre/web/feeds/__init__.py:119 +#: /home/kovid/work/calibre/src/calibre/web/feeds/__init__.py:120 msgid "Unknown section" msgstr "Nieznana sekcja" -#: /home/kovid/work/calibre/src/calibre/web/feeds/__init__.py:141 +#: /home/kovid/work/calibre/src/calibre/web/feeds/__init__.py:142 msgid "Unknown feed" msgstr "Nieznany strumień" -#: /home/kovid/work/calibre/src/calibre/web/feeds/__init__.py:159 -#: /home/kovid/work/calibre/src/calibre/web/feeds/__init__.py:186 +#: /home/kovid/work/calibre/src/calibre/web/feeds/__init__.py:160 +#: /home/kovid/work/calibre/src/calibre/web/feeds/__init__.py:187 msgid "Untitled article" -msgstr "Artukył bez tytułu" +msgstr "Artykuł bez tytułu" #: /home/kovid/work/calibre/src/calibre/web/feeds/input.py:22 msgid "Download periodical content from the internet" @@ -15080,120 +17754,122 @@ msgstr "Nie pobieraj najnowszych wersji wbudowanych źródeł z serwera calibre" msgid "Unknown News Source" msgstr "Nieznane źródło informacji" -#: /home/kovid/work/calibre/src/calibre/web/feeds/news.py:629 +#: /home/kovid/work/calibre/src/calibre/web/feeds/news.py:630 msgid "The \"%s\" recipe needs a username and password." msgstr "Źródło \"%s\" wymaga podania nazwy użytkownika i hasła." -#: /home/kovid/work/calibre/src/calibre/web/feeds/news.py:735 +#: /home/kovid/work/calibre/src/calibre/web/feeds/news.py:736 msgid "Download finished" msgstr "Pobranie zakończone" -#: /home/kovid/work/calibre/src/calibre/web/feeds/news.py:737 +#: /home/kovid/work/calibre/src/calibre/web/feeds/news.py:738 msgid "Failed to download the following articles:" msgstr "Nie udało się pobrać następujących artykułów:" -#: /home/kovid/work/calibre/src/calibre/web/feeds/news.py:743 +#: /home/kovid/work/calibre/src/calibre/web/feeds/news.py:744 msgid "Failed to download parts of the following articles:" msgstr "Nie udało się pobrać części następujących artykułów:" -#: /home/kovid/work/calibre/src/calibre/web/feeds/news.py:745 +#: /home/kovid/work/calibre/src/calibre/web/feeds/news.py:746 msgid " from " msgstr " z " -#: /home/kovid/work/calibre/src/calibre/web/feeds/news.py:747 +#: /home/kovid/work/calibre/src/calibre/web/feeds/news.py:748 msgid "\tFailed links:" msgstr "\tNieprawidłowe linki:" -#: /home/kovid/work/calibre/src/calibre/web/feeds/news.py:842 +#: /home/kovid/work/calibre/src/calibre/web/feeds/news.py:843 msgid "Could not fetch article." -msgstr "" +msgstr "Nie udało się pobrać artykułu." -#: /home/kovid/work/calibre/src/calibre/web/feeds/news.py:844 +#: /home/kovid/work/calibre/src/calibre/web/feeds/news.py:845 msgid "The debug traceback is available earlier in this log" -msgstr "" +msgstr "Informacje dotyczące debugowania są dostępne wcześniej w tym lgou" -#: /home/kovid/work/calibre/src/calibre/web/feeds/news.py:846 +#: /home/kovid/work/calibre/src/calibre/web/feeds/news.py:847 msgid "Run with -vv to see the reason" -msgstr "" +msgstr "Uruchom z opcją -vv aby zobaczyć powód" -#: /home/kovid/work/calibre/src/calibre/web/feeds/news.py:869 +#: /home/kovid/work/calibre/src/calibre/web/feeds/news.py:870 msgid "Fetching feeds..." msgstr "Pobieram strumienie..." -#: /home/kovid/work/calibre/src/calibre/web/feeds/news.py:874 +#: /home/kovid/work/calibre/src/calibre/web/feeds/news.py:875 msgid "Got feeds from index page" msgstr "Pobierz strumienie ze strony głównej" -#: /home/kovid/work/calibre/src/calibre/web/feeds/news.py:883 +#: /home/kovid/work/calibre/src/calibre/web/feeds/news.py:884 msgid "Trying to download cover..." msgstr "Próbuję pobrać okładkę..." -#: /home/kovid/work/calibre/src/calibre/web/feeds/news.py:885 +#: /home/kovid/work/calibre/src/calibre/web/feeds/news.py:886 msgid "Generating masthead..." -msgstr "" +msgstr "Generuje nagłówek..." -#: /home/kovid/work/calibre/src/calibre/web/feeds/news.py:965 +#: /home/kovid/work/calibre/src/calibre/web/feeds/news.py:966 msgid "Starting download [%d thread(s)]..." msgstr "Rozpoczynam pobieranie [%d wątek(ków)]..." -#: /home/kovid/work/calibre/src/calibre/web/feeds/news.py:981 +#: /home/kovid/work/calibre/src/calibre/web/feeds/news.py:982 msgid "Feeds downloaded to %s" msgstr "Strumienie pobrano do %s" -#: /home/kovid/work/calibre/src/calibre/web/feeds/news.py:990 +#: /home/kovid/work/calibre/src/calibre/web/feeds/news.py:991 msgid "Could not download cover: %s" msgstr "Nie można pobrać okładki: %s" -#: /home/kovid/work/calibre/src/calibre/web/feeds/news.py:999 +#: /home/kovid/work/calibre/src/calibre/web/feeds/news.py:1000 msgid "Downloading cover from %s" msgstr "Pobieranie okładki z %s" -#: /home/kovid/work/calibre/src/calibre/web/feeds/news.py:1045 +#: /home/kovid/work/calibre/src/calibre/web/feeds/news.py:1046 msgid "Masthead image downloaded" -msgstr "" +msgstr "Obrazek nagłówka pobrany" -#: /home/kovid/work/calibre/src/calibre/web/feeds/news.py:1213 +#: /home/kovid/work/calibre/src/calibre/web/feeds/news.py:1214 msgid "Untitled Article" msgstr "Artykuł bez tytułu" -#: /home/kovid/work/calibre/src/calibre/web/feeds/news.py:1284 +#: /home/kovid/work/calibre/src/calibre/web/feeds/news.py:1285 msgid "Article downloaded: %s" msgstr "Artykuł pobrany: %s" -#: /home/kovid/work/calibre/src/calibre/web/feeds/news.py:1295 +#: /home/kovid/work/calibre/src/calibre/web/feeds/news.py:1296 msgid "Article download failed: %s" msgstr "Pobieranie artykułu nie powiodło się: %s" -#: /home/kovid/work/calibre/src/calibre/web/feeds/news.py:1312 +#: /home/kovid/work/calibre/src/calibre/web/feeds/news.py:1313 msgid "Fetching feed" msgstr "Pobieram strumień" -#: /home/kovid/work/calibre/src/calibre/web/feeds/news.py:1459 +#: /home/kovid/work/calibre/src/calibre/web/feeds/news.py:1460 msgid "" "Failed to log in, check your username and password for the calibre " "Periodicals service." msgstr "" +"Nie udało się zalogować, sprawdź nazwę użytkownika i hasło do usługi " +"czasopisma calibre." -#: /home/kovid/work/calibre/src/calibre/web/feeds/news.py:1474 +#: /home/kovid/work/calibre/src/calibre/web/feeds/news.py:1475 msgid "" "You do not have permission to download this issue. Either your subscription " "has expired or you have exceeded the maximum allowed downloads for today." msgstr "" -"Nie masz uprawnień do pobrania tego wydania. Albo Twoja subskrypcja wygasła, " +"Nie masz uprawnień do pobrania tego wydania. Albo twoja subskrypcja wygasła, " "albo przekroczyłeś maksymalną dzienną ilość pobrań." -#: /home/kovid/work/calibre/src/calibre/web/feeds/recipes/collection.py:47 +#: /home/kovid/work/calibre/src/calibre/web/feeds/recipes/collection.py:46 msgid "You" msgstr "Ty" -#: /home/kovid/work/calibre/src/calibre/web/feeds/recipes/model.py:73 -#: /home/kovid/work/calibre/src/calibre/web/feeds/recipes/model.py:82 -#: /home/kovid/work/calibre/src/calibre/web/feeds/recipes/model.py:194 +#: /home/kovid/work/calibre/src/calibre/web/feeds/recipes/model.py:75 +#: /home/kovid/work/calibre/src/calibre/web/feeds/recipes/model.py:84 +#: /home/kovid/work/calibre/src/calibre/web/feeds/recipes/model.py:202 msgid "Scheduled" msgstr "Zaplanowano" -#: /home/kovid/work/calibre/src/calibre/web/feeds/recipes/model.py:84 -#: /home/kovid/work/calibre/src/calibre/web/feeds/recipes/model.py:195 +#: /home/kovid/work/calibre/src/calibre/web/feeds/recipes/model.py:86 +#: /home/kovid/work/calibre/src/calibre/web/feeds/recipes/model.py:203 msgid "Custom" msgstr "Dostosuj" @@ -15211,7 +17887,7 @@ msgstr "Poprzednia sekcja" #: /home/kovid/work/calibre/src/calibre/web/feeds/templates.py:214 msgid "Section Menu" -msgstr "" +msgstr "Menu Sekcji" #: /home/kovid/work/calibre/src/calibre/web/feeds/templates.py:217 msgid "Main Menu" @@ -15252,6 +17928,8 @@ msgid "" "Maximum number of levels to recurse i.e. depth of links to follow. Default " "%default" msgstr "" +"Maksymalna ilość poziomów do rekursji np. najdalsza ilość linków do " +"śledzenia. Domyślnie %default" #: /home/kovid/work/calibre/src/calibre/web/fetch/simple.py:488 msgid "" @@ -15295,11 +17973,564 @@ msgid "" "a link, it will be ignored.By default, no links are ignored. If both filter " "regexp and match regexp are specified, then filter regexp is applied first." msgstr "" +"Jakikolwiek link który pasuje do tego regularnego wyrażenia zostanie " +"zignorowany. Ta opcja może być określona wiele razy, w takim wypadku dopóki " +"jakiekolwiek regexp pasuje do linka, zostanie zignorowane. Domyślnie żadne " +"linki nie są ignorowane. Jeśli oba filtry regesp i match regexp są " +"okreslone, wówczas filtr regexp jest wpierw stosowany." #: /home/kovid/work/calibre/src/calibre/web/fetch/simple.py:498 msgid "Do not download CSS stylesheets." msgstr "Nie pobieraj arkuszy styli CSS." +#: /home/kovid/work/calibre/resources/default_tweaks.py:12 +msgid "Auto increment series index" +msgstr "" + +#: /home/kovid/work/calibre/resources/default_tweaks.py:13 +msgid "" +"The algorithm used to assign a new book in an existing series a series " +"number.\n" +"New series numbers assigned using this tweak are always integer values, " +"except\n" +"if a constant non-integer is specified.\n" +"Possible values are:\n" +"next - First available integer larger than the largest existing number\n" +"first_free - First available integer larger than 0\n" +"next_free - First available integer larger than the smallest existing " +"number\n" +"last_free - First available integer smaller than the largest existing " +"number\n" +"Return largest existing + 1 if no free number is found\n" +"const - Assign the number 1 always\n" +"a number - Assign that number always. The number is not in quotes. Note " +"that\n" +"0.0 can be used here.\n" +"Examples:\n" +"series_index_auto_increment = 'next'\n" +"series_index_auto_increment = 'next_free'\n" +"series_index_auto_increment = 16.5" +msgstr "" + +#: /home/kovid/work/calibre/resources/default_tweaks.py:31 +msgid "Add separator after completing an author name" +msgstr "" + +#: /home/kovid/work/calibre/resources/default_tweaks.py:32 +msgid "" +"Should the completion separator be append\n" +"to the end of the completed text to\n" +"automatically begin a new completion operation\n" +"for authors.\n" +"Can be either True or False" +msgstr "" + +#: /home/kovid/work/calibre/resources/default_tweaks.py:40 +msgid "Author sort name algorithm" +msgstr "" + +#: /home/kovid/work/calibre/resources/default_tweaks.py:41 +msgid "" +"The algorithm used to copy author to author_sort\n" +"Possible values are:\n" +"invert: use \"fn ln\" -> \"ln, fn\"\n" +"copy : copy author to author_sort without modification\n" +"comma : use 'copy' if there is a ',' in the name, otherwise use 'invert'\n" +"nocomma : \"fn ln\" -> \"ln fn\" (without the comma)\n" +"When this tweak is changed, the author_sort values stored with each author\n" +"must be recomputed by right-clicking on an author in the left-hand tags " +"pane,\n" +"selecting 'manage authors', and pressing 'Recalculate all author sort " +"values'.\n" +"The author name suffixes are words that are ignored when they occur at the\n" +"end of an author name. The case of the suffix is ignored and trailing\n" +"periods are automatically handled." +msgstr "" + +#: /home/kovid/work/calibre/resources/default_tweaks.py:58 +msgid "Use author sort in Tag Browser" +msgstr "" + +#: /home/kovid/work/calibre/resources/default_tweaks.py:59 +msgid "" +"Set which author field to display in the tags pane (the list of authors,\n" +"series, publishers etc on the left hand side). The choices are author and\n" +"author_sort. This tweak affects only what is displayed under the authors\n" +"category in the tags pane and content server. Please note that if you set " +"this\n" +"to author_sort, it is very possible to see duplicate names in the list " +"because\n" +"although it is guaranteed that author names are unique, there is no such\n" +"guarantee for author_sort values. Showing duplicates won't break anything, " +"but\n" +"it could lead to some confusion. When using 'author_sort', the tooltip will\n" +"show the author's name.\n" +"Examples:\n" +"categories_use_field_for_author_name = 'author'\n" +"categories_use_field_for_author_name = 'author_sort'" +msgstr "" + +#: /home/kovid/work/calibre/resources/default_tweaks.py:73 +msgid "Control partitioning of Tag Browser" +msgstr "" + +#: /home/kovid/work/calibre/resources/default_tweaks.py:74 +msgid "" +"When partitioning the tags browser, the format of the subcategory label is\n" +"controlled by a template: categories_collapsed_name_template if sorting by\n" +"name, categories_collapsed_rating_template if sorting by average rating, " +"and\n" +"categories_collapsed_popularity_template if sorting by popularity. There " +"are\n" +"two variables available to the template: first and last. The variable " +"'first'\n" +"is the initial item in the subcategory, and the variable 'last' is the " +"final\n" +"item in the subcategory. Both variables are 'objects'; they each have " +"multiple\n" +"values that are obtained by using a suffix. For example, first.name for an\n" +"author category will be the name of the author. The sub-values available " +"are:\n" +"name: the printable name of the item\n" +"count: the number of books that references this item\n" +"avg_rating: the average rating of all the books referencing this item\n" +"sort: the sort value. For authors, this is the author_sort for that author\n" +"category: the category (e.g., authors, series) that the item is in.\n" +"Note that the \"r'\" in front of the { is necessary if there are " +"backslashes\n" +"(\\ characters) in the template. It doesn't hurt anything to leave it there\n" +"even if there aren't any backslashes." +msgstr "" + +#: /home/kovid/work/calibre/resources/default_tweaks.py:96 +msgid "Specify columns to sort the booklist by on startup" +msgstr "" + +#: /home/kovid/work/calibre/resources/default_tweaks.py:97 +msgid "" +"Provide a set of columns to be sorted on when calibre starts\n" +"The argument is None if saved sort history is to be used\n" +"otherwise it is a list of column,order pairs. Column is the\n" +"lookup/search name, found using the tooltip for the column\n" +"Order is 0 for ascending, 1 for descending\n" +"For example, set it to [('authors',0),('title',0)] to sort by\n" +"title within authors." +msgstr "" + +#: /home/kovid/work/calibre/resources/default_tweaks.py:106 +msgid "Control how dates are displayed" +msgstr "" + +#: /home/kovid/work/calibre/resources/default_tweaks.py:107 +msgid "" +"Format to be used for publication date and the timestamp (date).\n" +"A string controlling how the publication date is displayed in the GUI\n" +"d the day as number without a leading zero (1 to 31)\n" +"dd the day as number with a leading zero (01 to 31)\n" +"ddd the abbreviated localized day name (e.g. 'Mon' to 'Sun').\n" +"dddd the long localized day name (e.g. 'Monday' to 'Qt::Sunday').\n" +"M the month as number without a leading zero (1-12)\n" +"MM the month as number with a leading zero (01-12)\n" +"MMM the abbreviated localized month name (e.g. 'Jan' to 'Dec').\n" +"MMMM the long localized month name (e.g. 'January' to 'December').\n" +"yy the year as two digit number (00-99)\n" +"yyyy the year as four digit number\n" +"For example, given the date of 9 Jan 2010, the following formats show\n" +"MMM yyyy ==> Jan 2010 yyyy ==> 2010 dd MMM yyyy ==> 09 Jan 2010\n" +"MM/yyyy ==> 01/2010 d/M/yy ==> 9/1/10 yy ==> 10\n" +"publication default if not set: MMM yyyy\n" +"timestamp default if not set: dd MMM yyyy" +msgstr "" + +#: /home/kovid/work/calibre/resources/default_tweaks.py:128 +msgid "Control sorting of titles and series in the library display" +msgstr "" + +#: /home/kovid/work/calibre/resources/default_tweaks.py:129 +msgid "" +"Control title and series sorting in the library view. If set to\n" +"'library_order', the title sort field will be used instead of the title.\n" +"Unless you have manually edited the title sort field, leading articles such " +"as\n" +"The and A will be ignored. If set to 'strictly_alphabetic', the titles will " +"be\n" +"sorted as-is (sort by title instead of title sort). For example, with\n" +"library_order, The Client will sort under 'C'. With strictly_alphabetic, " +"the\n" +"book will sort under 'T'.\n" +"This flag affects Calibre's library display. It has no effect on devices. " +"In\n" +"addition, titles for books added before changing the flag will retain their\n" +"order until the title is edited. Double-clicking on a title and hitting " +"return\n" +"without changing anything is sufficient to change the sort." +msgstr "" + +#: /home/kovid/work/calibre/resources/default_tweaks.py:142 +msgid "Control formatting of title and series when used in templates" +msgstr "" + +#: /home/kovid/work/calibre/resources/default_tweaks.py:143 +msgid "" +"Control how title and series names are formatted when saving to " +"disk/sending\n" +"to device. The behavior depends on the field being processed. If processing\n" +"title, then if this tweak is set to 'library_order', the title will be\n" +"replaced with title_sort. If it is set to 'strictly_alphabetic', then the\n" +"title will not be changed. If processing series, then if set to\n" +"'library_order', articles such as 'The' and 'An' will be moved to the end. " +"If\n" +"set to 'strictly_alphabetic', the series will be sent without change.\n" +"For example, if the tweak is set to library_order, \"The Lord of the " +"Rings\"\n" +"will become \"Lord of the Rings, The\". If the tweak is set to\n" +"strictly_alphabetic, it would remain \"The Lord of the Rings\"." +msgstr "" + +#: /home/kovid/work/calibre/resources/default_tweaks.py:155 +msgid "Set the list of words considered to be \"articles\" for sort strings" +msgstr "" + +#: /home/kovid/work/calibre/resources/default_tweaks.py:156 +msgid "" +"Set the list of words that are to be considered 'articles' when computing " +"the\n" +"title sort strings. The list is a regular expression, with the articles\n" +"separated by 'or' bars. Comparisons are case insensitive, and that cannot " +"be\n" +"changed. Changes to this tweak won't have an effect until the book is " +"modified\n" +"in some way. If you enter an invalid pattern, it is silently ignored.\n" +"To disable use the expression: '^$'\n" +"Default: '^(A|The|An)\\s+'" +msgstr "" + +#: /home/kovid/work/calibre/resources/default_tweaks.py:165 +msgid "Specify a folder calibre should connect to at startup" +msgstr "" + +#: /home/kovid/work/calibre/resources/default_tweaks.py:166 +msgid "" +"Specify a folder that calibre should connect to at startup using\n" +"connect_to_folder. This must be a full path to the folder. If the folder " +"does\n" +"not exist when calibre starts, it is ignored. If there are '\\' characters " +"in\n" +"the path (such as in Windows paths), you must double them.\n" +"Examples:\n" +"auto_connect_to_folder = 'C:\\\\Users\\\\someone\\\\Desktop\\\\testlib'\n" +"auto_connect_to_folder = '/home/dropbox/My Dropbox/someone/library'" +msgstr "" + +#: /home/kovid/work/calibre/resources/default_tweaks.py:175 +msgid "Specify renaming rules for SONY collections" +msgstr "" + +#: /home/kovid/work/calibre/resources/default_tweaks.py:176 +msgid "" +"Specify renaming rules for sony collections. This tweak is only applicable " +"if\n" +"metadata management is set to automatic. Collections on Sonys are named\n" +"depending upon whether the field is standard or custom. A collection " +"derived\n" +"from a standard field is named for the value in that field. For example, if\n" +"the standard 'series' column contains the value 'Darkover', then the\n" +"collection name is 'Darkover'. A collection derived from a custom field " +"will\n" +"have the name of the field added to the value. For example, if a custom " +"series\n" +"column named 'My Series' contains the name 'Darkover', then the collection\n" +"will by default be named 'Darkover (My Series)'. For purposes of this\n" +"documentation, 'Darkover' is called the value and 'My Series' is called the\n" +"category. If two books have fields that generate the same collection name,\n" +"then both books will be in that collection.\n" +"This set of tweaks lets you specify for a standard or custom field how\n" +"the collections are to be named. You can use it to add a description to a\n" +"standard field, for example 'Foo (Tag)' instead of the 'Foo'. You can also " +"use\n" +"it to force multiple fields to end up in the same collection. For example, " +"you\n" +"could force the values in 'series', '#my_series_1', and '#my_series_2' to\n" +"appear in collections named 'some_value (Series)', thereby merging all of " +"the\n" +"fields into one set of collections.\n" +"There are two related tweaks. The first determines the category name to use\n" +"for a metadata field. The second is a template, used to determines how the\n" +"value and category are combined to create the collection name.\n" +"The syntax of the first tweak, sony_collection_renaming_rules, is:\n" +"{'field_lookup_name':'category_name_to_use', 'lookup_name':'name', ...}\n" +"The second tweak, sony_collection_name_template, is a template. It uses the\n" +"same template language as plugboards and save templates. This tweak " +"controls\n" +"how the value and category are combined together to make the collection " +"name.\n" +"The only two fields available are {category} and {value}. The {value} field " +"is\n" +"never empty. The {category} field can be empty. The default is to put the\n" +"value first, then the category enclosed in parentheses, it is isn't empty:\n" +"'{value} {category:|(|)}'\n" +"Examples: The first three examples assume that the second tweak\n" +"has not been changed.\n" +"1: I want three series columns to be merged into one set of collections. " +"The\n" +"column lookup names are 'series', '#series_1' and '#series_2'. I want " +"nothing\n" +"in the parenthesis. The value to use in the tweak value would be:\n" +"sony_collection_renaming_rules={'series':'', '#series_1':'', " +"'#series_2':''}\n" +"2: I want the word '(Series)' to appear on collections made from series, " +"and\n" +"the word '(Tag)' to appear on collections made from tags. Use:\n" +"sony_collection_renaming_rules={'series':'Series', 'tags':'Tag'}\n" +"3: I want 'series' and '#myseries' to be merged, and for the collection " +"name\n" +"to have '(Series)' appended. The renaming rule is:\n" +"sony_collection_renaming_rules={'series':'Series', '#myseries':'Series'}\n" +"4: Same as example 2, but instead of having the category name in " +"parentheses\n" +"and appended to the value, I want it prepended and separated by a colon, " +"such\n" +"as in Series: Darkover. I must change the template used to format the " +"category name\n" +"The resulting two tweaks are:\n" +"sony_collection_renaming_rules={'series':'Series', 'tags':'Tag'}\n" +"sony_collection_name_template='{category:||: }{value}'" +msgstr "" + +#: /home/kovid/work/calibre/resources/default_tweaks.py:228 +msgid "Specify how SONY collections are sorted" +msgstr "" + +#: /home/kovid/work/calibre/resources/default_tweaks.py:229 +msgid "" +"Specify how sony collections are sorted. This tweak is only applicable if\n" +"metadata management is set to automatic. You can indicate which metadata is " +"to\n" +"be used to sort on a collection-by-collection basis. The format of the " +"tweak\n" +"is a list of metadata fields from which collections are made, followed by " +"the\n" +"name of the metadata field containing the sort value.\n" +"Example: The following indicates that collections built from pubdate and " +"tags\n" +"are to be sorted by the value in the custom column '#mydate', that " +"collections\n" +"built from 'series' are to be sorted by 'series_index', and that all other\n" +"collections are to be sorted by title. If a collection metadata field is " +"not\n" +"named, then if it is a series- based collection it is sorted by series " +"order,\n" +"otherwise it is sorted by title order.\n" +"[(['pubdate', 'tags'],'#mydate'), (['series'],'series_index'), (['*'], " +"'title')]\n" +"Note that the bracketing and parentheses are required. The syntax is\n" +"[ ( [list of fields], sort field ) , ( [ list of fields ] , sort field ) ]\n" +"Default: empty (no rules), so no collection attributes are named." +msgstr "" + +#: /home/kovid/work/calibre/resources/default_tweaks.py:247 +msgid "Control how tags are applied when copying books to another library" +msgstr "" + +#: /home/kovid/work/calibre/resources/default_tweaks.py:248 +msgid "" +"Set this to True to ensure that tags in 'Tags to add when adding\n" +"a book' are added when copying books to another library" +msgstr "" + +#: /home/kovid/work/calibre/resources/default_tweaks.py:253 +msgid "Set the maximum number of tags to show per book in the content server" +msgstr "" + +#: /home/kovid/work/calibre/resources/default_tweaks.py:257 +msgid "" +"Set custom metadata fields that the content server will or will not display." +msgstr "" + +#: /home/kovid/work/calibre/resources/default_tweaks.py:258 +msgid "" +"content_server_will_display is a list of custom fields to be displayed.\n" +"content_server_wont_display is a list of custom fields not to be displayed.\n" +"wont_display has priority over will_display.\n" +"The special value '*' means all custom fields. The value [] means no " +"entries.\n" +"Defaults:\n" +"content_server_will_display = ['*']\n" +"content_server_wont_display = []\n" +"Examples:\n" +"To display only the custom fields #mytags and #genre:\n" +"content_server_will_display = ['#mytags', '#genre']\n" +"content_server_wont_display = []\n" +"To display all fields except #mycomments:\n" +"content_server_will_display = ['*']\n" +"content_server_wont_display['#mycomments']" +msgstr "" + +#: /home/kovid/work/calibre/resources/default_tweaks.py:275 +msgid "Set the maximum number of sort 'levels'" +msgstr "" + +#: /home/kovid/work/calibre/resources/default_tweaks.py:276 +msgid "" +"Set the maximum number of sort 'levels' that calibre will use to resort the\n" +"library after certain operations such as searches or device insertion. Each\n" +"sort level adds a performance penalty. If the database is large (thousands " +"of\n" +"books) the penalty might be noticeable. If you are not concerned about multi-" +"\n" +"level sorts, and if you are seeing a slowdown, reduce the value of this " +"tweak." +msgstr "" + +#: /home/kovid/work/calibre/resources/default_tweaks.py:283 +msgid "Specify which font to use when generating a default cover" +msgstr "" + +#: /home/kovid/work/calibre/resources/default_tweaks.py:284 +msgid "" +"Absolute path to .ttf font files to use as the fonts for the title, author\n" +"and footer when generating a default cover. Useful if the default font " +"(Liberation\n" +"Serif) does not contain glyphs for the language of the books in your library." +msgstr "" + +#: /home/kovid/work/calibre/resources/default_tweaks.py:290 +msgid "Control behavior of double clicks on the book list" +msgstr "" + +#: /home/kovid/work/calibre/resources/default_tweaks.py:291 +msgid "" +"Behavior of doubleclick on the books list. Choices: open_viewer, " +"do_nothing,\n" +"edit_cell, edit_metadata. Selecting edit_metadata has the side effect of\n" +"disabling editing a field using a single click.\n" +"Default: open_viewer.\n" +"Example: doubleclick_on_library_view = 'do_nothing'" +msgstr "" + +#: /home/kovid/work/calibre/resources/default_tweaks.py:299 +msgid "Language to use when sorting." +msgstr "" + +#: /home/kovid/work/calibre/resources/default_tweaks.py:300 +msgid "" +"Setting this tweak will force sorting to use the\n" +"collating order for the specified language. This might be useful if you run\n" +"calibre in English but want sorting to work in the language where you live.\n" +"Set the tweak to the desired ISO 639-1 language code, in lower case.\n" +"You can find the list of supported locales at\n" +"http://publib.boulder.ibm.com/infocenter/iseries/v5r3/topic/nls/rbagsicusorts" +"equencetables.htm\n" +"Default: locale_for_sorting = '' -- use the language calibre displays in\n" +"Example: locale_for_sorting = 'fr' -- sort using French rules.\n" +"Example: locale_for_sorting = 'nb' -- sort using Norwegian rules." +msgstr "" + +#: /home/kovid/work/calibre/resources/default_tweaks.py:311 +msgid "Number of columns for custom metadata in the edit metadata dialog" +msgstr "" + +#: /home/kovid/work/calibre/resources/default_tweaks.py:312 +msgid "" +"Set whether to use one or two columns for custom metadata when editing\n" +"metadata one book at a time. If True, then the fields are laid out using " +"two\n" +"columns. If False, one column is used." +msgstr "" + +#: /home/kovid/work/calibre/resources/default_tweaks.py:317 +msgid "The number of seconds to wait before sending emails" +msgstr "" + +#: /home/kovid/work/calibre/resources/default_tweaks.py:318 +msgid "" +"The number of seconds to wait before sending emails when using a\n" +"public email server like gmail or hotmail. Default is: 5 minutes\n" +"Setting it to lower may cause the server's SPAM controls to kick in,\n" +"making email sending fail. Changes will take effect only after a restart of\n" +"calibre." +msgstr "" + +#: /home/kovid/work/calibre/resources/default_tweaks.py:325 +msgid "Remove the bright yellow lines at the edges of the book list" +msgstr "" + +#: /home/kovid/work/calibre/resources/default_tweaks.py:326 +msgid "" +"Control whether the bright yellow lines at the edges of book list are drawn\n" +"when a section of the user interface is hidden. Changes will take effect\n" +"after a restart of calibre." +msgstr "" + +#: /home/kovid/work/calibre/resources/default_tweaks.py:331 +msgid "The maximum width and height for covers saved in the calibre library" +msgstr "" + +#: /home/kovid/work/calibre/resources/default_tweaks.py:332 +msgid "" +"All covers in the calibre library will be resized, preserving aspect ratio,\n" +"to fit within this size. This is to prevent slowdowns caused by extremely\n" +"large covers" +msgstr "" + +#: /home/kovid/work/calibre/resources/default_tweaks.py:337 +msgid "Where to send downloaded news" +msgstr "Gdzie zapisywać pobrane wiadomości" + +#: /home/kovid/work/calibre/resources/default_tweaks.py:338 +msgid "" +"When automatically sending downloaded news to a connected device, calibre\n" +"will by default send it to the main memory. By changing this tweak, you can\n" +"control where it is sent. Valid values are \"main\", \"carda\", \"cardb\". " +"Note\n" +"that if there isn't enough free space available on the location you choose,\n" +"the files will be sent to the location with the most free space." +msgstr "" +"Podczas automatycznego pobierania wiadomości, calibre zapisuje je domyślnie\n" +"na główną pamięć urządzenia. Możesz to zmienić tym ustawieniem. Wspierane\n" +"możliwości to \"main\" (pamięć główna), \"carda\" (karta A) i \"cardb\" " +"(karta B).\n" +"Pamiętaj, że jeśli na wybranej przez Ciebie pamięci zabraknie miejsca, " +"pliki\n" +"zostaną przesłane do pamięci z największą ilością wolnego miejsca." + +#: /home/kovid/work/calibre/resources/default_tweaks.py:345 +msgid "What interfaces should the content server listen on" +msgstr "Na jakim interfejsie ma działać serwer treści calibre" + +#: /home/kovid/work/calibre/resources/default_tweaks.py:346 +msgid "" +"By default, the calibre content server listens on '0.0.0.0' which means that " +"it\n" +"accepts IPv4 connections on all interfaces. You can change this to, for\n" +"example, '127.0.0.1' to only listen for connections from the local machine, " +"or\n" +"to '::' to listen to all incoming IPv6 and IPv4 connections (this may not\n" +"work on all operating systems)" +msgstr "" +"Domyślnie serwer calibre nasłuchuje na '0.0.0.0', co oznacza, że akceptuje\n" +"połączenie IPv4 na wszystkich interfejsach. Możesz to to zmienić, na " +"przykład\n" +"na '127.0.0.1', aby akceptować tylko połączenia z lokalnego komputera ,\n" +"lub na '::', aby akceptować wszystkie przychodzące połączenia IPv4 i IPv6\n" +"(nie wszystkie systemy operacyne to umożliwiają)." + +#: /home/kovid/work/calibre/resources/default_tweaks.py:353 +msgid "Unified toolbar on OS X" +msgstr "Zunifikowany pasek narzędzi OS X" + +#: /home/kovid/work/calibre/resources/default_tweaks.py:354 +msgid "" +"If you enable this option and restart calibre, the toolbar will be " +"'unified'\n" +"with the titlebar as is normal for OS X applications. However, doing this " +"has\n" +"various bugs, for instance the minimum width of the toolbar becomes twice\n" +"what it should be and it causes other random bugs on some systems, so turn " +"it\n" +"on at your own risk!" +msgstr "" + #~ msgid "Control page layout" #~ msgstr "Wygląd strony kontrolnej" @@ -15339,9 +18570,6 @@ msgstr "Nie pobieraj arkuszy styli CSS." #~ msgid "Configuration" #~ msgstr "Ustawienia" -#~ msgid "Toolbar" -#~ msgstr "Pasek narzędzi" - #~ msgid "Path to the cover to be used for this book" #~ msgstr "Ścieżka do okładki dla tej książki" @@ -15625,6 +18853,18 @@ msgstr "Nie pobieraj arkuszy styli CSS." #~ msgid "&Convert tables to images (good for large/complex tables)" #~ msgstr "&Konwertuj tabele do obrazków (zalecane dla dużych/złożonych tabel)" +#~ msgid "Reset cover to default" +#~ msgstr "Przywróć domyślną okładkę" + +#~ msgid "Available Formats" +#~ msgstr "Dostępne formaty" + +#~ msgid "IS&BN:" +#~ msgstr "IS&BN:" + +#~ msgid "Add a new format for this book to the database" +#~ msgstr "Dodaj nowy format tej książki do bazy danych." + #~ msgid "description" #~ msgstr "opis" @@ -15730,6 +18970,9 @@ msgstr "Nie pobieraj arkuszy styli CSS." #~ msgid "Edit meta information" #~ msgstr "Edytuj metadane" +#~ msgid "Edit Meta Information" +#~ msgstr "Edytuj metadane" + #~ msgid "Change the publisher of this book" #~ msgstr "Zmień wydawcę tej ksiązki" @@ -15771,6 +19014,18 @@ msgstr "Nie pobieraj arkuszy styli CSS." #~ msgid "&Metadata from file name" #~ msgstr "&Metadane z nazwy pliku" +#~ msgid "No metadata found" +#~ msgstr "Nie znaleziono metadanych" + +#~ msgid "Fetch metadata" +#~ msgstr "Pobierz metadane" + +#~ msgid "Fetch" +#~ msgstr "Pobierz" + +#~ msgid "Could not fetch cover.
      " +#~ msgstr "Nie można pobrać okładki.
      " + #~ msgid "" #~ "Profile of the target device this EPUB is meant for. Set to None to create a " #~ "device independent EPUB. The profile is used for device specific " @@ -15882,6 +19137,9 @@ msgstr "Nie pobieraj arkuszy styli CSS." #~ " \n" #~ "%prog konwertuje mój_e-book.epub do mój_e-book.lrf" +#~ msgid "Cannot fetch cover" +#~ msgstr "Nie można pobrać okładki" + #~ msgid "%s: %s by Kovid Goyal %%(version)s
      %%(device)s

      " #~ msgstr "" #~ "%s: %s stworzył Kovid Goyal %%(version)s
      %%(device)s

      " @@ -15905,6 +19163,9 @@ msgstr "Nie pobieraj arkuszy styli CSS." #~ msgid "Click to see list of active jobs." #~ msgstr "Kliknij, aby zobaczyć listę aktywnych zadań." +#~ msgid "Meta information" +#~ msgstr "Metadane" + #~ msgid "Show &text in toolbar buttons" #~ msgstr "Pokazuj &tekst pod przyciskami na pasku narzędzi" @@ -16058,6 +19319,9 @@ msgstr "Nie pobieraj arkuszy styli CSS." #~ msgid " plugins" #~ msgstr " - wtyczki" +#~ msgid "No valid plugin found in " +#~ msgstr "Nie znaleziono wtyczki w " + #~ msgid "The series index" #~ msgstr "Spis serii" @@ -16067,6 +19331,9 @@ msgstr "Nie pobieraj arkuszy styli CSS." #~ msgid "This book has no available formats" #~ msgstr "Ta książka nie ma dostepnych formatów" +#~ msgid "Book %s of %s." +#~ msgstr "Książka %s z %s." + #~ msgid "Choose a location for your ebook library." #~ msgstr "Wybierz lokalizację dla twojej biblioteki książek." @@ -16144,6 +19411,9 @@ msgstr "Nie pobieraj arkuszy styli CSS." #~ msgid "Convert to EPUB" #~ msgstr "Konwersja do EPUB" +#~ msgid "Matches" +#~ msgstr "Pasujące" + #~ msgid "Convert to LRF" #~ msgstr "Konwersja do LRF" @@ -16275,18 +19545,34 @@ msgstr "Nie pobieraj arkuszy styli CSS." #~ msgid "Copy to Clipboard" #~ msgstr "Kopiuj do schowka" +#~ msgid "The download timed out." +#~ msgstr "Czas pobierania minął." + +#~ msgid "The metadata download seems to have stalled. Try again later." +#~ msgstr "" +#~ "Pobieranie metadanych wydaje się przedłużać. Spróbuj ponownie później." + +#~ msgid "Last downloaded" +#~ msgstr "Ostatnio pobrano" + #~ msgid "Download &cover" #~ msgstr "Pobierz o&kładkę" #~ msgid "Failed to download metadata for the following:" #~ msgstr "Nie udało się pobrać metadanych dla następujących e-ksiązek:" +#~ msgid "Download only metadata" +#~ msgstr "Pobierz tylko metadane" + #~ msgid "Failed to download metadata:" #~ msgstr "Nie udało się pobrać metadanych:" #~ msgid "Downloading %s for %d book(s)" #~ msgstr "Pobieram %s dla %d książki(ek)" +#~ msgid "Download only covers" +#~ msgstr "Pobierz tylko okładki" + #~ msgid "Do not add a blank line between paragraphs." #~ msgstr "Nie dodawaj pustej linii pomiędzy paragrafami." @@ -16322,6 +19608,9 @@ msgstr "Nie pobieraj arkuszy styli CSS." #~ msgid "Update available" #~ msgstr "Uaktualnienia dostępne" +#~ msgid "Update metadata from the metadata in the selected format" +#~ msgstr "Uaktualnij metadane pobierając je z metadanych wybranego formatu" + #~ msgid "Use a wizard to help construct the XPath expression" #~ msgstr "Użyj kreatora pomagającego skonstruować wyrażenie XPath" @@ -16331,9 +19620,51 @@ msgstr "Nie pobieraj arkuszy styli CSS." #~ msgid "Remove H&eader" #~ msgstr "Usuń &nagłówek" +#~ msgid "dd MMM yyyy" +#~ msgstr "dd MMM rrrr" + +#~ msgid "day" +#~ msgstr "dzień" + +#~ msgid "Monday" +#~ msgstr "poniedziałek" + +#~ msgid "Tuesday" +#~ msgstr "wtorek" + +#~ msgid "Download all scheduled recipes at once" +#~ msgstr "Pobierz wszystkie zaplanowane" + +#~ msgid "Friday" +#~ msgstr "piątek" + +#~ msgid "Thursday" +#~ msgstr "czwartek" + +#~ msgid "Every " +#~ msgstr "Co " + +#~ msgid "at" +#~ msgstr "o" + +#~ msgid "Wednesday" +#~ msgstr "środę" + +#~ msgid "Saturday" +#~ msgstr "sobotę" + +#~ msgid "Sunday" +#~ msgstr "niedzielę" + #~ msgid "Device database corrupted" #~ msgstr "Baza danych urządzenia uszkodzona" +#~ msgid "metadata" +#~ msgstr "metadane" + +#~ msgid "covers" +#~ msgstr "okładki" + #~ msgid "Invalid library location" #~ msgstr "Niewłaściwa lokalizacja biblioteki" @@ -16393,6 +19724,9 @@ msgstr "Nie pobieraj arkuszy styli CSS." #~ "Czas oczekiwania na odpowiedź z LibraryThing.com minął. Spróbuj ponownie " #~ "później." +#~ msgid "The ISBN ID of the book you want metadata for." +#~ msgstr "Kod ISBN książki, dla której chcesz pobrać metadane." + #~ msgid "" #~ "Email\n" #~ "Delivery" @@ -16415,6 +19749,18 @@ msgstr "Nie pobieraj arkuszy styli CSS." #~ msgid "Failed to check database integrity" #~ msgstr "Sprawdzenie integralności bazy danych się nie powiodło" +#~ msgid "ratings" +#~ msgstr "oceny" + +#~ msgid "Download %s from %s" +#~ msgstr "Pobierz %s z %s" + +#~ msgid "description/reviews" +#~ msgstr "opis/recenzje" + +#~ msgid "Downloads metadata from Google Books" +#~ msgstr "Pobierz metadane z Google Books" + #~ msgid "Character encoding for input. Default is to auto detect." #~ msgstr "Kodowanie znaków dla plików wejścia. Domyslnie - autowykrywanie." @@ -16470,6 +19816,23 @@ msgstr "Nie pobieraj arkuszy styli CSS." #~ msgid "No books selected to generate catalog for" #~ msgstr "Brak książek do wygenerowania katalogu" +#~ msgid "Could not fetch metadata from:" +#~ msgstr "Nie udało się pobrać metadanych z:" + +#~ msgid "Warning" +#~ msgstr "Ostrzeżenie" + +#~ msgid "Could not find cover for this book. Try specifying the ISBN first." +#~ msgstr "" +#~ "Nie udało się odnaleźć okładki dla tej książki. Spróbuj podać najpierw numer " +#~ "ISBN." + +#~ msgid "There were errors" +#~ msgstr "Pojawiły się błędy" + +#~ msgid "Bad cover" +#~ msgstr "Zła okładka" + #~ msgid "calibre can send your books to you (or your reader) by email" #~ msgstr "calibre może wysłać książki do Ciebie (lub czytelnika) przez e-mail" @@ -16483,6 +19846,9 @@ msgstr "Nie pobieraj arkuszy styli CSS." #~ msgid "Create catalog of books in your calibre library" #~ msgstr "Stwórz katalog książek w Twojej bibliotece calibre" +#~ msgid "&Fetch metadata from server" +#~ msgstr "&Pobierz metadane z serwera" + #~ msgid " " #~ msgstr " " @@ -16578,6 +19944,12 @@ msgstr "Nie pobieraj arkuszy styli CSS." #~ msgid "Sent by email:" #~ msgstr "Preślij poprzez email:" +#~ msgid "Could not find metadata" +#~ msgstr "Nie można było znaleźć metadanych" + +#~ msgid "Finding metadata..." +#~ msgstr "Wyszukuję metadane..." + #~ msgid "Abort the editing of all remaining books" #~ msgstr "Przerwij edycję wszystkich pozostałych książek" @@ -16599,11 +19971,21 @@ msgstr "Nie pobieraj arkuszy styli CSS." #~ msgid "Configure calibre" #~ msgstr "Konfiguruj calibre" +#~ msgid "You must set the username and password for the mail server." +#~ msgstr "" +#~ "Musisz ustawić nazwe uzytkownika i hasło dla tego serweru pocztowego." + #~ msgid "" #~ "No metadata found, try adjusting the title and author or the ISBN key." #~ msgstr "" #~ "Nie odnaleziono metadanych. Spróbuj poprawić tytuł i autora albo numer ISBN." +#~ msgid "Paste Image" +#~ msgstr "Wklej grafikę" + +#~ msgid "Copy Image" +#~ msgstr "Kopiuj grafikę" + #~ msgid "Communicate with the Kindle 2 eBook reader." #~ msgstr "Umożliwia komunikację z czytnikiem książek Kindle 2." @@ -16622,6 +20004,9 @@ msgstr "Nie pobieraj arkuszy styli CSS." #~ msgid "Could not access %s. Using %s as the library." #~ msgstr "Nie można uzyskać dostępu do %s. Używam katalogu %s jako biblioteki." +#~ msgid "Cannot fetch metadata" +#~ msgstr "Nie można pobrać metadanych" + #~ msgid "set in ui.py" #~ msgstr "usraw w ui.py" @@ -16659,6 +20044,26 @@ msgstr "Nie pobieraj arkuszy styli CSS." #~ msgid "Book Jacket" #~ msgstr "Obwoluta" +#~ msgid "social metadata" +#~ msgstr "metadane społecznościowe" + +#~ msgid "Download only social metadata" +#~ msgstr "Pobierz tylko metadane społecznościowe" + +#~ msgid "Downloads social metadata from amazon.com" +#~ msgstr "Pobierz metadane społecznościowe z amazon.com" + +#~ msgid "Download &social metadata (tags/ratings/etc.) by default" +#~ msgstr "Pobieraj domyślnie metadane &społecznościowe (etykiety/oceny/itd.)" + +#~ msgid "Downloading social metadata, please wait..." +#~ msgstr "Pobieranie szpołecznościowych metadanych, proszę czekać..." + +#~ msgid "Download &social metadata (tags/rating/etc.) for the selected book" +#~ msgstr "" +#~ "Pobierz metadane społecznościowe (etykiety/oceny/itd.) dla zaznaczonej " +#~ "książki" + #~ msgid "&Transliterate unicode characters to ASCII." #~ msgstr "&Transliteruj znaki Unicode na ASCII." @@ -16674,6 +20079,12 @@ msgstr "Nie pobieraj arkuszy styli CSS." #~ msgid "PDB Input" #~ msgstr "Źródłowy PDB" +#~ msgid "&Overwrite author and title by default when fetching metadata" +#~ msgstr "Domyślnie &nadpisuj autora i tytuł przy pobieraniu metadanych" + +#~ msgid "No matches found for this book" +#~ msgstr "Nie znaleziono wyników dla tej książki" + #~ msgid "Delete current search and clear search box" #~ msgstr "Usuń aktualne wyszukiwanie i wyczyść pole wyszukiwania" @@ -16690,12 +20101,15 @@ msgstr "Nie pobieraj arkuszy styli CSS." #~ "Nie można pobrać okładki z serwera, z powodu zbyt dużego obciążenia. Spróbuj " #~ "ponownie później." +#~ msgid "Calibre Quick Start Guide" +#~ msgstr "Krótki przewodnik po calibre" + +#~ msgid "Maximum number of waiting worker processes" +#~ msgstr "Maksymalna liczba oczekujących zadań" + #~ msgid "Column heading" #~ msgstr "Nagłówek kolumny" -#~ msgid "Text" -#~ msgstr "Tekst" - #~ msgid "Use brackets" #~ msgstr "Użyj nawiasów" @@ -16705,9 +20119,6 @@ msgstr "Nie pobieraj arkuszy styli CSS." #~ msgid "Column type" #~ msgstr "Typ kolumny" -#~ msgid "Number" -#~ msgstr "Liczba" - #~ msgid "Editing meta information for %d books" #~ msgstr "Edytowanie metadanych dla %d książek" @@ -16730,6 +20141,9 @@ msgstr "Nie pobieraj arkuszy styli CSS." #~ msgid "Downloads series information from librarything.com" #~ msgstr "Pobierz informacje o serii z librarything.com" +#~ msgid "Downloads metadata from Douban.com" +#~ msgstr "Pobierz metadane z Douban.com" + #~ msgid "" #~ "Preserve the aspect ratio of the cover, instead of stretching it to fill the " #~ "ull first page of the generated pdf." @@ -16750,6 +20164,9 @@ msgstr "Nie pobieraj arkuszy styli CSS." #~ msgid " " #~ msgstr " " +#~ msgid "Communicate with the Sweex MM300" +#~ msgstr "Umożliwia komunikację z czytnikiem Sweex MM300." + #~ msgid "&Restrict to:" #~ msgstr "&Zawęź do:" @@ -16777,6 +20194,9 @@ msgstr "Nie pobieraj arkuszy styli CSS." #~ msgid "Reader" #~ msgstr "Czytnik" +#~ msgid "Manage &user categories" +#~ msgstr "Zarządzaj kategoriami &użytkownika" + #~ msgid "Some inconsistencies found" #~ msgstr "Znaleziono pewne nieścisłości" @@ -16801,6 +20221,30 @@ msgstr "Nie pobieraj arkuszy styli CSS." #~ msgid "Show &donate button (restart)" #~ msgstr "Pokaż przycisk &dotacji (wymaga restartu)" +#~ msgid "Column &type" +#~ msgstr "&Typ kolumny" + +#~ msgid "mixed" +#~ msgstr "mieszany" + +#~ msgid "misc" +#~ msgstr "różny" + +#~ msgid "book" +#~ msgstr "książka" + +#~ msgid "replace" +#~ msgstr "zastąp" + +#~ msgid "ignore" +#~ msgstr "ignoruj" + +#~ msgid "ascii/LaTeX" +#~ msgstr "ascii/LaTeX" + +#~ msgid "strict" +#~ msgstr "ścisłe" + #~ msgid "Send specific format" #~ msgstr "Wyślij wybrany format" @@ -16872,18 +20316,45 @@ msgstr "Nie pobieraj arkuszy styli CSS." #~ msgid "Automatically number books in this series" #~ msgstr "Automatycznie numeruj książki w cyklu" +#~ msgid "tags" +#~ msgstr "etykiety" + #~ msgid "Cannot use tag editor" #~ msgstr "Nie można użyć edytora etykiet" #~ msgid "The tags editor cannot be used if you have modified the tags" #~ msgstr "Edytor etykiet nie może zostać użyty, jeśli zmodyfikowałeś etykiety" +#~ msgid "Add your own categories to the Tag Browser" +#~ msgstr "Dodaj swoje własne kategorie do Przeglądarki etykiet" + #~ msgid "Communicate with the Promedia eBook reader" #~ msgstr "Umożliwia komunikację z czytnikiem książek Promedia" +#~ msgid "The author whose book to search for." +#~ msgstr "Autor poszukiwanych książek." + +#~ msgid "The publisher of the book to search for." +#~ msgstr "Wydawca poszukiwanej książki." + +#~ msgid "The title of the book to search for." +#~ msgstr "Tytuł poszukiwanej książki." + +#~ msgid "Delete current saved search" +#~ msgstr "Usuń aktualne zapisane wyszukanie" + +#~ msgid "Save current search under the name shown in the box" +#~ msgstr "Zapisz aktualne wyszukanie pod nazwą pokazaną w polu" + +#~ msgid "Download covers from openlibrary.org" +#~ msgstr "Pobierz okładki z openlibrary.org" + #~ msgid "Download covers from librarything.com" #~ msgstr "Pobierz okładki z librarything.com" +#~ msgid "ISBN: %s not found" +#~ msgstr "ISBN: %s nie znaleziono" + #~ msgid "" #~ "Any link that matches this regular expression will be ignored. This option " #~ "can be specified multiple times, in which case as long as any regexp matches " @@ -16909,15 +20380,115 @@ msgstr "Nie pobieraj arkuszy styli CSS." #~ "książek.

      Użyj klawisza N lub F3 by przeskoczyć do następnej wyszukanej " #~ "pozycji." +#~ msgid "Cover download" +#~ msgstr "Pobieranie okładki" + +#~ msgid "Communicate with the Digma Q600" +#~ msgstr "Umożliwia komunikację z czytnikiem książek Digma Q600" + +#~ msgid "Communicate with the Kogan" +#~ msgstr "Umożliwia komunikację z czytnikiem książek Kogan" + +#~ msgid "Downloads metadata from amazon.de" +#~ msgstr "Pobiera metadane z amazon.de" + +#~ msgid "Downloads metadata from amazon.fr" +#~ msgstr "Pobiera metadane z amazon.fr" + +#~ msgid "Downloads metadata from amazon.com in spanish" +#~ msgstr "Pobiera z amazon.com metadane po hiszpańsku" + +#~ msgid "Downloads metadata from amazon.com in english" +#~ msgstr "Pobiera z amazon.com metadane po angielsku" + +#~ msgid "Downloads metadata from amazon.com" +#~ msgstr "Pobiera metadane z amazon.com" + +#~ msgid "Convert comments downloaded from %s to plain text" +#~ msgstr "Konwertuje komentarze pobrane z %s do postaci tekstowej" + +#~ msgid "Downloads metadata from Fictionwise" +#~ msgstr "Pobiera metadane z Fictionwise" + +#~ msgid "Query: %s" +#~ msgstr "Zapytanie: %s" + +#~ msgid "Book author(s)" +#~ msgstr "Autor(rzy) książki" + +#~ msgid "Book title" +#~ msgstr "Tytuł książki" + +#~ msgid "Keywords" +#~ msgstr "Słowa kluczowe" + +#~ msgid "Book publisher" +#~ msgstr "Wydawca książki" + +#~ msgid "Fictionwise encountered an error." +#~ msgstr "Wystąpił błąd podczas połączenia z Fictionwise" + +#~ msgid "Be more verbose about errors" +#~ msgstr "Wyświetlaj więcej informacji o błędach" + +#~ msgid "No result found for this search!" +#~ msgstr "Brak rezultatów tego wyszukiwania!" + +#~ msgid "Downloads metadata from french Nicebooks" +#~ msgstr "Pobiera metadane z francuskiego Nicebooks" + +#~ msgid "Downloads covers from french Nicebooks" +#~ msgstr "Pobiera pliki okładek z francuskiego Nicebooks" + +#~ msgid "An errror occured with Nicebooks cover fetcher" +#~ msgstr "Wystąpił błąd podczas pobierania pliku okładki z Nicebooks" + +#~ msgid "ISBN: %s not found." +#~ msgstr "ISBN: nie znaleziono %s" + +#~ msgid "Book ISBN" +#~ msgstr "ISBN książki" + +#~ msgid "Covers: 1-Check/ 2-Download" +#~ msgstr "Okładki: 1-Sprawdź/ 2-Pobierz" + #~ msgid "No errors found" #~ msgstr "Nie znaleziono żadnych błędów" +#~ msgid "" +#~ "Delete downloaded news older than the specified number of days. Set to zero " +#~ "to disable." +#~ msgstr "" +#~ "Usuń pobrane newsy starsze niż podana liczba dni. Ustaw zero, aby wyłączyć " +#~ "usuwanie." + +#~ msgid "Delete downloaded news older than " +#~ msgstr "Usuń pobrane newsy starsze niż " + #~ msgid "&Highlight" #~ msgstr "Podświetlaj" +#~ msgid "Customize the toolbar" +#~ msgstr "Dostosuj pasek narzędzi" + +#~ msgid "Covers files path" +#~ msgstr "Ścieżka do pliku okładek" + #~ msgid "Unknown publisher" #~ msgstr "Nieznany wydawca" +#~ msgid "No cover found!" +#~ msgstr "Nie znaleziono okładki!" + +#~ msgid "A cover was found for this book" +#~ msgstr "Znaleziono okładkę dla tej książki" + +#~ msgid "Cover saved to file " +#~ msgstr "Okładka zapisana do pliku " + +#~ msgid "The priority of worker processes" +#~ msgstr "Priorytet zadań" + #~ msgid "Extra covers in books" #~ msgstr "Dodatkowe okładki w książkach" @@ -16926,3 +20497,369 @@ msgstr "Nie pobieraj arkuszy styli CSS." #~ msgid "Downloads series/covers/rating information from librarything.com" #~ msgstr "Ś" + +#~ msgid "Failed to get all details for an entry" +#~ msgstr "Nie udało się zebrać wszystkich danych dla wpisu" + +#~ msgid "Downloading {0} for {1} book(s)" +#~ msgstr "Ściąganie {0} dla {1} książki/ek" + +#~ msgid "Maximum number of results to fetch" +#~ msgstr "Maksymalna liczba wyników do pobrania" + +#~ msgid "Customize searching" +#~ msgstr "Dostosuj wyszukiwanie" + +#~ msgid "Download covers from Douban.com" +#~ msgstr "Pobierz okładki z Douban.com" + +#~ msgid "Download covers from amazon.com" +#~ msgstr "Pobierz okładki z amazon.com" + +#~ msgid "" +#~ "SUMMARY:\n" +#~ " %s" +#~ msgstr "" +#~ "PODSUMOWANIE:\n" +#~ " %s" + +#~ msgid "Downloads series information from ww2.kdl.org" +#~ msgstr "Pobierz informacje o serii z ww2.kdl.org" + +#~ msgid "Downloads metadata from Amazon" +#~ msgstr "Pobierz metadane z Amazon.com" + +#~ msgid "All files from %s will be permanently deleted. Are you sure?" +#~ msgstr "" +#~ "Wszystkie pliki z %s zostaną usunięte na zawsze. Jesteś tego pewien?" + +#~ msgid "Has Summary" +#~ msgstr "Posiada podsumowanie" + +#~ msgid "Has Cover" +#~ msgstr "Posiada okładkę" + +#~ msgid "" +#~ " %prog [options]\n" +#~ "\n" +#~ " Fetch book metadata from Amazon. You must specify one of title, " +#~ "author,\n" +#~ " ISBN, publisher or keywords. Will fetch a maximum of 10 matches,\n" +#~ " so you should make your query as specific as possible.\n" +#~ " You can chose the language for metadata retrieval:\n" +#~ " All & english & french & german & spanish\n" +#~ " " +#~ msgstr "" +#~ " %prog [options]\n" +#~ "\n" +#~ " Pobiera metadane z Amazona. Musisz wskazać albo tytuł, albo autora,\n" +#~ " albo ISBN, albo wydawcę lub słowa kluczowe. Pobierze maksymalnie\n" +#~ " 10 trafień, więc powinieneś jak najbardziej zawęzić swoje " +#~ "zapytanie.\n" +#~ " Możesz wybrać język uzyskanych metadanych:\n" +#~ " Wszystkie & angielski & francuski & niemiecki & hiszpański\n" +#~ " " + +#~ msgid "Douban.com API timed out. Try again later." +#~ msgstr "Nie można nawiązać połączenia z Douban.com. Spróbuj później." + +#~ msgid "" +#~ "To use isbndb.com you must sign up for a %sfree account%s and enter your " +#~ "access key below." +#~ msgstr "" +#~ "Aby użyć bazy isbndb.com musisz stworzyć %sdarmowe konto%s i wpisać poniżej " +#~ "klucz dostępu." + +#~ msgid "" +#~ "Downloads series information from ww2.kdl.org. This website cannot handle " +#~ "large numbers of queries, so the plugin is disabled by default." +#~ msgstr "" +#~ "Pobierz informacje z ww2.kdl.org. Ta strona nie jest w stanie obsłużyć zbyt " +#~ "wielu zapytań, więc domyślnie ta wtyczka jest wyłączona." + +#~ msgid "Fictionwise timed out. Try again later." +#~ msgstr "Nie można nawiązać połączenia z Fictionwise. Spróbuj później." + +#~ msgid "" +#~ " %prog [options]\n" +#~ "\n" +#~ " Fetch book metadata from Fictionwise. You must specify one of title, " +#~ "author,\n" +#~ " or keywords. No ISBN specification possible. Will fetch a maximum of " +#~ "20 matches,\n" +#~ " so you should make your query as specific as possible.\n" +#~ " " +#~ msgstr "" +#~ " %prog [options]\n" +#~ "\n" +#~ " Pobiera metadane z Fictionwise. Musisz wskazać albo tytuł, albo " +#~ "autora,\n" +#~ " lub słowa kluczowe. Nie ma możliwości wskazania ISBN. Pobierze " +#~ "maksymalnie\n" +#~ " 20 trafień, więc powinieneś jak najbardziej zawęzić swoje " +#~ "zapytanie.\n" +#~ " " + +#~ msgid "" +#~ "\n" +#~ "%prog [options] key\n" +#~ "\n" +#~ "Fetch metadata for books from isndb.com. You can specify either the\n" +#~ "books ISBN ID or its title and author. If you specify the title and author,\n" +#~ "then more than one book may be returned.\n" +#~ "\n" +#~ "key is the account key you generate after signing up for a free account from " +#~ "isbndb.com.\n" +#~ "\n" +#~ msgstr "" +#~ "\n" +#~ "%prog [options] key\n" +#~ "\n" +#~ "Pobierz metadane dla książek z isndb.com. Musisz wskazać albo\n" +#~ "ISBN książki lub jej tytuł i autora. Jeśli wskażesz tytuł i autora,\n" +#~ "wówczas może pojawić się więcej niż jedna książka.\n" +#~ "\n" +#~ "kluczem jest klucz konta który wygenerujesz po zarejestrowaniu się na " +#~ "darmowym koncie w isbndb.com.\n" +#~ "\n" + +#~ msgid "Nicebooks timed out. Try again later." +#~ msgstr "Błąd połączenia z Nicebooks. Spróbuj później." + +#~ msgid "" +#~ " %prog [options]\n" +#~ "\n" +#~ " Fetch book metadata from Nicebooks. You must specify one of title, " +#~ "author,\n" +#~ " ISBN, publisher or keywords. Will fetch a maximum of 20 matches,\n" +#~ " so you should make your query as specific as possible.\n" +#~ " It can also get covers if the option is activated.\n" +#~ " " +#~ msgstr "" +#~ " %prog [options]\n" +#~ "\n" +#~ " Pobiera metadane z Nicebooks. Musisz wskazać albo tytuł, albo " +#~ "autora,\n" +#~ " albo ISBN, albo wydawcę lub słowa kluczowe. Pobierze maksymalnie\n" +#~ " 20 trafień, więc powinieneś jak najbardziej zawęzić swoje " +#~ "zapytanie.\n" +#~ " Można również aktywować ściąganie okładek.\n" +#~ " " + +#~ msgid "Nicebooks encountered an error." +#~ msgstr "Wystąpił błąd podczas połączenia z Nicebooks." + +#~ msgid "backslashreplace" +#~ msgstr "zastąpienie znaku \"\\\"" + +#~ msgid "" +#~ "No metadata found, try adjusting the title and author and/or removing the " +#~ "ISBN." +#~ msgstr "" +#~ "Nie znaleziono metadanych, spróbuj dostosować tytuł i autora i/lub usunąć " +#~ "ISBN." + +#~ msgid "" +#~ "

      calibre can find metadata for your books from two locations: Google " +#~ "Books and isbndb.com.

      To use isbndb.com you must sign up for a " +#~ "free account and enter your access key " +#~ "below." +#~ msgstr "" +#~ "

      calibre w poszukiwaniu metadanych dla twoich książek przeszukuje dwie " +#~ "bazy: Google Books oraz isbndb.com.

      Aby skorzystać z " +#~ "isbndb.com musisz zarejestrować się i " +#~ "wpisać poniżej swój klucz dostępu." + +#~ msgid "" +#~ "Select the book that most closely matches your copy from the list below" +#~ msgstr "" +#~ "Wybierz książkę, która najdokładniej pasuje do twojej kopii, z listy poniżej" + +#~ msgid "&Access Key:" +#~ msgstr "Klucz &dostępu:" + +#~ msgid "Overwrite author and title with author and title of selected book" +#~ msgstr "Nadpisz autora i tytuł danymi autora i tytułu z zaznaczonej książki" + +#~ msgid "Last modified: %s" +#~ msgstr "Ostatnia modyfikacja: %s" + +#~ msgid "" +#~ "For the error message from each cover source, click Show details below." +#~ msgstr "" +#~ "Dla komunikatu błędu z każdej źródłowej okładki, kliknij poniżej w Pokaż " +#~ "szczegóły" + +#~ msgid "The cover is not a valid picture" +#~ msgstr "Ta okładka nie jest poprawnym obrazkiem" + +#~ msgid "Timed out" +#~ msgstr "Upłynął limit czasu odpowiedzi" + +#~ msgid "You must specify at least one of ISBN, Title, Authors or Publisher" +#~ msgstr "" +#~ "Musisz określić co najmniej jeden element - kod ISBN, tytuł, autora lub " +#~ "wydawcę" + +#~ msgid "" +#~ "The download of social metadata timed out, the servers are probably busy. " +#~ "Try again later." +#~ msgstr "" +#~ "Pobieranie społecznych metadanych nie powiodło się, prawdopodobnie serwery " +#~ "są przeciążone. Spróbuj ponownie później." + +#~ msgid "There were errors downloading social metadata" +#~ msgstr "Wystąpiły błędy podczas pobierania społecznościowych metadanych" + +#~ msgid "Title &sort: " +#~ msgstr "&Sortowanie według tytułu: " + +#~ msgid "" +#~ "Automatically create the author sort entry based on the current author " +#~ "entry.\n" +#~ "Using this button to create author sort will change author sort from red to " +#~ "green." +#~ msgstr "" +#~ "Automatycznie stwórz sortowanie według autora w oparciu o bieżącego autora. " +#~ "Używając tego\n" +#~ "przycisku w celu stworzenia sortowania według autora zmieni sortowanie z " +#~ "czerwonego na zielony." + +#~ msgid "Author S&ort: " +#~ msgstr "&Sortowanie według autora: " + +#~ msgid "Generate a default cover based on the title and author" +#~ msgstr "Stwórz domyślną okładkę w oparciu o tytuł i autora" + +#~ msgid "Remove border (if any) from cover" +#~ msgstr "Usuń obramowanie (jeśli istnieje) z okładki" + +#~ msgid "Remove the selected formats for this book from the database." +#~ msgstr "Usuń z bazy zaznaczone formaty książki" + +#~ msgid "" +#~ "Books display will be restricted to those matching the selected saved search" +#~ msgstr "" +#~ "Wyświetlane książki zostaną ograniczone do pozycji pasujących do zapisanego " +#~ "wyszukania" + +#~ msgid "Change the way searching for books works" +#~ msgstr "Zmień sposób wyszukiwania książek" + +#~ msgid "Book %s of %s." +#~ msgstr "Książka %s z %s." + +#~ msgid "Book has neither title nor ISBN" +#~ msgstr "Książka nie ma ani tytułu, ani kodu ISBN" + +#~ msgid "cover" +#~ msgstr "okładka" + +#~ msgid "Failed to get" +#~ msgstr "Nie udało się uzyskać" + +#~ msgid "Downloaded" +#~ msgstr "Pobrano" + +#~ msgid "%s %s for: %s" +#~ msgstr "%s %s dla: %s" + +#~ msgid "" +#~ "Tags categorize the book. This is particularly useful while searching. " +#~ "

      They can be any wordsor phrases, separated by commas." +#~ msgstr "" +#~ "Etykiety klasyfikują książki. Jest to szczególnie przydatne przy " +#~ "wyszukiwaniu.

      Mogą to być jakiekolwiek słowa lub frazy, oddzielone " +#~ "przecinkami." + +#~ msgid "Successfully downloaded metadata for %d out of %d books" +#~ msgstr "Udało się pobrać metadane dla %d z %d książek" + +#~ msgid "Modified Date" +#~ msgstr "Data modyfikacji" + +#~ msgid "&Split the toolbar into two toolbars" +#~ msgstr "Podziel pa&sek narzędzi na dwa paski" + +#~ msgid "" +#~ "That format and device already has a plugboard or conflicts with another " +#~ "plugboard." +#~ msgstr "" +#~ "Ten format i urządzenie już ma wtyczkę lub wystąpił konflikt z inną wtyczką." + +#~ msgid "&Maximum number of waiting worker processes (needs restart):" +#~ msgstr "" +#~ "&Maksymalna ilość oczekujących procesów roboczych (wymaga ponownego " +#~ "uruchomienia):" + +#~ msgid "" +#~ "Choose you e-book device. If your device is not in the list, choose a \"%s\" " +#~ "device." +#~ msgstr "" +#~ "Wybierz swój czytnik książek, jeśli twojego urządzenia nie ma na liście, " +#~ "wybierz \"%s\" ." + +#~ msgid "" +#~ "Output field to sort on.\n" +#~ "Available fields: author_sort, id, rating, size, timestamp, title.\n" +#~ "Default: '%default'\n" +#~ "Applies to: CSV, XML output formats" +#~ msgstr "" +#~ "Pole wyściowe do sortowania.\n" +#~ "Dostępne pola: author_sort, id, rating, size, timestamp, title.\n" +#~ "Domyślnie: '%default'\n" +#~ "Odnosi się do: formaty wyjściowe CSV, XML" + +#~ msgid "" +#~ "format_date(val, format_string) -- format the value, which must be a date " +#~ "field, using the format_string, returning a string. The formatting codes " +#~ "are: d : the day as number without a leading zero (1 to 31) dd : the " +#~ "day as number with a leading zero (01 to 31) ddd : the abbreviated " +#~ "localized day name (e.g. \"Mon\" to \"Sun\"). dddd : the long localized day " +#~ "name (e.g. \"Monday\" to \"Sunday\"). M : the month as number without a " +#~ "leading zero (1 to 12). MM : the month as number with a leading zero (01 " +#~ "to 12) MMM : the abbreviated localized month name (e.g. \"Jan\" to " +#~ "\"Dec\"). MMMM : the long localized month name (e.g. \"January\" to " +#~ "\"December\"). yy : the year as two digit number (00 to 99). yyyy : the " +#~ "year as four digit number." +#~ msgstr "" +#~ "format_date(val, format_string) -- formatuje wartość, która musi być polem " +#~ "daty, używając format_string, zwraca tekst. Kody formatowania: d : dzień " +#~ "jako liczba bez zera poprzedzającego (1 do 31) dd : dzień jako liczba z " +#~ "zerem poprzedzającym (01 do 31) ddd : skrócona lokalna nazwa dnia (np. " +#~ "\"Pon\" do \"Nie\"). dddd : długa nazwa lokalna dnia (np. \"Poniedziałek\" " +#~ "do \"Niedziela\"). M : miesiąc jako liczba bez zera poprzedzającego (1 do " +#~ "12). MM : miesiąc jako liczba z zerem poprzedzającym (01 do 12) MMM : " +#~ "skrócona lokalna nazwa miesiąca (np. \"Sty\" do \"Gru\"). MMMM : długa nazwa " +#~ "lokalna miesiąca (np. \"Styczeń\" do \"Grudzień\"). yy : rok jako " +#~ "dwucyfrowy numer (00 do 99). yyyy : rok jako czterocyfrowy numer." + +#~ msgid "Skip 'Connect to iTunes' recommendation" +#~ msgstr "Pomiń rekomendację 'Podłącz do iTunes'" + +#~ msgid "Downloads metadata from The Open Library" +#~ msgstr "Pobierz metadane z Open Library" + +#~ msgid "Enable to skip the 'Connect to iTunes' recommendation dialog" +#~ msgstr "Włącz pomijanie okna rekomendacji 'Podłącz do iTunes'" + +#~ msgid "Kindle books from Amazon.uk" +#~ msgstr "Książki dla Kindle z Amazon.uk" + +#~ msgid "Kindle books from Amazon" +#~ msgstr "Książki dla Kindle z Amazon" + +#~ msgid "entertain, enrich, inspire." +#~ msgstr "bawi, uczy, inspiruje." + +#~ msgid "Feel every word" +#~ msgstr "Poczuj każde słowo" + +#~ msgid "Downloads metadata from Overdrive's Content Reserve" +#~ msgstr "Pobiera metadane z Overdrive's Content Reserve" + +#~ msgid "Configure metadata downloading" +#~ msgstr "Konfiguruj pobieranie metadanych" + +#~ msgid "Kindle eBooks" +#~ msgstr "eBooki dla Kindle" diff --git a/src/calibre/translations/pt.po b/src/calibre/translations/pt.po index 89b40e56bf..715180f0b3 100644 --- a/src/calibre/translations/pt.po +++ b/src/calibre/translations/pt.po @@ -7,120 +7,121 @@ msgid "" msgstr "" "Project-Id-Version: calibre\n" "Report-Msgid-Bugs-To: FULL NAME \n" -"POT-Creation-Date: 2011-02-18 21:06+0000\n" -"PO-Revision-Date: 2011-01-24 16:19+0000\n" -"Last-Translator: Alberto Ferreira \n" +"POT-Creation-Date: 2011-05-20 18:12+0000\n" +"PO-Revision-Date: 2011-03-21 20:14+0000\n" +"Last-Translator: Carlos Ricardo Santos \n" "Language-Team: 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-02-19 04:57+0000\n" -"X-Generator: Launchpad (build 12351)\n" +"X-Launchpad-Export-Date: 2011-05-21 04:53+0000\n" +"X-Generator: Launchpad (build 12959)\n" -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/scheduler_ui.py:204 -msgid "day" -msgstr "dias" +#~ msgid "Monday" +#~ msgstr "Segundas-Feiras" -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/scheduler_ui.py:205 -msgid "Monday" -msgstr "Segundas-Feiras" +#~ msgid "Tuesday" +#~ msgstr "Terças-Feiras" -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/scheduler_ui.py:206 -msgid "Tuesday" -msgstr "Terças-Feiras" +#~ msgid "Wednesday" +#~ msgstr "Quartas-Feiras" -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/scheduler_ui.py:207 -msgid "Wednesday" -msgstr "Quartas-Feiras" +#~ msgid "day" +#~ msgstr "dias" -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/scheduler_ui.py:208 -msgid "Thursday" -msgstr "Quintas-Feiras" +#~ msgid "Friday" +#~ msgstr "Sextas-Feiras" -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/scheduler_ui.py:209 -msgid "Friday" -msgstr "Sextas-Feiras" +#~ msgid "Saturday" +#~ msgstr "Sábados" -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/scheduler_ui.py:210 -msgid "Saturday" -msgstr "Sábados" +#~ msgid "Sunday" +#~ msgstr "Domingos" -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/scheduler_ui.py:211 -msgid "Sunday" -msgstr "Domingos" +#~ msgid "Thursday" +#~ msgstr "Quintas-Feiras" -#: /home/kovid/work/calibre/src/calibre/customize/__init__.py:43 +#: /home/kovid/work/calibre/src/calibre/customize/__init__.py:56 msgid "Does absolutely nothing" msgstr "Não faz absolutamente nada" -#: /home/kovid/work/calibre/src/calibre/customize/__init__.py:46 +#: /home/kovid/work/calibre/src/calibre/customize/__init__.py:59 #: /home/kovid/work/calibre/src/calibre/devices/hanvon/driver.py:87 #: /home/kovid/work/calibre/src/calibre/devices/hanvon/driver.py:88 #: /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/books.py:24 -#: /home/kovid/work/calibre/src/calibre/devices/kobo/driver.py:482 +#: /home/kovid/work/calibre/src/calibre/devices/kobo/driver.py:488 #: /home/kovid/work/calibre/src/calibre/devices/nook/driver.py:70 #: /home/kovid/work/calibre/src/calibre/devices/nook/driver.py:71 #: /home/kovid/work/calibre/src/calibre/devices/prs500/books.py:267 #: /home/kovid/work/calibre/src/calibre/devices/prs505/sony_cache.py:660 -#: /home/kovid/work/calibre/src/calibre/devices/usbms/driver.py:403 -#: /home/kovid/work/calibre/src/calibre/ebooks/chm/input.py:97 -#: /home/kovid/work/calibre/src/calibre/ebooks/chm/input.py:100 +#: /home/kovid/work/calibre/src/calibre/devices/usbms/driver.py:467 +#: /home/kovid/work/calibre/src/calibre/ebooks/chm/input.py:106 +#: /home/kovid/work/calibre/src/calibre/ebooks/chm/input.py:109 #: /home/kovid/work/calibre/src/calibre/ebooks/chm/metadata.py:56 -#: /home/kovid/work/calibre/src/calibre/ebooks/comic/input.py:419 +#: /home/kovid/work/calibre/src/calibre/ebooks/comic/input.py:435 #: /home/kovid/work/calibre/src/calibre/ebooks/epub/periodical.py:127 #: /home/kovid/work/calibre/src/calibre/ebooks/fb2/input.py:100 #: /home/kovid/work/calibre/src/calibre/ebooks/fb2/input.py:102 -#: /home/kovid/work/calibre/src/calibre/ebooks/html/input.py:331 -#: /home/kovid/work/calibre/src/calibre/ebooks/html/input.py:334 +#: /home/kovid/work/calibre/src/calibre/ebooks/html/input.py:332 +#: /home/kovid/work/calibre/src/calibre/ebooks/html/input.py:335 #: /home/kovid/work/calibre/src/calibre/ebooks/lrf/html/convert_from.py:1894 #: /home/kovid/work/calibre/src/calibre/ebooks/lrf/html/convert_from.py:1896 #: /home/kovid/work/calibre/src/calibre/ebooks/lrf/output.py:24 -#: /home/kovid/work/calibre/src/calibre/ebooks/metadata/__init__.py:236 -#: /home/kovid/work/calibre/src/calibre/ebooks/metadata/book/base.py:31 -#: /home/kovid/work/calibre/src/calibre/ebooks/metadata/book/base.py:32 -#: /home/kovid/work/calibre/src/calibre/ebooks/metadata/book/base.py:73 -#: /home/kovid/work/calibre/src/calibre/ebooks/metadata/book/base.py:379 -#: /home/kovid/work/calibre/src/calibre/ebooks/metadata/book/base.py:384 -#: /home/kovid/work/calibre/src/calibre/ebooks/metadata/book/base.py:616 +#: /home/kovid/work/calibre/src/calibre/ebooks/metadata/__init__.py:253 +#: /home/kovid/work/calibre/src/calibre/ebooks/metadata/book/base.py:34 +#: /home/kovid/work/calibre/src/calibre/ebooks/metadata/book/base.py:35 +#: /home/kovid/work/calibre/src/calibre/ebooks/metadata/book/base.py:89 +#: /home/kovid/work/calibre/src/calibre/ebooks/metadata/book/base.py:455 +#: /home/kovid/work/calibre/src/calibre/ebooks/metadata/book/base.py:460 +#: /home/kovid/work/calibre/src/calibre/ebooks/metadata/book/base.py:724 #: /home/kovid/work/calibre/src/calibre/ebooks/metadata/ereader.py:36 #: /home/kovid/work/calibre/src/calibre/ebooks/metadata/ereader.py:61 -#: /home/kovid/work/calibre/src/calibre/ebooks/metadata/fb2.py:54 -#: /home/kovid/work/calibre/src/calibre/ebooks/metadata/fetch.py:359 +#: /home/kovid/work/calibre/src/calibre/ebooks/metadata/extz.py:23 +#: /home/kovid/work/calibre/src/calibre/ebooks/metadata/fb2.py:55 #: /home/kovid/work/calibre/src/calibre/ebooks/metadata/meta.py:36 #: /home/kovid/work/calibre/src/calibre/ebooks/metadata/meta.py:64 #: /home/kovid/work/calibre/src/calibre/ebooks/metadata/meta.py:66 #: /home/kovid/work/calibre/src/calibre/ebooks/metadata/meta.py:124 #: /home/kovid/work/calibre/src/calibre/ebooks/metadata/meta.py:126 -#: /home/kovid/work/calibre/src/calibre/ebooks/metadata/opf2.py:1026 -#: /home/kovid/work/calibre/src/calibre/ebooks/metadata/opf2.py:1136 -#: /home/kovid/work/calibre/src/calibre/ebooks/metadata/pdb.py:39 +#: /home/kovid/work/calibre/src/calibre/ebooks/metadata/opf2.py:1066 +#: /home/kovid/work/calibre/src/calibre/ebooks/metadata/opf2.py:1176 +#: /home/kovid/work/calibre/src/calibre/ebooks/metadata/pdb.py:41 #: /home/kovid/work/calibre/src/calibre/ebooks/metadata/pdf.py:29 +#: /home/kovid/work/calibre/src/calibre/ebooks/metadata/plucker.py:25 #: /home/kovid/work/calibre/src/calibre/ebooks/metadata/pml.py:23 #: /home/kovid/work/calibre/src/calibre/ebooks/metadata/pml.py:49 #: /home/kovid/work/calibre/src/calibre/ebooks/metadata/rtf.py:88 #: /home/kovid/work/calibre/src/calibre/ebooks/metadata/rtf.py:91 #: /home/kovid/work/calibre/src/calibre/ebooks/metadata/rtf.py:101 #: /home/kovid/work/calibre/src/calibre/ebooks/metadata/snb.py:16 -#: /home/kovid/work/calibre/src/calibre/ebooks/metadata/sources/google.py:75 +#: /home/kovid/work/calibre/src/calibre/ebooks/metadata/sources/base.py:48 +#: /home/kovid/work/calibre/src/calibre/ebooks/metadata/sources/base.py:298 +#: /home/kovid/work/calibre/src/calibre/ebooks/metadata/sources/covers.py:79 +#: /home/kovid/work/calibre/src/calibre/ebooks/metadata/sources/covers.py:81 +#: /home/kovid/work/calibre/src/calibre/ebooks/metadata/sources/douban.py:78 +#: /home/kovid/work/calibre/src/calibre/ebooks/metadata/sources/google.py:81 +#: /home/kovid/work/calibre/src/calibre/ebooks/metadata/sources/identify.py:208 +#: /home/kovid/work/calibre/src/calibre/ebooks/metadata/sources/identify.py:303 +#: /home/kovid/work/calibre/src/calibre/ebooks/metadata/sources/identify.py:305 +#: /home/kovid/work/calibre/src/calibre/ebooks/metadata/sources/identify.py:406 #: /home/kovid/work/calibre/src/calibre/ebooks/metadata/txt.py:18 -#: /home/kovid/work/calibre/src/calibre/ebooks/metadata/txtz.py:23 -#: /home/kovid/work/calibre/src/calibre/ebooks/mobi/reader.py:42 -#: /home/kovid/work/calibre/src/calibre/ebooks/mobi/reader.py:68 -#: /home/kovid/work/calibre/src/calibre/ebooks/mobi/reader.py:81 -#: /home/kovid/work/calibre/src/calibre/ebooks/mobi/reader.py:124 -#: /home/kovid/work/calibre/src/calibre/ebooks/mobi/reader.py:158 -#: /home/kovid/work/calibre/src/calibre/ebooks/mobi/reader.py:663 -#: /home/kovid/work/calibre/src/calibre/ebooks/mobi/reader.py:878 -#: /home/kovid/work/calibre/src/calibre/ebooks/mobi/reader.py:880 -#: /home/kovid/work/calibre/src/calibre/ebooks/odt/input.py:49 -#: /home/kovid/work/calibre/src/calibre/ebooks/odt/input.py:51 -#: /home/kovid/work/calibre/src/calibre/ebooks/oeb/base.py:958 -#: /home/kovid/work/calibre/src/calibre/ebooks/oeb/base.py:963 -#: /home/kovid/work/calibre/src/calibre/ebooks/oeb/base.py:1029 -#: /home/kovid/work/calibre/src/calibre/ebooks/oeb/reader.py:145 -#: /home/kovid/work/calibre/src/calibre/ebooks/oeb/reader.py:152 +#: /home/kovid/work/calibre/src/calibre/ebooks/mobi/reader.py:43 +#: /home/kovid/work/calibre/src/calibre/ebooks/mobi/reader.py:69 +#: /home/kovid/work/calibre/src/calibre/ebooks/mobi/reader.py:82 +#: /home/kovid/work/calibre/src/calibre/ebooks/mobi/reader.py:125 +#: /home/kovid/work/calibre/src/calibre/ebooks/mobi/reader.py:159 +#: /home/kovid/work/calibre/src/calibre/ebooks/mobi/reader.py:714 +#: /home/kovid/work/calibre/src/calibre/ebooks/mobi/reader.py:961 +#: /home/kovid/work/calibre/src/calibre/ebooks/mobi/reader.py:963 +#: /home/kovid/work/calibre/src/calibre/ebooks/odt/input.py:99 +#: /home/kovid/work/calibre/src/calibre/ebooks/odt/input.py:101 +#: /home/kovid/work/calibre/src/calibre/ebooks/oeb/base.py:1001 +#: /home/kovid/work/calibre/src/calibre/ebooks/oeb/base.py:1006 +#: /home/kovid/work/calibre/src/calibre/ebooks/oeb/base.py:1072 +#: /home/kovid/work/calibre/src/calibre/ebooks/oeb/reader.py:144 +#: /home/kovid/work/calibre/src/calibre/ebooks/oeb/reader.py:151 #: /home/kovid/work/calibre/src/calibre/ebooks/oeb/transforms/jacket.py:65 #: /home/kovid/work/calibre/src/calibre/ebooks/oeb/transforms/jacket.py:112 #: /home/kovid/work/calibre/src/calibre/ebooks/oeb/transforms/jacket.py:119 @@ -144,108 +145,119 @@ msgstr "Não faz absolutamente nada" #: /home/kovid/work/calibre/src/calibre/ebooks/pdf/manipulate/rotate.py:63 #: /home/kovid/work/calibre/src/calibre/ebooks/pdf/manipulate/split.py:81 #: /home/kovid/work/calibre/src/calibre/ebooks/pdf/manipulate/split.py:82 -#: /home/kovid/work/calibre/src/calibre/ebooks/pdf/writer.py:100 #: /home/kovid/work/calibre/src/calibre/ebooks/pdf/writer.py:101 -#: /home/kovid/work/calibre/src/calibre/ebooks/rtf/input.py:312 -#: /home/kovid/work/calibre/src/calibre/ebooks/rtf/input.py:314 -#: /home/kovid/work/calibre/src/calibre/gui2/__init__.py:308 -#: /home/kovid/work/calibre/src/calibre/gui2/__init__.py:315 -#: /home/kovid/work/calibre/src/calibre/gui2/actions/add.py:101 -#: /home/kovid/work/calibre/src/calibre/gui2/actions/edit_metadata.py:332 -#: /home/kovid/work/calibre/src/calibre/gui2/actions/edit_metadata.py:335 +#: /home/kovid/work/calibre/src/calibre/ebooks/pdf/writer.py:102 +#: /home/kovid/work/calibre/src/calibre/ebooks/rtf/input.py:313 +#: /home/kovid/work/calibre/src/calibre/ebooks/rtf/input.py:315 +#: /home/kovid/work/calibre/src/calibre/gui2/__init__.py:347 +#: /home/kovid/work/calibre/src/calibre/gui2/__init__.py:355 +#: /home/kovid/work/calibre/src/calibre/gui2/actions/add.py:156 +#: /home/kovid/work/calibre/src/calibre/gui2/actions/edit_metadata.py:364 +#: /home/kovid/work/calibre/src/calibre/gui2/actions/edit_metadata.py:367 #: /home/kovid/work/calibre/src/calibre/gui2/add.py:160 #: /home/kovid/work/calibre/src/calibre/gui2/add.py:167 +#: /home/kovid/work/calibre/src/calibre/gui2/book_details.py:519 #: /home/kovid/work/calibre/src/calibre/gui2/convert/__init__.py:42 #: /home/kovid/work/calibre/src/calibre/gui2/convert/metadata.py:122 #: /home/kovid/work/calibre/src/calibre/gui2/convert/metadata.py:151 #: /home/kovid/work/calibre/src/calibre/gui2/convert/metadata.py:153 -#: /home/kovid/work/calibre/src/calibre/gui2/device.py:1089 -#: /home/kovid/work/calibre/src/calibre/gui2/device.py:1092 +#: /home/kovid/work/calibre/src/calibre/gui2/device.py:1120 +#: /home/kovid/work/calibre/src/calibre/gui2/device.py:1123 #: /home/kovid/work/calibre/src/calibre/gui2/dialogs/add_empty_book.py:56 #: /home/kovid/work/calibre/src/calibre/gui2/dialogs/add_empty_book.py:68 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/book_info.py:127 #: /home/kovid/work/calibre/src/calibre/gui2/dialogs/comicconf.py:47 -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/fetch_metadata.py:145 -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/fetch_metadata.py:185 -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single.py:732 -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/scheduler.py:193 -#: /home/kovid/work/calibre/src/calibre/gui2/email.py:236 -#: /home/kovid/work/calibre/src/calibre/gui2/email.py:245 -#: /home/kovid/work/calibre/src/calibre/gui2/library/models.py:421 -#: /home/kovid/work/calibre/src/calibre/gui2/library/models.py:440 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/scheduler.py:366 +#: /home/kovid/work/calibre/src/calibre/gui2/email.py:152 +#: /home/kovid/work/calibre/src/calibre/gui2/email.py:167 +#: /home/kovid/work/calibre/src/calibre/gui2/library/models.py:397 #: /home/kovid/work/calibre/src/calibre/gui2/library/models.py:972 -#: /home/kovid/work/calibre/src/calibre/gui2/library/models.py:1165 -#: /home/kovid/work/calibre/src/calibre/gui2/metadata/basic_widgets.py:70 -#: /home/kovid/work/calibre/src/calibre/gui2/metadata/basic_widgets.py:167 -#: /home/kovid/work/calibre/src/calibre/gui2/metadata/basic_widgets.py:185 -#: /home/kovid/work/calibre/src/calibre/gui2/metadata/bulk_download.py:112 -#: /home/kovid/work/calibre/src/calibre/gui2/viewer/main.py:197 -#: /home/kovid/work/calibre/src/calibre/library/cli.py:215 +#: /home/kovid/work/calibre/src/calibre/gui2/library/models.py:1148 +#: /home/kovid/work/calibre/src/calibre/gui2/library/models.py:1151 +#: /home/kovid/work/calibre/src/calibre/gui2/library/models.py:1154 +#: /home/kovid/work/calibre/src/calibre/gui2/library/models.py:1239 +#: /home/kovid/work/calibre/src/calibre/gui2/metadata/basic_widgets.py:82 +#: /home/kovid/work/calibre/src/calibre/gui2/metadata/basic_widgets.py:201 +#: /home/kovid/work/calibre/src/calibre/gui2/metadata/basic_widgets.py:220 +#: /home/kovid/work/calibre/src/calibre/gui2/metadata/bulk_download.py:119 +#: /home/kovid/work/calibre/src/calibre/gui2/metadata/single.py:358 +#: /home/kovid/work/calibre/src/calibre/gui2/metadata/single_download.py:160 +#: /home/kovid/work/calibre/src/calibre/gui2/metadata/single_download.py:164 +#: /home/kovid/work/calibre/src/calibre/gui2/store/google_books_plugin.py:90 +#: /home/kovid/work/calibre/src/calibre/gui2/store/search/models.py:151 +#: /home/kovid/work/calibre/src/calibre/gui2/viewer/main.py:199 +#: /home/kovid/work/calibre/src/calibre/library/cli.py:217 #: /home/kovid/work/calibre/src/calibre/library/database.py:914 -#: /home/kovid/work/calibre/src/calibre/library/database2.py:448 -#: /home/kovid/work/calibre/src/calibre/library/database2.py:454 -#: /home/kovid/work/calibre/src/calibre/library/database2.py:464 -#: /home/kovid/work/calibre/src/calibre/library/database2.py:1568 -#: /home/kovid/work/calibre/src/calibre/library/database2.py:1671 -#: /home/kovid/work/calibre/src/calibre/library/database2.py:2574 -#: /home/kovid/work/calibre/src/calibre/library/database2.py:2576 -#: /home/kovid/work/calibre/src/calibre/library/database2.py:2707 +#: /home/kovid/work/calibre/src/calibre/library/database2.py:502 +#: /home/kovid/work/calibre/src/calibre/library/database2.py:510 +#: /home/kovid/work/calibre/src/calibre/library/database2.py:521 +#: /home/kovid/work/calibre/src/calibre/library/database2.py:1800 +#: /home/kovid/work/calibre/src/calibre/library/database2.py:1937 +#: /home/kovid/work/calibre/src/calibre/library/database2.py:2944 +#: /home/kovid/work/calibre/src/calibre/library/database2.py:2946 +#: /home/kovid/work/calibre/src/calibre/library/database2.py:3079 #: /home/kovid/work/calibre/src/calibre/library/server/mobile.py:233 -#: /home/kovid/work/calibre/src/calibre/library/server/opds.py:158 -#: /home/kovid/work/calibre/src/calibre/library/server/opds.py:161 +#: /home/kovid/work/calibre/src/calibre/library/server/opds.py:156 +#: /home/kovid/work/calibre/src/calibre/library/server/opds.py:159 #: /home/kovid/work/calibre/src/calibre/library/server/xml.py:79 #: /home/kovid/work/calibre/src/calibre/utils/localization.py:131 #: /home/kovid/work/calibre/src/calibre/utils/podofo/__init__.py:46 #: /home/kovid/work/calibre/src/calibre/utils/podofo/__init__.py:64 #: /home/kovid/work/calibre/src/calibre/utils/podofo/__init__.py:78 -#: /home/kovid/work/calibre/src/calibre/web/feeds/recipes/collection.py:47 -#: /home/kovid/work/calibre/src/calibre/web/feeds/recipes/collection.py:55 +#: /home/kovid/work/calibre/src/calibre/web/feeds/recipes/collection.py:46 +#: /home/kovid/work/calibre/src/calibre/web/feeds/recipes/collection.py:54 msgid "Unknown" msgstr "Desconhecido(a)" -#: /home/kovid/work/calibre/src/calibre/customize/__init__.py:64 +#: /home/kovid/work/calibre/src/calibre/customize/__init__.py:77 msgid "Base" msgstr "Padrão" -#: /home/kovid/work/calibre/src/calibre/customize/__init__.py:135 +#: /home/kovid/work/calibre/src/calibre/customize/__init__.py:148 msgid "Customize" msgstr "Personalizar" -#: /home/kovid/work/calibre/src/calibre/customize/__init__.py:143 -#: /home/kovid/work/calibre/src/calibre/gui2/actions/preferences.py:39 -#: /home/kovid/work/calibre/src/calibre/gui2/actions/preferences.py:44 +#: /home/kovid/work/calibre/src/calibre/customize/__init__.py:156 +#: /home/kovid/work/calibre/src/calibre/gui2/actions/preferences.py:43 +#: /home/kovid/work/calibre/src/calibre/gui2/actions/preferences.py:48 msgid "Cannot configure" msgstr "É impossível configurar" -#: /home/kovid/work/calibre/src/calibre/customize/__init__.py:305 +#: /home/kovid/work/calibre/src/calibre/customize/__init__.py:318 msgid "File type" msgstr "Tipo de ficheiro" -#: /home/kovid/work/calibre/src/calibre/customize/__init__.py:341 +#: /home/kovid/work/calibre/src/calibre/customize/__init__.py:354 msgid "Metadata reader" msgstr "Leitor de metadados" -#: /home/kovid/work/calibre/src/calibre/customize/__init__.py:371 +#: /home/kovid/work/calibre/src/calibre/customize/__init__.py:384 msgid "Metadata writer" msgstr "Gravador de metadados" -#: /home/kovid/work/calibre/src/calibre/customize/__init__.py:401 +#: /home/kovid/work/calibre/src/calibre/customize/__init__.py:414 msgid "Catalog generator" msgstr "Gerador de catalogo" -#: /home/kovid/work/calibre/src/calibre/customize/__init__.py:510 +#: /home/kovid/work/calibre/src/calibre/customize/__init__.py:523 msgid "User Interface Action" msgstr "Ação de interface do usuário" -#: /home/kovid/work/calibre/src/calibre/customize/__init__.py:536 +#: /home/kovid/work/calibre/src/calibre/customize/__init__.py:557 #: /home/kovid/work/calibre/src/calibre/gui2/actions/preferences.py:18 -#: /home/kovid/work/calibre/src/calibre/gui2/actions/preferences.py:23 -#: /home/kovid/work/calibre/src/calibre/gui2/preferences/main.py:190 -#: /home/kovid/work/calibre/src/calibre/gui2/preferences/main.py:280 -#: /home/kovid/work/calibre/src/calibre/gui2/preferences/main.py:302 +#: /home/kovid/work/calibre/src/calibre/gui2/actions/preferences.py:22 +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/main.py:197 +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/main.py:287 +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/main.py:309 #: /home/kovid/work/calibre/src/calibre/gui2/viewer/main_ui.py:206 msgid "Preferences" msgstr "Preferências" +#: /home/kovid/work/calibre/src/calibre/customize/__init__.py:609 +#: /home/kovid/work/calibre/src/calibre/gui2/store/search/models.py:33 +msgid "Store" +msgstr "" + #: /home/kovid/work/calibre/src/calibre/customize/builtins.py:18 msgid "" "Follow all local links in an HTML file and create a ZIP file containing all " @@ -280,95 +292,100 @@ msgid "" "Textile references to images. The referenced images as well as the TXT file " "are added to the archive." msgstr "" +"Cria um arquivo TXTZ quando um ficheiro TXT importado contém referências " +"para imagens em Markdown ou Textile. Tanto as imagens referenciadas como o " +"próprio ficheiro TXT são adicionados ao arquivo." -#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:166 +#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:168 msgid "Extract cover from comic files" msgstr "Extrai a capa dos ficheiros de banda desenhada" -#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:195 -#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:206 -#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:218 +#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:205 +#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:216 #: /home/kovid/work/calibre/src/calibre/customize/builtins.py:228 #: /home/kovid/work/calibre/src/calibre/customize/builtins.py:238 -#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:249 +#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:248 #: /home/kovid/work/calibre/src/calibre/customize/builtins.py:259 -#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:269 -#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:279 -#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:289 -#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:299 -#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:309 +#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:270 +#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:280 +#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:290 +#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:300 +#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:310 #: /home/kovid/work/calibre/src/calibre/customize/builtins.py:320 -#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:332 +#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:330 +#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:341 #: /home/kovid/work/calibre/src/calibre/customize/builtins.py:353 -#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:364 #: /home/kovid/work/calibre/src/calibre/customize/builtins.py:374 #: /home/kovid/work/calibre/src/calibre/customize/builtins.py:385 #: /home/kovid/work/calibre/src/calibre/customize/builtins.py:395 #: /home/kovid/work/calibre/src/calibre/customize/builtins.py:406 +#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:416 +#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:427 msgid "Read metadata from %s files" msgstr "Lê os metadados dos ficheiros %s" -#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:343 +#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:364 msgid "Read metadata from ebooks in RAR archives" msgstr "Lê os metadados dos livros digitais, contidos nos arquivos RAR" -#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:417 +#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:438 msgid "Read metadata from ebooks in ZIP archives" msgstr "Lê os metadados dos livros digitais, contidos nos arquivos ZIP" -#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:430 -#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:440 -#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:450 +#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:451 #: /home/kovid/work/calibre/src/calibre/customize/builtins.py:472 -#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:483 -#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:493 +#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:482 +#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:504 +#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:515 +#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:525 msgid "Set metadata in %s files" msgstr "Define os metadados nos ficheiros %s" #: /home/kovid/work/calibre/src/calibre/customize/builtins.py:461 -#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:504 +#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:493 +#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:536 msgid "Set metadata from %s files" msgstr "Define os metadados a partir dos ficheiros %s" -#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:824 +#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:873 msgid "Look and Feel" msgstr "Aparência e Tacto" -#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:826 -#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:838 -#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:849 -#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:860 -#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:872 +#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:875 +#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:887 +#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:898 +#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:909 +#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:921 msgid "Interface" msgstr "Interface" -#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:830 +#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:879 msgid "Adjust the look and feel of the calibre interface to suit your tastes" msgstr "Ajuste o interface de utilizador do Calibre às suas necessidades" -#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:836 +#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:885 msgid "Behavior" msgstr "Comportamento" -#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:842 +#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:891 msgid "Change the way calibre behaves" msgstr "Altere o comportamento do Calibre" -#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:847 -#: /home/kovid/work/calibre/src/calibre/gui2/library/views.py:217 +#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:896 +#: /home/kovid/work/calibre/src/calibre/gui2/library/views.py:220 msgid "Add your own columns" msgstr "Adicione as suas próprias colunas" -#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:853 +#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:902 msgid "Add/remove your own columns to the calibre book list" msgstr "" "Adicione/remova as suas próprias colunas à lista de livros do Calibre" -#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:858 -msgid "Customize the toolbar" -msgstr "Personalize a barra de ferramentas" +#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:907 +msgid "Toolbar" +msgstr "Barra de ferramentas" -#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:864 +#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:913 msgid "" "Customize the toolbars and context menus, changing which actions are " "available in each" @@ -376,66 +393,66 @@ msgstr "" "Personalize as barras de ferramentas e menus de contexto, alterando as " "acções que estão disponíveis em cada um" -#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:870 -msgid "Customize searching" +#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:919 +msgid "Searching" msgstr "" -#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:876 +#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:925 msgid "Customize the way searching for books works in calibre" -msgstr "" +msgstr "Personalizar o modo como funciona a pesquisa de livros no calibre" -#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:881 +#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:930 msgid "Input Options" msgstr "Opções de entrada" -#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:883 -#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:894 -#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:905 +#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:932 +#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:943 +#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:954 msgid "Conversion" msgstr "Conversão" -#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:887 +#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:936 msgid "Set conversion options specific to each input format" msgstr "Defina opções especifícas para cada formato de entrada" -#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:892 +#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:941 msgid "Common Options" msgstr "Opções Comuns" -#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:898 +#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:947 msgid "Set conversion options common to all formats" msgstr "Defina opções comuns a todos os formatos" -#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:903 +#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:952 msgid "Output Options" msgstr "Opções de saída" -#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:909 +#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:958 msgid "Set conversion options specific to each output format" msgstr "Defina opções específicas para cada formato de saída" -#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:914 +#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:963 msgid "Adding books" msgstr "A adicionar livros" -#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:916 -#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:928 -#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:940 -#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:952 +#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:965 +#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:977 +#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:989 +#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:1001 msgid "Import/Export" msgstr "Importar/Exportar" -#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:920 +#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:969 msgid "Control how calibre reads metadata from files when adding books" msgstr "" "Controlar a forma como o Calibre revê metadados dos ficheiros ao adicionar " "livros" -#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:926 +#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:975 msgid "Saving books to disk" msgstr "A gravar livros para o disco" -#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:932 +#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:981 msgid "" "Control how calibre exports files from its database to disk when using Save " "to disk" @@ -443,47 +460,48 @@ msgstr "" "Controle a forma como o Calibre exporta ficheiro da sua base de dados quando " "grava para o disco" -#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:938 +#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:987 msgid "Sending books to devices" msgstr "Enviar livros para os dispositivos" -#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:944 +#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:993 msgid "Control how calibre transfers files to your ebook reader" msgstr "Controle a forma como o Calibre transfere livros para o seu e-reader" -#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:950 +#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:999 msgid "Metadata plugboards" msgstr "Módulos de extensão de metadados" -#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:956 +#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:1005 msgid "Change metadata fields before saving/sending" msgstr "Alterar os metadados antes de gravar/enviar" -#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:961 +#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:1010 msgid "Template Functions" -msgstr "" +msgstr "Funções de Template" -#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:963 -#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:999 -#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:1011 -#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:1022 +#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:1012 +#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:1059 +#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:1071 +#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:1082 msgid "Advanced" msgstr "Avançadas" -#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:967 +#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:1016 msgid "Create your own template functions" -msgstr "" +msgstr "Criar as suas próprias funções de template" -#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:972 +#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:1021 msgid "Sharing books by email" msgstr "Partilhar livros por e-mail" -#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:974 -#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:986 +#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:1023 +#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:1035 +#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:1048 msgid "Sharing" msgstr "Partilhar" -#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:978 +#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:1027 msgid "" "Setup sharing of books via email. Can be used for automatic sending of " "downloaded news to your devices" @@ -491,11 +509,11 @@ msgstr "" "Configurar partilha de livros através de correio electrónico. É possível " "enviar automaticamente as notícias transferidas para os seus dispositivos" -#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:984 +#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:1033 msgid "Sharing over the net" msgstr "Partilha através da Internet" -#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:990 +#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:1039 msgid "" "Setup the calibre Content Server which will give you access to your calibre " "library from anywhere, on any device, over the internet" @@ -504,31 +522,145 @@ msgstr "" "biblioteca Calibre através da Internet, independentemente do dispositivo ou " "sítio" -#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:997 -#: /home/kovid/work/calibre/src/calibre/gui2/preferences/plugins.py:267 +#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:1046 +msgid "Metadata download" +msgstr "Descarregar metadados" + +#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:1052 +msgid "Control how calibre downloads ebook metadata from the net" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:1057 +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/plugins.py:273 msgid "Plugins" msgstr "Extras" -#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:1003 +#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:1063 msgid "Add/remove/customize various bits of calibre functionality" msgstr "Adicionar/remover/personalizar as funcionalidades do Calibre" -#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:1009 +#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:1069 msgid "Tweaks" msgstr "Ajustes" -#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:1015 +#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:1075 msgid "Fine tune how calibre behaves in various contexts" msgstr "Ajustar o comportamento do Calibre em vários contextos" -#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:1020 +#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:1080 msgid "Miscellaneous" msgstr "Outras Opções" -#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:1026 +#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:1086 msgid "Miscellaneous advanced configuration" msgstr "Outras opções de configuração" +#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:1098 +msgid "Kindle books from Amazon." +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:1103 +msgid "Kindle books from Amazon.de." +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:1108 +msgid "Kindle books from Amazon.uk." +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:1113 +msgid "" +"Free Books : Download & Streaming : Ebook and Texts Archive : Internet " +"Archive." +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:1119 +msgid "Ebooks for readers." +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:1124 +msgid "Books, Textbooks, eBooks, Toys, Games and More." +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:1129 +msgid "Der eBook Shop." +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:1134 +msgid "Publishers of fine books." +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:1139 +msgid "World Famous eBook Store." +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:1144 +msgid "The digital bookstore." +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:1149 +msgid "EPUBReaders eBook Shop." +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:1154 +msgid "Entertain, enrich, inspire." +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:1159 +msgid "Read anywhere." +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:1164 +msgid "Foyles of London, online." +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:1170 +msgid "Zaczarowany świat książek" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:1175 +msgid "Google Books" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:1180 +msgid "The first producer of free ebooks." +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:1185 +msgid "eReading: anytime. anyplace." +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:1190 +msgid "The best ebooks at the best price: free!" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:1195 +msgid "Ebooks handcrafted with the utmost care." +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:1201 +msgid "Audiobooki mp3, ebooki, prasa - księgarnia internetowa." +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:1206 +msgid "One web page for every book." +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:1211 +msgid "DRM-Free tech ebooks." +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:1216 +msgid "The Pragmatic Bookshelf" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:1221 +msgid "Your ebook. Your way." +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:1226 +msgid "Feel every word." +msgstr "" + #: /home/kovid/work/calibre/src/calibre/customize/conversion.py:102 msgid "Conversion Input" msgstr "Origem da conversão" @@ -571,7 +703,7 @@ msgstr "" "sobre o documento de origem." #: /home/kovid/work/calibre/src/calibre/customize/profiles.py:61 -#: /home/kovid/work/calibre/src/calibre/customize/profiles.py:453 +#: /home/kovid/work/calibre/src/calibre/customize/profiles.py:454 msgid "" "This profile is intended for the SONY PRS line. The 500/505/600/700 etc." msgstr "" @@ -582,62 +714,62 @@ msgid "This profile is intended for the SONY PRS 300." msgstr "Este perfil funciona com o SONY PRS 300." #: /home/kovid/work/calibre/src/calibre/customize/profiles.py:82 -#: /home/kovid/work/calibre/src/calibre/customize/profiles.py:493 +#: /home/kovid/work/calibre/src/calibre/customize/profiles.py:494 msgid "This profile is intended for the SONY PRS-900." msgstr "Este perfil funciona com o SONY SONY PRS-900." #: /home/kovid/work/calibre/src/calibre/customize/profiles.py:90 -#: /home/kovid/work/calibre/src/calibre/customize/profiles.py:538 +#: /home/kovid/work/calibre/src/calibre/customize/profiles.py:539 msgid "This profile is intended for the Microsoft Reader." msgstr "Este perfil é destinado ao Microsoft Reader." #: /home/kovid/work/calibre/src/calibre/customize/profiles.py:101 -#: /home/kovid/work/calibre/src/calibre/customize/profiles.py:549 +#: /home/kovid/work/calibre/src/calibre/customize/profiles.py:550 msgid "This profile is intended for the Mobipocket books." msgstr "Este perfil é destinado aos livros Mobipocket." #: /home/kovid/work/calibre/src/calibre/customize/profiles.py:114 -#: /home/kovid/work/calibre/src/calibre/customize/profiles.py:562 +#: /home/kovid/work/calibre/src/calibre/customize/profiles.py:563 msgid "This profile is intended for the Hanlin V3 and its clones." msgstr "Este perfil é destinado ao Hanlin V3 e aos seus clones." #: /home/kovid/work/calibre/src/calibre/customize/profiles.py:126 -#: /home/kovid/work/calibre/src/calibre/customize/profiles.py:574 +#: /home/kovid/work/calibre/src/calibre/customize/profiles.py:575 msgid "This profile is intended for the Hanlin V5 and its clones." msgstr "Este perfil funciona com o Hanlin V5 e clones." #: /home/kovid/work/calibre/src/calibre/customize/profiles.py:136 -#: /home/kovid/work/calibre/src/calibre/customize/profiles.py:582 +#: /home/kovid/work/calibre/src/calibre/customize/profiles.py:583 msgid "This profile is intended for the Cybook G3." msgstr "Este perfil é destinado ao Cybook G3." #: /home/kovid/work/calibre/src/calibre/customize/profiles.py:149 -#: /home/kovid/work/calibre/src/calibre/customize/profiles.py:596 +#: /home/kovid/work/calibre/src/calibre/customize/profiles.py:597 msgid "This profile is intended for the Cybook Opus." msgstr "Este perfil é destinado ao Cybook Opus." #: /home/kovid/work/calibre/src/calibre/customize/profiles.py:161 -#: /home/kovid/work/calibre/src/calibre/customize/profiles.py:609 +#: /home/kovid/work/calibre/src/calibre/customize/profiles.py:610 msgid "This profile is intended for the Amazon Kindle." msgstr "Este perfil é destinado ao Amazon Kindle." #: /home/kovid/work/calibre/src/calibre/customize/profiles.py:173 -#: /home/kovid/work/calibre/src/calibre/customize/profiles.py:659 +#: /home/kovid/work/calibre/src/calibre/customize/profiles.py:660 msgid "This profile is intended for the Irex Illiad." msgstr "Este perfil é destinado ao Irex Illiad." #: /home/kovid/work/calibre/src/calibre/customize/profiles.py:185 -#: /home/kovid/work/calibre/src/calibre/customize/profiles.py:672 +#: /home/kovid/work/calibre/src/calibre/customize/profiles.py:673 msgid "This profile is intended for the IRex Digital Reader 1000." msgstr "Este perfil é destinado ao IRex Digital Reader 1000." #: /home/kovid/work/calibre/src/calibre/customize/profiles.py:198 -#: /home/kovid/work/calibre/src/calibre/customize/profiles.py:686 +#: /home/kovid/work/calibre/src/calibre/customize/profiles.py:687 msgid "This profile is intended for the IRex Digital Reader 800." msgstr "Este perfil funciona com o IRex Digital Reader 800." #: /home/kovid/work/calibre/src/calibre/customize/profiles.py:210 -#: /home/kovid/work/calibre/src/calibre/customize/profiles.py:700 +#: /home/kovid/work/calibre/src/calibre/customize/profiles.py:701 msgid "This profile is intended for the B&N Nook." msgstr "Este perfil funciona com o B&N Nook." @@ -659,11 +791,11 @@ msgid "" "Intended for the iPad and similar devices with a resolution of 768x1024" msgstr "Funciona com o iPad e aparelhos similares com resolução de 768x1024." -#: /home/kovid/work/calibre/src/calibre/customize/profiles.py:437 +#: /home/kovid/work/calibre/src/calibre/customize/profiles.py:438 msgid "Intended for generic tablet devices, does no resizing of images" msgstr "Destinado a dispositivos genéricos de tablet" -#: /home/kovid/work/calibre/src/calibre/customize/profiles.py:445 +#: /home/kovid/work/calibre/src/calibre/customize/profiles.py:446 msgid "" "Intended for the Samsung Galaxy and similar tablet devices with a resolution " "of 600x1280" @@ -671,27 +803,27 @@ msgstr "" "Destinado ao Samsung Galaxy e dispositivos tablets similares com uma " "resolução de 600x1280" -#: /home/kovid/work/calibre/src/calibre/customize/profiles.py:471 +#: /home/kovid/work/calibre/src/calibre/customize/profiles.py:472 msgid "This profile is intended for the Kobo Reader." msgstr "Este perfil funciona com o Kobo Reader." -#: /home/kovid/work/calibre/src/calibre/customize/profiles.py:484 +#: /home/kovid/work/calibre/src/calibre/customize/profiles.py:485 msgid "This profile is intended for the SONY PRS-300." msgstr "Este perfil destina-se ao Sony PRS-300." -#: /home/kovid/work/calibre/src/calibre/customize/profiles.py:502 +#: /home/kovid/work/calibre/src/calibre/customize/profiles.py:503 msgid "Suitable for use with any e-ink device" -msgstr "" +msgstr "Apropriado para uso em qualquer disposito e-ink" -#: /home/kovid/work/calibre/src/calibre/customize/profiles.py:509 +#: /home/kovid/work/calibre/src/calibre/customize/profiles.py:510 msgid "Suitable for use with any large screen e-ink device" -msgstr "" +msgstr "Apropriado para uso em qualquer disposito e-ink de ecrã largo" -#: /home/kovid/work/calibre/src/calibre/customize/profiles.py:518 +#: /home/kovid/work/calibre/src/calibre/customize/profiles.py:519 msgid "This profile is intended for the 5-inch JetBook." msgstr "Este perfil é destinado ao JetBook de 5 polegadas." -#: /home/kovid/work/calibre/src/calibre/customize/profiles.py:527 +#: /home/kovid/work/calibre/src/calibre/customize/profiles.py:528 msgid "" "This profile is intended for the SONY PRS line. The 500/505/700 etc, in " "landscape mode. Mainly useful for comics." @@ -699,47 +831,43 @@ msgstr "" "Este perfil é destinado à linha SONY PRS. A 500/505/700, etc, em modo " "paisagem. Principalmente útil para banda desenhada." -#: /home/kovid/work/calibre/src/calibre/customize/profiles.py:635 +#: /home/kovid/work/calibre/src/calibre/customize/profiles.py:636 msgid "This profile is intended for the Amazon Kindle DX." msgstr "Este perfil é destinado ao Amazon Kindle DX." -#: /home/kovid/work/calibre/src/calibre/customize/profiles.py:712 +#: /home/kovid/work/calibre/src/calibre/customize/profiles.py:713 msgid "This profile is intended for the B&N Nook Color." msgstr "Este perfil destina-se ao dispositivo B&N Nook Color." -#: /home/kovid/work/calibre/src/calibre/customize/profiles.py:723 +#: /home/kovid/work/calibre/src/calibre/customize/profiles.py:724 msgid "This profile is intended for the Sanda Bambook." msgstr "Este perfil destina-se ao Sanda Bambook." -#: /home/kovid/work/calibre/src/calibre/customize/ui.py:35 +#: /home/kovid/work/calibre/src/calibre/customize/ui.py:31 msgid "Installed plugins" msgstr "Extras instalados" -#: /home/kovid/work/calibre/src/calibre/customize/ui.py:36 +#: /home/kovid/work/calibre/src/calibre/customize/ui.py:32 msgid "Mapping for filetype plugins" msgstr "Estrutura para os extras de tipo de ficheiro" -#: /home/kovid/work/calibre/src/calibre/customize/ui.py:37 +#: /home/kovid/work/calibre/src/calibre/customize/ui.py:33 msgid "Local plugin customization" msgstr "Personalização do extra local" -#: /home/kovid/work/calibre/src/calibre/customize/ui.py:38 +#: /home/kovid/work/calibre/src/calibre/customize/ui.py:34 msgid "Disabled plugins" msgstr "Extras desactivados" -#: /home/kovid/work/calibre/src/calibre/customize/ui.py:39 +#: /home/kovid/work/calibre/src/calibre/customize/ui.py:35 msgid "Enabled plugins" msgstr "Módulos de extensão activados" -#: /home/kovid/work/calibre/src/calibre/customize/ui.py:94 -msgid "No valid plugin found in " -msgstr "Nenhum extra válido encontrado em " - -#: /home/kovid/work/calibre/src/calibre/customize/ui.py:520 +#: /home/kovid/work/calibre/src/calibre/customize/ui.py:470 msgid "Initialization of plugin %s failed with traceback:" msgstr "A inicialização do extra %s falhou, deixando o seguinte relatório:" -#: /home/kovid/work/calibre/src/calibre/customize/ui.py:553 +#: /home/kovid/work/calibre/src/calibre/customize/ui.py:508 msgid "" " %prog options\n" "\n" @@ -751,18 +879,18 @@ msgstr "" " Personalize o calibre carregando extras externos.\n" " " -#: /home/kovid/work/calibre/src/calibre/customize/ui.py:559 +#: /home/kovid/work/calibre/src/calibre/customize/ui.py:514 msgid "Add a plugin by specifying the path to the zip file containing it." msgstr "" "Adicione um extra especificando um caminho para o ficheiro zip que o contém." -#: /home/kovid/work/calibre/src/calibre/customize/ui.py:561 +#: /home/kovid/work/calibre/src/calibre/customize/ui.py:516 msgid "Remove a custom plugin by name. Has no effect on builtin plugins" msgstr "" "Remova um extra identificado pelo seu nome. Não tem qualquer efeito sobre os " "extras integrados." -#: /home/kovid/work/calibre/src/calibre/customize/ui.py:563 +#: /home/kovid/work/calibre/src/calibre/customize/ui.py:518 msgid "" "Customize plugin. Specify name of plugin and customization string separated " "by a comma." @@ -770,19 +898,19 @@ msgstr "" "Personalize o extra. Especifique o nome do extra e uma expressão " "identificadora, separados por uma vírgula." -#: /home/kovid/work/calibre/src/calibre/customize/ui.py:565 +#: /home/kovid/work/calibre/src/calibre/customize/ui.py:520 msgid "List all installed plugins" msgstr "Listar todos os extras instalados" -#: /home/kovid/work/calibre/src/calibre/customize/ui.py:567 +#: /home/kovid/work/calibre/src/calibre/customize/ui.py:522 msgid "Enable the named plugin" msgstr "Activar o extra mencionado" -#: /home/kovid/work/calibre/src/calibre/customize/ui.py:569 +#: /home/kovid/work/calibre/src/calibre/customize/ui.py:524 msgid "Disable the named plugin" msgstr "Desactivar o extra mencionado" -#: /home/kovid/work/calibre/src/calibre/debug.py:150 +#: /home/kovid/work/calibre/src/calibre/debug.py:152 msgid "Debug log" msgstr "Debug log" @@ -790,7 +918,7 @@ msgstr "Debug log" msgid "Communicate with Android phones." msgstr "Estabelecer ligação a telefones Android." -#: /home/kovid/work/calibre/src/calibre/devices/android/driver.py:74 +#: /home/kovid/work/calibre/src/calibre/devices/android/driver.py:96 msgid "" "Comma separated list of directories to send e-books to on the device. The " "first one that exists will be used" @@ -798,23 +926,59 @@ msgstr "" "Lista de directorias separada por vírgulas para enviar e-books para o " "dispositivo (a primeira existente será usada)" -#: /home/kovid/work/calibre/src/calibre/devices/android/driver.py:121 +#: /home/kovid/work/calibre/src/calibre/devices/android/driver.py:146 msgid "Communicate with S60 phones." msgstr "Estabelecer ligação a telefones S60." -#: /home/kovid/work/calibre/src/calibre/devices/apple/driver.py:92 +#: /home/kovid/work/calibre/src/calibre/devices/apple/driver.py:47 +msgid "" +"

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

      To " +"transfer books to your iDevice, click Disable Apple Driver, then use " +"the 'Connect to iTunes' method recommended in the Calibre + " +"iDevices FAQ, using the Connect/Share|Connect to " +"iTunes menu item.

      Enabling the Apple driver for direct connection " +"to iDevices is an unsupported advanced user mode.

      " +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/devices/apple/driver.py:65 +msgid "Disable Apple driver" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/devices/apple/driver.py:69 +msgid "Enable Apple driver" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/devices/apple/driver.py:117 +msgid "Use Series as Category in iTunes/iBooks" +msgstr "Usar Série como Categoria no iTunes/iBooks" + +#: /home/kovid/work/calibre/src/calibre/devices/apple/driver.py:118 +msgid "Enable to use the series name as the iTunes Genre, iBooks Category" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/devices/apple/driver.py:120 +msgid "Cache covers from iTunes/iBooks" +msgstr "Capas em 'cache' do iTunes/iBooks" + +#: /home/kovid/work/calibre/src/calibre/devices/apple/driver.py:122 +msgid "Enable to cache and display covers from iTunes/iBooks" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/devices/apple/driver.py:178 msgid "Apple device" msgstr "Dispositivo Apple" -#: /home/kovid/work/calibre/src/calibre/devices/apple/driver.py:94 +#: /home/kovid/work/calibre/src/calibre/devices/apple/driver.py:180 msgid "Communicate with iTunes/iBooks." msgstr "Estabelecer ligação com o sistema iTunes/iBooks." -#: /home/kovid/work/calibre/src/calibre/devices/apple/driver.py:100 +#: /home/kovid/work/calibre/src/calibre/devices/apple/driver.py:192 msgid "Apple device detected, launching iTunes, please wait ..." msgstr "Dispositivo Apple detectado. Aguarde enquanto o iTunes é iniciado..." -#: /home/kovid/work/calibre/src/calibre/devices/apple/driver.py:102 +#: /home/kovid/work/calibre/src/calibre/devices/apple/driver.py:194 msgid "" "Cannot copy books directly from iDevice. Drag from iTunes Library to " "desktop, then add to calibre's Library window." @@ -823,35 +987,28 @@ msgstr "" "livro da biblioteca iTunes para o ambiente de trabalho e, depois, para a " "janela Biblioteca do calibre." -#: /home/kovid/work/calibre/src/calibre/devices/apple/driver.py:262 -#: /home/kovid/work/calibre/src/calibre/devices/apple/driver.py:265 +#: /home/kovid/work/calibre/src/calibre/devices/apple/driver.py:357 +#: /home/kovid/work/calibre/src/calibre/devices/apple/driver.py:360 msgid "Updating device metadata listing..." msgstr "A actualizar a lista de metadados do dispositivo..." -#: /home/kovid/work/calibre/src/calibre/devices/apple/driver.py:341 -#: /home/kovid/work/calibre/src/calibre/devices/apple/driver.py:380 -#: /home/kovid/work/calibre/src/calibre/devices/apple/driver.py:949 -#: /home/kovid/work/calibre/src/calibre/devices/apple/driver.py:989 -#: /home/kovid/work/calibre/src/calibre/devices/apple/driver.py:2971 -#: /home/kovid/work/calibre/src/calibre/devices/apple/driver.py:3011 +#: /home/kovid/work/calibre/src/calibre/devices/apple/driver.py:436 +#: /home/kovid/work/calibre/src/calibre/devices/apple/driver.py:475 +#: /home/kovid/work/calibre/src/calibre/devices/apple/driver.py:1057 +#: /home/kovid/work/calibre/src/calibre/devices/apple/driver.py:1101 +#: /home/kovid/work/calibre/src/calibre/devices/apple/driver.py:3097 +#: /home/kovid/work/calibre/src/calibre/devices/apple/driver.py:3137 msgid "%d of %d" msgstr "%d de %d" -#: /home/kovid/work/calibre/src/calibre/devices/apple/driver.py:387 -#: /home/kovid/work/calibre/src/calibre/devices/apple/driver.py:994 -#: /home/kovid/work/calibre/src/calibre/devices/apple/driver.py:3017 +#: /home/kovid/work/calibre/src/calibre/devices/apple/driver.py:482 +#: /home/kovid/work/calibre/src/calibre/devices/apple/driver.py:1106 +#: /home/kovid/work/calibre/src/calibre/devices/apple/driver.py:3143 +#: /home/kovid/work/calibre/src/calibre/gui2/ebook_download.py:106 msgid "finished" msgstr "terminado" -#: /home/kovid/work/calibre/src/calibre/devices/apple/driver.py:562 -msgid "Use Series as Category in iTunes/iBooks" -msgstr "Usar Série como Categoria no iTunes/iBooks" - -#: /home/kovid/work/calibre/src/calibre/devices/apple/driver.py:564 -msgid "Cache covers from iTunes/iBooks" -msgstr "Capas em 'cache' do iTunes/iBooks" - -#: /home/kovid/work/calibre/src/calibre/devices/apple/driver.py:576 +#: /home/kovid/work/calibre/src/calibre/devices/apple/driver.py:667 msgid "" "Some books not found in iTunes database.\n" "Delete using the iBooks app.\n" @@ -861,7 +1018,7 @@ msgstr "" "Apague-os utilizando a aplicação iBooks.\n" "Carregue em 'Mostrar Detalhes' para obter a lista." -#: /home/kovid/work/calibre/src/calibre/devices/apple/driver.py:913 +#: /home/kovid/work/calibre/src/calibre/devices/apple/driver.py:1018 msgid "" "Some cover art could not be converted.\n" "Click 'Show Details' for a list." @@ -869,30 +1026,31 @@ msgstr "" "Algumas capas não puderam ser convertidas.\n" "Carregue em 'Mostrar Detalhes' para obter a lista." -#: /home/kovid/work/calibre/src/calibre/devices/apple/driver.py:2552 +#: /home/kovid/work/calibre/src/calibre/devices/apple/driver.py:2668 #: /home/kovid/work/calibre/src/calibre/devices/nook/driver.py:100 #: /home/kovid/work/calibre/src/calibre/devices/prs505/sony_cache.py:447 #: /home/kovid/work/calibre/src/calibre/devices/prs505/sony_cache.py:470 -#: /home/kovid/work/calibre/src/calibre/devices/usbms/device.py:908 -#: /home/kovid/work/calibre/src/calibre/devices/usbms/device.py:914 -#: /home/kovid/work/calibre/src/calibre/devices/usbms/device.py:944 -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/scheduler.py:262 -#: /home/kovid/work/calibre/src/calibre/library/database2.py:255 -#: /home/kovid/work/calibre/src/calibre/library/database2.py:268 -#: /home/kovid/work/calibre/src/calibre/library/database2.py:2438 -#: /home/kovid/work/calibre/src/calibre/library/field_metadata.py:150 +#: /home/kovid/work/calibre/src/calibre/devices/usbms/device.py:909 +#: /home/kovid/work/calibre/src/calibre/devices/usbms/device.py:915 +#: /home/kovid/work/calibre/src/calibre/devices/usbms/device.py:945 +#: /home/kovid/work/calibre/src/calibre/gui2/actions/fetch_news.py:73 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/scheduler.py:445 +#: /home/kovid/work/calibre/src/calibre/library/database2.py:298 +#: /home/kovid/work/calibre/src/calibre/library/database2.py:311 +#: /home/kovid/work/calibre/src/calibre/library/database2.py:2808 +#: /home/kovid/work/calibre/src/calibre/library/field_metadata.py:159 msgid "News" msgstr "Notícias" -#: /home/kovid/work/calibre/src/calibre/devices/apple/driver.py:2553 +#: /home/kovid/work/calibre/src/calibre/devices/apple/driver.py:2669 #: /home/kovid/work/calibre/src/calibre/gui2/catalog/catalog_epub_mobi.py:65 -#: /home/kovid/work/calibre/src/calibre/library/catalog.py:634 -#: /home/kovid/work/calibre/src/calibre/library/database2.py:2401 -#: /home/kovid/work/calibre/src/calibre/library/database2.py:2419 +#: /home/kovid/work/calibre/src/calibre/library/catalog.py:643 +#: /home/kovid/work/calibre/src/calibre/library/database2.py:2768 +#: /home/kovid/work/calibre/src/calibre/library/database2.py:2786 msgid "Catalog" msgstr "Catálogo" -#: /home/kovid/work/calibre/src/calibre/devices/apple/driver.py:2875 +#: /home/kovid/work/calibre/src/calibre/devices/apple/driver.py:3001 msgid "Communicate with iTunes." msgstr "Estabelecer ligação com o sistema iTunes." @@ -943,30 +1101,30 @@ msgstr "Bambook" #: /home/kovid/work/calibre/src/calibre/devices/kobo/driver.py:67 #: /home/kovid/work/calibre/src/calibre/devices/kobo/driver.py:70 #: /home/kovid/work/calibre/src/calibre/devices/kobo/driver.py:73 -#: /home/kovid/work/calibre/src/calibre/devices/kobo/driver.py:226 -#: /home/kovid/work/calibre/src/calibre/devices/usbms/driver.py:68 -#: /home/kovid/work/calibre/src/calibre/devices/usbms/driver.py:71 -#: /home/kovid/work/calibre/src/calibre/devices/usbms/driver.py:74 -#: /home/kovid/work/calibre/src/calibre/devices/usbms/driver.py:138 -#: /home/kovid/work/calibre/src/calibre/devices/usbms/driver.py:145 -#: /home/kovid/work/calibre/src/calibre/devices/usbms/driver.py:168 +#: /home/kovid/work/calibre/src/calibre/devices/kobo/driver.py:232 +#: /home/kovid/work/calibre/src/calibre/devices/usbms/driver.py:122 +#: /home/kovid/work/calibre/src/calibre/devices/usbms/driver.py:125 +#: /home/kovid/work/calibre/src/calibre/devices/usbms/driver.py:128 +#: /home/kovid/work/calibre/src/calibre/devices/usbms/driver.py:196 +#: /home/kovid/work/calibre/src/calibre/devices/usbms/driver.py:203 +#: /home/kovid/work/calibre/src/calibre/devices/usbms/driver.py:226 msgid "Getting list of books on device..." msgstr "A ir buscar a lista dos livros no aparelho..." #: /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:279 -#: /home/kovid/work/calibre/src/calibre/devices/usbms/driver.py:197 -#: /home/kovid/work/calibre/src/calibre/devices/usbms/driver.py:199 +#: /home/kovid/work/calibre/src/calibre/devices/usbms/driver.py:255 +#: /home/kovid/work/calibre/src/calibre/devices/usbms/driver.py:257 msgid "Transferring books to device..." msgstr "A transferir o(s) livro(s) para o aparelho..." #: /home/kovid/work/calibre/src/calibre/devices/bambook/driver.py:285 #: /home/kovid/work/calibre/src/calibre/devices/bambook/driver.py:299 -#: /home/kovid/work/calibre/src/calibre/devices/kobo/driver.py:343 -#: /home/kovid/work/calibre/src/calibre/devices/kobo/driver.py:378 -#: /home/kovid/work/calibre/src/calibre/devices/usbms/driver.py:221 -#: /home/kovid/work/calibre/src/calibre/devices/usbms/driver.py:252 +#: /home/kovid/work/calibre/src/calibre/devices/kobo/driver.py:349 +#: /home/kovid/work/calibre/src/calibre/devices/kobo/driver.py:384 +#: /home/kovid/work/calibre/src/calibre/devices/usbms/driver.py:279 +#: /home/kovid/work/calibre/src/calibre/devices/usbms/driver.py:310 msgid "Adding books to device metadata listing..." msgstr "A adicionar os livros à listagem de metadados do aparelho..." @@ -974,28 +1132,28 @@ msgstr "A adicionar os livros à listagem de metadados do aparelho..." #: /home/kovid/work/calibre/src/calibre/devices/bambook/driver.py:309 #: /home/kovid/work/calibre/src/calibre/devices/hanvon/driver.py:102 #: /home/kovid/work/calibre/src/calibre/devices/hanvon/driver.py:113 -#: /home/kovid/work/calibre/src/calibre/devices/kobo/driver.py:295 -#: /home/kovid/work/calibre/src/calibre/devices/kobo/driver.py:327 -#: /home/kovid/work/calibre/src/calibre/devices/usbms/driver.py:258 -#: /home/kovid/work/calibre/src/calibre/devices/usbms/driver.py:276 +#: /home/kovid/work/calibre/src/calibre/devices/kobo/driver.py:301 +#: /home/kovid/work/calibre/src/calibre/devices/kobo/driver.py:333 +#: /home/kovid/work/calibre/src/calibre/devices/usbms/driver.py:316 +#: /home/kovid/work/calibre/src/calibre/devices/usbms/driver.py:334 msgid "Removing books from device..." msgstr "A remover os livros do aparelho..." #: /home/kovid/work/calibre/src/calibre/devices/bambook/driver.py:324 #: /home/kovid/work/calibre/src/calibre/devices/bambook/driver.py:329 -#: /home/kovid/work/calibre/src/calibre/devices/kobo/driver.py:331 -#: /home/kovid/work/calibre/src/calibre/devices/kobo/driver.py:338 -#: /home/kovid/work/calibre/src/calibre/devices/usbms/driver.py:283 -#: /home/kovid/work/calibre/src/calibre/devices/usbms/driver.py:288 +#: /home/kovid/work/calibre/src/calibre/devices/kobo/driver.py:337 +#: /home/kovid/work/calibre/src/calibre/devices/kobo/driver.py:344 +#: /home/kovid/work/calibre/src/calibre/devices/usbms/driver.py:341 +#: /home/kovid/work/calibre/src/calibre/devices/usbms/driver.py:346 msgid "Removing books from device metadata listing..." msgstr "A apagar os livros da listagem de metadados do aparelho..." #: /home/kovid/work/calibre/src/calibre/devices/bambook/driver.py:397 -#: /home/kovid/work/calibre/src/calibre/devices/usbms/driver.py:318 +#: /home/kovid/work/calibre/src/calibre/devices/usbms/driver.py:376 msgid "Sending metadata to device..." msgstr "A enviar os metadados para o aparelho..." -#: /home/kovid/work/calibre/src/calibre/devices/bambook/libbambookcore.py:132 +#: /home/kovid/work/calibre/src/calibre/devices/bambook/libbambookcore.py:129 msgid "Bambook SDK has not been installed." msgstr "O BambookSDK não está instalado." @@ -1008,12 +1166,20 @@ msgid "Communicate with the Blackberry smart phone." msgstr "Estabelecer ligação com o dispositivo smartphone Blackberry." #: /home/kovid/work/calibre/src/calibre/devices/blackberry/driver.py:14 -#: /home/kovid/work/calibre/src/calibre/devices/eb600/driver.py:253 +#: /home/kovid/work/calibre/src/calibre/devices/eb600/driver.py:254 #: /home/kovid/work/calibre/src/calibre/devices/nuut2/driver.py:18 #: /home/kovid/work/calibre/src/calibre/devices/prs500/driver.py:90 msgid "Kovid Goyal" msgstr "Kovid Goyal" +#: /home/kovid/work/calibre/src/calibre/devices/boeye/driver.py:14 +msgid "Communicate with BOEYE BEX Serial eBook readers." +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/devices/boeye/driver.py:35 +msgid "Communicate with BOEYE BDX serial eBook readers." +msgstr "" + #: /home/kovid/work/calibre/src/calibre/devices/cybook/driver.py:22 msgid "Communicate with the Cybook Gen 3 / Opus eBook reader." msgstr "Estabelecer ligação com o dispositivo Cybook Gen 3 / Opus." @@ -1038,7 +1204,7 @@ msgstr "Comunicar com o leitor PocketBook 301" msgid "Communicate with the PocketBook 602/603/902/903 reader." msgstr "Comunicar com o leitor PocketBook 602/603/902/903" -#: /home/kovid/work/calibre/src/calibre/devices/eb600/driver.py:252 +#: /home/kovid/work/calibre/src/calibre/devices/eb600/driver.py:253 msgid "Communicate with the PocketBook 701" msgstr "Estabelecer ligação com o dispositivo PocketBook 701" @@ -1076,7 +1242,7 @@ msgstr "Comunicar com leitores Hanlin V3." msgid "Communicate with Hanlin V5 eBook readers." msgstr "Comunicar com leitores Hanlin V5." -#: /home/kovid/work/calibre/src/calibre/devices/hanlin/driver.py:115 +#: /home/kovid/work/calibre/src/calibre/devices/hanlin/driver.py:114 msgid "Communicate with the BOOX eBook reader." msgstr "Comunicar com leitores BOOX." @@ -1114,7 +1280,7 @@ msgstr "Comunica com o leitor IRex Iliad." #: /home/kovid/work/calibre/src/calibre/devices/iliad/driver.py:17 #: /home/kovid/work/calibre/src/calibre/devices/irexdr/driver.py:18 -#: /home/kovid/work/calibre/src/calibre/devices/usbms/driver.py:42 +#: /home/kovid/work/calibre/src/calibre/devices/usbms/driver.py:43 msgid "John Schember" msgstr "John Schember" @@ -1148,11 +1314,11 @@ msgstr "Comunicar com o leitor JetBook Mini." #: /home/kovid/work/calibre/src/calibre/devices/kindle/apnx.py:28 msgid "Not a valid MOBI file. Reports identity of %s" -msgstr "" +msgstr "Não é um ficheiro MOBI válido. Foi identificado como %s" #: /home/kovid/work/calibre/src/calibre/devices/kindle/apnx.py:44 msgid "Could not generate page mapping." -msgstr "" +msgstr "Não consegue gerar o mapeamento de páginas" #: /home/kovid/work/calibre/src/calibre/devices/kindle/driver.py:44 msgid "Communicate with the Kindle eBook reader." @@ -1164,7 +1330,7 @@ msgstr "Comunicar com o leitor Kindle 2/3." #: /home/kovid/work/calibre/src/calibre/devices/kindle/driver.py:180 msgid "Send page number information when sending books" -msgstr "" +msgstr "Enviar informação sobre número de página quando envia o livro" #: /home/kovid/work/calibre/src/calibre/devices/kindle/driver.py:182 msgid "" @@ -1173,10 +1339,14 @@ msgid "" "the Kindle when uploading MOBI files by USB. Note that the page numbers do " "not correspond to any paper book." msgstr "" +"O Kindle 3 e novas versões podem usar informação de paginação em ficheiros " +"MOBI. Com esta opção, o calibre irá calcular e enviar esta informação para o " +"Kindle ao enviar ficheiros MOBI por USB. Note-se que a paginação não " +"corresponde a nenhum livro imprimido." #: /home/kovid/work/calibre/src/calibre/devices/kindle/driver.py:187 msgid "Use slower but more accurate page number generation" -msgstr "" +msgstr "Usar uma geração de número de páginas mais lenta mas mais exacta" #: /home/kovid/work/calibre/src/calibre/devices/kindle/driver.py:189 msgid "" @@ -1185,6 +1355,9 @@ msgid "" "book. However, this method is slower and will slow down sending files to the " "Kindle." msgstr "" +"Existem duas maneiras de gerar os números de página. Usando um gerador mais " +"preciso irá produzir páginas que correspondem melhor a um livro imprimido. " +"Mas este método irá tornar mais lento o envio de ficheiros para o kindle." #: /home/kovid/work/calibre/src/calibre/devices/kindle/driver.py:257 msgid "Communicate with the Kindle DX eBook reader." @@ -1202,12 +1375,12 @@ msgstr "" "O dispositivo Kobo suporta apenas uma colecção actualmente: a lista " "\"Im_Reading\". Crie uma etiqueta com a denominação \"Im_Reading\". " -#: /home/kovid/work/calibre/src/calibre/devices/kobo/driver.py:462 -#: /home/kovid/work/calibre/src/calibre/gui2/actions/add.py:315 +#: /home/kovid/work/calibre/src/calibre/devices/kobo/driver.py:468 +#: /home/kovid/work/calibre/src/calibre/gui2/actions/add.py:388 msgid "Not Implemented" msgstr "Não Implementado" -#: /home/kovid/work/calibre/src/calibre/devices/kobo/driver.py:463 +#: /home/kovid/work/calibre/src/calibre/devices/kobo/driver.py:469 msgid "" "\".kobo\" files do not exist on the device as books instead, they are rows " "in the sqlite database. Currently they cannot be exported or viewed." @@ -1216,56 +1389,48 @@ msgstr "" "são considerados colunas na base de dados SQLite. Actualmente, estes não " "podem ser exportados ou exibidos." -#: /home/kovid/work/calibre/src/calibre/devices/misc.py:17 +#: /home/kovid/work/calibre/src/calibre/devices/misc.py:19 msgid "Communicate with the Palm Pre" msgstr "Comunicar com o leitor Palm Pre" -#: /home/kovid/work/calibre/src/calibre/devices/misc.py:37 +#: /home/kovid/work/calibre/src/calibre/devices/misc.py:39 msgid "Communicate with the Bq Avant" -msgstr "" +msgstr "Comunicar com Bq Avant" -#: /home/kovid/work/calibre/src/calibre/devices/misc.py:58 -msgid "Communicate with the Sweex MM300" -msgstr "Comunicar com o leitor Sweex MM300" +#: /home/kovid/work/calibre/src/calibre/devices/misc.py:60 +msgid "Communicate with the Sweex/Kogan/Q600/Wink" +msgstr "Comunicar com Sweex/Kogan/Q600/Wink" -#: /home/kovid/work/calibre/src/calibre/devices/misc.py:79 -msgid "Communicate with the Digma Q600" -msgstr "Comunicar com o leitor Digma Q600" - -#: /home/kovid/work/calibre/src/calibre/devices/misc.py:88 -msgid "Communicate with the Kogan" -msgstr "Comunicar com o leitor Kogan" - -#: /home/kovid/work/calibre/src/calibre/devices/misc.py:96 -#: /home/kovid/work/calibre/src/calibre/devices/misc.py:123 +#: /home/kovid/work/calibre/src/calibre/devices/misc.py:81 +#: /home/kovid/work/calibre/src/calibre/devices/misc.py:108 msgid "Communicate with the Pandigital Novel" msgstr "Comunicar com o leitor Pandigital Novel" -#: /home/kovid/work/calibre/src/calibre/devices/misc.py:142 +#: /home/kovid/work/calibre/src/calibre/devices/misc.py:127 msgid "Communicate with the VelocityMicro" msgstr "Comunicar com o leitor VelocityMicro" -#: /home/kovid/work/calibre/src/calibre/devices/misc.py:160 +#: /home/kovid/work/calibre/src/calibre/devices/misc.py:145 msgid "Communicate with the GM2000" msgstr "Comunicar com o leitor GM2000" -#: /home/kovid/work/calibre/src/calibre/devices/misc.py:180 +#: /home/kovid/work/calibre/src/calibre/devices/misc.py:165 msgid "Communicate with the Acer Lumiread" msgstr "Comunicar com o leitor Acer Lumiread" -#: /home/kovid/work/calibre/src/calibre/devices/misc.py:214 +#: /home/kovid/work/calibre/src/calibre/devices/misc.py:199 msgid "Communicate with the Aluratek Color" msgstr "Comunicar com o dispositivo Aluratek Color" -#: /home/kovid/work/calibre/src/calibre/devices/misc.py:234 +#: /home/kovid/work/calibre/src/calibre/devices/misc.py:219 msgid "Communicate with the Trekstor" msgstr "Estabelecer ligação com o dispositivo Trekstor" -#: /home/kovid/work/calibre/src/calibre/devices/misc.py:254 +#: /home/kovid/work/calibre/src/calibre/devices/misc.py:239 msgid "Communicate with the EEE Reader" msgstr "Comunicar com o EEE Reader" -#: /home/kovid/work/calibre/src/calibre/devices/misc.py:274 +#: /home/kovid/work/calibre/src/calibre/devices/misc.py:259 msgid "Communicate with the Nextbook Reader" msgstr "Comunicar com o Nextbook Reader" @@ -1309,15 +1474,15 @@ msgstr "Comunica com o leitor Sony PRS-500." msgid "Communicate with all the Sony eBook readers." msgstr "Comunicar com todos os leitores de livros Sony" -#: /home/kovid/work/calibre/src/calibre/devices/prs505/driver.py:61 +#: /home/kovid/work/calibre/src/calibre/devices/prs505/driver.py:62 msgid "All by title" msgstr "Todos por titulo" -#: /home/kovid/work/calibre/src/calibre/devices/prs505/driver.py:62 +#: /home/kovid/work/calibre/src/calibre/devices/prs505/driver.py:63 msgid "All by author" msgstr "Todos por Autor" -#: /home/kovid/work/calibre/src/calibre/devices/prs505/driver.py:65 +#: /home/kovid/work/calibre/src/calibre/devices/prs505/driver.py:66 msgid "" "Comma separated list of metadata fields to turn into collections on the " "device. Possibilities include: " @@ -1325,7 +1490,7 @@ msgstr "" "Lista de campos de metadados separada por vírgulas para a criação de " "colecções no dispositivo. Possibilidades incluídas: " -#: /home/kovid/work/calibre/src/calibre/devices/prs505/driver.py:68 +#: /home/kovid/work/calibre/src/calibre/devices/prs505/driver.py:69 msgid "" ". Two special collections are available: %s:%s and %s:%s. Add these values " "to the list to enable them. The collections will be given the name provided " @@ -1335,13 +1500,13 @@ msgstr "" "estes valores à lista para os activar. Às colecções será dado o nome " "existente a seguir aos dois pontos (caractere \":\")." -#: /home/kovid/work/calibre/src/calibre/devices/prs505/driver.py:72 +#: /home/kovid/work/calibre/src/calibre/devices/prs505/driver.py:73 msgid "Upload separate cover thumbnails for books (newer readers)" msgstr "" "Carregar miniaturas de capas individuais para os livros (leitores mais " "recentes)" -#: /home/kovid/work/calibre/src/calibre/devices/prs505/driver.py:73 +#: /home/kovid/work/calibre/src/calibre/devices/prs505/driver.py:74 msgid "" "Normally, the SONY readers get the cover image from the ebook file itself. " "With this option, calibre will send a separate cover image to the reader, " @@ -1349,34 +1514,56 @@ msgid "" "WARNING: This option should only be used with newer SONY readers: 350, 650, " "950 and newer." msgstr "" +"Normalmente, os dispositivos SONY usam a imagem da capa do próprio ebook. " +"Com esta opção, o calibre irá enviar uma capa separada para o dispositivo, " +"útil se estiver a enviar livros com DRM nos quais não se pode mudar a capa. " +"AVISO: Esta opção apenas deve ser usada nos novos dispositivos SONY: 50, " +"650, 950 e mais recentes." -#: /home/kovid/work/calibre/src/calibre/devices/prs505/driver.py:79 +#: /home/kovid/work/calibre/src/calibre/devices/prs505/driver.py:80 msgid "" "Refresh separate covers when using automatic management (newer readers)" msgstr "" "Actualizar capas individuais ao utilizar a gestão automática (leitores mais " "recentes)" -#: /home/kovid/work/calibre/src/calibre/devices/prs505/driver.py:81 +#: /home/kovid/work/calibre/src/calibre/devices/prs505/driver.py:82 msgid "" "Set this option to have separate book covers uploaded every time you connect " "your device. Unset this option if you have so many books on the reader that " "performance is unacceptable." msgstr "" +"Active esta opção para ter capas de livros separadas, enviadas cada vez que " +"liga o dispositivo. Desactive esta opção caso tenha uma grande quantidade de " +"livros no dispositivo que torne a performance inaceitável." -#: /home/kovid/work/calibre/src/calibre/devices/prs505/driver.py:85 +#: /home/kovid/work/calibre/src/calibre/devices/prs505/driver.py:86 msgid "Preserve cover aspect ratio when building thumbnails" -msgstr "" +msgstr "Manter a relação altura/largura quando são geradas as miniaturas" -#: /home/kovid/work/calibre/src/calibre/devices/prs505/driver.py:87 +#: /home/kovid/work/calibre/src/calibre/devices/prs505/driver.py:88 msgid "" "Set this option if you want the cover thumbnails to have the same aspect " "ratio (width to height) as the cover. Unset it if you want the thumbnail to " "be the maximum size, ignoring aspect ratio." msgstr "" +"Active esta opção se deseja que as miniaturas mantenham a mesma relação " +"altura/largura que a capa. Desactive se deseja que a miniatura fique no " +"tamanho máximo, ignorando a relação altura/largura." + +#: /home/kovid/work/calibre/src/calibre/devices/prs505/driver.py:92 +msgid "Search for books in all folders" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/devices/prs505/driver.py:94 +msgid "" +"Setting this option tells calibre to look for books in all folders on the " +"device and its cards. This permits calibre to find books put on the device " +"by other software and by wireless download." +msgstr "" #: /home/kovid/work/calibre/src/calibre/devices/prs505/sony_cache.py:190 -#: /home/kovid/work/calibre/src/calibre/ebooks/oeb/transforms/structure.py:68 +#: /home/kovid/work/calibre/src/calibre/ebooks/oeb/transforms/structure.py:69 msgid "Unnamed" msgstr "Sem nome" @@ -1394,7 +1581,7 @@ msgstr "Comunicar com o leitor Newsmy." #: /home/kovid/work/calibre/src/calibre/devices/teclast/driver.py:47 msgid "Communicate with the Archos reader." -msgstr "" +msgstr "Comunicar com o Archos reader." #: /home/kovid/work/calibre/src/calibre/devices/teclast/driver.py:57 msgid "Communicate with the Pico reader." @@ -1414,7 +1601,11 @@ msgstr "Estabelecer ligação com o dispositivo EB700 reader." #: /home/kovid/work/calibre/src/calibre/devices/teclast/driver.py:99 msgid "Communicate with the Stash W950 reader." -msgstr "" +msgstr "Comunicar com o Stash W950 reader." + +#: /home/kovid/work/calibre/src/calibre/devices/teclast/driver.py:111 +msgid "Communicate with the Wexler reader." +msgstr "Comunicar com o Wexler reader." #: /home/kovid/work/calibre/src/calibre/devices/usbms/device.py:282 msgid "Unable to detect the %s disk drive. Try rebooting." @@ -1452,21 +1643,21 @@ msgstr "" "A memória principal de %s é só de leitura. Isto acontece habitualmente " "devido a erros no sistema de ficheiros." -#: /home/kovid/work/calibre/src/calibre/devices/usbms/device.py:841 -#: /home/kovid/work/calibre/src/calibre/devices/usbms/device.py:843 +#: /home/kovid/work/calibre/src/calibre/devices/usbms/device.py:842 +#: /home/kovid/work/calibre/src/calibre/devices/usbms/device.py:844 msgid "The reader has no storage card in this slot." msgstr "O leitor não tem nenhum cartão de memória nesta ranhura." -#: /home/kovid/work/calibre/src/calibre/devices/usbms/device.py:845 +#: /home/kovid/work/calibre/src/calibre/devices/usbms/device.py:846 msgid "Selected slot: %s is not supported." msgstr "Ranhura seleccionada: %s não é suportado." -#: /home/kovid/work/calibre/src/calibre/devices/usbms/device.py:874 +#: /home/kovid/work/calibre/src/calibre/devices/usbms/device.py:875 msgid "There is insufficient free space in main memory" msgstr "O espaço livre na memória principal é insuficiente" -#: /home/kovid/work/calibre/src/calibre/devices/usbms/device.py:876 -#: /home/kovid/work/calibre/src/calibre/devices/usbms/device.py:878 +#: /home/kovid/work/calibre/src/calibre/devices/usbms/device.py:877 +#: /home/kovid/work/calibre/src/calibre/devices/usbms/device.py:879 msgid "There is insufficient free space on the storage card" msgstr "O espaço livre no cartão de memória é insuficiente" @@ -1503,23 +1694,90 @@ msgstr "Modelo para controlar a gravação de livros" msgid "Extra customization" msgstr "Personalização adicional" -#: /home/kovid/work/calibre/src/calibre/devices/usbms/driver.py:41 +#: /home/kovid/work/calibre/src/calibre/devices/usbms/driver.py:42 msgid "Communicate with an eBook reader." msgstr "Comunica com um leitor de livros." -#: /home/kovid/work/calibre/src/calibre/devices/usbms/driver.py:57 +#: /home/kovid/work/calibre/src/calibre/devices/usbms/driver.py:94 msgid "Get device information..." msgstr "A ir buscar informação sobre o aparelho..." -#: /home/kovid/work/calibre/src/calibre/ebooks/comic/input.py:190 +#: /home/kovid/work/calibre/src/calibre/devices/user_defined/driver.py:37 +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/device_user_defined.py:68 +msgid "USB Vendor ID (in hex)" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/devices/user_defined/driver.py:38 +#: /home/kovid/work/calibre/src/calibre/devices/user_defined/driver.py:41 +#: /home/kovid/work/calibre/src/calibre/devices/user_defined/driver.py:44 +msgid "" +"Get this ID using Preferences -> Misc -> Get information to set up the user-" +"defined device" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/devices/user_defined/driver.py:40 +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/device_user_defined.py:70 +msgid "USB Product ID (in hex)" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/devices/user_defined/driver.py:43 +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/device_user_defined.py:72 +msgid "USB Revision ID (in hex)" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/devices/user_defined/driver.py:47 +msgid "Windows main memory vendor string" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/devices/user_defined/driver.py:48 +#: /home/kovid/work/calibre/src/calibre/devices/user_defined/driver.py:52 +#: /home/kovid/work/calibre/src/calibre/devices/user_defined/driver.py:56 +#: /home/kovid/work/calibre/src/calibre/devices/user_defined/driver.py:60 +msgid "" +"This field is used only on windows. Get this ID using Preferences -> Misc -> " +"Get information to set up the user-defined device" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/devices/user_defined/driver.py:51 +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/device_user_defined.py:79 +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/device_user_defined.py:81 +msgid "Windows main memory ID string" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/devices/user_defined/driver.py:55 +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/device_user_defined.py:84 +msgid "Windows card A vendor string" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/devices/user_defined/driver.py:59 +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/device_user_defined.py:86 +msgid "Windows card A ID string" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/devices/user_defined/driver.py:63 +msgid "Main memory folder" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/devices/user_defined/driver.py:64 +#: /home/kovid/work/calibre/src/calibre/devices/user_defined/driver.py:67 +msgid "" +"Enter the folder where the books are to be stored. This folder is prepended " +"to any send_to_device template" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/devices/user_defined/driver.py:66 +msgid "Card A folder" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/ebooks/comic/input.py:202 msgid "Rendered %s" msgstr "%s representado" -#: /home/kovid/work/calibre/src/calibre/ebooks/comic/input.py:193 +#: /home/kovid/work/calibre/src/calibre/ebooks/comic/input.py:205 msgid "Failed %s" msgstr "Falha em %s" -#: /home/kovid/work/calibre/src/calibre/ebooks/comic/input.py:247 +#: /home/kovid/work/calibre/src/calibre/ebooks/comic/input.py:259 msgid "" "Failed to process comic: \n" "\n" @@ -1529,7 +1787,7 @@ msgstr "" "\n" "%s" -#: /home/kovid/work/calibre/src/calibre/ebooks/comic/input.py:266 +#: /home/kovid/work/calibre/src/calibre/ebooks/comic/input.py:278 msgid "" "Number of colors for grayscale image conversion. Default: %default. Values " "of less than 256 may result in blurred text on your device if you are " @@ -1540,23 +1798,23 @@ msgstr "" "esbatido no seu aparelho, se estiver a criar a banda desenhada no formato " "EPUB." -#: /home/kovid/work/calibre/src/calibre/ebooks/comic/input.py:270 +#: /home/kovid/work/calibre/src/calibre/ebooks/comic/input.py:282 msgid "" "Disable normalize (improve contrast) color range for pictures. Default: False" msgstr "" "Desactivar a normalização (melhoria do contraste) do campo de cores das " "imagens. A predefinição é: False" -#: /home/kovid/work/calibre/src/calibre/ebooks/comic/input.py:273 +#: /home/kovid/work/calibre/src/calibre/ebooks/comic/input.py:285 msgid "Maintain picture aspect ratio. Default is to fill the screen." msgstr "" "Manter a relação de aspecto da imagem. A predefinição é preencher o écran." -#: /home/kovid/work/calibre/src/calibre/ebooks/comic/input.py:275 +#: /home/kovid/work/calibre/src/calibre/ebooks/comic/input.py:287 msgid "Disable sharpening." msgstr "Desactivar a nitidez." -#: /home/kovid/work/calibre/src/calibre/ebooks/comic/input.py:277 +#: /home/kovid/work/calibre/src/calibre/ebooks/comic/input.py:289 msgid "" "Disable trimming of comic pages. For some comics, trimming might remove " "content as well as borders." @@ -1564,12 +1822,12 @@ msgstr "" "Desactivar o aparar das páginas de banda desenhada. Em certas bandas " "desenhadas aparar pode remover conteúdos além das margens." -#: /home/kovid/work/calibre/src/calibre/ebooks/comic/input.py:280 +#: /home/kovid/work/calibre/src/calibre/ebooks/comic/input.py:292 msgid "Don't split landscape images into two portrait images" msgstr "" "Não dividir as imagens em modo de paisagem em duas imagens em modo de retrato" -#: /home/kovid/work/calibre/src/calibre/ebooks/comic/input.py:282 +#: /home/kovid/work/calibre/src/calibre/ebooks/comic/input.py:294 msgid "" "Keep aspect ratio and scale image using screen height as image width for " "viewing in landscape mode." @@ -1577,7 +1835,7 @@ msgstr "" "Manter a relação de aspecto e escala da imagem usando a altura do écran como " "largura da imagem para visualização em modo paisagem." -#: /home/kovid/work/calibre/src/calibre/ebooks/comic/input.py:285 +#: /home/kovid/work/calibre/src/calibre/ebooks/comic/input.py:297 msgid "" "Used for right-to-left publications like manga. Causes landscape pages to be " "split into portrait pages from right to left." @@ -1586,7 +1844,7 @@ msgstr "" "páginas em modo de paisagem a serem divididas em páginas em modo de retrato " "da direita para a esquerda." -#: /home/kovid/work/calibre/src/calibre/ebooks/comic/input.py:289 +#: /home/kovid/work/calibre/src/calibre/ebooks/comic/input.py:301 msgid "" "Enable Despeckle. Reduces speckle noise. May greatly increase processing " "time." @@ -1594,7 +1852,7 @@ msgstr "" "Activar Limpar Irregularidades. Reduz as irregularidades. Pode aumentar " "muito o tempo de processamento." -#: /home/kovid/work/calibre/src/calibre/ebooks/comic/input.py:292 +#: /home/kovid/work/calibre/src/calibre/ebooks/comic/input.py:304 msgid "" "Don't sort the files found in the comic alphabetically by name. Instead use " "the order they were added to the comic." @@ -1603,7 +1861,7 @@ msgstr "" "nome. Em vez disso, usar a ordem pela qual foram adicionados à banda " "desenhada." -#: /home/kovid/work/calibre/src/calibre/ebooks/comic/input.py:296 +#: /home/kovid/work/calibre/src/calibre/ebooks/comic/input.py:308 msgid "" "The format that images in the created ebook are converted to. You can " "experiment to see which format gives you optimal size and look on your " @@ -1613,22 +1871,31 @@ msgstr "" "experimentar para ver qual o formato que fica com melhor tamanho e aparência " "no seu aparelho." -#: /home/kovid/work/calibre/src/calibre/ebooks/comic/input.py:300 +#: /home/kovid/work/calibre/src/calibre/ebooks/comic/input.py:312 msgid "Apply no processing to the image" msgstr "Não aplicar processamento à imagem" -#: /home/kovid/work/calibre/src/calibre/ebooks/comic/input.py:302 +#: /home/kovid/work/calibre/src/calibre/ebooks/comic/input.py:314 msgid "Do not convert the image to grayscale (black and white)" msgstr "Não converter a imagem para tons de cinzento (preto e branco)" -#: /home/kovid/work/calibre/src/calibre/ebooks/comic/input.py:304 +#: /home/kovid/work/calibre/src/calibre/ebooks/comic/input.py:316 msgid "" "Specify the image size as widthxheight pixels. Normally, an image size is " "automatically calculated from the output profile, this option overrides it." msgstr "" +"Especificar o tamanho da imagem com larguraxaltura pixels. Normalmente, o " +"tamanho de uma imagem é automaticamente calculado a partir do perfil de " +"saída, esta opção sobrepõe-se" -#: /home/kovid/work/calibre/src/calibre/ebooks/comic/input.py:443 -#: /home/kovid/work/calibre/src/calibre/ebooks/comic/input.py:454 +#: /home/kovid/work/calibre/src/calibre/ebooks/comic/input.py:320 +msgid "" +"When converting a CBC do not add links to each page to the TOC. Note this " +"only applies if the TOC has more than one section" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/ebooks/comic/input.py:459 +#: /home/kovid/work/calibre/src/calibre/ebooks/comic/input.py:471 msgid "Page" msgstr "Página" @@ -1681,27 +1948,27 @@ msgstr "" "\n" "Para a documentação completa do sistema de conversão veja\n" -#: /home/kovid/work/calibre/src/calibre/ebooks/conversion/cli.py:106 +#: /home/kovid/work/calibre/src/calibre/ebooks/conversion/cli.py:109 msgid "INPUT OPTIONS" msgstr "OPÇÕES DE ORIGEM" -#: /home/kovid/work/calibre/src/calibre/ebooks/conversion/cli.py:107 +#: /home/kovid/work/calibre/src/calibre/ebooks/conversion/cli.py:110 msgid "Options to control the processing of the input %s file" msgstr "Opções para controlar o processamento do ficheiro de origem %s" -#: /home/kovid/work/calibre/src/calibre/ebooks/conversion/cli.py:113 +#: /home/kovid/work/calibre/src/calibre/ebooks/conversion/cli.py:116 msgid "OUTPUT OPTIONS" msgstr "OPÇÕES DE DESTINO" -#: /home/kovid/work/calibre/src/calibre/ebooks/conversion/cli.py:114 +#: /home/kovid/work/calibre/src/calibre/ebooks/conversion/cli.py:117 msgid "Options to control the processing of the output %s" msgstr "Opções para controlar o processamento do ficheiro de destino %s" -#: /home/kovid/work/calibre/src/calibre/ebooks/conversion/cli.py:128 +#: /home/kovid/work/calibre/src/calibre/ebooks/conversion/cli.py:131 msgid "Options to control the look and feel of the output" msgstr "Opções para controlar o aspecto do ficheiro de destino" -#: /home/kovid/work/calibre/src/calibre/ebooks/conversion/cli.py:143 +#: /home/kovid/work/calibre/src/calibre/ebooks/conversion/cli.py:146 msgid "" "Modify the document text and structure using common patterns. Disabled by " "default. Use %s to enable. Individual actions can be disabled with the %s " @@ -1711,18 +1978,18 @@ msgstr "" "Desactivado por predefinição. Utilize o parâmetro \"%s\" para activar a " "opção. É possível desactivar acções individuais com as opções \"%s\"." -#: /home/kovid/work/calibre/src/calibre/ebooks/conversion/cli.py:151 -#: /home/kovid/work/calibre/src/calibre/gui2/convert/search_and_replace.py:16 +#: /home/kovid/work/calibre/src/calibre/ebooks/conversion/cli.py:154 +#: /home/kovid/work/calibre/src/calibre/gui2/convert/search_and_replace.py:18 msgid "Modify the document text and structure using user defined patterns." msgstr "" "Modificar o texto e estrutura do documento de acordo com padrões definidos " "pelo utilizador." -#: /home/kovid/work/calibre/src/calibre/ebooks/conversion/cli.py:160 +#: /home/kovid/work/calibre/src/calibre/ebooks/conversion/cli.py:163 msgid "Control auto-detection of document structure." msgstr "Controlar a detecção automática da estrutura do documento." -#: /home/kovid/work/calibre/src/calibre/ebooks/conversion/cli.py:169 +#: /home/kovid/work/calibre/src/calibre/ebooks/conversion/cli.py:173 msgid "" "Control the automatic generation of a Table of Contents. By default, if the " "source file has a Table of Contents, it will be used in preference to the " @@ -1731,28 +1998,28 @@ msgstr "" "Controlar a geração automática do Índice. Por predefinição, se o ficheiro de " "origem tem um Índice, este é utilizado em vez do gerado automaticamente." -#: /home/kovid/work/calibre/src/calibre/ebooks/conversion/cli.py:179 +#: /home/kovid/work/calibre/src/calibre/ebooks/conversion/cli.py:183 msgid "Options to set metadata in the output" msgstr "Opções para definir os metadados no ficheiro de saída" -#: /home/kovid/work/calibre/src/calibre/ebooks/conversion/cli.py:182 +#: /home/kovid/work/calibre/src/calibre/ebooks/conversion/cli.py:186 msgid "Options to help with debugging the conversion" msgstr "Opções para ajudar com a depuração da conversão" -#: /home/kovid/work/calibre/src/calibre/ebooks/conversion/cli.py:208 +#: /home/kovid/work/calibre/src/calibre/ebooks/conversion/cli.py:212 msgid "List builtin recipes" msgstr "Listar as receitas integradas" -#: /home/kovid/work/calibre/src/calibre/ebooks/conversion/cli.py:281 +#: /home/kovid/work/calibre/src/calibre/ebooks/conversion/cli.py:285 msgid "Output saved to" msgstr "Ficheiro de destino guardado em" -#: /home/kovid/work/calibre/src/calibre/ebooks/conversion/plumber.py:102 +#: /home/kovid/work/calibre/src/calibre/ebooks/conversion/plumber.py:103 msgid "Level of verbosity. Specify multiple times for greater verbosity." msgstr "" "Nível de indicações. Especificar múltiplas vezes para mais indicações." -#: /home/kovid/work/calibre/src/calibre/ebooks/conversion/plumber.py:109 +#: /home/kovid/work/calibre/src/calibre/ebooks/conversion/plumber.py:110 msgid "" "Save the output from different stages of the conversion pipeline to the " "specified directory. Useful if you are unsure at which stage of the " @@ -1762,7 +2029,7 @@ msgstr "" "especificada. Útil se não tem a certeza em que etapa do processo de " "conversão é que está a ocorrer o erro." -#: /home/kovid/work/calibre/src/calibre/ebooks/conversion/plumber.py:118 +#: /home/kovid/work/calibre/src/calibre/ebooks/conversion/plumber.py:119 msgid "" "Specify the input profile. The input profile gives the conversion system " "information on how to interpret various information in the input document. " @@ -1774,7 +2041,7 @@ msgstr "" "oriem. Por exemplo comprimento dependente da resolução (i. e. comprimento em " "pixels). Escolhas disponíveis:" -#: /home/kovid/work/calibre/src/calibre/ebooks/conversion/plumber.py:129 +#: /home/kovid/work/calibre/src/calibre/ebooks/conversion/plumber.py:130 msgid "" "Specify the output profile. The output profile tells the conversion system " "how to optimize the created document for the specified device. In some " @@ -1787,7 +2054,7 @@ msgstr "" "funcionem num aparelho. Por exemplo EPUB no leitor SONY. Escolhas " "disponíveis:" -#: /home/kovid/work/calibre/src/calibre/ebooks/conversion/plumber.py:140 +#: /home/kovid/work/calibre/src/calibre/ebooks/conversion/plumber.py:141 msgid "" "The base font size in pts. All font sizes in the produced book will be " "rescaled based on this size. By choosing a larger size you can make the " @@ -1800,7 +2067,7 @@ msgstr "" "destino serão maiores e vice versa. Por predefinição o tamanho do tipo de " "letra padrão é escolhido baseado no perfil de destino que escolheu." -#: /home/kovid/work/calibre/src/calibre/ebooks/conversion/plumber.py:150 +#: /home/kovid/work/calibre/src/calibre/ebooks/conversion/plumber.py:151 msgid "" "Mapping from CSS font names to font sizes in pts. An example setting is " "12,12,14,16,18,20,22,24. These are the mappings for the sizes xx-small to xx-" @@ -1816,11 +2083,11 @@ msgstr "" "tipos de letra. A predefinição é usar a estrutura baseada no perfil de " "destino que escolheu." -#: /home/kovid/work/calibre/src/calibre/ebooks/conversion/plumber.py:162 +#: /home/kovid/work/calibre/src/calibre/ebooks/conversion/plumber.py:163 msgid "Disable all rescaling of font sizes." msgstr "Desactivar a alteração proporcional do tamanho dos tipos de letra." -#: /home/kovid/work/calibre/src/calibre/ebooks/conversion/plumber.py:168 +#: /home/kovid/work/calibre/src/calibre/ebooks/conversion/plumber.py:169 msgid "" "The minimum line height, as a percentage of the element's calculated font " "size. calibre will ensure that every element has a line height of at least " @@ -1838,7 +2105,7 @@ msgstr "" "que está a fazer. Por exemplo, pode obter texto com \"espaço duplo\" se " "definir isto com 240." -#: /home/kovid/work/calibre/src/calibre/ebooks/conversion/plumber.py:183 +#: /home/kovid/work/calibre/src/calibre/ebooks/conversion/plumber.py:184 msgid "" "The line height in pts. Controls spacing between consecutive lines of text. " "Only applies to elements that do not define their own line height. In most " @@ -1850,7 +2117,7 @@ msgstr "" "linha. Na maioria dos casos, é mais útil usar a opção de altura mínima de " "linha. Por omissão não será feita qualquer manipulação da altura de linha." -#: /home/kovid/work/calibre/src/calibre/ebooks/conversion/plumber.py:194 +#: /home/kovid/work/calibre/src/calibre/ebooks/conversion/plumber.py:195 msgid "" "Some badly designed documents use tables to control the layout of text on " "the page. When converted these documents often have text that runs off the " @@ -1862,7 +2129,7 @@ msgstr "" "que sai para fora da página e outros problemas. Esta opção extrai o conteúdo " "das tabelas e apresenta-o de uma forma linear." -#: /home/kovid/work/calibre/src/calibre/ebooks/conversion/plumber.py:204 +#: /home/kovid/work/calibre/src/calibre/ebooks/conversion/plumber.py:205 msgid "" "XPath expression that specifies all tags that should be added to the Table " "of Contents at level one. If this is specified, it takes precedence over " @@ -1872,7 +2139,7 @@ msgstr "" "ao Índice com o nível 1. Se isto for especificado assume prevalência sobre " "outras formas de detecção automática." -#: /home/kovid/work/calibre/src/calibre/ebooks/conversion/plumber.py:213 +#: /home/kovid/work/calibre/src/calibre/ebooks/conversion/plumber.py:214 msgid "" "XPath expression that specifies all tags that should be added to the Table " "of Contents at level two. Each entry is added under the previous level one " @@ -1882,7 +2149,7 @@ msgstr "" "ao Índice com o nível 2. Cada entrada é acrescentada abaixo da entrada " "anterior com o nível 1." -#: /home/kovid/work/calibre/src/calibre/ebooks/conversion/plumber.py:221 +#: /home/kovid/work/calibre/src/calibre/ebooks/conversion/plumber.py:222 msgid "" "XPath expression that specifies all tags that should be added to the Table " "of Contents at level three. Each entry is added under the previous level two " @@ -1892,7 +2159,7 @@ msgstr "" "ao Índice com o nível 3. Cada entrada é acrescentada abaixo da entrada " "anterior com o nível 2." -#: /home/kovid/work/calibre/src/calibre/ebooks/conversion/plumber.py:229 +#: /home/kovid/work/calibre/src/calibre/ebooks/conversion/plumber.py:230 msgid "" "Normally, if the source file already has a Table of Contents, it is used in " "preference to the auto-generated one. With this option, the auto-generated " @@ -1902,11 +2169,11 @@ msgstr "" "vez do gerado automaticamente. Com esta opção o gerado automaticamente é " "sempre utilizado." -#: /home/kovid/work/calibre/src/calibre/ebooks/conversion/plumber.py:237 +#: /home/kovid/work/calibre/src/calibre/ebooks/conversion/plumber.py:238 msgid "Don't add auto-detected chapters to the Table of Contents." msgstr "Não adicionar ao Índice os capítulos detectados automaticamente." -#: /home/kovid/work/calibre/src/calibre/ebooks/conversion/plumber.py:244 +#: /home/kovid/work/calibre/src/calibre/ebooks/conversion/plumber.py:245 msgid "" "If fewer than this number of chapters is detected, then links are added to " "the Table of Contents. Default: %default" @@ -1914,7 +2181,7 @@ msgstr "" "Se forem detectados menos capítulos do que este número, os atalhos serão " "adicionados ao Índice. A predefinição é: %default" -#: /home/kovid/work/calibre/src/calibre/ebooks/conversion/plumber.py:251 +#: /home/kovid/work/calibre/src/calibre/ebooks/conversion/plumber.py:252 msgid "" "Maximum number of links to insert into the TOC. Set to 0 to disable. Default " "is: %default. Links are only added to the TOC if less than the threshold " @@ -1924,7 +2191,7 @@ msgstr "" "predefinição é: %default. Os atalhos só são adicionados ao Índice se forem " "detectados menos que o limite de capítulos." -#: /home/kovid/work/calibre/src/calibre/ebooks/conversion/plumber.py:259 +#: /home/kovid/work/calibre/src/calibre/ebooks/conversion/plumber.py:260 msgid "" "Remove entries from the Table of Contents whose titles match the specified " "regular expression. Matching entries and all their children are removed." @@ -1933,7 +2200,7 @@ msgstr "" "especificada. As entradas correspondentes e as suas dependentes são " "removidas." -#: /home/kovid/work/calibre/src/calibre/ebooks/conversion/plumber.py:270 +#: /home/kovid/work/calibre/src/calibre/ebooks/conversion/plumber.py:271 msgid "" "An XPath expression to detect chapter titles. The default is to consider " "

      or

      tags that contain the words \"chapter\",\"book\",\"section\" or " @@ -1950,7 +2217,7 @@ msgstr "" "expressão \"/\". Ver o Tutorial XPath no Manual do Utilizador do calibre " "para mais ajuda em como usar esta funcionalidade." -#: /home/kovid/work/calibre/src/calibre/ebooks/conversion/plumber.py:284 +#: /home/kovid/work/calibre/src/calibre/ebooks/conversion/plumber.py:285 msgid "" "Specify how to mark detected chapters. A value of \"pagebreak\" will insert " "page breaks before chapters. A value of \"rule\" will insert a line before " @@ -1963,7 +2230,7 @@ msgstr "" "desactivar a marcação de capítulos e um valor \"ambos\" irá usar tanto " "quebras de página como linhas para marcar os capítulos." -#: /home/kovid/work/calibre/src/calibre/ebooks/conversion/plumber.py:294 +#: /home/kovid/work/calibre/src/calibre/ebooks/conversion/plumber.py:295 msgid "" "Either the path to a CSS stylesheet or raw CSS. This CSS will be appended to " "the style rules from the source file, so it can be used to override those " @@ -1973,42 +2240,50 @@ msgstr "" "adicionado às regras de estilo do ficheiro de origem de modo a ser usado " "para se sobrepor a essas regras." -#: /home/kovid/work/calibre/src/calibre/ebooks/conversion/plumber.py:303 +#: /home/kovid/work/calibre/src/calibre/ebooks/conversion/plumber.py:304 msgid "" "An XPath expression. Page breaks are inserted before the specified elements." msgstr "" "Uma expressão XPath. As quebras de página são inseridas antes dos elementos " "especificados." -#: /home/kovid/work/calibre/src/calibre/ebooks/conversion/plumber.py:309 +#: /home/kovid/work/calibre/src/calibre/ebooks/conversion/plumber.py:310 +msgid "" +"Some documents specify page margins by specifying a left and right margin on " +"each individual paragraph. calibre will try to detect and remove these " +"margins. Sometimes, this can cause the removal of margins that should not " +"have been removed. In this case you can disable the removal." +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/ebooks/conversion/plumber.py:321 msgid "" "Set the top margin in pts. Default is %default. Note: 72 pts equals 1 inch" msgstr "" "Definir a margem superior em pts. A predefinição é %default. Nota: 72 pts é " "igual a 1 polegada" -#: /home/kovid/work/calibre/src/calibre/ebooks/conversion/plumber.py:314 +#: /home/kovid/work/calibre/src/calibre/ebooks/conversion/plumber.py:326 msgid "" "Set the bottom margin in pts. Default is %default. Note: 72 pts equals 1 inch" msgstr "" "Definir a margem inferior em pts. A predefinição é %default. Nota: 72 pts é " "igual a 1 polegada" -#: /home/kovid/work/calibre/src/calibre/ebooks/conversion/plumber.py:319 +#: /home/kovid/work/calibre/src/calibre/ebooks/conversion/plumber.py:331 msgid "" "Set the left margin in pts. Default is %default. Note: 72 pts equals 1 inch" msgstr "" "Definir a margem esquerda em pts. A predefinição é %default. Nota: 72 pts é " "igual a 1 polegada" -#: /home/kovid/work/calibre/src/calibre/ebooks/conversion/plumber.py:324 +#: /home/kovid/work/calibre/src/calibre/ebooks/conversion/plumber.py:336 msgid "" "Set the right margin in pts. Default is %default. Note: 72 pts equals 1 inch" msgstr "" "Definir a margem direita em pts. A predefinição é %default. Nota: 72 pts é " "igual a 1 polegada" -#: /home/kovid/work/calibre/src/calibre/ebooks/conversion/plumber.py:330 +#: /home/kovid/work/calibre/src/calibre/ebooks/conversion/plumber.py:342 msgid "" "Change text justification. A value of \"left\" converts all justified text " "in the source to left aligned (i.e. unjustified) text. A value of " @@ -2023,7 +2298,7 @@ msgstr "" "altera a justificação no arquivo original. Tenha em conta que apenas alguns " "formatos de saída suportam a justificação de texto." -#: /home/kovid/work/calibre/src/calibre/ebooks/conversion/plumber.py:340 +#: /home/kovid/work/calibre/src/calibre/ebooks/conversion/plumber.py:352 msgid "" "Remove spacing between paragraphs. Also sets an indent on paragraphs of " "1.5em. Spacing removal will not work if the source file does not use " @@ -2033,7 +2308,7 @@ msgstr "" "parágrafos de 1.5em. A remoção do espaçamento não funciona se o ficheiro de " "origem não usar parágrafos (etiquetas

      ou

      )." -#: /home/kovid/work/calibre/src/calibre/ebooks/conversion/plumber.py:347 +#: /home/kovid/work/calibre/src/calibre/ebooks/conversion/plumber.py:359 msgid "" "When calibre removes inter paragraph spacing, it automatically sets a " "paragraph indent, to ensure that paragraphs can be easily distinguished. " @@ -2044,14 +2319,14 @@ msgstr "" "facilmente distinguíveis. Esta opção controla a largura dessa indentação " "(ex.o avanço da primeira linha do parágrafo)." -#: /home/kovid/work/calibre/src/calibre/ebooks/conversion/plumber.py:354 +#: /home/kovid/work/calibre/src/calibre/ebooks/conversion/plumber.py:366 msgid "" "Use the cover detected from the source file in preference to the specified " "cover." msgstr "" "Usar a capa detectada no ficheiro de origem em vez da capa especificada." -#: /home/kovid/work/calibre/src/calibre/ebooks/conversion/plumber.py:360 +#: /home/kovid/work/calibre/src/calibre/ebooks/conversion/plumber.py:372 msgid "" "Insert a blank line between paragraphs. Will not work if the source file " "does not use paragraphs (

      or

      tags)." @@ -2059,7 +2334,7 @@ msgstr "" "Inserir uma linha em branco entre os parágrafos. Não funciona se o ficheiro " "de origem não usar parágrafos (etiquetas

      ou

      )." -#: /home/kovid/work/calibre/src/calibre/ebooks/conversion/plumber.py:367 +#: /home/kovid/work/calibre/src/calibre/ebooks/conversion/plumber.py:379 msgid "" "Remove the first image from the input ebook. Useful if the first image in " "the source file is a cover and you are specifying an external cover." @@ -2067,7 +2342,7 @@ msgstr "" "Remover a primeira imagem do livro de origem. Útil se a primeira imagem do " "ficheiro de origem é a capa e se está a especificar uma capa externa." -#: /home/kovid/work/calibre/src/calibre/ebooks/conversion/plumber.py:375 +#: /home/kovid/work/calibre/src/calibre/ebooks/conversion/plumber.py:387 msgid "" "Insert the book metadata at the start of the book. This is useful if your " "ebook reader does not support displaying/searching metadata directly." @@ -2075,7 +2350,7 @@ msgstr "" "Inserir os metadados do livro no seu início. Isto é útil se o seu leitor não " "suporta apresentar/procurar os metadados directamente." -#: /home/kovid/work/calibre/src/calibre/ebooks/conversion/plumber.py:383 +#: /home/kovid/work/calibre/src/calibre/ebooks/conversion/plumber.py:395 msgid "" "Convert plain quotes, dashes and ellipsis to their typographically correct " "equivalents. For details, see http://daringfireball.net/projects/smartypants" @@ -2084,7 +2359,7 @@ msgstr "" "tipográficos. Para detalhes, ver " "http://daringfireball.net/projects/smartypants" -#: /home/kovid/work/calibre/src/calibre/ebooks/conversion/plumber.py:392 +#: /home/kovid/work/calibre/src/calibre/ebooks/conversion/plumber.py:404 msgid "" "Read metadata from the specified OPF file. Metadata read from this file will " "override any metadata in the source file." @@ -2092,7 +2367,7 @@ msgstr "" "Ler os metadados do ficheiro OPF especificado. Os metadados lidos deste " "ficheiro vão sobrepor-se aos metadados no ficheiro de origem." -#: /home/kovid/work/calibre/src/calibre/ebooks/conversion/plumber.py:399 +#: /home/kovid/work/calibre/src/calibre/ebooks/conversion/plumber.py:411 msgid "" "Transliterate unicode characters to an ASCII representation. Use with care " "because this will replace unicode characters with ASCII. For instance it " @@ -2102,7 +2377,7 @@ msgid "" "current calibre interface language will be used." msgstr "" -#: /home/kovid/work/calibre/src/calibre/ebooks/conversion/plumber.py:414 +#: /home/kovid/work/calibre/src/calibre/ebooks/conversion/plumber.py:426 msgid "" "Preserve ligatures present in the input document. A ligature is a special " "rendering of a pair of characters like ff, fi, fl et cetera. Most readers do " @@ -2112,107 +2387,111 @@ msgid "" "instead." msgstr "" -#: /home/kovid/work/calibre/src/calibre/ebooks/conversion/plumber.py:426 +#: /home/kovid/work/calibre/src/calibre/ebooks/conversion/plumber.py:438 #: /home/kovid/work/calibre/src/calibre/ebooks/metadata/cli.py:38 msgid "Set the title." msgstr "Definir o título." -#: /home/kovid/work/calibre/src/calibre/ebooks/conversion/plumber.py:430 +#: /home/kovid/work/calibre/src/calibre/ebooks/conversion/plumber.py:442 msgid "Set the authors. Multiple authors should be separated by ampersands." msgstr "Definir os autores. Múltiplos autores devem ser separados por &." -#: /home/kovid/work/calibre/src/calibre/ebooks/conversion/plumber.py:435 +#: /home/kovid/work/calibre/src/calibre/ebooks/conversion/plumber.py:447 msgid "The version of the title to be used for sorting. " msgstr "A versão do título a ser usada para a ordenação. " -#: /home/kovid/work/calibre/src/calibre/ebooks/conversion/plumber.py:439 +#: /home/kovid/work/calibre/src/calibre/ebooks/conversion/plumber.py:451 msgid "String to be used when sorting by author. " msgstr "Expressão a ser usada quando ordenar por autor. " -#: /home/kovid/work/calibre/src/calibre/ebooks/conversion/plumber.py:443 +#: /home/kovid/work/calibre/src/calibre/ebooks/conversion/plumber.py:455 msgid "Set the cover to the specified file or URL" msgstr "Definir a capa com o ficheiro de imagem especificado ou com um URL" -#: /home/kovid/work/calibre/src/calibre/ebooks/conversion/plumber.py:447 +#: /home/kovid/work/calibre/src/calibre/ebooks/conversion/plumber.py:459 #: /home/kovid/work/calibre/src/calibre/ebooks/metadata/cli.py:54 msgid "Set the ebook description." msgstr "Definir a descrição do livro." -#: /home/kovid/work/calibre/src/calibre/ebooks/conversion/plumber.py:451 +#: /home/kovid/work/calibre/src/calibre/ebooks/conversion/plumber.py:463 #: /home/kovid/work/calibre/src/calibre/ebooks/metadata/cli.py:56 msgid "Set the ebook publisher." msgstr "Definir a editora do livro." -#: /home/kovid/work/calibre/src/calibre/ebooks/conversion/plumber.py:455 +#: /home/kovid/work/calibre/src/calibre/ebooks/conversion/plumber.py:467 #: /home/kovid/work/calibre/src/calibre/ebooks/metadata/cli.py:60 msgid "Set the series this ebook belongs to." msgstr "Definir a série a que este livro pertence." -#: /home/kovid/work/calibre/src/calibre/ebooks/conversion/plumber.py:459 +#: /home/kovid/work/calibre/src/calibre/ebooks/conversion/plumber.py:471 #: /home/kovid/work/calibre/src/calibre/ebooks/metadata/cli.py:62 msgid "Set the index of the book in this series." msgstr "Definir o índice do livro nesta série." -#: /home/kovid/work/calibre/src/calibre/ebooks/conversion/plumber.py:463 +#: /home/kovid/work/calibre/src/calibre/ebooks/conversion/plumber.py:475 #: /home/kovid/work/calibre/src/calibre/ebooks/metadata/cli.py:64 msgid "Set the rating. Should be a number between 1 and 5." msgstr "Definir a avaliação. Deve ser um algarismo entre 1 e 5." -#: /home/kovid/work/calibre/src/calibre/ebooks/conversion/plumber.py:467 +#: /home/kovid/work/calibre/src/calibre/ebooks/conversion/plumber.py:479 #: /home/kovid/work/calibre/src/calibre/ebooks/metadata/cli.py:66 msgid "Set the ISBN of the book." msgstr "Definir o ISBN do livro." -#: /home/kovid/work/calibre/src/calibre/ebooks/conversion/plumber.py:471 +#: /home/kovid/work/calibre/src/calibre/ebooks/conversion/plumber.py:483 #: /home/kovid/work/calibre/src/calibre/ebooks/metadata/cli.py:68 msgid "Set the tags for the book. Should be a comma separated list." msgstr "" "Definir as etiquetas do livro. Deve ser uma lista separada por vírgulas." -#: /home/kovid/work/calibre/src/calibre/ebooks/conversion/plumber.py:475 +#: /home/kovid/work/calibre/src/calibre/ebooks/conversion/plumber.py:487 #: /home/kovid/work/calibre/src/calibre/ebooks/metadata/cli.py:70 msgid "Set the book producer." msgstr "Definir o produtor do livro." -#: /home/kovid/work/calibre/src/calibre/ebooks/conversion/plumber.py:479 +#: /home/kovid/work/calibre/src/calibre/ebooks/conversion/plumber.py:491 #: /home/kovid/work/calibre/src/calibre/ebooks/metadata/cli.py:72 msgid "Set the language." msgstr "Definir a linguagem." -#: /home/kovid/work/calibre/src/calibre/ebooks/conversion/plumber.py:483 +#: /home/kovid/work/calibre/src/calibre/ebooks/conversion/plumber.py:495 msgid "Set the publication date." msgstr "Definir a data de publicação" -#: /home/kovid/work/calibre/src/calibre/ebooks/conversion/plumber.py:487 +#: /home/kovid/work/calibre/src/calibre/ebooks/conversion/plumber.py:499 msgid "Set the book timestamp (used by the date column in calibre)." msgstr "" "Definir a selo data/hora do livro (usado na coluna 'data' no calibre)" -#: /home/kovid/work/calibre/src/calibre/ebooks/conversion/plumber.py:491 +#: /home/kovid/work/calibre/src/calibre/ebooks/conversion/plumber.py:503 msgid "" "Enable heuristic processing. This option must be set for any heuristic " "processing to take place." msgstr "" +"Activar o processamento heurístico. Esta opção deve ser activada para o " +"processamento heurístico correr." -#: /home/kovid/work/calibre/src/calibre/ebooks/conversion/plumber.py:496 +#: /home/kovid/work/calibre/src/calibre/ebooks/conversion/plumber.py:508 msgid "" "Detect unformatted chapter headings and sub headings. Change them to h2 and " "h3 tags. This setting will not create a TOC, but can be used in conjunction " "with structure detection to create one." msgstr "" -#: /home/kovid/work/calibre/src/calibre/ebooks/conversion/plumber.py:503 +#: /home/kovid/work/calibre/src/calibre/ebooks/conversion/plumber.py:515 msgid "" "Look for common words and patterns that denote italics and italicize them." msgstr "" +"Pesquisar por palavras comuns e padrões que denotam itálicos e tornar esse " +"texto itálico" -#: /home/kovid/work/calibre/src/calibre/ebooks/conversion/plumber.py:508 +#: /home/kovid/work/calibre/src/calibre/ebooks/conversion/plumber.py:520 msgid "" "Turn indentation created from multiple non-breaking space entities into CSS " "indents." msgstr "" -#: /home/kovid/work/calibre/src/calibre/ebooks/conversion/plumber.py:513 +#: /home/kovid/work/calibre/src/calibre/ebooks/conversion/plumber.py:525 msgid "" "Scale used to determine the length at which a line should be unwrapped. " "Valid values are a decimal between 0 and 1. The default is 0.4, just below " @@ -2220,87 +2499,94 @@ msgid "" "unwrapping this value should be reduced" msgstr "" -#: /home/kovid/work/calibre/src/calibre/ebooks/conversion/plumber.py:521 +#: /home/kovid/work/calibre/src/calibre/ebooks/conversion/plumber.py:533 msgid "Unwrap lines using punctuation and other formatting clues." msgstr "" -#: /home/kovid/work/calibre/src/calibre/ebooks/conversion/plumber.py:525 +#: /home/kovid/work/calibre/src/calibre/ebooks/conversion/plumber.py:537 msgid "" "Remove empty paragraphs from the document when they exist between every " "other paragraph" msgstr "" +"Remover parágrafos vazios do documento quando eles existem entre outros " +"parágrafos" -#: /home/kovid/work/calibre/src/calibre/ebooks/conversion/plumber.py:530 +#: /home/kovid/work/calibre/src/calibre/ebooks/conversion/plumber.py:542 msgid "" "Left aligned scene break markers are center aligned. Replace soft scene " "breaks that use multiple blank lines withhorizontal rules." msgstr "" -#: /home/kovid/work/calibre/src/calibre/ebooks/conversion/plumber.py:536 +#: /home/kovid/work/calibre/src/calibre/ebooks/conversion/plumber.py:548 msgid "" "Replace scene breaks with the specified text. By default, the text from the " "input document is used." msgstr "" -#: /home/kovid/work/calibre/src/calibre/ebooks/conversion/plumber.py:541 +#: /home/kovid/work/calibre/src/calibre/ebooks/conversion/plumber.py:553 msgid "" "Analyze hyphenated words throughout the document. The document itself is " "used as a dictionary to determine whether hyphens should be retained or " "removed." msgstr "" -#: /home/kovid/work/calibre/src/calibre/ebooks/conversion/plumber.py:547 +#: /home/kovid/work/calibre/src/calibre/ebooks/conversion/plumber.py:559 msgid "" "Looks for occurrences of sequential

      or

      tags. The tags are " "renumbered to prevent splitting in the middle of chapter headings." msgstr "" +"Pesquisar por ocorrências de tags

      ou

      . As tags são re-enumeradas " +"para prevenir divisões no meio de cabeçalhos de parágrafos." -#: /home/kovid/work/calibre/src/calibre/ebooks/conversion/plumber.py:553 +#: /home/kovid/work/calibre/src/calibre/ebooks/conversion/plumber.py:565 msgid "Search pattern (regular expression) to be replaced with sr1-replace." msgstr "" +"Padrão de pesquisa (expressão regular) para ser substituido com um sr1-" +"replace" -#: /home/kovid/work/calibre/src/calibre/ebooks/conversion/plumber.py:558 +#: /home/kovid/work/calibre/src/calibre/ebooks/conversion/plumber.py:570 msgid "Replacement to replace the text found with sr1-search." -msgstr "" +msgstr "Substituição para substituir o texto encontrado com a sr1-search" -#: /home/kovid/work/calibre/src/calibre/ebooks/conversion/plumber.py:562 +#: /home/kovid/work/calibre/src/calibre/ebooks/conversion/plumber.py:574 msgid "Search pattern (regular expression) to be replaced with sr2-replace." -msgstr "" +msgstr "Substituição para substituir o texto encontrado com a sr2-replace" -#: /home/kovid/work/calibre/src/calibre/ebooks/conversion/plumber.py:567 +#: /home/kovid/work/calibre/src/calibre/ebooks/conversion/plumber.py:579 msgid "Replacement to replace the text found with sr2-search." -msgstr "" +msgstr "Substituição para substituir o texto encontrado com a sr2-search" -#: /home/kovid/work/calibre/src/calibre/ebooks/conversion/plumber.py:571 +#: /home/kovid/work/calibre/src/calibre/ebooks/conversion/plumber.py:583 msgid "Search pattern (regular expression) to be replaced with sr3-replace." msgstr "" +"Padrão de pesquisa (expressão regular) para ser substituido pela sr3-replace" -#: /home/kovid/work/calibre/src/calibre/ebooks/conversion/plumber.py:576 +#: /home/kovid/work/calibre/src/calibre/ebooks/conversion/plumber.py:588 msgid "Replacement to replace the text found with sr3-search." -msgstr "" +msgstr "Substituição para substituir o texto encontrado com a sr3-replace" -#: /home/kovid/work/calibre/src/calibre/ebooks/conversion/plumber.py:678 +#: /home/kovid/work/calibre/src/calibre/ebooks/conversion/plumber.py:690 msgid "Could not find an ebook inside the archive" msgstr "Foi impossível localizar um livro dentro do arquivo" -#: /home/kovid/work/calibre/src/calibre/ebooks/conversion/plumber.py:736 +#: /home/kovid/work/calibre/src/calibre/ebooks/conversion/plumber.py:748 msgid "Values of series index and rating must be numbers. Ignoring" msgstr "" "Os valores do índice da série e da avaliação devem ser algarismos. A ignorar" -#: /home/kovid/work/calibre/src/calibre/ebooks/conversion/plumber.py:743 +#: /home/kovid/work/calibre/src/calibre/ebooks/conversion/plumber.py:755 msgid "Failed to parse date/time" msgstr "Falha ao parsear a data/hora" -#: /home/kovid/work/calibre/src/calibre/ebooks/conversion/plumber.py:898 +#: /home/kovid/work/calibre/src/calibre/ebooks/conversion/plumber.py:914 msgid "Converting input to HTML..." msgstr "A converter o ficheiro de origem para HTML..." -#: /home/kovid/work/calibre/src/calibre/ebooks/conversion/plumber.py:925 +#: /home/kovid/work/calibre/src/calibre/ebooks/conversion/plumber.py:941 msgid "Running transforms on ebook..." msgstr "A executar as transformações no livro..." -#: /home/kovid/work/calibre/src/calibre/ebooks/conversion/plumber.py:1013 +#: /home/kovid/work/calibre/src/calibre/ebooks/conversion/plumber.py:1037 msgid "Creating" msgstr "A criar" @@ -2453,7 +2739,7 @@ msgstr "Iniciar" msgid "Do not insert a Table of Contents at the beginning of the book." msgstr "Não inserir um Índice no início do livro." -#: /home/kovid/work/calibre/src/calibre/ebooks/fb2/output.py:22 +#: /home/kovid/work/calibre/src/calibre/ebooks/fb2/output.py:149 msgid "" "Specify the sectionization of elements. A value of \"nothing\" turns the " "book into a single section. A value of \"files\" turns each file into a " @@ -2464,6 +2750,17 @@ msgid "" "of Contents)." msgstr "" +#: /home/kovid/work/calibre/src/calibre/ebooks/fb2/output.py:158 +msgid "" +"Genre for the book. Choices: %s\n" +"\n" +" See: " +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/ebooks/fb2/output.py:159 +msgid "for a complete list with descriptions." +msgstr "" + #: /home/kovid/work/calibre/src/calibre/ebooks/html/input.py:248 msgid "" "Traverse links in HTML files breadth first. Normally, they are traversed " @@ -2494,20 +2791,20 @@ msgstr "" "fazer porque pode resultar em vários efeitos colaterais nefastos no resto da " "cadeia de conversão." -#: /home/kovid/work/calibre/src/calibre/ebooks/html/output.py:33 +#: /home/kovid/work/calibre/src/calibre/ebooks/html/output.py:32 msgid "CSS file used for the output instead of the default file" msgstr "" "Folha de estílos (CSS) utilizada para output em vez da folha de estílos " "padrão" -#: /home/kovid/work/calibre/src/calibre/ebooks/html/output.py:36 +#: /home/kovid/work/calibre/src/calibre/ebooks/html/output.py:35 msgid "" "Template used for generation of the html index file instead of the default " "file" msgstr "" "Template utilizado para gerar o ficheiro index html em vez do ficheiro padrão" -#: /home/kovid/work/calibre/src/calibre/ebooks/html/output.py:39 +#: /home/kovid/work/calibre/src/calibre/ebooks/html/output.py:38 msgid "" "Template used for the generation of the html contents of the book instead of " "the default file" @@ -2515,7 +2812,7 @@ msgstr "" "Template utilizado para gerar os conteúdos html do livro em vez do ficheiro " "padrão" -#: /home/kovid/work/calibre/src/calibre/ebooks/html/output.py:42 +#: /home/kovid/work/calibre/src/calibre/ebooks/html/output.py:41 msgid "" "Extract the contents of the generated ZIP file to the specified directory. " "WARNING: The contents of the directory will be deleted." @@ -2523,6 +2820,22 @@ msgstr "" "Extraír os conteúdos do ficheiro ZIP para a directoria especificada. AVISO: " "Os conteúdos da directoria serão apagados." +#: /home/kovid/work/calibre/src/calibre/ebooks/htmlz/output.py:30 +msgid "" +"Specify the handling of CSS. Default is class.\n" +"class: Use CSS classes and have elements reference them.\n" +"inline: Write the CSS as an inline style attribute.\n" +"tag: Turn as many CSS styles as possible into HTML tags." +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/ebooks/htmlz/output.py:38 +msgid "" +"How to handle the CSS when using css-type = 'class'.\n" +"Default is external.\n" +"external: Use an external CSS file that is linked in the document.\n" +"inline: Place the CSS in the head section of the document." +msgstr "" + #: /home/kovid/work/calibre/src/calibre/ebooks/lit/from_any.py:47 msgid "Creating LIT file from EPUB..." msgstr "A criar o ficheiro LIT a partir do formato EPUB..." @@ -2661,7 +2974,6 @@ msgid "Path to output file" msgstr "Caminho para o ficheiro de destino" #: /home/kovid/work/calibre/src/calibre/ebooks/lrf/lrs/convert_from.py:290 -#: /home/kovid/work/calibre/src/calibre/ebooks/metadata/isbndb.py:126 msgid "Verbose processing" msgstr "Processamento com mais indicações" @@ -2818,48 +3130,6 @@ msgstr "Família de tipos de letra monospace a integrar" msgid "Comic" msgstr "Banda Desenhada" -#: /home/kovid/work/calibre/src/calibre/ebooks/metadata/amazonfr.py:26 -msgid "Downloads metadata from amazon.fr" -msgstr "Descarregar os metadados da amazon.fr" - -#: /home/kovid/work/calibre/src/calibre/ebooks/metadata/amazonfr.py:43 -msgid "Downloads metadata from amazon.com in spanish" -msgstr "Descarregar os metadados da amazon.com em espanhol" - -#: /home/kovid/work/calibre/src/calibre/ebooks/metadata/amazonfr.py:60 -msgid "Downloads metadata from amazon.com in english" -msgstr "Descarregar os metadados da amazon.com em inglês" - -#: /home/kovid/work/calibre/src/calibre/ebooks/metadata/amazonfr.py:77 -msgid "Downloads metadata from amazon.de" -msgstr "Descarregar os metadados da amazon.de" - -#: /home/kovid/work/calibre/src/calibre/ebooks/metadata/amazonfr.py:94 -msgid "Downloads metadata from amazon.com" -msgstr "Descarregar os metadados da amazon.com" - -#: /home/kovid/work/calibre/src/calibre/ebooks/metadata/amazonfr.py:474 -msgid "" -" %prog [options]\n" -"\n" -" Fetch book metadata from Amazon. You must specify one of title, " -"author,\n" -" ISBN, publisher or keywords. Will fetch a maximum of 10 matches,\n" -" so you should make your query as specific as possible.\n" -" You can chose the language for metadata retrieval:\n" -" All & english & french & german & spanish\n" -" " -msgstr "" -" %prog [opções]\n" -"\n" -" Obter os metadados da Amazon. Deve especificar o título, autor,\n" -" número ISBN, editora ou palavras-chave. Descarrega no máximo 10 " -"respostas,\n" -" pelo que deve fazer a pesquisa mais especifica possível.\n" -" Pode escolher a língua para os metadados:\n" -" Todas & inglês & francês & alemão & espanhol\n" -" " - #: /home/kovid/work/calibre/src/calibre/ebooks/metadata/archive.py:41 msgid "" "Extract common e-book formats from archives (zip/rar) files. Also try to " @@ -2868,107 +3138,100 @@ msgstr "" "Extrair os formatos e-book dos ficheiros de arquivo (zip/rar). Também tenta " "detectar se são ficheiros cbz/cbr." -#: /home/kovid/work/calibre/src/calibre/ebooks/metadata/book/base.py:116 +#: /home/kovid/work/calibre/src/calibre/ebooks/metadata/book/base.py:145 msgid "TEMPLATE ERROR" msgstr "ERRO DO TEMPLATE" -#: /home/kovid/work/calibre/src/calibre/ebooks/metadata/book/base.py:541 -#: /home/kovid/work/calibre/src/calibre/gui2/custom_column_widgets.py:64 -#: /home/kovid/work/calibre/src/calibre/gui2/custom_column_widgets.py:554 +#: /home/kovid/work/calibre/src/calibre/ebooks/metadata/book/base.py:628 +#: /home/kovid/work/calibre/src/calibre/gui2/custom_column_widgets.py:63 +#: /home/kovid/work/calibre/src/calibre/gui2/custom_column_widgets.py:563 msgid "No" msgstr "Não" -#: /home/kovid/work/calibre/src/calibre/ebooks/metadata/book/base.py:541 -#: /home/kovid/work/calibre/src/calibre/gui2/custom_column_widgets.py:64 -#: /home/kovid/work/calibre/src/calibre/gui2/custom_column_widgets.py:554 +#: /home/kovid/work/calibre/src/calibre/ebooks/metadata/book/base.py:628 +#: /home/kovid/work/calibre/src/calibre/gui2/custom_column_widgets.py:63 +#: /home/kovid/work/calibre/src/calibre/gui2/custom_column_widgets.py:563 msgid "Yes" msgstr "Sim" -#: /home/kovid/work/calibre/src/calibre/ebooks/metadata/book/base.py:615 +#: /home/kovid/work/calibre/src/calibre/ebooks/metadata/book/base.py:723 #: /home/kovid/work/calibre/src/calibre/ebooks/pdf/manipulate/info.py:45 -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/book_info.py:112 -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/book_info.py:113 #: /home/kovid/work/calibre/src/calibre/gui2/dialogs/delete_matching_from_device.py:75 -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/fetch_metadata.py:60 -#: /home/kovid/work/calibre/src/calibre/gui2/library/models.py:65 -#: /home/kovid/work/calibre/src/calibre/gui2/library/models.py:418 +#: /home/kovid/work/calibre/src/calibre/gui2/library/models.py:63 #: /home/kovid/work/calibre/src/calibre/gui2/library/models.py:977 -#: /home/kovid/work/calibre/src/calibre/library/field_metadata.py:304 -#: /home/kovid/work/calibre/src/calibre/library/server/opds.py:590 +#: /home/kovid/work/calibre/src/calibre/gui2/metadata/single_download.py:132 +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/metadata_sources.py:152 +#: /home/kovid/work/calibre/src/calibre/gui2/store/mobileread/models.py:23 +#: /home/kovid/work/calibre/src/calibre/gui2/store/search/models.py:33 +#: /home/kovid/work/calibre/src/calibre/library/field_metadata.py:331 +#: /home/kovid/work/calibre/src/calibre/library/server/opds.py:574 msgid "Title" msgstr "Título" -#: /home/kovid/work/calibre/src/calibre/ebooks/metadata/book/base.py:616 -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/fetch_metadata.py:61 -#: /home/kovid/work/calibre/src/calibre/gui2/library/models.py:67 -#: /home/kovid/work/calibre/src/calibre/gui2/library/models.py:423 +#: /home/kovid/work/calibre/src/calibre/ebooks/metadata/book/base.py:724 +#: /home/kovid/work/calibre/src/calibre/gui2/library/models.py:65 #: /home/kovid/work/calibre/src/calibre/gui2/library/models.py:978 +#: /home/kovid/work/calibre/src/calibre/gui2/store/mobileread/models.py:23 msgid "Author(s)" msgstr "Autor(es)" -#: /home/kovid/work/calibre/src/calibre/ebooks/metadata/book/base.py:617 -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/fetch_metadata.py:63 -#: /home/kovid/work/calibre/src/calibre/gui2/library/models.py:72 +#: /home/kovid/work/calibre/src/calibre/ebooks/metadata/book/base.py:725 +#: /home/kovid/work/calibre/src/calibre/gui2/library/models.py:70 +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/metadata_sources.py:149 msgid "Publisher" msgstr "Editora" -#: /home/kovid/work/calibre/src/calibre/ebooks/metadata/book/base.py:618 +#: /home/kovid/work/calibre/src/calibre/ebooks/metadata/book/base.py:726 #: /home/kovid/work/calibre/src/calibre/ebooks/pdf/manipulate/info.py:49 msgid "Producer" msgstr "Produtor" -#: /home/kovid/work/calibre/src/calibre/ebooks/metadata/book/base.py:619 -#: /home/kovid/work/calibre/src/calibre/gui2/book_details.py:40 -#: /home/kovid/work/calibre/src/calibre/gui2/book_details.py:214 -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/book_info.py:114 -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/book_info_ui.py:79 -#: /home/kovid/work/calibre/src/calibre/gui2/library/models.py:380 -#: /home/kovid/work/calibre/src/calibre/gui2/library/models.py:1184 -#: /home/kovid/work/calibre/src/calibre/library/field_metadata.py:188 +#: /home/kovid/work/calibre/src/calibre/ebooks/metadata/book/base.py:727 +#: /home/kovid/work/calibre/src/calibre/gui2/metadata/single.py:871 +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/metadata_sources.py:147 +#: /home/kovid/work/calibre/src/calibre/library/field_metadata.py:211 msgid "Comments" msgstr "Comentários" -#: /home/kovid/work/calibre/src/calibre/ebooks/metadata/book/base.py:621 +#: /home/kovid/work/calibre/src/calibre/ebooks/metadata/book/base.py:729 #: /home/kovid/work/calibre/src/calibre/ebooks/oeb/transforms/jacket.py:170 -#: /home/kovid/work/calibre/src/calibre/gui2/book_details.py:30 #: /home/kovid/work/calibre/src/calibre/gui2/dialogs/tag_categories.py:60 -#: /home/kovid/work/calibre/src/calibre/gui2/library/models.py:73 -#: /home/kovid/work/calibre/src/calibre/gui2/library/models.py:368 -#: /home/kovid/work/calibre/src/calibre/gui2/library/models.py:1180 -#: /home/kovid/work/calibre/src/calibre/library/field_metadata.py:161 -#: /home/kovid/work/calibre/src/calibre/library/server/browse.py:691 +#: /home/kovid/work/calibre/src/calibre/gui2/library/models.py:71 +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/create_custom_column.py:67 +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/metadata_sources.py:151 +#: /home/kovid/work/calibre/src/calibre/library/field_metadata.py:171 +#: /home/kovid/work/calibre/src/calibre/library/server/browse.py:761 msgid "Tags" msgstr "Etiquetas" -#: /home/kovid/work/calibre/src/calibre/ebooks/metadata/book/base.py:623 +#: /home/kovid/work/calibre/src/calibre/ebooks/metadata/book/base.py:731 #: /home/kovid/work/calibre/src/calibre/ebooks/oeb/transforms/jacket.py:168 -#: /home/kovid/work/calibre/src/calibre/gui2/book_details.py:29 #: /home/kovid/work/calibre/src/calibre/gui2/dialogs/tag_categories.py:60 -#: /home/kovid/work/calibre/src/calibre/gui2/library/models.py:74 -#: /home/kovid/work/calibre/src/calibre/gui2/library/models.py:385 -#: /home/kovid/work/calibre/src/calibre/gui2/library/models.py:1189 -#: /home/kovid/work/calibre/src/calibre/library/field_metadata.py:109 +#: /home/kovid/work/calibre/src/calibre/gui2/library/models.py:72 +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/create_custom_column.py:67 +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/metadata_sources.py:153 +#: /home/kovid/work/calibre/src/calibre/library/field_metadata.py:114 msgid "Series" msgstr "Série" -#: /home/kovid/work/calibre/src/calibre/ebooks/metadata/book/base.py:624 +#: /home/kovid/work/calibre/src/calibre/ebooks/metadata/book/base.py:732 +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/metadata_sources.py:154 msgid "Language" msgstr "Linguagem" -#: /home/kovid/work/calibre/src/calibre/ebooks/metadata/book/base.py:626 -#: /home/kovid/work/calibre/src/calibre/gui2/library/models.py:1172 +#: /home/kovid/work/calibre/src/calibre/ebooks/metadata/book/base.py:734 msgid "Timestamp" msgstr "Data e Hora" -#: /home/kovid/work/calibre/src/calibre/ebooks/metadata/book/base.py:628 +#: /home/kovid/work/calibre/src/calibre/ebooks/metadata/book/base.py:736 #: /home/kovid/work/calibre/src/calibre/ebooks/oeb/transforms/jacket.py:167 -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/fetch_metadata.py:65 -#: /home/kovid/work/calibre/src/calibre/gui2/library/models.py:70 -#: /home/kovid/work/calibre/src/calibre/library/field_metadata.py:259 +#: /home/kovid/work/calibre/src/calibre/gui2/library/models.py:68 +#: /home/kovid/work/calibre/src/calibre/gui2/metadata/single_download.py:132 +#: /home/kovid/work/calibre/src/calibre/library/field_metadata.py:271 msgid "Published" msgstr "Editado" -#: /home/kovid/work/calibre/src/calibre/ebooks/metadata/book/base.py:630 +#: /home/kovid/work/calibre/src/calibre/ebooks/metadata/book/base.py:738 msgid "Rights" msgstr "Direitos" @@ -3087,212 +3350,7 @@ msgstr "Capa guardada em" msgid "No cover found" msgstr "Não foi encontrada nenhuma capa" -#: /home/kovid/work/calibre/src/calibre/ebooks/metadata/covers.py:27 -#: /home/kovid/work/calibre/src/calibre/ebooks/metadata/nicebooks.py:45 -msgid "Cover download" -msgstr "Descarregar capa" - -#: /home/kovid/work/calibre/src/calibre/ebooks/metadata/covers.py:79 -msgid "Download covers from openlibrary.org" -msgstr "Descarregar capas de openlibrary.org" - -#: /home/kovid/work/calibre/src/calibre/ebooks/metadata/covers.py:107 -msgid "ISBN: %s not found" -msgstr "ISBN: %s não encontrado" - -#: /home/kovid/work/calibre/src/calibre/ebooks/metadata/covers.py:117 -msgid "Download covers from amazon.com" -msgstr "" - -#: /home/kovid/work/calibre/src/calibre/ebooks/metadata/covers.py:205 -msgid "Download covers from Douban.com" -msgstr "Descarregar capas de Douban.com" - -#: /home/kovid/work/calibre/src/calibre/ebooks/metadata/covers.py:214 -msgid "Douban.com API timed out. Try again later." -msgstr "A ligação à API do Douban.com expirou. Tente novamente mais tarde." - -#: /home/kovid/work/calibre/src/calibre/ebooks/metadata/douban.py:42 -msgid "Downloads metadata from Douban.com" -msgstr "Descarregar os metadados de Douban.com" - -#: /home/kovid/work/calibre/src/calibre/ebooks/metadata/fetch.py:57 -msgid "Metadata download" -msgstr "Descarregar metadados" - -#: /home/kovid/work/calibre/src/calibre/ebooks/metadata/fetch.py:141 -msgid "ratings" -msgstr "Avaliações" - -#: /home/kovid/work/calibre/src/calibre/ebooks/metadata/fetch.py:141 -msgid "tags" -msgstr "etiquetas" - -#: /home/kovid/work/calibre/src/calibre/ebooks/metadata/fetch.py:142 -msgid "description/reviews" -msgstr "descrições/revisões" - -#: /home/kovid/work/calibre/src/calibre/ebooks/metadata/fetch.py:143 -msgid "Download %s from %s" -msgstr "Descarregar %s de %s" - -#: /home/kovid/work/calibre/src/calibre/ebooks/metadata/fetch.py:150 -msgid "Convert comments downloaded from %s to plain text" -msgstr "Converter comentários descarregados de %s para texto simples" - -#: /home/kovid/work/calibre/src/calibre/ebooks/metadata/fetch.py:178 -#: /home/kovid/work/calibre/src/calibre/ebooks/metadata/sources/google.py:159 -msgid "Downloads metadata from Google Books" -msgstr "Descarregar os metadados do Google Books." - -#: /home/kovid/work/calibre/src/calibre/ebooks/metadata/fetch.py:195 -msgid "Downloads metadata from isbndb.com" -msgstr "Descarregar os metadados de isbndb.com" - -#: /home/kovid/work/calibre/src/calibre/ebooks/metadata/fetch.py:223 -msgid "" -"To use isbndb.com you must sign up for a %sfree account%s and enter your " -"access key below." -msgstr "" -"Para utilizar o isbndb.com tem primeiro que criar uma %sconta gratuíta%s e " -"introduzir a sua chave de acesso aqui." - -#: /home/kovid/work/calibre/src/calibre/ebooks/metadata/fetch.py:233 -msgid "Downloads social metadata from amazon.com" -msgstr "Descarregar metadados sociais da amazon.com" - -#: /home/kovid/work/calibre/src/calibre/ebooks/metadata/fetch.py:254 -msgid "Downloads series information from ww2.kdl.org" -msgstr "" - -#: /home/kovid/work/calibre/src/calibre/ebooks/metadata/fictionwise.py:25 -msgid "Downloads metadata from Fictionwise" -msgstr "Descarregar os metadados da Fictionwise" - -#: /home/kovid/work/calibre/src/calibre/ebooks/metadata/fictionwise.py:90 -#: /home/kovid/work/calibre/src/calibre/ebooks/metadata/nicebooks.py:108 -msgid "Query: %s" -msgstr "Pesquisa: %s" - -#: /home/kovid/work/calibre/src/calibre/ebooks/metadata/fictionwise.py:100 -#: /home/kovid/work/calibre/src/calibre/ebooks/metadata/fictionwise.py:285 -msgid "Fictionwise timed out. Try again later." -msgstr "A ligação do Fictionwise expirou. Tente novamente mais tarde." - -#: /home/kovid/work/calibre/src/calibre/ebooks/metadata/fictionwise.py:101 -#: /home/kovid/work/calibre/src/calibre/ebooks/metadata/fictionwise.py:286 -msgid "Fictionwise encountered an error." -msgstr "Foi encontrado um erro na Fictionwise" - -#: /home/kovid/work/calibre/src/calibre/ebooks/metadata/fictionwise.py:219 -msgid "" -"SUMMARY:\n" -" %s" -msgstr "" -"Sumário:\n" -" %s" - -#: /home/kovid/work/calibre/src/calibre/ebooks/metadata/fictionwise.py:316 -#: /home/kovid/work/calibre/src/calibre/ebooks/metadata/fictionwise.py:333 -msgid "Failed to get all details for an entry" -msgstr "Não foi possível obter os detalhes para uma entrada" - -#: /home/kovid/work/calibre/src/calibre/ebooks/metadata/fictionwise.py:354 -msgid "" -" %prog [options]\n" -"\n" -" Fetch book metadata from Fictionwise. You must specify one of title, " -"author,\n" -" or keywords. No ISBN specification possible. Will fetch a maximum of " -"20 matches,\n" -" so you should make your query as specific as possible.\n" -" " -msgstr "" -" %prog [opções]\n" -"\n" -" Obter os metadados do livro da Fictionwise. Deve especificar o " -"título, autor,\n" -" ou palavras-chave. Não é possível especificar o ISBN. Obtém no " -"máximo 20 registos,\n" -" pelo que deve fazer uma pesquisa tão especifica quanto possível\n" -" " - -#: /home/kovid/work/calibre/src/calibre/ebooks/metadata/fictionwise.py:362 -#: /home/kovid/work/calibre/src/calibre/ebooks/metadata/nicebooks.py:363 -msgid "Book title" -msgstr "Título do livro" - -#: /home/kovid/work/calibre/src/calibre/ebooks/metadata/fictionwise.py:363 -#: /home/kovid/work/calibre/src/calibre/ebooks/metadata/nicebooks.py:364 -msgid "Book author(s)" -msgstr "Autor(es) do livro" - -#: /home/kovid/work/calibre/src/calibre/ebooks/metadata/fictionwise.py:364 -#: /home/kovid/work/calibre/src/calibre/ebooks/metadata/nicebooks.py:365 -msgid "Book publisher" -msgstr "Editora do livro" - -#: /home/kovid/work/calibre/src/calibre/ebooks/metadata/fictionwise.py:365 -#: /home/kovid/work/calibre/src/calibre/ebooks/metadata/nicebooks.py:367 -msgid "Keywords" -msgstr "Palavras-chave" - -#: /home/kovid/work/calibre/src/calibre/ebooks/metadata/fictionwise.py:367 -#: /home/kovid/work/calibre/src/calibre/ebooks/metadata/nicebooks.py:373 -msgid "Maximum number of results to fetch" -msgstr "Número máximo de registos a devolver" - -#: /home/kovid/work/calibre/src/calibre/ebooks/metadata/fictionwise.py:369 -#: /home/kovid/work/calibre/src/calibre/ebooks/metadata/nicebooks.py:375 -msgid "Be more verbose about errors" -msgstr "Ser mais desciptivo acerca dos erros" - -#: /home/kovid/work/calibre/src/calibre/ebooks/metadata/fictionwise.py:383 -#: /home/kovid/work/calibre/src/calibre/ebooks/metadata/nicebooks.py:390 -msgid "No result found for this search!" -msgstr "A procura não produziu resultados !" - -#: /home/kovid/work/calibre/src/calibre/ebooks/metadata/isbndb.py:107 -msgid "" -"\n" -"%prog [options] key\n" -"\n" -"Fetch metadata for books from isndb.com. You can specify either the\n" -"books ISBN ID or its title and author. If you specify the title and author,\n" -"then more than one book may be returned.\n" -"\n" -"key is the account key you generate after signing up for a free account from " -"isbndb.com.\n" -"\n" -msgstr "" -"\n" -"%prog [options] key\n" -"\n" -"Recolher os metadados para os livros de isndb.com. Pode especificar ou\n" -"o ISBN do livro ou o seu título e autor. Se especificar o título e autor,\n" -"então pode ser mostrado mais do que um livro.\n" -"\n" -"key é a chave da conta que gerou depois de se ter registado para uma conta " -"grátis em isbndb.com.\n" -"\n" - -#: /home/kovid/work/calibre/src/calibre/ebooks/metadata/isbndb.py:118 -msgid "The ISBN ID of the book you want metadata for." -msgstr "O ISBN do livro para o qual quer os metadados." - -#: /home/kovid/work/calibre/src/calibre/ebooks/metadata/isbndb.py:120 -msgid "The author whose book to search for." -msgstr "O autor do livro que você está procura." - -#: /home/kovid/work/calibre/src/calibre/ebooks/metadata/isbndb.py:122 -msgid "The title of the book to search for." -msgstr "O título do livro que você está procura." - -#: /home/kovid/work/calibre/src/calibre/ebooks/metadata/isbndb.py:124 -msgid "The publisher of the book to search for." -msgstr "A editora do livro que você está procura." - -#: /home/kovid/work/calibre/src/calibre/ebooks/metadata/library_thing.py:75 +#: /home/kovid/work/calibre/src/calibre/ebooks/metadata/library_thing.py:64 msgid "" "\n" "%prog [options] ISBN\n" @@ -3306,88 +3364,106 @@ msgstr "" "Obter a imagem da capa/metadados socias para o livro identificado pelo ISBN " "a partir de LibraryThing.com\n" -#: /home/kovid/work/calibre/src/calibre/ebooks/metadata/nicebooks.py:26 -msgid "Downloads metadata from french Nicebooks" -msgstr "Descarregar os metadados a partir da Nicebooks francesa" - -#: /home/kovid/work/calibre/src/calibre/ebooks/metadata/nicebooks.py:42 -msgid "Downloads covers from french Nicebooks" -msgstr "Descarregar as capas a partir da Nicebooks francesa" - -#: /home/kovid/work/calibre/src/calibre/ebooks/metadata/nicebooks.py:118 -#: /home/kovid/work/calibre/src/calibre/ebooks/metadata/nicebooks.py:242 -#: /home/kovid/work/calibre/src/calibre/ebooks/metadata/nicebooks.py:320 -msgid "Nicebooks timed out. Try again later." -msgstr "A ligação à Nicebooks expirou. Tente novamente mais tarde." - -#: /home/kovid/work/calibre/src/calibre/ebooks/metadata/nicebooks.py:119 -#: /home/kovid/work/calibre/src/calibre/ebooks/metadata/nicebooks.py:243 -msgid "Nicebooks encountered an error." -msgstr "A ligação à Nicebooks encontrou um erro." - -#: /home/kovid/work/calibre/src/calibre/ebooks/metadata/nicebooks.py:323 -msgid "ISBN: %s not found." -msgstr "ISBN: %s não encontrado" - -#: /home/kovid/work/calibre/src/calibre/ebooks/metadata/nicebooks.py:324 -msgid "An errror occured with Nicebooks cover fetcher" -msgstr "Aconteceu um erro ao tentar obter a capa a partir da Nicebooks" - -#: /home/kovid/work/calibre/src/calibre/ebooks/metadata/nicebooks.py:354 -msgid "" -" %prog [options]\n" -"\n" -" Fetch book metadata from Nicebooks. You must specify one of title, " -"author,\n" -" ISBN, publisher or keywords. Will fetch a maximum of 20 matches,\n" -" so you should make your query as specific as possible.\n" -" It can also get covers if the option is activated.\n" -" " -msgstr "" -" %prog [opções]\n" -"\n" -" Obter os metadados a partir da Nicebooks. Deve especificar o título, " -"autor,\n" -" ISBN, editor ou palavras-chave. Obtêm no máximo 20 registos,\n" -" pelo que deve fazer a sua pesquisa o mais especifica possível.\n" -" Também permite obter as capas se esta opção estiver activa.\n" -" " - -#: /home/kovid/work/calibre/src/calibre/ebooks/metadata/nicebooks.py:366 -msgid "Book ISBN" -msgstr "ISBN do Livro" - -#: /home/kovid/work/calibre/src/calibre/ebooks/metadata/nicebooks.py:369 -msgid "Covers: 1-Check/ 2-Download" -msgstr "Capas: 1-Verificar/ 2-Descarregar" - -#: /home/kovid/work/calibre/src/calibre/ebooks/metadata/nicebooks.py:371 -msgid "Covers files path" -msgstr "Caminho para os ficheiros das Capas" - -#: /home/kovid/work/calibre/src/calibre/ebooks/metadata/nicebooks.py:396 -msgid "No cover found!" -msgstr "Não foi encontrada nenhuma capa!" - -#: /home/kovid/work/calibre/src/calibre/ebooks/metadata/nicebooks.py:398 -msgid "A cover was found for this book" -msgstr "Foi encontrada uma capa para este livro" - -#: /home/kovid/work/calibre/src/calibre/ebooks/metadata/nicebooks.py:407 -msgid "Cover saved to file " -msgstr "Capa guardada para o ficheiro " - -#: /home/kovid/work/calibre/src/calibre/ebooks/metadata/opf2.py:1312 -#: /home/kovid/work/calibre/src/calibre/ebooks/oeb/base.py:1448 +#: /home/kovid/work/calibre/src/calibre/ebooks/metadata/opf2.py:1358 +#: /home/kovid/work/calibre/src/calibre/ebooks/oeb/base.py:1493 +#: /home/kovid/work/calibre/src/calibre/gui2/metadata/single.py:883 +#: /home/kovid/work/calibre/src/calibre/gui2/store/search/models.py:33 msgid "Cover" msgstr "Capa" -#: /home/kovid/work/calibre/src/calibre/ebooks/metadata/sources/amazon.py:16 -msgid "Downloads metadata from Amazon" +#: /home/kovid/work/calibre/src/calibre/ebooks/metadata/sources/amazon.py:384 +msgid "Downloads metadata and covers from Amazon" msgstr "" -#: /home/kovid/work/calibre/src/calibre/ebooks/metadata/sources/base.py:22 +#: /home/kovid/work/calibre/src/calibre/ebooks/metadata/sources/amazon.py:394 +msgid "US" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/ebooks/metadata/sources/amazon.py:395 +msgid "France" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/ebooks/metadata/sources/amazon.py:396 +msgid "Germany" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/ebooks/metadata/sources/amazon.py:397 +msgid "UK" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/ebooks/metadata/sources/amazon.py:398 +msgid "Italy" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/ebooks/metadata/sources/amazon.py:402 +msgid "Amazon website to use:" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/ebooks/metadata/sources/amazon.py:403 +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:508 +msgid "Amazon timed out. Try again later." +msgstr "O pedido à Amazon está a demorar demasiado. Tente mais tarde." + +#: /home/kovid/work/calibre/src/calibre/ebooks/metadata/sources/base.py:159 msgid "Metadata source" +msgstr "Fonte de meta-dados" + +#: /home/kovid/work/calibre/src/calibre/ebooks/metadata/sources/douban.py:154 +msgid "Downloads metadata and covers from Douban.com" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/ebooks/metadata/sources/google.py:160 +msgid "Downloads metadata and covers from Google Books" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/ebooks/metadata/sources/isbndb.py:27 +msgid "Downloads metadata from isbndb.com" +msgstr "Descarregar os metadados de isbndb.com" + +#: /home/kovid/work/calibre/src/calibre/ebooks/metadata/sources/isbndb.py:37 +msgid "IsbnDB key:" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/ebooks/metadata/sources/isbndb.py:38 +msgid "" +"To use isbndb.com you have to sign up for a free accountat isbndb.com and " +"get an access key." +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/ebooks/metadata/sources/isbndb.py:42 +msgid "" +"To use metadata from isbndb.com you must sign up for a free account and get " +"an isbndb key and enter it below. Instructions to get the key are here." +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/ebooks/metadata/sources/openlibrary.py:15 +msgid "Downloads covers from The Open Library" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/ebooks/metadata/sources/overdrive.py:33 +msgid "Downloads metadata and covers from Overdrive's Content Reserve" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/ebooks/metadata/sources/overdrive.py:45 +msgid "Download all metadata (slow)" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/ebooks/metadata/sources/overdrive.py:46 +msgid "Enable this option to gather all metadata available from Overdrive." +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/ebooks/metadata/sources/overdrive.py:49 +msgid "" +"Additional metadata can be taken from Overdrive's book detail page. This " +"includes a limited set of tags used by libraries, comments, language, and " +"the ebook ISBN. Collecting this data is disabled by default due to the extra " +"time required. Check the download all metadata option below to enable " +"downloading this data." msgstr "" #: /home/kovid/work/calibre/src/calibre/ebooks/mobi/output.py:22 @@ -3434,74 +3510,74 @@ msgstr "" msgid "All articles" msgstr "Todos os artigos" -#: /home/kovid/work/calibre/src/calibre/ebooks/mobi/reader.py:267 +#: /home/kovid/work/calibre/src/calibre/ebooks/mobi/reader.py:274 msgid "This is an Amazon Topaz book. It cannot be processed." msgstr "Isto é um livro Amazon Topaz. Não pode ser processado." -#: /home/kovid/work/calibre/src/calibre/ebooks/oeb/base.py:1449 +#: /home/kovid/work/calibre/src/calibre/ebooks/oeb/base.py:1494 msgid "Title Page" msgstr "Página de Título" -#: /home/kovid/work/calibre/src/calibre/ebooks/oeb/base.py:1450 +#: /home/kovid/work/calibre/src/calibre/ebooks/oeb/base.py:1495 #: /home/kovid/work/calibre/src/calibre/ebooks/oeb/transforms/htmltoc.py:15 -#: /home/kovid/work/calibre/src/calibre/gui2/viewer/main.py:54 +#: /home/kovid/work/calibre/src/calibre/gui2/viewer/main.py:56 #: /home/kovid/work/calibre/src/calibre/gui2/viewer/main_ui.py:199 msgid "Table of Contents" msgstr "Índice" -#: /home/kovid/work/calibre/src/calibre/ebooks/oeb/base.py:1451 +#: /home/kovid/work/calibre/src/calibre/ebooks/oeb/base.py:1496 msgid "Index" msgstr "Índice" -#: /home/kovid/work/calibre/src/calibre/ebooks/oeb/base.py:1452 +#: /home/kovid/work/calibre/src/calibre/ebooks/oeb/base.py:1497 msgid "Glossary" msgstr "Glossário" -#: /home/kovid/work/calibre/src/calibre/ebooks/oeb/base.py:1453 +#: /home/kovid/work/calibre/src/calibre/ebooks/oeb/base.py:1498 msgid "Acknowledgements" msgstr "Agradecimentos" -#: /home/kovid/work/calibre/src/calibre/ebooks/oeb/base.py:1454 +#: /home/kovid/work/calibre/src/calibre/ebooks/oeb/base.py:1499 msgid "Bibliography" msgstr "Bibliografia" -#: /home/kovid/work/calibre/src/calibre/ebooks/oeb/base.py:1455 +#: /home/kovid/work/calibre/src/calibre/ebooks/oeb/base.py:1500 msgid "Colophon" msgstr "Marca Tipográfica" -#: /home/kovid/work/calibre/src/calibre/ebooks/oeb/base.py:1456 +#: /home/kovid/work/calibre/src/calibre/ebooks/oeb/base.py:1501 msgid "Copyright" msgstr "Direitos de Autor" -#: /home/kovid/work/calibre/src/calibre/ebooks/oeb/base.py:1457 +#: /home/kovid/work/calibre/src/calibre/ebooks/oeb/base.py:1502 msgid "Dedication" msgstr "Dedicatória" -#: /home/kovid/work/calibre/src/calibre/ebooks/oeb/base.py:1458 +#: /home/kovid/work/calibre/src/calibre/ebooks/oeb/base.py:1503 msgid "Epigraph" msgstr "Epígrafe" -#: /home/kovid/work/calibre/src/calibre/ebooks/oeb/base.py:1459 +#: /home/kovid/work/calibre/src/calibre/ebooks/oeb/base.py:1504 msgid "Foreword" msgstr "Prefácio" -#: /home/kovid/work/calibre/src/calibre/ebooks/oeb/base.py:1460 +#: /home/kovid/work/calibre/src/calibre/ebooks/oeb/base.py:1505 msgid "List of Illustrations" msgstr "Lista de Ilustrações" -#: /home/kovid/work/calibre/src/calibre/ebooks/oeb/base.py:1461 +#: /home/kovid/work/calibre/src/calibre/ebooks/oeb/base.py:1506 msgid "List of Tables" msgstr "Lista de Tabelas" -#: /home/kovid/work/calibre/src/calibre/ebooks/oeb/base.py:1462 +#: /home/kovid/work/calibre/src/calibre/ebooks/oeb/base.py:1507 msgid "Notes" msgstr "Notas" -#: /home/kovid/work/calibre/src/calibre/ebooks/oeb/base.py:1463 +#: /home/kovid/work/calibre/src/calibre/ebooks/oeb/base.py:1508 msgid "Preface" msgstr "Prefácio" -#: /home/kovid/work/calibre/src/calibre/ebooks/oeb/base.py:1464 +#: /home/kovid/work/calibre/src/calibre/ebooks/oeb/base.py:1509 msgid "Main Text" msgstr "Texto Principal" @@ -3511,8 +3587,7 @@ msgstr "Os livros no formato %s não são suportados" #: /home/kovid/work/calibre/src/calibre/ebooks/oeb/transforms/cover.py:98 #: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk.py:176 -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single.py:220 -#: /home/kovid/work/calibre/src/calibre/gui2/metadata/basic_widgets.py:703 +#: /home/kovid/work/calibre/src/calibre/gui2/metadata/basic_widgets.py:783 msgid "Book %s of %s" msgstr "Livro %s de %s" @@ -3521,8 +3596,10 @@ msgid "HTML TOC generation options." msgstr "Opções de geração do Índice em HTML." #: /home/kovid/work/calibre/src/calibre/ebooks/oeb/transforms/jacket.py:169 -#: /home/kovid/work/calibre/src/calibre/gui2/library/models.py:71 -#: /home/kovid/work/calibre/src/calibre/library/server/browse.py:689 +#: /home/kovid/work/calibre/src/calibre/gui2/library/models.py:69 +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/create_custom_column.py:68 +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/metadata_sources.py:150 +#: /home/kovid/work/calibre/src/calibre/library/server/browse.py:759 msgid "Rating" msgstr "Avaliação" @@ -3552,7 +3629,7 @@ msgstr "" msgid "Footnotes" msgstr "Notas de rodapé" -#: /home/kovid/work/calibre/src/calibre/ebooks/pdb/ereader/reader132.py:135 +#: /home/kovid/work/calibre/src/calibre/ebooks/pdb/ereader/reader132.py:139 msgid "Sidebar" msgstr "Barra lateral" @@ -3569,9 +3646,9 @@ msgstr "" "predefinição é cp1252. Nota: Esta opção não é cumprida por todos os formatos." #: /home/kovid/work/calibre/src/calibre/ebooks/pdb/output.py:32 -#: /home/kovid/work/calibre/src/calibre/ebooks/pml/output.py:37 +#: /home/kovid/work/calibre/src/calibre/ebooks/pml/output.py:36 #: /home/kovid/work/calibre/src/calibre/ebooks/rb/output.py:21 -#: /home/kovid/work/calibre/src/calibre/ebooks/txt/output.py:41 +#: /home/kovid/work/calibre/src/calibre/ebooks/txt/output.py:40 msgid "Add Table of Contents to beginning of the book." msgstr "Adicionar Índice no início do livro." @@ -3713,11 +3790,12 @@ msgstr "" #: /home/kovid/work/calibre/src/calibre/ebooks/pdf/manipulate/info.py:46 #: /home/kovid/work/calibre/src/calibre/gui2/dialogs/delete_matching_from_device.py:75 -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/edit_authors_dialog.py:35 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/edit_authors_dialog.py:40 msgid "Author" msgstr "Autor" #: /home/kovid/work/calibre/src/calibre/ebooks/pdf/manipulate/info.py:47 +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/emailp.py:27 msgid "Subject" msgstr "Assunto" @@ -3848,19 +3926,21 @@ msgid "" "Preserve the aspect ratio of the cover, instead of stretching it to fill the " "full first page of the generated pdf." msgstr "" +"Preservar a relação altura/largura da capa ao invés de esticá-la para caber " +"na primeira página inteira do pdf gerado." -#: /home/kovid/work/calibre/src/calibre/ebooks/pdf/pdftohtml.py:55 +#: /home/kovid/work/calibre/src/calibre/ebooks/pdf/pdftohtml.py:57 msgid "Could not find pdftohtml, check it is in your PATH" msgstr "É impossível encontrar pdftohtml, verifique se está no seu caminho" -#: /home/kovid/work/calibre/src/calibre/ebooks/pml/output.py:33 +#: /home/kovid/work/calibre/src/calibre/ebooks/pml/output.py:32 msgid "" "Specify the character encoding of the output document. The default is cp1252." msgstr "" "Especifique a codificação de caracteres para o documento de destino. A " "predefinição é cp1252." -#: /home/kovid/work/calibre/src/calibre/ebooks/pml/output.py:40 +#: /home/kovid/work/calibre/src/calibre/ebooks/pml/output.py:39 msgid "" "Do not reduce the size or bit depth of images. Images have their size and " "depth reduced by default to accommodate applications that can not convert " @@ -3875,7 +3955,7 @@ msgstr "" msgid "Table of Contents:" msgstr "Índice:" -#: /home/kovid/work/calibre/src/calibre/ebooks/rtf/input.py:271 +#: /home/kovid/work/calibre/src/calibre/ebooks/rtf/input.py:272 msgid "" "This RTF file has a feature calibre does not support. Convert it to HTML " "first and then try it.\n" @@ -3891,7 +3971,7 @@ msgstr "" #: /home/kovid/work/calibre/src/calibre/ebooks/snb/output.py:25 #: /home/kovid/work/calibre/src/calibre/ebooks/tcr/output.py:23 -#: /home/kovid/work/calibre/src/calibre/ebooks/txt/output.py:37 +#: /home/kovid/work/calibre/src/calibre/ebooks/txt/output.py:36 msgid "" "Specify the character encoding of the output document. The default is utf-8." msgstr "" @@ -3899,7 +3979,7 @@ msgstr "" "predefinição é utf-8." #: /home/kovid/work/calibre/src/calibre/ebooks/snb/output.py:29 -#: /home/kovid/work/calibre/src/calibre/ebooks/txt/output.py:44 +#: /home/kovid/work/calibre/src/calibre/ebooks/txt/output.py:43 msgid "" "The maximum number of characters per line. This splits on the first space " "before the specified value. If no space is found the line will be broken at " @@ -3993,12 +4073,14 @@ msgid "" "Normally extra space at the beginning of lines is retained. With this option " "they will be removed." msgstr "" +"Normalmente o espaço extra no início das linhas é mantido. Com esta opção " +"será removido." #: /home/kovid/work/calibre/src/calibre/ebooks/txt/input.py:59 msgid "Do not insert a Table of Contents into the output text." msgstr "Não inserir o índice no texto final" -#: /home/kovid/work/calibre/src/calibre/ebooks/txt/output.py:31 +#: /home/kovid/work/calibre/src/calibre/ebooks/txt/output.py:30 msgid "" "Type of newline to use. Options are %s. Default is 'system'. Use 'old_mac' " "for compatibility with Mac OS 9 and earlier. For Mac OS X use 'unix'. " @@ -4009,7 +4091,7 @@ msgstr "" "X use 'unix'. 'system' é a predefinição para o tipo de nova linha usada por " "este sistema operativo." -#: /home/kovid/work/calibre/src/calibre/ebooks/txt/output.py:51 +#: /home/kovid/work/calibre/src/calibre/ebooks/txt/output.py:50 msgid "" "Force splitting on the max-line-length value when no space is present. Also " "allows max-line-length to be below the minimum" @@ -4018,7 +4100,7 @@ msgstr "" "Também permite que o comprimento máximo da linha possa ser menor que o " "mínimo." -#: /home/kovid/work/calibre/src/calibre/ebooks/txt/output.py:56 +#: /home/kovid/work/calibre/src/calibre/ebooks/txt/output.py:55 msgid "" "Formatting used within the document.\n" "* plain: Produce plain text.\n" @@ -4026,88 +4108,101 @@ msgid "" "* textile: Produce Textile formatted text." msgstr "" -#: /home/kovid/work/calibre/src/calibre/ebooks/txt/output.py:62 +#: /home/kovid/work/calibre/src/calibre/ebooks/txt/output.py:61 msgid "" "Do not remove links within the document. This is only useful when paired " "with a txt-output-formatting option that is not none because links are " "always removed with plain text output." msgstr "" -#: /home/kovid/work/calibre/src/calibre/ebooks/txt/output.py:67 +#: /home/kovid/work/calibre/src/calibre/ebooks/txt/output.py:66 msgid "" "Do not remove image references within the document. This is only useful when " "paired with a txt-output-formatting option that is not none because links " "are always removed with plain text output." msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/__init__.py:71 +#: /home/kovid/work/calibre/src/calibre/ebooks/txt/output.py:71 +msgid "" +"Do not remove font color from output. This is only useful when txt-output-" +"formatting is set to textile. Textile is the only formatting that supports " +"setting font color. If this option is not specified font color will not be " +"set and default to the color displayed by the reader (generally this is " +"black)." +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/__init__.py:103 msgid "Send file to storage card instead of main memory by default" msgstr "" "Enviar o ficheiro para o cartão de memória em vez da memória principal por " "predefinição" -#: /home/kovid/work/calibre/src/calibre/gui2/__init__.py:73 +#: /home/kovid/work/calibre/src/calibre/gui2/__init__.py:105 msgid "Confirm before deleting" msgstr "Confirmar antes de apagar" -#: /home/kovid/work/calibre/src/calibre/gui2/__init__.py:75 +#: /home/kovid/work/calibre/src/calibre/gui2/__init__.py:107 msgid "Main window geometry" msgstr "Geometria da janela principal" -#: /home/kovid/work/calibre/src/calibre/gui2/__init__.py:77 +#: /home/kovid/work/calibre/src/calibre/gui2/__init__.py:109 msgid "Notify when a new version is available" msgstr "Notificar quando uma nova versão estiver disponível" -#: /home/kovid/work/calibre/src/calibre/gui2/__init__.py:79 +#: /home/kovid/work/calibre/src/calibre/gui2/__init__.py:111 msgid "Use Roman numerals for series number" msgstr "Usar números romanos para o número da série" -#: /home/kovid/work/calibre/src/calibre/gui2/__init__.py:81 +#: /home/kovid/work/calibre/src/calibre/gui2/__init__.py:113 msgid "Sort tags list by name, popularity, or rating" msgstr "Ordenar lista de etiquetas por nome, popularidade ou classificação" -#: /home/kovid/work/calibre/src/calibre/gui2/__init__.py:83 +#: /home/kovid/work/calibre/src/calibre/gui2/__init__.py:115 +msgid "Match tags by any or all." +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/__init__.py:117 msgid "Number of covers to show in the cover browsing mode" msgstr "Número de capas a mostrar no modo de navegação pelas capas" -#: /home/kovid/work/calibre/src/calibre/gui2/__init__.py:85 +#: /home/kovid/work/calibre/src/calibre/gui2/__init__.py:119 msgid "Defaults for conversion to LRF" msgstr "Predefinições para a conversão para o formato LRF" -#: /home/kovid/work/calibre/src/calibre/gui2/__init__.py:87 +#: /home/kovid/work/calibre/src/calibre/gui2/__init__.py:121 msgid "Options for the LRF ebook viewer" msgstr "Opções para o Visualizador de livros em formato LRF" -#: /home/kovid/work/calibre/src/calibre/gui2/__init__.py:90 +#: /home/kovid/work/calibre/src/calibre/gui2/__init__.py:124 msgid "Formats that are viewed using the internal viewer" msgstr "Formatos a usar pelo Visualizador interno" -#: /home/kovid/work/calibre/src/calibre/gui2/__init__.py:92 +#: /home/kovid/work/calibre/src/calibre/gui2/__init__.py:126 msgid "Columns to be displayed in the book list" msgstr "Colunas a serem apresentadas na lista de livros" -#: /home/kovid/work/calibre/src/calibre/gui2/__init__.py:93 +#: /home/kovid/work/calibre/src/calibre/gui2/__init__.py:127 msgid "Automatically launch content server on application startup" msgstr "" "Iniciar automaticamente o servidor de conteúdos no arranque da aplicação" -#: /home/kovid/work/calibre/src/calibre/gui2/__init__.py:94 +#: /home/kovid/work/calibre/src/calibre/gui2/__init__.py:128 msgid "Oldest news kept in database" msgstr "Notícias mais antigas guardadas na base de dados" -#: /home/kovid/work/calibre/src/calibre/gui2/__init__.py:95 +#: /home/kovid/work/calibre/src/calibre/gui2/__init__.py:129 msgid "Show system tray icon" msgstr "Mostrar o ícone na área de notificação" -#: /home/kovid/work/calibre/src/calibre/gui2/__init__.py:97 +#: /home/kovid/work/calibre/src/calibre/gui2/__init__.py:131 msgid "Upload downloaded news to device" msgstr "Carregar as notícias descarregadas para o aparelho" -#: /home/kovid/work/calibre/src/calibre/gui2/__init__.py:99 +#: /home/kovid/work/calibre/src/calibre/gui2/__init__.py:133 msgid "Delete books from library after uploading to device" msgstr "Apagar os livros da biblioteca após carregamento para o aparelho" -#: /home/kovid/work/calibre/src/calibre/gui2/__init__.py:101 +#: /home/kovid/work/calibre/src/calibre/gui2/__init__.py:135 msgid "" "Show the cover flow in a separate window instead of in the main calibre " "window" @@ -4115,91 +4210,143 @@ msgstr "" "Mostrar o fluxo de capas numa janela separada em vez de na janela principal " "do calibre" -#: /home/kovid/work/calibre/src/calibre/gui2/__init__.py:103 +#: /home/kovid/work/calibre/src/calibre/gui2/__init__.py:137 msgid "Disable notifications from the system tray icon" msgstr "Desactivar as notificações a partir do ícone da área de notificação" -#: /home/kovid/work/calibre/src/calibre/gui2/__init__.py:105 +#: /home/kovid/work/calibre/src/calibre/gui2/__init__.py:139 msgid "Default action to perform when send to device button is clicked" msgstr "" "Acção predefinida a executar quando se clica no botão \"Enviar para o " "aparelho\"" -#: /home/kovid/work/calibre/src/calibre/gui2/__init__.py:110 +#: /home/kovid/work/calibre/src/calibre/gui2/__init__.py:144 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 "" +"Começar a pesquisar à medida que escreve. Se isto estiver desactivado então " +"a pesquisa apenas ocorrerá quando a tecla Enter ou Return for pressionada." -#: /home/kovid/work/calibre/src/calibre/gui2/__init__.py:113 +#: /home/kovid/work/calibre/src/calibre/gui2/__init__.py:147 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:131 -msgid "Maximum number of waiting worker processes" -msgstr "Número máximo de processos de trabalho em espera." +#: /home/kovid/work/calibre/src/calibre/gui2/__init__.py:165 +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:133 +#: /home/kovid/work/calibre/src/calibre/gui2/__init__.py:169 msgid "Download social metadata (tags/rating/etc.)" msgstr "Transferir meta-dados sociais (etiquetas/classificações/etc)" -#: /home/kovid/work/calibre/src/calibre/gui2/__init__.py:135 +#: /home/kovid/work/calibre/src/calibre/gui2/__init__.py:171 msgid "Overwrite author and title with new metadata" msgstr "Substituir o autor e o título nos novos metadados" -#: /home/kovid/work/calibre/src/calibre/gui2/__init__.py:137 -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/fetch_metadata_ui.py:101 +#: /home/kovid/work/calibre/src/calibre/gui2/__init__.py:173 msgid "Automatically download the cover, if available" msgstr "Se possível, descarregar automaticamente a capa" -#: /home/kovid/work/calibre/src/calibre/gui2/__init__.py:139 +#: /home/kovid/work/calibre/src/calibre/gui2/__init__.py:175 msgid "Limit max simultaneous jobs to number of CPUs" msgstr "Limitar o número máximo de tarefa simultâneas ao número de CPUs" -#: /home/kovid/work/calibre/src/calibre/gui2/__init__.py:141 +#: /home/kovid/work/calibre/src/calibre/gui2/__init__.py:177 msgid "The layout of the user interface" msgstr "Esquema da interface de utilizador" -#: /home/kovid/work/calibre/src/calibre/gui2/__init__.py:143 +#: /home/kovid/work/calibre/src/calibre/gui2/__init__.py:179 msgid "Show the average rating per item indication in the tag browser" msgstr "" "Exibir classificação média por cada indicação de item no navegador de " "etiquetas" -#: /home/kovid/work/calibre/src/calibre/gui2/__init__.py:145 +#: /home/kovid/work/calibre/src/calibre/gui2/__init__.py:181 msgid "Disable UI animations" msgstr "Desactivar animações da interface" -#: /home/kovid/work/calibre/src/calibre/gui2/__init__.py:150 +#: /home/kovid/work/calibre/src/calibre/gui2/__init__.py:186 msgid "tag browser categories not to display" msgstr "Marque as categorias do navegador que não serão mostradas" -#: /home/kovid/work/calibre/src/calibre/gui2/__init__.py:419 +#: /home/kovid/work/calibre/src/calibre/gui2/__init__.py:461 msgid "Choose Files" msgstr "Escolher ficheiros" #: /home/kovid/work/calibre/src/calibre/gui2/actions/add.py:29 +#: /home/kovid/work/calibre/src/calibre/gui2/metadata/basic_widgets.py:599 +msgid "Books" +msgstr "Livros" + +#: /home/kovid/work/calibre/src/calibre/gui2/actions/add.py:30 +msgid "EPUB Books" +msgstr "Livros em formato EPUB" + +#: /home/kovid/work/calibre/src/calibre/gui2/actions/add.py:31 +msgid "LRF Books" +msgstr "Livros em formato LRF" + +#: /home/kovid/work/calibre/src/calibre/gui2/actions/add.py:32 +msgid "HTML Books" +msgstr "Livros em formato HTML" + +#: /home/kovid/work/calibre/src/calibre/gui2/actions/add.py:33 +msgid "LIT Books" +msgstr "Livros em formato LIT" + +#: /home/kovid/work/calibre/src/calibre/gui2/actions/add.py:34 +msgid "MOBI Books" +msgstr "Livros em formato MOBI" + +#: /home/kovid/work/calibre/src/calibre/gui2/actions/add.py:35 +msgid "Topaz books" +msgstr "Livros em formato Topaz" + +#: /home/kovid/work/calibre/src/calibre/gui2/actions/add.py:36 +msgid "Text books" +msgstr "Livros em formato texto" + +#: /home/kovid/work/calibre/src/calibre/gui2/actions/add.py:37 +msgid "PDF Books" +msgstr "Livros em formato PDF" + +#: /home/kovid/work/calibre/src/calibre/gui2/actions/add.py:38 +msgid "SNB Books" +msgstr "Livros em formato SNB" + +#: /home/kovid/work/calibre/src/calibre/gui2/actions/add.py:39 +msgid "Comics" +msgstr "Banda desenhada" + +#: /home/kovid/work/calibre/src/calibre/gui2/actions/add.py:40 +msgid "Archives" +msgstr "Arquivos" + +#: /home/kovid/work/calibre/src/calibre/gui2/actions/add.py:47 msgid "Add books" msgstr "Adicionar livros" -#: /home/kovid/work/calibre/src/calibre/gui2/actions/add.py:30 +#: /home/kovid/work/calibre/src/calibre/gui2/actions/add.py:48 msgid "Add books to the calibre library/device from files on your computer" msgstr "" "Adicionar livros ao dispositivo/à biblioteca do calibre a partir de " "ficheiros no computador" -#: /home/kovid/work/calibre/src/calibre/gui2/actions/add.py:31 +#: /home/kovid/work/calibre/src/calibre/gui2/actions/add.py:49 msgid "A" msgstr "A" -#: /home/kovid/work/calibre/src/calibre/gui2/actions/add.py:37 +#: /home/kovid/work/calibre/src/calibre/gui2/actions/add.py:55 msgid "Add books from a single directory" msgstr "Adicionar os livros a partir de uma pasta" -#: /home/kovid/work/calibre/src/calibre/gui2/actions/add.py:39 +#: /home/kovid/work/calibre/src/calibre/gui2/actions/add.py:57 msgid "" "Add books from directories, including sub-directories (One book per " "directory, assumes every ebook file is the same book in a different format)" @@ -4208,7 +4355,7 @@ msgstr "" "pasta, assume que todos os ficheiros de livros na mesma pasta estão em " "formatos diferentes)" -#: /home/kovid/work/calibre/src/calibre/gui2/actions/add.py:43 +#: /home/kovid/work/calibre/src/calibre/gui2/actions/add.py:61 msgid "" "Add books from directories, including sub directories (Multiple books per " "directory, assumes every ebook file is a different book)" @@ -4217,126 +4364,105 @@ msgstr "" "livros por pasta, assume que todos os ficheiros de livros são livros " "diferentes)" -#: /home/kovid/work/calibre/src/calibre/gui2/actions/add.py:47 +#: /home/kovid/work/calibre/src/calibre/gui2/actions/add.py:65 msgid "Add Empty book. (Book entry with no formats)" msgstr "Adicionar um livro vazio (Uma entrada de livro sem nenhum formato)" -#: /home/kovid/work/calibre/src/calibre/gui2/actions/add.py:48 +#: /home/kovid/work/calibre/src/calibre/gui2/actions/add.py:66 msgid "Shift+Ctrl+E" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/actions/add.py:49 +#: /home/kovid/work/calibre/src/calibre/gui2/actions/add.py:67 msgid "Add from ISBN" msgstr "Adicionar a partir do ISBN" -#: /home/kovid/work/calibre/src/calibre/gui2/actions/add.py:113 +#: /home/kovid/work/calibre/src/calibre/gui2/actions/add.py:69 +msgid "Add files to selected book records" +msgstr "Adicionar ficheiros aos registos de livros seleccionados" + +#: /home/kovid/work/calibre/src/calibre/gui2/actions/add.py:70 +msgid "Shift+A" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/actions/add.py:90 +msgid "Are you sure" +msgstr "Tem a certeza" + +#: /home/kovid/work/calibre/src/calibre/gui2/actions/add.py:91 +msgid "" +"Are you sure you want to add the same files to all %d books? If the " +"formatalready exists for a book, it will be replaced." +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/actions/add.py:97 +msgid "Select book files" +msgstr "Seleccionar ficheiros de livros" + +#: /home/kovid/work/calibre/src/calibre/gui2/actions/add.py:168 msgid "Adding" -msgstr "" +msgstr "Adicionando" -#: /home/kovid/work/calibre/src/calibre/gui2/actions/add.py:114 +#: /home/kovid/work/calibre/src/calibre/gui2/actions/add.py:169 msgid "Creating book records from ISBNs" -msgstr "" +msgstr "Criar regsistos de livros a partir de ISBNs" -#: /home/kovid/work/calibre/src/calibre/gui2/actions/add.py:194 -#: /home/kovid/work/calibre/src/calibre/gui2/actions/add.py:256 +#: /home/kovid/work/calibre/src/calibre/gui2/actions/add.py:268 +#: /home/kovid/work/calibre/src/calibre/gui2/actions/add.py:317 msgid "Uploading books to device." msgstr "A carregar os livros para o aparelho." -#: /home/kovid/work/calibre/src/calibre/gui2/actions/add.py:211 -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single.py:308 -#: /home/kovid/work/calibre/src/calibre/gui2/metadata/basic_widgets.py:529 -msgid "Books" -msgstr "Livros" - -#: /home/kovid/work/calibre/src/calibre/gui2/actions/add.py:212 -msgid "EPUB Books" -msgstr "Livros em formato EPUB" - -#: /home/kovid/work/calibre/src/calibre/gui2/actions/add.py:213 -msgid "LRF Books" -msgstr "Livros em formato LRF" - -#: /home/kovid/work/calibre/src/calibre/gui2/actions/add.py:214 -msgid "HTML Books" -msgstr "Livros em formato HTML" - -#: /home/kovid/work/calibre/src/calibre/gui2/actions/add.py:215 -msgid "LIT Books" -msgstr "Livros em formato LIT" - -#: /home/kovid/work/calibre/src/calibre/gui2/actions/add.py:216 -msgid "MOBI Books" -msgstr "Livros em formato MOBI" - -#: /home/kovid/work/calibre/src/calibre/gui2/actions/add.py:217 -msgid "Topaz books" -msgstr "Livros em formato Topaz" - -#: /home/kovid/work/calibre/src/calibre/gui2/actions/add.py:218 -msgid "Text books" -msgstr "Livros em formato texto" - -#: /home/kovid/work/calibre/src/calibre/gui2/actions/add.py:219 -msgid "PDF Books" -msgstr "Livros em formato PDF" - -#: /home/kovid/work/calibre/src/calibre/gui2/actions/add.py:220 -msgid "SNB Books" -msgstr "Livros em formato SNB" - -#: /home/kovid/work/calibre/src/calibre/gui2/actions/add.py:221 -msgid "Comics" -msgstr "Banda desenhada" - -#: /home/kovid/work/calibre/src/calibre/gui2/actions/add.py:222 -msgid "Archives" -msgstr "Arquivos" - -#: /home/kovid/work/calibre/src/calibre/gui2/actions/add.py:227 +#: /home/kovid/work/calibre/src/calibre/gui2/actions/add.py:288 msgid "Supported books" msgstr "Livros compatíveis" -#: /home/kovid/work/calibre/src/calibre/gui2/actions/add.py:266 +#: /home/kovid/work/calibre/src/calibre/gui2/actions/add.py:291 +msgid "Select books" +msgstr "Seleccionar livros" + +#: /home/kovid/work/calibre/src/calibre/gui2/actions/add.py:328 msgid "Merged some books" msgstr "Alguns livros foram combinados" -#: /home/kovid/work/calibre/src/calibre/gui2/actions/add.py:267 +#: /home/kovid/work/calibre/src/calibre/gui2/actions/add.py:329 msgid "" "The following duplicate books were found and incoming book formats were " "processed and merged into your Calibre database according to your automerge " "settings:" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/actions/add.py:276 +#: /home/kovid/work/calibre/src/calibre/gui2/actions/add.py:349 msgid "Failed to read metadata" msgstr "Falha ao ler os metadados" -#: /home/kovid/work/calibre/src/calibre/gui2/actions/add.py:277 +#: /home/kovid/work/calibre/src/calibre/gui2/actions/add.py:350 msgid "Failed to read metadata from the following" msgstr "Falha ao ler os metadados do(s) seguinte(s)" -#: /home/kovid/work/calibre/src/calibre/gui2/actions/add.py:298 -#: /home/kovid/work/calibre/src/calibre/gui2/actions/add.py:303 -#: /home/kovid/work/calibre/src/calibre/gui2/actions/add.py:322 +#: /home/kovid/work/calibre/src/calibre/gui2/actions/add.py:371 +#: /home/kovid/work/calibre/src/calibre/gui2/actions/add.py:376 +#: /home/kovid/work/calibre/src/calibre/gui2/actions/add.py:395 msgid "Add to library" msgstr "Adicionar à biblioteca" -#: /home/kovid/work/calibre/src/calibre/gui2/actions/add.py:303 -#: /home/kovid/work/calibre/src/calibre/gui2/actions/delete.py:116 +#: /home/kovid/work/calibre/src/calibre/gui2/actions/add.py:376 +#: /home/kovid/work/calibre/src/calibre/gui2/actions/delete.py:127 +#: /home/kovid/work/calibre/src/calibre/gui2/actions/store.py:76 +#: /home/kovid/work/calibre/src/calibre/gui2/actions/store.py:95 +#: /home/kovid/work/calibre/src/calibre/gui2/actions/store.py:104 #: /home/kovid/work/calibre/src/calibre/gui2/actions/tweak_epub.py:28 -#: /home/kovid/work/calibre/src/calibre/gui2/actions/view.py:85 -#: /home/kovid/work/calibre/src/calibre/gui2/actions/view.py:131 +#: /home/kovid/work/calibre/src/calibre/gui2/actions/view.py:139 +#: /home/kovid/work/calibre/src/calibre/gui2/actions/view.py:185 msgid "No book selected" msgstr "Nenhum livro seleccionado" -#: /home/kovid/work/calibre/src/calibre/gui2/actions/add.py:316 +#: /home/kovid/work/calibre/src/calibre/gui2/actions/add.py:389 msgid "" "The following books are virtual and cannot be added to the calibre library:" msgstr "" "Os seguintes livros são virtuais e não podem ser adicionados à biblioteca do " "calibre:" -#: /home/kovid/work/calibre/src/calibre/gui2/actions/add.py:322 +#: /home/kovid/work/calibre/src/calibre/gui2/actions/add.py:395 msgid "No book files found" msgstr "Não foram encontrados livros" @@ -4349,90 +4475,98 @@ msgid "Add books to your calibre library from the connected device" msgstr "Adicionar livros à sua biblioteca do calibre do dispositivo ligado" #: /home/kovid/work/calibre/src/calibre/gui2/actions/annotate.py:20 -#: /home/kovid/work/calibre/src/calibre/gui2/device.py:544 +#: /home/kovid/work/calibre/src/calibre/gui2/device.py:543 msgid "Fetch annotations (experimental)" msgstr "Extrair anotações (experimental)" #: /home/kovid/work/calibre/src/calibre/gui2/actions/annotate.py:56 -#: /home/kovid/work/calibre/src/calibre/gui2/actions/annotate.py:240 +msgid "Not supported" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/actions/annotate.py:57 +msgid "Fetching annotations is not supported for this device" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/actions/annotate.py:61 +#: /home/kovid/work/calibre/src/calibre/gui2/actions/annotate.py:245 msgid "Use library only" msgstr "Utilizar apenas a biblioteca" -#: /home/kovid/work/calibre/src/calibre/gui2/actions/annotate.py:57 -#: /home/kovid/work/calibre/src/calibre/gui2/actions/annotate.py:241 +#: /home/kovid/work/calibre/src/calibre/gui2/actions/annotate.py:62 +#: /home/kovid/work/calibre/src/calibre/gui2/actions/annotate.py:246 msgid "User annotations generated from main library only" msgstr "" "Anotações do utilizador criadas apenas a partir da biblioteca principal" -#: /home/kovid/work/calibre/src/calibre/gui2/actions/annotate.py:64 -#: /home/kovid/work/calibre/src/calibre/gui2/actions/catalog.py:30 +#: /home/kovid/work/calibre/src/calibre/gui2/actions/annotate.py:69 +#: /home/kovid/work/calibre/src/calibre/gui2/actions/catalog.py:33 #: /home/kovid/work/calibre/src/calibre/gui2/actions/convert.py:87 #: /home/kovid/work/calibre/src/calibre/gui2/actions/copy_to_library.py:127 -#: /home/kovid/work/calibre/src/calibre/gui2/actions/edit_metadata.py:80 -#: /home/kovid/work/calibre/src/calibre/gui2/actions/edit_metadata.py:127 -#: /home/kovid/work/calibre/src/calibre/gui2/actions/edit_metadata.py:188 -#: /home/kovid/work/calibre/src/calibre/gui2/actions/edit_metadata.py:225 +#: /home/kovid/work/calibre/src/calibre/gui2/actions/edit_metadata.py:72 +#: /home/kovid/work/calibre/src/calibre/gui2/actions/edit_metadata.py:156 +#: /home/kovid/work/calibre/src/calibre/gui2/actions/edit_metadata.py:220 +#: /home/kovid/work/calibre/src/calibre/gui2/actions/edit_metadata.py:257 #: /home/kovid/work/calibre/src/calibre/gui2/actions/save_to_disk.py:92 msgid "No books selected" msgstr "Nenhuns livros seleccionados" -#: /home/kovid/work/calibre/src/calibre/gui2/actions/annotate.py:65 +#: /home/kovid/work/calibre/src/calibre/gui2/actions/annotate.py:70 msgid "No books selected to fetch annotations from" msgstr "Não foram seleccionados livros para extrair anotações" -#: /home/kovid/work/calibre/src/calibre/gui2/actions/annotate.py:90 +#: /home/kovid/work/calibre/src/calibre/gui2/actions/annotate.py:95 msgid "Merging user annotations into database" msgstr "Intercalação de anotações de utilizador na base de dados" -#: /home/kovid/work/calibre/src/calibre/gui2/actions/annotate.py:118 +#: /home/kovid/work/calibre/src/calibre/gui2/actions/annotate.py:123 msgid "%s
      Last Page Read: %d (%d%%)" msgstr "%s
      Última página lida: %d (%d%%)" -#: /home/kovid/work/calibre/src/calibre/gui2/actions/annotate.py:124 +#: /home/kovid/work/calibre/src/calibre/gui2/actions/annotate.py:129 msgid "%s
      Last Page Read: Location %d (%d%%)" msgstr "%s
      Ultima Página Lida: Localização %d (%d%%)" -#: /home/kovid/work/calibre/src/calibre/gui2/actions/annotate.py:143 +#: /home/kovid/work/calibre/src/calibre/gui2/actions/annotate.py:148 msgid "Location %d • %s
      %s
      " msgstr "Localização %d • %s
      %s
      " -#: /home/kovid/work/calibre/src/calibre/gui2/actions/annotate.py:152 +#: /home/kovid/work/calibre/src/calibre/gui2/actions/annotate.py:157 msgid "Page %d • %s
      " msgstr "Página %d • %s
      " -#: /home/kovid/work/calibre/src/calibre/gui2/actions/annotate.py:157 +#: /home/kovid/work/calibre/src/calibre/gui2/actions/annotate.py:162 msgid "Location %d • %s
      " msgstr "Localização %d • %s
      " #: /home/kovid/work/calibre/src/calibre/gui2/actions/catalog.py:20 #: /home/kovid/work/calibre/src/calibre/gui2/actions/convert.py:34 msgid "Create a catalog of the books in your calibre library" -msgstr "" +msgstr "Criar um catálogo de livros na biblioteca do calibre" -#: /home/kovid/work/calibre/src/calibre/gui2/actions/catalog.py:31 +#: /home/kovid/work/calibre/src/calibre/gui2/actions/catalog.py:34 msgid "No books selected for catalog generation" -msgstr "" +msgstr "Não foram seleccionados livros para gerar um catálogo" -#: /home/kovid/work/calibre/src/calibre/gui2/actions/catalog.py:54 +#: /home/kovid/work/calibre/src/calibre/gui2/actions/catalog.py:57 msgid "Generating %s catalog..." msgstr "Gerando o catálogo %s ..." -#: /home/kovid/work/calibre/src/calibre/gui2/actions/catalog.py:78 +#: /home/kovid/work/calibre/src/calibre/gui2/actions/catalog.py:81 msgid "Catalog generated." msgstr "Catálogo gerado." -#: /home/kovid/work/calibre/src/calibre/gui2/actions/catalog.py:81 +#: /home/kovid/work/calibre/src/calibre/gui2/actions/catalog.py:84 msgid "Export Catalog Directory" msgstr "Exportar a directoria do catálogo" -#: /home/kovid/work/calibre/src/calibre/gui2/actions/catalog.py:82 +#: /home/kovid/work/calibre/src/calibre/gui2/actions/catalog.py:85 msgid "Select destination for %s.%s" msgstr "Seleccione o destino para %s.%s" #: /home/kovid/work/calibre/src/calibre/gui2/actions/choose_library.py:81 -#: /home/kovid/work/calibre/src/calibre/gui2/preferences/toolbar.py:54 -#: /home/kovid/work/calibre/src/calibre/library/server/browse.py:167 -#: /home/kovid/work/calibre/src/calibre/library/server/opds.py:126 +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/toolbar.py:57 +#: /home/kovid/work/calibre/src/calibre/library/server/browse.py:170 +#: /home/kovid/work/calibre/src/calibre/library/server/opds.py:125 msgid "%d books" msgstr "%d livros" @@ -4440,75 +4574,76 @@ msgstr "%d livros" msgid "Choose calibre library to work with" msgstr "Escolha a biblioteca calibre para utilizar" -#: /home/kovid/work/calibre/src/calibre/gui2/actions/choose_library.py:91 +#: /home/kovid/work/calibre/src/calibre/gui2/actions/choose_library.py:94 msgid "Switch/create library..." msgstr "Mudar/crear biblioteca..." -#: /home/kovid/work/calibre/src/calibre/gui2/actions/choose_library.py:102 -#: /home/kovid/work/calibre/src/calibre/gui2/layout.py:77 +#: /home/kovid/work/calibre/src/calibre/gui2/actions/choose_library.py:105 +#: /home/kovid/work/calibre/src/calibre/gui2/layout.py:87 msgid "Quick switch" msgstr "Mudar rapidamente" -#: /home/kovid/work/calibre/src/calibre/gui2/actions/choose_library.py:104 -#: /home/kovid/work/calibre/src/calibre/gui2/layout.py:78 +#: /home/kovid/work/calibre/src/calibre/gui2/actions/choose_library.py:107 +#: /home/kovid/work/calibre/src/calibre/gui2/layout.py:88 msgid "Rename library" msgstr "Renomear a biblioteca" -#: /home/kovid/work/calibre/src/calibre/gui2/actions/choose_library.py:106 -#: /home/kovid/work/calibre/src/calibre/gui2/layout.py:79 +#: /home/kovid/work/calibre/src/calibre/gui2/actions/choose_library.py:109 +#: /home/kovid/work/calibre/src/calibre/gui2/layout.py:89 msgid "Delete library" msgstr "Apagar a biblioteca" -#: /home/kovid/work/calibre/src/calibre/gui2/actions/choose_library.py:109 +#: /home/kovid/work/calibre/src/calibre/gui2/actions/choose_library.py:112 msgid "Pick a random book" msgstr "Escolher um livro aleatoriamente" -#: /home/kovid/work/calibre/src/calibre/gui2/actions/choose_library.py:128 +#: /home/kovid/work/calibre/src/calibre/gui2/actions/choose_library.py:132 msgid "Library Maintenance" msgstr "Manutenção da biblioteca" -#: /home/kovid/work/calibre/src/calibre/gui2/actions/choose_library.py:129 +#: /home/kovid/work/calibre/src/calibre/gui2/actions/choose_library.py:133 msgid "Library metadata backup status" msgstr "Estado dos backups dos metadados da biblioteca" -#: /home/kovid/work/calibre/src/calibre/gui2/actions/choose_library.py:133 +#: /home/kovid/work/calibre/src/calibre/gui2/actions/choose_library.py:137 msgid "Start backing up metadata of all books" msgstr "Começar a salvaguarda dos meta-dados de todos os livros" -#: /home/kovid/work/calibre/src/calibre/gui2/actions/choose_library.py:137 +#: /home/kovid/work/calibre/src/calibre/gui2/actions/choose_library.py:141 msgid "Check library" msgstr "Verificar biblioteca" -#: /home/kovid/work/calibre/src/calibre/gui2/actions/choose_library.py:141 +#: /home/kovid/work/calibre/src/calibre/gui2/actions/choose_library.py:145 msgid "Restore database" -msgstr "" +msgstr "Repor base de dados" -#: /home/kovid/work/calibre/src/calibre/gui2/actions/choose_library.py:216 +#: /home/kovid/work/calibre/src/calibre/gui2/actions/choose_library.py:220 msgid "Rename" msgstr "Renomear" -#: /home/kovid/work/calibre/src/calibre/gui2/actions/choose_library.py:217 +#: /home/kovid/work/calibre/src/calibre/gui2/actions/choose_library.py:221 msgid "Choose a new name for the library %s. " msgstr "Escolher um nome novo para a biblioteca %s. " -#: /home/kovid/work/calibre/src/calibre/gui2/actions/choose_library.py:218 +#: /home/kovid/work/calibre/src/calibre/gui2/actions/choose_library.py:222 msgid "Note that the actual library folder will be renamed." msgstr "Repare que o nome da pasta da biblioteca actual será renomeada." -#: /home/kovid/work/calibre/src/calibre/gui2/actions/choose_library.py:225 -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/user_profiles.py:191 +#: /home/kovid/work/calibre/src/calibre/gui2/actions/choose_library.py:229 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/user_profiles.py:199 +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/plugins.py:289 msgid "Already exists" msgstr "Já existe" -#: /home/kovid/work/calibre/src/calibre/gui2/actions/choose_library.py:226 +#: /home/kovid/work/calibre/src/calibre/gui2/actions/choose_library.py:230 msgid "The folder %s already exists. Delete it first." msgstr "A pasta %s já existe. Apague-a primeiro" -#: /home/kovid/work/calibre/src/calibre/gui2/actions/choose_library.py:232 +#: /home/kovid/work/calibre/src/calibre/gui2/actions/choose_library.py:236 msgid "Rename failed" msgstr "A renomeação falhou" -#: /home/kovid/work/calibre/src/calibre/gui2/actions/choose_library.py:233 +#: /home/kovid/work/calibre/src/calibre/gui2/actions/choose_library.py:237 msgid "" "Failed to rename the library at %s. The most common cause for this is if one " "of the files in the library is open in another program." @@ -4516,109 +4651,116 @@ msgstr "" "Não foi possível renomear a biblioteca %s. A causa mais comum para esta " "situação é um dos ficheiros da biblioteca estar aberto por outro programa." -#: /home/kovid/work/calibre/src/calibre/gui2/actions/choose_library.py:244 +#: /home/kovid/work/calibre/src/calibre/gui2/actions/choose_library.py:248 +#: /home/kovid/work/calibre/src/calibre/gui2/actions/delete.py:30 #: /home/kovid/work/calibre/src/calibre/gui2/dialogs/confirm_delete_ui.py:53 #: /home/kovid/work/calibre/src/calibre/gui2/dialogs/restore_library.py:78 -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/user_profiles.py:360 -#: /home/kovid/work/calibre/src/calibre/gui2/jobs.py:424 -#: /home/kovid/work/calibre/src/calibre/gui2/jobs.py:430 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/user_profiles.py:368 +#: /home/kovid/work/calibre/src/calibre/gui2/jobs.py:457 +#: /home/kovid/work/calibre/src/calibre/gui2/jobs.py:463 #: /home/kovid/work/calibre/src/calibre/gui2/preferences/columns.py:102 -#: /home/kovid/work/calibre/src/calibre/gui2/preferences/plugins.py:273 +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/plugins.py:279 +#: /home/kovid/work/calibre/src/calibre/gui2/wizard/send_email.py:223 msgid "Are you sure?" msgstr "Tem a certeza?" -#: /home/kovid/work/calibre/src/calibre/gui2/actions/choose_library.py:245 -msgid "All files from %s will be permanently deleted. Are you sure?" +#: /home/kovid/work/calibre/src/calibre/gui2/actions/choose_library.py:249 +msgid "" +"All files (not just ebooks) from " +"

      %s

      will be permanently deleted. Are you sure?" msgstr "" -"Todos os ficheiros de %s serão removidos permanentemente. Tem a " -"certeza?" -#: /home/kovid/work/calibre/src/calibre/gui2/actions/choose_library.py:265 +#: /home/kovid/work/calibre/src/calibre/gui2/actions/choose_library.py:270 msgid "none" msgstr "nenhum" -#: /home/kovid/work/calibre/src/calibre/gui2/actions/choose_library.py:266 +#: /home/kovid/work/calibre/src/calibre/gui2/actions/choose_library.py:271 msgid "Backup status" msgstr "Estatísticas da cópia de segurança" -#: /home/kovid/work/calibre/src/calibre/gui2/actions/choose_library.py:267 +#: /home/kovid/work/calibre/src/calibre/gui2/actions/choose_library.py:272 msgid "Book metadata files remaining to be written: %s" -msgstr "" +msgstr "Ficheiros de meta-dados de livros que faltam para serem criados: %s" -#: /home/kovid/work/calibre/src/calibre/gui2/actions/choose_library.py:273 +#: /home/kovid/work/calibre/src/calibre/gui2/actions/choose_library.py:278 msgid "Backup metadata" -msgstr "" +msgstr "Fazer backup de meta-dados" -#: /home/kovid/work/calibre/src/calibre/gui2/actions/choose_library.py:274 +#: /home/kovid/work/calibre/src/calibre/gui2/actions/choose_library.py:279 msgid "" "Metadata will be backed up while calibre is running, at the rate of " "approximately 1 book every three seconds." msgstr "" +"Os meta-dados serão guardados enquanto o calibre está a executar, ao ritmo " +"de aproximadamente 1 livro a cada três segundos." -#: /home/kovid/work/calibre/src/calibre/gui2/actions/choose_library.py:306 +#: /home/kovid/work/calibre/src/calibre/gui2/actions/choose_library.py:311 #: /home/kovid/work/calibre/src/calibre/gui2/dialogs/restore_library.py:106 #: /home/kovid/work/calibre/src/calibre/gui2/dialogs/restore_library.py:111 -#: /home/kovid/work/calibre/src/calibre/gui2/preferences/plugins.py:284 -#: /home/kovid/work/calibre/src/calibre/gui2/preferences/plugins.py:338 +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/plugins.py:295 +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/plugins.py:349 msgid "Success" -msgstr "" +msgstr "Sucesso" -#: /home/kovid/work/calibre/src/calibre/gui2/actions/choose_library.py:307 +#: /home/kovid/work/calibre/src/calibre/gui2/actions/choose_library.py:312 msgid "" "Found no errors in your calibre library database. Do you want calibre to " "check if the files in your library match the information in the database?" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/actions/choose_library.py:312 +#: /home/kovid/work/calibre/src/calibre/gui2/actions/choose_library.py:317 #: /home/kovid/work/calibre/src/calibre/gui2/actions/copy_to_library.py:150 -#: /home/kovid/work/calibre/src/calibre/gui2/device.py:672 -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk.py:911 +#: /home/kovid/work/calibre/src/calibre/gui2/device.py:692 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk.py:974 #: /home/kovid/work/calibre/src/calibre/gui2/dialogs/restore_library.py:101 -#: /home/kovid/work/calibre/src/calibre/gui2/metadata/bulk_download.py:186 -#: /home/kovid/work/calibre/src/calibre/gui2/preferences/tweaks.py:276 -#: /home/kovid/work/calibre/src/calibre/gui2/preferences/tweaks.py:316 +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/tweaks.py:277 +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/tweaks.py:317 msgid "Failed" -msgstr "" - -#: /home/kovid/work/calibre/src/calibre/gui2/actions/choose_library.py:313 -msgid "Database integrity check failed, click Show details for details." -msgstr "" +msgstr "Sem sucesso" #: /home/kovid/work/calibre/src/calibre/gui2/actions/choose_library.py:318 -msgid "No problems found" +msgid "Database integrity check failed, click Show details for details." msgstr "" +"Verificação de integridade da base de dados falhou, clique para Mostrar " +"Detalhes para mais detalhes" -#: /home/kovid/work/calibre/src/calibre/gui2/actions/choose_library.py:319 +#: /home/kovid/work/calibre/src/calibre/gui2/actions/choose_library.py:323 +msgid "No problems found" +msgstr "Não foi encontrado qualquer problema" + +#: /home/kovid/work/calibre/src/calibre/gui2/actions/choose_library.py:324 msgid "The files in your library match the information in the database." msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/actions/choose_library.py:328 +#: /home/kovid/work/calibre/src/calibre/gui2/actions/choose_library.py:333 msgid "No library found" -msgstr "" +msgstr "Não foi encontrada uma biblioteca" -#: /home/kovid/work/calibre/src/calibre/gui2/actions/choose_library.py:329 +#: /home/kovid/work/calibre/src/calibre/gui2/actions/choose_library.py:334 msgid "" "No existing calibre library was found at %s. It will be removed from the " "list of known libraries." msgstr "" +"Não foi encontrada a biblioteca do calibre em %s. Irá ser removida da lista " +"de bibliotecas conhecidas." -#: /home/kovid/work/calibre/src/calibre/gui2/actions/choose_library.py:394 -#: /home/kovid/work/calibre/src/calibre/gui2/actions/choose_library.py:399 +#: /home/kovid/work/calibre/src/calibre/gui2/actions/choose_library.py:400 +#: /home/kovid/work/calibre/src/calibre/gui2/actions/choose_library.py:405 #: /home/kovid/work/calibre/src/calibre/gui2/actions/copy_to_library.py:167 #: /home/kovid/work/calibre/src/calibre/gui2/actions/save_to_disk.py:101 -#: /home/kovid/work/calibre/src/calibre/gui2/library/views.py:782 +#: /home/kovid/work/calibre/src/calibre/gui2/library/views.py:854 msgid "Not allowed" msgstr "Não permitido" -#: /home/kovid/work/calibre/src/calibre/gui2/actions/choose_library.py:395 +#: /home/kovid/work/calibre/src/calibre/gui2/actions/choose_library.py:401 msgid "" "You cannot change libraries while using the environment variable " "CALIBRE_OVERRIDE_DATABASE_PATH." msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/actions/choose_library.py:400 +#: /home/kovid/work/calibre/src/calibre/gui2/actions/choose_library.py:406 msgid "You cannot change libraries while jobs are running." -msgstr "" +msgstr "Não pode modificar bibliotecas enquanto existem processos a correr." #: /home/kovid/work/calibre/src/calibre/gui2/actions/convert.py:22 msgid "C" @@ -4626,7 +4768,7 @@ msgstr "C" #: /home/kovid/work/calibre/src/calibre/gui2/actions/convert.py:22 msgid "Convert books" -msgstr "" +msgstr "Converter livros" #: /home/kovid/work/calibre/src/calibre/gui2/actions/convert.py:28 msgid "Convert individually" @@ -4637,7 +4779,7 @@ msgid "Bulk convert" msgstr "Converter a granel" #: /home/kovid/work/calibre/src/calibre/gui2/actions/convert.py:86 -#: /home/kovid/work/calibre/src/calibre/gui2/ui.py:505 +#: /home/kovid/work/calibre/src/calibre/gui2/ui.py:560 msgid "Cannot convert" msgstr "É impossível converter" @@ -4645,232 +4787,250 @@ msgstr "É impossível converter" msgid "Starting conversion of %d book(s)" msgstr "Iniciar a conversão de %d livro(s)" -#: /home/kovid/work/calibre/src/calibre/gui2/actions/convert.py:170 +#: /home/kovid/work/calibre/src/calibre/gui2/actions/convert.py:171 msgid "Empty output file, probably the conversion process crashed" msgstr "" +"Ficheiro de output vazio, provavelmente o processo de conversão falhou" #: /home/kovid/work/calibre/src/calibre/gui2/actions/copy_to_library.py:83 msgid "Copy to library" -msgstr "" +msgstr "Copiar para a biblioteca" #: /home/kovid/work/calibre/src/calibre/gui2/actions/copy_to_library.py:84 msgid "Copy selected books to the specified library" -msgstr "" +msgstr "Copiar livros seleccionados para a biblioteca escolhida" #: /home/kovid/work/calibre/src/calibre/gui2/actions/copy_to_library.py:117 msgid "(delete after copy)" -msgstr "" +msgstr "(apagar após cópia)" #: /home/kovid/work/calibre/src/calibre/gui2/actions/copy_to_library.py:126 msgid "Cannot copy" -msgstr "" +msgstr "Não é possível copiar" #: /home/kovid/work/calibre/src/calibre/gui2/actions/copy_to_library.py:131 msgid "No library" -msgstr "" +msgstr "Não existe biblioteca" #: /home/kovid/work/calibre/src/calibre/gui2/actions/copy_to_library.py:132 msgid "No library found at %s" -msgstr "" +msgstr "Não foi encontrada a biblioteca em %s" #: /home/kovid/work/calibre/src/calibre/gui2/actions/copy_to_library.py:135 #: /home/kovid/work/calibre/src/calibre/gui2/actions/copy_to_library.py:139 msgid "Copying" -msgstr "" +msgstr "A copiar" #: /home/kovid/work/calibre/src/calibre/gui2/actions/copy_to_library.py:150 msgid "Could not copy books: " -msgstr "" +msgstr "Não é possível copiar livros " #: /home/kovid/work/calibre/src/calibre/gui2/actions/copy_to_library.py:153 msgid "Copied %d books to %s" -msgstr "" +msgstr "Compiados %d livros de %s" #: /home/kovid/work/calibre/src/calibre/gui2/actions/copy_to_library.py:168 msgid "" "You cannot use other libraries while using the environment variable " "CALIBRE_OVERRIDE_DATABASE_PATH." msgstr "" +"Não pode usar outraz bibliotecas enquanto usa a variável de ambiente " +"CALIBRE_OVERRIDE_DATABASE_PATH." -#: /home/kovid/work/calibre/src/calibre/gui2/actions/delete.py:32 +#: /home/kovid/work/calibre/src/calibre/gui2/actions/delete.py:31 +msgid "" +"You are trying to delete %d books. Sending so many files to the Recycle Bin " +"can be slow. Should calibre skip the Recycle Bin? If you click Yes " +"the files will be permanently deleted." +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/actions/delete.py:42 msgid "Deleting..." -msgstr "" +msgstr "A apagar..." -#: /home/kovid/work/calibre/src/calibre/gui2/actions/delete.py:54 +#: /home/kovid/work/calibre/src/calibre/gui2/actions/delete.py:65 msgid "Deleted" -msgstr "" +msgstr "Eliminado" -#: /home/kovid/work/calibre/src/calibre/gui2/actions/delete.py:66 +#: /home/kovid/work/calibre/src/calibre/gui2/actions/delete.py:77 msgid "Failed to delete" -msgstr "" +msgstr "Erro ao apagar" -#: /home/kovid/work/calibre/src/calibre/gui2/actions/delete.py:67 +#: /home/kovid/work/calibre/src/calibre/gui2/actions/delete.py:78 msgid "" "Failed to delete some books, click the Show Details button for details." msgstr "" +"Erro ao apagar alguns livros, clique em Mostrar Detalhes para mais detalhes" -#: /home/kovid/work/calibre/src/calibre/gui2/actions/delete.py:73 +#: /home/kovid/work/calibre/src/calibre/gui2/actions/delete.py:84 msgid "Del" msgstr "Apagar" -#: /home/kovid/work/calibre/src/calibre/gui2/actions/delete.py:73 +#: /home/kovid/work/calibre/src/calibre/gui2/actions/delete.py:84 msgid "Remove books" msgstr "Remover livros" -#: /home/kovid/work/calibre/src/calibre/gui2/actions/delete.py:79 -msgid "Remove selected books" -msgstr "" - -#: /home/kovid/work/calibre/src/calibre/gui2/actions/delete.py:81 -msgid "Remove files of a specific format from selected books.." -msgstr "" - -#: /home/kovid/work/calibre/src/calibre/gui2/actions/delete.py:84 -msgid "Remove all formats from selected books, except..." -msgstr "" - -#: /home/kovid/work/calibre/src/calibre/gui2/actions/delete.py:87 -msgid "Remove covers from selected books" -msgstr "" - #: /home/kovid/work/calibre/src/calibre/gui2/actions/delete.py:90 +msgid "Remove selected books" +msgstr "Remover o livro seleccionado" + +#: /home/kovid/work/calibre/src/calibre/gui2/actions/delete.py:92 +msgid "Remove files of a specific format from selected books.." +msgstr "Remover ficheiros de um formato específico dos livros seleccionados." + +#: /home/kovid/work/calibre/src/calibre/gui2/actions/delete.py:95 +msgid "Remove all formats from selected books, except..." +msgstr "Remover todos os formatos dos livros seleccionados, excepto..." + +#: /home/kovid/work/calibre/src/calibre/gui2/actions/delete.py:98 +msgid "Remove covers from selected books" +msgstr "Remover capas dos livros seleccionados" + +#: /home/kovid/work/calibre/src/calibre/gui2/actions/delete.py:101 msgid "Remove matching books from device" -msgstr "" +msgstr "Remover livros do dispositivo que coincidam" -#: /home/kovid/work/calibre/src/calibre/gui2/actions/delete.py:113 +#: /home/kovid/work/calibre/src/calibre/gui2/actions/delete.py:124 msgid "Cannot delete" -msgstr "" +msgstr "Não é possível apagar" -#: /home/kovid/work/calibre/src/calibre/gui2/actions/delete.py:126 +#: /home/kovid/work/calibre/src/calibre/gui2/actions/delete.py:137 msgid "Choose formats to be deleted" -msgstr "" +msgstr "Escolha os formatos a serem apagados" -#: /home/kovid/work/calibre/src/calibre/gui2/actions/delete.py:144 +#: /home/kovid/work/calibre/src/calibre/gui2/actions/delete.py:155 msgid "Choose formats not to be deleted" -msgstr "" - -#: /home/kovid/work/calibre/src/calibre/gui2/actions/delete.py:164 -msgid "Cannot delete books" -msgstr "" - -#: /home/kovid/work/calibre/src/calibre/gui2/actions/delete.py:165 -msgid "No device is connected" -msgstr "" +msgstr "Escolha os formatos que não devem ser apagados" #: /home/kovid/work/calibre/src/calibre/gui2/actions/delete.py:175 -msgid "Main memory" -msgstr "" +msgid "Cannot delete books" +msgstr "Não é possível apagar os livros" #: /home/kovid/work/calibre/src/calibre/gui2/actions/delete.py:176 -#: /home/kovid/work/calibre/src/calibre/gui2/device.py:469 -#: /home/kovid/work/calibre/src/calibre/gui2/device.py:478 +msgid "No device is connected" +msgstr "Não existem dispositivos ligados" + +#: /home/kovid/work/calibre/src/calibre/gui2/actions/delete.py:186 +msgid "Main memory" +msgstr "Memória principal" + +#: /home/kovid/work/calibre/src/calibre/gui2/actions/delete.py:187 +#: /home/kovid/work/calibre/src/calibre/gui2/device.py:468 +#: /home/kovid/work/calibre/src/calibre/gui2/device.py:477 msgid "Storage Card A" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/actions/delete.py:177 -#: /home/kovid/work/calibre/src/calibre/gui2/device.py:471 -#: /home/kovid/work/calibre/src/calibre/gui2/device.py:480 +#: /home/kovid/work/calibre/src/calibre/gui2/actions/delete.py:188 +#: /home/kovid/work/calibre/src/calibre/gui2/device.py:470 +#: /home/kovid/work/calibre/src/calibre/gui2/device.py:479 msgid "Storage Card B" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/actions/delete.py:182 +#: /home/kovid/work/calibre/src/calibre/gui2/actions/delete.py:193 msgid "No books to delete" -msgstr "" +msgstr "Não existem livros para apagar" -#: /home/kovid/work/calibre/src/calibre/gui2/actions/delete.py:183 +#: /home/kovid/work/calibre/src/calibre/gui2/actions/delete.py:194 msgid "None of the selected books are on the device" -msgstr "" +msgstr "Nenhum dos livros seleccionados está no dispositvo" -#: /home/kovid/work/calibre/src/calibre/gui2/actions/delete.py:200 -#: /home/kovid/work/calibre/src/calibre/gui2/actions/delete.py:290 +#: /home/kovid/work/calibre/src/calibre/gui2/actions/delete.py:211 +#: /home/kovid/work/calibre/src/calibre/gui2/actions/delete.py:302 msgid "Deleting books from device." msgstr "A apagar livros do aparelho." -#: /home/kovid/work/calibre/src/calibre/gui2/actions/delete.py:245 +#: /home/kovid/work/calibre/src/calibre/gui2/actions/delete.py:257 msgid "" "Some of the selected books are on the attached device. Where do you " "want the selected files deleted from?" msgstr "" +"Alguns dos livros seleccionados estão no dispositivo ligado. Onde " +"deseja que os ficheiros sejam apagados?" -#: /home/kovid/work/calibre/src/calibre/gui2/actions/delete.py:257 +#: /home/kovid/work/calibre/src/calibre/gui2/actions/delete.py:269 msgid "" "The selected books will be permanently deleted and the files removed " "from your calibre library. Are you sure?" msgstr "" +"Os livros seleccionados serão apagados permanentemente e os ficheiros " +"removidos da biblioteca do calibre. Tem a certeza?" -#: /home/kovid/work/calibre/src/calibre/gui2/actions/delete.py:282 +#: /home/kovid/work/calibre/src/calibre/gui2/actions/delete.py:294 msgid "" "The selected books will be permanently deleted from your device. Are " "you sure?" msgstr "" +"Os livros seleccionados serão apagados permanentemente do " +"dispositivo. Tem a certeza?" #: /home/kovid/work/calibre/src/calibre/gui2/actions/device.py:31 msgid "Connect to folder" -msgstr "" +msgstr "Ligar à pasta" #: /home/kovid/work/calibre/src/calibre/gui2/actions/device.py:36 msgid "Connect to iTunes" -msgstr "" +msgstr "Ligar ao iTunes" #: /home/kovid/work/calibre/src/calibre/gui2/actions/device.py:42 msgid "Connect to Bambook" -msgstr "" +msgstr "Ligar a Bambook" #: /home/kovid/work/calibre/src/calibre/gui2/actions/device.py:56 #: /home/kovid/work/calibre/src/calibre/gui2/actions/device.py:64 msgid "Start Content Server" -msgstr "" +msgstr "Ligar Servidor de Conteúdo" #: /home/kovid/work/calibre/src/calibre/gui2/actions/device.py:66 msgid "Stop Content Server" -msgstr "" +msgstr "Parar Servidor de Conteúdo" #: /home/kovid/work/calibre/src/calibre/gui2/actions/device.py:77 -#: /home/kovid/work/calibre/src/calibre/gui2/actions/device.py:95 +#: /home/kovid/work/calibre/src/calibre/gui2/actions/device.py:96 msgid "Email to" msgstr "Email para" #: /home/kovid/work/calibre/src/calibre/gui2/actions/device.py:81 msgid "Email to and delete from library" -msgstr "" +msgstr "Enviar por email para e apagar da biblioteca" -#: /home/kovid/work/calibre/src/calibre/gui2/actions/device.py:89 +#: /home/kovid/work/calibre/src/calibre/gui2/actions/device.py:90 msgid "(delete from library)" -msgstr "" +msgstr "(apagar da biblioteca)" -#: /home/kovid/work/calibre/src/calibre/gui2/actions/device.py:104 +#: /home/kovid/work/calibre/src/calibre/gui2/actions/device.py:105 msgid "Setup email based sharing of books" -msgstr "" +msgstr "Configurar email para partilhar livros" -#: /home/kovid/work/calibre/src/calibre/gui2/actions/device.py:122 +#: /home/kovid/work/calibre/src/calibre/gui2/actions/device.py:123 msgid "D" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/actions/device.py:122 +#: /home/kovid/work/calibre/src/calibre/gui2/actions/device.py:123 msgid "Send to device" msgstr "Enviar para o aparelho" #: /home/kovid/work/calibre/src/calibre/gui2/actions/device.py:141 msgid "Connect/share" -msgstr "" +msgstr "Ligar/Partilhar" -#: /home/kovid/work/calibre/src/calibre/gui2/actions/device.py:174 -#: /home/kovid/work/calibre/src/calibre/gui2/preferences/server.py:84 +#: /home/kovid/work/calibre/src/calibre/gui2/actions/device.py:178 +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/server.py:79 msgid "Stopping" -msgstr "" +msgstr "A parar" -#: /home/kovid/work/calibre/src/calibre/gui2/actions/device.py:175 -#: /home/kovid/work/calibre/src/calibre/gui2/preferences/server.py:85 +#: /home/kovid/work/calibre/src/calibre/gui2/actions/device.py:179 +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/server.py:80 msgid "Stopping server, this could take upto a minute, please wait..." msgstr "" +"A parar servidor, isto pode demorar até um minuto, espere por favor..." #: /home/kovid/work/calibre/src/calibre/gui2/actions/edit_collections.py:13 msgid "Manage collections" -msgstr "" +msgstr "Gerir colecções" #: /home/kovid/work/calibre/src/calibre/gui2/actions/edit_collections.py:14 msgid "Manage the collections on this device" -msgstr "" +msgstr "Gerir colecções neste dispositivo" #: /home/kovid/work/calibre/src/calibre/gui2/actions/edit_metadata.py:24 msgid "E" @@ -4878,11 +5038,11 @@ msgstr "E" #: /home/kovid/work/calibre/src/calibre/gui2/actions/edit_metadata.py:24 msgid "Edit metadata" -msgstr "" +msgstr "Editar meta-dados" #: /home/kovid/work/calibre/src/calibre/gui2/actions/edit_metadata.py:28 msgid "Merge book records" -msgstr "" +msgstr "Convergir registos de livros" #: /home/kovid/work/calibre/src/calibre/gui2/actions/edit_metadata.py:29 msgid "M" @@ -4900,72 +5060,99 @@ msgstr "Editar os metadados a granel" msgid "Download metadata and covers" msgstr "Descarregar os metadados e as capas" -#: /home/kovid/work/calibre/src/calibre/gui2/actions/edit_metadata.py:40 -msgid "Download only metadata" -msgstr "Descarregar só os metadados" - #: /home/kovid/work/calibre/src/calibre/gui2/actions/edit_metadata.py:42 -msgid "Download only covers" -msgstr "Descarregar só as capas" - -#: /home/kovid/work/calibre/src/calibre/gui2/actions/edit_metadata.py:45 -msgid "Download only social metadata" -msgstr "" - -#: /home/kovid/work/calibre/src/calibre/gui2/actions/edit_metadata.py:51 msgid "Merge into first selected book - delete others" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/actions/edit_metadata.py:54 +#: /home/kovid/work/calibre/src/calibre/gui2/actions/edit_metadata.py:45 msgid "Merge into first selected book - keep others" -msgstr "" +msgstr "Convergir para o primeiro livro seleccionado - manter os outros" -#: /home/kovid/work/calibre/src/calibre/gui2/actions/edit_metadata.py:58 +#: /home/kovid/work/calibre/src/calibre/gui2/actions/edit_metadata.py:49 msgid "Merge only formats into first selected book - delete others" msgstr "" +"Convergir apenas formatos para o primeiro livro seleccionado - apagar os " +"outros" -#: /home/kovid/work/calibre/src/calibre/gui2/actions/edit_metadata.py:79 +#: /home/kovid/work/calibre/src/calibre/gui2/actions/edit_metadata.py:71 msgid "Cannot download metadata" msgstr "É impossível descarregar os metadados" -#: /home/kovid/work/calibre/src/calibre/gui2/actions/edit_metadata.py:99 -msgid "social metadata" -msgstr "metadados sociais" - -#: /home/kovid/work/calibre/src/calibre/gui2/actions/edit_metadata.py:101 -msgid "covers" -msgstr "capas" - -#: /home/kovid/work/calibre/src/calibre/gui2/actions/edit_metadata.py:101 -#: /home/kovid/work/calibre/src/calibre/gui2/metadata/bulk_download.py:227 -msgid "metadata" -msgstr "metadados" - -#: /home/kovid/work/calibre/src/calibre/gui2/actions/edit_metadata.py:102 -msgid "Downloading {0} for {1} book(s)" +#: /home/kovid/work/calibre/src/calibre/gui2/actions/edit_metadata.py:81 +msgid "Failed to download metadata" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/actions/edit_metadata.py:126 -#: /home/kovid/work/calibre/src/calibre/gui2/actions/edit_metadata.py:187 +#: /home/kovid/work/calibre/src/calibre/gui2/actions/edit_metadata.py:87 +#: /home/kovid/work/calibre/src/calibre/gui2/dnd.py:84 +#: /home/kovid/work/calibre/src/calibre/gui2/metadata/single_download.py:462 +#: /home/kovid/work/calibre/src/calibre/gui2/metadata/single_download.py:716 +msgid "Download failed" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/actions/edit_metadata.py:88 +msgid "Failed to download metadata or covers for any of the %d book(s)." +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/actions/edit_metadata.py:91 +msgid "Metadata download completed" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/actions/edit_metadata.py:93 +msgid "" +"Finished downloading metadata for %d book(s). Proceed with updating " +"the metadata in your library?" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/actions/edit_metadata.py:100 +msgid "" +"Could not download metadata and/or covers for %d of the books. Click \"Show " +"details\" to see which books." +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/actions/edit_metadata.py:107 +msgid "Download complete" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/actions/edit_metadata.py:107 +#: /home/kovid/work/calibre/src/calibre/gui2/metadata/single_download.py:777 +msgid "Download log" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/actions/edit_metadata.py:136 +msgid "Some books changed" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/actions/edit_metadata.py:137 +msgid "" +"The metadata for some books in your library has changed since you started " +"the download. If you proceed, some of those changes may be overwritten. " +"Click \"Show details\" to see the list of changed books. Do you want to " +"proceed?" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/actions/edit_metadata.py:155 +#: /home/kovid/work/calibre/src/calibre/gui2/actions/edit_metadata.py:219 msgid "Cannot edit metadata" msgstr "É impossível editar os metadados" -#: /home/kovid/work/calibre/src/calibre/gui2/actions/edit_metadata.py:224 -#: /home/kovid/work/calibre/src/calibre/gui2/actions/edit_metadata.py:227 +#: /home/kovid/work/calibre/src/calibre/gui2/actions/edit_metadata.py:256 +#: /home/kovid/work/calibre/src/calibre/gui2/actions/edit_metadata.py:259 msgid "Cannot merge books" msgstr "Não é possível combinar livros" -#: /home/kovid/work/calibre/src/calibre/gui2/actions/edit_metadata.py:228 +#: /home/kovid/work/calibre/src/calibre/gui2/actions/edit_metadata.py:260 msgid "At least two books must be selected for merging" msgstr "A combinação exige a selecção de pelo menos dois livros" -#: /home/kovid/work/calibre/src/calibre/gui2/actions/edit_metadata.py:231 +#: /home/kovid/work/calibre/src/calibre/gui2/actions/edit_metadata.py:263 msgid "" "You are about to merge more than 5 books. Are you sure you want to " "proceed?" msgstr "" +"Está prestes a convergir mais de 5 livros. Tem a certeza que quer " +"continuar?" -#: /home/kovid/work/calibre/src/calibre/gui2/actions/edit_metadata.py:239 +#: /home/kovid/work/calibre/src/calibre/gui2/actions/edit_metadata.py:271 msgid "" "Book formats and metadata from the selected books will be added to the " "first selected book (%s). ISBN will not be merged.

      The " @@ -4973,7 +5160,7 @@ msgid "" "changed.

      Please confirm you want to proceed." msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/actions/edit_metadata.py:251 +#: /home/kovid/work/calibre/src/calibre/gui2/actions/edit_metadata.py:283 msgid "" "Book formats from the selected books will be merged into the first " "selected book (%s). Metadata in the first selected book will not be " @@ -4985,7 +5172,7 @@ msgid "" "calibre library.

      Are you sure you want to proceed?" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/actions/edit_metadata.py:267 +#: /home/kovid/work/calibre/src/calibre/gui2/actions/edit_metadata.py:299 msgid "" "Book formats and metadata from the selected books will be merged into the " "first selected book (%s). ISBN will not be " @@ -4996,25 +5183,39 @@ msgid "" "Are you sure you want to proceed?" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/actions/fetch_news.py:17 +#: /home/kovid/work/calibre/src/calibre/gui2/actions/edit_metadata.py:455 +msgid "Applying changed metadata" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/actions/edit_metadata.py:520 +msgid "Some failures" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/actions/edit_metadata.py:521 +msgid "" +"Failed to apply updated metadata for some books in your library. Click " +"\"Show Details\" to see details." +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/actions/fetch_news.py:19 msgid "F" msgstr "F" -#: /home/kovid/work/calibre/src/calibre/gui2/actions/fetch_news.py:17 +#: /home/kovid/work/calibre/src/calibre/gui2/actions/fetch_news.py:19 msgid "Fetch news" msgstr "Recolher notícias" -#: /home/kovid/work/calibre/src/calibre/gui2/actions/fetch_news.py:52 +#: /home/kovid/work/calibre/src/calibre/gui2/actions/fetch_news.py:54 msgid "Fetching news from " msgstr "Recolher notícias de " -#: /home/kovid/work/calibre/src/calibre/gui2/actions/fetch_news.py:66 +#: /home/kovid/work/calibre/src/calibre/gui2/actions/fetch_news.py:83 msgid " fetched." msgstr " recolhida." #: /home/kovid/work/calibre/src/calibre/gui2/actions/help.py:16 msgid "Browse the calibre User Manual" -msgstr "" +msgstr "Pesquisar no Manual de Utilizador do calibre" #: /home/kovid/work/calibre/src/calibre/gui2/actions/help.py:16 msgid "F1" @@ -5027,21 +5228,21 @@ msgstr "Ajuda" #: /home/kovid/work/calibre/src/calibre/gui2/actions/next_match.py:12 msgid "Move to next match" -msgstr "" +msgstr "Passar para o próximo resultado" #: /home/kovid/work/calibre/src/calibre/gui2/actions/next_match.py:13 msgid "Move to next highlighted match" -msgstr "" +msgstr "Mover para o próximo resultado seleccionado" #: /home/kovid/work/calibre/src/calibre/gui2/actions/next_match.py:13 -#: /home/kovid/work/calibre/src/calibre/gui2/library/delegates.py:355 +#: /home/kovid/work/calibre/src/calibre/gui2/library/delegates.py:361 msgid "N" msgstr "" #: /home/kovid/work/calibre/src/calibre/gui2/actions/next_match.py:13 #: /home/kovid/work/calibre/src/calibre/gui2/viewer/main_ui.py:204 msgid "F3" -msgstr "" +msgstr "F3" #: /home/kovid/work/calibre/src/calibre/gui2/actions/next_match.py:25 msgid "Move to previous item" @@ -5073,21 +5274,25 @@ msgid "Ctrl+P" msgstr "Ctrl+P" #: /home/kovid/work/calibre/src/calibre/gui2/actions/preferences.py:24 -#: /home/kovid/work/calibre/src/calibre/gui2/preferences/main.py:201 +msgid "Change calibre behavior" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/actions/preferences.py:25 +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/main.py:208 msgid "Run welcome wizard" msgstr "Executar o assistente de boas vindas" -#: /home/kovid/work/calibre/src/calibre/gui2/actions/preferences.py:28 +#: /home/kovid/work/calibre/src/calibre/gui2/actions/preferences.py:29 msgid "Restart in debug mode" -msgstr "" +msgstr "Reiniciar em modo de debug" -#: /home/kovid/work/calibre/src/calibre/gui2/actions/preferences.py:40 +#: /home/kovid/work/calibre/src/calibre/gui2/actions/preferences.py:44 msgid "Cannot configure while there are running jobs." msgstr "É impossível configurar enquanto estiverem processos a executar." -#: /home/kovid/work/calibre/src/calibre/gui2/actions/preferences.py:45 +#: /home/kovid/work/calibre/src/calibre/gui2/actions/preferences.py:49 msgid "Cannot configure before calibre is restarted." -msgstr "" +msgstr "Não é possível configurar antes de o calibre ser reiniciado." #: /home/kovid/work/calibre/src/calibre/gui2/actions/restart.py:14 msgid "&Restart" @@ -5123,7 +5328,7 @@ msgstr "Guardar só o formato %s no disco" #: /home/kovid/work/calibre/src/calibre/gui2/actions/save_to_disk.py:54 #: /home/kovid/work/calibre/src/calibre/gui2/actions/save_to_disk.py:72 msgid "Save only %s format to disk in a single directory" -msgstr "" +msgstr "Guardar apenas %s formatos para o disco numa única directoria" #: /home/kovid/work/calibre/src/calibre/gui2/actions/save_to_disk.py:91 msgid "Cannot save to disk" @@ -5158,7 +5363,7 @@ msgid "Click the show details button to see which ones." msgstr "Clique no botão ver detalhes para ver quais." #: /home/kovid/work/calibre/src/calibre/gui2/actions/show_book_details.py:16 -#: /home/kovid/work/calibre/src/calibre/library/server/browse.py:696 +#: /home/kovid/work/calibre/src/calibre/library/server/browse.py:766 msgid "Show book details" msgstr "Mostrar os detalhes do livro" @@ -5210,14 +5415,77 @@ msgstr "Alt+T" msgid "Books with the same tags" msgstr "Livros com as mesmas etiquetas" +#: /home/kovid/work/calibre/src/calibre/gui2/actions/store.py:20 +msgid "Get books" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/actions/store.py:29 +msgid "Search for ebooks" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/actions/store.py:30 +msgid "Search for this author" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/actions/store.py:31 +msgid "Search for this title" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/actions/store.py:32 +msgid "Search for this book" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/actions/store.py:34 +#: /home/kovid/work/calibre/src/calibre/gui2/store/search/search_ui.py:122 +#: /home/kovid/work/calibre/src/calibre/gui2/store/search_ui.py:110 +msgid "Stores" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/actions/store.py:76 +#: /home/kovid/work/calibre/src/calibre/gui2/actions/store.py:95 +#: /home/kovid/work/calibre/src/calibre/gui2/actions/store.py:104 +msgid "Cannot search" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/actions/store.py:116 +msgid "" +"Calibre helps you find the ebooks you want by searching the websites of " +"various commercial and public domain book sources for you." +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/actions/store.py:120 +msgid "" +"Using the integrated search you can easily find which store has the book you " +"are looking for, at the best price. You also get DRM status and other useful " +"information." +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/actions/store.py:124 +msgid "" +"All transactions (paid or otherwise) are handled between you and the book " +"seller. Calibre is not part of this process and any issues related to a " +"purchase should be directed to the website you are buying from. Be sure to " +"double check that any books you get will work with your e-book reader, " +"especially if the book you are buying has DRM." +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/actions/store.py:134 +msgid "Show this message again" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/actions/store.py:135 +msgid "About Get Books" +msgstr "" + #: /home/kovid/work/calibre/src/calibre/gui2/actions/tweak_epub.py:15 -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/tweak_epub_ui.py:54 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/tweak_epub_ui.py:60 msgid "Tweak ePub" msgstr "" #: /home/kovid/work/calibre/src/calibre/gui2/actions/tweak_epub.py:16 msgid "Make small changes to ePub format books" -msgstr "" +msgstr "Fazer pequenas alterações ao formato de ebooks ePub" #: /home/kovid/work/calibre/src/calibre/gui2/actions/tweak_epub.py:17 msgid "T" @@ -5230,51 +5498,59 @@ msgstr "" #: /home/kovid/work/calibre/src/calibre/gui2/actions/tweak_epub.py:40 msgid "No ePub available. First convert the book to ePub." -msgstr "" +msgstr "Formato ePub não disponível. Converter primeiro o livro para ePub" -#: /home/kovid/work/calibre/src/calibre/gui2/actions/view.py:24 +#: /home/kovid/work/calibre/src/calibre/gui2/actions/view.py:36 msgid "V" msgstr "V" -#: /home/kovid/work/calibre/src/calibre/gui2/actions/view.py:24 -#: /home/kovid/work/calibre/src/calibre/gui2/actions/view.py:31 +#: /home/kovid/work/calibre/src/calibre/gui2/actions/view.py:36 msgid "View" msgstr "Ver" -#: /home/kovid/work/calibre/src/calibre/gui2/actions/view.py:32 +#: /home/kovid/work/calibre/src/calibre/gui2/actions/view.py:43 msgid "View specific format" msgstr "Ver o formato específico" -#: /home/kovid/work/calibre/src/calibre/gui2/actions/view.py:85 -#: /home/kovid/work/calibre/src/calibre/gui2/actions/view.py:170 +#: /home/kovid/work/calibre/src/calibre/gui2/actions/view.py:51 +msgid "Read a random book" +msgstr "Ler um livro aleatório" + +#: /home/kovid/work/calibre/src/calibre/gui2/actions/view.py:55 +msgid "Clear recently viewed list" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/actions/view.py:139 +#: /home/kovid/work/calibre/src/calibre/gui2/actions/view.py:219 +#: /home/kovid/work/calibre/src/calibre/gui2/actions/view.py:226 msgid "Cannot view" msgstr "É impossível ver" -#: /home/kovid/work/calibre/src/calibre/gui2/actions/view.py:98 -#: /home/kovid/work/calibre/src/calibre/gui2/actions/view.py:112 +#: /home/kovid/work/calibre/src/calibre/gui2/actions/view.py:152 +#: /home/kovid/work/calibre/src/calibre/gui2/actions/view.py:166 msgid "Format unavailable" -msgstr "" +msgstr "Formato não disponível" -#: /home/kovid/work/calibre/src/calibre/gui2/actions/view.py:99 +#: /home/kovid/work/calibre/src/calibre/gui2/actions/view.py:153 msgid "Selected books have no formats" -msgstr "" +msgstr "Os livros seleccionados não têm formato" -#: /home/kovid/work/calibre/src/calibre/gui2/actions/view.py:101 +#: /home/kovid/work/calibre/src/calibre/gui2/actions/view.py:155 #: /home/kovid/work/calibre/src/calibre/gui2/convert/regex_builder.py:127 msgid "Choose the format to view" msgstr "Escolher o formato para ver" -#: /home/kovid/work/calibre/src/calibre/gui2/actions/view.py:113 +#: /home/kovid/work/calibre/src/calibre/gui2/actions/view.py:167 msgid "" "Not all the selected books were available in the %s format. You should " "convert them first." msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/actions/view.py:120 +#: /home/kovid/work/calibre/src/calibre/gui2/actions/view.py:174 msgid "Multiple Books Selected" msgstr "Múltiplos livros seleccionados" -#: /home/kovid/work/calibre/src/calibre/gui2/actions/view.py:121 +#: /home/kovid/work/calibre/src/calibre/gui2/actions/view.py:175 msgid "" "You are attempting to open %d books. Opening too many books at once can be " "slow and have a negative effect on the responsiveness of your computer. Once " @@ -5285,11 +5561,15 @@ msgstr "" "e ter um efeito negativo na reacção do seu computador. Uma vez começado o " "processo não pode ser parado até estar completo. Deseja continuar?" -#: /home/kovid/work/calibre/src/calibre/gui2/actions/view.py:130 +#: /home/kovid/work/calibre/src/calibre/gui2/actions/view.py:184 msgid "Cannot open folder" msgstr "É impossível abrir a pasta" -#: /home/kovid/work/calibre/src/calibre/gui2/actions/view.py:171 +#: /home/kovid/work/calibre/src/calibre/gui2/actions/view.py:220 +msgid "This book no longer exists in your library" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/actions/view.py:227 msgid "%s has no available formats." msgstr "%s não tem formatos disponíveis." @@ -5314,7 +5594,7 @@ msgid "The specified directory could not be processed." msgstr "É impossível processar a pasta especificada." #: /home/kovid/work/calibre/src/calibre/gui2/add.py:274 -#: /home/kovid/work/calibre/src/calibre/gui2/device.py:821 +#: /home/kovid/work/calibre/src/calibre/gui2/device.py:845 msgid "No books" msgstr "Nenhuns livros" @@ -5363,34 +5643,34 @@ msgstr "A guardar..." msgid "Saved" msgstr "Guardado" -#: /home/kovid/work/calibre/src/calibre/gui2/add_wizard/__init__.py:57 +#: /home/kovid/work/calibre/src/calibre/gui2/add_wizard/__init__.py:56 msgid "Searching for sub-folders" msgstr "À procura de sub-pastas" -#: /home/kovid/work/calibre/src/calibre/gui2/add_wizard/__init__.py:62 +#: /home/kovid/work/calibre/src/calibre/gui2/add_wizard/__init__.py:61 msgid "Searching for books" msgstr "À procura de livros" -#: /home/kovid/work/calibre/src/calibre/gui2/add_wizard/__init__.py:74 +#: /home/kovid/work/calibre/src/calibre/gui2/add_wizard/__init__.py:73 msgid "Looking for duplicates based on file hash" -msgstr "" +msgstr "Pesquisar por duplicados baseado na hash do ficheiro" -#: /home/kovid/work/calibre/src/calibre/gui2/add_wizard/__init__.py:109 +#: /home/kovid/work/calibre/src/calibre/gui2/add_wizard/__init__.py:108 #: /home/kovid/work/calibre/src/calibre/gui2/add_wizard/welcome_ui.py:70 msgid "Choose root folder" -msgstr "" +msgstr "Escolher pasta da raíz" -#: /home/kovid/work/calibre/src/calibre/gui2/add_wizard/__init__.py:137 +#: /home/kovid/work/calibre/src/calibre/gui2/add_wizard/__init__.py:135 msgid "Invalid root folder" -msgstr "" +msgstr "Raíz de pasta inválida" -#: /home/kovid/work/calibre/src/calibre/gui2/add_wizard/__init__.py:138 +#: /home/kovid/work/calibre/src/calibre/gui2/add_wizard/__init__.py:136 msgid "is not a valid root folder" -msgstr "" +msgstr "não é uma raíz de pasta válida" -#: /home/kovid/work/calibre/src/calibre/gui2/add_wizard/__init__.py:148 +#: /home/kovid/work/calibre/src/calibre/gui2/add_wizard/__init__.py:146 msgid "Add books to calibre" -msgstr "" +msgstr "Adicionar livros ao calibre" #: /home/kovid/work/calibre/src/calibre/gui2/add_wizard/scan_ui.py:26 #: /home/kovid/work/calibre/src/calibre/gui2/add_wizard/welcome_ui.py:62 @@ -5403,19 +5683,19 @@ msgstr "Página do Assistente" #: /home/kovid/work/calibre/src/calibre/gui2/add_wizard/scan_ui.py:27 msgid "Scanning root folder for books" -msgstr "" +msgstr "Pesquisar a pasta raíz por livros" #: /home/kovid/work/calibre/src/calibre/gui2/add_wizard/scan_ui.py:28 msgid "This may take a few minutes" -msgstr "" +msgstr "Isto pode demorar alguns minutos" #: /home/kovid/work/calibre/src/calibre/gui2/add_wizard/welcome_ui.py:63 msgid "Choose the location to add books from" -msgstr "" +msgstr "Escolha a localização a partir de onde quer adicionar livros" #: /home/kovid/work/calibre/src/calibre/gui2/add_wizard/welcome_ui.py:64 msgid "Select a folder on your hard disk" -msgstr "" +msgstr "Escolha uma pasta no seu disco" #: /home/kovid/work/calibre/src/calibre/gui2/add_wizard/welcome_ui.py:65 msgid "" @@ -5452,21 +5732,15 @@ msgstr "" #: /home/kovid/work/calibre/src/calibre/gui2/convert/debug_ui.py:58 #: /home/kovid/work/calibre/src/calibre/gui2/convert/look_and_feel_ui.py:143 #: /home/kovid/work/calibre/src/calibre/gui2/convert/metadata_ui.py:162 -#: /home/kovid/work/calibre/src/calibre/gui2/convert/xexp_edit_ui.py:56 +#: /home/kovid/work/calibre/src/calibre/gui2/convert/xexp_edit_ui.py:57 #: /home/kovid/work/calibre/src/calibre/gui2/device_drivers/configwidget_ui.py:79 #: /home/kovid/work/calibre/src/calibre/gui2/device_drivers/configwidget_ui.py:80 #: /home/kovid/work/calibre/src/calibre/gui2/dialogs/choose_library_ui.py:86 -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:534 -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:539 -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single_ui.py:412 -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single_ui.py:414 -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single_ui.py:417 -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single_ui.py:437 -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single_ui.py:458 -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single_ui.py:460 -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single_ui.py:462 -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/saved_search_editor_ui.py:92 -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/saved_search_editor_ui.py:95 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:560 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:565 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/saved_search_editor_ui.py:98 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/saved_search_editor_ui.py:101 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/saved_search_editor_ui.py:103 #: /home/kovid/work/calibre/src/calibre/gui2/dialogs/tag_categories_ui.py:170 #: /home/kovid/work/calibre/src/calibre/gui2/dialogs/tag_categories_ui.py:173 #: /home/kovid/work/calibre/src/calibre/gui2/dialogs/tag_categories_ui.py:175 @@ -5478,16 +5752,16 @@ 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:80 #: /home/kovid/work/calibre/src/calibre/gui2/dialogs/tag_list_editor_ui.py:82 -#: /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:274 -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/user_profiles_ui.py:275 -#: /home/kovid/work/calibre/src/calibre/gui2/preferences/behavior_ui.py:148 -#: /home/kovid/work/calibre/src/calibre/gui2/preferences/behavior_ui.py:149 -#: /home/kovid/work/calibre/src/calibre/gui2/preferences/columns_ui.py:83 -#: /home/kovid/work/calibre/src/calibre/gui2/preferences/columns_ui.py:85 -#: /home/kovid/work/calibre/src/calibre/gui2/preferences/columns_ui.py:87 +#: /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/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 -#: /home/kovid/work/calibre/src/calibre/gui2/preferences/columns_ui.py:90 +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/columns_ui.py:91 +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/columns_ui.py:93 +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/columns_ui.py:95 +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/columns_ui.py:97 #: /home/kovid/work/calibre/src/calibre/gui2/preferences/custom_columns_ui.py:83 #: /home/kovid/work/calibre/src/calibre/gui2/preferences/custom_columns_ui.py:85 #: /home/kovid/work/calibre/src/calibre/gui2/preferences/custom_columns_ui.py:87 @@ -5500,19 +5774,23 @@ msgstr "" #: /home/kovid/work/calibre/src/calibre/gui2/preferences/toolbar_ui.py:110 #: /home/kovid/work/calibre/src/calibre/gui2/shortcuts_ui.py:80 #: /home/kovid/work/calibre/src/calibre/gui2/shortcuts_ui.py:85 +#: /home/kovid/work/calibre/src/calibre/gui2/store/mobileread/store_dialog_ui.py:75 +#: /home/kovid/work/calibre/src/calibre/gui2/store/search/search_ui.py:120 #: /home/kovid/work/calibre/src/calibre/gui2/viewer/main_ui.py:191 msgid "..." msgstr "..." #: /home/kovid/work/calibre/src/calibre/gui2/add_wizard/welcome_ui.py:72 msgid "Handle multiple files per book" -msgstr "" +msgstr "Gerir múltiplis ficheiros por livro" #: /home/kovid/work/calibre/src/calibre/gui2/add_wizard/welcome_ui.py:73 msgid "" "&One book per folder, assumes every ebook file in a folder is the same book " "in a different format" msgstr "" +"&Um livro por pasta, assume que cada ficheiro de ebook numa pasta " +"corresponde ao mesmo livro num formato diferente" #: /home/kovid/work/calibre/src/calibre/gui2/add_wizard/welcome_ui.py:74 msgid "" @@ -5520,60 +5798,52 @@ msgid "" msgstr "" "&Vários livros por pasta, assumir que cada ficheiro é um livro diferente" -#: /home/kovid/work/calibre/src/calibre/gui2/book_details.py:26 -#: /home/kovid/work/calibre/src/calibre/gui2/book_details.py:53 -#: /home/kovid/work/calibre/src/calibre/gui2/book_details.py:62 -#: /home/kovid/work/calibre/src/calibre/gui2/book_details.py:434 -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/book_info.py:130 -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/book_info.py:131 -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/book_info.py:132 -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/book_info.py:145 -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/delete_matching_from_device.py:76 -#: /home/kovid/work/calibre/src/calibre/gui2/library/models.py:375 -#: /home/kovid/work/calibre/src/calibre/gui2/library/models.py:1170 -msgid "Path" -msgstr "Caminho" +#: /home/kovid/work/calibre/src/calibre/gui2/bars.py:190 +msgid "Donate" +msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/book_details.py:27 -#: /home/kovid/work/calibre/src/calibre/gui2/book_details.py:56 -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/book_info.py:133 -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/book_info.py:134 -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/book_info.py:135 -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/book_info.py:138 -#: /home/kovid/work/calibre/src/calibre/gui2/library/models.py:374 -#: /home/kovid/work/calibre/src/calibre/gui2/preferences/emailp.py:24 -#: /home/kovid/work/calibre/src/calibre/library/field_metadata.py:118 -msgid "Formats" -msgstr "Formatos" - -#: /home/kovid/work/calibre/src/calibre/gui2/book_details.py:28 -#: /home/kovid/work/calibre/src/calibre/gui2/library/models.py:981 -#: /home/kovid/work/calibre/src/calibre/gui2/library/models.py:1173 -msgid "Collections" -msgstr "Colecções" - -#: /home/kovid/work/calibre/src/calibre/gui2/book_details.py:55 -#: /home/kovid/work/calibre/src/calibre/gui2/book_details.py:64 +#: /home/kovid/work/calibre/src/calibre/gui2/book_details.py:108 msgid "Click to open" msgstr "Clicar para abrir" -#: /home/kovid/work/calibre/src/calibre/gui2/book_details.py:56 -#: /home/kovid/work/calibre/src/calibre/gui2/library/models.py:367 -#: /home/kovid/work/calibre/src/calibre/gui2/library/models.py:373 -#: /home/kovid/work/calibre/src/calibre/gui2/library/models.py:379 -#: /home/kovid/work/calibre/src/calibre/gui2/library/models.py:1179 -#: /home/kovid/work/calibre/src/calibre/gui2/library/models.py:1183 -#: /home/kovid/work/calibre/src/calibre/gui2/shortcuts.py:48 -#: /home/kovid/work/calibre/src/calibre/gui2/shortcuts_ui.py:78 -#: /home/kovid/work/calibre/src/calibre/gui2/shortcuts_ui.py:83 -#: /home/kovid/work/calibre/src/calibre/gui2/widgets.py:316 -msgid "None" -msgstr "Nenhum" +#: /home/kovid/work/calibre/src/calibre/gui2/book_details.py:123 +msgid "Ids" +msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/book_details.py:433 +#: /home/kovid/work/calibre/src/calibre/gui2/book_details.py:133 +msgid "Book %s of %s" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/book_details.py:144 +#: /home/kovid/work/calibre/src/calibre/gui2/library/models.py:981 +msgid "Collections" +msgstr "Colecções" + +#: /home/kovid/work/calibre/src/calibre/gui2/book_details.py:246 +#: /home/kovid/work/calibre/src/calibre/gui2/widgets.py:243 +msgid "Paste Cover" +msgstr "Colar Capa" + +#: /home/kovid/work/calibre/src/calibre/gui2/book_details.py:247 +#: /home/kovid/work/calibre/src/calibre/gui2/widgets.py:244 +msgid "Copy Cover" +msgstr "Copiar Capa" + +#: /home/kovid/work/calibre/src/calibre/gui2/book_details.py:513 msgid "Double-click to open Book Details window" msgstr "Clicar duas vezes para abrir a janela \"Detalhes do Livro\"" +#: /home/kovid/work/calibre/src/calibre/gui2/book_details.py:514 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/delete_matching_from_device.py:76 +#: /home/kovid/work/calibre/src/calibre/library/field_metadata.py:261 +msgid "Path" +msgstr "Caminho" + +#: /home/kovid/work/calibre/src/calibre/gui2/book_details.py:515 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/book_info.py:109 +msgid "Cover size: %dx%d" +msgstr "" + #: /home/kovid/work/calibre/src/calibre/gui2/catalog/catalog_bibtex.py:16 msgid "BibTeX Options" msgstr "Opções BibTeX" @@ -5585,6 +5855,7 @@ msgstr "Opções BibTeX" #: /home/kovid/work/calibre/src/calibre/gui2/convert/epub_output.py:16 #: /home/kovid/work/calibre/src/calibre/gui2/convert/fb2_input.py:13 #: /home/kovid/work/calibre/src/calibre/gui2/convert/fb2_output.py:15 +#: /home/kovid/work/calibre/src/calibre/gui2/convert/htmlz_output.py:15 #: /home/kovid/work/calibre/src/calibre/gui2/convert/lrf_output.py:20 #: /home/kovid/work/calibre/src/calibre/gui2/convert/mobi_output.py:20 #: /home/kovid/work/calibre/src/calibre/gui2/convert/pdb_output.py:15 @@ -5604,6 +5875,7 @@ msgstr "Opções específicas a" #: /home/kovid/work/calibre/src/calibre/gui2/catalog/catalog_epub_mobi.py:19 #: /home/kovid/work/calibre/src/calibre/gui2/convert/epub_output.py:16 #: /home/kovid/work/calibre/src/calibre/gui2/convert/fb2_output.py:15 +#: /home/kovid/work/calibre/src/calibre/gui2/convert/htmlz_output.py:15 #: /home/kovid/work/calibre/src/calibre/gui2/convert/lrf_output.py:20 #: /home/kovid/work/calibre/src/calibre/gui2/convert/mobi_output.py:20 #: /home/kovid/work/calibre/src/calibre/gui2/convert/pdb_output.py:15 @@ -5616,16 +5888,17 @@ msgstr "Opções específicas a" msgid "output" msgstr "Ficheiro de destino" -#: /home/kovid/work/calibre/src/calibre/gui2/catalog/catalog_bibtex_ui.py:89 +#: /home/kovid/work/calibre/src/calibre/gui2/catalog/catalog_bibtex_ui.py:77 #: /home/kovid/work/calibre/src/calibre/gui2/catalog/catalog_csv_xml_ui.py:42 #: /home/kovid/work/calibre/src/calibre/gui2/catalog/catalog_epub_mobi_ui.py:295 #: /home/kovid/work/calibre/src/calibre/gui2/catalog/catalog_tab_template_ui.py:32 -#: /home/kovid/work/calibre/src/calibre/gui2/convert/comic_input_ui.py:100 +#: /home/kovid/work/calibre/src/calibre/gui2/convert/comic_input_ui.py:103 #: /home/kovid/work/calibre/src/calibre/gui2/convert/debug_ui.py:54 #: /home/kovid/work/calibre/src/calibre/gui2/convert/epub_output_ui.py:56 #: /home/kovid/work/calibre/src/calibre/gui2/convert/fb2_input_ui.py:33 -#: /home/kovid/work/calibre/src/calibre/gui2/convert/fb2_output_ui.py:38 +#: /home/kovid/work/calibre/src/calibre/gui2/convert/fb2_output_ui.py:44 #: /home/kovid/work/calibre/src/calibre/gui2/convert/heuristics_ui.py:112 +#: /home/kovid/work/calibre/src/calibre/gui2/convert/htmlz_output_ui.py:44 #: /home/kovid/work/calibre/src/calibre/gui2/convert/look_and_feel_ui.py:137 #: /home/kovid/work/calibre/src/calibre/gui2/convert/lrf_output_ui.py:120 #: /home/kovid/work/calibre/src/calibre/gui2/convert/metadata_ui.py:158 @@ -5639,23 +5912,24 @@ msgstr "Ficheiro de destino" #: /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:147 #: /home/kovid/work/calibre/src/calibre/gui2/convert/snb_output_ui.py:42 -#: /home/kovid/work/calibre/src/calibre/gui2/convert/structure_detection_ui.py:56 +#: /home/kovid/work/calibre/src/calibre/gui2/convert/structure_detection_ui.py:59 #: /home/kovid/work/calibre/src/calibre/gui2/convert/toc_ui.py:67 #: /home/kovid/work/calibre/src/calibre/gui2/convert/txt_input_ui.py:91 -#: /home/kovid/work/calibre/src/calibre/gui2/convert/txt_output_ui.py:84 -#: /home/kovid/work/calibre/src/calibre/gui2/convert/xexp_edit_ui.py:53 +#: /home/kovid/work/calibre/src/calibre/gui2/convert/txt_output_ui.py:87 +#: /home/kovid/work/calibre/src/calibre/gui2/convert/xexp_edit_ui.py:54 #: /home/kovid/work/calibre/src/calibre/gui2/convert/xpath_wizard_ui.py:72 #: /home/kovid/work/calibre/src/calibre/gui2/device_drivers/configwidget_ui.py:77 #: /home/kovid/work/calibre/src/calibre/gui2/dialogs/search_item_ui.py:40 -#: /home/kovid/work/calibre/src/calibre/gui2/filename_pattern_ui.py:114 +#: /home/kovid/work/calibre/src/calibre/gui2/filename_pattern_ui.py:128 #: /home/kovid/work/calibre/src/calibre/gui2/preferences/adding_ui.py:64 -#: /home/kovid/work/calibre/src/calibre/gui2/preferences/behavior_ui.py:130 -#: /home/kovid/work/calibre/src/calibre/gui2/preferences/columns_ui.py:81 +#: /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/custom_columns_ui.py:81 #: /home/kovid/work/calibre/src/calibre/gui2/preferences/email_ui.py:65 -#: /home/kovid/work/calibre/src/calibre/gui2/preferences/look_feel_ui.py:135 -#: /home/kovid/work/calibre/src/calibre/gui2/preferences/misc_ui.py:60 +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/look_feel_ui.py:197 +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/metadata_sources_ui.py:109 +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/misc_ui.py:61 #: /home/kovid/work/calibre/src/calibre/gui2/preferences/plugboard_ui.py:113 #: /home/kovid/work/calibre/src/calibre/gui2/preferences/plugins_ui.py:86 #: /home/kovid/work/calibre/src/calibre/gui2/preferences/save_template_ui.py:46 @@ -5666,72 +5940,41 @@ msgstr "Ficheiro de destino" #: /home/kovid/work/calibre/src/calibre/gui2/preferences/template_functions_ui.py:95 #: /home/kovid/work/calibre/src/calibre/gui2/preferences/toolbar_ui.py:98 #: /home/kovid/work/calibre/src/calibre/gui2/preferences/tweaks_ui.py:87 +#: /home/kovid/work/calibre/src/calibre/gui2/store/basic_config_widget_ui.py:37 #: /home/kovid/work/calibre/src/calibre/gui2/wizard/send_email_ui.py:123 msgid "Form" msgstr "Formulário" -#: /home/kovid/work/calibre/src/calibre/gui2/catalog/catalog_bibtex_ui.py:90 +#: /home/kovid/work/calibre/src/calibre/gui2/catalog/catalog_bibtex_ui.py:78 msgid "Bib file encoding:" msgstr "Codificação de ficheiro Bib:" -#: /home/kovid/work/calibre/src/calibre/gui2/catalog/catalog_bibtex_ui.py:91 +#: /home/kovid/work/calibre/src/calibre/gui2/catalog/catalog_bibtex_ui.py:79 #: /home/kovid/work/calibre/src/calibre/gui2/catalog/catalog_csv_xml_ui.py:43 msgid "Fields to include in output:" msgstr "Campos a incluir na saída:" -#: /home/kovid/work/calibre/src/calibre/gui2/catalog/catalog_bibtex_ui.py:92 -msgid "ascii/LaTeX" -msgstr "ascii/LaTeX" - -#: /home/kovid/work/calibre/src/calibre/gui2/catalog/catalog_bibtex_ui.py:93 +#: /home/kovid/work/calibre/src/calibre/gui2/catalog/catalog_bibtex_ui.py:80 msgid "Encoding configuration (change if you have errors) :" msgstr "Configuração da codificação (alterar se ocorrerem erros):" -#: /home/kovid/work/calibre/src/calibre/gui2/catalog/catalog_bibtex_ui.py:94 -msgid "strict" -msgstr "restrito" - -#: /home/kovid/work/calibre/src/calibre/gui2/catalog/catalog_bibtex_ui.py:95 -msgid "replace" -msgstr "substituir" - -#: /home/kovid/work/calibre/src/calibre/gui2/catalog/catalog_bibtex_ui.py:96 -msgid "ignore" -msgstr "ignorar" - -#: /home/kovid/work/calibre/src/calibre/gui2/catalog/catalog_bibtex_ui.py:97 -msgid "backslashreplace" -msgstr "" - -#: /home/kovid/work/calibre/src/calibre/gui2/catalog/catalog_bibtex_ui.py:98 +#: /home/kovid/work/calibre/src/calibre/gui2/catalog/catalog_bibtex_ui.py:81 msgid "BibTeX entry type:" msgstr "Tipo de entrada BibTeX:" -#: /home/kovid/work/calibre/src/calibre/gui2/catalog/catalog_bibtex_ui.py:99 -msgid "mixed" -msgstr "misturado" - -#: /home/kovid/work/calibre/src/calibre/gui2/catalog/catalog_bibtex_ui.py:100 -msgid "misc" -msgstr "vários" - -#: /home/kovid/work/calibre/src/calibre/gui2/catalog/catalog_bibtex_ui.py:101 -msgid "book" -msgstr "livro" - -#: /home/kovid/work/calibre/src/calibre/gui2/catalog/catalog_bibtex_ui.py:102 +#: /home/kovid/work/calibre/src/calibre/gui2/catalog/catalog_bibtex_ui.py:82 msgid "Create a citation tag?" msgstr "Criar uma etiqueta de citação?" -#: /home/kovid/work/calibre/src/calibre/gui2/catalog/catalog_bibtex_ui.py:103 +#: /home/kovid/work/calibre/src/calibre/gui2/catalog/catalog_bibtex_ui.py:83 msgid "Add files path with formats?" -msgstr "" +msgstr "Adicionar localização de ficheiros com formatos?" -#: /home/kovid/work/calibre/src/calibre/gui2/catalog/catalog_bibtex_ui.py:104 +#: /home/kovid/work/calibre/src/calibre/gui2/catalog/catalog_bibtex_ui.py:84 msgid "Expression to form the BibTeX citation tag:" msgstr "Expressão para criar a etiqueta de citação BibTeX:" -#: /home/kovid/work/calibre/src/calibre/gui2/catalog/catalog_bibtex_ui.py:105 +#: /home/kovid/work/calibre/src/calibre/gui2/catalog/catalog_bibtex_ui.py:85 msgid "" "Some explanation about this template:\n" " -The fields availables are 'author_sort', 'authors', 'id',\n" @@ -5760,35 +6003,35 @@ msgstr "Opções de E-book" #: /home/kovid/work/calibre/src/calibre/gui2/catalog/catalog_epub_mobi_ui.py:296 msgid "Sections to include in catalog." -msgstr "" +msgstr "Secções para incluir no catálogo" #: /home/kovid/work/calibre/src/calibre/gui2/catalog/catalog_epub_mobi_ui.py:297 msgid "Included sections" -msgstr "" +msgstr "Secções incluídas" #: /home/kovid/work/calibre/src/calibre/gui2/catalog/catalog_epub_mobi_ui.py:298 msgid "Books by &Genre" -msgstr "" +msgstr "Livros por &Género" #: /home/kovid/work/calibre/src/calibre/gui2/catalog/catalog_epub_mobi_ui.py:299 msgid "Recently &Added" -msgstr "" +msgstr "Adicionados &Recentemente" #: /home/kovid/work/calibre/src/calibre/gui2/catalog/catalog_epub_mobi_ui.py:300 msgid "&Descriptions" -msgstr "" +msgstr "&Descrições" #: /home/kovid/work/calibre/src/calibre/gui2/catalog/catalog_epub_mobi_ui.py:301 msgid "Books by &Series" -msgstr "" +msgstr "Livros por &Séries" #: /home/kovid/work/calibre/src/calibre/gui2/catalog/catalog_epub_mobi_ui.py:302 msgid "Books by &Title" -msgstr "" +msgstr "Livros por &Título" #: /home/kovid/work/calibre/src/calibre/gui2/catalog/catalog_epub_mobi_ui.py:303 msgid "Books by Author" -msgstr "" +msgstr "Livros por Autor" #: /home/kovid/work/calibre/src/calibre/gui2/catalog/catalog_epub_mobi_ui.py:304 msgid "" @@ -5800,12 +6043,12 @@ msgstr "" #: /home/kovid/work/calibre/src/calibre/gui2/catalog/catalog_epub_mobi_ui.py:308 msgid "Excluded genres" -msgstr "" +msgstr "Géneros excluídos" #: /home/kovid/work/calibre/src/calibre/gui2/catalog/catalog_epub_mobi_ui.py:309 #: /home/kovid/work/calibre/src/calibre/gui2/catalog/catalog_epub_mobi_ui.py:312 msgid "Tags to &exclude" -msgstr "" +msgstr "Tags a &excluir" #: /home/kovid/work/calibre/src/calibre/gui2/catalog/catalog_epub_mobi_ui.py:310 msgid "" @@ -5814,7 +6057,7 @@ msgstr "" #: /home/kovid/work/calibre/src/calibre/gui2/catalog/catalog_epub_mobi_ui.py:311 msgid "Excluded books" -msgstr "" +msgstr "Livros Excluídos" #: /home/kovid/work/calibre/src/calibre/gui2/catalog/catalog_epub_mobi_ui.py:313 msgid "" @@ -5825,27 +6068,27 @@ msgstr "" #: /home/kovid/work/calibre/src/calibre/gui2/catalog/catalog_epub_mobi_ui.py:315 #: /home/kovid/work/calibre/src/calibre/gui2/catalog/catalog_epub_mobi_ui.py:320 msgid "&Column/value" -msgstr "" +msgstr "&Coluna/valor" #: /home/kovid/work/calibre/src/calibre/gui2/catalog/catalog_epub_mobi_ui.py:316 msgid "Column containing additional exclusion criteria" -msgstr "" +msgstr "Coluna que contém critérios adicionais de exclusão" #: /home/kovid/work/calibre/src/calibre/gui2/catalog/catalog_epub_mobi_ui.py:317 msgid "Exclusion pattern" -msgstr "" +msgstr "Padrçao de exclusão" #: /home/kovid/work/calibre/src/calibre/gui2/catalog/catalog_epub_mobi_ui.py:318 msgid "Matching books will be displayed with a check mark" -msgstr "" +msgstr "Livros que coincidam serão mostrados com um símbolo de seleccionado" #: /home/kovid/work/calibre/src/calibre/gui2/catalog/catalog_epub_mobi_ui.py:319 msgid "Read books" -msgstr "" +msgstr "Ler livros" #: /home/kovid/work/calibre/src/calibre/gui2/catalog/catalog_epub_mobi_ui.py:321 msgid "Column containing 'read' status" -msgstr "" +msgstr "Coluna que contém o estado da 'leitura'" #: /home/kovid/work/calibre/src/calibre/gui2/catalog/catalog_epub_mobi_ui.py:322 msgid "'read book' pattern" @@ -5853,7 +6096,7 @@ msgstr "" #: /home/kovid/work/calibre/src/calibre/gui2/catalog/catalog_epub_mobi_ui.py:323 msgid "Other options" -msgstr "" +msgstr "Outras opções" #: /home/kovid/work/calibre/src/calibre/gui2/catalog/catalog_epub_mobi_ui.py:324 msgid "&Wishlist tag" @@ -5861,11 +6104,11 @@ msgstr "" #: /home/kovid/work/calibre/src/calibre/gui2/catalog/catalog_epub_mobi_ui.py:325 msgid "Books tagged as Wishlist items will be displayed with an X" -msgstr "" +msgstr "Livros com tag" #: /home/kovid/work/calibre/src/calibre/gui2/catalog/catalog_epub_mobi_ui.py:326 msgid "&Thumbnail width" -msgstr "" +msgstr "&Largura da Miniatura" #: /home/kovid/work/calibre/src/calibre/gui2/catalog/catalog_epub_mobi_ui.py:327 msgid "Size hint for Description cover thumbnails" @@ -5873,11 +6116,11 @@ msgstr "" #: /home/kovid/work/calibre/src/calibre/gui2/catalog/catalog_epub_mobi_ui.py:328 msgid " inch" -msgstr "" +msgstr " polegada" #: /home/kovid/work/calibre/src/calibre/gui2/catalog/catalog_epub_mobi_ui.py:329 msgid "&Description note" -msgstr "" +msgstr "&Nota de descrição" #: /home/kovid/work/calibre/src/calibre/gui2/catalog/catalog_epub_mobi_ui.py:330 msgid "Custom column source for note to include in Description header area" @@ -5885,35 +6128,36 @@ msgstr "" #: /home/kovid/work/calibre/src/calibre/gui2/catalog/catalog_epub_mobi_ui.py:331 msgid "&Merge with Comments" -msgstr "" +msgstr "&Convergir com Comentários" #: /home/kovid/work/calibre/src/calibre/gui2/catalog/catalog_epub_mobi_ui.py:332 msgid "Additional content merged with Comments during catalog generation" msgstr "" +"Conteúdo adicional convergido com Comentários durante a geração do catálogo" #: /home/kovid/work/calibre/src/calibre/gui2/catalog/catalog_epub_mobi_ui.py:333 msgid "Merge additional content before Comments" -msgstr "" +msgstr "Convergir conteúdo adicional antes dos Comentários" #: /home/kovid/work/calibre/src/calibre/gui2/catalog/catalog_epub_mobi_ui.py:334 msgid "&Before" -msgstr "" +msgstr "&Antes" #: /home/kovid/work/calibre/src/calibre/gui2/catalog/catalog_epub_mobi_ui.py:335 msgid "Merge additional content after Comments" -msgstr "" +msgstr "Convergir conteúdo adicional depois dos Comentários" #: /home/kovid/work/calibre/src/calibre/gui2/catalog/catalog_epub_mobi_ui.py:336 msgid "&After" -msgstr "" +msgstr "&Depois" #: /home/kovid/work/calibre/src/calibre/gui2/catalog/catalog_epub_mobi_ui.py:337 msgid "Separate Comments and additional content with horizontal rule" -msgstr "" +msgstr "Separar Comentários e conteúdo adicional com uma linha horizontal" #: /home/kovid/work/calibre/src/calibre/gui2/catalog/catalog_epub_mobi_ui.py:338 msgid "&Separator" -msgstr "" +msgstr "&Separador" #: /home/kovid/work/calibre/src/calibre/gui2/catalog/catalog_tab_template_ui.py:33 msgid "Tab template for catalog.ui" @@ -5921,15 +6165,15 @@ msgstr "" #: /home/kovid/work/calibre/src/calibre/gui2/comments_editor.py:69 msgid "Bold" -msgstr "" +msgstr "Negrito" #: /home/kovid/work/calibre/src/calibre/gui2/comments_editor.py:70 msgid "Italic" -msgstr "" +msgstr "Itálico" #: /home/kovid/work/calibre/src/calibre/gui2/comments_editor.py:73 msgid "Underline" -msgstr "" +msgstr "Sublinhado" #: /home/kovid/work/calibre/src/calibre/gui2/comments_editor.py:75 msgid "Strikethrough" @@ -5937,91 +6181,94 @@ msgstr "" #: /home/kovid/work/calibre/src/calibre/gui2/comments_editor.py:77 msgid "Superscript" -msgstr "" +msgstr "Elevado" #: /home/kovid/work/calibre/src/calibre/gui2/comments_editor.py:79 msgid "Subscript" -msgstr "" +msgstr "Rebaixado" #: /home/kovid/work/calibre/src/calibre/gui2/comments_editor.py:81 msgid "Ordered list" -msgstr "" +msgstr "Lista ordenada" #: /home/kovid/work/calibre/src/calibre/gui2/comments_editor.py:83 msgid "Unordered list" -msgstr "" +msgstr "Lista desordenada" #: /home/kovid/work/calibre/src/calibre/gui2/comments_editor.py:86 msgid "Align left" -msgstr "" +msgstr "Alinhar à esquerda" #: /home/kovid/work/calibre/src/calibre/gui2/comments_editor.py:88 msgid "Align center" -msgstr "" +msgstr "Alinhar ao centro" #: /home/kovid/work/calibre/src/calibre/gui2/comments_editor.py:90 msgid "Align right" -msgstr "" +msgstr "Alinhar à direita" #: /home/kovid/work/calibre/src/calibre/gui2/comments_editor.py:92 msgid "Align justified" -msgstr "" +msgstr "Alinhar justificado" #: /home/kovid/work/calibre/src/calibre/gui2/comments_editor.py:93 msgid "Undo" -msgstr "" +msgstr "Anular" #: /home/kovid/work/calibre/src/calibre/gui2/comments_editor.py:94 msgid "Redo" -msgstr "" +msgstr "Refazer" #: /home/kovid/work/calibre/src/calibre/gui2/comments_editor.py:95 msgid "Remove formatting" -msgstr "" +msgstr "Remover a formatação" #: /home/kovid/work/calibre/src/calibre/gui2/comments_editor.py:96 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/edit_authors_dialog.py:134 msgid "Copy" msgstr "Copiar" #: /home/kovid/work/calibre/src/calibre/gui2/comments_editor.py:97 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/edit_authors_dialog.py:136 msgid "Paste" -msgstr "" +msgstr "Colar" #: /home/kovid/work/calibre/src/calibre/gui2/comments_editor.py:98 msgid "Cut" -msgstr "" +msgstr "Cortar" #: /home/kovid/work/calibre/src/calibre/gui2/comments_editor.py:100 msgid "Increase Indentation" -msgstr "" +msgstr "Aumentar Identação" #: /home/kovid/work/calibre/src/calibre/gui2/comments_editor.py:102 msgid "Decrease Indentation" -msgstr "" +msgstr "Diminuir Identação" #: /home/kovid/work/calibre/src/calibre/gui2/comments_editor.py:104 msgid "Select all" -msgstr "" +msgstr "Seleccionar tudo" #: /home/kovid/work/calibre/src/calibre/gui2/comments_editor.py:109 msgid "Foreground color" -msgstr "" +msgstr "Cor de 1º plano" #: /home/kovid/work/calibre/src/calibre/gui2/comments_editor.py:114 msgid "Background color" -msgstr "" +msgstr "Cor de fundo" #: /home/kovid/work/calibre/src/calibre/gui2/comments_editor.py:118 msgid "Style text block" -msgstr "" +msgstr "Aplicar estilo ao bloco de texto" #: /home/kovid/work/calibre/src/calibre/gui2/comments_editor.py:120 msgid "Style the selected text block" -msgstr "" +msgstr "Aplicar estilo ao bloco de texto seleccionado" #: /home/kovid/work/calibre/src/calibre/gui2/comments_editor.py:125 -#: /home/kovid/work/calibre/src/calibre/gui2/preferences/behavior.py:32 -#: /home/kovid/work/calibre/src/calibre/gui2/preferences/behavior_ui.py:139 +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/behavior.py:34 +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/behavior.py:36 +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/behavior_ui.py:158 msgid "Normal" msgstr "Normal" @@ -6032,51 +6279,51 @@ msgstr "Normal" #: /home/kovid/work/calibre/src/calibre/gui2/comments_editor.py:130 #: /home/kovid/work/calibre/src/calibre/gui2/comments_editor.py:131 msgid "Heading" -msgstr "" +msgstr "Título" #: /home/kovid/work/calibre/src/calibre/gui2/comments_editor.py:132 msgid "Pre-formatted" -msgstr "" +msgstr "Pré-formatado" #: /home/kovid/work/calibre/src/calibre/gui2/comments_editor.py:133 msgid "Blockquote" -msgstr "" +msgstr "Citação" #: /home/kovid/work/calibre/src/calibre/gui2/comments_editor.py:134 msgid "Address" -msgstr "" +msgstr "Endereço" #: /home/kovid/work/calibre/src/calibre/gui2/comments_editor.py:141 msgid "Insert link" -msgstr "" +msgstr "Inserir link" #: /home/kovid/work/calibre/src/calibre/gui2/comments_editor.py:143 #: /home/kovid/work/calibre/src/calibre/gui2/shortcuts_ui.py:79 #: /home/kovid/work/calibre/src/calibre/gui2/shortcuts_ui.py:84 msgid "Clear" -msgstr "" +msgstr "Limpar" #: /home/kovid/work/calibre/src/calibre/gui2/comments_editor.py:161 msgid "Choose foreground color" -msgstr "" +msgstr "Escolha a cor do 1º plano" #: /home/kovid/work/calibre/src/calibre/gui2/comments_editor.py:167 msgid "Choose background color" -msgstr "" +msgstr "Escolha a cor de fundo" #: /home/kovid/work/calibre/src/calibre/gui2/comments_editor.py:172 msgid "Create link" -msgstr "" +msgstr "Criar ligação" #: /home/kovid/work/calibre/src/calibre/gui2/comments_editor.py:173 msgid "Enter URL" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/comments_editor.py:522 +#: /home/kovid/work/calibre/src/calibre/gui2/comments_editor.py:523 msgid "Normal view" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/comments_editor.py:523 +#: /home/kovid/work/calibre/src/calibre/gui2/comments_editor.py:524 msgid "HTML Source" msgstr "" @@ -6107,71 +6354,75 @@ msgstr "Banda Desenhada de origem" msgid "input" msgstr "ficheiro de origem" -#: /home/kovid/work/calibre/src/calibre/gui2/convert/comic_input_ui.py:101 +#: /home/kovid/work/calibre/src/calibre/gui2/convert/comic_input_ui.py:104 #: /home/kovid/work/calibre/src/calibre/gui2/dialogs/comicconf_ui.py:99 msgid "&Number of Colors:" msgstr "Número de &cores:" -#: /home/kovid/work/calibre/src/calibre/gui2/convert/comic_input_ui.py:102 +#: /home/kovid/work/calibre/src/calibre/gui2/convert/comic_input_ui.py:105 #: /home/kovid/work/calibre/src/calibre/gui2/dialogs/comicconf_ui.py:101 msgid "Disable &normalize" msgstr "Desactivar nor&malizar" -#: /home/kovid/work/calibre/src/calibre/gui2/convert/comic_input_ui.py:103 +#: /home/kovid/work/calibre/src/calibre/gui2/convert/comic_input_ui.py:106 #: /home/kovid/work/calibre/src/calibre/gui2/dialogs/comicconf_ui.py:102 msgid "Keep &aspect ratio" msgstr "Manter a &relação de aspecto" -#: /home/kovid/work/calibre/src/calibre/gui2/convert/comic_input_ui.py:104 +#: /home/kovid/work/calibre/src/calibre/gui2/convert/comic_input_ui.py:107 #: /home/kovid/work/calibre/src/calibre/gui2/dialogs/comicconf_ui.py:103 msgid "Disable &Sharpening" msgstr "Desactivar &nitidez" -#: /home/kovid/work/calibre/src/calibre/gui2/convert/comic_input_ui.py:105 +#: /home/kovid/work/calibre/src/calibre/gui2/convert/comic_input_ui.py:108 #: /home/kovid/work/calibre/src/calibre/gui2/dialogs/comicconf_ui.py:109 msgid "Disable &Trimming" msgstr "Desactivar &aparar" -#: /home/kovid/work/calibre/src/calibre/gui2/convert/comic_input_ui.py:106 +#: /home/kovid/work/calibre/src/calibre/gui2/convert/comic_input_ui.py:109 #: /home/kovid/work/calibre/src/calibre/gui2/dialogs/comicconf_ui.py:108 msgid "&Wide" msgstr "&Largo" -#: /home/kovid/work/calibre/src/calibre/gui2/convert/comic_input_ui.py:107 +#: /home/kovid/work/calibre/src/calibre/gui2/convert/comic_input_ui.py:110 #: /home/kovid/work/calibre/src/calibre/gui2/dialogs/comicconf_ui.py:104 msgid "&Landscape" msgstr "Paisa&gem" -#: /home/kovid/work/calibre/src/calibre/gui2/convert/comic_input_ui.py:108 +#: /home/kovid/work/calibre/src/calibre/gui2/convert/comic_input_ui.py:111 #: /home/kovid/work/calibre/src/calibre/gui2/dialogs/comicconf_ui.py:106 msgid "&Right to left" msgstr "&Direita para a esquerda" -#: /home/kovid/work/calibre/src/calibre/gui2/convert/comic_input_ui.py:109 +#: /home/kovid/work/calibre/src/calibre/gui2/convert/comic_input_ui.py:112 #: /home/kovid/work/calibre/src/calibre/gui2/dialogs/comicconf_ui.py:105 msgid "Don't so&rt" msgstr "Não &ordenar" -#: /home/kovid/work/calibre/src/calibre/gui2/convert/comic_input_ui.py:110 +#: /home/kovid/work/calibre/src/calibre/gui2/convert/comic_input_ui.py:113 #: /home/kovid/work/calibre/src/calibre/gui2/dialogs/comicconf_ui.py:107 msgid "De&speckle" msgstr "Limpar &irregularidades" -#: /home/kovid/work/calibre/src/calibre/gui2/convert/comic_input_ui.py:111 +#: /home/kovid/work/calibre/src/calibre/gui2/convert/comic_input_ui.py:114 msgid "&Disable comic processing" msgstr "Desactivar o &processamento de banda desenhada" -#: /home/kovid/work/calibre/src/calibre/gui2/convert/comic_input_ui.py:112 +#: /home/kovid/work/calibre/src/calibre/gui2/convert/comic_input_ui.py:115 #: /home/kovid/work/calibre/src/calibre/gui2/convert/single_ui.py:120 msgid "&Output format:" msgstr "Formato de &destino:" -#: /home/kovid/work/calibre/src/calibre/gui2/convert/comic_input_ui.py:113 +#: /home/kovid/work/calibre/src/calibre/gui2/convert/comic_input_ui.py:116 msgid "Disable conversion of images to &black and white" -msgstr "" +msgstr "Desactivar conversão de imagens para &preto e branco" -#: /home/kovid/work/calibre/src/calibre/gui2/convert/comic_input_ui.py:114 +#: /home/kovid/work/calibre/src/calibre/gui2/convert/comic_input_ui.py:117 msgid "Override image &size:" +msgstr "Sobrepor &tamanho da imagem:" + +#: /home/kovid/work/calibre/src/calibre/gui2/convert/comic_input_ui.py:118 +msgid "Don't add links to &pages to the Table of Contents for CBC files" msgstr "" #: /home/kovid/work/calibre/src/calibre/gui2/convert/debug.py:19 @@ -6232,11 +6483,11 @@ msgstr "Nenhuma &capa predefinida" #: /home/kovid/work/calibre/src/calibre/gui2/convert/epub_output_ui.py:59 msgid "No &SVG cover" -msgstr "" +msgstr "Não existe capa &SVG" #: /home/kovid/work/calibre/src/calibre/gui2/convert/epub_output_ui.py:60 msgid "Preserve cover &aspect ratio" -msgstr "" +msgstr "Preservar relação largura/altura da capa" #: /home/kovid/work/calibre/src/calibre/gui2/convert/epub_output_ui.py:61 msgid "Split files &larger than:" @@ -6262,10 +6513,14 @@ msgstr "Não inserir um &Índice no início do livro." msgid "FB2 Output" msgstr "Ficheiro de destino FB2" -#: /home/kovid/work/calibre/src/calibre/gui2/convert/fb2_output_ui.py:39 +#: /home/kovid/work/calibre/src/calibre/gui2/convert/fb2_output_ui.py:45 msgid "Sectionize:" msgstr "" +#: /home/kovid/work/calibre/src/calibre/gui2/convert/fb2_output_ui.py:46 +msgid "Genre" +msgstr "" + #: /home/kovid/work/calibre/src/calibre/gui2/convert/font_key_ui.py:104 msgid "Font rescaling wizard" msgstr "Assistente de alteração proporcional dos tipos de letra" @@ -6340,10 +6595,12 @@ msgid "" "Heuristic\n" "Processing" msgstr "" +"Processamento\n" +"Heurístico" #: /home/kovid/work/calibre/src/calibre/gui2/convert/heuristics.py:16 msgid "Modify the document text and structure using common patterns." -msgstr "" +msgstr "Modificar o texto e estrutura do documento usando padrões comuns." #: /home/kovid/work/calibre/src/calibre/gui2/convert/heuristics_ui.py:113 msgid "" @@ -6359,11 +6616,11 @@ msgstr "" #: /home/kovid/work/calibre/src/calibre/gui2/convert/heuristics_ui.py:114 msgid "Enable &heuristic processing" -msgstr "" +msgstr "Activar &processamento heurístico" #: /home/kovid/work/calibre/src/calibre/gui2/convert/heuristics_ui.py:115 msgid "Heuristic Processing" -msgstr "" +msgstr "Processamento heurístico" #: /home/kovid/work/calibre/src/calibre/gui2/convert/heuristics_ui.py:116 msgid "Unwrap lines" @@ -6379,15 +6636,15 @@ msgstr "" #: /home/kovid/work/calibre/src/calibre/gui2/convert/heuristics_ui.py:119 msgid "Renumber sequences of

      or

      tags to prevent splitting" -msgstr "" +msgstr "Re-enumerar sequências de tags

      e

      para prevenir divisão" #: /home/kovid/work/calibre/src/calibre/gui2/convert/heuristics_ui.py:120 msgid "Delete blank lines between paragraphs" -msgstr "" +msgstr "Apagar linhas em brancon entre parágrafos" #: /home/kovid/work/calibre/src/calibre/gui2/convert/heuristics_ui.py:121 msgid "Ensure scene breaks are consistently formatted" -msgstr "" +msgstr "Assegurar que as quebras de cena são formatadas de forma consistente" #: /home/kovid/work/calibre/src/calibre/gui2/convert/heuristics_ui.py:122 msgid "Replace soft scene &breaks:" @@ -6395,7 +6652,7 @@ msgstr "" #: /home/kovid/work/calibre/src/calibre/gui2/convert/heuristics_ui.py:123 msgid "Remove unnecessary hyphens" -msgstr "" +msgstr "Remover hífens desnecessários" #: /home/kovid/work/calibre/src/calibre/gui2/convert/heuristics_ui.py:124 msgid "Italicize common words and patterns" @@ -6405,6 +6662,18 @@ msgstr "" msgid "Replace entity indents with CSS indents" msgstr "" +#: /home/kovid/work/calibre/src/calibre/gui2/convert/htmlz_output.py:14 +msgid "HTMLZ Output" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/convert/htmlz_output_ui.py:45 +msgid "How to handle CSS" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/convert/htmlz_output_ui.py:46 +msgid "How to handle class based CSS" +msgstr "" + #: /home/kovid/work/calibre/src/calibre/gui2/convert/look_and_feel.py:16 msgid "Look & Feel" msgstr "Aparência" @@ -6415,15 +6684,15 @@ msgstr "Controlar o aspecto do ficheiro de destino" #: /home/kovid/work/calibre/src/calibre/gui2/convert/look_and_feel.py:31 msgid "Original" -msgstr "" +msgstr "Original" #: /home/kovid/work/calibre/src/calibre/gui2/convert/look_and_feel.py:32 msgid "Left align" -msgstr "" +msgstr "Alinhar à esquerda" #: /home/kovid/work/calibre/src/calibre/gui2/convert/look_and_feel.py:33 msgid "Justify text" -msgstr "" +msgstr "Justificar texto" #: /home/kovid/work/calibre/src/calibre/gui2/convert/look_and_feel_ui.py:138 msgid "&Disable font size rescaling" @@ -6495,7 +6764,7 @@ msgstr "" #: /home/kovid/work/calibre/src/calibre/gui2/convert/look_and_feel_ui.py:158 msgid "Minimum &line height:" -msgstr "" +msgstr "Altura de linha mínima" #: /home/kovid/work/calibre/src/calibre/gui2/convert/look_and_feel_ui.py:159 msgid " %" @@ -6555,7 +6824,7 @@ msgid "&Monospaced font family:" msgstr "Família de tipos de letra &Monospace:" #: /home/kovid/work/calibre/src/calibre/gui2/convert/metadata.py:47 -#: /home/kovid/work/calibre/src/calibre/gui2/viewer/main.py:115 +#: /home/kovid/work/calibre/src/calibre/gui2/viewer/main.py:117 #: /home/kovid/work/calibre/src/calibre/gui2/viewer/main_ui.py:200 msgid "Metadata" msgstr "Metadados" @@ -6569,49 +6838,41 @@ msgstr "" "quanto possível." #: /home/kovid/work/calibre/src/calibre/gui2/convert/metadata.py:180 -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single.py:171 -#: /home/kovid/work/calibre/src/calibre/gui2/metadata/basic_widgets.py:643 +#: /home/kovid/work/calibre/src/calibre/gui2/metadata/basic_widgets.py:726 msgid "Choose cover for " msgstr "Escolher a capa para " #: /home/kovid/work/calibre/src/calibre/gui2/convert/metadata.py:187 -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single.py:178 -#: /home/kovid/work/calibre/src/calibre/gui2/metadata/basic_widgets.py:651 +#: /home/kovid/work/calibre/src/calibre/gui2/metadata/basic_widgets.py:734 msgid "Cannot read" msgstr "É impossível ler" #: /home/kovid/work/calibre/src/calibre/gui2/convert/metadata.py:188 -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single.py:179 -#: /home/kovid/work/calibre/src/calibre/gui2/metadata/basic_widgets.py:652 +#: /home/kovid/work/calibre/src/calibre/gui2/metadata/basic_widgets.py:735 msgid "You do not have permission to read the file: " msgstr "Não tem permissão para ler o ficheiro: " #: /home/kovid/work/calibre/src/calibre/gui2/convert/metadata.py:196 #: /home/kovid/work/calibre/src/calibre/gui2/convert/metadata.py:203 -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single.py:187 -#: /home/kovid/work/calibre/src/calibre/gui2/metadata/basic_widgets.py:660 +#: /home/kovid/work/calibre/src/calibre/gui2/metadata/basic_widgets.py:743 msgid "Error reading file" msgstr "Erro ao ler o ficheiro" #: /home/kovid/work/calibre/src/calibre/gui2/convert/metadata.py:197 -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single.py:188 -#: /home/kovid/work/calibre/src/calibre/gui2/metadata/basic_widgets.py:661 +#: /home/kovid/work/calibre/src/calibre/gui2/metadata/basic_widgets.py:744 msgid "

      There was an error reading from file:
      " msgstr "

      Houve um erro ao ler do ficheiro:
      " #: /home/kovid/work/calibre/src/calibre/gui2/convert/metadata.py:204 -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single.py:196 -#: /home/kovid/work/calibre/src/calibre/gui2/metadata/basic_widgets.py:671 +#: /home/kovid/work/calibre/src/calibre/gui2/metadata/basic_widgets.py:754 msgid " is not a valid picture" msgstr " não é uma imagem válida" #: /home/kovid/work/calibre/src/calibre/gui2/convert/metadata_ui.py:159 -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single_ui.py:446 msgid "Book Cover" msgstr "Capa do livro" #: /home/kovid/work/calibre/src/calibre/gui2/convert/metadata_ui.py:160 -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single_ui.py:447 msgid "Change &cover image:" msgstr "Alterar a imagem da &capa:" @@ -6624,19 +6885,16 @@ msgid "Use cover from &source file" msgstr "Usar a capa do ficheiro de &origem" #: /home/kovid/work/calibre/src/calibre/gui2/convert/metadata_ui.py:164 -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single_ui.py:408 msgid "&Title: " msgstr "&Título: " #: /home/kovid/work/calibre/src/calibre/gui2/convert/metadata_ui.py:165 -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single_ui.py:409 -#: /home/kovid/work/calibre/src/calibre/gui2/metadata/basic_widgets.py:60 +#: /home/kovid/work/calibre/src/calibre/gui2/metadata/basic_widgets.py:72 msgid "Change the title of this book" msgstr "Alterar o título deste livro" #: /home/kovid/work/calibre/src/calibre/gui2/convert/metadata_ui.py:166 -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:499 -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single_ui.py:420 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:525 msgid "&Author(s): " msgstr "&Autor(es): " @@ -6653,19 +6911,17 @@ msgstr "" "vírgula" #: /home/kovid/work/calibre/src/calibre/gui2/convert/metadata_ui.py:169 -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:509 -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single_ui.py:428 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:535 msgid "&Publisher: " msgstr "&Editora: " #: /home/kovid/work/calibre/src/calibre/gui2/convert/metadata_ui.py:170 -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single_ui.py:429 msgid "Ta&gs: " msgstr "Eti&quetas: " #: /home/kovid/work/calibre/src/calibre/gui2/convert/metadata_ui.py:171 -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:511 -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single_ui.py:430 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:537 +#: /home/kovid/work/calibre/src/calibre/gui2/metadata/basic_widgets.py:909 msgid "" "Tags categorize the book. This is particularly useful while searching. " "

      They can be any words or phrases, separated by commas." @@ -6674,25 +6930,21 @@ msgstr "" "

      Podem ser quaisquer palavras ou frases, separadas por vírgulas." #: /home/kovid/work/calibre/src/calibre/gui2/convert/metadata_ui.py:172 -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:518 -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single_ui.py:433 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:544 #: /home/kovid/work/calibre/src/calibre/gui2/dialogs/search_ui.py:214 -#: /home/kovid/work/calibre/src/calibre/gui2/metadata/basic_widgets.py:293 +#: /home/kovid/work/calibre/src/calibre/gui2/metadata/basic_widgets.py:355 msgid "&Series:" msgstr "&Série:" #: /home/kovid/work/calibre/src/calibre/gui2/convert/metadata_ui.py:173 #: /home/kovid/work/calibre/src/calibre/gui2/convert/metadata_ui.py:174 -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:519 -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:520 -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single_ui.py:434 -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single_ui.py:435 -#: /home/kovid/work/calibre/src/calibre/gui2/metadata/basic_widgets.py:292 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:545 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:546 +#: /home/kovid/work/calibre/src/calibre/gui2/metadata/basic_widgets.py:354 msgid "List of known series. You can add new series." msgstr "Lista de séries conhecidas. Pode adicionar uma nova série." #: /home/kovid/work/calibre/src/calibre/gui2/convert/metadata_ui.py:175 -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single_ui.py:438 msgid "Book " msgstr "Livro " @@ -6701,6 +6953,7 @@ msgid "MOBI Output" msgstr "Ficheiro de destino MOBI" #: /home/kovid/work/calibre/src/calibre/gui2/convert/mobi_output.py:44 +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/behavior.py:64 msgid "Default" msgstr "Predefinição" @@ -6726,7 +6979,7 @@ msgstr "Não inserir um Índice no livro" #: /home/kovid/work/calibre/src/calibre/gui2/convert/mobi_output_ui.py:80 msgid "Kindle options" -msgstr "" +msgstr "Opções do Kindle" #: /home/kovid/work/calibre/src/calibre/gui2/convert/mobi_output_ui.py:81 msgid "Periodical masthead font:" @@ -6738,7 +6991,7 @@ msgstr "Etiqueta de Doc. Pessoal:" #: /home/kovid/work/calibre/src/calibre/gui2/convert/mobi_output_ui.py:83 msgid "Ignore &margins" -msgstr "" +msgstr "Ignorar &margens" #: /home/kovid/work/calibre/src/calibre/gui2/convert/page_setup.py:35 msgid "Page Setup" @@ -6789,13 +7042,14 @@ msgid "PDB Output" msgstr "Ficheiro de destino PDB" #: /home/kovid/work/calibre/src/calibre/gui2/convert/pdb_output_ui.py:48 +#: /home/kovid/work/calibre/src/calibre/gui2/store/search/adv_search_builder_ui.py:195 msgid "&Format:" msgstr "&Formato:" #: /home/kovid/work/calibre/src/calibre/gui2/convert/pdb_output_ui.py:49 #: /home/kovid/work/calibre/src/calibre/gui2/convert/pmlz_output_ui.py:47 #: /home/kovid/work/calibre/src/calibre/gui2/convert/rb_output_ui.py:34 -#: /home/kovid/work/calibre/src/calibre/gui2/convert/txt_output_ui.py:92 +#: /home/kovid/work/calibre/src/calibre/gui2/convert/txt_output_ui.py:95 msgid "&Inline TOC" msgstr "Índice em &linha" @@ -6830,7 +7084,7 @@ msgstr "&Orientação:" #: /home/kovid/work/calibre/src/calibre/gui2/convert/pdf_output_ui.py:50 msgid "Preserve &aspect ratio of cover" -msgstr "" +msgstr "Preservar relação largura/altura da capa" #: /home/kovid/work/calibre/src/calibre/gui2/convert/pml_output.py:14 msgid "PMLZ Output" @@ -6838,7 +7092,7 @@ msgstr "" #: /home/kovid/work/calibre/src/calibre/gui2/convert/pmlz_output_ui.py:48 msgid "Do not reduce image size and depth" -msgstr "" +msgstr "Não reduzir o tamanho e profundidade das imagens" #: /home/kovid/work/calibre/src/calibre/gui2/convert/rb_output.py:14 msgid "RB Output" @@ -6867,30 +7121,32 @@ msgid "Regex:" msgstr "Regex:" #: /home/kovid/work/calibre/src/calibre/gui2/convert/regex_builder_ui.py:92 -#: /home/kovid/work/calibre/src/calibre/gui2/filename_pattern_ui.py:122 +#: /home/kovid/work/calibre/src/calibre/gui2/filename_pattern_ui.py:136 msgid "Test" msgstr "Teste" #: /home/kovid/work/calibre/src/calibre/gui2/convert/regex_builder_ui.py:93 msgid "Occurrences:" -msgstr "" +msgstr "Ocorrências:" #: /home/kovid/work/calibre/src/calibre/gui2/convert/regex_builder_ui.py:94 +#: /home/kovid/work/calibre/src/calibre/gui2/store/mobileread/store_dialog_ui.py:78 +#: /home/kovid/work/calibre/src/calibre/gui2/store/mobileread_store_dialog_ui.py:64 msgid "0" -msgstr "" +msgstr "0" #: /home/kovid/work/calibre/src/calibre/gui2/convert/regex_builder_ui.py:95 msgid "Goto:" msgstr "" #: /home/kovid/work/calibre/src/calibre/gui2/convert/regex_builder_ui.py:96 -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/book_info_ui.py:81 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/book_info_ui.py:72 #: /home/kovid/work/calibre/src/calibre/gui2/preferences/plugins_ui.py:89 msgid "&Previous" msgstr "&Anterior" #: /home/kovid/work/calibre/src/calibre/gui2/convert/regex_builder_ui.py:97 -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/book_info_ui.py:82 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/book_info_ui.py:73 #: /home/kovid/work/calibre/src/calibre/gui2/preferences/plugins_ui.py:88 msgid "&Next" msgstr "&Seguinte" @@ -6899,32 +7155,32 @@ msgstr "&Seguinte" msgid "Preview" msgstr "Pré-visualizar" -#: /home/kovid/work/calibre/src/calibre/gui2/convert/search_and_replace.py:15 +#: /home/kovid/work/calibre/src/calibre/gui2/convert/search_and_replace.py:17 msgid "" "Search\n" "&\n" "Replace" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/convert/search_and_replace.py:28 -#: /home/kovid/work/calibre/src/calibre/gui2/convert/search_and_replace.py:31 -#: /home/kovid/work/calibre/src/calibre/gui2/convert/search_and_replace.py:34 +#: /home/kovid/work/calibre/src/calibre/gui2/convert/search_and_replace.py:30 +#: /home/kovid/work/calibre/src/calibre/gui2/convert/search_and_replace.py:33 +#: /home/kovid/work/calibre/src/calibre/gui2/convert/search_and_replace.py:36 msgid "&Search Regular Expression" -msgstr "" +msgstr "&Pesquisar Expressão regular" -#: /home/kovid/work/calibre/src/calibre/gui2/convert/search_and_replace.py:71 -#: /home/kovid/work/calibre/src/calibre/gui2/widgets.py:99 +#: /home/kovid/work/calibre/src/calibre/gui2/convert/search_and_replace.py:73 +#: /home/kovid/work/calibre/src/calibre/gui2/widgets.py:101 msgid "Invalid regular expression" msgstr "Expressão regular inválida" -#: /home/kovid/work/calibre/src/calibre/gui2/convert/search_and_replace.py:72 -#: /home/kovid/work/calibre/src/calibre/gui2/widgets.py:100 +#: /home/kovid/work/calibre/src/calibre/gui2/convert/search_and_replace.py:74 +#: /home/kovid/work/calibre/src/calibre/gui2/widgets.py:102 msgid "Invalid regular expression: %s" msgstr "Expressão regular inválida: %s" #: /home/kovid/work/calibre/src/calibre/gui2/convert/search_and_replace_ui.py:148 msgid "First expression" -msgstr "" +msgstr "Primeira expressão" #: /home/kovid/work/calibre/src/calibre/gui2/convert/search_and_replace_ui.py:149 #: /home/kovid/work/calibre/src/calibre/gui2/convert/search_and_replace_ui.py:151 @@ -6934,11 +7190,11 @@ msgstr "" #: /home/kovid/work/calibre/src/calibre/gui2/convert/search_and_replace_ui.py:150 msgid "Second Expression" -msgstr "" +msgstr "Segunda Expressão" #: /home/kovid/work/calibre/src/calibre/gui2/convert/search_and_replace_ui.py:152 msgid "Third expression" -msgstr "" +msgstr "Terceira Expressão" #: /home/kovid/work/calibre/src/calibre/gui2/convert/search_and_replace_ui.py:154 msgid "" @@ -6958,10 +7214,13 @@ msgid "Options specific to the input format." msgstr "Opções específicas ao formato de origem." #: /home/kovid/work/calibre/src/calibre/gui2/convert/single_ui.py:117 -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/book_info_ui.py:76 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/book_info_ui.py:69 #: /home/kovid/work/calibre/src/calibre/gui2/dialogs/comicconf_ui.py:96 #: /home/kovid/work/calibre/src/calibre/gui2/dialogs/message_box_ui.py:52 #: /home/kovid/work/calibre/src/calibre/gui2/dialogs/progress_ui.py:53 +#: /home/kovid/work/calibre/src/calibre/gui2/store/mobileread/cache_progress_dialog_ui.py:50 +#: /home/kovid/work/calibre/src/calibre/gui2/store/mobileread/store_dialog_ui.py:73 +#: /home/kovid/work/calibre/src/calibre/gui2/store/mobileread_store_dialog_ui.py:61 msgid "Dialog" msgstr "Caixa de diálogo" @@ -6979,15 +7238,15 @@ msgstr "" #: /home/kovid/work/calibre/src/calibre/gui2/convert/snb_output_ui.py:43 msgid "Hide chapter name" -msgstr "" +msgstr "Esconder nome do capítulo" #: /home/kovid/work/calibre/src/calibre/gui2/convert/snb_output_ui.py:44 msgid "Don't indent the first line for each paragraph" -msgstr "" +msgstr "Não identar a primeira linha de cada parágrafo" #: /home/kovid/work/calibre/src/calibre/gui2/convert/snb_output_ui.py:45 msgid "Insert empty line between paragraphs" -msgstr "" +msgstr "Inserir linha em branco entre parágrafos" #: /home/kovid/work/calibre/src/calibre/gui2/convert/snb_output_ui.py:46 msgid "Optimize for full-sceen view " @@ -7027,19 +7286,19 @@ msgstr "XPath inválido" msgid "The XPath expression %s is invalid." msgstr "A expressão XPath %s é inválida." -#: /home/kovid/work/calibre/src/calibre/gui2/convert/structure_detection_ui.py:57 +#: /home/kovid/work/calibre/src/calibre/gui2/convert/structure_detection_ui.py:60 msgid "Chapter &mark:" msgstr "&Marca do capítulo:" -#: /home/kovid/work/calibre/src/calibre/gui2/convert/structure_detection_ui.py:58 +#: /home/kovid/work/calibre/src/calibre/gui2/convert/structure_detection_ui.py:61 msgid "Remove first &image" msgstr "Remover a &primeira imagem" -#: /home/kovid/work/calibre/src/calibre/gui2/convert/structure_detection_ui.py:59 +#: /home/kovid/work/calibre/src/calibre/gui2/convert/structure_detection_ui.py:62 msgid "Insert &metadata as page at start of book" msgstr "&Inserir os metadados como uma página no início do livro" -#: /home/kovid/work/calibre/src/calibre/gui2/convert/structure_detection_ui.py:60 +#: /home/kovid/work/calibre/src/calibre/gui2/convert/structure_detection_ui.py:63 msgid "" "The header and footer removal options have been replaced by the Search & " "Replace options. Click the Search & Replace category in the bar to the left " @@ -7047,6 +7306,10 @@ msgid "" "header/footer removal regexps into the search field." msgstr "" +#: /home/kovid/work/calibre/src/calibre/gui2/convert/structure_detection_ui.py:64 +msgid "Remove &fake margins" +msgstr "" + #: /home/kovid/work/calibre/src/calibre/gui2/convert/toc.py:16 msgid "" "Table of\n" @@ -7095,7 +7358,7 @@ msgstr "Ficheiro de origem TXT" #: /home/kovid/work/calibre/src/calibre/gui2/convert/txt_input_ui.py:92 msgid "Structure" -msgstr "" +msgstr "Estrutura" #: /home/kovid/work/calibre/src/calibre/gui2/convert/txt_input_ui.py:93 msgid "Paragraph style:" @@ -7103,19 +7366,19 @@ msgstr "" #: /home/kovid/work/calibre/src/calibre/gui2/convert/txt_input_ui.py:94 msgid "Formatting style:" -msgstr "" +msgstr "Estilo de formatação" #: /home/kovid/work/calibre/src/calibre/gui2/convert/txt_input_ui.py:95 msgid "Common" -msgstr "" +msgstr "Comum" #: /home/kovid/work/calibre/src/calibre/gui2/convert/txt_input_ui.py:96 msgid "Preserve &spaces" -msgstr "" +msgstr "Preservar &espaços" #: /home/kovid/work/calibre/src/calibre/gui2/convert/txt_input_ui.py:97 msgid "Remove indents at the beginning of lines" -msgstr "" +msgstr "Remover identações no início das linhas" #: /home/kovid/work/calibre/src/calibre/gui2/convert/txt_input_ui.py:98 msgid "Markdown" @@ -7133,59 +7396,62 @@ msgstr "" #: /home/kovid/work/calibre/src/calibre/gui2/convert/txt_input_ui.py:100 msgid "Do not insert Table of Contents into output text when using markdown" -msgstr "" +msgstr "Não inserir Índice no texto gerado usando Markdown" #: /home/kovid/work/calibre/src/calibre/gui2/convert/txt_output.py:13 msgid "TXT Output" msgstr "Ficheiro de destino TXT" -#: /home/kovid/work/calibre/src/calibre/gui2/convert/txt_output_ui.py:85 +#: /home/kovid/work/calibre/src/calibre/gui2/convert/txt_output_ui.py:88 msgid "General" msgstr "Geral" -#: /home/kovid/work/calibre/src/calibre/gui2/convert/txt_output_ui.py:86 +#: /home/kovid/work/calibre/src/calibre/gui2/convert/txt_output_ui.py:89 msgid "Output &Encoding:" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/convert/txt_output_ui.py:87 +#: /home/kovid/work/calibre/src/calibre/gui2/convert/txt_output_ui.py:90 msgid "&Line ending style:" msgstr "Estilo de fim de &linha:" -#: /home/kovid/work/calibre/src/calibre/gui2/convert/txt_output_ui.py:88 +#: /home/kovid/work/calibre/src/calibre/gui2/convert/txt_output_ui.py:91 msgid "&Formatting:" -msgstr "" +msgstr "&Formatação:" -#: /home/kovid/work/calibre/src/calibre/gui2/convert/txt_output_ui.py:89 +#: /home/kovid/work/calibre/src/calibre/gui2/convert/txt_output_ui.py:92 msgid "Plain" -msgstr "" +msgstr "Simples" -#: /home/kovid/work/calibre/src/calibre/gui2/convert/txt_output_ui.py:90 +#: /home/kovid/work/calibre/src/calibre/gui2/convert/txt_output_ui.py:93 msgid "&Maximum line length:" msgstr "Comprimento &máximo da linha:" -#: /home/kovid/work/calibre/src/calibre/gui2/convert/txt_output_ui.py:91 +#: /home/kovid/work/calibre/src/calibre/gui2/convert/txt_output_ui.py:94 msgid "Force maximum line length" -msgstr "" +msgstr "Forçar tamanho máximo de linha" -#: /home/kovid/work/calibre/src/calibre/gui2/convert/txt_output_ui.py:93 +#: /home/kovid/work/calibre/src/calibre/gui2/convert/txt_output_ui.py:96 msgid "Markdown, Textile" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/convert/txt_output_ui.py:94 +#: /home/kovid/work/calibre/src/calibre/gui2/convert/txt_output_ui.py:97 msgid "Do not remove links ( tags) before processing" msgstr "Não remover links ( etiquetas) antes de começar o processamento" -#: /home/kovid/work/calibre/src/calibre/gui2/convert/txt_output_ui.py:95 +#: /home/kovid/work/calibre/src/calibre/gui2/convert/txt_output_ui.py:98 msgid "Do not remove image references before processing" +msgstr "Não remover referências a imagens antes do processamento" + +#: /home/kovid/work/calibre/src/calibre/gui2/convert/txt_output_ui.py:99 +msgid "Keep text color, when possible" msgstr "" #: /home/kovid/work/calibre/src/calibre/gui2/convert/txtz_output.py:12 msgid "TXTZ Output" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/convert/xexp_edit_ui.py:54 -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/book_info_ui.py:77 -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/book_info_ui.py:78 +#: /home/kovid/work/calibre/src/calibre/gui2/convert/xexp_edit_ui.py:55 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/book_info_ui.py:70 #: /home/kovid/work/calibre/src/calibre/gui2/dialogs/choose_format_ui.py:46 #: /home/kovid/work/calibre/src/calibre/gui2/dialogs/confirm_delete_ui.py:54 #: /home/kovid/work/calibre/src/calibre/gui2/dialogs/password_ui.py:62 @@ -7194,7 +7460,7 @@ msgstr "" msgid "TextLabel" msgstr "Rótulo do Texto" -#: /home/kovid/work/calibre/src/calibre/gui2/convert/xexp_edit_ui.py:55 +#: /home/kovid/work/calibre/src/calibre/gui2/convert/xexp_edit_ui.py:56 msgid "Use a wizard to help construct the Regular expression" msgstr "" @@ -7272,205 +7538,203 @@ msgid "" "href=\"http://calibre-ebook.com/user_manual/xpath.html\">XPath Tutorial." msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/cover_flow.py:118 +#: /home/kovid/work/calibre/src/calibre/gui2/cover_flow.py:128 msgid "Browse by covers" msgstr "Navegar pelas capas" -#: /home/kovid/work/calibre/src/calibre/gui2/cover_flow.py:149 +#: /home/kovid/work/calibre/src/calibre/gui2/cover_flow.py:158 msgid "Cover browser could not be loaded" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/custom_column_widgets.py:64 -#: /home/kovid/work/calibre/src/calibre/gui2/custom_column_widgets.py:89 -#: /home/kovid/work/calibre/src/calibre/gui2/custom_column_widgets.py:113 -#: /home/kovid/work/calibre/src/calibre/gui2/custom_column_widgets.py:150 -#: /home/kovid/work/calibre/src/calibre/gui2/custom_column_widgets.py:184 -#: /home/kovid/work/calibre/src/calibre/gui2/custom_column_widgets.py:294 -#: /home/kovid/work/calibre/src/calibre/gui2/custom_column_widgets.py:558 -#: /home/kovid/work/calibre/src/calibre/gui2/custom_column_widgets.py:599 -#: /home/kovid/work/calibre/src/calibre/gui2/custom_column_widgets.py:622 -#: /home/kovid/work/calibre/src/calibre/gui2/custom_column_widgets.py:673 +#: /home/kovid/work/calibre/src/calibre/gui2/custom_column_widgets.py:63 +#: /home/kovid/work/calibre/src/calibre/gui2/custom_column_widgets.py:88 +#: /home/kovid/work/calibre/src/calibre/gui2/custom_column_widgets.py:112 +#: /home/kovid/work/calibre/src/calibre/gui2/custom_column_widgets.py:149 +#: /home/kovid/work/calibre/src/calibre/gui2/custom_column_widgets.py:183 +#: /home/kovid/work/calibre/src/calibre/gui2/custom_column_widgets.py:302 +#: /home/kovid/work/calibre/src/calibre/gui2/custom_column_widgets.py:567 +#: /home/kovid/work/calibre/src/calibre/gui2/custom_column_widgets.py:608 +#: /home/kovid/work/calibre/src/calibre/gui2/custom_column_widgets.py:631 +#: /home/kovid/work/calibre/src/calibre/gui2/custom_column_widgets.py:682 #: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk.py:306 #: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk.py:311 -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single.py:503 -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single.py:504 -#: /home/kovid/work/calibre/src/calibre/gui2/library/delegates.py:114 -#: /home/kovid/work/calibre/src/calibre/gui2/library/delegates.py:134 -#: /home/kovid/work/calibre/src/calibre/gui2/library/delegates.py:216 -#: /home/kovid/work/calibre/src/calibre/gui2/library/delegates.py:249 -#: /home/kovid/work/calibre/src/calibre/gui2/library/delegates.py:253 -#: /home/kovid/work/calibre/src/calibre/gui2/metadata/basic_widgets.py:994 +#: /home/kovid/work/calibre/src/calibre/gui2/library/delegates.py:120 +#: /home/kovid/work/calibre/src/calibre/gui2/library/delegates.py:140 +#: /home/kovid/work/calibre/src/calibre/gui2/library/delegates.py:222 +#: /home/kovid/work/calibre/src/calibre/gui2/library/delegates.py:255 +#: /home/kovid/work/calibre/src/calibre/gui2/library/delegates.py:259 +#: /home/kovid/work/calibre/src/calibre/gui2/metadata/basic_widgets.py:1117 msgid "Undefined" -msgstr "" +msgstr "Não definido" -#: /home/kovid/work/calibre/src/calibre/gui2/custom_column_widgets.py:127 -#: /home/kovid/work/calibre/src/calibre/gui2/custom_column_widgets.py:630 +#: /home/kovid/work/calibre/src/calibre/gui2/custom_column_widgets.py:126 +#: /home/kovid/work/calibre/src/calibre/gui2/custom_column_widgets.py:639 msgid "star(s)" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/custom_column_widgets.py:128 -#: /home/kovid/work/calibre/src/calibre/gui2/custom_column_widgets.py:631 +#: /home/kovid/work/calibre/src/calibre/gui2/custom_column_widgets.py:127 +#: /home/kovid/work/calibre/src/calibre/gui2/custom_column_widgets.py:640 msgid "Unrated" -msgstr "" +msgstr "Sem avaliação" -#: /home/kovid/work/calibre/src/calibre/gui2/custom_column_widgets.py:171 -#: /home/kovid/work/calibre/src/calibre/gui2/custom_column_widgets.py:660 +#: /home/kovid/work/calibre/src/calibre/gui2/custom_column_widgets.py:170 +#: /home/kovid/work/calibre/src/calibre/gui2/custom_column_widgets.py:669 msgid "Set '%s' to today" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/custom_column_widgets.py:173 -#: /home/kovid/work/calibre/src/calibre/gui2/custom_column_widgets.py:662 +#: /home/kovid/work/calibre/src/calibre/gui2/custom_column_widgets.py:172 +#: /home/kovid/work/calibre/src/calibre/gui2/custom_column_widgets.py:671 msgid "Clear '%s'" -msgstr "" +msgstr "Limpar '%s'" -#: /home/kovid/work/calibre/src/calibre/gui2/custom_column_widgets.py:290 +#: /home/kovid/work/calibre/src/calibre/gui2/custom_column_widgets.py:298 msgid " index:" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/custom_column_widgets.py:359 +#: /home/kovid/work/calibre/src/calibre/gui2/custom_column_widgets.py:367 msgid "" "The enumeration \"{0}\" contains an invalid value that will be set to the " "default" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/custom_column_widgets.py:513 +#: /home/kovid/work/calibre/src/calibre/gui2/custom_column_widgets.py:522 msgid "Apply changes" -msgstr "" +msgstr "Aplicar alterações" -#: /home/kovid/work/calibre/src/calibre/gui2/custom_column_widgets.py:706 +#: /home/kovid/work/calibre/src/calibre/gui2/custom_column_widgets.py:715 msgid "Remove series" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/custom_column_widgets.py:709 +#: /home/kovid/work/calibre/src/calibre/gui2/custom_column_widgets.py:718 msgid "Automatically number books" -msgstr "" +msgstr "Numerar livros automaticamente" -#: /home/kovid/work/calibre/src/calibre/gui2/custom_column_widgets.py:712 +#: /home/kovid/work/calibre/src/calibre/gui2/custom_column_widgets.py:721 msgid "Force numbers to start with " msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/custom_column_widgets.py:783 +#: /home/kovid/work/calibre/src/calibre/gui2/custom_column_widgets.py:792 msgid "" "The enumeration \"{0}\" contains invalid values that will not appear in the " "list" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/custom_column_widgets.py:826 +#: /home/kovid/work/calibre/src/calibre/gui2/custom_column_widgets.py:836 msgid "Remove all tags" msgstr "Remover todas as etiquetas" -#: /home/kovid/work/calibre/src/calibre/gui2/custom_column_widgets.py:846 +#: /home/kovid/work/calibre/src/calibre/gui2/custom_column_widgets.py:856 msgid "tags to add" msgstr "Etiquetas a adicionar" -#: /home/kovid/work/calibre/src/calibre/gui2/custom_column_widgets.py:852 +#: /home/kovid/work/calibre/src/calibre/gui2/custom_column_widgets.py:863 msgid "tags to remove" msgstr "etiquetas a serem removidas" -#: /home/kovid/work/calibre/src/calibre/gui2/device.py:45 -#: /home/kovid/work/calibre/src/calibre/utils/ipc/job.py:136 +#: /home/kovid/work/calibre/src/calibre/gui2/device.py:43 +#: /home/kovid/work/calibre/src/calibre/utils/ipc/job.py:144 msgid "No details available." msgstr "Nenhuns detalhes disponíveis." -#: /home/kovid/work/calibre/src/calibre/gui2/device.py:185 +#: /home/kovid/work/calibre/src/calibre/gui2/device.py:168 msgid "Device no longer connected." msgstr "O aparelho já não está ligado." -#: /home/kovid/work/calibre/src/calibre/gui2/device.py:303 +#: /home/kovid/work/calibre/src/calibre/gui2/device.py:291 msgid "Get device information" msgstr "Ir buscar informação sobre o aparelho" -#: /home/kovid/work/calibre/src/calibre/gui2/device.py:314 +#: /home/kovid/work/calibre/src/calibre/gui2/device.py:305 msgid "Get list of books on device" msgstr "Listar os livros presentes no aparelho" -#: /home/kovid/work/calibre/src/calibre/gui2/device.py:324 +#: /home/kovid/work/calibre/src/calibre/gui2/device.py:315 msgid "Get annotations from device" -msgstr "" +msgstr "Obter anotações do dispositivo" -#: /home/kovid/work/calibre/src/calibre/gui2/device.py:336 +#: /home/kovid/work/calibre/src/calibre/gui2/device.py:327 msgid "Send metadata to device" msgstr "Enviar os metadados para o aparelho" -#: /home/kovid/work/calibre/src/calibre/gui2/device.py:341 +#: /home/kovid/work/calibre/src/calibre/gui2/device.py:332 msgid "Send collections to device" -msgstr "" +msgstr "Enviar colecções para o dispositivo" -#: /home/kovid/work/calibre/src/calibre/gui2/device.py:376 +#: /home/kovid/work/calibre/src/calibre/gui2/device.py:368 msgid "Upload %d books to device" msgstr "Carregar %d livro(s) para o aparelho" -#: /home/kovid/work/calibre/src/calibre/gui2/device.py:391 +#: /home/kovid/work/calibre/src/calibre/gui2/device.py:383 msgid "Delete books from device" msgstr "Apagar os livros do aparelho" -#: /home/kovid/work/calibre/src/calibre/gui2/device.py:408 +#: /home/kovid/work/calibre/src/calibre/gui2/device.py:400 msgid "Download books from device" msgstr "Descarregar os livros do aparelho" -#: /home/kovid/work/calibre/src/calibre/gui2/device.py:418 +#: /home/kovid/work/calibre/src/calibre/gui2/device.py:410 msgid "View book on device" msgstr "Ver o livro no aparelho" -#: /home/kovid/work/calibre/src/calibre/gui2/device.py:452 +#: /home/kovid/work/calibre/src/calibre/gui2/device.py:451 msgid "Set default send to device action" msgstr "Definir a acção predefinida Enviar para o aparelho" -#: /home/kovid/work/calibre/src/calibre/gui2/device.py:458 +#: /home/kovid/work/calibre/src/calibre/gui2/device.py:457 msgid "Send to main memory" msgstr "Enviar para a memória principal" -#: /home/kovid/work/calibre/src/calibre/gui2/device.py:460 +#: /home/kovid/work/calibre/src/calibre/gui2/device.py:459 msgid "Send to storage card A" msgstr "Enviar para o cartão de memória A" -#: /home/kovid/work/calibre/src/calibre/gui2/device.py:462 +#: /home/kovid/work/calibre/src/calibre/gui2/device.py:461 msgid "Send to storage card B" msgstr "Enviar para o cartão de memória B" -#: /home/kovid/work/calibre/src/calibre/gui2/device.py:467 -#: /home/kovid/work/calibre/src/calibre/gui2/device.py:476 +#: /home/kovid/work/calibre/src/calibre/gui2/device.py:466 +#: /home/kovid/work/calibre/src/calibre/gui2/device.py:475 msgid "Main Memory" -msgstr "" +msgstr "Memória principal" + +#: /home/kovid/work/calibre/src/calibre/gui2/device.py:487 +msgid "Send specific format to" +msgstr "Enviar em formato específico para" #: /home/kovid/work/calibre/src/calibre/gui2/device.py:488 -msgid "Send specific format to" -msgstr "" - -#: /home/kovid/work/calibre/src/calibre/gui2/device.py:489 msgid "Send and delete from library" -msgstr "" +msgstr "Enviar e apagar da biblioteca" -#: /home/kovid/work/calibre/src/calibre/gui2/device.py:532 +#: /home/kovid/work/calibre/src/calibre/gui2/device.py:531 msgid "Eject device" -msgstr "" +msgstr "Ejectar dispositivo" -#: /home/kovid/work/calibre/src/calibre/gui2/device.py:594 -#: /home/kovid/work/calibre/src/calibre/gui2/preferences/misc.py:41 -#: /home/kovid/work/calibre/src/calibre/gui2/preferences/plugins.py:302 +#: /home/kovid/work/calibre/src/calibre/gui2/device.py:611 +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/misc.py:55 +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/plugins.py:313 #: /home/kovid/work/calibre/src/calibre/utils/ipc/job.py:54 msgid "Error" msgstr "Erro" -#: /home/kovid/work/calibre/src/calibre/gui2/device.py:595 +#: /home/kovid/work/calibre/src/calibre/gui2/device.py:612 msgid "Error communicating with device" msgstr "Erro ao comunicar com o aparelho" -#: /home/kovid/work/calibre/src/calibre/gui2/device.py:611 -#: /home/kovid/work/calibre/src/calibre/gui2/device.py:1139 -#: /home/kovid/work/calibre/src/calibre/gui2/email.py:298 +#: /home/kovid/work/calibre/src/calibre/gui2/device.py:631 +#: /home/kovid/work/calibre/src/calibre/gui2/device.py:1170 +#: /home/kovid/work/calibre/src/calibre/gui2/email.py:221 msgid "No suitable formats" msgstr "Nenhuns formatos suportados" -#: /home/kovid/work/calibre/src/calibre/gui2/device.py:627 +#: /home/kovid/work/calibre/src/calibre/gui2/device.py:647 msgid "Select folder to open as device" -msgstr "" +msgstr "Seleccionar pasta para abrir como dispositivo" -#: /home/kovid/work/calibre/src/calibre/gui2/device.py:678 +#: /home/kovid/work/calibre/src/calibre/gui2/device.py:698 msgid "Error talking to device" msgstr "Erro ao comunicar com o aparelho" -#: /home/kovid/work/calibre/src/calibre/gui2/device.py:679 +#: /home/kovid/work/calibre/src/calibre/gui2/device.py:699 msgid "" "There was a temporary error talking to the device. Please unplug and " "reconnect the device and or reboot." @@ -7478,69 +7742,69 @@ msgstr "" "Houve um erro temporário ao comunicar com o aparelho. Por favor desligue e " "volte a ligar o aparelho ou reinicie." -#: /home/kovid/work/calibre/src/calibre/gui2/device.py:722 +#: /home/kovid/work/calibre/src/calibre/gui2/device.py:742 msgid "Device: " msgstr "Aparelho: " -#: /home/kovid/work/calibre/src/calibre/gui2/device.py:724 +#: /home/kovid/work/calibre/src/calibre/gui2/device.py:744 msgid " detected." msgstr " detectado." -#: /home/kovid/work/calibre/src/calibre/gui2/device.py:822 +#: /home/kovid/work/calibre/src/calibre/gui2/device.py:846 msgid "selected to send" msgstr "seleccionado para enviar" -#: /home/kovid/work/calibre/src/calibre/gui2/device.py:841 +#: /home/kovid/work/calibre/src/calibre/gui2/device.py:865 msgid "%i of %i Books" -msgstr "" +msgstr "%i de %i Livros" -#: /home/kovid/work/calibre/src/calibre/gui2/device.py:844 +#: /home/kovid/work/calibre/src/calibre/gui2/device.py:868 msgid "0 of %i Books" -msgstr "" +msgstr "0 de %i Livros" -#: /home/kovid/work/calibre/src/calibre/gui2/device.py:845 +#: /home/kovid/work/calibre/src/calibre/gui2/device.py:869 msgid "Choose format to send to device" msgstr "Escolher o formato a enviar para o aparelho" -#: /home/kovid/work/calibre/src/calibre/gui2/device.py:853 +#: /home/kovid/work/calibre/src/calibre/gui2/device.py:877 msgid "No device" msgstr "Nenhum aparelho" -#: /home/kovid/work/calibre/src/calibre/gui2/device.py:854 +#: /home/kovid/work/calibre/src/calibre/gui2/device.py:878 msgid "Cannot send: No device is connected" msgstr "É impossível enviar: O aparelho não está ligado" -#: /home/kovid/work/calibre/src/calibre/gui2/device.py:857 -#: /home/kovid/work/calibre/src/calibre/gui2/device.py:861 +#: /home/kovid/work/calibre/src/calibre/gui2/device.py:881 +#: /home/kovid/work/calibre/src/calibre/gui2/device.py:885 msgid "No card" msgstr "Nenhum cartão" -#: /home/kovid/work/calibre/src/calibre/gui2/device.py:858 -#: /home/kovid/work/calibre/src/calibre/gui2/device.py:862 +#: /home/kovid/work/calibre/src/calibre/gui2/device.py:882 +#: /home/kovid/work/calibre/src/calibre/gui2/device.py:886 msgid "Cannot send: Device has no storage card" msgstr "É impossível enviar: O aparelho não tem cartão de memória" -#: /home/kovid/work/calibre/src/calibre/gui2/device.py:918 -#: /home/kovid/work/calibre/src/calibre/gui2/device.py:1001 -#: /home/kovid/work/calibre/src/calibre/gui2/device.py:1133 +#: /home/kovid/work/calibre/src/calibre/gui2/device.py:947 +#: /home/kovid/work/calibre/src/calibre/gui2/device.py:1030 +#: /home/kovid/work/calibre/src/calibre/gui2/device.py:1164 msgid "Auto convert the following books before uploading to the device?" msgstr "" "Converter automaticamente os seguintes livros antes de os carregar para o " "aparelho?" -#: /home/kovid/work/calibre/src/calibre/gui2/device.py:947 +#: /home/kovid/work/calibre/src/calibre/gui2/device.py:976 msgid "Sending catalogs to device." msgstr "A enviar catálogos para o dispositivo" -#: /home/kovid/work/calibre/src/calibre/gui2/device.py:1046 +#: /home/kovid/work/calibre/src/calibre/gui2/device.py:1077 msgid "Sending news to device." msgstr "Enviar notícias para o aparelho." -#: /home/kovid/work/calibre/src/calibre/gui2/device.py:1100 +#: /home/kovid/work/calibre/src/calibre/gui2/device.py:1131 msgid "Sending books to device." msgstr "A enviar livros para o aparelho" -#: /home/kovid/work/calibre/src/calibre/gui2/device.py:1140 +#: /home/kovid/work/calibre/src/calibre/gui2/device.py:1171 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." @@ -7549,38 +7813,38 @@ msgstr "" "encontrados formatos adequados. Converta o(s) livro(s) para um formato " "suportado pelo seu aparelho primeiro." -#: /home/kovid/work/calibre/src/calibre/gui2/device.py:1204 +#: /home/kovid/work/calibre/src/calibre/gui2/device.py:1243 msgid "No space on device" msgstr "Sem espaço no aparelho" -#: /home/kovid/work/calibre/src/calibre/gui2/device.py:1205 +#: /home/kovid/work/calibre/src/calibre/gui2/device.py:1244 msgid "" "

      Cannot upload books to device there is no more free space available " msgstr "" "

      É impossível carregar os livros para o aparelho porque já não há mais " "espaço disponível " -#: /home/kovid/work/calibre/src/calibre/gui2/device_drivers/configwidget.py:124 +#: /home/kovid/work/calibre/src/calibre/gui2/device_drivers/configwidget.py:135 msgid "Unknown formats" -msgstr "" +msgstr "Formatos desconhecidos" -#: /home/kovid/work/calibre/src/calibre/gui2/device_drivers/configwidget.py:125 +#: /home/kovid/work/calibre/src/calibre/gui2/device_drivers/configwidget.py:136 msgid "" "You have enabled the {0} formats for your {1}. The {1} may not " "support them. If you send these formats to your {1} they may not work. Are " "you sure?" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/device_drivers/configwidget.py:137 -#: /home/kovid/work/calibre/src/calibre/gui2/library/delegates.py:403 -#: /home/kovid/work/calibre/src/calibre/gui2/preferences/plugboard.py:255 +#: /home/kovid/work/calibre/src/calibre/gui2/device_drivers/configwidget.py:148 +#: /home/kovid/work/calibre/src/calibre/gui2/library/delegates.py:409 +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/plugboard.py:293 #: /home/kovid/work/calibre/src/calibre/gui2/preferences/save_template.py:61 msgid "Invalid template" msgstr "Modelo inválido" -#: /home/kovid/work/calibre/src/calibre/gui2/device_drivers/configwidget.py:138 -#: /home/kovid/work/calibre/src/calibre/gui2/library/delegates.py:404 -#: /home/kovid/work/calibre/src/calibre/gui2/preferences/plugboard.py:256 +#: /home/kovid/work/calibre/src/calibre/gui2/device_drivers/configwidget.py:149 +#: /home/kovid/work/calibre/src/calibre/gui2/library/delegates.py:410 +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/plugboard.py:294 #: /home/kovid/work/calibre/src/calibre/gui2/preferences/save_template.py:62 msgid "The template %s is invalid:" msgstr "O modelo %s é inválido:" @@ -7595,6 +7859,9 @@ msgid "" "If checked, books are placed into sub directories based on their metadata on " "the device. If unchecked, books are all put into the top level directory." msgstr "" +"Caso seleccionado, os livros serão colocados em sub-directorias no " +"dispositivo baseados nos seus metadados. Caso não esteja seleccionados, os " +"livros serão todos colocados na directoria principal." #: /home/kovid/work/calibre/src/calibre/gui2/device_drivers/configwidget_ui.py:83 msgid "Use sub directories" @@ -7626,11 +7893,11 @@ msgstr "" #: /home/kovid/work/calibre/src/calibre/gui2/dialogs/add_from_isbn_ui.py:63 msgid "Add books by ISBN" -msgstr "" +msgstr "Adicionar livros por ISBN" #: /home/kovid/work/calibre/src/calibre/gui2/dialogs/add_from_isbn_ui.py:64 msgid "&Paste from clipboard" -msgstr "" +msgstr "&Colar a partir da área de transferência" #: /home/kovid/work/calibre/src/calibre/gui2/dialogs/add_from_isbn_ui.py:65 msgid "" @@ -7647,26 +7914,26 @@ msgstr "" msgid "&Tags to set on created book entries:" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/book_info_ui.py:80 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/book_info_ui.py:71 msgid "Fit &cover within view" -msgstr "" +msgstr "Ajustar a &capa com a vista" #: /home/kovid/work/calibre/src/calibre/gui2/dialogs/catalog.py:33 msgid "My Books" -msgstr "" +msgstr "Os Meus Livros" #: /home/kovid/work/calibre/src/calibre/gui2/dialogs/catalog_ui.py:92 #: /home/kovid/work/calibre/src/calibre/gui2/tools.py:314 msgid "Generate catalog" -msgstr "" +msgstr "Gerar catálogo" #: /home/kovid/work/calibre/src/calibre/gui2/dialogs/catalog_ui.py:93 msgid "Generate catalog for {0} books" -msgstr "" +msgstr "Gerar catálogo para {0} livros" #: /home/kovid/work/calibre/src/calibre/gui2/dialogs/catalog_ui.py:94 msgid "Catalog &format:" -msgstr "" +msgstr "Formato de &catálogo" #: /home/kovid/work/calibre/src/calibre/gui2/dialogs/catalog_ui.py:95 msgid "" @@ -7675,7 +7942,7 @@ msgstr "" #: /home/kovid/work/calibre/src/calibre/gui2/dialogs/catalog_ui.py:96 msgid "&Send catalog to device automatically" -msgstr "" +msgstr "&Enviar catálogo para o dispositivo automaticamente" #: /home/kovid/work/calibre/src/calibre/gui2/dialogs/catalog_ui.py:97 msgid "Catalog options" @@ -7688,15 +7955,15 @@ msgstr "A verificar a integridade da base de dados" #: /home/kovid/work/calibre/src/calibre/gui2/dialogs/check_library.py:55 msgid "Dumping database to SQL" -msgstr "" +msgstr "Fazer dump da base de dados para SQL" #: /home/kovid/work/calibre/src/calibre/gui2/dialogs/check_library.py:81 msgid "Loading database from SQL" -msgstr "" +msgstr "Carregando base de dados a partir de SQL" #: /home/kovid/work/calibre/src/calibre/gui2/dialogs/check_library.py:148 msgid "Check Library -- Problems Found" -msgstr "" +msgstr "Verifique a biblioteca - Problemas Encontrados" #: /home/kovid/work/calibre/src/calibre/gui2/dialogs/check_library.py:158 msgid "" @@ -7756,68 +8023,80 @@ msgid "" " have no entries in the database. Check the box next to the item you " "want\n" " to delete. Use with caution.

      \n" -"

      Fix marked is applicable only to covers (the two lines " -"marked\n" -" 'fixable'). In the case of missing cover files, checking the " -"fixable\n" -" box and pushing this button will remove the cover mark from the\n" -" database for all the files in that category. In the case of extra\n" -" cover files, checking the fixable box and pushing this button will\n" -" add the cover mark to the database for all the files in that\n" -" category.

      \n" +"\n" +"

      Fix marked is applicable only to covers and missing " +"formats\n" +" (the three lines marked 'fixable'). In the case of missing cover " +"files,\n" +" checking the fixable box and pushing this button will tell calibre " +"that\n" +" there is no cover for all of the books listed. Use this option if " +"you\n" +" are not going to restore the covers from a backup. In the case of " +"extra\n" +" cover files, checking the fixable box and pushing this button will " +"tell\n" +" calibre that the cover files it found are correct for all the books\n" +" listed. Use this when you are not going to delete the file(s). In " +"the\n" +" case of missing formats, checking the fixable box and pushing this\n" +" button will tell calibre that the formats are really gone. Use this " +"if\n" +" you are not going to restore the formats from a backup.

      \n" +"\n" " " msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/check_library.py:220 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/check_library.py:226 msgid "&Run the check again" -msgstr "" +msgstr "&Correr a verificação outra vez" -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/check_library.py:223 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/check_library.py:229 msgid "Copy &to clipboard" -msgstr "" +msgstr "Copiar ¶ a área de transferência" -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/check_library.py:230 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/check_library.py:236 msgid "Delete marked files (checked subitems)" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/check_library.py:236 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/check_library.py:242 msgid "Fix marked sections (checked fixable items)" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/check_library.py:246 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/check_library.py:252 msgid "Names to ignore:" -msgstr "" +msgstr "Nomes a ignorar:" -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/check_library.py:251 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/check_library.py:257 msgid "" "Enter comma-separated standard file name wildcards, such as synctoy*.dat" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/check_library.py:254 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/check_library.py:260 msgid "Extensions to ignore" -msgstr "" +msgstr "Extensões a ignorar" -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/check_library.py:259 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/check_library.py:265 msgid "" "Enter comma-separated extensions without a leading dot. Used only in book " "folders" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/check_library.py:308 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/check_library.py:314 msgid "(fixable)" msgstr "(reparável)" -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/check_library.py:331 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/check_library.py:337 msgid "Path from library" msgstr "Caminho da biblioteca" -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/check_library.py:331 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/check_library.py:337 #: /home/kovid/work/calibre/src/calibre/gui2/viewer/bookmarkmanager.py:89 -#: /home/kovid/work/calibre/src/calibre/library/server/browse.py:253 +#: /home/kovid/work/calibre/src/calibre/library/server/browse.py:256 msgid "Name" msgstr "Nome" -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/check_library.py:360 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/check_library.py:366 msgid "" "The marked files and folders will be permanently deleted. Are you " "sure?" @@ -7832,13 +8111,13 @@ msgstr "Escolher o formato" #: /home/kovid/work/calibre/src/calibre/gui2/dialogs/choose_format_device_ui.py:49 #: /home/kovid/work/calibre/src/calibre/gui2/dialogs/delete_matching_from_device.py:76 -#: /home/kovid/work/calibre/src/calibre/gui2/library/models.py:1169 +#: /home/kovid/work/calibre/src/calibre/gui2/store/mobileread/models.py:23 msgid "Format" msgstr "Formato" #: /home/kovid/work/calibre/src/calibre/gui2/dialogs/choose_format_device_ui.py:50 msgid "Existing" -msgstr "" +msgstr "Existente(s)" #: /home/kovid/work/calibre/src/calibre/gui2/dialogs/choose_format_device_ui.py:51 msgid "Convertible" @@ -7895,23 +8174,23 @@ msgstr "Escolha a sua bili" #: /home/kovid/work/calibre/src/calibre/gui2/dialogs/choose_library_ui.py:78 msgid "Your calibre library is currently located at {0}" -msgstr "" +msgstr "A sua bilbioteca do calibre localiza-se actualmente em {0}" #: /home/kovid/work/calibre/src/calibre/gui2/dialogs/choose_library_ui.py:79 msgid "New &Location:" -msgstr "" +msgstr "Nova &Localização:" #: /home/kovid/work/calibre/src/calibre/gui2/dialogs/choose_library_ui.py:80 msgid "Use &existing library at the new location" -msgstr "" +msgstr "Usar bilbioteca &existente numa nova localização" #: /home/kovid/work/calibre/src/calibre/gui2/dialogs/choose_library_ui.py:81 msgid "&Create an empty library at the new location" -msgstr "" +msgstr "&Criar uma biblioteca vazia na nova localização" #: /home/kovid/work/calibre/src/calibre/gui2/dialogs/choose_library_ui.py:82 msgid "&Copy structure from the current library" -msgstr "" +msgstr "&Copiar estrutura a partir da bilbioteca actual" #: /home/kovid/work/calibre/src/calibre/gui2/dialogs/choose_library_ui.py:83 msgid "" @@ -7921,7 +8200,24 @@ msgstr "" #: /home/kovid/work/calibre/src/calibre/gui2/dialogs/choose_library_ui.py:85 msgid "&Move current library to new location" +msgstr "&Mover a biblioteca actual para uma nova localização" + +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/choose_plugin_toolbars.py:23 +msgid "Add \"%s\" to toolbars or menus" +msgstr "Adicionar'%s' às barras de ferramentas ou menus" + +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/choose_plugin_toolbars.py:29 +msgid "Select the toolbars and/or menus to add %s to:" msgstr "" +"Seleccione as barras de ferramentas e/ou menus para adicionar %s a:" + +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/choose_plugin_toolbars.py:45 +msgid "" +"You can also customise the plugin locations using Preferences -> " +"Customise the toolbar" +msgstr "" +"Pode também personalizar a localização de plugins usando Preferências-> " +"Personalizar a barra de ferramentas" #: /home/kovid/work/calibre/src/calibre/gui2/dialogs/comicconf.py:33 msgid "Set defaults for conversion of comics (CBR/CBZ files)" @@ -7935,12 +8231,13 @@ msgstr "Definir as opções para converter %s" #: /home/kovid/work/calibre/src/calibre/gui2/dialogs/comicconf_ui.py:97 #: /home/kovid/work/calibre/src/calibre/gui2/dialogs/search_ui.py:211 -#: /home/kovid/work/calibre/src/calibre/gui2/metadata/basic_widgets.py:61 +#: /home/kovid/work/calibre/src/calibre/gui2/metadata/basic_widgets.py:73 +#: /home/kovid/work/calibre/src/calibre/gui2/store/search/adv_search_builder_ui.py:189 msgid "&Title:" msgstr "&Título" #: /home/kovid/work/calibre/src/calibre/gui2/dialogs/comicconf_ui.py:98 -#: /home/kovid/work/calibre/src/calibre/gui2/metadata/basic_widgets.py:155 +#: /home/kovid/work/calibre/src/calibre/gui2/metadata/basic_widgets.py:167 msgid "&Author(s):" msgstr "&Autor(es):" @@ -7951,38 +8248,38 @@ msgstr "&Perfil:" #: /home/kovid/work/calibre/src/calibre/gui2/dialogs/comments_dialog.py:24 #: /home/kovid/work/calibre/src/calibre/gui2/dialogs/template_dialog.py:30 msgid "&OK" -msgstr "" +msgstr "&OK" #: /home/kovid/work/calibre/src/calibre/gui2/dialogs/comments_dialog.py:25 #: /home/kovid/work/calibre/src/calibre/gui2/dialogs/template_dialog.py:31 -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/tweak_epub_ui.py:60 -#: /home/kovid/work/calibre/src/calibre/gui2/preferences/main.py:226 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/tweak_epub_ui.py:65 +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/main.py:233 msgid "&Cancel" -msgstr "" +msgstr "&Cancelar" #: /home/kovid/work/calibre/src/calibre/gui2/dialogs/comments_dialog_ui.py:43 #: /home/kovid/work/calibre/src/calibre/gui2/dialogs/template_dialog_ui.py:70 msgid "Edit Comments" -msgstr "" +msgstr "Editar Comentários" -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/confirm_delete_location_ui.py:62 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/confirm_delete_location_ui.py:76 msgid "Where do you want to delete from?" -msgstr "" +msgstr "De onde quer apagar?" -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/confirm_delete_location_ui.py:63 -#: /home/kovid/work/calibre/src/calibre/gui2/layout.py:63 -#: /home/kovid/work/calibre/src/calibre/library/server/opds.py:230 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/confirm_delete_location_ui.py:77 +#: /home/kovid/work/calibre/src/calibre/gui2/layout.py:68 +#: /home/kovid/work/calibre/src/calibre/library/server/opds.py:228 msgid "Library" msgstr "Biblioteca" -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/confirm_delete_location_ui.py:64 -#: /home/kovid/work/calibre/src/calibre/gui2/layout.py:65 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/confirm_delete_location_ui.py:78 +#: /home/kovid/work/calibre/src/calibre/gui2/layout.py:70 msgid "Device" msgstr "Dispositivo" -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/confirm_delete_location_ui.py:65 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/confirm_delete_location_ui.py:79 msgid "Library and Device" -msgstr "" +msgstr "Biblioteca e Dispositivo" #: /home/kovid/work/calibre/src/calibre/gui2/dialogs/confirm_delete_ui.py:55 msgid "&Show this warning again" @@ -8000,159 +8297,144 @@ msgstr "" #: /home/kovid/work/calibre/src/calibre/gui2/dialogs/delete_matching_from_device.py:75 msgid "Location" -msgstr "" +msgstr "Localização" #: /home/kovid/work/calibre/src/calibre/gui2/dialogs/delete_matching_from_device.py:76 -#: /home/kovid/work/calibre/src/calibre/gui2/library/models.py:69 +#: /home/kovid/work/calibre/src/calibre/gui2/library/models.py:67 #: /home/kovid/work/calibre/src/calibre/gui2/library/models.py:979 -#: /home/kovid/work/calibre/src/calibre/gui2/preferences/create_custom_column.py:33 -#: /home/kovid/work/calibre/src/calibre/library/field_metadata.py:295 -#: /home/kovid/work/calibre/src/calibre/library/server/opds.py:589 +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/create_custom_column.py:32 +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/create_custom_column.py:73 +#: /home/kovid/work/calibre/src/calibre/library/field_metadata.py:321 +#: /home/kovid/work/calibre/src/calibre/library/server/opds.py:573 msgid "Date" msgstr "Data" #: /home/kovid/work/calibre/src/calibre/gui2/dialogs/delete_matching_from_device_ui.py:55 msgid "Delete from device" -msgstr "" +msgstr "Remover do dispositivo" #: /home/kovid/work/calibre/src/calibre/gui2/dialogs/drm_error_ui.py:54 msgid "This book is DRMed" -msgstr "" +msgstr "Este livro tem DRM" #: /home/kovid/work/calibre/src/calibre/gui2/dialogs/drm_error_ui.py:55 msgid "" "

      This book is locked by DRM. To learn more about DRM and why you " "cannot read or convert this book in calibre, \n" -"click here." +" click " +"here.

      A large number of recent, DRM free releases are \n" +" available at Open " +"Books." msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/edit_authors_dialog.py:35 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/edit_authors_dialog.py:43 msgid "Author sort" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/edit_authors_dialog.py:117 -#: /home/kovid/work/calibre/src/calibre/gui2/tag_view.py:916 -msgid "Invalid author name" +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/edit_authors_dialog.py:104 +#: /home/kovid/work/calibre/src/calibre/gui2/lrf_renderer/main.py:160 +#: /home/kovid/work/calibre/src/calibre/gui2/metadata/single_download.py:471 +#: /home/kovid/work/calibre/src/calibre/gui2/viewer/main.py:503 +msgid "No matches found" +msgstr "Não foram encontradas correspondências" + +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/edit_authors_dialog.py:120 +#: /home/kovid/work/calibre/src/calibre/gui2/widgets.py:419 +msgid "Change Case" +msgstr "Alterar a Capitalização" + +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/edit_authors_dialog.py:121 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk.py:261 +#: /home/kovid/work/calibre/src/calibre/gui2/widgets.py:420 +msgid "Upper Case" +msgstr "Maiúsculas" + +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/edit_authors_dialog.py:122 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk.py:260 +#: /home/kovid/work/calibre/src/calibre/gui2/widgets.py:421 +msgid "Lower Case" +msgstr "Minúsculas" + +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/edit_authors_dialog.py:123 +#: /home/kovid/work/calibre/src/calibre/gui2/widgets.py:422 +msgid "Swap Case" +msgstr "Alterar a Capitalização" + +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/edit_authors_dialog.py:124 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk.py:262 +#: /home/kovid/work/calibre/src/calibre/gui2/widgets.py:423 +msgid "Title Case" +msgstr "Capitalização de Título" + +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/edit_authors_dialog.py:125 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk.py:263 +#: /home/kovid/work/calibre/src/calibre/gui2/widgets.py:424 +msgid "Capitalize" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/edit_authors_dialog.py:118 -#: /home/kovid/work/calibre/src/calibre/gui2/tag_view.py:917 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/edit_authors_dialog.py:141 +msgid "Copy to author sort" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/edit_authors_dialog.py:144 +msgid "Copy to author" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/edit_authors_dialog.py:271 +#: /home/kovid/work/calibre/src/calibre/gui2/tag_view.py:1439 +msgid "Invalid author name" +msgstr "Nome de autor inválido" + +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/edit_authors_dialog.py:272 +#: /home/kovid/work/calibre/src/calibre/gui2/tag_view.py:1440 msgid "Author names cannot contain & characters." msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/edit_authors_dialog_ui.py:71 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/edit_authors_dialog_ui.py:88 +#: /home/kovid/work/calibre/src/calibre/gui2/metadata/single.py:120 msgid "Manage authors" +msgstr "Gerir autores" + +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/edit_authors_dialog_ui.py:89 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:597 +msgid "&Search for:" +msgstr "&Procurar por:" + +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/edit_authors_dialog_ui.py:90 +#: /home/kovid/work/calibre/src/calibre/gui2/tag_view.py:2105 +msgid "F&ind" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/edit_authors_dialog_ui.py:72 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/edit_authors_dialog_ui.py:91 msgid "Sort by author" msgstr "Ordenar por autor" -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/edit_authors_dialog_ui.py:73 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/edit_authors_dialog_ui.py:92 msgid "Sort by author sort" msgstr "Ordenar por ID de ordem de autor" -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/edit_authors_dialog_ui.py:74 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/edit_authors_dialog_ui.py:93 msgid "" -"Reset all the author sort values to a value automatically generated from the " -"author. Exactly how this value is automatically generated can be controlled " -"via Preferences->Advanced->Tweaks" +"Reset all the author sort values to a value automatically\n" +"generated from the author. Exactly how this value is automatically\n" +"generated can be controlled via Preferences->Advanced->Tweaks" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/edit_authors_dialog_ui.py:75 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/edit_authors_dialog_ui.py:96 msgid "Recalculate all author sort values" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/fetch_metadata.py:62 -msgid "Author Sort" -msgstr "Ordenação do(s) Autor(es)" - -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/fetch_metadata.py:64 -msgid "ISBN" -msgstr "ISBN" - -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/fetch_metadata.py:66 -msgid "Has Cover" -msgstr "" - -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/fetch_metadata.py:67 -msgid "Has Summary" -msgstr "" - -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/fetch_metadata.py:192 -msgid "Finding metadata..." -msgstr "A encontrar os metadados..." - -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/fetch_metadata.py:206 -msgid "Could not find metadata" -msgstr "É impossível encontrar os metadados" - -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/fetch_metadata.py:207 -msgid "The metadata download seems to have stalled. Try again later." -msgstr "O descarregamento dos metadados parece ter parado. Tente mais tarde." - -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/fetch_metadata.py:216 -msgid "Warning" -msgstr "Aviso" - -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/fetch_metadata.py:217 -msgid "Could not fetch metadata from:" -msgstr "É impossível encontrar os metadados de:" - -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/fetch_metadata.py:221 -msgid "No metadata found" -msgstr "Não foram encontrados metadados" - -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/fetch_metadata.py:222 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/edit_authors_dialog_ui.py:97 msgid "" -"No metadata found, try adjusting the title and author and/or removing the " -"ISBN." +"Copy author sort to author for every author. You typically use this button\n" +"after changing Preferences->Advanced->Tweaks->Author sort name algorithm" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/fetch_metadata_ui.py:93 -msgid "Fetch metadata" -msgstr "Recolher os metadados" - -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/fetch_metadata_ui.py:94 -msgid "" -"

      calibre can find metadata for your books from two locations: Google " -"Books and isbndb.com.

      To use isbndb.com you must sign up for a " -"free account and enter your access key " -"below." -msgstr "" -"

      O calibre pode encontrar metadados para os seus livros de duas " -"localizações: Google Books e isbndb.com.

      Para usar o " -"isbndb.com deve registar-se para uma conta " -"grátis e introduzir a sua chave de acesso abaixo." - -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/fetch_metadata_ui.py:95 -msgid "&Access Key:" -msgstr "Chave de &Acesso:" - -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/fetch_metadata_ui.py:96 -msgid "Fetch" -msgstr "Recolher" - -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/fetch_metadata_ui.py:97 -msgid "Matches" -msgstr "Correspondências" - -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/fetch_metadata_ui.py:98 -msgid "" -"Select the book that most closely matches your copy from the list below" -msgstr "Seleccionar o livro que é mais parecido com o seu da lista abaixo" - -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/fetch_metadata_ui.py:99 -msgid "Overwrite author and title with author and title of selected book" +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/edit_authors_dialog_ui.py:99 +msgid "Copy all author sort values to author" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/fetch_metadata_ui.py:100 -msgid "Download &social metadata (tags/rating/etc.) for the selected book" -msgstr "" -"Transferir meta-dados &sociais (etiquetas/classif./etc) para o livro " -"seleccionado" - -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/job_view_ui.py:42 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/job_view_ui.py:45 msgid "Details of job" msgstr "Detalhes do processo" @@ -8170,29 +8452,41 @@ msgstr "Mostrar os &detalhes do processo" #: /home/kovid/work/calibre/src/calibre/gui2/dialogs/jobs_ui.py:52 msgid "Stop &all non device jobs" -msgstr "" +msgstr "Parar &todas as tarefas não dispositivo" -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/message_box.py:43 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/message_box.py:49 msgid "&Copy to clipboard" -msgstr "" +msgstr "&Copiar para a área de transferência" -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/message_box.py:48 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/message_box.py:53 msgid "Show &details" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/message_box.py:49 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/message_box.py:54 msgid "Hide &details" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/message_box.py:53 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/message_box.py:58 msgid "Show detailed information about this error" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/message_box.py:93 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/message_box.py:98 #: /home/kovid/work/calibre/src/calibre/gui2/wizard/__init__.py:525 msgid "Copied" msgstr "Copiado" +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/message_box.py:135 +#: /home/kovid/work/calibre/src/calibre/gui2/metadata/single_download.py:770 +#: /home/kovid/work/calibre/src/calibre/gui2/viewer/main_ui.py:205 +msgid "Copy to clipboard" +msgstr "Copiar para a Área de Transferência" + +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/message_box.py:179 +#: /home/kovid/work/calibre/src/calibre/gui2/metadata/single_download.py:831 +#: /home/kovid/work/calibre/src/calibre/gui2/metadata/single_download.py:922 +msgid "View log" +msgstr "" + #: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk.py:58 msgid "Title/Author" msgstr "" @@ -8202,6 +8496,7 @@ msgid "Standard metadata" msgstr "" #: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk.py:60 +#: /home/kovid/work/calibre/src/calibre/gui2/metadata/single.py:852 msgid "Custom metadata" msgstr "" @@ -8214,26 +8509,6 @@ msgstr "Procurar/Substituir" msgid "Working" msgstr "A trabalhar" -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk.py:260 -#: /home/kovid/work/calibre/src/calibre/gui2/widgets.py:384 -msgid "Lower Case" -msgstr "Minúsculas" - -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk.py:261 -#: /home/kovid/work/calibre/src/calibre/gui2/widgets.py:383 -msgid "Upper Case" -msgstr "Maiúsculas" - -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk.py:262 -#: /home/kovid/work/calibre/src/calibre/gui2/widgets.py:386 -msgid "Title Case" -msgstr "Capitalização de Título" - -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk.py:263 -#: /home/kovid/work/calibre/src/calibre/gui2/widgets.py:387 -msgid "Capitalize" -msgstr "" - #: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk.py:266 msgid "Character match" msgstr "" @@ -8244,15 +8519,15 @@ msgstr "" #: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk.py:270 msgid "Replace field" -msgstr "" +msgstr "Substituir campo" #: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk.py:271 msgid "Prepend to field" -msgstr "" +msgstr "Adicionar campo ao início" #: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk.py:272 msgid "Append to field" -msgstr "" +msgstr "Adicionar campo ao fim" #: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk.py:282 msgid "Editing meta information for %d books" @@ -8264,11 +8539,15 @@ msgid "" "cannot be canceled or undone" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk.py:381 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk.py:382 msgid "Book %d:" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk.py:396 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk.py:400 +msgid "Enter an identifier type" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk.py:405 msgid "" "You can destroy your library using this feature. Changes are " "permanent. There is no undo function. You are strongly encouraged to back up " @@ -8282,7 +8561,7 @@ msgstr "" "dados em capos de texto usando correspondências de caracteres ou expressões " "regulares. " -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk.py:404 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk.py:413 msgid "" "In character mode, the field is searched for the entered search text. The " "text is replaced by the specified replacement text everywhere it is found in " @@ -8292,7 +8571,7 @@ msgid "" "text will match both upper- and lower-case letters" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk.py:415 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk.py:424 msgid "" "In regular expression mode, the search text is an arbitrary python-" "compatible regular expression. The replacement text can contain " @@ -8307,80 +8586,84 @@ msgid "" "function." msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk.py:489 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk.py:502 msgid "S/R TEMPLATE ERROR" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk.py:616 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk.py:648 msgid "You must specify a destination when source is a composite field" +msgstr "Tem de especificar um destino quando a fonte é um campo composto" + +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk.py:654 +msgid "You must specify a destination identifier type" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk.py:715 -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk.py:723 -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk.py:844 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk.py:761 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk.py:780 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk.py:907 msgid "Search/replace invalid" msgstr "Procurar/Substituir Inválido !" -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk.py:716 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk.py:762 msgid "" "Authors cannot be set to the empty string. Book title %s not processed" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk.py:724 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk.py:781 msgid "Title cannot be set to the empty string. Book title %s not processed" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk.py:845 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk.py:908 msgid "Search pattern is invalid: %s" msgstr "O padrão da procura é inválido: %s" -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk.py:897 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk.py:960 msgid "" "Applying changes to %d books.\n" "Phase {0} {1}%%." msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk.py:927 -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:561 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk.py:990 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:587 msgid "Delete saved search/replace" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk.py:928 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk.py:991 msgid "The selected saved search/replace will be deleted. Are you sure?" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk.py:945 -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk.py:953 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk.py:1008 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk.py:1016 msgid "Save search/replace" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk.py:946 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk.py:1009 msgid "Search/replace name:" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk.py:954 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk.py:1017 msgid "" "That saved search/replace already exists and will be overwritten. Are you " "sure?" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:498 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:524 msgid "Edit Meta information" msgstr "Editar os metadados" -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:500 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:526 msgid "A&utomatically set author sort" msgstr "Definir a&utomaticamente a ordenação do(s) autor(es)" -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:501 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:527 msgid "&Swap title and author" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:502 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:528 msgid "Author s&ort: " msgstr "&Ordenação de autor: " -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:503 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:529 msgid "" "Specify how the author(s) of this book should be sorted. For example Charles " "Dickens should be sorted as Dickens, Charles." @@ -8388,66 +8671,60 @@ msgstr "" "Especificar como é que o(s) autor(es) deste livro deve(m) ser ordenado(s). " "Por exemplo: Charles Dickens deve ser ordenado como Dickens, Charles." -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:504 -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single_ui.py:424 -#: /home/kovid/work/calibre/src/calibre/gui2/metadata/basic_widgets.py:786 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:530 +#: /home/kovid/work/calibre/src/calibre/gui2/metadata/basic_widgets.py:867 msgid "&Rating:" msgstr "A&valiação:" -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:505 -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:506 -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single_ui.py:425 -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single_ui.py:426 -#: /home/kovid/work/calibre/src/calibre/gui2/metadata/basic_widgets.py:787 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:531 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:532 +#: /home/kovid/work/calibre/src/calibre/gui2/metadata/basic_widgets.py:868 msgid "Rating of this book. 0-5 stars" msgstr "Avaliação deste livro. 0-5 estrelas" -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:507 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:533 msgid "No change" -msgstr "" +msgstr "Sem alterações" -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:508 -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single_ui.py:427 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:534 msgid " stars" msgstr " estrelas" -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:510 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:536 msgid "Add ta&gs: " msgstr "Adicionar eti&quetas: " -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:512 -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:513 -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single_ui.py:431 -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single_ui.py:432 -#: /home/kovid/work/calibre/src/calibre/gui2/metadata/single.py:140 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:538 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:539 +#: /home/kovid/work/calibre/src/calibre/gui2/metadata/single.py:166 msgid "Open Tag Editor" msgstr "Abrir o Editor de Etiquetas" -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:514 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:540 msgid "&Remove tags:" msgstr "&Remover etiquetas:" -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:515 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:541 msgid "Comma separated list of tags to remove from the books. " msgstr "Lista de etiquetas separadas por vírgulas a remover dos livros. " -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:516 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:542 msgid "Check this box to remove all tags from the books." msgstr "Marque esta caixa para remover todos os livros." -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:517 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:543 msgid "Remove &all" -msgstr "" +msgstr "Remover &Todos" -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:521 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:547 msgid "If checked, the series will be cleared" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:522 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:548 msgid "&Clear series" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:523 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:549 msgid "" "If not checked, the series number for the books will be set to 1.\n" "If checked, selected books will be automatically numbered, in the order\n" @@ -8455,169 +8732,171 @@ msgid "" "Book A will have series number 1 and Book B series number 2." msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:527 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:553 msgid "&Automatically number books in this series" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:528 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:554 msgid "" "Series will normally be renumbered from the highest number in the database\n" "for that series. Checking this box will tell calibre to start numbering\n" "from the value in the box" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:531 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:557 msgid "&Force numbers to start with:" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:532 -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single_ui.py:440 -#: /home/kovid/work/calibre/src/calibre/gui2/metadata/basic_widgets.py:978 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:558 +#: /home/kovid/work/calibre/src/calibre/gui2/metadata/basic_widgets.py:1101 msgid "&Date:" msgstr "&Data:" -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:533 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:559 msgid "d MMM yyyy" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:535 -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:540 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:561 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:566 msgid "&Apply date" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:536 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:562 msgid "&Published:" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:538 -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single_ui.py:444 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:564 msgid "Clear published date" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:541 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:567 msgid "Remove &format:" msgstr "Remover &formatos:" -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:542 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:568 msgid "" "Force the title to be in title case. If both this and swap authors are " "checked,\n" "title and author are swapped before the title case is set" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:544 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:570 msgid "Change title to title &case" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:545 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:571 msgid "" "Update title sort based on the current title. This will be applied only " "after other changes to title." msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:546 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:572 msgid "Update &title sort" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:547 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:573 msgid "" "Remove stored conversion settings for the selected books.\n" "\n" "Future conversion of these books will use the default settings." msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:550 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:576 msgid "Remove &stored conversion settings for the selected books" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:551 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:577 msgid "Change &cover" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:552 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:578 msgid "&Generate default cover" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:553 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:579 msgid "&Remove cover" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:554 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:580 msgid "Set from &ebook file(s)" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:555 -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single_ui.py:465 -#: /home/kovid/work/calibre/src/calibre/gui2/metadata/single.py:392 -#: /home/kovid/work/calibre/src/calibre/gui2/metadata/single.py:521 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:581 +#: /home/kovid/work/calibre/src/calibre/gui2/metadata/single.py:495 +#: /home/kovid/work/calibre/src/calibre/gui2/metadata/single.py:659 msgid "&Basic metadata" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:556 -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single_ui.py:466 -#: /home/kovid/work/calibre/src/calibre/gui2/metadata/single.py:399 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:582 +#: /home/kovid/work/calibre/src/calibre/gui2/metadata/single.py:502 msgid "&Custom metadata" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:557 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:583 msgid "Load searc&h/replace:" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:558 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:584 msgid "Select saved search/replace to load." msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:559 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:585 msgid "Save current search/replace" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:560 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:586 msgid "Sa&ve" 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:588 #: /home/kovid/work/calibre/src/calibre/gui2/viewer/bookmarkmanager_ui.py:64 msgid "Delete" msgstr "Apagar" -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:563 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:589 msgid "Search &field:" msgstr "Procurar &campo:" -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:564 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:590 msgid "The name of the field that you want to search" msgstr "O Nome do campo que quer procurar" -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:565 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:591 msgid "Search &mode:" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:566 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:592 msgid "" "Choose whether to use basic text matching or advanced regular expression " "matching" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:567 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:593 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:615 +msgid "Identifier type:" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:594 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:616 +msgid "Choose which identifier type to operate upon" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:595 msgid "Te&mplate:" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:568 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:596 msgid "Enter a template to be used as the source for the search/replace" msgstr "" "Indique um modelo para ser usado como origem para a procura/substituição." -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:569 -msgid "&Search for:" -msgstr "&Procurar por:" - -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:570 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:598 msgid "" "Enter the what you are looking for, either plain text or a regular " "expression, depending on the mode" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:571 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:599 msgid "" "Check this box if the search string must match exactly upper and lower case. " "Uncheck it if case is to be ignored" @@ -8626,15 +8905,15 @@ msgstr "" "maiúsculas/minúsculas. Desmarque para a procura ignorar a capitalização das " "palavras." -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:572 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:600 msgid "Cas&e sensitive" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:573 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:601 msgid "&Replace with:" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:574 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:602 msgid "" "The replacement text. The matched search text will be replaced with this " "string" @@ -8642,11 +8921,11 @@ msgstr "" "O texto de substituição. As procuras correspondentes irão ser trocadas por " "esta expressão." -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:575 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:603 msgid "&Apply function after replace:" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:576 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:604 msgid "" "Specify how the text is to be processed after matching and replacement. In " "character mode, the entire\n" @@ -8654,25 +8933,25 @@ msgid "" "processed" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:578 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:606 msgid "&Destination field:" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:579 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:607 msgid "" "The field that the text will be put into after all replacements.\n" "If blank, the source field is used if the field is modifiable" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:581 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:609 msgid "M&ode:" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:582 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:610 msgid "Specify how the text should be copied into the destination." msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:583 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:611 msgid "" "Specifies whether result items should be split into multiple values or\n" "left as single values. This option has the most effect when the source field " @@ -8680,432 +8959,66 @@ msgid "" "not multiple and the destination field is multiple" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:586 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:614 msgid "Split &result" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:587 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:617 msgid "For multiple-valued fields, sho&w" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:588 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:618 msgid "values starting a&t" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:589 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:619 msgid "with values separated b&y" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:590 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:620 msgid "" "Used when displaying test results to separate values in multiple-valued " "fields" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:591 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:621 msgid "Test text" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:592 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:622 msgid "Test result" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:593 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:623 msgid "Your test:" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:594 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:624 msgid "&Search and replace" msgstr "&Procurar e substituir" -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single.py:98 -#: /home/kovid/work/calibre/src/calibre/gui2/metadata/basic_widgets.py:429 -msgid "Last modified: %s" -msgstr "" - -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single.py:122 -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single.py:128 -#: /home/kovid/work/calibre/src/calibre/gui2/metadata/single.py:252 -#: /home/kovid/work/calibre/src/calibre/gui2/metadata/single.py:259 -msgid "Could not read cover" -msgstr "É impossível ler a capa" - -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single.py:123 -#: /home/kovid/work/calibre/src/calibre/gui2/metadata/single.py:253 -msgid "Could not read cover from %s format" -msgstr "É impossível ler a capa do formato %s." - -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single.py:129 -#: /home/kovid/work/calibre/src/calibre/gui2/metadata/single.py:260 -msgid "The cover in the %s format is invalid" -msgstr "A capa do formato %s é inválida" - -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single.py:158 -#: /home/kovid/work/calibre/src/calibre/gui2/metadata/basic_widgets.py:746 -msgid "Cover size: %dx%d pixels" -msgstr "" - -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single.py:195 -#: /home/kovid/work/calibre/src/calibre/gui2/metadata/basic_widgets.py:670 -msgid "Not a valid picture" -msgstr "Não é uma imagem válida" - -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single.py:214 -#: /home/kovid/work/calibre/src/calibre/gui2/metadata/basic_widgets.py:697 -msgid "Specify title and author" -msgstr "" - -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single.py:215 -#: /home/kovid/work/calibre/src/calibre/gui2/metadata/basic_widgets.py:698 -msgid "You must specify a title and author before generating a cover" -msgstr "" - -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single.py:246 -msgid "Downloading cover..." -msgstr "A descarregar a capa..." - -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single.py:262 -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single.py:267 -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single.py:273 -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single.py:278 -msgid "Cannot fetch cover" -msgstr "É impossível recolher a capa" - -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single.py:263 -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single.py:274 -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single.py:279 -msgid "Could not fetch cover.
      " -msgstr "É impossível recolher a capa.
      " - -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single.py:264 -msgid "The download timed out." -msgstr "O descarregamento está a demorar demasiado tempo." - -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single.py:268 -msgid "Could not find cover for this book. Try specifying the ISBN first." -msgstr "" -"É impossível encontrar a capa para este livro. Tente especificar o ISBN " -"primeiro." - -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single.py:280 -msgid "" -"For the error message from each cover source, click Show details below." -msgstr "" - -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single.py:287 -msgid "Bad cover" -msgstr "Capa com erros" - -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single.py:288 -msgid "The cover is not a valid picture" -msgstr "A capa não é uma imagem válida" - -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single.py:307 -#: /home/kovid/work/calibre/src/calibre/gui2/metadata/basic_widgets.py:527 -msgid "Choose formats for " -msgstr "Escolher formatos para " - -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single.py:338 -#: /home/kovid/work/calibre/src/calibre/gui2/metadata/basic_widgets.py:559 -msgid "No permission" -msgstr "Não tem permissão" - -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single.py:339 -#: /home/kovid/work/calibre/src/calibre/gui2/metadata/basic_widgets.py:560 -msgid "You do not have permission to read the following files:" -msgstr "Não tem permissão para ler os seguintes ficheiros:" - -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single.py:366 -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single.py:367 -#: /home/kovid/work/calibre/src/calibre/gui2/metadata/basic_widgets.py:591 -#: /home/kovid/work/calibre/src/calibre/gui2/metadata/basic_widgets.py:592 -msgid "No format selected" -msgstr "Nenhum formato seleccionado" - -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single.py:378 -#: /home/kovid/work/calibre/src/calibre/gui2/metadata/basic_widgets.py:603 -msgid "Could not read metadata" -msgstr "É impossível ler os metadados" - -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single.py:379 -#: /home/kovid/work/calibre/src/calibre/gui2/metadata/basic_widgets.py:604 -msgid "Could not read metadata from %s format" -msgstr "É impossível ler os metadados do formato %s" - -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single.py:453 -#: /home/kovid/work/calibre/src/calibre/gui2/metadata/basic_widgets.py:229 -msgid "" -" The green color indicates that the current author sort matches the current " -"author" -msgstr "" - -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single.py:456 -#: /home/kovid/work/calibre/src/calibre/gui2/metadata/basic_widgets.py:232 -msgid "" -" The red color indicates that the current author sort does not match the " -"current author. No action is required if this is what you want." -msgstr "" - -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single.py:463 -#: /home/kovid/work/calibre/src/calibre/gui2/metadata/basic_widgets.py:119 -msgid "" -" The green color indicates that the current title sort matches the current " -"title" -msgstr "" - -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single.py:466 -#: /home/kovid/work/calibre/src/calibre/gui2/metadata/basic_widgets.py:122 -msgid "" -" The red color warns that the current title sort does not match the current " -"title. No action is required if this is what you want." -msgstr "" - -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single.py:472 -#: /home/kovid/work/calibre/src/calibre/gui2/metadata/single.py:49 -#: /home/kovid/work/calibre/src/calibre/library/server/browse.py:102 -#: /home/kovid/work/calibre/src/calibre/web/feeds/templates.py:221 -#: /home/kovid/work/calibre/src/calibre/web/feeds/templates.py:384 -msgid "Previous" -msgstr "" - -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single.py:475 -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single.py:483 -#: /home/kovid/work/calibre/src/calibre/gui2/metadata/single.py:358 -#: /home/kovid/work/calibre/src/calibre/gui2/metadata/single.py:362 -msgid "Save changes and edit the metadata of %s" -msgstr "" - -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single.py:480 -#: /home/kovid/work/calibre/src/calibre/gui2/metadata/single.py:46 -#: /home/kovid/work/calibre/src/calibre/library/server/browse.py:103 -#: /home/kovid/work/calibre/src/calibre/web/feeds/templates.py:211 -#: /home/kovid/work/calibre/src/calibre/web/feeds/templates.py:401 -msgid "Next" -msgstr "" - -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single.py:688 -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single.py:693 -#: /home/kovid/work/calibre/src/calibre/gui2/metadata/basic_widgets.py:913 -msgid "This ISBN number is valid" -msgstr "" - -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single.py:696 -#: /home/kovid/work/calibre/src/calibre/gui2/metadata/basic_widgets.py:920 -msgid "This ISBN number is invalid" -msgstr "" - -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single.py:781 -#: /home/kovid/work/calibre/src/calibre/gui2/metadata/basic_widgets.py:862 -msgid "Tags changed" -msgstr "Etiquetas modificadas" - -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single.py:782 -#: /home/kovid/work/calibre/src/calibre/gui2/metadata/basic_widgets.py:863 -msgid "" -"You have changed the tags. In order to use the tags editor, you must either " -"discard or apply these changes. Apply changes?" -msgstr "" - -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single.py:817 -msgid "Timed out" -msgstr "" - -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single.py:818 -msgid "" -"The download of social metadata timed out, the servers are probably busy. " -"Try again later." -msgstr "" - -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single.py:825 -msgid "There were errors" -msgstr "" - -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single.py:826 -msgid "There were errors downloading social metadata" -msgstr "" - -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single.py:860 -msgid "Cannot fetch metadata" -msgstr "É impossível recolher os metadados" - -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single.py:861 -msgid "You must specify at least one of ISBN, Title, Authors or Publisher" -msgstr "Deve especificar pelo menos um de ISBN, Título, Autores ou Editora" - -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single.py:959 -#: /home/kovid/work/calibre/src/calibre/gui2/metadata/single.py:307 -msgid "Permission denied" -msgstr "Permissão negada" - -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single.py:960 -#: /home/kovid/work/calibre/src/calibre/gui2/metadata/single.py:308 -msgid "Could not open %s. Is it being used by another program?" -msgstr "É impossível abrir %s. Está a ser usado por outro programa?" - -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single_ui.py:406 -msgid "Edit Meta Information" -msgstr "Editar os metadados" - -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single_ui.py:407 -msgid "Meta information" -msgstr "Metadados" - -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single_ui.py:410 -#: /home/kovid/work/calibre/src/calibre/gui2/metadata/single.py:89 -msgid "" -"Automatically create the title sort entry based on the current title entry.\n" -"Using this button to create title sort will change title sort from red to " -"green." -msgstr "" - -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single_ui.py:413 -#: /home/kovid/work/calibre/src/calibre/gui2/metadata/single.py:111 -msgid "Swap the author and title" -msgstr "Trocar o autor e o título" - -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single_ui.py:415 -#: /home/kovid/work/calibre/src/calibre/gui2/metadata/single.py:100 -msgid "" -"Automatically create the author sort entry based on the current author " -"entry.\n" -"Using this button to create author sort will change author sort from red to " -"green." -msgstr "" - -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single_ui.py:418 -msgid "Title &sort: " -msgstr "" - -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single_ui.py:419 -#: /home/kovid/work/calibre/src/calibre/gui2/metadata/basic_widgets.py:109 -msgid "" -"Specify how this book should be sorted when by title. For example, The " -"Exorcist might be sorted as Exorcist, The." -msgstr "" - -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single_ui.py:421 -msgid "Author S&ort: " -msgstr "&Ordenação do(s) Autor(es): " - -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single_ui.py:422 -#: /home/kovid/work/calibre/src/calibre/gui2/metadata/basic_widgets.py:215 -msgid "" -"Specify how the author(s) of this book should be sorted. For example Charles " -"Dickens should be sorted as Dickens, Charles.\n" -"If the box is colored green, then text matches the individual author's sort " -"strings. If it is colored red, then the authors and this text do not match." -msgstr "" - -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single_ui.py:436 -#: /home/kovid/work/calibre/src/calibre/gui2/metadata/single.py:118 -msgid "Remove unused series (Series that have no books)" -msgstr "Remover as séries não usadas (Séries que não têm livros)" - -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single_ui.py:439 -#: /home/kovid/work/calibre/src/calibre/gui2/metadata/basic_widgets.py:886 -msgid "IS&BN:" -msgstr "IS&BN:" - -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single_ui.py:441 -msgid "dd MMM yyyy" -msgstr "dd MMM aaaa" - -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single_ui.py:442 -#: /home/kovid/work/calibre/src/calibre/gui2/metadata/basic_widgets.py:1029 -msgid "Publishe&d:" -msgstr "Edita&do:" - -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single_ui.py:445 -#: /home/kovid/work/calibre/src/calibre/gui2/metadata/single.py:156 -msgid "&Fetch metadata from server" -msgstr "&Recolher os metadados do servidor" - -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single_ui.py:448 -#: /home/kovid/work/calibre/src/calibre/gui2/metadata/basic_widgets.py:621 -msgid "&Browse" -msgstr "" - -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single_ui.py:449 -msgid "Remove border (if any) from cover" -msgstr "" - -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single_ui.py:450 -#: /home/kovid/work/calibre/src/calibre/gui2/metadata/basic_widgets.py:623 -msgid "T&rim" -msgstr "" - -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single_ui.py:451 -msgid "Reset cover to default" -msgstr "Reiniciar a capa para a predefinida" - -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single_ui.py:452 -#: /home/kovid/work/calibre/src/calibre/gui2/metadata/basic_widgets.py:625 -msgid "&Remove" -msgstr "" - -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single_ui.py:453 -#: /home/kovid/work/calibre/src/calibre/gui2/metadata/basic_widgets.py:631 -msgid "Download co&ver" -msgstr "" - -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single_ui.py:454 -msgid "Generate a default cover based on the title and author" -msgstr "" - -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single_ui.py:455 -#: /home/kovid/work/calibre/src/calibre/gui2/metadata/basic_widgets.py:632 -msgid "&Generate cover" -msgstr "" - -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single_ui.py:456 -msgid "Available Formats" -msgstr "Formatos disponíveis" - -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single_ui.py:457 -msgid "Add a new format for this book to the database" -msgstr "Adicionar um novo formato para este livro à base de dados" - -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single_ui.py:459 -msgid "Remove the selected formats for this book from the database." -msgstr "Remover os formatos seleccionados deste livro da base de dados" - -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single_ui.py:461 -#: /home/kovid/work/calibre/src/calibre/gui2/metadata/basic_widgets.py:446 -msgid "Set the cover for the book from the selected format" -msgstr "Definir a capa para o livro a partir do formato seleccionado" - -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single_ui.py:463 -msgid "Update metadata from the metadata in the selected format" -msgstr "" -"Actualizar os metadados a partir dos metadados do formato seleccionado" - -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single_ui.py:464 -#: /home/kovid/work/calibre/src/calibre/gui2/metadata/single.py:580 -msgid "&Comments" -msgstr "&Comentários" - #: /home/kovid/work/calibre/src/calibre/gui2/dialogs/password_ui.py:61 msgid "Password needed" msgstr "É necessário a sua palavra-passe" #: /home/kovid/work/calibre/src/calibre/gui2/dialogs/password_ui.py:63 -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/scheduler_ui.py:217 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/scheduler_ui.py:205 #: /home/kovid/work/calibre/src/calibre/gui2/preferences/server_ui.py:125 #: /home/kovid/work/calibre/src/calibre/gui2/wizard/send_email_ui.py:133 msgid "&Username:" msgstr "&Utilizador:" #: /home/kovid/work/calibre/src/calibre/gui2/dialogs/password_ui.py:64 -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/scheduler_ui.py:218 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/scheduler_ui.py:206 #: /home/kovid/work/calibre/src/calibre/gui2/preferences/server_ui.py:126 #: /home/kovid/work/calibre/src/calibre/gui2/wizard/send_email_ui.py:135 msgid "&Password:" msgstr "Pala&vra-passe:" #: /home/kovid/work/calibre/src/calibre/gui2/dialogs/password_ui.py:65 -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/scheduler_ui.py:219 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/scheduler_ui.py:207 #: /home/kovid/work/calibre/src/calibre/gui2/preferences/server_ui.py:130 -#: /home/kovid/work/calibre/src/calibre/gui2/wizard/send_email.py:172 +#: /home/kovid/work/calibre/src/calibre/gui2/wizard/send_email.py:173 msgid "&Show password" msgstr "&Mostrar a palavra-passe" @@ -9148,187 +9061,300 @@ msgstr "" msgid "Restoring database was successful" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/saved_search_editor.py:55 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/saved_search_editor.py:48 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/saved_search_editor.py:75 +msgid "Saved search already exists" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/saved_search_editor.py:49 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/saved_search_editor.py:76 +msgid "The saved search %s already exists, perhaps with different case" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/saved_search_editor.py:62 msgid "" "The current saved search will be permanently deleted. Are you sure?" msgstr "" "A procura actualmente guardada será permanente apagada. Tem a certeza " "?" -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/saved_search_editor_ui.py:88 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/saved_search_editor_ui.py:94 msgid "Saved Search Editor" msgstr "Editor de Procuras Guardadas" -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/saved_search_editor_ui.py:89 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/saved_search_editor_ui.py:95 msgid "Saved Search: " msgstr "Procura Guardada: " -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/saved_search_editor_ui.py:90 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/saved_search_editor_ui.py:96 msgid "Select a saved search to edit" msgstr "Seleccione uma procura guardada para editar" -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/saved_search_editor_ui.py:91 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/saved_search_editor_ui.py:97 msgid "Delete this selected saved search" msgstr "Apagar a procura guardada seleccionada" -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/saved_search_editor_ui.py:93 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/saved_search_editor_ui.py:99 msgid "Enter a new saved search name." msgstr "Digite um novo nome para esta procura guardada." -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/saved_search_editor_ui.py:94 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/saved_search_editor_ui.py:100 msgid "Add the new saved search" msgstr "Adicionar a nova procura guardada" -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/saved_search_editor_ui.py:96 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/saved_search_editor_ui.py:102 +msgid "Rename the current search to what is in the box" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/saved_search_editor_ui.py:104 msgid "Change the contents of the saved search" msgstr "Alterar o conteúdo da procura guardada" -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/scheduler.py:35 -msgid "&Search:" -msgstr "&Procurar:" +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/scheduler.py:42 +msgid "" +" Download this periodical every week on the specified days " +"after\n" +" the specified time. For example, if you choose: Monday " +"after\n" +" 9:00 AM, then the periodical will be download every Monday " +"as\n" +" soon after 9:00 AM as possible.\n" +" " +msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/scheduler.py:133 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/scheduler.py:61 +msgid "&Download after:" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/scheduler.py:91 +msgid "" +" Download this periodical every month, on the specified " +"days.\n" +" The download will happen as soon after the specified time " +"as\n" +" possible on the specified days of each month. For example,\n" +" if you choose the 1st and the 15th after 9:00 AM, the\n" +" periodical will be downloaded on the 1st and 15th of every\n" +" month, as soon after 9:00 AM as possible.\n" +" " +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/scheduler.py:103 +msgid "&Days of the month:" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/scheduler.py:105 +msgid "Comma separated list of days of the month. For example: 1, 15" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/scheduler.py:109 +msgid "Download &after:" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/scheduler.py:142 +msgid "" +" Download this periodical every x days. For example, if you\n" +" choose 30 days, the periodical will be downloaded every 30\n" +" days. Note that you can set periods of less than a day, " +"like\n" +" 0.1 days to download a periodical more than once a day.\n" +" " +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/scheduler.py:151 +msgid "&Download every:" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/scheduler.py:154 +msgid "every hour" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/scheduler.py:157 +msgid "days" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/scheduler.py:161 +msgid "" +"Note: You can set intervals of less than a day, by typing the value manually." +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/scheduler.py:196 +msgid "%s news sources" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/scheduler.py:311 msgid "Need username and password" msgstr "É necessário utilizador e palavra-passe" -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/scheduler.py:134 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/scheduler.py:312 msgid "You must provide a username and/or password to use this news source." msgstr "" "Deve fornecer um utilizador e/ou uma palavra-passe para usar esta fonte de " "notícias." -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/scheduler.py:173 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/scheduler.py:346 msgid "Account" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/scheduler.py:174 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/scheduler.py:347 msgid "(optional)" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/scheduler.py:175 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/scheduler.py:348 msgid "(required)" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/scheduler.py:192 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/scheduler.py:365 msgid "Created by: " msgstr "Criado por: " -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/scheduler.py:199 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/scheduler.py:372 msgid "Last downloaded: never" msgstr "Último descarregamento: nunca" -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/scheduler.py:214 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/scheduler.py:373 +msgid "never" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/scheduler.py:379 msgid "%d days, %d hours and %d minutes ago" msgstr "à %d dias, %d horas e %d minutos" -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/scheduler.py:216 -msgid "Last downloaded" -msgstr "Último descarregamento" +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/scheduler.py:393 +msgid "Last downloaded:" +msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/scheduler.py:240 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/scheduler.py:421 #: /home/kovid/work/calibre/src/calibre/gui2/dialogs/scheduler_ui.py:197 msgid "Schedule news download" msgstr "Programar o descarregamento de notícias" -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/scheduler.py:243 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/scheduler.py:424 msgid "Add a custom news source" msgstr "Adicionar uma fonte de notícias personalizada" -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/scheduler.py:248 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/scheduler.py:429 msgid "Download all scheduled new sources" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/scheduler.py:353 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/scheduler.py:534 msgid "No internet connection" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/scheduler.py:354 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/scheduler.py:535 msgid "Cannot download news as no internet connection is active" msgstr "" #: /home/kovid/work/calibre/src/calibre/gui2/dialogs/scheduler_ui.py:198 -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/user_profiles.py:321 -msgid "Recipes" -msgstr "Receitas" +msgid "&Search:" +msgstr "&Procurar:" #: /home/kovid/work/calibre/src/calibre/gui2/dialogs/scheduler_ui.py:199 -msgid "Download all scheduled recipes at once" -msgstr "Descarregar todas as receitas programadas de uma só vez" - -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/scheduler_ui.py:200 -msgid "Download &all scheduled" -msgstr "Descarregar &todas as progamadas" - -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/scheduler_ui.py:201 msgid "blurb" msgstr "excerto" -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/scheduler_ui.py:202 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/scheduler_ui.py:200 msgid "&Schedule for download:" msgstr "&Programar o descarregamento" -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/scheduler_ui.py:203 -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/scheduler_ui.py:213 -msgid "Every " -msgstr "Todos/as " - -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/scheduler_ui.py:212 -msgid "at" -msgstr "às" - -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/scheduler_ui.py:214 -msgid "" -"Interval at which to download this recipe. A value of zero means that the " -"recipe will be downloaded every hour." +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/scheduler_ui.py:201 +msgid "Days of week" msgstr "" -"Intervalo no qual descarregar esta receita. Um valor 0 quer dizer que a " -"receita vai ser descarregada de hora a hora." -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/scheduler_ui.py:215 -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/scheduler_ui.py:227 -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/user_profiles_ui.py:268 -msgid " days" -msgstr " dias" +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/scheduler_ui.py:202 +msgid "Days of month" +msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/scheduler_ui.py:216 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/scheduler_ui.py:203 +msgid "Every x days" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/scheduler_ui.py:204 msgid "&Account" msgstr "&Conta" -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/scheduler_ui.py:220 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/scheduler_ui.py:208 msgid "For the scheduling to work, you must leave calibre running." msgstr "Para a programação funcionar tem de deixar o calibre a executar." -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/scheduler_ui.py:221 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/scheduler_ui.py:209 msgid "&Schedule" msgstr "&Programar" -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/scheduler_ui.py:222 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/scheduler_ui.py:210 msgid "Add &title as tag" msgstr "Adicionar o &título como etiqueta" -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/scheduler_ui.py:223 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/scheduler_ui.py:211 msgid "&Extra tags:" msgstr "&Etiquetas extra:" -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/scheduler_ui.py:224 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/scheduler_ui.py:212 +msgid "" +"Maximum number of copies (issues) of this recipe to keep. Set to 0 to keep " +"all (disable)." +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/scheduler_ui.py:213 +msgid "&Keep at most:" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/scheduler_ui.py:214 +msgid "" +"

      When set, this option will cause calibre to keep, at most, the specified " +"number of issues of this periodical. Every time a new issue is downloaded, " +"the oldest one is deleted, if the total is larger than this number.\n" +"

      Note that this feature only works if you have the option to add the title " +"as tag checked, above.\n" +"

      Also, the setting for deleting periodicals older than a number of days, " +"below, takes priority over this setting." +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/scheduler_ui.py:217 +msgid "all issues" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/scheduler_ui.py:218 +msgid " issues" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/scheduler_ui.py:219 msgid "&Advanced" msgstr "&Avançado" -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/scheduler_ui.py:225 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/scheduler_ui.py:220 msgid "&Download now" msgstr "&Descarregar agora" -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/scheduler_ui.py:226 -msgid "" -"Delete downloaded news older than the specified number of days. Set to zero " -"to disable." +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/scheduler_ui.py:221 +msgid "&Delete downloaded news older than:" msgstr "" -"Apagar as notícias descarregadas mais antigas que o número de dias " -"especificado. Definir 0 para desactivar." -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/scheduler_ui.py:228 -msgid "Delete downloaded news older than " -msgstr "Apagar as notícias mais antigas que " +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/scheduler_ui.py:222 +msgid "" +"

      Delete downloaded news older than the specified number of days. Set to " +"zero to disable.\n" +"

      You can also control the maximum number of issues of a specific " +"periodical that are kept by clicking the Advanced tab for that periodical " +"above." +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/scheduler_ui.py:224 +msgid "never delete" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/scheduler_ui.py:225 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/user_profiles_ui.py:273 +msgid " days" +msgstr " dias" + +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/scheduler_ui.py:226 +msgid "Download all scheduled news sources at once" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/scheduler_ui.py:227 +msgid "Download &all scheduled" +msgstr "Descarregar &todas as progamadas" #: /home/kovid/work/calibre/src/calibre/gui2/dialogs/search_item_ui.py:41 msgid "contains" @@ -9352,51 +9378,63 @@ msgid "Negate" msgstr "Negar" #: /home/kovid/work/calibre/src/calibre/gui2/dialogs/search_ui.py:198 +#: /home/kovid/work/calibre/src/calibre/gui2/store/search/adv_search_builder_ui.py:176 msgid "Advanced Search" msgstr "Procura Avançada" #: /home/kovid/work/calibre/src/calibre/gui2/dialogs/search_ui.py:199 +#: /home/kovid/work/calibre/src/calibre/gui2/store/search/adv_search_builder_ui.py:177 msgid "&What kind of match to use:" msgstr "" #: /home/kovid/work/calibre/src/calibre/gui2/dialogs/search_ui.py:200 +#: /home/kovid/work/calibre/src/calibre/gui2/store/search/adv_search_builder_ui.py:178 msgid "Contains: the word or phrase matches anywhere in the metadata field" msgstr "" #: /home/kovid/work/calibre/src/calibre/gui2/dialogs/search_ui.py:201 +#: /home/kovid/work/calibre/src/calibre/gui2/store/search/adv_search_builder_ui.py:179 msgid "Equals: the word or phrase must match the entire metadata field" msgstr "" #: /home/kovid/work/calibre/src/calibre/gui2/dialogs/search_ui.py:202 +#: /home/kovid/work/calibre/src/calibre/gui2/store/search/adv_search_builder_ui.py:180 msgid "" "Regular expression: the expression must match anywhere in the metadata field" msgstr "" #: /home/kovid/work/calibre/src/calibre/gui2/dialogs/search_ui.py:203 +#: /home/kovid/work/calibre/src/calibre/gui2/store/search/adv_search_builder_ui.py:181 msgid "Find entries that have..." msgstr "Encontrar as entradas que têm..." #: /home/kovid/work/calibre/src/calibre/gui2/dialogs/search_ui.py:204 +#: /home/kovid/work/calibre/src/calibre/gui2/store/search/adv_search_builder_ui.py:182 msgid "&All these words:" msgstr "&Todas estas palavras:" #: /home/kovid/work/calibre/src/calibre/gui2/dialogs/search_ui.py:205 +#: /home/kovid/work/calibre/src/calibre/gui2/store/search/adv_search_builder_ui.py:183 msgid "This exact &phrase:" msgstr "Esta &frase exacta:" #: /home/kovid/work/calibre/src/calibre/gui2/dialogs/search_ui.py:206 +#: /home/kovid/work/calibre/src/calibre/gui2/store/search/adv_search_builder_ui.py:184 msgid "&One or more of these words:" msgstr "&Uma ou mais destas palavras:" #: /home/kovid/work/calibre/src/calibre/gui2/dialogs/search_ui.py:207 +#: /home/kovid/work/calibre/src/calibre/gui2/store/search/adv_search_builder_ui.py:185 msgid "But dont show entries that have..." msgstr "Mas não mostrar as entradas que têm..." #: /home/kovid/work/calibre/src/calibre/gui2/dialogs/search_ui.py:208 +#: /home/kovid/work/calibre/src/calibre/gui2/store/search/adv_search_builder_ui.py:186 msgid "Any of these &unwanted words:" msgstr "Alguma destas palavras &indesejadas:" #: /home/kovid/work/calibre/src/calibre/gui2/dialogs/search_ui.py:209 +#: /home/kovid/work/calibre/src/calibre/gui2/store/search/adv_search_builder_ui.py:187 msgid "" "See the User Manual for more help" @@ -9405,19 +9443,22 @@ msgstr "" "search-interface\">User Manual para obter mais ajuda (em inglês)." #: /home/kovid/work/calibre/src/calibre/gui2/dialogs/search_ui.py:210 +#: /home/kovid/work/calibre/src/calibre/gui2/store/search/adv_search_builder_ui.py:188 msgid "A&dvanced Search" msgstr "Procura Avança&da" #: /home/kovid/work/calibre/src/calibre/gui2/dialogs/search_ui.py:212 +#: /home/kovid/work/calibre/src/calibre/gui2/store/search/adv_search_builder_ui.py:190 msgid "Enter the title." msgstr "" #: /home/kovid/work/calibre/src/calibre/gui2/dialogs/search_ui.py:213 +#: /home/kovid/work/calibre/src/calibre/gui2/store/search/adv_search_builder_ui.py:191 msgid "&Author:" msgstr "" #: /home/kovid/work/calibre/src/calibre/gui2/dialogs/search_ui.py:215 -#: /home/kovid/work/calibre/src/calibre/gui2/metadata/basic_widgets.py:827 +#: /home/kovid/work/calibre/src/calibre/gui2/metadata/basic_widgets.py:908 msgid "Ta&gs:" msgstr "" @@ -9436,10 +9477,12 @@ msgstr "Digite as etiquetas separadas por espaços" #: /home/kovid/work/calibre/src/calibre/gui2/dialogs/search_ui.py:219 #: /home/kovid/work/calibre/src/calibre/gui2/preferences/template_functions_ui.py:101 +#: /home/kovid/work/calibre/src/calibre/gui2/store/search/adv_search_builder_ui.py:193 msgid "&Clear" msgstr "" #: /home/kovid/work/calibre/src/calibre/gui2/dialogs/search_ui.py:220 +#: /home/kovid/work/calibre/src/calibre/gui2/store/search/adv_search_builder_ui.py:194 msgid "Search only in specific fields:" msgstr "Procurar apenas no campos especificados:" @@ -9452,31 +9495,48 @@ msgid "Choose formats" msgstr "" #: /home/kovid/work/calibre/src/calibre/gui2/dialogs/tag_categories.py:60 -#: /home/kovid/work/calibre/src/calibre/library/field_metadata.py:98 +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/metadata_sources.py:146 +#: /home/kovid/work/calibre/src/calibre/library/field_metadata.py:102 msgid "Authors" msgstr "Autor(es)" #: /home/kovid/work/calibre/src/calibre/gui2/dialogs/tag_categories.py:60 -#: /home/kovid/work/calibre/src/calibre/library/field_metadata.py:129 +#: /home/kovid/work/calibre/src/calibre/library/field_metadata.py:136 msgid "Publishers" msgstr "Editoras" -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/tag_categories.py:128 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/tag_categories.py:143 msgid " (not on any book)" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/tag_categories.py:175 -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/tag_categories.py:197 -#: /home/kovid/work/calibre/src/calibre/gui2/preferences/template_functions.py:151 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/tag_categories.py:146 +msgid "Category lookup name: " +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/tag_categories.py:191 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/tag_categories.py:222 +msgid "Invalid name" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/tag_categories.py:192 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/tag_categories.py:223 +msgid "" +"That name contains leading or trailing periods, multiple periods in a row or " +"spaces before or after periods." +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/tag_categories.py:200 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/tag_categories.py:230 +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/template_functions.py:152 msgid "Name already used" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/tag_categories.py:176 -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/tag_categories.py:198 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/tag_categories.py:201 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/tag_categories.py:231 msgid "That name is already used, perhaps with different case." msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/tag_categories.py:211 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/tag_categories.py:244 msgid "" "The current tag category will be permanently deleted. Are you sure?" msgstr "" @@ -9536,7 +9596,7 @@ msgid "Unapply (remove) tag from current tag category" msgstr "A etiqueta será apagada da actual categoria de etiquetas" #: /home/kovid/work/calibre/src/calibre/gui2/dialogs/tag_editor.py:70 -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/tag_list_editor.py:109 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/tag_list_editor.py:111 msgid "Are your sure?" msgstr "Tem a certeza?" @@ -9597,33 +9657,33 @@ msgstr "" msgid "%s (was %s)" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/tag_list_editor.py:83 -#: /home/kovid/work/calibre/src/calibre/gui2/tag_view.py:906 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/tag_list_editor.py:85 +#: /home/kovid/work/calibre/src/calibre/gui2/tag_view.py:1385 msgid "Item is blank" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/tag_list_editor.py:84 -#: /home/kovid/work/calibre/src/calibre/gui2/tag_view.py:907 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/tag_list_editor.py:86 +#: /home/kovid/work/calibre/src/calibre/gui2/tag_view.py:1386 msgid "An item cannot be set to nothing. Delete it instead." msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/tag_list_editor.py:97 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/tag_list_editor.py:99 msgid "No item selected" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/tag_list_editor.py:98 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/tag_list_editor.py:100 msgid "You must select one item from the list of Available items." msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/tag_list_editor.py:105 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/tag_list_editor.py:107 msgid "No items selected" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/tag_list_editor.py:106 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/tag_list_editor.py:108 msgid "You must select at least one items from the list." msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/tag_list_editor.py:110 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/tag_list_editor.py:112 msgid "Are you certain you want to delete the following items?" msgstr "" @@ -9674,28 +9734,16 @@ msgid "Send test mail from %s to:" msgstr "Enviar um email de teste de %s para:" #: /home/kovid/work/calibre/src/calibre/gui2/dialogs/test_email_ui.py:58 -#: /home/kovid/work/calibre/src/calibre/gui2/filename_pattern_ui.py:120 +#: /home/kovid/work/calibre/src/calibre/gui2/filename_pattern_ui.py:134 msgid "&Test" msgstr "&Teste" -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/tweak_epub_ui.py:55 -msgid "Display contents of exploded ePub" +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/tweak_epub.py:100 +msgid "Cannot preview" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/tweak_epub_ui.py:56 -msgid "&Explode ePub" -msgstr "" - -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/tweak_epub_ui.py:57 -msgid "Rebuild ePub from exploded contents" -msgstr "" - -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/tweak_epub_ui.py:58 -msgid "&Rebuild ePub" -msgstr "" - -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/tweak_epub_ui.py:59 -msgid "Discard changes" +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/tweak_epub.py:101 +msgid "You must first explode the epub before previewing." msgstr "" #: /home/kovid/work/calibre/src/calibre/gui2/dialogs/tweak_epub_ui.py:61 @@ -9707,116 +9755,148 @@ msgid "" "updating your calibre library.

      " msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/user_profiles.py:133 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/tweak_epub_ui.py:62 +msgid "Display contents of exploded ePub" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/tweak_epub_ui.py:63 +msgid "&Explode ePub" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/tweak_epub_ui.py:64 +msgid "Discard changes" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/tweak_epub_ui.py:66 +msgid "Rebuild ePub from exploded contents" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/tweak_epub_ui.py:67 +msgid "&Rebuild ePub" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/tweak_epub_ui.py:68 +msgid "&Preview ePub" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/user_profiles.py:141 msgid "No recipe selected" msgstr "Nenhuma receita seleccionada" -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/user_profiles.py:138 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/user_profiles.py:146 msgid "The attached file: %s is a recipe to download %s." msgstr "O ficheiro anexo: %s é a receita para descarregar %s." -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/user_profiles.py:139 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/user_profiles.py:147 msgid "Recipe for " msgstr "Receita para " -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/user_profiles.py:156 -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/user_profiles.py:167 -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/user_profiles_ui.py:260 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/user_profiles.py:164 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/user_profiles.py:175 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/user_profiles_ui.py:265 msgid "Switch to Advanced mode" msgstr "Mudar para o Modo Avançado" -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/user_profiles.py:162 #: /home/kovid/work/calibre/src/calibre/gui2/dialogs/user_profiles.py:170 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/user_profiles.py:178 msgid "Switch to Basic mode" msgstr "Mudar para o Modo Básico" -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/user_profiles.py:180 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/user_profiles.py:188 msgid "Feed must have a title" msgstr "A fonte deve ter um título" -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/user_profiles.py:181 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/user_profiles.py:189 msgid "The feed must have a title" msgstr "A fonte deve ter um título" -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/user_profiles.py:185 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/user_profiles.py:193 msgid "Feed must have a URL" msgstr "A fonte deve ter um URL" -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/user_profiles.py:186 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/user_profiles.py:194 msgid "The feed %s must have a URL" msgstr "A fonte %s deve ter um URL" -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/user_profiles.py:192 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/user_profiles.py:200 msgid "This feed has already been added to the recipe" msgstr "Esta fonte já foi adicionada à receita" -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/user_profiles.py:233 -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/user_profiles.py:242 -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/user_profiles.py:329 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/user_profiles.py:241 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/user_profiles.py:250 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/user_profiles.py:337 msgid "Invalid input" msgstr "Ficheiro de origem inválido" -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/user_profiles.py:234 -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/user_profiles.py:243 -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/user_profiles.py:330 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/user_profiles.py:242 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/user_profiles.py:251 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/user_profiles.py:338 msgid "

      Could not create recipe. Error:
      %s" msgstr "

      É impossível criar a receita. Erro:
      %s" -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/user_profiles.py:247 -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/user_profiles.py:306 -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/user_profiles.py:333 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/user_profiles.py:255 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/user_profiles.py:314 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/user_profiles.py:341 msgid "Replace recipe?" msgstr "Substituir a receita?" -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/user_profiles.py:248 -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/user_profiles.py:307 -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/user_profiles.py:334 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/user_profiles.py:256 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/user_profiles.py:315 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/user_profiles.py:342 msgid "A custom recipe named %s already exists. Do you want to replace it?" msgstr "A receita personalizada %s já existe. Quer substituí-la?" -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/user_profiles.py:274 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/user_profiles.py:282 msgid "Choose builtin recipe" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/user_profiles.py:320 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/user_profiles.py:328 msgid "Choose a recipe file" msgstr "Escolher um ficheiro de receita" -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/user_profiles.py:361 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/user_profiles.py:329 +msgid "Recipes" +msgstr "Receitas" + +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/user_profiles.py:369 msgid "" "You will lose any unsaved changes. To save your changes, click the " "Add/Update recipe button. Continue?" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/user_profiles_ui.py:253 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/user_profiles_ui.py:257 msgid "Add custom news source" msgstr "Adicionar uma fonte de notícias personalizada" -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/user_profiles_ui.py:254 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/user_profiles_ui.py:258 msgid "Available user recipes" msgstr "Receitas de utilizadores" -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/user_profiles_ui.py:255 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/user_profiles_ui.py:259 msgid "Add/Update &recipe" msgstr "&Adicionar/Actualizar a receita" -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/user_profiles_ui.py:256 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/user_profiles_ui.py:260 msgid "&Remove recipe" msgstr "&Remover a receita" -#: /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:261 msgid "&Share recipe" msgstr "&Partilhar a receita" -#: /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:262 +msgid "S&how recipe files" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/user_profiles_ui.py:263 msgid "Customize &builtin recipe" msgstr "Personalizar a receita &integrada" -#: /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:264 msgid "&Load recipe from file" msgstr "&Carregar a receita do ficheiro" -#: /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:266 msgid "" "