Merge from trunk

This commit is contained in:
Charles Haley 2012-03-16 19:55:19 +01:00
commit 00ffb87d2e
91 changed files with 26318 additions and 24797 deletions

View File

@ -19,6 +19,49 @@
# new recipes:
# - title:
- version: 0.8.43
date: 2012-03-16
new features:
- title: "Template language: Speedup evaluation of general program mode templates by pre-compiling them to python. If you experience errors with this optimization, you can turn it off via Preferences->Tweaks. Also other miscellaneous optimizations in evaluating templates with composite columns."
- title: "MOBI Output: Add an option to not convert all images to JPEG when creating MOBI files. For maximum compatibility of the produced MOBI files, do not use this option."
tickets: [954025]
- title: "Add iPad3 Output Profile"
bug fixes:
- title: "KF8 Input: Add support for KF8 files with obfuscated embedded fonts"
tickets: [953260]
- title: "Make the stars in the book list a little larger on windows >= vista"
- title: "Revised periodical Section layout, for touchscreen devices resolving iBooks problem with tables spanning multiple pages"
- title: "Read dc:contributor metadata from MOBI files"
- title: "MOBI Output: Fix a regression that caused the generated thumbnail embedded in calibre produced MOBI files to be a large, low quality image instead of a small, high quality image. You would have been affected by this bug only if you directly used the output from calibre, without exporting it via send to device or save to disk."
tickets: [954254]
- title: "KF8 Input: Recognize OpenType embedded fonts as well."
tickets: [954728]
- title: "Fix regression in 0.8.41 that caused file:/// URLs to stop working in the news download system on windows."
tickets: [955581]
- title: "When setting metadata in MOBI files fix cover not being updated if the mobi file has its first image record as the cover"
- title: "Fix column coloring rules based on the size column not working"
tickets: [953737]
improved recipes:
- Microwaves and RF
- idg.se
new recipes:
- title: SatMagazine
author: kiavash
- version: 0.8.42
date: 2012-03-12

View File

@ -8,14 +8,14 @@ msgstr ""
"Project-Id-Version: calibre\n"
"Report-Msgid-Bugs-To: FULL NAME <EMAIL@ADDRESS>\n"
"POT-Creation-Date: 2011-11-25 14:01+0000\n"
"PO-Revision-Date: 2011-12-17 09:29+0000\n"
"PO-Revision-Date: 2012-03-11 10:13+0000\n"
"Last-Translator: Jellby <Unknown>\n"
"Language-Team: Spanish <es@li.org>\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"X-Launchpad-Export-Date: 2011-12-18 04:37+0000\n"
"X-Generator: Launchpad (build 14525)\n"
"X-Launchpad-Export-Date: 2012-03-12 04:38+0000\n"
"X-Generator: Launchpad (build 14933)\n"
#. name for aaa
msgid "Ghotuo"
@ -1779,7 +1779,7 @@ msgstr "Awiyaana"
#. name for auz
msgid "Arabic; Uzbeki"
msgstr "Árabe uzbeco"
msgstr "Árabe uzbeko"
#. name for ava
msgid "Avaric"
@ -22207,7 +22207,7 @@ msgstr "Roglai septentrional"
#. name for roh
msgid "Romansh"
msgstr ""
msgstr "Romanche"
#. name for rol
msgid "Romblomanon"
@ -22607,7 +22607,7 @@ msgstr ""
#. name for sci
msgid "Creole Malay; Sri Lankan"
msgstr "Malo criollo de Sri Lanka"
msgstr "Malayo criollo de Sri Lanka"
#. name for sck
msgid "Sadri"
@ -26987,15 +26987,15 @@ msgstr ""
#. name for uzb
msgid "Uzbek"
msgstr "Uzbeco"
msgstr "Uzbeko"
#. name for uzn
msgid "Uzbek; Northern"
msgstr "Uzbeco septentrional"
msgstr "Uzbeko septentrional"
#. name for uzs
msgid "Uzbek; Southern"
msgstr "Uzbeco meridional"
msgstr "Uzbeko meridional"
#. name for vaa
msgid "Vaagri Booli"
@ -30319,7 +30319,7 @@ msgstr ""
#. name for zhn
msgid "Zhuang; Nong"
msgstr "Zhuang nong"
msgstr "Chuang nong"
#. name for zho
msgid "Chinese"

View File

