diff --git a/COPYRIGHT b/COPYRIGHT index 16f64b7d09..1d8f7a5793 100644 --- a/COPYRIGHT +++ b/COPYRIGHT @@ -352,7 +352,7 @@ License: other Liberation Fonts ----------------- calibre includes a copy of the liberation fonts, available from -https://fedorahosted.org/liberation-fonts +https://calibre.kovidgoyal.net/downloads/liberation-fonts BSD License (for all the BSD licensed code indicated above) ----------------------------------------------------------- diff --git a/setup.py b/setup.py index 407b852a57..ee2d54cc5a 100644 --- a/setup.py +++ b/setup.py @@ -72,6 +72,9 @@ if __name__ == '__main__': library_dirs=[os.environ.get('PODOFO_LIB_DIR', podofo_lib)], include_dirs=\ [os.environ.get('PODOFO_INC_DIR', podofo_inc)])) + else: + print 'WARNING: PoDoFo not found on your system. Various PDF related', + print 'functionality will not work.' ext_modules = optional + [ diff --git a/src/calibre/__init__.py b/src/calibre/__init__.py index a08f0417ee..79dc659f34 100644 --- a/src/calibre/__init__.py +++ b/src/calibre/__init__.py @@ -45,6 +45,13 @@ def to_unicode(raw, encoding='utf-8', errors='strict'): return raw return raw.decode(encoding, errors) +def patheq(p1, p2): + p = os.path + d = lambda x : p.normcase(p.normpath(p.realpath(p.normpath(x)))) + if not p1 or not p2: + return False + return d(p1) == d(p2) + def unicode_path(path, abs=False): if not isinstance(path, unicode): path = path.decode(sys.getfilesystemencoding()) diff --git a/src/calibre/constants.py b/src/calibre/constants.py index e1041a7b94..e7f7ffc344 100644 --- a/src/calibre/constants.py +++ b/src/calibre/constants.py @@ -2,8 +2,14 @@ __license__ = 'GPL v3' __copyright__ = '2008, Kovid Goyal kovid@kovidgoyal.net' __docformat__ = 'restructuredtext en' __appname__ = 'calibre' -__version__ = '0.5.13' +__version__ = '0.6.0b1' __author__ = "Kovid Goyal " + +import re +_ver = __version__.split('.') +_ver = [int(re.search(r'(\d+)', x).group(1)) for x in _ver] +numeric_version = tuple(_ver) + ''' Various run time constants. ''' diff --git a/src/calibre/customize/__init__.py b/src/calibre/customize/__init__.py index 0e6bad8d2e..deec0fcedf 100644 --- a/src/calibre/customize/__init__.py +++ b/src/calibre/customize/__init__.py @@ -5,58 +5,58 @@ __copyright__ = '2008, Kovid Goyal ' import sys from calibre.ptempfile import PersistentTemporaryFile -from calibre.constants import __version__, __author__ +from calibre.constants import numeric_version class Plugin(object): ''' A calibre plugin. Useful members include: - + * ``self.plugin_path``: Stores path to the zip file that contains this plugin or None if it is a builtin plugin * ``self.site_customization``: Stores a customization string entered by the user. - + Methods that should be overridden in sub classes: - + * :meth:`initialize` * :meth:`customization_help` - + Useful methods: - + * :meth:`temporary_file` - + ''' #: List of platforms this plugin works on #: For example: ``['windows', 'osx', 'linux'] supported_platforms = [] - + #: The name of this plugin name = 'Trivial Plugin' - + #: The version of this plugin as a 3-tuple (major, minor, revision) version = (1, 0, 0) - + #: A short string describing what this plugin does description = _('Does absolutely nothing') - + #: The author of this plugin author = _('Unknown') - + #: When more than one plugin exists for a filetype, #: the plugins are run in order of decreasing priority #: i.e. plugins with higher priority will be run first. #: The highest possible priority is ``sys.maxint``. #: Default pririty is 1. priority = 1 - + #: The earliest version of calibre this plugin requires minimum_calibre_version = (0, 4, 118) - + #: If False, the user will not be able to disable this plugin. Use with #: care. can_be_disabled = True - + #: The type of this plugin. Used for categorizing plugins in the #: GUI type = _('Base') @@ -64,109 +64,109 @@ class Plugin(object): def __init__(self, plugin_path): self.plugin_path = plugin_path self.site_customization = None - + def initialize(self): ''' Called once when calibre plugins are initialized. Plugins are re-initialized every time a new plugin is added. - + Perform any plugin specific initialization here, such as extracting resources from the plugin zip file. The path to the zip file is available as ``self.plugin_path``. - + Note that ``self.site_customization`` is **not** available at this point. ''' pass - + def customization_help(self, gui=False): ''' Return a string giving help on how to customize this plugin. By default raise a :class:`NotImplementedError`, which indicates that the plugin does not require customization. - + If you re-implement this method in your subclass, the user will be asked to enter a string as customization for this plugin. - The customization string will be available as + The customization string will be available as ``self.site_customization``. - + Site customization could be anything, for example, the path to a needed binary on the user's computer. - - :param gui: If True return HTML help, otherwise return plain text help. - + + :param gui: If True return HTML help, otherwise return plain text help. + ''' raise NotImplementedError - + def temporary_file(self, suffix): ''' Return a file-like object that is a temporary file on the file system. This file will remain available even after being closed and will only be removed on interpreter shutdown. Use the ``name`` member of the returned object to access the full path to the created temporary file. - + :param suffix: The suffix that the temporary file will have. ''' return PersistentTemporaryFile(suffix) - + def is_customizable(self): try: self.customization_help() return True except NotImplementedError: return False - + def __enter__(self, *args): if self.plugin_path is not None: sys.path.insert(0, self.plugin_path) - + def __exit__(self, *args): if self.plugin_path in sys.path: sys.path.remove(self.plugin_path) - - + + class FileTypePlugin(Plugin): ''' A plugin that is associated with a particular set of file types. ''' - + #: Set of file types for which this plugin should be run #: For example: ``set(['lit', 'mobi', 'prc'])`` file_types = set([]) - + #: If True, this plugin is run when books are added #: to the database on_import = False - + #: If True, this plugin is run whenever an any2* tool #: is used, on the file passed to the any2* tool. on_preprocess = False - + #: If True, this plugin is run after an any2* tool is #: used, on the final file produced by the tool. on_postprocess = False - + type = _('File type') def run(self, path_to_ebook): ''' Run the plugin. Must be implemented in subclasses. - It should perform whatever modifications are required - on the ebook and return the absolute path to the + It should perform whatever modifications are required + on the ebook and return the absolute path to the modified ebook. If no modifications are needed, it should return the path to the original ebook. If an error is encountered it should raise an Exception. The default implementation simply return the path to the original ebook. - - The modified ebook file should be created with the + + The modified ebook file should be created with the :meth:`temporary_file` method. - + :param path_to_ebook: Absolute path to the ebook. - :return: Absolute path to the modified ebook. + :return: Absolute path to the modified ebook. ''' # Default implementation does nothing return path_to_ebook - + class MetadataReaderPlugin(Plugin): ''' A plugin that implements reading metadata from a set of file types. @@ -174,26 +174,26 @@ class MetadataReaderPlugin(Plugin): #: Set of file types for which this plugin should be run #: For example: ``set(['lit', 'mobi', 'prc'])`` file_types = set([]) - + supported_platforms = ['windows', 'osx', 'linux'] - version = tuple(map(int, (__version__.split('.'))[:3])) + version = numeric_version author = 'Kovid Goyal' - + type = _('Metadata reader') - + def get_metadata(self, stream, type): ''' Return metadata for the file represented by stream (a file like object - that supports reading). Raise an exception when there is an error + that supports reading). Raise an exception when there is an error with the input data. - + :param type: The type of file. Guaranteed to be one of the entries in :attr:`file_types`. - :return: A :class:`calibre.ebooks.metadata.MetaInformation` object + :return: A :class:`calibre.ebooks.metadata.MetaInformation` object ''' return None - + class MetadataWriterPlugin(Plugin): ''' A plugin that implements reading metadata from a set of file types. @@ -201,24 +201,24 @@ class MetadataWriterPlugin(Plugin): #: Set of file types for which this plugin should be run #: For example: ``set(['lit', 'mobi', 'prc'])`` file_types = set([]) - + supported_platforms = ['windows', 'osx', 'linux'] - version = tuple(map(int, (__version__.split('.'))[:3])) + version = numeric_version author = 'Kovid Goyal' - + type = _('Metadata writer') - + def set_metadata(self, stream, mi, type): ''' Set metadata for the file represented by stream (a file like object - that supports reading). Raise an exception when there is an error + that supports reading). Raise an exception when there is an error with the input data. - + :param type: The type of file. Guaranteed to be one of the entries in :attr:`file_types`. - :param mi: A :class:`calibre.ebooks.metadata.MetaInformation` object + :param mi: A :class:`calibre.ebooks.metadata.MetaInformation` object ''' pass - + diff --git a/src/calibre/customize/builtins.py b/src/calibre/customize/builtins.py index d107413e38..54fa0e862e 100644 --- a/src/calibre/customize/builtins.py +++ b/src/calibre/customize/builtins.py @@ -5,7 +5,7 @@ import textwrap import os import glob from calibre.customize import FileTypePlugin, MetadataReaderPlugin, MetadataWriterPlugin -from calibre.constants import __version__ +from calibre.constants import numeric_version class HTML2ZIP(FileTypePlugin): name = 'HTML to ZIP' @@ -15,7 +15,7 @@ Follow all local links in an HTML file and create a ZIP \ file containing all linked files. This plugin is run \ every time you add an HTML file to the library.\ ''')) - version = tuple(map(int, (__version__.split('.'))[:3])) + version = numeric_version file_types = set(['html', 'htm', 'xhtml', 'xhtm']) supported_platforms = ['windows', 'osx', 'linux'] on_import = True diff --git a/src/calibre/customize/ui.py b/src/calibre/customize/ui.py index f6ab19a910..2227f6d6f5 100644 --- a/src/calibre/customize/ui.py +++ b/src/calibre/customize/ui.py @@ -9,13 +9,12 @@ from calibre.customize import Plugin, FileTypePlugin, MetadataReaderPlugin, \ from calibre.customize.conversion import InputFormatPlugin, OutputFormatPlugin from calibre.customize.profiles import InputProfile, OutputProfile from calibre.customize.builtins import plugins as builtin_plugins -from calibre.constants import __version__, iswindows, isosx +from calibre.constants import numeric_version as version, iswindows, isosx from calibre.devices.interface import DevicePlugin from calibre.ebooks.metadata import MetaInformation from calibre.utils.config import make_config_dir, Config, ConfigProxy, \ plugin_dir, OptionParser -version = tuple([int(x) for x in __version__.split('.')]) platform = 'linux' if iswindows: diff --git a/src/calibre/ebooks/compression/palmdoc.c b/src/calibre/ebooks/compression/palmdoc.c index 29e9579140..b85a404eb6 100644 --- a/src/calibre/ebooks/compression/palmdoc.c +++ b/src/calibre/ebooks/compression/palmdoc.c @@ -14,8 +14,6 @@ #include #include -#define DELTA sizeof(Byte)*4096 - #define BUFFER 6000 #define MIN(x, y) ( ((x) < (y)) ? (x) : (y) ) @@ -46,26 +44,25 @@ typedef struct { static PyObject * cpalmdoc_decompress(PyObject *self, PyObject *args) { const char *_input = NULL; Py_ssize_t input_len = 0; + Byte *input; char *output; Byte c; PyObject *ans; Py_ssize_t i = 0, o = 0, j = 0, di, n; if (!PyArg_ParseTuple(args, "t#", &_input, &input_len)) return NULL; - Byte *input = (Byte *)PyMem_Malloc(sizeof(Byte)*input_len); + input = (Byte *) PyMem_Malloc(sizeof(Byte)*input_len); if (input == NULL) return PyErr_NoMemory(); // Map chars to bytes for (j = 0; j < input_len; j++) input[j] = (_input[j] < 0) ? _input[j]+256 : _input[j]; - char *output = (char *)PyMem_Malloc(sizeof(char)*BUFFER); - Byte c; - PyObject *ans; + output = (char *)PyMem_Malloc(sizeof(char)*BUFFER); if (output == NULL) return PyErr_NoMemory(); while (i < input_len) { c = input[i++]; if (c >= 1 && c <= 8) // copy 'c' bytes - while (c--) output[o++] = input[i++]; + while (c--) output[o++] = (char)input[i++]; else if (c <= 0x7F) // 0, 09-7F = self - output[o++] = c; + output[o++] = (char)c; else if (c >= 0xC0) { // space + ASCII char output[o++] = ' '; @@ -107,8 +104,8 @@ cpalmdoc_do_compress(buffer *b, char *output) { Byte c, n; bool found; char *head; - head = output; buffer temp; + head = output; temp.data = (Byte *)PyMem_Malloc(sizeof(Byte)*8); temp.len = 0; if (temp.data == NULL) return 0; while (i < b->len) { @@ -151,7 +148,7 @@ cpalmdoc_do_compress(buffer *b, char *output) { } i += temp.len - 1; *(output++) = temp.len; - for (j=0; j < temp.len; j++) *(output++) = temp.data[j]; + for (j=0; j < temp.len; j++) *(output++) = (char)temp.data[j]; } } return output - head; @@ -160,6 +157,7 @@ cpalmdoc_do_compress(buffer *b, char *output) { static PyObject * cpalmdoc_compress(PyObject *self, PyObject *args) { const char *_input = NULL; Py_ssize_t input_len = 0; + char *output; PyObject *ans; Py_ssize_t j = 0; buffer b; if (!PyArg_ParseTuple(args, "t#", &_input, &input_len)) @@ -170,11 +168,11 @@ cpalmdoc_compress(PyObject *self, PyObject *args) { for (j = 0; j < input_len; j++) b.data[j] = (_input[j] < 0) ? _input[j]+256 : _input[j]; b.len = input_len; - char *output = (char *)PyMem_Malloc(sizeof(char) * b.len); + output = (char *)PyMem_Malloc(sizeof(char) * b.len); if (output == NULL) return PyErr_NoMemory(); j = cpalmdoc_do_compress(&b, output); if ( j == 0) return PyErr_NoMemory(); - PyObject *ans = Py_BuildValue("s#", output, j); + ans = Py_BuildValue("s#", output, j); PyMem_Free(output); PyMem_Free(b.data); return ans; diff --git a/src/calibre/ebooks/metadata/__init__.py b/src/calibre/ebooks/metadata/__init__.py index 0174bf1ec3..6d0d14f6c2 100644 --- a/src/calibre/ebooks/metadata/__init__.py +++ b/src/calibre/ebooks/metadata/__init__.py @@ -42,6 +42,31 @@ def title_sort(title): title = title.replace(prep, '') + ', ' + prep return title.strip() +coding = zip( +[1000,900,500,400,100,90,50,40,10,9,5,4,1], +["M","CM","D","CD","C","XC","L","XL","X","IX","V","IV","I"] +) + + + +def roman(num): + if num <= 0 or num >= 4000 or int(num) != num: + return str(num) + result = [] + for d, r in coding: + while num >= d: + result.append(r) + num -= d + return ''.join(result) + + +def fmt_sidx(i, fmt='%.2f', use_roman=False): + if i is None: + i = 1 + if int(i) == i: + return roman(i) if use_roman else '%d'%i + return fmt%i + class Resource(object): ''' @@ -187,7 +212,8 @@ class MetaInformation(object): 'publisher', 'series', 'series_index', 'rating', 'isbn', 'tags', 'cover_data', 'application_id', 'guide', 'manifest', 'spine', 'toc', 'cover', 'language', - 'book_producer', 'timestamp', 'lccn', 'lcc', 'ddc'): + 'book_producer', 'timestamp', 'lccn', 'lcc', 'ddc', + 'pubdate'): if hasattr(mi, attr): setattr(ans, attr, getattr(mi, attr)) @@ -212,7 +238,7 @@ class MetaInformation(object): for x in ('author_sort', 'title_sort', 'comments', 'category', 'publisher', 'series', 'series_index', 'rating', 'isbn', 'language', 'application_id', 'manifest', 'toc', 'spine', 'guide', 'cover', - 'book_producer', 'timestamp', 'lccn', 'lcc', 'ddc' + 'book_producer', 'timestamp', 'lccn', 'lcc', 'ddc', 'pubdate' ): setattr(self, x, getattr(mi, x, None)) @@ -231,7 +257,7 @@ class MetaInformation(object): 'publisher', 'series', 'series_index', 'rating', 'isbn', 'application_id', 'manifest', 'spine', 'toc', 'cover', 'language', 'guide', 'book_producer', - 'timestamp', 'lccn', 'lcc', 'ddc'): + 'timestamp', 'lccn', 'lcc', 'ddc', 'pubdate'): if hasattr(mi, attr): val = getattr(mi, attr) if val is not None: @@ -262,8 +288,8 @@ class MetaInformation(object): try: x = float(self.series_index) except ValueError: - x = 1.0 - return '%d'%x if int(x) == x else '%.2f'%x + x = 1 + return fmt_sidx(x) def authors_from_string(self, raw): self.authors = string_to_authors(raw) @@ -299,6 +325,8 @@ class MetaInformation(object): fmt('Rating', self.rating) if self.timestamp is not None: fmt('Timestamp', self.timestamp.isoformat(' ')) + if self.pubdate is not None: + fmt('Published', self.pubdate.isoformat(' ')) if self.lccn: fmt('LCCN', unicode(self.lccn)) if self.lcc: @@ -327,6 +355,8 @@ class MetaInformation(object): ans += [(_('Language'), unicode(self.language))] if self.timestamp is not None: ans += [(_('Timestamp'), unicode(self.timestamp.isoformat(' ')))] + if self.pubdate is not None: + ans += [(_('Published'), unicode(self.pubdate.isoformat(' ')))] for i, x in enumerate(ans): ans[i] = u'%s%s'%x return u'%s
'%u'\n'.join(ans) diff --git a/src/calibre/ebooks/metadata/fb2.py b/src/calibre/ebooks/metadata/fb2.py index 5b85f935a4..e81f8fe108 100644 --- a/src/calibre/ebooks/metadata/fb2.py +++ b/src/calibre/ebooks/metadata/fb2.py @@ -5,7 +5,7 @@ __copyright__ = '2008, Anatoly Shipitsin ' '''Read meta information from fb2 files''' -import sys, os, mimetypes +import mimetypes from base64 import b64decode from calibre.ebooks.BeautifulSoup import BeautifulStoneSoup @@ -32,7 +32,7 @@ def get_metadata(stream): if not exts: exts = ['.jpg'] cdata = (exts[0][1:], b64decode(binary.string.strip())) - + if comments: comments = u''.join(comments.findAll(text=True)) series = soup.find("sequence") @@ -42,7 +42,7 @@ def get_metadata(stream): if series: mi.series = series.get('name', None) try: - mi.series_index = int(series.get('number', None)) + mi.series_index = float(series.get('number', None)) except (TypeError, ValueError): pass if cdata: diff --git a/src/calibre/ebooks/metadata/meta.py b/src/calibre/ebooks/metadata/meta.py index 1460a97eee..9a11ea4fca 100644 --- a/src/calibre/ebooks/metadata/meta.py +++ b/src/calibre/ebooks/metadata/meta.py @@ -145,7 +145,7 @@ def metadata_from_filename(name, pat=None): pass try: si = match.group('series_index') - mi.series_index = int(si) + mi.series_index = float(si) except (IndexError, ValueError, TypeError): pass try: diff --git a/src/calibre/ebooks/metadata/opf.py b/src/calibre/ebooks/metadata/opf.py index 94264a285e..f508ffd517 100644 --- a/src/calibre/ebooks/metadata/opf.py +++ b/src/calibre/ebooks/metadata/opf.py @@ -2,8 +2,7 @@ __license__ = 'GPL v3' __copyright__ = '2008, Kovid Goyal ' '''Read/Write metadata from Open Packaging Format (.opf) files.''' -import sys, re, os, glob -import cStringIO +import re, os import uuid from urllib import unquote, quote @@ -15,14 +14,14 @@ from calibre.ebooks.metadata import Resource, ResourceCollection from calibre.ebooks.metadata.toc import TOC class OPFSoup(BeautifulStoneSoup): - + def __init__(self, raw): - BeautifulStoneSoup.__init__(self, raw, + BeautifulStoneSoup.__init__(self, raw, convertEntities=BeautifulSoup.HTML_ENTITIES, selfClosingTags=['item', 'itemref', 'reference']) class ManifestItem(Resource): - + @staticmethod def from_opf_manifest_item(item, basedir): if item.has_key('href'): @@ -37,7 +36,7 @@ class ManifestItem(Resource): if mt: res.mime_type = mt return res - + @dynamic_property def media_type(self): def fget(self): @@ -45,28 +44,28 @@ class ManifestItem(Resource): def fset(self, val): self.mime_type = val return property(fget=fget, fset=fset) - - + + def __unicode__(self): return u''%(self.id, self.href(), self.media_type) - + def __str__(self): return unicode(self).encode('utf-8') - + def __repr__(self): return unicode(self) - - + + def __getitem__(self, index): if index == 0: return self.href() if index == 1: return self.media_type raise IndexError('%d out of bounds.'%index) - + class Manifest(ResourceCollection): - + @staticmethod def from_opf_manifest_element(manifest, dir): m = Manifest() @@ -81,7 +80,7 @@ class Manifest(ResourceCollection): except ValueError: continue return m - + @staticmethod def from_paths(entries): ''' @@ -96,37 +95,37 @@ class Manifest(ResourceCollection): m.next_id += 1 m.append(mi) return m - + def __init__(self): ResourceCollection.__init__(self) self.next_id = 1 - - + + def item(self, id): for i in self: if i.id == id: return i - + def id_for_path(self, path): path = os.path.normpath(os.path.abspath(path)) for i in self: if i.path and os.path.normpath(i.path) == path: - return i.id - + return i.id + def path_for_id(self, id): for i in self: if i.id == id: return i.path class Spine(ResourceCollection): - + class Item(Resource): - + def __init__(self, idfunc, *args, **kwargs): Resource.__init__(self, *args, **kwargs) self.is_linear = True self.id = idfunc(self.path) - + @staticmethod def from_opf_spine_element(spine, manifest): s = Spine(manifest) @@ -137,7 +136,7 @@ class Spine(ResourceCollection): r.is_linear = itemref.get('linear', 'yes') == 'yes' s.append(r) return s - + @staticmethod def from_paths(paths, manifest): s = Spine(manifest) @@ -147,14 +146,14 @@ class Spine(ResourceCollection): except: continue return s - - - + + + def __init__(self, manifest): ResourceCollection.__init__(self) self.manifest = manifest - - + + def linear_items(self): for r in self: if r.is_linear: @@ -164,16 +163,16 @@ class Spine(ResourceCollection): for r in self: if not r.is_linear: yield r.path - + def items(self): for i in self: yield i.path - - + + class Guide(ResourceCollection): - + class Reference(Resource): - + @staticmethod def from_opf_resource_item(ref, basedir): title, href, type = ref.get('title', ''), ref['href'], ref['type'] @@ -181,14 +180,14 @@ class Guide(ResourceCollection): res.title = title res.type = type return res - + def __repr__(self): ans = '' - - + + @staticmethod def from_opf_guide(guide_elem, base_dir=os.getcwdu()): coll = Guide() @@ -199,29 +198,29 @@ class Guide(ResourceCollection): except: continue return coll - + def set_cover(self, path): map(self.remove, [i for i in self if 'cover' in i.type.lower()]) for type in ('cover', 'other.ms-coverimage-standard', 'other.ms-coverimage'): self.append(Guide.Reference(path, is_path=True)) self[-1].type = type self[-1].title = '' - + class standard_field(object): - + def __init__(self, name): self.name = name - + def __get__(self, obj, typ=None): return getattr(obj, 'get_'+self.name)() - + class OPF(MetaInformation): - + MIMETYPE = 'application/oebps-package+xml' ENTITY_PATTERN = re.compile(r'&(\S+?);') - + uid = standard_field('uid') application_id = standard_field('application_id') title = standard_field('title') @@ -238,29 +237,29 @@ class OPF(MetaInformation): series_index = standard_field('series_index') rating = standard_field('rating') tags = standard_field('tags') - + def __init__(self): raise NotImplementedError('Abstract base class') - + @dynamic_property def package(self): def fget(self): return self.soup.find(re.compile('package')) return property(fget=fget) - + @dynamic_property def metadata(self): def fget(self): return self.package.find(re.compile('metadata')) return property(fget=fget) - - + + def get_title(self): title = self.metadata.find('dc:title') if title and title.string: return self.ENTITY_PATTERN.sub(entity_to_unicode, title.string).strip() return self.default_title.strip() - + def get_authors(self): creators = self.metadata.findAll('dc:creator') for elem in creators: @@ -277,7 +276,7 @@ class OPF(MetaInformation): ans.extend(i.split('&')) return [a.strip() for a in ans] return [] - + def get_author_sort(self): creators = self.metadata.findAll('dc:creator') for elem in creators: @@ -288,37 +287,37 @@ class OPF(MetaInformation): fa = elem.get('file-as') return self.ENTITY_PATTERN.sub(entity_to_unicode, fa).strip() if fa else None return None - + def get_title_sort(self): title = self.package.find('dc:title') if title: if title.has_key('file-as'): return title['file-as'].strip() return None - + def get_comments(self): comments = self.soup.find('dc:description') if comments and comments.string: return self.ENTITY_PATTERN.sub(entity_to_unicode, comments.string).strip() return None - + def get_uid(self): package = self.package if package.has_key('unique-identifier'): return package['unique-identifier'] - + def get_category(self): category = self.soup.find('dc:type') if category and category.string: return self.ENTITY_PATTERN.sub(entity_to_unicode, category.string).strip() return None - + def get_publisher(self): publisher = self.soup.find('dc:publisher') if publisher and publisher.string: return self.ENTITY_PATTERN.sub(entity_to_unicode, publisher.string).strip() return None - + def get_isbn(self): for item in self.metadata.findAll('dc:identifier'): scheme = item.get('scheme') @@ -327,13 +326,13 @@ class OPF(MetaInformation): if scheme is not None and scheme.lower() == 'isbn' and item.string: return str(item.string).strip() return None - + def get_language(self): item = self.metadata.find('dc:language') if not item: return _('Unknown') return ''.join(item.findAll(text=True)).strip() - + def get_application_id(self): for item in self.metadata.findAll('dc:identifier'): scheme = item.get('scheme', None) @@ -342,7 +341,7 @@ class OPF(MetaInformation): if scheme in ['libprs500', 'calibre']: return str(item.string).strip() return None - + def get_cover(self): guide = getattr(self, 'guide', []) if not guide: @@ -352,7 +351,7 @@ class OPF(MetaInformation): matches = [r for r in references if r.type.lower() == candidate and r.path] if matches: return matches[0].path - + def possible_cover_prefixes(self): isbn, ans = [], [] for item in self.metadata.findAll('dc:identifier'): @@ -363,22 +362,22 @@ class OPF(MetaInformation): for item in isbn: ans.append(item[1].replace('-', '')) return ans - + def get_series(self): s = self.metadata.find('series') if s is not None: return str(s.string).strip() return None - + def get_series_index(self): s = self.metadata.find('series-index') if s and s.string: try: - return int(str(s.string).strip()) + return float(str(s.string).strip()) except: return None return None - + def get_rating(self): s = self.metadata.find('rating') if s and s.string: @@ -387,7 +386,7 @@ class OPF(MetaInformation): except: return None return None - + def get_tags(self): ans = [] subs = self.soup.findAll('dc:subject') @@ -396,17 +395,17 @@ class OPF(MetaInformation): if val: ans.append(val) return [unicode(a).strip() for a in ans] - - + + class OPFReader(OPF): - + def __init__(self, stream, dir=os.getcwdu()): manage = False if not hasattr(stream, 'read'): manage = True dir = os.path.dirname(stream) stream = open(stream, 'rb') - self.default_title = stream.name if hasattr(stream, 'name') else 'Unknown' + self.default_title = stream.name if hasattr(stream, 'name') else 'Unknown' if hasattr(stream, 'seek'): stream.seek(0) self.soup = OPFSoup(stream.read()) @@ -420,18 +419,18 @@ class OPFReader(OPF): spine = self.soup.find(re.compile('spine')) if spine is not None: self.spine = Spine.from_opf_spine_element(spine, self.manifest) - + self.toc = TOC(base_path=dir) self.toc.read_from_opf(self) guide = self.soup.find(re.compile('guide')) if guide is not None: self.guide = Guide.from_opf_guide(guide, dir) - self.base_dir = dir + self.base_dir = dir self.cover_data = (None, None) - - + + class OPFCreator(MetaInformation): - + def __init__(self, base_path, *args, **kwargs): ''' Initialize. @@ -451,62 +450,62 @@ class OPFCreator(MetaInformation): self.guide = Guide() if self.cover: self.guide.set_cover(self.cover) - - + + def create_manifest(self, entries): ''' Create - + `entries`: List of (path, mime-type) If mime-type is None it is autodetected ''' - entries = map(lambda x: x if os.path.isabs(x[0]) else + entries = map(lambda x: x if os.path.isabs(x[0]) else (os.path.abspath(os.path.join(self.base_path, x[0])), x[1]), entries) self.manifest = Manifest.from_paths(entries) self.manifest.set_basedir(self.base_path) - + def create_manifest_from_files_in(self, files_and_dirs): entries = [] - + def dodir(dir): for spec in os.walk(dir): root, files = spec[0], spec[-1] for name in files: path = os.path.join(root, name) if os.path.isfile(path): - entries.append((path, None)) - + entries.append((path, None)) + for i in files_and_dirs: if os.path.isdir(i): dodir(i) else: entries.append((i, None)) - - self.create_manifest(entries) - + + self.create_manifest(entries) + def create_spine(self, entries): ''' Create the element. Must first call :method:`create_manifest`. - + `entries`: List of paths ''' - entries = map(lambda x: x if os.path.isabs(x) else + entries = map(lambda x: x if os.path.isabs(x) else os.path.abspath(os.path.join(self.base_path, x)), entries) self.spine = Spine.from_paths(entries, self.manifest) - + def set_toc(self, toc): ''' Set the toc. You must call :method:`create_spine` before calling this method. - + :param toc: A :class:`TOC` object ''' self.toc = toc - + def create_guide(self, guide_element): self.guide = Guide.from_opf_guide(guide_element, self.base_path) self.guide.set_basedir(self.base_path) - + def render(self, opf_stream, ncx_stream=None, ncx_manifest_entry=None): from calibre.resources import opf_template from calibre.utils.genshi.template import MarkupTemplate @@ -530,7 +529,7 @@ class OPFCreator(MetaInformation): cover = os.path.abspath(os.path.join(self.base_path, cover)) self.guide.set_cover(cover) self.guide.set_basedir(self.base_path) - + opf = template.generate(__appname__=__appname__, mi=self, __version__=__version__).render('xml') if not opf.startswith('\n'+opf @@ -540,4 +539,4 @@ class OPFCreator(MetaInformation): if toc is not None and ncx_stream is not None: toc.render(ncx_stream, self.application_id) ncx_stream.flush() - \ No newline at end of file + diff --git a/src/calibre/ebooks/metadata/opf.xml b/src/calibre/ebooks/metadata/opf.xml index 027d560ffa..7acf0f5c78 100644 --- a/src/calibre/ebooks/metadata/opf.xml +++ b/src/calibre/ebooks/metadata/opf.xml @@ -9,15 +9,16 @@ ${author} ${'%s (%s)'%(__appname__, __version__)} [http://${__appname__}.kovidgoyal.net] ${mi.application_id} - ${mi.timestamp.isoformat()} + ${mi.pubdate.isoformat()} ${mi.language if mi.language else 'UND'} ${mi.category} ${mi.comments} ${mi.publisher} ${mi.isbn} - + + ${tag} diff --git a/src/calibre/ebooks/metadata/opf2.py b/src/calibre/ebooks/metadata/opf2.py index 7dc4c67d17..4d1af0ee75 100644 --- a/src/calibre/ebooks/metadata/opf2.py +++ b/src/calibre/ebooks/metadata/opf2.py @@ -442,9 +442,10 @@ class OPF(object): comments = MetadataField('description') category = MetadataField('category') series = MetadataField('series', is_dc=False) - series_index = MetadataField('series_index', is_dc=False, formatter=int, none_is=1) + series_index = MetadataField('series_index', is_dc=False, formatter=float, none_is=1) rating = MetadataField('rating', is_dc=False, formatter=int) - timestamp = MetadataField('date', formatter=parser.parse) + pubdate = MetadataField('date', formatter=parser.parse) + timestamp = MetadataField('timestamp', is_dc=False, formatter=parser.parse) def __init__(self, stream, basedir=os.getcwdu(), unquote_urls=True): diff --git a/src/calibre/ebooks/mobi/output.py b/src/calibre/ebooks/mobi/output.py index 026dbdc165..c5f94b5c28 100644 --- a/src/calibre/ebooks/mobi/output.py +++ b/src/calibre/ebooks/mobi/output.py @@ -27,6 +27,20 @@ class MOBIOutput(OutputFormatPlugin): OptionRecommendation(name='toc_title', recommended_value=None, help=_('Title for any generated in-line table of contents.') ), + OptionRecommendation(name='mobi_periodical', + recommended_value=False, level=OptionRecommendation.LOW, + help=_('When present, generate a periodical rather than a book.') + ), + OptionRecommendation(name='no_mobi_index', + recommended_value=False, level=OptionRecommendation.LOW, + help=_('Disable generation of MOBI index.') + ), + OptionRecommendation(name='dont_compress', + recommended_value=False, level=OptionRecommendation.LOW, + help=_('Disable compression of the file contents.') + ), + + ]) recommendations = set([ @@ -35,7 +49,8 @@ class MOBIOutput(OutputFormatPlugin): def convert(self, oeb, output_path, input_plugin, opts, log): self.log, self.opts, self.oeb = log, opts, oeb - from calibre.ebooks.mobi.writer import PALM_MAX_IMAGE_SIZE, MobiWriter + from calibre.ebooks.mobi.writer import PALM_MAX_IMAGE_SIZE, \ + MobiWriter, PALMDOC, UNCOMPRESSED from calibre.ebooks.mobi.mobiml import MobiMLizer from calibre.ebooks.oeb.transforms.manglecase import CaseMangler from calibre.ebooks.oeb.transforms.rasterize import SVGRasterizer @@ -49,7 +64,8 @@ class MOBIOutput(OutputFormatPlugin): rasterizer(oeb, opts) mobimlizer = MobiMLizer(ignore_tables=opts.linearize_tables) mobimlizer(oeb, opts) - writer = MobiWriter(imagemax=imagemax, + writer = MobiWriter(opts, imagemax=imagemax, + compression=UNCOMPRESSED if opts.dont_compress else PALMDOC, prefer_author_sort=opts.prefer_author_sort) writer(oeb, output_path) diff --git a/src/calibre/ebooks/mobi/reader.py b/src/calibre/ebooks/mobi/reader.py index 6a399ab145..2088ca7537 100644 --- a/src/calibre/ebooks/mobi/reader.py +++ b/src/calibre/ebooks/mobi/reader.py @@ -340,6 +340,13 @@ class MobiReader(object): 'href':'styles.css'}) head.insert(0, link) link.tail = '\n\t' + title = head.xpath('descendant::title') + if not title: + title = head.makeelement('title', {}) + title.text = self.book_header.title + title.tail = '\n\t' + head.insert(0, title) + head.text = '\n\t' self.upshift_markup(root) guides = root.xpath('//guide') @@ -460,7 +467,11 @@ class MobiReader(object): if attrib.has_key('align'): align = attrib.pop('align').strip() if align: - styles.append('text-align: %s' % align) + align = align.lower() + if align == 'baseline': + styles.append('vertical-align: '+align) + else: + styles.append('text-align: %s' % align) if tag.tag == 'hr': if mobi_version == 1: tag.tag = 'div' @@ -501,13 +512,13 @@ class MobiReader(object): except ValueError: pass if styles: - cls = None + ncls = None rule = '; '.join(styles) for sel, srule in self.tag_css_rules.items(): if srule == rule: - cls = sel + ncls = sel break - if cls is None: + if ncls is None: ncls = 'calibre_%d' % i self.tag_css_rules[ncls] = rule cls = attrib.get('class', '') diff --git a/src/calibre/ebooks/mobi/writer.py b/src/calibre/ebooks/mobi/writer.py index 99f50b4439..5b058870bb 100644 --- a/src/calibre/ebooks/mobi/writer.py +++ b/src/calibre/ebooks/mobi/writer.py @@ -3,7 +3,8 @@ Write content to Mobipocket books. ''' __license__ = 'GPL v3' -__copyright__ = '2008, Marshall T. Vandegrift ' +__copyright__ = '2008, Marshall T. Vandegrift and \ + Kovid Goyal ' from collections import defaultdict from itertools import count @@ -29,8 +30,6 @@ from calibre.ebooks.oeb.base import urlnormalize from calibre.ebooks.compression.palmdoc import compress_doc # TODO: -# - Allow override CSS (?) -# - Generate index records # - Optionally rasterize tables EXTH_CODES = { @@ -59,6 +58,25 @@ OTHER_MAX_IMAGE_SIZE = 10 * 1024 * 1024 MAX_THUMB_SIZE = 16 * 1024 MAX_THUMB_DIMEN = (180, 240) + +TAGX = { + 'chapter' : + '\x00\x00\x00\x01\x01\x01\x01\x00\x02\x01\x02\x00\x03\x01\x04\x00\x04\x01\x08\x00\x00\x00\x00\x01', + 'subchapter' : + '\x00\x00\x00\x01\x01\x01\x01\x00\x02\x01\x02\x00\x03\x01\x04\x00\x04\x01\x08\x00\x05\x01\x10\x00\x15\x01\x10\x00\x16\x01\x20\x00\x17\x01\x40\x00\x00\x00\x00\x01', + 'periodical' : + '\x00\x00\x00\x02\x01\x01\x01\x00\x02\x01\x02\x00\x03\x01\x04\x00\x04\x01\x08\x00\x05\x01\x10\x00\x15\x01\x20\x00\x16\x01\x40\x00\x17\x01\x80\x00\x00\x00\x00\x01\x45\x01\x01\x00\x46\x01\x02\x00\x47\x01\x04\x00\x00\x00\x00\x01' + } + +INDXT = { + 'chapter' : '\x0f', + 'subchapter' : '\x1f', + 'article' : '\x3f', + 'chapter with subchapters': '\x6f', + 'periodical' : '\xdf', + 'section' : '\xff', + } + def encode(data): return data.encode('utf-8') @@ -79,6 +97,14 @@ def decint(value, direction): bytes[-1] |= 0x80 return ''.join(chr(b) for b in reversed(bytes)) + +def align_block(raw, multiple=4, pad='\0'): + l = len(raw) + extra = l % multiple + if extra == 0: return raw + return raw + pad*(multiple - extra) + + def rescale_image(data, maxsizeb, dimen=None): image = Image.open(StringIO(data)) format = image.format @@ -204,13 +230,11 @@ class Serializer(object): def serialize_item(self, item): buffer = self.buffer - #buffer.write('') if not item.linear: self.breaks.append(buffer.tell() - 1) self.id_offsets[item.href] = buffer.tell() for elem in item.data.find(XHTML('body')): self.serialize_elem(elem, item) - #buffer.write('') buffer.write('') def serialize_elem(self, elem, item, nsrmap=NSRMAP): @@ -290,11 +314,13 @@ class Serializer(object): class MobiWriter(object): COLLAPSE_RE = re.compile(r'[ \t\r\n\v]+') - def __init__(self, compression=PALMDOC, imagemax=None, - prefer_author_sort=False): + def __init__(self, opts, compression=PALMDOC, imagemax=None, + prefer_author_sort=False): + self.opts = opts self._compression = compression or UNCOMPRESSED self._imagemax = imagemax or OTHER_MAX_IMAGE_SIZE self._prefer_author_sort = prefer_author_sort + self._primary_index_record = None @classmethod def generate(cls, opts): @@ -329,6 +355,8 @@ class MobiWriter(object): def _generate_content(self): self._map_image_names() self._generate_text() + if not self.opts.no_mobi_index: + self._generate_index() self._generate_images() def _map_image_names(self): @@ -374,6 +402,8 @@ class MobiWriter(object): serializer = Serializer(self._oeb, self._images) breaks = serializer.breaks text = serializer.text + self._id_offsets = serializer.id_offsets + self._content_length = len(text) self._text_length = len(text) text = StringIO(text) nrecords = 0 @@ -410,10 +440,205 @@ class MobiWriter(object): data, overlap = self._read_text_record(text) self._text_nrecords = nrecords + def _generate_indxt(self, ctoc): + if self.opts.mobi_periodical: + raise NotImplementedError('Indexing for periodicals not implemented') + toc = self._oeb.toc + indxt, indices, c = StringIO(), StringIO(), 0 + + indices.write('IDXT') + c = 0 + last_index = last_name = None + + def add_node(node, offset, length, count): + if self.opts.verbose > 2: + self._oeb.log.debug('Adding TOC node:', node.title, 'href:', + node.href) + + pos = 0xc0 + indxt.tell() + indices.write(pack('>H', pos)) + name = "%4d"%count + indxt.write(chr(len(name)) + name) + indxt.write(INDXT['chapter']) + indxt.write(decint(offset, DECINT_FORWARD)) + indxt.write(decint(length, DECINT_FORWARD)) + indxt.write(decint(self._ctoc_map[node], DECINT_FORWARD)) + indxt.write(decint(0, DECINT_FORWARD)) + + + entries = list(toc.iter())[1:] + for i, child in enumerate(entries): + if not child.title or not child.title.strip(): + continue + h = child.href + if h not in self._id_offsets: + self._oeb.log.warning('Could not find TOC entry:', child.title) + continue + offset = self._id_offsets[h] + length = None + for sibling in entries[i+1:]: + h2 = sibling.href + if h2 in self._id_offsets: + offset2 = self._id_offsets[h2] + if offset2 > offset: + length = offset2 - offset + break + if length is None: + length = self._content_length - offset + + add_node(child, offset, length, c) + last_index = c + ctoc_offset = self._ctoc_map[child] + last_name = "%4d"%c + c += 1 + + return align_block(indxt.getvalue()), c, \ + align_block(indices.getvalue()), last_index, last_name + + + def _generate_index(self): + self._oeb.log('Generating index...') + self._primary_index_record = None + ctoc = self._generate_ctoc() + indxt, indxt_count, indices, last_index, last_name = \ + self._generate_indxt(ctoc) + + indx1 = StringIO() + indx1.write('INDX'+pack('>I', 0xc0)) # header length + + # 0x8 - 0xb : Unknown + indx1.write('\0'*4) + + # 0xc - 0xf : Header type + indx1.write(pack('>I', 1)) + + # 0x10 - 0x13 : Unknown + indx1.write('\0'*4) + + # 0x14 - 0x17 : IDXT offset + # 0x18 - 0x1b : IDXT count + indx1.write(pack('>I', 0xc0+len(indxt))) + indx1.write(pack('>I', indxt_count)) + + # 0x1c - 0x23 : Unknown + indx1.write('\xff'*8) + + # 0x24 - 0xbf + indx1.write('\0'*156) + indx1.write(indxt) + indx1.write(indices) + indx1 = indx1.getvalue() + + idxt0 = chr(len(last_name)) + last_name + pack('>H', last_index) + idxt0 = align_block(idxt0) + indx0 = StringIO() + + tagx = TAGX['periodical' if self.opts.mobi_periodical else 'chapter'] + tagx = align_block('TAGX' + pack('>I', 8 + len(tagx)) + tagx) + indx0_indices_pos = 0xc0 + len(tagx) + len(idxt0) + indx0_indices = align_block('IDXT' + pack('>H', 0xc0 + len(tagx))) + # Generate record header + header = StringIO() + + header.write('INDX') + header.write(pack('>I', 0xc0)) # header length + + # 0x08 - 0x0b : Unknown + header.write('\0'*4) + + # 0x0c - 0x0f : Header type + header.write(pack('>I', 0)) + + # 0x10 - 0x13 : Generator ID + header.write(pack('>I', 6)) + + # 0x14 - 0x17 : IDXT offset + header.write(pack('>I', indx0_indices_pos)) + + # 0x18 - 0x1b : IDXT count + header.write(pack('>I', 1)) + + # 0x1c - 0x1f : Text encoding ? + header.write(pack('>I', 650001)) + + # 0x20 - 0x23 : Language code? + header.write(iana2mobi(str(self._oeb.metadata.language[0]))) + + # 0x24 - 0x27 : Number of TOC entries in INDX1 + header.write(pack('>I', indxt_count)) + + # 0x28 - 0x2b : ORDT Offset + header.write('\0'*4) + + # 0x2c - 0x2f : LIGT offset + header.write('\0'*4) + + # 0x30 - 0x33 : Number of LIGT entries + header.write('\0'*4) + + # 0x34 - 0x37 : Unknown + header.write(pack('>I', 1)) + + # 0x38 - 0xb3 : Unknown (pad?) + header.write('\0'*124) + + # 0xb4 - 0xb7 : TAGX offset + header.write(pack('>I', 0xc0)) + + # 0xb8 - 0xbf : Unknown + header.write('\0'*8) + + header = header.getvalue() + + indx0.write(header) + indx0.write(tagx) + indx0.write(idxt0) + indx0.write(indx0_indices) + indx0 = indx0.getvalue() + + self._primary_index_record = len(self._records) + if self.opts.verbose > 3: + from tempfile import mkdtemp + import os + t = mkdtemp() + open(os.path.join(t, 'indx0.bin'), 'wb').write(indx0) + open(os.path.join(t, 'indx1.bin'), 'wb').write(indx1) + open(os.path.join(t, 'ctoc.bin'), 'wb').write(ctoc) + self._oeb.log.debug('Index records dumped to', t) + + self._records.extend([indx0, indx1, ctoc]) + + def _generate_ctoc(self): + if self.opts.mobi_periodical: + raise NotImplementedError('Indexing for periodicals not implemented') + toc = self._oeb.toc + self._ctoc_map = {} + self._ctoc_name_map = {} + self._last_toc_entry = None + ctoc = StringIO() + + def add_node(node, cls): + t = node.title + if t and t.strip(): + t = t.strip() + if not isinstance(t, unicode): + t = t.decode('utf-8', 'replace') + t = t.encode('utf-8') + self._last_toc_entry = t + self._ctoc_map[node] = ctoc.tell() + self._ctoc_name_map[node] = t + ctoc.write(decint(len(t), DECINT_FORWARD)+t) + + for child in toc.iter(): + add_node(child, 'chapter') + + return align_block(ctoc.getvalue()) + def _generate_images(self): self._oeb.logger.info('Serializing images...') images = [(index, href) for href, index in self._images.items()] images.sort() + self._first_image_record = None for _, href in images: item = self._oeb.manifest.hrefs[href] try: @@ -422,35 +647,142 @@ class MobiWriter(object): self._oeb.logger.warn('Bad image file %r' % item.href) continue self._records.append(data) + if self._first_image_record is None: + self._first_image_record = len(self._records)-1 + + def _generate_end_records(self): + self._flis_number = len(self._records) + self._records.append( + 'FLIS\0\0\0\x08\0\x41\0\0\0\0\0\0\xff\xff\xff\xff\0\x01\0\x03\0\0\0\x03\0\0\0\x01'+ + '\xff'*4) + fcis = 'FCIS\x00\x00\x00\x14\x00\x00\x00\x10\x00\x00\x00\x01\x00\x00\x00\x00' + fcis += pack('>I', self._text_length) + fcis += '\x00\x00\x00\x00\x00\x00\x00\x20\x00\x00\x00\x08\x00\x01\x00\x01\x00\x00\x00\x00' + self._fcis_number = len(self._records) + self._records.append(fcis) + self._records.append('\xE9\x8E\x0D\x0A') def _generate_record0(self): metadata = self._oeb.metadata exth = self._build_exth() + last_content_record = len(self._records) - 1 + self._generate_end_records() record0 = StringIO() + # The PalmDOC Header record0.write(pack('>HHIHHHH', self._compression, 0, - self._text_length, self._text_nrecords, RECORD_SIZE, 0, 0)) + self._text_length, + self._text_nrecords, RECORD_SIZE, 0, 0)) # 0 - 15 (0x0 - 0xf) uid = random.randint(0, 0xffffffff) title = str(metadata.title[0]) + # The MOBI Header + + # 0x0 - 0x3 record0.write('MOBI') - record0.write(pack('>IIIII', 0xe8, 2, 65001, uid, 6)) - record0.write('\xff' * 40) - record0.write(pack('>I', self._text_nrecords + 1)) - record0.write(pack('>II', 0xe8 + 16 + len(exth), len(title))) - record0.write(iana2mobi(str(metadata.language[0]))) + + # 0x4 - 0x7 : Length of header + # 0x8 - 0x11 : MOBI type + # type meaning + # 0x002 MOBI book (chapter - chapter navigation) + # 0x101 News - Hierarchical navigation with sections and articles + # 0x102 News feed - Flat navigation + # 0x103 News magazine - same as 1x101 + # 0xC - 0xF : Text encoding (65001 is utf-8) + # 0x10 - 0x13 : UID + # 0x14 - 0x17 : Generator version + btype = 0x101 if self.opts.mobi_periodical else 2 + record0.write(pack('>IIIII', + 0xe8, btype, 65001, uid, 6)) + + # 0x18 - 0x1f : Unknown + record0.write('\xff' * 8) + + # 0x20 - 0x23 : Secondary index record + # TODO: implement + record0.write('\xff' * 4) + + # 0x24 - 0x3f : Unknown + record0.write('\xff' * 28) + + # 0x40 - 0x43 : Offset of first non-text record + record0.write(pack('>I', + self._text_nrecords + 1)) + + # 0x44 - 0x4b : title offset, title length + record0.write(pack('>II', + 0xe8 + 16 + len(exth), len(title))) + + # 0x4c - 0x4f : Language specifier + record0.write(iana2mobi( + str(metadata.language[0]))) + + # 0x50 - 0x57 : Unknown record0.write('\0' * 8) - record0.write(pack('>II', 6, self._text_nrecords + 1)) + + # 0x58 - 0x5b : Format version + # 0x5c - 0x5f : First image record number + record0.write(pack('>II', + 6, self._first_image_record if self._first_image_record else 0)) + + # 0x60 - 0x63 : First HUFF/CDIC record number + # 0x64 - 0x67 : Number of HUFF/CDIC records + # 0x68 - 0x6b : First DATP record number + # 0x6c - 0x6f : Number of DATP records record0.write('\0' * 16) + + # 0x70 - 0x73 : EXTH flags record0.write(pack('>I', 0x50)) + + # 0x74 - 0x93 : Unknown record0.write('\0' * 32) - record0.write(pack('>IIII', 0xffffffff, 0xffffffff, 0, 0)) + + # 0x94 - 0x97 : DRM offset + # 0x98 - 0x9b : DRM count + # 0x9c - 0x9f : DRM size + # 0xa0 - 0xa3 : DRM flags + record0.write(pack('>IIII', + 0xffffffff, 0xffffffff, 0, 0)) + + + # 0xa4 - 0xaf : Unknown + record0.write('\0'*12) + + # 0xb0 - 0xb1 : First content record number + # 0xb2 - 0xb3 : last content record number + # (Includes Image, DATP, HUFF, DRM) + record0.write(pack('>HH', 1, last_content_record)) + + # 0xb4 - 0xb7 : Unknown + record0.write('\0\0\0\x01') + + # 0xb8 - 0xbb : FCIS record number + record0.write(pack('>I', self._fcis_number)) + + # 0xbc - 0xbf : Unknown (FCIS record count?) + record0.write(pack('>I', 1)) + + # 0xc0 - 0xc3 : FLIS record number + record0.write(pack('>I', self._flis_number)) + + # 0xc4 - 0xc7 : Unknown (FLIS record count?) + record0.write(pack('>I', 1)) + + # 0xc8 - 0xcf : Unknown + record0.write('\0'*8) + + # 0xd0 - 0xdf : Unknown + record0.write(pack('>IIII', 0xffffffff, 0, 0xffffffff, 0xffffffff)) + + # 0xe0 - 0xe3 : Extra record data # The '5' is a bitmask of extra record data at the end: # - 0x1: (?) # - 0x4: # Of course, the formats aren't quite the same. - # TODO: What the hell are the rest of these fields? - record0.write(pack('>IIIIIIIIIIIIIIIII', - 0, 0, 0, 0xffffffff, 0, 0xffffffff, 0, 0xffffffff, 0, 0xffffffff, - 0, 0xffffffff, 0, 0xffffffff, 0xffffffff, 5, 0xffffffff)) + record0.write(pack('>I', 5)) + + # 0xe4 - 0xe7 : Primary index record + record0.write(pack('>I', 0xffffffff if self._primary_index_record is + None else self._primary_index_record)) + record0.write(exth) record0.write(title) record0 = record0.getvalue() diff --git a/src/calibre/ebooks/oeb/iterator.py b/src/calibre/ebooks/oeb/iterator.py index ba0e8b22b5..f4eb2c5a29 100644 --- a/src/calibre/ebooks/oeb/iterator.py +++ b/src/calibre/ebooks/oeb/iterator.py @@ -101,7 +101,7 @@ class EbookIterator(object): ''' for item in self.opf.manifest: if item.mime_type and 'css' in item.mime_type.lower(): - css = open(item.path, 'rb').read().decode('utf-8') + css = open(item.path, 'rb').read().decode('utf-8', 'replace') for match in re.compile(r'@font-face\s*{([^}]+)}').finditer(css): block = match.group(1) family = re.compile(r'font-family\s*:\s*([^;]+)').search(block) diff --git a/src/calibre/ebooks/oeb/transforms/htmltoc.py b/src/calibre/ebooks/oeb/transforms/htmltoc.py index 7f3391e72b..1aef7e56cc 100644 --- a/src/calibre/ebooks/oeb/transforms/htmltoc.py +++ b/src/calibre/ebooks/oeb/transforms/htmltoc.py @@ -30,7 +30,7 @@ STYLE_CSS = { margin-left: 3.6em; } """, - + 'centered': """ .calibre_toc_header { text-align: center; @@ -48,18 +48,18 @@ class HTMLTOCAdder(object): def __init__(self, title=None, style='nested'): self.title = title self.style = style - + @classmethod def config(cls, cfg): group = cfg.add_group('htmltoc', _('HTML TOC generation options.')) - group('toc_title', ['--toc-title'], default=None, + group('toc_title', ['--toc-title'], default=None, help=_('Title for any generated in-line table of contents.')) return cfg @classmethod def generate(cls, opts): return cls(title=opts.toc_title) - + def __call__(self, oeb, context): if 'toc' in oeb.guide: return diff --git a/src/calibre/ebooks/oeb/transforms/jacket.py b/src/calibre/ebooks/oeb/transforms/jacket.py index c0a656d64d..952870fcd1 100644 --- a/src/calibre/ebooks/oeb/transforms/jacket.py +++ b/src/calibre/ebooks/oeb/transforms/jacket.py @@ -65,7 +65,7 @@ class Jacket(object): comments = comments.replace('\r\n', '\n').replace('\n\n', '

