mirror of
https://github.com/kovidgoyal/calibre.git
synced 2025-08-11 09:13:57 -04:00
Merge from trunk
This commit is contained in:
commit
0c3212f256
@ -19,6 +19,65 @@
|
|||||||
# new recipes:
|
# new recipes:
|
||||||
# - title:
|
# - title:
|
||||||
|
|
||||||
|
- version: 0.8.12
|
||||||
|
date: 2011-07-29
|
||||||
|
|
||||||
|
new features:
|
||||||
|
- title: "Content server: Return the correct last modified date when serving ebook files. Also allow getting of book metadata as /get/opf/<book_id>"
|
||||||
|
|
||||||
|
- title: "Driver for the COBY MP977"
|
||||||
|
|
||||||
|
- title: "Get Books: Remove epub bud store. Add Ozon.ru and e-knigni.net stores. Fix broken amazon UK and DE stores."
|
||||||
|
tickets: [816091]
|
||||||
|
|
||||||
|
- title: "Add a new tweak to Preferences->Tweaks that allows auto generation of series numbers when importing books with a series name, but no number"
|
||||||
|
tickets: [815573]
|
||||||
|
|
||||||
|
bug fixes:
|
||||||
|
- title: "Fix a regression in 0.8.11 that broke calibre on linux systems that use a file system encoding that cannot support cyrillic characters"
|
||||||
|
tickets: [815224]
|
||||||
|
|
||||||
|
- title: "Fix long titles not wrapping in cover browser"
|
||||||
|
tickets: [816595]
|
||||||
|
|
||||||
|
- title: "When adding books, handle the case of files without read permission more gracefully."
|
||||||
|
tickets: [814771]
|
||||||
|
|
||||||
|
- title: "When changing metadata in EPUB files do not use the opf: namespace prefix on newly created elements. Apparently, FBReaderJ doesn't understand XML namespaces."
|
||||||
|
tickets: [814722]
|
||||||
|
|
||||||
|
- title: "Prevent metadata download from returning published dates earlier than 101 A.D."
|
||||||
|
|
||||||
|
- title: "Fix a bug where dates before 101AD in the database could cause errors"
|
||||||
|
tickets: [814964]
|
||||||
|
|
||||||
|
- title: "Fix an error in the book details panel if the user sets the default author link to blank"
|
||||||
|
|
||||||
|
improved recipes:
|
||||||
|
- The Economist
|
||||||
|
- Instapaper
|
||||||
|
- Corren
|
||||||
|
|
||||||
|
new recipes:
|
||||||
|
- title: Counterpunch
|
||||||
|
author: O. Emmerson
|
||||||
|
|
||||||
|
- title: National Geographic (PL)
|
||||||
|
author: Marcin Urban
|
||||||
|
|
||||||
|
- title: Caros Amigos
|
||||||
|
author: Pablo Aldama
|
||||||
|
|
||||||
|
- title: Aksiyon Dergisi
|
||||||
|
author: thomass
|
||||||
|
|
||||||
|
- title: Dnevnik (MK) and +Info
|
||||||
|
author: Darko Spasovski
|
||||||
|
|
||||||
|
- title: Dagens Industri
|
||||||
|
author: Jonas Svensson
|
||||||
|
|
||||||
|
|
||||||
- version: 0.8.11
|
- version: 0.8.11
|
||||||
date: 2011-07-22
|
date: 2011-07-22
|
||||||
|
|
||||||
|
@ -26,7 +26,8 @@ class Economist(BasicNewsRecipe):
|
|||||||
#cover_url = 'http://www.economist.com/images/covers/currentcoverus_large.jpg'
|
#cover_url = 'http://www.economist.com/images/covers/currentcoverus_large.jpg'
|
||||||
remove_tags = [
|
remove_tags = [
|
||||||
dict(name=['script', 'noscript', 'title', 'iframe', 'cf_floatingcontent']),
|
dict(name=['script', 'noscript', 'title', 'iframe', 'cf_floatingcontent']),
|
||||||
dict(attrs={'class':['dblClkTrk', 'ec-article-info', 'share_inline_header']}),
|
dict(attrs={'class':['dblClkTrk', 'ec-article-info',
|
||||||
|
'share_inline_header', 'related-items']}),
|
||||||
{'class': lambda x: x and 'share-links-header' in x},
|
{'class': lambda x: x and 'share-links-header' in x},
|
||||||
]
|
]
|
||||||
keep_only_tags = [dict(id='ec-article-body')]
|
keep_only_tags = [dict(id='ec-article-body')]
|
||||||
|
@ -21,7 +21,7 @@ class Economist(BasicNewsRecipe):
|
|||||||
remove_tags = [
|
remove_tags = [
|
||||||
dict(name=['script', 'noscript', 'title', 'iframe', 'cf_floatingcontent']),
|
dict(name=['script', 'noscript', 'title', 'iframe', 'cf_floatingcontent']),
|
||||||
dict(attrs={'class':['dblClkTrk', 'ec-article-info',
|
dict(attrs={'class':['dblClkTrk', 'ec-article-info',
|
||||||
'share_inline_header']}),
|
'share_inline_header', 'related-items']}),
|
||||||
{'class': lambda x: x and 'share-links-header' in x},
|
{'class': lambda x: x and 'share-links-header' in x},
|
||||||
]
|
]
|
||||||
keep_only_tags = [dict(id='ec-article-body')]
|
keep_only_tags = [dict(id='ec-article-body')]
|
||||||
|
58
recipes/el_colombiano.recipe
Normal file
58
recipes/el_colombiano.recipe
Normal file
@ -0,0 +1,58 @@
|
|||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
|
||||||
|
from calibre.web.feeds.news import BasicNewsRecipe
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
class AdvancedUserRecipe1311790237(BasicNewsRecipe):
|
||||||
|
title = u'Periódico El Colombiano'
|
||||||
|
language = 'es_CO'
|
||||||
|
__author__ = 'BIGO-CAVA'
|
||||||
|
cover_url = 'http://www.elcolombiano.com/images/logoElColombiano348x46.gif'
|
||||||
|
remove_tags_before = dict(id='contenidoArt')
|
||||||
|
remove_tags_after = dict(id='enviaTips')
|
||||||
|
remove_tags_after = dict(id='zonaPata')
|
||||||
|
oldest_article = 1
|
||||||
|
max_articles_per_feed = 100
|
||||||
|
remove_javascript = True
|
||||||
|
no_stylesheets = True
|
||||||
|
use_embedded_content = False
|
||||||
|
remove_empty_feeds = True
|
||||||
|
masthead_url = 'http://www.elcolombiano.com/images/logoElColombiano348x46.gif'
|
||||||
|
publication_type = 'newspaper'
|
||||||
|
|
||||||
|
extra_css = """
|
||||||
|
p{text-align: justify; font-size: 100%}
|
||||||
|
body{ text-align: left; font-size:100% }
|
||||||
|
h1{font-family: sans-serif; font-size:150%; font-weight:bold; text-align: justify; }
|
||||||
|
h3{font-family: sans-serif; font-size:100%; font-style: italic; text-align: justify; }
|
||||||
|
"""
|
||||||
|
|
||||||
|
|
||||||
|
feeds = [(u'Portada', u'http://www.elcolombiano.com/rss/portada.xml'),
|
||||||
|
(u'Antioquia', u'http://www.elcolombiano.com/rss/Antioquia.xml'),
|
||||||
|
(u'Colombia', u'http://www.elcolombiano.com/rss/Colombia.xml'),
|
||||||
|
(u'Economia', u'http://www.elcolombiano.com/rss/Economia.xml'),
|
||||||
|
(u'Internacional', u'http://www.elcolombiano.com/rss/Internacional.xml'),
|
||||||
|
(u'Politica', u'http://www.elcolombiano.com/rss/Politica.xml'),
|
||||||
|
(u'Cultura', u'http://www.elcolombiano.com/rss/Cultura.xml'),
|
||||||
|
(u'Entretenimiento', u'http://www.elcolombiano.com/rss/Farandula.xml'),
|
||||||
|
(u'Tecnologia', u'http://www.elcolombiano.com/rss/Tecnologia.xml'),
|
||||||
|
(u'Television', u'http://www.elcolombiano.com/rss/Television.xml'),
|
||||||
|
(u'Vida y Sociedad', u'http://www.elcolombiano.com/rss/Vida.xml'),
|
||||||
|
(u'Turismo', u'http://www.elcolombiano.com/rss/Turismo.xm'),
|
||||||
|
(u'Salud', u'http://www.elcolombiano.com/rss/Salud.xml'),
|
||||||
|
(u'Ciencia', u'http://www.elcolombiano.com/rss/Ciencia.xml')]
|
||||||
|
|
||||||
|
remove_tags = [dict(name='div', attrs={'class':'objetosRelacionados'}),
|
||||||
|
dict(name='div', attrs={'class':'notasRelacionadas contenedor'}),
|
||||||
|
dict(name='div', attrs={'class':'comentarios'}),
|
||||||
|
dict(name='div', attrs={'class':'mapaDelSitio'}),
|
||||||
|
dict(name='div', attrs={'class':'creditos'}),
|
||||||
|
dict(name='div', attrs={'class':'votos'}),
|
||||||
|
dict(name='div', attrs={'class':'divopt2'}),
|
||||||
|
dict(name='div', attrs={'class':'comentarios'}),
|
||||||
|
dict(name='div', attrs={'class':'pestanasLateral'}),
|
||||||
|
dict(name='div', attrs={'class':'resumenSeccion'}),
|
||||||
|
dict(name='div', attrs={'class':'zonaComercial'}),
|
||||||
|
dict(name='div', attrs={'id':'zonaPata'})]
|
53
recipes/el_tiempo.recipe
Normal file
53
recipes/el_tiempo.recipe
Normal file
@ -0,0 +1,53 @@
|
|||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
|
||||||
|
from calibre.web.feeds.news import BasicNewsRecipe
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
class ColombiaElTiempo02(BasicNewsRecipe):
|
||||||
|
title = u'Periódico el Tiempo'
|
||||||
|
language = 'es_CO'
|
||||||
|
__author__ = 'BIGO-CAVA'
|
||||||
|
cover_url = 'http://www.eltiempo.com/media/css/images/logo_footer.png'
|
||||||
|
remove_tags_before = dict(id='fb-root')
|
||||||
|
remove_tags_after = [dict(name='div', attrs={'class':'modulo reporte'})]
|
||||||
|
keep_only_tags = [dict(name='div', id='contenidoArt')]
|
||||||
|
remove_tags = [dict(name='div', attrs={'class':'social-media'}),
|
||||||
|
dict(name='div', attrs={'class':'caja-facebook'}),
|
||||||
|
dict(name='div', attrs={'class':'caja-twitter'}),
|
||||||
|
dict(name='div', attrs={'class':'caja-buzz'}),
|
||||||
|
dict(name='div', attrs={'class':'ico-mail2'}),
|
||||||
|
dict(name='div', attrs={'id':'caja-instapaper'}),
|
||||||
|
dict(name='div', attrs={'class':'modulo herramientas'})]
|
||||||
|
oldest_article = 2
|
||||||
|
max_articles_per_feed = 100
|
||||||
|
remove_javascript = True
|
||||||
|
no_stylesheets = True
|
||||||
|
use_embedded_content = False
|
||||||
|
remove_empty_feeds = True
|
||||||
|
masthead_url = 'http://www.eltiempo.com/media/css/images/logo_footer.png'
|
||||||
|
publication_type = 'newspaper'
|
||||||
|
|
||||||
|
extra_css = """
|
||||||
|
p{text-align: justify; font-size: 100%}
|
||||||
|
body{ text-align: left; font-size:100% }
|
||||||
|
h1{font-family: sans-serif; font-size:150%; font-weight:bold; text-align: justify; }
|
||||||
|
h3{font-family: sans-serif; font-size:100%; font-style: italic; text-align: justify; }
|
||||||
|
"""
|
||||||
|
|
||||||
|
|
||||||
|
feeds = [(u'Colombia', u'http://www.eltiempo.com/colombia/rss.xml'),
|
||||||
|
(u'Medellin', u'http://www.eltiempo.com/colombia/medellin/rss.xml'),
|
||||||
|
(u'Economia', u'http://www.eltiempo.com/economia/rss.xml'),
|
||||||
|
(u'Deportes', u'http://www.eltiempo.com/deportes/rss.xml'),
|
||||||
|
(u'Mundo', u'http://www.eltiempo.com/mundo/rss.xml'),
|
||||||
|
(u'Gente', u'http://www.eltiempo.com/gente/rss.xml'),
|
||||||
|
(u'Vida de Hoy', u'http://www.eltiempo.com/vida-de-hoy/rss.xml'),
|
||||||
|
(u'EEUU', u'http://www.eltiempo.com/mundo/estados-unidos/rss.xml'),
|
||||||
|
(u'LatinoAmerica', u'http://www.eltiempo.com/mundo/latinoamerica/rss.xml'),
|
||||||
|
(u'Europa', u'http://www.eltiempo.com/mundo/europa/rss.xml'),
|
||||||
|
(u'Medio Oriente', u'http://www.eltiempo.com/mundo/medio-oriente/rss.xml'),
|
||||||
|
(u'Vive in Medellin', u'http://medellin.vive.in/medellin/rss.xml'),
|
||||||
|
(u'Don Juan', u'http://www.revistadonjuan.com/feedrss/'),
|
||||||
|
(u'Alo', u'http://www.eltiempo.com/alo/rss.xml')]
|
36
recipes/portafolio.recipe
Normal file
36
recipes/portafolio.recipe
Normal file
@ -0,0 +1,36 @@
|
|||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
|
||||||
|
from calibre.web.feeds.news import BasicNewsRecipe
|
||||||
|
|
||||||
|
class AdvancedUserRecipe1311799898(BasicNewsRecipe):
|
||||||
|
title = u'Periódico Portafolio Colombia'
|
||||||
|
language = 'es_CO'
|
||||||
|
__author__ = 'BIGO-CAVA'
|
||||||
|
cover_url = 'http://www.portafolio.co/sites/portafolio.co/themes/portafolio_2011/logo.png'
|
||||||
|
remove_tags_before = dict(id='contenidoArt')
|
||||||
|
remove_tags_after = [dict(name='div', attrs={'class':'articulo-mas'})]
|
||||||
|
keep_only_tags = [dict(name='div', id='contenidoArt')]
|
||||||
|
oldest_article = 1
|
||||||
|
max_articles_per_feed = 100
|
||||||
|
remove_javascript = True
|
||||||
|
no_stylesheets = True
|
||||||
|
use_embedded_content = False
|
||||||
|
remove_empty_feeds = True
|
||||||
|
masthead_url = 'http://www.portafolio.co/sites/portafolio.co/themes/portafolio_2011/logo.png'
|
||||||
|
publication_type = 'newspaper'
|
||||||
|
|
||||||
|
extra_css = """
|
||||||
|
p{text-align: justify; font-size: 100%}
|
||||||
|
body{ text-align: left; font-size:100% }
|
||||||
|
h1{font-family: sans-serif; font-size:150%; font-weight:bold; text-align: justify; }
|
||||||
|
h3{font-family: sans-serif; font-size:100%; font-style: italic; text-align: justify; }
|
||||||
|
"""
|
||||||
|
|
||||||
|
|
||||||
|
feeds = [(u'Negocios', u'http://www.portafolio.co/negocios/feed'),
|
||||||
|
(u'Economia', u'http://www.portafolio.co/economia/feed'),
|
||||||
|
(u'Internacional', u'http://www.portafolio.co/internacional/feed'),
|
||||||
|
(u'Indicadores', u'http://www.portafolio.co/indicadores/feed'),
|
||||||
|
(u'Opinion', u'http://www.portafolio.co/opinion/feed'),
|
||||||
|
(u'Finanzas Personales', u'http://www.portafolio.co/finanzas-personales/feed'),
|
||||||
|
(u'Herramientas', u'http://www.portafolio.co/herramientas/feed')]
|
@ -353,9 +353,14 @@ def browser(honor_time=True, max_time=2, mobile_browser=False, user_agent=None):
|
|||||||
if user_agent is None:
|
if user_agent is None:
|
||||||
user_agent = USER_AGENT_MOBILE if mobile_browser else USER_AGENT
|
user_agent = USER_AGENT_MOBILE if mobile_browser else USER_AGENT
|
||||||
opener.addheaders = [('User-agent', user_agent)]
|
opener.addheaders = [('User-agent', user_agent)]
|
||||||
http_proxy = get_proxies().get('http', None)
|
proxies = get_proxies()
|
||||||
|
http_proxy = proxies.get('http', None)
|
||||||
if http_proxy:
|
if http_proxy:
|
||||||
opener.set_proxies({'http':http_proxy})
|
opener.set_proxies({'http':http_proxy})
|
||||||
|
https_proxy = proxies.get('https', None)
|
||||||
|
if https_proxy:
|
||||||
|
opener.set_proxies({'https':https_proxy})
|
||||||
|
|
||||||
return opener
|
return opener
|
||||||
|
|
||||||
def fit_image(width, height, pwidth, pheight):
|
def fit_image(width, height, pwidth, pheight):
|
||||||
|
@ -4,7 +4,7 @@ __license__ = 'GPL v3'
|
|||||||
__copyright__ = '2008, Kovid Goyal kovid@kovidgoyal.net'
|
__copyright__ = '2008, Kovid Goyal kovid@kovidgoyal.net'
|
||||||
__docformat__ = 'restructuredtext en'
|
__docformat__ = 'restructuredtext en'
|
||||||
__appname__ = u'calibre'
|
__appname__ = u'calibre'
|
||||||
numeric_version = (0, 8, 11)
|
numeric_version = (0, 8, 12)
|
||||||
__version__ = u'.'.join(map(unicode, numeric_version))
|
__version__ = u'.'.join(map(unicode, numeric_version))
|
||||||
__author__ = u"Kovid Goyal <kovid@kovidgoyal.net>"
|
__author__ = u"Kovid Goyal <kovid@kovidgoyal.net>"
|
||||||
|
|
||||||
|
@ -333,8 +333,14 @@ class KOBO(USBMS):
|
|||||||
except Exception as e:
|
except Exception as e:
|
||||||
if 'no such column' not in str(e):
|
if 'no such column' not in str(e):
|
||||||
raise
|
raise
|
||||||
cursor.execute('update content set ReadStatus=0, FirstTimeReading = \'true\', ___PercentRead=0 ' \
|
try:
|
||||||
'where BookID is Null and ContentID =?',t)
|
cursor.execute('update content set ReadStatus=0, FirstTimeReading = \'true\', ___PercentRead=0 ' \
|
||||||
|
'where BookID is Null and ContentID =?',t)
|
||||||
|
except Exception as e:
|
||||||
|
if 'no such column' not in str(e):
|
||||||
|
raise
|
||||||
|
cursor.execute('update content set ReadStatus=0, FirstTimeReading = \'true\' ' \
|
||||||
|
'where BookID is Null and ContentID =?',t)
|
||||||
|
|
||||||
|
|
||||||
connection.commit()
|
connection.commit()
|
||||||
|
@ -9,12 +9,20 @@ __docformat__ = 'restructuredtext en'
|
|||||||
|
|
||||||
import struct, datetime, sys, os, shutil
|
import struct, datetime, sys, os, shutil
|
||||||
from collections import OrderedDict, defaultdict
|
from collections import OrderedDict, defaultdict
|
||||||
|
|
||||||
|
from lxml import html
|
||||||
|
|
||||||
from calibre.utils.date import utc_tz
|
from calibre.utils.date import utc_tz
|
||||||
from calibre.ebooks.mobi.langcodes import main_language, sub_language
|
from calibre.ebooks.mobi.langcodes import main_language, sub_language
|
||||||
from calibre.ebooks.mobi.utils import (decode_hex_number, decint,
|
from calibre.ebooks.mobi.utils import (decode_hex_number, decint,
|
||||||
get_trailing_data, decode_tbs)
|
get_trailing_data, decode_tbs)
|
||||||
from calibre.utils.magick.draw import identify_data
|
from calibre.utils.magick.draw import identify_data
|
||||||
|
|
||||||
|
def format_bytes(byts):
|
||||||
|
byts = bytearray(byts)
|
||||||
|
byts = [hex(b)[2:] for b in byts]
|
||||||
|
return ' '.join(byts)
|
||||||
|
|
||||||
# PalmDB {{{
|
# PalmDB {{{
|
||||||
class PalmDOCAttributes(object):
|
class PalmDOCAttributes(object):
|
||||||
|
|
||||||
@ -73,7 +81,7 @@ class PalmDB(object):
|
|||||||
self.ident = self.type + self.creator
|
self.ident = self.type + self.creator
|
||||||
if self.ident not in (b'BOOKMOBI', b'TEXTREAD'):
|
if self.ident not in (b'BOOKMOBI', b'TEXTREAD'):
|
||||||
raise ValueError('Unknown book ident: %r'%self.ident)
|
raise ValueError('Unknown book ident: %r'%self.ident)
|
||||||
self.uid_seed, = struct.unpack(b'>I', self.raw[68:72])
|
self.last_record_uid, = struct.unpack(b'>I', self.raw[68:72])
|
||||||
self.next_rec_list_id = self.raw[72:76]
|
self.next_rec_list_id = self.raw[72:76]
|
||||||
|
|
||||||
self.number_of_records, = struct.unpack(b'>H', self.raw[76:78])
|
self.number_of_records, = struct.unpack(b'>H', self.raw[76:78])
|
||||||
@ -94,7 +102,7 @@ class PalmDB(object):
|
|||||||
ans.append('Sort Info ID: %r'%self.sort_info_id)
|
ans.append('Sort Info ID: %r'%self.sort_info_id)
|
||||||
ans.append('Type: %r'%self.type)
|
ans.append('Type: %r'%self.type)
|
||||||
ans.append('Creator: %r'%self.creator)
|
ans.append('Creator: %r'%self.creator)
|
||||||
ans.append('UID seed: %r'%self.uid_seed)
|
ans.append('Last record UID +1: %r'%self.last_record_uid)
|
||||||
ans.append('Next record list id: %r'%self.next_rec_list_id)
|
ans.append('Next record list id: %r'%self.next_rec_list_id)
|
||||||
ans.append('Number of records: %s'%self.number_of_records)
|
ans.append('Number of records: %s'%self.number_of_records)
|
||||||
|
|
||||||
@ -269,7 +277,8 @@ class MOBIHeader(object): # {{{
|
|||||||
self.first_image_index, = struct.unpack(b'>I', self.raw[108:112])
|
self.first_image_index, = struct.unpack(b'>I', self.raw[108:112])
|
||||||
self.huffman_record_offset, = struct.unpack(b'>I', self.raw[112:116])
|
self.huffman_record_offset, = struct.unpack(b'>I', self.raw[112:116])
|
||||||
self.huffman_record_count, = struct.unpack(b'>I', self.raw[116:120])
|
self.huffman_record_count, = struct.unpack(b'>I', self.raw[116:120])
|
||||||
self.unknown2 = self.raw[120:128]
|
self.datp_record_offset, = struct.unpack(b'>I', self.raw[120:124])
|
||||||
|
self.datp_record_count, = struct.unpack(b'>I', self.raw[124:128])
|
||||||
self.exth_flags, = struct.unpack(b'>I', self.raw[128:132])
|
self.exth_flags, = struct.unpack(b'>I', self.raw[128:132])
|
||||||
self.has_exth = bool(self.exth_flags & 0x40)
|
self.has_exth = bool(self.exth_flags & 0x40)
|
||||||
self.has_drm_data = self.length >= 174 and len(self.raw) >= 180
|
self.has_drm_data = self.length >= 174 and len(self.raw) >= 180
|
||||||
@ -344,7 +353,8 @@ class MOBIHeader(object): # {{{
|
|||||||
ans.append('First Image index: %d'%self.first_image_index)
|
ans.append('First Image index: %d'%self.first_image_index)
|
||||||
ans.append('Huffman record offset: %d'%self.huffman_record_offset)
|
ans.append('Huffman record offset: %d'%self.huffman_record_offset)
|
||||||
ans.append('Huffman record count: %d'%self.huffman_record_count)
|
ans.append('Huffman record count: %d'%self.huffman_record_count)
|
||||||
ans.append('Unknown2: %r'%self.unknown2)
|
ans.append('DATP record offset: %r'%self.datp_record_offset)
|
||||||
|
ans.append('DATP record count: %r'%self.datp_record_count)
|
||||||
ans.append('EXTH flags: %s (%s)'%(bin(self.exth_flags)[2:], self.has_exth))
|
ans.append('EXTH flags: %s (%s)'%(bin(self.exth_flags)[2:], self.has_exth))
|
||||||
if self.has_drm_data:
|
if self.has_drm_data:
|
||||||
ans.append('Unknown3: %r'%self.unknown3)
|
ans.append('Unknown3: %r'%self.unknown3)
|
||||||
@ -403,6 +413,109 @@ class TagX(object): # {{{
|
|||||||
self.num_values, bin(self.bitmask), self.eof)
|
self.num_values, bin(self.bitmask), self.eof)
|
||||||
# }}}
|
# }}}
|
||||||
|
|
||||||
|
class SecondaryIndexHeader(object): # {{{
|
||||||
|
|
||||||
|
def __init__(self, record):
|
||||||
|
self.record = record
|
||||||
|
raw = self.record.raw
|
||||||
|
#open('/t/index_header.bin', 'wb').write(raw)
|
||||||
|
if raw[:4] != b'INDX':
|
||||||
|
raise ValueError('Invalid Secondary Index Record')
|
||||||
|
self.header_length, = struct.unpack('>I', raw[4:8])
|
||||||
|
self.unknown1 = raw[8:16]
|
||||||
|
self.index_type, = struct.unpack('>I', raw[16:20])
|
||||||
|
self.index_type_desc = {0: 'normal', 2:
|
||||||
|
'inflection', 6: 'calibre'}.get(self.index_type, 'unknown')
|
||||||
|
self.idxt_start, = struct.unpack('>I', raw[20:24])
|
||||||
|
self.index_count, = struct.unpack('>I', raw[24:28])
|
||||||
|
self.index_encoding_num, = struct.unpack('>I', raw[28:32])
|
||||||
|
self.index_encoding = {65001: 'utf-8', 1252:
|
||||||
|
'cp1252'}.get(self.index_encoding_num, 'unknown')
|
||||||
|
if self.index_encoding == 'unknown':
|
||||||
|
raise ValueError(
|
||||||
|
'Unknown index encoding: %d'%self.index_encoding_num)
|
||||||
|
self.unknown2 = raw[32:36]
|
||||||
|
self.num_index_entries, = struct.unpack('>I', raw[36:40])
|
||||||
|
self.ordt_start, = struct.unpack('>I', raw[40:44])
|
||||||
|
self.ligt_start, = struct.unpack('>I', raw[44:48])
|
||||||
|
self.num_of_ligt_entries, = struct.unpack('>I', raw[48:52])
|
||||||
|
self.num_of_cncx_blocks, = struct.unpack('>I', raw[52:56])
|
||||||
|
self.unknown3 = raw[56:180]
|
||||||
|
self.tagx_offset, = struct.unpack(b'>I', raw[180:184])
|
||||||
|
if self.tagx_offset != self.header_length:
|
||||||
|
raise ValueError('TAGX offset and header length disagree')
|
||||||
|
self.unknown4 = raw[184:self.header_length]
|
||||||
|
|
||||||
|
tagx = raw[self.header_length:]
|
||||||
|
if not tagx.startswith(b'TAGX'):
|
||||||
|
raise ValueError('Invalid TAGX section')
|
||||||
|
self.tagx_header_length, = struct.unpack('>I', tagx[4:8])
|
||||||
|
self.tagx_control_byte_count, = struct.unpack('>I', tagx[8:12])
|
||||||
|
tag_table = tagx[12:self.tagx_header_length]
|
||||||
|
if len(tag_table) % 4 != 0:
|
||||||
|
raise ValueError('Invalid Tag table')
|
||||||
|
num_tagx_entries = len(tag_table) // 4
|
||||||
|
self.tagx_entries = []
|
||||||
|
for i in range(num_tagx_entries):
|
||||||
|
self.tagx_entries.append(TagX(tag_table[i*4:(i+1)*4],
|
||||||
|
self.tagx_control_byte_count))
|
||||||
|
if self.tagx_entries and not self.tagx_entries[-1].is_eof:
|
||||||
|
raise ValueError('TAGX last entry is not EOF')
|
||||||
|
|
||||||
|
idxt0_pos = self.header_length+self.tagx_header_length
|
||||||
|
num = ord(raw[idxt0_pos])
|
||||||
|
count_pos = idxt0_pos+1+num
|
||||||
|
self.last_entry = raw[idxt0_pos+1:count_pos]
|
||||||
|
self.ncx_count, = struct.unpack(b'>H', raw[count_pos:count_pos+2])
|
||||||
|
|
||||||
|
# There may be some alignment zero bytes between the end of the idxt0
|
||||||
|
# and self.idxt_start
|
||||||
|
idxt = raw[self.idxt_start:]
|
||||||
|
if idxt[:4] != b'IDXT':
|
||||||
|
raise ValueError('Invalid IDXT header')
|
||||||
|
length_check, = struct.unpack(b'>H', idxt[4:6])
|
||||||
|
if length_check != self.header_length + self.tagx_header_length:
|
||||||
|
raise ValueError('Length check failed')
|
||||||
|
if idxt[6:].replace(b'\0', b''):
|
||||||
|
raise ValueError('Non null trailing bytes after IDXT')
|
||||||
|
|
||||||
|
|
||||||
|
def __str__(self):
|
||||||
|
ans = ['*'*20 + ' Secondary Index Header '+ '*'*20]
|
||||||
|
a = ans.append
|
||||||
|
def u(w):
|
||||||
|
a('Unknown: %r (%d bytes) (All zeros: %r)'%(w,
|
||||||
|
len(w), not bool(w.replace(b'\0', b'')) ))
|
||||||
|
|
||||||
|
a('Header length: %d'%self.header_length)
|
||||||
|
u(self.unknown1)
|
||||||
|
a('Index Type: %s (%d)'%(self.index_type_desc, self.index_type))
|
||||||
|
a('Offset to IDXT start: %d'%self.idxt_start)
|
||||||
|
a('Number of index records: %d'%self.index_count)
|
||||||
|
a('Index encoding: %s (%d)'%(self.index_encoding,
|
||||||
|
self.index_encoding_num))
|
||||||
|
u(self.unknown2)
|
||||||
|
a('Number of index entries: %d'% self.num_index_entries)
|
||||||
|
a('ORDT start: %d'%self.ordt_start)
|
||||||
|
a('LIGT start: %d'%self.ligt_start)
|
||||||
|
a('Number of LIGT entries: %d'%self.num_of_ligt_entries)
|
||||||
|
a('Number of cncx blocks: %d'%self.num_of_cncx_blocks)
|
||||||
|
u(self.unknown3)
|
||||||
|
a('TAGX offset: %d'%self.tagx_offset)
|
||||||
|
u(self.unknown4)
|
||||||
|
a('\n\n')
|
||||||
|
a('*'*20 + ' TAGX Header (%d bytes)'%self.tagx_header_length+ '*'*20)
|
||||||
|
a('Header length: %d'%self.tagx_header_length)
|
||||||
|
a('Control byte count: %d'%self.tagx_control_byte_count)
|
||||||
|
for i in self.tagx_entries:
|
||||||
|
a('\t' + repr(i))
|
||||||
|
a('Index of last IndexEntry in secondary index record: %s'% self.last_entry)
|
||||||
|
a('Number of entries in the NCX: %d'% self.ncx_count)
|
||||||
|
|
||||||
|
return '\n'.join(ans)
|
||||||
|
|
||||||
|
# }}}
|
||||||
|
|
||||||
class IndexHeader(object): # {{{
|
class IndexHeader(object): # {{{
|
||||||
|
|
||||||
def __init__(self, record):
|
def __init__(self, record):
|
||||||
@ -452,12 +565,12 @@ class IndexHeader(object): # {{{
|
|||||||
self.tagx_control_byte_count))
|
self.tagx_control_byte_count))
|
||||||
if self.tagx_entries and not self.tagx_entries[-1].is_eof:
|
if self.tagx_entries and not self.tagx_entries[-1].is_eof:
|
||||||
raise ValueError('TAGX last entry is not EOF')
|
raise ValueError('TAGX last entry is not EOF')
|
||||||
self.tagx_entries = self.tagx_entries[:-1]
|
|
||||||
|
|
||||||
idxt0_pos = self.header_length+self.tagx_header_length
|
idxt0_pos = self.header_length+self.tagx_header_length
|
||||||
last_num, consumed = decode_hex_number(raw[idxt0_pos:])
|
last_num, consumed = decode_hex_number(raw[idxt0_pos:])
|
||||||
count_pos = idxt0_pos + consumed
|
count_pos = idxt0_pos + consumed
|
||||||
self.ncx_count, = struct.unpack(b'>H', raw[count_pos:count_pos+2])
|
self.ncx_count, = struct.unpack(b'>H', raw[count_pos:count_pos+2])
|
||||||
|
self.last_entry = last_num
|
||||||
|
|
||||||
if last_num != self.ncx_count - 1:
|
if last_num != self.ncx_count - 1:
|
||||||
raise ValueError('Last id number in the NCX != NCX count - 1')
|
raise ValueError('Last id number in the NCX != NCX count - 1')
|
||||||
@ -470,6 +583,9 @@ class IndexHeader(object): # {{{
|
|||||||
length_check, = struct.unpack(b'>H', idxt[4:6])
|
length_check, = struct.unpack(b'>H', idxt[4:6])
|
||||||
if length_check != self.header_length + self.tagx_header_length:
|
if length_check != self.header_length + self.tagx_header_length:
|
||||||
raise ValueError('Length check failed')
|
raise ValueError('Length check failed')
|
||||||
|
if idxt[6:].replace(b'\0', b''):
|
||||||
|
raise ValueError('Non null trailing bytes after IDXT')
|
||||||
|
|
||||||
|
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
ans = ['*'*20 + ' Index Header '+ '*'*20]
|
ans = ['*'*20 + ' Index Header '+ '*'*20]
|
||||||
@ -500,6 +616,7 @@ class IndexHeader(object): # {{{
|
|||||||
a('Control byte count: %d'%self.tagx_control_byte_count)
|
a('Control byte count: %d'%self.tagx_control_byte_count)
|
||||||
for i in self.tagx_entries:
|
for i in self.tagx_entries:
|
||||||
a('\t' + repr(i))
|
a('\t' + repr(i))
|
||||||
|
a('Index of last IndexEntry in primary index record: %s'% self.last_entry)
|
||||||
a('Number of entries in the NCX: %d'% self.ncx_count)
|
a('Number of entries in the NCX: %d'% self.ncx_count)
|
||||||
|
|
||||||
return '\n'.join(ans)
|
return '\n'.join(ans)
|
||||||
@ -518,6 +635,9 @@ class Tag(object): # {{{
|
|||||||
3: ('label_offset', 'Offset to label in CNCX'),
|
3: ('label_offset', 'Offset to label in CNCX'),
|
||||||
4: ('depth', 'Depth of this entry in TOC'),
|
4: ('depth', 'Depth of this entry in TOC'),
|
||||||
|
|
||||||
|
11: ('secondary', '[unknown, unknown, '
|
||||||
|
'tag type from TAGX in primary index header]'),
|
||||||
|
|
||||||
# The remaining tag types have to be interpreted subject to the type
|
# The remaining tag types have to be interpreted subject to the type
|
||||||
# of index entry they are present in
|
# of index entry they are present in
|
||||||
}
|
}
|
||||||
@ -532,6 +652,15 @@ class Tag(object): # {{{
|
|||||||
21 : ('Parent section index', 'parent_index'),
|
21 : ('Parent section index', 'parent_index'),
|
||||||
22 : ('Description offset in cncx', 'desc_offset'),
|
22 : ('Description offset in cncx', 'desc_offset'),
|
||||||
23 : ('Author offset in cncx', 'author_offset'),
|
23 : ('Author offset in cncx', 'author_offset'),
|
||||||
|
69 : ('Offset from first image record num to the'
|
||||||
|
' image record associated with this article',
|
||||||
|
'image_index'),
|
||||||
|
70 : ('Description offset in cncx', 'desc_offset'),
|
||||||
|
71 : ('Image attribution offset in cncx',
|
||||||
|
'image_attr_offset'),
|
||||||
|
72 : ('Image caption offset in cncx',
|
||||||
|
'image_caption_offset'),
|
||||||
|
73 : ('Author offset in cncx', 'author_offset'),
|
||||||
},
|
},
|
||||||
|
|
||||||
'chapter_with_subchapters' : {
|
'chapter_with_subchapters' : {
|
||||||
@ -543,6 +672,8 @@ class Tag(object): # {{{
|
|||||||
5 : ('Class offset in cncx', 'class_offset'),
|
5 : ('Class offset in cncx', 'class_offset'),
|
||||||
22 : ('First section index', 'first_child_index'),
|
22 : ('First section index', 'first_child_index'),
|
||||||
23 : ('Last section index', 'last_child_index'),
|
23 : ('Last section index', 'last_child_index'),
|
||||||
|
69 : ('Offset from first image record num to masthead'
|
||||||
|
' record', 'image_index'),
|
||||||
},
|
},
|
||||||
|
|
||||||
'section' : {
|
'section' : {
|
||||||
@ -555,21 +686,24 @@ class Tag(object): # {{{
|
|||||||
|
|
||||||
|
|
||||||
def __init__(self, tagx, vals, entry_type, cncx):
|
def __init__(self, tagx, vals, entry_type, cncx):
|
||||||
self.value = vals if len(vals) > 1 else vals[0]
|
self.value = vals if len(vals) > 1 else vals[0] if vals else None
|
||||||
self.entry_type = entry_type
|
self.entry_type = entry_type
|
||||||
|
tag_type = tagx.tag
|
||||||
|
|
||||||
self.cncx_value = None
|
self.cncx_value = None
|
||||||
if tagx.tag in self.TAG_MAP:
|
if tag_type in self.TAG_MAP:
|
||||||
self.attr, self.desc = self.TAG_MAP[tagx.tag]
|
self.attr, self.desc = self.TAG_MAP[tag_type]
|
||||||
else:
|
else:
|
||||||
try:
|
try:
|
||||||
td = self.INTERPRET_MAP[entry_type]
|
td = self.INTERPRET_MAP[entry_type]
|
||||||
except:
|
except:
|
||||||
raise ValueError('Unknown entry type: %s'%entry_type)
|
raise ValueError('Unknown entry type: %s'%entry_type)
|
||||||
try:
|
try:
|
||||||
self.desc, self.attr = td[tagx.tag]
|
self.desc, self.attr = td[tag_type]
|
||||||
except:
|
except:
|
||||||
raise ValueError('Unknown tag: %d for entry type: %s'%(
|
print ('Unknown tag value: %d'%tag_type)
|
||||||
tagx.tag, entry_type))
|
self.desc = '??Unknown (tag value: %d)'%tag_type
|
||||||
|
self.attr = 'unknown'
|
||||||
if '_offset' in self.attr:
|
if '_offset' in self.attr:
|
||||||
self.cncx_value = cncx[self.value]
|
self.cncx_value = cncx[self.value]
|
||||||
|
|
||||||
@ -590,6 +724,9 @@ class IndexEntry(object): # {{{
|
|||||||
'''
|
'''
|
||||||
|
|
||||||
TYPES = {
|
TYPES = {
|
||||||
|
# Present in secondary index record
|
||||||
|
0x01 : 'null',
|
||||||
|
0x02 : 'publication_meta',
|
||||||
# Present in book type files
|
# Present in book type files
|
||||||
0x0f : 'chapter',
|
0x0f : 'chapter',
|
||||||
0x6f : 'chapter_with_subchapters',
|
0x6f : 'chapter_with_subchapters',
|
||||||
@ -600,7 +737,8 @@ class IndexEntry(object): # {{{
|
|||||||
0x3f : 'article',
|
0x3f : 'article',
|
||||||
}
|
}
|
||||||
|
|
||||||
def __init__(self, ident, entry_type, raw, cncx, tagx_entries, flags=0):
|
def __init__(self, ident, entry_type, raw, cncx, tagx_entries,
|
||||||
|
control_byte_count):
|
||||||
self.index = ident
|
self.index = ident
|
||||||
self.raw = raw
|
self.raw = raw
|
||||||
self.tags = []
|
self.tags = []
|
||||||
@ -614,11 +752,26 @@ class IndexEntry(object): # {{{
|
|||||||
except KeyError:
|
except KeyError:
|
||||||
raise ValueError('Unknown Index Entry type: %s'%hex(entry_type))
|
raise ValueError('Unknown Index Entry type: %s'%hex(entry_type))
|
||||||
|
|
||||||
|
if control_byte_count not in (1, 2):
|
||||||
|
raise ValueError('Unknown control byte count: %d'%
|
||||||
|
control_byte_count)
|
||||||
|
|
||||||
|
self.flags = 0
|
||||||
|
|
||||||
|
if control_byte_count == 2:
|
||||||
|
self.flags = ord(raw[0])
|
||||||
|
raw = raw[1:]
|
||||||
|
|
||||||
expected_tags = [tag for tag in tagx_entries if tag.bitmask &
|
expected_tags = [tag for tag in tagx_entries if tag.bitmask &
|
||||||
entry_type]
|
entry_type]
|
||||||
|
|
||||||
|
flags = self.flags
|
||||||
for tag in expected_tags:
|
for tag in expected_tags:
|
||||||
vals = []
|
vals = []
|
||||||
|
if tag.tag > 64:
|
||||||
|
has_tag = flags & 0b1
|
||||||
|
flags = flags >> 1
|
||||||
|
if not has_tag: continue
|
||||||
for i in range(tag.num_values):
|
for i in range(tag.num_values):
|
||||||
if not raw:
|
if not raw:
|
||||||
raise ValueError('Index entry does not match TAGX header')
|
raise ValueError('Index entry does not match TAGX header')
|
||||||
@ -627,26 +780,11 @@ class IndexEntry(object): # {{{
|
|||||||
vals.append(val)
|
vals.append(val)
|
||||||
self.tags.append(Tag(tag, vals, self.entry_type, cncx))
|
self.tags.append(Tag(tag, vals, self.entry_type, cncx))
|
||||||
|
|
||||||
if flags & 0b10:
|
|
||||||
# Look for optional description and author
|
|
||||||
desc_tag = [t for t in tagx_entries if t.tag == 22]
|
|
||||||
if desc_tag and raw:
|
|
||||||
val, consumed = decint(raw)
|
|
||||||
raw = raw[consumed:]
|
|
||||||
if val:
|
|
||||||
self.tags.append(Tag(desc_tag[0], [val], self.entry_type,
|
|
||||||
cncx))
|
|
||||||
if flags & 0b100:
|
|
||||||
aut_tag = [t for t in tagx_entries if t.tag == 23]
|
|
||||||
if aut_tag and raw:
|
|
||||||
val, consumed = decint(raw)
|
|
||||||
raw = raw[consumed:]
|
|
||||||
if val:
|
|
||||||
self.tags.append(Tag(aut_tag[0], [val], self.entry_type,
|
|
||||||
cncx))
|
|
||||||
|
|
||||||
self.consumed = len(orig_raw) - len(raw)
|
self.consumed = len(orig_raw) - len(raw)
|
||||||
self.trailing_bytes = raw
|
self.trailing_bytes = raw
|
||||||
|
if self.trailing_bytes.replace(b'\0', b''):
|
||||||
|
raise ValueError('IndexEntry has leftover bytes: %s'%format_bytes(
|
||||||
|
self.trailing_bytes))
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def label(self):
|
def label(self):
|
||||||
@ -698,11 +836,13 @@ class IndexEntry(object): # {{{
|
|||||||
return -1
|
return -1
|
||||||
|
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
ans = ['Index Entry(index=%s, entry_type=%s (%s), length=%d, byte_size=%d)'%(
|
ans = ['Index Entry(index=%s, entry_type=%s, flags=%s, '
|
||||||
self.index, self.entry_type, bin(self.entry_type_raw)[2:],
|
'length=%d, byte_size=%d)'%(
|
||||||
|
self.index, self.entry_type, bin(self.flags)[2:],
|
||||||
len(self.tags), self.byte_size)]
|
len(self.tags), self.byte_size)]
|
||||||
for tag in self.tags:
|
for tag in self.tags:
|
||||||
ans.append('\t'+str(tag))
|
if tag.value is not None:
|
||||||
|
ans.append('\t'+str(tag))
|
||||||
if self.first_child_index != -1:
|
if self.first_child_index != -1:
|
||||||
ans.append('\tNumber of children: %d'%(self.last_child_index -
|
ans.append('\tNumber of children: %d'%(self.last_child_index -
|
||||||
self.first_child_index + 1))
|
self.first_child_index + 1))
|
||||||
@ -712,6 +852,90 @@ class IndexEntry(object): # {{{
|
|||||||
|
|
||||||
# }}}
|
# }}}
|
||||||
|
|
||||||
|
class SecondaryIndexRecord(object): # {{{
|
||||||
|
|
||||||
|
def __init__(self, record, index_header, cncx):
|
||||||
|
self.record = record
|
||||||
|
raw = self.record.raw
|
||||||
|
|
||||||
|
if raw[:4] != b'INDX':
|
||||||
|
raise ValueError('Invalid Primary Index Record')
|
||||||
|
|
||||||
|
u = struct.unpack
|
||||||
|
|
||||||
|
self.header_length, = u('>I', raw[4:8])
|
||||||
|
self.unknown1 = raw[8:12]
|
||||||
|
self.header_type, = u('>I', raw[12:16])
|
||||||
|
self.unknown2 = raw[16:20]
|
||||||
|
self.idxt_offset, self.idxt_count = u(b'>II', raw[20:28])
|
||||||
|
if self.idxt_offset < 192:
|
||||||
|
raise ValueError('Unknown Index record structure')
|
||||||
|
self.unknown3 = raw[28:36]
|
||||||
|
self.unknown4 = raw[36:192] # Should be 156 bytes
|
||||||
|
|
||||||
|
self.index_offsets = []
|
||||||
|
indices = raw[self.idxt_offset:]
|
||||||
|
if indices[:4] != b'IDXT':
|
||||||
|
raise ValueError("Invalid IDXT index table")
|
||||||
|
indices = indices[4:]
|
||||||
|
for i in range(self.idxt_count):
|
||||||
|
off, = u(b'>H', indices[i*2:(i+1)*2])
|
||||||
|
self.index_offsets.append(off-192)
|
||||||
|
rest = indices[(i+1)*2:]
|
||||||
|
if rest.replace(b'\0', ''): # There can be padding null bytes
|
||||||
|
raise ValueError('Extra bytes after IDXT table: %r'%rest)
|
||||||
|
|
||||||
|
indxt = raw[192:self.idxt_offset]
|
||||||
|
self.size_of_indxt_block = len(indxt)
|
||||||
|
|
||||||
|
self.indices = []
|
||||||
|
for i, off in enumerate(self.index_offsets):
|
||||||
|
try:
|
||||||
|
next_off = self.index_offsets[i+1]
|
||||||
|
except:
|
||||||
|
next_off = len(indxt)
|
||||||
|
num = ord(indxt[off])
|
||||||
|
index = indxt[off+1:off+1+num]
|
||||||
|
consumed = 1 + num
|
||||||
|
entry_type = ord(indxt[off+consumed])
|
||||||
|
pos = off+consumed+1
|
||||||
|
idxe = IndexEntry(index, entry_type,
|
||||||
|
indxt[pos:next_off], cncx,
|
||||||
|
index_header.tagx_entries,
|
||||||
|
index_header.tagx_control_byte_count)
|
||||||
|
self.indices.append(idxe)
|
||||||
|
|
||||||
|
rest = indxt[pos+self.indices[-1].consumed:]
|
||||||
|
if rest.replace(b'\0', b''): # There can be padding null bytes
|
||||||
|
raise ValueError('Extra bytes after IDXT table: %r'%rest)
|
||||||
|
|
||||||
|
|
||||||
|
def __str__(self):
|
||||||
|
ans = ['*'*20 + ' Secondary Index Record (%d bytes) '%len(self.record.raw)+ '*'*20]
|
||||||
|
a = ans.append
|
||||||
|
def u(w):
|
||||||
|
a('Unknown: %r (%d bytes) (All zeros: %r)'%(w,
|
||||||
|
len(w), not bool(w.replace(b'\0', b'')) ))
|
||||||
|
a('Header length: %d'%self.header_length)
|
||||||
|
u(self.unknown1)
|
||||||
|
a('Unknown (header type? index record number? always 1?): %d'%self.header_type)
|
||||||
|
u(self.unknown2)
|
||||||
|
a('IDXT Offset (%d block size): %d'%(self.size_of_indxt_block,
|
||||||
|
self.idxt_offset))
|
||||||
|
a('IDXT Count: %d'%self.idxt_count)
|
||||||
|
u(self.unknown3)
|
||||||
|
u(self.unknown4)
|
||||||
|
a('Index offsets: %r'%self.index_offsets)
|
||||||
|
a('\nIndex Entries (%d entries):'%len(self.indices))
|
||||||
|
for entry in self.indices:
|
||||||
|
a(str(entry))
|
||||||
|
a('')
|
||||||
|
|
||||||
|
|
||||||
|
return '\n'.join(ans)
|
||||||
|
|
||||||
|
# }}}
|
||||||
|
|
||||||
class IndexRecord(object): # {{{
|
class IndexRecord(object): # {{{
|
||||||
|
|
||||||
'''
|
'''
|
||||||
@ -721,6 +945,7 @@ class IndexRecord(object): # {{{
|
|||||||
|
|
||||||
def __init__(self, record, index_header, cncx):
|
def __init__(self, record, index_header, cncx):
|
||||||
self.record = record
|
self.record = record
|
||||||
|
self.alltext = None
|
||||||
raw = self.record.raw
|
raw = self.record.raw
|
||||||
|
|
||||||
if raw[:4] != b'INDX':
|
if raw[:4] != b'INDX':
|
||||||
@ -760,17 +985,15 @@ class IndexRecord(object): # {{{
|
|||||||
next_off = len(indxt)
|
next_off = len(indxt)
|
||||||
index, consumed = decode_hex_number(indxt[off:])
|
index, consumed = decode_hex_number(indxt[off:])
|
||||||
entry_type = ord(indxt[off+consumed])
|
entry_type = ord(indxt[off+consumed])
|
||||||
d, flags = 1, 0
|
pos = off+consumed+1
|
||||||
if index_header.index_type == 6:
|
idxe = IndexEntry(index, entry_type,
|
||||||
flags = ord(indxt[off+consumed+d])
|
indxt[pos:next_off], cncx,
|
||||||
d += 1
|
index_header.tagx_entries,
|
||||||
pos = off+consumed+d
|
index_header.tagx_control_byte_count)
|
||||||
self.indices.append(IndexEntry(index, entry_type,
|
self.indices.append(idxe)
|
||||||
indxt[pos:next_off], cncx,
|
|
||||||
index_header.tagx_entries, flags=flags))
|
|
||||||
|
|
||||||
rest = indxt[pos+self.indices[-1].consumed:]
|
rest = indxt[pos+self.indices[-1].consumed:]
|
||||||
if rest.replace(b'\0', ''): # There can be padding null bytes
|
if rest.replace(b'\0', b''): # There can be padding null bytes
|
||||||
raise ValueError('Extra bytes after IDXT table: %r'%rest)
|
raise ValueError('Extra bytes after IDXT table: %r'%rest)
|
||||||
|
|
||||||
def get_parent(self, index):
|
def get_parent(self, index):
|
||||||
@ -800,7 +1023,11 @@ class IndexRecord(object): # {{{
|
|||||||
a('Index offsets: %r'%self.index_offsets)
|
a('Index offsets: %r'%self.index_offsets)
|
||||||
a('\nIndex Entries (%d entries):'%len(self.indices))
|
a('\nIndex Entries (%d entries):'%len(self.indices))
|
||||||
for entry in self.indices:
|
for entry in self.indices:
|
||||||
a(str(entry)+'\n')
|
offset = entry.offset
|
||||||
|
a(str(entry))
|
||||||
|
if offset is not None and self.alltext is not None:
|
||||||
|
a('\tHTML at offset: %r'%self.alltext[offset:offset+100])
|
||||||
|
a('')
|
||||||
|
|
||||||
return '\n'.join(ans)
|
return '\n'.join(ans)
|
||||||
|
|
||||||
@ -823,8 +1050,15 @@ class CNCX(object) : # {{{
|
|||||||
while pos < len(raw):
|
while pos < len(raw):
|
||||||
length, consumed = decint(raw[pos:])
|
length, consumed = decint(raw[pos:])
|
||||||
if length > 0:
|
if length > 0:
|
||||||
self.records[pos+record_offset] = raw[
|
try:
|
||||||
|
self.records[pos+record_offset] = raw[
|
||||||
pos+consumed:pos+consumed+length].decode(codec)
|
pos+consumed:pos+consumed+length].decode(codec)
|
||||||
|
except:
|
||||||
|
byts = raw[pos+consumed:pos+consumed+length]
|
||||||
|
r = format_bytes(byts)
|
||||||
|
print ('CNCX entry at offset %d has unknown format %s'%(
|
||||||
|
pos+record_offset, r))
|
||||||
|
self.records[pos+record_offset] = r
|
||||||
pos += consumed+length
|
pos += consumed+length
|
||||||
record_offset += 0x10000
|
record_offset += 0x10000
|
||||||
|
|
||||||
@ -846,6 +1080,7 @@ class TextRecord(object): # {{{
|
|||||||
self.trailing_data, self.raw = get_trailing_data(record.raw, extra_data_flags)
|
self.trailing_data, self.raw = get_trailing_data(record.raw, extra_data_flags)
|
||||||
raw_trailing_bytes = record.raw[len(self.raw):]
|
raw_trailing_bytes = record.raw[len(self.raw):]
|
||||||
self.raw = decompress(self.raw)
|
self.raw = decompress(self.raw)
|
||||||
|
|
||||||
if 0 in self.trailing_data:
|
if 0 in self.trailing_data:
|
||||||
self.trailing_data['multibyte_overlap'] = self.trailing_data.pop(0)
|
self.trailing_data['multibyte_overlap'] = self.trailing_data.pop(0)
|
||||||
if 1 in self.trailing_data:
|
if 1 in self.trailing_data:
|
||||||
@ -887,7 +1122,7 @@ class BinaryRecord(object): # {{{
|
|||||||
self.raw = record.raw
|
self.raw = record.raw
|
||||||
sig = self.raw[:4]
|
sig = self.raw[:4]
|
||||||
name = '%06d'%idx
|
name = '%06d'%idx
|
||||||
if sig in (b'FCIS', b'FLIS', b'SRCS'):
|
if sig in (b'FCIS', b'FLIS', b'SRCS', b'DATP'):
|
||||||
name += '-' + sig.decode('ascii')
|
name += '-' + sig.decode('ascii')
|
||||||
elif sig == b'\xe9\x8e\r\n':
|
elif sig == b'\xe9\x8e\r\n':
|
||||||
name += '-' + 'EOF'
|
name += '-' + 'EOF'
|
||||||
@ -1109,15 +1344,16 @@ class MOBIFile(object): # {{{
|
|||||||
self.records.append(Record(section(i), self.record_headers[i]))
|
self.records.append(Record(section(i), self.record_headers[i]))
|
||||||
|
|
||||||
self.mobi_header = MOBIHeader(self.records[0])
|
self.mobi_header = MOBIHeader(self.records[0])
|
||||||
|
self.huffman_record_nums = []
|
||||||
|
|
||||||
if 'huff' in self.mobi_header.compression.lower():
|
if 'huff' in self.mobi_header.compression.lower():
|
||||||
huffrecs = [r.raw for r in
|
self.huffman_record_nums = list(xrange(self.mobi_header.huffman_record_offset,
|
||||||
xrange(self.mobi_header.huffman_record_offset,
|
|
||||||
self.mobi_header.huffman_record_offset +
|
self.mobi_header.huffman_record_offset +
|
||||||
self.mobi_header.huffman_record_count)]
|
self.mobi_header.huffman_record_count))
|
||||||
|
huffrecs = [self.records[r].raw for r in self.huffman_record_nums]
|
||||||
from calibre.ebooks.mobi.huffcdic import HuffReader
|
from calibre.ebooks.mobi.huffcdic import HuffReader
|
||||||
huffs = HuffReader(huffrecs)
|
huffs = HuffReader(huffrecs)
|
||||||
decompress = huffs.decompress
|
decompress = lambda x: huffs.decompress([x])
|
||||||
elif 'palmdoc' in self.mobi_header.compression.lower():
|
elif 'palmdoc' in self.mobi_header.compression.lower():
|
||||||
from calibre.ebooks.compression.palmdoc import decompress_doc
|
from calibre.ebooks.compression.palmdoc import decompress_doc
|
||||||
decompress = decompress_doc
|
decompress = decompress_doc
|
||||||
@ -1136,6 +1372,14 @@ class MOBIFile(object): # {{{
|
|||||||
self.index_header, self.cncx)
|
self.index_header, self.cncx)
|
||||||
self.indexing_record_nums = set(xrange(pir,
|
self.indexing_record_nums = set(xrange(pir,
|
||||||
pir+2+self.index_header.num_of_cncx_blocks))
|
pir+2+self.index_header.num_of_cncx_blocks))
|
||||||
|
self.secondary_index_record = self.secondary_index_record = None
|
||||||
|
sir = self.mobi_header.secondary_index_record
|
||||||
|
if sir != 0xffffffff:
|
||||||
|
self.secondary_index_header = SecondaryIndexHeader(self.records[sir])
|
||||||
|
self.indexing_record_nums.add(sir)
|
||||||
|
self.secondary_index_record = SecondaryIndexRecord(
|
||||||
|
self.records[sir+1], self.secondary_index_header, self.cncx)
|
||||||
|
self.indexing_record_nums.add(sir+1)
|
||||||
|
|
||||||
|
|
||||||
ntr = self.mobi_header.number_of_text_records
|
ntr = self.mobi_header.number_of_text_records
|
||||||
@ -1148,7 +1392,7 @@ class MOBIFile(object): # {{{
|
|||||||
min(len(self.records), ntr+1))]
|
min(len(self.records), ntr+1))]
|
||||||
self.image_records, self.binary_records = [], []
|
self.image_records, self.binary_records = [], []
|
||||||
for i in xrange(fntbr, len(self.records)):
|
for i in xrange(fntbr, len(self.records)):
|
||||||
if i in self.indexing_record_nums:
|
if i in self.indexing_record_nums or i in self.huffman_record_nums:
|
||||||
continue
|
continue
|
||||||
r = self.records[i]
|
r = self.records[i]
|
||||||
fmt = None
|
fmt = None
|
||||||
@ -1178,7 +1422,7 @@ class MOBIFile(object): # {{{
|
|||||||
print (str(self.mobi_header).encode('utf-8'), file=f)
|
print (str(self.mobi_header).encode('utf-8'), file=f)
|
||||||
# }}}
|
# }}}
|
||||||
|
|
||||||
def inspect_mobi(path_or_stream, prefix='decompiled'):
|
def inspect_mobi(path_or_stream, prefix='decompiled'): # {{{
|
||||||
stream = (path_or_stream if hasattr(path_or_stream, 'read') else
|
stream = (path_or_stream if hasattr(path_or_stream, 'read') else
|
||||||
open(path_or_stream, 'rb'))
|
open(path_or_stream, 'rb'))
|
||||||
f = MOBIFile(stream)
|
f = MOBIFile(stream)
|
||||||
@ -1190,10 +1434,31 @@ def inspect_mobi(path_or_stream, prefix='decompiled'):
|
|||||||
os.mkdir(ddir)
|
os.mkdir(ddir)
|
||||||
with open(os.path.join(ddir, 'header.txt'), 'wb') as out:
|
with open(os.path.join(ddir, 'header.txt'), 'wb') as out:
|
||||||
f.print_header(f=out)
|
f.print_header(f=out)
|
||||||
|
|
||||||
|
alltext = os.path.join(ddir, 'text.html')
|
||||||
|
with open(alltext, 'wb') as of:
|
||||||
|
alltext = b''
|
||||||
|
for rec in f.text_records:
|
||||||
|
of.write(rec.raw)
|
||||||
|
alltext += rec.raw
|
||||||
|
of.seek(0)
|
||||||
|
root = html.fromstring(alltext.decode('utf-8'))
|
||||||
|
with open(os.path.join(ddir, 'pretty.html'), 'wb') as of:
|
||||||
|
of.write(html.tostring(root, pretty_print=True, encoding='utf-8',
|
||||||
|
include_meta_content_type=True))
|
||||||
|
|
||||||
|
|
||||||
if f.index_header is not None:
|
if f.index_header is not None:
|
||||||
|
f.index_record.alltext = alltext
|
||||||
with open(os.path.join(ddir, 'index.txt'), 'wb') as out:
|
with open(os.path.join(ddir, 'index.txt'), 'wb') as out:
|
||||||
print(str(f.index_header), file=out)
|
print(str(f.index_header), file=out)
|
||||||
print('\n\n', file=out)
|
print('\n\n', file=out)
|
||||||
|
if f.secondary_index_header is not None:
|
||||||
|
print(str(f.secondary_index_header).encode('utf-8'), file=out)
|
||||||
|
print('\n\n', file=out)
|
||||||
|
if f.secondary_index_record is not None:
|
||||||
|
print(str(f.secondary_index_record).encode('utf-8'), file=out)
|
||||||
|
print('\n\n', file=out)
|
||||||
print(str(f.cncx).encode('utf-8'), file=out)
|
print(str(f.cncx).encode('utf-8'), file=out)
|
||||||
print('\n\n', file=out)
|
print('\n\n', file=out)
|
||||||
print(str(f.index_record), file=out)
|
print(str(f.index_record), file=out)
|
||||||
@ -1208,8 +1473,11 @@ def inspect_mobi(path_or_stream, prefix='decompiled'):
|
|||||||
for rec in getattr(f, attr):
|
for rec in getattr(f, attr):
|
||||||
rec.dump(tdir)
|
rec.dump(tdir)
|
||||||
|
|
||||||
|
|
||||||
print ('Debug data saved to:', ddir)
|
print ('Debug data saved to:', ddir)
|
||||||
|
|
||||||
|
# }}}
|
||||||
|
|
||||||
def main():
|
def main():
|
||||||
inspect_mobi(sys.argv[1])
|
inspect_mobi(sys.argv[1])
|
||||||
|
|
||||||
|
@ -7,7 +7,7 @@ __license__ = 'GPL v3'
|
|||||||
__copyright__ = '2011, Kovid Goyal <kovid@kovidgoyal.net>'
|
__copyright__ = '2011, Kovid Goyal <kovid@kovidgoyal.net>'
|
||||||
__docformat__ = 'restructuredtext en'
|
__docformat__ = 'restructuredtext en'
|
||||||
|
|
||||||
import os, subprocess, shutil
|
import os, subprocess, shutil, tempfile
|
||||||
|
|
||||||
from lxml import etree
|
from lxml import etree
|
||||||
|
|
||||||
@ -70,10 +70,12 @@ def kindlegen(oeb, opts, input_plugin, output_path):
|
|||||||
opf = [x for x in os.listdir(tdir) if x.endswith('.opf')][0]
|
opf = [x for x in os.listdir(tdir) if x.endswith('.opf')][0]
|
||||||
refactor_opf(os.path.join(tdir, opf), is_periodical, oeb.toc)
|
refactor_opf(os.path.join(tdir, opf), is_periodical, oeb.toc)
|
||||||
try:
|
try:
|
||||||
if os.path.exists('/tmp/kindlegen'):
|
td = tempfile.gettempdir()
|
||||||
shutil.rmtree('/tmp/kindlegen')
|
kd = os.path.join(td, 'kindlegen')
|
||||||
shutil.copytree(tdir, '/tmp/kindlegen')
|
if os.path.exists(kd):
|
||||||
oeb.log('kindlegen intermediate output stored in: /tmp/kindlegen')
|
shutil.rmtree(kd)
|
||||||
|
shutil.copytree(tdir, kd)
|
||||||
|
oeb.log('kindlegen intermediate output stored in: %s'%kd)
|
||||||
except:
|
except:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
@ -50,6 +50,13 @@ class MOBIOutput(OutputFormatPlugin):
|
|||||||
help=_('When adding the Table of Contents to the book, add it at the start of the '
|
help=_('When adding the Table of Contents to the book, add it at the start of the '
|
||||||
'book instead of the end. Not recommended.')
|
'book instead of the end. Not recommended.')
|
||||||
),
|
),
|
||||||
|
OptionRecommendation(name='mobi_navpoints_only_deepest',
|
||||||
|
recommended_value=False,
|
||||||
|
help=_('When adding navpoints for the chapter-to-chapter'
|
||||||
|
' navigation on the kindle, use only the lowest level '
|
||||||
|
'of items in the TOC, instead of items at every level.')
|
||||||
|
),
|
||||||
|
|
||||||
OptionRecommendation(name='kindlegen',
|
OptionRecommendation(name='kindlegen',
|
||||||
recommended_value=False,
|
recommended_value=False,
|
||||||
help=('Use kindlegen (must be in your PATH) to generate the'
|
help=('Use kindlegen (must be in your PATH) to generate the'
|
||||||
|
@ -1231,6 +1231,9 @@ class MobiWriter(object):
|
|||||||
self._oeb.logger.info(' Compressing markup content...')
|
self._oeb.logger.info(' Compressing markup content...')
|
||||||
data, overlap = self._read_text_record(text)
|
data, overlap = self._read_text_record(text)
|
||||||
|
|
||||||
|
if not self.opts.mobi_periodical:
|
||||||
|
self._flatten_toc()
|
||||||
|
|
||||||
# Evaluate toc for conformance
|
# Evaluate toc for conformance
|
||||||
if self.opts.mobi_periodical :
|
if self.opts.mobi_periodical :
|
||||||
self._oeb.logger.info(' MOBI periodical specified, evaluating TOC for periodical conformance ...')
|
self._oeb.logger.info(' MOBI periodical specified, evaluating TOC for periodical conformance ...')
|
||||||
@ -1631,7 +1634,7 @@ class MobiWriter(object):
|
|||||||
now = int(time.time())
|
now = int(time.time())
|
||||||
nrecords = len(self._records)
|
nrecords = len(self._records)
|
||||||
self._write(title, pack('>HHIIIIII', 0, 0, now, now, 0, 0, 0, 0),
|
self._write(title, pack('>HHIIIIII', 0, 0, now, now, 0, 0, 0, 0),
|
||||||
'BOOK', 'MOBI', pack('>IIH', nrecords, 0, nrecords))
|
'BOOK', 'MOBI', pack('>IIH', (2*nrecords)-1, 0, nrecords))
|
||||||
offset = self._tell() + (8 * nrecords) + 2
|
offset = self._tell() + (8 * nrecords) + 2
|
||||||
for i, record in enumerate(self._records):
|
for i, record in enumerate(self._records):
|
||||||
self._write(pack('>I', offset), '\0', pack('>I', 2*i)[1:])
|
self._write(pack('>I', offset), '\0', pack('>I', 2*i)[1:])
|
||||||
@ -1697,6 +1700,32 @@ class MobiWriter(object):
|
|||||||
|
|
||||||
# Index {{{
|
# Index {{{
|
||||||
|
|
||||||
|
def _flatten_toc(self):
|
||||||
|
'''
|
||||||
|
Flatten and re-order entries in TOC so that chapter to chapter jumping
|
||||||
|
never fails on the Kindle.
|
||||||
|
'''
|
||||||
|
from calibre.ebooks.oeb.base import TOC
|
||||||
|
items = list(self._oeb.toc.iterdescendants())
|
||||||
|
if self.opts.mobi_navpoints_only_deepest:
|
||||||
|
items = [i for i in items if i.depth == 1]
|
||||||
|
offsets = {i:self._id_offsets.get(i.href, -1) for i in items if i.href}
|
||||||
|
items = [i for i in items if offsets[i] > -1]
|
||||||
|
items.sort(key=lambda i:offsets[i])
|
||||||
|
filt = []
|
||||||
|
seen = set()
|
||||||
|
for i in items:
|
||||||
|
off = offsets[i]
|
||||||
|
if off in seen: continue
|
||||||
|
seen.add(off)
|
||||||
|
filt.append(i)
|
||||||
|
items = filt
|
||||||
|
newtoc = TOC()
|
||||||
|
for c, i in enumerate(items):
|
||||||
|
newtoc.add(i.title, i.href, play_order=c+1, id=str(c),
|
||||||
|
klass='chapter')
|
||||||
|
self._oeb.toc = newtoc
|
||||||
|
|
||||||
def _generate_index(self):
|
def _generate_index(self):
|
||||||
self._oeb.log('Generating INDX ...')
|
self._oeb.log('Generating INDX ...')
|
||||||
self._primary_index_record = None
|
self._primary_index_record = None
|
||||||
|
@ -34,6 +34,8 @@ EXTH_CODES = {
|
|||||||
'rights': 109,
|
'rights': 109,
|
||||||
'type': 111,
|
'type': 111,
|
||||||
'source': 112,
|
'source': 112,
|
||||||
|
'versionnumber': 114,
|
||||||
|
'lastupdatetime': 502,
|
||||||
'title': 503,
|
'title': 503,
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -201,6 +203,7 @@ class MobiWriter(object):
|
|||||||
self.text_length = len(text)
|
self.text_length = len(text)
|
||||||
text = StringIO(text)
|
text = StringIO(text)
|
||||||
nrecords = 0
|
nrecords = 0
|
||||||
|
records_size = 0
|
||||||
|
|
||||||
if self.compression != UNCOMPRESSED:
|
if self.compression != UNCOMPRESSED:
|
||||||
self.oeb.logger.info(' Compressing markup content...')
|
self.oeb.logger.info(' Compressing markup content...')
|
||||||
@ -214,9 +217,15 @@ class MobiWriter(object):
|
|||||||
data += pack(b'>B', len(overlap))
|
data += pack(b'>B', len(overlap))
|
||||||
|
|
||||||
self.records.append(data)
|
self.records.append(data)
|
||||||
|
records_size += len(data)
|
||||||
nrecords += 1
|
nrecords += 1
|
||||||
|
|
||||||
self.last_text_record_idx = nrecords
|
self.last_text_record_idx = nrecords
|
||||||
|
self.first_non_text_record_idx = nrecords + 1
|
||||||
|
# Pad so that the next records starts at a 4 byte boundary
|
||||||
|
if records_size % 4 != 0:
|
||||||
|
self.records.append(b'\x00'*(records_size % 4))
|
||||||
|
self.first_non_text_record_idx += 1
|
||||||
|
|
||||||
def read_text_record(self, text):
|
def read_text_record(self, text):
|
||||||
'''
|
'''
|
||||||
@ -322,7 +331,7 @@ class MobiWriter(object):
|
|||||||
if self.indexer.is_flat_periodical:
|
if self.indexer.is_flat_periodical:
|
||||||
bt = 0x102
|
bt = 0x102
|
||||||
elif self.indexer.is_periodical:
|
elif self.indexer.is_periodical:
|
||||||
bt = 0x103
|
bt = 0x101
|
||||||
|
|
||||||
record0.write(pack(b'>IIIII',
|
record0.write(pack(b'>IIIII',
|
||||||
0xe8, bt, 65001, uid, 6))
|
0xe8, bt, 65001, uid, 6))
|
||||||
@ -338,7 +347,7 @@ class MobiWriter(object):
|
|||||||
|
|
||||||
# 0x40 - 0x43 : Offset of first non-text record
|
# 0x40 - 0x43 : Offset of first non-text record
|
||||||
record0.write(pack(b'>I',
|
record0.write(pack(b'>I',
|
||||||
self.last_text_record_idx + 1))
|
self.first_non_text_record_idx))
|
||||||
|
|
||||||
# 0x44 - 0x4b : title offset, title length
|
# 0x44 - 0x4b : title offset, title length
|
||||||
record0.write(pack(b'>II',
|
record0.write(pack(b'>II',
|
||||||
@ -493,10 +502,12 @@ class MobiWriter(object):
|
|||||||
|
|
||||||
# Write cdetype
|
# Write cdetype
|
||||||
if self.is_periodical:
|
if self.is_periodical:
|
||||||
|
data = b'NWPR'
|
||||||
|
else:
|
||||||
data = b'EBOK'
|
data = b'EBOK'
|
||||||
exth.write(pack(b'>II', 501, len(data)+8))
|
exth.write(pack(b'>II', 501, len(data)+8))
|
||||||
exth.write(data)
|
exth.write(data)
|
||||||
nrecs += 1
|
nrecs += 1
|
||||||
|
|
||||||
# Add a publication date entry
|
# Add a publication date entry
|
||||||
if oeb.metadata['date']:
|
if oeb.metadata['date']:
|
||||||
@ -504,18 +515,28 @@ class MobiWriter(object):
|
|||||||
elif oeb.metadata['timestamp']:
|
elif oeb.metadata['timestamp']:
|
||||||
datestr = str(oeb.metadata['timestamp'][0])
|
datestr = str(oeb.metadata['timestamp'][0])
|
||||||
|
|
||||||
if datestr is not None:
|
if datestr is None:
|
||||||
datestr = bytes(datestr)
|
raise ValueError("missing date or timestamp")
|
||||||
exth.write(pack(b'>II', EXTH_CODES['pubdate'], len(datestr) + 8))
|
|
||||||
|
datestr = bytes(datestr)
|
||||||
|
exth.write(pack(b'>II', EXTH_CODES['pubdate'], len(datestr) + 8))
|
||||||
|
exth.write(datestr)
|
||||||
|
nrecs += 1
|
||||||
|
if self.is_periodical:
|
||||||
|
exth.write(pack(b'>II', EXTH_CODES['lastupdatetime'], len(datestr) + 8))
|
||||||
exth.write(datestr)
|
exth.write(datestr)
|
||||||
nrecs += 1
|
nrecs += 1
|
||||||
else:
|
exth.write(pack(b'>III', EXTH_CODES['versionnumber'], 12, 7))
|
||||||
raise NotImplementedError("missing date or timestamp needed for mobi_periodical")
|
nrecs += 1
|
||||||
|
|
||||||
# Write the same creator info as kindlegen 1.2
|
if self.is_periodical:
|
||||||
for code, val in [(204, 201), (205, 1), (206, 2), (207, 33307)]:
|
# Pretend to be amazon's super secret periodical generator
|
||||||
exth.write(pack(b'>II', code, 12))
|
vals = {204:201, 205:2, 206:0, 207:101}
|
||||||
exth.write(pack(b'>I', val))
|
else:
|
||||||
|
# Pretend to be kindlegen 1.2
|
||||||
|
vals = {204:201, 205:1, 206:2, 207:33307}
|
||||||
|
for code, val in vals:
|
||||||
|
exth.write(pack(b'>III', code, 12, val))
|
||||||
nrecs += 1
|
nrecs += 1
|
||||||
|
|
||||||
if (oeb.metadata.cover and
|
if (oeb.metadata.cover and
|
||||||
@ -550,7 +571,7 @@ class MobiWriter(object):
|
|||||||
now = int(time.time())
|
now = int(time.time())
|
||||||
nrecords = len(self.records)
|
nrecords = len(self.records)
|
||||||
self.write(title, pack(b'>HHIIIIII', 0, 0, now, now, 0, 0, 0, 0),
|
self.write(title, pack(b'>HHIIIIII', 0, 0, now, now, 0, 0, 0, 0),
|
||||||
b'BOOK', b'MOBI', pack(b'>IIH', nrecords, 0, nrecords))
|
b'BOOK', b'MOBI', pack(b'>IIH', (2*nrecords)-1, 0, nrecords))
|
||||||
offset = self.tell() + (8 * nrecords) + 2
|
offset = self.tell() + (8 * nrecords) + 2
|
||||||
for i, record in enumerate(self.records):
|
for i, record in enumerate(self.records):
|
||||||
self.write(pack(b'>I', offset), b'\0', pack(b'>I', 2*i)[1:])
|
self.write(pack(b'>I', offset), b'\0', pack(b'>I', 2*i)[1:])
|
||||||
|
@ -53,6 +53,50 @@ class Serializer(object):
|
|||||||
# become uncrossable breaks in the MOBI
|
# become uncrossable breaks in the MOBI
|
||||||
self.breaks = []
|
self.breaks = []
|
||||||
|
|
||||||
|
self.find_blocks()
|
||||||
|
|
||||||
|
def find_blocks(self):
|
||||||
|
'''
|
||||||
|
Mark every item in the spine if it is the start/end of a
|
||||||
|
section/article, so that it can be wrapped in divs appropriately.
|
||||||
|
'''
|
||||||
|
for item in self.oeb.spine:
|
||||||
|
item.is_section_start = item.is_section_end = False
|
||||||
|
item.is_article_start = item.is_article_end = False
|
||||||
|
|
||||||
|
def spine_item(tocitem):
|
||||||
|
href = urldefrag(tocitem.href)[0]
|
||||||
|
for item in self.oeb.spine:
|
||||||
|
if item.href == href:
|
||||||
|
return item
|
||||||
|
|
||||||
|
for item in self.oeb.toc.iterdescendants():
|
||||||
|
if item.klass == 'section':
|
||||||
|
articles = list(item)
|
||||||
|
if not articles: continue
|
||||||
|
spine_item(item).is_section_start = True
|
||||||
|
for i, article in enumerate(articles):
|
||||||
|
si = spine_item(article)
|
||||||
|
si.is_article_start = True
|
||||||
|
|
||||||
|
items = list(self.oeb.spine)
|
||||||
|
in_sec = in_art = False
|
||||||
|
for i, item in enumerate(items):
|
||||||
|
try:
|
||||||
|
prev_item = items[i-1]
|
||||||
|
except:
|
||||||
|
prev_item = None
|
||||||
|
if in_art and item.is_article_start == True:
|
||||||
|
prev_item.is_article_end = True
|
||||||
|
in_art = False
|
||||||
|
if in_sec and item.is_section_start == True:
|
||||||
|
prev_item.is_section_end = True
|
||||||
|
in_sec = False
|
||||||
|
if item.is_section_start: in_sec = True
|
||||||
|
if item.is_article_start: in_art = True
|
||||||
|
|
||||||
|
item.is_section_end = item.is_article_end = True
|
||||||
|
|
||||||
def __call__(self):
|
def __call__(self):
|
||||||
'''
|
'''
|
||||||
Return the document serialized as a single UTF-8 encoded bytestring.
|
Return the document serialized as a single UTF-8 encoded bytestring.
|
||||||
@ -155,15 +199,21 @@ class Serializer(object):
|
|||||||
if not item.linear:
|
if not item.linear:
|
||||||
self.breaks.append(buf.tell() - 1)
|
self.breaks.append(buf.tell() - 1)
|
||||||
self.id_offsets[urlnormalize(item.href)] = buf.tell()
|
self.id_offsets[urlnormalize(item.href)] = buf.tell()
|
||||||
# Kindle periodical articles are contained in a <div> tag
|
if item.is_section_start:
|
||||||
buf.write(b'<div>')
|
buf.write(b'<div>')
|
||||||
|
if item.is_article_start:
|
||||||
|
buf.write(b'<div>')
|
||||||
for elem in item.data.find(XHTML('body')):
|
for elem in item.data.find(XHTML('body')):
|
||||||
self.serialize_elem(elem, item)
|
self.serialize_elem(elem, item)
|
||||||
# Kindle periodical article end marker
|
if item.is_article_end:
|
||||||
buf.write(b'<div></div>')
|
# Kindle periodical article end marker
|
||||||
|
buf.write(b'<div></div>')
|
||||||
if self.write_page_breaks_after_item:
|
if self.write_page_breaks_after_item:
|
||||||
buf.write(b'<mbp:pagebreak/>')
|
buf.write(b'<mbp:pagebreak/>')
|
||||||
buf.write(b'</div>')
|
if item.is_article_end:
|
||||||
|
buf.write(b'</div>')
|
||||||
|
if item.is_section_end:
|
||||||
|
buf.write(b'</div>')
|
||||||
self.anchor_offset = None
|
self.anchor_offset = None
|
||||||
|
|
||||||
def serialize_elem(self, elem, item, nsrmap=NSRMAP):
|
def serialize_elem(self, elem, item, nsrmap=NSRMAP):
|
||||||
|
@ -40,7 +40,7 @@ if isosx:
|
|||||||
gprefs.defaults['action-layout-toolbar-device'] = (
|
gprefs.defaults['action-layout-toolbar-device'] = (
|
||||||
'Add Books', 'Edit Metadata', None, 'Convert Books', 'View',
|
'Add Books', 'Edit Metadata', None, 'Convert Books', 'View',
|
||||||
'Send To Device', None, None, 'Location Manager', None, None,
|
'Send To Device', None, None, 'Location Manager', None, None,
|
||||||
'Fetch News', 'Save To Disk', 'Connect Share', None,
|
'Fetch News', 'Store', 'Save To Disk', 'Connect Share', None,
|
||||||
'Remove Books',
|
'Remove Books',
|
||||||
)
|
)
|
||||||
else:
|
else:
|
||||||
@ -55,7 +55,7 @@ else:
|
|||||||
gprefs.defaults['action-layout-toolbar-device'] = (
|
gprefs.defaults['action-layout-toolbar-device'] = (
|
||||||
'Add Books', 'Edit Metadata', None, 'Convert Books', 'View',
|
'Add Books', 'Edit Metadata', None, 'Convert Books', 'View',
|
||||||
'Send To Device', None, None, 'Location Manager', None, None,
|
'Send To Device', None, None, 'Location Manager', None, None,
|
||||||
'Fetch News', 'Save To Disk', 'Connect Share', None,
|
'Fetch News', 'Save To Disk', 'Store', 'Connect Share', None,
|
||||||
'Remove Books', None, 'Help', 'Preferences',
|
'Remove Books', None, 'Help', 'Preferences',
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -25,7 +25,8 @@ class PluginWidget(Widget, Ui_Form):
|
|||||||
Widget.__init__(self, parent,
|
Widget.__init__(self, parent,
|
||||||
['prefer_author_sort', 'rescale_images', 'toc_title',
|
['prefer_author_sort', 'rescale_images', 'toc_title',
|
||||||
'mobi_ignore_margins', 'mobi_toc_at_start',
|
'mobi_ignore_margins', 'mobi_toc_at_start',
|
||||||
'dont_compress', 'no_inline_toc', 'masthead_font','personal_doc']
|
'dont_compress', 'no_inline_toc',
|
||||||
|
'masthead_font','personal_doc', 'mobi_navpoints_only_deepest']
|
||||||
)
|
)
|
||||||
from calibre.utils.fonts import fontconfig
|
from calibre.utils.fonts import fontconfig
|
||||||
self.db, self.book_id = db, book_id
|
self.db, self.book_id = db, book_id
|
||||||
|
@ -55,7 +55,7 @@
|
|||||||
</property>
|
</property>
|
||||||
</widget>
|
</widget>
|
||||||
</item>
|
</item>
|
||||||
<item row="8" column="0" colspan="2">
|
<item row="9" column="0" colspan="2">
|
||||||
<widget class="QGroupBox" name="groupBox">
|
<widget class="QGroupBox" name="groupBox">
|
||||||
<property name="title">
|
<property name="title">
|
||||||
<string>Kindle options</string>
|
<string>Kindle options</string>
|
||||||
@ -101,7 +101,7 @@
|
|||||||
</layout>
|
</layout>
|
||||||
</widget>
|
</widget>
|
||||||
</item>
|
</item>
|
||||||
<item row="9" column="0">
|
<item row="10" column="0">
|
||||||
<spacer name="verticalSpacer_2">
|
<spacer name="verticalSpacer_2">
|
||||||
<property name="orientation">
|
<property name="orientation">
|
||||||
<enum>Qt::Vertical</enum>
|
<enum>Qt::Vertical</enum>
|
||||||
@ -128,6 +128,13 @@
|
|||||||
</property>
|
</property>
|
||||||
</widget>
|
</widget>
|
||||||
</item>
|
</item>
|
||||||
|
<item row="7" column="0" colspan="2">
|
||||||
|
<widget class="QCheckBox" name="opt_mobi_navpoints_only_deepest">
|
||||||
|
<property name="text">
|
||||||
|
<string>Use only &lowest level of items in the TOC for chapter-to-chapter navigation</string>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
</layout>
|
</layout>
|
||||||
</widget>
|
</widget>
|
||||||
<resources/>
|
<resources/>
|
||||||
|
@ -6,7 +6,7 @@ __license__ = 'GPL 3'
|
|||||||
__copyright__ = '2011, John Schember <john@nachtimwald.com>'
|
__copyright__ = '2011, John Schember <john@nachtimwald.com>'
|
||||||
__docformat__ = 'restructuredtext en'
|
__docformat__ = 'restructuredtext en'
|
||||||
|
|
||||||
import re, urllib
|
import urllib
|
||||||
from contextlib import closing
|
from contextlib import closing
|
||||||
|
|
||||||
from lxml import html
|
from lxml import html
|
||||||
|
@ -6,7 +6,8 @@ __license__ = 'GPL 3'
|
|||||||
__copyright__ = '2011, Alex Stanev <alex@stanev.org>'
|
__copyright__ = '2011, Alex Stanev <alex@stanev.org>'
|
||||||
__docformat__ = 'restructuredtext en'
|
__docformat__ = 'restructuredtext en'
|
||||||
|
|
||||||
import urllib
|
import re
|
||||||
|
import urllib2
|
||||||
from contextlib import closing
|
from contextlib import closing
|
||||||
|
|
||||||
from lxml import html
|
from lxml import html
|
||||||
@ -39,54 +40,24 @@ class ChitankaStore(BasicStoreConfig, StorePlugin):
|
|||||||
d.exec_()
|
d.exec_()
|
||||||
|
|
||||||
def search(self, query, max_results=10, timeout=60):
|
def search(self, query, max_results=10, timeout=60):
|
||||||
|
# check for cyrilic symbols before performing search
|
||||||
|
uquery = unicode(query.strip(), 'utf-8')
|
||||||
|
reObj = re.search(u'^[а-яА-Я\\d]{4,}[а-яА-Я\\d\\s]*$', uquery)
|
||||||
|
if not reObj:
|
||||||
|
return
|
||||||
|
|
||||||
base_url = 'http://chitanka.info'
|
base_url = 'http://chitanka.info'
|
||||||
url = base_url + '/search?q=' + urllib.quote(query)
|
url = base_url + '/search?q=' + urllib2.quote(query)
|
||||||
counter = max_results
|
counter = max_results
|
||||||
|
|
||||||
# search for book title
|
# search for book title
|
||||||
br = browser()
|
br = browser()
|
||||||
with closing(br.open(url, timeout=timeout)) as f:
|
try:
|
||||||
f = unicode(f.read(), 'utf-8')
|
with closing(br.open(url, timeout=timeout)) as f:
|
||||||
doc = html.fromstring(f)
|
|
||||||
|
|
||||||
for data in doc.xpath('//ul[@class="superlist booklist"]/li'):
|
|
||||||
if counter <= 0:
|
|
||||||
break
|
|
||||||
|
|
||||||
id = ''.join(data.xpath('.//a[@class="booklink"]/@href')).strip()
|
|
||||||
if not id:
|
|
||||||
continue
|
|
||||||
|
|
||||||
counter -= 1
|
|
||||||
|
|
||||||
s = SearchResult()
|
|
||||||
s.cover_url = ''.join(data.xpath('.//a[@class="booklink"]/img/@src')).strip()
|
|
||||||
s.title = ''.join(data.xpath('.//a[@class="booklink"]/i/text()')).strip()
|
|
||||||
s.author = ''.join(data.xpath('.//span[@class="bookauthor"]/a/text()')).strip()
|
|
||||||
s.detail_item = id
|
|
||||||
s.drm = SearchResult.DRM_UNLOCKED
|
|
||||||
s.downloads['FB2'] = base_url + ''.join(data.xpath('.//a[@class="dl dl-fb2"]/@href')).strip().replace('.zip', '')
|
|
||||||
s.downloads['EPUB'] = base_url + ''.join(data.xpath('.//a[@class="dl dl-epub"]/@href')).strip().replace('.zip', '')
|
|
||||||
s.downloads['TXT'] = base_url + ''.join(data.xpath('.//a[@class="dl dl-txt"]/@href')).strip().replace('.zip', '')
|
|
||||||
s.formats = 'FB2, EPUB, TXT, SFB'
|
|
||||||
yield s
|
|
||||||
|
|
||||||
# search for author names
|
|
||||||
for data in doc.xpath('//ul[@class="superlist"][1]/li'):
|
|
||||||
author_url = ''.join(data.xpath('.//a[contains(@href,"/person/")]/@href'))
|
|
||||||
if counter <= 0:
|
|
||||||
break
|
|
||||||
|
|
||||||
br2 = browser()
|
|
||||||
with closing(br2.open(base_url + author_url, timeout=timeout)) as f:
|
|
||||||
if counter <= 0:
|
|
||||||
break
|
|
||||||
f = unicode(f.read(), 'utf-8')
|
f = unicode(f.read(), 'utf-8')
|
||||||
doc2 = html.fromstring(f)
|
doc = html.fromstring(f)
|
||||||
|
|
||||||
# search for book title
|
for data in doc.xpath('//ul[@class="superlist booklist"]/li'):
|
||||||
for data in doc2.xpath('//ul[@class="superlist booklist"]/li'):
|
|
||||||
if counter <= 0:
|
if counter <= 0:
|
||||||
break
|
break
|
||||||
|
|
||||||
@ -107,3 +78,51 @@ class ChitankaStore(BasicStoreConfig, StorePlugin):
|
|||||||
s.downloads['TXT'] = base_url + ''.join(data.xpath('.//a[@class="dl dl-txt"]/@href')).strip().replace('.zip', '')
|
s.downloads['TXT'] = base_url + ''.join(data.xpath('.//a[@class="dl dl-txt"]/@href')).strip().replace('.zip', '')
|
||||||
s.formats = 'FB2, EPUB, TXT, SFB'
|
s.formats = 'FB2, EPUB, TXT, SFB'
|
||||||
yield s
|
yield s
|
||||||
|
except urllib2.HTTPError, e:
|
||||||
|
if e.code == 404:
|
||||||
|
return
|
||||||
|
else:
|
||||||
|
raise
|
||||||
|
|
||||||
|
# search for author names
|
||||||
|
for data in doc.xpath('//ul[@class="superlist"][1]/li/dl/dt'):
|
||||||
|
author_url = ''.join(data.xpath('.//a[contains(@href,"/person/")]/@href'))
|
||||||
|
if author_url == '':
|
||||||
|
continue
|
||||||
|
if counter <= 0:
|
||||||
|
break
|
||||||
|
|
||||||
|
br2 = browser()
|
||||||
|
with closing(br2.open(base_url + author_url, timeout=timeout)) as f:
|
||||||
|
if counter <= 0:
|
||||||
|
break
|
||||||
|
f = unicode(f.read(), 'utf-8')
|
||||||
|
doc2 = html.fromstring(f)
|
||||||
|
|
||||||
|
# search for book title
|
||||||
|
for data in doc2.xpath('//ul[@class="superlist booklist"]/li'):
|
||||||
|
if counter <= 0:
|
||||||
|
break
|
||||||
|
|
||||||
|
id = ''.join(data.xpath('.//a[@class="booklink"]/@href')).strip()
|
||||||
|
if not id:
|
||||||
|
continue
|
||||||
|
|
||||||
|
title = ''.join(data.xpath('.//a[@class="booklink"]/i/text()')).strip()
|
||||||
|
author = ''.join(data.xpath('.//span[@class="bookauthor"]/a/text()')).strip()
|
||||||
|
if title.lower().find(query.lower()) == -1 and author.lower().find(query.lower()) == -1:
|
||||||
|
continue
|
||||||
|
|
||||||
|
counter -= 1
|
||||||
|
|
||||||
|
s = SearchResult()
|
||||||
|
s.cover_url = ''.join(data.xpath('.//a[@class="booklink"]/img/@src')).strip()
|
||||||
|
s.title = title
|
||||||
|
s.author = author
|
||||||
|
s.detail_item = id
|
||||||
|
s.drm = SearchResult.DRM_UNLOCKED
|
||||||
|
s.downloads['FB2'] = base_url + ''.join(data.xpath('.//a[@class="dl dl-fb2"]/@href')).strip().replace('.zip', '')
|
||||||
|
s.downloads['EPUB'] = base_url + ''.join(data.xpath('.//a[@class="dl dl-epub"]/@href')).strip().replace('.zip', '')
|
||||||
|
s.downloads['TXT'] = base_url + ''.join(data.xpath('.//a[@class="dl dl-txt"]/@href')).strip().replace('.zip', '')
|
||||||
|
s.formats = 'FB2, EPUB, TXT, SFB'
|
||||||
|
yield s
|
||||||
|
@ -6,6 +6,7 @@ __license__ = 'GPL 3'
|
|||||||
__copyright__ = '2011, Alex Stanev <alex@stanev.org>'
|
__copyright__ = '2011, Alex Stanev <alex@stanev.org>'
|
||||||
__docformat__ = 'restructuredtext en'
|
__docformat__ = 'restructuredtext en'
|
||||||
|
|
||||||
|
import re
|
||||||
import random
|
import random
|
||||||
import urllib2
|
import urllib2
|
||||||
from contextlib import closing
|
from contextlib import closing
|
||||||
@ -45,8 +46,14 @@ class eKnigiStore(BasicStoreConfig, StorePlugin):
|
|||||||
d.exec_()
|
d.exec_()
|
||||||
|
|
||||||
def search(self, query, max_results=10, timeout=60):
|
def search(self, query, max_results=10, timeout=60):
|
||||||
|
# check for cyrilic symbols before performing search
|
||||||
|
uquery = unicode(query.strip(), 'utf-8')
|
||||||
|
reObj = re.search(u'^[а-яА-Я\\d]{2,}[а-яА-Я\\d\\s]*$', uquery)
|
||||||
|
if not reObj:
|
||||||
|
return
|
||||||
|
|
||||||
base_url = 'http://e-knigi.net'
|
base_url = 'http://e-knigi.net'
|
||||||
url = base_url + '/virtuemart?page=shop.browse&search_category=0&search_limiter=anywhere&limitstart=0&limit=' + str(max_results) + '&keyword=' + urllib2.quote(query)
|
url = base_url + '/virtuemart?page=shop.browse&search_category=0&search_limiter=anywhere&keyword=' + urllib2.quote(query)
|
||||||
|
|
||||||
br = browser()
|
br = browser()
|
||||||
|
|
||||||
@ -75,12 +82,18 @@ class eKnigiStore(BasicStoreConfig, StorePlugin):
|
|||||||
if not id:
|
if not id:
|
||||||
continue
|
continue
|
||||||
|
|
||||||
|
title = ''.join(data.xpath('.//a[@class="gk_vm_product_image"]/img/@title')).strip()
|
||||||
|
author = ''.join(data.xpath('.//div[@style="float:left;width:90%"]/b/text()')).strip().replace('Автор: ', '')
|
||||||
|
|
||||||
|
if title.lower().find(query.lower()) == -1 and author.lower().find(query.lower()) == -1:
|
||||||
|
continue
|
||||||
|
|
||||||
counter -= 1
|
counter -= 1
|
||||||
|
|
||||||
s = SearchResult()
|
s = SearchResult()
|
||||||
s.cover_url = ''.join(data.xpath('.//a[@class="gk_vm_product_image"]/img/@src')).strip()
|
s.cover_url = ''.join(data.xpath('.//a[@class="gk_vm_product_image"]/img/@src')).strip()
|
||||||
s.title = ''.join(data.xpath('.//a[@class="gk_vm_product_image"]/img/@title')).strip()
|
s.title = title
|
||||||
s.author = ''.join(data.xpath('.//div[@style="float:left;width:90%"]/b/text()')).strip().replace('Автор: ', '')
|
s.author = author
|
||||||
s.price = ''.join(data.xpath('.//span[@class="productPrice"]/text()')).strip()
|
s.price = ''.join(data.xpath('.//span[@class="productPrice"]/text()')).strip()
|
||||||
s.detail_item = base_url + id
|
s.detail_item = base_url + id
|
||||||
s.drm = SearchResult.DRM_UNLOCKED
|
s.drm = SearchResult.DRM_UNLOCKED
|
||||||
|
@ -45,7 +45,7 @@ class GutenbergStore(BasicStoreConfig, StorePlugin):
|
|||||||
counter = max_results
|
counter = max_results
|
||||||
with closing(br.open(url, timeout=timeout)) as f:
|
with closing(br.open(url, timeout=timeout)) as f:
|
||||||
doc = html.fromstring(f.read())
|
doc = html.fromstring(f.read())
|
||||||
for data in doc.xpath('//ol[@class="results"]//li[contains(@class, "icon_title")]'):
|
for data in doc.xpath('//ol[@class="results"]//li[contains(@class, "icon_title") and not(contains(@class, "toplink"))]'):
|
||||||
if counter <= 0:
|
if counter <= 0:
|
||||||
break
|
break
|
||||||
|
|
||||||
@ -69,7 +69,7 @@ class GutenbergStore(BasicStoreConfig, StorePlugin):
|
|||||||
yield s
|
yield s
|
||||||
|
|
||||||
def get_details(self, search_result, timeout):
|
def get_details(self, search_result, timeout):
|
||||||
url = url_slash_cleaner('http://m.gutenberg.org/' + search_result.detail_item + '.mobile')
|
url = url_slash_cleaner('http://m.gutenberg.org/' + search_result.detail_item)
|
||||||
|
|
||||||
br = browser()
|
br = browser()
|
||||||
with closing(br.open(url, timeout=timeout)) as nf:
|
with closing(br.open(url, timeout=timeout)) as nf:
|
||||||
|
@ -150,6 +150,8 @@ class CheckLibrary(object):
|
|||||||
if not ext:
|
if not ext:
|
||||||
return False
|
return False
|
||||||
ext = ext[1:].lower()
|
ext = ext[1:].lower()
|
||||||
|
if ext.startswith('original_'):
|
||||||
|
ext = ext[len('original_'):]
|
||||||
if ext in EBOOK_EXTENSIONS:
|
if ext in EBOOK_EXTENSIONS:
|
||||||
return True
|
return True
|
||||||
return False
|
return False
|
||||||
|
@ -45,7 +45,7 @@ static void sort_concat_step(sqlite3_context *context, int argc, sqlite3_value *
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (list->count == list->length) {
|
if (list->count == list->length) {
|
||||||
list->vals = (SortConcatItem**)realloc(list->vals, list->length + 100);
|
list->vals = (SortConcatItem**)realloc(list->vals, sizeof(SortConcatItem*)*(list->length + 100));
|
||||||
if (list->vals == NULL) return;
|
if (list->vals == NULL) return;
|
||||||
list->length = list->length + 100;
|
list->length = list->length + 100;
|
||||||
}
|
}
|
||||||
@ -122,7 +122,6 @@ static void sort_concat_finalize(sqlite3_context *context) {
|
|||||||
free(ans);
|
free(ans);
|
||||||
sort_concat_free(list);
|
sort_concat_free(list);
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
static void sort_concat_finalize2(sqlite3_context *context) {
|
static void sort_concat_finalize2(sqlite3_context *context) {
|
||||||
@ -190,7 +189,7 @@ static void identifiers_concat_step(sqlite3_context *context, int argc, sqlite3_
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (list->count == list->length) {
|
if (list->count == list->length) {
|
||||||
list->vals = (IdentifiersConcatItem**)realloc(list->vals, list->length + 100);
|
list->vals = (IdentifiersConcatItem**)realloc(list->vals, sizeof(IdentifiersConcatItem*)*(list->length + 100));
|
||||||
if (list->vals == NULL) return;
|
if (list->vals == NULL) return;
|
||||||
list->length = list->length + 100;
|
list->length = list->length + 100;
|
||||||
}
|
}
|
||||||
|
@ -27,6 +27,9 @@ def remove_dir(x):
|
|||||||
except:
|
except:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
def app_prefix(prefix):
|
||||||
|
return '%s_%s_%s'%(__appname__, __version__, prefix)
|
||||||
|
|
||||||
def base_dir():
|
def base_dir():
|
||||||
global _base_dir
|
global _base_dir
|
||||||
if _base_dir is not None and not os.path.exists(_base_dir):
|
if _base_dir is not None and not os.path.exists(_base_dir):
|
||||||
@ -44,7 +47,7 @@ def base_dir():
|
|||||||
_base_dir = td
|
_base_dir = td
|
||||||
else:
|
else:
|
||||||
base = os.environ.get('CALIBRE_TEMP_DIR', None)
|
base = os.environ.get('CALIBRE_TEMP_DIR', None)
|
||||||
prefix = u'%s_%s_tmp_'%(__appname__, __version__)
|
prefix = app_prefix(u'tmp_')
|
||||||
try:
|
try:
|
||||||
# First try an ascii path as that is what was done historically
|
# First try an ascii path as that is what was done historically
|
||||||
# and we dont want to break working code
|
# and we dont want to break working code
|
||||||
@ -60,6 +63,32 @@ def base_dir():
|
|||||||
atexit.register(remove_dir, _base_dir)
|
atexit.register(remove_dir, _base_dir)
|
||||||
return _base_dir
|
return _base_dir
|
||||||
|
|
||||||
|
def _make_file(suffix, prefix, base):
|
||||||
|
try:
|
||||||
|
fd, name = tempfile.mkstemp(suffix, prefix, dir=base)
|
||||||
|
except UnicodeDecodeError:
|
||||||
|
global _base_dir
|
||||||
|
from calibre.constants import filesystem_encoding
|
||||||
|
base_dir()
|
||||||
|
if not isinstance(_base_dir, unicode):
|
||||||
|
_base_dir = _base_dir.decode(filesystem_encoding)
|
||||||
|
base = base.decode(filesystem_encoding)
|
||||||
|
fd, name = tempfile.mkstemp(suffix, prefix, dir=dir)
|
||||||
|
return fd, name
|
||||||
|
|
||||||
|
def _make_dir(suffix, prefix, base):
|
||||||
|
try:
|
||||||
|
tdir = tempfile.mkdtemp(suffix, prefix, base)
|
||||||
|
except ValueError:
|
||||||
|
global _base_dir
|
||||||
|
from calibre.constants import filesystem_encoding
|
||||||
|
base_dir()
|
||||||
|
if not isinstance(_base_dir, unicode):
|
||||||
|
_base_dir = _base_dir.decode(filesystem_encoding)
|
||||||
|
base = base.decode(filesystem_encoding)
|
||||||
|
tdir = tempfile.mkdtemp(suffix, prefix, base)
|
||||||
|
return tdir
|
||||||
|
|
||||||
class PersistentTemporaryFile(object):
|
class PersistentTemporaryFile(object):
|
||||||
"""
|
"""
|
||||||
A file-like object that is a temporary file that is available even after being closed on
|
A file-like object that is a temporary file that is available even after being closed on
|
||||||
@ -72,18 +101,7 @@ class PersistentTemporaryFile(object):
|
|||||||
prefix = ""
|
prefix = ""
|
||||||
if dir is None:
|
if dir is None:
|
||||||
dir = base_dir()
|
dir = base_dir()
|
||||||
try:
|
fd, name = _make_file(suffix, prefix, dir)
|
||||||
fd, name = tempfile.mkstemp(suffix, __appname__+"_"+ __version__+"_" + prefix,
|
|
||||||
dir=dir)
|
|
||||||
except UnicodeDecodeError:
|
|
||||||
global _base_dir
|
|
||||||
from calibre.constants import filesystem_encoding
|
|
||||||
base_dir()
|
|
||||||
if not isinstance(_base_dir, unicode):
|
|
||||||
_base_dir = _base_dir.decode(filesystem_encoding)
|
|
||||||
dir = dir.decode(filesystem_encoding)
|
|
||||||
fd, name = tempfile.mkstemp(suffix, __appname__+"_"+ __version__+"_" + prefix,
|
|
||||||
dir=dir)
|
|
||||||
|
|
||||||
self._file = os.fdopen(fd, mode)
|
self._file = os.fdopen(fd, mode)
|
||||||
self._name = name
|
self._name = name
|
||||||
@ -114,16 +132,7 @@ def PersistentTemporaryDirectory(suffix='', prefix='', dir=None):
|
|||||||
'''
|
'''
|
||||||
if dir is None:
|
if dir is None:
|
||||||
dir = base_dir()
|
dir = base_dir()
|
||||||
try:
|
tdir = _make_dir(suffix, prefix, dir)
|
||||||
tdir = tempfile.mkdtemp(suffix, __appname__+"_"+ __version__+"_" +prefix, dir)
|
|
||||||
except ValueError:
|
|
||||||
global _base_dir
|
|
||||||
from calibre.constants import filesystem_encoding
|
|
||||||
base_dir()
|
|
||||||
if not isinstance(_base_dir, unicode):
|
|
||||||
_base_dir = _base_dir.decode(filesystem_encoding)
|
|
||||||
dir = dir.decode(filesystem_encoding)
|
|
||||||
tdir = tempfile.mkdtemp(suffix, __appname__+"_"+ __version__+"_" +prefix, dir)
|
|
||||||
|
|
||||||
atexit.register(remove_dir, tdir)
|
atexit.register(remove_dir, tdir)
|
||||||
return tdir
|
return tdir
|
||||||
@ -141,7 +150,7 @@ class TemporaryDirectory(object):
|
|||||||
self.keep = keep
|
self.keep = keep
|
||||||
|
|
||||||
def __enter__(self):
|
def __enter__(self):
|
||||||
self.tdir = tempfile.mkdtemp(self.suffix, __appname__+"_"+ __version__+"_" +self.prefix, self.dir)
|
self.tdir = _make_dir(self.suffix, self.prefix, self.dir)
|
||||||
return self.tdir
|
return self.tdir
|
||||||
|
|
||||||
def __exit__(self, *args):
|
def __exit__(self, *args):
|
||||||
@ -161,9 +170,7 @@ class TemporaryFile(object):
|
|||||||
self._file = None
|
self._file = None
|
||||||
|
|
||||||
def __enter__(self):
|
def __enter__(self):
|
||||||
fd, name = tempfile.mkstemp(self.suffix,
|
fd, name = _make_file(self.suffix, self.prefix, self.dir)
|
||||||
__appname__+"_"+ __version__+"_" + self.prefix,
|
|
||||||
dir=self.dir)
|
|
||||||
self._file = os.fdopen(fd, self.mode)
|
self._file = os.fdopen(fd, self.mode)
|
||||||
self._name = name
|
self._name = name
|
||||||
self._file.close()
|
self._file.close()
|
||||||
|
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@ -4,9 +4,9 @@
|
|||||||
#
|
#
|
||||||
msgid ""
|
msgid ""
|
||||||
msgstr ""
|
msgstr ""
|
||||||
"Project-Id-Version: calibre 0.8.11\n"
|
"Project-Id-Version: calibre 0.8.12\n"
|
||||||
"POT-Creation-Date: 2011-07-22 09:50+MDT\n"
|
"POT-Creation-Date: 2011-07-29 10:47+MDT\n"
|
||||||
"PO-Revision-Date: 2011-07-22 09:50+MDT\n"
|
"PO-Revision-Date: 2011-07-29 10:47+MDT\n"
|
||||||
"Last-Translator: Automatically generated\n"
|
"Last-Translator: Automatically generated\n"
|
||||||
"Language-Team: LANGUAGE\n"
|
"Language-Team: LANGUAGE\n"
|
||||||
"MIME-Version: 1.0\n"
|
"MIME-Version: 1.0\n"
|
||||||
@ -57,15 +57,15 @@ msgstr ""
|
|||||||
#: /home/kovid/work/calibre/src/calibre/ebooks/metadata/ereader.py:36
|
#: /home/kovid/work/calibre/src/calibre/ebooks/metadata/ereader.py:36
|
||||||
#: /home/kovid/work/calibre/src/calibre/ebooks/metadata/ereader.py:61
|
#: /home/kovid/work/calibre/src/calibre/ebooks/metadata/ereader.py:61
|
||||||
#: /home/kovid/work/calibre/src/calibre/ebooks/metadata/extz.py:23
|
#: /home/kovid/work/calibre/src/calibre/ebooks/metadata/extz.py:23
|
||||||
#: /home/kovid/work/calibre/src/calibre/ebooks/metadata/fb2.py:40
|
#: /home/kovid/work/calibre/src/calibre/ebooks/metadata/fb2.py:39
|
||||||
#: /home/kovid/work/calibre/src/calibre/ebooks/metadata/fb2.py:100
|
#: /home/kovid/work/calibre/src/calibre/ebooks/metadata/fb2.py:99
|
||||||
#: /home/kovid/work/calibre/src/calibre/ebooks/metadata/meta.py:36
|
#: /home/kovid/work/calibre/src/calibre/ebooks/metadata/meta.py:36
|
||||||
#: /home/kovid/work/calibre/src/calibre/ebooks/metadata/meta.py:64
|
#: /home/kovid/work/calibre/src/calibre/ebooks/metadata/meta.py:64
|
||||||
#: /home/kovid/work/calibre/src/calibre/ebooks/metadata/meta.py:66
|
#: /home/kovid/work/calibre/src/calibre/ebooks/metadata/meta.py:66
|
||||||
#: /home/kovid/work/calibre/src/calibre/ebooks/metadata/meta.py:124
|
#: /home/kovid/work/calibre/src/calibre/ebooks/metadata/meta.py:124
|
||||||
#: /home/kovid/work/calibre/src/calibre/ebooks/metadata/meta.py:126
|
#: /home/kovid/work/calibre/src/calibre/ebooks/metadata/meta.py:126
|
||||||
#: /home/kovid/work/calibre/src/calibre/ebooks/metadata/opf2.py:1072
|
#: /home/kovid/work/calibre/src/calibre/ebooks/metadata/opf2.py:1080
|
||||||
#: /home/kovid/work/calibre/src/calibre/ebooks/metadata/opf2.py:1182
|
#: /home/kovid/work/calibre/src/calibre/ebooks/metadata/opf2.py:1190
|
||||||
#: /home/kovid/work/calibre/src/calibre/ebooks/metadata/pdb.py:41
|
#: /home/kovid/work/calibre/src/calibre/ebooks/metadata/pdb.py:41
|
||||||
#: /home/kovid/work/calibre/src/calibre/ebooks/metadata/pdf.py:29
|
#: /home/kovid/work/calibre/src/calibre/ebooks/metadata/pdf.py:29
|
||||||
#: /home/kovid/work/calibre/src/calibre/ebooks/metadata/plucker.py:25
|
#: /home/kovid/work/calibre/src/calibre/ebooks/metadata/plucker.py:25
|
||||||
@ -81,10 +81,10 @@ msgstr ""
|
|||||||
#: /home/kovid/work/calibre/src/calibre/ebooks/metadata/sources/covers.py:81
|
#: /home/kovid/work/calibre/src/calibre/ebooks/metadata/sources/covers.py:81
|
||||||
#: /home/kovid/work/calibre/src/calibre/ebooks/metadata/sources/douban.py:80
|
#: /home/kovid/work/calibre/src/calibre/ebooks/metadata/sources/douban.py:80
|
||||||
#: /home/kovid/work/calibre/src/calibre/ebooks/metadata/sources/google.py:81
|
#: /home/kovid/work/calibre/src/calibre/ebooks/metadata/sources/google.py:81
|
||||||
#: /home/kovid/work/calibre/src/calibre/ebooks/metadata/sources/identify.py:256
|
#: /home/kovid/work/calibre/src/calibre/ebooks/metadata/sources/identify.py:257
|
||||||
#: /home/kovid/work/calibre/src/calibre/ebooks/metadata/sources/identify.py:361
|
#: /home/kovid/work/calibre/src/calibre/ebooks/metadata/sources/identify.py:362
|
||||||
#: /home/kovid/work/calibre/src/calibre/ebooks/metadata/sources/identify.py:363
|
#: /home/kovid/work/calibre/src/calibre/ebooks/metadata/sources/identify.py:364
|
||||||
#: /home/kovid/work/calibre/src/calibre/ebooks/metadata/sources/identify.py:464
|
#: /home/kovid/work/calibre/src/calibre/ebooks/metadata/sources/identify.py:465
|
||||||
#: /home/kovid/work/calibre/src/calibre/ebooks/metadata/txt.py:18
|
#: /home/kovid/work/calibre/src/calibre/ebooks/metadata/txt.py:18
|
||||||
#: /home/kovid/work/calibre/src/calibre/ebooks/mobi/reader.py:43
|
#: /home/kovid/work/calibre/src/calibre/ebooks/mobi/reader.py:43
|
||||||
#: /home/kovid/work/calibre/src/calibre/ebooks/mobi/reader.py:69
|
#: /home/kovid/work/calibre/src/calibre/ebooks/mobi/reader.py:69
|
||||||
@ -95,6 +95,7 @@ msgstr ""
|
|||||||
#: /home/kovid/work/calibre/src/calibre/ebooks/mobi/reader.py:964
|
#: /home/kovid/work/calibre/src/calibre/ebooks/mobi/reader.py:964
|
||||||
#: /home/kovid/work/calibre/src/calibre/ebooks/mobi/reader.py:966
|
#: /home/kovid/work/calibre/src/calibre/ebooks/mobi/reader.py:966
|
||||||
#: /home/kovid/work/calibre/src/calibre/ebooks/mobi/reader.py:968
|
#: /home/kovid/work/calibre/src/calibre/ebooks/mobi/reader.py:968
|
||||||
|
#: /home/kovid/work/calibre/src/calibre/ebooks/mobi/utils.py:292
|
||||||
#: /home/kovid/work/calibre/src/calibre/ebooks/odt/input.py:99
|
#: /home/kovid/work/calibre/src/calibre/ebooks/odt/input.py:99
|
||||||
#: /home/kovid/work/calibre/src/calibre/ebooks/odt/input.py:101
|
#: /home/kovid/work/calibre/src/calibre/ebooks/odt/input.py:101
|
||||||
#: /home/kovid/work/calibre/src/calibre/ebooks/oeb/base.py:1001
|
#: /home/kovid/work/calibre/src/calibre/ebooks/oeb/base.py:1001
|
||||||
@ -134,9 +135,9 @@ msgstr ""
|
|||||||
#: /home/kovid/work/calibre/src/calibre/gui2/actions/add.py:156
|
#: /home/kovid/work/calibre/src/calibre/gui2/actions/add.py:156
|
||||||
#: /home/kovid/work/calibre/src/calibre/gui2/actions/edit_metadata.py:376
|
#: /home/kovid/work/calibre/src/calibre/gui2/actions/edit_metadata.py:376
|
||||||
#: /home/kovid/work/calibre/src/calibre/gui2/actions/edit_metadata.py:379
|
#: /home/kovid/work/calibre/src/calibre/gui2/actions/edit_metadata.py:379
|
||||||
#: /home/kovid/work/calibre/src/calibre/gui2/add.py:160
|
#: /home/kovid/work/calibre/src/calibre/gui2/add.py:161
|
||||||
#: /home/kovid/work/calibre/src/calibre/gui2/add.py:167
|
#: /home/kovid/work/calibre/src/calibre/gui2/add.py:168
|
||||||
#: /home/kovid/work/calibre/src/calibre/gui2/book_details.py:549
|
#: /home/kovid/work/calibre/src/calibre/gui2/book_details.py:550
|
||||||
#: /home/kovid/work/calibre/src/calibre/gui2/convert/__init__.py:42
|
#: /home/kovid/work/calibre/src/calibre/gui2/convert/__init__.py:42
|
||||||
#: /home/kovid/work/calibre/src/calibre/gui2/convert/metadata.py:122
|
#: /home/kovid/work/calibre/src/calibre/gui2/convert/metadata.py:122
|
||||||
#: /home/kovid/work/calibre/src/calibre/gui2/convert/metadata.py:151
|
#: /home/kovid/work/calibre/src/calibre/gui2/convert/metadata.py:151
|
||||||
@ -164,21 +165,21 @@ msgstr ""
|
|||||||
#: /home/kovid/work/calibre/src/calibre/gui2/metadata/single.py:364
|
#: /home/kovid/work/calibre/src/calibre/gui2/metadata/single.py:364
|
||||||
#: /home/kovid/work/calibre/src/calibre/gui2/metadata/single_download.py:161
|
#: /home/kovid/work/calibre/src/calibre/gui2/metadata/single_download.py:161
|
||||||
#: /home/kovid/work/calibre/src/calibre/gui2/metadata/single_download.py:165
|
#: /home/kovid/work/calibre/src/calibre/gui2/metadata/single_download.py:165
|
||||||
#: /home/kovid/work/calibre/src/calibre/gui2/store/search/models.py:166
|
#: /home/kovid/work/calibre/src/calibre/gui2/store/search/models.py:170
|
||||||
#: /home/kovid/work/calibre/src/calibre/gui2/store/stores/google_books_plugin.py:90
|
#: /home/kovid/work/calibre/src/calibre/gui2/store/stores/google_books_plugin.py:108
|
||||||
#: /home/kovid/work/calibre/src/calibre/gui2/viewer/main.py:200
|
#: /home/kovid/work/calibre/src/calibre/gui2/viewer/main.py:200
|
||||||
#: /home/kovid/work/calibre/src/calibre/library/cli.py:217
|
#: /home/kovid/work/calibre/src/calibre/library/cli.py:217
|
||||||
#: /home/kovid/work/calibre/src/calibre/library/database.py:914
|
#: /home/kovid/work/calibre/src/calibre/library/database.py:914
|
||||||
#: /home/kovid/work/calibre/src/calibre/library/database2.py:535
|
#: /home/kovid/work/calibre/src/calibre/library/database2.py:535
|
||||||
#: /home/kovid/work/calibre/src/calibre/library/database2.py:543
|
#: /home/kovid/work/calibre/src/calibre/library/database2.py:543
|
||||||
#: /home/kovid/work/calibre/src/calibre/library/database2.py:554
|
#: /home/kovid/work/calibre/src/calibre/library/database2.py:554
|
||||||
#: /home/kovid/work/calibre/src/calibre/library/database2.py:2003
|
#: /home/kovid/work/calibre/src/calibre/library/database2.py:2005
|
||||||
#: /home/kovid/work/calibre/src/calibre/library/database2.py:2150
|
#: /home/kovid/work/calibre/src/calibre/library/database2.py:2152
|
||||||
#: /home/kovid/work/calibre/src/calibre/library/database2.py:3164
|
#: /home/kovid/work/calibre/src/calibre/library/database2.py:3169
|
||||||
#: /home/kovid/work/calibre/src/calibre/library/database2.py:3166
|
#: /home/kovid/work/calibre/src/calibre/library/database2.py:3171
|
||||||
#: /home/kovid/work/calibre/src/calibre/library/database2.py:3299
|
#: /home/kovid/work/calibre/src/calibre/library/database2.py:3304
|
||||||
#: /home/kovid/work/calibre/src/calibre/library/server/content.py:212
|
#: /home/kovid/work/calibre/src/calibre/library/server/content.py:225
|
||||||
#: /home/kovid/work/calibre/src/calibre/library/server/content.py:213
|
#: /home/kovid/work/calibre/src/calibre/library/server/content.py:226
|
||||||
#: /home/kovid/work/calibre/src/calibre/library/server/mobile.py:243
|
#: /home/kovid/work/calibre/src/calibre/library/server/mobile.py:243
|
||||||
#: /home/kovid/work/calibre/src/calibre/library/server/opds.py:156
|
#: /home/kovid/work/calibre/src/calibre/library/server/opds.py:156
|
||||||
#: /home/kovid/work/calibre/src/calibre/library/server/opds.py:159
|
#: /home/kovid/work/calibre/src/calibre/library/server/opds.py:159
|
||||||
@ -238,7 +239,7 @@ msgid "Preferences"
|
|||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: /home/kovid/work/calibre/src/calibre/customize/__init__.py:609
|
#: /home/kovid/work/calibre/src/calibre/customize/__init__.py:609
|
||||||
#: /home/kovid/work/calibre/src/calibre/gui2/store/search/models.py:37
|
#: /home/kovid/work/calibre/src/calibre/gui2/store/search/models.py:41
|
||||||
msgid "Store"
|
msgid "Store"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
@ -967,7 +968,7 @@ msgstr ""
|
|||||||
#: /home/kovid/work/calibre/src/calibre/gui2/tag_browser/model.py:1134
|
#: /home/kovid/work/calibre/src/calibre/gui2/tag_browser/model.py:1134
|
||||||
#: /home/kovid/work/calibre/src/calibre/library/database2.py:330
|
#: /home/kovid/work/calibre/src/calibre/library/database2.py:330
|
||||||
#: /home/kovid/work/calibre/src/calibre/library/database2.py:343
|
#: /home/kovid/work/calibre/src/calibre/library/database2.py:343
|
||||||
#: /home/kovid/work/calibre/src/calibre/library/database2.py:3028
|
#: /home/kovid/work/calibre/src/calibre/library/database2.py:3030
|
||||||
#: /home/kovid/work/calibre/src/calibre/library/field_metadata.py:171
|
#: /home/kovid/work/calibre/src/calibre/library/field_metadata.py:171
|
||||||
msgid "News"
|
msgid "News"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
@ -975,8 +976,8 @@ msgstr ""
|
|||||||
#: /home/kovid/work/calibre/src/calibre/devices/apple/driver.py:2685
|
#: /home/kovid/work/calibre/src/calibre/devices/apple/driver.py:2685
|
||||||
#: /home/kovid/work/calibre/src/calibre/gui2/catalog/catalog_epub_mobi.py:65
|
#: /home/kovid/work/calibre/src/calibre/gui2/catalog/catalog_epub_mobi.py:65
|
||||||
#: /home/kovid/work/calibre/src/calibre/library/catalog.py:652
|
#: /home/kovid/work/calibre/src/calibre/library/catalog.py:652
|
||||||
#: /home/kovid/work/calibre/src/calibre/library/database2.py:2988
|
#: /home/kovid/work/calibre/src/calibre/library/database2.py:2990
|
||||||
#: /home/kovid/work/calibre/src/calibre/library/database2.py:3006
|
#: /home/kovid/work/calibre/src/calibre/library/database2.py:3008
|
||||||
msgid "Catalog"
|
msgid "Catalog"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
@ -1345,6 +1346,10 @@ msgstr ""
|
|||||||
msgid "Communicate with the Moovybook Reader"
|
msgid "Communicate with the Moovybook Reader"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
|
#: /home/kovid/work/calibre/src/calibre/devices/misc.py:358
|
||||||
|
msgid "Communicate with the COBY"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
#: /home/kovid/work/calibre/src/calibre/devices/nokia/driver.py:17
|
#: /home/kovid/work/calibre/src/calibre/devices/nokia/driver.py:17
|
||||||
msgid "Communicate with the Nokia 770 internet tablet."
|
msgid "Communicate with the Nokia 770 internet tablet."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
@ -1792,11 +1797,11 @@ msgstr ""
|
|||||||
msgid "Options to help with debugging the conversion"
|
msgid "Options to help with debugging the conversion"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: /home/kovid/work/calibre/src/calibre/ebooks/conversion/cli.py:214
|
#: /home/kovid/work/calibre/src/calibre/ebooks/conversion/cli.py:216
|
||||||
msgid "List builtin recipes"
|
msgid "List builtin recipe names. You can create an ebook from a builtin recipe like this: ebook-convert \"Recipe Name.recipe\" output.epub"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: /home/kovid/work/calibre/src/calibre/ebooks/conversion/cli.py:287
|
#: /home/kovid/work/calibre/src/calibre/ebooks/conversion/cli.py:288
|
||||||
msgid "Output saved to"
|
msgid "Output saved to"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
@ -2589,7 +2594,7 @@ msgstr ""
|
|||||||
#: /home/kovid/work/calibre/src/calibre/gui2/library/models.py:1016
|
#: /home/kovid/work/calibre/src/calibre/gui2/library/models.py:1016
|
||||||
#: /home/kovid/work/calibre/src/calibre/gui2/metadata/single_download.py:133
|
#: /home/kovid/work/calibre/src/calibre/gui2/metadata/single_download.py:133
|
||||||
#: /home/kovid/work/calibre/src/calibre/gui2/preferences/metadata_sources.py:162
|
#: /home/kovid/work/calibre/src/calibre/gui2/preferences/metadata_sources.py:162
|
||||||
#: /home/kovid/work/calibre/src/calibre/gui2/store/search/models.py:37
|
#: /home/kovid/work/calibre/src/calibre/gui2/store/search/models.py:41
|
||||||
#: /home/kovid/work/calibre/src/calibre/gui2/store/stores/mobileread/models.py:23
|
#: /home/kovid/work/calibre/src/calibre/gui2/store/stores/mobileread/models.py:23
|
||||||
#: /home/kovid/work/calibre/src/calibre/library/field_metadata.py:349
|
#: /home/kovid/work/calibre/src/calibre/library/field_metadata.py:349
|
||||||
#: /home/kovid/work/calibre/src/calibre/library/server/opds.py:578
|
#: /home/kovid/work/calibre/src/calibre/library/server/opds.py:578
|
||||||
@ -2757,10 +2762,10 @@ msgid ""
|
|||||||
"Fetch a cover image/social metadata for the book identified by ISBN from LibraryThing.com\n"
|
"Fetch a cover image/social metadata for the book identified by ISBN from LibraryThing.com\n"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: /home/kovid/work/calibre/src/calibre/ebooks/metadata/opf2.py:1366
|
#: /home/kovid/work/calibre/src/calibre/ebooks/metadata/opf2.py:1374
|
||||||
#: /home/kovid/work/calibre/src/calibre/ebooks/oeb/base.py:1498
|
#: /home/kovid/work/calibre/src/calibre/ebooks/oeb/base.py:1498
|
||||||
#: /home/kovid/work/calibre/src/calibre/gui2/metadata/single.py:900
|
#: /home/kovid/work/calibre/src/calibre/gui2/metadata/single.py:900
|
||||||
#: /home/kovid/work/calibre/src/calibre/gui2/store/search/models.py:37
|
#: /home/kovid/work/calibre/src/calibre/gui2/store/search/models.py:41
|
||||||
msgid "Cover"
|
msgid "Cover"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
@ -2881,7 +2886,7 @@ msgstr ""
|
|||||||
msgid "When adding the Table of Contents to the book, add it at the start of the book instead of the end. Not recommended."
|
msgid "When adding the Table of Contents to the book, add it at the start of the book instead of the end. Not recommended."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: /home/kovid/work/calibre/src/calibre/ebooks/mobi/output.py:119
|
#: /home/kovid/work/calibre/src/calibre/ebooks/mobi/output.py:105
|
||||||
msgid "All articles"
|
msgid "All articles"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
@ -3942,7 +3947,7 @@ msgstr ""
|
|||||||
#: /home/kovid/work/calibre/src/calibre/gui2/actions/choose_library.py:419
|
#: /home/kovid/work/calibre/src/calibre/gui2/actions/choose_library.py:419
|
||||||
#: /home/kovid/work/calibre/src/calibre/gui2/actions/copy_to_library.py:178
|
#: /home/kovid/work/calibre/src/calibre/gui2/actions/copy_to_library.py:178
|
||||||
#: /home/kovid/work/calibre/src/calibre/gui2/actions/save_to_disk.py:100
|
#: /home/kovid/work/calibre/src/calibre/gui2/actions/save_to_disk.py:100
|
||||||
#: /home/kovid/work/calibre/src/calibre/gui2/library/views.py:883
|
#: /home/kovid/work/calibre/src/calibre/gui2/library/views.py:886
|
||||||
msgid "Not allowed"
|
msgid "Not allowed"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
@ -4744,68 +4749,77 @@ msgstr ""
|
|||||||
msgid "%s has no available formats."
|
msgid "%s has no available formats."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: /home/kovid/work/calibre/src/calibre/gui2/add.py:68
|
#: /home/kovid/work/calibre/src/calibre/gui2/add.py:69
|
||||||
msgid "Searching in"
|
msgid "Searching in"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: /home/kovid/work/calibre/src/calibre/gui2/add.py:245
|
#: /home/kovid/work/calibre/src/calibre/gui2/add.py:246
|
||||||
msgid "Adding..."
|
msgid "Adding..."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: /home/kovid/work/calibre/src/calibre/gui2/add.py:258
|
#: /home/kovid/work/calibre/src/calibre/gui2/add.py:259
|
||||||
msgid "Searching in all sub-directories..."
|
msgid "Searching in all sub-directories..."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: /home/kovid/work/calibre/src/calibre/gui2/add.py:269
|
#: /home/kovid/work/calibre/src/calibre/gui2/add.py:270
|
||||||
msgid "Path error"
|
msgid "Path error"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: /home/kovid/work/calibre/src/calibre/gui2/add.py:270
|
#: /home/kovid/work/calibre/src/calibre/gui2/add.py:271
|
||||||
msgid "The specified directory could not be processed."
|
msgid "The specified directory could not be processed."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: /home/kovid/work/calibre/src/calibre/gui2/add.py:274
|
#: /home/kovid/work/calibre/src/calibre/gui2/add.py:275
|
||||||
#: /home/kovid/work/calibre/src/calibre/gui2/device.py:895
|
#: /home/kovid/work/calibre/src/calibre/gui2/device.py:895
|
||||||
msgid "No books"
|
msgid "No books"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: /home/kovid/work/calibre/src/calibre/gui2/add.py:275
|
#: /home/kovid/work/calibre/src/calibre/gui2/add.py:276
|
||||||
msgid "No books found"
|
msgid "No books found"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: /home/kovid/work/calibre/src/calibre/gui2/add.py:339
|
#: /home/kovid/work/calibre/src/calibre/gui2/add.py:289
|
||||||
|
#: /home/kovid/work/calibre/src/calibre/gui2/metadata/basic_widgets.py:777
|
||||||
|
msgid "No permission"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: /home/kovid/work/calibre/src/calibre/gui2/add.py:290
|
||||||
|
msgid "Cannot add some files as you do not have permission to access them. Click Show Details to see the list of such files."
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: /home/kovid/work/calibre/src/calibre/gui2/add.py:358
|
||||||
msgid "Added"
|
msgid "Added"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: /home/kovid/work/calibre/src/calibre/gui2/add.py:352
|
#: /home/kovid/work/calibre/src/calibre/gui2/add.py:371
|
||||||
msgid "Adding failed"
|
msgid "Adding failed"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: /home/kovid/work/calibre/src/calibre/gui2/add.py:353
|
#: /home/kovid/work/calibre/src/calibre/gui2/add.py:372
|
||||||
msgid "The add books process seems to have hung. Try restarting calibre and adding the books in smaller increments, until you find the problem book."
|
msgid "The add books process seems to have hung. Try restarting calibre and adding the books in smaller increments, until you find the problem book."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: /home/kovid/work/calibre/src/calibre/gui2/add.py:368
|
#: /home/kovid/work/calibre/src/calibre/gui2/add.py:387
|
||||||
msgid "Duplicates found!"
|
msgid "Duplicates found!"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: /home/kovid/work/calibre/src/calibre/gui2/add.py:369
|
#: /home/kovid/work/calibre/src/calibre/gui2/add.py:388
|
||||||
msgid "Books with the same title as the following already exist in the database. Add them anyway?"
|
msgid "Books with the same title as the following already exist in the database. Add them anyway?"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: /home/kovid/work/calibre/src/calibre/gui2/add.py:372
|
#: /home/kovid/work/calibre/src/calibre/gui2/add.py:391
|
||||||
msgid "Adding duplicates..."
|
msgid "Adding duplicates..."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: /home/kovid/work/calibre/src/calibre/gui2/add.py:441
|
#: /home/kovid/work/calibre/src/calibre/gui2/add.py:460
|
||||||
msgid "Saving..."
|
msgid "Saving..."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: /home/kovid/work/calibre/src/calibre/gui2/add.py:448
|
#: /home/kovid/work/calibre/src/calibre/gui2/add.py:467
|
||||||
msgid "Collecting data, please wait..."
|
msgid "Collecting data, please wait..."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: /home/kovid/work/calibre/src/calibre/gui2/add.py:520
|
#: /home/kovid/work/calibre/src/calibre/gui2/add.py:539
|
||||||
msgid "Saved"
|
msgid "Saved"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
@ -4958,37 +4972,37 @@ msgstr ""
|
|||||||
msgid "Ids"
|
msgid "Ids"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: /home/kovid/work/calibre/src/calibre/gui2/book_details.py:162
|
#: /home/kovid/work/calibre/src/calibre/gui2/book_details.py:163
|
||||||
#, python-format
|
#, python-format
|
||||||
msgid "Book %(sidx)s of <span class=\"series_name\">%(series)s</span>"
|
msgid "Book %(sidx)s of <span class=\"series_name\">%(series)s</span>"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: /home/kovid/work/calibre/src/calibre/gui2/book_details.py:173
|
#: /home/kovid/work/calibre/src/calibre/gui2/book_details.py:174
|
||||||
#: /home/kovid/work/calibre/src/calibre/gui2/library/models.py:1020
|
#: /home/kovid/work/calibre/src/calibre/gui2/library/models.py:1020
|
||||||
msgid "Collections"
|
msgid "Collections"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: /home/kovid/work/calibre/src/calibre/gui2/book_details.py:275
|
#: /home/kovid/work/calibre/src/calibre/gui2/book_details.py:276
|
||||||
#: /home/kovid/work/calibre/src/calibre/gui2/widgets.py:247
|
#: /home/kovid/work/calibre/src/calibre/gui2/widgets.py:247
|
||||||
msgid "Paste Cover"
|
msgid "Paste Cover"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: /home/kovid/work/calibre/src/calibre/gui2/book_details.py:276
|
#: /home/kovid/work/calibre/src/calibre/gui2/book_details.py:277
|
||||||
#: /home/kovid/work/calibre/src/calibre/gui2/widgets.py:248
|
#: /home/kovid/work/calibre/src/calibre/gui2/widgets.py:248
|
||||||
msgid "Copy Cover"
|
msgid "Copy Cover"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: /home/kovid/work/calibre/src/calibre/gui2/book_details.py:542
|
#: /home/kovid/work/calibre/src/calibre/gui2/book_details.py:543
|
||||||
msgid "Double-click to open Book Details window"
|
msgid "Double-click to open Book Details window"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: /home/kovid/work/calibre/src/calibre/gui2/book_details.py:543
|
#: /home/kovid/work/calibre/src/calibre/gui2/book_details.py:544
|
||||||
#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/delete_matching_from_device.py:76
|
#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/delete_matching_from_device.py:76
|
||||||
#: /home/kovid/work/calibre/src/calibre/library/field_metadata.py:279
|
#: /home/kovid/work/calibre/src/calibre/library/field_metadata.py:279
|
||||||
msgid "Path"
|
msgid "Path"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: /home/kovid/work/calibre/src/calibre/gui2/book_details.py:544
|
#: /home/kovid/work/calibre/src/calibre/gui2/book_details.py:545
|
||||||
#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/book_info.py:109
|
#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/book_info.py:109
|
||||||
#, python-format
|
#, python-format
|
||||||
msgid "Cover size: %(width)d x %(height)d"
|
msgid "Cover size: %(width)d x %(height)d"
|
||||||
@ -6624,11 +6638,11 @@ msgstr ""
|
|||||||
msgid "<p>For example, to match all h2 tags that have class=\"chapter\", set tag to <i>h2</i>, attribute to <i>class</i> and value to <i>chapter</i>.</p><p>Leaving attribute blank will match any attribute and leaving value blank will match any value. Setting tag to * will match any tag.</p><p>To learn more advanced usage of XPath see the <a href=\"http://manual.calibre-ebook.com/xpath.html\">XPath Tutorial</a>."
|
msgid "<p>For example, to match all h2 tags that have class=\"chapter\", set tag to <i>h2</i>, attribute to <i>class</i> and value to <i>chapter</i>.</p><p>Leaving attribute blank will match any attribute and leaving value blank will match any value. Setting tag to * will match any tag.</p><p>To learn more advanced usage of XPath see the <a href=\"http://manual.calibre-ebook.com/xpath.html\">XPath Tutorial</a>."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: /home/kovid/work/calibre/src/calibre/gui2/cover_flow.py:140
|
#: /home/kovid/work/calibre/src/calibre/gui2/cover_flow.py:145
|
||||||
msgid "Browse by covers"
|
msgid "Browse by covers"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: /home/kovid/work/calibre/src/calibre/gui2/cover_flow.py:171
|
#: /home/kovid/work/calibre/src/calibre/gui2/cover_flow.py:176
|
||||||
msgid "Cover browser could not be loaded"
|
msgid "Cover browser could not be loaded"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
@ -8314,19 +8328,19 @@ msgstr ""
|
|||||||
msgid "Authors"
|
msgid "Authors"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/quickview.py:189
|
#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/quickview.py:188
|
||||||
msgid "**No items found**"
|
msgid "**No items found**"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/quickview.py:190
|
#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/quickview.py:189
|
||||||
msgid "Click in a column in the library view to see the information for that book"
|
msgid "Click in a column in the library view to see the information for that book"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/quickview.py:206
|
#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/quickview.py:205
|
||||||
msgid "Books with selected item \"{0}\": {1}"
|
msgid "Books with selected item \"{0}\": {1}"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/quickview.py:212
|
#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/quickview.py:211
|
||||||
msgid "Double-click on a book to change the selection in the library view. Shift- or control-double-click to edit the metadata of a book"
|
msgid "Double-click on a book to change the selection in the library view. Shift- or control-double-click to edit the metadata of a book"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
@ -9753,7 +9767,7 @@ msgstr ""
|
|||||||
msgid "Restore default layout"
|
msgid "Restore default layout"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: /home/kovid/work/calibre/src/calibre/gui2/library/views.py:884
|
#: /home/kovid/work/calibre/src/calibre/gui2/library/views.py:887
|
||||||
msgid "Dropping onto a device is not supported. First add the book to the calibre library."
|
msgid "Dropping onto a device is not supported. First add the book to the calibre library."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
@ -10061,10 +10075,6 @@ msgstr ""
|
|||||||
msgid "Choose formats for "
|
msgid "Choose formats for "
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: /home/kovid/work/calibre/src/calibre/gui2/metadata/basic_widgets.py:777
|
|
||||||
msgid "No permission"
|
|
||||||
msgstr ""
|
|
||||||
|
|
||||||
#: /home/kovid/work/calibre/src/calibre/gui2/metadata/basic_widgets.py:778
|
#: /home/kovid/work/calibre/src/calibre/gui2/metadata/basic_widgets.py:778
|
||||||
msgid "You do not have permission to read the following files:"
|
msgid "You do not have permission to read the following files:"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
@ -12581,7 +12591,7 @@ msgid "Invert"
|
|||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: /home/kovid/work/calibre/src/calibre/gui2/store/config/chooser/models.py:21
|
#: /home/kovid/work/calibre/src/calibre/gui2/store/config/chooser/models.py:21
|
||||||
#: /home/kovid/work/calibre/src/calibre/gui2/store/search/models.py:37
|
#: /home/kovid/work/calibre/src/calibre/gui2/store/search/models.py:41
|
||||||
msgid "Affiliate"
|
msgid "Affiliate"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
@ -12619,7 +12629,7 @@ msgid "This store is headquartered in %s. This is a good indication of what mark
|
|||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: /home/kovid/work/calibre/src/calibre/gui2/store/config/chooser/models.py:143
|
#: /home/kovid/work/calibre/src/calibre/gui2/store/config/chooser/models.py:143
|
||||||
#: /home/kovid/work/calibre/src/calibre/gui2/store/search/models.py:211
|
#: /home/kovid/work/calibre/src/calibre/gui2/store/search/models.py:215
|
||||||
#, python-format
|
#, python-format
|
||||||
msgid "Buying from this store supports the calibre developer: %s."
|
msgid "Buying from this store supports the calibre developer: %s."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
@ -12735,36 +12745,36 @@ msgstr ""
|
|||||||
msgid "Titl&e/Author/Price ..."
|
msgid "Titl&e/Author/Price ..."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: /home/kovid/work/calibre/src/calibre/gui2/store/search/models.py:37
|
#: /home/kovid/work/calibre/src/calibre/gui2/store/search/models.py:41
|
||||||
msgid "DRM"
|
msgid "DRM"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: /home/kovid/work/calibre/src/calibre/gui2/store/search/models.py:37
|
#: /home/kovid/work/calibre/src/calibre/gui2/store/search/models.py:41
|
||||||
msgid "Download"
|
msgid "Download"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: /home/kovid/work/calibre/src/calibre/gui2/store/search/models.py:37
|
#: /home/kovid/work/calibre/src/calibre/gui2/store/search/models.py:41
|
||||||
msgid "Price"
|
msgid "Price"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: /home/kovid/work/calibre/src/calibre/gui2/store/search/models.py:196
|
#: /home/kovid/work/calibre/src/calibre/gui2/store/search/models.py:200
|
||||||
#, python-format
|
#, python-format
|
||||||
msgid "Detected price as: %s. Check with the store before making a purchase to verify this price is correct. This price often does not include promotions the store may be running."
|
msgid "Detected price as: %s. Check with the store before making a purchase to verify this price is correct. This price often does not include promotions the store may be running."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: /home/kovid/work/calibre/src/calibre/gui2/store/search/models.py:199
|
#: /home/kovid/work/calibre/src/calibre/gui2/store/search/models.py:203
|
||||||
msgid "This book as been detected as having DRM restrictions. This book may not work with your reader and you will have limitations placed upon you as to what you can do with this book. Check with the store before making any purchases to ensure you can actually read this book."
|
msgid "This book as been detected as having DRM restrictions. This book may not work with your reader and you will have limitations placed upon you as to what you can do with this book. Check with the store before making any purchases to ensure you can actually read this book."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: /home/kovid/work/calibre/src/calibre/gui2/store/search/models.py:201
|
#: /home/kovid/work/calibre/src/calibre/gui2/store/search/models.py:205
|
||||||
msgid "This book has been detected as being DRM Free. You should be able to use this book on any device provided it is in a format calibre supports for conversion. However, before making a purchase double check the DRM status with the store. The store may not be disclosing the use of DRM."
|
msgid "This book has been detected as being DRM Free. You should be able to use this book on any device provided it is in a format calibre supports for conversion. However, before making a purchase double check the DRM status with the store. The store may not be disclosing the use of DRM."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: /home/kovid/work/calibre/src/calibre/gui2/store/search/models.py:203
|
#: /home/kovid/work/calibre/src/calibre/gui2/store/search/models.py:207
|
||||||
msgid "The DRM status of this book could not be determined. There is a very high likelihood that this book is actually DRM restricted."
|
msgid "The DRM status of this book could not be determined. There is a very high likelihood that this book is actually DRM restricted."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: /home/kovid/work/calibre/src/calibre/gui2/store/search/models.py:208
|
#: /home/kovid/work/calibre/src/calibre/gui2/store/search/models.py:212
|
||||||
#, python-format
|
#, python-format
|
||||||
msgid "The following formats can be downloaded directly: %s."
|
msgid "The following formats can be downloaded directly: %s."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
@ -13236,51 +13246,51 @@ msgstr ""
|
|||||||
msgid "will keep running in the system tray. To close it, choose <b>Quit</b> in the context menu of the system tray."
|
msgid "will keep running in the system tray. To close it, choose <b>Quit</b> in the context menu of the system tray."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: /home/kovid/work/calibre/src/calibre/gui2/update.py:73
|
#: /home/kovid/work/calibre/src/calibre/gui2/update.py:74
|
||||||
#, python-format
|
#, python-format
|
||||||
msgid "%(app)s has been updated to version <b>%(ver)s</b>. See the <a href=\"http://calibre-ebook.com/whats-new\">new features</a>."
|
msgid "%(app)s has been updated to version <b>%(ver)s</b>. See the <a href=\"http://calibre-ebook.com/whats-new\">new features</a>."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: /home/kovid/work/calibre/src/calibre/gui2/update.py:79
|
#: /home/kovid/work/calibre/src/calibre/gui2/update.py:80
|
||||||
msgid "Update available!"
|
msgid "Update available!"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: /home/kovid/work/calibre/src/calibre/gui2/update.py:84
|
#: /home/kovid/work/calibre/src/calibre/gui2/update.py:85
|
||||||
msgid "Show this notification for future updates"
|
msgid "Show this notification for future updates"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: /home/kovid/work/calibre/src/calibre/gui2/update.py:89
|
#: /home/kovid/work/calibre/src/calibre/gui2/update.py:90
|
||||||
msgid "&Get update"
|
msgid "&Get update"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: /home/kovid/work/calibre/src/calibre/gui2/update.py:93
|
#: /home/kovid/work/calibre/src/calibre/gui2/update.py:94
|
||||||
msgid "Update &plugins"
|
msgid "Update &plugins"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: /home/kovid/work/calibre/src/calibre/gui2/update.py:152
|
#: /home/kovid/work/calibre/src/calibre/gui2/update.py:153
|
||||||
#, python-format
|
#, python-format
|
||||||
msgid " (%d plugin updates)"
|
msgid " (%d plugin updates)"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: /home/kovid/work/calibre/src/calibre/gui2/update.py:155
|
#: /home/kovid/work/calibre/src/calibre/gui2/update.py:156
|
||||||
msgid "Update found"
|
msgid "Update found"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: /home/kovid/work/calibre/src/calibre/gui2/update.py:158
|
#: /home/kovid/work/calibre/src/calibre/gui2/update.py:159
|
||||||
msgid "updated plugins"
|
msgid "updated plugins"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: /home/kovid/work/calibre/src/calibre/gui2/update.py:184
|
#: /home/kovid/work/calibre/src/calibre/gui2/update.py:185
|
||||||
#: /home/kovid/work/calibre/src/calibre/gui2/update.py:189
|
#: /home/kovid/work/calibre/src/calibre/gui2/update.py:190
|
||||||
msgid "Plugin Updates"
|
msgid "Plugin Updates"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: /home/kovid/work/calibre/src/calibre/gui2/update.py:187
|
#: /home/kovid/work/calibre/src/calibre/gui2/update.py:188
|
||||||
#, python-format
|
#, python-format
|
||||||
msgid "There are %d plugin updates available"
|
msgid "There are %d plugin updates available"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: /home/kovid/work/calibre/src/calibre/gui2/update.py:191
|
#: /home/kovid/work/calibre/src/calibre/gui2/update.py:192
|
||||||
msgid "Install and configure user plugins"
|
msgid "Install and configure user plugins"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
@ -14935,17 +14945,17 @@ msgstr ""
|
|||||||
msgid "%(tt)sAverage rating is %(rating)3.1f"
|
msgid "%(tt)sAverage rating is %(rating)3.1f"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: /home/kovid/work/calibre/src/calibre/library/database2.py:3325
|
#: /home/kovid/work/calibre/src/calibre/library/database2.py:3330
|
||||||
#, python-format
|
#, python-format
|
||||||
msgid "<p>Migrating old database to ebook library in %s<br><center>"
|
msgid "<p>Migrating old database to ebook library in %s<br><center>"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: /home/kovid/work/calibre/src/calibre/library/database2.py:3354
|
#: /home/kovid/work/calibre/src/calibre/library/database2.py:3359
|
||||||
#, python-format
|
#, python-format
|
||||||
msgid "Copying <b>%s</b>"
|
msgid "Copying <b>%s</b>"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: /home/kovid/work/calibre/src/calibre/library/database2.py:3371
|
#: /home/kovid/work/calibre/src/calibre/library/database2.py:3376
|
||||||
msgid "Compacting database"
|
msgid "Compacting database"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
@ -16229,225 +16239,225 @@ msgid "Auto increment series index"
|
|||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: /home/kovid/work/calibre/resources/default_tweaks.py:13
|
#: /home/kovid/work/calibre/resources/default_tweaks.py:13
|
||||||
msgid "The algorithm used to assign a new book in an existing series a series number.\nNew series numbers assigned using this tweak are always integer values, except\nif a constant non-integer is specified.\nPossible values are:\nnext - First available integer larger than the largest existing number\nfirst_free - First available integer larger than 0\nnext_free - First available integer larger than the smallest existing number\nlast_free - First available integer smaller than the largest existing number\nReturn largest existing + 1 if no free number is found\nconst - Assign the number 1 always\na number - Assign that number always. The number is not in quotes. Note that\n0.0 can be used here.\nExamples:\nseries_index_auto_increment = 'next'\nseries_index_auto_increment = 'next_free'\nseries_index_auto_increment = 16.5"
|
msgid "The algorithm used to assign a book added to an existing series a series number.\nNew series numbers assigned using this tweak are always integer values, except\nif a constant non-integer is specified.\nPossible values are:\nnext - First available integer larger than the largest existing number\nfirst_free - First available integer larger than 0\nnext_free - First available integer larger than the smallest existing number\nlast_free - First available integer smaller than the largest existing number\nReturn largest existing + 1 if no free number is found\nconst - Assign the number 1 always\na number - Assign that number always. The number is not in quotes. Note that\n0.0 can be used here.\nExamples:\nseries_index_auto_increment = 'next'\nseries_index_auto_increment = 'next_free'\nseries_index_auto_increment = 16.5\n\nSet the use_series_auto_increment_tweak_when_importing tweak to True to\nuse the above values when importing/adding books. If this tweak is set to\nFalse (the default) then the series number will be set to 1 if it is not\nexplicitly set to during the import. If set to True, then the\nseries index will be set according to the series_index_auto_increment setting.\nNote that the use_series_auto_increment_tweak_when_importing tweak is used\nonly when a value is not provided during import. If the importing regular\nexpression produces a value for series_index, or if you are reading metadata\nfrom books and the import plugin produces a value, than that value will\nbe used irrespective of the setting of the tweak."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: /home/kovid/work/calibre/resources/default_tweaks.py:31
|
#: /home/kovid/work/calibre/resources/default_tweaks.py:43
|
||||||
msgid "Add separator after completing an author name"
|
msgid "Add separator after completing an author name"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: /home/kovid/work/calibre/resources/default_tweaks.py:32
|
#: /home/kovid/work/calibre/resources/default_tweaks.py:44
|
||||||
msgid "Should the completion separator be append\nto the end of the completed text to\nautomatically begin a new completion operation\nfor authors.\nCan be either True or False"
|
msgid "Should the completion separator be append\nto the end of the completed text to\nautomatically begin a new completion operation\nfor authors.\nCan be either True or False"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: /home/kovid/work/calibre/resources/default_tweaks.py:39
|
#: /home/kovid/work/calibre/resources/default_tweaks.py:51
|
||||||
msgid "Author sort name algorithm"
|
msgid "Author sort name algorithm"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: /home/kovid/work/calibre/resources/default_tweaks.py:40
|
#: /home/kovid/work/calibre/resources/default_tweaks.py:52
|
||||||
msgid "The algorithm used to copy author to author_sort\nPossible values are:\ninvert: use \"fn ln\" -> \"ln, fn\"\ncopy : copy author to author_sort without modification\ncomma : use 'copy' if there is a ',' in the name, otherwise use 'invert'\nnocomma : \"fn ln\" -> \"ln fn\" (without the comma)\nWhen this tweak is changed, the author_sort values stored with each author\nmust be recomputed by right-clicking on an author in the left-hand tags pane,\nselecting 'manage authors', and pressing 'Recalculate all author sort values'.\nThe author name suffixes are words that are ignored when they occur at the\nend of an author name. The case of the suffix is ignored and trailing\nperiods are automatically handled."
|
msgid "The algorithm used to copy author to author_sort\nPossible values are:\ninvert: use \"fn ln\" -> \"ln, fn\"\ncopy : copy author to author_sort without modification\ncomma : use 'copy' if there is a ',' in the name, otherwise use 'invert'\nnocomma : \"fn ln\" -> \"ln fn\" (without the comma)\nWhen this tweak is changed, the author_sort values stored with each author\nmust be recomputed by right-clicking on an author in the left-hand tags pane,\nselecting 'manage authors', and pressing 'Recalculate all author sort values'.\nThe author name suffixes are words that are ignored when they occur at the\nend of an author name. The case of the suffix is ignored and trailing\nperiods are automatically handled."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: /home/kovid/work/calibre/resources/default_tweaks.py:57
|
#: /home/kovid/work/calibre/resources/default_tweaks.py:69
|
||||||
msgid "Use author sort in Tag Browser"
|
msgid "Use author sort in Tag Browser"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: /home/kovid/work/calibre/resources/default_tweaks.py:58
|
#: /home/kovid/work/calibre/resources/default_tweaks.py:70
|
||||||
msgid "Set which author field to display in the tags pane (the list of authors,\nseries, publishers etc on the left hand side). The choices are author and\nauthor_sort. This tweak affects only what is displayed under the authors\ncategory in the tags pane and content server. Please note that if you set this\nto author_sort, it is very possible to see duplicate names in the list because\nalthough it is guaranteed that author names are unique, there is no such\nguarantee for author_sort values. Showing duplicates won't break anything, but\nit could lead to some confusion. When using 'author_sort', the tooltip will\nshow the author's name.\nExamples:\ncategories_use_field_for_author_name = 'author'\ncategories_use_field_for_author_name = 'author_sort'"
|
msgid "Set which author field to display in the tags pane (the list of authors,\nseries, publishers etc on the left hand side). The choices are author and\nauthor_sort. This tweak affects only what is displayed under the authors\ncategory in the tags pane and content server. Please note that if you set this\nto author_sort, it is very possible to see duplicate names in the list because\nalthough it is guaranteed that author names are unique, there is no such\nguarantee for author_sort values. Showing duplicates won't break anything, but\nit could lead to some confusion. When using 'author_sort', the tooltip will\nshow the author's name.\nExamples:\ncategories_use_field_for_author_name = 'author'\ncategories_use_field_for_author_name = 'author_sort'"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: /home/kovid/work/calibre/resources/default_tweaks.py:72
|
#: /home/kovid/work/calibre/resources/default_tweaks.py:84
|
||||||
msgid "Completion sort order: choose when to change from lexicographic to ASCII-like"
|
msgid "Completion sort order: choose when to change from lexicographic to ASCII-like"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: /home/kovid/work/calibre/resources/default_tweaks.py:73
|
#: /home/kovid/work/calibre/resources/default_tweaks.py:85
|
||||||
msgid "Calibre normally uses locale-dependent lexicographic ordering when showing\ncompletion values. This means that the sort order is correct for the user's\nlanguage. However, this can be slow. Performance is improved by switching to\nascii ordering. This tweak controls when that switch happens. Set it to zero\nto always use ascii ordering. Set it to something larger than zero to switch\nto ascii ordering for performance reasons."
|
msgid "Calibre normally uses locale-dependent lexicographic ordering when showing\ncompletion values. This means that the sort order is correct for the user's\nlanguage. However, this can be slow. Performance is improved by switching to\nascii ordering. This tweak controls when that switch happens. Set it to zero\nto always use ascii ordering. Set it to something larger than zero to switch\nto ascii ordering for performance reasons."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: /home/kovid/work/calibre/resources/default_tweaks.py:81
|
#: /home/kovid/work/calibre/resources/default_tweaks.py:93
|
||||||
msgid "Control partitioning of Tag Browser"
|
msgid "Control partitioning of Tag Browser"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: /home/kovid/work/calibre/resources/default_tweaks.py:82
|
#: /home/kovid/work/calibre/resources/default_tweaks.py:94
|
||||||
msgid "When partitioning the tags browser, the format of the subcategory label is\ncontrolled by a template: categories_collapsed_name_template if sorting by\nname, categories_collapsed_rating_template if sorting by average rating, and\ncategories_collapsed_popularity_template if sorting by popularity. There are\ntwo variables available to the template: first and last. The variable 'first'\nis the initial item in the subcategory, and the variable 'last' is the final\nitem in the subcategory. Both variables are 'objects'; they each have multiple\nvalues that are obtained by using a suffix. For example, first.name for an\nauthor category will be the name of the author. The sub-values available are:\nname: the printable name of the item\ncount: the number of books that references this item\navg_rating: the average rating of all the books referencing this item\nsort: the sort value. For authors, this is the author_sort for that author\ncategory: the category (e.g., authors, series) that the item is in.\nNote that the \"r'\" in front of the { is necessary if there are backslashes\n(\\ characters) in the template. It doesn't hurt anything to leave it there\neven if there aren't any backslashes."
|
msgid "When partitioning the tags browser, the format of the subcategory label is\ncontrolled by a template: categories_collapsed_name_template if sorting by\nname, categories_collapsed_rating_template if sorting by average rating, and\ncategories_collapsed_popularity_template if sorting by popularity. There are\ntwo variables available to the template: first and last. The variable 'first'\nis the initial item in the subcategory, and the variable 'last' is the final\nitem in the subcategory. Both variables are 'objects'; they each have multiple\nvalues that are obtained by using a suffix. For example, first.name for an\nauthor category will be the name of the author. The sub-values available are:\nname: the printable name of the item\ncount: the number of books that references this item\navg_rating: the average rating of all the books referencing this item\nsort: the sort value. For authors, this is the author_sort for that author\ncategory: the category (e.g., authors, series) that the item is in.\nNote that the \"r'\" in front of the { is necessary if there are backslashes\n(\\ characters) in the template. It doesn't hurt anything to leave it there\neven if there aren't any backslashes."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: /home/kovid/work/calibre/resources/default_tweaks.py:103
|
#: /home/kovid/work/calibre/resources/default_tweaks.py:115
|
||||||
msgid "Specify columns to sort the booklist by on startup"
|
msgid "Specify columns to sort the booklist by on startup"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: /home/kovid/work/calibre/resources/default_tweaks.py:104
|
#: /home/kovid/work/calibre/resources/default_tweaks.py:116
|
||||||
msgid "Provide a set of columns to be sorted on when calibre starts\nThe argument is None if saved sort history is to be used\notherwise it is a list of column,order pairs. Column is the\nlookup/search name, found using the tooltip for the column\nOrder is 0 for ascending, 1 for descending\nFor example, set it to [('authors',0),('title',0)] to sort by\ntitle within authors."
|
msgid "Provide a set of columns to be sorted on when calibre starts\nThe argument is None if saved sort history is to be used\notherwise it is a list of column,order pairs. Column is the\nlookup/search name, found using the tooltip for the column\nOrder is 0 for ascending, 1 for descending\nFor example, set it to [('authors',0),('title',0)] to sort by\ntitle within authors."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: /home/kovid/work/calibre/resources/default_tweaks.py:113
|
#: /home/kovid/work/calibre/resources/default_tweaks.py:125
|
||||||
msgid "Control how dates are displayed"
|
msgid "Control how dates are displayed"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: /home/kovid/work/calibre/resources/default_tweaks.py:114
|
#: /home/kovid/work/calibre/resources/default_tweaks.py:126
|
||||||
msgid "Format to be used for publication date and the timestamp (date).\nA string controlling how the publication date is displayed in the GUI\nd the day as number without a leading zero (1 to 31)\ndd the day as number with a leading zero (01 to 31)\nddd the abbreviated localized day name (e.g. 'Mon' to 'Sun').\ndddd the long localized day name (e.g. 'Monday' to 'Qt::Sunday').\nM the month as number without a leading zero (1-12)\nMM the month as number with a leading zero (01-12)\nMMM the abbreviated localized month name (e.g. 'Jan' to 'Dec').\nMMMM the long localized month name (e.g. 'January' to 'December').\nyy the year as two digit number (00-99)\nyyyy the year as four digit number\nFor example, given the date of 9 Jan 2010, the following formats show\nMMM yyyy ==> Jan 2010 yyyy ==> 2010 dd MMM yyyy ==> 09 Jan 2010\nMM/yyyy ==> 01/2010 d/M/yy ==> 9/1/10 yy ==> 10\npublication default if not set: MMM yyyy\ntimestamp default if not set: dd MMM yyyy"
|
msgid "Format to be used for publication date and the timestamp (date).\nA string controlling how the publication date is displayed in the GUI\nd the day as number without a leading zero (1 to 31)\ndd the day as number with a leading zero (01 to 31)\nddd the abbreviated localized day name (e.g. 'Mon' to 'Sun').\ndddd the long localized day name (e.g. 'Monday' to 'Qt::Sunday').\nM the month as number without a leading zero (1-12)\nMM the month as number with a leading zero (01-12)\nMMM the abbreviated localized month name (e.g. 'Jan' to 'Dec').\nMMMM the long localized month name (e.g. 'January' to 'December').\nyy the year as two digit number (00-99)\nyyyy the year as four digit number\nFor example, given the date of 9 Jan 2010, the following formats show\nMMM yyyy ==> Jan 2010 yyyy ==> 2010 dd MMM yyyy ==> 09 Jan 2010\nMM/yyyy ==> 01/2010 d/M/yy ==> 9/1/10 yy ==> 10\npublication default if not set: MMM yyyy\ntimestamp default if not set: dd MMM yyyy"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: /home/kovid/work/calibre/resources/default_tweaks.py:135
|
#: /home/kovid/work/calibre/resources/default_tweaks.py:147
|
||||||
msgid "Control sorting of titles and series in the library display"
|
msgid "Control sorting of titles and series in the library display"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: /home/kovid/work/calibre/resources/default_tweaks.py:136
|
#: /home/kovid/work/calibre/resources/default_tweaks.py:148
|
||||||
msgid "Control title and series sorting in the library view. If set to\n'library_order', the title sort field will be used instead of the title.\nUnless you have manually edited the title sort field, leading articles such as\nThe and A will be ignored. If set to 'strictly_alphabetic', the titles will be\nsorted as-is (sort by title instead of title sort). For example, with\nlibrary_order, The Client will sort under 'C'. With strictly_alphabetic, the\nbook will sort under 'T'.\nThis flag affects Calibre's library display. It has no effect on devices. In\naddition, titles for books added before changing the flag will retain their\norder until the title is edited. Double-clicking on a title and hitting return\nwithout changing anything is sufficient to change the sort."
|
msgid "Control title and series sorting in the library view. If set to\n'library_order', the title sort field will be used instead of the title.\nUnless you have manually edited the title sort field, leading articles such as\nThe and A will be ignored. If set to 'strictly_alphabetic', the titles will be\nsorted as-is (sort by title instead of title sort). For example, with\nlibrary_order, The Client will sort under 'C'. With strictly_alphabetic, the\nbook will sort under 'T'.\nThis flag affects Calibre's library display. It has no effect on devices. In\naddition, titles for books added before changing the flag will retain their\norder until the title is edited. Double-clicking on a title and hitting return\nwithout changing anything is sufficient to change the sort."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: /home/kovid/work/calibre/resources/default_tweaks.py:149
|
#: /home/kovid/work/calibre/resources/default_tweaks.py:161
|
||||||
msgid "Control formatting of title and series when used in templates"
|
msgid "Control formatting of title and series when used in templates"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: /home/kovid/work/calibre/resources/default_tweaks.py:150
|
#: /home/kovid/work/calibre/resources/default_tweaks.py:162
|
||||||
msgid "Control how title and series names are formatted when saving to disk/sending\nto device. The behavior depends on the field being processed. If processing\ntitle, then if this tweak is set to 'library_order', the title will be\nreplaced with title_sort. If it is set to 'strictly_alphabetic', then the\ntitle will not be changed. If processing series, then if set to\n'library_order', articles such as 'The' and 'An' will be moved to the end. If\nset to 'strictly_alphabetic', the series will be sent without change.\nFor example, if the tweak is set to library_order, \"The Lord of the Rings\"\nwill become \"Lord of the Rings, The\". If the tweak is set to\nstrictly_alphabetic, it would remain \"The Lord of the Rings\"."
|
msgid "Control how title and series names are formatted when saving to disk/sending\nto device. The behavior depends on the field being processed. If processing\ntitle, then if this tweak is set to 'library_order', the title will be\nreplaced with title_sort. If it is set to 'strictly_alphabetic', then the\ntitle will not be changed. If processing series, then if set to\n'library_order', articles such as 'The' and 'An' will be moved to the end. If\nset to 'strictly_alphabetic', the series will be sent without change.\nFor example, if the tweak is set to library_order, \"The Lord of the Rings\"\nwill become \"Lord of the Rings, The\". If the tweak is set to\nstrictly_alphabetic, it would remain \"The Lord of the Rings\"."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: /home/kovid/work/calibre/resources/default_tweaks.py:162
|
#: /home/kovid/work/calibre/resources/default_tweaks.py:174
|
||||||
msgid "Set the list of words considered to be \"articles\" for sort strings"
|
msgid "Set the list of words considered to be \"articles\" for sort strings"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: /home/kovid/work/calibre/resources/default_tweaks.py:163
|
#: /home/kovid/work/calibre/resources/default_tweaks.py:175
|
||||||
msgid "Set the list of words that are to be considered 'articles' when computing the\ntitle sort strings. The list is a regular expression, with the articles\nseparated by 'or' bars. Comparisons are case insensitive, and that cannot be\nchanged. Changes to this tweak won't have an effect until the book is modified\nin some way. If you enter an invalid pattern, it is silently ignored.\nTo disable use the expression: '^$'\nDefault: '^(A|The|An)\\s+'"
|
msgid "Set the list of words that are to be considered 'articles' when computing the\ntitle sort strings. The list is a regular expression, with the articles\nseparated by 'or' bars. Comparisons are case insensitive, and that cannot be\nchanged. Changes to this tweak won't have an effect until the book is modified\nin some way. If you enter an invalid pattern, it is silently ignored.\nTo disable use the expression: '^$'\nDefault: '^(A|The|An)\\s+'"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: /home/kovid/work/calibre/resources/default_tweaks.py:172
|
#: /home/kovid/work/calibre/resources/default_tweaks.py:184
|
||||||
msgid "Specify a folder calibre should connect to at startup"
|
msgid "Specify a folder calibre should connect to at startup"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: /home/kovid/work/calibre/resources/default_tweaks.py:173
|
#: /home/kovid/work/calibre/resources/default_tweaks.py:185
|
||||||
msgid "Specify a folder that calibre should connect to at startup using\nconnect_to_folder. This must be a full path to the folder. If the folder does\nnot exist when calibre starts, it is ignored. If there are '\\' characters in\nthe path (such as in Windows paths), you must double them.\nExamples:\nauto_connect_to_folder = 'C:\\\\Users\\\\someone\\\\Desktop\\\\testlib'\nauto_connect_to_folder = '/home/dropbox/My Dropbox/someone/library'"
|
msgid "Specify a folder that calibre should connect to at startup using\nconnect_to_folder. This must be a full path to the folder. If the folder does\nnot exist when calibre starts, it is ignored. If there are '\\' characters in\nthe path (such as in Windows paths), you must double them.\nExamples:\nauto_connect_to_folder = 'C:\\\\Users\\\\someone\\\\Desktop\\\\testlib'\nauto_connect_to_folder = '/home/dropbox/My Dropbox/someone/library'"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: /home/kovid/work/calibre/resources/default_tweaks.py:182
|
#: /home/kovid/work/calibre/resources/default_tweaks.py:194
|
||||||
msgid "Specify renaming rules for SONY collections"
|
msgid "Specify renaming rules for SONY collections"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: /home/kovid/work/calibre/resources/default_tweaks.py:183
|
#: /home/kovid/work/calibre/resources/default_tweaks.py:195
|
||||||
msgid "Specify renaming rules for sony collections. This tweak is only applicable if\nmetadata management is set to automatic. Collections on Sonys are named\ndepending upon whether the field is standard or custom. A collection derived\nfrom a standard field is named for the value in that field. For example, if\nthe standard 'series' column contains the value 'Darkover', then the\ncollection name is 'Darkover'. A collection derived from a custom field will\nhave the name of the field added to the value. For example, if a custom series\ncolumn named 'My Series' contains the name 'Darkover', then the collection\nwill by default be named 'Darkover (My Series)'. For purposes of this\ndocumentation, 'Darkover' is called the value and 'My Series' is called the\ncategory. If two books have fields that generate the same collection name,\nthen both books will be in that collection.\nThis set of tweaks lets you specify for a standard or custom field how\nthe collections are to be named. You can use it to add a description to a\nstandard field, for example 'Foo (Tag)' instead of the 'Foo'. You can also use\nit to force multiple fields to end up in the same collection. For example, you\ncould force the values in 'series', '#my_series_1', and '#my_series_2' to\nappear in collections named 'some_value (Series)', thereby merging all of the\nfields into one set of collections.\nThere are two related tweaks. The first determines the category name to use\nfor a metadata field. The second is a template, used to determines how the\nvalue and category are combined to create the collection name.\nThe syntax of the first tweak, sony_collection_renaming_rules, is:\n{'field_lookup_name':'category_name_to_use', 'lookup_name':'name', ...}\nThe second tweak, sony_collection_name_template, is a template. It uses the\nsame template language as plugboards and save templates. This tweak controls\nhow the value and category are combined together to make the collection name.\nThe only two fields available are {category} and {value}. The {value} field is\nnever empty. The {category} field can be empty. The default is to put the\nvalue first, then the category enclosed in parentheses, it is isn't empty:\n'{value} {category:|(|)}'\nExamples: The first three examples assume that the second tweak\nhas not been changed.\n1: I want three series columns to be merged into one set of collections. The\ncolumn lookup names are 'series', '#series_1' and '#series_2'. I want nothing\nin the parenthesis. The value to use in the tweak value would be:\nsony_collection_renaming_rules={'series':'', '#series_1':'', '#series_2':''}\n2: I want the word '(Series)' to appear on collections made from series, and\nthe word '(Tag)' to appear on collections made from tags. Use:\nsony_collection_renaming_rules={'series':'Series', 'tags':'Tag'}\n3: I want 'series' and '#myseries' to be merged, and for the collection name\nto have '(Series)' appended. The renaming rule is:\nsony_collection_renaming_rules={'series':'Series', '#myseries':'Series'}\n4: Same as example 2, but instead of having the category name in parentheses\nand appended to the value, I want it prepended and separated by a colon, such\nas in Series: Darkover. I must change the template used to format the category name\nThe resulting two tweaks are:\nsony_collection_renaming_rules={'series':'Series', 'tags':'Tag'}\nsony_collection_name_template='{category:||: }{value}'"
|
msgid "Specify renaming rules for sony collections. This tweak is only applicable if\nmetadata management is set to automatic. Collections on Sonys are named\ndepending upon whether the field is standard or custom. A collection derived\nfrom a standard field is named for the value in that field. For example, if\nthe standard 'series' column contains the value 'Darkover', then the\ncollection name is 'Darkover'. A collection derived from a custom field will\nhave the name of the field added to the value. For example, if a custom series\ncolumn named 'My Series' contains the name 'Darkover', then the collection\nwill by default be named 'Darkover (My Series)'. For purposes of this\ndocumentation, 'Darkover' is called the value and 'My Series' is called the\ncategory. If two books have fields that generate the same collection name,\nthen both books will be in that collection.\nThis set of tweaks lets you specify for a standard or custom field how\nthe collections are to be named. You can use it to add a description to a\nstandard field, for example 'Foo (Tag)' instead of the 'Foo'. You can also use\nit to force multiple fields to end up in the same collection. For example, you\ncould force the values in 'series', '#my_series_1', and '#my_series_2' to\nappear in collections named 'some_value (Series)', thereby merging all of the\nfields into one set of collections.\nThere are two related tweaks. The first determines the category name to use\nfor a metadata field. The second is a template, used to determines how the\nvalue and category are combined to create the collection name.\nThe syntax of the first tweak, sony_collection_renaming_rules, is:\n{'field_lookup_name':'category_name_to_use', 'lookup_name':'name', ...}\nThe second tweak, sony_collection_name_template, is a template. It uses the\nsame template language as plugboards and save templates. This tweak controls\nhow the value and category are combined together to make the collection name.\nThe only two fields available are {category} and {value}. The {value} field is\nnever empty. The {category} field can be empty. The default is to put the\nvalue first, then the category enclosed in parentheses, it is isn't empty:\n'{value} {category:|(|)}'\nExamples: The first three examples assume that the second tweak\nhas not been changed.\n1: I want three series columns to be merged into one set of collections. The\ncolumn lookup names are 'series', '#series_1' and '#series_2'. I want nothing\nin the parenthesis. The value to use in the tweak value would be:\nsony_collection_renaming_rules={'series':'', '#series_1':'', '#series_2':''}\n2: I want the word '(Series)' to appear on collections made from series, and\nthe word '(Tag)' to appear on collections made from tags. Use:\nsony_collection_renaming_rules={'series':'Series', 'tags':'Tag'}\n3: I want 'series' and '#myseries' to be merged, and for the collection name\nto have '(Series)' appended. The renaming rule is:\nsony_collection_renaming_rules={'series':'Series', '#myseries':'Series'}\n4: Same as example 2, but instead of having the category name in parentheses\nand appended to the value, I want it prepended and separated by a colon, such\nas in Series: Darkover. I must change the template used to format the category name\nThe resulting two tweaks are:\nsony_collection_renaming_rules={'series':'Series', 'tags':'Tag'}\nsony_collection_name_template='{category:||: }{value}'"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: /home/kovid/work/calibre/resources/default_tweaks.py:235
|
#: /home/kovid/work/calibre/resources/default_tweaks.py:247
|
||||||
msgid "Specify how SONY collections are sorted"
|
msgid "Specify how SONY collections are sorted"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: /home/kovid/work/calibre/resources/default_tweaks.py:236
|
#: /home/kovid/work/calibre/resources/default_tweaks.py:248
|
||||||
msgid "Specify how sony collections are sorted. This tweak is only applicable if\nmetadata management is set to automatic. You can indicate which metadata is to\nbe used to sort on a collection-by-collection basis. The format of the tweak\nis a list of metadata fields from which collections are made, followed by the\nname of the metadata field containing the sort value.\nExample: The following indicates that collections built from pubdate and tags\nare to be sorted by the value in the custom column '#mydate', that collections\nbuilt from 'series' are to be sorted by 'series_index', and that all other\ncollections are to be sorted by title. If a collection metadata field is not\nnamed, then if it is a series- based collection it is sorted by series order,\notherwise it is sorted by title order.\n[(['pubdate', 'tags'],'#mydate'), (['series'],'series_index'), (['*'], 'title')]\nNote that the bracketing and parentheses are required. The syntax is\n[ ( [list of fields], sort field ) , ( [ list of fields ] , sort field ) ]\nDefault: empty (no rules), so no collection attributes are named."
|
msgid "Specify how sony collections are sorted. This tweak is only applicable if\nmetadata management is set to automatic. You can indicate which metadata is to\nbe used to sort on a collection-by-collection basis. The format of the tweak\nis a list of metadata fields from which collections are made, followed by the\nname of the metadata field containing the sort value.\nExample: The following indicates that collections built from pubdate and tags\nare to be sorted by the value in the custom column '#mydate', that collections\nbuilt from 'series' are to be sorted by 'series_index', and that all other\ncollections are to be sorted by title. If a collection metadata field is not\nnamed, then if it is a series- based collection it is sorted by series order,\notherwise it is sorted by title order.\n[(['pubdate', 'tags'],'#mydate'), (['series'],'series_index'), (['*'], 'title')]\nNote that the bracketing and parentheses are required. The syntax is\n[ ( [list of fields], sort field ) , ( [ list of fields ] , sort field ) ]\nDefault: empty (no rules), so no collection attributes are named."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: /home/kovid/work/calibre/resources/default_tweaks.py:253
|
#: /home/kovid/work/calibre/resources/default_tweaks.py:265
|
||||||
msgid "Control how tags are applied when copying books to another library"
|
msgid "Control how tags are applied when copying books to another library"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: /home/kovid/work/calibre/resources/default_tweaks.py:254
|
#: /home/kovid/work/calibre/resources/default_tweaks.py:266
|
||||||
msgid "Set this to True to ensure that tags in 'Tags to add when adding\na book' are added when copying books to another library"
|
msgid "Set this to True to ensure that tags in 'Tags to add when adding\na book' are added when copying books to another library"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: /home/kovid/work/calibre/resources/default_tweaks.py:258
|
#: /home/kovid/work/calibre/resources/default_tweaks.py:270
|
||||||
msgid "Set the maximum number of tags to show per book in the content server"
|
msgid "Set the maximum number of tags to show per book in the content server"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: /home/kovid/work/calibre/resources/default_tweaks.py:261
|
#: /home/kovid/work/calibre/resources/default_tweaks.py:273
|
||||||
msgid "Set custom metadata fields that the content server will or will not display."
|
msgid "Set custom metadata fields that the content server will or will not display."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: /home/kovid/work/calibre/resources/default_tweaks.py:262
|
#: /home/kovid/work/calibre/resources/default_tweaks.py:274
|
||||||
msgid "content_server_will_display is a list of custom fields to be displayed.\ncontent_server_wont_display is a list of custom fields not to be displayed.\nwont_display has priority over will_display.\nThe special value '*' means all custom fields. The value [] means no entries.\nDefaults:\ncontent_server_will_display = ['*']\ncontent_server_wont_display = []\nExamples:\nTo display only the custom fields #mytags and #genre:\ncontent_server_will_display = ['#mytags', '#genre']\ncontent_server_wont_display = []\nTo display all fields except #mycomments:\ncontent_server_will_display = ['*']\ncontent_server_wont_display['#mycomments']"
|
msgid "content_server_will_display is a list of custom fields to be displayed.\ncontent_server_wont_display is a list of custom fields not to be displayed.\nwont_display has priority over will_display.\nThe special value '*' means all custom fields. The value [] means no entries.\nDefaults:\ncontent_server_will_display = ['*']\ncontent_server_wont_display = []\nExamples:\nTo display only the custom fields #mytags and #genre:\ncontent_server_will_display = ['#mytags', '#genre']\ncontent_server_wont_display = []\nTo display all fields except #mycomments:\ncontent_server_will_display = ['*']\ncontent_server_wont_display['#mycomments']"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: /home/kovid/work/calibre/resources/default_tweaks.py:279
|
#: /home/kovid/work/calibre/resources/default_tweaks.py:291
|
||||||
msgid "Set the maximum number of sort 'levels'"
|
msgid "Set the maximum number of sort 'levels'"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: /home/kovid/work/calibre/resources/default_tweaks.py:280
|
#: /home/kovid/work/calibre/resources/default_tweaks.py:292
|
||||||
msgid "Set the maximum number of sort 'levels' that calibre will use to resort the\nlibrary after certain operations such as searches or device insertion. Each\nsort level adds a performance penalty. If the database is large (thousands of\nbooks) the penalty might be noticeable. If you are not concerned about multi-\nlevel sorts, and if you are seeing a slowdown, reduce the value of this tweak."
|
msgid "Set the maximum number of sort 'levels' that calibre will use to resort the\nlibrary after certain operations such as searches or device insertion. Each\nsort level adds a performance penalty. If the database is large (thousands of\nbooks) the penalty might be noticeable. If you are not concerned about multi-\nlevel sorts, and if you are seeing a slowdown, reduce the value of this tweak."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: /home/kovid/work/calibre/resources/default_tweaks.py:287
|
#: /home/kovid/work/calibre/resources/default_tweaks.py:299
|
||||||
msgid "Specify which font to use when generating a default cover"
|
msgid "Specify which font to use when generating a default cover"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: /home/kovid/work/calibre/resources/default_tweaks.py:288
|
#: /home/kovid/work/calibre/resources/default_tweaks.py:300
|
||||||
msgid "Absolute path to .ttf font files to use as the fonts for the title, author\nand footer when generating a default cover. Useful if the default font (Liberation\nSerif) does not contain glyphs for the language of the books in your library."
|
msgid "Absolute path to .ttf font files to use as the fonts for the title, author\nand footer when generating a default cover. Useful if the default font (Liberation\nSerif) does not contain glyphs for the language of the books in your library."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: /home/kovid/work/calibre/resources/default_tweaks.py:294
|
#: /home/kovid/work/calibre/resources/default_tweaks.py:306
|
||||||
msgid "Control behavior of the book list"
|
msgid "Control behavior of the book list"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: /home/kovid/work/calibre/resources/default_tweaks.py:295
|
#: /home/kovid/work/calibre/resources/default_tweaks.py:307
|
||||||
msgid "You can control the behavior of doubleclicks on the books list.\nChoices: open_viewer, do_nothing,\nedit_cell, edit_metadata. Selecting edit_metadata has the side effect of\ndisabling editing a field using a single click.\nDefault: open_viewer.\nExample: doubleclick_on_library_view = 'do_nothing'\nYou can also control whether the book list scrolls horizontal per column or\nper pixel. Default is per column."
|
msgid "You can control the behavior of doubleclicks on the books list.\nChoices: open_viewer, do_nothing,\nedit_cell, edit_metadata. Selecting edit_metadata has the side effect of\ndisabling editing a field using a single click.\nDefault: open_viewer.\nExample: doubleclick_on_library_view = 'do_nothing'\nYou can also control whether the book list scrolls horizontal per column or\nper pixel. Default is per column."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: /home/kovid/work/calibre/resources/default_tweaks.py:306
|
#: /home/kovid/work/calibre/resources/default_tweaks.py:318
|
||||||
msgid "Language to use when sorting."
|
msgid "Language to use when sorting."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: /home/kovid/work/calibre/resources/default_tweaks.py:307
|
#: /home/kovid/work/calibre/resources/default_tweaks.py:319
|
||||||
msgid "Setting this tweak will force sorting to use the\ncollating order for the specified language. This might be useful if you run\ncalibre in English but want sorting to work in the language where you live.\nSet the tweak to the desired ISO 639-1 language code, in lower case.\nYou can find the list of supported locales at\nhttp://publib.boulder.ibm.com/infocenter/iseries/v5r3/topic/nls/rbagsicusortsequencetables.htm\nDefault: locale_for_sorting = '' -- use the language calibre displays in\nExample: locale_for_sorting = 'fr' -- sort using French rules.\nExample: locale_for_sorting = 'nb' -- sort using Norwegian rules."
|
msgid "Setting this tweak will force sorting to use the\ncollating order for the specified language. This might be useful if you run\ncalibre in English but want sorting to work in the language where you live.\nSet the tweak to the desired ISO 639-1 language code, in lower case.\nYou can find the list of supported locales at\nhttp://publib.boulder.ibm.com/infocenter/iseries/v5r3/topic/nls/rbagsicusortsequencetables.htm\nDefault: locale_for_sorting = '' -- use the language calibre displays in\nExample: locale_for_sorting = 'fr' -- sort using French rules.\nExample: locale_for_sorting = 'nb' -- sort using Norwegian rules."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: /home/kovid/work/calibre/resources/default_tweaks.py:318
|
#: /home/kovid/work/calibre/resources/default_tweaks.py:330
|
||||||
msgid "Number of columns for custom metadata in the edit metadata dialog"
|
msgid "Number of columns for custom metadata in the edit metadata dialog"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: /home/kovid/work/calibre/resources/default_tweaks.py:319
|
#: /home/kovid/work/calibre/resources/default_tweaks.py:331
|
||||||
msgid "Set whether to use one or two columns for custom metadata when editing\nmetadata one book at a time. If True, then the fields are laid out using two\ncolumns. If False, one column is used."
|
msgid "Set whether to use one or two columns for custom metadata when editing\nmetadata one book at a time. If True, then the fields are laid out using two\ncolumns. If False, one column is used."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: /home/kovid/work/calibre/resources/default_tweaks.py:324
|
#: /home/kovid/work/calibre/resources/default_tweaks.py:336
|
||||||
msgid "The number of seconds to wait before sending emails"
|
msgid "The number of seconds to wait before sending emails"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: /home/kovid/work/calibre/resources/default_tweaks.py:325
|
#: /home/kovid/work/calibre/resources/default_tweaks.py:337
|
||||||
msgid "The number of seconds to wait before sending emails when using a\npublic email server like gmail or hotmail. Default is: 5 minutes\nSetting it to lower may cause the server's SPAM controls to kick in,\nmaking email sending fail. Changes will take effect only after a restart of\ncalibre."
|
msgid "The number of seconds to wait before sending emails when using a\npublic email server like gmail or hotmail. Default is: 5 minutes\nSetting it to lower may cause the server's SPAM controls to kick in,\nmaking email sending fail. Changes will take effect only after a restart of\ncalibre."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: /home/kovid/work/calibre/resources/default_tweaks.py:332
|
#: /home/kovid/work/calibre/resources/default_tweaks.py:344
|
||||||
msgid "Remove the bright yellow lines at the edges of the book list"
|
msgid "Remove the bright yellow lines at the edges of the book list"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: /home/kovid/work/calibre/resources/default_tweaks.py:333
|
#: /home/kovid/work/calibre/resources/default_tweaks.py:345
|
||||||
msgid "Control whether the bright yellow lines at the edges of book list are drawn\nwhen a section of the user interface is hidden. Changes will take effect\nafter a restart of calibre."
|
msgid "Control whether the bright yellow lines at the edges of book list are drawn\nwhen a section of the user interface is hidden. Changes will take effect\nafter a restart of calibre."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: /home/kovid/work/calibre/resources/default_tweaks.py:338
|
#: /home/kovid/work/calibre/resources/default_tweaks.py:350
|
||||||
msgid "The maximum width and height for covers saved in the calibre library"
|
msgid "The maximum width and height for covers saved in the calibre library"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: /home/kovid/work/calibre/resources/default_tweaks.py:339
|
#: /home/kovid/work/calibre/resources/default_tweaks.py:351
|
||||||
msgid "All covers in the calibre library will be resized, preserving aspect ratio,\nto fit within this size. This is to prevent slowdowns caused by extremely\nlarge covers"
|
msgid "All covers in the calibre library will be resized, preserving aspect ratio,\nto fit within this size. This is to prevent slowdowns caused by extremely\nlarge covers"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: /home/kovid/work/calibre/resources/default_tweaks.py:344
|
#: /home/kovid/work/calibre/resources/default_tweaks.py:356
|
||||||
msgid "Where to send downloaded news"
|
msgid "Where to send downloaded news"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: /home/kovid/work/calibre/resources/default_tweaks.py:345
|
#: /home/kovid/work/calibre/resources/default_tweaks.py:357
|
||||||
msgid "When automatically sending downloaded news to a connected device, calibre\nwill by default send it to the main memory. By changing this tweak, you can\ncontrol where it is sent. Valid values are \"main\", \"carda\", \"cardb\". Note\nthat if there isn't enough free space available on the location you choose,\nthe files will be sent to the location with the most free space."
|
msgid "When automatically sending downloaded news to a connected device, calibre\nwill by default send it to the main memory. By changing this tweak, you can\ncontrol where it is sent. Valid values are \"main\", \"carda\", \"cardb\". Note\nthat if there isn't enough free space available on the location you choose,\nthe files will be sent to the location with the most free space."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: /home/kovid/work/calibre/resources/default_tweaks.py:352
|
#: /home/kovid/work/calibre/resources/default_tweaks.py:364
|
||||||
msgid "What interfaces should the content server listen on"
|
msgid "What interfaces should the content server listen on"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: /home/kovid/work/calibre/resources/default_tweaks.py:353
|
#: /home/kovid/work/calibre/resources/default_tweaks.py:365
|
||||||
msgid "By default, the calibre content server listens on '0.0.0.0' which means that it\naccepts IPv4 connections on all interfaces. You can change this to, for\nexample, '127.0.0.1' to only listen for connections from the local machine, or\nto '::' to listen to all incoming IPv6 and IPv4 connections (this may not\nwork on all operating systems)"
|
msgid "By default, the calibre content server listens on '0.0.0.0' which means that it\naccepts IPv4 connections on all interfaces. You can change this to, for\nexample, '127.0.0.1' to only listen for connections from the local machine, or\nto '::' to listen to all incoming IPv6 and IPv4 connections (this may not\nwork on all operating systems)"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: /home/kovid/work/calibre/resources/default_tweaks.py:360
|
#: /home/kovid/work/calibre/resources/default_tweaks.py:372
|
||||||
msgid "Unified toolbar on OS X"
|
msgid "Unified toolbar on OS X"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: /home/kovid/work/calibre/resources/default_tweaks.py:361
|
#: /home/kovid/work/calibre/resources/default_tweaks.py:373
|
||||||
msgid "If you enable this option and restart calibre, the toolbar will be 'unified'\nwith the titlebar as is normal for OS X applications. However, doing this has\nvarious bugs, for instance the minimum width of the toolbar becomes twice\nwhat it should be and it causes other random bugs on some systems, so turn it\non at your own risk!"
|
msgid "If you enable this option and restart calibre, the toolbar will be 'unified'\nwith the titlebar as is normal for OS X applications. However, doing this has\nvarious bugs, for instance the minimum width of the toolbar becomes twice\nwhat it should be and it causes other random bugs on some systems, so turn it\non at your own risk!"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: /home/kovid/work/calibre/resources/default_tweaks.py:368
|
#: /home/kovid/work/calibre/resources/default_tweaks.py:380
|
||||||
msgid "Save original file when converting from same format to same format"
|
msgid "Save original file when converting from same format to same format"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: /home/kovid/work/calibre/resources/default_tweaks.py:369
|
#: /home/kovid/work/calibre/resources/default_tweaks.py:381
|
||||||
msgid "When calibre does a conversion from the same format to the same format, for\nexample, from EPUB to EPUB, the original file is saved, so that in case the\nconversion is poor, you can tweak the settings and run it again. By setting\nthis to False you can prevent calibre from saving the original file."
|
msgid "When calibre does a conversion from the same format to the same format, for\nexample, from EPUB to EPUB, the original file is saved, so that in case the\nconversion is poor, you can tweak the settings and run it again. By setting\nthis to False you can prevent calibre from saving the original file."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@ -140,6 +140,7 @@ _extra_lang_codes = {
|
|||||||
'es_VE' : _('Spanish (Venezuela)'),
|
'es_VE' : _('Spanish (Venezuela)'),
|
||||||
'es_BO' : _('Spanish (Bolivia)'),
|
'es_BO' : _('Spanish (Bolivia)'),
|
||||||
'es_NI' : _('Spanish (Nicaragua)'),
|
'es_NI' : _('Spanish (Nicaragua)'),
|
||||||
|
'es_CO' : _('Spanish (Colombia)'),
|
||||||
'de_AT' : _('German (AT)'),
|
'de_AT' : _('German (AT)'),
|
||||||
'fr_BE' : _('French (BE)'),
|
'fr_BE' : _('French (BE)'),
|
||||||
'nl' : _('Dutch (NL)'),
|
'nl' : _('Dutch (NL)'),
|
||||||
|
Loading…
x
Reference in New Issue
Block a user