@ -8,31 +8,31 @@ msgstr ""
"Report-Msgid-Bugs-To: Debian iso-codes team <pkg-isocodes-"
"devel@lists.alioth.debian.org>\n"
"POT-Creation-Date: 2011-11-25 14:01+0000\n"
"PO-Revision-Date: 2012-03-03 21:35+0000\n"
"PO-Revision-Date: 2012-03-14 21:30+0000\n"
"Last-Translator: Иван Старчевић <ivanstar61@gmail.com>\n"
"Language-Team: Serbian <gnu@prevod.org>\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"X-Launchpad-Export-Date: 2012-03-04 04:59+0000\n"
"X-Generator: Launchpad (build 14886)\n"
"X-Launchpad-Export-Date: 2012-03-15 04:45+0000\n"
"X-Generator: Launchpad (build 14933)\n"
"Language: sr\n"
#. name for aaa
msgid "Ghotuo"
msgstr "Ghotuo"
msgstr "Готуо"
#. name for aab
msgid "Alumu-Tesu"
msgstr "Alumu-Tesu"
msgstr "Алуму-Тесу"
#. name for aac
msgid "Ari"
msgstr ""
msgstr "Ари"
#. name for aad
msgid "Amal"
msgstr "Amal"
msgstr "Амал"
#. name for aae
msgid "Albanian; Arbëreshë"
@ -52,7 +52,7 @@ msgstr "Арабеш; Абу'"
#. name for aai
msgid "Arifama-Miniafia"
msgstr "Орифама-Миниафиа"
msgstr "Арифама-Миниафиа"
#. name for aak
msgid "Ankave"
@ -60,15 +60,15 @@ msgstr "Анкаве"
#. name for aal
msgid "Afade"
msgstr ""
msgstr "Афаде"
#. name for aam
msgid "Aramanik"
msgstr ""
msgstr "Араманик"
#. name for aan
msgid "Anambé"
msgstr ""
msgstr "Анамбе"
#. name for aao
msgid "Arabic; Algerian Saharan"
@ -76,11 +76,11 @@ msgstr "Арапски; Алжирска Сахара"
#. name for aap
msgid "Arára; Pará"
msgstr ""
msgstr "Арара;Пара"
#. name for aaq
msgid "Abnaki; Eastern"
msgstr ""
msgstr "Абнаки;Источни"
#. name for aar
msgid "Afar"
@ -88,39 +88,39 @@ msgstr "Афар"
#. name for aas
msgid "Aasáx"
msgstr ""
msgstr "Асакс"
#. name for aat
msgid "Albanian; Arvanitika"
msgstr ""
msgstr "Албански (арванитска)"
#. name for aau
msgid "Abau"
msgstr ""
msgstr "Абау"
#. name for aaw
msgid "Solong"
msgstr ""
msgstr "Солонг"
#. name for aax
msgid "Mandobo Atas"
msgstr ""
msgstr "Мандобо Атас"
#. name for aaz
msgid "Amarasi"
msgstr ""
msgstr "Амараси"
#. name for aba
msgid "Abé"
msgstr ""
msgstr "Абе"
#. name for abb
msgid "Bankon"
msgstr ""
msgstr "Банкон"
#. name for abc
msgid "Ayta; Ambala"
msgstr ""
msgstr "Аита;Амбала"
#. name for abd
msgid "Manide"
@ -132,11 +132,11 @@ msgstr "Абнаки; Западни"
#. name for abf
msgid "Abai Sungai"
msgstr ""
msgstr "Абаи Сунгаи"
#. name for abg
msgid "Abaga"
msgstr ""
msgstr "Абага"
#. name for abh
msgid "Arabic; Tajiki"
@ -148,7 +148,7 @@ msgstr "Абиџи"
#. name for abj
msgid "Aka-Bea"
msgstr ""
msgstr "Ака-Беа"
#. name for abk
msgid "Abkhazian"
@ -156,31 +156,31 @@ msgstr "Абхазијски"
#. name for abl
msgid "Lampung Nyo"
msgstr ""
msgstr "Лампунг Нио"
#. name for abm
msgid "Abanyom"
msgstr ""
msgstr "Абањјом"
#. name for abn
msgid "Abua"
msgstr ""
msgstr "Абуа"
#. name for abo
msgid "Abon"
msgstr ""
msgstr "Абон"
#. name for abp
msgid "Ayta; Abellen"
msgstr ""
msgstr "Ајта (абелијска)"
#. name for abq
msgid "Abaza"
msgstr ""
msgstr "Абаза"
#. name for abr
msgid "Abron"
msgstr ""
msgstr "Аброн"
#. name for abs
msgid "Malay; Ambonese"
@ -188,43 +188,43 @@ msgstr "Малајски; Амбонијски"
#. name for abt
msgid "Ambulas"
msgstr ""
msgstr "Амбулас"
#. name for abu
msgid "Abure"
msgstr ""
msgstr "Абуре"
#. name for abv
msgid "Arabic; Baharna"
msgstr ""
msgstr "Арапски (Бахреин)"
#. name for abw
msgid "Pal"
msgstr ""
msgstr "Пал"
#. name for abx
msgid "Inabaknon"
msgstr ""
msgstr "Инабакнон"
#. name for aby
msgid "Aneme Wake"
msgstr ""
msgstr "Анем Ваке"
#. name for abz
msgid "Abui"
msgstr ""
msgstr "Абуи"
#. name for aca
msgid "Achagua"
msgstr ""
msgstr "Ачагуа"
#. name for acb
msgid "Áncá"
msgstr ""
msgstr "Анка"
#. name for acd
msgid "Gikyode"
msgstr ""
msgstr "Гикиод"
#. name for ace
msgid "Achinese"
@ -240,123 +240,123 @@ msgstr "Аколи"
#. name for aci
msgid "Aka-Cari"
msgstr ""
msgstr "Ака-Кари"
#. name for ack
msgid "Aka-Kora"
msgstr ""
msgstr "Ака-Кора"
#. name for acl
msgid "Akar-Bale"
msgstr ""
msgstr "Акар-Бале"
#. name for acm
msgid "Arabic; Mesopotamian"
msgstr ""
msgstr "Арапски (Месопотамија)"
#. name for acn
msgid "Achang"
msgstr ""
msgstr "Ачанг"
#. name for acp
msgid "Acipa; Eastern"
msgstr ""
msgstr "Акипа;Источни"
#. name for acq
msgid "Arabic; Ta'izzi-Adeni"
msgstr ""
msgstr "Арапски; Северни Јемен"
#. name for acr
msgid "Achi"
msgstr ""
msgstr "Ачи"
#. name for acs
msgid "Acroá"
msgstr ""
msgstr "Акроа"
#. name for act
msgid "Achterhoeks"
msgstr ""
msgstr "Ахтерхекс"
#. name for acu
msgid "Achuar-Shiwiar"
msgstr ""
msgstr "Ачуар-Шивиар"
#. name for acv
msgid "Achumawi"
msgstr ""
msgstr "Ачумави"
#. name for acw
msgid "Arabic; Hijazi"
msgstr ""
msgstr "Арапски;Хиџази"
#. name for acx
msgid "Arabic; Omani"
msgstr ""
msgstr "Арапски;Оман"
#. name for acy
msgid "Arabic; Cypriot"
msgstr ""
msgstr "Арапски;Кипар"
#. name for acz
msgid "Acheron"
msgstr ""
msgstr "Ачерон"
#. name for ada
msgid "Adangme"
msgstr "адангме"
msgstr "Адангме"
#. name for adb
msgid "Adabe"
msgstr ""
msgstr "Адабе"
#. name for add
msgid "Dzodinka"
msgstr ""
msgstr "Ђодинка"
#. name for ade
msgid "Adele"
msgstr ""
msgstr "Аделе"
#. name for adf
msgid "Arabic; Dhofari"
msgstr ""
msgstr "Арапски;Дофари"
#. name for adg
msgid "Andegerebinha"
msgstr ""
msgstr "Андегеребина"
#. name for adh
msgid "Adhola"
msgstr ""
msgstr "Адола"
#. name for adi
msgid "Adi"
msgstr ""
msgstr "Ади"
#. name for adj
msgid "Adioukrou"
msgstr ""
msgstr "Адиокру"
#. name for adl
msgid "Galo"
msgstr ""
msgstr "Гало"
#. name for adn
msgid "Adang"
msgstr ""
msgstr "Аданг"
#. name for ado
msgid "Abu"
msgstr ""
msgstr "Абу"
#. name for adp
msgid "Adap"
msgstr ""
msgstr "Адап"
#. name for adq
msgid "Adangbe"
msgstr ""
msgstr "Адангбе"
#. name for adr
msgid "Adonara"
@ -364,59 +364,59 @@ msgstr ""
#. name for ads
msgid "Adamorobe Sign Language"
msgstr ""
msgstr "Адамороб знаковни језик"
#. name for adt
msgid "Adnyamathanha"
msgstr ""
msgstr "Адњаматана"
#. name for adu
msgid "Aduge"
msgstr ""
msgstr "Адуге"
#. name for adw
msgid "Amundava"
msgstr ""
msgstr "Амундава"
#. name for adx
msgid "Tibetan; Amdo"
msgstr ""
msgstr "Тибетански;Амдо"
#. name for ady
msgid "Adyghe"
msgstr ""
msgstr "Адиге"
#. name for adz
msgid "Adzera"
msgstr ""
msgstr "Адзера"
#. name for aea
msgid "Areba"
msgstr ""
msgstr "Ареба"
#. name for aeb
msgid "Arabic; Tunisian"
msgstr ""
msgstr "Арапски;Туниски"
#. name for aec
msgid "Arabic; Saidi"
msgstr ""
msgstr "Арапски (Горњи Египат)"
#. name for aed
msgid "Argentine Sign Language"
msgstr ""
msgstr "Аргентински знаковни језик"
#. name for aee
msgid "Pashayi; Northeast"
msgstr ""
msgstr "Пашаи (североисточни)"
#. name for aek
msgid "Haeke"
msgstr ""
msgstr "Хаеке"
#. name for ael
msgid "Ambele"
msgstr ""
msgstr "Амбеле"
#. name for aem
msgid "Arem"
@ -460,15 +460,15 @@ msgstr ""
#. name for afd
msgid "Andai"
msgstr ""
msgstr "Андаи"
#. name for afe
msgid "Putukwam"
msgstr ""
msgstr "Путуквам"
#. name for afg
msgid "Afghan Sign Language"
msgstr ""
msgstr "Афганистански знаковни језик"
#. name for afh
msgid "Afrihili"
@ -476,7 +476,7 @@ msgstr "африхили"
#. name for afi
msgid "Akrukay"
msgstr ""
msgstr "Акрукај"
#. name for afk
msgid "Nanubae"
@ -484,15 +484,15 @@ msgstr ""
#. name for afn
msgid "Defaka"
msgstr ""
msgstr "Дефака"
#. name for afo
msgid "Eloyi"
msgstr ""
msgstr "Елоји"
#. name for afp
msgid "Tapei"
msgstr ""
msgstr "Тапеи"
#. name for afr
msgid "Afrikaans"
@ -500,51 +500,51 @@ msgstr "африканс"
#. name for afs
msgid "Creole; Afro-Seminole"
msgstr ""
msgstr "Креолски;Афричко-Семинолслки"
#. name for aft
msgid "Afitti"
msgstr ""
msgstr "Афити"
#. name for afu
msgid "Awutu"
msgstr ""
msgstr "Авуту"
#. name for afz
msgid "Obokuitai"
msgstr ""
msgstr "Обокуитаи"
#. name for aga
msgid "Aguano"
msgstr ""
msgstr "Агвано"
#. name for agb
msgid "Legbo"
msgstr ""
msgstr "Легбо"
#. name for agc
msgid "Agatu"
msgstr ""
msgstr "Агату"
#. name for agd
msgid "Agarabi"
msgstr ""
msgstr "Агараби"
#. name for age
msgid "Angal"
msgstr ""
msgstr "Ангал"
#. name for agf
msgid "Arguni"
msgstr ""
msgstr "Аргуни"
#. name for agg
msgid "Angor"
msgstr ""
msgstr "Ангор"
#. name for agh
msgid "Ngelima"
msgstr ""
msgstr "Нгелима"
#. name for agi
msgid "Agariya"
@ -588,15 +588,15 @@ msgstr ""
#. name for agt
msgid "Agta; Central Cagayan"
msgstr ""
msgstr "Агта;Централно Кагајански"
#. name for agu
msgid "Aguacateco"
msgstr ""
msgstr "Агвакатеко"
#. name for agv
msgid "Dumagat; Remontado"
msgstr ""
msgstr "Думагат;Ремонтадо"
#. name for agw
msgid "Kahua"
@ -604,27 +604,27 @@ msgstr ""
#. name for agx
msgid "Aghul"
msgstr ""
msgstr "Агхул"
#. name for agy
msgid "Alta; Southern"
msgstr ""
msgstr "Алта;Јужни"
#. name for agz
msgid "Agta; Mt. Iriga"
msgstr ""
msgstr "Агта;Мт.Ирига"
#. name for aha
msgid "Ahanta"
msgstr ""
msgstr "Аханта"
#. name for ahb
msgid "Axamb"
msgstr ""
msgstr "Аксамб"
#. name for ahg
msgid "Qimant"
msgstr ""
msgstr "Кимант"
#. name for ahh
msgid "Aghu"
@ -668,95 +668,95 @@ msgstr ""
#. name for aht
msgid "Ahtena"
msgstr ""
msgstr "Ахтена"
#. name for aia
msgid "Arosi"
msgstr ""
msgstr "Ароси"
#. name for aib
msgid "Ainu (China)"
msgstr ""
msgstr "Аину(Кина)"
#. name for aic
msgid "Ainbai"
msgstr ""
msgstr "Аинбаи"
#. name for aid
msgid "Alngith"
msgstr ""
msgstr "Алнгит"
#. name for aie
msgid "Amara"
msgstr ""
msgstr "Амара"
#. name for aif
msgid "Agi"
msgstr ""
msgstr "Аги"
#. name for aig
msgid "Creole English; Antigua and Barbuda"
msgstr ""
msgstr "Креолски Енглески;Антигва и Барбуда"
#. name for aih
msgid "Ai-Cham"
msgstr ""
msgstr "Аи-Чам"
#. name for aii
msgid "Neo-Aramaic; Assyrian"
msgstr ""
msgstr "Ново-Арамејски;Асирски"
#. name for aij
msgid "Lishanid Noshan"
msgstr ""
msgstr "Лианид Ношан"
#. name for aik
msgid "Ake"
msgstr ""
msgstr "Аке"
#. name for ail
msgid "Aimele"
msgstr ""
msgstr "Ајмеле"
#. name for aim
msgid "Aimol"
msgstr ""
msgstr "Ајмол"
#. name for ain
msgid "Ainu (Japan)"
msgstr ""
msgstr "Аину(Јапан)"
#. name for aio
msgid "Aiton"
msgstr ""
msgstr "Аитон"
#. name for aip
msgid "Burumakok"
msgstr ""
msgstr "Бурумакок"
#. name for aiq
msgid "Aimaq"
msgstr ""
msgstr "Ајмак"
#. name for air
msgid "Airoran"
msgstr ""
msgstr "Ајроран"
#. name for ais
msgid "Amis; Nataoran"
msgstr ""
msgstr "Амис;Натаоран"
#. name for ait
msgid "Arikem"
msgstr ""
msgstr "Арикем"
#. name for aiw
msgid "Aari"
msgstr ""
msgstr "Аари"
#. name for aix
msgid "Aighon"
msgstr ""
msgstr "Аигхон"
#. name for aiy
msgid "Ali"
@ -764,35 +764,35 @@ msgstr ""
#. name for aja
msgid "Aja (Sudan)"
msgstr ""
msgstr "Аја(Судан)"
#. name for ajg
msgid "Aja (Benin)"
msgstr ""
msgstr "Аја(Бенин)"
#. name for aji
msgid "Ajië"
msgstr ""
msgstr "Ајие"
#. name for ajp
msgid "Arabic; South Levantine"
msgstr ""
msgstr "Арапски;Јужно-Левантински"
#. name for ajt
msgid "Arabic; Judeo-Tunisian"
msgstr ""
msgstr "Арапски;Јудео-Туниски"
#. name for aju
msgid "Arabic; Judeo-Moroccan"
msgstr ""
msgstr "Арапски;Јудео-Марокански"
#. name for ajw
msgid "Ajawa"
msgstr ""
msgstr "Ајава"
#. name for ajz
msgid "Karbi; Amri"
msgstr ""
msgstr "Карби;Амри"
#. name for aka
msgid "Akan"
@ -800,35 +800,35 @@ msgstr "акан"
#. name for akb
msgid "Batak Angkola"
msgstr ""
msgstr "Батак Ангкола"
#. name for akc
msgid "Mpur"
msgstr ""
msgstr "Мпур"
#. name for akd
msgid "Ukpet-Ehom"
msgstr ""
msgstr "Укпет-Ехом"
#. name for ake
msgid "Akawaio"
msgstr ""
msgstr "Акавајо"
#. name for akf
msgid "Akpa"
msgstr ""
msgstr "Акипа"
#. name for akg
msgid "Anakalangu"
msgstr ""
msgstr "Анакалангу"
#. name for akh
msgid "Angal Heneng"
msgstr ""
msgstr "Ангал Хененг"
#. name for aki
msgid "Aiome"
msgstr ""
msgstr "Ајоме"
#. name for akj
msgid "Aka-Jeru"

