From b3cbbd3ea8e05e1cd75b12e70d28ca1decc505d4 Mon Sep 17 00:00:00 2001
From: Charles Haley <>
Date: Thu, 26 Aug 2010 14:32:57 +0100
Subject: [PATCH 001/412] Initial attempt, including full cached metadata, json
serialization, and custom fields in save paths. Custom fields in collections
probably work, but they haven't been tested.
---
src/calibre/devices/apple/driver.py | 7 +-
src/calibre/devices/interface.py | 4 +-
src/calibre/devices/kobo/books.py | 21 +-
src/calibre/devices/usbms/books.py | 43 +--
src/calibre/devices/usbms/driver.py | 21 +-
src/calibre/ebooks/metadata/__init__.py | 216 +------------
src/calibre/ebooks/metadata/book/__init__.py | 41 ++-
src/calibre/ebooks/metadata/book/base.py | 296 +++++++++++++++---
.../ebooks/metadata/book/json_codec.py | 125 ++++++++
src/calibre/ebooks/metadata/isbndb.py | 6 +-
src/calibre/ebooks/metadata/opf2.py | 9 +-
.../gui2/dialogs/config/save_template.py | 29 +-
src/calibre/gui2/library/models.py | 3 +-
src/calibre/library/database2.py | 31 +-
src/calibre/library/save_to_disk.py | 19 +-
15 files changed, 514 insertions(+), 357 deletions(-)
create mode 100644 src/calibre/ebooks/metadata/book/json_codec.py
diff --git a/src/calibre/devices/apple/driver.py b/src/calibre/devices/apple/driver.py
index 916c88f203..75517e9df7 100644
--- a/src/calibre/devices/apple/driver.py
+++ b/src/calibre/devices/apple/driver.py
@@ -13,7 +13,8 @@ from calibre.devices.errors import UserFeedback
from calibre.devices.usbms.deviceconfig import DeviceConfig
from calibre.devices.interface import DevicePlugin
from calibre.ebooks.BeautifulSoup import BeautifulSoup
-from calibre.ebooks.metadata import MetaInformation, authors_to_string
+from calibre.ebooks.metadata import authors_to_string
+from calibre.ebooks.metadata.book.base import Metadata
from calibre.ebooks.metadata.epub import set_metadata
from calibre.library.server.utils import strftime
from calibre.utils.config import config_dir
@@ -2998,14 +2999,14 @@ class BookList(list):
'''
return {}
-class Book(MetaInformation):
+class Book(Metadata):
'''
A simple class describing a book in the iTunes Books Library.
- See ebooks.metadata.__init__ for all fields
'''
def __init__(self,title,author):
- MetaInformation.__init__(self, title, authors=[author])
+ Metadata.__init__(self, title, authors=[author])
@dynamic_property
def title_sorter(self):
diff --git a/src/calibre/devices/interface.py b/src/calibre/devices/interface.py
index 1384fa03d9..7783173c9c 100644
--- a/src/calibre/devices/interface.py
+++ b/src/calibre/devices/interface.py
@@ -316,7 +316,7 @@ class DevicePlugin(Plugin):
being uploaded to the device.
:param names: A list of file names that the books should have
once uploaded to the device. len(names) == len(files)
- :param metadata: If not None, it is a list of :class:`MetaInformation` objects.
+ :param metadata: If not None, it is a list of :class:`Metadata` objects.
The idea is to use the metadata to determine where on the device to
put the book. len(metadata) == len(files). Apart from the regular
cover (path to cover), there may also be a thumbnail attribute, which should
@@ -335,7 +335,7 @@ class DevicePlugin(Plugin):
the device.
:param locations: Result of a call to L{upload_books}
- :param metadata: List of :class:`MetaInformation` objects, same as for
+ :param metadata: List of :class:`Metadata` objects, same as for
:meth:`upload_books`.
:param booklists: A tuple containing the result of calls to
(:meth:`books(oncard=None)`,
diff --git a/src/calibre/devices/kobo/books.py b/src/calibre/devices/kobo/books.py
index a5b2e98d2f..11ab42c29f 100644
--- a/src/calibre/devices/kobo/books.py
+++ b/src/calibre/devices/kobo/books.py
@@ -7,11 +7,11 @@ import os
import re
import time
-from calibre.ebooks.metadata import MetaInformation
+from calibre.ebooks.metadata.book.base import Metadata
from calibre.constants import filesystem_encoding, preferred_encoding
from calibre import isbytestring
-class Book(MetaInformation):
+class Book(Metadata):
BOOK_ATTRS = ['lpath', 'size', 'mime', 'device_collections', '_new_book']
@@ -23,9 +23,9 @@ class Book(MetaInformation):
'uuid',
]
- def __init__(self, prefix, lpath, title, authors, mime, date, ContentType, thumbnail_name, other=None):
-
- MetaInformation.__init__(self, '')
+ def __init__(self, prefix, lpath, title, authors, mime, date, ContentType,
+ thumbnail_name, other=None):
+ Metadata.__init__(self, '')
self.device_collections = []
self._new_book = False
@@ -34,7 +34,7 @@ class Book(MetaInformation):
self.path = self.path.replace('/', '\\')
self.lpath = lpath.replace('\\', '/')
else:
- self.lpath = lpath
+ self.lpath = lpath
self.title = title
if not authors:
@@ -52,10 +52,9 @@ class Book(MetaInformation):
else:
self.datetime = time.gmtime(os.path.getctime(self.path))
except:
- self.datetime = time.gmtime()
-
- if thumbnail_name is not None:
- self.thumbnail = ImageWrapper(thumbnail_name)
+ self.datetime = time.gmtime()
+ if thumbnail_name is not None:
+ self.thumbnail = ImageWrapper(thumbnail_name)
self.tags = []
if other:
self.smart_update(other)
@@ -90,7 +89,7 @@ class Book(MetaInformation):
in C{other} takes precedence, unless the information in C{other} is NULL.
'''
- MetaInformation.smart_update(self, other)
+ Metadata.smart_update(self, other)
for attr in self.BOOK_ATTRS:
if hasattr(other, attr):
diff --git a/src/calibre/devices/usbms/books.py b/src/calibre/devices/usbms/books.py
index 959f26199c..e3c405ee4e 100644
--- a/src/calibre/devices/usbms/books.py
+++ b/src/calibre/devices/usbms/books.py
@@ -6,29 +6,18 @@ __docformat__ = 'restructuredtext en'
import os, re, time, sys
-from calibre.ebooks.metadata import MetaInformation
+from calibre.ebooks.metadata.book.base import Metadata
from calibre.devices.mime import mime_type_ext
from calibre.devices.interface import BookList as _BookList
from calibre.constants import filesystem_encoding, preferred_encoding
from calibre import isbytestring
from calibre.utils.config import prefs
-class Book(MetaInformation):
-
- BOOK_ATTRS = ['lpath', 'size', 'mime', 'device_collections', '_new_book']
-
- JSON_ATTRS = [
- 'lpath', 'title', 'authors', 'mime', 'size', 'tags', 'author_sort',
- 'title_sort', 'comments', 'category', 'publisher', 'series',
- 'series_index', 'rating', 'isbn', 'language', 'application_id',
- 'book_producer', 'lccn', 'lcc', 'ddc', 'rights', 'publication_type',
- 'uuid',
- ]
-
+class Book(Metadata):
def __init__(self, prefix, lpath, size=None, other=None):
from calibre.ebooks.metadata.meta import path_to_ext
- MetaInformation.__init__(self, '')
+ Metadata.__init__(self, '')
self._new_book = False
self.device_collections = []
@@ -72,32 +61,6 @@ class Book(MetaInformation):
def thumbnail(self):
return None
- def smart_update(self, other, replace_metadata=False):
- '''
- Merge the information in C{other} into self. In case of conflicts, the information
- in C{other} takes precedence, unless the information in C{other} is NULL.
- '''
-
- MetaInformation.smart_update(self, other, replace_metadata)
-
- for attr in self.BOOK_ATTRS:
- if hasattr(other, attr):
- val = getattr(other, attr, None)
- setattr(self, attr, val)
-
- def to_json(self):
- json = {}
- for attr in self.JSON_ATTRS:
- val = getattr(self, attr)
- if isbytestring(val):
- enc = filesystem_encoding if attr == 'lpath' else preferred_encoding
- val = val.decode(enc, 'replace')
- elif isinstance(val, (list, tuple)):
- val = [x.decode(preferred_encoding, 'replace') if
- isbytestring(x) else x for x in val]
- json[attr] = val
- return json
-
class BookList(_BookList):
def __init__(self, oncard, prefix, settings):
diff --git a/src/calibre/devices/usbms/driver.py b/src/calibre/devices/usbms/driver.py
index 0d28f06f49..a0d1d9dbf8 100644
--- a/src/calibre/devices/usbms/driver.py
+++ b/src/calibre/devices/usbms/driver.py
@@ -13,7 +13,6 @@ for a particular device.
import os
import re
import time
-import json
from itertools import cycle
from calibre import prints, isbytestring
@@ -21,6 +20,7 @@ from calibre.constants import filesystem_encoding, DEBUG
from calibre.devices.usbms.cli import CLI
from calibre.devices.usbms.device import Device
from calibre.devices.usbms.books import BookList, Book
+from calibre.ebooks.metadata.book.json_codec import JsonCodec
BASE_TIME = None
def debug_print(*args):
@@ -288,6 +288,7 @@ class USBMS(CLI, Device):
# at the end just before the return
def sync_booklists(self, booklists, end_session=True):
debug_print('USBMS: starting sync_booklists')
+ json_codec = JsonCodec()
if not os.path.exists(self.normalize_path(self._main_prefix)):
os.makedirs(self.normalize_path(self._main_prefix))
@@ -296,10 +297,8 @@ class USBMS(CLI, Device):
if prefix is not None and isinstance(booklists[listid], self.booklist_class):
if not os.path.exists(prefix):
os.makedirs(self.normalize_path(prefix))
- js = [item.to_json() for item in booklists[listid] if
- hasattr(item, 'to_json')]
with open(self.normalize_path(os.path.join(prefix, self.METADATA_CACHE)), 'wb') as f:
- f.write(json.dumps(js, indent=2, encoding='utf-8'))
+ json_codec.encode_to_file(f, booklists[listid])
write_prefix(self._main_prefix, 0)
write_prefix(self._card_a_prefix, 1)
write_prefix(self._card_b_prefix, 2)
@@ -345,19 +344,13 @@ class USBMS(CLI, Device):
@classmethod
def parse_metadata_cache(cls, bl, prefix, name):
- # bl = cls.booklist_class()
- js = []
+ json_codec = JsonCodec()
need_sync = False
cache_file = cls.normalize_path(os.path.join(prefix, name))
if os.access(cache_file, os.R_OK):
try:
with open(cache_file, 'rb') as f:
- js = json.load(f, encoding='utf-8')
- for item in js:
- book = cls.book_class(prefix, item.get('lpath', None))
- for key in item.keys():
- setattr(book, key, item[key])
- bl.append(book)
+ json_codec.decode_from_file(f, bl, cls.book_class, prefix)
except:
import traceback
traceback.print_exc()
@@ -392,7 +385,7 @@ class USBMS(CLI, Device):
@classmethod
def book_from_path(cls, prefix, lpath):
- from calibre.ebooks.metadata import MetaInformation
+ from calibre.ebooks.metadata.book.base import Metadata
if cls.settings().read_metadata or cls.MUST_READ_METADATA:
mi = cls.metadata_from_path(cls.normalize_path(os.path.join(prefix, lpath)))
@@ -401,7 +394,7 @@ class USBMS(CLI, Device):
mi = metadata_from_filename(cls.normalize_path(os.path.basename(lpath)),
cls.build_template_regexp())
if mi is None:
- mi = MetaInformation(os.path.splitext(os.path.basename(lpath))[0],
+ mi = Metadata(os.path.splitext(os.path.basename(lpath))[0],
[_('Unknown')])
size = os.stat(cls.normalize_path(os.path.join(prefix, lpath))).st_size
book = cls.book_class(prefix, lpath, other=mi, size=size)
diff --git a/src/calibre/ebooks/metadata/__init__.py b/src/calibre/ebooks/metadata/__init__.py
index d4a21e2c8c..fb894d3bbd 100644
--- a/src/calibre/ebooks/metadata/__init__.py
+++ b/src/calibre/ebooks/metadata/__init__.py
@@ -221,214 +221,18 @@ class ResourceCollection(object):
-class MetaInformation(object):
- '''Convenient encapsulation of book metadata'''
-
- @staticmethod
- def copy(mi):
- ans = MetaInformation(mi.title, mi.authors)
- for attr in ('author_sort', 'title_sort', 'comments', 'category',
- 'publisher', 'series', 'series_index', 'rating',
- 'isbn', 'tags', 'cover_data', 'application_id', 'guide',
- 'manifest', 'spine', 'toc', 'cover', 'language',
- 'book_producer', 'timestamp', 'lccn', 'lcc', 'ddc',
- 'author_sort_map',
- 'pubdate', 'rights', 'publication_type', 'uuid'):
- if hasattr(mi, attr):
- setattr(ans, attr, getattr(mi, attr))
-
- def __init__(self, title, authors=(_('Unknown'),)):
- '''
+def MetaInformation(title, authors=(_('Unknown'),)):
+ ''' Convenient encapsulation of book metadata, needed for compatibility
@param title: title or ``_('Unknown')`` or a MetaInformation object
@param authors: List of strings or []
- '''
- mi = None
- if hasattr(title, 'title') and hasattr(title, 'authors'):
- mi = title
- title = mi.title
- authors = mi.authors
- self.title = title
- self.author = list(authors) if authors else []# Needed for backward compatibility
- #: List of strings or []
- self.authors = list(authors) if authors else []
- self.tags = getattr(mi, 'tags', [])
- #: mi.cover_data = (ext, data)
- self.cover_data = getattr(mi, 'cover_data', (None, None))
- self.author_sort_map = getattr(mi, 'author_sort_map', {})
-
- 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', 'pubdate',
- 'rights', 'publication_type', 'uuid',
- ):
- setattr(self, x, getattr(mi, x, None))
-
- def print_all_attributes(self):
- for x in ('title','author', 'author_sort', 'title_sort', 'comments', 'category', 'publisher',
- 'series', 'series_index', 'tags', 'rating', 'isbn', 'language',
- 'application_id', 'manifest', 'toc', 'spine', 'guide', 'cover',
- 'book_producer', 'timestamp', 'lccn', 'lcc', 'ddc', 'pubdate',
- 'rights', 'publication_type', 'uuid', 'author_sort_map'
- ):
- prints(x, getattr(self, x, 'None'))
-
- def smart_update(self, mi, replace_metadata=False):
- '''
- Merge the information in C{mi} into self. In case of conflicts, the
- information in C{mi} takes precedence, unless the information in mi is
- NULL. If replace_metadata is True, then the information in mi always
- takes precedence.
- '''
- if mi.title and mi.title != _('Unknown'):
- self.title = mi.title
-
- if mi.authors and mi.authors[0] != _('Unknown'):
- self.authors = mi.authors
-
- for attr in ('author_sort', 'title_sort', 'category',
- 'publisher', 'series', 'series_index', 'rating',
- 'isbn', 'application_id', 'manifest', 'spine', 'toc',
- 'cover', 'guide', 'book_producer',
- 'timestamp', 'lccn', 'lcc', 'ddc', 'pubdate', 'rights',
- 'publication_type', 'uuid'):
- if replace_metadata:
- setattr(self, attr, getattr(mi, attr, 1.0 if \
- attr == 'series_index' else None))
- elif hasattr(mi, attr):
- val = getattr(mi, attr)
- if val is not None:
- setattr(self, attr, val)
-
- if replace_metadata:
- self.tags = mi.tags
- elif mi.tags:
- self.tags += mi.tags
- self.tags = list(set(self.tags))
-
- if mi.author_sort_map:
- self.author_sort_map.update(mi.author_sort_map)
-
- if getattr(mi, 'cover_data', False):
- other_cover = mi.cover_data[-1]
- self_cover = self.cover_data[-1] if self.cover_data else ''
- if not self_cover: self_cover = ''
- if not other_cover: other_cover = ''
- if len(other_cover) > len(self_cover):
- self.cover_data = mi.cover_data
-
- if replace_metadata:
- self.comments = getattr(mi, 'comments', '')
- else:
- my_comments = getattr(self, 'comments', '')
- other_comments = getattr(mi, 'comments', '')
- if not my_comments:
- my_comments = ''
- if not other_comments:
- other_comments = ''
- if len(other_comments.strip()) > len(my_comments.strip()):
- self.comments = other_comments
-
- other_lang = getattr(mi, 'language', None)
- if other_lang and other_lang.lower() != 'und':
- self.language = other_lang
-
-
- def format_series_index(self):
- try:
- x = float(self.series_index)
- except ValueError:
- x = 1
- return fmt_sidx(x)
-
- def authors_from_string(self, raw):
- self.authors = string_to_authors(raw)
-
- def format_authors(self):
- return authors_to_string(self.authors)
-
- def format_tags(self):
- return u', '.join([unicode(t) for t in self.tags])
-
- def format_rating(self):
- return unicode(self.rating)
-
- def __unicode__(self):
- ans = []
- def fmt(x, y):
- ans.append(u'%-20s: %s'%(unicode(x), unicode(y)))
-
- fmt('Title', self.title)
- if self.title_sort:
- fmt('Title sort', self.title_sort)
- if self.authors:
- fmt('Author(s)', authors_to_string(self.authors) + \
- ((' [' + self.author_sort + ']') if self.author_sort else ''))
- if self.publisher:
- fmt('Publisher', self.publisher)
- if getattr(self, 'book_producer', False):
- fmt('Book Producer', self.book_producer)
- if self.category:
- fmt('Category', self.category)
- if self.comments:
- fmt('Comments', self.comments)
- if self.isbn:
- fmt('ISBN', self.isbn)
- if self.tags:
- fmt('Tags', u', '.join([unicode(t) for t in self.tags]))
- if self.series:
- fmt('Series', self.series + ' #%s'%self.format_series_index())
- if self.language:
- fmt('Language', self.language)
- if self.rating is not None:
- fmt('Rating', self.rating)
- if self.timestamp is not None:
- fmt('Timestamp', isoformat(self.timestamp))
- if self.pubdate is not None:
- fmt('Published', isoformat(self.pubdate))
- if self.rights is not None:
- fmt('Rights', unicode(self.rights))
- if self.lccn:
- fmt('LCCN', unicode(self.lccn))
- if self.lcc:
- fmt('LCC', unicode(self.lcc))
- if self.ddc:
- fmt('DDC', unicode(self.ddc))
-
- return u'\n'.join(ans)
-
- def to_html(self):
- ans = [(_('Title'), unicode(self.title))]
- ans += [(_('Author(s)'), (authors_to_string(self.authors) if self.authors else _('Unknown')))]
- ans += [(_('Publisher'), unicode(self.publisher))]
- ans += [(_('Producer'), unicode(self.book_producer))]
- ans += [(_('Comments'), unicode(self.comments))]
- ans += [('ISBN', unicode(self.isbn))]
- if self.lccn:
- ans += [('LCCN', unicode(self.lccn))]
- if self.lcc:
- ans += [('LCC', unicode(self.lcc))]
- if self.ddc:
- ans += [('DDC', unicode(self.ddc))]
- ans += [(_('Tags'), u', '.join([unicode(t) for t in self.tags]))]
- if self.series:
- ans += [(_('Series'), unicode(self.series)+ ' #%s'%self.format_series_index())]
- 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(' ')))]
- if self.rights is not None:
- ans += [(_('Rights'), unicode(self.rights))]
- for i, x in enumerate(ans):
- ans[i] = u'
%s
%s
'%x
- return u'
%s
'%u'\n'.join(ans)
-
- def __str__(self):
- return self.__unicode__().encode('utf-8')
-
- def __nonzero__(self):
- return bool(self.title or self.author or self.comments or self.tags)
+ '''
+ from calibre.ebooks.metadata.book.base import Metadata
+ mi = None
+ if hasattr(title, 'title') and hasattr(title, 'authors'):
+ mi = title
+ title = mi.title
+ authors = mi.authors
+ return Metadata(title, authors, mi)
def check_isbn10(isbn):
try:
diff --git a/src/calibre/ebooks/metadata/book/__init__.py b/src/calibre/ebooks/metadata/book/__init__.py
index c3b95f1188..9de7ca1c6b 100644
--- a/src/calibre/ebooks/metadata/book/__init__.py
+++ b/src/calibre/ebooks/metadata/book/__init__.py
@@ -24,6 +24,8 @@ SOCIAL_METADATA_FIELDS = frozenset([
# For example: {'isbn':'123456789', 'doi':'xxxx', ... }
'classifiers',
'isbn', # Pseudo field for convenience, should get/set isbn classifier
+ # TODO: not sure what this is, but it is used by OPF
+ 'category',
])
@@ -69,7 +71,8 @@ BOOK_STRUCTURE_FIELDS = frozenset([
])
USER_METADATA_FIELDS = frozenset([
- # A dict of a form to be specified
+ # A dict of dicts similar to field_metadata. Each field description dict
+ # also contains a value field with the key #value#.
'user_metadata',
])
@@ -86,16 +89,42 @@ DEVICE_METADATA_FIELDS = frozenset([
CALIBRE_METADATA_FIELDS = frozenset([
# An application id
# Semantics to be defined. Is it a db key? a db name + key? A uuid?
+ # (It is currently set to the db_id.)
'application_id',
+ # the calibre primary key of the item. May want to remove this once Sony's no longer use it
+ 'db_id',
]
)
+ALL_METADATA_FIELDS = SOCIAL_METADATA_FIELDS.union(
+ PUBLICATION_METADATA_FIELDS).union(
+ BOOK_STRUCTURE_FIELDS).union(
+ USER_METADATA_FIELDS).union(
+ DEVICE_METADATA_FIELDS).union(
+ CALIBRE_METADATA_FIELDS)
-SERIALIZABLE_FIELDS = SOCIAL_METADATA_FIELDS.union(
- USER_METADATA_FIELDS).union(
- PUBLICATION_METADATA_FIELDS).union(
- CALIBRE_METADATA_FIELDS).union(
- frozenset(['lpath'])) # I don't think we need device_collections
+# All fields except custom fields
+STANDARD_METADATA_FIELDS = SOCIAL_METADATA_FIELDS.union(
+ PUBLICATION_METADATA_FIELDS).union(
+ BOOK_STRUCTURE_FIELDS).union(
+ DEVICE_METADATA_FIELDS).union(
+ CALIBRE_METADATA_FIELDS)
+
+# Metadata fields that smart update should copy without special handling
+COPYABLE_METADATA_FIELDS = SOCIAL_METADATA_FIELDS.union(
+ PUBLICATION_METADATA_FIELDS).union(
+ BOOK_STRUCTURE_FIELDS).union(
+ DEVICE_METADATA_FIELDS).union(
+ CALIBRE_METADATA_FIELDS) - \
+ frozenset(['title', 'authors', 'comments', 'cover_data'])
+
+SERIALIZABLE_FIELDS = SOCIAL_METADATA_FIELDS.union(
+ USER_METADATA_FIELDS).union(
+ PUBLICATION_METADATA_FIELDS).union(
+ CALIBRE_METADATA_FIELDS).union(
+ DEVICE_METADATA_FIELDS) - \
+ frozenset(['device_collections'])
+ # I don't think we need device_collections
# Serialization of covers/thumbnails will have to be handled carefully, maybe
# as an option to the serializer class
diff --git a/src/calibre/ebooks/metadata/book/base.py b/src/calibre/ebooks/metadata/book/base.py
index 3fed47091f..697de8d890 100644
--- a/src/calibre/ebooks/metadata/book/base.py
+++ b/src/calibre/ebooks/metadata/book/base.py
@@ -6,8 +6,13 @@ __copyright__ = '2010, Kovid Goyal '
__docformat__ = 'restructuredtext en'
import copy
+import traceback
+
+from calibre import prints
+from calibre.ebooks.metadata.book import COPYABLE_METADATA_FIELDS
+from calibre.ebooks.metadata.book import STANDARD_METADATA_FIELDS
+from calibre.utils.date import isoformat
-from calibre.ebooks.metadata.book import RESERVED_METADATA_FIELDS
NULL_VALUES = {
'user_metadata': {},
@@ -24,98 +29,313 @@ NULL_VALUES = {
class Metadata(object):
'''
- This class must expose a superset of the API of MetaInformation in terms
- of attribute access and methods. Only the __init__ method is different.
- MetaInformation will simply become a function that creates and fills in
- the attributes of this class.
+ A class representing all the metadata for a book.
Please keep the method based API of this class to a minimum. Every method
becomes a reserved field name.
'''
- def __init__(self):
+ def __init__(self, title, authors=(_('Unknown'),), other=None):
+ '''
+ @param title: title or ``_('Unknown')``
+ @param authors: List of strings or []
+ @param other: None or a metadata object
+ '''
object.__setattr__(self, '_data', copy.deepcopy(NULL_VALUES))
+ if other is not None:
+ self.smart_update(other)
+ else:
+ if title:
+ self.title = title
+ if authors:
+ #: List of strings or []
+ self.author = list(authors) if authors else []# Needed for backward compatibility
+ self.authors = list(authors) if authors else []
def __getattribute__(self, field):
_data = object.__getattribute__(self, '_data')
- if field in RESERVED_METADATA_FIELDS:
+ if field in STANDARD_METADATA_FIELDS:
return _data.get(field, None)
try:
return object.__getattribute__(self, field)
except AttributeError:
pass
if field in _data['user_metadata'].iterkeys():
- # TODO: getting user metadata values
- pass
+ return _data['user_metadata'][field]['#value#']
raise AttributeError(
'Metadata object has no attribute named: '+ repr(field))
-
def __setattr__(self, field, val):
_data = object.__getattribute__(self, '_data')
- if field in RESERVED_METADATA_FIELDS:
- if field != 'user_metadata':
- if not val:
- val = NULL_VALUES[field]
- _data[field] = val
- else:
- raise AttributeError('You cannot set user_metadata directly.')
+ if field in STANDARD_METADATA_FIELDS:
+ if not val:
+ val = NULL_VALUES.get(field, None)
+ _data[field] = val
elif field in _data['user_metadata'].iterkeys():
- # TODO: Setting custom column values
- pass
+ _data['user_metadata'][field]['#value#'] = val
else:
# You are allowed to stick arbitrary attributes onto this object as
# long as they dont conflict with global or user metadata names
# Don't abuse this privilege
self.__dict__[field] = val
+ def get(self, field):
+ return self.__getattribute__(field)
+
+ def set(self, field, val):
+ self.__setattr__(field, val)
+
@property
- def user_metadata_names(self):
+ def user_metadata_keys(self):
'The set of user metadata names this object knows about'
_data = object.__getattribute__(self, '_data')
return frozenset(_data['user_metadata'].iterkeys())
- # Old MetaInformation API {{{
- def copy(self):
- pass
+ @property
+ def all_user_metadata(self):
+ '''
+ return a dict containing all the custom field metadata associated with
+ the book. Return a deep copy, just in case the user wants to change
+ values in the dict (json does).
+ '''
+ _data = object.__getattribute__(self, '_data')
+ _data = _data['user_metadata']
+ res = {}
+ for k in _data:
+ res[k] = copy.deepcopy(_data[k])
+ return res
+
+ def get_user_metadata(self, field):
+ '''
+ return field metadata from the object if it is there. Otherwise return
+ None. field is the key name, not the label. Return a shallow copy,
+ just in case the user wants to change values in the dict (json does).
+ '''
+ _data = object.__getattribute__(self, '_data')
+ _data = _data['user_metadata']
+ if field in _data:
+ return copy.deepcopy(_data[field])
+ return None
+
+ def set_all_user_metadata(self, metadata):
+ '''
+ store custom field metadata into the object. Field is the key name
+ not the label
+ '''
+ if metadata is None:
+ traceback.print_stack()
+ else:
+ for key in metadata:
+ self.set_user_metadata(key, metadata[key])
+
+ def set_user_metadata(self, field, metadata):
+ '''
+ store custom field metadata for one column into the object. Field is
+ the key name not the label
+ '''
+ if field is not None:
+ if metadata is None:
+ traceback.print_stack()
+ metadata = copy.deepcopy(metadata)
+ if '#value#' not in metadata:
+ metadata['#value#'] = None
+ _data = object.__getattribute__(self, '_data')
+ _data['user_metadata'][field] = metadata
+
+ @property
+ def all_attributes(self):
+ result = {}
+ _data = object.__getattribute__(self, '_data')
+ for attr in STANDARD_METADATA_FIELDS:
+ v = _data.get(attr, None)
+ if v is not None:
+ result[attr] = v
+ for attr in self.user_metadata_keys:
+ if self.get(attr) is not None:
+ result[attr] = self.get(attr)
+ return result
+
+ # Old Metadata API {{{
+ @staticmethod
+ def copy(mi):
+ ans = Metadata(mi.title, mi.authors)
+ for attr in STANDARD_METADATA_FIELDS:
+ if hasattr(mi, attr):
+ setattr(ans, attr, copy.deepcopy(getattr(mi, attr)))
+ for x in mi.user_metadata_keys:
+ meta = mi.get_user_metadata(x)
+ if meta is not None:
+ ans.set_user_metadata(x, meta) # get... did the deep copy
def print_all_attributes(self):
- pass
+ for x in STANDARD_METADATA_FIELDS:
+ prints('%s:'%x, getattr(self, x, 'None'))
+ for x in self.user_metadata_keys:
+ meta = self.get_user_metadata(x)
+ if meta is not None:
+ prints(x, meta)
+ prints('--------------')
def smart_update(self, other, replace_metadata=False):
- pass
+ '''
+ Merge the information in C{other} into self. In case of conflicts, the information
+ in C{other} takes precedence, unless the information in other is NULL.
+ '''
+ if other.title and other.title != _('Unknown'):
+ self.title = other.title
+
+ if other.authors and other.authors[0] != _('Unknown'):
+ self.authors = other.authors
+
+ for attr in COPYABLE_METADATA_FIELDS:
+ if replace_metadata:
+ setattr(self, attr, getattr(other, attr, 1.0 if \
+ attr == 'series_index' else None))
+ elif hasattr(other, attr):
+ val = getattr(other, attr)
+ if val is not None:
+ setattr(self, attr, copy.deepcopy(val))
+
+ if replace_metadata:
+ self.tags = other.tags
+ elif other.tags:
+ self.tags += other.tags
+ self.tags = list(set(self.tags))
+
+ if getattr(other, 'author_sort_map', None):
+ self.author_sort_map.update(other.author_sort_map)
+
+ if getattr(other, 'cover_data', False):
+ other_cover = other.cover_data[-1]
+ self_cover = self.cover_data[-1] if self.cover_data else ''
+ if not self_cover: self_cover = ''
+ if not other_cover: other_cover = ''
+ if len(other_cover) > len(self_cover):
+ self.cover_data = other.cover_data
+
+ if getattr(other, 'user_metadata_keys', None):
+ for x in other.user_metadata_keys:
+ meta = other.get_user_metadata(x)
+ if meta is not None or replace_metadata:
+ self.set_user_metadata(x, meta) # get... did the deepcopy
+
+ if replace_metadata:
+ self.comments = getattr(other, 'comments', '')
+ else:
+ my_comments = getattr(self, 'comments', '')
+ other_comments = getattr(other, 'comments', '')
+ if not my_comments:
+ my_comments = ''
+ if not other_comments:
+ other_comments = ''
+ if len(other_comments.strip()) > len(my_comments.strip()):
+ self.comments = other_comments
+
+ other_lang = getattr(other, 'language', None)
+ if other_lang and other_lang.lower() != 'und':
+ self.language = other_lang
+
def format_series_index(self):
- pass
+ from calibre.ebooks.metadata import fmt_sidx
+ try:
+ x = float(self.series_index)
+ except ValueError:
+ x = 1
+ return fmt_sidx(x)
def authors_from_string(self, raw):
- pass
+ from calibre.ebooks.metadata import string_to_authors
+ self.authors = string_to_authors(raw)
def format_authors(self):
- pass
+ from calibre.ebooks.metadata import authors_to_string
+ return authors_to_string(self.authors)
def format_tags(self):
- pass
+ return u', '.join([unicode(t) for t in self.tags])
def format_rating(self):
return unicode(self.rating)
def __unicode__(self):
- pass
+ from calibre.ebooks.metadata import authors_to_string
+ ans = []
+ def fmt(x, y):
+ ans.append(u'%-20s: %s'%(unicode(x), unicode(y)))
+
+ fmt('Title', self.title)
+ if self.title_sort:
+ fmt('Title sort', self.title_sort)
+ if self.authors:
+ fmt('Author(s)', authors_to_string(self.authors) + \
+ ((' [' + self.author_sort + ']') if self.author_sort else ''))
+ if self.publisher:
+ fmt('Publisher', self.publisher)
+ if getattr(self, 'book_producer', False):
+ fmt('Book Producer', self.book_producer)
+ if self.category:
+ fmt('Category', self.category)
+ if self.comments:
+ fmt('Comments', self.comments)
+ if self.isbn:
+ fmt('ISBN', self.isbn)
+ if self.tags:
+ fmt('Tags', u', '.join([unicode(t) for t in self.tags]))
+ if self.series:
+ fmt('Series', self.series + ' #%s'%self.format_series_index())
+ if self.language:
+ fmt('Language', self.language)
+ if self.rating is not None:
+ fmt('Rating', self.rating)
+ if self.timestamp is not None:
+ fmt('Timestamp', isoformat(self.timestamp))
+ if self.pubdate is not None:
+ fmt('Published', isoformat(self.pubdate))
+ if self.rights is not None:
+ fmt('Rights', unicode(self.rights))
+ if self.lccn:
+ fmt('LCCN', unicode(self.lccn))
+ if self.lcc:
+ fmt('LCC', unicode(self.lcc))
+ if self.ddc:
+ fmt('DDC', unicode(self.ddc))
+ # CUSTFIELD: What to do about custom fields?
+ return u'\n'.join(ans)
def to_html(self):
- pass
+ from calibre.ebooks.metadata import authors_to_string
+ ans = [(_('Title'), unicode(self.title))]
+ ans += [(_('Author(s)'), (authors_to_string(self.authors) if self.authors else _('Unknown')))]
+ ans += [(_('Publisher'), unicode(self.publisher))]
+ ans += [(_('Producer'), unicode(self.book_producer))]
+ ans += [(_('Comments'), unicode(self.comments))]
+ ans += [('ISBN', unicode(self.isbn))]
+ if self.lccn:
+ ans += [('LCCN', unicode(self.lccn))]
+ if self.lcc:
+ ans += [('LCC', unicode(self.lcc))]
+ if self.ddc:
+ ans += [('DDC', unicode(self.ddc))]
+ ans += [(_('Tags'), u', '.join([unicode(t) for t in self.tags]))]
+ if self.series:
+ ans += [(_('Series'), unicode(self.series)+ ' #%s'%self.format_series_index())]
+ 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(' ')))]
+ if self.rights is not None:
+ ans += [(_('Rights'), unicode(self.rights))]
+ for i, x in enumerate(ans):
+ ans[i] = u'
%s
%s
'%x
+ # CUSTFIELD: What to do about custom fields
+ return u'
%s
'%u'\n'.join(ans)
def __str__(self):
return self.__unicode__().encode('utf-8')
def __nonzero__(self):
- return True
+ return bool(self.title or self.author or self.comments or self.tags)
# }}}
-
-# We don't need reserved field names for this object any more. Lets just use a
-# protocol like the last char of a user field label should be _ when using this
-# object
-# So mi.tags returns the builtin tags and mi.tags_ returns the user tags
-
diff --git a/src/calibre/ebooks/metadata/book/json_codec.py b/src/calibre/ebooks/metadata/book/json_codec.py
new file mode 100644
index 0000000000..5e13650f0e
--- /dev/null
+++ b/src/calibre/ebooks/metadata/book/json_codec.py
@@ -0,0 +1,125 @@
+'''
+Created on 4 Jun 2010
+
+@author: charles
+'''
+
+from base64 import b64encode, b64decode
+import json
+import traceback
+from PIL import Image
+
+from . import SERIALIZABLE_FIELDS
+from calibre.constants import filesystem_encoding, preferred_encoding
+from calibre.library.field_metadata import FieldMetadata
+from calibre.utils.date import parse_date, isoformat, UNDEFINED_DATE
+
+# Translate datetimes to and from strings. The string form is the datetime in
+# UTC. The returned date is also UTC
+def string_to_datetime(src):
+ if src == "None":
+ return None
+# dt = strptime(src, '%d %m %Y %H:%M:%S', assume_utc=True, as_utc=True)
+# if dt == UNDEFINED_DATE:
+# return None
+ return parse_date(src)
+
+def datetime_to_string(dateval):
+ if dateval is None or dateval == UNDEFINED_DATE:
+ return "None"
+# tt = date_to_utc(dateval).timetuple()
+# res = "%02d %02d %04d %02d:%02d:%02d"%(tt.tm_mday, tt.tm_mon, tt.tm_year,
+# tt.tm_hour, tt.tm_min, tt.tm_sec)
+ return isoformat(dateval)
+
+def encode_thumbnail(thumbnail):
+ '''
+ Encode the image part of a thumbnail, then return the 3 part tuple
+ '''
+ if thumbnail is None:
+ return None
+ return (thumbnail[0], thumbnail[1], b64encode(str(thumbnail[2])))
+
+def decode_thumbnail(tup):
+ '''
+ Decode an encoded thumbnail into its 3 component parts
+ '''
+ if tup is None:
+ return None
+ return (tup[0], tup[1], b64decode(tup[2]))
+
+class JsonCodec(object):
+
+ def __init__(self):
+ self.field_metadata = FieldMetadata()
+
+ def encode_to_file(self, file, booklist):
+ json.dump(self.encode_booklist_metadata(booklist), file, indent=2, encoding='utf-8')
+
+ def encode_booklist_metadata(self, booklist):
+ result = []
+ for book in booklist:
+ result.append(self.encode_book_metadata(book))
+ return result
+
+ def encode_book_metadata(self, book):
+ result = {}
+ for key in SERIALIZABLE_FIELDS:
+ result[key] = self.encode_metadata_attr(book, key)
+ return result
+
+ def encode_metadata_attr(self, book, key):
+ if key == 'user_metadata':
+ meta = book.all_user_metadata
+ for k in meta:
+ if meta[k]['datatype'] == 'datetime':
+ meta[k]['#value#'] = datetime_to_string(meta[k]['#value#'])
+ return meta
+ if key in self.field_metadata:
+ datatype = self.field_metadata[key]['datatype']
+ else:
+ datatype = None
+ value = book.get(key)
+ if key == 'thumbnail':
+ return encode_thumbnail(value)
+ elif isinstance(value, str): # str includes bytes
+ enc = filesystem_encoding if key == 'lpath' else preferred_encoding
+ return value.decode(enc, 'replace')
+ elif isinstance(value, (list, tuple)):
+ return [x.decode(preferred_encoding, 'replace') if
+ isinstance(x, str) else x for x in value]
+ elif datatype == 'datetime':
+ return datetime_to_string(value)
+ else:
+ return value
+
+ def decode_from_file(self, file, booklist, book_class, prefix):
+ js = []
+ try:
+ js = json.load(file, encoding='utf-8')
+ for item in js:
+ book = book_class(prefix, item.get('lpath', None))
+ for key in item.keys():
+ meta = self.decode_metadata(key, item[key])
+ if key == 'user_metadata':
+ book.set_all_user_metadata(meta)
+ else:
+ setattr(book, key, meta)
+ booklist.append(book)
+ except:
+ print 'exception during JSON decoding'
+ traceback.print_exc()
+ booklist = []
+
+ def decode_metadata(self, key, value):
+ if key == 'user_metadata':
+ for k in value:
+ if value[k]['datatype'] == 'datetime':
+ value[k]['#value#'] = string_to_datetime(value[k]['#value#'])
+ return value
+ elif key in self.field_metadata:
+ if self.field_metadata[key]['datatype'] == 'datetime':
+ return string_to_datetime(value)
+ if key == 'thumbnail':
+ return decode_thumbnail(value)
+ return value
diff --git a/src/calibre/ebooks/metadata/isbndb.py b/src/calibre/ebooks/metadata/isbndb.py
index 356cc3f1b1..b5fc5830c8 100644
--- a/src/calibre/ebooks/metadata/isbndb.py
+++ b/src/calibre/ebooks/metadata/isbndb.py
@@ -8,7 +8,7 @@ import sys, re
from urllib import quote
from calibre.utils.config import OptionParser
-from calibre.ebooks.metadata import MetaInformation
+from calibre.ebooks.metadata.book.base import Metadata
from calibre.ebooks.BeautifulSoup import BeautifulStoneSoup
from calibre import browser
@@ -42,10 +42,10 @@ def fetch_metadata(url, max=100, timeout=5.):
return books
-class ISBNDBMetadata(MetaInformation):
+class ISBNDBMetadata(Metadata):
def __init__(self, book):
- MetaInformation.__init__(self, None, [])
+ Metadata.__init__(self, None, [])
self.isbn = book.get('isbn13', book.get('isbn'))
self.title = book.find('titlelong').string
diff --git a/src/calibre/ebooks/metadata/opf2.py b/src/calibre/ebooks/metadata/opf2.py
index f93b614ef2..0ab6d3bbc0 100644
--- a/src/calibre/ebooks/metadata/opf2.py
+++ b/src/calibre/ebooks/metadata/opf2.py
@@ -16,7 +16,8 @@ from lxml import etree
from calibre.ebooks.chardet import xml_to_unicode
from calibre.constants import __appname__, __version__, filesystem_encoding
from calibre.ebooks.metadata.toc import TOC
-from calibre.ebooks.metadata import MetaInformation, string_to_authors
+from calibre.ebooks.metadata import string_to_authors
+from calibre.ebooks.metadata.book.base import Metadata
from calibre.utils.date import parse_date, isoformat
from calibre.utils.localization import get_lang
@@ -926,16 +927,16 @@ class OPF(object):
setattr(self, attr, val)
-class OPFCreator(MetaInformation):
+class OPFCreator(Metadata):
- def __init__(self, base_path, *args, **kwargs):
+ def __init__(self, base_path, other):
'''
Initialize.
@param base_path: An absolute path to the directory in which this OPF file
will eventually be. This is used by the L{create_manifest} method
to convert paths to files into relative paths.
'''
- MetaInformation.__init__(self, *args, **kwargs)
+ Metadata.__init__(self, title='', other=other)
self.base_path = os.path.abspath(base_path)
if self.application_id is None:
self.application_id = str(uuid.uuid4())
diff --git a/src/calibre/gui2/dialogs/config/save_template.py b/src/calibre/gui2/dialogs/config/save_template.py
index 71eb15f4aa..2157d5b3bf 100644
--- a/src/calibre/gui2/dialogs/config/save_template.py
+++ b/src/calibre/gui2/dialogs/config/save_template.py
@@ -34,25 +34,24 @@ class SaveTemplate(QWidget, Ui_Form):
self.option_name = name
def validate(self):
- tmpl = preprocess_template(self.opt_template.text())
- fa = {}
- for x in FORMAT_ARG_DESCS.keys():
- fa[x]='random long string'
- try:
- tmpl.format(**fa)
- except Exception, err:
- error_dialog(self, _('Invalid template'),
- '
'+_('The template %s is invalid:')%tmpl + \
- ' '+str(err), show=True)
- return False
+ # TODO: I haven't figured out how to get the custom columns into here,
+ # so for the moment make all templates valid.
return True
+# tmpl = preprocess_template(self.opt_template.text())
+# fa = {}
+# for x in FORMAT_ARG_DESCS.keys():
+# fa[x]='random long string'
+# try:
+# tmpl.format(**fa)
+# except Exception, err:
+# error_dialog(self, _('Invalid template'),
+# '
'+_('The template %s is invalid:')%tmpl + \
+# ' '+str(err), show=True)
+# return False
+# return True
def save_settings(self, config, name):
val = unicode(self.opt_template.text())
config.set(name, val)
self.opt_template.save_history(self.option_name+'_template_history')
-
-
-
-
diff --git a/src/calibre/gui2/library/models.py b/src/calibre/gui2/library/models.py
index 89008735fe..fdf21ecc23 100644
--- a/src/calibre/gui2/library/models.py
+++ b/src/calibre/gui2/library/models.py
@@ -372,7 +372,6 @@ class BooksModel(QAbstractTableModel): # {{{
return ans
def get_metadata(self, rows, rows_are_ids=False, full_metadata=False):
- # Should this add the custom columns? It doesn't at the moment
metadata, _full_metadata = [], []
if not rows_are_ids:
rows = [self.db.id(row.row()) for row in rows]
@@ -1053,7 +1052,7 @@ class DeviceBooksModel(BooksModel): # {{{
if hasattr(cdata, 'image_path'):
img.load(cdata.image_path)
else:
- img.loadFromData(cdata)
+ img.loadFromData(cdata[2])
if img.isNull():
img = self.default_image
data['cover'] = img
diff --git a/src/calibre/library/database2.py b/src/calibre/library/database2.py
index ef74188bdf..9f21fe0eda 100644
--- a/src/calibre/library/database2.py
+++ b/src/calibre/library/database2.py
@@ -509,15 +509,15 @@ class LibraryDatabase2(LibraryDatabase, SchemaUpgrade, CustomColumns):
'''
Convenience method to return metadata as a L{MetaInformation} object.
'''
- aum = self.authors(idx, index_is_id=index_is_id)
- if aum: aum = [a.strip().replace('|', ',') for a in aum.split(',')]
+ aut_list = self.authors_with_sort_strings(idx, index_is_id=index_is_id)
+ aum = []
+ aus = {}
+ for (author, author_sort) in aut_list:
+ aum.append(author)
+ aus[author] = author_sort
mi = MetaInformation(self.title(idx, index_is_id=index_is_id), aum)
mi.author_sort = self.author_sort(idx, index_is_id=index_is_id)
- if mi.authors:
- mi.author_sort_map = {}
- for name, sort in zip(mi.authors, self.authors_sort_strings(idx,
- index_is_id)):
- mi.author_sort_map[name] = sort
+ mi.author_sort_map = aus
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)
@@ -534,6 +534,10 @@ class LibraryDatabase2(LibraryDatabase, SchemaUpgrade, CustomColumns):
mi.isbn = self.isbn(idx, index_is_id=index_is_id)
id = idx if index_is_id else self.id(idx)
mi.application_id = id
+ for key,meta in self.field_metadata.iteritems():
+ if meta['is_custom']:
+ mi.set_user_metadata(key, meta)
+ mi.set(key, self.get_custom(idx, label=meta['label'], index_is_id=index_is_id))
if get_cover:
mi.cover = self.cover(id, index_is_id=True, as_path=True)
return mi
@@ -1049,6 +1053,19 @@ class LibraryDatabase2(LibraryDatabase, SchemaUpgrade, CustomColumns):
result.append(sort)
return result
+ # Given a book, return the map of author sort strings for the book's authors
+ def authors_with_sort_strings(self, id, index_is_id=False):
+ id = id if index_is_id else self.id(id)
+ aut_strings = self.conn.get('''
+ SELECT authors.name, authors.sort
+ FROM authors, books_authors_link as bl
+ WHERE bl.book=? and authors.id=bl.author
+ ORDER BY bl.id''', (id,))
+ result = []
+ for (author, sort,) in aut_strings:
+ result.append((author.replace('|', ','), sort))
+ return result
+
# Given a book, return the author_sort string for authors of the book
def author_sort_from_book(self, id, index_is_id=False):
auts = self.authors_sort_strings(id, index_is_id)
diff --git a/src/calibre/library/save_to_disk.py b/src/calibre/library/save_to_disk.py
index 15020855f7..258ea7ba9e 100644
--- a/src/calibre/library/save_to_disk.py
+++ b/src/calibre/library/save_to_disk.py
@@ -105,6 +105,8 @@ def safe_format(x, format_args):
pass
except AttributeError: # Thrown if user used a non existing attribute
pass
+ except KeyError: # Thrown if user used custom field w/value None
+ pass
return ''
def get_components(template, mi, id, timefmt='%b %Y', length=250,
@@ -113,13 +115,12 @@ def get_components(template, mi, id, timefmt='%b %Y', length=250,
library_order = tweaks['save_template_title_series_sorting'] == 'library_order'
tsfmt = title_sort if library_order else lambda x: x
format_args = dict(**FORMAT_ARGS)
+ format_args.update(mi.all_attributes)
if mi.title:
format_args['title'] = tsfmt(mi.title)
if mi.authors:
format_args['authors'] = mi.format_authors()
format_args['author'] = format_args['authors']
- if mi.author_sort:
- format_args['author_sort'] = mi.author_sort
if mi.tags:
format_args['tags'] = mi.format_tags()
if format_args['tags'].startswith('/'):
@@ -132,15 +133,21 @@ def get_components(template, mi, id, timefmt='%b %Y', length=250,
template = re.sub(r'\{series_index[^}]*?\}', '', template)
if mi.rating is not None:
format_args['rating'] = mi.format_rating()
- if mi.isbn:
- format_args['isbn'] = mi.isbn
- if mi.publisher:
- format_args['publisher'] = mi.publisher
if hasattr(mi.timestamp, 'timetuple'):
format_args['timestamp'] = strftime(timefmt, mi.timestamp.timetuple())
if hasattr(mi.pubdate, 'timetuple'):
format_args['pubdate'] = strftime(timefmt, mi.pubdate.timetuple())
format_args['id'] = str(id)
+
+ # These are not necessary any more. The values are set by
+ # 'format_args.update' above, and there is no special formatting
+# if mi.author_sort:
+# format_args['author_sort'] = mi.author_sort
+# if mi.isbn:
+# format_args['isbn'] = mi.isbn
+# if mi.publisher:
+# format_args['publisher'] = mi.publisher
+
components = [x.strip() for x in template.split('/') if x.strip()]
components = [safe_format(x, format_args) for x in components]
components = [sanitize_func(x) for x in components if x]
From b04faf70c2378c3569a4d1cf010da3d57a707c42 Mon Sep 17 00:00:00 2001
From: Charles Haley <>
Date: Fri, 27 Aug 2010 08:22:08 +0100
Subject: [PATCH 002/412] Make Kobo driver use new metadata framework
---
src/calibre/devices/kobo/books.py | 80 ++----------------------------
src/calibre/devices/kobo/driver.py | 2 +-
2 files changed, 5 insertions(+), 77 deletions(-)
diff --git a/src/calibre/devices/kobo/books.py b/src/calibre/devices/kobo/books.py
index f0cf7c3763..1c3d05ea12 100644
--- a/src/calibre/devices/kobo/books.py
+++ b/src/calibre/devices/kobo/books.py
@@ -4,37 +4,15 @@ __copyright__ = '2010, Timothy Legge '
'''
import os
-import re
import time
-from calibre.ebooks.metadata.book.base import Metadata
-from calibre.constants import filesystem_encoding, preferred_encoding
-from calibre import isbytestring
+from calibre.devices.usbms.books import Book as Book_
-class Book(Metadata):
-
- BOOK_ATTRS = ['lpath', 'size', 'mime', 'device_collections', '_new_book']
-
- JSON_ATTRS = [
- 'lpath', 'title', 'authors', 'mime', 'size', 'tags', 'author_sort',
- 'title_sort', 'comments', 'category', 'publisher', 'series',
- 'series_index', 'rating', 'isbn', 'language', 'application_id',
- 'book_producer', 'lccn', 'lcc', 'ddc', 'rights', 'publication_type',
- 'uuid', 'device_collections',
- ]
+class Book(Book_):
def __init__(self, prefix, lpath, title, authors, mime, date, ContentType,
thumbnail_name, other=None):
- Metadata.__init__(self, '')
- self.device_collections = []
- self._new_book = False
-
- self.path = os.path.join(prefix, lpath)
- if os.sep == '\\':
- self.path = self.path.replace('/', '\\')
- self.lpath = lpath.replace('\\', '/')
- else:
- self.lpath = lpath
+ Book_.__init__(self, prefix, lpath)
self.title = title
if not authors:
@@ -59,57 +37,7 @@ class Book(Metadata):
if other:
self.smart_update(other)
- def __eq__(self, other):
- return self.path == getattr(other, 'path', None)
-
- @dynamic_property
- def db_id(self):
- doc = '''The database id in the application database that this file corresponds to'''
- def fget(self):
- match = re.search(r'_(\d+)$', self.lpath.rpartition('.')[0])
- if match:
- return int(match.group(1))
- return None
- return property(fget=fget, doc=doc)
-
- @dynamic_property
- def title_sorter(self):
- doc = '''String to sort the title. If absent, title is returned'''
- def fget(self):
- return re.sub('^\s*A\s+|^\s*The\s+|^\s*An\s+', '', self.title).rstrip()
- return property(doc=doc, fget=fget)
-
- @dynamic_property
- def thumbnail(self):
- return None
-
- def smart_update(self, other, replace_metadata=False):
- '''
- Merge the information in C{other} into self. In case of conflicts, the information
- in C{other} takes precedence, unless the information in C{other} is NULL.
- '''
-
- Metadata.smart_update(self, other)
-
- for attr in self.BOOK_ATTRS:
- if hasattr(other, attr):
- val = getattr(other, attr, None)
- setattr(self, attr, val)
-
- def to_json(self):
- json = {}
- for attr in self.JSON_ATTRS:
- val = getattr(self, attr)
- if isbytestring(val):
- enc = filesystem_encoding if attr == 'lpath' else preferred_encoding
- val = val.decode(enc, 'replace')
- elif isinstance(val, (list, tuple)):
- val = [x.decode(preferred_encoding, 'replace') if
- isbytestring(x) else x for x in val]
- json[attr] = val
- return json
-
class ImageWrapper(object):
def __init__(self, image_path):
- self.image_path = image_path
+ self.image_path = image_path
diff --git a/src/calibre/devices/kobo/driver.py b/src/calibre/devices/kobo/driver.py
index 35fceb80f7..5f939a4498 100644
--- a/src/calibre/devices/kobo/driver.py
+++ b/src/calibre/devices/kobo/driver.py
@@ -132,7 +132,7 @@ class KOBO(USBMS):
changed = False
for i, row in enumerate(cursor):
- # self.report_progress((i+1) / float(numrows), _('Getting list of books on device...'))
+ # self.report_progress((i+1) / float(numrows), _('Getting list of books on device...'))
path = self.path_from_contentid(row[3], row[5], oncard)
mime = mime_type_ext(path_to_ext(row[3]))
From 47fedcee36db9d7f9f5ab39460a6eafcbe21df20 Mon Sep 17 00:00:00 2001
From: Charles Haley <>
Date: Fri, 27 Aug 2010 10:26:36 +0100
Subject: [PATCH 003/412] Bug fixes: 1) Only reset to initial values when None
is assigned. Using 'if not var' is true for empty lists 2) Take unused values
out of the to_html and unicode functions 3) add 'language' as a valid
metadata field
---
src/calibre/ebooks/metadata/book/__init__.py | 68 +++++++-------------
src/calibre/ebooks/metadata/book/base.py | 31 +++++----
2 files changed, 42 insertions(+), 57 deletions(-)
diff --git a/src/calibre/ebooks/metadata/book/__init__.py b/src/calibre/ebooks/metadata/book/__init__.py
index 9de7ca1c6b..b1a322b143 100644
--- a/src/calibre/ebooks/metadata/book/__init__.py
+++ b/src/calibre/ebooks/metadata/book/__init__.py
@@ -11,50 +11,39 @@ an empty list/dictionary for complex types and (None, None) for cover_data
'''
SOCIAL_METADATA_FIELDS = frozenset([
- 'tags', # Ordered list
- # A floating point number between 0 and 10
- 'rating',
- # A simple HTML enabled string
- 'comments',
- # A simple string
- 'series',
- # A floating point number
- 'series_index',
+ 'tags', # Ordered list
+ 'rating', # A floating point number between 0 and 10
+ 'comments', # A simple HTML enabled string
+ 'series', # A simple string
+ 'series_index', # A floating point number
# Of the form { scheme1:value1, scheme2:value2}
# For example: {'isbn':'123456789', 'doi':'xxxx', ... }
'classifiers',
- 'isbn', # Pseudo field for convenience, should get/set isbn classifier
- # TODO: not sure what this is, but it is used by OPF
- 'category',
-
+ 'isbn', # Pseudo field for convenience, should get/set isbn classifier
+ 'category', # TODO: not sure what this is, but it is used by OPF
])
PUBLICATION_METADATA_FIELDS = frozenset([
- # title must never be None. Should be _('Unknown')
- 'title',
+ 'title', # title must never be None. Should be _('Unknown')
# Pseudo field that can be set, but if not set is auto generated
# from title and languages
'title_sort',
- # Ordered list of authors. Must never be None, can be [_('Unknown')]
- 'authors',
- # Map of sort strings for each author
- 'author_sort_map',
+ 'authors', # Ordered list. Must never be None, can be [_('Unknown')]
+ 'author_sort_map', # Map of sort strings for each author
# Pseudo field that can be set, but if not set is auto generated
# from authors and languages
'author_sort',
'book_producer',
- # Dates and times must be timezone aware
- 'timestamp',
+ 'timestamp', # Dates and times must be timezone aware
'pubdate',
'rights',
# So far only known publication type is periodical:calibre
# If None, means book
'publication_type',
- # A UUID usually of type 4
- 'uuid',
- 'languages', # ordered list
- # Simple string, no special semantics
- 'publisher',
+ 'uuid', # A UUID usually of type 4
+ 'language', # the primary language of this book
+ 'languages', # ordered list
+ 'publisher', # Simple string, no special semantics
# Absolute path to image file encoded in filesystem_encoding
'cover',
# Of the form (format, data) where format is, for e.g. 'jpeg', 'png', 'gif'...
@@ -77,22 +66,18 @@ USER_METADATA_FIELDS = frozenset([
])
DEVICE_METADATA_FIELDS = frozenset([
- # Ordered list of strings
- 'device_collections',
- 'lpath', # Unicode, / separated
- # In bytes
- 'size',
- # Mimetype of the book file being represented
- 'mime',
+ 'device_collections', # Ordered list of strings
+ 'lpath', # Unicode, / separated
+ 'size', # In bytes
+ 'mime', # Mimetype of the book file being represented
+
])
CALIBRE_METADATA_FIELDS = frozenset([
- # An application id
- # Semantics to be defined. Is it a db key? a db name + key? A uuid?
- # (It is currently set to the db_id.)
- 'application_id',
- # the calibre primary key of the item. May want to remove this once Sony's no longer use it
- 'db_id',
+ 'application_id', # An application id, currently set to the db_id.
+ # the calibre primary key of the item.
+ 'db_id', # the calibre primary key of the item.
+ # TODO: May want to remove once Sony's no longer use it
]
)
@@ -124,7 +109,4 @@ SERIALIZABLE_FIELDS = SOCIAL_METADATA_FIELDS.union(
CALIBRE_METADATA_FIELDS).union(
DEVICE_METADATA_FIELDS) - \
frozenset(['device_collections'])
- # I don't think we need device_collections
-
-# Serialization of covers/thumbnails will have to be handled carefully, maybe
-# as an option to the serializer class
+ # device_collections is rebuilt when needed
diff --git a/src/calibre/ebooks/metadata/book/base.py b/src/calibre/ebooks/metadata/book/base.py
index 697de8d890..e352aecbf8 100644
--- a/src/calibre/ebooks/metadata/book/base.py
+++ b/src/calibre/ebooks/metadata/book/base.py
@@ -24,6 +24,7 @@ NULL_VALUES = {
'author_sort_map': {},
'authors' : [_('Unknown')],
'title' : _('Unknown'),
+ 'language' : 'und'
}
class Metadata(object):
@@ -68,14 +69,14 @@ class Metadata(object):
def __setattr__(self, field, val):
_data = object.__getattribute__(self, '_data')
if field in STANDARD_METADATA_FIELDS:
- if not val:
+ if val is None:
val = NULL_VALUES.get(field, None)
_data[field] = val
elif field in _data['user_metadata'].iterkeys():
_data['user_metadata'][field]['#value#'] = val
else:
# You are allowed to stick arbitrary attributes onto this object as
- # long as they dont conflict with global or user metadata names
+ # long as they don't conflict with global or user metadata names
# Don't abuse this privilege
self.__dict__[field] = val
@@ -294,12 +295,13 @@ class Metadata(object):
fmt('Published', isoformat(self.pubdate))
if self.rights is not None:
fmt('Rights', unicode(self.rights))
- if self.lccn:
- fmt('LCCN', unicode(self.lccn))
- if self.lcc:
- fmt('LCC', unicode(self.lcc))
- if self.ddc:
- fmt('DDC', unicode(self.ddc))
+# TODO: These are not in metadata. Should they be?
+# if self.lccn:
+# fmt('LCCN', unicode(self.lccn))
+# if self.lcc:
+# fmt('LCC', unicode(self.lcc))
+# if self.ddc:
+# fmt('DDC', unicode(self.ddc))
# CUSTFIELD: What to do about custom fields?
return u'\n'.join(ans)
@@ -311,12 +313,13 @@ class Metadata(object):
ans += [(_('Producer'), unicode(self.book_producer))]
ans += [(_('Comments'), unicode(self.comments))]
ans += [('ISBN', unicode(self.isbn))]
- if self.lccn:
- ans += [('LCCN', unicode(self.lccn))]
- if self.lcc:
- ans += [('LCC', unicode(self.lcc))]
- if self.ddc:
- ans += [('DDC', unicode(self.ddc))]
+# TODO: These are not in metadata. Should they be?
+# if self.lccn:
+# ans += [('LCCN', unicode(self.lccn))]
+# if self.lcc:
+# ans += [('LCC', unicode(self.lcc))]
+# if self.ddc:
+# ans += [('DDC', unicode(self.ddc))]
ans += [(_('Tags'), u', '.join([unicode(t) for t in self.tags]))]
if self.series:
ans += [(_('Series'), unicode(self.series)+ ' #%s'%self.format_series_index())]
From b4b0cb483df4a647de50145abf88a7b26ecd79c9 Mon Sep 17 00:00:00 2001
From: Charles Haley <>
Date: Sat, 28 Aug 2010 13:34:49 +0100
Subject: [PATCH 004/412] Changes to respond to Kovid's mail, and some
cleanups.
---
src/calibre/ebooks/metadata/book/__init__.py | 4 +-
src/calibre/ebooks/metadata/book/base.py | 132 +++++++++++-------
.../ebooks/metadata/book/json_codec.py | 2 +-
src/calibre/library/database2.py | 15 +-
src/calibre/library/save_to_disk.py | 13 +-
5 files changed, 100 insertions(+), 66 deletions(-)
diff --git a/src/calibre/ebooks/metadata/book/__init__.py b/src/calibre/ebooks/metadata/book/__init__.py
index b1a322b143..fbcca79aba 100644
--- a/src/calibre/ebooks/metadata/book/__init__.py
+++ b/src/calibre/ebooks/metadata/book/__init__.py
@@ -101,7 +101,9 @@ COPYABLE_METADATA_FIELDS = SOCIAL_METADATA_FIELDS.union(
BOOK_STRUCTURE_FIELDS).union(
DEVICE_METADATA_FIELDS).union(
CALIBRE_METADATA_FIELDS) - \
- frozenset(['title', 'authors', 'comments', 'cover_data'])
+ frozenset(['title', 'title_sort', 'authors',
+ 'author_sort', 'author_sort_map' 'comments',
+ 'cover_data', 'tags', 'language'])
SERIALIZABLE_FIELDS = SOCIAL_METADATA_FIELDS.union(
USER_METADATA_FIELDS).union(
diff --git a/src/calibre/ebooks/metadata/book/base.py b/src/calibre/ebooks/metadata/book/base.py
index e352aecbf8..a81ce46c34 100644
--- a/src/calibre/ebooks/metadata/book/base.py
+++ b/src/calibre/ebooks/metadata/book/base.py
@@ -66,7 +66,7 @@ class Metadata(object):
raise AttributeError(
'Metadata object has no attribute named: '+ repr(field))
- def __setattr__(self, field, val):
+ def __setattr__(self, field, val, extra=None):
_data = object.__getattribute__(self, '_data')
if field in STANDARD_METADATA_FIELDS:
if val is None:
@@ -74,17 +74,23 @@ class Metadata(object):
_data[field] = val
elif field in _data['user_metadata'].iterkeys():
_data['user_metadata'][field]['#value#'] = val
+ _data['user_metadata'][field]['#extra#'] = extra
else:
# You are allowed to stick arbitrary attributes onto this object as
# long as they don't conflict with global or user metadata names
# Don't abuse this privilege
self.__dict__[field] = val
- def get(self, field):
+ def get(self, field, default=None):
+ if default is not None:
+ try:
+ return self.__getattribute__(field)
+ except AttributeError:
+ return default
return self.__getattribute__(field)
- def set(self, field, val):
- self.__setattr__(field, val)
+ def set(self, field, val, extra=None):
+ self.__setattr__(field, val, extra)
@property
def user_metadata_keys(self):
@@ -92,25 +98,25 @@ class Metadata(object):
_data = object.__getattribute__(self, '_data')
return frozenset(_data['user_metadata'].iterkeys())
- @property
- def all_user_metadata(self):
+ def get_all_user_metadata(self, make_copy):
'''
return a dict containing all the custom field metadata associated with
- the book. Return a deep copy, just in case the user wants to change
- values in the dict (json does).
+ the book.
'''
_data = object.__getattribute__(self, '_data')
- _data = _data['user_metadata']
+ user_metadata = _data['user_metadata']
+ if not make_copy:
+ return user_metadata
res = {}
- for k in _data:
- res[k] = copy.deepcopy(_data[k])
+ for k in user_metadata:
+ res[k] = copy.deepcopy(user_metadata[k])
return res
def get_user_metadata(self, field):
'''
return field metadata from the object if it is there. Otherwise return
- None. field is the key name, not the label. Return a shallow copy,
- just in case the user wants to change values in the dict (json does).
+ None. field is the key name, not the label. Return a copy, just in case
+ the user wants to change values in the dict (json does).
'''
_data = object.__getattribute__(self, '_data')
_data = _data['user_metadata']
@@ -118,6 +124,14 @@ class Metadata(object):
return copy.deepcopy(_data[field])
return None
+ @classmethod
+ def get_user_metadata_value(user_mi):
+ return user_mi['#value#']
+
+ @classmethod
+ def get_user_metadata_extra(user_mi):
+ return user_mi['#extra#']
+
def set_all_user_metadata(self, metadata):
'''
store custom field metadata into the object. Field is the key name
@@ -139,21 +153,30 @@ class Metadata(object):
traceback.print_stack()
metadata = copy.deepcopy(metadata)
if '#value#' not in metadata:
- metadata['#value#'] = None
+ if metadata['datatype'] == 'text' and metadata['is_multiple']:
+ metadata['#value#'] = []
+ else:
+ metadata['#value#'] = None
_data = object.__getattribute__(self, '_data')
_data['user_metadata'][field] = metadata
- @property
- def all_attributes(self):
+ def get_all_non_none_attributes(self):
+ '''
+ Return a dictionary containing all non-None metadata fields, including
+ the custom ones.
+ '''
result = {}
_data = object.__getattribute__(self, '_data')
for attr in STANDARD_METADATA_FIELDS:
v = _data.get(attr, None)
if v is not None:
result[attr] = v
- for attr in self.user_metadata_keys:
- if self.get(attr) is not None:
- result[attr] = self.get(attr)
+ for attr in _data['user_metadata'].iterkeys():
+ v = _data['user_metadata'][attr]['#value#']
+ if v is not None:
+ result[attr] = v
+ if _data['user_metadata'][attr]['datatype'] == 'series':
+ result[attr+'_index'] = _data['user_metadata'][attr]['#extra#']
return result
# Old Metadata API {{{
@@ -184,45 +207,49 @@ class Metadata(object):
'''
if other.title and other.title != _('Unknown'):
self.title = other.title
+ if hasattr(other, 'title_sort'):
+ self.title_sort = other.title_sort
if other.authors and other.authors[0] != _('Unknown'):
self.authors = other.authors
+ if hasattr(other, 'author_sort_map'):
+ self.author_sort_map = other.author_sort_map
+ if hasattr(other, 'author_sort'):
+ self.author_sort = other.author_sort
- for attr in COPYABLE_METADATA_FIELDS:
- if replace_metadata:
+ if replace_metadata:
+ for attr in COPYABLE_METADATA_FIELDS:
setattr(self, attr, getattr(other, attr, 1.0 if \
attr == 'series_index' else None))
- elif hasattr(other, attr):
- val = getattr(other, attr)
- if val is not None:
- setattr(self, attr, copy.deepcopy(val))
-
- if replace_metadata:
self.tags = other.tags
- elif other.tags:
- self.tags += other.tags
- self.tags = list(set(self.tags))
-
- if getattr(other, 'author_sort_map', None):
- self.author_sort_map.update(other.author_sort_map)
-
- if getattr(other, 'cover_data', False):
- other_cover = other.cover_data[-1]
- self_cover = self.cover_data[-1] if self.cover_data else ''
- if not self_cover: self_cover = ''
- if not other_cover: other_cover = ''
- if len(other_cover) > len(self_cover):
- self.cover_data = other.cover_data
-
- if getattr(other, 'user_metadata_keys', None):
- for x in other.user_metadata_keys:
- meta = other.get_user_metadata(x)
- if meta is not None or replace_metadata:
- self.set_user_metadata(x, meta) # get... did the deepcopy
-
- if replace_metadata:
+ self.cover_data = getattr(other, 'cover_data', '')
+ self.set_all_user_metadata(other.get_all_user_metadata(make_copy=True))
self.comments = getattr(other, 'comments', '')
+ self.language = getattr(other, 'language', None)
else:
+ for attr in COPYABLE_METADATA_FIELDS:
+ if hasattr(other, attr):
+ val = getattr(other, attr)
+ if val is not None:
+ setattr(self, attr, copy.deepcopy(val))
+
+ if other.tags:
+ self.tags += list(set(self.tags + other.tags))
+
+ if getattr(other, 'cover_data', False):
+ other_cover = other.cover_data[-1]
+ self_cover = self.cover_data[-1] if self.cover_data else ''
+ if not self_cover: self_cover = ''
+ if not other_cover: other_cover = ''
+ if len(other_cover) > len(self_cover):
+ self.cover_data = other.cover_data
+
+ if getattr(other, 'user_metadata_keys', None):
+ for x in other.user_metadata_keys:
+ meta = other.get_user_metadata(x)
+ if meta is not None:
+ self.set_user_metadata(x, meta) # get... did the deepcopy
+
my_comments = getattr(self, 'comments', '')
other_comments = getattr(other, 'comments', '')
if not my_comments:
@@ -232,10 +259,9 @@ class Metadata(object):
if len(other_comments.strip()) > len(my_comments.strip()):
self.comments = other_comments
- other_lang = getattr(other, 'language', None)
- if other_lang and other_lang.lower() != 'und':
- self.language = other_lang
-
+ other_lang = getattr(other, 'language', None)
+ if other_lang and other_lang.lower() != 'und':
+ self.language = other_lang
def format_series_index(self):
from calibre.ebooks.metadata import fmt_sidx
diff --git a/src/calibre/ebooks/metadata/book/json_codec.py b/src/calibre/ebooks/metadata/book/json_codec.py
index 5e13650f0e..7a80e16854 100644
--- a/src/calibre/ebooks/metadata/book/json_codec.py
+++ b/src/calibre/ebooks/metadata/book/json_codec.py
@@ -70,7 +70,7 @@ class JsonCodec(object):
def encode_metadata_attr(self, book, key):
if key == 'user_metadata':
- meta = book.all_user_metadata
+ meta = book.get_all_user_metadata(make_copy=True)
for k in meta:
if meta[k]['datatype'] == 'datetime':
meta[k]['#value#'] = datetime_to_string(meta[k]['#value#'])
diff --git a/src/calibre/library/database2.py b/src/calibre/library/database2.py
index 9f21fe0eda..935776f838 100644
--- a/src/calibre/library/database2.py
+++ b/src/calibre/library/database2.py
@@ -22,6 +22,7 @@ from calibre.library.sqlite import connect, IntegrityError, DBThread
from calibre.library.prefs import DBPrefs
from calibre.ebooks.metadata import string_to_authors, authors_to_string, \
MetaInformation
+from calibre.ebooks.metadata.book.base import Metadata
from calibre.ebooks.metadata.meta import get_metadata, metadata_from_formats
from calibre.constants import preferred_encoding, iswindows, isosx, filesystem_encoding
from calibre.ptempfile import PersistentTemporaryFile
@@ -537,7 +538,10 @@ class LibraryDatabase2(LibraryDatabase, SchemaUpgrade, CustomColumns):
for key,meta in self.field_metadata.iteritems():
if meta['is_custom']:
mi.set_user_metadata(key, meta)
- mi.set(key, self.get_custom(idx, label=meta['label'], index_is_id=index_is_id))
+ mi.set(key, val=self.get_custom(idx, label=meta['label'],
+ index_is_id=index_is_id),
+ extra=self.get_custom_extra(idx, label=meta['label'],
+ index_is_id=index_is_id))
if get_cover:
mi.cover = self.cover(id, index_is_id=True, as_path=True)
return mi
@@ -1038,6 +1042,15 @@ class LibraryDatabase2(LibraryDatabase, SchemaUpgrade, CustomColumns):
if getattr(mi, 'timestamp', None) is not None:
doit(self.set_timestamp, id, mi.timestamp, notify=False)
self.set_path(id, True)
+
+ user_mi = mi.get_all_user_metadata(make_copy=False)
+ for key in user_mi.iterkeys():
+ if key in self.field_metadata and \
+ user_mi[key]['datatype'] == self.field_metadata[key]['datatype']:
+ doit(self.set_custom, id,
+ val=Metadata.get_user_metadata_value(user_mi[key]),
+ extra=Metadata.get_user_metadata_extra(user_mi[key]),
+ label=user_mi[key]['label'])
self.notify('metadata', [id])
# Given a book, return the list of author sort strings for the book's authors
diff --git a/src/calibre/library/save_to_disk.py b/src/calibre/library/save_to_disk.py
index 258ea7ba9e..7a3515305b 100644
--- a/src/calibre/library/save_to_disk.py
+++ b/src/calibre/library/save_to_disk.py
@@ -115,7 +115,7 @@ def get_components(template, mi, id, timefmt='%b %Y', length=250,
library_order = tweaks['save_template_title_series_sorting'] == 'library_order'
tsfmt = title_sort if library_order else lambda x: x
format_args = dict(**FORMAT_ARGS)
- format_args.update(mi.all_attributes)
+ format_args.update(mi.get_all_non_none_attributes())
if mi.title:
format_args['title'] = tsfmt(mi.title)
if mi.authors:
@@ -131,6 +131,8 @@ def get_components(template, mi, id, timefmt='%b %Y', length=250,
format_args['series_index'] = mi.format_series_index()
else:
template = re.sub(r'\{series_index[^}]*?\}', '', template)
+ ## TODO: format custom values. Check all the datatypes.
+
if mi.rating is not None:
format_args['rating'] = mi.format_rating()
if hasattr(mi.timestamp, 'timetuple'):
@@ -139,15 +141,6 @@ def get_components(template, mi, id, timefmt='%b %Y', length=250,
format_args['pubdate'] = strftime(timefmt, mi.pubdate.timetuple())
format_args['id'] = str(id)
- # These are not necessary any more. The values are set by
- # 'format_args.update' above, and there is no special formatting
-# if mi.author_sort:
-# format_args['author_sort'] = mi.author_sort
-# if mi.isbn:
-# format_args['isbn'] = mi.isbn
-# if mi.publisher:
-# format_args['publisher'] = mi.publisher
-
components = [x.strip() for x in template.split('/') if x.strip()]
components = [safe_format(x, format_args) for x in components]
components = [sanitize_func(x) for x in components if x]
From 65f8767057afa440c143353c52e038a092da6783 Mon Sep 17 00:00:00 2001
From: Charles Haley <>
Date: Sun, 29 Aug 2010 11:18:09 +0100
Subject: [PATCH 005/412] New metadata: 1) remove 'category' from standard
metadata fields 2) make isbn a classifier. Add ability to add more
classifiers 3) fixup TODO: to make them easier to find.
---
src/calibre/devices/usbms/books.py | 1 +
src/calibre/ebooks/metadata/book/__init__.py | 12 ++++++---
src/calibre/ebooks/metadata/book/base.py | 25 ++++++-------------
src/calibre/ebooks/metadata/opf2.py | 2 +-
.../gui2/dialogs/config/save_template.py | 4 +--
src/calibre/library/save_to_disk.py | 2 +-
6 files changed, 21 insertions(+), 25 deletions(-)
diff --git a/src/calibre/devices/usbms/books.py b/src/calibre/devices/usbms/books.py
index e3c405ee4e..0efa507e09 100644
--- a/src/calibre/devices/usbms/books.py
+++ b/src/calibre/devices/usbms/books.py
@@ -135,6 +135,7 @@ class CollectionsBookList(BookList):
elif isinstance(val, unicode):
val = [val]
for category in val:
+ # TODO: NEWMETA: format the custom fields
if attr == 'tags' and len(category) > 1 and \
category[0] == '[' and category[-1] == ']':
continue
diff --git a/src/calibre/ebooks/metadata/book/__init__.py b/src/calibre/ebooks/metadata/book/__init__.py
index fbcca79aba..47eb616394 100644
--- a/src/calibre/ebooks/metadata/book/__init__.py
+++ b/src/calibre/ebooks/metadata/book/__init__.py
@@ -19,8 +19,14 @@ SOCIAL_METADATA_FIELDS = frozenset([
# Of the form { scheme1:value1, scheme2:value2}
# For example: {'isbn':'123456789', 'doi':'xxxx', ... }
'classifiers',
- 'isbn', # Pseudo field for convenience, should get/set isbn classifier
- 'category', # TODO: not sure what this is, but it is used by OPF
+])
+
+'''
+The list of names that convert to classifiers when in get and set.
+'''
+
+TOP_LEVEL_CLASSIFIERS = frozenset([
+ 'isbn',
])
PUBLICATION_METADATA_FIELDS = frozenset([
@@ -77,7 +83,7 @@ CALIBRE_METADATA_FIELDS = frozenset([
'application_id', # An application id, currently set to the db_id.
# the calibre primary key of the item.
'db_id', # the calibre primary key of the item.
- # TODO: May want to remove once Sony's no longer use it
+ # TODO: NEWMETA: May want to remove once Sony's no longer use it
]
)
diff --git a/src/calibre/ebooks/metadata/book/base.py b/src/calibre/ebooks/metadata/book/base.py
index a81ce46c34..6d89049bfb 100644
--- a/src/calibre/ebooks/metadata/book/base.py
+++ b/src/calibre/ebooks/metadata/book/base.py
@@ -11,6 +11,7 @@ import traceback
from calibre import prints
from calibre.ebooks.metadata.book import COPYABLE_METADATA_FIELDS
from calibre.ebooks.metadata.book import STANDARD_METADATA_FIELDS
+from calibre.ebooks.metadata.book import TOP_LEVEL_CLASSIFIERS
from calibre.utils.date import isoformat
@@ -55,6 +56,8 @@ class Metadata(object):
def __getattribute__(self, field):
_data = object.__getattribute__(self, '_data')
+ if field in TOP_LEVEL_CLASSIFIERS:
+ return _data.get('classifiers').get(field, None)
if field in STANDARD_METADATA_FIELDS:
return _data.get(field, None)
try:
@@ -68,7 +71,9 @@ class Metadata(object):
def __setattr__(self, field, val, extra=None):
_data = object.__getattribute__(self, '_data')
- if field in STANDARD_METADATA_FIELDS:
+ if field in TOP_LEVEL_CLASSIFIERS:
+ _data['classifiers'].update({field: val})
+ elif field in STANDARD_METADATA_FIELDS:
if val is None:
val = NULL_VALUES.get(field, None)
_data[field] = val
@@ -301,8 +306,6 @@ class Metadata(object):
fmt('Publisher', self.publisher)
if getattr(self, 'book_producer', False):
fmt('Book Producer', self.book_producer)
- if self.category:
- fmt('Category', self.category)
if self.comments:
fmt('Comments', self.comments)
if self.isbn:
@@ -321,14 +324,7 @@ class Metadata(object):
fmt('Published', isoformat(self.pubdate))
if self.rights is not None:
fmt('Rights', unicode(self.rights))
-# TODO: These are not in metadata. Should they be?
-# if self.lccn:
-# fmt('LCCN', unicode(self.lccn))
-# if self.lcc:
-# fmt('LCC', unicode(self.lcc))
-# if self.ddc:
-# fmt('DDC', unicode(self.ddc))
- # CUSTFIELD: What to do about custom fields?
+ # TODO: NEWMETA: What to do about custom fields?
return u'\n'.join(ans)
def to_html(self):
@@ -339,13 +335,6 @@ class Metadata(object):
ans += [(_('Producer'), unicode(self.book_producer))]
ans += [(_('Comments'), unicode(self.comments))]
ans += [('ISBN', unicode(self.isbn))]
-# TODO: These are not in metadata. Should they be?
-# if self.lccn:
-# ans += [('LCCN', unicode(self.lccn))]
-# if self.lcc:
-# ans += [('LCC', unicode(self.lcc))]
-# if self.ddc:
-# ans += [('DDC', unicode(self.ddc))]
ans += [(_('Tags'), u', '.join([unicode(t) for t in self.tags]))]
if self.series:
ans += [(_('Series'), unicode(self.series)+ ' #%s'%self.format_series_index())]
diff --git a/src/calibre/ebooks/metadata/opf2.py b/src/calibre/ebooks/metadata/opf2.py
index 0ab6d3bbc0..54d97fc157 100644
--- a/src/calibre/ebooks/metadata/opf2.py
+++ b/src/calibre/ebooks/metadata/opf2.py
@@ -1188,7 +1188,7 @@ def metadata_to_opf(mi, as_string=True):
factory(DC('contributor'), mi.book_producer, __appname__, 'bkp')
if hasattr(mi.pubdate, 'isoformat'):
factory(DC('date'), isoformat(mi.pubdate))
- if mi.category:
+ if hasattr(mi, 'category') and mi.category:
factory(DC('type'), mi.category)
if mi.comments:
factory(DC('description'), mi.comments)
diff --git a/src/calibre/gui2/dialogs/config/save_template.py b/src/calibre/gui2/dialogs/config/save_template.py
index 2157d5b3bf..7e49e86d29 100644
--- a/src/calibre/gui2/dialogs/config/save_template.py
+++ b/src/calibre/gui2/dialogs/config/save_template.py
@@ -34,8 +34,8 @@ class SaveTemplate(QWidget, Ui_Form):
self.option_name = name
def validate(self):
- # TODO: I haven't figured out how to get the custom columns into here,
- # so for the moment make all templates valid.
+ # TODO: NEWMETA: I haven't figured out how to get the custom columns
+ # into here, so for the moment make all templates valid.
return True
# tmpl = preprocess_template(self.opt_template.text())
# fa = {}
diff --git a/src/calibre/library/save_to_disk.py b/src/calibre/library/save_to_disk.py
index 7a3515305b..963afd4085 100644
--- a/src/calibre/library/save_to_disk.py
+++ b/src/calibre/library/save_to_disk.py
@@ -131,7 +131,7 @@ def get_components(template, mi, id, timefmt='%b %Y', length=250,
format_args['series_index'] = mi.format_series_index()
else:
template = re.sub(r'\{series_index[^}]*?\}', '', template)
- ## TODO: format custom values. Check all the datatypes.
+ ## TODO: NEWMETA: format custom values. Check all the datatypes.
if mi.rating is not None:
format_args['rating'] = mi.format_rating()
From 6eb7383e82b564a146cd9e719d6424ec8f79c355 Mon Sep 17 00:00:00 2001
From: Charles Haley <>
Date: Mon, 30 Aug 2010 12:16:37 +0100
Subject: [PATCH 006/412] Remove unused (and unworking) copy method
---
src/calibre/ebooks/metadata/book/base.py | 13 +------------
1 file changed, 1 insertion(+), 12 deletions(-)
diff --git a/src/calibre/ebooks/metadata/book/base.py b/src/calibre/ebooks/metadata/book/base.py
index 6d89049bfb..dcb31c3ecc 100644
--- a/src/calibre/ebooks/metadata/book/base.py
+++ b/src/calibre/ebooks/metadata/book/base.py
@@ -185,17 +185,6 @@ class Metadata(object):
return result
# Old Metadata API {{{
- @staticmethod
- def copy(mi):
- ans = Metadata(mi.title, mi.authors)
- for attr in STANDARD_METADATA_FIELDS:
- if hasattr(mi, attr):
- setattr(ans, attr, copy.deepcopy(getattr(mi, attr)))
- for x in mi.user_metadata_keys:
- meta = mi.get_user_metadata(x)
- if meta is not None:
- ans.set_user_metadata(x, meta) # get... did the deep copy
-
def print_all_attributes(self):
for x in STANDARD_METADATA_FIELDS:
prints('%s:'%x, getattr(self, x, 'None'))
@@ -347,7 +336,7 @@ class Metadata(object):
ans += [(_('Rights'), unicode(self.rights))]
for i, x in enumerate(ans):
ans[i] = u'
%s
%s
'%x
- # CUSTFIELD: What to do about custom fields
+ # TODO: NEWMETA: What to do about custom fields
return u'
%s
'%u'\n'.join(ans)
def __str__(self):
From 11f7bd06a82a0b1d03afffd374344593dfb0c5dc Mon Sep 17 00:00:00 2001
From: Charles Haley <>
Date: Mon, 30 Aug 2010 17:47:25 +0100
Subject: [PATCH 007/412] Format custom fields in save_to_disk.
---
src/calibre/library/save_to_disk.py | 16 ++++++++++++++--
1 file changed, 14 insertions(+), 2 deletions(-)
diff --git a/src/calibre/library/save_to_disk.py b/src/calibre/library/save_to_disk.py
index 963afd4085..2bc71cde9c 100644
--- a/src/calibre/library/save_to_disk.py
+++ b/src/calibre/library/save_to_disk.py
@@ -14,6 +14,7 @@ from calibre.utils.filenames import shorten_components_to, supports_long_names,
from calibre.ebooks.metadata.opf2 import metadata_to_opf
from calibre.ebooks.metadata.meta import set_metadata
from calibre.constants import preferred_encoding, filesystem_encoding
+from calibre.ebooks.metadata import fmt_sidx
from calibre.ebooks.metadata import title_sort
from calibre import strftime
@@ -131,8 +132,6 @@ def get_components(template, mi, id, timefmt='%b %Y', length=250,
format_args['series_index'] = mi.format_series_index()
else:
template = re.sub(r'\{series_index[^}]*?\}', '', template)
- ## TODO: NEWMETA: format custom values. Check all the datatypes.
-
if mi.rating is not None:
format_args['rating'] = mi.format_rating()
if hasattr(mi.timestamp, 'timetuple'):
@@ -140,6 +139,19 @@ def get_components(template, mi, id, timefmt='%b %Y', length=250,
if hasattr(mi.pubdate, 'timetuple'):
format_args['pubdate'] = strftime(timefmt, mi.pubdate.timetuple())
format_args['id'] = str(id)
+ # Now format the custom fields
+ custom_metadata = mi.get_all_user_metadata(make_copy=False)
+ for key in custom_metadata:
+ if key in format_args:
+ ## TODO: NEWMETA: should ratings be divided by 2? The standard rating isn't...
+ if custom_metadata[key]['datatype'] == 'series':
+ format_args[key] = tsfmt(format_args[key])
+ if key+'_index' in format_args:
+ format_args[key+'_index'] = fmt_sidx(format_args[key+'_index'])
+ elif custom_metadata[key]['datatype'] == 'datetime':
+ format_args[key] = strftime(timefmt, format_args[key].timetuple())
+ elif custom_metadata[key]['datatype'] == 'bool':
+ format_args[key] = _('yes') if format_args[key] else _('no')
components = [x.strip() for x in template.split('/') if x.strip()]
components = [safe_format(x, format_args) for x in components]
From 16e5b2f3b0204efefe1c73531c4ce98376342fc7 Mon Sep 17 00:00:00 2001
From: Charles Haley <>
Date: Wed, 1 Sep 2010 16:44:12 +0100
Subject: [PATCH 008/412] Add code for Sony collections
---
resources/default_tweaks.py | 32 ++++++-
src/calibre/devices/usbms/books.py | 95 ++++++++++++++------
src/calibre/ebooks/metadata/book/__init__.py | 2 +-
src/calibre/ebooks/metadata/book/base.py | 17 ++--
4 files changed, 111 insertions(+), 35 deletions(-)
diff --git a/resources/default_tweaks.py b/resources/default_tweaks.py
index e03b0680be..e68096ecd5 100644
--- a/resources/default_tweaks.py
+++ b/resources/default_tweaks.py
@@ -90,4 +90,34 @@ save_template_title_series_sorting = 'library_order'
# Examples:
# auto_connect_to_folder = 'C:\\Users\\someone\\Desktop\\testlib'
# auto_connect_to_folder = '/home/dropbox/My Dropbox/someone/library'
-auto_connect_to_folder = ''
\ No newline at end of file
+auto_connect_to_folder = ''
+
+# Specify renaming rules for sony collections. Collections on Sonys are named
+# depending upon whether the field is standard or custom. A collection derived
+# from a standard field is named for the value in that field. For example, if
+# the standard 'series' column contains the name 'Darkover', then the series
+# will be named 'Darkover'. A collection derived from a custom field will have
+# the name of the field added to the value. For example, if a custom series
+# column named 'My Series' contains the name 'Darkover', then the collection
+# will be named 'Darkover (My Series)'. If two books have fields that generate
+# the same collection name, then both books will be in that collection. This
+# tweak lets you specify for a standard or custom field the value to be put
+# inside the parentheses. You can use it to add a parenthetical description to a
+# standard field, for example 'Foo (Tag)' instead of the 'Foo'. You can also use
+# it to force multiple fields to end up in the same collection. For example, you
+# could force the values in 'series', '#my_series_1', and '#my_series_2' to
+# appear in collections named 'some_value (Series)', thereby merging all of the
+# fields into one set of collections. The syntax of this tweak is
+# {'field_lookup_name':'name_to_use', 'lookup_name':'name', ...}
+# Example 1: I want three series columns to be merged into one set of
+# collections. If the column lookup names are 'series', '#series_1' and
+# '#series_2', and if I want nothing in the parenthesis, then the value to use
+# in the tweak value would be:
+# sony_collection_renaming_rules={'series':'', '#series_1':'', '#series_2':''}
+# Example 2: I want the word '(Series)' to appear on collections made from
+# series, and the word '(Tag)' to appear on collections made from tags. Use:
+# sony_collection_renaming_rules={'series':'Series', 'tags':'Tag'}
+# Example 3: I want 'series' and '#myseries' to be merged, and for the
+# collection name to have '(Series)' appended. The renaming rule is:
+# sony_collection_renaming_rules={'series':'Series', '#myseries':'Series'}
+sony_collection_renaming_rules={}
\ No newline at end of file
diff --git a/src/calibre/devices/usbms/books.py b/src/calibre/devices/usbms/books.py
index 0efa507e09..3e13527bd0 100644
--- a/src/calibre/devices/usbms/books.py
+++ b/src/calibre/devices/usbms/books.py
@@ -9,9 +9,10 @@ import os, re, time, sys
from calibre.ebooks.metadata.book.base import Metadata
from calibre.devices.mime import mime_type_ext
from calibre.devices.interface import BookList as _BookList
-from calibre.constants import filesystem_encoding, preferred_encoding
+from calibre.constants import preferred_encoding
from calibre import isbytestring
-from calibre.utils.config import prefs
+from calibre.utils.config import prefs, tweaks
+from calibre.utils.date import format_date
class Book(Metadata):
def __init__(self, prefix, lpath, size=None, other=None):
@@ -94,11 +95,38 @@ class CollectionsBookList(BookList):
def supports_collections(self):
return True
+ def compute_category_name(self, attr, category, cust_field_meta):
+ renames = tweaks['sony_collection_renaming_rules']
+ attr_name = renames.get(attr, None)
+ if attr_name is None:
+ if attr in cust_field_meta:
+ attr_name = '(%s)'%cust_field_meta[attr]['name']
+ else:
+ attr_name = ''
+ elif attr_name != '':
+ attr_name = '(%s)'%attr_name
+
+ if attr not in cust_field_meta:
+ cat_name = '%s %s'%(category, attr_name)
+ else:
+ fm = cust_field_meta[attr]
+ if fm['datatype'] == 'bool':
+ if category:
+ cat_name = '%s %s'%(_('Yes'), attr_name)
+ else:
+ cat_name = '%s %s'%(_('No'), attr_name)
+ elif fm['datatype'] == 'datetime':
+ cat_name = '%s %s'%(format_date(category,
+ fm['display'].get('date_format','dd MMM yyyy')), attr_name)
+ else:
+ cat_name = '%s %s'%(category, attr_name)
+ return cat_name.strip()
+
def get_collections(self, collection_attributes):
from calibre.devices.usbms.driver import debug_print
debug_print('Starting get_collections:', prefs['manage_device_metadata'])
+ debug_print('Renaming rules:', tweaks['sony_collection_renaming_rules'])
collections = {}
- series_categories = set([])
# This map of sets is used to avoid linear searches when testing for
# book equality
collections_lpaths = {}
@@ -124,42 +152,55 @@ class CollectionsBookList(BookList):
# For existing books, modify the collections only if the user
# specified 'on_connect'
attrs = collection_attributes
+ meta_vals = book.get_all_non_none_attributes()
+ cust_field_meta = book.get_all_user_metadata(make_copy=False)
for attr in attrs:
attr = attr.strip()
- val = getattr(book, attr, None)
+ val = meta_vals.get(attr, None)
if not val: continue
if isbytestring(val):
val = val.decode(preferred_encoding, 'replace')
if isinstance(val, (list, tuple)):
val = list(val)
- elif isinstance(val, unicode):
+ else:
val = [val]
for category in val:
- # TODO: NEWMETA: format the custom fields
- if attr == 'tags' and len(category) > 1 and \
- category[0] == '[' and category[-1] == ']':
+ is_series = False
+ if attr in cust_field_meta: # is a custom field
+ fm = cust_field_meta[attr]
+ if fm['datatype'] == 'text' and len(category) > 1 and \
+ category[0] == '[' and category[-1] == ']':
+ continue
+ if fm['datatype'] == 'series':
+ is_series = True
+ else: # is a standard field
+ if attr == 'tags' and len(category) > 1 and \
+ category[0] == '[' and category[-1] == ']':
+ continue
+ if attr == 'series' or \
+ ('series' in collection_attributes and
+ meta_vals.get('series', None) == category):
+ is_series = True
+ cat_name = self.compute_category_name(attr, category,
+ cust_field_meta)
+ if cat_name not in collections:
+ collections[cat_name] = []
+ collections_lpaths[cat_name] = set()
+ if lpath in collections_lpaths[cat_name]:
continue
- if category not in collections:
- collections[category] = []
- collections_lpaths[category] = set()
- if lpath not in collections_lpaths[category]:
- collections_lpaths[category].add(lpath)
- collections[category].append(book)
- if attr == 'series' or \
- ('series' in collection_attributes and
- getattr(book, 'series', None) == category):
- series_categories.add(category)
+ collections_lpaths[cat_name].add(lpath)
+ if is_series:
+ collections[cat_name].append(
+ (book, meta_vals.get(attr+'_index', sys.maxint)))
+ else:
+ collections[cat_name].append(
+ (book, meta_vals.get('title_sort', 'zzzz')))
# Sort collections
+ result = {}
for category, books in collections.items():
- def tgetter(x):
- return getattr(x, 'title_sort', 'zzzz')
- books.sort(cmp=lambda x,y:cmp(tgetter(x), tgetter(y)))
- if category in series_categories:
- # Ensures books are sub sorted by title
- def getter(x):
- return getattr(x, 'series_index', sys.maxint)
- books.sort(cmp=lambda x,y:cmp(getter(x), getter(y)))
- return collections
+ books.sort(cmp=lambda x,y:cmp(x[1], y[1]))
+ result[category] = [x[0] for x in books]
+ return result
def rebuild_collections(self, booklist, oncard):
'''
diff --git a/src/calibre/ebooks/metadata/book/__init__.py b/src/calibre/ebooks/metadata/book/__init__.py
index 47eb616394..ca7f4f7074 100644
--- a/src/calibre/ebooks/metadata/book/__init__.py
+++ b/src/calibre/ebooks/metadata/book/__init__.py
@@ -109,7 +109,7 @@ COPYABLE_METADATA_FIELDS = SOCIAL_METADATA_FIELDS.union(
CALIBRE_METADATA_FIELDS) - \
frozenset(['title', 'title_sort', 'authors',
'author_sort', 'author_sort_map' 'comments',
- 'cover_data', 'tags', 'language'])
+ 'cover_data', 'tags', 'language', 'lpath'])
SERIALIZABLE_FIELDS = SOCIAL_METADATA_FIELDS.union(
USER_METADATA_FIELDS).union(
diff --git a/src/calibre/ebooks/metadata/book/base.py b/src/calibre/ebooks/metadata/book/base.py
index dcb31c3ecc..3f5507d676 100644
--- a/src/calibre/ebooks/metadata/book/base.py
+++ b/src/calibre/ebooks/metadata/book/base.py
@@ -117,16 +117,18 @@ class Metadata(object):
res[k] = copy.deepcopy(user_metadata[k])
return res
- def get_user_metadata(self, field):
+ def get_user_metadata(self, field, make_copy):
'''
return field metadata from the object if it is there. Otherwise return
- None. field is the key name, not the label. Return a copy, just in case
- the user wants to change values in the dict (json does).
+ None. field is the key name, not the label. Return a copy if requested,
+ just in case the user wants to change values in the dict.
'''
_data = object.__getattribute__(self, '_data')
_data = _data['user_metadata']
if field in _data:
- return copy.deepcopy(_data[field])
+ if make_copy:
+ return copy.deepcopy(_data[field])
+ return _data[field]
return None
@classmethod
@@ -189,7 +191,7 @@ class Metadata(object):
for x in STANDARD_METADATA_FIELDS:
prints('%s:'%x, getattr(self, x, 'None'))
for x in self.user_metadata_keys:
- meta = self.get_user_metadata(x)
+ meta = self.get_user_metadata(x, make_copy=False)
if meta is not None:
prints(x, meta)
prints('--------------')
@@ -220,6 +222,9 @@ class Metadata(object):
self.set_all_user_metadata(other.get_all_user_metadata(make_copy=True))
self.comments = getattr(other, 'comments', '')
self.language = getattr(other, 'language', None)
+ lpath = getattr(other, 'lpath', None)
+ if lpath is not None:
+ self.lpath = lpath
else:
for attr in COPYABLE_METADATA_FIELDS:
if hasattr(other, attr):
@@ -240,7 +245,7 @@ class Metadata(object):
if getattr(other, 'user_metadata_keys', None):
for x in other.user_metadata_keys:
- meta = other.get_user_metadata(x)
+ meta = other.get_user_metadata(x, make_copy=True)
if meta is not None:
self.set_user_metadata(x, meta) # get... did the deepcopy
From 0606afc8ff6edb0dfb6042bbc6cde3f891a068e9 Mon Sep 17 00:00:00 2001
From: Charles Haley <>
Date: Thu, 2 Sep 2010 13:19:01 +0100
Subject: [PATCH 009/412] 1) make to_html support custom fields 2) clean up the
_extra code 3) add a format_custom_field method to avoid duplicating code 4)
pass custom metadata to book_details
---
src/calibre/ebooks/metadata/book/base.py | 43 ++++++++++++++++++------
src/calibre/gui2/book_details.py | 2 ++
src/calibre/gui2/library/models.py | 6 +++-
src/calibre/library/database2.py | 4 +--
4 files changed, 42 insertions(+), 13 deletions(-)
diff --git a/src/calibre/ebooks/metadata/book/base.py b/src/calibre/ebooks/metadata/book/base.py
index 3f5507d676..caaaccb3d0 100644
--- a/src/calibre/ebooks/metadata/book/base.py
+++ b/src/calibre/ebooks/metadata/book/base.py
@@ -12,7 +12,8 @@ from calibre import prints
from calibre.ebooks.metadata.book import COPYABLE_METADATA_FIELDS
from calibre.ebooks.metadata.book import STANDARD_METADATA_FIELDS
from calibre.ebooks.metadata.book import TOP_LEVEL_CLASSIFIERS
-from calibre.utils.date import isoformat
+from calibre.utils.date import isoformat, format_date
+
NULL_VALUES = {
@@ -94,6 +95,13 @@ class Metadata(object):
return default
return self.__getattribute__(field)
+ def get_extra(self, field):
+ _data = object.__getattribute__(self, '_data')
+ if field in _data['user_metadata'].iterkeys():
+ return _data['user_metadata'][field]['#extra#']
+ raise AttributeError(
+ 'Metadata object has no attribute named: '+ repr(field))
+
def set(self, field, val, extra=None):
self.__setattr__(field, val, extra)
@@ -131,14 +139,6 @@ class Metadata(object):
return _data[field]
return None
- @classmethod
- def get_user_metadata_value(user_mi):
- return user_mi['#value#']
-
- @classmethod
- def get_user_metadata_extra(user_mi):
- return user_mi['#extra#']
-
def set_all_user_metadata(self, metadata):
'''
store custom field metadata into the object. Field is the key name
@@ -284,6 +284,25 @@ class Metadata(object):
def format_rating(self):
return unicode(self.rating)
+ def format_custom_field(self, key):
+ '''
+ returns the tuple (field_name, formatted_value)
+ '''
+ cmeta = self.get_user_metadata(key, make_copy=False)
+ name = unicode(cmeta['name'])
+ res = self.get(key, None)
+ if res is not None:
+ datatype = cmeta['datatype']
+ if datatype == 'text' and cmeta['is_multiple']:
+ res = u', '.join(res)
+ elif datatype == 'series':
+ res = res + ' [%s]'%self.format_series_index(self.get_extra(key))
+ elif datatype == 'datetime':
+ res = format_date(res, cmeta['display'].get('date_format','dd MMM yyyy'))
+ elif datatype == 'bool':
+ res = _('Yes') if res else _('No')
+ return (name, unicode(res))
+
def __unicode__(self):
from calibre.ebooks.metadata import authors_to_string
ans = []
@@ -339,9 +358,13 @@ class Metadata(object):
ans += [(_('Published'), unicode(self.pubdate.isoformat(' ')))]
if self.rights is not None:
ans += [(_('Rights'), unicode(self.rights))]
+ for key in self.user_metadata_keys:
+ val = self.get(key, None)
+ if val is not None:
+ (name, val) = self.format_custom_field(key)
+ ans += [(name, val)]
for i, x in enumerate(ans):
ans[i] = u'
%s
%s
'%x
- # TODO: NEWMETA: What to do about custom fields
return u'
%s
'%u'\n'.join(ans)
def __str__(self):
diff --git a/src/calibre/gui2/book_details.py b/src/calibre/gui2/book_details.py
index f08dd09429..4e11e0c84f 100644
--- a/src/calibre/gui2/book_details.py
+++ b/src/calibre/gui2/book_details.py
@@ -28,6 +28,8 @@ WEIGHTS[_('Tags')] = 4
def render_rows(data):
keys = data.keys()
+ # First sort by name. The WEIGHTS sort will preserve this sub-order
+ keys.sort(cmp=lambda x, y: cmp(x.lower(), y.lower()))
keys.sort(cmp=lambda x, y: cmp(WEIGHTS[x], WEIGHTS[y]))
rows = []
for key in keys:
diff --git a/src/calibre/gui2/library/models.py b/src/calibre/gui2/library/models.py
index fdf21ecc23..2711756856 100644
--- a/src/calibre/gui2/library/models.py
+++ b/src/calibre/gui2/library/models.py
@@ -323,7 +323,11 @@ class BooksModel(QAbstractTableModel): # {{{
data[_('Series')] = \
_('Book %s of %s.')%\
(sidx, prepare_string_for_xml(series))
-
+ mi = self.db.get_metadata(idx)
+ for key in mi.user_metadata_keys:
+ name, val = mi.format_custom_field(key)
+ if val is not None:
+ data[name] = val
return data
def set_cache(self, idx):
diff --git a/src/calibre/library/database2.py b/src/calibre/library/database2.py
index 6457e12905..bb6d72bcff 100644
--- a/src/calibre/library/database2.py
+++ b/src/calibre/library/database2.py
@@ -1052,8 +1052,8 @@ class LibraryDatabase2(LibraryDatabase, SchemaUpgrade, CustomColumns):
if key in self.field_metadata and \
user_mi[key]['datatype'] == self.field_metadata[key]['datatype']:
doit(self.set_custom, id,
- val=Metadata.get_user_metadata_value(user_mi[key]),
- extra=Metadata.get_user_metadata_extra(user_mi[key]),
+ val=mi.get(key),
+ extra=mi.get_extra(key),
label=user_mi[key]['label'])
self.notify('metadata', [id])
From 7ff7da0fbb2485301142a6ba2e6c04923a6d4a59 Mon Sep 17 00:00:00 2001
From: Charles Haley <>
Date: Fri, 3 Sep 2010 18:43:17 +0100
Subject: [PATCH 010/412] Remove relative import
---
src/calibre/ebooks/metadata/book/json_codec.py | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/src/calibre/ebooks/metadata/book/json_codec.py b/src/calibre/ebooks/metadata/book/json_codec.py
index 7a80e16854..96178c4a63 100644
--- a/src/calibre/ebooks/metadata/book/json_codec.py
+++ b/src/calibre/ebooks/metadata/book/json_codec.py
@@ -9,7 +9,7 @@ import json
import traceback
from PIL import Image
-from . import SERIALIZABLE_FIELDS
+from calibre.ebooks.metadata.book import SERIALIZABLE_FIELDS
from calibre.constants import filesystem_encoding, preferred_encoding
from calibre.library.field_metadata import FieldMetadata
from calibre.utils.date import parse_date, isoformat, UNDEFINED_DATE
From 0ba513e2879b5e476d4787b333fb82b8dbe2e914 Mon Sep 17 00:00:00 2001
From: Charles Haley <>
Date: Sun, 5 Sep 2010 11:01:55 +0100
Subject: [PATCH 011/412] Add a comment about not using the Metadata class, and
why
---
src/calibre/library/server/mobile.py | 4 ++++
src/calibre/library/server/xml.py | 7 ++++---
2 files changed, 8 insertions(+), 3 deletions(-)
diff --git a/src/calibre/library/server/mobile.py b/src/calibre/library/server/mobile.py
index 229e0c21c4..6e08581aed 100644
--- a/src/calibre/library/server/mobile.py
+++ b/src/calibre/library/server/mobile.py
@@ -199,6 +199,10 @@ class MobileServer(object):
CKEYS = [key for key in sorted(CFM.get_custom_fields(),
cmp=lambda x,y: cmp(CFM[x]['name'].lower(),
CFM[y]['name'].lower()))]
+ # This method uses its own book dict, not the Metadata dict. The loop
+ # below could be changed to use db.get_metadata instead of reading
+ # info directly from the record made by the view, but it doesn't seem
+ # worth it at the moment.
books = []
for record in items[(start-1):(start-1)+num]:
book = {'formats':record[FM['formats']], 'size':record[FM['size']]}
diff --git a/src/calibre/library/server/xml.py b/src/calibre/library/server/xml.py
index ed8479980e..5bf2783f96 100644
--- a/src/calibre/library/server/xml.py
+++ b/src/calibre/library/server/xml.py
@@ -66,6 +66,10 @@ class XMLServer(object):
return x.decode(preferred_encoding, 'replace')
return unicode(x)
+ # This method uses its own book dict, not the Metadata dict. The loop
+ # below could be changed to use db.get_metadata instead of reading
+ # info directly from the record made by the view, but it doesn't seem
+ # worth it at the moment.
for record in items[start:start+num]:
kwargs = {}
aus = record[FM['authors']] if record[FM['authors']] else __builtin__._('Unknown')
@@ -138,6 +142,3 @@ class XMLServer(object):
return etree.tostring(ans, encoding='utf-8', pretty_print=True,
xml_declaration=True)
-
-
-
From 38c6199c7b54b243d039aba25cad86125326a582 Mon Sep 17 00:00:00 2001
From: Charles Haley <>
Date: Sun, 5 Sep 2010 11:06:54 +0100
Subject: [PATCH 012/412] Change some comments from 'class:MetaInformation' to
'class:Metadata'
---
src/calibre/customize/__init__.py | 4 ++--
src/calibre/devices/apple/driver.py | 2 +-
src/calibre/ebooks/metadata/epub.py | 2 +-
src/calibre/ebooks/metadata/fetch.py | 2 +-
4 files changed, 5 insertions(+), 5 deletions(-)
diff --git a/src/calibre/customize/__init__.py b/src/calibre/customize/__init__.py
index 27e319de14..8ddc791b2f 100644
--- a/src/calibre/customize/__init__.py
+++ b/src/calibre/customize/__init__.py
@@ -218,7 +218,7 @@ class MetadataReaderPlugin(Plugin): # {{{
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.book.Metadata` object
'''
return None
# }}}
@@ -248,7 +248,7 @@ class MetadataWriterPlugin(Plugin): # {{{
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.book.Metadata` object
'''
pass
diff --git a/src/calibre/devices/apple/driver.py b/src/calibre/devices/apple/driver.py
index 75517e9df7..94aea1e79d 100644
--- a/src/calibre/devices/apple/driver.py
+++ b/src/calibre/devices/apple/driver.py
@@ -872,7 +872,7 @@ class ITUNES(DriverBase):
once uploaded to the device. len(names) == len(files)
:return: A list of 3-element tuples. The list is meant to be passed
to L{add_books_to_metadata}.
- :metadata: If not None, it is a list of :class:`MetaInformation` objects.
+ :metadata: If not None, it is a list of :class:`Metadata` objects.
The idea is to use the metadata to determine where on the device to
put the book. len(metadata) == len(files). Apart from the regular
cover (path to cover), there may also be a thumbnail attribute, which should
diff --git a/src/calibre/ebooks/metadata/epub.py b/src/calibre/ebooks/metadata/epub.py
index 041a1ee603..ac6b5feebe 100644
--- a/src/calibre/ebooks/metadata/epub.py
+++ b/src/calibre/ebooks/metadata/epub.py
@@ -164,7 +164,7 @@ def get_cover(opf, opf_path, stream, reader=None):
return render_html_svg_workaround(cpage, default_log)
def get_metadata(stream, extract_cover=True):
- """ Return metadata as a :class:`MetaInformation` object """
+ """ Return metadata as a :class:`Metadata` object """
stream.seek(0)
reader = OCFZipReader(stream)
mi = MetaInformation(reader.opf)
diff --git a/src/calibre/ebooks/metadata/fetch.py b/src/calibre/ebooks/metadata/fetch.py
index 96807c06ae..9b8a42e482 100644
--- a/src/calibre/ebooks/metadata/fetch.py
+++ b/src/calibre/ebooks/metadata/fetch.py
@@ -29,7 +29,7 @@ class MetadataSource(Plugin): # {{{
future use.
The fetch method must store the results in `self.results` as a list of
- :class:`MetaInformation` objects. If there is an error, it should be stored
+ :class:`Metadata` objects. If there is an error, it should be stored
in `self.exception` and `self.tb` (for the traceback).
'''
From 06b266840c8785aa93aa265c0c20308c1c7286f7 Mon Sep 17 00:00:00 2001
From: Charles Haley <>
Date: Sun, 5 Sep 2010 12:35:35 +0100
Subject: [PATCH 013/412] Make gui version of content server able to show both
abbreviated and full lists of tags.
---
resources/content_server/gui.js | 34 ++++++++++++++++++++++++
src/calibre/ebooks/metadata/book/base.py | 7 ++---
src/calibre/library/server/utils.py | 9 ++++---
src/calibre/library/server/xml.py | 6 +++--
4 files changed, 47 insertions(+), 9 deletions(-)
diff --git a/resources/content_server/gui.js b/resources/content_server/gui.js
index 8368866a5e..abbd409dc8 100644
--- a/resources/content_server/gui.js
+++ b/resources/content_server/gui.js
@@ -59,14 +59,44 @@ function render_book(book) {
title = title.slice(0, title.length-2);
title += ' ({0} MB) '.format(size);
}
+ title += ''
+ if (tags) {
+ t = tags.split(':&:', 2);
+ m = parseInt(t[0]);
+ t = t[1].split(',', m);
+ if (t.length == m) t[m] = '...'
+ title += 'Tags=[{0}] '.format(t.join(','));
+ }
+ custcols = book.attr("custcols").split(',')
+ for ( i = 0; i < custcols.length; i++) {
+ if (custcols[i].length > 0) {
+ vals = book.attr(custcols[i]).split(':#:', 2);
+ if (vals[0].indexOf('#T#') == 0) { //startswith
+ vals[0] = vals[0].substr(3, vals[0].length)
+ t = vals[1].split(':&:', 2);
+ m = parseInt(t[0]);
+ t = t[1].split(',', m);
+ if (t.length == m) t[m] = '...';
+ vals[1] = t.join(',');
+ }
+ title += '{0}=[{1}] '.format(vals[0], vals[1]);
+ }
+ }
+ title += ''
+ title += ''
if (tags) title += 'Tags=[{0}] '.format(tags);
custcols = book.attr("custcols").split(',')
for ( i = 0; i < custcols.length; i++) {
if (custcols[i].length > 0) {
vals = book.attr(custcols[i]).split(':#:', 2);
+ if (vals[0].indexOf('#T#') == 0) { //startswith
+ vals[0] = vals[0].substr(3, vals[0].length)
+ vals[1] = (vals[1].split(':&:', 2))[1];
+ }
title += '{0}=[{1}] '.format(vals[0], vals[1]);
}
}
+ title += ''
title += ''.format(id);
title += '
{0}
'.format(comments)
// Render authors cell
@@ -170,11 +200,15 @@ function fetch_library_books(start, num, timeout, sort, order, search) {
var cover = row.find('img').attr('src');
var collapsed = row.find('.comments').css('display') == 'none';
$("#book_list tbody tr * .comments").css('display', 'none');
+ $("#book_list tbody tr * .tagdata_short").css('display', 'inherit');
+ $("#book_list tbody tr * .tagdata_long").css('display', 'none');
$('#cover_pane').css('visibility', 'hidden');
if (collapsed) {
row.find('.comments').css('display', 'inherit');
$('#cover_pane img').attr('src', cover);
$('#cover_pane').css('visibility', 'visible');
+ row.find(".tagdata_short").css('display', 'none');
+ row.find(".tagdata_long").css('display', 'inherit');
}
});
diff --git a/src/calibre/ebooks/metadata/book/base.py b/src/calibre/ebooks/metadata/book/base.py
index caaaccb3d0..2ff24b0ddc 100644
--- a/src/calibre/ebooks/metadata/book/base.py
+++ b/src/calibre/ebooks/metadata/book/base.py
@@ -262,10 +262,11 @@ class Metadata(object):
if other_lang and other_lang.lower() != 'und':
self.language = other_lang
- def format_series_index(self):
+ def format_series_index(self, val=None):
from calibre.ebooks.metadata import fmt_sidx
+ v = self.series_index if val is None else val
try:
- x = float(self.series_index)
+ x = float(v)
except ValueError:
x = 1
return fmt_sidx(x)
@@ -296,7 +297,7 @@ class Metadata(object):
if datatype == 'text' and cmeta['is_multiple']:
res = u', '.join(res)
elif datatype == 'series':
- res = res + ' [%s]'%self.format_series_index(self.get_extra(key))
+ res = res + ' [%s]'%self.format_series_index(val=self.get_extra(key))
elif datatype == 'datetime':
res = format_date(res, cmeta['display'].get('date_format','dd MMM yyyy'))
elif datatype == 'bool':
diff --git a/src/calibre/library/server/utils.py b/src/calibre/library/server/utils.py
index 23916aa75c..373653c15f 100644
--- a/src/calibre/library/server/utils.py
+++ b/src/calibre/library/server/utils.py
@@ -5,7 +5,7 @@ __license__ = 'GPL v3'
__copyright__ = '2010, Kovid Goyal '
__docformat__ = 'restructuredtext en'
-import time
+import time, sys
import cherrypy
@@ -44,8 +44,8 @@ def strftime(fmt='%Y/%m/%d %H:%M:%S', dt=None):
except:
return _strftime(fmt, nowf().timetuple())
-def format_tag_string(tags, sep):
- MAX = tweaks['max_content_server_tags_shown']
+def format_tag_string(tags, sep, ignore_max=False):
+ MAX = sys.maxint if ignore_max else tweaks['max_content_server_tags_shown']
if tags:
tlist = [t.strip() for t in tags.split(sep)]
else:
@@ -53,5 +53,6 @@ def format_tag_string(tags, sep):
tlist.sort(cmp=lambda x,y:cmp(x.lower(), y.lower()))
if len(tlist) > MAX:
tlist = tlist[:MAX]+['...']
- return u'%s'%(', '.join(tlist)) if tlist else ''
+ return u'%s:&:%s'%(tweaks['max_content_server_tags_shown'],
+ ', '.join(tlist)) if tlist else ''
diff --git a/src/calibre/library/server/xml.py b/src/calibre/library/server/xml.py
index 5bf2783f96..8715dda7d0 100644
--- a/src/calibre/library/server/xml.py
+++ b/src/calibre/library/server/xml.py
@@ -89,7 +89,7 @@ class XMLServer(object):
'comments'):
y = record[FM[x]]
if x == 'tags':
- y = format_tag_string(y, ',')
+ y = format_tag_string(y, ',', ignore_max=True)
kwargs[x] = serialize(y) if y else ''
c = kwargs.pop('comments')
@@ -111,7 +111,9 @@ class XMLServer(object):
name = CFM[key]['name']
custcols.append(k)
if datatype == 'text' and CFM[key]['is_multiple']:
- kwargs[k] = concat(name, format_tag_string(val,'|'))
+ kwargs[k] = concat('#T#'+name,
+ format_tag_string(val,'|',
+ ignore_max=True))
elif datatype == 'series':
kwargs[k] = concat(name, '%s [%s]'%(val,
fmt_sidx(record[CFM.cc_series_index_column_for(key)])))
From 6bad998546cc2c9891bdf48a5ff89ebe8a86b0cd Mon Sep 17 00:00:00 2001
From: Charles Haley <>
Date: Wed, 8 Sep 2010 16:52:40 +0100
Subject: [PATCH 014/412] Add custom fields to the 'unicode' function
---
src/calibre/ebooks/metadata/book/base.py | 6 +++++-
1 file changed, 5 insertions(+), 1 deletion(-)
diff --git a/src/calibre/ebooks/metadata/book/base.py b/src/calibre/ebooks/metadata/book/base.py
index 2ff24b0ddc..648beb7b5c 100644
--- a/src/calibre/ebooks/metadata/book/base.py
+++ b/src/calibre/ebooks/metadata/book/base.py
@@ -338,7 +338,11 @@ class Metadata(object):
fmt('Published', isoformat(self.pubdate))
if self.rights is not None:
fmt('Rights', unicode(self.rights))
- # TODO: NEWMETA: What to do about custom fields?
+ for key in self.user_metadata_keys:
+ val = self.get(key, None)
+ if val is not None:
+ (name, val) = self.format_custom_field(key)
+ fmt(name, unicode(val))
return u'\n'.join(ans)
def to_html(self):
From 9155f838f1f43d878c083bd1a9b9ce955d1c32c8 Mon Sep 17 00:00:00 2001
From: Charles Haley <>
Date: Wed, 8 Sep 2010 19:39:57 +0100
Subject: [PATCH 015/412] Fix some bugs found after or introduced by trunk
merges
---
src/calibre/ebooks/metadata/book/__init__.py | 3 ++-
src/calibre/ebooks/metadata/book/base.py | 26 ++++++++++----------
src/calibre/library/save_to_disk.py | 25 ++++++++++---------
3 files changed, 29 insertions(+), 25 deletions(-)
diff --git a/src/calibre/ebooks/metadata/book/__init__.py b/src/calibre/ebooks/metadata/book/__init__.py
index ca7f4f7074..e7f58ce858 100644
--- a/src/calibre/ebooks/metadata/book/__init__.py
+++ b/src/calibre/ebooks/metadata/book/__init__.py
@@ -109,7 +109,8 @@ COPYABLE_METADATA_FIELDS = SOCIAL_METADATA_FIELDS.union(
CALIBRE_METADATA_FIELDS) - \
frozenset(['title', 'title_sort', 'authors',
'author_sort', 'author_sort_map' 'comments',
- 'cover_data', 'tags', 'language', 'lpath'])
+ 'cover_data', 'tags', 'language', 'lpath',
+ 'size'])
SERIALIZABLE_FIELDS = SOCIAL_METADATA_FIELDS.union(
USER_METADATA_FIELDS).union(
diff --git a/src/calibre/ebooks/metadata/book/base.py b/src/calibre/ebooks/metadata/book/base.py
index 648beb7b5c..69a3c42f4d 100644
--- a/src/calibre/ebooks/metadata/book/base.py
+++ b/src/calibre/ebooks/metadata/book/base.py
@@ -201,6 +201,11 @@ class Metadata(object):
Merge the information in C{other} into self. In case of conflicts, the information
in C{other} takes precedence, unless the information in other is NULL.
'''
+ def copy_not_none(dest, src, attr):
+ v = getattr(src, attr, None)
+ if v is not None:
+ setattr(dest, attr, copy.deepcopy(v))
+
if other.title and other.title != _('Unknown'):
self.title = other.title
if hasattr(other, 'title_sort'):
@@ -220,21 +225,19 @@ class Metadata(object):
self.tags = other.tags
self.cover_data = getattr(other, 'cover_data', '')
self.set_all_user_metadata(other.get_all_user_metadata(make_copy=True))
- self.comments = getattr(other, 'comments', '')
- self.language = getattr(other, 'language', None)
- lpath = getattr(other, 'lpath', None)
- if lpath is not None:
- self.lpath = lpath
+ copy_not_none(self, other, 'lpath')
+ copy_not_none(self, other, 'size')
+ copy_not_none(self, other, 'comments')
+ # language is handled below
else:
for attr in COPYABLE_METADATA_FIELDS:
if hasattr(other, attr):
+ copy_not_none(self, other, attr)
val = getattr(other, attr)
if val is not None:
setattr(self, attr, copy.deepcopy(val))
-
if other.tags:
self.tags += list(set(self.tags + other.tags))
-
if getattr(other, 'cover_data', False):
other_cover = other.cover_data[-1]
self_cover = self.cover_data[-1] if self.cover_data else ''
@@ -242,13 +245,11 @@ class Metadata(object):
if not other_cover: other_cover = ''
if len(other_cover) > len(self_cover):
self.cover_data = other.cover_data
-
if getattr(other, 'user_metadata_keys', None):
for x in other.user_metadata_keys:
meta = other.get_user_metadata(x, make_copy=True)
if meta is not None:
self.set_user_metadata(x, meta) # get... did the deepcopy
-
my_comments = getattr(self, 'comments', '')
other_comments = getattr(other, 'comments', '')
if not my_comments:
@@ -257,10 +258,9 @@ class Metadata(object):
other_comments = ''
if len(other_comments.strip()) > len(my_comments.strip()):
self.comments = other_comments
-
- other_lang = getattr(other, 'language', None)
- if other_lang and other_lang.lower() != 'und':
- self.language = other_lang
+ other_lang = getattr(other, 'language', None)
+ if other_lang and other_lang.lower() != 'und':
+ self.language = other_lang
def format_series_index(self, val=None):
from calibre.ebooks.metadata import fmt_sidx
diff --git a/src/calibre/library/save_to_disk.py b/src/calibre/library/save_to_disk.py
index c940cc006b..d33cbb04b5 100644
--- a/src/calibre/library/save_to_disk.py
+++ b/src/calibre/library/save_to_disk.py
@@ -6,7 +6,7 @@ __license__ = 'GPL v3'
__copyright__ = '2009, Kovid Goyal '
__docformat__ = 'restructuredtext en'
-import os, traceback, cStringIO, re
+import os, traceback, cStringIO, re, string
from calibre.utils.config import Config, StringConfig, tweaks
from calibre.utils.filenames import shorten_components_to, supports_long_names, \
@@ -98,17 +98,20 @@ def preprocess_template(template):
template = template.decode(preferred_encoding, 'replace')
return template
+class SafeFormat(string.Formatter):
+ '''
+ Provides a format function that substitutes '' for any missing value
+ '''
+ def get_value(self, key, args, kwargs):
+ try:
+ return kwargs[key]
+ except:
+ return ''
+safe_formatter = SafeFormat()
+
def safe_format(x, format_args):
- try:
- ans = x.format(**format_args).strip()
- return re.sub(r'\s+', ' ', ans)
- except IndexError: # Thrown if user used [] and index is out of bounds
- pass
- except AttributeError: # Thrown if user used a non existing attribute
- pass
- except KeyError: # Thrown if user used custom field w/value None
- pass
- return ''
+ ans = safe_formatter.vformat(x, [], format_args).strip()
+ return re.sub(r'\s+', ' ', ans)
def get_components(template, mi, id, timefmt='%b %Y', length=250,
sanitize_func=ascii_filename, replace_whitespace=False,
From 7603f208b39032cbcdc42939c0753ed8ddbbe987 Mon Sep 17 00:00:00 2001
From: Charles Haley <>
Date: Wed, 8 Sep 2010 20:29:42 +0100
Subject: [PATCH 016/412] SLight cleanup
---
src/calibre/library/save_to_disk.py | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/src/calibre/library/save_to_disk.py b/src/calibre/library/save_to_disk.py
index d33cbb04b5..3fa40c68b2 100644
--- a/src/calibre/library/save_to_disk.py
+++ b/src/calibre/library/save_to_disk.py
@@ -118,7 +118,7 @@ def get_components(template, mi, id, timefmt='%b %Y', length=250,
to_lowercase=False):
library_order = tweaks['save_template_title_series_sorting'] == 'library_order'
tsfmt = title_sort if library_order else lambda x: x
- format_args = dict(**FORMAT_ARGS)
+ format_args = FORMAT_ARGS.copy()
format_args.update(mi.get_all_non_none_attributes())
if mi.title:
format_args['title'] = tsfmt(mi.title)
From f175b8bf6d75ac8615227c2557dcccc341f493d4 Mon Sep 17 00:00:00 2001
From: Kovid Goyal
Date: Fri, 10 Sep 2010 20:12:12 -0600
Subject: [PATCH 017/412] ...
---
src/calibre/ebooks/metadata/__init__.py | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/src/calibre/ebooks/metadata/__init__.py b/src/calibre/ebooks/metadata/__init__.py
index f64a269fd1..429ba06c6e 100644
--- a/src/calibre/ebooks/metadata/__init__.py
+++ b/src/calibre/ebooks/metadata/__init__.py
@@ -231,7 +231,7 @@ def MetaInformation(title, authors=(_('Unknown'),)):
mi = title
title = mi.title
authors = mi.authors
- return Metadata(title, authors, mi)
+ return Metadata(title, authors, other=mi)
def check_isbn10(isbn):
try:
From 4f01b09ded8a0aa21f273596051ca8f32426bef2 Mon Sep 17 00:00:00 2001
From: Charles Haley <>
Date: Sat, 11 Sep 2010 09:21:29 +0100
Subject: [PATCH 018/412] Check in the metadata class that custom field names
begin with '#'
---
src/calibre/ebooks/metadata/book/base.py | 3 +++
1 file changed, 3 insertions(+)
diff --git a/src/calibre/ebooks/metadata/book/base.py b/src/calibre/ebooks/metadata/book/base.py
index 69a3c42f4d..d0b428bf96 100644
--- a/src/calibre/ebooks/metadata/book/base.py
+++ b/src/calibre/ebooks/metadata/book/base.py
@@ -156,6 +156,9 @@ class Metadata(object):
the key name not the label
'''
if field is not None:
+ if not field.startswith('#'):
+ raise AttributeError(
+ 'Custom field name %s must begin with \'#\''%repr(field))
if metadata is None:
traceback.print_stack()
metadata = copy.deepcopy(metadata)
From afe5546a15aa34fec849c875cb2b9b139c36ebd6 Mon Sep 17 00:00:00 2001
From: Charles Haley <>
Date: Sat, 11 Sep 2010 09:23:03 +0100
Subject: [PATCH 019/412] Avoid spurious exceptions when adding None custom
metadata
---
src/calibre/ebooks/metadata/book/base.py | 1 +
1 file changed, 1 insertion(+)
diff --git a/src/calibre/ebooks/metadata/book/base.py b/src/calibre/ebooks/metadata/book/base.py
index d0b428bf96..be9a4675c0 100644
--- a/src/calibre/ebooks/metadata/book/base.py
+++ b/src/calibre/ebooks/metadata/book/base.py
@@ -161,6 +161,7 @@ class Metadata(object):
'Custom field name %s must begin with \'#\''%repr(field))
if metadata is None:
traceback.print_stack()
+ return
metadata = copy.deepcopy(metadata)
if '#value#' not in metadata:
if metadata['datatype'] == 'text' and metadata['is_multiple']:
From db54abd2b659290ba8a8d598f99fc16c853bf2f7 Mon Sep 17 00:00:00 2001
From: Kovid Goyal
Date: Sun, 12 Sep 2010 12:08:18 -0600
Subject: [PATCH 020/412] Various tweaks to the way smart_update works
---
src/calibre/ebooks/metadata/book/base.py | 34 +++++++++++++++---------
1 file changed, 21 insertions(+), 13 deletions(-)
diff --git a/src/calibre/ebooks/metadata/book/base.py b/src/calibre/ebooks/metadata/book/base.py
index be9a4675c0..f52c41e4c5 100644
--- a/src/calibre/ebooks/metadata/book/base.py
+++ b/src/calibre/ebooks/metadata/book/base.py
@@ -202,12 +202,12 @@ class Metadata(object):
def smart_update(self, other, replace_metadata=False):
'''
- Merge the information in C{other} into self. In case of conflicts, the information
- in C{other} takes precedence, unless the information in other is NULL.
+ Merge the information in `other` into self. In case of conflicts, the information
+ in `other` takes precedence, unless the information in `other` is NULL.
'''
def copy_not_none(dest, src, attr):
v = getattr(src, attr, None)
- if v is not None:
+ if v not in (None, NULL_VALUES.get(attr, None)):
setattr(dest, attr, copy.deepcopy(v))
if other.title and other.title != _('Unknown'):
@@ -216,32 +216,38 @@ class Metadata(object):
self.title_sort = other.title_sort
if other.authors and other.authors[0] != _('Unknown'):
- self.authors = other.authors
+ self.authors = list(other.authors)
if hasattr(other, 'author_sort_map'):
- self.author_sort_map = other.author_sort_map
+ self.author_sort_map = dict(other.author_sort_map)
if hasattr(other, 'author_sort'):
self.author_sort = other.author_sort
if replace_metadata:
+ SPECIAL_FIELDS = frozenset(['lpath', 'size', 'comments'])
for attr in COPYABLE_METADATA_FIELDS:
setattr(self, attr, getattr(other, attr, 1.0 if \
attr == 'series_index' else None))
self.tags = other.tags
- self.cover_data = getattr(other, 'cover_data', '')
+ self.cover_data = getattr(other, 'cover_data',
+ NULL_VALUES['cover_data'])
self.set_all_user_metadata(other.get_all_user_metadata(make_copy=True))
- copy_not_none(self, other, 'lpath')
- copy_not_none(self, other, 'size')
- copy_not_none(self, other, 'comments')
+ for x in SPECIAL_FIELDS:
+ copy_not_none(self, other, x)
# language is handled below
else:
for attr in COPYABLE_METADATA_FIELDS:
if hasattr(other, attr):
copy_not_none(self, other, attr)
- val = getattr(other, attr)
- if val is not None:
- setattr(self, attr, copy.deepcopy(val))
if other.tags:
- self.tags += list(set(self.tags + other.tags))
+ # Case-insensitive but case preserving merging
+ lotags = [t.lower() for t in other.tags]
+ lstags = [t.lower() for t in self.tags]
+ ot, st = map(frozenset, (lotags, lstags))
+ for t in st.interection(ot):
+ sidx = lstags.index(t)
+ oidx = lotags.index(t)
+ self.tags[sidx] = other.tags[oidx]
+ self.tags += [t for t in other.tags if t.lower() in ot-st]
if getattr(other, 'cover_data', False):
other_cover = other.cover_data[-1]
self_cover = self.cover_data[-1] if self.cover_data else ''
@@ -262,6 +268,7 @@ class Metadata(object):
other_comments = ''
if len(other_comments.strip()) > len(my_comments.strip()):
self.comments = other_comments
+
other_lang = getattr(other, 'language', None)
if other_lang and other_lang.lower() != 'und':
self.language = other_lang
@@ -383,3 +390,4 @@ class Metadata(object):
return bool(self.title or self.author or self.comments or self.tags)
# }}}
+
From b01b603358c0332d481a3c572fcd44d63d5cccdf Mon Sep 17 00:00:00 2001
From: Kovid Goyal
Date: Sun, 12 Sep 2010 13:28:22 -0600
Subject: [PATCH 021/412] json_codec: Handle dictionaries with bytsestring
keys/vals as well
---
.../ebooks/metadata/book/json_codec.py | 35 ++++++++++++-------
1 file changed, 22 insertions(+), 13 deletions(-)
diff --git a/src/calibre/ebooks/metadata/book/json_codec.py b/src/calibre/ebooks/metadata/book/json_codec.py
index 0e205c52b0..ea0de07342 100644
--- a/src/calibre/ebooks/metadata/book/json_codec.py
+++ b/src/calibre/ebooks/metadata/book/json_codec.py
@@ -12,23 +12,18 @@ from calibre.ebooks.metadata.book import SERIALIZABLE_FIELDS
from calibre.constants import filesystem_encoding, preferred_encoding
from calibre.library.field_metadata import FieldMetadata
from calibre.utils.date import parse_date, isoformat, UNDEFINED_DATE
+from calibre import isbytestring
# Translate datetimes to and from strings. The string form is the datetime in
# UTC. The returned date is also UTC
def string_to_datetime(src):
if src == "None":
return None
-# dt = strptime(src, '%d %m %Y %H:%M:%S', assume_utc=True, as_utc=True)
-# if dt == UNDEFINED_DATE:
-# return None
return parse_date(src)
def datetime_to_string(dateval):
if dateval is None or dateval == UNDEFINED_DATE:
return "None"
-# tt = date_to_utc(dateval).timetuple()
-# res = "%02d %02d %04d %02d:%02d:%02d"%(tt.tm_mday, tt.tm_mon, tt.tm_year,
-# tt.tm_hour, tt.tm_min, tt.tm_sec)
return isoformat(dateval)
def encode_thumbnail(thumbnail):
@@ -47,6 +42,24 @@ def decode_thumbnail(tup):
return None
return (tup[0], tup[1], b64decode(tup[2]))
+def object_to_unicode(obj, enc=preferred_encoding):
+
+ def dec(x):
+ return x.decode(enc, 'replace')
+
+ if isbytestring(obj):
+ return dec(obj)
+ if isinstance(obj, (list, tuple)):
+ return [dec(x) if isbytestring(x) else x for x in obj]
+ if isinstance(obj, dict):
+ ans = {}
+ for k, v in obj.items():
+ k = object_to_unicode(k)
+ v = object_to_unicode(v)
+ ans[k] = v
+ return ans
+ return obj
+
class JsonCodec(object):
def __init__(self):
@@ -81,16 +94,13 @@ class JsonCodec(object):
value = book.get(key)
if key == 'thumbnail':
return encode_thumbnail(value)
- elif isinstance(value, str): # str includes bytes
+ elif isbytestring(value): # str includes bytes
enc = filesystem_encoding if key == 'lpath' else preferred_encoding
- return value.decode(enc, 'replace')
- elif isinstance(value, (list, tuple)):
- return [x.decode(preferred_encoding, 'replace') if
- isinstance(x, str) else x for x in value]
+ return object_to_unicode(value, enc=enc)
elif datatype == 'datetime':
return datetime_to_string(value)
else:
- return value
+ return object_to_unicode(value)
def decode_from_file(self, file, booklist, book_class, prefix):
js = []
@@ -108,7 +118,6 @@ class JsonCodec(object):
except:
print 'exception during JSON decoding'
traceback.print_exc()
- booklist = []
def decode_metadata(self, key, value):
if key == 'user_metadata':
From ea3719c7737b4c4df60840ed0b86bed872e07a6c Mon Sep 17 00:00:00 2001
From: Charles Haley <>
Date: Sun, 12 Sep 2010 20:52:26 +0100
Subject: [PATCH 022/412] Content server fixes
---
src/calibre/library/server/mobile.py | 8 ++++++--
src/calibre/library/server/opds.py | 10 +++++++---
src/calibre/library/server/utils.py | 7 +++++--
3 files changed, 18 insertions(+), 7 deletions(-)
diff --git a/src/calibre/library/server/mobile.py b/src/calibre/library/server/mobile.py
index 6e08581aed..ab5b39eed8 100644
--- a/src/calibre/library/server/mobile.py
+++ b/src/calibre/library/server/mobile.py
@@ -124,6 +124,7 @@ def build_index(books, num, search, sort, order, start, total, url_base, CKEYS):
series = u'[%s - %s]'%(book['series'], book['series_index']) \
if book['series'] else ''
tags = u'Tags=[%s]'%book['tags'] if book['tags'] else ''
+ print tags
ctext = ''
for key in CKEYS:
@@ -217,7 +218,8 @@ class MobileServer(object):
book['authors'] = authors
book['series_index'] = fmt_sidx(float(record[FM['series_index']]))
book['series'] = record[FM['series']]
- book['tags'] = format_tag_string(record[FM['tags']], ',')
+ book['tags'] = format_tag_string(record[FM['tags']], ',',
+ no_tag_count=True)
book['title'] = record[FM['title']]
for x in ('timestamp', 'pubdate'):
book[x] = strftime('%Y/%m/%d %H:%M:%S', record[FM[x]])
@@ -233,7 +235,9 @@ class MobileServer(object):
continue
name = CFM[key]['name']
if datatype == 'text' and CFM[key]['is_multiple']:
- book[key] = concat(name, format_tag_string(val, '|'))
+ book[key] = concat(name,
+ format_tag_string(val, '|',
+ no_tag_count=True))
elif datatype == 'series':
book[key] = concat(name, '%s [%s]'%(val,
fmt_sidx(record[CFM.cc_series_index_column_for(key)])))
diff --git a/src/calibre/library/server/opds.py b/src/calibre/library/server/opds.py
index c3a1d68749..e495598a2f 100644
--- a/src/calibre/library/server/opds.py
+++ b/src/calibre/library/server/opds.py
@@ -17,6 +17,7 @@ import routes
from calibre.constants import __appname__
from calibre.ebooks.metadata import fmt_sidx
from calibre.library.comments import comments_to_html
+from calibre.library.server.utils import format_tag_string
from calibre import guess_type
from calibre.utils.ordered_dict import OrderedDict
from calibre.utils.date import format_date
@@ -147,8 +148,9 @@ def ACQUISITION_ENTRY(item, version, FM, updated, CFM, CKEYS):
extra.append(_('RATING: %s ')%rating)
tags = item[FM['tags']]
if tags:
- extra.append(_('TAGS: %s ')%\
- ', '.join(tags.split(',')))
+ extra.append(_('TAGS: %s ')%format_tag_string(tags, ',',
+ ignore_max=True,
+ no_tag_count=True))
series = item[FM['series']]
if series:
extra.append(_('SERIES: %s [%s] ')%\
@@ -160,7 +162,9 @@ def ACQUISITION_ENTRY(item, version, FM, updated, CFM, CKEYS):
name = CFM[key]['name']
datatype = CFM[key]['datatype']
if datatype == 'text' and CFM[key]['is_multiple']:
- extra.append('%s: %s '%(name, ', '.join(val.split('|'))))
+ extra.append('%s: %s '%(name, format_tag_string(val, '|',
+ ignore_max=True,
+ no_tag_count=True)))
elif datatype == 'series':
extra.append('%s: %s [%s] '%(name, val,
fmt_sidx(item[CFM.cc_series_index_column_for(key)])))
diff --git a/src/calibre/library/server/utils.py b/src/calibre/library/server/utils.py
index 373653c15f..9a64948a3d 100644
--- a/src/calibre/library/server/utils.py
+++ b/src/calibre/library/server/utils.py
@@ -44,7 +44,7 @@ def strftime(fmt='%Y/%m/%d %H:%M:%S', dt=None):
except:
return _strftime(fmt, nowf().timetuple())
-def format_tag_string(tags, sep, ignore_max=False):
+def format_tag_string(tags, sep, ignore_max=False, no_tag_count=False):
MAX = sys.maxint if ignore_max else tweaks['max_content_server_tags_shown']
if tags:
tlist = [t.strip() for t in tags.split(sep)]
@@ -53,6 +53,9 @@ def format_tag_string(tags, sep, ignore_max=False):
tlist.sort(cmp=lambda x,y:cmp(x.lower(), y.lower()))
if len(tlist) > MAX:
tlist = tlist[:MAX]+['...']
- return u'%s:&:%s'%(tweaks['max_content_server_tags_shown'],
+ if no_tag_count:
+ return ', '.join(tlist) if tlist else ''
+ else:
+ return u'%s:&:%s'%(tweaks['max_content_server_tags_shown'],
', '.join(tlist)) if tlist else ''
From 240c9428f5da2c5452dd7f0ab6cc8a6c8cd67afe Mon Sep 17 00:00:00 2001
From: Kovid Goyal
Date: Sun, 12 Sep 2010 13:57:42 -0600
Subject: [PATCH 023/412] Revert change to how Metadata.thumbnail is
interpreted in library.models
---
src/calibre/gui2/library/models.py | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/src/calibre/gui2/library/models.py b/src/calibre/gui2/library/models.py
index b0aec7446a..b81628cd27 100644
--- a/src/calibre/gui2/library/models.py
+++ b/src/calibre/gui2/library/models.py
@@ -1055,8 +1055,8 @@ class DeviceBooksModel(BooksModel): # {{{
img = QImage()
if hasattr(cdata, 'image_path'):
img.load(cdata.image_path)
- else:
- img.loadFromData(cdata[2])
+ elif cdata:
+ img.loadFromData(cdata)
if img.isNull():
img = self.default_image
data['cover'] = img
From 0a2f80fdbff1b5fb541e567a8735ba5b8ca20ffa Mon Sep 17 00:00:00 2001
From: Charles Haley <>
Date: Sun, 12 Sep 2010 21:13:35 +0100
Subject: [PATCH 024/412] Merge from trunk
---
src/calibre/devices/usbms/books.py | 2 +-
src/calibre/ebooks/metadata/book/__init__.py | 2 +-
src/calibre/ebooks/metadata/book/base.py | 2 +-
3 files changed, 3 insertions(+), 3 deletions(-)
diff --git a/src/calibre/devices/usbms/books.py b/src/calibre/devices/usbms/books.py
index 3e13527bd0..4d5110b049 100644
--- a/src/calibre/devices/usbms/books.py
+++ b/src/calibre/devices/usbms/books.py
@@ -59,7 +59,7 @@ class Book(Metadata):
return property(doc=doc, fget=fget)
@dynamic_property
- def thumbnail(self):
+ def thumbnail(self):'
return None
class BookList(_BookList):
diff --git a/src/calibre/ebooks/metadata/book/__init__.py b/src/calibre/ebooks/metadata/book/__init__.py
index e7f58ce858..84a88606f2 100644
--- a/src/calibre/ebooks/metadata/book/__init__.py
+++ b/src/calibre/ebooks/metadata/book/__init__.py
@@ -110,7 +110,7 @@ COPYABLE_METADATA_FIELDS = SOCIAL_METADATA_FIELDS.union(
frozenset(['title', 'title_sort', 'authors',
'author_sort', 'author_sort_map' 'comments',
'cover_data', 'tags', 'language', 'lpath',
- 'size'])
+ 'size', 'thumbnail'])
SERIALIZABLE_FIELDS = SOCIAL_METADATA_FIELDS.union(
USER_METADATA_FIELDS).union(
diff --git a/src/calibre/ebooks/metadata/book/base.py b/src/calibre/ebooks/metadata/book/base.py
index f52c41e4c5..647a9f467e 100644
--- a/src/calibre/ebooks/metadata/book/base.py
+++ b/src/calibre/ebooks/metadata/book/base.py
@@ -243,7 +243,7 @@ class Metadata(object):
lotags = [t.lower() for t in other.tags]
lstags = [t.lower() for t in self.tags]
ot, st = map(frozenset, (lotags, lstags))
- for t in st.interection(ot):
+ for t in st.intersection(ot):
sidx = lstags.index(t)
oidx = lotags.index(t)
self.tags[sidx] = other.tags[oidx]
From db4b8d8216bd2299d471be43b114e3e05edf1f26 Mon Sep 17 00:00:00 2001
From: Charles Haley <>
Date: Sun, 12 Sep 2010 21:15:31 +0100
Subject: [PATCH 025/412] Fix some inadvertent changes
---
src/calibre/devices/usbms/books.py | 2 +-
src/calibre/ebooks/metadata/book/__init__.py | 2 +-
2 files changed, 2 insertions(+), 2 deletions(-)
diff --git a/src/calibre/devices/usbms/books.py b/src/calibre/devices/usbms/books.py
index 4d5110b049..3e13527bd0 100644
--- a/src/calibre/devices/usbms/books.py
+++ b/src/calibre/devices/usbms/books.py
@@ -59,7 +59,7 @@ class Book(Metadata):
return property(doc=doc, fget=fget)
@dynamic_property
- def thumbnail(self):'
+ def thumbnail(self):
return None
class BookList(_BookList):
diff --git a/src/calibre/ebooks/metadata/book/__init__.py b/src/calibre/ebooks/metadata/book/__init__.py
index 84a88606f2..e7f58ce858 100644
--- a/src/calibre/ebooks/metadata/book/__init__.py
+++ b/src/calibre/ebooks/metadata/book/__init__.py
@@ -110,7 +110,7 @@ COPYABLE_METADATA_FIELDS = SOCIAL_METADATA_FIELDS.union(
frozenset(['title', 'title_sort', 'authors',
'author_sort', 'author_sort_map' 'comments',
'cover_data', 'tags', 'language', 'lpath',
- 'size', 'thumbnail'])
+ 'size'])
SERIALIZABLE_FIELDS = SOCIAL_METADATA_FIELDS.union(
USER_METADATA_FIELDS).union(
From 6ca5263d005a10218008ba74a1a8942ca5c6f74f Mon Sep 17 00:00:00 2001
From: Charles Haley <>
Date: Sun, 12 Sep 2010 21:36:23 +0100
Subject: [PATCH 026/412] Fix typo, add merge is_multiple
---
src/calibre/ebooks/metadata/book/base.py | 13 +++++++++++++
1 file changed, 13 insertions(+)
diff --git a/src/calibre/ebooks/metadata/book/base.py b/src/calibre/ebooks/metadata/book/base.py
index 647a9f467e..f2031afd0e 100644
--- a/src/calibre/ebooks/metadata/book/base.py
+++ b/src/calibre/ebooks/metadata/book/base.py
@@ -259,7 +259,20 @@ class Metadata(object):
for x in other.user_metadata_keys:
meta = other.get_user_metadata(x, make_copy=True)
if meta is not None:
+ self_tags = self.get(x, [])
self.set_user_metadata(x, meta) # get... did the deepcopy
+ other_tags = other.get(x, [])
+ if meta['is_multiple']:
+ # Case-insensitive but case preserving merging
+ lotags = [t.lower() for t in other_tags]
+ lstags = [t.lower() for t in self_tags]
+ ot, st = map(frozenset, (lotags, lstags))
+ for t in st.intersection(ot):
+ sidx = lstags.index(t)
+ oidx = lotags.index(t)
+ self_tags[sidx] = other.tags[oidx]
+ self_tags += [t for t in other.tags if t.lower() in ot-st]
+ setattr(self, x, self_tags)
my_comments = getattr(self, 'comments', '')
other_comments = getattr(other, 'comments', '')
if not my_comments:
From 8b554ee0cda99dd11c50ba6def1634ca2416396b Mon Sep 17 00:00:00 2001
From: Charles Haley <>
Date: Sun, 12 Sep 2010 22:04:10 +0100
Subject: [PATCH 027/412] Fixes for thumbnails.
---
src/calibre/ebooks/metadata/book/__init__.py | 2 +-
src/calibre/ebooks/metadata/book/base.py | 3 ++-
2 files changed, 3 insertions(+), 2 deletions(-)
diff --git a/src/calibre/ebooks/metadata/book/__init__.py b/src/calibre/ebooks/metadata/book/__init__.py
index e7f58ce858..84a88606f2 100644
--- a/src/calibre/ebooks/metadata/book/__init__.py
+++ b/src/calibre/ebooks/metadata/book/__init__.py
@@ -110,7 +110,7 @@ COPYABLE_METADATA_FIELDS = SOCIAL_METADATA_FIELDS.union(
frozenset(['title', 'title_sort', 'authors',
'author_sort', 'author_sort_map' 'comments',
'cover_data', 'tags', 'language', 'lpath',
- 'size'])
+ 'size', 'thumbnail'])
SERIALIZABLE_FIELDS = SOCIAL_METADATA_FIELDS.union(
USER_METADATA_FIELDS).union(
diff --git a/src/calibre/ebooks/metadata/book/base.py b/src/calibre/ebooks/metadata/book/base.py
index f2031afd0e..7812f81180 100644
--- a/src/calibre/ebooks/metadata/book/base.py
+++ b/src/calibre/ebooks/metadata/book/base.py
@@ -223,7 +223,7 @@ class Metadata(object):
self.author_sort = other.author_sort
if replace_metadata:
- SPECIAL_FIELDS = frozenset(['lpath', 'size', 'comments'])
+ SPECIAL_FIELDS = frozenset(['lpath', 'size', 'comments', 'thumbnail'])
for attr in COPYABLE_METADATA_FIELDS:
setattr(self, attr, getattr(other, attr, 1.0 if \
attr == 'series_index' else None))
@@ -238,6 +238,7 @@ class Metadata(object):
for attr in COPYABLE_METADATA_FIELDS:
if hasattr(other, attr):
copy_not_none(self, other, attr)
+ copy_not_none(self, other, 'thumbnail')
if other.tags:
# Case-insensitive but case preserving merging
lotags = [t.lower() for t in other.tags]
From 171ac9488aeadfe1b6fd0605c17bbc71662726ed Mon Sep 17 00:00:00 2001
From: Charles Haley <>
Date: Sun, 12 Sep 2010 22:32:20 +0100
Subject: [PATCH 028/412] An attempt to make covers work.
---
src/calibre/gui2/library/models.py | 5 ++++-
1 file changed, 4 insertions(+), 1 deletion(-)
diff --git a/src/calibre/gui2/library/models.py b/src/calibre/gui2/library/models.py
index b81628cd27..4e8e9a10bd 100644
--- a/src/calibre/gui2/library/models.py
+++ b/src/calibre/gui2/library/models.py
@@ -1056,7 +1056,10 @@ class DeviceBooksModel(BooksModel): # {{{
if hasattr(cdata, 'image_path'):
img.load(cdata.image_path)
elif cdata:
- img.loadFromData(cdata)
+ if isinstance(cdata, tuple):
+ img.loadFromData(cdata[2])
+ else:
+ img.loadFromData(cdata)
if img.isNull():
img = self.default_image
data['cover'] = img
From e73b688ca8ee61b39a65dbfb40d36863b74800f2 Mon Sep 17 00:00:00 2001
From: Charles Haley <>
Date: Sun, 12 Sep 2010 22:44:36 +0100
Subject: [PATCH 029/412] Deal with the two thumbnail formats
---
src/calibre/ebooks/metadata/book/json_codec.py | 8 +++++++-
1 file changed, 7 insertions(+), 1 deletion(-)
diff --git a/src/calibre/ebooks/metadata/book/json_codec.py b/src/calibre/ebooks/metadata/book/json_codec.py
index ea0de07342..a6235e64d5 100644
--- a/src/calibre/ebooks/metadata/book/json_codec.py
+++ b/src/calibre/ebooks/metadata/book/json_codec.py
@@ -12,6 +12,7 @@ from calibre.ebooks.metadata.book import SERIALIZABLE_FIELDS
from calibre.constants import filesystem_encoding, preferred_encoding
from calibre.library.field_metadata import FieldMetadata
from calibre.utils.date import parse_date, isoformat, UNDEFINED_DATE
+from calibre.utils.magick.draw import identify_data
from calibre import isbytestring
# Translate datetimes to and from strings. The string form is the datetime in
@@ -32,7 +33,12 @@ def encode_thumbnail(thumbnail):
'''
if thumbnail is None:
return None
- return (thumbnail[0], thumbnail[1], b64encode(str(thumbnail[2])))
+ if isinstance(thumbnail, tuple):
+ try:
+ thumbnail = identify_data(thumbnail)
+ except:
+ return None
+ return (0, 0, b64encode(str(thumbnail)))
def decode_thumbnail(tup):
'''
From e0261c2ba1e53300275849818df0b78729f99b98 Mon Sep 17 00:00:00 2001
From: Charles Haley <>
Date: Sun, 12 Sep 2010 22:50:34 +0100
Subject: [PATCH 030/412] This time, do json thumbnails right.
---
src/calibre/ebooks/metadata/book/json_codec.py | 11 +++++++----
1 file changed, 7 insertions(+), 4 deletions(-)
diff --git a/src/calibre/ebooks/metadata/book/json_codec.py b/src/calibre/ebooks/metadata/book/json_codec.py
index a6235e64d5..51b9722803 100644
--- a/src/calibre/ebooks/metadata/book/json_codec.py
+++ b/src/calibre/ebooks/metadata/book/json_codec.py
@@ -12,7 +12,7 @@ from calibre.ebooks.metadata.book import SERIALIZABLE_FIELDS
from calibre.constants import filesystem_encoding, preferred_encoding
from calibre.library.field_metadata import FieldMetadata
from calibre.utils.date import parse_date, isoformat, UNDEFINED_DATE
-from calibre.utils.magick.draw import identify_data
+from calibre.utils.magick import Image
from calibre import isbytestring
# Translate datetimes to and from strings. The string form is the datetime in
@@ -33,12 +33,15 @@ def encode_thumbnail(thumbnail):
'''
if thumbnail is None:
return None
- if isinstance(thumbnail, tuple):
+ if not isinstance(thumbnail, tuple):
try:
- thumbnail = identify_data(thumbnail)
+ img = Image()
+ img.load(thumbnail)
+ width, height = img.size
+ thumbnail = (width, height, thumbnail)
except:
return None
- return (0, 0, b64encode(str(thumbnail)))
+ return (thumbnail[0], thumbnail[1], b64encode(str(thumbnail[2])))
def decode_thumbnail(tup):
'''
From be95815b6ce234602f1fe6f76a51a3571db06535 Mon Sep 17 00:00:00 2001
From: Kovid Goyal
Date: Sun, 12 Sep 2010 18:10:52 -0600
Subject: [PATCH 031/412] ...
---
src/calibre/gui2/library/models.py | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/src/calibre/gui2/library/models.py b/src/calibre/gui2/library/models.py
index 4e8e9a10bd..09a28fb04e 100644
--- a/src/calibre/gui2/library/models.py
+++ b/src/calibre/gui2/library/models.py
@@ -1056,8 +1056,8 @@ class DeviceBooksModel(BooksModel): # {{{
if hasattr(cdata, 'image_path'):
img.load(cdata.image_path)
elif cdata:
- if isinstance(cdata, tuple):
- img.loadFromData(cdata[2])
+ if isinstance(cdata, (tuple, list)):
+ img.loadFromData(cdata[-1])
else:
img.loadFromData(cdata)
if img.isNull():
From 43adf4226a6d381cf121ad5871b9237af57c58af Mon Sep 17 00:00:00 2001
From: Charles Haley <>
Date: Mon, 13 Sep 2010 08:18:09 +0100
Subject: [PATCH 032/412] Rationalize how smart_update knows what to do.
Introduced 2 more metadata groups. One is the list of attributes that
smart_update is to process specially. The other is the list of attributes
that are to be copied if not none (what you called SPECIAL_FIELDS). These two
help keep the replace and merge branches in sync.
---
src/calibre/ebooks/metadata/book/__init__.py | 17 ++++++++++-----
src/calibre/ebooks/metadata/book/base.py | 23 ++++++++++++--------
2 files changed, 26 insertions(+), 14 deletions(-)
diff --git a/src/calibre/ebooks/metadata/book/__init__.py b/src/calibre/ebooks/metadata/book/__init__.py
index 84a88606f2..e087f8072d 100644
--- a/src/calibre/ebooks/metadata/book/__init__.py
+++ b/src/calibre/ebooks/metadata/book/__init__.py
@@ -101,16 +101,23 @@ STANDARD_METADATA_FIELDS = SOCIAL_METADATA_FIELDS.union(
DEVICE_METADATA_FIELDS).union(
CALIBRE_METADATA_FIELDS)
+# Metadata fields that smart update must do special processing to copy.
+
+SC_FIELDS_NOT_COPIED = frozenset(['title', 'title_sort', 'authors',
+ 'author_sort', 'author_sort_map',
+ 'cover_data', 'tags', 'language'])
+
+# Metadata fields that smart update should copy only if the source is not None
+SC_FIELDS_COPY_NOT_NULL = frozenset(['lpath', 'size', 'comments', 'thumbnail'])
+
# Metadata fields that smart update should copy without special handling
-COPYABLE_METADATA_FIELDS = SOCIAL_METADATA_FIELDS.union(
+SC_COPYABLE_FIELDS = SOCIAL_METADATA_FIELDS.union(
PUBLICATION_METADATA_FIELDS).union(
BOOK_STRUCTURE_FIELDS).union(
DEVICE_METADATA_FIELDS).union(
CALIBRE_METADATA_FIELDS) - \
- frozenset(['title', 'title_sort', 'authors',
- 'author_sort', 'author_sort_map' 'comments',
- 'cover_data', 'tags', 'language', 'lpath',
- 'size', 'thumbnail'])
+ SC_FIELDS_NOT_COPIED.union(
+ SC_FIELDS_COPY_NOT_NULL)
SERIALIZABLE_FIELDS = SOCIAL_METADATA_FIELDS.union(
USER_METADATA_FIELDS).union(
diff --git a/src/calibre/ebooks/metadata/book/base.py b/src/calibre/ebooks/metadata/book/base.py
index 7812f81180..8538ed886c 100644
--- a/src/calibre/ebooks/metadata/book/base.py
+++ b/src/calibre/ebooks/metadata/book/base.py
@@ -9,7 +9,8 @@ import copy
import traceback
from calibre import prints
-from calibre.ebooks.metadata.book import COPYABLE_METADATA_FIELDS
+from calibre.ebooks.metadata.book import SC_COPYABLE_FIELDS
+from calibre.ebooks.metadata.book import SC_FIELDS_COPY_NOT_NULL
from calibre.ebooks.metadata.book import STANDARD_METADATA_FIELDS
from calibre.ebooks.metadata.book import TOP_LEVEL_CLASSIFIERS
from calibre.utils.date import isoformat, format_date
@@ -223,22 +224,23 @@ class Metadata(object):
self.author_sort = other.author_sort
if replace_metadata:
- SPECIAL_FIELDS = frozenset(['lpath', 'size', 'comments', 'thumbnail'])
- for attr in COPYABLE_METADATA_FIELDS:
+ # SPECIAL_FIELDS = frozenset(['lpath', 'size', 'comments', 'thumbnail'])
+ for attr in SC_COPYABLE_FIELDS:
setattr(self, attr, getattr(other, attr, 1.0 if \
attr == 'series_index' else None))
self.tags = other.tags
self.cover_data = getattr(other, 'cover_data',
- NULL_VALUES['cover_data'])
+ NULL_VALUES['cover_data'])
self.set_all_user_metadata(other.get_all_user_metadata(make_copy=True))
- for x in SPECIAL_FIELDS:
+ for x in SC_FIELDS_COPY_NOT_NULL:
copy_not_none(self, other, x)
# language is handled below
else:
- for attr in COPYABLE_METADATA_FIELDS:
- if hasattr(other, attr):
- copy_not_none(self, other, attr)
- copy_not_none(self, other, 'thumbnail')
+ for attr in SC_COPYABLE_FIELDS:
+ copy_not_none(self, other, attr)
+ for x in SC_FIELDS_COPY_NOT_NULL:
+ copy_not_none(self, other, x)
+
if other.tags:
# Case-insensitive but case preserving merging
lotags = [t.lower() for t in other.tags]
@@ -249,6 +251,7 @@ class Metadata(object):
oidx = lotags.index(t)
self.tags[sidx] = other.tags[oidx]
self.tags += [t for t in other.tags if t.lower() in ot-st]
+
if getattr(other, 'cover_data', False):
other_cover = other.cover_data[-1]
self_cover = self.cover_data[-1] if self.cover_data else ''
@@ -256,6 +259,7 @@ class Metadata(object):
if not other_cover: other_cover = ''
if len(other_cover) > len(self_cover):
self.cover_data = other.cover_data
+
if getattr(other, 'user_metadata_keys', None):
for x in other.user_metadata_keys:
meta = other.get_user_metadata(x, make_copy=True)
@@ -274,6 +278,7 @@ class Metadata(object):
self_tags[sidx] = other.tags[oidx]
self_tags += [t for t in other.tags if t.lower() in ot-st]
setattr(self, x, self_tags)
+
my_comments = getattr(self, 'comments', '')
other_comments = getattr(other, 'comments', '')
if not my_comments:
From a85be2ba3229f2e27221fb7f64d25a7e48977ab7 Mon Sep 17 00:00:00 2001
From: Charles Haley <>
Date: Mon, 13 Sep 2010 11:59:45 +0100
Subject: [PATCH 033/412] Several changes: 1) allow use of unusual standard
fields in get_collections. Format them appropriately 2) change
metadata.book.base.format_field to handle standard fields. 3) add standard
metadata access methods to metadata.book.base.
---
src/calibre/devices/usbms/books.py | 18 +-----
src/calibre/ebooks/metadata/book/base.py | 71 +++++++++++++++++++++---
src/calibre/gui2/library/models.py | 2 +-
src/calibre/library/field_metadata.py | 17 ++++--
4 files changed, 76 insertions(+), 32 deletions(-)
diff --git a/src/calibre/devices/usbms/books.py b/src/calibre/devices/usbms/books.py
index 3e13527bd0..2b19027df4 100644
--- a/src/calibre/devices/usbms/books.py
+++ b/src/calibre/devices/usbms/books.py
@@ -105,21 +105,7 @@ class CollectionsBookList(BookList):
attr_name = ''
elif attr_name != '':
attr_name = '(%s)'%attr_name
-
- if attr not in cust_field_meta:
- cat_name = '%s %s'%(category, attr_name)
- else:
- fm = cust_field_meta[attr]
- if fm['datatype'] == 'bool':
- if category:
- cat_name = '%s %s'%(_('Yes'), attr_name)
- else:
- cat_name = '%s %s'%(_('No'), attr_name)
- elif fm['datatype'] == 'datetime':
- cat_name = '%s %s'%(format_date(category,
- fm['display'].get('date_format','dd MMM yyyy')), attr_name)
- else:
- cat_name = '%s %s'%(category, attr_name)
+ cat_name = '%s %s'%(category, attr_name)
return cat_name.strip()
def get_collections(self, collection_attributes):
@@ -156,7 +142,7 @@ class CollectionsBookList(BookList):
cust_field_meta = book.get_all_user_metadata(make_copy=False)
for attr in attrs:
attr = attr.strip()
- val = meta_vals.get(attr, None)
+ ign, val = book.format_field(attr, ignore_series_index=True)
if not val: continue
if isbytestring(val):
val = val.decode(preferred_encoding, 'replace')
diff --git a/src/calibre/ebooks/metadata/book/base.py b/src/calibre/ebooks/metadata/book/base.py
index 8538ed886c..6e0351353f 100644
--- a/src/calibre/ebooks/metadata/book/base.py
+++ b/src/calibre/ebooks/metadata/book/base.py
@@ -13,6 +13,7 @@ from calibre.ebooks.metadata.book import SC_COPYABLE_FIELDS
from calibre.ebooks.metadata.book import SC_FIELDS_COPY_NOT_NULL
from calibre.ebooks.metadata.book import STANDARD_METADATA_FIELDS
from calibre.ebooks.metadata.book import TOP_LEVEL_CLASSIFIERS
+from calibre.library.field_metadata import FieldMetadata
from calibre.utils.date import isoformat, format_date
@@ -30,6 +31,8 @@ NULL_VALUES = {
'language' : 'und'
}
+field_metadata = FieldMetadata()
+
class Metadata(object):
'''
@@ -112,6 +115,31 @@ class Metadata(object):
_data = object.__getattribute__(self, '_data')
return frozenset(_data['user_metadata'].iterkeys())
+ def get_standard_metadata(self, field, make_copy):
+ '''
+ return field metadata from the field if it is there. Otherwise return
+ None. field is the key name, not the label. Return a copy if requested,
+ just in case the user wants to change values in the dict.
+ '''
+ if field in field_metadata and field_metadata[field]['kind'] == 'field':
+ if make_copy:
+ return copy.deepcopy(field_metadata[field])
+ return field_metadata[field]
+ return None
+
+ def get_all_standard_metadata(self, make_copy):
+ '''
+ return a dict containing all the standard field metadata associated with
+ the book.
+ '''
+ if not make_copy:
+ return field_metadata
+ res = {}
+ for k in field_metadata:
+ if field_metadata[k]['kind'] == 'field':
+ res[k] = copy.deepcopy(field_metadata[k])
+ return res
+
def get_all_user_metadata(self, make_copy):
'''
return a dict containing all the custom field metadata associated with
@@ -315,24 +343,49 @@ class Metadata(object):
def format_rating(self):
return unicode(self.rating)
- def format_custom_field(self, key):
+ def format_field(self, key, ignore_series_index=False):
+ from calibre.ebooks.metadata import authors_to_string
'''
returns the tuple (field_name, formatted_value)
'''
- cmeta = self.get_user_metadata(key, make_copy=False)
- name = unicode(cmeta['name'])
- res = self.get(key, None)
- if res is not None:
+ if key in self.user_metadata_keys:
+ res = self.get(key, None)
+ if res is None or res == '':
+ return (None, None)
+ cmeta = self.get_user_metadata(key, make_copy=False)
+ name = unicode(cmeta['name'])
datatype = cmeta['datatype']
if datatype == 'text' and cmeta['is_multiple']:
res = u', '.join(res)
elif datatype == 'series':
- res = res + ' [%s]'%self.format_series_index(val=self.get_extra(key))
+ if not ignore_series_index:
+ res = res + \
+ ' [%s]'%self.format_series_index(val=self.get_extra(key))
elif datatype == 'datetime':
res = format_date(res, cmeta['display'].get('date_format','dd MMM yyyy'))
elif datatype == 'bool':
res = _('Yes') if res else _('No')
- return (name, unicode(res))
+ return (name, unicode(res))
+
+ if key in field_metadata and field_metadata[key]['kind'] == 'field':
+ res = self.get(key, None)
+ if res is None or res == '':
+ return (None, None)
+ fmeta = field_metadata[key]
+ name = unicode(fmeta['name'])
+ datatype = fmeta['datatype']
+ if key == 'authors':
+ res = authors_to_string(res)
+ elif datatype == 'text' and fmeta['is_multiple']:
+ res = u', '.join(res)
+ elif datatype == 'series':
+ if not ignore_series_index:
+ res = res + ' [%s]'%self.format_series_index()
+ elif datatype == 'datetime':
+ res = format_date(res, fmeta['display'].get('date_format','dd MMM yyyy'))
+ return (name, unicode(res))
+
+ return (None, None)
def __unicode__(self):
from calibre.ebooks.metadata import authors_to_string
@@ -371,7 +424,7 @@ class Metadata(object):
for key in self.user_metadata_keys:
val = self.get(key, None)
if val is not None:
- (name, val) = self.format_custom_field(key)
+ (name, val) = self.format_field(key)
fmt(name, unicode(val))
return u'\n'.join(ans)
@@ -396,7 +449,7 @@ class Metadata(object):
for key in self.user_metadata_keys:
val = self.get(key, None)
if val is not None:
- (name, val) = self.format_custom_field(key)
+ (name, val) = self.format_field(key)
ans += [(name, val)]
for i, x in enumerate(ans):
ans[i] = u'
%s
%s
'%x
diff --git a/src/calibre/gui2/library/models.py b/src/calibre/gui2/library/models.py
index 09a28fb04e..5fa514ae8a 100644
--- a/src/calibre/gui2/library/models.py
+++ b/src/calibre/gui2/library/models.py
@@ -320,7 +320,7 @@ class BooksModel(QAbstractTableModel): # {{{
(sidx, prepare_string_for_xml(series))
mi = self.db.get_metadata(idx)
for key in mi.user_metadata_keys:
- name, val = mi.format_custom_field(key)
+ name, val = mi.format_field(key)
if val is not None:
data[name] = val
return data
diff --git a/src/calibre/library/field_metadata.py b/src/calibre/library/field_metadata.py
index 276a6ba971..2773f573b2 100644
--- a/src/calibre/library/field_metadata.py
+++ b/src/calibre/library/field_metadata.py
@@ -5,6 +5,7 @@ Created on 25 May 2010
'''
from calibre.utils.ordered_dict import OrderedDict
+from calibre.utils.config import tweaks
class TagsIcons(dict):
'''
@@ -213,7 +214,7 @@ class FieldMetadata(dict):
'datatype':'text',
'is_multiple':None,
'kind':'field',
- 'name':None,
+ 'name':_('On Device'),
'search_terms':['ondevice'],
'is_custom':False,
'is_category':False}),
@@ -231,7 +232,7 @@ class FieldMetadata(dict):
'datatype':'datetime',
'is_multiple':None,
'kind':'field',
- 'name':None,
+ 'name':_('Published'),
'search_terms':['pubdate'],
'is_custom':False,
'is_category':False}),
@@ -258,7 +259,7 @@ class FieldMetadata(dict):
'datatype':'float',
'is_multiple':None,
'kind':'field',
- 'name':None,
+ 'name':_('Size (MB)'),
'search_terms':['size'],
'is_custom':False,
'is_category':False}),
@@ -267,7 +268,7 @@ class FieldMetadata(dict):
'datatype':'datetime',
'is_multiple':None,
'kind':'field',
- 'name':None,
+ 'name':_('Date'),
'search_terms':['date'],
'is_custom':False,
'is_category':False}),
@@ -276,7 +277,7 @@ class FieldMetadata(dict):
'datatype':'text',
'is_multiple':None,
'kind':'field',
- 'name':None,
+ 'name':_('Title'),
'search_terms':['title'],
'is_custom':False,
'is_category':False}),
@@ -310,6 +311,10 @@ class FieldMetadata(dict):
self._tb_cats[k]['display'] = {}
self._tb_cats[k]['is_editable'] = True
self._add_search_terms_to_map(k, v['search_terms'])
+ self._tb_cats['timestamp']['display'] = {
+ 'date_format': tweaks['gui_timestamp_display_format']}
+ self._tb_cats['pubdate']['display'] = {
+ 'date_format': tweaks['gui_pubdate_display_format']}
self.custom_field_prefix = '#'
self.get = self._tb_cats.get
@@ -410,7 +415,7 @@ class FieldMetadata(dict):
if datatype == 'series':
key += '_index'
self._tb_cats[key] = {'table':None, 'column':None,
- 'datatype':'float', 'is_multiple':False,
+ 'datatype':'float', 'is_multiple':None,
'kind':'field', 'name':'',
'search_terms':[key], 'label':label+'_index',
'colnum':None, 'display':{},
From 2a654f3062401e864cdd357850033ad05937f34e Mon Sep 17 00:00:00 2001
From: Charles Haley <>
Date: Tue, 14 Sep 2010 07:41:06 +0100
Subject: [PATCH 034/412] Fix stupidity in collectiions_management where I
broke tag splitting
---
src/calibre/devices/usbms/books.py | 4 +++-
src/calibre/ebooks/metadata/book/base.py | 13 ++++++++-----
2 files changed, 11 insertions(+), 6 deletions(-)
diff --git a/src/calibre/devices/usbms/books.py b/src/calibre/devices/usbms/books.py
index cf60f1311c..d25787fc89 100644
--- a/src/calibre/devices/usbms/books.py
+++ b/src/calibre/devices/usbms/books.py
@@ -141,7 +141,9 @@ class CollectionsBookList(BookList):
cust_field_meta = book.get_all_user_metadata(make_copy=False)
for attr in attrs:
attr = attr.strip()
- ign, val = book.format_field(attr, ignore_series_index=True)
+ ign, val = book.format_field(attr,
+ ignore_series_index=True,
+ return_multiples_as_list=True)
if not val: continue
if isbytestring(val):
val = val.decode(preferred_encoding, 'replace')
diff --git a/src/calibre/ebooks/metadata/book/base.py b/src/calibre/ebooks/metadata/book/base.py
index 6e0351353f..7405f20a7c 100644
--- a/src/calibre/ebooks/metadata/book/base.py
+++ b/src/calibre/ebooks/metadata/book/base.py
@@ -343,7 +343,8 @@ class Metadata(object):
def format_rating(self):
return unicode(self.rating)
- def format_field(self, key, ignore_series_index=False):
+ def format_field(self, key, ignore_series_index=False,
+ return_multiples_as_list=False):
from calibre.ebooks.metadata import authors_to_string
'''
returns the tuple (field_name, formatted_value)
@@ -356,7 +357,8 @@ class Metadata(object):
name = unicode(cmeta['name'])
datatype = cmeta['datatype']
if datatype == 'text' and cmeta['is_multiple']:
- res = u', '.join(res)
+ if not return_multiples_as_list:
+ res = u', '.join(res)
elif datatype == 'series':
if not ignore_series_index:
res = res + \
@@ -365,7 +367,7 @@ class Metadata(object):
res = format_date(res, cmeta['display'].get('date_format','dd MMM yyyy'))
elif datatype == 'bool':
res = _('Yes') if res else _('No')
- return (name, unicode(res))
+ return (name, res)
if key in field_metadata and field_metadata[key]['kind'] == 'field':
res = self.get(key, None)
@@ -377,13 +379,14 @@ class Metadata(object):
if key == 'authors':
res = authors_to_string(res)
elif datatype == 'text' and fmeta['is_multiple']:
- res = u', '.join(res)
+ if not return_multiples_as_list:
+ res = u', '.join(res)
elif datatype == 'series':
if not ignore_series_index:
res = res + ' [%s]'%self.format_series_index()
elif datatype == 'datetime':
res = format_date(res, fmeta['display'].get('date_format','dd MMM yyyy'))
- return (name, unicode(res))
+ return (name, res)
return (None, None)
From 6653fff4cd3228629c879e0e534024a9cc203fd2 Mon Sep 17 00:00:00 2001
From: Kovid Goyal
Date: Wed, 15 Sep 2010 21:20:55 -0600
Subject: [PATCH 035/412] OPF serialization of user metadata
---
.../ebooks/metadata/book/json_codec.py | 2 +-
src/calibre/ebooks/metadata/opf2.py | 108 ++++++++++++++++--
2 files changed, 98 insertions(+), 12 deletions(-)
diff --git a/src/calibre/ebooks/metadata/book/json_codec.py b/src/calibre/ebooks/metadata/book/json_codec.py
index 51b9722803..2550089473 100644
--- a/src/calibre/ebooks/metadata/book/json_codec.py
+++ b/src/calibre/ebooks/metadata/book/json_codec.py
@@ -33,7 +33,7 @@ def encode_thumbnail(thumbnail):
'''
if thumbnail is None:
return None
- if not isinstance(thumbnail, tuple):
+ if not isinstance(thumbnail, (tuple, list)):
try:
img = Image()
img.load(thumbnail)
diff --git a/src/calibre/ebooks/metadata/opf2.py b/src/calibre/ebooks/metadata/opf2.py
index be8507f478..236b2fa18f 100644
--- a/src/calibre/ebooks/metadata/opf2.py
+++ b/src/calibre/ebooks/metadata/opf2.py
@@ -7,7 +7,7 @@ __docformat__ = 'restructuredtext en'
lxml based OPF parser.
'''
-import re, sys, unittest, functools, os, mimetypes, uuid, glob, cStringIO
+import re, sys, unittest, functools, os, mimetypes, uuid, glob, cStringIO, json
from urllib import unquote
from urlparse import urlparse
@@ -20,8 +20,9 @@ from calibre.ebooks.metadata import string_to_authors, MetaInformation
from calibre.ebooks.metadata.book.base import Metadata
from calibre.utils.date import parse_date, isoformat
from calibre.utils.localization import get_lang
+from calibre import prints
-class Resource(object):
+class Resource(object): # {{{
'''
Represents a resource (usually a file on the filesystem or a URL pointing
to the web. Such resources are commonly referred to in OPF files.
@@ -102,8 +103,9 @@ class Resource(object):
def __repr__(self):
return 'Resource(%s, %s)'%(repr(self.path), repr(self.href()))
+# }}}
-class ResourceCollection(object):
+class ResourceCollection(object): # {{{
def __init__(self):
self._resources = []
@@ -154,10 +156,9 @@ class ResourceCollection(object):
for res in self:
res.set_basedir(path)
+# }}}
-
-
-class ManifestItem(Resource):
+class ManifestItem(Resource): # {{{
@staticmethod
def from_opf_manifest_item(item, basedir):
@@ -195,8 +196,9 @@ class ManifestItem(Resource):
return self.media_type
raise IndexError('%d out of bounds.'%index)
+# }}}
-class Manifest(ResourceCollection):
+class Manifest(ResourceCollection): # {{{
@staticmethod
def from_opf_manifest_element(items, dir):
@@ -263,7 +265,9 @@ class Manifest(ResourceCollection):
if i.id == id:
return i.mime_type
-class Spine(ResourceCollection):
+# }}}
+
+class Spine(ResourceCollection): # {{{
class Item(Resource):
@@ -335,7 +339,9 @@ class Spine(ResourceCollection):
for i in self:
yield i.path
-class Guide(ResourceCollection):
+# }}}
+
+class Guide(ResourceCollection): # {{{
class Reference(Resource):
@@ -372,6 +378,7 @@ class Guide(ResourceCollection):
self[-1].type = type
self[-1].title = ''
+# }}}
class MetadataField(object):
@@ -413,7 +420,29 @@ class MetadataField(object):
elem = obj.create_metadata_element(self.name, is_dc=self.is_dc)
obj.set_text(elem, unicode(val))
-class OPF(object):
+
+def serialize_user_metadata(metadata_elem, all_user_metadata, tail='\n'+(' '*8)):
+ from calibre.utils.config import to_json
+ from calibre.ebooks.metadata.book.json_codec import object_to_unicode
+
+ for name, fm in all_user_metadata.items():
+ try:
+ fm = object_to_unicode(fm)
+ fm = json.dumps(fm, default=to_json, ensure_ascii=False)
+ except:
+ prints('Failed to write user metadata:', name)
+ import traceback
+ traceback.print_exc()
+ continue
+ meta = metadata_elem.makeelement('meta')
+ meta.set('name', 'calibre:user_metadata:'+name)
+ meta.set('content', fm)
+ meta.tail = tail
+ metadata_elem.append(meta)
+
+
+class OPF(object): # {{{
+
MIMETYPE = 'application/oebps-package+xml'
PARSER = etree.XMLParser(recover=True)
NAMESPACES = {
@@ -498,6 +527,34 @@ class OPF(object):
self.guide = Guide.from_opf_guide(guide, basedir) if guide else None
self.cover_data = (None, None)
self.find_toc()
+ self.read_user_metadata()
+
+ def read_user_metadata(self):
+ self.user_metadata = {}
+ from calibre.utils.config import from_json
+ elems = self.root.xpath('//*[name() = "meta" and starts-with(@name,'
+ '"calibre:user_metadata:") and @content]')
+ for elem in elems:
+ name = elem.get('name')
+ name = ':'.join(name.split(':')[2:])
+ if not name or not name.startswith('#'):
+ continue
+ fm = elem.get('content')
+ try:
+ fm = json.loads(fm, object_hook=from_json)
+ except:
+ prints('Failed to read user metadata:', name)
+ import traceback
+ traceback.print_exc()
+ continue
+ self.user_metadata[name] = fm
+
+
+ def write_user_metadata(self):
+ for elem in self.user_metadata_path(self.root):
+ elem.getparent().remove(elem)
+ serialize_user_metadata(self.metadata,
+ self.user_metadata)
def find_toc(self):
self.toc = None
@@ -912,6 +969,7 @@ class OPF(object):
return elem
def render(self, encoding='utf-8'):
+ self.write_user_metadata()
raw = etree.tostring(self.root, encoding=encoding, pretty_print=True)
if not raw.lstrip().startswith('\n'%encoding.upper()+raw
@@ -926,6 +984,7 @@ class OPF(object):
if val is not None and val != [] and val != (None, None):
setattr(self, attr, val)
+# }}}
class OPFCreator(Metadata):
@@ -1116,6 +1175,8 @@ class OPFCreator(Metadata):
item.set('title', ref.title)
guide.append(item)
+ serialize_user_metadata(metadata, self.get_all_user_metadata(False))
+
root = E.package(
metadata,
manifest,
@@ -1218,6 +1279,8 @@ def metadata_to_opf(mi, as_string=True):
if mi.title_sort:
meta('title_sort', mi.title_sort)
+ serialize_user_metadata(metadata, mi.get_all_user_metadata(False))
+
metadata[-1].tail = '\n' +(' '*4)
if mi.cover:
@@ -1335,5 +1398,28 @@ def suite():
def test():
unittest.TextTestRunner(verbosity=2).run(suite())
+def test_user_metadata():
+ from cStringIO import StringIO
+ mi = Metadata('Test title', ['test author1', 'test author2'])
+ um = {
+ '#myseries': { '#value#': u'test series\xe4', 'datatype':'text',
+ 'is_multiple': False, 'name': u'My Series'},
+ '#myseries_index': { '#value#': 2.45, 'datatype': 'float',
+ 'is_multiple': False}
+ }
+ mi.set_all_user_metadata(um)
+ raw = metadata_to_opf(mi)
+ opfc = OPFCreator(os.getcwd(), other=mi)
+ out = StringIO()
+ opfc.render(out)
+ raw2 = out.getvalue()
+ f = StringIO(raw)
+ opf = OPF(f)
+ f2 = StringIO(raw2)
+ opf2 = OPF(f2)
+ assert um == opf.user_metadata
+ assert um == opf2.user_metadata
+ print raw
+
if __name__ == '__main__':
- test()
+ test_user_metadata()
From 420db7851b8650ae7e61f2b441b9a7822dddbd8b Mon Sep 17 00:00:00 2001
From: Kovid Goyal
Date: Wed, 15 Sep 2010 21:50:06 -0600
Subject: [PATCH 036/412] Fix use of OPF class to generate a Metadata object
and have OPF.smart_update also update user metadata
---
src/calibre/customize/builtins.py | 3 +--
src/calibre/ebooks/conversion/plumber.py | 2 +-
src/calibre/ebooks/metadata/cli.py | 2 +-
src/calibre/ebooks/metadata/epub.py | 2 +-
src/calibre/ebooks/metadata/lit.py | 3 +--
src/calibre/ebooks/metadata/meta.py | 2 +-
src/calibre/ebooks/metadata/opf2.py | 24 +++++++++++++++++-------
src/calibre/ebooks/mobi/reader.py | 2 +-
src/calibre/ebooks/oeb/reader.py | 3 +--
src/calibre/gui2/add.py | 2 +-
src/calibre/library/cli.py | 2 +-
11 files changed, 27 insertions(+), 20 deletions(-)
diff --git a/src/calibre/customize/builtins.py b/src/calibre/customize/builtins.py
index 68df832048..1ddb2843a1 100644
--- a/src/calibre/customize/builtins.py
+++ b/src/calibre/customize/builtins.py
@@ -226,8 +226,7 @@ class OPFMetadataReader(MetadataReaderPlugin):
def get_metadata(self, stream, ftype):
from calibre.ebooks.metadata.opf2 import OPF
- from calibre.ebooks.metadata import MetaInformation
- return MetaInformation(OPF(stream, os.getcwd()))
+ return OPF(stream, os.getcwd()).to_book_metadata()
class PDBMetadataReader(MetadataReaderPlugin):
diff --git a/src/calibre/ebooks/conversion/plumber.py b/src/calibre/ebooks/conversion/plumber.py
index 16282dd28d..38e47f6bf7 100644
--- a/src/calibre/ebooks/conversion/plumber.py
+++ b/src/calibre/ebooks/conversion/plumber.py
@@ -692,7 +692,7 @@ OptionRecommendation(name='timestamp',
self.opts.read_metadata_from_opf)
opf = OPF(open(self.opts.read_metadata_from_opf, 'rb'),
os.path.dirname(self.opts.read_metadata_from_opf))
- mi = MetaInformation(opf)
+ mi = opf.to_book_metadata()
self.opts_to_mi(mi)
if mi.cover:
if mi.cover.startswith('http:') or mi.cover.startswith('https:'):
diff --git a/src/calibre/ebooks/metadata/cli.py b/src/calibre/ebooks/metadata/cli.py
index 780d3febcf..a0be187512 100644
--- a/src/calibre/ebooks/metadata/cli.py
+++ b/src/calibre/ebooks/metadata/cli.py
@@ -109,7 +109,7 @@ def do_set_metadata(opts, mi, stream, stream_type):
from_opf = getattr(opts, 'from_opf', None)
if from_opf is not None:
from calibre.ebooks.metadata.opf2 import OPF
- opf_mi = MetaInformation(OPF(open(from_opf, 'rb')))
+ opf_mi = OPF(open(from_opf, 'rb')).to_book_metadata()
mi.smart_update(opf_mi)
for pref in config().option_set.preferences:
diff --git a/src/calibre/ebooks/metadata/epub.py b/src/calibre/ebooks/metadata/epub.py
index ac6b5feebe..8984a252a3 100644
--- a/src/calibre/ebooks/metadata/epub.py
+++ b/src/calibre/ebooks/metadata/epub.py
@@ -167,7 +167,7 @@ def get_metadata(stream, extract_cover=True):
""" Return metadata as a :class:`Metadata` object """
stream.seek(0)
reader = OCFZipReader(stream)
- mi = MetaInformation(reader.opf)
+ mi = reader.opf.to_book_metadata()
if extract_cover:
try:
cdata = get_cover(reader.opf, reader.opf_path, stream, reader=reader)
diff --git a/src/calibre/ebooks/metadata/lit.py b/src/calibre/ebooks/metadata/lit.py
index 1a267b6858..3be1f22632 100644
--- a/src/calibre/ebooks/metadata/lit.py
+++ b/src/calibre/ebooks/metadata/lit.py
@@ -6,7 +6,6 @@ Support for reading the metadata from a LIT file.
import cStringIO, os
-from calibre.ebooks.metadata import MetaInformation
from calibre.ebooks.metadata.opf2 import OPF
def get_metadata(stream):
@@ -16,7 +15,7 @@ def get_metadata(stream):
src = litfile.get_metadata().encode('utf-8')
litfile = litfile._litfile
opf = OPF(cStringIO.StringIO(src), os.getcwd())
- mi = MetaInformation(opf)
+ mi = opf.to_book_metadata()
covers = []
for item in opf.iterguide():
if 'cover' not in item.get('type', '').lower():
diff --git a/src/calibre/ebooks/metadata/meta.py b/src/calibre/ebooks/metadata/meta.py
index eae8171362..68deca5e10 100644
--- a/src/calibre/ebooks/metadata/meta.py
+++ b/src/calibre/ebooks/metadata/meta.py
@@ -194,7 +194,7 @@ def opf_metadata(opfpath):
try:
opf = OPF(f, os.path.dirname(opfpath))
if opf.application_id is not None:
- mi = MetaInformation(opf)
+ mi = opf.to_book_metadata()
if hasattr(opf, 'cover') and opf.cover:
cpath = os.path.join(os.path.dirname(opfpath), opf.cover)
if os.access(cpath, os.R_OK):
diff --git a/src/calibre/ebooks/metadata/opf2.py b/src/calibre/ebooks/metadata/opf2.py
index 236b2fa18f..96f1fa4832 100644
--- a/src/calibre/ebooks/metadata/opf2.py
+++ b/src/calibre/ebooks/metadata/opf2.py
@@ -530,7 +530,7 @@ class OPF(object): # {{{
self.read_user_metadata()
def read_user_metadata(self):
- self.user_metadata = {}
+ self._user_metadata_ = {}
from calibre.utils.config import from_json
elems = self.root.xpath('//*[name() = "meta" and starts-with(@name,'
'"calibre:user_metadata:") and @content]')
@@ -547,14 +547,21 @@ class OPF(object): # {{{
import traceback
traceback.print_exc()
continue
- self.user_metadata[name] = fm
+ self._user_metadata_[name] = fm
+ def to_book_metadata(self):
+ ans = MetaInformation(self)
+ for n, v in self._user_metadata_.items():
+ ans.set_user_metadata(n, v)
+ return ans
def write_user_metadata(self):
- for elem in self.user_metadata_path(self.root):
+ elems = self.root.xpath('//*[name() = "meta" and starts-with(@name,'
+ '"calibre:user_metadata:") and @content]')
+ for elem in elems:
elem.getparent().remove(elem)
serialize_user_metadata(self.metadata,
- self.user_metadata)
+ self._user_metadata_)
def find_toc(self):
self.toc = None
@@ -983,6 +990,9 @@ class OPF(object): # {{{
val = getattr(mi, attr, None)
if val is not None and val != [] and val != (None, None):
setattr(self, attr, val)
+ temp = self.to_book_metadata()
+ temp.smart_update(mi, replace_metadata=replace_metadata)
+ self._user_metadata_ = temp.get_all_user_metadata(True)
# }}}
@@ -1417,9 +1427,9 @@ def test_user_metadata():
opf = OPF(f)
f2 = StringIO(raw2)
opf2 = OPF(f2)
- assert um == opf.user_metadata
- assert um == opf2.user_metadata
- print raw
+ assert um == opf._user_metadata_
+ assert um == opf2._user_metadata_
+ print opf.render()
if __name__ == '__main__':
test_user_metadata()
diff --git a/src/calibre/ebooks/mobi/reader.py b/src/calibre/ebooks/mobi/reader.py
index 2a35c7cb45..6a44c2aa77 100644
--- a/src/calibre/ebooks/mobi/reader.py
+++ b/src/calibre/ebooks/mobi/reader.py
@@ -441,7 +441,7 @@ class MobiReader(object):
html.tostring(elem, encoding='utf-8') + ''
stream = cStringIO.StringIO(raw)
opf = OPF(stream)
- self.embedded_mi = MetaInformation(opf)
+ self.embedded_mi = opf.to_book_metadata()
if guide is not None:
for ref in guide.xpath('descendant::reference'):
if 'cover' in ref.get('type', '').lower():
diff --git a/src/calibre/ebooks/oeb/reader.py b/src/calibre/ebooks/oeb/reader.py
index d7d7bbf725..559421326c 100644
--- a/src/calibre/ebooks/oeb/reader.py
+++ b/src/calibre/ebooks/oeb/reader.py
@@ -126,10 +126,9 @@ class OEBReader(object):
def _metadata_from_opf(self, opf):
from calibre.ebooks.metadata.opf2 import OPF
- from calibre.ebooks.metadata import MetaInformation
from calibre.ebooks.oeb.transforms.metadata import meta_info_to_oeb_metadata
stream = cStringIO.StringIO(etree.tostring(opf))
- mi = MetaInformation(OPF(stream))
+ mi = OPF(stream).to_book_metadata()
if not mi.language:
mi.language = get_lang().replace('_', '-')
self.oeb.metadata.add('language', mi.language)
diff --git a/src/calibre/gui2/add.py b/src/calibre/gui2/add.py
index 5b9fb35be3..9f246aeb93 100644
--- a/src/calibre/gui2/add.py
+++ b/src/calibre/gui2/add.py
@@ -138,7 +138,7 @@ class DBAdder(Thread): # {{{
self.critical[name] = open(opf, 'rb').read().decode('utf-8', 'replace')
else:
try:
- mi = MetaInformation(OPF(opf))
+ mi = OPF(opf).to_book_metadata()
except:
import traceback
mi = MetaInformation('', [_('Unknown')])
diff --git a/src/calibre/library/cli.py b/src/calibre/library/cli.py
index 9a2d0b0a62..cd4e472807 100644
--- a/src/calibre/library/cli.py
+++ b/src/calibre/library/cli.py
@@ -448,7 +448,7 @@ def command_show_metadata(args, dbpath):
return 0
def do_set_metadata(db, id, stream):
- mi = OPF(stream)
+ mi = OPF(stream).to_book_metadata()
db.set_metadata(id, mi)
db.clean()
do_show_metadata(db, id, False)
From 4bc7aa1b710e00bdefdd82bed915c8a977b2523e Mon Sep 17 00:00:00 2001
From: Kovid Goyal
Date: Wed, 15 Sep 2010 21:56:02 -0600
Subject: [PATCH 037/412] More robust reading of user metadata from OPF
---
src/calibre/ebooks/metadata/opf2.py | 4 +++-
1 file changed, 3 insertions(+), 1 deletion(-)
diff --git a/src/calibre/ebooks/metadata/opf2.py b/src/calibre/ebooks/metadata/opf2.py
index 96f1fa4832..ecbef3194d 100644
--- a/src/calibre/ebooks/metadata/opf2.py
+++ b/src/calibre/ebooks/metadata/opf2.py
@@ -531,6 +531,7 @@ class OPF(object): # {{{
def read_user_metadata(self):
self._user_metadata_ = {}
+ temp = Metadata('x', ['x'])
from calibre.utils.config import from_json
elems = self.root.xpath('//*[name() = "meta" and starts-with(@name,'
'"calibre:user_metadata:") and @content]')
@@ -542,12 +543,13 @@ class OPF(object): # {{{
fm = elem.get('content')
try:
fm = json.loads(fm, object_hook=from_json)
+ temp.set_user_metadata(name, fm)
except:
prints('Failed to read user metadata:', name)
import traceback
traceback.print_exc()
continue
- self._user_metadata_[name] = fm
+ self._user_metadata_ = temp.get_all_user_metadata(True)
def to_book_metadata(self):
ans = MetaInformation(self)
From 56023722709d35816424cd3c77f5afe972103d0c Mon Sep 17 00:00:00 2001
From: Charles Haley <>
Date: Thu, 16 Sep 2010 12:24:56 +0100
Subject: [PATCH 038/412] Minor changes to OPF testing
---
src/calibre/ebooks/metadata/opf2.py | 6 ++++--
1 file changed, 4 insertions(+), 2 deletions(-)
diff --git a/src/calibre/ebooks/metadata/opf2.py b/src/calibre/ebooks/metadata/opf2.py
index ecbef3194d..8a4ff6a5bd 100644
--- a/src/calibre/ebooks/metadata/opf2.py
+++ b/src/calibre/ebooks/metadata/opf2.py
@@ -1415,9 +1415,11 @@ def test_user_metadata():
mi = Metadata('Test title', ['test author1', 'test author2'])
um = {
'#myseries': { '#value#': u'test series\xe4', 'datatype':'text',
- 'is_multiple': False, 'name': u'My Series'},
+ 'is_multiple': None, 'name': u'My Series'},
'#myseries_index': { '#value#': 2.45, 'datatype': 'float',
- 'is_multiple': False}
+ 'is_multiple': None},
+ '#mytags': {'#value#':['t1','t2','t3'], 'datatype':'text',
+ 'is_multiple': '|', 'name': u'My Tags'}
}
mi.set_all_user_metadata(um)
raw = metadata_to_opf(mi)
From e1dd08acef1b14c73a3da542f974acd9a10a1f79 Mon Sep 17 00:00:00 2001
From: Charles Haley <>
Date: Thu, 16 Sep 2010 14:02:49 +0100
Subject: [PATCH 039/412] Several changes: 1) Add an option to specify the time
format when sending to device. This is the analog of the same option that
already exists for save to disk. 2) refactor the format_field code. Remove
the special parameters on format_field. Add format_field_extended that
returns a 4-element tuple (name, formatted val, original val, field
metadata). 3) change (simplify) usbms collections management to use new
format_field_extended method. 4) change device.py to not call sync_booklists
twice. 5) add the fix for gui-not-updating, in hopes that we can avoid merge
conflicts
---
src/calibre/devices/usbms/books.py | 21 +++++++--------
src/calibre/devices/usbms/device.py | 4 ++-
src/calibre/ebooks/metadata/book/base.py | 33 ++++++++++++------------
src/calibre/gui2/actions/add.py | 2 +-
src/calibre/gui2/device.py | 28 ++++++++++++--------
src/calibre/gui2/preferences/sending.py | 3 +++
src/calibre/gui2/preferences/sending.ui | 15 ++++++++++-
src/calibre/library/save_to_disk.py | 3 +++
8 files changed, 68 insertions(+), 41 deletions(-)
diff --git a/src/calibre/devices/usbms/books.py b/src/calibre/devices/usbms/books.py
index d25787fc89..eab625f7be 100644
--- a/src/calibre/devices/usbms/books.py
+++ b/src/calibre/devices/usbms/books.py
@@ -94,12 +94,12 @@ class CollectionsBookList(BookList):
def supports_collections(self):
return True
- def compute_category_name(self, attr, category, cust_field_meta):
+ def compute_category_name(self, attr, category, field_meta):
renames = tweaks['sony_collection_renaming_rules']
attr_name = renames.get(attr, None)
if attr_name is None:
- if attr in cust_field_meta:
- attr_name = '(%s)'%cust_field_meta[attr]['name']
+ if field_meta['is_custom']:
+ attr_name = '(%s)'%field_meta['name']
else:
attr_name = ''
elif attr_name != '':
@@ -138,23 +138,23 @@ class CollectionsBookList(BookList):
# specified 'on_connect'
attrs = collection_attributes
meta_vals = book.get_all_non_none_attributes()
- cust_field_meta = book.get_all_user_metadata(make_copy=False)
for attr in attrs:
attr = attr.strip()
- ign, val = book.format_field(attr,
- ignore_series_index=True,
- return_multiples_as_list=True)
+ ign, val, orig_val, fm = book.format_field_extended(attr)
if not val: continue
if isbytestring(val):
val = val.decode(preferred_encoding, 'replace')
if isinstance(val, (list, tuple)):
val = list(val)
+ elif fm['datatype'] == 'series':
+ val = [orig_val]
+ elif fm['datatype'] == 'text' and fm['is_multiple']:
+ val = orig_val
else:
val = [val]
for category in val:
is_series = False
- if attr in cust_field_meta: # is a custom field
- fm = cust_field_meta[attr]
+ if fm['is_custom']: # is a custom field
if fm['datatype'] == 'text' and len(category) > 1 and \
category[0] == '[' and category[-1] == ']':
continue
@@ -168,8 +168,7 @@ class CollectionsBookList(BookList):
('series' in collection_attributes and
meta_vals.get('series', None) == category):
is_series = True
- cat_name = self.compute_category_name(attr, category,
- cust_field_meta)
+ cat_name = self.compute_category_name(attr, category, fm)
if cat_name not in collections:
collections[cat_name] = []
collections_lpaths[cat_name] = set()
diff --git a/src/calibre/devices/usbms/device.py b/src/calibre/devices/usbms/device.py
index b954911242..928d00ad4a 100644
--- a/src/calibre/devices/usbms/device.py
+++ b/src/calibre/devices/usbms/device.py
@@ -829,12 +829,14 @@ class Device(DeviceConfig, DevicePlugin):
ext = os.path.splitext(fname)[1]
from calibre.library.save_to_disk import get_components
+ from calibre.library.save_to_disk import config
+ opts = config().parse()
if not isinstance(template, unicode):
template = template.decode('utf-8')
app_id = str(getattr(mdata, 'application_id', ''))
# The db id will be in the created filename
extra_components = get_components(template, mdata, fname,
- length=250-len(app_id)-1)
+ timefmt=opts.send_timefmt, length=250-len(app_id)-1)
if not extra_components:
extra_components.append(sanitize(self.filename_callback(fname,
mdata)))
diff --git a/src/calibre/ebooks/metadata/book/base.py b/src/calibre/ebooks/metadata/book/base.py
index 7405f20a7c..b252f518da 100644
--- a/src/calibre/ebooks/metadata/book/base.py
+++ b/src/calibre/ebooks/metadata/book/base.py
@@ -343,8 +343,11 @@ class Metadata(object):
def format_rating(self):
return unicode(self.rating)
- def format_field(self, key, ignore_series_index=False,
- return_multiples_as_list=False):
+ def format_field(self, key):
+ name, val, ign, ign = self.format_field_extended(key)
+ return (name, val)
+
+ def format_field_extended(self, key):
from calibre.ebooks.metadata import authors_to_string
'''
returns the tuple (field_name, formatted_value)
@@ -352,43 +355,41 @@ class Metadata(object):
if key in self.user_metadata_keys:
res = self.get(key, None)
if res is None or res == '':
- return (None, None)
+ return (None, None, None, None)
+ orig_res = res
cmeta = self.get_user_metadata(key, make_copy=False)
name = unicode(cmeta['name'])
datatype = cmeta['datatype']
if datatype == 'text' and cmeta['is_multiple']:
- if not return_multiples_as_list:
- res = u', '.join(res)
+ res = u', '.join(res)
elif datatype == 'series':
- if not ignore_series_index:
- res = res + \
- ' [%s]'%self.format_series_index(val=self.get_extra(key))
+ res = res + \
+ ' [%s]'%self.format_series_index(val=self.get_extra(key))
elif datatype == 'datetime':
res = format_date(res, cmeta['display'].get('date_format','dd MMM yyyy'))
elif datatype == 'bool':
res = _('Yes') if res else _('No')
- return (name, res)
+ return (name, res, orig_res, cmeta)
if key in field_metadata and field_metadata[key]['kind'] == 'field':
res = self.get(key, None)
if res is None or res == '':
- return (None, None)
+ return (None, None, None, None)
+ orig_res = res
fmeta = field_metadata[key]
name = unicode(fmeta['name'])
datatype = fmeta['datatype']
if key == 'authors':
res = authors_to_string(res)
elif datatype == 'text' and fmeta['is_multiple']:
- if not return_multiples_as_list:
- res = u', '.join(res)
+ res = u', '.join(res)
elif datatype == 'series':
- if not ignore_series_index:
- res = res + ' [%s]'%self.format_series_index()
+ res = res + ' [%s]'%self.format_series_index()
elif datatype == 'datetime':
res = format_date(res, fmeta['display'].get('date_format','dd MMM yyyy'))
- return (name, res)
+ return (name, res, orig_res, fmeta)
- return (None, None)
+ return (None, None, None, None)
def __unicode__(self):
from calibre.ebooks.metadata import authors_to_string
diff --git a/src/calibre/gui2/actions/add.py b/src/calibre/gui2/actions/add.py
index add7bf1d5b..aa20b8bc16 100644
--- a/src/calibre/gui2/actions/add.py
+++ b/src/calibre/gui2/actions/add.py
@@ -230,7 +230,7 @@ class AddAction(InterfaceAction):
self._files_added(paths, names, infos, on_card=on_card)
# set the in-library flags, and as a consequence send the library's
# metadata for this book to the device. This sets the uuid to the
- # correct value.
+ # correct value. Note that set_books_in_library might sync_booklists
self.gui.set_books_in_library(booklists=[model.db], reset=True)
model.reset()
diff --git a/src/calibre/gui2/device.py b/src/calibre/gui2/device.py
index f839e1d519..196e97f2a3 100644
--- a/src/calibre/gui2/device.py
+++ b/src/calibre/gui2/device.py
@@ -745,6 +745,7 @@ class DeviceMixin(object): # {{{
if job.failed:
self.device_job_exception(job)
return
+ # set_books_in_library might schedule a sync_booklists job
self.set_books_in_library(job.result, reset=True)
mainlist, cardalist, cardblist = job.result
self.memory_view.set_database(mainlist)
@@ -789,11 +790,12 @@ class DeviceMixin(object): # {{{
self.device_manager.remove_books_from_metadata(paths,
self.booklists())
model.paths_deleted(paths)
- self.upload_booklists()
# Force recomputation the library's ondevice info. We need to call
# set_books_in_library even though books were not added because
- # the deleted book might have been an exact match.
- self.set_books_in_library(self.booklists(), reset=True)
+ # the deleted book might have been an exact match. Upload the booklists
+ # if set_books_in_library did not.
+ if not self.set_books_in_library(self.booklists(), reset=True):
+ self.upload_booklists()
self.book_on_device(None, None, reset=True)
# We need to reset the ondevice flags in the library. Use a big hammer,
# so we don't need to worry about whether some succeeded or not.
@@ -1280,8 +1282,6 @@ class DeviceMixin(object): # {{{
self.device_manager.add_books_to_metadata(job.result,
metadata, self.booklists())
- self.upload_booklists()
-
books_to_be_deleted = []
if memory and memory[1]:
books_to_be_deleted = memory[1]
@@ -1291,12 +1291,15 @@ class DeviceMixin(object): # {{{
# book already there with a different book. This happens frequently in
# news. When this happens, the book match indication will be wrong
# because the UUID changed. Force both the device and the library view
- # to refresh the flags.
- self.set_books_in_library(self.booklists(), reset=True)
+ # to refresh the flags. Set_books_in_library could upload the booklists.
+ # If it does not, then do it here.
+ if not self.set_books_in_library(self.booklists(), reset=True):
+ self.upload_booklists()
self.book_on_device(None, reset=True)
self.refresh_ondevice_info(device_connected = True)
- view = self.card_a_view if on_card == 'carda' else self.card_b_view if on_card == 'cardb' else self.memory_view
+ view = self.card_a_view if on_card == 'carda' else \
+ self.card_b_view if on_card == 'cardb' else self.memory_view
view.model().resort(reset=False)
view.model().research()
for f in files:
@@ -1371,7 +1374,7 @@ class DeviceMixin(object): # {{{
try:
db = self.library_view.model().db
except:
- return
+ return False
# Build a cache (map) of the library, so the search isn't On**2
self.db_book_title_cache = {}
self.db_book_uuid_cache = {}
@@ -1466,10 +1469,13 @@ class DeviceMixin(object): # {{{
# Set author_sort if it isn't already
asort = getattr(book, 'author_sort', None)
if not asort and book.authors:
- book.author_sort = self.library_view.model().db.author_sort_from_authors(book.authors)
+ book.author_sort = self.library_view.model().db.\
+ author_sort_from_authors(book.authors)
if update_metadata:
if self.device_manager.is_device_connected:
- self.device_manager.sync_booklists(None, booklists)
+ self.device_manager.sync_booklists(
+ Dispatcher(self.metadata_synced), booklists)
+ return update_metadata
# }}}
diff --git a/src/calibre/gui2/preferences/sending.py b/src/calibre/gui2/preferences/sending.py
index 748c6b2a2d..ac4abbcf41 100644
--- a/src/calibre/gui2/preferences/sending.py
+++ b/src/calibre/gui2/preferences/sending.py
@@ -22,6 +22,9 @@ class ConfigWidget(ConfigWidgetBase, Ui_Form):
r = self.register
+ for x in ('send_timefmt',):
+ r(x, self.proxy)
+
choices = [(_('Manual management'), 'manual'),
(_('Only on send'), 'on_send'),
(_('Automatic management'), 'on_connect')]
diff --git a/src/calibre/gui2/preferences/sending.ui b/src/calibre/gui2/preferences/sending.ui
index e064646afd..b9d1d1e1d2 100644
--- a/src/calibre/gui2/preferences/sending.ui
+++ b/src/calibre/gui2/preferences/sending.ui
@@ -80,7 +80,20 @@
-
+
+
+
+ Format &dates as:
+
+
+ opt_send_timefmt
+
+
+
+
+
+
+ Here you can control how calibre will save your books when you click the Send to Device button. This setting can be overriden for individual devices by customizing the device interface plugins in Preferences->Advanced->Plugins
diff --git a/src/calibre/library/save_to_disk.py b/src/calibre/library/save_to_disk.py
index 3fa40c68b2..71850abcd5 100644
--- a/src/calibre/library/save_to_disk.py
+++ b/src/calibre/library/save_to_disk.py
@@ -84,6 +84,9 @@ def config(defaults=None):
x('timefmt', default='%b, %Y',
help=_('The format in which to display dates. %d - day, %b - month, '
'%Y - year. Default is: %b, %Y'))
+ x('send_timefmt', default='%b, %Y',
+ help=_('The format in which to display dates. %d - day, %b - month, '
+ '%Y - year. Default is: %b, %Y'))
x('to_lowercase', default=False,
help=_('Convert paths to lowercase.'))
x('replace_whitespace', default=False,
From 4645138a67193537ba3e91fc3e4942017d72e1de Mon Sep 17 00:00:00 2001
From: Charles Haley <>
Date: Thu, 16 Sep 2010 16:03:07 +0100
Subject: [PATCH 040/412] 1) Re-enable syntactic validation of save templates.
2) fix row numbering on send_to_device preferences ui template.
---
src/calibre/gui2/preferences/save_template.py | 37 +++++++++++--------
src/calibre/gui2/preferences/sending.ui | 2 +-
2 files changed, 22 insertions(+), 17 deletions(-)
diff --git a/src/calibre/gui2/preferences/save_template.py b/src/calibre/gui2/preferences/save_template.py
index 26dc02f259..0dbee5bf21 100644
--- a/src/calibre/gui2/preferences/save_template.py
+++ b/src/calibre/gui2/preferences/save_template.py
@@ -8,8 +8,10 @@ __docformat__ = 'restructuredtext en'
from PyQt4.Qt import QWidget, pyqtSignal
+from calibre.gui2 import error_dialog
from calibre.gui2.preferences.save_template_ui import Ui_Form
-from calibre.library.save_to_disk import FORMAT_ARG_DESCS
+from calibre.library.save_to_disk import FORMAT_ARG_DESCS, preprocess_template,\
+ safe_format
class SaveTemplate(QWidget, Ui_Form):
@@ -24,8 +26,11 @@ class SaveTemplate(QWidget, Ui_Form):
variables = sorted(FORMAT_ARG_DESCS.keys())
rows = []
for var in variables:
- rows.append(u'
%s
%s
'%
+ rows.append(u'
%s
%s
'%
(var, FORMAT_ARG_DESCS[var]))
+ rows.append(u'
%s
%s
'%(
+ _('Any custom field'),
+ _('The lookup name of any custom field. These names begin with "#")')))
table = u'
%s
'%(u'\n'.join(rows))
self.template_variables.setText(table)
@@ -39,21 +44,21 @@ class SaveTemplate(QWidget, Ui_Form):
self.changed_signal.emit()
def validate(self):
- # TODO: NEWMETA: I haven't figured out how to get the custom columns
- # into here, so for the moment make all templates valid.
+ '''
+ Do a syntax check on the format string. Doing a semantic check
+ (verifying that the fields exist) is not useful in the presence of
+ custom fields, because they may or may not exist.
+ '''
+ tmpl = preprocess_template(self.opt_template.text())
+ fa = {}
+ try:
+ safe_format(tmpl, fa)
+ except Exception, err:
+ error_dialog(self, _('Invalid template'),
+ '
'+_('The template %s is invalid:')%tmpl + \
+ ' '+str(err), show=True)
+ return False
return True
-# tmpl = preprocess_template(self.opt_template.text())
-# fa = {}
-# for x in FORMAT_ARG_DESCS.keys():
-# fa[x]='random long string'
-# try:
-# tmpl.format(**fa)
-# except Exception, err:
-# error_dialog(self, _('Invalid template'),
-# '
-
+
From 788128627459035cf0e87fc6b0baa4da708cef3d Mon Sep 17 00:00:00 2001
From: Charles Haley <>
Date: Sat, 18 Sep 2010 11:08:33 +0100
Subject: [PATCH 041/412] 1) add the composite field custom datatype 2) clean
up content server code so it uses the new formatting facilities
---
src/calibre/devices/usbms/books.py | 7 ++-
src/calibre/ebooks/metadata/book/__init__.py | 7 ++-
src/calibre/ebooks/metadata/book/base.py | 35 +++++++++---
src/calibre/gui2/library/models.py | 33 ++++++++++--
src/calibre/gui2/preferences/columns.py | 3 +-
.../gui2/preferences/create_custom_column.py | 30 ++++++++---
.../gui2/preferences/create_custom_column.ui | 53 ++++++++++++++++++-
src/calibre/library/custom_columns.py | 6 +--
src/calibre/library/database2.py | 1 +
src/calibre/library/field_metadata.py | 2 +-
src/calibre/library/server/mobile.py | 36 +++++--------
src/calibre/library/server/opds.py | 24 ++++-----
src/calibre/library/server/xml.py | 40 ++++++--------
13 files changed, 181 insertions(+), 96 deletions(-)
diff --git a/src/calibre/devices/usbms/books.py b/src/calibre/devices/usbms/books.py
index eab625f7be..13fcb90b49 100644
--- a/src/calibre/devices/usbms/books.py
+++ b/src/calibre/devices/usbms/books.py
@@ -137,7 +137,6 @@ class CollectionsBookList(BookList):
# For existing books, modify the collections only if the user
# specified 'on_connect'
attrs = collection_attributes
- meta_vals = book.get_all_non_none_attributes()
for attr in attrs:
attr = attr.strip()
ign, val, orig_val, fm = book.format_field_extended(attr)
@@ -166,7 +165,7 @@ class CollectionsBookList(BookList):
continue
if attr == 'series' or \
('series' in collection_attributes and
- meta_vals.get('series', None) == category):
+ book.get('series', None) == category):
is_series = True
cat_name = self.compute_category_name(attr, category, fm)
if cat_name not in collections:
@@ -177,10 +176,10 @@ class CollectionsBookList(BookList):
collections_lpaths[cat_name].add(lpath)
if is_series:
collections[cat_name].append(
- (book, meta_vals.get(attr+'_index', sys.maxint)))
+ (book, book.get(attr+'_index', sys.maxint)))
else:
collections[cat_name].append(
- (book, meta_vals.get('title_sort', 'zzzz')))
+ (book, book.get('title_sort', 'zzzz')))
# Sort collections
result = {}
for category, books in collections.items():
diff --git a/src/calibre/ebooks/metadata/book/__init__.py b/src/calibre/ebooks/metadata/book/__init__.py
index e087f8072d..e6dff9110b 100644
--- a/src/calibre/ebooks/metadata/book/__init__.py
+++ b/src/calibre/ebooks/metadata/book/__init__.py
@@ -81,9 +81,8 @@ DEVICE_METADATA_FIELDS = frozenset([
CALIBRE_METADATA_FIELDS = frozenset([
'application_id', # An application id, currently set to the db_id.
- # the calibre primary key of the item.
'db_id', # the calibre primary key of the item.
- # TODO: NEWMETA: May want to remove once Sony's no longer use it
+ 'formats', # list of formats (extensions) for this book
]
)
@@ -124,5 +123,5 @@ SERIALIZABLE_FIELDS = SOCIAL_METADATA_FIELDS.union(
PUBLICATION_METADATA_FIELDS).union(
CALIBRE_METADATA_FIELDS).union(
DEVICE_METADATA_FIELDS) - \
- frozenset(['device_collections'])
- # device_collections is rebuilt when needed
+ frozenset(['device_collections', 'formats'])
+ # these are rebuilt when needed
diff --git a/src/calibre/ebooks/metadata/book/base.py b/src/calibre/ebooks/metadata/book/base.py
index b252f518da..31485dfe1b 100644
--- a/src/calibre/ebooks/metadata/book/base.py
+++ b/src/calibre/ebooks/metadata/book/base.py
@@ -5,8 +5,7 @@ __license__ = 'GPL v3'
__copyright__ = '2010, Kovid Goyal '
__docformat__ = 'restructuredtext en'
-import copy
-import traceback
+import copy, re, string, traceback
from calibre import prints
from calibre.ebooks.metadata.book import SC_COPYABLE_FIELDS
@@ -33,6 +32,23 @@ NULL_VALUES = {
field_metadata = FieldMetadata()
+class SafeFormat(string.Formatter):
+ '''
+ Provides a format function that substitutes '' for any missing value
+ '''
+ def get_value(self, key, args, mi):
+ ign, v = mi.format_field(key, series_with_index=False)
+ if v is None:
+ return ''
+ return v
+
+composite_formatter = SafeFormat()
+compress_spaces = re.compile(r'\s+')
+
+def format_composite(x, mi):
+ ans = composite_formatter.vformat(x, [], mi).strip()
+ return compress_spaces.sub(' ', ans)
+
class Metadata(object):
'''
@@ -343,18 +359,19 @@ class Metadata(object):
def format_rating(self):
return unicode(self.rating)
- def format_field(self, key):
- name, val, ign, ign = self.format_field_extended(key)
+ def format_field(self, key, series_with_index=True):
+ name, val, ign, ign = self.format_field_extended(key, series_with_index)
return (name, val)
- def format_field_extended(self, key):
+ def format_field_extended(self, key, series_with_index=True):
from calibre.ebooks.metadata import authors_to_string
'''
returns the tuple (field_name, formatted_value)
'''
if key in self.user_metadata_keys:
res = self.get(key, None)
- if res is None or res == '':
+ cmeta = self.get_user_metadata(key, make_copy=False)
+ if cmeta['datatype'] != 'composite' and (res is None or res == ''):
return (None, None, None, None)
orig_res = res
cmeta = self.get_user_metadata(key, make_copy=False)
@@ -362,13 +379,15 @@ class Metadata(object):
datatype = cmeta['datatype']
if datatype == 'text' and cmeta['is_multiple']:
res = u', '.join(res)
- elif datatype == 'series':
+ elif datatype == 'series' and series_with_index:
res = res + \
' [%s]'%self.format_series_index(val=self.get_extra(key))
elif datatype == 'datetime':
res = format_date(res, cmeta['display'].get('date_format','dd MMM yyyy'))
elif datatype == 'bool':
res = _('Yes') if res else _('No')
+ elif datatype == 'composite':
+ res = format_composite(cmeta['display']['composite_template'], self)
return (name, res, orig_res, cmeta)
if key in field_metadata and field_metadata[key]['kind'] == 'field':
@@ -383,7 +402,7 @@ class Metadata(object):
res = authors_to_string(res)
elif datatype == 'text' and fmeta['is_multiple']:
res = u', '.join(res)
- elif datatype == 'series':
+ elif datatype == 'series' and series_with_index:
res = res + ' [%s]'%self.format_series_index()
elif datatype == 'datetime':
res = format_date(res, fmeta['display'].get('date_format','dd MMM yyyy'))
diff --git a/src/calibre/gui2/library/models.py b/src/calibre/gui2/library/models.py
index e9e688c93b..7839b89d7e 100644
--- a/src/calibre/gui2/library/models.py
+++ b/src/calibre/gui2/library/models.py
@@ -86,6 +86,7 @@ class BooksModel(QAbstractTableModel): # {{{
self.last_search = '' # The last search performed on this model
self.column_map = []
self.headers = {}
+ self.metadata_cache = {}
self.alignment_map = {}
self.buffer_size = buffer
self.cover_cache = None
@@ -114,6 +115,16 @@ class BooksModel(QAbstractTableModel): # {{{
def clear_caches(self):
if self.cover_cache:
self.cover_cache.clear_cache()
+ self.metadata_cache = {}
+
+ def get_cached_metadata(self, idx):
+ if idx not in self.metadata_cache:
+ self.metadata_cache[idx] = self.db.get_metadata(idx)
+ return self.metadata_cache[idx]
+
+ def remove_cached_metadata(self, idx):
+ if idx in self.metadata_cache:
+ del self.metadata_cache[idx]
def read_config(self):
self.use_roman_numbers = config['use_roman_numerals_for_series_number']
@@ -146,6 +157,7 @@ class BooksModel(QAbstractTableModel): # {{{
elif col in self.custom_columns:
self.headers[col] = self.custom_columns[col]['name']
+ self.metadata_cache = {}
self.build_data_convertors()
self.reset()
self.database_changed.emit(db)
@@ -159,11 +171,13 @@ class BooksModel(QAbstractTableModel): # {{{
db.add_listener(refresh_cover)
def refresh_ids(self, ids, current_row=-1):
+ self.metadata_cache = {}
rows = self.db.refresh_ids(ids)
if rows:
self.refresh_rows(rows, current_row=current_row)
def refresh_rows(self, rows, current_row=-1):
+ self.metadata_cache = {}
for row in rows:
if row == current_row:
self.new_bookdisplay_data.emit(
@@ -193,6 +207,7 @@ class BooksModel(QAbstractTableModel): # {{{
return ret
def count_changed(self, *args):
+ self.metadata_cache = {}
self.count_changed_signal.emit(self.db.count())
def row_indices(self, index):
@@ -262,6 +277,7 @@ class BooksModel(QAbstractTableModel): # {{{
self.sorting_done.emit(self.db.index)
def refresh(self, reset=True):
+ self.metadata_cache = {}
self.db.refresh(field=None)
self.resort(reset=reset)
@@ -318,7 +334,7 @@ class BooksModel(QAbstractTableModel): # {{{
data[_('Series')] = \
_('Book %s of %s.')%\
(sidx, prepare_string_for_xml(series))
- mi = self.db.get_metadata(idx)
+ mi = self.get_cached_metadata(idx)
for key in mi.user_metadata_keys:
name, val = mi.format_field(key)
if val is not None:
@@ -327,6 +343,7 @@ class BooksModel(QAbstractTableModel): # {{{
def set_cache(self, idx):
l, r = 0, self.count()-1
+ self.remove_cached_metadata(idx)
if self.cover_cache is not None:
l = max(l, idx-self.buffer_size)
r = min(r, idx+self.buffer_size)
@@ -586,6 +603,10 @@ class BooksModel(QAbstractTableModel): # {{{
def number_type(r, idx=-1):
return QVariant(self.db.data[r][idx])
+ def composite_type(r, key=None):
+ mi = self.get_cached_metadata(r)
+ return QVariant(mi.format_field(key)[1])
+
self.dc = {
'title' : functools.partial(text_type,
idx=self.db.field_metadata['title']['rec_index'], mult=False),
@@ -620,7 +641,8 @@ class BooksModel(QAbstractTableModel): # {{{
idx = self.custom_columns[col]['rec_index']
datatype = self.custom_columns[col]['datatype']
if datatype in ('text', 'comments'):
- self.dc[col] = functools.partial(text_type, idx=idx, mult=self.custom_columns[col]['is_multiple'])
+ self.dc[col] = functools.partial(text_type, idx=idx,
+ mult=self.custom_columns[col]['is_multiple'])
elif datatype in ('int', 'float'):
self.dc[col] = functools.partial(number_type, idx=idx)
elif datatype == 'datetime':
@@ -628,13 +650,15 @@ class BooksModel(QAbstractTableModel): # {{{
elif datatype == 'bool':
self.dc[col] = functools.partial(bool_type, idx=idx)
self.dc_decorator[col] = functools.partial(
- bool_type_decorator, idx=idx,
- bool_cols_are_tristate=tweaks['bool_custom_columns_are_tristate'] == 'yes')
+ bool_type_decorator, idx=idx,
+ bool_cols_are_tristate=tweaks['bool_custom_columns_are_tristate'] == 'yes')
elif datatype == 'rating':
self.dc[col] = functools.partial(rating_type, idx=idx)
elif datatype == 'series':
self.dc[col] = functools.partial(series_type, idx=idx,
siix=self.db.field_metadata.cc_series_index_column_for(col))
+ elif datatype == 'composite':
+ self.dc[col] = functools.partial(composite_type, key=col)
else:
print 'What type is this?', col, datatype
# build a index column to data converter map, to remove the string lookup in the data loop
@@ -729,6 +753,7 @@ class BooksModel(QAbstractTableModel): # {{{
if role == Qt.EditRole:
row, col = index.row(), index.column()
column = self.column_map[col]
+ self.remove_cached_metadata(row)
if self.is_custom_column(column):
if not self.set_custom_column_data(row, column, value):
return False
diff --git a/src/calibre/gui2/preferences/columns.py b/src/calibre/gui2/preferences/columns.py
index c1b9230f42..761a9880b1 100644
--- a/src/calibre/gui2/preferences/columns.py
+++ b/src/calibre/gui2/preferences/columns.py
@@ -155,7 +155,8 @@ class ConfigWidget(ConfigWidgetBase, Ui_Form):
name=self.custcols[c]['name'],
datatype=self.custcols[c]['datatype'],
is_multiple=self.custcols[c]['is_multiple'],
- display = self.custcols[c]['display'])
+ display = self.custcols[c]['display'],
+ editable = self.custcols[c]['editable'])
must_restart = True
elif '*deleteme' in self.custcols[c]:
db.delete_custom_column(label=self.custcols[c]['label'])
diff --git a/src/calibre/gui2/preferences/create_custom_column.py b/src/calibre/gui2/preferences/create_custom_column.py
index e8ab8707e2..4b21301ccd 100644
--- a/src/calibre/gui2/preferences/create_custom_column.py
+++ b/src/calibre/gui2/preferences/create_custom_column.py
@@ -38,6 +38,8 @@ class CreateCustomColumn(QDialog, Ui_QCreateCustomColumn):
'is_multiple':False},
8:{'datatype':'bool',
'text':_('Yes/No'), 'is_multiple':False},
+ 8:{'datatype':'composite',
+ 'text':_('Field built from other fields'), 'is_multiple':False},
}
def __init__(self, parent, editing, standard_colheads, standard_colnames):
@@ -86,6 +88,8 @@ class CreateCustomColumn(QDialog, Ui_QCreateCustomColumn):
if ct == 'datetime':
if c['display'].get('date_format', None):
self.date_format_box.setText(c['display'].get('date_format', ''))
+ elif ct == 'composite':
+ self.composite_box.setText(c['display'].get('composite_template', ''))
self.datatype_changed()
self.exec_()
@@ -94,9 +98,10 @@ class CreateCustomColumn(QDialog, Ui_QCreateCustomColumn):
col_type = self.column_types[self.column_type_box.currentIndex()]['datatype']
except:
col_type = None
- df_visible = col_type == 'datetime'
for x in ('box', 'default_label', 'label'):
- getattr(self, 'date_format_'+x).setVisible(df_visible)
+ getattr(self, 'date_format_'+x).setVisible(col_type == 'datetime')
+ for x in ('box', 'default_label', 'label'):
+ getattr(self, 'composite_'+x).setVisible(col_type == 'composite')
def accept(self):
@@ -122,6 +127,7 @@ class CreateCustomColumn(QDialog, Ui_QCreateCustomColumn):
bad_col = True
if bad_col:
return self.simple_error('', _('The lookup name %s is already used')%col)
+
bad_head = False
for t in self.parent.custcols:
if self.parent.custcols[t]['name'] == col_heading:
@@ -133,12 +139,20 @@ class CreateCustomColumn(QDialog, Ui_QCreateCustomColumn):
if bad_head:
return self.simple_error('', _('The heading %s is already used')%col_heading)
- date_format = {}
+ display_dict = {}
if col_type == 'datetime':
if self.date_format_box.text():
- date_format = {'date_format':unicode(self.date_format_box.text())}
+ display_dict = {'date_format':unicode(self.date_format_box.text())}
else:
- date_format = {'date_format': None}
+ display_dict = {'date_format': None}
+
+ if col_type == 'composite':
+ if not self.composite_box.text():
+ return self.simple_error('', _('You must enter a template for composite fields')%col_heading)
+ display_dict = {'composite_template':unicode(self.composite_box.text())}
+ is_editable = False
+ else:
+ is_editable = True
db = self.parent.gui.library_view.model().db
key = db.field_metadata.custom_field_prefix+col
@@ -148,8 +162,8 @@ class CreateCustomColumn(QDialog, Ui_QCreateCustomColumn):
'label':col,
'name':col_heading,
'datatype':col_type,
- 'editable':True,
- 'display':date_format,
+ 'editable':is_editable,
+ 'display':display_dict,
'normalized':None,
'colnum':None,
'is_multiple':is_multiple,
@@ -164,7 +178,7 @@ class CreateCustomColumn(QDialog, Ui_QCreateCustomColumn):
item.setText(col_heading)
self.parent.custcols[self.orig_column_name]['label'] = col
self.parent.custcols[self.orig_column_name]['name'] = col_heading
- self.parent.custcols[self.orig_column_name]['display'].update(date_format)
+ self.parent.custcols[self.orig_column_name]['display'].update(display_dict)
self.parent.custcols[self.orig_column_name]['*edited'] = True
self.parent.custcols[self.orig_column_name]['*must_restart'] = True
QDialog.accept(self)
diff --git a/src/calibre/gui2/preferences/create_custom_column.ui b/src/calibre/gui2/preferences/create_custom_column.ui
index 5cb9494845..640becca8c 100644
--- a/src/calibre/gui2/preferences/create_custom_column.ui
+++ b/src/calibre/gui2/preferences/create_custom_column.ui
@@ -147,9 +147,59 @@
+
+
+
+
+
+
+ 0
+ 0
+
+
+
+ <p>Field template. Uses the same syntax as save templates.
+
+
+
+
+
+
+ Similar to save templates. For example, {title} {isbn}
+
+
+ Default: (nothing)
+
+
+
+
+
+
+
+
+ &Template
+
+
+ composite_box
+
+
+
+
+
+
+ Qt::Vertical
+
+
+
+ 20
+ 40
+
+
+
+
-
+ Qt::Horizontal
@@ -184,6 +234,7 @@
column_heading_boxcolumn_type_boxdate_format_box
+ composite_boxbutton_box
diff --git a/src/calibre/library/custom_columns.py b/src/calibre/library/custom_columns.py
index 4ba664dadc..d74024280e 100644
--- a/src/calibre/library/custom_columns.py
+++ b/src/calibre/library/custom_columns.py
@@ -18,7 +18,7 @@ from calibre.utils.date import parse_date
class CustomColumns(object):
CUSTOM_DATA_TYPES = frozenset(['rating', 'text', 'comments', 'datetime',
- 'int', 'float', 'bool', 'series'])
+ 'int', 'float', 'bool', 'series', 'composite'])
def custom_table_names(self, num):
return 'custom_column_%d'%num, 'books_custom_column_%d_link'%num
@@ -540,7 +540,7 @@ class CustomColumns(object):
if datatype not in self.CUSTOM_DATA_TYPES:
raise ValueError('%r is not a supported data type'%datatype)
normalized = datatype not in ('datetime', 'comments', 'int', 'bool',
- 'float')
+ 'float', 'composite')
is_multiple = is_multiple and datatype in ('text',)
num = self.conn.execute(
('INSERT INTO '
@@ -551,7 +551,7 @@ class CustomColumns(object):
if datatype in ('rating', 'int'):
dt = 'INT'
- elif datatype in ('text', 'comments', 'series'):
+ elif datatype in ('text', 'comments', 'series', 'composite'):
dt = 'TEXT'
elif datatype in ('float',):
dt = 'REAL'
diff --git a/src/calibre/library/database2.py b/src/calibre/library/database2.py
index 9e9e75a26e..d06d217b76 100644
--- a/src/calibre/library/database2.py
+++ b/src/calibre/library/database2.py
@@ -538,6 +538,7 @@ class LibraryDatabase2(LibraryDatabase, SchemaUpgrade, CustomColumns):
mi.pubdate = self.pubdate(idx, index_is_id=index_is_id)
mi.uuid = self.uuid(idx, index_is_id=index_is_id)
mi.title_sort = self.title_sort(idx, index_is_id=index_is_id)
+ mi.formats = self.formats(idx, index_is_id=index_is_id).split(',')
tags = self.tags(idx, index_is_id=index_is_id)
if tags:
mi.tags = [i.strip() for i in tags.split(',')]
diff --git a/src/calibre/library/field_metadata.py b/src/calibre/library/field_metadata.py
index 2773f573b2..dcdfcfd9d6 100644
--- a/src/calibre/library/field_metadata.py
+++ b/src/calibre/library/field_metadata.py
@@ -68,7 +68,7 @@ class FieldMetadata(dict):
'''
VALID_DATA_TYPES = frozenset([None, 'rating', 'text', 'comments', 'datetime',
- 'int', 'float', 'bool', 'series'])
+ 'int', 'float', 'bool', 'series', 'composite'])
# Builtin metadata {{{
diff --git a/src/calibre/library/server/mobile.py b/src/calibre/library/server/mobile.py
index ab5b39eed8..8e7c75b0ac 100644
--- a/src/calibre/library/server/mobile.py
+++ b/src/calibre/library/server/mobile.py
@@ -228,29 +228,19 @@ class MobileServer(object):
for key in CKEYS:
def concat(name, val):
return '%s:#:%s'%(name, unicode(val))
- val = record[CFM[key]['rec_index']]
- if val:
- datatype = CFM[key]['datatype']
- if datatype in ['comments']:
- continue
- name = CFM[key]['name']
- if datatype == 'text' and CFM[key]['is_multiple']:
- book[key] = concat(name,
- format_tag_string(val, '|',
- no_tag_count=True))
- elif datatype == 'series':
- book[key] = concat(name, '%s [%s]'%(val,
- fmt_sidx(record[CFM.cc_series_index_column_for(key)])))
- elif datatype == 'datetime':
- book[key] = concat(name,
- format_date(val, CFM[key]['display'].get('date_format','dd MMM yyyy')))
- elif datatype == 'bool':
- if val:
- book[key] = concat(name, __builtin__._('Yes'))
- else:
- book[key] = concat(name, __builtin__._('No'))
- else:
- book[key] = concat(name, val)
+ mi = self.db.get_metadata(record[CFM['id']['rec_index']], index_is_id=True)
+ name, val = mi.format_field(key)
+ if val is None:
+ continue
+ datatype = CFM[key]['datatype']
+ if datatype in ['comments']:
+ continue
+ if datatype == 'text' and CFM[key]['is_multiple']:
+ book[key] = concat(name,
+ format_tag_string(val, ',',
+ no_tag_count=True))
+ else:
+ book[key] = concat(name, val)
updated = self.db.last_modified()
diff --git a/src/calibre/library/server/opds.py b/src/calibre/library/server/opds.py
index e495598a2f..0eb7379ac5 100644
--- a/src/calibre/library/server/opds.py
+++ b/src/calibre/library/server/opds.py
@@ -132,7 +132,8 @@ def CATALOG_GROUP_ENTRY(item, category, base_href, version, updated):
link
)
-def ACQUISITION_ENTRY(item, version, FM, updated, CFM, CKEYS):
+def ACQUISITION_ENTRY(item, version, db, updated, CFM, CKEYS):
+ FM = db.FIELD_MAP
title = item[FM['title']]
if not title:
title = _('Unknown')
@@ -157,22 +158,16 @@ def ACQUISITION_ENTRY(item, version, FM, updated, CFM, CKEYS):
(series,
fmt_sidx(float(item[FM['series_index']]))))
for key in CKEYS:
- val = item[CFM[key]['rec_index']]
+ mi = db.get_metadata(item[CFM['id']['rec_index']], index_is_id=True)
+ name, val = mi.format_field(key)
if val is not None:
- name = CFM[key]['name']
datatype = CFM[key]['datatype']
if datatype == 'text' and CFM[key]['is_multiple']:
- extra.append('%s: %s '%(name, format_tag_string(val, '|',
+ extra.append('%s: %s '%(name, format_tag_string(val, ',',
ignore_max=True,
no_tag_count=True)))
- elif datatype == 'series':
- extra.append('%s: %s [%s] '%(name, val,
- fmt_sidx(item[CFM.cc_series_index_column_for(key)])))
- elif datatype == 'datetime':
- extra.append('%s: %s '%(name,
- format_date(val, CFM[key]['display'].get('date_format','dd MMM yyyy'))))
else:
- extra.append('%s: %s ' % (CFM[key]['name'], val))
+ extra.append('%s: %s '%(name, val))
comments = item[FM['comments']]
if comments:
comments = comments_to_html(comments)
@@ -280,13 +275,14 @@ class NavFeed(Feed):
class AcquisitionFeed(NavFeed):
def __init__(self, updated, id_, items, offsets, page_url, up_url, version,
- FM, CFM):
+ db):
NavFeed.__init__(self, id_, updated, version, offsets, page_url, up_url)
+ CFM = db.field_metadata
CKEYS = [key for key in sorted(CFM.get_custom_fields(),
cmp=lambda x,y: cmp(CFM[x]['name'].lower(),
CFM[y]['name'].lower()))]
for item in items:
- self.root.append(ACQUISITION_ENTRY(item, version, FM, updated,
+ self.root.append(ACQUISITION_ENTRY(item, version, db, updated,
CFM, CKEYS))
class CategoryFeed(NavFeed):
@@ -384,7 +380,7 @@ class OPDSServer(object):
cherrypy.response.headers['Last-Modified'] = self.last_modified(updated)
cherrypy.response.headers['Content-Type'] = 'application/atom+xml;profile=opds-catalog'
return str(AcquisitionFeed(updated, id_, items, offsets,
- page_url, up_url, version, self.db.FIELD_MAP, self.db.field_metadata))
+ page_url, up_url, version, self.db))
def opds_search(self, query=None, version=0, offset=0):
try:
diff --git a/src/calibre/library/server/xml.py b/src/calibre/library/server/xml.py
index 8715dda7d0..7f5bc31e70 100644
--- a/src/calibre/library/server/xml.py
+++ b/src/calibre/library/server/xml.py
@@ -102,31 +102,21 @@ class XMLServer(object):
for key in CKEYS:
def concat(name, val):
return '%s:#:%s'%(name, unicode(val))
- val = record[CFM[key]['rec_index']]
- if val:
- datatype = CFM[key]['datatype']
- if datatype in ['comments']:
- continue
- k = str('CF_'+key[1:])
- name = CFM[key]['name']
- custcols.append(k)
- if datatype == 'text' and CFM[key]['is_multiple']:
- kwargs[k] = concat('#T#'+name,
- format_tag_string(val,'|',
- ignore_max=True))
- elif datatype == 'series':
- kwargs[k] = concat(name, '%s [%s]'%(val,
- fmt_sidx(record[CFM.cc_series_index_column_for(key)])))
- elif datatype == 'datetime':
- kwargs[k] = concat(name,
- format_date(val, CFM[key]['display'].get('date_format','dd MMM yyyy')))
- elif datatype == 'bool':
- if val:
- kwargs[k] = concat(name, __builtin__._('Yes'))
- else:
- kwargs[k] = concat(name, __builtin__._('No'))
- else:
- kwargs[k] = concat(name, val)
+ mi = self.db.get_metadata(record[CFM['id']['rec_index']], index_is_id=True)
+ name, val = mi.format_field(key)
+ if not val:
+ continue
+ datatype = CFM[key]['datatype']
+ if datatype in ['comments']:
+ continue
+ k = str('CF_'+key[1:])
+ name = CFM[key]['name']
+ custcols.append(k)
+ if datatype == 'text' and CFM[key]['is_multiple']:
+ kwargs[k] = concat('#T#'+name, format_tag_string(val,',',
+ ignore_max=True))
+ else:
+ kwargs[k] = concat(name, val)
kwargs['custcols'] = ','.join(custcols)
books.append(E.book(c, **kwargs))
From 83fc5b2cc0452533cdcdc342d8ce21e3ab5501a4 Mon Sep 17 00:00:00 2001
From: Charles Haley <>
Date: Sat, 18 Sep 2010 11:38:51 +0100
Subject: [PATCH 042/412] Small cleanup of composite field code.
---
src/calibre/ebooks/metadata/book/base.py | 12 ++++++++----
src/calibre/gui2/library/models.py | 2 +-
2 files changed, 9 insertions(+), 5 deletions(-)
diff --git a/src/calibre/ebooks/metadata/book/base.py b/src/calibre/ebooks/metadata/book/base.py
index 31485dfe1b..ce6e2ee78d 100644
--- a/src/calibre/ebooks/metadata/book/base.py
+++ b/src/calibre/ebooks/metadata/book/base.py
@@ -46,7 +46,10 @@ composite_formatter = SafeFormat()
compress_spaces = re.compile(r'\s+')
def format_composite(x, mi):
- ans = composite_formatter.vformat(x, [], mi).strip()
+ try:
+ ans = composite_formatter.vformat(x, [], mi).strip()
+ except:
+ ans = x
return compress_spaces.sub(' ', ans)
class Metadata(object):
@@ -86,7 +89,10 @@ class Metadata(object):
except AttributeError:
pass
if field in _data['user_metadata'].iterkeys():
- return _data['user_metadata'][field]['#value#']
+ d = _data['user_metadata'][field]
+ if d['datatype'] != 'composite':
+ return d['#value#']
+ return format_composite(d['display']['composite_template'], self)
raise AttributeError(
'Metadata object has no attribute named: '+ repr(field))
@@ -386,8 +392,6 @@ class Metadata(object):
res = format_date(res, cmeta['display'].get('date_format','dd MMM yyyy'))
elif datatype == 'bool':
res = _('Yes') if res else _('No')
- elif datatype == 'composite':
- res = format_composite(cmeta['display']['composite_template'], self)
return (name, res, orig_res, cmeta)
if key in field_metadata and field_metadata[key]['kind'] == 'field':
diff --git a/src/calibre/gui2/library/models.py b/src/calibre/gui2/library/models.py
index 7839b89d7e..2a116f6f3d 100644
--- a/src/calibre/gui2/library/models.py
+++ b/src/calibre/gui2/library/models.py
@@ -605,7 +605,7 @@ class BooksModel(QAbstractTableModel): # {{{
def composite_type(r, key=None):
mi = self.get_cached_metadata(r)
- return QVariant(mi.format_field(key)[1])
+ return QVariant(mi.get(key, ''))
self.dc = {
'title' : functools.partial(text_type,
From c59545a96a968488221f494fcee0baccab642a63 Mon Sep 17 00:00:00 2001
From: Charles Haley <>
Date: Sat, 18 Sep 2010 13:45:01 +0100
Subject: [PATCH 043/412] Change composites to use the cache correctly, so that
searches & sorts used. In the process, remove the metadata cache from
models.py.
Fix some bugs introduced by composite columns:
1) no edit widget in bulk_metadata edit
2) explicitly do not make a delegate in views.py
---
src/calibre/gui2/custom_column_widgets.py | 2 ++
src/calibre/gui2/library/models.py | 28 ++---------------------
src/calibre/gui2/library/views.py | 3 +++
src/calibre/library/caches.py | 20 +++++++++++++++-
src/calibre/library/database2.py | 11 ++++-----
5 files changed, 31 insertions(+), 33 deletions(-)
diff --git a/src/calibre/gui2/custom_column_widgets.py b/src/calibre/gui2/custom_column_widgets.py
index 67ab94d29a..d16233be1a 100644
--- a/src/calibre/gui2/custom_column_widgets.py
+++ b/src/calibre/gui2/custom_column_widgets.py
@@ -348,6 +348,8 @@ def populate_metadata_page(layout, db, book_id, bulk=False, two_column=False, pa
ans = []
column = row = comments_row = 0
for col in cols:
+ if not x[col]['editable']:
+ continue
dt = x[col]['datatype']
if dt == 'comments':
continue
diff --git a/src/calibre/gui2/library/models.py b/src/calibre/gui2/library/models.py
index 2a116f6f3d..be1bf9bc2d 100644
--- a/src/calibre/gui2/library/models.py
+++ b/src/calibre/gui2/library/models.py
@@ -86,7 +86,6 @@ class BooksModel(QAbstractTableModel): # {{{
self.last_search = '' # The last search performed on this model
self.column_map = []
self.headers = {}
- self.metadata_cache = {}
self.alignment_map = {}
self.buffer_size = buffer
self.cover_cache = None
@@ -115,16 +114,6 @@ class BooksModel(QAbstractTableModel): # {{{
def clear_caches(self):
if self.cover_cache:
self.cover_cache.clear_cache()
- self.metadata_cache = {}
-
- def get_cached_metadata(self, idx):
- if idx not in self.metadata_cache:
- self.metadata_cache[idx] = self.db.get_metadata(idx)
- return self.metadata_cache[idx]
-
- def remove_cached_metadata(self, idx):
- if idx in self.metadata_cache:
- del self.metadata_cache[idx]
def read_config(self):
self.use_roman_numbers = config['use_roman_numerals_for_series_number']
@@ -157,7 +146,6 @@ class BooksModel(QAbstractTableModel): # {{{
elif col in self.custom_columns:
self.headers[col] = self.custom_columns[col]['name']
- self.metadata_cache = {}
self.build_data_convertors()
self.reset()
self.database_changed.emit(db)
@@ -171,13 +159,11 @@ class BooksModel(QAbstractTableModel): # {{{
db.add_listener(refresh_cover)
def refresh_ids(self, ids, current_row=-1):
- self.metadata_cache = {}
rows = self.db.refresh_ids(ids)
if rows:
self.refresh_rows(rows, current_row=current_row)
def refresh_rows(self, rows, current_row=-1):
- self.metadata_cache = {}
for row in rows:
if row == current_row:
self.new_bookdisplay_data.emit(
@@ -207,7 +193,6 @@ class BooksModel(QAbstractTableModel): # {{{
return ret
def count_changed(self, *args):
- self.metadata_cache = {}
self.count_changed_signal.emit(self.db.count())
def row_indices(self, index):
@@ -277,7 +262,6 @@ class BooksModel(QAbstractTableModel): # {{{
self.sorting_done.emit(self.db.index)
def refresh(self, reset=True):
- self.metadata_cache = {}
self.db.refresh(field=None)
self.resort(reset=reset)
@@ -334,7 +318,7 @@ class BooksModel(QAbstractTableModel): # {{{
data[_('Series')] = \
_('Book %s of %s.')%\
(sidx, prepare_string_for_xml(series))
- mi = self.get_cached_metadata(idx)
+ mi = self.db.get_metadata(idx)
for key in mi.user_metadata_keys:
name, val = mi.format_field(key)
if val is not None:
@@ -343,7 +327,6 @@ class BooksModel(QAbstractTableModel): # {{{
def set_cache(self, idx):
l, r = 0, self.count()-1
- self.remove_cached_metadata(idx)
if self.cover_cache is not None:
l = max(l, idx-self.buffer_size)
r = min(r, idx+self.buffer_size)
@@ -603,10 +586,6 @@ class BooksModel(QAbstractTableModel): # {{{
def number_type(r, idx=-1):
return QVariant(self.db.data[r][idx])
- def composite_type(r, key=None):
- mi = self.get_cached_metadata(r)
- return QVariant(mi.get(key, ''))
-
self.dc = {
'title' : functools.partial(text_type,
idx=self.db.field_metadata['title']['rec_index'], mult=False),
@@ -640,7 +619,7 @@ class BooksModel(QAbstractTableModel): # {{{
for col in self.custom_columns:
idx = self.custom_columns[col]['rec_index']
datatype = self.custom_columns[col]['datatype']
- if datatype in ('text', 'comments'):
+ if datatype in ('text', 'comments', 'composite'):
self.dc[col] = functools.partial(text_type, idx=idx,
mult=self.custom_columns[col]['is_multiple'])
elif datatype in ('int', 'float'):
@@ -657,8 +636,6 @@ class BooksModel(QAbstractTableModel): # {{{
elif datatype == 'series':
self.dc[col] = functools.partial(series_type, idx=idx,
siix=self.db.field_metadata.cc_series_index_column_for(col))
- elif datatype == 'composite':
- self.dc[col] = functools.partial(composite_type, key=col)
else:
print 'What type is this?', col, datatype
# build a index column to data converter map, to remove the string lookup in the data loop
@@ -753,7 +730,6 @@ class BooksModel(QAbstractTableModel): # {{{
if role == Qt.EditRole:
row, col = index.row(), index.column()
column = self.column_map[col]
- self.remove_cached_metadata(row)
if self.is_custom_column(column):
if not self.set_custom_column_data(row, column, value):
return False
diff --git a/src/calibre/gui2/library/views.py b/src/calibre/gui2/library/views.py
index d67d286aeb..9951edf21b 100644
--- a/src/calibre/gui2/library/views.py
+++ b/src/calibre/gui2/library/views.py
@@ -391,6 +391,9 @@ class BooksView(QTableView): # {{{
self.setItemDelegateForColumn(cm.index(colhead), self.cc_bool_delegate)
elif cc['datatype'] == 'rating':
self.setItemDelegateForColumn(cm.index(colhead), self.rating_delegate)
+ elif cc['datatype'] == 'composite':
+ pass
+ # no delegate for composite columns, as they are not editable
else:
dattr = colhead+'_delegate'
delegate = colhead if hasattr(self, dattr) else 'text'
diff --git a/src/calibre/library/caches.py b/src/calibre/library/caches.py
index 4f795ab733..a013d23cb9 100644
--- a/src/calibre/library/caches.py
+++ b/src/calibre/library/caches.py
@@ -121,6 +121,11 @@ class ResultCache(SearchQueryParser):
self.build_date_relop_dict()
self.build_numeric_relop_dict()
+ self.composites = []
+ for key in field_metadata:
+ if field_metadata[key]['datatype'] == 'composite':
+ self.composites.append((key, field_metadata[key]['rec_index']))
+
def __getitem__(self, row):
return self._data[self._map_filtered[row]]
@@ -372,7 +377,7 @@ class ResultCache(SearchQueryParser):
if len(self.field_metadata[x]['search_terms']):
db_col[x] = self.field_metadata[x]['rec_index']
if self.field_metadata[x]['datatype'] not in \
- ['text', 'comments', 'series']:
+ ['composite', 'text', 'comments', 'series']:
exclude_fields.append(db_col[x])
col_datatype[db_col[x]] = self.field_metadata[x]['datatype']
is_multiple_cols[db_col[x]] = self.field_metadata[x]['is_multiple']
@@ -534,6 +539,10 @@ class ResultCache(SearchQueryParser):
self._data[id] = db.conn.get('SELECT * from meta2 WHERE id=?', (id,))[0]
self._data[id].append(db.has_cover(id, index_is_id=True))
self._data[id].append(db.book_on_device_string(id))
+ if len(self.composites) > 0:
+ mi = db.get_metadata(id, index_is_id=True)
+ for k,c in self.composites:
+ self._data[id][c] = mi.format_field(k)[1]
except IndexError:
return None
try:
@@ -550,6 +559,10 @@ class ResultCache(SearchQueryParser):
self._data[id] = db.conn.get('SELECT * from meta2 WHERE id=?', (id,))[0]
self._data[id].append(db.has_cover(id, index_is_id=True))
self._data[id].append(db.book_on_device_string(id))
+ if len(self.composites) > 0:
+ mi = db.get_metadata(id, index_is_id=True)
+ for k,c in self.composites:
+ self._data[id][c] = mi.format_field(k)[1]
self._map[0:0] = ids
self._map_filtered[0:0] = ids
@@ -575,6 +588,11 @@ class ResultCache(SearchQueryParser):
if item is not None:
item.append(db.has_cover(item[0], index_is_id=True))
item.append(db.book_on_device_string(item[0]))
+ if len(self.composites) > 0:
+ mi = db.get_metadata(item[0], index_is_id=True)
+ for k,c in self.composites:
+ item[c] = mi.format_field(k)[1]
+
self._map = [i[0] for i in self._data if i is not None]
if field is not None:
self.sort(field, ascending)
diff --git a/src/calibre/library/database2.py b/src/calibre/library/database2.py
index d06d217b76..d51a8a62c0 100644
--- a/src/calibre/library/database2.py
+++ b/src/calibre/library/database2.py
@@ -323,12 +323,6 @@ class LibraryDatabase2(LibraryDatabase, SchemaUpgrade, CustomColumns):
self.has_id = self.data.has_id
self.count = self.data.count
- self.refresh_ondevice = functools.partial(self.data.refresh_ondevice, self)
-
- self.refresh()
- self.last_update_check = self.last_modified()
-
-
for prop in ('author_sort', 'authors', 'comment', 'comments', 'isbn',
'publisher', 'rating', 'series', 'series_index', 'tags',
'title', 'timestamp', 'uuid', 'pubdate', 'ondevice'):
@@ -337,6 +331,11 @@ class LibraryDatabase2(LibraryDatabase, SchemaUpgrade, CustomColumns):
setattr(self, 'title_sort', functools.partial(self.get_property,
loc=self.FIELD_MAP['sort']))
+ self.refresh_ondevice = functools.partial(self.data.refresh_ondevice, self)
+ self.refresh()
+ self.last_update_check = self.last_modified()
+
+
def initialize_database(self):
metadata_sqlite = open(P('metadata_sqlite.sql'), 'rb').read()
self.conn.executescript(metadata_sqlite)
From ed7597ae5f142998c3444f1ad941725fa4d21b0d Mon Sep 17 00:00:00 2001
From: Charles Haley <>
Date: Sat, 18 Sep 2010 19:40:44 +0100
Subject: [PATCH 044/412] Playing with search & replace. Added 'global'
template values to the replace expression. Also fixed some problems with
exceptions, and problems with case-insensitive matching in the history boxes.
---
src/calibre/ebooks/metadata/book/base.py | 9 +++
src/calibre/gui2/dialogs/metadata_bulk.py | 68 +++++++++++++++++++----
2 files changed, 66 insertions(+), 11 deletions(-)
diff --git a/src/calibre/ebooks/metadata/book/base.py b/src/calibre/ebooks/metadata/book/base.py
index ce6e2ee78d..1eae2e5326 100644
--- a/src/calibre/ebooks/metadata/book/base.py
+++ b/src/calibre/ebooks/metadata/book/base.py
@@ -12,6 +12,7 @@ from calibre.ebooks.metadata.book import SC_COPYABLE_FIELDS
from calibre.ebooks.metadata.book import SC_FIELDS_COPY_NOT_NULL
from calibre.ebooks.metadata.book import STANDARD_METADATA_FIELDS
from calibre.ebooks.metadata.book import TOP_LEVEL_CLASSIFIERS
+from calibre.ebooks.metadata.book import ALL_METADATA_FIELDS
from calibre.library.field_metadata import FieldMetadata
from calibre.utils.date import isoformat, format_date
@@ -131,6 +132,14 @@ class Metadata(object):
def set(self, field, val, extra=None):
self.__setattr__(field, val, extra)
+ @property
+ def all_keys(self):
+ '''
+ All attribute keys known by this instance, even if their value is None
+ '''
+ _data = object.__getattribute__(self, '_data')
+ return frozenset(ALL_METADATA_FIELDS.union(_data['user_metadata'].iterkeys()))
+
@property
def user_metadata_keys(self):
'The set of user metadata names this object knows about'
diff --git a/src/calibre/gui2/dialogs/metadata_bulk.py b/src/calibre/gui2/dialogs/metadata_bulk.py
index b7d1d0c54b..1fb889757f 100644
--- a/src/calibre/gui2/dialogs/metadata_bulk.py
+++ b/src/calibre/gui2/dialogs/metadata_bulk.py
@@ -4,15 +4,15 @@ __copyright__ = '2008, Kovid Goyal '
'''Dialog to edit metadata in bulk'''
from threading import Thread
-import re
+import re, string
-from PyQt4.Qt import QDialog, QGridLayout
+from PyQt4.Qt import Qt, QDialog, QGridLayout
from PyQt4 import QtGui
from calibre.gui2.dialogs.metadata_bulk_ui import Ui_MetadataBulkDialog
from calibre.gui2.dialogs.tag_editor import TagEditor
from calibre.ebooks.metadata import string_to_authors, \
- authors_to_string
+ authors_to_string, MetaInformation
from calibre.gui2.custom_column_widgets import populate_metadata_page
from calibre.gui2.dialogs.progress import BlockingBusy
from calibre.gui2 import error_dialog, Dispatcher
@@ -99,6 +99,26 @@ class Worker(Thread):
self.callback()
+class SafeFormat(string.Formatter):
+ '''
+ Provides a format function that substitutes '' for any missing value
+ '''
+ def get_value(self, key, args, vals):
+ v = vals.get(key, None)
+ if v is None:
+ return ''
+ if isinstance(v, (tuple, list)):
+ v = ','.join(v)
+ return v
+
+composite_formatter = SafeFormat()
+
+def format_composite(x, mi):
+ try:
+ ans = composite_formatter.vformat(x, [], mi).strip()
+ except:
+ ans = x
+ return ans
class MetadataBulkDialog(QDialog, Ui_MetadataBulkDialog):
@@ -163,7 +183,7 @@ class MetadataBulkDialog(QDialog, Ui_MetadataBulkDialog):
self.s_r_number_of_books = min(7, len(self.ids))
for i in range(1,self.s_r_number_of_books+1):
w = QtGui.QLabel(self.tabWidgetPage3)
- w.setText(_('Book %d:'%i))
+ w.setText(_('Book %d:')%i)
self.gridLayout1.addWidget(w, i+offset, 0, 1, 1)
w = QtGui.QLineEdit(self.tabWidgetPage3)
w.setReadOnly(True)
@@ -205,6 +225,10 @@ class MetadataBulkDialog(QDialog, Ui_MetadataBulkDialog):
self.test_text.editTextChanged[str].connect(self.s_r_paint_results)
self.central_widget.setCurrentIndex(0)
+ self.search_for.completer().setCaseSensitivity(Qt.CaseSensitive)
+ self.replace_with.completer().setCaseSensitivity(Qt.CaseSensitive)
+
+
def s_r_field_changed(self, txt):
txt = unicode(txt)
for i in range(0, self.s_r_number_of_books):
@@ -220,6 +244,8 @@ class MetadataBulkDialog(QDialog, Ui_MetadataBulkDialog):
if val:
val.sort(cmp=lambda x,y: cmp(x.lower(), y.lower()))
val = val[0]
+ if txt == 'authors':
+ val = val.replace('|', ',')
else:
val = ''
else:
@@ -239,37 +265,55 @@ class MetadataBulkDialog(QDialog, Ui_MetadataBulkDialog):
for i in range(0,self.s_r_number_of_books):
getattr(self, 'book_%d_result'%(i+1)).setText('')
+ field_match_re = re.compile(r'(^|[^\\])(\\g<)([^>]+)(>)')
+
def s_r_func(self, match):
- rf = self.s_r_functions[unicode(self.replace_func.currentText())]
- rv = unicode(self.replace_with.text())
- val = match.expand(rv)
- return rf(val)
+ rfunc = self.s_r_functions[unicode(self.replace_func.currentText())]
+ rtext = unicode(self.replace_with.text())
+ mi_data = self.mi.get_all_non_none_attributes()
+
+ def fm_func(m):
+ try:
+ if m.group(3) not in self.mi.all_keys: return m.group(0)
+ else: return '%s{%s}'%(m.group(1), m.group(3))
+ except:
+ import traceback
+ traceback.print_exc()
+ return m.group(0)
+
+ rtext = re.sub(self.field_match_re, fm_func, rtext)
+ rtext = match.expand(rtext)
+ rtext = format_composite(rtext, mi_data)
+ return rfunc(rtext)
def s_r_paint_results(self, txt):
self.s_r_error = None
self.s_r_set_colors()
try:
self.s_r_obj = re.compile(unicode(self.search_for.text()))
- except re.error as e:
+ except Exception as e:
self.s_r_obj = None
self.s_r_error = e
self.s_r_set_colors()
return
try:
+ self.mi = MetaInformation(None, None)
self.test_result.setText(self.s_r_obj.sub(self.s_r_func,
unicode(self.test_text.text())))
- except re.error as e:
+ except Exception as e:
self.s_r_error = e
self.s_r_set_colors()
return
for i in range(0,self.s_r_number_of_books):
+ id = self.ids[i]
+ self.mi = self.db.get_metadata(id, index_is_id=True)
wt = getattr(self, 'book_%d_text'%(i+1))
wr = getattr(self, 'book_%d_result'%(i+1))
try:
wr.setText(self.s_r_obj.sub(self.s_r_func, unicode(wt.text())))
- except re.error as e:
+ except Exception as e:
self.s_r_error = e
self.s_r_set_colors()
break
@@ -303,6 +347,8 @@ class MetadataBulkDialog(QDialog, Ui_MetadataBulkDialog):
# The standard tags and authors values want to be lists.
# All custom columns are to be strings
val = fm['is_multiple'].join(val)
+ elif field == 'authors':
+ val = [v.replace('|', ',') for v in val]
else:
val = apply_pattern(val)
From 3f763407a02f5c00599bdbe43f053a821fb0a3e3 Mon Sep 17 00:00:00 2001
From: Kovid Goyal
Date: Sat, 18 Sep 2010 20:37:59 -0600
Subject: [PATCH 045/412] Refactor to use new field formatting infrastructure
of Metadata class
---
src/calibre/devices/usbms/books.py | 7 ++--
src/calibre/ebooks/metadata/book/__init__.py | 7 ++--
src/calibre/ebooks/metadata/book/base.py | 12 +++---
src/calibre/library/database2.py | 1 +
src/calibre/library/server/mobile.py | 36 +++++++-----------
src/calibre/library/server/opds.py | 25 +++++-------
src/calibre/library/server/xml.py | 40 ++++++++------------
7 files changed, 51 insertions(+), 77 deletions(-)
diff --git a/src/calibre/devices/usbms/books.py b/src/calibre/devices/usbms/books.py
index eab625f7be..13fcb90b49 100644
--- a/src/calibre/devices/usbms/books.py
+++ b/src/calibre/devices/usbms/books.py
@@ -137,7 +137,6 @@ class CollectionsBookList(BookList):
# For existing books, modify the collections only if the user
# specified 'on_connect'
attrs = collection_attributes
- meta_vals = book.get_all_non_none_attributes()
for attr in attrs:
attr = attr.strip()
ign, val, orig_val, fm = book.format_field_extended(attr)
@@ -166,7 +165,7 @@ class CollectionsBookList(BookList):
continue
if attr == 'series' or \
('series' in collection_attributes and
- meta_vals.get('series', None) == category):
+ book.get('series', None) == category):
is_series = True
cat_name = self.compute_category_name(attr, category, fm)
if cat_name not in collections:
@@ -177,10 +176,10 @@ class CollectionsBookList(BookList):
collections_lpaths[cat_name].add(lpath)
if is_series:
collections[cat_name].append(
- (book, meta_vals.get(attr+'_index', sys.maxint)))
+ (book, book.get(attr+'_index', sys.maxint)))
else:
collections[cat_name].append(
- (book, meta_vals.get('title_sort', 'zzzz')))
+ (book, book.get('title_sort', 'zzzz')))
# Sort collections
result = {}
for category, books in collections.items():
diff --git a/src/calibre/ebooks/metadata/book/__init__.py b/src/calibre/ebooks/metadata/book/__init__.py
index e087f8072d..e6dff9110b 100644
--- a/src/calibre/ebooks/metadata/book/__init__.py
+++ b/src/calibre/ebooks/metadata/book/__init__.py
@@ -81,9 +81,8 @@ DEVICE_METADATA_FIELDS = frozenset([
CALIBRE_METADATA_FIELDS = frozenset([
'application_id', # An application id, currently set to the db_id.
- # the calibre primary key of the item.
'db_id', # the calibre primary key of the item.
- # TODO: NEWMETA: May want to remove once Sony's no longer use it
+ 'formats', # list of formats (extensions) for this book
]
)
@@ -124,5 +123,5 @@ SERIALIZABLE_FIELDS = SOCIAL_METADATA_FIELDS.union(
PUBLICATION_METADATA_FIELDS).union(
CALIBRE_METADATA_FIELDS).union(
DEVICE_METADATA_FIELDS) - \
- frozenset(['device_collections'])
- # device_collections is rebuilt when needed
+ frozenset(['device_collections', 'formats'])
+ # these are rebuilt when needed
diff --git a/src/calibre/ebooks/metadata/book/base.py b/src/calibre/ebooks/metadata/book/base.py
index b252f518da..8868709db2 100644
--- a/src/calibre/ebooks/metadata/book/base.py
+++ b/src/calibre/ebooks/metadata/book/base.py
@@ -343,26 +343,26 @@ class Metadata(object):
def format_rating(self):
return unicode(self.rating)
- def format_field(self, key):
- name, val, ign, ign = self.format_field_extended(key)
+ def format_field(self, key, series_with_index=True):
+ name, val, ign, ign = self.format_field_extended(key, series_with_index)
return (name, val)
- def format_field_extended(self, key):
+ def format_field_extended(self, key, series_with_index=True):
from calibre.ebooks.metadata import authors_to_string
'''
returns the tuple (field_name, formatted_value)
'''
if key in self.user_metadata_keys:
res = self.get(key, None)
+ cmeta = self.get_user_metadata(key, make_copy=False)
if res is None or res == '':
return (None, None, None, None)
orig_res = res
- cmeta = self.get_user_metadata(key, make_copy=False)
name = unicode(cmeta['name'])
datatype = cmeta['datatype']
if datatype == 'text' and cmeta['is_multiple']:
res = u', '.join(res)
- elif datatype == 'series':
+ elif datatype == 'series' and series_with_index:
res = res + \
' [%s]'%self.format_series_index(val=self.get_extra(key))
elif datatype == 'datetime':
@@ -383,7 +383,7 @@ class Metadata(object):
res = authors_to_string(res)
elif datatype == 'text' and fmeta['is_multiple']:
res = u', '.join(res)
- elif datatype == 'series':
+ elif datatype == 'series' and series_with_index:
res = res + ' [%s]'%self.format_series_index()
elif datatype == 'datetime':
res = format_date(res, fmeta['display'].get('date_format','dd MMM yyyy'))
diff --git a/src/calibre/library/database2.py b/src/calibre/library/database2.py
index 9e9e75a26e..d06d217b76 100644
--- a/src/calibre/library/database2.py
+++ b/src/calibre/library/database2.py
@@ -538,6 +538,7 @@ class LibraryDatabase2(LibraryDatabase, SchemaUpgrade, CustomColumns):
mi.pubdate = self.pubdate(idx, index_is_id=index_is_id)
mi.uuid = self.uuid(idx, index_is_id=index_is_id)
mi.title_sort = self.title_sort(idx, index_is_id=index_is_id)
+ mi.formats = self.formats(idx, index_is_id=index_is_id).split(',')
tags = self.tags(idx, index_is_id=index_is_id)
if tags:
mi.tags = [i.strip() for i in tags.split(',')]
diff --git a/src/calibre/library/server/mobile.py b/src/calibre/library/server/mobile.py
index ab5b39eed8..8e7c75b0ac 100644
--- a/src/calibre/library/server/mobile.py
+++ b/src/calibre/library/server/mobile.py
@@ -228,29 +228,19 @@ class MobileServer(object):
for key in CKEYS:
def concat(name, val):
return '%s:#:%s'%(name, unicode(val))
- val = record[CFM[key]['rec_index']]
- if val:
- datatype = CFM[key]['datatype']
- if datatype in ['comments']:
- continue
- name = CFM[key]['name']
- if datatype == 'text' and CFM[key]['is_multiple']:
- book[key] = concat(name,
- format_tag_string(val, '|',
- no_tag_count=True))
- elif datatype == 'series':
- book[key] = concat(name, '%s [%s]'%(val,
- fmt_sidx(record[CFM.cc_series_index_column_for(key)])))
- elif datatype == 'datetime':
- book[key] = concat(name,
- format_date(val, CFM[key]['display'].get('date_format','dd MMM yyyy')))
- elif datatype == 'bool':
- if val:
- book[key] = concat(name, __builtin__._('Yes'))
- else:
- book[key] = concat(name, __builtin__._('No'))
- else:
- book[key] = concat(name, val)
+ mi = self.db.get_metadata(record[CFM['id']['rec_index']], index_is_id=True)
+ name, val = mi.format_field(key)
+ if val is None:
+ continue
+ datatype = CFM[key]['datatype']
+ if datatype in ['comments']:
+ continue
+ if datatype == 'text' and CFM[key]['is_multiple']:
+ book[key] = concat(name,
+ format_tag_string(val, ',',
+ no_tag_count=True))
+ else:
+ book[key] = concat(name, val)
updated = self.db.last_modified()
diff --git a/src/calibre/library/server/opds.py b/src/calibre/library/server/opds.py
index e495598a2f..d495f58fa1 100644
--- a/src/calibre/library/server/opds.py
+++ b/src/calibre/library/server/opds.py
@@ -20,7 +20,6 @@ from calibre.library.comments import comments_to_html
from calibre.library.server.utils import format_tag_string
from calibre import guess_type
from calibre.utils.ordered_dict import OrderedDict
-from calibre.utils.date import format_date
BASE_HREFS = {
0 : '/stanza',
@@ -132,7 +131,8 @@ def CATALOG_GROUP_ENTRY(item, category, base_href, version, updated):
link
)
-def ACQUISITION_ENTRY(item, version, FM, updated, CFM, CKEYS):
+def ACQUISITION_ENTRY(item, version, db, updated, CFM, CKEYS):
+ FM = db.FIELD_MAP
title = item[FM['title']]
if not title:
title = _('Unknown')
@@ -157,22 +157,16 @@ def ACQUISITION_ENTRY(item, version, FM, updated, CFM, CKEYS):
(series,
fmt_sidx(float(item[FM['series_index']]))))
for key in CKEYS:
- val = item[CFM[key]['rec_index']]
+ mi = db.get_metadata(item[CFM['id']['rec_index']], index_is_id=True)
+ name, val = mi.format_field(key)
if val is not None:
- name = CFM[key]['name']
datatype = CFM[key]['datatype']
if datatype == 'text' and CFM[key]['is_multiple']:
- extra.append('%s: %s '%(name, format_tag_string(val, '|',
+ extra.append('%s: %s '%(name, format_tag_string(val, ',',
ignore_max=True,
no_tag_count=True)))
- elif datatype == 'series':
- extra.append('%s: %s [%s] '%(name, val,
- fmt_sidx(item[CFM.cc_series_index_column_for(key)])))
- elif datatype == 'datetime':
- extra.append('%s: %s '%(name,
- format_date(val, CFM[key]['display'].get('date_format','dd MMM yyyy'))))
else:
- extra.append('%s: %s ' % (CFM[key]['name'], val))
+ extra.append('%s: %s '%(name, val))
comments = item[FM['comments']]
if comments:
comments = comments_to_html(comments)
@@ -280,13 +274,14 @@ class NavFeed(Feed):
class AcquisitionFeed(NavFeed):
def __init__(self, updated, id_, items, offsets, page_url, up_url, version,
- FM, CFM):
+ db):
NavFeed.__init__(self, id_, updated, version, offsets, page_url, up_url)
+ CFM = db.field_metadata
CKEYS = [key for key in sorted(CFM.get_custom_fields(),
cmp=lambda x,y: cmp(CFM[x]['name'].lower(),
CFM[y]['name'].lower()))]
for item in items:
- self.root.append(ACQUISITION_ENTRY(item, version, FM, updated,
+ self.root.append(ACQUISITION_ENTRY(item, version, db, updated,
CFM, CKEYS))
class CategoryFeed(NavFeed):
@@ -384,7 +379,7 @@ class OPDSServer(object):
cherrypy.response.headers['Last-Modified'] = self.last_modified(updated)
cherrypy.response.headers['Content-Type'] = 'application/atom+xml;profile=opds-catalog'
return str(AcquisitionFeed(updated, id_, items, offsets,
- page_url, up_url, version, self.db.FIELD_MAP, self.db.field_metadata))
+ page_url, up_url, version, self.db))
def opds_search(self, query=None, version=0, offset=0):
try:
diff --git a/src/calibre/library/server/xml.py b/src/calibre/library/server/xml.py
index 8715dda7d0..7f5bc31e70 100644
--- a/src/calibre/library/server/xml.py
+++ b/src/calibre/library/server/xml.py
@@ -102,31 +102,21 @@ class XMLServer(object):
for key in CKEYS:
def concat(name, val):
return '%s:#:%s'%(name, unicode(val))
- val = record[CFM[key]['rec_index']]
- if val:
- datatype = CFM[key]['datatype']
- if datatype in ['comments']:
- continue
- k = str('CF_'+key[1:])
- name = CFM[key]['name']
- custcols.append(k)
- if datatype == 'text' and CFM[key]['is_multiple']:
- kwargs[k] = concat('#T#'+name,
- format_tag_string(val,'|',
- ignore_max=True))
- elif datatype == 'series':
- kwargs[k] = concat(name, '%s [%s]'%(val,
- fmt_sidx(record[CFM.cc_series_index_column_for(key)])))
- elif datatype == 'datetime':
- kwargs[k] = concat(name,
- format_date(val, CFM[key]['display'].get('date_format','dd MMM yyyy')))
- elif datatype == 'bool':
- if val:
- kwargs[k] = concat(name, __builtin__._('Yes'))
- else:
- kwargs[k] = concat(name, __builtin__._('No'))
- else:
- kwargs[k] = concat(name, val)
+ mi = self.db.get_metadata(record[CFM['id']['rec_index']], index_is_id=True)
+ name, val = mi.format_field(key)
+ if not val:
+ continue
+ datatype = CFM[key]['datatype']
+ if datatype in ['comments']:
+ continue
+ k = str('CF_'+key[1:])
+ name = CFM[key]['name']
+ custcols.append(k)
+ if datatype == 'text' and CFM[key]['is_multiple']:
+ kwargs[k] = concat('#T#'+name, format_tag_string(val,',',
+ ignore_max=True))
+ else:
+ kwargs[k] = concat(name, val)
kwargs['custcols'] = ','.join(custcols)
books.append(E.book(c, **kwargs))
From 7eaf417bb10e9d87038b47941c524ea9aa121ad2 Mon Sep 17 00:00:00 2001
From: Charles Haley <>
Date: Sun, 19 Sep 2010 07:47:03 +0100
Subject: [PATCH 046/412] Fix content server tags display problem
---
resources/content_server/gui.js | 5 ++++-
1 file changed, 4 insertions(+), 1 deletion(-)
diff --git a/resources/content_server/gui.js b/resources/content_server/gui.js
index afc21137e1..bd0743a854 100644
--- a/resources/content_server/gui.js
+++ b/resources/content_server/gui.js
@@ -84,7 +84,10 @@ function render_book(book) {
}
title += ''
title += ''
- if (tags) title += 'Tags=[{0}] '.format(tags);
+ if (tags) {
+ t = tags.split(':&:', 2);
+ title += 'Tags=[{0}] '.format(t[1]);
+ }
custcols = book.attr("custcols").split(',')
for ( i = 0; i < custcols.length; i++) {
if (custcols[i].length > 0) {
From db446dc4ee28f3280f4b1a139e8243c7dbf817d7 Mon Sep 17 00:00:00 2001
From: Charles Haley <>
Date: Sun, 19 Sep 2010 09:24:57 +0100
Subject: [PATCH 047/412] Add an attribute to the main item record for caching
a Metadata instance. LibraryDatabase2.get_metadata it if None, and returns it
if not None.
---
src/calibre/ebooks/metadata/book/base.py | 6 ++++++
src/calibre/library/caches.py | 3 +++
src/calibre/library/database2.py | 25 ++++++++++++++++++------
src/calibre/library/field_metadata.py | 9 +++++++++
4 files changed, 37 insertions(+), 6 deletions(-)
diff --git a/src/calibre/ebooks/metadata/book/base.py b/src/calibre/ebooks/metadata/book/base.py
index cd2f2a3165..7b8eb07908 100644
--- a/src/calibre/ebooks/metadata/book/base.py
+++ b/src/calibre/ebooks/metadata/book/base.py
@@ -114,6 +114,12 @@ class Metadata(object):
# Don't abuse this privilege
self.__dict__[field] = val
+ def deepcopy(self):
+ m = Metadata(None)
+ m.__dict__ = copy.deepcopy(self.__dict__)
+ object.__setattr__(m, '_data', copy.deepcopy(object.__getattribute__(self, '_data')))
+ return m
+
def get(self, field, default=None):
if default is not None:
try:
diff --git a/src/calibre/library/caches.py b/src/calibre/library/caches.py
index ead7a2b46d..b3f901ecd3 100644
--- a/src/calibre/library/caches.py
+++ b/src/calibre/library/caches.py
@@ -539,6 +539,7 @@ class ResultCache(SearchQueryParser):
self._data[id] = db.conn.get('SELECT * from meta2 WHERE id=?', (id,))[0]
self._data[id].append(db.has_cover(id, index_is_id=True))
self._data[id].append(db.book_on_device_string(id))
+ self._data[id].append(None)
if len(self.composites) > 0:
mi = db.get_metadata(id, index_is_id=True)
for k,c in self.composites:
@@ -559,6 +560,7 @@ class ResultCache(SearchQueryParser):
self._data[id] = db.conn.get('SELECT * from meta2 WHERE id=?', (id,))[0]
self._data[id].append(db.has_cover(id, index_is_id=True))
self._data[id].append(db.book_on_device_string(id))
+ self._data[id].append(None)
if len(self.composites) > 0:
mi = db.get_metadata(id, index_is_id=True)
for k,c in self.composites:
@@ -588,6 +590,7 @@ class ResultCache(SearchQueryParser):
if item is not None:
item.append(db.has_cover(item[0], index_is_id=True))
item.append(db.book_on_device_string(item[0]))
+ item.append(None)
if len(self.composites) > 0:
mi = db.get_metadata(item[0], index_is_id=True)
for k,c in self.composites:
diff --git a/src/calibre/library/database2.py b/src/calibre/library/database2.py
index d51a8a62c0..489504dbb5 100644
--- a/src/calibre/library/database2.py
+++ b/src/calibre/library/database2.py
@@ -20,8 +20,8 @@ from calibre.library.caches import ResultCache
from calibre.library.custom_columns import CustomColumns
from calibre.library.sqlite import connect, IntegrityError, DBThread
from calibre.library.prefs import DBPrefs
-from calibre.ebooks.metadata import string_to_authors, authors_to_string, \
- MetaInformation
+from calibre.ebooks.metadata import string_to_authors, authors_to_string
+from calibre.ebooks.metadata.book.base import Metadata
from calibre.ebooks.metadata.meta import get_metadata, metadata_from_formats
from calibre.constants import preferred_encoding, iswindows, isosx, filesystem_encoding
from calibre.ptempfile import PersistentTemporaryFile
@@ -282,6 +282,8 @@ class LibraryDatabase2(LibraryDatabase, SchemaUpgrade, CustomColumns):
self.field_metadata.set_field_record_index('cover', base+1, prefer_custom=False)
self.FIELD_MAP['ondevice'] = base+2
self.field_metadata.set_field_record_index('ondevice', base+2, prefer_custom=False)
+ self.FIELD_MAP['all_metadata'] = base+3
+ self.field_metadata.set_field_record_index('all_metadata', base+3, prefer_custom=False)
script = '''
DROP VIEW IF EXISTS meta2;
@@ -520,15 +522,26 @@ class LibraryDatabase2(LibraryDatabase, SchemaUpgrade, CustomColumns):
def get_metadata(self, idx, index_is_id=False, get_cover=False):
'''
- Convenience method to return metadata as a L{MetaInformation} object.
+ Convenience method to return metadata as a L{Metadata} object.
'''
+ mi = self.data.get(idx, self.FIELD_MAP['all_metadata'],
+ row_is_id = index_is_id)
+ if mi is not None:
+ return mi
+
+ mi = self.field_metadata.get_empty_metadata_instance()
+ self.data.set(idx, self.FIELD_MAP['all_metadata'], mi,
+ row_is_id = index_is_id)
+
aut_list = self.authors_with_sort_strings(idx, index_is_id=index_is_id)
aum = []
aus = {}
for (author, author_sort) in aut_list:
aum.append(author)
aus[author] = author_sort
- mi = MetaInformation(self.title(idx, index_is_id=index_is_id), aum)
+ mi = self.field_metadata.get_empty_metadata_instance()
+ mi.title = self.title(idx, index_is_id=index_is_id)
+ mi.authors = aum
mi.author_sort = self.author_sort(idx, index_is_id=index_is_id)
mi.author_sort_map = aus
mi.comments = self.comments(idx, index_is_id=index_is_id)
@@ -1056,7 +1069,7 @@ class LibraryDatabase2(LibraryDatabase, SchemaUpgrade, CustomColumns):
def set_metadata(self, id, mi, ignore_errors=False):
'''
- Set metadata for the book `id` from the `MetaInformation` object `mi`
+ Set metadata for the book `id` from the `Metadata` object `mi`
'''
def doit(func, *args, **kwargs):
try:
@@ -1710,7 +1723,7 @@ class LibraryDatabase2(LibraryDatabase, SchemaUpgrade, CustomColumns):
try:
mi = get_metadata(stream, format)
except:
- mi = MetaInformation(title, ['calibre'])
+ mi = Metadata(title, ['calibre'])
stream.seek(0)
mi.title, mi.authors = title, ['calibre']
mi.tags = [_('Catalog')]
diff --git a/src/calibre/library/field_metadata.py b/src/calibre/library/field_metadata.py
index dcdfcfd9d6..258c739e1c 100644
--- a/src/calibre/library/field_metadata.py
+++ b/src/calibre/library/field_metadata.py
@@ -209,6 +209,15 @@ class FieldMetadata(dict):
'search_terms':[],
'is_custom':False,
'is_category':False}),
+ ('all_metadata',{'table':None,
+ 'column':None,
+ 'datatype':None,
+ 'is_multiple':None,
+ 'kind':'field',
+ 'name':None,
+ 'search_terms':[],
+ 'is_custom':False,
+ 'is_category':False}),
('ondevice', {'table':None,
'column':None,
'datatype':'text',
From 354db58545add6acf9ab35c4305d233a6071f7bd Mon Sep 17 00:00:00 2001
From: Charles Haley <>
Date: Sun, 19 Sep 2010 10:27:18 +0100
Subject: [PATCH 048/412] 1) fix the 'new' Metadata caching to really work. :)
2) remove a useless comment in FieldMetadata
---
src/calibre/library/caches.py | 1 +
src/calibre/library/database2.py | 3 +--
src/calibre/library/field_metadata.py | 1 -
3 files changed, 2 insertions(+), 3 deletions(-)
diff --git a/src/calibre/library/caches.py b/src/calibre/library/caches.py
index b3f901ecd3..073f98583c 100644
--- a/src/calibre/library/caches.py
+++ b/src/calibre/library/caches.py
@@ -509,6 +509,7 @@ class ResultCache(SearchQueryParser):
def set(self, row, col, val, row_is_id=False):
id = row if row_is_id else self._map_filtered[row]
+ self._data[id][self.FIELD_MAP['all_metadata']] = None
self._data[id][col] = val
def get(self, row, col, row_is_id=False):
diff --git a/src/calibre/library/database2.py b/src/calibre/library/database2.py
index 489504dbb5..3158cbf94f 100644
--- a/src/calibre/library/database2.py
+++ b/src/calibre/library/database2.py
@@ -529,7 +529,7 @@ class LibraryDatabase2(LibraryDatabase, SchemaUpgrade, CustomColumns):
if mi is not None:
return mi
- mi = self.field_metadata.get_empty_metadata_instance()
+ mi = Metadata(None)
self.data.set(idx, self.FIELD_MAP['all_metadata'], mi,
row_is_id = index_is_id)
@@ -539,7 +539,6 @@ class LibraryDatabase2(LibraryDatabase, SchemaUpgrade, CustomColumns):
for (author, author_sort) in aut_list:
aum.append(author)
aus[author] = author_sort
- mi = self.field_metadata.get_empty_metadata_instance()
mi.title = self.title(idx, index_is_id=index_is_id)
mi.authors = aum
mi.author_sort = self.author_sort(idx, index_is_id=index_is_id)
diff --git a/src/calibre/library/field_metadata.py b/src/calibre/library/field_metadata.py
index 258c739e1c..971d91b248 100644
--- a/src/calibre/library/field_metadata.py
+++ b/src/calibre/library/field_metadata.py
@@ -304,7 +304,6 @@ class FieldMetadata(dict):
# search labels that are not db columns
search_items = [ 'all',
-# 'date',
'search',
]
From ef3fd4df536811ca7b91be06ab10595ae1dc6a4c Mon Sep 17 00:00:00 2001
From: Kovid Goyal
Date: Sun, 19 Sep 2010 10:39:45 -0600
Subject: [PATCH 049/412] ...
---
resources/content_server/gui.js | 5 ++++-
1 file changed, 4 insertions(+), 1 deletion(-)
diff --git a/resources/content_server/gui.js b/resources/content_server/gui.js
index afc21137e1..bd0743a854 100644
--- a/resources/content_server/gui.js
+++ b/resources/content_server/gui.js
@@ -84,7 +84,10 @@ function render_book(book) {
}
title += ''
title += ''
- if (tags) title += 'Tags=[{0}] '.format(tags);
+ if (tags) {
+ t = tags.split(':&:', 2);
+ title += 'Tags=[{0}] '.format(t[1]);
+ }
custcols = book.attr("custcols").split(',')
for ( i = 0; i < custcols.length; i++) {
if (custcols[i].length > 0) {
From e22efca956253cd52294dde0cb6e59dfb87ac945 Mon Sep 17 00:00:00 2001
From: Kovid Goyal
Date: Sun, 19 Sep 2010 10:42:23 -0600
Subject: [PATCH 050/412] ...
---
src/calibre/library/server/mobile.py | 2 +-
src/calibre/library/server/xml.py | 1 -
2 files changed, 1 insertion(+), 2 deletions(-)
diff --git a/src/calibre/library/server/mobile.py b/src/calibre/library/server/mobile.py
index 8e7c75b0ac..c0a3c122cd 100644
--- a/src/calibre/library/server/mobile.py
+++ b/src/calibre/library/server/mobile.py
@@ -17,7 +17,7 @@ from calibre.library.server.utils import strftime, format_tag_string
from calibre.ebooks.metadata import fmt_sidx
from calibre.constants import __appname__
from calibre import human_readable
-from calibre.utils.date import utcfromtimestamp, format_date
+from calibre.utils.date import utcfromtimestamp
def CLASS(*args, **kwargs): # class is a reserved word in Python
kwargs['class'] = ' '.join(args)
diff --git a/src/calibre/library/server/xml.py b/src/calibre/library/server/xml.py
index 7f5bc31e70..45ffdc2737 100644
--- a/src/calibre/library/server/xml.py
+++ b/src/calibre/library/server/xml.py
@@ -15,7 +15,6 @@ from calibre.library.server.utils import strftime, format_tag_string
from calibre.ebooks.metadata import fmt_sidx
from calibre.constants import preferred_encoding
from calibre import isbytestring
-from calibre.utils.date import format_date
E = ElementMaker()
From 16266c6e700513fd60abafc88de205951cbea90e Mon Sep 17 00:00:00 2001
From: Charles Haley <>
Date: Sun, 19 Sep 2010 20:11:39 +0100
Subject: [PATCH 051/412] Change field_keys to sortable_keys
---
src/calibre/library/caches.py | 2 +-
src/calibre/library/field_metadata.py | 6 ++++--
src/calibre/library/server/content.py | 2 +-
3 files changed, 6 insertions(+), 4 deletions(-)
diff --git a/src/calibre/library/caches.py b/src/calibre/library/caches.py
index 073f98583c..770a362a1d 100644
--- a/src/calibre/library/caches.py
+++ b/src/calibre/library/caches.py
@@ -621,7 +621,7 @@ class ResultCache(SearchQueryParser):
def multisort(self, fields=[], subsort=False):
fields = [(self.sanitize_sort_field_name(x), bool(y)) for x, y in fields]
- keys = self.field_metadata.field_keys()
+ keys = self.field_metadata.sortable_keys()
fields = [x for x in fields if x[0] in keys]
if subsort and 'sort' not in [x[0] for x in fields]:
fields += [('sort', True)]
diff --git a/src/calibre/library/field_metadata.py b/src/calibre/library/field_metadata.py
index 971d91b248..e4a4f5270d 100644
--- a/src/calibre/library/field_metadata.py
+++ b/src/calibre/library/field_metadata.py
@@ -348,8 +348,10 @@ class FieldMetadata(dict):
def keys(self):
return self._tb_cats.keys()
- def field_keys(self):
- return [k for k in self._tb_cats.keys() if self._tb_cats[k]['kind']=='field']
+ def sortable_keys(self):
+ return [k for k in self._tb_cats.keys()
+ if self._tb_cats[k]['kind']=='field' and
+ self._tb_cats[k]['datatype'] is not None]
def iterkeys(self):
for key in self._tb_cats:
diff --git a/src/calibre/library/server/content.py b/src/calibre/library/server/content.py
index 95794a8c1d..acfc1f9ab1 100644
--- a/src/calibre/library/server/content.py
+++ b/src/calibre/library/server/content.py
@@ -56,7 +56,7 @@ class ContentServer(object):
def sort(self, items, field, order):
field = self.db.data.sanitize_sort_field_name(field)
- if field not in self.db.field_metadata.field_keys():
+ if field not in self.db.field_metadata.sortable_keys():
raise cherrypy.HTTPError(400, '%s is not a valid sort field'%field)
keyg = CSSortKeyGenerator([(field, order)], self.db.field_metadata)
items.sort(key=keyg, reverse=not order)
From 3a45aa84b07b19a91e66684bdaa178d8fc5cd71a Mon Sep 17 00:00:00 2001
From: Charles Haley <>
Date: Sun, 19 Sep 2010 20:42:22 +0100
Subject: [PATCH 052/412] Fix exception instead of error when composite column
template is blank
---
.../gui2/preferences/create_custom_column.py | 17 +++++++++++------
1 file changed, 11 insertions(+), 6 deletions(-)
diff --git a/src/calibre/gui2/preferences/create_custom_column.py b/src/calibre/gui2/preferences/create_custom_column.py
index 4b21301ccd..ab1e736223 100644
--- a/src/calibre/gui2/preferences/create_custom_column.py
+++ b/src/calibre/gui2/preferences/create_custom_column.py
@@ -82,7 +82,8 @@ class CreateCustomColumn(QDialog, Ui_QCreateCustomColumn):
ct = c['datatype'] if not c['is_multiple'] else '*text'
self.orig_column_number = c['colnum']
self.orig_column_name = col
- column_numbers = dict(map(lambda x:(self.column_types[x]['datatype'], x), self.column_types))
+ column_numbers = dict(map(lambda x:(self.column_types[x]['datatype'], x),
+ self.column_types))
self.column_type_box.setCurrentIndex(column_numbers[ct])
self.column_type_box.setEnabled(False)
if ct == 'datetime':
@@ -109,9 +110,11 @@ class CreateCustomColumn(QDialog, Ui_QCreateCustomColumn):
if not col:
return self.simple_error('', _('No lookup name was provided'))
if re.match('^\w*$', col) is None or not col[0].isalpha() or col.lower() != col:
- return self.simple_error('', _('The lookup name must contain only lower case letters, digits and underscores, and start with a letter'))
+ return self.simple_error('', _('The lookup name must contain only '
+ 'lower case letters, digits and underscores, and start with a letter'))
if col.endswith('_index'):
- return self.simple_error('', _('Lookup names cannot end with _index, because these names are reserved for the index of a series column.'))
+ return self.simple_error('', _('Lookup names cannot end with _index, '
+ 'because these names are reserved for the index of a series column.'))
col_heading = unicode(self.column_heading_box.text())
col_type = self.column_types[self.column_type_box.currentIndex()]['datatype']
if col_type == '*text':
@@ -123,7 +126,8 @@ class CreateCustomColumn(QDialog, Ui_QCreateCustomColumn):
return self.simple_error('', _('No column heading was provided'))
bad_col = False
if col in self.parent.custcols:
- if not self.editing_col or self.parent.custcols[col]['colnum'] != self.orig_column_number:
+ if not self.editing_col or \
+ self.parent.custcols[col]['colnum'] != self.orig_column_number:
bad_col = True
if bad_col:
return self.simple_error('', _('The lookup name %s is already used')%col)
@@ -131,7 +135,8 @@ class CreateCustomColumn(QDialog, Ui_QCreateCustomColumn):
bad_head = False
for t in self.parent.custcols:
if self.parent.custcols[t]['name'] == col_heading:
- if not self.editing_col or self.parent.custcols[t]['colnum'] != self.orig_column_number:
+ if not self.editing_col or \
+ self.parent.custcols[t]['colnum'] != self.orig_column_number:
bad_head = True
for t in self.standard_colheads:
if self.standard_colheads[t] == col_heading:
@@ -148,7 +153,7 @@ class CreateCustomColumn(QDialog, Ui_QCreateCustomColumn):
if col_type == 'composite':
if not self.composite_box.text():
- return self.simple_error('', _('You must enter a template for composite fields')%col_heading)
+ return self.simple_error('', _('You must enter a template for composite fields'))
display_dict = {'composite_template':unicode(self.composite_box.text())}
is_editable = False
else:
From ce865b1eee32aacded18b57bff861c8ea4d4e267 Mon Sep 17 00:00:00 2001
From: Kovid Goyal
Date: Sun, 19 Sep 2010 13:54:03 -0600
Subject: [PATCH 053/412] Beta version numbering
---
src/calibre/constants.py | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/src/calibre/constants.py b/src/calibre/constants.py
index 4f1dfc25c2..334406e01b 100644
--- a/src/calibre/constants.py
+++ b/src/calibre/constants.py
@@ -2,7 +2,7 @@ __license__ = 'GPL v3'
__copyright__ = '2008, Kovid Goyal kovid@kovidgoyal.net'
__docformat__ = 'restructuredtext en'
__appname__ = 'calibre'
-__version__ = '0.7.19'
+__version__ = '0.7.900'
__author__ = "Kovid Goyal "
import re
From 9f42077e11027c13cbecc1a3c1260c38b22197ff Mon Sep 17 00:00:00 2001
From: Kovid Goyal
Date: Sun, 19 Sep 2010 14:46:48 -0600
Subject: [PATCH 054/412] Document the calibre template language
---
setup/installer/__init__.py | 2 +-
src/calibre/manual/faq.rst | 2 +-
src/calibre/manual/gui.rst | 7 ++++
src/calibre/manual/index.rst | 6 +++
src/calibre/manual/template_lang.rst | 61 ++++++++++++++++++++++++++++
5 files changed, 76 insertions(+), 2 deletions(-)
create mode 100644 src/calibre/manual/template_lang.rst
diff --git a/setup/installer/__init__.py b/setup/installer/__init__.py
index 959d8d14e1..39ca671f09 100644
--- a/setup/installer/__init__.py
+++ b/setup/installer/__init__.py
@@ -123,7 +123,7 @@ class VMInstaller(Command):
subprocess.check_call(['scp',
self.VM_NAME+':build/calibre/'+installer, 'dist'])
if not os.path.exists(installer):
- self.warn('Failed to download installer')
+ self.warn('Failed to download installer: '+installer)
raise SystemExit(1)
def clean(self):
diff --git a/src/calibre/manual/faq.rst b/src/calibre/manual/faq.rst
index 02881881c0..10c523f030 100644
--- a/src/calibre/manual/faq.rst
+++ b/src/calibre/manual/faq.rst
@@ -280,7 +280,7 @@ Why doesn't |app| have a column for foo?
|app| is designed to have columns for the most frequently and widely used fields. In addition, you can add any columns you like. Columns can be added via :guilabel:`Preferences->Interface->Add your own columns`.
Watch the tutorial `UI Power tips `_ to learn how to create your own columns.
-You can also create "virtual columns" that contain combinations of the metadata from other columns. In the add column dialog choose the option "Column from other columns" and in the template enter the other column names. For example to create a virtual column containing formats or ISBN, enter ``{formats}`` for formats or ``{isbn}`` for ISBN.
+You can also create "virtual columns" that contain combinations of the metadata from other columns. In the add column dialog choose the option "Column from other columns" and in the template enter the other column names. For example to create a virtual column containing formats or ISBN, enter ``{formats}`` for formats or ``{isbn}`` for ISBN. For more details, see :ref:`templatelangcalibre`.
Can I have a column showing the formats or the ISBN?
diff --git a/src/calibre/manual/gui.rst b/src/calibre/manual/gui.rst
index aa49c51b76..377c409bd0 100644
--- a/src/calibre/manual/gui.rst
+++ b/src/calibre/manual/gui.rst
@@ -84,6 +84,9 @@ Send to device
1. **Send to main memory**: The selected books are transferred to the main memory of the ebook reader.
2. **Send to card**: The selected books are transferred to the storage card on the ebook reader.
+You can control the file name and folder structure of files sent to the device by setting up a template in
+:guilabel:`Preferences->Import/Export->Sending books to devices`. Also see :ref:`templatelangcalibre`.
+
.. _save_to_disk:
Save to disk
@@ -108,6 +111,10 @@ All available formats as well as metadata is stored to disk for each selected bo
Saved books can be re-imported to the library without any loss of information by using the :ref:`Add books ` action.
+You can control the file name and folder structure of files saved to disk by setting up a template in
+:guilabel:`Preferences->Import/Export->Saving books to disk`. Also see :ref:`templatelangcalibre`.
+
+
.. _fetch_news:
Fetch news
diff --git a/src/calibre/manual/index.rst b/src/calibre/manual/index.rst
index 827d848eb1..40c260b8b5 100644
--- a/src/calibre/manual/index.rst
+++ b/src/calibre/manual/index.rst
@@ -39,4 +39,10 @@ Sections
develop
glossary
+.. toctree::
+ :hidden:
+ :maxdepth: 2
+
+ template_lang
+ portable
diff --git a/src/calibre/manual/template_lang.rst b/src/calibre/manual/template_lang.rst
new file mode 100644
index 0000000000..541b5da138
--- /dev/null
+++ b/src/calibre/manual/template_lang.rst
@@ -0,0 +1,61 @@
+
+.. include:: global.rst
+
+.. _templatelangcalibre:
+
+The |app| template language
+=======================================================
+
+The |app| template language is used in various places. It is used to control the folder structure and file name when saving files from the |app| library to the disk or eBook reader.
+It is used to define "virtual" columns that contain data from other columns and so on.
+
+In essence, the template language is very simple. The basic idea is that a template consists of names in curly brackets that are then replaced by the corresponding metadata from the book being processed. So, for example, the default template used for saving books to device in |app| is::
+
+ {author_sort}/{title}/{title} - {authors}
+
+For the book "The Foundation" by "Isaac Asimov" it will become::
+
+ Asimov, Isaac/The Foundation/The Foundation - Isaac Asimov
+
+You can use all the various metadata fields available in calibre in a template, including the custom columns you have created yourself. To find out the template name for a column sinply hover your mouse over the column header. Names for custom fields (columns you have created yourself) are always prefixed by an #. For series type fields, there is always an additional field named ``series_index`` that becomes the series index for that series. So if you have a custom series field named #myseries, there will also be a field named #myseries_index. In addition to the column based fields, you also can use::
+
+ {formats} - A list of formats available in the |app| library for a book
+ {isbn} - The ISBN number of the book
+
+If a particular book does not have a particular piece of metadata, the field in the template is automatically removed for that book. So for example::
+
+ {author_sort}/{series}/{title} {series_index}
+
+will become::
+
+ {Asimov, Isaac}/Foundation/Second Foundation - 3
+
+and if a book does not have a series::
+
+ {Asimov, Isaac}/Second Foundation
+
+(|app| automatically removes multiple slashes and leading or trailing spaces).
+
+
+Advanced formatting
+----------------------
+
+You can do more than just simple substitution with the templates. You can also control how the substituted data is formatted. For instance, suppose you wanted to ensure that the series_index is always formatted as three digits with leading zeros. This would do the trick::
+
+ {series_index:0>3s} - Three digits with leading zeros
+
+If instead of leading zeros you want leading spaces, use::
+
+ {series_index:>3s} - Thre digits with leading spaces
+
+For trailing zeros, use::
+
+ {series_index:0<3s} - Three digits with trailing zeros
+
+
+If you want only the first two letters of the data to be rendered, use::
+
+ {author_sort:.2} - Only the first two letter of the author sort name
+
+The |app| template language comes from python and for more details on the syntax of these advanced formatting operations, look at the `Python documentation `_.
+
From 792fccbcad06c995b93ce9b97539d359e17740b5 Mon Sep 17 00:00:00 2001
From: Charles Haley <>
Date: Mon, 20 Sep 2010 08:32:16 +0100
Subject: [PATCH 055/412] Fix validation exception and exception when empty
fields are subscripted in templates
---
src/calibre/gui2/preferences/save_template.py | 18 +++++++++++++++---
src/calibre/library/save_to_disk.py | 5 ++++-
2 files changed, 19 insertions(+), 4 deletions(-)
diff --git a/src/calibre/gui2/preferences/save_template.py b/src/calibre/gui2/preferences/save_template.py
index 0dbee5bf21..0f48893b69 100644
--- a/src/calibre/gui2/preferences/save_template.py
+++ b/src/calibre/gui2/preferences/save_template.py
@@ -6,12 +6,24 @@ __license__ = 'GPL v3'
__copyright__ = '2009, Kovid Goyal '
__docformat__ = 'restructuredtext en'
+import string
+
from PyQt4.Qt import QWidget, pyqtSignal
from calibre.gui2 import error_dialog
from calibre.gui2.preferences.save_template_ui import Ui_Form
-from calibre.library.save_to_disk import FORMAT_ARG_DESCS, preprocess_template,\
- safe_format
+from calibre.library.save_to_disk import FORMAT_ARG_DESCS, preprocess_template
+
+class ValidateFormat(string.Formatter):
+ '''
+ Provides a format function that substitutes '' for any missing value
+ '''
+ def get_value(self, key, args, kwargs):
+ return 'this is some text that should be long enough'
+
+validate_formatter = ValidateFormat()
+def validate_format(x, format_args):
+ return validate_formatter.vformat(x, [], format_args).strip()
class SaveTemplate(QWidget, Ui_Form):
@@ -52,7 +64,7 @@ class SaveTemplate(QWidget, Ui_Form):
tmpl = preprocess_template(self.opt_template.text())
fa = {}
try:
- safe_format(tmpl, fa)
+ validate_format(tmpl, fa)
except Exception, err:
error_dialog(self, _('Invalid template'),
'
'+_('The template %s is invalid:')%tmpl + \
diff --git a/src/calibre/library/save_to_disk.py b/src/calibre/library/save_to_disk.py
index 71850abcd5..d5300d93e9 100644
--- a/src/calibre/library/save_to_disk.py
+++ b/src/calibre/library/save_to_disk.py
@@ -113,7 +113,10 @@ class SafeFormat(string.Formatter):
safe_formatter = SafeFormat()
def safe_format(x, format_args):
- ans = safe_formatter.vformat(x, [], format_args).strip()
+ try:
+ ans = safe_formatter.vformat(x, [], format_args).strip()
+ except:
+ ans = ''
return re.sub(r'\s+', ' ', ans)
def get_components(template, mi, id, timefmt='%b %Y', length=250,
From 89f64db891cfbc3c1ab276c87dc2eb8e826edb56 Mon Sep 17 00:00:00 2001
From: Charles Haley <>
Date: Mon, 20 Sep 2010 09:51:04 +0100
Subject: [PATCH 056/412] Field interface, including refactoring (renaming)
some existing methods.
---
src/calibre/ebooks/metadata/book/base.py | 88 +++++++++++++++--------
src/calibre/gui2/dialogs/metadata_bulk.py | 4 +-
src/calibre/gui2/library/models.py | 2 +-
src/calibre/gui2/ui.py | 3 +
src/calibre/library/caches.py | 2 +-
src/calibre/library/database2.py | 36 ++++++++++
src/calibre/library/field_metadata.py | 46 +++++-------
src/calibre/library/save_to_disk.py | 2 +-
src/calibre/library/server/content.py | 2 +-
9 files changed, 123 insertions(+), 62 deletions(-)
diff --git a/src/calibre/ebooks/metadata/book/base.py b/src/calibre/ebooks/metadata/book/base.py
index 7b8eb07908..3d6d6b1bb8 100644
--- a/src/calibre/ebooks/metadata/book/base.py
+++ b/src/calibre/ebooks/metadata/book/base.py
@@ -138,20 +138,66 @@ class Metadata(object):
def set(self, field, val, extra=None):
self.__setattr__(field, val, extra)
- @property
- def all_keys(self):
+ # field-oriented interface. Intended to be the same as in LibraryDatabase
+
+ def standard_field_keys(self):
'''
- All attribute keys known by this instance, even if their value is None
+ return a list of all possible keys, even if this book doesn't have them
+ '''
+ return STANDARD_METADATA_FIELDS
+
+ def custom_field_keys(self):
+ '''
+ return a list of the custom fields in this book
+ '''
+ return object.__getattribute__(self, '_data')['user_metadata'].iterkeys()
+
+ def all_field_keys(self):
+ '''
+ All field keys known by this instance, even if their value is None
'''
_data = object.__getattribute__(self, '_data')
return frozenset(ALL_METADATA_FIELDS.union(_data['user_metadata'].iterkeys()))
- @property
+ def metadata_for_field(self, key):
+ '''
+ return metadata describing a standard or custom field.
+ '''
+ if key in self.user_metadata_keys():
+ return self.get_standard_metadata(self, key, make_copy=False)
+ return self.get_user_metadata(key, make_copy=False)
+
def user_metadata_keys(self):
- 'The set of user metadata names this object knows about'
+ '''
+ Return the standard keys actually in this book.
+ '''
_data = object.__getattribute__(self, '_data')
return frozenset(_data['user_metadata'].iterkeys())
+ def all_non_none_fields(self):
+ '''
+ Return a dictionary containing all non-None metadata fields, including
+ the custom ones.
+ '''
+ result = {}
+ _data = object.__getattribute__(self, '_data')
+ for attr in STANDARD_METADATA_FIELDS:
+ v = _data.get(attr, None)
+ if v is not None:
+ result[attr] = v
+ for attr in _data['user_metadata'].iterkeys():
+ v = _data['user_metadata'][attr]['#value#']
+ if v is not None:
+ result[attr] = v
+ if _data['user_metadata'][attr]['datatype'] == 'series':
+ result[attr+'_index'] = _data['user_metadata'][attr]['#extra#']
+ return result
+
+ # End of field-oriented interface
+
+ # Extended interfaces. These permit one to get copies of metadata dictionaries, and to
+ # get and set custom field metadata
+
def get_standard_metadata(self, field, make_copy):
'''
return field metadata from the field if it is there. Otherwise return
@@ -237,30 +283,11 @@ class Metadata(object):
_data = object.__getattribute__(self, '_data')
_data['user_metadata'][field] = metadata
- def get_all_non_none_attributes(self):
- '''
- Return a dictionary containing all non-None metadata fields, including
- the custom ones.
- '''
- result = {}
- _data = object.__getattribute__(self, '_data')
- for attr in STANDARD_METADATA_FIELDS:
- v = _data.get(attr, None)
- if v is not None:
- result[attr] = v
- for attr in _data['user_metadata'].iterkeys():
- v = _data['user_metadata'][attr]['#value#']
- if v is not None:
- result[attr] = v
- if _data['user_metadata'][attr]['datatype'] == 'series':
- result[attr+'_index'] = _data['user_metadata'][attr]['#extra#']
- return result
-
# Old Metadata API {{{
def print_all_attributes(self):
for x in STANDARD_METADATA_FIELDS:
prints('%s:'%x, getattr(self, x, 'None'))
- for x in self.user_metadata_keys:
+ for x in self.user_metadata_keys():
meta = self.get_user_metadata(x, make_copy=False)
if meta is not None:
prints(x, meta)
@@ -326,7 +353,7 @@ class Metadata(object):
self.cover_data = other.cover_data
if getattr(other, 'user_metadata_keys', None):
- for x in other.user_metadata_keys:
+ for x in other.user_metadata_keys():
meta = other.get_user_metadata(x, make_copy=True)
if meta is not None:
self_tags = self.get(x, [])
@@ -389,7 +416,7 @@ class Metadata(object):
'''
returns the tuple (field_name, formatted_value)
'''
- if key in self.user_metadata_keys:
+ if key in self.user_metadata_keys():
res = self.get(key, None)
cmeta = self.get_user_metadata(key, make_copy=False)
if cmeta['datatype'] != 'composite' and (res is None or res == ''):
@@ -432,6 +459,9 @@ class Metadata(object):
return (None, None, None, None)
+ def expand_template(self, template):
+ return format_composite(template, self)
+
def __unicode__(self):
from calibre.ebooks.metadata import authors_to_string
ans = []
@@ -466,7 +496,7 @@ class Metadata(object):
fmt('Published', isoformat(self.pubdate))
if self.rights is not None:
fmt('Rights', unicode(self.rights))
- for key in self.user_metadata_keys:
+ for key in self.user_metadata_keys():
val = self.get(key, None)
if val is not None:
(name, val) = self.format_field(key)
@@ -491,7 +521,7 @@ class Metadata(object):
ans += [(_('Published'), unicode(self.pubdate.isoformat(' ')))]
if self.rights is not None:
ans += [(_('Rights'), unicode(self.rights))]
- for key in self.user_metadata_keys:
+ for key in self.user_metadata_keys():
val = self.get(key, None)
if val is not None:
(name, val) = self.format_field(key)
diff --git a/src/calibre/gui2/dialogs/metadata_bulk.py b/src/calibre/gui2/dialogs/metadata_bulk.py
index 1fb889757f..83cf6278e5 100644
--- a/src/calibre/gui2/dialogs/metadata_bulk.py
+++ b/src/calibre/gui2/dialogs/metadata_bulk.py
@@ -270,11 +270,11 @@ class MetadataBulkDialog(QDialog, Ui_MetadataBulkDialog):
def s_r_func(self, match):
rfunc = self.s_r_functions[unicode(self.replace_func.currentText())]
rtext = unicode(self.replace_with.text())
- mi_data = self.mi.get_all_non_none_attributes()
+ mi_data = self.mi.all_non_none_fields()
def fm_func(m):
try:
- if m.group(3) not in self.mi.all_keys: return m.group(0)
+ if m.group(3) not in self.mi.all_field_keys(): return m.group(0)
else: return '%s{%s}'%(m.group(1), m.group(3))
except:
import traceback
diff --git a/src/calibre/gui2/library/models.py b/src/calibre/gui2/library/models.py
index be1bf9bc2d..6941869e44 100644
--- a/src/calibre/gui2/library/models.py
+++ b/src/calibre/gui2/library/models.py
@@ -319,7 +319,7 @@ class BooksModel(QAbstractTableModel): # {{{
_('Book %s of %s.')%\
(sidx, prepare_string_for_xml(series))
mi = self.db.get_metadata(idx)
- for key in mi.user_metadata_keys:
+ for key in mi.user_metadata_keys():
name, val = mi.format_field(key)
if val is not None:
data[name] = val
diff --git a/src/calibre/gui2/ui.py b/src/calibre/gui2/ui.py
index f8d50d1cd2..647e31ff51 100644
--- a/src/calibre/gui2/ui.py
+++ b/src/calibre/gui2/ui.py
@@ -533,6 +533,9 @@ class Main(MainWindow, MainWindowMixin, DeviceMixin, # {{{
# Save the current field_metadata for applications like calibre2opds
# Goes here, because if cf is valid, db is valid.
db.prefs['field_metadata'] = db.field_metadata.all_metadata()
+ if db.gm_count > 0:
+ print 'get_metadata cache: {0:d} calls, {1:4.2f}% misses'.format(
+ db.gm_count, (db.gm_missed*100.0)/db.gm_count)
for action in self.iactions.values():
if not action.shutting_down():
return
diff --git a/src/calibre/library/caches.py b/src/calibre/library/caches.py
index 770a362a1d..5f7fbdccc9 100644
--- a/src/calibre/library/caches.py
+++ b/src/calibre/library/caches.py
@@ -621,7 +621,7 @@ class ResultCache(SearchQueryParser):
def multisort(self, fields=[], subsort=False):
fields = [(self.sanitize_sort_field_name(x), bool(y)) for x, y in fields]
- keys = self.field_metadata.sortable_keys()
+ keys = self.field_metadata.sortable_field_keys()
fields = [x for x in fields if x[0] in keys]
if subsort and 'sort' not in [x[0] for x in fields]:
fields += [('sort', True)]
diff --git a/src/calibre/library/database2.py b/src/calibre/library/database2.py
index 106b498ee8..f5a474edbc 100644
--- a/src/calibre/library/database2.py
+++ b/src/calibre/library/database2.py
@@ -325,6 +325,10 @@ class LibraryDatabase2(LibraryDatabase, SchemaUpgrade, CustomColumns):
self.has_id = self.data.has_id
self.count = self.data.count
+ # Count times get_metadata is called, and how many times in the cache
+ self.gm_count = 0
+ self.gm_missed = 0
+
for prop in ('author_sort', 'authors', 'comment', 'comments', 'isbn',
'publisher', 'rating', 'series', 'series_index', 'tags',
'title', 'timestamp', 'uuid', 'pubdate', 'ondevice'):
@@ -520,15 +524,47 @@ class LibraryDatabase2(LibraryDatabase, SchemaUpgrade, CustomColumns):
f.close()
return ans
+ ### The field-style interface. These use field keys.
+
+ def get_field(self, idx, key, default=None, index_is_id=False):
+ mi = self.get_metadata(idx, index_is_id=index_is_id, get_cover=True)
+ try:
+ return mi[key]
+ except:
+ return default
+
+ def standard_field_keys(self):
+ return self.field_metadata.standard_field_keys()
+
+ def custom_field_keys(self):
+ return self.field_metadata.custom_field_keys()
+
+ def all_field_keys(self):
+ return self.field_metadata.all_field_keys()
+
+ def sortable_field_keys(self):
+ return self.field_metadata.sortable_field_keys()
+
+ def searchable_fields(self):
+ return self.field_metadata.searchable_field_keys()
+
+ def search_term_to_field_key(self, term):
+ return self.field_metadata.search_term_to_key(term)
+
+ def metadata_for_field(self, key):
+ return self.field_metadata[key]
+
def get_metadata(self, idx, index_is_id=False, get_cover=False):
'''
Convenience method to return metadata as a :class:`Metadata` object.
'''
+ self.gm_count += 1
mi = self.data.get(idx, self.FIELD_MAP['all_metadata'],
row_is_id = index_is_id)
if mi is not None:
return mi
+ self.gm_missed += 1
mi = Metadata(None)
self.data.set(idx, self.FIELD_MAP['all_metadata'], mi,
row_is_id = index_is_id)
diff --git a/src/calibre/library/field_metadata.py b/src/calibre/library/field_metadata.py
index e4a4f5270d..a8031e5172 100644
--- a/src/calibre/library/field_metadata.py
+++ b/src/calibre/library/field_metadata.py
@@ -348,11 +348,24 @@ class FieldMetadata(dict):
def keys(self):
return self._tb_cats.keys()
- def sortable_keys(self):
+ def sortable_field_keys(self):
return [k for k in self._tb_cats.keys()
if self._tb_cats[k]['kind']=='field' and
self._tb_cats[k]['datatype'] is not None]
+ def standard_field_keys(self):
+ return [k for k in self._tb_cats.keys()
+ if self._tb_cats[k]['kind']=='field' and
+ not self._tb_cats[k]['is_custom']]
+
+ def custom_field_keys(self):
+ return [k for k in self._tb_cats.keys()
+ if self._tb_cats[k]['kind']=='field' and
+ self._tb_cats[k]['is_custom']]
+
+ def all_field_keys(self):
+ return [k for k in self._tb_cats.keys() if self._tb_cats[k]['kind']=='field']
+
def iterkeys(self):
for key in self._tb_cats:
yield key
@@ -474,36 +487,10 @@ class FieldMetadata(dict):
key = self.custom_field_prefix+label
self._tb_cats[key]['rec_index'] = index # let the exception fly ...
-
-# DEFAULT_LOCATIONS = frozenset([
-# 'all',
-# 'author', # compatibility
-# 'authors',
-# 'comment', # compatibility
-# 'comments',
-# 'cover',
-# 'date',
-# 'format', # compatibility
-# 'formats',
-# 'isbn',
-# 'ondevice',
-# 'pubdate',
-# 'publisher',
-# 'search',
-# 'series',
-# 'rating',
-# 'tag', # compatibility
-# 'tags',
-# 'title',
-# ])
-
def get_search_terms(self):
s_keys = sorted(self._search_term_map.keys())
for v in self.search_items:
s_keys.append(v)
-# if set(s_keys) != self.DEFAULT_LOCATIONS:
-# print 'search labels and default_locations do not match:'
-# print set(s_keys) ^ self.DEFAULT_LOCATIONS
return s_keys
def _add_search_terms_to_map(self, key, terms):
@@ -518,3 +505,8 @@ class FieldMetadata(dict):
if term in self._search_term_map:
return self._search_term_map[term]
return term
+
+ def searchable_field_keys(self):
+ return [k for k in self._tb_cats.keys()
+ if self._tb_cats[k]['kind']=='field' and
+ len(self._tb_cats[k]['search_terms']) > 0]
diff --git a/src/calibre/library/save_to_disk.py b/src/calibre/library/save_to_disk.py
index d5300d93e9..fe62dcb7fd 100644
--- a/src/calibre/library/save_to_disk.py
+++ b/src/calibre/library/save_to_disk.py
@@ -125,7 +125,7 @@ def get_components(template, mi, id, timefmt='%b %Y', length=250,
library_order = tweaks['save_template_title_series_sorting'] == 'library_order'
tsfmt = title_sort if library_order else lambda x: x
format_args = FORMAT_ARGS.copy()
- format_args.update(mi.get_all_non_none_attributes())
+ format_args.update(mi.all_non_none_fields())
if mi.title:
format_args['title'] = tsfmt(mi.title)
if mi.authors:
diff --git a/src/calibre/library/server/content.py b/src/calibre/library/server/content.py
index c3a662c0fd..041ea78051 100644
--- a/src/calibre/library/server/content.py
+++ b/src/calibre/library/server/content.py
@@ -56,7 +56,7 @@ class ContentServer(object):
def sort(self, items, field, order):
field = self.db.data.sanitize_sort_field_name(field)
- if field not in self.db.field_metadata.sortable_keys():
+ if field not in self.db.field_metadata.sortable_field_keys():
raise cherrypy.HTTPError(400, '%s is not a valid sort field'%field)
keyg = CSSortKeyGenerator([(field, order)], self.db.field_metadata)
items.sort(key=keyg, reverse=not order)
From e721bd44eeb674b89346baf0ab13c053bd26e149 Mon Sep 17 00:00:00 2001
From: Charles Haley <>
Date: Mon, 20 Sep 2010 14:52:53 +0100
Subject: [PATCH 057/412] Interim release
---
src/calibre/gui2/dialogs/metadata_bulk.py | 248 ++++++++++++++--------
src/calibre/gui2/dialogs/metadata_bulk.ui | 152 +++++++++++--
src/calibre/library/database2.py | 5 +-
3 files changed, 294 insertions(+), 111 deletions(-)
diff --git a/src/calibre/gui2/dialogs/metadata_bulk.py b/src/calibre/gui2/dialogs/metadata_bulk.py
index 83cf6278e5..3659547b13 100644
--- a/src/calibre/gui2/dialogs/metadata_bulk.py
+++ b/src/calibre/gui2/dialogs/metadata_bulk.py
@@ -122,12 +122,20 @@ def format_composite(x, mi):
class MetadataBulkDialog(QDialog, Ui_MetadataBulkDialog):
- s_r_functions = {
- '' : lambda x: x,
- _('Lower Case') : lambda x: x.lower(),
- _('Upper Case') : lambda x: x.upper(),
- _('Title Case') : lambda x: x.title(),
- }
+ s_r_functions = { '' : lambda x: x,
+ _('Lower Case') : lambda x: x.lower(),
+ _('Upper Case') : lambda x: x.upper(),
+ _('Title Case') : lambda x: x.title(),
+ }
+
+ s_r_match_modes = [ _('Character match'),
+ _('Regular Expression'),
+ ]
+
+ s_r_replace_modes = [ _('Replace field'),
+ _('Prepend to field'),
+ _('Append to field'),
+ ]
def __init__(self, window, rows, db):
QDialog.__init__(self, window)
@@ -179,27 +187,34 @@ class MetadataBulkDialog(QDialog, Ui_MetadataBulkDialog):
fields.sort()
self.search_field.addItems(fields)
self.search_field.setMaxVisibleItems(min(len(fields), 20))
+ self.destination_field.addItems(fields)
+ self.destination_field.setMaxVisibleItems(min(len(fields), 20))
offset = 10
self.s_r_number_of_books = min(7, len(self.ids))
for i in range(1,self.s_r_number_of_books+1):
w = QtGui.QLabel(self.tabWidgetPage3)
w.setText(_('Book %d:')%i)
- self.gridLayout1.addWidget(w, i+offset, 0, 1, 1)
+ self.testgrid.addWidget(w, i+offset, 0, 1, 1)
w = QtGui.QLineEdit(self.tabWidgetPage3)
w.setReadOnly(True)
name = 'book_%d_text'%i
setattr(self, name, w)
self.book_1_text.setObjectName(name)
- self.gridLayout1.addWidget(w, i+offset, 1, 1, 1)
+ self.testgrid.addWidget(w, i+offset, 1, 1, 1)
w = QtGui.QLineEdit(self.tabWidgetPage3)
w.setReadOnly(True)
name = 'book_%d_result'%i
setattr(self, name, w)
self.book_1_text.setObjectName(name)
- self.gridLayout1.addWidget(w, i+offset, 2, 1, 1)
+ self.testgrid.addWidget(w, i+offset, 2, 1, 1)
self.s_r_heading.setText('
'+
- _('Search and replace in text fields using '
+ _('You can destroy your library '
+ 'using this feature. Changes are permanent. There '
+ 'is no undo function. You are strongly encouraged '
+ 'to back up your library before proceeding.'
+ ) + '
' + _(
+ 'Search and replace in text fields using '
'regular expressions. The search text is an '
'arbitrary python-compatible regular expression. '
'The replacement text can contain backreferences '
@@ -209,51 +224,86 @@ class MetadataBulkDialog(QDialog, Ui_MetadataBulkDialog):
' '
'this reference '
'for more information, and in particular the \'sub\' '
- 'function.') + '
' + _(
- 'Note: you can destroy your library '
- 'using this feature. Changes are permanent. There '
- 'is no undo function. You are strongly encouraged '
- 'to back up your library before proceeding.'))
+ 'function.'
+ ))
+ self.search_mode.addItems(self.s_r_match_modes)
+ self.search_mode.setCurrentIndex(0)
+ self.replace_mode.addItems(self.s_r_replace_modes)
+ self.replace_mode.setCurrentIndex(0)
+
+ self.s_r_search_mode = 0
self.s_r_error = None
self.s_r_obj = None
self.replace_func.addItems(sorted(self.s_r_functions.keys()))
- self.search_field.currentIndexChanged[str].connect(self.s_r_field_changed)
+ self.search_mode.currentIndexChanged[int].connect(self.s_r_search_mode_changed)
+ self.search_field.currentIndexChanged[str].connect(self.s_r_search_field_changed)
+ self.destination_field.currentIndexChanged[str].connect(self.s_r_destination_field_changed)
+
+ self.replace_mode.currentIndexChanged[int].connect(self.s_r_paint_results)
self.replace_func.currentIndexChanged[str].connect(self.s_r_paint_results)
self.search_for.editTextChanged[str].connect(self.s_r_paint_results)
self.replace_with.editTextChanged[str].connect(self.s_r_paint_results)
self.test_text.editTextChanged[str].connect(self.s_r_paint_results)
+ self.comma_separated.stateChanged.connect(self.s_r_paint_results)
+ self.case_sensitive.stateChanged.connect(self.s_r_paint_results)
self.central_widget.setCurrentIndex(0)
self.search_for.completer().setCaseSensitivity(Qt.CaseSensitive)
self.replace_with.completer().setCaseSensitivity(Qt.CaseSensitive)
+ self.s_r_search_mode_changed(0)
- def s_r_field_changed(self, txt):
+ def s_r_get_field(self, mi, field):
+ if field:
+ fm = self.db.metadata_for_field(field)
+ val = mi.get(field, None)
+ if val is None:
+ val = []
+ elif not fm['is_multiple']:
+ val = [val]
+ elif field == 'authors':
+ val = [v.replace(',', '|') for v in val]
+ else:
+ val = []
+ return val
+
+ def s_r_search_field_changed(self, txt):
txt = unicode(txt)
for i in range(0, self.s_r_number_of_books):
- if txt:
- fm = self.db.field_metadata[txt]
- id = self.ids[i]
- val = self.db.get_property(id, index_is_id=True,
- loc=fm['rec_index'])
- if val is None:
- val = ''
- if fm['is_multiple']:
- val = [t.strip() for t in val.split(fm['is_multiple']) if t.strip()]
- if val:
- val.sort(cmp=lambda x,y: cmp(x.lower(), y.lower()))
- val = val[0]
- if txt == 'authors':
- val = val.replace('|', ',')
- else:
- val = ''
- else:
- val = ''
w = getattr(self, 'book_%d_text'%(i+1))
- w.setText(val)
+ mi = self.db.get_metadata(self.ids[i], index_is_id=True)
+ src = unicode(self.search_field.currentText())
+ t = self.s_r_get_field(mi, src)
+ w.setText(''.join(t[0:1]))
self.s_r_paint_results(None)
+ def s_r_destination_field_changed(self, txt):
+ txt = unicode(txt)
+ self.comma_separated.setEnabled(True)
+ if txt:
+ fm = self.db.metadata_for_field(txt)
+ if fm['is_multiple']:
+ self.comma_separated.setEnabled(False)
+ self.comma_separated.setChecked(True)
+ self.s_r_paint_results(None)
+
+ def s_r_search_mode_changed(self, val):
+ if val == 0:
+ self.destination_field.setCurrentIndex(0)
+ self.destination_field.setVisible(False)
+ self.destination_field_label.setVisible(False)
+ self.replace_mode.setCurrentIndex(0)
+ self.replace_mode.setVisible(False)
+ self.replace_mode_label.setVisible(False)
+ self.comma_separated.setVisible(False)
+ else:
+ self.destination_field.setVisible(True)
+ self.destination_field_label.setVisible(True)
+ self.replace_mode.setVisible(True)
+ self.replace_mode_label.setVisible(True)
+ self.comma_separated.setVisible(True)
+
def s_r_set_colors(self):
if self.s_r_error is not None:
col = 'rgb(255, 0, 0, 20%)'
@@ -265,32 +315,66 @@ class MetadataBulkDialog(QDialog, Ui_MetadataBulkDialog):
for i in range(0,self.s_r_number_of_books):
getattr(self, 'book_%d_result'%(i+1)).setText('')
- field_match_re = re.compile(r'(^|[^\\])(\\g<)([^>]+)(>)')
-
def s_r_func(self, match):
rfunc = self.s_r_functions[unicode(self.replace_func.currentText())]
rtext = unicode(self.replace_with.text())
- mi_data = self.mi.all_non_none_fields()
-
- def fm_func(m):
- try:
- if m.group(3) not in self.mi.all_field_keys(): return m.group(0)
- else: return '%s{%s}'%(m.group(1), m.group(3))
- except:
- import traceback
- traceback.print_exc()
- return m.group(0)
-
- rtext = re.sub(self.field_match_re, fm_func, rtext)
rtext = match.expand(rtext)
- rtext = format_composite(rtext, mi_data)
return rfunc(rtext)
+ def s_r_do_regexp(self, mi):
+ src_field = unicode(self.search_field.currentText())
+ src = self.s_r_get_field(mi, src_field)
+ result = []
+ for s in src:
+ result.append(self.s_r_obj.sub(self.s_r_func, s))
+ return result
+
+ def s_r_do_destination(self, mi, val):
+ src = unicode(self.search_field.currentText())
+ if src == '':
+ return ''
+ dest = unicode(self.destination_field.currentText())
+ if dest == '':
+ dest = src
+ dest_mode = self.replace_mode.currentIndex()
+
+ if dest_mode != 0:
+ dest_val = mi.get(dest, '')
+ if dest_val is None:
+ dest_val = []
+ elif isinstance(dest_val, list):
+ if dest == 'authors':
+ dest_val = [v.replace(',', '|') for v in dest_val]
+ else:
+ dest_val = [dest_val]
+ else:
+ dest_val = []
+
+ if len(val) > 0:
+ if src == 'authors':
+ val = [v.replace(',', '|') for v in val]
+ if dest_mode == 1:
+ val.extend(dest_val)
+ elif dest_mode == 2:
+ val[0:0] = dest_val
+ return val
+
+ def s_r_replace_mode_separator(self):
+ if self.comma_separated.isChecked():
+ return ','
+ return ''
+
def s_r_paint_results(self, txt):
self.s_r_error = None
self.s_r_set_colors()
+
+ if self.case_sensitive.isChecked():
+ flags = 0
+ else:
+ flags = re.I
+
try:
- self.s_r_obj = re.compile(unicode(self.search_for.text()))
+ self.s_r_obj = re.compile(unicode(self.search_for.text()), flags)
except Exception as e:
self.s_r_obj = None
self.s_r_error = e
@@ -298,7 +382,6 @@ class MetadataBulkDialog(QDialog, Ui_MetadataBulkDialog):
return
try:
- self.mi = MetaInformation(None, None)
self.test_result.setText(self.s_r_obj.sub(self.s_r_func,
unicode(self.test_text.text())))
except Exception as e:
@@ -307,60 +390,53 @@ class MetadataBulkDialog(QDialog, Ui_MetadataBulkDialog):
return
for i in range(0,self.s_r_number_of_books):
- id = self.ids[i]
- self.mi = self.db.get_metadata(id, index_is_id=True)
- wt = getattr(self, 'book_%d_text'%(i+1))
+ mi = self.db.get_metadata(self.ids[i], index_is_id=True)
wr = getattr(self, 'book_%d_result'%(i+1))
try:
- wr.setText(self.s_r_obj.sub(self.s_r_func, unicode(wt.text())))
+ result = self.s_r_do_regexp(mi)
+ t = self.s_r_do_destination(mi, result[0:1])
+ t = self.s_r_replace_mode_separator().join(t)
+ wr.setText(t)
except Exception as e:
+ import traceback
+ traceback.print_exc()
self.s_r_error = e
self.s_r_set_colors()
break
def do_search_replace(self):
- field = unicode(self.search_field.currentText())
- if not field or not self.s_r_obj:
+ source = unicode(self.search_field.currentText())
+ if not source or not self.s_r_obj:
return
-
- fm = self.db.field_metadata[field]
-
- def apply_pattern(val):
- try:
- return self.s_r_obj.sub(self.s_r_func, val)
- except:
- return val
+ dest = unicode(self.destination_field.currentText())
+ if not dest:
+ dest = source
+ dfm = self.db.field_metadata[source]
for id in self.ids:
- val = self.db.get_property(id, index_is_id=True,
- loc=fm['rec_index'])
+ mi = self.db.get_metadata(id, index_is_id=True,)
+ val = mi.get(source)
if val is None:
continue
- if fm['is_multiple']:
- res = []
- for val in [t.strip() for t in val.split(fm['is_multiple'])]:
- v = apply_pattern(val).strip()
- if v:
- res.append(v)
- val = res
- if fm['is_custom']:
+ val = self.s_r_do_regexp(mi)
+ val = self.s_r_do_destination(mi, val)
+ if dfm['is_multiple']:
+ if dfm['is_custom']:
# The standard tags and authors values want to be lists.
# All custom columns are to be strings
- val = fm['is_multiple'].join(val)
- elif field == 'authors':
- val = [v.replace('|', ',') for v in val]
+ val = dfm['is_multiple'].join(val)
else:
- val = apply_pattern(val)
+ val = self.s_r_replace_mode_separator().join(val)
- if fm['is_custom']:
- extra = self.db.get_custom_extra(id, label=fm['label'], index_is_id=True)
- self.db.set_custom(id, val, label=fm['label'], extra=extra,
+ if dfm['is_custom']:
+ extra = self.db.get_custom_extra(id, label=dfm['label'], index_is_id=True)
+ self.db.set_custom(id, val, label=dfm['label'], extra=extra,
commit=False)
else:
- if field == 'comments':
+ if dest == 'comments':
setter = self.db.set_comment
else:
- setter = getattr(self.db, 'set_'+field)
+ setter = getattr(self.db, 'set_'+dest)
setter(id, val, notify=False, commit=False)
self.db.commit()
diff --git a/src/calibre/gui2/dialogs/metadata_bulk.ui b/src/calibre/gui2/dialogs/metadata_bulk.ui
index aca7b0cb75..e433aaf327 100644
--- a/src/calibre/gui2/dialogs/metadata_bulk.ui
+++ b/src/calibre/gui2/dialogs/metadata_bulk.ui
@@ -319,7 +319,7 @@ Future conversion of these books will use the default settings.
&Search and replace (experimental)
-
+ QLayout::SetMinimumSize
@@ -351,6 +351,39 @@ Future conversion of these books will use the default settings.
+
+
+
+
+
+
+
+ Search mode:
+
+
+ search_field
+
+
+
+
+
+
+
+
+
+ Qt::Horizontal
+
+
+
+ 20
+ 10
+
+
+
+
+
+
+ &Search for:
@@ -360,7 +393,20 @@ Future conversion of these books will use the default settings.
-
+
+
+
+
+
+
+ Case sensitive
+
+
+ true
+
+
+
+ &Replace with:
@@ -370,29 +416,93 @@ Future conversion of these books will use the default settings.
-
-
-
-
-
-
-
+
-
-
+
+
+
+
+
+ Apply function after replace:
+
+
+ replace_func
+
+
+
+
+
+
+
+
+
+ Qt::Horizontal
+
+
+
+ 20
+ 10
+
+
+
+
+
+
+
+
- Apply function &after replace:
+ &Destination field:
- replace_func
+ destination_field
-
-
-
+
+
+
+
+
+
+
+ Mode:
+
+
+ replace_mode
+
+
+
+
+
+
+
+
+
+ use comma
+
+
+ true
+
+
+
+
+
+
+ Qt::Horizontal
+
+
+
+ 20
+ 10
+
+
+
+
+
+
+ Test &text
@@ -402,8 +512,8 @@ Future conversion of these books will use the default settings.
-
-
+
+ Test re&sult
@@ -412,17 +522,17 @@ Future conversion of these books will use the default settings.
-
+ Your test:
-
+
-
+
diff --git a/src/calibre/library/database2.py b/src/calibre/library/database2.py
index f5a474edbc..2f9f9b6f89 100644
--- a/src/calibre/library/database2.py
+++ b/src/calibre/library/database2.py
@@ -528,10 +528,7 @@ class LibraryDatabase2(LibraryDatabase, SchemaUpgrade, CustomColumns):
def get_field(self, idx, key, default=None, index_is_id=False):
mi = self.get_metadata(idx, index_is_id=index_is_id, get_cover=True)
- try:
- return mi[key]
- except:
- return default
+ return mi.get(key, default)
def standard_field_keys(self):
return self.field_metadata.standard_field_keys()
From ea44e9053faf49c85df6d7e6abf8392ef37ffd12 Mon Sep 17 00:00:00 2001
From: Charles Haley <>
Date: Mon, 20 Sep 2010 17:03:53 +0100
Subject: [PATCH 058/412] Finish search and replace.
Fix a bug in database2 that seems to be triggered by interactions with the cover cache.
---
src/calibre/gui2/dialogs/metadata_bulk.py | 60 +++++++++++++----------
src/calibre/library/database2.py | 23 ++++++---
2 files changed, 51 insertions(+), 32 deletions(-)
diff --git a/src/calibre/gui2/dialogs/metadata_bulk.py b/src/calibre/gui2/dialogs/metadata_bulk.py
index 3659547b13..b01869deaa 100644
--- a/src/calibre/gui2/dialogs/metadata_bulk.py
+++ b/src/calibre/gui2/dialogs/metadata_bulk.py
@@ -11,11 +11,11 @@ from PyQt4 import QtGui
from calibre.gui2.dialogs.metadata_bulk_ui import Ui_MetadataBulkDialog
from calibre.gui2.dialogs.tag_editor import TagEditor
-from calibre.ebooks.metadata import string_to_authors, \
- authors_to_string, MetaInformation
+from calibre.ebooks.metadata import string_to_authors, authors_to_string
from calibre.gui2.custom_column_widgets import populate_metadata_page
from calibre.gui2.dialogs.progress import BlockingBusy
from calibre.gui2 import error_dialog, Dispatcher
+from calibre.utils.config import dynamic
class Worker(Thread):
@@ -208,26 +208,27 @@ class MetadataBulkDialog(QDialog, Ui_MetadataBulkDialog):
self.book_1_text.setObjectName(name)
self.testgrid.addWidget(w, i+offset, 2, 1, 1)
- self.s_r_heading.setText('
'+
- _('You can destroy your library '
- 'using this feature. Changes are permanent. There '
- 'is no undo function. You are strongly encouraged '
- 'to back up your library before proceeding.'
- ) + '
' + _(
- 'Search and replace in text fields using '
- 'regular expressions. The search text is an '
- 'arbitrary python-compatible regular expression. '
- 'The replacement text can contain backreferences '
- 'to parenthesized expressions in the pattern. '
- 'The search is not anchored, and can match and '
- 'replace multiple times on the same string. See '
- ' '
- 'this reference '
- 'for more information, and in particular the \'sub\' '
- 'function.'
- ))
+ self.s_r_heading.setText('
'+ _(
+ 'You can destroy your library using this feature. '
+ 'Changes are permanent. There is no undo function. '
+ ' This feature is experimental, and there may be bugs. '
+ 'You are strongly encouraged to back up your library '
+ 'before proceeding.'
+ ) + '
' + _(
+ 'Search and replace in text fields using character matching '
+ 'or regular expressions. In character mode, search text '
+ 'found in the specified field is replaced with replace '
+ 'text. In regular expression mode, the search text is an '
+ 'arbitrary python-compatible regular expression. The '
+ 'replacement text can contain backreferences to parenthesized '
+ 'expressions in the pattern. The search is not anchored, '
+ 'and can match and replace multiple times on the same string. '
+ 'See '
+ 'this reference for more information, and in particular '
+ 'the \'sub\' function.'
+ ))
self.search_mode.addItems(self.s_r_match_modes)
- self.search_mode.setCurrentIndex(0)
+ self.search_mode.setCurrentIndex(dynamic.get('s_r_search_mode', 0))
self.replace_mode.addItems(self.s_r_replace_modes)
self.replace_mode.setCurrentIndex(0)
@@ -252,7 +253,7 @@ class MetadataBulkDialog(QDialog, Ui_MetadataBulkDialog):
self.search_for.completer().setCaseSensitivity(Qt.CaseSensitive)
self.replace_with.completer().setCaseSensitivity(Qt.CaseSensitive)
- self.s_r_search_mode_changed(0)
+ self.s_r_search_mode_changed(self.search_mode.currentIndex())
def s_r_get_field(self, mi, field):
if field:
@@ -303,6 +304,7 @@ class MetadataBulkDialog(QDialog, Ui_MetadataBulkDialog):
self.replace_mode.setVisible(True)
self.replace_mode_label.setVisible(True)
self.comma_separated.setVisible(True)
+ self.s_r_paint_results(None)
def s_r_set_colors(self):
if self.s_r_error is not None:
@@ -325,8 +327,12 @@ class MetadataBulkDialog(QDialog, Ui_MetadataBulkDialog):
src_field = unicode(self.search_field.currentText())
src = self.s_r_get_field(mi, src_field)
result = []
+ rfunc = self.s_r_functions[unicode(self.replace_func.currentText())]
for s in src:
- result.append(self.s_r_obj.sub(self.s_r_func, s))
+ t = self.s_r_obj.sub(self.s_r_func, s)
+ if self.search_mode.currentIndex() == 0:
+ t = rfunc(t)
+ result.append(t)
return result
def s_r_do_destination(self, mi, val):
@@ -374,7 +380,10 @@ class MetadataBulkDialog(QDialog, Ui_MetadataBulkDialog):
flags = re.I
try:
- self.s_r_obj = re.compile(unicode(self.search_for.text()), flags)
+ if self.search_mode.currentIndex() == 0:
+ self.s_r_obj = re.compile(re.escape(unicode(self.search_for.text())), flags)
+ else:
+ self.s_r_obj = re.compile(unicode(self.search_for.text()), flags)
except Exception as e:
self.s_r_obj = None
self.s_r_error = e
@@ -411,7 +420,7 @@ class MetadataBulkDialog(QDialog, Ui_MetadataBulkDialog):
dest = unicode(self.destination_field.currentText())
if not dest:
dest = source
- dfm = self.db.field_metadata[source]
+ dfm = self.db.field_metadata[dest]
for id in self.ids:
mi = self.db.get_metadata(id, index_is_id=True,)
@@ -439,6 +448,7 @@ class MetadataBulkDialog(QDialog, Ui_MetadataBulkDialog):
setter = getattr(self.db, 'set_'+dest)
setter(id, val, notify=False, commit=False)
self.db.commit()
+ dynamic['s_r_search_mode'] = self.search_mode.currentIndex()
def create_custom_column_editors(self):
w = self.central_widget.widget(1)
diff --git a/src/calibre/library/database2.py b/src/calibre/library/database2.py
index 2f9f9b6f89..c1ada94a84 100644
--- a/src/calibre/library/database2.py
+++ b/src/calibre/library/database2.py
@@ -464,11 +464,11 @@ class LibraryDatabase2(LibraryDatabase, SchemaUpgrade, CustomColumns):
# change case don't cause any changes to the directories in the file
# system. This can lead to having the directory names not match the
# title/author, which leads to trouble when libraries are copied to
- # a case-sensitive system. The following code fixes this by checking
- # each segment. If they are different because of case, then rename
- # the segment to some temp file name, then rename it back to the
- # correct name. Note that the code above correctly handles files in
- # the directories, so no need to do them here.
+ # a case-sensitive system. The following code attempts to fix this
+ # by checking each segment. If they are different because of case,
+ # then rename the segment to some temp file name, then rename it
+ # back to the correct name. Note that the code above correctly
+ # handles files in the directories, so no need to do them here.
for oldseg, newseg in zip(c1, c2):
if oldseg.lower() == newseg.lower() and oldseg != newseg:
while True:
@@ -476,8 +476,17 @@ class LibraryDatabase2(LibraryDatabase, SchemaUpgrade, CustomColumns):
tempname = os.path.join(curpath, 'TEMP.%f'%time.time())
if not os.path.exists(tempname):
break
- os.rename(os.path.join(curpath, oldseg), tempname)
- os.rename(tempname, os.path.join(curpath, newseg))
+ try:
+ os.rename(os.path.join(curpath, oldseg), tempname)
+ except (IOError, OSError):
+ # Windows (at least) sometimes refuses to do the rename
+ # probably because a file such a cover is open in the
+ # hierarchy. Just go on -- nothing is hurt beyond the
+ # case of the filesystem not matching the case in
+ # name stored by calibre
+ print 'rename of library component failed'
+ else:
+ os.rename(tempname, os.path.join(curpath, newseg))
curpath = os.path.join(curpath, newseg)
def add_listener(self, listener):
From e2f4b969bc6d36fbb285818cb50184cf83efed6e Mon Sep 17 00:00:00 2001
From: Charles Haley <>
Date: Mon, 20 Sep 2010 19:49:03 +0100
Subject: [PATCH 059/412] 1) add tooltips 2) change main heading text depending
on the mode 3) add an error if attempting to assign '' to authors or title
---
src/calibre/gui2/dialogs/metadata_bulk.py | 48 +++++++++++++++++----
src/calibre/gui2/dialogs/metadata_bulk.ui | 52 +++++++++++++++++++----
2 files changed, 84 insertions(+), 16 deletions(-)
diff --git a/src/calibre/gui2/dialogs/metadata_bulk.py b/src/calibre/gui2/dialogs/metadata_bulk.py
index b01869deaa..681f65b19e 100644
--- a/src/calibre/gui2/dialogs/metadata_bulk.py
+++ b/src/calibre/gui2/dialogs/metadata_bulk.py
@@ -208,25 +208,43 @@ class MetadataBulkDialog(QDialog, Ui_MetadataBulkDialog):
self.book_1_text.setObjectName(name)
self.testgrid.addWidget(w, i+offset, 2, 1, 1)
- self.s_r_heading.setText('
'+ _(
+ self.main_heading = _(
'You can destroy your library using this feature. '
'Changes are permanent. There is no undo function. '
' This feature is experimental, and there may be bugs. '
'You are strongly encouraged to back up your library '
'before proceeding.'
- ) + '
' + _(
+ + '
' +
'Search and replace in text fields using character matching '
- 'or regular expressions. In character mode, search text '
- 'found in the specified field is replaced with replace '
- 'text. In regular expression mode, the search text is an '
+ 'or regular expressions. ')
+
+ self.character_heading = _(
+ 'In character mode, the field is searched for the entered '
+ 'search text. The text is replaced by the specified replacement '
+ 'text everywhere it is found in the specified field. After '
+ 'replacement is finished, the text can be changed to '
+ 'upper-case, lower-case, or title-case. If the case-sensitive '
+ 'check box is checked, the search text must match exactly. If '
+ 'it is unchecked, the search text will match both upper- and '
+ 'lower-case letters'
+ )
+
+ self.regexp_heading = _(
+ 'In regular expression mode, the search text is an '
'arbitrary python-compatible regular expression. The '
'replacement text can contain backreferences to parenthesized '
'expressions in the pattern. The search is not anchored, '
'and can match and replace multiple times on the same string. '
+ 'The modification functions (lower-case etc) are applied to the '
+ 'matched text, not to the field as a whole. '
+ 'The destination box specifies the field where the result after '
+ 'matching and replacement is to be assigned. You can replace '
+ 'the text in the field, or prepend or append the matched text. '
'See '
- 'this reference for more information, and in particular '
- 'the \'sub\' function.'
- ))
+ 'this reference for more information on python\'s regular '
+ 'expressions, and in particular the \'sub\' function.'
+ )
+
self.search_mode.addItems(self.s_r_match_modes)
self.search_mode.setCurrentIndex(dynamic.get('s_r_search_mode', 0))
self.replace_mode.addItems(self.s_r_replace_modes)
@@ -298,12 +316,14 @@ class MetadataBulkDialog(QDialog, Ui_MetadataBulkDialog):
self.replace_mode.setVisible(False)
self.replace_mode_label.setVisible(False)
self.comma_separated.setVisible(False)
+ self.s_r_heading.setText('
'+self.main_heading + self.regexp_heading)
self.s_r_paint_results(None)
def s_r_set_colors(self):
@@ -434,8 +454,20 @@ class MetadataBulkDialog(QDialog, Ui_MetadataBulkDialog):
# The standard tags and authors values want to be lists.
# All custom columns are to be strings
val = dfm['is_multiple'].join(val)
+ if dest == 'authors' and len(val) == 0:
+ error_dialog(self, _('Search/replace invalid'),
+ _('Authors cannot be set to the empty string. '
+ 'Book title %s not processed')%mi.title,
+ show=True)
+ continue
else:
val = self.s_r_replace_mode_separator().join(val)
+ if dest == 'title' and len(val) == 0:
+ error_dialog(self, _('Search/replace invalid'),
+ _('Title cannot be set to the empty string. '
+ 'Book title %s not processed')%mi.title,
+ show=True)
+ continue
if dfm['is_custom']:
extra = self.db.get_custom_extra(id, label=dfm['label'], index_is_id=True)
diff --git a/src/calibre/gui2/dialogs/metadata_bulk.ui b/src/calibre/gui2/dialogs/metadata_bulk.ui
index e433aaf327..b2a3e11b4a 100644
--- a/src/calibre/gui2/dialogs/metadata_bulk.ui
+++ b/src/calibre/gui2/dialogs/metadata_bulk.ui
@@ -351,7 +351,11 @@ Future conversion of these books will use the default settings.
-
+
+
+ The name of the field that you want to search
+
+
@@ -361,12 +365,16 @@ Future conversion of these books will use the default settings.
Search mode:
- search_field
+ search_mode
-
+
+
+ Choose whether to use basic text matching or advanced regular expression matching
+
+
@@ -394,7 +402,11 @@ Future conversion of these books will use the default settings.
-
+
+
+ Enter the what you are looking for, either plain text or a regular expression, depending on the mode
+
+
@@ -404,6 +416,9 @@ Future conversion of these books will use the default settings.
true
+
+ Check this box if the search string must match exactly upper and lower case. Uncheck it if case is to be ignored
+
@@ -417,7 +432,11 @@ Future conversion of these books will use the default settings.
-
+
+
+ The replacement text. The matched search text will be replaced with this string
+
+
@@ -432,7 +451,12 @@ Future conversion of these books will use the default settings.
-
+
+
+ Specify how the text is to be processed after matching and replacement. In character mode, the entire
+field is processed. In regular expression mode, only the matched text is processed
+
+
@@ -460,7 +484,11 @@ Future conversion of these books will use the default settings.
-
+
+
+ The field that the text will be put into after all replacements. If blank, the source field is used.
+
+
@@ -475,7 +503,11 @@ Future conversion of these books will use the default settings.
-
+
+
+ Specify how the text should be copied into the destination.
+
+
@@ -485,6 +517,10 @@ Future conversion of these books will use the default settings.
true
+
+ If the replace mode is prepend or append, then this box indicates whether a comma or
+nothing should be put between the original text and the inserted text
+
From 57a8705ec1b869d97d04ceab75db9caf1426faad Mon Sep 17 00:00:00 2001
From: Kovid Goyal
Date: Mon, 20 Sep 2010 13:27:55 -0600
Subject: [PATCH 060/412] Don't maintain a separate prompt buffer
---
src/calibre/utils/pyconsole/editor.py | 49 +++++++++++++++------------
1 file changed, 28 insertions(+), 21 deletions(-)
diff --git a/src/calibre/utils/pyconsole/editor.py b/src/calibre/utils/pyconsole/editor.py
index 68b83539f2..431a10eda5 100644
--- a/src/calibre/utils/pyconsole/editor.py
+++ b/src/calibre/utils/pyconsole/editor.py
@@ -58,7 +58,6 @@ class Editor(QTextEdit):
QTextEdit.__init__(self, parent)
self.buf = ''
self.prompt_frame = None
- self.current_prompt = ['']
self.allow_output = False
self.prompt_frame_format = QTextFrameFormat()
self.prompt_frame_format.setBorder(1)
@@ -84,11 +83,21 @@ class Editor(QTextEdit):
self.interpreter = Interpreter(parent=self)
self.interpreter.show_error.connect(self.show_error)
- #it = self.prompt_frame.begin()
- #while not it.atEnd():
- # bl = it.currentBlock()
- # prints(repr(bl.text()))
- # it += 1
+ print list(self.prompt())
+
+
+ def prompt(self, strip_prompt_strings=True):
+ if not self.prompt_frame:
+ yield u'' if strip_prompt_strings else self.formatter.prompt
+ else:
+ it = self.prompt_frame.begin()
+ while not it.atEnd():
+ bl = it.currentBlock()
+ t = unicode(bl.text())
+ if strip_prompt_strings:
+ t = t[self.prompt_len:]
+ yield t
+ it += 1
# Rendering {{{
@@ -113,15 +122,16 @@ class Editor(QTextEdit):
c.setPosition(self.prompt_frame.firstPosition())
def render_current_prompt(self):
+ cp = list(self.prompt())
self.clear_current_prompt()
- for i, line in enumerate(self.current_prompt):
+ for i, line in enumerate(cp):
start = i == 0
- end = i == len(self.current_prompt) - 1
+ end = i == len(cp) - 1
self.formatter.render_prompt(not start, self.cursor)
self.formatter.render(self.lexer.get_tokens(line), self.cursor)
if not end:
- self.cursor.insertText('\n')
+ self.cursor.insertBlock()
def show_error(self, is_syntax_err, tb):
if self.prompt_frame is not None:
@@ -194,32 +204,29 @@ class Editor(QTextEdit):
def enter_pressed(self):
if self.prompt_frame is None:
return
- if self.current_prompt[0]:
+ cp = list(self.prompt())
+ if cp[0]:
c = self.root_frame.lastCursorPosition()
self.setTextCursor(c)
old_pf = self.prompt_frame
self.prompt_frame = None
oldbuf = self.buf
self.buf = ''
- ret = self.interpreter.runsource('\n'.join(self.current_prompt))
+ ret = self.interpreter.runsource('\n'.join(cp))
if ret: # Incomplete command
self.buf = oldbuf
self.prompt_frame = old_pf
- self.current_prompt.append('')
+ c = old_pf.lastCursorPosition()
+ c.insertBlock()
+ self.setTextCursor(c)
else: # Command completed
- self.current_prompt = ['']
old_pf.setFrameFormat(QTextFrameFormat())
self.render_current_prompt()
def text_typed(self, text):
- if not self.current_prompt[0]:
- self.cursor.beginEditBlock()
- else:
- self.cursor.joinPreviousEditBlock()
- self.current_prompt[-1] += text
- self.render_current_prompt()
- self.cursor.endEditBlock()
-
+ if self.prompt_frame is not None:
+ self.cursor.insertText(text)
+ self.render_current_prompt()
# }}}
From 111c73ab80549913cc405d86d09ca69ef648e583 Mon Sep 17 00:00:00 2001
From: Kovid Goyal
Date: Mon, 20 Sep 2010 13:46:11 -0600
Subject: [PATCH 061/412] ...
---
.../utils/pyconsole/{editor.py => console.py} | 31 ++++++++++++-------
src/calibre/utils/pyconsole/main.py | 4 +--
2 files changed, 21 insertions(+), 14 deletions(-)
rename src/calibre/utils/pyconsole/{editor.py => console.py} (96%)
diff --git a/src/calibre/utils/pyconsole/editor.py b/src/calibre/utils/pyconsole/console.py
similarity index 96%
rename from src/calibre/utils/pyconsole/editor.py
rename to src/calibre/utils/pyconsole/console.py
index 431a10eda5..d95e86c7ef 100644
--- a/src/calibre/utils/pyconsole/editor.py
+++ b/src/calibre/utils/pyconsole/console.py
@@ -29,7 +29,7 @@ class EditBlock(object): # {{{
self.cursor.endEditBlock()
# }}}
-class Editor(QTextEdit):
+class Console(QTextEdit):
@property
def doc(self):
@@ -86,6 +86,8 @@ class Editor(QTextEdit):
print list(self.prompt())
+ # Prompt management {{{
+
def prompt(self, strip_prompt_strings=True):
if not self.prompt_frame:
yield u'' if strip_prompt_strings else self.formatter.prompt
@@ -99,15 +101,8 @@ class Editor(QTextEdit):
yield t
it += 1
-
- # Rendering {{{
-
- def render_block(self, text, restore_prompt=True):
- self.formatter.render(self.lexer.get_tokens(text), self.cursor)
- self.cursor.insertBlock()
- self.cursor.movePosition(self.cursor.End)
- if restore_prompt:
- self.render_current_prompt()
+ def set_prompt(self, lines):
+ self.render_current_prompt(lines)
def clear_current_prompt(self):
if self.prompt_frame is None:
@@ -121,8 +116,8 @@ class Editor(QTextEdit):
c.removeSelectedText()
c.setPosition(self.prompt_frame.firstPosition())
- def render_current_prompt(self):
- cp = list(self.prompt())
+ def render_current_prompt(self, lines=None):
+ cp = list(self.prompt()) if lines is None else lines
self.clear_current_prompt()
for i, line in enumerate(cp):
@@ -133,6 +128,18 @@ class Editor(QTextEdit):
if not end:
self.cursor.insertBlock()
+ # }}}
+
+
+ # Non-prompt Rendering {{{
+
+ def render_block(self, text, restore_prompt=True):
+ self.formatter.render(self.lexer.get_tokens(text), self.cursor)
+ self.cursor.insertBlock()
+ self.cursor.movePosition(self.cursor.End)
+ if restore_prompt:
+ self.render_current_prompt()
+
def show_error(self, is_syntax_err, tb):
if self.prompt_frame is not None:
# At a prompt, so redirect output
diff --git a/src/calibre/utils/pyconsole/main.py b/src/calibre/utils/pyconsole/main.py
index c2694aae5f..af99ec66bb 100644
--- a/src/calibre/utils/pyconsole/main.py
+++ b/src/calibre/utils/pyconsole/main.py
@@ -10,7 +10,7 @@ from PyQt4.Qt import QMainWindow, QToolBar, QStatusBar, QLabel, QFont, Qt, \
QApplication
from calibre.constants import __appname__, __version__
-from calibre.utils.pyconsole.editor import Editor
+from calibre.utils.pyconsole.console import Console
class MainWindow(QMainWindow):
@@ -37,7 +37,7 @@ class MainWindow(QMainWindow):
self.tool_bar.setToolButtonStyle(Qt.ToolButtonTextOnly)
# }}}
- self.editor = Editor(parent=self)
+ self.editor = Console(parent=self)
self.setCentralWidget(self.editor)
From f770aa43bbf4a175cb614e437694732361209637 Mon Sep 17 00:00:00 2001
From: Kovid Goyal
Date: Mon, 20 Sep 2010 14:24:42 -0600
Subject: [PATCH 062/412] Left and right arrow keys work
---
src/calibre/utils/pyconsole/console.py | 64 ++++++++++++++++++--------
1 file changed, 45 insertions(+), 19 deletions(-)
diff --git a/src/calibre/utils/pyconsole/console.py b/src/calibre/utils/pyconsole/console.py
index d95e86c7ef..73a19e7958 100644
--- a/src/calibre/utils/pyconsole/console.py
+++ b/src/calibre/utils/pyconsole/console.py
@@ -45,18 +45,30 @@ class Console(QTextEdit):
@property
def cursor_pos(self):
- pass
- #pos = self.cursor.position() - self.prompt_frame.firstPosition()
- #i = 0
- #for line in self.current_prompt:
- # i += self.prompt_len
+ '''
+ Return cursor position in prompt frame as (row, col).
+ row starts at 0 for the first line
+ col is 0 if the cursor is at the start of the line, 1 if it is after
+ the first character, n if it is after the nth char.
+ '''
+ if self.prompt_frame is not None:
+ pos = self.cursor.position()
+ it = self.prompt_frame.begin()
+ lineno = 0
+ while not it.atEnd():
+ bl = it.currentBlock()
+ if bl.contains(pos):
+ return (lineno, pos - bl.position())
+ it += 1
+ lineno += 1
+ return (-1, -1)
def __init__(self,
prompt='>>> ',
continuation='... ',
parent=None):
QTextEdit.__init__(self, parent)
- self.buf = ''
+ self.buf = []
self.prompt_frame = None
self.allow_output = False
self.prompt_frame_format = QTextFrameFormat()
@@ -130,7 +142,6 @@ class Console(QTextEdit):
# }}}
-
# Non-prompt Rendering {{{
def render_block(self, text, restore_prompt=True):
@@ -143,26 +154,25 @@ class Console(QTextEdit):
def show_error(self, is_syntax_err, tb):
if self.prompt_frame is not None:
# At a prompt, so redirect output
- return prints(tb)
+ return prints(tb, end='')
try:
- self.buf += tb
+ self.buf.append(tb)
if is_syntax_err:
self.formatter.render_syntax_error(tb, self.cursor)
else:
self.formatter.render(self.tb_lexer.get_tokens(tb), self.cursor)
except:
- prints(tb)
+ prints(tb, end='')
def show_output(self, raw):
if self.prompt_frame is not None:
# At a prompt, so redirect output
- return prints(raw)
+ return prints(raw, end='')
try:
- self.current_prompt_range = None
- self.buf += raw
+ self.buf.append(raw)
self.formatter.render_raw(raw, self.cursor)
except:
- prints(raw)
+ prints(raw, end='')
# }}}
@@ -187,13 +197,29 @@ class Console(QTextEdit):
QTextEdit.keyPressEvent(self, ev)
def left_pressed(self):
- pass
+ lineno, pos = self.cursor_pos
+ if lineno < 0: return
+ if pos > self.prompt_len:
+ c = self.cursor
+ c.movePosition(c.PreviousCharacter)
+ self.setTextCursor(c)
+ elif lineno > 0:
+ c = self.cursor
+ c.movePosition(c.Up)
+ c.movePosition(c.EndOfLine)
+ self.setTextCursor(c)
def right_pressed(self):
- if self.prompt_frame is not None:
- c = self.cursor
+ lineno, pos = self.cursor_pos
+ if lineno < 0: return
+ c = self.cursor
+ lineno, pos = self.cursor_pos
+ cp = list(self.prompt(False))
+ if pos < len(cp[lineno]):
c.movePosition(c.NextCharacter)
- self.setTextCursor(c)
+ elif lineno < len(cp)-1:
+ c.movePosition(c.NextCharacter, n=1+self.prompt_len)
+ self.setTextCursor(c)
def home_pressed(self):
if self.prompt_frame is not None:
@@ -218,7 +244,7 @@ class Console(QTextEdit):
old_pf = self.prompt_frame
self.prompt_frame = None
oldbuf = self.buf
- self.buf = ''
+ self.buf = []
ret = self.interpreter.runsource('\n'.join(cp))
if ret: # Incomplete command
self.buf = oldbuf
From acec240ef8a8ad5be4a02deb52e7eb72f01bfa0f Mon Sep 17 00:00:00 2001
From: Charles Haley <>
Date: Mon, 20 Sep 2010 21:48:52 +0100
Subject: [PATCH 063/412] Add scroll bar. Increase number of books to 10
---
src/calibre/gui2/dialogs/metadata_bulk.py | 2 +-
src/calibre/gui2/dialogs/metadata_bulk.ui | 42 +++++++++++++++++------
2 files changed, 32 insertions(+), 12 deletions(-)
diff --git a/src/calibre/gui2/dialogs/metadata_bulk.py b/src/calibre/gui2/dialogs/metadata_bulk.py
index 681f65b19e..7122fe14fa 100644
--- a/src/calibre/gui2/dialogs/metadata_bulk.py
+++ b/src/calibre/gui2/dialogs/metadata_bulk.py
@@ -190,7 +190,7 @@ class MetadataBulkDialog(QDialog, Ui_MetadataBulkDialog):
self.destination_field.addItems(fields)
self.destination_field.setMaxVisibleItems(min(len(fields), 20))
offset = 10
- self.s_r_number_of_books = min(7, len(self.ids))
+ self.s_r_number_of_books = min(10, len(self.ids))
for i in range(1,self.s_r_number_of_books+1):
w = QtGui.QLabel(self.tabWidgetPage3)
w.setText(_('Book %d:')%i)
diff --git a/src/calibre/gui2/dialogs/metadata_bulk.ui b/src/calibre/gui2/dialogs/metadata_bulk.ui
index ec5a952346..f28f3fb57c 100644
--- a/src/calibre/gui2/dialogs/metadata_bulk.ui
+++ b/src/calibre/gui2/dialogs/metadata_bulk.ui
@@ -319,7 +319,7 @@ Future conversion of these books will use the default settings.
&Search and replace (experimental)
-
+ QLayout::SetMinimumSize
@@ -406,6 +406,12 @@ Future conversion of these books will use the default settings.
Enter the what you are looking for, either plain text or a regular expression, depending on the mode
+
+
+ 100
+ 0
+
+
@@ -558,19 +564,33 @@ nothing should be put between the original text and the inserted text
-
-
-
- Your test:
+
+
+
+ QFrame::NoFrame
+
+ true
+
+
+
+
+
+
+ Your test:
+
+
+
+
+
+
+
+
+
+
+
-
-
-
-
-
-
From 4b981a5257be82a9f4cfaeb8fca32f48f54dc7d0 Mon Sep 17 00:00:00 2001
From: Kovid Goyal
Date: Mon, 20 Sep 2010 14:58:24 -0600
Subject: [PATCH 064/412] Inserting, not just appending text now works
---
src/calibre/utils/pyconsole/console.py | 68 +++++++++++++++++---------
1 file changed, 45 insertions(+), 23 deletions(-)
diff --git a/src/calibre/utils/pyconsole/console.py b/src/calibre/utils/pyconsole/console.py
index 73a19e7958..19a24dfdd7 100644
--- a/src/calibre/utils/pyconsole/console.py
+++ b/src/calibre/utils/pyconsole/console.py
@@ -43,25 +43,6 @@ class Console(QTextEdit):
def root_frame(self):
return self.doc.rootFrame()
- @property
- def cursor_pos(self):
- '''
- Return cursor position in prompt frame as (row, col).
- row starts at 0 for the first line
- col is 0 if the cursor is at the start of the line, 1 if it is after
- the first character, n if it is after the nth char.
- '''
- if self.prompt_frame is not None:
- pos = self.cursor.position()
- it = self.prompt_frame.begin()
- lineno = 0
- while not it.atEnd():
- bl = it.currentBlock()
- if bl.contains(pos):
- return (lineno, pos - bl.position())
- it += 1
- lineno += 1
- return (-1, -1)
def __init__(self,
prompt='>>> ',
@@ -95,11 +76,48 @@ class Console(QTextEdit):
self.interpreter = Interpreter(parent=self)
self.interpreter.show_error.connect(self.show_error)
- print list(self.prompt())
-
# Prompt management {{{
+ @dynamic_property
+ def cursor_pos(self):
+ doc = '''
+ The cursor position in the prompt has the form (row, col).
+ row starts at 0 for the first line
+ col is 0 if the cursor is at the start of the line, 1 if it is after
+ the first character, n if it is after the nth char.
+ '''
+
+ def fget(self):
+ if self.prompt_frame is not None:
+ pos = self.cursor.position()
+ it = self.prompt_frame.begin()
+ lineno = 0
+ while not it.atEnd():
+ bl = it.currentBlock()
+ if bl.contains(pos):
+ return (lineno, pos - bl.position())
+ it += 1
+ lineno += 1
+ return (-1, -1)
+
+ def fset(self, val):
+ row, col = val
+ if self.prompt_frame is not None:
+ it = self.prompt_frame.begin()
+ lineno = 0
+ while not it.atEnd():
+ if lineno == row:
+ c = self.cursor
+ c.setPosition(it.currentBlock().position())
+ c.movePosition(c.NextCharacter, n=col)
+ self.setTextCursor(c)
+ break
+ it += 1
+ lineno += 1
+
+ return property(fget=fget, fset=fset, doc=doc)
+
def prompt(self, strip_prompt_strings=True):
if not self.prompt_frame:
yield u'' if strip_prompt_strings else self.formatter.prompt
@@ -128,7 +146,8 @@ class Console(QTextEdit):
c.removeSelectedText()
c.setPosition(self.prompt_frame.firstPosition())
- def render_current_prompt(self, lines=None):
+ def render_current_prompt(self, lines=None, restore_cursor=False):
+ row, col = self.cursor_pos
cp = list(self.prompt()) if lines is None else lines
self.clear_current_prompt()
@@ -140,6 +159,9 @@ class Console(QTextEdit):
if not end:
self.cursor.insertBlock()
+ if row > -1 and restore_cursor:
+ self.cursor_pos = (row, col)
+
# }}}
# Non-prompt Rendering {{{
@@ -259,7 +281,7 @@ class Console(QTextEdit):
def text_typed(self, text):
if self.prompt_frame is not None:
self.cursor.insertText(text)
- self.render_current_prompt()
+ self.render_current_prompt(restore_cursor=True)
# }}}
From fe6d962bee771a34dd4937b6f332e0d0d4114676 Mon Sep 17 00:00:00 2001
From: Kovid Goyal
Date: Mon, 20 Sep 2010 19:33:17 -0600
Subject: [PATCH 065/412] Use dialog instead of main window. Set console
stylesheet based on pygments style. Don't block if there is a lot of output
---
imgsrc/console.svg | 4339 ++++++++++++++++++++++
resources/images/console.png | Bin 0 -> 5110 bytes
src/calibre/utils/pyconsole/__init__.py | 9 +
src/calibre/utils/pyconsole/console.py | 117 +-
src/calibre/utils/pyconsole/formatter.py | 14 +-
src/calibre/utils/pyconsole/main.py | 44 +-
6 files changed, 4482 insertions(+), 41 deletions(-)
create mode 100644 imgsrc/console.svg
create mode 100644 resources/images/console.png
diff --git a/imgsrc/console.svg b/imgsrc/console.svg
new file mode 100644
index 0000000000..0d502bb1da
--- /dev/null
+++ b/imgsrc/console.svg
@@ -0,0 +1,4339 @@
+
+
+
diff --git a/resources/images/console.png b/resources/images/console.png
new file mode 100644
index 0000000000000000000000000000000000000000..168f0ccb2a177087d2ee6157bb67166688c84c05
GIT binary patch
literal 5110
zcmVxU{8
zPX+SQ2HuQqEW9l2vVZVDFa={E2%3ks4|OUPwOKoMg@^=5$wNZbR$+}383{v5y#|{)
zyEePt#rEF4GjsYdf9{zx-#K$;?wz}{*YlIcXJ*ddIp6pFzVDowy#{hz*Dy5h%gW@)
zE|}yz1W~vKcNDmDs%(zzfpIAVU~}iBB8Zk=aE}szB6s9UkbUsNx46TEqX;$#B`CS6
zC;TFSb^smj(e9S<*Z^Gl9NB#QUXx{+5vnGzzy!P6hNC>E?0nT
zLR0_~Ko!6>09V`ta1U2e
zwR!XArvXd=xCG!ToPaRE0NxXNOF(}4SG?z*d+xY&=@Q1r$H7BqB2^Qdq+UJ!)Z0HL
z5M2Q^Gp|yqV0LyEj4`ZTyLJ<5mhZXnfD7RLx|dnt4r9rZC2bcjTtFisT3OV2Bl^+l
zU#A@v6=x}La&i*w?d=^1FX02=wL%J>_IWf~U^3|R(}=M&>v_@wuMm|=g;J2vLq$gR
zwr$4>R)B@K@J5#rpI`L5S;-Uc3wh*2QZ68H#1(!LWXZy1WwJ!?x_P#31Htz>A(Uhp
zRD-8Zox;@AR8*B53y2jfR$#-14FL3(j*$w$V|scT!^6YLMwX)~ICSWcFL;*$l!(Ou
z9=f`^uxj;6tXjPaot-zpFbsMP%K(O2>SVboW6oNHO1W};{aPOUGVscRtD{&?i(?9m
zO*N*-v4!W1TB|n_j4{m5&*RdiOZf83FEKki8_JJDECuj<;;PjvvF>|!DJlgLUp&0P
z#1d|2H9?B`if8dlsh&49CN7Pbr+}_Sj!h_)Vhm*%20A)AaP!SK0{~`cX9IDI4g-im
zUUs;nIL#s+hKLBS-rFc)vfen=Kz*3Uk;!qBVrXm
zB9Q`gb#+P0)Naz3Iu_>@3{JhM2{fC8X_sIhp&lvX2sV+bMUw^Zp`)V%0Mcau56Wej
zglfYVlB!G;I6(nKlZoj+)R1CK6jLpbz(r6pNsR$iVQqE!8Alt~-9n^ztZtpenQ1Bpi9
z70#ox#SB`o$_Lh@c%6C+<^TCNf8tv}&)TUaxeAH)0c2&u;grncgp?6Wt3W+TzkdI!
z!AVD?)=*+e8X6;^lu78HF&22|;+=a!)uD2#&E_*eprVlSmoX{6;r?I3Cekq}>kGFc
zToW%`QP7P*Q(Ztqmw!@$iy1VV)V=)E(Jm(TDj7dxuq?|LvM1Dw&C>8wJjf;Ol25s%Z6=R
zFii`VZGqVg{yTvMSoW#VPz8{E9~Uf4U+P$#m!}sS#GrpMb?fVljWNKoE!dVLaND-R
zFN30CUJr?(=uUOwrlBbu;z|U9h(R&1pP@*6HSQ$l5Dsn$xC8!a{uor
zm2F$FOv@L1=vx3~WEdhH06wObfP0>x@N@
zjq6&fgBcTi^J4%h)iOc)qBBl!`R56o3f!Uu7KllF!G~QM|7=;Ie;~dBXuS-g{%lIN
z{OjG-i*K#|RH7dq
zq1tKB?YCp+qdRf#+Nv8ydrP*-yN(y>v+
zG9-8ksqOHq#7$B!Sw+0Q;#
zP%V>MoMl@uP1E0FmyTEYFp@rR2XA{vU|H574kLf;{~0cfSP#L!nUck7B8a
zHEX_u!Ka3?ZR;agvEnAK1G&X9+eWoIk89Vip;D>9G)?J4DR%(_0t|GWh=l@K*^s-B
zxF3ba`oj(0AS;B=-~DBviJ
zA+`v!YQnNiSeEmfMvSmPrIt&mnhXmf>Md6PuZzUXK$XQ!OibV({_*e8b9)aSc;G>-
z`Su#{-gpC-XD-uCD&{!(#~-b=`v=4i0_%$bD2hec;y(QF6rMe>AOH5#f5ptqj2|0#
zD~pbf4*dC_ZNZQK?#HzjNWPZQzuHoTa8(bIU^HGM%||L5LE)Q$gf=pwonQR&7wGNV
zg`fQMVa&|Tpja#hj?T_5b>Nkh{zDBBRTaS~9UEki{yn7rHZbnTAGU2d{@pZT6bdL6
zi^|_5X&C(n>eFM3ej_Q#&z{@?q___d+6Yq7zYEK>P&G~1mIW9FzW@Ed#QlGDKb9?9
z9?>b)hX(zNso;xrm`awX#X=cC)0TlMpK6&F=BrgO+Xe%$e*Jp<&0jx+TW`Hp+TQu|
z=kc>+M~Rj_fhQjnX~+@vV#f>_M9Oai!-8@wzUseczvGUzc=(}*aeGgXbSBS?pTX$p
zPx1cyzafPjNARTTC~3TPmVX|a`#RXoax8w{|3I>*XC)qf=wW>KyMH3}_2c7bFgiMl
z_s4z{{M<@N)nikXl?9{jKauUL*dTjy_tBiUfu7(k$Kw4RI{=UG>chs3e~MzUK+e+_
zU!2F!jvd3V-}@COi0Gh_A4}}zUpO34Jg`CuGc0%t4O0MdBgk_1=sm$Z1)XHmrcFd6
z!1&p5jE;_C?8N&-JFc~$mkykz6{bqA_k~b=9HGopN)@?cJJO3Ze6wfBVF3!@c&5=
zYa_^KfZ9Crm%)n$S)B8O*OEFhpk^Vu{l)12Q+XXMHXa>#$@4^;Qdu0pP%4$`pZG*$
z)2yE{r21Jncej?R1gQGKy{|A8K;h{hP
z7z`;mAu`m28u+Up+43*m*cJ;5r1Hn1)DlpTLo`AGpW5O8hEG2EgoysRl(}LRQgJm9
z=uT^F#DgwEG{gn)i25QZGywOXkiZr7{Zbl!Tgo8Pn;J}&w*N^P;-~vjNTvXSrHZ~Z
zNVT+-b?84-O~aUFF$p~;y;x>3`TvH06BxPsr@UFaSC7Rd`S>Hyyat;nhm?J}Cq{iP
z0FX|ORN&w<)Pha&I|O7n5mB#|{C~q4fNnfOfSNB_MAYs7Rg~0%AAe9tMFEsQ67BvI
zhvFOV|4G3|u@GIq73e>XvLL-eeUO
zAf}7bs+VyzWgh^5bduC;Ao2I!l{S~xQc~blJ1S2U$CtDi5-$Tq($u5cAYxIi#Q)bW
zBz<3JAAdAyAHYRIi&O0qhnkW4e^G!!KRPjqSch+Iamn3BbM^sZ`c@r_)AIik$uc23
z5=1acjsJQ;9SjgnwZU>UwEVw>{&fT{Do1E+g!U{}`Ujfi0_aIqfUr0ns!i|PfBJN>
z(?8JEeEcS2i4f9VvAVtNm2jXKo>L{&=x1&O6%hf@1B&Y^t{xfLaHvH
zhImw)g#NXPsDer+i$!SmX$AkE1hF=PLhk}nw7C3-ACsZE`v5#tv#zpKssAV1X2fl|
zJ}6_qR`vf){2m}owRFHoqi+ANtap8U{Lu*1zYOG|VR1TCn|S%hm{KpI#?}ffurMw_
zI;m=llhc26y%^y{uqmoe3kcprbN2xj`r{AH*g%h|u0Q$*>V6xj0AX=;(|@48$Og&2
z{r8`wMdY69_W;UI!eUyS7X6E<5dD#CKtaJmts;02sm1{9?d^eUsldT!&;m{J@rT$i
zl_#s$iUA*mVzC&jd@Kbhm&*V=0Yq6UjW~iQdr&ATj90vjY%-bC*Lu6%XH?UfrqAXQtOmj4$+dA>yWwj4w-8X9y9
zpi-$Ms+ULsKK=AlImIG;D&ER}__6VroSaNlPy0urcsFqF^Ybta11ned;D*l5U_rv-
zbf`A};m2f{o14Sr^p@(0SCw0ES^;cz75G4<7XI4Gs?Cg%@7%
zuLlMO@Z59H`PWZA`6Qlw_F4bBudfgL_wVR`mx6z!|vU?{p+1OcjD=%
zpZ5FTv112@hK78_*tTsO1_uZI@{c_72nGfQ{OfJowqa;!$iLpPV+Te?Mgr~g{q5Sd
zD=?nNAAdYB-Y1@TBJe!@{r!RGed?*F@ci@7``1H5LwNDU7yavzkrBM~(o6pJo;`c;
z$}6w<*ZcPE!>g~p>R&(e%rkiHwb#%nBw7aY=x|?(S}Bf3ot8f&qsG+5vO|
zSOMUh?y&+u=La8ra4<>G$DcPk0F<>Y3jl%a1#XSXi^As3o3{YC0N^5kuK>&g@Rta6
zxd4&=hlYlHx-$%er2hj44*1tP^uK4%9{<{-e^0O;{d?f)-!Kep-MSS60|WjOrh!pa_qSbm?40XP$eAL$`6<4`a;F_XH%;eH5Lm#^bfm_a|$g
zzZToqoiECrBtfJLIC=8qL7=g;o#r`z&oGQk&~%!4QHbjT7;$JVy#@yd19T_Se}8|!
zPydEt`1J3!-P_w+``(wd;Qfx|Gpe4l!Stjkgdrnx{njhs_2|wJ?NCz&5xpc
z72QUy7Iem2&uSq7{gd+bTtFKnE?{hI>~Bk@QVB?ps?$0LuJ0{i+qSFs-h1y~Am{?B
zh;RY6doTcOVq)S#WKRjutldQss)=ibJRX-XUp|AHEJ+l&Oi%EZdzb*M!-o&QV_BA&
z_BL%)8*%%gSb#z+j1g{EV=K#
z`)+&i!3Q^Xc6N4(6ziQA3WdPEq-FSfUU{*cPTL;%iL-4xa4kmDjIqFLRFQ5e`i*Ln
z%BNDX->5bz7>2FlAPz109z>NTw
z!x8>+xC#I#STwjgIkE-Dr6zzm0J8wT1aQesXWaW1R~{wQmI)Ss3Vr$Q2;V;1$4zBkXxNGOVDs`xjCGLpU$EM+r_`;2pW}vkzVgECg3`{L(%E
zQJ@BQ6u5J$Y>w=KR|1a>0^9l`WI`30A;;nZlbq+skt0Wr96562$dMyQjvP61 -1 and restore_cursor:
self.cursor_pos = (row, col)
+ self.ensureCursorVisible()
+
# }}}
# Non-prompt Rendering {{{
@@ -185,16 +237,26 @@ class Console(QTextEdit):
self.formatter.render(self.tb_lexer.get_tokens(tb), self.cursor)
except:
prints(tb, end='')
+ self.ensureCursorVisible()
+ QCoreApplication.processEvents()
def show_output(self, raw):
+ def do_show():
+ try:
+ self.buf.append(raw)
+ self.formatter.render_raw(raw, self.cursor)
+ except:
+ import traceback
+ prints(traceback.format_exc())
+ prints(raw, end='')
+
if self.prompt_frame is not None:
- # At a prompt, so redirect output
- return prints(raw, end='')
- try:
- self.buf.append(raw)
- self.formatter.render_raw(raw, self.cursor)
- except:
- prints(raw, end='')
+ with Prepender(self):
+ do_show()
+ else:
+ do_show()
+ self.ensureCursorVisible()
+ QCoreApplication.processEvents()
# }}}
@@ -203,16 +265,11 @@ class Console(QTextEdit):
def keyPressEvent(self, ev):
text = unicode(ev.text())
key = ev.key()
- if key in (Qt.Key_Enter, Qt.Key_Return):
- self.enter_pressed()
- elif key == Qt.Key_Home:
- self.home_pressed()
- elif key == Qt.Key_End:
- self.end_pressed()
- elif key == Qt.Key_Left:
- self.left_pressed()
- elif key == Qt.Key_Right:
- self.right_pressed()
+ action = self.key_dispatcher.get(key, None)
+ if callable(action):
+ action()
+ elif key in (Qt.Key_Escape,):
+ QTextEdit.keyPressEvent(self, ev)
elif text:
self.text_typed(text)
else:
@@ -230,6 +287,7 @@ class Console(QTextEdit):
c.movePosition(c.Up)
c.movePosition(c.EndOfLine)
self.setTextCursor(c)
+ self.ensureCursorVisible()
def right_pressed(self):
lineno, pos = self.cursor_pos
@@ -242,6 +300,7 @@ class Console(QTextEdit):
elif lineno < len(cp)-1:
c.movePosition(c.NextCharacter, n=1+self.prompt_len)
self.setTextCursor(c)
+ self.ensureCursorVisible()
def home_pressed(self):
if self.prompt_frame is not None:
@@ -249,12 +308,14 @@ class Console(QTextEdit):
c.movePosition(c.StartOfLine)
c.movePosition(c.NextCharacter, n=self.prompt_len)
self.setTextCursor(c)
+ self.ensureCursorVisible()
def end_pressed(self):
if self.prompt_frame is not None:
c = self.cursor
c.movePosition(c.EndOfLine)
self.setTextCursor(c)
+ self.ensureCursorVisible()
def enter_pressed(self):
if self.prompt_frame is None:
@@ -267,7 +328,13 @@ class Console(QTextEdit):
self.prompt_frame = None
oldbuf = self.buf
self.buf = []
- ret = self.interpreter.runsource('\n'.join(cp))
+ self.running.emit()
+ try:
+ ret = self.interpreter.runsource('\n'.join(cp))
+ except SystemExit:
+ ret = False
+ self.show_output('Raising SystemExit not allowed\n')
+ self.running_done.emit()
if ret: # Incomplete command
self.buf = oldbuf
self.prompt_frame = old_pf
@@ -275,7 +342,13 @@ class Console(QTextEdit):
c.insertBlock()
self.setTextCursor(c)
else: # Command completed
- old_pf.setFrameFormat(QTextFrameFormat())
+ try:
+ old_pf.setFrameFormat(QTextFrameFormat())
+ except RuntimeError:
+ # Happens if enough lines of output that the old
+ # frame was deleted
+ pass
+
self.render_current_prompt()
def text_typed(self, text):
diff --git a/src/calibre/utils/pyconsole/formatter.py b/src/calibre/utils/pyconsole/formatter.py
index 7f99983ef6..9409007ec6 100644
--- a/src/calibre/utils/pyconsole/formatter.py
+++ b/src/calibre/utils/pyconsole/formatter.py
@@ -8,7 +8,7 @@ __docformat__ = 'restructuredtext en'
from PyQt4.Qt import QTextCharFormat, QFont, QBrush, QColor
from pygments.formatter import Formatter as PF
-from pygments.token import Token
+from pygments.token import Token, Generic
class Formatter(object):
@@ -22,11 +22,16 @@ class Formatter(object):
pf = PF(**options)
self.styles = {}
self.normal = self.base_fmt()
+ self.background_color = pf.style.background_color
+ self.color = 'black'
+
for ttype, ndef in pf.style:
fmt = self.base_fmt()
if ndef['color']:
fmt.setForeground(QBrush(QColor('#%s'%ndef['color'])))
fmt.setUnderlineColor(QColor('#%s'%ndef['color']))
+ if ttype == Generic.Output:
+ self.color = '#%s'%ndef['color']
if ndef['bold']:
fmt.setFontWeight(QFont.Bold)
if ndef['italic']:
@@ -40,6 +45,11 @@ class Formatter(object):
self.styles[ttype] = fmt
+ self.stylesheet = '''
+ QTextEdit { color: %s; background-color: %s }
+ '''%(self.color, self.background_color)
+
+
def base_fmt(self):
fmt = QTextCharFormat()
fmt.setFontFamily('monospace')
@@ -74,7 +84,7 @@ class Formatter(object):
def render_prompt(self, is_continuation, cursor):
pr = self.continuation if is_continuation else self.prompt
- fmt = self.styles[Token.Generic.Subheading]
+ fmt = self.styles[Generic.Prompt]
cursor.insertText(pr, fmt)
diff --git a/src/calibre/utils/pyconsole/main.py b/src/calibre/utils/pyconsole/main.py
index af99ec66bb..f098ce2ee2 100644
--- a/src/calibre/utils/pyconsole/main.py
+++ b/src/calibre/utils/pyconsole/main.py
@@ -6,19 +6,31 @@ __copyright__ = '2010, Kovid Goyal '
__docformat__ = 'restructuredtext en'
__version__ = '0.1.0'
-from PyQt4.Qt import QMainWindow, QToolBar, QStatusBar, QLabel, QFont, Qt, \
- QApplication
+from functools import partial
+
+from PyQt4.Qt import QDialog, QToolBar, QStatusBar, QLabel, QFont, Qt, \
+ QApplication, QIcon, QVBoxLayout
from calibre.constants import __appname__, __version__
from calibre.utils.pyconsole.console import Console
-class MainWindow(QMainWindow):
+class MainWindow(QDialog):
- def __init__(self, default_status_msg):
+ def __init__(self,
+ default_status_msg=_('Welcome to') + ' ' + __appname__+' console',
+ parent=None):
- QMainWindow.__init__(self)
+ QDialog.__init__(self, parent)
+ self.l = QVBoxLayout()
+ self.setLayout(self.l)
- self.resize(600, 700)
+ self.resize(800, 600)
+
+ # Setup tool bar {{{
+ self.tool_bar = QToolBar(self)
+ self.tool_bar.setToolButtonStyle(Qt.ToolButtonTextOnly)
+ self.l.addWidget(self.tool_bar)
+ # }}}
# Setup status bar {{{
self.status_bar = QStatusBar(self)
@@ -28,25 +40,23 @@ class MainWindow(QMainWindow):
self.status_bar._font.setBold(True)
self.status_bar.defmsg.setFont(self.status_bar._font)
self.status_bar.addWidget(self.status_bar.defmsg)
- self.setStatusBar(self.status_bar)
# }}}
- # Setup tool bar {{{
- self.tool_bar = QToolBar(self)
- self.addToolBar(Qt.BottomToolBarArea, self.tool_bar)
- self.tool_bar.setToolButtonStyle(Qt.ToolButtonTextOnly)
- # }}}
-
- self.editor = Console(parent=self)
- self.setCentralWidget(self.editor)
-
+ self.console = Console(parent=self)
+ self.console.running.connect(partial(self.status_bar.showMessage,
+ _('Code is running')))
+ self.console.running_done.connect(self.status_bar.clearMessage)
+ self.l.addWidget(self.console)
+ self.l.addWidget(self.status_bar)
+ self.setWindowTitle(__appname__ + ' console')
+ self.setWindowIcon(QIcon(I('console.png')))
def main():
QApplication.setApplicationName(__appname__+' console')
QApplication.setOrganizationName('Kovid Goyal')
app = QApplication([])
- m = MainWindow(_('Welcome to') + ' ' + __appname__+' console')
+ m = MainWindow()
m.show()
app.exec_()
From 3fff4da652dd242fbdeb52c8d5676043c02df7c4 Mon Sep 17 00:00:00 2001
From: Kovid Goyal
Date: Mon, 20 Sep 2010 21:32:29 -0600
Subject: [PATCH 066/412] Infrastructure changes to launch pyconsole
interpreter process
---
src/calibre/utils/ipc/launch.py | 21 ++++++++++++---------
src/calibre/utils/ipc/worker.py | 4 ++++
src/calibre/utils/pyconsole/console.py | 1 +
3 files changed, 17 insertions(+), 9 deletions(-)
diff --git a/src/calibre/utils/ipc/launch.py b/src/calibre/utils/ipc/launch.py
index 0de81ed644..aa93469119 100644
--- a/src/calibre/utils/ipc/launch.py
+++ b/src/calibre/utils/ipc/launch.py
@@ -22,13 +22,15 @@ class Worker(object):
have the environment variable :envvar:`CALIBRE_WORKER` set.
Useful attributes: ``is_alive``, ``returncode``
- usefule methods: ``kill``
+ Useful methods: ``kill``
To launch child simply call the Worker object. By default, the child's
output is redirected to an on disk file, the path to which is returned by
the call.
'''
+ exe_name = 'calibre-parallel'
+
@property
def osx_interpreter(self):
exe = os.path.basename(sys.executable)
@@ -41,32 +43,33 @@ class Worker(object):
@property
def executable(self):
+ e = self.exe_name
if iswindows:
return os.path.join(os.path.dirname(sys.executable),
- 'calibre-parallel.exe' if isfrozen else \
- 'Scripts\\calibre-parallel.exe')
+ e+'.exe' if isfrozen else \
+ 'Scripts\\%s.exe'%e)
if isnewosx:
- return os.path.join(sys.console_binaries_path, 'calibre-parallel')
+ return os.path.join(sys.console_binaries_path, e)
if isosx:
- if not isfrozen: return 'calibre-parallel'
+ if not isfrozen: return e
contents = os.path.join(self.osx_contents_dir,
'console.app', 'Contents')
return os.path.join(contents, 'MacOS', self.osx_interpreter)
if isfrozen:
- return os.path.join(getattr(sys, 'frozen_path'), 'calibre-parallel')
+ return os.path.join(getattr(sys, 'frozen_path'), e)
- c = os.path.join(sys.executables_location, 'calibre-parallel')
+ c = os.path.join(sys.executables_location, e)
if os.access(c, os.X_OK):
return c
- return 'calibre-parallel'
+ return e
@property
def gui_executable(self):
if isnewosx:
- return os.path.join(sys.binaries_path, 'calibre-parallel')
+ return os.path.join(sys.binaries_path, self.exe_name)
if isfrozen and isosx:
return os.path.join(self.osx_contents_dir,
diff --git a/src/calibre/utils/ipc/worker.py b/src/calibre/utils/ipc/worker.py
index 73233840fe..b7510426aa 100644
--- a/src/calibre/utils/ipc/worker.py
+++ b/src/calibre/utils/ipc/worker.py
@@ -80,8 +80,12 @@ def main():
if isosx and 'CALIBRE_WORKER_ADDRESS' not in os.environ:
# On some OS X computers launchd apparently tries to
# launch the last run process from the bundle
+ # so launch the gui as usual
from calibre.gui2.main import main as gui_main
return gui_main(['calibre'])
+ if 'CALIBRE_LAUNCH_INTERPRETER' in os.environ:
+ from calibre.utils.pyconsole.interpreter import main
+ return main()
address = cPickle.loads(unhexlify(os.environ['CALIBRE_WORKER_ADDRESS']))
key = unhexlify(os.environ['CALIBRE_WORKER_KEY'])
resultf = unhexlify(os.environ['CALIBRE_WORKER_RESULT'])
diff --git a/src/calibre/utils/pyconsole/console.py b/src/calibre/utils/pyconsole/console.py
index 251e8424a0..f741562f03 100644
--- a/src/calibre/utils/pyconsole/console.py
+++ b/src/calibre/utils/pyconsole/console.py
@@ -47,6 +47,7 @@ class Prepender(object): # {{{
self.console.cursor_pos = self.opos
# }}}
+
class Console(QTextEdit):
running = pyqtSignal()
From ff73865d9e75d5d4e44eb584961209654d7239e9 Mon Sep 17 00:00:00 2001
From: Charles Haley <>
Date: Tue, 21 Sep 2010 14:13:03 +0100
Subject: [PATCH 067/412] Prevent cross-thread lock errors by having the cover
cache get the image on the GUI thread.
---
src/calibre/gui2/__init__.py | 30 +++++++++++++++++++++++++++++-
src/calibre/gui2/library/models.py | 4 ++--
src/calibre/library/caches.py | 7 +++++--
3 files changed, 36 insertions(+), 5 deletions(-)
diff --git a/src/calibre/gui2/__init__.py b/src/calibre/gui2/__init__.py
index e58dce5559..ba32c09e06 100644
--- a/src/calibre/gui2/__init__.py
+++ b/src/calibre/gui2/__init__.py
@@ -1,7 +1,7 @@
__license__ = 'GPL v3'
__copyright__ = '2008, Kovid Goyal '
""" The GUI """
-import os, sys
+import os, sys, Queue
from threading import RLock
from PyQt4.Qt import QVariant, QFileInfo, QObject, SIGNAL, QBuffer, Qt, \
@@ -296,6 +296,34 @@ class Dispatcher(QObject):
def dispatch(self, args, kwargs):
self.func(*args, **kwargs)
+class FunctionDispatcher(QObject):
+ '''
+ Convenience class to use Qt signals with arbitrary python functions.
+ By default, ensures that a function call always happens in the
+ thread this Dispatcher was created in.
+ '''
+ dispatch_signal = pyqtSignal(object, object, object)
+
+ def __init__(self, func, queued=True, parent=None):
+ QObject.__init__(self, parent)
+ self.func = func
+ typ = Qt.QueuedConnection
+ if not queued:
+ typ = Qt.AutoConnection if queued is None else Qt.DirectConnection
+ self.dispatch_signal.connect(self.dispatch, type=typ)
+
+ def __call__(self, *args, **kwargs):
+ q = Queue.Queue()
+ self.dispatch_signal.emit(q, args, kwargs)
+ return q.get()
+
+ def dispatch(self, q, args, kwargs):
+ try:
+ res = self.func(*args, **kwargs)
+ except:
+ res = None
+ q.put(res)
+
class GetMetadata(QObject):
'''
Convenience class to ensure that metadata readers are used only in the
diff --git a/src/calibre/gui2/library/models.py b/src/calibre/gui2/library/models.py
index 6941869e44..4b1e974b12 100644
--- a/src/calibre/gui2/library/models.py
+++ b/src/calibre/gui2/library/models.py
@@ -12,7 +12,7 @@ from operator import attrgetter
from PyQt4.Qt import QAbstractTableModel, Qt, pyqtSignal, QIcon, QImage, \
QModelIndex, QVariant, QDate
-from calibre.gui2 import NONE, config, UNDEFINED_QDATE
+from calibre.gui2 import NONE, config, UNDEFINED_QDATE, FunctionDispatcher
from calibre.utils.pyparsing import ParseException
from calibre.ebooks.metadata import fmt_sidx, authors_to_string, string_to_authors
from calibre.ptempfile import PersistentTemporaryFile
@@ -151,7 +151,7 @@ class BooksModel(QAbstractTableModel): # {{{
self.database_changed.emit(db)
if self.cover_cache is not None:
self.cover_cache.stop()
- self.cover_cache = CoverCache(db)
+ self.cover_cache = CoverCache(db, FunctionDispatcher(self.db.cover))
self.cover_cache.start()
def refresh_cover(event, ids):
if event == 'cover' and self.cover_cache is not None:
diff --git a/src/calibre/library/caches.py b/src/calibre/library/caches.py
index 5f7fbdccc9..573c1f5797 100644
--- a/src/calibre/library/caches.py
+++ b/src/calibre/library/caches.py
@@ -23,10 +23,11 @@ from calibre import fit_image
class CoverCache(Thread):
- def __init__(self, db):
+ def __init__(self, db, cover_func):
Thread.__init__(self)
self.daemon = True
self.db = db
+ self.cover_func = cover_func
self.load_queue = Queue()
self.keep_running = True
self.cache = {}
@@ -37,7 +38,9 @@ class CoverCache(Thread):
self.keep_running = False
def _image_for_id(self, id_):
- img = self.db.cover(id_, index_is_id=True, as_image=True)
+ import time
+ time.sleep(0.050) # Limit 20/second to not overwhelm the GUI
+ img = self.cover_func(id_, index_is_id=True, as_image=True)
if img is None:
img = QImage()
if not img.isNull():
From be2210f928d0023ae1f752aeea3fb51f84132e8d Mon Sep 17 00:00:00 2001
From: Charles Haley <>
Date: Tue, 21 Sep 2010 15:26:02 +0100
Subject: [PATCH 068/412] Add 'start series renumbering from N' to bulk edit.
---
src/calibre/gui2/dialogs/metadata_bulk.py | 25 +++++++-
src/calibre/gui2/dialogs/metadata_bulk.ui | 69 +++++++++++++++++++----
2 files changed, 79 insertions(+), 15 deletions(-)
diff --git a/src/calibre/gui2/dialogs/metadata_bulk.py b/src/calibre/gui2/dialogs/metadata_bulk.py
index 7122fe14fa..8a692d94d5 100644
--- a/src/calibre/gui2/dialogs/metadata_bulk.py
+++ b/src/calibre/gui2/dialogs/metadata_bulk.py
@@ -31,7 +31,8 @@ class Worker(Thread):
def doit(self):
remove, add, au, aus, do_aus, rating, pub, do_series, \
do_autonumber, do_remove_format, remove_format, do_swap_ta, \
- do_remove_conv, do_auto_author, series = self.args
+ do_remove_conv, do_auto_author, series, do_series_restart, \
+ series_start_value = self.args
# first loop: do author and title. These will commit at the end of each
# operation, because each operation modifies the file system. We want to
@@ -69,7 +70,11 @@ class Worker(Thread):
self.db.set_publisher(id, pub, notify=False, commit=False)
if do_series:
- next = self.db.get_next_series_num_for(series)
+ if do_series_restart:
+ next = series_start_value
+ series_start_value += 1
+ else:
+ next = self.db.get_next_series_num_for(series)
self.db.set_series(id, series, notify=False, commit=False)
num = next if do_autonumber and series else 1.0
self.db.set_series_index(id, num, notify=False, commit=False)
@@ -163,6 +168,7 @@ class MetadataBulkDialog(QDialog, Ui_MetadataBulkDialog):
self.series.currentIndexChanged[int].connect(self.series_changed)
self.series.editTextChanged.connect(self.series_changed)
self.tag_editor_button.clicked.connect(self.tag_editor)
+ self.autonumber_series.stateChanged[int].connect(self.auto_number_changed)
if len(db.custom_column_label_map) == 0:
self.central_widget.removeTab(1)
@@ -538,6 +544,16 @@ class MetadataBulkDialog(QDialog, Ui_MetadataBulkDialog):
self.tags.update_tags_cache(self.db.all_tags())
self.remove_tags.update_tags_cache(self.db.all_tags())
+ def auto_number_changed(self, state):
+ if state:
+ self.series_numbering_restarts.setEnabled(True)
+ self.series_start_number.setEnabled(True)
+ else:
+ self.series_numbering_restarts.setEnabled(False)
+ self.series_numbering_restarts.setChecked(False)
+ self.series_start_number.setEnabled(False)
+ self.series_start_number.setValue(1)
+
def accept(self):
if len(self.ids) < 1:
return QDialog.accept(self)
@@ -566,6 +582,8 @@ class MetadataBulkDialog(QDialog, Ui_MetadataBulkDialog):
do_series = self.write_series
series = unicode(self.series.currentText()).strip()
do_autonumber = self.autonumber_series.isChecked()
+ do_series_restart = self.series_numbering_restarts.isChecked()
+ series_start_value = self.series_start_number.value()
do_remove_format = self.remove_format.currentIndex() > -1
remove_format = unicode(self.remove_format.currentText())
do_swap_ta = self.swap_title_and_author.isChecked()
@@ -574,7 +592,8 @@ class MetadataBulkDialog(QDialog, Ui_MetadataBulkDialog):
args = (remove, add, au, aus, do_aus, rating, pub, do_series,
do_autonumber, do_remove_format, remove_format, do_swap_ta,
- do_remove_conv, do_auto_author, series)
+ do_remove_conv, do_auto_author, series, do_series_restart,
+ series_start_value)
bb = BlockingBusy(_('Applying changes to %d books. This may take a while.')
%len(self.ids), parent=self)
diff --git a/src/calibre/gui2/dialogs/metadata_bulk.ui b/src/calibre/gui2/dialogs/metadata_bulk.ui
index f28f3fb57c..10e22c5df9 100644
--- a/src/calibre/gui2/dialogs/metadata_bulk.ui
+++ b/src/calibre/gui2/dialogs/metadata_bulk.ui
@@ -270,18 +270,63 @@
-
-
-
- Selected books will be automatically numbered,
-in the order you selected them.
-So if you selected Book A and then Book B,
+
+
+
+
+
+ If not checked, the series number for the books will be set to 1.
+If checked, selected books will be automatically numbered, in the order
+you selected them. So if you selected Book A and then Book B,
Book A will have series number 1 and Book B series number 2.
-
-
- Automatically number books in this series
-
-
+
+
+ Automatically number books in this series
+
+
+
+
+
+
+ false
+
+
+ Series will normally be renumbered from the highest number in the database
+for that series. Checking this box will tell calibre to start numbering
+from the value in the box
+
+
+ Force numbers to start with
+
+
+
+
+
+
+ false
+
+
+ 1
+
+
+ 1
+
+
+
+
+
+
+ Qt::Horizontal
+
+
+
+ 20
+ 10
+
+
+
+
+
@@ -599,7 +644,7 @@ nothing should be put between the original text and the inserted text
20
- 40
+ 0
From 8a3aa64776aaf11c9909a6518c4f0f90f55a2946 Mon Sep 17 00:00:00 2001
From: Charles Haley <>
Date: Tue, 21 Sep 2010 15:53:54 +0100
Subject: [PATCH 069/412] Changed sort to use
field_metadata.search_term_to_field_key.
In the process, refactored field_metadata and LibraryDatabase2 to use the same method names for the same function (in more cases).
---
src/calibre/library/caches.py | 11 ++++-------
src/calibre/library/database2.py | 4 ++--
src/calibre/library/field_metadata.py | 4 ++--
3 files changed, 8 insertions(+), 11 deletions(-)
diff --git a/src/calibre/library/caches.py b/src/calibre/library/caches.py
index 573c1f5797..d310a0e6fe 100644
--- a/src/calibre/library/caches.py
+++ b/src/calibre/library/caches.py
@@ -334,7 +334,7 @@ class ResultCache(SearchQueryParser):
if query and query.strip():
# get metadata key associated with the search term. Eliminates
# dealing with plurals and other aliases
- location = self.field_metadata.search_term_to_key(location.lower().strip())
+ location = self.field_metadata.search_term_to_field_key(location.lower().strip())
if isinstance(location, list):
if allow_recursion:
for loc in location:
@@ -610,12 +610,9 @@ class ResultCache(SearchQueryParser):
# Sorting functions {{{
def sanitize_sort_field_name(self, field):
- field = field.lower().strip()
- if field not in self.field_metadata.iterkeys():
- if field in ('author', 'tag', 'comment'):
- field += 's'
- if field == 'date': field = 'timestamp'
- elif field == 'title': field = 'sort'
+ field = self.field_metadata.search_term_to_field_key(field.lower().strip())
+ # translate some fields to their hidden equivalent
+ if field == 'title': field = 'sort'
elif field == 'authors': field = 'author_sort'
return field
diff --git a/src/calibre/library/database2.py b/src/calibre/library/database2.py
index c1ada94a84..77e3afc8a3 100644
--- a/src/calibre/library/database2.py
+++ b/src/calibre/library/database2.py
@@ -552,10 +552,10 @@ class LibraryDatabase2(LibraryDatabase, SchemaUpgrade, CustomColumns):
return self.field_metadata.sortable_field_keys()
def searchable_fields(self):
- return self.field_metadata.searchable_field_keys()
+ return self.field_metadata.searchable_fields()
def search_term_to_field_key(self, term):
- return self.field_metadata.search_term_to_key(term)
+ return self.field_metadata.search_term_to_field_key(term)
def metadata_for_field(self, key):
return self.field_metadata[key]
diff --git a/src/calibre/library/field_metadata.py b/src/calibre/library/field_metadata.py
index a8031e5172..bac423f46d 100644
--- a/src/calibre/library/field_metadata.py
+++ b/src/calibre/library/field_metadata.py
@@ -501,12 +501,12 @@ class FieldMetadata(dict):
raise ValueError('Attempt to add duplicate search term "%s"'%t)
self._search_term_map[t] = key
- def search_term_to_key(self, term):
+ def search_term_to_field_key(self, term):
if term in self._search_term_map:
return self._search_term_map[term]
return term
- def searchable_field_keys(self):
+ def searchable_fields(self):
return [k for k in self._tb_cats.keys()
if self._tb_cats[k]['kind']=='field' and
len(self._tb_cats[k]['search_terms']) > 0]
From ba6f2f0c5e6cd9943827cda2ccc4a59d32fdbb8b Mon Sep 17 00:00:00 2001
From: Charles Haley <>
Date: Tue, 21 Sep 2010 18:50:21 +0100
Subject: [PATCH 070/412] Take out commit= parameter on set_authors and
set_title. Change other code where necessary
---
src/calibre/gui2/dialogs/metadata_bulk.py | 5 ++++-
src/calibre/library/database2.py | 19 ++++++++-----------
2 files changed, 12 insertions(+), 12 deletions(-)
diff --git a/src/calibre/gui2/dialogs/metadata_bulk.py b/src/calibre/gui2/dialogs/metadata_bulk.py
index 8a692d94d5..18d00191cc 100644
--- a/src/calibre/gui2/dialogs/metadata_bulk.py
+++ b/src/calibre/gui2/dialogs/metadata_bulk.py
@@ -484,7 +484,10 @@ class MetadataBulkDialog(QDialog, Ui_MetadataBulkDialog):
setter = self.db.set_comment
else:
setter = getattr(self.db, 'set_'+dest)
- setter(id, val, notify=False, commit=False)
+ if dest in ['title', 'authors']:
+ setter(id, val, notify=False)
+ else:
+ setter(id, val, notify=False, commit=False)
self.db.commit()
dynamic['s_r_search_mode'] = self.search_mode.currentIndex()
diff --git a/src/calibre/library/database2.py b/src/calibre/library/database2.py
index 77e3afc8a3..1fdacfc09f 100644
--- a/src/calibre/library/database2.py
+++ b/src/calibre/library/database2.py
@@ -407,7 +407,7 @@ class LibraryDatabase2(LibraryDatabase, SchemaUpgrade, CustomColumns):
path = path.lower()
return path
- def set_path(self, index, index_is_id=False, commit=True):
+ def set_path(self, index, index_is_id=False):
'''
Set the path to the directory containing this books files based on its
current title and author. If there was a previous directory, its contents
@@ -447,8 +447,7 @@ class LibraryDatabase2(LibraryDatabase, SchemaUpgrade, CustomColumns):
self.add_format(id, format, stream, index_is_id=True,
path=tpath, notify=False)
self.conn.execute('UPDATE books SET path=? WHERE id=?', (path, id))
- if commit:
- self.conn.commit()
+ self.conn.commit()
self.data.set(id, self.FIELD_MAP['path'], path, row_is_id=True)
# Delete not needed directories
if current_path and os.path.exists(spath):
@@ -1212,7 +1211,7 @@ class LibraryDatabase2(LibraryDatabase, SchemaUpgrade, CustomColumns):
result.append(r)
return ' & '.join(result).replace('|', ',')
- def set_authors(self, id, authors, notify=True, commit=True):
+ def set_authors(self, id, authors, notify=True):
'''
`authors`: A list of authors.
'''
@@ -1240,17 +1239,16 @@ class LibraryDatabase2(LibraryDatabase, SchemaUpgrade, CustomColumns):
ss = self.author_sort_from_book(id, index_is_id=True)
self.conn.execute('UPDATE books SET author_sort=? WHERE id=?',
(ss, id))
- if commit:
- self.conn.commit()
+ self.conn.commit()
self.data.set(id, self.FIELD_MAP['authors'],
','.join([a.replace(',', '|') for a in authors]),
row_is_id=True)
self.data.set(id, self.FIELD_MAP['author_sort'], ss, row_is_id=True)
- self.set_path(id, index_is_id=True, commit=commit)
+ self.set_path(id, index_is_id=True)
if notify:
self.notify('metadata', [id])
- def set_title(self, id, title, notify=True, commit=True):
+ def set_title(self, id, title, notify=True):
if not title:
return
if not isinstance(title, unicode):
@@ -1261,9 +1259,8 @@ class LibraryDatabase2(LibraryDatabase, SchemaUpgrade, CustomColumns):
self.data.set(id, self.FIELD_MAP['sort'], title_sort(title), row_is_id=True)
else:
self.data.set(id, self.FIELD_MAP['sort'], title, row_is_id=True)
- self.set_path(id, index_is_id=True, commit=commit)
- if commit:
- self.conn.commit()
+ self.set_path(id, index_is_id=True)
+ self.conn.commit()
if notify:
self.notify('metadata', [id])
From a62ad5f70cbae026d64d183117a3ec02de59444c Mon Sep 17 00:00:00 2001
From: Charles Haley <>
Date: Tue, 21 Sep 2010 20:20:34 +0100
Subject: [PATCH 071/412] Use one queue object in FunctionDispatch.
Theory: the producer (Qt GUI cover function) exists only once per instance of FunctionDispatcher. This follows from the fact that the dispatcher instance is created on the recipient thread. The consumer (the cover cache) could in theory be multiple threads (but it isn't). Because the items produced by the producer are not equivalent, we need to ensure that the order of items put in the queue by the producer is equal to the order of the requests. To guarantee this order, regardless of the number of consumer threads, we ensure that only one request to the producer can be outstanding.
---
src/calibre/gui2/__init__.py | 11 +++++++----
1 file changed, 7 insertions(+), 4 deletions(-)
diff --git a/src/calibre/gui2/__init__.py b/src/calibre/gui2/__init__.py
index 66e199b8a0..8cfcc17eba 100644
--- a/src/calibre/gui2/__init__.py
+++ b/src/calibre/gui2/__init__.py
@@ -1,7 +1,7 @@
__license__ = 'GPL v3'
__copyright__ = '2008, Kovid Goyal '
""" The GUI """
-import os, sys, Queue
+import os, sys, Queue, threading
from threading import RLock
from PyQt4.Qt import QVariant, QFileInfo, QObject, SIGNAL, QBuffer, Qt, \
@@ -311,11 +311,14 @@ class FunctionDispatcher(QObject):
if not queued:
typ = Qt.AutoConnection if queued is None else Qt.DirectConnection
self.dispatch_signal.connect(self.dispatch, type=typ)
+ self.q = Queue.Queue()
+ self.lock = threading.Lock()
def __call__(self, *args, **kwargs):
- q = Queue.Queue()
- self.dispatch_signal.emit(q, args, kwargs)
- return q.get()
+ with self.lock:
+ self.dispatch_signal.emit(self.q, args, kwargs)
+ res = self.q.get()
+ return res
def dispatch(self, q, args, kwargs):
try:
From 06173bccebb0d8b3df497d0d0df030c1710aa80b Mon Sep 17 00:00:00 2001
From: Kovid Goyal
Date: Tue, 21 Sep 2010 13:45:34 -0600
Subject: [PATCH 072/412] Second beta
---
src/calibre/constants.py | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/src/calibre/constants.py b/src/calibre/constants.py
index 334406e01b..91c114359c 100644
--- a/src/calibre/constants.py
+++ b/src/calibre/constants.py
@@ -2,7 +2,7 @@ __license__ = 'GPL v3'
__copyright__ = '2008, Kovid Goyal kovid@kovidgoyal.net'
__docformat__ = 'restructuredtext en'
__appname__ = 'calibre'
-__version__ = '0.7.900'
+__version__ = '0.7.901'
__author__ = "Kovid Goyal "
import re
From 63f02aa91beaa330a45d8c572cb6e092832b6cb0 Mon Sep 17 00:00:00 2001
From: Kovid Goyal
Date: Tue, 21 Sep 2010 15:19:14 -0600
Subject: [PATCH 073/412] Fix regression in get_metadata for books with no
formats
---
src/calibre/library/database2.py | 6 +++++-
1 file changed, 5 insertions(+), 1 deletion(-)
diff --git a/src/calibre/library/database2.py b/src/calibre/library/database2.py
index 01d46083b2..3e7b932808 100644
--- a/src/calibre/library/database2.py
+++ b/src/calibre/library/database2.py
@@ -590,7 +590,11 @@ class LibraryDatabase2(LibraryDatabase, SchemaUpgrade, CustomColumns):
mi.pubdate = self.pubdate(idx, index_is_id=index_is_id)
mi.uuid = self.uuid(idx, index_is_id=index_is_id)
mi.title_sort = self.title_sort(idx, index_is_id=index_is_id)
- mi.formats = self.formats(idx, index_is_id=index_is_id).split(',')
+ mi.formats = self.formats(idx, index_is_id=index_is_id)
+ if hasattr(mi.formats, 'split'):
+ mi.formats = mi.formats.split(',')
+ else:
+ mi.formats = None
tags = self.tags(idx, index_is_id=index_is_id)
if tags:
mi.tags = [i.strip() for i in tags.split(',')]
From d225fd9ff5c08083d79238f489cd72abbef8b61d Mon Sep 17 00:00:00 2001
From: Kovid Goyal
Date: Tue, 21 Sep 2010 15:22:00 -0600
Subject: [PATCH 074/412] Allow --reinitialize-db to use an SQL dump from
elsewhere
---
src/calibre/debug.py | 47 ++++++++++++++++++++++++++++----------------
1 file changed, 30 insertions(+), 17 deletions(-)
diff --git a/src/calibre/debug.py b/src/calibre/debug.py
index 8a2097ddd1..8cc125b118 100644
--- a/src/calibre/debug.py
+++ b/src/calibre/debug.py
@@ -36,13 +36,17 @@ Run an embedded python interpreter.
'plugin code.')
parser.add_option('--reinitialize-db', default=None,
help='Re-initialize the sqlite calibre database at the '
- 'specified path. Useful to recover from db corruption.')
+ 'specified path. Useful to recover from db corruption.'
+ ' You can also specify the path to an SQL dump which '
+ 'will be used instead of trying to dump the database.'
+ ' This can be useful when dumping fails, but dumping '
+ 'with sqlite3 works.')
parser.add_option('-p', '--py-console', help='Run python console',
default=False, action='store_true')
return parser
-def reinit_db(dbpath, callback=None):
+def reinit_db(dbpath, callback=None, sql_dump=None):
if not os.path.exists(dbpath):
raise ValueError(dbpath + ' does not exist')
from calibre.library.sqlite import connect
@@ -52,26 +56,32 @@ def reinit_db(dbpath, callback=None):
uv = conn.get('PRAGMA user_version;', all=False)
conn.execute('PRAGMA writable_schema=ON')
conn.commit()
- sql_lines = conn.dump()
+ if sql_dump is None:
+ sql_lines = conn.dump()
+ else:
+ sql_lines = open(sql_dump, 'rb').read()
conn.close()
dest = dbpath + '.tmp'
try:
with closing(connect(dest, False)) as nconn:
nconn.execute('create temporary table temp_sequence(id INTEGER PRIMARY KEY AUTOINCREMENT)')
nconn.commit()
- if callable(callback):
- callback(len(sql_lines), True)
- for i, line in enumerate(sql_lines):
- try:
- nconn.execute(line)
- except:
- import traceback
- prints('SQL line %r failed with error:'%line)
- prints(traceback.format_exc())
- continue
- finally:
- if callable(callback):
- callback(i, False)
+ if sql_dump is None:
+ if callable(callback):
+ callback(len(sql_lines), True)
+ for i, line in enumerate(sql_lines):
+ try:
+ nconn.execute(line)
+ except:
+ import traceback
+ prints('SQL line %r failed with error:'%line)
+ prints(traceback.format_exc())
+ continue
+ finally:
+ if callable(callback):
+ callback(i, False)
+ else:
+ nconn.executescript(sql_lines)
nconn.execute('pragma user_version=%d'%int(uv))
nconn.commit()
os.remove(dbpath)
@@ -170,7 +180,10 @@ def main(args=sys.argv):
prints('CALIBRE_EXTENSIONS_PATH='+sys.extensions_location)
prints('CALIBRE_PYTHON_PATH='+os.pathsep.join(sys.path))
elif opts.reinitialize_db is not None:
- reinit_db(opts.reinitialize_db)
+ sql_dump = None
+ if len(args) > 1 and os.access(args[-1], os.R_OK):
+ sql_dump = args[-1]
+ reinit_db(opts.reinitialize_db, sql_dump=sql_dump)
else:
from calibre import ipython
ipython()
From 7f472f742ece9ada0736690c440dc2e9da30cc74 Mon Sep 17 00:00:00 2001
From: Kovid Goyal
Date: Tue, 21 Sep 2010 21:05:23 -0600
Subject: [PATCH 075/412] Styling cleanups
---
src/calibre/utils/pyconsole/formatter.py | 23 ++++++++++++++++++-----
1 file changed, 18 insertions(+), 5 deletions(-)
diff --git a/src/calibre/utils/pyconsole/formatter.py b/src/calibre/utils/pyconsole/formatter.py
index 9409007ec6..6e7d982a82 100644
--- a/src/calibre/utils/pyconsole/formatter.py
+++ b/src/calibre/utils/pyconsole/formatter.py
@@ -8,18 +8,20 @@ __docformat__ = 'restructuredtext en'
from PyQt4.Qt import QTextCharFormat, QFont, QBrush, QColor
from pygments.formatter import Formatter as PF
-from pygments.token import Token, Generic
+from pygments.token import Token, Generic, string_to_tokentype
class Formatter(object):
- def __init__(self, prompt, continuation, **options):
+ def __init__(self, prompt, continuation, style='default'):
if len(prompt) != len(continuation):
raise ValueError('%r does not have the same length as %r' %
(prompt, continuation))
self.prompt, self.continuation = prompt, continuation
+ self.set_style(style)
- pf = PF(**options)
+ def set_style(self, style):
+ pf = PF(style=style)
self.styles = {}
self.normal = self.base_fmt()
self.background_color = pf.style.background_color
@@ -27,6 +29,7 @@ class Formatter(object):
for ttype, ndef in pf.style:
fmt = self.base_fmt()
+ fmt.setProperty(fmt.UserProperty, str(ttype))
if ndef['color']:
fmt.setForeground(QBrush(QColor('#%s'%ndef['color'])))
fmt.setUnderlineColor(QColor('#%s'%ndef['color']))
@@ -49,6 +52,14 @@ class Formatter(object):
QTextEdit { color: %s; background-color: %s }
'''%(self.color, self.background_color)
+ def get_fmt(self, token):
+ if type(token) != type(Token.Generic):
+ token = string_to_tokentype(token)
+ fmt = self.styles.get(token, None)
+ if fmt is None:
+ fmt = self.base_fmt()
+ fmt.setProperty(fmt.UserProperty, str(token))
+ return fmt
def base_fmt(self):
fmt = QTextCharFormat()
@@ -59,7 +70,7 @@ class Formatter(object):
cursor.insertText(raw, self.normal)
def render_syntax_error(self, tb, cursor):
- fmt = self.styles[Token.Error]
+ fmt = self.get_fmt(Token.Error)
cursor.insertText(tb, fmt)
def render(self, tokens, cursor):
@@ -84,7 +95,9 @@ class Formatter(object):
def render_prompt(self, is_continuation, cursor):
pr = self.continuation if is_continuation else self.prompt
- fmt = self.styles[Generic.Prompt]
+ fmt = self.get_fmt(Generic.Prompt)
+ if fmt is None:
+ fmt = self.base_fmt()
cursor.insertText(pr, fmt)
From 35dba964c6241d5600d6aeba4bfcf27d104ee8f9 Mon Sep 17 00:00:00 2001
From: Kovid Goyal
Date: Tue, 21 Sep 2010 21:46:56 -0600
Subject: [PATCH 076/412] Theming support for console via right click menu
---
src/calibre/utils/pyconsole/__init__.py | 14 +++++---
src/calibre/utils/pyconsole/console.py | 44 +++++++++++++++++++++---
src/calibre/utils/pyconsole/formatter.py | 4 ---
3 files changed, 49 insertions(+), 13 deletions(-)
diff --git a/src/calibre/utils/pyconsole/__init__.py b/src/calibre/utils/pyconsole/__init__.py
index 0dfa9398e1..06a7011132 100644
--- a/src/calibre/utils/pyconsole/__init__.py
+++ b/src/calibre/utils/pyconsole/__init__.py
@@ -8,14 +8,18 @@ __docformat__ = 'restructuredtext en'
import sys
from calibre import prints as prints_
-from calibre.utils.config import Config, StringConfig
+from calibre.utils.config import Config, ConfigProxy
-def console_config(defaults=None):
- desc=_('Settings to control the calibre content server')
- c = Config('console', desc) if defaults is None else StringConfig(defaults, desc)
+def console_config():
+ desc='Settings to control the calibre console'
+ c = Config('console', desc)
- c.add_opt('--theme', default='default', help='The color theme')
+ c.add_opt('theme', default='default', help='The color theme')
+
+ return c
+
+prefs = ConfigProxy(console_config())
def prints(*args, **kwargs):
diff --git a/src/calibre/utils/pyconsole/console.py b/src/calibre/utils/pyconsole/console.py
index f741562f03..b0ecce0cb3 100644
--- a/src/calibre/utils/pyconsole/console.py
+++ b/src/calibre/utils/pyconsole/console.py
@@ -6,16 +6,18 @@ __copyright__ = '2010, Kovid Goyal '
__docformat__ = 'restructuredtext en'
import sys, textwrap, traceback, StringIO
+from functools import partial
from PyQt4.Qt import QTextEdit, Qt, QTextFrameFormat, pyqtSignal, \
- QCoreApplication
+ QCoreApplication, QColor, QPalette, QMenu, QActionGroup
from pygments.lexers import PythonLexer, PythonTracebackLexer
+from pygments.styles import get_all_styles
from calibre.constants import __appname__, __version__
from calibre.utils.pyconsole.formatter import Formatter
from calibre.utils.pyconsole.repl import Interpreter, DummyFile
-from calibre.utils.pyconsole import prints
+from calibre.utils.pyconsole import prints, prefs
from calibre.gui2 import error_dialog
class EditBlock(object): # {{{
@@ -47,6 +49,28 @@ class Prepender(object): # {{{
self.console.cursor_pos = self.opos
# }}}
+class ThemeMenu(QMenu):
+
+ def __init__(self, parent):
+ QMenu.__init__(self, _('Choose theme (needs restart)'))
+ parent.addMenu(self)
+ self.group = QActionGroup(self)
+ current = prefs['theme']
+ alls = list(sorted(get_all_styles()))
+ if current not in alls:
+ current = prefs['theme'] = 'default'
+ self.actions = []
+ for style in alls:
+ ac = self.group.addAction(style)
+ if current == style:
+ ac.setChecked(True)
+ self.actions.append(ac)
+ ac.triggered.connect(partial(self.set_theme, style))
+ self.addAction(ac)
+
+ def set_theme(self, style, *args):
+ prefs['theme'] = style
+
class Console(QTextEdit):
@@ -99,8 +123,16 @@ class Console(QTextEdit):
self.doc.setMaximumBlockCount(10000)
self.lexer = PythonLexer(ensurenl=False)
self.tb_lexer = PythonTracebackLexer()
- self.formatter = Formatter(prompt, continuation, style='default')
- self.setStyleSheet(self.formatter.stylesheet)
+
+ self.context_menu = cm = QMenu(self) # {{{
+ cm.theme = ThemeMenu(cm)
+ # }}}
+
+ self.formatter = Formatter(prompt, continuation, style=prefs['theme'])
+ p = QPalette()
+ p.setColor(p.Base, QColor(self.formatter.background_color))
+ p.setColor(p.Text, QColor(self.formatter.color))
+ self.setPalette(p)
self.key_dispatcher = { # {{{
Qt.Key_Enter : self.enter_pressed,
@@ -127,6 +159,10 @@ class Console(QTextEdit):
sys.excepthook = self.unhandled_exception
+ def contextMenuEvent(self, event):
+ self.context_menu.popup(event.globalPos())
+ event.accept()
+
# Prompt management {{{
diff --git a/src/calibre/utils/pyconsole/formatter.py b/src/calibre/utils/pyconsole/formatter.py
index 6e7d982a82..17360fecb3 100644
--- a/src/calibre/utils/pyconsole/formatter.py
+++ b/src/calibre/utils/pyconsole/formatter.py
@@ -48,10 +48,6 @@ class Formatter(object):
self.styles[ttype] = fmt
- self.stylesheet = '''
- QTextEdit { color: %s; background-color: %s }
- '''%(self.color, self.background_color)
-
def get_fmt(self, token):
if type(token) != type(Token.Generic):
token = string_to_tokentype(token)
From e7adf45c01b21a584158728de903837d79f82298 Mon Sep 17 00:00:00 2001
From: Kovid Goyal
Date: Tue, 21 Sep 2010 22:21:36 -0600
Subject: [PATCH 077/412] Restart and context menu added to console
---
src/calibre/utils/pyconsole/__init__.py | 2 +-
src/calibre/utils/pyconsole/console.py | 5 +++--
src/calibre/utils/pyconsole/main.py | 23 ++++++++++++++++++-----
3 files changed, 22 insertions(+), 8 deletions(-)
diff --git a/src/calibre/utils/pyconsole/__init__.py b/src/calibre/utils/pyconsole/__init__.py
index 06a7011132..32eb926143 100644
--- a/src/calibre/utils/pyconsole/__init__.py
+++ b/src/calibre/utils/pyconsole/__init__.py
@@ -15,7 +15,7 @@ def console_config():
desc='Settings to control the calibre console'
c = Config('console', desc)
- c.add_opt('theme', default='default', help='The color theme')
+ c.add_opt('theme', default='native', help='The color theme')
return c
diff --git a/src/calibre/utils/pyconsole/console.py b/src/calibre/utils/pyconsole/console.py
index b0ecce0cb3..164cf4e2ca 100644
--- a/src/calibre/utils/pyconsole/console.py
+++ b/src/calibre/utils/pyconsole/console.py
@@ -49,7 +49,7 @@ class Prepender(object): # {{{
self.console.cursor_pos = self.opos
# }}}
-class ThemeMenu(QMenu):
+class ThemeMenu(QMenu): # {{{
def __init__(self, parent):
QMenu.__init__(self, _('Choose theme (needs restart)'))
@@ -62,6 +62,7 @@ class ThemeMenu(QMenu):
self.actions = []
for style in alls:
ac = self.group.addAction(style)
+ ac.setCheckable(True)
if current == style:
ac.setChecked(True)
self.actions.append(ac)
@@ -71,6 +72,7 @@ class ThemeMenu(QMenu):
def set_theme(self, style, *args):
prefs['theme'] = style
+# }}}
class Console(QTextEdit):
@@ -163,7 +165,6 @@ class Console(QTextEdit):
self.context_menu.popup(event.globalPos())
event.accept()
-
# Prompt management {{{
@dynamic_property
diff --git a/src/calibre/utils/pyconsole/main.py b/src/calibre/utils/pyconsole/main.py
index f098ce2ee2..a5a4b42266 100644
--- a/src/calibre/utils/pyconsole/main.py
+++ b/src/calibre/utils/pyconsole/main.py
@@ -9,7 +9,7 @@ __version__ = '0.1.0'
from functools import partial
from PyQt4.Qt import QDialog, QToolBar, QStatusBar, QLabel, QFont, Qt, \
- QApplication, QIcon, QVBoxLayout
+ QApplication, QIcon, QVBoxLayout, QAction
from calibre.constants import __appname__, __version__
from calibre.utils.pyconsole.console import Console
@@ -19,8 +19,9 @@ class MainWindow(QDialog):
def __init__(self,
default_status_msg=_('Welcome to') + ' ' + __appname__+' console',
parent=None):
-
QDialog.__init__(self, parent)
+
+ self.restart_requested = False
self.l = QVBoxLayout()
self.setLayout(self.l)
@@ -51,14 +52,26 @@ class MainWindow(QDialog):
self.setWindowTitle(__appname__ + ' console')
self.setWindowIcon(QIcon(I('console.png')))
+ self.restart_action = QAction(_('Restart'), self)
+ self.restart_action.setShortcut(_('Ctrl+R'))
+ self.addAction(self.restart_action)
+ self.restart_action.triggered.connect(self.restart)
+ self.console.context_menu.addAction(self.restart_action)
+
+ def restart(self):
+ self.restart_requested = True
+ self.reject()
def main():
QApplication.setApplicationName(__appname__+' console')
QApplication.setOrganizationName('Kovid Goyal')
app = QApplication([])
- m = MainWindow()
- m.show()
- app.exec_()
+ app
+ while True:
+ m = MainWindow()
+ m.exec_()
+ if not m.restart_requested:
+ break
if __name__ == '__main__':
From a41f481ee7d3a72b727c1f6b2d98ae10443cb0d2 Mon Sep 17 00:00:00 2001
From: Kovid Goyal
Date: Tue, 21 Sep 2010 22:28:49 -0600
Subject: [PATCH 078/412] Backspace and delete now work in console
---
src/calibre/utils/pyconsole/console.py | 18 ++++++++++++++++++
1 file changed, 18 insertions(+)
diff --git a/src/calibre/utils/pyconsole/console.py b/src/calibre/utils/pyconsole/console.py
index 164cf4e2ca..aa0ff84d77 100644
--- a/src/calibre/utils/pyconsole/console.py
+++ b/src/calibre/utils/pyconsole/console.py
@@ -143,6 +143,8 @@ class Console(QTextEdit):
Qt.Key_End : self.end_pressed,
Qt.Key_Left : self.left_pressed,
Qt.Key_Right : self.right_pressed,
+ Qt.Key_Backspace : self.backspace_pressed,
+ Qt.Key_Delete : self.delete_pressed,
} # }}}
motd = textwrap.dedent('''\
@@ -327,6 +329,22 @@ class Console(QTextEdit):
self.setTextCursor(c)
self.ensureCursorVisible()
+ def backspace_pressed(self):
+ lineno, pos = self.cursor_pos
+ if lineno < 0: return
+ if pos > self.prompt_len:
+ self.cursor.deletePreviousChar()
+ elif lineno > 0:
+ c = self.cursor
+ c.movePosition(c.Up)
+ c.movePosition(c.EndOfLine)
+ self.setTextCursor(c)
+ self.ensureCursorVisible()
+
+ def delete_pressed(self):
+ self.cursor.deleteChar()
+ self.ensureCursorVisible()
+
def right_pressed(self):
lineno, pos = self.cursor_pos
if lineno < 0: return
From fdd839af7cafa964b26cc9481cc55a7d5b65b16a Mon Sep 17 00:00:00 2001
From: Kovid Goyal
Date: Tue, 21 Sep 2010 22:46:54 -0600
Subject: [PATCH 079/412] Ctrl+Home and Ctrl+End now work
---
src/calibre/utils/pyconsole/console.py | 23 ++++++++++++++++-------
src/calibre/utils/pyconsole/main.py | 2 +-
2 files changed, 17 insertions(+), 8 deletions(-)
diff --git a/src/calibre/utils/pyconsole/console.py b/src/calibre/utils/pyconsole/console.py
index aa0ff84d77..81169140cd 100644
--- a/src/calibre/utils/pyconsole/console.py
+++ b/src/calibre/utils/pyconsole/console.py
@@ -9,7 +9,7 @@ import sys, textwrap, traceback, StringIO
from functools import partial
from PyQt4.Qt import QTextEdit, Qt, QTextFrameFormat, pyqtSignal, \
- QCoreApplication, QColor, QPalette, QMenu, QActionGroup
+ QApplication, QColor, QPalette, QMenu, QActionGroup
from pygments.lexers import PythonLexer, PythonTracebackLexer
from pygments.styles import get_all_styles
@@ -278,7 +278,7 @@ class Console(QTextEdit):
except:
prints(tb, end='')
self.ensureCursorVisible()
- QCoreApplication.processEvents()
+ QApplication.processEvents()
def show_output(self, raw):
def do_show():
@@ -296,7 +296,7 @@ class Console(QTextEdit):
else:
do_show()
self.ensureCursorVisible()
- QCoreApplication.processEvents()
+ QApplication.processEvents()
# }}}
@@ -360,14 +360,23 @@ class Console(QTextEdit):
def home_pressed(self):
if self.prompt_frame is not None:
- c = self.cursor
- c.movePosition(c.StartOfLine)
- c.movePosition(c.NextCharacter, n=self.prompt_len)
- self.setTextCursor(c)
+ mods = QApplication.keyboardModifiers()
+ ctrl = bool(int(mods & Qt.CTRL))
+ if ctrl:
+ self.cursor_pos = (0, self.prompt_len)
+ else:
+ c = self.cursor
+ c.movePosition(c.StartOfLine)
+ c.movePosition(c.NextCharacter, n=self.prompt_len)
+ self.setTextCursor(c)
self.ensureCursorVisible()
def end_pressed(self):
if self.prompt_frame is not None:
+ mods = QApplication.keyboardModifiers()
+ ctrl = bool(int(mods & Qt.CTRL))
+ if ctrl:
+ self.cursor_pos = (len(list(self.prompt()))-1, self.prompt_len)
c = self.cursor
c.movePosition(c.EndOfLine)
self.setTextCursor(c)
diff --git a/src/calibre/utils/pyconsole/main.py b/src/calibre/utils/pyconsole/main.py
index a5a4b42266..a708ca1652 100644
--- a/src/calibre/utils/pyconsole/main.py
+++ b/src/calibre/utils/pyconsole/main.py
@@ -52,7 +52,7 @@ class MainWindow(QDialog):
self.setWindowTitle(__appname__ + ' console')
self.setWindowIcon(QIcon(I('console.png')))
- self.restart_action = QAction(_('Restart'), self)
+ self.restart_action = QAction(_('Restart console'), self)
self.restart_action.setShortcut(_('Ctrl+R'))
self.addAction(self.restart_action)
self.restart_action.triggered.connect(self.restart)
From 7893c01807e401ae6014485e6278ced3a633bb9c Mon Sep 17 00:00:00 2001
From: Charles Haley <>
Date: Wed, 22 Sep 2010 13:22:02 +0100
Subject: [PATCH 080/412] Three changes: 1) make get_metadata return an
unverified list of formats. Avoids a file system operation per format 2)
enhancement request #2845 3) permit composite fields as search/replace source
fields.
---
src/calibre/gui2/dialogs/metadata_bulk.py | 53 +++++++++++++++--------
src/calibre/gui2/dialogs/metadata_bulk.ui | 13 +++++-
src/calibre/library/database2.py | 10 +++--
3 files changed, 55 insertions(+), 21 deletions(-)
diff --git a/src/calibre/gui2/dialogs/metadata_bulk.py b/src/calibre/gui2/dialogs/metadata_bulk.py
index 18d00191cc..fa3b1a9aa7 100644
--- a/src/calibre/gui2/dialogs/metadata_bulk.py
+++ b/src/calibre/gui2/dialogs/metadata_bulk.py
@@ -32,24 +32,30 @@ class Worker(Thread):
remove, add, au, aus, do_aus, rating, pub, do_series, \
do_autonumber, do_remove_format, remove_format, do_swap_ta, \
do_remove_conv, do_auto_author, series, do_series_restart, \
- series_start_value = self.args
+ series_start_value, do_title_case = self.args
# first loop: do author and title. These will commit at the end of each
# operation, because each operation modifies the file system. We want to
# try hard to keep the DB and the file system in sync, even in the face
# of exceptions or forced exits.
for id in self.ids:
+ title_set = False
if do_swap_ta:
title = self.db.title(id, index_is_id=True)
aum = self.db.authors(id, index_is_id=True)
if aum:
aum = [a.strip().replace('|', ',') for a in aum.split(',')]
new_title = authors_to_string(aum)
+ if do_title_case:
+ new_title = new_title.title()
self.db.set_title(id, new_title, notify=False)
+ title_set = True
if title:
new_authors = string_to_authors(title)
self.db.set_authors(id, new_authors, notify=False)
-
+ if do_title_case and not title_set:
+ title = self.db.title(id, index_is_id=True)
+ self.db.set_title(id, title.title(), notify=False)
if au:
self.db.set_authors(id, string_to_authors(au), notify=False)
@@ -182,19 +188,22 @@ class MetadataBulkDialog(QDialog, Ui_MetadataBulkDialog):
self.search_for.initialize('bulk_edit_search_for')
self.replace_with.initialize('bulk_edit_replace_with')
self.test_text.initialize('bulk_edit_test_test')
- fields = ['']
+ self.all_fields = ['']
+ self.writable_fields = ['']
fm = self.db.field_metadata
for f in fm:
if (f in ['author_sort'] or (
- fm[f]['datatype'] == 'text' or fm[f]['datatype'] == 'series')
+ fm[f]['datatype'] in ['text', 'series'])
and fm[f].get('search_terms', None)
and f not in ['formats', 'ondevice']):
- fields.append(f)
- fields.sort()
- self.search_field.addItems(fields)
- self.search_field.setMaxVisibleItems(min(len(fields), 20))
- self.destination_field.addItems(fields)
- self.destination_field.setMaxVisibleItems(min(len(fields), 20))
+ self.all_fields.append(f)
+ self.writable_fields.append(f)
+ if fm[f]['datatype'] == 'composite':
+ self.all_fields.append(f)
+ self.all_fields.sort()
+ self.writable_fields.sort()
+ self.search_field.setMaxVisibleItems(20)
+ self.destination_field.setMaxVisibleItems(20)
offset = 10
self.s_r_number_of_books = min(10, len(self.ids))
for i in range(1,self.s_r_number_of_books+1):
@@ -262,7 +271,7 @@ class MetadataBulkDialog(QDialog, Ui_MetadataBulkDialog):
self.replace_func.addItems(sorted(self.s_r_functions.keys()))
self.search_mode.currentIndexChanged[int].connect(self.s_r_search_mode_changed)
- self.search_field.currentIndexChanged[str].connect(self.s_r_search_field_changed)
+ self.search_field.currentIndexChanged[int].connect(self.s_r_search_field_changed)
self.destination_field.currentIndexChanged[str].connect(self.s_r_destination_field_changed)
self.replace_mode.currentIndexChanged[int].connect(self.s_r_paint_results)
@@ -293,15 +302,18 @@ class MetadataBulkDialog(QDialog, Ui_MetadataBulkDialog):
val = []
return val
- def s_r_search_field_changed(self, txt):
- txt = unicode(txt)
+ def s_r_search_field_changed(self, idx):
for i in range(0, self.s_r_number_of_books):
w = getattr(self, 'book_%d_text'%(i+1))
mi = self.db.get_metadata(self.ids[i], index_is_id=True)
src = unicode(self.search_field.currentText())
t = self.s_r_get_field(mi, src)
w.setText(''.join(t[0:1]))
- self.s_r_paint_results(None)
+
+ if self.search_mode.currentIndex() == 0:
+ self.destination_field.setCurrentIndex(idx)
+ else:
+ self.s_r_paint_results(None)
def s_r_destination_field_changed(self, txt):
txt = unicode(txt)
@@ -314,7 +326,11 @@ class MetadataBulkDialog(QDialog, Ui_MetadataBulkDialog):
self.s_r_paint_results(None)
def s_r_search_mode_changed(self, val):
+ self.search_field.clear()
+ self.destination_field.clear()
if val == 0:
+ self.search_field.addItems(self.writable_fields)
+ self.destination_field.addItems(self.writable_fields)
self.destination_field.setCurrentIndex(0)
self.destination_field.setVisible(False)
self.destination_field_label.setVisible(False)
@@ -324,6 +340,8 @@ class MetadataBulkDialog(QDialog, Ui_MetadataBulkDialog):
self.comma_separated.setVisible(False)
self.s_r_heading.setText('
'+self.main_heading + self.character_heading)
else:
+ self.search_field.addItems(self.all_fields)
+ self.destination_field.addItems(self.writable_fields)
self.destination_field.setVisible(True)
self.destination_field_label.setVisible(True)
self.replace_mode.setVisible(True)
@@ -367,6 +385,8 @@ class MetadataBulkDialog(QDialog, Ui_MetadataBulkDialog):
return ''
dest = unicode(self.destination_field.currentText())
if dest == '':
+ if self.db.metadata_for_field(src)['datatype'] == 'composite':
+ raise Exception(_('You must specify a destination when source is a composite field'))
dest = src
dest_mode = self.replace_mode.currentIndex()
@@ -433,8 +453,6 @@ class MetadataBulkDialog(QDialog, Ui_MetadataBulkDialog):
t = self.s_r_replace_mode_separator().join(t)
wr.setText(t)
except Exception as e:
- import traceback
- traceback.print_exc()
self.s_r_error = e
self.s_r_set_colors()
break
@@ -592,11 +610,12 @@ class MetadataBulkDialog(QDialog, Ui_MetadataBulkDialog):
do_swap_ta = self.swap_title_and_author.isChecked()
do_remove_conv = self.remove_conversion_settings.isChecked()
do_auto_author = self.auto_author_sort.isChecked()
+ do_title_case = self.change_title_to_title_case.isChecked()
args = (remove, add, au, aus, do_aus, rating, pub, do_series,
do_autonumber, do_remove_format, remove_format, do_swap_ta,
do_remove_conv, do_auto_author, series, do_series_restart,
- series_start_value)
+ series_start_value, do_title_case)
bb = BlockingBusy(_('Applying changes to %d books. This may take a while.')
%len(self.ids), parent=self)
diff --git a/src/calibre/gui2/dialogs/metadata_bulk.ui b/src/calibre/gui2/dialogs/metadata_bulk.ui
index 10e22c5df9..e03a59b7ea 100644
--- a/src/calibre/gui2/dialogs/metadata_bulk.ui
+++ b/src/calibre/gui2/dialogs/metadata_bulk.ui
@@ -270,6 +270,17 @@
+
+
+
+ Change title to title case
+
+
+ Force the title to be in title case. If both this and swap authors are checked,
+title and author are swapped before the title case is set
+
+
+
@@ -340,7 +351,7 @@ Future conversion of these books will use the default settings.
-
+ Qt::Vertical
diff --git a/src/calibre/library/database2.py b/src/calibre/library/database2.py
index 3e7b932808..c72e990738 100644
--- a/src/calibre/library/database2.py
+++ b/src/calibre/library/database2.py
@@ -535,7 +535,8 @@ class LibraryDatabase2(LibraryDatabase, SchemaUpgrade, CustomColumns):
### The field-style interface. These use field keys.
def get_field(self, idx, key, default=None, index_is_id=False):
- mi = self.get_metadata(idx, index_is_id=index_is_id, get_cover=True)
+ mi = self.get_metadata(idx, index_is_id=index_is_id,
+ get_cover=key == 'cover')
return mi.get(key, default)
def standard_field_keys(self):
@@ -590,7 +591,8 @@ class LibraryDatabase2(LibraryDatabase, SchemaUpgrade, CustomColumns):
mi.pubdate = self.pubdate(idx, index_is_id=index_is_id)
mi.uuid = self.uuid(idx, index_is_id=index_is_id)
mi.title_sort = self.title_sort(idx, index_is_id=index_is_id)
- mi.formats = self.formats(idx, index_is_id=index_is_id)
+ mi.formats = self.formats(idx, index_is_id=index_is_id,
+ verify_formats=False)
if hasattr(mi.formats, 'split'):
mi.formats = mi.formats.split(',')
else:
@@ -731,7 +733,7 @@ class LibraryDatabase2(LibraryDatabase, SchemaUpgrade, CustomColumns):
return set([])
return set([f[0] for f in formats])
- def formats(self, index, index_is_id=False):
+ def formats(self, index, index_is_id=False, verify_formats=True):
''' Return available formats as a comma separated list or None if there are no available formats '''
id = index if index_is_id else self.id(index)
try:
@@ -739,6 +741,8 @@ class LibraryDatabase2(LibraryDatabase, SchemaUpgrade, CustomColumns):
formats = map(lambda x:x[0], formats)
except:
return None
+ if not verify_formats:
+ return formats
ans = []
for format in formats:
if self.format_abspath(id, format, index_is_id=True) is not None:
From b1250a6db18366d599ea63e27658d7851d1e1f35 Mon Sep 17 00:00:00 2001
From: Charles Haley <>
Date: Wed, 22 Sep 2010 16:43:28 +0100
Subject: [PATCH 081/412] Add prefix and postfix to template values. Syntax:
either '{key}' or '{txt1|key|txt2}'. In the second case, if val[key] is not
empty, then the result is 'txt1' + val[key] + txt2.
---
src/calibre/ebooks/metadata/book/base.py | 18 +++++++++++++-----
src/calibre/gui2/dialogs/metadata_bulk.py | 9 ---------
src/calibre/library/save_to_disk.py | 17 ++++++++++++++++-
3 files changed, 29 insertions(+), 15 deletions(-)
diff --git a/src/calibre/ebooks/metadata/book/base.py b/src/calibre/ebooks/metadata/book/base.py
index 3d6d6b1bb8..d5a86264bf 100644
--- a/src/calibre/ebooks/metadata/book/base.py
+++ b/src/calibre/ebooks/metadata/book/base.py
@@ -14,8 +14,8 @@ from calibre.ebooks.metadata.book import STANDARD_METADATA_FIELDS
from calibre.ebooks.metadata.book import TOP_LEVEL_CLASSIFIERS
from calibre.ebooks.metadata.book import ALL_METADATA_FIELDS
from calibre.library.field_metadata import FieldMetadata
-from calibre.utils.date import isoformat, format_date
+from calibre.utils.date import isoformat, format_date
NULL_VALUES = {
@@ -38,10 +38,17 @@ class SafeFormat(string.Formatter):
Provides a format function that substitutes '' for any missing value
'''
def get_value(self, key, args, mi):
- ign, v = mi.format_field(key, series_with_index=False)
- if v is None:
- return ''
- return v
+ from calibre.library.save_to_disk import explode_string_template_value
+ try:
+ prefix, key, suffix = explode_string_template_value(key)
+ ign, v = mi.format_field(key, series_with_index=False)
+ if v is None:
+ return ''
+ if v is '':
+ return ''
+ return '%s%s%s'%(prefix, v, suffix)
+ except:
+ return key
composite_formatter = SafeFormat()
compress_spaces = re.compile(r'\s+')
@@ -50,6 +57,7 @@ def format_composite(x, mi):
try:
ans = composite_formatter.vformat(x, [], mi).strip()
except:
+ traceback.print_exc()
ans = x
return compress_spaces.sub(' ', ans)
diff --git a/src/calibre/gui2/dialogs/metadata_bulk.py b/src/calibre/gui2/dialogs/metadata_bulk.py
index fa3b1a9aa7..a9e45087fd 100644
--- a/src/calibre/gui2/dialogs/metadata_bulk.py
+++ b/src/calibre/gui2/dialogs/metadata_bulk.py
@@ -122,15 +122,6 @@ class SafeFormat(string.Formatter):
v = ','.join(v)
return v
-composite_formatter = SafeFormat()
-
-def format_composite(x, mi):
- try:
- ans = composite_formatter.vformat(x, [], mi).strip()
- except:
- ans = x
- return ans
-
class MetadataBulkDialog(QDialog, Ui_MetadataBulkDialog):
s_r_functions = { '' : lambda x: x,
diff --git a/src/calibre/library/save_to_disk.py b/src/calibre/library/save_to_disk.py
index fe62dcb7fd..6f7929a072 100644
--- a/src/calibre/library/save_to_disk.py
+++ b/src/calibre/library/save_to_disk.py
@@ -101,15 +101,30 @@ def preprocess_template(template):
template = template.decode(preferred_encoding, 'replace')
return template
+template_value_re = re.compile(r'^([^\|]*(?=\|))(?:\|?)([^\|]*)(?:\|?)((?<=\|).*?)$')
+
+def explode_string_template_value(key):
+ try:
+ matches = template_value_re.match(key)
+ if matches.lastindex != 3:
+ return key
+ return matches.groups()
+ except:
+ return '', key, ''
+
class SafeFormat(string.Formatter):
'''
Provides a format function that substitutes '' for any missing value
'''
def get_value(self, key, args, kwargs):
try:
- return kwargs[key]
+ prefix, key, suffix = explode_string_template_value(key)
+ if kwargs[key]:
+ return '%s%s%s'%(prefix, kwargs[key], suffix)
+ return ''
except:
return ''
+
safe_formatter = SafeFormat()
def safe_format(x, format_args):
From 9112277d00187936e7cc8ffd3bf1eba3b87cfbe7 Mon Sep 17 00:00:00 2001
From: Kovid Goyal
Date: Wed, 22 Sep 2010 09:50:01 -0600
Subject: [PATCH 082/412] ...
---
src/calibre/manual/index.rst | 8 +-------
src/calibre/manual/tutorials.rst | 17 +++++++++++++++++
2 files changed, 18 insertions(+), 7 deletions(-)
create mode 100644 src/calibre/manual/tutorials.rst
diff --git a/src/calibre/manual/index.rst b/src/calibre/manual/index.rst
index 40c260b8b5..d63b0b71a9 100644
--- a/src/calibre/manual/index.rst
+++ b/src/calibre/manual/index.rst
@@ -33,16 +33,10 @@ Sections
conversion
metadata
faq
- xpath
+ tutorials
customize
cli/cli-index
develop
glossary
-.. toctree::
- :hidden:
- :maxdepth: 2
-
- template_lang
- portable
diff --git a/src/calibre/manual/tutorials.rst b/src/calibre/manual/tutorials.rst
new file mode 100644
index 0000000000..d07316deb9
--- /dev/null
+++ b/src/calibre/manual/tutorials.rst
@@ -0,0 +1,17 @@
+
+.. include:: global.rst
+
+.. _tutorials:
+
+Tutorials
+=======================================================
+
+Here you will find tutorials to get you started using |app|'s more advanced features, like XPath and templates.
+
+.. toctree::
+ :maxdepth: 1
+
+ xpath
+ template_lang
+ portable
+
From bfc3e63980dd0fc47601529ca1f224602e10f777 Mon Sep 17 00:00:00 2001
From: Kovid Goyal
Date: Wed, 22 Sep 2010 09:57:33 -0600
Subject: [PATCH 083/412] ...
---
src/calibre/library/save_to_disk.py | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/src/calibre/library/save_to_disk.py b/src/calibre/library/save_to_disk.py
index 6f7929a072..19727deb17 100644
--- a/src/calibre/library/save_to_disk.py
+++ b/src/calibre/library/save_to_disk.py
@@ -120,7 +120,7 @@ class SafeFormat(string.Formatter):
try:
prefix, key, suffix = explode_string_template_value(key)
if kwargs[key]:
- return '%s%s%s'%(prefix, kwargs[key], suffix)
+ return prefix + kwargs[key] + suffix
return ''
except:
return ''
From bd8a206219b69fb92305db8135b28f98021a3b62 Mon Sep 17 00:00:00 2001
From: Charles Haley <>
Date: Wed, 22 Sep 2010 17:10:57 +0100
Subject: [PATCH 084/412] Changes to faq
---
src/calibre/manual/template_lang.rst | 15 ++++++++++++++-
1 file changed, 14 insertions(+), 1 deletion(-)
diff --git a/src/calibre/manual/template_lang.rst b/src/calibre/manual/template_lang.rst
index 541b5da138..780d742eff 100644
--- a/src/calibre/manual/template_lang.rst
+++ b/src/calibre/manual/template_lang.rst
@@ -40,7 +40,20 @@ and if a book does not have a series::
Advanced formatting
----------------------
-You can do more than just simple substitution with the templates. You can also control how the substituted data is formatted. For instance, suppose you wanted to ensure that the series_index is always formatted as three digits with leading zeros. This would do the trick::
+You can do more than just simple substitution with the templates. You can also conditionally include text and control how the substituted data is formatted.
+
+Regarding conditionally including text: there are cases where you might want to have text appear in the output only if a field is not empty. A common case is series and series_index, where you want either nothing or the two values with a hyphen between them. Calibre handles this case using a special field syntax.
+For example, assume you want to use the template
+
+ {series} - {series_index} - {title}
+
+Unfortunately, if the book has no series, the answer will be '- - title'. Many people would rather it be simply 'title', without the hyphens. To do this, use the extended syntax {some_text|field|other_text}. When you use this syntax, if field has the value SERIES then the result will be some_textSERIESother_text. If field has no value, then the result will be the empty string (nothing). Using this syntax, we can solve the above series problem with the template
+
+ {series}{ - |series_index| - }{title}
+
+The hyphens will be included only if the book has a series index. Note: you must either use no | characters or both of them. Using one, such as in {field| - }, is not allowed. It is OK to not provide any text for one side or the other, such as in {|series| - }. Using {|title|} is the same as using {title}.
+
+Now to formatting. Suppose you wanted to ensure that the series_index is always formatted as three digits with leading zeros. This would do the trick::
{series_index:0>3s} - Three digits with leading zeros
From f280cc956fa7b234212ed995fd7497b29b9ac5ea Mon Sep 17 00:00:00 2001
From: Charles Haley <>
Date: Wed, 22 Sep 2010 20:56:08 +0100
Subject: [PATCH 085/412] Fix template bugs introduced by using + instead of
'%s'
---
src/calibre/ebooks/metadata/book/base.py | 4 ++--
src/calibre/library/save_to_disk.py | 5 +++--
2 files changed, 5 insertions(+), 4 deletions(-)
diff --git a/src/calibre/ebooks/metadata/book/base.py b/src/calibre/ebooks/metadata/book/base.py
index 39b9b34174..4e78bf5a48 100644
--- a/src/calibre/ebooks/metadata/book/base.py
+++ b/src/calibre/ebooks/metadata/book/base.py
@@ -43,9 +43,9 @@ class SafeFormat(string.Formatter):
ign, v = mi.format_field(key, series_with_index=False)
if v is None:
return ''
- if v is '':
+ if v == '':
return ''
- return prefix + v + suffix
+ return prefix + unicode(v) + suffix
except:
return key
diff --git a/src/calibre/library/save_to_disk.py b/src/calibre/library/save_to_disk.py
index 19727deb17..90e5413389 100644
--- a/src/calibre/library/save_to_disk.py
+++ b/src/calibre/library/save_to_disk.py
@@ -101,7 +101,8 @@ def preprocess_template(template):
template = template.decode(preferred_encoding, 'replace')
return template
-template_value_re = re.compile(r'^([^\|]*(?=\|))(?:\|?)([^\|]*)(?:\|?)((?<=\|).*?)$')
+template_value_re = re.compile(r'^([^\|]*(?=\|))(?:\|?)([^\|]*)(?:\|?)((?<=\|).*?)$',
+ flags= re.UNICODE)
def explode_string_template_value(key):
try:
@@ -120,7 +121,7 @@ class SafeFormat(string.Formatter):
try:
prefix, key, suffix = explode_string_template_value(key)
if kwargs[key]:
- return prefix + kwargs[key] + suffix
+ return prefix + unicode(kwargs[key]) + suffix
return ''
except:
return ''
From 8a9ae38ebff2cc641d2d661da2d6577db7f0acd0 Mon Sep 17 00:00:00 2001
From: Charles Haley <>
Date: Wed, 22 Sep 2010 21:00:25 +0100
Subject: [PATCH 086/412] Change to fix to make the value unicode in
format_field_extended. This is a more general fix. Note that the orig_field
has not been changed.
---
src/calibre/ebooks/metadata/book/base.py | 6 +++---
1 file changed, 3 insertions(+), 3 deletions(-)
diff --git a/src/calibre/ebooks/metadata/book/base.py b/src/calibre/ebooks/metadata/book/base.py
index 4e78bf5a48..a2b2790ed9 100644
--- a/src/calibre/ebooks/metadata/book/base.py
+++ b/src/calibre/ebooks/metadata/book/base.py
@@ -45,7 +45,7 @@ class SafeFormat(string.Formatter):
return ''
if v == '':
return ''
- return prefix + unicode(v) + suffix
+ return prefix + v + suffix
except:
return key
@@ -444,7 +444,7 @@ class Metadata(object):
res = format_date(res, cmeta['display'].get('date_format','dd MMM yyyy'))
elif datatype == 'bool':
res = _('Yes') if res else _('No')
- return (name, res, orig_res, cmeta)
+ return (name, unicode(res), orig_res, cmeta)
if key in field_metadata and field_metadata[key]['kind'] == 'field':
res = self.get(key, None)
@@ -462,7 +462,7 @@ class Metadata(object):
res = res + ' [%s]'%self.format_series_index()
elif datatype == 'datetime':
res = format_date(res, fmeta['display'].get('date_format','dd MMM yyyy'))
- return (name, res, orig_res, fmeta)
+ return (name, unicode(res), orig_res, fmeta)
return (None, None, None, None)
From 7e233696a1c2bbe3ca4c0469a99540da665fc94f Mon Sep 17 00:00:00 2001
From: Kovid Goyal
Date: Wed, 22 Sep 2010 16:11:53 -0600
Subject: [PATCH 087/412] ...
---
src/calibre/manual/tutorials.rst | 1 +
src/calibre/utils/pyconsole/__init__.py | 4 ++--
src/calibre/utils/pyconsole/main.py | 22 +++++++++++++++++-----
3 files changed, 20 insertions(+), 7 deletions(-)
diff --git a/src/calibre/manual/tutorials.rst b/src/calibre/manual/tutorials.rst
index d07316deb9..1e4cab8493 100644
--- a/src/calibre/manual/tutorials.rst
+++ b/src/calibre/manual/tutorials.rst
@@ -11,6 +11,7 @@ Here you will find tutorials to get you started using |app|'s more advanced feat
.. toctree::
:maxdepth: 1
+ news
xpath
template_lang
portable
diff --git a/src/calibre/utils/pyconsole/__init__.py b/src/calibre/utils/pyconsole/__init__.py
index 32eb926143..cf7e5eab50 100644
--- a/src/calibre/utils/pyconsole/__init__.py
+++ b/src/calibre/utils/pyconsole/__init__.py
@@ -8,7 +8,7 @@ __docformat__ = 'restructuredtext en'
import sys
from calibre import prints as prints_
-from calibre.utils.config import Config, ConfigProxy
+from calibre.utils.config import Config, ConfigProxy, JSONConfig
def console_config():
@@ -20,7 +20,7 @@ def console_config():
return c
prefs = ConfigProxy(console_config())
-
+dynamic = JSONConfig('console')
def prints(*args, **kwargs):
kwargs['file'] = sys.__stdout__
diff --git a/src/calibre/utils/pyconsole/main.py b/src/calibre/utils/pyconsole/main.py
index a708ca1652..4027897ddd 100644
--- a/src/calibre/utils/pyconsole/main.py
+++ b/src/calibre/utils/pyconsole/main.py
@@ -12,6 +12,7 @@ from PyQt4.Qt import QDialog, QToolBar, QStatusBar, QLabel, QFont, Qt, \
QApplication, QIcon, QVBoxLayout, QAction
from calibre.constants import __appname__, __version__
+from calibre.utils.pyconsole import dynamic
from calibre.utils.pyconsole.console import Console
class MainWindow(QDialog):
@@ -26,6 +27,9 @@ class MainWindow(QDialog):
self.setLayout(self.l)
self.resize(800, 600)
+ geom = dynamic.get('console_window_geometry', None)
+ if geom is not None:
+ self.restoreGeometry(geom)
# Setup tool bar {{{
self.tool_bar = QToolBar(self)
@@ -62,17 +66,25 @@ class MainWindow(QDialog):
self.restart_requested = True
self.reject()
-def main():
- QApplication.setApplicationName(__appname__+' console')
- QApplication.setOrganizationName('Kovid Goyal')
- app = QApplication([])
- app
+ def closeEvent(self, *args):
+ dynamic.set('console_window_geometry',
+ bytearray(self.saveGeometry()))
+ return QDialog.closeEvent(self, *args)
+
+
+def show():
while True:
m = MainWindow()
m.exec_()
if not m.restart_requested:
break
+def main():
+ QApplication.setApplicationName(__appname__+' console')
+ QApplication.setOrganizationName('Kovid Goyal')
+ app = QApplication([])
+ app
+ show()
if __name__ == '__main__':
main()
From 69e4cb006545c9db1bc7704c4363a9a111d9c3c1 Mon Sep 17 00:00:00 2001
From: Kovid Goyal
Date: Wed, 22 Sep 2010 17:05:35 -0600
Subject: [PATCH 088/412] ...
---
src/calibre/utils/pyconsole/__init__.py | 3 +++
1 file changed, 3 insertions(+)
diff --git a/src/calibre/utils/pyconsole/__init__.py b/src/calibre/utils/pyconsole/__init__.py
index cf7e5eab50..b3f811faca 100644
--- a/src/calibre/utils/pyconsole/__init__.py
+++ b/src/calibre/utils/pyconsole/__init__.py
@@ -9,6 +9,7 @@ import sys
from calibre import prints as prints_
from calibre.utils.config import Config, ConfigProxy, JSONConfig
+from calibre.utils.ipc.launch import Worker
def console_config():
@@ -26,4 +27,6 @@ def prints(*args, **kwargs):
kwargs['file'] = sys.__stdout__
prints_(*args, **kwargs)
+class Process(Worker):
+ pass
From c5e26ad9d5660aebdc21afc57e0626e83b2c0b92 Mon Sep 17 00:00:00 2001
From: Kovid Goyal
Date: Wed, 22 Sep 2010 21:43:08 -0600
Subject: [PATCH 089/412] Refactor console to run interpreter in separate
process
---
src/calibre/utils/pyconsole/__init__.py | 3 +
src/calibre/utils/pyconsole/console.py | 114 ++++++++-----
src/calibre/utils/pyconsole/controller.py | 125 +++++++++++++++
src/calibre/utils/pyconsole/interpreter.py | 177 +++++++++++++++++++++
src/calibre/utils/pyconsole/main.py | 1 +
src/calibre/utils/pyconsole/repl.py | 67 --------
6 files changed, 381 insertions(+), 106 deletions(-)
create mode 100644 src/calibre/utils/pyconsole/controller.py
create mode 100644 src/calibre/utils/pyconsole/interpreter.py
delete mode 100644 src/calibre/utils/pyconsole/repl.py
diff --git a/src/calibre/utils/pyconsole/__init__.py b/src/calibre/utils/pyconsole/__init__.py
index 3b32a5a9f3..3be9382413 100644
--- a/src/calibre/utils/pyconsole/__init__.py
+++ b/src/calibre/utils/pyconsole/__init__.py
@@ -13,6 +13,9 @@ from calibre.utils.ipc.launch import Worker
from calibre.constants import __appname__, __version__, iswindows
from calibre.gui2 import error_dialog
+# Time to wait for communication to/from the interpreter process
+POLL_TIMEOUT = 0.01 # seconds
+
preferred_encoding, isbytestring, __appname__, __version__, error_dialog, \
iswindows
diff --git a/src/calibre/utils/pyconsole/console.py b/src/calibre/utils/pyconsole/console.py
index 77c8d9a2f6..1acb6e96a9 100644
--- a/src/calibre/utils/pyconsole/console.py
+++ b/src/calibre/utils/pyconsole/console.py
@@ -9,13 +9,13 @@ import sys, textwrap, traceback, StringIO
from functools import partial
from PyQt4.Qt import QTextEdit, Qt, QTextFrameFormat, pyqtSignal, \
- QApplication, QColor, QPalette, QMenu, QActionGroup
+ QApplication, QColor, QPalette, QMenu, QActionGroup, QTimer
from pygments.lexers import PythonLexer, PythonTracebackLexer
from pygments.styles import get_all_styles
from calibre.utils.pyconsole.formatter import Formatter
-from calibre.utils.pyconsole.repl import Interpreter, DummyFile
+from calibre.utils.pyconsole.controller import Controller
from calibre.utils.pyconsole import prints, prefs, __appname__, \
__version__, error_dialog
@@ -113,7 +113,8 @@ class Console(QTextEdit):
continuation='... ',
parent=None):
QTextEdit.__init__(self, parent)
- self.buf = []
+ self.shutting_down = False
+ self.buf = self.old_buf = []
self.prompt_frame = None
self.allow_output = False
self.prompt_frame_format = QTextFrameFormat()
@@ -152,20 +153,80 @@ class Console(QTextEdit):
'''.format(sys.version.splitlines()[0], __appname__,
__version__))
+ self.controllers = []
+ QTimer.singleShot(0, self.launch_controller)
+
+ sys.excepthook = self.unhandled_exception
+
with EditBlock(self.cursor):
self.render_block(motd)
- sys.stdout = sys.stderr = DummyFile(parent=self)
- sys.stdout.write_output.connect(self.show_output)
- self.interpreter = Interpreter(parent=self)
- self.interpreter.show_error.connect(self.show_error)
-
- sys.excepthook = self.unhandled_exception
+ def shutdown(self):
+ self.shutton_down = True
+ for c in self.controllers:
+ c.kill()
def contextMenuEvent(self, event):
self.context_menu.popup(event.globalPos())
event.accept()
+ # Controller management {{{
+ @property
+ def controller(self):
+ return self.controllers[-1]
+
+ def no_controller_error(self):
+ error_dialog(self, _('No interpreter'),
+ _('No active interpreter found. Try restarting the'
+ ' console'), show=True)
+
+ def launch_controller(self, *args):
+ c = Controller(self)
+ c.write_output.connect(self.show_output, type=Qt.QueuedConnection)
+ c.show_error.connect(self.show_error, type=Qt.QueuedConnection)
+ c.interpreter_died.connect(self.interpreter_died,
+ type=Qt.QueuedConnection)
+ c.interpreter_done.connect(self.execution_done)
+ self.controllers.append(c)
+
+ def interpreter_died(self, controller, returncode):
+ if not self.shutting_down and controller.current_command is not None:
+ error_dialog(self, _('Interpreter died'),
+ _('Interpreter dies while excuting a command. To see '
+ 'the command, click Show details'),
+ det_msg=controller.current_command, show=True)
+
+ def execute(self, prompt_lines):
+ c = self.root_frame.lastCursorPosition()
+ self.setTextCursor(c)
+ self.old_prompt_frame = self.prompt_frame
+ self.prompt_frame = None
+ self.old_buf = self.buf
+ self.buf = []
+ self.running.emit()
+ self.controller.runsource('\n'.join(prompt_lines))
+
+ def execution_done(self, controller, ret):
+ if controller is self.controller:
+ self.running_done.emit()
+ if ret: # Incomplete command
+ self.buf = self.old_buf
+ self.prompt_frame = self.old_prompt_frame
+ c = self.prompt_frame.lastCursorPosition()
+ c.insertBlock()
+ self.setTextCursor(c)
+ else: # Command completed
+ try:
+ self.old_prompt_frame.setFrameFormat(QTextFrameFormat())
+ except RuntimeError:
+ # Happens if enough lines of output that the old
+ # frame was deleted
+ pass
+
+ self.render_current_prompt()
+
+ # }}}
+
# Prompt management {{{
@dynamic_property
@@ -264,7 +325,7 @@ class Console(QTextEdit):
if restore_prompt:
self.render_current_prompt()
- def show_error(self, is_syntax_err, tb):
+ def show_error(self, is_syntax_err, tb, controller=None):
if self.prompt_frame is not None:
# At a prompt, so redirect output
return prints(tb, end='')
@@ -279,7 +340,7 @@ class Console(QTextEdit):
self.ensureCursorVisible()
QApplication.processEvents()
- def show_output(self, raw):
+ def show_output(self, raw, which='stdout', controller=None):
def do_show():
try:
self.buf.append(raw)
@@ -384,36 +445,11 @@ class Console(QTextEdit):
def enter_pressed(self):
if self.prompt_frame is None:
return
+ if not self.controller.is_alive:
+ return self.no_controller_error()
cp = list(self.prompt())
if cp[0]:
- c = self.root_frame.lastCursorPosition()
- self.setTextCursor(c)
- old_pf = self.prompt_frame
- self.prompt_frame = None
- oldbuf = self.buf
- self.buf = []
- self.running.emit()
- try:
- ret = self.interpreter.runsource('\n'.join(cp))
- except SystemExit:
- ret = False
- self.show_output('Raising SystemExit not allowed\n')
- self.running_done.emit()
- if ret: # Incomplete command
- self.buf = oldbuf
- self.prompt_frame = old_pf
- c = old_pf.lastCursorPosition()
- c.insertBlock()
- self.setTextCursor(c)
- else: # Command completed
- try:
- old_pf.setFrameFormat(QTextFrameFormat())
- except RuntimeError:
- # Happens if enough lines of output that the old
- # frame was deleted
- pass
-
- self.render_current_prompt()
+ self.execute(cp)
def text_typed(self, text):
if self.prompt_frame is not None:
diff --git a/src/calibre/utils/pyconsole/controller.py b/src/calibre/utils/pyconsole/controller.py
new file mode 100644
index 0000000000..173881d14e
--- /dev/null
+++ b/src/calibre/utils/pyconsole/controller.py
@@ -0,0 +1,125 @@
+#!/usr/bin/env python
+# vim:fileencoding=UTF-8:ts=4:sw=4:sta:et:sts=4:ai
+
+__license__ = 'GPL v3'
+__copyright__ = '2010, Kovid Goyal '
+__docformat__ = 'restructuredtext en'
+
+import os, cPickle, signal, time
+from Queue import Queue, Empty
+from multiprocessing.connection import Listener, arbitrary_address
+from binascii import hexlify
+
+from PyQt4.Qt import QThread, pyqtSignal
+
+from calibre.utils.pyconsole import Process, iswindows, POLL_TIMEOUT
+
+class Controller(QThread):
+
+ # show_error(is_syntax_error, traceback, self)
+ show_error = pyqtSignal(object, object, object)
+ # write_output(unicode_object, stdout or stderr, self)
+ write_output = pyqtSignal(object, object, object)
+ # Indicates interpreter has finished evaluating current command
+ interpreter_done = pyqtSignal(object, object)
+ # interpreter_died(self, returncode or None if no return code available)
+ interpreter_died = pyqtSignal(object, object)
+
+ def __init__(self, parent):
+ QThread.__init__(self, parent)
+ self.keep_going = True
+ self.current_command = None
+
+ self.out_queue = Queue()
+ self.address = arbitrary_address('AF_PIPE' if iswindows else 'AF_UNIX')
+ self.auth_key = os.urandom(32)
+ if iswindows and self.address[1] == ':':
+ self.address = self.address[2:]
+ self.listener = Listener(address=self.address,
+ authkey=self.auth_key, backlog=4)
+
+ self.env = {
+ 'CALIBRE_LAUNCH_INTERPRETER': '1',
+ 'CALIBRE_WORKER_ADDRESS':
+ hexlify(cPickle.dumps(self.listener.address, -1)),
+ 'CALIBRE_WORKER_KEY': hexlify(self.auth_key)
+ }
+ self.process = Process(self.env)
+ self.output_file_buf = self.process(redirect_output=False)
+ self.conn = self.listener.accept()
+ self.start()
+
+ def run(self):
+ while self.keep_going and self.is_alive:
+ try:
+ self.communicate()
+ except KeyboardInterrupt:
+ pass
+ except EOFError:
+ break
+ self.interpreter_died.emit(self, self.returncode)
+ try:
+ self.listener.close()
+ except:
+ pass
+
+ def communicate(self):
+ if self.conn.poll(POLL_TIMEOUT):
+ self.dispatch_incoming_message(self.conn.recv())
+ try:
+ obj = self.out_queue.get_nowait()
+ except Empty:
+ pass
+ else:
+ try:
+ self.conn.send(obj)
+ except:
+ raise EOFError('controller failed to send')
+
+ def dispatch_incoming_message(self, obj):
+ try:
+ cmd, data = obj
+ except:
+ print 'Controller received invalid message'
+ print repr(obj)
+ return
+ if cmd in ('stdout', 'stderr'):
+ self.write_output.emit(data, cmd, self)
+ elif cmd == 'syntaxerror':
+ self.show_error.emit(True, data, self)
+ elif cmd == 'traceback':
+ self.show_error(self, False, data)
+ elif cmd == 'done':
+ self.current_command = None
+ self.interpreter_done.emit(self, data)
+
+ def runsource(self, cmd):
+ self.current_command = cmd
+ self.out_queue.put(('run', cmd))
+
+ def __nonzero__(self):
+ return self.process.is_alive
+
+ @property
+ def returncode(self):
+ return self.process.returncode
+
+ @property
+ def interrupt(self):
+ if hasattr(signal, 'SIGINT'):
+ os.kill(self.process.pid, signal.SIGINT)
+ elif hasattr(signal, 'CTRL_C_EVENT'):
+ os.kill(self.process.pid, signal.CTRL_C_EVENT)
+
+ @property
+ def is_alive(self):
+ return self.process.is_alive
+
+ def kill(self):
+ self.out_queue.put(('quit', 0))
+ t = 0
+ while self.is_alive and t < 10:
+ time.sleep(0.1)
+ self.process.kill()
+ self.keep_going = False
+
diff --git a/src/calibre/utils/pyconsole/interpreter.py b/src/calibre/utils/pyconsole/interpreter.py
new file mode 100644
index 0000000000..6a1aff26c9
--- /dev/null
+++ b/src/calibre/utils/pyconsole/interpreter.py
@@ -0,0 +1,177 @@
+#!/usr/bin/env python
+# vim:fileencoding=UTF-8:ts=4:sw=4:sta:et:sts=4:ai
+
+__license__ = 'GPL v3'
+__copyright__ = '2010, Kovid Goyal '
+__docformat__ = 'restructuredtext en'
+
+import sys, cPickle, os
+from code import InteractiveInterpreter
+from Queue import Queue, Empty
+from threading import Thread
+from binascii import unhexlify
+from multiprocessing.connection import Client
+
+from calibre.utils.pyconsole import preferred_encoding, isbytestring, \
+ POLL_TIMEOUT
+
+'''
+Messages sent by client:
+
+ (stdout, unicode)
+ (stderr, unicode)
+ (syntaxerror, unicode)
+ (traceback, unicode)
+ (done, True iff incomplete command)
+
+Messages that can be received by client:
+ (quit, return code)
+ (run, unicode)
+
+'''
+
+def tounicode(raw): # {{{
+ if isbytestring(raw):
+ try:
+ raw = raw.decode(preferred_encoding, 'replace')
+ except:
+ raw = repr(raw)
+
+ if isbytestring(raw):
+ try:
+ raw.decode('utf-8', 'replace')
+ except:
+ raw = u'Undecodable bytestring'
+ return raw
+# }}}
+
+class DummyFile(object): # {{{
+
+ def __init__(self, what, out_queue):
+ self.closed = False
+ self.name = 'console'
+ self.softspace = 0
+ self.what = what
+ self.out_queue = out_queue
+
+ def flush(self):
+ pass
+
+ def close(self):
+ pass
+
+ def write(self, raw):
+ self.out_queue.put((self.what, tounicode(raw)))
+# }}}
+
+class Comm(Thread): # {{{
+
+ def __init__(self, conn, out_queue, in_queue):
+ Thread.__init__(self)
+ self.daemon = True
+ self.conn = conn
+ self.out_queue = out_queue
+ self.in_queue = in_queue
+ self.keep_going = True
+
+ def run(self):
+ while self.keep_going:
+ try:
+ self.communicate()
+ except KeyboardInterrupt:
+ pass
+ except EOFError:
+ pass
+
+ def communicate(self):
+ if self.conn.poll(POLL_TIMEOUT):
+ try:
+ obj = self.conn.recv()
+ except:
+ pass
+ else:
+ self.in_queue.put(obj)
+ try:
+ obj = self.out_queue.get_nowait()
+ except Empty:
+ pass
+ else:
+ try:
+ self.conn.send(obj)
+ except:
+ raise EOFError('interpreter failed to send')
+# }}}
+
+class Interpreter(InteractiveInterpreter): # {{{
+
+ def __init__(self, queue, local={}):
+ if '__name__' not in local:
+ local['__name__'] = '__console__'
+ if '__doc__' not in local:
+ local['__doc__'] = None
+ self.out_queue = queue
+ sys.stdout = DummyFile('stdout', queue)
+ sys.stderr = DummyFile('sdterr', queue)
+ InteractiveInterpreter.__init__(self, locals=local)
+
+ def showtraceback(self, *args, **kwargs):
+ self.is_syntax_error = False
+ InteractiveInterpreter.showtraceback(self, *args, **kwargs)
+
+ def showsyntaxerror(self, *args, **kwargs):
+ self.is_syntax_error = True
+ InteractiveInterpreter.showsyntaxerror(self, *args, **kwargs)
+
+ def write(self, raw):
+ what = 'syntaxerror' if self.is_syntax_error else 'traceback'
+ self.out_queue.put((what, tounicode(raw)))
+
+# }}}
+
+def connect():
+ os.chdir(os.environ['ORIGWD'])
+ address = cPickle.loads(unhexlify(os.environ['CALIBRE_WORKER_ADDRESS']))
+ key = unhexlify(os.environ['CALIBRE_WORKER_KEY'])
+ return Client(address, authkey=key)
+
+def main():
+ out_queue = Queue()
+ in_queue = Queue()
+ conn = connect()
+ comm = Comm(conn, out_queue, in_queue)
+ comm.start()
+ interpreter = Interpreter(out_queue)
+
+ ret = 0
+
+ while True:
+ try:
+ try:
+ cmd, data = in_queue.get(1)
+ except Empty:
+ pass
+ else:
+ if cmd == 'quit':
+ ret = data
+ comm.keep_going = False
+ comm.join()
+ break
+ elif cmd == 'run':
+ if not comm.is_alive():
+ ret = 1
+ break
+ ret = False
+ try:
+ ret = interpreter.runsource(data)
+ except KeyboardInterrupt:
+ pass
+ except SystemExit:
+ out_queue.put(('stderr', 'SystemExit ignored\n'))
+ out_queue.put(('done', ret))
+ except KeyboardInterrupt:
+ pass
+
+ return ret
+
+if __name__ == '__main__':
+ main()
diff --git a/src/calibre/utils/pyconsole/main.py b/src/calibre/utils/pyconsole/main.py
index a64bc15ec7..664f41ef2e 100644
--- a/src/calibre/utils/pyconsole/main.py
+++ b/src/calibre/utils/pyconsole/main.py
@@ -68,6 +68,7 @@ class MainWindow(QDialog):
def closeEvent(self, *args):
dynamic.set('console_window_geometry',
bytearray(self.saveGeometry()))
+ self.console.shutdown()
return QDialog.closeEvent(self, *args)
diff --git a/src/calibre/utils/pyconsole/repl.py b/src/calibre/utils/pyconsole/repl.py
deleted file mode 100644
index de6262de14..0000000000
--- a/src/calibre/utils/pyconsole/repl.py
+++ /dev/null
@@ -1,67 +0,0 @@
-#!/usr/bin/env python
-# vim:fileencoding=UTF-8:ts=4:sw=4:sta:et:sts=4:ai
-
-__license__ = 'GPL v3'
-__copyright__ = '2010, Kovid Goyal '
-__docformat__ = 'restructuredtext en'
-
-from code import InteractiveInterpreter
-
-from PyQt4.Qt import QObject, pyqtSignal
-
-from calibre import isbytestring
-from calibre.constants import preferred_encoding
-
-class Interpreter(QObject, InteractiveInterpreter):
-
- # show_error(is_syntax_error, traceback)
- show_error = pyqtSignal(object, object)
-
- def __init__(self, local={}, parent=None):
- QObject.__init__(self, parent)
- if '__name__' not in local:
- local['__name__'] = '__console__'
- if '__doc__' not in local:
- local['__doc__'] = None
- InteractiveInterpreter.__init__(self, locals=local)
-
- def showtraceback(self, *args, **kwargs):
- self.is_syntax_error = False
- InteractiveInterpreter.showtraceback(self, *args, **kwargs)
-
- def showsyntaxerror(self, *args, **kwargs):
- self.is_syntax_error = True
- InteractiveInterpreter.showsyntaxerror(self, *args, **kwargs)
-
- def write(self, tb):
- self.show_error.emit(self.is_syntax_error, tb)
-
-class DummyFile(QObject):
-
- # write_output(unicode_object)
- write_output = pyqtSignal(object)
-
- def __init__(self, parent=None):
- QObject.__init__(self, parent)
- self.closed = False
- self.name = 'console'
- self.softspace = 0
-
- def flush(self):
- pass
-
- def close(self):
- pass
-
- def write(self, raw):
- #import sys, traceback
- #print >> sys.__stdout__, 'file,write stack:\n', ''.join(traceback.format_stack())
- if isbytestring(raw):
- try:
- raw = raw.decode(preferred_encoding, 'replace')
- except:
- raw = repr(raw)
- if isbytestring(raw):
- raw = raw.decode(preferred_encoding, 'replace')
- self.write_output.emit(raw)
-
From 950f592b87173daaeb84244560c276adb0cb49f8 Mon Sep 17 00:00:00 2001
From: Kovid Goyal
Date: Wed, 22 Sep 2010 21:48:17 -0600
Subject: [PATCH 090/412] ...
---
src/calibre/utils/pyconsole/console.py | 6 ++++++
1 file changed, 6 insertions(+)
diff --git a/src/calibre/utils/pyconsole/console.py b/src/calibre/utils/pyconsole/console.py
index 1acb6e96a9..2611965345 100644
--- a/src/calibre/utils/pyconsole/console.py
+++ b/src/calibre/utils/pyconsole/console.py
@@ -268,6 +268,11 @@ class Console(QTextEdit):
return property(fget=fget, fset=fset, doc=doc)
+ def move_cursor_to_prompt(self):
+ if self.prompt_frame is not None and self.cursor_pos[0] < 0:
+ c = self.prompt_frame.lastCursorPosition()
+ self.setTextCursor(c)
+
def prompt(self, strip_prompt_strings=True):
if not self.prompt_frame:
yield u'' if strip_prompt_strings else self.formatter.prompt
@@ -453,6 +458,7 @@ class Console(QTextEdit):
def text_typed(self, text):
if self.prompt_frame is not None:
+ self.move_cursor_to_prompt()
self.cursor.insertText(text)
self.render_current_prompt(restore_cursor=True)
From 9b44f557850a7cdbceac98194cc9bda77d76330c Mon Sep 17 00:00:00 2001
From: Kovid Goyal
Date: Wed, 22 Sep 2010 21:54:58 -0600
Subject: [PATCH 091/412] ...
---
src/calibre/utils/pyconsole/controller.py | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/src/calibre/utils/pyconsole/controller.py b/src/calibre/utils/pyconsole/controller.py
index 173881d14e..368e665079 100644
--- a/src/calibre/utils/pyconsole/controller.py
+++ b/src/calibre/utils/pyconsole/controller.py
@@ -88,7 +88,7 @@ class Controller(QThread):
elif cmd == 'syntaxerror':
self.show_error.emit(True, data, self)
elif cmd == 'traceback':
- self.show_error(self, False, data)
+ self.show_error.emit(False, data, self)
elif cmd == 'done':
self.current_command = None
self.interpreter_done.emit(self, data)
From d9e5e74695e82e56b8cc0f61399036ea17a485dd Mon Sep 17 00:00:00 2001
From: Kovid Goyal
Date: Thu, 23 Sep 2010 01:02:03 -0600
Subject: [PATCH 092/412] Console now has history
---
src/calibre/utils/pyconsole/__init__.py | 2 +
src/calibre/utils/pyconsole/console.py | 62 ++++++++++++++++++++--
src/calibre/utils/pyconsole/controller.py | 1 -
src/calibre/utils/pyconsole/interpreter.py | 3 +-
4 files changed, 61 insertions(+), 7 deletions(-)
diff --git a/src/calibre/utils/pyconsole/__init__.py b/src/calibre/utils/pyconsole/__init__.py
index 3be9382413..6ef9f04d4b 100644
--- a/src/calibre/utils/pyconsole/__init__.py
+++ b/src/calibre/utils/pyconsole/__init__.py
@@ -24,6 +24,8 @@ def console_config():
c = Config('console', desc)
c.add_opt('theme', default='native', help='The color theme')
+ c.add_opt('scrollback', default=10000,
+ help='Max number of lines to keep in the scrollback buffer')
return c
diff --git a/src/calibre/utils/pyconsole/console.py b/src/calibre/utils/pyconsole/console.py
index 2611965345..14670fdb59 100644
--- a/src/calibre/utils/pyconsole/console.py
+++ b/src/calibre/utils/pyconsole/console.py
@@ -7,6 +7,7 @@ __docformat__ = 'restructuredtext en'
import sys, textwrap, traceback, StringIO
from functools import partial
+from codeop import CommandCompiler
from PyQt4.Qt import QTextEdit, Qt, QTextFrameFormat, pyqtSignal, \
QApplication, QColor, QPalette, QMenu, QActionGroup, QTimer
@@ -16,8 +17,9 @@ from pygments.styles import get_all_styles
from calibre.utils.pyconsole.formatter import Formatter
from calibre.utils.pyconsole.controller import Controller
+from calibre.utils.pyconsole.history import History
from calibre.utils.pyconsole import prints, prefs, __appname__, \
- __version__, error_dialog
+ __version__, error_dialog, dynamic
class EditBlock(object): # {{{
@@ -73,6 +75,7 @@ class ThemeMenu(QMenu): # {{{
# }}}
+
class Console(QTextEdit):
running = pyqtSignal()
@@ -114,7 +117,9 @@ class Console(QTextEdit):
parent=None):
QTextEdit.__init__(self, parent)
self.shutting_down = False
+ self.compiler = CommandCompiler()
self.buf = self.old_buf = []
+ self.history = History([''], dynamic.get('console_history', []))
self.prompt_frame = None
self.allow_output = False
self.prompt_frame_format = QTextFrameFormat()
@@ -122,7 +127,7 @@ class Console(QTextEdit):
self.prompt_frame_format.setBorderStyle(QTextFrameFormat.BorderStyle_Solid)
self.prompt_len = len(prompt)
- self.doc.setMaximumBlockCount(10000)
+ self.doc.setMaximumBlockCount(int(prefs['scrollback']))
self.lexer = PythonLexer(ensurenl=False)
self.tb_lexer = PythonTracebackLexer()
@@ -139,6 +144,8 @@ class Console(QTextEdit):
self.key_dispatcher = { # {{{
Qt.Key_Enter : self.enter_pressed,
Qt.Key_Return : self.enter_pressed,
+ Qt.Key_Up : self.up_pressed,
+ Qt.Key_Down : self.down_pressed,
Qt.Key_Home : self.home_pressed,
Qt.Key_End : self.end_pressed,
Qt.Key_Left : self.left_pressed,
@@ -153,15 +160,17 @@ class Console(QTextEdit):
'''.format(sys.version.splitlines()[0], __appname__,
__version__))
+ sys.excepthook = self.unhandled_exception
+
self.controllers = []
QTimer.singleShot(0, self.launch_controller)
- sys.excepthook = self.unhandled_exception
with EditBlock(self.cursor):
self.render_block(motd)
def shutdown(self):
+ dynamic.set('console_history', self.history.serialize())
self.shutton_down = True
for c in self.controllers:
c.kill()
@@ -365,7 +374,7 @@ class Console(QTextEdit):
# }}}
- # Keyboard handling {{{
+ # Keyboard management {{{
def keyPressEvent(self, ev):
text = unicode(ev.text())
@@ -394,6 +403,20 @@ class Console(QTextEdit):
self.setTextCursor(c)
self.ensureCursorVisible()
+ def up_pressed(self):
+ lineno, pos = self.cursor_pos
+ if lineno < 0: return
+ if lineno == 0:
+ b = self.history.back()
+ if b is not None:
+ self.set_prompt(b)
+ else:
+ c = self.cursor
+ c.movePosition(c.Up)
+ self.setTextCursor(c)
+ self.ensureCursorVisible()
+
+
def backspace_pressed(self):
lineno, pos = self.cursor_pos
if lineno < 0: return
@@ -414,7 +437,6 @@ class Console(QTextEdit):
lineno, pos = self.cursor_pos
if lineno < 0: return
c = self.cursor
- lineno, pos = self.cursor_pos
cp = list(self.prompt(False))
if pos < len(cp[lineno]):
c.movePosition(c.NextCharacter)
@@ -423,6 +445,22 @@ class Console(QTextEdit):
self.setTextCursor(c)
self.ensureCursorVisible()
+ def down_pressed(self):
+ lineno, pos = self.cursor_pos
+ if lineno < 0: return
+ c = self.cursor
+ cp = list(self.prompt(False))
+ if lineno >= len(cp) - 1:
+ b = self.history.forward()
+ if b is not None:
+ self.set_prompt(b)
+ else:
+ c = self.cursor
+ c.movePosition(c.Down)
+ self.setTextCursor(c)
+ self.ensureCursorVisible()
+
+
def home_pressed(self):
if self.prompt_frame is not None:
mods = QApplication.keyboardModifiers()
@@ -454,6 +492,19 @@ class Console(QTextEdit):
return self.no_controller_error()
cp = list(self.prompt())
if cp[0]:
+ try:
+ ret = self.compiler('\n'.join(cp))
+ except:
+ pass
+ else:
+ if ret is None:
+ c = self.prompt_frame.lastCursorPosition()
+ c.insertBlock()
+ self.setTextCursor(c)
+ self.render_current_prompt()
+ return
+ else:
+ self.history.enter(cp)
self.execute(cp)
def text_typed(self, text):
@@ -461,6 +512,7 @@ class Console(QTextEdit):
self.move_cursor_to_prompt()
self.cursor.insertText(text)
self.render_current_prompt(restore_cursor=True)
+ self.history.current = list(self.prompt())
# }}}
diff --git a/src/calibre/utils/pyconsole/controller.py b/src/calibre/utils/pyconsole/controller.py
index 368e665079..d372cb4ebc 100644
--- a/src/calibre/utils/pyconsole/controller.py
+++ b/src/calibre/utils/pyconsole/controller.py
@@ -104,7 +104,6 @@ class Controller(QThread):
def returncode(self):
return self.process.returncode
- @property
def interrupt(self):
if hasattr(signal, 'SIGINT'):
os.kill(self.process.pid, signal.SIGINT)
diff --git a/src/calibre/utils/pyconsole/interpreter.py b/src/calibre/utils/pyconsole/interpreter.py
index 6a1aff26c9..3cd0d94711 100644
--- a/src/calibre/utils/pyconsole/interpreter.py
+++ b/src/calibre/utils/pyconsole/interpreter.py
@@ -11,6 +11,7 @@ from Queue import Queue, Empty
from threading import Thread
from binascii import unhexlify
from multiprocessing.connection import Client
+from repr import repr as safe_repr
from calibre.utils.pyconsole import preferred_encoding, isbytestring, \
POLL_TIMEOUT
@@ -35,7 +36,7 @@ def tounicode(raw): # {{{
try:
raw = raw.decode(preferred_encoding, 'replace')
except:
- raw = repr(raw)
+ raw = safe_repr(raw)
if isbytestring(raw):
try:
From ea29f4b683ada1c41593ff90664cfa146008be5f Mon Sep 17 00:00:00 2001
From: Charles Haley <>
Date: Thu, 23 Sep 2010 14:36:47 +0100
Subject: [PATCH 093/412] Changes:
1) complete rewrite of composite field processing
-- creation of of formatter class in utils
-- change template validator (prefs/save_template.py) to use new formatting class
-- change save_to_disk to use new formatting class
-- change Metadata class to use new formatting class
-- Check for mutually recursive composite fields
-- change caches.py to use the 'get' interface (now the right one) for composites
2) Add template validation to the base deviceconfig plugin. It checks if the display widget has a 'validate' method, and if so, it calls it.
3) Change models.py so that composite templates can be edited on the library display.
-- back out the changes that set 'editable = False'
4) Fix problem in models.py where book info view was not being updated when a field is changed on library display
5) Changed save_to_disk to permit slashes in field specifications. Did this by splitting the template after template processing. This gives us basic variable folder structures
Example: Simple example: we want the folder structure series/series_index - title. If series does not exist, then the title should be in the top folder.
Template: {series:||/}{series_index:|| - }{title}
6) Change syntax for extended templates
-- prefixes and suffixes have moved to the end of the field specification.
Syntax: {series:|prefix value|suffix value}
You can put a standard python format specification between the : and the first |.
Either zero or two bars must be present.
7) Addition of some built-in functions to template processing. These appear in the format part.
Syntax: {title:uppercase()|prefix value|suffix value}
Functions apply to the value of the field in the format specification.
The functions available are:
-- uppercase(), lowercase(), titlecase(), capitalise()
-- ifempty(text)
If the field is empty, replace it with text.
-- shorten(from start, center string, from end)
Replace the field with a shortened version. The shortened version is found by joining the field's first 'from start' characters, the center string, and the field's last 'from end' characters.
Example: assume that the title is 'Values of beta will give rise to dom'. The field specification
{title:shorten(6,---,6)} will produce the result 'Values---to dom'
-- lookup(key if field not empty, key if field empty)
Replace the value of 'field' with the value of another field. The first field key (lookup name) is used if 'field' is not empty. The second field key is used if field is empty. This, coupled with composite fields and the change to save_to_disk above, facilitates complex variable folder trees on devices.
Example: If a book has a series, then we want the folder structure series/series index - title.fmt. If the book does not have a series, then we want the folder structure genre/author_sort/title.fmt. If the book has no genre, use 'Unknown'. To accomplish this, we:
1) create a composite field named AA containing '{series:||}/{series_index} - {title'.
2) create a composite field named BB containing '{#genre:ifempty(Unknown)}/{author_sort}/{title}
3) set the save template to '{series:lookup(AA,BB)}
---
src/calibre/ebooks/metadata/book/base.py | 50 ++++----
src/calibre/gui2/custom_column_widgets.py | 2 +
.../gui2/device_drivers/configwidget.py | 15 +++
src/calibre/gui2/library/delegates.py | 28 ++++-
src/calibre/gui2/library/models.py | 12 +-
src/calibre/gui2/library/views.py | 6 +-
src/calibre/gui2/preferences/columns.py | 3 +-
.../gui2/preferences/create_custom_column.py | 4 -
src/calibre/gui2/preferences/plugins.py | 6 +-
src/calibre/gui2/preferences/save_template.py | 14 +--
src/calibre/library/caches.py | 2 +-
src/calibre/library/save_to_disk.py | 29 +----
src/calibre/utils/formatter.py | 113 ++++++++++++++++++
13 files changed, 210 insertions(+), 74 deletions(-)
create mode 100644 src/calibre/utils/formatter.py
diff --git a/src/calibre/ebooks/metadata/book/base.py b/src/calibre/ebooks/metadata/book/base.py
index a2b2790ed9..16819cbd39 100644
--- a/src/calibre/ebooks/metadata/book/base.py
+++ b/src/calibre/ebooks/metadata/book/base.py
@@ -5,7 +5,7 @@ __license__ = 'GPL v3'
__copyright__ = '2010, Kovid Goyal '
__docformat__ = 'restructuredtext en'
-import copy, re, string, traceback
+import copy, re, traceback
from calibre import prints
from calibre.ebooks.metadata.book import SC_COPYABLE_FIELDS
@@ -15,6 +15,7 @@ from calibre.ebooks.metadata.book import TOP_LEVEL_CLASSIFIERS
from calibre.ebooks.metadata.book import ALL_METADATA_FIELDS
from calibre.library.field_metadata import FieldMetadata
from calibre.utils.date import isoformat, format_date
+from calibre.utils.formatter import TemplateFormatter
NULL_VALUES = {
@@ -32,33 +33,19 @@ NULL_VALUES = {
field_metadata = FieldMetadata()
-class SafeFormat(string.Formatter):
- '''
- Provides a format function that substitutes '' for any missing value
- '''
+class SafeFormat(TemplateFormatter):
def get_value(self, key, args, mi):
- from calibre.library.save_to_disk import explode_string_template_value
try:
- prefix, key, suffix = explode_string_template_value(key)
ign, v = mi.format_field(key, series_with_index=False)
if v is None:
return ''
if v == '':
return ''
- return prefix + v + suffix
+ return v
except:
return key
composite_formatter = SafeFormat()
-compress_spaces = re.compile(r'\s+')
-
-def format_composite(x, mi):
- try:
- ans = composite_formatter.vformat(x, [], mi).strip()
- except:
- traceback.print_exc()
- ans = x
- return compress_spaces.sub(' ', ans)
class Metadata(object):
@@ -75,7 +62,9 @@ class Metadata(object):
@param authors: List of strings or []
@param other: None or a metadata object
'''
- object.__setattr__(self, '_data', copy.deepcopy(NULL_VALUES))
+ _data = copy.deepcopy(NULL_VALUES)
+ object.__setattr__(self, '_data', _data)
+ _data['_curseq'] = _data['_compseq'] = 0
if other is not None:
self.smart_update(other)
else:
@@ -98,14 +87,28 @@ class Metadata(object):
pass
if field in _data['user_metadata'].iterkeys():
d = _data['user_metadata'][field]
- if d['datatype'] != 'composite':
- return d['#value#']
- return format_composite(d['display']['composite_template'], self)
+ val = d['#value#']
+ if d['datatype'] != 'composite' or \
+ (_data['_curseq'] == _data['_compseq'] and val is not None):
+ return val
+ # Data in the structure has changed. Recompute the composite fields
+ _data['_compseq'] = _data['_curseq']
+ for ck in _data['user_metadata']:
+ cf = _data['user_metadata'][ck]
+ if cf['datatype'] != 'composite':
+ continue
+ cf['#value#'] = 'RECURSIVE_COMPOSITE FIELD ' + field
+ cf['#value#'] = composite_formatter.safe_format(
+ d['display']['composite_template'],
+ self, _('TEMPLATE ERROR')).strip()
+ return d['#value#']
+
raise AttributeError(
'Metadata object has no attribute named: '+ repr(field))
def __setattr__(self, field, val, extra=None):
_data = object.__getattribute__(self, '_data')
+ _data['_curseq'] += 1
if field in TOP_LEVEL_CLASSIFIERS:
_data['classifiers'].update({field: val})
elif field in STANDARD_METADATA_FIELDS:
@@ -193,7 +196,7 @@ class Metadata(object):
if v is not None:
result[attr] = v
for attr in _data['user_metadata'].iterkeys():
- v = _data['user_metadata'][attr]['#value#']
+ v = self.get(attr, None)
if v is not None:
result[attr] = v
if _data['user_metadata'][attr]['datatype'] == 'series':
@@ -466,9 +469,6 @@ class Metadata(object):
return (None, None, None, None)
- def expand_template(self, template):
- return format_composite(template, self)
-
def __unicode__(self):
from calibre.ebooks.metadata import authors_to_string
ans = []
diff --git a/src/calibre/gui2/custom_column_widgets.py b/src/calibre/gui2/custom_column_widgets.py
index d16233be1a..90abfc2474 100644
--- a/src/calibre/gui2/custom_column_widgets.py
+++ b/src/calibre/gui2/custom_column_widgets.py
@@ -351,6 +351,8 @@ def populate_metadata_page(layout, db, book_id, bulk=False, two_column=False, pa
if not x[col]['editable']:
continue
dt = x[col]['datatype']
+ if dt == 'composite':
+ continue
if dt == 'comments':
continue
w = widget_factory(dt, col)
diff --git a/src/calibre/gui2/device_drivers/configwidget.py b/src/calibre/gui2/device_drivers/configwidget.py
index 3d9c9ab2ee..1d6c84ef7c 100644
--- a/src/calibre/gui2/device_drivers/configwidget.py
+++ b/src/calibre/gui2/device_drivers/configwidget.py
@@ -6,7 +6,9 @@ __docformat__ = 'restructuredtext en'
from PyQt4.Qt import QWidget, QListWidgetItem, Qt, QVariant, SIGNAL
+from calibre.gui2 import error_dialog
from calibre.gui2.device_drivers.configwidget_ui import Ui_ConfigWidget
+from calibre.utils.formatter import validation_formatter
class ConfigWidget(QWidget, Ui_ConfigWidget):
@@ -77,3 +79,16 @@ class ConfigWidget(QWidget, Ui_ConfigWidget):
def use_author_sort(self):
return self.opt_use_author_sort.isChecked()
+
+ def validate(self):
+ print 'here in validate'
+ tmpl = unicode(self.opt_save_template.text())
+ try:
+ validation_formatter.validate(tmpl)
+ return True
+ except Exception, err:
+ error_dialog(self, _('Invalid template'),
+ '
'+_('The template %s is invalid:')%tmpl + \
+ ' '+str(err), show=True)
+
+ return False
diff --git a/src/calibre/gui2/library/delegates.py b/src/calibre/gui2/library/delegates.py
index bf233b1175..ceb1cf14a8 100644
--- a/src/calibre/gui2/library/delegates.py
+++ b/src/calibre/gui2/library/delegates.py
@@ -15,10 +15,11 @@ from PyQt4.Qt import QColor, Qt, QModelIndex, QSize, \
QStyledItemDelegate, QCompleter, \
QComboBox
-from calibre.gui2 import UNDEFINED_QDATE
+from calibre.gui2 import UNDEFINED_QDATE, error_dialog
from calibre.gui2.widgets import EnLineEdit, TagsLineEdit
from calibre.utils.date import now, format_date
from calibre.utils.config import tweaks
+from calibre.utils.formatter import validation_formatter
from calibre.gui2.dialogs.comments_dialog import CommentsDialog
class RatingDelegate(QStyledItemDelegate): # {{{
@@ -303,6 +304,31 @@ class CcBoolDelegate(QStyledItemDelegate): # {{{
val = 2 if val is None else 1 if not val else 0
editor.setCurrentIndex(val)
+class CcTemplateDelegate(QStyledItemDelegate): # {{{
+ def __init__(self, parent):
+ '''
+ Delegate for custom_column bool data.
+ '''
+ QStyledItemDelegate.__init__(self, parent)
+
+ def createEditor(self, parent, option, index):
+ return EnLineEdit(parent)
+
+ def setModelData(self, editor, model, index):
+ val = unicode(editor.text())
+ try:
+ validation_formatter.validate(val)
+ except Exception, err:
+ error_dialog(self.parent(), _('Invalid template'),
+ '
'+_('The template %s is invalid:')%val + \
+ ' '+str(err), show=True)
+ model.setData(index, QVariant(val), Qt.EditRole)
+
+ def setEditorData(self, editor, index):
+ m = index.model()
+ val = m.custom_columns[m.column_map[index.column()]]['display']['composite_template']
+ editor.setText(val)
+
# }}}
diff --git a/src/calibre/gui2/library/models.py b/src/calibre/gui2/library/models.py
index 4b1e974b12..fe64a33c47 100644
--- a/src/calibre/gui2/library/models.py
+++ b/src/calibre/gui2/library/models.py
@@ -696,7 +696,8 @@ class BooksModel(QAbstractTableModel): # {{{
return flags
def set_custom_column_data(self, row, colhead, value):
- typ = self.custom_columns[colhead]['datatype']
+ cc = self.custom_columns[colhead]
+ typ = cc['datatype']
label=self.db.field_metadata.key_to_label(colhead)
s_index = None
if typ in ('text', 'comments'):
@@ -722,6 +723,14 @@ class BooksModel(QAbstractTableModel): # {{{
val = qt_to_dt(val, as_utc=False)
elif typ == 'series':
val, s_index = parse_series_string(self.db, label, value.toString())
+ elif typ == 'composite':
+ tmpl = unicode(value.toString()).strip()
+ disp = cc['display']
+ disp['composite_template'] = tmpl
+ self.db.set_custom_column_metadata(cc['colnum'], display = disp)
+ self.refresh(reset=True)
+ return True
+
self.db.set_custom(self.db.id(row), val, extra=s_index,
label=label, num=None, append=False, notify=True)
return True
@@ -768,6 +777,7 @@ class BooksModel(QAbstractTableModel): # {{{
self.db.set_pubdate(id, qt_to_dt(val, as_utc=False))
else:
self.db.set(row, column, val)
+ self.refresh_rows([row], row)
self.dataChanged.emit(index, index)
return True
diff --git a/src/calibre/gui2/library/views.py b/src/calibre/gui2/library/views.py
index d3ead429cf..b113866ecc 100644
--- a/src/calibre/gui2/library/views.py
+++ b/src/calibre/gui2/library/views.py
@@ -13,7 +13,7 @@ from PyQt4.Qt import QTableView, Qt, QAbstractItemView, QMenu, pyqtSignal, \
from calibre.gui2.library.delegates import RatingDelegate, PubDateDelegate, \
TextDelegate, DateDelegate, TagsDelegate, CcTextDelegate, \
- CcBoolDelegate, CcCommentsDelegate, CcDateDelegate
+ CcBoolDelegate, CcCommentsDelegate, CcDateDelegate, CcTemplateDelegate
from calibre.gui2.library.models import BooksModel, DeviceBooksModel
from calibre.utils.config import tweaks, prefs
from calibre.gui2 import error_dialog, gprefs
@@ -47,6 +47,7 @@ class BooksView(QTableView): # {{{
self.cc_text_delegate = CcTextDelegate(self)
self.cc_bool_delegate = CcBoolDelegate(self)
self.cc_comments_delegate = CcCommentsDelegate(self)
+ self.cc_template_delegate = CcTemplateDelegate(self)
self.display_parent = parent
self._model = modelcls(self)
self.setModel(self._model)
@@ -392,8 +393,7 @@ class BooksView(QTableView): # {{{
elif cc['datatype'] == 'rating':
self.setItemDelegateForColumn(cm.index(colhead), self.rating_delegate)
elif cc['datatype'] == 'composite':
- pass
- # no delegate for composite columns, as they are not editable
+ self.setItemDelegateForColumn(cm.index(colhead), self.cc_template_delegate)
else:
dattr = colhead+'_delegate'
delegate = colhead if hasattr(self, dattr) else 'text'
diff --git a/src/calibre/gui2/preferences/columns.py b/src/calibre/gui2/preferences/columns.py
index 761a9880b1..c1b9230f42 100644
--- a/src/calibre/gui2/preferences/columns.py
+++ b/src/calibre/gui2/preferences/columns.py
@@ -155,8 +155,7 @@ class ConfigWidget(ConfigWidgetBase, Ui_Form):
name=self.custcols[c]['name'],
datatype=self.custcols[c]['datatype'],
is_multiple=self.custcols[c]['is_multiple'],
- display = self.custcols[c]['display'],
- editable = self.custcols[c]['editable'])
+ display = self.custcols[c]['display'])
must_restart = True
elif '*deleteme' in self.custcols[c]:
db.delete_custom_column(label=self.custcols[c]['label'])
diff --git a/src/calibre/gui2/preferences/create_custom_column.py b/src/calibre/gui2/preferences/create_custom_column.py
index e88949a23c..bec21270df 100644
--- a/src/calibre/gui2/preferences/create_custom_column.py
+++ b/src/calibre/gui2/preferences/create_custom_column.py
@@ -156,9 +156,6 @@ class CreateCustomColumn(QDialog, Ui_QCreateCustomColumn):
return self.simple_error('', _('You must enter a template for'
' composite columns'))
display_dict = {'composite_template':unicode(self.composite_box.text())}
- is_editable = False
- else:
- is_editable = True
db = self.parent.gui.library_view.model().db
key = db.field_metadata.custom_field_prefix+col
@@ -168,7 +165,6 @@ class CreateCustomColumn(QDialog, Ui_QCreateCustomColumn):
'label':col,
'name':col_heading,
'datatype':col_type,
- 'editable':is_editable,
'display':display_dict,
'normalized':None,
'colnum':None,
diff --git a/src/calibre/gui2/preferences/plugins.py b/src/calibre/gui2/preferences/plugins.py
index a26553db1c..388227e438 100644
--- a/src/calibre/gui2/preferences/plugins.py
+++ b/src/calibre/gui2/preferences/plugins.py
@@ -199,7 +199,11 @@ class ConfigWidget(ConfigWidgetBase, Ui_Form):
config_dialog.exec_()
if config_dialog.result() == QDialog.Accepted:
- plugin.save_settings(config_widget)
+ if hasattr(config_widget, 'validate'):
+ if config_widget.validate():
+ plugin.save_settings(config_widget)
+ else:
+ plugin.save_settings(config_widget)
self._plugin_model.refresh_plugin(plugin)
else:
help_text = plugin.customization_help(gui=True)
diff --git a/src/calibre/gui2/preferences/save_template.py b/src/calibre/gui2/preferences/save_template.py
index 0f48893b69..5b3f0321b2 100644
--- a/src/calibre/gui2/preferences/save_template.py
+++ b/src/calibre/gui2/preferences/save_template.py
@@ -13,17 +13,8 @@ from PyQt4.Qt import QWidget, pyqtSignal
from calibre.gui2 import error_dialog
from calibre.gui2.preferences.save_template_ui import Ui_Form
from calibre.library.save_to_disk import FORMAT_ARG_DESCS, preprocess_template
+from calibre.utils.formatter import validation_formatter
-class ValidateFormat(string.Formatter):
- '''
- Provides a format function that substitutes '' for any missing value
- '''
- def get_value(self, key, args, kwargs):
- return 'this is some text that should be long enough'
-
-validate_formatter = ValidateFormat()
-def validate_format(x, format_args):
- return validate_formatter.vformat(x, [], format_args).strip()
class SaveTemplate(QWidget, Ui_Form):
@@ -62,9 +53,8 @@ class SaveTemplate(QWidget, Ui_Form):
custom fields, because they may or may not exist.
'''
tmpl = preprocess_template(self.opt_template.text())
- fa = {}
try:
- validate_format(tmpl, fa)
+ validation_formatter.validate(tmpl)
except Exception, err:
error_dialog(self, _('Invalid template'),
'
'+_('The template %s is invalid:')%tmpl + \
diff --git a/src/calibre/library/caches.py b/src/calibre/library/caches.py
index 42feb6f8fa..7849eecb2e 100644
--- a/src/calibre/library/caches.py
+++ b/src/calibre/library/caches.py
@@ -546,7 +546,7 @@ class ResultCache(SearchQueryParser):
if len(self.composites) > 0:
mi = db.get_metadata(id, index_is_id=True)
for k,c in self.composites:
- self._data[id][c] = mi.format_field(k)[1]
+ self._data[id][c] = mi.get(k, None)
except IndexError:
return None
try:
diff --git a/src/calibre/library/save_to_disk.py b/src/calibre/library/save_to_disk.py
index 90e5413389..a0f739e4c2 100644
--- a/src/calibre/library/save_to_disk.py
+++ b/src/calibre/library/save_to_disk.py
@@ -9,6 +9,7 @@ __docformat__ = 'restructuredtext en'
import os, traceback, cStringIO, re, string
from calibre.utils.config import Config, StringConfig, tweaks
+from calibre.utils.formatter import TemplateFormatter
from calibre.utils.filenames import shorten_components_to, supports_long_names, \
ascii_filename, sanitize_file_name
from calibre.ebooks.metadata.opf2 import metadata_to_opf
@@ -101,40 +102,20 @@ def preprocess_template(template):
template = template.decode(preferred_encoding, 'replace')
return template
-template_value_re = re.compile(r'^([^\|]*(?=\|))(?:\|?)([^\|]*)(?:\|?)((?<=\|).*?)$',
- flags= re.UNICODE)
-
-def explode_string_template_value(key):
- try:
- matches = template_value_re.match(key)
- if matches.lastindex != 3:
- return key
- return matches.groups()
- except:
- return '', key, ''
-
-class SafeFormat(string.Formatter):
+class SafeFormat(TemplateFormatter):
'''
Provides a format function that substitutes '' for any missing value
'''
def get_value(self, key, args, kwargs):
try:
- prefix, key, suffix = explode_string_template_value(key)
if kwargs[key]:
- return prefix + unicode(kwargs[key]) + suffix
+ return kwargs[key]
return ''
except:
return ''
safe_formatter = SafeFormat()
-def safe_format(x, format_args):
- try:
- ans = safe_formatter.vformat(x, [], format_args).strip()
- except:
- ans = ''
- return re.sub(r'\s+', ' ', ans)
-
def get_components(template, mi, id, timefmt='%b %Y', length=250,
sanitize_func=ascii_filename, replace_whitespace=False,
to_lowercase=False):
@@ -178,8 +159,8 @@ def get_components(template, mi, id, timefmt='%b %Y', length=250,
elif custom_metadata[key]['datatype'] == 'bool':
format_args[key] = _('yes') if format_args[key] else _('no')
- components = [x.strip() for x in template.split('/') if x.strip()]
- components = [safe_format(x, format_args) for x in components]
+ components = safe_formatter.safe_format(template, format_args, '')
+ components = [x.strip() for x in components.split('/') if x.strip()]
components = [sanitize_func(x) for x in components if x]
if not components:
components = [str(id)]
diff --git a/src/calibre/utils/formatter.py b/src/calibre/utils/formatter.py
new file mode 100644
index 0000000000..f9ef4e0846
--- /dev/null
+++ b/src/calibre/utils/formatter.py
@@ -0,0 +1,113 @@
+'''
+Created on 23 Sep 2010
+
+@author: charles
+'''
+
+import re, string
+
+def _lookup(val, mi, field_if_set, field_not_set):
+ if hasattr(mi, 'format_field'):
+ if val:
+ return mi.format_field(field_if_set.strip())[1]
+ else:
+ return mi.format_field(field_not_set.strip())[1]
+ else:
+ if val:
+ return mi.get(field_if_set.strip(), '')
+ else:
+ return mi.get(field_not_set.strip(), '')
+
+def _ifempty(val, mi, value_if_empty):
+ if val:
+ return val
+ else:
+ return value_if_empty
+
+def _shorten(val, mi, leading, center_string, trailing):
+ l = int(leading)
+ t = int(trailing)
+ if len(val) > l + len(center_string) + t:
+ return val[0:l] + center_string + val[-t:]
+ else:
+ return val
+
+class TemplateFormatter(string.Formatter):
+ '''
+ Provides a format function that substitutes '' for any missing value
+ '''
+
+ functions = {
+ 'uppercase' : (0, lambda x: x.upper()),
+ 'lowercase' : (0, lambda x: x.lower()),
+ 'titlecase' : (0, lambda x: x.title()),
+ 'capitalize' : (0, lambda x: x.capitalize()),
+ 'ifempty' : (1, _ifempty),
+ 'lookup' : (2, _lookup),
+ 'shorten' : (3, _shorten),
+ }
+
+ def get_value(self, key, args, mi):
+ raise Exception('get_value must be implemented in the subclass')
+
+ format_string_re = re.compile(r'^(.*)\|(.*)\|(.*)$')
+
+ def _explode_format_string(self, fmt):
+ try:
+ matches = self.format_string_re.match(fmt)
+ if matches is None or matches.lastindex != 3:
+ return fmt, '', ''
+ return matches.groups()
+ except:
+ import traceback
+ traceback.print_exc()
+ return fmt, '', ''
+
+ def format_field(self, val, fmt):
+ fmt, prefix, suffix = self._explode_format_string(fmt)
+
+ p = fmt.find('(')
+ if p >= 0 and fmt[-1] == ')' and fmt[0:p] in self.functions:
+ field = fmt[0:p]
+ func = self.functions[field]
+ args = fmt[p+1:-1].split(',')
+ if (func[0] == 0 and (len(args) != 1 or args[0])) or \
+ (func[0] > 0 and func[0] != len(args)):
+ raise Exception ('Incorrect number of arguments for function '+ fmt[0:p])
+ if func[0] == 0:
+ val = func[1](val, self.mi)
+ else:
+ val = func[1](val, self.mi, *args)
+ else:
+ val = string.Formatter.format_field(self, val, fmt)
+ if not val:
+ return ''
+ return prefix + val + suffix
+
+ compress_spaces = re.compile(r'\s+')
+
+ def vformat(self, fmt, args, kwargs):
+ self.mi = kwargs
+ ans = string.Formatter.vformat(self, fmt, args, kwargs)
+ return self.compress_spaces.sub(' ', ans).strip()
+
+ def safe_format(self, fmt, kwargs, error_value):
+ try:
+ ans = self.vformat(fmt, [], kwargs).strip()
+ except:
+ ans = error_value
+ return ans
+
+class ValidateFormat(TemplateFormatter):
+ '''
+ Provides a format function that substitutes '' for any missing value
+ '''
+ def get_value(self, key, args, kwargs):
+ return 'this is some text that should be long enough'
+
+ def validate(self, x):
+ return self.vformat(x, [], {})
+
+validation_formatter = ValidateFormat()
+
+
From 30c96df50546e9730ad1903ac31e54a05d09f723 Mon Sep 17 00:00:00 2001
From: Kovid Goyal
Date: Thu, 23 Sep 2010 08:13:51 -0600
Subject: [PATCH 094/412] ...
---
src/calibre/utils/pyconsole/history.py | 56 ++++++++++++++++++++++++++
1 file changed, 56 insertions(+)
create mode 100644 src/calibre/utils/pyconsole/history.py
diff --git a/src/calibre/utils/pyconsole/history.py b/src/calibre/utils/pyconsole/history.py
new file mode 100644
index 0000000000..5440e57153
--- /dev/null
+++ b/src/calibre/utils/pyconsole/history.py
@@ -0,0 +1,56 @@
+#!/usr/bin/env python
+# vim:fileencoding=UTF-8:ts=4:sw=4:sta:et:sts=4:ai
+
+__license__ = 'GPL v3'
+__copyright__ = '2010, Kovid Goyal '
+__docformat__ = 'restructuredtext en'
+
+from collections import deque
+
+class History(object): # {{{
+
+ def __init__(self, current, entries):
+ self.entries = deque(entries, maxlen=max(2000, len(entries)))
+ self.index = len(self.entries) - 1
+ self.current = self.default = current
+ self.last_was_back = False
+
+ def back(self, amt=1):
+ if self.entries:
+ oidx = self.index
+ ans = self.entries[self.index]
+ self.index = max(0, self.index - amt)
+ self.last_was_back = self.index != oidx
+ return ans
+
+ def forward(self, amt=1):
+ if self.entries:
+ d = self.index
+ if self.last_was_back:
+ d += 1
+ if d >= len(self.entries) - 1:
+ self.index = len(self.entries) - 1
+ self.last_was_back = False
+ return self.current
+ if self.last_was_back:
+ amt += 1
+ self.index = min(len(self.entries)-1, self.index + amt)
+ self.last_was_back = False
+ return self.entries[self.index]
+
+ def enter(self, x):
+ try:
+ self.entries.remove(x)
+ except ValueError:
+ pass
+ self.entries.append(x)
+ self.index = len(self.entries) - 1
+ self.current = self.default
+ self.last_was_back = False
+
+ def serialize(self):
+ return list(self.entries)
+
+# }}}
+
+
From 36ce8740816958c064a10334df0cb50e36f4784c Mon Sep 17 00:00:00 2001
From: Charles Haley <>
Date: Thu, 23 Sep 2010 17:18:49 +0100
Subject: [PATCH 095/412] Fix db2.get_metadata to handle format correctly (it
is already a list) Fix Metadata to put composite fields back where they
belong
---
src/calibre/ebooks/metadata/book/base.py | 2 +-
src/calibre/library/database2.py | 4 +---
2 files changed, 2 insertions(+), 4 deletions(-)
diff --git a/src/calibre/ebooks/metadata/book/base.py b/src/calibre/ebooks/metadata/book/base.py
index 16819cbd39..2bbe76488e 100644
--- a/src/calibre/ebooks/metadata/book/base.py
+++ b/src/calibre/ebooks/metadata/book/base.py
@@ -99,7 +99,7 @@ class Metadata(object):
continue
cf['#value#'] = 'RECURSIVE_COMPOSITE FIELD ' + field
cf['#value#'] = composite_formatter.safe_format(
- d['display']['composite_template'],
+ cf['display']['composite_template'],
self, _('TEMPLATE ERROR')).strip()
return d['#value#']
diff --git a/src/calibre/library/database2.py b/src/calibre/library/database2.py
index fd5809f937..fde57e2a2e 100644
--- a/src/calibre/library/database2.py
+++ b/src/calibre/library/database2.py
@@ -584,9 +584,7 @@ class LibraryDatabase2(LibraryDatabase, SchemaUpgrade, CustomColumns):
mi.title_sort = self.title_sort(idx, index_is_id=index_is_id)
mi.formats = self.formats(idx, index_is_id=index_is_id,
verify_formats=False)
- if hasattr(mi.formats, 'split'):
- mi.formats = mi.formats.split(',')
- else:
+ if len(mi.formats) == 0:
mi.formats = None
tags = self.tags(idx, index_is_id=index_is_id)
if tags:
From b2d5f740b5268d369941e337c78c5838060caa98 Mon Sep 17 00:00:00 2001
From: Charles Haley <>
Date: Thu, 23 Sep 2010 17:46:46 +0100
Subject: [PATCH 096/412] 1) Put back get_metadata code for format, and fix
format. 2) Ensure that gui editing does an lcase.
---
src/calibre/gui2/library/models.py | 2 +-
src/calibre/library/database2.py | 6 ++++--
2 files changed, 5 insertions(+), 3 deletions(-)
diff --git a/src/calibre/gui2/library/models.py b/src/calibre/gui2/library/models.py
index fe64a33c47..bab2a59b1c 100644
--- a/src/calibre/gui2/library/models.py
+++ b/src/calibre/gui2/library/models.py
@@ -724,7 +724,7 @@ class BooksModel(QAbstractTableModel): # {{{
elif typ == 'series':
val, s_index = parse_series_string(self.db, label, value.toString())
elif typ == 'composite':
- tmpl = unicode(value.toString()).strip()
+ tmpl = unicode(value.toString()).lower().strip()
disp = cc['display']
disp['composite_template'] = tmpl
self.db.set_custom_column_metadata(cc['colnum'], display = disp)
diff --git a/src/calibre/library/database2.py b/src/calibre/library/database2.py
index fde57e2a2e..22de8df41f 100644
--- a/src/calibre/library/database2.py
+++ b/src/calibre/library/database2.py
@@ -584,7 +584,9 @@ class LibraryDatabase2(LibraryDatabase, SchemaUpgrade, CustomColumns):
mi.title_sort = self.title_sort(idx, index_is_id=index_is_id)
mi.formats = self.formats(idx, index_is_id=index_is_id,
verify_formats=False)
- if len(mi.formats) == 0:
+ if hasattr(mi.formats, 'split'):
+ mi.formats = mi.formats.split(',')
+ else:
mi.formats = None
tags = self.tags(idx, index_is_id=index_is_id)
if tags:
@@ -731,7 +733,7 @@ class LibraryDatabase2(LibraryDatabase, SchemaUpgrade, CustomColumns):
except:
return None
if not verify_formats:
- return formats
+ return ','.join(formats)
ans = []
for format in formats:
if self.format_abspath(id, format, index_is_id=True) is not None:
From 232ce4748ddcf03fc21007cc3a1d5ea72e01ce64 Mon Sep 17 00:00:00 2001
From: Charles Haley <>
Date: Thu, 23 Sep 2010 17:59:50 +0100
Subject: [PATCH 097/412] Back out the models 'strip' change
---
src/calibre/ebooks/metadata/book/base.py | 2 +-
src/calibre/gui2/library/models.py | 2 +-
src/calibre/library/save_to_disk.py | 4 ++--
3 files changed, 4 insertions(+), 4 deletions(-)
diff --git a/src/calibre/ebooks/metadata/book/base.py b/src/calibre/ebooks/metadata/book/base.py
index 2bbe76488e..9e1085df25 100644
--- a/src/calibre/ebooks/metadata/book/base.py
+++ b/src/calibre/ebooks/metadata/book/base.py
@@ -36,7 +36,7 @@ field_metadata = FieldMetadata()
class SafeFormat(TemplateFormatter):
def get_value(self, key, args, mi):
try:
- ign, v = mi.format_field(key, series_with_index=False)
+ ign, v = mi.format_field(key.lower(), series_with_index=False)
if v is None:
return ''
if v == '':
diff --git a/src/calibre/gui2/library/models.py b/src/calibre/gui2/library/models.py
index bab2a59b1c..fe64a33c47 100644
--- a/src/calibre/gui2/library/models.py
+++ b/src/calibre/gui2/library/models.py
@@ -724,7 +724,7 @@ class BooksModel(QAbstractTableModel): # {{{
elif typ == 'series':
val, s_index = parse_series_string(self.db, label, value.toString())
elif typ == 'composite':
- tmpl = unicode(value.toString()).lower().strip()
+ tmpl = unicode(value.toString()).strip()
disp = cc['display']
disp['composite_template'] = tmpl
self.db.set_custom_column_metadata(cc['colnum'], display = disp)
diff --git a/src/calibre/library/save_to_disk.py b/src/calibre/library/save_to_disk.py
index a0f739e4c2..11922b7154 100644
--- a/src/calibre/library/save_to_disk.py
+++ b/src/calibre/library/save_to_disk.py
@@ -108,8 +108,8 @@ class SafeFormat(TemplateFormatter):
'''
def get_value(self, key, args, kwargs):
try:
- if kwargs[key]:
- return kwargs[key]
+ if kwargs[key.lower()]:
+ return kwargs[key.lower()]
return ''
except:
return ''
From 1a782eb0ffa9ca7bfc063902b35b06f6e8399271 Mon Sep 17 00:00:00 2001
From: Charles Haley <>
Date: Thu, 23 Sep 2010 20:36:52 +0100
Subject: [PATCH 098/412] - Some cleanups on templates. - Make save_to_disk
templates sanitize all fields except composites
---
src/calibre/ebooks/metadata/book/base.py | 9 ++-
src/calibre/library/save_to_disk.py | 12 ++--
src/calibre/utils/formatter.py | 72 ++++++++++++------------
3 files changed, 51 insertions(+), 42 deletions(-)
diff --git a/src/calibre/ebooks/metadata/book/base.py b/src/calibre/ebooks/metadata/book/base.py
index 9e1085df25..929dc01aec 100644
--- a/src/calibre/ebooks/metadata/book/base.py
+++ b/src/calibre/ebooks/metadata/book/base.py
@@ -34,9 +34,10 @@ NULL_VALUES = {
field_metadata = FieldMetadata()
class SafeFormat(TemplateFormatter):
- def get_value(self, key, args, mi):
+
+ def get_value(self, key, args, kwargs):
try:
- ign, v = mi.format_field(key.lower(), series_with_index=False)
+ ign, v = self.book.format_field(key.lower(), series_with_index=False)
if v is None:
return ''
if v == '':
@@ -100,7 +101,9 @@ class Metadata(object):
cf['#value#'] = 'RECURSIVE_COMPOSITE FIELD ' + field
cf['#value#'] = composite_formatter.safe_format(
cf['display']['composite_template'],
- self, _('TEMPLATE ERROR')).strip()
+ self,
+ _('TEMPLATE ERROR'),
+ self).strip()
return d['#value#']
raise AttributeError(
diff --git a/src/calibre/library/save_to_disk.py b/src/calibre/library/save_to_disk.py
index 11922b7154..2d0a3d1277 100644
--- a/src/calibre/library/save_to_disk.py
+++ b/src/calibre/library/save_to_disk.py
@@ -108,8 +108,12 @@ class SafeFormat(TemplateFormatter):
'''
def get_value(self, key, args, kwargs):
try:
- if kwargs[key.lower()]:
- return kwargs[key.lower()]
+ b = self.book.get_user_metadata(key, False)
+ key = key.lower()
+ if b is not None and b['datatype'] == 'composite':
+ return self.vformat(b['display']['composite_template'], [], kwargs)
+ if kwargs[key]:
+ return self.sanitize(kwargs[key.lower()])
return ''
except:
return ''
@@ -159,9 +163,9 @@ def get_components(template, mi, id, timefmt='%b %Y', length=250,
elif custom_metadata[key]['datatype'] == 'bool':
format_args[key] = _('yes') if format_args[key] else _('no')
- components = safe_formatter.safe_format(template, format_args, '')
+ components = safe_formatter.safe_format(template, format_args, '', mi,
+ sanitize=sanitize_func)
components = [x.strip() for x in components.split('/') if x.strip()]
- components = [sanitize_func(x) for x in components if x]
if not components:
components = [str(id)]
components = [x.encode(filesystem_encoding, 'replace') if isinstance(x,
diff --git a/src/calibre/utils/formatter.py b/src/calibre/utils/formatter.py
index f9ef4e0846..95870d9c61 100644
--- a/src/calibre/utils/formatter.py
+++ b/src/calibre/utils/formatter.py
@@ -6,48 +6,48 @@ Created on 23 Sep 2010
import re, string
-def _lookup(val, mi, field_if_set, field_not_set):
- if hasattr(mi, 'format_field'):
- if val:
- return mi.format_field(field_if_set.strip())[1]
- else:
- return mi.format_field(field_not_set.strip())[1]
- else:
- if val:
- return mi.get(field_if_set.strip(), '')
- else:
- return mi.get(field_not_set.strip(), '')
-
-def _ifempty(val, mi, value_if_empty):
- if val:
- return val
- else:
- return value_if_empty
-
-def _shorten(val, mi, leading, center_string, trailing):
- l = int(leading)
- t = int(trailing)
- if len(val) > l + len(center_string) + t:
- return val[0:l] + center_string + val[-t:]
- else:
- return val
-
class TemplateFormatter(string.Formatter):
'''
Provides a format function that substitutes '' for any missing value
'''
+ def __init__(self):
+ string.Formatter.__init__(self)
+ self.book = None
+ self.kwargs = None
+ self.sanitize = None
+
+ def _lookup(self, val, field_if_set, field_not_set):
+ if val:
+ return self.vformat('{'+field_if_set.strip()+'}', [], self.kwargs)
+ else:
+ return self.vformat('{'+field_not_set.strip()+'}', [], self.kwargs)
+
+ def _ifempty(self, val, value_if_empty):
+ if val:
+ return val
+ else:
+ return value_if_empty
+
+ def _shorten(self, val, leading, center_string, trailing):
+ l = int(leading)
+ t = int(trailing)
+ if len(val) > l + len(center_string) + t:
+ return val[0:l] + center_string + val[-t:]
+ else:
+ return val
+
functions = {
- 'uppercase' : (0, lambda x: x.upper()),
- 'lowercase' : (0, lambda x: x.lower()),
- 'titlecase' : (0, lambda x: x.title()),
- 'capitalize' : (0, lambda x: x.capitalize()),
+ 'uppercase' : (0, lambda s,x: x.upper()),
+ 'lowercase' : (0, lambda s,x: x.lower()),
+ 'titlecase' : (0, lambda s,x: x.title()),
+ 'capitalize' : (0, lambda s,x: x.capitalize()),
'ifempty' : (1, _ifempty),
'lookup' : (2, _lookup),
'shorten' : (3, _shorten),
}
- def get_value(self, key, args, mi):
+ def get_value(self, key, args):
raise Exception('get_value must be implemented in the subclass')
format_string_re = re.compile(r'^(.*)\|(.*)\|(.*)$')
@@ -75,9 +75,9 @@ class TemplateFormatter(string.Formatter):
(func[0] > 0 and func[0] != len(args)):
raise Exception ('Incorrect number of arguments for function '+ fmt[0:p])
if func[0] == 0:
- val = func[1](val, self.mi)
+ val = func[1](self, val)
else:
- val = func[1](val, self.mi, *args)
+ val = func[1](self, val, *args)
else:
val = string.Formatter.format_field(self, val, fmt)
if not val:
@@ -87,11 +87,13 @@ class TemplateFormatter(string.Formatter):
compress_spaces = re.compile(r'\s+')
def vformat(self, fmt, args, kwargs):
- self.mi = kwargs
ans = string.Formatter.vformat(self, fmt, args, kwargs)
return self.compress_spaces.sub(' ', ans).strip()
- def safe_format(self, fmt, kwargs, error_value):
+ def safe_format(self, fmt, kwargs, error_value, book, sanitize=None):
+ self.kwargs = kwargs
+ self.book = book
+ self.sanitize = sanitize
try:
ans = self.vformat(fmt, [], kwargs).strip()
except:
From 1ad0eebd5658c73913dc0ef4b73a95ae0c8960a5 Mon Sep 17 00:00:00 2001
From: Kovid Goyal
Date: Thu, 23 Sep 2010 23:30:16 -0600
Subject: [PATCH 099/412] API for dealing with distributed metadata backup
---
src/calibre/library/custom_columns.py | 5 ++-
src/calibre/library/database2.py | 47 +++++++++++++++++++++++++-
src/calibre/library/schema_upgrades.py | 12 +++++++
3 files changed, 62 insertions(+), 2 deletions(-)
diff --git a/src/calibre/library/custom_columns.py b/src/calibre/library/custom_columns.py
index d74024280e..2d8634659b 100644
--- a/src/calibre/library/custom_columns.py
+++ b/src/calibre/library/custom_columns.py
@@ -382,6 +382,7 @@ class CustomColumns(object):
)
# get rid of the temp tables
self.conn.executescript(drops)
+ self.dirtied(ids, commit=False)
self.conn.commit()
# set the in-memory copies of the tags
@@ -402,19 +403,21 @@ class CustomColumns(object):
same length as ids.
'''
if extras is not None and len(extras) != len(ids):
- raise ValueError('Lentgh of ids and extras is not the same')
+ raise ValueError('Length of ids and extras is not the same')
ev = None
for idx,id in enumerate(ids):
if extras is not None:
ev = extras[idx]
self._set_custom(id, val, label=label, num=num, append=append,
notify=notify, extra=ev)
+ self.dirtied(ids, commit=False)
self.conn.commit()
def set_custom(self, id, val, label=None, num=None,
append=False, notify=True, extra=None, commit=True):
self._set_custom(id, val, label=label, num=num, append=append,
notify=notify, extra=extra)
+ self.dirtied([id], commit=False)
if commit:
self.conn.commit()
diff --git a/src/calibre/library/database2.py b/src/calibre/library/database2.py
index 1fe77077b9..7a8aef541d 100644
--- a/src/calibre/library/database2.py
+++ b/src/calibre/library/database2.py
@@ -13,6 +13,7 @@ from math import floor
from PyQt4.QtGui import QImage
from calibre.ebooks.metadata import title_sort, author_to_author_sort
+from calibre.ebooks.metadata.opf2 import metadata_to_opf
from calibre.library.database import LibraryDatabase
from calibre.library.field_metadata import FieldMetadata, TagsIcons
from calibre.library.schema_upgrades import SchemaUpgrade
@@ -126,6 +127,7 @@ class LibraryDatabase2(LibraryDatabase, SchemaUpgrade, CustomColumns):
def __init__(self, library_path, row_factory=False):
self.field_metadata = FieldMetadata()
+ self.dirtied_cache = set([])
if not os.path.exists(library_path):
os.makedirs(library_path)
self.listeners = set([])
@@ -337,6 +339,9 @@ class LibraryDatabase2(LibraryDatabase, SchemaUpgrade, CustomColumns):
setattr(self, 'title_sort', functools.partial(self.get_property,
loc=self.FIELD_MAP['sort']))
+ d = self.conn.get('SELECT book FROM metadata_dirtied', all=True)
+ self.dirtied_cache.update(set([x[0] for x in d]))
+
self.refresh_ondevice = functools.partial(self.data.refresh_ondevice, self)
self.refresh()
self.last_update_check = self.last_modified()
@@ -550,6 +555,33 @@ class LibraryDatabase2(LibraryDatabase, SchemaUpgrade, CustomColumns):
def metadata_for_field(self, key):
return self.field_metadata[key]
+ def dump_metadata(self, book_ids, remove_from_dirtied=True, commit=True):
+ for book_id in book_ids:
+ mi = self.get_metadata(book_id, index_is_id=True, get_cover=True)
+ # Always set cover to cover.jpg. Even if cover doesn't exist,
+ # no harm done. This way no need to call dirtied when
+ # cover is set/removed
+ mi.cover = 'cover.jpg'
+ raw = metadata_to_opf(mi)
+ path = self.abspath(book_id, index_is_id=True)
+ with open(os.path.join(path, 'metadata.opf'), 'wb') as f:
+ f.write(raw)
+ if remove_from_dirtied:
+ self.conn.execute('DELETE FROM metadata_dirtied WHERE book=?',
+ (book_id,))
+ if book_id in self.dirtied_cache:
+ self.dirtied_cache.remove(book_id)
+ if commit:
+ self.conn.commit()
+
+ def dirtied(self, book_ids, commit=True):
+ self.conn.executemany(
+ 'INSERT OR REPLACE INTO metadata_dirtied VALUES (?)',
+ [(x,) for x in book_ids])
+ if commit:
+ self.conn.commit()
+ self.dirtied.update(set(book_ids))
+
def get_metadata(self, idx, index_is_id=False, get_cover=False):
'''
Convenience method to return metadata as a :class:`Metadata` object.
@@ -583,7 +615,7 @@ class LibraryDatabase2(LibraryDatabase, SchemaUpgrade, CustomColumns):
mi.uuid = self.uuid(idx, index_is_id=index_is_id)
mi.title_sort = self.title_sort(idx, index_is_id=index_is_id)
mi.formats = self.formats(idx, index_is_id=index_is_id,
- verify_formats=False)
+ verify_formats=False)
if hasattr(mi.formats, 'split'):
mi.formats = mi.formats.split(',')
else:
@@ -1242,6 +1274,7 @@ class LibraryDatabase2(LibraryDatabase, SchemaUpgrade, CustomColumns):
ss = self.author_sort_from_book(id, index_is_id=True)
self.conn.execute('UPDATE books SET author_sort=? WHERE id=?',
(ss, id))
+ self.dirtied([id], commit=False)
if commit:
self.conn.commit()
self.data.set(id, self.FIELD_MAP['authors'],
@@ -1268,6 +1301,7 @@ class LibraryDatabase2(LibraryDatabase, SchemaUpgrade, CustomColumns):
else:
self.data.set(id, self.FIELD_MAP['sort'], title, row_is_id=True)
self.set_path(id, index_is_id=True)
+ self.dirtied([id], commit=False)
if commit:
self.conn.commit()
if notify:
@@ -1277,6 +1311,7 @@ class LibraryDatabase2(LibraryDatabase, SchemaUpgrade, CustomColumns):
if dt:
self.conn.execute('UPDATE books SET timestamp=? WHERE id=?', (dt, id))
self.data.set(id, self.FIELD_MAP['timestamp'], dt, row_is_id=True)
+ self.dirtied([id], commit=False)
if commit:
self.conn.commit()
if notify:
@@ -1286,6 +1321,7 @@ class LibraryDatabase2(LibraryDatabase, SchemaUpgrade, CustomColumns):
if dt:
self.conn.execute('UPDATE books SET pubdate=? WHERE id=?', (dt, id))
self.data.set(id, self.FIELD_MAP['pubdate'], dt, row_is_id=True)
+ self.dirtied([id], commit=False)
if commit:
self.conn.commit()
if notify:
@@ -1304,6 +1340,7 @@ class LibraryDatabase2(LibraryDatabase, SchemaUpgrade, CustomColumns):
else:
aid = self.conn.execute('INSERT INTO publishers(name) VALUES (?)', (publisher,)).lastrowid
self.conn.execute('INSERT INTO books_publishers_link(book, publisher) VALUES (?,?)', (id, aid))
+ self.dirtied([id], commit=False)
if commit:
self.conn.commit()
self.data.set(id, self.FIELD_MAP['publisher'], publisher, row_is_id=True)
@@ -1594,6 +1631,7 @@ class LibraryDatabase2(LibraryDatabase, SchemaUpgrade, CustomColumns):
'''.format(tables[0], tables[1])
)
self.conn.executescript(drops)
+ self.dirtied(ids, commit=False)
self.conn.commit()
for x in ids:
@@ -1639,6 +1677,7 @@ class LibraryDatabase2(LibraryDatabase, SchemaUpgrade, CustomColumns):
(id, tid), all=False):
self.conn.execute('INSERT INTO books_tags_link(book, tag) VALUES (?,?)',
(id, tid))
+ self.dirtied([id], commit=False)
if commit:
self.conn.commit()
tags = u','.join(self.get_tags(id))
@@ -1693,6 +1732,7 @@ class LibraryDatabase2(LibraryDatabase, SchemaUpgrade, CustomColumns):
else:
aid = self.conn.execute('INSERT INTO series(name) VALUES (?)', (series,)).lastrowid
self.conn.execute('INSERT INTO books_series_link(book, series) VALUES (?,?)', (id, aid))
+ self.dirtied([id], commit=False)
if commit:
self.conn.commit()
self.data.set(id, self.FIELD_MAP['series'], series, row_is_id=True)
@@ -1707,6 +1747,7 @@ class LibraryDatabase2(LibraryDatabase, SchemaUpgrade, CustomColumns):
except:
idx = 1.0
self.conn.execute('UPDATE books SET series_index=? WHERE id=?', (idx, id))
+ self.dirtied([id], commit=False)
if commit:
self.conn.commit()
self.data.set(id, self.FIELD_MAP['series_index'], idx, row_is_id=True)
@@ -1719,6 +1760,7 @@ class LibraryDatabase2(LibraryDatabase, SchemaUpgrade, CustomColumns):
rat = self.conn.get('SELECT id FROM ratings WHERE rating=?', (rating,), all=False)
rat = rat if rat else self.conn.execute('INSERT INTO ratings(rating) VALUES (?)', (rating,)).lastrowid
self.conn.execute('INSERT INTO books_ratings_link(book, rating) VALUES (?,?)', (id, rat))
+ self.dirtied([id], commit=False)
if commit:
self.conn.commit()
self.data.set(id, self.FIELD_MAP['rating'], rating, row_is_id=True)
@@ -1731,11 +1773,13 @@ class LibraryDatabase2(LibraryDatabase, SchemaUpgrade, CustomColumns):
if commit:
self.conn.commit()
self.data.set(id, self.FIELD_MAP['comments'], text, row_is_id=True)
+ self.dirtied([id], commit=False)
if notify:
self.notify('metadata', [id])
def set_author_sort(self, id, sort, notify=True, commit=True):
self.conn.execute('UPDATE books SET author_sort=? WHERE id=?', (sort, id))
+ self.dirtied([id], commit=False)
if commit:
self.conn.commit()
self.data.set(id, self.FIELD_MAP['author_sort'], sort, row_is_id=True)
@@ -1744,6 +1788,7 @@ class LibraryDatabase2(LibraryDatabase, SchemaUpgrade, CustomColumns):
def set_isbn(self, id, isbn, notify=True, commit=True):
self.conn.execute('UPDATE books SET isbn=? WHERE id=?', (isbn, id))
+ self.dirtied([id], commit=False)
if commit:
self.conn.commit()
self.data.set(id, self.FIELD_MAP['isbn'], isbn, row_is_id=True)
diff --git a/src/calibre/library/schema_upgrades.py b/src/calibre/library/schema_upgrades.py
index b08161abf2..167cc0a327 100644
--- a/src/calibre/library/schema_upgrades.py
+++ b/src/calibre/library/schema_upgrades.py
@@ -397,3 +397,15 @@ class SchemaUpgrade(object):
UNIQUE(key));
'''
self.conn.executescript(script)
+
+ def upgrade_version_13(self):
+ 'Dirtied table for OPF metadata backups'
+ script = '''
+ DROP TABLE IF EXISTS metadata_dirtied;
+ CREATE TABLE metadata_dirtied(id INTEGER PRIMARY KEY,
+ book INTEGER NOT NULL,
+ UNIQUE(book));
+ INSERT INTO metadata_dirtied (book) SELECT id FROM books;
+ '''
+ self.conn.executescript(script)
+
From f46d919c751dbccb24f21c348ca130833ffc5a7a Mon Sep 17 00:00:00 2001
From: Kovid Goyal
Date: Thu, 23 Sep 2010 23:50:22 -0600
Subject: [PATCH 100/412] Add thread to GUI for distributed metadata backup
---
src/calibre/gui2/library/models.py | 5 ++++-
src/calibre/gui2/ui.py | 4 ++++
src/calibre/library/caches.py | 30 ++++++++++++++++++++++++++++--
src/calibre/library/database2.py | 15 +++++++++------
4 files changed, 45 insertions(+), 9 deletions(-)
diff --git a/src/calibre/gui2/library/models.py b/src/calibre/gui2/library/models.py
index fe64a33c47..9d9de358c8 100644
--- a/src/calibre/gui2/library/models.py
+++ b/src/calibre/gui2/library/models.py
@@ -21,7 +21,7 @@ from calibre.utils.date import dt_factory, qt_to_dt, isoformat
from calibre.ebooks.metadata.meta import set_metadata as _set_metadata
from calibre.utils.search_query_parser import SearchQueryParser
from calibre.library.caches import _match, CONTAINS_MATCH, EQUALS_MATCH, \
- REGEXP_MATCH, CoverCache
+ REGEXP_MATCH, CoverCache, MetadataBackup
from calibre.library.cli import parse_series_string
from calibre import strftime, isbytestring, prepare_string_for_xml
from calibre.constants import filesystem_encoding
@@ -153,6 +153,9 @@ class BooksModel(QAbstractTableModel): # {{{
self.cover_cache.stop()
self.cover_cache = CoverCache(db, FunctionDispatcher(self.db.cover))
self.cover_cache.start()
+ self.metadata_backup = MetadataBackup(db,
+ FunctionDispatcher(self.db.dump_metadata))
+ self.metadata_backup.start()
def refresh_cover(event, ids):
if event == 'cover' and self.cover_cache is not None:
self.cover_cache.refresh(ids)
diff --git a/src/calibre/gui2/ui.py b/src/calibre/gui2/ui.py
index 9bc504a001..88a8c68572 100644
--- a/src/calibre/gui2/ui.py
+++ b/src/calibre/gui2/ui.py
@@ -551,6 +551,10 @@ class Main(MainWindow, MainWindowMixin, DeviceMixin, # {{{
cc = self.library_view.model().cover_cache
if cc is not None:
cc.stop()
+ mb = self.library_view.model().metadata_backup
+ if mb is not None:
+ mb.stop()
+
self.hide_windows()
self.emailer.stop()
try:
diff --git a/src/calibre/library/caches.py b/src/calibre/library/caches.py
index 7849eecb2e..2d37314896 100644
--- a/src/calibre/library/caches.py
+++ b/src/calibre/library/caches.py
@@ -21,7 +21,31 @@ from calibre.utils.pyparsing import ParseException
from calibre.ebooks.metadata import title_sort
from calibre import fit_image
-class CoverCache(Thread):
+class MetadataBackup(Thread): # {{{
+
+ def __init__(self, db, dump_func):
+ Thread.__init__(self)
+ self.daemon = True
+ self.db = db
+ self.dump_func = dump_func
+ self.keep_running = True
+
+ def stop(self):
+ self.keep_running = False
+
+ def run(self):
+ while self.keep_running:
+ try:
+ id_ = self.db.dirtied_queue.get(True, 5)
+ except Empty:
+ continue
+ # If there is an exception is dump_func, we
+ # have no way of knowing
+ self.dump_func([id_])
+
+# }}}
+
+class CoverCache(Thread): # {{{
def __init__(self, db, cover_func):
Thread.__init__(self)
@@ -90,6 +114,7 @@ class CoverCache(Thread):
for id_ in ids:
self.cache.pop(id_, None)
self.load_queue.put(id_)
+# }}}
### Global utility function for get_match here and in gui2/library.py
CONTAINS_MATCH = 0
@@ -107,7 +132,7 @@ def _match(query, value, matchkind):
pass
return False
-class ResultCache(SearchQueryParser):
+class ResultCache(SearchQueryParser): # {{{
'''
Stores sorted and filtered metadata in memory.
@@ -694,4 +719,5 @@ class SortKeyGenerator(object):
# }}}
+# }}}
diff --git a/src/calibre/library/database2.py b/src/calibre/library/database2.py
index 7a8aef541d..92f8cca0db 100644
--- a/src/calibre/library/database2.py
+++ b/src/calibre/library/database2.py
@@ -9,6 +9,7 @@ The database used to store ebook metadata
import os, sys, shutil, cStringIO, glob, time, functools, traceback, re
from itertools import repeat
from math import floor
+from Queue import Queue
from PyQt4.QtGui import QImage
@@ -127,7 +128,7 @@ class LibraryDatabase2(LibraryDatabase, SchemaUpgrade, CustomColumns):
def __init__(self, library_path, row_factory=False):
self.field_metadata = FieldMetadata()
- self.dirtied_cache = set([])
+ self.dirtied_queue = Queue()
if not os.path.exists(library_path):
os.makedirs(library_path)
self.listeners = set([])
@@ -340,7 +341,8 @@ class LibraryDatabase2(LibraryDatabase, SchemaUpgrade, CustomColumns):
loc=self.FIELD_MAP['sort']))
d = self.conn.get('SELECT book FROM metadata_dirtied', all=True)
- self.dirtied_cache.update(set([x[0] for x in d]))
+ for x in d:
+ self.dirtied_queue.put(x[0])
self.refresh_ondevice = functools.partial(self.data.refresh_ondevice, self)
self.refresh()
@@ -557,6 +559,8 @@ class LibraryDatabase2(LibraryDatabase, SchemaUpgrade, CustomColumns):
def dump_metadata(self, book_ids, remove_from_dirtied=True, commit=True):
for book_id in book_ids:
+ if not self.data.has_id(book_id):
+ continue
mi = self.get_metadata(book_id, index_is_id=True, get_cover=True)
# Always set cover to cover.jpg. Even if cover doesn't exist,
# no harm done. This way no need to call dirtied when
@@ -569,18 +573,17 @@ class LibraryDatabase2(LibraryDatabase, SchemaUpgrade, CustomColumns):
if remove_from_dirtied:
self.conn.execute('DELETE FROM metadata_dirtied WHERE book=?',
(book_id,))
- if book_id in self.dirtied_cache:
- self.dirtied_cache.remove(book_id)
if commit:
self.conn.commit()
def dirtied(self, book_ids, commit=True):
self.conn.executemany(
- 'INSERT OR REPLACE INTO metadata_dirtied VALUES (?)',
+ 'INSERT OR REPLACE INTO metadata_dirtied (book) VALUES (?)',
[(x,) for x in book_ids])
if commit:
self.conn.commit()
- self.dirtied.update(set(book_ids))
+ for x in book_ids:
+ self.dirtied_queue.put(x)
def get_metadata(self, idx, index_is_id=False, get_cover=False):
'''
From 703ea3c77827a4053d4c020fa79e7d5e8111f24a Mon Sep 17 00:00:00 2001
From: Kovid Goyal
Date: Thu, 23 Sep 2010 23:55:56 -0600
Subject: [PATCH 101/412] propert indentation in generated OPF files
---
src/calibre/ebooks/metadata/opf2.py | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/src/calibre/ebooks/metadata/opf2.py b/src/calibre/ebooks/metadata/opf2.py
index 8a4ff6a5bd..5c2477c3dc 100644
--- a/src/calibre/ebooks/metadata/opf2.py
+++ b/src/calibre/ebooks/metadata/opf2.py
@@ -1230,7 +1230,7 @@ def metadata_to_opf(mi, as_string=True):
%(id)s%(uuid)s
-
+
'''%dict(a=__appname__, id=mi.application_id, uuid=mi.uuid)))
From 87d70304bd7c96b3f65c565b1d8f8177f017f7a0 Mon Sep 17 00:00:00 2001
From: Kovid Goyal
Date: Fri, 24 Sep 2010 00:05:11 -0600
Subject: [PATCH 102/412] Make metadata backup a little more robust
---
src/calibre/library/caches.py | 14 ++++++++++----
src/calibre/library/database2.py | 1 +
2 files changed, 11 insertions(+), 4 deletions(-)
diff --git a/src/calibre/library/caches.py b/src/calibre/library/caches.py
index 2d37314896..0b5a922209 100644
--- a/src/calibre/library/caches.py
+++ b/src/calibre/library/caches.py
@@ -19,7 +19,7 @@ from calibre.utils.date import parse_date, now, UNDEFINED_DATE
from calibre.utils.search_query_parser import SearchQueryParser
from calibre.utils.pyparsing import ParseException
from calibre.ebooks.metadata import title_sort
-from calibre import fit_image
+from calibre import fit_image, prints
class MetadataBackup(Thread): # {{{
@@ -39,9 +39,15 @@ class MetadataBackup(Thread): # {{{
id_ = self.db.dirtied_queue.get(True, 5)
except Empty:
continue
- # If there is an exception is dump_func, we
- # have no way of knowing
- self.dump_func([id_])
+ except:
+ # Happens during interpreter shutdown
+ break
+ if self.dump_func([id_]) is None:
+ # An exception occured in dump_func, retry once
+ prints('Failed to backup metadata for id:', id_, 'once')
+ time.sleep(2)
+ if not self.dump_func([id_]):
+ prints('Failed to backup metadata for id:', id_, 'again, giving up')
# }}}
diff --git a/src/calibre/library/database2.py b/src/calibre/library/database2.py
index 92f8cca0db..6a0d442927 100644
--- a/src/calibre/library/database2.py
+++ b/src/calibre/library/database2.py
@@ -575,6 +575,7 @@ class LibraryDatabase2(LibraryDatabase, SchemaUpgrade, CustomColumns):
(book_id,))
if commit:
self.conn.commit()
+ return True
def dirtied(self, book_ids, commit=True):
self.conn.executemany(
From 992e5c3c087c1e28cb1e5f1aa61ead0e4556de18 Mon Sep 17 00:00:00 2001
From: Charles Haley <>
Date: Fri, 24 Sep 2010 08:21:10 +0100
Subject: [PATCH 103/412] Repair damage during conflict resolution
---
src/calibre/utils/formatter.py | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/src/calibre/utils/formatter.py b/src/calibre/utils/formatter.py
index f1c2a2cb4d..a98f0e7f45 100644
--- a/src/calibre/utils/formatter.py
+++ b/src/calibre/utils/formatter.py
@@ -50,7 +50,7 @@ class TemplateFormatter(string.Formatter):
format_string_re = re.compile(r'^(.*)\|(.*)\|(.*)$')
compress_spaces = re.compile(r'\s+')
- def get_value(self, key, args):
+ def get_value(self, key, args, kwargs):
raise Exception('get_value must be implemented in the subclass')
From 12768864a59be5ddf477c490072fd682b1f5942a Mon Sep 17 00:00:00 2001
From: Charles Haley <>
Date: Fri, 24 Sep 2010 08:54:06 +0100
Subject: [PATCH 104/412] 1) fix exception in set_metadata related to composite
custom columns 2) make ondevice work with add_books_from_device
---
src/calibre/gui2/actions/add.py | 2 +-
src/calibre/gui2/device.py | 16 +++++++++-------
src/calibre/gui2/library/models.py | 3 +++
src/calibre/library/custom_columns.py | 2 ++
4 files changed, 15 insertions(+), 8 deletions(-)
diff --git a/src/calibre/gui2/actions/add.py b/src/calibre/gui2/actions/add.py
index aa20b8bc16..e0a7b5647e 100644
--- a/src/calibre/gui2/actions/add.py
+++ b/src/calibre/gui2/actions/add.py
@@ -232,7 +232,7 @@ class AddAction(InterfaceAction):
# metadata for this book to the device. This sets the uuid to the
# correct value. Note that set_books_in_library might sync_booklists
self.gui.set_books_in_library(booklists=[model.db], reset=True)
- model.reset()
+ self.gui.refresh_ondevice()
def add_books_from_device(self, view):
rows = view.selectionModel().selectedRows()
diff --git a/src/calibre/gui2/device.py b/src/calibre/gui2/device.py
index a7e55c4619..58c5e5d9ad 100644
--- a/src/calibre/gui2/device.py
+++ b/src/calibre/gui2/device.py
@@ -721,14 +721,16 @@ class DeviceMixin(object): # {{{
self.device_manager.device.__class__.get_gui_name()+\
_(' detected.'), 3000)
self.device_connected = device_kind
- self.refresh_ondevice_info (device_connected = True, reset_only = True)
+ self.library_view.set_device_connected(self.device_connected)
+ self.refresh_ondevice (reset_only = True)
else:
self.device_connected = None
self.status_bar.device_disconnected()
if self.current_view() != self.library_view:
self.book_details.reset_info()
self.location_manager.update_devices()
- self.refresh_ondevice_info(device_connected=False)
+ self.library_view.set_device_connected(self.device_connected)
+ self.refresh_ondevice()
def info_read(self, job):
'''
@@ -760,9 +762,9 @@ class DeviceMixin(object): # {{{
self.card_b_view.set_editable(self.device_manager.device.CAN_SET_METADATA)
self.sync_news()
self.sync_catalogs()
- self.refresh_ondevice_info(device_connected = True)
+ self.refresh_ondevice()
- def refresh_ondevice_info(self, device_connected, reset_only = False):
+ def refresh_ondevice(self, reset_only = False):
'''
Force the library view to refresh, taking into consideration new
device books information
@@ -770,7 +772,7 @@ class DeviceMixin(object): # {{{
self.book_on_device(None, reset=True)
if reset_only:
return
- self.library_view.set_device_connected(device_connected)
+ self.library_view.model().refresh_ondevice()
# }}}
@@ -803,7 +805,7 @@ class DeviceMixin(object): # {{{
self.book_on_device(None, reset=True)
# We need to reset the ondevice flags in the library. Use a big hammer,
# so we don't need to worry about whether some succeeded or not.
- self.refresh_ondevice_info(device_connected=True, reset_only=False)
+ self.refresh_ondevice(reset_only=False)
def dispatch_sync_event(self, dest, delete, specific):
rows = self.library_view.selectionModel().selectedRows()
@@ -1300,7 +1302,7 @@ class DeviceMixin(object): # {{{
if not self.set_books_in_library(self.booklists(), reset=True):
self.upload_booklists()
self.book_on_device(None, reset=True)
- self.refresh_ondevice_info(device_connected = True)
+ self.refresh_ondevice()
view = self.card_a_view if on_card == 'carda' else \
self.card_b_view if on_card == 'cardb' else self.memory_view
diff --git a/src/calibre/gui2/library/models.py b/src/calibre/gui2/library/models.py
index 9d9de358c8..640a588d29 100644
--- a/src/calibre/gui2/library/models.py
+++ b/src/calibre/gui2/library/models.py
@@ -120,6 +120,9 @@ class BooksModel(QAbstractTableModel): # {{{
def set_device_connected(self, is_connected):
self.device_connected = is_connected
+ self.refresh_ondevice()
+
+ def refresh_ondevice(self):
self.db.refresh_ondevice()
self.refresh() # does a resort()
self.research()
diff --git a/src/calibre/library/custom_columns.py b/src/calibre/library/custom_columns.py
index 2d8634659b..97c8565177 100644
--- a/src/calibre/library/custom_columns.py
+++ b/src/calibre/library/custom_columns.py
@@ -427,6 +427,8 @@ class CustomColumns(object):
data = self.custom_column_label_map[label]
if num is not None:
data = self.custom_column_num_map[num]
+ if data['datatype'] == 'composite':
+ return None
if not data['editable']:
raise ValueError('Column %r is not editable'%data['label'])
table, lt = self.custom_table_names(data['num'])
From 97e2c838d0e6e4920ce4e1f4d71688979f57b68d Mon Sep 17 00:00:00 2001
From: Charles Haley <>
Date: Fri, 24 Sep 2010 10:50:50 +0100
Subject: [PATCH 105/412] 1) Fix of json codec. 2) make dump_metadata set
get_cover=False
---
src/calibre/ebooks/metadata/book/json_codec.py | 3 ++-
src/calibre/library/database2.py | 2 +-
2 files changed, 3 insertions(+), 2 deletions(-)
diff --git a/src/calibre/ebooks/metadata/book/json_codec.py b/src/calibre/ebooks/metadata/book/json_codec.py
index 2550089473..c02d4e953d 100644
--- a/src/calibre/ebooks/metadata/book/json_codec.py
+++ b/src/calibre/ebooks/metadata/book/json_codec.py
@@ -75,7 +75,8 @@ class JsonCodec(object):
self.field_metadata = FieldMetadata()
def encode_to_file(self, file, booklist):
- json.dump(self.encode_booklist_metadata(booklist), file, indent=2, encoding='utf-8')
+ file.write(json.dumps(self.encode_booklist_metadata(booklist),
+ indent=2, encoding='utf-8'))
def encode_booklist_metadata(self, booklist):
result = []
diff --git a/src/calibre/library/database2.py b/src/calibre/library/database2.py
index 6a0d442927..773a4bdc9f 100644
--- a/src/calibre/library/database2.py
+++ b/src/calibre/library/database2.py
@@ -561,7 +561,7 @@ class LibraryDatabase2(LibraryDatabase, SchemaUpgrade, CustomColumns):
for book_id in book_ids:
if not self.data.has_id(book_id):
continue
- mi = self.get_metadata(book_id, index_is_id=True, get_cover=True)
+ mi = self.get_metadata(book_id, index_is_id=True, get_cover=False)
# Always set cover to cover.jpg. Even if cover doesn't exist,
# no harm done. This way no need to call dirtied when
# cover is set/removed
From 8b9b64a8e6bdab03f62c98a4f2c35ec73957cca7 Mon Sep 17 00:00:00 2001
From: Charles Haley <>
Date: Fri, 24 Sep 2010 11:34:52 +0100
Subject: [PATCH 106/412] 1) add two tweaks controlling what custom fields the
content server displays 2) add & cleanup some field_metadata methods
---
resources/default_tweaks.py | 18 ++++++++++++++++++
src/calibre/gui2/library/models.py | 2 +-
src/calibre/library/database2.py | 6 ++++++
src/calibre/library/field_metadata.py | 2 +-
src/calibre/library/server/__init__.py | 12 +++++++++++-
src/calibre/library/server/mobile.py | 3 ++-
src/calibre/library/server/opds.py | 3 ++-
src/calibre/library/server/xml.py | 3 ++-
8 files changed, 43 insertions(+), 6 deletions(-)
diff --git a/resources/default_tweaks.py b/resources/default_tweaks.py
index 04b861605e..095eba0c3d 100644
--- a/resources/default_tweaks.py
+++ b/resources/default_tweaks.py
@@ -145,6 +145,24 @@ add_new_book_tags_when_importing_books = False
# Set the maximum number of tags to show per book in the content server
max_content_server_tags_shown=5
+# Set custom metadata fields that the content server will or will not display.
+# content_server_will_display is a list of custom fields to be displayed.
+# content_server_wont_display is a list of custom fields not to be displayed.
+# wont_display has priority over will_display.
+# The special value '*' means all custom fields.
+# Defaults:
+# content_server_will_display = ['*']
+# content_server_wont_display = ['']
+# Examples:
+# To display only the custom fields #mytags and #genre:
+# content_server_will_display = ['#mytags', '#genre']
+# content_server_wont_display = ['']
+# To display all fields except #mycomments:
+# content_server_will_display = ['*']
+# content_server_wont_display['#mycomments']
+content_server_will_display = ['*']
+content_server_wont_display = ['']
+
# Set the maximum number of sort 'levels' that calibre will use to resort the
# library after certain operations such as searches or device insertion. Each
diff --git a/src/calibre/gui2/library/models.py b/src/calibre/gui2/library/models.py
index 640a588d29..af1b42bf33 100644
--- a/src/calibre/gui2/library/models.py
+++ b/src/calibre/gui2/library/models.py
@@ -132,7 +132,7 @@ class BooksModel(QAbstractTableModel): # {{{
def set_database(self, db):
self.db = db
- self.custom_columns = self.db.field_metadata.get_custom_field_metadata()
+ self.custom_columns = self.db.field_metadata.custom_field_metadata()
self.column_map = list(self.orig_headers.keys()) + \
list(self.custom_columns)
def col_idx(name):
diff --git a/src/calibre/library/database2.py b/src/calibre/library/database2.py
index 773a4bdc9f..c7c4926b14 100644
--- a/src/calibre/library/database2.py
+++ b/src/calibre/library/database2.py
@@ -554,6 +554,12 @@ class LibraryDatabase2(LibraryDatabase, SchemaUpgrade, CustomColumns):
def search_term_to_field_key(self, term):
return self.field_metadata.search_term_to_key(term)
+ def custom_field_metadata(self):
+ return self.field_metadata.custom_field_metadata()
+
+ def all_metadata(self):
+ return self.field_metadata.all_metadata()
+
def metadata_for_field(self, key):
return self.field_metadata[key]
diff --git a/src/calibre/library/field_metadata.py b/src/calibre/library/field_metadata.py
index bac423f46d..d608dca49d 100644
--- a/src/calibre/library/field_metadata.py
+++ b/src/calibre/library/field_metadata.py
@@ -411,7 +411,7 @@ class FieldMetadata(dict):
l[k] = self._tb_cats[k]
return l
- def get_custom_field_metadata(self):
+ def custom_field_metadata(self):
l = {}
for k in self._tb_cats:
if self._tb_cats[k]['is_custom']:
diff --git a/src/calibre/library/server/__init__.py b/src/calibre/library/server/__init__.py
index 5050dfaa99..7cdea9f602 100644
--- a/src/calibre/library/server/__init__.py
+++ b/src/calibre/library/server/__init__.py
@@ -7,7 +7,7 @@ __docformat__ = 'restructuredtext en'
import os
-from calibre.utils.config import Config, StringConfig, config_dir
+from calibre.utils.config import Config, StringConfig, config_dir, tweaks
listen_on = '0.0.0.0'
@@ -46,6 +46,16 @@ def server_config(defaults=None):
'to disable grouping.'))
return c
+def custom_fields_to_display(db):
+ ckeys = db.custom_field_keys()
+ yes_fields = set(tweaks['content_server_will_display'])
+ no_fields = set(tweaks['content_server_wont_display'])
+ if '*' in yes_fields:
+ yes_fields = set(ckeys)
+ if '*' in no_fields:
+ no_fields = set(ckeys)
+ return frozenset(yes_fields - no_fields)
+
def main():
from calibre.library.server.main import main
return main()
diff --git a/src/calibre/library/server/mobile.py b/src/calibre/library/server/mobile.py
index c0a3c122cd..071c7b1077 100644
--- a/src/calibre/library/server/mobile.py
+++ b/src/calibre/library/server/mobile.py
@@ -13,6 +13,7 @@ from lxml import html
from lxml.html.builder import HTML, HEAD, TITLE, LINK, DIV, IMG, BODY, \
OPTION, SELECT, INPUT, FORM, SPAN, TABLE, TR, TD, A, HR
+from calibre.library.server import custom_fields_to_display
from calibre.library.server.utils import strftime, format_tag_string
from calibre.ebooks.metadata import fmt_sidx
from calibre.constants import __appname__
@@ -197,7 +198,7 @@ class MobileServer(object):
self.sort(items, sort, (order.lower().strip() == 'ascending'))
CFM = self.db.field_metadata
- CKEYS = [key for key in sorted(CFM.get_custom_fields(),
+ CKEYS = [key for key in sorted(custom_fields_to_display(self.db),
cmp=lambda x,y: cmp(CFM[x]['name'].lower(),
CFM[y]['name'].lower()))]
# This method uses its own book dict, not the Metadata dict. The loop
diff --git a/src/calibre/library/server/opds.py b/src/calibre/library/server/opds.py
index d495f58fa1..0e6917c504 100644
--- a/src/calibre/library/server/opds.py
+++ b/src/calibre/library/server/opds.py
@@ -17,6 +17,7 @@ import routes
from calibre.constants import __appname__
from calibre.ebooks.metadata import fmt_sidx
from calibre.library.comments import comments_to_html
+from calibre.library.server import custom_fields_to_display
from calibre.library.server.utils import format_tag_string
from calibre import guess_type
from calibre.utils.ordered_dict import OrderedDict
@@ -277,7 +278,7 @@ class AcquisitionFeed(NavFeed):
db):
NavFeed.__init__(self, id_, updated, version, offsets, page_url, up_url)
CFM = db.field_metadata
- CKEYS = [key for key in sorted(CFM.get_custom_fields(),
+ CKEYS = [key for key in sorted(custom_fields_to_display(db),
cmp=lambda x,y: cmp(CFM[x]['name'].lower(),
CFM[y]['name'].lower()))]
for item in items:
diff --git a/src/calibre/library/server/xml.py b/src/calibre/library/server/xml.py
index 45ffdc2737..12fcc217f0 100644
--- a/src/calibre/library/server/xml.py
+++ b/src/calibre/library/server/xml.py
@@ -11,6 +11,7 @@ import cherrypy
from lxml.builder import ElementMaker
from lxml import etree
+from calibre.library.server import custom_fields_to_display
from calibre.library.server.utils import strftime, format_tag_string
from calibre.ebooks.metadata import fmt_sidx
from calibre.constants import preferred_encoding
@@ -94,7 +95,7 @@ class XMLServer(object):
c = kwargs.pop('comments')
CFM = self.db.field_metadata
- CKEYS = [key for key in sorted(CFM.get_custom_fields(),
+ CKEYS = [key for key in sorted(custom_fields_to_display(self.db),
cmp=lambda x,y: cmp(CFM[x]['name'].lower(),
CFM[y]['name'].lower()))]
custcols = []
From 67c7555fd0eb22802892ec716ec5564f3d423bc4 Mon Sep 17 00:00:00 2001
From: Charles Haley <>
Date: Fri, 24 Sep 2010 11:36:26 +0100
Subject: [PATCH 107/412] Fix content server gui.js bug where it put '...' on
the end of a list even if the list was exactly the right size.
---
resources/content_server/gui.js | 3 ++-
1 file changed, 2 insertions(+), 1 deletion(-)
diff --git a/resources/content_server/gui.js b/resources/content_server/gui.js
index bd0743a854..86cd04289b 100644
--- a/resources/content_server/gui.js
+++ b/resources/content_server/gui.js
@@ -63,8 +63,9 @@ function render_book(book) {
if (tags) {
t = tags.split(':&:', 2);
m = parseInt(t[0]);
+ tall = t[1].split(',');
t = t[1].split(',', m);
- if (t.length == m) t[m] = '...'
+ if (tall.length > m) t[m] = '...'
title += 'Tags=[{0}] '.format(t.join(','));
}
custcols = book.attr("custcols").split(',')
From ad69ef985a04b5485550f83661ee0b56723605f1 Mon Sep 17 00:00:00 2001
From: Charles Haley <>
Date: Fri, 24 Sep 2010 12:27:39 +0100
Subject: [PATCH 108/412] Add a 'test' function to templates. Analogous to
lookup, but inserts plain text instead of a template.
---
src/calibre/utils/formatter.py | 7 +++++++
1 file changed, 7 insertions(+)
diff --git a/src/calibre/utils/formatter.py b/src/calibre/utils/formatter.py
index a98f0e7f45..5c5893576c 100644
--- a/src/calibre/utils/formatter.py
+++ b/src/calibre/utils/formatter.py
@@ -23,6 +23,12 @@ class TemplateFormatter(string.Formatter):
else:
return self.vformat('{'+field_not_set.strip()+'}', [], self.kwargs)
+ def _test(self, val, value_if_set, value_not_set):
+ if val:
+ return value_if_set
+ else:
+ return value_not_set
+
def _ifempty(self, val, value_if_empty):
if val:
return val
@@ -45,6 +51,7 @@ class TemplateFormatter(string.Formatter):
'ifempty' : (1, _ifempty),
'lookup' : (2, _lookup),
'shorten' : (3, _shorten),
+ 'test' : (2, _lookup),
}
format_string_re = re.compile(r'^(.*)\|(.*)\|(.*)$')
From b2a6ed3af48a47c5cc7bb3372a3b084a004b7fcf Mon Sep 17 00:00:00 2001
From: Charles Haley <>
Date: Fri, 24 Sep 2010 12:43:54 +0100
Subject: [PATCH 109/412] 1) fix bulk edit to not display a tab if library has
only composite columns 2) fix a reference to get_custom_field_metadata that I
somehow missed.
---
src/calibre/gui2/dialogs/metadata_bulk.py | 3 ++-
src/calibre/gui2/preferences/columns.py | 2 +-
2 files changed, 3 insertions(+), 2 deletions(-)
diff --git a/src/calibre/gui2/dialogs/metadata_bulk.py b/src/calibre/gui2/dialogs/metadata_bulk.py
index a9e45087fd..1e3576e333 100644
--- a/src/calibre/gui2/dialogs/metadata_bulk.py
+++ b/src/calibre/gui2/dialogs/metadata_bulk.py
@@ -167,7 +167,8 @@ class MetadataBulkDialog(QDialog, Ui_MetadataBulkDialog):
self.tag_editor_button.clicked.connect(self.tag_editor)
self.autonumber_series.stateChanged[int].connect(self.auto_number_changed)
- if len(db.custom_column_label_map) == 0:
+ if len([k for k in db.custom_field_metadata().values()
+ if k['datatype'] != 'composite']) == 0:
self.central_widget.removeTab(1)
else:
self.create_custom_column_editors()
diff --git a/src/calibre/gui2/preferences/columns.py b/src/calibre/gui2/preferences/columns.py
index c1b9230f42..03a50e6f3a 100644
--- a/src/calibre/gui2/preferences/columns.py
+++ b/src/calibre/gui2/preferences/columns.py
@@ -21,7 +21,7 @@ class ConfigWidget(ConfigWidgetBase, Ui_Form):
def genesis(self, gui):
self.gui = gui
db = self.gui.library_view.model().db
- self.custcols = copy.deepcopy(db.field_metadata.get_custom_field_metadata())
+ self.custcols = copy.deepcopy(db.field_metadata.custom_field_metadata())
self.column_up.clicked.connect(self.up_column)
self.column_down.clicked.connect(self.down_column)
From 529756238340e0dd66c3be0cfc1f5f7a8a178cfa Mon Sep 17 00:00:00 2001
From: Charles Haley <>
Date: Fri, 24 Sep 2010 12:57:49 +0100
Subject: [PATCH 110/412] Refactor code to clean interfaces and remove overly
complex loop in bulk edit
---
src/calibre/gui2/dialogs/metadata_bulk.py | 3 +--
src/calibre/library/database2.py | 8 ++++----
src/calibre/library/field_metadata.py | 22 +++++++++++-----------
3 files changed, 16 insertions(+), 17 deletions(-)
diff --git a/src/calibre/gui2/dialogs/metadata_bulk.py b/src/calibre/gui2/dialogs/metadata_bulk.py
index 1e3576e333..b14390e001 100644
--- a/src/calibre/gui2/dialogs/metadata_bulk.py
+++ b/src/calibre/gui2/dialogs/metadata_bulk.py
@@ -167,8 +167,7 @@ class MetadataBulkDialog(QDialog, Ui_MetadataBulkDialog):
self.tag_editor_button.clicked.connect(self.tag_editor)
self.autonumber_series.stateChanged[int].connect(self.auto_number_changed)
- if len([k for k in db.custom_field_metadata().values()
- if k['datatype'] != 'composite']) == 0:
+ if len(db.custom_field_keys(include_composites=False)) == 0:
self.central_widget.removeTab(1)
else:
self.create_custom_column_editors()
diff --git a/src/calibre/library/database2.py b/src/calibre/library/database2.py
index c7c4926b14..22175d3910 100644
--- a/src/calibre/library/database2.py
+++ b/src/calibre/library/database2.py
@@ -539,8 +539,8 @@ class LibraryDatabase2(LibraryDatabase, SchemaUpgrade, CustomColumns):
def standard_field_keys(self):
return self.field_metadata.standard_field_keys()
- def custom_field_keys(self):
- return self.field_metadata.custom_field_keys()
+ def custom_field_keys(self, include_composites=True):
+ return self.field_metadata.custom_field_keys(include_composites)
def all_field_keys(self):
return self.field_metadata.all_field_keys()
@@ -554,8 +554,8 @@ class LibraryDatabase2(LibraryDatabase, SchemaUpgrade, CustomColumns):
def search_term_to_field_key(self, term):
return self.field_metadata.search_term_to_key(term)
- def custom_field_metadata(self):
- return self.field_metadata.custom_field_metadata()
+ def custom_field_metadata(self, include_composites=True):
+ return self.field_metadata.custom_field_metadata(include_composites)
def all_metadata(self):
return self.field_metadata.all_metadata()
diff --git a/src/calibre/library/field_metadata.py b/src/calibre/library/field_metadata.py
index d608dca49d..37393d0d2c 100644
--- a/src/calibre/library/field_metadata.py
+++ b/src/calibre/library/field_metadata.py
@@ -358,10 +358,14 @@ class FieldMetadata(dict):
if self._tb_cats[k]['kind']=='field' and
not self._tb_cats[k]['is_custom']]
- def custom_field_keys(self):
- return [k for k in self._tb_cats.keys()
- if self._tb_cats[k]['kind']=='field' and
- self._tb_cats[k]['is_custom']]
+ def custom_field_keys(self, include_composites=True):
+ res = []
+ for k in self._tb_cats.keys():
+ fm = self._tb_cats[k]
+ if fm['kind']=='field' and fm['is_custom'] and \
+ (fm['datatype'] != 'composite' or include_composites):
+ res.append(k)
+ return res
def all_field_keys(self):
return [k for k in self._tb_cats.keys() if self._tb_cats[k]['kind']=='field']
@@ -402,20 +406,16 @@ class FieldMetadata(dict):
return self.custom_label_to_key_map[label]
raise ValueError('Unknown key [%s]'%(label))
- def get_custom_fields(self):
- return [l for l in self._tb_cats if self._tb_cats[l]['is_custom']]
-
def all_metadata(self):
l = {}
for k in self._tb_cats:
l[k] = self._tb_cats[k]
return l
- def custom_field_metadata(self):
+ def custom_field_metadata(self, include_composites=True):
l = {}
- for k in self._tb_cats:
- if self._tb_cats[k]['is_custom']:
- l[k] = self._tb_cats[k]
+ for k in self.custom_field_keys(include_composites):
+ l[k] = self._tb_cats[k]
return l
def add_custom_field(self, label, table, column, datatype, colnum, name,
From 25905a349c745108abd6290d5835b109359a72de Mon Sep 17 00:00:00 2001
From: Charles Haley <>
Date: Fri, 24 Sep 2010 13:20:26 +0100
Subject: [PATCH 111/412] Test the 'test' function. Add 're' function and test
it.
---
src/calibre/utils/formatter.py | 6 +++++-
1 file changed, 5 insertions(+), 1 deletion(-)
diff --git a/src/calibre/utils/formatter.py b/src/calibre/utils/formatter.py
index 5c5893576c..c6bcaa1c3e 100644
--- a/src/calibre/utils/formatter.py
+++ b/src/calibre/utils/formatter.py
@@ -43,6 +43,9 @@ class TemplateFormatter(string.Formatter):
else:
return val
+ def _re(self, val, pattern, replacement):
+ return re.sub(pattern, replacement, val)
+
functions = {
'uppercase' : (0, lambda s,x: x.upper()),
'lowercase' : (0, lambda s,x: x.lower()),
@@ -50,8 +53,9 @@ class TemplateFormatter(string.Formatter):
'capitalize' : (0, lambda s,x: x.capitalize()),
'ifempty' : (1, _ifempty),
'lookup' : (2, _lookup),
+ 're' : (2, _re),
'shorten' : (3, _shorten),
- 'test' : (2, _lookup),
+ 'test' : (2, _test),
}
format_string_re = re.compile(r'^(.*)\|(.*)\|(.*)$')
From 211bb81113865b1cb38c2f8697b33694a4bb38fe Mon Sep 17 00:00:00 2001
From: Charles Haley <>
Date: Fri, 24 Sep 2010 14:43:07 +0100
Subject: [PATCH 112/412] Put back the sanitize after split on slashes.
---
src/calibre/library/save_to_disk.py | 1 +
1 file changed, 1 insertion(+)
diff --git a/src/calibre/library/save_to_disk.py b/src/calibre/library/save_to_disk.py
index a58686f709..e479d27121 100644
--- a/src/calibre/library/save_to_disk.py
+++ b/src/calibre/library/save_to_disk.py
@@ -166,6 +166,7 @@ def get_components(template, mi, id, timefmt='%b %Y', length=250,
components = safe_formatter.safe_format(template, format_args, '', mi,
sanitize=sanitize_func)
components = [x.strip() for x in components.split('/') if x.strip()]
+ components = [sanitize_func(x) for x in components if x]
if not components:
components = [str(id)]
components = [x.encode(filesystem_encoding, 'replace') if isinstance(x,
From 93c8836cb6622579323d95f86dfb6fa03dda78cb Mon Sep 17 00:00:00 2001
From: Charles Haley <>
Date: Fri, 24 Sep 2010 15:18:03 +0100
Subject: [PATCH 113/412] Changes to template faq
---
src/calibre/manual/template_lang.rst | 85 +++++++++++++++++++++++-----
1 file changed, 71 insertions(+), 14 deletions(-)
diff --git a/src/calibre/manual/template_lang.rst b/src/calibre/manual/template_lang.rst
index 59e5c1da4c..6d87a90c93 100644
--- a/src/calibre/manual/template_lang.rst
+++ b/src/calibre/manual/template_lang.rst
@@ -7,9 +7,9 @@ The |app| template language
=======================================================
The |app| template language is used in various places. It is used to control the folder structure and file name when saving files from the |app| library to the disk or eBook reader.
-It is used to define "virtual" columns that contain data from other columns and so on.
+It is also used to define "virtual" columns that contain data from other columns and so on.
-In essence, the template language is very simple. The basic idea is that a template consists of names in curly brackets that are then replaced by the corresponding metadata from the book being processed. So, for example, the default template used for saving books to device in |app| is::
+The basi template language is very simple, but has very powerful advanced features. The basic idea is that a template consists of names in curly brackets that are then replaced by the corresponding metadata from the book being processed. So, for example, the default template used for saving books to device in |app| is::
{author_sort}/{title}/{title} - {authors}
@@ -17,7 +17,9 @@ For the book "The Foundation" by "Isaac Asimov" it will become::
Asimov, Isaac/The Foundation/The Foundation - Isaac Asimov
-You can use all the various metadata fields available in calibre in a template, including the custom columns you have created yourself. To find out the template name for a column sinply hover your mouse over the column header. Names for custom fields (columns you have created yourself) are always prefixed by an #. For series type fields, there is always an additional field named ``series_index`` that becomes the series index for that series. So if you have a custom series field named #myseries, there will also be a field named #myseries_index. In addition to the column based fields, you also can use::
+You can use all the various metadata fields available in calibre in a template, including any custom columns you have created yourself. To find out the template name for a column simply hover your mouse over the column header. Names for custom fields (columns you have created yourself) always have a # as the first character. For series type custom fields, there is always an additional field named ``#seriesname_index`` that becomes the series index for that series. So if you have a custom series field named #myseries, there will also be a field named #myseries_index.
+
+In addition to the column based fields, you also can use::
{formats} - A list of formats available in the calibre library for a book
{isbn} - The ISBN number of the book
@@ -26,7 +28,7 @@ If a particular book does not have a particular piece of metadata, the field in
{author_sort}/{series}/{title} {series_index}
-will become::
+If a book has a series, the template will produce::
{Asimov, Isaac}/Foundation/Second Foundation - 3
@@ -40,35 +42,90 @@ and if a book does not have a series::
Advanced formatting
----------------------
-You can do more than just simple substitution with the templates. You can also conditionally include text and control how the substituted data is formatted.
+You can do more than just simple substitution with the templates. You can also conditionally include text and control how the substituted data is formatted.
+
+First, conditionally including text. There are cases where you might want to have text appear in the output only if a field is not empty. A common case is series and series_index, where you want either nothing or the two values with a hyphen between them. Calibre handles this case using a special field syntax.
-Regarding conditionally including text: there are cases where you might want to have text appear in the output only if a field is not empty. A common case is series and series_index, where you want either nothing or the two values with a hyphen between them. Calibre handles this case using a special field syntax.
For example, assume you want to use the template
{series} - {series_index} - {title}
-Unfortunately, if the book has no series, the answer will be '- - title'. Many people would rather it be simply 'title', without the hyphens. To do this, use the extended syntax {some_text|field|other_text}. When you use this syntax, if field has the value SERIES then the result will be some_textSERIESother_text. If field has no value, then the result will be the empty string (nothing). Using this syntax, we can solve the above series problem with the template::
+If the book has no series, the answer will be '- - title'. Many people would rather the result be simply 'title', without the hyphens. To do this, use the extended syntax `{field:|prefix_text|suffix_text}`. When you use this syntax, if field has the value SERIES then the result will be prefix_textSERIESsuffix_text. If field has no value, then the result will be the empty string (nothing). The prefix and suffix can contain blanks.
- {series}{ - |series_index| - }{title}
+Using this syntax, we can solve the above series problem with the template:
-The hyphens will be included only if the book has a series index. Note: you must either use no | characters or both of them. Using one, such as in {field| - }, is not allowed. It is OK to not provide any text for one side or the other, such as in {\|series\| - }. Using {\|title\|} is the same as using {title}.
+ {series}{series_index:| - | - }{title}
-Now to formatting. Suppose you wanted to ensure that the series_index is always formatted as three digits with leading zeros. This would do the trick::
+The hyphens will be included only if the book has a series index.
+
+Notes: you must include the : character if you want to use a prefix or a suffix. You must either use no | characters or both of them; using one, as in `{field:| - }`, is not allowed. It is OK not to provide any text for one side or the other, such as in `{series:|| - }`. Using `{title:||}` is the same as using `{title}`.
+
+Second: formatting. Suppose you wanted to ensure that the series_index is always formatted as three digits with leading zeros. This would do the trick::
{series_index:0>3s} - Three digits with leading zeros
-If instead of leading zeros you want leading spaces, use::
+If instead of leading zeros you want leading spaces, use:
- {series_index:>3s} - Thre digits with leading spaces
+ {series_index:>3s} - Three digits with leading spaces
-For trailing zeros, use::
+For trailing zeros, use:
{series_index:0<3s} - Three digits with trailing zeros
-If you want only the first two letters of the data to be rendered, use::
+If you want only the first two letters of the data, use::
{author_sort:.2} - Only the first two letter of the author sort name
The |app| template language comes from python and for more details on the syntax of these advanced formatting operations, look at the `Python documentation `_.
+Advanced features
+------------------
+
+Using templates in custom columns
+----------------------------------
+
+There are sometimes cases where you want to display metadata that |app| does not normally display, or to display data in a way different from how |app| normally does. For example, you might want to display the ISBN, a field that |app| does not display. You can use custom columns for this. To do so, you create a column with the type 'column built from other columns' (hereafter called composite columns), enter a template, and |app| will display in the column the result of evaluating that template. To display the isbn, create the column and enter `{isbn}` into the template box. To display a column containing the values of two series custom columns separated by a comma, use `{#series1:||,}{#series2}`.
+
+Composite columns can use any template option, including formatting.
+
+You cannot change the data contained in a composite column. If you edit a composite column by double-clicking on any item, you will open the template for editing, not the underlying data. Editing the template on the GUI is a quick way of testing and changing composite columns.
+
+Using functions in templates
+-----------------------------
+
+Suppose you want to display the value of a field in upper case, when that field is normally in title case. You can do this (and many more things) using the functions available for templates. For example, to display the title in upper case, use `{title:uppercase()}`. To display it in title case, use `{title:titlecase()}`.
+
+Function references replace the formatting specification, going after the : and before the first `|` or the closing `}`. Functions must always end with `()`. Some functions take extra values (arguments), and these go inside the `()`.
+
+The syntax for using functions is `{field:function(arguments)}`, or `{field:function(arguments)|prefix|suffix}`. Argument values cannot contain a comma, because it is used to separate arguments. Functions return the value of the field used in the template, suitably modified.
+
+The functions available are:
+
+* `lowercase()` -- return value of the field in lower case.
+* `uppercase()` -- return the value of the field in upper case.
+* `titlecase()` -- return the value of the field in title case.
+* `capitalize()` -- return the value as capitalized.
+* `ifempty(text)` -- if the field is not empty, return the value of the field. Otherwise return `text`.
+* `test(text if not empty, text if empty)` -- return `text if not empty` if the field is not empty, otherwise return `text if empty`.
+* `shorten(left chars, middle text, right chars)` -- Return a shortened version of the field, consisting of `left chars` characters from the beginning of the field, followed by `middle text`, followed by `right chars` characters from the end of the string. `Left chars` and `right chars` must be integers. For example, assume the title of the book is `Ancient English Laws in the Times of Ivanhoe`, and you want it to fit in a space of at most 15 characters. If you use `{title:shorten(9,-,5)}, the result will be `Ancient E-nhoe`. If the field's length is less than `left chars` + `right chars` + the length of `middle text`, then the field will be used intact. For example, the title `The Dome` would not be changed.
+* `lookup(field if not empty, field if empty)` -- like test, except the arguments are field (metadata) names, not text. The value of the appropriate field will be fetched and used. Note that because composite columns are fields, you can use this function in one composite field to use the value of some other composite field. This is extremely useful when constructing variable save paths (more later).
+* `re(pattern, replacement)` -- return the field after applying the regular expression. All instances of `pattern` are replaced with `replacement`. As in all of |app|, these are python-compatible regular expressions.
+
+Special notes for save/send templates
+-------------------------------------
+
+Special processing is applied when a template is used in a `save to disk` or `send to device` template. The values of the fields are cleaned, replacing characters that are special to file systems with underscores, including slashes. This means that field text cannot be used to create folders. However, slashes are not changed in prefix or suffix strings, so slashes in these strings will cause folders to be created. Because of this, you can create variable-depth folder structure.
+
+For example, assume we want the folder structure `series/series_index - title`, with the caveat that if series does not exist, then the title should be in the top folder. The template to do this is
+
+ {series:||/}{series_index:|| - }{title}
+
+The slash and the hyphen appear only if series is not empty.
+
+The lookup function lets us do even fancier processing. For example, assume we want the following: if a book has a series, then we want the folder structure `series/series index - title.fmt`. If the book does not have a series, then we want the folder structure `genre/author_sort/title.fmt`. If the book has no genre, use 'Unknown'. We want two completely different paths, depending on the value of series.
+
+To accomplish this, we:
+1. Create a composite field (call it AA) containing `{series:||}/{series_index} - {title'}`. If the series is not empty, then this template will produce `series/series_index - title`.
+2. Create a composite field (call it BB) containing `{#genre:ifempty(Unknown)}/{author_sort}/{title}`. This template produces `genre/author_sort/title`, where an empty genre is replaced wuth `Unknown`.
+3. Set the save template to `{series:lookup(AA,BB)}`. This template chooses composite field AA if series is not empty, and composite field BB if series is empty. We therefore have two completely different save paths, depending on whether or not `series` is empty.
From 02e9160f3752c179391bfddbb1b3febb9cb3a517 Mon Sep 17 00:00:00 2001
From: Charles Haley <>
Date: Fri, 24 Sep 2010 15:23:35 +0100
Subject: [PATCH 114/412] Fix typo
---
src/calibre/manual/template_lang.rst | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/src/calibre/manual/template_lang.rst b/src/calibre/manual/template_lang.rst
index 6d87a90c93..2c49cfe308 100644
--- a/src/calibre/manual/template_lang.rst
+++ b/src/calibre/manual/template_lang.rst
@@ -9,7 +9,7 @@ The |app| template language
The |app| template language is used in various places. It is used to control the folder structure and file name when saving files from the |app| library to the disk or eBook reader.
It is also used to define "virtual" columns that contain data from other columns and so on.
-The basi template language is very simple, but has very powerful advanced features. The basic idea is that a template consists of names in curly brackets that are then replaced by the corresponding metadata from the book being processed. So, for example, the default template used for saving books to device in |app| is::
+The basic template language is very simple, but has very powerful advanced features. The basic idea is that a template consists of names in curly brackets that are then replaced by the corresponding metadata from the book being processed. So, for example, the default template used for saving books to device in |app| is::
{author_sort}/{title}/{title} - {authors}
From 60e77299062148aa04e325de03b8089d26781234 Mon Sep 17 00:00:00 2001
From: Kovid Goyal
Date: Fri, 24 Sep 2010 08:33:42 -0600
Subject: [PATCH 115/412] Don't put duplicates in dirtied_queue
---
src/calibre/library/database2.py | 14 +++++++++-----
1 file changed, 9 insertions(+), 5 deletions(-)
diff --git a/src/calibre/library/database2.py b/src/calibre/library/database2.py
index 6a0d442927..dc320eb011 100644
--- a/src/calibre/library/database2.py
+++ b/src/calibre/library/database2.py
@@ -578,13 +578,17 @@ class LibraryDatabase2(LibraryDatabase, SchemaUpgrade, CustomColumns):
return True
def dirtied(self, book_ids, commit=True):
- self.conn.executemany(
- 'INSERT OR REPLACE INTO metadata_dirtied (book) VALUES (?)',
- [(x,) for x in book_ids])
+ for book in book_ids:
+ try:
+ self.conn.execute(
+ 'INSERT INTO metadata_dirtied (book) VALUES (?)',
+ (book,))
+ self.dirtied_queue.put(book)
+ except IntegrityError:
+ # Already in table
+ continue
if commit:
self.conn.commit()
- for x in book_ids:
- self.dirtied_queue.put(x)
def get_metadata(self, idx, index_is_id=False, get_cover=False):
'''
From 41ebe2bd1443bbf2a88731c6d1919a8207f08275 Mon Sep 17 00:00:00 2001
From: Kovid Goyal
Date: Fri, 24 Sep 2010 09:00:46 -0600
Subject: [PATCH 116/412] calibredb now does a backup of changed metadata
---
src/calibre/library/caches.py | 9 ++++++---
src/calibre/library/cli.py | 8 ++++++--
src/calibre/library/database2.py | 6 +++++-
3 files changed, 17 insertions(+), 6 deletions(-)
diff --git a/src/calibre/library/caches.py b/src/calibre/library/caches.py
index 0b5a922209..714579ec77 100644
--- a/src/calibre/library/caches.py
+++ b/src/calibre/library/caches.py
@@ -36,14 +36,14 @@ class MetadataBackup(Thread): # {{{
def run(self):
while self.keep_running:
try:
- id_ = self.db.dirtied_queue.get(True, 5)
+ id_ = self.db.dirtied_queue.get()
except Empty:
continue
except:
# Happens during interpreter shutdown
break
if self.dump_func([id_]) is None:
- # An exception occured in dump_func, retry once
+ # An exception occurred in dump_func, retry once
prints('Failed to backup metadata for id:', id_, 'once')
time.sleep(2)
if not self.dump_func([id_]):
@@ -84,9 +84,12 @@ class CoverCache(Thread): # {{{
def run(self):
while self.keep_running:
try:
- id_ = self.load_queue.get(True, 1)
+ id_ = self.load_queue.get()
except Empty:
continue
+ except:
+ #Happens during interpreter shutdown
+ break
try:
img = self._image_for_id(id_)
except:
diff --git a/src/calibre/library/cli.py b/src/calibre/library/cli.py
index cd4e472807..6ff17b0781 100644
--- a/src/calibre/library/cli.py
+++ b/src/calibre/library/cli.py
@@ -32,8 +32,9 @@ def send_message(msg=''):
t.conn.send('refreshdb:'+msg)
t.conn.close()
-
-
+def write_dirtied(db):
+ prints('Backing up metadata')
+ db.dump_metadata()
def get_parser(usage):
parser = OptionParser(usage)
@@ -259,6 +260,7 @@ def do_add(db, paths, one_book_per_directory, recurse, add_duplicates):
print >>sys.stderr, '\t', title+':'
print >>sys.stderr, '\t\t ', path
+ write_dirtied(db)
send_message()
finally:
sys.stdout = orig
@@ -299,6 +301,7 @@ def do_add_empty(db, title, authors, isbn):
if isbn:
mi.isbn = isbn
db.import_book(mi, [])
+ write_dirtied()
send_message()
def command_add(args, dbpath):
@@ -452,6 +455,7 @@ def do_set_metadata(db, id, stream):
db.set_metadata(id, mi)
db.clean()
do_show_metadata(db, id, False)
+ write_dirtied()
send_message()
def set_metadata_option_parser():
diff --git a/src/calibre/library/database2.py b/src/calibre/library/database2.py
index dc320eb011..bdaa643d83 100644
--- a/src/calibre/library/database2.py
+++ b/src/calibre/library/database2.py
@@ -557,7 +557,11 @@ class LibraryDatabase2(LibraryDatabase, SchemaUpgrade, CustomColumns):
def metadata_for_field(self, key):
return self.field_metadata[key]
- def dump_metadata(self, book_ids, remove_from_dirtied=True, commit=True):
+ def dump_metadata(self, book_ids=None, remove_from_dirtied=True, commit=True):
+ 'Write metadata for each record to an individual OPF file'
+ if book_ids is None:
+ book_ids = [x[0] for x in self.conn.get(
+ 'SELECT book FROM metadata_dirtied', all=True)]
for book_id in book_ids:
if not self.data.has_id(book_id):
continue
From c67a9d848745c4fd4280b370583160bad288cbbd Mon Sep 17 00:00:00 2001
From: Charles Haley <>
Date: Fri, 24 Sep 2010 16:38:11 +0100
Subject: [PATCH 117/412] Changes to device editable columns to give fine-grain
control over what columns can be edited.
---
src/calibre/devices/folder_device/driver.py | 2 +-
src/calibre/devices/interface.py | 2 +-
src/calibre/devices/kobo/driver.py | 16 +++++++-------
src/calibre/devices/prs505/driver.py | 2 +-
src/calibre/devices/usbms/driver.py | 2 +-
src/calibre/gui2/library/models.py | 23 ++++++++++++++-------
6 files changed, 27 insertions(+), 20 deletions(-)
diff --git a/src/calibre/devices/folder_device/driver.py b/src/calibre/devices/folder_device/driver.py
index 9cd1280cc9..5919d6d2fb 100644
--- a/src/calibre/devices/folder_device/driver.py
+++ b/src/calibre/devices/folder_device/driver.py
@@ -38,7 +38,7 @@ class FOLDER_DEVICE(USBMS):
THUMBNAIL_HEIGHT = 68 # Height for thumbnails on device
- CAN_SET_METADATA = True
+ CAN_SET_METADATA = ['title', 'authors']
SUPPORTS_SUB_DIRS = True
#: Icon for this device
diff --git a/src/calibre/devices/interface.py b/src/calibre/devices/interface.py
index fc3332a337..2307bf94d6 100644
--- a/src/calibre/devices/interface.py
+++ b/src/calibre/devices/interface.py
@@ -37,7 +37,7 @@ class DevicePlugin(Plugin):
THUMBNAIL_HEIGHT = 68
#: Whether the metadata on books can be set via the GUI.
- CAN_SET_METADATA = True
+ CAN_SET_METADATA = ['title', 'authors', 'collections']
#: Path separator for paths to books on device
path_sep = os.sep
diff --git a/src/calibre/devices/kobo/driver.py b/src/calibre/devices/kobo/driver.py
index f06a804b93..b8516aab4f 100644
--- a/src/calibre/devices/kobo/driver.py
+++ b/src/calibre/devices/kobo/driver.py
@@ -30,7 +30,7 @@ class KOBO(USBMS):
# Ordered list of supported formats
FORMATS = ['epub', 'pdf']
- CAN_SET_METADATA = True
+ CAN_SET_METADATA = ['collections']
VENDOR_ID = [0x2237]
PRODUCT_ID = [0x4161]
@@ -126,7 +126,7 @@ class KOBO(USBMS):
book = self.book_from_path(prefix, lpath, title, authors, mime, date, ContentType, ImageID)
# print 'Update booklist'
book.device_collections = [playlist_map[lpath]] if lpath in playlist_map else []
-
+
if bl.add_book(book, replace_metadata=False):
changed = True
except: # Probably a path encoding error
@@ -250,7 +250,7 @@ class KOBO(USBMS):
# print "Delete file normalized path: " + path
extension = os.path.splitext(path)[1]
ContentType = self.get_content_type_from_extension(extension)
-
+
ContentID = self.contentid_from_path(path, ContentType)
ImageID = self.delete_via_sql(ContentID, ContentType)
@@ -453,7 +453,7 @@ class KOBO(USBMS):
query= 'update content set ReadStatus=0, FirstTimeReading = \'true\' where BookID is Null and ReadStatus = 1 and ContentID like \'file:///mnt/sd/%\''
elif oncard != 'carda' and oncard != 'cardb':
query= 'update content set ReadStatus=0, FirstTimeReading = \'true\' where BookID is Null and ReadStatus = 1 and ContentID not like \'file:///mnt/sd/%\''
-
+
try:
cursor.execute (query)
except:
@@ -489,7 +489,7 @@ class KOBO(USBMS):
query= 'update content set ReadStatus=0, FirstTimeReading = \'true\' where BookID is Null and ReadStatus = 2 and ContentID like \'file:///mnt/sd/%\''
elif oncard != 'carda' and oncard != 'cardb':
query= 'update content set ReadStatus=0, FirstTimeReading = \'true\' where BookID is Null and ReadStatus = 2 and ContentID not like \'file:///mnt/sd/%\''
-
+
try:
cursor.execute (query)
except:
@@ -519,7 +519,7 @@ class KOBO(USBMS):
else:
connection.commit()
# debug_print('Database: Commit set ReadStatus as Finished')
- else: # No collections
+ else: # No collections
# Since no collections exist the ReadStatus needs to be reset to 0 (Unread)
print "Reseting ReadStatus to 0"
# Reset Im_Reading list in the database
@@ -527,7 +527,7 @@ class KOBO(USBMS):
query= 'update content set ReadStatus=0, FirstTimeReading = \'true\' where BookID is Null and ContentID like \'file:///mnt/sd/%\''
elif oncard != 'carda' and oncard != 'cardb':
query= 'update content set ReadStatus=0, FirstTimeReading = \'true\' where BookID is Null and ContentID not like \'file:///mnt/sd/%\''
-
+
try:
cursor.execute (query)
except:
@@ -541,7 +541,7 @@ class KOBO(USBMS):
connection.close()
# debug_print('Finished update_device_database_collections', collections_attributes)
-
+
def sync_booklists(self, booklists, end_session=True):
# debug_print('KOBO: started sync_booklists')
paths = self.get_device_paths()
diff --git a/src/calibre/devices/prs505/driver.py b/src/calibre/devices/prs505/driver.py
index f90a8ab263..7952660c21 100644
--- a/src/calibre/devices/prs505/driver.py
+++ b/src/calibre/devices/prs505/driver.py
@@ -27,7 +27,7 @@ class PRS505(USBMS):
FORMATS = ['epub', 'lrf', 'lrx', 'rtf', 'pdf', 'txt']
- CAN_SET_METADATA = True
+ CAN_SET_METADATA = ['title', 'authors', 'collections']
VENDOR_ID = [0x054c] #: SONY Vendor Id
PRODUCT_ID = [0x031e]
diff --git a/src/calibre/devices/usbms/driver.py b/src/calibre/devices/usbms/driver.py
index a0d1d9dbf8..b4fe5d25fc 100644
--- a/src/calibre/devices/usbms/driver.py
+++ b/src/calibre/devices/usbms/driver.py
@@ -50,7 +50,7 @@ class USBMS(CLI, Device):
book_class = Book
FORMATS = []
- CAN_SET_METADATA = False
+ CAN_SET_METADATA = []
METADATA_CACHE = 'metadata.calibre'
def get_device_information(self, end_session=True):
diff --git a/src/calibre/gui2/library/models.py b/src/calibre/gui2/library/models.py
index af1b42bf33..8efd038db8 100644
--- a/src/calibre/gui2/library/models.py
+++ b/src/calibre/gui2/library/models.py
@@ -907,7 +907,7 @@ class DeviceBooksModel(BooksModel): # {{{
}
self.marked_for_deletion = {}
self.search_engine = OnDeviceSearch(self)
- self.editable = True
+ self.editable = ['title', 'authors', 'collections']
self.book_in_library = None
def mark_for_deletion(self, job, rows, rows_are_ids=False):
@@ -953,13 +953,13 @@ class DeviceBooksModel(BooksModel): # {{{
if self.map[index.row()] in self.indices_to_be_deleted():
return Qt.ItemIsUserCheckable # Can't figure out how to get the disabled flag in python
flags = QAbstractTableModel.flags(self, index)
- if index.isValid() and self.editable:
+ if index.isValid():
cname = self.column_map[index.column()]
- if cname in ('title', 'authors') or \
- (cname == 'collections' and \
- callable(getattr(self.db, 'supports_collections', None)) and \
- self.db.supports_collections() and \
- prefs['manage_device_metadata']=='manual'):
+ if cname in self.editable and \
+ cname != 'collections' or \
+ (callable(getattr(self.db, 'supports_collections', None)) and \
+ self.db.supports_collections() and \
+ prefs['manage_device_metadata']=='manual'):
flags |= Qt.ItemIsEditable
return flags
@@ -1243,7 +1243,14 @@ class DeviceBooksModel(BooksModel): # {{{
def set_editable(self, editable):
# Cannot edit if metadata is sent on connect. Reason: changes will
# revert to what is in the library on next connect.
- self.editable = editable and prefs['manage_device_metadata']!='on_connect'
+ if isinstance(editable, list):
+ self.editable = editable
+ elif editable:
+ self.editable = ['title', 'authors', 'collections']
+ else:
+ self.editable = []
+ if prefs['manage_device_metadata']=='on_connect':
+ self.editable = []
def set_search_restriction(self, s):
pass
From 993983a70767b56d2ecde432fcea828068d8e7f9 Mon Sep 17 00:00:00 2001
From: Kovid Goyal
Date: Fri, 24 Sep 2010 10:05:54 -0600
Subject: [PATCH 118/412] Oops. Restore removed call to commit in set_path and
have set_path call dirtied. Also limit the rate of metadata backups
---
src/calibre/library/caches.py | 1 +
src/calibre/library/database2.py | 2 ++
2 files changed, 3 insertions(+)
diff --git a/src/calibre/library/caches.py b/src/calibre/library/caches.py
index 714579ec77..339f1393f5 100644
--- a/src/calibre/library/caches.py
+++ b/src/calibre/library/caches.py
@@ -48,6 +48,7 @@ class MetadataBackup(Thread): # {{{
time.sleep(2)
if not self.dump_func([id_]):
prints('Failed to backup metadata for id:', id_, 'again, giving up')
+ time.sleep(0.2) # Limit to five per second
# }}}
diff --git a/src/calibre/library/database2.py b/src/calibre/library/database2.py
index a34ef9cf89..f62c4ce074 100644
--- a/src/calibre/library/database2.py
+++ b/src/calibre/library/database2.py
@@ -455,6 +455,8 @@ class LibraryDatabase2(LibraryDatabase, SchemaUpgrade, CustomColumns):
self.add_format(id, format, stream, index_is_id=True,
path=tpath, notify=False)
self.conn.execute('UPDATE books SET path=? WHERE id=?', (path, id))
+ self.dirtied([id], commit=False)
+ self.commit()
self.data.set(id, self.FIELD_MAP['path'], path, row_is_id=True)
# Delete not needed directories
if current_path and os.path.exists(spath):
From a11ccd8598d3ba3cf34599647d58666b49038302 Mon Sep 17 00:00:00 2001
From: Charles Haley <>
Date: Fri, 24 Sep 2010 17:20:26 +0100
Subject: [PATCH 119/412] Added 'contains' function to templates
---
src/calibre/manual/template_lang.rst | 1 +
src/calibre/utils/formatter.py | 13 ++++++++++---
2 files changed, 11 insertions(+), 3 deletions(-)
diff --git a/src/calibre/manual/template_lang.rst b/src/calibre/manual/template_lang.rst
index 5f672c4989..0c3a87a157 100644
--- a/src/calibre/manual/template_lang.rst
+++ b/src/calibre/manual/template_lang.rst
@@ -108,6 +108,7 @@ The functions available are:
* ``capitalize()`` -- return the value as capitalized.
* ``ifempty(text)`` -- if the field is not empty, return the value of the field. Otherwise return `text`.
* ``test(text if not empty, text if empty)`` -- return `text if not empty` if the field is not empty, otherwise return `text if empty`.
+ * ``contains(pattern, text if match, text if not match`` -- checks if field contains matches for the regular expression `pattern`. Returns `text if match` if matches are found, otherwise it returns `text if no match`.
* ``shorten(left chars, middle text, right chars)`` -- Return a shortened version of the field, consisting of `left chars` characters from the beginning of the field, followed by `middle text`, followed by `right chars` characters from the end of the string. `Left chars` and `right chars` must be integers. For example, assume the title of the book is `Ancient English Laws in the Times of Ivanhoe`, and you want it to fit in a space of at most 15 characters. If you use ``{title:shorten(9,-,5)}``, the result will be `Ancient E-nhoe`. If the field's length is less than ``left chars`` + ``right chars`` + the length of ``middle text``, then the field will be used intact. For example, the title `The Dome` would not be changed.
* ``lookup(field if not empty, field if empty)`` -- like test, except the arguments are field (metadata) names, not text. The value of the appropriate field will be fetched and used. Note that because composite columns are fields, you can use this function in one composite field to use the value of some other composite field. This is extremely useful when constructing variable save paths (more later).
* ``re(pattern, replacement)`` -- return the field after applying the regular expression. All instances of `pattern` are replaced with `replacement`. As in all of |app|, these are python-compatible regular expressions.
diff --git a/src/calibre/utils/formatter.py b/src/calibre/utils/formatter.py
index c6bcaa1c3e..6fed4e157a 100644
--- a/src/calibre/utils/formatter.py
+++ b/src/calibre/utils/formatter.py
@@ -29,6 +29,15 @@ class TemplateFormatter(string.Formatter):
else:
return value_not_set
+ def _contains(self, val, test, value_if_present, value_if_not):
+ if re.search(test, val):
+ return value_if_present
+ else:
+ return value_if_not
+
+ def _re(self, val, pattern, replacement):
+ return re.sub(pattern, replacement, val)
+
def _ifempty(self, val, value_if_empty):
if val:
return val
@@ -43,14 +52,12 @@ class TemplateFormatter(string.Formatter):
else:
return val
- def _re(self, val, pattern, replacement):
- return re.sub(pattern, replacement, val)
-
functions = {
'uppercase' : (0, lambda s,x: x.upper()),
'lowercase' : (0, lambda s,x: x.lower()),
'titlecase' : (0, lambda s,x: x.title()),
'capitalize' : (0, lambda s,x: x.capitalize()),
+ 'contains' : (3, _contains),
'ifempty' : (1, _ifempty),
'lookup' : (2, _lookup),
're' : (2, _re),
From 7d9ca9dda75a03ae6d8b97660e05017193cc8ba3 Mon Sep 17 00:00:00 2001
From: Kovid Goyal
Date: Fri, 24 Sep 2010 10:40:53 -0600
Subject: [PATCH 120/412] ...
---
src/calibre/library/caches.py | 8 ++++++--
1 file changed, 6 insertions(+), 2 deletions(-)
diff --git a/src/calibre/library/caches.py b/src/calibre/library/caches.py
index 339f1393f5..1e52350e46 100644
--- a/src/calibre/library/caches.py
+++ b/src/calibre/library/caches.py
@@ -97,8 +97,12 @@ class CoverCache(Thread): # {{{
import traceback
traceback.print_exc()
continue
- with self.lock:
- self.cache[id_] = img
+ try:
+ with self.lock:
+ self.cache[id_] = img
+ except:
+ # Happens during interpreter shutdown
+ break
def set_cache(self, ids):
with self.lock:
From f782ef0cb6348cc3deea6fe515ace73b5dd18b92 Mon Sep 17 00:00:00 2001
From: Charles Haley <>
Date: Fri, 24 Sep 2010 17:56:27 +0100
Subject: [PATCH 121/412] Make format_field return '' instead of None when the
value really is ''
---
src/calibre/ebooks/metadata/book/base.py | 18 +++++++++---------
src/calibre/gui2/library/models.py | 2 +-
src/calibre/library/server/mobile.py | 3 +--
src/calibre/library/server/opds.py | 2 +-
4 files changed, 12 insertions(+), 13 deletions(-)
diff --git a/src/calibre/ebooks/metadata/book/base.py b/src/calibre/ebooks/metadata/book/base.py
index 8791d59242..87d034aba8 100644
--- a/src/calibre/ebooks/metadata/book/base.py
+++ b/src/calibre/ebooks/metadata/book/base.py
@@ -432,14 +432,14 @@ class Metadata(object):
if key in self.user_metadata_keys():
res = self.get(key, None)
cmeta = self.get_user_metadata(key, make_copy=False)
+ name = unicode(cmeta['name'])
if cmeta['datatype'] != 'composite' and (res is None or res == ''):
- return (None, None, None, None)
+ return (name, res, None, None)
orig_res = res
cmeta = self.get_user_metadata(key, make_copy=False)
if res is None or res == '':
- return (None, None, None, None)
+ return (name, res, None, None)
orig_res = res
- name = unicode(cmeta['name'])
datatype = cmeta['datatype']
if datatype == 'text' and cmeta['is_multiple']:
res = u', '.join(res)
@@ -454,11 +454,12 @@ class Metadata(object):
if key in field_metadata and field_metadata[key]['kind'] == 'field':
res = self.get(key, None)
- if res is None or res == '':
- return (None, None, None, None)
- orig_res = res
fmeta = field_metadata[key]
name = unicode(fmeta['name'])
+ if res is None or res == '':
+ return (name, res, None, None)
+ orig_res = res
+ name = unicode(fmeta['name'])
datatype = fmeta['datatype']
if key == 'authors':
res = authors_to_string(res)
@@ -508,9 +509,8 @@ class Metadata(object):
fmt('Rights', unicode(self.rights))
for key in self.user_metadata_keys():
val = self.get(key, None)
- if val is not None:
- (name, val) = self.format_field(key)
- fmt(name, unicode(val))
+ (name, val) = self.format_field(key)
+ fmt(name, unicode(val))
return u'\n'.join(ans)
def to_html(self):
diff --git a/src/calibre/gui2/library/models.py b/src/calibre/gui2/library/models.py
index d19bed49fe..fe1701a918 100644
--- a/src/calibre/gui2/library/models.py
+++ b/src/calibre/gui2/library/models.py
@@ -327,7 +327,7 @@ class BooksModel(QAbstractTableModel): # {{{
mi = self.db.get_metadata(idx)
for key in mi.user_metadata_keys():
name, val = mi.format_field(key)
- if val is not None:
+ if val:
data[name] = val
return data
diff --git a/src/calibre/library/server/mobile.py b/src/calibre/library/server/mobile.py
index 071c7b1077..c51de90c6d 100644
--- a/src/calibre/library/server/mobile.py
+++ b/src/calibre/library/server/mobile.py
@@ -125,7 +125,6 @@ def build_index(books, num, search, sort, order, start, total, url_base, CKEYS):
series = u'[%s - %s]'%(book['series'], book['series_index']) \
if book['series'] else ''
tags = u'Tags=[%s]'%book['tags'] if book['tags'] else ''
- print tags
ctext = ''
for key in CKEYS:
@@ -231,7 +230,7 @@ class MobileServer(object):
return '%s:#:%s'%(name, unicode(val))
mi = self.db.get_metadata(record[CFM['id']['rec_index']], index_is_id=True)
name, val = mi.format_field(key)
- if val is None:
+ if not val:
continue
datatype = CFM[key]['datatype']
if datatype in ['comments']:
diff --git a/src/calibre/library/server/opds.py b/src/calibre/library/server/opds.py
index 0e6917c504..bd5b2f36b3 100644
--- a/src/calibre/library/server/opds.py
+++ b/src/calibre/library/server/opds.py
@@ -160,7 +160,7 @@ def ACQUISITION_ENTRY(item, version, db, updated, CFM, CKEYS):
for key in CKEYS:
mi = db.get_metadata(item[CFM['id']['rec_index']], index_is_id=True)
name, val = mi.format_field(key)
- if val is not None:
+ if not val:
datatype = CFM[key]['datatype']
if datatype == 'text' and CFM[key]['is_multiple']:
extra.append('%s: %s '%(name, format_tag_string(val, ',',
From fb06e4c72eacb21b6f101d6f7e7d7a1785450a86 Mon Sep 17 00:00:00 2001
From: Kovid Goyal
Date: Fri, 24 Sep 2010 11:04:18 -0600
Subject: [PATCH 122/412] ...
---
src/calibre/gui2/add.py | 7 ++-----
src/calibre/library/database2.py | 2 +-
2 files changed, 3 insertions(+), 6 deletions(-)
diff --git a/src/calibre/gui2/add.py b/src/calibre/gui2/add.py
index 9f246aeb93..1d7b5075b4 100644
--- a/src/calibre/gui2/add.py
+++ b/src/calibre/gui2/add.py
@@ -381,11 +381,7 @@ class Adder(QObject): # {{{
# }}}
-###############################################################################
-############################## END ADDER ######################################
-###############################################################################
-
-class Saver(QObject):
+class Saver(QObject): # {{{
def __init__(self, parent, db, callback, rows, path, opts,
spare_server=None):
@@ -446,4 +442,5 @@ class Saver(QObject):
self.pd.set_msg(_('Saved')+' '+title)
if not ok:
self.failures.add((title, tb))
+# }}}
diff --git a/src/calibre/library/database2.py b/src/calibre/library/database2.py
index a9160f976f..4775e13818 100644
--- a/src/calibre/library/database2.py
+++ b/src/calibre/library/database2.py
@@ -1924,7 +1924,7 @@ class LibraryDatabase2(LibraryDatabase, SchemaUpgrade, CustomColumns):
mi.timestamp = utcnow()
if mi.pubdate is None:
mi.pubdate = utcnow()
- self.set_metadata(id, mi)
+ self.set_metadata(id, mi, ignore_errors=True)
if cover is not None:
try:
self.set_cover(id, cover)
From 4b92c7d68b70c03b3d1b46d17fc643ab7bb00f5d Mon Sep 17 00:00:00 2001
From: Charles Haley <>
Date: Fri, 24 Sep 2010 18:17:15 +0100
Subject: [PATCH 123/412] Don't put '' values into __unicode__ and to_html
---
src/calibre/ebooks/metadata/book/base.py | 7 ++++---
1 file changed, 4 insertions(+), 3 deletions(-)
diff --git a/src/calibre/ebooks/metadata/book/base.py b/src/calibre/ebooks/metadata/book/base.py
index 87d034aba8..df64d16c26 100644
--- a/src/calibre/ebooks/metadata/book/base.py
+++ b/src/calibre/ebooks/metadata/book/base.py
@@ -509,8 +509,9 @@ class Metadata(object):
fmt('Rights', unicode(self.rights))
for key in self.user_metadata_keys():
val = self.get(key, None)
- (name, val) = self.format_field(key)
- fmt(name, unicode(val))
+ if val:
+ (name, val) = self.format_field(key)
+ fmt(name, unicode(val))
return u'\n'.join(ans)
def to_html(self):
@@ -533,7 +534,7 @@ class Metadata(object):
ans += [(_('Rights'), unicode(self.rights))]
for key in self.user_metadata_keys():
val = self.get(key, None)
- if val is not None:
+ if val:
(name, val) = self.format_field(key)
ans += [(name, val)]
for i, x in enumerate(ans):
From fef5703c1eb74e80c7c5a079f8b5ba3dc473bde8 Mon Sep 17 00:00:00 2001
From: Kovid Goyal
Date: Fri, 24 Sep 2010 11:32:21 -0600
Subject: [PATCH 124/412] Conversion pipeline: Fix merging of metadata, broken
by new Metadata class
---
src/calibre/ebooks/metadata/book/base.py | 5 ++++
src/calibre/ebooks/oeb/transforms/metadata.py | 30 +++++++++----------
2 files changed, 20 insertions(+), 15 deletions(-)
diff --git a/src/calibre/ebooks/metadata/book/base.py b/src/calibre/ebooks/metadata/book/base.py
index 87d034aba8..28a5f21a46 100644
--- a/src/calibre/ebooks/metadata/book/base.py
+++ b/src/calibre/ebooks/metadata/book/base.py
@@ -76,6 +76,11 @@ class Metadata(object):
self.author = list(authors) if authors else []# Needed for backward compatibility
self.authors = list(authors) if authors else []
+ def is_null(self, field):
+ null_val = NULL_VALUES.get(field, None)
+ val = getattr(self, field, None)
+ return not val or val == null_val
+
def __getattribute__(self, field):
_data = object.__getattribute__(self, '_data')
if field in TOP_LEVEL_CLASSIFIERS:
diff --git a/src/calibre/ebooks/oeb/transforms/metadata.py b/src/calibre/ebooks/oeb/transforms/metadata.py
index 22a89f5a47..4bb25f650e 100644
--- a/src/calibre/ebooks/oeb/transforms/metadata.py
+++ b/src/calibre/ebooks/oeb/transforms/metadata.py
@@ -12,33 +12,33 @@ from calibre import guess_type
def meta_info_to_oeb_metadata(mi, m, log):
from calibre.ebooks.oeb.base import OPF
- if mi.title:
+ if not mi.is_null('title'):
m.clear('title')
m.add('title', mi.title)
if mi.title_sort:
if not m.title:
m.add('title', mi.title_sort)
m.title[0].file_as = mi.title_sort
- if mi.authors:
+ if not mi.is_null('authors'):
m.filter('creator', lambda x : x.role.lower() in ['aut', ''])
for a in mi.authors:
attrib = {'role':'aut'}
if mi.author_sort:
attrib[OPF('file-as')] = mi.author_sort
m.add('creator', a, attrib=attrib)
- if mi.book_producer:
+ if not mi.is_null('book_producer'):
m.filter('contributor', lambda x : x.role.lower() == 'bkp')
m.add('contributor', mi.book_producer, role='bkp')
- if mi.comments:
+ if not mi.is_null('comments'):
m.clear('description')
m.add('description', mi.comments)
- if mi.publisher:
+ if not mi.is_null('publisher'):
m.clear('publisher')
m.add('publisher', mi.publisher)
- if mi.series:
+ if not mi.is_null('series'):
m.clear('series')
m.add('series', mi.series)
- if mi.isbn:
+ if not mi.is_null('isbn'):
has = False
for x in m.identifier:
if x.scheme.lower() == 'isbn':
@@ -46,29 +46,29 @@ def meta_info_to_oeb_metadata(mi, m, log):
has = True
if not has:
m.add('identifier', mi.isbn, scheme='ISBN')
- if mi.language:
+ if not mi.is_null('language'):
m.clear('language')
m.add('language', mi.language)
- if mi.series_index is not None:
+ if not mi.is_null('series_index'):
m.clear('series_index')
m.add('series_index', mi.format_series_index())
- if mi.rating is not None:
+ if not mi.is_null('rating'):
m.clear('rating')
m.add('rating', '%.2f'%mi.rating)
- if mi.tags:
+ if not mi.is_null('tags'):
m.clear('subject')
for t in mi.tags:
m.add('subject', t)
- if mi.pubdate is not None:
+ if not mi.is_null('pubdate'):
m.clear('date')
m.add('date', isoformat(mi.pubdate))
- if mi.timestamp is not None:
+ if not mi.is_null('timestamp'):
m.clear('timestamp')
m.add('timestamp', isoformat(mi.timestamp))
- if mi.rights is not None:
+ if not mi.is_null('rights'):
m.clear('rights')
m.add('rights', mi.rights)
- if mi.publication_type is not None:
+ if not mi.is_null('publication_type'):
m.clear('publication_type')
m.add('publication_type', mi.publication_type)
if not m.timestamp:
From 47ff1ddc42d8d08e145af1ebefaa38b93579b549 Mon Sep 17 00:00:00 2001
From: Charles Haley <>
Date: Fri, 24 Sep 2010 18:47:44 +0100
Subject: [PATCH 125/412] Minor updates to the FAQ
---
src/calibre/manual/template_lang.rst | 14 +++++++-------
1 file changed, 7 insertions(+), 7 deletions(-)
diff --git a/src/calibre/manual/template_lang.rst b/src/calibre/manual/template_lang.rst
index 0c3a87a157..1ab004f3f3 100644
--- a/src/calibre/manual/template_lang.rst
+++ b/src/calibre/manual/template_lang.rst
@@ -17,14 +17,14 @@ For the book "The Foundation" by "Isaac Asimov" it will become::
Asimov, Isaac/The Foundation/The Foundation - Isaac Asimov
-You can use all the various metadata fields available in calibre in a template, including any custom columns you have created yourself. To find out the template name for a column simply hover your mouse over the column header. Names for custom fields (columns you have created yourself) always have a # as the first character. For series type custom fields, there is always an additional field named ``#seriesname_index`` that becomes the series index for that series. So if you have a custom series field named #myseries, there will also be a field named #myseries_index.
+You can use all the various metadata fields available in calibre in a template, including any custom columns you have created yourself. To find out the template name for a column simply hover your mouse over the column header. Names for custom fields (columns you have created yourself) always have a # as the first character. For series type custom fields, there is always an additional field named ``#seriesname_index`` that becomes the series index for that series. So if you have a custom series field named ``#myseries``, there will also be a field named ``#myseries_index``.
In addition to the column based fields, you also can use::
{formats} - A list of formats available in the calibre library for a book
{isbn} - The ISBN number of the book
-If a particular book does not have a particular piece of metadata, the field in the template is automatically removed for that book. So for example::
+If a particular book does not have a particular piece of metadata, the field in the template is automatically removed for that book. Consider, for example::
{author_sort}/{series}/{title} {series_index}
@@ -44,19 +44,19 @@ Advanced formatting
You can do more than just simple substitution with the templates. You can also conditionally include text and control how the substituted data is formatted.
-First, conditionally including text. There are cases where you might want to have text appear in the output only if a field is not empty. A common case is series and series_index, where you want either nothing or the two values with a hyphen between them. Calibre handles this case using a special field syntax.
+First, conditionally including text. There are cases where you might want to have text appear in the output only if a field is not empty. A common case is ``series`` and ``series_index``, where you want either nothing or the two values with a hyphen between them. Calibre handles this case using a special field syntax.
For example, assume you want to use the template::
{series} - {series_index} - {title}
-If the book has no series, the answer will be '- - title'. Many people would rather the result be simply 'title', without the hyphens. To do this, use the extended syntax ``{field:|prefix_text|suffix_text}``. When you use this syntax, if field has the value SERIES then the result will be prefix_textSERIESsuffix_text. If field has no value, then the result will be the empty string (nothing). The prefix and suffix can contain blanks.
+If the book has no series, the answer will be ``- - title``. Many people would rather the result be simply ``title``, without the hyphens. To do this, use the extended syntax ``{field:|prefix_text|suffix_text}``. When you use this syntax, if field has the value SERIES then the result will be ``prefix_textSERIESsuffix_text``. If field has no value, then the result will be the empty string (nothing); the prefix and suffix are ignored. The prefix and suffix can contain blanks.
Using this syntax, we can solve the above series problem with the template::
{series}{series_index:| - | - }{title}
-The hyphens will be included only if the book has a series index.
+The hyphens will be included only if the book has a series index, which it will have only if it has a series.
Notes: you must include the : character if you want to use a prefix or a suffix. You must either use no \| characters or both of them; using one, as in ``{field:| - }``, is not allowed. It is OK not to provide any text for one side or the other, such as in ``{series:|| - }``. Using ``{title:||}`` is the same as using ``{title}``.
@@ -85,7 +85,7 @@ Advanced features
Using templates in custom columns
----------------------------------
-There are sometimes cases where you want to display metadata that |app| does not normally display, or to display data in a way different from how |app| normally does. For example, you might want to display the ISBN, a field that |app| does not display. You can use custom columns for this. To do so, you create a column with the type 'column built from other columns' (hereafter called composite columns), enter a template, and |app| will display in the column the result of evaluating that template. To display the isbn, create the column and enter ``{isbn}`` into the template box. To display a column containing the values of two series custom columns separated by a comma, use ``{#series1:||,}{#series2}``.
+There are sometimes cases where you want to display metadata that |app| does not normally display, or to display data in a way different from how |app| normally does. For example, you might want to display the ISBN, a field that |app| does not display. You can use custom columns for this by creating a column with the type 'column built from other columns' (hereafter called composite columns), and entering a template. Result: |app| will display a column showing the result of evaluating that template. To display the ISBN, create the column and enter ``{isbn}`` into the template box. To display a column containing the values of two series custom columns separated by a comma, use ``{#series1:||,}{#series2}``.
Composite columns can use any template option, including formatting.
@@ -98,7 +98,7 @@ Suppose you want to display the value of a field in upper case, when that field
Function references replace the formatting specification, going after the : and before the first ``|`` or the closing ``}``. Functions must always end with ``()``. Some functions take extra values (arguments), and these go inside the ``()``.
-The syntax for using functions is ``{field:function(arguments)}``, or ``{field:function(arguments)|prefix|suffix}``. Argument values cannot contain a comma, because it is used to separate arguments. Functions return the value of the field used in the template, suitably modified.
+The syntax for using functions is ``{field:function(arguments)}``, or ``{field:function(arguments)|prefix|suffix}``. Argument values cannot contain a comma, because it is used to separate arguments. The last (or only) argument cannot contain a closing parenthesis ( ')' ). Functions return the value of the field used in the template, suitably modified.
The functions available are:
From cf6f251b740d8601c80e4e3e4a28ad736ebff7d2 Mon Sep 17 00:00:00 2001
From: Charles Haley <>
Date: Fri, 24 Sep 2010 19:41:43 +0100
Subject: [PATCH 126/412] Added dirty bit cache
---
src/calibre/gui2/ui.py | 1 +
src/calibre/library/database2.py | 31 ++++++++++++++++++++++++++++++-
2 files changed, 31 insertions(+), 1 deletion(-)
diff --git a/src/calibre/gui2/ui.py b/src/calibre/gui2/ui.py
index 88a8c68572..6b04f6fa1f 100644
--- a/src/calibre/gui2/ui.py
+++ b/src/calibre/gui2/ui.py
@@ -533,6 +533,7 @@ class Main(MainWindow, MainWindowMixin, DeviceMixin, # {{{
# Save the current field_metadata for applications like calibre2opds
# Goes here, because if cf is valid, db is valid.
db.prefs['field_metadata'] = db.field_metadata.all_metadata()
+ db.commit_dirty_cache()
if DEBUG and db.gm_count > 0:
print 'get_metadata cache: {0:d} calls, {1:4.2f}% misses'.format(
db.gm_count, (db.gm_missed*100.0)/db.gm_count)
diff --git a/src/calibre/library/database2.py b/src/calibre/library/database2.py
index 4775e13818..c4d2666dd1 100644
--- a/src/calibre/library/database2.py
+++ b/src/calibre/library/database2.py
@@ -340,6 +340,7 @@ class LibraryDatabase2(LibraryDatabase, SchemaUpgrade, CustomColumns):
setattr(self, 'title_sort', functools.partial(self.get_property,
loc=self.FIELD_MAP['sort']))
+ self.dirtied_cache = set()
d = self.conn.get('SELECT book FROM metadata_dirtied', all=True)
for x in d:
self.dirtied_queue.put(x[0])
@@ -585,12 +586,20 @@ class LibraryDatabase2(LibraryDatabase, SchemaUpgrade, CustomColumns):
if remove_from_dirtied:
self.conn.execute('DELETE FROM metadata_dirtied WHERE book=?',
(book_id,))
+ # if a later exception prevents the commit, then the dirtied
+ # table will still have the book. No big deal, because the OPF
+ # is there and correct. We will simply do it again on next
+ # start
+ self.dirtied_cache.discard(book_id)
if commit:
self.conn.commit()
return True
def dirtied(self, book_ids, commit=True):
for book in book_ids:
+ if book in self.dirtied_cache:
+ print 'in dirty cache', book
+ continue
try:
self.conn.execute(
'INSERT INTO metadata_dirtied (book) VALUES (?)',
@@ -598,10 +607,30 @@ class LibraryDatabase2(LibraryDatabase, SchemaUpgrade, CustomColumns):
self.dirtied_queue.put(book)
except IntegrityError:
# Already in table
- continue
+ pass
+ # If the commit doesn't happen, then our cache will be wrong. This
+ # could lead to a problem because we won't put the book back into
+ # the dirtied table. We deal with this by writing the dirty cache
+ # back to the table on GUI exit. Not perfect, but probably OK
+ self.dirtied_cache.add(book)
+ print 'added book', book
if commit:
self.conn.commit()
+ def commit_dirty_cache(self):
+ '''
+ Set the dirty indication for every book in the cache. The vast majority
+ of the time, the indication will already be set. However, sometimes
+ exceptions may have prevented a commit, which may remove some dirty
+ indications from the DB. This call will put them back. Note that there
+ is no problem with setting a dirty indication for a book that isn't in
+ fact dirty. Just wastes a few cycles.
+ '''
+ print 'commit cache'
+ book_ids = list(self.dirtied_cache)
+ self.dirtied_cache = set()
+ self.dirtied(book_ids)
+
def get_metadata(self, idx, index_is_id=False, get_cover=False):
'''
Convenience method to return metadata as a :class:`Metadata` object.
From b2b5e20c8f8c8a48eae23b288be7f347e09c0441 Mon Sep 17 00:00:00 2001
From: Kovid Goyal
Date: Fri, 24 Sep 2010 13:51:22 -0600
Subject: [PATCH 127/412] Fourth beta
---
src/calibre/constants.py | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/src/calibre/constants.py b/src/calibre/constants.py
index 4c372c63a5..be387d8ca2 100644
--- a/src/calibre/constants.py
+++ b/src/calibre/constants.py
@@ -2,7 +2,7 @@ __license__ = 'GPL v3'
__copyright__ = '2008, Kovid Goyal kovid@kovidgoyal.net'
__docformat__ = 'restructuredtext en'
__appname__ = 'calibre'
-__version__ = '0.7.902'
+__version__ = '0.7.903'
__author__ = "Kovid Goyal "
import re
From 03e81f7654f4e296ad0ede2164f3f360289389b3 Mon Sep 17 00:00:00 2001
From: Kovid Goyal
Date: Sat, 25 Sep 2010 04:42:57 +0000
Subject: [PATCH 128/412] Launchpad automatic translations update.
---
src/calibre/translations/ar.po | 701 ++--
src/calibre/translations/ca.po | 701 ++--
src/calibre/translations/da.po | 703 ++--
src/calibre/translations/eu.po | 703 ++--
src/calibre/translations/fr.po | 703 ++--
src/calibre/translations/hu.po | 5078 +++++++++++++++--------------
src/calibre/translations/ja.po | 701 ++--
src/calibre/translations/ko.po | 703 ++--
src/calibre/translations/nl.po | 701 ++--
src/calibre/translations/pl.po | 701 ++--
src/calibre/translations/pt_BR.po | 703 ++--
src/calibre/translations/ru.po | 703 ++--
src/calibre/translations/sk.po | 703 ++--
src/calibre/translations/sr.po | 710 ++--
src/calibre/translations/th.po | 759 +++--
src/calibre/translations/tr.po | 703 ++--
src/calibre/translations/uk.po | 1025 +++---
src/calibre/translations/vi.po | 703 ++--
src/calibre/translations/zh_CN.po | 701 ++--
19 files changed, 9760 insertions(+), 8345 deletions(-)
diff --git a/src/calibre/translations/ar.po b/src/calibre/translations/ar.po
index d0b5eb08f9..de24181fea 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: 2010-09-17 21:00+0000\n"
-"PO-Revision-Date: 2010-09-17 22:37+0000\n"
+"POT-Creation-Date: 2010-09-24 21:33+0000\n"
+"PO-Revision-Date: 2010-09-24 20:47+0000\n"
"Last-Translator: Hsn \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: 2010-09-18 04:47+0000\n"
+"X-Launchpad-Export-Date: 2010-09-25 04:39+0000\n"
"X-Generator: Launchpad (build Unknown)\n"
#: /home/kovid/work/calibre/src/calibre/customize/__init__.py:43
@@ -24,7 +24,8 @@ msgstr "لا يفعل شيءً"
#: /home/kovid/work/calibre/src/calibre/customize/__init__.py:46
#: /home/kovid/work/calibre/src/calibre/devices/jetbook/driver.py:74
#: /home/kovid/work/calibre/src/calibre/devices/kindle/driver.py:76
-#: /home/kovid/work/calibre/src/calibre/devices/kobo/driver.py:410
+#: /home/kovid/work/calibre/src/calibre/devices/kobo/books.py:46
+#: /home/kovid/work/calibre/src/calibre/devices/kobo/driver.py:412
#: /home/kovid/work/calibre/src/calibre/devices/nook/driver.py:70
#: /home/kovid/work/calibre/src/calibre/devices/nook/driver.py:71
#: /home/kovid/work/calibre/src/calibre/devices/prs500/books.py:267
@@ -105,24 +106,24 @@ msgstr "لا يفعل شيءً"
#: /home/kovid/work/calibre/src/calibre/ebooks/pdf/writer.py:98
#: /home/kovid/work/calibre/src/calibre/ebooks/rtf/input.py:239
#: /home/kovid/work/calibre/src/calibre/ebooks/rtf/input.py:241
-#: /home/kovid/work/calibre/src/calibre/gui2/__init__.py:324
-#: /home/kovid/work/calibre/src/calibre/gui2/__init__.py:331
-#: /home/kovid/work/calibre/src/calibre/gui2/actions/edit_metadata.py:293
+#: /home/kovid/work/calibre/src/calibre/gui2/__init__.py:352
+#: /home/kovid/work/calibre/src/calibre/gui2/__init__.py:359
#: /home/kovid/work/calibre/src/calibre/gui2/actions/edit_metadata.py:296
+#: /home/kovid/work/calibre/src/calibre/gui2/actions/edit_metadata.py:299
#: /home/kovid/work/calibre/src/calibre/gui2/add.py:137
#: /home/kovid/work/calibre/src/calibre/gui2/add.py:144
#: /home/kovid/work/calibre/src/calibre/gui2/convert/__init__.py:42
#: /home/kovid/work/calibre/src/calibre/gui2/convert/metadata.py:111
#: /home/kovid/work/calibre/src/calibre/gui2/convert/metadata.py:136
#: /home/kovid/work/calibre/src/calibre/gui2/convert/metadata.py:138
-#: /home/kovid/work/calibre/src/calibre/gui2/device.py:865
-#: /home/kovid/work/calibre/src/calibre/gui2/device.py:874
-#: /home/kovid/work/calibre/src/calibre/gui2/device.py:1158
-#: /home/kovid/work/calibre/src/calibre/gui2/device.py:1161
+#: /home/kovid/work/calibre/src/calibre/gui2/device.py:869
+#: /home/kovid/work/calibre/src/calibre/gui2/device.py:878
+#: /home/kovid/work/calibre/src/calibre/gui2/device.py:1162
+#: /home/kovid/work/calibre/src/calibre/gui2/device.py:1165
#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/comicconf.py:47
#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/fetch_metadata.py:120
#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/fetch_metadata.py:155
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single.py:552
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single.py:571
#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/scheduler.py:173
#: /home/kovid/work/calibre/src/calibre/gui2/library/models.py:357
#: /home/kovid/work/calibre/src/calibre/gui2/library/models.py:377
@@ -135,11 +136,11 @@ msgstr "لا يفعل شيءً"
#: /home/kovid/work/calibre/src/calibre/library/database.py:913
#: /home/kovid/work/calibre/src/calibre/library/database2.py:375
#: /home/kovid/work/calibre/src/calibre/library/database2.py:387
-#: /home/kovid/work/calibre/src/calibre/library/database2.py:1065
-#: /home/kovid/work/calibre/src/calibre/library/database2.py:1137
-#: /home/kovid/work/calibre/src/calibre/library/database2.py:1837
-#: /home/kovid/work/calibre/src/calibre/library/database2.py:1839
-#: /home/kovid/work/calibre/src/calibre/library/database2.py:1966
+#: /home/kovid/work/calibre/src/calibre/library/database2.py:1064
+#: /home/kovid/work/calibre/src/calibre/library/database2.py:1139
+#: /home/kovid/work/calibre/src/calibre/library/database2.py:1843
+#: /home/kovid/work/calibre/src/calibre/library/database2.py:1845
+#: /home/kovid/work/calibre/src/calibre/library/database2.py:1972
#: /home/kovid/work/calibre/src/calibre/library/server/mobile.py:211
#: /home/kovid/work/calibre/src/calibre/library/server/opds.py:137
#: /home/kovid/work/calibre/src/calibre/library/server/opds.py:140
@@ -260,162 +261,162 @@ msgstr "ضبط دليل المعلومات في الملفات %s"
msgid "Set metadata from %s files"
msgstr "ضبط دليل المعلومات من ملفات %s"
-#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:684
+#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:688
msgid "Look and Feel"
msgstr "المظهر"
-#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:686
-#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:698
-#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:709
-#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:720
+#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:690
+#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:702
+#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:713
+#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:724
msgid "Interface"
msgstr "الواجهة"
-#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:690
+#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:694
msgid "Adjust the look and feel of the calibre interface to suit your tastes"
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:696
+#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:700
msgid "Behavior"
msgstr "سلوك"
-#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:702
+#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:706
msgid "Change the way calibre behaves"
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:707
+#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:711
#: /home/kovid/work/calibre/src/calibre/gui2/library/views.py:176
msgid "Add your own columns"
msgstr "اضف عامودك الخاص"
-#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:713
+#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:717
msgid "Add/remove your own columns to the calibre book list"
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:718
+#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:722
msgid "Customize the toolbar"
msgstr "خصِّص شريط الأدوات"
-#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:724
+#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:728
msgid ""
"Customize the toolbars and context menus, changing which actions are "
"available in each"
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:730
+#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:734
msgid "Input Options"
msgstr "خيارات الإدخال"
-#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:732
-#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:743
-#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:754
+#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:736
+#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:747
+#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:758
msgid "Conversion"
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:736
+#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:740
msgid "Set conversion options specific to each input format"
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:741
+#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:745
msgid "Common Options"
msgstr "خيارات متداولة"
-#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:747
+#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:751
msgid "Set conversion options common to all formats"
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:752
+#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:756
msgid "Output Options"
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:758
+#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:762
msgid "Set conversion options specific to each output format"
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:763
+#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:767
msgid "Adding books"
msgstr "إضافة كتب"
-#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:765
-#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:777
-#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:789
+#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:769
+#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:781
+#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:793
msgid "Import/Export"
msgstr "إستيراد/تصدير"
-#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:769
+#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:773
msgid "Control how calibre reads metadata from files when adding books"
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:775
+#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:779
msgid "Saving books to disk"
msgstr "حفظ الكتب على القرص"
-#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:781
+#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:785
msgid ""
"Control how calibre exports files from its database to disk when using Save "
"to disk"
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:787
+#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:791
msgid "Sending books to devices"
msgstr "ارسال الكتب الى الاجهزة"
-#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:793
+#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:797
msgid "Control how calibre transfers files to your ebook reader"
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:799
+#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:803
msgid "Sharing books by email"
msgstr "مشاركة الكتب عبر البريد الالكتروني"
-#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:801
-#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:813
+#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:805
+#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:817
msgid "Sharing"
msgstr "مشاركة"
-#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:805
+#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:809
msgid ""
"Setup sharing of books via email. Can be used for automatic sending of "
"downloaded news to your devices"
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:811
+#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:815
msgid "Sharing over the net"
msgstr "المشاركة على الشبكة العنكبوتية"
-#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:817
+#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:821
msgid ""
"Setup the calibre Content Server which will give you access to your calibre "
"library from anywhere, on any device, over the internet"
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:824
+#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:828
msgid "Plugins"
msgstr "الملحقات"
-#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:826
-#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:838
-#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:849
+#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:830
+#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:842
+#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:853
msgid "Advanced"
msgstr "متقدّم"
-#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:830
+#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:834
msgid "Add/remove/customize various bits of calibre functionality"
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:836
+#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:840
msgid "Tweaks"
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:842
+#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:846
msgid "Fine tune how calibre behaves in various contexts"
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:847
+#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:851
msgid "Miscellaneous"
msgstr "متفرقات"
-#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:853
+#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:857
msgid "Miscellaneous advanced configuration"
msgstr ""
@@ -592,15 +593,15 @@ msgstr "ملحقات معطلة"
msgid "Enabled plugins"
msgstr "تفعيل الاضافات"
-#: /home/kovid/work/calibre/src/calibre/customize/ui.py:86
+#: /home/kovid/work/calibre/src/calibre/customize/ui.py:93
msgid "No valid plugin found in "
msgstr "لا يجد ملحق صالح "
-#: /home/kovid/work/calibre/src/calibre/customize/ui.py:501
+#: /home/kovid/work/calibre/src/calibre/customize/ui.py:508
msgid "Initialization of plugin %s failed with traceback:"
msgstr "فشل استهلال الملحق s% مع اقتفاء الأثر:"
-#: /home/kovid/work/calibre/src/calibre/customize/ui.py:534
+#: /home/kovid/work/calibre/src/calibre/customize/ui.py:541
msgid ""
" %prog options\n"
"\n"
@@ -612,29 +613,29 @@ msgstr ""
" المقدرة على التخصيص بتحميل الملحقات الخارجية .\n"
" "
-#: /home/kovid/work/calibre/src/calibre/customize/ui.py:540
+#: /home/kovid/work/calibre/src/calibre/customize/ui.py:547
msgid "Add a plugin by specifying the path to the zip file containing it."
msgstr "إضافة ملحق يتخصيص مسار إلى ملف zip الذي يحتويه."
-#: /home/kovid/work/calibre/src/calibre/customize/ui.py:542
+#: /home/kovid/work/calibre/src/calibre/customize/ui.py:549
msgid "Remove a custom plugin by name. Has no effect on builtin plugins"
msgstr "حذف الملحق المخصص عن طريق اسمه. لا يؤثر على الملحقات المضمنة"
-#: /home/kovid/work/calibre/src/calibre/customize/ui.py:544
+#: /home/kovid/work/calibre/src/calibre/customize/ui.py:551
msgid ""
"Customize plugin. Specify name of plugin and customization string separated "
"by a comma."
msgstr "تخصيص الملحق . حدد اسم الملحق وسلسلة التخصيص وفرقهما بفاصلة."
-#: /home/kovid/work/calibre/src/calibre/customize/ui.py:546
+#: /home/kovid/work/calibre/src/calibre/customize/ui.py:553
msgid "List all installed plugins"
msgstr "قائمة كل الملحقات المثبتة"
-#: /home/kovid/work/calibre/src/calibre/customize/ui.py:548
+#: /home/kovid/work/calibre/src/calibre/customize/ui.py:555
msgid "Enable the named plugin"
msgstr "تمكين الملحق المسمى"
-#: /home/kovid/work/calibre/src/calibre/customize/ui.py:550
+#: /home/kovid/work/calibre/src/calibre/customize/ui.py:557
msgid "Disable the named plugin"
msgstr "تعطيل الملحق المسمى"
@@ -642,13 +643,13 @@ msgstr "تعطيل الملحق المسمى"
msgid "Communicate with Android phones."
msgstr "التواصل مع هواتف أندرويد ."
-#: /home/kovid/work/calibre/src/calibre/devices/android/driver.py:50
+#: /home/kovid/work/calibre/src/calibre/devices/android/driver.py:52
msgid ""
"Comma separated list of directories to send e-books to on the device. The "
"first one that exists will be used"
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/devices/android/driver.py:92
+#: /home/kovid/work/calibre/src/calibre/devices/android/driver.py:94
msgid "Communicate with S60 phones."
msgstr "تواصل معا هواتف S60."
@@ -712,7 +713,7 @@ msgstr ""
#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/scheduler.py:244
#: /home/kovid/work/calibre/src/calibre/library/database2.py:198
#: /home/kovid/work/calibre/src/calibre/library/database2.py:211
-#: /home/kovid/work/calibre/src/calibre/library/database2.py:1706
+#: /home/kovid/work/calibre/src/calibre/library/database2.py:1712
#: /home/kovid/work/calibre/src/calibre/library/field_metadata.py:134
msgid "News"
msgstr "الأخبار"
@@ -720,8 +721,8 @@ msgstr "الأخبار"
#: /home/kovid/work/calibre/src/calibre/devices/apple/driver.py:2500
#: /home/kovid/work/calibre/src/calibre/gui2/catalog/catalog_epub_mobi.py:20
#: /home/kovid/work/calibre/src/calibre/library/catalog.py:556
-#: /home/kovid/work/calibre/src/calibre/library/database2.py:1669
-#: /home/kovid/work/calibre/src/calibre/library/database2.py:1687
+#: /home/kovid/work/calibre/src/calibre/library/database2.py:1675
+#: /home/kovid/work/calibre/src/calibre/library/database2.py:1693
msgid "Catalog"
msgstr "الفهرس"
@@ -803,23 +804,23 @@ msgid ""
"first one that exists will be used."
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/devices/hanvon/driver.py:18
+#: /home/kovid/work/calibre/src/calibre/devices/hanvon/driver.py:22
msgid "Communicate with the Hanvon N520 eBook reader."
msgstr "التواصل مع القارئ الكتاب الاليكترونى Hanvon N520 ."
-#: /home/kovid/work/calibre/src/calibre/devices/hanvon/driver.py:40
+#: /home/kovid/work/calibre/src/calibre/devices/hanvon/driver.py:47
msgid "Communicate with The Book reader."
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/devices/hanvon/driver.py:52
+#: /home/kovid/work/calibre/src/calibre/devices/hanvon/driver.py:59
msgid "Communicate with the SpringDesign Alex eBook reader."
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/devices/hanvon/driver.py:68
+#: /home/kovid/work/calibre/src/calibre/devices/hanvon/driver.py:78
msgid "Communicate with the Azbooka"
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/devices/hanvon/driver.py:81
+#: /home/kovid/work/calibre/src/calibre/devices/hanvon/driver.py:94
msgid "Communicate with the Elonex EB 511 eBook reader."
msgstr "اﻹتصال مع الـElonex EB 511 القارئ اﻹلكتروني"
@@ -878,7 +879,7 @@ msgstr ""
#: /home/kovid/work/calibre/src/calibre/devices/kobo/driver.py:63
#: /home/kovid/work/calibre/src/calibre/devices/kobo/driver.py:66
#: /home/kovid/work/calibre/src/calibre/devices/kobo/driver.py:69
-#: /home/kovid/work/calibre/src/calibre/devices/kobo/driver.py:186
+#: /home/kovid/work/calibre/src/calibre/devices/kobo/driver.py:188
#: /home/kovid/work/calibre/src/calibre/devices/usbms/driver.py:68
#: /home/kovid/work/calibre/src/calibre/devices/usbms/driver.py:71
#: /home/kovid/work/calibre/src/calibre/devices/usbms/driver.py:74
@@ -888,33 +889,33 @@ msgstr ""
msgid "Getting list of books on device..."
msgstr "يجري إحصاء قائمة كتب من الجهاز..."
-#: /home/kovid/work/calibre/src/calibre/devices/kobo/driver.py:246
-#: /home/kovid/work/calibre/src/calibre/devices/kobo/driver.py:278
+#: /home/kovid/work/calibre/src/calibre/devices/kobo/driver.py:248
+#: /home/kovid/work/calibre/src/calibre/devices/kobo/driver.py:280
#: /home/kovid/work/calibre/src/calibre/devices/usbms/driver.py:253
#: /home/kovid/work/calibre/src/calibre/devices/usbms/driver.py:271
msgid "Removing books from device..."
msgstr "يجري حذف الكتب من الجهاز..."
-#: /home/kovid/work/calibre/src/calibre/devices/kobo/driver.py:282
-#: /home/kovid/work/calibre/src/calibre/devices/kobo/driver.py:289
+#: /home/kovid/work/calibre/src/calibre/devices/kobo/driver.py:284
+#: /home/kovid/work/calibre/src/calibre/devices/kobo/driver.py:291
#: /home/kovid/work/calibre/src/calibre/devices/usbms/driver.py:278
#: /home/kovid/work/calibre/src/calibre/devices/usbms/driver.py:283
msgid "Removing books from device metadata listing..."
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/devices/kobo/driver.py:294
-#: /home/kovid/work/calibre/src/calibre/devices/kobo/driver.py:328
+#: /home/kovid/work/calibre/src/calibre/devices/kobo/driver.py:296
+#: /home/kovid/work/calibre/src/calibre/devices/kobo/driver.py:330
#: /home/kovid/work/calibre/src/calibre/devices/usbms/driver.py:217
#: /home/kovid/work/calibre/src/calibre/devices/usbms/driver.py:247
msgid "Adding books to device metadata listing..."
msgstr "إضافة كتب لقائمة البيانات الوصفية للجهاز ..."
-#: /home/kovid/work/calibre/src/calibre/devices/kobo/driver.py:390
+#: /home/kovid/work/calibre/src/calibre/devices/kobo/driver.py:392
#: /home/kovid/work/calibre/src/calibre/gui2/actions/add.py:252
msgid "Not Implemented"
msgstr "غير مطبق"
-#: /home/kovid/work/calibre/src/calibre/devices/kobo/driver.py:391
+#: /home/kovid/work/calibre/src/calibre/devices/kobo/driver.py:393
msgid ""
"\".kobo\" files do not exist on the device as books instead, they are rows "
"in the sqlite database. Currently they cannot be exported or viewed."
@@ -3013,7 +3014,7 @@ msgstr ""
msgid "Copy to Clipboard"
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/gui2/__init__.py:434
+#: /home/kovid/work/calibre/src/calibre/gui2/__init__.py:462
msgid "Choose Files"
msgstr ""
@@ -3139,6 +3140,7 @@ msgstr ""
#: /home/kovid/work/calibre/src/calibre/gui2/actions/add.py:240
#: /home/kovid/work/calibre/src/calibre/gui2/actions/delete.py:56
+#: /home/kovid/work/calibre/src/calibre/gui2/actions/tweak_epub.py:28
#: /home/kovid/work/calibre/src/calibre/gui2/actions/view.py:95
#: /home/kovid/work/calibre/src/calibre/gui2/actions/view.py:120
msgid "No book selected"
@@ -3182,8 +3184,8 @@ msgstr ""
#: /home/kovid/work/calibre/src/calibre/gui2/actions/copy_to_library.py:116
#: /home/kovid/work/calibre/src/calibre/gui2/actions/edit_metadata.py:76
#: /home/kovid/work/calibre/src/calibre/gui2/actions/edit_metadata.py:142
-#: /home/kovid/work/calibre/src/calibre/gui2/actions/edit_metadata.py:178
-#: /home/kovid/work/calibre/src/calibre/gui2/actions/edit_metadata.py:205
+#: /home/kovid/work/calibre/src/calibre/gui2/actions/edit_metadata.py:180
+#: /home/kovid/work/calibre/src/calibre/gui2/actions/edit_metadata.py:208
#: /home/kovid/work/calibre/src/calibre/gui2/actions/save_to_disk.py:92
msgid "No books selected"
msgstr ""
@@ -3332,7 +3334,7 @@ msgstr ""
#: /home/kovid/work/calibre/src/calibre/gui2/actions/choose_library.py:249
#: /home/kovid/work/calibre/src/calibre/gui2/actions/choose_library.py:254
#: /home/kovid/work/calibre/src/calibre/gui2/actions/save_to_disk.py:101
-#: /home/kovid/work/calibre/src/calibre/gui2/library/views.py:554
+#: /home/kovid/work/calibre/src/calibre/gui2/library/views.py:584
msgid "Not allowed"
msgstr ""
@@ -3399,7 +3401,7 @@ msgstr ""
#: /home/kovid/work/calibre/src/calibre/gui2/actions/copy_to_library.py:138
#: /home/kovid/work/calibre/src/calibre/gui2/device.py:670
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk.py:424
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk.py:428
msgid "Failed"
msgstr ""
@@ -3636,20 +3638,20 @@ msgid "Error"
msgstr "خطأ"
#: /home/kovid/work/calibre/src/calibre/gui2/actions/edit_metadata.py:141
-#: /home/kovid/work/calibre/src/calibre/gui2/actions/edit_metadata.py:177
+#: /home/kovid/work/calibre/src/calibre/gui2/actions/edit_metadata.py:179
msgid "Cannot edit metadata"
msgstr "لا يمكن تحرير الميتاداتا"
-#: /home/kovid/work/calibre/src/calibre/gui2/actions/edit_metadata.py:204
#: /home/kovid/work/calibre/src/calibre/gui2/actions/edit_metadata.py:207
+#: /home/kovid/work/calibre/src/calibre/gui2/actions/edit_metadata.py:210
msgid "Cannot merge books"
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/gui2/actions/edit_metadata.py:208
+#: /home/kovid/work/calibre/src/calibre/gui2/actions/edit_metadata.py:211
msgid "At least two books must be selected for merging"
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/gui2/actions/edit_metadata.py:212
+#: /home/kovid/work/calibre/src/calibre/gui2/actions/edit_metadata.py:215
msgid ""
"Book formats and metadata from the selected books will be added to the "
"first selected book. ISBN will not be merged.
The "
@@ -3657,7 +3659,7 @@ msgid ""
"changed.
Please confirm you want to proceed."
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/gui2/actions/edit_metadata.py:224
+#: /home/kovid/work/calibre/src/calibre/gui2/actions/edit_metadata.py:227
msgid ""
"Book formats and metadata from the selected books will be merged into the "
"first selected book. ISBN will not be merged.
After "
@@ -3668,7 +3670,7 @@ msgid ""
"you want to proceed?"
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/gui2/actions/edit_metadata.py:237
+#: /home/kovid/work/calibre/src/calibre/gui2/actions/edit_metadata.py:240
msgid ""
"You are about to merge more than 5 books. Are you sure you want to "
"proceed?"
@@ -3850,6 +3852,28 @@ msgstr ""
msgid "Books with the same tags"
msgstr "كتب بنفس الوسوم"
+#: /home/kovid/work/calibre/src/calibre/gui2/actions/tweak_epub.py:15
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/tweak_epub_ui.py:54
+msgid "Tweak ePub"
+msgstr ""
+
+#: /home/kovid/work/calibre/src/calibre/gui2/actions/tweak_epub.py:16
+msgid "Make small changes to ePub format books"
+msgstr ""
+
+#: /home/kovid/work/calibre/src/calibre/gui2/actions/tweak_epub.py:17
+msgid "T"
+msgstr ""
+
+#: /home/kovid/work/calibre/src/calibre/gui2/actions/tweak_epub.py:27
+#: /home/kovid/work/calibre/src/calibre/gui2/actions/tweak_epub.py:39
+msgid "Cannot tweak ePub"
+msgstr ""
+
+#: /home/kovid/work/calibre/src/calibre/gui2/actions/tweak_epub.py:40
+msgid "No ePub available. First convert the book to ePub."
+msgstr ""
+
#: /home/kovid/work/calibre/src/calibre/gui2/actions/view.py:24
msgid "V"
msgstr "V"
@@ -3914,7 +3938,7 @@ msgid "The specified directory could not be processed."
msgstr ""
#: /home/kovid/work/calibre/src/calibre/gui2/add.py:228
-#: /home/kovid/work/calibre/src/calibre/gui2/device.py:807
+#: /home/kovid/work/calibre/src/calibre/gui2/device.py:811
msgid "No books"
msgstr ""
@@ -4039,13 +4063,13 @@ msgstr ""
#: /home/kovid/work/calibre/src/calibre/gui2/device_drivers/configwidget_ui.py:84
#: /home/kovid/work/calibre/src/calibre/gui2/device_drivers/configwidget_ui.py:85
#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/choose_library_ui.py:77
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single_ui.py:369
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single_ui.py:376
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single_ui.py:390
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single_ui.py:401
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single_ui.py:403
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single_ui.py:405
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single_ui.py:375
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single_ui.py:382
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single_ui.py:396
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single_ui.py:407
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single_ui.py:409
#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single_ui.py:411
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single_ui.py:418
#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/saved_search_editor_ui.py:92
#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/saved_search_editor_ui.py:95
#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/tag_categories_ui.py:161
@@ -4788,7 +4812,7 @@ msgid " is not a valid picture"
msgstr " ليست صورة صالحة"
#: /home/kovid/work/calibre/src/calibre/gui2/convert/metadata_ui.py:172
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single_ui.py:407
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single_ui.py:413
msgid "Book Cover"
msgstr "غلاف الكتاب"
@@ -4797,7 +4821,7 @@ msgid "Use cover from &source file"
msgstr "استخدم غلاف من المصدر&"
#: /home/kovid/work/calibre/src/calibre/gui2/convert/metadata_ui.py:174
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single_ui.py:408
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single_ui.py:414
msgid "Change &cover image:"
msgstr "تغيير صورة الغلاف&:"
@@ -4806,18 +4830,18 @@ msgid "Browse for an image to use as the cover of this book."
msgstr ""
#: /home/kovid/work/calibre/src/calibre/gui2/convert/metadata_ui.py:177
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single_ui.py:366
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single_ui.py:372
msgid "&Title: "
msgstr ":ال&عنوان "
#: /home/kovid/work/calibre/src/calibre/gui2/convert/metadata_ui.py:178
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single_ui.py:367
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single_ui.py:373
msgid "Change the title of this book"
msgstr "تغيير عنوان هذا الكتاب"
#: /home/kovid/work/calibre/src/calibre/gui2/convert/metadata_ui.py:179
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:229
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single_ui.py:370
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:230
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single_ui.py:376
msgid "&Author(s): "
msgstr "ال&مؤلف: "
@@ -4832,19 +4856,19 @@ msgid ""
msgstr ""
#: /home/kovid/work/calibre/src/calibre/gui2/convert/metadata_ui.py:182
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:238
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single_ui.py:381
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:239
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single_ui.py:387
msgid "&Publisher: "
msgstr "&الناشر: "
#: /home/kovid/work/calibre/src/calibre/gui2/convert/metadata_ui.py:183
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single_ui.py:382
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single_ui.py:388
msgid "Ta&gs: "
msgstr "الو&سوم: "
#: /home/kovid/work/calibre/src/calibre/gui2/convert/metadata_ui.py:184
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:240
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single_ui.py:383
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:241
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single_ui.py:389
msgid ""
"Tags categorize the book. This is particularly useful while searching. "
"
They can be any words or phrases, separated by commas."
@@ -4853,22 +4877,22 @@ msgstr ""
"مجموعة كلمات، مفرقة بفاصلة."
#: /home/kovid/work/calibre/src/calibre/gui2/convert/metadata_ui.py:185
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:247
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single_ui.py:386
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:248
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single_ui.py:392
msgid "&Series:"
msgstr "&سلسلات:"
#: /home/kovid/work/calibre/src/calibre/gui2/convert/metadata_ui.py:186
#: /home/kovid/work/calibre/src/calibre/gui2/convert/metadata_ui.py:187
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:248
#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:249
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single_ui.py:387
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single_ui.py:388
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:250
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single_ui.py:393
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single_ui.py:394
msgid "List of known series. You can add new series."
msgstr "قائمة السلسلات المعروفة. بإمكانك إضافة سلسلات جديدة."
#: /home/kovid/work/calibre/src/calibre/gui2/convert/metadata_ui.py:188
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single_ui.py:393
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single_ui.py:399
msgid "Book "
msgstr "الكتاب "
@@ -5350,7 +5374,7 @@ msgid " index:"
msgstr ""
#: /home/kovid/work/calibre/src/calibre/gui2/custom_column_widgets.py:451
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:256
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:257
msgid "Automatically number books in this series"
msgstr ""
@@ -5462,132 +5486,132 @@ msgid ""
"reconnect the device and or reboot."
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/gui2/device.py:716
+#: /home/kovid/work/calibre/src/calibre/gui2/device.py:720
msgid "Device: "
msgstr "الجهاز: "
-#: /home/kovid/work/calibre/src/calibre/gui2/device.py:718
+#: /home/kovid/work/calibre/src/calibre/gui2/device.py:722
msgid " detected."
msgstr " تم كشفه."
-#: /home/kovid/work/calibre/src/calibre/gui2/device.py:808
+#: /home/kovid/work/calibre/src/calibre/gui2/device.py:812
msgid "selected to send"
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/gui2/device.py:813
+#: /home/kovid/work/calibre/src/calibre/gui2/device.py:817
msgid "Choose format to send to device"
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/gui2/device.py:822
+#: /home/kovid/work/calibre/src/calibre/gui2/device.py:826
msgid "No device"
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/gui2/device.py:823
+#: /home/kovid/work/calibre/src/calibre/gui2/device.py:827
msgid "Cannot send: No device is connected"
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/gui2/device.py:826
#: /home/kovid/work/calibre/src/calibre/gui2/device.py:830
+#: /home/kovid/work/calibre/src/calibre/gui2/device.py:834
msgid "No card"
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/gui2/device.py:827
#: /home/kovid/work/calibre/src/calibre/gui2/device.py:831
+#: /home/kovid/work/calibre/src/calibre/gui2/device.py:835
msgid "Cannot send: Device has no storage card"
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/gui2/device.py:872
+#: /home/kovid/work/calibre/src/calibre/gui2/device.py:876
msgid "E-book:"
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/gui2/device.py:875
+#: /home/kovid/work/calibre/src/calibre/gui2/device.py:879
msgid "Attached, you will find the e-book"
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/gui2/device.py:876
+#: /home/kovid/work/calibre/src/calibre/gui2/device.py:880
#: /home/kovid/work/calibre/src/calibre/gui2/preferences/plugins.py:107
msgid "by"
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/gui2/device.py:877
+#: /home/kovid/work/calibre/src/calibre/gui2/device.py:881
msgid "in the %s format."
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/gui2/device.py:890
+#: /home/kovid/work/calibre/src/calibre/gui2/device.py:894
msgid "Sending email to"
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/gui2/device.py:920
-#: /home/kovid/work/calibre/src/calibre/gui2/device.py:928
-#: /home/kovid/work/calibre/src/calibre/gui2/device.py:1021
-#: /home/kovid/work/calibre/src/calibre/gui2/device.py:1083
-#: /home/kovid/work/calibre/src/calibre/gui2/device.py:1202
-#: /home/kovid/work/calibre/src/calibre/gui2/device.py:1210
+#: /home/kovid/work/calibre/src/calibre/gui2/device.py:924
+#: /home/kovid/work/calibre/src/calibre/gui2/device.py:932
+#: /home/kovid/work/calibre/src/calibre/gui2/device.py:1025
+#: /home/kovid/work/calibre/src/calibre/gui2/device.py:1087
+#: /home/kovid/work/calibre/src/calibre/gui2/device.py:1206
+#: /home/kovid/work/calibre/src/calibre/gui2/device.py:1214
msgid "No suitable formats"
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/gui2/device.py:921
+#: /home/kovid/work/calibre/src/calibre/gui2/device.py:925
msgid "Auto convert the following books before sending via email?"
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/gui2/device.py:929
+#: /home/kovid/work/calibre/src/calibre/gui2/device.py:933
msgid ""
"Could not email the following books as no suitable formats were found:"
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/gui2/device.py:947
+#: /home/kovid/work/calibre/src/calibre/gui2/device.py:951
msgid "Failed to email books"
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/gui2/device.py:948
+#: /home/kovid/work/calibre/src/calibre/gui2/device.py:952
msgid "Failed to email the following books:"
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/gui2/device.py:952
+#: /home/kovid/work/calibre/src/calibre/gui2/device.py:956
msgid "Sent by email:"
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/gui2/device.py:980
+#: /home/kovid/work/calibre/src/calibre/gui2/device.py:984
msgid "News:"
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/gui2/device.py:981
+#: /home/kovid/work/calibre/src/calibre/gui2/device.py:985
msgid "Attached is the"
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/gui2/device.py:992
+#: /home/kovid/work/calibre/src/calibre/gui2/device.py:996
msgid "Sent news to"
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/gui2/device.py:1022
-#: /home/kovid/work/calibre/src/calibre/gui2/device.py:1084
-#: /home/kovid/work/calibre/src/calibre/gui2/device.py:1203
+#: /home/kovid/work/calibre/src/calibre/gui2/device.py:1026
+#: /home/kovid/work/calibre/src/calibre/gui2/device.py:1088
+#: /home/kovid/work/calibre/src/calibre/gui2/device.py:1207
msgid "Auto convert the following books before uploading to the device?"
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/gui2/device.py:1052
+#: /home/kovid/work/calibre/src/calibre/gui2/device.py:1056
msgid "Sending catalogs to device."
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/gui2/device.py:1116
+#: /home/kovid/work/calibre/src/calibre/gui2/device.py:1120
msgid "Sending news to device."
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/gui2/device.py:1169
+#: /home/kovid/work/calibre/src/calibre/gui2/device.py:1173
msgid "Sending books to device."
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/gui2/device.py:1211
+#: /home/kovid/work/calibre/src/calibre/gui2/device.py:1215
msgid ""
"Could not upload the following books to the device, as no suitable formats "
"were found. Convert the book(s) to a format supported by your device first."
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/gui2/device.py:1273
+#: /home/kovid/work/calibre/src/calibre/gui2/device.py:1277
msgid "No space on device"
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/gui2/device.py:1274
+#: /home/kovid/work/calibre/src/calibre/gui2/device.py:1278
msgid ""
"
Cannot upload books to device there is no more free space available "
msgstr ""
@@ -5641,7 +5665,7 @@ msgid "My Books"
msgstr ""
#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/catalog_ui.py:69
-#: /home/kovid/work/calibre/src/calibre/gui2/tools.py:297
+#: /home/kovid/work/calibre/src/calibre/gui2/tools.py:301
msgid "Generate catalog"
msgstr ""
@@ -5958,93 +5982,93 @@ msgid ""
"your library before proceeding."
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk.py:382
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk.py:386
msgid "Search/replace invalid"
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk.py:383
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk.py:387
msgid "Search pattern is invalid: %s"
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk.py:415
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk.py:419
msgid "Applying changes to %d books. This may take a while."
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:228
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:229
msgid "Edit Meta information"
msgstr "تحرير معلومات الميتا"
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:230
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:231
msgid "A&utomatically set author sort"
msgstr "ضبط& ترتيب المؤلف آلياً"
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:231
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:232
msgid "Author s&ort: "
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:232
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:233
msgid ""
"Specify how the author(s) of this book should be sorted. For example Charles "
"Dickens should be sorted as Dickens, Charles."
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:233
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single_ui.py:377
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:234
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single_ui.py:383
msgid "&Rating:"
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:234
#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:235
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single_ui.py:378
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single_ui.py:379
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:236
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single_ui.py:384
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single_ui.py:385
msgid "Rating of this book. 0-5 stars"
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:236
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:237
msgid "No change"
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:237
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single_ui.py:380
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:238
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single_ui.py:386
msgid " stars"
msgstr " نجمة"
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:239
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:240
msgid "Add ta&gs: "
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:241
#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:242
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single_ui.py:384
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single_ui.py:385
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:243
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single_ui.py:390
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single_ui.py:391
msgid "Open Tag Editor"
msgstr "فتح محرر الوسوم"
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:243
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:244
msgid "&Remove tags:"
msgstr "حذف& الوسوم:"
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:244
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:245
msgid "Comma separated list of tags to remove from the books. "
msgstr "قائمة من الوسوم مفرقة بالفاصلة لحذفها من الكتب. "
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:245
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:246
msgid "Check this box to remove all tags from the books."
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:246
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:247
msgid "Remove all"
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:250
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:251
msgid "Remove &format:"
msgstr "حذف الت&هيئة:"
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:251
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:252
msgid "&Swap title and author"
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:252
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:253
msgid ""
"Selected books will be automatically numbered,\n"
"in the order you selected them.\n"
@@ -6052,56 +6076,56 @@ msgid ""
"Book A will have series number 1 and Book B series number 2."
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:257
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:258
msgid ""
"Remove stored conversion settings for the selected books.\n"
"\n"
"Future conversion of these books will use the default settings."
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:260
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:261
msgid "Remove &stored conversion settings for the selected books"
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:261
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single_ui.py:415
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:262
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single_ui.py:422
msgid "&Basic metadata"
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:262
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single_ui.py:416
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:263
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single_ui.py:423
msgid "&Custom metadata"
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:263
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:264
msgid "Search &field:"
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:264
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:265
msgid "&Search for:"
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:265
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:266
msgid "&Replace with:"
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:266
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:267
msgid "Apply function &after replace:"
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:267
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:268
msgid "Test &text"
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:268
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:269
msgid "Test re&sult"
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:269
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:270
msgid "Your test:"
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:270
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:271
msgid "&Search and replace (experimental)"
msgstr ""
@@ -6159,118 +6183,118 @@ msgstr ""
msgid "The cover in the %s format is invalid"
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single.py:333
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single.py:351
msgid ""
" The green color indicates that the current author sort matches the current "
"author"
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single.py:336
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single.py:354
msgid ""
" The red color indicates that the current author sort does not match the "
"current author"
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single.py:341
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single.py:359
msgid "Abort the editing of all remaining books"
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single.py:505
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single.py:510
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single.py:524
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single.py:529
msgid "This ISBN number is valid"
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single.py:513
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single.py:532
msgid "This ISBN number is invalid"
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single.py:592
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single.py:611
msgid "Cannot use tag editor"
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single.py:593
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single.py:612
msgid "The tags editor cannot be used if you have modified the tags"
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single.py:613
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single.py:632
msgid "Downloading cover..."
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single.py:625
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single.py:630
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single.py:636
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single.py:641
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single.py:644
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single.py:649
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single.py:655
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single.py:660
msgid "Cannot fetch cover"
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single.py:626
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single.py:637
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single.py:642
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single.py:645
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single.py:656
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single.py:661
msgid "Could not fetch cover. "
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single.py:627
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single.py:646
msgid "The download timed out."
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single.py:631
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single.py:650
msgid "Could not find cover for this book. Try specifying the ISBN first."
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single.py:643
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single.py:662
msgid ""
"For the error message from each cover source, click Show details below."
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single.py:650
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single.py:669
msgid "Bad cover"
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single.py:651
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single.py:670
msgid "The cover is not a valid picture"
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single.py:684
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single.py:703
msgid "There were errors"
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single.py:685
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single.py:704
msgid "There were errors downloading social metadata"
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single.py:714
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single.py:733
msgid "Cannot fetch metadata"
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single.py:715
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single.py:734
msgid "You must specify at least one of ISBN, Title, Authors or Publisher"
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single.py:798
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single.py:820
msgid "Permission denied"
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single.py:799
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single.py:821
msgid "Could not open %s. Is it being used by another program?"
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single_ui.py:364
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single_ui.py:370
msgid "Edit Meta Information"
msgstr "تحرير معلومات الميتا"
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single_ui.py:365
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single_ui.py:371
msgid "Meta information"
msgstr "معلومات الميتا"
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single_ui.py:368
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single_ui.py:374
msgid "Swap the author and title"
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single_ui.py:371
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single_ui.py:377
msgid "Author S&ort: "
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single_ui.py:372
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single_ui.py:378
msgid ""
"Specify how the author(s) of this book should be sorted. For example Charles "
"Dickens should be sorted as Dickens, Charles.\n"
@@ -6278,7 +6302,7 @@ msgid ""
"strings. If it is colored red, then the authors and this text do not match."
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single_ui.py:374
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single_ui.py:380
msgid ""
"Automatically create the author sort entry based on the current author "
"entry.\n"
@@ -6286,71 +6310,75 @@ msgid ""
"green."
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single_ui.py:389
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single_ui.py:395
msgid "Remove unused series (Series that have no books)"
msgstr "حذف سلسلات غير مستخدمة (سلسلات التي لا تحتوي على كتب)"
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single_ui.py:391
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single_ui.py:397
msgid "IS&BN:"
msgstr "IS&BN:"
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single_ui.py:392
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single_ui.py:398
msgid "Publishe&d:"
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single_ui.py:395
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single_ui.py:401
msgid "dd MMM yyyy"
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single_ui.py:396
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single_ui.py:402
msgid "&Date:"
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single_ui.py:397
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single_ui.py:403
msgid "&Comments"
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single_ui.py:398
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single_ui.py:404
msgid "&Fetch metadata from server"
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single_ui.py:399
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single_ui.py:405
msgid "Available Formats"
msgstr "التهيئات المتوفرة"
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single_ui.py:400
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single_ui.py:406
msgid "Add a new format for this book to the database"
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single_ui.py:402
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single_ui.py:408
msgid "Remove the selected formats for this book from the database."
msgstr "حذف التهيئات المختارة لهذا الكتاب من قاعدة البيانات."
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single_ui.py:404
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single_ui.py:410
msgid "Set the cover for the book from the selected format"
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single_ui.py:406
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single_ui.py:412
msgid "Update metadata from the metadata in the selected format"
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single_ui.py:409
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single_ui.py:415
msgid "&Browse"
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single_ui.py:410
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single_ui.py:416
+msgid "Remove border (if any) from cover"
+msgstr ""
+
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single_ui.py:417
msgid "Reset cover to default"
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single_ui.py:412
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single_ui.py:419
msgid "Download co&ver"
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single_ui.py:413
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single_ui.py:420
msgid "Generate a default cover based on the title and author"
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single_ui.py:414
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single_ui.py:421
msgid "&Generate cover"
msgstr ""
@@ -6846,6 +6874,39 @@ msgstr ""
msgid "&Test"
msgstr "&تجربة"
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/tweak_epub_ui.py:55
+msgid "Display contents of exploded ePub"
+msgstr ""
+
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/tweak_epub_ui.py:56
+msgid "&Explode ePub"
+msgstr ""
+
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/tweak_epub_ui.py:57
+msgid "Rebuild ePub from exploded contents"
+msgstr ""
+
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/tweak_epub_ui.py:58
+msgid "&Rebuild ePub"
+msgstr ""
+
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/tweak_epub_ui.py:59
+msgid "Discard changes"
+msgstr ""
+
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/tweak_epub_ui.py:60
+#: /home/kovid/work/calibre/src/calibre/gui2/preferences/main.py:218
+msgid "&Cancel"
+msgstr ""
+
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/tweak_epub_ui.py:61
+msgid ""
+"Explode the ePub to display contents in a file browser window. To tweak "
+"individual files, right-click, then 'Open with...' your editor of choice. "
+"When tweaks are complete, close the file browser window. Rebuild the ePub, "
+"updating your calibre library."
+msgstr ""
+
#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/user_profiles.py:127
msgid "No recipe selected"
msgstr ""
@@ -7239,7 +7300,7 @@ msgid "Show books in the main memory of the device"
msgstr ""
#: /home/kovid/work/calibre/src/calibre/gui2/layout.py:66
-#: /home/kovid/work/calibre/src/calibre/library/database2.py:656
+#: /home/kovid/work/calibre/src/calibre/library/database2.py:655
msgid "Card A"
msgstr ""
@@ -7248,7 +7309,7 @@ msgid "Show books in storage card A"
msgstr ""
#: /home/kovid/work/calibre/src/calibre/gui2/layout.py:68
-#: /home/kovid/work/calibre/src/calibre/library/database2.py:658
+#: /home/kovid/work/calibre/src/calibre/library/database2.py:657
msgid "Card B"
msgstr ""
@@ -7374,7 +7435,7 @@ msgstr ""
msgid "Restore default layout"
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/gui2/library/views.py:555
+#: /home/kovid/work/calibre/src/calibre/gui2/library/views.py:585
msgid ""
"Dropping onto a device is not supported. First add the book to the calibre "
"library."
@@ -8073,10 +8134,6 @@ msgstr ""
msgid "&Apply"
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/gui2/preferences/main.py:218
-msgid "&Cancel"
-msgstr ""
-
#: /home/kovid/work/calibre/src/calibre/gui2/preferences/main.py:222
msgid "Restore &defaults"
msgstr ""
@@ -8356,7 +8413,7 @@ msgid ""
msgstr ""
#: /home/kovid/work/calibre/src/calibre/gui2/preferences/server.py:75
-#: /home/kovid/work/calibre/src/calibre/gui2/ui.py:319
+#: /home/kovid/work/calibre/src/calibre/gui2/ui.py:320
msgid "Failed to start content server"
msgstr "فشل في تشغيل خادم المحتوى"
@@ -8757,78 +8814,78 @@ msgstr ""
msgid "Queueing "
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/gui2/tools.py:239
+#: /home/kovid/work/calibre/src/calibre/gui2/tools.py:243
msgid "Fetch news from "
msgstr "احصل على الأخبار من "
-#: /home/kovid/work/calibre/src/calibre/gui2/tools.py:309
+#: /home/kovid/work/calibre/src/calibre/gui2/tools.py:313
msgid "Convert existing"
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/gui2/tools.py:310
+#: /home/kovid/work/calibre/src/calibre/gui2/tools.py:314
msgid ""
"The following books have already been converted to %s format. Do you wish to "
"reconvert them?"
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/gui2/ui.py:167
+#: /home/kovid/work/calibre/src/calibre/gui2/ui.py:168
msgid "&Restore"
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/gui2/ui.py:169
+#: /home/kovid/work/calibre/src/calibre/gui2/ui.py:170
msgid "&Donate to support calibre"
msgstr "تبرع& لدعم كاليبر"
-#: /home/kovid/work/calibre/src/calibre/gui2/ui.py:173
+#: /home/kovid/work/calibre/src/calibre/gui2/ui.py:174
msgid "&Eject connected device"
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/gui2/ui.py:215
+#: /home/kovid/work/calibre/src/calibre/gui2/ui.py:216
msgid "Calibre Quick Start Guide"
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/gui2/ui.py:417
-#: /home/kovid/work/calibre/src/calibre/gui2/ui.py:445
+#: /home/kovid/work/calibre/src/calibre/gui2/ui.py:418
+#: /home/kovid/work/calibre/src/calibre/gui2/ui.py:446
msgid "Conversion Error"
msgstr "خطأ في التحويل"
-#: /home/kovid/work/calibre/src/calibre/gui2/ui.py:418
+#: /home/kovid/work/calibre/src/calibre/gui2/ui.py:419
msgid ""
"
Could not convert: %s
It is a DRMed book. You must "
"first remove the DRM using third party tools."
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/gui2/ui.py:431
+#: /home/kovid/work/calibre/src/calibre/gui2/ui.py:432
msgid "Recipe Disabled"
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/gui2/ui.py:446
+#: /home/kovid/work/calibre/src/calibre/gui2/ui.py:447
msgid "Failed"
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/gui2/ui.py:482
+#: /home/kovid/work/calibre/src/calibre/gui2/ui.py:483
msgid ""
"is the result of the efforts of many volunteers from all over the world. If "
"you find it useful, please consider donating to support its development. "
"Your donation helps keep calibre development going."
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/gui2/ui.py:508
+#: /home/kovid/work/calibre/src/calibre/gui2/ui.py:509
msgid "There are active jobs. Are you sure you want to quit?"
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/gui2/ui.py:511
+#: /home/kovid/work/calibre/src/calibre/gui2/ui.py:512
msgid ""
" is communicating with the device! \n"
" Quitting may cause corruption on the device. \n"
" Are you sure you want to quit?"
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/gui2/ui.py:515
+#: /home/kovid/work/calibre/src/calibre/gui2/ui.py:516
msgid "WARNING: Active jobs"
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/gui2/ui.py:583
+#: /home/kovid/work/calibre/src/calibre/gui2/ui.py:584
msgid ""
"will keep running in the system tray. To close it, choose Quit in the "
"context menu of the system tray."
@@ -9605,48 +9662,48 @@ msgstr ""
msgid "Turn on the &content server"
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/library/caches.py:232
+#: /home/kovid/work/calibre/src/calibre/library/caches.py:234
msgid "today"
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/library/caches.py:235
+#: /home/kovid/work/calibre/src/calibre/library/caches.py:237
msgid "yesterday"
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/library/caches.py:238
+#: /home/kovid/work/calibre/src/calibre/library/caches.py:240
msgid "thismonth"
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/library/caches.py:241
-#: /home/kovid/work/calibre/src/calibre/library/caches.py:242
+#: /home/kovid/work/calibre/src/calibre/library/caches.py:243
+#: /home/kovid/work/calibre/src/calibre/library/caches.py:244
msgid "daysago"
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/library/caches.py:406
-#: /home/kovid/work/calibre/src/calibre/library/caches.py:416
+#: /home/kovid/work/calibre/src/calibre/library/caches.py:408
+#: /home/kovid/work/calibre/src/calibre/library/caches.py:418
msgid "no"
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/library/caches.py:406
-#: /home/kovid/work/calibre/src/calibre/library/caches.py:416
+#: /home/kovid/work/calibre/src/calibre/library/caches.py:408
+#: /home/kovid/work/calibre/src/calibre/library/caches.py:418
msgid "unchecked"
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/library/caches.py:409
-#: /home/kovid/work/calibre/src/calibre/library/caches.py:419
+#: /home/kovid/work/calibre/src/calibre/library/caches.py:411
+#: /home/kovid/work/calibre/src/calibre/library/caches.py:421
msgid "checked"
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/library/caches.py:409
-#: /home/kovid/work/calibre/src/calibre/library/caches.py:419
+#: /home/kovid/work/calibre/src/calibre/library/caches.py:411
+#: /home/kovid/work/calibre/src/calibre/library/caches.py:421
msgid "yes"
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/library/caches.py:413
+#: /home/kovid/work/calibre/src/calibre/library/caches.py:415
msgid "blank"
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/library/caches.py:413
+#: /home/kovid/work/calibre/src/calibre/library/caches.py:415
msgid "empty"
msgstr ""
@@ -10232,31 +10289,31 @@ msgstr ""
msgid "%sAverage rating is %3.1f"
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/library/database2.py:654
+#: /home/kovid/work/calibre/src/calibre/library/database2.py:653
msgid "Main"
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/library/database2.py:1992
+#: /home/kovid/work/calibre/src/calibre/library/database2.py:1998
msgid "
Migrating old database to ebook library in %s
"
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/library/database2.py:2021
+#: /home/kovid/work/calibre/src/calibre/library/database2.py:2027
msgid "Copying %s"
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/library/database2.py:2038
+#: /home/kovid/work/calibre/src/calibre/library/database2.py:2044
msgid "Compacting database"
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/library/database2.py:2131
+#: /home/kovid/work/calibre/src/calibre/library/database2.py:2137
msgid "Checking SQL integrity..."
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/library/database2.py:2170
+#: /home/kovid/work/calibre/src/calibre/library/database2.py:2176
msgid "Checking for missing files."
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/library/database2.py:2192
+#: /home/kovid/work/calibre/src/calibre/library/database2.py:2198
msgid "Checked id"
msgstr ""
@@ -10726,59 +10783,59 @@ msgid ""
"Do not download latest version of builtin recipes from the calibre server"
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/web/feeds/news.py:46
+#: /home/kovid/work/calibre/src/calibre/web/feeds/news.py:47
msgid "Unknown News Source"
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/web/feeds/news.py:611
+#: /home/kovid/work/calibre/src/calibre/web/feeds/news.py:612
msgid "The \"%s\" recipe needs a username and password."
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/web/feeds/news.py:710
+#: /home/kovid/work/calibre/src/calibre/web/feeds/news.py:711
msgid "Download finished"
msgstr "تم التنزيل"
-#: /home/kovid/work/calibre/src/calibre/web/feeds/news.py:712
+#: /home/kovid/work/calibre/src/calibre/web/feeds/news.py:713
msgid "Failed to download the following articles:"
msgstr "فشل تنزيل المقالات التالية:"
-#: /home/kovid/work/calibre/src/calibre/web/feeds/news.py:718
+#: /home/kovid/work/calibre/src/calibre/web/feeds/news.py:719
msgid "Failed to download parts of the following articles:"
msgstr "فشل تنزيل أجزاء من المقالات التالية:"
-#: /home/kovid/work/calibre/src/calibre/web/feeds/news.py:720
+#: /home/kovid/work/calibre/src/calibre/web/feeds/news.py:721
msgid " from "
msgstr " من "
-#: /home/kovid/work/calibre/src/calibre/web/feeds/news.py:722
+#: /home/kovid/work/calibre/src/calibre/web/feeds/news.py:723
msgid "\tFailed links:"
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/web/feeds/news.py:811
+#: /home/kovid/work/calibre/src/calibre/web/feeds/news.py:812
msgid "Could not fetch article. Run with -vv to see the reason"
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/web/feeds/news.py:832
+#: /home/kovid/work/calibre/src/calibre/web/feeds/news.py:833
msgid "Fetching feeds..."
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/web/feeds/news.py:837
+#: /home/kovid/work/calibre/src/calibre/web/feeds/news.py:838
msgid "Got feeds from index page"
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/web/feeds/news.py:843
+#: /home/kovid/work/calibre/src/calibre/web/feeds/news.py:844
msgid "Trying to download cover..."
msgstr "محاولة تنزيل الغلاف..."
-#: /home/kovid/work/calibre/src/calibre/web/feeds/news.py:845
+#: /home/kovid/work/calibre/src/calibre/web/feeds/news.py:846
msgid "Generating masthead..."
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/web/feeds/news.py:926
+#: /home/kovid/work/calibre/src/calibre/web/feeds/news.py:927
msgid "Starting download [%d thread(s)]..."
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/web/feeds/news.py:942
+#: /home/kovid/work/calibre/src/calibre/web/feeds/news.py:943
msgid "Feeds downloaded to %s"
msgstr "تم تنزيل التلقيم إلى %s"
@@ -10786,37 +10843,37 @@ msgstr "تم تنزيل التلقيم إلى %s"
msgid "Could not download cover: %s"
msgstr "لم يتمكّن من تنزيل الغلاف: %s"
-#: /home/kovid/work/calibre/src/calibre/web/feeds/news.py:964
+#: /home/kovid/work/calibre/src/calibre/web/feeds/news.py:959
msgid "Downloading cover from %s"
msgstr "يتم تنزيل الغلاف من %s"
-#: /home/kovid/work/calibre/src/calibre/web/feeds/news.py:1005
+#: /home/kovid/work/calibre/src/calibre/web/feeds/news.py:1004
msgid "Masthead image downloaded"
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/web/feeds/news.py:1173
+#: /home/kovid/work/calibre/src/calibre/web/feeds/news.py:1172
msgid "Untitled Article"
msgstr "مقالة بدون عنوان"
-#: /home/kovid/work/calibre/src/calibre/web/feeds/news.py:1244
+#: /home/kovid/work/calibre/src/calibre/web/feeds/news.py:1243
msgid "Article downloaded: %s"
msgstr "المقالة منزّلة: %s"
-#: /home/kovid/work/calibre/src/calibre/web/feeds/news.py:1255
+#: /home/kovid/work/calibre/src/calibre/web/feeds/news.py:1254
msgid "Article download failed: %s"
msgstr "فشل تنزيل المقالة: %s"
-#: /home/kovid/work/calibre/src/calibre/web/feeds/news.py:1272
+#: /home/kovid/work/calibre/src/calibre/web/feeds/news.py:1271
msgid "Fetching feed"
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/web/feeds/news.py:1419
+#: /home/kovid/work/calibre/src/calibre/web/feeds/news.py:1418
msgid ""
"Failed to log in, check your username and password for the calibre "
"Periodicals service."
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/web/feeds/news.py:1435
+#: /home/kovid/work/calibre/src/calibre/web/feeds/news.py:1433
msgid ""
"You do not have permission to download this issue. Either your subscription "
"has expired or you have exceeded the maximum allowed downloads for today."
diff --git a/src/calibre/translations/ca.po b/src/calibre/translations/ca.po
index 97e0005161..afd463b551 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: 2010-09-17 21:00+0000\n"
-"PO-Revision-Date: 2010-09-23 09:05+0000\n"
+"POT-Creation-Date: 2010-09-24 21:33+0000\n"
+"PO-Revision-Date: 2010-09-24 20:45+0000\n"
"Last-Translator: oscarl \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: 2010-09-24 04:39+0000\n"
+"X-Launchpad-Export-Date: 2010-09-25 04:39+0000\n"
"X-Generator: Launchpad (build Unknown)\n"
#: /home/kovid/work/calibre/src/calibre/customize/__init__.py:43
@@ -27,7 +27,8 @@ msgstr "No fa absolutament res"
#: /home/kovid/work/calibre/src/calibre/customize/__init__.py:46
#: /home/kovid/work/calibre/src/calibre/devices/jetbook/driver.py:74
#: /home/kovid/work/calibre/src/calibre/devices/kindle/driver.py:76
-#: /home/kovid/work/calibre/src/calibre/devices/kobo/driver.py:410
+#: /home/kovid/work/calibre/src/calibre/devices/kobo/books.py:46
+#: /home/kovid/work/calibre/src/calibre/devices/kobo/driver.py:412
#: /home/kovid/work/calibre/src/calibre/devices/nook/driver.py:70
#: /home/kovid/work/calibre/src/calibre/devices/nook/driver.py:71
#: /home/kovid/work/calibre/src/calibre/devices/prs500/books.py:267
@@ -108,24 +109,24 @@ msgstr "No fa absolutament res"
#: /home/kovid/work/calibre/src/calibre/ebooks/pdf/writer.py:98
#: /home/kovid/work/calibre/src/calibre/ebooks/rtf/input.py:239
#: /home/kovid/work/calibre/src/calibre/ebooks/rtf/input.py:241
-#: /home/kovid/work/calibre/src/calibre/gui2/__init__.py:324
-#: /home/kovid/work/calibre/src/calibre/gui2/__init__.py:331
-#: /home/kovid/work/calibre/src/calibre/gui2/actions/edit_metadata.py:293
+#: /home/kovid/work/calibre/src/calibre/gui2/__init__.py:352
+#: /home/kovid/work/calibre/src/calibre/gui2/__init__.py:359
#: /home/kovid/work/calibre/src/calibre/gui2/actions/edit_metadata.py:296
+#: /home/kovid/work/calibre/src/calibre/gui2/actions/edit_metadata.py:299
#: /home/kovid/work/calibre/src/calibre/gui2/add.py:137
#: /home/kovid/work/calibre/src/calibre/gui2/add.py:144
#: /home/kovid/work/calibre/src/calibre/gui2/convert/__init__.py:42
#: /home/kovid/work/calibre/src/calibre/gui2/convert/metadata.py:111
#: /home/kovid/work/calibre/src/calibre/gui2/convert/metadata.py:136
#: /home/kovid/work/calibre/src/calibre/gui2/convert/metadata.py:138
-#: /home/kovid/work/calibre/src/calibre/gui2/device.py:865
-#: /home/kovid/work/calibre/src/calibre/gui2/device.py:874
-#: /home/kovid/work/calibre/src/calibre/gui2/device.py:1158
-#: /home/kovid/work/calibre/src/calibre/gui2/device.py:1161
+#: /home/kovid/work/calibre/src/calibre/gui2/device.py:869
+#: /home/kovid/work/calibre/src/calibre/gui2/device.py:878
+#: /home/kovid/work/calibre/src/calibre/gui2/device.py:1162
+#: /home/kovid/work/calibre/src/calibre/gui2/device.py:1165
#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/comicconf.py:47
#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/fetch_metadata.py:120
#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/fetch_metadata.py:155
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single.py:552
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single.py:571
#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/scheduler.py:173
#: /home/kovid/work/calibre/src/calibre/gui2/library/models.py:357
#: /home/kovid/work/calibre/src/calibre/gui2/library/models.py:377
@@ -138,11 +139,11 @@ msgstr "No fa absolutament res"
#: /home/kovid/work/calibre/src/calibre/library/database.py:913
#: /home/kovid/work/calibre/src/calibre/library/database2.py:375
#: /home/kovid/work/calibre/src/calibre/library/database2.py:387
-#: /home/kovid/work/calibre/src/calibre/library/database2.py:1065
-#: /home/kovid/work/calibre/src/calibre/library/database2.py:1137
-#: /home/kovid/work/calibre/src/calibre/library/database2.py:1837
-#: /home/kovid/work/calibre/src/calibre/library/database2.py:1839
-#: /home/kovid/work/calibre/src/calibre/library/database2.py:1966
+#: /home/kovid/work/calibre/src/calibre/library/database2.py:1064
+#: /home/kovid/work/calibre/src/calibre/library/database2.py:1139
+#: /home/kovid/work/calibre/src/calibre/library/database2.py:1843
+#: /home/kovid/work/calibre/src/calibre/library/database2.py:1845
+#: /home/kovid/work/calibre/src/calibre/library/database2.py:1972
#: /home/kovid/work/calibre/src/calibre/library/server/mobile.py:211
#: /home/kovid/work/calibre/src/calibre/library/server/opds.py:137
#: /home/kovid/work/calibre/src/calibre/library/server/opds.py:140
@@ -264,45 +265,45 @@ msgstr "Estableix la metainformació als fitxers %s"
msgid "Set metadata from %s files"
msgstr "Estableix metainformació des dels fitxers %s"
-#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:684
+#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:688
msgid "Look and Feel"
msgstr "Aspecte i comportament"
-#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:686
-#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:698
-#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:709
-#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:720
+#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:690
+#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:702
+#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:713
+#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:724
msgid "Interface"
msgstr "Interfície"
-#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:690
+#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:694
msgid "Adjust the look and feel of the calibre interface to suit your tastes"
msgstr ""
"Ajusta l'aspecte i el comportament de la interfície de calibre per tal que "
"s'adapti al teu gust"
-#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:696
+#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:700
msgid "Behavior"
msgstr "Comportament"
-#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:702
+#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:706
msgid "Change the way calibre behaves"
msgstr "Canvia el comportament de calibre"
-#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:707
+#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:711
#: /home/kovid/work/calibre/src/calibre/gui2/library/views.py:176
msgid "Add your own columns"
msgstr "Afegeix les teves columnes"
-#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:713
+#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:717
msgid "Add/remove your own columns to the calibre book list"
msgstr "Afegeix/elimina les teves columnes a la llista de llibres de calibre"
-#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:718
+#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:722
msgid "Customize the toolbar"
msgstr "Personalitza la barra d'eines"
-#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:724
+#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:728
msgid ""
"Customize the toolbars and context menus, changing which actions are "
"available in each"
@@ -310,59 +311,59 @@ msgstr ""
"Personalitza les barres d'eines i els menús de context, canviant quines "
"accions estan disponibles"
-#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:730
+#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:734
msgid "Input Options"
msgstr "Opcions d'entrada"
-#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:732
-#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:743
-#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:754
+#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:736
+#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:747
+#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:758
msgid "Conversion"
msgstr "Conversió"
-#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:736
+#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:740
msgid "Set conversion options specific to each input format"
msgstr ""
"Ajusta les opcions de conversió específiques per a cada format d'entrada"
-#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:741
+#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:745
msgid "Common Options"
msgstr "Opcions comunes"
-#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:747
+#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:751
msgid "Set conversion options common to all formats"
msgstr "Ajusta les opcions de conversió comunes a tots els formats"
-#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:752
+#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:756
msgid "Output Options"
msgstr "Opcions de sortida"
-#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:758
+#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:762
msgid "Set conversion options specific to each output format"
msgstr ""
"Ajusta les opcions de conversió específiques de cada format de sortida"
-#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:763
+#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:767
msgid "Adding books"
msgstr "Afegint llibres"
-#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:765
-#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:777
-#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:789
+#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:769
+#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:781
+#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:793
msgid "Import/Export"
msgstr "Importa/exporta"
-#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:769
+#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:773
msgid "Control how calibre reads metadata from files when adding books"
msgstr ""
"Controla com calibre llegeix les metadades dels arxius quan s'afegeixen "
"llibres"
-#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:775
+#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:779
msgid "Saving books to disk"
msgstr "Desant els llibres al disc"
-#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:781
+#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:785
msgid ""
"Control how calibre exports files from its database to disk when using Save "
"to disk"
@@ -370,24 +371,24 @@ msgstr ""
"Controla com calibre exporta arxius de la seva base de dades al disc quan "
"s'utilitza Desa al disc"
-#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:787
+#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:791
msgid "Sending books to devices"
msgstr "Enviant llibres als dispositius"
-#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:793
+#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:797
msgid "Control how calibre transfers files to your ebook reader"
msgstr "Controla com calibre envia arxius al teu lector d'ebooks"
-#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:799
+#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:803
msgid "Sharing books by email"
msgstr "Compartint llibres via correu electrònic"
-#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:801
-#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:813
+#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:805
+#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:817
msgid "Sharing"
msgstr "Compartint"
-#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:805
+#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:809
msgid ""
"Setup sharing of books via email. Can be used for automatic sending of "
"downloaded news to your devices"
@@ -395,11 +396,11 @@ msgstr ""
"Configura compartir llibres via correu electrònic. Es pot utilitzar per "
"enviar notícies descarregades als teus dispositius"
-#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:811
+#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:815
msgid "Sharing over the net"
msgstr "Compartint en xarxa"
-#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:817
+#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:821
msgid ""
"Setup the calibre Content Server which will give you access to your calibre "
"library from anywhere, on any device, over the internet"
@@ -407,35 +408,35 @@ msgstr ""
"Configura el Servidor de Continguts que et donarà accés a la teva llibreria "
"des de qualsevol lloc i dispositiu, a través d'internet"
-#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:824
+#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:828
msgid "Plugins"
msgstr "Plugins"
-#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:826
-#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:838
-#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:849
+#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:830
+#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:842
+#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:853
msgid "Advanced"
msgstr "Avançat"
-#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:830
+#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:834
msgid "Add/remove/customize various bits of calibre functionality"
msgstr ""
"Afegeix/eliminina/personalitza diversos bits de la funcionalitat de calibre"
-#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:836
+#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:840
msgid "Tweaks"
msgstr "Ajustos"
-#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:842
+#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:846
msgid "Fine tune how calibre behaves in various contexts"
msgstr ""
"Configuració detallada del comportament de calibre en diversos contextos"
-#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:847
+#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:851
msgid "Miscellaneous"
msgstr "Miscel·lània"
-#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:853
+#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:857
msgid "Miscellaneous advanced configuration"
msgstr "Configuració avançada"
@@ -616,15 +617,15 @@ msgstr "Connectors inhabilitats"
msgid "Enabled plugins"
msgstr "Connectors permesos"
-#: /home/kovid/work/calibre/src/calibre/customize/ui.py:86
+#: /home/kovid/work/calibre/src/calibre/customize/ui.py:93
msgid "No valid plugin found in "
msgstr "No s'ha trobat cap connector vàlid a "
-#: /home/kovid/work/calibre/src/calibre/customize/ui.py:501
+#: /home/kovid/work/calibre/src/calibre/customize/ui.py:508
msgid "Initialization of plugin %s failed with traceback:"
msgstr "No s'ha pogut inicialitzar el connector %s i s'ha generat la traça:"
-#: /home/kovid/work/calibre/src/calibre/customize/ui.py:534
+#: /home/kovid/work/calibre/src/calibre/customize/ui.py:541
msgid ""
" %prog options\n"
"\n"
@@ -636,18 +637,18 @@ msgstr ""
" Personalitzeu el calibre carregant connectors externs.\n"
" "
-#: /home/kovid/work/calibre/src/calibre/customize/ui.py:540
+#: /home/kovid/work/calibre/src/calibre/customize/ui.py:547
msgid "Add a plugin by specifying the path to the zip file containing it."
msgstr ""
"Afegeix un connector especificiant el camí al fitxer ZIP que el conté"
-#: /home/kovid/work/calibre/src/calibre/customize/ui.py:542
+#: /home/kovid/work/calibre/src/calibre/customize/ui.py:549
msgid "Remove a custom plugin by name. Has no effect on builtin plugins"
msgstr ""
"Suprimeix un connector personalitzat per nom. No té cap efecte als "
"complements integrats"
-#: /home/kovid/work/calibre/src/calibre/customize/ui.py:544
+#: /home/kovid/work/calibre/src/calibre/customize/ui.py:551
msgid ""
"Customize plugin. Specify name of plugin and customization string separated "
"by a comma."
@@ -655,15 +656,15 @@ msgstr ""
"Personalitza el connector. Especifiqueu el nom del connector i el text que "
"desitgeu, separats per una coma."
-#: /home/kovid/work/calibre/src/calibre/customize/ui.py:546
+#: /home/kovid/work/calibre/src/calibre/customize/ui.py:553
msgid "List all installed plugins"
msgstr "Fes una llista amb tots els connectors instal·lats"
-#: /home/kovid/work/calibre/src/calibre/customize/ui.py:548
+#: /home/kovid/work/calibre/src/calibre/customize/ui.py:555
msgid "Enable the named plugin"
msgstr "Habilita el connector anomenat"
-#: /home/kovid/work/calibre/src/calibre/customize/ui.py:550
+#: /home/kovid/work/calibre/src/calibre/customize/ui.py:557
msgid "Disable the named plugin"
msgstr "Inhabilita el connector anomenat"
@@ -671,7 +672,7 @@ msgstr "Inhabilita el connector anomenat"
msgid "Communicate with Android phones."
msgstr "Estableix comunicació amb telèfons Android"
-#: /home/kovid/work/calibre/src/calibre/devices/android/driver.py:50
+#: /home/kovid/work/calibre/src/calibre/devices/android/driver.py:52
msgid ""
"Comma separated list of directories to send e-books to on the device. The "
"first one that exists will be used"
@@ -680,7 +681,7 @@ msgstr ""
"dispositiu. S'usarà el primer directori del llistat que ja existeixi al "
"dispositiu"
-#: /home/kovid/work/calibre/src/calibre/devices/android/driver.py:92
+#: /home/kovid/work/calibre/src/calibre/devices/android/driver.py:94
msgid "Communicate with S60 phones."
msgstr "Estableix comunicació amb els telèfons S60."
@@ -749,7 +750,7 @@ msgstr ""
#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/scheduler.py:244
#: /home/kovid/work/calibre/src/calibre/library/database2.py:198
#: /home/kovid/work/calibre/src/calibre/library/database2.py:211
-#: /home/kovid/work/calibre/src/calibre/library/database2.py:1706
+#: /home/kovid/work/calibre/src/calibre/library/database2.py:1712
#: /home/kovid/work/calibre/src/calibre/library/field_metadata.py:134
msgid "News"
msgstr "Notícies"
@@ -757,8 +758,8 @@ msgstr "Notícies"
#: /home/kovid/work/calibre/src/calibre/devices/apple/driver.py:2500
#: /home/kovid/work/calibre/src/calibre/gui2/catalog/catalog_epub_mobi.py:20
#: /home/kovid/work/calibre/src/calibre/library/catalog.py:556
-#: /home/kovid/work/calibre/src/calibre/library/database2.py:1669
-#: /home/kovid/work/calibre/src/calibre/library/database2.py:1687
+#: /home/kovid/work/calibre/src/calibre/library/database2.py:1675
+#: /home/kovid/work/calibre/src/calibre/library/database2.py:1693
msgid "Catalog"
msgstr "Catàleg"
@@ -848,25 +849,25 @@ msgstr ""
"Llista separada per comes dels directoris als quals enviar llibres "
"electrònics en el dispositiu. S'usarà el primer que existeixi."
-#: /home/kovid/work/calibre/src/calibre/devices/hanvon/driver.py:18
+#: /home/kovid/work/calibre/src/calibre/devices/hanvon/driver.py:22
msgid "Communicate with the Hanvon N520 eBook reader."
msgstr ""
"Estableix comunicació amb el lector de llibres electrònics Hanvon N520."
-#: /home/kovid/work/calibre/src/calibre/devices/hanvon/driver.py:40
+#: /home/kovid/work/calibre/src/calibre/devices/hanvon/driver.py:47
msgid "Communicate with The Book reader."
msgstr "Comunicar-se amb el lector de llibres electrònics."
-#: /home/kovid/work/calibre/src/calibre/devices/hanvon/driver.py:52
+#: /home/kovid/work/calibre/src/calibre/devices/hanvon/driver.py:59
msgid "Communicate with the SpringDesign Alex eBook reader."
msgstr ""
"Estableix comunicació amb el lector de llibres electrònics SpringDesign Alex."
-#: /home/kovid/work/calibre/src/calibre/devices/hanvon/driver.py:68
+#: /home/kovid/work/calibre/src/calibre/devices/hanvon/driver.py:78
msgid "Communicate with the Azbooka"
msgstr "Comunicar-se amb Azbooka"
-#: /home/kovid/work/calibre/src/calibre/devices/hanvon/driver.py:81
+#: /home/kovid/work/calibre/src/calibre/devices/hanvon/driver.py:94
msgid "Communicate with the Elonex EB 511 eBook reader."
msgstr ""
"Estableix comunicació amb el lector de llibres electrònics Elonex EB 511."
@@ -934,7 +935,7 @@ msgstr ""
#: /home/kovid/work/calibre/src/calibre/devices/kobo/driver.py:63
#: /home/kovid/work/calibre/src/calibre/devices/kobo/driver.py:66
#: /home/kovid/work/calibre/src/calibre/devices/kobo/driver.py:69
-#: /home/kovid/work/calibre/src/calibre/devices/kobo/driver.py:186
+#: /home/kovid/work/calibre/src/calibre/devices/kobo/driver.py:188
#: /home/kovid/work/calibre/src/calibre/devices/usbms/driver.py:68
#: /home/kovid/work/calibre/src/calibre/devices/usbms/driver.py:71
#: /home/kovid/work/calibre/src/calibre/devices/usbms/driver.py:74
@@ -944,33 +945,33 @@ msgstr ""
msgid "Getting list of books on device..."
msgstr "S'està obtenint el llistat de llibres disponibles al dispositiu..."
-#: /home/kovid/work/calibre/src/calibre/devices/kobo/driver.py:246
-#: /home/kovid/work/calibre/src/calibre/devices/kobo/driver.py:278
+#: /home/kovid/work/calibre/src/calibre/devices/kobo/driver.py:248
+#: /home/kovid/work/calibre/src/calibre/devices/kobo/driver.py:280
#: /home/kovid/work/calibre/src/calibre/devices/usbms/driver.py:253
#: /home/kovid/work/calibre/src/calibre/devices/usbms/driver.py:271
msgid "Removing books from device..."
msgstr "S'estan suprimint els llibres del dispositiu..."
-#: /home/kovid/work/calibre/src/calibre/devices/kobo/driver.py:282
-#: /home/kovid/work/calibre/src/calibre/devices/kobo/driver.py:289
+#: /home/kovid/work/calibre/src/calibre/devices/kobo/driver.py:284
+#: /home/kovid/work/calibre/src/calibre/devices/kobo/driver.py:291
#: /home/kovid/work/calibre/src/calibre/devices/usbms/driver.py:278
#: /home/kovid/work/calibre/src/calibre/devices/usbms/driver.py:283
msgid "Removing books from device metadata listing..."
msgstr "S'estan suprimint llibres del llistat de metadades del dispositiu..."
-#: /home/kovid/work/calibre/src/calibre/devices/kobo/driver.py:294
-#: /home/kovid/work/calibre/src/calibre/devices/kobo/driver.py:328
+#: /home/kovid/work/calibre/src/calibre/devices/kobo/driver.py:296
+#: /home/kovid/work/calibre/src/calibre/devices/kobo/driver.py:330
#: /home/kovid/work/calibre/src/calibre/devices/usbms/driver.py:217
#: /home/kovid/work/calibre/src/calibre/devices/usbms/driver.py:247
msgid "Adding books to device metadata listing..."
msgstr "S'estan afegint llibres al llistat de metadades del dispositiu..."
-#: /home/kovid/work/calibre/src/calibre/devices/kobo/driver.py:390
+#: /home/kovid/work/calibre/src/calibre/devices/kobo/driver.py:392
#: /home/kovid/work/calibre/src/calibre/gui2/actions/add.py:252
msgid "Not Implemented"
msgstr "No implementat"
-#: /home/kovid/work/calibre/src/calibre/devices/kobo/driver.py:391
+#: /home/kovid/work/calibre/src/calibre/devices/kobo/driver.py:393
msgid ""
"\".kobo\" files do not exist on the device as books instead, they are rows "
"in the sqlite database. Currently they cannot be exported or viewed."
@@ -3478,7 +3479,7 @@ msgstr "Copia"
msgid "Copy to Clipboard"
msgstr "Copia al porta-retalls"
-#: /home/kovid/work/calibre/src/calibre/gui2/__init__.py:434
+#: /home/kovid/work/calibre/src/calibre/gui2/__init__.py:462
msgid "Choose Files"
msgstr "Escull fitxers"
@@ -3610,6 +3611,7 @@ msgstr "Afegeix a la biblioteca"
#: /home/kovid/work/calibre/src/calibre/gui2/actions/add.py:240
#: /home/kovid/work/calibre/src/calibre/gui2/actions/delete.py:56
+#: /home/kovid/work/calibre/src/calibre/gui2/actions/tweak_epub.py:28
#: /home/kovid/work/calibre/src/calibre/gui2/actions/view.py:95
#: /home/kovid/work/calibre/src/calibre/gui2/actions/view.py:120
msgid "No book selected"
@@ -3654,8 +3656,8 @@ msgstr ""
#: /home/kovid/work/calibre/src/calibre/gui2/actions/copy_to_library.py:116
#: /home/kovid/work/calibre/src/calibre/gui2/actions/edit_metadata.py:76
#: /home/kovid/work/calibre/src/calibre/gui2/actions/edit_metadata.py:142
-#: /home/kovid/work/calibre/src/calibre/gui2/actions/edit_metadata.py:178
-#: /home/kovid/work/calibre/src/calibre/gui2/actions/edit_metadata.py:205
+#: /home/kovid/work/calibre/src/calibre/gui2/actions/edit_metadata.py:180
+#: /home/kovid/work/calibre/src/calibre/gui2/actions/edit_metadata.py:208
#: /home/kovid/work/calibre/src/calibre/gui2/actions/save_to_disk.py:92
msgid "No books selected"
msgstr "Cap llibre seleccionat"
@@ -3812,7 +3814,7 @@ msgstr ""
#: /home/kovid/work/calibre/src/calibre/gui2/actions/choose_library.py:249
#: /home/kovid/work/calibre/src/calibre/gui2/actions/choose_library.py:254
#: /home/kovid/work/calibre/src/calibre/gui2/actions/save_to_disk.py:101
-#: /home/kovid/work/calibre/src/calibre/gui2/library/views.py:554
+#: /home/kovid/work/calibre/src/calibre/gui2/library/views.py:584
msgid "Not allowed"
msgstr "No està permès"
@@ -3880,7 +3882,7 @@ msgstr "No s'ha pogut copiar els llibres: "
#: /home/kovid/work/calibre/src/calibre/gui2/actions/copy_to_library.py:138
#: /home/kovid/work/calibre/src/calibre/gui2/device.py:670
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk.py:424
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk.py:428
msgid "Failed"
msgstr "Ha fallat"
@@ -4124,20 +4126,20 @@ msgid "Error"
msgstr "Error"
#: /home/kovid/work/calibre/src/calibre/gui2/actions/edit_metadata.py:141
-#: /home/kovid/work/calibre/src/calibre/gui2/actions/edit_metadata.py:177
+#: /home/kovid/work/calibre/src/calibre/gui2/actions/edit_metadata.py:179
msgid "Cannot edit metadata"
msgstr "No puc editar les meta-dades"
-#: /home/kovid/work/calibre/src/calibre/gui2/actions/edit_metadata.py:204
#: /home/kovid/work/calibre/src/calibre/gui2/actions/edit_metadata.py:207
+#: /home/kovid/work/calibre/src/calibre/gui2/actions/edit_metadata.py:210
msgid "Cannot merge books"
msgstr "No es pot fusionar els llibres"
-#: /home/kovid/work/calibre/src/calibre/gui2/actions/edit_metadata.py:208
+#: /home/kovid/work/calibre/src/calibre/gui2/actions/edit_metadata.py:211
msgid "At least two books must be selected for merging"
msgstr "Cal seleccionar almenys dos llibres per fer una fusió"
-#: /home/kovid/work/calibre/src/calibre/gui2/actions/edit_metadata.py:212
+#: /home/kovid/work/calibre/src/calibre/gui2/actions/edit_metadata.py:215
msgid ""
"Book formats and metadata from the selected books will be added to the "
"first selected book. ISBN will not be merged.
The "
@@ -4149,7 +4151,7 @@ msgstr ""
"fusionarà. El segon llibre i els que s'hagin seleccionat a continuació no "
"s'esborraran ni canviaran
Si us plau confirmeu que voleu continuar."
-#: /home/kovid/work/calibre/src/calibre/gui2/actions/edit_metadata.py:224
+#: /home/kovid/work/calibre/src/calibre/gui2/actions/edit_metadata.py:227
msgid ""
"Book formats and metadata from the selected books will be merged into the "
"first selected book. ISBN will not be merged.
After "
@@ -4165,7 +4167,7 @@ msgstr ""
"continuació s'esborraran de forma permanent de vostre "
"ordinador.
Esteu segur que voleu continuar?"
-#: /home/kovid/work/calibre/src/calibre/gui2/actions/edit_metadata.py:237
+#: /home/kovid/work/calibre/src/calibre/gui2/actions/edit_metadata.py:240
msgid ""
"You are about to merge more than 5 books. Are you sure you want to "
"proceed?"
@@ -4353,6 +4355,28 @@ msgstr "Alt+T"
msgid "Books with the same tags"
msgstr "Llibres amb les mateixes etiquetes"
+#: /home/kovid/work/calibre/src/calibre/gui2/actions/tweak_epub.py:15
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/tweak_epub_ui.py:54
+msgid "Tweak ePub"
+msgstr ""
+
+#: /home/kovid/work/calibre/src/calibre/gui2/actions/tweak_epub.py:16
+msgid "Make small changes to ePub format books"
+msgstr ""
+
+#: /home/kovid/work/calibre/src/calibre/gui2/actions/tweak_epub.py:17
+msgid "T"
+msgstr ""
+
+#: /home/kovid/work/calibre/src/calibre/gui2/actions/tweak_epub.py:27
+#: /home/kovid/work/calibre/src/calibre/gui2/actions/tweak_epub.py:39
+msgid "Cannot tweak ePub"
+msgstr ""
+
+#: /home/kovid/work/calibre/src/calibre/gui2/actions/tweak_epub.py:40
+msgid "No ePub available. First convert the book to ePub."
+msgstr ""
+
#: /home/kovid/work/calibre/src/calibre/gui2/actions/view.py:24
msgid "V"
msgstr "V"
@@ -4420,7 +4444,7 @@ msgid "The specified directory could not be processed."
msgstr "La carpeta que s'ha especificat no es pot processar."
#: /home/kovid/work/calibre/src/calibre/gui2/add.py:228
-#: /home/kovid/work/calibre/src/calibre/gui2/device.py:807
+#: /home/kovid/work/calibre/src/calibre/gui2/device.py:811
msgid "No books"
msgstr "Cap llibre"
@@ -4560,13 +4584,13 @@ msgstr ""
#: /home/kovid/work/calibre/src/calibre/gui2/device_drivers/configwidget_ui.py:84
#: /home/kovid/work/calibre/src/calibre/gui2/device_drivers/configwidget_ui.py:85
#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/choose_library_ui.py:77
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single_ui.py:369
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single_ui.py:376
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single_ui.py:390
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single_ui.py:401
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single_ui.py:403
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single_ui.py:405
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single_ui.py:375
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single_ui.py:382
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single_ui.py:396
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single_ui.py:407
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single_ui.py:409
#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single_ui.py:411
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single_ui.py:418
#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/saved_search_editor_ui.py:92
#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/saved_search_editor_ui.py:95
#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/tag_categories_ui.py:161
@@ -5338,7 +5362,7 @@ msgid " is not a valid picture"
msgstr " no és una imatge vàlida"
#: /home/kovid/work/calibre/src/calibre/gui2/convert/metadata_ui.py:172
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single_ui.py:407
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single_ui.py:413
msgid "Book Cover"
msgstr "Coberta"
@@ -5347,7 +5371,7 @@ msgid "Use cover from &source file"
msgstr ""
#: /home/kovid/work/calibre/src/calibre/gui2/convert/metadata_ui.py:174
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single_ui.py:408
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single_ui.py:414
msgid "Change &cover image:"
msgstr "Canvia la imatge de la &coberta:"
@@ -5356,18 +5380,18 @@ msgid "Browse for an image to use as the cover of this book."
msgstr "Cerca una imatge per a utilitzar com a coberta d'aquest llibre."
#: /home/kovid/work/calibre/src/calibre/gui2/convert/metadata_ui.py:177
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single_ui.py:366
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single_ui.py:372
msgid "&Title: "
msgstr "&Títol: "
#: /home/kovid/work/calibre/src/calibre/gui2/convert/metadata_ui.py:178
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single_ui.py:367
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single_ui.py:373
msgid "Change the title of this book"
msgstr "Canvia el títol del llibre"
#: /home/kovid/work/calibre/src/calibre/gui2/convert/metadata_ui.py:179
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:229
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single_ui.py:370
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:230
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single_ui.py:376
msgid "&Author(s): "
msgstr "&Autor(s): "
@@ -5383,19 +5407,19 @@ msgstr ""
"Canvia l'autor(s). Per a especificar més d'un, separeu-los amb comes."
#: /home/kovid/work/calibre/src/calibre/gui2/convert/metadata_ui.py:182
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:238
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single_ui.py:381
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:239
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single_ui.py:387
msgid "&Publisher: "
msgstr "&Editorial: "
#: /home/kovid/work/calibre/src/calibre/gui2/convert/metadata_ui.py:183
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single_ui.py:382
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single_ui.py:388
msgid "Ta&gs: "
msgstr "Etique&tes: "
#: /home/kovid/work/calibre/src/calibre/gui2/convert/metadata_ui.py:184
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:240
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single_ui.py:383
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:241
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single_ui.py:389
msgid ""
"Tags categorize the book. This is particularly useful while searching. "
"
They can be any words or phrases, separated by commas."
@@ -5404,22 +5428,22 @@ msgstr ""
"
Pot emprar-se qualsevol paraula o frase, separada per comes."
#: /home/kovid/work/calibre/src/calibre/gui2/convert/metadata_ui.py:185
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:247
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single_ui.py:386
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:248
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single_ui.py:392
msgid "&Series:"
msgstr "&Sèries:"
#: /home/kovid/work/calibre/src/calibre/gui2/convert/metadata_ui.py:186
#: /home/kovid/work/calibre/src/calibre/gui2/convert/metadata_ui.py:187
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:248
#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:249
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single_ui.py:387
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single_ui.py:388
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:250
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single_ui.py:393
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single_ui.py:394
msgid "List of known series. You can add new series."
msgstr "Llistat de sèries conegudes. Podeu afegir-hi de noves."
#: /home/kovid/work/calibre/src/calibre/gui2/convert/metadata_ui.py:188
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single_ui.py:393
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single_ui.py:399
msgid "Book "
msgstr "Llibre "
@@ -5901,7 +5925,7 @@ msgid " index:"
msgstr ""
#: /home/kovid/work/calibre/src/calibre/gui2/custom_column_widgets.py:451
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:256
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:257
msgid "Automatically number books in this series"
msgstr ""
@@ -6015,132 +6039,132 @@ msgstr ""
"Hi ha hagut un error de comunicació amb el dispositiu. Lleve, torne a "
"connectar el dispositiu i torne a iniciar el programa"
-#: /home/kovid/work/calibre/src/calibre/gui2/device.py:716
+#: /home/kovid/work/calibre/src/calibre/gui2/device.py:720
msgid "Device: "
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/gui2/device.py:718
+#: /home/kovid/work/calibre/src/calibre/gui2/device.py:722
msgid " detected."
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/gui2/device.py:808
+#: /home/kovid/work/calibre/src/calibre/gui2/device.py:812
msgid "selected to send"
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/gui2/device.py:813
+#: /home/kovid/work/calibre/src/calibre/gui2/device.py:817
msgid "Choose format to send to device"
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/gui2/device.py:822
+#: /home/kovid/work/calibre/src/calibre/gui2/device.py:826
msgid "No device"
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/gui2/device.py:823
+#: /home/kovid/work/calibre/src/calibre/gui2/device.py:827
msgid "Cannot send: No device is connected"
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/gui2/device.py:826
#: /home/kovid/work/calibre/src/calibre/gui2/device.py:830
+#: /home/kovid/work/calibre/src/calibre/gui2/device.py:834
msgid "No card"
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/gui2/device.py:827
#: /home/kovid/work/calibre/src/calibre/gui2/device.py:831
+#: /home/kovid/work/calibre/src/calibre/gui2/device.py:835
msgid "Cannot send: Device has no storage card"
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/gui2/device.py:872
+#: /home/kovid/work/calibre/src/calibre/gui2/device.py:876
msgid "E-book:"
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/gui2/device.py:875
+#: /home/kovid/work/calibre/src/calibre/gui2/device.py:879
msgid "Attached, you will find the e-book"
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/gui2/device.py:876
+#: /home/kovid/work/calibre/src/calibre/gui2/device.py:880
#: /home/kovid/work/calibre/src/calibre/gui2/preferences/plugins.py:107
msgid "by"
msgstr "per"
-#: /home/kovid/work/calibre/src/calibre/gui2/device.py:877
+#: /home/kovid/work/calibre/src/calibre/gui2/device.py:881
msgid "in the %s format."
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/gui2/device.py:890
+#: /home/kovid/work/calibre/src/calibre/gui2/device.py:894
msgid "Sending email to"
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/gui2/device.py:920
-#: /home/kovid/work/calibre/src/calibre/gui2/device.py:928
-#: /home/kovid/work/calibre/src/calibre/gui2/device.py:1021
-#: /home/kovid/work/calibre/src/calibre/gui2/device.py:1083
-#: /home/kovid/work/calibre/src/calibre/gui2/device.py:1202
-#: /home/kovid/work/calibre/src/calibre/gui2/device.py:1210
+#: /home/kovid/work/calibre/src/calibre/gui2/device.py:924
+#: /home/kovid/work/calibre/src/calibre/gui2/device.py:932
+#: /home/kovid/work/calibre/src/calibre/gui2/device.py:1025
+#: /home/kovid/work/calibre/src/calibre/gui2/device.py:1087
+#: /home/kovid/work/calibre/src/calibre/gui2/device.py:1206
+#: /home/kovid/work/calibre/src/calibre/gui2/device.py:1214
msgid "No suitable formats"
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/gui2/device.py:921
+#: /home/kovid/work/calibre/src/calibre/gui2/device.py:925
msgid "Auto convert the following books before sending via email?"
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/gui2/device.py:929
+#: /home/kovid/work/calibre/src/calibre/gui2/device.py:933
msgid ""
"Could not email the following books as no suitable formats were found:"
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/gui2/device.py:947
+#: /home/kovid/work/calibre/src/calibre/gui2/device.py:951
msgid "Failed to email books"
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/gui2/device.py:948
+#: /home/kovid/work/calibre/src/calibre/gui2/device.py:952
msgid "Failed to email the following books:"
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/gui2/device.py:952
+#: /home/kovid/work/calibre/src/calibre/gui2/device.py:956
msgid "Sent by email:"
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/gui2/device.py:980
+#: /home/kovid/work/calibre/src/calibre/gui2/device.py:984
msgid "News:"
msgstr "Notícies:"
-#: /home/kovid/work/calibre/src/calibre/gui2/device.py:981
+#: /home/kovid/work/calibre/src/calibre/gui2/device.py:985
msgid "Attached is the"
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/gui2/device.py:992
+#: /home/kovid/work/calibre/src/calibre/gui2/device.py:996
msgid "Sent news to"
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/gui2/device.py:1022
-#: /home/kovid/work/calibre/src/calibre/gui2/device.py:1084
-#: /home/kovid/work/calibre/src/calibre/gui2/device.py:1203
+#: /home/kovid/work/calibre/src/calibre/gui2/device.py:1026
+#: /home/kovid/work/calibre/src/calibre/gui2/device.py:1088
+#: /home/kovid/work/calibre/src/calibre/gui2/device.py:1207
msgid "Auto convert the following books before uploading to the device?"
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/gui2/device.py:1052
+#: /home/kovid/work/calibre/src/calibre/gui2/device.py:1056
msgid "Sending catalogs to device."
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/gui2/device.py:1116
+#: /home/kovid/work/calibre/src/calibre/gui2/device.py:1120
msgid "Sending news to device."
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/gui2/device.py:1169
+#: /home/kovid/work/calibre/src/calibre/gui2/device.py:1173
msgid "Sending books to device."
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/gui2/device.py:1211
+#: /home/kovid/work/calibre/src/calibre/gui2/device.py:1215
msgid ""
"Could not upload the following books to the device, as no suitable formats "
"were found. Convert the book(s) to a format supported by your device first."
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/gui2/device.py:1273
+#: /home/kovid/work/calibre/src/calibre/gui2/device.py:1277
msgid "No space on device"
msgstr "Sense espai al dispositiu"
-#: /home/kovid/work/calibre/src/calibre/gui2/device.py:1274
+#: /home/kovid/work/calibre/src/calibre/gui2/device.py:1278
msgid ""
"
Cannot upload books to device there is no more free space available "
msgstr "
No puc desar llibres al dispositiu perquè no hi ha espai restant "
@@ -6194,7 +6218,7 @@ msgid "My Books"
msgstr ""
#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/catalog_ui.py:69
-#: /home/kovid/work/calibre/src/calibre/gui2/tools.py:297
+#: /home/kovid/work/calibre/src/calibre/gui2/tools.py:301
msgid "Generate catalog"
msgstr ""
@@ -6511,31 +6535,31 @@ msgid ""
"your library before proceeding."
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk.py:382
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk.py:386
msgid "Search/replace invalid"
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk.py:383
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk.py:387
msgid "Search pattern is invalid: %s"
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk.py:415
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk.py:419
msgid "Applying changes to %d books. This may take a while."
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:228
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:229
msgid "Edit Meta information"
msgstr "Editar Meta-informació"
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:230
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:231
msgid "A&utomatically set author sort"
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:231
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:232
msgid "Author s&ort: "
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:232
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:233
msgid ""
"Specify how the author(s) of this book should be sorted. For example Charles "
"Dickens should be sorted as Dickens, Charles."
@@ -6543,63 +6567,63 @@ msgstr ""
"Especifiqueu com s'ha d'ordenar l'autor(s) d'aquest llibre. Per "
"exemple,ordena Vicent A. Estellés com a Estellés, Vicent A."
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:233
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single_ui.py:377
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:234
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single_ui.py:383
msgid "&Rating:"
msgstr "&Valoració:"
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:234
#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:235
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single_ui.py:378
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single_ui.py:379
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:236
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single_ui.py:384
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single_ui.py:385
msgid "Rating of this book. 0-5 stars"
msgstr "Valora aquest llibre: 0-5 estreles"
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:236
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:237
msgid "No change"
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:237
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single_ui.py:380
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:238
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single_ui.py:386
msgid " stars"
msgstr " estreles"
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:239
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:240
msgid "Add ta&gs: "
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:241
#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:242
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single_ui.py:384
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single_ui.py:385
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:243
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single_ui.py:390
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single_ui.py:391
msgid "Open Tag Editor"
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:243
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:244
msgid "&Remove tags:"
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:244
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:245
msgid "Comma separated list of tags to remove from the books. "
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:245
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:246
msgid "Check this box to remove all tags from the books."
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:246
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:247
msgid "Remove all"
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:250
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:251
msgid "Remove &format:"
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:251
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:252
msgid "&Swap title and author"
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:252
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:253
msgid ""
"Selected books will be automatically numbered,\n"
"in the order you selected them.\n"
@@ -6607,56 +6631,56 @@ msgid ""
"Book A will have series number 1 and Book B series number 2."
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:257
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:258
msgid ""
"Remove stored conversion settings for the selected books.\n"
"\n"
"Future conversion of these books will use the default settings."
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:260
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:261
msgid "Remove &stored conversion settings for the selected books"
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:261
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single_ui.py:415
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:262
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single_ui.py:422
msgid "&Basic metadata"
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:262
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single_ui.py:416
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:263
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single_ui.py:423
msgid "&Custom metadata"
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:263
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:264
msgid "Search &field:"
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:264
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:265
msgid "&Search for:"
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:265
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:266
msgid "&Replace with:"
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:266
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:267
msgid "Apply function &after replace:"
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:267
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:268
msgid "Test &text"
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:268
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:269
msgid "Test re&sult"
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:269
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:270
msgid "Your test:"
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:270
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:271
msgid "&Search and replace (experimental)"
msgstr ""
@@ -6714,118 +6738,118 @@ msgstr ""
msgid "The cover in the %s format is invalid"
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single.py:333
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single.py:351
msgid ""
" The green color indicates that the current author sort matches the current "
"author"
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single.py:336
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single.py:354
msgid ""
" The red color indicates that the current author sort does not match the "
"current author"
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single.py:341
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single.py:359
msgid "Abort the editing of all remaining books"
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single.py:505
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single.py:510
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single.py:524
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single.py:529
msgid "This ISBN number is valid"
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single.py:513
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single.py:532
msgid "This ISBN number is invalid"
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single.py:592
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single.py:611
msgid "Cannot use tag editor"
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single.py:593
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single.py:612
msgid "The tags editor cannot be used if you have modified the tags"
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single.py:613
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single.py:632
msgid "Downloading cover..."
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single.py:625
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single.py:630
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single.py:636
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single.py:641
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single.py:644
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single.py:649
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single.py:655
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single.py:660
msgid "Cannot fetch cover"
msgstr "No puc aconseguir la coberta"
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single.py:626
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single.py:637
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single.py:642
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single.py:645
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single.py:656
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single.py:661
msgid "Could not fetch cover. "
msgstr "No puc aconseguir la coberta. "
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single.py:627
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single.py:646
msgid "The download timed out."
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single.py:631
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single.py:650
msgid "Could not find cover for this book. Try specifying the ISBN first."
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single.py:643
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single.py:662
msgid ""
"For the error message from each cover source, click Show details below."
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single.py:650
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single.py:669
msgid "Bad cover"
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single.py:651
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single.py:670
msgid "The cover is not a valid picture"
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single.py:684
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single.py:703
msgid "There were errors"
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single.py:685
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single.py:704
msgid "There were errors downloading social metadata"
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single.py:714
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single.py:733
msgid "Cannot fetch metadata"
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single.py:715
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single.py:734
msgid "You must specify at least one of ISBN, Title, Authors or Publisher"
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single.py:798
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single.py:820
msgid "Permission denied"
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single.py:799
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single.py:821
msgid "Could not open %s. Is it being used by another program?"
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single_ui.py:364
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single_ui.py:370
msgid "Edit Meta Information"
msgstr "Edita la meta-informació"
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single_ui.py:365
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single_ui.py:371
msgid "Meta information"
msgstr "Meta-informació"
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single_ui.py:368
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single_ui.py:374
msgid "Swap the author and title"
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single_ui.py:371
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single_ui.py:377
msgid "Author S&ort: "
msgstr "&Ordena autors: "
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single_ui.py:372
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single_ui.py:378
msgid ""
"Specify how the author(s) of this book should be sorted. For example Charles "
"Dickens should be sorted as Dickens, Charles.\n"
@@ -6833,7 +6857,7 @@ msgid ""
"strings. If it is colored red, then the authors and this text do not match."
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single_ui.py:374
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single_ui.py:380
msgid ""
"Automatically create the author sort entry based on the current author "
"entry.\n"
@@ -6841,72 +6865,76 @@ msgid ""
"green."
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single_ui.py:389
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single_ui.py:395
msgid "Remove unused series (Series that have no books)"
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single_ui.py:391
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single_ui.py:397
msgid "IS&BN:"
msgstr "IS&BN:"
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single_ui.py:392
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single_ui.py:398
msgid "Publishe&d:"
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single_ui.py:395
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single_ui.py:401
msgid "dd MMM yyyy"
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single_ui.py:396
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single_ui.py:402
msgid "&Date:"
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single_ui.py:397
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single_ui.py:403
msgid "&Comments"
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single_ui.py:398
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single_ui.py:404
msgid "&Fetch metadata from server"
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single_ui.py:399
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single_ui.py:405
msgid "Available Formats"
msgstr "Formats disponibles"
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single_ui.py:400
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single_ui.py:406
msgid "Add a new format for this book to the database"
msgstr "Afegir un nou format per a aquest llibre a la base de dades"
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single_ui.py:402
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single_ui.py:408
msgid "Remove the selected formats for this book from the database."
msgstr ""
"Elimina els formats seleccionats per a aquest llibre de la base de dades."
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single_ui.py:404
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single_ui.py:410
msgid "Set the cover for the book from the selected format"
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single_ui.py:406
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single_ui.py:412
msgid "Update metadata from the metadata in the selected format"
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single_ui.py:409
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single_ui.py:415
msgid "&Browse"
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single_ui.py:410
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single_ui.py:416
+msgid "Remove border (if any) from cover"
+msgstr ""
+
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single_ui.py:417
msgid "Reset cover to default"
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single_ui.py:412
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single_ui.py:419
msgid "Download co&ver"
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single_ui.py:413
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single_ui.py:420
msgid "Generate a default cover based on the title and author"
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single_ui.py:414
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single_ui.py:421
msgid "&Generate cover"
msgstr ""
@@ -7397,6 +7425,39 @@ msgstr ""
msgid "&Test"
msgstr ""
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/tweak_epub_ui.py:55
+msgid "Display contents of exploded ePub"
+msgstr ""
+
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/tweak_epub_ui.py:56
+msgid "&Explode ePub"
+msgstr ""
+
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/tweak_epub_ui.py:57
+msgid "Rebuild ePub from exploded contents"
+msgstr ""
+
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/tweak_epub_ui.py:58
+msgid "&Rebuild ePub"
+msgstr ""
+
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/tweak_epub_ui.py:59
+msgid "Discard changes"
+msgstr ""
+
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/tweak_epub_ui.py:60
+#: /home/kovid/work/calibre/src/calibre/gui2/preferences/main.py:218
+msgid "&Cancel"
+msgstr ""
+
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/tweak_epub_ui.py:61
+msgid ""
+"Explode the ePub to display contents in a file browser window. To tweak "
+"individual files, right-click, then 'Open with...' your editor of choice. "
+"When tweaks are complete, close the file browser window. Rebuild the ePub, "
+"updating your calibre library."
+msgstr ""
+
#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/user_profiles.py:127
msgid "No recipe selected"
msgstr ""
@@ -7790,7 +7851,7 @@ msgid "Show books in the main memory of the device"
msgstr ""
#: /home/kovid/work/calibre/src/calibre/gui2/layout.py:66
-#: /home/kovid/work/calibre/src/calibre/library/database2.py:656
+#: /home/kovid/work/calibre/src/calibre/library/database2.py:655
msgid "Card A"
msgstr ""
@@ -7799,7 +7860,7 @@ msgid "Show books in storage card A"
msgstr ""
#: /home/kovid/work/calibre/src/calibre/gui2/layout.py:68
-#: /home/kovid/work/calibre/src/calibre/library/database2.py:658
+#: /home/kovid/work/calibre/src/calibre/library/database2.py:657
msgid "Card B"
msgstr ""
@@ -7925,7 +7986,7 @@ msgstr ""
msgid "Restore default layout"
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/gui2/library/views.py:555
+#: /home/kovid/work/calibre/src/calibre/gui2/library/views.py:585
msgid ""
"Dropping onto a device is not supported. First add the book to the calibre "
"library."
@@ -8624,10 +8685,6 @@ msgstr ""
msgid "&Apply"
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/gui2/preferences/main.py:218
-msgid "&Cancel"
-msgstr ""
-
#: /home/kovid/work/calibre/src/calibre/gui2/preferences/main.py:222
msgid "Restore &defaults"
msgstr ""
@@ -8907,7 +8964,7 @@ msgid ""
msgstr ""
#: /home/kovid/work/calibre/src/calibre/gui2/preferences/server.py:75
-#: /home/kovid/work/calibre/src/calibre/gui2/ui.py:319
+#: /home/kovid/work/calibre/src/calibre/gui2/ui.py:320
msgid "Failed to start content server"
msgstr ""
@@ -9308,78 +9365,78 @@ msgstr ""
msgid "Queueing "
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/gui2/tools.py:239
+#: /home/kovid/work/calibre/src/calibre/gui2/tools.py:243
msgid "Fetch news from "
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/gui2/tools.py:309
+#: /home/kovid/work/calibre/src/calibre/gui2/tools.py:313
msgid "Convert existing"
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/gui2/tools.py:310
+#: /home/kovid/work/calibre/src/calibre/gui2/tools.py:314
msgid ""
"The following books have already been converted to %s format. Do you wish to "
"reconvert them?"
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/gui2/ui.py:167
+#: /home/kovid/work/calibre/src/calibre/gui2/ui.py:168
msgid "&Restore"
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/gui2/ui.py:169
+#: /home/kovid/work/calibre/src/calibre/gui2/ui.py:170
msgid "&Donate to support calibre"
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/gui2/ui.py:173
+#: /home/kovid/work/calibre/src/calibre/gui2/ui.py:174
msgid "&Eject connected device"
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/gui2/ui.py:215
+#: /home/kovid/work/calibre/src/calibre/gui2/ui.py:216
msgid "Calibre Quick Start Guide"
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/gui2/ui.py:417
-#: /home/kovid/work/calibre/src/calibre/gui2/ui.py:445
+#: /home/kovid/work/calibre/src/calibre/gui2/ui.py:418
+#: /home/kovid/work/calibre/src/calibre/gui2/ui.py:446
msgid "Conversion Error"
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/gui2/ui.py:418
+#: /home/kovid/work/calibre/src/calibre/gui2/ui.py:419
msgid ""
"
Could not convert: %s
It is a DRMed book. You must "
"first remove the DRM using third party tools."
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/gui2/ui.py:431
+#: /home/kovid/work/calibre/src/calibre/gui2/ui.py:432
msgid "Recipe Disabled"
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/gui2/ui.py:446
+#: /home/kovid/work/calibre/src/calibre/gui2/ui.py:447
msgid "Failed"
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/gui2/ui.py:482
+#: /home/kovid/work/calibre/src/calibre/gui2/ui.py:483
msgid ""
"is the result of the efforts of many volunteers from all over the world. If "
"you find it useful, please consider donating to support its development. "
"Your donation helps keep calibre development going."
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/gui2/ui.py:508
+#: /home/kovid/work/calibre/src/calibre/gui2/ui.py:509
msgid "There are active jobs. Are you sure you want to quit?"
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/gui2/ui.py:511
+#: /home/kovid/work/calibre/src/calibre/gui2/ui.py:512
msgid ""
" is communicating with the device! \n"
" Quitting may cause corruption on the device. \n"
" Are you sure you want to quit?"
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/gui2/ui.py:515
+#: /home/kovid/work/calibre/src/calibre/gui2/ui.py:516
msgid "WARNING: Active jobs"
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/gui2/ui.py:583
+#: /home/kovid/work/calibre/src/calibre/gui2/ui.py:584
msgid ""
"will keep running in the system tray. To close it, choose Quit in the "
"context menu of the system tray."
@@ -10156,48 +10213,48 @@ msgstr ""
msgid "Turn on the &content server"
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/library/caches.py:232
+#: /home/kovid/work/calibre/src/calibre/library/caches.py:234
msgid "today"
msgstr "avui"
-#: /home/kovid/work/calibre/src/calibre/library/caches.py:235
+#: /home/kovid/work/calibre/src/calibre/library/caches.py:237
msgid "yesterday"
msgstr "ahir"
-#: /home/kovid/work/calibre/src/calibre/library/caches.py:238
+#: /home/kovid/work/calibre/src/calibre/library/caches.py:240
msgid "thismonth"
msgstr "aquestmes"
-#: /home/kovid/work/calibre/src/calibre/library/caches.py:241
-#: /home/kovid/work/calibre/src/calibre/library/caches.py:242
+#: /home/kovid/work/calibre/src/calibre/library/caches.py:243
+#: /home/kovid/work/calibre/src/calibre/library/caches.py:244
msgid "daysago"
msgstr "dies"
-#: /home/kovid/work/calibre/src/calibre/library/caches.py:406
-#: /home/kovid/work/calibre/src/calibre/library/caches.py:416
+#: /home/kovid/work/calibre/src/calibre/library/caches.py:408
+#: /home/kovid/work/calibre/src/calibre/library/caches.py:418
msgid "no"
msgstr "no"
-#: /home/kovid/work/calibre/src/calibre/library/caches.py:406
-#: /home/kovid/work/calibre/src/calibre/library/caches.py:416
+#: /home/kovid/work/calibre/src/calibre/library/caches.py:408
+#: /home/kovid/work/calibre/src/calibre/library/caches.py:418
msgid "unchecked"
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/library/caches.py:409
-#: /home/kovid/work/calibre/src/calibre/library/caches.py:419
+#: /home/kovid/work/calibre/src/calibre/library/caches.py:411
+#: /home/kovid/work/calibre/src/calibre/library/caches.py:421
msgid "checked"
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/library/caches.py:409
-#: /home/kovid/work/calibre/src/calibre/library/caches.py:419
+#: /home/kovid/work/calibre/src/calibre/library/caches.py:411
+#: /home/kovid/work/calibre/src/calibre/library/caches.py:421
msgid "yes"
msgstr "sí"
-#: /home/kovid/work/calibre/src/calibre/library/caches.py:413
+#: /home/kovid/work/calibre/src/calibre/library/caches.py:415
msgid "blank"
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/library/caches.py:413
+#: /home/kovid/work/calibre/src/calibre/library/caches.py:415
msgid "empty"
msgstr "buit"
@@ -10780,31 +10837,31 @@ msgstr ""
msgid "%sAverage rating is %3.1f"
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/library/database2.py:654
+#: /home/kovid/work/calibre/src/calibre/library/database2.py:653
msgid "Main"
msgstr "Inici"
-#: /home/kovid/work/calibre/src/calibre/library/database2.py:1992
+#: /home/kovid/work/calibre/src/calibre/library/database2.py:1998
msgid "
Migrating old database to ebook library in %s
"
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/library/database2.py:2021
+#: /home/kovid/work/calibre/src/calibre/library/database2.py:2027
msgid "Copying %s"
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/library/database2.py:2038
+#: /home/kovid/work/calibre/src/calibre/library/database2.py:2044
msgid "Compacting database"
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/library/database2.py:2131
+#: /home/kovid/work/calibre/src/calibre/library/database2.py:2137
msgid "Checking SQL integrity..."
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/library/database2.py:2170
+#: /home/kovid/work/calibre/src/calibre/library/database2.py:2176
msgid "Checking for missing files."
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/library/database2.py:2192
+#: /home/kovid/work/calibre/src/calibre/library/database2.py:2198
msgid "Checked id"
msgstr ""
@@ -11274,59 +11331,59 @@ msgid ""
"Do not download latest version of builtin recipes from the calibre server"
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/web/feeds/news.py:46
+#: /home/kovid/work/calibre/src/calibre/web/feeds/news.py:47
msgid "Unknown News Source"
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/web/feeds/news.py:611
+#: /home/kovid/work/calibre/src/calibre/web/feeds/news.py:612
msgid "The \"%s\" recipe needs a username and password."
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/web/feeds/news.py:710
+#: /home/kovid/work/calibre/src/calibre/web/feeds/news.py:711
msgid "Download finished"
msgstr "S'ha finalitzat la baixada"
-#: /home/kovid/work/calibre/src/calibre/web/feeds/news.py:712
+#: /home/kovid/work/calibre/src/calibre/web/feeds/news.py:713
msgid "Failed to download the following articles:"
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/web/feeds/news.py:718
+#: /home/kovid/work/calibre/src/calibre/web/feeds/news.py:719
msgid "Failed to download parts of the following articles:"
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/web/feeds/news.py:720
+#: /home/kovid/work/calibre/src/calibre/web/feeds/news.py:721
msgid " from "
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/web/feeds/news.py:722
+#: /home/kovid/work/calibre/src/calibre/web/feeds/news.py:723
msgid "\tFailed links:"
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/web/feeds/news.py:811
+#: /home/kovid/work/calibre/src/calibre/web/feeds/news.py:812
msgid "Could not fetch article. Run with -vv to see the reason"
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/web/feeds/news.py:832
+#: /home/kovid/work/calibre/src/calibre/web/feeds/news.py:833
msgid "Fetching feeds..."
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/web/feeds/news.py:837
+#: /home/kovid/work/calibre/src/calibre/web/feeds/news.py:838
msgid "Got feeds from index page"
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/web/feeds/news.py:843
+#: /home/kovid/work/calibre/src/calibre/web/feeds/news.py:844
msgid "Trying to download cover..."
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/web/feeds/news.py:845
+#: /home/kovid/work/calibre/src/calibre/web/feeds/news.py:846
msgid "Generating masthead..."
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/web/feeds/news.py:926
+#: /home/kovid/work/calibre/src/calibre/web/feeds/news.py:927
msgid "Starting download [%d thread(s)]..."
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/web/feeds/news.py:942
+#: /home/kovid/work/calibre/src/calibre/web/feeds/news.py:943
msgid "Feeds downloaded to %s"
msgstr ""
@@ -11334,37 +11391,37 @@ msgstr ""
msgid "Could not download cover: %s"
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/web/feeds/news.py:964
+#: /home/kovid/work/calibre/src/calibre/web/feeds/news.py:959
msgid "Downloading cover from %s"
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/web/feeds/news.py:1005
+#: /home/kovid/work/calibre/src/calibre/web/feeds/news.py:1004
msgid "Masthead image downloaded"
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/web/feeds/news.py:1173
+#: /home/kovid/work/calibre/src/calibre/web/feeds/news.py:1172
msgid "Untitled Article"
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/web/feeds/news.py:1244
+#: /home/kovid/work/calibre/src/calibre/web/feeds/news.py:1243
msgid "Article downloaded: %s"
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/web/feeds/news.py:1255
+#: /home/kovid/work/calibre/src/calibre/web/feeds/news.py:1254
msgid "Article download failed: %s"
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/web/feeds/news.py:1272
+#: /home/kovid/work/calibre/src/calibre/web/feeds/news.py:1271
msgid "Fetching feed"
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/web/feeds/news.py:1419
+#: /home/kovid/work/calibre/src/calibre/web/feeds/news.py:1418
msgid ""
"Failed to log in, check your username and password for the calibre "
"Periodicals service."
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/web/feeds/news.py:1435
+#: /home/kovid/work/calibre/src/calibre/web/feeds/news.py:1433
msgid ""
"You do not have permission to download this issue. Either your subscription "
"has expired or you have exceeded the maximum allowed downloads for today."
diff --git a/src/calibre/translations/da.po b/src/calibre/translations/da.po
index df0f6d11a4..a7df259e89 100644
--- a/src/calibre/translations/da.po
+++ b/src/calibre/translations/da.po
@@ -7,14 +7,14 @@ msgid ""
msgstr ""
"Project-Id-Version: calibre\n"
"Report-Msgid-Bugs-To: FULL NAME \n"
-"POT-Creation-Date: 2010-09-17 21:00+0000\n"
-"PO-Revision-Date: 2010-09-23 17:40+0000\n"
-"Last-Translator: Glenn \n"
+"POT-Creation-Date: 2010-09-24 21:33+0000\n"
+"PO-Revision-Date: 2010-09-24 20:56+0000\n"
+"Last-Translator: Kovid Goyal \n"
"Language-Team: Danish \n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
-"X-Launchpad-Export-Date: 2010-09-24 04:39+0000\n"
+"X-Launchpad-Export-Date: 2010-09-25 04:39+0000\n"
"X-Generator: Launchpad (build Unknown)\n"
#: /home/kovid/work/calibre/src/calibre/customize/__init__.py:43
@@ -24,7 +24,8 @@ msgstr "Gør absolut ingenting"
#: /home/kovid/work/calibre/src/calibre/customize/__init__.py:46
#: /home/kovid/work/calibre/src/calibre/devices/jetbook/driver.py:74
#: /home/kovid/work/calibre/src/calibre/devices/kindle/driver.py:76
-#: /home/kovid/work/calibre/src/calibre/devices/kobo/driver.py:410
+#: /home/kovid/work/calibre/src/calibre/devices/kobo/books.py:46
+#: /home/kovid/work/calibre/src/calibre/devices/kobo/driver.py:412
#: /home/kovid/work/calibre/src/calibre/devices/nook/driver.py:70
#: /home/kovid/work/calibre/src/calibre/devices/nook/driver.py:71
#: /home/kovid/work/calibre/src/calibre/devices/prs500/books.py:267
@@ -105,24 +106,24 @@ msgstr "Gør absolut ingenting"
#: /home/kovid/work/calibre/src/calibre/ebooks/pdf/writer.py:98
#: /home/kovid/work/calibre/src/calibre/ebooks/rtf/input.py:239
#: /home/kovid/work/calibre/src/calibre/ebooks/rtf/input.py:241
-#: /home/kovid/work/calibre/src/calibre/gui2/__init__.py:324
-#: /home/kovid/work/calibre/src/calibre/gui2/__init__.py:331
-#: /home/kovid/work/calibre/src/calibre/gui2/actions/edit_metadata.py:293
+#: /home/kovid/work/calibre/src/calibre/gui2/__init__.py:352
+#: /home/kovid/work/calibre/src/calibre/gui2/__init__.py:359
#: /home/kovid/work/calibre/src/calibre/gui2/actions/edit_metadata.py:296
+#: /home/kovid/work/calibre/src/calibre/gui2/actions/edit_metadata.py:299
#: /home/kovid/work/calibre/src/calibre/gui2/add.py:137
#: /home/kovid/work/calibre/src/calibre/gui2/add.py:144
#: /home/kovid/work/calibre/src/calibre/gui2/convert/__init__.py:42
#: /home/kovid/work/calibre/src/calibre/gui2/convert/metadata.py:111
#: /home/kovid/work/calibre/src/calibre/gui2/convert/metadata.py:136
#: /home/kovid/work/calibre/src/calibre/gui2/convert/metadata.py:138
-#: /home/kovid/work/calibre/src/calibre/gui2/device.py:865
-#: /home/kovid/work/calibre/src/calibre/gui2/device.py:874
-#: /home/kovid/work/calibre/src/calibre/gui2/device.py:1158
-#: /home/kovid/work/calibre/src/calibre/gui2/device.py:1161
+#: /home/kovid/work/calibre/src/calibre/gui2/device.py:869
+#: /home/kovid/work/calibre/src/calibre/gui2/device.py:878
+#: /home/kovid/work/calibre/src/calibre/gui2/device.py:1162
+#: /home/kovid/work/calibre/src/calibre/gui2/device.py:1165
#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/comicconf.py:47
#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/fetch_metadata.py:120
#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/fetch_metadata.py:155
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single.py:552
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single.py:571
#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/scheduler.py:173
#: /home/kovid/work/calibre/src/calibre/gui2/library/models.py:357
#: /home/kovid/work/calibre/src/calibre/gui2/library/models.py:377
@@ -135,11 +136,11 @@ msgstr "Gør absolut ingenting"
#: /home/kovid/work/calibre/src/calibre/library/database.py:913
#: /home/kovid/work/calibre/src/calibre/library/database2.py:375
#: /home/kovid/work/calibre/src/calibre/library/database2.py:387
-#: /home/kovid/work/calibre/src/calibre/library/database2.py:1065
-#: /home/kovid/work/calibre/src/calibre/library/database2.py:1137
-#: /home/kovid/work/calibre/src/calibre/library/database2.py:1837
-#: /home/kovid/work/calibre/src/calibre/library/database2.py:1839
-#: /home/kovid/work/calibre/src/calibre/library/database2.py:1966
+#: /home/kovid/work/calibre/src/calibre/library/database2.py:1064
+#: /home/kovid/work/calibre/src/calibre/library/database2.py:1139
+#: /home/kovid/work/calibre/src/calibre/library/database2.py:1843
+#: /home/kovid/work/calibre/src/calibre/library/database2.py:1845
+#: /home/kovid/work/calibre/src/calibre/library/database2.py:1972
#: /home/kovid/work/calibre/src/calibre/library/server/mobile.py:211
#: /home/kovid/work/calibre/src/calibre/library/server/opds.py:137
#: /home/kovid/work/calibre/src/calibre/library/server/opds.py:140
@@ -261,43 +262,43 @@ msgstr "Gemmer metadata i %s filerne"
msgid "Set metadata from %s files"
msgstr "Sæt metadata fra %s filer"
-#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:684
+#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:688
msgid "Look and Feel"
msgstr "Fremtoning"
-#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:686
-#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:698
-#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:709
-#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:720
+#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:690
+#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:702
+#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:713
+#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:724
msgid "Interface"
msgstr "Brugergrænseflade"
-#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:690
+#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:694
msgid "Adjust the look and feel of the calibre interface to suit your tastes"
msgstr "Tilpas calibres grænseflades fremtoning til din smag"
-#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:696
+#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:700
msgid "Behavior"
msgstr "Opførsel"
-#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:702
+#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:706
msgid "Change the way calibre behaves"
msgstr "Ændr måden calibre opfører sig på"
-#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:707
+#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:711
#: /home/kovid/work/calibre/src/calibre/gui2/library/views.py:176
msgid "Add your own columns"
msgstr "Tilføj dine egne søjler"
-#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:713
+#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:717
msgid "Add/remove your own columns to the calibre book list"
msgstr "Tilføj/fjern dine egne søjler til calibre boglisten"
-#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:718
+#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:722
msgid "Customize the toolbar"
msgstr "Tilpas værktøjslinjen"
-#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:724
+#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:728
msgid ""
"Customize the toolbars and context menus, changing which actions are "
"available in each"
@@ -305,55 +306,55 @@ msgstr ""
"Tilpas værktøjslinjen og kontekstmenuen, ændre hvilke aktioner som er "
"tilgængelige i hver"
-#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:730
+#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:734
msgid "Input Options"
msgstr "Input tilvalg"
-#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:732
-#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:743
-#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:754
+#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:736
+#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:747
+#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:758
msgid "Conversion"
msgstr "Konvertering"
-#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:736
+#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:740
msgid "Set conversion options specific to each input format"
msgstr "Vælg konverteringsvalgmuligheder specifikke for hvert input-format"
-#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:741
+#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:745
msgid "Common Options"
msgstr "Fælles tilvalg"
-#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:747
+#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:751
msgid "Set conversion options common to all formats"
msgstr "Vælg konverteringsvalgmuligheder fælles for alle formater"
-#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:752
+#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:756
msgid "Output Options"
msgstr "Output valgmuligheder"
-#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:758
+#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:762
msgid "Set conversion options specific to each output format"
msgstr "Vælg konverteringsvalgmuligheder specifikke for hvert output-format"
-#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:763
+#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:767
msgid "Adding books"
msgstr "Tilføjer boger"
-#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:765
-#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:777
-#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:789
+#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:769
+#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:781
+#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:793
msgid "Import/Export"
msgstr "Import/eksport"
-#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:769
+#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:773
msgid "Control how calibre reads metadata from files when adding books"
msgstr "Styre hvordan calibre læser metadata fra filer, når bøger tilføjes"
-#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:775
+#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:779
msgid "Saving books to disk"
msgstr "Gemmer bøger til disk"
-#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:781
+#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:785
msgid ""
"Control how calibre exports files from its database to disk when using Save "
"to disk"
@@ -361,24 +362,24 @@ msgstr ""
"Styre hvordan calibre eksporterer filer fra dens database til disk, når gem-"
"til-disk anvendes"
-#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:787
+#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:791
msgid "Sending books to devices"
msgstr "Sender bøger til enheder"
-#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:793
+#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:797
msgid "Control how calibre transfers files to your ebook reader"
msgstr "Styre hvordan calibre overfører filer til dil e-bogslæser"
-#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:799
+#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:803
msgid "Sharing books by email"
msgstr "Deler bøger via email"
-#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:801
-#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:813
+#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:805
+#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:817
msgid "Sharing"
msgstr "Deler"
-#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:805
+#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:809
msgid ""
"Setup sharing of books via email. Can be used for automatic sending of "
"downloaded news to your devices"
@@ -386,11 +387,11 @@ msgstr ""
"Opsætning som deler bøger via email. Kan anvendes til automatisk sending af "
"downloadede nyheder til dine enheder"
-#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:811
+#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:815
msgid "Sharing over the net"
msgstr "Deler over internettet"
-#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:817
+#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:821
msgid ""
"Setup the calibre Content Server which will give you access to your calibre "
"library from anywhere, on any device, over the internet"
@@ -398,33 +399,33 @@ msgstr ""
"Opsætning af calibre indholdsserveren, hvilket vil give dig adgang til dit "
"calibre-bibliotek fra overalt, på enhver enhed, over internettet"
-#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:824
+#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:828
msgid "Plugins"
msgstr "Udvidelsesmoduler"
-#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:826
-#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:838
-#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:849
+#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:830
+#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:842
+#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:853
msgid "Advanced"
msgstr "Avanceret"
-#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:830
+#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:834
msgid "Add/remove/customize various bits of calibre functionality"
msgstr "Tilføj/fjern/tilpas forskellige dele af calibres funktionalitet"
-#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:836
+#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:840
msgid "Tweaks"
msgstr "Tweaks"
-#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:842
+#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:846
msgid "Fine tune how calibre behaves in various contexts"
msgstr "Finjustér hvordan calibre opfører sig i forskellige sammenhænge"
-#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:847
+#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:851
msgid "Miscellaneous"
msgstr "Diverse"
-#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:853
+#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:857
msgid "Miscellaneous advanced configuration"
msgstr "Diverse avanceret opsætning"
@@ -603,16 +604,16 @@ msgstr "Deaktiverede udvidelsesmoduler"
msgid "Enabled plugins"
msgstr "Aktiverede udvidelsesmoduler"
-#: /home/kovid/work/calibre/src/calibre/customize/ui.py:86
+#: /home/kovid/work/calibre/src/calibre/customize/ui.py:93
msgid "No valid plugin found in "
msgstr "Intet gyldigt udvidelsesmodul fundet i "
-#: /home/kovid/work/calibre/src/calibre/customize/ui.py:501
+#: /home/kovid/work/calibre/src/calibre/customize/ui.py:508
msgid "Initialization of plugin %s failed with traceback:"
msgstr ""
"Initialiseringen af udvidelsesmodul %s fejlede med følgende backtrace:"
-#: /home/kovid/work/calibre/src/calibre/customize/ui.py:534
+#: /home/kovid/work/calibre/src/calibre/customize/ui.py:541
msgid ""
" %prog options\n"
"\n"
@@ -624,19 +625,19 @@ msgstr ""
" Tilpas Calibre ved at indlæse eksterne udvidelsesmoduler.\n"
" "
-#: /home/kovid/work/calibre/src/calibre/customize/ui.py:540
+#: /home/kovid/work/calibre/src/calibre/customize/ui.py:547
msgid "Add a plugin by specifying the path to the zip file containing it."
msgstr ""
"Tilføj et udvidelsesmodul ved at angive stien til ZIP-filen, hvori det er "
"gemt."
-#: /home/kovid/work/calibre/src/calibre/customize/ui.py:542
+#: /home/kovid/work/calibre/src/calibre/customize/ui.py:549
msgid "Remove a custom plugin by name. Has no effect on builtin plugins"
msgstr ""
"Fjerner det angivne brugerdefinerede udvidelsesmodul. Har ingen effekt på "
"indbyggede udvidelser"
-#: /home/kovid/work/calibre/src/calibre/customize/ui.py:544
+#: /home/kovid/work/calibre/src/calibre/customize/ui.py:551
msgid ""
"Customize plugin. Specify name of plugin and customization string separated "
"by a comma."
@@ -644,15 +645,15 @@ msgstr ""
"Brugertilpasset udvidelsesmodul. Angiv udvidelsesmodulnavn og "
"tilpasningstekststrenge adskilt af kommaer."
-#: /home/kovid/work/calibre/src/calibre/customize/ui.py:546
+#: /home/kovid/work/calibre/src/calibre/customize/ui.py:553
msgid "List all installed plugins"
msgstr "Vis alle installerede udvidelsesmoduler"
-#: /home/kovid/work/calibre/src/calibre/customize/ui.py:548
+#: /home/kovid/work/calibre/src/calibre/customize/ui.py:555
msgid "Enable the named plugin"
msgstr "Aktivér det angivne udvidelsesmodul"
-#: /home/kovid/work/calibre/src/calibre/customize/ui.py:550
+#: /home/kovid/work/calibre/src/calibre/customize/ui.py:557
msgid "Disable the named plugin"
msgstr "Deaktivér det angivne udvidelsesmodul"
@@ -660,7 +661,7 @@ msgstr "Deaktivér det angivne udvidelsesmodul"
msgid "Communicate with Android phones."
msgstr "Kommunikér med Android telefoner"
-#: /home/kovid/work/calibre/src/calibre/devices/android/driver.py:50
+#: /home/kovid/work/calibre/src/calibre/devices/android/driver.py:52
msgid ""
"Comma separated list of directories to send e-books to on the device. The "
"first one that exists will be used"
@@ -668,7 +669,7 @@ msgstr ""
"Komma separeret liste af mapper til at sende e-bøger til, på enheden. Den "
"første som findes, vil blive brugt."
-#: /home/kovid/work/calibre/src/calibre/devices/android/driver.py:92
+#: /home/kovid/work/calibre/src/calibre/devices/android/driver.py:94
msgid "Communicate with S60 phones."
msgstr "Kommunikér med S60 telefoner."
@@ -737,7 +738,7 @@ msgstr ""
#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/scheduler.py:244
#: /home/kovid/work/calibre/src/calibre/library/database2.py:198
#: /home/kovid/work/calibre/src/calibre/library/database2.py:211
-#: /home/kovid/work/calibre/src/calibre/library/database2.py:1706
+#: /home/kovid/work/calibre/src/calibre/library/database2.py:1712
#: /home/kovid/work/calibre/src/calibre/library/field_metadata.py:134
msgid "News"
msgstr "Nyheder"
@@ -745,8 +746,8 @@ msgstr "Nyheder"
#: /home/kovid/work/calibre/src/calibre/devices/apple/driver.py:2500
#: /home/kovid/work/calibre/src/calibre/gui2/catalog/catalog_epub_mobi.py:20
#: /home/kovid/work/calibre/src/calibre/library/catalog.py:556
-#: /home/kovid/work/calibre/src/calibre/library/database2.py:1669
-#: /home/kovid/work/calibre/src/calibre/library/database2.py:1687
+#: /home/kovid/work/calibre/src/calibre/library/database2.py:1675
+#: /home/kovid/work/calibre/src/calibre/library/database2.py:1693
msgid "Catalog"
msgstr "Katalog"
@@ -830,23 +831,23 @@ msgstr ""
"Komma separeret liste af mapper til at sende e-bøger til på enheden. Den "
"første først fundne anvendes."
-#: /home/kovid/work/calibre/src/calibre/devices/hanvon/driver.py:18
+#: /home/kovid/work/calibre/src/calibre/devices/hanvon/driver.py:22
msgid "Communicate with the Hanvon N520 eBook reader."
msgstr "Kommunikér med Hanvon N520 eBook læser."
-#: /home/kovid/work/calibre/src/calibre/devices/hanvon/driver.py:40
+#: /home/kovid/work/calibre/src/calibre/devices/hanvon/driver.py:47
msgid "Communicate with The Book reader."
msgstr "Kommunikér med The Book læser."
-#: /home/kovid/work/calibre/src/calibre/devices/hanvon/driver.py:52
+#: /home/kovid/work/calibre/src/calibre/devices/hanvon/driver.py:59
msgid "Communicate with the SpringDesign Alex eBook reader."
msgstr "Kommunikér med SpringDesign Alex eBook læser."
-#: /home/kovid/work/calibre/src/calibre/devices/hanvon/driver.py:68
+#: /home/kovid/work/calibre/src/calibre/devices/hanvon/driver.py:78
msgid "Communicate with the Azbooka"
msgstr "Kommunikér med Azbooka"
-#: /home/kovid/work/calibre/src/calibre/devices/hanvon/driver.py:81
+#: /home/kovid/work/calibre/src/calibre/devices/hanvon/driver.py:94
msgid "Communicate with the Elonex EB 511 eBook reader."
msgstr "Kommunikér med Elonex EB 511 eBook læser."
@@ -907,7 +908,7 @@ msgstr ""
#: /home/kovid/work/calibre/src/calibre/devices/kobo/driver.py:63
#: /home/kovid/work/calibre/src/calibre/devices/kobo/driver.py:66
#: /home/kovid/work/calibre/src/calibre/devices/kobo/driver.py:69
-#: /home/kovid/work/calibre/src/calibre/devices/kobo/driver.py:186
+#: /home/kovid/work/calibre/src/calibre/devices/kobo/driver.py:188
#: /home/kovid/work/calibre/src/calibre/devices/usbms/driver.py:68
#: /home/kovid/work/calibre/src/calibre/devices/usbms/driver.py:71
#: /home/kovid/work/calibre/src/calibre/devices/usbms/driver.py:74
@@ -917,33 +918,33 @@ msgstr ""
msgid "Getting list of books on device..."
msgstr "Henter liste over bøger på enheden..."
-#: /home/kovid/work/calibre/src/calibre/devices/kobo/driver.py:246
-#: /home/kovid/work/calibre/src/calibre/devices/kobo/driver.py:278
+#: /home/kovid/work/calibre/src/calibre/devices/kobo/driver.py:248
+#: /home/kovid/work/calibre/src/calibre/devices/kobo/driver.py:280
#: /home/kovid/work/calibre/src/calibre/devices/usbms/driver.py:253
#: /home/kovid/work/calibre/src/calibre/devices/usbms/driver.py:271
msgid "Removing books from device..."
msgstr "Fjerner bøger fra enhed..."
-#: /home/kovid/work/calibre/src/calibre/devices/kobo/driver.py:282
-#: /home/kovid/work/calibre/src/calibre/devices/kobo/driver.py:289
+#: /home/kovid/work/calibre/src/calibre/devices/kobo/driver.py:284
+#: /home/kovid/work/calibre/src/calibre/devices/kobo/driver.py:291
#: /home/kovid/work/calibre/src/calibre/devices/usbms/driver.py:278
#: /home/kovid/work/calibre/src/calibre/devices/usbms/driver.py:283
msgid "Removing books from device metadata listing..."
msgstr "Fjerner bøger fra enhedens metadataliste..."
-#: /home/kovid/work/calibre/src/calibre/devices/kobo/driver.py:294
-#: /home/kovid/work/calibre/src/calibre/devices/kobo/driver.py:328
+#: /home/kovid/work/calibre/src/calibre/devices/kobo/driver.py:296
+#: /home/kovid/work/calibre/src/calibre/devices/kobo/driver.py:330
#: /home/kovid/work/calibre/src/calibre/devices/usbms/driver.py:217
#: /home/kovid/work/calibre/src/calibre/devices/usbms/driver.py:247
msgid "Adding books to device metadata listing..."
msgstr "Tilføjer bøger til enhedens metadataliste..."
-#: /home/kovid/work/calibre/src/calibre/devices/kobo/driver.py:390
+#: /home/kovid/work/calibre/src/calibre/devices/kobo/driver.py:392
#: /home/kovid/work/calibre/src/calibre/gui2/actions/add.py:252
msgid "Not Implemented"
msgstr "Ikke implementeret"
-#: /home/kovid/work/calibre/src/calibre/devices/kobo/driver.py:391
+#: /home/kovid/work/calibre/src/calibre/devices/kobo/driver.py:393
msgid ""
"\".kobo\" files do not exist on the device as books instead, they are rows "
"in the sqlite database. Currently they cannot be exported or viewed."
@@ -3387,7 +3388,7 @@ msgstr "Kopiér"
msgid "Copy to Clipboard"
msgstr "Kopiér til udklipsholder"
-#: /home/kovid/work/calibre/src/calibre/gui2/__init__.py:434
+#: /home/kovid/work/calibre/src/calibre/gui2/__init__.py:462
msgid "Choose Files"
msgstr "Vælg filer"
@@ -3519,6 +3520,7 @@ msgstr "Tilføj til bibliotek"
#: /home/kovid/work/calibre/src/calibre/gui2/actions/add.py:240
#: /home/kovid/work/calibre/src/calibre/gui2/actions/delete.py:56
+#: /home/kovid/work/calibre/src/calibre/gui2/actions/tweak_epub.py:28
#: /home/kovid/work/calibre/src/calibre/gui2/actions/view.py:95
#: /home/kovid/work/calibre/src/calibre/gui2/actions/view.py:120
msgid "No book selected"
@@ -3563,8 +3565,8 @@ msgstr "Kun brugernoter genereret fra arbejdsbibliotek"
#: /home/kovid/work/calibre/src/calibre/gui2/actions/copy_to_library.py:116
#: /home/kovid/work/calibre/src/calibre/gui2/actions/edit_metadata.py:76
#: /home/kovid/work/calibre/src/calibre/gui2/actions/edit_metadata.py:142
-#: /home/kovid/work/calibre/src/calibre/gui2/actions/edit_metadata.py:178
-#: /home/kovid/work/calibre/src/calibre/gui2/actions/edit_metadata.py:205
+#: /home/kovid/work/calibre/src/calibre/gui2/actions/edit_metadata.py:180
+#: /home/kovid/work/calibre/src/calibre/gui2/actions/edit_metadata.py:208
#: /home/kovid/work/calibre/src/calibre/gui2/actions/save_to_disk.py:92
msgid "No books selected"
msgstr "Ingen bøger valgt"
@@ -3720,7 +3722,7 @@ msgstr ""
#: /home/kovid/work/calibre/src/calibre/gui2/actions/choose_library.py:249
#: /home/kovid/work/calibre/src/calibre/gui2/actions/choose_library.py:254
#: /home/kovid/work/calibre/src/calibre/gui2/actions/save_to_disk.py:101
-#: /home/kovid/work/calibre/src/calibre/gui2/library/views.py:554
+#: /home/kovid/work/calibre/src/calibre/gui2/library/views.py:584
msgid "Not allowed"
msgstr "Ikke tilladt"
@@ -3787,7 +3789,7 @@ msgstr "Kunne ikke kopiere bøger: "
#: /home/kovid/work/calibre/src/calibre/gui2/actions/copy_to_library.py:138
#: /home/kovid/work/calibre/src/calibre/gui2/device.py:670
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk.py:424
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk.py:428
msgid "Failed"
msgstr "Fejlede"
@@ -4028,20 +4030,20 @@ msgid "Error"
msgstr "Fejl"
#: /home/kovid/work/calibre/src/calibre/gui2/actions/edit_metadata.py:141
-#: /home/kovid/work/calibre/src/calibre/gui2/actions/edit_metadata.py:177
+#: /home/kovid/work/calibre/src/calibre/gui2/actions/edit_metadata.py:179
msgid "Cannot edit metadata"
msgstr "Kan ikke redigere metadata"
-#: /home/kovid/work/calibre/src/calibre/gui2/actions/edit_metadata.py:204
#: /home/kovid/work/calibre/src/calibre/gui2/actions/edit_metadata.py:207
+#: /home/kovid/work/calibre/src/calibre/gui2/actions/edit_metadata.py:210
msgid "Cannot merge books"
msgstr "Kan ikke flette bøger"
-#: /home/kovid/work/calibre/src/calibre/gui2/actions/edit_metadata.py:208
+#: /home/kovid/work/calibre/src/calibre/gui2/actions/edit_metadata.py:211
msgid "At least two books must be selected for merging"
msgstr "Mindst to bøger skal vælges for at kunne flette"
-#: /home/kovid/work/calibre/src/calibre/gui2/actions/edit_metadata.py:212
+#: /home/kovid/work/calibre/src/calibre/gui2/actions/edit_metadata.py:215
msgid ""
"Book formats and metadata from the selected books will be added to the "
"first selected book. ISBN will not be merged.
The "
@@ -4053,7 +4055,7 @@ msgstr ""
"og resterende valgte bøger, vil ikke blive slettet eller "
"ændret.
Venligst bekræft at du ønsker at fortsætte."
-#: /home/kovid/work/calibre/src/calibre/gui2/actions/edit_metadata.py:224
+#: /home/kovid/work/calibre/src/calibre/gui2/actions/edit_metadata.py:227
msgid ""
"Book formats and metadata from the selected books will be merged into the "
"first selected book. ISBN will not be merged.
After "
@@ -4071,7 +4073,7 @@ msgstr ""
"slettet fra din computer.
Er du sikker på at du vil "
"fortsætte?"
-#: /home/kovid/work/calibre/src/calibre/gui2/actions/edit_metadata.py:237
+#: /home/kovid/work/calibre/src/calibre/gui2/actions/edit_metadata.py:240
msgid ""
"You are about to merge more than 5 books. Are you sure you want to "
"proceed?"
@@ -4258,6 +4260,28 @@ msgstr "Alt+T"
msgid "Books with the same tags"
msgstr "Bøger med samme mærker"
+#: /home/kovid/work/calibre/src/calibre/gui2/actions/tweak_epub.py:15
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/tweak_epub_ui.py:54
+msgid "Tweak ePub"
+msgstr ""
+
+#: /home/kovid/work/calibre/src/calibre/gui2/actions/tweak_epub.py:16
+msgid "Make small changes to ePub format books"
+msgstr ""
+
+#: /home/kovid/work/calibre/src/calibre/gui2/actions/tweak_epub.py:17
+msgid "T"
+msgstr ""
+
+#: /home/kovid/work/calibre/src/calibre/gui2/actions/tweak_epub.py:27
+#: /home/kovid/work/calibre/src/calibre/gui2/actions/tweak_epub.py:39
+msgid "Cannot tweak ePub"
+msgstr ""
+
+#: /home/kovid/work/calibre/src/calibre/gui2/actions/tweak_epub.py:40
+msgid "No ePub available. First convert the book to ePub."
+msgstr ""
+
#: /home/kovid/work/calibre/src/calibre/gui2/actions/view.py:24
msgid "V"
msgstr "V"
@@ -4325,7 +4349,7 @@ msgid "The specified directory could not be processed."
msgstr "Den angivne mappe kunne ikke behandles."
#: /home/kovid/work/calibre/src/calibre/gui2/add.py:228
-#: /home/kovid/work/calibre/src/calibre/gui2/device.py:807
+#: /home/kovid/work/calibre/src/calibre/gui2/device.py:811
msgid "No books"
msgstr "Ingen bøger"
@@ -4464,13 +4488,13 @@ msgstr ""
#: /home/kovid/work/calibre/src/calibre/gui2/device_drivers/configwidget_ui.py:84
#: /home/kovid/work/calibre/src/calibre/gui2/device_drivers/configwidget_ui.py:85
#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/choose_library_ui.py:77
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single_ui.py:369
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single_ui.py:376
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single_ui.py:390
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single_ui.py:401
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single_ui.py:403
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single_ui.py:405
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single_ui.py:375
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single_ui.py:382
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single_ui.py:396
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single_ui.py:407
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single_ui.py:409
#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single_ui.py:411
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single_ui.py:418
#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/saved_search_editor_ui.py:92
#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/saved_search_editor_ui.py:95
#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/tag_categories_ui.py:161
@@ -5257,7 +5281,7 @@ msgid " is not a valid picture"
msgstr " er ikke valid billede"
#: /home/kovid/work/calibre/src/calibre/gui2/convert/metadata_ui.py:172
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single_ui.py:407
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single_ui.py:413
msgid "Book Cover"
msgstr "Bogomslag"
@@ -5266,7 +5290,7 @@ msgid "Use cover from &source file"
msgstr "Brug omslag fra &kildefilen"
#: /home/kovid/work/calibre/src/calibre/gui2/convert/metadata_ui.py:174
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single_ui.py:408
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single_ui.py:414
msgid "Change &cover image:"
msgstr "Skift &omslagsbillede:"
@@ -5275,18 +5299,18 @@ msgid "Browse for an image to use as the cover of this book."
msgstr "Browse efter et billede til anvendelse som denne bogs omslag."
#: /home/kovid/work/calibre/src/calibre/gui2/convert/metadata_ui.py:177
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single_ui.py:366
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single_ui.py:372
msgid "&Title: "
msgstr "&Titel: "
#: /home/kovid/work/calibre/src/calibre/gui2/convert/metadata_ui.py:178
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single_ui.py:367
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single_ui.py:373
msgid "Change the title of this book"
msgstr "Udskift bogens titel"
#: /home/kovid/work/calibre/src/calibre/gui2/convert/metadata_ui.py:179
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:229
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single_ui.py:370
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:230
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single_ui.py:376
msgid "&Author(s): "
msgstr "&Forfatter(e): "
@@ -5302,19 +5326,19 @@ msgstr ""
"Udskift bogens forfatter(e). Flere forfattere skal adskilles med et komma"
#: /home/kovid/work/calibre/src/calibre/gui2/convert/metadata_ui.py:182
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:238
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single_ui.py:381
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:239
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single_ui.py:387
msgid "&Publisher: "
msgstr "F&orlag: "
#: /home/kovid/work/calibre/src/calibre/gui2/convert/metadata_ui.py:183
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single_ui.py:382
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single_ui.py:388
msgid "Ta&gs: "
msgstr "&Mærker: "
#: /home/kovid/work/calibre/src/calibre/gui2/convert/metadata_ui.py:184
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:240
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single_ui.py:383
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:241
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single_ui.py:389
msgid ""
"Tags categorize the book. This is particularly useful while searching. "
"
They can be any words or phrases, separated by commas."
@@ -5324,22 +5348,22 @@ msgstr ""
"kommaer."
#: /home/kovid/work/calibre/src/calibre/gui2/convert/metadata_ui.py:185
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:247
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single_ui.py:386
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:248
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single_ui.py:392
msgid "&Series:"
msgstr "&Serier:"
#: /home/kovid/work/calibre/src/calibre/gui2/convert/metadata_ui.py:186
#: /home/kovid/work/calibre/src/calibre/gui2/convert/metadata_ui.py:187
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:248
#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:249
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single_ui.py:387
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single_ui.py:388
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:250
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single_ui.py:393
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single_ui.py:394
msgid "List of known series. You can add new series."
msgstr "Liste over kendte serier. Du kan tilføje nye serier."
#: /home/kovid/work/calibre/src/calibre/gui2/convert/metadata_ui.py:188
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single_ui.py:393
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single_ui.py:399
msgid "Book "
msgstr "Bog "
@@ -5838,7 +5862,7 @@ msgid " index:"
msgstr " indeks:"
#: /home/kovid/work/calibre/src/calibre/gui2/custom_column_widgets.py:451
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:256
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:257
msgid "Automatically number books in this series"
msgstr "Automatisk nummerér bøger i denne serie"
@@ -5952,123 +5976,123 @@ msgstr ""
"Der var en midlertidig fejl ved kommunikation med enheden. Afbryd og "
"genforbind enheden eller genstart."
-#: /home/kovid/work/calibre/src/calibre/gui2/device.py:716
+#: /home/kovid/work/calibre/src/calibre/gui2/device.py:720
msgid "Device: "
msgstr "Enhed: "
-#: /home/kovid/work/calibre/src/calibre/gui2/device.py:718
+#: /home/kovid/work/calibre/src/calibre/gui2/device.py:722
msgid " detected."
msgstr " detekteret."
-#: /home/kovid/work/calibre/src/calibre/gui2/device.py:808
+#: /home/kovid/work/calibre/src/calibre/gui2/device.py:812
msgid "selected to send"
msgstr "valgt til at sende"
-#: /home/kovid/work/calibre/src/calibre/gui2/device.py:813
+#: /home/kovid/work/calibre/src/calibre/gui2/device.py:817
msgid "Choose format to send to device"
msgstr "Vælg format til at sende til enheden"
-#: /home/kovid/work/calibre/src/calibre/gui2/device.py:822
+#: /home/kovid/work/calibre/src/calibre/gui2/device.py:826
msgid "No device"
msgstr "Ingen enhed"
-#: /home/kovid/work/calibre/src/calibre/gui2/device.py:823
+#: /home/kovid/work/calibre/src/calibre/gui2/device.py:827
msgid "Cannot send: No device is connected"
msgstr "Kan ikke sende: Ingen enhed er forbundet"
-#: /home/kovid/work/calibre/src/calibre/gui2/device.py:826
#: /home/kovid/work/calibre/src/calibre/gui2/device.py:830
+#: /home/kovid/work/calibre/src/calibre/gui2/device.py:834
msgid "No card"
msgstr "Intet hukommelseskort"
-#: /home/kovid/work/calibre/src/calibre/gui2/device.py:827
#: /home/kovid/work/calibre/src/calibre/gui2/device.py:831
+#: /home/kovid/work/calibre/src/calibre/gui2/device.py:835
msgid "Cannot send: Device has no storage card"
msgstr "Kan ikke sende: Enheden har intet hukommelseskort"
-#: /home/kovid/work/calibre/src/calibre/gui2/device.py:872
+#: /home/kovid/work/calibre/src/calibre/gui2/device.py:876
msgid "E-book:"
msgstr "E-bog:"
-#: /home/kovid/work/calibre/src/calibre/gui2/device.py:875
+#: /home/kovid/work/calibre/src/calibre/gui2/device.py:879
msgid "Attached, you will find the e-book"
msgstr "Tilknyttet, vil du finde e-bogen"
-#: /home/kovid/work/calibre/src/calibre/gui2/device.py:876
+#: /home/kovid/work/calibre/src/calibre/gui2/device.py:880
#: /home/kovid/work/calibre/src/calibre/gui2/preferences/plugins.py:107
msgid "by"
msgstr "af"
-#: /home/kovid/work/calibre/src/calibre/gui2/device.py:877
+#: /home/kovid/work/calibre/src/calibre/gui2/device.py:881
msgid "in the %s format."
msgstr "i %s formatet."
-#: /home/kovid/work/calibre/src/calibre/gui2/device.py:890
+#: /home/kovid/work/calibre/src/calibre/gui2/device.py:894
msgid "Sending email to"
msgstr "Sender e-mail til"
-#: /home/kovid/work/calibre/src/calibre/gui2/device.py:920
-#: /home/kovid/work/calibre/src/calibre/gui2/device.py:928
-#: /home/kovid/work/calibre/src/calibre/gui2/device.py:1021
-#: /home/kovid/work/calibre/src/calibre/gui2/device.py:1083
-#: /home/kovid/work/calibre/src/calibre/gui2/device.py:1202
-#: /home/kovid/work/calibre/src/calibre/gui2/device.py:1210
+#: /home/kovid/work/calibre/src/calibre/gui2/device.py:924
+#: /home/kovid/work/calibre/src/calibre/gui2/device.py:932
+#: /home/kovid/work/calibre/src/calibre/gui2/device.py:1025
+#: /home/kovid/work/calibre/src/calibre/gui2/device.py:1087
+#: /home/kovid/work/calibre/src/calibre/gui2/device.py:1206
+#: /home/kovid/work/calibre/src/calibre/gui2/device.py:1214
msgid "No suitable formats"
msgstr "Ingen egnede formater"
-#: /home/kovid/work/calibre/src/calibre/gui2/device.py:921
+#: /home/kovid/work/calibre/src/calibre/gui2/device.py:925
msgid "Auto convert the following books before sending via email?"
msgstr "Auto konvertér følgende bøger før sending via e-mail?"
-#: /home/kovid/work/calibre/src/calibre/gui2/device.py:929
+#: /home/kovid/work/calibre/src/calibre/gui2/device.py:933
msgid ""
"Could not email the following books as no suitable formats were found:"
msgstr ""
"Kunne ikke e-maile følgende bøger da ingen egnede formater blev fundet:"
-#: /home/kovid/work/calibre/src/calibre/gui2/device.py:947
+#: /home/kovid/work/calibre/src/calibre/gui2/device.py:951
msgid "Failed to email books"
msgstr "Fejlede med at e-maile bøger"
-#: /home/kovid/work/calibre/src/calibre/gui2/device.py:948
+#: /home/kovid/work/calibre/src/calibre/gui2/device.py:952
msgid "Failed to email the following books:"
msgstr "Fejlede med at e-maile følgende bøger:"
-#: /home/kovid/work/calibre/src/calibre/gui2/device.py:952
+#: /home/kovid/work/calibre/src/calibre/gui2/device.py:956
msgid "Sent by email:"
msgstr "Sendt via e-mail:"
-#: /home/kovid/work/calibre/src/calibre/gui2/device.py:980
+#: /home/kovid/work/calibre/src/calibre/gui2/device.py:984
msgid "News:"
msgstr "Nyheder:"
-#: /home/kovid/work/calibre/src/calibre/gui2/device.py:981
+#: /home/kovid/work/calibre/src/calibre/gui2/device.py:985
msgid "Attached is the"
msgstr "Forbundet er"
-#: /home/kovid/work/calibre/src/calibre/gui2/device.py:992
+#: /home/kovid/work/calibre/src/calibre/gui2/device.py:996
msgid "Sent news to"
msgstr "Send nyheder til"
-#: /home/kovid/work/calibre/src/calibre/gui2/device.py:1022
-#: /home/kovid/work/calibre/src/calibre/gui2/device.py:1084
-#: /home/kovid/work/calibre/src/calibre/gui2/device.py:1203
+#: /home/kovid/work/calibre/src/calibre/gui2/device.py:1026
+#: /home/kovid/work/calibre/src/calibre/gui2/device.py:1088
+#: /home/kovid/work/calibre/src/calibre/gui2/device.py:1207
msgid "Auto convert the following books before uploading to the device?"
msgstr "Auto konvertér følgende bøger før upload til enheden?"
-#: /home/kovid/work/calibre/src/calibre/gui2/device.py:1052
+#: /home/kovid/work/calibre/src/calibre/gui2/device.py:1056
msgid "Sending catalogs to device."
msgstr "Sender kataloger til enheden."
-#: /home/kovid/work/calibre/src/calibre/gui2/device.py:1116
+#: /home/kovid/work/calibre/src/calibre/gui2/device.py:1120
msgid "Sending news to device."
msgstr "Sender nyheder til enheden."
-#: /home/kovid/work/calibre/src/calibre/gui2/device.py:1169
+#: /home/kovid/work/calibre/src/calibre/gui2/device.py:1173
msgid "Sending books to device."
msgstr "Sender bøger til enheden."
-#: /home/kovid/work/calibre/src/calibre/gui2/device.py:1211
+#: /home/kovid/work/calibre/src/calibre/gui2/device.py:1215
msgid ""
"Could not upload the following books to the device, as no suitable formats "
"were found. Convert the book(s) to a format supported by your device first."
@@ -6076,11 +6100,11 @@ msgstr ""
"Kunne ikke uploade følgende bøger til enheden, da ingen egnede formater blev "
"fundet. Konvertér bog/bøgerne til et format understøttet af din enhed først."
-#: /home/kovid/work/calibre/src/calibre/gui2/device.py:1273
+#: /home/kovid/work/calibre/src/calibre/gui2/device.py:1277
msgid "No space on device"
msgstr "Ingen plads på enheden"
-#: /home/kovid/work/calibre/src/calibre/gui2/device.py:1274
+#: /home/kovid/work/calibre/src/calibre/gui2/device.py:1278
msgid ""
"
Cannot upload books to device there is no more free space available "
msgstr ""
@@ -6139,7 +6163,7 @@ msgid "My Books"
msgstr "Mine bøger"
#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/catalog_ui.py:69
-#: /home/kovid/work/calibre/src/calibre/gui2/tools.py:297
+#: /home/kovid/work/calibre/src/calibre/gui2/tools.py:301
msgid "Generate catalog"
msgstr "Generér katalog"
@@ -6479,31 +6503,31 @@ msgstr ""
"Ændringer er permanente. Der er ingen fortryd funktion. Du anbefales at tage "
"en kopi af dit bibliotek før fortsættelse."
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk.py:382
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk.py:386
msgid "Search/replace invalid"
msgstr "Søg/erstat ugyldig"
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk.py:383
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk.py:387
msgid "Search pattern is invalid: %s"
msgstr "Søge mønsteret er ugyldigt: %s"
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk.py:415
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk.py:419
msgid "Applying changes to %d books. This may take a while."
msgstr "Gennemfører ændringer på %d bøger. Dette kan tage et stykke tid."
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:228
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:229
msgid "Edit Meta information"
msgstr "Rediger metaoplysninger"
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:230
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:231
msgid "A&utomatically set author sort"
msgstr "A&utomatisk forfattersortering"
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:231
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:232
msgid "Author s&ort: "
msgstr "Forfatters&ortering: "
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:232
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:233
msgid ""
"Specify how the author(s) of this book should be sorted. For example Charles "
"Dickens should be sorted as Dickens, Charles."
@@ -6511,63 +6535,63 @@ msgstr ""
"Angiv hvordan bogens forfatter(e) skal sorteres. Som eksempel burde Charles "
"Dickens sorteres som Dickens, Charles."
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:233
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single_ui.py:377
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:234
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single_ui.py:383
msgid "&Rating:"
msgstr "&Vurdering:"
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:234
#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:235
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single_ui.py:378
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single_ui.py:379
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:236
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single_ui.py:384
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single_ui.py:385
msgid "Rating of this book. 0-5 stars"
msgstr "Vurdering af bogen. 0-5 stjerner"
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:236
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:237
msgid "No change"
msgstr "Ingen ændring"
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:237
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single_ui.py:380
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:238
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single_ui.py:386
msgid " stars"
msgstr " stjerner"
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:239
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:240
msgid "Add ta&gs: "
msgstr "Tilføj &mærker: "
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:241
#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:242
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single_ui.py:384
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single_ui.py:385
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:243
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single_ui.py:390
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single_ui.py:391
msgid "Open Tag Editor"
msgstr "Åben mærke-editor"
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:243
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:244
msgid "&Remove tags:"
msgstr "&Fjern mærker:"
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:244
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:245
msgid "Comma separated list of tags to remove from the books. "
msgstr "Kommasepareret liste over mærker, der skal fjernes fra bøgerne. "
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:245
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:246
msgid "Check this box to remove all tags from the books."
msgstr "Afkryds denne boks for at fjerne alle mærker fra bøgerne."
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:246
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:247
msgid "Remove all"
msgstr "Fjern alt"
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:250
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:251
msgid "Remove &format:"
msgstr "Fjern &format"
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:251
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:252
msgid "&Swap title and author"
msgstr "&Ombyt titel og forfatter"
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:252
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:253
msgid ""
"Selected books will be automatically numbered,\n"
"in the order you selected them.\n"
@@ -6579,7 +6603,7 @@ msgstr ""
"Så hvis du valgte Book A og derefter Book B,\n"
"Book A vil have serienummer 1 - og Book B serienummer 2."
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:257
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:258
msgid ""
"Remove stored conversion settings for the selected books.\n"
"\n"
@@ -6589,49 +6613,49 @@ msgstr ""
"\n"
"Fremtidige konverteringer af disse bøger vil anvende standardindstillinger."
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:260
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:261
msgid "Remove &stored conversion settings for the selected books"
msgstr "Fjern &gemte konverteringsindstillinger for de valgte bøger"
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:261
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single_ui.py:415
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:262
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single_ui.py:422
msgid "&Basic metadata"
msgstr "&Grund metadata"
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:262
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single_ui.py:416
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:263
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single_ui.py:423
msgid "&Custom metadata"
msgstr "&Tilpas metadata"
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:263
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:264
msgid "Search &field:"
msgstr "Søg &felt:"
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:264
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:265
msgid "&Search for:"
msgstr "&Søg efter:"
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:265
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:266
msgid "&Replace with:"
msgstr "E&rstat med:"
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:266
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:267
msgid "Apply function &after replace:"
msgstr "Anvend funktion &efter erstat:"
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:267
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:268
msgid "Test &text"
msgstr "Test &tekst"
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:268
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:269
msgid "Test re&sult"
msgstr "Test re&sultat"
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:269
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:270
msgid "Your test:"
msgstr "Dit resultat:"
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:270
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:271
msgid "&Search and replace (experimental)"
msgstr "&Søg og erstat (eksperimentiel)"
@@ -6689,7 +6713,7 @@ msgstr "Kunne ikke læse omslaget fra %s-formatet"
msgid "The cover in the %s format is invalid"
msgstr "Omslaget i %s-formatet er ugyldigt"
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single.py:333
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single.py:351
msgid ""
" The green color indicates that the current author sort matches the current "
"author"
@@ -6697,7 +6721,7 @@ msgstr ""
" Den grønne farve indikerer at den aktuelle forfatter-sort, matcher den "
"aktuelle forfatter"
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single.py:336
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single.py:354
msgid ""
" The red color indicates that the current author sort does not match the "
"current author"
@@ -6705,108 +6729,108 @@ msgstr ""
" Den røde farve indikerer at den aktuelle forfatter-sort, ikke matcher den "
"aktuelle forfatter"
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single.py:341
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single.py:359
msgid "Abort the editing of all remaining books"
msgstr "Afbryd redigeringen af alle resterende bøger"
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single.py:505
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single.py:510
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single.py:524
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single.py:529
msgid "This ISBN number is valid"
msgstr "Dette ISBN-nummer er gyldigt"
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single.py:513
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single.py:532
msgid "This ISBN number is invalid"
msgstr "Dette ISBN-nummer er ugyldigt"
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single.py:592
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single.py:611
msgid "Cannot use tag editor"
msgstr "Kan ikke anvende mærke-editor"
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single.py:593
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single.py:612
msgid "The tags editor cannot be used if you have modified the tags"
msgstr "Mærke-editor kan ikke anvendes hvis du har ændret mærkaterne"
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single.py:613
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single.py:632
msgid "Downloading cover..."
msgstr "Henter omslag..."
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single.py:625
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single.py:630
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single.py:636
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single.py:641
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single.py:644
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single.py:649
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single.py:655
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single.py:660
msgid "Cannot fetch cover"
msgstr "Kan ikke hente omslag"
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single.py:626
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single.py:637
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single.py:642
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single.py:645
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single.py:656
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single.py:661
msgid "Could not fetch cover. "
msgstr "Kunne ikke hente omslag "
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single.py:627
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single.py:646
msgid "The download timed out."
msgstr "Download timeout."
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single.py:631
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single.py:650
msgid "Could not find cover for this book. Try specifying the ISBN first."
msgstr "Kunne ikke finde et omslag til denne bog. Prøv at angive ISBN først."
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single.py:643
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single.py:662
msgid ""
"For the error message from each cover source, click Show details below."
msgstr ""
"For hver fejlmeddelse fra hver omslagskilde, tryk på \"Vis detaljer\" "
"herunder."
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single.py:650
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single.py:669
msgid "Bad cover"
msgstr "Dårligt omslag"
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single.py:651
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single.py:670
msgid "The cover is not a valid picture"
msgstr "Omslaget er ikke et gyldigt billede"
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single.py:684
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single.py:703
msgid "There were errors"
msgstr "Der var fejl"
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single.py:685
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single.py:704
msgid "There were errors downloading social metadata"
msgstr "Der var fejl under download af sociale metadata"
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single.py:714
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single.py:733
msgid "Cannot fetch metadata"
msgstr "Kan ikke hente metadata"
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single.py:715
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single.py:734
msgid "You must specify at least one of ISBN, Title, Authors or Publisher"
msgstr "Du skal mindst angive ISBN, titel, forfatter eller udgiver"
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single.py:798
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single.py:820
msgid "Permission denied"
msgstr "Adgang nægtet"
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single.py:799
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single.py:821
msgid "Could not open %s. Is it being used by another program?"
msgstr "Kunne ikke åbne %s. Bliver den anvendt af et andet program?"
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single_ui.py:364
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single_ui.py:370
msgid "Edit Meta Information"
msgstr "Rediger metadata"
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single_ui.py:365
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single_ui.py:371
msgid "Meta information"
msgstr "Metaoplysninger"
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single_ui.py:368
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single_ui.py:374
msgid "Swap the author and title"
msgstr "Ombyt forfatter og titel"
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single_ui.py:371
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single_ui.py:377
msgid "Author S&ort: "
msgstr "Forfatters&ortering "
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single_ui.py:372
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single_ui.py:378
msgid ""
"Specify how the author(s) of this book should be sorted. For example Charles "
"Dickens should be sorted as Dickens, Charles.\n"
@@ -6819,7 +6843,7 @@ msgstr ""
"strenge. Hvis boksen er rødfarvet, så matcher forfatterens og denne tekst "
"ikke."
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single_ui.py:374
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single_ui.py:380
msgid ""
"Automatically create the author sort entry based on the current author "
"entry.\n"
@@ -6831,71 +6855,75 @@ msgstr ""
"Anvend denne knap for at oprette forfatter-sort, vil ændre forfatter-sort "
"fra rød til grøn."
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single_ui.py:389
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single_ui.py:395
msgid "Remove unused series (Series that have no books)"
msgstr "Fjern ubenyttede serier (Serier uden bøger)"
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single_ui.py:391
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single_ui.py:397
msgid "IS&BN:"
msgstr "IS&BN:"
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single_ui.py:392
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single_ui.py:398
msgid "Publishe&d:"
msgstr "Ud&givet:"
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single_ui.py:395
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single_ui.py:401
msgid "dd MMM yyyy"
msgstr "dd MMM yyyy"
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single_ui.py:396
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single_ui.py:402
msgid "&Date:"
msgstr "&Dato:"
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single_ui.py:397
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single_ui.py:403
msgid "&Comments"
msgstr "&Kommentarer"
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single_ui.py:398
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single_ui.py:404
msgid "&Fetch metadata from server"
msgstr "&Hent metadata fra server"
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single_ui.py:399
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single_ui.py:405
msgid "Available Formats"
msgstr "Tilgængelige formater"
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single_ui.py:400
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single_ui.py:406
msgid "Add a new format for this book to the database"
msgstr "Tilføj et nyt format for denne bog til databasen"
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single_ui.py:402
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single_ui.py:408
msgid "Remove the selected formats for this book from the database."
msgstr "Fjern de valgte formater for denne bog fra databasen."
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single_ui.py:404
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single_ui.py:410
msgid "Set the cover for the book from the selected format"
msgstr "Vælg omslaget for denne bog fra det valgte format"
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single_ui.py:406
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single_ui.py:412
msgid "Update metadata from the metadata in the selected format"
msgstr "Opdatér metadata fra metadata i det valgte format"
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single_ui.py:409
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single_ui.py:415
msgid "&Browse"
msgstr "&Gennemse"
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single_ui.py:410
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single_ui.py:416
+msgid "Remove border (if any) from cover"
+msgstr ""
+
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single_ui.py:417
msgid "Reset cover to default"
msgstr "Nulstil til standardomslag"
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single_ui.py:412
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single_ui.py:419
msgid "Download co&ver"
msgstr "Download &omslag"
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single_ui.py:413
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single_ui.py:420
msgid "Generate a default cover based on the title and author"
msgstr "Lav et standard omslag baseret på titlen og forfatteren"
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single_ui.py:414
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single_ui.py:421
msgid "&Generate cover"
msgstr "&Lav omslag"
@@ -7408,6 +7436,39 @@ msgstr "Send test e-mail fra %s til:"
msgid "&Test"
msgstr "&Test"
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/tweak_epub_ui.py:55
+msgid "Display contents of exploded ePub"
+msgstr ""
+
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/tweak_epub_ui.py:56
+msgid "&Explode ePub"
+msgstr ""
+
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/tweak_epub_ui.py:57
+msgid "Rebuild ePub from exploded contents"
+msgstr ""
+
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/tweak_epub_ui.py:58
+msgid "&Rebuild ePub"
+msgstr ""
+
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/tweak_epub_ui.py:59
+msgid "Discard changes"
+msgstr ""
+
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/tweak_epub_ui.py:60
+#: /home/kovid/work/calibre/src/calibre/gui2/preferences/main.py:218
+msgid "&Cancel"
+msgstr "&Annullér"
+
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/tweak_epub_ui.py:61
+msgid ""
+"Explode the ePub to display contents in a file browser window. To tweak "
+"individual files, right-click, then 'Open with...' your editor of choice. "
+"When tweaks are complete, close the file browser window. Rebuild the ePub, "
+"updating your calibre library."
+msgstr ""
+
#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/user_profiles.py:127
msgid "No recipe selected"
msgstr "Ingen opskrift valgt"
@@ -7836,7 +7897,7 @@ msgid "Show books in the main memory of the device"
msgstr "Vis bøger i enhedens arbejdshukommelse"
#: /home/kovid/work/calibre/src/calibre/gui2/layout.py:66
-#: /home/kovid/work/calibre/src/calibre/library/database2.py:656
+#: /home/kovid/work/calibre/src/calibre/library/database2.py:655
msgid "Card A"
msgstr "Kort A"
@@ -7845,7 +7906,7 @@ msgid "Show books in storage card A"
msgstr "Vis bøger i hukommelseskort A"
#: /home/kovid/work/calibre/src/calibre/gui2/layout.py:68
-#: /home/kovid/work/calibre/src/calibre/library/database2.py:658
+#: /home/kovid/work/calibre/src/calibre/library/database2.py:657
msgid "Card B"
msgstr "Kort B"
@@ -7974,7 +8035,7 @@ msgstr "Vis kolonne"
msgid "Restore default layout"
msgstr "Gendan standard layout"
-#: /home/kovid/work/calibre/src/calibre/gui2/library/views.py:555
+#: /home/kovid/work/calibre/src/calibre/gui2/library/views.py:585
msgid ""
"Dropping onto a device is not supported. First add the book to the calibre "
"library."
@@ -8730,10 +8791,6 @@ msgstr "&Del værktøjslinjen i to værktøjslinjer"
msgid "&Apply"
msgstr "&Anvend"
-#: /home/kovid/work/calibre/src/calibre/gui2/preferences/main.py:218
-msgid "&Cancel"
-msgstr "&Annullér"
-
#: /home/kovid/work/calibre/src/calibre/gui2/preferences/main.py:222
msgid "Restore &defaults"
msgstr "Gendan &standardindstillinger"
@@ -9048,7 +9105,7 @@ msgstr ""
">Avanceret->Moduludvidelser"
#: /home/kovid/work/calibre/src/calibre/gui2/preferences/server.py:75
-#: /home/kovid/work/calibre/src/calibre/gui2/ui.py:319
+#: /home/kovid/work/calibre/src/calibre/gui2/ui.py:320
msgid "Failed to start content server"
msgstr "Fejlede med at starte indholdsserveren"
@@ -9471,15 +9528,15 @@ msgstr "Lægger bøger i kø for klyngekonvertering"
msgid "Queueing "
msgstr "Lægger i kø "
-#: /home/kovid/work/calibre/src/calibre/gui2/tools.py:239
+#: /home/kovid/work/calibre/src/calibre/gui2/tools.py:243
msgid "Fetch news from "
msgstr "Henter nyheder fra "
-#: /home/kovid/work/calibre/src/calibre/gui2/tools.py:309
+#: /home/kovid/work/calibre/src/calibre/gui2/tools.py:313
msgid "Convert existing"
msgstr "Konverterer eksisterende"
-#: /home/kovid/work/calibre/src/calibre/gui2/tools.py:310
+#: /home/kovid/work/calibre/src/calibre/gui2/tools.py:314
msgid ""
"The following books have already been converted to %s format. Do you wish to "
"reconvert them?"
@@ -9487,28 +9544,28 @@ msgstr ""
"Følgende bøger er allerede konverteret til %s formatet. Ønsker du at "
"genkonvertere dem?"
-#: /home/kovid/work/calibre/src/calibre/gui2/ui.py:167
+#: /home/kovid/work/calibre/src/calibre/gui2/ui.py:168
msgid "&Restore"
msgstr "&Gendan"
-#: /home/kovid/work/calibre/src/calibre/gui2/ui.py:169
+#: /home/kovid/work/calibre/src/calibre/gui2/ui.py:170
msgid "&Donate to support calibre"
msgstr "&Donér for at støtte calibre"
-#: /home/kovid/work/calibre/src/calibre/gui2/ui.py:173
+#: /home/kovid/work/calibre/src/calibre/gui2/ui.py:174
msgid "&Eject connected device"
msgstr "&Skub forbunden enhed ud"
-#: /home/kovid/work/calibre/src/calibre/gui2/ui.py:215
+#: /home/kovid/work/calibre/src/calibre/gui2/ui.py:216
msgid "Calibre Quick Start Guide"
msgstr "Calibre kvik start guide"
-#: /home/kovid/work/calibre/src/calibre/gui2/ui.py:417
-#: /home/kovid/work/calibre/src/calibre/gui2/ui.py:445
+#: /home/kovid/work/calibre/src/calibre/gui2/ui.py:418
+#: /home/kovid/work/calibre/src/calibre/gui2/ui.py:446
msgid "Conversion Error"
msgstr "Konverteringsfejl"
-#: /home/kovid/work/calibre/src/calibre/gui2/ui.py:418
+#: /home/kovid/work/calibre/src/calibre/gui2/ui.py:419
msgid ""
"
Could not convert: %s
It is a DRMed book. You must "
"first remove the DRM using third party tools."
@@ -9516,15 +9573,15 @@ msgstr ""
"
Kunne ikke konvertere: %s
Det er en e-bog med DRM. "
"Du skal først fjerne DRM med et tredjepartsværktøj."
-#: /home/kovid/work/calibre/src/calibre/gui2/ui.py:431
+#: /home/kovid/work/calibre/src/calibre/gui2/ui.py:432
msgid "Recipe Disabled"
msgstr "Opskrift deaktiveret"
-#: /home/kovid/work/calibre/src/calibre/gui2/ui.py:446
+#: /home/kovid/work/calibre/src/calibre/gui2/ui.py:447
msgid "Failed"
msgstr "Fejlede"
-#: /home/kovid/work/calibre/src/calibre/gui2/ui.py:482
+#: /home/kovid/work/calibre/src/calibre/gui2/ui.py:483
msgid ""
"is the result of the efforts of many volunteers from all over the world. If "
"you find it useful, please consider donating to support its development. "
@@ -9534,11 +9591,11 @@ msgstr ""
"calibre brugbart, venligst overvej at give en donation for at støtte dets "
"udvikling. Din donation hjælper med calibres fortsatte udvikling."
-#: /home/kovid/work/calibre/src/calibre/gui2/ui.py:508
+#: /home/kovid/work/calibre/src/calibre/gui2/ui.py:509
msgid "There are active jobs. Are you sure you want to quit?"
msgstr "Der er aktive opgaver. Er du sikker på du vil afslutte?"
-#: /home/kovid/work/calibre/src/calibre/gui2/ui.py:511
+#: /home/kovid/work/calibre/src/calibre/gui2/ui.py:512
msgid ""
" is communicating with the device! \n"
" Quitting may cause corruption on the device. \n"
@@ -9549,11 +9606,11 @@ msgstr ""
"enheden. \n"
" Er du sikker på at du vil afslutte?"
-#: /home/kovid/work/calibre/src/calibre/gui2/ui.py:515
+#: /home/kovid/work/calibre/src/calibre/gui2/ui.py:516
msgid "WARNING: Active jobs"
msgstr "ADVARSEL: Aktive opgaver"
-#: /home/kovid/work/calibre/src/calibre/gui2/ui.py:583
+#: /home/kovid/work/calibre/src/calibre/gui2/ui.py:584
msgid ""
"will keep running in the system tray. To close it, choose Quit in the "
"context menu of the system tray."
@@ -10386,48 +10443,48 @@ msgstr ""
msgid "Turn on the &content server"
msgstr "Slå &indholdsserveren til"
-#: /home/kovid/work/calibre/src/calibre/library/caches.py:232
+#: /home/kovid/work/calibre/src/calibre/library/caches.py:234
msgid "today"
msgstr "i dag"
-#: /home/kovid/work/calibre/src/calibre/library/caches.py:235
+#: /home/kovid/work/calibre/src/calibre/library/caches.py:237
msgid "yesterday"
msgstr "i går"
-#: /home/kovid/work/calibre/src/calibre/library/caches.py:238
+#: /home/kovid/work/calibre/src/calibre/library/caches.py:240
msgid "thismonth"
msgstr "denne måned"
-#: /home/kovid/work/calibre/src/calibre/library/caches.py:241
-#: /home/kovid/work/calibre/src/calibre/library/caches.py:242
+#: /home/kovid/work/calibre/src/calibre/library/caches.py:243
+#: /home/kovid/work/calibre/src/calibre/library/caches.py:244
msgid "daysago"
msgstr "dage siden"
-#: /home/kovid/work/calibre/src/calibre/library/caches.py:406
-#: /home/kovid/work/calibre/src/calibre/library/caches.py:416
+#: /home/kovid/work/calibre/src/calibre/library/caches.py:408
+#: /home/kovid/work/calibre/src/calibre/library/caches.py:418
msgid "no"
msgstr "nej"
-#: /home/kovid/work/calibre/src/calibre/library/caches.py:406
-#: /home/kovid/work/calibre/src/calibre/library/caches.py:416
+#: /home/kovid/work/calibre/src/calibre/library/caches.py:408
+#: /home/kovid/work/calibre/src/calibre/library/caches.py:418
msgid "unchecked"
msgstr "umarkeret"
-#: /home/kovid/work/calibre/src/calibre/library/caches.py:409
-#: /home/kovid/work/calibre/src/calibre/library/caches.py:419
+#: /home/kovid/work/calibre/src/calibre/library/caches.py:411
+#: /home/kovid/work/calibre/src/calibre/library/caches.py:421
msgid "checked"
msgstr "markeret"
-#: /home/kovid/work/calibre/src/calibre/library/caches.py:409
-#: /home/kovid/work/calibre/src/calibre/library/caches.py:419
+#: /home/kovid/work/calibre/src/calibre/library/caches.py:411
+#: /home/kovid/work/calibre/src/calibre/library/caches.py:421
msgid "yes"
msgstr "ja"
-#: /home/kovid/work/calibre/src/calibre/library/caches.py:413
+#: /home/kovid/work/calibre/src/calibre/library/caches.py:415
msgid "blank"
msgstr "blank"
-#: /home/kovid/work/calibre/src/calibre/library/caches.py:413
+#: /home/kovid/work/calibre/src/calibre/library/caches.py:415
msgid "empty"
msgstr "tom"
@@ -11225,31 +11282,31 @@ msgstr ""
msgid "%sAverage rating is %3.1f"
msgstr "%sMiddel vurderingen er %3.1f"
-#: /home/kovid/work/calibre/src/calibre/library/database2.py:654
+#: /home/kovid/work/calibre/src/calibre/library/database2.py:653
msgid "Main"
msgstr "Main/hjem/primær"
-#: /home/kovid/work/calibre/src/calibre/library/database2.py:1992
+#: /home/kovid/work/calibre/src/calibre/library/database2.py:1998
msgid "
Migrating old database to ebook library in %s
"
msgstr "
Flytter gammel database til e-bogsbibliotek i %s
"
-#: /home/kovid/work/calibre/src/calibre/library/database2.py:2021
+#: /home/kovid/work/calibre/src/calibre/library/database2.py:2027
msgid "Copying %s"
msgstr "Kopierer %s"
-#: /home/kovid/work/calibre/src/calibre/library/database2.py:2038
+#: /home/kovid/work/calibre/src/calibre/library/database2.py:2044
msgid "Compacting database"
msgstr "Komprimerer database"
-#: /home/kovid/work/calibre/src/calibre/library/database2.py:2131
+#: /home/kovid/work/calibre/src/calibre/library/database2.py:2137
msgid "Checking SQL integrity..."
msgstr "Checker SQL integritet..."
-#: /home/kovid/work/calibre/src/calibre/library/database2.py:2170
+#: /home/kovid/work/calibre/src/calibre/library/database2.py:2176
msgid "Checking for missing files."
msgstr "Checker for manglende filer."
-#: /home/kovid/work/calibre/src/calibre/library/database2.py:2192
+#: /home/kovid/work/calibre/src/calibre/library/database2.py:2198
msgid "Checked id"
msgstr "Checket id"
@@ -11764,59 +11821,59 @@ msgstr ""
"Download ikke den sidste version af de indbyggede opskrifter fra calibre-"
"serveren"
-#: /home/kovid/work/calibre/src/calibre/web/feeds/news.py:46
+#: /home/kovid/work/calibre/src/calibre/web/feeds/news.py:47
msgid "Unknown News Source"
msgstr "Ukendt nyhedskilde"
-#: /home/kovid/work/calibre/src/calibre/web/feeds/news.py:611
+#: /home/kovid/work/calibre/src/calibre/web/feeds/news.py:612
msgid "The \"%s\" recipe needs a username and password."
msgstr "\"%s\"-opskriften kræver et brugernavn og adgangskode."
-#: /home/kovid/work/calibre/src/calibre/web/feeds/news.py:710
+#: /home/kovid/work/calibre/src/calibre/web/feeds/news.py:711
msgid "Download finished"
msgstr "Download afsluttet"
-#: /home/kovid/work/calibre/src/calibre/web/feeds/news.py:712
+#: /home/kovid/work/calibre/src/calibre/web/feeds/news.py:713
msgid "Failed to download the following articles:"
msgstr "Kunne ikke downloade følgende artikler:"
-#: /home/kovid/work/calibre/src/calibre/web/feeds/news.py:718
+#: /home/kovid/work/calibre/src/calibre/web/feeds/news.py:719
msgid "Failed to download parts of the following articles:"
msgstr "Kunne ikke downloade dele af følgende artikler:"
-#: /home/kovid/work/calibre/src/calibre/web/feeds/news.py:720
+#: /home/kovid/work/calibre/src/calibre/web/feeds/news.py:721
msgid " from "
msgstr " fra "
-#: /home/kovid/work/calibre/src/calibre/web/feeds/news.py:722
+#: /home/kovid/work/calibre/src/calibre/web/feeds/news.py:723
msgid "\tFailed links:"
msgstr "\tMislykkede henvisninger:"
-#: /home/kovid/work/calibre/src/calibre/web/feeds/news.py:811
+#: /home/kovid/work/calibre/src/calibre/web/feeds/news.py:812
msgid "Could not fetch article. Run with -vv to see the reason"
msgstr "Kunne ikke hente artikler. Kør med -vv for at se årsagen"
-#: /home/kovid/work/calibre/src/calibre/web/feeds/news.py:832
+#: /home/kovid/work/calibre/src/calibre/web/feeds/news.py:833
msgid "Fetching feeds..."
msgstr "Henter feeds..."
-#: /home/kovid/work/calibre/src/calibre/web/feeds/news.py:837
+#: /home/kovid/work/calibre/src/calibre/web/feeds/news.py:838
msgid "Got feeds from index page"
msgstr "Fik feeds fra indekssiden"
-#: /home/kovid/work/calibre/src/calibre/web/feeds/news.py:843
+#: /home/kovid/work/calibre/src/calibre/web/feeds/news.py:844
msgid "Trying to download cover..."
msgstr "Prøver at downloade omslag..."
-#: /home/kovid/work/calibre/src/calibre/web/feeds/news.py:845
+#: /home/kovid/work/calibre/src/calibre/web/feeds/news.py:846
msgid "Generating masthead..."
msgstr "Genererer masthead..."
-#: /home/kovid/work/calibre/src/calibre/web/feeds/news.py:926
+#: /home/kovid/work/calibre/src/calibre/web/feeds/news.py:927
msgid "Starting download [%d thread(s)]..."
msgstr "Starter download [%d tråd(e)]..."
-#: /home/kovid/work/calibre/src/calibre/web/feeds/news.py:942
+#: /home/kovid/work/calibre/src/calibre/web/feeds/news.py:943
msgid "Feeds downloaded to %s"
msgstr "Feeds er hentet til %s"
@@ -11824,31 +11881,31 @@ msgstr "Feeds er hentet til %s"
msgid "Could not download cover: %s"
msgstr "Kunne ikke hente omslaget: %s"
-#: /home/kovid/work/calibre/src/calibre/web/feeds/news.py:964
+#: /home/kovid/work/calibre/src/calibre/web/feeds/news.py:959
msgid "Downloading cover from %s"
msgstr "Downloader omslag fra %s"
-#: /home/kovid/work/calibre/src/calibre/web/feeds/news.py:1005
+#: /home/kovid/work/calibre/src/calibre/web/feeds/news.py:1004
msgid "Masthead image downloaded"
msgstr "Masthead billede downloadet"
-#: /home/kovid/work/calibre/src/calibre/web/feeds/news.py:1173
+#: /home/kovid/work/calibre/src/calibre/web/feeds/news.py:1172
msgid "Untitled Article"
msgstr "Unavngiven artikel"
-#: /home/kovid/work/calibre/src/calibre/web/feeds/news.py:1244
+#: /home/kovid/work/calibre/src/calibre/web/feeds/news.py:1243
msgid "Article downloaded: %s"
msgstr "Artikel hentet: %s"
-#: /home/kovid/work/calibre/src/calibre/web/feeds/news.py:1255
+#: /home/kovid/work/calibre/src/calibre/web/feeds/news.py:1254
msgid "Article download failed: %s"
msgstr "Hentning af artikel mislykkedes: %s"
-#: /home/kovid/work/calibre/src/calibre/web/feeds/news.py:1272
+#: /home/kovid/work/calibre/src/calibre/web/feeds/news.py:1271
msgid "Fetching feed"
msgstr "Henter feed"
-#: /home/kovid/work/calibre/src/calibre/web/feeds/news.py:1419
+#: /home/kovid/work/calibre/src/calibre/web/feeds/news.py:1418
msgid ""
"Failed to log in, check your username and password for the calibre "
"Periodicals service."
@@ -11856,7 +11913,7 @@ msgstr ""
"Login mislykkedes, check dit brugernavn og adgangskode til calibre "
"tidsskriftsservice."
-#: /home/kovid/work/calibre/src/calibre/web/feeds/news.py:1435
+#: /home/kovid/work/calibre/src/calibre/web/feeds/news.py:1433
msgid ""
"You do not have permission to download this issue. Either your subscription "
"has expired or you have exceeded the maximum allowed downloads for today."
diff --git a/src/calibre/translations/eu.po b/src/calibre/translations/eu.po
index 8a78bae393..88094f0fb2 100644
--- a/src/calibre/translations/eu.po
+++ b/src/calibre/translations/eu.po
@@ -7,14 +7,14 @@ msgid ""
msgstr ""
"Project-Id-Version: calibre\n"
"Report-Msgid-Bugs-To: FULL NAME \n"
-"POT-Creation-Date: 2010-09-17 21:00+0000\n"
-"PO-Revision-Date: 2010-09-20 11:11+0000\n"
+"POT-Creation-Date: 2010-09-24 21:33+0000\n"
+"PO-Revision-Date: 2010-09-24 20:42+0000\n"
"Last-Translator: gorkaazk \n"
"Language-Team: Basque \n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
-"X-Launchpad-Export-Date: 2010-09-21 04:57+0000\n"
+"X-Launchpad-Export-Date: 2010-09-25 04:39+0000\n"
"X-Generator: Launchpad (build Unknown)\n"
#: /home/kovid/work/calibre/src/calibre/customize/__init__.py:43
@@ -24,7 +24,8 @@ msgstr "Ez du ezer egiten"
#: /home/kovid/work/calibre/src/calibre/customize/__init__.py:46
#: /home/kovid/work/calibre/src/calibre/devices/jetbook/driver.py:74
#: /home/kovid/work/calibre/src/calibre/devices/kindle/driver.py:76
-#: /home/kovid/work/calibre/src/calibre/devices/kobo/driver.py:410
+#: /home/kovid/work/calibre/src/calibre/devices/kobo/books.py:46
+#: /home/kovid/work/calibre/src/calibre/devices/kobo/driver.py:412
#: /home/kovid/work/calibre/src/calibre/devices/nook/driver.py:70
#: /home/kovid/work/calibre/src/calibre/devices/nook/driver.py:71
#: /home/kovid/work/calibre/src/calibre/devices/prs500/books.py:267
@@ -105,24 +106,24 @@ msgstr "Ez du ezer egiten"
#: /home/kovid/work/calibre/src/calibre/ebooks/pdf/writer.py:98
#: /home/kovid/work/calibre/src/calibre/ebooks/rtf/input.py:239
#: /home/kovid/work/calibre/src/calibre/ebooks/rtf/input.py:241
-#: /home/kovid/work/calibre/src/calibre/gui2/__init__.py:324
-#: /home/kovid/work/calibre/src/calibre/gui2/__init__.py:331
-#: /home/kovid/work/calibre/src/calibre/gui2/actions/edit_metadata.py:293
+#: /home/kovid/work/calibre/src/calibre/gui2/__init__.py:352
+#: /home/kovid/work/calibre/src/calibre/gui2/__init__.py:359
#: /home/kovid/work/calibre/src/calibre/gui2/actions/edit_metadata.py:296
+#: /home/kovid/work/calibre/src/calibre/gui2/actions/edit_metadata.py:299
#: /home/kovid/work/calibre/src/calibre/gui2/add.py:137
#: /home/kovid/work/calibre/src/calibre/gui2/add.py:144
#: /home/kovid/work/calibre/src/calibre/gui2/convert/__init__.py:42
#: /home/kovid/work/calibre/src/calibre/gui2/convert/metadata.py:111
#: /home/kovid/work/calibre/src/calibre/gui2/convert/metadata.py:136
#: /home/kovid/work/calibre/src/calibre/gui2/convert/metadata.py:138
-#: /home/kovid/work/calibre/src/calibre/gui2/device.py:865
-#: /home/kovid/work/calibre/src/calibre/gui2/device.py:874
-#: /home/kovid/work/calibre/src/calibre/gui2/device.py:1158
-#: /home/kovid/work/calibre/src/calibre/gui2/device.py:1161
+#: /home/kovid/work/calibre/src/calibre/gui2/device.py:869
+#: /home/kovid/work/calibre/src/calibre/gui2/device.py:878
+#: /home/kovid/work/calibre/src/calibre/gui2/device.py:1162
+#: /home/kovid/work/calibre/src/calibre/gui2/device.py:1165
#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/comicconf.py:47
#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/fetch_metadata.py:120
#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/fetch_metadata.py:155
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single.py:552
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single.py:571
#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/scheduler.py:173
#: /home/kovid/work/calibre/src/calibre/gui2/library/models.py:357
#: /home/kovid/work/calibre/src/calibre/gui2/library/models.py:377
@@ -135,11 +136,11 @@ msgstr "Ez du ezer egiten"
#: /home/kovid/work/calibre/src/calibre/library/database.py:913
#: /home/kovid/work/calibre/src/calibre/library/database2.py:375
#: /home/kovid/work/calibre/src/calibre/library/database2.py:387
-#: /home/kovid/work/calibre/src/calibre/library/database2.py:1065
-#: /home/kovid/work/calibre/src/calibre/library/database2.py:1137
-#: /home/kovid/work/calibre/src/calibre/library/database2.py:1837
-#: /home/kovid/work/calibre/src/calibre/library/database2.py:1839
-#: /home/kovid/work/calibre/src/calibre/library/database2.py:1966
+#: /home/kovid/work/calibre/src/calibre/library/database2.py:1064
+#: /home/kovid/work/calibre/src/calibre/library/database2.py:1139
+#: /home/kovid/work/calibre/src/calibre/library/database2.py:1843
+#: /home/kovid/work/calibre/src/calibre/library/database2.py:1845
+#: /home/kovid/work/calibre/src/calibre/library/database2.py:1972
#: /home/kovid/work/calibre/src/calibre/library/server/mobile.py:211
#: /home/kovid/work/calibre/src/calibre/library/server/opds.py:137
#: /home/kovid/work/calibre/src/calibre/library/server/opds.py:140
@@ -261,43 +262,43 @@ msgstr "Ezarri metadatuak %s fitxategietan"
msgid "Set metadata from %s files"
msgstr "Ezarri metadatuak %s fitxategietatik"
-#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:684
+#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:688
msgid "Look and Feel"
msgstr "Itxura eta izaera"
-#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:686
-#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:698
-#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:709
-#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:720
+#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:690
+#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:702
+#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:713
+#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:724
msgid "Interface"
msgstr "Interfazea"
-#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:690
+#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:694
msgid "Adjust the look and feel of the calibre interface to suit your tastes"
msgstr "Doi ezazu calibreren interfazearen itxura zure gustuen arabera"
-#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:696
+#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:700
msgid "Behavior"
msgstr "Jokabidea"
-#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:702
+#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:706
msgid "Change the way calibre behaves"
msgstr "Calibre-ren jokatzeko era aldatu"
-#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:707
+#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:711
#: /home/kovid/work/calibre/src/calibre/gui2/library/views.py:176
msgid "Add your own columns"
msgstr "Gehi itzazu zureak diren zutabeak"
-#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:713
+#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:717
msgid "Add/remove your own columns to the calibre book list"
msgstr "Calibre-ren liburu zerrendan zuk egindako zutabeak gehitu/ezabatu"
-#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:718
+#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:722
msgid "Customize the toolbar"
msgstr "Tresna-barra pertsonalizatu"
-#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:724
+#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:728
msgid ""
"Customize the toolbars and context menus, changing which actions are "
"available in each"
@@ -305,57 +306,57 @@ msgstr ""
"Tresna-barra eta kontextuaren araberako menuak pertsonalizatu, bakoitzean "
"aukeragarri agertuko diren ekintzak aldatuz."
-#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:730
+#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:734
msgid "Input Options"
msgstr "Sorburu aukerak"
-#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:732
-#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:743
-#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:754
+#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:736
+#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:747
+#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:758
msgid "Conversion"
msgstr "Bihurketa"
-#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:736
+#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:740
msgid "Set conversion options specific to each input format"
msgstr "Ezarri itzazu sorburu formatu bakoitzeko bihurketa aukera zehatzak"
-#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:741
+#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:745
msgid "Common Options"
msgstr "Aukera komunak"
-#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:747
+#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:751
msgid "Set conversion options common to all formats"
msgstr "Formatu guztientzako eraldatze aukera komunak ezartzea"
-#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:752
+#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:756
msgid "Output Options"
msgstr "Helburu aukerak"
-#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:758
+#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:762
msgid "Set conversion options specific to each output format"
msgstr "Ezarri itzazu helburu formatu bakoitzeko bihurketa aukera zehatzak"
-#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:763
+#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:767
msgid "Adding books"
msgstr "Liburuak gehitzen"
-#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:765
-#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:777
-#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:789
+#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:769
+#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:781
+#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:793
msgid "Import/Export"
msgstr "Inportatu/Esportatu"
-#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:769
+#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:773
msgid "Control how calibre reads metadata from files when adding books"
msgstr ""
"Liburuak gehitzean, calibre-k fitxategietako metadatuak zelan irakurriko "
"dituen kontrolatzea"
-#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:775
+#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:779
msgid "Saving books to disk"
msgstr "Liburuak diskan gordetzen"
-#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:781
+#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:785
msgid ""
"Control how calibre exports files from its database to disk when using Save "
"to disk"
@@ -363,26 +364,26 @@ msgstr ""
"Kontrola ezazu calibrek nola esportatzen dituen fitxategiak bere datu "
"basetik diskora \"Diskoan gorde\" aukera erabiltzen denean."
-#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:787
+#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:791
msgid "Sending books to devices"
msgstr "Liburuak gailuetara bidaltzea"
-#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:793
+#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:797
msgid "Control how calibre transfers files to your ebook reader"
msgstr ""
"calibre-k zure liburu elektronikoen irakurgailura fitxategiak zelan igaroko "
"dituen kontrolatzen du"
-#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:799
+#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:803
msgid "Sharing books by email"
msgstr "elektropostaz liburuak elkarbanatzea"
-#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:801
-#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:813
+#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:805
+#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:817
msgid "Sharing"
msgstr "Elkarbanatzea"
-#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:805
+#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:809
msgid ""
"Setup sharing of books via email. Can be used for automatic sending of "
"downloaded news to your devices"
@@ -390,11 +391,11 @@ msgstr ""
"Elektropostaz liburuak elkarbanatzea arautzen du. Saretik jaitsitako "
"albisteak norbere gailuetara automatikoki bidaltzeko erabil daiteke"
-#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:811
+#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:815
msgid "Sharing over the net"
msgstr "Sarean zehar elkarbanatu"
-#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:817
+#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:821
msgid ""
"Setup the calibre Content Server which will give you access to your calibre "
"library from anywhere, on any device, over the internet"
@@ -403,35 +404,35 @@ msgstr ""
"interneten bidezko sarbidea emango dizun edozein lekutan eta edozein "
"gailuren bidez"
-#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:824
+#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:828
msgid "Plugins"
msgstr "Gehigarriak"
-#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:826
-#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:838
-#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:849
+#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:830
+#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:842
+#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:853
msgid "Advanced"
msgstr "Aurreratua"
-#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:830
+#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:834
msgid "Add/remove/customize various bits of calibre functionality"
msgstr "Gehitu/ezabatu/pertsonalizatu calibreren zenbait funtzio"
-#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:836
+#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:840
msgid "Tweaks"
msgstr "Doikuntzak"
-#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:842
+#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:846
msgid "Fine tune how calibre behaves in various contexts"
msgstr ""
"Afina ezazu zehaztasun handiz nola jokatuko duen calibrek hainbat "
"testuingurutan"
-#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:847
+#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:851
msgid "Miscellaneous"
msgstr "Denetarik"
-#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:853
+#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:857
msgid "Miscellaneous advanced configuration"
msgstr "Hainbat gauzetarako ezarpen aurreratuak"
@@ -614,16 +615,16 @@ msgstr "Desgaitutako gehigarriak"
msgid "Enabled plugins"
msgstr "Gehigarri gaituak"
-#: /home/kovid/work/calibre/src/calibre/customize/ui.py:86
+#: /home/kovid/work/calibre/src/calibre/customize/ui.py:93
msgid "No valid plugin found in "
msgstr "Baliogabeko gehigarria aurkitu da hemen: "
-#: /home/kovid/work/calibre/src/calibre/customize/ui.py:501
+#: /home/kovid/work/calibre/src/calibre/customize/ui.py:508
msgid "Initialization of plugin %s failed with traceback:"
msgstr ""
"%s gehigarriaren abiarazteak huts egin du eta ondoko aztarna utzi du:"
-#: /home/kovid/work/calibre/src/calibre/customize/ui.py:534
+#: /home/kovid/work/calibre/src/calibre/customize/ui.py:541
msgid ""
" %prog options\n"
"\n"
@@ -635,18 +636,18 @@ msgstr ""
" Pertsonalizatu calibre kanpoko gehigarriak kargatuz.\n"
" "
-#: /home/kovid/work/calibre/src/calibre/customize/ui.py:540
+#: /home/kovid/work/calibre/src/calibre/customize/ui.py:547
msgid "Add a plugin by specifying the path to the zip file containing it."
msgstr ""
"Gehigarria gehitu bere barnean duen ZIP fitxategiaren bidea adieraziz."
-#: /home/kovid/work/calibre/src/calibre/customize/ui.py:542
+#: /home/kovid/work/calibre/src/calibre/customize/ui.py:549
msgid "Remove a custom plugin by name. Has no effect on builtin plugins"
msgstr ""
"Pertsonalizatutako gehigarria izenez kendu. Ez du efekturik izango "
"\"builtin\" gehigarrietan, \"Nola eraiki zen\" gehigarrietan."
-#: /home/kovid/work/calibre/src/calibre/customize/ui.py:544
+#: /home/kovid/work/calibre/src/calibre/customize/ui.py:551
msgid ""
"Customize plugin. Specify name of plugin and customization string separated "
"by a comma."
@@ -654,15 +655,15 @@ msgstr ""
"Pertsonalizatu gehigarria. Adierazi gehigarriaren izena eta "
"pertsonalizaturiko katearena komaren bidez bereizirik."
-#: /home/kovid/work/calibre/src/calibre/customize/ui.py:546
+#: /home/kovid/work/calibre/src/calibre/customize/ui.py:553
msgid "List all installed plugins"
msgstr "Zerrendatu instalatutako gehigarri guztiak"
-#: /home/kovid/work/calibre/src/calibre/customize/ui.py:548
+#: /home/kovid/work/calibre/src/calibre/customize/ui.py:555
msgid "Enable the named plugin"
msgstr "Gaitu izendaturiko gehigarria."
-#: /home/kovid/work/calibre/src/calibre/customize/ui.py:550
+#: /home/kovid/work/calibre/src/calibre/customize/ui.py:557
msgid "Disable the named plugin"
msgstr "Desgaitu izendaturiko gehigarria."
@@ -670,7 +671,7 @@ msgstr "Desgaitu izendaturiko gehigarria."
msgid "Communicate with Android phones."
msgstr "Adroid telefonoekin komunikatu."
-#: /home/kovid/work/calibre/src/calibre/devices/android/driver.py:50
+#: /home/kovid/work/calibre/src/calibre/devices/android/driver.py:52
msgid ""
"Comma separated list of directories to send e-books to on the device. The "
"first one that exists will be used"
@@ -678,7 +679,7 @@ msgstr ""
"Gailuan dagoen komen bitartez bereizitako direktorioen zerrenda, liburu "
"elektronikoak hara igortzeko. Existitzen den lehena erabiliko da."
-#: /home/kovid/work/calibre/src/calibre/devices/android/driver.py:92
+#: /home/kovid/work/calibre/src/calibre/devices/android/driver.py:94
msgid "Communicate with S60 phones."
msgstr "S60 telefonoekin komunikatu."
@@ -751,7 +752,7 @@ msgstr ""
#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/scheduler.py:244
#: /home/kovid/work/calibre/src/calibre/library/database2.py:198
#: /home/kovid/work/calibre/src/calibre/library/database2.py:211
-#: /home/kovid/work/calibre/src/calibre/library/database2.py:1706
+#: /home/kovid/work/calibre/src/calibre/library/database2.py:1712
#: /home/kovid/work/calibre/src/calibre/library/field_metadata.py:134
msgid "News"
msgstr "Albisteak"
@@ -759,8 +760,8 @@ msgstr "Albisteak"
#: /home/kovid/work/calibre/src/calibre/devices/apple/driver.py:2500
#: /home/kovid/work/calibre/src/calibre/gui2/catalog/catalog_epub_mobi.py:20
#: /home/kovid/work/calibre/src/calibre/library/catalog.py:556
-#: /home/kovid/work/calibre/src/calibre/library/database2.py:1669
-#: /home/kovid/work/calibre/src/calibre/library/database2.py:1687
+#: /home/kovid/work/calibre/src/calibre/library/database2.py:1675
+#: /home/kovid/work/calibre/src/calibre/library/database2.py:1693
msgid "Catalog"
msgstr "Katalogoa"
@@ -845,23 +846,23 @@ msgstr ""
"Gailuan dagoen komen bitartez bereizitako direktorioen zerrenda, liburu "
"elektronikoak hara igortzeko. Existitzen den lehena erabiliko da."
-#: /home/kovid/work/calibre/src/calibre/devices/hanvon/driver.py:18
+#: /home/kovid/work/calibre/src/calibre/devices/hanvon/driver.py:22
msgid "Communicate with the Hanvon N520 eBook reader."
msgstr "Jar zaitez harremanetan \"Hanvon N520 eBook reader\" horrekin."
-#: /home/kovid/work/calibre/src/calibre/devices/hanvon/driver.py:40
+#: /home/kovid/work/calibre/src/calibre/devices/hanvon/driver.py:47
msgid "Communicate with The Book reader."
msgstr "Jar zaitez harremanetan \"The Book reader\" horrekin."
-#: /home/kovid/work/calibre/src/calibre/devices/hanvon/driver.py:52
+#: /home/kovid/work/calibre/src/calibre/devices/hanvon/driver.py:59
msgid "Communicate with the SpringDesign Alex eBook reader."
msgstr "Jar zaitez harremanetan \"SpringDesign Alex eBook reader\" horrekin."
-#: /home/kovid/work/calibre/src/calibre/devices/hanvon/driver.py:68
+#: /home/kovid/work/calibre/src/calibre/devices/hanvon/driver.py:78
msgid "Communicate with the Azbooka"
msgstr "Jar zaitez harremanetan \"Azabooka\" horrekin."
-#: /home/kovid/work/calibre/src/calibre/devices/hanvon/driver.py:81
+#: /home/kovid/work/calibre/src/calibre/devices/hanvon/driver.py:94
msgid "Communicate with the Elonex EB 511 eBook reader."
msgstr "Jar zaitez harremanetan \"Elonex EB 511 eBook reader\" horrekin."
@@ -922,7 +923,7 @@ msgstr ""
#: /home/kovid/work/calibre/src/calibre/devices/kobo/driver.py:63
#: /home/kovid/work/calibre/src/calibre/devices/kobo/driver.py:66
#: /home/kovid/work/calibre/src/calibre/devices/kobo/driver.py:69
-#: /home/kovid/work/calibre/src/calibre/devices/kobo/driver.py:186
+#: /home/kovid/work/calibre/src/calibre/devices/kobo/driver.py:188
#: /home/kovid/work/calibre/src/calibre/devices/usbms/driver.py:68
#: /home/kovid/work/calibre/src/calibre/devices/usbms/driver.py:71
#: /home/kovid/work/calibre/src/calibre/devices/usbms/driver.py:74
@@ -932,33 +933,33 @@ msgstr ""
msgid "Getting list of books on device..."
msgstr "Liburu zerrenda gailutik eskuratzen..."
-#: /home/kovid/work/calibre/src/calibre/devices/kobo/driver.py:246
-#: /home/kovid/work/calibre/src/calibre/devices/kobo/driver.py:278
+#: /home/kovid/work/calibre/src/calibre/devices/kobo/driver.py:248
+#: /home/kovid/work/calibre/src/calibre/devices/kobo/driver.py:280
#: /home/kovid/work/calibre/src/calibre/devices/usbms/driver.py:253
#: /home/kovid/work/calibre/src/calibre/devices/usbms/driver.py:271
msgid "Removing books from device..."
msgstr "Gailutik liburuak ezabatzen..."
-#: /home/kovid/work/calibre/src/calibre/devices/kobo/driver.py:282
-#: /home/kovid/work/calibre/src/calibre/devices/kobo/driver.py:289
+#: /home/kovid/work/calibre/src/calibre/devices/kobo/driver.py:284
+#: /home/kovid/work/calibre/src/calibre/devices/kobo/driver.py:291
#: /home/kovid/work/calibre/src/calibre/devices/usbms/driver.py:278
#: /home/kovid/work/calibre/src/calibre/devices/usbms/driver.py:283
msgid "Removing books from device metadata listing..."
msgstr "Gailuaren metadatu zerrendatik liburuak kentzen..."
-#: /home/kovid/work/calibre/src/calibre/devices/kobo/driver.py:294
-#: /home/kovid/work/calibre/src/calibre/devices/kobo/driver.py:328
+#: /home/kovid/work/calibre/src/calibre/devices/kobo/driver.py:296
+#: /home/kovid/work/calibre/src/calibre/devices/kobo/driver.py:330
#: /home/kovid/work/calibre/src/calibre/devices/usbms/driver.py:217
#: /home/kovid/work/calibre/src/calibre/devices/usbms/driver.py:247
msgid "Adding books to device metadata listing..."
msgstr "Gailuaren metadatu zerrendara liburuak gehitzen..."
-#: /home/kovid/work/calibre/src/calibre/devices/kobo/driver.py:390
+#: /home/kovid/work/calibre/src/calibre/devices/kobo/driver.py:392
#: /home/kovid/work/calibre/src/calibre/gui2/actions/add.py:252
msgid "Not Implemented"
msgstr "Abiarazi gabea, inplementatu gabea"
-#: /home/kovid/work/calibre/src/calibre/devices/kobo/driver.py:391
+#: /home/kovid/work/calibre/src/calibre/devices/kobo/driver.py:393
msgid ""
"\".kobo\" files do not exist on the device as books instead, they are rows "
"in the sqlite database. Currently they cannot be exported or viewed."
@@ -3545,7 +3546,7 @@ msgstr "Kopiatu"
msgid "Copy to Clipboard"
msgstr "Kopiatu arbelean"
-#: /home/kovid/work/calibre/src/calibre/gui2/__init__.py:434
+#: /home/kovid/work/calibre/src/calibre/gui2/__init__.py:462
msgid "Choose Files"
msgstr "Aukeratu fitxategiak"
@@ -3680,6 +3681,7 @@ msgstr "Gehitu liburutegira"
#: /home/kovid/work/calibre/src/calibre/gui2/actions/add.py:240
#: /home/kovid/work/calibre/src/calibre/gui2/actions/delete.py:56
+#: /home/kovid/work/calibre/src/calibre/gui2/actions/tweak_epub.py:28
#: /home/kovid/work/calibre/src/calibre/gui2/actions/view.py:95
#: /home/kovid/work/calibre/src/calibre/gui2/actions/view.py:120
msgid "No book selected"
@@ -3724,8 +3726,8 @@ msgstr "Liburutegi nagusian erabiltzaileak sortu dituen oharrak bakarrik"
#: /home/kovid/work/calibre/src/calibre/gui2/actions/copy_to_library.py:116
#: /home/kovid/work/calibre/src/calibre/gui2/actions/edit_metadata.py:76
#: /home/kovid/work/calibre/src/calibre/gui2/actions/edit_metadata.py:142
-#: /home/kovid/work/calibre/src/calibre/gui2/actions/edit_metadata.py:178
-#: /home/kovid/work/calibre/src/calibre/gui2/actions/edit_metadata.py:205
+#: /home/kovid/work/calibre/src/calibre/gui2/actions/edit_metadata.py:180
+#: /home/kovid/work/calibre/src/calibre/gui2/actions/edit_metadata.py:208
#: /home/kovid/work/calibre/src/calibre/gui2/actions/save_to_disk.py:92
msgid "No books selected"
msgstr "Libururik ez da hautatu"
@@ -3883,7 +3885,7 @@ msgstr ""
#: /home/kovid/work/calibre/src/calibre/gui2/actions/choose_library.py:249
#: /home/kovid/work/calibre/src/calibre/gui2/actions/choose_library.py:254
#: /home/kovid/work/calibre/src/calibre/gui2/actions/save_to_disk.py:101
-#: /home/kovid/work/calibre/src/calibre/gui2/library/views.py:554
+#: /home/kovid/work/calibre/src/calibre/gui2/library/views.py:584
msgid "Not allowed"
msgstr "Not allowed"
@@ -3952,7 +3954,7 @@ msgstr "Ezin izan dira liburuak kopiatu: "
#: /home/kovid/work/calibre/src/calibre/gui2/actions/copy_to_library.py:138
#: /home/kovid/work/calibre/src/calibre/gui2/device.py:670
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk.py:424
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk.py:428
msgid "Failed"
msgstr "Huts egin du"
@@ -4193,20 +4195,20 @@ msgid "Error"
msgstr "Errorea"
#: /home/kovid/work/calibre/src/calibre/gui2/actions/edit_metadata.py:141
-#: /home/kovid/work/calibre/src/calibre/gui2/actions/edit_metadata.py:177
+#: /home/kovid/work/calibre/src/calibre/gui2/actions/edit_metadata.py:179
msgid "Cannot edit metadata"
msgstr "Ezin izan dira metadatuak editatu"
-#: /home/kovid/work/calibre/src/calibre/gui2/actions/edit_metadata.py:204
#: /home/kovid/work/calibre/src/calibre/gui2/actions/edit_metadata.py:207
+#: /home/kovid/work/calibre/src/calibre/gui2/actions/edit_metadata.py:210
msgid "Cannot merge books"
msgstr "Ezin izan dira liburuak bateratu"
-#: /home/kovid/work/calibre/src/calibre/gui2/actions/edit_metadata.py:208
+#: /home/kovid/work/calibre/src/calibre/gui2/actions/edit_metadata.py:211
msgid "At least two books must be selected for merging"
msgstr "Gutxienez bi liburu hautatu beharko dira haiekin bakarra egiteko"
-#: /home/kovid/work/calibre/src/calibre/gui2/actions/edit_metadata.py:212
+#: /home/kovid/work/calibre/src/calibre/gui2/actions/edit_metadata.py:215
msgid ""
"Book formats and metadata from the selected books will be added to the "
"first selected book. ISBN will not be merged.
The "
@@ -4218,7 +4220,7 @@ msgstr ""
"Hautatutako bigarren liburua eta hurrengoak ez dira ez ezabatuko ezta "
"aldatuko ere.
Mesedez, baiezta ezazu aurrera egin nahi duzula."
-#: /home/kovid/work/calibre/src/calibre/gui2/actions/edit_metadata.py:224
+#: /home/kovid/work/calibre/src/calibre/gui2/actions/edit_metadata.py:227
msgid ""
"Book formats and metadata from the selected books will be merged into the "
"first selected book. ISBN will not be merged.
After "
@@ -4236,7 +4238,7 @@ msgstr ""
"bigarren liburuan eta hurrengoetan betiko ezabatu egingo dira zure "
"ordenagailutik.
Ziur zaude? Benetan aurrera egin nahi duzu?"
-#: /home/kovid/work/calibre/src/calibre/gui2/actions/edit_metadata.py:237
+#: /home/kovid/work/calibre/src/calibre/gui2/actions/edit_metadata.py:240
msgid ""
"You are about to merge more than 5 books. Are you sure you want to "
"proceed?"
@@ -4424,6 +4426,28 @@ msgstr "Alt+T"
msgid "Books with the same tags"
msgstr "Books with the same tags"
+#: /home/kovid/work/calibre/src/calibre/gui2/actions/tweak_epub.py:15
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/tweak_epub_ui.py:54
+msgid "Tweak ePub"
+msgstr ""
+
+#: /home/kovid/work/calibre/src/calibre/gui2/actions/tweak_epub.py:16
+msgid "Make small changes to ePub format books"
+msgstr ""
+
+#: /home/kovid/work/calibre/src/calibre/gui2/actions/tweak_epub.py:17
+msgid "T"
+msgstr ""
+
+#: /home/kovid/work/calibre/src/calibre/gui2/actions/tweak_epub.py:27
+#: /home/kovid/work/calibre/src/calibre/gui2/actions/tweak_epub.py:39
+msgid "Cannot tweak ePub"
+msgstr ""
+
+#: /home/kovid/work/calibre/src/calibre/gui2/actions/tweak_epub.py:40
+msgid "No ePub available. First convert the book to ePub."
+msgstr ""
+
#: /home/kovid/work/calibre/src/calibre/gui2/actions/view.py:24
msgid "V"
msgstr "I"
@@ -4492,7 +4516,7 @@ msgid "The specified directory could not be processed."
msgstr "Zehaztutako direktorioa ezin izan da prozesatu."
#: /home/kovid/work/calibre/src/calibre/gui2/add.py:228
-#: /home/kovid/work/calibre/src/calibre/gui2/device.py:807
+#: /home/kovid/work/calibre/src/calibre/gui2/device.py:811
msgid "No books"
msgstr "Libururik ez"
@@ -4634,13 +4658,13 @@ msgstr ""
#: /home/kovid/work/calibre/src/calibre/gui2/device_drivers/configwidget_ui.py:84
#: /home/kovid/work/calibre/src/calibre/gui2/device_drivers/configwidget_ui.py:85
#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/choose_library_ui.py:77
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single_ui.py:369
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single_ui.py:376
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single_ui.py:390
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single_ui.py:401
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single_ui.py:403
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single_ui.py:405
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single_ui.py:375
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single_ui.py:382
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single_ui.py:396
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single_ui.py:407
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single_ui.py:409
#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single_ui.py:411
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single_ui.py:418
#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/saved_search_editor_ui.py:92
#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/saved_search_editor_ui.py:95
#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/tag_categories_ui.py:161
@@ -5436,7 +5460,7 @@ msgid " is not a valid picture"
msgstr " ez da irudi baliogarria"
#: /home/kovid/work/calibre/src/calibre/gui2/convert/metadata_ui.py:172
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single_ui.py:407
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single_ui.py:413
msgid "Book Cover"
msgstr "Liburuaren azala"
@@ -5445,7 +5469,7 @@ msgid "Use cover from &source file"
msgstr "Erabil ezazu liburu-azala &sorburu fitxategitik"
#: /home/kovid/work/calibre/src/calibre/gui2/convert/metadata_ui.py:174
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single_ui.py:408
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single_ui.py:414
msgid "Change &cover image:"
msgstr "Aldatu &azaleko irudia:"
@@ -5454,18 +5478,18 @@ msgid "Browse for an image to use as the cover of this book."
msgstr "Arakatu irudi egoki baten bila liburu honen azal bezala erabiltzeko"
#: /home/kovid/work/calibre/src/calibre/gui2/convert/metadata_ui.py:177
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single_ui.py:366
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single_ui.py:372
msgid "&Title: "
msgstr "&Izenburua: "
#: /home/kovid/work/calibre/src/calibre/gui2/convert/metadata_ui.py:178
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single_ui.py:367
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single_ui.py:373
msgid "Change the title of this book"
msgstr "Aldatu liburu honen izenburua"
#: /home/kovid/work/calibre/src/calibre/gui2/convert/metadata_ui.py:179
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:229
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single_ui.py:370
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:230
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single_ui.py:376
msgid "&Author(s): "
msgstr "&Egilea(k):s "
@@ -5482,19 +5506,19 @@ msgstr ""
"beharko lirateke."
#: /home/kovid/work/calibre/src/calibre/gui2/convert/metadata_ui.py:182
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:238
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single_ui.py:381
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:239
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single_ui.py:387
msgid "&Publisher: "
msgstr "&Argitaratzailea: "
#: /home/kovid/work/calibre/src/calibre/gui2/convert/metadata_ui.py:183
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single_ui.py:382
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single_ui.py:388
msgid "Ta&gs: "
msgstr "Etike&tak: "
#: /home/kovid/work/calibre/src/calibre/gui2/convert/metadata_ui.py:184
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:240
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single_ui.py:383
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:241
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single_ui.py:389
msgid ""
"Tags categorize the book. This is particularly useful while searching. "
"
They can be any words or phrases, separated by commas."
@@ -5504,22 +5528,22 @@ msgstr ""
"etiketa, komekin bereiziak."
#: /home/kovid/work/calibre/src/calibre/gui2/convert/metadata_ui.py:185
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:247
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single_ui.py:386
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:248
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single_ui.py:392
msgid "&Series:"
msgstr "&Sailak:"
#: /home/kovid/work/calibre/src/calibre/gui2/convert/metadata_ui.py:186
#: /home/kovid/work/calibre/src/calibre/gui2/convert/metadata_ui.py:187
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:248
#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:249
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single_ui.py:387
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single_ui.py:388
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:250
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single_ui.py:393
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single_ui.py:394
msgid "List of known series. You can add new series."
msgstr "Ezagunak diren sailen zerrenda. Sail berria gehi dezakezu."
#: /home/kovid/work/calibre/src/calibre/gui2/convert/metadata_ui.py:188
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single_ui.py:393
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single_ui.py:399
msgid "Book "
msgstr "Liburua "
@@ -6024,7 +6048,7 @@ msgid " index:"
msgstr " aurkibidea:"
#: /home/kovid/work/calibre/src/calibre/gui2/custom_column_widgets.py:451
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:256
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:257
msgid "Automatically number books in this series"
msgstr "Automatikoki esleitu zenbakiak liburuei sail honetan"
@@ -6138,127 +6162,127 @@ msgstr ""
"Behin-behineko huts egite bat egon da irakurgailuarekin komunikatzerakoan. "
"Mesedez, deskonektatu eta konektatu berriro gailua, edo berrabiarazi."
-#: /home/kovid/work/calibre/src/calibre/gui2/device.py:716
+#: /home/kovid/work/calibre/src/calibre/gui2/device.py:720
msgid "Device: "
msgstr "Gailua: "
-#: /home/kovid/work/calibre/src/calibre/gui2/device.py:718
+#: /home/kovid/work/calibre/src/calibre/gui2/device.py:722
msgid " detected."
msgstr " detektaturik."
-#: /home/kovid/work/calibre/src/calibre/gui2/device.py:808
+#: /home/kovid/work/calibre/src/calibre/gui2/device.py:812
msgid "selected to send"
msgstr "hautatua bidaltzeko"
-#: /home/kovid/work/calibre/src/calibre/gui2/device.py:813
+#: /home/kovid/work/calibre/src/calibre/gui2/device.py:817
msgid "Choose format to send to device"
msgstr "Aukeratu irakurgailura bidaltzeko formatua"
-#: /home/kovid/work/calibre/src/calibre/gui2/device.py:822
+#: /home/kovid/work/calibre/src/calibre/gui2/device.py:826
msgid "No device"
msgstr "Gailurik ez dago"
-#: /home/kovid/work/calibre/src/calibre/gui2/device.py:823
+#: /home/kovid/work/calibre/src/calibre/gui2/device.py:827
msgid "Cannot send: No device is connected"
msgstr "Ezin izan da igorri: ez dago inolako gailurik konektatua"
-#: /home/kovid/work/calibre/src/calibre/gui2/device.py:826
#: /home/kovid/work/calibre/src/calibre/gui2/device.py:830
+#: /home/kovid/work/calibre/src/calibre/gui2/device.py:834
msgid "No card"
msgstr "Txartelik ez dago"
-#: /home/kovid/work/calibre/src/calibre/gui2/device.py:827
#: /home/kovid/work/calibre/src/calibre/gui2/device.py:831
+#: /home/kovid/work/calibre/src/calibre/gui2/device.py:835
msgid "Cannot send: Device has no storage card"
msgstr "Ezin bidali: gailuak ez dauka memoria-txartelik"
-#: /home/kovid/work/calibre/src/calibre/gui2/device.py:872
+#: /home/kovid/work/calibre/src/calibre/gui2/device.py:876
msgid "E-book:"
msgstr "Liburu elektronikoa:"
-#: /home/kovid/work/calibre/src/calibre/gui2/device.py:875
+#: /home/kovid/work/calibre/src/calibre/gui2/device.py:879
msgid "Attached, you will find the e-book"
msgstr "Erantsita, liburu elektronikoa topatuko duzu"
-#: /home/kovid/work/calibre/src/calibre/gui2/device.py:876
+#: /home/kovid/work/calibre/src/calibre/gui2/device.py:880
#: /home/kovid/work/calibre/src/calibre/gui2/preferences/plugins.py:107
msgid "by"
msgstr "egilea:"
-#: /home/kovid/work/calibre/src/calibre/gui2/device.py:877
+#: /home/kovid/work/calibre/src/calibre/gui2/device.py:881
msgid "in the %s format."
msgstr "%s formatuan."
-#: /home/kovid/work/calibre/src/calibre/gui2/device.py:890
+#: /home/kovid/work/calibre/src/calibre/gui2/device.py:894
msgid "Sending email to"
msgstr "E-posta igortzen honi:"
-#: /home/kovid/work/calibre/src/calibre/gui2/device.py:920
-#: /home/kovid/work/calibre/src/calibre/gui2/device.py:928
-#: /home/kovid/work/calibre/src/calibre/gui2/device.py:1021
-#: /home/kovid/work/calibre/src/calibre/gui2/device.py:1083
-#: /home/kovid/work/calibre/src/calibre/gui2/device.py:1202
-#: /home/kovid/work/calibre/src/calibre/gui2/device.py:1210
+#: /home/kovid/work/calibre/src/calibre/gui2/device.py:924
+#: /home/kovid/work/calibre/src/calibre/gui2/device.py:932
+#: /home/kovid/work/calibre/src/calibre/gui2/device.py:1025
+#: /home/kovid/work/calibre/src/calibre/gui2/device.py:1087
+#: /home/kovid/work/calibre/src/calibre/gui2/device.py:1206
+#: /home/kovid/work/calibre/src/calibre/gui2/device.py:1214
msgid "No suitable formats"
msgstr "Ez dago formatu egokirik"
-#: /home/kovid/work/calibre/src/calibre/gui2/device.py:921
+#: /home/kovid/work/calibre/src/calibre/gui2/device.py:925
msgid "Auto convert the following books before sending via email?"
msgstr ""
"Bihurtu modu automatikoan ondorengo liburuak e-postaren bidez igorri "
"aurretik?"
-#: /home/kovid/work/calibre/src/calibre/gui2/device.py:929
+#: /home/kovid/work/calibre/src/calibre/gui2/device.py:933
msgid ""
"Could not email the following books as no suitable formats were found:"
msgstr ""
"Ezin izan da e-postaz ondorengo liburuak igorri formatu egokirik topatu ez "
"delako:"
-#: /home/kovid/work/calibre/src/calibre/gui2/device.py:947
+#: /home/kovid/work/calibre/src/calibre/gui2/device.py:951
msgid "Failed to email books"
msgstr "Huts egin du liburuak igortzerakoan"
-#: /home/kovid/work/calibre/src/calibre/gui2/device.py:948
+#: /home/kovid/work/calibre/src/calibre/gui2/device.py:952
msgid "Failed to email the following books:"
msgstr "Huts egin du e-postaz hurrengo liburuak bidaltzerakoan:"
-#: /home/kovid/work/calibre/src/calibre/gui2/device.py:952
+#: /home/kovid/work/calibre/src/calibre/gui2/device.py:956
msgid "Sent by email:"
msgstr "E-postaz igorria:"
-#: /home/kovid/work/calibre/src/calibre/gui2/device.py:980
+#: /home/kovid/work/calibre/src/calibre/gui2/device.py:984
msgid "News:"
msgstr "Albisteak:"
-#: /home/kovid/work/calibre/src/calibre/gui2/device.py:981
+#: /home/kovid/work/calibre/src/calibre/gui2/device.py:985
msgid "Attached is the"
msgstr "Erantsita doana hau da"
-#: /home/kovid/work/calibre/src/calibre/gui2/device.py:992
+#: /home/kovid/work/calibre/src/calibre/gui2/device.py:996
msgid "Sent news to"
msgstr "Bidali albisteak honi:"
-#: /home/kovid/work/calibre/src/calibre/gui2/device.py:1022
-#: /home/kovid/work/calibre/src/calibre/gui2/device.py:1084
-#: /home/kovid/work/calibre/src/calibre/gui2/device.py:1203
+#: /home/kovid/work/calibre/src/calibre/gui2/device.py:1026
+#: /home/kovid/work/calibre/src/calibre/gui2/device.py:1088
+#: /home/kovid/work/calibre/src/calibre/gui2/device.py:1207
msgid "Auto convert the following books before uploading to the device?"
msgstr ""
"Bihurtu modu automatikoan hurrengo liburuak irakurgailuan kargatu aurretik?"
-#: /home/kovid/work/calibre/src/calibre/gui2/device.py:1052
+#: /home/kovid/work/calibre/src/calibre/gui2/device.py:1056
msgid "Sending catalogs to device."
msgstr "Katalogoak irakurgailura bidaltzen."
-#: /home/kovid/work/calibre/src/calibre/gui2/device.py:1116
+#: /home/kovid/work/calibre/src/calibre/gui2/device.py:1120
msgid "Sending news to device."
msgstr "Albisteak irakurgailura bidaltzen."
-#: /home/kovid/work/calibre/src/calibre/gui2/device.py:1169
+#: /home/kovid/work/calibre/src/calibre/gui2/device.py:1173
msgid "Sending books to device."
msgstr "Liburuak irakurgailura bidaltzen."
-#: /home/kovid/work/calibre/src/calibre/gui2/device.py:1211
+#: /home/kovid/work/calibre/src/calibre/gui2/device.py:1215
msgid ""
"Could not upload the following books to the device, as no suitable formats "
"were found. Convert the book(s) to a format supported by your device first."
@@ -6267,11 +6291,11 @@ msgstr ""
"topatu egin ez delako. Lehenengo eta behin, bihurtu liburua(k) zure "
"irakurgailuak onartzen duen formaturen batean."
-#: /home/kovid/work/calibre/src/calibre/gui2/device.py:1273
+#: /home/kovid/work/calibre/src/calibre/gui2/device.py:1277
msgid "No space on device"
msgstr "Lekurik ez irakurgailuan"
-#: /home/kovid/work/calibre/src/calibre/gui2/device.py:1274
+#: /home/kovid/work/calibre/src/calibre/gui2/device.py:1278
msgid ""
"
Cannot upload books to device there is no more free space available "
msgstr ""
@@ -6332,7 +6356,7 @@ msgid "My Books"
msgstr "Nire liburuak"
#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/catalog_ui.py:69
-#: /home/kovid/work/calibre/src/calibre/gui2/tools.py:297
+#: /home/kovid/work/calibre/src/calibre/gui2/tools.py:301
msgid "Generate catalog"
msgstr "Sortu katalogoa"
@@ -6685,32 +6709,32 @@ msgstr ""
"Mesedez, arren, otoi eskatzen zaizu liburutegiaren babes kopia ona egitea "
"kontu hauekin aurrera segi baino lehen."
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk.py:382
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk.py:386
msgid "Search/replace invalid"
msgstr "Bilatu/ordeztu ez dabil"
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk.py:383
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk.py:387
msgid "Search pattern is invalid: %s"
msgstr "Bilatzeko patroaia ez dabil: %s"
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk.py:415
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk.py:419
msgid "Applying changes to %d books. This may take a while."
msgstr ""
"Aplikatu aldaketak %d liburuetara. Honek denbora une bat beharko du, agian."
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:228
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:229
msgid "Edit Meta information"
msgstr "Editatu meta informazioa"
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:230
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:231
msgid "A&utomatically set author sort"
msgstr "A&utomatikoki ezarri egile izenaren araberako sailkapena"
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:231
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:232
msgid "Author s&ort: "
msgstr "Egile izenaren araberako s&ailkapena: "
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:232
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:233
msgid ""
"Specify how the author(s) of this book should be sorted. For example Charles "
"Dickens should be sorted as Dickens, Charles."
@@ -6718,63 +6742,63 @@ msgstr ""
"Zehaztu ea nola sailkatuko d(ir)en liburu honen egile izena(k) Adibidez "
"Charles Dickens honela sailkatuko da; Dickens, Charles."
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:233
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single_ui.py:377
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:234
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single_ui.py:383
msgid "&Rating:"
msgstr "&Balorazioa:"
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:234
#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:235
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single_ui.py:378
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single_ui.py:379
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:236
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single_ui.py:384
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single_ui.py:385
msgid "Rating of this book. 0-5 stars"
msgstr "Liburu honen balorazioa. 0-5 izar"
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:236
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:237
msgid "No change"
msgstr "Aldaketarik ez"
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:237
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single_ui.py:380
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:238
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single_ui.py:386
msgid " stars"
msgstr " izarrak"
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:239
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:240
msgid "Add ta&gs: "
msgstr "Gehitu eti&ketak: "
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:241
#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:242
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single_ui.py:384
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single_ui.py:385
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:243
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single_ui.py:390
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single_ui.py:391
msgid "Open Tag Editor"
msgstr "Zabaldu etiketen editorea"
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:243
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:244
msgid "&Remove tags:"
msgstr "&Ezabatu etiketak:"
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:244
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:245
msgid "Comma separated list of tags to remove from the books. "
msgstr "Liburuetatik ezabatzeko komen bidez bereiziriko etiketen zerrenda. "
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:245
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:246
msgid "Check this box to remove all tags from the books."
msgstr "Aztertu kutxatila hau liburuetako etiketa guztiak ezabatzeko"
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:246
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:247
msgid "Remove all"
msgstr "Ezabatu dena"
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:250
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:251
msgid "Remove &format:"
msgstr "Ezabatu &formatua:"
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:251
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:252
msgid "&Swap title and author"
msgstr "&Trukatu haien artean izenburua eta egilearen izena"
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:252
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:253
msgid ""
"Selected books will be automatically numbered,\n"
"in the order you selected them.\n"
@@ -6786,7 +6810,7 @@ msgstr ""
"Beraz, aukeratu badituzu A liburua eta gero B liburua, A liburuak zerrendako "
"1. zenbakia izango du eta B liburuak zerrendako 2. zenbakia."
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:257
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:258
msgid ""
"Remove stored conversion settings for the selected books.\n"
"\n"
@@ -6797,49 +6821,49 @@ msgstr ""
"Etorkizunean liburu hauen bihurketek lehenetsitako ezarpenak erabiliko "
"dituzte."
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:260
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:261
msgid "Remove &stored conversion settings for the selected books"
msgstr "Ezabatu &gordetako bihurketa ezarpenak hautatutako liburuentzat"
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:261
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single_ui.py:415
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:262
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single_ui.py:422
msgid "&Basic metadata"
msgstr "&Oinarrizko metadatuak"
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:262
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single_ui.py:416
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:263
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single_ui.py:423
msgid "&Custom metadata"
msgstr "&Pertsonalizaturiko metadatuak"
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:263
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:264
msgid "Search &field:"
msgstr "Search &eremua:"
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:264
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:265
msgid "&Search for:"
msgstr "&Bilatu hau:"
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:265
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:266
msgid "&Replace with:"
msgstr "&Ordeztu honekin:"
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:266
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:267
msgid "Apply function &after replace:"
msgstr "Aplikatu funtzioa ordeztu &eta_gero"
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:267
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:268
msgid "Test &text"
msgstr "Testua &testua"
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:268
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:269
msgid "Test re&sult"
msgstr "Testu emai&tza"
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:269
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:270
msgid "Your test:"
msgstr "Zure testua:"
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:270
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:271
msgid "&Search and replace (experimental)"
msgstr "&Bilatu eta ordeztu (esperimentala, beta)"
@@ -6898,7 +6922,7 @@ msgstr "Ezin izan da %s formatutik irakurri"
msgid "The cover in the %s format is invalid"
msgstr "Liburu-azala %s formatuan ez du balio"
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single.py:333
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single.py:351
msgid ""
" The green color indicates that the current author sort matches the current "
"author"
@@ -6906,7 +6930,7 @@ msgstr ""
" Kolore berdeak adierazten du erabiltzen ari garen egile mota honek bat "
"egiten duela erabiltzen ari garen egilearekin"
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single.py:336
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single.py:354
msgid ""
" The red color indicates that the current author sort does not match the "
"current author"
@@ -6914,112 +6938,112 @@ msgstr ""
" Kolore gorriak adierazten du erabiltzen ari garen egile mota honek ez duela "
"bat egiten erabiltzen ari garen egilearekin"
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single.py:341
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single.py:359
msgid "Abort the editing of all remaining books"
msgstr "Bertan behera utzi geratzen diren liburu guztien editatze lana"
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single.py:505
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single.py:510
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single.py:524
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single.py:529
msgid "This ISBN number is valid"
msgstr "ISBN zenbaki baliogarria"
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single.py:513
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single.py:532
msgid "This ISBN number is invalid"
msgstr "ISBN zenbaki balio gabea"
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single.py:592
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single.py:611
msgid "Cannot use tag editor"
msgstr "Ezin da etiketa editorea erabili"
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single.py:593
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single.py:612
msgid "The tags editor cannot be used if you have modified the tags"
msgstr "Etiketa editorea ezin izango da erabili etiketak aldatu badituzu"
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single.py:613
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single.py:632
msgid "Downloading cover..."
msgstr "Deskargatzen liburu-azala..."
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single.py:625
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single.py:630
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single.py:636
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single.py:641
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single.py:644
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single.py:649
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single.py:655
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single.py:660
msgid "Cannot fetch cover"
msgstr "Ezin da liburu-azala eskuratu"
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single.py:626
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single.py:637
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single.py:642
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single.py:645
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single.py:656
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single.py:661
msgid "Could not fetch cover. "
msgstr "Ezin izan da liburu-azalik eskuratu. "
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single.py:627
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single.py:646
msgid "The download timed out."
msgstr "Deskargatzeko denbora gainditu egin da."
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single.py:631
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single.py:650
msgid "Could not find cover for this book. Try specifying the ISBN first."
msgstr ""
"Ezin izan da liburu honetarako liburu-azalik aurkitu. Saia zaitez, lehenengo "
"eta behin, ISBN-arekin."
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single.py:643
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single.py:662
msgid ""
"For the error message from each cover source, click Show details below."
msgstr ""
"Liburu-azaletako sorburuen errore mezurik balego, egin klik Erakutsi "
"zehaztasunak behean."
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single.py:650
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single.py:669
msgid "Bad cover"
msgstr "Liburu-azal okerra"
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single.py:651
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single.py:670
msgid "The cover is not a valid picture"
msgstr "Liburu azala ez da irudi baliogarria"
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single.py:684
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single.py:703
msgid "There were errors"
msgstr "Akatsak egon dira"
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single.py:685
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single.py:704
msgid "There were errors downloading social metadata"
msgstr "Akatsak egon dira gizarte mailako metadatuak deskargatzerakoan"
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single.py:714
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single.py:733
msgid "Cannot fetch metadata"
msgstr "Ezin dira metadatuak eskuratu"
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single.py:715
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single.py:734
msgid "You must specify at least one of ISBN, Title, Authors or Publisher"
msgstr ""
"Gutxienez hauetako bat zehaztu beharko duzu: ISBNa, izenburua, egilea(k) "
"edo argitaletxea"
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single.py:798
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single.py:820
msgid "Permission denied"
msgstr "Ez zaizu baimenik eman"
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single.py:799
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single.py:821
msgid "Could not open %s. Is it being used by another program?"
msgstr "Ezin izan da %s zabaldu. Beste programa bat erabiltzen ari?"
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single_ui.py:364
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single_ui.py:370
msgid "Edit Meta Information"
msgstr "Editatu meta informazioa"
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single_ui.py:365
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single_ui.py:371
msgid "Meta information"
msgstr "Meta informazioa"
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single_ui.py:368
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single_ui.py:374
msgid "Swap the author and title"
msgstr "Trukatu haien artean zenburua eta egilearen izena"
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single_ui.py:371
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single_ui.py:377
msgid "Author S&ort: "
msgstr "Egile izenaren araberako S&ailkapena: "
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single_ui.py:372
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single_ui.py:378
msgid ""
"Specify how the author(s) of this book should be sorted. For example Charles "
"Dickens should be sorted as Dickens, Charles.\n"
@@ -7032,7 +7056,7 @@ msgstr ""
"batekin. Kutxatila gorri baldin badago, orduan egileak edo egileek eta "
"testuek ez dute bat egin."
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single_ui.py:374
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single_ui.py:380
msgid ""
"Automatically create the author sort entry based on the current author "
"entry.\n"
@@ -7044,73 +7068,77 @@ msgstr ""
"Egile mota berria sortzeko botoi hau erabiliz gero, egile mota gorritik "
"berdera aldatzea lortuko dugu."
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single_ui.py:389
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single_ui.py:395
msgid "Remove unused series (Series that have no books)"
msgstr "Ezabatu erabiltzen ez diren serieak (libururik ez daukaten serieak)"
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single_ui.py:391
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single_ui.py:397
msgid "IS&BN:"
msgstr "IS&BNa:"
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single_ui.py:392
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single_ui.py:398
msgid "Publishe&d:"
msgstr "Argitaratu&rik:"
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single_ui.py:395
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single_ui.py:401
msgid "dd MMM yyyy"
msgstr "dd MMM yyyy (AEBetako ohitura)"
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single_ui.py:396
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single_ui.py:402
msgid "&Date:"
msgstr "&Data:"
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single_ui.py:397
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single_ui.py:403
msgid "&Comments"
msgstr "&Iruzkinak"
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single_ui.py:398
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single_ui.py:404
msgid "&Fetch metadata from server"
msgstr "&Eskuratu metadatuak zerbitzaritik"
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single_ui.py:399
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single_ui.py:405
msgid "Available Formats"
msgstr "Eskuragarri dauden formatuak"
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single_ui.py:400
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single_ui.py:406
msgid "Add a new format for this book to the database"
msgstr "Gehi ezazu datu basera liburu honetarako formatu berri bat"
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single_ui.py:402
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single_ui.py:408
msgid "Remove the selected formats for this book from the database."
msgstr "Ezaba ezazu datu basetik liburu honetarako hautatutako formatuak"
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single_ui.py:404
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single_ui.py:410
msgid "Set the cover for the book from the selected format"
msgstr "Ezarri ezazu liburu honen azala hautatutako formatutik"
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single_ui.py:406
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single_ui.py:412
msgid "Update metadata from the metadata in the selected format"
msgstr "Egunera itzazu metadatuak hautatutako formatuaren metadatuetatik"
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single_ui.py:409
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single_ui.py:415
msgid "&Browse"
msgstr "&Arakatu"
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single_ui.py:410
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single_ui.py:416
+msgid "Remove border (if any) from cover"
+msgstr ""
+
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single_ui.py:417
msgid "Reset cover to default"
msgstr "Berrezarri lehenetsitako liburu-azala"
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single_ui.py:412
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single_ui.py:419
msgid "Download co&ver"
msgstr "Deskargatu liburu&azala"
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single_ui.py:413
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single_ui.py:420
msgid "Generate a default cover based on the title and author"
msgstr ""
"Sortu lehenetsitako liburu-azal oinarrizko bat egilearen izenarekin eta "
"izenbuarekin"
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single_ui.py:414
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single_ui.py:421
msgid "&Generate cover"
msgstr "&Sortu liburu-azala"
@@ -7631,6 +7659,39 @@ msgstr "Igorri aztertzeko e-posta bat hemendik %s horra:"
msgid "&Test"
msgstr "&Aztertu"
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/tweak_epub_ui.py:55
+msgid "Display contents of exploded ePub"
+msgstr ""
+
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/tweak_epub_ui.py:56
+msgid "&Explode ePub"
+msgstr ""
+
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/tweak_epub_ui.py:57
+msgid "Rebuild ePub from exploded contents"
+msgstr ""
+
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/tweak_epub_ui.py:58
+msgid "&Rebuild ePub"
+msgstr ""
+
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/tweak_epub_ui.py:59
+msgid "Discard changes"
+msgstr ""
+
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/tweak_epub_ui.py:60
+#: /home/kovid/work/calibre/src/calibre/gui2/preferences/main.py:218
+msgid "&Cancel"
+msgstr "&Bertan behera utzi"
+
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/tweak_epub_ui.py:61
+msgid ""
+"Explode the ePub to display contents in a file browser window. To tweak "
+"individual files, right-click, then 'Open with...' your editor of choice. "
+"When tweaks are complete, close the file browser window. Rebuild the ePub, "
+"updating your calibre library."
+msgstr ""
+
#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/user_profiles.py:127
msgid "No recipe selected"
msgstr "Ez da formula edo errezetarik hautatu"
@@ -8060,7 +8121,7 @@ msgid "Show books in the main memory of the device"
msgstr "Erakutsi liburuak irakurgailuaren memoria nagusian"
#: /home/kovid/work/calibre/src/calibre/gui2/layout.py:66
-#: /home/kovid/work/calibre/src/calibre/library/database2.py:656
+#: /home/kovid/work/calibre/src/calibre/library/database2.py:655
msgid "Card A"
msgstr "A txartela"
@@ -8069,7 +8130,7 @@ msgid "Show books in storage card A"
msgstr "Erakutsi liburuak A memoria-txartelean"
#: /home/kovid/work/calibre/src/calibre/gui2/layout.py:68
-#: /home/kovid/work/calibre/src/calibre/library/database2.py:658
+#: /home/kovid/work/calibre/src/calibre/library/database2.py:657
msgid "Card B"
msgstr "B txartela"
@@ -8199,7 +8260,7 @@ msgstr "Erakutsi zutabea"
msgid "Restore default layout"
msgstr "Berrezarri lehenetsitako diseinua"
-#: /home/kovid/work/calibre/src/calibre/gui2/library/views.py:555
+#: /home/kovid/work/calibre/src/calibre/gui2/library/views.py:585
msgid ""
"Dropping onto a device is not supported. First add the book to the calibre "
"library."
@@ -8984,10 +9045,6 @@ msgstr "&Banatu tresna barra bi tresna barra berrietan"
msgid "&Apply"
msgstr "&Aplikatu"
-#: /home/kovid/work/calibre/src/calibre/gui2/preferences/main.py:218
-msgid "&Cancel"
-msgstr "&Bertan behera utzi"
-
#: /home/kovid/work/calibre/src/calibre/gui2/preferences/main.py:222
msgid "Restore &defaults"
msgstr "Berrezarri &lehenetsiak"
@@ -9311,7 +9368,7 @@ msgstr ""
"honela: Preferences->Advanced->Plugins (Aukerak>Aurreratua>Gehigarriak)"
#: /home/kovid/work/calibre/src/calibre/gui2/preferences/server.py:75
-#: /home/kovid/work/calibre/src/calibre/gui2/ui.py:319
+#: /home/kovid/work/calibre/src/calibre/gui2/ui.py:320
msgid "Failed to start content server"
msgstr "Huts egin du edukien zerbitzaria abiarazten"
@@ -9743,15 +9800,15 @@ msgstr "Liburuak errenkadan jartzen bihurketa multzoka egiteko"
msgid "Queueing "
msgstr "Errenkadan jartzen "
-#: /home/kovid/work/calibre/src/calibre/gui2/tools.py:239
+#: /home/kovid/work/calibre/src/calibre/gui2/tools.py:243
msgid "Fetch news from "
msgstr "Eskuratu berriak hemendik "
-#: /home/kovid/work/calibre/src/calibre/gui2/tools.py:309
+#: /home/kovid/work/calibre/src/calibre/gui2/tools.py:313
msgid "Convert existing"
msgstr "Bihurtu dagoen"
-#: /home/kovid/work/calibre/src/calibre/gui2/tools.py:310
+#: /home/kovid/work/calibre/src/calibre/gui2/tools.py:314
msgid ""
"The following books have already been converted to %s format. Do you wish to "
"reconvert them?"
@@ -9759,28 +9816,28 @@ msgstr ""
"Honako liburuak dagoeneko %s formatura bihurtu dira. Nahi dituzu berriro "
"bihurtu?"
-#: /home/kovid/work/calibre/src/calibre/gui2/ui.py:167
+#: /home/kovid/work/calibre/src/calibre/gui2/ui.py:168
msgid "&Restore"
msgstr "&Lehengoratu"
-#: /home/kovid/work/calibre/src/calibre/gui2/ui.py:169
+#: /home/kovid/work/calibre/src/calibre/gui2/ui.py:170
msgid "&Donate to support calibre"
msgstr "&Emaitza egin calibre diruz laguntzeko"
-#: /home/kovid/work/calibre/src/calibre/gui2/ui.py:173
+#: /home/kovid/work/calibre/src/calibre/gui2/ui.py:174
msgid "&Eject connected device"
msgstr "&Deskonektatu konektatuta zegoen gailua"
-#: /home/kovid/work/calibre/src/calibre/gui2/ui.py:215
+#: /home/kovid/work/calibre/src/calibre/gui2/ui.py:216
msgid "Calibre Quick Start Guide"
msgstr "Calibre Quick Start Guide (azkar erabiltzeko gida)"
-#: /home/kovid/work/calibre/src/calibre/gui2/ui.py:417
-#: /home/kovid/work/calibre/src/calibre/gui2/ui.py:445
+#: /home/kovid/work/calibre/src/calibre/gui2/ui.py:418
+#: /home/kovid/work/calibre/src/calibre/gui2/ui.py:446
msgid "Conversion Error"
msgstr "Bihurketa akatsa"
-#: /home/kovid/work/calibre/src/calibre/gui2/ui.py:418
+#: /home/kovid/work/calibre/src/calibre/gui2/ui.py:419
msgid ""
"
Could not convert: %s
It is a DRMed book. You must "
"first remove the DRM using third party tools."
@@ -9790,15 +9847,15 @@ msgstr ""
"sistema bat). Bihurtu ahal izateko DRM arazo hori konpondu beharko duzu "
"beste batzuen lanabesak erabiliz."
-#: /home/kovid/work/calibre/src/calibre/gui2/ui.py:431
+#: /home/kovid/work/calibre/src/calibre/gui2/ui.py:432
msgid "Recipe Disabled"
msgstr "Formula desgaitua"
-#: /home/kovid/work/calibre/src/calibre/gui2/ui.py:446
+#: /home/kovid/work/calibre/src/calibre/gui2/ui.py:447
msgid "Failed"
msgstr "Huts egin du"
-#: /home/kovid/work/calibre/src/calibre/gui2/ui.py:482
+#: /home/kovid/work/calibre/src/calibre/gui2/ui.py:483
msgid ""
"is the result of the efforts of many volunteers from all over the world. If "
"you find it useful, please consider donating to support its development. "
@@ -9809,12 +9866,12 @@ msgstr ""
"kontua garapenean laguntzeko. Zure emaitzak lagunduko dio calibreri garatzen "
"jarraitzen."
-#: /home/kovid/work/calibre/src/calibre/gui2/ui.py:508
+#: /home/kovid/work/calibre/src/calibre/gui2/ui.py:509
msgid "There are active jobs. Are you sure you want to quit?"
msgstr ""
"Oraintxe bertan lan batzuk egiten ari dira. Ziur zaude irten nahi duzula?"
-#: /home/kovid/work/calibre/src/calibre/gui2/ui.py:511
+#: /home/kovid/work/calibre/src/calibre/gui2/ui.py:512
msgid ""
" is communicating with the device! \n"
" Quitting may cause corruption on the device. \n"
@@ -9825,11 +9882,11 @@ msgstr ""
"daiteke. \n"
" Ziur zaude? Benetan irten nahi duzu?"
-#: /home/kovid/work/calibre/src/calibre/gui2/ui.py:515
+#: /home/kovid/work/calibre/src/calibre/gui2/ui.py:516
msgid "WARNING: Active jobs"
msgstr "ABISUA: lanean dihardu"
-#: /home/kovid/work/calibre/src/calibre/gui2/ui.py:583
+#: /home/kovid/work/calibre/src/calibre/gui2/ui.py:584
msgid ""
"will keep running in the system tray. To close it, choose Quit in the "
"context menu of the system tray."
@@ -10681,48 +10738,48 @@ msgstr ""
msgid "Turn on the &content server"
msgstr "Gaitu &edukien zerbitzaria"
-#: /home/kovid/work/calibre/src/calibre/library/caches.py:232
+#: /home/kovid/work/calibre/src/calibre/library/caches.py:234
msgid "today"
msgstr "gaur"
-#: /home/kovid/work/calibre/src/calibre/library/caches.py:235
+#: /home/kovid/work/calibre/src/calibre/library/caches.py:237
msgid "yesterday"
msgstr "atzo"
-#: /home/kovid/work/calibre/src/calibre/library/caches.py:238
+#: /home/kovid/work/calibre/src/calibre/library/caches.py:240
msgid "thismonth"
msgstr "hilabetehonetan"
-#: /home/kovid/work/calibre/src/calibre/library/caches.py:241
-#: /home/kovid/work/calibre/src/calibre/library/caches.py:242
+#: /home/kovid/work/calibre/src/calibre/library/caches.py:243
+#: /home/kovid/work/calibre/src/calibre/library/caches.py:244
msgid "daysago"
msgstr "duelaegunbatzuk"
-#: /home/kovid/work/calibre/src/calibre/library/caches.py:406
-#: /home/kovid/work/calibre/src/calibre/library/caches.py:416
+#: /home/kovid/work/calibre/src/calibre/library/caches.py:408
+#: /home/kovid/work/calibre/src/calibre/library/caches.py:418
msgid "no"
msgstr "Ez"
-#: /home/kovid/work/calibre/src/calibre/library/caches.py:406
-#: /home/kovid/work/calibre/src/calibre/library/caches.py:416
+#: /home/kovid/work/calibre/src/calibre/library/caches.py:408
+#: /home/kovid/work/calibre/src/calibre/library/caches.py:418
msgid "unchecked"
msgstr "aztertu gabea"
-#: /home/kovid/work/calibre/src/calibre/library/caches.py:409
-#: /home/kovid/work/calibre/src/calibre/library/caches.py:419
+#: /home/kovid/work/calibre/src/calibre/library/caches.py:411
+#: /home/kovid/work/calibre/src/calibre/library/caches.py:421
msgid "checked"
msgstr "aztertua"
-#: /home/kovid/work/calibre/src/calibre/library/caches.py:409
-#: /home/kovid/work/calibre/src/calibre/library/caches.py:419
+#: /home/kovid/work/calibre/src/calibre/library/caches.py:411
+#: /home/kovid/work/calibre/src/calibre/library/caches.py:421
msgid "yes"
msgstr "bai"
-#: /home/kovid/work/calibre/src/calibre/library/caches.py:413
+#: /home/kovid/work/calibre/src/calibre/library/caches.py:415
msgid "blank"
msgstr "hutsunea, zuriunea"
-#: /home/kovid/work/calibre/src/calibre/library/caches.py:413
+#: /home/kovid/work/calibre/src/calibre/library/caches.py:415
msgid "empty"
msgstr "hutsik"
@@ -10908,7 +10965,7 @@ msgid ""
"Applies to: ePub, MOBI output formats"
msgstr ""
"Baita ere bere baitan 'Serieak atala, katalogoan.\n"
-"Lehenetsita: '%lehenetsita'\n"
+"Lehenetsita: '%default'\n"
"Aplikatzeko: ePub, MOBI output formatuetara"
#: /home/kovid/work/calibre/src/calibre/library/catalog.py:581
@@ -11539,33 +11596,33 @@ msgstr ""
msgid "%sAverage rating is %3.1f"
msgstr "%sBatezbesteko balorazioa hauxe: %3.1f"
-#: /home/kovid/work/calibre/src/calibre/library/database2.py:654
+#: /home/kovid/work/calibre/src/calibre/library/database2.py:653
msgid "Main"
msgstr "Nagusia"
-#: /home/kovid/work/calibre/src/calibre/library/database2.py:1992
+#: /home/kovid/work/calibre/src/calibre/library/database2.py:1998
msgid "
Migrating old database to ebook library in %s
"
msgstr ""
"
Migrazioa egiten datu base zaharretik liburu elektronikoen liburutegira "
"zera honetan: %s
"
-#: /home/kovid/work/calibre/src/calibre/library/database2.py:2021
+#: /home/kovid/work/calibre/src/calibre/library/database2.py:2027
msgid "Copying %s"
msgstr "Kopiatzen %s"
-#: /home/kovid/work/calibre/src/calibre/library/database2.py:2038
+#: /home/kovid/work/calibre/src/calibre/library/database2.py:2044
msgid "Compacting database"
msgstr "Trinkotzen datu basea"
-#: /home/kovid/work/calibre/src/calibre/library/database2.py:2131
+#: /home/kovid/work/calibre/src/calibre/library/database2.py:2137
msgid "Checking SQL integrity..."
msgstr "Aztertzen SQL-ren osotasuna..."
-#: /home/kovid/work/calibre/src/calibre/library/database2.py:2170
+#: /home/kovid/work/calibre/src/calibre/library/database2.py:2176
msgid "Checking for missing files."
msgstr "Aztertzen fitxategi galduen bila."
-#: /home/kovid/work/calibre/src/calibre/library/database2.py:2192
+#: /home/kovid/work/calibre/src/calibre/library/database2.py:2198
msgid "Checked id"
msgstr "Aztertutako ID"
@@ -12092,61 +12149,61 @@ msgid ""
"Do not download latest version of builtin recipes from the calibre server"
msgstr "Ez deskargatu azken builtin formulen bertsioak calibre zerbitzaritik"
-#: /home/kovid/work/calibre/src/calibre/web/feeds/news.py:46
+#: /home/kovid/work/calibre/src/calibre/web/feeds/news.py:47
msgid "Unknown News Source"
msgstr "Albiste iturri ezezaguna"
-#: /home/kovid/work/calibre/src/calibre/web/feeds/news.py:611
+#: /home/kovid/work/calibre/src/calibre/web/feeds/news.py:612
msgid "The \"%s\" recipe needs a username and password."
msgstr ""
"Honek \"%s\" formula honek erabiltzaile-izena eta pasahitza behar ditu."
-#: /home/kovid/work/calibre/src/calibre/web/feeds/news.py:710
+#: /home/kovid/work/calibre/src/calibre/web/feeds/news.py:711
msgid "Download finished"
msgstr "Deskarga burutua"
-#: /home/kovid/work/calibre/src/calibre/web/feeds/news.py:712
+#: /home/kovid/work/calibre/src/calibre/web/feeds/news.py:713
msgid "Failed to download the following articles:"
msgstr "Huts egin du honako artikulu hauek deskargatzerakoan:"
-#: /home/kovid/work/calibre/src/calibre/web/feeds/news.py:718
+#: /home/kovid/work/calibre/src/calibre/web/feeds/news.py:719
msgid "Failed to download parts of the following articles:"
msgstr "Huts egin du honako artikulu hauen zatiak deskargatzerakoan:"
-#: /home/kovid/work/calibre/src/calibre/web/feeds/news.py:720
+#: /home/kovid/work/calibre/src/calibre/web/feeds/news.py:721
msgid " from "
msgstr " hemendik "
-#: /home/kovid/work/calibre/src/calibre/web/feeds/news.py:722
+#: /home/kovid/work/calibre/src/calibre/web/feeds/news.py:723
msgid "\tFailed links:"
msgstr "\tEz dabiltzan estekak:"
-#: /home/kovid/work/calibre/src/calibre/web/feeds/news.py:811
+#: /home/kovid/work/calibre/src/calibre/web/feeds/news.py:812
msgid "Could not fetch article. Run with -vv to see the reason"
msgstr ""
"Ezin izan da artikulua eskuratu. Exekutatu -vv hori zergatia ikusteko"
-#: /home/kovid/work/calibre/src/calibre/web/feeds/news.py:832
+#: /home/kovid/work/calibre/src/calibre/web/feeds/news.py:833
msgid "Fetching feeds..."
msgstr "Jarioak eskuratzen..."
-#: /home/kovid/work/calibre/src/calibre/web/feeds/news.py:837
+#: /home/kovid/work/calibre/src/calibre/web/feeds/news.py:838
msgid "Got feeds from index page"
msgstr "Jarioak hartuta hasiera orrialdetik"
-#: /home/kovid/work/calibre/src/calibre/web/feeds/news.py:843
+#: /home/kovid/work/calibre/src/calibre/web/feeds/news.py:844
msgid "Trying to download cover..."
msgstr "Liburu-azala deskargatzen saiatzen..."
-#: /home/kovid/work/calibre/src/calibre/web/feeds/news.py:845
+#: /home/kovid/work/calibre/src/calibre/web/feeds/news.py:846
msgid "Generating masthead..."
msgstr "Sortzen idazpurua..."
-#: /home/kovid/work/calibre/src/calibre/web/feeds/news.py:926
+#: /home/kovid/work/calibre/src/calibre/web/feeds/news.py:927
msgid "Starting download [%d thread(s)]..."
msgstr "Hasi deskarga [%d haria(k)]..."
-#: /home/kovid/work/calibre/src/calibre/web/feeds/news.py:942
+#: /home/kovid/work/calibre/src/calibre/web/feeds/news.py:943
msgid "Feeds downloaded to %s"
msgstr "Jarioak hona deskargatuta %s"
@@ -12154,31 +12211,31 @@ msgstr "Jarioak hona deskargatuta %s"
msgid "Could not download cover: %s"
msgstr "Ezin izan da liburu-azala deskargatu: %s"
-#: /home/kovid/work/calibre/src/calibre/web/feeds/news.py:964
+#: /home/kovid/work/calibre/src/calibre/web/feeds/news.py:959
msgid "Downloading cover from %s"
msgstr "Deskargatzen liburu-azala hemendik %s"
-#: /home/kovid/work/calibre/src/calibre/web/feeds/news.py:1005
+#: /home/kovid/work/calibre/src/calibre/web/feeds/news.py:1004
msgid "Masthead image downloaded"
msgstr "Idazpuruko irudia deskargatuta"
-#: /home/kovid/work/calibre/src/calibre/web/feeds/news.py:1173
+#: /home/kovid/work/calibre/src/calibre/web/feeds/news.py:1172
msgid "Untitled Article"
msgstr "Izenbururik gabeko artikulua"
-#: /home/kovid/work/calibre/src/calibre/web/feeds/news.py:1244
+#: /home/kovid/work/calibre/src/calibre/web/feeds/news.py:1243
msgid "Article downloaded: %s"
msgstr "Deskargatutako artikulua: %s"
-#: /home/kovid/work/calibre/src/calibre/web/feeds/news.py:1255
+#: /home/kovid/work/calibre/src/calibre/web/feeds/news.py:1254
msgid "Article download failed: %s"
msgstr "Huts egin du artikuluaren deskarga: %s"
-#: /home/kovid/work/calibre/src/calibre/web/feeds/news.py:1272
+#: /home/kovid/work/calibre/src/calibre/web/feeds/news.py:1271
msgid "Fetching feed"
msgstr "Jarioa eskuratzen"
-#: /home/kovid/work/calibre/src/calibre/web/feeds/news.py:1419
+#: /home/kovid/work/calibre/src/calibre/web/feeds/news.py:1418
msgid ""
"Failed to log in, check your username and password for the calibre "
"Periodicals service."
@@ -12186,7 +12243,7 @@ msgstr ""
"Izena emate saioak huts egin du, azter itzazu zure erabiltzaile-izena eta "
"pasahitza calibreren Aldian behingo zerbitzurako."
-#: /home/kovid/work/calibre/src/calibre/web/feeds/news.py:1435
+#: /home/kovid/work/calibre/src/calibre/web/feeds/news.py:1433
msgid ""
"You do not have permission to download this issue. Either your subscription "
"has expired or you have exceeded the maximum allowed downloads for today."
diff --git a/src/calibre/translations/fr.po b/src/calibre/translations/fr.po
index 50367ea8b7..8e874a61e4 100644
--- a/src/calibre/translations/fr.po
+++ b/src/calibre/translations/fr.po
@@ -6,14 +6,14 @@ msgid ""
msgstr ""
"Project-Id-Version: calibre 0.4.22\n"
"Report-Msgid-Bugs-To: \n"
-"POT-Creation-Date: 2010-09-17 21:00+0000\n"
-"PO-Revision-Date: 2010-09-21 05:52+0000\n"
-"Last-Translator: sengian \n"
+"POT-Creation-Date: 2010-09-24 21:33+0000\n"
+"PO-Revision-Date: 2010-09-24 20:44+0000\n"
+"Last-Translator: Kovid Goyal \n"
"Language-Team: fr\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
-"X-Launchpad-Export-Date: 2010-09-22 04:59+0000\n"
+"X-Launchpad-Export-Date: 2010-09-25 04:40+0000\n"
"X-Generator: Launchpad (build Unknown)\n"
"X-Poedit-Bookmarks: 1177,-1,-1,-1,-1,-1,-1,-1,-1,-1\n"
"Generated-By: pygettext.py 1.5\n"
@@ -25,7 +25,8 @@ msgstr "Ne fait strictement rien"
#: /home/kovid/work/calibre/src/calibre/customize/__init__.py:46
#: /home/kovid/work/calibre/src/calibre/devices/jetbook/driver.py:74
#: /home/kovid/work/calibre/src/calibre/devices/kindle/driver.py:76
-#: /home/kovid/work/calibre/src/calibre/devices/kobo/driver.py:410
+#: /home/kovid/work/calibre/src/calibre/devices/kobo/books.py:46
+#: /home/kovid/work/calibre/src/calibre/devices/kobo/driver.py:412
#: /home/kovid/work/calibre/src/calibre/devices/nook/driver.py:70
#: /home/kovid/work/calibre/src/calibre/devices/nook/driver.py:71
#: /home/kovid/work/calibre/src/calibre/devices/prs500/books.py:267
@@ -106,24 +107,24 @@ msgstr "Ne fait strictement rien"
#: /home/kovid/work/calibre/src/calibre/ebooks/pdf/writer.py:98
#: /home/kovid/work/calibre/src/calibre/ebooks/rtf/input.py:239
#: /home/kovid/work/calibre/src/calibre/ebooks/rtf/input.py:241
-#: /home/kovid/work/calibre/src/calibre/gui2/__init__.py:324
-#: /home/kovid/work/calibre/src/calibre/gui2/__init__.py:331
-#: /home/kovid/work/calibre/src/calibre/gui2/actions/edit_metadata.py:293
+#: /home/kovid/work/calibre/src/calibre/gui2/__init__.py:352
+#: /home/kovid/work/calibre/src/calibre/gui2/__init__.py:359
#: /home/kovid/work/calibre/src/calibre/gui2/actions/edit_metadata.py:296
+#: /home/kovid/work/calibre/src/calibre/gui2/actions/edit_metadata.py:299
#: /home/kovid/work/calibre/src/calibre/gui2/add.py:137
#: /home/kovid/work/calibre/src/calibre/gui2/add.py:144
#: /home/kovid/work/calibre/src/calibre/gui2/convert/__init__.py:42
#: /home/kovid/work/calibre/src/calibre/gui2/convert/metadata.py:111
#: /home/kovid/work/calibre/src/calibre/gui2/convert/metadata.py:136
#: /home/kovid/work/calibre/src/calibre/gui2/convert/metadata.py:138
-#: /home/kovid/work/calibre/src/calibre/gui2/device.py:865
-#: /home/kovid/work/calibre/src/calibre/gui2/device.py:874
-#: /home/kovid/work/calibre/src/calibre/gui2/device.py:1158
-#: /home/kovid/work/calibre/src/calibre/gui2/device.py:1161
+#: /home/kovid/work/calibre/src/calibre/gui2/device.py:869
+#: /home/kovid/work/calibre/src/calibre/gui2/device.py:878
+#: /home/kovid/work/calibre/src/calibre/gui2/device.py:1162
+#: /home/kovid/work/calibre/src/calibre/gui2/device.py:1165
#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/comicconf.py:47
#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/fetch_metadata.py:120
#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/fetch_metadata.py:155
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single.py:552
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single.py:571
#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/scheduler.py:173
#: /home/kovid/work/calibre/src/calibre/gui2/library/models.py:357
#: /home/kovid/work/calibre/src/calibre/gui2/library/models.py:377
@@ -136,11 +137,11 @@ msgstr "Ne fait strictement rien"
#: /home/kovid/work/calibre/src/calibre/library/database.py:913
#: /home/kovid/work/calibre/src/calibre/library/database2.py:375
#: /home/kovid/work/calibre/src/calibre/library/database2.py:387
-#: /home/kovid/work/calibre/src/calibre/library/database2.py:1065
-#: /home/kovid/work/calibre/src/calibre/library/database2.py:1137
-#: /home/kovid/work/calibre/src/calibre/library/database2.py:1837
-#: /home/kovid/work/calibre/src/calibre/library/database2.py:1839
-#: /home/kovid/work/calibre/src/calibre/library/database2.py:1966
+#: /home/kovid/work/calibre/src/calibre/library/database2.py:1064
+#: /home/kovid/work/calibre/src/calibre/library/database2.py:1139
+#: /home/kovid/work/calibre/src/calibre/library/database2.py:1843
+#: /home/kovid/work/calibre/src/calibre/library/database2.py:1845
+#: /home/kovid/work/calibre/src/calibre/library/database2.py:1972
#: /home/kovid/work/calibre/src/calibre/library/server/mobile.py:211
#: /home/kovid/work/calibre/src/calibre/library/server/opds.py:137
#: /home/kovid/work/calibre/src/calibre/library/server/opds.py:140
@@ -262,44 +263,44 @@ msgstr "Définir les métadonnées des fichiers %s"
msgid "Set metadata from %s files"
msgstr "Définir les métadonnées à partir des fichiers %s"
-#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:684
+#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:688
msgid "Look and Feel"
msgstr "Apparence"
-#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:686
-#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:698
-#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:709
-#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:720
+#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:690
+#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:702
+#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:713
+#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:724
msgid "Interface"
msgstr "Interface"
-#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:690
+#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:694
msgid "Adjust the look and feel of the calibre interface to suit your tastes"
msgstr ""
"Ajuster l'aspect et l'ergonomie de l'interface de Calibre à votre convenance"
-#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:696
+#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:700
msgid "Behavior"
msgstr "Comportement"
-#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:702
+#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:706
msgid "Change the way calibre behaves"
msgstr "Changer le comportement de calibre"
-#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:707
+#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:711
#: /home/kovid/work/calibre/src/calibre/gui2/library/views.py:176
msgid "Add your own columns"
msgstr "Ajouter vos colonnes personnalisées"
-#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:713
+#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:717
msgid "Add/remove your own columns to the calibre book list"
msgstr "Ajouter/retirer vos propres colonnes dans la liste des livres"
-#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:718
+#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:722
msgid "Customize the toolbar"
msgstr "Personnaliser la barre d'outils"
-#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:724
+#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:728
msgid ""
"Customize the toolbars and context menus, changing which actions are "
"available in each"
@@ -307,59 +308,59 @@ msgstr ""
"Personnaliser les barres d'outils et les menus contextuels, en changeant les "
"actions disponibles dans ceux-ci"
-#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:730
+#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:734
msgid "Input Options"
msgstr "Options de saisie"
-#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:732
-#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:743
-#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:754
+#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:736
+#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:747
+#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:758
msgid "Conversion"
msgstr "Conversion"
-#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:736
+#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:740
msgid "Set conversion options specific to each input format"
msgstr ""
"Définissez les options de conversion spécfiques pour chaque format d'entrée"
-#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:741
+#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:745
msgid "Common Options"
msgstr "Options communes"
-#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:747
+#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:751
msgid "Set conversion options common to all formats"
msgstr ""
"Définisser les options de conversion communes à tous les formats d'entrée"
-#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:752
+#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:756
msgid "Output Options"
msgstr "Formats de sortie"
-#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:758
+#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:762
msgid "Set conversion options specific to each output format"
msgstr "Définissez des options spécifiques pour chaque format de sortie"
-#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:763
+#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:767
msgid "Adding books"
msgstr "Ajouter des livres"
-#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:765
-#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:777
-#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:789
+#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:769
+#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:781
+#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:793
msgid "Import/Export"
msgstr "Import/Export"
-#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:769
+#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:773
msgid "Control how calibre reads metadata from files when adding books"
msgstr ""
"Définir comment les métadonnées sont lues par Calibre lors de l'ajout de "
"livres"
-#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:775
+#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:779
msgid "Saving books to disk"
msgstr "Sauvegarder les livres sur le disque"
-#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:781
+#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:785
msgid ""
"Control how calibre exports files from its database to disk when using Save "
"to disk"
@@ -367,26 +368,26 @@ msgstr ""
"Contrôle la manière dont Calibre exporte les fichiers de sa base de données "
"sur le disque lors des sauvegardes sur disque"
-#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:787
+#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:791
msgid "Sending books to devices"
msgstr "Envoyer les livres aux appareils"
-#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:793
+#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:797
msgid "Control how calibre transfers files to your ebook reader"
msgstr ""
"Controller la façon dont calibre exporte les fichiers vers votre lecteur "
"d'eBook"
-#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:799
+#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:803
msgid "Sharing books by email"
msgstr "Partager des livres par courriel"
-#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:801
-#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:813
+#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:805
+#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:817
msgid "Sharing"
msgstr "Partage"
-#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:805
+#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:809
msgid ""
"Setup sharing of books via email. Can be used for automatic sending of "
"downloaded news to your devices"
@@ -394,11 +395,11 @@ msgstr ""
"Mise en place du partage de livre par courriel. Peu aussi être utilisé pour "
"envoyer automatiquement les dernières nouvelles téléchargées à votre appareil"
-#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:811
+#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:815
msgid "Sharing over the net"
msgstr "Partager a travers le réseau"
-#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:817
+#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:821
msgid ""
"Setup the calibre Content Server which will give you access to your calibre "
"library from anywhere, on any device, over the internet"
@@ -407,34 +408,34 @@ msgstr ""
"bibliothèque de calibre depuis n'importe où, sur tout les périphérique ou "
"depuis Internet"
-#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:824
+#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:828
msgid "Plugins"
msgstr "Plugins"
-#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:826
-#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:838
-#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:849
+#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:830
+#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:842
+#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:853
msgid "Advanced"
msgstr "Mode avancé"
-#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:830
+#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:834
msgid "Add/remove/customize various bits of calibre functionality"
msgstr "Ajouter/Retirer/Modifier diverses fonctionalités de Calibre"
-#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:836
+#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:840
msgid "Tweaks"
msgstr "Ajustements"
-#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:842
+#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:846
msgid "Fine tune how calibre behaves in various contexts"
msgstr ""
"Affiner la manière dont Calibre se comporte dans différents contextes"
-#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:847
+#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:851
msgid "Miscellaneous"
msgstr "Divers"
-#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:853
+#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:857
msgid "Miscellaneous advanced configuration"
msgstr "Diverses configurations avancées"
@@ -613,15 +614,15 @@ msgstr "Plugins désactivés."
msgid "Enabled plugins"
msgstr "Plugins activés"
-#: /home/kovid/work/calibre/src/calibre/customize/ui.py:86
+#: /home/kovid/work/calibre/src/calibre/customize/ui.py:93
msgid "No valid plugin found in "
msgstr "Absence de plugin compatible dans "
-#: /home/kovid/work/calibre/src/calibre/customize/ui.py:501
+#: /home/kovid/work/calibre/src/calibre/customize/ui.py:508
msgid "Initialization of plugin %s failed with traceback:"
msgstr "L'initialisation du plugin %s a échoué en laissant une trace:"
-#: /home/kovid/work/calibre/src/calibre/customize/ui.py:534
+#: /home/kovid/work/calibre/src/calibre/customize/ui.py:541
msgid ""
" %prog options\n"
"\n"
@@ -633,18 +634,18 @@ msgstr ""
" Personnaliser Calibre en chargeant des modules additionnels externes.\n"
" "
-#: /home/kovid/work/calibre/src/calibre/customize/ui.py:540
+#: /home/kovid/work/calibre/src/calibre/customize/ui.py:547
msgid "Add a plugin by specifying the path to the zip file containing it."
msgstr ""
"Ajouter un plugin en précisant le chemin vers le fichier zip qui le contient."
-#: /home/kovid/work/calibre/src/calibre/customize/ui.py:542
+#: /home/kovid/work/calibre/src/calibre/customize/ui.py:549
msgid "Remove a custom plugin by name. Has no effect on builtin plugins"
msgstr ""
"Suppression d'un plugin personnalisé d'après son nom. Ceci n'a pas d'effet "
"sur les plugins intégrés."
-#: /home/kovid/work/calibre/src/calibre/customize/ui.py:544
+#: /home/kovid/work/calibre/src/calibre/customize/ui.py:551
msgid ""
"Customize plugin. Specify name of plugin and customization string separated "
"by a comma."
@@ -652,15 +653,15 @@ msgstr ""
"Personnaliser le plugin. Spécifier le nom du plugin et la chaîne de "
"personnalisation séparés par une virgule."
-#: /home/kovid/work/calibre/src/calibre/customize/ui.py:546
+#: /home/kovid/work/calibre/src/calibre/customize/ui.py:553
msgid "List all installed plugins"
msgstr "Lister tous les plugins installés"
-#: /home/kovid/work/calibre/src/calibre/customize/ui.py:548
+#: /home/kovid/work/calibre/src/calibre/customize/ui.py:555
msgid "Enable the named plugin"
msgstr "Activer le plugin nommé"
-#: /home/kovid/work/calibre/src/calibre/customize/ui.py:550
+#: /home/kovid/work/calibre/src/calibre/customize/ui.py:557
msgid "Disable the named plugin"
msgstr "Désactive le plugin nommé"
@@ -668,7 +669,7 @@ msgstr "Désactive le plugin nommé"
msgid "Communicate with Android phones."
msgstr "Communiquer avec les téléphones Android"
-#: /home/kovid/work/calibre/src/calibre/devices/android/driver.py:50
+#: /home/kovid/work/calibre/src/calibre/devices/android/driver.py:52
msgid ""
"Comma separated list of directories to send e-books to on the device. The "
"first one that exists will be used"
@@ -676,7 +677,7 @@ msgstr ""
"Liste de répertoires séparés par des virgules utilisée pour envoyer les "
"ebooks vers l'appareil. Le premier existant sera utilisé."
-#: /home/kovid/work/calibre/src/calibre/devices/android/driver.py:92
+#: /home/kovid/work/calibre/src/calibre/devices/android/driver.py:94
msgid "Communicate with S60 phones."
msgstr "Communiquer avec les téléphones S60"
@@ -745,7 +746,7 @@ msgstr ""
#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/scheduler.py:244
#: /home/kovid/work/calibre/src/calibre/library/database2.py:198
#: /home/kovid/work/calibre/src/calibre/library/database2.py:211
-#: /home/kovid/work/calibre/src/calibre/library/database2.py:1706
+#: /home/kovid/work/calibre/src/calibre/library/database2.py:1712
#: /home/kovid/work/calibre/src/calibre/library/field_metadata.py:134
msgid "News"
msgstr "Informations"
@@ -753,8 +754,8 @@ msgstr "Informations"
#: /home/kovid/work/calibre/src/calibre/devices/apple/driver.py:2500
#: /home/kovid/work/calibre/src/calibre/gui2/catalog/catalog_epub_mobi.py:20
#: /home/kovid/work/calibre/src/calibre/library/catalog.py:556
-#: /home/kovid/work/calibre/src/calibre/library/database2.py:1669
-#: /home/kovid/work/calibre/src/calibre/library/database2.py:1687
+#: /home/kovid/work/calibre/src/calibre/library/database2.py:1675
+#: /home/kovid/work/calibre/src/calibre/library/database2.py:1693
msgid "Catalog"
msgstr "Catalogue"
@@ -838,23 +839,23 @@ msgstr ""
"Liste des répertoires séparés par une virgule utilisés pour envoyer les "
"livres électroniques à l'appareil. Le premier qui existe sera utilisé."
-#: /home/kovid/work/calibre/src/calibre/devices/hanvon/driver.py:18
+#: /home/kovid/work/calibre/src/calibre/devices/hanvon/driver.py:22
msgid "Communicate with the Hanvon N520 eBook reader."
msgstr "Communiquer avec le lecteur d'ebook Hanvon N520."
-#: /home/kovid/work/calibre/src/calibre/devices/hanvon/driver.py:40
+#: /home/kovid/work/calibre/src/calibre/devices/hanvon/driver.py:47
msgid "Communicate with The Book reader."
msgstr "Communiquer avec le lecteur The Book"
-#: /home/kovid/work/calibre/src/calibre/devices/hanvon/driver.py:52
+#: /home/kovid/work/calibre/src/calibre/devices/hanvon/driver.py:59
msgid "Communicate with the SpringDesign Alex eBook reader."
msgstr "Communiquer avec le lecteur d'ebook Alex de SpringDesign"
-#: /home/kovid/work/calibre/src/calibre/devices/hanvon/driver.py:68
+#: /home/kovid/work/calibre/src/calibre/devices/hanvon/driver.py:78
msgid "Communicate with the Azbooka"
msgstr "Communiquer avec l'Azbooka"
-#: /home/kovid/work/calibre/src/calibre/devices/hanvon/driver.py:81
+#: /home/kovid/work/calibre/src/calibre/devices/hanvon/driver.py:94
msgid "Communicate with the Elonex EB 511 eBook reader."
msgstr "Communiquer avec le lecteur d'ebook Elonex EB 511."
@@ -915,7 +916,7 @@ msgstr ""
#: /home/kovid/work/calibre/src/calibre/devices/kobo/driver.py:63
#: /home/kovid/work/calibre/src/calibre/devices/kobo/driver.py:66
#: /home/kovid/work/calibre/src/calibre/devices/kobo/driver.py:69
-#: /home/kovid/work/calibre/src/calibre/devices/kobo/driver.py:186
+#: /home/kovid/work/calibre/src/calibre/devices/kobo/driver.py:188
#: /home/kovid/work/calibre/src/calibre/devices/usbms/driver.py:68
#: /home/kovid/work/calibre/src/calibre/devices/usbms/driver.py:71
#: /home/kovid/work/calibre/src/calibre/devices/usbms/driver.py:74
@@ -925,33 +926,33 @@ msgstr ""
msgid "Getting list of books on device..."
msgstr "Lit la liste des livres de l'appareil..."
-#: /home/kovid/work/calibre/src/calibre/devices/kobo/driver.py:246
-#: /home/kovid/work/calibre/src/calibre/devices/kobo/driver.py:278
+#: /home/kovid/work/calibre/src/calibre/devices/kobo/driver.py:248
+#: /home/kovid/work/calibre/src/calibre/devices/kobo/driver.py:280
#: /home/kovid/work/calibre/src/calibre/devices/usbms/driver.py:253
#: /home/kovid/work/calibre/src/calibre/devices/usbms/driver.py:271
msgid "Removing books from device..."
msgstr "Supprime les livres de l'appareil..."
-#: /home/kovid/work/calibre/src/calibre/devices/kobo/driver.py:282
-#: /home/kovid/work/calibre/src/calibre/devices/kobo/driver.py:289
+#: /home/kovid/work/calibre/src/calibre/devices/kobo/driver.py:284
+#: /home/kovid/work/calibre/src/calibre/devices/kobo/driver.py:291
#: /home/kovid/work/calibre/src/calibre/devices/usbms/driver.py:278
#: /home/kovid/work/calibre/src/calibre/devices/usbms/driver.py:283
msgid "Removing books from device metadata listing..."
msgstr "Supprime les livres de la liste des métadonnées de l'appareil..."
-#: /home/kovid/work/calibre/src/calibre/devices/kobo/driver.py:294
-#: /home/kovid/work/calibre/src/calibre/devices/kobo/driver.py:328
+#: /home/kovid/work/calibre/src/calibre/devices/kobo/driver.py:296
+#: /home/kovid/work/calibre/src/calibre/devices/kobo/driver.py:330
#: /home/kovid/work/calibre/src/calibre/devices/usbms/driver.py:217
#: /home/kovid/work/calibre/src/calibre/devices/usbms/driver.py:247
msgid "Adding books to device metadata listing..."
msgstr "Ajoute les livres à liste des métadonnées de l'appareil..."
-#: /home/kovid/work/calibre/src/calibre/devices/kobo/driver.py:390
+#: /home/kovid/work/calibre/src/calibre/devices/kobo/driver.py:392
#: /home/kovid/work/calibre/src/calibre/gui2/actions/add.py:252
msgid "Not Implemented"
msgstr "Non implémenté"
-#: /home/kovid/work/calibre/src/calibre/devices/kobo/driver.py:391
+#: /home/kovid/work/calibre/src/calibre/devices/kobo/driver.py:393
msgid ""
"\".kobo\" files do not exist on the device as books instead, they are rows "
"in the sqlite database. Currently they cannot be exported or viewed."
@@ -3466,7 +3467,7 @@ msgstr "Copier"
msgid "Copy to Clipboard"
msgstr "Copier dans le Presse-papiers"
-#: /home/kovid/work/calibre/src/calibre/gui2/__init__.py:434
+#: /home/kovid/work/calibre/src/calibre/gui2/__init__.py:462
msgid "Choose Files"
msgstr "Choisir les fichiers"
@@ -3599,6 +3600,7 @@ msgstr "Ajouter à la bibliothèqye"
#: /home/kovid/work/calibre/src/calibre/gui2/actions/add.py:240
#: /home/kovid/work/calibre/src/calibre/gui2/actions/delete.py:56
+#: /home/kovid/work/calibre/src/calibre/gui2/actions/tweak_epub.py:28
#: /home/kovid/work/calibre/src/calibre/gui2/actions/view.py:95
#: /home/kovid/work/calibre/src/calibre/gui2/actions/view.py:120
msgid "No book selected"
@@ -3648,8 +3650,8 @@ msgstr ""
#: /home/kovid/work/calibre/src/calibre/gui2/actions/copy_to_library.py:116
#: /home/kovid/work/calibre/src/calibre/gui2/actions/edit_metadata.py:76
#: /home/kovid/work/calibre/src/calibre/gui2/actions/edit_metadata.py:142
-#: /home/kovid/work/calibre/src/calibre/gui2/actions/edit_metadata.py:178
-#: /home/kovid/work/calibre/src/calibre/gui2/actions/edit_metadata.py:205
+#: /home/kovid/work/calibre/src/calibre/gui2/actions/edit_metadata.py:180
+#: /home/kovid/work/calibre/src/calibre/gui2/actions/edit_metadata.py:208
#: /home/kovid/work/calibre/src/calibre/gui2/actions/save_to_disk.py:92
msgid "No books selected"
msgstr "Aucun livre sélectionné"
@@ -3808,7 +3810,7 @@ msgstr ""
#: /home/kovid/work/calibre/src/calibre/gui2/actions/choose_library.py:249
#: /home/kovid/work/calibre/src/calibre/gui2/actions/choose_library.py:254
#: /home/kovid/work/calibre/src/calibre/gui2/actions/save_to_disk.py:101
-#: /home/kovid/work/calibre/src/calibre/gui2/library/views.py:554
+#: /home/kovid/work/calibre/src/calibre/gui2/library/views.py:584
msgid "Not allowed"
msgstr "Interdit"
@@ -3878,7 +3880,7 @@ msgstr "IImposible de copier les livres: "
#: /home/kovid/work/calibre/src/calibre/gui2/actions/copy_to_library.py:138
#: /home/kovid/work/calibre/src/calibre/gui2/device.py:670
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk.py:424
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk.py:428
msgid "Failed"
msgstr "Echec"
@@ -4121,20 +4123,20 @@ msgid "Error"
msgstr "Erreur"
#: /home/kovid/work/calibre/src/calibre/gui2/actions/edit_metadata.py:141
-#: /home/kovid/work/calibre/src/calibre/gui2/actions/edit_metadata.py:177
+#: /home/kovid/work/calibre/src/calibre/gui2/actions/edit_metadata.py:179
msgid "Cannot edit metadata"
msgstr "Impossible d'éditer les métadonnées"
-#: /home/kovid/work/calibre/src/calibre/gui2/actions/edit_metadata.py:204
#: /home/kovid/work/calibre/src/calibre/gui2/actions/edit_metadata.py:207
+#: /home/kovid/work/calibre/src/calibre/gui2/actions/edit_metadata.py:210
msgid "Cannot merge books"
msgstr "Impossible de fusionner les livres"
-#: /home/kovid/work/calibre/src/calibre/gui2/actions/edit_metadata.py:208
+#: /home/kovid/work/calibre/src/calibre/gui2/actions/edit_metadata.py:211
msgid "At least two books must be selected for merging"
msgstr "Au moins deux livres doivent être séléctionnés avant de fusionner"
-#: /home/kovid/work/calibre/src/calibre/gui2/actions/edit_metadata.py:212
+#: /home/kovid/work/calibre/src/calibre/gui2/actions/edit_metadata.py:215
msgid ""
"Book formats and metadata from the selected books will be added to the "
"first selected book. ISBN will not be merged.
The "
@@ -4142,7 +4144,7 @@ msgid ""
"changed.
Please confirm you want to proceed."
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/gui2/actions/edit_metadata.py:224
+#: /home/kovid/work/calibre/src/calibre/gui2/actions/edit_metadata.py:227
msgid ""
"Book formats and metadata from the selected books will be merged into the "
"first selected book. ISBN will not be merged.
After "
@@ -4153,7 +4155,7 @@ msgid ""
"you want to proceed?"
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/gui2/actions/edit_metadata.py:237
+#: /home/kovid/work/calibre/src/calibre/gui2/actions/edit_metadata.py:240
msgid ""
"You are about to merge more than 5 books. Are you sure you want to "
"proceed?"
@@ -4343,6 +4345,28 @@ msgstr "Alt+T"
msgid "Books with the same tags"
msgstr "Livres avec les mêmes étiquettes"
+#: /home/kovid/work/calibre/src/calibre/gui2/actions/tweak_epub.py:15
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/tweak_epub_ui.py:54
+msgid "Tweak ePub"
+msgstr ""
+
+#: /home/kovid/work/calibre/src/calibre/gui2/actions/tweak_epub.py:16
+msgid "Make small changes to ePub format books"
+msgstr ""
+
+#: /home/kovid/work/calibre/src/calibre/gui2/actions/tweak_epub.py:17
+msgid "T"
+msgstr ""
+
+#: /home/kovid/work/calibre/src/calibre/gui2/actions/tweak_epub.py:27
+#: /home/kovid/work/calibre/src/calibre/gui2/actions/tweak_epub.py:39
+msgid "Cannot tweak ePub"
+msgstr ""
+
+#: /home/kovid/work/calibre/src/calibre/gui2/actions/tweak_epub.py:40
+msgid "No ePub available. First convert the book to ePub."
+msgstr ""
+
#: /home/kovid/work/calibre/src/calibre/gui2/actions/view.py:24
msgid "V"
msgstr "V"
@@ -4411,7 +4435,7 @@ msgid "The specified directory could not be processed."
msgstr "Le chemin spécifié ne peut pas être traité."
#: /home/kovid/work/calibre/src/calibre/gui2/add.py:228
-#: /home/kovid/work/calibre/src/calibre/gui2/device.py:807
+#: /home/kovid/work/calibre/src/calibre/gui2/device.py:811
msgid "No books"
msgstr "Aucun livre"
@@ -4551,13 +4575,13 @@ msgstr ""
#: /home/kovid/work/calibre/src/calibre/gui2/device_drivers/configwidget_ui.py:84
#: /home/kovid/work/calibre/src/calibre/gui2/device_drivers/configwidget_ui.py:85
#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/choose_library_ui.py:77
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single_ui.py:369
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single_ui.py:376
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single_ui.py:390
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single_ui.py:401
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single_ui.py:403
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single_ui.py:405
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single_ui.py:375
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single_ui.py:382
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single_ui.py:396
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single_ui.py:407
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single_ui.py:409
#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single_ui.py:411
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single_ui.py:418
#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/saved_search_editor_ui.py:92
#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/saved_search_editor_ui.py:95
#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/tag_categories_ui.py:161
@@ -5347,7 +5371,7 @@ msgid " is not a valid picture"
msgstr " n'est pas une image compatible"
#: /home/kovid/work/calibre/src/calibre/gui2/convert/metadata_ui.py:172
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single_ui.py:407
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single_ui.py:413
msgid "Book Cover"
msgstr "Couverture du livre"
@@ -5356,7 +5380,7 @@ msgid "Use cover from &source file"
msgstr "Utiliser la couverture du fichier &source"
#: /home/kovid/work/calibre/src/calibre/gui2/convert/metadata_ui.py:174
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single_ui.py:408
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single_ui.py:414
msgid "Change &cover image:"
msgstr "Modifier l'image de la &couverture:"
@@ -5365,18 +5389,18 @@ msgid "Browse for an image to use as the cover of this book."
msgstr "Rechercher une image à utiliser en tant que couverture du livre."
#: /home/kovid/work/calibre/src/calibre/gui2/convert/metadata_ui.py:177
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single_ui.py:366
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single_ui.py:372
msgid "&Title: "
msgstr "&Titre : "
#: /home/kovid/work/calibre/src/calibre/gui2/convert/metadata_ui.py:178
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single_ui.py:367
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single_ui.py:373
msgid "Change the title of this book"
msgstr "Modifie le titre du livre"
#: /home/kovid/work/calibre/src/calibre/gui2/convert/metadata_ui.py:179
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:229
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single_ui.py:370
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:230
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single_ui.py:376
msgid "&Author(s): "
msgstr "&Auteur(s): "
@@ -5393,19 +5417,19 @@ msgstr ""
"séparer leurs noms par une virgule."
#: /home/kovid/work/calibre/src/calibre/gui2/convert/metadata_ui.py:182
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:238
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single_ui.py:381
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:239
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single_ui.py:387
msgid "&Publisher: "
msgstr "&Editeur: "
#: /home/kovid/work/calibre/src/calibre/gui2/convert/metadata_ui.py:183
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single_ui.py:382
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single_ui.py:388
msgid "Ta&gs: "
msgstr "Eti&quettes: "
#: /home/kovid/work/calibre/src/calibre/gui2/convert/metadata_ui.py:184
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:240
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single_ui.py:383
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:241
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single_ui.py:389
msgid ""
"Tags categorize the book. This is particularly useful while searching. "
"
They can be any words or phrases, separated by commas."
@@ -5415,22 +5439,22 @@ msgstr ""
"ou phrases, séparés par des virgules."
#: /home/kovid/work/calibre/src/calibre/gui2/convert/metadata_ui.py:185
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:247
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single_ui.py:386
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:248
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single_ui.py:392
msgid "&Series:"
msgstr "&Séries:"
#: /home/kovid/work/calibre/src/calibre/gui2/convert/metadata_ui.py:186
#: /home/kovid/work/calibre/src/calibre/gui2/convert/metadata_ui.py:187
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:248
#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:249
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single_ui.py:387
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single_ui.py:388
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:250
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single_ui.py:393
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single_ui.py:394
msgid "List of known series. You can add new series."
msgstr "Liste de séries connues. Vous pouvez ajouter de nouvelles séries."
#: /home/kovid/work/calibre/src/calibre/gui2/convert/metadata_ui.py:188
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single_ui.py:393
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single_ui.py:399
msgid "Book "
msgstr "Livre "
@@ -5937,7 +5961,7 @@ msgid " index:"
msgstr " index:"
#: /home/kovid/work/calibre/src/calibre/gui2/custom_column_widgets.py:451
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:256
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:257
msgid "Automatically number books in this series"
msgstr "Numérote automatiquement les livres dans ces séries"
@@ -6052,128 +6076,128 @@ msgstr ""
"lecteur électronique. Veuillez déconnecter et reconnecter le lecteur "
"électronique et redémarrer."
-#: /home/kovid/work/calibre/src/calibre/gui2/device.py:716
+#: /home/kovid/work/calibre/src/calibre/gui2/device.py:720
msgid "Device: "
msgstr "Appareil: "
-#: /home/kovid/work/calibre/src/calibre/gui2/device.py:718
+#: /home/kovid/work/calibre/src/calibre/gui2/device.py:722
msgid " detected."
msgstr " detecté."
-#: /home/kovid/work/calibre/src/calibre/gui2/device.py:808
+#: /home/kovid/work/calibre/src/calibre/gui2/device.py:812
msgid "selected to send"
msgstr "sélectionné pour l'envoi"
-#: /home/kovid/work/calibre/src/calibre/gui2/device.py:813
+#: /home/kovid/work/calibre/src/calibre/gui2/device.py:817
msgid "Choose format to send to device"
msgstr "Choisir le format à envoyer au lecteur"
-#: /home/kovid/work/calibre/src/calibre/gui2/device.py:822
+#: /home/kovid/work/calibre/src/calibre/gui2/device.py:826
msgid "No device"
msgstr "Aucun appareil"
-#: /home/kovid/work/calibre/src/calibre/gui2/device.py:823
+#: /home/kovid/work/calibre/src/calibre/gui2/device.py:827
msgid "Cannot send: No device is connected"
msgstr "Impossible d'envoyer: Aucun appareil connecté"
-#: /home/kovid/work/calibre/src/calibre/gui2/device.py:826
#: /home/kovid/work/calibre/src/calibre/gui2/device.py:830
+#: /home/kovid/work/calibre/src/calibre/gui2/device.py:834
msgid "No card"
msgstr "Aucune carte"
-#: /home/kovid/work/calibre/src/calibre/gui2/device.py:827
#: /home/kovid/work/calibre/src/calibre/gui2/device.py:831
+#: /home/kovid/work/calibre/src/calibre/gui2/device.py:835
msgid "Cannot send: Device has no storage card"
msgstr "Impossible d'envoyer: L'appareil n'a pas de carte mémoire"
-#: /home/kovid/work/calibre/src/calibre/gui2/device.py:872
+#: /home/kovid/work/calibre/src/calibre/gui2/device.py:876
msgid "E-book:"
msgstr "E-book:"
-#: /home/kovid/work/calibre/src/calibre/gui2/device.py:875
+#: /home/kovid/work/calibre/src/calibre/gui2/device.py:879
msgid "Attached, you will find the e-book"
msgstr "En pièce jointe, vous trouverez l'ebook"
-#: /home/kovid/work/calibre/src/calibre/gui2/device.py:876
+#: /home/kovid/work/calibre/src/calibre/gui2/device.py:880
#: /home/kovid/work/calibre/src/calibre/gui2/preferences/plugins.py:107
msgid "by"
msgstr "par"
-#: /home/kovid/work/calibre/src/calibre/gui2/device.py:877
+#: /home/kovid/work/calibre/src/calibre/gui2/device.py:881
msgid "in the %s format."
msgstr "dans le format %s."
-#: /home/kovid/work/calibre/src/calibre/gui2/device.py:890
+#: /home/kovid/work/calibre/src/calibre/gui2/device.py:894
msgid "Sending email to"
msgstr "Envoi d'un email à"
-#: /home/kovid/work/calibre/src/calibre/gui2/device.py:920
-#: /home/kovid/work/calibre/src/calibre/gui2/device.py:928
-#: /home/kovid/work/calibre/src/calibre/gui2/device.py:1021
-#: /home/kovid/work/calibre/src/calibre/gui2/device.py:1083
-#: /home/kovid/work/calibre/src/calibre/gui2/device.py:1202
-#: /home/kovid/work/calibre/src/calibre/gui2/device.py:1210
+#: /home/kovid/work/calibre/src/calibre/gui2/device.py:924
+#: /home/kovid/work/calibre/src/calibre/gui2/device.py:932
+#: /home/kovid/work/calibre/src/calibre/gui2/device.py:1025
+#: /home/kovid/work/calibre/src/calibre/gui2/device.py:1087
+#: /home/kovid/work/calibre/src/calibre/gui2/device.py:1206
+#: /home/kovid/work/calibre/src/calibre/gui2/device.py:1214
msgid "No suitable formats"
msgstr "Pas de format convenable"
-#: /home/kovid/work/calibre/src/calibre/gui2/device.py:921
+#: /home/kovid/work/calibre/src/calibre/gui2/device.py:925
msgid "Auto convert the following books before sending via email?"
msgstr ""
"Convertir automatiquement les livres suivants avant de les envoyer par email "
"?"
-#: /home/kovid/work/calibre/src/calibre/gui2/device.py:929
+#: /home/kovid/work/calibre/src/calibre/gui2/device.py:933
msgid ""
"Could not email the following books as no suitable formats were found:"
msgstr ""
"Impossible d'envoyer par email les livres suivants car aucun format "
"convenable n'a été trouvé :"
-#: /home/kovid/work/calibre/src/calibre/gui2/device.py:947
+#: /home/kovid/work/calibre/src/calibre/gui2/device.py:951
msgid "Failed to email books"
msgstr "L'envoi par email des livres a échoué"
-#: /home/kovid/work/calibre/src/calibre/gui2/device.py:948
+#: /home/kovid/work/calibre/src/calibre/gui2/device.py:952
msgid "Failed to email the following books:"
msgstr "L'envoi par email des livres suivants a échoué:"
-#: /home/kovid/work/calibre/src/calibre/gui2/device.py:952
+#: /home/kovid/work/calibre/src/calibre/gui2/device.py:956
msgid "Sent by email:"
msgstr "Envoyer par email:"
-#: /home/kovid/work/calibre/src/calibre/gui2/device.py:980
+#: /home/kovid/work/calibre/src/calibre/gui2/device.py:984
msgid "News:"
msgstr "News:"
-#: /home/kovid/work/calibre/src/calibre/gui2/device.py:981
+#: /home/kovid/work/calibre/src/calibre/gui2/device.py:985
msgid "Attached is the"
msgstr "Le fichier attaché est"
-#: /home/kovid/work/calibre/src/calibre/gui2/device.py:992
+#: /home/kovid/work/calibre/src/calibre/gui2/device.py:996
msgid "Sent news to"
msgstr "Envoi des News vers"
-#: /home/kovid/work/calibre/src/calibre/gui2/device.py:1022
-#: /home/kovid/work/calibre/src/calibre/gui2/device.py:1084
-#: /home/kovid/work/calibre/src/calibre/gui2/device.py:1203
+#: /home/kovid/work/calibre/src/calibre/gui2/device.py:1026
+#: /home/kovid/work/calibre/src/calibre/gui2/device.py:1088
+#: /home/kovid/work/calibre/src/calibre/gui2/device.py:1207
msgid "Auto convert the following books before uploading to the device?"
msgstr ""
"Convertir automatiquement les livres suivants avant de les télécharger dans "
"l'appareil ?"
-#: /home/kovid/work/calibre/src/calibre/gui2/device.py:1052
+#: /home/kovid/work/calibre/src/calibre/gui2/device.py:1056
msgid "Sending catalogs to device."
msgstr "Envoie les catalogues vers l'appareil."
-#: /home/kovid/work/calibre/src/calibre/gui2/device.py:1116
+#: /home/kovid/work/calibre/src/calibre/gui2/device.py:1120
msgid "Sending news to device."
msgstr "Envoi les News vers l'appareil."
-#: /home/kovid/work/calibre/src/calibre/gui2/device.py:1169
+#: /home/kovid/work/calibre/src/calibre/gui2/device.py:1173
msgid "Sending books to device."
msgstr "Envoie les livres dans l'appareil."
-#: /home/kovid/work/calibre/src/calibre/gui2/device.py:1211
+#: /home/kovid/work/calibre/src/calibre/gui2/device.py:1215
msgid ""
"Could not upload the following books to the device, as no suitable formats "
"were found. Convert the book(s) to a format supported by your device first."
@@ -6182,11 +6206,11 @@ msgstr ""
"convenable n'a été trouvé. Convertissez avant le(s) livre(s) vers un format "
"supporté par votre appareil."
-#: /home/kovid/work/calibre/src/calibre/gui2/device.py:1273
+#: /home/kovid/work/calibre/src/calibre/gui2/device.py:1277
msgid "No space on device"
msgstr "Le lecteur électronique n'a plus d'espace mémoire disponible"
-#: /home/kovid/work/calibre/src/calibre/gui2/device.py:1274
+#: /home/kovid/work/calibre/src/calibre/gui2/device.py:1278
msgid ""
"
Cannot upload books to device there is no more free space available "
msgstr ""
@@ -6247,7 +6271,7 @@ msgid "My Books"
msgstr "Mes Livres"
#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/catalog_ui.py:69
-#: /home/kovid/work/calibre/src/calibre/gui2/tools.py:297
+#: /home/kovid/work/calibre/src/calibre/gui2/tools.py:301
msgid "Generate catalog"
msgstr "Générer le catalogue"
@@ -6590,32 +6614,32 @@ msgstr ""
"revenir en arrière. Vous êtes fortement encouragé à sauvegarder votre "
"bibliothèque avant de continuer."
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk.py:382
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk.py:386
msgid "Search/replace invalid"
msgstr "Recherche/Remplacement erroné"
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk.py:383
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk.py:387
msgid "Search pattern is invalid: %s"
msgstr "Motif de recherche erroné: %s"
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk.py:415
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk.py:419
msgid "Applying changes to %d books. This may take a while."
msgstr ""
"Application des modifications à %d livres. Ceci peut prendre un moment."
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:228
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:229
msgid "Edit Meta information"
msgstr "Editer les informations sur les Métadonnées"
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:230
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:231
msgid "A&utomatically set author sort"
msgstr "Effectuer un tri a&utomatique par auteur"
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:231
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:232
msgid "Author s&ort: "
msgstr "&Tri par auteur: "
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:232
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:233
msgid ""
"Specify how the author(s) of this book should be sorted. For example Charles "
"Dickens should be sorted as Dickens, Charles."
@@ -6623,64 +6647,64 @@ msgstr ""
"Définit comment l'auteur de ce livre doit être classé. Par exemple, Charles "
"Dickens peut être classé comme Dickens, Charles."
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:233
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single_ui.py:377
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:234
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single_ui.py:383
msgid "&Rating:"
msgstr "&Note :"
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:234
#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:235
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single_ui.py:378
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single_ui.py:379
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:236
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single_ui.py:384
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single_ui.py:385
msgid "Rating of this book. 0-5 stars"
msgstr "Note de ce livre. de 0 à 5 étoiles"
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:236
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:237
msgid "No change"
msgstr "Aucune modification"
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:237
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single_ui.py:380
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:238
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single_ui.py:386
msgid " stars"
msgstr " étoiles"
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:239
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:240
msgid "Add ta&gs: "
msgstr "Ajouter des &étiquettes: "
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:241
#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:242
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single_ui.py:384
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single_ui.py:385
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:243
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single_ui.py:390
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single_ui.py:391
msgid "Open Tag Editor"
msgstr "Ouvre l'éditeur de mots-clefs"
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:243
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:244
msgid "&Remove tags:"
msgstr "&Supprime les étiquettes :"
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:244
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:245
msgid "Comma separated list of tags to remove from the books. "
msgstr ""
"Liste d'étiquettes séparées par des virgules à supprimer des livres. "
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:245
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:246
msgid "Check this box to remove all tags from the books."
msgstr "Cocher cette cas pour supprimer toutes les étiquettes des livres."
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:246
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:247
msgid "Remove all"
msgstr "Tout supprimer"
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:250
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:251
msgid "Remove &format:"
msgstr "Supprimer le &format:"
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:251
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:252
msgid "&Swap title and author"
msgstr "&Intervertir le titre et l'auteur"
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:252
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:253
msgid ""
"Selected books will be automatically numbered,\n"
"in the order you selected them.\n"
@@ -6692,7 +6716,7 @@ msgstr ""
"Ainsi si vous avez sélectionné le livre A et puis le livre B,\n"
"le livre A aura un numéro de série 1 et le livre B un numéro de série 2."
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:257
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:258
msgid ""
"Remove stored conversion settings for the selected books.\n"
"\n"
@@ -6702,51 +6726,51 @@ msgstr ""
"\n"
"Toute conversion future de ces livres utilisera les paramètres par défaut."
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:260
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:261
msgid "Remove &stored conversion settings for the selected books"
msgstr ""
"Supprimer les paramètres de conversion &enregistrés pour les livres "
"sélectionnés"
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:261
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single_ui.py:415
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:262
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single_ui.py:422
msgid "&Basic metadata"
msgstr "Metadonnées de &base"
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:262
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single_ui.py:416
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:263
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single_ui.py:423
msgid "&Custom metadata"
msgstr "Metadonnées &personnalisées"
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:263
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:264
msgid "Search &field:"
msgstr "&Champs de recherche:"
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:264
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:265
msgid "&Search for:"
msgstr "&Rechercher:"
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:265
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:266
msgid "&Replace with:"
msgstr "Re&mplacer par:"
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:266
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:267
msgid "Apply function &after replace:"
msgstr "Appliquer la fonction &après remplacement:"
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:267
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:268
msgid "Test &text"
msgstr "&Texte de test"
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:268
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:269
msgid "Test re&sult"
msgstr "Ré&sultat de test"
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:269
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:270
msgid "Your test:"
msgstr "Votre test:"
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:270
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:271
msgid "&Search and replace (experimental)"
msgstr "&Rechercher et remplacer (experimental)"
@@ -6805,7 +6829,7 @@ msgstr "Impossible de lire la couverture à partir du format %s"
msgid "The cover in the %s format is invalid"
msgstr "La couverture au format %s est incompatible"
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single.py:333
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single.py:351
msgid ""
" The green color indicates that the current author sort matches the current "
"author"
@@ -6813,7 +6837,7 @@ msgstr ""
" La couleur verte indique que la clé de tri par auteur actuelle correspond à "
"l'auteur"
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single.py:336
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single.py:354
msgid ""
" The red color indicates that the current author sort does not match the "
"current author"
@@ -6821,116 +6845,116 @@ msgstr ""
" La couleur rouge indique que la clé de tri par auteur actuelle ne "
"correspond pas à l'auteur"
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single.py:341
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single.py:359
msgid "Abort the editing of all remaining books"
msgstr "Abandonner l'édition des livres restants"
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single.py:505
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single.py:510
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single.py:524
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single.py:529
msgid "This ISBN number is valid"
msgstr "Le numéro ISBN est correct"
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single.py:513
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single.py:532
msgid "This ISBN number is invalid"
msgstr "Le numéro ISBN est incorrect"
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single.py:592
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single.py:611
msgid "Cannot use tag editor"
msgstr "Editeur d'étiquettes indisponible"
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single.py:593
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single.py:612
msgid "The tags editor cannot be used if you have modified the tags"
msgstr ""
"L'éditeur d'étiquettes ne peut pas être utilisé si vous avez modifié les "
"étiquettes"
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single.py:613
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single.py:632
msgid "Downloading cover..."
msgstr "Télécharge la couverture..."
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single.py:625
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single.py:630
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single.py:636
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single.py:641
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single.py:644
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single.py:649
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single.py:655
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single.py:660
msgid "Cannot fetch cover"
msgstr "Erreur à la récupération de l'image de couverture"
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single.py:626
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single.py:637
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single.py:642
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single.py:645
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single.py:656
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single.py:661
msgid "Could not fetch cover. "
msgstr "Erreur à la récupération de l'image de couverture. "
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single.py:627
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single.py:646
msgid "The download timed out."
msgstr "Timeout lors du téléchargement."
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single.py:631
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single.py:650
msgid "Could not find cover for this book. Try specifying the ISBN first."
msgstr ""
"Impossible de trouver la couverture de ce livre. Essayez déjà de spécifier "
"le numéro ISBN."
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single.py:643
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single.py:662
msgid ""
"For the error message from each cover source, click Show details below."
msgstr ""
"Pour obtenir le message d'erreur pour chaque fournisseur de couvertures, "
"cliquer sur Afficher les détails ci-dessous"
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single.py:650
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single.py:669
msgid "Bad cover"
msgstr "Mauvaise couverture"
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single.py:651
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single.py:670
msgid "The cover is not a valid picture"
msgstr "La couverture n'est pas une image"
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single.py:684
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single.py:703
msgid "There were errors"
msgstr "Il y a eu des erreurs"
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single.py:685
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single.py:704
msgid "There were errors downloading social metadata"
msgstr ""
"Il y a eu des erreurs lors du téléchargement des métadonnées sociales"
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single.py:714
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single.py:733
msgid "Cannot fetch metadata"
msgstr "Impossible de récupérer les métadonnées"
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single.py:715
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single.py:734
msgid "You must specify at least one of ISBN, Title, Authors or Publisher"
msgstr ""
"Vous devez spécifier au moins un ISBN, un titre, des auteurs ou un éditeur"
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single.py:798
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single.py:820
msgid "Permission denied"
msgstr "Permission refusée"
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single.py:799
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single.py:821
msgid "Could not open %s. Is it being used by another program?"
msgstr ""
"Impossible d'ouvrir %s. Est-il en cours d'utilisation par un autre programme "
"?"
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single_ui.py:364
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single_ui.py:370
msgid "Edit Meta Information"
msgstr "Editer les métadonnées"
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single_ui.py:365
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single_ui.py:371
msgid "Meta information"
msgstr "Informations sur les Métadonnées"
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single_ui.py:368
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single_ui.py:374
msgid "Swap the author and title"
msgstr "Inverse l'auteur et le titre"
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single_ui.py:371
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single_ui.py:377
msgid "Author S&ort: "
msgstr "Clé de tr&i d'auteur: "
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single_ui.py:372
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single_ui.py:378
msgid ""
"Specify how the author(s) of this book should be sorted. For example Charles "
"Dickens should be sorted as Dickens, Charles.\n"
@@ -6938,7 +6962,7 @@ msgid ""
"strings. If it is colored red, then the authors and this text do not match."
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single_ui.py:374
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single_ui.py:380
msgid ""
"Automatically create the author sort entry based on the current author "
"entry.\n"
@@ -6946,73 +6970,77 @@ msgid ""
"green."
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single_ui.py:389
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single_ui.py:395
msgid "Remove unused series (Series that have no books)"
msgstr ""
"Supprimer les séries inutilisées (Les séries qui ne possèdent aucun livres)"
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single_ui.py:391
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single_ui.py:397
msgid "IS&BN:"
msgstr "I&SBN :"
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single_ui.py:392
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single_ui.py:398
msgid "Publishe&d:"
msgstr "Pu&blié:"
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single_ui.py:395
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single_ui.py:401
msgid "dd MMM yyyy"
msgstr "dd MMM yyyy"
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single_ui.py:396
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single_ui.py:402
msgid "&Date:"
msgstr "&Date:"
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single_ui.py:397
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single_ui.py:403
msgid "&Comments"
msgstr "&Commentaires"
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single_ui.py:398
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single_ui.py:404
msgid "&Fetch metadata from server"
msgstr "&Récupérer les métadonnées à partir du serveur"
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single_ui.py:399
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single_ui.py:405
msgid "Available Formats"
msgstr "Formats disponibles"
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single_ui.py:400
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single_ui.py:406
msgid "Add a new format for this book to the database"
msgstr "Ajouter un nouveau format dans la base de données pour ce livre"
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single_ui.py:402
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single_ui.py:408
msgid "Remove the selected formats for this book from the database."
msgstr "Retire les formats sélectionnés de ce livre de la base de données."
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single_ui.py:404
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single_ui.py:410
msgid "Set the cover for the book from the selected format"
msgstr "Indiquer la couverture pour le livre à partir du format sélectionné"
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single_ui.py:406
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single_ui.py:412
msgid "Update metadata from the metadata in the selected format"
msgstr ""
"Mettre à jour les métadonnées à partir des métadonnées du format sélectionné"
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single_ui.py:409
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single_ui.py:415
msgid "&Browse"
msgstr "&Parcourir"
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single_ui.py:410
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single_ui.py:416
+msgid "Remove border (if any) from cover"
+msgstr ""
+
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single_ui.py:417
msgid "Reset cover to default"
msgstr "Revenir à la couverture par défaut"
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single_ui.py:412
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single_ui.py:419
msgid "Download co&ver"
msgstr "Télécharger la cou&verture"
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single_ui.py:413
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single_ui.py:420
msgid "Generate a default cover based on the title and author"
msgstr "Générer une couverture par défaut basée sur le titre et l'auteur"
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single_ui.py:414
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single_ui.py:421
msgid "&Generate cover"
msgstr "&Générer la couverture"
@@ -7540,6 +7568,39 @@ msgstr "Envoyer un email de test de %s vers:"
msgid "&Test"
msgstr "&Test"
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/tweak_epub_ui.py:55
+msgid "Display contents of exploded ePub"
+msgstr ""
+
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/tweak_epub_ui.py:56
+msgid "&Explode ePub"
+msgstr ""
+
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/tweak_epub_ui.py:57
+msgid "Rebuild ePub from exploded contents"
+msgstr ""
+
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/tweak_epub_ui.py:58
+msgid "&Rebuild ePub"
+msgstr ""
+
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/tweak_epub_ui.py:59
+msgid "Discard changes"
+msgstr ""
+
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/tweak_epub_ui.py:60
+#: /home/kovid/work/calibre/src/calibre/gui2/preferences/main.py:218
+msgid "&Cancel"
+msgstr "&Annuler"
+
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/tweak_epub_ui.py:61
+msgid ""
+"Explode the ePub to display contents in a file browser window. To tweak "
+"individual files, right-click, then 'Open with...' your editor of choice. "
+"When tweaks are complete, close the file browser window. Rebuild the ePub, "
+"updating your calibre library."
+msgstr ""
+
#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/user_profiles.py:127
msgid "No recipe selected"
msgstr "Aucune recette sélectionnée"
@@ -7971,7 +8032,7 @@ msgid "Show books in the main memory of the device"
msgstr "Afficher les livres dans mémoire principale de l'appareil"
#: /home/kovid/work/calibre/src/calibre/gui2/layout.py:66
-#: /home/kovid/work/calibre/src/calibre/library/database2.py:656
+#: /home/kovid/work/calibre/src/calibre/library/database2.py:655
msgid "Card A"
msgstr "Carte A"
@@ -7980,7 +8041,7 @@ msgid "Show books in storage card A"
msgstr "Afficher les livres dans la carte mémoire A"
#: /home/kovid/work/calibre/src/calibre/gui2/layout.py:68
-#: /home/kovid/work/calibre/src/calibre/library/database2.py:658
+#: /home/kovid/work/calibre/src/calibre/library/database2.py:657
msgid "Card B"
msgstr "Carte B"
@@ -8112,7 +8173,7 @@ msgstr "Afficher la colonne"
msgid "Restore default layout"
msgstr "Restorer l'affichage par défaut"
-#: /home/kovid/work/calibre/src/calibre/gui2/library/views.py:555
+#: /home/kovid/work/calibre/src/calibre/gui2/library/views.py:585
msgid ""
"Dropping onto a device is not supported. First add the book to the calibre "
"library."
@@ -8897,10 +8958,6 @@ msgstr "&Sépare la barre d'outils en deux bares distinctes"
msgid "&Apply"
msgstr "&Appliquer"
-#: /home/kovid/work/calibre/src/calibre/gui2/preferences/main.py:218
-msgid "&Cancel"
-msgstr "&Annuler"
-
#: /home/kovid/work/calibre/src/calibre/gui2/preferences/main.py:222
msgid "Restore &defaults"
msgstr "Restaurer les valeurs par &défaut"
@@ -9208,7 +9265,7 @@ msgid ""
msgstr ""
#: /home/kovid/work/calibre/src/calibre/gui2/preferences/server.py:75
-#: /home/kovid/work/calibre/src/calibre/gui2/ui.py:319
+#: /home/kovid/work/calibre/src/calibre/gui2/ui.py:320
msgid "Failed to start content server"
msgstr "A échoué lors du démarrage du serveur de contenu"
@@ -9639,15 +9696,15 @@ msgstr "Mise en file d'attente des livres pour la conversion par lot"
msgid "Queueing "
msgstr "Mise en file d'attente "
-#: /home/kovid/work/calibre/src/calibre/gui2/tools.py:239
+#: /home/kovid/work/calibre/src/calibre/gui2/tools.py:243
msgid "Fetch news from "
msgstr "Récupérer des News de "
-#: /home/kovid/work/calibre/src/calibre/gui2/tools.py:309
+#: /home/kovid/work/calibre/src/calibre/gui2/tools.py:313
msgid "Convert existing"
msgstr "Conversion existante"
-#: /home/kovid/work/calibre/src/calibre/gui2/tools.py:310
+#: /home/kovid/work/calibre/src/calibre/gui2/tools.py:314
msgid ""
"The following books have already been converted to %s format. Do you wish to "
"reconvert them?"
@@ -9655,28 +9712,28 @@ msgstr ""
"Les fichiers suivants ont déjà été convertis au format %s. Souhaitez-vous "
"les reconvertir ?"
-#: /home/kovid/work/calibre/src/calibre/gui2/ui.py:167
+#: /home/kovid/work/calibre/src/calibre/gui2/ui.py:168
msgid "&Restore"
msgstr "&Montrer"
-#: /home/kovid/work/calibre/src/calibre/gui2/ui.py:169
+#: /home/kovid/work/calibre/src/calibre/gui2/ui.py:170
msgid "&Donate to support calibre"
msgstr "&Donner pour supporter Calibre"
-#: /home/kovid/work/calibre/src/calibre/gui2/ui.py:173
+#: /home/kovid/work/calibre/src/calibre/gui2/ui.py:174
msgid "&Eject connected device"
msgstr "&Ejecter l'appareil connecté"
-#: /home/kovid/work/calibre/src/calibre/gui2/ui.py:215
+#: /home/kovid/work/calibre/src/calibre/gui2/ui.py:216
msgid "Calibre Quick Start Guide"
msgstr "Guide De Démarrage Rapide Calibre"
-#: /home/kovid/work/calibre/src/calibre/gui2/ui.py:417
-#: /home/kovid/work/calibre/src/calibre/gui2/ui.py:445
+#: /home/kovid/work/calibre/src/calibre/gui2/ui.py:418
+#: /home/kovid/work/calibre/src/calibre/gui2/ui.py:446
msgid "Conversion Error"
msgstr "Erreur lors de la conversion"
-#: /home/kovid/work/calibre/src/calibre/gui2/ui.py:418
+#: /home/kovid/work/calibre/src/calibre/gui2/ui.py:419
msgid ""
"
Could not convert: %s
It is a DRMed book. You must "
"first remove the DRM using third party tools."
@@ -9685,15 +9742,15 @@ msgstr ""
"href=\"%s\">DRM. Vous devez d'abord enlever les DRM avec des outils "
"tiers."
-#: /home/kovid/work/calibre/src/calibre/gui2/ui.py:431
+#: /home/kovid/work/calibre/src/calibre/gui2/ui.py:432
msgid "Recipe Disabled"
msgstr "Recette désactivée"
-#: /home/kovid/work/calibre/src/calibre/gui2/ui.py:446
+#: /home/kovid/work/calibre/src/calibre/gui2/ui.py:447
msgid "Failed"
msgstr "Échoué"
-#: /home/kovid/work/calibre/src/calibre/gui2/ui.py:482
+#: /home/kovid/work/calibre/src/calibre/gui2/ui.py:483
msgid ""
"is the result of the efforts of many volunteers from all over the world. If "
"you find it useful, please consider donating to support its development. "
@@ -9703,11 +9760,11 @@ msgstr ""
"le monde. Si vous le trouvez utile, vous pouvez donner pour soutenir son "
"développement. Vos donations aident Calibre à continuer à se développer."
-#: /home/kovid/work/calibre/src/calibre/gui2/ui.py:508
+#: /home/kovid/work/calibre/src/calibre/gui2/ui.py:509
msgid "There are active jobs. Are you sure you want to quit?"
msgstr "Il y a des travaux actifs. Voulez-vous vraiment finir ?"
-#: /home/kovid/work/calibre/src/calibre/gui2/ui.py:511
+#: /home/kovid/work/calibre/src/calibre/gui2/ui.py:512
msgid ""
" is communicating with the device! \n"
" Quitting may cause corruption on the device. \n"
@@ -9718,11 +9775,11 @@ msgstr ""
"l'appareil. \n"
" Êtes-vous sûr de vouloir quitter ?"
-#: /home/kovid/work/calibre/src/calibre/gui2/ui.py:515
+#: /home/kovid/work/calibre/src/calibre/gui2/ui.py:516
msgid "WARNING: Active jobs"
msgstr "ATTENTION: Travaux actifs"
-#: /home/kovid/work/calibre/src/calibre/gui2/ui.py:583
+#: /home/kovid/work/calibre/src/calibre/gui2/ui.py:584
msgid ""
"will keep running in the system tray. To close it, choose Quit in the "
"context menu of the system tray."
@@ -10568,48 +10625,48 @@ msgstr ""
msgid "Turn on the &content server"
msgstr "Démarrer le serveur de &contenu"
-#: /home/kovid/work/calibre/src/calibre/library/caches.py:232
+#: /home/kovid/work/calibre/src/calibre/library/caches.py:234
msgid "today"
msgstr "Aujourd'hui"
-#: /home/kovid/work/calibre/src/calibre/library/caches.py:235
+#: /home/kovid/work/calibre/src/calibre/library/caches.py:237
msgid "yesterday"
msgstr "Hier"
-#: /home/kovid/work/calibre/src/calibre/library/caches.py:238
+#: /home/kovid/work/calibre/src/calibre/library/caches.py:240
msgid "thismonth"
msgstr "Ce mois-ci"
-#: /home/kovid/work/calibre/src/calibre/library/caches.py:241
-#: /home/kovid/work/calibre/src/calibre/library/caches.py:242
+#: /home/kovid/work/calibre/src/calibre/library/caches.py:243
+#: /home/kovid/work/calibre/src/calibre/library/caches.py:244
msgid "daysago"
msgstr "il y a quelques jours"
-#: /home/kovid/work/calibre/src/calibre/library/caches.py:406
-#: /home/kovid/work/calibre/src/calibre/library/caches.py:416
+#: /home/kovid/work/calibre/src/calibre/library/caches.py:408
+#: /home/kovid/work/calibre/src/calibre/library/caches.py:418
msgid "no"
msgstr "Non"
-#: /home/kovid/work/calibre/src/calibre/library/caches.py:406
-#: /home/kovid/work/calibre/src/calibre/library/caches.py:416
+#: /home/kovid/work/calibre/src/calibre/library/caches.py:408
+#: /home/kovid/work/calibre/src/calibre/library/caches.py:418
msgid "unchecked"
msgstr "Non vérifié"
-#: /home/kovid/work/calibre/src/calibre/library/caches.py:409
-#: /home/kovid/work/calibre/src/calibre/library/caches.py:419
+#: /home/kovid/work/calibre/src/calibre/library/caches.py:411
+#: /home/kovid/work/calibre/src/calibre/library/caches.py:421
msgid "checked"
msgstr "Vérifié"
-#: /home/kovid/work/calibre/src/calibre/library/caches.py:409
-#: /home/kovid/work/calibre/src/calibre/library/caches.py:419
+#: /home/kovid/work/calibre/src/calibre/library/caches.py:411
+#: /home/kovid/work/calibre/src/calibre/library/caches.py:421
msgid "yes"
msgstr "Oui"
-#: /home/kovid/work/calibre/src/calibre/library/caches.py:413
+#: /home/kovid/work/calibre/src/calibre/library/caches.py:415
msgid "blank"
msgstr "vierge"
-#: /home/kovid/work/calibre/src/calibre/library/caches.py:413
+#: /home/kovid/work/calibre/src/calibre/library/caches.py:415
msgid "empty"
msgstr "Vide"
@@ -11428,31 +11485,31 @@ msgstr ""
msgid "%sAverage rating is %3.1f"
msgstr "La note moyenne de %sest %3.1f"
-#: /home/kovid/work/calibre/src/calibre/library/database2.py:654
+#: /home/kovid/work/calibre/src/calibre/library/database2.py:653
msgid "Main"
msgstr "Principal"
-#: /home/kovid/work/calibre/src/calibre/library/database2.py:1992
+#: /home/kovid/work/calibre/src/calibre/library/database2.py:1998
msgid "
Migrating old database to ebook library in %s
"
msgstr "
Migre l'ancienne base vers la bibliothèque dans %s
"
-#: /home/kovid/work/calibre/src/calibre/library/database2.py:2021
+#: /home/kovid/work/calibre/src/calibre/library/database2.py:2027
msgid "Copying %s"
msgstr "Copie %s"
-#: /home/kovid/work/calibre/src/calibre/library/database2.py:2038
+#: /home/kovid/work/calibre/src/calibre/library/database2.py:2044
msgid "Compacting database"
msgstr "Compacte la base"
-#: /home/kovid/work/calibre/src/calibre/library/database2.py:2131
+#: /home/kovid/work/calibre/src/calibre/library/database2.py:2137
msgid "Checking SQL integrity..."
msgstr "Vérifie l'intégrité SQL..."
-#: /home/kovid/work/calibre/src/calibre/library/database2.py:2170
+#: /home/kovid/work/calibre/src/calibre/library/database2.py:2176
msgid "Checking for missing files."
msgstr "Vérifie si des fichiers sont manquants."
-#: /home/kovid/work/calibre/src/calibre/library/database2.py:2192
+#: /home/kovid/work/calibre/src/calibre/library/database2.py:2198
msgid "Checked id"
msgstr "Id vérifié"
@@ -11975,63 +12032,63 @@ msgstr ""
"Ne pas télécharger la dernière version des recettes intégrées à partir du "
"server Calibre."
-#: /home/kovid/work/calibre/src/calibre/web/feeds/news.py:46
+#: /home/kovid/work/calibre/src/calibre/web/feeds/news.py:47
msgid "Unknown News Source"
msgstr "Source de News inconnue"
-#: /home/kovid/work/calibre/src/calibre/web/feeds/news.py:611
+#: /home/kovid/work/calibre/src/calibre/web/feeds/news.py:612
msgid "The \"%s\" recipe needs a username and password."
msgstr ""
"La recette \"%s\" a besoin d'un nom d'utilisateur et d'un mot de passe."
-#: /home/kovid/work/calibre/src/calibre/web/feeds/news.py:710
+#: /home/kovid/work/calibre/src/calibre/web/feeds/news.py:711
msgid "Download finished"
msgstr "Téléchargement effectué"
-#: /home/kovid/work/calibre/src/calibre/web/feeds/news.py:712
+#: /home/kovid/work/calibre/src/calibre/web/feeds/news.py:713
msgid "Failed to download the following articles:"
msgstr "Impossible de télécharger les articles suivants;"
-#: /home/kovid/work/calibre/src/calibre/web/feeds/news.py:718
+#: /home/kovid/work/calibre/src/calibre/web/feeds/news.py:719
msgid "Failed to download parts of the following articles:"
msgstr ""
"Impossible de télécharger certaines parties pour les articles suivants:"
-#: /home/kovid/work/calibre/src/calibre/web/feeds/news.py:720
+#: /home/kovid/work/calibre/src/calibre/web/feeds/news.py:721
msgid " from "
msgstr " de "
-#: /home/kovid/work/calibre/src/calibre/web/feeds/news.py:722
+#: /home/kovid/work/calibre/src/calibre/web/feeds/news.py:723
msgid "\tFailed links:"
msgstr "\tLiens qui ont échoués:"
-#: /home/kovid/work/calibre/src/calibre/web/feeds/news.py:811
+#: /home/kovid/work/calibre/src/calibre/web/feeds/news.py:812
msgid "Could not fetch article. Run with -vv to see the reason"
msgstr ""
"Impossible de récupérer l'article. Le lancer avec -w pour en connaitre la "
"raison"
-#: /home/kovid/work/calibre/src/calibre/web/feeds/news.py:832
+#: /home/kovid/work/calibre/src/calibre/web/feeds/news.py:833
msgid "Fetching feeds..."
msgstr "Récupération des flux..."
-#: /home/kovid/work/calibre/src/calibre/web/feeds/news.py:837
+#: /home/kovid/work/calibre/src/calibre/web/feeds/news.py:838
msgid "Got feeds from index page"
msgstr "Obtient les flux à partir de la page d'index"
-#: /home/kovid/work/calibre/src/calibre/web/feeds/news.py:843
+#: /home/kovid/work/calibre/src/calibre/web/feeds/news.py:844
msgid "Trying to download cover..."
msgstr "Essaie de télécharger la couverture..."
-#: /home/kovid/work/calibre/src/calibre/web/feeds/news.py:845
+#: /home/kovid/work/calibre/src/calibre/web/feeds/news.py:846
msgid "Generating masthead..."
msgstr "Génération du masthead"
-#: /home/kovid/work/calibre/src/calibre/web/feeds/news.py:926
+#: /home/kovid/work/calibre/src/calibre/web/feeds/news.py:927
msgid "Starting download [%d thread(s)]..."
msgstr "Commence le téléchargement [processus %d]..."
-#: /home/kovid/work/calibre/src/calibre/web/feeds/news.py:942
+#: /home/kovid/work/calibre/src/calibre/web/feeds/news.py:943
msgid "Feeds downloaded to %s"
msgstr "Flux téléchargés de %s"
@@ -12039,31 +12096,31 @@ msgstr "Flux téléchargés de %s"
msgid "Could not download cover: %s"
msgstr "Impossible de télécharger la couverture: %s"
-#: /home/kovid/work/calibre/src/calibre/web/feeds/news.py:964
+#: /home/kovid/work/calibre/src/calibre/web/feeds/news.py:959
msgid "Downloading cover from %s"
msgstr "Télécharge la couverture de %s"
-#: /home/kovid/work/calibre/src/calibre/web/feeds/news.py:1005
+#: /home/kovid/work/calibre/src/calibre/web/feeds/news.py:1004
msgid "Masthead image downloaded"
msgstr "L'image du titre a été téléchargée"
-#: /home/kovid/work/calibre/src/calibre/web/feeds/news.py:1173
+#: /home/kovid/work/calibre/src/calibre/web/feeds/news.py:1172
msgid "Untitled Article"
msgstr "Article sans titre"
-#: /home/kovid/work/calibre/src/calibre/web/feeds/news.py:1244
+#: /home/kovid/work/calibre/src/calibre/web/feeds/news.py:1243
msgid "Article downloaded: %s"
msgstr "Article téléchargé : %s"
-#: /home/kovid/work/calibre/src/calibre/web/feeds/news.py:1255
+#: /home/kovid/work/calibre/src/calibre/web/feeds/news.py:1254
msgid "Article download failed: %s"
msgstr "Impossible de télécharger l'article: %s"
-#: /home/kovid/work/calibre/src/calibre/web/feeds/news.py:1272
+#: /home/kovid/work/calibre/src/calibre/web/feeds/news.py:1271
msgid "Fetching feed"
msgstr "Récupération du flux"
-#: /home/kovid/work/calibre/src/calibre/web/feeds/news.py:1419
+#: /home/kovid/work/calibre/src/calibre/web/feeds/news.py:1418
msgid ""
"Failed to log in, check your username and password for the calibre "
"Periodicals service."
@@ -12071,7 +12128,7 @@ msgstr ""
"La connexion a échouée, vérifier votre nom d'utilisateur et votre mot de "
"passe pour le service Périodique Calibre."
-#: /home/kovid/work/calibre/src/calibre/web/feeds/news.py:1435
+#: /home/kovid/work/calibre/src/calibre/web/feeds/news.py:1433
msgid ""
"You do not have permission to download this issue. Either your subscription "
"has expired or you have exceeded the maximum allowed downloads for today."
diff --git a/src/calibre/translations/hu.po b/src/calibre/translations/hu.po
index f7b5c36159..345a618292 100644
--- a/src/calibre/translations/hu.po
+++ b/src/calibre/translations/hu.po
@@ -7,14 +7,14 @@ msgid ""
msgstr ""
"Project-Id-Version: calibre\n"
"Report-Msgid-Bugs-To: FULL NAME \n"
-"POT-Creation-Date: 2010-09-03 19:17+0000\n"
-"PO-Revision-Date: 2010-09-03 19:07+0000\n"
-"Last-Translator: Kovid Goyal \n"
+"POT-Creation-Date: 2010-09-24 21:33+0000\n"
+"PO-Revision-Date: 2010-09-24 19:19+0000\n"
+"Last-Translator: Devilinside \n"
"Language-Team: Hungarian \n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
-"X-Launchpad-Export-Date: 2010-09-04 04:39+0000\n"
+"X-Launchpad-Export-Date: 2010-09-25 04:40+0000\n"
"X-Generator: Launchpad (build Unknown)\n"
#: /home/kovid/work/calibre/src/calibre/customize/__init__.py:43
@@ -24,11 +24,12 @@ msgstr "Semmit nem csinál"
#: /home/kovid/work/calibre/src/calibre/customize/__init__.py:46
#: /home/kovid/work/calibre/src/calibre/devices/jetbook/driver.py:74
#: /home/kovid/work/calibre/src/calibre/devices/kindle/driver.py:76
-#: /home/kovid/work/calibre/src/calibre/devices/kobo/driver.py:395
+#: /home/kovid/work/calibre/src/calibre/devices/kobo/books.py:46
+#: /home/kovid/work/calibre/src/calibre/devices/kobo/driver.py:412
#: /home/kovid/work/calibre/src/calibre/devices/nook/driver.py:70
#: /home/kovid/work/calibre/src/calibre/devices/nook/driver.py:71
#: /home/kovid/work/calibre/src/calibre/devices/prs500/books.py:267
-#: /home/kovid/work/calibre/src/calibre/devices/prs505/sony_cache.py:522
+#: /home/kovid/work/calibre/src/calibre/devices/prs505/sony_cache.py:526
#: /home/kovid/work/calibre/src/calibre/devices/usbms/driver.py:405
#: /home/kovid/work/calibre/src/calibre/ebooks/chm/input.py:97
#: /home/kovid/work/calibre/src/calibre/ebooks/chm/input.py:100
@@ -78,7 +79,9 @@ msgstr "Semmit nem csinál"
#: /home/kovid/work/calibre/src/calibre/ebooks/oeb/base.py:982
#: /home/kovid/work/calibre/src/calibre/ebooks/oeb/reader.py:137
#: /home/kovid/work/calibre/src/calibre/ebooks/oeb/reader.py:139
-#: /home/kovid/work/calibre/src/calibre/ebooks/oeb/transforms/jacket.py:108
+#: /home/kovid/work/calibre/src/calibre/ebooks/oeb/transforms/jacket.py:64
+#: /home/kovid/work/calibre/src/calibre/ebooks/oeb/transforms/jacket.py:112
+#: /home/kovid/work/calibre/src/calibre/ebooks/oeb/transforms/jacket.py:118
#: /home/kovid/work/calibre/src/calibre/ebooks/pdb/ereader/writer.py:173
#: /home/kovid/work/calibre/src/calibre/ebooks/pdb/ereader/writer.py:174
#: /home/kovid/work/calibre/src/calibre/ebooks/pdb/input.py:39
@@ -101,43 +104,43 @@ msgstr "Semmit nem csinál"
#: /home/kovid/work/calibre/src/calibre/ebooks/pdf/manipulate/split.py:82
#: /home/kovid/work/calibre/src/calibre/ebooks/pdf/writer.py:97
#: /home/kovid/work/calibre/src/calibre/ebooks/pdf/writer.py:98
-#: /home/kovid/work/calibre/src/calibre/ebooks/rtf/input.py:247
-#: /home/kovid/work/calibre/src/calibre/ebooks/rtf/input.py:249
-#: /home/kovid/work/calibre/src/calibre/gui2/__init__.py:323
-#: /home/kovid/work/calibre/src/calibre/gui2/__init__.py:330
-#: /home/kovid/work/calibre/src/calibre/gui2/actions/edit_metadata.py:289
-#: /home/kovid/work/calibre/src/calibre/gui2/actions/edit_metadata.py:292
+#: /home/kovid/work/calibre/src/calibre/ebooks/rtf/input.py:239
+#: /home/kovid/work/calibre/src/calibre/ebooks/rtf/input.py:241
+#: /home/kovid/work/calibre/src/calibre/gui2/__init__.py:352
+#: /home/kovid/work/calibre/src/calibre/gui2/__init__.py:359
+#: /home/kovid/work/calibre/src/calibre/gui2/actions/edit_metadata.py:296
+#: /home/kovid/work/calibre/src/calibre/gui2/actions/edit_metadata.py:299
#: /home/kovid/work/calibre/src/calibre/gui2/add.py:137
#: /home/kovid/work/calibre/src/calibre/gui2/add.py:144
-#: /home/kovid/work/calibre/src/calibre/gui2/convert/__init__.py:41
+#: /home/kovid/work/calibre/src/calibre/gui2/convert/__init__.py:42
#: /home/kovid/work/calibre/src/calibre/gui2/convert/metadata.py:111
#: /home/kovid/work/calibre/src/calibre/gui2/convert/metadata.py:136
#: /home/kovid/work/calibre/src/calibre/gui2/convert/metadata.py:138
#: /home/kovid/work/calibre/src/calibre/gui2/device.py:869
#: /home/kovid/work/calibre/src/calibre/gui2/device.py:878
-#: /home/kovid/work/calibre/src/calibre/gui2/device.py:1163
-#: /home/kovid/work/calibre/src/calibre/gui2/device.py:1166
+#: /home/kovid/work/calibre/src/calibre/gui2/device.py:1162
+#: /home/kovid/work/calibre/src/calibre/gui2/device.py:1165
#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/comicconf.py:47
#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/fetch_metadata.py:120
#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/fetch_metadata.py:155
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single.py:513
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single.py:571
#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/scheduler.py:173
-#: /home/kovid/work/calibre/src/calibre/gui2/library/models.py:362
-#: /home/kovid/work/calibre/src/calibre/gui2/library/models.py:382
-#: /home/kovid/work/calibre/src/calibre/gui2/library/models.py:882
-#: /home/kovid/work/calibre/src/calibre/gui2/library/models.py:1060
+#: /home/kovid/work/calibre/src/calibre/gui2/library/models.py:357
+#: /home/kovid/work/calibre/src/calibre/gui2/library/models.py:377
+#: /home/kovid/work/calibre/src/calibre/gui2/library/models.py:877
+#: /home/kovid/work/calibre/src/calibre/gui2/library/models.py:1062
#: /home/kovid/work/calibre/src/calibre/gui2/metadata.py:91
#: /home/kovid/work/calibre/src/calibre/gui2/metadata.py:96
-#: /home/kovid/work/calibre/src/calibre/gui2/viewer/main.py:186
+#: /home/kovid/work/calibre/src/calibre/gui2/viewer/main.py:187
#: /home/kovid/work/calibre/src/calibre/library/cli.py:213
#: /home/kovid/work/calibre/src/calibre/library/database.py:913
#: /home/kovid/work/calibre/src/calibre/library/database2.py:375
#: /home/kovid/work/calibre/src/calibre/library/database2.py:387
-#: /home/kovid/work/calibre/src/calibre/library/database2.py:1057
-#: /home/kovid/work/calibre/src/calibre/library/database2.py:1126
-#: /home/kovid/work/calibre/src/calibre/library/database2.py:1825
-#: /home/kovid/work/calibre/src/calibre/library/database2.py:1827
-#: /home/kovid/work/calibre/src/calibre/library/database2.py:1954
+#: /home/kovid/work/calibre/src/calibre/library/database2.py:1064
+#: /home/kovid/work/calibre/src/calibre/library/database2.py:1139
+#: /home/kovid/work/calibre/src/calibre/library/database2.py:1843
+#: /home/kovid/work/calibre/src/calibre/library/database2.py:1845
+#: /home/kovid/work/calibre/src/calibre/library/database2.py:1972
#: /home/kovid/work/calibre/src/calibre/library/server/mobile.py:211
#: /home/kovid/work/calibre/src/calibre/library/server/opds.py:137
#: /home/kovid/work/calibre/src/calibre/library/server/opds.py:140
@@ -171,15 +174,17 @@ msgstr "Metaadat író"
msgid "Catalog generator"
msgstr "Katalógus készítő"
-#: /home/kovid/work/calibre/src/calibre/customize/__init__.py:366
+#: /home/kovid/work/calibre/src/calibre/customize/__init__.py:369
msgid "User Interface Action"
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/customize/__init__.py:376
+#: /home/kovid/work/calibre/src/calibre/customize/__init__.py:386
#: /home/kovid/work/calibre/src/calibre/gui2/actions/preferences.py:17
#: /home/kovid/work/calibre/src/calibre/gui2/actions/preferences.py:22
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/config/config_ui.py:612
-#: /home/kovid/work/calibre/src/calibre/gui2/viewer/main_ui.py:201
+#: /home/kovid/work/calibre/src/calibre/gui2/preferences/main.py:187
+#: /home/kovid/work/calibre/src/calibre/gui2/preferences/main.py:251
+#: /home/kovid/work/calibre/src/calibre/gui2/preferences/main.py:273
+#: /home/kovid/work/calibre/src/calibre/gui2/viewer/main_ui.py:206
msgid "Preferences"
msgstr "Beállítások"
@@ -257,20 +262,165 @@ msgstr "Metaadatok beállítása a %s típusú fájlokban."
msgid "Set metadata from %s files"
msgstr "Metadatok beállítása a következő fájlokból: %s"
-#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:681
+#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:688
msgid "Look and Feel"
-msgstr ""
+msgstr "Megjelenés"
-#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:683
-#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:692
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/config/__init__.py:199
+#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:690
+#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:702
+#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:713
+#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:724
msgid "Interface"
msgstr "Kezelőfelület"
-#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:690
+#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:694
+msgid "Adjust the look and feel of the calibre interface to suit your tastes"
+msgstr "Állítsa be a calibre kezelőfelületét saját ízlésének megfelelően"
+
+#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:700
msgid "Behavior"
msgstr ""
+#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:706
+msgid "Change the way calibre behaves"
+msgstr ""
+
+#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:711
+#: /home/kovid/work/calibre/src/calibre/gui2/library/views.py:176
+msgid "Add your own columns"
+msgstr ""
+
+#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:717
+msgid "Add/remove your own columns to the calibre book list"
+msgstr ""
+
+#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:722
+msgid "Customize the toolbar"
+msgstr "Az eszköztár testreszabása"
+
+#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:728
+msgid ""
+"Customize the toolbars and context menus, changing which actions are "
+"available in each"
+msgstr ""
+
+#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:734
+msgid "Input Options"
+msgstr "Bemeneti beállítások"
+
+#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:736
+#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:747
+#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:758
+msgid "Conversion"
+msgstr "Konvertálás"
+
+#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:740
+msgid "Set conversion options specific to each input format"
+msgstr ""
+
+#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:745
+msgid "Common Options"
+msgstr ""
+
+#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:751
+msgid "Set conversion options common to all formats"
+msgstr ""
+
+#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:756
+msgid "Output Options"
+msgstr ""
+
+#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:762
+msgid "Set conversion options specific to each output format"
+msgstr ""
+
+#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:767
+msgid "Adding books"
+msgstr ""
+
+#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:769
+#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:781
+#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:793
+msgid "Import/Export"
+msgstr ""
+
+#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:773
+msgid "Control how calibre reads metadata from files when adding books"
+msgstr ""
+
+#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:779
+msgid "Saving books to disk"
+msgstr ""
+
+#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:785
+msgid ""
+"Control how calibre exports files from its database to disk when using Save "
+"to disk"
+msgstr ""
+
+#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:791
+msgid "Sending books to devices"
+msgstr ""
+
+#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:797
+msgid "Control how calibre transfers files to your ebook reader"
+msgstr ""
+
+#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:803
+msgid "Sharing books by email"
+msgstr ""
+
+#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:805
+#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:817
+msgid "Sharing"
+msgstr "Megosztás"
+
+#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:809
+msgid ""
+"Setup sharing of books via email. Can be used for automatic sending of "
+"downloaded news to your devices"
+msgstr ""
+
+#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:815
+msgid "Sharing over the net"
+msgstr "Hálózati megosztás"
+
+#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:821
+msgid ""
+"Setup the calibre Content Server which will give you access to your calibre "
+"library from anywhere, on any device, over the internet"
+msgstr ""
+
+#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:828
+msgid "Plugins"
+msgstr "Plugin-ok"
+
+#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:830
+#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:842
+#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:853
+msgid "Advanced"
+msgstr "Haladó"
+
+#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:834
+msgid "Add/remove/customize various bits of calibre functionality"
+msgstr ""
+
+#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:840
+msgid "Tweaks"
+msgstr ""
+
+#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:846
+msgid "Fine tune how calibre behaves in various contexts"
+msgstr ""
+
+#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:851
+msgid "Miscellaneous"
+msgstr ""
+
+#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:857
+msgid "Miscellaneous advanced configuration"
+msgstr ""
+
#: /home/kovid/work/calibre/src/calibre/customize/conversion.py:102
msgid "Conversion Input"
msgstr "Konverzió bemenet"
@@ -311,7 +461,7 @@ msgstr ""
"ha nincsenek információi a bementi dokumentumról."
#: /home/kovid/work/calibre/src/calibre/customize/profiles.py:57
-#: /home/kovid/work/calibre/src/calibre/customize/profiles.py:414
+#: /home/kovid/work/calibre/src/calibre/customize/profiles.py:418
msgid ""
"This profile is intended for the SONY PRS line. The 500/505/600/700 etc."
msgstr ""
@@ -322,62 +472,62 @@ msgid "This profile is intended for the SONY PRS 300."
msgstr "A profil a SONY PRS-300-ra vonatkozik"
#: /home/kovid/work/calibre/src/calibre/customize/profiles.py:78
-#: /home/kovid/work/calibre/src/calibre/customize/profiles.py:449
+#: /home/kovid/work/calibre/src/calibre/customize/profiles.py:453
msgid "This profile is intended for the SONY PRS-900."
msgstr "A profil a SONY PRS-900-ra vonatkozik"
#: /home/kovid/work/calibre/src/calibre/customize/profiles.py:86
-#: /home/kovid/work/calibre/src/calibre/customize/profiles.py:479
+#: /home/kovid/work/calibre/src/calibre/customize/profiles.py:483
msgid "This profile is intended for the Microsoft Reader."
msgstr "Ez a profil a Microsoft Reader-hez készült"
#: /home/kovid/work/calibre/src/calibre/customize/profiles.py:97
-#: /home/kovid/work/calibre/src/calibre/customize/profiles.py:490
+#: /home/kovid/work/calibre/src/calibre/customize/profiles.py:494
msgid "This profile is intended for the Mobipocket books."
msgstr "Ez a Mobipocket (PRC, MOBI) könyvekhez való profil"
#: /home/kovid/work/calibre/src/calibre/customize/profiles.py:110
-#: /home/kovid/work/calibre/src/calibre/customize/profiles.py:503
+#: /home/kovid/work/calibre/src/calibre/customize/profiles.py:507
msgid "This profile is intended for the Hanlin V3 and its clones."
msgstr "Ez a Hanlin V3 és klónjainak profilja"
#: /home/kovid/work/calibre/src/calibre/customize/profiles.py:122
-#: /home/kovid/work/calibre/src/calibre/customize/profiles.py:515
+#: /home/kovid/work/calibre/src/calibre/customize/profiles.py:519
msgid "This profile is intended for the Hanlin V5 and its clones."
msgstr "A profil a Hanlin V5-re és klónjaira vonatkozik"
#: /home/kovid/work/calibre/src/calibre/customize/profiles.py:132
-#: /home/kovid/work/calibre/src/calibre/customize/profiles.py:523
+#: /home/kovid/work/calibre/src/calibre/customize/profiles.py:527
msgid "This profile is intended for the Cybook G3."
msgstr "Ez a Cybook G3 profilja."
#: /home/kovid/work/calibre/src/calibre/customize/profiles.py:145
-#: /home/kovid/work/calibre/src/calibre/customize/profiles.py:536
+#: /home/kovid/work/calibre/src/calibre/customize/profiles.py:540
msgid "This profile is intended for the Cybook Opus."
msgstr "Ez a Cybook Opus profilja."
#: /home/kovid/work/calibre/src/calibre/customize/profiles.py:157
-#: /home/kovid/work/calibre/src/calibre/customize/profiles.py:547
+#: /home/kovid/work/calibre/src/calibre/customize/profiles.py:551
msgid "This profile is intended for the Amazon Kindle."
msgstr "Ez az Amazon Kindle profilja."
#: /home/kovid/work/calibre/src/calibre/customize/profiles.py:169
-#: /home/kovid/work/calibre/src/calibre/customize/profiles.py:584
+#: /home/kovid/work/calibre/src/calibre/customize/profiles.py:589
msgid "This profile is intended for the Irex Illiad."
msgstr "Az Irex Illiad-hoz tartozó profil"
#: /home/kovid/work/calibre/src/calibre/customize/profiles.py:181
-#: /home/kovid/work/calibre/src/calibre/customize/profiles.py:597
+#: /home/kovid/work/calibre/src/calibre/customize/profiles.py:602
msgid "This profile is intended for the IRex Digital Reader 1000."
msgstr "Ez az IReax Digital Reader 1000 profilja."
#: /home/kovid/work/calibre/src/calibre/customize/profiles.py:194
-#: /home/kovid/work/calibre/src/calibre/customize/profiles.py:611
+#: /home/kovid/work/calibre/src/calibre/customize/profiles.py:616
msgid "This profile is intended for the IRex Digital Reader 800."
msgstr "Az IRex Digital Reader 800 profilja"
#: /home/kovid/work/calibre/src/calibre/customize/profiles.py:206
-#: /home/kovid/work/calibre/src/calibre/customize/profiles.py:625
+#: /home/kovid/work/calibre/src/calibre/customize/profiles.py:630
msgid "This profile is intended for the B&N Nook."
msgstr "A profil a B&N Nook-ra vonatkozik"
@@ -395,25 +545,25 @@ msgstr ""
"ha számítógépen, vagy többfajta eszközön olvasható dokumentumot akar "
"készíteni."
-#: /home/kovid/work/calibre/src/calibre/customize/profiles.py:259
+#: /home/kovid/work/calibre/src/calibre/customize/profiles.py:262
msgid ""
"Intended for the iPad and similar devices with a resolution of 768x1024"
msgstr ""
"Az iPad, vagy az ehhez hasonlóan 768x1024 felbontású eszközök profilja"
-#: /home/kovid/work/calibre/src/calibre/customize/profiles.py:427
+#: /home/kovid/work/calibre/src/calibre/customize/profiles.py:431
msgid "This profile is intended for the Kobo Reader."
msgstr "A Kobo Reader profilja"
-#: /home/kovid/work/calibre/src/calibre/customize/profiles.py:440
+#: /home/kovid/work/calibre/src/calibre/customize/profiles.py:444
msgid "This profile is intended for the SONY PRS-300."
msgstr "A profil a SONY PRS-300-ra vonatkozik"
-#: /home/kovid/work/calibre/src/calibre/customize/profiles.py:458
+#: /home/kovid/work/calibre/src/calibre/customize/profiles.py:462
msgid "This profile is intended for the 5-inch JetBook."
msgstr "Ez az 5-inches JetBook profilja."
-#: /home/kovid/work/calibre/src/calibre/customize/profiles.py:467
+#: /home/kovid/work/calibre/src/calibre/customize/profiles.py:471
msgid ""
"This profile is intended for the SONY PRS line. The 500/505/700 etc, in "
"landscape mode. Mainly useful for comics."
@@ -421,7 +571,7 @@ msgstr ""
"Ez a SONY PRS sorozathoz (pl. 500/505/700) való profil fekvő nézetben. "
"Többnyire képregényekhez használatos."
-#: /home/kovid/work/calibre/src/calibre/customize/profiles.py:566
+#: /home/kovid/work/calibre/src/calibre/customize/profiles.py:571
msgid "This profile is intended for the Amazon Kindle DX."
msgstr "Ez az Amazon Kindle DX profilja."
@@ -445,15 +595,15 @@ msgstr "Letiltott bővítmények"
msgid "Enabled plugins"
msgstr "Plugin-ok engedélyezése"
-#: /home/kovid/work/calibre/src/calibre/customize/ui.py:86
+#: /home/kovid/work/calibre/src/calibre/customize/ui.py:93
msgid "No valid plugin found in "
msgstr "Nem találtam érvényes bővítményt itt: "
-#: /home/kovid/work/calibre/src/calibre/customize/ui.py:501
+#: /home/kovid/work/calibre/src/calibre/customize/ui.py:508
msgid "Initialization of plugin %s failed with traceback:"
msgstr "A %s bővítmény indítása a következő hibaüzenettel meghiusult:"
-#: /home/kovid/work/calibre/src/calibre/customize/ui.py:534
+#: /home/kovid/work/calibre/src/calibre/customize/ui.py:541
msgid ""
" %prog options\n"
"\n"
@@ -465,17 +615,17 @@ msgstr ""
" Igazítsa Calibret saját igényeihez külső pluginok betöltésével.\n"
" "
-#: /home/kovid/work/calibre/src/calibre/customize/ui.py:540
+#: /home/kovid/work/calibre/src/calibre/customize/ui.py:547
msgid "Add a plugin by specifying the path to the zip file containing it."
msgstr "Bővítmény hozzáadása a ZIP fájl elérési útjának megadásával."
-#: /home/kovid/work/calibre/src/calibre/customize/ui.py:542
+#: /home/kovid/work/calibre/src/calibre/customize/ui.py:549
msgid "Remove a custom plugin by name. Has no effect on builtin plugins"
msgstr ""
"Egy telepített bővítmény eltávolítása név alapján. Nincs hatással a "
"beépített bővítményekre."
-#: /home/kovid/work/calibre/src/calibre/customize/ui.py:544
+#: /home/kovid/work/calibre/src/calibre/customize/ui.py:551
msgid ""
"Customize plugin. Specify name of plugin and customization string separated "
"by a comma."
@@ -483,15 +633,15 @@ msgstr ""
"Bővítmény testreszabása. Adja meg a bővítmény nevét, és a kért beállítást, "
"vesszővel elválasztva."
-#: /home/kovid/work/calibre/src/calibre/customize/ui.py:546
+#: /home/kovid/work/calibre/src/calibre/customize/ui.py:553
msgid "List all installed plugins"
msgstr "A telepített bővítőmodulok listázása"
-#: /home/kovid/work/calibre/src/calibre/customize/ui.py:548
+#: /home/kovid/work/calibre/src/calibre/customize/ui.py:555
msgid "Enable the named plugin"
msgstr "A bővítmény engedélyezése"
-#: /home/kovid/work/calibre/src/calibre/customize/ui.py:550
+#: /home/kovid/work/calibre/src/calibre/customize/ui.py:557
msgid "Disable the named plugin"
msgstr "A bővítmény letiltása"
@@ -499,7 +649,7 @@ msgstr "A bővítmény letiltása"
msgid "Communicate with Android phones."
msgstr "Kapcsolódás Android telefonhoz."
-#: /home/kovid/work/calibre/src/calibre/devices/android/driver.py:50
+#: /home/kovid/work/calibre/src/calibre/devices/android/driver.py:52
msgid ""
"Comma separated list of directories to send e-books to on the device. The "
"first one that exists will be used"
@@ -507,10 +657,14 @@ msgstr ""
"Vesszővel tagolt mappa-lista az e-könyveknek az olvasóra való küldéséhez. Az "
"első létező mappába kerülnek a könyvek."
-#: /home/kovid/work/calibre/src/calibre/devices/android/driver.py:92
+#: /home/kovid/work/calibre/src/calibre/devices/android/driver.py:94
msgid "Communicate with S60 phones."
msgstr "Kapcsolódás S60 telefonokhoz"
+#: /home/kovid/work/calibre/src/calibre/devices/apple/driver.py:85
+msgid "Apple device"
+msgstr "Apple eszköz"
+
#: /home/kovid/work/calibre/src/calibre/devices/apple/driver.py:87
msgid "Communicate with iTunes/iBooks."
msgstr ""
@@ -526,16 +680,16 @@ msgstr ""
#: /home/kovid/work/calibre/src/calibre/devices/apple/driver.py:323
#: /home/kovid/work/calibre/src/calibre/devices/apple/driver.py:362
-#: /home/kovid/work/calibre/src/calibre/devices/apple/driver.py:921
-#: /home/kovid/work/calibre/src/calibre/devices/apple/driver.py:957
-#: /home/kovid/work/calibre/src/calibre/devices/apple/driver.py:2822
-#: /home/kovid/work/calibre/src/calibre/devices/apple/driver.py:2861
+#: /home/kovid/work/calibre/src/calibre/devices/apple/driver.py:922
+#: /home/kovid/work/calibre/src/calibre/devices/apple/driver.py:962
+#: /home/kovid/work/calibre/src/calibre/devices/apple/driver.py:2831
+#: /home/kovid/work/calibre/src/calibre/devices/apple/driver.py:2871
msgid "%d of %d"
msgstr ""
#: /home/kovid/work/calibre/src/calibre/devices/apple/driver.py:369
-#: /home/kovid/work/calibre/src/calibre/devices/apple/driver.py:962
-#: /home/kovid/work/calibre/src/calibre/devices/apple/driver.py:2867
+#: /home/kovid/work/calibre/src/calibre/devices/apple/driver.py:967
+#: /home/kovid/work/calibre/src/calibre/devices/apple/driver.py:2877
msgid "finished"
msgstr ""
@@ -560,19 +714,27 @@ msgid ""
"Click 'Show Details' for a list."
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/devices/apple/driver.py:2491
+#: /home/kovid/work/calibre/src/calibre/devices/apple/driver.py:2499
#: /home/kovid/work/calibre/src/calibre/devices/usbms/device.py:817
#: /home/kovid/work/calibre/src/calibre/devices/usbms/device.py:823
#: /home/kovid/work/calibre/src/calibre/devices/usbms/device.py:851
#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/scheduler.py:244
-#: /home/kovid/work/calibre/src/calibre/library/database2.py:192
-#: /home/kovid/work/calibre/src/calibre/library/database2.py:205
-#: /home/kovid/work/calibre/src/calibre/library/database2.py:1694
-#: /home/kovid/work/calibre/src/calibre/library/field_metadata.py:132
+#: /home/kovid/work/calibre/src/calibre/library/database2.py:198
+#: /home/kovid/work/calibre/src/calibre/library/database2.py:211
+#: /home/kovid/work/calibre/src/calibre/library/database2.py:1712
+#: /home/kovid/work/calibre/src/calibre/library/field_metadata.py:134
msgid "News"
msgstr "Hírek (RSS)"
-#: /home/kovid/work/calibre/src/calibre/devices/apple/driver.py:2729
+#: /home/kovid/work/calibre/src/calibre/devices/apple/driver.py:2500
+#: /home/kovid/work/calibre/src/calibre/gui2/catalog/catalog_epub_mobi.py:20
+#: /home/kovid/work/calibre/src/calibre/library/catalog.py:556
+#: /home/kovid/work/calibre/src/calibre/library/database2.py:1675
+#: /home/kovid/work/calibre/src/calibre/library/database2.py:1693
+msgid "Catalog"
+msgstr "Katalógus"
+
+#: /home/kovid/work/calibre/src/calibre/devices/apple/driver.py:2738
msgid "Communicate with iTunes."
msgstr ""
@@ -650,23 +812,23 @@ msgid ""
"first one that exists will be used."
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/devices/hanvon/driver.py:18
+#: /home/kovid/work/calibre/src/calibre/devices/hanvon/driver.py:22
msgid "Communicate with the Hanvon N520 eBook reader."
msgstr "A Hanvon N520 eBook olvasóval kommunikál."
-#: /home/kovid/work/calibre/src/calibre/devices/hanvon/driver.py:40
+#: /home/kovid/work/calibre/src/calibre/devices/hanvon/driver.py:47
msgid "Communicate with The Book reader."
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/devices/hanvon/driver.py:52
+#: /home/kovid/work/calibre/src/calibre/devices/hanvon/driver.py:59
msgid "Communicate with the SpringDesign Alex eBook reader."
msgstr "Kapcsolódás SpringDesign Alex eBook olvasóhoz"
-#: /home/kovid/work/calibre/src/calibre/devices/hanvon/driver.py:68
+#: /home/kovid/work/calibre/src/calibre/devices/hanvon/driver.py:78
msgid "Communicate with the Azbooka"
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/devices/hanvon/driver.py:81
+#: /home/kovid/work/calibre/src/calibre/devices/hanvon/driver.py:94
msgid "Communicate with the Elonex EB 511 eBook reader."
msgstr "Az Elonex EB 511 eBook olvasóval kommunikál."
@@ -712,14 +874,20 @@ msgstr ""
msgid "Communicate with the Kindle DX eBook reader."
msgstr "Kapcsolódás Kindle DX eBook olvasóhoz"
-#: /home/kovid/work/calibre/src/calibre/devices/kobo/driver.py:22
+#: /home/kovid/work/calibre/src/calibre/devices/kobo/driver.py:23
msgid "Communicate with the Kobo Reader"
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/devices/kobo/driver.py:53
-#: /home/kovid/work/calibre/src/calibre/devices/kobo/driver.py:56
-#: /home/kovid/work/calibre/src/calibre/devices/kobo/driver.py:59
-#: /home/kovid/work/calibre/src/calibre/devices/kobo/driver.py:170
+#: /home/kovid/work/calibre/src/calibre/devices/kobo/driver.py:47
+msgid ""
+"The Kobo supports only one collection currently: the \"Im_Reading\" list. "
+"Create a tag called \"Im_Reading\" "
+msgstr ""
+
+#: /home/kovid/work/calibre/src/calibre/devices/kobo/driver.py:63
+#: /home/kovid/work/calibre/src/calibre/devices/kobo/driver.py:66
+#: /home/kovid/work/calibre/src/calibre/devices/kobo/driver.py:69
+#: /home/kovid/work/calibre/src/calibre/devices/kobo/driver.py:188
#: /home/kovid/work/calibre/src/calibre/devices/usbms/driver.py:68
#: /home/kovid/work/calibre/src/calibre/devices/usbms/driver.py:71
#: /home/kovid/work/calibre/src/calibre/devices/usbms/driver.py:74
@@ -729,33 +897,33 @@ msgstr ""
msgid "Getting list of books on device..."
msgstr "Az eszközön lévő könyvek listájának összeállítása..."
-#: /home/kovid/work/calibre/src/calibre/devices/kobo/driver.py:230
-#: /home/kovid/work/calibre/src/calibre/devices/kobo/driver.py:274
+#: /home/kovid/work/calibre/src/calibre/devices/kobo/driver.py:248
+#: /home/kovid/work/calibre/src/calibre/devices/kobo/driver.py:280
#: /home/kovid/work/calibre/src/calibre/devices/usbms/driver.py:253
#: /home/kovid/work/calibre/src/calibre/devices/usbms/driver.py:271
msgid "Removing books from device..."
msgstr "Könyvek eltávolítása az eszközről"
-#: /home/kovid/work/calibre/src/calibre/devices/kobo/driver.py:278
-#: /home/kovid/work/calibre/src/calibre/devices/kobo/driver.py:285
+#: /home/kovid/work/calibre/src/calibre/devices/kobo/driver.py:284
+#: /home/kovid/work/calibre/src/calibre/devices/kobo/driver.py:291
#: /home/kovid/work/calibre/src/calibre/devices/usbms/driver.py:278
#: /home/kovid/work/calibre/src/calibre/devices/usbms/driver.py:283
msgid "Removing books from device metadata listing..."
msgstr "Könyvek eltávolítása az eszköz metaadat listáról..."
-#: /home/kovid/work/calibre/src/calibre/devices/kobo/driver.py:290
-#: /home/kovid/work/calibre/src/calibre/devices/kobo/driver.py:324
+#: /home/kovid/work/calibre/src/calibre/devices/kobo/driver.py:296
+#: /home/kovid/work/calibre/src/calibre/devices/kobo/driver.py:330
#: /home/kovid/work/calibre/src/calibre/devices/usbms/driver.py:217
#: /home/kovid/work/calibre/src/calibre/devices/usbms/driver.py:247
msgid "Adding books to device metadata listing..."
msgstr "Könyvek hozzáadása az eszköz metaadat listához..."
-#: /home/kovid/work/calibre/src/calibre/devices/kobo/driver.py:375
-#: /home/kovid/work/calibre/src/calibre/gui2/actions/add.py:251
+#: /home/kovid/work/calibre/src/calibre/devices/kobo/driver.py:392
+#: /home/kovid/work/calibre/src/calibre/gui2/actions/add.py:252
msgid "Not Implemented"
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/devices/kobo/driver.py:376
+#: /home/kovid/work/calibre/src/calibre/devices/kobo/driver.py:393
msgid ""
"\".kobo\" files do not exist on the device as books instead, they are rows "
"in the sqlite database. Currently they cannot be exported or viewed."
@@ -781,6 +949,10 @@ msgstr ""
msgid "Communicate with the Pandigital Novel"
msgstr ""
+#: /home/kovid/work/calibre/src/calibre/devices/misc.py:114
+msgid "Communicate with the GM2000"
+msgstr ""
+
#: /home/kovid/work/calibre/src/calibre/devices/nokia/driver.py:17
msgid "Communicate with the Nokia 770 internet tablet."
msgstr "Kapcsolódás Nokia 770 internet tablethez"
@@ -821,7 +993,7 @@ msgstr ""
"Vesszővel tagolt metadaadat mezők gyűjtemények létrehozására az olvasón. "
"Lehetőségek: "
-#: /home/kovid/work/calibre/src/calibre/devices/prs505/sony_cache.py:145
+#: /home/kovid/work/calibre/src/calibre/devices/prs505/sony_cache.py:149
#: /home/kovid/work/calibre/src/calibre/ebooks/oeb/transforms/structure.py:68
msgid "Unnamed"
msgstr "Névtelen"
@@ -842,6 +1014,10 @@ msgstr ""
msgid "Communicate with the iPapyrus reader."
msgstr ""
+#: /home/kovid/work/calibre/src/calibre/devices/teclast/driver.py:59
+msgid "Communicate with the Sovos reader."
+msgstr ""
+
#: /home/kovid/work/calibre/src/calibre/devices/usbms/device.py:255
msgid "Unable to detect the %s disk drive. Try rebooting."
msgstr ""
@@ -907,7 +1083,7 @@ msgid "Place files in sub directories if the device supports them"
msgstr "Fájlok almappákba rendezése, ha eszköz ezt támogatja"
#: /home/kovid/work/calibre/src/calibre/devices/usbms/deviceconfig.py:43
-#: /home/kovid/work/calibre/src/calibre/gui2/device_drivers/configwidget_ui.py:81
+#: /home/kovid/work/calibre/src/calibre/gui2/device_drivers/configwidget_ui.py:86
msgid "Read metadata from files on device"
msgstr "Metaadatok olvasása az eszközön lévő fájlokból"
@@ -920,7 +1096,7 @@ msgid "Template to control how books are saved"
msgstr "Sablon annak ellenőrzésére, hogyan lettek a könyvek elmentve."
#: /home/kovid/work/calibre/src/calibre/devices/usbms/deviceconfig.py:50
-#: /home/kovid/work/calibre/src/calibre/gui2/device_drivers/configwidget_ui.py:84
+#: /home/kovid/work/calibre/src/calibre/gui2/device_drivers/configwidget_ui.py:89
msgid "Extra customization"
msgstr "Kiegészítő testreszabás"
@@ -1528,22 +1704,37 @@ msgstr ""
"lehet, ezért óvatosan használja."
#: /home/kovid/work/calibre/src/calibre/ebooks/conversion/plumber.py:367
+msgid ""
+"Scale used to determine the length at which a line should be unwrapped if "
+"preprocess is enabled. Valid values are a decimal between 0 and 1. The "
+"default is 0.40, just below the median line length. This will unwrap typical "
+"books with hard line breaks, but should be reduced if the line length is "
+"variable."
+msgstr ""
+
+#: /home/kovid/work/calibre/src/calibre/ebooks/conversion/plumber.py:376
+msgid ""
+"Convert plain quotes, dashes and ellipsis to their typographically correct "
+"equivalents. For details, see http://daringfireball.net/projects/smartypants"
+msgstr ""
+
+#: /home/kovid/work/calibre/src/calibre/ebooks/conversion/plumber.py:384
msgid "Use a regular expression to try and remove the header."
msgstr "Reguláris kifejezés használata a fejléc eltávolításához."
-#: /home/kovid/work/calibre/src/calibre/ebooks/conversion/plumber.py:374
+#: /home/kovid/work/calibre/src/calibre/ebooks/conversion/plumber.py:391
msgid "The regular expression to use to remove the header."
msgstr "A reguláris kifejezés a fejléc eltávolításához."
-#: /home/kovid/work/calibre/src/calibre/ebooks/conversion/plumber.py:380
+#: /home/kovid/work/calibre/src/calibre/ebooks/conversion/plumber.py:397
msgid "Use a regular expression to try and remove the footer."
msgstr "Reguláris kifejezés használata a lábléc eltávolításához."
-#: /home/kovid/work/calibre/src/calibre/ebooks/conversion/plumber.py:387
+#: /home/kovid/work/calibre/src/calibre/ebooks/conversion/plumber.py:404
msgid "The regular expression to use to remove the footer."
msgstr "A reguláris kifejezés a lábléc eltávolításához."
-#: /home/kovid/work/calibre/src/calibre/ebooks/conversion/plumber.py:394
+#: /home/kovid/work/calibre/src/calibre/ebooks/conversion/plumber.py:411
msgid ""
"Read metadata from the specified OPF file. Metadata read from this file will "
"override any metadata in the source file."
@@ -1551,7 +1742,7 @@ msgstr ""
"Metaadatok olvasása a meghatározott OPF fájlból. Ez felülírja a forrásfájl "
"összes metaadatát."
-#: /home/kovid/work/calibre/src/calibre/ebooks/conversion/plumber.py:401
+#: /home/kovid/work/calibre/src/calibre/ebooks/conversion/plumber.py:418
msgid ""
"Transliterate unicode characters to an ASCII representation. Use with care "
"because this will replace unicode characters with ASCII. For instance it "
@@ -1566,7 +1757,7 @@ msgstr ""
"használt közös betűk) az átalakított betű a legtöbb ember által használt (ez "
"esetben a kínai) megfelelője lesz."
-#: /home/kovid/work/calibre/src/calibre/ebooks/conversion/plumber.py:416
+#: /home/kovid/work/calibre/src/calibre/ebooks/conversion/plumber.py:433
msgid ""
"Preserve ligatures present in the input document. A ligature is a special "
"rendering of a pair of characters like ff, fi, fl et cetera. Most readers do "
@@ -1576,105 +1767,105 @@ msgid ""
"instead."
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/ebooks/conversion/plumber.py:428
+#: /home/kovid/work/calibre/src/calibre/ebooks/conversion/plumber.py:445
#: /home/kovid/work/calibre/src/calibre/ebooks/metadata/cli.py:38
msgid "Set the title."
msgstr "Könyvcím megadása"
-#: /home/kovid/work/calibre/src/calibre/ebooks/conversion/plumber.py:432
+#: /home/kovid/work/calibre/src/calibre/ebooks/conversion/plumber.py:449
msgid "Set the authors. Multiple authors should be separated by ampersands."
msgstr ""
"Adja meg a szerzőt. Több szerző esetén pontosvesszővel kell elválasztani "
"azokat."
-#: /home/kovid/work/calibre/src/calibre/ebooks/conversion/plumber.py:437
+#: /home/kovid/work/calibre/src/calibre/ebooks/conversion/plumber.py:454
msgid "The version of the title to be used for sorting. "
msgstr "A rendezéshez használandó könyvcím verzió. "
-#: /home/kovid/work/calibre/src/calibre/ebooks/conversion/plumber.py:441
+#: /home/kovid/work/calibre/src/calibre/ebooks/conversion/plumber.py:458
msgid "String to be used when sorting by author. "
msgstr "A szerző alapján történő rendezéskor használ szöveg. "
-#: /home/kovid/work/calibre/src/calibre/ebooks/conversion/plumber.py:445
+#: /home/kovid/work/calibre/src/calibre/ebooks/conversion/plumber.py:462
msgid "Set the cover to the specified file or URL"
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/ebooks/conversion/plumber.py:449
+#: /home/kovid/work/calibre/src/calibre/ebooks/conversion/plumber.py:466
#: /home/kovid/work/calibre/src/calibre/ebooks/metadata/cli.py:54
msgid "Set the ebook description."
msgstr "Ebook leírás megadása"
-#: /home/kovid/work/calibre/src/calibre/ebooks/conversion/plumber.py:453
+#: /home/kovid/work/calibre/src/calibre/ebooks/conversion/plumber.py:470
#: /home/kovid/work/calibre/src/calibre/ebooks/metadata/cli.py:56
msgid "Set the ebook publisher."
msgstr "Adja meg a kiadót."
-#: /home/kovid/work/calibre/src/calibre/ebooks/conversion/plumber.py:457
+#: /home/kovid/work/calibre/src/calibre/ebooks/conversion/plumber.py:474
#: /home/kovid/work/calibre/src/calibre/ebooks/metadata/cli.py:60
msgid "Set the series this ebook belongs to."
msgstr "Sorozat megadása"
-#: /home/kovid/work/calibre/src/calibre/ebooks/conversion/plumber.py:461
+#: /home/kovid/work/calibre/src/calibre/ebooks/conversion/plumber.py:478
#: /home/kovid/work/calibre/src/calibre/ebooks/metadata/cli.py:62
msgid "Set the index of the book in this series."
msgstr "Könyv sorszámának megadása a soroztaon belül"
-#: /home/kovid/work/calibre/src/calibre/ebooks/conversion/plumber.py:465
+#: /home/kovid/work/calibre/src/calibre/ebooks/conversion/plumber.py:482
#: /home/kovid/work/calibre/src/calibre/ebooks/metadata/cli.py:64
msgid "Set the rating. Should be a number between 1 and 5."
msgstr "Értékelés. 1 és 5 közötti számnak kell lennie."
-#: /home/kovid/work/calibre/src/calibre/ebooks/conversion/plumber.py:469
+#: /home/kovid/work/calibre/src/calibre/ebooks/conversion/plumber.py:486
#: /home/kovid/work/calibre/src/calibre/ebooks/metadata/cli.py:66
msgid "Set the ISBN of the book."
msgstr "Könyv ISBN számának megadása"
-#: /home/kovid/work/calibre/src/calibre/ebooks/conversion/plumber.py:473
+#: /home/kovid/work/calibre/src/calibre/ebooks/conversion/plumber.py:490
#: /home/kovid/work/calibre/src/calibre/ebooks/metadata/cli.py:68
msgid "Set the tags for the book. Should be a comma separated list."
msgstr ""
"Könyv cimkéinek megadása. Ez egy vesszővel elválasztott lista legyen."
-#: /home/kovid/work/calibre/src/calibre/ebooks/conversion/plumber.py:477
+#: /home/kovid/work/calibre/src/calibre/ebooks/conversion/plumber.py:494
#: /home/kovid/work/calibre/src/calibre/ebooks/metadata/cli.py:70
msgid "Set the book producer."
msgstr "Könyv producer megadása"
-#: /home/kovid/work/calibre/src/calibre/ebooks/conversion/plumber.py:481
+#: /home/kovid/work/calibre/src/calibre/ebooks/conversion/plumber.py:498
#: /home/kovid/work/calibre/src/calibre/ebooks/metadata/cli.py:72
msgid "Set the language."
msgstr "Nyelv megadása."
-#: /home/kovid/work/calibre/src/calibre/ebooks/conversion/plumber.py:485
+#: /home/kovid/work/calibre/src/calibre/ebooks/conversion/plumber.py:502
msgid "Set the publication date."
msgstr "Állítsd be kiadás dátumát."
-#: /home/kovid/work/calibre/src/calibre/ebooks/conversion/plumber.py:489
+#: /home/kovid/work/calibre/src/calibre/ebooks/conversion/plumber.py:506
msgid "Set the book timestamp (used by the date column in calibre)."
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/ebooks/conversion/plumber.py:589
+#: /home/kovid/work/calibre/src/calibre/ebooks/conversion/plumber.py:606
msgid "Could not find an ebook inside the archive"
msgstr "Nem találtam e-könyvet a tömörített fájlban."
-#: /home/kovid/work/calibre/src/calibre/ebooks/conversion/plumber.py:647
+#: /home/kovid/work/calibre/src/calibre/ebooks/conversion/plumber.py:664
msgid "Values of series index and rating must be numbers. Ignoring"
msgstr ""
"A sorozat index és az értékelés értékei csak számok lehetnek. Kihagyásuk"
-#: /home/kovid/work/calibre/src/calibre/ebooks/conversion/plumber.py:654
+#: /home/kovid/work/calibre/src/calibre/ebooks/conversion/plumber.py:671
msgid "Failed to parse date/time"
msgstr "Nem sikerült elemezni a dátum/idő-t"
-#: /home/kovid/work/calibre/src/calibre/ebooks/conversion/plumber.py:809
+#: /home/kovid/work/calibre/src/calibre/ebooks/conversion/plumber.py:826
msgid "Converting input to HTML..."
msgstr "Konvertálás HTML formátumba..."
-#: /home/kovid/work/calibre/src/calibre/ebooks/conversion/plumber.py:836
+#: /home/kovid/work/calibre/src/calibre/ebooks/conversion/plumber.py:853
msgid "Running transforms on ebook..."
msgstr "Átalakítások futtatatása a könyvön..."
-#: /home/kovid/work/calibre/src/calibre/ebooks/conversion/plumber.py:923
+#: /home/kovid/work/calibre/src/calibre/ebooks/conversion/plumber.py:940
msgid "Creating"
msgstr "Létrehozás"
@@ -2095,8 +2286,8 @@ msgstr "Képregény"
#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/delete_matching_from_device.py:75
#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/fetch_metadata.py:58
#: /home/kovid/work/calibre/src/calibre/gui2/library/models.py:65
-#: /home/kovid/work/calibre/src/calibre/gui2/library/models.py:359
-#: /home/kovid/work/calibre/src/calibre/gui2/library/models.py:887
+#: /home/kovid/work/calibre/src/calibre/gui2/library/models.py:354
+#: /home/kovid/work/calibre/src/calibre/gui2/library/models.py:882
#: /home/kovid/work/calibre/src/calibre/library/server/opds.py:589
msgid "Title"
msgstr "Cím"
@@ -2104,8 +2295,8 @@ msgstr "Cím"
#: /home/kovid/work/calibre/src/calibre/ebooks/metadata/__init__.py:402
#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/fetch_metadata.py:59
#: /home/kovid/work/calibre/src/calibre/gui2/library/models.py:67
-#: /home/kovid/work/calibre/src/calibre/gui2/library/models.py:364
-#: /home/kovid/work/calibre/src/calibre/gui2/library/models.py:888
+#: /home/kovid/work/calibre/src/calibre/gui2/library/models.py:359
+#: /home/kovid/work/calibre/src/calibre/gui2/library/models.py:883
msgid "Author(s)"
msgstr "Szerző(k)"
@@ -2124,31 +2315,33 @@ msgstr "Producer"
#: /home/kovid/work/calibre/src/calibre/gui2/book_details.py:35
#: /home/kovid/work/calibre/src/calibre/gui2/book_details.py:210
#: /home/kovid/work/calibre/src/calibre/gui2/book_details.py:211
-#: /home/kovid/work/calibre/src/calibre/gui2/convert/metadata_ui.py:184
+#: /home/kovid/work/calibre/src/calibre/gui2/convert/metadata_ui.py:189
#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/book_info.py:99
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/book_info_ui.py:67
-#: /home/kovid/work/calibre/src/calibre/gui2/library/models.py:318
-#: /home/kovid/work/calibre/src/calibre/gui2/library/models.py:1079
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/book_info_ui.py:72
+#: /home/kovid/work/calibre/src/calibre/gui2/library/models.py:313
+#: /home/kovid/work/calibre/src/calibre/gui2/library/models.py:1081
msgid "Comments"
msgstr "Megjegyzés"
#: /home/kovid/work/calibre/src/calibre/ebooks/metadata/__init__.py:413
+#: /home/kovid/work/calibre/src/calibre/ebooks/oeb/transforms/jacket.py:154
#: /home/kovid/work/calibre/src/calibre/gui2/book_details.py:27
#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/tag_categories.py:50
#: /home/kovid/work/calibre/src/calibre/gui2/library/models.py:73
-#: /home/kovid/work/calibre/src/calibre/gui2/library/models.py:306
-#: /home/kovid/work/calibre/src/calibre/gui2/library/models.py:1075
-#: /home/kovid/work/calibre/src/calibre/library/field_metadata.py:143
+#: /home/kovid/work/calibre/src/calibre/gui2/library/models.py:301
+#: /home/kovid/work/calibre/src/calibre/gui2/library/models.py:1077
+#: /home/kovid/work/calibre/src/calibre/library/field_metadata.py:145
msgid "Tags"
msgstr "Címkék"
#: /home/kovid/work/calibre/src/calibre/ebooks/metadata/__init__.py:415
+#: /home/kovid/work/calibre/src/calibre/ebooks/oeb/transforms/jacket.py:152
#: /home/kovid/work/calibre/src/calibre/gui2/book_details.py:26
#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/tag_categories.py:50
#: /home/kovid/work/calibre/src/calibre/gui2/library/models.py:74
-#: /home/kovid/work/calibre/src/calibre/gui2/library/models.py:323
-#: /home/kovid/work/calibre/src/calibre/gui2/library/models.py:1084
-#: /home/kovid/work/calibre/src/calibre/library/field_metadata.py:91
+#: /home/kovid/work/calibre/src/calibre/gui2/library/models.py:318
+#: /home/kovid/work/calibre/src/calibre/gui2/library/models.py:1086
+#: /home/kovid/work/calibre/src/calibre/library/field_metadata.py:93
msgid "Series"
msgstr "Sorozatok"
@@ -2157,11 +2350,12 @@ msgid "Language"
msgstr "Nyelv"
#: /home/kovid/work/calibre/src/calibre/ebooks/metadata/__init__.py:418
-#: /home/kovid/work/calibre/src/calibre/gui2/library/models.py:1067
+#: /home/kovid/work/calibre/src/calibre/gui2/library/models.py:1069
msgid "Timestamp"
msgstr "Dátum"
#: /home/kovid/work/calibre/src/calibre/ebooks/metadata/__init__.py:420
+#: /home/kovid/work/calibre/src/calibre/ebooks/oeb/transforms/jacket.py:151
#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/fetch_metadata.py:63
#: /home/kovid/work/calibre/src/calibre/gui2/library/models.py:70
msgid "Published"
@@ -2432,7 +2626,7 @@ msgid ""
"LibraryThing.com\n"
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/ebooks/metadata/opf2.py:1226
+#: /home/kovid/work/calibre/src/calibre/ebooks/metadata/opf2.py:1227
#: /home/kovid/work/calibre/src/calibre/ebooks/oeb/base.py:1399
msgid "Cover"
msgstr "Borító"
@@ -2482,7 +2676,7 @@ msgstr "Címlap"
#: /home/kovid/work/calibre/src/calibre/ebooks/oeb/base.py:1401
#: /home/kovid/work/calibre/src/calibre/ebooks/oeb/transforms/htmltoc.py:15
#: /home/kovid/work/calibre/src/calibre/gui2/viewer/main.py:53
-#: /home/kovid/work/calibre/src/calibre/gui2/viewer/main_ui.py:194
+#: /home/kovid/work/calibre/src/calibre/gui2/viewer/main_ui.py:199
msgid "Table of Contents"
msgstr "Tartalomjegyzék"
@@ -2546,13 +2740,19 @@ msgstr "Fő szöveg"
msgid "%s format books are not supported"
msgstr "A %s formátumú könyvek sajnos nem támogatottak"
+#: /home/kovid/work/calibre/src/calibre/ebooks/oeb/transforms/cover.py:101
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single.py:156
+msgid "Book %s of %s"
+msgstr ""
+
#: /home/kovid/work/calibre/src/calibre/ebooks/oeb/transforms/htmltoc.py:54
msgid "HTML TOC generation options."
msgstr "HTML Tartalomjegyzék generálás beállításai."
-#: /home/kovid/work/calibre/src/calibre/ebooks/oeb/transforms/jacket.py:113
-msgid "Book Jacket"
-msgstr "Book Jacket"
+#: /home/kovid/work/calibre/src/calibre/ebooks/oeb/transforms/jacket.py:153
+#: /home/kovid/work/calibre/src/calibre/gui2/library/models.py:71
+msgid "Rating"
+msgstr "Értékelés"
#: /home/kovid/work/calibre/src/calibre/ebooks/oeb/transforms/split.py:34
msgid ""
@@ -2625,12 +2825,9 @@ msgstr "Ne nyerje ki a képeket a dokumentumból"
#: /home/kovid/work/calibre/src/calibre/ebooks/pdf/input.py:26
msgid ""
"Scale used to determine the length at which a line should be unwrapped. "
-"Valid values are a decimal between 0 and 1. The default is 0.5, this is the "
-"median line length."
+"Valid values are a decimal between 0 and 1. The default is 0.45, just below "
+"the median line length."
msgstr ""
-"Az az érték, amely azt mutatja, milyen hosszúságnál legyen sortörés. Az "
-"érték 0 és 1 közötti érték legyen. Az alapértelmezett érték a 0.5, vagyis "
-"közepes sorhossz."
#: /home/kovid/work/calibre/src/calibre/ebooks/pdf/input.py:30
msgid "Use the new PDF conversion engine."
@@ -2987,133 +3184,133 @@ msgstr ""
"Mindenképpen darabolja fel a sorokat a maximális sorhossz értéknél, akkor is "
"ha nincs szóköz. A minimálisnál kisebb érték is megadható"
-#: /home/kovid/work/calibre/src/calibre/gui2/__init__.py:65
+#: /home/kovid/work/calibre/src/calibre/gui2/__init__.py:66
msgid "Send file to storage card instead of main memory by default"
msgstr "Alapbeállításként a memóriakártyára küldje a belső memória helyett"
-#: /home/kovid/work/calibre/src/calibre/gui2/__init__.py:67
+#: /home/kovid/work/calibre/src/calibre/gui2/__init__.py:68
msgid "Confirm before deleting"
msgstr "Megerősítés törlés előtt"
-#: /home/kovid/work/calibre/src/calibre/gui2/__init__.py:69
+#: /home/kovid/work/calibre/src/calibre/gui2/__init__.py:70
msgid "Main window geometry"
msgstr "A főablak méretei"
-#: /home/kovid/work/calibre/src/calibre/gui2/__init__.py:71
+#: /home/kovid/work/calibre/src/calibre/gui2/__init__.py:72
msgid "Notify when a new version is available"
msgstr "Figyelmeztetés új verzió esetén."
-#: /home/kovid/work/calibre/src/calibre/gui2/__init__.py:73
+#: /home/kovid/work/calibre/src/calibre/gui2/__init__.py:74
msgid "Use Roman numerals for series number"
msgstr "Római számok használata a könyvsorozatok számozásánál"
-#: /home/kovid/work/calibre/src/calibre/gui2/__init__.py:75
+#: /home/kovid/work/calibre/src/calibre/gui2/__init__.py:76
msgid "Sort tags list by name, popularity, or rating"
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/gui2/__init__.py:77
+#: /home/kovid/work/calibre/src/calibre/gui2/__init__.py:78
msgid "Number of covers to show in the cover browsing mode"
msgstr "A borító alapján történő böngészéskor a megjelenített borítók száma"
-#: /home/kovid/work/calibre/src/calibre/gui2/__init__.py:79
+#: /home/kovid/work/calibre/src/calibre/gui2/__init__.py:80
msgid "Defaults for conversion to LRF"
msgstr "Az LRF-be való konvertálás alapértelmezett értékei"
-#: /home/kovid/work/calibre/src/calibre/gui2/__init__.py:81
+#: /home/kovid/work/calibre/src/calibre/gui2/__init__.py:82
msgid "Options for the LRF ebook viewer"
msgstr "A beépített LRF olvasóprogram beállításai"
-#: /home/kovid/work/calibre/src/calibre/gui2/__init__.py:84
+#: /home/kovid/work/calibre/src/calibre/gui2/__init__.py:85
msgid "Formats that are viewed using the internal viewer"
msgstr "A beépített olvasóprogram által megjelenített formátumok"
-#: /home/kovid/work/calibre/src/calibre/gui2/__init__.py:86
+#: /home/kovid/work/calibre/src/calibre/gui2/__init__.py:87
msgid "Columns to be displayed in the book list"
msgstr "A könyvlistában megjelenítendő oszlopok"
-#: /home/kovid/work/calibre/src/calibre/gui2/__init__.py:87
+#: /home/kovid/work/calibre/src/calibre/gui2/__init__.py:88
msgid "Automatically launch content server on application startup"
msgstr "A tartalomkiszolgáló automatikus indítása az alkalmazás indulásakor."
-#: /home/kovid/work/calibre/src/calibre/gui2/__init__.py:88
+#: /home/kovid/work/calibre/src/calibre/gui2/__init__.py:89
msgid "Oldest news kept in database"
msgstr "A legrégebbi adatbázisban megtartandó hír"
-#: /home/kovid/work/calibre/src/calibre/gui2/__init__.py:89
+#: /home/kovid/work/calibre/src/calibre/gui2/__init__.py:90
msgid "Show system tray icon"
msgstr "Ikon megjelenítése a tálcán"
-#: /home/kovid/work/calibre/src/calibre/gui2/__init__.py:91
+#: /home/kovid/work/calibre/src/calibre/gui2/__init__.py:92
msgid "Upload downloaded news to device"
msgstr "Letöltött hírek küldése az eszközre"
-#: /home/kovid/work/calibre/src/calibre/gui2/__init__.py:93
+#: /home/kovid/work/calibre/src/calibre/gui2/__init__.py:94
msgid "Delete books from library after uploading to device"
msgstr "Könyvek törlése az adatbázisból az eszközre való feltöltés utén"
-#: /home/kovid/work/calibre/src/calibre/gui2/__init__.py:95
+#: /home/kovid/work/calibre/src/calibre/gui2/__init__.py:96
msgid ""
"Show the cover flow in a separate window instead of in the main calibre "
"window"
msgstr "A borítók megjelenítése külön ablakban a calibre főablaka helyett."
-#: /home/kovid/work/calibre/src/calibre/gui2/__init__.py:97
+#: /home/kovid/work/calibre/src/calibre/gui2/__init__.py:98
msgid "Disable notifications from the system tray icon"
msgstr "A tálcaikon ne mutassa a figyelmeztetéseket"
-#: /home/kovid/work/calibre/src/calibre/gui2/__init__.py:99
+#: /home/kovid/work/calibre/src/calibre/gui2/__init__.py:100
msgid "Default action to perform when send to device button is clicked"
msgstr ""
"Az alapértelmezett művelet a 'Küldés eszközre' gombra való kattintáskor"
-#: /home/kovid/work/calibre/src/calibre/gui2/__init__.py:119
+#: /home/kovid/work/calibre/src/calibre/gui2/__init__.py:120
msgid "Maximum number of waiting worker processes"
msgstr "A sorban álló műveletek maximális száma"
-#: /home/kovid/work/calibre/src/calibre/gui2/__init__.py:121
+#: /home/kovid/work/calibre/src/calibre/gui2/__init__.py:122
msgid "Download social metadata (tags/rating/etc.)"
msgstr "Közösségi metaadatok letöltése (címkék, értékelés stb.)"
-#: /home/kovid/work/calibre/src/calibre/gui2/__init__.py:123
+#: /home/kovid/work/calibre/src/calibre/gui2/__init__.py:124
msgid "Overwrite author and title with new metadata"
msgstr "Irja felül a szerzőt és a címet új metaadattal."
-#: /home/kovid/work/calibre/src/calibre/gui2/__init__.py:125
+#: /home/kovid/work/calibre/src/calibre/gui2/__init__.py:126
msgid "Limit max simultaneous jobs to number of CPUs"
msgstr ""
"A maximális párhuzamosan végrehajtandó műveletek számának korlátozása a "
"processzorok számára"
-#: /home/kovid/work/calibre/src/calibre/gui2/__init__.py:127
+#: /home/kovid/work/calibre/src/calibre/gui2/__init__.py:128
msgid "tag browser categories not to display"
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/gui2/__init__.py:129
+#: /home/kovid/work/calibre/src/calibre/gui2/__init__.py:130
msgid "The layout of the user interface"
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/gui2/__init__.py:131
+#: /home/kovid/work/calibre/src/calibre/gui2/__init__.py:132
msgid "Show the average rating per item indication in the tag browser"
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/gui2/__init__.py:133
+#: /home/kovid/work/calibre/src/calibre/gui2/__init__.py:134
msgid "Disable UI animations"
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/gui2/__init__.py:181
+#: /home/kovid/work/calibre/src/calibre/gui2/__init__.py:182
#: /home/kovid/work/calibre/src/calibre/gui2/wizard/__init__.py:479
msgid "Copied"
msgstr "Másolva"
-#: /home/kovid/work/calibre/src/calibre/gui2/__init__.py:215
+#: /home/kovid/work/calibre/src/calibre/gui2/__init__.py:216
msgid "Copy"
msgstr "Másolás"
-#: /home/kovid/work/calibre/src/calibre/gui2/__init__.py:215
+#: /home/kovid/work/calibre/src/calibre/gui2/__init__.py:216
msgid "Copy to Clipboard"
msgstr "Másolás a vágólapra"
-#: /home/kovid/work/calibre/src/calibre/gui2/__init__.py:433
+#: /home/kovid/work/calibre/src/calibre/gui2/__init__.py:462
msgid "Choose Files"
msgstr "Válasszon a fájlok közül"
@@ -3129,11 +3326,11 @@ msgstr ""
msgid "A"
msgstr "A"
-#: /home/kovid/work/calibre/src/calibre/gui2/actions/add.py:32
+#: /home/kovid/work/calibre/src/calibre/gui2/actions/add.py:33
msgid "Add books from a single directory"
msgstr "Könyvek hozzáadása egy mappából"
-#: /home/kovid/work/calibre/src/calibre/gui2/actions/add.py:34
+#: /home/kovid/work/calibre/src/calibre/gui2/actions/add.py:35
msgid ""
"Add books from directories, including sub-directories (One book per "
"directory, assumes every ebook file is the same book in a different format)"
@@ -3142,7 +3339,7 @@ msgstr ""
"könyv, feltételezve, hogy minden egyes fájl ugyanaz a könyv különböző "
"formátumokban)"
-#: /home/kovid/work/calibre/src/calibre/gui2/actions/add.py:38
+#: /home/kovid/work/calibre/src/calibre/gui2/actions/add.py:39
msgid ""
"Add books from directories, including sub directories (Multiple books per "
"directory, assumes every ebook file is a different book)"
@@ -3150,112 +3347,113 @@ msgstr ""
"Könyvek hozzáadása egy, vagy több mappából, almappából (mappánként több "
"könyv, feltételezve, hogy minden egyes fájl egy külön könyv)"
-#: /home/kovid/work/calibre/src/calibre/gui2/actions/add.py:42
+#: /home/kovid/work/calibre/src/calibre/gui2/actions/add.py:43
msgid "Add Empty book. (Book entry with no formats)"
msgstr ""
"Üres könyv hozzáadása. (Formátum nélküli könyv-bejegyzés az adatbázisban)"
-#: /home/kovid/work/calibre/src/calibre/gui2/actions/add.py:44
+#: /home/kovid/work/calibre/src/calibre/gui2/actions/add.py:45
msgid "Add from ISBN"
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/gui2/actions/add.py:83
+#: /home/kovid/work/calibre/src/calibre/gui2/actions/add.py:84
msgid "How many empty books?"
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/gui2/actions/add.py:84
+#: /home/kovid/work/calibre/src/calibre/gui2/actions/add.py:85
msgid "How many empty books should be added?"
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/gui2/actions/add.py:142
-#: /home/kovid/work/calibre/src/calibre/gui2/actions/add.py:200
+#: /home/kovid/work/calibre/src/calibre/gui2/actions/add.py:143
+#: /home/kovid/work/calibre/src/calibre/gui2/actions/add.py:201
msgid "Uploading books to device."
msgstr "Könyvek feltöltése az eszközre."
-#: /home/kovid/work/calibre/src/calibre/gui2/actions/add.py:159
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single.py:165
+#: /home/kovid/work/calibre/src/calibre/gui2/actions/add.py:160
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single.py:170
msgid "Books"
msgstr "Könyvek"
-#: /home/kovid/work/calibre/src/calibre/gui2/actions/add.py:160
+#: /home/kovid/work/calibre/src/calibre/gui2/actions/add.py:161
msgid "EPUB Books"
msgstr "EPUB könyvek"
-#: /home/kovid/work/calibre/src/calibre/gui2/actions/add.py:161
+#: /home/kovid/work/calibre/src/calibre/gui2/actions/add.py:162
msgid "LRF Books"
msgstr "LRF könyvek"
-#: /home/kovid/work/calibre/src/calibre/gui2/actions/add.py:162
+#: /home/kovid/work/calibre/src/calibre/gui2/actions/add.py:163
msgid "HTML Books"
msgstr "HTML könyvek"
-#: /home/kovid/work/calibre/src/calibre/gui2/actions/add.py:163
+#: /home/kovid/work/calibre/src/calibre/gui2/actions/add.py:164
msgid "LIT Books"
msgstr "LIT könyvek"
-#: /home/kovid/work/calibre/src/calibre/gui2/actions/add.py:164
+#: /home/kovid/work/calibre/src/calibre/gui2/actions/add.py:165
msgid "MOBI Books"
msgstr "MOBI könyvek"
-#: /home/kovid/work/calibre/src/calibre/gui2/actions/add.py:165
+#: /home/kovid/work/calibre/src/calibre/gui2/actions/add.py:166
msgid "Topaz books"
msgstr "Topaz könyvek"
-#: /home/kovid/work/calibre/src/calibre/gui2/actions/add.py:166
+#: /home/kovid/work/calibre/src/calibre/gui2/actions/add.py:167
msgid "Text books"
msgstr "Text könyvek"
-#: /home/kovid/work/calibre/src/calibre/gui2/actions/add.py:167
+#: /home/kovid/work/calibre/src/calibre/gui2/actions/add.py:168
msgid "PDF Books"
msgstr "PDF könyvek"
-#: /home/kovid/work/calibre/src/calibre/gui2/actions/add.py:168
+#: /home/kovid/work/calibre/src/calibre/gui2/actions/add.py:169
msgid "Comics"
msgstr "Képregény"
-#: /home/kovid/work/calibre/src/calibre/gui2/actions/add.py:169
+#: /home/kovid/work/calibre/src/calibre/gui2/actions/add.py:170
msgid "Archives"
msgstr "Archívumok"
-#: /home/kovid/work/calibre/src/calibre/gui2/actions/add.py:173
+#: /home/kovid/work/calibre/src/calibre/gui2/actions/add.py:174
msgid "Supported books"
msgstr "Támogatott könyvek"
-#: /home/kovid/work/calibre/src/calibre/gui2/actions/add.py:209
+#: /home/kovid/work/calibre/src/calibre/gui2/actions/add.py:210
msgid "Merged some books"
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/gui2/actions/add.py:210
+#: /home/kovid/work/calibre/src/calibre/gui2/actions/add.py:211
msgid ""
"Some duplicates were found and merged into the following existing books:"
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/gui2/actions/add.py:219
+#: /home/kovid/work/calibre/src/calibre/gui2/actions/add.py:220
msgid "Failed to read metadata"
msgstr "Hiba történt a metaadatok olvasása közben"
-#: /home/kovid/work/calibre/src/calibre/gui2/actions/add.py:220
+#: /home/kovid/work/calibre/src/calibre/gui2/actions/add.py:221
msgid "Failed to read metadata from the following"
msgstr "Nem sikerült a metaadatok olvasása a következőből:"
-#: /home/kovid/work/calibre/src/calibre/gui2/actions/add.py:239
-#: /home/kovid/work/calibre/src/calibre/gui2/actions/add.py:258
+#: /home/kovid/work/calibre/src/calibre/gui2/actions/add.py:240
+#: /home/kovid/work/calibre/src/calibre/gui2/actions/add.py:259
msgid "Add to library"
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/gui2/actions/add.py:239
-#: /home/kovid/work/calibre/src/calibre/gui2/actions/delete.py:55
-#: /home/kovid/work/calibre/src/calibre/gui2/actions/view.py:94
-#: /home/kovid/work/calibre/src/calibre/gui2/actions/view.py:119
+#: /home/kovid/work/calibre/src/calibre/gui2/actions/add.py:240
+#: /home/kovid/work/calibre/src/calibre/gui2/actions/delete.py:56
+#: /home/kovid/work/calibre/src/calibre/gui2/actions/tweak_epub.py:28
+#: /home/kovid/work/calibre/src/calibre/gui2/actions/view.py:95
+#: /home/kovid/work/calibre/src/calibre/gui2/actions/view.py:120
msgid "No book selected"
msgstr "Nincs könyv kiválasztva"
-#: /home/kovid/work/calibre/src/calibre/gui2/actions/add.py:252
+#: /home/kovid/work/calibre/src/calibre/gui2/actions/add.py:253
msgid ""
"The following books are virtual and cannot be added to the calibre library:"
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/gui2/actions/add.py:258
+#: /home/kovid/work/calibre/src/calibre/gui2/actions/add.py:259
msgid "No book files found"
msgstr ""
@@ -3268,98 +3466,98 @@ msgid "Add books to your calibre library from the connected device"
msgstr ""
#: /home/kovid/work/calibre/src/calibre/gui2/actions/annotate.py:20
-#: /home/kovid/work/calibre/src/calibre/gui2/device.py:499
+#: /home/kovid/work/calibre/src/calibre/gui2/device.py:498
msgid "Fetch annotations (experimental)"
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/gui2/actions/annotate.py:55
-#: /home/kovid/work/calibre/src/calibre/gui2/actions/annotate.py:235
-msgid "Use library only"
-msgstr ""
-
#: /home/kovid/work/calibre/src/calibre/gui2/actions/annotate.py:56
#: /home/kovid/work/calibre/src/calibre/gui2/actions/annotate.py:236
+msgid "Use library only"
+msgstr ""
+
+#: /home/kovid/work/calibre/src/calibre/gui2/actions/annotate.py:57
+#: /home/kovid/work/calibre/src/calibre/gui2/actions/annotate.py:237
msgid "User annotations generated from main library only"
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/gui2/actions/annotate.py:63
-#: /home/kovid/work/calibre/src/calibre/gui2/actions/catalog.py:31
-#: /home/kovid/work/calibre/src/calibre/gui2/actions/convert.py:86
-#: /home/kovid/work/calibre/src/calibre/gui2/actions/copy_to_library.py:115
-#: /home/kovid/work/calibre/src/calibre/gui2/actions/edit_metadata.py:74
-#: /home/kovid/work/calibre/src/calibre/gui2/actions/edit_metadata.py:140
-#: /home/kovid/work/calibre/src/calibre/gui2/actions/edit_metadata.py:176
-#: /home/kovid/work/calibre/src/calibre/gui2/actions/edit_metadata.py:203
-#: /home/kovid/work/calibre/src/calibre/gui2/actions/save_to_disk.py:91
+#: /home/kovid/work/calibre/src/calibre/gui2/actions/annotate.py:64
+#: /home/kovid/work/calibre/src/calibre/gui2/actions/catalog.py:30
+#: /home/kovid/work/calibre/src/calibre/gui2/actions/convert.py:87
+#: /home/kovid/work/calibre/src/calibre/gui2/actions/copy_to_library.py:116
+#: /home/kovid/work/calibre/src/calibre/gui2/actions/edit_metadata.py:76
+#: /home/kovid/work/calibre/src/calibre/gui2/actions/edit_metadata.py:142
+#: /home/kovid/work/calibre/src/calibre/gui2/actions/edit_metadata.py:180
+#: /home/kovid/work/calibre/src/calibre/gui2/actions/edit_metadata.py:208
+#: /home/kovid/work/calibre/src/calibre/gui2/actions/save_to_disk.py:92
msgid "No books selected"
msgstr "Nincs könyv kiválasztva."
-#: /home/kovid/work/calibre/src/calibre/gui2/actions/annotate.py:64
+#: /home/kovid/work/calibre/src/calibre/gui2/actions/annotate.py:65
msgid "No books selected to fetch annotations from"
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/gui2/actions/annotate.py:89
+#: /home/kovid/work/calibre/src/calibre/gui2/actions/annotate.py:90
msgid "Merging user annotations into database"
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/gui2/actions/annotate.py:117
+#: /home/kovid/work/calibre/src/calibre/gui2/actions/annotate.py:118
msgid "%s Last Page Read: %d (%d%%)"
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/gui2/actions/annotate.py:123
+#: /home/kovid/work/calibre/src/calibre/gui2/actions/annotate.py:124
msgid "%s Last Page Read: Location %d (%d%%)"
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/gui2/actions/annotate.py:142
+#: /home/kovid/work/calibre/src/calibre/gui2/actions/annotate.py:143
msgid "Location %d • %s %s "
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/gui2/actions/annotate.py:151
+#: /home/kovid/work/calibre/src/calibre/gui2/actions/annotate.py:152
msgid "Page %d • %s "
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/gui2/actions/annotate.py:156
+#: /home/kovid/work/calibre/src/calibre/gui2/actions/annotate.py:157
msgid "Location %d • %s "
msgstr ""
#: /home/kovid/work/calibre/src/calibre/gui2/actions/catalog.py:20
-#: /home/kovid/work/calibre/src/calibre/gui2/actions/convert.py:33
+#: /home/kovid/work/calibre/src/calibre/gui2/actions/convert.py:34
msgid "Create catalog of books in your calibre library"
msgstr "Katalógus készítése az adatbázisban lévő könyvekről"
-#: /home/kovid/work/calibre/src/calibre/gui2/actions/catalog.py:32
+#: /home/kovid/work/calibre/src/calibre/gui2/actions/catalog.py:31
msgid "No books selected to generate catalog for"
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/gui2/actions/catalog.py:49
+#: /home/kovid/work/calibre/src/calibre/gui2/actions/catalog.py:53
msgid "Generating %s catalog..."
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/gui2/actions/catalog.py:54
+#: /home/kovid/work/calibre/src/calibre/gui2/actions/catalog.py:58
#: /home/kovid/work/calibre/src/calibre/gui2/add.py:229
msgid "No books found"
msgstr "Nem talált könyveket."
-#: /home/kovid/work/calibre/src/calibre/gui2/actions/catalog.py:55
+#: /home/kovid/work/calibre/src/calibre/gui2/actions/catalog.py:59
msgid ""
"No books to catalog\n"
"Check exclude tags"
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/gui2/actions/catalog.py:65
+#: /home/kovid/work/calibre/src/calibre/gui2/actions/catalog.py:69
msgid "Catalog generated."
msgstr "Katalógus kész."
-#: /home/kovid/work/calibre/src/calibre/gui2/actions/catalog.py:68
+#: /home/kovid/work/calibre/src/calibre/gui2/actions/catalog.py:72
msgid "Export Catalog Directory"
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/gui2/actions/catalog.py:69
+#: /home/kovid/work/calibre/src/calibre/gui2/actions/catalog.py:73
msgid "Select destination for %s.%s"
msgstr ""
#: /home/kovid/work/calibre/src/calibre/gui2/actions/choose_library.py:81
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/config/toolbar.py:50
+#: /home/kovid/work/calibre/src/calibre/gui2/preferences/toolbar.py:51
#: /home/kovid/work/calibre/src/calibre/library/server/opds.py:111
msgid "%d books"
msgstr ""
@@ -3405,38 +3603,48 @@ msgstr "Már létezik"
msgid "The folder %s already exists. Delete it first."
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/gui2/actions/choose_library.py:186
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/config/__init__.py:753
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/confirm_delete_ui.py:48
+#: /home/kovid/work/calibre/src/calibre/gui2/actions/choose_library.py:184
+msgid "Rename failed"
+msgstr ""
+
+#: /home/kovid/work/calibre/src/calibre/gui2/actions/choose_library.py:185
+msgid ""
+"Failed to rename the library at %s. The most common cause for this is if one "
+"of the files in the library is open in another program."
+msgstr ""
+
+#: /home/kovid/work/calibre/src/calibre/gui2/actions/choose_library.py:195
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/confirm_delete_ui.py:53
+#: /home/kovid/work/calibre/src/calibre/gui2/preferences/columns.py:102
msgid "Are you sure?"
msgstr "Biztos benne?"
-#: /home/kovid/work/calibre/src/calibre/gui2/actions/choose_library.py:187
+#: /home/kovid/work/calibre/src/calibre/gui2/actions/choose_library.py:196
msgid "All files from %s will be permanently deleted. Are you sure?"
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/gui2/actions/choose_library.py:207
+#: /home/kovid/work/calibre/src/calibre/gui2/actions/choose_library.py:216
msgid "No library found"
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/gui2/actions/choose_library.py:208
+#: /home/kovid/work/calibre/src/calibre/gui2/actions/choose_library.py:217
msgid ""
"No existing calibre library was found at %s. It will be removed from the "
"list of known libraries."
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/gui2/actions/choose_library.py:240
-#: /home/kovid/work/calibre/src/calibre/gui2/actions/choose_library.py:245
-#: /home/kovid/work/calibre/src/calibre/gui2/actions/save_to_disk.py:100
-#: /home/kovid/work/calibre/src/calibre/gui2/library/views.py:542
+#: /home/kovid/work/calibre/src/calibre/gui2/actions/choose_library.py:249
+#: /home/kovid/work/calibre/src/calibre/gui2/actions/choose_library.py:254
+#: /home/kovid/work/calibre/src/calibre/gui2/actions/save_to_disk.py:101
+#: /home/kovid/work/calibre/src/calibre/gui2/library/views.py:584
msgid "Not allowed"
msgstr "Nem engedélyezett"
-#: /home/kovid/work/calibre/src/calibre/gui2/actions/choose_library.py:241
+#: /home/kovid/work/calibre/src/calibre/gui2/actions/choose_library.py:250
msgid "You cannot change libraries when a device is connected."
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/gui2/actions/choose_library.py:246
+#: /home/kovid/work/calibre/src/calibre/gui2/actions/choose_library.py:255
msgid "You cannot change libraries while jobs are running."
msgstr ""
@@ -3448,19 +3656,19 @@ msgstr "C"
msgid "Convert books"
msgstr "Konvertálás"
-#: /home/kovid/work/calibre/src/calibre/gui2/actions/convert.py:27
+#: /home/kovid/work/calibre/src/calibre/gui2/actions/convert.py:28
msgid "Convert individually"
msgstr "Egy könyv konvertálása"
-#: /home/kovid/work/calibre/src/calibre/gui2/actions/convert.py:29
+#: /home/kovid/work/calibre/src/calibre/gui2/actions/convert.py:30
msgid "Bulk convert"
msgstr "Csoportos konvertálás"
-#: /home/kovid/work/calibre/src/calibre/gui2/actions/convert.py:85
+#: /home/kovid/work/calibre/src/calibre/gui2/actions/convert.py:86
msgid "Cannot convert"
msgstr "Nem lehet konvertálni"
-#: /home/kovid/work/calibre/src/calibre/gui2/actions/convert.py:114
+#: /home/kovid/work/calibre/src/calibre/gui2/actions/convert.py:115
msgid "Starting conversion of %d book(s)"
msgstr "%d könyv konvertálása"
@@ -3472,34 +3680,34 @@ msgstr ""
msgid "Copy selected books to the specified library"
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/gui2/actions/copy_to_library.py:114
+#: /home/kovid/work/calibre/src/calibre/gui2/actions/copy_to_library.py:115
msgid "Cannot copy"
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/gui2/actions/copy_to_library.py:119
+#: /home/kovid/work/calibre/src/calibre/gui2/actions/copy_to_library.py:120
msgid "No library"
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/gui2/actions/copy_to_library.py:120
+#: /home/kovid/work/calibre/src/calibre/gui2/actions/copy_to_library.py:121
msgid "No library found at %s"
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/gui2/actions/copy_to_library.py:123
-#: /home/kovid/work/calibre/src/calibre/gui2/actions/copy_to_library.py:127
+#: /home/kovid/work/calibre/src/calibre/gui2/actions/copy_to_library.py:124
+#: /home/kovid/work/calibre/src/calibre/gui2/actions/copy_to_library.py:128
msgid "Copying"
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/gui2/actions/copy_to_library.py:137
+#: /home/kovid/work/calibre/src/calibre/gui2/actions/copy_to_library.py:138
msgid "Could not copy books: "
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/gui2/actions/copy_to_library.py:137
-#: /home/kovid/work/calibre/src/calibre/gui2/device.py:678
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk.py:234
+#: /home/kovid/work/calibre/src/calibre/gui2/actions/copy_to_library.py:138
+#: /home/kovid/work/calibre/src/calibre/gui2/device.py:670
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk.py:428
msgid "Failed"
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/gui2/actions/copy_to_library.py:140
+#: /home/kovid/work/calibre/src/calibre/gui2/actions/copy_to_library.py:141
msgid "Copied %d books to %s"
msgstr ""
@@ -3511,84 +3719,82 @@ msgstr "Del"
msgid "Remove books"
msgstr "Könyv törlése"
-#: /home/kovid/work/calibre/src/calibre/gui2/actions/delete.py:23
+#: /home/kovid/work/calibre/src/calibre/gui2/actions/delete.py:24
msgid "Remove selected books"
msgstr "A kiválasztott könyvek törlése"
-#: /home/kovid/work/calibre/src/calibre/gui2/actions/delete.py:25
+#: /home/kovid/work/calibre/src/calibre/gui2/actions/delete.py:26
msgid "Remove files of a specific format from selected books.."
msgstr "Bizonyos formátumú könyvek törlése a kiválasztottakból..."
-#: /home/kovid/work/calibre/src/calibre/gui2/actions/delete.py:28
+#: /home/kovid/work/calibre/src/calibre/gui2/actions/delete.py:29
msgid "Remove all formats from selected books, except..."
msgstr "A kiválasztott könyvekből minden formátumú törlése, kivéve..."
-#: /home/kovid/work/calibre/src/calibre/gui2/actions/delete.py:31
+#: /home/kovid/work/calibre/src/calibre/gui2/actions/delete.py:32
msgid "Remove covers from selected books"
msgstr "Borítók törlése a kiválasztott könyvekből"
-#: /home/kovid/work/calibre/src/calibre/gui2/actions/delete.py:34
+#: /home/kovid/work/calibre/src/calibre/gui2/actions/delete.py:35
msgid "Remove matching books from device"
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/gui2/actions/delete.py:52
+#: /home/kovid/work/calibre/src/calibre/gui2/actions/delete.py:53
msgid "Cannot delete"
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/gui2/actions/delete.py:65
+#: /home/kovid/work/calibre/src/calibre/gui2/actions/delete.py:66
msgid "Choose formats to be deleted"
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/gui2/actions/delete.py:83
+#: /home/kovid/work/calibre/src/calibre/gui2/actions/delete.py:84
msgid "Choose formats not to be deleted"
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/gui2/actions/delete.py:103
+#: /home/kovid/work/calibre/src/calibre/gui2/actions/delete.py:104
msgid "Cannot delete books"
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/gui2/actions/delete.py:104
+#: /home/kovid/work/calibre/src/calibre/gui2/actions/delete.py:105
msgid "No device is connected"
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/gui2/actions/delete.py:114
+#: /home/kovid/work/calibre/src/calibre/gui2/actions/delete.py:115
msgid "Main memory"
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/gui2/actions/delete.py:115
-#: /home/kovid/work/calibre/src/calibre/gui2/device.py:436
-#: /home/kovid/work/calibre/src/calibre/gui2/device.py:445
+#: /home/kovid/work/calibre/src/calibre/gui2/actions/delete.py:116
+#: /home/kovid/work/calibre/src/calibre/gui2/device.py:435
+#: /home/kovid/work/calibre/src/calibre/gui2/device.py:444
msgid "Storage Card A"
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/gui2/actions/delete.py:116
-#: /home/kovid/work/calibre/src/calibre/gui2/device.py:438
-#: /home/kovid/work/calibre/src/calibre/gui2/device.py:447
+#: /home/kovid/work/calibre/src/calibre/gui2/actions/delete.py:117
+#: /home/kovid/work/calibre/src/calibre/gui2/device.py:437
+#: /home/kovid/work/calibre/src/calibre/gui2/device.py:446
msgid "Storage Card B"
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/gui2/actions/delete.py:121
+#: /home/kovid/work/calibre/src/calibre/gui2/actions/delete.py:122
msgid "No books to delete"
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/gui2/actions/delete.py:122
+#: /home/kovid/work/calibre/src/calibre/gui2/actions/delete.py:123
msgid "None of the selected books are on the device"
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/gui2/actions/delete.py:139
-#: /home/kovid/work/calibre/src/calibre/gui2/actions/delete.py:194
+#: /home/kovid/work/calibre/src/calibre/gui2/actions/delete.py:140
+#: /home/kovid/work/calibre/src/calibre/gui2/actions/delete.py:195
msgid "Deleting books from device."
msgstr "Könyvek törlése az eszközről."
-#: /home/kovid/work/calibre/src/calibre/gui2/actions/delete.py:160
+#: /home/kovid/work/calibre/src/calibre/gui2/actions/delete.py:161
msgid ""
"The selected books will be permanently deleted and the files removed "
-"from your computer. Are you sure?"
+"from your calibre library. Are you sure?"
msgstr ""
-"A kiválasztott könyvek véglegesen törölve lesznek a számítógépedről. "
-"Biztosan törölni akarja?"
-#: /home/kovid/work/calibre/src/calibre/gui2/actions/delete.py:179
+#: /home/kovid/work/calibre/src/calibre/gui2/actions/delete.py:180
msgid ""
"The selected books will be permanently deleted from your device. Are "
"you sure?"
@@ -3653,123 +3859,124 @@ msgstr "E"
msgid "Edit metadata"
msgstr "Metaadatok szerkesztése"
-#: /home/kovid/work/calibre/src/calibre/gui2/actions/edit_metadata.py:27
+#: /home/kovid/work/calibre/src/calibre/gui2/actions/edit_metadata.py:28
msgid "Merge book records"
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/gui2/actions/edit_metadata.py:28
+#: /home/kovid/work/calibre/src/calibre/gui2/actions/edit_metadata.py:29
msgid "M"
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/gui2/actions/edit_metadata.py:30
+#: /home/kovid/work/calibre/src/calibre/gui2/actions/edit_metadata.py:31
msgid "Edit metadata individually"
msgstr "Metaadatok szerkesztése egyenként"
-#: /home/kovid/work/calibre/src/calibre/gui2/actions/edit_metadata.py:33
+#: /home/kovid/work/calibre/src/calibre/gui2/actions/edit_metadata.py:34
msgid "Edit metadata in bulk"
msgstr "Metaadatok csoportos szerkesztése"
-#: /home/kovid/work/calibre/src/calibre/gui2/actions/edit_metadata.py:36
+#: /home/kovid/work/calibre/src/calibre/gui2/actions/edit_metadata.py:37
msgid "Download metadata and covers"
msgstr "Metaadatok és borítók letöltése"
-#: /home/kovid/work/calibre/src/calibre/gui2/actions/edit_metadata.py:39
+#: /home/kovid/work/calibre/src/calibre/gui2/actions/edit_metadata.py:40
msgid "Download only metadata"
msgstr "Csak a metaadatok letöltése"
-#: /home/kovid/work/calibre/src/calibre/gui2/actions/edit_metadata.py:41
+#: /home/kovid/work/calibre/src/calibre/gui2/actions/edit_metadata.py:42
msgid "Download only covers"
msgstr "Csak a borítók letöltése"
-#: /home/kovid/work/calibre/src/calibre/gui2/actions/edit_metadata.py:44
+#: /home/kovid/work/calibre/src/calibre/gui2/actions/edit_metadata.py:45
msgid "Download only social metadata"
msgstr "Csak a közösségi metaadatok letöltése"
-#: /home/kovid/work/calibre/src/calibre/gui2/actions/edit_metadata.py:50
+#: /home/kovid/work/calibre/src/calibre/gui2/actions/edit_metadata.py:51
msgid "Merge into first selected book - delete others"
msgstr ""
"Könyvek összefűzése az első kijelölt könyvbe - a többi törlése az összefűzés "
"után"
-#: /home/kovid/work/calibre/src/calibre/gui2/actions/edit_metadata.py:53
+#: /home/kovid/work/calibre/src/calibre/gui2/actions/edit_metadata.py:54
msgid "Merge into first selected book - keep others"
msgstr ""
"Könyvek összefűzése az első kijelölt könyvbe - a többi megtartása az "
"összefűzés után"
-#: /home/kovid/work/calibre/src/calibre/gui2/actions/edit_metadata.py:73
+#: /home/kovid/work/calibre/src/calibre/gui2/actions/edit_metadata.py:75
msgid "Cannot download metadata"
msgstr "Nem lehet letölteni a metaadatokat"
-#: /home/kovid/work/calibre/src/calibre/gui2/actions/edit_metadata.py:96
+#: /home/kovid/work/calibre/src/calibre/gui2/actions/edit_metadata.py:98
msgid "social metadata"
msgstr "közösségi metaadatok"
-#: /home/kovid/work/calibre/src/calibre/gui2/actions/edit_metadata.py:98
+#: /home/kovid/work/calibre/src/calibre/gui2/actions/edit_metadata.py:100
msgid "covers"
msgstr "borítók"
-#: /home/kovid/work/calibre/src/calibre/gui2/actions/edit_metadata.py:98
+#: /home/kovid/work/calibre/src/calibre/gui2/actions/edit_metadata.py:100
msgid "metadata"
msgstr "metaadatok"
-#: /home/kovid/work/calibre/src/calibre/gui2/actions/edit_metadata.py:103
+#: /home/kovid/work/calibre/src/calibre/gui2/actions/edit_metadata.py:105
msgid "Downloading %s for %d book(s)"
msgstr "%s letöltése %d könyvhöz"
-#: /home/kovid/work/calibre/src/calibre/gui2/actions/edit_metadata.py:124
+#: /home/kovid/work/calibre/src/calibre/gui2/actions/edit_metadata.py:126
msgid "Failed to download some metadata"
msgstr "Néhány metaadatot nem sikerült letölteni"
-#: /home/kovid/work/calibre/src/calibre/gui2/actions/edit_metadata.py:125
+#: /home/kovid/work/calibre/src/calibre/gui2/actions/edit_metadata.py:127
msgid "Failed to download metadata for the following:"
msgstr "Hiba történt a metaadatok letöltése közben a következőhöz:"
-#: /home/kovid/work/calibre/src/calibre/gui2/actions/edit_metadata.py:128
+#: /home/kovid/work/calibre/src/calibre/gui2/actions/edit_metadata.py:130
msgid "Failed to download metadata:"
msgstr "Nem sikerült metaadatokat letölteni:"
-#: /home/kovid/work/calibre/src/calibre/gui2/actions/edit_metadata.py:129
-#: /home/kovid/work/calibre/src/calibre/gui2/device.py:608
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/config/__init__.py:570
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/config/__init__.py:1005
+#: /home/kovid/work/calibre/src/calibre/gui2/actions/edit_metadata.py:131
+#: /home/kovid/work/calibre/src/calibre/gui2/device.py:607
+#: /home/kovid/work/calibre/src/calibre/gui2/preferences/misc.py:65
+#: /home/kovid/work/calibre/src/calibre/gui2/preferences/misc.py:112
#: /home/kovid/work/calibre/src/calibre/utils/ipc/job.py:54
msgid "Error"
msgstr "Hiba"
-#: /home/kovid/work/calibre/src/calibre/gui2/actions/edit_metadata.py:139
-#: /home/kovid/work/calibre/src/calibre/gui2/actions/edit_metadata.py:175
+#: /home/kovid/work/calibre/src/calibre/gui2/actions/edit_metadata.py:141
+#: /home/kovid/work/calibre/src/calibre/gui2/actions/edit_metadata.py:179
msgid "Cannot edit metadata"
msgstr "Metaadat nem szerkeszthető"
-#: /home/kovid/work/calibre/src/calibre/gui2/actions/edit_metadata.py:202
-#: /home/kovid/work/calibre/src/calibre/gui2/actions/edit_metadata.py:205
+#: /home/kovid/work/calibre/src/calibre/gui2/actions/edit_metadata.py:207
+#: /home/kovid/work/calibre/src/calibre/gui2/actions/edit_metadata.py:210
msgid "Cannot merge books"
msgstr "Nem lehet a könyveket összefűzni"
-#: /home/kovid/work/calibre/src/calibre/gui2/actions/edit_metadata.py:206
+#: /home/kovid/work/calibre/src/calibre/gui2/actions/edit_metadata.py:211
msgid "At least two books must be selected for merging"
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/gui2/actions/edit_metadata.py:210
+#: /home/kovid/work/calibre/src/calibre/gui2/actions/edit_metadata.py:215
msgid ""
-"All book formats and metadata from the selected books will be added to the "
-"first selected book.
The second and subsequently selected "
-"books will not be deleted or changed.
Please confirm you want to "
-"proceed."
+"Book formats and metadata from the selected books will be added to the "
+"first selected book. ISBN will not be merged.
The "
+"second and subsequently selected books will not be deleted or "
+"changed.
Please confirm you want to proceed."
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/gui2/actions/edit_metadata.py:221
+#: /home/kovid/work/calibre/src/calibre/gui2/actions/edit_metadata.py:227
msgid ""
-"All book formats and metadata from the selected books will be merged into "
-"the first selected book.
After merger the second and "
-"subsequently selected books will be deleted.
All book formats "
-"of the first selected book will be kept and any duplicate formats in the "
-"second and subsequently selected books will be permanently deleted "
-"from your computer.
Are you sure you want to proceed?"
+"Book formats and metadata from the selected books will be merged into the "
+"first selected book. ISBN will not be merged.
After "
+"merger the second and subsequently selected books will be deleted. "
+"
All book formats of the first selected book will be kept and any "
+"duplicate formats in the second and subsequently selected books will be "
+"permanently deleted from your computer.
Are you sure "
+"you want to proceed?"
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/gui2/actions/edit_metadata.py:233
+#: /home/kovid/work/calibre/src/calibre/gui2/actions/edit_metadata.py:240
msgid ""
"You are about to merge more than 5 books. Are you sure you want to "
"proceed?"
@@ -3851,53 +4058,53 @@ msgid "S"
msgstr "S"
#: /home/kovid/work/calibre/src/calibre/gui2/actions/save_to_disk.py:40
-#: /home/kovid/work/calibre/src/calibre/gui2/actions/save_to_disk.py:45
+#: /home/kovid/work/calibre/src/calibre/gui2/actions/save_to_disk.py:46
msgid "Save to disk"
msgstr "Mentés lemezre"
-#: /home/kovid/work/calibre/src/calibre/gui2/actions/save_to_disk.py:47
+#: /home/kovid/work/calibre/src/calibre/gui2/actions/save_to_disk.py:48
msgid "Save to disk in a single directory"
msgstr "Mentés lemezre egy mappába"
-#: /home/kovid/work/calibre/src/calibre/gui2/actions/save_to_disk.py:49
-#: /home/kovid/work/calibre/src/calibre/gui2/actions/save_to_disk.py:68
+#: /home/kovid/work/calibre/src/calibre/gui2/actions/save_to_disk.py:50
+#: /home/kovid/work/calibre/src/calibre/gui2/actions/save_to_disk.py:69
msgid "Save only %s format to disk"
msgstr "Csak a(z) %s formátum mentése a lemezre"
-#: /home/kovid/work/calibre/src/calibre/gui2/actions/save_to_disk.py:53
-#: /home/kovid/work/calibre/src/calibre/gui2/actions/save_to_disk.py:71
+#: /home/kovid/work/calibre/src/calibre/gui2/actions/save_to_disk.py:54
+#: /home/kovid/work/calibre/src/calibre/gui2/actions/save_to_disk.py:72
msgid "Save only %s format to disk in a single directory"
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/gui2/actions/save_to_disk.py:90
+#: /home/kovid/work/calibre/src/calibre/gui2/actions/save_to_disk.py:91
msgid "Cannot save to disk"
msgstr "Nem lehet lemezre menteni"
-#: /home/kovid/work/calibre/src/calibre/gui2/actions/save_to_disk.py:93
+#: /home/kovid/work/calibre/src/calibre/gui2/actions/save_to_disk.py:94
msgid "Choose destination directory"
msgstr "Válassza ki a célkönyvtárt"
-#: /home/kovid/work/calibre/src/calibre/gui2/actions/save_to_disk.py:101
+#: /home/kovid/work/calibre/src/calibre/gui2/actions/save_to_disk.py:102
msgid ""
"You are trying to save files into the calibre library. This can cause "
"corruption of your library. Save to disk is meant to export files from your "
"calibre library elsewhere."
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/gui2/actions/save_to_disk.py:135
+#: /home/kovid/work/calibre/src/calibre/gui2/actions/save_to_disk.py:136
msgid "Error while saving"
msgstr "Mentési hiba"
-#: /home/kovid/work/calibre/src/calibre/gui2/actions/save_to_disk.py:136
+#: /home/kovid/work/calibre/src/calibre/gui2/actions/save_to_disk.py:137
msgid "There was an error while saving."
msgstr "Hiba történt mentés közben."
-#: /home/kovid/work/calibre/src/calibre/gui2/actions/save_to_disk.py:143
#: /home/kovid/work/calibre/src/calibre/gui2/actions/save_to_disk.py:144
+#: /home/kovid/work/calibre/src/calibre/gui2/actions/save_to_disk.py:145
msgid "Could not save some books"
msgstr "Néhány könyvet nem sikerült menteni"
-#: /home/kovid/work/calibre/src/calibre/gui2/actions/save_to_disk.py:145
+#: /home/kovid/work/calibre/src/calibre/gui2/actions/save_to_disk.py:146
msgid "Click the show details button to see which ones."
msgstr "Kattintson a 'Részletek megjelenítése' gombra"
@@ -3909,11 +4116,11 @@ msgstr "Könyv adatainak megjelenítése"
msgid "I"
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/gui2/actions/show_book_details.py:25
+#: /home/kovid/work/calibre/src/calibre/gui2/actions/show_book_details.py:26
msgid "No detailed info available"
msgstr "Nincs részletes információ"
-#: /home/kovid/work/calibre/src/calibre/gui2/actions/show_book_details.py:26
+#: /home/kovid/work/calibre/src/calibre/gui2/actions/show_book_details.py:27
msgid "No detailed information is available for books on the device."
msgstr "A könyvekhez nincsenek részletes információk az eszközön."
@@ -3921,66 +4128,88 @@ msgstr "A könyvekhez nincsenek részletes információk az eszközön."
msgid "Similar books..."
msgstr "Hasonló könyvek..."
-#: /home/kovid/work/calibre/src/calibre/gui2/actions/similar_books.py:23
+#: /home/kovid/work/calibre/src/calibre/gui2/actions/similar_books.py:24
msgid "Alt+A"
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/gui2/actions/similar_books.py:23
+#: /home/kovid/work/calibre/src/calibre/gui2/actions/similar_books.py:24
msgid "Books by same author"
msgstr "Könyvek ugyanettől a szerzőtől"
-#: /home/kovid/work/calibre/src/calibre/gui2/actions/similar_books.py:24
+#: /home/kovid/work/calibre/src/calibre/gui2/actions/similar_books.py:25
msgid "Books in this series"
msgstr "Könyvek ebből a sorozatból"
-#: /home/kovid/work/calibre/src/calibre/gui2/actions/similar_books.py:25
+#: /home/kovid/work/calibre/src/calibre/gui2/actions/similar_books.py:26
msgid "Alt+Shift+S"
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/gui2/actions/similar_books.py:26
+#: /home/kovid/work/calibre/src/calibre/gui2/actions/similar_books.py:27
msgid "Alt+P"
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/gui2/actions/similar_books.py:26
+#: /home/kovid/work/calibre/src/calibre/gui2/actions/similar_books.py:27
msgid "Books by this publisher"
msgstr "Könyvek ennek a kiadónak a gondozásában"
-#: /home/kovid/work/calibre/src/calibre/gui2/actions/similar_books.py:27
+#: /home/kovid/work/calibre/src/calibre/gui2/actions/similar_books.py:28
msgid "Alt+T"
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/gui2/actions/similar_books.py:27
+#: /home/kovid/work/calibre/src/calibre/gui2/actions/similar_books.py:28
msgid "Books with the same tags"
msgstr "Könyvek ugyanilyen cimkékkel"
+#: /home/kovid/work/calibre/src/calibre/gui2/actions/tweak_epub.py:15
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/tweak_epub_ui.py:54
+msgid "Tweak ePub"
+msgstr ""
+
+#: /home/kovid/work/calibre/src/calibre/gui2/actions/tweak_epub.py:16
+msgid "Make small changes to ePub format books"
+msgstr ""
+
+#: /home/kovid/work/calibre/src/calibre/gui2/actions/tweak_epub.py:17
+msgid "T"
+msgstr ""
+
+#: /home/kovid/work/calibre/src/calibre/gui2/actions/tweak_epub.py:27
+#: /home/kovid/work/calibre/src/calibre/gui2/actions/tweak_epub.py:39
+msgid "Cannot tweak ePub"
+msgstr ""
+
+#: /home/kovid/work/calibre/src/calibre/gui2/actions/tweak_epub.py:40
+msgid "No ePub available. First convert the book to ePub."
+msgstr ""
+
#: /home/kovid/work/calibre/src/calibre/gui2/actions/view.py:24
msgid "V"
msgstr "V"
#: /home/kovid/work/calibre/src/calibre/gui2/actions/view.py:24
-#: /home/kovid/work/calibre/src/calibre/gui2/actions/view.py:31
+#: /home/kovid/work/calibre/src/calibre/gui2/actions/view.py:32
msgid "View"
msgstr "Olvasás"
-#: /home/kovid/work/calibre/src/calibre/gui2/actions/view.py:32
+#: /home/kovid/work/calibre/src/calibre/gui2/actions/view.py:33
msgid "View specific format"
msgstr "Meghatározott formátum olvasása"
-#: /home/kovid/work/calibre/src/calibre/gui2/actions/view.py:94
-#: /home/kovid/work/calibre/src/calibre/gui2/actions/view.py:155
+#: /home/kovid/work/calibre/src/calibre/gui2/actions/view.py:95
+#: /home/kovid/work/calibre/src/calibre/gui2/actions/view.py:156
msgid "Cannot view"
msgstr "Nem lehet a könyvet olvasni"
-#: /home/kovid/work/calibre/src/calibre/gui2/actions/view.py:100
+#: /home/kovid/work/calibre/src/calibre/gui2/actions/view.py:101
#: /home/kovid/work/calibre/src/calibre/gui2/convert/regex_builder.py:77
msgid "Choose the format to view"
msgstr "Válassza ki a kívánt formátumot olvasásra"
-#: /home/kovid/work/calibre/src/calibre/gui2/actions/view.py:108
+#: /home/kovid/work/calibre/src/calibre/gui2/actions/view.py:109
msgid "Multiple Books Selected"
msgstr "Több könyv is ki van választva"
-#: /home/kovid/work/calibre/src/calibre/gui2/actions/view.py:109
+#: /home/kovid/work/calibre/src/calibre/gui2/actions/view.py:110
msgid ""
"You are attempting to open %d books. Opening too many books at once can be "
"slow and have a negative effect on the responsiveness of your computer. Once "
@@ -3991,11 +4220,11 @@ msgstr ""
"lelassíthatja a gépedet. Ha a művelet elindult, már nem lehet leállítani. "
"Biztosan folytatja?"
-#: /home/kovid/work/calibre/src/calibre/gui2/actions/view.py:118
+#: /home/kovid/work/calibre/src/calibre/gui2/actions/view.py:119
msgid "Cannot open folder"
msgstr "A mappát nem lehet megnyitni"
-#: /home/kovid/work/calibre/src/calibre/gui2/actions/view.py:156
+#: /home/kovid/work/calibre/src/calibre/gui2/actions/view.py:157
msgid "%s has no available formats."
msgstr "Nincs elérhető formátum a következőhöz: %s"
@@ -4076,7 +4305,7 @@ msgid "Looking for duplicates based on file hash"
msgstr ""
#: /home/kovid/work/calibre/src/calibre/gui2/add_wizard/__init__.py:109
-#: /home/kovid/work/calibre/src/calibre/gui2/add_wizard/welcome_ui.py:65
+#: /home/kovid/work/calibre/src/calibre/gui2/add_wizard/welcome_ui.py:70
msgid "Choose root folder"
msgstr ""
@@ -4093,7 +4322,7 @@ msgid "Add books to calibre"
msgstr ""
#: /home/kovid/work/calibre/src/calibre/gui2/add_wizard/scan_ui.py:21
-#: /home/kovid/work/calibre/src/calibre/gui2/add_wizard/welcome_ui.py:57
+#: /home/kovid/work/calibre/src/calibre/gui2/add_wizard/welcome_ui.py:62
#: /home/kovid/work/calibre/src/calibre/gui2/wizard/finish_ui.py:41
#: /home/kovid/work/calibre/src/calibre/gui2/wizard/kindle_ui.py:41
#: /home/kovid/work/calibre/src/calibre/gui2/wizard/library_ui.py:49
@@ -4109,15 +4338,15 @@ msgstr ""
msgid "This may take a few minutes"
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/gui2/add_wizard/welcome_ui.py:58
+#: /home/kovid/work/calibre/src/calibre/gui2/add_wizard/welcome_ui.py:63
msgid "Choose the location to add books from"
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/gui2/add_wizard/welcome_ui.py:59
+#: /home/kovid/work/calibre/src/calibre/gui2/add_wizard/welcome_ui.py:64
msgid "Select a folder on your hard disk"
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/gui2/add_wizard/welcome_ui.py:60
+#: /home/kovid/work/calibre/src/calibre/gui2/add_wizard/welcome_ui.py:65
msgid ""
"
calibre can scan your computer for existing books automatically. These "
"books will then be copied into the calibre library. This wizard will "
@@ -4129,83 +4358,81 @@ msgid ""
"not under the root folder you choose.
This wizard will help you choose an appropriate font size key for your "
"needs. Just enter the base font size of the input document and then enter an "
@@ -4669,25 +4903,25 @@ msgid ""
"for a discussion of how font size rescaling works.
When calibre removes inter paragraph spacing, it automatically sets a "
"paragraph indent, to ensure that paragraphs can be easily distinguished. "
@@ -4776,34 +5010,38 @@ msgstr ""
"beállítja a bekezdés behúzását azok könnyebb olvashatósága érdekében. Ez a "
"beállítás határozza meg a behúzás nagyságát."
-#: /home/kovid/work/calibre/src/calibre/gui2/convert/look_and_feel_ui.py:132
+#: /home/kovid/work/calibre/src/calibre/gui2/convert/look_and_feel_ui.py:135
msgid " em"
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/gui2/convert/look_and_feel_ui.py:133
+#: /home/kovid/work/calibre/src/calibre/gui2/convert/look_and_feel_ui.py:136
msgid "Text justification:"
msgstr "Szöveg igazítás:"
-#: /home/kovid/work/calibre/src/calibre/gui2/convert/look_and_feel_ui.py:134
+#: /home/kovid/work/calibre/src/calibre/gui2/convert/look_and_feel_ui.py:137
msgid "&Linearize tables"
msgstr "Táblázatok szöveggé alakítása"
-#: /home/kovid/work/calibre/src/calibre/gui2/convert/look_and_feel_ui.py:135
+#: /home/kovid/work/calibre/src/calibre/gui2/convert/look_and_feel_ui.py:138
msgid "Extra &CSS"
msgstr "Extra CSS"
-#: /home/kovid/work/calibre/src/calibre/gui2/convert/look_and_feel_ui.py:136
+#: /home/kovid/work/calibre/src/calibre/gui2/convert/look_and_feel_ui.py:139
msgid "&Transliterate unicode characters to ASCII"
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/gui2/convert/look_and_feel_ui.py:137
+#: /home/kovid/work/calibre/src/calibre/gui2/convert/look_and_feel_ui.py:140
msgid "Insert &blank line"
msgstr "Üres sor beszúrása"
-#: /home/kovid/work/calibre/src/calibre/gui2/convert/look_and_feel_ui.py:138
+#: /home/kovid/work/calibre/src/calibre/gui2/convert/look_and_feel_ui.py:141
msgid "Keep &ligatures"
msgstr ""
+#: /home/kovid/work/calibre/src/calibre/gui2/convert/look_and_feel_ui.py:142
+msgid "Smarten &punctuation"
+msgstr ""
+
#: /home/kovid/work/calibre/src/calibre/gui2/convert/lrf_output.py:19
msgid "LRF Output"
msgstr "LRF kimenet"
@@ -4858,7 +5096,7 @@ msgstr "Monospace('rögzített szélességű') betűkészlet"
#: /home/kovid/work/calibre/src/calibre/gui2/convert/metadata.py:41
#: /home/kovid/work/calibre/src/calibre/gui2/viewer/main.py:114
-#: /home/kovid/work/calibre/src/calibre/gui2/viewer/main_ui.py:195
+#: /home/kovid/work/calibre/src/calibre/gui2/viewer/main_ui.py:200
msgid "Metadata"
msgstr "Metaadatok"
@@ -4871,75 +5109,75 @@ msgstr ""
"ezekből."
#: /home/kovid/work/calibre/src/calibre/gui2/convert/metadata.py:165
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single.py:112
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single.py:109
msgid "Choose cover for "
msgstr "Borító választása a következőhöz: "
#: /home/kovid/work/calibre/src/calibre/gui2/convert/metadata.py:172
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single.py:119
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single.py:116
msgid "Cannot read"
msgstr "Olvasási hiba"
#: /home/kovid/work/calibre/src/calibre/gui2/convert/metadata.py:173
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single.py:120
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single.py:117
msgid "You do not have permission to read the file: "
msgstr "Nincs megfelelő jogosultsága a következő fájl olvasásához: "
#: /home/kovid/work/calibre/src/calibre/gui2/convert/metadata.py:181
#: /home/kovid/work/calibre/src/calibre/gui2/convert/metadata.py:188
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single.py:128
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single.py:125
msgid "Error reading file"
msgstr "Fájl olvasási hiba"
#: /home/kovid/work/calibre/src/calibre/gui2/convert/metadata.py:182
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single.py:129
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single.py:126
msgid "
There was an error reading from file: "
msgstr "
Hiba történt a következő fájl olvasása közben: "
#: /home/kovid/work/calibre/src/calibre/gui2/convert/metadata.py:189
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single.py:137
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single.py:134
msgid " is not a valid picture"
msgstr " nem érvényes képformátum."
-#: /home/kovid/work/calibre/src/calibre/gui2/convert/metadata_ui.py:167
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single_ui.py:400
+#: /home/kovid/work/calibre/src/calibre/gui2/convert/metadata_ui.py:172
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single_ui.py:413
msgid "Book Cover"
msgstr "Könyvborító"
-#: /home/kovid/work/calibre/src/calibre/gui2/convert/metadata_ui.py:168
+#: /home/kovid/work/calibre/src/calibre/gui2/convert/metadata_ui.py:173
msgid "Use cover from &source file"
msgstr "A forrásfájlban tárolt borító használata"
-#: /home/kovid/work/calibre/src/calibre/gui2/convert/metadata_ui.py:169
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single_ui.py:401
+#: /home/kovid/work/calibre/src/calibre/gui2/convert/metadata_ui.py:174
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single_ui.py:414
msgid "Change &cover image:"
msgstr "Borító kép cseréje:"
-#: /home/kovid/work/calibre/src/calibre/gui2/convert/metadata_ui.py:170
+#: /home/kovid/work/calibre/src/calibre/gui2/convert/metadata_ui.py:175
msgid "Browse for an image to use as the cover of this book."
msgstr "Képfájl kiválasztása és beállítása borítóként."
-#: /home/kovid/work/calibre/src/calibre/gui2/convert/metadata_ui.py:172
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single_ui.py:361
+#: /home/kovid/work/calibre/src/calibre/gui2/convert/metadata_ui.py:177
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single_ui.py:372
msgid "&Title: "
msgstr "&Cím: "
-#: /home/kovid/work/calibre/src/calibre/gui2/convert/metadata_ui.py:173
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single_ui.py:362
+#: /home/kovid/work/calibre/src/calibre/gui2/convert/metadata_ui.py:178
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single_ui.py:373
msgid "Change the title of this book"
msgstr "Könyv címének megadása"
-#: /home/kovid/work/calibre/src/calibre/gui2/convert/metadata_ui.py:174
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:161
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single_ui.py:365
+#: /home/kovid/work/calibre/src/calibre/gui2/convert/metadata_ui.py:179
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:230
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single_ui.py:376
msgid "&Author(s): "
msgstr "Szerző(k) "
-#: /home/kovid/work/calibre/src/calibre/gui2/convert/metadata_ui.py:175
+#: /home/kovid/work/calibre/src/calibre/gui2/convert/metadata_ui.py:180
msgid "Author So&rt:"
msgstr "Rendezési forma:"
-#: /home/kovid/work/calibre/src/calibre/gui2/convert/metadata_ui.py:176
+#: /home/kovid/work/calibre/src/calibre/gui2/convert/metadata_ui.py:181
msgid ""
"Change the author(s) of this book. Multiple authors should be separated by a "
"comma"
@@ -4947,20 +5185,20 @@ msgstr ""
"A könyv szerzőjének megváltoztatása. Több szerző esetén vesszővel kell "
"azokat elválasztani"
-#: /home/kovid/work/calibre/src/calibre/gui2/convert/metadata_ui.py:177
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:170
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single_ui.py:374
+#: /home/kovid/work/calibre/src/calibre/gui2/convert/metadata_ui.py:182
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:239
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single_ui.py:387
msgid "&Publisher: "
msgstr "&Kiadó: "
-#: /home/kovid/work/calibre/src/calibre/gui2/convert/metadata_ui.py:178
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single_ui.py:375
+#: /home/kovid/work/calibre/src/calibre/gui2/convert/metadata_ui.py:183
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single_ui.py:388
msgid "Ta&gs: "
msgstr "Címkék: "
-#: /home/kovid/work/calibre/src/calibre/gui2/convert/metadata_ui.py:179
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:172
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single_ui.py:376
+#: /home/kovid/work/calibre/src/calibre/gui2/convert/metadata_ui.py:184
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:241
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single_ui.py:389
msgid ""
"Tags categorize the book. This is particularly useful while searching. "
"
They can be any words or phrases, separated by commas."
@@ -4968,23 +5206,23 @@ msgstr ""
"A könyv címkéi. Nagyon hasznos keresésnél.
Lehet szó vagy kifejezés "
"vesszővel elválasztva."
-#: /home/kovid/work/calibre/src/calibre/gui2/convert/metadata_ui.py:180
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:179
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single_ui.py:379
+#: /home/kovid/work/calibre/src/calibre/gui2/convert/metadata_ui.py:185
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:248
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single_ui.py:392
msgid "&Series:"
msgstr "&Sorozat:"
-#: /home/kovid/work/calibre/src/calibre/gui2/convert/metadata_ui.py:181
-#: /home/kovid/work/calibre/src/calibre/gui2/convert/metadata_ui.py:182
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:180
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:181
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single_ui.py:380
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single_ui.py:381
+#: /home/kovid/work/calibre/src/calibre/gui2/convert/metadata_ui.py:186
+#: /home/kovid/work/calibre/src/calibre/gui2/convert/metadata_ui.py:187
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:249
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:250
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single_ui.py:393
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single_ui.py:394
msgid "List of known series. You can add new series."
msgstr "Ismert sotozatok listája. Hozzáadhat újakat is."
-#: /home/kovid/work/calibre/src/calibre/gui2/convert/metadata_ui.py:183
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single_ui.py:386
+#: /home/kovid/work/calibre/src/calibre/gui2/convert/metadata_ui.py:188
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single_ui.py:399
msgid "Book "
msgstr "Könyv "
@@ -4992,7 +5230,7 @@ msgstr "Könyv "
msgid "MOBI Output"
msgstr "MOBI kimenet"
-#: /home/kovid/work/calibre/src/calibre/gui2/convert/mobi_output.py:42
+#: /home/kovid/work/calibre/src/calibre/gui2/convert/mobi_output.py:43
msgid "Default"
msgstr "Alapértelmezett"
@@ -5143,7 +5381,7 @@ msgid "Regex:"
msgstr "Reguláris kifejezés:"
#: /home/kovid/work/calibre/src/calibre/gui2/convert/regex_builder_ui.py:55
-#: /home/kovid/work/calibre/src/calibre/gui2/filename_pattern_ui.py:117
+#: /home/kovid/work/calibre/src/calibre/gui2/filename_pattern_ui.py:122
msgid "Test"
msgstr "Teszt"
@@ -5155,18 +5393,18 @@ msgstr "Konvertálás"
msgid "Options specific to the input format."
msgstr "A bemeneti formátumnak megfelelő beállítások"
-#: /home/kovid/work/calibre/src/calibre/gui2/convert/single_ui.py:112
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/book_info_ui.py:64
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/comicconf_ui.py:91
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/progress_ui.py:48
+#: /home/kovid/work/calibre/src/calibre/gui2/convert/single_ui.py:117
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/book_info_ui.py:69
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/comicconf_ui.py:96
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/progress_ui.py:53
msgid "Dialog"
msgstr "Párbeszédablak"
-#: /home/kovid/work/calibre/src/calibre/gui2/convert/single_ui.py:113
+#: /home/kovid/work/calibre/src/calibre/gui2/convert/single_ui.py:118
msgid "&Input format:"
msgstr "Bemeneti formátum:"
-#: /home/kovid/work/calibre/src/calibre/gui2/convert/single_ui.py:114
+#: /home/kovid/work/calibre/src/calibre/gui2/convert/single_ui.py:119
msgid "Use &saved conversion settings for individual books"
msgstr ""
@@ -5183,67 +5421,71 @@ msgid ""
"Fine tune the detection of chapter headings and other document structure."
msgstr "Fejezetek és más dokumentum elemek felismerésének finomhangolása"
-#: /home/kovid/work/calibre/src/calibre/gui2/convert/structure_detection.py:35
+#: /home/kovid/work/calibre/src/calibre/gui2/convert/structure_detection.py:37
msgid "Detect chapters at (XPath expression):"
msgstr "Fejezetek felismerése (XPath kifejezés):"
-#: /home/kovid/work/calibre/src/calibre/gui2/convert/structure_detection.py:36
+#: /home/kovid/work/calibre/src/calibre/gui2/convert/structure_detection.py:38
msgid "Insert page breaks before (XPath expression):"
msgstr "Oldaltörés beszúrása a következő elé (XPath kifejezés):"
-#: /home/kovid/work/calibre/src/calibre/gui2/convert/structure_detection.py:38
+#: /home/kovid/work/calibre/src/calibre/gui2/convert/structure_detection.py:40
msgid "Header regular expression:"
msgstr "Fejléc reguláris kifejezés:"
-#: /home/kovid/work/calibre/src/calibre/gui2/convert/structure_detection.py:41
+#: /home/kovid/work/calibre/src/calibre/gui2/convert/structure_detection.py:43
msgid "Footer regular expression:"
msgstr "Lábléc reguláris kifejezés:"
-#: /home/kovid/work/calibre/src/calibre/gui2/convert/structure_detection.py:57
-#: /home/kovid/work/calibre/src/calibre/gui2/widgets.py:76
+#: /home/kovid/work/calibre/src/calibre/gui2/convert/structure_detection.py:59
+#: /home/kovid/work/calibre/src/calibre/gui2/widgets.py:87
msgid "Invalid regular expression"
msgstr "Érvénytelen reguláris kifejezés"
-#: /home/kovid/work/calibre/src/calibre/gui2/convert/structure_detection.py:58
-#: /home/kovid/work/calibre/src/calibre/gui2/widgets.py:77
+#: /home/kovid/work/calibre/src/calibre/gui2/convert/structure_detection.py:60
+#: /home/kovid/work/calibre/src/calibre/gui2/widgets.py:88
msgid "Invalid regular expression: %s"
msgstr "Érvénytelen reguláris kifejezés: %s"
-#: /home/kovid/work/calibre/src/calibre/gui2/convert/structure_detection.py:63
+#: /home/kovid/work/calibre/src/calibre/gui2/convert/structure_detection.py:65
#: /home/kovid/work/calibre/src/calibre/gui2/convert/toc.py:39
msgid "Invalid XPath"
msgstr "Érvénytelen XPath kifejezés"
-#: /home/kovid/work/calibre/src/calibre/gui2/convert/structure_detection.py:64
+#: /home/kovid/work/calibre/src/calibre/gui2/convert/structure_detection.py:66
#: /home/kovid/work/calibre/src/calibre/gui2/convert/toc.py:40
msgid "The XPath expression %s is invalid."
msgstr "A következő XPath kifejezés érvénytelen: %s."
-#: /home/kovid/work/calibre/src/calibre/gui2/convert/structure_detection_ui.py:61
+#: /home/kovid/work/calibre/src/calibre/gui2/convert/structure_detection_ui.py:81
msgid "Chapter &mark:"
msgstr "Fejezet jelölő:"
-#: /home/kovid/work/calibre/src/calibre/gui2/convert/structure_detection_ui.py:62
+#: /home/kovid/work/calibre/src/calibre/gui2/convert/structure_detection_ui.py:82
msgid "Remove first &image"
msgstr "Első kép eltávolítása"
-#: /home/kovid/work/calibre/src/calibre/gui2/convert/structure_detection_ui.py:63
+#: /home/kovid/work/calibre/src/calibre/gui2/convert/structure_detection_ui.py:83
msgid "Insert &metadata as page at start of book"
msgstr "Metaadatok beszúrása a könyv elejére külön oldalként"
-#: /home/kovid/work/calibre/src/calibre/gui2/convert/structure_detection_ui.py:64
-msgid "&Preprocess input file to possibly improve structure detection"
-msgstr ""
-"A bementi fájl előfeldolgozása az esetlegesen jobb struktúra felismeréshez"
-
-#: /home/kovid/work/calibre/src/calibre/gui2/convert/structure_detection_ui.py:65
+#: /home/kovid/work/calibre/src/calibre/gui2/convert/structure_detection_ui.py:84
msgid "Remove F&ooter"
msgstr "Lábléc eltávolítása"
-#: /home/kovid/work/calibre/src/calibre/gui2/convert/structure_detection_ui.py:66
+#: /home/kovid/work/calibre/src/calibre/gui2/convert/structure_detection_ui.py:85
msgid "Remove H&eader"
msgstr "Fejléc eltávolítása"
+#: /home/kovid/work/calibre/src/calibre/gui2/convert/structure_detection_ui.py:86
+msgid "Line &un-wrap factor during preprocess:"
+msgstr ""
+
+#: /home/kovid/work/calibre/src/calibre/gui2/convert/structure_detection_ui.py:87
+msgid "&Preprocess input file to possibly improve structure detection"
+msgstr ""
+"A bementi fájl előfeldolgozása az esetlegesen jobb struktúra felismeréshez"
+
#: /home/kovid/work/calibre/src/calibre/gui2/convert/toc.py:16
msgid ""
"Table of\n"
@@ -5329,18 +5571,18 @@ msgstr "&Maximális sorhossz:"
msgid "Force maximum line length"
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/gui2/convert/xexp_edit_ui.py:51
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/book_info_ui.py:65
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/book_info_ui.py:66
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/choose_format_ui.py:41
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/confirm_delete_ui.py:49
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/password_ui.py:57
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/progress_ui.py:49
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/progress_ui.py:50
+#: /home/kovid/work/calibre/src/calibre/gui2/convert/xexp_edit_ui.py:56
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/book_info_ui.py:70
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/book_info_ui.py:71
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/choose_format_ui.py:46
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/confirm_delete_ui.py:54
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/password_ui.py:62
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/progress_ui.py:54
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/progress_ui.py:55
msgid "TextLabel"
msgstr "TextLabel"
-#: /home/kovid/work/calibre/src/calibre/gui2/convert/xexp_edit_ui.py:52
+#: /home/kovid/work/calibre/src/calibre/gui2/convert/xexp_edit_ui.py:57
msgid "Use a wizard to help construct the XPath expression"
msgstr "Varázsló használata az XPath kifejezés létrehozásához"
@@ -5441,16 +5683,12 @@ msgid "Undefined"
msgstr ""
#: /home/kovid/work/calibre/src/calibre/gui2/custom_column_widgets.py:59
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/config/create_ct_column_ui.py:131
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/config/create_ct_column_ui.py:133
#: /home/kovid/work/calibre/src/calibre/library/server/mobile.py:241
#: /home/kovid/work/calibre/src/calibre/library/server/xml.py:119
msgid "Yes"
msgstr ""
#: /home/kovid/work/calibre/src/calibre/gui2/custom_column_widgets.py:59
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/config/create_ct_column_ui.py:132
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/config/create_ct_column_ui.py:134
#: /home/kovid/work/calibre/src/calibre/library/server/mobile.py:243
#: /home/kovid/work/calibre/src/calibre/library/server/xml.py:121
msgid "No"
@@ -5473,7 +5711,7 @@ msgid " index:"
msgstr ""
#: /home/kovid/work/calibre/src/calibre/gui2/custom_column_widgets.py:451
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:188
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:257
msgid "Automatically number books in this series"
msgstr ""
@@ -5489,97 +5727,97 @@ msgstr ""
msgid "tags to remove"
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/gui2/device.py:49
+#: /home/kovid/work/calibre/src/calibre/gui2/device.py:48
#: /home/kovid/work/calibre/src/calibre/utils/ipc/job.py:136
msgid "No details available."
msgstr "Részletek nem elérhetőek."
-#: /home/kovid/work/calibre/src/calibre/gui2/device.py:166
+#: /home/kovid/work/calibre/src/calibre/gui2/device.py:165
msgid "Device no longer connected."
msgstr "Az eszköz már nem csatlakozik."
-#: /home/kovid/work/calibre/src/calibre/gui2/device.py:284
+#: /home/kovid/work/calibre/src/calibre/gui2/device.py:283
msgid "Get device information"
msgstr "Eszköz-információk lekérdezése"
-#: /home/kovid/work/calibre/src/calibre/gui2/device.py:295
+#: /home/kovid/work/calibre/src/calibre/gui2/device.py:294
msgid "Get list of books on device"
msgstr "Az eszközön lévő könyvek listájának letöltése"
-#: /home/kovid/work/calibre/src/calibre/gui2/device.py:305
+#: /home/kovid/work/calibre/src/calibre/gui2/device.py:304
msgid "Get annotations from device"
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/gui2/device.py:314
+#: /home/kovid/work/calibre/src/calibre/gui2/device.py:313
msgid "Send metadata to device"
msgstr "Metaadatok küldése az eszközre"
-#: /home/kovid/work/calibre/src/calibre/gui2/device.py:319
+#: /home/kovid/work/calibre/src/calibre/gui2/device.py:318
msgid "Send collections to device"
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/gui2/device.py:343
+#: /home/kovid/work/calibre/src/calibre/gui2/device.py:342
msgid "Upload %d books to device"
msgstr "%d könyv feltöltése az eszközre"
-#: /home/kovid/work/calibre/src/calibre/gui2/device.py:358
+#: /home/kovid/work/calibre/src/calibre/gui2/device.py:357
msgid "Delete books from device"
msgstr "Könyvek törlése az eszközről"
-#: /home/kovid/work/calibre/src/calibre/gui2/device.py:375
+#: /home/kovid/work/calibre/src/calibre/gui2/device.py:374
msgid "Download books from device"
msgstr "Könyvek letöltése az eszközről"
-#: /home/kovid/work/calibre/src/calibre/gui2/device.py:385
+#: /home/kovid/work/calibre/src/calibre/gui2/device.py:384
msgid "View book on device"
msgstr "Könyv megnyitása ez eszközön"
-#: /home/kovid/work/calibre/src/calibre/gui2/device.py:419
+#: /home/kovid/work/calibre/src/calibre/gui2/device.py:418
msgid "Set default send to device action"
msgstr "Alapértelmezett eszközre küldési művelet beállítása"
-#: /home/kovid/work/calibre/src/calibre/gui2/device.py:425
+#: /home/kovid/work/calibre/src/calibre/gui2/device.py:424
msgid "Send to main memory"
msgstr "Küldés a belső memóriába"
-#: /home/kovid/work/calibre/src/calibre/gui2/device.py:427
+#: /home/kovid/work/calibre/src/calibre/gui2/device.py:426
msgid "Send to storage card A"
msgstr "Küldés az 'A' memóriakártyára"
-#: /home/kovid/work/calibre/src/calibre/gui2/device.py:429
+#: /home/kovid/work/calibre/src/calibre/gui2/device.py:428
msgid "Send to storage card B"
msgstr "Küldés a 'B' memóriakártyára"
-#: /home/kovid/work/calibre/src/calibre/gui2/device.py:434
-#: /home/kovid/work/calibre/src/calibre/gui2/device.py:443
+#: /home/kovid/work/calibre/src/calibre/gui2/device.py:433
+#: /home/kovid/work/calibre/src/calibre/gui2/device.py:442
msgid "Main Memory"
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/gui2/device.py:454
+#: /home/kovid/work/calibre/src/calibre/gui2/device.py:453
msgid "Send and delete from library"
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/gui2/device.py:455
+#: /home/kovid/work/calibre/src/calibre/gui2/device.py:454
msgid "Send specific format"
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/gui2/device.py:491
+#: /home/kovid/work/calibre/src/calibre/gui2/device.py:490
msgid "Eject device"
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/gui2/device.py:609
+#: /home/kovid/work/calibre/src/calibre/gui2/device.py:608
msgid "Error communicating with device"
msgstr "Hiba az eszközkapcsolatban"
-#: /home/kovid/work/calibre/src/calibre/gui2/device.py:636
+#: /home/kovid/work/calibre/src/calibre/gui2/device.py:629
msgid "Select folder to open as device"
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/gui2/device.py:684
+#: /home/kovid/work/calibre/src/calibre/gui2/device.py:676
msgid "Error talking to device"
msgstr "Hiba a kapcsolatban"
-#: /home/kovid/work/calibre/src/calibre/gui2/device.py:685
+#: /home/kovid/work/calibre/src/calibre/gui2/device.py:677
msgid ""
"There was a temporary error talking to the device. Please unplug and "
"reconnect the device and or reboot."
@@ -5587,11 +5825,11 @@ msgstr ""
"Hiba az eszközkapcsolatban. Válassza le majd csatlakoztassa újra az eszközt "
"és/vagy indítssa újra az eszközt, esetleg a programot."
-#: /home/kovid/work/calibre/src/calibre/gui2/device.py:724
+#: /home/kovid/work/calibre/src/calibre/gui2/device.py:720
msgid "Device: "
msgstr "Eszköz: "
-#: /home/kovid/work/calibre/src/calibre/gui2/device.py:726
+#: /home/kovid/work/calibre/src/calibre/gui2/device.py:722
msgid " detected."
msgstr " felismerve"
@@ -5630,7 +5868,7 @@ msgid "Attached, you will find the e-book"
msgstr "Az ebook csatolva"
#: /home/kovid/work/calibre/src/calibre/gui2/device.py:880
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/config/__init__.py:180
+#: /home/kovid/work/calibre/src/calibre/gui2/preferences/plugins.py:107
msgid "by"
msgstr ""
@@ -5644,10 +5882,10 @@ msgstr "Email küldése:"
#: /home/kovid/work/calibre/src/calibre/gui2/device.py:924
#: /home/kovid/work/calibre/src/calibre/gui2/device.py:932
-#: /home/kovid/work/calibre/src/calibre/gui2/device.py:1026
-#: /home/kovid/work/calibre/src/calibre/gui2/device.py:1088
-#: /home/kovid/work/calibre/src/calibre/gui2/device.py:1207
-#: /home/kovid/work/calibre/src/calibre/gui2/device.py:1215
+#: /home/kovid/work/calibre/src/calibre/gui2/device.py:1025
+#: /home/kovid/work/calibre/src/calibre/gui2/device.py:1087
+#: /home/kovid/work/calibre/src/calibre/gui2/device.py:1206
+#: /home/kovid/work/calibre/src/calibre/gui2/device.py:1214
msgid "No suitable formats"
msgstr "Nincs megfelelő formátum"
@@ -5676,39 +5914,39 @@ msgstr "Hiba a következő könyvek email-ben való elküldése közben:"
msgid "Sent by email:"
msgstr "Emailbe elküldve:"
-#: /home/kovid/work/calibre/src/calibre/gui2/device.py:985
+#: /home/kovid/work/calibre/src/calibre/gui2/device.py:984
msgid "News:"
msgstr "Hírek:"
-#: /home/kovid/work/calibre/src/calibre/gui2/device.py:986
+#: /home/kovid/work/calibre/src/calibre/gui2/device.py:985
msgid "Attached is the"
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/gui2/device.py:997
+#: /home/kovid/work/calibre/src/calibre/gui2/device.py:996
msgid "Sent news to"
msgstr "Hírek elküldve:"
-#: /home/kovid/work/calibre/src/calibre/gui2/device.py:1027
-#: /home/kovid/work/calibre/src/calibre/gui2/device.py:1089
-#: /home/kovid/work/calibre/src/calibre/gui2/device.py:1208
+#: /home/kovid/work/calibre/src/calibre/gui2/device.py:1026
+#: /home/kovid/work/calibre/src/calibre/gui2/device.py:1088
+#: /home/kovid/work/calibre/src/calibre/gui2/device.py:1207
msgid "Auto convert the following books before uploading to the device?"
msgstr ""
"Az eszközre való feltöltés előtt automatikusan konvertáljam a kijelölt "
"könyveket?"
-#: /home/kovid/work/calibre/src/calibre/gui2/device.py:1057
+#: /home/kovid/work/calibre/src/calibre/gui2/device.py:1056
msgid "Sending catalogs to device."
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/gui2/device.py:1121
+#: /home/kovid/work/calibre/src/calibre/gui2/device.py:1120
msgid "Sending news to device."
msgstr "Hírek küldése az eszközre"
-#: /home/kovid/work/calibre/src/calibre/gui2/device.py:1174
+#: /home/kovid/work/calibre/src/calibre/gui2/device.py:1173
msgid "Sending books to device."
msgstr "Könyvek küldése az eszközre."
-#: /home/kovid/work/calibre/src/calibre/gui2/device.py:1216
+#: /home/kovid/work/calibre/src/calibre/gui2/device.py:1215
msgid ""
"Could not upload the following books to the device, as no suitable formats "
"were found. Convert the book(s) to a format supported by your device first."
@@ -5717,38 +5955,38 @@ msgstr ""
"formátumú változatuk. Konvertálja át a nem megfelelőeket valamelyik, az "
"eszközöd által támogatott formátumra."
-#: /home/kovid/work/calibre/src/calibre/gui2/device.py:1278
+#: /home/kovid/work/calibre/src/calibre/gui2/device.py:1277
msgid "No space on device"
msgstr "Nincs elég hely az eszközön"
-#: /home/kovid/work/calibre/src/calibre/gui2/device.py:1279
+#: /home/kovid/work/calibre/src/calibre/gui2/device.py:1278
msgid ""
"
Cannot upload books to device there is no more free space available "
msgstr ""
"
Nem lehet feltölteni könyveket az eszközre, nincs elég szabad hely "
-#: /home/kovid/work/calibre/src/calibre/gui2/device_drivers/configwidget_ui.py:78
+#: /home/kovid/work/calibre/src/calibre/gui2/device_drivers/configwidget_ui.py:83
msgid "Select available formats and their order for this device"
msgstr ""
"Válassza ki az elérhető formátumokat és azok sorrendjét ehhez az eszközhöz"
-#: /home/kovid/work/calibre/src/calibre/gui2/device_drivers/configwidget_ui.py:82
+#: /home/kovid/work/calibre/src/calibre/gui2/device_drivers/configwidget_ui.py:87
msgid "Use sub directories"
msgstr "Almappák használata"
-#: /home/kovid/work/calibre/src/calibre/gui2/device_drivers/configwidget_ui.py:83
+#: /home/kovid/work/calibre/src/calibre/gui2/device_drivers/configwidget_ui.py:88
msgid "Use author sort for author"
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/gui2/device_drivers/configwidget_ui.py:85
+#: /home/kovid/work/calibre/src/calibre/gui2/device_drivers/configwidget_ui.py:90
msgid "Save &template:"
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/add_from_isbn_ui.py:43
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/add_from_isbn_ui.py:48
msgid "Add books by ISBN"
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/add_from_isbn_ui.py:44
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/add_from_isbn_ui.py:49
msgid ""
"
Enter a list of ISBNs in the box to the left, one per line. calibre will "
"automatically create entries for books based on the ISBN and download "
@@ -5756,19 +5994,19 @@ msgid ""
"ignored."
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/add_from_isbn_ui.py:45
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/add_from_isbn_ui.py:50
msgid "&Paste from clipboard"
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/book_info_ui.py:68
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/book_info_ui.py:73
msgid "Fit &cover within view"
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/book_info_ui.py:69
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/book_info_ui.py:74
msgid "&Previous"
msgstr "&Előző"
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/book_info_ui.py:70
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/book_info_ui.py:75
msgid "&Next"
msgstr "&Következő"
@@ -5777,7 +6015,7 @@ msgid "My Books"
msgstr "Könyveim"
#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/catalog_ui.py:69
-#: /home/kovid/work/calibre/src/calibre/gui2/tools.py:289
+#: /home/kovid/work/calibre/src/calibre/gui2/tools.py:301
msgid "Generate catalog"
msgstr ""
@@ -5802,7 +6040,7 @@ msgstr "Katalógus automatiku&s küldése az eszközre"
msgid "Catalog options"
msgstr "Katalógus beállításai"
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/choose_format_ui.py:40
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/choose_format_ui.py:45
msgid "Choose Format"
msgstr "Formátum kiválasztása"
@@ -5850,27 +6088,27 @@ msgstr ""
msgid "%s is not an existing folder"
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/choose_library_ui.py:66
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/choose_library_ui.py:71
msgid "Choose your calibre library"
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/choose_library_ui.py:67
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/choose_library_ui.py:72
msgid "Your calibre library is currently located at {0}"
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/choose_library_ui.py:68
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/choose_library_ui.py:73
msgid "New &Location:"
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/choose_library_ui.py:69
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/choose_library_ui.py:74
msgid "Use &existing library at the new location"
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/choose_library_ui.py:70
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/choose_library_ui.py:75
msgid "&Create an empty library at the new location"
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/choose_library_ui.py:71
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/choose_library_ui.py:76
msgid "&Move current library to new location"
msgstr ""
@@ -5882,15 +6120,15 @@ msgstr "Képregények (CBR/CBZ fájlok) alapértelmezett beállításai"
msgid "Set options for converting %s"
msgstr "A %s konvertálásának beállításai"
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/comicconf_ui.py:92
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/comicconf_ui.py:97
msgid "&Title:"
msgstr "&Cím:"
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/comicconf_ui.py:93
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/comicconf_ui.py:98
msgid "&Author(s):"
msgstr "Szerző(k)"
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/comicconf_ui.py:95
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/comicconf_ui.py:100
msgid "&Profile:"
msgstr "&Profil:"
@@ -5898,1153 +6136,11 @@ msgstr "&Profil:"
msgid "Edit Comments"
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/config/__init__.py:173
-msgid "%(plugin_type)s %(plugins)s"
-msgstr "%(plugin_type)s %(plugins)s"
-
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/config/__init__.py:174
-msgid "plugins"
-msgstr "pluginok"
-
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/config/__init__.py:183
-msgid ""
-"\n"
-"Customization: "
-msgstr ""
-
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/config/__init__.py:198
-msgid "General"
-msgstr "Általános"
-
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/config/__init__.py:200
-msgid "Conversion"
-msgstr "Konvertálás"
-
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/config/__init__.py:201
-msgid ""
-"Email\n"
-"Delivery"
-msgstr ""
-"Küldés\n"
-"emailben"
-
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/config/__init__.py:202
-msgid "Add/Save"
-msgstr "Hozzáadás/Mentés"
-
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/config/__init__.py:203
-msgid "Advanced"
-msgstr "Haladó"
-
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/config/__init__.py:204
-msgid ""
-"Content\n"
-"Server"
-msgstr ""
-"Tartalom\n"
-"kiszolgáló"
-
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/config/__init__.py:205
-msgid "Plugins"
-msgstr "Plugin-ok"
-
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/config/__init__.py:229
-msgid "Auto send"
-msgstr "Auto küld"
-
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/config/__init__.py:229
-msgid "Email"
-msgstr "Email"
-
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/config/__init__.py:234
-msgid "Formats to email. The first matching format will be sent."
-msgstr ""
-"Emailben elkündendő formátum. Az első létező formátum kerül elküldésre."
-
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/config/__init__.py:235
-msgid ""
-"If checked, downloaded news will be automatically mailed to this email "
-"address (provided it is in one of the listed formats)."
-msgstr ""
-"Ha be van jelölve, akkor a letöltött hírek automatikusan el lesznek "
-"küldve emailben erre a címre (ha létezik a 'Formátumok' oszlopban beírtaknak "
-"megfelelő)"
-
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/config/__init__.py:309
-msgid "new email address"
-msgstr "új email cím"
-
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/config/__init__.py:492
-#: /home/kovid/work/calibre/src/calibre/gui2/preferences/look_feel.py:24
-msgid "Wide"
-msgstr ""
-
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/config/__init__.py:493
-#: /home/kovid/work/calibre/src/calibre/gui2/preferences/look_feel.py:24
-msgid "Narrow"
-msgstr ""
-
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/config/__init__.py:509
-#: /home/kovid/work/calibre/src/calibre/gui2/preferences/look_feel.py:50
-msgid "Medium"
-msgstr "Közepes"
-
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/config/__init__.py:509
-#: /home/kovid/work/calibre/src/calibre/gui2/preferences/look_feel.py:50
-msgid "Small"
-msgstr "Kicsi"
-
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/config/__init__.py:510
-#: /home/kovid/work/calibre/src/calibre/gui2/preferences/look_feel.py:51
-msgid "Large"
-msgstr "Nagy"
-
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/config/__init__.py:516
-#: /home/kovid/work/calibre/src/calibre/gui2/preferences/look_feel.py:54
-msgid "Always"
-msgstr ""
-
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/config/__init__.py:516
-#: /home/kovid/work/calibre/src/calibre/gui2/preferences/look_feel.py:54
-msgid "Automatic"
-msgstr ""
-
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/config/__init__.py:517
-#: /home/kovid/work/calibre/src/calibre/gui2/preferences/look_feel.py:55
-msgid "Never"
-msgstr ""
-
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/config/__init__.py:530
-msgid "Toolbars/Context menus"
-msgstr ""
-
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/config/__init__.py:544
-#: /home/kovid/work/calibre/src/calibre/gui2/preferences/behavior.py:156
-msgid "Done"
-msgstr ""
-
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/config/__init__.py:545
-#: /home/kovid/work/calibre/src/calibre/gui2/preferences/behavior.py:157
-msgid "Confirmation dialogs have all been reset"
-msgstr ""
-
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/config/__init__.py:550
-msgid "System port selected"
-msgstr ""
-
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/config/__init__.py:551
-msgid ""
-"The value %d you have chosen for the content server port is a system "
-"port. Your operating system may not allow the server to run on this "
-"port. To be safe choose a port number larger than 1024."
-msgstr ""
-
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/config/__init__.py:571
-msgid "Failed to install command line tools."
-msgstr "A parancssori eszközök telepítése nem sikerült."
-
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/config/__init__.py:574
-msgid "Command line tools installed"
-msgstr "Parancssori eszközök installálva"
-
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/config/__init__.py:575
-msgid "Command line tools installed in"
-msgstr "Parancssori eszközök installálva:"
-
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/config/__init__.py:576
-msgid ""
-"If you move calibre.app, you have to re-install the command line tools."
-msgstr ""
-"Ha áthelyezi a calibre.app-ot, akkor a parancssori eszközöket újra kell "
-"installálnia."
-
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/config/__init__.py:627
-msgid "No valid plugin path"
-msgstr "Nincs érvényes plugin elérési útvonal"
-
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/config/__init__.py:628
-msgid "%s is not a valid plugin path"
-msgstr "Nem érvényes plugin elérési út: %s"
-
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/config/__init__.py:631
-msgid "Choose plugin"
-msgstr "Plugin kiválasztása"
-
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/config/__init__.py:643
-msgid "Plugin cannot be disabled"
-msgstr "Ezt a plugint nem lehet letiltani"
-
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/config/__init__.py:644
-msgid "The plugin: %s cannot be disabled"
-msgstr "A következő plugint nem lehet letiltani: %s"
-
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/config/__init__.py:653
-msgid "Plugin not customizable"
-msgstr "A pluginnak nincsenek beállításai"
-
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/config/__init__.py:654
-msgid "Plugin: %s does not need customization"
-msgstr "A %s pluginnak nincsenek beállításai"
-
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/config/__init__.py:662
-msgid "Customize"
-msgstr ""
-
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/config/__init__.py:700
-msgid "Cannot remove builtin plugin"
-msgstr "A beépített pluginok nem távolíthatóak el"
-
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/config/__init__.py:701
-msgid " cannot be removed. It is a builtin plugin. Try disabling it instead."
-msgstr " nem törölhető. Ez egy beépített plugin. Tiltsd le inkább."
-
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/config/__init__.py:716
-msgid "Invalid tweaks"
-msgstr ""
-
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/config/__init__.py:717
-msgid ""
-"The tweaks you entered are invalid, try resetting the tweaks to default and "
-"changing them one by one until you find the invalid setting."
-msgstr ""
-
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/config/__init__.py:747
-msgid "You must select a column to delete it"
-msgstr ""
-
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/config/__init__.py:752
-msgid "The selected column is not a custom column"
-msgstr ""
-
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/config/__init__.py:754
-msgid "Do you really want to delete column %s and all its data?"
-msgstr ""
-
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/config/__init__.py:821
-msgid "Error log:"
-msgstr "Hibanapló:"
-
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/config/__init__.py:828
-msgid "Access log:"
-msgstr "Hozzáférési naplófájl:"
-
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/config/__init__.py:856
-#: /home/kovid/work/calibre/src/calibre/gui2/ui.py:319
-msgid "Failed to start content server"
-msgstr "A tartalomkiszolgáló indítása nem sikerült"
-
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/config/__init__.py:881
-msgid "Invalid size"
-msgstr "Érvénytelen méret"
-
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/config/__init__.py:882
-msgid "The size %s is invalid. must be of the form widthxheight"
-msgstr ""
-"A következő méret érvénytelen: %s. A méretnek szélességxmagasság formátumban "
-"kell lennie."
-
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/config/__init__.py:951
-msgid "Must restart"
-msgstr ""
-
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/config/__init__.py:952
-msgid ""
-"The changes you made require that Calibre be restarted. Please restart as "
-"soon as practical."
-msgstr ""
-
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/config/__init__.py:986
-msgid "Checking database integrity"
-msgstr "Adatbázis ellenőrzése"
-
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/config/__init__.py:1006
-msgid "Failed to check database integrity"
-msgstr "Az adatbázis ellenőrzése nem sikerült"
-
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/config/__init__.py:1011
-msgid "Some inconsistencies found"
-msgstr "Néhány hibát találtam"
-
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/config/__init__.py:1012
-msgid ""
-"The following books had formats listed in the database that are not actually "
-"available. The entries for the formats have been removed. You should check "
-"them manually. This can happen if you manipulate the files in the library "
-"folder directly."
-msgstr ""
-"A következő könyveknek van olyan formátuma az adatbázisban, amelyek "
-"valójában nem léteznek. Le kell ellenőrznie manuálisan. Ez akkor fordulhat "
-"elő, ha közvetlenül törölte a fájlrendszerből ezeket."
-
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/config/add_save_ui.py:133
-msgid "TabWidget"
-msgstr "TabWidget"
-
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/config/add_save_ui.py:134
-msgid ""
-"Here you can control how calibre will read metadata from the files you add "
-"to it. calibre can either read metadata from the contents of the file, or "
-"from the filename."
-msgstr ""
-"Itt azt tudja beállítani, hogy a calibre hogyan olvassa ki a metaadatokat a "
-"fájlokból. A calibre a fájlból, és a fájlnévből is tud metaadatokat olvasni."
-
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/config/add_save_ui.py:135
-msgid "Read metadata only from &file name"
-msgstr "Metaadatok olvasása csak a fájlnévből"
-
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/config/add_save_ui.py:136
-msgid ""
-"Swap the firstname and lastname of the author. This affects only metadata "
-"read from file names."
-msgstr ""
-
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/config/add_save_ui.py:137
-msgid "&Swap author firstname and lastname"
-msgstr ""
-
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/config/add_save_ui.py:138
-msgid ""
-"If an existing book with a similar title and author is found that does not "
-"have the format being added, the format is added\n"
-"to the existing book, instead of creating a new entry. If the existing book "
-"already has the format, then it is silently ignored.\n"
-"\n"
-"Title match ignores leading indefinite articles (\"the\", \"a\", \"an\"), "
-"punctuation, case, etc. Author match is exact."
-msgstr ""
-
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/config/add_save_ui.py:142
-msgid ""
-"If books with similar titles and authors found, &merge the new files "
-"automatically"
-msgstr ""
-
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/config/add_save_ui.py:143
-msgid "&Configure metadata from file name"
-msgstr "Beállítások a metaadatok kiolvasására a fáljnévből"
-
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/config/add_save_ui.py:144
-msgid "&Adding books"
-msgstr "Könyvek hozzáadása"
-
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/config/add_save_ui.py:145
-msgid ""
-"Here you can control how calibre will save your books when you click the "
-"Save to Disk button:"
-msgstr ""
-"Itt azt tudja beállítani, hogy mit csináljon a calibre, amikor a 'Mentés "
-"lemezre' gombra kattint:"
-
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/config/add_save_ui.py:146
-msgid "Save &cover separately"
-msgstr "Borító mentése külön fájlba"
-
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/config/add_save_ui.py:147
-msgid "Update &metadata in saved copies"
-msgstr "Metaadatok frissítése a mentett másolatokban"
-
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/config/add_save_ui.py:148
-msgid "Save metadata in &OPF file"
-msgstr "Metaadatok mentése &OPF fájlba"
-
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/config/add_save_ui.py:149
-msgid "Convert non-English characters to &English equivalents"
-msgstr "Nem angol karakterek konvertálása angol karakterekké"
-
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/config/add_save_ui.py:150
-msgid "Format &dates as:"
-msgstr "Dátumok formátuma:"
-
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/config/add_save_ui.py:151
-msgid "File &formats to save:"
-msgstr "Mentendő könyvformátumok:"
-
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/config/add_save_ui.py:152
-msgid "Replace space with &underscores"
-msgstr "Szóköz cseréje alsóvonallal"
-
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/config/add_save_ui.py:153
-msgid "Change paths to &lowercase"
-msgstr "Elérési út kisbetűssé alakítása"
-
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/config/add_save_ui.py:154
-msgid "&Saving books"
-msgstr "Könyvek mentése"
-
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/config/add_save_ui.py:155
-msgid "Metadata &management:"
-msgstr ""
-
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/config/add_save_ui.py:156
-msgid "Manual management"
-msgstr ""
-
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/config/add_save_ui.py:157
-msgid "Only on send"
-msgstr ""
-
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/config/add_save_ui.py:158
-msgid "Automatic management"
-msgstr ""
-
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/config/add_save_ui.py:159
-msgid ""
-"
Manual Management: Calibre updates the metadata and adds "
-"collections only when a book is sent. With this option, calibre will never "
-"remove a collection.
\n"
-"
Only on send: Calibre updates metadata and adds/removes "
-"collections for a book only when it is sent to the device.
\n"
-"
Automatic management: Calibre automatically keeps metadata on the "
-"device in sync with the calibre library, on every connect
"
-msgstr ""
-
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/config/add_save_ui.py:162
-msgid ""
-"Here you can control how calibre will save your books when you click the "
-"Send to Device button. This setting can be overriden for individual devices "
-"by customizing the device interface plugins in Preferences->Plugins"
-msgstr ""
-
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/config/add_save_ui.py:163
-msgid "Sending to &device"
-msgstr ""
-
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/config/config_ui.py:613
-#: /home/kovid/work/calibre/src/calibre/gui2/preferences/behavior_ui.py:139
-msgid "Show notification when &new version is available"
-msgstr "Figyelmeztetés, ha az új verzió elérhető"
-
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/config/config_ui.py:614
-#: /home/kovid/work/calibre/src/calibre/gui2/preferences/behavior_ui.py:138
-msgid "Download &social metadata (tags/ratings/etc.) by default"
-msgstr ""
-"Közösségi metaadatok letöltése (címkék, értékelés stb.) alapértelmezettként"
-
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/config/config_ui.py:615
-#: /home/kovid/work/calibre/src/calibre/gui2/preferences/behavior_ui.py:137
-msgid "&Overwrite author and title by default when fetching metadata"
-msgstr ""
-
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/config/config_ui.py:616
-#: /home/kovid/work/calibre/src/calibre/gui2/preferences/behavior_ui.py:142
-msgid "Default network &timeout:"
-msgstr "Alapértelmezett hálózati időtúllépés:"
-
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/config/config_ui.py:617
-#: /home/kovid/work/calibre/src/calibre/gui2/preferences/behavior_ui.py:143
-msgid ""
-"Set the default timeout for network fetches (i.e. anytime we go out to the "
-"internet to get information)"
-msgstr ""
-"Alapértelmezett hálózati időtúllépés a letöltéshez (bármilyen internetet "
-"igénylő művelethez)"
-
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/config/config_ui.py:618
-#: /home/kovid/work/calibre/src/calibre/gui2/preferences/behavior_ui.py:144
-msgid " seconds"
-msgstr " másodperc"
-
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/config/config_ui.py:619
-#: /home/kovid/work/calibre/src/calibre/gui2/preferences/look_feel_ui.py:100
-msgid "Choose &language (requires restart):"
-msgstr "Nyelv kiválasztása (újraindítás szükséges):"
-
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/config/config_ui.py:620
-#: /home/kovid/work/calibre/src/calibre/gui2/preferences/behavior.py:31
-#: /home/kovid/work/calibre/src/calibre/gui2/preferences/behavior_ui.py:145
-msgid "Normal"
-msgstr "Normál"
-
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/config/config_ui.py:621
-#: /home/kovid/work/calibre/src/calibre/gui2/preferences/behavior.py:31
-#: /home/kovid/work/calibre/src/calibre/gui2/preferences/behavior_ui.py:146
-msgid "High"
-msgstr "Magas"
-
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/config/config_ui.py:622
-#: /home/kovid/work/calibre/src/calibre/gui2/preferences/behavior.py:31
-#: /home/kovid/work/calibre/src/calibre/gui2/preferences/behavior_ui.py:147
-msgid "Low"
-msgstr "Alacsony"
-
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/config/config_ui.py:623
-#: /home/kovid/work/calibre/src/calibre/gui2/preferences/behavior_ui.py:148
-msgid "Job &priority:"
-msgstr "Műveletek prioritása:"
-
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/config/config_ui.py:624
-#: /home/kovid/work/calibre/src/calibre/gui2/preferences/behavior_ui.py:149
-msgid "Preferred &output format:"
-msgstr "Kimeneti formátum:"
-
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/config/config_ui.py:625
-#: /home/kovid/work/calibre/src/calibre/gui2/preferences/behavior_ui.py:153
-msgid "Tags to apply when adding a book:"
-msgstr ""
-
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/config/config_ui.py:626
-#: /home/kovid/work/calibre/src/calibre/gui2/preferences/behavior_ui.py:152
-msgid ""
-"A comma-separated list of tags that will be applied to books added to the "
-"library"
-msgstr ""
-
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/config/config_ui.py:627
-#: /home/kovid/work/calibre/src/calibre/gui2/preferences/behavior_ui.py:154
-msgid "Reset all disabled &confirmation dialogs"
-msgstr ""
-
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/config/config_ui.py:628
-#: /home/kovid/work/calibre/src/calibre/gui2/preferences/behavior_ui.py:155
-msgid "Preferred &input format order:"
-msgstr "Bemeneti formátumok preferált sorrendje:"
-
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/config/config_ui.py:631
-#: /home/kovid/work/calibre/src/calibre/gui2/preferences/look_feel_ui.py:98
-msgid "User Interface &layout (needs restart):"
-msgstr ""
-
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/config/config_ui.py:632
-#: /home/kovid/work/calibre/src/calibre/gui2/preferences/look_feel_ui.py:99
-msgid "&Number of covers to show in browse mode (needs restart):"
-msgstr ""
-"Megjelenített borítók száma böngészéskor (borító böngészés üzemmódban, "
-"újraindítást igényel)"
-
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/config/config_ui.py:633
-#: /home/kovid/work/calibre/src/calibre/gui2/preferences/behavior_ui.py:150
-msgid "Restriction to apply when the current library is opened:"
-msgstr ""
-
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/config/config_ui.py:634
-#: /home/kovid/work/calibre/src/calibre/gui2/preferences/behavior_ui.py:151
-msgid ""
-"Apply this restriction on calibre startup if the current library is being "
-"used. Also applied when switching to this library. Note that this setting is "
-"per library. "
-msgstr ""
-
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/config/config_ui.py:635
-#: /home/kovid/work/calibre/src/calibre/gui2/preferences/look_feel_ui.py:102
-msgid "Disable all animations. Useful if you have a slow/old computer."
-msgstr "Minden animáció letiltása. Hasznos funkció lassú/régi gépek esetén."
-
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/config/config_ui.py:636
-#: /home/kovid/work/calibre/src/calibre/gui2/preferences/look_feel_ui.py:103
-msgid "Disable &animations"
-msgstr "Animációk tiltása"
-
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/config/config_ui.py:637
-#: /home/kovid/work/calibre/src/calibre/gui2/preferences/look_feel_ui.py:110
-msgid "&Toolbar"
-msgstr ""
-
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/config/config_ui.py:638
-#: /home/kovid/work/calibre/src/calibre/gui2/preferences/look_feel_ui.py:111
-msgid "&Icon size:"
-msgstr ""
-
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/config/config_ui.py:639
-#: /home/kovid/work/calibre/src/calibre/gui2/preferences/look_feel_ui.py:112
-msgid "Show &text under icons:"
-msgstr ""
-
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/config/config_ui.py:640
-#: /home/kovid/work/calibre/src/calibre/gui2/preferences/behavior_ui.py:141
-msgid "&Delete news from library when it is automatically sent to reader"
-msgstr "Hírek törlése az adatbázisból automatikus eszközre küldés után"
-
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/config/config_ui.py:641
-msgid "Select visible &columns in library view"
-msgstr "Látható oszlopok kiválasztása az adatbázis nézetben"
-
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/config/config_ui.py:643
-#: /home/kovid/work/calibre/src/calibre/gui2/preferences/custom_columns_ui.py:79
-msgid "Remove a user-defined column"
-msgstr ""
-
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/config/config_ui.py:645
-#: /home/kovid/work/calibre/src/calibre/gui2/preferences/custom_columns_ui.py:81
-msgid "Add a user-defined column"
-msgstr ""
-
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/config/config_ui.py:647
-#: /home/kovid/work/calibre/src/calibre/gui2/preferences/custom_columns_ui.py:83
-msgid "Edit settings of a user-defined column"
-msgstr ""
-
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/config/config_ui.py:650
-#: /home/kovid/work/calibre/src/calibre/gui2/preferences/behavior_ui.py:158
-msgid "Use internal &viewer for:"
-msgstr "A beépített nézőke használata a következőkhöz:"
-
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/config/config_ui.py:651
-#: /home/kovid/work/calibre/src/calibre/gui2/preferences/look_feel_ui.py:109
-msgid "Search as you type"
-msgstr "Keresés gépelés közben"
-
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/config/config_ui.py:652
-#: /home/kovid/work/calibre/src/calibre/gui2/preferences/look_feel_ui.py:107
-msgid "Use &Roman numerals for series"
-msgstr ""
-
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/config/config_ui.py:653
-#: /home/kovid/work/calibre/src/calibre/gui2/preferences/look_feel_ui.py:104
-msgid "Enable system &tray icon (needs restart)"
-msgstr "Ikon megjelenítése a tálcán (újraindítás szükséges)"
-
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/config/config_ui.py:654
-#: /home/kovid/work/calibre/src/calibre/gui2/preferences/look_feel_ui.py:101
-msgid "Show &average ratings in the tags browser"
-msgstr ""
-
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/config/config_ui.py:655
-#: /home/kovid/work/calibre/src/calibre/gui2/preferences/behavior_ui.py:140
-msgid "Automatically send downloaded &news to ebook reader"
-msgstr "Letöltött hírek automatikus küldése a csatlakoztatott eszközre"
-
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/config/config_ui.py:656
-#: /home/kovid/work/calibre/src/calibre/gui2/preferences/look_feel_ui.py:105
-msgid "Show &splash screen at startup"
-msgstr ""
-
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/config/config_ui.py:657
-#: /home/kovid/work/calibre/src/calibre/gui2/preferences/look_feel_ui.py:108
-msgid "Show cover &browser in a separate window (needs restart)"
-msgstr ""
-"A borító alapján történő keresés külön ablakba (újraindítás szükséges)"
-
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/config/config_ui.py:658
-msgid "Show ¬ifications in system tray"
-msgstr "Figyelmeztetések megjelenítése a tálcaikonon"
-
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/config/config_ui.py:659
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/config/config_ui.py:671
-msgid "&Miscellaneous"
-msgstr ""
-
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/config/config_ui.py:660
-msgid "Add an email address to which to send books"
-msgstr "Email cím hozzáadása"
-
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/config/config_ui.py:661
-msgid "&Add email"
-msgstr "Email hozzáadása"
-
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/config/config_ui.py:662
-msgid "Make &default"
-msgstr "Legyen alapértelmezett"
-
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/config/config_ui.py:663
-msgid "&Remove email"
-msgstr "Email eltávolítása"
-
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/config/config_ui.py:664
-msgid ""
-"calibre can send your books to you (or your reader) by email. Emails will be "
-"automatically sent for downloaded news to all email addresses that have Auto-"
-"send checked."
-msgstr ""
-
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/config/config_ui.py:665
-msgid "&Maximum number of waiting worker processes (needs restart):"
-msgstr "A sorban álló &műveletek maximális száma (újraindítás szükséges):"
-
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/config/config_ui.py:666
-msgid "Limit the max. simultaneous jobs to the available CPU &cores"
-msgstr ""
-"A maximális párhuzamosan végrehajtandó műveletek számának korlátozása a "
-"processzor magok számára"
-
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/config/config_ui.py:667
-msgid "Debug &device detection"
-msgstr ""
-
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/config/config_ui.py:668
-msgid "&Check database integrity"
-msgstr "Adatbázis ellenőrzése"
-
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/config/config_ui.py:669
-msgid "Open calibre &configuration directory"
-msgstr ""
-
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/config/config_ui.py:670
-msgid "&Install command line tools"
-msgstr "Parancssori eszközök installálása"
-
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/config/config_ui.py:672
-msgid ""
-"Values for the tweaks are shown below. Edit them to change the behavior of "
-"calibre"
-msgstr ""
-
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/config/config_ui.py:673
-msgid "All available tweaks"
-msgstr ""
-
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/config/config_ui.py:674
-msgid "&Current tweaks"
-msgstr ""
-
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/config/config_ui.py:675
-msgid "&Restore to defaults"
-msgstr ""
-
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/config/config_ui.py:676
-msgid "&Tweaks"
-msgstr ""
-
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/config/config_ui.py:677
-msgid ""
-"calibre contains a network server that allows you to access your book "
-"collection using a browser from anywhere in the world. Any changes to the "
-"settings will only take effect after a server restart."
-msgstr ""
-"A calibre tartalmaz egy beépített hálózati szervert, mely segítségével "
-"bárhonnan elérhető lesz gyűjteménye. A beállítások változtatása csak a "
-"szerver újraindítása után lép életbe."
-
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/config/config_ui.py:678
-msgid "Server &port:"
-msgstr "Szerver port:"
-
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/config/config_ui.py:679
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/password_ui.py:58
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/scheduler_ui.py:212
-#: /home/kovid/work/calibre/src/calibre/gui2/wizard/send_email_ui.py:117
-msgid "&Username:"
-msgstr "&Felhasználónév:"
-
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/config/config_ui.py:680
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/password_ui.py:59
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/scheduler_ui.py:213
-#: /home/kovid/work/calibre/src/calibre/gui2/wizard/send_email_ui.py:119
-msgid "&Password:"
-msgstr "&Jelszó:"
-
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/config/config_ui.py:681
-msgid ""
-"If you leave the password blank, anyone will be able to access your book "
-"collection using the web interface."
-msgstr "Ha nem ad meg jelszót, akkor bárki hozzáférhet az adatbázisához."
-
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/config/config_ui.py:682
-msgid ""
-"The maximum size (widthxheight) for displayed covers. Larger covers are "
-"resized. "
-msgstr ""
-"A megjelenített borító maximális mérete (szélességxmagasság). Nagyobb méretű "
-"borítók át lesznek méretezve. "
-
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/config/config_ui.py:683
-msgid "Max. &cover size:"
-msgstr "Max. borító méret:"
-
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/config/config_ui.py:684
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/password_ui.py:60
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/scheduler_ui.py:214
-msgid "&Show password"
-msgstr "Jelszó mutatása"
-
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/config/config_ui.py:685
-msgid "Max. &OPDS items per query:"
-msgstr ""
-
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/config/config_ui.py:686
-msgid "Max. OPDS &ungrouped items:"
-msgstr ""
-
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/config/config_ui.py:687
-msgid "Restriction (saved search) to apply:"
-msgstr ""
-
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/config/config_ui.py:688
-msgid ""
-"This restriction (based on a saved search) will restrict the books the "
-"content server makes available to those matching the search. This setting is "
-"per library (i.e. you can have a different restriction per library)."
-msgstr ""
-
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/config/config_ui.py:689
-msgid "&Start Server"
-msgstr "Szerver indítása"
-
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/config/config_ui.py:690
-msgid "St&op Server"
-msgstr "Szerver leállítása"
-
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/config/config_ui.py:691
-msgid "&Test Server"
-msgstr "Szerver teszt"
-
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/config/config_ui.py:692
-msgid "Run server &automatically on startup"
-msgstr "Szerver automatikus indítása a program indulásakor"
-
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/config/config_ui.py:693
-msgid "View &server logs"
-msgstr "Szerverüzenetek megtekintése"
-
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/config/config_ui.py:694
-#: /home/kovid/work/calibre/src/calibre/gui2/wizard/stanza_ui.py:46
-msgid ""
-"
Remember to leave calibre running as the server only runs as long as "
-"calibre is running.\n"
-"
Stanza should see your calibre collection automatically. If not, try "
-"adding the URL http://myhostname:8080 as a new catalog in the Stanza reader "
-"on your iPhone. Here myhostname should be the fully qualified hostname or "
-"the IP address of the computer calibre is running on."
-msgstr ""
-"
Ne feledje, hogy a szerver csak akkor fut, ha a calibre is fut.\n"
-"
Elméletileg a Stanza automatikusan látni fogja adatbázisát. Ha mégsem, "
-"akkor adja a 'http://myhostname:8080' URL-t új katalógusként az iPhone "
-"Stanza olvasóprogramjában. A 'myhostname' helyére a calibre-t futtató "
-"számítógép teljes neve, vagy IP címe kerüljön."
-
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/config/config_ui.py:696
-msgid ""
-"Here you can customize the behavior of Calibre by controlling what plugins "
-"it uses."
-msgstr ""
-"Itt beállíthatja a calibre működését, azzal, hogy milyen pluginokat "
-"használjon."
-
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/config/config_ui.py:697
-msgid "Enable/&Disable plugin"
-msgstr "Plugin engedélyezése/tiltása"
-
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/config/config_ui.py:698
-msgid "&Customize plugin"
-msgstr "Plugin testreszabása"
-
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/config/config_ui.py:699
-msgid "&Remove plugin"
-msgstr "Plugin eltávolítása"
-
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/config/config_ui.py:700
-msgid "Add new plugin"
-msgstr "Új plugin hozzáadása"
-
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/config/config_ui.py:701
-msgid "Plugin &file:"
-msgstr "Plugin &fájl:"
-
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/config/config_ui.py:703
-msgid "&Add"
-msgstr "&Hozzáadás"
-
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/config/create_ct_column_ui.py:125
-msgid "Create Tag-based Column"
-msgstr ""
-
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/config/create_ct_column_ui.py:126
-msgid "Lookup name"
-msgstr ""
-
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/config/create_ct_column_ui.py:127
-msgid "Column heading"
-msgstr ""
-
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/config/create_ct_column_ui.py:128
-msgid "Column type"
-msgstr ""
-
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/config/create_ct_column_ui.py:129
-msgid "Use brackets"
-msgstr ""
-
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/config/create_ct_column_ui.py:130
-msgid "Values can be edited"
-msgstr ""
-
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/config/create_ct_column_ui.py:135
-msgid "Text"
-msgstr ""
-
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/config/create_ct_column_ui.py:136
-msgid "Number"
-msgstr ""
-
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/config/create_ct_column_ui.py:137
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/config/create_custom_column.py:31
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/delete_matching_from_device.py:76
-#: /home/kovid/work/calibre/src/calibre/gui2/library/models.py:69
-#: /home/kovid/work/calibre/src/calibre/gui2/library/models.py:889
-#: /home/kovid/work/calibre/src/calibre/library/server/opds.py:588
-msgid "Date"
-msgstr "Dátum"
-
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/config/create_ct_column_ui.py:138
-msgid "Tag on book"
-msgstr ""
-
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/config/create_ct_column_ui.py:139
-msgid "Explanation text added in create_ct_column.py"
-msgstr ""
-
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/config/create_ct_column_ui.py:140
-msgid "Create and edit tag-based columns"
-msgstr ""
-
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/config/create_custom_column.py:19
-msgid "Text, column shown in the tag browser"
-msgstr ""
-
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/config/create_custom_column.py:22
-msgid "Comma separated text, like tags, shown in the tag browser"
-msgstr ""
-
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/config/create_custom_column.py:25
-msgid "Long text, like comments, not shown in the tag browser"
-msgstr ""
-
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/config/create_custom_column.py:28
-msgid "Text column for keeping series-like information"
-msgstr ""
-
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/config/create_custom_column.py:33
-msgid "Floating point numbers"
-msgstr ""
-
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/config/create_custom_column.py:35
-msgid "Integers"
-msgstr ""
-
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/config/create_custom_column.py:37
-msgid "Ratings, shown with stars"
-msgstr ""
-
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/config/create_custom_column.py:40
-msgid "Yes/No"
-msgstr ""
-
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/config/create_custom_column.py:69
-msgid "No column selected"
-msgstr ""
-
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/config/create_custom_column.py:70
-msgid "No column has been selected"
-msgstr ""
-
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/config/create_custom_column.py:74
-msgid "Selected column is not a user-defined column"
-msgstr ""
-
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/config/create_custom_column.py:105
-msgid "No lookup name was provided"
-msgstr ""
-
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/config/create_custom_column.py:107
-msgid ""
-"The lookup name must contain only lower case letters, digits and "
-"underscores, and start with a letter"
-msgstr ""
-
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/config/create_custom_column.py:109
-msgid ""
-"Lookup names cannot end with _index, because these names are reserved for "
-"the index of a series column."
-msgstr ""
-
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/config/create_custom_column.py:118
-msgid "No column heading was provided"
-msgstr ""
-
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/config/create_custom_column.py:124
-msgid "The lookup name %s is already used"
-msgstr ""
-
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/config/create_custom_column.py:134
-msgid "The heading %s is already used"
-msgstr ""
-
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/config/create_custom_column_ui.py:101
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/config/create_custom_column_ui.py:117
-msgid "Create or edit custom columns"
-msgstr ""
-
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/config/create_custom_column_ui.py:102
-msgid "&Lookup name"
-msgstr ""
-
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/config/create_custom_column_ui.py:103
-msgid "Column &heading"
-msgstr ""
-
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/config/create_custom_column_ui.py:104
-msgid ""
-"Used for searching the column. Must contain only digits and lower case "
-"letters."
-msgstr ""
-
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/config/create_custom_column_ui.py:105
-msgid ""
-"Column heading in the library view and category name in the tag browser"
-msgstr ""
-
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/config/create_custom_column_ui.py:106
-msgid "Column &type"
-msgstr ""
-
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/config/create_custom_column_ui.py:107
-msgid "What kind of information will be kept in the column."
-msgstr ""
-
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/config/create_custom_column_ui.py:108
-msgid ""
-"
Date format. Use 1-4 'd's for day, 1-4 'M's for month, and 2 or 4 'y's "
-"for year.
\n"
-"
For example:\n"
-"
\n"
-"
ddd, d MMM yyyy gives Mon, 5 Jan 2010
\n"
-"
dd MMMM yy gives 05 January 10
\n"
-"
"
-msgstr ""
-
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/config/create_custom_column_ui.py:114
-msgid "Use MMM yyyy for month + year, yyyy for year only"
-msgstr ""
-
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/config/create_custom_column_ui.py:115
-msgid "Default: dd MMM yyyy."
-msgstr ""
-
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/config/create_custom_column_ui.py:116
-msgid "Format for &dates"
-msgstr ""
-
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/config/device_debug.py:21
-msgid "Getting debug information"
-msgstr ""
-
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/config/device_debug.py:22
-msgid "Copy to &clipboard"
-msgstr "Másolás a vágólapra"
-
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/config/device_debug.py:24
-msgid "Debug device detection"
-msgstr "Eszközkapcsolódás ellenőrzése"
-
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/config/save_template.py:44
-msgid "Invalid template"
-msgstr "Érvénytelen minta"
-
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/config/save_template.py:45
-msgid "The template %s is invalid:"
-msgstr "A következő minta érvénytelen: %s"
-
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/config/save_template_ui.py:42
-msgid "Save &template"
-msgstr "Minta mentése:"
-
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/config/save_template_ui.py:43
-msgid ""
-"By adjusting the template below, you can control what folders the files are "
-"saved in and what filenames they are given. You can use the / character to "
-"indicate sub-folders. Available metadata variables are described below. If a "
-"particular book does not have some metadata, the variable will be replaced "
-"by the empty string."
-msgstr ""
-"A lenti mintát pontosítva be tudja állítani a lemezre történő mentés mappa "
-"és fájlneveit. A '/' karakter jeleni az almappákat. A használható metaadat-"
-"változókat a listamezőben láthatja. Ha az adott könyv nem tartalmazza a "
-"megadott metaadatot, akkor az egy üres karakterrel lesz helyettesítve."
-
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/config/save_template_ui.py:44
-msgid "Available variables:"
-msgstr "Engedélyezett változók:"
-
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/config/social.py:34
-msgid "Downloading social metadata, please wait..."
-msgstr "Közösségi metaadatok letöltése, kérem várjon..."
-
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/config/toolbar.py:35
-msgid "Switch between library and device views"
-msgstr ""
-
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/config/toolbar.py:38
-msgid "Separator"
-msgstr ""
-
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/config/toolbar.py:51
-msgid "Choose library"
-msgstr ""
-
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/config/toolbar.py:201
-msgid "The main toolbar"
-msgstr ""
-
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/config/toolbar.py:202
-msgid "The main toolbar when a device is connected"
-msgstr ""
-
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/config/toolbar.py:203
-msgid "The context menu for the books in the calibre library"
-msgstr ""
-
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/config/toolbar.py:205
-msgid "The context menu for the books on the device"
-msgstr ""
-
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/config/toolbar.py:243
-msgid "Cannot add"
-msgstr ""
-
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/config/toolbar.py:244
-msgid "Cannot add the actions %s to this location"
-msgstr ""
-
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/config/toolbar.py:261
-msgid "Cannot remove"
-msgstr ""
-
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/config/toolbar.py:262
-msgid "Cannot remove the actions %s from this location"
-msgstr ""
-
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/config/toolbar_ui.py:97
-msgid "Customize the actions in:"
-msgstr ""
-
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/config/toolbar_ui.py:98
-msgid "A&vailable actions"
-msgstr ""
-
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/config/toolbar_ui.py:99
-msgid "&Current actions"
-msgstr ""
-
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/config/toolbar_ui.py:100
-msgid "Move selected action up"
-msgstr ""
-
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/config/toolbar_ui.py:102
-msgid "Move selected action down"
-msgstr ""
-
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/config/toolbar_ui.py:104
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/tag_list_editor_ui.py:78
-msgid "Ctrl+S"
-msgstr ""
-
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/config/toolbar_ui.py:105
-msgid "Add selected actions to toolbar"
-msgstr ""
-
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/config/toolbar_ui.py:107
-msgid "Remove selected actions from toolbar"
-msgstr ""
-
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/config/toolbar_ui.py:109
-msgid "Restore to &default"
-msgstr ""
-
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/confirm_delete_ui.py:50
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/confirm_delete_ui.py:55
msgid "&Show this warning again"
msgstr "Mindig mutassam ezt a figyelmeztetést"
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/conversion_error_ui.py:42
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/conversion_error_ui.py:47
msgid "ERROR"
msgstr "HIBA"
@@ -7059,7 +6155,15 @@ msgid "Location"
msgstr ""
#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/delete_matching_from_device.py:76
-#: /home/kovid/work/calibre/src/calibre/gui2/library/models.py:1064
+#: /home/kovid/work/calibre/src/calibre/gui2/library/models.py:69
+#: /home/kovid/work/calibre/src/calibre/gui2/library/models.py:884
+#: /home/kovid/work/calibre/src/calibre/gui2/preferences/create_custom_column.py:31
+#: /home/kovid/work/calibre/src/calibre/library/server/opds.py:588
+msgid "Date"
+msgstr "Dátum"
+
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/delete_matching_from_device.py:76
+#: /home/kovid/work/calibre/src/calibre/gui2/library/models.py:1066
msgid "Format"
msgstr "Formátum"
@@ -7133,11 +6237,11 @@ msgstr ""
"Nem található metaadat, próbálja meg finomítani a könyvcímet, szerzőt vagy "
"az ISBN-t."
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/fetch_metadata_ui.py:85
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/fetch_metadata_ui.py:90
msgid "Fetch metadata"
msgstr "Metaadatok letöltése"
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/fetch_metadata_ui.py:86
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/fetch_metadata_ui.py:91
msgid ""
"
calibre can find metadata for your books from two locations: Google "
"Books and isbndb.com.
To use isbndb.com you must sign up for a "
@@ -7150,77 +6254,120 @@ msgstr ""
"href=\"http://www.isbndb.com\">felhasználói fiókkal és be kell írnia a "
"kapott kulcsot az alábbi mezőbe."
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/fetch_metadata_ui.py:87
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/fetch_metadata_ui.py:92
msgid "&Access Key:"
msgstr "Felhasználói kulcs:"
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/fetch_metadata_ui.py:88
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/fetch_metadata_ui.py:93
msgid "Fetch"
msgstr "Letöltés"
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/fetch_metadata_ui.py:89
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/fetch_metadata_ui.py:94
msgid "Matches"
msgstr "Találatok"
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/fetch_metadata_ui.py:90
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/fetch_metadata_ui.py:95
msgid ""
"Select the book that most closely matches your copy from the list below"
msgstr "Válassza ki azt, amelyik a leginkább illik a könyvéhez"
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/fetch_metadata_ui.py:91
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/fetch_metadata_ui.py:96
msgid "Download &social metadata (tags/rating/etc.) for the selected book"
msgstr ""
"Közösségi metaadatok (címkék, értékelés stb.) letöltése a kiválasztott "
"könyvhöz"
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/fetch_metadata_ui.py:92
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/fetch_metadata_ui.py:97
msgid "Overwrite author and title with author and title of selected book"
msgstr ""
"A szerző és a cím felülírása a listából kiválasztott könyv szerzőjével és "
"címével"
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/job_view_ui.py:37
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/job_view_ui.py:42
msgid "Details of job"
msgstr "A művelet részletei"
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/jobs_ui.py:44
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/jobs_ui.py:49
msgid "Active Jobs"
msgstr "Aktív műveletek"
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/jobs_ui.py:45
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/jobs_ui.py:50
msgid "&Stop selected job"
msgstr "A kiválasztott művelet megszakítása"
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/jobs_ui.py:46
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/jobs_ui.py:51
msgid "Show job &details"
msgstr "Műveletek részleteinek megjelenítése"
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/jobs_ui.py:47
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/jobs_ui.py:52
msgid "Stop &all non device jobs"
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk.py:111
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk.py:107
+#: /home/kovid/work/calibre/src/calibre/gui2/widgets.py:385
+msgid "Lower Case"
+msgstr "Kisbetűk"
+
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk.py:108
+#: /home/kovid/work/calibre/src/calibre/gui2/widgets.py:384
+msgid "Upper Case"
+msgstr "Nagybetűk"
+
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk.py:109
+#: /home/kovid/work/calibre/src/calibre/gui2/widgets.py:387
+msgid "Title Case"
+msgstr "Minden Szó Nagybetűvel Kezdődik"
+
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk.py:119
msgid "Editing meta information for %d books"
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk.py:225
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk.py:166
+msgid "Book %d:"
+msgstr ""
+
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk.py:182
+msgid ""
+"Search and replace in text fields using regular expressions. The search text "
+"is an arbitrary python-compatible regular expression. The replacement text "
+"can contain backreferences to parenthesized expressions in the pattern. The "
+"search is not anchored, and can match and replace multiple times on the same "
+"string. See this "
+"reference for more information, and in particular the 'sub' function."
+msgstr ""
+
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk.py:192
+msgid ""
+"Note: you can destroy your library using this feature. Changes are "
+"permanent. There is no undo function. You are strongly encouraged to back up "
+"your library before proceeding."
+msgstr ""
+
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk.py:386
+msgid "Search/replace invalid"
+msgstr ""
+
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk.py:387
+msgid "Search pattern is invalid: %s"
+msgstr ""
+
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk.py:419
msgid "Applying changes to %d books. This may take a while."
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:160
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:229
msgid "Edit Meta information"
msgstr "Metaadatok szerkesztése"
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:162
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:231
msgid "A&utomatically set author sort"
msgstr "Rendezési forma automatikus beállítása"
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:163
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:232
msgid "Author s&ort: "
msgstr "Rendezési forma: "
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:164
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single_ui.py:367
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:233
msgid ""
"Specify how the author(s) of this book should be sorted. For example Charles "
"Dickens should be sorted as Dickens, Charles."
@@ -7228,63 +6375,63 @@ msgstr ""
"A szerző rendezési formájának megadása. Például Charles Dickens-t célszerű "
"Dickens, Charles-ként rendezni."
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:165
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single_ui.py:370
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:234
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single_ui.py:383
msgid "&Rating:"
msgstr "Értékelés:"
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:166
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:167
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single_ui.py:371
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single_ui.py:372
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:235
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:236
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single_ui.py:384
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single_ui.py:385
msgid "Rating of this book. 0-5 stars"
msgstr "A könyv értékelése. 0-5 csillag."
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:168
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:237
msgid "No change"
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:169
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single_ui.py:373
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:238
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single_ui.py:386
msgid " stars"
msgstr " csillag"
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:171
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:240
msgid "Add ta&gs: "
msgstr "Címkék hozzáadása: "
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:173
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:174
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single_ui.py:377
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single_ui.py:378
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:242
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:243
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single_ui.py:390
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single_ui.py:391
msgid "Open Tag Editor"
msgstr "Címke-szerkesztő megnyitása"
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:175
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:244
msgid "&Remove tags:"
msgstr "Címkék eltávolítása:"
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:176
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:245
msgid "Comma separated list of tags to remove from the books. "
msgstr "A könyvből eltávolítandó címkék vesszővel elválasztott listája "
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:177
-msgid "Remove all"
-msgstr ""
-
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:178
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:246
msgid "Check this box to remove all tags from the books."
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:182
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:247
+msgid "Remove all"
+msgstr ""
+
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:251
msgid "Remove &format:"
msgstr "Formátum eltávolítása:"
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:183
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:252
msgid "&Swap title and author"
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:184
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:253
msgid ""
"Selected books will be automatically numbered,\n"
"in the order you selected them.\n"
@@ -7292,264 +6439,342 @@ msgid ""
"Book A will have series number 1 and Book B series number 2."
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:189
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:258
msgid ""
"Remove stored conversion settings for the selected books.\n"
"\n"
"Future conversion of these books will use the default settings."
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:192
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:261
msgid "Remove &stored conversion settings for the selected books"
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:193
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single_ui.py:408
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:262
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single_ui.py:422
msgid "&Basic metadata"
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:194
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single_ui.py:409
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:263
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single_ui.py:423
msgid "&Custom metadata"
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single.py:94
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:264
+msgid "Search &field:"
+msgstr ""
+
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:265
+msgid "&Search for:"
+msgstr ""
+
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:266
+msgid "&Replace with:"
+msgstr ""
+
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:267
+msgid "Apply function &after replace:"
+msgstr ""
+
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:268
+msgid "Test &text"
+msgstr ""
+
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:269
+msgid "Test re&sult"
+msgstr ""
+
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:270
+msgid "Your test:"
+msgstr ""
+
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:271
+msgid "&Search and replace (experimental)"
+msgstr ""
+
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single.py:91
msgid "Last modified: %s"
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single.py:136
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single.py:133
msgid "Not a valid picture"
msgstr "Nem érvényes képfájl"
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single.py:151
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single.py:150
msgid "Specify title and author"
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single.py:152
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single.py:151
msgid "You must specify a title and author before generating a cover"
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single.py:164
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single.py:169
msgid "Choose formats for "
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single.py:195
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single.py:200
msgid "No permission"
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single.py:196
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single.py:201
msgid "You do not have permission to read the following files:"
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single.py:223
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single.py:224
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single.py:228
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single.py:229
msgid "No format selected"
msgstr "Nincs formátum kiválasztva"
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single.py:235
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single.py:240
msgid "Could not read metadata"
msgstr "Nem lehet olvasni a metaadatokat"
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single.py:236
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single.py:241
msgid "Could not read metadata from %s format"
msgstr "Nem lehet kiolvasni a metaadtokat a %s formátumból."
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single.py:284
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single.py:290
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single.py:289
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single.py:295
msgid "Could not read cover"
msgstr "Nem lehet olvasni a borítót"
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single.py:285
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single.py:290
msgid "Could not read cover from %s format"
msgstr "Nem lehet kiolvasni a borítót a %s formátumból"
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single.py:291
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single.py:296
msgid "The cover in the %s format is invalid"
msgstr "A %s formátumban lévő borító érvénytelen"
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single.py:328
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single.py:351
+msgid ""
+" The green color indicates that the current author sort matches the current "
+"author"
+msgstr ""
+
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single.py:354
+msgid ""
+" The red color indicates that the current author sort does not match the "
+"current author"
+msgstr ""
+
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single.py:359
msgid "Abort the editing of all remaining books"
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single.py:466
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single.py:471
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single.py:524
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single.py:529
msgid "This ISBN number is valid"
msgstr "Az ISBN szám érvényes"
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single.py:474
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single.py:532
msgid "This ISBN number is invalid"
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single.py:553
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single.py:611
msgid "Cannot use tag editor"
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single.py:554
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single.py:612
msgid "The tags editor cannot be used if you have modified the tags"
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single.py:574
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single.py:632
msgid "Downloading cover..."
msgstr "Borító letöltése..."
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single.py:586
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single.py:591
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single.py:597
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single.py:602
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single.py:644
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single.py:649
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single.py:655
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single.py:660
msgid "Cannot fetch cover"
msgstr "Nem lehet letölteni a borítót"
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single.py:587
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single.py:598
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single.py:603
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single.py:645
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single.py:656
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single.py:661
msgid "Could not fetch cover. "
msgstr "Nem lehet letölteni a borítót. "
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single.py:588
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single.py:646
msgid "The download timed out."
msgstr "Időtúllépés letöltés közben"
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single.py:592
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single.py:650
msgid "Could not find cover for this book. Try specifying the ISBN first."
msgstr ""
"Nem található borító ehhez a könyvhöz. Próbálja meg először az ISBN-t "
"meghatározni."
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single.py:604
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single.py:662
msgid ""
"For the error message from each cover source, click Show details below."
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single.py:611
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single.py:669
msgid "Bad cover"
msgstr "Hibás borító"
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single.py:612
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single.py:670
msgid "The cover is not a valid picture"
msgstr "A borító nem érvényes képfájl"
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single.py:645
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single.py:703
msgid "There were errors"
msgstr "Hibák történtek"
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single.py:646
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single.py:704
msgid "There were errors downloading social metadata"
msgstr "Hiba történt a közösségi metaadatok letöltése közben"
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single.py:675
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single.py:733
msgid "Cannot fetch metadata"
msgstr "Metadatok nem letölthetőek"
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single.py:676
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single.py:734
msgid "You must specify at least one of ISBN, Title, Authors or Publisher"
msgstr ""
"Legalább az egyik adatot meg kell adnia a következők közül: ISBN, könyv "
"címe, szerző vagy kiadó"
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single.py:759
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single.py:820
msgid "Permission denied"
msgstr "Hozzáférés megtagadva"
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single.py:760
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single.py:821
msgid "Could not open %s. Is it being used by another program?"
msgstr "Nem lehet megnyitni: %s. Esetleg másik program használja?"
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single_ui.py:359
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single_ui.py:370
msgid "Edit Meta Information"
msgstr "Metaadatok szerkesztése"
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single_ui.py:360
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single_ui.py:371
msgid "Meta information"
msgstr "Metaadat infromációk"
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single_ui.py:363
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single_ui.py:374
msgid "Swap the author and title"
msgstr "A szerző és a könyvcím megcserélése"
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single_ui.py:366
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single_ui.py:377
msgid "Author S&ort: "
msgstr "Rendezési forma: "
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single_ui.py:368
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single_ui.py:378
msgid ""
-"Automatically create the author sort entry based on the current author entry"
+"Specify how the author(s) of this book should be sorted. For example Charles "
+"Dickens should be sorted as Dickens, Charles.\n"
+"If the box is colored green, then text matches the individual author's sort "
+"strings. If it is colored red, then the authors and this text do not match."
msgstr ""
-"A rendezési forma automatikus létrehozása az aktuális szerzői adatok alapján"
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single_ui.py:382
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single_ui.py:380
+msgid ""
+"Automatically create the author sort entry based on the current author "
+"entry.\n"
+"Using this button to create author sort will change author sort from red to "
+"green."
+msgstr ""
+
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single_ui.py:395
msgid "Remove unused series (Series that have no books)"
msgstr ""
"Nem használt sorozatok törlése (Olyan sorozatok, melyekhez nem tartoznak "
"könyvek)"
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single_ui.py:384
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single_ui.py:397
msgid "IS&BN:"
msgstr "IS&BN:"
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single_ui.py:385
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single_ui.py:398
msgid "Publishe&d:"
msgstr "Kiadva:"
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single_ui.py:388
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single_ui.py:401
msgid "dd MMM yyyy"
msgstr "nn HHH éééé"
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single_ui.py:389
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single_ui.py:402
msgid "&Date:"
msgstr "&Dátum:"
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single_ui.py:390
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single_ui.py:403
msgid "&Comments"
msgstr "Megjegyzések"
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single_ui.py:391
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single_ui.py:404
msgid "&Fetch metadata from server"
msgstr "Metadatok lekérdezése szerverről"
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single_ui.py:392
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single_ui.py:405
msgid "Available Formats"
msgstr "Elérhető formátumok"
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single_ui.py:393
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single_ui.py:406
msgid "Add a new format for this book to the database"
msgstr "Új formátum hozzáadása az adatbázishoz"
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single_ui.py:395
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single_ui.py:408
msgid "Remove the selected formats for this book from the database."
msgstr "A kiválasztott formátumok törlése az adatbázisból."
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single_ui.py:397
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single_ui.py:410
msgid "Set the cover for the book from the selected format"
msgstr "Borító beállítása a kiválasztott formátumú könyből"
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single_ui.py:399
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single_ui.py:412
msgid "Update metadata from the metadata in the selected format"
msgstr "Metaadatok frissítése a kiválasztott formátum metaadatai alapján"
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single_ui.py:402
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single_ui.py:415
msgid "&Browse"
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single_ui.py:403
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single_ui.py:416
+msgid "Remove border (if any) from cover"
+msgstr ""
+
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single_ui.py:417
msgid "Reset cover to default"
msgstr "Alapértelmezett borító visszaállítása"
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single_ui.py:405
-msgid "Download &cover"
-msgstr "Borító letöltése"
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single_ui.py:419
+msgid "Download co&ver"
+msgstr ""
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single_ui.py:406
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single_ui.py:420
msgid "Generate a default cover based on the title and author"
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single_ui.py:407
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single_ui.py:421
msgid "&Generate cover"
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/password_ui.py:56
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/password_ui.py:61
msgid "Password needed"
msgstr "Jelszó szükséges"
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/password_ui.py:63
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/scheduler_ui.py:217
+#: /home/kovid/work/calibre/src/calibre/gui2/preferences/server_ui.py:125
+#: /home/kovid/work/calibre/src/calibre/gui2/wizard/send_email_ui.py:117
+msgid "&Username:"
+msgstr "&Felhasználónév:"
+
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/password_ui.py:64
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/scheduler_ui.py:218
+#: /home/kovid/work/calibre/src/calibre/gui2/preferences/server_ui.py:126
+#: /home/kovid/work/calibre/src/calibre/gui2/wizard/send_email_ui.py:119
+msgid "&Password:"
+msgstr "&Jelszó:"
+
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/password_ui.py:65
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/scheduler_ui.py:219
+#: /home/kovid/work/calibre/src/calibre/gui2/preferences/server_ui.py:130
+msgid "&Show password"
+msgstr "Jelszó mutatása"
+
#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/progress.py:59
msgid "Aborting..."
msgstr "Megszakítás..."
@@ -7563,31 +6788,31 @@ msgid ""
"The current saved search will be permanently deleted. Are you sure?"
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/saved_search_editor_ui.py:83
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/saved_search_editor_ui.py:88
msgid "Saved Search Editor"
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/saved_search_editor_ui.py:84
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/saved_search_editor_ui.py:89
msgid "Saved Search: "
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/saved_search_editor_ui.py:85
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/saved_search_editor_ui.py:90
msgid "Select a saved search to edit"
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/saved_search_editor_ui.py:86
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/saved_search_editor_ui.py:91
msgid "Delete this selected saved search"
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/saved_search_editor_ui.py:88
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/saved_search_editor_ui.py:93
msgid "Enter a new saved search name."
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/saved_search_editor_ui.py:89
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/saved_search_editor_ui.py:94
msgid "Add the new saved search"
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/saved_search_editor_ui.py:91
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/saved_search_editor_ui.py:96
msgid "Change the contents of the saved search"
msgstr ""
@@ -7616,7 +6841,7 @@ msgid "Last downloaded"
msgstr "Utoljára letöltve"
#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/scheduler.py:220
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/scheduler_ui.py:192
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/scheduler_ui.py:197
msgid "Schedule news download"
msgstr "Ütemezett hírletöltés"
@@ -7636,69 +6861,69 @@ msgstr ""
msgid "Cannot download news as no internet connection is active"
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/scheduler_ui.py:193
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/scheduler_ui.py:198
#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/user_profiles.py:278
msgid "Recipes"
msgstr "Hírösszeállítások"
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/scheduler_ui.py:194
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/scheduler_ui.py:199
msgid "Download all scheduled recipes at once"
msgstr "Minden ütemezett hírösszeállítás letöltése"
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/scheduler_ui.py:195
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/scheduler_ui.py:200
msgid "Download &all scheduled"
msgstr "Minden ütemezett letöltése"
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/scheduler_ui.py:196
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/scheduler_ui.py:201
msgid "blurb"
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/scheduler_ui.py:197
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/scheduler_ui.py:202
msgid "&Schedule for download:"
msgstr "Ütemezés:"
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/scheduler_ui.py:198
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/scheduler_ui.py:208
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/scheduler_ui.py:203
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/scheduler_ui.py:213
msgid "Every "
msgstr "Minden "
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/scheduler_ui.py:199
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/scheduler_ui.py:204
msgid "day"
msgstr "nap"
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/scheduler_ui.py:200
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/scheduler_ui.py:205
msgid "Monday"
msgstr "Hétfő"
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/scheduler_ui.py:201
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/scheduler_ui.py:206
msgid "Tuesday"
msgstr "Kedd"
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/scheduler_ui.py:202
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/scheduler_ui.py:207
msgid "Wednesday"
msgstr "Szerda"
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/scheduler_ui.py:203
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/scheduler_ui.py:208
msgid "Thursday"
msgstr "Csütörtök"
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/scheduler_ui.py:204
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/scheduler_ui.py:209
msgid "Friday"
msgstr "Péntek"
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/scheduler_ui.py:205
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/scheduler_ui.py:210
msgid "Saturday"
msgstr "Szombat"
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/scheduler_ui.py:206
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/scheduler_ui.py:211
msgid "Sunday"
msgstr "Vasárnap"
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/scheduler_ui.py:207
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/scheduler_ui.py:212
msgid "at"
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/scheduler_ui.py:209
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/scheduler_ui.py:214
msgid ""
"Interval at which to download this recipe. A value of zero means that the "
"recipe will be downloaded every hour."
@@ -7706,48 +6931,48 @@ msgstr ""
"Milyen időközönként töltse le a hírösszeállítást. A '0,0' azt jelenti, hogy "
"óránként."
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/scheduler_ui.py:210
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/scheduler_ui.py:222
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/user_profiles_ui.py:263
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/scheduler_ui.py:215
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/scheduler_ui.py:227
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/user_profiles_ui.py:268
msgid " days"
msgstr " nap"
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/scheduler_ui.py:211
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/scheduler_ui.py:216
msgid "&Account"
msgstr "Fiókbeállítás"
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/scheduler_ui.py:215
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/scheduler_ui.py:220
msgid "For the scheduling to work, you must leave calibre running."
msgstr "Az ütemezett letöltéshez a calibre-nek futnia kell"
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/scheduler_ui.py:216
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/scheduler_ui.py:221
msgid "&Schedule"
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/scheduler_ui.py:217
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/scheduler_ui.py:222
msgid "Add &title as tag"
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/scheduler_ui.py:218
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/scheduler_ui.py:223
msgid "&Extra tags:"
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/scheduler_ui.py:219
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/scheduler_ui.py:224
msgid "&Advanced"
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/scheduler_ui.py:220
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/scheduler_ui.py:225
msgid "&Download now"
msgstr "Letöltés most"
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/scheduler_ui.py:221
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/scheduler_ui.py:226
msgid ""
"Delete downloaded news older than the specified number of days. Set to zero "
"to disable."
msgstr ""
"A megadott napoknál régebbi hírek törlése. Állítsa 0-ra, ha ne töröljön."
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/scheduler_ui.py:223
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/scheduler_ui.py:228
msgid "Delete downloaded news older than "
msgstr "A következőnél régebbi hírek törlése: "
@@ -7769,57 +6994,57 @@ msgstr ""
msgid "Negate"
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/search_ui.py:113
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/search_ui.py:118
msgid "Advanced Search"
msgstr "Részletes keresés"
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/search_ui.py:114
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/search_ui.py:119
msgid "Find entries that have..."
msgstr "Keresés azokra, melyek tartalmazzák..."
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/search_ui.py:115
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/search_ui.py:120
msgid "&All these words:"
msgstr "Ezen szavak mindegyikét:"
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/search_ui.py:116
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/search_ui.py:121
msgid "This exact &phrase:"
msgstr "Pontosan ezt a kifejezést:"
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/search_ui.py:117
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/search_ui.py:122
msgid "&One or more of these words:"
msgstr "Egy vagy több szót ezek közül:"
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/search_ui.py:118
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/search_ui.py:123
msgid "But dont show entries that have..."
msgstr "...de nem tartalmazzák"
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/search_ui.py:119
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/search_ui.py:124
msgid "Any of these &unwanted words:"
msgstr "Egyik szót sem ezek közül:"
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/search_ui.py:120
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/search_ui.py:125
msgid "What kind of match to use:"
msgstr "Az egyezés típusa:"
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/search_ui.py:121
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/search_ui.py:126
msgid "Contains: the word or phrase matches anywhere in the metadata"
msgstr "A szó vagy kifejezés bárhol megtalálható a metaadatban."
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/search_ui.py:122
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/search_ui.py:127
msgid "Equals: the word or phrase must match an entire metadata field"
msgstr ""
"A szó vagy kifejezés teljes egészében megegyezik egy metaadat mezővel"
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/search_ui.py:123
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/search_ui.py:128
msgid ""
"Regular expression: the expression must match anywhere in the metadata"
msgstr "Reguláris kifejezés bárhol a metadatban"
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/search_ui.py:124
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/search_ui.py:129
msgid " "
msgstr " "
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/search_ui.py:125
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/search_ui.py:130
msgid ""
"See the User Manual for more help"
@@ -7830,12 +7055,12 @@ msgid "Choose formats"
msgstr "Formátum kiválasztása"
#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/tag_categories.py:50
-#: /home/kovid/work/calibre/src/calibre/library/field_metadata.py:80
+#: /home/kovid/work/calibre/src/calibre/library/field_metadata.py:82
msgid "Authors"
msgstr "Szerzők"
#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/tag_categories.py:50
-#: /home/kovid/work/calibre/src/calibre/library/field_metadata.py:111
+#: /home/kovid/work/calibre/src/calibre/library/field_metadata.py:113
msgid "Publishers"
msgstr "Kiadók"
@@ -7848,51 +7073,51 @@ msgid ""
"The current tag category will be permanently deleted. Are you sure?"
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/tag_categories_ui.py:153
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/tag_categories_ui.py:158
msgid "User Categories Editor"
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/tag_categories_ui.py:154
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/tag_categories_ui.py:159
msgid "A&vailable items"
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/tag_categories_ui.py:155
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/tag_categories_ui.py:160
msgid "Apply tags to current tag category"
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/tag_categories_ui.py:157
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/tag_categories_ui.py:162
msgid "A&pplied items"
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/tag_categories_ui.py:158
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/tag_categories_ui.py:163
msgid "Unapply (remove) tag from current tag category"
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/tag_categories_ui.py:160
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/tag_categories_ui.py:165
msgid "Category name: "
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/tag_categories_ui.py:161
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/tag_categories_ui.py:166
msgid "Select a category to edit"
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/tag_categories_ui.py:162
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/tag_categories_ui.py:167
msgid "Delete this selected tag category"
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/tag_categories_ui.py:164
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/tag_categories_ui.py:169
msgid "Enter a new category name. Select the kind before adding it."
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/tag_categories_ui.py:165
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/tag_categories_ui.py:170
msgid "Add the new category"
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/tag_categories_ui.py:167
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/tag_categories_ui.py:172
msgid "Category filter: "
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/tag_categories_ui.py:168
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/tag_categories_ui.py:173
msgid "Select the content kind of the new category"
msgstr ""
@@ -7909,15 +7134,15 @@ msgstr ""
"A következő címkék egy vagy több könyvnél szerepelnek. Biztos, hogy törli "
"őket?"
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/tag_editor_ui.py:123
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/tag_editor_ui.py:128
msgid "Tag Editor"
msgstr "Címkeszerkesztő"
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/tag_editor_ui.py:124
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/tag_editor_ui.py:129
msgid "A&vailable tags"
msgstr "Létező címkék"
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/tag_editor_ui.py:125
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/tag_editor_ui.py:130
msgid ""
"Delete tag from database. This will unapply the tag from all books and then "
"remove it from the database."
@@ -7925,23 +7150,23 @@ msgstr ""
"Cimkék törlése az adatbázisból. Ez eltávolítja a kérdéses cinkéket a "
"könyvekből, majd törli őket az adatbázisból."
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/tag_editor_ui.py:127
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/tag_editor_ui.py:132
msgid "Apply tag to current book"
msgstr "Címke alkalmazása az aktuális könyvön"
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/tag_editor_ui.py:129
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/tag_editor_ui.py:134
msgid "A&pplied tags"
msgstr "Alkalmazott címkék"
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/tag_editor_ui.py:130
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/tag_editor_ui.py:135
msgid "Unapply (remove) tag from current book"
msgstr "Könyvcímke eltávolítása"
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/tag_editor_ui.py:132
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/tag_editor_ui.py:137
msgid "&Add tag:"
msgstr "Címke hozzáadása:"
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/tag_editor_ui.py:133
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/tag_editor_ui.py:138
msgid ""
"If the tag you want is not in the available list, you can add it here. "
"Accepts a comma separated list of tags."
@@ -7949,7 +7174,7 @@ msgstr ""
"Ha a kívánt címke nem szerepel a listán, itt hozzáadhatja. Vesszővel "
"elválasztva több címkét is megadhat."
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/tag_editor_ui.py:134
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/tag_editor_ui.py:139
msgid "Add tag to available tags and apply it to current book"
msgstr "Címke hozzáadása az elérhető címkékhez és alkalmazás a könyvön"
@@ -7958,12 +7183,12 @@ msgid "%s (was %s)"
msgstr ""
#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/tag_list_editor.py:74
-#: /home/kovid/work/calibre/src/calibre/gui2/tag_view.py:503
+#: /home/kovid/work/calibre/src/calibre/gui2/tag_view.py:501
msgid "Item is blank"
msgstr ""
#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/tag_list_editor.py:75
-#: /home/kovid/work/calibre/src/calibre/gui2/tag_view.py:504
+#: /home/kovid/work/calibre/src/calibre/gui2/tag_view.py:502
msgid "An item cannot be set to nothing. Delete it instead."
msgstr ""
@@ -7987,37 +7212,75 @@ msgstr ""
msgid "Are you certain you want to delete the following items?"
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/tag_list_editor_ui.py:72
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/tag_list_editor_ui.py:77
msgid "Category Editor"
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/tag_list_editor_ui.py:73
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/tag_list_editor_ui.py:78
msgid "Items in use"
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/tag_list_editor_ui.py:74
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/tag_list_editor_ui.py:79
msgid ""
"Delete item from database. This will unapply the item from all books and "
"then remove it from the database."
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/tag_list_editor_ui.py:76
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/tag_list_editor_ui.py:81
msgid "Rename the item in every book where it is used."
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/test_email_ui.py:51
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/tag_list_editor_ui.py:83
+#: /home/kovid/work/calibre/src/calibre/gui2/preferences/toolbar_ui.py:106
+msgid "Ctrl+S"
+msgstr ""
+
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/test_email_ui.py:56
msgid "Test email settings"
msgstr "Email beállítások tesztelése"
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/test_email_ui.py:52
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/test_email_ui.py:57
msgid "Send test mail from %s to:"
msgstr "Teszt email küldése %s-ról a következőre:"
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/test_email_ui.py:53
-#: /home/kovid/work/calibre/src/calibre/gui2/filename_pattern_ui.py:115
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/test_email_ui.py:58
+#: /home/kovid/work/calibre/src/calibre/gui2/filename_pattern_ui.py:120
msgid "&Test"
msgstr "&Teszt"
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/tweak_epub_ui.py:55
+msgid "Display contents of exploded ePub"
+msgstr ""
+
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/tweak_epub_ui.py:56
+msgid "&Explode ePub"
+msgstr ""
+
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/tweak_epub_ui.py:57
+msgid "Rebuild ePub from exploded contents"
+msgstr ""
+
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/tweak_epub_ui.py:58
+msgid "&Rebuild ePub"
+msgstr ""
+
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/tweak_epub_ui.py:59
+msgid "Discard changes"
+msgstr ""
+
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/tweak_epub_ui.py:60
+#: /home/kovid/work/calibre/src/calibre/gui2/preferences/main.py:218
+msgid "&Cancel"
+msgstr ""
+
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/tweak_epub_ui.py:61
+msgid ""
+"Explode the ePub to display contents in a file browser window. To tweak "
+"individual files, right-click, then 'Open with...' your editor of choice. "
+"When tweaks are complete, close the file browser window. Rebuild the ePub, "
+"updating your calibre library."
+msgstr ""
+
#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/user_profiles.py:127
msgid "No recipe selected"
msgstr "Nincs hírösszeállítás kiválasztva"
@@ -8032,7 +7295,7 @@ msgstr "Hírösszeállítás "
#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/user_profiles.py:150
#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/user_profiles.py:161
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/user_profiles_ui.py:255
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/user_profiles_ui.py:260
msgid "Switch to Advanced mode"
msgstr "Váltás Haladó módba"
@@ -8097,35 +7360,35 @@ msgstr "A következő hírösszeállítás testreszabása:"
msgid "Choose a recipe file"
msgstr "Hírösszeállítás-fájl kiválasztása"
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/user_profiles_ui.py:248
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/user_profiles_ui.py:253
msgid "Add custom news source"
msgstr "Felhasználói hírforrás hozzáadása"
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/user_profiles_ui.py:249
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/user_profiles_ui.py:254
msgid "Available user recipes"
msgstr "Elérhető felhasználói hírösszeállítások"
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/user_profiles_ui.py:250
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/user_profiles_ui.py:255
msgid "Add/Update &recipe"
msgstr "Hírösszeállítás hozzáadása/módosítása"
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/user_profiles_ui.py:251
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/user_profiles_ui.py:256
msgid "&Remove recipe"
msgstr "Hírösszeállítás eltávolítása"
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/user_profiles_ui.py:252
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/user_profiles_ui.py:257
msgid "&Share recipe"
msgstr "Hírösszeállítás megosztása"
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/user_profiles_ui.py:253
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/user_profiles_ui.py:258
msgid "Customize &builtin recipe"
msgstr "Beépített hírösszeállítás testreszabása"
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/user_profiles_ui.py:254
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/user_profiles_ui.py:259
msgid "&Load recipe from file"
msgstr "Hírösszeállítás betöltése fájlból"
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/user_profiles_ui.py:256
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/user_profiles_ui.py:261
msgid ""
"