') series = 'Series: ' + mi.series if mi.series else '' if series and mi.series_index is not None: - series += ' [%s]'%mi.series_index + series += ' [%s]'%mi.format_series_index() tags = mi.tags if not tags: try: diff --git a/src/calibre/ebooks/oeb/transforms/metadata.py b/src/calibre/ebooks/oeb/transforms/metadata.py index cd6edcf699..894cb4fb08 100644 --- a/src/calibre/ebooks/oeb/transforms/metadata.py +++ b/src/calibre/ebooks/oeb/transforms/metadata.py @@ -58,7 +58,7 @@ class MergeMetadata(object): m.add('creator', mi.book_producer, role='bkp') if mi.series_index is not None: m.clear('series_index') - m.add('series_index', '%.2f'%mi.series_index) + m.add('series_index', mi.format_series_index()) if mi.rating is not None: m.clear('rating') m.add('rating', '%.2f'%mi.rating) diff --git a/src/calibre/ebooks/oeb/transforms/structure.py b/src/calibre/ebooks/oeb/transforms/structure.py index 5e3322a57a..2f083153ce 100644 --- a/src/calibre/ebooks/oeb/transforms/structure.py +++ b/src/calibre/ebooks/oeb/transforms/structure.py @@ -172,4 +172,3 @@ class DetectStructure(object): level2.add(text, _href, play_order=self.oeb.toc.next_play_order()) - diff --git a/src/calibre/ebooks/pdb/output.py b/src/calibre/ebooks/pdb/output.py index 29de9bd99c..fb6984e1e2 100644 --- a/src/calibre/ebooks/pdb/output.py +++ b/src/calibre/ebooks/pdb/output.py @@ -15,13 +15,13 @@ class PDBOutput(OutputFormatPlugin): name = 'PDB Output' author = 'John Schember' file_type = 'pdb' - + options = set([ OptionRecommendation(name='format', recommended_value='doc', level=OptionRecommendation.LOW, short_switch='f', choices=FORMAT_WRITERS.keys(), - help=_('Format to use inside the pdb container. Choices are: ' - '%s' % FORMAT_WRITERS.keys())), + help=(_('Format to use inside the pdb container. Choices are:')+\ + ' %s' % FORMAT_WRITERS.keys())), ]) def convert(self, oeb_book, output_path, input_plugin, opts, log): @@ -33,19 +33,19 @@ class PDBOutput(OutputFormatPlugin): out_stream = open(output_path, 'wb') else: out_stream = output_path - + Writer = get_writer(opts.format) - + if Writer is None: raise PDBError('No writer avaliable for format %s.' % format) - + writer = Writer(opts, log) - + out_stream.seek(0) out_stream.truncate() - + writer.write_content(oeb_book, out_stream, oeb_book.metadata) if close: out_stream.close() - + diff --git a/src/calibre/ebooks/pdf/manipulate/cli.py b/src/calibre/ebooks/pdf/manipulate/cli.py index 4876cbd8f5..c6e52f85d3 100644 --- a/src/calibre/ebooks/pdf/manipulate/cli.py +++ b/src/calibre/ebooks/pdf/manipulate/cli.py @@ -14,7 +14,6 @@ import string, sys from calibre.utils.config import OptionParser from calibre.utils.logging import Log from calibre.constants import preferred_encoding -from calibre.customize.conversion import OptionRecommendation from calibre.ebooks.pdf.manipulate import crop, decrypt, encrypt, \ info, merge, reverse, rotate, split @@ -30,14 +29,14 @@ COMMANDS = { } USAGE = '%prog ' + _('''command ... - + command can be one of the following: [%%commands] Use %prog command --help to get more information about a specific command Manipulate a PDF. -'''.replace('%%commands', string.join(sorted(COMMANDS.keys()), ', '))) +''').replace('%%commands', string.join(sorted(COMMANDS.keys()), ', ')) def print_help(parser, log): help = parser.format_help().encode(preferred_encoding, 'replace') @@ -54,9 +53,9 @@ def main(args=sys.argv): print 'Error: No command sepecified.\n' print_help(parser, log) return 1 - + command = args[1].lower().strip() - + if command in COMMANDS.keys(): del args[1] return COMMANDS[command].main(args, command) @@ -65,7 +64,7 @@ def main(args=sys.argv): print 'Unknown command %s.\n' % command print_help(parser, log) return 1 - + # We should never get here. return 0 diff --git a/src/calibre/ebooks/pdf/manipulate/crop.py b/src/calibre/ebooks/pdf/manipulate/crop.py index 0f24f04638..c35d4615a4 100644 --- a/src/calibre/ebooks/pdf/manipulate/crop.py +++ b/src/calibre/ebooks/pdf/manipulate/crop.py @@ -10,7 +10,7 @@ __docformat__ = 'restructuredtext en' Crop a pdf file ''' -import os, sys, re +import sys, re from optparse import OptionGroup, Option from calibre.ebooks.metadata.meta import metadata_from_formats @@ -37,16 +37,16 @@ OPTIONS = set([ help=_('Path to output file. By default a file is created in the current directory.')), OptionRecommendation(name='bottom_left_x', recommended_value=DEFAULT_CROP, level=OptionRecommendation.LOW, long_switch='leftx', short_switch='x', - help=_('Number of pixels to crop from the left most x (default is %s) ' % DEFAULT_CROP)), + help=_('Number of pixels to crop from the left most x (default is %s)') % DEFAULT_CROP), OptionRecommendation(name='bottom_left_y', recommended_value=DEFAULT_CROP, level=OptionRecommendation.LOW, long_switch='lefty', short_switch='y', - help=_('Number of pixels to crop from the left most y (default is %s) ' % DEFAULT_CROP)), + help=_('Number of pixels to crop from the left most y (default is %s)') % DEFAULT_CROP), OptionRecommendation(name='top_right_x', recommended_value=DEFAULT_CROP, level=OptionRecommendation.LOW, long_switch='rightx', short_switch='v', - help=_('Number of pixels to crop from the right most x (default is %s) ' % DEFAULT_CROP)), + help=_('Number of pixels to crop from the right most x (default is %s)') % DEFAULT_CROP), OptionRecommendation(name='top_right_y', recommended_value=DEFAULT_CROP, level=OptionRecommendation.LOW, long_switch='right y', short_switch='w', - help=_('Number of pixels to crop from the right most y (default is %s)' % DEFAULT_CROP)), + help=_('Number of pixels to crop from the right most y (default is %s)') % DEFAULT_CROP), OptionRecommendation(name='bounding', recommended_value=None, level=OptionRecommendation.LOW, long_switch='bounding', short_switch='b', help=_('A file generated by ghostscript which allows each page to be individually cropped `gs -dSAFER -dNOPAUSE -dBATCH -sDEVICE=bbox file.pdf 2> bounding`')), @@ -72,7 +72,7 @@ def add_options(parser): group = OptionGroup(parser, _('Crop Options:'), _('Options to control the transformation of pdf')) parser.add_option_group(group) add_option = group.add_option - + for rec in OPTIONS: option_recommendation_to_cli_option(add_option, rec) @@ -85,7 +85,7 @@ def crop_pdf(pdf_path, opts, metadata=None): author = authors_to_string(metadata.authors) input_pdf = PdfFileReader(open(pdf_path, 'rb')) - + bounding_lines = [] if opts.bounding != None: try: @@ -93,14 +93,14 @@ def crop_pdf(pdf_path, opts, metadata=None): bounding_regex = re.compile('%%BoundingBox: (?P\d+) (?P\d+) (?P\d+) (?P\d+)') except: raise Exception('Error reading %s' % opts.bounding) - + lines = bounding.readlines() for line in lines: if line.startswith('%%BoundingBox:'): bounding_lines.append(line) if len(bounding_lines) != input_pdf.numPages: - raise Exception('Error bounding file %s page count does not correspond to specified pdf' % opts.bounding) - + raise Exception('Error bounding file %s page count does not correspond to specified pdf' % opts.bounding) + output_pdf = PdfFileWriter(title=title,author=author) blines = iter(bounding_lines) for page in input_pdf.pages: @@ -114,33 +114,33 @@ def crop_pdf(pdf_path, opts, metadata=None): page.mediaBox.upperRight = (page.bleedBox.getUpperRight_x() - opts.top_right_x, page.bleedBox.getUpperRight_y() - opts.top_right_y) page.mediaBox.lowerLeft = (page.bleedBox.getLowerLeft_x() + opts.bottom_left_x, page.bleedBox.getLowerLeft_y() + opts.bottom_left_y) output_pdf.addPage(page) - + with open(opts.output, 'wb') as output_file: output_pdf.write(output_file) - + def main(args=sys.argv, name=''): log = Log() parser = option_parser(name) add_options(parser) - + opts, args = parser.parse_args(args) args = args[1:] - + if len(args) < 1: print 'Error: A PDF file is required.\n' print_help(parser, log) return 1 - + if not is_valid_pdf(args[0]): print 'Error: Could not read file `%s`.' % args[0] return 1 - + if is_encrypted(args[0]): print 'Error: file `%s` is encrypted.' % args[0] return 1 - + mi = metadata_from_formats([args[0]]) - + crop_pdf(args[0], opts, mi) return 0 diff --git a/src/calibre/ebooks/pdf/manipulate/info.py b/src/calibre/ebooks/pdf/manipulate/info.py index 13a39d10f6..0cb4f69172 100644 --- a/src/calibre/ebooks/pdf/manipulate/info.py +++ b/src/calibre/ebooks/pdf/manipulate/info.py @@ -9,16 +9,14 @@ __docformat__ = 'restructuredtext en' Merge PDF files into a single PDF document. ''' -import os, re, sys, time -from optparse import OptionGroup, Option +import os, sys from calibre.utils.config import OptionParser from calibre.utils.logging import Log from calibre.constants import preferred_encoding -from calibre.customize.conversion import OptionRecommendation -from calibre.ebooks.pdf.verify import is_valid_pdfs, is_encrypted, is_encrypted - -from pyPdf import PdfFileWriter, PdfFileReader +from calibre.ebooks.pdf.verify import is_valid_pdfs, is_encrypted +from calibre import prints +from calibre.utils.podofo import podofo, podofo_err USAGE = '\n%prog %%name ' + _('''\ file.pdf ... @@ -35,40 +33,36 @@ def option_parser(name): return OptionParser(usage=usage) def print_info(pdf_path): - with open(os.path.abspath(pdf_path), 'rb') as pdf_file: - pdf = PdfFileReader(pdf_file) - print _('Title: %s' % pdf.documentInfo.title) - print _('Author: %s' % pdf.documentInfo.author) - print _('Subject: %s' % pdf.documentInfo.subject) - print _('Creator: %s' % pdf.documentInfo.creator) - print _('Producer: %s' % pdf.documentInfo.producer) - #print _('Creation Date: %s' % time.strftime('%a %b %d %H:%M:%S %Y', time.gmtime(os.path.getctime(pdf_path)))) - #print _('Modification Date: %s' % time.strftime('%a %b %d %H:%M:%S %Y', time.gmtime(os.path.getmtime(pdf_path)))) - print _('Pages: %s' % pdf.numPages) - #print _('Encrypted: %s' % pdf.isEncrypted) - try: - print _('File Size: %s bytes' % os.path.getsize(pdf_path)) - except: pass - try: - pdf_file.seek(0) - vline = pdf_file.readline() - mo = re.search('(?iu)^%...-(?P\d+\.\d+)', vline) - if mo != None: - print _('PDF Version: %s' % mo.group('version')) - except: pass + if not podofo: + raise RuntimeError('Failed to load PoDoFo with error:'+podofo_err) + p = podofo.PDFDoc() + p.open(pdf_path) + + fmt = lambda x, y: '%-20s: %s'%(x, y) + + print + + prints(fmt(_('Title'), p.title)) + prints(fmt(_('Author'), p.author)) + prints(fmt(_('Subject'), p.subject)) + prints(fmt(_('Creator'), p.creator)) + prints(fmt(_('Producer'), p.producer)) + prints(fmt(_('Pages'), p.pages)) + prints(fmt(_('File Size'), os.stat(pdf_path).st_size)) + prints(fmt(_('PDF Version'), p.version if p.version else _('Unknown'))) def main(args=sys.argv, name=''): log = Log() parser = option_parser(name) - + opts, args = parser.parse_args(args) args = args[1:] - + if len(args) < 1: print 'Error: No PDF sepecified.\n' print_help(parser, log) return 1 - + bad_pdfs = is_valid_pdfs(args) if bad_pdfs != []: for pdf in bad_pdfs: @@ -85,7 +79,7 @@ def main(args=sys.argv, name=''): for pdf in args: print_info(pdf) - + return 0 if __name__ == '__main__': diff --git a/src/calibre/ebooks/pdf/output.py b/src/calibre/ebooks/pdf/output.py index bed5342bb4..a20f503c57 100644 --- a/src/calibre/ebooks/pdf/output.py +++ b/src/calibre/ebooks/pdf/output.py @@ -32,12 +32,12 @@ class PDFOutput(OutputFormatPlugin): level=OptionRecommendation.LOW, short_switch='u', choices=UNITS.keys(), help=_('The unit of measure. Default is inch. Choices ' 'are %s ' - 'Note: This does not override the unit for margins!' % UNITS.keys())), + 'Note: This does not override the unit for margins!') % UNITS.keys()), OptionRecommendation(name='paper_size', recommended_value='letter', level=OptionRecommendation.LOW, choices=PAPER_SIZES.keys(), help=_('The size of the paper. This size will be overridden when an ' 'output profile is used. Default is letter. Choices ' - 'are %s' % PAPER_SIZES.keys())), + 'are %s') % PAPER_SIZES.keys()), OptionRecommendation(name='custom_size', recommended_value=None, help=_('Custom size of the document. Use the form widthxheight ' 'EG. `123x321` to specify the width and height. ' @@ -45,7 +45,7 @@ class PDFOutput(OutputFormatPlugin): OptionRecommendation(name='orientation', recommended_value='portrait', level=OptionRecommendation.LOW, choices=ORIENTATIONS.keys(), help=_('The orientation of the page. Default is portrait. Choices ' - 'are %s' % ORIENTATIONS.keys())), + 'are %s') % ORIENTATIONS.keys()), ]) def convert(self, oeb_book, output_path, input_plugin, opts, log): diff --git a/src/calibre/ebooks/txt/output.py b/src/calibre/ebooks/txt/output.py index 6afc5452b2..64835c3c52 100644 --- a/src/calibre/ebooks/txt/output.py +++ b/src/calibre/ebooks/txt/output.py @@ -23,7 +23,7 @@ class TXTOutput(OutputFormatPlugin): help=_('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.' % sorted(TxtNewlines.NEWLINE_TYPES.keys()))), + 'type used by this OS.') % sorted(TxtNewlines.NEWLINE_TYPES.keys())), ]) def convert(self, oeb_book, output_path, input_plugin, opts, log): @@ -38,11 +38,11 @@ class TXTOutput(OutputFormatPlugin): out_stream = open(output_path, 'wb') else: out_stream = output_path - + out_stream.seek(0) out_stream.truncate() out_stream.write(txt.encode('utf-8')) - + if close: out_stream.close() - + diff --git a/src/calibre/gui2/__init__.py b/src/calibre/gui2/__init__.py index 3fbc3a9e10..a8b6f2d05b 100644 --- a/src/calibre/gui2/__init__.py +++ b/src/calibre/gui2/__init__.py @@ -19,7 +19,8 @@ from calibre.ebooks.metadata import MetaInformation NONE = QVariant() #: Null value to return from the data function of item models -ALL_COLUMNS = ['title', 'authors', 'size', 'timestamp', 'rating', 'publisher', 'tags', 'series'] +ALL_COLUMNS = ['title', 'authors', 'size', 'timestamp', 'rating', 'publisher', + 'tags', 'series', 'pubdate'] def _config(): c = Config('gui', 'preferences for the calibre GUI') diff --git a/src/calibre/gui2/convert/__init__.py b/src/calibre/gui2/convert/__init__.py index 2050108bde..70223acb70 100644 --- a/src/calibre/gui2/convert/__init__.py +++ b/src/calibre/gui2/convert/__init__.py @@ -119,7 +119,8 @@ class Widget(QWidget): elif isinstance(g, XPathEdit): g.edit.setText(val if val else '') else: - raise Exception('Can\'t set value %s in %s'%(repr(val), type(g))) + raise Exception('Can\'t set value %s in %s'%(repr(val), + unicode(g.objectName()))) self.post_set_value(g, val) def set_help(self, msg): diff --git a/src/calibre/gui2/convert/metadata.py b/src/calibre/gui2/convert/metadata.py index 31d8db0867..d961fef473 100644 --- a/src/calibre/gui2/convert/metadata.py +++ b/src/calibre/gui2/convert/metadata.py @@ -83,7 +83,7 @@ class MetadataWidget(Widget, Ui_Form): comments = unicode(self.comment.toPlainText()).strip() if comments: mi.comments = comments - mi.series_index = int(self.series_index.value()) + mi.series_index = float(self.series_index.value()) if self.series.currentIndex() > -1: mi.series = unicode(self.series.currentText()).strip() tags = [t.strip() for t in unicode(self.tags.text()).strip().split(',')] diff --git a/src/calibre/gui2/convert/metadata.ui b/src/calibre/gui2/convert/metadata.ui index 5b68d6383d..3721483893 100644 --- a/src/calibre/gui2/convert/metadata.ui +++ b/src/calibre/gui2/convert/metadata.ui @@ -1,7 +1,8 @@ - + + Form - - + + 0 0 @@ -9,59 +10,89 @@ 500 - + Form - + - - + + Book Cover - - - - + + + + + + + + + + :/images/book.svg + + + true + + + Qt::AlignCenter + + + + + + + + + Use cover from &source file + + + true + + + + + + 6 - + 0 - - + + Change &cover image: - + cover_path - - + + 6 - + 0 - - + + true - - + + Browse for an image to use as the cover of this book. - + ... - - + + :/images/document_open.svg:/images/document_open.svg @@ -70,243 +101,204 @@ - - - - Use cover from &source file - - - true - - - - - - - - - - - - :/images/book.svg - - - true - - - Qt::AlignCenter - - - - - opt_prefer_metadata_cover - + - - - - + + + + &Title: - + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter - + title - - - + + + Change the title of this book - - - + + + &Author(s): - + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter - + author - - - - + + + + 1 0 - + Change the author(s) of this book. Multiple authors should be separated by an &. If the author name contains an &, use && to represent it. - - - + + + Author So&rt: - + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter - + author_sort - - - - + + + + 0 0 - + Change the author(s) of this book. Multiple authors should be separated by a comma - - - + + + &Publisher: - + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter - + publisher - - - + + + Change the publisher of this book - - - + + + Ta&gs: - + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter - + tags - - - - Tags categorize the book. This is particularly useful while searching. <br><br>They can be any words or phrases, separated by commas. + + + + Tags categorize the book. This is particularly useful while searching. <br><br>They can be any words or phrases, separated by commas. - - - + + + &Series: - + Qt::PlainText - + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter - + series - - - - + + + + 10 0 - + List of known series. You can add new series. - + List of known series. You can add new series. - + true - + QComboBox::InsertAlphabetically - + QComboBox::AdjustToContents - - - - true - - - Series index. - - - Series index. - - + + + Book - - 1 + + 9999.989999999999782 - - 10000 + + 1.000000000000000 - - - + + + 0 0 - + 16777215 200 - + Comments - - - - + + + + 16777215 180 @@ -329,8 +321,8 @@ - - + + diff --git a/src/calibre/gui2/convert/page_setup.py b/src/calibre/gui2/convert/page_setup.py index 0d2ce91dd1..3f59537db0 100644 --- a/src/calibre/gui2/convert/page_setup.py +++ b/src/calibre/gui2/convert/page_setup.py @@ -35,7 +35,7 @@ class PageSetupWidget(Widget, Ui_Form): TITLE = _('Page Setup') def __init__(self, parent, get_option, get_help, db=None, book_id=None): - Widget.__init__(self, parent, 'lrf_output', + Widget.__init__(self, parent, 'page_setup', ['margin_top', 'margin_left', 'margin_right', 'margin_bottom', 'input_profile', 'output_profile'] ) diff --git a/src/calibre/gui2/dialogs/config.py b/src/calibre/gui2/dialogs/config.py index 8b1e61f546..c0030d7b3e 100644 --- a/src/calibre/gui2/dialogs/config.py +++ b/src/calibre/gui2/dialogs/config.py @@ -1,18 +1,16 @@ __license__ = 'GPL v3' __copyright__ = '2008, Kovid Goyal ' -import os, re, time, textwrap, sys, cStringIO -from binascii import hexlify, unhexlify +import os, re, time, textwrap from PyQt4.Qt import QDialog, QMessageBox, QListWidgetItem, QIcon, \ QDesktopServices, QVBoxLayout, QLabel, QPlainTextEdit, \ QStringListModel, QAbstractItemModel, QFont, \ SIGNAL, QTimer, Qt, QSize, QVariant, QUrl, \ QModelIndex, QInputDialog, QAbstractTableModel, \ - QDialogButtonBox, QTabWidget + QDialogButtonBox, QTabWidget, QBrush from calibre.constants import islinux, iswindows from calibre.gui2.dialogs.config_ui import Ui_Dialog -from calibre.gui2.dialogs.test_email_ui import Ui_Dialog as TE_Dialog from calibre.gui2 import qstring_to_unicode, choose_dir, error_dialog, config, \ ALL_COLUMNS, NONE, info_dialog, choose_files from calibre.utils.config import prefs @@ -95,6 +93,9 @@ class PluginModel(QAbstractItemModel): def __init__(self, *args): QAbstractItemModel.__init__(self, *args) self.icon = QVariant(QIcon(':/images/plugins.svg')) + p = QIcon(self.icon).pixmap(32, 32, QIcon.Disabled, QIcon.On) + self.disabled_icon = QVariant(QIcon(p)) + self._p = p self.populate() def populate(self): @@ -154,9 +155,7 @@ class PluginModel(QAbstractItemModel): return 0 if index.internalId() == -1: return Qt.ItemIsEnabled - flags = Qt.ItemIsSelectable - if not is_disabled(self.data(index, Qt.UserRole)): - flags |= Qt.ItemIsEnabled + flags = Qt.ItemIsSelectable | Qt.ItemIsEnabled return flags def data(self, index, role): @@ -177,7 +176,9 @@ class PluginModel(QAbstractItemModel): ans += '\nCustomization: '+c return QVariant(ans) if role == Qt.DecorationRole: - return self.icon + return self.disabled_icon if is_disabled(plugin) else self.icon + if role == Qt.ForegroundRole and is_disabled(plugin): + return QVariant(QBrush(Qt.gray)) if role == Qt.UserRole: return plugin return NONE @@ -202,34 +203,6 @@ class CategoryModel(QStringListModel): return self.icons[index.row()] return QStringListModel.data(self, index, role) -class TestEmail(QDialog, TE_Dialog): - - def __init__(self, accounts, parent): - QDialog.__init__(self, parent) - TE_Dialog.__init__(self) - self.setupUi(self) - opts = smtp_prefs().parse() - self.test_func = parent.test_email_settings - self.connect(self.test_button, SIGNAL('clicked(bool)'), self.test) - self.from_.setText(unicode(self.from_.text())%opts.from_) - if accounts: - self.to.setText(list(accounts.keys())[0]) - if opts.relay_host: - self.label.setText(_('Using: %s:%s@%s:%s and %s encryption')% - (opts.relay_username, unhexlify(opts.relay_password), - opts.relay_host, opts.relay_port, opts.encryption)) - - def test(self): - self.log.setPlainText(_('Sending...')) - self.test_button.setEnabled(False) - try: - tb = self.test_func(unicode(self.to.text())) - if not tb: - tb = _('Mail successfully sent') - self.log.setPlainText(tb) - finally: - self.test_button.setEnabled(True) - class EmailAccounts(QAbstractTableModel): def __init__(self, accounts): @@ -477,32 +450,19 @@ class ConfigDialog(QDialog, Ui_Dialog): self.stackedWidget.insertWidget(2, self.conversion_options) def setup_email_page(self): - opts = smtp_prefs().parse() - if opts.from_: - self.email_from.setText(opts.from_) + def x(): + if self._email_accounts.account_order: + return self._email_accounts.account_order[0] + self.send_email_widget.initialize(x) + opts = self.send_email_widget.smtp_opts self._email_accounts = EmailAccounts(opts.accounts) self.email_view.setModel(self._email_accounts) - if opts.relay_host: - self.relay_host.setText(opts.relay_host) - self.relay_port.setValue(opts.relay_port) - if opts.relay_username: - self.relay_username.setText(opts.relay_username) - if opts.relay_password: - self.relay_password.setText(unhexlify(opts.relay_password)) - (self.relay_tls if opts.encryption == 'TLS' else self.relay_ssl).setChecked(True) - self.connect(self.relay_use_gmail, SIGNAL('clicked(bool)'), - self.create_gmail_relay) - self.connect(self.relay_show_password, SIGNAL('stateChanged(int)'), - lambda - state:self.relay_password.setEchoMode(self.relay_password.Password if - state == 0 else self.relay_password.Normal)) + self.connect(self.email_add, SIGNAL('clicked(bool)'), self.add_email_account) self.connect(self.email_make_default, SIGNAL('clicked(bool)'), lambda c: self._email_accounts.make_default(self.email_view.currentIndex())) self.email_view.resizeColumnsToContents() - self.connect(self.test_email_button, SIGNAL('clicked(bool)'), - self.test_email) self.connect(self.email_remove, SIGNAL('clicked()'), self.remove_email_account) @@ -516,68 +476,14 @@ class ConfigDialog(QDialog, Ui_Dialog): idx = self.email_view.currentIndex() self._email_accounts.remove(idx) - def create_gmail_relay(self, *args): - self.relay_username.setText('@gmail.com') - self.relay_password.setText('') - self.relay_host.setText('smtp.gmail.com') - self.relay_port.setValue(587) - self.relay_tls.setChecked(True) - - info_dialog(self, _('Finish gmail setup'), - _('Dont forget to enter your gmail username and password')).exec_() - self.relay_username.setFocus(Qt.OtherFocusReason) - self.relay_username.setCursorPosition(0) - def set_email_settings(self): - from_ = unicode(self.email_from.text()).strip() - if self._email_accounts.accounts and not from_: - error_dialog(self, _('Bad configuration'), - _('You must set the From email address')).exec_() - return False - 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_() + to_set = bool(self._email_accounts.accounts) + if not self.send_email_widget.set_email_settings(to_set): return False conf = smtp_prefs() - conf.set('from_', from_) conf.set('accounts', self._email_accounts.accounts) - 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') return True - def test_email(self, *args): - if self.set_email_settings(): - TestEmail(self._email_accounts.accounts, self).exec_() - - def test_email_settings(self, to): - opts = smtp_prefs().parse() - from calibre.utils.smtp import sendmail, create_mail - buf = cStringIO.StringIO() - oout, oerr = sys.stdout, sys.stderr - sys.stdout = sys.stderr = buf - tb = None - try: - msg = create_mail(opts.from_, to, 'Test mail from calibre', - 'Test mail from calibre') - sendmail(msg, from_=opts.from_, to=[to], - verbose=3, timeout=30, relay=opts.relay_host, - username=opts.relay_username, - password=unhexlify(opts.relay_password), - encryption=opts.encryption, port=opts.relay_port) - except: - import traceback - tb = traceback.format_exc() - tb += '\n\nLog:\n' + buf.getvalue() - finally: - sys.stdout, sys.stderr = oout, oerr - return tb def add_plugin(self): path = unicode(self.plugin_path.text()) @@ -722,7 +628,7 @@ class ConfigDialog(QDialog, Ui_Dialog): def browse(self): dir = choose_dir(self, 'database location dialog', - _('Select database location')) + _('Select location for books')) if dir: self.location.setText(dir) diff --git a/src/calibre/gui2/dialogs/config.ui b/src/calibre/gui2/dialogs/config.ui index 0ee6629f91..90a53364ca 100644 --- a/src/calibre/gui2/dialogs/config.ui +++ b/src/calibre/gui2/dialogs/config.ui @@ -541,38 +541,7 @@ - - - - calibre can send your books to you (or your reader) by email - - - true - - - - - - - - Send email &from: - - - email_from - - - - - - - <p>This is what will be present in the From: field of emails sent by calibre.<br> Set it to your email address - - - - - - @@ -640,195 +609,18 @@ - - - - <p>A mail server is useful if the service you are sending mail to only accepts email from well know mail services. + + + + calibre can send your books to you (or your reader) by email - - Mail &Server + + true - - - - - calibre can <b>optionally</b> use a server to send mail - - - true - - - - - - - &Hostname: - - - relay_host - - - - - - - The hostname of your mail server. For e.g. smtp.gmail.com - - - - - - - - - &Port: - - - relay_port - - - - - - - The port your mail server listens for connections on. The default is 25 - - - 1 - - - 65555 - - - 25 - - - - - - - - - &Username: - - - relay_username - - - - - - - Your username on the mail server - - - - - - - &Password: - - - relay_password - - - - - - - Your password on the mail server - - - QLineEdit::Password - - - - - - - &Show - - - - - - - &Encryption: - - - relay_tls - - - - - - - Use TLS encryption when connecting to the mail server. This is the most common. - - - &TLS - - - true - - - - - - - Use SSL encryption when connecting to the mail server. - - - &SSL - - - - - - - Qt::Horizontal - - - - 40 - 20 - - - - - - - - - - - Use Gmail - - - - :/images/gmail_logo.png:/images/gmail_logo.png - - - - 48 - 48 - - - - Qt::ToolButtonTextUnderIcon - - - - - - - &Test email - - - - + + @@ -1062,7 +854,8 @@ - If you want to use the content server to access your ebook collection on your iphone with Stanza, you will need to add the URL http://myhostname:8080/stanza as a new catalog in the stanza reader on your iphone. Here myhostname should be the fully qualified hostname or the IP address of this computer. + <p>Remember to leave calibre running as the server only runs as long as calibre is running. +<p>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. true @@ -1216,6 +1009,14 @@ + + + SendEmail + QWidget +
calibre/gui2/wizard/send_email.h
+ 1 +
+
diff --git a/src/calibre/gui2/dialogs/metadata_single.ui b/src/calibre/gui2/dialogs/metadata_single.ui index 2c4ab859a3..4163c51583 100644 --- a/src/calibre/gui2/dialogs/metadata_single.ui +++ b/src/calibre/gui2/dialogs/metadata_single.ui @@ -177,7 +177,7 @@
- + Rating of this book. 0-5 stars @@ -309,28 +309,6 @@ - - - - false - - - Series index. - - - Series index. - - - Book - - - 0 - - - 10000 - - - @@ -357,6 +335,19 @@ + + + + false + + + Book + + + 9999.989999999999782 + + + @@ -640,7 +631,6 @@ series tag_editor_button remove_series_button - series_index isbn comments fetch_metadata_button diff --git a/src/calibre/gui2/images/news/carta.png b/src/calibre/gui2/images/news/carta.png new file mode 100644 index 0000000000..a9a184f5d8 Binary files /dev/null and b/src/calibre/gui2/images/news/carta.png differ diff --git a/src/calibre/gui2/images/news/elektrolese.png b/src/calibre/gui2/images/news/elektrolese.png new file mode 100644 index 0000000000..a614cf6934 Binary files /dev/null and b/src/calibre/gui2/images/news/elektrolese.png differ diff --git a/src/calibre/gui2/images/welcome_wizard.svg b/src/calibre/gui2/images/welcome_wizard.svg new file mode 100644 index 0000000000..d8ed0e69c9 --- /dev/null +++ b/src/calibre/gui2/images/welcome_wizard.svg @@ -0,0 +1,1712 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + image/svg+xml + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/calibre/gui2/library.py b/src/calibre/gui2/library.py index c85c4248c8..21583e8f98 100644 --- a/src/calibre/gui2/library.py +++ b/src/calibre/gui2/library.py @@ -20,7 +20,7 @@ from calibre.gui2 import NONE, TableView, qstring_to_unicode, config, \ error_dialog from calibre.utils.search_query_parser import SearchQueryParser from calibre.ebooks.metadata.meta import set_metadata as _set_metadata -from calibre.ebooks.metadata import string_to_authors +from calibre.ebooks.metadata import string_to_authors, fmt_sidx class LibraryDelegate(QItemDelegate): COLOR = QColor("blue") @@ -98,40 +98,38 @@ class DateDelegate(QStyledItemDelegate): qde.setCalendarPopup(True) return qde -class BooksModel(QAbstractTableModel): - coding = zip( - [1000,900,500,400,100,90,50,40,10,9,5,4,1], - ["M","CM","D","CD","C","XC","L","XL","X","IX","V","IV","I"] - ) +class PubDateDelegate(QStyledItemDelegate): + def displayText(self, val, locale): + return val.toDate().toString('MMM yyyy') + + def createEditor(self, parent, option, index): + qde = QStyledItemDelegate.createEditor(self, parent, option, index) + qde.setDisplayFormat('MM yyyy') + qde.setMinimumDate(QDate(101,1,1)) + qde.setCalendarPopup(True) + return qde + + +class BooksModel(QAbstractTableModel): headers = { 'title' : _("Title"), 'authors' : _("Author(s)"), 'size' : _("Size (MB)"), 'timestamp' : _("Date"), + 'pubdate' : _('Published'), 'rating' : _('Rating'), 'publisher' : _("Publisher"), 'tags' : _("Tags"), 'series' : _("Series"), } - @classmethod - def roman(cls, num): - if num <= 0 or num >= 4000 or int(num) != num: - return str(num) - result = [] - for d, r in cls.coding: - while num >= d: - result.append(r) - num -= d - return ''.join(result) - def __init__(self, parent=None, buffer=40): QAbstractTableModel.__init__(self, parent) self.db = None self.column_map = config['column_map'] self.editable_cols = ['title', 'authors', 'rating', 'publisher', - 'tags', 'series', 'timestamp'] + 'tags', 'series', 'timestamp', 'pubdate'] self.default_image = QImage(':/images/book.svg') self.sorted_on = ('timestamp', Qt.AscendingOrder) self.last_search = '' # The last search performed on this model @@ -157,8 +155,12 @@ class BooksModel(QAbstractTableModel): tidx = self.column_map.index('timestamp') except ValueError: tidx = -1 + try: + pidx = self.column_map.index('pubdate') + except ValueError: + pidx = -1 - self.emit(SIGNAL('columns_sorted(int,int)'), idx, tidx) + self.emit(SIGNAL('columns_sorted(int,int,int)'), idx, tidx, pidx) def set_database(self, db): @@ -186,8 +188,8 @@ class BooksModel(QAbstractTableModel): self.db = None self.reset() - def add_books(self, paths, formats, metadata, uris=[], add_duplicates=False): - ret = self.db.add_books(paths, formats, metadata, uris, + def add_books(self, paths, formats, metadata, add_duplicates=False): + ret = self.db.add_books(paths, formats, metadata, add_duplicates=add_duplicates) self.count_changed() return ret @@ -313,7 +315,7 @@ class BooksModel(QAbstractTableModel): series = self.db.series(idx) if series: sidx = self.db.series_index(idx) - sidx = self.__class__.roman(sidx) if self.use_roman_numbers else str(sidx) + sidx = fmt_sidx(sidx, use_roman = self.use_roman_numbers) data[_('Series')] = _('Book %s of %s.')%(sidx, series) return data @@ -492,6 +494,7 @@ class BooksModel(QAbstractTableModel): ridx = FIELD_MAP['rating'] pidx = FIELD_MAP['publisher'] tmdx = FIELD_MAP['timestamp'] + pddx = FIELD_MAP['pubdate'] srdx = FIELD_MAP['series'] tgdx = FIELD_MAP['tags'] siix = FIELD_MAP['series_index'] @@ -508,6 +511,12 @@ class BooksModel(QAbstractTableModel): dt = dt - timedelta(seconds=time.timezone) + timedelta(hours=time.daylight) return QDate(dt.year, dt.month, dt.day) + def pubdate(r): + dt = self.db.data[r][pddx] + if dt: + dt = dt - timedelta(seconds=time.timezone) + timedelta(hours=time.daylight) + return QDate(dt.year, dt.month, dt.day) + def rating(r): r = self.db.data[r][ridx] r = r/2 if r else 0 @@ -526,8 +535,8 @@ class BooksModel(QAbstractTableModel): def series(r): series = self.db.data[r][srdx] if series: - return series + ' [%d]'%self.db.data[r][siix] - + idx = fmt_sidx(self.db.data[r][siix]) + return series + ' [%s]'%idx def size(r): size = self.db.data[r][sidx] if size: @@ -538,6 +547,7 @@ class BooksModel(QAbstractTableModel): 'authors' : authors, 'size' : size, 'timestamp': timestamp, + 'pubdate' : pubdate, 'rating' : rating, 'publisher': publisher, 'tags' : tags, @@ -577,7 +587,7 @@ class BooksModel(QAbstractTableModel): if column not in self.editable_cols: return False val = int(value.toInt()[0]) if column == 'rating' else \ - value.toDate() if column == 'timestamp' else \ + value.toDate() if column in ('timestamp', 'pubdate') else \ unicode(value.toString()) id = self.db.id(row) if column == 'rating': @@ -585,10 +595,10 @@ class BooksModel(QAbstractTableModel): val *= 2 self.db.set_rating(id, val) elif column == 'series': - pat = re.compile(r'\[(\d+)\]') + pat = re.compile(r'\[([.0-9]+)\]') match = pat.search(val) if match is not None: - self.db.set_series_index(id, int(match.group(1))) + self.db.set_series_index(id, float(match.group(1))) val = pat.sub('', val) val = val.strip() if val: @@ -598,6 +608,11 @@ class BooksModel(QAbstractTableModel): return False dt = datetime(val.year(), val.month(), val.day()) + timedelta(seconds=time.timezone) - timedelta(hours=time.daylight) self.db.set_timestamp(id, dt) + elif column == 'pubdate': + if val.isNull() or not val.isValid(): + return False + dt = datetime(val.year(), val.month(), val.day()) + timedelta(seconds=time.timezone) - timedelta(hours=time.daylight) + self.db.set_pubdate(id, dt) else: self.db.set(row, column, val) self.emit(SIGNAL("dataChanged(QModelIndex, QModelIndex)"), \ @@ -625,29 +640,35 @@ class BooksView(TableView): TableView.__init__(self, parent) self.rating_delegate = LibraryDelegate(self) self.timestamp_delegate = DateDelegate(self) + self.pubdate_delegate = PubDateDelegate(self) self.display_parent = parent self._model = modelcls(self) self.setModel(self._model) self.setSelectionBehavior(QAbstractItemView.SelectRows) self.setSortingEnabled(True) try: - self.columns_sorted(self._model.column_map.index('rating'), - self._model.column_map.index('timestamp')) + cm = self._model.column_map + self.columns_sorted(cm.index('rating') if 'rating' in cm else -1, + cm.index('timestamp') if 'timestamp' in cm else -1, + cm.index('pubdate') if 'pubdate' in cm else -1) except ValueError: pass QObject.connect(self.selectionModel(), SIGNAL('currentRowChanged(QModelIndex, QModelIndex)'), self._model.current_changed) - self.connect(self._model, SIGNAL('columns_sorted(int, int)'), self.columns_sorted, Qt.QueuedConnection) + self.connect(self._model, SIGNAL('columns_sorted(int,int,int)'), + self.columns_sorted, Qt.QueuedConnection) - def columns_sorted(self, rating_col, timestamp_col): + def columns_sorted(self, rating_col, timestamp_col, pubdate_col): for i in range(self.model().columnCount(None)): if self.itemDelegateForColumn(i) in (self.rating_delegate, - self.timestamp_delegate): + self.timestamp_delegate, self.pubdate_delegate): self.setItemDelegateForColumn(i, self.itemDelegate()) if rating_col > -1: self.setItemDelegateForColumn(rating_col, self.rating_delegate) if timestamp_col > -1: self.setItemDelegateForColumn(timestamp_col, self.timestamp_delegate) + if pubdate_col > -1: + self.setItemDelegateForColumn(pubdate_col, self.pubdate_delegate) def set_context_menu(self, edit_metadata, send_to_device, convert, view, save, open_folder, book_details, similar_menu=None): diff --git a/src/calibre/gui2/main.py b/src/calibre/gui2/main.py index 03c5f1dbdf..f7c00fc0c4 100644 --- a/src/calibre/gui2/main.py +++ b/src/calibre/gui2/main.py @@ -11,11 +11,11 @@ from PyQt4.Qt import Qt, SIGNAL, QObject, QCoreApplication, QUrl, QTimer, \ QModelIndex, QPixmap, QColor, QPainter, QMenu, QIcon, \ QToolButton, QDialog, QDesktopServices, QFileDialog, \ QSystemTrayIcon, QApplication, QKeySequence, QAction, \ - QProgressDialog, QMessageBox, QStackedLayout + QMessageBox, QStackedLayout from PyQt4.QtSvg import QSvgRenderer from calibre import __version__, __appname__, sanitize_file_name, \ - iswindows, isosx, prints + iswindows, isosx, prints, patheq from calibre.ptempfile import PersistentTemporaryFile from calibre.utils.config import prefs, dynamic from calibre.gui2 import APP_UID, warning_dialog, choose_files, error_dialog, \ @@ -27,6 +27,7 @@ from calibre.gui2 import APP_UID, warning_dialog, choose_files, error_dialog, \ available_width, GetMetadata from calibre.gui2.cover_flow import CoverFlow, DatabaseImages, pictureflowerror from calibre.gui2.widgets import ProgressIndicator +from calibre.gui2.wizard import move_library from calibre.gui2.dialogs.scheduler import Scheduler from calibre.gui2.update import CheckForUpdates from calibre.gui2.main_window import MainWindow, option_parser as _option_parser @@ -297,6 +298,14 @@ class Main(MainWindow, Ui_MainWindow, DeviceGUI): QObject.connect(self.action_convert, SIGNAL('triggered(bool)'), self.convert_single) self.convert_menu = cm + pm = QMenu() + pm.addAction(self.action_preferences) + pm.addAction(_('Run welcome wizard')) + self.connect(pm.actions()[1], SIGNAL('triggered(bool)'), + self.run_wizard) + self.action_preferences.setMenu(pm) + self.preferences_menu = pm + self.tool_bar.widgetForAction(self.action_news).\ setPopupMode(QToolButton.MenuButtonPopup) self.tool_bar.widgetForAction(self.action_edit).\ @@ -311,6 +320,8 @@ class Main(MainWindow, Ui_MainWindow, DeviceGUI): setPopupMode(QToolButton.MenuButtonPopup) self.tool_bar.widgetForAction(self.action_view).\ setPopupMode(QToolButton.MenuButtonPopup) + self.tool_bar.widgetForAction(self.action_preferences).\ + setPopupMode(QToolButton.MenuButtonPopup) self.tool_bar.setContextMenuPolicy(Qt.PreventContextMenu) self.connect(self.preferences_action, SIGNAL('triggered(bool)'), @@ -1376,56 +1387,27 @@ class Main(MainWindow, Ui_MainWindow, DeviceGUI): self.save_menu.actions()[2].setText( _('Save only %s format to disk')% prefs['output_format'].upper()) - if self.library_path != d.database_location: - try: - newloc = d.database_location - if not os.path.exists(os.path.join(newloc, 'metadata.db')): - if os.access(self.library_path, os.R_OK): - pd = QProgressDialog('', '', 0, 100, self) - pd.setWindowModality(Qt.ApplicationModal) - pd.setCancelButton(None) - pd.setWindowTitle(_('Copying database')) - pd.show() - self.status_bar.showMessage( - _('Copying library to ')+newloc) - self.setCursor(Qt.BusyCursor) - self.library_view.setEnabled(False) - self.library_view.model().db.move_library_to( - newloc, pd) - else: - try: - db = LibraryDatabase2(newloc) - self.library_view.set_database(db) - except Exception, err: - traceback.print_exc() - d = error_dialog(self, _('Invalid database'), - _('

An invalid database already exists at ' - '%s, delete it before trying to move the ' - 'existing database.
Error: %s')%(newloc, - str(err))) - d.exec_() - self.library_path = \ - self.library_view.model().db.library_path - prefs['library_path'] = self.library_path - except Exception, err: - traceback.print_exc() - d = error_dialog(self, _('Could not move database'), - unicode(err)) - d.exec_() - finally: - self.unsetCursor() - self.library_view.setEnabled(True) - self.status_bar.clearMessage() - self.search.clear_to_help() - self.status_bar.reset_info() - self.library_view.sortByColumn(3, Qt.DescendingOrder) - self.library_view.resizeRowsToContents() if hasattr(d, 'directories'): set_sidebar_directories(d.directories) self.library_view.model().read_config() self.create_device_menu() + if not patheq(self.library_path, d.database_location): + newloc = d.database_location + move_library(self.library_path, newloc, self, + self.library_moved) + + + def library_moved(self, newloc): + if newloc is None: return + db = LibraryDatabase2(newloc) + self.library_view.set_database(db) + self.status_bar.clearMessage() + self.search.clear_to_help() + self.status_bar.reset_info() + self.library_view.sortByColumn(3, Qt.DescendingOrder) + ############################################################################ ################################ Book info ################################# @@ -1652,6 +1634,17 @@ class Main(MainWindow, Ui_MainWindow, DeviceGUI): self.hide() return True + def run_wizard(self, *args): + if self.confirm_quit(): + self.run_wizard_b4_shutdown = True + self.restart_after_quit = True + try: + self.shutdown(write_settings=False) + except: + pass + QApplication.instance().quit() + + def closeEvent(self, e): self.write_settings() @@ -1677,7 +1670,7 @@ class Main(MainWindow, Ui_MainWindow, DeviceGUI): def update_found(self, version): os = 'windows' if iswindows else 'osx' if isosx else 'linux' url = 'http://%s.kovidgoyal.net/download_%s'%(__appname__, os) - self.latest_version = _('' + self.latest_version = '
'+_('' 'Latest version: %s')%(url, version) self.vanity.setText(self.vanity_template%\ (dict(version=self.latest_version, @@ -1726,12 +1719,19 @@ def init_qt(args): def run_gui(opts, args, actions, listener, app): initialize_file_icon_provider() + if not dynamic.get('welcome_wizard_was_run', False): + from calibre.gui2.wizard import wizard + wizard().exec_() + dynamic.set('welcome_wizard_was_run', True) main = Main(listener, opts, actions) sys.excepthook = main.unhandled_exception if len(args) > 1: args[1] = os.path.abspath(args[1]) main.add_filesystem_book(args[1]) ret = app.exec_() + if getattr(main, 'run_wizard_b4_shutdown', False): + from calibre.gui2.wizard import wizard + wizard().exec_() if getattr(main, 'restart_after_quit', False): e = sys.executable if getattr(sys, 'froze', False) else sys.argv[0] print 'Restarting with:', e, sys.argv diff --git a/src/calibre/gui2/tools.py b/src/calibre/gui2/tools.py index 711c10943b..5610dd0ecf 100644 --- a/src/calibre/gui2/tools.py +++ b/src/calibre/gui2/tools.py @@ -68,7 +68,8 @@ def convert_single_ebook(parent, db, book_ids, auto_conversion=False, out_format msg = '%s' % '\n'.join(res) warning_dialog(parent, _('Could not convert some books'), - _('Could not convert %d of %d books, because no suitable source format was found.' % (len(res), total)), + _('Could not convert %d of %d books, because no suitable source' + ' format was found.') % (len(res), total), msg).exec_() return jobs, changed, bad @@ -122,7 +123,8 @@ def convert_bulk_ebook(parent, db, book_ids, out_format=None): msg = '%s' % '\n'.join(res) warning_dialog(parent, _('Could not convert some books'), - _('Could not convert %d of %d books, because no suitable source format was found.' % (len(res), total)), + _('Could not convert %d of %d books, because no suitable ' + 'source format was found.') % (len(res), total), msg).exec_() return jobs, changed, bad diff --git a/src/calibre/gui2/wizard/__init__.py b/src/calibre/gui2/wizard/__init__.py new file mode 100644 index 0000000000..1a0b626fbb --- /dev/null +++ b/src/calibre/gui2/wizard/__init__.py @@ -0,0 +1,503 @@ +#!/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 ' +__docformat__ = 'restructuredtext en' + +import os, traceback, re +from Queue import Empty, Queue +from contextlib import closing + + +from PyQt4.Qt import QWizard, QWizardPage, QPixmap, Qt, QAbstractListModel, \ + QVariant, QItemSelectionModel, SIGNAL, QObject, QTimer +from calibre import __appname__, patheq +from calibre.library.database2 import LibraryDatabase2 +from calibre.library.move import MoveLibrary +from calibre.resources import server_resources +from calibre.gui2.wizard.send_email import smtp_prefs +from calibre.gui2.wizard.device_ui import Ui_WizardPage as DeviceUI +from calibre.gui2.wizard.library_ui import Ui_WizardPage as LibraryUI +from calibre.gui2.wizard.finish_ui import Ui_WizardPage as FinishUI +from calibre.gui2.wizard.kindle_ui import Ui_WizardPage as KindleUI +from calibre.gui2.wizard.stanza_ui import Ui_WizardPage as StanzaUI + +from calibre.utils.config import dynamic, prefs +from calibre.gui2 import NONE, choose_dir, error_dialog +from calibre.gui2.dialogs.progress import ProgressDialog + +class Device(object): + + output_profile = 'default' + output_format = 'EPUB' + name = _('Default') + manufacturer = _('Default') + id = 'default' + + @classmethod + def set_output_profile(cls): + if cls.output_profile: + from calibre.ebooks.conversion.config import load_defaults, save_defaults + recs = load_defaults('page_setup') + recs['output_profile'] = cls.output_profile + save_defaults('page_setup', recs) + + @classmethod + def set_output_format(cls): + if cls.output_format: + prefs.set('output_format', cls.output_format) + + @classmethod + def commit(cls): + cls.set_output_profile() + cls.set_output_format() + +class Kindle(Device): + + output_profile = 'kindle' + output_format = 'MOBI' + name = 'Kindle 1 or 2' + manufacturer = 'Amazon' + id = 'kindle' + +class Sony500(Device): + + output_profile = 'sony' + name = 'SONY PRS 500' + output_format = 'LRF' + manufacturer = 'SONY' + id = 'prs500' + +class Sony505(Sony500): + + output_format = 'EPUB' + name = 'SONY PRS 505/700' + id = 'prs505' + +class CybookG3(Device): + + name = 'Cybook Gen 3' + output_format = 'MOBI' + output_profile = 'cybookg3' + manufacturer = 'Booken' + id = 'cybookg3' + +class BeBook(Device): + + name = 'BeBook or BeBook Mini' + output_format = 'EPUB' + output_profile = 'sony' + manufacturer = 'Endless Ideas' + id = 'bebook' + +class iPhone(Device): + + name = 'iPhone/iTouch + Stanza' + output_format = 'EPUB' + manufacturer = 'Apple' + id = 'iphone' + +class Hanlin(Device): + + name = 'Hanlin V3' + output_format = 'MOBI' + output_profile = 'hanlinv3' + manufacturer = 'Hanlin' + id = 'hanlinv3' + +def get_devices(): + for x in globals().values(): + if isinstance(x, type) and issubclass(x, Device): + yield x + +def get_manufacturers(): + mans = set([]) + for x in get_devices(): + mans.add(x.manufacturer) + mans.remove(_('Default')) + return [_('Default')] + sorted(mans) + +def get_devices_of(manufacturer): + ans = [d for d in get_devices() if d.manufacturer == manufacturer] + return sorted(ans, cmp=lambda x,y:cmp(x.name, y.name)) + +class ManufacturerModel(QAbstractListModel): + + def __init__(self): + QAbstractListModel.__init__(self) + self.manufacturers = get_manufacturers() + + def rowCount(self, p): + return len(self.manufacturers) + + def columnCount(self, p): + return 1 + + def data(self, index, role): + if role == Qt.DisplayRole: + return QVariant(self.manufacturers[index.row()]) + if role == Qt.UserRole: + return self.manufacturers[index.row()] + return NONE + + def index_of(self, man): + for i, x in enumerate(self.manufacturers): + if x == man: + return self.index(i) + +class DeviceModel(QAbstractListModel): + + def __init__(self, manufacturer): + QAbstractListModel.__init__(self) + self.devices = get_devices_of(manufacturer) + + def rowCount(self, p): + return len(self.devices) + + def columnCount(self, p): + return 1 + + def data(self, index, role): + if role == Qt.DisplayRole: + return QVariant(self.devices[index.row()].name) + if role == Qt.UserRole: + return self.devices[index.row()] + return NONE + + def index_of(self, dev): + for i, device in enumerate(self.devices): + if device is dev: + return self.index(i) + +class KindlePage(QWizardPage, KindleUI): + + ID = 3 + + def __init__(self): + QWizardPage.__init__(self) + self.setupUi(self) + + def initializePage(self): + opts = smtp_prefs().parse() + for x in opts.accounts.keys(): + if x.strip().endswith('@kindle.com'): + self.to_address.setText(x) + def x(): + t = unicode(self.to_address.text()) + if t.strip(): + return t.strip() + + self.send_email_widget.initialize(x) + + def commit(self): + x = unicode(self.to_address.text()).strip() + parts = x.split('@') + if len(parts) < 2 or not parts[0]: return + + if self.send_email_widget.set_email_settings(True): + conf = smtp_prefs() + accounts = conf.get('accounts', {}) + if not accounts: accounts = {} + for y in accounts.values(): + y[2] = False + accounts[x] = ['AZW, MOBI, TPZ, PRC, AZW1', True, True] + conf.set('accounts', accounts) + + def nextId(self): + return FinishPage.ID + +class StanzaPage(QWizardPage, StanzaUI): + + ID = 5 + + def __init__(self): + QWizardPage.__init__(self) + self.setupUi(self) + self.connect(self.content_server, SIGNAL('stateChanged(int)'), self.set_port) + + def initializePage(self): + from calibre.gui2 import config + yes = config['autolaunch_server'] + self.content_server.setChecked(yes) + self.set_port() + + def nextId(self): + return FinishPage.ID + + def commit(self): + p = self.set_port() + if p is not None: + from calibre.library import server_config + c = server_config() + c.set('port', p) + + + def set_port(self, *args): + if not self.content_server.isChecked(): return + import socket + s = socket.socket() + with closing(s): + for p in range(8080, 8100): + try: + s.bind(('0.0.0.0', p)) + t = unicode(self.instructions.text()) + t = re.sub(r':\d+', ':'+str(p), t) + self.instructions.setText(t) + return p + except: + continue + + + + +class DevicePage(QWizardPage, DeviceUI): + + ID = 2 + + def __init__(self): + QWizardPage.__init__(self) + self.setupUi(self) + self.registerField("manufacturer", self.manufacturer_view) + self.registerField("device", self.device_view) + + def initializePage(self): + self.man_model = ManufacturerModel() + self.manufacturer_view.setModel(self.man_model) + previous = dynamic.get('welcome_wizard_device', False) + if previous: + previous = [x for x in get_devices() if \ + x.id == previous] + if not previous: + previous = [Device] + previous = previous[0] + else: + previous = Device + idx = self.man_model.index_of(previous.manufacturer) + if idx is None: + idx = self.man_model.index_of(Device.manufacturer) + previous = Device + self.manufacturer_view.selectionModel().select(idx, + QItemSelectionModel.Select) + self.dev_model = DeviceModel(self.man_model.data(idx, Qt.UserRole)) + idx = self.dev_model.index_of(previous) + self.device_view.setModel(self.dev_model) + self.device_view.selectionModel().select(idx, + QItemSelectionModel.Select) + self.connect(self.manufacturer_view.selectionModel(), + SIGNAL('selectionChanged(QItemSelection,QItemSelection)'), + self.manufacturer_changed) + + def manufacturer_changed(self, current, previous): + new = list(current.indexes())[0] + man = self.man_model.data(new, Qt.UserRole) + self.dev_model = DeviceModel(man) + self.device_view.setModel(self.dev_model) + self.device_view.selectionModel().select(self.dev_model.index(0), + QItemSelectionModel.Select) + + def commit(self): + idx = list(self.device_view.selectionModel().selectedIndexes())[0] + dev = self.dev_model.data(idx, Qt.UserRole) + dev.commit() + dynamic.set('welcome_wizard_device', dev.id) + + def nextId(self): + idx = list(self.device_view.selectionModel().selectedIndexes())[0] + dev = self.dev_model.data(idx, Qt.UserRole) + if dev is Kindle: + return KindlePage.ID + if dev is iPhone: + return StanzaPage.ID + return FinishPage.ID + +class MoveMonitor(QObject): + + def __init__(self, worker, rq, callback, parent): + QObject.__init__(self, parent) + self.worker = worker + self.rq = rq + self.callback = callback + self.parent = parent + + self.worker.start() + self.dialog = ProgressDialog(_('Moving library...'), '', + max=self.worker.total, parent=parent) + self.dialog.button_box.setDisabled(True) + self.dialog.setModal(True) + self.dialog.show() + self.timer = QTimer(self) + self.connect(self.timer, SIGNAL('timeout()'), self.check) + self.timer.start(200) + + def check(self): + if self.worker.is_alive(): + self.update() + else: + self.timer.stop() + self.dialog.hide() + if self.worker.failed: + error_dialog(self.parent, _('Failed to move library'), + _('Failed to move library'), self.worker.details, show=True) + return self.callback(None) + else: + return self.callback(self.worker.to) + + def update(self): + try: + title = self.rq.get_nowait()[-1] + self.dialog.value += 1 + self.dialog.set_msg(_('Copied') + ' '+title) + except Empty: + pass + + +class Callback(object): + + def __init__(self, callback): + self.callback = callback + + def __call__(self, newloc): + if newloc is not None: + prefs['library_path'] = newloc + self.callback(newloc) + +_mm = None +def move_library(oldloc, newloc, parent, callback_on_complete): + callback = Callback(callback_on_complete) + try: + if not os.path.exists(os.path.join(newloc, 'metadata.db')): + if oldloc and os.access(os.path.join(oldloc, 'metadata.db'), os.R_OK): + # Move old library to new location + try: + db = LibraryDatabase2(oldloc) + except: + return move_library(None, newloc, parent, + callback) + else: + rq = Queue() + m = MoveLibrary(oldloc, newloc, db.data.count(), rq) + global _mm + _mm = MoveMonitor(m, rq, callback, parent) + return + else: + # Create new library at new location + db = LibraryDatabase2(newloc) + callback(newloc) + return + + # Try to load existing library at new location + try: + ndb = LibraryDatabase2(newloc) + except Exception, err: + det = traceback.format_exc() + error_dialog(parent, _('Invalid database'), + _('

An invalid library already exists at ' + '%s, delete it before trying to move the ' + 'existing library.
Error: %s')%(newloc, + str(err)), det, show=True) + callback(None) + return + else: + callback(newloc) + return + except Exception, err: + det = traceback.format_exc() + error_dialog(parent, _('Could not move library'), + unicode(err), det, show=True) + callback(None) + +class LibraryPage(QWizardPage, LibraryUI): + + ID = 1 + + def __init__(self): + QWizardPage.__init__(self) + self.setupUi(self) + self.registerField('library_location', self.location) + self.connect(self.button_change, SIGNAL('clicked()'), self.change) + + def change(self): + dir = choose_dir(self, 'database location dialog', + _('Select location for books')) + if dir: + self.location.setText(dir) + + def initializePage(self): + lp = prefs['library_path'] + if not lp: + lp = os.path.expanduser('~') + self.location.setText(lp) + + def isComplete(self): + lp = unicode(self.location.text()) + return lp and os.path.exists(lp) and os.path.isdir(lp) and os.access(lp, + os.W_OK) + + def commit(self, completed): + oldloc = prefs['library_path'] + newloc = unicode(self.location.text()) + if not patheq(oldloc, newloc): + move_library(oldloc, newloc, self.wizard(), completed) + return True + return False + + def nextId(self): + return DevicePage.ID + +class FinishPage(QWizardPage, FinishUI): + + ID = 4 + + def __init__(self): + QWizardPage.__init__(self) + self.setupUi(self) + + def nextId(self): + return -1 + + + +class Wizard(QWizard): + + def __init__(self, parent): + QWizard.__init__(self, parent) + self.setWindowTitle(__appname__+' '+_('welcome wizard')) + p = QPixmap() + p.loadFromData(server_resources['calibre.png']) + self.setPixmap(self.LogoPixmap, p.scaledToHeight(80, + Qt.SmoothTransformation)) + self.setPixmap(self.WatermarkPixmap, + QPixmap(':/images/welcome_wizard.svg')) + self.setPixmap(self.BackgroundPixmap, QPixmap(':/images/wizard.svg')) + self.device_page = DevicePage() + self.library_page = LibraryPage() + self.finish_page = FinishPage() + self.kindle_page = KindlePage() + self.stanza_page = StanzaPage() + self.setPage(self.library_page.ID, self.library_page) + self.setPage(self.device_page.ID, self.device_page) + self.setPage(self.finish_page.ID, self.finish_page) + self.setPage(self.kindle_page.ID, self.kindle_page) + self.setPage(self.stanza_page.ID, self.stanza_page) + + self.device_extra_page = None + + def accept(self): + self.device_page.commit() + if not self.library_page.commit(self.completed): + self.completed(None) + + def completed(self, newloc): + return QWizard.accept(self) + +def wizard(parent=None): + w = Wizard(parent) + return w + +if __name__ == '__main__': + from PyQt4.Qt import QApplication + app = QApplication([]) + wizard().exec_() + diff --git a/src/calibre/gui2/wizard/device.ui b/src/calibre/gui2/wizard/device.ui new file mode 100644 index 0000000000..27af13b3ed --- /dev/null +++ b/src/calibre/gui2/wizard/device.ui @@ -0,0 +1,75 @@ + + + WizardPage + + + + 0 + 0 + 400 + 300 + + + + Welcome to calibre + + + + :/images/wizard.svg:/images/wizard.svg + + + Welcome to calibre + + + The one stop solution to all your e-book needs. + + + + + + Choose your book reader. This will set the conversion options to produce books optimized for your device. + + + true + + + + + + + &Manufacturers + + + + + + QAbstractItemView::SelectRows + + + + + + + + + + &Devices + + + + + + QAbstractItemView::SelectRows + + + + + + + + + + + + + diff --git a/src/calibre/gui2/wizard/finish.ui b/src/calibre/gui2/wizard/finish.ui new file mode 100644 index 0000000000..b42e8b1c32 --- /dev/null +++ b/src/calibre/gui2/wizard/finish.ui @@ -0,0 +1,108 @@ + + + WizardPage + + + + 0 + 0 + 400 + 300 + + + + WizardPage + + + Welcome to calibre + + + The one stop solution to all your e-book needs. + + + + + + <h2>Congratulations!</h2> You have succesfully setup calibre. Press the Finish button to apply your settings. + + + true + + + + + + + Qt::Vertical + + + + 20 + 56 + + + + + + + + <h2>Demo videos</h2>Videos demonstrating the various features of calibre are available <a href="http://calibre.kovidgoyal.net/downloads/videos/">online</a>. + + + true + + + true + + + Qt::LinksAccessibleByKeyboard|Qt::LinksAccessibleByMouse + + + + + + + Qt::Vertical + + + + 20 + 56 + + + + + + + + <h2>User Manual</h2>A User Manual is also available <a href="http://calibre.kovidgoyal.net/user_manual">online</a>. + + + true + + + true + + + Qt::LinksAccessibleByKeyboard|Qt::LinksAccessibleByMouse + + + + + + + Qt::Vertical + + + + 20 + 40 + + + + + + + + + diff --git a/src/calibre/gui2/wizard/kindle.ui b/src/calibre/gui2/wizard/kindle.ui new file mode 100644 index 0000000000..7bafc6d618 --- /dev/null +++ b/src/calibre/gui2/wizard/kindle.ui @@ -0,0 +1,80 @@ + + + WizardPage + + + + 0 + 0 + 400 + 300 + + + + WizardPage + + + Welcome to calibre + + + The one stop solution to all your e-book needs. + + + + + + <p>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 <a href="http://gmail.com">gmail account</a> and click the Use gmail button below. You will also have to register your gmail address in your Amazon account. + + + true + + + true + + + Qt::LinksAccessibleByKeyboard|Qt::LinksAccessibleByMouse + + + + + + + &Kindle email: + + + to_address + + + + + + + + + + + + + Qt::Vertical + + + + 20 + 40 + + + + + + + + + SendEmail + QWidget +

calibre/gui2/wizard/send_email.h
+ 1 + + + + + diff --git a/src/calibre/gui2/wizard/library.ui b/src/calibre/gui2/wizard/library.ui new file mode 100644 index 0000000000..d3c93bbd3c --- /dev/null +++ b/src/calibre/gui2/wizard/library.ui @@ -0,0 +1,74 @@ + + + WizardPage + + + + 0 + 0 + 481 + 300 + + + + WizardPage + + + Welcome to calibre + + + The one stop solution to all your e-book needs. + + + + + + Choose a location for your books. When you add books to calibre, they will be stored here: + + + true + + + + + + + true + + + + + + + &Change + + + + + + + Qt::Vertical + + + + 20 + 40 + + + + + + + + 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. + + + true + + + + + + + + diff --git a/src/calibre/gui2/wizard/send_email.py b/src/calibre/gui2/wizard/send_email.py new file mode 100644 index 0000000000..5650279c15 --- /dev/null +++ b/src/calibre/gui2/wizard/send_email.py @@ -0,0 +1,143 @@ +#!/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 ' +__docformat__ = 'restructuredtext en' + +import cStringIO, sys +from binascii import hexlify, unhexlify + +from PyQt4.Qt import QWidget, SIGNAL, QDialog, Qt + +from calibre.gui2.wizard.send_email_ui import Ui_Form +from calibre.utils.smtp import config as smtp_prefs +from calibre.gui2.dialogs.test_email_ui import Ui_Dialog as TE_Dialog +from calibre.gui2 import error_dialog, info_dialog + +class TestEmail(QDialog, TE_Dialog): + + def __init__(self, pa, parent): + QDialog.__init__(self, parent) + TE_Dialog.__init__(self) + self.setupUi(self) + opts = smtp_prefs().parse() + self.test_func = parent.test_email_settings + self.connect(self.test_button, SIGNAL('clicked(bool)'), self.test) + self.from_.setText(unicode(self.from_.text())%opts.from_) + if pa: + self.to.setText(pa) + if opts.relay_host: + self.label.setText(_('Using: %s:%s@%s:%s and %s encryption')% + (opts.relay_username, unhexlify(opts.relay_password), + opts.relay_host, opts.relay_port, opts.encryption)) + + def test(self): + self.log.setPlainText(_('Sending...')) + self.test_button.setEnabled(False) + try: + tb = self.test_func(unicode(self.to.text())) + if not tb: + tb = _('Mail successfully sent') + self.log.setPlainText(tb) + finally: + self.test_button.setEnabled(True) + + +class SendEmail(QWidget, Ui_Form): + + def __init__(self, parent=None): + QWidget.__init__(self, parent) + self.setupUi(self) + + def initialize(self, preferred_to_address): + self.preferred_to_address = preferred_to_address + opts = smtp_prefs().parse() + self.smtp_opts = opts + if opts.from_: + self.email_from.setText(opts.from_) + if opts.relay_host: + self.relay_host.setText(opts.relay_host) + self.relay_port.setValue(opts.relay_port) + if opts.relay_username: + self.relay_username.setText(opts.relay_username) + if opts.relay_password: + self.relay_password.setText(unhexlify(opts.relay_password)) + (self.relay_tls if opts.encryption == 'TLS' else self.relay_ssl).setChecked(True) + self.connect(self.relay_use_gmail, SIGNAL('clicked(bool)'), + self.create_gmail_relay) + self.connect(self.relay_show_password, SIGNAL('stateChanged(int)'), + lambda + state:self.relay_password.setEchoMode(self.relay_password.Password if + state == 0 else self.relay_password.Normal)) + self.connect(self.test_email_button, SIGNAL('clicked(bool)'), + self.test_email) + + + def test_email(self, *args): + pa = self.preferred_to_address() + to_set = pa is not None + if self.set_email_settings(to_set): + TestEmail(pa, self).exec_() + + def test_email_settings(self, to): + opts = smtp_prefs().parse() + from calibre.utils.smtp import sendmail, create_mail + buf = cStringIO.StringIO() + oout, oerr = sys.stdout, sys.stderr + sys.stdout = sys.stderr = buf + tb = None + try: + msg = create_mail(opts.from_, to, 'Test mail from calibre', + 'Test mail from calibre') + sendmail(msg, from_=opts.from_, to=[to], + verbose=3, timeout=30, relay=opts.relay_host, + username=opts.relay_username, + password=unhexlify(opts.relay_password), + encryption=opts.encryption, port=opts.relay_port) + except: + import traceback + tb = traceback.format_exc() + tb += '\n\nLog:\n' + buf.getvalue() + finally: + sys.stdout, sys.stderr = oout, oerr + return tb + + def create_gmail_relay(self, *args): + self.relay_username.setText('@gmail.com') + self.relay_password.setText('') + self.relay_host.setText('smtp.gmail.com') + self.relay_port.setValue(587) + self.relay_tls.setChecked(True) + + info_dialog(self, _('Finish gmail setup'), + _('Dont forget to enter your gmail username and password')).exec_() + self.relay_username.setFocus(Qt.OtherFocusReason) + self.relay_username.setCursorPosition(0) + + def set_email_settings(self, to_set): + from_ = unicode(self.email_from.text()).strip() + if to_set and not from_: + error_dialog(self, _('Bad configuration'), + _('You must set the From email address')).exec_() + return False + 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 + 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') + return True + + + diff --git a/src/calibre/gui2/wizard/send_email.ui b/src/calibre/gui2/wizard/send_email.ui new file mode 100644 index 0000000000..3802d7f451 --- /dev/null +++ b/src/calibre/gui2/wizard/send_email.ui @@ -0,0 +1,234 @@ + + + Form + + + + 0 + 0 + 585 + 238 + + + + Form + + + + + + + + Send email &from: + + + email_from + + + + + + + <p>This is what will be present in the From: field of emails sent by calibre.<br> Set it to your email address + + + + + + + + + <p>A mail server is useful if the service you are sending mail to only accepts email from well know mail services. + + + Mail &Server + + + + + + calibre can <b>optionally</b> use a server to send mail + + + true + + + + + + + &Hostname: + + + relay_host + + + + + + + The hostname of your mail server. For e.g. smtp.gmail.com + + + + + + + + + &Port: + + + relay_port + + + + + + + The port your mail server listens for connections on. The default is 25 + + + 1 + + + 65555 + + + 25 + + + + + + + + + &Username: + + + relay_username + + + + + + + Your username on the mail server + + + + + + + &Password: + + + relay_password + + + + + + + Your password on the mail server + + + QLineEdit::Password + + + + + + + &Show + + + + + + + &Encryption: + + + relay_tls + + + + + + + Use TLS encryption when connecting to the mail server. This is the most common. + + + &TLS + + + true + + + + + + + Use SSL encryption when connecting to the mail server. + + + &SSL + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + + + + + + Use Gmail + + + + :/images/gmail_logo.png:/images/gmail_logo.png + + + + 48 + 48 + + + + Qt::ToolButtonTextUnderIcon + + + + + + + &Test email + + + + + + + + + + + + diff --git a/src/calibre/gui2/wizard/stanza.ui b/src/calibre/gui2/wizard/stanza.ui new file mode 100644 index 0000000000..6dee91f8fc --- /dev/null +++ b/src/calibre/gui2/wizard/stanza.ui @@ -0,0 +1,97 @@ + + + WizardPage + + + + 0 + 0 + 400 + 300 + + + + WizardPage + + + Welcome to calibre + + + The one stop solution to all your e-book needs. + + + + + + <p>If you use the <a href="http://www.lexcycle.com/download">Stanza</a> 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. + + + true + + + true + + + + + + + Qt::Vertical + + + + 20 + 40 + + + + + + + + Turn on the &content server + + + + + + + Qt::Vertical + + + + 20 + 40 + + + + + + + + <p>Remember to leave calibre running as the server only runs as long as calibre is running. +<p>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. + + + true + + + + + + + Qt::Vertical + + + + 20 + 40 + + + + + + + + + diff --git a/src/calibre/library/database.py b/src/calibre/library/database.py index cf352c464d..7261aed7ad 100644 --- a/src/calibre/library/database.py +++ b/src/calibre/library/database.py @@ -27,7 +27,7 @@ class Concatenate(object): return self.ans[:-len(self.sep)] return self.ans class Connection(sqlite.Connection): - + def get(self, *args, **kw): ans = self.execute(*args) if not kw.get('all', True): @@ -785,8 +785,8 @@ ALTER TABLE books ADD COLUMN isbn TEXT DEFAULT "" COLLATE NOCASE; FROM books; ''') conn.execute('pragma user_version=12') - conn.commit() - + conn.commit() + def __init__(self, dbpath, row_factory=False): self.dbpath = dbpath self.conn = _connect(dbpath) @@ -901,7 +901,7 @@ ALTER TABLE books ADD COLUMN isbn TEXT DEFAULT "" COLLATE NOCASE; def id(self, index): return self.data[index][0] - + def row(self, id): for r, record in enumerate(self.data): if record[0] == id: @@ -916,8 +916,8 @@ ALTER TABLE books ADD COLUMN isbn TEXT DEFAULT "" COLLATE NOCASE; return _('Unknown') def authors(self, index, index_is_id=False): - ''' - Authors as a comma separated list or None. + ''' + Authors as a comma separated list or None. In the comma separated list, commas in author names are replaced by | symbols ''' if not index_is_id: @@ -939,11 +939,11 @@ ALTER TABLE books ADD COLUMN isbn TEXT DEFAULT "" COLLATE NOCASE; if index_is_id: return self.conn.get('SELECT publisher FROM meta WHERE id=?', (index,), all=False) return self.data[index][3] - + def publisher_id(self, index, index_is_id=False): id = index if index_is_id else self.id(index) return self.conn.get('SELECT publisher from books_publishers_link WHERE book=?', (id,), all=False) - + def rating(self, index, index_is_id=False): if index_is_id: return self.conn.get('SELECT rating FROM meta WHERE id=?', (index,), all=False) @@ -983,7 +983,7 @@ ALTER TABLE books ADD COLUMN isbn TEXT DEFAULT "" COLLATE NOCASE; def series(self, index, index_is_id=False): id = self.series_id(index, index_is_id) return self.conn.get('SELECT name from series WHERE id=?', (id,), all=False) - + def series_index(self, index, index_is_id=False): ans = None if not index_is_id: @@ -991,9 +991,9 @@ ALTER TABLE books ADD COLUMN isbn TEXT DEFAULT "" COLLATE NOCASE; else: ans = self.conn.get('SELECT series_index FROM books WHERE id=?', (index,), all=False) try: - return int(ans) + return float(ans) except: - return 1 + return 1.0 def books_in_series(self, series_id): ''' @@ -1021,7 +1021,7 @@ ALTER TABLE books ADD COLUMN isbn TEXT DEFAULT "" COLLATE NOCASE; '''Comments as string or None''' id = index if index_is_id else self.id(index) return self.conn.get('SELECT text FROM comments WHERE book=?', (id,), all=False) - + def formats(self, index, index_is_id=False): ''' Return available formats as a comma separated list ''' id = index if index_is_id else self.id(index) @@ -1041,11 +1041,11 @@ ALTER TABLE books ADD COLUMN isbn TEXT DEFAULT "" COLLATE NOCASE; def all_series(self): return [ (i[0], i[1]) for i in \ self.conn.get('SELECT id, name FROM series')] - + def all_authors(self): return [ (i[0], i[1]) for i in \ self.conn.get('SELECT id, name FROM authors')] - + def all_publishers(self): return [ (i[0], i[1]) for i in \ self.conn.get('SELECT id, name FROM publishers')] @@ -1278,9 +1278,9 @@ ALTER TABLE books ADD COLUMN isbn TEXT DEFAULT "" COLLATE NOCASE; self.set_series(id, mi.series) if mi.cover_data[1] is not None: self.set_cover(id, mi.cover_data[1]) - - - + + + def add_books(self, paths, formats, metadata, uris=[], add_duplicates=True): ''' @@ -1385,16 +1385,16 @@ ALTER TABLE books ADD COLUMN isbn TEXT DEFAULT "" COLLATE NOCASE; def all_ids(self): return [i[0] for i in self.conn.get('SELECT id FROM books')] - - + + def has_id(self, id): return self.conn.get('SELECT id FROM books where id=?', (id,), all=False) is not None - - + + class SearchToken(object): @@ -1455,4 +1455,4 @@ def text_to_tokens(text): if __name__ == '__main__': sqlite.enable_callback_tracebacks(True) - db = LibraryDatabase('/home/kovid/temp/library1.db.orig') \ No newline at end of file + db = LibraryDatabase('/home/kovid/temp/library1.db.orig') diff --git a/src/calibre/library/database2.py b/src/calibre/library/database2.py index 9135182258..ec1b68f2c6 100644 --- a/src/calibre/library/database2.py +++ b/src/calibre/library/database2.py @@ -50,7 +50,8 @@ copyfile = os.link if hasattr(os, 'link') else shutil.copyfile FIELD_MAP = {'id':0, 'title':1, 'authors':2, 'publisher':3, 'rating':4, 'timestamp':5, 'size':6, 'tags':7, 'comments':8, 'series':9, 'series_index':10, - 'sort':11, 'author_sort':12, 'formats':13, 'isbn':14, 'path':15} + 'sort':11, 'author_sort':12, 'formats':13, 'isbn':14, 'path':15, + 'lccn':16, 'pubdate':17, 'flags':18} INDEX_MAP = dict(zip(FIELD_MAP.values(), FIELD_MAP.keys())) @@ -472,6 +473,53 @@ class LibraryDatabase2(LibraryDatabase): FROM books; ''') + def upgrade_version_4(self): + 'Rationalize books table' + self.conn.executescript(''' + BEGIN TRANSACTION; + CREATE TEMPORARY TABLE + books_backup(id,title,sort,timestamp,series_index,author_sort,isbn,path); + INSERT INTO books_backup SELECT id,title,sort,timestamp,series_index,author_sort,isbn,path FROM books; + DROP TABLE books; + CREATE TABLE books ( id INTEGER PRIMARY KEY AUTOINCREMENT, + title TEXT NOT NULL DEFAULT 'Unknown' COLLATE NOCASE, + sort TEXT COLLATE NOCASE, + timestamp TIMESTAMP DEFAULT CURRENT_TIMESTAMP, + pubdate TIMESTAMP DEFAULT CURRENT_TIMESTAMP, + series_index REAL NOT NULL DEFAULT 1.0, + author_sort TEXT COLLATE NOCASE, + isbn TEXT DEFAULT "" COLLATE NOCASE, + lccn TEXT DEFAULT "" COLLATE NOCASE, + path TEXT NOT NULL DEFAULT "", + flags INTEGER NOT NULL DEFAULT 1 + ); + INSERT INTO + books (id,title,sort,timestamp,pubdate,series_index,author_sort,isbn,path) + SELECT id,title,sort,timestamp,timestamp,series_index,author_sort,isbn,path FROM books_backup; + DROP TABLE books_backup; + + DROP VIEW meta; + CREATE VIEW meta AS + SELECT id, title, + (SELECT concat(name) FROM authors WHERE authors.id IN (SELECT author from books_authors_link WHERE book=books.id)) authors, + (SELECT name FROM publishers WHERE publishers.id IN (SELECT publisher from books_publishers_link WHERE book=books.id)) publisher, + (SELECT rating FROM ratings WHERE ratings.id IN (SELECT rating from books_ratings_link WHERE book=books.id)) rating, + timestamp, + (SELECT MAX(uncompressed_size) FROM data WHERE book=books.id) size, + (SELECT concat(name) FROM tags WHERE tags.id IN (SELECT tag from books_tags_link WHERE book=books.id)) tags, + (SELECT text FROM comments WHERE book=books.id) comments, + (SELECT name FROM series WHERE series.id IN (SELECT series FROM books_series_link WHERE book=books.id)) series, + series_index, + sort, + author_sort, + (SELECT concat(format) FROM data WHERE data.book=books.id) formats, + isbn, + path, + lccn, + pubdate, + flags + FROM books; + ''') def last_modified(self): ''' Return last modified time as a UTC datetime object''' @@ -610,6 +658,16 @@ class LibraryDatabase2(LibraryDatabase): return img return f if as_file else f.read() + def timestamp(self, index, index_is_id=False): + if index_is_id: + return self.conn.get('SELECT timestamp FROM meta WHERE id=?', (index,), all=False) + return self.data[index][FIELD_MAP['timestamp']] + + def pubdate(self, index, index_is_id=False): + if index_is_id: + return self.conn.get('SELECT pubdate FROM meta WHERE id=?', (index,), all=False) + return self.data[index][FIELD_MAP['pubdate']] + def get_metadata(self, idx, index_is_id=False, get_cover=False): ''' Convenience method to return metadata as a L{MetaInformation} object. @@ -621,6 +679,7 @@ class LibraryDatabase2(LibraryDatabase): mi.comments = self.comments(idx, index_is_id=index_is_id) mi.publisher = self.publisher(idx, index_is_id=index_is_id) mi.timestamp = self.timestamp(idx, index_is_id=index_is_id) + mi.pubdate = self.pubdate(idx, index_is_id=index_is_id) tags = self.tags(idx, index_is_id=index_is_id) if tags: mi.tags = [i.strip() for i in tags.split(',')] @@ -917,7 +976,7 @@ class LibraryDatabase2(LibraryDatabase): self.set_comment(id, mi.comments, notify=False) if mi.isbn and mi.isbn.strip(): self.set_isbn(id, mi.isbn, notify=False) - if mi.series_index and mi.series_index > 0: + if mi.series_index: self.set_series_index(id, mi.series_index, notify=False) if getattr(mi, 'timestamp', None) is not None: self.set_timestamp(id, mi.timestamp, notify=False) @@ -983,6 +1042,15 @@ class LibraryDatabase2(LibraryDatabase): if notify: self.notify('metadata', [id]) + def set_pubdate(self, id, dt, notify=True): + if dt: + self.conn.execute('UPDATE books SET pubdate=? WHERE id=?', (dt, id)) + self.data.set(id, FIELD_MAP['pubdate'], dt, row_is_id=True) + self.conn.commit() + if notify: + self.notify('metadata', [id]) + + def set_publisher(self, id, publisher, notify=True): self.conn.execute('DELETE FROM books_publishers_link WHERE book=?',(id,)) self.conn.execute('DELETE FROM publishers WHERE (SELECT COUNT(id) FROM books_publishers_link WHERE publisher=publishers.id) < 1') @@ -1103,17 +1171,11 @@ class LibraryDatabase2(LibraryDatabase): def set_series_index(self, id, idx, notify=True): if idx is None: - idx = 1 - idx = int(idx) - self.conn.execute('UPDATE books SET series_index=? WHERE id=?', (int(idx), id)) + idx = 1.0 + idx = float(idx) + self.conn.execute('UPDATE books SET series_index=? WHERE id=?', (idx, id)) self.conn.commit() - try: - row = self.row(id) - if row is not None: - self.data.set(row, 10, idx) - except ValueError: - pass - self.data.set(id, FIELD_MAP['series_index'], int(idx), row_is_id=True) + self.data.set(id, FIELD_MAP['series_index'], idx, row_is_id=True) if notify: self.notify('metadata', [id]) @@ -1156,7 +1218,7 @@ class LibraryDatabase2(LibraryDatabase): stream.seek(0) mi = get_metadata(stream, format, use_libprs_metadata=False) stream.seek(0) - mi.series_index = 1 + mi.series_index = 1.0 mi.tags = [_('News'), recipe.title] obj = self.conn.execute('INSERT INTO books(title, author_sort) VALUES (?, ?)', (mi.title, mi.authors[0])) @@ -1188,7 +1250,7 @@ class LibraryDatabase2(LibraryDatabase): def create_book_entry(self, mi, cover=None, add_duplicates=True): if not add_duplicates and self.has_book(mi): return None - series_index = 1 if mi.series_index is None else mi.series_index + series_index = 1.0 if mi.series_index is None else mi.series_index aus = mi.author_sort if mi.author_sort else ', '.join(mi.authors) title = mi.title if isinstance(aus, str): @@ -1207,33 +1269,29 @@ class LibraryDatabase2(LibraryDatabase): return id - def add_books(self, paths, formats, metadata, uris=[], add_duplicates=True): + def add_books(self, paths, formats, metadata, add_duplicates=True): ''' Add a book to the database. The result cache is not updated. :param:`paths` List of paths to book files or file-like objects ''' - formats, metadata, uris = iter(formats), iter(metadata), iter(uris) + formats, metadata = iter(formats), iter(metadata) duplicates = [] ids = [] for path in paths: mi = metadata.next() format = formats.next() - try: - uri = uris.next() - except StopIteration: - uri = None if not add_duplicates and self.has_book(mi): - duplicates.append((path, format, mi, uri)) + duplicates.append((path, format, mi)) continue - series_index = 1 if mi.series_index is None else mi.series_index + series_index = 1.0 if mi.series_index is None else mi.series_index aus = mi.author_sort if mi.author_sort else ', '.join(mi.authors) title = mi.title if isinstance(aus, str): aus = aus.decode(preferred_encoding, 'replace') if isinstance(title, str): title = title.decode(preferred_encoding) - obj = self.conn.execute('INSERT INTO books(title, uri, series_index, author_sort) VALUES (?, ?, ?, ?)', - (title, uri, series_index, aus)) + obj = self.conn.execute('INSERT INTO books(title, series_index, author_sort) VALUES (?, ?, ?)', + (title, series_index, aus)) id = obj.lastrowid self.data.books_added([id], self.conn) ids.append(id) @@ -1251,12 +1309,11 @@ class LibraryDatabase2(LibraryDatabase): paths = list(duplicate[0] for duplicate in duplicates) formats = list(duplicate[1] for duplicate in duplicates) metadata = list(duplicate[2] for duplicate in duplicates) - uris = list(duplicate[3] for duplicate in duplicates) - return (paths, formats, metadata, uris), len(ids) + return (paths, formats, metadata), len(ids) return None, len(ids) def import_book(self, mi, formats, notify=True): - series_index = 1 if mi.series_index is None else mi.series_index + series_index = 1.0 if mi.series_index is None else mi.series_index if not mi.title: mi.title = _('Unknown') if not mi.authors: @@ -1266,8 +1323,8 @@ class LibraryDatabase2(LibraryDatabase): aus = aus.decode(preferred_encoding, 'replace') title = mi.title if isinstance(mi.title, unicode) else \ mi.title.decode(preferred_encoding, 'replace') - obj = self.conn.execute('INSERT INTO books(title, uri, series_index, author_sort) VALUES (?, ?, ?, ?)', - (title, None, series_index, aus)) + obj = self.conn.execute('INSERT INTO books(title, series_index, author_sort) VALUES (?, ?, ?)', + (title, series_index, aus)) id = obj.lastrowid self.data.books_added([id], self.conn) self.set_path(id, True) @@ -1282,21 +1339,12 @@ class LibraryDatabase2(LibraryDatabase): if notify: self.notify('add', [id]) - def move_library_to(self, newloc, progress=None): - header = _(u'

Copying books to %s

')%newloc + def move_library_to(self, newloc, progress=lambda x: x): books = self.conn.get('SELECT id, path, title FROM books') - if progress is not None: - progress.setValue(0) - progress.setLabelText(header) - QCoreApplication.processEvents() - progress.setAutoReset(False) - progress.setRange(0, len(books)) if not os.path.exists(newloc): os.makedirs(newloc) old_dirs = set([]) for i, book in enumerate(books): - if progress is not None: - progress.setLabelText(header+_(u'Copying %s')%book[2]) path = book[1] if not path: continue @@ -1308,8 +1356,7 @@ class LibraryDatabase2(LibraryDatabase): if os.path.exists(srcdir): shutil.copytree(srcdir, tdir) old_dirs.add(srcdir) - if progress is not None: - progress.setValue(i+1) + progress(book[2]) dbpath = os.path.join(newloc, os.path.basename(self.dbpath)) shutil.copyfile(self.dbpath, dbpath) @@ -1323,10 +1370,6 @@ class LibraryDatabase2(LibraryDatabase): shutil.rmtree(dir) except: pass - if progress is not None: - progress.reset() - progress.hide() - def __iter__(self): for record in self.data._data: @@ -1357,6 +1400,8 @@ class LibraryDatabase2(LibraryDatabase): data.append(x) x['id'] = record[FIELD_MAP['id']] x['formats'] = [] + if not x['authors']: + x['authors'] = _('Unknown') x['authors'] = [i.replace('|', ',') for i in x['authors'].split(',')] if authors_as_string: x['authors'] = authors_to_string(x['authors']) @@ -1382,12 +1427,12 @@ class LibraryDatabase2(LibraryDatabase): QCoreApplication.processEvents() db.conn.row_factory = lambda cursor, row : tuple(row) db.conn.text_factory = lambda x : unicode(x, 'utf-8', 'replace') - books = db.conn.get('SELECT id, title, sort, timestamp, uri, series_index, author_sort, isbn FROM books ORDER BY id ASC') + books = db.conn.get('SELECT id, title, sort, timestamp, series_index, author_sort, isbn FROM books ORDER BY id ASC') progress.setAutoReset(False) progress.setRange(0, len(books)) for book in books: - self.conn.execute('INSERT INTO books(id, title, sort, timestamp, uri, series_index, author_sort, isbn) VALUES(?, ?, ?, ?, ?, ?, ?, ?);', book) + self.conn.execute('INSERT INTO books(id, title, sort, timestamp, series_index, author_sort, isbn) VALUES(?, ?, ?, ?, ?, ?, ?, ?);', book) tables = ''' authors ratings tags series books_tags_link diff --git a/src/calibre/library/move.py b/src/calibre/library/move.py new file mode 100644 index 0000000000..d162d962fe --- /dev/null +++ b/src/calibre/library/move.py @@ -0,0 +1,63 @@ +#!/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 ' +__docformat__ = 'restructuredtext en' + +import time, os +from threading import Thread +from Queue import Empty + +from calibre.library.database2 import LibraryDatabase2 +from calibre.utils.ipc.server import Server +from calibre.utils.ipc.job import ParallelJob + + +def move_library(from_, to, notification = lambda x:x): + time.sleep(1) + old = LibraryDatabase2(from_) + old.move_library_to(to, notification) + return True + +class MoveLibrary(Thread): + + def __init__(self, from_, to, count, result_queue): + Thread.__init__(self) + self.total = count + self.result_queue = result_queue + self.from_ = from_ + self.to = to + self.count = 0 + self.failed = False + self.details = None + + def run(self): + job = ParallelJob('move_library', + 'Move library from %s to %s'%(self.from_, self.to), + lambda x,y:x, + args=[self.from_, self.to]) + server = Server(pool_size=1) + server.add_job(job) + + while not job.is_finished: + time.sleep(0.2) + job.update(consume_notifications=False) + while True: + try: + title = job.notifications.get_nowait()[0] + self.count += 1 + self.result_queue.put((float(self.count)/self.total, title)) + except Empty: + break + + job.update() + server.close() + if not job.result: + self.failed = True + self.details = job.details + + if os.path.exists(job.log_path): + os.remove(job.log_path) + diff --git a/src/calibre/library/server.py b/src/calibre/library/server.py index 26788bcf6d..3a4b0131cf 100644 --- a/src/calibre/library/server.py +++ b/src/calibre/library/server.py @@ -25,6 +25,7 @@ from calibre.library.database2 import LibraryDatabase2, FIELD_MAP from calibre.utils.config import config_dir from calibre.utils.mdns import publish as publish_zeroconf, \ stop_server as stop_zeroconf +from calibre.ebooks.metadata import fmt_sidx build_time = datetime.strptime(build_time, '%d %m %Y %H%M%S') server_resources['jquery.js'] = jquery @@ -271,7 +272,7 @@ class LibraryServer(object): @expose def stanza(self): - ' Feeds to read calibre books on a ipod with stanza.' + 'Feeds to read calibre books on a ipod with stanza.' books = [] for record in iter(self.db): r = record[FIELD_MAP['formats']] @@ -289,8 +290,8 @@ class LibraryServer(object): extra.append('TAGS: %s
'%', '.join(tags.split(','))) series = record[FIELD_MAP['series']] if series: - extra.append('SERIES: %s [%d]
'%(series, - record[FIELD_MAP['series_index']])) + extra.append('SERIES: %s [%s]
'%(series, + fmt_sidx(record[FIELD_MAP['series_index']]))) fmt = 'epub' if 'EPUB' in r else 'pdb' mimetype = guess_type('dummy.'+fmt)[0] books.append(self.STANZA_ENTRY.generate( @@ -339,6 +340,7 @@ class LibraryServer(object): for record in items[start:start+num]: aus = record[2] if record[2] else __builtins__._('Unknown') authors = '|'.join([i.replace('|', ',') for i in aus.split(',')]) + r[10] = fmt_sidx(r[10]) books.append(book.generate(r=record, authors=authors).render('xml').decode('utf-8')) updated = self.db.last_modified() diff --git a/src/calibre/library/static/calibre.png b/src/calibre/library/static/calibre.png index f42e4926ca..871a5e31a8 100644 Binary files a/src/calibre/library/static/calibre.png and b/src/calibre/library/static/calibre.png differ diff --git a/src/calibre/linux.py b/src/calibre/linux.py index 9582651ca0..ffa6dd6b16 100644 --- a/src/calibre/linux.py +++ b/src/calibre/linux.py @@ -20,7 +20,6 @@ entry_points = { 'ebook-convert = calibre.ebooks.conversion.cli:main', 'markdown-calibre = calibre.ebooks.markdown.markdown:main', 'web2disk = calibre.web.fetch.simple:main', - 'feeds2disk = calibre.web.feeds.main:main', 'calibre-server = calibre.library.server:main', 'lrf2lrs = calibre.ebooks.lrf.lrfparser:main', 'lrs2lrf = calibre.ebooks.lrf.lrs.convert_from:main', diff --git a/src/calibre/trac/plugins/download.py b/src/calibre/trac/plugins/download.py index dd25279071..72105cd68f 100644 --- a/src/calibre/trac/plugins/download.py +++ b/src/calibre/trac/plugins/download.py @@ -9,8 +9,8 @@ DEPENDENCIES = [ ('setuptools', '0.6c5', 'setuptools', 'python-setuptools', 'python-setuptools-devel'), ('Python Imaging Library', '1.1.6', 'imaging', 'python-imaging', 'python-imaging'), ('libusb', '0.1.12', None, None, None), - ('Qt', '4.4.0', 'qt', 'libqt4-core libqt4-gui', 'qt4'), - ('PyQt', '4.4.2', 'PyQt4', 'python-qt4', 'PyQt4'), + ('Qt', '4.5.0', 'qt', 'libqt4-core libqt4-gui', 'qt4'), + ('PyQt', '4.5.0', 'PyQt4', 'python-qt4', 'PyQt4'), ('python-mechanize', '0.1.11', 'dev-python/mechanize', 'python-mechanize', 'python-mechanize'), ('ImageMagick', '6.3.5', 'imagemagick', 'imagemagick', 'ImageMagick'), ('xdg-utils', '1.0.2', 'xdg-utils', 'xdg-utils', 'xdg-utils'), @@ -39,6 +39,7 @@ def get_linux_data(version='1.0.0'): ('debian', 'Debian Sid'), ('exherbo', 'Exherbo'), ('foresight', 'Foresight 2.1'), + ('gentoo', 'Gentoo'), ('ubuntu', 'Ubuntu Jaunty Jackalope'), ('linux_mint', 'Linux Mint Gloria'), ]: diff --git a/src/calibre/trac/plugins/htdocs/images/gentoo_logo.png b/src/calibre/trac/plugins/htdocs/images/gentoo_logo.png index b0892849a9..d5b4564e35 100644 Binary files a/src/calibre/trac/plugins/htdocs/images/gentoo_logo.png and b/src/calibre/trac/plugins/htdocs/images/gentoo_logo.png differ diff --git a/src/calibre/translations/ar.po b/src/calibre/translations/ar.po index d83f3e0c8c..cbaff2a888 100644 --- a/src/calibre/translations/ar.po +++ b/src/calibre/translations/ar.po @@ -7,14 +7,14 @@ msgid "" msgstr "" "Project-Id-Version: calibre\n" "Report-Msgid-Bugs-To: FULL NAME \n" -"POT-Creation-Date: 2009-05-16 06:38+0000\n" +"POT-Creation-Date: 2009-05-21 15:37+0000\n" "PO-Revision-Date: 2009-05-16 07:10+0000\n" "Last-Translator: Kovid Goyal \n" "Language-Team: Arabic \n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" -"X-Launchpad-Export-Date: 2009-05-21 14:41+0000\n" +"X-Launchpad-Export-Date: 2009-05-29 16:28+0000\n" "X-Generator: Launchpad (build Unknown)\n" #: /home/kovid/work/calibre/src/calibre/customize/__init__.py:41 @@ -63,7 +63,7 @@ msgstr "لا يفعل شيءً" #: /home/kovid/work/calibre/src/calibre/ebooks/mobi/reader.py:61 #: /home/kovid/work/calibre/src/calibre/ebooks/mobi/reader.py:70 #: /home/kovid/work/calibre/src/calibre/ebooks/mobi/reader.py:140 -#: /home/kovid/work/calibre/src/calibre/ebooks/mobi/reader.py:661 +#: /home/kovid/work/calibre/src/calibre/ebooks/mobi/reader.py:662 #: /home/kovid/work/calibre/src/calibre/ebooks/odt/to_oeb.py:46 #: /home/kovid/work/calibre/src/calibre/ebooks/oeb/base.py:576 #: /home/kovid/work/calibre/src/calibre/ebooks/oeb/base.py:581 @@ -94,15 +94,15 @@ msgstr "لا يفعل شيءً" #: /home/kovid/work/calibre/src/calibre/gui2/tools.py:123 #: /home/kovid/work/calibre/src/calibre/library/cli.py:264 #: /home/kovid/work/calibre/src/calibre/library/database.py:916 -#: /home/kovid/work/calibre/src/calibre/library/database2.py:498 -#: /home/kovid/work/calibre/src/calibre/library/database2.py:510 -#: /home/kovid/work/calibre/src/calibre/library/database2.py:895 -#: /home/kovid/work/calibre/src/calibre/library/database2.py:930 -#: /home/kovid/work/calibre/src/calibre/library/database2.py:1237 +#: /home/kovid/work/calibre/src/calibre/library/database2.py:500 +#: /home/kovid/work/calibre/src/calibre/library/database2.py:512 +#: /home/kovid/work/calibre/src/calibre/library/database2.py:897 +#: /home/kovid/work/calibre/src/calibre/library/database2.py:932 #: /home/kovid/work/calibre/src/calibre/library/database2.py:1239 -#: /home/kovid/work/calibre/src/calibre/library/database2.py:1419 -#: /home/kovid/work/calibre/src/calibre/library/database2.py:1442 -#: /home/kovid/work/calibre/src/calibre/library/database2.py:1493 +#: /home/kovid/work/calibre/src/calibre/library/database2.py:1241 +#: /home/kovid/work/calibre/src/calibre/library/database2.py:1421 +#: /home/kovid/work/calibre/src/calibre/library/database2.py:1444 +#: /home/kovid/work/calibre/src/calibre/library/database2.py:1495 #: /home/kovid/work/calibre/src/calibre/library/server.py:340 #: /home/kovid/work/calibre/src/calibre/utils/podofo/__init__.py:28 #: /home/kovid/work/calibre/src/calibre/utils/podofo/__init__.py:31 @@ -640,7 +640,7 @@ msgid "%prog [options] LITFILE" msgstr "" #: /home/kovid/work/calibre/src/calibre/ebooks/lit/reader.py:895 -#: /home/kovid/work/calibre/src/calibre/ebooks/mobi/reader.py:696 +#: /home/kovid/work/calibre/src/calibre/ebooks/mobi/reader.py:697 msgid "Output directory. Defaults to current directory." msgstr "دليل الخرج. الإفتراضي هو الدليل الحالي." @@ -655,7 +655,7 @@ msgid "Useful for debugging." msgstr "" #: /home/kovid/work/calibre/src/calibre/ebooks/lit/reader.py:912 -#: /home/kovid/work/calibre/src/calibre/ebooks/mobi/reader.py:720 +#: /home/kovid/work/calibre/src/calibre/ebooks/mobi/reader.py:721 msgid "OEB ebook created in" msgstr "تم إنشاء كتاب OEB في" @@ -1646,11 +1646,11 @@ msgstr "الاستخدام: rb-meta file.rb" msgid "Creating Mobipocket file from EPUB..." msgstr "إنشاء ملف Mobipocket من EPUB..." -#: /home/kovid/work/calibre/src/calibre/ebooks/mobi/reader.py:694 +#: /home/kovid/work/calibre/src/calibre/ebooks/mobi/reader.py:695 msgid "%prog [options] myebook.mobi" msgstr "" -#: /home/kovid/work/calibre/src/calibre/ebooks/mobi/reader.py:718 +#: /home/kovid/work/calibre/src/calibre/ebooks/mobi/reader.py:719 msgid "Raw MOBI HTML saved in" msgstr "" @@ -3826,9 +3826,9 @@ msgstr "" #: /home/kovid/work/calibre/src/calibre/gui2/dialogs/scheduler.py:466 #: /home/kovid/work/calibre/src/calibre/gui2/tags.py:50 -#: /home/kovid/work/calibre/src/calibre/library/database2.py:839 -#: /home/kovid/work/calibre/src/calibre/library/database2.py:843 -#: /home/kovid/work/calibre/src/calibre/library/database2.py:1158 +#: /home/kovid/work/calibre/src/calibre/library/database2.py:841 +#: /home/kovid/work/calibre/src/calibre/library/database2.py:845 +#: /home/kovid/work/calibre/src/calibre/library/database2.py:1160 msgid "News" msgstr "الأخبار" @@ -5661,20 +5661,20 @@ msgid "" "For help on an individual command: %%prog command --help\n" msgstr "" -#: /home/kovid/work/calibre/src/calibre/library/database2.py:1262 +#: /home/kovid/work/calibre/src/calibre/library/database2.py:1264 msgid "

Copying books to %s

" msgstr "" -#: /home/kovid/work/calibre/src/calibre/library/database2.py:1275 -#: /home/kovid/work/calibre/src/calibre/library/database2.py:1384 +#: /home/kovid/work/calibre/src/calibre/library/database2.py:1277 +#: /home/kovid/work/calibre/src/calibre/library/database2.py:1386 msgid "Copying %s" msgstr "" -#: /home/kovid/work/calibre/src/calibre/library/database2.py:1355 +#: /home/kovid/work/calibre/src/calibre/library/database2.py:1357 msgid "

Migrating old database to ebook library in %s

" msgstr "" -#: /home/kovid/work/calibre/src/calibre/library/database2.py:1401 +#: /home/kovid/work/calibre/src/calibre/library/database2.py:1403 msgid "Compacting database" msgstr "" @@ -6118,6 +6118,7 @@ msgstr "الصربي" #: /home/kovid/work/calibre/src/calibre/web/feeds/recipes/recipe_seattle_times.py:22 #: /home/kovid/work/calibre/src/calibre/web/feeds/recipes/recipe_security_watch.py:15 #: /home/kovid/work/calibre/src/calibre/web/feeds/recipes/recipe_shacknews.py:10 +#: /home/kovid/work/calibre/src/calibre/web/feeds/recipes/recipe_slashdot.py:15 #: /home/kovid/work/calibre/src/calibre/web/feeds/recipes/recipe_smh.py:19 #: /home/kovid/work/calibre/src/calibre/web/feeds/recipes/recipe_soldiers.py:26 #: /home/kovid/work/calibre/src/calibre/web/feeds/recipes/recipe_spiegel_int.py:17 @@ -6134,7 +6135,7 @@ msgstr "الصربي" #: /home/kovid/work/calibre/src/calibre/web/feeds/recipes/recipe_themarketticker.py:17 #: /home/kovid/work/calibre/src/calibre/web/feeds/recipes/recipe_theonion.py:20 #: /home/kovid/work/calibre/src/calibre/web/feeds/recipes/recipe_time_magazine.py:19 -#: /home/kovid/work/calibre/src/calibre/web/feeds/recipes/recipe_times_online.py:19 +#: /home/kovid/work/calibre/src/calibre/web/feeds/recipes/recipe_times_online.py:25 #: /home/kovid/work/calibre/src/calibre/web/feeds/recipes/recipe_tomshardware.py:21 #: /home/kovid/work/calibre/src/calibre/web/feeds/recipes/recipe_twitchfilms.py:22 #: /home/kovid/work/calibre/src/calibre/web/feeds/recipes/recipe_upi.py:15 @@ -6218,15 +6219,18 @@ msgstr "" msgid "Portugese" msgstr "البرتغالي" -#: /home/kovid/work/calibre/src/calibre/web/feeds/recipes/recipe_hindu.py:12 -msgid "Kovid Goyal" -msgstr "Kovid Goyal" - +#: /home/kovid/work/calibre/src/calibre/web/feeds/recipes/recipe_h1.py:15 +#: /home/kovid/work/calibre/src/calibre/web/feeds/recipes/recipe_h2.py:15 +#: /home/kovid/work/calibre/src/calibre/web/feeds/recipes/recipe_h3.py:15 #: /home/kovid/work/calibre/src/calibre/web/feeds/recipes/recipe_index_hu.py:8 #: /home/kovid/work/calibre/src/calibre/web/feeds/recipes/recipe_pcworld_hu.py:17 msgid "Hungarian" msgstr "" +#: /home/kovid/work/calibre/src/calibre/web/feeds/recipes/recipe_hindu.py:12 +msgid "Kovid Goyal" +msgstr "Kovid Goyal" + #: /home/kovid/work/calibre/src/calibre/web/feeds/recipes/recipe_le_monde.py:83 msgid "Skipping duplicated article: %s" msgstr "" diff --git a/src/calibre/translations/bg.po b/src/calibre/translations/bg.po index b8144e9a38..64aae97c30 100644 --- a/src/calibre/translations/bg.po +++ b/src/calibre/translations/bg.po @@ -6,14 +6,14 @@ msgid "" msgstr "" "Project-Id-Version: calibre 0.4.51\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2009-05-16 06:38+0000\n" +"POT-Creation-Date: 2009-05-21 15:37+0000\n" "PO-Revision-Date: 2008-05-24 06:23+0000\n" "Last-Translator: Kovid Goyal \n" "Language-Team: bg\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" -"X-Launchpad-Export-Date: 2009-05-21 14:40+0000\n" +"X-Launchpad-Export-Date: 2009-05-29 16:28+0000\n" "X-Generator: Launchpad (build Unknown)\n" "Generated-By: pygettext.py 1.5\n" @@ -63,7 +63,7 @@ msgstr "" #: /home/kovid/work/calibre/src/calibre/ebooks/mobi/reader.py:61 #: /home/kovid/work/calibre/src/calibre/ebooks/mobi/reader.py:70 #: /home/kovid/work/calibre/src/calibre/ebooks/mobi/reader.py:140 -#: /home/kovid/work/calibre/src/calibre/ebooks/mobi/reader.py:661 +#: /home/kovid/work/calibre/src/calibre/ebooks/mobi/reader.py:662 #: /home/kovid/work/calibre/src/calibre/ebooks/odt/to_oeb.py:46 #: /home/kovid/work/calibre/src/calibre/ebooks/oeb/base.py:576 #: /home/kovid/work/calibre/src/calibre/ebooks/oeb/base.py:581 @@ -94,15 +94,15 @@ msgstr "" #: /home/kovid/work/calibre/src/calibre/gui2/tools.py:123 #: /home/kovid/work/calibre/src/calibre/library/cli.py:264 #: /home/kovid/work/calibre/src/calibre/library/database.py:916 -#: /home/kovid/work/calibre/src/calibre/library/database2.py:498 -#: /home/kovid/work/calibre/src/calibre/library/database2.py:510 -#: /home/kovid/work/calibre/src/calibre/library/database2.py:895 -#: /home/kovid/work/calibre/src/calibre/library/database2.py:930 -#: /home/kovid/work/calibre/src/calibre/library/database2.py:1237 +#: /home/kovid/work/calibre/src/calibre/library/database2.py:500 +#: /home/kovid/work/calibre/src/calibre/library/database2.py:512 +#: /home/kovid/work/calibre/src/calibre/library/database2.py:897 +#: /home/kovid/work/calibre/src/calibre/library/database2.py:932 #: /home/kovid/work/calibre/src/calibre/library/database2.py:1239 -#: /home/kovid/work/calibre/src/calibre/library/database2.py:1419 -#: /home/kovid/work/calibre/src/calibre/library/database2.py:1442 -#: /home/kovid/work/calibre/src/calibre/library/database2.py:1493 +#: /home/kovid/work/calibre/src/calibre/library/database2.py:1241 +#: /home/kovid/work/calibre/src/calibre/library/database2.py:1421 +#: /home/kovid/work/calibre/src/calibre/library/database2.py:1444 +#: /home/kovid/work/calibre/src/calibre/library/database2.py:1495 #: /home/kovid/work/calibre/src/calibre/library/server.py:340 #: /home/kovid/work/calibre/src/calibre/utils/podofo/__init__.py:28 #: /home/kovid/work/calibre/src/calibre/utils/podofo/__init__.py:31 @@ -621,7 +621,7 @@ msgid "%prog [options] LITFILE" msgstr "" #: /home/kovid/work/calibre/src/calibre/ebooks/lit/reader.py:895 -#: /home/kovid/work/calibre/src/calibre/ebooks/mobi/reader.py:696 +#: /home/kovid/work/calibre/src/calibre/ebooks/mobi/reader.py:697 msgid "Output directory. Defaults to current directory." msgstr "" @@ -636,7 +636,7 @@ msgid "Useful for debugging." msgstr "" #: /home/kovid/work/calibre/src/calibre/ebooks/lit/reader.py:912 -#: /home/kovid/work/calibre/src/calibre/ebooks/mobi/reader.py:720 +#: /home/kovid/work/calibre/src/calibre/ebooks/mobi/reader.py:721 msgid "OEB ebook created in" msgstr "" @@ -1592,11 +1592,11 @@ msgstr "" msgid "Creating Mobipocket file from EPUB..." msgstr "" -#: /home/kovid/work/calibre/src/calibre/ebooks/mobi/reader.py:694 +#: /home/kovid/work/calibre/src/calibre/ebooks/mobi/reader.py:695 msgid "%prog [options] myebook.mobi" msgstr "" -#: /home/kovid/work/calibre/src/calibre/ebooks/mobi/reader.py:718 +#: /home/kovid/work/calibre/src/calibre/ebooks/mobi/reader.py:719 msgid "Raw MOBI HTML saved in" msgstr "" @@ -3756,9 +3756,9 @@ msgstr "" #: /home/kovid/work/calibre/src/calibre/gui2/dialogs/scheduler.py:466 #: /home/kovid/work/calibre/src/calibre/gui2/tags.py:50 -#: /home/kovid/work/calibre/src/calibre/library/database2.py:839 -#: /home/kovid/work/calibre/src/calibre/library/database2.py:843 -#: /home/kovid/work/calibre/src/calibre/library/database2.py:1158 +#: /home/kovid/work/calibre/src/calibre/library/database2.py:841 +#: /home/kovid/work/calibre/src/calibre/library/database2.py:845 +#: /home/kovid/work/calibre/src/calibre/library/database2.py:1160 msgid "News" msgstr "" @@ -5583,20 +5583,20 @@ msgid "" "For help on an individual command: %%prog command --help\n" msgstr "" -#: /home/kovid/work/calibre/src/calibre/library/database2.py:1262 +#: /home/kovid/work/calibre/src/calibre/library/database2.py:1264 msgid "

Copying books to %s

" msgstr "" -#: /home/kovid/work/calibre/src/calibre/library/database2.py:1275 -#: /home/kovid/work/calibre/src/calibre/library/database2.py:1384 +#: /home/kovid/work/calibre/src/calibre/library/database2.py:1277 +#: /home/kovid/work/calibre/src/calibre/library/database2.py:1386 msgid "Copying %s" msgstr "" -#: /home/kovid/work/calibre/src/calibre/library/database2.py:1355 +#: /home/kovid/work/calibre/src/calibre/library/database2.py:1357 msgid "

Migrating old database to ebook library in %s

" msgstr "" -#: /home/kovid/work/calibre/src/calibre/library/database2.py:1401 +#: /home/kovid/work/calibre/src/calibre/library/database2.py:1403 msgid "Compacting database" msgstr "" @@ -6037,6 +6037,7 @@ msgstr "" #: /home/kovid/work/calibre/src/calibre/web/feeds/recipes/recipe_seattle_times.py:22 #: /home/kovid/work/calibre/src/calibre/web/feeds/recipes/recipe_security_watch.py:15 #: /home/kovid/work/calibre/src/calibre/web/feeds/recipes/recipe_shacknews.py:10 +#: /home/kovid/work/calibre/src/calibre/web/feeds/recipes/recipe_slashdot.py:15 #: /home/kovid/work/calibre/src/calibre/web/feeds/recipes/recipe_smh.py:19 #: /home/kovid/work/calibre/src/calibre/web/feeds/recipes/recipe_soldiers.py:26 #: /home/kovid/work/calibre/src/calibre/web/feeds/recipes/recipe_spiegel_int.py:17 @@ -6053,7 +6054,7 @@ msgstr "" #: /home/kovid/work/calibre/src/calibre/web/feeds/recipes/recipe_themarketticker.py:17 #: /home/kovid/work/calibre/src/calibre/web/feeds/recipes/recipe_theonion.py:20 #: /home/kovid/work/calibre/src/calibre/web/feeds/recipes/recipe_time_magazine.py:19 -#: /home/kovid/work/calibre/src/calibre/web/feeds/recipes/recipe_times_online.py:19 +#: /home/kovid/work/calibre/src/calibre/web/feeds/recipes/recipe_times_online.py:25 #: /home/kovid/work/calibre/src/calibre/web/feeds/recipes/recipe_tomshardware.py:21 #: /home/kovid/work/calibre/src/calibre/web/feeds/recipes/recipe_twitchfilms.py:22 #: /home/kovid/work/calibre/src/calibre/web/feeds/recipes/recipe_upi.py:15 @@ -6137,15 +6138,18 @@ msgstr "" msgid "Portugese" msgstr "" -#: /home/kovid/work/calibre/src/calibre/web/feeds/recipes/recipe_hindu.py:12 -msgid "Kovid Goyal" -msgstr "" - +#: /home/kovid/work/calibre/src/calibre/web/feeds/recipes/recipe_h1.py:15 +#: /home/kovid/work/calibre/src/calibre/web/feeds/recipes/recipe_h2.py:15 +#: /home/kovid/work/calibre/src/calibre/web/feeds/recipes/recipe_h3.py:15 #: /home/kovid/work/calibre/src/calibre/web/feeds/recipes/recipe_index_hu.py:8 #: /home/kovid/work/calibre/src/calibre/web/feeds/recipes/recipe_pcworld_hu.py:17 msgid "Hungarian" msgstr "" +#: /home/kovid/work/calibre/src/calibre/web/feeds/recipes/recipe_hindu.py:12 +msgid "Kovid Goyal" +msgstr "" + #: /home/kovid/work/calibre/src/calibre/web/feeds/recipes/recipe_le_monde.py:83 msgid "Skipping duplicated article: %s" msgstr "" diff --git a/src/calibre/translations/ca.po b/src/calibre/translations/ca.po index b13289e483..09379383ea 100644 --- a/src/calibre/translations/ca.po +++ b/src/calibre/translations/ca.po @@ -10,14 +10,14 @@ msgid "" msgstr "" "Project-Id-Version: ca\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2009-05-16 06:38+0000\n" -"PO-Revision-Date: 2009-05-16 08:18+0000\n" +"POT-Creation-Date: 2009-05-21 15:37+0000\n" +"PO-Revision-Date: 2009-05-21 15:19+0000\n" "Last-Translator: Kovid Goyal \n" "Language-Team: \n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" -"X-Launchpad-Export-Date: 2009-05-21 14:41+0000\n" +"X-Launchpad-Export-Date: 2009-05-29 16:28+0000\n" "X-Generator: Launchpad (build Unknown)\n" #: /home/kovid/work/calibre/src/calibre/customize/__init__.py:41 @@ -66,7 +66,7 @@ msgstr "" #: /home/kovid/work/calibre/src/calibre/ebooks/mobi/reader.py:61 #: /home/kovid/work/calibre/src/calibre/ebooks/mobi/reader.py:70 #: /home/kovid/work/calibre/src/calibre/ebooks/mobi/reader.py:140 -#: /home/kovid/work/calibre/src/calibre/ebooks/mobi/reader.py:661 +#: /home/kovid/work/calibre/src/calibre/ebooks/mobi/reader.py:662 #: /home/kovid/work/calibre/src/calibre/ebooks/odt/to_oeb.py:46 #: /home/kovid/work/calibre/src/calibre/ebooks/oeb/base.py:576 #: /home/kovid/work/calibre/src/calibre/ebooks/oeb/base.py:581 @@ -97,15 +97,15 @@ msgstr "" #: /home/kovid/work/calibre/src/calibre/gui2/tools.py:123 #: /home/kovid/work/calibre/src/calibre/library/cli.py:264 #: /home/kovid/work/calibre/src/calibre/library/database.py:916 -#: /home/kovid/work/calibre/src/calibre/library/database2.py:498 -#: /home/kovid/work/calibre/src/calibre/library/database2.py:510 -#: /home/kovid/work/calibre/src/calibre/library/database2.py:895 -#: /home/kovid/work/calibre/src/calibre/library/database2.py:930 -#: /home/kovid/work/calibre/src/calibre/library/database2.py:1237 +#: /home/kovid/work/calibre/src/calibre/library/database2.py:500 +#: /home/kovid/work/calibre/src/calibre/library/database2.py:512 +#: /home/kovid/work/calibre/src/calibre/library/database2.py:897 +#: /home/kovid/work/calibre/src/calibre/library/database2.py:932 #: /home/kovid/work/calibre/src/calibre/library/database2.py:1239 -#: /home/kovid/work/calibre/src/calibre/library/database2.py:1419 -#: /home/kovid/work/calibre/src/calibre/library/database2.py:1442 -#: /home/kovid/work/calibre/src/calibre/library/database2.py:1493 +#: /home/kovid/work/calibre/src/calibre/library/database2.py:1241 +#: /home/kovid/work/calibre/src/calibre/library/database2.py:1421 +#: /home/kovid/work/calibre/src/calibre/library/database2.py:1444 +#: /home/kovid/work/calibre/src/calibre/library/database2.py:1495 #: /home/kovid/work/calibre/src/calibre/library/server.py:340 #: /home/kovid/work/calibre/src/calibre/utils/podofo/__init__.py:28 #: /home/kovid/work/calibre/src/calibre/utils/podofo/__init__.py:31 @@ -624,7 +624,7 @@ msgid "%prog [options] LITFILE" msgstr "" #: /home/kovid/work/calibre/src/calibre/ebooks/lit/reader.py:895 -#: /home/kovid/work/calibre/src/calibre/ebooks/mobi/reader.py:696 +#: /home/kovid/work/calibre/src/calibre/ebooks/mobi/reader.py:697 msgid "Output directory. Defaults to current directory." msgstr "" @@ -639,7 +639,7 @@ msgid "Useful for debugging." msgstr "" #: /home/kovid/work/calibre/src/calibre/ebooks/lit/reader.py:912 -#: /home/kovid/work/calibre/src/calibre/ebooks/mobi/reader.py:720 +#: /home/kovid/work/calibre/src/calibre/ebooks/mobi/reader.py:721 msgid "OEB ebook created in" msgstr "" @@ -1638,11 +1638,11 @@ msgstr "" msgid "Creating Mobipocket file from EPUB..." msgstr "" -#: /home/kovid/work/calibre/src/calibre/ebooks/mobi/reader.py:694 +#: /home/kovid/work/calibre/src/calibre/ebooks/mobi/reader.py:695 msgid "%prog [options] myebook.mobi" msgstr "" -#: /home/kovid/work/calibre/src/calibre/ebooks/mobi/reader.py:718 +#: /home/kovid/work/calibre/src/calibre/ebooks/mobi/reader.py:719 msgid "Raw MOBI HTML saved in" msgstr "" @@ -3817,9 +3817,9 @@ msgstr "" #: /home/kovid/work/calibre/src/calibre/gui2/dialogs/scheduler.py:466 #: /home/kovid/work/calibre/src/calibre/gui2/tags.py:50 -#: /home/kovid/work/calibre/src/calibre/library/database2.py:839 -#: /home/kovid/work/calibre/src/calibre/library/database2.py:843 -#: /home/kovid/work/calibre/src/calibre/library/database2.py:1158 +#: /home/kovid/work/calibre/src/calibre/library/database2.py:841 +#: /home/kovid/work/calibre/src/calibre/library/database2.py:845 +#: /home/kovid/work/calibre/src/calibre/library/database2.py:1160 msgid "News" msgstr "" @@ -5650,20 +5650,20 @@ msgid "" "For help on an individual command: %%prog command --help\n" msgstr "" -#: /home/kovid/work/calibre/src/calibre/library/database2.py:1262 +#: /home/kovid/work/calibre/src/calibre/library/database2.py:1264 msgid "

Copying books to %s

" msgstr "" -#: /home/kovid/work/calibre/src/calibre/library/database2.py:1275 -#: /home/kovid/work/calibre/src/calibre/library/database2.py:1384 +#: /home/kovid/work/calibre/src/calibre/library/database2.py:1277 +#: /home/kovid/work/calibre/src/calibre/library/database2.py:1386 msgid "Copying %s" msgstr "" -#: /home/kovid/work/calibre/src/calibre/library/database2.py:1355 +#: /home/kovid/work/calibre/src/calibre/library/database2.py:1357 msgid "

Migrating old database to ebook library in %s

" msgstr "" -#: /home/kovid/work/calibre/src/calibre/library/database2.py:1401 +#: /home/kovid/work/calibre/src/calibre/library/database2.py:1403 msgid "Compacting database" msgstr "" @@ -6104,6 +6104,7 @@ msgstr "" #: /home/kovid/work/calibre/src/calibre/web/feeds/recipes/recipe_seattle_times.py:22 #: /home/kovid/work/calibre/src/calibre/web/feeds/recipes/recipe_security_watch.py:15 #: /home/kovid/work/calibre/src/calibre/web/feeds/recipes/recipe_shacknews.py:10 +#: /home/kovid/work/calibre/src/calibre/web/feeds/recipes/recipe_slashdot.py:15 #: /home/kovid/work/calibre/src/calibre/web/feeds/recipes/recipe_smh.py:19 #: /home/kovid/work/calibre/src/calibre/web/feeds/recipes/recipe_soldiers.py:26 #: /home/kovid/work/calibre/src/calibre/web/feeds/recipes/recipe_spiegel_int.py:17 @@ -6120,7 +6121,7 @@ msgstr "" #: /home/kovid/work/calibre/src/calibre/web/feeds/recipes/recipe_themarketticker.py:17 #: /home/kovid/work/calibre/src/calibre/web/feeds/recipes/recipe_theonion.py:20 #: /home/kovid/work/calibre/src/calibre/web/feeds/recipes/recipe_time_magazine.py:19 -#: /home/kovid/work/calibre/src/calibre/web/feeds/recipes/recipe_times_online.py:19 +#: /home/kovid/work/calibre/src/calibre/web/feeds/recipes/recipe_times_online.py:25 #: /home/kovid/work/calibre/src/calibre/web/feeds/recipes/recipe_tomshardware.py:21 #: /home/kovid/work/calibre/src/calibre/web/feeds/recipes/recipe_twitchfilms.py:22 #: /home/kovid/work/calibre/src/calibre/web/feeds/recipes/recipe_upi.py:15 @@ -6204,15 +6205,18 @@ msgstr "" msgid "Portugese" msgstr "" -#: /home/kovid/work/calibre/src/calibre/web/feeds/recipes/recipe_hindu.py:12 -msgid "Kovid Goyal" -msgstr "" - +#: /home/kovid/work/calibre/src/calibre/web/feeds/recipes/recipe_h1.py:15 +#: /home/kovid/work/calibre/src/calibre/web/feeds/recipes/recipe_h2.py:15 +#: /home/kovid/work/calibre/src/calibre/web/feeds/recipes/recipe_h3.py:15 #: /home/kovid/work/calibre/src/calibre/web/feeds/recipes/recipe_index_hu.py:8 #: /home/kovid/work/calibre/src/calibre/web/feeds/recipes/recipe_pcworld_hu.py:17 msgid "Hungarian" msgstr "" +#: /home/kovid/work/calibre/src/calibre/web/feeds/recipes/recipe_hindu.py:12 +msgid "Kovid Goyal" +msgstr "" + #: /home/kovid/work/calibre/src/calibre/web/feeds/recipes/recipe_le_monde.py:83 msgid "Skipping duplicated article: %s" msgstr "" diff --git a/src/calibre/translations/calibre.pot b/src/calibre/translations/calibre.pot index 1d258db746..26ebc84299 100644 --- a/src/calibre/translations/calibre.pot +++ b/src/calibre/translations/calibre.pot @@ -4,9 +4,9 @@ # msgid "" msgstr "" -"Project-Id-Version: calibre 0.5.13\n" -"POT-Creation-Date: 2009-05-21 07:58+PDT\n" -"PO-Revision-Date: 2009-05-21 07:58+PDT\n" +"Project-Id-Version: calibre 0.5.14\n" +"POT-Creation-Date: 2009-05-31 09:36+PDT\n" +"PO-Revision-Date: 2009-05-31 09:36+PDT\n" "Last-Translator: Automatically generated\n" "Language-Team: LANGUAGE\n" "MIME-Version: 1.0\n" @@ -20,91 +20,111 @@ msgid "Does absolutely nothing" msgstr "" #: /home/kovid/work/calibre/src/calibre/customize/__init__.py:44 -#: /home/kovid/work/calibre/src/calibre/devices/jetbook/driver.py:98 -#: /home/kovid/work/calibre/src/calibre/devices/kindle/driver.py:50 +#: /home/kovid/work/calibre/src/calibre/devices/cybookg3/driver.py:68 +#: /home/kovid/work/calibre/src/calibre/devices/cybookg3/driver.py:69 +#: /home/kovid/work/calibre/src/calibre/devices/jetbook/driver.py:116 +#: /home/kovid/work/calibre/src/calibre/devices/kindle/driver.py:58 #: /home/kovid/work/calibre/src/calibre/devices/prs505/books.py:58 -#: /home/kovid/work/calibre/src/calibre/devices/prs505/books.py:196 -#: /home/kovid/work/calibre/src/calibre/ebooks/epub/from_any.py:71 -#: /home/kovid/work/calibre/src/calibre/ebooks/html.py:529 -#: /home/kovid/work/calibre/src/calibre/ebooks/html.py:1055 -#: /home/kovid/work/calibre/src/calibre/ebooks/html.py:1071 -#: /home/kovid/work/calibre/src/calibre/ebooks/html.py:1073 -#: /home/kovid/work/calibre/src/calibre/ebooks/lrf/__init__.py:79 -#: /home/kovid/work/calibre/src/calibre/ebooks/lrf/__init__.py:81 -#: /home/kovid/work/calibre/src/calibre/ebooks/lrf/__init__.py:83 -#: /home/kovid/work/calibre/src/calibre/ebooks/lrf/__init__.py:88 -#: /home/kovid/work/calibre/src/calibre/ebooks/lrf/comic/convert_from.py:296 -#: /home/kovid/work/calibre/src/calibre/ebooks/lrf/fb2/convert_from.py:62 -#: /home/kovid/work/calibre/src/calibre/ebooks/lrf/fb2/convert_from.py:96 -#: /home/kovid/work/calibre/src/calibre/ebooks/lrf/fb2/convert_from.py:98 -#: /home/kovid/work/calibre/src/calibre/ebooks/lrf/fb2/convert_from.py:100 -#: /home/kovid/work/calibre/src/calibre/ebooks/lrf/fb2/convert_from.py:102 -#: /home/kovid/work/calibre/src/calibre/ebooks/lrf/pdf/convert_from.py:83 -#: /home/kovid/work/calibre/src/calibre/ebooks/lrf/rtf/convert_from.py:179 -#: /home/kovid/work/calibre/src/calibre/ebooks/lrf/txt/convert_from.py:70 -#: /home/kovid/work/calibre/src/calibre/ebooks/metadata/__init__.py:199 -#: /home/kovid/work/calibre/src/calibre/ebooks/metadata/__init__.py:229 -#: /home/kovid/work/calibre/src/calibre/ebooks/metadata/__init__.py:232 -#: /home/kovid/work/calibre/src/calibre/ebooks/metadata/__init__.py:271 -#: /home/kovid/work/calibre/src/calibre/ebooks/metadata/__init__.py:301 -#: /home/kovid/work/calibre/src/calibre/ebooks/metadata/meta.py:53 -#: /home/kovid/work/calibre/src/calibre/ebooks/metadata/meta.py:55 -#: /home/kovid/work/calibre/src/calibre/ebooks/metadata/meta.py:95 -#: /home/kovid/work/calibre/src/calibre/ebooks/metadata/meta.py:97 -#: /home/kovid/work/calibre/src/calibre/ebooks/metadata/mobi.py:152 -#: /home/kovid/work/calibre/src/calibre/ebooks/metadata/opf.py:334 -#: /home/kovid/work/calibre/src/calibre/ebooks/metadata/opf.py:449 -#: /home/kovid/work/calibre/src/calibre/ebooks/metadata/opf2.py:863 -#: /home/kovid/work/calibre/src/calibre/ebooks/metadata/pdf.py:38 +#: /home/kovid/work/calibre/src/calibre/devices/prs505/books.py:199 +#: /home/kovid/work/calibre/src/calibre/devices/prs505/driver.py:163 +#: /home/kovid/work/calibre/src/calibre/devices/prs505/driver.py:164 +#: /home/kovid/work/calibre/src/calibre/devices/usbms/driver.py:145 +#: /home/kovid/work/calibre/src/calibre/devices/usbms/driver.py:146 +#: /home/kovid/work/calibre/src/calibre/ebooks/comic/input.py:404 +#: /home/kovid/work/calibre/src/calibre/ebooks/fb2/input.py:50 +#: /home/kovid/work/calibre/src/calibre/ebooks/fb2/input.py:52 +#: /home/kovid/work/calibre/src/calibre/ebooks/lrf/output.py:24 +#: /home/kovid/work/calibre/src/calibre/ebooks/metadata/__init__.py:220 +#: /home/kovid/work/calibre/src/calibre/ebooks/metadata/__init__.py:250 +#: /home/kovid/work/calibre/src/calibre/ebooks/metadata/__init__.py:253 +#: /home/kovid/work/calibre/src/calibre/ebooks/metadata/__init__.py:341 +#: /home/kovid/work/calibre/src/calibre/ebooks/metadata/ereader.py:23 +#: /home/kovid/work/calibre/src/calibre/ebooks/metadata/ereader.py:45 +#: /home/kovid/work/calibre/src/calibre/ebooks/metadata/meta.py:36 +#: /home/kovid/work/calibre/src/calibre/ebooks/metadata/meta.py:61 +#: /home/kovid/work/calibre/src/calibre/ebooks/metadata/meta.py:63 +#: /home/kovid/work/calibre/src/calibre/ebooks/metadata/meta.py:103 +#: /home/kovid/work/calibre/src/calibre/ebooks/metadata/meta.py:105 +#: /home/kovid/work/calibre/src/calibre/ebooks/metadata/mobi.py:153 +#: /home/kovid/work/calibre/src/calibre/ebooks/metadata/opf.py:333 +#: /home/kovid/work/calibre/src/calibre/ebooks/metadata/opf.py:448 +#: /home/kovid/work/calibre/src/calibre/ebooks/metadata/opf2.py:866 +#: /home/kovid/work/calibre/src/calibre/ebooks/metadata/pdb.py:39 +#: /home/kovid/work/calibre/src/calibre/ebooks/metadata/pdf.py:58 #: /home/kovid/work/calibre/src/calibre/ebooks/metadata/topaz.py:29 -#: /home/kovid/work/calibre/src/calibre/ebooks/mobi/reader.py:37 -#: /home/kovid/work/calibre/src/calibre/ebooks/mobi/reader.py:61 -#: /home/kovid/work/calibre/src/calibre/ebooks/mobi/reader.py:70 -#: /home/kovid/work/calibre/src/calibre/ebooks/mobi/reader.py:140 -#: /home/kovid/work/calibre/src/calibre/ebooks/mobi/reader.py:662 -#: /home/kovid/work/calibre/src/calibre/ebooks/odt/to_oeb.py:46 -#: /home/kovid/work/calibre/src/calibre/ebooks/oeb/base.py:576 -#: /home/kovid/work/calibre/src/calibre/ebooks/oeb/base.py:581 -#: /home/kovid/work/calibre/src/calibre/ebooks/oeb/base.py:1157 -#: /home/kovid/work/calibre/src/calibre/ebooks/oeb/base.py:1160 -#: /home/kovid/work/calibre/src/calibre/ebooks/pdf/pdftrim.py:53 -#: /home/kovid/work/calibre/src/calibre/ebooks/pdf/pdftrim.py:54 -#: /home/kovid/work/calibre/src/calibre/gui2/__init__.py:186 -#: /home/kovid/work/calibre/src/calibre/gui2/__init__.py:193 -#: /home/kovid/work/calibre/src/calibre/gui2/device.py:458 -#: /home/kovid/work/calibre/src/calibre/gui2/device.py:467 -#: /home/kovid/work/calibre/src/calibre/gui2/device.py:619 -#: /home/kovid/work/calibre/src/calibre/gui2/device.py:622 +#: /home/kovid/work/calibre/src/calibre/ebooks/metadata/txt.py:14 +#: /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:78 +#: /home/kovid/work/calibre/src/calibre/ebooks/mobi/reader.py:149 +#: /home/kovid/work/calibre/src/calibre/ebooks/mobi/reader.py:520 +#: /home/kovid/work/calibre/src/calibre/ebooks/mobi/reader.py:704 +#: /home/kovid/work/calibre/src/calibre/ebooks/odt/input.py:44 +#: /home/kovid/work/calibre/src/calibre/ebooks/odt/input.py:46 +#: /home/kovid/work/calibre/src/calibre/ebooks/oeb/base.py:791 +#: /home/kovid/work/calibre/src/calibre/ebooks/oeb/base.py:796 +#: /home/kovid/work/calibre/src/calibre/ebooks/oeb/reader.py:162 +#: /home/kovid/work/calibre/src/calibre/ebooks/oeb/reader.py:165 +#: /home/kovid/work/calibre/src/calibre/ebooks/oeb/transforms/jacket.py:82 +#: /home/kovid/work/calibre/src/calibre/ebooks/pdb/ereader/writer.py:96 +#: /home/kovid/work/calibre/src/calibre/ebooks/pdb/ereader/writer.py:97 +#: /home/kovid/work/calibre/src/calibre/ebooks/pdb/input.py:26 +#: /home/kovid/work/calibre/src/calibre/ebooks/pdb/palmdoc/writer.py:28 +#: /home/kovid/work/calibre/src/calibre/ebooks/pdb/ztxt/writer.py:26 +#: /home/kovid/work/calibre/src/calibre/ebooks/pdf/manipulate/crop.py:81 +#: /home/kovid/work/calibre/src/calibre/ebooks/pdf/manipulate/crop.py:82 +#: /home/kovid/work/calibre/src/calibre/ebooks/pdf/manipulate/decrypt.py:75 +#: /home/kovid/work/calibre/src/calibre/ebooks/pdf/manipulate/decrypt.py:76 +#: /home/kovid/work/calibre/src/calibre/ebooks/pdf/manipulate/encrypt.py:61 +#: /home/kovid/work/calibre/src/calibre/ebooks/pdf/manipulate/encrypt.py:62 +#: /home/kovid/work/calibre/src/calibre/ebooks/pdf/manipulate/info.py:52 +#: /home/kovid/work/calibre/src/calibre/ebooks/pdf/manipulate/merge.py:65 +#: /home/kovid/work/calibre/src/calibre/ebooks/pdf/manipulate/merge.py:66 +#: /home/kovid/work/calibre/src/calibre/ebooks/pdf/manipulate/reverse.py:63 +#: /home/kovid/work/calibre/src/calibre/ebooks/pdf/manipulate/reverse.py:64 +#: /home/kovid/work/calibre/src/calibre/ebooks/pdf/manipulate/rotate.py:62 +#: /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:28 +#: /home/kovid/work/calibre/src/calibre/ebooks/pdf/writer.py:29 +#: /home/kovid/work/calibre/src/calibre/ebooks/rtf/input.py:93 +#: /home/kovid/work/calibre/src/calibre/ebooks/rtf/input.py:95 +#: /home/kovid/work/calibre/src/calibre/gui2/__init__.py:197 +#: /home/kovid/work/calibre/src/calibre/gui2/__init__.py:204 +#: /home/kovid/work/calibre/src/calibre/gui2/convert/__init__.py:19 +#: /home/kovid/work/calibre/src/calibre/gui2/convert/metadata.py:69 +#: /home/kovid/work/calibre/src/calibre/gui2/convert/metadata.py:71 +#: /home/kovid/work/calibre/src/calibre/gui2/device.py:514 +#: /home/kovid/work/calibre/src/calibre/gui2/device.py:523 +#: /home/kovid/work/calibre/src/calibre/gui2/device.py:738 +#: /home/kovid/work/calibre/src/calibre/gui2/device.py:741 #: /home/kovid/work/calibre/src/calibre/gui2/dialogs/comicconf.py:48 -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/epub.py:171 -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/epub.py:173 #: /home/kovid/work/calibre/src/calibre/gui2/dialogs/fetch_metadata.py:101 #: /home/kovid/work/calibre/src/calibre/gui2/dialogs/fetch_metadata.py:134 -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/lrf_single.py:366 -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/scheduler.py:33 -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/scheduler.py:38 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/scheduler.py:34 #: /home/kovid/work/calibre/src/calibre/gui2/dialogs/scheduler.py:39 -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/scheduler.py:122 -#: /home/kovid/work/calibre/src/calibre/gui2/library.py:364 -#: /home/kovid/work/calibre/src/calibre/gui2/library.py:377 -#: /home/kovid/work/calibre/src/calibre/gui2/library.py:905 -#: /home/kovid/work/calibre/src/calibre/gui2/tools.py:61 -#: /home/kovid/work/calibre/src/calibre/gui2/tools.py:123 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/scheduler.py:40 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/scheduler.py:123 +#: /home/kovid/work/calibre/src/calibre/gui2/library.py:356 +#: /home/kovid/work/calibre/src/calibre/gui2/library.py:369 +#: /home/kovid/work/calibre/src/calibre/gui2/library.py:932 #: /home/kovid/work/calibre/src/calibre/library/cli.py:264 #: /home/kovid/work/calibre/src/calibre/library/database.py:916 -#: /home/kovid/work/calibre/src/calibre/library/database2.py:500 -#: /home/kovid/work/calibre/src/calibre/library/database2.py:512 -#: /home/kovid/work/calibre/src/calibre/library/database2.py:897 -#: /home/kovid/work/calibre/src/calibre/library/database2.py:932 -#: /home/kovid/work/calibre/src/calibre/library/database2.py:1239 -#: /home/kovid/work/calibre/src/calibre/library/database2.py:1241 -#: /home/kovid/work/calibre/src/calibre/library/database2.py:1421 -#: /home/kovid/work/calibre/src/calibre/library/database2.py:1444 -#: /home/kovid/work/calibre/src/calibre/library/database2.py:1495 -#: /home/kovid/work/calibre/src/calibre/library/server.py:340 -#: /home/kovid/work/calibre/src/calibre/utils/podofo/__init__.py:28 -#: /home/kovid/work/calibre/src/calibre/utils/podofo/__init__.py:31 -#: /home/kovid/work/calibre/src/calibre/web/feeds/news.py:50 +#: /home/kovid/work/calibre/src/calibre/library/database2.py:548 +#: /home/kovid/work/calibre/src/calibre/library/database2.py:560 +#: /home/kovid/work/calibre/src/calibre/library/database2.py:956 +#: /home/kovid/work/calibre/src/calibre/library/database2.py:991 +#: /home/kovid/work/calibre/src/calibre/library/database2.py:1318 +#: /home/kovid/work/calibre/src/calibre/library/database2.py:1320 +#: /home/kovid/work/calibre/src/calibre/library/database2.py:1404 +#: /home/kovid/work/calibre/src/calibre/library/database2.py:1488 +#: /home/kovid/work/calibre/src/calibre/library/database2.py:1511 +#: /home/kovid/work/calibre/src/calibre/library/database2.py:1562 +#: /home/kovid/work/calibre/src/calibre/library/server.py:341 +#: /home/kovid/work/calibre/src/calibre/utils/podofo/__init__.py:74 +#: /home/kovid/work/calibre/src/calibre/utils/podofo/__init__.py:90 +#: /home/kovid/work/calibre/src/calibre/web/feeds/news.py:51 msgid "Unknown" msgstr "" @@ -124,72 +144,148 @@ msgstr "" msgid "Metadata writer" msgstr "" -#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:12 +#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:13 msgid "Follow all local links in an HTML file and create a ZIP file containing all linked files. This plugin is run every time you add an HTML file to the library." msgstr "" -#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:32 -#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:43 -#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:53 -#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:64 -#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:74 -#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:84 -#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:94 -#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:105 -#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:116 -#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:126 -#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:136 -#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:147 -#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:157 -#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:167 -msgid "Read metadata from %s files" -msgstr "" - -#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:177 +#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:48 msgid "Extract cover from comic files" msgstr "" -#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:197 -msgid "Read metadata from ebooks in ZIP archives" +#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:69 +#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:79 +#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:89 +#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:99 +#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:110 +#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:120 +#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:130 +#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:140 +#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:150 +#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:160 +#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:171 +#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:182 +#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:202 +#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:213 +#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:223 +#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:233 +msgid "Read metadata from %s files" msgstr "" -#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:207 +#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:192 msgid "Read metadata from ebooks in RAR archives" msgstr "" -#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:218 -#: /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:248 -#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:259 +#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:244 +msgid "Read metadata from ebooks in ZIP archives" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:255 +#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:265 +#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:275 +#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:297 +#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:308 msgid "Set metadata in %s files" msgstr "" -#: /home/kovid/work/calibre/src/calibre/customize/ui.py:28 -msgid "Installed plugins" +#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:286 +msgid "Set metadata from %s files" msgstr "" -#: /home/kovid/work/calibre/src/calibre/customize/ui.py:29 -msgid "Mapping for filetype plugins" +#: /home/kovid/work/calibre/src/calibre/customize/conversion.py:99 +msgid "Conversion Input" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/customize/conversion.py:118 +msgid "Save the output from the input plugin to the specified directory. Useful if you are unsure at which stage of the conversion process a bug is occurring. WARNING: This completely deletes the contents of the specified directory." +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/customize/conversion.py:127 +msgid "Specify the character encoding of the input document. If set this option will override any encoding declared by the document itself. Particularly useful for documents that do not declare an encoding or that have erroneous encoding declarations." +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/customize/conversion.py:241 +msgid "Conversion Output" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/customize/conversion.py:255 +msgid "If specified, the output plugin will try to create output that is as human readable as possible. May not have any effect for some output plugins." +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/customize/profiles.py:44 +msgid "Input profile" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/customize/profiles.py:48 +msgid "This profile tries to provide sane defaults and is useful if you know nothing about the input document." +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/customize/profiles.py:56 +#: /home/kovid/work/calibre/src/calibre/customize/profiles.py:154 +msgid "This profile is intended for the SONY PRS line. The 500/505/700 etc." +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/customize/profiles.py:69 +#: /home/kovid/work/calibre/src/calibre/customize/profiles.py:178 +msgid "This profile is intended for the Microsoft Reader." +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/customize/profiles.py:80 +#: /home/kovid/work/calibre/src/calibre/customize/profiles.py:189 +msgid "This profile is intended for the Mobipocket books." +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/customize/profiles.py:93 +#: /home/kovid/work/calibre/src/calibre/customize/profiles.py:202 +msgid "This profile is intended for the Hanlin V3 and its clones." +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/customize/profiles.py:105 +#: /home/kovid/work/calibre/src/calibre/customize/profiles.py:214 +msgid "This profile is intended for the Cybook G3." +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/customize/profiles.py:117 +#: /home/kovid/work/calibre/src/calibre/customize/profiles.py:226 +msgid "This profile is intended for the Amazon Kindle." +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/customize/profiles.py:135 +msgid "Output profile" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/customize/profiles.py:139 +msgid "This profile tries to provide sane defaults and is useful if you want to produce a document intended to be read at a computer or on a range of devices." +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/customize/profiles.py:166 +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/ui.py:30 -msgid "Local plugin customization" +msgid "Installed plugins" msgstr "" #: /home/kovid/work/calibre/src/calibre/customize/ui.py:31 +msgid "Mapping for filetype plugins" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/customize/ui.py:32 +msgid "Local plugin customization" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/customize/ui.py:33 msgid "Disabled plugins" msgstr "" -#: /home/kovid/work/calibre/src/calibre/customize/ui.py:73 +#: /home/kovid/work/calibre/src/calibre/customize/ui.py:75 msgid "No valid plugin found in " msgstr "" -#: /home/kovid/work/calibre/src/calibre/customize/ui.py:192 +#: /home/kovid/work/calibre/src/calibre/customize/ui.py:215 msgid "Initialization of plugin %s failed with traceback:" msgstr "" -#: /home/kovid/work/calibre/src/calibre/customize/ui.py:269 +#: /home/kovid/work/calibre/src/calibre/customize/ui.py:333 msgid "" " %prog options\n" "\n" @@ -197,740 +293,572 @@ msgid "" " " msgstr "" -#: /home/kovid/work/calibre/src/calibre/customize/ui.py:275 +#: /home/kovid/work/calibre/src/calibre/customize/ui.py:339 msgid "Add a plugin by specifying the path to the zip file containing it." msgstr "" -#: /home/kovid/work/calibre/src/calibre/customize/ui.py:277 +#: /home/kovid/work/calibre/src/calibre/customize/ui.py:341 msgid "Remove a custom plugin by name. Has no effect on builtin plugins" msgstr "" -#: /home/kovid/work/calibre/src/calibre/customize/ui.py:279 +#: /home/kovid/work/calibre/src/calibre/customize/ui.py:343 msgid "Customize plugin. Specify name of plugin and customization string separated by a comma." msgstr "" -#: /home/kovid/work/calibre/src/calibre/customize/ui.py:281 +#: /home/kovid/work/calibre/src/calibre/customize/ui.py:345 msgid "List all installed plugins" msgstr "" -#: /home/kovid/work/calibre/src/calibre/customize/ui.py:283 +#: /home/kovid/work/calibre/src/calibre/customize/ui.py:347 msgid "Enable the named plugin" msgstr "" -#: /home/kovid/work/calibre/src/calibre/customize/ui.py:285 +#: /home/kovid/work/calibre/src/calibre/customize/ui.py:349 msgid "Disable the named plugin" msgstr "" -#: /home/kovid/work/calibre/src/calibre/devices/cybookg3/driver.py:41 -#: /home/kovid/work/calibre/src/calibre/devices/prs505/driver.py:394 -#: /home/kovid/work/calibre/src/calibre/devices/usbms/driver.py:72 -msgid "The reader has no storage card connected." +#: /home/kovid/work/calibre/src/calibre/devices/bebook/driver.py:11 +msgid "Communicate with the BeBook eBook reader." msgstr "" -#: /home/kovid/work/calibre/src/calibre/devices/cybookg3/driver.py:60 -#: /home/kovid/work/calibre/src/calibre/devices/usbms/driver.py:91 -msgid "There is insufficient free space on the storage card" +#: /home/kovid/work/calibre/src/calibre/devices/bebook/driver.py:12 +msgid "Tijmen Ruizendaal" msgstr "" -#: /home/kovid/work/calibre/src/calibre/devices/cybookg3/driver.py:62 -#: /home/kovid/work/calibre/src/calibre/devices/usbms/driver.py:93 +#: /home/kovid/work/calibre/src/calibre/devices/bebook/driver.py:49 +msgid "Communicate with the BeBook Mini eBook reader." +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/devices/blackberry/driver.py:12 +msgid "Communicate with the Blackberry smart phone." +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/devices/blackberry/driver.py:13 +#: /home/kovid/work/calibre/src/calibre/devices/eb600/driver.py:19 +#: /home/kovid/work/calibre/src/calibre/devices/prs500/driver.py:88 +#: /home/kovid/work/calibre/src/calibre/web/feeds/recipes/recipe_hindu.py:12 +msgid "Kovid Goyal" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/devices/cybookg3/driver.py:16 +msgid "Communicate with the Cybook eBook reader." +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/devices/cybookg3/driver.py:17 +#: /home/kovid/work/calibre/src/calibre/devices/kindle/driver.py:14 +#: /home/kovid/work/calibre/src/calibre/devices/kindle/driver.py:71 +#: /home/kovid/work/calibre/src/calibre/devices/usbms/driver.py:26 +msgid "John Schember" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/devices/cybookg3/driver.py:58 +#: /home/kovid/work/calibre/src/calibre/devices/jetbook/driver.py:62 +#: /home/kovid/work/calibre/src/calibre/devices/prs505/driver.py:152 +#: /home/kovid/work/calibre/src/calibre/devices/usbms/driver.py:133 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/scheduler.py:467 +#: /home/kovid/work/calibre/src/calibre/gui2/tags.py:50 +#: /home/kovid/work/calibre/src/calibre/library/database2.py:900 +#: /home/kovid/work/calibre/src/calibre/library/database2.py:904 +#: /home/kovid/work/calibre/src/calibre/library/database2.py:1222 +msgid "News" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/devices/cybookg3/driver.py:97 +#: /home/kovid/work/calibre/src/calibre/devices/cybookg3/driver.py:99 +#: /home/kovid/work/calibre/src/calibre/devices/jetbook/driver.py:97 +#: /home/kovid/work/calibre/src/calibre/devices/jetbook/driver.py:99 +#: /home/kovid/work/calibre/src/calibre/devices/prs505/driver.py:178 +#: /home/kovid/work/calibre/src/calibre/devices/prs505/driver.py:180 +#: /home/kovid/work/calibre/src/calibre/devices/usbms/driver.py:165 +#: /home/kovid/work/calibre/src/calibre/devices/usbms/driver.py:167 +msgid "Transferring books to device..." +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/devices/cybookg3/driver.py:105 +#: /home/kovid/work/calibre/src/calibre/devices/cybookg3/driver.py:125 +#: /home/kovid/work/calibre/src/calibre/devices/kindle/driver.py:43 +#: /home/kovid/work/calibre/src/calibre/devices/kindle/driver.py:52 +#: /home/kovid/work/calibre/src/calibre/devices/prs505/driver.py:199 +#: /home/kovid/work/calibre/src/calibre/devices/prs505/driver.py:202 +#: /home/kovid/work/calibre/src/calibre/devices/usbms/driver.py:186 +#: /home/kovid/work/calibre/src/calibre/devices/usbms/driver.py:195 +msgid "Removing books from device..." +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/devices/eb600/driver.py:18 +msgid "Communicate with the EB600 eBook reader." +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/devices/interface.py:20 +msgid "Device Interface" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/devices/jetbook/driver.py:15 +msgid "Communicate with the JetBook eBook reader." +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/devices/jetbook/driver.py:16 +msgid "James Ralston" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/devices/kindle/driver.py:13 +msgid "Communicate with the Kindle eBook reader." +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/devices/kindle/driver.py:70 +msgid "Communicate with the Kindle 2 eBook reader." +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/devices/prs500/driver.py:87 +msgid "Communicate with the Sony PRS-500 eBook reader." +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/devices/prs505/books.py:150 +#: /home/kovid/work/calibre/src/calibre/devices/prs505/driver.py:83 +#: /home/kovid/work/calibre/src/calibre/devices/prs505/driver.py:86 +#: /home/kovid/work/calibre/src/calibre/devices/prs505/driver.py:89 +#: /home/kovid/work/calibre/src/calibre/devices/prs505/driver.py:100 +#: /home/kovid/work/calibre/src/calibre/devices/usbms/driver.py:49 +#: /home/kovid/work/calibre/src/calibre/devices/usbms/driver.py:52 +#: /home/kovid/work/calibre/src/calibre/devices/usbms/driver.py:55 +#: /home/kovid/work/calibre/src/calibre/devices/usbms/driver.py:68 +#: /home/kovid/work/calibre/src/calibre/devices/usbms/driver.py:74 +#: /home/kovid/work/calibre/src/calibre/devices/usbms/driver.py:78 +msgid "Getting list of books on device..." +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/devices/prs505/driver.py:19 +msgid "Communicate with the Sony PRS-505 eBook reader." +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/devices/prs505/driver.py:20 +#: /home/kovid/work/calibre/src/calibre/devices/prs700/driver.py:14 +msgid "Kovid Goyal and John Schember" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/devices/prs505/driver.py:78 +#: /home/kovid/work/calibre/src/calibre/devices/usbms/driver.py:41 +msgid "Get device information..." +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/devices/prs505/driver.py:106 +#: /home/kovid/work/calibre/src/calibre/devices/prs505/driver.py:108 +#: /home/kovid/work/calibre/src/calibre/devices/prs505/driver.py:110 +#: /home/kovid/work/calibre/src/calibre/devices/usbms/driver.py:84 +#: /home/kovid/work/calibre/src/calibre/devices/usbms/driver.py:86 +#: /home/kovid/work/calibre/src/calibre/devices/usbms/driver.py:88 +msgid "The reader has no storage card in this slot." +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/devices/prs505/driver.py:131 +#: /home/kovid/work/calibre/src/calibre/devices/usbms/driver.py:109 msgid "There is insufficient free space in main memory" msgstr "" -#: /home/kovid/work/calibre/src/calibre/devices/prs505/driver.py:140 -#: /home/kovid/work/calibre/src/calibre/devices/prs505/driver.py:168 -#: /home/kovid/work/calibre/src/calibre/devices/prs505/driver.py:196 -#: /home/kovid/work/calibre/src/calibre/devices/usbms/device.py:204 -#: /home/kovid/work/calibre/src/calibre/devices/usbms/device.py:251 -#: /home/kovid/work/calibre/src/calibre/devices/usbms/device.py:278 +#: /home/kovid/work/calibre/src/calibre/devices/prs505/driver.py:133 +#: /home/kovid/work/calibre/src/calibre/devices/prs505/driver.py:135 +#: /home/kovid/work/calibre/src/calibre/devices/usbms/driver.py:111 +#: /home/kovid/work/calibre/src/calibre/devices/usbms/driver.py:113 +msgid "There is insufficient free space on the storage card" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/devices/prs505/driver.py:230 +#: /home/kovid/work/calibre/src/calibre/devices/usbms/driver.py:210 +msgid "Sending metadata to device..." +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/devices/prs700/driver.py:13 +msgid "Communicate with the Sony PRS-700 eBook reader." +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/devices/usbms/device.py:227 +#: /home/kovid/work/calibre/src/calibre/devices/usbms/device.py:277 +#: /home/kovid/work/calibre/src/calibre/devices/usbms/device.py:312 msgid "Unable to detect the %s disk drive. Try rebooting." msgstr "" -#: /home/kovid/work/calibre/src/calibre/ebooks/epub/__init__.py:94 -msgid "Options to control the conversion to EPUB" +#: /home/kovid/work/calibre/src/calibre/devices/usbms/deviceconfig.py:11 +msgid "Ordered list of formats the device will accept" msgstr "" -#: /home/kovid/work/calibre/src/calibre/ebooks/epub/__init__.py:105 -msgid "The output EPUB file. If not specified, it is derived from the input file name." +#: /home/kovid/work/calibre/src/calibre/devices/usbms/deviceconfig.py:16 +msgid "settings for device drivers" msgstr "" -#: /home/kovid/work/calibre/src/calibre/ebooks/epub/__init__.py:108 -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 restrictions on the EPUB. Choices are: " +#: /home/kovid/work/calibre/src/calibre/devices/usbms/driver.py:25 +msgid "Communicate with an eBook reader." msgstr "" -#: /home/kovid/work/calibre/src/calibre/ebooks/epub/__init__.py:113 -msgid "Either the path to a CSS stylesheet or raw CSS. This CSS will override any existing CSS declarations in the source files." +#: /home/kovid/work/calibre/src/calibre/devices/usbms/driver.py:173 +#: /home/kovid/work/calibre/src/calibre/devices/usbms/driver.py:181 +msgid "Adding books to device metadata listing..." msgstr "" -#: /home/kovid/work/calibre/src/calibre/ebooks/epub/__init__.py:117 -msgid "Control auto-detection of document structure." +#: /home/kovid/work/calibre/src/calibre/devices/usbms/driver.py:199 +#: /home/kovid/work/calibre/src/calibre/devices/usbms/driver.py:204 +msgid "Removing books from device metadata listing..." msgstr "" -#: /home/kovid/work/calibre/src/calibre/ebooks/epub/__init__.py:122 -msgid "" -"An XPath expression to detect chapter titles. The default is to consider

or\n" -"

tags that contain the words \"chapter\",\"book\",\"section\" or \"part\" as chapter titles as \n" -"well as any tags that have class=\"chapter\". \n" -"The expression used must evaluate to a list of elements. To disable chapter detection,\n" -"use the expression \"/\". See the XPath Tutorial in the calibre User Manual for further\n" -"help on using this feature.\n" -msgstr "" - -#: /home/kovid/work/calibre/src/calibre/ebooks/epub/__init__.py:132 -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 chapters. A value of \"none\" will disable chapter marking and a value of \"both\" will use both page breaks and lines to mark chapters." -msgstr "" - -#: /home/kovid/work/calibre/src/calibre/ebooks/epub/__init__.py:139 -msgid "Path to the cover to be used for this book" -msgstr "" - -#: /home/kovid/work/calibre/src/calibre/ebooks/epub/__init__.py:142 -msgid "Use the cover detected from the source file in preference to the specified cover." -msgstr "" - -#: /home/kovid/work/calibre/src/calibre/ebooks/epub/__init__.py:145 -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/epub/__init__.py:149 -msgid "Turn off splitting at page breaks. Normally, input files are automatically split at every page break into two files. This gives an output ebook that can be parsed faster and with less resources. However, splitting is slow and if your source file contains a very large number of page breaks, you should turn off splitting on page breaks." -msgstr "" - -#: /home/kovid/work/calibre/src/calibre/ebooks/epub/__init__.py:157 -msgid "XPath expression to detect page boundaries for building a custom pagination map, as used by AdobeDE. Default is not to build an explicit pagination map." -msgstr "" - -#: /home/kovid/work/calibre/src/calibre/ebooks/epub/__init__.py:161 -msgid "XPath expression to find the name of each page in the pagination map relative to its boundary element. Default is to number all pages staring with 1." -msgstr "" - -#: /home/kovid/work/calibre/src/calibre/ebooks/epub/__init__.py:165 -msgid "" -"Control the automatic generation of a Table of Contents. If an OPF file is detected\n" -"and it specifies a Table of Contents, then that will be used rather than trying\n" -"to auto-generate a Table of Contents.\n" -msgstr "" - -#: /home/kovid/work/calibre/src/calibre/ebooks/epub/__init__.py:171 -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 --toc-threshold number of chapters were detected." -msgstr "" - -#: /home/kovid/work/calibre/src/calibre/ebooks/epub/__init__.py:175 -msgid "Don't add auto-detected chapters to the Table of Contents." -msgstr "" - -#: /home/kovid/work/calibre/src/calibre/ebooks/epub/__init__.py:177 -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/epub/__init__.py:180 -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/epub/__init__.py:184 -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/epub/__init__.py:188 -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/epub/__init__.py:192 -msgid "Path to a .ncx file that contains the table of contents to use for this ebook. The NCX file should contain links relative to the directory it is placed in. See http://www.niso.org/workrooms/daisy/Z39-86-2005.html#NCX for an overview of the NCX format." -msgstr "" - -#: /home/kovid/work/calibre/src/calibre/ebooks/epub/__init__.py:198 -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/epub/__init__.py:202 -msgid "Control page layout" -msgstr "" - -#: /home/kovid/work/calibre/src/calibre/ebooks/epub/__init__.py:204 -msgid "Set the top margin in pts. Default is %default" -msgstr "" - -#: /home/kovid/work/calibre/src/calibre/ebooks/epub/__init__.py:206 -msgid "Set the bottom margin in pts. Default is %default" -msgstr "" - -#: /home/kovid/work/calibre/src/calibre/ebooks/epub/__init__.py:208 -msgid "Set the left margin in pts. Default is %default" -msgstr "" - -#: /home/kovid/work/calibre/src/calibre/ebooks/epub/__init__.py:210 -msgid "Set the right margin in pts. Default is %default" -msgstr "" - -#: /home/kovid/work/calibre/src/calibre/ebooks/epub/__init__.py:212 -msgid "The base font size in pts. Default is %defaultpt. Set to 0 to disable rescaling of fonts." -msgstr "" - -#: /home/kovid/work/calibre/src/calibre/ebooks/epub/__init__.py:215 -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 "" - -#: /home/kovid/work/calibre/src/calibre/ebooks/epub/__init__.py:221 -msgid "Do not force text to be justified in output." -msgstr "" - -#: /home/kovid/work/calibre/src/calibre/ebooks/epub/__init__.py:223 -msgid "Remove table markup, converting it into paragraphs. This is useful if your source file uses a table to manage layout." -msgstr "" - -#: /home/kovid/work/calibre/src/calibre/ebooks/epub/__init__.py:226 -msgid "Preserve the HTML tag structure while splitting large HTML files. This is only neccessary if the HTML files contain CSS that uses sibling selectors. Enabling this greatly slows down processing of large HTML files." -msgstr "" - -#: /home/kovid/work/calibre/src/calibre/ebooks/epub/__init__.py:232 -msgid "Print generated OPF file to stdout" -msgstr "" - -#: /home/kovid/work/calibre/src/calibre/ebooks/epub/__init__.py:234 -msgid "Print generated NCX file to stdout" -msgstr "" - -#: /home/kovid/work/calibre/src/calibre/ebooks/epub/__init__.py:237 -msgid "Keep intermediate files during processing by html2epub" -msgstr "" - -#: /home/kovid/work/calibre/src/calibre/ebooks/epub/__init__.py:239 -msgid "Extract the contents of the produced EPUB file to the specified directory." -msgstr "" - -#: /home/kovid/work/calibre/src/calibre/ebooks/epub/from_any.py:187 -msgid "" -"%%prog [options] filename\n" -"\n" -"Convert any of a large number of ebook formats to a %s file. Supported formats are: %s\n" -msgstr "" - -#: /home/kovid/work/calibre/src/calibre/ebooks/epub/from_html.py:105 -msgid "Could not find an ebook inside the archive" -msgstr "" - -#: /home/kovid/work/calibre/src/calibre/ebooks/epub/from_html.py:262 -msgid "" -"%prog [options] file.html|opf\n" -"\n" -"Convert a HTML file to an EPUB ebook. Recursively follows links in the HTML file.\n" -"If you specify an OPF file instead of an HTML file, the list of links is takes from\n" -"the element of the OPF file.\n" -msgstr "" - -#: /home/kovid/work/calibre/src/calibre/ebooks/epub/from_html.py:519 -#: /home/kovid/work/calibre/src/calibre/ebooks/lit/writer.py:758 -#: /home/kovid/work/calibre/src/calibre/ebooks/mobi/writer.py:621 -msgid "Output written to " -msgstr "" - -#: /home/kovid/work/calibre/src/calibre/ebooks/epub/from_html.py:541 -#: /home/kovid/work/calibre/src/calibre/ebooks/html.py:1158 -msgid "You must specify an input HTML file" -msgstr "" - -#: /home/kovid/work/calibre/src/calibre/ebooks/epub/iterator.py:36 -msgid "%s format books are not supported" -msgstr "" - -#: /home/kovid/work/calibre/src/calibre/ebooks/epub/split.py:30 -msgid "Could not find reasonable point at which to split: %s Sub-tree size: %d KB" -msgstr "" - -#: /home/kovid/work/calibre/src/calibre/ebooks/epub/split.py:149 -msgid "\t\tToo much markup. Re-splitting without structure preservation. This may cause incorrect rendering." -msgstr "" - -#: /home/kovid/work/calibre/src/calibre/ebooks/html.py:541 -msgid "Written processed HTML to " -msgstr "" - -#: /home/kovid/work/calibre/src/calibre/ebooks/html.py:939 -msgid "Options to control the traversal of HTML" -msgstr "" - -#: /home/kovid/work/calibre/src/calibre/ebooks/html.py:946 -msgid "The output directory. Default is the current directory." -msgstr "" - -#: /home/kovid/work/calibre/src/calibre/ebooks/html.py:948 -#: /home/kovid/work/calibre/src/calibre/ebooks/mobi/writer.py:568 -msgid "Character encoding for HTML files. Default is to auto detect." -msgstr "" - -#: /home/kovid/work/calibre/src/calibre/ebooks/html.py:950 -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 "" - -#: /home/kovid/work/calibre/src/calibre/ebooks/html.py:952 -msgid "Control the following of links in HTML files." -msgstr "" - -#: /home/kovid/work/calibre/src/calibre/ebooks/html.py:954 -msgid "Traverse links in HTML files breadth first. Normally, they are traversed depth first" -msgstr "" - -#: /home/kovid/work/calibre/src/calibre/ebooks/html.py:956 -msgid "Maximum levels of recursion when following links in HTML files. Must be non-negative. 0 implies that no links in the root HTML file are followed." -msgstr "" - -#: /home/kovid/work/calibre/src/calibre/ebooks/html.py:958 -msgid "Set metadata of the generated ebook" -msgstr "" - -#: /home/kovid/work/calibre/src/calibre/ebooks/html.py:960 -msgid "Set the title. Default is to autodetect." -msgstr "" - -#: /home/kovid/work/calibre/src/calibre/ebooks/html.py:962 -msgid "The author(s) of the ebook, as a & separated list." -msgstr "" - -#: /home/kovid/work/calibre/src/calibre/ebooks/html.py:964 -msgid "The subject(s) of this book, as a comma separated list." -msgstr "" - -#: /home/kovid/work/calibre/src/calibre/ebooks/html.py:966 -msgid "Set the publisher of this book." -msgstr "" - -#: /home/kovid/work/calibre/src/calibre/ebooks/html.py:968 -msgid "A summary of this book." -msgstr "" - -#: /home/kovid/work/calibre/src/calibre/ebooks/html.py:970 -msgid "Load metadata from the specified OPF file" -msgstr "" - -#: /home/kovid/work/calibre/src/calibre/ebooks/html.py:972 -msgid "Options useful for debugging" -msgstr "" - -#: /home/kovid/work/calibre/src/calibre/ebooks/html.py:974 -msgid "Be more verbose while processing. Can be specified multiple times to increase verbosity." -msgstr "" - -#: /home/kovid/work/calibre/src/calibre/ebooks/html.py:976 -msgid "Output HTML is \"pretty printed\" for easier parsing by humans" -msgstr "" - -#: /home/kovid/work/calibre/src/calibre/ebooks/html.py:982 -msgid "" -"%prog [options] file.html|opf\n" -"\n" -"Follow all links in an HTML file and collect them into the specified directory.\n" -"Also collects any resources like images, stylesheets, scripts, etc.\n" -"If an OPF file is specified instead, the list of files in its element\n" -"is used.\n" -msgstr "" - -#: /home/kovid/work/calibre/src/calibre/ebooks/lit/from_any.py:47 -msgid "Creating LIT file from EPUB..." -msgstr "" - -#: /home/kovid/work/calibre/src/calibre/ebooks/lit/reader.py:892 -msgid "%prog [options] LITFILE" -msgstr "" - -#: /home/kovid/work/calibre/src/calibre/ebooks/lit/reader.py:895 -#: /home/kovid/work/calibre/src/calibre/ebooks/mobi/reader.py:697 -msgid "Output directory. Defaults to current directory." -msgstr "" - -#: /home/kovid/work/calibre/src/calibre/ebooks/lit/reader.py:898 -msgid "Legibly format extracted markup. May modify meaningful whitespace." -msgstr "" - -#: /home/kovid/work/calibre/src/calibre/ebooks/lit/reader.py:901 -#: /home/kovid/work/calibre/src/calibre/ebooks/lit/writer.py:731 -#: /home/kovid/work/calibre/src/calibre/ebooks/mobi/writer.py:580 -msgid "Useful for debugging." -msgstr "" - -#: /home/kovid/work/calibre/src/calibre/ebooks/lit/reader.py:912 -#: /home/kovid/work/calibre/src/calibre/ebooks/mobi/reader.py:721 -msgid "OEB ebook created in" -msgstr "" - -#: /home/kovid/work/calibre/src/calibre/ebooks/lit/writer.py:725 -msgid "%prog [options] OPFFILE" -msgstr "" - -#: /home/kovid/work/calibre/src/calibre/ebooks/lit/writer.py:728 -#: /home/kovid/work/calibre/src/calibre/ebooks/mobi/from_feeds.py:26 -#: /home/kovid/work/calibre/src/calibre/ebooks/mobi/writer.py:577 -msgid "Output file. Default is derived from input filename." -msgstr "" - -#: /home/kovid/work/calibre/src/calibre/ebooks/lrf/__init__.py:76 -msgid "Set the title. Default: filename." -msgstr "" - -#: /home/kovid/work/calibre/src/calibre/ebooks/lrf/__init__.py:78 -msgid "Set the author(s). Multiple authors should be set as a comma separated list. Default: %default" -msgstr "" - -#: /home/kovid/work/calibre/src/calibre/ebooks/lrf/__init__.py:81 -msgid "Set the comment." -msgstr "" - -#: /home/kovid/work/calibre/src/calibre/ebooks/lrf/__init__.py:83 -msgid "Set the category" -msgstr "" - -#: /home/kovid/work/calibre/src/calibre/ebooks/lrf/__init__.py:85 -msgid "Sort key for the title" -msgstr "" - -#: /home/kovid/work/calibre/src/calibre/ebooks/lrf/__init__.py:87 -msgid "Sort key for the author" -msgstr "" - -#: /home/kovid/work/calibre/src/calibre/ebooks/lrf/__init__.py:89 -#: /home/kovid/work/calibre/src/calibre/ebooks/metadata/__init__.py:302 -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/fetch_metadata.py:58 -#: /home/kovid/work/calibre/src/calibre/gui2/library.py:113 -msgid "Publisher" -msgstr "" - -#: /home/kovid/work/calibre/src/calibre/ebooks/lrf/__init__.py:91 -msgid "Path to file containing image to be used as cover" -msgstr "" - -#: /home/kovid/work/calibre/src/calibre/ebooks/lrf/__init__.py:93 -msgid "If there is a cover graphic detected in the source file, use that instead of the specified cover." -msgstr "" - -#: /home/kovid/work/calibre/src/calibre/ebooks/lrf/__init__.py:96 -msgid "Output file name. Default is derived from input filename" -msgstr "" - -#: /home/kovid/work/calibre/src/calibre/ebooks/lrf/__init__.py:98 -#: /home/kovid/work/calibre/src/calibre/ebooks/mobi/writer.py:552 -msgid "Render HTML tables as blocks of text instead of actual tables. This is neccessary if the HTML contains very large or complex tables." -msgstr "" - -#: /home/kovid/work/calibre/src/calibre/ebooks/lrf/__init__.py:101 -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 "" - -#: /home/kovid/work/calibre/src/calibre/ebooks/lrf/__init__.py:103 -msgid "Enable autorotation of images that are wider than the screen width." -msgstr "" - -#: /home/kovid/work/calibre/src/calibre/ebooks/lrf/__init__.py:106 -msgid "Set the space between words in pts. Default is %default" -msgstr "" - -#: /home/kovid/work/calibre/src/calibre/ebooks/lrf/__init__.py:108 -msgid "Separate paragraphs by blank lines." -msgstr "" - -#: /home/kovid/work/calibre/src/calibre/ebooks/lrf/__init__.py:110 -msgid "Add a header to all the pages with title and author." -msgstr "" - -#: /home/kovid/work/calibre/src/calibre/ebooks/lrf/__init__.py:112 -msgid "Set the format of the header. %a is replaced by the author and %t by the title. Default is %default" -msgstr "" - -#: /home/kovid/work/calibre/src/calibre/ebooks/lrf/__init__.py:114 -msgid "Add extra spacing below the header. Default is %default px." -msgstr "" - -#: /home/kovid/work/calibre/src/calibre/ebooks/lrf/__init__.py:116 -msgid "Override the CSS. Can be either a path to a CSS stylesheet or a string. If it is a string it is interpreted as CSS." -msgstr "" - -#: /home/kovid/work/calibre/src/calibre/ebooks/lrf/__init__.py:118 -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 "" - -#: /home/kovid/work/calibre/src/calibre/ebooks/lrf/__init__.py:120 -msgid "Minimum paragraph indent (the indent of the first line of a paragraph) in pts. Default: %default" -msgstr "" - -#: /home/kovid/work/calibre/src/calibre/ebooks/lrf/__init__.py:122 -msgid "Increase the font size by 2 * FONT_DELTA pts and the line spacing by FONT_DELTA pts. FONT_DELTA can be a fraction.If FONT_DELTA is negative, the font size is decreased." -msgstr "" - -#: /home/kovid/work/calibre/src/calibre/ebooks/lrf/__init__.py:127 -msgid "Render all content as black on white instead of the colors specified by the HTML or CSS." -msgstr "" - -#: /home/kovid/work/calibre/src/calibre/ebooks/lrf/__init__.py:133 -msgid "Profile of the target device for which this LRF is being generated. The profile determines things like the resolution and screen size of the target device. Default: %s Supported profiles: " -msgstr "" - -#: /home/kovid/work/calibre/src/calibre/ebooks/lrf/__init__.py:139 -msgid "Left margin of page. Default is %default px." -msgstr "" - -#: /home/kovid/work/calibre/src/calibre/ebooks/lrf/__init__.py:141 -msgid "Right margin of page. Default is %default px." -msgstr "" - -#: /home/kovid/work/calibre/src/calibre/ebooks/lrf/__init__.py:143 -msgid "Top margin of page. Default is %default px." -msgstr "" - -#: /home/kovid/work/calibre/src/calibre/ebooks/lrf/__init__.py:145 -msgid "Bottom margin of page. Default is %default px." -msgstr "" - -#: /home/kovid/work/calibre/src/calibre/ebooks/lrf/__init__.py:147 -msgid "Render tables in the HTML as images (useful if the document has large or complex tables)" -msgstr "" - -#: /home/kovid/work/calibre/src/calibre/ebooks/lrf/__init__.py:149 -msgid "Multiply the size of text in rendered tables by this factor. Default is %default" -msgstr "" - -#: /home/kovid/work/calibre/src/calibre/ebooks/lrf/__init__.py:154 -msgid "The maximum number of levels to recursively process links. A value of 0 means thats links are not followed. A negative value means that tags are ignored." -msgstr "" - -#: /home/kovid/work/calibre/src/calibre/ebooks/lrf/__init__.py:158 -msgid "A regular expression. tags whose href matches will be ignored. Defaults to %default" -msgstr "" - -#: /home/kovid/work/calibre/src/calibre/ebooks/lrf/__init__.py:162 -msgid "Don't add links to the table of contents." -msgstr "" - -#: /home/kovid/work/calibre/src/calibre/ebooks/lrf/__init__.py:166 -msgid "Prevent the automatic detection chapters." -msgstr "" - -#: /home/kovid/work/calibre/src/calibre/ebooks/lrf/__init__.py:169 -msgid "The regular expression used to detect chapter titles. It is searched for in heading tags (h1-h6). Defaults to %default" -msgstr "" - -#: /home/kovid/work/calibre/src/calibre/ebooks/lrf/__init__.py:172 -msgid "Detect a chapter beginning at an element having the specified attribute. The format for this option is tagname regexp,attribute name,attribute value regexp. For example to match all heading tags that have the attribute class=\"chapter\" you would use \"h\\d,class,chapter\". You can set the attribute to \"none\" to match only on tag names. So for example, to match all h2 tags, you would use \"h2,none,\". Default is %default" -msgstr "" - -#: /home/kovid/work/calibre/src/calibre/ebooks/lrf/__init__.py:174 -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 "" - -#: /home/kovid/work/calibre/src/calibre/ebooks/lrf/__init__.py:184 -msgid "Force a page break before tags whose names match this regular expression." -msgstr "" - -#: /home/kovid/work/calibre/src/calibre/ebooks/lrf/__init__.py:186 -msgid "Force a page break before an element having the specified attribute. The format for this option is tagname regexp,attribute name,attribute value regexp. For example to match all heading tags that have the attribute class=\"chapter\" you would use \"h\\d,class,chapter\". Default is %default" -msgstr "" - -#: /home/kovid/work/calibre/src/calibre/ebooks/lrf/__init__.py:189 -msgid "Add detected chapters to the table of contents." -msgstr "" - -#: /home/kovid/work/calibre/src/calibre/ebooks/lrf/__init__.py:192 -msgid "Preprocess Baen HTML files to improve generated LRF." -msgstr "" - -#: /home/kovid/work/calibre/src/calibre/ebooks/lrf/__init__.py:194 -msgid "You must add this option if processing files generated by pdftohtml, otherwise conversion will fail." -msgstr "" - -#: /home/kovid/work/calibre/src/calibre/ebooks/lrf/__init__.py:196 -msgid "Use this option on html0 files from Book Designer." -msgstr "" - -#: /home/kovid/work/calibre/src/calibre/ebooks/lrf/__init__.py:199 -msgid "" -"Specify trutype font families for serif, sans-serif and monospace fonts. These fonts will be embedded in the LRF file. Note that custom fonts lead to slower page turns. For example: --serif-family \"Times New Roman\"\n" -" " -msgstr "" - -#: /home/kovid/work/calibre/src/calibre/ebooks/lrf/__init__.py:207 -msgid "The serif family of fonts to embed" -msgstr "" - -#: /home/kovid/work/calibre/src/calibre/ebooks/lrf/__init__.py:210 -msgid "The sans-serif family of fonts to embed" -msgstr "" - -#: /home/kovid/work/calibre/src/calibre/ebooks/lrf/__init__.py:213 -msgid "The monospace family of fonts to embed" -msgstr "" - -#: /home/kovid/work/calibre/src/calibre/ebooks/lrf/__init__.py:217 -msgid "Be verbose while processing" -msgstr "" - -#: /home/kovid/work/calibre/src/calibre/ebooks/lrf/__init__.py:219 -msgid "Convert to LRS" -msgstr "" - -#: /home/kovid/work/calibre/src/calibre/ebooks/lrf/__init__.py:221 -msgid "Minimize memory usage at the cost of longer processing times. Use this option if you are on a memory constrained machine." -msgstr "" - -#: /home/kovid/work/calibre/src/calibre/ebooks/lrf/__init__.py:223 -msgid "Specify the character encoding of the source file. If the output LRF file contains strange characters, try changing this option. A common encoding for files from windows computers is cp-1252. Another common choice is utf-8. The default is to try and guess the encoding." -msgstr "" - -#: /home/kovid/work/calibre/src/calibre/ebooks/lrf/any/convert_from.py:164 -msgid "Converting from %s to LRF is not supported." -msgstr "" - -#: /home/kovid/work/calibre/src/calibre/ebooks/lrf/any/convert_from.py:175 -msgid "" -"any2lrf [options] myfile\n" -"\n" -"Convert any ebook format into LRF. Supported formats are:\n" -"LIT, RTF, TXT, HTML, EPUB, MOBI, PRC and PDF. any2lrf will also process a RAR or\n" -"ZIP archive, looking for an ebook inside the archive.\n" -" " -msgstr "" - -#: /home/kovid/work/calibre/src/calibre/ebooks/lrf/any/convert_from.py:190 -msgid "No file to convert specified." -msgstr "" - -#: /home/kovid/work/calibre/src/calibre/ebooks/lrf/comic/convert_from.py:226 +#: /home/kovid/work/calibre/src/calibre/ebooks/comic/input.py:196 msgid "Rendered %s" msgstr "" -#: /home/kovid/work/calibre/src/calibre/ebooks/lrf/comic/convert_from.py:229 +#: /home/kovid/work/calibre/src/calibre/ebooks/comic/input.py:199 msgid "Failed %s" msgstr "" -#: /home/kovid/work/calibre/src/calibre/ebooks/lrf/comic/convert_from.py:280 +#: /home/kovid/work/calibre/src/calibre/ebooks/comic/input.py:257 msgid "" -"Failed to process comic: %s\n" +"Failed to process comic: \n" "\n" "%s" msgstr "" -#: /home/kovid/work/calibre/src/calibre/ebooks/lrf/comic/convert_from.py:287 -msgid "Options to control the conversion of comics (CBR, CBZ) files into ebooks" -msgstr "" - -#: /home/kovid/work/calibre/src/calibre/ebooks/lrf/comic/convert_from.py:293 -msgid "Title for generated ebook. Default is to use the filename." -msgstr "" - -#: /home/kovid/work/calibre/src/calibre/ebooks/lrf/comic/convert_from.py:295 -msgid "Set the author in the metadata of the generated ebook. Default is %default" -msgstr "" - -#: /home/kovid/work/calibre/src/calibre/ebooks/lrf/comic/convert_from.py:298 -#: /home/kovid/work/calibre/src/calibre/ebooks/pdf/pdftrim.py:22 -msgid "Path to output file. By default a file is created in the current directory." -msgstr "" - -#: /home/kovid/work/calibre/src/calibre/ebooks/lrf/comic/convert_from.py:300 +#: /home/kovid/work/calibre/src/calibre/ebooks/comic/input.py:275 msgid "Number of colors for grayscale image conversion. Default: %default" msgstr "" -#: /home/kovid/work/calibre/src/calibre/ebooks/lrf/comic/convert_from.py:302 +#: /home/kovid/work/calibre/src/calibre/ebooks/comic/input.py:277 msgid "Disable normalize (improve contrast) color range for pictures. Default: False" msgstr "" -#: /home/kovid/work/calibre/src/calibre/ebooks/lrf/comic/convert_from.py:304 +#: /home/kovid/work/calibre/src/calibre/ebooks/comic/input.py:280 msgid "Maintain picture aspect ratio. Default is to fill the screen." msgstr "" -#: /home/kovid/work/calibre/src/calibre/ebooks/lrf/comic/convert_from.py:306 +#: /home/kovid/work/calibre/src/calibre/ebooks/comic/input.py:282 msgid "Disable sharpening." msgstr "" -#: /home/kovid/work/calibre/src/calibre/ebooks/lrf/comic/convert_from.py:308 +#: /home/kovid/work/calibre/src/calibre/ebooks/comic/input.py:284 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/lrf/comic/convert_from.py:311 +#: /home/kovid/work/calibre/src/calibre/ebooks/comic/input.py:287 msgid "Don't split landscape images into two portrait images" msgstr "" -#: /home/kovid/work/calibre/src/calibre/ebooks/lrf/comic/convert_from.py:313 +#: /home/kovid/work/calibre/src/calibre/ebooks/comic/input.py:289 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/lrf/comic/convert_from.py:315 +#: /home/kovid/work/calibre/src/calibre/ebooks/comic/input.py:292 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/lrf/comic/convert_from.py:317 +#: /home/kovid/work/calibre/src/calibre/ebooks/comic/input.py:296 msgid "Enable Despeckle. Reduces speckle noise. May greatly increase processing time." msgstr "" -#: /home/kovid/work/calibre/src/calibre/ebooks/lrf/comic/convert_from.py:319 +#: /home/kovid/work/calibre/src/calibre/ebooks/comic/input.py:299 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/lrf/comic/convert_from.py:321 -msgid "Choose a profile for the device you are generating this file for. The default is the SONY PRS-500 with a screen size of 584x754 pixels. This is suitable for any reader with the same screen size. Choices are %s" -msgstr "" - -#: /home/kovid/work/calibre/src/calibre/ebooks/lrf/comic/convert_from.py:323 -#: /home/kovid/work/calibre/src/calibre/ebooks/pdf/pdftrim.py:20 -msgid "Be verbose, useful for debugging. Can be specified multiple times for greater verbosity." -msgstr "" - -#: /home/kovid/work/calibre/src/calibre/ebooks/lrf/comic/convert_from.py:325 -msgid "Don't show progress bar." -msgstr "" - -#: /home/kovid/work/calibre/src/calibre/ebooks/lrf/comic/convert_from.py:328 +#: /home/kovid/work/calibre/src/calibre/ebooks/comic/input.py:303 msgid "Apply no processing to the image" msgstr "" -#: /home/kovid/work/calibre/src/calibre/ebooks/lrf/comic/convert_from.py:333 +#: /home/kovid/work/calibre/src/calibre/ebooks/comic/input.py:428 +#: /home/kovid/work/calibre/src/calibre/ebooks/comic/input.py:439 +msgid "Page" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/ebooks/conversion/cli.py:10 msgid "" -"%prog [options] comic.cb[z|r]\n" +"input_file output_file [options]\n" "\n" -"Convert a comic in a CBZ or CBR file to an ebook.\n" -msgstr "" - -#: /home/kovid/work/calibre/src/calibre/ebooks/lrf/comic/convert_from.py:393 -msgid "Output written to" -msgstr "" - -#: /home/kovid/work/calibre/src/calibre/ebooks/lrf/comic/convert_from.py:553 -#: /home/kovid/work/calibre/src/calibre/ebooks/mobi/from_comic.py:35 -msgid "Rendering comic pages..." -msgstr "" - -#: /home/kovid/work/calibre/src/calibre/ebooks/lrf/epub/convert_from.py:17 -msgid "" -"Usage: %prog [options] mybook.epub\n" -" \n" -" \n" -"%prog converts mybook.epub to mybook.lrf" -msgstr "" - -#: /home/kovid/work/calibre/src/calibre/ebooks/lrf/fb2/convert_from.py:23 -msgid "" -"%prog [options] mybook.fb2\n" +"Convert an ebook from one format to another.\n" "\n" +"input_file is the input and output_file is the output. Both must be specified as the first two arguments to the command.\n" "\n" -"%prog converts mybook.fb2 to mybook.lrf" +"The output ebook format is guessed from the file extension of output_file. output_file can also be of the special format .EXT where EXT is the output file extension. In this case, the name of the output file is derived the name of the input file. Note that the filenames must not start with a hyphen. Finally, if output_file has no extension, then it is treated as a directory and an \"open ebook\" (OEB) consisting of HTML files is written to that directory. These files are the files that would normally have been passed to the output plugin.\n" +"\n" +"After specifying the input and output file you can customize the conversion by specifying various options. the available options depend on the input and output file types. To get help on them specify the input and output file and then use the -h option.\n" +"\n" +"For full documentation of the conversion system see\n" msgstr "" -#: /home/kovid/work/calibre/src/calibre/ebooks/lrf/fb2/convert_from.py:28 -#: /home/kovid/work/calibre/src/calibre/ebooks/lrf/txt/convert_from.py:24 -msgid "Print generated HTML to stdout and quit." +#: /home/kovid/work/calibre/src/calibre/ebooks/conversion/cli.py:86 +msgid "INPUT OPTIONS" msgstr "" -#: /home/kovid/work/calibre/src/calibre/ebooks/lrf/fb2/convert_from.py:30 -msgid "Keep generated HTML files after completing conversion to LRF." +#: /home/kovid/work/calibre/src/calibre/ebooks/conversion/cli.py:87 +msgid "Options to control the processing of the input %s file" msgstr "" -#: /home/kovid/work/calibre/src/calibre/ebooks/lrf/feeds/convert_from.py:20 -msgid "Options to control the behavior of feeds2disk" +#: /home/kovid/work/calibre/src/calibre/ebooks/conversion/cli.py:93 +msgid "OUTPUT OPTIONS" msgstr "" -#: /home/kovid/work/calibre/src/calibre/ebooks/lrf/feeds/convert_from.py:22 -msgid "Options to control the behavior of html2lrf" +#: /home/kovid/work/calibre/src/calibre/ebooks/conversion/cli.py:94 +msgid "Options to control the processing of the output %s" msgstr "" -#: /home/kovid/work/calibre/src/calibre/ebooks/lrf/feeds/convert_from.py:44 -msgid "Fetching of recipe failed: " +#: /home/kovid/work/calibre/src/calibre/ebooks/conversion/cli.py:108 +msgid "Options to control the look and feel of the output" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/ebooks/conversion/cli.py:122 +msgid "Control auto-detection of document structure." +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/ebooks/conversion/cli.py:132 +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:142 +msgid "Options to set metadata in the output" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/ebooks/conversion/cli.py:145 +msgid "Options to help with debugging the conversion" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/ebooks/conversion/cli.py:219 +msgid "Output saved to" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/ebooks/conversion/plumber.py:71 +msgid "Level of verbosity. Specify multiple times for greater verbosity." +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/ebooks/conversion/plumber.py:78 +msgid "Specify the input profile. The input profile gives the conversion system information on how to interpret various information in the input document. For example resolution dependent lengths (i.e. lengths in pixels). Choices are:" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/ebooks/conversion/plumber.py:89 +msgid "Specify the output profile. The output profile tells the conversion system how to optimize the created document for the specified device. In some cases, an output profile is required to produce documents that will work on a device. For example EPUB on the SONY reader. Choices are:" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/ebooks/conversion/plumber.py:100 +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 fonts in the output bigger and vice versa. By default, the base font size is chosen based on the output profile you chose." +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/ebooks/conversion/plumber.py:110 +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-large, with the final size being for huge fonts. The font rescaling algorithm uses these sizes to intelligently rescale fonts. The default is to use a mapping based on the output profile you chose." +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/ebooks/conversion/plumber.py:122 +msgid "Disable all rescaling of font sizes." +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/ebooks/conversion/plumber.py:129 +msgid "The line height in pts. Controls spacing between consecutive lines of text. By default no line height manipulation is performed." +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/ebooks/conversion/plumber.py:137 +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 page and other artifacts. This option will extract the content from the tables and present it in a linear fashion." +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/ebooks/conversion/plumber.py:147 +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:156 +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:164 +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:172 +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:180 +msgid "Don't add auto-detected chapters to the Table of Contents." +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/ebooks/conversion/plumber.py:187 +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:194 +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:202 +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:213 +msgid "An XPath expression to detect chapter titles. The default is to consider

or

tags that contain the words \"chapter\",\"book\",\"section\" or \"part\" as chapter titles as well as any tags that have class=\"chapter\". The expression used must evaluate to a list of elements. To disable chapter detection, use the expression \"/\". See the XPath Tutorial in the calibre User Manual for further help on using this feature." +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/ebooks/conversion/plumber.py:227 +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 chapters. A value of \"none\" will disable chapter marking and a value of \"both\" will use both page breaks and lines to mark chapters." +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/ebooks/conversion/plumber.py:237 +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:246 +msgid "An XPath expression. Page breaks are inserted before the specified elements." +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/ebooks/conversion/plumber.py:252 +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:257 +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:262 +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:267 +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:272 +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 "" + +#: /home/kovid/work/calibre/src/calibre/ebooks/conversion/plumber.py:279 +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:286 +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:292 +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:299 +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:307 +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:315 +msgid "Attempt to detect and correct hard line breaks and other problems in the source file. This may make things worse, so use with care." +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/ebooks/conversion/plumber.py:325 +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:333 +#: /home/kovid/work/calibre/src/calibre/ebooks/metadata/cli.py:35 +msgid "Set the title." +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/ebooks/conversion/plumber.py:337 +msgid "Set the authors. Multiple authors should be separated by ampersands." +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/ebooks/conversion/plumber.py:342 +msgid "The version of the title to be used for sorting. " +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/ebooks/conversion/plumber.py:346 +msgid "String to be used when sorting by author. " +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/ebooks/conversion/plumber.py:350 +#: /home/kovid/work/calibre/src/calibre/ebooks/metadata/cli.py:49 +msgid "Set the cover to the specified file." +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/ebooks/conversion/plumber.py:354 +#: /home/kovid/work/calibre/src/calibre/ebooks/metadata/cli.py:51 +msgid "Set the ebook description." +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/ebooks/conversion/plumber.py:358 +#: /home/kovid/work/calibre/src/calibre/ebooks/metadata/cli.py:53 +msgid "Set the ebook publisher." +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/ebooks/conversion/plumber.py:362 +#: /home/kovid/work/calibre/src/calibre/ebooks/metadata/cli.py:57 +msgid "Set the series this ebook belongs to." +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/ebooks/conversion/plumber.py:366 +#: /home/kovid/work/calibre/src/calibre/ebooks/metadata/cli.py:59 +msgid "Set the index of the book in this series." +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/ebooks/conversion/plumber.py:370 +#: /home/kovid/work/calibre/src/calibre/ebooks/metadata/cli.py:61 +msgid "Set the rating. Should be a number between 1 and 5." +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/ebooks/conversion/plumber.py:374 +#: /home/kovid/work/calibre/src/calibre/ebooks/metadata/cli.py:63 +msgid "Set the ISBN of the book." +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/ebooks/conversion/plumber.py:378 +#: /home/kovid/work/calibre/src/calibre/ebooks/metadata/cli.py:65 +msgid "Set the tags for the book. Should be a comma separated list." +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/ebooks/conversion/plumber.py:382 +#: /home/kovid/work/calibre/src/calibre/ebooks/metadata/cli.py:67 +msgid "Set the book producer." +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/ebooks/conversion/plumber.py:386 +#: /home/kovid/work/calibre/src/calibre/ebooks/metadata/cli.py:69 +msgid "Set the language." +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/ebooks/conversion/plumber.py:389 +msgid "List available recipes." +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/ebooks/conversion/plumber.py:458 +msgid "Could not find an ebook inside the archive" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/ebooks/conversion/plumber.py:585 +msgid "Converting input to HTML..." +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/ebooks/conversion/plumber.py:598 +msgid "Running transforms on ebook..." +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/ebooks/conversion/plumber.py:667 +msgid "Creating" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/ebooks/epub/output.py:29 +msgid "Extract the contents of the generated EPUB file to the specified directory. The contents of the directory are first deleted, so be careful." +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/ebooks/epub/output.py:35 +msgid "Turn off splitting at page breaks. Normally, input files are automatically split at every page break into two files. This gives an output ebook that can be parsed faster and with less resources. However, splitting is slow and if your source file contains a very large number of page breaks, you should turn off splitting on page breaks." +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/ebooks/epub/output.py:46 +msgid "Split all HTML files larger than this size (in KB). This is necessary as most EPUB readers cannot handle large file sizes. The default of %defaultKB is the size required for Adobe Digital Editions." +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/ebooks/html/input.py:241 +msgid "Traverse links in HTML files breadth first. Normally, they are traversed depth first." +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/ebooks/html/input.py:248 +msgid "Maximum levels of recursion when following links in HTML files. Must be non-negative. 0 implies that no links in the root HTML file are followed. Default is %default." +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/ebooks/html/input.py:257 +msgid "Normally this input plugin re-arranges all the input files into a standard folder hierarchy. Only use this option if you know what you are doing as it can result in various nasty side effects in the rest of of the conversion pipeline." +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/ebooks/lit/from_any.py:47 +msgid "Creating LIT file from EPUB..." msgstr "" #: /home/kovid/work/calibre/src/calibre/ebooks/lrf/html/convert_from.py:321 @@ -988,55 +916,32 @@ msgid "" "%s" msgstr "" -#: /home/kovid/work/calibre/src/calibre/ebooks/lrf/html/convert_from.py:1772 +#: /home/kovid/work/calibre/src/calibre/ebooks/lrf/html/convert_from.py:1773 msgid "An error occurred while processing a table: %s. Ignoring table markup." msgstr "" -#: /home/kovid/work/calibre/src/calibre/ebooks/lrf/html/convert_from.py:1774 +#: /home/kovid/work/calibre/src/calibre/ebooks/lrf/html/convert_from.py:1775 msgid "" "Bad table:\n" "%s" msgstr "" -#: /home/kovid/work/calibre/src/calibre/ebooks/lrf/html/convert_from.py:1796 +#: /home/kovid/work/calibre/src/calibre/ebooks/lrf/html/convert_from.py:1797 msgid "Table has cell that is too large" msgstr "" -#: /home/kovid/work/calibre/src/calibre/ebooks/lrf/html/convert_from.py:1826 -msgid "You have to save the website %s as an html file first and then run html2lrf on it." -msgstr "" - -#: /home/kovid/work/calibre/src/calibre/ebooks/lrf/html/convert_from.py:1869 +#: /home/kovid/work/calibre/src/calibre/ebooks/lrf/html/convert_from.py:1863 msgid "Could not read cover image: %s" msgstr "" -#: /home/kovid/work/calibre/src/calibre/ebooks/lrf/html/convert_from.py:1872 +#: /home/kovid/work/calibre/src/calibre/ebooks/lrf/html/convert_from.py:1866 msgid "Cannot read from: %s" msgstr "" -#: /home/kovid/work/calibre/src/calibre/ebooks/lrf/html/convert_from.py:1997 +#: /home/kovid/work/calibre/src/calibre/ebooks/lrf/html/convert_from.py:1990 msgid "Failed to process opf file" msgstr "" -#: /home/kovid/work/calibre/src/calibre/ebooks/lrf/html/convert_from.py:2003 -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 "" - -#: /home/kovid/work/calibre/src/calibre/ebooks/lrf/lit/convert_from.py:15 -msgid "" -"Usage: %prog [options] mybook.lit\n" -"\n" -"\n" -"%prog converts mybook.lit to mybook.lrf" -msgstr "" - #: /home/kovid/work/calibre/src/calibre/ebooks/lrf/lrfparser.py:136 msgid "" "%prog book.lrf\n" @@ -1086,11 +991,11 @@ msgstr "" msgid "Convert LRS to LRS, useful for debugging." msgstr "" -#: /home/kovid/work/calibre/src/calibre/ebooks/lrf/meta.py:455 +#: /home/kovid/work/calibre/src/calibre/ebooks/lrf/meta.py:458 msgid "Invalid LRF file. Could not set metadata." msgstr "" -#: /home/kovid/work/calibre/src/calibre/ebooks/lrf/meta.py:580 +#: /home/kovid/work/calibre/src/calibre/ebooks/lrf/meta.py:583 msgid "" "%prog [options] mybook.lrf\n" "\n" @@ -1099,221 +1004,256 @@ msgid "" "\n" msgstr "" -#: /home/kovid/work/calibre/src/calibre/ebooks/lrf/meta.py:587 -#: /home/kovid/work/calibre/src/calibre/ebooks/metadata/__init__.py:42 +#: /home/kovid/work/calibre/src/calibre/ebooks/lrf/meta.py:590 msgid "Set the book title" msgstr "" -#: /home/kovid/work/calibre/src/calibre/ebooks/lrf/meta.py:589 +#: /home/kovid/work/calibre/src/calibre/ebooks/lrf/meta.py:592 msgid "Set sort key for the title" msgstr "" -#: /home/kovid/work/calibre/src/calibre/ebooks/lrf/meta.py:591 +#: /home/kovid/work/calibre/src/calibre/ebooks/lrf/meta.py:594 msgid "Set the author" msgstr "" -#: /home/kovid/work/calibre/src/calibre/ebooks/lrf/meta.py:593 +#: /home/kovid/work/calibre/src/calibre/ebooks/lrf/meta.py:596 msgid "Set sort key for the author" msgstr "" -#: /home/kovid/work/calibre/src/calibre/ebooks/lrf/meta.py:595 -#: /home/kovid/work/calibre/src/calibre/ebooks/metadata/__init__.py:46 +#: /home/kovid/work/calibre/src/calibre/ebooks/lrf/meta.py:598 msgid "The category this book belongs to. E.g.: History" msgstr "" -#: /home/kovid/work/calibre/src/calibre/ebooks/lrf/meta.py:598 +#: /home/kovid/work/calibre/src/calibre/ebooks/lrf/meta.py:601 msgid "Path to a graphic that will be set as this files' thumbnail" msgstr "" -#: /home/kovid/work/calibre/src/calibre/ebooks/lrf/meta.py:601 +#: /home/kovid/work/calibre/src/calibre/ebooks/lrf/meta.py:604 msgid "Path to a txt file containing the comment to be stored in the lrf file." msgstr "" -#: /home/kovid/work/calibre/src/calibre/ebooks/lrf/meta.py:605 +#: /home/kovid/work/calibre/src/calibre/ebooks/lrf/meta.py:608 msgid "Extract thumbnail from LRF file" msgstr "" -#: /home/kovid/work/calibre/src/calibre/ebooks/lrf/meta.py:606 -#: /home/kovid/work/calibre/src/calibre/ebooks/metadata/mobi.py:195 +#: /home/kovid/work/calibre/src/calibre/ebooks/lrf/meta.py:609 msgid "Set the publisher" msgstr "" -#: /home/kovid/work/calibre/src/calibre/ebooks/lrf/meta.py:607 +#: /home/kovid/work/calibre/src/calibre/ebooks/lrf/meta.py:610 msgid "Set the book classification" msgstr "" -#: /home/kovid/work/calibre/src/calibre/ebooks/lrf/meta.py:608 +#: /home/kovid/work/calibre/src/calibre/ebooks/lrf/meta.py:611 msgid "Set the book creator" msgstr "" -#: /home/kovid/work/calibre/src/calibre/ebooks/lrf/meta.py:609 +#: /home/kovid/work/calibre/src/calibre/ebooks/lrf/meta.py:612 msgid "Set the book producer" msgstr "" -#: /home/kovid/work/calibre/src/calibre/ebooks/lrf/meta.py:611 +#: /home/kovid/work/calibre/src/calibre/ebooks/lrf/meta.py:614 msgid "Extract cover from LRF file. Note that the LRF format has no defined cover, so we use some heuristics to guess the cover." msgstr "" -#: /home/kovid/work/calibre/src/calibre/ebooks/lrf/meta.py:613 +#: /home/kovid/work/calibre/src/calibre/ebooks/lrf/meta.py:616 msgid "Set book ID" msgstr "" -#: /home/kovid/work/calibre/src/calibre/ebooks/lrf/mobi/convert_from.py:43 -msgid "" -"Usage: %prog [options] mybook.mobi|prc\n" -"\n" -"\n" -"%prog converts mybook.mobi to mybook.lrf" +#: /home/kovid/work/calibre/src/calibre/ebooks/lrf/output.py:77 +msgid "Enable autorotation of images that are wider than the screen width." msgstr "" -#: /home/kovid/work/calibre/src/calibre/ebooks/lrf/pdf/convert_from.py:49 -msgid "Could not find pdftohtml, check it is in your PATH" +#: /home/kovid/work/calibre/src/calibre/ebooks/lrf/output.py:81 +msgid "Set the space between words in pts. Default is %default" msgstr "" -#: /home/kovid/work/calibre/src/calibre/ebooks/lrf/pdf/convert_from.py:75 -msgid " is an image based PDF. Only conversion of text based PDFs is supported." +#: /home/kovid/work/calibre/src/calibre/ebooks/lrf/output.py:84 +msgid "Add a header to all the pages with title and author." msgstr "" -#: /home/kovid/work/calibre/src/calibre/ebooks/lrf/pdf/convert_from.py:94 -msgid "" -"%prog [options] mybook.pdf\n" -"\n" -"\n" -"%prog converts mybook.pdf to mybook.lrf" +#: /home/kovid/work/calibre/src/calibre/ebooks/lrf/output.py:87 +msgid "Set the format of the header. %a is replaced by the author and %t by the title. Default is %default" msgstr "" -#: /home/kovid/work/calibre/src/calibre/ebooks/lrf/pdf/reflow.py:403 -msgid "Path to output directory in which to create the HTML file. Defaults to current directory." +#: /home/kovid/work/calibre/src/calibre/ebooks/lrf/output.py:91 +msgid "Add extra spacing below the header. Default is %default pt." msgstr "" -#: /home/kovid/work/calibre/src/calibre/ebooks/lrf/pdf/reflow.py:405 -msgid "Be more verbose." +#: /home/kovid/work/calibre/src/calibre/ebooks/lrf/output.py:94 +msgid "Minimum paragraph indent (the indent of the first line of a paragraph) in pts. Default: %default" msgstr "" -#: /home/kovid/work/calibre/src/calibre/ebooks/lrf/pdf/reflow.py:417 -msgid "You must specify a single PDF file." +#: /home/kovid/work/calibre/src/calibre/ebooks/lrf/output.py:99 +msgid "Render tables in the HTML as images (useful if the document has large or complex tables)" msgstr "" -#: /home/kovid/work/calibre/src/calibre/ebooks/lrf/rtf/convert_from.py:21 -msgid "" -"%prog [options] mybook.rtf\n" -"\n" -"\n" -"%prog converts mybook.rtf to mybook.lrf" +#: /home/kovid/work/calibre/src/calibre/ebooks/lrf/output.py:104 +msgid "Multiply the size of text in rendered tables by this factor. Default is %default" msgstr "" -#: /home/kovid/work/calibre/src/calibre/ebooks/lrf/rtf/convert_from.py:146 -msgid "This RTF file has a feature calibre does not support. Convert it to HTML and then convert it." +#: /home/kovid/work/calibre/src/calibre/ebooks/lrf/output.py:108 +msgid "The serif family of fonts to embed" msgstr "" -#: /home/kovid/work/calibre/src/calibre/ebooks/lrf/txt/convert_from.py:19 -msgid "" -"%prog [options] mybook.txt\n" -"\n" -"\n" -"%prog converts mybook.txt to mybook.lrf" +#: /home/kovid/work/calibre/src/calibre/ebooks/lrf/output.py:111 +msgid "The sans-serif family of fonts to embed" msgstr "" -#: /home/kovid/work/calibre/src/calibre/ebooks/metadata/__init__.py:44 -msgid "Set the authors" +#: /home/kovid/work/calibre/src/calibre/ebooks/lrf/output.py:114 +msgid "The monospace family of fonts to embed" msgstr "" -#: /home/kovid/work/calibre/src/calibre/ebooks/metadata/__init__.py:48 -msgid "Set the comment" +#: /home/kovid/work/calibre/src/calibre/ebooks/lrf/output.py:139 +msgid "Comic" msgstr "" -#: /home/kovid/work/calibre/src/calibre/ebooks/metadata/__init__.py:300 +#: /home/kovid/work/calibre/src/calibre/ebooks/metadata/__init__.py:340 +#: /home/kovid/work/calibre/src/calibre/ebooks/pdf/manipulate/info.py:45 #: /home/kovid/work/calibre/src/calibre/gui2/dialogs/book_info.py:69 #: /home/kovid/work/calibre/src/calibre/gui2/dialogs/book_info.py:70 #: /home/kovid/work/calibre/src/calibre/gui2/dialogs/fetch_metadata.py:55 -#: /home/kovid/work/calibre/src/calibre/gui2/library.py:108 -#: /home/kovid/work/calibre/src/calibre/gui2/library.py:361 -#: /home/kovid/work/calibre/src/calibre/gui2/library.py:971 +#: /home/kovid/work/calibre/src/calibre/gui2/library.py:116 +#: /home/kovid/work/calibre/src/calibre/gui2/library.py:353 +#: /home/kovid/work/calibre/src/calibre/gui2/library.py:998 msgid "Title" msgstr "" -#: /home/kovid/work/calibre/src/calibre/ebooks/metadata/__init__.py:301 +#: /home/kovid/work/calibre/src/calibre/ebooks/metadata/__init__.py:341 #: /home/kovid/work/calibre/src/calibre/gui2/dialogs/fetch_metadata.py:56 -#: /home/kovid/work/calibre/src/calibre/gui2/library.py:109 -#: /home/kovid/work/calibre/src/calibre/gui2/library.py:366 -#: /home/kovid/work/calibre/src/calibre/gui2/library.py:972 +#: /home/kovid/work/calibre/src/calibre/gui2/library.py:117 +#: /home/kovid/work/calibre/src/calibre/gui2/library.py:358 +#: /home/kovid/work/calibre/src/calibre/gui2/library.py:999 msgid "Author(s)" msgstr "" -#: /home/kovid/work/calibre/src/calibre/ebooks/metadata/__init__.py:303 +#: /home/kovid/work/calibre/src/calibre/ebooks/metadata/__init__.py:342 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/fetch_metadata.py:58 +#: /home/kovid/work/calibre/src/calibre/gui2/library.py:122 +msgid "Publisher" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/ebooks/metadata/__init__.py:343 +#: /home/kovid/work/calibre/src/calibre/ebooks/pdf/manipulate/info.py:49 msgid "Producer" msgstr "" -#: /home/kovid/work/calibre/src/calibre/ebooks/metadata/__init__.py:304 +#: /home/kovid/work/calibre/src/calibre/ebooks/metadata/__init__.py:344 +#: /home/kovid/work/calibre/src/calibre/gui2/convert/metadata_ui.py:178 #: /home/kovid/work/calibre/src/calibre/gui2/dialogs/book_info.py:71 #: /home/kovid/work/calibre/src/calibre/gui2/dialogs/book_info_ui.py:64 #: /home/kovid/work/calibre/src/calibre/gui2/dialogs/epub_ui.py:489 #: /home/kovid/work/calibre/src/calibre/gui2/dialogs/lrf_single_ui.py:517 -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single_ui.py:353 -#: /home/kovid/work/calibre/src/calibre/gui2/library.py:322 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single_ui.py:349 +#: /home/kovid/work/calibre/src/calibre/gui2/library.py:314 #: /home/kovid/work/calibre/src/calibre/gui2/status.py:58 msgid "Comments" msgstr "" -#: /home/kovid/work/calibre/src/calibre/ebooks/metadata/__init__.py:312 -#: /home/kovid/work/calibre/src/calibre/gui2/library.py:114 -#: /home/kovid/work/calibre/src/calibre/gui2/library.py:311 -#: /home/kovid/work/calibre/src/calibre/gui2/library.py:915 -#: /home/kovid/work/calibre/src/calibre/gui2/library.py:975 +#: /home/kovid/work/calibre/src/calibre/ebooks/metadata/__init__.py:352 +#: /home/kovid/work/calibre/src/calibre/gui2/library.py:123 +#: /home/kovid/work/calibre/src/calibre/gui2/library.py:303 +#: /home/kovid/work/calibre/src/calibre/gui2/library.py:942 +#: /home/kovid/work/calibre/src/calibre/gui2/library.py:1002 #: /home/kovid/work/calibre/src/calibre/gui2/status.py:60 #: /home/kovid/work/calibre/src/calibre/gui2/tags.py:50 msgid "Tags" msgstr "" -#: /home/kovid/work/calibre/src/calibre/ebooks/metadata/__init__.py:314 -#: /home/kovid/work/calibre/src/calibre/gui2/library.py:115 -#: /home/kovid/work/calibre/src/calibre/gui2/library.py:327 +#: /home/kovid/work/calibre/src/calibre/ebooks/metadata/__init__.py:354 +#: /home/kovid/work/calibre/src/calibre/gui2/library.py:124 +#: /home/kovid/work/calibre/src/calibre/gui2/library.py:319 #: /home/kovid/work/calibre/src/calibre/gui2/status.py:59 #: /home/kovid/work/calibre/src/calibre/gui2/tags.py:50 msgid "Series" msgstr "" -#: /home/kovid/work/calibre/src/calibre/ebooks/metadata/__init__.py:315 +#: /home/kovid/work/calibre/src/calibre/ebooks/metadata/__init__.py:355 msgid "Language" msgstr "" -#: /home/kovid/work/calibre/src/calibre/ebooks/metadata/__init__.py:317 -#: /home/kovid/work/calibre/src/calibre/gui2/library.py:914 +#: /home/kovid/work/calibre/src/calibre/ebooks/metadata/__init__.py:357 +#: /home/kovid/work/calibre/src/calibre/gui2/library.py:941 msgid "Timestamp" msgstr "" -#: /home/kovid/work/calibre/src/calibre/ebooks/metadata/epub.py:204 -msgid "A comma separated list of tags to set" +#: /home/kovid/work/calibre/src/calibre/ebooks/metadata/__init__.py:359 +#: /home/kovid/work/calibre/src/calibre/gui2/library.py:120 +msgid "Published" msgstr "" -#: /home/kovid/work/calibre/src/calibre/ebooks/metadata/epub.py:206 -msgid "The series to which this book belongs" +#: /home/kovid/work/calibre/src/calibre/ebooks/metadata/cli.py:9 +msgid "options" msgstr "" -#: /home/kovid/work/calibre/src/calibre/ebooks/metadata/epub.py:208 -msgid "The series index" +#: /home/kovid/work/calibre/src/calibre/ebooks/metadata/cli.py:10 +msgid "" +"\n" +"Read/Write metadata from/to ebook files.\n" +"\n" +"Supported formats for reading metadata: %s\n" +"\n" +"Supported formats for writing metadata: %s\n" +"\n" +"Different file types support different kinds of metadata. If you try to set\n" +"some metadata on a file type that does not support it, the metadata will be\n" +"silently ignored.\n" msgstr "" -#: /home/kovid/work/calibre/src/calibre/ebooks/metadata/epub.py:210 -msgid "The book language" +#: /home/kovid/work/calibre/src/calibre/ebooks/metadata/cli.py:37 +msgid "Set the authors. Multiple authors should be separated by the & character. Author names should be in the order Firstname Lastname." msgstr "" -#: /home/kovid/work/calibre/src/calibre/ebooks/metadata/epub.py:212 -msgid "Extract the cover" +#: /home/kovid/work/calibre/src/calibre/ebooks/metadata/cli.py:41 +msgid "The version of the title to be used for sorting. If unspecified, and the title is specified, it will be auto-generated from the title." msgstr "" -#: /home/kovid/work/calibre/src/calibre/ebooks/metadata/fb2.py:54 -msgid "Usage:" +#: /home/kovid/work/calibre/src/calibre/ebooks/metadata/cli.py:45 +msgid "String to be used when sorting by author. If unspecified, and the author(s) are specified, it will be auto-generated from the author(s)." msgstr "" -#: /home/kovid/work/calibre/src/calibre/ebooks/metadata/imp.py:53 -msgid "Usage: imp-meta file.imp" +#: /home/kovid/work/calibre/src/calibre/ebooks/metadata/cli.py:55 +msgid "Set the book category." msgstr "" -#: /home/kovid/work/calibre/src/calibre/ebooks/metadata/imp.py:54 -#: /home/kovid/work/calibre/src/calibre/ebooks/metadata/pdf.py:116 -#: /home/kovid/work/calibre/src/calibre/ebooks/metadata/rb.py:60 -msgid "No filename specified." +#: /home/kovid/work/calibre/src/calibre/ebooks/metadata/cli.py:72 +msgid "Get the cover from the ebook and save it at as the specified file." +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/ebooks/metadata/cli.py:75 +msgid "Specify the name of an OPF file. The metadata will be written to the OPF file." +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/ebooks/metadata/cli.py:78 +msgid "Read metadata from the specified OPF file and use it to set metadata in the ebook. Metadata specified on thecommand line will override metadata read from the OPF file" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/ebooks/metadata/cli.py:83 +msgid "Set the BookID in LRF files" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/ebooks/metadata/cli.py:141 +msgid "No file specified" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/ebooks/metadata/cli.py:156 +msgid "Original metadata" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/ebooks/metadata/cli.py:173 +msgid "Changed metadata" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/ebooks/metadata/cli.py:185 +msgid "OPF created in" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/ebooks/metadata/cli.py:191 +msgid "Cover saved to" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/ebooks/metadata/cli.py:193 +msgid "No cover found" msgstr "" #: /home/kovid/work/calibre/src/calibre/ebooks/metadata/isbndb.py:98 @@ -1369,528 +1309,998 @@ msgid "" "Fetch a cover image for the book identified by ISBN from LibraryThing.com\n" msgstr "" -#: /home/kovid/work/calibre/src/calibre/ebooks/metadata/lit.py:43 -msgid "Usage: %s file.lit" -msgstr "" - -#: /home/kovid/work/calibre/src/calibre/ebooks/metadata/lit.py:53 -#: /home/kovid/work/calibre/src/calibre/ebooks/metadata/mobi.py:240 -msgid "Cover saved to" -msgstr "" - -#: /home/kovid/work/calibre/src/calibre/ebooks/metadata/mobi.py:191 -msgid "Set the subject tags" -msgstr "" - -#: /home/kovid/work/calibre/src/calibre/ebooks/metadata/mobi.py:193 -msgid "Set the language" -msgstr "" - -#: /home/kovid/work/calibre/src/calibre/ebooks/metadata/mobi.py:197 -msgid "Set the ISBN" -msgstr "" - -#: /home/kovid/work/calibre/src/calibre/ebooks/metadata/opf2.py:1026 -msgid "Set the dc:language field" -msgstr "" - -#: /home/kovid/work/calibre/src/calibre/ebooks/metadata/pdf.py:115 -msgid "Usage: pdf-meta file.pdf" -msgstr "" - -#: /home/kovid/work/calibre/src/calibre/ebooks/metadata/rb.py:59 -msgid "Usage: rb-meta file.rb" -msgstr "" - -#: /home/kovid/work/calibre/src/calibre/ebooks/mobi/from_any.py:55 -msgid "Creating Mobipocket file from EPUB..." -msgstr "" - -#: /home/kovid/work/calibre/src/calibre/ebooks/mobi/reader.py:695 -msgid "%prog [options] myebook.mobi" -msgstr "" - -#: /home/kovid/work/calibre/src/calibre/ebooks/mobi/reader.py:719 -msgid "Raw MOBI HTML saved in" -msgstr "" - -#: /home/kovid/work/calibre/src/calibre/ebooks/mobi/writer.py:536 -msgid "Options to control the conversion to MOBI" -msgstr "" - -#: /home/kovid/work/calibre/src/calibre/ebooks/mobi/writer.py:543 -msgid "Mobipocket-specific options." -msgstr "" - -#: /home/kovid/work/calibre/src/calibre/ebooks/mobi/writer.py:545 -msgid "Compress file text using PalmDOC compression. Results in smaller files, but takes a long time to run." -msgstr "" - -#: /home/kovid/work/calibre/src/calibre/ebooks/mobi/writer.py:548 +#: /home/kovid/work/calibre/src/calibre/ebooks/mobi/output.py:21 msgid "Modify images to meet Palm device size limitations." msgstr "" -#: /home/kovid/work/calibre/src/calibre/ebooks/mobi/writer.py:550 +#: /home/kovid/work/calibre/src/calibre/ebooks/mobi/output.py:25 +msgid "When present, use author sort field as author." +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/ebooks/mobi/output.py:28 +#: /home/kovid/work/calibre/src/calibre/ebooks/oeb/transforms/htmltoc.py:56 msgid "Title for any generated in-line table of contents." msgstr "" -#: /home/kovid/work/calibre/src/calibre/ebooks/mobi/writer.py:556 -msgid "When present, use the author sorting information for generating the Mobipocket author metadata." +#: /home/kovid/work/calibre/src/calibre/ebooks/mobi/output.py:32 +msgid "When present, generate a periodical rather than a book." msgstr "" -#: /home/kovid/work/calibre/src/calibre/ebooks/mobi/writer.py:558 -msgid "Device renderer profiles. Affects conversion of font sizes, image rescaling and rasterization of tables. Valid profiles are: %s." +#: /home/kovid/work/calibre/src/calibre/ebooks/mobi/output.py:36 +msgid "Disable generation of MOBI index." msgstr "" -#: /home/kovid/work/calibre/src/calibre/ebooks/mobi/writer.py:563 -msgid "Source renderer profile. Default is %default." +#: /home/kovid/work/calibre/src/calibre/ebooks/mobi/output.py:40 +msgid "Disable compression of the file contents." msgstr "" -#: /home/kovid/work/calibre/src/calibre/ebooks/mobi/writer.py:566 -msgid "Destination renderer profile. Default is %default." -msgstr "" - -#: /home/kovid/work/calibre/src/calibre/ebooks/mobi/writer.py:574 -msgid "[options]" -msgstr "" - -#: /home/kovid/work/calibre/src/calibre/ebooks/mobi/writer.py:592 -msgid "Unknown source profile %r" -msgstr "" - -#: /home/kovid/work/calibre/src/calibre/ebooks/mobi/writer.py:596 -msgid "Unknown destination profile %r" -msgstr "" - -#: /home/kovid/work/calibre/src/calibre/ebooks/odt/to_oeb.py:57 -msgid "The output directory. Defaults to the current directory." -msgstr "" - -#: /home/kovid/work/calibre/src/calibre/ebooks/oeb/base.py:829 +#: /home/kovid/work/calibre/src/calibre/ebooks/oeb/base.py:1164 msgid "Cover" msgstr "" -#: /home/kovid/work/calibre/src/calibre/ebooks/oeb/base.py:830 +#: /home/kovid/work/calibre/src/calibre/ebooks/oeb/base.py:1165 msgid "Title Page" msgstr "" -#: /home/kovid/work/calibre/src/calibre/ebooks/oeb/base.py:831 -#: /home/kovid/work/calibre/src/calibre/ebooks/oeb/transforms/htmltoc.py:18 -#: /home/kovid/work/calibre/src/calibre/gui2/viewer/main.py:47 -#: /home/kovid/work/calibre/src/calibre/gui2/viewer/main_ui.py:160 +#: /home/kovid/work/calibre/src/calibre/ebooks/oeb/base.py:1166 +#: /home/kovid/work/calibre/src/calibre/ebooks/oeb/transforms/htmltoc.py:15 +#: /home/kovid/work/calibre/src/calibre/gui2/viewer/main.py:48 +#: /home/kovid/work/calibre/src/calibre/gui2/viewer/main_ui.py:167 msgid "Table of Contents" msgstr "" -#: /home/kovid/work/calibre/src/calibre/ebooks/oeb/base.py:832 +#: /home/kovid/work/calibre/src/calibre/ebooks/oeb/base.py:1167 msgid "Index" msgstr "" -#: /home/kovid/work/calibre/src/calibre/ebooks/oeb/base.py:833 +#: /home/kovid/work/calibre/src/calibre/ebooks/oeb/base.py:1168 msgid "Glossary" msgstr "" -#: /home/kovid/work/calibre/src/calibre/ebooks/oeb/base.py:834 +#: /home/kovid/work/calibre/src/calibre/ebooks/oeb/base.py:1169 msgid "Acknowledgements" msgstr "" -#: /home/kovid/work/calibre/src/calibre/ebooks/oeb/base.py:835 +#: /home/kovid/work/calibre/src/calibre/ebooks/oeb/base.py:1170 msgid "Bibliography" msgstr "" -#: /home/kovid/work/calibre/src/calibre/ebooks/oeb/base.py:836 +#: /home/kovid/work/calibre/src/calibre/ebooks/oeb/base.py:1171 msgid "Colophon" msgstr "" -#: /home/kovid/work/calibre/src/calibre/ebooks/oeb/base.py:837 +#: /home/kovid/work/calibre/src/calibre/ebooks/oeb/base.py:1172 msgid "Copyright" msgstr "" -#: /home/kovid/work/calibre/src/calibre/ebooks/oeb/base.py:838 +#: /home/kovid/work/calibre/src/calibre/ebooks/oeb/base.py:1173 msgid "Dedication" msgstr "" -#: /home/kovid/work/calibre/src/calibre/ebooks/oeb/base.py:839 +#: /home/kovid/work/calibre/src/calibre/ebooks/oeb/base.py:1174 msgid "Epigraph" msgstr "" -#: /home/kovid/work/calibre/src/calibre/ebooks/oeb/base.py:840 +#: /home/kovid/work/calibre/src/calibre/ebooks/oeb/base.py:1175 msgid "Foreword" msgstr "" -#: /home/kovid/work/calibre/src/calibre/ebooks/oeb/base.py:841 +#: /home/kovid/work/calibre/src/calibre/ebooks/oeb/base.py:1176 msgid "List of Illustrations" msgstr "" -#: /home/kovid/work/calibre/src/calibre/ebooks/oeb/base.py:842 +#: /home/kovid/work/calibre/src/calibre/ebooks/oeb/base.py:1177 msgid "List of Tables" msgstr "" -#: /home/kovid/work/calibre/src/calibre/ebooks/oeb/base.py:843 +#: /home/kovid/work/calibre/src/calibre/ebooks/oeb/base.py:1178 msgid "Notes" msgstr "" -#: /home/kovid/work/calibre/src/calibre/ebooks/oeb/base.py:844 +#: /home/kovid/work/calibre/src/calibre/ebooks/oeb/base.py:1179 msgid "Preface" msgstr "" -#: /home/kovid/work/calibre/src/calibre/ebooks/oeb/base.py:845 +#: /home/kovid/work/calibre/src/calibre/ebooks/oeb/base.py:1180 msgid "Main Text" msgstr "" -#: /home/kovid/work/calibre/src/calibre/ebooks/pdf/pdftrim.py:13 +#: /home/kovid/work/calibre/src/calibre/ebooks/oeb/factory.py:53 +msgid "Options to control e-book conversion." +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/ebooks/oeb/factory.py:60 +msgid "Character encoding for input. Default is to auto detect." +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/ebooks/oeb/factory.py:62 +msgid "Output file. Default is derived from input filename." +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/ebooks/oeb/factory.py:64 +msgid "Produce more human-readable XML output." +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/ebooks/oeb/factory.py:66 +msgid "Useful for debugging." +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/ebooks/oeb/factory.py:71 +msgid "Usage: ebook-convert INFILE OUTFILE [OPTIONS..]" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/ebooks/oeb/iterator.py:38 +msgid "%s format books are not supported" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/ebooks/oeb/transforms/htmltoc.py:54 +msgid "HTML TOC generation options." +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/ebooks/oeb/transforms/jacket.py:85 +msgid "Book Jacket" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/ebooks/oeb/transforms/split.py:34 +msgid "Could not find reasonable point at which to split: %s Sub-tree size: %d KB" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/ebooks/oeb/writer.py:32 +msgid "OPF/NCX/etc. generation options." +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/ebooks/oeb/writer.py:35 +msgid "OPF version to generate. Default is %default." +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/ebooks/oeb/writer.py:37 +msgid "Generate an Adobe \"page-map\" file if pagination information is avaliable." +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/ebooks/pdb/ereader/reader132.py:112 +msgid "Footnotes" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/ebooks/pdb/ereader/reader132.py:121 +msgid "Sidebar" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/ebooks/pdb/output.py:23 +msgid "Format to use inside the pdb container. Choices are:" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/ebooks/pdf/manipulate/cli.py:31 +msgid "" +"command ...\n" +"\n" +"command can be one of the following:\n" +"[%%commands]\n" +"\n" +"Use %prog command --help to get more information about a specific command\n" +"\n" +"Manipulate a PDF.\n" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/ebooks/pdf/manipulate/crop.py:28 +msgid "" +"[options] file.pdf\n" +"\n" +"Crop a PDF file.\n" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/ebooks/pdf/manipulate/crop.py:37 +#: /home/kovid/work/calibre/src/calibre/ebooks/pdf/manipulate/decrypt.py:34 +#: /home/kovid/work/calibre/src/calibre/ebooks/pdf/manipulate/encrypt.py:32 +#: /home/kovid/work/calibre/src/calibre/ebooks/pdf/manipulate/merge.py:36 +#: /home/kovid/work/calibre/src/calibre/ebooks/pdf/manipulate/reverse.py:34 +#: /home/kovid/work/calibre/src/calibre/ebooks/pdf/manipulate/rotate.py:33 +#: /home/kovid/work/calibre/src/calibre/ebooks/pdf/manipulate/split.py:41 +msgid "Path to output file. By default a file is created in the current directory." +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/ebooks/pdf/manipulate/crop.py:40 +msgid "Number of pixels to crop from the left most x (default is %s)" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/ebooks/pdf/manipulate/crop.py:43 +msgid "Number of pixels to crop from the left most y (default is %s)" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/ebooks/pdf/manipulate/crop.py:46 +msgid "Number of pixels to crop from the right most x (default is %s)" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/ebooks/pdf/manipulate/crop.py:49 +msgid "Number of pixels to crop from the right most y (default is %s)" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/ebooks/pdf/manipulate/crop.py:52 +msgid "A file generated by ghostscript which allows each page to be individually cropped `gs -dSAFER -dNOPAUSE -dBATCH -sDEVICE=bbox file.pdf 2> bounding`" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/ebooks/pdf/manipulate/crop.py:72 +msgid "Crop Options:" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/ebooks/pdf/manipulate/crop.py:72 +#: /home/kovid/work/calibre/src/calibre/ebooks/pdf/manipulate/decrypt.py:62 +#: /home/kovid/work/calibre/src/calibre/ebooks/pdf/manipulate/encrypt.py:52 +#: /home/kovid/work/calibre/src/calibre/ebooks/pdf/manipulate/merge.py:56 +#: /home/kovid/work/calibre/src/calibre/ebooks/pdf/manipulate/reverse.py:54 +#: /home/kovid/work/calibre/src/calibre/ebooks/pdf/manipulate/rotate.py:53 +#: /home/kovid/work/calibre/src/calibre/ebooks/pdf/manipulate/split.py:61 msgid "Options to control the transformation of pdf" msgstr "" -#: /home/kovid/work/calibre/src/calibre/ebooks/pdf/pdftrim.py:24 -msgid "Number of pixels to crop from the left most x (default is %d) " -msgstr "" - -#: /home/kovid/work/calibre/src/calibre/ebooks/pdf/pdftrim.py:26 -msgid "Number of pixels to crop from the left most y (default is %d) " -msgstr "" - -#: /home/kovid/work/calibre/src/calibre/ebooks/pdf/pdftrim.py:28 -msgid "Number of pixels to crop from the right most x (default is %d) " -msgstr "" - -#: /home/kovid/work/calibre/src/calibre/ebooks/pdf/pdftrim.py:30 -msgid "Number of pixels to crop from the right most y (default is %d)" -msgstr "" - -#: /home/kovid/work/calibre/src/calibre/ebooks/pdf/pdftrim.py:32 -msgid "A file generated by ghostscript which allows each page to be individually cropped [gs -dSAFER -dNOPAUSE -dBATCH -sDEVICE=bbox > bounding] " -msgstr "" - -#: /home/kovid/work/calibre/src/calibre/ebooks/pdf/pdftrim.py:38 +#: /home/kovid/work/calibre/src/calibre/ebooks/pdf/manipulate/decrypt.py:25 msgid "" -"\t%prog [options] file.pdf\n" +"[options] file.pdf password\n" "\n" -"\tCrops a pdf. \n" -"\t" +"Decrypt a PDF.\n" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/__init__.py:27 -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/config_ui.py:554 +#: /home/kovid/work/calibre/src/calibre/ebooks/pdf/manipulate/decrypt.py:62 +msgid "Decrypt Options:" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/ebooks/pdf/manipulate/encrypt.py:23 +msgid "" +"[options] file.pdf password\n" +"\n" +"Encrypt a PDF.\n" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/ebooks/pdf/manipulate/encrypt.py:52 +msgid "Encrypt Options:" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/ebooks/pdf/manipulate/info.py:21 +msgid "" +"file.pdf ...\n" +"\n" +"Get info about a PDF.\n" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/ebooks/pdf/manipulate/info.py:46 +msgid "Author" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/ebooks/pdf/manipulate/info.py:47 +msgid "Subject" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/ebooks/pdf/manipulate/info.py:48 +msgid "Creator" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/ebooks/pdf/manipulate/info.py:50 +msgid "Pages" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/ebooks/pdf/manipulate/info.py:51 +msgid "File Size" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/ebooks/pdf/manipulate/info.py:52 +msgid "PDF Version" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/ebooks/pdf/manipulate/merge.py:25 +msgid "" +"[options] file1.pdf file2.pdf ...\n" +"\n" +"Metadata will be used from the first PDF specified.\n" +"\n" +"Merges individual PDFs.\n" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/ebooks/pdf/manipulate/merge.py:56 +msgid "Merge Options:" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/ebooks/pdf/manipulate/reverse.py:25 +msgid "" +"[options] file.pdf\n" +"\n" +"Reverse a PDF.\n" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/ebooks/pdf/manipulate/reverse.py:54 +msgid "Reverse Options:" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/ebooks/pdf/manipulate/rotate.py:24 +msgid "" +"file.pdf degrees\n" +"\n" +"Rotate pages of a PDF clockwise.\n" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/ebooks/pdf/manipulate/rotate.py:53 +msgid "Rotate Options:" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/ebooks/pdf/manipulate/split.py:25 +msgid "" +"\n" +"%prog %%name [options] file.pdf page_to_split_on ...\n" +"%prog %%name [options] file.pdf page_range_to_split_on ...\n" +"\t\n" +"Ex.\n" +"\t\n" +"%prog %%name file.pdf 6\n" +"%prog %%name file.pdf 6-12\n" +"%prog %%name file.pdf 6-12 8 10 9-20\n" +"\n" +"Split a PDF.\n" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/ebooks/pdf/manipulate/split.py:61 +msgid "Split Options:" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/ebooks/pdf/output.py:33 +msgid "The unit of measure. Default is inch. Choices are %s Note: This does not override the unit for margins!" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/ebooks/pdf/output.py:38 +msgid "The size of the paper. This size will be overridden when an output profile is used. Default is letter. Choices are %s" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/ebooks/pdf/output.py:42 +msgid "Custom size of the document. Use the form widthxheight EG. `123x321` to specify the width and height. This overrides any specified paper-size." +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/ebooks/pdf/output.py:47 +msgid "The orientation of the page. Default is portrait. Choices are %s" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/ebooks/pdf/pdftohtml.py:52 +msgid "Could not find pdftohtml, check it is in your PATH" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/ebooks/pdf/pdftohtml.py:75 +msgid " is an image based PDF. Only conversion of text based PDFs is supported." +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/ebooks/rtf/input.py:75 +msgid "This RTF file has a feature calibre does not support. Convert it to HTML first and then try it." +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/ebooks/txt/output.py:23 +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/gui2/__init__.py:28 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/config_ui.py:474 msgid "Frequently used directories" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/__init__.py:29 +#: /home/kovid/work/calibre/src/calibre/gui2/__init__.py:30 msgid "Send file to storage card instead of main memory by default" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/__init__.py:31 -msgid "The format to use when saving single files to disk" -msgstr "" - -#: /home/kovid/work/calibre/src/calibre/gui2/__init__.py:33 +#: /home/kovid/work/calibre/src/calibre/gui2/__init__.py:32 msgid "Confirm before deleting" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/__init__.py:35 +#: /home/kovid/work/calibre/src/calibre/gui2/__init__.py:34 msgid "Toolbar icon size" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/__init__.py:37 +#: /home/kovid/work/calibre/src/calibre/gui2/__init__.py:36 msgid "Show button labels in the toolbar" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/__init__.py:39 +#: /home/kovid/work/calibre/src/calibre/gui2/__init__.py:38 msgid "Main window geometry" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/__init__.py:41 +#: /home/kovid/work/calibre/src/calibre/gui2/__init__.py:40 msgid "Notify when a new version is available" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/__init__.py:43 +#: /home/kovid/work/calibre/src/calibre/gui2/__init__.py:42 msgid "Use Roman numerals for series number" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/__init__.py:45 +#: /home/kovid/work/calibre/src/calibre/gui2/__init__.py:44 msgid "Sort tags list by popularity" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/__init__.py:47 +#: /home/kovid/work/calibre/src/calibre/gui2/__init__.py:46 msgid "Number of covers to show in the cover browsing mode" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/__init__.py:49 +#: /home/kovid/work/calibre/src/calibre/gui2/__init__.py:48 msgid "Defaults for conversion to LRF" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/__init__.py:51 +#: /home/kovid/work/calibre/src/calibre/gui2/__init__.py:50 msgid "Options for the LRF ebook viewer" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/__init__.py:53 +#: /home/kovid/work/calibre/src/calibre/gui2/__init__.py:52 msgid "Formats that are viewed using the internal viewer" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/__init__.py:55 +#: /home/kovid/work/calibre/src/calibre/gui2/__init__.py:54 msgid "Columns to be displayed in the book list" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/__init__.py:56 +#: /home/kovid/work/calibre/src/calibre/gui2/__init__.py:55 msgid "Automatically launch content server on application startup" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/__init__.py:57 +#: /home/kovid/work/calibre/src/calibre/gui2/__init__.py:56 msgid "Oldest news kept in database" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/__init__.py:58 +#: /home/kovid/work/calibre/src/calibre/gui2/__init__.py:57 msgid "Show system tray icon" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/__init__.py:60 +#: /home/kovid/work/calibre/src/calibre/gui2/__init__.py:59 msgid "Upload downloaded news to device" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/__init__.py:62 +#: /home/kovid/work/calibre/src/calibre/gui2/__init__.py:61 msgid "Delete books from library after uploading to device" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/__init__.py:64 +#: /home/kovid/work/calibre/src/calibre/gui2/__init__.py:63 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:66 +#: /home/kovid/work/calibre/src/calibre/gui2/__init__.py:65 msgid "Disable notifications from the system tray icon" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/__init__.py:68 +#: /home/kovid/work/calibre/src/calibre/gui2/__init__.py:67 msgid "Default action to perform when send to device button is clicked" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/add.py:87 -msgid "Added %s to library" -msgstr "" - -#: /home/kovid/work/calibre/src/calibre/gui2/add.py:89 -#: /home/kovid/work/calibre/src/calibre/gui2/add.py:162 -msgid "Read metadata from " -msgstr "" - -#: /home/kovid/work/calibre/src/calibre/gui2/add.py:116 -#: /home/kovid/work/calibre/src/calibre/gui2/add.py:224 -msgid "

Books with the same title as the following already exist in the database. Add them anyway?