View File

@ -4,7 +4,7 @@ __license__ = 'GPL v3'
__copyright__ = '2008, Kovid Goyal kovid@kovidgoyal.net'
__docformat__ = 'restructuredtext en'
__appname__ = u'calibre'
numeric_version = (0, 8, 42)
numeric_version = (0, 8, 43)
__version__ = u'.'.join(map(unicode, numeric_version))
__author__ = u"Kovid Goyal <kovid@kovidgoyal.net>"

View File

@ -379,6 +379,7 @@ class iPadOutput(OutputProfile):
/* Feed summary formatting */
.article_summary {
display:inline-block;
padding-bottom:0.5em;
}
.feed {
font-family:sans-serif;
@ -431,6 +432,15 @@ class iPadOutput(OutputProfile):
'''
# }}}
class iPad3Output(iPadOutput):
screen_size = comic_screen_size = (2048, 1536)
dpi = 264.0
name = 'iPad 3'
short_name = 'ipad3'
description = _('Intended for the iPad 3 and similar devices with a '
'resolution of 1536x2048')
class TabletOutput(iPadOutput):
name = 'Tablet'
short_name = 'tablet'
@ -754,7 +764,7 @@ class PocketBook900Output(OutputProfile):
output_profiles = [OutputProfile, SonyReaderOutput, SonyReader300Output,
SonyReader900Output, MSReaderOutput, MobipocketOutput, HanlinV3Output,
HanlinV5Output, CybookG3Output, CybookOpusOutput, KindleOutput,
iPadOutput, KoboReaderOutput, TabletOutput, SamsungGalaxy,
iPadOutput, iPad3Output, KoboReaderOutput, TabletOutput, SamsungGalaxy,
SonyReaderLandscapeOutput, KindleDXOutput, IlliadOutput,
IRexDR1000Output, IRexDR800Output, JetBook5Output, NookOutput,
BambookOutput, NookColorOutput, PocketBook900Output, GenericEink,

View File

@ -51,8 +51,9 @@ Run an embedded python interpreter.
'with sqlite3 works.')
parser.add_option('-p', '--py-console', help='Run python console',
default=False, action='store_true')
parser.add_option('-m', '--inspect-mobi',
help='Inspect the MOBI file at the specified path', default=None)
parser.add_option('-m', '--inspect-mobi', action='store_true',
default=False,
help='Inspect the MOBI file(s) at the specified path(s)')
parser.add_option('--test-build', help='Test binary modules in build',
action='store_true', default=False)
@ -232,9 +233,13 @@ def main(args=sys.argv):
if len(args) > 1 and os.access(args[-1], os.R_OK):
sql_dump = args[-1]
reinit_db(opts.reinitialize_db, sql_dump=sql_dump)
elif opts.inspect_mobi is not None:
elif opts.inspect_mobi:
from calibre.ebooks.mobi.debug import inspect_mobi
inspect_mobi(opts.inspect_mobi)
for path in args[1:]:
prints('Inspecting:', path)
inspect_mobi(path)
print
elif opts.test_build:
from calibre.test_build import test
test()

View File

@ -14,6 +14,7 @@ from lxml import html
from calibre.utils.date import utc_tz
from calibre.ebooks.mobi.langcodes import main_language, sub_language
from calibre.ebooks.mobi.reader.headers import NULL_INDEX
from calibre.ebooks.mobi.utils import (decode_hex_number, decint,
get_trailing_data, decode_tbs, read_font_record)
from calibre.utils.magick.draw import identify_data
@ -151,6 +152,10 @@ class EXTHRecord(object):
117 : 'adult',
118 : 'retailprice',
119 : 'retailpricecurrency',
121 : 'KF8 header section index',
125 : 'KF8 resources (images/fonts) count',
129 : 'KF8 cover URI',
131 : 'KF8 unknown count',
201 : 'coveroffset',
202 : 'thumboffset',
203 : 'hasfakecover',
@ -169,9 +174,10 @@ class EXTHRecord(object):
503 : 'updatedtitle',
}.get(self.type, repr(self.type))
if self.name in ('coveroffset', 'thumboffset', 'hasfakecover',
if (self.name in {'coveroffset', 'thumboffset', 'hasfakecover',
'Creator Major Version', 'Creator Minor Version',
'Creator Build Number', 'Creator Software', 'startreading'):
'Creator Build Number', 'Creator Software', 'startreading'} or
self.type in {121, 125, 131}):
self.data, = struct.unpack(b'>I', self.data)
def __str__(self):
@ -338,9 +344,9 @@ class MOBIHeader(object): # {{{
ans.append('File version: %d'%self.file_version)
ans.append('Reserved: %r'%self.reserved)
ans.append('Secondary index record: %d (null val: %d)'%(
self.secondary_index_record, 0xffffffff))
self.secondary_index_record, NULL_INDEX))
ans.append('Reserved2: %r'%self.reserved2)
ans.append('First non-book record (null value: %d): %d'%(0xffffffff,
ans.append('First non-book record (null value: %d): %d'%(NULL_INDEX,
self.first_non_book_record))
ans.append('Full name offset: %d'%self.fullname_offset)
ans.append('Full name length: %d bytes'%self.fullname_length)
@ -379,7 +385,7 @@ class MOBIHeader(object): # {{{
'(has indexing: %s) (has uncrossable breaks: %s)')%(
bin(self.extra_data_flags), self.has_multibytes,
self.has_indexing_bytes, self.has_uncrossable_breaks ))
ans.append('Primary index record (null value: %d): %d'%(0xffffffff,
ans.append('Primary index record (null value: %d): %d'%(NULL_INDEX,
self.primary_index_record))
ans = '\n'.join(ans)
@ -399,7 +405,7 @@ class MOBIHeader(object): # {{{
class TagX(object): # {{{
def __init__(self, raw, control_byte_count):
def __init__(self, raw):
self.tag = ord(raw[0])
self.num_values = ord(raw[1])
self.bitmask = ord(raw[2])
@ -459,8 +465,7 @@ class SecondaryIndexHeader(object): # {{{
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))
self.tagx_entries.append(TagX(tag_table[i*4:(i+1)*4]))
if self.tagx_entries and not self.tagx_entries[-1].is_eof:
raise ValueError('TAGX last entry is not EOF')
@ -563,8 +568,7 @@ class IndexHeader(object): # {{{
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))
self.tagx_entries.append(TagX(tag_table[i*4:(i+1)*4]))
if self.tagx_entries and not self.tagx_entries[-1].is_eof:
raise ValueError('TAGX last entry is not EOF')
@ -634,57 +638,29 @@ class Tag(object): # {{{
TAG_MAP = {
1: ('offset', 'Offset in HTML'),
2: ('size', 'Size in HTML'),
3: ('label_offset', 'Offset to label in CNCX'),
3: ('label_offset', 'Label offset in CNCX'),
4: ('depth', 'Depth of this entry in TOC'),
5: ('class_offset', 'Class offset in CNCX'),
6: ('pos_fid', 'File Index'),
11: ('secondary', '[unknown, unknown, '
'tag type from TAGX in primary index header]'),
# The remaining tag types have to be interpreted subject to the type
# of index entry they are present in
21: ('parent_index', 'Parent'),
22: ('first_child_index', 'First child'),
23: ('last_child_index', 'Last child'),
69 : ('image_index', 'Offset from first image record to the'
' image record associated with this entry'
' (masthead for periodical or thumbnail for'
' article entry).'),
70 : ('desc_offset', 'Description offset in cncx'),
71 : ('author_offset', 'Author offset in cncx'),
72 : ('image_caption_offset', 'Image caption offset in cncx'),
73 : ('image_attr_offset', 'Image attribution offset in cncx'),
}
INTERPRET_MAP = {
'subchapter': {
21 : ('Parent chapter index', 'parent_index')
},
'article' : {
5 : ('Class offset in cncx', 'class_offset'),
21 : ('Parent section index', 'parent_index'),
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 : ('Author offset in cncx', 'author_offset'),
72 : ('Image caption offset in cncx',
'image_caption_offset'),
73 : ('Image attribution offset in cncx',
'image_attr_offset'),
},
'chapter_with_subchapters' : {
22 : ('First subchapter index', 'first_child_index'),
23 : ('Last subchapter index', 'last_child_index'),
},
'periodical' : {
5 : ('Class offset in cncx', 'class_offset'),
22 : ('First section index', 'first_child_index'),
23 : ('Last section index', 'last_child_index'),
69 : ('Offset from first image record num to masthead'
' record', 'image_index'),
},
'section' : {
5 : ('Class offset in cncx', 'class_offset'),
21 : ('Periodical index', 'parent_index'),
22 : ('First article index', 'first_child_index'),
23 : ('Last article index', 'last_child_index'),
},
}
def __init__(self, tagx, vals, entry_type, cncx):
self.value = vals if len(vals) > 1 else vals[0] if vals else None
self.entry_type = entry_type
@ -694,17 +670,12 @@ class Tag(object): # {{{
if tag_type in self.TAG_MAP:
self.attr, self.desc = self.TAG_MAP[tag_type]
else:
try:
td = self.INTERPRET_MAP[entry_type]
except:
raise ValueError('Unknown entry type: %s'%entry_type)
try:
self.desc, self.attr = td[tag_type]
except:
print ('Unknown tag value: %d'%tag_type)
print ('Unknown tag value: %d in entry type: %s'%(tag_type,
entry_type))
self.desc = '??Unknown (tag value: %d type: %s)'%(
tag_type, entry_type)
self.attr = 'unknown'
if '_offset' in self.attr:
self.cncx_value = cncx[self.value]
@ -719,40 +690,21 @@ class IndexEntry(object): # {{{
'''
The index is made up of entries, each of which is represented by an
instance of this class. Index entries typically point to offsets int eh
instance of this class. Index entries typically point to offsets in the
HTML, specify HTML sizes and point to text strings in the CNCX that are
used in the navigation UI.
'''
TYPES = {
# Present in secondary index record
0x01 : 'null',
0x02 : 'publication_meta',
# Present in book type files
0x0f : 'chapter',
0x6f : 'chapter_with_subchapters',
0x1f : 'subchapter',
# Present in periodicals
0xdf : 'periodical',
0xff : 'section',
0x3f : 'article',
}
def __init__(self, ident, entry_type, raw, cncx, tagx_entries,
control_byte_count):
self.index = ident
self.raw = raw
self.tags = []
self.entry_type_raw = entry_type
self.entry_type = entry_type
self.byte_size = len(raw)
orig_raw = raw
try:
self.entry_type = self.TYPES[entry_type]
except KeyError:
raise ValueError('Unknown Index Entry type: %s'%bin(entry_type))
if control_byte_count not in (1, 2):
raise ValueError('Unknown control byte count: %d'%
control_byte_count)
@ -770,7 +722,7 @@ class IndexEntry(object): # {{{
for tag in expected_tags:
vals = []
if tag.tag > 64:
if tag.tag > 0b1000000: # 0b1000000 = 64
has_tag = flags & 0b1
flags = flags >> 1
if not has_tag: continue
@ -837,10 +789,17 @@ class IndexEntry(object): # {{{
return tag.value
return -1
@property
def pos_fid(self):
for tag in self.tags:
if tag.attr == 'pos_fid':
return tag.value
return [0, 0]
def __str__(self):
ans = ['Index Entry(index=%s, entry_type=%s, flags=%s, '
'length=%d, byte_size=%d)'%(
self.index, self.entry_type, bin(self.flags)[2:],
self.index, bin(self.entry_type), bin(self.flags)[2:],
len(self.tags), self.byte_size)]
for tag in self.tags:
if tag.value is not None:
@ -1401,7 +1360,7 @@ class MOBIFile(object): # {{{
self.index_header = self.index_record = None
self.indexing_record_nums = set()
pir = self.mobi_header.primary_index_record
if pir != 0xffffffff:
if pir != NULL_INDEX:
self.index_header = IndexHeader(self.records[pir])
self.cncx = CNCX(self.records[
pir+2:pir+2+self.index_header.num_of_cncx_blocks],
@ -1412,7 +1371,7 @@ class MOBIFile(object): # {{{
pir+2+self.index_header.num_of_cncx_blocks))
self.secondary_index_record = self.secondary_index_header = None
sir = self.mobi_header.secondary_index_record
if sir != 0xffffffff:
if sir != NULL_INDEX:
self.secondary_index_header = SecondaryIndexHeader(self.records[sir])
self.indexing_record_nums.add(sir)
self.secondary_index_record = SecondaryIndexRecord(
@ -1423,7 +1382,7 @@ class MOBIFile(object): # {{{
ntr = self.mobi_header.number_of_text_records
fntbr = self.mobi_header.first_non_book_record
fii = self.mobi_header.first_image_index
if fntbr == 0xffffffff:
if fntbr == NULL_INDEX:
fntbr = len(self.records)
self.text_records = [TextRecord(r, self.records[r],
self.mobi_header.extra_data_flags, decompress) for r in xrange(1,

View File

@ -31,26 +31,26 @@ class EXTHHeader(object): # {{{
while left > 0:
left -= 1
id, size = struct.unpack('>LL', raw[pos:pos + 8])
idx, size = struct.unpack('>LL', raw[pos:pos + 8])
content = raw[pos + 8:pos + size]
pos += size
if id >= 100 and id < 200:
self.process_metadata(id, content, codec)
elif id == 203:
if idx >= 100 and idx < 200:
self.process_metadata(idx, content, codec)
elif idx == 203:
self.has_fake_cover = bool(struct.unpack('>L', content)[0])
elif id == 201:
elif idx == 201:
co, = struct.unpack('>L', content)
if co < NULL_INDEX:
self.cover_offset = co
elif id == 202:
elif idx == 202:
self.thumbnail_offset, = struct.unpack('>L', content)
elif id == 501:
elif idx == 501:
# cdetype
pass
elif id == 502:
elif idx == 502:
# last update time
pass
elif id == 503: # Long title
elif idx == 503: # Long title
# Amazon seems to regard this as the definitive book title
# rather than the title from the PDB header. In fact when
# sending MOBI files through Amazon's email service if the
@ -61,45 +61,45 @@ class EXTHHeader(object): # {{{
except:
pass
#else:
# print 'unknown record', id, repr(content)
# print 'unknown record', idx, repr(content)
if title:
self.mi.title = replace_entities(title)
def process_metadata(self, id, content, codec):
if id == 100:
if self.mi.authors == [_('Unknown')]:
def process_metadata(self, idx, content, codec):
if idx == 100:
if self.mi.is_null('authors'):
self.mi.authors = []
au = content.decode(codec, 'ignore').strip()
self.mi.authors.append(au)
if re.match(r'\S+?\s*,\s+\S+', au.strip()):
self.mi.author_sort = au.strip()
elif id == 101:
elif idx == 101:
self.mi.publisher = content.decode(codec, 'ignore').strip()
elif id == 103:
elif idx == 103:
self.mi.comments = content.decode(codec, 'ignore')
elif id == 104:
elif idx == 104:
self.mi.isbn = content.decode(codec, 'ignore').strip().replace('-', '')
elif id == 105:
elif idx == 105:
if not self.mi.tags:
self.mi.tags = []
self.mi.tags.extend([x.strip() for x in content.decode(codec,
'ignore').split(';')])
self.mi.tags = list(set(self.mi.tags))
elif id == 106:
elif idx == 106:
try:
self.mi.pubdate = parse_date(content, as_utc=False)
except:
pass
elif id == 108:
pass # Producer
elif id == 113:
elif idx == 108:
self.mi.book_producer = content.decode(codec, 'ignore').strip()
elif idx == 113:
pass # ASIN or UUID
elif id == 116:
elif idx == 116:
self.start_offset, = struct.unpack(b'>L', content)
elif id == 121:
elif idx == 121:
self.kf8_header, = struct.unpack(b'>L', content)
#else:
# print 'unhandled metadata record', id, repr(content)
# print 'unhandled metadata record', idx, repr(content)
# }}}
class BookHeader(object):

View File

@ -105,9 +105,11 @@ def get_tag_map(control_byte_count, tags, data, start, end):
if value != 0:
if value == mask:
if count_set_bits(mask) > 1:
# If all bits of masked value are set and the mask has more than one bit, a variable width value
# will follow after the control bytes which defines the length of bytes (NOT the value count!)
# which will contain the corresponding variable width values.
# If all bits of masked value are set and the mask has more
# than one bit, a variable width value will follow after
# the control bytes which defines the length of bytes (NOT
# the value count!) which will contain the corresponding
# variable width values.
value, consumed = decint(data[data_start:])
data_start += consumed
ptags.append((tag, None, value, values_per_entry))

View File

@ -151,6 +151,7 @@ class MobiReader(object):
self.processed_html = self.processed_html.replace('</</', '</')
self.processed_html = re.sub(r'</([a-zA-Z]+)<', r'</\1><',
self.processed_html)
self.processed_html = self.processed_html.replace(u'\ufeff', '')
# Remove tags of the form <xyz: ...> as they can cause issues further
# along the pipeline
self.processed_html = re.sub(r'</{0,1}[a-zA-Z]+:\s+[^>]*>', '',

View File

@ -124,12 +124,18 @@ def rescale_image(data, maxsizeb=IMAGE_MAX_SIZE, dimen=None):
to JPEG. Ensure the resultant image has a byte size less than
maxsizeb.
If dimen is not None, generate a thumbnail of width=dimen, height=dimen
If dimen is not None, generate a thumbnail of
width=dimen, height=dimen or width, height = dimen (depending on the type
of dimen)
Returns the image as a bytestring
'''
if dimen is not None:
data = thumbnail(data, width=dimen, height=dimen,
if hasattr(dimen, '__len__'):
width, height = dimen
else:
width = height = dimen
data = thumbnail(data, width=width, height=height,
compression_quality=90)[-1]
else:
# Replace transparent pixels with white pixels and convert to JPEG

View File

@ -352,9 +352,12 @@ def parse_html(data, log=None, decoder=None, preprocessor=None,
title = etree.SubElement(head, XHTML('title'))
title.text = _('Unknown')
elif not xpath(data, '/h:html/h:head/h:title'):
log.warn('File %s missing <title/> element' % filename)
title = etree.SubElement(head, XHTML('title'))
title.text = _('Unknown')
# Ensure <title> is not empty
title = xpath(data, '/h:html/h:head/h:title')[0]
if not title.text or not title.text.strip():
title.text = _('Unknown')
# Remove any encoding-specifying <meta/> elements
for meta in META_XP(data):
meta.getparent().remove(meta)

View File

@ -242,11 +242,18 @@ class PocketBook900(PocketBook):
class iPhone(Device):
name = 'iPad or iPhone/iTouch + Stanza'
name = 'iPhone/iTouch'
output_format = 'EPUB'
manufacturer = 'Apple'
id = 'iphone'
supports_color = True
output_profile = 'ipad'
class iPad(iPhone):
name = 'iPad'
id = 'ipad'
output_profile = 'ipad3'
class Android(Device):

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

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

View File

@ -352,11 +352,9 @@ class TouchscreenFeedTemplate(Template):
d.append(BR())
div.append(d)
toc = TABLE(CLASS('toc'),width="100%",border="0",cellpadding="3px")
for i, article in enumerate(feed.articles):
if not getattr(article, 'downloaded', False):
continue
tr = TR()
div_td = DIV(CLASS('article_summary'),
A(article.title, CLASS('summary_headline','calibre_rescale_120',
@ -367,11 +365,8 @@ class TouchscreenFeedTemplate(Template):
if article.summary:
div_td.append(DIV(cutoff(article.text_summary),
CLASS('summary_text', 'calibre_rescale_100')))
tr.append(TD(div_td))
toc.append(tr)
div.append(div_td)
div.append(toc)
div.append(BR())
div.append(bottom_navbar)
self.root = HTML(head, body)
if self.html_lang: