calibre
Author: Kovid Goyal
Date: 2024-05-03T03:02:11+00:00
Description: Comprehensive documentation for calibre

Navigace

- moduly
- další |
- Spustit »
- Uživatelská příručka Calibre

Uživatelská příručka Calibre

Calibre je správce knihovny e-knih. Může zobrazovat, převádět a katalogizovat e-knihy ve většině hlavních formátů e-knih. Může také komunikovat s mnoha čtecími zařízeními e-knih. Může přejít na internet a načíst metadata pro vaše knihy. Může stahovat noviny a převést je na e-knihy pro pohodlné čtení. Je víceplatformové, funguje na systémech Windows, Linux a macOS.

Právě jste spustili Calibre. Co uděláte nyní? Než může Calibre udělat cokoliv s vašimi e-knihami, musí o nich nejdříve vědět. Přetáhněte pár souborů e-knih do Calibre nebo klikněte na tlačítko „Přidat knihy“ a najděte knihy, se kterými chcete pracovat. Poté, co jste přidali knihy, zobrazí se v hlavním zobrazení, které vypadá nějak takto:

Poté, co jste spokojeně obdivovali seznam knih, které jste právě přidali, nejspíš si budete chtít nějakou přečíst. Abyste to mohli udělat, budete muset převést knihu do formátu, kterému vaše čtečka rozumí. Při prvním spuštění Calibre se spustí Uvítací průvodce a nastaví Calibre pro vaše čtecí zařízení. Převod je snadná věc. Stačí vybrat knihu, kterou chcete převést, a pak klikněte na tlačítko „Převést knihy“. Prozatím ignorujte všechny volby a klikněte na „OK“. V pravém dolním rohu se začne otáčet malá ikona. Jakmile se přestane otáčet, vaše převedená kniha je připravena. Klikněte na tlačítko „Zobrazit“ pro čtení knihy.

Pokud chcete číst knihu ve své čtečce, připojte čtečku k počítači, počkejte, dokud ji Calibre nerozpozná (10-20 sekund), a pak klikněte na tlačítko „Odeslat do zařízení“. Jakmile se ikona přestane znovu otáčet, odpojte svou čtečku a dejte se do čtení! Pokud jste v předchozím kroku nepřevedli knihu, Calibre ji převede automaticky do formátu, kterému vaše čtecí zařízení rozumí.

Abyste začali s pokročilejším používáním, měli byste si přečíst o grafickém uživatelském rozhraní. Pro ještě větší výkonnost a všestrannost si projděte Rozhraní příkazového řádku. Také shledáte užitečným seznam nejčastějších dotazů.

Pokud máte další otázky nebo chcete probrat Calibre s ostatními uživateli nebo požádat o pomoc s konkrétními věcmi, jsou dostupná fóra a další zdroje nápovědy [https://calibre-ebook.com/help].

Oddíly

- Grafické uživatelské rozhraní

- Přidávání oblíbených webových stránek se zprávami

- Prohlížeč e-knih
- Převod e-knihy
- Úprava e-knih
- Server s obsahem Calibre
- Porovnání e-knih
- Úprava metadat e-knihy
- Nejčastější dotazy
- Kurzy
- calibre:// URL schéma
- Přizpůsobení Calibre
- Rozhraní příkazového řádku
- Nastavení vývojového prostředí Calibre
- Správa digitálních práv (DRM)
- Slovník pojmů

© Copyright Kovid Goyal.
Naposledy aktualizováno kvě 03, 2024.

Navigace

- moduly
- další |
- předchozí |
- Spustit »
- Grafické uživatelské rozhraní

Grafické uživatelské rozhraní

Grafické uživatelské rozhraní (GUI) poskytuje přístup k celé správě knihovny a funkcím převodů formátů e-knih. Základní pracovní postup pro používání Calibre je nejdříve přidat knihy do knihovny z vašeho pevného disku. Calibre se automaticky pokusí přečíst metadata z knih a přidat je do své vnitřní databáze. Jakmile jsou v databázi, můžete na nich provádět různé Akce, což zahrnuje převod z jednoho formátu do druhého, přenos do čtecího zařízení, zobrazení v počítači a úpravu metadat. Posledně zmíněné zahrnuje úpravu obálky, popisu a štítků společně s ostatními podrobnostmi. Pamatujte, že Calibre vytváří kopie souborů, které do něj přidáváte. Vaše původní soubory zůstanou nedotčené.

Rozhraní je rozděleno do různých oddílů:

- Akce

- Předvolby

- Katalogy

- Hledání a řazení

- Rozhraní hledání

- Ukládání hledání

- Searching the full text of all books

- Virtuální knihovny

- Dočasné označování knih

- Odhadování metadat z názvů souborů

- Podrobnosti o knize

- Prohlížeč štítků

- Mřížka obálek

- Prohlížeč obálek

- Adding notes for authors, series, etc.

- Rychlé zobrazení

- Úlohy

- Klávesové zkratky

Akce

Panel nástrojů akcí poskytuje vhodné zkratky k běžně používaným akcím. Pokud kliknete pravým tlačítkem myši na tlačítka, můžete provést varianty výchozí akce. Pamatujte, prosím, že panel nástrojů akcí bude vypadat mírně odlišně v závislosti na tom, zda máte k počítači připojenou čtečku e-knih.

- Přidat knihy

- Upravit metadata

- Převést knihy

- Zobrazit

- Odeslat do zařízení

- Načíst zprávy

- Knihovna

- Zařízení

- Uložit na disk

- Připojit nebo sdílet

- Odebrat knihy

Přidat knihy

Akce Přidat knihy má šedm variant přístupných kliknutím prvým tlačítkem myši na tlačítko.

1. Přidat knihy z jedné složky: Otevře dialogové okno výběru souborů a umožní zadat, které knihy ve složce by měly být přidány. Tato akce je kontextově závislá, t.j. že závisí na tom, který katalog jste vybrali. Pokud jste vybrali Knihovnu, budou knihy přidány do knihovny. Pokud jste vybrali čtečku e-knih, budou knihy nahrány do zařízení, a tak dále.

2. Přidat knihy ze složek a podsložek: Umožňuje vám zvolit složku. Složka a všechny její podsložky jsou prohledány rekurzivně a všechny nalezené e-knihy jsou přidány do knihovny. Můžete zvolit, zda má Calibre přidat všechny soubory v jedné složce do záznamu jedné knihy nebo do záznamů více knih. Všechny soubory e-knih ve složce jsou považovány za stejnou knihu v jiných formátech. Tato akce je opak akce Uložit na disk, t.j. můžete Uložit na disk, odstranit knihy a znovu je přidat v režimu jedné knihy na složku bez ztráty informací kromě data (toto předpokládá, že jste nezměnili žádné nastavení pro akci Uložit na disk).

3. Přidat více knih z archivu (ZIP nebo RAR): Umožňuje vám přidat více e-knih, které jsou uloženy ve vybraných souborech ZIP nebo RAR. Je to vhodná zkratka, která zabraňuje tomu, abyste museli nejdříve rozbalit archiv, a potom přidat knihy prostřednictvím jedné ze dvou výše uvedených voleb.

4. Přidat prázdnou knihu (Položka knihy bez formátů): Umožňuje vám vytvořit prázdný záznam knihy. To může být použito pro následné ruční vyplnění informací o knize, kterou ještě nemusíte mít ve své sbírce.

5. Přidat z ISBN: Umožňuje vám přidat jednu nebo více e-knih zadáním jejich ISBN.

6. Přidat soubory do vybraných záznamů knih: Umožňuje vám přidat nebo aktualizovat soubory přidružené k existující knize ve vaší knihovně.

7. Add data files to selected book records: Allows you to add any number of extra files that will be stored in a data sub-directory in the book directory. See Přidání dodatečných souborů dat do knihy for details.

8. Přidat prázdný soubor do vybraných záznamů knih: Umožňuje vám přidat prázdný soubor zadaného formátu do vybraných záznamů knih.

Akce Přidat knihy může číst metadata z široké nabídky formátů e-knih. Navíc se pokusí odhadnout metadat z názvu souboru. Podívejte se na oddíl Odhadování metadat z názvů souborů, abyste zjistili, jak toto konfigurovat.

Pro přidání dodatečného formátu pro existující knihu můžete udělat kteroukoliv ze tří věcí:

1. Přetáhnout soubor na panel Podrobnosti o knize na pravé straně hlavního okna

2. Kliknout pravým tlačítkem myši na tlačítko Přidat knihy a zvolit Přidat soubory k vybraným záznamům knih.

3. Kliknout na tlačítko Přidat knihy v pravé horní oblasti dialogového okna Upravit metadata otevřeného akcí Upravit metadata.

Upravit metadata

Akce Upravit metadata má čtyři varianty, které mohou být otevřeny kliknutím pravým tlačítkem myši na tlačítko.

1. Upravit metadata jednotlivě: Umožňuje vám upravit metadata knih postupně s možností načtení metadat včetně obálek z internetu. Také vám umožňuje přidat nebo odebrat konkrétní formáty e-knih z knihy.

2. Upravit metadata hromadně: Umožňuje vám upravit běžná pole metadat pro velké počty knih souběžně. Toto pracuje na všech knihách, které jste vybrali v Zobrazení knihovny.

3. Stáhnout metadata a obálky: Stáhne metadata a obálky (pokud jsou dostupné) pro knihy, které jste vybrali v seznamu knih.

4. Sloučit záznamy knih: Poskytne vám možnost sloučení metadat a formátů dvou nebo více záznamů knih. Můžete zvolit buď odstranit nebo ponechat záznamy, na které nebylo kliknuto nejdříve.

5. Manage data files: Manage the extra data files associated with the selected books.

Pro více podrobností se podívejte na Úprava metadat e-knihy.

Převést knihy

E-knihy mohou být převedeny z několika formátů do jakéhokoliv formátu, který vaše čtečka e-knih upřednostňuje. Mnoho e-knih dostupných k zakoupení bude chráněno technologií :doc:`Správa digitálních práv <drm>`_ technologie (DRM). Celibre tyto knihy nepřevede. Je snadné odebrat DRM z mnoha formátů, ale může to být nezákonné, budete si muset najít nástroje pro osvobození svých knih sami, a pak použít Calibre k jejich převodu.

Pro většinu lidí by převod měla být jednoduchá záležitost jednoho kliknutí. Pokud se chcete dozvědět více o procesu převodu, podívejte se na Převod e-knihy.

Akce Převést knihy má tři varianty přístupné kliknutím pravým tlačítkem myši na tlačítko.

1. Převést jednotlivě: Umožňuje vám zadat volby převodu pro přizpůsobení převodu každé vybrané e-knihy.

2. Hromadný převod: Umožňuje vám zadat volby pouze jednou pro převod několika e-knih hromadně.

3. Vytvořit katalog knih ve vaší knihovně Calibre: Umožňuje vám generovat kompletní výpis knih ve vaší knihovně, včetně všech metadat, v několika formátech, jako jsou XML, CSV, BiBTeX, EPUB a MOBI. Katalog bude obsahovat všechny knihy aktuálně zobrazené v zobrazení knihovny. To vám umožní použít funkce hledání pro omezení knihy, které mají být uloženy do katalogu. Navíc pokud vyberete více knih pomocí myši, budou do katalogu přidány pouze tyto knihy. Pokud generujete katalog ve formátu e-knihy, jako je EPUB, MOBI nebo AZW3, příště když připojíte svou čtečku e-knih, bude katalog automaticky odeslán do zařízení. Pro více informací o tom, jak katalogy fungují, si přečtěte Vytváření katalogů AZW3 • EPUB • MOBI.

Zobrazit

Akce Zobrazit zobrazí knihu v programu prohlížeče e-knih. Calibre má vestavěný prohlížeč pro mnoho formátů e-knih. Pro ostatní formáty používá výchozí aplikace operačního systému. Které formáty by se měly otevírat vnitřním prohlížečem, můžete nastavit pomocí Předvolby → Rozhraní → Chování. Pokud má kniha více než jeden formát, můžete zobrazit určitý formát kliknutím pravým tlačítkem myši na tlačítko.

Odeslat do zařízení

Akce Odeslat do zařízení má osm variant, které jsou přístupné kliknutím pravým tlačítkem myši na tlačítko.

1. Odeslat do hlavní paměti: Vybrané knihy jsou přeneseny do hlavní paměti čtečky e-knih.

2. Odeslat na paměťovou kartu A: Vybrané knihy jsou přeneseny na paměťovou kartu A ve čtečce e-knih.

3. Odeslat na paměťovou kartu B: Vybrané knihy jsou přeneseny na paměťovou kartu B ve čtečce e-knih.

4. Odeslat konkrétní formát do: Vybrané knihy jsou přeneseny do vybraného úložiště v zařízení ve formátu, který zadáte.

5. Vysunout zařízení: Odpojí zařízení od Calibre.

6. Nastavit výchozí akci odeslání do zařízení: Umožňuje vám zadat, která z voleb, 1 až 5 nad nebo 7 pod, bude výchozí akcí, když kliknete na hlavní tlačítko.

7. Odeslat a odstranit z knihovny: Vybrané knihy jsou přeneseny do zvoleného úložiště v zařízení a pak odstraněny z knihovny.

8. Načíst poznámky (experimentální): Přenese poznámky, které jste si udělali k e-knize ve vašem zařízení, k metadatům komentářů knihy v knihovně Calibre.

Název souboru a strukturu složek souborů odeslaných do zařízení můžete ovládat nastavením šablony v Předvolby → Import a export → Odesílání knih do zařízení. Také se podívejte na Jazyk šablon v programu Calibre.

Načíst zprávy

Akce Načíst zprávy stahuje zprávy z různých webových stránek a převádí je na e-knihu, kterou můžete číst ve své čtečce e-knih. Obyčejně je nově vytvořená e-kniha přidána do vaší knihovny e-knih, ale pokud je v době dokončení stahování připojena čtečka e-knih, zprávy jsou také automaticky nahrány do čtečky.

Akce Načíst zprávy používá jednoduché předpisy (10-15 řádků kódu) pro každou stránku se zprávami. Abyste se naučili, jak vytvořit předpisy pro vlastní zdroje zpráv, podívejte se na Přidávání oblíbených webových stránek se zprávami.

Akce Načíst zprávy má tři varianty, přístupné kliknutím pravým tlačítkem myši na tlačítko.

1. Naplánovat stahování zpráv: Umožňuje vám naplánovat stahování z vámi vybraných zdrojů zpráv ze seznamu stovek dostupných. Plánování může být nastaveno jednotlivě pro každý zdroj zpráv, který vyberete, a plánování je flexibilní, což vám umožňuje vybrat konkrétní dny v týdnu nebo počet dnů mezi stahováním.

2. Přidat vlastní zdroj zpráv: Umožňuje vám vytvořit jednoduchý předpis pro stahování zpráv z vlastní stránky se zprávami, ke kterým chcete mít přístup. Vytváření předpisu může být tak jednoduché, jako zadání adresy URL informačního kanálu RSS, nebo můžete být více předpisující vytvořením kódu založeném na Pythonu pro tuto úlohu. Více informací naleznete na Přidávání oblíbených webových stránek se zprávami.

3. Stáhnout všechny naplánované zdroje zpráv: Způsobí, že Calibre okamžitě začne stahovat všechny zdroje zpráv, které jste naplánovali.

Knihovna

Akce Library vám umožňuje vytvářet, přepínat mezi, přejmenovávat nebo odebírat knihovny. Calibre vám umožňuje vytvořit tolik knihoven, kolik budete chtít. Mohli byste například vytvořit knihovnu pro sci-fi, knihovnu pro naučnou literaturu, knihovnu pro cizí jazyk, knihovnu pro projekt nebo jakoukoliv strukturu, která vyhovuje vašim potřebám. Knihovny jsou nejvyšší organizační struktura v Calibre. Každá knihovna má svou vlastní sadu knih, štítků, kategorií a základní umístění úložiště.

1. **Přepnout nebo vytvořit knihovnu… **: Umožňuje vám a) připojit se k již existující knihovně Calibre v jiném umístění, b) vytvořit prázdnou knihovnu v novém umístění nebo c) přesunout aktuální knihovnu do nově zadaného umístění.

2. Rychlé přepnutí: Umožňuje vám přepínat mezi knihovnami, které byly registrovány nebo vytvořeny v Calibre.

3. Přejmenovat knihovnu: Umožňuje vám přejmenovat knihovnu.

4. Vybrat náhodnou knihu: Zvolí náhodnou knihu z knihovny

5. Odebrat knihovnu: Umožňuje vám odregistrovat knihovnu z Calibre.

6. Exportovat nebo importovat všechna data Calibre: Umožní vám buď exportovat data Calibre kvůli přechodu na nový počítač nebo importovat dříve exportovaná data.

7. <library name>: Akce 7, 8 atd… vám poskytuje okamžitý přístup k přepnutí mezi více knihovnami, které jste vytvořili nebo připojili. Tento seznam obsahuje pouze 5 nejčastěji používaných knihoven. Pro kompletní seznam použijte nabídku Rychlé přepnutí.

8. Údržba knihovny: Umožňuje vám zkontrolovat v aktuální knihovně problémy s konzistencí dat a obnovit databázi aktuální knihovny ze záloh.

Poznámka

Metadata o vašich e-knihách, např. název, autor a štítky, jsou uloženy v jednom souboru ve složce knihovny Calibre nazvané metadata.db. Pokud se tento soubor poškodí (velice vzácná událost), můžete ztratit metadata. Naštěstí Calibre automaticky zálohuje metadata pro každou jednotlivou knihu ve složce knihy jako soubor OPF. Použitím akce ‚Obnovit databázi‘ pod výše popsanou ‚Údržbou knihovny‘ pro vás může Calibre znovu sestavit soubor metadata.db z jednotlivých souborů OPF.

Můžete kopírovat nebo přesouvat knihy mezi různými knihovnami (jakmile budete mít nastavenou více než jednu knihovnu) kliknutím pravým tlačítkem myši na knihu a výběrem akce Kopírovat do knihovny.

Zařízení

Akce Zařízení vám umožňuje zobrazit knihy v hlavní paměti nebo na paměťových kartách vašeho zařízení, nebo vysunout zařízení (odpojit ho od Calibre). Tato ikona se automaticky zobrazí na hlavním panelu nástrojů Calibre, když připojíte podporované zařízení. Můžete na ni kliknout, abyste viděli knihy ve svém zařízení. Můžete také přetáhnout knihy ze své knihovny Calibre na ikonu, abyste je přenesli do svého zařízení. A naopak můžete přetáhnout knihy ze svého zařízení na ikonu knihovny na panelu nástrojů, abyste přenesli knihy ze svého zařízení do knihovny Calibre.

Uložit na disk

Akce Uložit na disk má pět variant, které jsou přístupné kliknutím pravým tlačítkem myši na tlačítko.

1. Uložit na disk: Uloží vybrané knihy na disk roztříděné do složek. Struktura složek vypadá takto:

Author_(sort)
Title
Book Files

Název souboru a strukturu složek souborů uložených na disk můžete ovládat nastavením šablony v Předvolby → Import a export → Ukládání knih na disk. Také se podívejte na Jazyk šablon v programu Calibre.

2. Uložit na disk do jedné složky: Uloží vybrané knihy na disk do jedné složky.

Pro 1. a 2. jsou všechny dostupné formáty, stejně jako metadata, uloženy na disk pro každou vybranou knihu. Metadata jsou uložena do souboru OPF. Uložené knihy mohou být znovu importovány do knihovny bez jakékoliv ztráty informací pomocí akce Přidat knihy.

3. Uložit na disk pouze <vámi upřednostňovaný> formát: Uloží vybrané knihy na disk ve struktuře složek, jak je zobrazeno v (1.), ale pouze ve vámi upřednostňovaném formátu e-knihy. Svůj upřednostňovaný formát můžete nastavit v Předvolby → Rozhraní → Chování → Upřednostňovaný výstupní formát

4. Uložit na disk do jedné složky pouze <vámi upřednostňovaný> formát: Uloží vybrané knihy na disk do jedné složky, ale pouze ve vámi upřednostňovaném formátu e-knihy. Svůj upřednostňovaný formát můžete nastavit v Předvolby → Rozhraní → Chování → Upřednostňovaný výstupní formát

5. Uložit na disk jeden formát…: Uloží vybrané knihy na disk ve struktuře složek, jak je zobrazeno v (1.), ale pouze ve formátu, který jste vybrali z automaticky otevíraného seznamu.

Připojit nebo sdílet

Akce Připojit nebo sdílet vám umožňuje ručně se připojit k zařízení nebo složce ve vašem počítači. Také vám to umožňuje nastavit vaši knihovnu Calibre pro přístup prostřednictvím webového prohlížeče nebo e-mailu.

Akce Připojit nebo sdílet má čtyři varianty, přístupné kliknutím pravým tlačítkem myši na tlačítko.

1. Připojit ke složce: Umožňuje vám připojit se ke kterékoliv složce ve vašem počítači, jako by to bylo zařízení a využívat s touto složkou všechno vybavení, které má Calibre pro zařízení. To je užitečné, pokud vaše zařízení nemůže být podporováno v Calibre, ale je dostupné jako disk USB.

2. Spustit Server s obsahem: Spustí vestavěný webový server Calibre. Když je spuštěný, bude vaše knihovna Calibre přístupná prostřednictvím webového prohlížeče z internetu (pokud si to zvolíte). Jak je přistupováno k webovému serveru můžete konfigurovat nastavením předvoleb v Předvolby → Sdílení → Sdílení po síti

3. Nastavit sdílení knih na základě e-mailu: Umožňuje sdílení knih a kanálů zpráv e-mailem. Po nastavení e-mailové adresy pro tuto volbu bude Calibre odesílat aktualizace zpráv a knih na zadané e-mailové adresy. Jak Calibre odesílá e-maily můžete konfigurovat nastavením předvoleb v Předvolby → Sdílení → Sdílení knih e-mailem. Jakmile nastavíte jednu nebo více e-mailových adres, bude tato položka nabídky nahrazena položkami nabídky pro odesílání knih na nastavené e-mailové adresy.

Odebrat knihy

Akce Odebrat knihy trvale odstraní knihy, takže ji používejte opatrně. Je kontextově závislá, t.j. závisí na tom, který katalog jste vybrali. Pokud jste vybrali Knihovnu, knihy budou odebrány z knihovny. Pokud jste zvolili zařízení čtečky e-knih, budou knihy odebrány ze zařízení. Abyste odebrali pouze určitý formát pro danou knihu, použijte akci Upravit metadata. Odebrání knih má také pět variant, ke kterým můžete získat přístup kliknutím pravým tlačítkem myši na tlačítko.

1. Odebrat vybrané knihy: Umožňuje vám trvale odebrat všechny knihy, které jsou vybrány v seznamu knih.

2. Odebrat soubory konkrétního formátu z vybraných knih: Umožňuje vám trvale odebrat soubory e-knih konkrétního formátu z knih, které jsou vybrány v seznamu knih.

3. Odebrat všechny formáty z vybraných knih, kromě…: Umožňuje vám trvale odebrat soubory e-knih všech formátů kromě zadaného formátu z knih, které jsou vybrány v seznamu knih.

4. Odebrat všechny formáty z vybraných knih: Umožňuje vám trvale odebrat všechny soubory e-knih z knih, které jsou vybrány v seznamu knih. Zůstanou pouze metadata.

5. Odebrat obálky z vybraných knih: Umožňuje vám trvale odebrat všechny soubory obrázků obálky z knih, které jsou vybrány v seznamu knih.

6. Odebrat odpovídající knihy ze zařízení: Umožňuje vám odebrat soubory e-knih z připojeného zařízení, které odpovídají knihám, které jsou vybrány v seznamu knih.

7. Restore recently deleted: Allows you to undo the removal of books or formats.

Poznámka

Note that when you use Remove books to delete books from your calibre library, the book record is deleted, but the books are temporarily stored, for a few days, in a trash folder. You can undo the delete by right clicking the Remove books button and choosing to Restore recently deleted books.

Předvolby

Akce Předvolby vám umožňuje změnit způsob, jakým různé aspekty Calibre fungují. Má čtyři varianty přístupné kliknutím pravým tlačítkem myši na tlačítko.

1. Předvolby: Umožňuje vám změnit způsob, jakým různé aspekty Calibre fungují. Kliknutí na tlačítko také provede tuto akci.

2. Spustit Uvítacího průvodce: Umožňuje vám spustit Uvítacího průvodce, který se objevil, když jste poprvé spustili Calibre.

3. Získat moduly pro rozšíření Calibre: Otevře nové okno, které zobrazí moduly pro Calibre. Tyto moduly jsou vyvíjeny třetími stranami pro rozšíření funkčnosti Calibre.

4. Restartovat v režimu ladění: Umožňuje vám povolit režim ladění, který může pomáhat vývojářům Calibre při řešení problémů, na které narazíte v programu. Pro většinu uživatelů by toto mělo zůstat zakázáno, pokud jim vývojáři neudělí pokyn, aby to povolili.

Katalogy

Katalog je kolekce knih. Calibre může spravovat dva typy různých katalogů:

1. Knihovna: Toto je kolekce knih uložených ve vaší knihovně Calibre ve vašem počítači.

2. Zařízení: Toto je kolekce knih uložených ve vaší čtečce e-knih. Bude dostupná, když připojíte čtečku k počítači.

Mnoho operací, jako je přidávání knih, odstraňování, zobrazování atd., je kontextově závislých. Takže například pokud kliknete na tlačítko Zobrazit, když máte vybraný katalog Zařízení, Calibre otevře pro zobrazení soubory v zařízení. Pokud máte vybraný katalog Knihovna, budou namísto toho otevřeny soubory ve vaší knihovně Calibre.

Hledání a řazení

Oddíl Hledání a řazení vám umožňuje provést několik výkonných akcí na vaší kolekci knih.

- Můžete je seřadit podle názvu, autora, data, hodnocení atd. kliknutím na názvy sloupců. Můžete také „podřazovat“, t.j. řadit podle více sloupců. Například pokud kliknete na sloupec název a pak na sloupec autor, knihy budou seřazeny podle autora a všechny položky pro stejného autora budou seřazeny podle názvu.

- Pomocí Panelu hledání můžete hledat konkrétní knihu nebo sadu knih. Více o tom níže.

- Můžete rychle a pohodlně upravovat metadata výběrem položky v seznamu, kterou chcete změnit, a stisknutím klávesy E.

- Můžete provádět akce na sadách knih. Pro výběr více knih můžete:

- Ponechat stisknutou klávesu Ctrl a kliknout na knihy, které chcete vybrat.

- Ponechat stisknutou klávesu Shift a kliknout na první a poslední knihu z rozsahu knih, které chcete vybrat.

- Můžete konfigurovat, která pole chcete zobrazit pomocí dialogového okna Předvolby.

- To perform complex multiple column based sub-sorting add the Sort by tool to a toolbar via Preferences → Toolbars & menus.

Rozhraní hledání

Všechna metadata knihy můžete prohledávat zadáním hledaných výrazů do Panelu hledání. Například:

Asimov Foundation format:lrf

Toto najde všechny knihy ve vaší knihovně, které mají ve svých metadatech Asimov a Foundation a jsou dostupné ve formátu LRF. Některé další příklady:

author:Asimov and not series:Foundation
title:"The Ring" or "This book is about a ring"
format:epub publisher:feedbooks.com

Druhy hledání

Existují čtyři druhy hledání: obsahuje, rovnost, regulární výraz (viz regulární výrazy [https://cs.wikipedia.org/wiki/Regul%C3%A1rn%C3%AD_v%C3%BDraz]), a varianta znaků. Druh hledání volíte pomocí znaku předvolby.

‚Contains‘ searches

Searches with no prefix character are contains and are by default case insensitive. An item matches if the search string appears anywhere in the indicated metadata. You can make contains searches case sensitive by checking the option Case sensitive searching in Preferences / Searching. If the search option Unaccented characters match accented characters and punctuation is ignored is checked then a character will match all its variants (e.g., e matches é, è, ê, and ë) and all punctuation and whitespace are ignored. For example, if the Unaccented characters match … option is checked then given the two book titles:

1. Big, Bothéred, and Bad

2. Big Bummer

then these searches find:

- title:"er" matches both (‚e‘ matches both ‚é‘ and ‚e‘).

- title:"g " matches both because spaces are ignored.

- title:"g," matches both because the comma is ignored.

- title:"gb" matches both because ‚, ‚ is ignored in book 1 and spaces are ignored in book 2.

- title:"g b" matches both because comma and space are ignored.

- title:"db" matches #1 because the space in ‚and Bad‘ is ignored.

- title:"," matches both (it actually matches all books) because commas are ignored.

If the Unaccented characters match … option is not checked then character variants, punctuation, and spacing are all significant.

You can set only one of the search options Case sensitive searching and Unaccented characters match accented characters and punctuation is ignored.

‚Equality‘ searches

Equality searches are indicated by prefixing the search string with an equals sign (=). For example, the query tag:"=science" will match science, but not science fiction or hard science. Character variants are significant: é doesn’t match e.

Two variants of equality searches are used for hierarchical items (e.g., A.B.C): hierarchical prefix searches and hierarchical component searches. The first, indicated by a single period after the equals (=.) matches the initial parts of a hierarchical item. The second, indicated by two periods after the equals (=..) matches an internal name in the hierarchical item. Examples, using the tag History.Military.WWII as the value:

- tags:"=.History" : True. History is a prefix of the tag.

- tags:"=.History.Military" : True. History.Military is a prefix of the tag.

- tags:"=.History.Military.WWII" : True. History.Military.WWII is a prefix of the tag, albeit an improper one.

- tags:"=.Military" : False. Military is not a prefix of the tag.

- tags:"=.WWII" : False. WWII is not a prefix of the tag.

- tags:"=..History" : True. The hierarchy contains the value History.

- tags:"=..Military" : True. The hierarchy contains the value Military.

- tags:"=..WWII" : True. The hierarchy contains the value WWII.

- tags:"=..Military.WWII" : False. The .. search looks for single values.

‚Regular expression‘ searches

Regular expression searches are indicated by prefixing the search string with a tilde (~). Any Python-compatible regular expression [https://docs.python.org/library/re.html] can be used. Backslashes used to escape special characters in regular expressions must be doubled because single backslashes will be removed during query parsing. For example, to match a literal parenthesis you must enter \\( or alternatively use super-quotes (see below). Regular expression searches are ‚contains‘ searches unless the expression is anchored. Character variants are significant: ~e doesn’t match é.

‚Character variant‘ searches

Character variant searches are indicated by prefixing the search string with a caret (^). This search is similar to the contains search (above) except that:

- letter case is always ignored.

- character variants always match each other.

- punctuation and whitespace are always significant.

The search options Unaccented characters match accented characters and punctuation is ignored and Case sensitive searching are ignored. They have no effect on this search’s behavior.

The following compares this search to a contains search assuming the Unaccented characters match… option is checked (see above) given the same two book titles:

1. Big, Bothéred, and Bad

2. Big Bummer

then these character variant searches find:

- title:"^er" matches both (‚e‘ matches both ‚é‘ and ‚e‘)

- title:"^g" matches both

- title:"^g " matches #2 because the space is significant

- title:"^g," matches #1 because the comma is significant

- title:"^gb" matches nothing because space and comma are significant

- title:"^g b" matches #2 because the comma is significant

- title:"^db" matches nothing

- title:"^," matches #1 (instead of all books) because the comma is significant

Search Expression Syntax

A search expression is a sequence of search terms optionally separated by the operators and and or. If two search terms occur without a separating operator, and is assumed. The and operator has priority over the or operator; for example the expression a or b and c is the same as a or (b and c). You can use parenthesis to change the priority; for example (a or b) and c to make the or evaluate before the and. You can use the operator not to negate (invert) the result of evaluating a search expression. Examples:

- not tag:foo finds all books that don’t contain the tag foo

- not (author:Asimov or author:Weber) finds all books not written by either Asimov or Weber.

The above examples show examples of search terms. A basic search term is a sequence of characters not including spaces, quotes ("), backslashes (\), or parentheses (( )). It can be optionally preceded by a column name specifier: the lookup name of a column followed by a colon (:), for example author:Asimov. If a search term must contain a space then the entire term must be enclosed in quotes, as in title:"The Ring". If the search term must contain quotes then they must be escaped with backslashes. For example, to search for a series named The „Ball“ and The „Chain“, use:

series:"The \"Ball\" and The \"Chain\"

If you need an actual backslash, something that happens frequently in regular expression searches, use two of them (\\).

It is sometimes hard to get all the escapes right so the result is what you want, especially in regular expression and template searches. In these cases use the super-quote: """sequence of characters""". Super-quoted characters are used unchanged: no escape processing is done.

More information

To search for a string that begins with an equals, tilde, or caret; prefix the string with a backslash.

Enclose search strings with quotes (“) if the string contains parenthesis or spaces. For example, to find books with the tag Science Fiction you must search for tag:"=science fiction". If you search for tag:=science fiction you will find all books with the tag science and the word fiction in any metadata.

You can build advanced search queries easily using the Advanced search dialog accessed by clicking the button on the left of the search box.

Pole dostupná pro vyhledávání jsou: tag, title, author, publisher, series, series_index, rating, cover, comments, format, identifiers, date, pubdate, search, size, vl a vlastní sloupce. Pokud je připojeno zařízení, bude při hledání v zobrazení knihovny Calibre dostupné pole ondevice. Abyste našli název hledání (ve skutečnosti nazvaný název vyhledávání) pro vlastní sloupec, přejeďte myší nad záhlavím sloupce v zobrazení knihovny.

Dates

Syntaxe pro hledání dat je:

pubdate:>2000-1 Will find all books published after Jan, 2000
date:<=2000-1-3 Will find all books added to calibre before 3 Jan, 2000
pubdate:=2009 Will find all books published in 2009

If the date is ambiguous then the current locale is used for date comparison. For example, in an mm/dd/yyyy locale 2/1/2009 is interpreted as 1 Feb 2009. In a dd/mm/yyyy locale it is interpreted as 2 Jan 2009. Some special date strings are available. The string today translates to today’s date, whatever it is. The strings yesterday and thismonth (or the translated equivalent in the current language) also work.
In addition, the string daysago (also translated) can be used to compare to a date some number of days ago.
For example:

date:>10daysago
date:<=45daysago

Aby se zabránilo případným problémům s přeloženými řetězci při používání neanglické verze Calibre, jsou vždy dostupné řetězce _today, _yesterday, _thismonth a _daysago. Ty nejsou přeložené.

Searching dates and numeric values with relational comparisons

Data a číselná pole podporují relační operátory = (rovná se), > (větší než), >= (větší než nebo rovno), < (menší než), <= (menší než nebo rovno) a != (není rovno). Pole hodnocení jsou považována za číselná. Například hledání rating:>=3 nalezne všechny knihy s hodnocením 3 nebo vyšším.

You can search for books that have a format of a certain size like this:

- size:>1.1M will find books with a format larger than 1.1MB

- size:<=1K will find books with a format smaller than or equal to 1KB

You can search for the number of items in multiple-valued fields such as tags using the character # then using the same syntax as numeric fields. For example, to find all books with more than 4 tags use tags:#>4. To find all books with exactly 10 tags use tags:#=10.

Series indices

Series indices are searchable. For the standard series, the search name is series_index. For custom series columns, use the column search name followed by _index. For example, to search the indices for a custom series column named #my_series, you would use the search name #my_series_index.
Series indices are numbers, so you can use the relational operators described above.

Saved searches

The special field search is used for saved searches. If you save a search with the name
„My spouse’s books“ you can enter search:"My spouse's books" in the Search bar to reuse the saved search. More about saving searches below.

Virtual libraries

The special field vl is used to search for books in a Virtual library. For example, vl:Read will find all the books in the Read Virtual library. The search vl:Read and vl:"Science Fiction" will find all the books that are in both the Read and Science Fiction virtual libraries. The value following vl: must be the name of a
Virtual library. If the Virtual library name contains spaces then surround it with quotes.

Whether a field has a value

You can search for the absence or presence of a value for a field using „true“ and „false“. For example:

- cover:false finds all books without a cover

- series:true finds all books that are in a series

- series:false finds all books that are not in a series

- comments:false finds all books with an empty comment

- formats:false finds all books with no book files (empty records)

Yes/no custom columns

Searching Yes/no custom columns for false, empty, or blank will find all books with undefined values in the column. Searching for true will find all books that do not have undefined values in the column. Searching for yes or checked will find all books with Yes in the column.
Searching for no or unchecked will find all books with No in the column. Note that the words yes, no, blank, empty, checked and unchecked are translated; you can use either the current language’s equivalent word or the English word. The words true and false and the special values _yes, _no, and _empty are not translated.

Identifiers

Identifiers (e.g., ISBN, DOI, LCCN, etc.) use an extended syntax. An identifier has the form type:value, as in isbn:123456789. The extended syntax permits you to specify independently the type and value to search for. Both the type and the value parts of the query can use any of the search kinds. Examples:

- identifiers:true najde knihy s jakýmkoliv identifikátorem.

- identifiers:false najde knihy bez identifikátoru.

- identifiers:123 bude hledat knihy s jakýmkoliv typem majícím hodnotu obsahující 123.

- identifiers:=123456789 bude hledat knihy s jakýmkoliv typem majícím hodnotu rovnající se 123456789.

- identifiers:=isbn: a identifiers:isbn:true najde knihy s typem rovnajícím se ISBN majícím jakoukoliv hodnotu.

- identifiers:=isbn:false najde knihy s žádným typem rovnajícím se ISBN.

- identifiers:=isbn:123 najde knihy s typem rovnajícím se ISBN majícím hodnotu obsahující 123.

- identifiers:=isbn:=123456789 najde knihy s typem rovnajícím se ISBN majícím hodnotu rovnající se 123456789.

- identifiers:i:1 najde knihy s typem obsahujícím i majícím hodnotu obsahující 1.

Categories visible in the Tag browser

The search in_tag_browser:true finds all books that are in categories
(tags, authors, etc.) currently shown in the Tag browser. This is useful if you set the two preferences Preferences → Look & feel → Tag browser → Hide empty categories and Find shows all items that match. With those two preferences set, doing a Find in the Tag browser shows only categories containing items matched by the Find. Then, the search in_tag_browser:true additionally finds books with these categories / items. You can easily run this search by pressing the key Ctrl+Alt+Shift+F or clicking the configure button in the Tag browser and choosing the Show only books that have visible categories entry.

Search using templates

You can search using a template in Jazyk šablon v programu Calibre instead of a metadata field. To do so you enter a template, a search type, and the value to search for. The syntax is:

template: (the template) #@#: (search type) : (the value)

The template is any valid calibre template language template. The search type must be one of t (text search), d (date search), n (numeric search), or b (set/not set (boolean)). The value is whatever you want, and can use the search kinds described above for the various search types. You must quote the entire search string if there are spaces anywhere in it.

Příklady:

- template:"program: connected_device_name('main')#@#:t:kindle" – is true when the kindle device is connected.

- template:"program: select(formats_sizes(), 'EPUB')#@#:n:>1000000" – finds books with EPUB files larger than 1 MB.

- template:"program: select(formats_modtimes('iso'), 'EPUB')#@#:d:>10daysago" – finds books with EPUB files newer than 10 days ago.

- template:"""program: book_count('tags:^"' & $series & '"', 0) != 0#@#:n:1""" – finds all books containing the series name in the tags. This example uses super-quoting because the template uses both single quotes (') and double quotes (") when constructing the search expression.

You can build template search queries easily using the Advanced search dialog accessed by clicking the button . You can test templates on specific books using the calibre Template tester, which can be added to the toolbars or menus via Preferences → Toolbars & menus. It can also be assigned a keyboard shortcut via Preferences → Shortcuts.

Advanced search dialog

Dialogové okno Rozšířené hledání

Ukládání hledání

Calibre vám umožňuje uložit často používané hledání pod speciálním názvem a pak znovu použít toto hledání pomocí jediného kliknutí. Abyste to udělali, vytvořte hledání buď zadáním do Panelu hledání, nebo pomocí Prohlížeče štítků. Potom zadejte název, který chcete dát hledání, do pole Uložená hledání vedle Panelu hledání. Klikněte na ikonu plus vedle pole uložených hledání pro uložení hledání.

Nyní můžete přistupovat ke svému uloženému hledání v Prohlížeči štítků pod Uložená hledání. Jedno kliknutím vám umožní snadno znovu použít jakékoliv libovolně složité hledání, aniž byste ho museli znovu vytvářet.

Searching the full text of all books
calibre can optionally index the full text of books in the library to allow for instant searching of words inside any book. To use this functionality click the FT button at the left edge of the search bar.

Then, enable indexing for the current library. Once indexing is complete you can search all the text in the full library. When you add new books to the library, they will be automatically indexed in the background. This search allows you to quickly find all books that contain a word or combination of words. You can even search for words that occur near other words, as shown in the examples in the search popup window. Note that this search tool will find only one occurrence of the search query in a particular book, not list every occurrence, for that it is best to search inside the book using the calibre E-book viewer.

You can re-index an individual book by right clicking on the Book details panel in calibre and choosing Re-index this book for full text searching.

Virtuální knihovny

Virtuální knihovna je způsob, jak předstírat, že vaše knihovna Calibre má pouze několik knih, namísto celé její kolekce. Je to vynikající způsob, jak rozdělit velkou kolekci knih na menší, snadno ovladatelné bloky. Abyste zjistili, jak vytvořit a používat Virtuální knihovny, podívejte se na kurz: Virtuální knihovny.

Dočasné označování knih

You can temporarily mark arbitrary sets of books. Marked books will have a pin on them and can be found with the search marked:true. To mark a book press Ctrl+M or go to Preferences → Toolbars & menus and add the Mark books button to the main toolbar.

You can mark books with a specific text label by right clicking the Mark books button and choosing Mark books with text label. Books marked with text labels can later be found using the search marked:"=the-text-you-entered".

Odhadování metadat z názvů souborů

Obyčejně Calibre čte metadata zevnitř souboru knihy. Může však být nakonfigurováno tak, aby nemísto toho četlo metadata z názvu souboru, a to pomocí Předvolby → Import a export → Přidávání knih → Číst metadata z obsahu souboru.

Můžete také určit, jak jsou čtena metadata z názvu souboru pomocí regulárních výrazů (viz Vše o používání regulárních výrazů v Calibre). V oddílu konfiguračního dialogového okna Přidávání knih můžete zadat regulární výraz, který Calibre použije, aby se pokusilo odhadnout metadata z názvů souborů e-knih, které přidáte do knihovny. Výchozí regulární výraz je:

title - author

to znamená, že předpokládá, že všechny znaky až do první - jsou název knihy a následující znaky jsou autor knihy. Například název souboru:

Foundation and Earth - Isaac Asimov.txt

bude interpretován, že má název: Foundation and Earth a autora: Isaac Asimov

Tip

Pokud název souboru neobsahuje spojovník, výše uvedený regulární výraz selže.

Podrobnosti o knize

Podrobnosti o knize zobrazují obálku a všechna metadata pro aktuálně vybranou knihu. Mohou být skryty prostřednictvím tlačítka Rozložení v pravém dolním rohu hlavního okna Calibre. Na jména autorů zobrazená v panelu Podrobnosti o knize lze kliknout, ve výchozím nastavení vás přenesou na stránku autora na Wikipedii. Toto může být upraveno kliknutím pravým tlačítkem myši na jméno autora a výběrem Spravovat tohoto autora.

Podobně, pokud stáhnete metadata pro knihu, panel Podrobností o knize vám automaticky zobrazí odkazy směřující na webové stránky pro knihu na Amazonu, WorldCatu atd., odkud byla metadata stažena.

Můžete kliknout pravým tlačítkem myši na jednotlivé formáty e-knih v panelu Podrobnosti o knize k jejich odstranění, porovnání s jejich původními verzemi, uložení na disk, otevření v externím programu atd.

Obálku knihy můžete změnit jednoduše přetažením obrázku na panel Podrobnosti o knize. Pokud si přejete upravit obrázek obálky v externím programu, jednoduše na něj klikněte pravým tlačítkem myši a zvolte Otevřít v programu.

Můžete také přidat soubory e-knihy do aktuální knihy přetažením souborů na panel Podrobnosti o knize.

Dvojité kliknutí na panel Podrobnosti o knize ji otevře v samostatném automaticky otevřeném okně.

A nakonec můžete přesně přizpůsobit, jaké informace jsou zobrazeny v panelu Podrobnosti o knize pomocí Předvolby → Rozhraní → Vzhled a chování → Podrobnosti o knize.

Prohlížeč štítků

Prohlížeč štítků vám umožňuje snadno procházet vaši kolekci podle autora, štítků, série atd. Pokud kliknete na kteroukoliv položku v Prohlížeči štítků, například jméno autora Isaac Asimov, pak je seznam knih na pravé straně je omezen, aby zobrazoval knihy od tohoto autora. Můžete kliknout rovněž na názvy kategorií. Například kliknutí na „Série“ zobrazí všechny knihy v kterékoliv sérii.

První kliknutí na položku omezí seznam knih na ty, které obsahují nebo odpovídají položce. Jako pokračování výše uvedeného příkladu kliknutí na Isaaca Asimova zobrazí knihy od tohoto autora. Nové kliknutí na položku změní to, co je zobrazeno, v závislosti na tom, zda má položka podřízené položky (viz podkategorie a hierarchické položky níže). Jako pokračování příkladu s Isaacem Asimovem nové kliknutí na Isaaca Asimova omezí seznam knih na ty, které nejsou od Isaaca Asimova. Třetí kliknutí odebere omezení a zobrazí všechny knihy. Pokud podržíte klávesu Ctrl nebo Shift a kliknete na více položek, pak jsou vytvořena omezení na základě více položek. Například byste mohli držet klávesu Ctrl a kliknout na štítky Historická a Evropa pro nalezení knih o dějinách Evropy. Prohlížeč štítků funguje sestavováním vyhledávacích výrazů, které jsou automaticky zadávány do panelu hledání. Dívání se na to, co Prohlížeč štítků generuje, je dobrý způsob zjištění, jak vytvořit základní vyhledávací výrazy.

Položky v Prohlížeči štítků mají částečně barevné ikony. Množství barev závisí na průměrném hodnocení knih v dané kategorii. Takže například pokud knihy od Isaaca Asimova mají průměrně čtyři hvězdičky, bude ikona pro Isaaca Asimova v Prohlížeči štítků barevná ze 4/5. Můžete přejet myší nad ikonou, abyste viděli průměrné hodnocení.

Položky na vnější úrovni v Prohlížeči štítků, jako jsou Autoři a Série, se nazývají kategorie. Můžete si vytvořit své vlastní kategorie, nazvané Uživatelské kategorie, které jsou užitečné pro organizování položek. Například můžete použít Editor uživatelských kategorií (vlevo pod Prohlížečem štítků klikněte na tlačítko Konfigurovat a zvolte Spravovat autory, štítky atd. → Uživatelské kategorie) pro vytvoření Uživatelské kategorie nazvané Oblíbení autoři, a pak umístit své oblíbené položky do kategorie. Uživatelské kategorie mohou mít podkategorie. Například Uživatelská kategorie Oblíbené.Autoři je podkategorie Oblíbené. Možná také máte Oblíbené.Série, v takovém případě budou pod Oblíbené dvě podkategorie. Podkategorie mohou být vytvořeny kliknutím pravým tlačítkem myši na Uživatelskou kategorii, výběrem Přidat podkategorii do… a zadáním názvu podkategorie; nebo pomocí Editoru uživatelských kategorií zadáním názvů, jako výše uvedený příklad Oblíbené.

V Uživatelských kategoriích můžete vyhledávat stejným způsobem jako ve vestavěných kategoriích kliknutím na ně. Při kliknutí kolují čtyři různá vyhledávání:

1. „všechno odpovídající položce v kategorii“ označené jednoduchým zeleným znaménkem plus.

2. „všechno odpovídající položce v kategorii nebo jejím podkategoriím“ označené dvěma zelenými znaménky plus.

3. „všechno neodpovídající položce v kategorii“ označené jednoduchým červeným znaménkem minus.

4. „všechno neodpovídající položce v kategorii nebo jejím podkategoriím“ označené dvěma červenými znaménky minus.

Je také možné vytvářet hierarchie uvnitř některých textových kategorií, jako jsou štítky, série a vlastní sloupce. Tyto hierarchie se zobrazují s malým trojúhelníkem umožňujícím skrytí podpoložek. Pro použití hierarchií položek v kategorii musíte nejprve přejít do Předvolby → Rozhraní → Vzhled a chování a zadat názvy kategorií do pole „Kategorie s hierarchickými položkami“. Jakmile to uděláte, položky v této kategorii, které obsahují tečky, se zobrazí pomocí malého trojúhelníku. Například předpokládejme, že vytvoříte vlastní sloupec nazvaný „Žánr“ a označíte ho, že obsahuje hierarchické položky. Jakmile to uděláte, položky jako Mysteriózní.Thriller a Mysteriózní.Česky se zobrazí jako Mysteriózní s malým trojúhelníkem. Kliknutí na trojúhelník zobrazí Thriller a Česky jako podpoložky. Pro více informací se podívejte na Správa podskupiny knih, například „žánr“.

Hierarchické položky (položky s podřízenými položkami) používají stejná čtyři ‚kliknutelná‘ vyhledávání jako Uživatelské kategorie. Položky, které nemají podřízené položky, používají dvě z vyhledávání: „všechno odpovídající“ a „všechno neodpovídající“.

Položky v Prohlížeči štítků můžete přetahovat na Uživatelské kategorie pro jejich přidání do této kategorie. Pokud je zdrojem Uživatelská kategorie, podržení klávesy Shift při přetažení přesune položku do nové kategorie. Můžete také přetahovat knihy ze seznamu knih na položky v Prohlížeči štítků; přetažení knihy na položku způsobí, že položka bude automaticky použita na přetaženou knihu. Například přetažení knihy na Isaaca Asimova nastaví autora této knihy na Isaaca Asimova. Její přetažení na štítek Historická přidá štítek Historická do štítků knihy.

You can easily find any item in the Tag browser by clicking the search button at the lower-right corner. In addition, you can right click on any item and choose one of several operations. Some examples are to hide it, rename it, or open a „Manage x“ dialog that allows you to manage items of that kind. For example, the Manage authors dialog allows you to rename authors and control how their names are sorted.

Jak jsou položky v Prohlížeči štítků řazeny, můžete určit prostřednictvím tlačítka Konfigurovat vlevo pod Prohlížečem štítků. Můžete si zvolit seřazení podle názvu, průměrného hodnocení nebo oblíbenosti (oblíbenost je počet knih u položky ve vaší knihovně; například oblíbenost Isaaca Asimova je počet knih ve vaší knihovně od Isaaca Asimova).

Mřížka obálek

Můžete nechat Calibre zobrazit mřížku obálek knih namísto seznamu knih, pokud dáváte přednost procházení své kolekce podle obálek. Mřížka obálek se aktivuje kliknutím na tlačítko Rozložení v pravém dolním rohu hlavního okna Calibre. Velikosti obálek a pozadí Mřížky obálek můžete přizpůsobit pomocí Předvolby → Rozhraní → Vzhled a chování → Mřížka obálek. Můžete dokonce nechat Calibre zobrazit jakékoliv zadané pole pod obálkami podle své vlastní vůle, jako je název, autoři, hodnocení nebo vlastní sloupec.

Prohlížeč obálek

Kromě Mřížky obálek popsané výše, můžete také nechat Calibre zobrazit obálky v jedné řadě. To se aktivuje prostřednictvím tlačítka Rozložení v pravém dolním rohu hlavního okna. V Předvolby → Rozhraní → Vzhled a chování → Prohlížeč obálek můžete změnit počet zobrazených obálek a dokonce nechat zobrazit samotný Prohlížeč obálek v samostatném automaticky otevíraném okně.

Adding notes for authors, series, etc.

You can add notes for an author/series/tag/publisher/etc. to your calibre library. To do so right click on the author name in the Tag browser on the left or the Book details panel on the right and choose Create note or Edit note.

A simple popup window will allow you to enter your notes using basic formatting and supporting links and images. Once a note for an author is created, it can be viewed easily from the Book details panel by clicking the little pencil icon next to the author name.

You can search through all the notes in your library using the Browse notes tool by pressing Ctrl+Shift+N or adding it to the toolbar via Preferences → Toolbars & menus.

Rychlé zobrazení

Někdy chcete vybrat knihu a rychle získat seznam knih se stejnou hodnotou v některé kategorii (autoři, štítky, vydavatel, série atd.) jako aktuálně vybraná kniha, ale beze změny aktuálního zobrazení knihovny. Můžete to udělat pomocí Rychlého zobrazení. Rychlé zobrazení otevře buď druhé okno nebo panel v seznamu knih zobrazující seznam knih odpovídajících hodnotě, o kterou máte zájem. Předpokládejme například, že chcete vidět seznam všech knih s jedním nebo více autory aktuálně vybrané knihy. Klikněte do buňky autora, která vás zajímá, a stiskněte klávesu ‚Q‘ nebo klikněte na ikonu Rychlé zobrazení v oddíle Rozložení v okně Calibre. Vlevo se otevře okno nebo panel se všemi autory pro tuto knihu a vpravo všechny knihy od vybraného autora.

Některé příklady použití Rychlého zobrazení: rychlé ukázání, které další knihy:

- mají některé štítky použité u aktuálně vybrané knihy,

- jsou ve stejné sérii jako aktuální kniha,

- mají stejné hodnoty ve vlastním sloupci jako aktuální kniha,

- jsou napsány jedním ze stejných autorů aktuální knihy

- sdílet hodnoty ve vlastním sloupci

Existují dvě možnosti, kde se informace Rychlého zobrazení objeví:

1. Mohou být otevřeny „neukotvené“: nad oknem Calibre a zůstanou otevřeny, dokud je výslovně nezavřete.

2. Mohou být otevřeny „ukotvené“: jako panel v oddíle seznamu knih hlavního okna Calibre.

Pomocí tlačítka „Ukotvit nebo zrušit ukotvení“ můžete měnit okno z ukotveného na neukotvené podle potřeby.

Panel Rychlého zobrazení může být ponechán otevřen trvale, v takovém případě sleduje pohyby na seznamu knih. Například pokud v zobrazení knihovny Calibre kliknete na sloupec kategorie (štítky, série, vydavatel, autoři atd.) pro knihu, obsah okna Rychlého zobrazení se změní, aby vám ukázal v levém bočním panelu hodnoty v této kategorii pro vybranou knihu (např. štítky pro tuto knihu). Bude vybrána první položka v tomto seznamu a Rychlé zobrazení vám ukáže v pravém bočním panelu všechny knihy ve vaší knihovně, které používají tuto hodnotu. Klikněte na jinou hodnotu v levém panelu, abyste viděli knihy s touto jinou hodnotou.

Poklepejte na knihu v okně Rychlého zobrazení pro výběr této knihy v zobrazení knihovny. Toto také změní položky zobrazené v okně Rychlého zobrazení (levý panel), aby se zobrazily položky v nově vybrané knize.

Poklepejte se Shift- nebo Ctrl- na knihu v okně Rychlého zobrazení pro otevření dialogového okna úpravy metadat této knihy v okně Calibre. Upravovaná kniha bude rychle zobrazena, když zavřete dialogové okno úpravy metadat.

Přejetím myší nad záhlavím sloupce a podíváním se na popis tlačítka pro toto záhlaví můžete vidět, zda sloupec může být zobrazen v Rychlém zobrazení. Také můžete zjistit kliknutím pravým tlačítkem myši na záhlaví sloupce, zda je volba „Rychlé zobrazení“ uvedeno v nabídce, v takovém případě výběr volby Rychlého zobrazení odpovídá stisknutí ‚Q‘ v aktuální buňce.

Volby (v Předvolby → Vzhled a chování → Rychlé zobrazení):

- Respektovat (nebo ne) aktuální Virtuální knihovnu. Pokud je zaškrtnuto, Rychlé zobrazení ukáže pouze knihy v aktuální Virtuální knihovně. Výchozí: respektovat Virtuální knihovny

- Změňte obsah okna Rychlého zobrazení, když je změněn sloupec v seznamu knih pomocí kurzorových kláves. Výchozí: nesledovat změny prováděné kurzorovými klávesami

- Změnit sloupec, který je „rychle zobrazen“, když je poklepáno v buňce v okně Rychlého náhledu. Jinak se změní kniha, ale prozkoumávaný sloupec ne. Výchozí: změnit sloupec

- Změnit sloupec, který je „rychle zobrazen“, na aktuální sloupec, když je v panelu Rychlého náhledu stisknuta klávesa Enter. Jinak se změní kniha, ale prozkoumávaný sloupec ne. Výchozí: změnit sloupec

- Zvolte, které sloupce jsou zobrazeny v okně nebo panelu Rychlého zobrazení.

Úlohy

Panel Úlohy zobrazuje počet aktuálně spuštěných úloh. Úlohy jsou úkoly, které běží v samostatném procesu. Zahrnují převody e-knih a komunikaci s vaším čtecím zařízení. Pro přístup k seznamu úloh můžete kliknout na panel úloh. Jakmile je úloha dokončena, můžete si prohlédnout podrobný protokol z této úlohy poklepáním na něj v seznamu. To je užitečné pro úlohy ladění, které nemusely být úspěšně dokončeny.

Klávesové zkratky
calibre has several keyboard shortcuts to save you time and mouse movement. These shortcuts are active in the book list view (when you’re not editing the details of a particular book), and most of them affect the title you have selected. The calibre E-book viewer has its own shortcuts which can be customised in the viewer Preferences.

Poznámka

Poznámka: Klávesové zkratky Calibre nevyžadují modifikační klávesu (Command, Option, Control atd.), pokud není výslovně uvedeno jinak. Stačí pouze stisknout klávesu s písmenem, například E pro úpravu.

Klávesové zkratky pro hlavní program Calibre

Klávesová zkratka

Akce

F2 (Enter for macOS)

Upravit metadata aktuálně vybraného pole v seznamu knih

A

Přidat knihy

Shift+A

Přidat formáty k vybraným knihám

C

Převést vybrané knihy

D

Odeslat do zařízení

Del

Odebrat vybrané knihy

E

Upravit metadata vybraných knih

G

Získat knihy

I

Zobrazit Podrobnosti o knize

K

Upravit obsah

M

Sloučit vybrané záznamy

Alt+M

Sloučit vybrané záznamy, ponechat originály

O

Otevřít nadřazenou složku

P

Vylepšit knihy

S

Uložit na disk

T

Upravit knihu

V

Zobrazit

Shift+V

Zobrazit naposledy čtenou knihu

Alt+V/Cmd+V for macOS

Zobrazit konkrétní formát

Alt+Shift+J

Přepnout seznam úloh

Alt+Shift+B

Přepnout Prohlížeč obálek

Alt+Shift+D

Přepnout panel Podrobnosti o knize

Alt+Shift+T

Přepnout Prohlížeč štítků

Alt+Shift+G

Přepnout Mřížku obálek

Alt+A

Zobrazit knihy od stejného autora jako aktuální kniha

Alt+T

Zobrazit knihy se stejnými štítky jako aktuální kniha

Alt+P

Zobrazit knihy od stejného vydavatele jako aktuální kniha

Alt+Shift+S

Zobrazit knihy ze stejné série jako aktuální kniha

/, Ctrl+F

Zaměřit Panel hledání

Shift+Ctrl+F

Otevřít dialogové okno Rozšířené hledání

Shift+Alt+F

Přepnout Panel hledání

Esc

Vymazat aktuální hledání

Shift+Esc

Zaměřit seznam knih

Ctrl+Esc

Vymazat Virtuální knihovnu

Alt+Esc

Vymazat dodatečná omezení

Ctrl+*

Vytvořit dočasnou Virtuální knihovnu založenou na aktuálním hledání

Ctrl+Vpravo

Vybrat další kartu Virtuální knihovny

Ctrl+Vlevo

Vybrat předchozí kartu Virtuální knihovny

N nebo F3

Najít další knihu, která odpovídá aktuálnímu hledání (funguje, pouze pokud je zapnuto zvýrazňování hledání v předvolbách hledání)

Shift+N nebo Shift+F3

Najít předchozí knihu, která odpovídá aktuálnímu hledání (funguje, pouze pokud je zapnuto zvýrazňování hledání v předvolbách hledání)

Ctrl+D

Stáhnout metadata a obálky

Ctrl+R

Restartovat Calibre

Ctrl+Shift+R

Restartovat Calibre v režimu ladění

Shift+Ctrl+E

Přidat prázdné knihy do Calibre

Ctrl+M

Přepnout stav označení nebo neoznačení pro vybrané knihy

Ctrl+/ or Ctrl+Alt+F

Open the popup to search the full text of all books in the library

Q

Otevřít automaticky otevírané Rychlé zobrazení pro zobrazení knih v souvisejících sériích, štítcích atd.

Shift+Q

Zaměřit na otevřený panel Rychlého zobrazení

Shift+S

Provést hledání v panelu Rychlého zobrazení

F5

Znovu použít aktuální řazení

Ctrl+Q

Ukončit Calibre

X

Přepnout automatické posouvání seznamu knih

Ctrl+Alt+Shift+F

Restrict the displayed books to only those books that are in a category currently displayed in the Tag browser

B

Browse annotations (highlights and bookmarks) made in the calibre viewer for all books in the library

Ctrl+Shift+N

Browse notes associated with authors/series/tags/etc.

Alt+Shift+L

Toggle the layout between wide and narrow views

© Copyright Kovid Goyal.
Naposledy aktualizováno kvě 03, 2024.

Navigace

- moduly
- další |
- předchozí |
- Spustit »

- Přidávání oblíbených webových stránek se zprávami

Přidávání oblíbených webových stránek se zprávami

Calibre má výkonné, pružné a snadno použitelné rozhraní pro stahování zpráv z internetu a jejich převodu do e-knihy. Následující vám za pomoci příkladů ukáže, jak získat zprávy z různých webových stránek.

K pochopení, jak používat rozhraní, následujte příklady ve vypsaném pořadí níže:

- Úplně automatické načítání

- Blog Calibre

- bbc.co.uk

- Přizpůsobení procesu načítání

- Použití tiskové verze bbc.co.uk

- Nahrazování stylů článku

- Krájení a sekání

- Příklad ze skutečného života

- Tipy pro vývoj nových předpisů

- Další čtení

- Dokumentace API

Úplně automatické načítání

Pokud je váš zdroj zpráv dostatečně jednoduchý, Calibre může být schopné získat ho úplně automaticky, potřebujete jen zadat URL. Calibre shromáždí všechny informace potřebné ke stažení zdroje zpráv do předpisu. Abyste řekli Calibre o zdroji zpráv, musíte pro to vytvořit předpis. Pojďme se podívat na nějaké příklady:

Blog Calibre

Blog Calibre je blog příspěvků, které popisují mnoho užitečných funkcí Calibre jednoduchým a přístupným způsobem pro nové uživatele Calibre. Pro stažení tohoto blogu do e-knihy spoléháme na informační kanál RSS blogu:

http://blog.calibre-ebook.com/feeds/posts/default

URL RSS jsem získal podíváním se pod „Subscribe to“ dole na stránce blogu a výběrem Posts → Atom. Aby Calibre stáhlo informační kanály a převedlo je na e-knihu, měli byste kliknout pravým tlačítkem myši na tlačítko Načíst zprávy a pak na položku nabídky Přidat vlastní zdroj zpráv a pak na tlačítko :guilabel:`Nový předpis. Mělo by se otevřít dialogové okno podobné tomu níže zobrazenému.

Nejdříve zadejte Blog Calibre do pole Název předpisu. To bude název e-knihy, která bude vytvořena z článků ve výše uvedených informačních kanálech.

Další dvě pole (Nejstarší článek a Max. počet článků) vám umožní kontrolu nad tím, kolik článků by mělo být staženo z každého informačního kanálu, a jsou docela sebevysvětlující.

Pro přidání informačních kanálů do předpisu zadejte název a URL informačního kanálu a klikněte na tlačítko Přidat informační kanál. Jakmile jste přidali informační kanál, jednoduše klikněte na tlačítko Uložit a máte hotovo! Zavřete dialogové okno.

Pro otestování nového předpisu klikněte na tlačítko Načíst zprávy a v podnabídce Vlastní zdroje zpráv klikněte na Blog Calibre. Po několika minutách se nově stažená e-kniha příspěvků blogu zobrazí v hlavním zobrazení knihovny (pokud máte připojenou čtečku, bude umístěna do čtečky namísto do knihovny). Vyberte ji a stiskněte tlačítko Zobrazit pro čtení!

Důvod, proč toto fungovalo tak dobře s tak málo úsilím, je ten, že blog poskytuje informační kanály RSS s úplným obsahem, t.j. obsah článku je vložen do samotného informačního kanálu. Pro většinu zdrojů zpráv, které poskytují zprávy tímto způsobem s informačními kanály s úplným obsahem, nepotřebujete žádné další úsilí, abyste je převedli na e-knihy. Nyní se podíváme na zdroje zpráv, které neposkytují informační kanály s úplným obsahem. V takových informačních kanálech je celý článek webová stránka a informační kanál obsahuje pouze odkaz na webovou stránku s krátkým souhrnem článku.

bbc.co.uk

Zkusme následující dva informační kanály z BBC:

1. Titulní sránka zpráv: https://newsrss.bbc.co.uk/rss/newsonline_world_edition/front_page/rss.xml

2. Věda a příroda: https://newsrss.bbc.co.uk/rss/newsonline_world_edition/science/nature/rss.xml

Postupujte podle pokynů navržených ve výše uvedeném Blog Calibre pro vytvoření předpisu pro BBC (pomocí výše uvedených informačních kanálů). Při pohledu na staženou e-knihu vidíme, že Calibre udělalo slušnou práci vyextrahováním pouze obsahu, na kterém vám záleží, z každé webové stránky článku. Proces extrakce však není dokonalý. Někdy ponechá nežádoucí obsah, jako jsou nabídky a navigační pomůcky, nebo odebere obsah, který by měl být ponechán, jako jsou záhlaví článku. Abychom dosáhli dokonalé extrakce obsahu, budeme muset přizpůsobit proces načtení, jak je popsáno v následujícím oddíle.

Přizpůsobení procesu načítání

Pokud chcete zdokonalit proces stahování, nebo stáhnout obsah z obzvláště složité webové stránky, můžete využít celou sílu a pružnost rozhraní předpisů. Za tímto účelem v dialogovém okně Přidat vlastní zdroje zpráv jednoduše klikněte na tlačítko Přepnout do rozšířeného režimu.

Nejjednodušší a často nejproduktivnější přizpůsobení je použít tiskovou verzi online článků. Tisková verze má obvykle mnohem méně nadbytečných dat a překládá se mnohem plynuleji do e-knihy. Zkusme použít tiskovou verzi článků z BBC.

Použití tiskové verze bbc.co.uk

Prvním krokem je podívat se na e-knihu, kterou jsme dříve stáhli z bbc.co.uk. Na konci každého článku v e-knize je malá záložka oznamující, odkud byl článek stažen z. Zkopírujte a vložte tuto URL do prohlížeče. Nyní na webové stránce článku najděte odkaz, který odkazuje na „Tisknutelnou verzi“. Klikněte na něj pro zobrazení tiskové verze článku. Vypadá to mnohem čistěji! Nyní porovnejte obě URL. Pro mě byly:

URL článku

https://news.bbc.co.uk/2/hi/science/nature/7312016.stm

URL tiskové verze

https://newsvote.bbc.co.uk/mpapps/pagetools/print/news.bbc.co.uk/2/hi/science/nature/7312016.stm

Takže to vypadá, že pro získání tiskové verze musíme přidat ke každé URL článku předponu:

newsvote.bbc.co.uk/mpapps/pagetools/print/

Nyní byste měli v Pokročilém režimu dialogového okna Vlastní zdroje zpráv vidět něco podobného (nezapomeňte vybrat předpis BBC před přepnutím do pokročilého režimu):

Můžete vidět, že pole ze Základního režimu byla přeložena do kódu Pythonu přímočarým způsobem. Potřebujeme přidat pokyny k tomuto předpisu, aby použil tiskovou verzi článků. Vše, co je potřeba, je přidat následující dva řádky:

def print_version(self, url):
return url.replace('https://', 'https://newsvote.bbc.co.uk/mpapps/pagetools/print/')

Toto je Python, takže odsazení je důležité. Poté, co jste přidali řádky, mělo by to vypadat takto:

Ve výše uvedeném def print_version(self, url) definuje metodu, která je volána Calibre pro každý článek. url je URL původního článku. print_version dělá to, že vezme tuto URL a nahradí ji novou URL, která odkazuje na tiskovou verzi článku. Abyste se naučili více o Pythonu [https://www.python.org], podívejte se na kurz [https://docs.python.org/tutorial/].

Nyní klikněte na tlačítko Přidat nebo aktualizovat předpis a vaše změny budou uloženy. Znovu stáhněte e-knihu. Měli byste mít mnohem lepší e-knihu. Jeden z problémů s novou verzí je, že písma na tiskové verzi webové stránky jsou příliš malá. To je automaticky opraveno při převodu na e-knihu, ale i po procesu opravy bude velikost písma nabídek a navigačního panelu příliš velká vzhledem k textu článku. Abychom to opravili, provedeme nějaká další přizpůsobení v dalším oddíle.

Nahrazování stylů článku

V předchozím oddíle jsme viděli, že velikost písma pro články z tiskové verze BBC byla příliš malá. Ve většině webových stránek, včetně BBC, je tato velikost písma nastavena pomocí šablon stylů CSS. Můžeme zakázat načítání těchto šablon stylů přidáním řádku:

no_stylesheets = True

Předpis nyní vypadá takto:

Nová verze vypadá docela dobře. Pokud jste perfekcionista, budete si chtít přečíst další oddíl, který se zabývá skutečnou úpravou staženého obsahu.

Krájení a sekání

Calibre obsahuje velice výkonné a pružné možnosti, pokud jde o zacházení se staženým obsahem. Abychom vám pár z nich ukázali, pojďme se znovu podívat na našeho starého přítele, na předpis BBC. Při pohledu na zdrojový kód (HTML) pár článků (tisková verze) vidíme, že mají patičku, která neobsahuje žádné užitečné informace, obsažené v

<div class="footer">
...
</div>

Toto může být odebráno přidáním:

remove_tags = [dict(name='div', attrs={'class':'footer'})]

k předpisu. A nakonec nahraďme některé z CSS, které jsme dříve zakázali, naším vlastním CSS, které je vhodné pro převod na e-knihu:

extra_css = '.headline {font-size: x-large;} \n .fact { padding-top: 10pt }'

S těmito dodatky se náš předpis stal „kvalitou výroby“.

Tento předpis prozkoumá pouze špičku ledovce, pokud jde o sílu Calibre. Abychom prozkoumali více možností Calibre, prohlédneme si v dalším oddíle složitější příklad ze skutečného života.

Příklad ze skutečného života

Přiměřeně složitý příklad ze skutečného života, který odhaluje více z API BasicNewsRecipe, je předpis pro The New York Times
import string, re from calibre import strftime from calibre.web.feeds.recipes import BasicNewsRecipe from calibre.ebooks.BeautifulSoup import BeautifulSoup class NYTimes(BasicNewsRecipe):

title = 'The New York Times'
__author__ = 'Kovid Goyal'
description = 'Daily news from the New York Times'
timefmt = ' [%a, %d %b, %Y]'
needs_subscription = True remove_tags_before = dict(id='article')
remove_tags_after = dict(id='article')
remove_tags = [dict(attrs={'class':['articleTools', 'post-tools', 'side_tool', 'nextArticleLink clearfix']}), dict(id=['footer', 'toolsRight', 'articleInline', 'navigation', 'archive', 'side_search', 'blog_sidebar', 'side_tool', 'side_index']), dict(name=['script', 'noscript', 'style'])]
encoding = 'cp1252'
no_stylesheets = True extra_css = 'h1 {font: sans-serif large;}\n.byline {font:monospace;}'

def get_browser(self):
br = BasicNewsRecipe.get_browser(self)
if self.username is not None and self.password is not None:
br.open('https://www.nytimes.com/auth/login')
br.select_form(name='login')
br['USERID'] = self.username br['PASSWORD'] = self.password br.submit()
return br def parse_index(self):
soup = self.index_to_soup('https://www.nytimes.com/pages/todayspaper/index.html')

def feed_title(div):
return ''.join(div.findAll(text=True, recursive=False)).strip()

articles = {}
key = None ans = []
for div in soup.findAll(True, attrs={'class':['section-headline', 'story', 'story headline']}):

if ''.join(div['class']) == 'section-headline':
key = string.capwords(feed_title(div))
articles[key] = []
ans.append(key)

elif ''.join(div['class']) in ['story', 'story headline']:
a = div.find('a', href=True)
if not a:
continue url = re.sub(r'\?.*', '', a['href'])
url += '?pagewanted=all'
title = self.tag_to_string(a, use_alt=True).strip()
description = ''
pubdate = strftime('%a, %d %b')
summary = div.find(True, attrs={'class':'summary'})
if summary:
description = self.tag_to_string(summary, use_alt=False)

feed = key if key is not None else 'Uncategorized'
if feed not in articles:
articles[feed] = []
if not 'podcasts' in url:
articles[feed].append( dict(title=title, url=url, date=pubdate, description=description, content=''))
ans = self.sort_index_by(ans, {'The Front Page':-1, 'Dining In, Dining Out':1, 'Obituaries':2})
ans = [(key, articles[key]) for key in ans if key in articles]
return ans def preprocess_html(self, soup):
refresh = soup.find('meta', {'http-equiv':'refresh'})
if refresh is None:
return soup content = refresh.get('content').partition('=')[2]
raw = self.browser.open('https://www.nytimes.com'+content).read()
return BeautifulSoup(raw.decode('cp1252', 'replace'))

V tomto předpisu vidíme několik nových funkcí. Nejdříve máme:

timefmt = ' [%a, %d %b, %Y]'

To nastaví zobrazený čas na úvodní stránce vytvořené e-knihy, aby byl ve formátu Day, Day_Number Month, Year. Podívejte se na timefmt.

Pak vidíme skupinu směrnic pro vyčištění staženého HTML:

remove_tags_before = dict(name='h1')
remove_tags_after = dict(id='footer')
remove_tags = ...

Tyto odeberou vše před první značkou <h1> a vše za první značkou, jejíž identifikátor je footer. Podívejte se na remove_tags, remove_tags_before, remove_tags_after.

Další zajímavá funkce je:

needs_subscription = True
...
def get_browser(self):
...

needs_subscription = True říká Calibre, že tento předpis potřebuje uživatelské jméno a heslo pro přístup k obsahu. To způsobí, že Calibre požádá o uživatelské jméno a heslo, kdykoliv se pokusíte použít tento předpis. Kód v calibre.web.feeds.news.BasicNewsRecipe.get_browser() skutečně provede přihlášení na webovou stránku NYT. Jakmile se přihlásíte, Calibre použije stejnou, přihlášenou instance prohlížeče k načtení celého obsahu. Podívejte se na automatizace [https://mechanize.readthedocs.io/en/latest/] k porozumění kódu v get_browser.

Další novou funkcí je metoda calibre.web.feeds.news.BasicNewsRecipe.parse_index(). Její úlohou je přejít na https://www.nytimes.com/pages/todayspaper/index.html a načíst seznam článků, které se objevují v dnešních novinách. I když je to složitější, než jednoduché použití RSS, předpis vytvoří e-knihu, která je velice blízká novinám. parse_index provádí těžké použití BeautifulSoup [https://www.crummy.com/software/BeautifulSoup/bs4/doc/] pro analýzu webových stránek novin. Můžete také použít jiné, modernější analyzátory, pokud se vám nelíbí BeatifulSoup. Calibre přichází s lxml [https://lxml.de/] a html5lib [https://github.com/html5lib/html5lib-python], které jsou doporučenými analyzátory. Pro jejich použití nahraďte volání index_to_soup() následujícím:

raw = self.index_to_soup(url, raw=True)
# For html5lib
import html5lib
root = html5lib.parse(raw, namespaceHTMLElements=False, treebuilder='lxml')
# For the lxml html 4 parser
from lxml import html
root = html.fromstring(raw)

Poslední novou funkcí je metoda calibre.web.feeds.news.BasicNewsRecipe.preprocess_html(). Může být použita k provedení libovolných transformací na každé stažené stránce HTML. Zde je použita k obejití reklam, které NYT zobrazuje před každým článkem.

Tipy pro vývoj nových předpisů

Nejlepším způsobem vývoje nových předpisů je použít rozhraní příkazového řádku. Vytvořte předpis pomocí svého oblíbeného editoru Pythonu a uložte ho do souboru myrecipe.recipe. Přípona .recipe je vyžadována. Pomocí tohoto předpisu můžete stáhnout obsah příkazem:

ebook-convert myrecipe.recipe .epub --test -vv --debug-pipeline debug

Příkaz ebook-convert stáhne celé webové stránky a uloží je do souboru EPUB myrecipe.epub. Volba -vv způsobí, že ebook-convert vyplivne spoustu informací o tom, co dělá. Volba ebook-convert-recipe-input --test umožní stáhnout pouze pár článků z nejvýše dvou informačních kanálů. Kromě toho ebook-convert umístí stažené HTML do složky debug/input, kde debug je složka, kterou jste zadali ve volbě ebook-convert --debug-pipeline.

Jakmile je stahování dokončeno, můžete se podívat na stažené HTML otevřením souboru debug/input/index.html v prohlížeči. Jakmile jste spokojeni se správností stahování a předzpracování, můžete generovat e-knihy v různých formátech, jak je zobrazeno níže:

ebook-convert myrecipe.recipe myrecipe.epub
ebook-convert myrecipe.recipe myrecipe.mobi
...

Pokud jste se svým předpisem spokojeni a máte pocit, že je dostatečná poptávka pro ospravedlnění jeho zahrnutí do sady vestavěných předpisů, zveřejněte svůj předpis ve fóru předpisů Calibre [https://www.mobileread.com/forums/forumdisplay.php?f=228], abyste ho sdíleli s ostatními uživateli Calibre.

Poznámka

Na macOS jsou nástroje příkazového řádku uvnitř sady Calibre, například pokud jste nainstalovali Calibre do /Applications, nástroje příkazového řádku jsou v /Applications/calibre.app/Contents/MacOS/.

Viz také

ebook-convert

Rozhraní příkazového řádku pro všechny převody e-knih.

Další čtení

Abyste se dozvěděli více o psaní pokročilých předpisů pomocí některého z vybavení dostupného v BasicNewsRecipe, měli byste si pročíst následující zdroje:

Dokumentace API

Dokumentace třídy BasicNewsRecipe a všechny její důležité metody a pole.

BasicNewsRecipe [https://github.com/kovidgoyal/calibre/blob/master/src/calibre/web/feeds/news.py]

Zdrojový kód BasicNewsRecipe

Vestavěné předpisy [https://github.com/kovidgoyal/calibre/tree/master/recipes]

Zdrojový kód vestavěných předpisů, které jsou dodávány s Calibre

Fórum předpisů Calibre [https://www.mobileread.com/forums/forumdisplay.php?f=228]

Zde se nachází spousta znalostí pisatelů předpisů Calibre.

Dokumentace API

Dokumentace API pro předpisyBasicNewsRecipe

BasicNewsRecipe.abort_article()
BasicNewsRecipe.abort_recipe_processing()
BasicNewsRecipe.add_toc_thumbnail()
BasicNewsRecipe.adeify_images()
BasicNewsRecipe.canonicalize_internal_url()
BasicNewsRecipe.cleanup()
BasicNewsRecipe.clone_browser()
BasicNewsRecipe.default_cover()
BasicNewsRecipe.download()
BasicNewsRecipe.extract_readable_article()
BasicNewsRecipe.get_article_url()
BasicNewsRecipe.get_browser()
BasicNewsRecipe.get_cover_url()
BasicNewsRecipe.get_extra_css()
BasicNewsRecipe.get_feeds()
BasicNewsRecipe.get_masthead_title()
BasicNewsRecipe.get_masthead_url()
BasicNewsRecipe.get_obfuscated_article()
BasicNewsRecipe.get_url_specific_delay()
BasicNewsRecipe.image_url_processor()
BasicNewsRecipe.index_to_soup()
BasicNewsRecipe.is_link_wanted()
BasicNewsRecipe.parse_feeds()
BasicNewsRecipe.parse_index()
BasicNewsRecipe.populate_article_metadata()
BasicNewsRecipe.postprocess_book()
BasicNewsRecipe.postprocess_html()
BasicNewsRecipe.preprocess_html()
BasicNewsRecipe.preprocess_image()
BasicNewsRecipe.preprocess_raw_html()
BasicNewsRecipe.print_version()
BasicNewsRecipe.publication_date()
BasicNewsRecipe.skip_ad_pages()
BasicNewsRecipe.sort_index_by()
BasicNewsRecipe.tag_to_string()
BasicNewsRecipe.articles_are_obfuscated
BasicNewsRecipe.auto_cleanup
BasicNewsRecipe.auto_cleanup_keep
BasicNewsRecipe.center_navbar
BasicNewsRecipe.compress_news_images

BasicNewsRecipe.compress_news_images_auto_size

BasicNewsRecipe.compress_news_images_max_size
BasicNewsRecipe.conversion_options
BasicNewsRecipe.cover_margins
BasicNewsRecipe.delay
BasicNewsRecipe.description
BasicNewsRecipe.encoding
BasicNewsRecipe.extra_css
BasicNewsRecipe.feeds
BasicNewsRecipe.filter_regexps
BasicNewsRecipe.handle_gzip
BasicNewsRecipe.ignore_duplicate_articles
BasicNewsRecipe.keep_only_tags
BasicNewsRecipe.language
BasicNewsRecipe.masthead_url
BasicNewsRecipe.match_regexps
BasicNewsRecipe.max_articles_per_feed
BasicNewsRecipe.needs_subscription
BasicNewsRecipe.no_stylesheets
BasicNewsRecipe.oldest_article
BasicNewsRecipe.preprocess_regexps
BasicNewsRecipe.publication_type
BasicNewsRecipe.recipe_disabled
BasicNewsRecipe.recursions
BasicNewsRecipe.remove_attributes
BasicNewsRecipe.remove_empty_feeds
BasicNewsRecipe.remove_javascript
BasicNewsRecipe.remove_tags
BasicNewsRecipe.remove_tags_after
BasicNewsRecipe.remove_tags_before
BasicNewsRecipe.requires_version
BasicNewsRecipe.resolve_internal_links
BasicNewsRecipe.reverse_article_order
BasicNewsRecipe.scale_news_images
BasicNewsRecipe.scale_news_images_to_device
BasicNewsRecipe.simultaneous_downloads
BasicNewsRecipe.summary_length
BasicNewsRecipe.template_css
BasicNewsRecipe.timefmt
BasicNewsRecipe.timeout
BasicNewsRecipe.title
BasicNewsRecipe.use_embedded_content

© Copyright Kovid Goyal.
Naposledy aktualizováno kvě 03, 2024.

Navigace

- moduly
- další |
- předchozí |
- Spustit »

- Přidávání oblíbených webových stránek se zprávami »

- Dokumentace API pro předpisy

Dokumentace API pro předpisy

API pro psaní předpisů je definováno pomocí BasicNewsRecipe
class calibre.web.feeds.news.BasicNewsRecipe(options, log, progress_reporter)[zdroj]

Base class that contains logic needed in all recipes. By overriding progressively more of the functionality in this class, you can make progressively more customized/powerful recipes. For a tutorial introduction to creating recipes, see Přidávání oblíbených webových stránek se zprávami.

abort_article(msg=None)[zdroj]

Call this method inside any of the preprocess methods to abort the download for the current article. Useful to skip articles that contain inappropriate content, such as pure video articles.

abort_recipe_processing(msg)[zdroj]

Causes the recipe download system to abort the download of this recipe, displaying a simple feedback message to the user.

add_toc_thumbnail(article, src)[zdroj]

Call this from populate_article_metadata with the src attribute of an
<img> tag from the article that is appropriate for use as the thumbnail representing the article in the Table of Contents. Whether the thumbnail is actually used is device dependent (currently only used by the Kindles). Note that the referenced image must be one that was successfully downloaded, otherwise it will be ignored.

classmethod adeify_images(soup)[zdroj]

If your recipe when converted to EPUB has problems with images when viewed in Adobe Digital Editions, call this method from within postprocess_html().

canonicalize_internal_url(url, is_link=True)[zdroj]

Return a set of canonical representations of url. The default implementation uses just the server hostname and path of the URL, ignoring any query parameters, fragments, etc. The canonical representations must be unique across all URLs for this news source. If they are not, then internal links may be resolved incorrectly.

Parametry:

is_link – Is True if the URL is coming from an internal link in an HTML file. False if the URL is the URL used to download an article.

cleanup()[zdroj]

Called after all articles have been download. Use it to do any cleanup like logging out of subscription sites, etc.

clone_browser(br)[zdroj]

Clone the browser br. Cloned browsers are used for multi-threaded downloads, since mechanize is not thread safe. The default cloning routines should capture most browser customization, but if you do something exotic in your recipe, you should override this method in your recipe and clone manually.

Cloned browser instances use the same, thread-safe CookieJar by default, unless you have customized cookie handling.

default_cover(cover_file)[zdroj]

Create a generic cover for recipes that don’t have a cover

download()[zdroj]

Download and pre-process all articles from the feeds in this recipe.
This method should be called only once on a particular Recipe instance.
Calling it more than once will lead to undefined behavior.
:return: Path to index.html

extract_readable_article(html, url)[zdroj]

Extracts main article content from ‚html‘, cleans up and returns as a (article_html, extracted_title) tuple.
Based on the original readability algorithm by Arc90.

get_article_url(article)[zdroj]

Override in a subclass to customize extraction of the URL that points to the content for each article. Return the article URL. It is called with article, an object representing a parsed article from a feed. See feedparser [https://pythonhosted.org/feedparser/].
By default it looks for the original link (for feeds syndicated via a service like FeedBurner or Pheedo) and if found, returns that or else returns article.link [https://pythonhosted.org/feedparser/reference-entry-link.html].

get_browser(*args, **kwargs)[zdroj]

Return a browser instance used to fetch documents from the web. By default it returns a mechanize [https://mechanize.readthedocs.io/en/latest/] browser instance that supports cookies, ignores robots.txt, handles refreshes and has a mozilla firefox user agent.

If your recipe requires that you login first, override this method in your subclass. For example, the following code is used in the New York
Times recipe to login for full access:

def get_browser(self):
br = BasicNewsRecipe.get_browser(self)
if self.username is not None and self.password is not None:
br.open('https://www.nytimes.com/auth/login')
br.select_form(name='login')
br['USERID'] = self.username
br['PASSWORD'] = self.password
br.submit()
return br

get_cover_url()[zdroj]

Return a URL to the cover image for this issue or None.
By default it returns the value of the member self.cover_url which is normally None. If you want your recipe to download a cover for the e-book override this method in your subclass, or set the member variable self.cover_url before this method is called.

get_extra_css()[zdroj]

By default returns self.extra_css. Override if you want to programmatically generate the extra_css.

get_feeds()[zdroj]

Return a list of RSS feeds to fetch for this profile. Each element of the list must be a 2-element tuple of the form (title, url). If title is None or an empty string, the title from the feed is used. This method is useful if your recipe needs to do some processing to figure out the list of feeds to download. If so, override in your subclass.

get_masthead_title()[zdroj]

Override in subclass to use something other than the recipe title

get_masthead_url()[zdroj]

Return a URL to the masthead image for this issue or None.
By default it returns the value of the member self.masthead_url which is normally None. If you want your recipe to download a masthead for the e-book override this method in your subclass, or set the member variable self.masthead_url before this method is called.
Masthead images are used in Kindle MOBI files.

get_obfuscated_article(url)[zdroj]

If you set articles_are_obfuscated this method is called with every article URL. It should return the path to a file on the filesystem that contains the article HTML. That file is processed by the recursive
HTML fetching engine, so it can contain links to pages/images on the web.
Alternately, you can return a dictionary of the form:
{‚data‘: <HTML data>, ‚url‘: <the resolved URL of the article>}. This avoids needing to create temporary files. The url key in the dictionary is useful if the effective URL of the article is different from the URL passed into this method, for example, because of redirects. It can be omitted if the URL is unchanged.

This method is typically useful for sites that try to make it difficult to access article content automatically.

get_url_specific_delay(url)[zdroj]

Return the delay in seconds before downloading this URL. If you want to programmatically determine the delay for the specified URL, override this method in your subclass, returning self.delay by default for URLs you do not want to affect.

Vrací:

A floating point number, the delay in seconds.

classmethod image_url_processor(baseurl, url)[zdroj]

Perform some processing on image urls (perhaps removing size restrictions for dynamically generated images, etc.) and return the precessed URL. Return None or an empty string to skip fetching the image.

index_to_soup(url_or_raw, raw=False, as_tree=False, save_raw=None)[zdroj]

Convenience method that takes an URL to the index page and returns a BeautifulSoup [https://www.crummy.com/software/BeautifulSoup/bs4/doc] of it.

url_or_raw: Either a URL or the downloaded index page as a string

is_link_wanted(url, tag)[zdroj]

Return True if the link should be followed or False otherwise. By default, raises NotImplementedError which causes the downloader to ignore it.

Parametry:

- url – The URL to be followed

- tag – The tag from which the URL was derived

parse_feeds()[zdroj]

Create a list of articles from the list of feeds returned by BasicNewsRecipe.get_feeds().
Return a list of Feed objects.

parse_index()[zdroj]

This method should be implemented in recipes that parse a website instead of feeds to generate a list of articles. Typical uses are for news sources that have a „Print Edition“ webpage that lists all the articles in the current print edition. If this function is implemented, it will be used in preference to BasicNewsRecipe.parse_feeds().

It must return a list. Each element of the list must be a 2-element tuple of the form ('feed title', list of articles).

Each list of articles must contain dictionaries of the form:

{
'title' : article title,
'url' : URL of print version,
'date' : The publication date of the article as a string,
'description' : A summary of the article
'content' : The full article (can be an empty string). Obsolete
do not use, instead save the content to a temporary
file and pass a file:///path/to/temp/file.html as
the URL.
}

For an example, see the recipe for downloading The Atlantic.
In addition, you can add ‚author‘ for the author of the article.

If you want to abort processing for some reason and have calibre show the user a simple message instead of an error, call abort_recipe_processing().

populate_article_metadata(article, soup, first)[zdroj]

Called when each HTML page belonging to article is downloaded.
Intended to be used to get article metadata like author/summary/etc.
from the parsed HTML (soup).

Parametry:

- article – A object of class calibre.web.feeds.Article.
If you change the summary, remember to also change the text_summary

- soup – Parsed HTML belonging to this article

- first – True iff the parsed HTML is the first page of the article.

postprocess_book(oeb, opts, log)[zdroj]

Run any needed post processing on the parsed downloaded e-book.

Parametry:

- oeb – An OEBBook object

- opts – Conversion options

postprocess_html(soup, first_fetch)[zdroj]

This method is called with the source of each downloaded HTML file, after it is parsed for links and images.
It can be used to do arbitrarily powerful post-processing on the HTML.
It should return soup after processing it.

Parametry:

- soup – A BeautifulSoup [https://www.crummy.com/software/BeautifulSoup/bs4/doc/] instance containing the downloaded HTML.

- first_fetch – True if this is the first page of an article.

preprocess_html(soup)[zdroj]

This method is called with the source of each downloaded HTML file, before it is parsed for links and images. It is called after the cleanup as specified by remove_tags etc.
It can be used to do arbitrarily powerful pre-processing on the HTML.
It should return soup after processing it.

soup: A BeautifulSoup [https://www.crummy.com/software/BeautifulSoup/bs4/doc/] instance containing the downloaded HTML.

preprocess_image(img_data, image_url)[zdroj]

Perform some processing on downloaded image data. This is called on the raw data before any resizing is done. Must return the processed raw data. Return
None to skip the image.

preprocess_raw_html(raw_html, url)[zdroj]

This method is called with the source of each downloaded HTML file, before it is parsed into an object tree. raw_html is a unicode string representing the raw HTML downloaded from the web. url is the URL from which the HTML was downloaded.

Note that this method acts before preprocess_regexps.

This method must return the processed raw_html as a unicode object.

classmethod print_version(url)[zdroj]

Take a url pointing to the webpage with article content and return the URL pointing to the print version of the article. By default does nothing. For example:

def print_version(self, url):
return url + '?&pagewanted=print'

publication_date()[zdroj]

Use this method to set the date when this issue was published.
Defaults to the moment of download. Must return a datetime.datetime object.

skip_ad_pages(soup)[zdroj]

This method is called with the source of each downloaded HTML file, before any of the cleanup attributes like remove_tags, keep_only_tags are applied. Note that preprocess_regexps will have already been applied.
It is meant to allow the recipe to skip ad pages. If the soup represents an ad page, return the HTML of the real page. Otherwise return
None.

soup: A BeautifulSoup [https://www.crummy.com/software/BeautifulSoup/bs4/doc/] instance containing the downloaded HTML.

sort_index_by(index, weights)[zdroj]

Convenience method to sort the titles in index according to weights. index is sorted in place. Returns index.

index: A list of titles.

weights: A dictionary that maps weights to titles. If any titles in index are not in weights, they are assumed to have a weight of 0.

classmethod tag_to_string(tag, use_alt=True, normalize_whitespace=True)[zdroj]

Convenience method to take a BeautifulSoup [https://www.crummy.com/software/BeautifulSoup/bs4/doc/]
Tag and extract the text from it recursively, including any CDATA sections and alt tag attributes. Return a possibly empty Unicode string.

use_alt: If True try to use the alt attribute for tags that don’t have any textual content
tag: BeautifulSoup [https://www.crummy.com/software/BeautifulSoup/bs4/doc/]
Tag

articles_are_obfuscated = False

Set to True and implement get_obfuscated_article() to handle websites that try to make it difficult to scrape content.

auto_cleanup = False

Automatically extract all the text from downloaded article pages. Uses the algorithms from the readability project. Setting this to True, means that you do not have to worry about cleaning up the downloaded HTML manually (though manual cleanup will always be superior).

auto_cleanup_keep = None

Specify elements that the auto cleanup algorithm should never remove.
The syntax is a XPath expression. For example:

auto_cleanup_keep = '//div[@id="article-image"]' will keep all divs with
id="article-image"
auto_cleanup_keep = '//*[@class="important"]' will keep all elements
with class="important"
auto_cleanup_keep = '//div[@id="article-image"]|//span[@class="important"]'
will keep all divs with id="article-image" and spans
with class="important"

center_navbar = True

If True the navigation bar is center aligned, otherwise it is left aligned

compress_news_images = False

Set this to False to ignore all scaling and compression parameters and pass images through unmodified. If True and the other compression parameters are left at their default values, JPEG images will be scaled to fit in the screen dimensions set by the output profile and compressed to size at most (w * h)/16 where w x h are the scaled image dimensions.

compress_news_images_auto_size = 16

The factor used when auto compressing JPEG images. If set to None, auto compression is disabled. Otherwise, the images will be reduced in size to
(w * h)/compress_news_images_auto_size bytes if possible by reducing the quality level, where w x h are the image dimensions in pixels.
The minimum JPEG quality will be 5/100 so it is possible this constraint will not be met. This parameter can be overridden by the parameter compress_news_images_max_size which provides a fixed maximum size for images.
Note that if you enable scale_news_images_to_device then the image will first be scaled and then its quality lowered until its size is less than
(w * h)/factor where w and h are now the scaled image dimensions. In other words, this compression happens after scaling.

compress_news_images_max_size = None

Set JPEG quality so images do not exceed the size given (in KBytes).
If set, this parameter overrides auto compression via compress_news_images_auto_size.
The minimum JPEG quality will be 5/100 so it is possible this constraint will not be met.

conversion_options = {}

Recipe specific options to control the conversion of the downloaded content into an e-book. These will override any user or plugin specified values, so only use if absolutely necessary. For example:

conversion_options = {
'base_font_size' : 16,
'linearize_tables' : True,
}

cover_margins = (0, 0, '#ffffff')

By default, the cover image returned by get_cover_url() will be used as the cover for the periodical. Overriding this in your recipe instructs calibre to render the downloaded cover into a frame whose width and height are expressed as a percentage of the downloaded cover.
cover_margins = (10, 15, ‚#ffffff‘) pads the cover with a white margin
10px on the left and right, 15px on the top and bottom.
Color names are defined here [https://www.imagemagick.org/script/color.php].
Note that for some reason, white does not always work in Windows. Use
#ffffff instead

delay = 0

The default delay between consecutive downloads in seconds. The argument may be a floating point number to indicate a more precise time. See get_url_specific_delay() to implement per URL delays.

description = ''

A couple of lines that describe the content this recipe downloads.
This will be used primarily in a GUI that presents a list of recipes.

encoding = None

Specify an override encoding for sites that have an incorrect charset specification. The most common being specifying latin1 and using cp1252. If None, try to detect the encoding. If it is a callable, the callable is called with two arguments: The recipe object and the source to be decoded. It must return the decoded source.

extra_css = None

Specify any extra CSS that should be added to downloaded HTML files.
It will be inserted into <style> tags, just before the closing </head> tag thereby overriding all CSS except that which is declared using the style attribute on individual HTML tags.
Note that if you want to programmatically generate the extra_css override the get_extra_css() method instead.
For example:

extra_css = '.heading { font: serif x-large }'

feeds = None

List of feeds to download.
Can be either [url1, url2, ...] or [('title1', url1), ('title2', url2),...]

filter_regexps = []

List of regular expressions that determines which links to ignore.
If empty it is ignored. Used only if is_link_wanted is not implemented. For example:

filter_regexps = [r'ads\.doubleclick\.net']

will remove all URLs that have ads.doubleclick.net in them.

Only one of BasicNewsRecipe.match_regexps or BasicNewsRecipe.filter_regexps should be defined.

handle_gzip = True

Set to False if you do not want to use gzipped transfers. Note that some old servers flake out with gzip

ignore_duplicate_articles = None

Ignore duplicates of articles that are present in more than one section.
A duplicate article is an article that has the same title and/or URL.
To ignore articles with the same title, set this to:

ignore_duplicate_articles = {'title'}

To use URLs instead, set it to:

ignore_duplicate_articles = {'url'}

To match on title or URL, set it to:

ignore_duplicate_articles = {'title', 'url'}

keep_only_tags = []

Keep only the specified tags and their children.
For the format for specifying a tag see BasicNewsRecipe.remove_tags.
If this list is not empty, then the <body> tag will be emptied and re-filled with the tags that match the entries in this list. For example:

keep_only_tags = [dict(id=['content', 'heading'])]

will keep only tags that have an id attribute of „content“ or „heading“.

language = 'und'

The language that the news is in. Must be an ISO-639 code either two or three characters long

masthead_url = None

By default, calibre will use a default image for the masthead (Kindle only).
Override this in your recipe to provide a URL to use as a masthead.

match_regexps = []

List of regular expressions that determines which links to follow.
If empty, it is ignored. Used only if is_link_wanted is not implemented. For example:

match_regexps = [r'page=[0-9]+']

will match all URLs that have page=some number in them.

Only one of BasicNewsRecipe.match_regexps or BasicNewsRecipe.filter_regexps should be defined.

max_articles_per_feed = 100

Maximum number of articles to download from each feed. This is primarily useful for feeds that don’t have article dates. For most feeds, you should use BasicNewsRecipe.oldest_article

needs_subscription = False

If True the GUI will ask the user for a username and password to use while downloading.
If set to „optional“ the use of a username and password becomes optional

no_stylesheets = False

Convenient flag to disable loading of stylesheets for websites that have overly complex stylesheets unsuitable for conversion to e-book formats.
If True stylesheets are not downloaded and processed

oldest_article = 7.0

Oldest article to download from this news source. In days.

preprocess_regexps = []

List of regexp substitution rules to run on the downloaded HTML.
Each element of the list should be a two element tuple. The first element of the tuple should be a compiled regular expression and the second a callable that takes a single match object and returns a string to replace the match. For example:

preprocess_regexps = [
(re.compile(r'<!--Article ends here-->.*</body>', re.DOTALL|re.IGNORECASE),
lambda match: '</body>'),
]

will remove everything from <!–Article ends here–> to </body>.

publication_type = 'unknown'

Publication type
Set to newspaper, magazine or blog. If set to None, no publication type metadata will be written to the opf file.

recipe_disabled = None

Set to a non empty string to disable this recipe.
The string will be used as the disabled message

recursions = 0

Number of levels of links to follow on article webpages

remove_attributes = []

List of attributes to remove from all tags.
For example:

remove_attributes = ['style', 'font']

remove_empty_feeds = False

If True empty feeds are removed from the output.
This option has no effect if parse_index is overridden in the sub class. It is meant only for recipes that return a list of feeds using feeds or get_feeds(). It is also used if you use the ignore_duplicate_articles option.

remove_javascript = True

Convenient flag to strip all JavaScript tags from the downloaded HTML

remove_tags = []

List of tags to be removed. Specified tags are removed from downloaded HTML.
A tag is specified as a dictionary of the form:

{
name : 'tag name', #e.g. 'div'
attrs : a dictionary, #e.g. {'class': 'advertisment'}
}

All keys are optional. For a full explanation of the search criteria, see Beautiful Soup [https://www.crummy.com/software/BeautifulSoup/bs4/doc/#searching-the-tree] A common example:

remove_tags = [dict(name='div', class_='advert')]

This will remove all <div class=“advert“> tags and all their children from the downloaded HTML.

remove_tags_after = None

Remove all tags that occur after the specified tag.
For the format for specifying a tag see BasicNewsRecipe.remove_tags.
For example:

remove_tags_after = [dict(id='content')]

will remove all tags after the first element with id=“content“.

remove_tags_before = None

Remove all tags that occur before the specified tag.
For the format for specifying a tag see BasicNewsRecipe.remove_tags.
For example:

remove_tags_before = dict(id='content')

will remove all tags before the first element with id=“content“.

requires_version = (0, 6, 0)

Minimum calibre version needed to use this recipe

resolve_internal_links = False

If set to True then links in downloaded articles that point to other downloaded articles are changed to point to the downloaded copy of the article rather than its original web URL. If you set this to True, you might also need to implement canonicalize_internal_url() to work with the URL scheme of your particular website.

reverse_article_order = False

Reverse the order of articles in each feed

scale_news_images = None

Maximum dimensions (w,h) to scale images to. If scale_news_images_to_device is True this is set to the device screen dimensions set by the output profile unless there is no profile set, in which case it is left at whatever value it has been assigned (default None).

scale_news_images_to_device = True

Rescale images to fit in the device screen dimensions set by the output profile.
Ignored if no output profile is set.

simultaneous_downloads = 5

Number of simultaneous downloads. Set to 1 if the server is picky.
Automatically reduced to 1 if BasicNewsRecipe.delay > 0

summary_length = 500

Max number of characters in the short description

template_css = '\n            .article_date {\n                color: gray; font-family: monospace;\n            }\n\n            .article_description {\n                text-indent: 0pt;\n            }\n\n            a.article {\n                font-weight: bold; text-align:left;\n            }\n\n            a.feed {\n                font-weight: bold;\n            }\n\n            .calibre_navbar {\n                font-family:monospace;\n            }\n    '

The CSS that is used to style the templates, i.e., the navigation bars and the Tables of Contents. Rather than overriding this variable, you should use extra_css in your recipe to customize look and feel.

timefmt = ' [%a, %d %b %Y]'

The format string for the date shown on the first page.
By default: Day_Name, Day_Number Month_Name Year

timeout = 120.0

Timeout for fetching files from server in seconds

title = 'Neznámý zdroj zpráv'

The title to use for the e-book

use_embedded_content = None

Normally we try to guess if a feed has full articles embedded in it based on the length of the embedded content. If None, then the default guessing is used. If True then the we always assume the feeds has embedded content and if False we always assume the feed does not have embedded content.

© Copyright Kovid Goyal.
Naposledy aktualizováno kvě 03, 2024.

Navigace

- moduly
- další |
- předchozí |
- Spustit »
- Prohlížeč e-knih

Prohlížeč e-knih

Calibre obsahuje vestavěný Prohlížeč e-knih, který umí zobrazit všechny hlavní formáty e-knih. Prohlížeč e-knih je vysoce přizpůsobitelný a má mnoho pokročilých funkcí.

- Spuštění Prohlížeče e-knih

- Navigace v e-knize

- Text zvýraznění

- Číst nahlas

- Vyhledávání textu

- Následování odkazů pouze pomocí klávesnice

- Přizpůsobení vzhledu a chování vašeho zážitku ze čtení

- Vyhledávání ve slovníku

- Kopírování textu a obrázků

- Přibližování obrázků

- Klávesové zkratky

- Neplovoucí obsah

- Navrhování knihy tak, aby dobře fungovala s prohlížečem Calibre

Spuštění Prohlížeče e-knih

Jakoukoliv z knih ve své knihovně Calibre můžete zobrazit výběrem knihy a stisknutím tlačítka Zobrazit. To otevře knihu v Prohlížeči e-knih. Můžete také spustit samotný Prohlížeč e-knih z nabídky Start ve Windows. Na macOS ho můžete připnout do doku a spouštět ho odtud. Na Linuxu můžete použít jeho spouštěč v menu nebo spuštěním příkazu ebook-viewer.

Navigace v e-knize

„Otáčet knihy“ v knize můžete budˇ:

- Kliknutím myší do levého nebo pravého okraje stránky

- Stisknutím spacebar, page up, page down nebo kurzorových kláves

- Na dotykové obrazovce klepnutím na text nebo přejetím prstem vlevo a vpravo

Přistupovat k ovládacím prvkům prohlížeče můžete budˇ:

- Kliknutím pravým tlačítkem myši na text

- Stisknutím kláves Esc nebo Nabídka

- Na dotykové obrazovce klepnutím na horní třetinu obrazovky

Prohlížeč má dva režimy, „stránkovaný“ a „plovoucí“. V režimu stránek je obsah knihy prezentován jako stránky, podobně jako v papírové knize. V plovoucím režimu je text zobrazován nepřetržitě, jako ve webovém prohlížeči. Můžete mezi nimi přepínat pomocí Předvoleb prohlížeče pod Rozložení stránky nebo stisknutím klávesové zkratky Ctrl+M.

Záložky

Když jste uprostřed knihy a zavřete Prohlížeč e-knih, zapamatuje si, kde jste přestali číst, a vrátí se tam, když příště otevřete knihu. Můžete také nastavit záložky v knize pomocí tlačítka Záložky v ovládacích prvcích Prohlížeče e-knih nebo stisknutím Ctrl+B. Při prohlížení knih ve formátu EPUB jsou tyto záložky skutečně ukládány do samotného souboru EPUB. Můžete přidat záložky, a pak odeslat soubor přátelům. Když otevřou soubor, uvidí vaše záložky. Toto chování můžete vypnout v oddílu Různé v předvolbách prohlížeče.

Obsah

Pokud kniha, kterou čtete, má definovaný obsah, můžete k němu přistoupit stisknutím tlačítka Obsah. Tím se zobrazí seznam oddílů v knize. Můžete kliknout na kterýkoliv z nich pro přejití na tuto část knihy.

Navigace podle místa

E-knihy, na rozdíl od papírových knih, nemají žádnou koncepci stránek. Na přesná umístění v e-knihách můžete odkazovat pomocí funkce Přejít na → Umístění v ovládacích prvcích prohlížeče.

Tuto informaci o umístění můžete použít pro jednoznačné odkazy na části knih, když o nich diskutujete s přáteli, nebo na ně odkazujete v jiných pracích. Tato umístění můžete zadat pod Přejít na → Umístění v ovládacích prvcích prohlížeče.

Existuje adresa URL, kterou můžete zkopírovat do schránky a vložit do jiných programů nebo dokumentů. Kliknutí na tuto adresu URL otevře knihu v Prohlížeči e-knih Calibre v aktuálním umístění.

Pokud kliknete na odkazy uvnitř e-knihy, aby vás přenesly na různé části knihy, jako jsou vysvětlivky, můžete použít tlačítka Zpět a Vpřed v levém horním rohu v ovládacích prvcích prohlížeče. Tato tlačítka se chovají stejně jako ta v internetovém prohlížeči.

Režim odkazu

Calibre má také velice šikovný Režim odkazů. Můžete ho zapnout kliknutím na tlačítko Režim odkazů v ovládacích prvcích prohlížeče. Jakmile to uděláte, tak při spuštění bude mít každý odstavec zobrazeno jedinečné číslo složené z čísel oddílu a odstavce.

Toto číslo můžete použít pro jednoznačné odkazy na části knih, když o nich diskutujete s přáteli, nebo na ně odkazujete v jiných pracích. Tato čísla můžete zadat do Přejít na funkci pro pro přejití na konkrétní umístění odkazu.

Text zvýraznění

Když vyberete text v prohlížeči, vedle výběru se objeví malý automaticky otevíraný panel. Klikněte na tlačítko zvýraznění na tomto panelu pro vytvoření zvýraznění. Můžete přidávat poznámky a měnit barvu zvýraznění. Na dotykové obrazovce dlouze klepněte na nějaké slovo pro jeho výběr a zobrazení automaticky otevírané lišty. Jakmile jste v režimu zvýraznění, můžete změnit, jaký text je vybrán, pomocí úchytů pro snadný výběr na dotykové obrazovce. Přetažením úchytů k horním nebo dolním okrajům můžete posouvat při výběru. Můžete také Shift a kliknout nebo kliknout pravým tlačítkem pro rozšíření výběru, což je obzvlášť užitečné pro vícestránkové výběry.

Pro zobrazení samostatného panelu se seznamem všech zvýraznění v knize seřazených podle kapitoly můžete použít tlačítko Zvýraznění v ovládacích prvcích prohlížeče.

Můžete procházet všechna zvýraznění v celé knihovně Calibre kliknutím pravým tlačítkem myši na tlačítko Zobrazit a výběrem Procházet anotace.

A konečně, pokud používáte Server s obsahem Calibre v prohlížeči prohlížeče, můžete si nechat prohlížeč synchronizovat jeho anotace s prohlížečem prohlížeče tak, že přejdete do Předvolby → Různé v předvolbách prohlížeče a zadáte uživatelské jméno Serveru s obsahem, se kterým má prohlížeč synchronizovat. Pro synchronizaci s anonymními uživateli použijte speciální hodnotu *.

Číst nahlas

Prohlížeč může číst text knihy nahlas. Pro použití můžete jednoduše kliknout na tlačítko Číst nahlas v ovládacích prvcích prohlížeče pro zahájení čtení textu knihy nahlas. Aktuálně čtené slovo je zvýrazněno. Řeč je syntetizována z textu pomocí služeb vašeho operačního systému pro převod textu na řeč. Použitý hlas můžete změnit kliknutím na ikonu ozubeného kolečka v panelu, který je zobrazen, když je Číst nahlas aktivní.

Zvýrazněné pasáže můžete také číst nahlas přidáním tlačítka Číst nahlas do panelu výběru v předvolbách prohlížeče pod Chování výběru.

Poznámka

Podpora převodu textu na řeč v prohlížečích je velmi neúplná a chybovitá, takže to, jak dobře bude fungovat Číst nahlas v prohlížeči, závisí na tom, jak dobře základní prohlížeč podporuje převod textu na řeč. Zejména nefunguje zvýrazňování aktuálního slova a změna rychlosti nebo hlasu způsobí, že čtení začne znovu od začátku.

Poznámka

Na Linux Číst nahlas vyžaduje nainstalovaný a funkční Speech Dispatcher [https://freebsoft.org/speechd].

Poznámka

V systému Windows nemusí být všechny nainstalované hlasy viditelné pro podsystém SAPI, který se používá pro převod textu na řeč. Existují pokyny ke zviditelnění všech hlasů [https://www.mobileread.com/forums/showpost.php?p=4084051&postcount=108].

Vyhledávání textu

Prohlížeč má velmi výkonné možnosti vyhledávání. Stiskněte klávesu Ctrl+F nebo vstupte do ovládacích prvků prohlížeče a klikněte na vyhledávání. Nejjednodušší formou vyhledávání je prostě vyhledat jakýkoliv text, který zadáte do textového pole. Různé formy vyhledávání se volí pomocí pole režimu vyhledávání pod vstupem pro vyhledávání. Dostupné jsou tyto režimy:

1. Obsahuje - Nejjednodušší výchozí režim. Text zadaný do vyhledávacího pole se hledá kdekoliv. Veškerá interpunkce, diakritika a mezery jsou ignorovány. Například vyhledávání: Pena bude odpovídat všemu následujícímu: penal, pen a, pen.a a Peña. Pokud zaškrtnete políčko Rozlišovat velikost písmen, pak už diakritika, mezery a interpunkce nebudou ignorovány.

2. Celá slova - Vyhledává celá slova. Takže například vyhledávání pena bude odpovídat slovu Peña, ale ne slovu Penal. Stejně jako u výše uvedeného vyhledávání Obsahuje je diakritika a interpunkce ignorována, pokud není zaškrtnuto políčko Rozlišovat velikost písmen.

3. Slova poblíž - Vyhledává celá slova, která jsou blízko sebe. Takže například vyhledání calibre skvělé bude hledat místa, kde se slova calibre a skvělé vyskytují do šedesáti znaků od sebe. Pokud chcete změnit počet znaků, přidejte nové číslo na konec seznamu slov. Například calibre skvělé úžasné 120 bude odpovídat místům, kde se tato tři slova vyskytují do 120 znaků od sebe. Pamatujte, že interpunkce a diakritika není při tomto vyhledávání ignorována.

4. Regulární výraz - Interpretuje hledaný text jako regulární výraz. Více informací o používání regulárních výrazů naleznete v návodu.

Následování odkazů pouze pomocí klávesnice

Prohlížeč e-knih má Režim rad, který umožňuje klikat na odkazy v textu bez použití myši. Stiskněte klávesy Alt+F a všechny odkazy na aktuální obrazovce budou zvýrazněny číslem nebo písmenem. Stisknutím písmene na klávesnici kliknete na odkaz. Stisknutím klávesy Esc přerušíte Režim rad, aniž byste vybrali jakýkoliv odkaz.

Pokud je na obrazovce více než třicet pět odkazů, některé z nich budou mít více písmen, v takovém případě zadejte první a druhé nebo první a aktivujte je stisknutím Enter. Klávesu Backspace můžete také použít k vrácení chyby při psaní.

Přizpůsobení vzhledu a chování vašeho zážitku ze čtení

Velikost písma můžete změnit za běhu pomocí Velikost písma v ovládacích prvcích prohlížeče nebo pomocí Ctrl++ a Ctrl+- nebo držením klávesy Ctrl a použitím kolečka myši.

Barvy lze změnit v oddílu Barvy v předvolbách prohlížeče.

Počet stránek zobrazených na obrazovce stejně jako okraje stránky můžete změnit v Rozložení stránky v předvolbách prohlížeče.

Vlastní záhlaví a zápatí, jako je zbývající čas čtení, název aktuální kapitoly, pozice v knize atd., můžete zobrazit prostřednictvím oddílu Záhlaví a zápatí předvoleb prohlížeče.

Pokročilejší přizpůsobení může být dosaženo nastavením Stylů. Zde můžete zadat obrázek pozadí, který bude zobrazen pod textem, a také šablonu stylů, kterou můžete nastavit, aby se použila na každou knihu. Pomocí ní můžete dělat věci, jako jsou změna stylů odstavců, zarovnání textu do bloku atd. Pro příklady vlastních šablon stylů používaných uživateli Calibre navštivte fórum [https://www.mobileread.com/forums/showthread.php?t=51500].

Vyhledávání ve slovníku

Význam slov v aktuální knize můžete vyhledat poklepáním nebo dlouhým klepnutím na slovo, které chcete vyhledat, a následným kliknutím na tlačítko vyhledávání, které vypadá jako knihovna.

Kopírování textu a obrázků

Text a obrázky můžete vybrat přetažením obsahu myší a potom kliknutím pravým tlačítkem myši a výběrem Kopírovat pro zkopírování do schránky. Zkopírovaný materiál může být vložen do jiné aplikace jako prostý text a obrázky.

Přibližování obrázků

Přiblížit obrázek pro zobrazení v plné velikosti v samostatném okně můžete buď poklepáním nebo dlouhým klepnutím na něj. Můžete na něj také kliknout pravým tlačítkem myši a zvolit Zobrazit obrázek.

Klávesové zkratky

The viewer has extensive keyboard shortcuts, like the rest of calibre. They can be customised in the viewer Preferences. The default shortcuts are listed below:

Keyboard shortcuts for the calibre viewer

Klávesová zkratka

Akce

Home, Ctrl+ArrowUp, Ctrl+ArrowLeft

Scroll to the start of the current file in a multi file book

Ctrl+Home

Posunout na začátek knihy

Ctrl+End

Posunout na konec knihy

End, Ctrl+ArrowDown, Ctrl+ArrowRight

Scroll to the end of the current file in a multi file book

ArrowUp

Posouvat zpět, plynule v plovoucím režimu a po celých obrazovkách v režimu stránek

ArrowDown

Posouvat vpřed, plynule v plovoucím režimu a po celých obrazovkách v režimu stránek

ArrowLeft

Posouvat vlevo o trochu v plovoucím režimu a po celých obrazovkách v režimu stránek

ArrowRight

Posouvat vpravo o trochu v plovoucím režimu a po celých obrazovkách v režimu stránek

PageUp, Shift+Spacebar

Posouvat zpět po celých obrazovkách

PageDown, Spacebar

Posouvat vpřed po celých obrazovkách

Ctrl+PageUp

Posunout na předchozí oddíl

Ctrl+PageDown

Posunout na další oddíl

Alt+ArrowLeft

Zpět

Alt+ArrowRight

Vpřed

Ctrl+T

Toggle Table of Contents

Ctrl+S

Číst nahlas

Alt+P

Change settings quickly by creating and switching to profiles

Alt+f

Následovat odkazy pomocí klávesnice

Ctrl+C

Kopírovat do schránky

Alt+C

Kopírovat aktuální umístění do schránky

Ctrl+Shift+C

Kopírovat aktuální umístění do schránky jako URL calibre://

/, Ctrl+f, Cmd+f

Spustit hledání

F3, Enter

Najít další

Shift+F3, Shift+Enter

Najít předchozí

Ctrl+Plus, Meta+Plus

Zvětšit velikost písma

Ctrl+Minus, Meta+Minus

Zmenšit velikost písma

Ctrl+0

Obnovit výchozí velikost písma

Ctrl+]

Zvýšit počet stránek na obrazovku

Ctrl+[

Snížit počet stránek na obrazovku

Ctrl+Alt+C

Nastavit počet stránek na obrazovku automaticky

F11, Ctrl+Shift+F

Přepnout na celou obrazovku

Ctrl+M

Přepnout mezi Režimem stránek a Plovoucím režimem pro rozložení textu

Ctrl+W

Přepnout posuvník

Ctrl+X

Přepnout Režim odkazu

Ctrl+B

Zobrazit nebo skrýt záložky

Ctrl+Alt+B

Nová záložka

Ctrl+N, Ctrl+E

Zobrazit metadata knihy

Ctrl+Alt+F5, Ctrl+Alt+R

Znovu načíst knihu

Ctrl+Shift+ArrowRight

Upravit aktuální výběr vpřed o slovo

Ctrl+Shift+ArrowLeft

Upravit aktuální výběr zpět o slovo

Shift+ArrowRight

Upravit aktuální výběr vpřed o znak

Shift+ArrowLeft

Upravit aktuální výběr zpět o znak

Shift+ArrowDown

Upravit aktuální výběr vpřed o řádek

Shift+Home

Rozšířit aktuální výběr na začátek řádku

Shift+End

Rozšířit aktuální výběr na konec řádku

Ctrl+A

Vybrat vše

Shift+ArrowUp

Upravit aktuální výběr zpět o řádek

Ctrl+Shift+ArrowDown

Upravit aktuální výběr vpřed o odstavec

Ctrl+Shift+ArrowUp

Upravit aktuální výběr zpět o odstavec

Esc, MenuKey

Zobrazit ovládací prvky Prohlížeče e-knih

Ctrl+Comma, Ctrl+Esc, Meta+Esc, Meta+Comma

Zobrazit předvolby Prohlížeče e-knih

Ctrl+G, ;, :

Přejít na zadané umístění nebo pozici v knize

Ctrl+Spacebar

Přepnout automatické posouvání

Alt+ArrowUp

Automaticky posouvat rychleji

Alt+ArrowDown

Automaticky posouvat pomaleji

Ctrl+I

Zobrazit nebo skrýt kontrolu

Ctrl+L

Zobrazit nebo skrýt panel vyhledání slova

Ctrl+Q (Cmd+Q on macOS)

Ukončit

Ctrl+P

Tisknout knihu do PDF

Ctrl+F11

Přepnout panel nástrojů

Ctrl+H

Přepnout panel zvýraznění

Ctrl+D

Upravit tuto knihu

Neplovoucí obsah

Některé knihy mají velmi široký obsah, který nemůže být zalomen na ohraničeních stránky. Například tabulky nebo značky <pre>. V takových případech byste měli přepnout prohlížeč do plovoucího režimu stisknutím Ctrl+M pro čtení tohoto obsahu. Alternativně můžete také přidat následující CSS do oddílu Styly v předvolbách prohlížeče, abyste prohlížeč donutili rozdělit řádky textu ve značkách <pre>:

code, pre { white-space: pre-wrap }

Navrhování knihy tak, aby dobře fungovala s prohlížečem Calibre

Prohlížeč Calibre nastaví třídu is-calibre-viewer na kořenovém elementu. Můžete tedy napsat pravidla CSS, která se vztahují pouze na něj. Kromě toho prohlížeč nastaví následující třídy na prvku body:

body.calibre-viewer-dark-colors

Nastavit při použití tmavého barevného schématu

body.calibre-viewer-light-colors

Nastavit při použití světlého barevného schématu

body.calibre-viewer-paginated

Nastavit při použití režimu stránek

body.calibre-viewer-scrolling

Nastavit při použití plovoucího režimu

body.calibre-footnote-container

Set when displaying a popup footnote

Nakonec můžete použít barvy barevného schématu Calibre prostřednictvím proměnných CSS [https://developer.mozilla.org/en-US/docs/Web/CSS/Using_CSS_custom_properties]. Prohlížeč Calibre definuje v barevných motivech, které definují barvu odkazu, následující proměnné: --calibre-viewer-background-color, --calibre-viewer-foreground-color a volitelně --calibre-viewer-link-color.

© Copyright Kovid Goyal.
Naposledy aktualizováno kvě 03, 2024.

Navigace

- moduly
- další |
- předchozí |
- Spustit »
- Převod e-knihy

Převod e-knihy

Calibre má systém převodu, který je navržen tak, aby se velmi snadno používal. Obyčejně stačí přidat knihu do Calibre, kliknout na převod a Calibre se pokusí vygenerovat výstup, který je co nejpodobnější vstupu. Calibre však přijímá velké množství vstupních formátů, z nichž ne všechny jsou stejně vhodné jako jiné pro převod na e-knihy. V případě takových vstupních formátů, nebo pokud pouze chcete větší kontrolu nad systémem převodu, Calibre má mnoho voleb pro jemné ladění procesu převodu. Pamatujte však, že systému převodu Calibre není náhradou za plnohodnotný editor e-knih. Pro úpravu e-knih je doporučuji nejdříve pomocí Calibre převést do formátu EPUB nebo AZW3, a pak použít funkci Upravit knihu, abyste je dostali do dokonalé podoby. Pak můžete použít upravenou e-knihu jako vstup pro převod do jiných formátů v Calibre.

Tento dokument bude odkazovat hlavně na nastavení převodu, které naleznete v dialogovém okně převodu, jak je zobrazeno níže. Všechna tato nastavení jsou také dostupná prostřednictvím rozhraní příkazového řádku pro převod zdokumentovaného v ebook-convert. V Calibre můžete získat nápovědu k jakémukoliv individuálnímu nastavení podržením myši nad ním, objeví se popisek tlačítka popisující nastavení.

Obsah

- Úvod

- Vzhled a chování

- Nastavení stránky

- Heuristické zpracování

- Hledat a nahradit

- Rozpoznávání struktury

- Obsah

- Požití obrázku jako názvu kapitol pří konverzi HTML vstupních dokumentů

- Používání atributů značek pro dodání textu pro položky v obsahu

- Jak jsou nastaveny nebo uloženy volby pro převod

- Tipy pro konkrétní formáty

Úvod

První věc, kterou musíte pochopit o systému převodu, je to, že je navržen jako řetězec. Schematicky to vypadá takto:

Vstupní formát se nejdříve převede na XHTML vhodným vstupním modulem. Toto HTML se pak transformuje. V posledním kroku se zpracované XHTML převede na zadaný výstupní formát vhodným výstupním modulem. Výsledky převodu se mohou značně lišit v závislosti na vstupním formátu. Některé formáty se převádějí mnohem lépe než ostatní. Seznam nejlepších zdrojových formátů pro převod je dostupný zde.

Transformace, které se provádějí na výstupu XHTML, je místo, kde se odehrává veškerá práce. Existují různé transformace, například pro vložení metadat knihy jako stránky na začátek knihy, pro rozpoznání nadpisů kapitol a automatického vytvoření obsahu, pro proporcionální změnu velikosti písma a tak dále. Je důležité si zapamatovat, že všechny transformace se provádějí na výstupním XHTML pomocí vstupního modulu, ne na samotném vstupním souboru. Takže například když požádáte Calibre, aby převedlo soubor RTF na EPUB, tak bude nejdříve vnitřně převeden na XHTML, na toto XHTML budou použity různé transformace, a pak výstupní modul vytvoří soubor EPUB, automaticky vygeneruje všechna metadata, obsah a tak dále.

Tento proces můžete vidět v akci použitím volby ladění . Stačí zadat cestu ke složce pro výstup ladění. Během převodu Calibre umístí XHTML generované různými fázemi převodního řetězce do různých podsložek. Čtyři podsložky jsou:

Fáze převodního řetězce

Složka

Popis

vstup

Obsahuje výstup HTML ze vstupního modulu. Použijte pro ladění vstupního modulu.

analyzováno

Výsledek předzpracování a převodu na XHTML výstupu ze vstupního modulu. Použijte pro ladění rozpoznávání struktury.

struktura

Po rozpoznání struktury, ale před vyhlazením CSS a převodem velikosti písma. Použijte pro ladění převodu velikosti písma a transformace CSS.

zpracováno

Těsně před tím, než je e-kniha předána výstupnímu modulu. Použijte pro ladění výstupního modulu.

Pokud chcete trochu upravit vstupní dokument před tím, než ho necháte Calibre převést, to nejlepší, co můžete udělat, je upravit soubory v podsložce vstup, pak je zazipovat a použít soubor ZIP jako vstupní formát pro následující převody. Abyste to udělali, použijte dialogové okno Upravit metadata pro přidání souboru ZIP jako formátu pro knihu, a pak v levém horním rohu dialogového okna převodu vyberte ZIP jako vstupní formát.

Tento dokument se bude zabývat hlavně různými transformacemi, které pracují na polotovaru XHTML, a jak je ovládat. Na konci je několik konkrétních tipů pro každý vstupní nebo výstupní formát.

Vzhled a chování

Obsah

- Písma

- Text

- Rozložení

- Stylování

- Převod stylů

- Převod HTML

Tato skupina voleb určuje různé aspekty vzhledu a chování převedené e-knihy.

Písma

Jedna z nejhezčích funkcí při čtení e-knih je schopnost snadno upravit velikost písma, aby vyhovovalo jednotlivým potřebám a světelným podmínkám. Calibre má propracované algoritmy, aby zajistilo, že všechny produkované knihy mají jednotné velikosti písma, bez ohledu na to, jaké velikosti písma jsou zadány ve vstupním dokumentu.

Základní velikost písma dokumentu je nejběžnější velikost písma v tomto dokumentu, t. j. velikost větší části textu v tomto dokumentu. Když zadáte Základní velikost písma, Calibre automaticky přiměřeně změní měřítko všech velikostí písma v dokumentu, takže nejběžnější velikost písma se stane zadanou základní velikostí písma a ostatním velikostem písma bude přiměřeně upraveno měřítko. Výběrem větší základní velikosti písma můžete písma v dokumentu zvětšit a naopak. Když nastavíte základní velikost písma, měli byste pro dosažení nejlepších výsledků také nastavit klíč velikosti písma.

Obyčejně Calibre automaticky zvolí základní velikost písma vhodnou pro výstupní profil, který jste zvolili (viz Nastavení stránky). Toto však zde můžete přepsat, pokud pro vás výchozí nastavení není vhodné.

Volba Klíč velikosti písma vám umožňuje určit, jak se mění nezákladní velikosti písma. Algoritmus změny velikosti písma funguje pomocí klíče velikosti písma, což je jednoduše čárkami oddělený seznam velikostí písma. Klíč velikost písma říká Calibre, o kolik „kroků“ větší nebo menší by měla daná velikost písma být v porovnání s základní velikostí písma. Předpokládá se, že by v dokumentu měl být omezený počet velikostí písma. Například jedna velikost pro základní text, několik velikostí pro různé úrovně nadpisů a několik velikostí pro horní nebo dolní indexy a poznámky pod čarou. Klíč velikosti písma umožňuje Calibre rozčlenit velikosti písma ve vstupních dokumentech do samostatných „košů“ odpovídajících různým logickým velikostem písem.

Vysvětleme si to na příkladu. Předpokládejme, že zdrojový dokument, který převádíme, byl vytvořen někým s vynikajícím zrakem a má základní velikost písma 8pt. To znamená, že větší část textu v dokumentu má velikost 8pt, zatímco nadpisy jsou o něco větší (řekněme 10 a 12pt) a poznámky pod čarou o něco menší 6pt. Takže pokud použijeme následující nastavení:

Base font size : 12pt
Font size key : 7, 8, 10, 12, 14, 16, 18, 20

výstupní dokument bude mít základní velikost písma 12pt, nadpisy 14 a 16pt a poznámky pod čarou 8pt. Nyní předpokládejme, že chceme, aby největší velikost nadpisu více vyčnívala a také aby poznámky pod čarou byly trochu větší. Abychom toho dosáhli, klíč písma by měl být změněn na:

New font size key : 7, 9, 12, 14, 18, 20, 22

Největší nadpisy budou mít nyní 18pt, zatímco poznámky pod čarou budou mít 9pt. Můžete si hrát s tímto nastavením, abyste se pokusili zjistit, co by pro vás bylo nejlepší pomocí průvodce změny měřítka písma, který můžete otevřít kliknutím na malé tlačítko vedle nastavení Klíč velikosti písma.

Může tu být také zakázána veškerá změna velikosti písma v převodu, pokud chcete zachovat velikost písma ve vstupním dokumentu.

Související nastavení je Výška řádku. Výška řádku určuje svislou výšku řádků. Ve výchozím nastavení (výška řádku 0) se neprovádí žádná manipulace s výškami řádků. Pokud zadáte nevýchozí hodnotu, budou výšky řádků nastaveny na všech místech, které nemají určenu vlastní výšky řádků. Toto je však taková tupá zbraň a mělo by to být používáno šetrně. Pokud chcete upravit výšky řádků pro nějaký oddíl vstupu, je lepší použít Dodatečné CSS.

V tomto oddíle můžete také říct Calibre, aby do knihy vložilo jakákoliv odkazovaná písma. To umožní, aby písma na čtecích zařízeních fungovala, i když nejsou v zařízení dostupná.

Text

Text může být buď zarovnaný, nebo ne. Zarovnaný text má dodatečné mezery mezi slovy, aby měl hladký pravý okraj. Někteří lidé upřednostňují zarovnaný text, jiní ne. Calibre obyčejně zachová zarovnání v původním dokumentu. Pokud ho chcete přepsat, můžete v tomto oddíle použít volbu Zarovnání textu.

Můžete také říct Calibre, aby Vylepšilo interpunkci, což nahradí prosté uvozovky, pomlčky a výpustky jejich typograficky správnými alternativami. Pamatujte, že tento algoritmus není dokonalý, takže stojí za to přezkoumat výsledky. Naopak je také dostupná Nevylepšená interpunkce.

Nakonec je zde Vstupní kódování znaků. Starší dokumenty někdy nezadávají své kódování znaků. Při převodu to může vést k neanglické znakům nebo speciálním znakům, jako je poškození chytrých uvozovek. Calibre zkouší automaticky rozpoznat kódování znaků zdrojového dokumentu, ale není vždy úspěšné. Pomocí tohoto nastavení můžete vynutit předpokládání konkrétního kódování znaků. cp1252 je běžné kódování pro dokumenty vytvořené pomocí softwaru Windows. Měli byste si také přečíst Jak převedu můj soubor obsahující neanglické znaky nebo chytré uvozovky? pro více informací o problematice kódování.

Rozložení

Obyčejně jsou odstavce v XHTML vykresleny s prázdným řádkem mezi nimi a bez úvodního odsazení textu. Calibre má pár voleb, jak toto určit. Odebrat mezery mezi odstavci vynutí zajištění, aby všechny odstavce neměly žádné vnitřní mezery mezi odstavci. Nastaví také odsazení textu na 1,5em (může být změněno) pro označení začátku každého odstavce. Vložit prázdný řádek dělá opak, což zaručuje, že je právě jeden prázdný řádek mezi každou dvojicí odstavců. Obě tyto volby jsou velice ucelené, odebírají mezery nebo je vkládají pro všechny odstavce (technicky vzato značky <p> a <div>). Je to proto, abyste mohli jen nastavit volbu a být si jistí, že se to provede tak, jak bylo ohlášeno bez ohledu na to, jak je neuspořádaný vstupní soubor. Jedinou výjimkou je případ, kdy vstupní soubor používá pevné konce řádků pro doplnění mezer mezi odstavce.

Pokud chcete odebrat mezery mezi všemi odstavci kromě pár vybraných, nepoužívejte tyto volby. Místo toho přidejte následující kód CSS do Dodatečné CSS:

p, div { margin: 0pt; border: 0pt; text-indent: 1.5em }
.spacious { margin-bottom: 1em; text-indent: 0pt; }

Pak ve zdrojovém dokumentu označte odstavce, které potřebují mezery class=“spacious“. Pokud váš vstupní dokument není v HTML, použijte volbu ladění popsanou v Úvodu, abyste získali HTML (použijte podsložku input).

Další užitečnou volbou je Převést tabulky na řádky. Některé chybně navržené dokumenty používají tabulky pro ovládání rozložení textu na stránce. Při převodu mají tyto dokumenty často text, který vybíhá mimo stránku a jiné artefakty. Tato volba extrahuje obsah z tabulek a zobrazí ho v lineárním tvaru. Pamatujte, že tato volba převede na řádky všechny tabulky, takže ji používejte, pouze pokud jste si jisti, že vstupní dokument nepoužívá tabulky pro oprávněné účely, jako je zobrazení tabulkových informací.

Stylování

Volba Dodatečné CSS vám umožňuje zadat libovolné CSS, které bude použito na všechny soubory HTML na vstupu. Toto CSS je použito s velice vysokou prioritou, a proto by mělo přepsat většinu CSS přítomného v samotném vstupním dokumentu. Toto nastavení můžete použít pro jemné ladění prezentace nebo rozložení dokumentu. Například pokud chcete, aby všechny odstavce třídy endnote byly zarovnány vpravo, stačí přidat:

.endnote { text-align: right }

nebo pokud chcete změnit odsazení všech odstavců:

p { text-indent: 5mm; }

Dodatečné CSS je velice výkonná volba, ale potřebujete pochopit, jak CSS funguje, pro použití jejího plného potenciálu. Můžete použít volbu ladění řetězce popsanou výše, abyste viděli, jaké CSS je přítomné ve vstupním dokumentu.

Jednodušší volbou je použít Filtrovat informace o stylu. To vám umožňuje odebrat všechny vlastnosti CSS zadaných typů z dokumentu. Můžete to například použít k odebrání všech barev nebo písem.

Převod stylů

Toto je nejvýkonnější vybavení související se stylováním. Můžete ho použít k definování pravidel, které mění styly na základě různých podmínek. Můžete ho například použít ke změně všech zelených barev na modré nebo k odebrání všech tučných stylů z textu nebo obarvení všech záhlaví určitou barvou apod.

Převod HTML

Podobné převodu stylů, ale umožňuje provádět změny v obsahu knihy ve formátu HTML. Můžete nahradit jednu značku jinou, přidat značkám třídy nebo jiné atributy na základě jejich obsahu atd.

Nastavení stránky

Volby Nastavení stránky jsou pro ovládání rozložení obrazovky, jako jsou okraje a velikosti obrazovky. Existují volby pro nastavení okrajů stránky, které budou použity Výstupním modulem, pokud vybraný Výstupní formát podporuje okraje stránky. Kromě toho byste měli vybrat Vstupní profil a Výstupní profil. Obě sady profilů se v podstatě zabývají tím, jak interpretovat rozměry ve vstupních a výstupních dokumentech, velikosti obrazovky a klíče úpravy měřítka výchozího písma.

Pokud víte, že soubor, který převádíte, byl zamýšlen k použití na konkrétním zařízení nebo softwarové platformě, zvolte odpovídající vstupní profil, jinak prostě zvolte výchozí vstupní profil. Pokud víte, že soubory, které vytváříte, jsou určeny pro konkrétní typ zařízení, zvolte odpovídající výstupní profil. Pokud víte, že soubory, které vytváříte, jsou určeny pro konkrétní typ zařízení, zvolte odpovídající výstupní profil. V opačném případě zvolte jeden z obecných výstupních profilů. Pokud převádíte na MOBI nebo AZW3, budete téměř vždy chtít zvolit jeden z výstupních profilů Kindle. V opačném případě je nejlepším řešením pro moderní zařízení pro čtení e-knih zvolit výstupní profil Obecné HD zařízení e-ink.

Výstupní profil také určuje velikost obrazovky. To například způsobí, že v některých výstupních formátech bude u obrázků automaticky změněna velikost, aby se přizpůsobily obrazovce. Takže zvolte profil zařízení, které má podobnou velikost obrazovky vašemu zařízení.

Heuristické zpracování

Heuristické zpracování poskytuje celou řadu funkcí, které mohou být použity pro pokus o rozpoznání a opravu běžných problémů ve špatně formátovaných vstupních dokumentech. Použijte tyto funkce, pokud váš vstupní dokument trpí špatným formátování Protože tyto funkce spoléhají na běžné vzory, uvědomte si, že v některých případech může volba vést k horším výsledkům, tak to používejte opatrně. Například několik z těchto voleb odebere všechny entity pevných mezer, nebo mohou zahrnovat falešně pozitivní shody souvisejících s funkcí.

Povolit heuristické zpracování

Tato volba aktivuje fázi Heuristického zpracování převodního řetězce Calibre. Musí to být povoleno, aby byly použity různé dílčí funkce.

Zrušit zalomení řádků

Povolení této volby způsobí, že se Calibre pokusí rozpoznat a opravit tvrdé pevné konce řádků existujících v dokumentu pomocí interpunkčních vodítek a délky řádku. Calibre se nejdříve pokusí rozpoznat, zda pevné konce řádků existují, pokud se zdá, že neexistují, Calibre se nepokusí zrušit zalomení řádků. Koeficient zrušení zalomení řádku může být zmenšen, pokud chcete ‚vynutit‘, aby Calibre zrušilo zalomení řádků.

Koeficient zrušení zalomení řádku

Tato volba určuje algoritmus, který Calibre používá k odebrání pevných konců řádků. Například pokud je hodnota této volby 0,4, znamená to, že Calibre odebere pevné konce řádků z konců řádků, jejichž délky jsou menší než délka 40 % všech řádků v dokumentu. Pokud má váš dokument pouze několik konců řádků, které potřebují opravu, pak by tato hodnota měla být snížena někam mezi 0,1 a 0,2.

Rozpoznat a označit neformátované nadpisy a podnadpisy kapitol

Pokud váš dokument neobsahuje nadpisy kapitol a názvy formátované odlišně od zbytku textu, Calibre může použít tuto volbu, aby se je pokusilo rozpoznat a obklopit je značkami nadpisů. Značky <h2> se používají pro nadpisy kapitol; značky <h3> se používají pro jakékoliv rozpoznané názvy.

Tato funkce nevytvoří obsah, ale v mnoha případech způsobí, že výchozí nastavení rozpoznání kapitol Calibre správně rozpozná kapitoly a sestaví obsah. Upravte XPath v Rozpoznávání struktury, pokud není obsah automaticky vytvořen. Pokud v dokumentu nejsou použity žádné další nadpisy, pak nastavení „//h:h2“ v Rozpoznávání struktury bude nejsnadnější způsob, jak vytvořit obsah pro dokument.

Tyto vložené nadpisy nejsou naformátovány, pro použití formátování použijte volbu Dodatečné CSS pod Vzhledem a chováním v nastavení převodu. Například pro značky zarovnání nadpisů na střed použijte následující:

h2, h3 { text-align: center }

Přečíslovat sekvence značek <h1> nebo <h2>

Někteří vydavatelé formátují nadpisy kapitol pomocí více značek <h1> nebo <h2> postupně. Výchozí nastavení převodu Calibre způsobí, že takové nadpisy budou rozděleny na dvě části. Tato volba přečísluje značky nadpisů, aby se zabránilo rozdělení.

Odstranit prázdné řádky mezi odstavci

Tato volba způsobí, že Calibre analyzuje prázdné řádky obsažené v dokumentu. Pokud je každý odstavec proložený prázdným řádkem, pak Calibre odebere všechny tyto prázdné odstavce. Sekvence více prázdných řádků budou považovány za přerušení příběhu a budou zachovány jako jeden odstavec. Tato volba se liší od volby Odebrat mezery mezi odstavci pod Vzhled a chování v tom, že skutečně mění obsah HTML, zatímco druhá volba mění styly dokumentu. Tato volba může také odebrat odstavce, které byly vloženy pomocí volby Calibre Vložit prázdný řádek.

Zajistit konzistentní formátování přerušení příběhu

Pomocí této volby se Calibre pokusí rozpoznat běžné značky přerušení příběhu a zajistit, aby byly zarovnané na střed. Značkám ‚měkkého‘ přerušení příběhu, tj. přerušení příběhu definované pouze dodatečnými prázdnými znaky, jsou změněny styly, aby se zajistilo, že nebudou zobrazeny ve spojení s konci stránek.

Nahradit přerušení příběhu

Pokud je tato volba konfigurována, pak Calibre nahradí značky přerušení příběhu, které najde, nahrazovacím textem zadaným uživatelem. Pamatujte, prosím, že některé ozdobné znaky nemusí být podporovány na všech čtecích zařízení.

Obecně byste se měli vyhnout používání značek HTML, Calibre zahodí jakékoliv značky a použije předem definované značení. Značky <hr />, tj. vodorovné čáry, a značky <img> jsou výjimky. Vodorovné čáry mohou být volitelně zadány styly, pokud se rozhodnete přidat vlastní styl, nezapomeňte zahrnout nastavení šířky ‚width‘, jinak bude informace stylu zahozena. Můžete použít značky obrázků, ale Calibre neposkytuje možnost přidat obrázek během převodu, to musí být provedeno po faktickém použití funkce ‚Upravit knihu‘.

Příklad značky obrázku (po převodu umístí obrázek do složky ‚Images‘ uvnitř souboru EPUB):

<img style=“width:10%“ src=“../Images/scenebreak.png“ />

Příklad vodorovné čáry se styly:

<hr style=“width:20%;padding-top: 1px;border-top: 2px ridge black;border-bottom: 2px groove black;“/>

Odebrat nadbytečné spojovníky

Calibre analyzuje veškerý obsah rozdělený spojovníky v dokumentu, když je tato volba povolena. Samotný dokument je použit jako slovník pro analýzu. To umožňuje Calibre přesně odebrat spojovníky pro jakákoliv slova v dokumentu v jakémkoliv jazyce, společně s vymyšlenými a neznámými vědeckými slovy. Primární nevýhodou je, že slova objevující se v dokumentu pouze jednou nebudou změněna. Analýza probíhá ve dvou průchodech, první průchod analyzuje konce řádků. Řádkům je zrušeno zalomení, pouze pokud v dokumentu slovo existuje s nebo bez spojovníku. Druhý průchod analyzuje všechna slova rozdělená spojovníky v celém dokumentu, spojovníky jsou odebrány, pokud slovo existuje někde jinde v dokumentu bez shody.

Převést na kurzívu obecná slova a vzory

Pokud je povoleno, bude Calibre hledat obecná slova a vzory, které označují kurzívu a převede je na kurzívu. Příkladem jsou obecné textové zásady, jako je ~slovo~, nebo fráze, které by obecně měly být kurzívou, např. latinské fráze, jako jsou ‚etc.‘ nebo ‚et cetera‘.

Nahradit odsazení entitami pomocí odsazeními CSS

Některé dokumenty používají zásadu definování odsazení textu pomocí entit pevných mezer. Když je tato volba povolena, Calibre se pokusí rozpoznat tento druh formátování a převést je na 3% odsazení textu pomocí CSS.

Hledat a nahradit

Tyto volby jsou užitečné především pro převody dokumentů PDF nebo převody OCR, i když mohou být také použity pro opravu mnoha konkrétních problémů dokumentů. Například některé převody mohou zanechat záhlaví a zápatí stránek v textu. Tyto volby používají regulární výrazy, aby se pokusily rozpoznat záhlaví, zápatí nebo jiný libovolný text a odebrat ho nebo nahradit. Nezapomeňte, že pracují s polotovarem XHTML vytvořeným převodním řetězcem. K dispozici je průvodce, aby vám pomohl přizpůsobit regulární výrazy pro váš dokument. Klikněte na kouzelnou hůlku vedle pole výrazu a klikněte na tlačítko ‚Tesovat‘ po sestavení vyhledávacího výrazu. Úspěšné shody budou zvýrazněny žlutě.

Vyhledávání funguje pomocí regulárního výrazu Pythonu. Všechny odpovídající texty jsou jednoduše odebrány z dokumentu nebo nahrazeny pomocí vzoru nahrazení. Vzor nahrazení je nepovinný, pokud zůstane prázdný, pak text odpovídající hledanému vzoru bude z dokumentu odstraněn. Další informace o regulárních výrazech a jejich syntaxi můžete zjistit v Vše o používání regulárních výrazů v Calibre.

Rozpoznávání struktury

Rozpoznávání struktury zahrnuje to, že se Calibre pokouší co nejlépe rozpoznat strukturální prvky ve vstupním dokumentu, pokud nejsou správně zadány. Například kapitoly, konce stránek, záhlaví, zápatí atd. Jak si dokážete představit, tento proces se velice liší knihu od knihy. Naštěstí má Calibre velice mocné možnosti, aby toto určilo. S mocí přichází složitost, ale pokud si jednou uděláte čas na to naučit se to složité, zjistíte, že to stálo za námahu.

Konce kapitol a stránek

Calibre má dvě sady možností pro rozpoznávání kapitol a vkládání konců stránek. To může být někdy trochu matoucí, protože ve výchozím nastavení Calibre vloží konce stránek před rozpoznané kapitoly, stejně jako před místa rozpoznaná možností konců stránek. Důvodem pro to je to, že často existují místa, kam by měly být vloženy konce stránek, která nejsou hranicemi kapitoly. Rozpoznané kapitoly mohou být také volitelně vloženy do automaticky generovaného obsahu.

Calibre používá XPath, výkonný jazyk, který umožňuje uživateli zadat hranice kapitol nebo konce stránek. XPath se může zdát nejdříve trochu skličující pro používání, naštěstí je zde kurz XPath v uživatelské příručce. Nezapomeňte, že Rozpoznávání struktury pracuje na polotovaru XHTML vytvořeného převodním řetězcem. Použijte možnost ladění popsanou v Úvod, abyste určili vhodné nastavení pro svou knihu. Je zde také tlačítko pro Průvodce XPath, aby pomohl s generováním jednoduchých výrazů XPath.

Ve výchozím nastavení používá Calibre následující výraz pro rozpoznávání kapitol:

//*[((name()='h1' or name()='h2') and re:test(., 'chapter|book|section|part\s+', 'i')) or @class = 'chapter']

Tento výraz je poměrně složitý, protože se snaží zpracovat velký počet běžných případů souběžně. To znamená, že Calibre bude předpokládat, že kapitoly začínají buď značkou <h1> nebo <h2>, které obsahují kterékoliv z těchto slov (kapitola, kniha, oddíl nebo část), nebo které mají atribut class=“kapitola“.

Související možností je Označení kapitol, který vám umožní určit, co Calibre udělá, když rozpozná kapitolu. Ve výchozím nastavení vloží konec stránky před kapitolu. Můžete ho nechat vložit linku namísto nebo navíc ke konci stránky. Můžete ho také nechat nedělat nic.

Výchozí nastavení pro rozpoznávání konců stránek je:

//*[name()='h1' or name()='h2']

což znamená, že Calibre ve výchozím nastavení vloží konce stránek před každou značku <h1> a <h2>.

Poznámka

Výchozí výrazy se mohou měnit v závislosti na vstupním formátu, který převádíte.

Různé

V tomto oddílu je několik dalších voleb.

Vložit metadata jako stránku na začátek knihy

Jednou ze skvělých věcí na Calibre je to, že vám umožňuje udržovat velice kompletní metadata o všech vašich knihách, například hodnocení, štítky, komentáře atd. Tato možnost vytvoří jednoduchou stránku se všemi těmito metadaty a vloží ji do převedené e-knihy, obvykle hned za obálku. Berte to jako způsob, jak si vytvořit svůj vlastní přizpůsobený přebal knihy.

Odebrat první obrázek

Někdy zdrojový dokument, který převádíte, obsahuje obálku jako součást knihy namísto samostatné obálky. Pokud jste také zadali obálku v Calibre, pak bude mít převedená kniha dvě obálky. Tato možnost jednoduše odebere první obrázek ze zdrojového dokumentu, čímž zajistí, že převedená kniha má pouze jednu obálku, tu zadanou v Calibre.

Obsah

Když má vstupní dokument ve svých metadatech obsah, Calibre ho prostě použije. Nicméně velké množství starších formátů buď nepodporuje obsah založený na metadatech nebo ho jednotlivé dokumenty nemají. V těchto případech vám volby v tomto oddílu mohou pomoct automaticky vygenerovat obsah v převáděné e-knize založený na skutečném obsahu ve vstupním dokumentu.

Poznámka

Používání těchto voleb může být trochu složitější, abyste získali přesně to pravé. Pokud upřednostňujete vytváření nebo úpravu obsahu ručně, převeďte na formáty EPUB nebo AZW3 a vyberte zaškrtávací políčko v dolní oddílu Obsah v dialogovém okně převodu, která říká: Ručně jemně ladit obsah po dokončení převodu. Tímto se po převodu spustí nástroj Upravit obsah. Umožní vám vytvářet položky v obsahu jednoduše kliknutím na místo v knize, kam chcete, aby položka ukazovala. Můžete také použít samotný nástroj Upravit obsah, aniž byste prováděli převod. Přejděte na Předvolby → Rozhraní → Panely nástrojů a přidejte Upravit obsah na hlavní panel nástrojů. Pak stačí vybrat knihu, kterou chcete upravit, a kliknout na tlačítko Upravit obsah.

První volba je Vynutit použití automaticky generovaného obsahu. Zaškrtnutím této volby můžete nechat Calibre přepsat jakýkoliv nalezený obsah v metadatech vstupního dokumentu automaticky vygenerovaným.

Výchozí způsob, jakým vytvoření automaticky vygenerovaného obsahu funguje, je ten, že Calibre nejdříve zkusí přidat všechny rozpoznané kapitoly do vygenerovaného obsahu. Jak přizpůsobit rozpoznávání kapitol můžete zjistit ve výše uvedeném oddílu Rozpoznávání struktury. Pokud nechcete zahrnout rozpoznané kapitoly do vygenerovaného obsahu, zaškrtněte volbu Nepřidávat rozpoznané kapitoly do obsahu.

Pokud bylo rozpoznáno méně kapitol, než je hodnota Mezní hodnota kapitol, Calibre pak přidá do obsahu všechny hypertextové odkazy, které najde ve vstupním dokumentu. Toto často funguje dobře na mnoha vstupních dokumentech, které obsahují hypertextové odkazy obsahu hned na začátku. Volba Počet odkazů může být použita k ovládání tohoto chování. Pokud je nastavena na nulu, nejsou přidány žádné odkazy. Pokud je nastavena na číslo větší než nula, je přidán nanejvýše tento počet odkazů.

Calibre automaticky vyfiltruje duplicity z generovaného obsahu. Pokud však jsou zde nějaké další nežádoucí položky, můžete je vyfiltrovat pomocí volby Filtr obsahu. To je regulární výraz, který porovná názvy položek ve vygenerovaném obsahu. Kdykoliv je nalezena shoda, bude odebrána. Například pro odebrání všech názvů položek „Další“ nebo „Předchozí“ použijte:

Next|Previous

Volby Obsah 1., 2. a 3. úrovně vám umožní vytvořit propracovaný víceúrovňový obsah. Jsou to výrazy XPath, které porovnají značky v polotovaru XHTML vytvořeného převodním řetězcem. Podívejte se na Úvod, jak získat přístup k tomuto XHTML. Přečtěte si také Kurz XPath, abyste zjistili, jak vytvářet výrazy XPath. Vedle každé volby je tlačítko, které spustí průvodce, který vám pomůže s vytvořením základních výrazů XPath. Následující jednoduchý příklad vysvětluje, jak používat tyto volby.

Předpokládejte, že máte vstupní dokument, jehož výstupem je XHTML, které vypadá takto:

<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<title>Sample document</title>
</head>
<body>
<h1>Chapter 1</h1>
...
<h2>Section 1.1</h2>
...
<h2>Section 1.2</h2>
...
<h1>Chapter 2</h1>
...
<h2>Section 2.1</h2>
...
</body>
</html>

Potom, nastavíme možnosti jako:

Level 1 TOC : //h:h1
Level 2 TOC : //h:h2

Toto bude mít za výsledek automaticky generovaný dvouúrovňový obsah, který vypadá jako:

Chapter 1
Section 1.1
Section 1.2
Chapter 2
Section 2.1

Varování

Ne všechny formáty podporují víceúrovňové obsahy. Nejdříve byste to měli zkusit s výstupem EPUB. Pokud to funguje, zkuste formát vlastního výběru.

Požití obrázku jako názvu kapitol pří konverzi HTML vstupních dokumentů

Předpokládejme, že chcete použít obrázek jako název kapitoly, ale stále chcete, aby Calibre bylo pro vás schopno automaticky generovat obsah z názvů kapitol. Použijte následující značení HTML, abyste toho dosáhli:

<html>
<body>
<h2>Chapter 1</h2>
<p>chapter 1 text...</p>
<h2 title="Chapter 2"><img src="chapter2.jpg" /></h2>
<p>chapter 2 text...</p>
</body>
</html>

Nastavte Obsah 1. úrovně na //h:h2. Pak pro druhou kapitolu Calibre vezme název z hodnoty atributu title značky <h2>, protože značka nemá žádný text.

Používání atributů značek pro dodání textu pro položky v obsahu

Pokud máte obzvlášť dlouhé názvy kapitol a chcete mít v obsahu zkrácené verze, můžete použít atribut názvu, abyste toho dosáhli, například:

<html>
<body>
<h2 title="Chapter 1">Chapter 1: Some very long title</h2>
<p>chapter 1 text...</p>
<h2 title="Chapter 2">Chapter 2: Some other very long title</h2>
<p>chapter 2 text...</p>
</body>
</html>

Nastavte Obsah 1. úrovně na //h:h2/@title. Pak Calibre vezme názvy z hodnot atributů title značek <h2>, namísto použití textu uvnitř značky. Pamatujte na koncové /@title ve výrazu XPath, tento formulář můžete použít, abyste řekli Calibre, aby získalo text z jakéhokoliv atributu chcete.

Jak jsou nastaveny nebo uloženy volby pro převod

V Calibre jsou dvě místa, kde mohou být nastaveny volby převodu. První je v Nastavení->Převod. Tato nastavení jsou výchozí pro volby převodu. Kdykoliv se pokusíte převést novou knihu, zde zadaná nastavení budou použita jako výchozí.

Můžete také změnit nastavení v dialogovém okně převodu pro převod každé knihy. Když převádíte nějakou knihu, Calibre si pamatuje nastavení použité pro tuto knihu, takže pokud ji převedete znovu, bude mít přednost uložené nastavení pro jednotlivou knihu před výchozími hodnotami nastavenými v předvolbách. Jednotlivé nastavení můžete obnovit na výchozí hodnoty pomocí tlačítka Obnovit výchozí nastavení v dialogovém okně převodu jednotlivé knihy. Uložené nastavení pro skupinu knih můžete odebrat výběrem všech knih a klepnutím na tlačítko Upravit metadata, které vyvolá dialogové okno hromadné úpravy metadat, v blízkosti spodní části dialogového okna je volba odebrat uložené nastavení převodu.

Když hromadně převádíte sadu knih, nastavení jsou brána v následujícím pořadí (poslední vyhrává):

- Z výchozího nastavení v Předvolby->Převod

- Z uloženého nastavení převodu pro každou převáděnou knihu (pokud existuje). Toto lze vypnout pomocí volby v levém horním rohu dialogového okna Hromadný převod.

- Z nastavení v dialogovém okně Hromadný převod

Pamatujte, že konečné nastavení pro každou knihu v Hromadném převodu bude uloženo a znovu použito, pokud je kniha znovu převáděna. Vzhledem k tomu, že nejvyšší priorita v Hromadném převodu je dána nastavením v dialogovém okně Hromadný převod, budou mít přednost před všemi konkrétními nastaveními knihy. Takže byste měli společně hromadně převádět pouze knihy, které potřebují podobná nastavení. Výjimkou jsou metadata a konkrétní nastavení formátu vstupu. Vzhledem k tomu, že dialogové okno Hromadný převod nemá nastavení pro tyto dvě kategorie, budou převzaty z konkrétního nastavení knihy (pokud existuje), nebo z výchozího nastavení.

Poznámka

Aktuální nastavení použité během jakéhokoliv převodu můžete zobrazit kliknutím na otáčející se ikonu v pravém dolním rohu a pak poklepáním na jednotlivou úlohu převodu. To zobrazí protokol převodu, který bude obsahovat aktuálně použitá nastavení na začátku.

Tipy pro konkrétní formáty

Zde najdete konkrétní tipy pro převod určitých formátů. Konkrétní volby pro určitý formát, zda je vstup nebo výstup dostupný v dialogovém okně převodu pod vlastním oddílem, například Vstup TXT nebo Výstup EPUB.

Převod dokumentů Microsoft Word

Calibre umí automaticky převést soubory .docx vytvořené v Microsoft Word 2007 a novějším. Stačí přidat soubor do Calibre a kliknout na tlačítko Převést.

Poznámka

Zde je ukázkový soubor .docx [https://calibre-ebook.com/downloads/demos/demo.docx], který ukazuje schopnosti převodního modulu Calibre. Stačí ho stáhnout a převést do formátu EPUB nebo AZW3, abyste viděli, co Calibre umí.

Calibre automaticky vygeneruje Obsah na základě nadpisů, pokud označíte nadpisy ve Wordu pomocí stylů Nadpis 1, Nadpis 2 atd. Otevřete výstupní e-knihu v prohlížeči Calibre a klikněte na tlačítko Obsah pro zobrazení vygenerovaného obsahu.

Starší soubory .doc

U starších souborů .doc můžete v Microsoft Word uložit dokument jako HTML a pak převést výsledný soubor HTML pomocí Calibre. Při ukládání jako HTML je nutné použít volbu „Uložit jako webovou stránku, zjednodušený formát“, protože to vytvoří čisté HTML, které se bude dobře převádět. Pamatujte, že Word vytváří opravdu chaotické HTML, jehož převod může trvat dlouho, takže buďte trpěliví. Pokud máte k dispozici novější verzi Wordu, můžete ho také přímo uložit jako .docx.

Jinou alternativou je využít bezplatné LibreOffice. Otevřete v LibreOffice svůj soubor .doc a uložte ho jako .docx, který může být přímo převeden v Calibre.

Převod dokumentů TXT

Dokumenty TXT nemají dobře definovaný způsob, jak zadávat formátování, jako je tučné, kurzíva atd, nebo strukturu dokumentu, jako jsou odstavce, nadpisy, oddíly a tak dále, ale existuje celá řada běžně používaných zásad. Ve výchozím nastavení se Calibre pokusí o automatické rozpoznání správného formátování a značení na základě těchto zásad.

Vstup TXT podporuje celou řadu voleb k odlišení, jak jsou rozpoznány odstavce.

Styl odstavců: Automaticky

Analyzuje textový soubor a pokusí se automaticky určit, jak jsou definovány odstavce. Tato volba bude obvykle fungovat dobře, pokud dosáhnete nežádoucí výsledky, zkuste jednu z ručních voleb.

Styl odstavců: Blok

Předpokládá, že jeden nebo více prázdných řádků jsou hranice odstavce:

This is the first.

This is the
second paragraph.

Styl odstavců: Jednoduchý

Předpokládá, že každý řádek je odstavec:

This is the first.
This is the second.
This is the third.

Styl odstavců: Tisk

Předpokládá, že každý odstavec začíná odsazením (buď tabulátor nebo 2 a více mezer). Odstavec končí, když je dosaženo dalšího řádku, který začíná odsazením:

This is the
first.
This is the second.

This is the
third.

Styl odstavců: Neformátovaný

Předpokládá, že dokument nemá žádné formátování, ale používá pevné konce řádků. Pro pokus o znovu vytvoření odstavců je použita interpunkce a střední délka řádku.

Styl formátování: Automaticky

Pokusí se rozpoznat použitý typ značek formátování. Pokud nejsou požity žádné značky, bude použito heuristické formátování.

Styl formátování: Heuristiké

Analyzuje v dokumentu běžné nadpisy kapitol, přerušení příběhu a slova kurzívou a použije příslušné značky HTML během převodu.

Styl formátování: Značkování

Calibre také podporuje průběh vstupu TXT transformačním předzpracováním známým jako Značkování. Značkování umožňuje přidání základního formátování do dokumentů TXT, jako je tučné, kurzíva, nadpisy oddílů, tabulky, seznamy, obsahy, atd. Označení nadpisů kapitol úvodním znakem # a nastavení výrazu XPath pro rozpoznání kapitol na „//h:h1“ je nejjednodušší způsob, jak získat řádný obsah vygenerovaný z dokumentu TXT. Více o Syntaxi značkování se můžete dozvědět na daringfireball [https://daringfireball.net/projects/markdown/syntax].

Styl formátování: Žádný

Nepoužije na text žádné zvláštní formátování, dokument je převeden na HTML bez žádných dalších změn.

Převod dokumentů PDF

Dokumenty PDF jsou jedním z nejhorších formátů, ze kterého převádět. Mají pevný formát velikosti stránky a umístění textu. To znamená, že je velice obtížné určit, kde končí jeden odstavec a začíná další. Calibre se pokusí zrušit zalomení odstavců pomocí konfigurovatelného Koeficientu nezalamování řádků. Je to měřítko použité k určení délky, při které by měl být řádek nezalomený. Platné hodnoty jsou desetinná čísla mezi 0 a 1. Výchozí hodnota je 0,45, těsně pod střední délkou řádku. Snižte tuto hodnotu pro zahrnutí více textu do nezalamování. Zvyšte pro zahrnutí méně. Tuto hodnotu můžete upravit v nastavení převodu pod Vstup PDF.

Často také mají záhlaví a zápatí jako součást dokumentu, které se zahrnou do textu. Použijte panel Najít a nahradit pro odebrání záhlaví a zápatí ke zmírnění tohoto problému. Pokud záhlaví a zápatí nejsou odebrány z textu, mohou rozhodit zrušení zalamování odstavců. Abyste se naučili, jak používat volbu odebírání záhlaví a zápatí, přečtěte si regexptutorial`.

Některá omezení vstupu PDF jsou:

- Nejsou podporovány dokumenty, které jsou složité, vícesloupcové a založené na obrázcích.

- Také není podporována extrakce vektorových obrázků a tabulek zevnitř dokumentu.

- Některá PDF používají speciální glyfy zastupující ll, ff nebo fi, atd. Jejich převod může nebo nemusí fungovat v závislosti na tom, jak jsou zastoupeny vnitřně v PDF.

- Nejsou podporovány odkazy a obsahy.

- PDF, která používají vložená písma nepodporující kód Unicode pro zastupování neanglických znaků, způsobí zkomolený výstup pro tyto znaky.

- Některá PDF jsou tvořena fotografiemi stránky s opticky rozpoznaným textem (OCR) za nimi. V takových případech Calibre použije opticky rozpoznaný text, což může být velice odlišné od toho, co vidíte, když si prohlížíte soubor PDF.

- PDF, která jsou použita pro zobrazení složitého textu, jako jsou jazyky zprava doleva a matematické vzorce, nebudou správně převedena.

Znovu opakujeme, že PDF je opravdu, ale opravdu špatný formát pro použití jako vstupu. Pokud musíte naprosto nutně použít PDF, pak buďte připraveni na výstup kdekoliv v rozmezí od slušného po nepoužitelný, v závislosti na vstupním PDF.

Sbírky komiksů

Sbírka komiksů je soubor .cbc. Soubor .cbc je soubor ZIP, který obsahuje jiné soubory CBZ nebo CBR. Soubor .cbc musí navíc obsahovat jednoduchý textový soubor nazvaný comics.txt kódovaný v UTF-8. Soubor comics.txt musí obsahovat seznam souborů komiksů uvnitř souboru .cbc v podobě názevsouboru:název, jak je zobrazeno níže:

one.cbz:Chapter One
two.cbz:Chapter Two
three.cbz:Chapter Three

Soubor .cbc pak bude obsahovat:

comics.txt
one.cbz
two.cbz
three.cbz

Calibre automaticky převede tento soubor .cbc na e-knihu s obsahem ukazujícím na každou položku v comics.txt.

Ukázka rozšířeného formátování EPUB

V tomto ukázkovém souboru [https://calibre-ebook.com/downloads/demos/demo.epub] je znázorněno různé rozšířené formátování pro soubory EPUB. Soubor byl vytvořen z ručně kódovaného HTML pomocí Calibre a je určen pro použití jako šablony pro vaše vlastní úsilí o vytváření EPUB.

Zdrojové HTML, ze kterého byl vytvořen, je dostupné v demo.zip [https://calibre-ebook.com/downloads/demos/demo.zip]. Nastavení použitá pro vytvoření EPUB ze souboru ZIP jsou:

ebook-convert demo.zip .epub -vv --authors "Kovid Goyal" --language en --level1-toc '//*[@class="title"]' --disable-font-rescaling --page-breaks-before / --no-default-epub-cover

Pamatujte, že protože tento soubor prozkoumává potenciál EPUB, většina rozšířeného formátování nebude fungovat ve čtečkách méně schopných, než je vestavěný prohlížeč EPUB Calibre.

Převod dokumentů ODT

Calibre umí přímo převést soubory ODT (OpenDocument Text). Pro formátování dokumentu byste měli používat styly a minimalizovat použití přímého formátování. Při vkládání obrázků do dokumentu je musíte ukotvit k odstavci, obrázky ukotvené ke stránce skončí všechny na začátku převodu.

Pro povolení automatického rozpoznání kapitol je musíte označit vestavěnými styly nazvanými Nadpis 1, Nadpis 2, …, Nadpis 6 (Nadpis 1 se rovná značce HTML <h1>, Nadpis 2 se rovná <h2> atd.). Při převodu v Calibre můžete zadat, který styl jste použili, do pole Rozpoznat kapitoly v. Příklad:

- Pokud označíte kapitoly stylem Nadpis 2, musíte nastavit pole ‚Rozpoznat kapitoly v‘ na //h:h2

- Pro vnořený obsah s oddíly označenými Nadpis 2 a kapitolami označenými ‚Nadpis 3 musíte zadat //h:h2|//h:h3. Na stránce Převod - Obsah nastavte pole Obsah 1. úrovně na //h:h2 a pole Obsah 2. úrovně na //h:h3.

Jsou rozpoznány dobře známé vlastnosti dokumentu (název, klíčová slova, popis, autor) a Calibre použije první obrázek (ne příliš malý a s dobrým poměrem stran) jako obráek obálky.

K dispozici je také převodní režim rozšířených vlastností, který je aktivován nastavením vlastní vlastnosti opf.metadata (typ ‚Ano nebo ne‘) na Ano ve vašem dokumentu ODT (Soubor->Vlastnosti->Vlastní vlastnosti). Pokud Calibre tuto vlastnost rozpozná, jsou rozpoznány následující vlastní vlastnosti (opf.authors přepíše autora dokumentu):

opf.titlesort
opf.authors
opf.authorsort
opf.publisher
opf.pubdate
opf.isbn
opf.language
opf.series
opf.seriesindex

Kromě toho můžete zadat obrázek, který se použije jako obálka, jeho pojmenováním opf.cover (klikněte pravým tlačítkem myši, Obrázek->Volby->Název) v ODT. Pokud není nalezen žádný obrázek s tímto názvem, použije se ‚chytrá‘ metoda. Protože rozpoznávání obálky může u určitých výstupních formátů vést ke dvěma obálkám, zpracování odebere odstavec (pouze pokud jediný obsah je obálka!) z dokumentu. Ale funguje to pouze s pojmenovaným obrázkem!

Pro zakázání rozpoznávání obálky můžete v rozšířeném režimu nastavit vlastní vlastnost opf.nocover (typ ‚Ano nebo Ne‘) na Ano.

Převádění do PDF

První, nejdůležitější nastavení, o kterém musíte rozhodnout při převodu na PDF, je velikost stránky. Ve výchozím nastavení Calibre používá velikost stránky „US Letter“. Toto můžete změnit na jinou standardní velikost stránky nebo zcela vlastní velikost v oddílu Výstup PDF dialogového okna převodu. Pokud generujete PDF, které má být použito na konkrétním zařízení, můžete namísto toho zapnout volbu použít velikost stránky z výstupního profilu . Takže pokud je váš výstupní profil nastaven na Kindle, Calibre vytvoří PDF s velikostí stránky vhodné pro zobrazení na malé obrazovce Kindle.

Záhlaví a zápatí

Zadáním šablony záhlaví a zápatí můžete vložit libovolná záhlaví a zápatí na každou stránku PDF. Šablony jsou prosté fragmenty kódu HTML, které se vykreslí v místech záhlaví a zápatí. Například pro zobrazení čísel stránek uprostřed dolní části každé stránky zeleně, použijte následující šablonu zápatí:

<footer><div style="margin: auto; color: green">_PAGENUM_</div></footer>

Calibre automaticky nahradí _PAGENUM_ aktuálním číslem stránky. Dokonce můžete dát různý obsah na sudé a liché stránky, například následující šablona záhlaví zobrazí název na lichých stránkách a autora na sudých stránkách:

<header style="justify-content: flex-end">
<div class="even-page">_AUTHOR_</div>
<div class="odd-page"><i>_TITLE_</i></div>
</header>

Calibre automaticky nahradí _TITLE_ a _AUTHOR_ názvem a autorem převáděného dokumentu. Nastavení justify-content na flex-end způsobí, že text bude zarovnán vpravo.

Můžete také zobrazit text na levých a pravých okrajích a měnit velikost písma, jak je zobrazeno na příkladu s touto šablonou záhlaví:

<header style="justify-content: space-between; font-size: smaller">
<div>_TITLE_</div>
<div>_AUTHOR_</div>
</header>

Toto zobrazí název vlevo a autora vpravo s velikostí písma menší než hlavní text.

V šablonách můžete také použít aktuální oddíl, jak je zobrazeno níže:

<header><div>_SECTION_</div></header>

_SECTION_ je nahrazeno bez ohledu na to, jaký je název aktuálního oddílu. Tyto názvy jsou převzaty z metadat obsahu v dokumentu (osnovy PDF). Pokud dokument nemá žádný obsah, pak bude nahrazeno prázdným textem. Pokud jedna stránka PDF má několik oddílů, bude použit první oddíl na stránce. Podobně je zde proměnná _TOP_LEVEL_SECTION_, která může být použita pro získání názvu aktuálního oddílu nejvyšší úrovně.

Uvnitř šablon záhlaví a zápatí dokonce můžete použít JavaScript, například následující šablona způsobí, že čísla stránek začnou od 4 namísto od 1:

<footer>
<div></div>
<script>document.currentScript.parentNode.querySelector("div").innerHTML = "" + (_PAGENUM_ + 3)</script>
</footer>

Níže jsou zdokumentovány některé další proměnné. které můžete použít v záhlavích a zápatích:

- _TOTAL_PAGES_ - celkový počet stránek v souboru PDF, užitečné například pro implementaci počítadla průběhu.

- _TOP_LEVEL_SECTION_PAGES_ - celkový počet stránek v aktuálním oddílu nejvyšší úrovně

- _TOP_LEVEL_SECTION_PAGENUM_ - číslo stránky aktuální stránky v aktuálním oddílu nejvyšší úrovně

Poznámka

Při přidávání záhlaví a zápatí se ujistěte, že jste v oddíle Výstup PDF v dialogovém okně převodu nastavili horní a dolní okraje stránky na dostatečně velké hodnoty.

Tisknutelný obsah

Na konec PDF můžete také vložit tisknutelný obsah, který vypíše čísla stránek pro každý oddíl. To je velmi užitečné, pokud zamýšlíte vytisknout PDF na papír. Pokud si přejete použít PDF na elektronickém zařízení, pak Osnova PDF poskytuje tuto funkci a je generována ve výchozím nastavení.

Vzhled generovaného obsahu můžete přizpůsobit pomocí nastavení Dodatečné CSS pod částí Vzhled a chování dialogového okna převodu. CSS použité ve výchozím nastavení je uvedeno níže, jednoduše ho zkopírujte proveďte v něm libovolné změny.

.calibre-pdf-toc table { width: 100%% }

.calibre-pdf-toc table tr td:last-of-type { text-align: right }

.calibre-pdf-toc .level-0 {
font-size: larger;
}

.calibre-pdf-toc .level-1 td:first-of-type { padding-left: 1.4em }
.calibre-pdf-toc .level-2 td:first-of-type { padding-left: 2.8em }

Vlastní okraje stránky pro jednotlivé soubory HTML

Pokud převádíte soubor EPUB nebo AZW3 s více jednotlivými soubory HTML uvnitř a chcete změnit okraje stránek pro konkrétní soubor HTML, můžete pomocí Editoru e-knih Calibre přidat do souboru HTML následující blok stylu:

<style>
@page {
margin-left: 10pt;
margin-right: 10pt;
margin-top: 10pt;
margin-bottom: 10pt;
}
</style>

Pak v oddílu výstupu PDF v dialogovém okně převodu zapněte volbu na Použít okraje stránek z převáděného dokumentu. Nyní budou mít všechny stránky generované z tohoto souboru HTML okraje 10pt.

© Copyright Kovid Goyal.
Naposledy aktualizováno kvě 03, 2024.

Navigace

- moduly
- další |
- předchozí |
- Spustit »
- Úprava e-knih

Úprava e-knih

Calibre má integrovaný editor e-knih, který může být použit pro úpravu knih ve formátech EPUB a AZW3 (Kindle). Editor vám zobrazí HTML a CSS, které jsou použity uvnitř souborů knih s živým náhledem, který se aktualizace, zatímco provádíte změny. Také obsahuje různé automatizované nástroje pro provedení úkolů běžného vyčištění a oprav.

Tento editor můžete použít kliknutím pravým tlačítkem myši na kteroukoliv knihu v Calibre a výběrem Upravit knihu.

Obsah

Základní pracovní postup

Prohlížeč souborů

- Přejmenovávání souborů

- Slučování souborů

- Změna pořadí textových souborů

- Označování obálky

- Odstraňování souborů

- Exportování souborů

- Přidávání nových obrázků, písem atd. nebo vytváření nových prázdných souborů

- Nahrazování souborů

- Efektivní propojování šablon stylů se soubory HTML

Hledat a nahradit

- Uložená hledání

- Režim funkcí

- Hledat a ignorovat značky HTML

Automatizované nástroje

- Upravování obsahu

- Kontrolování knihy

- Přidávání obálky

- Vkládání odkazovaných písem

- Částečné vkládání písem

- Vylepšení interpunkce

- Transformace vlastností CSS

- Odebírání nepoužívaných pravidel CSS

- Opravování HTML

- Zkrášlování souborů

- Vkládání vloženého obsahu

- Nastavování sémantiky

- Filtrování informací stylu

- Upgradování vnitřních prvků knihy

Kontrolní body

Panel Živý náhled

- Rozdělování souborů HTML

Panel Živé CSS

Různé nástroje

- Zobrazení obsahu

- Kontrola pravopisu slov v knize

- Vkládání speciálních znaků

- Zobrazení kontroly kódu

- Kontrola externích odkazů

- Stahování externích zdrojů

- Uspořádávání souborů do složek podle typu

- Importování souborů v jiných formátech e-knih než EPUB

- Nástroj Hlášení

Speciální funkce v editoru kódu

- Zvýraznění syntaxe

- Nápověda závislá na kontextu

- Automatické dokončování

- Fragmenty

Základní pracovní postup

Poznámka

Videoprohlídka Editoru e-knih Calibre je dostupná zde [https://calibre-ebook.com/demo#tutorials].

Když poprvé otevřete nástrojem pro úpravu knihy, bude vám vlevo nabídnut seznam souborů. Jsou to jednotlivé soubory HTML, šablony stylů, obrázky atd., které tvoří obsah knihy. Jednoduše poklepejte na soubor pro spuštění jeho úpravy. Pamatujte, že pokud chcete udělat něco složitějšího než provedení několika malých vylepšení, budete potřebovat znát Kurz HTML [http://html.net/tutorials/html/] a Kurz CSS [http://html.net/tutorials/css/].

Zatímco provádíte v editoru změny v HTML nebo CSS, bude se vpravo v panelu náhledu zobrazovat živý náhled změn. Pokud jste spokojeni s tím, jak vypadají změny, které jste provedli, klikněte na tlačítko Uložit nebo použijte Soubor → Uložit pro uložení změn do e-knihy.

Jednou z užitečných funkcí jsou Kontrolní body. Než se pustíte do nějaké ambiciózní sady úprav, můžete vytvořit kontrolní bod. Kontrolní bod zachová aktuální stav vaší knihy, pak pokud se v budoucnu rozhodnete, že se vám nelíbí změny, které jste provedli, můžete se vrátit do stavu, kdy jste vytvořili kontrolní bod. Abyste vytvořili kontrolní bod, použijte Úpravy → Vytvořit kontrolní bod. Kontrolní body budou pro vás také automaticky vytvořeny, kdykoliv spustíte jakýkoliv automatizovaný nástroj, jako je globální hledání a nahrazování. Funkce kontrolních bodů je doplněk k normálnímu mechanismu zpět/znovu při úpravách jednotlivých souborů. Kontrolní body jsou vyžadovány, když jsou změny rozděleny do více souborů v knize.

Toto je základní pracovní postup pro úpravy knih – Otevřete soubor, proveďte změny, zkontrolujte je a uložte. Zbytek této příručky probere různé přítomné nástroje a funkce, které vám umožní efektivně provést konkrétní úkoly.

Prohlížeč souborů

Prohlížeč souborů vám poskytuje přehled o různých souborech uvnitř knihy, kterou upravujete. Soubory jsou uspořádány podle kategorie s textovými soubory (HTML) nahoře, následovanými soubory šablon stylů (CSS), obrázky a tak dále. Jednoduše poklepejte na soubor pro spuštění jeho úpravy. Úprava je podporována pro soubory HTML, CSS a obrázky. Pořadí textových souborů je stejné, v jakém by byly zobrazeny, pokud byste četli knihu. Všechny ostatní soubory jsou uspořádány abecedně.

Přejetím myší nad položkou můžete vidět její velikost a také úplnou cestu k souboru uvnitř knihy ve spodní části obrazovky. Pamatujte, že soubory uvnitř e-knih jsou komprimované, takže velikost konečné knihy není součtem jednotlivých velikostí souborů.

Mnoho souborů má v knize zvláštní význam. Ty budou mít obvykle vedle svých názvů ikonu označující zvláštní význam. Například na obrázku vlevo můžete vidět, že soubory cover_image.jpg a titlepage.xhtml mají vedle sebe ikonu obálky, to znamená, že jsou to obrázek obálky a titulní stránka knihy. Podobně soubor content.opf má vedle sebe ikonu metadat, která označuje, že obsahuje metadata knihy, a soubor toc.ncx má vedle sebe ikonu T označující, že je to Obsah.

Na jednotlivých souborech můžete provádět mnoho akcí kliknutím pravým tlačítkem myši na ně.

Přejmenovávání souborů

Jednotlivý soubor můžete přejmenovat kliknutím pravým tlačítkem myši na něj a výběrem Přejmenovat. Přejmenování souboru automaticky aktualizuje všechny odkazy na něj v celé knize. Takže musíte pouze zadat nový název, Calibre se postará o zbytek.

Můžete také hromadně přejmenovat mnoho souborů najednou. To je užitečné, pokud chcete, aby soubory měly nějaký jednoduchý vzor názvu. Například můžete chtít přejmenovat všechny soubory HTML, aby měly názvy Kapitola-1.html, Kapitola-2.html a tak dále. Vyberte soubory, které chcete hromadně přejmenovat, podržením klávesy Shift nebo Ctrl a kliknutím na soubory. Pak klikněte pravým tlačítkem myši a vyberte Hromadně přejmenovat vybrané soubory. Zadejte předponu a číslo, kterým má začínat automatické číslování, klikněte na OK a je hotovo. Dialogové okno pro hromadné přejmenování také umožňuje přejmenování souborů podle pořadí, v jakém se objevují v knize namísto pořadí, v jakém jste je vybrali, což je užitečné například pro přejmenování všech obrázků podle pořadí, v jakém se zobrazí.

A konečně můžete hromadně změnit příponu souboru pro všechny vybrané soubory. Vyberte více souborů, jak je uvedeno výše, klikněte pravým tlačítkem myši a zvolte Změnit příponu souboru pro vybrané soubory.

Slučování souborů

Někdy můžete chtít sloučit dohromady dva soubory HTML nebo dva soubory CSS. Někdy může být užitečné mít vše v jednom souboru. Ale buďte opatrní, uložení velkého množství obsahu do jednoho souboru způsobí problémy s výkonem při zobrazování knihy v typické čtečce e-knih.

Pro sloučení více souborů dohromady je vyberte podržením klávesy Ctrl a kliknutím na ně (ujistěte se, že vybíráte pouze soubory jednoho typu, buď všechny soubory HTML nebo všechny soubory CSS a tak dále). Pak klikněte pravým tlačítkem myši a vyberte Sloučit. To je všechno, Calibre sloučí soubory, automaticky se postará o migraci všech odkazů na sloučené soubory. Pamatujte, že slučování souborů může někdy způsobit změnu stylu textů, protože jednotlivé soubory mohly používat různé šablony stylů.

Můžete také vybrat textové soubory a poté je přetáhnout na jiný textový soubor pro sloučení přetažených textových souborů do cílového textového souboru.

Změna pořadí textových souborů

Pořadí, v jakém se textové (HTML) soubory při čtení knihy otevírají, můžete jednoduše změnit přetažením v Prohlížeči souborů nebo kliknutím na soubor, který chcete přesunout, a následným stisknutím modifikátorů Ctrl+Shift s klávesami Page Up, Page Down, Home nebo End. Pro technicky zdatné se tomu říká změna pořadí hřbetu knihy.

Pamatujte, že položky je potřeba přetáhnout mezi jiné položky, nikoliv na ně, což může být trochu obtížné, dokud si na to nezvyknete. Přetažení na jiný soubor způsobí, že se soubory sloučí.

Označování obálky

E-knihy mají obvykle obrázek obálky. Tento obrázek je označen v Prohlížeči souborů ikonou hnědé knihy vedle názvu obrázku. Pokud chcete určit nějaký jiný obrázek jako obálku, můžete to provést kliknutím pravým tlačítkem myši na soubor a výběrem Označit jako obrázek obálky.

Navíc soubory EPUB mají koncept titulní stránky. Titulní stránka je soubor HTML, který se chová jako titulní stránka nebo obálka knihy. Označit soubor HTML jako titulní stránku při úpravě souborů EPUB můžete kliknutím pravým tlačítkem myši. Dávejte pozor na to, aby označený soubor obsahoval pouze informace o obálce. Pokud obsahuje jiný obsah, jako je první kapitola, pak bude tento obsah ztracen, pokud uživatel někdy převede soubor EPUB v Calibre do jiného formátu. To je proto, že při převodu Calibre předpokládá, že označená titulní stránka obsahuje pouze obálku a žádný jiný obsah.

Odstraňování souborů

Soubory můžete odstranit buď kliknutím pravým tlačítkem myši na ně, nebo jejich výběrem a stisknutím klávesy Delete. Odstranění souboru odebere všechny odkazy na soubor ze souboru OPF, což vám ušetří tu práci. Odkazy na jiných místech však odebrány nejsou, pro jejich snadné nalezení a odebrání nebo nahrazení můžete použít nástroj Zkontrolovat knihu.

Exportování souborů

Soubor zevnitř knihy můžete exportovat někam jinam ve vašem počítači. To je užitečné, pokud chcete pracovat na souboru izolovaně pomocí specializovaných nástrojů. Abyste to udělali, jednoduše klikněte pravým tlačítkem myši na soubor a zvolte Exportovat.

Jakmile dokončíte práci na exportovaném souboru, můžete ho znovu importovat do knihy novým kliknutím pravým tlačítkem myši na soubor a výběrem Nahradit souborem…, což vám umožní nahradit soubor v knize dříve exportovaným souborem.

Soubory můžete také kopírovat mezi více instancemi editoru. V Prohlížeči souborů vyberte soubory, které chcete zkopírovat, klikněte pravým tlačítkem myši a zvolte Kopírovat vybrané soubory do jiné instance editoru. Potom v jiné instanci editoru klikněte pravým tlačítkem myši v Prohlížeči souborů a zvolte Vložit soubor z jiné instance editoru.

Přidávání nových obrázků, písem atd. nebo vytváření nových prázdných souborů

Přidat nový obrázek, písmo, šablonu stylů atd. z vašeho počítače do knihy můžete kliknutím na Soubor → Nový soubor. To vám umožní buď importovat soubor kliknutím na tlačítko Importovat zdrojový soubor, nebo vytvořit nový prázdný soubor HTML nebo šablonu stylů jednoduše zadáním názvu souboru do pole pro nový soubor.

Můžete také importovat více souborů do knihy najednou pomocí Soubor-> Importovat soubory do knihy.

Nahrazování souborů

Existující soubory v knize můžete snadno nahradit kliknutím pravým tlačítkem myši na soubor a výběrem nahradit. Tím se automaticky aktualizují všechny odkazy a reference, pokud má nahrazující soubor jiný název než nahrazený soubor.

Efektivní propojování šablon stylů se soubory HTML

Pro větší pohodlí můžete v Prohlížeči souborů vybrat více souborů HTML, kliknout pravým tlačítkem myši a vybrat Propojit šablony stylů, aby Calibre automaticky vložilo značky <link> pro tyto šablony stylů do všech vybraných souborů HTML.

Hledat a nahradit

Upravit knihu má velice výkonné rozhraní hledání a nahrazení, které vám umožňuje hledat a nahradit text v aktuálním souboru, napříč všemi soubory a dokonce i v označené oblasti aktuálního souboru. Hledat můžete pomocí normálního hledání nebo pomocí regulárních výrazů. Abyste zjistili, jak používat regulární výrazy pro pokročilé vyhledávání, podívejte se na Vše o používání regulárních výrazů v Calibre.

Hledání a nahrazení spusťte pomocí položky nabídky Hledat → Najít a nahradit (musíte upravovat soubor HTML nebo CSS).

Napište text, který chcete najít, do pole Najít a jeho nahrazení do pole Nahradit. Můžete kliknout na příslušná tlačítka pro nalezení další shody, nahrazení aktuální shody a nahrazení všech shod.

Pomocí rozevíracích nabídek v dolní části okna můžete nechat hledání pracovat nad aktuálním souborem, všemi textovými soubory, všemi soubory stylů nebo všemi soubory. Můžete také zvolit režim hledání, aby byl normální (řetězec) nebo regulární výraz.

Všechny shody pro hledaný výraz můžete spočítat pomocí Hledat → Spočítat vše. Počítání se spustí nad všemi soubory nebo oblastmi, které jste vybrali v rozevíracím poli.

Můžete také přejít na konkrétní řádek v aktuálně otevřeném editoru pomocí Hledat → Přejít na řádek.

Poznámka

Nezapomeňte, že pro využití plného výkonu hledání a nahrazení budete muset použít regulární výrazy. Podívejte se na Vše o používání regulárních výrazů v Calibre.

Uložená hledání

Často používané výrazy hledání nebo nahrazení (včetně výrazů režimu funkcí) můžete uložit a vícekrát je znovu použít . Pro uložení hledání jednoduše klikněte pravým tlačítkem myši do pole Najít a vyberte Uložit aktuální hledání.

Uložená hledání můžete vyvolat pomocí Hledat → Uložená hledání. To zobrazí seznam výrazů hledání a nahrazení, které můžete použít. Můžete dokonce vybrat více položek v seznamu podržením klávesy Ctrl při kliknutí, aby se spustilo více výrazů hledání a nahrazení v jedné operaci.

Režim funkcí

Režim funkcí vám umožňuje psát libovolně výkonné funkce Pythonu, které jsou spuštěny na každém Najít a nahradit. V režimu funkcí můžete provést skoro jakékoliv zpracování textu, jaké chcete. Další informace naleznete v Režim funkcí pro Hledat a nahradit v editoru.

Hledat a ignorovat značky HTML

Je zde také vyhrazený nástroj pro hledání textu, který ignoruje jakékoliv značky HTML v něm obsažené. Například pokud kniha obsahuje HTML Přízvuk na <i>slově</i>., můžete hledat na slově a bude nalezeno, přestože je uprostřed značka <i>. Použijte tento nástroj prostřednictvím položky nabídky Hledat → Hledat a ignorovat značky HTML.

Automatizované nástroje

Editor knih má různé nástroje pro pomoc s běžnými úkoly. Ty jsou přístupné pomocí nabídky Nástroje.

Upravování obsahu

Pro usnadnění úpravy obsahu je zde specializovaný nástroj. Spustíte ho pomocí Nástroje → Obsah → Upravit obsah.

Nástroj Upravit obsah zobrazí aktuální Obsah (pokud existuje) vlevo. Jednoduše poklepejte na jakoukoliv položku pro změnu jejího textu. Můžete také přeuspořádat položky přetažením nebo pomocí tlačítek vpravo.

U knih, které nemají již existující Obsah, vám nástroj dává různé možnosti automatického vygenerování obsahu z textu. Můžete ho vygenerovat z nadpisů v dokumentu, z odkazů, z jednotlivých souborů a tak dále.

Jednotlivé položky můžete upravit kliknutím na ně a pak kliknutím na tlačítko Změnit umístění, na které tato položka ukazuje. To otevře malý náhled knihy, jednoduše přesuňte kurzor myši nad panel zobrazení knihy a klikněte na umístění, kam chcete, aby položka ukazovala. Tlustá zelená čára vám ukáže umístění. Klikněte na OK, jakmile jste spokojeni s umístěním.

Kontrolování knihy

Nástroj Zkontrolovat knihu vyhledá ve vaší knize problémy, které by mohly bránit zamýšlenému fungování na skutečných čtecích zařízeních. Aktivujte ho pomocí Nástroje → Zkontrolovat knihu.

Všechny nalezené problémy jsou nahlášeny v pěkném, snadno použitelném seznamu. Kliknutí na jakoukoliv položku v seznamu zobrazí nějakou nápovědu k této chybě a také vám dá možnost tuto chybu automaticky opravit, pokud chyba může být automaticky opravena. Můžete také poklepat na chybu pro otevření umístění chyby v editoru, abyste ji mohli opravit sami.

Některé z prováděných kontrol jsou:

- Poškozené značení HTML. Je nahlášeno jakékoliv značení HTML, které není analyzována jako dobře vytvořené XML. Oprava zajistí, že vaše značení funguje ve všech kontextech tak, jak má. Calibre také může automaticky opravit tyto chyby, ale automatická oprava může mít někdy neočekávané účinky, proto používejte opatrně. Jako vždy je před automatickou opravou vytvořen kontrolní bod, takže můžete snadno vrátit všechny změny. Automatická oprava funguje na základě analýzy značení pomocí algoritmu HTML5, který je vysoce odolný proti chybám, a pak převodem na dobře vytvořené XML.

- Poškozené nebo neznámé styly CSS. Je nahlášeno jakékoliv CSS, které není platné, nebo které má vlastnosti nedefinované ve standardu CSS 2.1 (a několik z CSS 3). CSS je kontrolováno ve všech šablonách stylů, vložených atributů stylů a značkách <style> v souborech HTML.

- Nefunkční odkazy. Jsou nahlášeny odkazy, které ukazují na soubory uvnitř knihy, které chybí.

- Neodkazované soubory. Jsou nahlášeny soubory v knize, na které neodkazuje žádný jiný soubor, nebo nejsou ve hřbetu.

- Různé běžné problémy v souborech OPF, jako jsou duplicitní položky hřbetu nebo manifestu, poškozené atributy idref nebo metaznačky obálek, chybějící požadované oddíly a tak dále.

- Různé kontroly kompatibility na známé problémy, které mohou způsobit nefunkčnost knihy ve čtecích zařízeních.

Přidávání obálky

Přidat obálku knize můžete snadno pomocí Nástroje → Přidat obálku. To vám umožní vybrat si jako obálku existující obrázek v knize nebo importovat nový obrázek do knihy a udělat z něj obálku. Při úpravě souborů EPUB je pro obálku automaticky vygenerováno obalující HTML. Pokud je v knize nalezena existující obálka, je nahrazena. Nástroj se také automaticky postará o správné označení souborů obálek jako obálek v OPF.

Vkládání odkazovaných písem

Přístupné pomocí Nástroje → Vložit odkazovaná písma. Tento nástroj najde všechna písma odkazovaná v knize, a pokud již nejsou vložena, vyhledá je ve vašem počítači a vloží je do knihy, pokud jsou nalezena. Ujistěte se, že máte potřebná autorská práva pro vkládání komerčně licencovaných písem, než to uděláte.

Částečné vkládání písem

Přístupné pomocí Nástroje → Částečně vložit písma. Tento nástroj zmenší všechna písma v knize tak, aby obsahovala pouze glyfy pro text skutečně přítomný v knize. Toto běžně zmenší velikost souborů písem o asi 50 %. Je však třeba si uvědomit, že jakmile jsou písma vložena částečně, tak pokud přidáte nový text, jehož znaky nejsou přítomné v částečně vloženém písmu, písmo nebude pro nový text fungovat. Takže toto dělejte pouze jako poslední krok ve vašem pracovním postupu.

Vylepšení interpunkce

Převede pomlčky, tři tečky, uvozovky, vícenásobné spojovníky atd. v prostém textu na jejich typograficky správné ekvivalenty. Pamatujte, že algoritmus může někdy generovat nesprávné výsledky, obzvláště pokud jde o jednoduché uvozovky na začátku zkrácených tvarů. Přístupné pomocí Nástroje → Vylepšit interpunkci.

Transformace vlastností CSS

Vytvořte pravidla pro transformaci stylování knihy. Například vytvořte pravidlo pro převod veškerého červeného textu na zelený nebo zdvojnásobte velikost písma veškerého textu v knize nebo změňte text určité rodiny písma na kurzívu atd.

Vytváření pravidel je jednoduché, pravidla dodržují formát přirozeného jazyka, který vypadá takto:

- Pokud vlastnost color (barva) je red (červená), změň ji na green (zelená)

- Pokud vlastnost font-size (velikost písma) je jakákoliv hodnota, vynásob hodnotu číslem 2

Dostupné prostřednictvím Nástroje → Transformace stylů.

Odebírání nepoužívaných pravidel CSS

Odebere všechna nepoužívaná pravidla CSS ze šablon stylů a značek <style>. Některé knihy vytvořené pomocí výrobních šablon mohou mít velký počet nadbytečných pravidel CSS, která neodpovídají žádnému skutečnému obsahu. Tato nadbytečná pravidla mohou zpomalit čtečky, které je musejí všechny zpracovat. Přístupné pomocí Nástroje → Odebrat nepoužívaná pravidla CSS.

Opravování HTML

Tento nástroj jednoduše převádí HTML, které nemůže být analyzováno jako XML, na dobře vytvořené XML. U e-knih je velice běžné, že nemají dobře vytvořené XML, takže tento nástroj jednoduše automatizuje proces opravy takového HTML. Nástroj funguje analýzou HTML pomocí algoritmu HTML5 (algoritmus použitý ve všech moderních prohlížečích) a poté převedením výsledek na XML. Uvědomte si, že automatické opravy mohou někdy mít neintuitivní výsledky. Pokud tomu dáváte přednost, můžete použít výše probíraný nástroj Zkontrolovat knihu k nalezení a ruční opravě problémů v HTML. Přístupné pomocí Nástroje → Opravit HTML.

Zkrášlování souborů

Tento nástroj se používá pro automatické formátování všech souborů HTML a CSS tak, aby „vypadali pěkně“. Kód je automaticky odsazený, takže je pěkně řazený, v případě potřeby jsou vloženy prázdné řádky a tak dále. Pamatujte, že zkrášlování také automaticky opraví poškozené HTML nebo CSS. Proto pokud nechcete, aby byly provedeny jakékoliv automatické opravy, použijte nejdříve nástroj Zkontrolovat knihu pro opravu všech problémů, a teprve pak spusťte zkrášlení. Přístupné pomocí Nástroje → Zkrášlit všechny soubory.

Poznámka

V HTML může mít jakýkoliv text významové prázdné znaky podle směrnice CSS o prázdných znacích. Proto by mohlo zkrášlení změnit vykreslení HTML. Aby se tomu zabránilo, pokud je to možné, algoritmus zkrášlení zkrášluje pouze značky na úrovni bloků, které obsahují jiné značky na úrovni bloků. Takže například text uvnitř značky <p> nebude mít změněny prázdné znaky. Ale značka <body>, která obsahuje pouze další značky <p> a <div>, bude zkrášlena. To může někdy znamenat, že určitý soubor nebude zkrášlením ovlivněn, protože nemá žádné vhodné značky na úrovni bloků. V takových případech můžete zkusit různé nástroje zkrášlení, které jsou méně opatrné, například: HTML Tidy <https://infohound.net/tidy/> _.

Vkládání vloženého obsahu

Normálně je v e-knihách obsah oddělen od hlavního textu a je obvykle přístupný prostřednictvím speciálního tlačítka nebo nabídky Obsah ve čtecím zařízení e-knih. Můžete také nechat Calibre automaticky vygenerovat vložený obsah, který se stane součástí textu knihy. Je generovaný na základě aktuálně definovaného obsahu.

Pokud tento nástroj použijete vícekrát, každé vyvolání způsobí že dříve vytvořený vložený obsah bude nahrazen. Nástroj je přístupný pomocí Nástroje → Obsah → Vložit vložený obsah.

Nastavování sémantiky

Tento nástroj se používá k nastavení sémantiky v souborech EPUB. Sémantika jsou jednoduše odkazy v souboru OPF, které identifikují určitá umístění v knize, že mají zvláštní význam. Můžete je použít k identifikaci předmluvy, věnování, obálky, obsahu atd. Jednoduše zvolte typ sémantické informace, který chcete zadat, a pak vyberte umístění v knize, na které má odkaz směřovat. Tento nástroj je přístupný pomocí Nástroje → Nastavit sémantiku.

Filtrování informací stylu

Tento nástroj může být použit pro snadné odebrání zadaných vlastností stylů CSS z celé knihy. Můžete mu říct, které vlastnosti chcete odebrat, například color, background-color, line-height, a budou odebrány ze všech míst, kde se vyskytují — šablony stylů, značky <style> a vložené atributy style. Po odebrání informací o stylu se zobrazí souhrn všech provedených změn, takže můžete vidět, co přesně se změnilo. Nástroj je přístupný pomocí Nástroje → Filtrovat informace o stylu.

Upgradování vnitřních prvků knihy

Tento nástroj může být použit pro upgrade vnitřních prvků knihy. Například upgraduje knihy EPUB 2 na knihy EPUB 3. Nástroj je dostupný prostřednictvím Upgradovat vnitřní prvky knihy.

Kontrolní body

Kontrolní body jsou způsob, jak označit aktuální stav knihy jako „speciální“. Pak můžete pokračovat v provádění jakýchkoliv změn v knize, a pokud se vám nelíbí výsledky, můžete se vrátit do stavu kontrolního bodu. Kontrolní body jsou automaticky vytvořeny při každém spuštění některého z automatizovaných nástrojů popsaných v předchozí části.

Kontrolní bod můžete vytvořit pomocí Úpravy → Vytvořit kontrolní bod. A vrátit se k předchozímu kontrolnímu bodu pomocí Úpravy → Vrátit …

Funkce kontrolních bodů je doplněk k normálnímu mechanismu Zpět/Znovu při úpravách jednotlivých souborů. Kontrolní body jsou vyžadovány, když jsou změny rozděleny do více souborů v knize, nebo pokud chcete být schopni vrátit velkou skupinu souvisejících změn jako celku.

Seznam dostupných kontrolních bodů můžete zobrazit pomocí Zobrazení → Kontrolní body. Porovnat současný stav knihy se zadaným kontrolním bodem můžete pomocí nástroje Porovnání e-knih – výběrem kontrolního bodu a kliknutím na tlačítko Porovnat. Tlačítko Vrátit obnoví knihu na vybraný kontrolní bod a zruší všechny změny od vytvoření kontrolního bodu.

Panel Živý náhled

Náhled souboru vám poskytuje přehled o různých souborech uvnitř knihy. Panel Živý náhled vám živě ukazuje změny, které provádíte (se zpožděním jedné nebo dvou sekund). Jak upravujete soubory HTML nebo CSS, panel náhledu se automaticky aktualizuje, aby odrážel změny. Když pohybujete kurzorem v editoru, panel náhledu sleduje jeho umístění a ukazuje vám odpovídající umístění v knize. Kliknutí do panelu náhledu způsobí, že kurzor v editoru bude umístěn nad prvek, na který jste kliknuli. Pokud kliknete na odkaz směřující na jiný soubor v knize, bude tento soubor automaticky otevřen panelu úprav a náhledu.

Automatickou synchronizaci pozice a živého náhledu změn můžete vypnout pomocí tlačítek pod panelem náhledu. K živé aktualizaci panelu náhledu dochází pouze tehdy, pokud aktivně nepíšete v editoru, abyste nebyli rušeni nebo zpomalováni čekáním na vykreslení náhledu.

Panel náhledu vám ukazuje, jak bude text vypadat při zobrazení. Panel náhledu však není náhradou za skutečné testování vaší knihy ve skutečném čtecím zařízení. Je jak více, tak méně schopný než skutečná čtečka. Bude tolerovat chyby a nedbalé značení mnohem lépe než většina čtecích zařízení. Také nezobrazí okraje stránek, konce stránek a vložená písma, která používají aliasy názvu písma. Používejte panel náhledu při práci na knize, ale jakmile skončíte, zkontrolujte ji ve skutečném čtecím zařízení nebo softwarovém emulátoru.

Poznámka

Panel náhledu nepodporuje vložená písma, pokud název písma uvnitř souboru písma neodpovídá názvu v pravidle CSS @font-face. Můžete použít nástroj Zkontrolovat knihu pro rychlé nalezení a opravu jakýchkoliv takových problémových písem.

Rozdělování souborů HTML

Jedno možná ne tak zřejmé použití panelu náhledu je rozdělení dlouhých souborů HTML. Během zobrazení souboru, který chcete rozdělit, klikněte na tlačítko Režim rozdělení pod panelem náhledu . Pak jednoduše přesuňte myš na místo, kde chcete rozdělit soubor, a klikněte. Tlustá zelená čára vám ukáže, kde přesně dojde k rozdělení, když budete pohybovat myší. Jakmile najdete umístění, které chcete, jednoduše klikněte a rozdělení bude provedeno.

Rozdělení souboru automaticky aktualizuje všechny odkazy, které odkazují na dolní polovinu souboru, a otevře nově rozdělený soubor v editoru.

Jeden soubor HTML můžete také automaticky rozdělit na více místech kliknutím pravým tlačítkem myši uvnitř souboru v editoru a výběrem Rozdělit na více místech. To vám umožní snadno rozdělit velký soubor na všech značkách nadpisů nebo všech značkách, které mají určitou třídu, a tak dále.

Panel Živé CSS

Panel Živé CSS zobrazí všechna pravidla stylů, která jsou použita na značku, kterou právě upravujete. Je zobrazen název značky společně s jejím číslem řádku v editoru následovaný seznamem odpovídajících pravidel stylů.

Je to skvělý způsob, jak rychle zjistit, která pravidla stylů jsou použita na jakoukoliv značku. Zobrazení má také kliknutelné odkazy (modře), které vás přenesou přímo na místo, kde byl styl definován, pokud chcete provést jakékoliv změny v pravidlech stylu. Jsou zobrazen pravidla stylů, která jsou použita přímo na značku, stejně jako pravidla, která jsou zděděna z nadřazených značek.

Panelu také ukazuje, jaké jsou konečné vypočítané styly pro značku. Vlastnosti v seznamu, které jsou nahrazeny pravidly s vyšší prioritou, jsou zobrazeny přeškrtnuty čárou.

Panel Živé CSS můžete povolit pomocí Zobrazení → Živé CSS.

Různé nástroje

Je zde několik dalších nástrojů, které mohou být užitečné při úpravách knihy.

Zobrazení obsahu

Zobrazení obsahu vám ukazuje aktuální obsah v knize. Poklepáním na jakoukoliv položku se v editoru otevře místo, na které položka ukazuje. Pro úpravu obsahu, aktualizaci zobrazení nebo rozbalení a sbalení všech položek můžete kliknout pravým tlačítkem myši. Toto zobrazení je přístupné pomocí Zobrazení → Obsah.

Kontrola pravopisu slov v knize

Kontrolu pravopisu můžete spustit pomocí Nástroje → Zkontrolovat pravopis.

Slova jsou zobrazena s počtem výskytů v knize a jazykem, do kterého slovo patří. Informace o jazyku je převzata z metadat knihy a z atributů lang v souborech HTML. To umožňuje, aby kontrola pravopisu fungovala dobře i s knihami, které obsahují text ve více jazycích. Například v následujícím úryvku HTML, bude slovo color zkontrolováno pomocí americké angličtiny a slovo colour pomocí britské angličtiny:

<div lang="en_US">color <span lang="en_GB">colour</span></div>

Poznámka

Pro zvýraznění dalšího výskytu slova můžete poklepat na toto slovo v editoru. To je užitečné, pokud chcete ručně upravit slovo, nebo vidět, v jakém je v kontextu.

Pro změnu slova jednoduše poklepejte na jeden z nabízených alternativních pravopisů vpravo, nebo napište svůj vlastní opravený pravopis a klikněte na tlačítko Změnit vybrané slovo na. To nahradí všechny výskyty slova v knize. Můžete také kliknout pravým tlačítkem myši na slovo v hlavním seznamu slov pro pohodlnou změnu slova z nabídky zobrazené po kliknutí pravým tlačítkem myši.

Můžete nechat kontrolu pravopisu ignorovat slovo pro aktuální relaci kliknutím na tlačítko Ignorovat. Můžete také přidat slovo do uživatelského slovníku kliknutím na tlačítko Přidat slovo do slovníku. Kontrola pravopisu podporuje více uživatelských slovníků, takže můžete vybrat slovníku, do kterého chcete slovo přidat.

Můžete také nechat kontrolu pravopisu zobrazit všechna slova v knize, ne jen ta špatně napsaná. To je užitečné, abyste viděli, která slova jsou v knize nejčastější a ke spuštění jednoduchého hledání a nahrazení na jednotlivých slovech.

Poznámka

Pokud provedete jakékoliv změny v knize úpravou souborů, když je otevřen nástroj kontroly pravopisu, měli byste kliknout na tlačítko Aktualizovat v nástroji Kontrola pravopisu. Pokud to neuděláte a budete pokračovat v používání nástroje Kontrola pravopisu, mohli byste přijít o změny provedené v editoru.

Poznámka

Pro vyloučení jednotlivého souboru z kontroly pravopisu při spuštění nástroje kontroly pravopisu můžete použít tlačítko Vyloučit soubory nebo přidat následující komentář těsně pod otevírací značku v souboru:

<!-- calibre-no-spell-check -->

Přidávání nových slovníků

Kontrola pravopisu obsahuje vestavěné slovníky pro angličtinu a španělštinu. Vlastní slovníky můžete nainstalovat pomocí Úpravy → Předvolby → Nastavení editoru → Spravovat pravopisné slovníky. Kontrola pravopisu může použít slovníky z programu LibreOffice (ve formátu .oxt). Tyto slovníky si můžete stáhnout z Repozitáře rozšíření LibreOffice [https://extensions.libreoffice.org/?Tags%5B%5D=50].

Vkládání speciálních znaků

Znaky, které jsou obtížně napsat, můžete vložit pomocí nástroje Úpravy → Vložit speciální znak. Ten vám zobrazí všechny znaky Unicode, jednoduše klikněte na znak, který chcete zadat. Pokud při kliknutí podržíte klávesu Ctrl, okno se samo zavře po vložení vybraného znaku. Tento nástroj může být použit k vložení speciálních znaků do hlavního textu, nebo do jakékoliv jiné oblasti uživatelského rozhraní, jako je například nástroj Hledat a nahradit.

Protože existuje spousta znaků, můžete definovat své Oblíbené znaky, které budou zobrazeny jako první. Jednoduše klikněte pravým tlačítkem myši na znak pro jeho označení jako oblíbeného. Můžete také kliknout pravým tlačítkem myši na znak v oblíbených pro jeho odebrání z oblíbených. A konečně můžete přeuspořádat pořadí znaků v oblíbených kliknutím na tlačítko Přeuspořádat oblíbené a přetažením znaku v oblíbených.

Můžete také přímo zadat speciální znaky pomocí klávesnice. Abyste to udělali, zadejte kód Unicode pro znak (šestnáctkově) a pak stiskněte Alt+X, což převede dříve zadaný kód na odpovídající znak. Například abyste napsali ÿ, napsali byste ff a pak Alt+X. Pro zadání pevné mezery byste použili a0 a pak Alt+X, pro zadání vodorovných tří teček byste použili 2026 a Alt+X a tak dále.

A nakonec speciální znaky můžete zadat pomocí pojmenovaných entit HTML. Například zadání &nbsp; bude nahrazeno pevnou mezerou, když napíšete středník. K nahrazení dojde, pouze když napíšete středník.

Zobrazení kontroly kódu

Toto zobrazení vám ukáže kódování HTML a CSS, které jsou použity na aktuální prvek. Otevřete ho kliknutím pravým tlačítkem myši na místo v panelu náhledu a výběrem Zkontrolovat prvek. To vám umožní zobrazit kódování HTML pro tento prvek a, co je důležitější, styly CSS, které jsou na něj použity. Můžete dokonce dynamicky upravovat styly a okamžitě uvidíte, jaký vliv mají vaše změny. Pamatujte, že úprava stylů ve skutečnosti neprovádí změny obsahu knihy, umožňuje to pouze rychlé experimentování. Schopnost živé úpravy uvnitř kontroly prvku je ve vývoji.

Kontrola externích odkazů

Tento nástroj můžete použít ke kontrole všech odkazů ve své knize, které odkazují na externí webové stránky. Nástroj se pokusí navštívit každou externě odkazovanou webovou stránku, a pokud návštěva selže, nahlásí všechny poškozené odkazy v pro vás vhodném formátu, abyste je opravili.

Stahování externích zdrojů

Tento nástroj můžete použít pro automatické stáhnutí jakýchkoliv obrázků, šablon stylů atd. v knize, které nejsou v sadě s knihou (tj. mají adresy URL ukazující na umístění na internetu). Nástroj najde všechny takové zdroje a automaticky je stáhne, přidá je ke knize a nahradí všechny odkazy na ně, aby používaly stažené soubory.

Uspořádávání souborů do složek podle typu

Při úpravách souborů EPUB, které jste odněkud získali, často zjistíte, že soubory uvnitř EPUB jsou uspořádány nahodile v různých podsložkách. Tento nástroj vám umožňuje automaticky přesunout všechny soubory do podsložek na základě jejich typů. Přístupné je to pomocí Nástroje → Uspořádat do složek. Pamatujte, že tento nástroj pouze mění způsob, jakým jsou soubory uspořádány uvnitř EPUB, ale nemění to, jak jsou zobrazeny v Prohlížeči souborů.

Importování souborů v jiných formátech e-knih než EPUB

Editor obsahuje schopnost přímo importovat soubory v některých jiných formátech e-knih jako nový EPUB, aniž by prošly úplným převodem. To je obzvlášť užitečné pro přímé vytvoření souborů EPUB z vašich ručně upravených souborů HTML. Můžete to udělat pomocí Soubor → Importovat soubor HTML nebo DOCX jako novou knihu.

Nástroj Hlášení

Editor obsahuje pěkný nástroj Hlášení (přes Nástroje → Hlášení), který zobrazuje souhrny souborů, obrázků, odkazů, slov, znaků a stylů použitých v knize. Každý řádek v hlášení je propojený. Poklepáním na řádek skočíte na místo v knize, kde je tato položka použita nebo definována (podle potřeby). Například v zobrazení Odkazy můžete poklepat na položky sloupce Zdroj, abyste skočili tam, kde je odkaz definován, a na položky ve sloupci Cíl, abyste skočili, kam odkaz ukazuje.

Speciální funkce v editoru kódu

Editor HTML v Calibre je velice výkonný. Má mnoho funkcí, díky kterým je úprava HTML (a CSS) jednodušší.

Zvýraznění syntaxe

Editor HTML má velice propracované zvýraznění syntaxe. Funkce zahrnují:

- Text uvnitř značek pro tučné písmo, kurzívu a nadpisy jsou tučné nebo kurzívou

- Jak přesouváte kurzor přes HTML, zvýrazní se odpovídající značky HTML jsou zvýrazněny a pomocí klávesových zkratek můžete přeskočit na otevírací nebo ukončovací značku Ctrl+{ a :kdb:`Ctrl+}`. Podobně můžete vybrat obsah značky pomocí Ctrl+Alt+T nebo Ctrl+Shift+T.

- Neplatné HTML je zvýrazněno červeným podtržením

- Pravopisné chyby v textu uvnitř značek HTML a atributy, jako jsou názvy, jsou zvýrazněny. Kontrola pravopisu je schopná rozpoznat jazyk na základě hodnoty atributu lang aktuální značky a celkového jazyka knihy.

- CSS vložené uvnitř značek <style> je zvýrazněno

- Speciální znaky, které může být těžké rozlišit, jako jsou pevné mezery, různé druhy pomlček atd., jsou zvýrazněny.

- Odkazy na jiné soubory ve značkách <a>, <img> a <link> mají všechny zvýrazněné názvy souborů. Pokud odkazují na název souboru, který neexistuje, je název souboru označen červeným podtržením.

Nápověda závislá na kontextu

Pro získání nápovědy ke značce HTML nebo vlastnosti CSS můžete kliknout pravým tlačítkem myši na název této značky nebo vlastnosti.

Můžete také podržet klávesu Ctrl a kliknout na jakýkoliv název souboru uvnitř značky odkazu pro automatické otevření tohoto souboru v editoru. Podobně Ctrl a kliknutím na název třídy se dostanete k prvnímu pravidlu stylu, které odpovídá značce a třídě.

Kliknutí pravým tlačítkem na název třídy v souboru HTML vám umožní přejmenovat třídu a změnit tak všechny výskyty třídy v celé knize a ve všech jejích šablonách stylů.

Automatické dokončování

Při úpravě e-knihy je jedním z nejobtížnějších úkolů vytvořit odkazy na jiné soubory uvnitř knihy, šablony stylů CSS nebo obrázky. Musíte vyřešit správný název souboru a relativní cestu k souboru. Editor má automatické dokončování, aby toto zjednodušil.

Během psaní názvu souboru editor automaticky nabízí návrhy. Jednoduše použijte klávesu Tab pro výběr správného názvu souboru. Editor dokonce nabízí návrhy pro odkazy ukazující na kotvu uvnitř jiného souboru HTML. Po napsání znaku # zobrazí editor seznam všech kotev v cílovém souboru s malým úryvkem textu, aby vám pomohl zvolit správnou kotvu.

Pamatujte, že na rozdíl od většiny ostatních systémů dokončování, systém dokončování editoru používá podposloupnou shodu. To znamená, že pro dokončení názvu souboru můžete zadat jen dvě nebo tři písmena odkudkoliv z názvu souboru . Řekněme například, že když chcete název souboru ../images/arrow1.png, můžete jednoduše napsat ia1 a stisknout Tab pro dokončení názvu souboru. Při hledání shod systém dokončování upřednostňuje písmena, která jsou na začátku slova, nebo ihned po oddělovači cesty. Jakmile si zvyknete na tento systém, zjistíte, že vám ušetří spoustu času a úsilí.

Fragmenty

Editor e-knih Calibre podporuje fragmenty. Fragment je kus textu, který je buď často znovu použit nebo obsahuje hodně nadbytečného textu. Editor vám umožňuje vložit fragment pomocí jen několika stisků kláves. Fragmenty jsou velice výkonné, s mnoha funkcemi, jako jsou zástupné symboly, mezi kterými můžete přeskakovat, automatické zrcadlení opakované textu a tak dále. Pro více informací navštivte Fragmenty.

© Copyright Kovid Goyal.
Naposledy aktualizováno kvě 03, 2024.

Navigace

- moduly
- další |
- předchozí |
- Spustit »
- Úprava e-knih »

- Režim funkcí pro Hledat a nahradit v editoru

Režim funkcí pro Hledat a nahradit v editoru

Nástroj Hledat a nahradit v editoru podporuje režim funkcí. V tomto režimu můžete kombinovat regulární výrazy (viz: Vše o používání regulárních výrazů v Calibre) s libovolnými výkonnými funkcemi Pythonu pro provádění všech druhů rozšířeného zpracování textu.

Ve standardním režimu regulární výraz pro hledání a nahrazení zadáváte jak regulární výraz pro hledání, tak šablonu, která je použita pro nahrazení všech nalezených shod. V režimu funkcí, namísto použití pevné šablony, zadáte libovolnou funkci v programovacím jazyce Python [https://docs.python.org/]. Umožní vám to udělat spoustu věcí, které nejsou možné u jednoduchých šablon.

Techniky pro použití režimu funkcí a syntaxe budou popsány pomocí příkladů, které vám ukáží, jak vytvořit funkce pro provádění postupně složitějších úloh.

Automatická oprava velikosti písmen nadpisů v dokumentu

Zde budeme využívat jednu z vestavěných funkcí v editoru pro automatickou změnu velikosti písmen celého textu uvnitř značek nadpisů, aby byla všechna první písmena velká:

Find expression: <([Hh][1-6])[^>]*>.+?</\1>

Pro funkci jednoduše zvolte vestavěnou funkci Všechna první velká (ignorovat značky). Toto změní nadpisy, které vypadají takto: <h1>nějaký NADPIS</h1> na <h1>Nějaký Nadpis</h1>. Bude to fungovat, i když jsou jiné značky HTML uvnitř značek nadpisů.

Vaše první vlastní funkce – vylepšení pomlček

Skutečná síla režimu funkcí pochází ze schopnosti vytvářet vaše vlastní funkce pro zpracování textu libovolnými způsoby. Nástroj Vylepšit interpunkci v editoru nechává samostatné pomlčky být, takže můžete použít tuto funkci pro jejich nahrazení dlouhými pomlčkami.

Pro vytvoření nové funkce jednoduše klikněte na tlačítko Vytvořit nebo upravit pro vytvoření nové funkce a zkopírujte níže uvedený kód Pythonu.

def replace(match, number, file_name, metadata, dictionaries, data, functions, *args, **kwargs):
return match.group().replace('--', '—').replace('-', '—')

Každá vlastní funkce Najít a nahradit musí mít jedinečný název a skládat se z funkce Pythonu s názvem replace, která přijímá všechny výše uvedené parametry. Pro tuto chvíli se nebudeme starat o všechny různé parametry funkce replace(). Soustředíme se pouze na parametr match. Zastupuje shodu při spuštění hledat a nahradit. Jeho úplná dokumentace je dostupná zde [https://docs.python.org/library/re.html#match-objects]. match.group() jednoduše vrátí všechny odpovídající texty a vše, co uděláme je, že nahradíme pomlčky v tomto textu dlouhými pomlčkami, nejdříve se dvojité pomlčky a potom jednotlivé pomlčky.

Použijte tuto funkci s hledáním regulárního výrazu:

>[^<>]+<

A nahradí to všechny pomlčky dlouhými pomlčkami, ale pouze ve skutečném textu a ne uvnitř definicí značek HTML.

Síla režimu funkcí – používání pravopisného slovníku pro opravu chybně rozdělených slov spojovníkem

E-knihy vytvořené naskenováním tištěných knih často obsahují chybně rozdělená slova spojovníkem – slova, která byla rozdělena na konci řádku na vytištěné stránce. Napíšeme si jednoduchou funkci pro automatické nalezení a opravení takových slov.

import regex
from calibre import replace_entities
from calibre import prepare_string_for_xml

def replace(match, number, file_name, metadata, dictionaries, data, functions, *args, **kwargs):

def replace_word(wmatch):
# Try to remove the hyphen and replace the words if the resulting
# hyphen free word is recognized by the dictionary
without_hyphen = wmatch.group(1) + wmatch.group(2)
if dictionaries.recognized(without_hyphen):
return without_hyphen
return wmatch.group()

# Search for words split by a hyphen
text = replace_entities(match.group()[1:-1]) # Handle HTML entities like &amp;
corrected = regex.sub(r'(\w+)\s*-\s*(\w+)', replace_word, text, flags=regex.VERSION1 | regex.UNICODE)
return '>%s<' % prepare_string_for_xml(corrected) # Put back required entities

Použijte tuto funkci se stejným vyhledávacím výrazem jako předtím, a to:

>[^<>]+<

A jako kouzlem to opraví všechna chybně rozdělená slova v textu knihy. Hlavní trik je použít jeden z užitečných dodatečných parametrů pro funkci nahrazení, dictionaries. Ten odkazuje na slovníky, které samotný editor používá pro kontrolu pravopisu textu v knize. Tato funkce hledá slova oddělená spojovníkem, odebere spojovník a zkontrolujte, zda slovník rozpozná složené slovo. Pokud ano, původní slova jsou nahrazena složeným slovem bez spojovníku.

Pamatujte, že jediné omezení této techniky je, že to bude fungovat pouze pro jednojazyčné knihy, protože dictionaries.recognized() ve výchozím nastavení používá hlavní jazyk knihy.

Automatické číslování oddílů

Nyní uvidíme něco trochu jiného. Předpokládejme, že váš soubor HTML má mnoho oddílů, každý s nadpisem ve značce <h2>, která vypadají takto <h2>Nějaký text</h2>. Můžete si vytvořit vlastní funkci, která automaticky očísluje tyto nadpisy za sebou následujícími čísly oddílů, takže budou vypadat takto <h2>1. Nějaký text</h2>.

def replace(match, number, file_name, metadata, dictionaries, data, functions, *args, **kwargs):
section_number = '%d. ' % number
return match.group(1) + section_number + match.group(2)

# Ensure that when running over multiple files, the files are processed
# in the order in which they appear in the book
replace.file_order = 'spine'

Použijte vyhledávací výraz:

(?s)(<h2[^<>]*>)(.+?</h2>)

Umístěte kurzor na začátek souboru a klikněte na Nahradit vše.

Tato funkce využívá další z užitečných dodatečných parametrů pro replace(): parametr number. Při provádění Nahradit vše je číslo automaticky zvýšeno pro každou následující shodu.

Další novinkou je použití replace.file_order – nastavení, které pro 'páteř' znamená, že pokud je toto hledání spuštěno na více souborech HTML, soubory jsou zpracovávány v pořadí, v jakém se objevují v knize. Pro podrobnosti viz Volba pořadí souborů při spuštění na více souborech HTML.

Automatické vytvoření obsahu

Nakonec zkusme něco trochu náročnějšího. Předpokládejme, že vaše kniha má nadpisy ve značkách h1 a h2, které vypadají takto <h1 id="nejakeid">Nějaký text</h1>. Automaticky vygenerujeme HTML obsah na základě těchto nadpisů. Vytvořte níže uvedenou vlastní funkci:

from calibre import replace_entities from calibre.ebooks.oeb.polish.toc import TOC, toc_to_html from calibre.gui2.tweak_book import current_container from calibre.ebooks.oeb.base import xml2str def replace(match, number, file_name, metadata, dictionaries, data, functions, *args, **kwargs):
if match is None:
# All matches found, output the resulting Table of Contents.
# The argument metadata is the metadata of the book being edited if 'toc' in data:
toc = data['toc']
root = TOC()
for (file_name, tag_name, anchor, text) in toc:
parent = root.children[-1] if tag_name == 'h2' and root.children else root parent.add(text, file_name, anchor)
toc = toc_to_html(root, current_container(), 'toc.html', 'Table of Contents for ' + metadata.title, metadata.language)
print(xml2str(toc))
else:
print('No headings to build ToC from found')
else:
# Add an entry corresponding to this match to the Table of Contents if 'toc' not in data:
# The entries are stored in the data object, which will persist
# for all invocations of this function during a 'Replace All' operation data['toc'] = []
tag_name, anchor, text = match.group(1), replace_entities(match.group(2)), replace_entities(match.group(3))
data['toc'].append((file_name, tag_name, anchor, text))
return match.group() # We don't want to make any actual changes, so return the original matched text

# Ensure that we are called once after the last match is found so we can
# output the ToC replace.call_after_last_match = True
# Ensure that when running over multiple files, this function is called,
# the files are processed in the order in which they appear in the book replace.file_order = 'spine'

A použijte ji s vyhledávacím výrazem:

<(h[12]) [^<>]* id=['"]([^'"]+)['"][^<>]*>([^<>]+)

Spusťte hledání nad Všechny textové soubory a na konci vyhledávání se otevře okno s „Výstup ladění z vaší funkce“, které bude mít HTML obsah připravený k vložení do toc.html.

Výše uvedená funkce je silně okomentovaná, takže by měla být snadno pochopitelná. Klíčovou novinkou je použití dalšího užitečného dodatečného parametru funkce replace(), objektu data. Objekt data je slovník Pythonu, který přetrvává mezi všemi po sobě následujícími voláními funkce replace() během jedné operace Nahradit vše`.

Další novinkou je použití call_after_last_match – nastavení, které pro True nad funkcí replace() znamená, že editor vyvolá replace() ještě jednou poté, co byly nalezeny všechny shody. Pro toto dodatečné vyvolání bude shoda objektu None.

To byla jen ukázka, která vám předvedla sílu režimu funkcí, pokud opravdu potřebujete vygenerovat obsah z nadpisů ve vaší knize, měli byste raději použít vyhrazený nástroj pro obsah v Nástroje → Obsah.

API pro režim funkcí

Všechny funkce režimu funkcí musí být funkce Pythonu pojmenované replace s následujícím podpisem:

def replace(match, number, file_name, metadata, dictionaries, data, functions, *args, **kwargs):
return a_string

Když je spuštěno najít a nahradit, pro každou shodu, která je nalezena, bude vyvolána funkce replace(), která musí vrátit nahrazovací řetězec pro tuto shodu. Pokud není třeba provést žádná nahrazení, mělo by se vrátit match.group(), což je původní řetězec. Jednotlivé parametry pro funkci replace() jsou zdokumentovány níže.

Parametr match

Parametr match představuje aktuálně nalezenou shodu. Je to objekt Match Pythonu [https://docs.python.org/library/re.html#match-objects]. Jeho nejužitečnější metoda je group(), která může být použita k získání textu odpovídajícího jednotlivým skupin zachycování v hledání regulárního výrazu.

Parametr number

Parametr number je číslo aktuální shody. Když spustíte Nahradit vše, každá po sobě jdoucí shoda způsobí, že bude vyvoláno``replace()`` se zvyšujícím se číslem. První shoda má číslo 1.

Parametr file_name

Toto je název souboru, ve kterém byla nalezena aktuální shoda. Při hledání uvnitř označeného textu je file_name prázdný. file_name je v kanonické podobě, cesta relativní ke kořenu knihy, s / jako oddělovačem cesty.

Parametr metadata

Toto představuje metadata aktuální knihy, jako je název, autoři, jazyk atd. Je to objekt třídy: calibre.ebooks.metadata.book.base.Metadata. Užitečné atributy zahrnují title, authors (seznam autorů) a language (kód jazyka).

Parametr dictionaries

Toto představuje sbírku slovníků používaných ke kontrole pravopisu aktuální knihy. Jeho nejužitečnější metoda je dictionaries.recognized(word), která vrátí True, pokud je předané slovo rozpoznáno slovníkem pro aktuální jazyk knihy.

Parametr data

Jedná se o jednoduchý slovník Pythonu. Když spustíte Nahradit vše`, každá následná shoda způsobí, že se zavolá replace() se stejným slovníkem jako data. Můžete jej tedy použít k uložení libovolných dat mezi voláními replace() během operace Nahradit vše`.

Parametr functions

Parametr functions vám dá přístup ke všem ostatním uživatelem definovaným funkcím. To je užitečné pro opětovné použití kódu. Můžete definovat obslužné funkce na jednom místě a znovu je použít ve všech svých ostatních funkcích. Předpokládejme například, že vytvoříte funkci pojmenovanou My Function takto:

def utility():
# do something

def replace(match, number, file_name, metadata, dictionaries, data, functions, *args, **kwargs):
...

Potom v jiné funkci můžete přistupovat k funkci utility() takto:

def replace(match, number, file_name, metadata, dictionaries, data, functions, *args, **kwargs):
utility = functions['My Function']['utility']
...

Můžete také použít objekt funkce pro ukládání trvalých dat, která mohou být znovu použita jinými funkcemi. Například byste mohli mít jednu funkci, která při spuštění s Nahradit vše shromažďuje některá data, a další funkci, která je používá, když je spuštěna později. Vezměme si následující dvě funkce:

# Function One
persistent_data = {}

def replace(match, number, file_name, metadata, dictionaries, data, functions, *args, **kwargs):
...
persistent_data['something'] = 'some data'

# Function Two
def replace(match, number, file_name, metadata, dictionaries, data, functions, *args, **kwargs):
persistent_data = functions['Function One']['persistent_data']
...

Ladění vašich funkcí

Funkce, které vytvoříte, můžete ladit pomocí standardní funkce print() z Pythonu. Výstup tisku se zobrazí v automaticky otevíraném okně po dokončení Najít a nahradit. Příklad použití print() pro výstup celého obsahu jste viděli výše.

Volba pořadí souborů při spuštění na více souborech HTML

Když spustíte Nahradit vše na více souborech HTML, pořadí, ve kterém jsou soubory zpracovány, závisí na tom, jaké soubory máte otevřené pro úpravy. Můžete vynutit, aby vyhledávání zpracovalo soubory v pořadí, ve kterém se objeví, nastavením parametru file_order ve vaší funkci takto:

def replace(match, number, file_name, metadata, dictionaries, data, functions, *args, **kwargs):
...

replace.file_order = 'spine'

file_order přijímá dvě hodnoty, spine a spine-reverse, které způsobují, že hledání zpracuje více souborů v pořadí, v jakém jsou uvedeny v knize, a to buď vpřed nebo zpět v uvedeném pořadí.

Vyvolání vaší funkce ještě jednou poté, co je nalezena poslední shoda

Někdy, jako ve výše uvedeném příkladu automatického generování obsahu, je užitečné vyvolat vaši funkci ještě jednou poté, co je nalezena poslední shoda. To můžete provést nastavením parametru ``call_after_last_match` ve vaší funkci takto:

def replace(match, number, file_name, metadata, dictionaries, data, functions, *args, **kwargs):
...

replace.call_after_last_match = True

Připojování výstupu z funkce pro označený text

Při spuštění hledání a nahrazení na označeném textu je někdy užitečné připojit nějaký text na konec označeného textu. Můžete to udělat nastavením atributu append_final_output_to_marked na vaši funkci (pamatujte, že také musíte nastavit call_after_last_match) takto:

def replace(match, number, file_name, metadata, dictionaries, data, functions, *args, **kwargs):
...
return 'some text to append'

replace.call_after_last_match = True
replace.append_final_output_to_marked = True

Potlačení dialogového okna výsledku při provádění vyhledávání na označeném textu

Můžete také potlačit dialogové okno výsledku (což může zpomalit opakovanou aplikaci hledání a nahrazení na mnoha blocích textu) nastavením atributu suppress_result_dialog na vaší funkci takto:

def replace(match, number, file_name, metadata, dictionaries, data, functions, *args, **kwargs):
...

replace.suppress_result_dialog = True

Další příklady

Další užitečné příklady, které vytvořili uživatelé Calibre, můžete nalézt ve fóru Editoru e-knih Calibre [https://www.mobileread.com/forums/showthread.php?t=237181].

© Copyright Kovid Goyal.
Naposledy aktualizováno kvě 03, 2024.

Navigace

- moduly
- další |
- předchozí |
- Spustit »
- Úprava e-knih »
- Fragmenty

Fragmenty

Editor e-knih Calibre podporuje fragmenty. Fragment je kus textu, který je buď znovu často použit, nebo obsahuje velké množství nadbytečného textu. Editor vám umožňuje vložit fragment pouhými několika stisky kláves. Předpokládejme například, že zjistíte, že často vkládáte značky odkazů při úpravě souborů HTML, pak můžete jednoduše v editoru napsat <a a stiskněte Control+J. Editor to rozbalí na:

<a href="filename"></a>

Nejen to, slovo filename bude vybráno a kurzor umístěn nad ním, abyste mohli snadno zadat skutečný název souboru pomocí šikovné funkce editoru Automatické dokončování. A jakmile zadáte název souboru, stiskněte znovu Control+J a kurzor se přesune na pozici mezi značkami <a>, abyste mohli snadno zadat text pro odkaz.

Systém fragmentů v editoru je velice propracovaný, je zde několik předdefinovaných fragmentů a můžete vytvářet své vlastní, aby vyhovovali vašemu stylu úprav.

Následující diskuze o předdefinovaných fragmentech by měla pomoci vysvětlit sílu systému fragmentů.

Poznámka

Fragmenty můžete také použít ve vstupních polích textu v panelu Hledat a nahradit, zástupné symboly (pomocí Control+J pro přeskakování) však nebudou fungovat.

Předdefinované fragmenty

Předdefinované fragmenty jsou popsány níže. Pamatujte, že je můžete přepsat vytvořením svých vlastních fragmentů se stejným textem spouštěče.

Vkládání výplňového textu [Lorem]

První a nejjednodušší předdefinovaný fragment je používán pro vkládání výplňového textu do dokumentu. Výplňový text je brán z De finibus bonorum et malorum <https://en.wikipedia.org/wiki/De_finibus_bonorum_et_malorum>, filozofického díla od Cicera (přeloženého do angličtiny). Pro použití jednoduše zadejte Lorem do souboru HTML a stiskněte Control+J. Bude to nahrazeno několika odstavci výplně.

Definice tohoto fragmentu je velice jednoduchá, text spouštěče je definován jako Lorem a šablona je definována jednoduše jako literární text, který má být vložen. Můžete ho snadno upravit pro použití vámi oblíbené podoby výplňového textu.

Vkládání samouzavírací značky HTML [<>]

Nyní se podívejme na jednoduchý příklad výkonného konceptu zástupných symbolů. Řekněme, že chcete vložit samouzavírací značku <hr/>. Jednoduše napište <> a stiskněte Control+J, editor rozbalí fragment na:

<|/>

Symbol | zde představuje aktuální pozici kurzoru. Pak můžete napsat hr a stisknout Control+J pro přesun kurzoru za konec značky. Fragment je definován jako:

Trigger: <>
Template: <$1/>$2

Zástupné symboly jsou jednoduše symboly dolaru ($) následované číslem. Když je fragment rozbalen stiskem Control+J, kurzor je umístěn na první zástupný symbol (zástupný symbol s nejnižším číslem). Když znovu stisknete Control+J, kurzor přeskočí na další zástupný symbol (zástupný symbol s nejbližším vyšším číslem).

Vkládání značky odkazu HTML [<a]

Všechny značky odkazů HTML sdílejí společnou strukturu. Mají atribut href a nějaký text mezi otevíracími a uzavíracími značkami. Fragment, který učiní jejich psaní efektivnějším nás seznámí s některými dalšími funkcemi zástupných symbolů. Pro použití tohoto fragmentu jednoduše napište <a a stiskněte Control+J. Editor to rozbalí na:

<a href="filename|"></a>

Nejen to, slovo filename bude vybráno a kurzor umístěn nad ním, abyste mohli snadno zadat skutečný název souboru pomocí šikovné funkce editoru Automatické dokončování. A jakmile zadáte název souboru, stiskněte znovu Control+J a kurzor se přesune na pozici mezi značkami <a>, abyste mohli snadno zadat text pro odkaz. Po dokončení psaní textu stiskněte znovu Control+J pro přeskočení na místo za ukončovací značku. Tento fragment je definován jako:

Trigger: <a
Template: <a href="${1:filename}">${2*}</a>$3

Zde je pár nových funkcí. Zaprvé se zástupný symbol $1 stal složitějším. Nyní obsahuje nějaký výchozí text (slovo filename). Pokud zástupný symbol obsahuje výchozí text, tak výchozí text nahradí zástupný symbol, když je fragment rozbalen. Také když skočíte na zástupný symbol s výchozím textem pomocí Control+J, výchozí text bude vybrán. Tímto způsobem můžete použít výchozí text, aby vám připomenul, abyste vyplnili důležité části šablony. Výchozí text pro zástupný symbol můžete zadat pomocí syntaxe: ${<number>:výchozí text}.

Druhou novou funkcí je, že druhý zástupný symbol má za sebou hvězdičku (${2*}). To znamená, že jakýkoliv text, který byl vybrán před rozbalením šablony nahradí zástupný symbol. Abyste toto viděli v akci, vyberte v editoru nějaký text, stiskněte Control+J, napište <a a stiskněte znovu Control+J, šablona se rozbalí na:

<a href="filename">whatever text you selected</a>

Vkládání značky obrázku HTML [<i]

Toto je velice podobné vkládání odkazu HTML, což jsme viděli výše. Umožní vám to rychle zadat značku <img src="filename" alt="description" /> a přeskakovat mezi atributy src a alt:

Trigger: <i
Template: <img src="${1:filename}" alt="${2*:description}" />$3

Vkládání libovolné značky HTML [<<]

Toto vám umožní vložit libovolnou úplnou značku HTML (nebo obalit dříve vybraný text značkou). Pro použití jednoduše napište << a stiskněte Control+J. Editor to rozbalí na:

<|></>

Napište název značky, například: span, a stiskněte Control+J, výsledkem bude:

<span>|</span>

Všimněte si, že do ukončovací značky bylo automaticky vyplněno span. Toho je dosaženo dalšího funkcí zástupných symbolů, zrcadlením. Zrcadlení jednoduše znamená, že pokud zadáte do šablony ukázkový zástupný symbol více než jednou, druhá a všechny pozdější pozice budou automaticky vyplní tím, co napíšete do první pozici, když stisknete Control+J. Definice tohoto fragmentu je:

Trigger: <<
Template: <$1>${2*}</$1>$3

Jak můžete vidět, první zástupný symbol ($1) byl zadán dvakrát, podruhé do ukončovací značky, což jednoduše zkopíruje cokoliv, co napíšete do otevírací značky.

Vkládání libovolné značky HTML s atributem třídy [<c]

Toto je velice podobné výše uvedenému příkladu vkládání libovolné značky kromě toho, že se předpokládá, že chcete pro značku zadat třídu:

Trigger: <c
Template: <$1 class="${2:classname}">${3*}</$1>$4

Umožní vám to nejdříve zadat název značky, stiskněte Control+J, zadejte název třídy, stiskněte Control+J, zadejte obsah značky a stiskněte naposledy Control+J, abyste vyskočili ze značky. Ukončovací značka bude vyplněna automaticky.

Vytváření vlastních fragmentů

Fragmenty jsou opravdu senzační, protože si můžete vytvořit svůj vlastní, aby vyhovoval vašemu stylu úprav. Abyste vytvořili svůj vlastní fragment, přejděte v editoru do Úpravy → Předvolby → Nastavení editoru → Spravovat fragmenty. Objeví se snadno použitelné dialogové okno, které vám pomůže vytvořit si vlastní fragmenty. Jednoduše klikněte na tlačítko Přidat fragment a uvidíte dialogové okno, které vypadá takto:

Nejdříve svůj fragment pojmenujte, něco popisného, co vám pomůže v budoucnu fragment poznat. Pak zadejte spouštěč. Spouštěč je jednoduše text, který musíte zadat v editoru před stisknutím Control+J, aby se rozbalil fragment.

Pak zadejte šablonu fragmentu. Měli byste začít jedním z výše uvedených příkladů a upravit ho podle svých potřeb. Nakonec zadejte, pro které typy souborů chcete, aby byl fragment aktivní. Tímto způsobem můžete mít více fragmentů se stejným spouštěcím textem, které pracují odlišně pro různé typy souborů.

Dalším krokem je otestovat váš nově vytvořený fragment. Použijte pole Test v dolní části. Zadejte spouštěcí text a stiskněte Control+J pro rozbalení fragmentu a přeskočení mezi zástupné symboly.

© Copyright Kovid Goyal.
Naposledy aktualizováno kvě 03, 2024.

Navigace

- moduly
- další |
- předchozí |
- Spustit »
- Server s obsahem Calibre

Server s obsahem Calibre

Server s obsahem Calibre vám umožňuje přístup k vašim knihovnám Calibre a čtení knih přímo v prohlížeči na vašem oblíbeném mobilním telefonu nebo tabletu. Následkem toho nemusíte do telefonu instalovat žádné speciální aplikace pro čtení a správu knih. Stačí použít prohlížeč. Server stahuje a ukládá knihu, kterou čtete, do off-line mezipaměti, takže ji můžete číst, i když nejste připojeni k internetu.

Obsah

- Přístup k Serveru s obsahem z jiných zařízení

- Přístup k serveru ze zařízení ve vaší domácí síti

- Přístup k serveru odkudkoliv z internetu

- Rozhraní serveru

- Seznam knih

- Čtečka knih

- Podpora prohlížečů

- Povolení offline podpory

- Správa uživatelských účtů pouze z příkazového řádku

- Integrace Serveru s obsahem Calibre do jiných serverů

- Použití plně virtuálního hostitele

- Používání předpony adresy URL

- Vytváření služby pro server Calibre na moderním systému Linux

Pro spuštění serveru klikněte na tlačítko Připojit nebo sdílet a zvolte Spustit Server s obsahem. Z brány firewall počítače nebo antivirového programu se může zobrazit zpráva s dotazem, zda je v pořádku povolit přístup k souboru calibre.exe. Klikněte na tlačítko Povolit nebo OK. Pak v počítači otevřete prohlížeč (nejlépe Chrome nebo Firefox) a zadejte následující adresu:

http://127.0.0.1:8080

This will open a page in the browser showing you your calibre libraries, click on any one and browse the books in it. Click on a book, and it will show you all the metadata about the book, along with buttons to Read and Download the book. Click the Read button to start reading the book.

Poznámka

Výše uvedená adresa http: //127.0.0.1: 8080 bude fungovat pouze na počítači, kde je spuštěno Calibre. Pro přístup k serveru z jiných počítačů, telefonů, tabletů atd. budete muset udělat trochu více práce, jak je popsáno v následující části.

Přístup k Serveru s obsahem z jiných zařízení

Existují dva typy přístupu ze vzdáleného zařízení, které budete obvykle potřebovat. První, jednodušší typ, je z vaší domácí sítě. Pokud používáte Calibre na počítači ve vaší domácí síti a vaše ostatní zařízení jsou také připojena ke stejné domácí síti, měl by být přístup k serveru na těchto zařízeních snadný.

Přístup k serveru ze zařízení ve vaší domácí síti

Po spuštění serveru v Calibre, jak je popsáno výše, klikněte znovu na tlačítko Připojit nebo sdílet. Namísto akce Spustit Server s obsahem byste měli vidět akci Zastavit Server s obsahem. Vpravo od této akce bude uvedena IP adresa a číslo portu. Vypadají jako mnoho čísel oddělených tečkami. Například:

Stop Content server [192.168.1.5, port 8080]

Tato čísla vám sdělují, jakou adresu použít pro připojení k serveru ve vašem zařízení. Podle výše uvedeného příkladu bude adresa:

http://192.168.1.5:8080

První část adresy je vždy http:// další část je adresa IP, což jsou čísla před čárkou a nakonec máme číslo portu, které musí být přidáno k IP adrese s dvojtečkou (:). Pokud máte štěstí, mělo by to být vše, co potřebujete, a můžete se dívat na knihovny Calibre na svém zařízení. Pokud ne, čtěte dál.

Odstraňování problémů s připojením v domácí síti

Pokud se vám nedaří připojit se k serveru z vašeho zařízení, zkuste následující kroky:

1. Zkuste, zda je server spuštěn, otevřením adresy http://127.0.0.1:8080 v prohlížeči spuštěném na stejném počítači jako server.

2. Zkontrolujte, zda vaše brána firewall nebo antivirus umožňují připojení k vašemu počítači na portu 8080 a k programu Calibre. Nejjednodušší způsob, jak odstranit firewall nebo antivirus jako zdroj problémů, je dočasně je oba vypnout a pak se zkusit připojit. Před vypnutím brány firewall byste se měli nejprve odpojit od internetu, aby byl váš počítač v bezpečí.

3. Zkontrolujte, zda jsou vaše zařízení a počítač ve stejné síti. To znamená, že by oba měly být připojeny ke stejnému bezdrátovému směrovači. Zvláště nesmí být používán mobilní připojení nebo poskytovatel internetového připojení poskytující přímé WiFi připojení.

4. Pokud máte nestandardní nastavení sítě, může se stát, že je adresa IP uvedená v nabídce Připojit nebo sdílet nesprávná. V takovém případě budete muset sami zjistit, jaká je správná adresa IP. Bohužel, vzhledem k nekonečné rozmanitosti možných síťových konfigurací, není možné vám poskytnout přesný návod, jak to udělat.

5. Pokud máte nastaveno uživatelské jméno a heslo, zkuste to nejdříve bez nich, abyste zjistili, zda to způsobuje problémy. Některá zařízení s elektronickým inkoustem mají prohlížeče, které nezvládají ověřování. Někdy můžete tento problém vyřešit zahrnutím uživatelského jména a hesla do adresy URL, například: http://uzivatelskejmeno:heslo@192.168.1.2:8080.

6. Pokud si nebudete vědět rady, vždy můžete požádat o pomoc v calibre user forums [https://www.mobileread.com/forums/forumdisplay.php?f=166].

Přístup k serveru odkudkoliv z internetu

Varování

Než to uděláte, měli byste na serveru zapnout ochranu uživatelským jménem a heslem, jinak bude mít k vašim knihám přístup kdokoliv na světě. Přejděte na Předvolby → Sdílení → Sdílení po síti a povolte volbu Vyžadovat uživatelské jméno a heslo pro přístup k serveru s obsahem.

Zatímco se konkrétní podrobnosti o nastavení přístupu k internetu liší v závislosti na konfiguraci sítě a typu používaného počítače, základní schéma je následující.

1. Zjistěte externí IP adresu počítače, na kterém chcete spustit server. Můžete to udělat tak, že v prohlížeči spuštěném na tomto počítači navštívíte stránku What is my IP address [https://www.whatismyip.com/].

2. Pokud je počítač za směrovačem, povolte přesměrování portů na směrovači, aby přesměrovával port 8080 (nebo jakýkoliv port, na kterém se rozhodnete spustit Server s obsahem Calibre) do počítače.

3. Ujistěte se, že je server Calibre povolen ve všech firewallech a antivirových programech ve vašem počítači.

4. Nyní byste měli mít možnost přistupovat k serveru na jakémkoli zařízení připojeném k internetu pomocí adresy IP, kterou jste zjistili v prvním kroku. Pokud je například adresa IP, kterou jste zjistili, 123.123.123.123 a port, který používáte pro server Calibre, je 8080, adresa, kterou použijete ve svém zařízení, bude: http://123.123.123.123:8080.

5. Volitelně použijte službu, jako je no-ip [https://www.noip.com/free], abyste nastavili snadno zapamatovatelnou adresu, kterou chcete použít namísto adresy IP, kterou jste zjistili v prvním kroku.

Poznámka

Pro maximální zabezpečení byste také měli na Serveru s obsahem povolit protokol HTTPS. Můžete tak učinit buď přímo na serveru poskytnutím cesty k certifikátu HTTPS, který se má použít, v rozšířených volbách konfigurace pro server, nebo můžete nastavit reverzní proxy, jak je popsáno níže, pro použití existujícího nastavení HTTPS.

Rozhraní serveru

Rozhraní serveru je zjednodušená verze hlavního rozhraní Calibre, optimalizovaná pro použití s dotykovými obrazovkami. Na domovské obrazovce se zobrazují knihy, které právě čtete, a také možnost vybrat knihovnu Calibre, kterou chcete procházet. Server v Calibre vám poskytuje přístup ke všem vašim knihovnám, ne jen k jedné jako dříve.

Seznam knih

Seznam knih na serveru je jednoduchá mřížka obálek. Klepněte na obálku pro zobrazení podrobných metadat knihy nebo přečtení knihy. Pokud dáváte přednost podrobnějšímu seznamu, můžete změnit výchozí zobrazení kliknutím na tři svislé tečky v pravém horním rohu.

Řazení a vyhledávání v seznamu knih by mělo být uživatelům Calibre známé. Jsou přístupné kliknutím na jejich ikony v pravé horní oblasti. Obě pracují přesně stejně jako v hlavním programu Calibre. Stránka vyhledávání vám dokonce umožňuje vytvářet vyhledávací dotazy kliknutím na autory, štítky atd., stejně jako pomocí Prohlížeče štítků v hlavním programu.

V rozhraní serveru je také přítomna velmi oblíbená funkce hlavního programu Virtuální knihovny. Klikněte na tři svislé tečky v pravém horním rohu pro výběr Virtuální knihovny.

Čtečka knih

You can read any book in your calibre library by simply tapping on it and then tapping the Read button. The book reader is very simple to operate. You can both tap and swipe to turn pages. Swiping up/down skips between chapters. Tapping the top quarter of the screen gets you the detailed controls and viewer preferences.

Pokud ponecháte Server s obsahem spuštěný, můžete dokonce otevřít stejnou knihu na více zařízeních a bude si pamatovat poslední pozici čtení. Pokud ne, můžete vynutit synchronizaci klepnutím na v horní čtvrtinu obrazovky a výběrem Synchronizovat.

Podpora prohlížečů

Nový server Calibre využívá mnoho pokročilých funkcí HTML 5 a CSS 3. Proto vyžaduje použití aktuálního prohlížeče. Byl testován na Chrome pro Android a Safari pro iOS, stejně jako Chrome a Firefox pro počítač.

Server dbá na to, aby používal funkce, které buď již byly standardizovány, nebo jsou na cestě ke standardizaci. Pokud tedy v současné době nefunguje s vaším oblíbeným prohlížečem, pravděpodobně jednou bude, až ten prohlížeč dožene ztrátu.

Pokud používáte obzvláště zastaralý nebo omezený prohlížeč nebo nechcete spouštět JavaScript, můžete použít mobilní zobrazení jednoduše přidáním /mobile k adrese serveru.

Poznámka

Apple na iOS umožňuje pouze jeden modul prohlížeče, takže Firefox, Chrome a Safari jsou vlastně pod kapotou stejný prohlížeč. Nové rozhraní serveru vyžaduje iOS 10.3.2 nebo novější. Na Androidu byl server testován s Chrome verze 58 a novější.

Povolení offline podpory

Browser makers have been trying to force people to use SSL by disabling advanced features in their browsers for plain HTTP connections. One such casualty is ApplicationCache, which was what was used in calibre for offline support. As a result now-a-days sadly, offline mode works only as long as you keep the browser tab open. In addition, in Firefox on Android, you will need to type about:config and create a preference called browser.tabs.useCache and set it to true.

Správa uživatelských účtů pouze z příkazového řádku

Program Calibre má v Předvolby pěknou sekci, která vám umožní spravovat uživatelské účty pro server. Pokud však chcete spustit samostatný server a nemůžete spustit hlavní program Calibre na stejném počítači nebo pod stejným uživatelským účtem, můžete také spravovat uživatele pouze pomocí příkazového řádku.

Uživatelské účty můžete spravovat pomocí volby --manage-users v samostatném programu calibre-server. Předpokládejme, že chcete uložit databázi uživatelů do složky /srv/calibre, pak ji vytvoříte spuštěním:

calibre-server --userdb /srv/calibre/users.sqlite --manage-users

Postupujte podle pokynů a vytvořte uživatelské účty, nastavte jejich oprávnění atd. Po dokončení můžete spustit server takto:

calibre-server --userdb /srv/calibre/users.sqlite --enable-auth

Použije uživatelské účty, které jste vytvořili v předchozím kroku.

Integrace Serveru s obsahem Calibre do jiných serverů

Zde vám ukážeme, jak integrovat Server s obsahem Calibre do jiného serveru. Nejčastějším důvodem je využití SSL nebo aby knihovna Calibre sloužila jako součást větší stránky. Základní technikou je spuštění serveru Calibre a nastavení reverzní proxy tomuto serveru z hlavního serveru.

Reverzní proxy je, když váš normální server přijímá příchozí požadavky a předává je serveru Calibre. Pak přečte odpověď ze serveru Calibre a předá ji klientovi. To znamená, že můžete jednoduše spustit server Calibre, aniž byste se ho snažili integrovat s hlavním serverem.

Použití plně virtuálního hostitele

Nejjednodušší konfigurací je vyhradit pro server Calibre plně virtuálnho hostitele. V takovém případě spusťte server Calibre takto:

calibre-server

Nyní nastavte virtuálního hostitele na hlavním serveru, například pro nginx:

http {
client_max_body_size 64M; # needed to upload large books
}

server {
listen [::]:80;
server_name myserver.example.com;

location / {
proxy_pass http://127.0.0.1:8080;
}
}

Nebo pro Apache:

LoadModule proxy_module modules/mod_proxy.so
LoadModule proxy_http_module modules/mod_proxy_http.so

<VirtualHost *:80>
ServerName myserver.example.com
AllowEncodedSlashes On
ProxyPreserveHost On
ProxyPass "/" "http://localhost:8080/"
</VirtualHost>

Používání předpony adresy URL

Pokud nechcete pro Calibre vyhradit plně virtuálního hostitele, můžete použít předponu adresy URL. Spusťte server Calibre takto:

calibre-server --url-prefix /calibre --port 8080

Klíčovým parametrem je zde --url-prefix /calibre. Způsobí, že Server s obsahem bude sloužit všem adresám URL s předponou /calibre. Abyste to viděli v akci, navštivte ve svém prohlížeči adresu http://localhost:8080/calibre. Měli byste vidět normální webovou stránku Serveru s obsahem, ale nyní bude spuštěna pod /calibre.

S nginx je vyžadována konfigurace:

http {
client_max_body_size 64M; # needed to upload large books
}

proxy_set_header X-Forwarded-For $remote_addr;
location /calibre/ {
proxy_buffering off;
proxy_pass http://127.0.0.1:8080$request_uri;
}
location /calibre {
# we need a trailing slash for the Application Cache to work
rewrite /calibre /calibre/ permanent;
}

Pro Apache nejdříve povolte moduly proxy v Apache přidáním následujícího textu do httpd.conf:

LoadModule proxy_module modules/mod_proxy.so
LoadModule proxy_http_module modules/mod_proxy_http.so

Přesná technika pro povolení modulů proxy se bude lišit v závislosti na instalaci Apache. Jakmile povolíte moduly proxy, přidejte následující pravidla do httpd.conf (nebo pokud používáte virtuální hostitele, tak do konfiguračního souboru pro daného virtuálního hostitele):

AllowEncodedSlashes On
RewriteEngine on
RewriteRule ^/calibre/(.*) http://127.0.0.1:8080/calibre/$1 [proxy]
RedirectMatch permanent ^/calibre$ /calibre/

To je vše, nyní budete mít přístup k Serveru s obsahem Calibre pro URL /calibre na vašem hlavním serveru. Výše uvedená pravidla předávají všechny požadavky pod /calibre serveru Calibre běžícímu na portu 8080 a díky výše uvedené volbě --url-prefix je server Calibre zpracovává transparentně.

Poznámka

Pokud používáte reverzní proxy, měli byste říct Serveru s obsahem Calibre, aby naslouchal pouze na localhost pomocí --listen-on 127.0.0.1. Tímto způsobem bude server naslouchat pouze spojením přicházejícím ze stejného počítače, tj. z reverzní proxy.

Poznámka

Pokud máte pro váš hlavní server nastaveno SSL, měli byste říci serveru Calibre, aby používal základní ověřování namísto ověřování algoritmem digest, protože je rychlejší. Abyste to udělali, předejte volbu --auth-mode=basic pro calibre-server.

Vytváření služby pro server Calibre na moderním systému Linux

Můžete snadno vytvořit službu, která spustí Calibre při spuštění moderního systému založeného na Linuxu (systemd [https://www.freedesktop.org/wiki/Software/systemd/]). Stačí vytvořit soubor /etc/systemd/system/calibre-server.service s níže zobrazeným obsahem:

[Unit]
Description=calibre Content server
After=network.target

[Service]
Type=simple
User=mylinuxuser
Group=mylinuxgroup
ExecStart=/opt/calibre/calibre-server "/path/to/calibre library folder"

[Install]
WantedBy=multi-user.target

Změňte mylinuxuser a mylinuxgroup na libovolného uživatele a skupinu, kterou má server spouštět. Měli by to být stejný uživatel a skupina, která vlastní soubory ve složce knihovny Calibre. Pamatujte, že není obecně dobrý nápad spouštět server jako root. Také změňte cestu ke složce knihovny Calibre, aby vyhovovala vašemu systému. V případě potřeby můžete přidat více knihoven. Viz nápověda k příkazu calibre-server.

Nyní spusťte:

sudo systemctl start calibre-server

pro spuštění serveru. Zkontrolujte jeho stav pomocí:

sudo systemctl status calibre-server

Aby se spustil při spouštění, spusťte:

sudo systemctl enable calibre-server

Poznámka

Server Calibre nepotřebuje spuštěný server X, ale potřebuje mít nainstalované knihovny X, protože některé komponenty, které používá, na ně odkazují.

Poznámka

Server Calibre také podporuje aktivaci systemd socketu, takže ji můžete použít, pokud je to potřeba.

© Copyright Kovid Goyal.
Naposledy aktualizováno kvě 03, 2024.

Navigace

- moduly
- další |
- předchozí |
- Spustit »
- Porovnání e-knih

Porovnání e-knih

Calibre zahrnuje integrovaný nástroj porovnání e-knih, který může být použit pro zobrazení, co se změnilo uvnitř e-knihy po úpravě nebo převodu. Umí porovnat knihy ve formátech EPUB a AZW3.

Pro jeho použití buď otevřete e-knihu v nástroji pro Úprava e-knih a klikněte na Soubor → Porovnat s jinou knihou nebo použijte panel Podrobnosti o knize. Pokud provádíte převod z EPUB do EPUB, původní soubor EPUB bude uložen jako ORIGINAL_EPUB. Jednoduše klikněte pravým tlačítkem myši na položku ORIGINAL_EPUB v panelu Podrobnosti o knize a zvolte Porovnat s formátem EPUB.

Nástroj porovnání, který se otevře, bude vypadat jako snímek obrazovky níže. Ukáže vám rozdíly v textu, stylech a obrázcích ve zvolených knihách.

Pochopení zobrazení porovnání

Jak můžete vidět na snímku obrazovky výše, zobrazení porovnání ukazuje rozdíly mezi dvěma knihami vedle sebe. Jsou zobrazeny pouze rozdíly s několika řádky kontextu kolem nich. Tak můžete snadno vidět na první pohled pouze to, co se změnilo uvnitř velkého dokumentu, jako je kniha.

Přidaný text je zobrazen se zeleným pozadím, odebraný text s červeným pozadím a změněný text s modrým pozadím.

Čísla řádků všeho změněného textu jsou zobrazena po stranách, umožňují snadno přejít na konkrétní změnu v editoru. Když otevřete nástroj porovnání z editoru, můžete také poklepat na řádek v pravém panelu pro automatické přejití na tento řádek v editoru.

Užitečná technika při porovnávání knih je říct nástroji porovnání, aby zkrášlil textové soubory a soubory stylů, než vypočítá rozdíly. To může mít často za výsledek čistější a snadněji sledovatelné rozdíly. Abyste to udělali, klikněte na tlačítko Volby vpravo dole a zvolte Zkrášlit soubory před jejich porovnáním. Pamatujte, že zkrášlení může mít někdy nežádoucí účinky, protože může způsobit, že jsou neplatné značky změněny, aby byly platné. Pomocí tlačítka Volby můžete také změnit počet řádků kontextu zobrazovaného kolem rozdílů.

Prostřednictvím Panelu hledání v dolní části můžete hledat jakýkoliv text v rozdílech. Budete muset zadat, ve kterém panelu hledat, Levý nebo Pravý.

Spuštění nástroje porovnání

Nástroj porovnání je nejužitečnější, když máte dvě verze stejné knihy a chcete vidět, jaký je mezi nimi rozdíl. Za tímto účelem je několik způsobů spuštění nástroje.

Porovnání dvou souborů e-knih

Otevřete první soubor v nástroji Úprava e-knih. Nyní klikněte na Soubor → Porovnat s jinou knihou a zvolte druhý soubor (musí být ve stejném formátu jako první). Otevře se zobrazení porovnání s upravovaným souborem vpravo a druhým souborem vlevo.

Porovnání ORIGINAL_FMT s FMT

Když provádíte v calibre převod z FMT do něho samého, původní soubor je uložen jako ORIGINAL_FMT. Co se změnilo převodem, můžete zobrazit kliknutím pravým tlačítkem myši na položku ORIGINAL_FMT v panelu Podrobnosti o knize v hlavním okně calibre a výběrem Porovnat s FMT. Otevře se zobrazení porovnání s ORIGINAL_FMT vlevo a FMT vpravo.

Porovnání kontrolního bodu s aktuálním stavem knihy během úpravy

Nástroj Úprava e-knih má velice užitečnou funkci zvanou Kontrolní body. Ta vám umožňuje uložit aktuální stav knihy jako pojmenovaný kontrolní bod, do kterého se můžete vrátit, pokud se vám nelíbí změny, které jste udělali od vytvoření kontrolního bodu. Kontrolní body jsou také vytvářeny automaticky, když provedete různé automatizované akce v editoru. Seznam kontrolních bodů můžete vidět přejitím na Zobrazení → Kontrolní body a pak použitím tlačítka Porovnat pro porovnání knihy ve vybraném kontrolním bodu s aktuálním stavem. Nástroj porovnání zobrazí kontrolní bod vlevo a aktuální stav vpravo.

© Copyright Kovid Goyal.
Naposledy aktualizováno kvě 03, 2024.

Navigace

- moduly
- další |
- předchozí |
- Spustit »
- Úprava metadat e-knihy

Úprava metadat e-knihy

Obsah

- Úprava metadat jedné knihy najednou

- Stahování metadat

- Správa formátů knihy

- Vše o obálkách

- Úprava metadat mnoha knih najednou

- Hledat a nahradit

- Hromadné stahování metadat

- Přidání dodatečných souborů dat do knihy

E-knihy se objevují ve všech tvarech a velikostech a spíše častěji než ne jsou jejich metadata (věci, jako jsou název, autor, série, vydavatel) neúplná nebo nesprávná. Nejjednodušší způsob, jak změnit metadata v calibre, je jednoduše poklepat na položku a zadat správné nahrazení. Pro propracovanější „výkonné úpravy“ použijte nástroje pro úpravu metadat probrané níže.

Úprava metadat jedné knihy najednou

Klikněte na knihu, kterou chcete upravit, a pak klikněte na tlačítko Upravit metadata nebo stiskněte klávesu E. Otevře se dialogové okno, které vám umožní upravit všechny aspekty metadat. Má různé funkce, aby byly úpravy rychlejší a efektivnější. Seznam běžně používaných tipů:

- Můžete kliknout na tlačítko mezi názvem a autory pro jejich automatické prohození.

- Můžete kliknout na tlačítko vedle řazení autora, aby ho calibre automaticky vyplnilo pomocí hodnot řazení uložených u každého autora. Použije dialogové okno Spravovat autory, abyste zobrazili a změnili hodnoty řazení autorů. Toto dialogové okno lze otevřít kliknutím a podržením tlačítka vedle řazení autora.

- Můžete kliknout na tlačítko vedle štítků pro použití Editoru štítků pro správu štítků přidružených ke knize.

- Pole „Identifikátory“ může být použito pro zadání ISBN (a mnoho jiných typů identifikátorů), bude mít červené pozadí, pokud zadáte neplatné ISBN. Pro platná ISBN bude zelené.

- Pole řazení autora bude červené, pokud se hodnota řazení autora liší od té, kterou calibre předpokládá.

Stahování metadat

Nejhezčí funkce dialogového okna úpravy metadat je jeho schopnost automaticky vyplnit mnoho polí metadat získáním metadat z různých webových stránek. Calibre aktuálně používá Knihy Google a Amazon. Stažená metadata za vás mohou vyplnit název, autora, sérii, štítky, hodnocení, popis a ISBN.

Pro použití stahování vyplňte pole název a autor a klikněte na tlačítko Stáhnout metadata. calibre vám nabídne seznam knih, které nejvíce odpovídají názvu a autorovi. Pokud vyplníte nejdříve pole ISBN, bude použito přednostně před názvem a autorem. Pokud nejsou nalezeny žádné shody, zkuste učinit hledání trochu méně konkrétní zahrnutím pouze některých klíčových slov v názvu a použitím pouze příjmení autora.

Správa formátů knihy

V calibre může mít jedna kniha přidruženo mnoho různých formátů. Například jste mohli získat kompletní shakespearovo dílo ve formátu EPUB a později ho převést do MOBI, abyste ho mohli číst na Kindle. calibre pro vás automaticky spravuje více formátů. V oddílu Dostupné formáty dialogového okna Upravit metadata můžete tyto formáty spravovat. Můžete přidat nový formát, odstranit existující formát a také požádat calibre o nastavení metadat a obálky pro položku knihy z metadat v jednom z formátů.

Vše o obálkách

Můžete požádat calibre, aby pro vás stáhlo obálky knih, za předpokladu, že kniha má známé ISBN. Případně můžete zadat soubor ve svém počítači, který chcete použít jako obálku. calibre pro vás může dokonce vygenerovat výchozí obálku se základními metadaty. Můžete přetahovat obrázky na obálku, abyste ji změnili, a také kliknutím pravým tlačítkem myši kopírovat a vložit obrázky obálky.

Kromě toho tu je tlačítko pro automatické oříznutí rámečků obálky v případě, že má vaše obálka ošklivý rámeček.

Úprava metadat mnoha knih najednou

Nejdříve vyberte knihy, které chcete upravit, podržením Ctrl or Shift a kliknutím na ně. Pokud vyberete více než jednu knihu, kliknutí na tlačítko Upravit metadata způsobí otevření dialogového okna hromadné úpravy metadat. Pomocí tohoto dialogového okna můžete rychle nastavit autora, vydavatele, hodnocení, štítky, sérii atd. mnoha knih na stejnou hodnotu. To je obzvlášť užitečné, pokud jste právě importovali řadu knih, které mají některá společná metadata. Toto dialogové okno je velice výkonné, například má kartu Hledat a nahradit, kterou můžete použít k provedení hromadných operací na metadatech a dokonce i ke kopírování metadat z jednoho sloupce do jiného.

Normální dialogové okno úpravy metadat má také tlačítka Další a Předchozí, která můžete použít k úpravě metadat několika knih jednu po druhé.

Hledat a nahradit

Dialogové okno Upravit metadata pro hodně knih vám umožňuje provádět libovolně výkonné operace hledání a nahrazení na vybraných knihách. Ve výchozím nastavení používá jednoduché textové hledání a nahrazení, ale také podporuje regulární výrazy. Pro více informací o regulárních výrazech se podívejte na Vše o používání regulárních výrazů v Calibre.

Jak již bylo zmíněno výše, existují dva režimy hledání a nahrazení: znaková shoda a regulární výraz. Znaková shoda bude hledat ve Vyhledávacím poli, které zvolíte, znaky, které zadáte do pole Hledat, a nahradí tyto znaky tím, co zadáte do pole Nahradit čím. Bude nahrazen každý výskyt hledaných znaků. Předpokládejme například, že vyhledávací pole obsahuje o zlobivé kočičce. Pokud hledáte o, které má být nahrazeno AHOJ, pak výsledek bude AHOJ zlAHOJbivé kAHOJčičce.

Pokud pole, ve kterém hledáte, je vícenásobné pole, jako jsou štítky, pak se každý štítek posuzuje samostatně. Například pokud vaše štítky obsahují Horor, Strašidelné, vyhledávací výraz r, nebude odpovídat ničemu, protože výraz bude nejdříve použit na Horor a pak na Strašidelné.

Pokud chcete, aby hledání ignorovalo rozdíly mezi velkými a malými písmeny, zrušte zaškrtnutí pole Rozlišovat velikost písmen.

Můžete nechat calibre, aby změnilo velikost písmen výsledku (informace po provedení nahrazení) výběrem jedné z funkcí z pole Použít funkci po nahrazení. Dostupné operace jsou:

- Malá písmena – změnit všechny znaky v poli na malá písmena

- Velká písmena – změnit všechny znaky v poli na velká písmena

- Všechna první velká – převést každé slovo ve výsledku na velká počáteční písmena

Pole Váš test je vám k dispozici pro zadání textu, abyste si ověřili, že hledání a nahrazení dělá to, co chcete. Ve většině případů budou stačit testovací pole knih, ale je možné, že existuje případ, který chcete zkontrolovat, a který není zobrazen v těchto polích. Zadejte tento případ do Váš test.

Režim regulární výraz má některé rozdíly od znakového režimu, kromě (samozřejmě) používání regulárních výrazů. První je, že funkce jsou použity na části řetězce odpovídající hledanému řetězci, nikoliv na celá pole. Druhý je, že se funkce použijí na řetězec nahrazení, nikoliv na celé pole.

Třetí a nejdůležitější je, že řetězec nahrazení může odkazovat na části hledaného řetězce pomocí zpětných odkazů. Zpětný odkaz je \\n, kde n je celé číslo, které odkazuje na ntou skupinu v závorkách ve vyhledávacím výrazu. Například, se stejným příkladem jako výše, o zlobivé kočičce, vyhledávací výraz o (…) (…) a výraz nahrazení o \2 \1, bude výsledek o kočičce zlobivé. Pro více informací o zpětných odkazech se podívejte na Vše o používání regulárních výrazů v Calibre.

Jeden užitečný vzor: předpokládejme, že chcete změnit velikost písmen celého pole. Nejsnadnější způsob, jak to udělat, je použít znakový režim, ale předpokládejme dále, že chcete použít režim regulárního výrazu. Vyhledávací výraz by měl být (^.*$), výraz nahrazení by měl být \1 a měla by být vybrána požadovaná funkce změny velikosti písmen.

A konečně, v režimu regulárního výrazu můžete kopírovat hodnoty z jednoho pole do druhého. Stačí, aby se zdrojové a cílová pole lišilo. Kopie může nahradit cílové pole, předřadit se poli (přidat na začátek) nebo se připojit k poli (přidat na konec). Zaškrtávací políčko ‚použít čárku‘ říká calibre, aby přidalo (nebo nepřidalo) čárku mezi text a cílové pole v režimech předřazení a připojení. Pokud je cíl vícenásobný (např. štítky), pak nemůžete zrušit zaškrtnutí tohoto políčka.

Hledat a nahradit je provedeno po použití všech ostatních změn metadat na ostatních kartách. To může vést k určitému zmatku, protože testovací pole zobrazí informace před ostatními změnami, ale operace bude použita po ostatních změn. Pokud máte jakékoliv pochybnosti o tom, co se stane, nemíchejte hledání a nahrazení s ostatními změnami.

Hromadné stahování metadat

Pokud chcete stáhnout metadata pro více knih najednou, klikněte pravým tlačítkem myši na tlačítko Upravit metadata a vyberte Stáhnout metadata. Můžete si zvolit stáhnout pouze metadata, pouze obálky nebo obojí.

Přidání dodatečných souborů dat do knihy

Calibre může ke knize uložit libovolný počet dodatečných souborů dat. Mohou to být alternativní obálky, doplňkový materiál atd. Nelze je přímo prohlížet ani používat jako zdroje pro převod. Nejsou ani indexovány fulltextovým vyhledávačem v Calibre. Pokud je chcete zobrazit, přidat nebo odstranit, vyberte knihu a klikněte pravým tlačítkem myši na tlačítko Upravit metadata a zvolte Soubory dat. Tím se zobrazí okno, ve kterém můžete s těmito soubory provádět operace. Alternativně můžete kliknout pravým tlačítkem myši na tlačítko Přidat knihy a zvolit Přidat soubory dat k vybraným záznamům knihy pro rychlejší přidání souborů dat.

© Copyright Kovid Goyal.
Naposledy aktualizováno kvě 03, 2024.

Navigace

- moduly
- další |
- předchozí |
- Spustit »
- Nejčastější dotazy

Nejčastější dotazy

Obsah

- Převod formátu e-knihy

- Integrace zařízení

- Library management

- Různé

Převod formátu e-knihy

Obsah

- Ze kterých a do kterých formátů podporuje Calibre převod?

- Jaké jsou nejlepší zdrojové formáty pro převod?

- Převedl jsem soubor PDF, ale výsledek má různé problémy?

- Jak převedu můj soubor obsahující neanglické znaky nebo chytré uvozovky?

- O co jde s obsahem v souborech MOBI?

- Jak převedu kolekci souborů HTML v konkrétním pořadí?

- EPUB vytvořený pomocí Calibre není platný?

- Jak použiji některé pokročilé funkce nástrojů převodu?

Ze kterých a do kterých formátů podporuje Calibre převod?

Calibre podporuje převod mnoha vstupních formátů do mnoha výstupních formátů. Umí převést každý vstupní formát z následujícího seznamu do každého výstupního formátu.

Vstupní formáty: AZW, AZW3, AZW4, CBZ, CBR, CB7, CBC, CHM, DJVU, DOCX, EPUB, FB2, FBZ, HTML, HTMLZ, LIT, LRF, MOBI, ODT, PDF, PRC, PDB, PML, RB, RTF, SNB, TCR, TXT, TXTZ

Výstupní formáty: AZW3, EPUB, DOCX, FB2, HTMLZ, OEB, LIT, LRF, MOBI, PDB, PMLZ, RB, PDF, RTF, SNB, TCR, TXT, TXTZ, ZIP

Poznámka

PRC je všeobecný formát, Calibre podporuje soubory PRC s hlavičkami TextRead a MOBIBook. PDB je také všeobecný formát. Calibre podporuje soubory PDB eReader, Plucker (pouze vstup), PML a zTxt. Podpora DJVU je pouze pro převod souborů DJVU, které obsahují vložený text. Ty jsou obvykle generovány programy pro OCR. Knihy MOBI mohou být dvou typů, Mobi6 a KF8. Calibre plně podporuje oba. Soubory MOBI mají často přípony .azw nebo .azw3. Soubory DOCX z Microsoft Word 2007 a novější jsou podporovány.

Jaké jsou nejlepší zdrojové formáty pro převod?

Pořadí podle klesajícího upřednostnění: LIT, MOBI, AZW, EPUB, AZW3, FB2, FBZ, DOCX, HTML, PRC, ODT, RTF, PDB, TXT, PDF

Převedl jsem soubor PDF, ale výsledek má různé problémy?

PDF je hrozný formát, ze kterého převádět. Seznam různých problémů, se kterými se setkáte při převodu PDF, naleznete v Převod dokumentů PDF.

Jak převedu můj soubor obsahující neanglické znaky nebo chytré uvozovky?

Existují dva aspekty tohoto problému:

1. Znalost kódování zdrojového souboru: Calibre se pokusí odhadnout, jaké kódování znaků vaše zdrojové soubory používají, ale často je to nemožné, takže mu musíte říct, jaké kódování použít. To lze provést v grafickém rozhraní prostřednictvím pole Vstupní kódování znaků v oddílu Vzhled a chování → Text dialogového okna převodu. Nástroje příkazového řádku mají volbu ebook-convert-txt-input --input-encoding.

2. Při přidávání souborů HTML do Calibre možná budete muset říct Calibre, v jakém kódování soubory jsou. Abyste to udělali, přejděte do Předvolby → Rozšířené → Moduly → Typ souboru a přizpůsobte modul HTML do ZIP a řekněte mu, v jakém kódování jsou vaše soubory HTML. Nyní, když přidáte do Calibre soubory HTML, budou správně zpracovány. Soubory HTML z různých zdrojů mají často různá kódování, takže možná budete muset změnit toto nastavení opakovaně. Běžné kódování mnoha souborů z webu je cp1252 a navrhuji, abyste ho vyzkoušeli jako první. Pamatujte, že při převodu souborů HTML máte výše zmíněné nastavení vstupního kódování ponechat prázdné. Je to proto, že modul HTML do ZIP automaticky převede soubory HTML na standardní kódování (utf-8).

O co jde s obsahem v souborech MOBI?

Nejprve si musíte uvědomit, že většina e-knih má dva obsahy. Jedním z nich je obvyklý obsah, který najdete v papírových knihách. Tento obsah je součástí hlavního dokumentu a může mít jakýkoliv styl. Tato obsah se nazývá obsahový obsah.

Pak existuje metadatový obsah. Metadatový obsah je obsah, který není součástí textu knihy a je obvykle přístupný některým speciálním tlačítkem ve čtečce. Například v Prohlížeči e-knih Calibre použijete tlačítko Zobrazit obsah, abyste viděli tento obsah. U tohoto obsahu nemůže tvůrce knihy měnit styl. Jak je zobrazen, závisí na programu prohlížeče.

Ve formátu MOBI je situace trochu zmatená. Je to proto, že formát MOBI, který patří mezi hlavní formáty e-knih, nemá slušnou podporu pro metadatový obsah. Kniha MOBI simuluje přítomnost metadatového obsahu umístěním dodatečného obsahového obsahu na konci knihy. Když klipnete na Přejít na obsah ve vašem Kindle, je to tento dodatečný obsahový obsah, kam vás Kindle přenese.

Může se vám zdát, že kniha MOBI má dva totožné obsahy. Nezapomeňte, že jeden je sémanticky obsahový obsah a druhý je metadatový obsah, přestože oba mohou mít přesně stejné položky a vypadat stejně. Jeden může být přístupný přímo z nabídky Kindle, druhý ne.

Při převodu do MOBI Calibre rozpoznává ve vstupním dokumentu metadatový obsah a generuje ve výstupním souboru MOBI obsah na konci souboru. Toto můžete vypnout volbou v nastavení Výstup MOBI. Můžete také říct Calibre, zda ho má umístit na začátek nebo konec knihy prostřednictvím volby v nastavení Výstup MOBI. Nezapomeňte, že tento obsah je sémanticky metadatový obsah, v jakémkoliv jiném formátu než MOBI nemůže být součástí textu. Skutečnost, že v MOBI je součástí textu, je nehoda způsobená omezeními MOBI. Pokud chcete mít obsah v konkrétním místě v textu vašeho dokumentu, vytvořte ho ručně. Důrazně doporučujeme, abyste výchozí ponechali tak, jak je, tj. s metadatovým obsahem na konci knihy. Pamatujte také, že pokud zakážete generování obsahu na konci souboru, výsledný soubor MOBI nemusí na Kindle správně fungovat, protože Kindle používá metadatový obsah pro mnoho věcí, včetně funkce překlápění stránky.

Pokud máte ve vstupním dokumentu ručně upravený obsah, můžete v Calibre použít možnost rozpoznání obsahu, které z něj automaticky vygeneruje metadatový obsah. Více informací o tom, jak tyto volby používat, naleznete v oddílu o převodu v uživatelské příručce.

Nakonec vám doporučuji, abyste ve svých e-knihách vynechali obsahový obsah a měli pouze metadatový obsah. Metadatové obsahy poskytnou lidem čtoucím vaše e-knihy mnohem lepší navigaci (kromě Kindle, kde jsou v podstatě stejné jako obsahový obsah).

Poznámka

Novější formát AZW3 má správnou podporu pro metadatový obsah. Firmware Kindle však má sklon k selhání, pokud zakážete generování vloženého obsahu na konci souboru. Takže je doporučeno, abyste nechali vygenerovaný obsah být. Pokud vytvoříte soubor AZW3 s metadatovým obsahem a žádným vygenerovaným obsahem na konci souboru, některé funkce v Kindle nebudou fungovat, jako je funkce překlápění stránek.

Jak převedu kolekci souborů HTML v konkrétním pořadí?

Abyste převedli kolekci souborů HTML v konkrétním pořadí, musíte vytvořit soubor s obsahem. To znamená další soubor HTML, který obsahuje odkazy na všechny ostatní soubory v požadovaném pořadí. Takový soubor vypadá takto:

<html>
<body>
<h1>Table of Contents</h1>
<p style="text-indent:0pt">
<a href="file1.html">First File</a><br/>
<a href="file2.html">Second File</a><br/>
.
.
.
</p>
</body>
</html>

Pak jednoduše přidejte tento soubor HTML do grafického rozhraní a použijte tlačítko Převést pro vytvoření vaší elektronické knihy. Pro ovládání toho, jak je obsah generován, můžete použít volbu v oddílu Obsah v dialogovém okně převodu.

Poznámka

Ve výchozím nastavení při přidávání souborů HTML Calibre sleduje odkazy v souborech v pořadí do hloubky. To znamená, že pokud soubor A.html odkazuje na B.html a C.html a D.html, ale B.html také odkazuje na D.html, pak budou soubory v pořadí A.html, B.html, D.html, C.html. Pokud místo toho chcete, aby pořadí bylo A.html, B.html, C.html, D.html, pak musíte říct Calibre, aby přidalo vaše soubory v pořadí do šířky. Udělejte to přejitím na Předvolby → Rozšířené → Moduly → Typ souboru a úpravou modulu HTML do ZIP.

EPUB vytvořený pomocí Calibre není platný?

Calibre nezaručuje, že jím vytvořený EPUB je platný. Jedinou zárukou je to, že pokud mu dodáte platné XHTML 1.1 + CSS 2.1, bude výstupem platný EPUB. Calibre se snaží zajistit, aby soubory EPUB, které vytváří, skutečně fungovaly tak, jak je zamýšleno na široké škále zařízení, což je cíl, který není kompatibilní s vytvářením platných souborů EPUB, a to je mnohem důležitější pro drtivou většinu jeho uživatelů. Pokud potřebujete nástroj, který vždy vytváří platné soubory EPUB, Calibre není pro vás. To znamená, že pokud chcete poslat EPUB vytvořený v Calibre do online úložiště používajícího kontrolu platnosti EPUB, musíte se sami ujistit, že je EPUB platný, Calibre to za vás neudělá – jinými slovy musíte jako vstupní dokumenty dodat Calibre platné XHTML + CSS.

Jak použiji některé pokročilé funkce nástrojů převodu?

Pomoc s jakoukoli jednotlivou funkcí převaděčů můžete získat tak, že na ni najedete myší v grafickém rozhraní nebo v terminálu spustíte ebook-convert dummy.html .epub -h. Dobrým místem pro začátek je podívat se na následující ukázkový soubor, který ukazuje některé pokročilé funkce html-demo.zip [https://calibre-ebook.com/downloads/html-demo.zip].

Integrace zařízení

Obsah

- Která zařízení Calibre podporuje?

- Jak mohu pomoci, aby Calibre podporovalo mé zařízení?

- Moje zařízení není v Calibre rozpoznáno?

- Mé zařízení je nestandardní nebo neobvyklé. Co můžu udělat pro jeho připojení?

- Jak použiji Calibre s mým zařízením iPad, iPhone nebo iPod touch?

- How do I use calibre with my Android phone/tablet or Kindle Fire?

- Can I access my calibre books using the web browser in my Kindle or other reading device?

- I cannot send emails using calibre?

- My device is getting mounted read-only in Linux, so calibre cannot connect to it?

- Why does calibre not support collections on the Kindle or shelves on the Nook?

- I am getting an error when I try to use calibre with my Kobo Touch/Glo/etc.?

- Covers for books I send to my e-ink Kindle show up momentarily and then are replaced by a generic cover?

- Obálky pro mé soubory MOBI se přestaly zobrazovat v Kindle pro PC nebo Kindle pro Android, iPad apod.

- I transferred some books to my Kindle using calibre and they did not show up?

Která zařízení Calibre podporuje?

Calibre se umí přímo připojit ke všem hlavním (a většině menších) čtecím zařízením e-knih, chytrým telefonům, tabletům atd. Kromě toho pomocí funkce Připojit ke složce ho můžete použít s jakoukoliv čtečkou elektronických knih, která se exportuje jako disk USB. A nakonec se můžete bezdrátově připojit k jakémukoliv zařízení, které má webový prohlížeč, pomocí Serveru s obsahem Calibre.

Jak mohu pomoci, aby Calibre podporovalo mé zařízení?

Pokud se vaše zařízení v operačním systému zobrazí jako disk USB, je přidání jeho podpory do Calibre velice snadné. Potřebujeme od vás jen pár informací:

- Úplný seznam formátů e-knih, které vaše zařízení podporuje.

- Je v zařízení nějaká zvláštní složka, do které by měly být umístěny všechny soubory e-knih? Také jestli zařízení rozpoznává soubory umístěné do podsložek.

- Potřebujeme také informace o vašem zařízení, které bude Calibre shromažďovat automaticky. Nejprve, pokud vaše zařízení podporuje SD karty, vložte je. Pak připojte zařízení k počítači. V Calibre přejděte na Předvolby → Různé a klikněte na tlačítko „Ladit rozpoznávání zařízení“. Tím se vytvoří nějaký výstup ladění. Zkopírujte ho do souboru a opakujte postup, tentokrát se zařízením odpojeným od počítače.

- Zašlete nám oba výše uvedené výstupy s ostatními informacemi a my pro vás napíšeme ovladač zařízení.

Jakmile nám odešlete výstup pro konkrétní operační systém, podpora pro zařízení v tomto operačním systému se objeví v příštím vydání Calibre. Abyste nám zaslali výstup, otevřete hlášení o chybě a připojte k němu výstup. Viz jak hlásit chyby [https://calibre-ebook.com/bugs].

Moje zařízení není v Calibre rozpoznáno?

Následujte tyto kroky pro nalezení problému:

- Ujistěte se, že k počítači připojujete současně pouze jedno zařízení. Nepřipojujte současně další zařízení podporované Calibre, jako je iPhone nebo iPad atd.

- Pokud připojujete zařízení Apple iDevice (iPad, iPod Touch, iPhone), společnost Apple již nepovoluje připojení softwaru třetích stran k jejich zařízením pomocí kabelu USB. Místo toho použijte bezdrátové připojení prostřednictvím Serveru s obsahem Calibre.

- Pokud připojujete Kindle Fire nebo jiné zařízení s Androidem, přečtěte si poznámku pod Pomocí kabelu USB.

- Pokud v systému MacOS zjistíte chyby oprávnění při připojování zařízení ke Calibre, můžete to napravit pod Předvolby systému > Zabezpečení a soukromí > Soukromí > Soubory a složky.

- Ujistěte se, že používáte nejnovější verzi Calibre (aktuálně 7.10.0). Nejnovější verzi lze vždy stáhnout z webové stránky Calibre [https://calibre-ebook.com/download]. Jakou verzi Calibre právě používáte, zjistíte na dolním řádku hlavního okna Calibre.

- Ujistěte se, že váš operační systém zařízení vidí. To znamená, že by se zařízení mělo zobrazit v Průzkumníkovi Windows (v systému Windows) nebo Finderu (v systému MacOS).

- V Calibre přejděte na Předvolby → Ignorovaná zařízení a zkontrolujte, zda vaše zařízení není ignorováno.

- Pokud všechny výše uvedené kroky selžou, přejděte na Předvolby → Různé a klikněte na Ladění rozpoznávání zařízení zatímco je zařízení připojeno a odešlete výstup jako ticket na hlášení chyb Calibre [https://bugs.launchpad.net/calibre].

Mé zařízení je nestandardní nebo neobvyklé. Co můžu udělat pro jeho připojení?

Kromě funkce Připojit ke složce, kterou naleznete pod tlačítkem Připojit nebo sdílet Calibre poskytuje modul zařízení Definováno uživatelem, který lze použít pro připojení k libovolnému zařízení USB, které se zobrazí jako disková jednotka ve vašem operačním systému. Poznámka: ve Windows musí mít zařízení pro používání v Calibre přiřazeno písmeno jednotky. Další informace naleznete v modulu zařízení Předvolby -> Moduly -> Moduly zařízení -> Definováno uživatelem a Předvolby -> Různé -> Získat informace pro nastavení zařízení definovaného uživatelem. Pamatujte, že pokud používáte uživatelem definovaný modul pro zařízení normálně rozpoznané vestavěným modulem Calibre, musíte nejdříve zakázat vestavěný modul, aby se místo toho použil uživatelem definovaný modul.

Jak použiji Calibre s mým zařízením iPad, iPhone nebo iPod touch?

Snadným způsobem procházení vaší kolekce Calibre ze zařízení Apple je použití serveru, díky kterému je kolekce dostupná přes síť. Nejprve proveďte následující kroky v Calibre

- Nastavte Upřednostňovaný výstupní formát v Calibre na EPUB (výstupní formát lze nastavit pod Předvolby → Rozhraní → Chování)

- Nastavte výstupní profil na iPad (bude to fungovat i pro iPhone nebo iPod) pod Předvolby → Převod → Běžné volby → Nastavení stránky.

- Převeďte knihy, které chcete na svém iZařízení číst, do formátu EPUB tak, že je vyberete a kliknete na tlačítko Převést.

- Zapněte Server s obsahem kliknutím na tlačítko Připojit/sdílet a nechte Calibre spuštěné. Můžete také Calibre říct, aby automaticky spustilo Server s obsahem prostřednictvím Předvolby → Sdílení → Sdílení po síti.

Server s obsahem umožňuje číst knihy přímo v samotném prohlížeči Safari. Kromě toho existuje mnoho aplikací pro vaše iZařízení, které se mohou připojit k Serveru s obsahem Calibre. Příklady zahrnují: Marvin, Mapleread a samotné iBooks.

Používání Serveru s obsahem

Spusťte prohlížeč Safari a zadejte IP adresu a port počítače, na kterém je spuštěn server Calibre:

http://192.168.1.2:8080/

Nahraďte 192.168.1.2 místní IP adresou počítače, na kterém je spuštěno Calibre. Podrobnosti o spuštění serveru a nalezení správné IP adresy naleznete na Server s obsahem Calibre.

V prohlížeči Safari uvidíte seznam knih, klepněte na kteroukoli knihu a budete mít možnost si ji buď stáhnout, nebo si ji přečíst v samotném prohlížeči. Pokud se rozhodnete ji stáhnout, Safari se vás zeptá, jestli ji chcete otevřít pomocí iBooks.

Mnoho čtecích aplikací podporuje procházení knihovny Calibre přímo prostřednictvím její podpory OPDS. V takových aplikacích můžete přejít na obrazovku online katalogu a přidat IP adresu serveru Calibre pro procházení a stahování knih z knihovny Calibre v rámci aplikace.

How do I use calibre with my Android phone/tablet or Kindle Fire?

Zařízení Android můžete připojit ke Calibre dvěma způsoby. Pomocí kabelu USB nebo bezdrátově, vzduchem. Prvním krokem pro použití zařízení Android je nainstalovat do něj aplikaci pro čtení e-knih. Existuje mnoho bezplatných a placených aplikací pro čtení e-knih pro Android: Některé příklady (v žádném konkrétním pořadí): FBReader [https://play.google.com/store/apps/details?id=org.geometerplus.zlibrary.ui.android&hl=en], Moon+ [https://play.google.com/store/apps/details?id=com.flyersoft.moonreader&hl=en], Mantano [https://play.google.com/store/apps/details?id=com.mantano.reader.android.lite&hl=en], Aldiko [https://play.google.com/store/apps/details?id=com.aldiko.android&hl=en], Kindle [https://play.google.com/store/apps/details?id=com.amazon.kindle&feature=related_apps].

Pomocí kabelu USB

Jednoduše připojte zařízení k počítači pomocí kabelu USB. Calibre by mělo automaticky rozpoznat zařízení a pak do něj můžete přenést knihy kliknutím na tlačítko Odeslat do zařízení. Pamatujte, že v MacOS a Linuxu lze k zařízení Android připojit pouze jeden program najednou, takže se ujistěte, že zařízení není otevřeno ve Správci souborů OS nebo v nástroji pro přenos souborů Android atd.

Poznámka

U novějších zařízení Android bude možná nutné provést několik věcí, aby připojení fungovalo, protože Google opravdu nechce, abyste byli nezávislí na jeho cloudu. Nejprve odemkněte obrazovku před připojením kabelu USB. Když připojíte kabel USB, zobrazí se automaticky otevírané oznámení. Ujistěte se, že říká něco jako „Přenos mediálních souborů“ nebo „MTP (Režim přenosu médií)“. Pokud tomu tak není, klepněte na oznámení a změňte režim na Přenos médií (MTP). V tomto bodě možná bude nutné restartovat Calibre, aby bylo zařízení rozpoznáno. Nakonec klepněte na OK. Automaticky otevírané okno se možná v zařízení bude objevovat pokaždé, když se k němu Calibre nebo operační systém skutečně pokusí připojit, a bude požádat o povolení.

Poznámka

U zařízení Kindle Fire 8 a novějších se po připojení kabelu USB zobrazí ikona , která ukazuje, že se zařízení nabíjí. Klepněte na ni a přepněte zařízení do režimu přenosu dat a poté spusťte Calibre, mělo být pak být rozpoznáno.

Vzduchem

Calibre má vestavěný webový server Server s obsahem Calibre. Díky němu je vaše kolekce Calibre dostupná po síti. Můžete ji procházet na svém zařízení pomocí jednoduchého prohlížeče nebo specializované aplikace. Nejprve proveďte následující kroky v Calibre:

- Nastavte Upřednostňovaný výstupní formát v Calibre na EPUB pro normální zařízení Android nebo MOBI pro Kindle (Výstupní formát lze nastavit pod Předvolby → Rozhraní → Chování)

- Převeďte knihy, které chcete na svém zařízení číst, do formátu EPUB nebo MOBI tak, že je vyberete a kliknete na tlačítko Převést.

- Zapněte Server s obsahem v předvolbách Calibre a nechte Calibre spuštěné.

Nyní ve svém zařízení Android otevřete prohlížeč a přejděte na

http://192.168.1.2:8080/

Nahraďte 192.168.1.2 místní IP adresou počítače, na kterém je spuštěno Calibre. Podrobnosti o spuštění serveru a nalezení správné IP adresy naleznete na Server s obsahem Calibre.

Nyní můžete procházet svou kolekci knih a stahovat knihy z Calibre do vašeho zařízení a otevírat je pomocí jakéhokoliv softwaru pro čtení e-knih, který máte na svém zařízení Android.

Mnoho čtecích aplikací podporuje procházení knihovny Calibre přímo prostřednictvím její podpory OPDS. V takových aplikacích můžete přejít na obrazovku online katalogu a přidat IP adresu serveru Calibre pro procházení a stahování knih z knihovny Calibre v rámci aplikace.

Can I access my calibre books using the web browser in my Kindle or other reading device?

calibre has a Content server that exports the books in calibre as a web page. See Server s obsahem Calibre for details.

Some devices, like the Kindle (1/2/DX), do not allow you to access port 8080 (the default port on which the content server runs). In that case, change the port in the calibre Preferences to 80. (On some operating systems, you may not be able to run the server on a port number less than 1024 because of security settings. In this case the simplest solution is to adjust your router to forward requests on port 80 to port 8080).

Also some devices do not have browsers advanced enough to run the app-like interface used by the Content server. For such devices, you can simply add /mobile to the server URL to get a simplified, non-JavaScript interface.

I cannot send emails using calibre?

Because of the large amount of spam in email, sending email can be tricky, as different mail servers use different strategies to block email. The most common problem is if you are sending email directly (without a mail relay) in calibre. Many servers (for example, Amazon) block email that does not come from a well known relay. The most robust way to setup email sending in calibre is to do the following:

- Create a free GMX account at GMX [https://www.gmx.com].

- Goto Preferences → Sharing → Sharing books by email in calibre and click the Use GMX button and fill in the information asked for.

- Log into your GMX account on the website and enable SMTP sending (Settings->POP3 & IMAP->Send and receive emails via external program)

- calibre will then be able to use GMX to send the mail.

- If you are sending to your Kindle, remember to update the email preferences on your Amazon Kindle page to allow email sent from your GMX email address. Also note that Amazon does not allow email delivery of AZW3 and new style (KF8) MOBI files. Finally, Amazon recently started sending confirmation emails that you have to click on back to your GMX account before the book is actually delivered.

Even after doing this, you may have problems. One common source of problems is that some poorly designed antivirus programs block calibre from opening a connection to send email. Try adding an exclusion for calibre in your antivirus program.

Poznámka

Microsoft/GMX can disable your account if you use it to send large amounts of email. So, when using these services to send mail calibre automatically restricts itself to sending one book every five minutes. If you don’t mind risking your account being blocked you can reduce this wait interval by going to Preferences → Advanced → Tweaks in calibre.

Poznámka

Google recently deliberately broke their email sending protocol (SMTP) support in an attempt to force everyone to use their web interface so they can show you more ads. They are trying to claim that SMTP is insecure, that is incorrect and simply an excuse. Use some other email provider instead.

Poznámka

If you are concerned about giving calibre access to your email account, simply create a new free email account with GMX or Outlook and use it only for calibre.

My device is getting mounted read-only in Linux, so calibre cannot connect to it?

Linux kernels mount devices read-only when their filesystems have errors. You can repair the filesystem with:

sudo fsck.vfat -y /dev/sdc

Replace /dev/sdc with the path to the device node of your device. You can find the device node of your device, which will always be under /dev by examining the output of:

mount

Why does calibre not support collections on the Kindle or shelves on the Nook?

Neither the Kindle nor the Nook provide any way to manipulate collections over a USB connection. If you really care about using collections, I would urge you to sell your Kindle/Nook and get a Kobo. Only Kobo seems to understand that life is too short to be entering collections one by one on an e-ink screen 😇

Note that in the case of the Kindle, there is a way to manipulate collections via USB, but it requires that the Kindle be rebooted every time it is disconnected from the computer, for the changes to the collections to be recognized. As such, it is unlikely that any calibre developers will ever feel motivated enough to support it. There is however, a calibre plugin that allows you to create collections on your Kindle from the calibre metadata. It is available from here [https://www.mobileread.com/forums/showthread.php?t=244202].

Poznámka

Amazon have removed the ability to manipulate collections completely in their newer models, like the Kindle Touch and Kindle Fire, making even the above plugin useless, unless you root your Kindle and install custom firmware.

I am getting an error when I try to use calibre with my Kobo Touch/Glo/etc.?

The Kobo has very buggy firmware. Connecting to it has been known to fail at random. Certain combinations of motherboard, USB ports/cables/hubs can exacerbate this tendency to fail. If you are getting an error when connecting to your touch with calibre try the following, each of which has solved the problem for some calibre users.

- Connect the Kobo directly to your computer, not via USB Hub

- Try a different USB cable and a different USB port on your computer

- Log out of the Kobo and log in again, this causes it to rebuild the database, fixing corrupted database errors.

- Try upgrading the firmware on your Kobo Touch to the latest

- Try resetting the Kobo (sometimes this cures the problem for a little while, but then it re-appears, in which case you have to reset again and again)

- Try only putting one or two books onto the Kobo at a time and do not keep large collections on the Kobo

Covers for books I send to my e-ink Kindle show up momentarily and then are replaced by a generic cover?

This happens because of an Amazon bug. They try to download a cover for the book from their servers and when that fails, they replace the existing cover that calibre created with a generic cover. For details see this forum thread [https://www.mobileread.com/forums/showthread.php?t=329945]. As of version
4.17, calibre has a workaround, where if you connect the Kindle to calibre after the covers have been destroyed by Amazon, calibre will restore them automatically. So in order to see the covers on your Kindle, you have to:

1. Send the book to the Kindle with calibre

2. Disconnect the Kindle and wait for Amazon to destroy the cover

3. Reconnect the Kindle to calibre

Note that this workaround only works for books sent with calibre 4.17 or later.
Alternately, simply keep your Kindle in airplane mode, you don’t really want
Amazon knowing every book you read anyway. I encourage you to contact Amazon customer support and complain loudly about this bug. Maybe Amazon will listen.

Poznámka

If the workaround is not working for you make sure you Kindle firmware is at least version 5.12.5, released in April 2020.

Obálky pro mé soubory MOBI se přestaly zobrazovat v Kindle pro PC nebo Kindle pro Android, iPad apod.

Je to způsobeno chybou v softwaru Amazonu. Můžete to obejít přejitím do Předvolby → Převod → Volby výstupu → Výstup MOBI a nastavením volby Povolit sdílení obsahu knihy…. Pokud převádíte dříve převedenou knihu, budete muset tuto volbu také povolit v dialogovém okně převodu pro danou jednotlivou knihu (protože nastavení převodu knihy se ukládají a mají přednost).

Pamatujte, že pokud to uděláte, bude to znamenat, že vygenerované MOBI se v Kindle Fire zobrazí pod osobními dokumenty namísto Books a nebude fungovat synchronizace Amazonu, ale obálky ano. Je to vaše volba, která funkce je pro vás důležitější. Doporučuji, abyste se obrátili na Amazon a požádali je o opravu této chyby.

Chyba v softwaru Amazonu je, že když do Kindle vložíte soubor MOBI, pokud není soubor označen jako osobní dokument, Amazon předpokládá, že jste knihu zakoupili od něj a pokusí se stáhnout náhled obálky ze svých serverů. Když se stahování nezdaří, odmítne se vrátit zpět k obálce definované v souboru MOBI. Je pravděpodobné, že se tak Amazon snaží zkusit donutit autory, aby prodávali pouze prostřednictvím něj. Jinými slovy, Kindle zobrazí obálky pouze pro knihy označené jako osobní dokumenty nebo knihy zakoupené přímo od Amazonu.

Pokud odešlete soubor MOBI do čtečky Kindle z Calibre pomocí připojení USB, Calibre obejde tuto chybu Amazonu nahráním samotného náhledu obálky. Toto řešení je však možné pouze při použití připojení USB a odesílání z Calibre. Pamatujte, že pokud odesíláte e-mailem, Amazon automaticky označí soubor MOBI jako osobní dokument a obálka bude fungovat, ale kniha se zobrazí v osobních dokumentech.

I transferred some books to my Kindle using calibre and they did not show up?

Books sent to the Kindle only show up on the Kindle after they have been indexed by the Kindle. This can take some time. If the book still does not show up after some time, then it is likely that the Kindle indexer crashed.
Sometimes a particular book can cause the indexer to crash. Unfortunately, Amazon has not provided any way to deduce which book is causing a crash on the Kindle.
Your only recourse is to either reset the Kindle, or delete all files from its memory using Windows Explorer (or whatever file manager you use) and then send the books to it again, one by one, until you discover the problem book. Once you have found the problem book, delete it off the Kindle and do a MOBI to MOBI or MOBI to AZW3 conversion in calibre and then send it back. This will most likely take care of the problem.

Library management

Obsah

- Where are the book files stored?

- How does calibre manage author names and sorting?

- Why doesn’t calibre let me store books in my own folder structure?

- Why doesn’t calibre have a column for foo?

- Can I have a column showing the formats or the ISBN?

- How do I move my calibre data from one computer to another?

- The list of books in calibre is blank!

- I am getting errors with my calibre library on a networked drive/NAS?

Where are the book files stored?

When you first run calibre, it will ask you for a folder in which to store your books. Whenever you add a book to calibre, it will copy the book into that folder. Books in the folder are nicely arranged into sub-folders by Author and Title. Note that the contents of this folder are automatically managed by calibre, do not add any files/folders manually to this folder, as they may be automatically deleted. If you want to add a file associated to a particular book, use the top right area of Edit metadata dialog to do so. Then, calibre will automatically put that file into the correct folder and move it around when the title/author changes.

Metadata about the books is stored in the file metadata.db at the top level of the library folder. This file is a sqlite database. When backing up your library make sure you copy the entire folder and all its sub-folders.

The library folder and all its contents make up what is called a calibre library. You can have multiple such libraries. To manage the libraries, click the calibre icon on the toolbar. You can create new libraries, remove/rename existing ones and switch between libraries easily.

You can copy or move books between different libraries (once you have more than one library setup) by right clicking on a book and selecting the Copy to library action.

How does calibre manage author names and sorting?

Author names are complex, especially across cultures, see this note [https://www.w3.org/International/questions/qa-personal-names.en.php?changelang=en] for some of the complexities. calibre has a very flexible strategy for managing author names. The first thing to understand is that books and authors are separate entities in calibre. A book can have more than one author, and an author can have more than one book. You can manage the authors of a book by the edit metadata dialog. You can manage individual authors by right clicking on the author in the Tag browser on the left of the main calibre window and selecting Manage authors. Using this dialog you can change the name of an author and also how that name is sorted. This will automatically change the name of the author in all the books of that author. When a book has multiple authors, separate their names using the & character.

Now coming to author name sorting:

- When a new author is added to calibre (this happens whenever a book by a new author is added), calibre automatically computes a sort string for both the book and the author.

- Authors in the Tag browser are sorted by the sort value for the authors. Remember that this is different from the Author sort field for a book.

- By default, this sort algorithm assumes that the author name is in First name Last name format and generates a Last name, First name sort value.

- You can change this algorithm by going to Preferences → Advanced → Tweaks and setting the author_sort_copy_method tweak.

- You can force calibre to recalculate the author sort values for every author by right clicking on any author and selecting Manage authors, then pushing the Recalculate all author sort values button. Do this after you have set the author_sort_copy_method tweak to what you want.

- You can force calibre to recalculate the author sort values for all books by using the bulk metadata edit dialog (select all books and click edit metadata, check the Automatically set author sort checkbox, then press OK).

- When recalculating the author sort values for books, calibre uses the author sort values for each individual author. Therefore, ensure that the individual author sort values are correct before recalculating the books‘ author sort values.

- You can control whether the Tag browser display authors using their names or their sort values by setting the categories_use_field_for_author_name tweak in Preferences → Advanced → Tweaks

Note that you can set an individual author’s sort value to whatever you want using Manage authors. This is useful when dealing with names that calibre will not get right, such as complex multi-part names like Miguel de Cervantes Saavedra or when dealing with Asian names like Sun Tzu.

With all this flexibility, it is possible to have calibre manage your author names however you like. For example, one common request is to have calibre display author names LN, FN. To do this, and if the note below does not apply to you, then:

- Set the author_sort_copy_method tweak to copy as described above.

- Restart calibre. Do not change any book metadata before doing the remaining steps.

- Change all author names to LN, FN using the Manage authors dialog.

- After you have changed all the authors, press the Recalculate all author sort values button.

- Press OK, at which point calibre will change the authors in all your books. This can take a while.

Poznámka

When changing from FN LN to LN, FN, it is often the case that the values in author_sort are already in LN, FN format. If this is your case, then do the following:

- Set the author_sort_copy_method tweak to copy as described above.

- Restart calibre. Do not change any book metadata before doing the remaining steps.

- Open the Manage authors dialog. Press the copy all author sort values to author button.

- Check through the authors to be sure you are happy. You can still press Cancel to abandon the changes. Once you press OK, there is no undo.

- Press OK, at which point calibre will change the authors in all your books. This can take a while.

Why doesn’t calibre let me store books in my own folder structure?

The whole point of calibre’s library management features is that they provide a search and sort based interface for locating books that is much more efficient than any possible folder scheme you could come up with for your collection. Indeed, once you become comfortable using calibre’s interface to find, sort and browse your collection, you won’t ever feel the need to hunt through the files on your disk to find a book again. By managing books in its own folder structure of Author -> Title -> Book files, calibre is able to achieve a high level of reliability and standardization. To illustrate why a search/tagging based interface is superior to folders, consider the following. Suppose your book collection is nicely sorted into folders with the following scheme:

Genre -> Author -> Series -> ReadStatus

Now this makes it very easy to find for example all science fiction books by Isaac Asimov in the Foundation series. But suppose you want to find all unread science fiction books. There’s no easy way to do this with this folder scheme, you would instead need a folder scheme that looks like:

ReadStatus -> Genre -> Author -> Series

In calibre, you would instead use tags to mark genre and read status and then just use a simple search query like tag:scifi and not tag:read. calibre even has a nice graphical interface, so you don’t need to learn its search language instead you can just click on tags to include or exclude them from the search.

To those of you that claim that you need access to the filesystem, so that you can have access to your books over the network, calibre has an excellent Content server that gives you access to your calibre library over the net.

If you are worried that someday calibre will cease to be developed, leaving all your books marooned in its folder structure, explore the powerful Save to disk feature in calibre that lets you export all your files into a folder structure of arbitrary complexity based on their metadata.

Finally, the reason there are numbers at the end of every title folder, is for robustness. That number is the id number of the book record in the calibre database. The presence of the number allows you to have multiple records with the same title and author names. It is also part of what allows calibre to magically regenerate the database with all metadata if the database file gets corrupted. Given that calibre’s mission is to get you to stop storing metadata in filenames and stop using the filesystem to find things, the increased robustness afforded by the id numbers is well worth the uglier folder names.

If you are still not convinced, then I’m afraid calibre is not for you. Look elsewhere for your book cataloguing needs. Just so we’re clear, this is not going to change. Kindly do not contact us in an attempt to get us to change this.

Why doesn’t calibre have a column for foo?

calibre is designed to have columns for the most frequently and widely used fields. In addition, you can add any columns you like. Columns can be added via Preferences → Interface → Add your own columns. Watch the tutorial UI Power tips [https://calibre-ebook.com/demo#tutorials] to learn how to create your own columns, or read this blog post [https://blog.calibre-ebook.com/calibre-custom-columns/].

You can also create „virtual columns“ that contain combinations of the metadata from other columns. In the add column dialog use the Quick create links to easily create columns to show the book ISBN or formats. You can use the powerful calibre template language to do much more with columns. For more details, see Jazyk šablon v programu Calibre.

Can I have a column showing the formats or the ISBN?

Yes, you can. Follow the instructions in the answer above for adding custom columns.

How do I move my calibre data from one computer to another?

You can export all calibre data (books, settings and plugins) and then import it on another computer. First let’s see how to export the data:

- Right click the calibre icon in the main calibre toolbar and select Export/import all calibre data. Note that if there is currently a device connected, this menu option will not be available – so, disconnect any connected devices. Then click the button labelled Export all your calibre data. You will see a list of all your calibre libraries. Click OK and choose an empty folder somewhere on your computer. The exported data will be saved in this folder. Simply copy this folder to your new computer and follow the instructions below to import the data.

- Install calibre on your new computer and run through the Welcome wizard, it does not matter what you do there, as you will be importing your old settings in the next step. You will now have an empty calibre, with just the Getting Started guide in your library. Once again, right click the calibre button and choose Export/import all calibre data. Then click the button labelled Import previously exported data. Select the folder with the exported data that you copied over earlier. You will now have a list of libraries you can import. Go through the list one by one, and select the new location for each library (a location is just an empty folder somewhere on your computer). Click OK.
After the import completes, calibre will restart, with all your old libraries, settings and calibre plugins.

Poznámka

This import/export functionality is only available from calibre version 2.47 onwards. If you have an older version of calibre, or if you encounter problems with the import/export, you can just copy over your calibre library folder manually, as described in the next paragraph.

Simply copy the calibre library folder from the old to the new computer. You can find out what the library folder is by clicking the calibre icon in the toolbar. Choose the Switch/create calibre library action and you will see the path to the current calibre library.

Now on the new computer, start calibre for the first time. It will run the Welcome wizard asking you for the location of the calibre library. Point it to the previously copied folder. If the computer you are transferring to already has a calibre installation, then the Welcome wizard won’t run. In that case, right-click the calibre icon in the toolbar and point it to the newly copied folder. You will now have two calibre libraries on your computer and you can switch between them by clicking the calibre icon on the toolbar. Transferring your library in this manner preserver all your metadata, tags, custom columns, etc.

The list of books in calibre is blank!

In order to understand why that happened, you have to understand what a calibre library is. At the most basic level, a calibre library is just a folder. Whenever you add a book to calibre, that book’s files are copied into this folder
(arranged into sub folders by author and title). Inside the calibre library folder, at the top level, you will see a file called metadata.db. This file is where calibre stores the metadata like title/author/rating/tags etc. for every book in your calibre library. The list of books that calibre displays is created by reading the contents of this metadata.db file.

There can be two reasons why calibre is showing a empty list of books:

- Your calibre library folder changed its location. This can happen if it was on an external disk and the drive letter for that disk changed. Or if you accidentally moved the folder. In this case, calibre cannot find its library and so starts up with an empty library instead. To remedy this, do a right-click on the calibre icon in the calibre toolbar and select Switch/create library. Click the little blue icon to select the new location of your calibre library and click OK. If you don’t know the new location search your computer for the file metadata.db.

- Your metadata.db file was deleted/corrupted. In this case, you can ask calibre to rebuild the metadata.db from its backups. Right click the calibre icon in the calibre toolbar and select Library maintenance->Restore database.
calibre will automatically rebuild metadata.db.

I am getting errors with my calibre library on a networked drive/NAS?

Do not put your calibre library on a networked drive.

A filesystem is a complex beast. Most network filesystems lack various filesystem features that calibre uses. Some don’t support file locking, some don’t support hardlinking, some are just flaky. Additionally, calibre is a single user application, if you accidentally run two copies of calibre on the same networked library, bad things will happen. Finally, different OSes impose different limitations on filesystems, so if you share your networked drive across OSes, once again, bad things will happen.

Consider using the calibre Content server to make your books available on other computers. Run calibre on a single computer and access it via the Content server or a Remote Desktop solution.

If you must share the actual library, use a file syncing tool like
DropBox or rsync instead of a networked drive. If you are using a file-syncing tool it is essential that you make sure that both calibre and the file syncing tool do not try to access the calibre library at the same time. In other words, do not run the file syncing tool and calibre at the same time.

Even with these tools there is danger of data corruption/loss, so only do this if you are willing to live with that risk. In particular, be aware that Google Drive is incompatible with calibre, if you put your calibre library in
Google Drive, you will suffer data loss. See this thread [https://www.mobileread.com/forums/showthread.php?t=205581] for details.

Různé

Obsah

- Amazon is stopping email delivery of MOBI files?

- I want calibre to download news from my favorite news website.

- Why the name calibre?

- Why does calibre show only some of my fonts on macOS?

- calibre is not starting on Windows?

- calibre freezes/crashes occasionally?

- The calibre E-book viewer and Edit book tools do not work on Windows?

- Using the viewer or doing any conversions results in a permission denied error on Windows

- calibre is not starting/crashing on macOS?

- I get only a black or white screen when running the calibre E-book viewer?

- Stáhnul jsem instalační soubor, ale nefunguje?

- My antivirus program claims calibre is a virus/trojan?

- How do I backup calibre?

- How do I use purchased EPUB books with calibre (or what do I do with .acsm files)?

- I am getting a „Permission Denied“ error?

- Can I have the comment metadata show up on my reader?

- How do I get calibre to use my HTTP proxy?

- I want some feature added to calibre. What can I do?

- Why doesn’t calibre have an automatic update?

- How is calibre licensed?

- How do I run calibre from my USB stick?

- How do I run parts of calibre like news download and the Content server on my own Linux server?

Amazon is stopping email delivery of MOBI files?

Amazon have announced [https://blog.the-ebook-reader.com/2022/05/03/amazon-dropping-mobi-support-on-send-to-kindle-apps/] that they will stop accepting MOBI files emailed to @kindle.com email addresses. You can instruct calibre to send EPUB instead of MOBI by going to Preferences → Sharing books by email and then removing MOBI from the list of formats to send to your @kindle.com email address and adding EPUB instead.

Note however, that Amazon’s EPUB intake is very flawed, they will reject a number of EPUB files that work everywhere else. In such cases you can try the following trick:

1. Convert the EPUB file to MOBI

2. Then convert the MOBI file back to EPUB and send the resulting EPUB file

This will remove all advanced formatting, embedded fonts, etc., but greatly increase the chances of Amazon accepting the EPUB.

Poznámka

If you were previously using email delivery of periodicals downloaded by calibre, you will be better off sending those by USB cable or downloading them from the calibre Content server via the Kindle’s built-in browser.
However, if you want to continue using email delivery you can try changing the output format in Preferences->Behavior to EPUB, then calibre will download the news in EPUB format. Whether Amazon will accept the EPUB or not is a whole other question.

I want calibre to download news from my favorite news website.

If you are reasonably proficient with computers, you can teach calibre to download news from any website of your choosing. To learn how to do this see Přidávání oblíbených webových stránek se zprávami.

Otherwise, you can request a particular news site by posting in the calibre Recipes forum [https://www.mobileread.com/forums/forumdisplay.php?f=228].

Why the name calibre?

Take your pick:

- Converter And LIBRary for E-books

- A high calibre product

- A tribute to the SONY Librie which was the first e-ink based e-book reader

- My wife chose it ;-)

calibre is pronounced as cal-i-ber not ca-li-bre. If you’re wondering, calibre is the British/commonwealth spelling for caliber. Being Indian, that’s the natural spelling for me.

Why does calibre show only some of my fonts on macOS?

calibre embeds fonts in e-book files it creates. E-book files support embedding only TrueType and OpenType (.ttf and .otf) fonts. Most fonts on macOS systems are in .dfont format, thus they cannot be embedded. calibre shows only TrueType and OpenType fonts found on your system. You can obtain many such fonts on the web. Simply download the .ttf/.otf files and add them to the Library/Fonts folder in your home folder.

calibre is not starting on Windows?

There can be several causes for this:

- If you get no errors but the calibre window does not appear, it has probably just appeared off screen. You can gather all windows onto the current screen using one of the techniques described here [https://www.wikihow.com/Bring-an-Off-Screen-Window-Back-on-Windows].

- If you get an error about calibre not being able to open a file because it is in use by another program, do the following:

- Uninstall calibre

- Reboot your computer

- Re-install calibre. But do not start calibre from the installation wizard.

- Temporarily disable your antivirus program (disconnect from the Internet before doing so, to be safe)

- Look inside the folder you chose for your calibre library. If you see a file named metadata.db, delete it.

- Start calibre

- From now on you should be able to start calibre normally.

- If you get an error about a Python function terminating unexpectedly after upgrading calibre, first uninstall calibre, then delete the folders (if they exists) C:\Program Files\Calibre and C:\Program Files\Calibre2. Now re-install and you should be fine.

- If you get an error in the Welcome wizard on an initial run of calibre, try choosing a folder like C:\library as the calibre library (calibre sometimes has trouble with library locations if the path contains non-English characters, or only numbers, etc.)

- Try running it as administrator (Right click on the icon and select Run as administrator)

If it still won’t launch, start a command prompt (press the Windows key and R; then type cmd.exe in the Run dialog that appears). At the command prompt type the following command and press Enter:

calibre-debug -g

Post any output you see in a help message on the Forum [https://www.mobileread.com/forums/forumdisplay.php?f=166].

calibre freezes/crashes occasionally?

There are several possible things I know of, that can cause this:

- You recently connected an external monitor or TV to your computer. In this case, whenever calibre opens a new window like the edit metadata window or the conversion dialog, it appears on the second monitor where you don’t notice it and so you think calibre has frozen. Disconnect your second monitor and restart calibre.

- The following programs have been reported to cause crashes in calibre: If you are running any of these, close them before starting calibre, or uninstall them: RoboForm, Logitech SetPoint Settings, Constant Guard Protection by
Xfinity, Spybot, Killer Network Manager, Nahimic UI Interface, Acronis True Image.

- You are using a Wacom branded USB mouse/tablet. There is an incompatibility between
Wacom drivers and the graphics toolkit calibre uses. Try using a non-Wacom mouse.

- On some 64 bit versions of Windows there are security software/settings that prevent 64-bit calibre from working properly. If you are using the 64-bit version of calibre try switching to the 32-bit version.

- If the crash happens when you are trying to copy text from the calibre
E-book viewer, it is most likely caused by some clipboard monitoring/managing application you have running. Turn it off and you should be fine.

- If the crashes happen specifically when you are using a file dialog, like clicking on the Add books button or the Save to
Disk button, then you have some software that has installed broken Shell extensions on your computer. Known culprits include: SpiderOak, odrive sync and Dell Backup and Recovery and NetDrive. If you have one of these, uninstall them and you will be fine. You can also use the NirSoft
Shell Extension Viewer [https://www.nirsoft.net/utils/shexview.html] to see what shell extensions are installed on your system and disable them individually, if you don’t want to uninstall the full program. Remember to use „Restart Explorer“ or reboot your computer after disabling the shell extensions.

If none of the above apply to you, then there is some other program on your computer that is interfering with calibre. First reboot your computer in safe mode, to have as few running programs as possible, and see if the crashes still happen. If they do not, then you know it is some program causing the problem.
The most likely such culprit is a program that modifies other programs‘ behavior, such as an antivirus, a device driver, something like RoboForm (an automatic form filling app) or an assistive technology like Voice Control or a
Screen Reader.

The only way to find the culprit is to eliminate the programs one by one and see which one is causing the issue. Basically, stop a program, run calibre, check for crashes. If they still happen, stop another program and repeat.

The calibre E-book viewer and Edit book tools do not work on Windows?

These two programs use hardware acceleration as they embed a version of the
Chrome browser to render HTML. If they do not work it will be because of incompatibility with your system’s GPU (graphics) drivers. Try updating these first, and reboot. If that does not fix it, you can set the QTWEBENGINE_CHROMIUM_FLAGS environment variable to the value --disable-gpu to turn off hardware acceleration. See this page [https://doc.qt.io/qt-6/qtwebengine-debugging.html] for details.

Using the viewer or doing any conversions results in a permission denied error on Windows

Something on your computer is preventing calibre from accessing its own temporary files. Most likely the permissions on your Temp folder are incorrect.
Go to the folder file:C:\Users\USERNAME\AppData\Local in Windows
Explorer and then right click on the file:Temp folder, select Properties and go to the Security tab. Make sure that your user account has full control for this folder.

Some users have reported that running the following command in an Administrator
Command Prompt fixed their permissions. To get an Administrator Command Prompt search for cmd.exe in the start menu, then right click on the command prompt entry and select Run as administrator. At the command prompt type the following command and press Enter:

icacls "%appdata%\..\Local\Temp" /reset /T

Alternately, you can run calibre as Administrator, but doing so will cause some functionality, such as drag and drop to not work.

Finally, some users have reported that disabling UAC fixes the problem.

calibre is not starting/crashing on macOS?

One common cause of failures on macOS is the use of accessibility technologies that are incompatible with the graphics toolkit calibre uses. Try turning off
VoiceOver if you have it on. Also go to System Preferences->System->Universal
Access and turn off the setting for enabling access for assistive devices in all the tabs. Another cause can be some third party apps that modify system behavior, such as Smart Scroll.

You can obtain debug output about why calibre is not starting by running Console.app. Debug output will be printed to it. If the debug output contains a line that looks like:

Qt: internal: -108: Error ATSUMeasureTextImage text/qfontengine_mac.mm

then the problem is probably a corrupted font cache. You can clear the cache by following these instructions [https://www.macworld.com/article/1139383/fontcacheclear.html]. If that doesn’t solve it, look for a corrupted font file on your system, in ~/Library/Fonts or the like. An easy way to check for corrupted fonts in macOS is to start the „Font Book“ application, select all fonts and then in the File menu, choose „Validate fonts“.

I get only a black or white screen when running the calibre E-book viewer?

This will be because of an incompatibility between Qt WebEngine, which the viewer uses to render and the GPU drivers on your system. First try upgrading the GPU drivers. If that does not help, you can try turning off hardware acceleration in Qt WebEngine by setting the environment variable QTWEBENGINE_CHROMIUM_FLAGS to the value --disable-gpu.
See Proměnné prostředí for how to change environment variables.

Stáhnul jsem instalační soubor, ale nefunguje?

Downloading from the Internet can sometimes result in a corrupted download. If the calibre installer you downloaded is not opening, try downloading it again. If re-downloading it does not work, download it from an alternate location [https://github.com/kovidgoyal/calibre/releases/latest]. If the installer still doesn’t work, then something on your computer is preventing it from running.

- Try temporarily disabling your antivirus program (Microsoft Security Essentials, or Kaspersky or Norton or McAfee or whatever). This is most likely the culprit if the upgrade process is hanging in the middle.

- Similarly, if the installer is failing/rolling back and you have Microsoft PowerToys running, quit it.

- Try rebooting your computer and running a registry cleaner like Wise registry cleaner [https://www.wisecleaner.com].

- Try a clean install. That is, uninstall calibre, delete C:\Program Files\Calibre2 (or wherever you previously chose to install calibre). Then re-install calibre. Note that uninstalling does not touch your books or settings.

- Try downloading the installer with an alternate browser. For example if you are using Microsoft Edge, try using Firefox or Chrome instead.

- If you get an error about a missing DLL on Windows, then most likely, the permissions on your temporary folder are incorrect. Go to the folder C:\Users\USERNAME\AppData\Local in Windows Explorer and then right click on the Temp folder and select Properties and go to the Security tab. Make sure that your user account has full control for this folder.

If you still cannot get the installer to work and you are on Windows, you can use the calibre portable install [https://calibre-ebook.com/download_portable], which does not need an installer (it is just a ZIP file).

My antivirus program claims calibre is a virus/trojan?

The first thing to check is that you are downloading calibre from the official website [https://calibre-ebook.com/download]. Make sure you are clicking the download links on the left, not the advertisements on the right. calibre is a very popular program and unscrupulous people try to setup websites offering it for download to fool the unwary.

If you have the official download and your antivirus program is still claiming calibre is a virus, then, your antivirus program is wrong. Antivirus programs use heuristics, patterns of code that „look suspicious“ to detect viruses. It’s rather like racial profiling. calibre is a completely open source product. You can actually browse the source code yourself (or hire someone to do it for you)
to verify that it is not a virus. Please report the false identification to whatever company you buy your antivirus software from. If the antivirus program is preventing you from downloading/installing calibre, disable it temporarily, install calibre and then re-enable it.

How do I backup calibre?

The most important thing to backup is the calibre library folder, that contains all your books and metadata. This is the folder you chose for your calibre library when you ran calibre for the first time. You can get the path to the library folder by clicking the calibre icon on the main toolbar. You must backup this complete folder with all its files and sub-folders.

You can switch calibre to using a backed up library folder by simply clicking the calibre icon on the toolbar and choosing your backup library folder. A backed up library folder backs up your custom columns and saved searches as well as all your books and metadata.

If you want to backup the calibre configuration/plugins, you have to backup the config folder. You can find this config folder via Preferences → Miscellaneous. Note that restoring configuration folders is not officially supported, but should work in most cases. Just copy the contents of the backup folder into the current configuration folder to restore.

How do I use purchased EPUB books with calibre (or what do I do with .acsm files)?

Most purchased EPUB books have DRM. This prevents calibre from opening them. You can still use calibre to store and transfer them to your e-book reader. First, you must authorize your reader on a Windows machine with Adobe Digital Editions. Once this is done, EPUB books transferred with calibre will work fine on your reader. When you purchase an epub book from a website, you will get an „.acsm“ file. This file should be opened with Adobe Digital Editions, which will then download the actual „.epub“ e-book. The e-book file will be stored in the folder „My Digital Editions“, from where you can add it to calibre.

I am getting a „Permission Denied“ error?

A permission denied error can occur because of many possible reasons, none of them having anything to do with calibre.

- You can get permission denied errors if you are using an SD card with write protect enabled.

- Pokud v systému MacOS zjistíte chyby oprávnění při připojování zařízení ke Calibre, můžete to napravit pod Předvolby systému > Zabezpečení a soukromí > Soukromí > Soubory a složky.

- If you, or some program you used changed the file permissions of the files in question to read only.

- If there is a filesystem error on the device which caused your operating system to mount the filesystem in read only mode or mark a particular file as read only pending recovery.

- If the files have their owner set to a user other than you.

- If your file is open in another program.

- If the file resides on a device, you may have reached the limit of a maximum of 256 files in the root of the device. In this case you need to reformat the device/sd card referred to in the error message with a FAT32 filesystem, or delete some files from the SD card/device memory.

You will need to fix the underlying cause of the permissions error before resuming to use calibre. Read the error message carefully, see what file it points to and fix the permissions on that file or its containing folders.

Can I have the comment metadata show up on my reader?

Most readers do not support this. You should complain to the manufacturer about it and hopefully if enough people complain, things will change. In the meantime, you can insert the metadata, including comments into a „Jacket page“ at the start of the e-book, by using the option to „Insert metadata as page at start of book“ during conversion. The option is found in the Structure detection section of the conversion settings. Note that for this to have effect you have to convert the book. If your book is already in a format that does not need conversion, you can convert from that format to the same format.

Another alternative is to create a catalog in e-book form containing a listing of all the books in your calibre library, with their metadata. Click-and-hold the Convert button to access the catalog creation tool. And before you ask, no you cannot have the catalog „link directly to“ books on your reader.

How do I get calibre to use my HTTP proxy?

By default, calibre uses whatever proxy settings are set in your OS. Sometimes these are incorrect, for example, on Windows if you don’t use Microsoft Edge then the proxy settings may not be up to date. You can tell calibre to use a particular proxy server by setting the http_proxy and https_proxy environment variables. The format of the variable is: http://username:password@servername you should ask your network administrator to give you the correct value for this variable. Note that calibre only supports HTTP proxies not SOCKS proxies. You can see the current proxies used by calibre in Preferences->Miscellaneous.

I want some feature added to calibre. What can I do?

You have two choices:

1. Create a patch by hacking on calibre and send it to me for review and inclusion. See Development [https://calibre-ebook.com/get-involved].

2. Open a bug requesting the feature [https://calibre-ebook.com/bugs]. Remember that while you may think your feature request is extremely important/essential, calibre developers might not agree. Fortunately, calibre is open source, which means you always have the option of implementing your feature yourself, or hiring someone to do it for you. Furthermore, calibre has a comprehensive plugin architecture, so you might be able to develop your feature as a plugin, see Psaní vlastních modulů pro rozšíření funkcí Calibre.

Why doesn’t calibre have an automatic update?

Z mnoha důvodů:

- There is no need to update every week. If you are happy with how calibre works turn off the update notification and be on your merry way. Check back to see if you want to update once a year or so. There is a check box to turn off the update notification, on the update notification itself.

- calibre downloads currently use about 150TB of bandwidth a month [https://calibre-ebook.com/dynamic/downloads]. Implementing automatic updates would greatly increase that and end up costing thousands of dollars a month, which someone has to pay.

- If I implement a dialog that downloads the update and launches it, instead of going to the website as it does now, that would save the most ardent calibre updater, at most five clicks a week. There are far higher priority things to do in calibre development.

- If you really, really hate downloading calibre every week but still want to be up to the latest, I encourage you to run from source, which makes updating trivial. Instructions are available here.

- There are third party automatic updaters for calibre made by calibre users in the calibre forum [https://www.mobileread.com/forums/forumdisplay.php?f=238].

How is calibre licensed?

calibre is licensed under the GNU General Public License v3 (an open source license). This means that you are free to redistribute calibre as long as you make the source code available. So if you want to put calibre on a CD with your product, you must also put the calibre source code on the CD. The source code is available for download [https://download.calibre-ebook.com]. You are free to use the results of conversions from calibre however you want. You cannot use either code or libraries from calibre in your software without making your software open source. For details, see The GNU GPL v3 [https://www.gnu.org/licenses/gpl.html].

How do I run calibre from my USB stick?

A portable version of calibre is available here [https://calibre-ebook.com/download_portable].

How do I run parts of calibre like news download and the Content server on my own Linux server?

First, you must install calibre onto your Linux server. If your server is using a modern Linux distribution, you should have no problems installing calibre onto it.

Poznámka
calibre needs GLIBC >= 2.31 and libstdc++ >= 6.0.28. If you have an older server, you will either need to compile these from source, or use calibre
3.48 which requires GLIBC >= 2.17 or 2.85.1 which requires GLIBC >= 2.13 or calibre 1.48 which requires only GLIBC >= 2.10. In addition, although the calibre command line utilities do not need a running X server, some of them do require the X server libraries to be installed on your system. This is because of Qt, which is used for various image processing tasks, and links against these libraries. If you get an ImportError about some Qt modules, you are likely missing some X libraries. Typical candidates are: libxcb-cursor0, libxcb-xinerama0, libegl1, libopengl0.

You can run the calibre server via the command:

/opt/calibre/calibre-server /path/to/the/library/you/want/to/share

You can download news and convert it into an e-book with the command:

/opt/calibre/ebook-convert "Title of news source.recipe" outputfile.epub

If you want to generate MOBI, use outputfile.mobi instead and use --output-profile kindle.

You can email downloaded news with the command:

/opt/calibre/calibre-smtp

I leave figuring out the exact command line as an exercise for the reader.

Finally, you can add downloaded news to the calibre library with:

/opt/calibre/calibredb add --with-library /path/to/library outfile.epub

Remember to read the Rozhraní příkazového řádku section of the calibre User Manual to learn more about these, and other commands.

© Copyright Kovid Goyal.
Naposledy aktualizováno kvě 03, 2024.

Navigace

- moduly
- další |
- předchozí |
- Spustit »
- Kurzy

Kurzy

Zde naleznete kurzy, abyste mohli začít používat pokročilejší funkce Calibre, jako jsou XPath a šablony.

- Přidávání oblíbených webových stránek se zprávami

- Správa podskupin knih, například „žánr
- Kurz XPath
- Jazyk šablon v programu Calibre

- Vše o používání regulárních výrazů v Calibre

- Psaní vlastních modulů pro rozšíření funkcí Calibre

- Sazba matematiky v e-knihách
- Vytváření katalogů AZW3 • EPUB • MOBI
- Virtuální knihovny

© Copyright Kovid Goyal.
Naposledy aktualizováno kvě 03, 2024.

Navigace

- moduly
- další |
- předchozí |
- Spustit »
- Kurzy »
- Správa podskupin knih, například „žánr

Správa podskupin knih, například „žánr

Někteří lidé si chtějí uspořádat knihy ve své knihovně do podskupin podobných podsložkám. Nejběžněji udávaný důvod je vytvořit hierarchie žánrů, ale je mnoho dalších. Jeden uživatel požadoval způsob, jak uspořádat učebnice podle předmětu a čísla kurzu. Další uživatel chtěl sledovat dárky podle předmětu a příjemce. Tento kurz bude používat po zbytek tohoto příspěvku příklad žánru.

Než budete pokračovat, pamatujte, že nemluvíme o složkách na pevném disku. Podskupiny nejsou složky souborů. Knihy nebudou nikam kopírovány. Struktura souborů knihovny Calibre není ovlivněna. Namísto toho představujeme způsob, jak uspořádat a zobrazit podskupiny knih v knihovně Calibre.

- Nastavení

- Hledání

- Omezení

- Užitečné funkce šablon

Běžně udávané požadavky pro podskupiny, jako žánry, jsou:

- Podskupina (např. žánr) musí obsahovat (ukazovat na) knihy, ne kategorie knih. To odlišuje podskupiny od uživatelských kategorií Calibre.

- Kniha může být ve více podskupinách (žánrech). To odlišuje podskupiny od fyzických složek souborů.

- Podskupiny (žánry) musí tvořit hierarchii; podskupiny mohou obsahovat podskupiny.

Štítky vám poskytují první dva. Když označíte knihu štítkem žánru, můžete pak použít Prohlížeč štítků (nebo hledat) pro nalezení knih s tímto žánrem, což je první. Mnoho knih může mít stejný štítek nebo štítky, to je druhý. Problém je, že štítky nevyhovují třetímu požadavku. Neposkytují hierarchii.

Funkce hierarchie Calibre vám poskytuje třetí – schopnost vidět žánry ve ‚stromu‘ a schopnost snadno hledat knihy v žánru nebo podžánru. Například předpokládejme, že vaše struktura žánrů je podobná následující:

Genre
. History
.. Japanese
.. Military
.. Roman
. Mysteries
.. English
.. Vampire
. Science Fiction
.. Alternate History
.. Military
.. Space Opera
. Thrillers
.. Crime
.. Horror
etc.

Použitím funkce hierarchie můžete vidět tyto žánry v Prohlížeči štítků v podobě stromu, jak je zobrazeno na obrázku. V tomto příkladu je nejkrajnější úroveň (Žánr) vlastním sloupcem, který obsahuje žánry. Žánry obsahující podžánry se zobrazují s malým trojúhelníkem vedle sebe. Kliknutí na tento trojúhelník otevře položku a zobrazí podžánry, jak můžete vidět u Historie a Sci-fi.

Kliknutí na žánr může vyhledat všechny knihy s tímto žánrem nebo podřízené položky tohoto žánru. Například kliknutí na Sci-fi může předat všechny tři podřízené žánry, Alternativní historie, Vojenská, Vesmírná opera. Kliknutí na Alternativní historie předá knihy v tomto žánru, ignoruje ty v žánrech Vojenská a Vesmírná opera. Knihy samozřejmě mohou mít více žánrů. Pokud mát kniha žánr jak Vesmírná opera, tak Vojenská, pak uvidíte tuto knihu při kliknutí na oba žánry. Hledání je probráno podrobněji níže.

Další věc, kterou můžete vidět na obrázku, je, že žánr Vojenská se objevuje dvakrát, jednou pod Historie a jednou pod Sci-fi. Protože jsou žánry v hierarchii, jsou to dva samostatné žánry. Kniha může být v jednom, v druhém nebo (s pochybami v tomto případě) v obou. Například knihy Winstona Churchilla ze série „Druhá světová válka“ by mohly být v „Historie.Vojenská“. Knihy Davida Weber o Honor Harringtonové by mohly být v „Sci-fi.Vojenská“ a ostatně také v „Sci-fi.Vesmírná opera“.

Jakmile žánr existuje, tj. alespoň jedna kniha má tento žánr, můžete ho snadno použít na další knihy přetažením knih ze zobrazení knihovny na žánr, který chcete, aby knihy měly. Žánry můžete také použít v editorech metadat; více o tomto níže.

Nastavení

Nyní se možná ptáte: „Jak bylo tohle všechno nastaveno?“ Jsou tři kroky: 1) vytvořte vlastní sloupec, 2) řekněte Calibre, že tento nový sloupec má být považován za hierarchii a 3) přidejte žánry.

Vlastní sloupec vytvoříte obvyklým způsobem pomocí Předvolby -> Přidat své vlastní sloupce. Tento příklad používá „#genre“ jako název vyhledávání a „Žánr“ jako záhlaví sloupce. Typ sloupce je „Čárkami oddělený text, jako jsou štítky, zobrazený v Prohlížeči štítků“.

Pak po restartování Calibre musíte říct Calibre, že sloupec má být považován za hierarchii. Přejděte do Předvolby  →  Vzhled  →  Prohlížeč štítků a zadejte název vyhledávání „#genre“ do pole „Kategorie s hierarchickými položkami“. Stiskněte Použít a jste hotovi s nastavením.

V tuto chvíli nejsou v tomto sloupci žádné žánry. Zbývá nám poslední krok: jak použít žánr na knihu. Žánr v Calibre neexistuje, dokud se neobjeví alespoň u jedné knihy. Pro naučení se, jak poprvé použít žánr, musíme probrat podrobnosti o tom, jak žánr vypadá v metadatech knihy.

Hierarchie ‚věcí‘ je vystavěna vytvořením položky skládající se z frází oddělených tečkami. V pokračování příkladu se žánrem by tyto položky byly „Historie.Vojenská“, „Mysteriózní.Upíři“, „Sci-fi.Vesmírná opera“ atd. Tudíž pro vytvoření nového žánru vyberte knihu, která by měla mít tento žánr, upravte její metadata a zadejte nový žánr do sloupce, který jste vytvořili. V pokračování našeho příkladu, pokud chcete ke knize přiřadit nový žánr „Komiks“ s podžánrem „Superhrdinové“, otevřeli byste ‚Upravit metadata‘ pro tuto knihu (komiks), zvolili kartu Vlastní metadata a pak zadali „Komiks.Superhrdinové“, jak je zobrazeno na následujícím obrázku (ignorujte ostatní vlastní sloupce):

Po provedení výše uvedeného uvidíte v Prohlížeči štítků:

Od teď pro použití tohoto nového žánru na knihu (pravděpodobně komiksu) můžete buď přetáhnout knihu na žánru, nebo ho přidat do knihy pomocí úpravy metadat přesně stejným způsobem, jak je provedeno výše.

Poznámka

Hierarchické zobrazení funguje pouze pokud je Prohlížeč štítků nastaven na řazení položek podle názvu. Toto je výchozí nastavení a lze jej zkontrolovat kliknutím na tlačítko Konfigurovat v dolní části Prohlížeče štítků.

Hledání

Nejsnadnější způsob hledání žánrů je pomocí Prohlížeče štítků, kliknutím na žánr, který chcete vidět. Kliknutím na žánr s podřízenými položkami vám zobrazí knihy s tímto žánrem a se všemi podřízenými žánry. To však může vyvolat otázku. Jen proto, že má žánr podřízené položky, neznamená to, že to není žánr sám o sobě. Například kniha může mít žánr „Historie“, ale ne „Historie.Vojenská“. Jak hledat knihy, které mají pouze „Historie“?

Mechanismus hledání Prohlížeče štítku ví, pokud má položka podřízené položky. Pokud ano, klepnutí na položku proběhne 5 hledáními namísto normálních tří. První je normální zelené plus, které vám zobrazí knihy pouze s tímto žánrem(např. Historie). Druhé je dvojité plus (zobrazeno výše), která vám ukáže knihy s tímto žánrem a všemi podžánry (např. Historie a Historie.Vojenská). Třetí je normální červené minus, které vám ukáže knihy bez tohoto přesného žánru. Čtvrté je dvojité minus, které vám ukáže knihy, bez tohoto žánru nebo podžánrů. Páté je zpět na začátek, bez označení, což znamená žádné hledání.

Omezení

Pokud hledáte žánr, pak k tomu vytvořte uložené hledání, můžete použít pole ‚omezit na‘ pro vytvoření Virtuální knihovny knih s tímto žánrem. To je užitečné, pokud chcete provést další hledání v rámci žánru, nebo spravovat nebo aktualizovat metadata knih v žánru. V pokračování našeho příkladu můžete vytvořit Uložené hledání s názvem „Historie.Japonská“ nejdříve kliknutím na žánr Japonská v Prohlížeči štítků pro získání hledání do pole hledání, zadáním Historie.Japonská do pole uloženého hledání, a pak stisknutím tlačítka „Uložit hledání“ (zelené pole s bílým plus na pravé straně).

Po vytvoření uloženého hledání ho můžete použít jako omezení.

Užitečné funkce šablon

Možná budete chtít použít informace o žánru v šabloně, jako u uložení na disk nebo odeslat do zařízení. Otázkou by pak mohlo být „Jak získám nejkrajnější název nebo názvy žánru?“ K dispozici je funkce šablony Calibre, podpoložky, aby to udělala snadněji.

Předpokládejme například, že chcete přidat nejkrajnější úroveň žánru do šablony uložit na disk pro vytvoření složek žánrů, jako v „Historie/Blížící se bouře - Churchill, Winston“. Abyste to provedli, musíte extrahovat první úroveň hierarchie a přidat ji před spolu s lomítkem pro označení, že by se měla vytvořit složka. Níže uvedená šablona toto splňuje:

{#genre:subitems(0,1)||/}{title} - {authors}

Pro více informací o šablonách a funkci subitems() se podívejte na Jazyk šablony.

© Copyright Kovid Goyal.
Naposledy aktualizováno kvě 03, 2024.

Navigace

- moduly
- další |
- předchozí |
- Spustit »
- Kurzy »
- Kurz XPath

Kurz XPath

V tomto kurzu vám bude poskytnut mírný úvod do XPath [https://en.wikipedia.org/wiki/XPath], dotazovací jazyk, který může být použit pro výběr libovolných částí dokumentů HTML [https://en.wikipedia.org/wiki/HTML] v Calibre. XPath je široce používaný standard a jeho vyhledání na Google vám poskytne mnoho informací. Tento kurz se však zaměřuje na používání XPath pro úkoly spojené s e-knihami, jako je hledání nadpisů kapitol v nestrukturovaném dokumentu HTML.

Obsah

- Výběr podle názvu značky

- Výběr podle atributů

- Výběr podle obsahu značky

- Ukázková e-kniha

- Vestavěné funkce XPath

Výběr podle názvu značky

Nejjednodušší forma výběru je vybrat značky podle názvu. Například předpokládejme, že chcete vybrat v dokumentu všechny značky <h2>. Dotaz XPath pro toto je jednoduše:

//h:h2 (Selects all <h2> tags)

Předpona // znamená hledat na jakékoliv úrovni dokumentu. Nyní předpokládejme, že chcete hledat značky <span>, které jsou uvnitř značek <a>. Toho může být dosaženo pomocí:

//h:a/h:span (Selects <span> tags inside <a> tags)

Pokud chcete hledat značky na určité úrovni v dokumentu, změňte předponu:

/h:body/h:div/h:p (Selects <p> tags that are children of <div> tags that are
children of the <body> tag)

Toto porovná pouze <p>Velice krátká e-kniha pro ukázání použití XPath.</p> v Ukázková e-kniha, ale ne žádné další značky <p>. Předpona h: ve výše uvedených příkladech je zapotřebí pro porovnání značek XHTML. To je proto, že Calibre vnitřně představuje celý obsah jako XHTML. V XHTML mají značky obor názvů a h: je předpona oboru názvů pro značky HTML.

Nyní předpokládejme, že chcete vybrat jak značky <h1>, tak <h2>. K tomu potřebujeme konstrukci XPath zvanou predikát. Predikát je jednoduše test, který je použitý pro výběr značek. Testy mohou být libovolně výkonné, a jak bude tento kurz postupovat, uvidíte výkonnější příklady. Predikát je vytvořen uzavřením testovacího výrazu do hranatých závorek:

//*[name()='h1' or name()='h2']

V tomto výrazu XPath je několik nových funkcí. První je použití zástupného znaku *. Ten znamená porovnat jakoukoliv značku. Nyní se podívejte na testovací výraz name()='h1' or name()='h2'. name() je příklad vestavěné funkce. Jednoduše vyhodnocuje podle názvu značky. Takže jejím použitím můžeme vybrat značky, jejichž názvy jsou buď h1 nebo h2. Pamatujte, že funkce name() ignoruje obory názvů, takže není potřeba předpona h:. XPath má několik užitečných vestavěných funkcí. V tomto kurzu bude představeno několik dalších.

Výběr podle atributů

Pro výběr značek na základě jejich atributů je požadováno použití predikátů:

//*[@style] (Select all tags that have a style attribute)
//*[@class="chapter"] (Select all tags that have class="chapter")
//h:h1[@class="bookTitle"] (Select all h1 tags that have class="bookTitle")

Operátor @ zde odkazuje na atributy značky. Můžete použít některé z vestavěných funkcí XPath pro provedení propracovanějšího porovnání hodnot atributů.

Výběr podle obsahu značky

Pomocí XPath můžete dokonce vybrat značky na základě textu, který obsahují. Nejlepší způsob, jak to udělat, je použít sílu regulárních výrazů pomocí vestavěné funkce re:test():

//h:h2[re:test(., 'chapter|section', 'i')] (Selects <h2> tags that contain the words chapter or
section)

Operátor . zde odkazuje na obsah značky, stejně jako operátor @ odkazoval na její atributy.

Ukázková e-kniha

<html>
<head>
<title>A very short e-book</title>
<meta name="charset" value="utf-8" />
</head>
<body>
<h1 class="bookTitle">A very short e-book</h1>
<p style="text-align:right">Written by Kovid Goyal</p>
<div class="introduction">
<p>A very short e-book to demonstrate the use of XPath.</p>
</div>

<h2 class="chapter">Chapter One</h2>
<p>This is a truly fascinating chapter.</p>

<h2 class="chapter">Chapter Two</h2>
<p>A worthy continuation of a fine tradition.</p>
</body>
</html>

Vestavěné funkce XPath

name()

Název aktuální značky.

contains()

contains(s1, s2) vrací true, pokud s1 obsahuje s2.

re:test()

re:test(zdroj, vzor, příznaky) vrací true, pokud řetězec zdroj odpovídá regulárnímu výrazu vzor. Obzvlášť užitečný příznak je i, porovnání je pak bez rozlišení malých a velkých písmen. Dobré základy syntaxe pro regulární výrazy můžete nalézt na syntaxe regexp [https://docs.python.org/library/re.html]

© Copyright Kovid Goyal.
Naposledy aktualizováno kvě 03, 2024.

Navigace

- moduly
- další |
- předchozí |
- Spustit »
- Kurzy »
- Jazyk šablon v programu Calibre

Jazyk šablon v programu Calibre

The calibre template language is a calibre-specific language used throughout calibre for tasks such as specifying file paths, formatting values, and computing the value for user-specified columns. Examples:

- Specify the folder structure and file names when saving files from the calibre library to the disk or e-book reader.

- Define rules for adding icons and colors to the calibre book list.

- Define virtual columns that contain data from other columns.

- Advanced library searching.

- Advanced metadata search and replace.

The language is built around the notion of a template, which specifies which book metadata to use, computations on that metadata, and how it is to be formatted.

Basic templates

A basic template consists one or more template expressions. A template expression consists of text and names in curly brackets ({}) that is replaced by the corresponding metadata from the book being processed. For example, the default template in calibre used for saving books to device has 4 template expressions:

{author_sort}/{title}/{title} - {authors}

For the book „The Foundation“ by „Isaac Asimov“ the will become:

Asimov, Isaac/The Foundation/The Foundation - Isaac Asimov

The slashes are not template expressions because they are in between in {}. Such text is left where it appears. For example, if the template is:

{author_sort} Some Important Text {title}/{title} - {authors}

then for „The Foundation“ the template produces:

Asimov, Isaac Some Important Text The Foundation/The Foundation - Isaac Asimov

A template expression can access all the metadata available in calibre, including custom columns (columns you create yourself), by using a column’s lookup name. To find the lookup name for a column (sometimes called fields), hover your mouse over the column header in calibre’s book list. Lookup names for custom columns always begin with #. For series type columns there is an additional field named #lookup name_index that is the series index for that book in the series. For example, if you have a custom series column named #myseries, there will also be a column named #myseries_index. The standard series column’s index is named series_index.

In addition to the standard column based fields, you also can use:

- {formats} - A list of formats available in the calibre library for a book

- {identifiers:select(isbn)} - The ISBN of the book

If the metadata for the field for a given book is not defined then the field in the template is replaced by the empty string (''). For example, consider the following template:

{author_sort}/{series}/{title} {series_index}

If Asimov’s book „Second Foundation“ is in the series „Foundation“ then the template produces:

Asimov, Isaac/Foundation/Second Foundation 3

If a series has not been entered for the book then the template produces:

Asimov, Isaac/Second Foundation

The template processor automatically removes multiple slashes and leading or trailing spaces.

Advanced formatting

In addition to metadata substitution, templates can conditionally include additional text and control how substituted data is formatted.

Conditionally including text

Sometimes you want text to appear in the output only if a field is not empty. A common case is series and series_index where you want either nothing or the two values separated by a hyphen. calibre handles this case using a special template expression syntax.

For example and using the above Foundation example, assume you want the template to produce Foundation - 3 - Second Foundation. This template produces that output:

{series} - {series_index} - {title}

However, if a book has no series the template will produce - - the title, which is probably not what you want. Generally, people want the result be the title without the extraneous hyphens. You can accomplish this using the following template syntax:

{field:|prefix_text|suffix_text}

This template expression says that if field has the value XXXX then the result will be prefix_textXXXXXsuffix_text. If field is empty (has no value) then the result will be the empty string (nothing) because the prefix and suffix are ignored. The prefix and suffix can contain blanks.

Do not use subtemplates (`{ … }`) or functions (see below) in the prefix or the suffix.

Using this syntax, we can solve the above no-series problem with the template:

{series}{series_index:| - | - }{title}

The hyphens will be included only if the book has a series index, which it has only if it has a series. Continuing the Foundation example again, the template will produce Foundation - 1 - Second Foundation.

Notes:

- You must include the colon after the lookup name if you are using a prefix or a suffix.

- You must either use either no or both | characters. Using one, as in {field:| - }, is not allowed.

- It is OK to provide no text for either the prefix or the suffix, such as in {series:|| - }. The template {title:||} is the same as {title}.

Formatting

Suppose you want the series_index to be formatted as three digits with leading zeros. This does the trick:

{series_index:0>3s} - Three digits with leading zeros

For trailing zeros, use:

{series_index:0<3s} - Three digits with trailing zeros

If you use series indices with fractional values, e.g., 1.1, you might want the decimal points to line up. For example, you might want the indices 1 and 2.5 to appear as 01.00 and 02.50 so that they will sort correctly on a device that does lexical sorting. To do this, use:

{series_index:0>5.2f} - Five characters consisting of two digits with leading zeros, a decimal point, then 2 digits after the decimal point.

If you want only the first two letters of the data, use:

{author_sort:.2} - Only the first two letters of the author sort name

Much of the calibre template language formatting comes from Python. For more details on the syntax of these advanced formatting operations see the Python documentation [https://docs.python.org/3/library/string.html#formatstrings].

Using templates to define custom columns

Templates can be used to display information that isn’t in calibre metadata, or to display metadata differently from calibre’s normal format. For example, you might want to show the ISBN, a field that calibre does not display. You can accomplish this creating a custom column with the type Column built from other columns (hereafter called composite columns) and providing a template to generate the displayed text. The column will display the result of evaluating the template. For example, to display the ISBN, create the column and enter {identifiers:select(isbn)} in the template box. To display a column containing the values of two series custom columns, separated by a comma, use {#series1:||,}{#series2}.

Composite columns can use any template option, including formatting.

Note: You cannot edit the data displayed in a composite column. Instead you edit the source columns. If you edit a composite column, for example by double-clicking it, calibre will open the template for editing, not the underlying data.

Templates and plugboards

Plugboards are used for changing the metadata written into books during send-to-device and save-to-disk operations. A plugboard permits you to specify a template to provide the data to write into the book’s metadata. You can use plugboards to modify the following fields: authors, author_sort, language, publisher, tags, title, title_sort. This feature helps people who want to use different metadata in books on devices to solve sorting or display issues.

When you create a plugboard, you specify the format and device for which the plugboard is to be used. A special device is provided, save_to_disk, that is used when saving formats (as opposed to sending them to a device). Once you have chosen the format and device, you choose the metadata fields to change, providing templates to supply the new values. These templates are connected to their destination fields, hence the name plugboards. You can of course use composite columns in these templates.

Plugboards are quite flexible and can be written in Single Function Mode, Template Program Mode, General Program Mode, or Python Template mode.

When a plugboard might apply (Content server, save to disk, or send to device), calibre searches the defined plugboards to choose the correct one for the given format and device. For example, to find the appropriate plugboard for an EPUB book being sent to an ANDROID device, calibre searches the plugboards using the following search order:

- a plugboard with an exact match on format and device, e.g., EPUB and ANDROID

- a plugboard with an exact match on format and the special any device choice, e.g., EPUB and any device

- a plugboard with the special any format choice and an exact match on device, e.g., any format and ANDROID

- a plugboard with any format and any device

The tags and authors fields have special treatment, because both of these fields can hold more than one item. A book can have many tags and many authors. When you specify that one of these two fields is to be changed, the template’s result is examined to see if more than one item is there. For tags, the result is cut apart wherever calibre finds a comma. For example, if the template produces the value Thriller, Horror, then the result will be two tags, Thriller and Horror. There is no way to put a comma in the middle of a tag.

The same thing happens for authors, but using a different character for the cut, a & (ampersand) instead of a comma. For example, if the template produces the value Blogs, Joe&Posts, Susan, then the book will end up with two authors, Blogs, Joe and Posts, Susan. If the template produces the value Blogs, Joe;Posts, Susan, then the book will have one author with a rather strange name.

Plugboards affect the metadata written into the book when it is saved to disk or written to the device. Plugboards do not affect the metadata used by save to disk and send to device to create the file names. Instead, file names are constructed using the templates entered on the appropriate preferences window.

Using functions in templates - Single Function Mode

Suppose you want to display the value of a field in upper case when that field is normally in title case. You can do this using template functions. For example, to display the title in upper case use the uppercase function, as in {title:uppercase()}. To display it in title case, use {title:titlecase()}.

Functions go into the format part of the template, after the : and before the first | or the closing } if no prefix/suffix is used. If you have both a format and a function reference, the function comes after a second :. Functions return the value of the column specified in the template, suitably modified.

The syntax for using functions is one of:

{lookup_name:function(arguments)}
{lookup_name:format:function(arguments)}
{lookup_name:function(arguments)|prefix|suffix}
{lookup_name:format:function(arguments)|prefix|suffix}

Function names must always be followed by opening and closing parentheses. Some functions require extra values (arguments), and these go inside the parentheses. Arguments are separated by commas. Literal commas (commas as text, not argument separators) must be preceded by a backslash (\) . The last (or only) argument cannot contain a textual closing parenthesis.

Functions are evaluated before format specifications and the prefix/suffix. See further down for an example of using both a format and a function.

Important: If you have programming experience, please note that the syntax in Single Function Mode is not what you expect. Strings are not quoted and spaces are significant. All arguments are considered to be constants; there are no expressions.

Do not use subtemplates (`{ … }`) as function arguments. Instead, use Template Program Mode and General Program Mode.

Some functions require regular expressions. In the template language regular expression matching is case-insensitive.

In the function documentation below, the notation [something]* means that something can be repeated zero or more times. The notation [something]+ means that the something is repeated one or more times (must exist at least one time).

The functions intended for use in Single Function Mode are:

capitalize() – returns the value with the first letter upper case and the rest lower case.

contains(pattern, text if match, text if not match) – checks if the value is matched by the regular expression pattern. Returns text if match if the pattern matches the value, otherwise returns text if no match.

count(separator) – interprets the value as a list of items separated by separator and returns the number of items in the list. Most lists use a comma as the separator, but authors uses an ampersand (&). Examples: {tags:count(,)}, {authors:count(&)}. Aliases: count(), list_count()

format_number(template) – interprets the value as a number and formats that number using a Python formatting template such as {0:5.2f} or {0:,d} or ${0:5,.2f}. The formatting template must begin with {0: and end with } as in the above examples. Exception: you can leave off the leading „{0:“ and trailing „}“ if the format template contains only a format. See the template language and the Python documentation [https://docs.python.org/3/library/string.html#formatstrings] for more examples. Returns the empty string if formatting fails.

human_readable() – expects the value to be a number and returns a string representing that number in KB, MB, GB, etc.

ifempty(text if empty) – if the value is not empty then return the value of the field, otherwise return text if empty.

in_list(separator, [ pattern, found_val, ]* not_found_val) – interpret the value as a list of items separated by separator, checking the pattern against each item in the list. If the pattern matches an item then return found_val, otherwise return not_found_val. The pair pattern and found_value can be repeated as many times as desired, permitting returning different values depending on the item’s value. The patterns are checked in order, and the first match is returned.

language_strings(localize) – return the language names [https://www.loc.gov/standards/iso639-2/php/code_list.php] for the language codes [https://www.loc.gov/standards/iso639-2/php/code_list.php] passed in as the value. Example: {languages:language_strings()}. If localize is zero, return the strings in English. If localize is not zero, return the strings in the language of the current locale. Lang_codes is a comma-separated list.

list_item(index, separator) – interpret the value as a list of items separated by separator, returning the ‚index’th item. The first item is number zero. The last item has the index -1 as in list_item(-1,separator). If the item is not in the list, then the empty string is returned.

lookup([ pattern, key, ]* else_key) – The patterns will be checked against the value in order. If a pattern matches then the value of the field named by key is returned. If no pattern matches then the value of the field named by else_key is returned. See``switch`` (below).

lowercase() – returns the value of the field in lower case.

rating_to_stars(use_half_stars) – Returns the rating as string of star (★) characters. The value must be a number between 0 and 5. Set use_half_stars to 1 if you want half star characters for fractional numbers available with custom ratings columns.

re(pattern, replacement) – return the value after applying the regular expression. All instances of pattern in the value are replaced with replacement. The template language uses case insensitive Python regular expressions [https://docs.python.org/3/library/re.html].

select(key) – interpret the value as a comma-separated list of items with each item having the form id:value (the calibre identifier format). The function finds the first pair with the id equal to key and returns the corresponding value. If no id matches then the function returns the empty string.

shorten(left chars, middle text, right chars) – Return a shortened version of the value, consisting of left chars characters from the beginning of the value, followed by middle text, followed by right chars characters from the end of the value. Left chars and right chars must be non-negative integers. Example: assume you want to display the title with a length of at most 15 characters in length. One template that does this is {title:shorten(9,-,5)}. For a book with the title Ancient English Laws in the Times of Ivanhoe the result will be Ancient E-anhoe: the first 9 characters of the title, a -, then the last 5 characters. If the value’s length is less than left chars + right chars + the length of middle text then the value will be returned unchanged. For example, the title The Dome would not be changed.

str_in_list(separator, [ string, found_val, ]+ not_found_val) – interpret the value as a list of items separated by separator then compare string against each value in the list. The string is not a regular expression. If string is equal to any item (ignoring case) then return the corresponding found_val. If string contains separators then it is also treated as a list and each subvalue is checked. The string and found_value pairs can be repeated as many times as desired, permitting returning different values depending on string’s value. If none of the strings match then not_found_value is returned. The strings are checked in order. The first match is returned.

subitems(start_index, end_index) – This function breaks apart lists of tag-like hierarchical items such as genres. It interprets the value as a comma-separated list of tag-like items, where each item is a period-separated list. It returns a new list made by extracting from each item the components from start_index to end_index, then merging the results back together. Duplicates are removed. The first subitem in a period-separated list has an index of zero. If an index is negative then it counts from the end of the list. As a special case, an end_index of zero is assumed to be the length of the list.

Příklady:

- Assuming a #genre column containing A.B.C:

- {#genre:subitems(0,1)} returns „A“

- {#genre:subitems(0,2)} returns „A.B“

- {#genre:subitems(1,0)} returns „B.C“

- Assuming a #genre column containing „A.B.C, D.E“:

- {#genre:subitems(0,1)} returns „A, D“

- {#genre:subitems(0,2)} returns „A.B, D.E“

sublist(start_index, end_index, separator) – interpret the value as a list of items separated by separator, returning a new list made from the items from start_index to end_index. The first item is number zero. If an index is negative, then it counts from the end of the list. As a special case, an end_index of zero is assumed to be the length of the list.

Examples assuming that the tags column (which is comma-separated) contains „A, B ,C“:

- {tags:sublist(0,1,\,)} returns „A“

- {tags:sublist(-1,0,\,)} returns „C“

- {tags:sublist(0,-1,\,)} returns „A, B“

swap_around_articles(separator) – returns the value with articles moved to the end. The value can be a list, in which case each item in the list is processed. If the value is a list then you must provide the separator. If no separator is provided then the value is treated as being a single value, not a list. The articles are those used by calibre to generate the title_sort.

swap_around_comma() – given a value of the form B, A, return A B. This is most useful for converting names in LN, FN format to FN LN. If there is no comma in the value then the function returns the value unchanged.

switch([pattern, value,]+ else_value) – for each pattern, value pair, checks if the value matches the regular expression pattern and if so returns the associated value. If no pattern matches, then else_value is returned. You can have as many pattern, value pairs as you wish. The first match is returned.

test(text if not empty, text if empty) – return text if not empty if the value is not empty, otherwise return text if empty.

titlecase() – returns the value of the field in title case.

transliterate() – Return a string in a latin alphabet formed by approximating the sound of the words in the source field. For example, if the source field is Фёдор Миха́йлович Достоевский this function returns Fiodor Mikhailovich Dostoievskii.

uppercase() – returns the value of the field in upper case.

Using functions and formatting in the same template

Suppose you have an integer custom column #myint that you want displayed with leading zeros, as in 003. One way to do this is to use a format of 0>3s. However, by default if a number (integer or float) equals zero then the value is displayed as the empty string so zero values will produce the empty string, not 000. If you want to see 000 values then you use both the format string and the ifempty function to change the empty value back to a zero. The template would be:

{#myint:0>3s:ifempty(0)}

Note that you can use the prefix and suffix as well. If you want the number to appear as [003] or [000], then use the template:

{#myint:0>3s:ifempty(0)|[|]}

General Program Mode

General Program Mode (GPM) replaces template expressions with a program written in the template language. The syntax of the language is defined by the following grammar:

program ::= 'program:' expression_list expression_list ::= top_expression [ ';' top_expression ]* top_expression ::= or_expression or_expression ::= and_expression [ '||' and_expression ]* and_expression ::= not_expression [ '&&' not_expression ]* not_expression ::= [ '!' not_expression ]* | concatenate_expr concatenate_expr::= compare_expr [ '&' compare_expr ]* compare_expr ::= add_sub_expr [ compare_op add_sub_expr ]
compare_op ::= '==' | '!=' | '>=' | '>' | '<=' | '<' | 'in' | 'inlist' |
'==#' | '!=#' | '>=#' | '>#' | '<=#' | '<#'
add_sub_expr ::= times_div_expr [ add_sub_op times_div_expr ]* add_sub_op ::= '+' | '-'
times_div_expr ::= unary_op_expr [ times_div_op unary_op_expr ]* times_div_op ::= '*' | '/'
unary_op_expr ::= [ add_sub_op unary_op_expr ]* | expression expression ::= identifier | constant | function | assignment | field_reference | if_expr | for_expr | break_expr | continue_expr |
'(' expression_list ')' | function_def field_reference ::= '$' [ '$' ] [ '#' ] identifier identifier ::= id_start [ id_rest ]* id_start ::= letter | underscore id_rest ::= id_start | digit constant ::= " string " | ' string ' | number function ::= identifier '(' expression_list [ ',' expression_list ]* ')'
function_def ::= 'def' identifier '(' top_expression [ ',' top_expression ]* ')' ':'
expression_list 'fed'
assignment ::= identifier '=' top_expression if_expr ::= 'if' condition 'then' expression_list
[ elif_expr ] [ 'else' expression_list ] 'fi'
condition ::= top_expression elif_expr ::= 'elif' condition 'then' expression_list elif_expr | ''
for_expr ::= for_list | for_range for_list ::= 'for' identifier 'in' list_expr
[ 'separator' separator_expr ] ':' expression_list 'rof'
for_range ::= 'for' identifier 'in' range_expr ':' expression_list 'rof'
range_expr ::= 'range' '(' [ start_expr ',' ] stop_expr
[ ',' step_expr [ ',' limit_expr ] ] ')'
list_expr ::= top_expression break_expr ::= 'break'
continue_expr ::= 'continue'
separator_expr ::= top_expression start_expr ::= top_expression stop_expr ::= top_expression step_expr ::= top_expression limit_expr ::= top_expression

Notes:

- a top_expression always has a value. The value of an expression_list is the value of the last top_expression in the list. For example, the value of the expression list 1;2;'foobar';3 is 3.

- In a logical context, any non-empty value is True

- In a logical context, the empty value is False

- Strings and numbers can be used interchangeably. For example, 10 and '10' are the same thing.

- Comments are lines starting with a ‚#‘ character. Comments beginning later in a line are not supported.

Operator precedence

The operator precedence (order of evaluation) from highest (evaluated first) to lowest (evaluated last) is:

- Function calls, constants, parenthesized expressions, statement expressions, assignment expressions, field references.

- Unary plus (+) and minus (-). These operators evaluate right to left.

These and all the other arithmetic operators return integers if the expression results in a fractional part equal to zero. For example, if an expression returns 3.0 it is changed to 3.

- Multiply (*) and divide (/). These operators are associative and evaluate left to right. Use parentheses if you want to change the order of evaluation.

- Add (+) and subtract (-). These operators are associative and evaluate left to right.

- Numeric and string comparisons. These operators return '1' if the comparison succeeds, otherwise the empty string (''). Comparisons are not associative: a < b < c is a syntax error.

- String concatenation (&). The & operator returns a string formed by concatenating the left-hand and right-hand expressions. Example: 'aaa' & 'bbb' returns 'aaabbb'. The operator is associative and evaluates left to right.

- Unary logical not (!). This operator returns '1' if the expression is False (evaluates to the empty string), otherwise ''.

- Logical and (&&). This operator returns ‚1‘ if both the left-hand and right-hand expressions are True, or the empty string '' if either is False. It is associative, evaluates left to right, and does short-circuiting [https://chortle.ccsu.edu/java5/Notes/chap40/ch40_2.html].

- Logical or (||). This operator returns '1' if either the left-hand or right-hand expression is True, or '' if both are False. It is associative, evaluates left to right, and does short-circuiting [https://chortle.ccsu.edu/java5/Notes/chap40/ch40_2.html]. It is an inclusive or, returning '1' if both the left- and right-hand expressions are True.

Field references

A field_reference evaluates to the value of the metadata field named by lookup name that follows the $ or $$. Using $ is equivalent to using the field() function. Using $$ is equivalent to using the raw_field function. Examples:

* $authors ==> field('authors')
* $#genre ==> field('#genre')
* $$pubdate ==> raw_field('pubdate')
* $$#my_int ==> raw_field('#my_int')

If expressions

If expressions first evaluate the condition. If the condition is True (a non-empty value) then the expression_list in the then clause is evaluated. If it is False then if present the expression_list in the elif or else clause is evaluated. The elif and else parts are optional. The words if, then, elif, else, and fi are reserved; you cannot use them as identifier names. You can put newlines and white space wherever they make sense. The condition is a top_expression not an expression_list; semicolons are not allowed. The expression_lists are semicolon-separated sequences of top_expressions. An if expression returns the result of the last top_expression in the evaluated expression_list, or the empty string if no expression list was evaluated.

Examples:

* program: if field('series') then 'yes' else 'no' fi
* program:
if field('series') then
a = 'yes';
b = 'no'
else
a = 'no';
b = 'yes'
fi;
strcat(a, '-', b)

Nested if example:

program:
if field('series') then
if check_yes_no(field('#mybool'), '', '', '1') then
'yes'
else
'no'
fi
else
'no series'
fi

As said above, an if produces a value. This means that all the following are equivalent:

* program: if field('series') then 'foo' else 'bar' fi
* program: if field('series') then a = 'foo' else a = 'bar' fi; a
* program: a = if field('series') then 'foo' else 'bar' fi; a

As a last example, this program returns the value of the series column if the book has a series, otherwise the value of the title column:

program: field(if field('series') then 'series' else 'title' fi)

For expressions

The for expression iterates over a list of values, processing them one at a time. The list_expression must evaluate either to a metadata field lookup name e.g., tags or #genre, or to a list of values. The range() function (see below) generates a list of numbers. If the result is a valid lookup name then the field’s value is fetched and the separator specified for that field type is used. If the result isn’t a valid lookup name then it is assumed to be a list of values. The list is assumed to be separated by commas unless the optional keyword separator is supplied, in which case the list values must be separated by the result of evaluating the separator_expr. A separator cannot be used if the list is generated by range(). Each value in the list is assigned to the specified variable then the expression_list is evaluated. You can use break to jump out of the loop, and continue to jump to the beginning of the loop for the next iteration.

Example: This template removes the first hierarchical name for each value in Genre (#genre), constructing a list with the new names:

program:
new_tags = '';
for i in '#genre':
j = re(i, '^.*?\.(.*)$', '\1');
new_tags = list_union(new_tags, j, ',')
rof;
new_tags

If the original Genre is History.Military, Science Fiction.Alternate History, ReadMe then the template returns Military, Alternate History, ReadMe. You could use this template in calibre’s Edit metadata in bulk  →  Search & replace with Search for set to template to strip off the first level of the hierarchy and assign the resulting value to Genre.

Note: the last line in the template, new_tags, isn’t strictly necessary in this case because for returns the value of the last top_expression in the expression list. The value of an assignment is the value of its expression, so the value of the for statement is what was assigned to new_tags.

Function definition

If you have code in a template that repeats then you can put that code into a local function. The def keyword starts the definition. It is followed by the function name, the argument list, then the code in the function. The function definition ends with the fed keyword.

Arguments are positional. When a function is called the supplied arguments are matched left to right against the defined parameters, with the value of the argument assigned to the parameter. It is an error to provide more arguments than defined parameters. Parameters can have default values, such as a = 25. If an argument is not supplied for that parameter then the default value is used, otherwise the parameter is set to the empty string.

The return statement can be used in a local function.

A function must be defined before it can be used.

Example: This template computes an approximate duration in years, months, and days from a number of days. The function to_plural() formats the computed values. Note that the example also uses the & operator:

program:
days = 2112;
years = floor(days/360);
months = floor(mod(days, 360)/30);
days = days - ((years*360) + (months * 30));

def to_plural(v, str):
if v == 0 then return '' fi;
return v & ' ' & (if v == 1 then str else str & 's' fi) & ' '
fed;

to_plural(years, 'year') & to_plural(months, 'month') & to_plural(days,'day')

Relational operators

Relational operators return '1' if the comparison is true, otherwise the empty string (‚‘).

There are two forms of relational operators: string comparisons and numeric comparisons.

String comparisons do case-insensitive string comparison using lexical order. The supported string comparison operators are ==, !=, <, <=, >, >=, in, and inlist.
For the in operator, the result of the left hand expression is interpreted as a regular expression pattern. The in operator is True if the value of left-hand regular expression matches the value of the right hand expression. The inlist operator is true if the left hand regular expression matches any one of the items in the right hand list where the items in the list are separated by commas. The matches are case-insensitive.

The numeric comparison operators are ==#, !=#, <#, <=#, >#, >=#. The left and right expressions must evaluate to numeric values with two exceptions: both the string value „None“ (undefined field) and the empty string evaluate to the value zero.

Příklady:

- program: field('series') == 'foo' returns '1' if the book’s series is ‚foo‘, otherwise ''.

- program: 'f.o' in field('series') returns '1' if the book’s series matches the regular expression f.o (e.g., foo, Off Onyx, etc.), otherwise ''.

- program: 'science' inlist field('#genre') returns '1' if any of the book’s genres match the regular expression science, e.g., Science, History of Science, Science Fiction etc., otherwise ''.

- program: '^science$' inlist field('#genre') returns '1' if any of the book’s genres exactly match the regular expression ^science$, e.g., Science. The genres History of Science and Science Fiction don’t match. If there isn’t a match then returns ''.

- program: if field('series') != 'foo' then 'bar' else 'mumble' fi returns 'bar' if the book’s series is not foo. Otherwise it returns 'mumble'.

- program: if field('series') == 'foo' || field('series') == '1632' then 'yes' else 'no' fi returns 'yes' if series is either 'foo' or '1632', otherwise 'no'.

- program: if '^(foo|1632)$' in field('series') then 'yes' else 'no' fi returns 'yes' if series is either 'foo' or '1632', otherwise 'no'.

- program: if 11 > 2 then 'yes' else 'no' fi returns 'no' because the > operator does a lexical comparison.

- program: if 11 ># 2 then 'yes' else 'no' fi returns 'yes' because the ># operator does a numeric comparison.

Additional available functions

The following functions are available in addition to those described in Single Function Mode.

In GPM the functions described in Single Function Mode all require an additional first parameter specifying the value to operate upon. All parameters are expression_lists (see the grammar above).

add(x [, y]*) – returns the sum of its arguments. Throws an exception if an argument is not a number. In most cases you can use the + operator instead of this function.

and(value [, value]*) – returns the string „1“ if all values are not empty, otherwise returns the empty string. You can have as many values as you want. In most cases you can use the && operator instead of this function. One reason not to replace and with && is if short-circuiting can change the results because of side effects. For example, and(a='',b=5) will always do both assignments, where the && operator won’t do the second.

assign(id, val) – assigns val to id, then returns val. id must be an identifier, not an expression. In most cases you can use the = operator instead of this function.

approximate_formats() – return a comma-separated list of formats associated with the book. There is no guarantee that the list is correct, although it probably is. This and other zero-parameter functions can be called in Template Program Mode (see below) using the template {:'approximate_formats()'}. Note that resulting format names are always uppercase, as in EPUB. The approximate_formats() function is significantly faster than the formats_... functions discussed below.

author_links(val_separator, pair_separator) – returns a string containing a list of authors and those authors‘ link values in the form:

author1 val_separator author1_link pair_separator author2 val_separator author2_link etc.

An author is separated from its link value by the val_separator string with no added spaces. author:linkvalue pairs are separated by the pair_separator string argument with no added spaces. It is up to you to choose separator strings that do not occur in author names or links. An author is included even if the author link is empty.

author_sorts(val_separator) – returns a string containing a list of author’s sort values for the authors of the book. The sort is the one in the author metadata information (different from the author_sort in books). The returned list has the form author sort 1 val_separator author sort 2 etc. with no added spaces. The author sort values in this list are in the same order as the authors of the book. If you want spaces around val_separator then include them in the val_separator string.

book_count(query, use_vl) – returns the count of books found by searching for query. If use_vl is 0 (zero) then virtual libraries are ignored. This function and its companion book_values() are particularly useful in template searches, supporting searches that combine information from many books such as looking for series with only one book. It cannot be used in composite columns unless the tweak allow_template_database_functions_in_composites is set to True. It can be used only in the GUI.

For example this template search uses this function and its companion to find all series with only one book:

1. Define a stored template (using Preferences → Advanced → Template functions) named series_only_one_book (the name is arbitrary). The template is:

program:
vals = globals(vals='');
if !vals then
all_series = book_values('series', 'series:true', ',', 0);
for series in all_series:
if book_count('series:="' & series & '"', 0) == 1 then
vals = list_join(',', vals, ',', series, ',')
fi
rof;
set_globals(vals)
fi;
str_in_list(vals, ',', $series, 1, '')

The first time the template runs (the first book checked) it stores the results of the database lookups in a global template variable named vals. These results are used to check subsequent books without redoing the lookups.

2. Use the stored template in a template search:

template:"program: series_only_one_book()#@#:n:1"

Using a stored template instead of putting the template into the search eliminates problems caused by the requirement to escape quotes in search expressions.

book_values(column, query, sep, use_vl) – returns a list of the unique values contained in the column column (a lookup name), separated by sep, in the books found by searching for query. If use_vl is 0 (zero) then virtual libraries are ignored. This function and its companion book_count() are particularly useful in template searches, supporting searches that combine information from many books such as looking for series with only one book. It cannot be used in composite columns unless the tweak allow_template_database_functions_in_composites is set to True. It can be used only in the GUI.

booksize() – returns the value of the calibre ‚size‘ field. Returns ‚‘ if there are no formats.

check_yes_no(field_name, is_undefined, is_false, is_true) – checks if the value of the yes/no field named by the lookup name field_name is one of the values specified by the parameters, returning 'yes' if a match is found otherwise returning the empty string. Set the parameter is_undefined, is_false, or is_true to 1 (the number) to check that condition, otherwise set it to 0. Example:

check_yes_no("#bool", 1, 0, 1) returns 'Yes' if the yes/no field #bool is either True or undefined (neither True nor False).

More than one of is_undefined, is_false, or is_true can be set to 1.

ceiling(x) – returns the smallest integer greater than or equal to x. Throws an exception if x is not a number.

character(character_name) – returns the character named by character_name. For example, character('newline') returns a newline character ('\n'). The supported character names are newline, return, tab, and backslash.

cmp(x, y, lt, eq, gt) – compares x and y after converting both to numbers. Returns lt if x <# y, eq if x ==# y, otherwise gt. This function can usually be replaced with one of the numeric compare operators (==#, <#, >#, etc).

connected_device_name(storage_location_key) – if a device is connected then return the device name, otherwise return the empty string. Each storage location on a device has its own device name. The storage_location_key names are 'main', 'carda' and 'cardb'. This function works only in the GUI.

connected_device_uuid(storage_location_key) – if a device is connected then return the device uuid (unique id), otherwise return the empty string. Each storage location on a device has a different uuid. The storage_location_key location names are 'main', 'carda' and 'cardb'. This function works only in the GUI.

current_library_name() – return the last name on the path to the current calibre library.

current_library_path() – return the full path to the current calibre library.

current_virtual_library_name() – return the name of the current virtual library if there is one, otherwise the empty string. Library name case is preserved. Example: program: current_virtual_library_name(). This function works only in the GUI.

date_arithmetic(date, calc_spec, fmt) – Calculate a new date from date using calc_spec. Return the new date formatted according to optional fmt: if not supplied then the result will be in ISO format. The calc_spec is a string formed by concatenating pairs of vW (valueWhat) where v is a possibly-negative number and W is one of the following letters:

- s: add v seconds to date

- m: add v minutes to date

- h: add v hours to date

- d: add v days to date

- w: add v weeks to date

- y: add v years to date, where a year is 365 days.

Example: '1s3d-1m' will add 1 second, add 3 days, and subtract 1 minute from date.

days_between(date1, date2) – return the number of days between date1 and date2. The number is positive if date1 is greater than date2, otherwise negative. If either date1 or date2 are not dates, the function returns the empty string.

divide(x, y) – returns x / y. Throws an exception if either x or y are not numbers. This function can usually be replaced by the / operator.

eval(string) – evaluates the string as a program, passing the local variables. This permits using the template processor to construct complex results from local variables. In Template Program Mode, because the { and } characters are interpreted before the template is evaluated you must use [[ for the { character and ]] for the } character. They are converted automatically. Note also that prefixes and suffixes (the |prefix|suffix syntax) cannot be used in the argument to this function when using Template Program Mode.

extra_file_size(file_name) – returns the size in bytes of the extra file file_name in the book’s data/ folder if it exists, otherwise -1. See also the functions has_extra_files(), extra_file_names() and extra_file_modtime(). This function can be used only in the GUI.

extra_file_modtime(file_name, format_string) – returns the modification time of the extra file file_name in the book’s data/ folder if it exists, otherwise -1. The modtime is formatted according to format_string (see format_date() for details). If format_string is the empty string, returns the modtime as the floating point number of seconds since the epoch. See also the functions has_extra_files(), extra_file_names() and extra_file_size(). The epoch is OS dependent. This function can be used only in the GUI.

extra_file_names(sep [, pattern]) returns a sep-separated list of extra files in the book’s data/ folder. If the optional parameter pattern, a regular expression, is supplied then the list is filtered to files that match pattern. The pattern match is case insensitive. See also the functions has_extra_files(), extra_file_modtime() and extra_file_size(). This function can be used only in the GUI.

field(lookup_name) – returns the value of the metadata field with lookup name lookup_name.

field_exists(field_name) – checks if a field (column) with the lookup name field_name exists, returning '1' if so and the empty string if not.

finish_formatting(val, fmt, prefix, suffix) – apply the format, prefix, and suffix to a value in the same way as done in a template like {series_index:05.2f| - |- }. This function is provided to ease conversion of complex single-function- or template-program-mode templates to GPM Templates. For example, the following program produces the same output as the above template:

program: finish_formatting(field("series_index"), "05.2f", " - ", " - ")

Another example: for the template {series:re(([^\s])[^\s]+(\s|$),\1)}{series_index:0>2s| - | - }{title} use:

program:
strcat(
re(field('series'), '([^\s])[^\s]+(\s|$)', '\1'),
finish_formatting(field('series_index'), '0>2s', ' - ', ' - '),
field('title')
)

first_matching_cmp(val, [ cmp, result, ]* else_result) – compares val < cmp in sequence, returning the associated result for the first comparison that succeeds. Returns else_result if no comparison succeeds. Example:

i = 10;
first_matching_cmp(i,5,"small",10,"middle",15,"large","giant")

returns "large". The same example with a first value of 16 returns "giant".

first_non_empty(value [, value]*) – returns the first value that is not empty. If all values are empty, then the empty string is returned. You can have as many values as you want.

floor(x) – returns the largest integer less than or equal to x. Throws an exception if x is not a number.

format_date(val, format_string) – format the value, which must be a date string, using the format_string, returning a string. The formatting codes are:

- d    : the day as number without a leading zero (1 to 31)

- dd   : the day as number with a leading zero (01 to 31)

- ddd  : the abbreviated localized day name (e.g. „Mon“ to „Sun“).

- dddd : the long localized day name (e.g. „Monday“ to „Sunday“).

- M    : the month as number without a leading zero (1 to 12).

- MM   : the month as number with a leading zero (01 to 12)

- MMM  : the abbreviated localized month name (e.g. „Jan“ to „Dec“).

- MMMM : the long localized month name (e.g. „January“ to „December“).

- yy   : the year as two digit number (00 to 99).

- yyyy : the year as four digit number.

- h    : the hours without a leading 0 (0 to 11 or 0 to 23, depending on am/pm)

- hh   : the hours with a leading 0 (00 to 11 or 00 to 23, depending on am/pm)

- m    : the minutes without a leading 0 (0 to 59)

- mm   : the minutes with a leading 0 (00 to 59)

- s    : the seconds without a leading 0 (0 to 59)

- ss   : the seconds with a leading 0 (00 to 59)

- ap   : use a 12-hour clock instead of a 24-hour clock, with ‚ap‘ replaced by the localized string for am or pm.

- AP   : use a 12-hour clock instead of a 24-hour clock, with ‚AP‘ replaced by the localized string for AM or PM.

- iso  : the date with time and timezone. Must be the only format present.

- to_number   : convert the date & time into a floating point number (a timestamp)

- from_number : convert a floating point number (a timestamp) into an iso formatted date. If you want a different date format then add the desired formatting string after from_number and a colon (:). Example: from_number:MMM dd yyyy

You might get unexpected results if the date you are formatting contains localized month names, which can happen if you changed the date format tweaks to contain MMMM. In this case, instead of using the field() function as in:

format_date(field('pubdate'), 'yyyy')

use the raw_field() function as in:

format_date(raw_field('pubdate'), 'yyyy')

format_date_field(field_name, format_string) – format the value in the field field_name, which must be the lookup name of date field, either standard or custom. See format_date() for the formatting codes. This function is much faster than format_date and should be used when you are formatting the value in a field (column). It can’t be used for computed dates or dates in string variables. Examples:

format_date_field('pubdate', 'yyyy.MM.dd')
format_date_field('#date_read', 'MMM dd, yyyy')

formats_modtimes(date_format_string) – return a comma-separated list of colon-separated items FMT:DATE representing modification times for the formats of a book. The date_format_string parameter specifies how the date is to be formatted. See the format_date() function for details. You can use the select function to get the modification time for a specific format. Note that format names are always uppercase, as in EPUB.

formats_paths() – return a comma-separated list of colon-separated items FMT:PATH giving the full path to the formats of a book. You can use the select function to get the path for a specific format. Note that format names are always uppercase, as in EPUB.

formats_sizes() – return a comma-separated list of colon-separated FMT:SIZE items giving the sizes in bytes of the formats of a book. You can use the select function to get the size for a specific format. Note that format names are always uppercase, as in EPUB.

fractional_part(x) – returns the value after the decimal point. For example, fractional_part(3.14) returns 0.14. Throws an exception if x is not a number.

get_link(field_name, field_value) – fetch the link for field field_name with value field_value. If there is no attached link, return the empty string. Examples:

- The following returns the link attached to the tag Fiction:

get_link('tags', 'Fiction')

- This template makes a list of the links for all the tags associated with a book in the form value:link, ...:

program:
ans = '';
for t in $tags:
l = get_link('tags', t);
if l then
ans = list_join(', ', ans, ',', t & ':' & get_link('tags', t), ',')
fi
rof;
ans

has_cover() – return 'Yes' if the book has a cover, otherwise the empty string.

has_extra_files([pattern]) – returns the count of extra files, otherwise ‚‘ (the empty string). If the optional parameter pattern (a regular expression) is supplied then the list is filtered to files that match pattern before the files are counted. The pattern match is case insensitive. See also the functions extra_file_names(), extra_file_size() and extra_file_modtime(). This function can be used only in the GUI.

identifier_in_list(val, id_name [, found_val, not_found_val]) – treat val as a list of identifiers separated by commas. An identifier has the format id_name:value. The id_name parameter is the id_name text to search for, either id_name or id_name:regexp. The first case matches if there is any identifier matching that id_name. The second case matches if id_name matches an identifier and the regexp matches the identifier’s value. If found_val and not_found_val are provided then if there is a match then return found_val, otherwise return not_found_val. If found_val and not_found_val are not provided then if there is a match then return the identifier:value pair, otherwise the empty string ('').

is_marked() – check whether the book is marked in calibre. If it is then return the value of the mark, either 'true' (lower case) or a comma-separated list of named marks. Returns '' (the empty string) if the book is not marked. This function works only in the GUI.

language_codes(lang_strings) – return the language codes [https://www.loc.gov/standards/iso639-2/php/code_list.php] for the language names passed in lang_strings. The strings must be in the language of the current locale. Lang_strings is a comma-separated list.

list_contains(value, separator, [ pattern, found_val, ]* not_found_val) – (Alias of in_list) Interpreting the value as a list of items separated by separator, evaluate the pattern against each value in the list. If the pattern matches any value then return found_val, otherwise return not_found_val. The pattern and found_value can be repeated as many times as desired, permitting returning different values depending on the search. The patterns are checked in order. The first match is returned. Aliases: in_list(), list_contains()

list_count(value, separator) – interprets value as a list of items separated by separator, returning the count of items in the list. Aliases: count(), list_count()

list_count_matching(list, pattern, separator) – interprets list as a list of items separated by separator, returning the number of items in the list that match the regular expression pattern. Aliases: list_count_matching(), count_matching()

list_difference(list1, list2, separator) – return a list made by removing from list1 any item found in list2 using a case-insensitive comparison. The items in list1 and list2 are separated by separator, as are the items in the returned list.

list_equals(list1, sep1, list2, sep2, yes_val, no_val) – return yes_val if list1 and list2 contain the same items, otherwise return no_val. The items are determined by splitting each list using the appropriate separator character (sep1 or sep2). The order of items in the lists is not relevant. The comparison is case-insensitive.

list_intersection(list1, list2, separator) – return a list made by removing from list1 any item not found in list2, using a case-insensitive comparison. The items in list1 and list2 are separated by separator, as are the items in the returned list.

list_join(with_separator, list1, separator1 [, list2, separator2]*) – return a list made by joining the items in the source lists (list1 etc) using with_separator between the items in the result list. Items in each source list[123...] are separated by the associated separator[123...]. A list can contain zero values. It can be a field like publisher that is single-valued, effectively a one-item list. Duplicates are removed using a case-insensitive comparison. Items are returned in the order they appear in the source lists. If items on lists differ only in letter case then the last is used. All separators can be more than one character.

Příklad:

program:
list_join('#@#', $authors, '&', $tags, ',')

You can use list_join on the results of previous calls to list_join as follows:

program:
a = list_join('#@#', $authors, '&', $tags, ',');
b = list_join('#@#', a, '#@#', $#genre, ',', $#people, '&', 'some value', ',')

You can use expressions to generate a list. For example, assume you want items for authors and #genre, but with the genre changed to the word „Genre: „ followed by the first letter of the genre, i.e. the genre „Fiction“ becomes „Genre: F“. The following will do that:

program:
list_join('#@#', $authors, '&', list_re($#genre, ',', '^(.).*$', 'Genre: \1'), ',')

list_re(src_list, separator, include_re, opt_replace) – Construct a list by first separating src_list into items using the separator character. For each item in the list, check if it matches include_re. If it does then add it to the list to be returned. If opt_replace is not the empty string then apply the replacement before adding the item to the returned list.

list_re_group(src_list, separator, include_re, search_re [, template_for_group]*) – Like list_re except replacements are not optional. It uses re_group(item, search_re, template ...) when doing the replacements.

list_remove_duplicates(list, separator) – return a list made by removing duplicate items in list. If items differ only in case then the last is returned. The items in list are separated by separator, as are the items in the returned list.

list_sort(list, direction, separator) – return list sorted using a case-insensitive lexical sort. If direction is zero, list is sorted ascending, otherwise descending. The list items are separated by separator, as are the items in the returned list.

list_split(list_val, sep, id_prefix) – splits list_val into separate values using sep, then assigns the values to local variables named id_prefix_N where N is the position of the value in the list. The first item has position 0 (zero). The function returns the last element in the list.

Příklad:

list_split('one:two:foo', ':', 'var')

is equivalent to:

var_0 = 'one';
var_1 = 'two';
var_2 = 'foo

list_union(list1, list2, separator) – return a list made by merging the items in list1 and list2, removing duplicate items using a case-insensitive comparison. If items differ in case, the one in list1 is used. The items in list1 and list2 are separated by separator, as are the items in the returned list. Aliases: merge_lists(), list_union()

mod(x, y) – returns the floor of the remainder of x / y. Throws an exception if either x or y is not a number.

multiply(x [, y]*) – returns the product of its arguments. Throws an exception if any argument is not a number. This function can usually be replaced by the * operator.

not(value) – returns the string „1“ if the value is empty, otherwise returns the empty string. This function can usually be replaced with the unary not (!) operator.

ondevice() – return the string 'Yes' if ondevice is set, otherwise return the empty string.

or(value [, value]*) – returns the string '1' if any value is not empty, otherwise returns the empty string. You can have as many values as you want. This function can usually be replaced by the || operator. A reason it cannot be replaced is if short-circuiting will change the results because of side effects.

print(a [, b]*) – prints the arguments to standard output. Unless you start calibre from the command line (calibre-debug -g), the output will go into a black hole. The print function always returns its first argument.

range(start, stop, step, limit) – returns a list of numbers generated by looping over the range specified by the parameters start, stop, and step, with a maximum length of limit. The first value produced is ‚start‘. Subsequent values next_v = current_v + step. The loop continues while next_v < stop assuming step is positive, otherwise while next_v > stop. An empty list is produced if start fails the test: start >= stop if step is positive. The limit sets the maximum length of the list and has a default of 1000. The parameters start, step, and limit are optional. Calling range() with one argument specifies stop. Two arguments specify start and stop. Three arguments specify start, stop, and step. Four arguments specify start, stop, step and limit. Examples:

range(5) -> '0, 1, 2, 3, 4'
range(0, 5) -> '0, 1, 2, 3, 4'
range(-1, 5) -> '-1, 0, 1, 2, 3, 4'
range(1, 5) -> '1, 2, 3, 4'
range(1, 5, 2) -> '1, 3'
range(1, 5, 2, 5) -> '1, 3'
range(1, 5, 2, 1) -> error(limit exceeded)

raw_field(lookup_name [, optional_default]) – returns the metadata field named by lookup_name without applying any formatting. It evaluates and returns the optional second argument optional_default if the field’s value is undefined (None).

raw_list(lookup_name, separator) – returns the metadata list named by lookup_name without applying any formatting or sorting, with the items separated by separator.

re_group(value, pattern [, template_for_group]*) – return a string made by applying the regular expression pattern to value and replacing each matched instance with the value returned by the corresponding template. In Template Program Mode, like for the template and the eval functions, you use [[ for { and ]] for }.

The following example looks for a series with more than one word and uppercases the first word:

program: re_group(field('series'), "(\S* )(.*)", "{$:uppercase()}", "{$}")'}

round(x) – returns the nearest integer to x. Throws an exception if x is not a number.

series_sort() – returns the series sort value.

strcat(a [, b]*) – can take any number of arguments. Returns a string formed by concatenating all the arguments.

strcat_max(max, string1 [, prefix2, string2]*) – Returns a string formed by concatenating the arguments. The returned value is initialized to string1. Strings made from prefix, string pairs are added to the end of the value as long as the resulting string length is less than max. Prefixes can be empty. Returns string1 even if string1 is longer than max. You can pass as many prefix, string pairs as you wish.

strcmp(x, y, lt, eq, gt) – does a case-insensitive lexical comparison of x and y. Returns lt if x < y, eq if x == y, otherwise gt. This function can often be replaced by one of the lexical comparison operators (==, >, <, etc.)

strcmpcase(x, y, lt, eq, gt) – does a case-sensitive lexical comparison of x and y. Returns lt if x < y, eq if x == y, otherwise gt.

Note: This is NOT the default behavior used by calibre, for example, in the lexical comparison operators (==, >, <, etc.). This function could cause unexpected results, preferably use strcmp() whenever possible.

strlen(value) – Returns the length of the string value.

substr(str, start, end) – returns the start’th through the end’th characters of str. The first character in str is the zero’th character. If end is negative, then it indicates that many characters counting from the right. If end is zero, then it indicates the last character. For example, substr('12345', 1, 0) returns '2345', and substr('12345', 1, -1) returns '234'.

subtract(x, y) – returns x - y. Throws an exception if either x or y are not numbers. This function can usually be replaced by the - operator.

switch_if([test_expression, value_expression,]+ else_expression) – for each test_expression, value_expression pair, checks if test_expression is True (non-empty) and if so returns the result of value_expression. If no test_expression is True then the result of else_expression` is returned. You can have as many ``test_expression, value_expression pairs as you want.

today() – return a date+time string for today (now). This value is designed for use in format_date or days_between, but can be manipulated like any other string. The date is in ISO [https://en.wikipedia.org/wiki/ISO_8601] date/time format.

template(x) – evaluates x as a template. The evaluation is done in its own context, meaning that variables are not shared between the caller and the template evaluation.

to_hex(val) – returns the string val encoded in hex. This is useful when constructing calibre URLs.

urls_from_identifiers(identifiers, sort_results) – given a comma-separated list of identifiers, where an identifier is a colon-separated pair of values (id_name:id_value), returns a comma-separated list of HTML URLs generated from the identifiers. The list not sorted if sort_results is 0 (character or number), otherwise it is sorted alphabetically by the identifier name. The URLs are generated in the same way as the built-in identifiers column when shown in Book details.

More complex programs in template expressions - Template Program Mode

Template Program Mode (TPM) is a blend of General Program Mode and Single Function Mode. TPM differs from Single Function Mode in that it permits writing template expressions that refer to other metadata fields, use nested functions, modify variables, and do arithmetic. It differs from General Program Mode in that the template is contained between { and } characters and doesn’t begin with the word program:. The program portion of the template is a General Program Mode expression list.

Example: assume you want a template to show the series for a book if it has one, otherwise show the value of a custom field #genre. You cannot do this in the Single Function Mode because you cannot make reference to another metadata field within a template expression. In TPM you can, as the following expression demonstrates:

{#series:'ifempty($, field('#genre'))'}

The example shows several things:

- TPM is used if the expression begins with :' and ends with '}. Anything else is assumed to be in Single Function Mode.

- the variable $ stands for the field named in the template: the expression is operating upon, #series in this case.

- functions must be given all their arguments. There is no default value. For example, the standard built-in functions must be given an additional initial parameter indicating the source field.

- white space is ignored and can be used anywhere within the expression.

- constant strings are enclosed in matching quotes, either ' or ".

All the functions listed under Single Function Mode and General Program Mode can be used in TPM.

In TPM, using { and } characters in string literals can lead to errors or unexpected results because they confuse the template processor. It tries to treat them as template expression boundaries, not characters. In some but not all cases you can replace a { with [[ and a } with ]]. Generally, if your program contains { and } characters then you should use General Program Mode.

As with General Program Mode, for functions documented under Single Function Mode you must supply the value the function is to act upon as the first parameter in addition to the documented parameters. In TPM you can use $ to access the value specified by the lookup name for the template expression.

Python Template Mode

Python Template Mode (PTM) lets you write templates using native python and the calibre API [https://manual.calibre-ebook.com/develop.html#api-documentation-for-various-parts-of-calibre]. The database API will be of most use; further discussion is beyond the scope of this manual. PTM templates are faster and can do more complicated operations but you must know how to write code in python using the calibre API.

A PTM template begins with:

python:
def evaluate(book, context):
# book is a calibre metadata object
# context is an instance of calibre.utils.formatter.PythonTemplateContext,
# which currently contains the following attributes:
# db: a calibre legacy database object.
# globals: the template global variable dictionary.
# arguments: is a list of arguments if the template is called by a GPM template, otherwise None.
# funcs: used to call Built-in/User functions and Stored GPM/Python templates.
# Example: context.funcs.list_re_group()

# your Python code goes here
return 'a string'

You can add the above text to your template using the context menu, usually accessed with a right click. The comments are not significant and can be removed. You must use python indenting.

The context object supports str(context) that returns a string of the context’s contents, and context.attributes that returns a list of the attribute names in the context.

The context.funcs attribute allows calling Built-in and User template functions, and Stored GPM/Python templates, so that you can execute them directly in your code. The functions are retrieved using their names. If the name conflicts with a Python keyword, add an underscore to the end of the name. Examples:

context.funcs.list_re_group()
context.funcs.assert_()

Here is an example of a PTM template that produces a list of all the authors for a series. The list is stored in a Column built from other columns, behaves like tags. It shows in Book details and has the on separate lines checked (in Preferences → Look & feel → Book details). That option requires the list to be comma-separated. To satisfy that requirement the template converts commas in author names to semicolons then builds a comma-separated list of authors. The authors are then sorted, which is why the template uses author_sort.

python:
def evaluate(book, context):
if book.series is None:
return ''
db = context.db.new_api
ans = set()
# Get the list of books in the series
ids = db.search(f'series:"={book.series}"', '')
if ids:
# Get all the author_sort values for the books in the series
author_sorts = (v for v in db.all_field_for('author_sort', ids).values())
# Add the names to the result set, removing duplicates
for aus in author_sorts:
ans.update(v.strip() for v in aus.split('&'))
# Make a sorted comma-separated string from the result set
return ', '.join(v.replace(',', ';') for v in sorted(ans))

The output in Book details looks like this:

Uložené šablony

Both General Program Mode and Python Template Mode support saving templates and calling those templates from another template, much like calling stored functions. You save templates using Preferences → Advanced → Template functions. More information is provided in that dialog. You call a template the same way you call a function, passing positional arguments if desired. An argument can be any expression. Examples of calling a template, assuming the stored template is named foo:

- foo() – call the template passing no arguments.

- foo(a, b) call the template passing the values of the two variables a and b.

- foo(if field('series') then field('series_index') else 0 fi) – if the book has a series then pass the series_index, otherwise pass the value 0.

In GPM you retrieve the arguments passed in the call to the stored template using the arguments function. It both declares and initializes local variables, effectively parameters. The variables are positional; they get the value of the parameter given in the call in the same position. If the corresponding parameter is not provided in the call then arguments assigns that variable the provided default value. If there is no default value then the variable is set to the empty string. For example, the following arguments function declares 2 variables, key, alternate:

arguments(key, alternate='series')

Examples, again assuming the stored template is named foo:

- foo('#myseries') – argument key is assigned the value 'myseries' and the argument alternate is assigned the default value 'series'.

- foo('series', '#genre') the variable key is assigned the value 'series' and the variable alternate is assigned the value '#genre'.

- foo() – the variable key is assigned the empty string and the variable alternate is assigned the value 'series'.

In PTM the arguments are passed in the arguments parameter, which is a list of strings. There isn’t any way to specify default values. You must check the length of the arguments list to be sure that the number of arguments is what you expect.

An easy way to test stored templates is using the Template tester dialog. For ease of access give it a keyboard shortcut in Preferences → Advanced → Keyboard shortcuts → Template tester. Giving the Stored templates dialog a shortcut will help switching more rapidly between the tester and editing the stored template’s source code.

Providing additional information to templates

A developer can choose to pass additional information to the template processor, such as application-specific book metadata or information about what the processor is being asked to do. A template can access this information and use it during the evaluation.

Developer: how to pass additional information

The additional information is a Python dictionary containing pairs variable_name: variable_value where the values must be strings. The template can access the dictionary, creating template local variables named variable_name containing the value variable_value. The user cannot change the name so it is best to use names that won’t collide with other template local variables, for example by prefixing the name with an underscore.

This dictionary is passed to the template processor (the formatter) using the named parameter global_vars=your_dict. The full method signature is:

def safe_format(self, fmt, kwargs, error_value, book,
column_name=None, template_cache=None,
strip_results=True, template_functions=None,
global_vars={})

Template writer: how to access the additional information

You access the additional information (the globals dictionary) in a template using the template function:

globals(id[=expression] [, id[=expression]]*)

where id is any legal variable name. This function checks whether the additional information provided by the developer contains the name. If it does then the function assigns the provided value to a template local variable with that name. If the name is not in the additional information and if an expression is provided, the expression is evaluated and the result is assigned to the local variable. If neither a value nor an expression is provided, the function assigns the empty string ('') to the local variable.

A template can set a value in the globals dictionary using the template function:

set_globals(id[=expression] [, id[=expression]]*)

This function sets the globals dictionary key:value pair id:value where value is the value of the template local variable id. If that local variable doesn’t exist then value is set to the result of evaluating expression.

Notes on the difference between modes

The three program modes, Single Function Mode (SFM), Template Program Mode (TPM), and General Program Mode (GPM), work differently. SFM is intended to be ‚simple‘ so it hides a lot of programming language bits.

Differences:

- In SFM the value of the column is always passed as an ‚invisible‘ first argument to a function included in the template.

- SFM doesn’t support the difference between variables and strings; all values are strings.

- The following SFM template returns either the series name or the string „no series“:

{series:ifempty(no series)}

The equivalent template in TPM is

{series:'ifempty($, 'no series')'}

The equivalent template in GPM is:

program: ifempty(field('series'), 'no series')

The first argument to ifempty is the value of the field series. The second argument is the string no series. In SFM the first argument, the value of the field, is automatically passed (the invisible argument).

- Several template functions, for example booksize() and current_library_name(), take no arguments. Because of the ‚invisible argument‘ you cannot use these functions in SFM.

- Nested functions, where a function calls another function to compute an argument, cannot be used in SFM. For example this template, intended to return the first 5 characters of the series value uppercased, won’t work in SFM:

{series:uppercase(substr(0,5))}

- TPM and GPM support nested functions. The above template in TPM would be:

{series:'uppercase(substr($, 0,5))'}

In GPM it would be:

program: uppercase(substr(field('series'), 0,5))

- As noted in the above Template Program Mode section, using { and } characters in TPM string literals can lead to errors or unexpected results because they confuse the template processor. It tries to treat them as template boundaries, not characters. In some but not all cases you can replace a { with [[ and a } with ]]. Generally, if your program contains { and } characters then you should use General Program Mode.

User-defined Python template functions

You can add your own Python functions to the template processor. Such functions can be used in any of the three template programming modes. The functions are added by going to Preferences  →  Advanced  →  Template functions. Instructions are shown in that dialog.

Special notes for save/send templates

Special processing is applied when a template is used in a save to disk or send to device template. The values of the fields are cleaned, replacing characters that are special to file systems with underscores, including slashes. This means that field text cannot be used to create folders. However, slashes are not changed in prefix or suffix strings, so slashes in these strings will cause folders to be created. Because of this, you can create variable-depth folder structure.

For example, assume we want the folder structure series/series_index - title, with the caveat that if series does not exist, then the title should be in the top folder. The template to do this is:

{series:||/}{series_index:|| - }{title}

The slash and the hyphen appear only if series is not empty.

The lookup function lets us do even fancier processing. For example, assume that if a book has a series, then we want the folder structure series/series index - title.fmt. If the book does not have a series then we want the folder structure genre/author_sort/title.fmt. If the book has no genre then we want to use ‚Unknown‘. We want two completely different paths, depending on the value of series.

To accomplish this, we:

1. Create a composite field (give it lookup name #aa) containing {series}/{series_index} - {title}. If the series is not empty, then this template will produce series/series_index - title.

2. Create a composite field (give it lookup name #bb) containing {#genre:ifempty(Unknown)}/{author_sort}/{title}. This template produces genre/author_sort/title, where an empty genre is replaced with Unknown.

3. Set the save template to {series:lookup(.,#aa,#bb)}. This template chooses composite field #aa if series is not empty and composite field #bb if series is empty. We therefore have two completely different save paths, depending on whether or not series is empty.

Tips

- Use the Template Tester to test templates. Add the tester to the context menu for books in the library and/or give it a keyboard shortcut.

- Templates can use other templates by referencing composite columns built with the desired template. Alternatively, you can use Stored Templates.

- In a plugboard, you can set a field to empty (or whatever is equivalent to empty) by using the special template {}. This template will always evaluate to an empty string.

- The technique described above to show numbers even if they have a zero value works with the standard field series_index.

Function reference

Reference for all built-in template language functions

Arithmetic

- add(x [, y]*)
- ceiling(x)
- divide(x, y)
- floor(x)
- fractional_part(x)
- mod(x)
- multiply(x [, y]*)
- round(x)
- subtract(x, y)

Boolean

- and(hodnota [, hodnota]*)
- not(hodnota)
- or(hodnota [, hodnota]*)

Date functions

- date_arithmetic(date, calc_spec, fmt)
- days_between(datum1, datum2)
- today()

Formatting values

- finish_formatting(hodnota, formát, předpona, přípona)

- format_date(hodnota, formát_řetězce)

- format_date_field(field_name, format_string)

- format_number(v template)
- human_readable(hodnota)
- rating_to_stars (hodnota, use_half_stars)

- urls_from_identifiers(identifikátory, řazení_výsledků)

Get values from metadata

- annotation_count()
- approximate_formats()
- author_links(val_separator, pair_separator)
- author_sorts(val_separator)
- booksize()
- connected_device_name(storage_location)
- connected_device_uuid(storage_location)
- current_library_name()
- current_library_path()
- current_virtual_library_name()
- field(lookup_name)
- formats_modtimes(formát_data)
- formats_paths()
- formats_sizes()
- has_cover()
- is_marked()
- language_codes(řetězce_jazyka)
- language_strings(kódy_jazyka, lokalizovat)
- ondevice()
- raw_field(lookup_name)
- raw_list(lookup_název, oddělovač)
- series_sort()
- user_categories()
- virtual_libraries()

If-then-else

- check_yes_no(field_name, is_undefined, is_false, is_true)

- contains(hodnota, vzor, text při shodě, text při neshodě)

- field_exists(field_name)

- ifempty(hodnota, text při prázdné)

- test(hodnota, text při neprázdném, text při prázdném)

Iterating over values

- first_non_empty(hodnota [, hodnota]*)

- lookup(hodnota, [vzor, pole,]+ jinak_pole)

- switch(hodnota, [výraz, hodnota,]+ jinak_hodnota)

- switch_if([testovaný_výraz, hodnota_výrazu,]+ jinak_výraz)

List lookup

- identifier_in_list(val, id_name [, found_val, not_found_val])

- in_list(hodnota, oddělovač, [ vzor, nalezená_hodnota, ]+ nenalezená_hodnota)

- list_item(hodnota, pořadí, oddělovač)

- select(hodnota, klíč)

- str_in_list(hodnota, oddělovač, [ řetězec, nalezená_hodnota, ]+ nenalezená_hodnota)

List manipulation

- count(hodnota, oddělovač)

- list_count_matching(seznam, vzor, oddělovač)

- list_difference(seznam1, seznam2, oddělovač)

- list_equals(seznam1, oddělovač1, seznam2, oddělovač2, hodnota_ano, hodnota_ne)

- list_intersection(seznam1, seznam2, oddělovač)

- list_join(with_separator, seznam1, oddělovač1 [, seznam2, oddělovač2]*)

- list_re(seznam_zdrojů, oddělovač, zahrnout_vrácené, volitelně_nahradit)

- list_re_group(src_list, separator, include_re, search_re [, group_template]+)

- list_remove_duplicates(seznam, oddělovač)
- list_sort(seznam, směr, oddělovač)
- list_split(list_val, sep, id_prefix)
- list_union(seznam1, seznam2, oddělovač)
- range(start, stop, step, limit)

- subitems(hodnota, počáteční_index, konečný_index)

- sublist(hodnota, počáteční_index, konečný_index, oddělovač)

Other

- arguments(id[=výraz] [, id[=výraz]]*)
- assign(id, val)
- globals(id[=výraz] [, id[=výraz]]*)
- print(a [, b]*)

Recursion

- eval(template)

- template(x)

Relational

- cmp(x, y, lt, eq, gt)

- first_matching_cmp(val, [cmp1, result1,]+, else_result)

- strcmp(x, y, lt, eq, gt)

- strcmpcase(x, y, lt, eq, gt)

String case changes

- capitalize(hodnota)
- lowercase(hodnota)
- titlecase(hodnota)
- uppercase(hodnota)

String manipulation

- character(character_name)

- re(hodnota, vzor, náhrada)

- re_group(val, pattern [, template_for_group]*)

- shorten(hodnota, znaky vlevo, text uprostřed, znaky vpravo)

- strcat(a [, b]*)

- strcat_max(max, řetězec1 [, předpona2, řetězec2]*)

- strlen(a)
- substr(řetězec, počátek, konec)
- swap_around_articles (hodnota, oddělovač)
- swap_around_comma(hodnota)
- to_hex(val)
- transliterate(a)

Template database functions

- book_count(dotaz, use_vl)

- book_values(sloupec, dotaz, oddělovač, use_vl)

- extra_file_modtime(file_name, format_string)

- extra_file_names(oddělovač [, vzor])
- extra_file_size(název_souboru)
- get_link(field_name, field_value)

- get_note(field_name, field_value, plain_text)

- has_extra_files([vzor])

- has_note(field_name, field_value)

other

- set_globals(id[=výraz] [, id[=výraz]]*)

API of the Metadata objects

- Metadata

- STANDARD_METADATA_FIELDS

© Copyright Kovid Goyal.
Naposledy aktualizováno kvě 03, 2024.

Navigace

- moduly
- další |
- předchozí |
- Spustit »
- Kurzy »
- Jazyk šablon v programu Calibre »

- Reference for all built-in template language functions

Reference for all built-in template language functions

Here, we document all the built-in functions available in the calibre template language. Every function is implemented as a class in python and you can click the source links to see the source code, in case the documentation is insufficient. The functions are arranged in logical groups by type.

Arithmetic

- add(x [, y]*)

- ceiling(x)

- divide(x, y)

- floor(x)

- fractional_part(x)

- mod(x)

- multiply(x [, y]*)

- round(x)

- subtract(x, y)

Boolean

- and(hodnota [, hodnota]*)

- not(hodnota)

- or(hodnota [, hodnota]*)

Date functions

- date_arithmetic(date, calc_spec, fmt)

- days_between(datum1, datum2)

- today()

Formatting values

- finish_formatting(hodnota, formát, předpona, přípona)

- format_date(hodnota, formát_řetězce)

- format_date_field(field_name, format_string)

- format_number(v template)

- human_readable(hodnota)

- rating_to_stars (hodnota, use_half_stars)

- urls_from_identifiers(identifikátory, řazení_výsledků)

Get values from metadata

- annotation_count()

- approximate_formats()

- author_links(val_separator, pair_separator)

- author_sorts(val_separator)

- booksize()

- connected_device_name(storage_location)

- connected_device_uuid(storage_location)

- current_library_name()

- current_library_path()

- current_virtual_library_name()

- field(lookup_name)

- formats_modtimes(formát_data)

- formats_paths()

- formats_sizes()

- has_cover()

- is_marked()

- language_codes(řetězce_jazyka)

- language_strings(kódy_jazyka, lokalizovat)

- ondevice()

- raw_field(lookup_name)

- raw_list(lookup_název, oddělovač)

- series_sort()

- user_categories()

- virtual_libraries()

If-then-else

- check_yes_no(field_name, is_undefined, is_false, is_true)

- contains(hodnota, vzor, text při shodě, text při neshodě)

- field_exists(field_name)

- ifempty(hodnota, text při prázdné)

- test(hodnota, text při neprázdném, text při prázdném)

Iterating over values

- first_non_empty(hodnota [, hodnota]*)

- lookup(hodnota, [vzor, pole,]+ jinak_pole)

- switch(hodnota, [výraz, hodnota,]+ jinak_hodnota)

- switch_if([testovaný_výraz, hodnota_výrazu,]+ jinak_výraz)

List lookup

- identifier_in_list(val, id_name [, found_val, not_found_val])

- in_list(hodnota, oddělovač, [ vzor, nalezená_hodnota, ]+ nenalezená_hodnota)

- list_item(hodnota, pořadí, oddělovač)

- select(hodnota, klíč)

- str_in_list(hodnota, oddělovač, [ řetězec, nalezená_hodnota, ]+ nenalezená_hodnota)

List manipulation

- count(hodnota, oddělovač)

- list_count_matching(seznam, vzor, oddělovač)

- list_difference(seznam1, seznam2, oddělovač)

- list_equals(seznam1, oddělovač1, seznam2, oddělovač2, hodnota_ano, hodnota_ne)

- list_intersection(seznam1, seznam2, oddělovač)

- list_join(with_separator, seznam1, oddělovač1 [, seznam2, oddělovač2]*)

- list_re(seznam_zdrojů, oddělovač, zahrnout_vrácené, volitelně_nahradit)

- list_re_group(src_list, separator, include_re, search_re [, group_template]+)

- list_remove_duplicates(seznam, oddělovač)

- list_sort(seznam, směr, oddělovač)

- list_split(list_val, sep, id_prefix)

- list_union(seznam1, seznam2, oddělovač)

- range(start, stop, step, limit)

- subitems(hodnota, počáteční_index, konečný_index)

- sublist(hodnota, počáteční_index, konečný_index, oddělovač)

Other

- arguments(id[=výraz] [, id[=výraz]]*)

- assign(id, val)

- globals(id[=výraz] [, id[=výraz]]*)

- print(a [, b]*)

Recursion

- eval(template)

- template(x)

Relational

- cmp(x, y, lt, eq, gt)

- first_matching_cmp(val, [cmp1, result1,]+, else_result)

- strcmp(x, y, lt, eq, gt)

- strcmpcase(x, y, lt, eq, gt)

String case changes

- capitalize(hodnota)

- lowercase(hodnota)

- titlecase(hodnota)

- uppercase(hodnota)

String manipulation

- character(character_name)

- re(hodnota, vzor, náhrada)

- re_group(val, pattern [, template_for_group]*)

- shorten(hodnota, znaky vlevo, text uprostřed, znaky vpravo)

- strcat(a [, b]*)

- strcat_max(max, řetězec1 [, předpona2, řetězec2]*)

- strlen(a)

- substr(řetězec, počátek, konec)

- swap_around_articles (hodnota, oddělovač)

- swap_around_comma(hodnota)

- to_hex(val)

- transliterate(a)

Template database functions

- book_count(dotaz, use_vl)

- book_values(sloupec, dotaz, oddělovač, use_vl)

- extra_file_modtime(file_name, format_string)

- extra_file_names(oddělovač [, vzor])

- extra_file_size(název_souboru)

- get_link(field_name, field_value)

- get_note(field_name, field_value, plain_text)

- has_extra_files([vzor])

- has_note(field_name, field_value)

other

- set_globals(id[=výraz] [, id[=výraz]]*)

API of the Metadata objects

Arithmetic

add(x [, y]*)

class calibre.utils.formatter_functions.BuiltinAdd[zdroj]

add(x [, y]*) – vrací součet jeho parametrů. Vyvolá výjimku, pokud parametr není číslo. Tato funkce může být často nahrazena operátorem +.

ceiling(x)

class calibre.utils.formatter_functions.BuiltinCeiling[zdroj]

ceiling(x) – vrací nejmenší celé číslo větší nebo rovno x. Vyvolá výjimku, pokud x není číslo.

divide(x, y)

class calibre.utils.formatter_functions.BuiltinDivide[zdroj]

divide(x, y) – vrací x / y. Vyhazuje výjimku, když buď x nebo y nejsou čísla. Tato funkce může být často nahrazena operátorem /.

floor(x)

class calibre.utils.formatter_functions.BuiltinFloor[zdroj]

floor(x) – vrací největší celé číslo menší nebo rovno x. Vyvolá výjimku, pokud x není číslo.

fractional_part(x)

class calibre.utils.formatter_functions.BuiltinFractionalPart[zdroj]

fractional_part(x) – vrací hodnotu za desetinnou čárkou. Například fractional_part(3,14) vrací 0,14. Vyvolá výjimku, pokud x není číslo.

mod(x)

class calibre.utils.formatter_functions.BuiltinMod[zdroj]

mod(x) – vrací dolní celou část (zbytek x/y). Vyvolá výjimku, pokud x nebo y není číslo.

multiply(x [, y]*)

class calibre.utils.formatter_functions.BuiltinMultiply[zdroj]

multiply(x [, y]*) – vrací součin jeho parametrů. Vyvolá výjimku, pokud kterýkoliv parametr není číslo. Tato funkce může být často nahrazena operátorem *.

round(x)

class calibre.utils.formatter_functions.BuiltinRound[zdroj]

round(x) – vrací nejbližší celé číslo k x. Vyvolá výjimku, pokud x není číslo.

subtract(x, y)

class calibre.utils.formatter_functions.BuiltinSubtract[zdroj]

subtract(x, y) – vrací x - y. Vyvolá výjimku, když buď x nebo y nejsou čísla. Tato funkce může být často nahrazena operátorem -.

Boolean

and(hodnota [, hodnota]*)

class calibre.utils.formatter_functions.BuiltinAnd[zdroj]

and(hodnota [, hodnota]*) – vrací řetězec „1“, pokud všechny hodnoty nejsou prázdné, jinak vrací prázdný řetězec. Tato funkce funguje dobře s test nebo first_non_empty. Můžete mít tolik hodnot, kolik chcete. V mnoha případech může tuto funkci nahradit operátor &&.

not(hodnota)

class calibre.utils.formatter_functions.BuiltinNot[zdroj]

not(hodnota) – vrací řetězec „1“, pokud je hodnota prázdná, jinak vrací prázdný řetězec. Tato funkce funguje dobře s test nebo first_non_empty. V mnoha případech může tuto funkci nahradit operátor !.

or(hodnota [, hodnota]*)

class calibre.utils.formatter_functions.BuiltinOr[zdroj]

or(hodnota [, hodnota]*) – vrací řetězec „1“, pokud jakákoliv hodnota není prázdná, jinak vrací prázdný řetězec. Tato funkce funguje dobře s test nebo first_non_empty. Můžete mít tolik hodnot, kolik chcete. V mnoha případech může tuto funkci nahradit operátor ||.

Date functions

date_arithmetic(date, calc_spec, fmt)

class calibre.utils.formatter_functions.BuiltinDateArithmetic[zdroj]

date_arithmetic(date, calc_spec, fmt) – Vypočítá nové datum z ‚date‘ pomocí ‚calc_spec‘. Vrátí nové datum formátované podle nepovinného ‚fmt‘: pokud není zadáno, bude výsledek ve formátu iso. Calc_spec je řetězec vytvořený spojením dvojic ‚vW‘ (valueWhat), kde ‚v‘ je případně záporné číslo a W je jedno z následujících písmen: s: přidat ‚v‘ sekund k ‚date‘ m: přidat ‚v‘ minut k ‚date‘ h: přidat ‚v‘ hodin k ‚date‘ d: přidat ‚v‘ dnů k ‚date‘ w: přidat ‚v‘ týdnů k ‚date‘ y: přidat ‚v‘ let k ‚date‘, kde rok je 365 dní. Příklad: ‚1s3d-1m‘ přidá 1 sekundu, přidá 3 dny a odebere 1 minutu od ‚date‘.

days_between(datum1, datum2)

class calibre.utils.formatter_functions.BuiltinDaysBetween[zdroj]

days_between(datum1, datum2) – vrací počet dní mezi datum1 a datum2. Počet je kladný, pokud je datum1 větší než datum2, jinak je záporný. Pokud datum1 nebo datum2 nejsou data, funkce vrací prázdný řetězec.

today()

class calibre.utils.formatter_functions.BuiltinToday[zdroj]

today() – vrací řetězec data pro dnešek. Tato hodnota je navržena pro použití ve format_date nebo days_between, ale může s ní být zacházeno jako s jakýmkoliv jiným řetězcem. Datum je ve formátu ISO.

Formatting values

finish_formatting(hodnota, formát, předpona, přípona)

class calibre.utils.formatter_functions.BuiltinFinishFormatting[zdroj]

finish_formatting(hodnota, formát, předpona, přípona) – použije formát, předponu a příponu na hodnotu stejným způsobem, jak bylo provedeno v šabloně jako {series_index:05.2f| - |- }. Například následující vytváří stejný výstup jako výše uvedená šablona: program: finish_formatting(field(„series_index“), „05.2f“, „ - „, „ - „)

format_date(hodnota, formát_řetězce)

class calibre.utils.formatter_functions.BuiltinFormatDate[zdroj]

format_date(hodnota, formát_řetězce) – formátuje hodnotu, což musí být datum, pomocí formát_řetězce, vrací řetězec. Kódy formátování jsou: d: den jako číslo bez úvodní nuly (1 až 31), dd: den jako číslo s úvodní nulou (01 až 31), ddd: zkrácený lokalizovaný název dne (např. „Pon“ až „Ned“), dddd: dlouhý lokalizovaný název dne (např. „Pondělí“ až „Neděle“). M: měsíc jako číslo bez úvodní nuly (1 až 12), MM: měsíc jako číslo s úvodní nulou (01 až 12), MMM: zkrácený lokalizovaný název měsíce (např. „Led“ až „Pro“), MMMM: dlouhý lokalizovaný název měsíce (např. „Leden“ až „Prosinec“). yy: rok jako dvoumístné číslo (00 až 99), yyyy: rok jako čtyřmístné číslo. h: hodiny bez úvodní nuly (0 až 11 nebo 0 až 23, v závislosti na dop./odp.), hh: hodiny s úvodní nulou (00 až 11 nebo 00 až 23, v závislosti na dop./odp.), m: minuty bez úvodní nuly (0 až 59), mm: minuty s úvodní nulou (00 až 59), s: sekundy bez úvodní nuly (0 až 59), ss: sekundy s úvodní nulou (00 až 59). ap: použít 12hodinový formát namísto 24hodinového formátu, kde je „ap“ nahrazeno lokalizovaným řetězcem pro am nebo pm, AP: použít 12hodinový formát namísto 24hodinového formátu, kde je „AP“ nahrazeno lokalizovaným řetězcem pro AM nebo PM, iso: datum s časem a časovou zónou. Musí být přítomen jediný formát to_number: datum jako číslo s plovoucí desetinnou čárkou from_number[:fmt]: naformátujte časové razítko pomocí fmt, pokud je k dispozici, jinak iso

format_date_field(field_name, format_string)

class calibre.utils.formatter_functions.BuiltinFormatDateField[zdroj]

format_date_field(field_name, format_string) – format the value in the field ‚field_name‘, which must be the lookup name of date field, either standard or custom. See ‚format_date‘ for the formatting codes. This function is much faster than format_date and should be used when you are formatting the value in a field (column). It can’t be used for computed dates or dates in string variables. Example: format_date_field(‚pubdate‘, ‚yyyy.MM.dd‘)

format_number(v template)

class calibre.utils.formatter_functions.BuiltinFormatNumber[zdroj]

format_number(v template) – formátuje číslo v pomocí šablony formátování Pythonu, jako je „{0:5.2f}“ nebo „{0:,d}“ nebo „${0:5,.2f}“. Součást šablony field_name musí být 0 (nula) („{0:“ ve výše uvedených příkladech). Pro více příkladů se podívejte na jazyk šablony a do dokumentace Pythonu. Pokud šablona obsahuje pouze formát, můžete vynechat úvodní „{0:“ a koncové „}“. Vrací prázdný řetězec, pokud formátování selže.

human_readable(hodnota)

class calibre.utils.formatter_functions.BuiltinHumanReadable[zdroj]

human_readable(hodnota) – vrací řetězec představující číslo v KB, MB, GB atd.

rating_to_stars (hodnota, use_half_stars)

class calibre.utils.formatter_functions.BuiltinRatingToStars[zdroj]

rating_to_stars (hodnota, use_half_stars) – Vrací hodnocení jako řetězec znaků hvězdiček. Hodnota je číslo mezi 0 a 5. Nastavte use_half_stars na 1, pokud chcete používat poloviční hvězdičky ve sloupcích s vlastním hodnocením, které podporují neceločíselná hodnocení, například 2,5.

urls_from_identifiers(identifikátory, řazení_výsledků)

class calibre.utils.formatter_functions.BuiltinUrlsFromIdentifiers[zdroj]

urls_from_identifiers(identifikátory, řazení_výsledků) – při zadání seznamu identifikátorů oddělených čárkou, kde identifikátor je dvojtečkou oddělená dvojice hodnot (název:hodnota_id), vrátí čárkou oddělený seznam URL HTML vygenerovaných z identifikátorů. Seznam není seřazen, pokud je řazení_výsledků 0 (znak nebo číslo), jinak je seřazen abecedně podle názvu identifikátoru. Adresy URL jsou generovány stejným způsobem jako vestavěný sloupec identifikátorů, když jsou zobrazeny v Podrobnostech o knize.

Get values from metadata

annotation_count()

class calibre.utils.formatter_functions.BuiltinAnnotationCount[zdroj]

annotation_count() – vrací celkový počet anotací všechtypů připojených k aktuální knize. Tato funkce funguje pouze v grafickém rozhraní.

approximate_formats()

class calibre.utils.formatter_functions.BuiltinApproximateFormats[zdroj]

approximate_formats() – vrací čárkami oddělený seznam formátů, které svého času byly přidruženy ke knize. Není žádná záruka, že je tento seznam správný, ačkoliv nejspíš je. Tato funkce může být volána v režimu šablony programu pomocí šablony „{:‘approximate_formats()‘}“. Pamatujte, že názvy formátů jsou vždy velkými písmeny, jako v EPUB. Tato funkce funguje pouze v grafickém rozhraní. Pokud chcete použít tyto hodnoty v šablonách uložení na disk nebo odeslání do zařízení, pak musíte vytvořit vlastní „Sloupec sestavený z jiných sloupců“, použít funkci v šabloně toho sloupce a použít hodnotu toho sloupce ve své šabloně uložení nebo odeslání

author_links(val_separator, pair_separator)

class calibre.utils.formatter_functions.BuiltinAuthorLinks[zdroj]

author_links(val_separator, pair_separator) – vrací řetězec obsahující seznam autorů a hodnoty odkazů na tyto autory ve formě author1 val_separator author1link pair_separator author2 val_separator author2link atd. Autor je oddělen od své hodnoty odkazu řetězcem val_separator bez přidaných mezer. Páry author:linkvalue jsou odděleny parametrem řetězce pair_separator bez přidaných mezer. Je na vás zvolit si řetězce oddělovače, které se nevyskytují ve jménech autorů nebo odkazech. Autor je zahrnut i v případě, že odkaz na autora je prázdný.

author_sorts(val_separator)

class calibre.utils.formatter_functions.BuiltinAuthorSorts[zdroj]

author_sorts(val_separator) – vrací řetězec obsahující seznam hodnot řazení autorů pro autory knihy. Řazení je takové jako v metadatech autora (odlišné od author_sort v knihách). Vrácený seznam má podobu řazení autora 1 val_separator řazení autora 2 atd. Hodnoty řazení autora v tomto seznamu jsou ve stejném pořadí jako autoři knihy. Pokud chcete kolem val_separator mezery, tak je zahrňte do řetězce oddělovače

booksize()

class calibre.utils.formatter_functions.BuiltinBooksize[zdroj]

booksize() – vrací hodnotu pole velikost. Tato funkce funguje pouze v grafickém rozhraní. Pokud chcete použít tuto hodnotu v šablonách uložení na disk nebo odeslání do zařízení, pak musíte vytvořit vlastní „Sloupec sestavený z jiných sloupců“, použít funkci v šabloně toho sloupce a použít hodnotu toho sloupce ve své šabloně uložení nebo odeslání

connected_device_name(storage_location)

class calibre.utils.formatter_functions.BuiltinConnectedDeviceName[zdroj]

connected_device_name(storage_location) – pokud je zařízení připojeno, vrátit název zařízení, jinak vrátit prázdný řetězec. Každé umístění úložiště v zařízení může mít jiný název. Názvy umístění jsou ‚main‘, ‚carda‘ a ‚cardb‘. Tato funkce funguje pouze v grafickém rozhraní.

connected_device_uuid(storage_location)

class calibre.utils.formatter_functions.BuiltinConnectedDeviceUUID[zdroj]

connected_device_uuid(storage_location) – pokud je zařízení připojeno, vrátit uuid zařízení (jedinečný identifikátor), jinak vrátit prázdný řetězec. Každé umístění úložiště v zařízení má jiné uuid. Názvy umístění jsou ‚main‘, ‚carda‘ a ‚cardb‘. Tato funkce funguje pouze v grafickém rozhraní.

current_library_name()

class calibre.utils.formatter_functions.BuiltinCurrentLibraryName[zdroj]

current_library_name() – vrací poslední název v cestě k aktuální knihovně Calibre. Tato funkce může být volána v režimu šablony programu pomocí šablony „{:‘current_library_name()‘}“.

current_library_path()

class calibre.utils.formatter_functions.BuiltinCurrentLibraryPath[zdroj]

current_library_path() – vrací cestu k aktuální knihovně Calibre. Tato funkce může být volána v režimu šablony programu pomocí šablony „{:‘current_library_path()‘}“.

current_virtual_library_name()

class calibre.utils.formatter_functions.BuiltinCurrentVirtualLibraryName[zdroj]

current_virtual_library_name() – vrací název aktuální virtuální knihovny, pokud nějaká existuje, jinak prázdný řetězec. Velikost písmen názvu knihovny je zachována. Příklad: „program: current_virtual_library_name()“.

field(lookup_name)

class calibre.utils.formatter_functions.BuiltinField[zdroj]

field(lookup_name) – vrací pole metadat pojmenovaná podle lookup_name

formats_modtimes(formát_data)

class calibre.utils.formatter_functions.BuiltinFormatsModtimes[zdroj]

formats_modtimes(formát_data) – vrací čárkami oddělený seznam dvojtečkami oddělených položek představujících časy úpravy pro formáty knihy. Parametr formát_data určuje, jak má být datum formátováno. Pro podrobnosti se podívejte na funkci date_format. Můžete použít funkci select pro získání času úpravy konkrétního formátu. Pamatujte, že názvy formátu jsou vždy velkými písmeny, jako v EPUB.

formats_paths()

class calibre.utils.formatter_functions.BuiltinFormatsPaths[zdroj]

formats_paths() – vrací čárkami oddělený seznam dvojtečkami oddělených položek představujících celé cesty k formátům knihy. Můžete použít funkci select pro získání cesty ke konkrétnímu formátu. Pamatujte, že názvy formátu jsou vždy velkými písmeny, jako v EPUB.

formats_sizes()

class calibre.utils.formatter_functions.BuiltinFormatsSizes[zdroj]

formats_sizes() – vrací čárkami oddělený seznam dvojtečkami oddělených položek představujících velikosti formátů knihy v bajtech. Můžete použít funkci select pro získání velikosti pro konkrétní formát. Pamatujte, že názvy formátu jsou vždy velkými písmeny, jako v EPUB.

has_cover()

class calibre.utils.formatter_functions.BuiltinHasCover[zdroj]

has_cover() – vrací Ano, pokud má kniha obálku, jinak vrací prázdný řetězec

is_marked()

class calibre.utils.formatter_functions.BuiltinIsMarked[zdroj]

is_marked() – zkontrolujte, zda je kniha v Calibre ‚označena‘. Pokud je, pak vrací hodnotu označení, buď ‚true‘, nebo seznam pojmenovaných označení oddělených čárkami. Vrací ‚‘, pokud kniha není označena.

language_codes(řetězce_jazyka)

class calibre.utils.formatter_functions.BuiltinLanguageCodes[zdroj]

language_codes(řetězce_jazyka) – vrací kódy jazyka pro řetězce předané v řetězce_jazyka. Řetězce musí být v jazyce aktuálního národního prostředí. Řetězce_jazyka je čárkami oddělený seznam.

language_strings(kódy_jazyka, lokalizovat)

class calibre.utils.formatter_functions.BuiltinLanguageStrings[zdroj]

language_strings(kódy_jazyka, lokalizovat) – vrací řetězec pro kódy jazyka předané v kódy_jazyka. Pokud lokalizovat je nula, vrací řetězce v angličtině. Pokud lokalizovat není nula, vrací řetězce v jazyce aktuálního národního prostředí. Kódy_jazyka je čárkami oddělený seznam.

ondevice()

class calibre.utils.formatter_functions.BuiltinOndevice[zdroj]

ondevice() – vrací Ano, pokud je nastaveno ondevice, jinak vrací prázdný řetězec. Tato funkce funguje pouze v grafickém rozhraní. Pokud chcete použít tuto hodnotu v šablonách uložení na disk nebo odeslání do zařízení, pak musíte vytvořit vlastní „Sloupec sestavený z jiných sloupců“, použít funkci v šabloně toho sloupce a použít hodnotu toho sloupce ve své šabloně uložení nebo odeslání

raw_field(lookup_name)

class calibre.utils.formatter_functions.BuiltinRawField[zdroj]

raw_field(lookup_name) – vrací pole metadata pojmenovaná podle lookup_name bez použití jakéhokoliv formátování. Vyhodnocuje a vrací volitelný druhý parametr ‚default‘, pokud je pole nedefinováno (‚None‘).

raw_list(lookup_název, oddělovač)

class calibre.utils.formatter_functions.BuiltinRawList[zdroj]

raw_list(lookup_název, oddělovač) – vrací seznam metadat pojmenovaný podle lookup_name bez použití jakéhokoliv formátování nebo řazení a s položkami oddělenými oddělovačem.

series_sort()

class calibre.utils.formatter_functions.BuiltinSeriesSort[zdroj]

series_sort() – vrací hodnotu řazení série

user_categories()

class calibre.utils.formatter_functions.BuiltinUserCategories[zdroj]

user_categories() – vrátí čárkami oddělený seznam uživatelských kategorií, které obsahují tuto knihu. Tato funkce funguje pouze v grafickém rozhraníI. Pokud chcete použít tyto hodnoty v šablonách uložení na disk nebo odeslání do zařízení, pak musíte vytvořit vlastní „Sloupec sestavený z jiných sloupců“, použít funkci v šabloně toho sloupce a použít hodnotu toho sloupce ve svých šablonách uložení nebo odeslání

virtual_libraries()

class calibre.utils.formatter_functions.BuiltinVirtualLibraries[zdroj]

virtual_libraries() – vrací čárkami oddělený seznam Virtuálních knihoven, které obsahují tuto knihu. Tato funkce funguje pouze v grafickém rozhraní. Pokud chcete použít tyto hodnoty v šablonách uložení na disk nebo odeslání do zařízení, pak musíte vytvořit vlastní „Sloupec sestavený z jiných sloupců“, použít funkci v šabloně toho sloupce a použít hodnotu toho sloupce ve své šabloně uložení nebo odeslání

If-then-else

check_yes_no(field_name, is_undefined, is_false, is_true)

class calibre.utils.formatter_functions.BuiltinCheckYesNo[zdroj]

check_yes_no(field_name, is_undefined, is_false, is_true) – kontroluje hodnotu pole ano/ne pojmenovanou vyhledávacím klíčem field_name podle hodnoty zadané parametry, vrací „ano“, pokud je nalezena shoda, jinak vrací prázdný řetězec. Nastavte parametry is_undefined, is_false nebo is_true na 1 (číslo) pro kontrolu těchto podmínek, jinak je nastavte na 0. Příklad: check_yes_no(„#bool“, 1, 0, 1) vrací „ano“, pokud pole ano/ne „#bool“ je buď nedefinováno (ani True ani False) nebo True. Na 1 může být nastaveno více než jeden z parametrů is_undefined, is_false nebo is_true. Tato funkce je obvykle používána funkcemi test() nebo is_empty().

contains(hodnota, vzor, text při shodě, text při neshodě)

class calibre.utils.formatter_functions.BuiltinContains[zdroj]

contains(hodnota, vzor, text při shodě, text při neshodě) – kontroluje, zda pole obsahuje shody pro vzor regulárního výrazu. Vrací text při shodě, pokud jsou nalzeny shody, jinak vrací text při neshodě

field_exists(field_name)

class calibre.utils.formatter_functions.BuiltinFieldExists[zdroj]

field_exists(field_name) – kontroluje, zda existuje pole (sloupec) s názvem field_name, vrací ‚1‘, pokud ano, a ‚‘, pokud ne.

ifempty(hodnota, text při prázdné)

class calibre.utils.formatter_functions.BuiltinIfempty[zdroj]

ifempty(hodnota, text při prázdné) – vrací hodnotu, pokud hodnota není prázdná, jinak vrací text při prázdné

test(hodnota, text při neprázdném, text při prázdném)

class calibre.utils.formatter_functions.BuiltinTest[zdroj]

test(hodnota, text při neprázdném, text při prázdném) – vrací text při neprázdném, pokud pole není prázdné, jinak vrací text při prázdném

Iterating over values

first_non_empty(hodnota [, hodnota]*)

class calibre.utils.formatter_functions.BuiltinFirstNonEmpty[zdroj]

first_non_empty(hodnota [, hodnota]*) – vrací první hodnotu, která není prázdná. Pokud jsou všechny hodnoty prázdné, pak je vrácen prázdný řetězec. Můžete mít tolik hodnot, kolik chcete.

lookup(hodnota, [vzor, pole,]+ jinak_pole)

class calibre.utils.formatter_functions.BuiltinLookup[zdroj]

lookup(hodnota, [vzor, pole,]+ jinak_pole) – jako switch kromě toho, že parametry jsou názvy polí (metadat), ne text. Hodnota vhodného pole bude načtena a použita. Všimněte si, že protože složené sloupce jsou pole, můžete použít tuto funkci v jednom složeném poli, aby použilo hodnotu některého jiného složeného pole. Toto je neobyčejně užitečné při sestavování proměnných cest uložení.

switch(hodnota, [výraz, hodnota,]+ jinak_hodnota)

class calibre.utils.formatter_functions.BuiltinSwitch[zdroj]

switch(hodnota, [výraz, hodnota,]+ jinak_hodnota) – pro každý pár výraz, hodnota kontroluje, zda pole odpovídá regulárnímu výrazu výraz a pokud ano, vrací tu hodnotu. Pokud neodpovídají žádné výrazy, pak je vrácena jinak_hodnota. Můžete mít tolik párů výraz, hodnota, kolik chcete.

switch_if([testovaný_výraz, hodnota_výrazu,]+ jinak_výraz)

class calibre.utils.formatter_functions.BuiltinSwitchIf[zdroj]

switch_if([testovaný_výraz, hodnota_výrazu,]+ jinak_výraz) – pro každý pár „testovaný_výraz, hodnota_výrazu“ zkontroluje, zda je testovaný_výraz True (neprázdný), a pokud ano, vrátí výsledek hodnota_výrazu. Pokud žádný testovaný_výraz není True, pak se vrátí výsledek jinak_výraz. Můžete mít libovolný počet párů „testovaný_výraz, hodnota_výrazu“.

List lookup

identifier_in_list(val, id_name [, found_val, not_found_val])

class calibre.utils.formatter_functions.BuiltinIdentifierInList[zdroj]

identifier_in_list(val, id_name [, found_val, not_found_val]) – treat val as a list of identifiers separated by commas. An identifier has the format „id_name:value“. The id_name parameter is the id_name text to search for, either „id_name“ or „id_name:regexp“. The first case matches if there is any identifier matching that id_name. The second case matches if id_name matches an identifier and the regexp matches the identifier’s value. If found_val and not_found_val are provided then if there is a match then return found_val, otherwise return not_found_val. If found_val and not_found_val are not provided then if there is a match then return the identifier:value pair, otherwise the empty string.

in_list(hodnota, oddělovač, [ vzor, nalezená_hodnota, ]+ nenalezená_hodnota)

class calibre.utils.formatter_functions.BuiltinInList[zdroj]

in_list(hodnota, oddělovač, [ vzor, nalezená_hodnota, ]+ nenalezená_hodnota) – považuje hodnotu za seznam položek oddělených oddělovačem, pokud vzor odpovídá kterékoliv hodnotě ze seznamu, pak vrací nalezená_hodnota. Pokud vzor neodpovídá žádné hodnotě ze seznamu, pak vrací nenalezená_hodnota. Páry vzoru a nalezené_hodnoty mohou být opakovány tolikrát, kolikrát je potřeba. Vzory jsou kontrolovány v pořadí. Je vrácena nenalezená_hodnota pro první shodu. Aliasy: in_list(), list_contains().

list_item(hodnota, pořadí, oddělovač)

class calibre.utils.formatter_functions.BuiltinListitem[zdroj]

list_item(hodnota, pořadí, oddělovač) – interpretuje hodnotu jako seznam položek oddělených oddělovačem, vrací položku na pořadí. První položka je číslo nula. Poslední položka může být vrácena pomocí list_item(-1,oddělovač). Pokud položka naní v seznamu, pak je vrácena prázdné hodnota. Oddělovač má stejný význam jako ve funkci count.

select(hodnota, klíč)

class calibre.utils.formatter_functions.BuiltinSelect[zdroj]

select(hodnota, klíč) – interpretuje hodnotu jako čárkami oddělený seznam položek, s položkami „identifikátor:hodnota“. Nejde pár s identifikátorem rovnajícím se klíči a vrátí odpovídající hodnotu. Vrací prázdný řetězec, pokud není nalezena žádná shoda.

str_in_list(hodnota, oddělovač, [ řetězec, nalezená_hodnota, ]+ nenalezená_hodnota)

class calibre.utils.formatter_functions.BuiltinStrInList[zdroj]

str_in_list(hodnota, oddělovač, [ řetězec, nalezená_hodnota, ]+ nenalezená_hodnota) – považuje hodnotu za seznam položek oddělených oddělovačem, pokud řetězec odpovídá kterékoliv hodnotě ze seznamu, pak vrací nalezenou_hodnotu. Pokud řetězec neodpovídá žádné hodnotě ze seznamu, pak vrací nenalezenou_hodnotu. Porovnání je přesná shoda (neobsahuje) a nerozlišuje velikost písmen. Páry řetězce a nalezené_hodnoty mohou být opakovány tolikrát, kolikrát je potřeba. Vzory jsou kontrolovány v pořadí. Je vrácena nenalezená_hodnota pro první shodu.

List manipulation

count(hodnota, oddělovač)

class calibre.utils.formatter_functions.BuiltinCount[zdroj]

count(hodnota, oddělovač) – interpretuje hodnotu jako seznam položek oddělených oddělovačem, vrací počet položek v seznamu. Většina seznamů používá jako oddělovač čárku, ale autoři používají znak &. Příklady: {tags:count(,)}, {authors:count(&)}. Aliasy: count(), list_count().

list_count_matching(seznam, vzor, oddělovač)

class calibre.utils.formatter_functions.BuiltinListCountMatching[zdroj]

list_count_matching(seznam, vzor, oddělovač) – interpretuje ‚seznam‘ jako seznam položek oddělených ‚oddělovačem‘ a vrací počet položek v seznamu, které odpovídají regulárnímu výrazu ‚vzor‘. Aliasy: list_count_matching(), count_matching().

list_difference(seznam1, seznam2, oddělovač)

class calibre.utils.formatter_functions.BuiltinListDifference[zdroj]

list_difference(seznam1, seznam2, oddělovač) – vrací seznam vytvořený odebrání ze seznam1 všech položek nalezených v seznam2 pomocí porovnání bez rozlišení malých a velkých písmen. Položky v seznam1 a seznam2 jsou odděleny oddělovačem, stejně jako položky ve vráceném seznamu.

list_equals(seznam1, oddělovač1, seznam2, oddělovač2, hodnota_ano, hodnota_ne)

class calibre.utils.formatter_functions.BuiltinListEquals[zdroj]

list_equals(seznam1, oddělovač1, seznam2, oddělovač2, hodnota_ano, hodnota_ne) – vrací hodnota_ano, pokud seznam1 a seznam2 obsahují stejné položky, jinak vrací hodnota_ne. Položky jsou určeny rozdělením každého seznamu pomocí příslušného znaku oddělovače (oddělovač1 nebo oddělovač2). Pořadí položek v seznamu není relevantní. Porovnání je bez rozlišení malých a velkých písmen.

list_intersection(seznam1, seznam2, oddělovač)

class calibre.utils.formatter_functions.BuiltinListIntersection[zdroj]

list_intersection(seznam1, seznam2, oddělovač) – vrací seznam vytvořený odebrání ze seznam1 všech položek nenalezených v seznam2 pomocí porovnání bez rozlišení malých a velkých písmen. Položky v seznam1 a seznam2 jsou odděleny oddělovačem, stejně jako položky ve vráceném seznamu.

list_join(with_separator, seznam1, oddělovač1 [, seznam2, oddělovač2]*)

class calibre.utils.formatter_functions.BuiltinListJoin[zdroj]

list_join(with_separator, seznam1, oddělovač1 [, seznam2, oddělovač2]*) – vrací seznam vytvořený spojením položek zdrojových seznamů (seznam1 atd.) s použitím oddělovače mezi položkami výsledného seznamu. Položky v každém zdrojovém seznamu[123…] jsou odděleny přidruženým oddělovačem[123…]. Seznam může obsahovat nulové hodnoty. Může to být pole jako vydavatel, které je jednohodnotové, tedy vlastně jednopoložkový seznam. Duplicity jsou odebrány pomocí porovnávání bez ohledu na velikost písmen. Položky jsou vráceny v pořadí, v jakém jsou uvedeny ve zdrojových seznamech. Pokud se položky v seznamech liší pouze velikostí písmen, použije se poslední z nich. Všechny oddělovače mohou mít více než jeden znak.
Příklad:

program:

list_join(‚#@#‘, $authors, ‚&‘, $tags, ‚,‘)

Příkaz list_join můžete použít na výsledky předchozích volání příkazu list_join následujícím způsobem:

program:

a = list_join(‚#@#‘, $authors, ‚&‘, $tags, ‚,‘); b = list_join(‚#@#‘, a, ‚#@#‘, $#genre, ‚,‘, $#people, ‚&‘)

K vygenerování seznamu můžete použít výrazy. Předpokládejme například, že chcete položky pro autory a #žánr, ale se změnou žánru na slovo ‚Žánr: ‘ následované prvním písmenem žánru, tj. z žánru ‚Fikce‘ se stane ‚Žánr: F‘. To uděláte následujícím způsobem:

program:

list_join(‚#@#‘, $authors, ‚&‘, list_re($#genre, ‚,‘, ‚^(.).*$‘, ‚Genre: 1‘), ‚,‘)

list_re(seznam_zdrojů, oddělovač, zahrnout_vrácené, volitelně_nahradit)

class calibre.utils.formatter_functions.BuiltinListRe[zdroj]

list_re(seznam_zdrojů, oddělovač, zahrnout_vrácené, volitelně_nahradit) – Vytváří seznam nejdříve rozdělením seznam_zdrojů na položky pomocí znaku oddělovače. Pro každou položku na seznamu kontroluje, zda odpovídá zahrnout_vrácené. Pokud ano, pak ji přidá na seznam vrácených. Pokud volitelně_nahradit není prázdný řetězec, pak použije náhradu před přidáním položky na seznam vrácených.

list_re_group(src_list, separator, include_re, search_re [, group_template]+)

class calibre.utils.formatter_functions.BuiltinListReGroup[zdroj]

list_re_group(src_list, separator, include_re, search_re [, group_template]+) – Jako list_re kromě toho, že nahrazení nejsou volitelná. Používáre_group(list_item, search_re, group_template, …) při provádění nahrazení na seznamu výsledků.

list_remove_duplicates(seznam, oddělovač)

class calibre.utils.formatter_functions.BuiltinListRemoveDuplicates[zdroj]

list_remove_duplicates(seznam, oddělovač) – vrací seznam vytvořený odebráním duplicitních položek ze zdrojového seznamu. Pokud se položky liší pouze ve velikost písmen, vrátí se poslední z nich. Položky ve zdrojovém seznamu jsou odděleny oddělovačem, stejně jako položky ve vráceném seznamu.

list_sort(seznam, směr, oddělovač)

class calibre.utils.formatter_functions.BuiltinListSort[zdroj]

list_sort(seznam, směr, oddělovač) – vrací seznam seřazený pomocí řazení bez rozlišení malých a velkých písmen. Pokud je směr nula, je seznam seřazen vzestupně, jinak sestupně. Položky seznamu jsou odděleny oddělovačem, stejně jako položky ve vráceném seznamu.

list_split(list_val, sep, id_prefix)

class calibre.utils.formatter_functions.BuiltinListSplit[zdroj]

list_split(list_val, sep, id_prefix) – rozdělí list_val na samostatné hodnoty pomocí ‚sep‘, poté přiřadí hodnoty proměnným s názvem ‚id_prefix_N‘, kde N je pozice hodnoty v seznamu. První položka má pozici 0 (nula). Funkce vrací poslední prvek v seznamu. Příklad: split(‚jedna:dva:moc‘, ‚:‘, ‚var‘) je ekvivalentem k var_0 = ‚jedna‘; var_1 = ‚dva‘; var_2 = ‚moc‘.

list_union(seznam1, seznam2, oddělovač)

class calibre.utils.formatter_functions.BuiltinListUnion[zdroj]

list_union(seznam1, seznam2, oddělovač) – vrací seznam vytvořený sloučením položek v seznam1 a seznam2, odebráním duplicitních položek pomocí porovnání bez rozlišení malých a velkých písmen. Pokud se položky liší velikostí písmen, je použita ta z seznam1. Položky v seznam1 a seznam2 jsou odděleny oddělovačem, stejně jako položky ve vráceném seznamu. Aliasy: list_union(), merge_lists().

range(start, stop, step, limit)

class calibre.utils.formatter_functions.BuiltinRange[zdroj]

range(start, stop, step, limit) – vrací seznam čísel vygenerovaných smyčkou nad rozsahem zadaném parametry start (začátek), stop (konec) a step (krok) s maximální délkou limit (omezení). První vytvořená hodnota je ‚start‘. Následující hodnoty next_v jsou current_v+step. Smyčka pokračuje, dokud next_v < stop assuming step is positive, otherwise while next_v > stop. Pokud start neprojde testem, vytvoří se prázdný seznam: start>=stop, pokud je step kladný. limit nastaví maximální délku seznamu a má výchozí hodnotu 1000. Parametry start, step a limit jsou nepovinné. Volání range() s jedním argumentem určuje stop. Dva parametry určují start a stop. Tři parametry určují start, stop a krok. Čtyři parametry určují start, stop, krok a limit. Příklady: range(5) -> ‚0,1,2,3,4‘. range(0,5) -> ‚0,1,2,3,4‘. range(-1,5) -> ‚-1,0,1,2,3,4‘. range(1,5) -> ‚1,2,3,4‘. range(1,5,2) -> ‚1,3‘. range(1,5,2,5) -> ‚1,3‘. range(1,5,2,1) -> chyba(překročen limit).

subitems(hodnota, počáteční_index, konečný_index)

class calibre.utils.formatter_functions.BuiltinSubitems[zdroj]

subitems(hodnota, počáteční_index, konečný_index) – tato funkce je používána na rozdělení seznamů položek, jako jsou žánry. Interpretuje hodnotu jako čárkami oddělený seznam položek, kde každá položka je tečkami oddělený seznam. Vrací nový seznam vytvořený nejdříve nalezením všech tečkami oddělených položek, pak pro každou takovou položku extrahováním komponent od počáteční_index po konečný_index a pak zkombinováním výsledků zpět dohromady. První komponenta v tečkami odděleném seznamu má index nula. Pokud je index záporný, pak se počítá od konce seznamu. Jako speciální příklad je koncový_index nula považován za délku seznamu. Příklady používající režim základní šablony a předpokládající, že hodnota #genre „A.B.C“: {#genre:subitems(0,1)} vrací „A“. {#genre:subitems(0,2)} vrací „A.B“. {#genre:subitems(1,0)} vrací „B.C“. Předpokládající, že hodnota #genre „A.B.C, D.E.F“, {#genre:subitems(0,1)} vrací „A, D“. {#genre:subitems(0,2)} vrací „A.B, D.E“

sublist(hodnota, počáteční_index, konečný_index, oddělovač)

class calibre.utils.formatter_functions.BuiltinSublist[zdroj]

sublist(hodnota, počáteční_index, konečný_index, oddělovač) – interpretuje hodnotu jako seznam položek oddělený oddělovačem, vrací nový seznam vytvořený od položky počáteční_pořadí po konečný_pořadí. První položka je číslo nula. Pokud je pořadí záporný, pak se počítá od konce seznamu. Jako speciální příklad je koncový_index nula považován za délku seznamu. Příklady používající základní režim šablony a předpokládající, že sloupec štítky (který je čárkami oddělený) obsahující „A, B, C“: {tags:sublist(0,1,\,)} vrací „A“. {tags:sublist(-1,0,\,)} vrací „C“. {tags:sublist(0,-1,\,)} vrací „A, B“.

Other

arguments(id[=výraz] [, id[=výraz]]*)

class calibre.utils.formatter_functions.BuiltinArguments[zdroj]

arguments(id[=výraz] [, id[=výraz]]*) – používá se v uložené šabloně k načtení parametrů předaných ve volání. Deklaruje a inicializuje místní proměnné, účinně parametry. Proměnné jsou poziční; získají hodnotu parametru zadaného ve volání na stejné pozici. Pokud odpovídající parametr není uveden ve volání, pak parametry přiřadí dané proměnné uvedenou výchozí hodnotu. Pokud neexistuje žádná výchozí hodnota, je proměnná nastavena na prázdný řetězec.

assign(id, val)

class calibre.utils.formatter_functions.BuiltinAssign[zdroj]

assign(id, val) – přiřadí val k id, pak vrací val. id musí být identifikátor, ne výraz. Tato funkce může být často nahrazena operátorem =.

globals(id[=výraz] [, id[=výraz]]*)

class calibre.utils.formatter_functions.BuiltinGlobals[zdroj]

globals(id[=výraz] [, id[=výraz]]*) – načte „globální proměnné“, které lze předat do formátovače. Deklaruje a inicializuje místní proměnné s názvy předaných globálních proměnných. Pokud odpovídající proměnná není v předaných globálních uvedena, přiřadí dané proměnné uvedenou výchozí hodnotu. Pokud neexistuje žádná výchozí hodnota, je proměnná nastavena na prázdný řetězec.

print(a [, b]*)

class calibre.utils.formatter_functions.BuiltinPrint[zdroj]

print(a [, b]*) – vypíše parametry do standardního výstupu. Pokud nespustíte Calibre z příkazové řádky (calibre-debug -g), výstup půjde do černé díry.

Recursion

eval(template)

class calibre.utils.formatter_functions.BuiltinEval[zdroj]

eval(template) – vyhodnocuje šablonu, předává místní proměnné (ty ‚přiřazené‘) namísto metadat knihy. Toto umožňuje pomocí zpracování šablony vytvořit složité výsledky z místních proměnných. Protože znaky { a } jsou speciální, musíte použít [[ pro znak { a ]] pro znak }; jsou převedeny automaticky. Pamatujte také na to, že předpony a přípony (syntaxe |prefix|suffix) nemohou být použity v parametru této funkce při použití režimu šablony programu.

template(x)

class calibre.utils.formatter_functions.BuiltinTemplate[zdroj]

template(x) – vyhodnocuje x jako šablonu.Vyhodnocení se provádí ve vlastním kontextu, což znamená, že proměnné nejsou sdíleny mezi volajícím a vyhodnocením šablony. Protože znaky { a } jsou speciální, musíte použít [[ pro znak { a ]] pro znak }; jsou převedeny automaticky. Například template(‚[[title_sort]]‘) vyhodnotí šablonu {title_sort} a vrátí její hodnotu. Pamatujte také na to, že předpony a přípony (syntaxe |prefix|suffix) nemohou být použity v parametru této funkce při použití režimu šablony programu.

Relational

cmp(x, y, lt, eq, gt)

class calibre.utils.formatter_functions.BuiltinCmp[zdroj]

cmp(x, y, lt, eq, gt) – porovnává x a y po převedení obou na čísla. Vrací lt, když x < y. Vrací eq, když x == y. Jinak vrací gt. V mnoha případech mohou tuto funkci nahradit slovní operátory porovnání (>#, <#, ==# atd.).

first_matching_cmp(val, [cmp1, result1,]+, else_result)

class calibre.utils.formatter_functions.BuiltinFirstMatchingCmp[zdroj]

first_matching_cmp(val, [cmp1, result1,]+, else_result) – porovnává „val < cmpN“ v posloupnosti, vrací resultN pro první porovnání, které uspěje. Vrací else_result, když žádné porovnání neuspěje. Příklad: first_matching_cmp(10,5,“malý“,10,“střední“,15,“velký“,“obrovský“) vrací „velký“. Stejný příklad s první hodnotou 16 vrací „obrovský“.

strcmp(x, y, lt, eq, gt)

class calibre.utils.formatter_functions.BuiltinStrcmp[zdroj]

strcmp(x, y, lt, eq, gt) – dělá porovnání x a y jako řetězců bez rozlišení malých a velkých písmen. Vrací lt, když x < y. Vrací eq, když x == y. Jinak vrací gt. V mnoha případech mohou tuto funkci nahradit slovní operátory porovnání (>, <, == atd.).

strcmpcase(x, y, lt, eq, gt)

class calibre.utils.formatter_functions.BuiltinStrcmpcase[zdroj]

strcmpcase(x, y, lt, eq, gt) – porovná x a y jako řetězce s ohledem na velikost písmen. Vrací lt, pokud x < y. Vrací eq, pokud x == y. Jinak vrací gt.
Poznámka: Toto NENÍ výchozí chování, které Calibre používá například u operátorů lexikálního porovnávání (==, >, < atd.). Tato funkce by mohla způsobit neočekávané výsledky, raději používejte funkci strcmp(), kdykoliv je to možné.

String case changes

capitalize(hodnota)

class calibre.utils.formatter_functions.BuiltinCapitalize[zdroj]

capitalize(hodnota) – vrací hodnotu kapitálkami

lowercase(hodnota)

class calibre.utils.formatter_functions.BuiltinLowercase[zdroj]

lowercase(hodnota) – vrací hodnotu malými písmeny

titlecase(hodnota)

class calibre.utils.formatter_functions.BuiltinTitlecase[zdroj]

titlecase(hodnota) – vrací hodnotu se všemi prvními písmeny velkými

uppercase(hodnota)

class calibre.utils.formatter_functions.BuiltinUppercase[zdroj]

uppercase(hodnota) – vrací hodnotu velkými písmeny

String manipulation

character(character_name)

class calibre.utils.formatter_functions.BuiltinCharacter[zdroj]

character(character_name) – vrací znak pojmenovaný podle character_name. Například character(‚newline‘) vrací znak nového řádku (‘n‘). Podporované názvy znaků jsou ‚newline‘ (nový řádek), ‚return‘ (enter), ‚tab‘ (tabulátor) a ‚backslash‘ (zpětné lomítko).

re(hodnota, vzor, náhrada)

class calibre.utils.formatter_functions.BuiltinRe[zdroj]

re(hodnota, vzor, náhrada) – vrací pole po použití regulárního výrazu. Všechny instance vzor jsou nahrazeny náhradou. Jako v celém Calibre jsou toto regulární výrazy kompatibilní s Pythonem

re_group(val, pattern [, template_for_group]*)

class calibre.utils.formatter_functions.BuiltinReGroup[zdroj]

re_group(val, pattern [, template_for_group]*) – vrací řetězec vytvořený použitím vzoru regulárního výrazu (pattern) na hodnotu (val) a nahrazením každé odpovídající instance řetězcem vypočítaným nahrazením každé odpovídající skupiny hodnotou vrácenou odpovídající šablonou (tempůate_for_group_1). Původní odpovídající hodnota pro skupinu je dostupná jako $. V režimu programu šablony, jako pro šablonu a funkce eval, použijte [[ pro { a ]] pro }. Následující příklad v režimu programu šablony hledá série s více než jedním slovem a prvním slovem velkými písmeny: {series:‘re_group($, „(S* )(.*)“, „[[$:uppercase()]]“, „[[$]]“)‘}

shorten(hodnota, znaky vlevo, text uprostřed, znaky vpravo)

class calibre.utils.formatter_functions.BuiltinShorten[zdroj]

shorten(hodnota, znaky vlevo, text uprostřed, znaky vpravo) – Vrací zkrácenou verzi hodnoty, skládající se ze znaky vlevo znaků od začátku hodnoty, následované text uptostřed, následovné znaky vpravo znaků od konce řetězce. Znaky vlevo a znaky vpravo musí být celá čísla. Například předpokládejme, že název knihy je Život a doba soudce Roye Beana a chcete, aby se vešel do míst s nanejvýše 15 znaky. Pokud použijete {title:shorten(9,-,5)}, výsledek bude Život a d-Beana. Pokud je délka pole menší než znaky vlevo + znaky vpravo + délka text uprostřed, pak bude pole použito nezměněno. Například název Kopule by nebyl změněn.

strcat(a [, b]*)

class calibre.utils.formatter_functions.BuiltinStrcat[zdroj]

strcat(a [, b]*) – může brát jakýkoliv počet parametrů. Vrací řetězec vytvořený spojením všech parametrů

strcat_max(max, řetězec1 [, předpona2, řetězec2]*)

class calibre.utils.formatter_functions.BuiltinStrcatMax[zdroj]

strcat_max(max, řetězec1 [, předpona2, řetězec2]*) – Vrací řetězec vytvořený spojením parametrů. Vrácená hodnota je spuštěna na řetězec1. Páry předpona, řetězec jsou přidávány na konec hodnoty, dokud je výsledný řetězec kratší než max. Řetězec1 je vrácen, dokonce i když je řetězec1 delší než max. Můžete předat tolik párů předpona, řetězec, kolik si přejete.

strlen(a)

class calibre.utils.formatter_functions.BuiltinStrlen[zdroj]

strlen(a) – vrací délku řetězce předanou jako parametr

substr(řetězec, počátek, konec)

class calibre.utils.formatter_functions.BuiltinSubstr[zdroj]

substr(řetězec, počátek, konec) – vrací počáteční až koncové znaky řetězce. První znak v řetězci je nultý znak. Pokud je konec záporný, pak označuje, kolik znaků počítat zprava. Pokud je konec nula, pak označuje poslední znak. Například substr(‚12345‘, 1, 0) vrací ‚2345‘ a substr(‚12345‘, 1, -1) vrací ‚234‘.

swap_around_articles (hodnota, oddělovač)

class calibre.utils.formatter_functions.BuiltinSwapAroundArticles[zdroj]

swap_around_articles (hodnota, oddělovač) – Vrací hodnotu s články přesunutými na konec. Hodnota může být seznam, v takovém případě je zpracován každý člen seznamu. Pokud je hodnota seznam, pak musíte zadat oddělovač hodnot seznamu. Pokud není k dispozici žádný oddělovač, bude se s hodnotou zacházet jako s jedinou hodnotou, nikoliv jako se seznamem.

swap_around_comma(hodnota)

class calibre.utils.formatter_functions.BuiltinSwapAroundComma[zdroj]

swap_around_comma(hodnota) – zadává se hodnota v podobě „B, A“, vrací „A B“. Toto je nejužitečnější pro převod jmen z formátu „příjmení, jméno“ na „jméno příjmení“. Pokud zde není čárka, funkce vrací hodnotu nezměněnou.

to_hex(val)

class calibre.utils.formatter_functions.BuiltinToHex[zdroj]

to_hex(val) – vrací řetězec kódovaný v hexadecimálním tvaru. To je užitečné při vytváření adres URL Calibre.

transliterate(a)

class calibre.utils.formatter_functions.BuiltinTransliterate[zdroj]

transliterate(a) – Vrací řetězec v latince vytvořený přibližným odhadem zvuku slov ve zdrojovém řetězci. Například pokud je zdroj „Фёдор Миха́йлович Достоевский“, funkce vrací „Fiodor Mikhailovich Dostoievskii“.

Template database functions

book_count(dotaz, use_vl)

class calibre.utils.formatter_functions.BuiltinBookCount[zdroj]

book_count(dotaz, use_vl) – vrací počet knih nalezených při vyhledávání dotazu. Pokud je use_vl 0 (nula), pak jsou virtuální knihovny ignorovány. Tato funkce může být použita pouze v grafickém rozhraní.

book_values(sloupec, dotaz, oddělovač, use_vl)

class calibre.utils.formatter_functions.BuiltinBookValues[zdroj]

book_values(sloupec, dotaz, oddělovač, use_vl) – vrací seznam hodnot obsažených ve sloupci „sloupec“ oddělených znakem „oddělovač“ v knihách nalezených při vyhledání „doatz“. Pokud je use_vl 0 (nula), pak jsou virtuální knihovny ignorovány. Tato funkce může být použita pouze v grafickém rozhraní.

extra_file_modtime(file_name, format_string)

class calibre.utils.formatter_functions.BuiltinExtraFileModtime[zdroj]

extra_file_modtime(file_name, format_string) – returns the modification time of the extra file ‚file_name‘ in the book’s ‚data/‘ folder if it exists, otherwise -1.0. The modtime is formatted according to ‚format_string‘ (see format_date()). If ‚format_string‘ is empty, returns the modtime as the floating point number of seconds since the epoch. The epoch is OS dependent. This function can be used only in the GUI.

extra_file_names(oddělovač [, vzor])

class calibre.utils.formatter_functions.BuiltinExtraFileNames[zdroj]

extra_file_names(oddělovač [, vzor]) – vrací seznam dodatečných souborů oddělených oddělovačem ve složce ‚data/‘ knihy. Pokud je zadán volitelný parametr ‚vzor‘, regulární výraz, je seznam filtrován na soubory, které odpovídají vzoru. Shoda se vzorem nerozlišuje velká a malá písmena. Tato funkce může být použita pouze v grafickém rozhraní.

extra_file_size(název_souboru)

class calibre.utils.formatter_functions.BuiltinExtraFileSize[zdroj]

extra_file_size(název_souboru) – vrací velikost v bajtech dodatečného souboru ‚název_souboru‘ ve složce ‚data/‘ knihy, pokud existuje, jinak -1. Tato funkce může být použita pouze v grafickém rozhraní.

get_link(field_name, field_value)

class calibre.utils.formatter_functions.BuiltinGetLink[zdroj]

get_link(field_name, field_value) – fetch the link for field ‚field_name‘ with value ‚field_value‘. If there is no attached link, return ‚‘. Example: get_link(‚tags‘, ‚Fiction‘) returns the link attached to the tag ‚Fiction‘.

get_note(field_name, field_value, plain_text)

class calibre.utils.formatter_functions.BuiltinGetNote[zdroj]

get_note(field_name, field_value, plain_text) – fetch the note for field ‚field_name‘ with value ‚field_value‘. If ‚plain_text‘ is empty, return the note’s HTML. If ‚plain_text‘ is non-empty, return the note’s plain text. If the note doesn’t exist, return ‚‘ in both cases. Example: get_note(‚tags‘, ‚Fiction‘, ‚‘) returns the HTML of the note attached to the tag ‚Fiction‘.

has_extra_files([vzor])

class calibre.utils.formatter_functions.BuiltinHasExtraFiles[zdroj]

has_extra_files([vzor]) – vrací počet dodatečných souborů, jinak ‚‘ (prázdný řetězec). Pokud je zadán volitelný parametr ‚vzor‘ (regulární výraz), je seznam před spočítáním souborů filtrován na soubory, které odpovídají vzoru. Shoda se vzorem nerozlišuje velká a malá písmena. Tato funkce může být použita pouze v grafickém rozhraní.

has_note(field_name, field_value)

class calibre.utils.formatter_functions.BuiltinHasNote[zdroj]

has_note(field_name, field_value) – return ‚1‘ if the value ‚field_value‘ in the field ‚field_name‘ has an attached note, ‚‘ otherwise. Example: has_note(‚tags‘, ‚Fiction‘) returns ‚1‘ if the tag ‚fiction‘ has an attached note, ‚‘ otherwise.

other

set_globals(id[=výraz] [, id[=výraz]]*)

class calibre.utils.formatter_functions.BuiltinSetGlobals[zdroj]

set_globals(id[=výraz] [, id[=výraz]]*) – Nastaví „globální proměnné“, které mohou být předány formátovacímu modulu. Globální proměnné dostanou název předaného identifikátoru. Pokud není zadán výraz, použije se hodnota identifikátoru.

API of the Metadata objects

The python implementation of the template functions is passed in a Metadata object. Knowing it’s API is useful if you want to define your own template functions.

class calibre.ebooks.metadata.book.base.Metadata(title, authors=('Neznámý',), other=None, template_cache=None, formatter=None)[zdroj]

A class representing all the metadata for a book. The various standard metadata fields are available as attributes of this object. You can also stick arbitrary attributes onto this object.

Metadata from custom columns should be accessed via the get() method, passing in the lookup name for the column, for example: „#mytags“.

Use the is_null() method to test if a field is null.

This object also has functions to format fields into strings.

The list of standard metadata fields grows with time is in STANDARD_METADATA_FIELDS.

Please keep the method based API of this class to a minimum. Every method becomes a reserved field name.

is_null(field)[zdroj]

Return True if the value of field is null in this object.
‚null‘ means it is unknown or evaluates to False. So a title of
_(‚Unknown‘) is null or a language of ‚und‘ is null.

Be careful with numeric fields since this will return True for zero as well as None.

Also returns True if the field does not exist.

deepcopy(class_generator=<function Metadata.<lambda>>)[zdroj]

Do not use this method unless you know what you are doing, if you want to create a simple clone of this object, use deepcopy_metadata() instead. Class_generator must be a function that returns an instance of Metadata or a subclass of it.

get_identifiers()[zdroj]

Return a copy of the identifiers dictionary.
The dict is small, and the penalty for using a reference where a copy is needed is large. Also, we don’t want any manipulations of the returned dict to show up in the book.

set_identifiers(identifiers)[zdroj]

Set all identifiers. Note that if you previously set ISBN, calling this method will delete it.

set_identifier(typ, val)[zdroj]

If val is empty, deletes identifier of type typ

standard_field_keys()[zdroj]

return a list of all possible keys, even if this book doesn’t have them

custom_field_keys()[zdroj]

return a list of the custom fields in this book

all_field_keys()[zdroj]

All field keys known by this instance, even if their value is None

metadata_for_field(key)[zdroj]

return metadata describing a standard or custom field.

all_non_none_fields()[zdroj]

Return a dictionary containing all non-None metadata fields, including the custom ones.

get_standard_metadata(field, make_copy)[zdroj]

return field metadata from the field if it is there. Otherwise return
None. field is the key name, not the label. Return a copy if requested, just in case the user wants to change values in the dict.

get_all_standard_metadata(make_copy)[zdroj]

return a dict containing all the standard field metadata associated with the book.

get_all_user_metadata(make_copy)[zdroj]

return a dict containing all the custom field metadata associated with the book.

get_user_metadata(field, make_copy)[zdroj]

return field metadata from the object if it is there. Otherwise return
None. field is the key name, not the label. Return a copy if requested, just in case the user wants to change values in the dict.

set_all_user_metadata(metadata)[zdroj]

store custom field metadata into the object. Field is the key name not the label

set_user_metadata(field, metadata)[zdroj]

store custom field metadata for one column into the object. Field is the key name not the label

remove_stale_user_metadata(other_mi)[zdroj]

Remove user metadata keys (custom column keys) if they don’t exist in ‚other_mi‘, which must be a metadata object

template_to_attribute(other, ops)[zdroj]

Takes a list [(src,dest), (src,dest)], evaluates the template in the context of other, then copies the result to self[dest]. This is on a best-efforts basis. Some assignments can make no sense.

smart_update(other, replace_metadata=False)[zdroj]

Merge the information in other into self. In case of conflicts, the information in other takes precedence, unless the information in other is NULL.

format_field(key, series_with_index=True)[zdroj]

Returns the tuple (display_name, formatted_value)

to_html()[zdroj]

A HTML representation of this object.

calibre.ebooks.metadata.book.base.STANDARD_METADATA_FIELDS

The set of standard metadata fields.

'''
All fields must have a NULL value represented as None for simple types, an empty list/dictionary for complex types and (None, None) for cover_data
'''

SOCIAL_METADATA_FIELDS = frozenset((
'tags', # Ordered list
'rating', # A floating point number between 0 and 10
'comments', # A simple HTML enabled string
'series', # A simple string
'series_index', # A floating point number
# Of the form { scheme1:value1, scheme2:value2}
# For example: {'isbn':'123456789', 'doi':'xxxx', ... }
'identifiers',
))

'''
The list of names that convert to identifiers when in get and set.
'''

TOP_LEVEL_IDENTIFIERS = frozenset((
'isbn',
))

PUBLICATION_METADATA_FIELDS = frozenset((
'title', # title must never be None. Should be _('Unknown')
# Pseudo field that can be set, but if not set is auto generated
# from title and languages
'title_sort',
'authors', # Ordered list. Must never be None, can be [_('Unknown')]
'author_sort_map', # Map of sort strings for each author
# Pseudo field that can be set, but if not set is auto generated
# from authors and languages
'author_sort',
'book_producer',
'timestamp', # Dates and times must be timezone aware
'pubdate',
'last_modified',
'rights',
# So far only known publication type is periodical:calibre
# If None, means book
'publication_type',
'uuid', # A UUID usually of type 4
'languages', # ordered list of languages in this publication
'publisher', # Simple string, no special semantics
# Absolute path to image file encoded in filesystem_encoding
'cover',
# Of the form (format, data) where format is, e.g. 'jpeg', 'png', 'gif'...
'cover_data',
# Either thumbnail data, or an object with the attribute
# image_path which is the path to an image file, encoded
# in filesystem_encoding
'thumbnail',
))

BOOK_STRUCTURE_FIELDS = frozenset((
# These are used by code, Null values are None.
'toc', 'spine', 'guide', 'manifest',
))

USER_METADATA_FIELDS = frozenset((
# A dict of dicts similar to field_metadata. Each field description dict
# also contains a value field with the key #value#.
'user_metadata',
))

DEVICE_METADATA_FIELDS = frozenset((
'device_collections', # Ordered list of strings
'lpath', # Unicode, / separated
'size', # In bytes
'mime', # Mimetype of the book file being represented
))

CALIBRE_METADATA_FIELDS = frozenset((
'application_id', # An application id, currently set to the db_id.
'db_id', # the calibre primary key of the item.
'formats', # list of formats (extensions) for this book
# a dict of user category names, where the value is a list of item names
# from the book that are in that category
'user_categories',
# a dict of items to associated hyperlink
'link_maps',
))

ALL_METADATA_FIELDS = SOCIAL_METADATA_FIELDS.union(
PUBLICATION_METADATA_FIELDS).union(
BOOK_STRUCTURE_FIELDS).union(
USER_METADATA_FIELDS).union(
DEVICE_METADATA_FIELDS).union(
CALIBRE_METADATA_FIELDS)

# All fields except custom fields
STANDARD_METADATA_FIELDS = SOCIAL_METADATA_FIELDS.union(
PUBLICATION_METADATA_FIELDS).union(
BOOK_STRUCTURE_FIELDS).union(
DEVICE_METADATA_FIELDS).union(
CALIBRE_METADATA_FIELDS)

# Metadata fields that smart update must do special processing to copy.
SC_FIELDS_NOT_COPIED = frozenset(('title', 'title_sort', 'authors',
'author_sort', 'author_sort_map',
'cover_data', 'tags', 'languages',
'identifiers'))

# Metadata fields that smart update should copy only if the source is not None
SC_FIELDS_COPY_NOT_NULL = frozenset(('device_collections', 'lpath', 'size', 'comments', 'thumbnail'))

# Metadata fields that smart update should copy without special handling
SC_COPYABLE_FIELDS = SOCIAL_METADATA_FIELDS.union(
PUBLICATION_METADATA_FIELDS).union(
BOOK_STRUCTURE_FIELDS).union(
DEVICE_METADATA_FIELDS).union(
CALIBRE_METADATA_FIELDS) - \ SC_FIELDS_NOT_COPIED.union(
SC_FIELDS_COPY_NOT_NULL)

SERIALIZABLE_FIELDS = SOCIAL_METADATA_FIELDS.union(
USER_METADATA_FIELDS).union(
PUBLICATION_METADATA_FIELDS).union(
CALIBRE_METADATA_FIELDS).union(
DEVICE_METADATA_FIELDS) - \ frozenset(('device_collections', 'formats',
'cover_data'))
# these are rebuilt when needed

© Copyright Kovid Goyal.
Naposledy aktualizováno kvě 03, 2024.

Navigace

- moduly
- další |
- předchozí |
- Spustit »
- Kurzy »

- Vše o používání regulárních výrazů v Calibre

Vše o používání regulárních výrazů v Calibre

Regulární výrazy jsou funkce používané na mnoha místech v Calibre pro provádění propracovaného zpracování obsahu e-knihy a metadat. Tento kurz je mírný úvod do problematiky používání regulárních výrazů v Calibre.

Obsah

- Nejdříve malé varování a malé povzbuzení

- Kde v Calibre můžete použít regulární výrazy?

- Co to u všech všudy je regulární příkaz?

- Zajímá vás vysvětlení?

- To nezní tak špatně. Co dál?

- Parádní! Začíná to dávat smysl!

- No, tyto speciální znaky jsou velice elegantní a tak, ale co když chci porovnat tečku nebo otazník?

- Takže, jaké jsou nejužitečnější sady?

- Ale když budu mít několik různých řetězců, které chci porovnat, zkomplikuje se to?

- Zapomněl jsi…

- Na začátku jsi říkal, že existuje způsob, aby regulární výraz nerozlišoval malá a velká písmena?

- Myslím, že teď už začínám ty regulární výrazy chápat… Jak je použiju v Calibre?

- Převody

- Přidávání knih

- Hromadná úprava metadat

- Rychlý odkaz

- Poděkování

Nejdříve malé varování a malé povzbuzení

Toto bude nevyhnutelně poněkud technické – ostatně regulární výrazy jsou technickým nástrojem pro to technické záležitosti. Budu muset použít nějaký žargon a pojmy, které se mohou zdát složité a spletité. Budu se snažit vysvětlit tyto pojmy co nejjasněji, ale opravdu není možné obejít se bez jejich použití. Jak již bylo řečeno, nenechte se odradit žádným žargonem, protože se snažím vysvětlit všechno nové. A zatímco samotné regulární výrazy vám můžou připadat jako tajemná, černé magie (nebo, abychom byli prozaičtější, náhodný řetězec nesmyslných písmen a znaků), slibuji, že nejsou vůbec tak složité. Dokonce i ti, kteří pochopili regulární výrazy opravdu dobře, mají problémy se čtením těch složitějších, ale jejich psaní není tak obtížně – vytváříte výraz krok za krokem. Takže vykročte a následujte mě do králičí nory.

Kde v Calibre můžete použít regulární výrazy?

Existuje několik míst, kde Calibre používá regulární výrazy. Je zde Najít a nahradit ve volbách převodu, detekce metadat z názvů souborů v nastavení importu a Najít a nahradit při hromadné úpravě metadat knih. Editor knih Calibre může také používat regulární výrazy ve své funkci Najít a nahradit. A nakonec můžete regulární výrazy použít při prohledávání seznamu knih Calibre a při vyhledávání v Prohlížeči e-knih Calibre.

Co to u všech všudy je regulární příkaz?

Regulární výraz je způsob, jak popsat sady řetězců. Jeden regulární výraz může odpovídat velkému počtu různých řetězců. Proto jsou regulární výrazy tak mocné – jsou stručným způsobem popisu potenciálně velkého počtu variací.

Poznámka

Řetězec zde používám ve smyslu, v jakém se používá v programovacích jazycích: řetězec jednoho nebo více znaků, znaků včetně skutečných znaků, čísel, interpunkčních znamének a takzvaných prázdných znaků (konce řádků, tabulátorů atd). Pamatujte, že velká a malá písmena nejsou obecně považována za stejná, tedy „a“ je jiný znak než „A“ a tak dále. V Calibre regulární výrazy rozlišují velikost písmen v Panelu hledání, ale ne ve volbách převodu. Existuje způsob, aby každý regulární výraz rozlišoval velikost písmen, ale to probereme později. Trochu se to komplikuje, protože regulární výrazy umožňují variace v řetězcích, se kterými se porovnávají, takže jeden výraz může odpovídat více řetězcům, což je důvod, proč se je lidé vůbec obtěžují používat. Více si o tom povíme za chvíli.

Zajímá vás vysvětlení?

No, proto jsme tady. Za prvé, toto je nejdůležitější koncept v regulárních výrazech: Řetězec sám o sobě je regulární výraz, který odpovídá sobě samému. To znamená, že když chci pomocí regulárního výrazu porovnat řetězec "Hello, World!", použitý regulární výraz by byl Hello, World!. Ano, opravdu je to tak jednoduché. Ale všimněte si, že toto odpovídá pouze přesnému řetězci "Hello, World!", ne například "Hello, wOrld!" nebo "hello, world!" nebo jakékoliv jiné takové variaci.

To nezní tak špatně. Co dál?

Dál začínají opravdu dobré věci. Pamatujete si, jak jsem říkal, že regulární výrazy mohou odpovídat více řetězcům? Teď to začne být trochu složitější. Řekněme, jako poněkud praktičtější cvičení, že e-kniha, kterou chcete převést, má ošklivé zápatí počítající stránky, jako „Strana 5 z 423“. Je zřejmé, že číslo stránky bude růst od 1 do 423, takže budete muset porovnat 423 různých řetězců, že? Špatně, ve skutečnosti: regulární výrazy vám umožňují definovat sady znaků, které jsou porovnávány. Abyste definovali sadu, dejte všechny znaky, které chcete mít v sadě, do hranatých závorek. Takže například sada [abc] by odpovídala buď znaku „a“, „b“ nebo „c“. Sady budou vždy porovnávat pouze jeden ze znaků v sadě. „Rozumějí“ rozsahům znaků, takže pokud chcete porovnat všechna malá písmena, použili byste sadu [a-z], pro malá a velká písmena byste použili [a-zA-Z] a tak dále. Chápete to? Takže samozřejmě pomocí výrazu Strana [0-9] z 423 byste byli schopni porovnat prvních 9 stránek, čímž se sníží počet potřebných výrazů na tři: Druhý výraz Strana [0-9][0-9] z 423 by porovnal všechna dvojciferná čísla stránek, a jsem si jistý, že umíte odhadnout, jak by vypadal třetí výraz. Ano, do toho. Napište ho.

Parádní! Začíná to dávat smysl!

Doufal jsem, že to řeknete. Ale připravte se, teď to bude ještě lepší! Právě jsme viděli, že pomocí sad jsme mohli porovnat jeden z několika znaků najednou. Ale můžete dokonce opakovat znak nebo sadu, čímž snížíte počet výrazů potřebných pro zpracování výše uvedeného příkladu s čísly stránek na jeden. Ano, JEDEN! Jste nadšení? Měli byste být! Funguje to takhle: Některé takzvané speciální znaky, „+“, „?“ a „*“, opakují jednotlivý předcházející prvek. (Prvek znamená buď jeden znak, sadu znaků, řídící sekvenci nebo skupinu (o těch posledních dvou se budeme učit později.) – zkrátka každý jednotlivý prvek v regulárním výrazu). Tyto znaky se nazývají zástupné znaky nebo kvantifikátory. Abychom byli přesnější, „?“ odpovídá 0 nebo 1 výskytů předcházejícího prvku, „*“ odpovídá 0 nebo více výskytů předcházejícího prvku a „+“ odpovídá 1 nebo více výskytů předcházejícího prvku. Několik příkladů: Výraz a? by odpovídal buď „“ (což je prázdný řetězec, není nezbytně užitečný v tomto případě), nebo „a“, výraz a* by odpovídal „“, „a“, „aa“ nebo libovolnému počtu a v řadě, a konečně výraz a+ by odpovídal „a“, „aa“ nebo libovolnému počtu a v řadě (Poznámka: neodpovídal by prázdnému řetězci!). To samé pro sady: Výraz [0-9]+ by odpovídal každému existujícímu celému číslu! Vím, co si myslíte, a máte pravdu: Když toto použijete ve výše uvedeném případu porovnávání čísel stránek, nebyl by to jediný výraz pro porovnání všech čísel stránek? Ano, výraz Strana [0-9]+ z 423 by porovnal každé číslo stránky v této knize!

Poznámka

Poznámka o těchto kvantifikátorech: Obvykle se snaží porovnat tolik textu, kolik je možné, takže buďte opatrní při jejich použití. Nazývá se to „chamtivý chování“ – jsem si jistý, že chápete proč. Je to komplikovanější, když, řekněme, chcete porovnat značku. Vezměme například řetězec "<p class="calibre2">Tady je nadpis</p>" a řekněme, že byste chtěli porovnat otevírací značku (část mezi první dvojicí hranatých závorek, značky probereme trochu více později). Mysleli byste si, že by této značce odpovídal výraz <p.*>, ale ve skutečnosti odpovídá celému řetězci! (Znak „.“ je další speciální znak. Odpovídá čemukoliv kromě koncům řádků, takže v podstatě výraz .*` by odpovídal každičkému řádku, který si dokážete představit.) Místo toho zkuste použít ``<p.*?>, čímž přestane být kvantifikátor "*" chamtivým. Tento výraz bude odpovídat pouze první otevírací značce, jak bylo zamýšleno. Existuje vlastně další způsob, jak toho dosáhnout: Výraz <p[^>]*> bude odpovídat té samé otevírací značce – po další části uvidíte proč. Jen vezměte na vědomí, že dost často existuje více než jeden způsob, jak napsat regulární výraz.

No, tyto speciální znaky jsou velice elegantní a tak, ale co když chci porovnat tečku nebo otazník?

Samozřejmě to můžete udělat: Stačí před jakýkoliv speciální znak vložit zpětné lomítko a je interpretován jako prostý znak bez jakéhokoliv speciálního významu. Tento pár zpětného lomítka následovaného jedním znakem se nazývá řídící sekvence, a vložení zpětného lomítka před speciální znak se nazývá uvozování toho znaku. Řídící sekvence je interpretována jako jeden prvek. Existují samozřejmě řídící sekvence, které dělají více než jen uvozování speciálních znaků, například "\t" znamená tabulátor. K některým řídícím sekvencím se dostaneme později. Jo a mimochodem, pokud jde o tyto speciální znaky: Uvažujte o kterémkoliv znaku, o kterém se budeme v tomto úvodu bavit, že má nějakou funkci, která je speciální, a tedy musí být uvozen, pokud chcete prostý znak.

Takže, jaké jsou nejužitečnější sady?

Věděl jsem, že se zeptáte. Některé užitečné sady jsou [0-9] odpovídající jednomu číslu, [a-z] odpovídající jednomu malému písmenu, [A-Z] odpovídající jednomu velkému písmenu, [a-zA-Z] odpovídající jednomu písmenu a [a-zA-Z0-9] odpovídající jednomu písmenu nebo číslu. Můžete také použít řídící sekvenci jako zjednodušení:

\d

je ekvivalentem k [0-9]

\w

je ekvivalentem k [a-zA-Z0-9_]

\s

je ekvivalentem k jakémukoliv prázdnému znaku

Poznámka

„Prázdný znak“ je termín pro cokoliv, co nebude vytištěno. Tyto znaky zahrnují mezeru, tabulátor, posun řádku, posun strany, návrat na začátek řádku, pevné mezery atd.

Poznámka

Sady velkých a malých písmen mohou odpovídat jak velkým, tak i malým písmenům, pokud je povoleno nastavení pro vyhledávání bez rozlišování velkých a malých písmen. Taková nastavení lze nalézt například v Předvolby->Vyhledávání v samotném Calibre a na panelu Vyhledávání v nástroji Calibre Prohlížeč e-knih a také v nástroji Calibre Upravit knihu.

Jako poslední poznámka o sadách, sadu můžete také definovat jako jakýkoliv znak kromě těch v sadě. Uděláte to zahrnutím znaku "^" jako úplně prvního znaku v sadě. Takže [^a] by odpovídalo libovolnému znaku kromě „a“. Tomu se říká doplňující sada. Zkratky řídící sekvence, které jsme viděli dříve, mohou být také doplněny: "\D" znamená jakýkoli nečíselný znak, takže je ekvivalentem [^0-9]. Jak už jste asi uhodli, ostatní zkratky mohou být doplněny pomocí příslušného velkého písmena namísto malého. Takže když se vrátíme k příkladu <p[^>]*> z předchozí části, můžete nyní vidět, že použitá sada znaků se pokusí porovnat jakýkoliv znak kromě uzavírací úhlové závorky.

Ale když budu mít několik různých řetězců, které chci porovnat, zkomplikuje se to?

Nebojte se, život je pořád jednoduchý. Vezměme si tento příklad: Kniha, kterou převádíte má napsán „Název“ na každé liché stránce a „Autor“ na každé sudé stránce. V tisku to vypadá skvěle, že jo? Ale v e-knize je to otravné. Můžete seskupit celé výrazy do normálních závorek a znak "|" vám umožní porovnat buď výraz s jeho pravou nebo levou stranou. Toto zkombinujte a máte hotovo. Je to na vás příliš? Dobře, nejdříve seskupíme výrazy pro sudé a liché stránky, takže získáme (Název)(Autor) jako naše dva potřebné výrazy. Nyní věci zjednodušíme pomocí svislé čáry ho pruhu (znak "|" se nazývá svislá čára): Pokud použijete výraz (Název|Autor), dostanete shodu buď pro „Název“ (na lichých stránkách), nebo pro „Autor“ (na sudých stránkách). No, nebylo to jednoduché?

Svislou čáru můžete samozřejmě použít také bez použití seskupovacích závorek. Pamatujete, když jsem říkal, že kvantifikátory opakují jim předcházející prvek? No, svislá čára funguje trochu jinak: Výraz „Název|Autor“ bude také odpovídat buď řetězci „Název“ nebo řetězci „Autor“, stejně jako výše uvedený příklad používající seskupování. Svislá čára vybírá mezi celým výrazem, který ji předchází a následuje. Takže pokud byste chtěli porovnat řetězce „Calibre“ a „calibre“ a chtěli byste vybrat pouze mezi malým a velkým „c“, museli byste použít výraz (c|C)alibre, kde seskupení zajišťuje, že bude vybráno pouze „c“. Pokud byste použili c|Calibre, získali byste shodu na řetězec „c“ nebo na řetězec „Calibre“, což není to, co jsme chtěli. Stručně řečeno: Pokud si nejste jisti, použijte seskupování společně se svislou čárou.

Zapomněl jsi…

… počkejte chvilku, je tu ještě jedna poslední, opravdu užitečná věc, kterou můžete dělat se skupinami. Pokud máte skupinu, kterou jste předtím porovnal, můžete použít odkazy na tuto skupinu později ve výrazu: Skupiny jsou číslovány od 1, a odkazujete na ně uvozením čísla skupiny, na kterou chcete odkázat, tedy pátá skupina by byla odkázána \5. Takže pokud jste hledali ([^ ]+) \1 v řetězci „Test Test“, porovnali byste celý řetězec!

Na začátku jsi říkal, že existuje způsob, aby regulární výraz nerozlišoval malá a velká písmena?

Ano, říkal, díky za věnování pozornosti a připomenutí. Můžete říct Calibre, jak chcete řešit některé věci pomocí tzv. vlajek. Vlajky zahrnete do svého výrazu pomocí speciální konstrukce (?sem přijdou vlajky), kde byste samozřejmě nahradili „sem přijdou vlajky“ konkrétními vlajkami, které chcete. Pro ignorování velikosti písmen je vlajka i, takže zahrnete do svého výrazu (?i). Takže (?i)test by odpovídal „Test“, „tEst“, „TEst“ a jakémukoliv případu variace si dokážete vymyslet.

Další užitečná vlajka umožňuje, aby tečka odpovídala všem libovolným znakům, včetně nového řádku, a to vlajka s. Pokud chcete použít ve výrazu více vlajek, stačí je dát do stejného příkazu: (?is) by ignorovalo velikost písmen a tečka by odpovídala všemu. Nezáleží na tom, kterou vlajkou uvedete první, (?si) by bylo ekvivalentem výše uvedeného.

Myslím, že teď už začínám ty regulární výrazy chápat… Jak je použiju v Calibre?

Převody

Začněme s nastavením převodu, což je opravdu elegantní. Do části Hledat a nahradit můžete zadat regulární výraz, který popisuje řetězec, který bude při převodu nahrazen. Elegantní na tom je průvodce. Klikněte na kouzelnickou hůlku a získáte náhled toho, co Calibre „vidí“ během procesu převodu. Přejděte dolů na řetězec, který chcete odebrat, vyberte ho a zkopírujte, vložte ho do pole regulárního výrazu v horní části okna. Pokud jsou zde proměnné části, jako jsou čísla stránek nebo tak, použijte pro jejich pokrytí sady a kvantifikátory, a když už jsme u toho, nezapomeňte uvodit speciální znaky, pokud tu nějaké jsou. Stiskněte tlačítko označené Testovat a Calibre zvýrazní části, které budou nahrazeny, pokud použijete regulární výraz. Jakmile jste spokojeni, klikněte na tlačítko OK a proveďte převod. Buďte opatrní, pokud váš zdroj převodu obsahuje značky, jako tento příklad:

Maybe, but the cops feel like you do, Anita. What's one more dead vampire?
New laws don't change that. </p>
<p class="calibre4"> <b class="calibre2">Generated by ABC Amber LIT Conv
<a href="http://www.processtext.com/abclit.html" class="calibre3">erter,
http://www.processtext.com/abclit.html</a></b></p>
<p class="calibre4"> It had only been two years since Addison v. Clark.
The court case gave us a revised version of what life was

(nestydatě vytrženo z tohoto vlákna [https://www.mobileread.com/forums/showthread.php?t=75594"]). Museli byste také odstranit některé značky. V tomto příkladu bych doporučoval začít značkou <b class="calibre2">, nyní budete muset skončit odpovídající uzavírací značkou (otevírací značky jsou <značka>, uzavírací značky jsou </značka>), což je v tomto případě jednoduše následující </b>. (Podívejte se do dobré příručky k HTML nebo se zeptejte ve fóru, pokud si v tomto bodě nejste jisti.) Počáteční značku lze popsat pomocí <b.*?>, uzavírací značku pomocí </b>, takže bychom mohli odstranit vše mezi těmito značkami pomocí <b.*?>.*?</b>. Ale použití tohoto výrazu by byl špatný nápad, protože odstraňuje vše, co je uzavřeno značkami <b> (který mimochodem vykreslí uzavřený text tučným písmem), a vsaďte se, že tímto způsobem odstraníme části knihy. Místo toho zahrňte i začátek uzavřeného řetězce, takže regulární výraz bude <b.*?>\s*Generated\s+by\s+ABC\s+Amber\s+LIT.*?</b>. \s s kvantifikátory jsou zde zahrnuty namísto výslovného použití mezer, jak je vidět v řetězci, aby byly zachyceny všechny variace řetězce, které by mohly nastat. Nezapomeňte zkontrolovat, co Calibre odstraní, když testuje nový výraz, abyste se ujistili, že neodstraníte žádné části, které chcete zachovat. Pokud zkontrolujete pouze jeden výskyt, může vám uniknout neshoda někde jinde v textu. Také pamatujte, že kdybyste omylem odstranili více nebo méně značek, než jste doopravdy chtěli, Calibre se pokusí opravit poškozený kód po provedení odstranění.

Přidávání knih

Další věc, ke které můžete použít regulární výrazy, je extrahování metadat z názvů souborů. Tuto funkci můžete najít v části nastavení „Přidávání knih“. Je zde speciální funkce: Můžete použít názvy polí pro pole metadat, například (?P<title&gt;) by znamenalo, že Calibre použije tuto část řetězce jako název knihy. Povolené názvy polí jsou uvedeny v oknech, společně s dalším pěkným testovacím polem. Příklad: Řekněme, že chcete importovat spoustu souborů s názvem jako je Klasické texty: Božská komedie, Dante Alighieri.mobi. (Samozřejmě, že tu už ve své knihovně máte, protože všichni milujeme klasickou italskou poezii) nebo Sci-fi eposy: Trilogie Nadace, Isaac Asimov.epub. To je samozřejmě schéma názvů, ze kterého by Calibre nevyextrahovalo žádná smysluplná data – standardní výraz pro extrahování metadat je (?P<title>.+) - (?P,author>[^_]+). Regulární výraz, který by tu fungoval, by byl [a-zA-Z]+: (?P<title>.+), (?P<author>.+). Pamatujte, že uvnitř skupiny pro pole metadat musíte použít výrazy k popisu toho, čemu pole skutečně odpovídá. A také pamatujte, že při použití testovacího pole, které Calibre poskytuje, musíte přidat příponu souboru k testovanému souboru, jinak nezískáte vůbec žádnou shodu, i když použijete fungující výraz.

Hromadná úprava metadat

Poslední část je Hledat a nahradit v polích metadat pomocí regulárního výrazu. K tomuto se můžete dostat výběr více knih v knihovně a použitím hromadné úpravy metadat. Buďte velmi opatrní při používání této poslední funkce, protože může způsobit velice špatné věci vaší knihovně! Překontrolujte, že vaše výrazy dělají to, co chcete, pomocí testovacích polí, a označte pouze knihy, které opravdu chcete změnit! V režimu hledání regulárními výrazy můžete hledat v jednom poli, nahradit text něčím a dokonce zapsat výsledek do jiného pole. Praktický příklad: Řekněme, že vaše knihovna obsahuje knihy série Duna Franka Herberta pojmenované po vzoru Duna 1 - Duna, Duna 2 - Spasitel Duny a tak dále. Nyní chcete dostat Duna do pole série. Můžete to udělat hledáním (.*?) \d+ - .* v poli názvu a nahrazením \1 v poli série. Vidíte, co jsem udělal? To je odkaz na první skupinu, kterou nahrazujete pole série. Nyní, když máte sérii nastavenou, potřebujete jen provést další vyhledávání .*? - v poli názvu a nahrazením "" (prázdný řetězec) opět v poli názvu, a vaše metadata jsou jsou pěkně čistá a uklizená. Není to skvělé? Mimochodem, namísto nahrazení celého pole můžete také poli připojit nebo předřadit, takže pokud chcete, aby byla názvu knihy předřazena informace o sérii, mohli byste to udělat stejně. Jak jste si už nepochybně všimli, je zde zaškrtávací políčko označené Rozlišovat malá a velká, takže zde nebudete muset používat vlajky pro výběr chování.

No, je asi na čase uzavřít velice krátký úvod do regulárních výrazů. Doufám, že jsem vám ukázal dost, abyste alespoň mohli začít a umožnil vám pokračovat v samostatném učení – dobrým výchozím bodem je Dokumentace Pythonu pro regulární výrazy [https://docs.python.org/library/re.html].

Ale ještě jedna poslední varování: Regulární výrazy jsou výkonné, ale také je velice snadné je pokazit. Calibre poskytuje opravdu skvělé možnosti pro testování, abyste viděli, jestli se vaše výrazy chovat podle očekávání. Používejte je. Pokuste se nestřelit se do nohy. (Bože, tenhle výraz miluju…). Ale kdybyste si i přes varování poranili nohu (nebo jiné části těla), pokuste se z toho poučit.

Rychlý odkaz

- Základy vyhledávání prostřednictvím regulárních výrazů

- Třídy znaků
- Zjednodušené třídy znaků
- Kvantifikátory
- Nenasytnost
- Alternace
- Vyloučení
- Kotvy
- Skupiny
- Prohledávání
- Rekurze
- Speciální znaky
- Metaznaky
- Režimy

Poděkování

Díky za pomoc s tipy, opravami a tak:

- ldolse

- kovidgoyal

- chaley

- dwanthny

- kacir

- Starson17

- Orpheu

Více informací o regulárních výrazech naleznete v Uživatelské příručce Pythonu [https://docs.python.org/library/re.html]. Skutečná knihovna regulárních výrazů používaná Calibre je: regex [https://bitbucket.org/mrabarnett/mrab-regex/src/hg/], která podporuje několik užitečných vylepšení oproti standardní knihovně Pythonu.

© Copyright Kovid Goyal.
Naposledy aktualizováno kvě 03, 2024.

Navigace

- moduly
- další |
- předchozí |
- Spustit »
- Kurzy »

- Vše o používání regulárních výrazů v Calibre »

- Základy vyhledávání prostřednictvím regulárních výrazů

Základy vyhledávání prostřednictvím regulárních výrazů

Tento kontrolní seznam shrnuje nejčastěji používané nebo těžko zapamatovatelné části modulu regulárních výrazů, které jsou k dostupné ve většině součástí Calibre.

Obsah

- Třídy znaků

- Zjednodušené třídy znaků

- Kvantifikátory

- Nenasytnost

- Alternace

- Vyloučení

- Kotvy

- Skupiny

- Prohledávání

- Rekurze

- Speciální znaky

- Metaznaky

- Režimy

Třídy znaků

Stručně řečeno jsou třídy znaků užitečné pro zastupování různých skupin znaků.

Příklady:

Zastoupení

Třída

[a-z]

Malá písmena. Nezahrnuje znaky s diakritikou a ligatury

[a-z0-9]

Malá písmena od a do z nebo čísla od 0 do 9

[A-Za-z-]

Velká nebo malá písmena nebo pomlčka. Pro zahrnutí pomlčky do třídy ji musíte umístit na začátek nebo na konec, aby se nepletla se spojovníkem, který určuje rozsah znaků.

[^0-9]

Libovolný znak kromě číslice. Stříška (^) umístěná na začátku třídy vylučuje znaky ze třídy (doplňková třída)

[[a-z]--[aeiouy]]

Malá písmena souhlásek. Třída může být zahrnuta do třídy. Znaky -- vylučují to, co následuje

[\w--[\d_]]

Všechna písmena (včetně cizích znaků s diakritikou). Uvnitř třídy lze použít zkrácené třídy

Příklad:

<[^<>]+> to select an HTML tag

Zjednodušené třídy znaků

Zastoupení

Třída

\d

Číslo (stejné jako [0-9])

\D

Libovolný nečíselný znak (stejné jako [^0-9])

\w

Alfanumerický znak ([a-zA-Z0-9]) včetně znaků s diakritikou a ligatur

\W

Libovolný znak, který není „slovo“

\s

Mezera, pevná mezera, tabulátor, enter.

\S

Libovolný znak, který není „prázdný znak“

.

Jakýkoliv znak kromě nového řádku. Použijte zaškrtávací políčko „vytečkovat vše“ nebo modifikátor regulárních výrazů (?s) pro zahrnutí nového řádku.

Kvantifikátory

Kvantifikátor

Počet výskytů výrazů předcházejících kvantifikátor

?

0 nebo 1 výskytů výrazu. Stejné jako {0,1}

+

1 nebo více výskytů výrazu. Stejné jako {1,}

*

0, 1 nebo více výskytů výrazu. Stejné jako {0,}

{n}

Přesně n výskytů výrazu

{min,max}

Počet výskytů mezi minimální a maximální hodnotou včetně

{min,}

Počet výskytů mezi minimální hodnotou včetně a nekonečnem

{,max}

Počet výskytů mezi 0 a maximální hodnotou včetně

Nenasytnost

Ve výchozím nastavení je modul regulárního výrazu s kvantifikátory nenasytný: rozšiřuje výběr co nejvíce. To zpočátku často způsobuje překvapení. ? následovaný kvantifikátorem je lenost. Vyhněte se dávání dvou do stejného výrazu, výsledek může být nepředvídatelný.

Dejte si pozor na vnořené kvantifikátory, například vzor (a*)*, protože exponenciálně zvyšuje dobu zpracování.

Alternace

Znak | v regulárním výrazu je logické OR. To znamená, že se může shodovat buď předcházející nebo následující výraz.

Vyloučení

Metoda 1
vzor_k_vyloučení(*SKIP)(*FAIL)|vzor_k_vybrání

Příklad:

"Blabla"(*SKIP)(*FAIL)|Blabla
vybere Blabla v řetězcích Blabla nebo „Blabla nebo Blabla“, ale ne v „Blabla“.

Metoda 2
vzor_k_vyloučení\K|(vzor_k_vybrání)

"Blabla"\K|(Blabla)

vybere Blabla v řetězcích Blabla nebo „Blabla nebo Blabla“, ale ne v „Blabla“.

Kotvy

Kotva je způsob, jak porovnat logické umístění v řetězci, spíše než znaku. Nejužitečnější kotvy pro zpracování textu jsou:

\b

Určuje hranici slova, tj. přechod z mezery na nemezerový znak. Například můžete použít \bdar, aby odpovídal mám dar, ale ne nezdar.

^

Odpovídá začátku řádku (v režimu více řádků, což je výchozí nastavení)

$

Odpovídá konci řádku (v režimu více řádků, což je výchozí nastavení)

\K

Obnovuje výchozí pozici výběru na pozici ve vzoru. Některé moduly regulárních výrazů (ale ne Calibre) nedovolí hledat vpřed v proměnlivé délce, obzvláště s kvantifikátory. Když můžete s těmito moduly použít \K, také vám to umožní zbavit se tohoto limitu napsáním ekvivalentu pozitivního hledání vpřed v proměnlivé délce.

Skupiny

(výraz)

Zachytávací skupina: Uloží výběr a může být později vyvolána ve vzorech search nebo replace pomocí \n, kde n je pořadové číslo zachytávací skupiny (počínaje od 1 v pořadí čtení)

(?:výraz)

Skupina, která nezachytí výběr

(?>výraz)

Atomická skupina: Jakmile je výraz splněn, modul regulárních výrazů ji předá. A pokud zbytek vzoru selže, nebude se snažit vyzkoušet jiné kombinace s výrazem. Atomické skupiny nezachycují.

(?|výraz)

Skupina obnovení větve: Větve alternativ zahrnutých ve výrazu sdílejí stejná čísla skupin

(?<name>výraz)

Skupina s názvem „name“: Výběr lze později vyvolat ve vzorci search pomocí výrazu (?P=name) a ve vzorci replace výrazem \g. Dvě různé skupiny mohou používat stejný název.

Prohledávání

Prohledávání

Význam

?=

Pozitivní hledání vpřed (bude umístěno za výběr)

?!

Negativní hledání vpřed (bude umístěno za výběr)

?<=

Pozitivní hledání zpět (bude umístěno před výběr)

?<!

Negativní hledání zpět (bude umístěno před výběr)

Hledání vpřed a zpět nespotřebovávají znaky, jsou nulové délky a nezachycují. Jsou to atomické skupiny: jakmile je tvrzení splněno, modul regulárních výrazů ho předá, a pokud zbytek vzoru selže, nebude se vracet zpět do prohledávání, aby vyzkoušel jiné kombinace.

Při hledání více shod v řetězci, v počáteční pozici každého pokusu o shodu, může hledání zpět zkontrolovat znaky před aktuální pozicí. Proto v řetězci 123 by měl vzor (?<=\d)\d (číslice předcházející číslici) teoreticky vybrat 2 a 3. Na druhou stranu \d\K\d může vybrat pouze 2, protože počáteční pozice po prvním výběru je bezprostředně před 3 a pro druhou shodu zde není dostatek číslic. Podobně \d(\d) zachytí pouze 2. V modulu regulárních výrazů Calibre se pozitivní hledání zpět chová stejně a vybere pouze 2, na rozdíl od teorie.

Skupiny mohou být umístěny uvnitř prohledávání, ale zachytávání je zřídka užitečné. Pokud je to přesto užitečné, bude muset být při používání kvantifikátoru v hledání zpět velmi opatrní: chamtivost spojená s absencí zpětného dozoru může poskytovat překvapující zachytávání. Z tohoto důvodu použijte spíše \K než pozitivní hledání zpět, když máte v zachytávací skupině pozitivního hledání zpět kvantifikátor (nebo ještě hůře několik).

Příklad negativního hledání vpřed:

(?![^<>{}]*[>}])

Umístění na konci vzoru zabraňuje výběru uvnitř značky nebo stylu vloženého do souboru.

Kdykoliv je to možné, je vždy lepší vyhledávání „ukotvit“, aby se snížil počet kroků nezbytných k dosažení výsledku.

Rekurze

Zastoupení

Význam

(?R)

Rekurze celého vzoru

(?1)

Rekurze jediného vzoru číslované zachytávací skupiny, zde skupina 1

Rekurze je vyvolání sebe sama. To je užitečné pro vyvážené dotazy, jako jsou řetězce v uvozovkách, které mohou obsahovat vložené řetězce v uvozovkách. Pokud tedy během zpracování řetězce mezi uvozovkami narazíme na začátek nového řetězce mezi uvozovkami, dobře víme, jak to udělat, a vyvoláme sebe sama. Pak máme podobný vzor:

start-pattern(?>atomic sub-pattern|(?R))*end-pattern

Pro výběr řetězce mezi uvozovkami bez zastavení na vloženém řetězci:

“((?>[^“”]+|(?R))*[^“”]+)”

Tuto šablonu lze také použít ke změně párů značek, které mohou být vloženy, například značek <div>.

Speciální znaky

Zastoupení

Znak

\t

tabulátor

\n

konec řádku

\x20

(měkká) mezera

\xa0

tvrdá mezera

Metaznaky

Metaznaky jsou ty, které mají zvláštní význam pro modul regulárních výrazů. Dvanáct z nich musí předcházet řídící znak, zpětné lomítko (\), aby ztratily svůj zvláštní význam a stal se z nich opět normální znak:

^ . [ ] $ ( ) * + ? | \

Sedm dalších metaznaků nemusí předcházet zpětné lomítko (ale může bez jakýchkoliv dalších důsledků):

{ } ! < > = :

Speciální znaky ztratí svůj status, pokud jsou použity ve třídě (mezi lomenými závorkami []). Uzavírací lomená závorka a pomlčka mají ve třídě zvláštní status. Mimo třídu je pomlčka jednoduchým literálem, uzavírací lomená závorka zůstává metaznakem.

Lomítko (/) a křížek (nebo znak hash) (#) nejsou metaznaky, nemusejí mít řídící znak.

V některých nástrojích, jako je například regex101.com s modulem Python, mají dvojité uvozovky zvláštní status oddělovače a musí mít řídící znak, nebo se možnosti změní. Toto není případ editoru Calibre.

Režimy

(?s)

Způsobí, že tečka (.) bude odpovídat také znakům nového řádku

(?m)

Učiní, že kotvy ^ a $ budou odpovídat začátku a konci řádků namísto začátku a konce celého řetězce.

© Copyright Kovid Goyal.
Naposledy aktualizováno kvě 03, 2024.

Navigace

- moduly
- další |
- předchozí |
- Spustit »
- Kurzy »

- Psaní vlastních modulů pro rozšíření funkcí Calibre

Psaní vlastních modulů pro rozšíření funkcí Calibre

Calibre má velice modulární provedení. Téměř všechny funkce jsou ve formě modulů. Moduly jsou použity pro převod, stahování zpráv (ačkoliv ty jsou nazvány předpisy), různé komponenty uživatelského rozhraní, připojení k různým zařízením, zpracování souborů při jejich přidávání do Calibre a tak dále. Kompletní seznam všech vestavěných modulů můžete získat v Calibre v Předvolby → Rozšířené → Moduly.

Zde vás naučíme, jak vytvořit vlastní moduly pro přidání nových funkcí do Calibre.

Obsah

- Anatomie modulu Calibre

- Modul uživatelského rozhraní

- __init__.py

- ui.py

- main.py

- Získání zdrojů ze souboru ZIP modulu

- Povolení uživatelské konfigurace vašeho modulu

- Moduly pro úpravu knihy

- main.py

- Přidávání překladů do vašeho modulu

- Rozhraní API modulu

- Ladění modulů

- Další příklady modulů

- Sdílení vašeho modulu s ostatními

Poznámka

Toto platí pouze pro vydání Calibre >= 0.8.60

Anatomie modulu Calibre

Modul Calibre je velice jednoduchý, je to prostě soubor ZIP, který obsahuje nějaký kód Pythonu a jakékoliv jiné zdroje, jako soubory obrázků vyžadované modulem. Bez dalších okolků se pojďme podívat na základní příklad.

Předpokládejme, že máte instalaci Calibre, kterou používáte pro vlastní publikaci různých elektronických dokumentů ve formátech EPUB a MOBI. Chtěli byste, aby všechny soubory generované v Calibre měli vydavatele nastaveného jako „Hello World“, zde je návod, jak to udělat. Vytvořte soubor s názvem __init__.py (toto je speciální název a musí být vždy použit pro hlavní soubor vašeho modulu) a zadejte do něj následující kód Pythonu:

from calibre.customize import FileTypePlugin

class HelloWorld(FileTypePlugin):

name = 'Hello World Plugin' # Name of the plugin
description = 'Set the publisher to Hello World for all new conversions'
supported_platforms = ['windows', 'osx', 'linux'] # Platforms this plugin will run on
author = 'Acme Inc.' # The author of this plugin
version = (1, 0, 0) # The version number of this plugin
file_types = {'epub', 'mobi'} # The file types that this plugin will be applied to
on_postprocess = True # Run this plugin after conversion is complete
minimum_calibre_version = (0, 7, 53)

def run(self, path_to_ebook):
from calibre.ebooks.metadata.meta import get_metadata, set_metadata
with open(path_to_ebook, 'r+b') as file:
ext = os.path.splitext(path_to_ebook)[-1][1:].lower()
mi = get_metadata(file, ext)
mi.publisher = 'Hello World'
set_metadata(file, mi, ext)
return path_to_ebook

To je vše. Pro přidání tohoto kódu do Calibre jako modulu stačí spustit následující ve složce, ve které jste vytvořili __init__.py:

calibre-customize -b .

Poznámka

Na macOS jsou nástroje příkazového řádku uvnitř sady Calibre, například pokud jste nainstalovali Calibre do /Applications, nástroje příkazového řádku jsou v /Applications/calibre.app/Contents/MacOS/.

Modul Hello World můžete stáhnout z helloworld_plugin.zip [https://calibre-ebook.com/downloads/helloworld_plugin.zip].

Pokaždé, když použijete Calibre, aby převedlo knihu, bude zavolána metoda run() modulu a převedená kniha bude mít vydavatele nastaveného na „Hello World“. Toto je jednoduchý modul, pojďme se přesunout na složitější příklad, který skutečně přidá komponentu do uživatelského rozhraní.

Modul uživatelského rozhraní

Tento modul bude rozdělen do několika souborů (aby kód zůstal čistý). Ukáže vám, jak získat zdroje (soubory obrázků nebo dat) ze souboru ZIP modulu, což umožňuje uživatelům konfigurovat váš modul, jak vytvořit prvky v uživatelském rozhraní Calibre a jak přistupovat k a dotazovat se databáze knih v Calibre.

Tento modul můžete stáhnout z interface_demo_plugin.zip [https://calibre-ebook.com/downloads/interface_demo_plugin.zip]

První věc, na kterou pamatujte, je to, že tento soubor ZIP má v sobě mnohem více souborů, vysvětleno níže, věnujte zvláštní pozornost plugin-import-name-interface_demo.txt.

plugin-import-name-interface_demo.txt

Prázdný textový soubor použitý pro umožnění kouzla vícesouborového modulu. Tento soubor musí být přítomen ve všech modulech, které používají více než jeden soubor .py. Měl by být prázdný a jeho název souboru musí být ve formě: plugin-import-name-some_name.txt. Přítomnost tohoto souboru vám umožňuje importovat kód ze souborů .py přítomných uvnitř souboru ZIP pomocí příkazu, jako je:

from calibre_plugins.some_name.some_module import some_object

Předpona calibre_plugins musí být vždy přítomná. some_name pochází z názvu prázdného textového souboru. some_module odkazuje na soubor some_module.py uvnitř souboru ZIP. Pamatujte, že toto importování je právě tak výkonné jako obyčejné importy Pythonu. Uvnitř souboru ZIP můžete vytvářet balíčky a podbalíčky modulů .py, jako byste to dělali normálně (definováním __init__.py v každé podsložce) a všechno by mělo „prostě fungovat.

Název, který použijete pro some_name, zadá globální obor názvů sdílený všemi moduly, takže by měl být co nejjedinečnější. Ale nezapomeňte, že to musí být platný identifikátor Pythonu (pouze abecední znaky, číslice a podtržítko).

__init__.py

Jako předtím, soubor, který definuje třídu modulu

main.py

Tento soubor obsahuje skutečný kód, který dělá něco užitečného

ui.py

Tento soubor definuje součást rozhraní modulu

images/icon.png

Ikona pro tento modul

about.txt

Textový soubor s informacemi o modulu

translations

Složka obsahující soubory .mo s překlady uživatelského rozhraní vašeho modulu do různých jazyků. Podívejte se níže na podrobnosti.

Nyní se podívejme na kód.

__init__.py

Nejdříve povinný __init__.py pro definování metadat modulu:

from calibre.customize import InterfaceActionBase

class InterfacePluginDemo(InterfaceActionBase):
'''
This class is a simple wrapper that provides information about the actual
plugin class. The actual interface plugin class is called InterfacePlugin
and is defined in the ui.py file, as specified in the actual_plugin field
below.

The reason for having two classes is that it allows the command line
calibre utilities to run without needing to load the GUI libraries.
'''
name = 'Interface Plugin Demo'
description = 'An advanced plugin demo'
supported_platforms = ['windows', 'osx', 'linux']
author = 'Kovid Goyal'
version = (1, 0, 0)
minimum_calibre_version = (0, 7, 53)

#: This field defines the GUI plugin class that contains all the code
#: that actually does something. Its format is module_path:class_name
#: The specified class must be defined in the specified module.
actual_plugin = 'calibre_plugins.interface_demo.ui:InterfacePlugin'

def is_customizable(self):
'''
This method must return True to enable customization via
Preferences->Plugins
'''
return True

def config_widget(self):
'''
Implement this method and :meth:`save_settings` in your plugin to
use a custom configuration dialog.

This method, if implemented, must return a QWidget. The widget can have
an optional method validate() that takes no arguments and is called
immediately after the user clicks OK. Changes are applied if and only
if the method returns True.

If for some reason you cannot perform the configuration at this time,
return a tuple of two strings (message, details), these will be
displayed as a warning dialog to the user and the process will be
aborted.

The base class implementation of this method raises NotImplementedError
so by default no user configuration is possible.
'''
# It is important to put this import statement here rather than at the
# top of the module as importing the config class will also cause the
# GUI libraries to be loaded, which we do not want when using calibre
# from the command line
from calibre_plugins.interface_demo.config import ConfigWidget
return ConfigWidget()

def save_settings(self, config_widget):
'''
Save the settings specified by the user with config_widget.

:param config_widget: The widget returned by :meth:`config_widget`.
'''
config_widget.save_settings()

# Apply the changes
ac = self.actual_plugin_
if ac is not None:
ac.apply_settings()

Jediná funkce, která stojí za pozornost, je pole actual_plugin. Protože Calibre má jak příkazový řádek, tak grafická rozhraní, moduly grafického rozhraní, jako je tento, by v __init__.py neměly načítat žádné knihovny grafického rozhraní. Pole actual_plugin to dělá za vás tím, že řekne Calibre, že skutečný modul je třeba hledat v jiném souboru uvnitř vašeho archivu ZIP, který bude načten pouze v kontextu grafického rozhraní.

Nezapomeňte, že aby toto fungovalo, musíte mít ve svém souboru ZIP soubor plugin-import-name-some_name.txt, jak bylo probráno výše.

Také existuje pár metod pro povolení uživatelské konfigurace modulu. Ty jsou popsány níže.

ui.py

Nyní se podívejme na ui.py, který definuje skutečné grafické rozhraní modulu. Zdrojový kód je silně okomentovaný a měl by být sebevysvětlující:

from calibre.gui2.actions import InterfaceAction
from calibre_plugins.interface_demo.main import DemoDialog

class InterfacePlugin(InterfaceAction):

name = 'Interface Plugin Demo'

# Declare the main action associated with this plugin
# The keyboard shortcut can be None if you dont want to use a keyboard
# shortcut. Remember that currently calibre has no central management for
# keyboard shortcuts, so try to use an unusual/unused shortcut.
action_spec = ('Interface Plugin Demo', None,
'Run the Interface Plugin Demo', 'Ctrl+Shift+F1')

def genesis(self):
# This method is called once per plugin, do initial setup here

# Set the icon for this interface action
# The get_icons function is a builtin function defined for all your
# plugin code. It loads icons from the plugin zip file. It returns
# QIcon objects, if you want the actual data, use the analogous
# get_resources builtin function.
#
# Note that if you are loading more than one icon, for performance, you
# should pass a list of names to get_icons. In this case, get_icons
# will return a dictionary mapping names to QIcons. Names that
# are not found in the zip file will result in null QIcons.
icon = get_icons('images/icon.png', 'Interface Demo Plugin')

# The qaction is automatically created from the action_spec defined
# above
self.qaction.setIcon(icon)
self.qaction.triggered.connect(self.show_dialog)

def show_dialog(self):
# The base plugin object defined in __init__.py
base_plugin_object = self.interface_action_base_plugin
# Show the config dialog
# The config dialog can also be shown from within
# Preferences->Plugins, which is why the do_user_config
# method is defined on the base plugin class
do_user_config = base_plugin_object.do_user_config

# self.gui is the main calibre GUI. It acts as the gateway to access
# all the elements of the calibre user interface, it should also be the
# parent of the dialog
d = DemoDialog(self.gui, self.qaction.icon(), do_user_config)
d.show()

def apply_settings(self):
from calibre_plugins.interface_demo.config import prefs
# In an actual non trivial plugin, you would probably need to
# do something based on the settings in prefs
prefs

main.py

Skutečná logika pro implementaci dialogového okna rozhraní ukázkového modulu.

from qt.core import QDialog, QLabel, QMessageBox, QPushButton, QVBoxLayout class DemoDialog(QDialog):

def __init__(self, gui, icon, do_user_config):
QDialog.__init__(self, gui)
self.gui = gui self.do_user_config = do_user_config

# The current database shown in the GUI
# db is an instance of the class LibraryDatabase from db/legacy.py
# This class has many, many methods that allow you to do a lot of
# things. For most purposes you should use db.new_api, which has
# a much nicer interface from db/cache.py self.db = gui.current_db self.l = QVBoxLayout()
self.setLayout(self.l)

self.label = QLabel(prefs['hello_world_msg'])
self.l.addWidget(self.label)

self.setWindowTitle('Interface Plugin Demo')
self.setWindowIcon(icon)

self.about_button = QPushButton('About', self)
self.about_button.clicked.connect(self.about)
self.l.addWidget(self.about_button)

self.marked_button = QPushButton(
'Show books with only one format in the calibre GUI', self)
self.marked_button.clicked.connect(self.marked)
self.l.addWidget(self.marked_button)

self.view_button = QPushButton(
'View the most recently added book', self)
self.view_button.clicked.connect(self.view)
self.l.addWidget(self.view_button)

self.update_metadata_button = QPushButton(
'Update metadata in a book\'s files', self)
self.update_metadata_button.clicked.connect(self.update_metadata)
self.l.addWidget(self.update_metadata_button)

self.conf_button = QPushButton(
'Configure this plugin', self)
self.conf_button.clicked.connect(self.config)
self.l.addWidget(self.conf_button)

self.resize(self.sizeHint())

def about(self):
# Get the about text from a file inside the plugin zip file
# The get_resources function is a builtin function defined for all your
# plugin code. It loads files from the plugin zip file. It returns
# the bytes from the specified file.
#
# Note that if you are loading more than one file, for performance, you
# should pass a list of names to get_resources. In this case,
# get_resources will return a dictionary mapping names to bytes. Names that
# are not found in the zip file will not be in the returned dictionary.
text = get_resources('about.txt')
QMessageBox.about(self, 'About the Interface Plugin Demo', text.decode('utf-8'))

def marked(self):
''' Show books with only one format '''
db = self.db.new_api matched_ids = {book_id for book_id in db.all_book_ids() if len(db.formats(book_id)) == 1}
# Mark the records with the matching ids
# new_api does not know anything about marked books, so we use the full
# db object self.db.set_marked_ids(matched_ids)

# Tell the GUI to search for all marked records self.gui.search.setEditText('marked:true')
self.gui.search.do_search()

def view(self):
''' View the most recently added book '''
most_recent = most_recent_id = None db = self.db.new_api for book_id, timestamp in db.all_field_for('timestamp', db.all_book_ids()).items():
if most_recent is None or timestamp > most_recent:
most_recent = timestamp most_recent_id = book_id if most_recent_id is not None:
# Get a reference to the View plugin view_plugin = self.gui.iactions['View']
# Ask the view plugin to launch the viewer for row_number view_plugin._view_calibre_books([most_recent_id])

def update_metadata(self):
'''
Set the metadata in the files in the selected book's record to match the current metadata in the database.
'''
from calibre.ebooks.metadata.meta import set_metadata from calibre.gui2 import error_dialog, info_dialog

# Get currently selected books rows = self.gui.library_view.selectionModel().selectedRows()
if not rows or len(rows) == 0:
return error_dialog(self.gui, 'Cannot update metadata',
'No books selected', show=True)
# Map the rows to book ids ids = list(map(self.gui.library_view.model().id, rows))
db = self.db.new_api for book_id in ids:
# Get the current metadata for this book from the db mi = db.get_metadata(book_id, get_cover=True, cover_as_data=True)
fmts = db.formats(book_id)
if not fmts:
continue for fmt in fmts:
fmt = fmt.lower()
# Get a python file object for the format. This will be either
# an in memory file or a temporary on disk file ffile = db.format(book_id, fmt, as_file=True)
ffile.seek(0)
# Set metadata in the format set_metadata(ffile, mi, fmt)
ffile.seek(0)
# Now replace the file in the calibre library with the updated
# file. We dont use add_format_with_hooks as the hooks were
# already run when the file was first added to calibre.
db.add_format(book_id, fmt, ffile, run_hooks=False)

info_dialog(self, 'Updated files',
'Updated the metadata in the files of %d book(s)'%len(ids), show=True)

def config(self):
self.do_user_config(parent=self)
# Apply the changes self.label.setText(prefs['hello_world_msg'])

Získání zdrojů ze souboru ZIP modulu

Systém načítání doplňků Calibre definuje pár vestavěných funkcí, které vám umožňují pohodlně získat soubory ze souboru ZIP modulu.

get_resources(name_or_list_of_names)

Tato funkce by měla být volána se seznamem cest k souborům uvnitř souboru ZIP. Například pro přístup k souboru icon.png ve složce images v souboru ZIP byste použili: images/icon.png. Jako oddělovač cest vždy používejte lomítko, a to i ve Windows. Pokud předáte jeden název, funkce vrátí nezpracované bajty tohoto souboru nebo None, pokud název nebyl v souboru ZIP nalezen. Pokud předáte více než jeden název, vrátí slovník mapující názvy na bajty. Pokud název nebyl nalezen, nebude ve vráceném slovníku přítomen.

get_icons(name_or_list_of_names, plugin_name=‘‘)

Obal pro funkci get_resources(), který vytváří objekty QIcon ze surových bajtů vrácených funkcí get_resources. Pokud v souboru ZIP není nalezen název, bude odpovídající QIcon nulový. Aby bylo možné podporovat změnu ikon motivu, předejte lidsky přívětivý název vašeho modulu jako nazev_modulu. Pokud uživatel používá motiv ikon s ikonami vašeho modulu, budou načteny přednostně.

Povolení uživatelské konfigurace vašeho modulu

Abyste povolili uživatelům konfigurovat váš modul, musíte definovat ve své základní třídě modulu tři metody, is_customizable, config_widget a save_settings jak je uvedeno níže:

def is_customizable(self):
'''
This method must return True to enable customization via
Preferences->Plugins
'''
return True

def config_widget(self):
'''
Implement this method and :meth:`save_settings` in your plugin to
use a custom configuration dialog.

This method, if implemented, must return a QWidget. The widget can have
an optional method validate() that takes no arguments and is called
immediately after the user clicks OK. Changes are applied if and only
if the method returns True.

If for some reason you cannot perform the configuration at this time,
return a tuple of two strings (message, details), these will be
displayed as a warning dialog to the user and the process will be
aborted.

The base class implementation of this method raises NotImplementedError
so by default no user configuration is possible.
'''
# It is important to put this import statement here rather than at the
# top of the module as importing the config class will also cause the
# GUI libraries to be loaded, which we do not want when using calibre
# from the command line
from calibre_plugins.interface_demo.config import ConfigWidget
return ConfigWidget()

def save_settings(self, config_widget):
'''
Save the settings specified by the user with config_widget.

:param config_widget: The widget returned by :meth:`config_widget`.
'''
config_widget.save_settings()

# Apply the changes
ac = self.actual_plugin_
if ac is not None:
ac.apply_settings()

Calibre má mnoho různých způsobů, jak ukládat konfigurační data (starší verze jeho dlouhé historie). Doporučený způsob je použít třídu JSONConfig, která ukládá vaše konfigurační informace v souboru .json.

Kód pro správu konfiguračních dat v ukázkovém modulu je v config.py:

from qt.core import QHBoxLayout, QLabel, QLineEdit, QWidget

# This is where all preferences for this plugin will be stored
# Remember that this name (i.e. plugins/interface_demo) is also
# in a global namespace, so make it as unique as possible.
# You should always prefix your config file name with plugins/,
# so as to ensure you dont accidentally clobber a calibre config file
prefs = JSONConfig('plugins/interface_demo')

# Set defaults
prefs.defaults['hello_world_msg'] = 'Hello, World!'

class ConfigWidget(QWidget):

def __init__(self):
QWidget.__init__(self)
self.l = QHBoxLayout()
self.setLayout(self.l)

self.label = QLabel('Hello world &message:')
self.l.addWidget(self.label)

self.msg = QLineEdit(self)
self.msg.setText(prefs['hello_world_msg'])
self.l.addWidget(self.msg)
self.label.setBuddy(self.msg)

def save_settings(self):
prefs['hello_world_msg'] = self.msg.text()

Object prefs je nyní dostupný v celém kódu modulu jednoduchým:

from calibre_plugins.interface_demo.config import prefs

Můžete vidět objekt prefs použitý v main.py:

self.do_user_config(parent=self)
# Apply the changes
self.label.setText(prefs['hello_world_msg'])

Moduly pro úpravu knihy

Nyní pojďme na chvíli přeřadit a podívejme se na vytvoření modulu pro přidání nástrojů do editoru knih Calibre. Modul je dostupný zde: editor_demo_plugin.zip [https://calibre-ebook.com/downloads/editor_demo_plugin.zip].

Prvním krokem, stejně jako pro všechny moduly, je vytvořit název importu prázdného souboru txt, jak je popsáno výše. Pojmenujeme soubor plugin-import-name-editor_plugin_demo.txt.

Nyní vytvoříme povinný soubor __init__.py, který obsahuje metadata o modulu – jeho název, autora, verzi atd.

from calibre.customize import EditBookToolPlugin

class DemoPlugin(EditBookToolPlugin):

name = 'Edit Book plugin demo'
version = (1, 0, 0)
author = 'Kovid Goyal'
supported_platforms = ['windows', 'osx', 'linux']
description = 'A demonstration of the plugin interface for the ebook editor'
minimum_calibre_version = (1, 46, 0)

Jeden modul editoru může poskytnout více nástrojů, každý nástroj odpovídá jednomu tlačítku na panelu nástrojů a položce v nabídce :guilabel:Moduly` v editoru. Ty mohou mít podnabídky v případě, že nástroj má více souvisejících akcí.

Nástroje musí být všechny definovány v souboru main.py ve vašem modulu. Každý nástroj je třída, která dědí ze třídy calibre.gui2.tweak_book.plugin.Tool. Pojďme se podívat na main.py z ukázkového modulu, zdrojový kód je silně komentován a měl by být samovysvětlující. Pro více informací si přečtěte dokumenty API ke třídě calibre.gui2.tweak_book.plugin.Tool.

main.py

Zde uvidíme definici jednoho nástroje, který se bude násobit všechny velikosti písem v knize číslem zadaným uživatelem. Tento nástroj ukazuje různé důležité pojmy, které budete potřebovat při vývoji svých vlastních modulů, takže byste si měli pečlivě přečíst (silně komentovaný) zdrojový kód.

import re from calibre import force_unicode from calibre.ebooks.oeb.polish.container import OEB_DOCS, OEB_STYLES, serialize from calibre.gui2 import error_dialog

# The base class that all tools must inherit from from calibre.gui2.tweak_book.plugin import Tool from css_parser.css import CSSRule from qt.core import QAction, QInputDialog class DemoTool(Tool):

#: Set this to a unique name it will be used as a key name = 'demo-tool'

#: If True the user can choose to place this tool in the plugins toolbar allowed_in_toolbar = True

#: If True the user can choose to place this tool in the plugins menu allowed_in_menu = True def create_action(self, for_toolbar=True):
# Create an action, this will be added to the plugins toolbar and
# the plugins menu ac = QAction(get_icons('images/icon.png'), 'Magnify fonts', self.gui) # noqa if not for_toolbar:
# Register a keyboard shortcut for this toolbar action. We only
# register it for the action created for the menu, not the toolbar,
# to avoid a double trigger self.register_shortcut(ac, 'magnify-fonts-tool', default_keys=('Ctrl+Shift+Alt+D',))
ac.triggered.connect(self.ask_user)
return ac def ask_user(self):
# Ask the user for a factor by which to multiply all font sizes factor, ok = QInputDialog.getDouble( self.gui, 'Enter a magnification factor', 'Allow font sizes in the book will be multiplied by the specified factor', value=2, min=0.1, max=4
)
if ok:
# Ensure any in progress editing the user is doing is present in the container self.boss.commit_all_editors_to_container()
try:
self.magnify_fonts(factor)
except Exception:
# Something bad happened report the error to the user import traceback error_dialog(self.gui, _('Failed to magnify fonts'), _(
'Failed to magnify fonts, click "Show details" for more info'), det_msg=traceback.format_exc(), show=True)
# Revert to the saved restore point self.boss.revert_requested(self.boss.global_undo.previous_container)
else:
# Show the user what changes we have made, allowing her to
# revert them if necessary self.boss.show_current_diff()
# Update the editor UI to take into account all the changes we
# have made self.boss.apply_container_update_to_gui()

def magnify_fonts(self, factor):
# Magnify all font sizes defined in the book by the specified factor
# First we create a restore point so that the user can undo all changes
# we make.
self.boss.add_savepoint('Before: Magnify fonts')

container = self.current_container # The book being edited as a container object

# Iterate over all style declarations in the book, this means css
# stylesheets, <style> tags and style="" attributes for name, media_type in container.mime_map.items():
if media_type in OEB_STYLES:
# A stylesheet. Parsed stylesheets are css_parser CSSStylesheet
# objects.
self.magnify_stylesheet(container.parsed(name), factor)
container.dirty(name) # Tell the container that we have changed the stylesheet elif media_type in OEB_DOCS:
# A HTML file. Parsed HTML files are lxml elements for style_tag in container.parsed(name).xpath('//*[local-name="style"]'):
if style_tag.text and style_tag.get('type', None) in {None, 'text/css'}:
# We have an inline CSS <style> tag, parse it into a
# stylesheet object sheet = container.parse_css(style_tag.text)
self.magnify_stylesheet(sheet, factor)
style_tag.text = serialize(sheet, 'text/css', pretty_print=True)
container.dirty(name) # Tell the container that we have changed the stylesheet for elem in container.parsed(name).xpath('//*[@style]'):
# Process inline style attributes block = container.parse_css(elem.get('style'), is_declaration=True)
self.magnify_declaration(block, factor)
elem.set('style', force_unicode(block.getCssText(separator=' '), 'utf-8'))

def magnify_stylesheet(self, sheet, factor):
# Magnify all fonts in the specified stylesheet by the specified
# factor.
for rule in sheet.cssRules.rulesOfType(CSSRule.STYLE_RULE):
self.magnify_declaration(rule.style, factor)

def magnify_declaration(self, style, factor):
# Magnify all fonts in the specified style declaration by the specified
# factor val = style.getPropertyValue('font-size')
if not val:
return
# see if the font-size contains a number num = re.search(r'[0-9.]+', val)
if num is not None:
num = num.group()
val = val.replace(num, '%f' % (float(num) * factor))
style.setProperty('font-size', val)
# We should also be dealing with the font shorthand property and
# font sizes specified as non numbers, but those are left as exercises
# for the reader

Pojďme si rozebrat main.py. Vidíme, že definuje jeden nástroj s názvem Zvětšit písma. Tento nástroj požádá uživatele o číslo a vynásobí všechny velikosti písem v knize tímto číslem.

První důležitá věc je název nástroje, který musíte nastavit na nějaký relativně jedinečný řetězec, protože bude použit jako klíč pro tento nástroj.

Dalším důležitým vstupním bodem je calibre.gui2.tweak_book.plugin.Tool.create_action(). Tato metoda vytvoří objekty QAction, které se zobrazí v panelu nástrojů modulů a nabídce modulu. Také to volitelně přiřadí klávesovou zkratku, kterou si uživatel může přizpůsobit. Spuštěný signál z QAction je spojený s metodou ask_user(), která se zeptá uživatele na násobitel velikosti písma, a pak spustí kód zvětšení.

Kód zvětšení kód je dobře okomentován a poměrně jednoduchý. Hlavními věcmi, na které pamatujte, je to, že jste získali odkaz na okno editoru jako self.gui a Boss editoru jako self.boss. Boss je objekt, který řídí uživatelské rozhraní editoru. Má mnoho užitečných metod, které jsou popsány ve třídě calibre.gui2.tweak_book.boss.Boss.

Nakonec je zde self.current_container, což je odkaz na knihu upravovanou jako objekt calibre.ebooks.oeb.polish.container.Container. Znázorňuje knihu jako kolekci jejích základních souborů HTML, CSS nebo obrázků a má pohodlné metody pro provádění mnoha užitečných věcí. Objekt kontejneru a různé užitečné funkce nástrojů, které mohou být znovu použity v kódu vašeho modulu, jsou popsány v Dokumentace API pro nástroje pro úpravu e-knih.

Přidávání překladů do vašeho modulu

Všechny řetězce uživatelského rozhraní ve svém modulu můžete mít přeloženy a zobrazeny v jakémkoliv jazyce, který je nastaven pro hlavní uživatelské rozhraní Calibre.

Prvním krokem je projít zdrojový kód svého modulu a označit všechny řetězce viditelné uživatelem jako přeložitelné jejich obklopením _(). Například:

action_spec = (_('My plugin'), None, _('My plugin is cool'), None)

Pak použijte nějaký program pro vygenerování souborů .po ze zdrojového kódu vašeho doplňku. Měl by být jeden soubor .po pro každý jazyk, do kterého chcete překládat. Například: cs.po pro češtinu, de.po pro němčinu, fr.po pro francouzštinu a tak dále. K tomu můžete použít program Poedit [https://poedit.net/].

Tyto soubory .po pošlete svým překladatelům. Jakmile je dostanete zpátky, zkompilovat je do souborů .mo. K tomu můžete znovu použít Poedit, nebo prostě udělejte:

calibre-debug -c "from calibre.translations.msgfmt import main; main()" filename.po

Soubory .mo umístěte do složky translations ve svém modulu.

Posledním krokem je jednoduše zavolat funkci load_translations() na začátku souborů .py vašeho modulu. Z výkonnostních důvodů byste měli volat tuto funkci pouze v těch souborech .py, které skutečně mají přeložitelné řetězce. Takže v typickém modulu uživatelského rozhraní byste ji volali na začátku ui.py ale ne __init__.py.

Překlady svých modulů můžete otestovat změnou jazyka uživatelského rozhraní v Calibre pod Předvolby → Rozhraní → Vzhled a chování nebo spuštěním Calibre s nastavenou proměnnou prostředí CALIBRE_OVERRIDE_LANG. Například:

CALIBRE_OVERRIDE_LANG=de

Nahraďte de kódem jazyka, který chcete otestovat.

Pro překlady s množnými čísly použijte funkci ngettext() namísto _(). Například:

ngettext('Delete a book', 'Delete {} books', num_books).format(num_books)

Rozhraní API modulu

Jak jste si možná všimli výše, modul Calibre je třída. V Calibre existují různé třídy pro různé typy modulů. Podrobnosti o každé třídě, včetně základní třídy všech modulů, lze nalézt v moduly.

Váš modul téměř určitě bude používat kód z Calibre. Abyste zjistili, jak najít různé kousky funkcí v základním kódu Calibre, přečtěte si část o Rozložení kódu Calibre.

Ladění modulů

Prvním a nejdůležitějším krokem je spuštění Calibre v režimu ladění. To můžete udělat z příkazového řádku pomocí:

calibre-debug -g

Nebo z Calibre kliknutím pravým tlačítkem myši na tlačítko Předvolby nebo pomocí klávesové zkratky Ctrl+Shift+R.

Při spuštění z příkazového řádku, bude výstup ladění vypsán do konzole, při spuštění z Calibre, půjde výstup do textového souboru.

Do kódu svého modulu můžete kamkoliv vložit příkazy tisku, budou mít výstup v režimu ladění. Nezapomeňte, že je to Python, neměli byste opravdu potřebovat nic víc než příkazy tisku pro ladění ;) Celé Celibre jsem vytvořil pouze pomocí této techniky ladění.

Změny ve svém modulu můžete rychle testovat pomocí následujícího příkazového řádku:

calibre-debug -s; calibre-customize -b /path/to/your/plugin/folder; calibre

Toto vypne spuštěné Calibre, počkejte až bude vypnutí dokončeno, a pak aktualizujte svůj modul v Calibre a znovu spusťte Calibre.

Další příklady modulů

Seznam mnoha propracovaných modulů Calibre můžete nalézt zde [https://www.mobileread.com/forums/showthread.php?t=118764].

Sdílení vašeho modulu s ostatními

Pokud byste chtěli sdílet moduly, které jste vytvořili, s ostatními uživateli Calibre, zveřejněte svůj modul v novém vláknu ve fóru modulů Calibre [https://www.mobileread.com/forums/forumdisplay.php?f=237].

© Copyright Kovid Goyal.
Naposledy aktualizováno kvě 03, 2024.

Navigace

- moduly
- další |
- předchozí |
- Spustit »
- Kurzy »
- Sazba matematiky v e-knihách

Sazba matematiky v e-knihách

Prohlížeč e-knih Calibre má schopnost zobrazit matematiku vloženou do e-knih (soubory EPUB a HTML). Můžete sázet matematiku přímo pomocí TeX, MathML nebo AsciiMath. Prohlížeč e-knih Calibre k tomuto používá vynikající knihovnu MathJax [https://www.mathjax.org]. Toto je stručný kurz vytváření e-knih s matematikou, které fungují dobře s Prohlížečem e-knih Calibre.

Zjednodušený soubor HTML s matematikou

Můžete zapsat matematiku vloženou uvnitř souboru zjednodušeného HTML a Prohlížeč e-knih Calibre ji vykreslí se správnou sazbou matematiky. V příkladu níže používáme pro matematiku zápis TeX. Uvidíte, že můžete použít normální příkazy TeX, s malým upozorněním, že ampersandy a znaky menší než a větší než musí být zapsány jako &amp; &lt; a &gt; v uvedeném pořadí.

První krok je říct Calibre, že toto bude obsahovat matematiku. To uděláte přidáním následujícího fragmentu kódu do oddílu <head> souboru HTML:

<script type="text/x-mathjax-config"></script>

To je vše, nyní můžete psát matematiku, jak byste to udělali v souboru .tex. Například zde jsou Lorentzovy rovnice:

<h2>The Lorenz Equations</h2>

<p>
\begin{align}
\dot{x} &amp; = \sigma(y-x) \\
\dot{y} &amp; = \rho x - y - xz \\
\dot{z} &amp; = -\beta z + xy
\end{align}
</p>

Tento fragment vypadá jako následující snímek obrazovky v Prohlížeči e-knih Calibre.

Lorentzovy rovnice

Kompletní soubor HTML s více rovnicemi a vloženou matematikou je reprodukován níže. Tento soubor HTML můžete převést do EPUB v Calibre, abyste získali e-knihu, kterou můžete snadno distribuovat ostatním lidem.

<!DOCTYPE html>
<html>
<!-- Copyright (c) 2012 Design Science, Inc. -->
<head>
<title>Math Test Page</title>
<meta http-equiv="content-type" content="text/html; charset=UTF-8" />

<!-- This script tag is needed to make calibre's ebook-viewer recpgnize that this file needs math typesetting -->
<script type="text/x-mathjax-config">
// This line adds numbers to all equations automatically, unless explicitly suppressed.
MathJax.tex = {tags: 'all'};
</script>

<style> h1 {text-align:center}
h2 { font-weight: bold; background-color: #DDDDDD; padding: .2em .5em; margin-top: 1.5em; border-top: 3px solid #666666; border-bottom: 2px solid #999999;
}
</style>
</head>
<body>

<h1>Sample Equations</h1>

<h2>The Lorenz Equations</h2>

<p> \begin{align}
\dot{x} &amp; = \sigma(y-x) \label{lorenz}\\
\dot{y} &amp; = \rho x - y - xz \\
\dot{z} &amp; = -\beta z + xy
\end{align} </p>

<h2>The Cauchy-Schwarz Inequality</h2>

<p>\[
\left( \sum_{k=1}^n a_k b_k \right)^{\!\!2} \leq
\left( \sum_{k=1}^n a_k^2 \right) \left( \sum_{k=1}^n b_k^2 \right)
\]</p>

<h2>A Cross Product Formula</h2>

<p>\[
\mathbf{V}_1 \times \mathbf{V}_2 =
\begin{vmatrix}
\mathbf{i} &amp; \mathbf{j} &amp; \mathbf{k} \\
\frac{\partial X}{\partial u} &amp; \frac{\partial Y}{\partial u} &amp; 0 \\
\frac{\partial X}{\partial v} &amp; \frac{\partial Y}{\partial v} &amp; 0 \\
\end{vmatrix}
\]</p>

<h2>The probability of getting \(k\) heads when flipping \(n\) coins is:</h2>

<p>\[P(E) = {n \choose k} p^k (1-p)^{ n-k} \]</p>

<h2>An Identity of Ramanujan</h2>

<p>\[
\frac{1}{(\sqrt{\phi \sqrt{5}}-\phi) e^{\frac25 \pi}} =
1+\frac{e^{-2\pi}} {1+\frac{e^{-4\pi}} {1+\frac{e^{-6\pi}}
{1+\frac{e^{-8\pi}} {1+\ldots} } } }
\]</p>

<h2>A Rogers-Ramanujan Identity</h2>

<p>\[
1 + \frac{q^2}{(1-q)}+\frac{q^6}{(1-q)(1-q^2)}+\cdots =
\prod_{j=0}^{\infty}\frac{1}{(1-q^{5j+2})(1-q^{5j+3})},
\quad\quad \text{for $|q|&lt;1$}.
\]</p>

<h2>Maxwell's Equations</h2>

<p> \begin{align}
\nabla \times \vec{\mathbf{B}} -\, \frac1c\, \frac{\partial\vec{\mathbf{E}}}{\partial t} &amp; = \frac{4\pi}{c}\vec{\mathbf{j}} \\
\nabla \cdot \vec{\mathbf{E}} &amp; = 4 \pi \rho \\
\nabla \times \vec{\mathbf{E}}\, +\, \frac1c\, \frac{\partial\vec{\mathbf{B}}}{\partial t} &amp; = \vec{\mathbf{0}} \\
\nabla \cdot \vec{\mathbf{B}} &amp; = 0
\end{align} </p>

<h2>In-line Mathematics</h2>

<p>While display equations look good for a page of samples, the ability to mix math and text in a paragraph is also important. This expression \(\sqrt{3x-1}+(1+x)^2\) is an example of an inline equation. As you see, equations can be used this way as well, without unduly disturbing the spacing between lines.</p>

<h2>References to equations</h2>

<p>Here is a reference to the Lorenz Equations (\ref{lorenz}). Clicking on the equation number will take you back to the equation.</p>

</body>
</html>

Více informací

Protože Prohlížeč e-knih Calibre používá pro vykreslení matematiky knihovnu MathJax, nejlepší místo pro zjištění více o matematice v e-knihách a získání nápovědy je Webová stránka MathJax [https://www.mathjax.org].

© Copyright Kovid Goyal.
Naposledy aktualizováno kvě 03, 2024.

Navigace

- moduly
- další |
- předchozí |
- Spustit »
- Kurzy »
- Vytváření katalogů AZW3 • EPUB • MOBI

Vytváření katalogů AZW3 • EPUB • MOBI

Funkce calibre Vytvořit katalog vám umožňuje vytvořit z vaší knihovny katalog v různých formátech. Tento soubor nápovědy popisuje volby zařazení do katalogu při generování katalogu ve formátech AZW3, EPUB a MOBI.

- Výběr knih do katalogu

- Zahrnuté oddíly

- Předpony

- Vyloučené knihy

- Vyloučené žánry

- Další volby

- Vlastní obálky katalogu

- Dodatečné zdroje nápovědy

Výběr knih do katalogu

Pokud chcete katalogizovat celou svou knihovnu, odeberte v hlavním okně jakákoliv kritéria hledání nebo filtrování. Pokud máte vybránu jednu knihu, všechny knihy ve vaší knihovně budou kandidáti na zahrnutí do generovaného katalogu. Jednotlivé knihy mohou být vyloučeny různými kritérii, pro více informací se podívejte na oddíl Vyloučené žánry níže.

Pokud chcete katalogizovat pouze něco z vaší knihovny, máte dvě volby:

- Vytvořte výběr více knih, které chcete katalogizovat. Pokud je v hlavním okně calibre vybrána více než jedna kniha, budou katalogizovány pouze vybrané knihy.

- Použijte pole Hledat nebo Prohlížeč štítků pro filtrování zobrazených knih. Budou katalogizovány pouze vybrané knihy.

Pro zahájení generování katalogu vyberte položku nabídky Převést knihy > Vytvořit katalog knih ve vaší knihovně calibre. Můžete také přidat tlačítko Vytvořit katalog na panel nástrojů v Předvolby > Rozhraní > Panely nástrojů a nabídky pro snadnější přístup k dialogovému oknu Vytvořit katalog.

Ve Volby katalogu vyberte jako formát katalogu AZW3, EPUB nebo MOBI. V poli Název katalogu zadejte název, který bude použit pro vygenerovaný katalog. Pokud již existuje katalog se stejným názvem a formátem, bude nahrazen nově vytvořeným katalogem.

Povolení Odeslat katalog do zařízení automaticky stáhne po dokončení vygenerovaný katalog do připojeného zařízení.

Zahrnuté oddíly

Oddíly povolené zatržítkem budou zahrnuty do generovaného katalogu:

- Autoři – všechny knihy seřazené podle autora zobrazené ve formátu seznamu. Knihy bez sérií jsou vypsány před knihami v sérii.

- Názvy – všechny knihy seřazené podle názvu zobrazené ve formátu seznamu.

- Série – všechny knihy, které jsou součástí série, seřazené podle názvu zobrazené ve formátu seznamu.

- Žánry – jednotlivé žánry zobrazené v seznamu, seřazené podle autora a série.

- Nedávno přidané – všechny knihy seřazené v obráceném chronologickém pořadí. Seznam obsahuje knihy přidané za posledních 30 dní, pak výpis přidaných knih po měsících.

- Popisy – podrobná stránka popisu pro každou knihu včetně náhledu obálky a komentářů. Seřazeno podle autora s knihami bez sérií vypsanými před knihami v sérii.

Předpony

Pravidla předpony vám umožňují přidat předponu do výpisů knih, když jsou splněna určitá kritéria. Například možná chcete označit přečtené knihy zatržítkem, nebo knihy na seznamu přání označit X.

Zatržítko v prvním sloupci povolí pravidlo. Název je název pravidla, který zadáte. Pole je buď Štítky, nebo vlastní sloupec z vaší knihovny. Hodnota je obsah Pole, který porovnat. Když je pravidlo předpony splněno, kniha bude označena vybranou Předponou.

Vy výše uvedeném příkladu byla zadána tři pravidla předpony:

1. Přečtená kniha určuje, že kniha s jakýmkoliv datem ve vlastním sloupci pojmenovaném Přečteno bude označena předponou se symbolem zatržítka.

2. Položka seznamu přání určuje, že jakákoliv kniha se štítkem Seznam přání bude označena předponou se symbolem X.

3. Knihy v knihovně určuje, že jakákoliv kniha s hodnotou Pravda (nebo Ano) ve vlastním sloupci Dostupná v knihovně bude označena předponou se symbolem obousměrné šipky.

První odpovídající pravidlo určí předponu. Zakázaná nebo neúplná pravidla jsou ignorována.

Vyloučené knihy

Pravidla vyloučení vám umožňují zadat knihy, které nebudou katalogizovány.

Zatržítko v prvním sloupci povolí pravidlo. Název je název pravidla, který zadáte. Pole je buď Štítky, nebo vlastní sloupec z vaší knihovny. Hodnota je obsah Pole, který porovnat. Když je pravidlo předpony splněno, kniha bude vyloučena z generovaného katalogu.

Vy výše uvedeném příkladu byla zadána dvě pravidla vyloučení:

1. Pravidlo Katalogy určuje, že jakákoliv kniha se štítkem Katalog bude vyloučena z generovaného katalogu.

2. Pravidlo Archivované knihy určuje, že jakákoliv kniha s hodnotou Archivováno ve vlastním sloupci guilabel:Stav bude vyloučena z generovaného katalogu.

Jsou vyhodnocována všechna pravidla pro každou knihu. Zakázaná nebo neúplná pravidla jsou ignorována.

Vyloučené žánry

Když je generován katalog, jsou štítky ve vaší databázi použity jako žánry. Například můžete použít štítky Román a Literatura faktu. Tyto štítky se stanou žánry ve vygenerovaném katalogu s knihami vypsanými pod jejich příslušnými seznamy žánrů na základě svých přiřazených štítků.

Možná používáte určité štítky pro jiné účely, možná + označení přečtené knihy, nebo štítek v hranatých závorkách, jako je [Amazon Freebie], pro označení zdroje knihy. Regulární výraz Vyloučené žánry vám umožňuje zadat štítky, které nechcete použít jako žánry v generovaném katalogu. Výchozí regulární výraz vyloučení \[.+\]\+ vyloučí jakékoliv štítky ve formě [štítek], stejně jako vyloučí +, výchozí štítek pro přečtené knihy, aby nebyly použity jako žánry v generovaném katalogu.

V regulárním výrazu můžete také použít přesný název štítku. Například [Amazon Freebie] nebo [Projekt Gutenberg]. Pokud chcete vypsat více přesných štítků k vyloučení, vložte mezi ně znak svislé čáry: [Amazon Freebie]|[Projekt Gutenberg].

Výsledky regulárního výrazu zobrazí, které štítky budou vyloučeny při sestavování katalogu na základě štítků ve vaší databázi a regulárního výrazu, který jste zadali. Výsledky jsou aktualizovány, jak měníte regulární výraz.

Další volby

Obálka katalogu určuje, zda generovat novou obálku nebo použít existující obálku. Je možné vytvořit vlastní obálku pro vaše katalogy – pro více informací se podívejte na Vlastní obálky katalogu. Pokud jste vytvořili vlastní obálku, kterou chcete znovu použít, vyberte Použít existující obálku. Jinak vyberte Generovat novou obálku.

Dodatečná poznámka popisu určuje obsah vlastního sloupce, který vložit do stránky Popis vedle náhledu obálky. Například možná chcete zobrazit datum, kdy jste naposledy četli knihu, pomocí vlastního slupce Naposledy čteno. Pro pokročilé používání funkce poznámky Popisu se podívejte na tento příspěvek ve fóru calibre <https://www.mobileread.com/forums/showpost.php?p=1335767&postcount=395>`_.

Šířka náhledu určuje předvolbu šířky pro náhledy obálek obsažené na stránkách Popis. Náhledy jsou ukládány do mezipaměti pro zlepšení výkonu. Pro experimentování s různými šířkami zkuste generovat katalog jen s pár knihami, doku neurčíte svou upřednostňovanou šířku, pak vygenerujte úplný katalog. Když je katalog generován poprvé s novou šířkou náhledu, výkon bude nižší, ale následující sestavení katalogu využijí náhledy v mezipaměti.

Sloučit s komentáři určuje vlastní sloupec, jehož obsah bude nedestruktivně sloučen s metadaty komentářů během generování katalogu. Například můžete mít vlastní sloupec Biografie autora, který byste chtěli připojit k metadatům komentářů. Můžete zvolit vložit obsah vlastního sloupce před nebo za oddíl komentářů a volitelně oddělit připojený obsah vodorovnou čárou. Vhodné typy vlastních sloupců zahrnují text, komentáře a složený.

Vlastní obálky katalogu

S nainstalovaným modulem Generovat obálku [https://www.mobileread.com/forums/showthread.php?t=124219] můžete vytvořit vlastní obálky pro své katalogy. Pro nainstalování modulu přejděte na Předvolby > Pokročilé > Moduly > Získat nové moduly.

Dodatečné zdroje nápovědy

Pro více informací o funkci Katalog calibre navštivte fórum MobileRead Creating Catalogs: Start here [https://www.mobileread.com/forums/showthread.php?t=118556], kde můžete najít informace o tom, jak přizpůsobit šablony katalogu, a jak poslat chybové hlášení.

Pokud máte otázky nebo chcete diskutovat o funkci Katalog Calibre s ostatními uživateli, navštivte fórum MobileRead Library Management [https://www.mobileread.com/forums/forumdisplay.php?f=236].

© Copyright Kovid Goyal.
Naposledy aktualizováno kvě 03, 2024.

Navigace

- moduly
- další |
- předchozí |
- Spustit »
- Kurzy »
- Virtuální knihovny

Virtuální knihovny

Virtuální knihovna je způsob, jak říct Calibre, aby otevřelo pouze podmnožinu normální knihovny. Můžete například chtít pracovat pouze s knihami od určitých autorů nebo s knihami, které mají pouze určitý štítek. Používání Virtuálních knihoven je upřednostňovaným způsobem rozdělení velké kolekce knih do menších podkolekcí. Je lepší rozdělit knihovnu do více menších knihoven, protože pokud chcete prohledávat celou kolekci, můžete se jednoduše vrátit zpět do plné knihovny. Neexistuje způsob, jak v Calibre prohledávat více samostatných knihoven současně.

Virtuální knihovna se liší od jednoduchého vyhledávání. Hledání je omezeno pouze na seznam knih zobrazených v seznamu knih. Virtuální knihovna dělá to samé a navíc také omezuje položky zobrazené v Prohlížeči štítků vlevo. Prohlížeč štítků zobrazí pouze štítky, autory, série, vydavatele atd., kteří pocházejí z knih ve Virtuální knihovně. Virtuální knihovna se tak chová, jako kdyby skutečná knihovna obsahovala pouze omezenou sadu knih.

Vytváření Virtuálních knihoven

Pro použití Virtuální knihovny klikněte na tlačítko Virtuální knihovna umístěné na levé straně panelu Hledat a vyberte možnost Vytvořit Virtuální knihovnu. Jako první příklad vytvořme Virtuální knihovnu, která nám zobrazí pouze knihy od konkrétního autora. Klikněte na odkaz Autoři, jak je znázorněno na obrázku níže, zvolte autora, kterého chcete použít, a klikněte na OK.

Dialogové okno Vytvořit Virtuální knihovnu bylo pro vás vyplněno. Klikněte na OK a uvidíte, že byla vytvořena nová Virtuální knihovna a automaticky na ni bylo přepnuto, takže ukazuje pouze knihy od vybraného autora. Pokud jde o Calibre, je to, jako kdyby vaše knihovna obsahovala pouze knihy od vybraného autora.

Na plnou knihovnu můžete kdykoliv přepnout zpět opětovným kliknutím na tlačítko Virtuální knihovna a výběrem položky pojmenované <Žádná>.

Virtuální knihovny jsou založeny na hledání. Jako základ Virtuální knihovny můžete použít jakékoliv hledání. Virtuální knihovna bude obsahovat pouze knihy odpovídající tomuto hledání. Do Panelu hledání nejdříve vepište hledání, které chcete použít, nebo sestavte hledání pomocí Prohlížeče štítků. Až budete spokojení s vrácenými výsledky, klikněte na tlačítko Virtuální knihovna, zvolte Vytvořit knihovnu a zadejte název pro novou Virtuální knihovnu. Virtuální knihovna pak bude vytvořena na základě hledání, které jste právě zadali. Hledání jsou velice výkonná, pro příklady druhů věcí, které s nimi můžete dělat, se podívejte na Rozhraní hledání.

Příklady užitečných Virtuálních knihoven

- Knihy přidané do Calibre za poslední den:

date:>1daysago

- Knihy přidané do Calibre za poslední měsíc:

date:>30daysago

- Knihy s hodnocením 5 hvězdiček:

rating:5

- Knihy s hodnocením alespoň 4 hvězdičky:

rating:>=4

- Knihy bez hodnocení:

rating:false

- Periodika stahovaná funkcí Načíst zprávy v Calibre:

tags:=Zprávy and author:=Calibre

- Knihy bez štítků:

tags:false

- Knihy bez obálek:

cover:false

Práce s Virtuálními knihovnami

Upravit nebo odebrat dříve vytvořenou Virtuální knihovnu můžete kliknutím na Virtuální knihovna a zvolením příslušné akce.

Můžete říct Calibre, že při otevření aktuální knihovny chcete vždy použít určitou Virtuální knihovnu, a to na adrese Předvolby → Rozhraní → Chování.

Aktuální hledání můžete rychle použít jako dočasnou Virtuální knihovnu kliknutím na tlačítko Virtuální knihovna a zvolením položky *aktuální hledání.

Všechny Virtuální knihovny můžete zobrazit jako karty nad seznamem knih. To je obzvlášť šikovné, pokud chcete velice často přepínat mezi Virtuálními knihovnami. Klikněte na tlačítko Virtuální knihovna a vyberte Zobrazit Virtuální knihovny jako karty. Karty můžete přeuspořádat přetažením a zavřením těch, které nechcete vidět. Zavřené karty mohou být obnoveny kliknutím pravým tlačítkem myši na panel karet.

Používání Virtuálních knihoven v hledáních

Knihy, které jsou ve Virtuální knihovně, můžete hledat pomocí předpony vl:. Například vl:Přečíst najde všechny knihy ve Virtuální knihovně Přečíst. Hledání vl:Přečíst a `vl:"Sci-Fi" najdete všechny knihy, které jsou v obou Virtuálních knihovnách Přečíst a * Sci-Fi*.

Hodnota následující vl: musí být název Virtuální knihovny. Pokud název virtuální knihovny obsahuje mezery, uzavřete ho uvozovkami.

Jedno použití pro hledání ve Virtuální knihovně je v Serveru s obsahem. V Předvolby → Sdílení po síti>Vyžadovat uživatelské jméno a heslo můžete omezit knihovny Calibre viditelné uživateli. Pro každou viditelnou knihovnu můžete zadat vyhledávací výraz pro další omezení, které knihy jsou vidět. Použijte vl:"Název virtuální knihovny" pro omezení knih na ty, které jsou ve Virtuální knihovně.

Používání dodatečných omezení

Knihy zobrazené ve virtuální knihovně můžete dále omezit pomocí Dodatečných omezení. Dodatečné omezení je vámi dříve vytvořené uložené hledání, které může být použito na aktuální virtuální knihovnu pro další omezení zobrazených knih ve Virtuální knihovně. Například řekněme, že máte virtuální knihovnu pro knihy se štítkem Historický román a uložené hledání, které vám ukazuje nepřečtené knihy. Můžete kliknout na tlačítko Virtuální knihovna a zvolit volbu Dodatečná omezení pro zobrazení pouze nepřečtených historických románů. Abyste se naučili o uložených hledáních, podívejte se na Ukládání hledání.

© Copyright Kovid Goyal.
Naposledy aktualizováno kvě 03, 2024.

Navigace

- moduly
- další |
- předchozí |
- Spustit »
- calibre:// URL schéma

calibre:// URL schéma
calibre se registruje jako handler program pro calibre:// URL. Takže je můžete použít pro vykonávaní akcí jako jsou otevírání knih, vyhládávání knih, atd. z jiných programů/dokumentů nebo přes příkazový řádek. Například spuštěním následujícího příkazu:

calibre calibre://switch-library/Some_Library

Otevře se calibre s knihovnou pojmenovanou „Some Library“. Názvy knihoven jsou názvy adresářů složky knihovny, kde jsou mezery nahrazeny podtržítky. Speciální hodnota „_“ znamená současnou knihovnu. Níže jsou zdokumentovány rozličné typy URL.

Dokonce můžete vložit odkazy do HTML souborů nebo Word dokumentů a operační systém automaticky otevře calibre pro provedení specifikované akce.

- Přepnout na konkrétní knihovnu

- Zobrazit určitou knihu v calibre

- Otevřít konkrétní knihu v Prohlížeči e-knih na konkrétní pozici

- Hledání knih

- Otevřít okno s podrobnostmi o knize na knize v některé knihovně

- Otevřít poznámky přidružené k autorovi, sérii atd.

- Hex kódování URL parametrů

Přepnout na konkrétní knihovnu

Syntaxe URL je:

calibre://switch-library/Library_Name

Názvy knihoven jsou názvy adresářů složky knihovny, kde jsou mezery nahrazeny podtržítky. Speciální hodnota „_“ znamená současnou knihovnu. Pro názvy knihoven můžete rovněž použít :ref:‘hex enconding<hex_encoding>‘, to je užitečné v přípdadě, že názvy knihoven obsahují speciální znaky, který by jinak vyžadovaly URL kódování. Hex kódované názvy vypadají takto:

_hex_-AD23F4BC

Kde část za „_hex_“ předponou je název knihovny zakódovaný v UTF-8 a každý byte jej reprezentován dvěma hexadecimálními znaky.

Zobrazit určitou knihu v calibre

Syntaxe URL je:

calibre://show-book/Library_Name/book_id

Toto v Calibre zobrazí knihu s book_id (číslo). Tyto identifikátory knih lze v rozhraní Calibre zobrazit přejetím myši nad odkazem Kliknout pro otevření na panelu Podrobnosti o knize, je to to číslo v závorce na konci cesty ke složce knihy.

Odkaz na aktuální knihu zobrazenou v Calibre můžete zkopírovat kliknutím pravým tlačítkem myši na panel Podrobnosti o knize a výběrem Zkopírovat odkaz na knihu.

Pokud je vyhledávání aktivní a kniha neodpovídá vyhledávání, pak se vyhledávání vymaže.

Pokud je vybrána virtuální knihovna, Calibre ji použije při zobrazení knihy. Pokud kniha není nalezena v této virtuální knihovně, je virtuální knihovna vymazána.

Pokud chcete přepnout na určitou Virtuální knihovnu, když zobrazuje knihu, použijte:

calibre://show-book/Library_Name/book_id?virtual_library=Library%20Name
or
calibre://show-book/Library_Name/book_id?encoded_virtual_library=hex_encoded_virtual_library_name

nahrazení mezer v názvu virtuální knihovny znakem %20. Pokud kniha není nalezena v této virtuální knihovně, pak je virtuální knihovna ignorována.

Otevřít konkrétní knihu v Prohlížeči e-knih na konkrétní pozici

Syntaxe URL je:

calibre://view-book/Library_Name/book_id/book_format?open_at=location

Tady je „book_format“ formát knihy, například „EPUB“ nebo „MOBI“ a „location“ je volitelná pozice v knize. Nejednodušší způsob získání takových odkazů je otevřít knihu v prohlížeči, pak přes ovládání prohlížeče vyberte :guilabel:‘Přejít na->Umístění‘, kde se tento odkaz pro kopírování/vložení jinam zobrazí.

Hledání knih

Syntaxe URL je:

calibre://search/Library_Name?q=query
calibre://search/Library_Name?eq=hex_encoded_query

Zde je dotaz jakýkoliv platný vyhledávací výraz. Pokud je vyledávací výraz komplikovaný, zakódujte jej jako hexadecimální řetězec a namísto toho použijte eq. Vynechání dotazu způsobí vymazání aktuálního vyhledávání.

Ve výchozím nastavení, pokud je vybrána Virtuální knihovna, Calibre ji před provedením vyhledávání vymaže, aby se zajistilo, že budou nalezeny všechny knihy. Pokud chcete zachovat Virtuální knihovnu, použijte:

calibre://search/Library_Name?q=query&virtual_library=_

Pokud chcete přepnout na určitou Virtuální knihovnu, použijte:

calibre://search/Library_Name?virtual_library=Library%20Name
or
calibre://search/Library_Name?encoded_virtual_library=hex_encoded_virtual_library_name

nahrazování mezer v názvu Virtuální knihovně hodnotou %20.

Pokud v Calibre provádíte vyhledávání a chcete pro něj vygenerovat odkaz, můžete tak učinit kliknutím pravým tlačítkem myši na panel hledání a výběrem Kopírovat hledání jako URL.

Otevřít okno s podrobnostmi o knize na knize v některé knihovně

Syntaxe URL je:

calibre://book-details/Library_Name/book_id

Toto otevře okno s podrobnostmi o knize na zadané knize ze zadané knihovny, aniž by se změnila aktuální knihovna nebo vybraná kniha.

Otevřít poznámky přidružené k autorovi, sérii atd.

Syntaxe URL je:

calibre://book-details/Library_Name/Field_Name/id_Item_Id

Otevře se okno s poznámkami k zadané položce. Nejjednodušší způsob, jak takové adresy URL vytvořit, je zobrazit požadované poznámky v Calibre a kliknutím na tlačítko Kopírovat URL zkopírovat adresu URL do schránky a vložit ji, kam potřebujete.

Zde Field_Name je název sloupce, například authors nebo tags. U sloupců vytvořených uživatelem nahraďte počáteční # v názvu pole podtržítkem, takže #mytags se změní na _mytags.

Kromě zadávání položek podle id pomocí Item_Id je můžete zadat také podle názvu pomocí val_Item_Name nebo hex_Hex_Encoded_Item_Name. Například:

calibre://book-details/Library_Name/authors/val_John%20Doe

Hex kódování URL parametrů

Hex kódování URL parametrů se provádí tak, že se parametry nejprve zakódují jako UTF-8 byty a pak se každý byte nahradí dvěma hexadecimálními znaky reprezentujícími tento byte. Například řetězec „abc“ jsou byty „0x61 0x62 a 0x63“ v UTF-8 a zakódovaná verze tedy bude mít podobu řetězce: „616263“.

© Copyright Kovid Goyal.
Naposledy aktualizováno kvě 03, 2024.

Navigace

- moduly
- další |
- předchozí |
- Spustit »
- Přizpůsobení Calibre

Přizpůsobení Calibre

Calibre má vysoce modulární návrh. Různé jeho části mohou být přizpůsobeny. Zde se dozvíte,:

- jak používat proměnné prostředí a vylepšení k přizpůsobení chování Calibre,

- jak zadat vlastní statické zdroje, jako jsou ikony a šablony, pro přepsání výchozího nastavení,

- jak používat moduly pro přidání funkcí do Calibre,

- jak sdílet motivy ikon a moduly s ostatními uživateli Calibre.

- pro zjištění, jak vytvořit předpisy pro přidávání nových zdrojů online obsahu do Calibre, navštivte oddíl Přidávání oblíbených webových stránek se zprávami.

Poznámka

Pamatujte, že ačkoliv jsou motivy ikon a moduly indexovány a lze je stáhnout prostřednictvím vestavěného nástroje aktualizací Calibre, nejsou součástí Calibre a jejich kanonické umístění pro podporu a zdrojový kód je na fóru Mobileread forums [https://www.mobileread.com/forums/forumdisplay.php?f=166] v jejich vláknech podpory.

- Proměnné prostředí

- Vylepšení

- Přepsání ikon, šablon atd.

- Vytvoření vlastního motivu ikon pro Calibre

- Přizpůsobení Calibre pomocí modulů

Proměnné prostředí

- CALIBRE_CONFIG_DIRECTORY – Nastaví složku, kde jsou uložené konfigurační soubory.

- CALIBRE_TEMP_DIR – Nastaví dočasnou složku používanou Calibre.

- CALIBRE_CACHE_DIRECTORY – Nastaví složku, kterou Calibre používá pro ukládání trvalých dat do mezipaměti mezi relacemi

- CALIBRE_OVERRIDE_DATABASE_PATH – Umožní vám zadat úplnou cestu k metadata.db. Pomocí této proměnné můžete mít metadata.db v jiném umístění než ve složce knihovny. Užitečné, pokud je vaše složka knihovny na síťové jednotce, která nepodporuje uzamykání souboru.

- CALIBRE_DEVELOP_FROM – použito pro spuštění z vývojového prostředí Calibre. Podívejte se na Nastavení vývojového prostředí Calibre.

- CALIBRE_OVERRIDE_LANG – použito pro vynucení jazyka použitého rozhraním (kód jazyka ISO 639).

- CALIBRE_TEST_TRANSLATION - použito pro testování souboru překladu .po (měla by to být cesta k souboru .po)

- CALIBRE_NO_NATIVE_FILEDIALOGS – způsobí, že Calibre nepoužívá nativní dialogová okna souboru pro výběr souborů nebo složek.

- CALIBRE_NO_NATIVE_MENUBAR – způsobí, aby Calibre nevytvářelo nativní (globální) nabídku v Ubuntu Unity a podobných desktopových prostředí Linux. Nabídka je namísto toho umístěna uvnitř okna, jak je obvyklé.

- CALIBRE_USE_SYSTEM_THEME - ve výchozím nastavení na Linuxu používá Calibre svůj vlastní vestavěný styl Qt. Tím je zabráněno pádům a zablokováním způsobeným nekompatibilitou mezi verzí Qt Calibre a systému. Nevýhodou je, že Calibre nemusí odpovídat vzhledu a chování systému. Pokud nastavíte tuto proměnnou prostředí na Linuxu, způsobí to, že Calibre bude používat motiv systému - dejte pozor na pády a zablokování.

- CALIBRE_SHOW_DEPRECATION_WARNINGS – způsobí, že Calibre vytiskne varování o neschválení do stdout. Užitečné pro vývojáře Calibre.

- CALIBRE_NO_DEFAULT_PROGRAMS - zabrání Calibre, aby automaticky registrovalo typy souborů, se kterými je schopné pracovat pomocí Windows.

- QT_QPA_PLATFORM - Na Linuxu toto nastavte na wayland pro vynucení, aby Calibre používalo Wayland a xcb pro vynucení použití X11.

- SYSFS_PATH – Použijte, pokud je sysfs připojeno někde jinde než /sys.

- http_proxy, https_proxy – použito v Linuxu pro zadání proxy HTTP.

Podívejte se na Jak nastavit proměnné prostředí ve Windows [https://www.computerhope.com/issues/ch000549.htm]. Pokud jste na macOS, můžete nastavit proměnné prostředí vytvořením ~/Library/Preferences/calibre/macos-env.txt a uložením proměnných prostředí do něj, každou na jeden řádek, na příklad:

CALIBRE_DEVELOP_FROM=$HOME/calibre-src/src
CALIBRE_NO_NATIVE_FILEDIALOGS=1
CALIBRE_CONFIG_DIRECTORY=~/.config/calibre

Vylepšení

Vylepšení jsou malé změny, které můžete zadat pro určení různých aspektů chování Calibre. Můžete je změnit v Předvolby->Rozšířené->Vylepšení. Výchozí hodnoty pro vylepšení jsou zopakovány níže

#!/usr/bin/env python
# vim:fileencoding=UTF-8:ts=4:sw=4:sta:et:sts=4:ai
# License: GPLv3 Copyright: 2010, Kovid Goyal <kovid at kovidgoyal.net>

# Contains various tweaks that affect calibre behavior. Only edit this file if
# you know what you are doing. If you delete this file, it will be recreated from
# defaults.

#: Auto increment series index
# The algorithm used to assign a book added to an existing series a series number.
# New series numbers assigned using this tweak are always integer values, except
# if a constant non-integer is specified.
# Possible values are:
# next - First available integer larger than the largest existing number
# first_free - First available integer larger than 0
# next_free - First available integer larger than the smallest existing number
# last_free - First available integer smaller than the largest existing number. Return largest existing + 1 if no free number is found
# const - Assign the number 1 always
# no_change - Do not change the series index
# a number - Assign that number always. The number is not in quotes. Note that 0.0 can be used here.
# Examples:
# series_index_auto_increment = 'next'
# series_index_auto_increment = 'next_free'
# series_index_auto_increment = 16.5
#
# Set the use_series_auto_increment_tweak_when_importing tweak to True to
# use the above values when importing/adding books. If this tweak is set to
# False (the default) then the series number will be set to 1 if it is not
# explicitly set during the import. If set to True, then the
# series index will be set according to the series_index_auto_increment setting.
# Note that the use_series_auto_increment_tweak_when_importing tweak is used
# only when a value is not provided during import. If the importing regular
# expression produces a value for series_index, or if you are reading metadata
# from books and the import plugin produces a value, then that value will
# be used irrespective of the setting of the tweak.
series_index_auto_increment = 'next'
use_series_auto_increment_tweak_when_importing = False

#: Add separator after completing an author name
# Set this if the completion separator should be appended to the end of the
# completed text to automatically begin a new completion operation for authors.
# It can be either True or False authors_completer_append_separator = False

#: Author sort name algorithm
# The algorithm used to copy author to author_sort.
# Possible values are:
# invert: use "fn ln" -> "ln, fn"
# copy : copy author to author_sort without modification
# comma : use 'copy' if there is a ',' in the name, otherwise use 'invert'
# nocomma : "fn ln" -> "ln fn" (without the comma)
# When this tweak is changed, the author_sort values stored with each author
# must be recomputed by right-clicking on an author in the left-hand tags
# panel, selecting 'Manage authors', and pressing
# 'Recalculate all author sort values'.
#
# The author_name_suffixes are words that are ignored when they occur at the
# end of an author name. The case of the suffix is ignored and trailing
# periods are automatically handled.
#
# The same is true for author_name_prefixes.
#
# The author_name_copywords are a set of words which, if they occur in an
# author name, cause the automatically generated author sort string to be
# identical to the author's name. This means that the sort for a string like
# "Acme Inc." will be "Acme Inc." instead of "Inc., Acme".
#
# If author_use_surname_prefixes is enabled, any of the words in
# author_surname_prefixes will be treated as a prefix to the surname, if they
# occur before the surname. So for example, "John von Neumann" would be sorted
# as "von Neumann, John" and not "Neumann, John von".
author_sort_copy_method = 'comma'
author_name_suffixes = ('Jr', 'Sr', 'Inc', 'Ph.D', 'Phd',
'MD', 'M.D', 'I', 'II', 'III', 'IV',
'Junior', 'Senior')
author_name_prefixes = ('Mr', 'Mrs', 'Ms', 'Dr', 'Prof')
author_name_copywords = (
'Agency', 'Corporation', 'Company', 'Co.', 'Council',
'Committee', 'Inc.', 'Institute', 'National', 'Society', 'Club', 'Team',
'Software', 'Games', 'Entertainment', 'Media', 'Studios',
)
author_use_surname_prefixes = False author_surname_prefixes = ('da', 'de', 'di', 'la', 'le', 'van', 'von')

#: Splitting multiple author names
# By default, calibre splits a string containing multiple author names on
# ampersands and the words "and" and "with". You can customize the splitting
# by changing the regular expression below. Strings are split on whatever the
# specified regular expression matches, in addition to ampersands.
# Default: r'(?i),?\s+(and|with)\s+'
authors_split_regex = r'(?i),?\s+(and|with)\s+'

#: Use author sort in Tag browser
# Set which author field to display in the Tag browser (the list of authors,
# series, publishers etc on the left hand side). The choices are author and
# author_sort. This tweak affects only what is displayed under the authors
# category in the Tag browser and Content server. Please note that if you set this
# to author_sort, it is very possible to see duplicate names in the list because
# although it is guaranteed that author names are unique, there is no such
# guarantee for author_sort values. Showing duplicates won't break anything, but
# it could lead to some confusion. When using 'author_sort', the tooltip will
# show the author's name.
# Examples:
# categories_use_field_for_author_name = 'author'
# categories_use_field_for_author_name = 'author_sort'
categories_use_field_for_author_name = 'author'

#: Control partitioning of Tag browser
# When partitioning the Tag browser, the format of the subcategory label is
# controlled by a template: categories_collapsed_name_template if sorting by
# name, categories_collapsed_rating_template if sorting by average rating, and
# categories_collapsed_popularity_template if sorting by popularity. There are
# two variables available to the template: first and last. The variable 'first'
# is the initial item in the subcategory, and the variable 'last' is the final
# item in the subcategory. Both variables are 'objects'; they each have multiple
# values that are obtained by using a suffix. For example, first.name for an
# author category will be the name of the author. The sub-values available are:
# name: the printable name of the item
# count: the number of books that references this item
# avg_rating: the average rating of all the books referencing this item
# sort: the sort value. For authors, this is the author_sort for that author
# category: the category (e.g., authors, series) that the item is in.
# Note that the "r'" in front of the { is necessary if there are backslashes
# (\ characters) in the template. It doesn't hurt anything to leave it there
# even if there aren't any backslashes.
categories_collapsed_name_template = r'{first.sort:shorten(4,,0)} - {last.sort:shorten(4,,0)}'
categories_collapsed_rating_template = r'{first.avg_rating:4.2f:ifempty(0)} - {last.avg_rating:4.2f:ifempty(0)}'
categories_collapsed_popularity_template = r'{first.count:d} - {last.count:d}'

#: Specify columns to sort the booklist by on startup
# Provide a set of columns to be sorted on when calibre starts.
# The argument is None if saved sort history is to be used
# otherwise it is a list of column,order pairs. Column is the
# lookup/search name, found using the tooltip for the column
# Order is 0 for ascending, 1 for descending.
# For example, set it to [('authors',0),('title',0)] to sort by
# title within authors.
sort_columns_at_startup = None

#: Control how dates are displayed
# Format to be used for publication date and the timestamp (date).
# A string controlling how the publication date is displayed in the GUI
# d the day as number without a leading zero (1 to 31)
# dd the day as number with a leading zero (01 to 31)
# ddd the abbreviated localized day name (e.g. 'Mon' to 'Sun').
# dddd the long localized day name (e.g. 'Monday' to 'Sunday').
# M the month as number without a leading zero (1-12)
# MM the month as number with a leading zero (01-12)
# MMM the abbreviated localized month name (e.g. 'Jan' to 'Dec').
# MMMM the long localized month name (e.g. 'January' to 'December').
# yy the year as two digit number (00-99)
# yyyy the year as four digit number
# h the hours without a leading 0 (0 to 11 or 0 to 23, depending on am/pm) '
# hh the hours with a leading 0 (00 to 11 or 00 to 23, depending on am/pm) '
# m the minutes without a leading 0 (0 to 59) '
# mm the minutes with a leading 0 (00 to 59) '
# s the seconds without a leading 0 (0 to 59) '
# ss the seconds with a leading 0 (00 to 59) '
# ap use a 12-hour clock instead of a 24-hour clock, with "ap" replaced by the localized string for am or pm
# AP use a 12-hour clock instead of a 24-hour clock, with "AP" replaced by the localized string for AM or PM
# iso the date with time and timezone. Must be the only format present
# For example, given the date of 9 Jan 2010, the following formats show
# MMM yyyy ==> Jan 2010 yyyy ==> 2010 dd MMM yyyy ==> 09 Jan 2010
# MM/yyyy ==> 01/2010 d/M/yy ==> 9/1/10 yy ==> 10
#
# publication default if not set: MMM yyyy
# timestamp default if not set: dd MMM yyyy
# last_modified_display_format if not set: dd MMM yyyy gui_pubdate_display_format = 'MMM yyyy'
gui_timestamp_display_format = 'dd MMM yyyy'
gui_last_modified_display_format = 'dd MMM yyyy'

#: Control sorting of titles and series in the library display
# Control title and series sorting in the library view. If set to
# 'library_order', the title sort field will be used instead of the title.
# Unless you have manually edited the title sort field, leading articles such as
# The and A will be ignored. If set to 'strictly_alphabetic', the titles will be
# sorted as-is (sort by title instead of title sort). For example, with
# library_order, The Client will sort under 'C'. With strictly_alphabetic, the
# book will sort under 'T'.
# This flag affects calibre's library display. It has no effect on devices. In
# addition, titles for books added before changing the flag will retain their
# order until the title is edited. Editing a title and hitting Enter
# without changing anything is sufficient to change the sort. Or you can use
# the 'Update title sort' action in the Bulk metadata edit dialog to update
# it for many books at once.
title_series_sorting = 'library_order'

#: Control formatting of title and series when used in templates
# Control how title and series names are formatted when saving to disk/sending
# to device. The behavior depends on the field being processed. If processing
# title, then if this tweak is set to 'library_order', the title will be
# replaced with title_sort. If it is set to 'strictly_alphabetic', then the
# title will not be changed. If processing series, then if set to
# 'library_order', articles such as 'The' and 'An' will be moved to the end. If
# set to 'strictly_alphabetic', the series will be sent without change.
# For example, if the tweak is set to library_order, "The Lord of the Rings"
# will become "Lord of the Rings, The". If the tweak is set to
# strictly_alphabetic, it would remain "The Lord of the Rings". Note that the
# formatter function raw_field will return the base value for title and
# series regardless of the setting of this tweak.
save_template_title_series_sorting = 'library_order'

#: Set the list of words considered to be "articles" for sort strings
# Set the list of words that are to be considered 'articles' when computing the
# title sort strings. The articles differ by language. By default, calibre uses
# a combination of articles from English and whatever language the calibre user
# interface is set to. In addition, in some contexts where the book language is
# available, the language of the book is used. You can change the list of
# articles for a given language or add a new language by editing
# per_language_title_sort_articles. To tell calibre to use a language other
# than the user interface language, set, default_language_for_title_sort. For
# example, to use German, set it to 'deu'. A value of None means the user
# interface language is used. The setting title_sort_articles is ignored
# (present only for legacy reasons).
per_language_title_sort_articles = {
# English
'eng' : (r'A\s+', r'The\s+', r'An\s+'),
# Esperanto
'epo': (r'La\s+', r"L'", 'L´'),
# Spanish
'spa' : (r'El\s+', r'La\s+', r'Lo\s+', r'Los\s+', r'Las\s+', r'Un\s+', r'Una\s+', r'Unos\s+', r'Unas\s+'),
# French
'fra' : (r'Le\s+', r'La\s+', r"L'", u'L´', u'L’', r'Les\s+', r'Un\s+', r'Une\s+', r'Des\s+', r'De\s+La\s+', r'De\s+', r"D'", r'D´', r'D’'),
# Polish
'pol': (),
# Italian
'ita': ('Lo\\s+', 'Il\\s+', "L'", 'L´', 'La\\s+', 'Gli\\s+',
'I\\s+', 'Le\\s+', 'Uno\\s+', 'Un\\s+', 'Una\\s+', "Un'",
'Un´', 'Dei\\s+', 'Degli\\s+', 'Delle\\s+', 'Del\\s+',
'Della\\s+', 'Dello\\s+', "Dell'", 'Dell´'),
# Portuguese
'por' : (r'A\s+', r'O\s+', r'Os\s+', r'As\s+', r'Um\s+', r'Uns\s+', r'Uma\s+', r'Umas\s+', ),
# Romanian
'ron' : (r'Un\s+', r'O\s+', r'Nişte\s+', ),
# German
'deu' : (r'Der\s+', r'Die\s+', r'Das\s+', r'Den\s+', r'Ein\s+', r'Eine\s+', r'Einen\s+', r'Dem\s+', r'Des\s+', r'Einem\s+', r'Eines\s+'),
# Dutch
'nld' : (r'De\s+', r'Het\s+', r'Een\s+', r"'n\s+", r"'s\s+", r'Ene\s+', r'Ener\s+', r'Enes\s+', r'Den\s+', r'Der\s+', r'Des\s+', r"'t\s+"),
# Swedish
'swe' : (r'En\s+', r'Ett\s+', r'Det\s+', r'Den\s+', r'De\s+', ),
# Turkish
'tur' : (r'Bir\s+', ),
# Afrikaans
'afr' : (r"'n\s+", r'Die\s+', ),
# Greek
'ell' : (r'O\s+', r'I\s+', r'To\s+', r'Ta\s+', r'Tus\s+', r'Tis\s+', r"'Enas\s+", r"'Mia\s+", r"'Ena\s+", r"'Enan\s+", ),
# Hungarian
'hun' : (r'A\s+', r'Az\s+', r'Egy\s+',),
}
default_language_for_title_sort = None title_sort_articles=r'^(A|The|An)\s+'

#: Specify a folder calibre should connect to at startup
# Specify a folder that calibre should connect to at startup using
# connect_to_folder. This must be a full path to the folder. If the folder does
# not exist when calibre starts, it is ignored.
# Example for Windows:
# auto_connect_to_folder = 'C:/Users/someone/Desktop/testlib'
# Example for other operating systems:
# auto_connect_to_folder = '/home/dropbox/My Dropbox/someone/library'
auto_connect_to_folder = ''

#: Specify renaming rules for SONY collections
# Specify renaming rules for SONY collections. This tweak is only applicable if
# metadata management is set to automatic. Collections on SONYs are named
# depending upon whether the field is standard or custom. A collection derived
# from a standard field is named for the value in that field.
#
# For example, if the standard 'series' column contains the value 'Darkover', then the
# collection name is 'Darkover'. A collection derived from a custom field will
# have the name of the field added to the value. For example, if a custom series
# column named 'My Series' contains the name 'Darkover', then the collection
# will by default be named 'Darkover (My Series)'. For purposes of this
# documentation, 'Darkover' is called the value and 'My Series' is called the
# category. If two books have fields that generate the same collection name,
# then both books will be in that collection.
#
# This set of tweaks lets you specify for a standard or custom field how
# the collections are to be named. You can use it to add a description to a
# standard field, for example 'Foo (Tag)' instead of the 'Foo'. You can also use
# it to force multiple fields to end up in the same collection.
#
# For example, you could force the values in 'series', '#my_series_1', and
# '#my_series_2' to appear in collections named 'some_value (Series)', thereby
# merging all of the fields into one set of collections.
#
# There are two related tweaks. The first determines the category name to use
# for a metadata field. The second is a template, used to determines how the
# value and category are combined to create the collection name.
# The syntax of the first tweak, sony_collection_renaming_rules, is:
# {'field_lookup_name':'category_name_to_use', 'lookup_name':'name', ...}
#
# The second tweak, sony_collection_name_template, is a template. It uses the
# same template language as plugboards and save templates. This tweak controls
# how the value and category are combined together to make the collection name.
# The only two fields available are {category} and {value}. The {value} field is
# never empty. The {category} field can be empty. The default is to put the
# value first, then the category enclosed in parentheses, it isn't empty:
# '{value} {category:|(|)}'
#
# Examples: The first three examples assume that the second tweak
# has not been changed.
#
# 1) I want three series columns to be merged into one set of collections. The
# column lookup names are 'series', '#series_1' and '#series_2'. I want nothing
# in the parenthesis. The value to use in the tweak value would be:
# sony_collection_renaming_rules={'series':'', '#series_1':'', '#series_2':''}
#
# 2) I want the word '(Series)' to appear on collections made from series, and
# the word '(Tag)' to appear on collections made from tags. Use:
# sony_collection_renaming_rules={'series':'Series', 'tags':'Tag'}
#
# 3) I want 'series' and '#myseries' to be merged, and for the collection name
# to have '(Series)' appended. The renaming rule is:
# sony_collection_renaming_rules={'series':'Series', '#myseries':'Series'}
#
# 4) Same as example 2, but instead of having the category name in parentheses
# and appended to the value, I want it prepended and separated by a colon, such
# as in Series: Darkover. I must change the template used to format the category name
#
# The resulting two tweaks are:
# sony_collection_renaming_rules={'series':'Series', 'tags':'Tag'}
# sony_collection_name_template='{category:||: }{value}'
sony_collection_renaming_rules={}
sony_collection_name_template='{value}{category:| (|)}'

#: Specify how SONY collections are sorted
# Specify how SONY collections are sorted. This tweak is only applicable if
# metadata management is set to automatic. You can indicate which metadata is to
# be used to sort on a collection-by-collection basis. The format of the tweak
# is a list of metadata fields from which collections are made, followed by the
# name of the metadata field containing the sort value.
# Example: The following indicates that collections built from pubdate and tags
# are to be sorted by the value in the custom column '#mydate', that collections
# built from 'series' are to be sorted by 'series_index', and that all other
# collections are to be sorted by title. If a collection metadata field is not
# named, then if it is a series- based collection it is sorted by series order,
# otherwise it is sorted by title order.
# [(['pubdate', 'tags'],'#mydate'), (['series'],'series_index'), (['*'], 'title')]
# Note that the bracketing and parentheses are required. The syntax is
# [ ( [list of fields], sort field ) , ( [ list of fields ] , sort field ) ]
# Default: empty (no rules), so no collection attributes are named.
sony_collection_sorting_rules = []

#: Control how tags are applied when copying books to another library
# Set this to True to ensure that tags in 'Tags to add when adding
# a book' are added when copying books to another library add_new_book_tags_when_importing_books = False

#: Set the maximum number of sort 'levels'
# Set the maximum number of sort 'levels' that calibre will use to resort the
# library after certain operations such as searches or device insertion. Each
# sort level adds a performance penalty. If the database is large (thousands of
# books) the penalty might be noticeable. If you are not concerned about multi-
# level sorts, and if you are seeing a slowdown, reduce the value of this tweak.
maximum_resort_levels = 5

#: Choose whether dates are sorted using visible fields
# Date values contain both a date and a time. When sorted, all the fields are
# used, regardless of what is displayed. Set this tweak to True to use only
# the fields that are being displayed.
sort_dates_using_visible_fields = False

#: Fuzz value for trimming covers
# The value used for the fuzz distance when trimming a cover.
# Colors within this distance are considered equal.
# The distance is in absolute intensity units.
cover_trim_fuzz_value = 10

#: Control behavior of the book list
# You can control the behavior of double clicks and pressing Enter on the books
# list. Choices: open_viewer, do_nothing, show_book_details,
# show_locked_book_details, edit_cell, edit_metadata. Selecting anything other
# than open_viewer, show_book_details, or show_locked_book_details has the side
# effect of disabling editing a field using a single click.
# Default: open_viewer.
# Example: doubleclick_on_library_view = 'do_nothing'
# You can also control whether the book list scrolls per item or
# per pixel. Default is per item.
doubleclick_on_library_view = 'open_viewer'
enter_key_behavior = 'do_nothing'
horizontal_scrolling_per_column = False vertical_scrolling_per_row = False

#: Language to use when sorting
# Setting this tweak will force sorting to use the
# collating order for the specified language. This might be useful if you run
# calibre in English but want sorting to work in the language where you live.
# Set the tweak to the desired ISO 639-1 language code, in lower case.
# You can find the list of supported locales at
# https://en.wikipedia.org/wiki/List_of_ISO_639-1_codes
# Default: locale_for_sorting = '' -- use the language calibre displays in
# Example: locale_for_sorting = 'fr' -- sort using French rules.
# Example: locale_for_sorting = 'nb' -- sort using Norwegian rules.
locale_for_sorting = ''

#: The number of seconds to wait before sending emails
# The number of seconds to wait before sending emails when using a
# public email server like GMX/Hotmail/Gmail. Default is: 5 minutes
# Setting it to lower may cause the server's SPAM controls to kick in,
# making email sending fail. Changes will take effect only after a restart of
# calibre. You can also change the list of hosts that calibre considers
# to be public relays here. Any relay host ending with one of the suffixes
# in the list below will be considered a public email server.
public_smtp_relay_delay = 301 public_smtp_relay_host_suffixes = ['gmail.com', 'live.com', 'gmx.com', 'outlook.com']

#: The maximum width and height for covers saved in the calibre library
# All covers in the calibre library will be resized, preserving aspect ratio,
# to fit within this size. This is to prevent slowdowns caused by extremely
# large covers maximum_cover_size = (1650, 2200)

#: Where to send downloaded news
# When automatically sending downloaded news to a connected device, calibre
# will by default send it to the main memory. By changing this tweak, you can
# control where it is sent. Valid values are "main", "carda", "cardb". Note
# that if there isn't enough free space available on the location you choose,
# the files will be sent to the location with the most free space.
send_news_to_device_location = "main"

#: Unified toolbar on macOS
# If you enable this option and restart calibre, the toolbar will be 'unified'
# with the titlebar as is normal for macOS applications. However, doing this has
# various bugs, for instance the minimum width of the toolbar becomes twice
# what it should be and it causes other random bugs on some systems, so turn it
# on at your own risk!
unified_title_toolbar_on_osx = False

#: Save original file when converting/polishing from same format to same format
# When calibre does a conversion from the same format to the same format, for
# example, from EPUB to EPUB, the original file is saved, so that in case the
# conversion is poor, you can tweak the settings and run it again. By setting
# this to False you can prevent calibre from saving the original file.
# Similarly, by setting save_original_format_when_polishing to False you can
# prevent calibre from saving the original file when polishing.
save_original_format = True save_original_format_when_polishing = True

#: Number of recently viewed books to show
# Right-clicking the "View" button shows a list of recently viewed books. Control
# how many should be shown, here.
gui_view_history_size = 15

#: Change the font size of the Book details panel in the interface
# Change the font size at which book details are rendered in the side panel and
# comments are rendered in the metadata edit dialog. Set it to a positive or
# negative number to increase or decrease the font size.
change_book_details_font_size_by = 0

#: What format to default to when using the "Unpack book" feature
# The "Unpack book" feature of calibre allows direct editing of a book format.
# If multiple formats are available, calibre will offer you a choice
# of formats, defaulting to your preferred output format if it is available.
# Set this tweak to a specific value of 'EPUB' or 'AZW3' to always default
# to that format rather than your output format preference.
# Set to a value of 'remember' to use whichever format you chose last time you
# used the "Unpack book" feature.
# Examples:
# default_tweak_format = None (Use output format)
# default_tweak_format = 'EPUB'
# default_tweak_format = 'remember'
default_tweak_format = None

#: Do not preselect a completion when editing authors/tags/series/etc.
# This means that you can make changes and press Enter and your changes will
# not be overwritten by a matching completion. However, if you wish to use the
# completions you will now have to press Tab to select one before pressing
# Enter. Which technique you prefer will depend on the state of metadata in
# your library and your personal editing style.
#
# If preselect_first_completion is False and you want Tab to accept what you
# typed instead of the first completion then set tab_accepts_uncompleted_text
# to True. If you do this then to select from the completions you must press
# the Down or Up arrow keys. The tweak tab_accepts_uncompleted_text is ignored
# if preselect_first_completion is True preselect_first_completion = False tab_accepts_uncompleted_text = False

#: Completion mode when editing authors/tags/series/etc.
# By default, when completing items, calibre will show you all the candidates
# that start with the text you have already typed. You can instead have it show
# all candidates that contain the text you have already typed. To do this, set
# completion_mode to 'contains'. For example, if you type asi it will match both
# Asimov and Quasimodo, whereas the default behavior would match only Asimov.
completion_mode = 'prefix'

#: Sort the list of libraries alphabetically
# The list of libraries in the Copy to library and Quick switch menus are
# normally sorted by most used. However, if there are more than a certain
# number of such libraries, the sorting becomes alphabetic. You can set that
# number here. The default is ten libraries.
many_libraries = 10

#: Choose available output formats for conversion
# Restrict the list of available output formats in the conversion dialogs.
# For example, if you only want to convert to EPUB and AZW3, change this to
# restrict_output_formats = ['EPUB', 'AZW3']. The default value of None causes
# all available output formats to be present.
restrict_output_formats = None

#: Set the thumbnail image quality used by the Content server
# The quality of a thumbnail is largely controlled by the compression quality
# used when creating it. Set this to a larger number to improve the quality.
# Note that the thumbnails get much larger with larger compression quality
# numbers.
# The value can be between 50 and 99 content_server_thumbnail_compression_quality = 75

#: Image file types to treat as e-books when dropping onto the "Book details" panel
# Normally, if you drop any image file in a format known to calibre onto the
# "Book details" panel, it will be used to set the cover. If you want to store
# some image types as e-books instead, you can set this tweak.
# Examples:
# cover_drop_exclude = {'tiff', 'webp'}
cover_drop_exclude = ()

#: Exclude fields when copy/pasting metadata
# You can ask calibre to not paste some metadata fields when using the
# Edit metadata->Copy metadata/Paste metadata actions. For example,
# exclude_fields_on_paste = ['cover', 'timestamp', '#mycolumn']
# to prevent pasting of the cover, Date and custom column, mycolumn.
# You can also add a shortcut in Preferences->Shortcuts->Edit metadata
# to paste metadata ignoring this tweak.
exclude_fields_on_paste = []

#: Skip internet connected check
# Skip checking whether the internet is available before downloading news.
# Useful if for some reason your operating systems network checking
# facilities are not reliable (for example NetworkManager on Linux).
skip_network_check = False

#: Tab stop width in the template editor
# Sets the width of the tab stop in the template editor in "average characters".
# For example, a value of 1 results in a space with the width of one average character.
template_editor_tab_stop_width = 4

#: Value for undefined numbers when sorting
# Sets the value to use for undefined numbers when sorting.
# For example, the value -10 sorts undefined numbers as if they were set to -10.
# Use 'maximum' for the largest possible number. Use 'minimum' for the smallest
# possible number. Quotes are optional if entering a number.
# Examples:
# value_for_undefined_numbers_when_sorting = -100
# value_for_undefined_numbers_when_sorting = '2'
# value_for_undefined_numbers_when_sorting = -0.01
# value_for_undefined_numbers_when_sorting = 'minimum'
# value_for_undefined_numbers_when_sorting = 'maximum'
value_for_undefined_numbers_when_sorting = 0

#: Allow template database functions in composite columns
# If True then the template database functions book_values() and book_count()
# can be used in composite custom columns. Note: setting this tweak to True and
# using these functions in composites can be very slow.
# Default: False allow_template_database_functions_in_composites = False

#: Change the programs that are run when opening files/URLs
# By default, calibre passes URLs to the operating system to open using
# whatever default programs are configured there. Here you can override
# that by specifying the program to use, per URL type. For local files,
# the type is "file" and for web links it is "http*". For example:
# openers_by_scheme = { "http*": "firefox %u" } will make calibre run Firefox
# for https://whatever URLs. %u is replaced by the URL to be opened. The scheme
# takes a glob pattern allowing a single entry to match multiple URL types.
openers_by_scheme = {}

#: Set the first day of the week for calendar popups
# It must be one of the values Default, Sunday, Monday, Tuesday, Wednesday,
# Thursday, Friday, or Saturday, all in English, spelled exactly as shown.
calendar_start_day_of_week = 'Default'

Přepsání ikon, šablon atd.

Poznámka

Calibre má přímou podporu pro motivy ikon, je pro něj dostupných několik motivů ikon, které můžete použít přejitím na Předvolby → Rozhraní → Vzhled a chování → Změnit motiv ikon. Je vhodnější používat motivy ikon než přepisovat jednotlivé ikony.

Calibre vám umožňuje přepsat statické zdroje, jako jsou ikony, JavaScript a šablony, pro přebal s metadaty, katalogy atd. upravenými verzemi, které se vám líbí. Všechny statické zdroje jsou uložené v podsložce resources instalačního umístění Calibre. Ve Windows je to obvykle C:\Program Files\Calibre2\app\resources. V macOS /Applications/calibre.app/Contents/Resources/resources/. V Linuxu, pokud používáte binární instalační balíček z webové stránky Calibre, to bude /opt/calibre/resources. Tyto cesty se můžou měnit v závislosti na tom, kam si zvolíte nainstalovat Calibre.

Soubory v této složce zdrojů byste neměli měnit, protože vaše změny budou přepsaány při příští aktualizaci Calibre. Namísto toho přejděte do Předvolby → Rozšířené → Různé a klikněte na Otevřít konfigurační složku Calibre. V této konfigurační složce vytvořte podsložku nazvanou resources a umístěte do ní soubory, které chcete přepsat. Umístěte soubory do příslušných podsložek, například obrázky umístěte do resources/images atd. Calibre automaticky použije při příštím spuštění váš vlastní soubor přednostně před vestavěným.

Například pokud chcete změnit ikonu pro akci Odebrat knihy, nejdříve byste se podívali do vestavěné složky resources a viděli byste, že odpovídající soubor je resources/images/remove_books.png. Pokud máte alternativní ikonu ve formátu PNG nazvanou my_remove_books.png, uložili byste ji do konfigurační složky jako resources/images/remove_books.png. Všechny ikony použité uživatelským rozhraním Calibre jsou v resources/images a jeho podsložkách. Umístění přepisujícího souboru sem bude mít ještě vyšší prioritu než vlastní motiv ikony.

Vytvoření vlastního motivu ikon pro Calibre

Pokud jste vytvořili krásnou sadu ikon a chcete ji sdílet s ostatními uživateli Calibre prostřednictvím vestavěného podpory motivů ikon Calibre, můžete snadno zabalit vaše ikony do motivu. K tomu přejděte na Předvolby → Různé → Vytvořit motiv ikon, vyberte složku, kam jste vložili své ikony. Pak vyplňte metadata motivu a klikněte na OK. Vznikne tak soubor ZIP obsahující ikony motivu. Ten můžete nahrát na fórum Calibre na Mobileread [https://www.mobileread.com/forums/forumdisplay.php?f=166] a já pak zpřístupním váš motiv prostřednictvím vestavěného systému motivu ikon Calibre. Ve výchozím nastavení se právě vytvořené ikony motivu nainstalují jako aktuální motiv v Calibre. Pokud svůj motiv testujete, nezapomeňte odebrat obrázky ze složky resources/images, aby se použily ikony z motivu.

Od Calibre 6 můžete mít vlastní ikony pro světlý a tmavý režim. Jednoduše vytvořte dvě verze ikony a pojmenujte soubory s příponou -for-dark-theme a -for-light-theme. Například modified-for-dark-theme.png a modified-for-light-theme.png. Calibre pak automaticky použije příslušnou ikonu na základě aktuálního motivu.

Přizpůsobení Calibre pomocí modulů

Calibre má velice modulární provedení. Téměř všechny funkce jsou ve formě modulů. Moduly jsou použity pro převod, stahování zpráv (ačkoliv ty jsou nazvány předpisy), různé komponenty uživatelského rozhraní, připojení k různým zařízením, zpracování souborů při jejich přidávání do Calibre a tak dále. Kompletní seznam všech vestavěných modulů můžete získat v Calibre v Předvolby → Rozšířené → Moduly.

Pro přizpůsobení a rozšíření chování Calibre můžete napsat své vlastní moduly. Architektura modulu v Calibre je velice jednoduchá, podívejte se na kurz Psaní vlastních modulů pro rozšíření funkcí Calibre.

Jakmile napíšete modul, můžete jej nahrát na fórum modulů Calibre na Mobileread [https://www.mobileread.com/forums/forumdisplay.php?f=237] a bude dostupný prostřednictvím vestavěného nástroje pro aktualizaci modulů Calibre.

© Copyright Kovid Goyal.
Naposledy aktualizováno kvě 03, 2024.

Navigace

- moduly
- další |
- předchozí |
- Spustit »
- Přizpůsobení Calibre »
- Dokumentace API pro moduly

Dokumentace API pro moduly

Definuje různé abstraktní základní třídy, které mohou být podtřídami pro vytvoření výkonných modulů. Užitečné třídy jsou:

- Modul

- ModulTypuSouboru

- Moduly metadat

- Moduly katalogu

- Moduly stahování metadat

- Moduly převodu

- Ovladače zařízení

- User interface actions

- Moduly předvoleb

Modul
class calibre.customize.Plugin(plugin_path)[zdroj]

A calibre plugin. Useful members include:

- self.installation_type: Stores how the plugin was installed.

- self.plugin_path: Stores path to the ZIP file that contains

this plugin or None if it is a builtin plugin

- self.site_customization: Stores a customization string entered

uživatelem.

Methods that should be overridden in sub classes:

- initialize()

- customization_help()

Useful methods:

- temporary_file()

- __enter__()

- load_resources()

supported_platforms = []

List of platforms this plugin works on.
For example: ['windows', 'osx', 'linux']

name = 'Trivial Plugin'

The name of this plugin. You must set it something other than Trivial Plugin for it to work.

version = (1, 0, 0)

The version of this plugin as a 3-tuple (major, minor, revision)

description = 'Nedělá vůbec nic'

A short string describing what this plugin does

author = 'Neznámý'

Autor doplňku

priority = 1

When more than one plugin exists for a filetype, the plugins are run in order of decreasing priority.
Plugins with higher priority will be run first.
The highest possible priority is sys.maxsize.
Default priority is 1.

minimum_calibre_version = (0, 4, 118)

The earliest version of calibre this plugin requires

installation_type = None

The way this plugin is installed

can_be_disabled = True

If False, the user will not be able to disable this plugin. Use with care.

type = 'Základ'

The type of this plugin. Used for categorizing plugins in the
GUI

initialize()[zdroj]

Called once when calibre plugins are initialized. Plugins are re-initialized every time a new plugin is added. Also note that if the plugin is run in a worker process, such as for adding books, then the plugin will be initialized for every new worker process.

Perform any plugin specific initialization here, such as extracting resources from the plugin ZIP file. The path to the ZIP file is available as self.plugin_path.

Note that self.site_customization is not available at this point.

config_widget()[zdroj]

Implement this method and save_settings() in your plugin to use a custom configuration dialog, rather then relying on the simple string based default customization.

This method, if implemented, must return a QWidget. The widget can have an optional method validate() that takes no arguments and is called immediately after the user clicks OK. Changes are applied if and only if the method returns True.

If for some reason you cannot perform the configuration at this time, return a tuple of two strings (message, details), these will be displayed as a warning dialog to the user and the process will be aborted.

save_settings(config_widget)[zdroj]

Save the settings specified by the user with config_widget.

Parametry:

config_widget – The widget returned by config_widget().

do_user_config(parent=None)[zdroj]

This method shows a configuration dialog for this plugin. It returns
True if the user clicks OK, False otherwise. The changes are automatically applied.

load_resources(names)[zdroj]

If this plugin comes in a ZIP file (user added plugin), this method will allow you to load resources from the ZIP file.

For example to load an image:

pixmap = QPixmap()
pixmap.loadFromData(self.load_resources(['images/icon.png'])['images/icon.png'])
icon = QIcon(pixmap)

Parametry:

names – List of paths to resources in the ZIP file using / as separator

Vrací:

A dictionary of the form {name: file_contents}. Any names that were not found in the ZIP file will not be present in the dictionary.

customization_help(gui=False)[zdroj]

Return a string giving help on how to customize this plugin.
By default raise a NotImplementedError, which indicates that the plugin does not require customization.

If you re-implement this method in your subclass, the user will be asked to enter a string as customization for this plugin.
The customization string will be available as self.site_customization.

Site customization could be anything, for example, the path to a needed binary on the user’s computer.

Parametry:

gui – If True return HTML help, otherwise return plain text help.

temporary_file(suffix)[zdroj]

Return a file-like object that is a temporary file on the file system.
This file will remain available even after being closed and will only be removed on interpreter shutdown. Use the name member of the returned object to access the full path to the created temporary file.

Parametry:

suffix – The suffix that the temporary file will have.

cli_main(args)[zdroj]

This method is the main entry point for your plugins command line interface. It is called when the user does: calibre-debug -r „Plugin
Name“. Any arguments passed are present in the args variable.

ModulTypuSouboru
class calibre.customize.FileTypePlugin(plugin_path)[zdroj]

Bases: Plugin

A plugin that is associated with a particular set of file types.

file_types = {}

Set of file types for which this plugin should be run.
Use ‚*‘ for all file types.
For example: {'lit', 'mobi', 'prc'}

on_import = False

If True, this plugin is run when books are added to the database

on_postimport = False

If True, this plugin is run after books are added to the database. In this case the postimport and postadd methods of the plugin are called.

on_postconvert = False

If True, this plugin is run after a book is converted.
In this case the postconvert method of the plugin is called.

on_postdelete = False

If True, this plugin is run after a book file is deleted from the database. In this case the postdelete method of the plugin is called.

on_preprocess = False

If True, this plugin is run just before a conversion

on_postprocess = False

If True, this plugin is run after conversion on the final file produced by the conversion output plugin.

type = 'Typ souboru'

The type of this plugin. Used for categorizing plugins in the
GUI

run(path_to_ebook)[zdroj]

Run the plugin. Must be implemented in subclasses.
It should perform whatever modifications are required on the e-book and return the absolute path to the modified e-book. If no modifications are needed, it should return the path to the original e-book. If an error is encountered it should raise an Exception. The default implementation simply return the path to the original e-book. Note that the path to the original file (before any file type plugins are run, is available as self.original_path_to_file).

The modified e-book file should be created with the temporary_file() method.

Parametry:

path_to_ebook – Absolute path to the e-book.

Vrací:

Absolute path to the modified e-book.

postimport(book_id, book_format, db)[zdroj]

Called post import, i.e., after the book file has been added to the database. Note that this is different from postadd() which is called when the book record is created for the first time. This method is called whenever a new file is added to a book record. It is useful for modifying the book record based on the contents of the newly added file.

Parametry:

- book_id – Database id of the added book.

- book_format – The file type of the book that was added.

- db – Library database.

postconvert(book_id, book_format, db)[zdroj]

Called post conversion, i.e., after the conversion output book file has been added to the database.
Note that it is run after a conversion only, not after a book is added. It is useful for modifying the book record based on the contents of the newly added file.

Parametry:

- book_id – Database id of the added book.

- book_format – The file type of the book that was added.

- db – Library database.

postdelete(book_id, book_format, db)[zdroj]

Called post deletion, i.e., after the book file has been deleted from the database. Note that it is not run when a book record is deleted, only when one or more formats from the book are deleted. It is useful for modifying the book record based on the format of the deleted file.

Parametry:

- book_id – Database id of the added book.

- book_format – The file type of the book that was added.

- db – Library database.

postadd(book_id, fmt_map, db)[zdroj]

Called post add, i.e. after a book has been added to the db. Note that this is different from postimport(), which is called after a single book file has been added to a book. postadd() is called only when an entire book record with possibly more than one book file has been created for the first time.
This is useful if you wish to modify the book record in the database when the book is first added to calibre.

Parametry:

- book_id – Database id of the added book.

- fmt_map – Map of file format to path from which the file format was added. Note that this might or might not point to an actual existing file, as sometimes files are added as streams. In which case it might be a dummy value or a non-existent path.

- db – Library database

Moduly metadat

class calibre.customize.MetadataReaderPlugin(*args, **kwargs)[zdroj]

Bases: Plugin

A plugin that implements reading metadata from a set of file types.

file_types = {}

Set of file types for which this plugin should be run.
For example: set(['lit', 'mobi', 'prc'])

supported_platforms = ['windows', 'osx', 'linux']

List of platforms this plugin works on.
For example: ['windows', 'osx', 'linux']

version = (7, 10, 0)

The version of this plugin as a 3-tuple (major, minor, revision)

author = 'Kovid Goyal'

Autor doplňku

type = 'Čtení metadat'

The type of this plugin. Used for categorizing plugins in the
GUI

get_metadata(stream, type)[zdroj]

Return metadata for the file represented by stream (a file like object that supports reading). Raise an exception when there is an error with the input data.

Parametry:

type – The type of file. Guaranteed to be one of the entries in file_types.

Vrací:

A calibre.ebooks.metadata.book.Metadata object

class calibre.customize.MetadataWriterPlugin(*args, **kwargs)[zdroj]

Bases: Plugin

A plugin that implements reading metadata from a set of file types.

file_types = {}

Set of file types for which this plugin should be run.
For example: set(['lit', 'mobi', 'prc'])

supported_platforms = ['windows', 'osx', 'linux']

List of platforms this plugin works on.
For example: ['windows', 'osx', 'linux']

version = (7, 10, 0)

The version of this plugin as a 3-tuple (major, minor, revision)

author = 'Kovid Goyal'

Autor doplňku

type = 'Zápis metadat'

The type of this plugin. Used for categorizing plugins in the
GUI

set_metadata(stream, mi, type)[zdroj]

Set metadata for the file represented by stream (a file like object that supports reading). Raise an exception when there is an error with the input data.

Parametry:

- type – The type of file. Guaranteed to be one of the entries in file_types.

- mi – A calibre.ebooks.metadata.book.Metadata object

Moduly katalogu

class calibre.customize.CatalogPlugin(plugin_path)[zdroj]

Bases: Plugin

A plugin that implements a catalog generator.

file_types = {}

Output file type for which this plugin should be run.
For example: ‚epub‘ or ‚xml‘

type = 'Generátor katalogu'

The type of this plugin. Used for categorizing plugins in the
GUI

cli_options = []

CLI parser options specific to this plugin, declared as namedtuple Option:

from collections import namedtuple
Option = namedtuple(‚Option‘, ‚option, default, dest, help‘)
cli_options = [Option(‘–catalog-title‘, default = ‚My Catalog‘, dest = ‚catalog_title‘, help = (_(‚Title of generated catalog. nDefault:‘) + „ ‚“ + ‚%default‘ + „‘“))]
cli_options parsed in calibre.db.cli.cmd_catalog:option_parser()

initialize()[zdroj]

If plugin is not a built-in, copy the plugin’s .ui and .py files from the ZIP file to $TMPDIR.
Tab will be dynamically generated and added to the Catalog Options dialog in calibre.gui2.dialogs.catalog.py:Catalog

run(path_to_output, opts, db, ids, notification=None)[zdroj]

Run the plugin. Must be implemented in subclasses.
It should generate the catalog in the format specified in file_types, returning the absolute path to the generated catalog file. If an error is encountered it should raise an Exception.

The generated catalog file should be created with the temporary_file() method.

Parametry:

- path_to_output – Absolute path to the generated catalog file.

- opts – A dictionary of keyword arguments

- db – A LibraryDatabase2 object

Moduly stahování metadat
class calibre.ebooks.metadata.sources.base.Source(*args, **kwargs)[zdroj]

Bases: Plugin

type = 'Zdroj metadat'

The type of this plugin. Used for categorizing plugins in the
GUI

author = 'Kovid Goyal'

Autor doplňku

supported_platforms = ['windows', 'osx', 'linux']

List of platforms this plugin works on.
For example: ['windows', 'osx', 'linux']

capabilities = frozenset({})

Set of capabilities supported by this plugin.
Useful capabilities are: ‚identify‘, ‚cover‘

touched_fields = frozenset({})

List of metadata fields that can potentially be download by this plugin during the identify phase

has_html_comments = False

Set this to True if your plugin returns HTML formatted comments

supports_gzip_transfer_encoding = False

Setting this to True means that the browser object will indicate that it supports gzip transfer encoding. This can speedup downloads but make sure that the source actually supports gzip transfer encoding correctly first

ignore_ssl_errors = False

Set this to True to ignore HTTPS certificate errors when connecting to this source.

cached_cover_url_is_reliable = True

Cached cover URLs can sometimes be unreliable (i.e. the download could fail or the returned image could be bogus). If that is often the case with this source, set to False

options = ()

A list of Option objects. They will be used to automatically construct the configuration widget for this plugin

config_help_message = None

A string that is displayed at the top of the config widget for this plugin

can_get_multiple_covers = False

If True this source can return multiple covers for a given query

auto_trim_covers = False

If set to True covers downloaded by this plugin are automatically trimmed.

prefer_results_with_isbn = True

If set to True, and this source returns multiple results for a query, some of which have ISBNs and some of which do not, the results without
ISBNs will be ignored

is_configured()[zdroj]

Return False if your plugin needs to be configured before it can be used. For example, it might need a username/password/API key.

customization_help()[zdroj]

Return a string giving help on how to customize this plugin.
By default raise a NotImplementedError, which indicates that the plugin does not require customization.

If you re-implement this method in your subclass, the user will be asked to enter a string as customization for this plugin.
The customization string will be available as self.site_customization.

Site customization could be anything, for example, the path to a needed binary on the user’s computer.

Parametry:

gui – If True return HTML help, otherwise return plain text help.

config_widget()[zdroj]

Implement this method and save_settings() in your plugin to use a custom configuration dialog, rather then relying on the simple string based default customization.

This method, if implemented, must return a QWidget. The widget can have an optional method validate() that takes no arguments and is called immediately after the user clicks OK. Changes are applied if and only if the method returns True.

If for some reason you cannot perform the configuration at this time, return a tuple of two strings (message, details), these will be displayed as a warning dialog to the user and the process will be aborted.

save_settings(config_widget)[zdroj]

Save the settings specified by the user with config_widget.

Parametry:

config_widget – The widget returned by config_widget().

get_author_tokens(authors, only_first_author=True)[zdroj]

Take a list of authors and return a list of tokens useful for an
AND search query. This function tries to return tokens in first name middle names last name order, by assuming that if a comma is in the author name, the name is in lastname, other names form.

get_title_tokens(title, strip_joiners=True, strip_subtitle=False)[zdroj]

Take a title and return a list of tokens useful for an AND search query.
Excludes connectives(optionally) and punctuation.

split_jobs(jobs, num)[zdroj]

Split a list of jobs into at most num groups, as evenly as possible

test_fields(mi)[zdroj]

Return the first field from self.touched_fields that is null on the mi object

clean_downloaded_metadata(mi)[zdroj]

Call this method in your plugin’s identify method to normalize metadata before putting the Metadata object into result_queue. You can of course, use a custom algorithm suited to your metadata source.

get_book_url(identifiers)[zdroj]

Return a 3-tuple or None. The 3-tuple is of the form:
(identifier_type, identifier_value, URL).
The URL is the URL for the book identified by identifiers at this source. identifier_type, identifier_value specify the identifier corresponding to the URL.
This URL must be browsable to by a human using a browser. It is meant to provide a clickable link for the user to easily visit the books page at this source.
If no URL is found, return None. This method must be quick, and consistent, so only implement it if it is possible to construct the URL from a known scheme given identifiers.

get_book_url_name(idtype, idval, url)[zdroj]

Return a human readable name from the return value of get_book_url().

get_book_urls(identifiers)[zdroj]

Override this method if you would like to return multiple URLs for this book.
Return a list of 3-tuples. By default this method simply calls get_book_url().

get_cached_cover_url(identifiers)[zdroj]

Return cached cover URL for the book identified by the identifiers dictionary or None if no such URL exists.

Note that this method must only return validated URLs, i.e. not URLS that could result in a generic cover image or a not found error.

id_from_url(url)[zdroj]

Parse a URL and return a tuple of the form:
(identifier_type, identifier_value).
If the URL does not match the pattern for the metadata source, return None.

identify_results_keygen(title=None, authors=None, identifiers={})[zdroj]

Return a function that is used to generate a key that can sort Metadata objects by their relevance given a search query (title, authors, identifiers).

These keys are used to sort the results of a call to identify().

For details on the default algorithm see InternalMetadataCompareKeyGen. Re-implement this function in your plugin if the default algorithm is not suitable.

identify(log, result_queue, abort, title=None, authors=None, identifiers={}, timeout=30)[zdroj]

Identify a book by its Title/Author/ISBN/etc.

If identifiers(s) are specified and no match is found and this metadata source does not store all related identifiers (for example, all ISBNs of a book), this method should retry with just the title and author
(assuming they were specified).

If this metadata source also provides covers, the URL to the cover should be cached so that a subsequent call to the get covers API with the same ISBN/special identifier does not need to get the cover URL again. Use the caching API for this.

Every Metadata object put into result_queue by this method must have a source_relevance attribute that is an integer indicating the order in which the results were returned by the metadata source for this query.
This integer will be used by compare_identify_results(). If the order is unimportant, set it to zero for every result.

Make sure that any cover/ISBN mapping information is cached before the
Metadata object is put into result_queue.

Parametry:

- log – A log object, use it to output debugging information/errors

- result_queue – A result Queue, results should be put into it.
Each result is a Metadata object

- abort – If abort.is_set() returns True, abort further processing and return as soon as possible

- title – The title of the book, can be None

- authors – A list of authors of the book, can be None

- identifiers – A dictionary of other identifiers, most commonly
{‚isbn‘:‘1234…‘}

- timeout – Timeout in seconds, no network request should hang for longer than timeout.

Vrací:

None if no errors occurred, otherwise a unicode representation of the error suitable for showing to the user

download_cover(log, result_queue, abort, title=None, authors=None, identifiers={}, timeout=30, get_best_cover=False)[zdroj]

Download a cover and put it into result_queue. The parameters all have the same meaning as for identify(). Put (self, cover_data) into result_queue.

This method should use cached cover URLs for efficiency whenever possible. When cached data is not present, most plugins simply call identify and use its results.

If the parameter get_best_cover is True and this plugin can get multiple covers, it should only get the „best“ one.

class calibre.ebooks.metadata.sources.base.InternalMetadataCompareKeyGen(mi, source_plugin, title, authors, identifiers)[zdroj]

Generate a sort key for comparison of the relevance of Metadata objects, given a search query. This is used only to compare results from the same metadata source, not across different sources.

The sort key ensures that an ascending order sort is a sort by order of decreasing relevance.

The algorithm is:

- Prefer results that have at least one identifier the same as for the query

- Prefer results with a cached cover URL

- Prefer results with all available fields filled in

- Prefer results with the same language as the current user interface language

- Prefer results that are an exact title match to the query

- Prefer results with longer comments (greater than 10% longer)

- Use the relevance of the result as reported by the metadata source’s search

engine

Moduly převodu
class calibre.customize.conversion.InputFormatPlugin(*args)[zdroj]

Bases: Plugin

InputFormatPlugins are responsible for converting a document into
HTML+OPF+CSS+etc.
The results of the conversion must be encoded in UTF-8.
The main action happens in convert().

type = 'Vstup převodu'

The type of this plugin. Used for categorizing plugins in the
GUI

can_be_disabled = False

If False, the user will not be able to disable this plugin. Use with care.

supported_platforms = ['windows', 'osx', 'linux']

List of platforms this plugin works on.
For example: ['windows', 'osx', 'linux']

file_types = {}

Set of file types for which this plugin should be run
For example: set(['azw', 'mobi', 'prc'])

is_image_collection = False

If True, this input plugin generates a collection of images, one per HTML file. This can be set dynamically, in the convert method if the input files can be both image collections and non-image collections.
If you set this to True, you must implement the get_images() method that returns a list of images.

core_usage = 1

Number of CPU cores used by this plugin.
A value of -1 means that it uses all available cores

for_viewer = False

If set to True, the input plugin will perform special processing to make its output suitable for viewing

output_encoding = 'utf-8'

The encoding that this input plugin creates files in. A value of
None means that the encoding is undefined and must be detected individually

common_options = {<calibre.customize.conversion.OptionRecommendation object>}

Options shared by all Input format plugins. Do not override in sub-classes. Use options instead. Every option must be an instance of OptionRecommendation.

options = {}

Options to customize the behavior of this plugin. Every option must be an instance of OptionRecommendation.

recommendations = {}

A set of 3-tuples of the form
(option_name, recommended_value, recommendation_level)

get_images()[zdroj]

Return a list of absolute paths to the images, if this input plugin represents an image collection. The list of images is in the same order as the spine and the TOC.

convert(stream, options, file_ext, log, accelerators)[zdroj]

This method must be implemented in sub-classes. It must return the path to the created OPF file or an OEBBook instance.
All output should be contained in the current folder.
If this plugin creates files outside the current folder they must be deleted/marked for deletion before this method returns.

Parametry:

- stream – A file like object that contains the input file.

- options – Options to customize the conversion process.
Guaranteed to have attributes corresponding to all the options declared by this plugin. In addition, it will have a verbose attribute that takes integral values from zero upwards. Higher numbers mean be more verbose. Another useful attribute is input_profile that is an instance of calibre.customize.profiles.InputProfile.

- file_ext – The extension (without the .) of the input file. It is guaranteed to be one of the file_types supported by this plugin.

- log – A calibre.utils.logging.Log object. All output should use this object.

- accelarators – A dictionary of various information that the input plugin can get easily that would speed up the subsequent stages of the conversion.

postprocess_book(oeb, opts, log)[zdroj]

Called to allow the input plugin to perform postprocessing after the book has been parsed.

specialize(oeb, opts, log, output_fmt)[zdroj]

Called to allow the input plugin to specialize the parsed book for a particular output format. Called after postprocess_book and before any transforms are performed on the parsed book.

gui_configuration_widget(parent, get_option_by_name, get_option_help, db, book_id=None)[zdroj]

Called to create the widget used for configuring this plugin in the calibre GUI. The widget must be an instance of the PluginWidget class.
See the builtin input plugins for examples.

class calibre.customize.conversion.OutputFormatPlugin(*args)[zdroj]

Bases: Plugin

OutputFormatPlugins are responsible for converting an OEB document
(OPF+HTML) into an output e-book.

The OEB document can be assumed to be encoded in UTF-8.
The main action happens in convert().

type = 'Výstup převodu'

The type of this plugin. Used for categorizing plugins in the
GUI

can_be_disabled = False

If False, the user will not be able to disable this plugin. Use with care.

supported_platforms = ['windows', 'osx', 'linux']

List of platforms this plugin works on.
For example: ['windows', 'osx', 'linux']

file_type = None

The file type (extension without leading period) that this plugin outputs

common_options = {<calibre.customize.conversion.OptionRecommendation object>}

Options shared by all Input format plugins. Do not override in sub-classes. Use options instead. Every option must be an instance of OptionRecommendation.

options = {}

Options to customize the behavior of this plugin. Every option must be an instance of OptionRecommendation.

recommendations = {}

A set of 3-tuples of the form
(option_name, recommended_value, recommendation_level)

property description

str(object=‘‘) -> str str(bytes_or_buffer[, encoding[, errors]]) -> str

Create a new string object from the given object. If encoding or errors is specified, then the object must expose a data buffer that will be decoded using the given encoding and error handler.
Otherwise, returns the result of object.__str__() (if defined)
or repr(object).
encoding defaults to sys.getdefaultencoding().
errors defaults to ‚strict‘.

convert(oeb_book, output, input_plugin, opts, log)[zdroj]

Render the contents of oeb_book (which is an instance of calibre.ebooks.oeb.OEBBook) to the file specified by output.

Parametry:

- output – Either a file like object or a string. If it is a string it is the path to a folder that may or may not exist. The output plugin should write its output into that folder. If it is a file like object, the output plugin should write its output into the file.

- input_plugin – The input plugin that was used at the beginning of the conversion pipeline.

- opts – Conversion options. Guaranteed to have attributes corresponding to the OptionRecommendations of this plugin.

- log – The logger. Print debug/info messages etc. using this.

specialize_options(log, opts, input_fmt)[zdroj]

Can be used to change the values of conversion options, as used by the conversion pipeline.

specialize_css_for_output(log, opts, item, stylizer)[zdroj]

Can be used to make changes to the CSS during the CSS flattening process.

Parametry:

- item – The item (HTML file) being processed

- stylizer – A Stylizer object containing the flattened styles for item. You can get the style for any element by stylizer.style(element).

gui_configuration_widget(parent, get_option_by_name, get_option_help, db, book_id=None)[zdroj]

Called to create the widget used for configuring this plugin in the calibre GUI. The widget must be an instance of the PluginWidget class.
See the builtin output plugins for examples.

Ovladače zařízení

The base class for all device drivers is DevicePlugin. However, if your device exposes itself as a USBMS drive to the operating system, you should use the USBMS class instead as it implements all the logic needed to support these kinds of devices.

class calibre.devices.interface.DevicePlugin(plugin_path)[zdroj]

Bases: Plugin

Defines the interface that should be implemented by backends that communicate with an e-book reader.

type = 'Rozhraní zařízení'

The type of this plugin. Used for categorizing plugins in the
GUI

FORMATS = ['lrf', 'rtf', 'pdf', 'txt']

Ordered list of supported formats

VENDOR_ID = 0

VENDOR_ID can be either an integer, a list of integers or a dictionary
If it is a dictionary, it must be a dictionary of dictionaries, of the form:

{
integer_vendor_id : { product_id : [list of BCDs], ... },
...
}

PRODUCT_ID = 0

An integer or a list of integers

BCD = None

BCD can be either None to not distinguish between devices based on BCD, or it can be a list of the BCD numbers of all devices supported by this driver.

THUMBNAIL_HEIGHT = 68

Height for thumbnails on the device

THUMBNAIL_COMPRESSION_QUALITY = 75

Compression quality for thumbnails. Set this closer to 100 to have better quality thumbnails with fewer compression artifacts. Of course, the thumbnails get larger as well.

WANTS_UPDATED_THUMBNAILS = False

Set this to True if the device supports updating cover thumbnails during sync_booklists. Setting it to true will ask device.py to refresh the cover thumbnails during book matching

CAN_SET_METADATA = ['title', 'authors', 'collections']

Whether the metadata on books can be set via the GUI.

CAN_DO_DEVICE_DB_PLUGBOARD = False

Whether the device can handle device_db metadata plugboards

path_sep = '/'

Path separator for paths to books on device

icon = 'reader.png'

Icon for this device

UserAnnotation

alias of Annotation

OPEN_FEEDBACK_MESSAGE = None

GUI displays this as a message if not None in the status bar. Useful if opening can take a long time

VIRTUAL_BOOK_EXTENSIONS = frozenset({})

Set of extensions that are „virtual books“ on the device and therefore cannot be viewed/saved/added to library.
For example: frozenset(['kobo'])

VIRTUAL_BOOK_EXTENSION_MESSAGE = None

Message to display to user for virtual book extensions.

NUKE_COMMENTS = None

Whether to nuke comments in the copy of the book sent to the device. If not None this should be short string that the comments will be replaced by.

MANAGES_DEVICE_PRESENCE = False

If True indicates that this driver completely manages device detection, ejecting and so forth. If you set this to True, you must implement the detect_managed_devices and debug_managed_device_detection methods.
A driver with this set to true is responsible for detection of devices, managing a blacklist of devices, a list of ejected devices and so forth.
calibre will periodically call the detect_managed_devices() method and if it returns a detected device, calibre will call open(). open() will be called every time a device is returned even if previous calls to open()
failed, therefore the driver must maintain its own blacklist of failed devices. Similarly, when ejecting, calibre will call eject() and then assuming the next call to detect_managed_devices() returns None, it will call post_yank_cleanup().

SLOW_DRIVEINFO = False

If set the True, calibre will call the get_driveinfo() method after the books lists have been loaded to get the driveinfo.

ASK_TO_ALLOW_CONNECT = False

If set to True, calibre will ask the user if they want to manage the device with calibre, the first time it is detected. If you set this to
True you must implement get_device_uid() and ignore_connected_device() and get_user_blacklisted_devices() and set_user_blacklisted_devices()

user_feedback_after_callback = None

Set this to a dictionary of the form {‚title‘:title, ‚msg‘:msg, ‚det_msg‘:detailed_msg} to have calibre popup a message to the user after some callbacks are run (currently only upload_books).
Be careful to not spam the user with too many messages. This variable is checked after every callback, so only set it when you really need to.

classmethod get_open_popup_message()[zdroj]

GUI displays this as a non-modal popup. Should be an instance of OpenPopupMessage

is_usb_connected(devices_on_system, debug=False, only_presence=False)[zdroj]

Return True, device_info if a device handled by this plugin is currently connected.

Parametry:

devices_on_system – List of devices currently connected

detect_managed_devices(devices_on_system, force_refresh=False)[zdroj]

Called only if MANAGES_DEVICE_PRESENCE is True.

Scan for devices that this driver can handle. Should return a device object if a device is found. This object will be passed to the open()
method as the connected_device. If no device is found, return None. The returned object can be anything, calibre does not use it, it is only passed to open().

This method is called periodically by the GUI, so make sure it is not too resource intensive. Use a cache to avoid repeatedly scanning the system.

Parametry:

- devices_on_system – Set of USB devices found on the system.

- force_refresh – If True and the driver uses a cache to prevent repeated scanning, the cache must be flushed.

debug_managed_device_detection(devices_on_system, output)[zdroj]

Called only if MANAGES_DEVICE_PRESENCE is True.

Should write information about the devices detected on the system to output, which is a file like object.

Should return True if a device was detected and successfully opened, otherwise False.

reset(key='-1', log_packets=False, report_progress=None, detected_device=None)[zdroj]

Parametry:

- key – The key to unlock the device

- log_packets – If true the packet stream to/from the device is logged

- report_progress – Function that is called with a % progress
(number between 0 and 100) for various tasks.
If it is called with -1 that means that the task does not have any progress information

- detected_device – Device information from the device scanner

can_handle_windows(usbdevice, debug=False)[zdroj]

Optional method to perform further checks on a device to see if this driver is capable of handling it. If it is not it should return False. This method is only called after the vendor, product ids and the bcd have matched, so it can do some relatively time intensive checks. The default implementation returns True. This method is called only on Windows. See also can_handle().

Note that for devices based on USBMS this method by default delegates to can_handle(). So you only need to override can_handle() in your subclass of USBMS.

Parametry:

usbdevice – A usbdevice as returned by calibre.devices.winusb.scan_usb_devices()

can_handle(device_info, debug=False)[zdroj]

Unix version of can_handle_windows().

Parametry:

device_info – Is a tuple of (vid, pid, bcd, manufacturer, product, serial number)

open(connected_device, library_uuid)[zdroj]

Perform any device specific initialization. Called after the device is detected but before any other functions that communicate with the device.
For example: For devices that present themselves as USB Mass storage devices, this method would be responsible for mounting the device or if the device has been automounted, for finding out where it has been mounted. The method calibre.devices.usbms.device.Device.open() has an implementation of this function that should serve as a good example for USB Mass storage devices.

This method can raise an OpenFeedback exception to display a message to the user.

Parametry:

- connected_device – The device that we are trying to open. It is a tuple of (vendor id, product id, bcd, manufacturer name, product name, device serial number). However, some devices have no serial number and on Windows only the first three fields are present, the rest are None.

- library_uuid – The UUID of the current calibre library. Can be
None if there is no library (for example when used from the command line).

eject()[zdroj]

Un-mount / eject the device from the OS. This does not check if there are pending GUI jobs that need to communicate with the device.

NOTE: That this method may not be called on the same thread as the rest of the device methods.

post_yank_cleanup()[zdroj]

Called if the user yanks the device without ejecting it first.

set_progress_reporter(report_progress)[zdroj]

Set a function to report progress information.

Parametry:

report_progress – Function that is called with a % progress
(number between 0 and 100) for various tasks.
If it is called with -1 that means that the task does not have any progress information

get_device_information(end_session=True)[zdroj]

Ask device for device information. See L{DeviceInfoQuery}.

Vrací:

(device name, device version, software version on device, MIME type)
The tuple can optionally have a fifth element, which is a drive information dictionary. See usbms.driver for an example.

get_driveinfo()[zdroj]

Return the driveinfo dictionary. Usually called from get_device_information(), but if loading the driveinfo is slow for this driver, then it should set SLOW_DRIVEINFO. In this case, this method will be called by calibre after the book lists have been loaded. Note that it is not called on the device thread, so the driver should cache the drive info in the books() method and this function should return the cached data.

card_prefix(end_session=True)[zdroj]

Return a 2 element list of the prefix to paths on the cards.
If no card is present None is set for the card’s prefix.
E.G.
(‚/place‘, ‚/place2‘)
(None, ‚place2‘)
(‚place‘, None)
(None, None)

total_space(end_session=True)[zdroj]

Get total space available on the mountpoints:

1. Hlavní paměť

2. Memory Card A

3. Memory Card B

Vrací:

A 3 element list with total space in bytes of (1, 2, 3). If a particular device doesn’t have any of these locations it should return 0.

free_space(end_session=True)[zdroj]

Get free space available on the mountpoints:

1. Hlavní paměť

2. Karta A

3. Karta B

Vrací:

A 3 element list with free space in bytes of (1, 2, 3). If a particular device doesn’t have any of these locations it should return -1.

books(oncard=None, end_session=True)[zdroj]

Return a list of e-books on the device.

Parametry:

oncard – If ‚carda‘ or ‚cardb‘ return a list of e-books on the specific storage card, otherwise return list of e-books in main memory of device. If a card is specified and no books are on the card return empty list.

Vrací:

A BookList.

upload_books(files, names, on_card=None, end_session=True, metadata=None)[zdroj]

Upload a list of books to the device. If a file already exists on the device, it should be replaced.
This method should raise a FreeSpaceError if there is not enough free space on the device. The text of the FreeSpaceError must contain the word „card“ if on_card is not None otherwise it must contain the word „memory“.

Parametry:

- files – A list of paths

- names – A list of file names that the books should have once uploaded to the device. len(names) == len(files)

- metadata – If not None, it is a list of Metadata objects.
The idea is to use the metadata to determine where on the device to put the book. len(metadata) == len(files). Apart from the regular cover (path to cover), there may also be a thumbnail attribute, which should be used in preference. The thumbnail attribute is of the form
(width, height, cover_data as jpeg).

Vrací:

A list of 3-element tuples. The list is meant to be passed to add_books_to_metadata().

classmethod add_books_to_metadata(locations, metadata, booklists)[zdroj]

Add locations to the booklists. This function must not communicate with the device.

Parametry:

- locations – Result of a call to L{upload_books}

- metadata – List of Metadata objects, same as for upload_books().

- booklists – A tuple containing the result of calls to
(books(oncard=None)(), books(oncard='carda')(),
:meth`books(oncard=‘cardb‘)`).

delete_books(paths, end_session=True)[zdroj]

Delete books at paths on device.

classmethod remove_books_from_metadata(paths, booklists)[zdroj]

Remove books from the metadata list. This function must not communicate with the device.

Parametry:

- paths – paths to books on the device.

- booklists – A tuple containing the result of calls to
(books(oncard=None)(), books(oncard='carda')(),
:meth`books(oncard=‘cardb‘)`).

sync_booklists(booklists, end_session=True)[zdroj]

Update metadata on device.

Parametry:

booklists – A tuple containing the result of calls to
(books(oncard=None)(), books(oncard='carda')(),
:meth`books(oncard=‘cardb‘)`).

get_file(path, outfile, end_session=True)[zdroj]

Read the file at path on the device and write it to outfile.

Parametry:

outfile – file object like sys.stdout or the result of an open() call.

classmethod config_widget()[zdroj]

Should return a QWidget. The QWidget contains the settings for the device interface

classmethod save_settings(settings_widget)[zdroj]

Should save settings to disk. Takes the widget created in config_widget() and saves all settings to disk.

classmethod settings()[zdroj]

Should return an opts object. The opts object should have at least one attribute format_map which is an ordered list of formats for the device.

set_plugboards(plugboards, pb_func)[zdroj]

provide the driver the current set of plugboards and a function to select a specific plugboard. This method is called immediately before add_books and sync_booklists.

pb_func is a callable with the following signature::

def pb_func(device_name, format, plugboards)

You give it the current device name (either the class name or
DEVICE_PLUGBOARD_NAME), the format you are interested in (a ‚real‘ format or ‚device_db‘), and the plugboards (you were given those by set_plugboards, the same place you got this method).

Vrací:

None or a single plugboard instance.

set_driveinfo_name(location_code, name)[zdroj]

Set the device name in the driveinfo file to ‚name‘. This setting will persist until the file is re-created or the name is changed again.

Non-disk devices should implement this method based on the location codes returned by the get_device_information() method.

prepare_addable_books(paths)[zdroj]

Given a list of paths, returns another list of paths. These paths point to addable versions of the books.

If there is an error preparing a book, then instead of a path, the position in the returned list for that book should be a three tuple:
(original_path, the exception instance, traceback)

startup()[zdroj]

Called when calibre is starting the device. Do any initialization required. Note that multiple instances of the class can be instantiated, and thus __init__ can be called multiple times, but only one instance will have this method called. This method is called on the device thread, not the GUI thread.

shutdown()[zdroj]

Called when calibre is shutting down, either for good or in preparation to restart. Do any cleanup required. This method is called on the device thread, not the GUI thread.

get_device_uid()[zdroj]

Must return a unique id for the currently connected device (this is called immediately after a successful call to open()). You must implement this method if you set ASK_TO_ALLOW_CONNECT = True

ignore_connected_device(uid)[zdroj]

Should ignore the device identified by uid (the result of a call to get_device_uid()) in the future. You must implement this method if you set ASK_TO_ALLOW_CONNECT = True. Note that this function is called immediately after open(), so if open() caches some state, the driver should reset that state.

get_user_blacklisted_devices()[zdroj]

Return map of device uid to friendly name for all devices that the user has asked to be ignored.

set_user_blacklisted_devices(devices)[zdroj]

Set the list of device uids that should be ignored by this driver.

specialize_global_preferences(device_prefs)[zdroj]

Implement this method if your device wants to override a particular preference. You must ensure that all call sites that want a preference that can be overridden use device_prefs[‚something‘] instead of prefs[‚something‘]. Your method should call device_prefs.set_overrides(pref=val, pref=val, …).
Currently used for:
metadata management (prefs[‚manage_device_metadata‘])

set_library_info(library_name, library_uuid, field_metadata)[zdroj]

Implement this method if you want information about the current calibre library. This method is called at startup and when the calibre library changes while connected.

is_dynamically_controllable()[zdroj]

Called by the device manager when starting plugins. If this method returns a string, then a) it supports the device manager’s dynamic control interface, and b) that name is to be used when talking to the plugin.

This method can be called on the GUI thread. A driver that implements this method must be thread safe.

start_plugin()[zdroj]

This method is called to start the plugin. The plugin should begin to accept device connections however it does that. If the plugin is already accepting connections, then do nothing.

This method can be called on the GUI thread. A driver that implements this method must be thread safe.

stop_plugin()[zdroj]

This method is called to stop the plugin. The plugin should no longer accept connections, and should cleanup behind itself. It is likely that this method should call shutdown. If the plugin is already not accepting connections, then do nothing.

This method can be called on the GUI thread. A driver that implements this method must be thread safe.

get_option(opt_string, default=None)[zdroj]

Return the value of the option indicated by opt_string. This method can be called when the plugin is not started. Return None if the option does not exist.

This method can be called on the GUI thread. A driver that implements this method must be thread safe.

set_option(opt_string, opt_value)[zdroj]

Set the value of the option indicated by opt_string. This method can be called when the plugin is not started.

This method can be called on the GUI thread. A driver that implements this method must be thread safe.

is_running()[zdroj]

Return True if the plugin is started, otherwise false

This method can be called on the GUI thread. A driver that implements this method must be thread safe.

synchronize_with_db(db, book_id, book_metadata, first_call)[zdroj]

Called during book matching when a book on the device is matched with a book in calibre’s db. The method is responsible for synchronizing data from the device to calibre’s db (if needed).

The method must return a two-value tuple. The first value is a set of calibre book ids changed if calibre’s database was changed or None if the database was not changed. If the first value is an empty set then the metadata for the book on the device is updated with calibre’s metadata and given back to the device, but no GUI refresh of that book is done.
This is useful when the calibre data is correct but must be sent to the device.

The second value is itself a 2-value tuple. The first value in the tuple specifies whether a book format should be sent to the device. The intent is to permit verifying that the book on the device is the same as the book in calibre. This value must be None if no book is to be sent, otherwise return the base file name on the device (a string like foobar.epub). Be sure to include the extension in the name. The device subsystem will construct a send_books job for all books with not- None returned values. Note: other than to later retrieve the extension, the name is ignored in cases where the device uses a template to generate the file name, which most do. The second value in the returned tuple indicated whether the format is future-dated. Return True if it is, otherwise return False. calibre will display a dialog to the user listing all future dated books.

Extremely important: this method is called on the GUI thread. It must be threadsafe with respect to the device manager’s thread.

book_id: the calibre id for the book in the database.
book_metadata: the Metadata object for the book coming from the device.
first_call: True if this is the first call during a sync, False otherwise

class calibre.devices.interface.BookList(oncard, prefix, settings)[zdroj]

Bases: list

A list of books. Each Book object must have the fields

1. title

2. authors

3. size (file size of the book)

4. datetime (a UTC time tuple)

5. path (path on the device to the book)

6. thumbnail (can be None) thumbnail is either a str/bytes object with the image data or it should have an attribute image_path that stores an absolute (platform native) path to the image

7. tags (a list of strings, can be empty).

supports_collections()[zdroj]

Return True if the device supports collections for this book list.

add_book(book, replace_metadata)[zdroj]

Add the book to the booklist. Intent is to maintain any device-internal metadata. Return True if booklists must be sync’ed

remove_book(book)[zdroj]

Remove a book from the booklist. Correct any device metadata at the same time

get_collections(collection_attributes)[zdroj]

Return a dictionary of collections created from collection_attributes.
Each entry in the dictionary is of the form collection name:[list of books]

The list of books is sorted by book title, except for collections created from series, in which case series_index is used.

Parametry:

collection_attributes – A list of attributes of the Book object

Zařízení založená na Velkokapacitním paměťovém zařízení USB

The base class for such devices is calibre.devices.usbms.driver.USBMS. This class in turn inherits some of its functionality from its bases, documented below. A typical basic USBMS based driver looks like this:

from calibre.devices.usbms.driver import USBMS

class PDNOVEL(USBMS):
name = 'Pandigital Novel device interface'
gui_name = 'PD Novel'
description = _('Communicate with the Pandigital Novel')
author = 'Kovid Goyal'
supported_platforms = ['windows', 'linux', 'osx']
FORMATS = ['epub', 'pdf']

VENDOR_ID = [0x18d1]
PRODUCT_ID = [0xb004]
BCD = [0x224]

THUMBNAIL_HEIGHT = 144

EBOOK_DIR_MAIN = 'eBooks'
SUPPORTS_SUB_DIRS = False

def upload_cover(self, path, filename, metadata):
coverdata = getattr(metadata, 'thumbnail', None)
if coverdata and coverdata[2]:
with open('%s.jpg' % os.path.join(path, filename), 'wb') as coverfile:
coverfile.write(coverdata[2])

class calibre.devices.usbms.device.Device(plugin_path)[zdroj]

Bases: DeviceConfig, DevicePlugin

This class provides logic common to all drivers for devices that export themselves as USB Mass Storage devices. Provides implementations for mounting/ejecting of USBMS devices on all platforms.

VENDOR_ID = 0

VENDOR_ID can be either an integer, a list of integers or a dictionary
If it is a dictionary, it must be a dictionary of dictionaries, of the form:

{
integer_vendor_id : { product_id : [list of BCDs], ... },
...
}

PRODUCT_ID = 0

An integer or a list of integers

BCD = None

BCD can be either None to not distinguish between devices based on BCD, or it can be a list of the BCD numbers of all devices supported by this driver.

WINDOWS_MAIN_MEM = None

String identifying the main memory of the device in the Windows PnP id strings
This can be None, string, list of strings or compiled regex

WINDOWS_CARD_A_MEM = None

String identifying the first card of the device in the Windows PnP id strings
This can be None, string, list of strings or compiled regex

WINDOWS_CARD_B_MEM = None

String identifying the second card of the device in the Windows PnP id strings
This can be None, string, list of strings or compiled regex

OSX_MAIN_MEM_VOL_PAT = None

Used by the new driver detection to disambiguate main memory from storage cards. Should be a regular expression that matches the main memory mount point assigned by macOS

BACKLOADING_ERROR_MESSAGE = None

MAX_PATH_LEN = 250

The maximum length of paths created on the device

NEWS_IN_FOLDER = True

Put news in its own folder

reset(key='-1', log_packets=False, report_progress=None, detected_device=None)[zdroj]

Parametry:

- key – The key to unlock the device

- log_packets – If true the packet stream to/from the device is logged

- report_progress – Function that is called with a % progress
(number between 0 and 100) for various tasks.
If it is called with -1 that means that the task does not have any progress information

- detected_device – Device information from the device scanner

set_progress_reporter(report_progress)[zdroj]

Set a function to report progress information.

Parametry:

report_progress – Function that is called with a % progress
(number between 0 and 100) for various tasks.
If it is called with -1 that means that the task does not have any progress information

card_prefix(end_session=True)[zdroj]

Return a 2 element list of the prefix to paths on the cards.
If no card is present None is set for the card’s prefix.
E.G.
(‚/place‘, ‚/place2‘)
(None, ‚place2‘)
(‚place‘, None)
(None, None)

total_space(end_session=True)[zdroj]

Get total space available on the mountpoints:

1. Hlavní paměť

2. Memory Card A

3. Memory Card B

Vrací:

A 3 element list with total space in bytes of (1, 2, 3). If a particular device doesn’t have any of these locations it should return 0.

free_space(end_session=True)[zdroj]

Get free space available on the mountpoints:

1. Hlavní paměť

2. Karta A

3. Karta B

Vrací:

A 3 element list with free space in bytes of (1, 2, 3). If a particular device doesn’t have any of these locations it should return -1.

windows_sort_drives(drives)[zdroj]

Called to disambiguate main memory and storage card for devices that do not distinguish between them on the basis of WINDOWS_CARD_NAME.
For example: The EB600

can_handle_windows(usbdevice, debug=False)[zdroj]

Optional method to perform further checks on a device to see if this driver is capable of handling it. If it is not it should return False. This method is only called after the vendor, product ids and the bcd have matched, so it can do some relatively time intensive checks. The default implementation returns True. This method is called only on Windows. See also can_handle().

Note that for devices based on USBMS this method by default delegates to can_handle(). So you only need to override can_handle() in your subclass of USBMS.

Parametry:

usbdevice – A usbdevice as returned by calibre.devices.winusb.scan_usb_devices()

open(connected_device, library_uuid)[zdroj]

Perform any device specific initialization. Called after the device is detected but before any other functions that communicate with the device.
For example: For devices that present themselves as USB Mass storage devices, this method would be responsible for mounting the device or if the device has been automounted, for finding out where it has been mounted. The method calibre.devices.usbms.device.Device.open() has an implementation of this function that should serve as a good example for USB Mass storage devices.

This method can raise an OpenFeedback exception to display a message to the user.

Parametry:

- connected_device – The device that we are trying to open. It is a tuple of (vendor id, product id, bcd, manufacturer name, product name, device serial number). However, some devices have no serial number and on Windows only the first three fields are present, the rest are None.

- library_uuid – The UUID of the current calibre library. Can be
None if there is no library (for example when used from the command line).

eject()[zdroj]

Un-mount / eject the device from the OS. This does not check if there are pending GUI jobs that need to communicate with the device.

NOTE: That this method may not be called on the same thread as the rest of the device methods.

post_yank_cleanup()[zdroj]

Called if the user yanks the device without ejecting it first.

sanitize_callback(path)[zdroj]

Callback to allow individual device drivers to override the path sanitization used by create_upload_path().

filename_callback(default, mi)[zdroj]

Callback to allow drivers to change the default file name set by create_upload_path().

sanitize_path_components(components)[zdroj]

Perform any device specific sanitization on the path components for files to be uploaded to the device

get_annotations(path_map)[zdroj]

Resolve path_map to annotation_map of files found on the device

add_annotation_to_library(db, db_id, annotation)[zdroj]

Add an annotation to the calibre library

class calibre.devices.usbms.cli.CLI[zdroj]

class calibre.devices.usbms.driver.USBMS(plugin_path)[zdroj]

Bases: CLI, Device

The base class for all USBMS devices. Implements the logic for sending/getting/updating metadata/caching metadata/etc.

description = 'Komunikace se čtečkou e-knih.'

A short string describing what this plugin does

author = 'John Schember'

Autor doplňku

supported_platforms = ['windows', 'osx', 'linux']

List of platforms this plugin works on.
For example: ['windows', 'osx', 'linux']

booklist_class

alias of BookList

book_class

alias of Book

FORMATS = []

Ordered list of supported formats

CAN_SET_METADATA = []

Whether the metadata on books can be set via the GUI.

get_device_information(end_session=True)[zdroj]

Ask device for device information. See L{DeviceInfoQuery}.

Vrací:

(device name, device version, software version on device, MIME type)
The tuple can optionally have a fifth element, which is a drive information dictionary. See usbms.driver for an example.

set_driveinfo_name(location_code, name)[zdroj]

Set the device name in the driveinfo file to ‚name‘. This setting will persist until the file is re-created or the name is changed again.

Non-disk devices should implement this method based on the location codes returned by the get_device_information() method.

books(oncard=None, end_session=True)[zdroj]

Return a list of e-books on the device.

Parametry:

oncard – If ‚carda‘ or ‚cardb‘ return a list of e-books on the specific storage card, otherwise return list of e-books in main memory of device. If a card is specified and no books are on the card return empty list.

Vrací:

A BookList.

upload_books(files, names, on_card=None, end_session=True, metadata=None)[zdroj]

Upload a list of books to the device. If a file already exists on the device, it should be replaced.
This method should raise a FreeSpaceError if there is not enough free space on the device. The text of the FreeSpaceError must contain the word „card“ if on_card is not None otherwise it must contain the word „memory“.

Parametry:

- files – A list of paths

- names – A list of file names that the books should have once uploaded to the device. len(names) == len(files)

- metadata – If not None, it is a list of Metadata objects.
The idea is to use the metadata to determine where on the device to put the book. len(metadata) == len(files). Apart from the regular cover (path to cover), there may also be a thumbnail attribute, which should be used in preference. The thumbnail attribute is of the form
(width, height, cover_data as jpeg).

Vrací:

A list of 3-element tuples. The list is meant to be passed to add_books_to_metadata().

upload_cover(path, filename, metadata, filepath)[zdroj]

Upload book cover to the device. Default implementation does nothing.

Parametry:

- path – The full path to the folder where the associated book is located.

- filename – The name of the book file without the extension.

- metadata – metadata belonging to the book. Use metadata.thumbnail for cover

- filepath – The full path to the e-book file

add_books_to_metadata(locations, metadata, booklists)[zdroj]

Add locations to the booklists. This function must not communicate with the device.

Parametry:

- locations – Result of a call to L{upload_books}

- metadata – List of Metadata objects, same as for upload_books().

- booklists – A tuple containing the result of calls to
(books(oncard=None)(), books(oncard='carda')(),
:meth`books(oncard=‘cardb‘)`).

delete_books(paths, end_session=True)[zdroj]

Delete books at paths on device.

remove_books_from_metadata(paths, booklists)[zdroj]

Remove books from the metadata list. This function must not communicate with the device.

Parametry:

- paths – paths to books on the device.

- booklists – A tuple containing the result of calls to
(books(oncard=None)(), books(oncard='carda')(),
:meth`books(oncard=‘cardb‘)`).

sync_booklists(booklists, end_session=True)[zdroj]

Update metadata on device.

Parametry:

booklists – A tuple containing the result of calls to
(books(oncard=None)(), books(oncard='carda')(),
:meth`books(oncard=‘cardb‘)`).

classmethod normalize_path(path)[zdroj]

Return path with platform native path separators

User interface actions

If you are adding your own plugin in a ZIP file, you should subclass both InterfaceActionBase and InterfaceAction. The load_actual_plugin() method of your InterfaceActionBase subclass must return an instantiated object of your InterfaceBase subclass.

class calibre.gui2.actions.InterfaceAction(parent, site_customization)[zdroj]

Bases: QObject

A plugin representing an „action“ that can be taken in the graphical user interface. All the items in the toolbar and context menus are implemented by these plugins.

Note that this class is the base class for these plugins, however, to integrate the plugin with calibre’s plugin system, you have to make a wrapper class that references the actual plugin. See the calibre.customize.builtins module for examples.

If two InterfaceAction objects have the same name, the one with higher priority takes precedence.

Sub-classes should implement the genesis(), library_changed(), location_selected(), shutting_down(), initialization_complete() and tag_browser_context_action() methods.

Once initialized, this plugin has access to the main calibre GUI via the gui member. You can access other plugins by name, for example:

self.gui.iactions['Save To Disk']

To access the actual plugin, use the interface_action_base_plugin attribute, this attribute only becomes available after the plugin has been initialized. Useful if you want to use methods from the plugin class like do_user_config().

The QAction specified by action_spec is automatically create and made available as self.qaction.

name = 'Implement me'

The plugin name. If two plugins with the same name are present, the one with higher priority takes precedence.

priority = 1

The plugin priority. If two plugins with the same name are present, the one with higher priority takes precedence.

popup_type = 1

The menu popup type for when this plugin is added to a toolbar

auto_repeat = False

Whether this action should be auto repeated when its shortcut key is held down.

action_spec = ('text', 'icon', None, None)

Of the form: (text, icon_path, tooltip, keyboard shortcut).
icon, tooltip and keyboard shortcut can be None.
keyboard shortcut must be either a string, None or tuple of shortcuts.
If None, a keyboard shortcut corresponding to the action is not registered. If you pass an empty tuple, then the shortcut is registered with no default key binding.

action_shortcut_name = None

If not None, used for the name displayed to the user when customizing the keyboard shortcuts for the above action spec instead of action_spec[0]

action_add_menu = False

If True, a menu is automatically created and added to self.qaction

action_menu_clone_qaction = False

If True, a clone of self.qaction is added to the menu of self.qaction
If you want the text of this action to be different from that of self.qaction, set this variable to the new text

dont_add_to = frozenset({})

Set of locations to which this action must not be added.
See all_locations for a list of possible locations

dont_remove_from = frozenset({})

Set of locations from which this action must not be removed.
See all_locations for a list of possible locations

action_type = 'global'

Type of action
‚current‘ means acts on the current view
‚global‘ means an action that does not act on the current view, but rather on calibre as a whole

accepts_drops = False

If True, then this InterfaceAction will have the opportunity to interact with drag and drop events. See the methods, accept_enter_event(),
:meth`:accept_drag_move_event`, drop_event() for details.

accept_enter_event(event, mime_data)[zdroj]

This method should return True iff this interface action is capable of handling the drag event. Do not call accept/ignore on the event, that will be taken care of by the calibre UI.

accept_drag_move_event(event, mime_data)[zdroj]

This method should return True iff this interface action is capable of handling the drag event. Do not call accept/ignore on the event, that will be taken care of by the calibre UI.

drop_event(event, mime_data)[zdroj]

This method should perform some useful action and return True iff this interface action is capable of handling the drop event. Do not call accept/ignore on the event, that will be taken care of by the calibre UI. You should not perform blocking/long operations in this function. Instead emit a signal or use QTimer.singleShot and return quickly. See the builtin actions for examples.

create_menu_action(menu, unique_name, text, icon=None, shortcut=None, description=None, triggered=None, shortcut_name=None, persist_shortcut=False)[zdroj]

Convenience method to easily add actions to a QMenu.
Returns the created QAction. This action has one extra attribute calibre_shortcut_unique_name which if not None refers to the unique name under which this action is registered with the keyboard manager.

Parametry:

- menu – The QMenu the newly created action will be added to

- unique_name – A unique name for this action, this must be globally unique, so make it as descriptive as possible. If in doubt, add an UUID to it.

- text – The text of the action.

- icon – Either a QIcon or a file name. The file name is passed to the QIcon.ic() builtin, so you do not need to pass the full path to the images folder.

- shortcut – A string, a list of strings, None or False. If False, no keyboard shortcut is registered for this action. If None, a keyboard shortcut with no default keybinding is registered. String and list of strings register a shortcut with default keybinding as specified.

- description – A description for this action. Used to set tooltips.

- triggered – A callable which is connected to the triggered signal of the created action.

- shortcut_name – The text displayed to the user when customizing the keyboard shortcuts for this action. By default it is set to the value of text.

- persist_shortcut – Shortcuts for actions that don’t always appear, or are library dependent, may disappear when other keyboard shortcuts are edited unless `persist_shortcut` is set True.

load_resources(names)[zdroj]

If this plugin comes in a ZIP file (user added plugin), this method will allow you to load resources from the ZIP file.

For example to load an image:

pixmap = QPixmap()
pixmap.loadFromData(tuple(self.load_resources(['images/icon.png']).values())[0])
icon = QIcon(pixmap)

Parametry:

names – List of paths to resources in the ZIP file using / as separator

Vrací:

A dictionary of the form {name : file_contents}. Any names that were not found in the ZIP file will not be present in the dictionary.

genesis()[zdroj]

Setup this plugin. Only called once during initialization. self.gui is available. The action specified by action_spec is available as self.qaction.

location_selected(loc)[zdroj]

Called whenever the book list being displayed in calibre changes.
Currently values for loc are: library, main, card and cardb.

This method should enable/disable this action and its sub actions as appropriate for the location.

library_about_to_change(olddb, db)[zdroj]

Called whenever the current library is changed.

Parametry:

- olddb – The LibraryDatabase corresponding to the previous library.

- db – The LibraryDatabase corresponding to the new library.

library_changed(db)[zdroj]

Called whenever the current library is changed.

Parametry:

db – The LibraryDatabase corresponding to the current library.

gui_layout_complete()[zdroj]

Called once per action when the layout of the main GUI is completed. If your action needs to make changes to the layout, they should be done here, rather than in initialization_complete().

initialization_complete()[zdroj]

Called once per action when the initialization of the main GUI is completed.

tag_browser_context_action(index)[zdroj]

Called when displaying the context menu in the Tag browser. index is the QModelIndex that points to the Tag browser item that was right clicked.
Test it for validity with index.valid() and get the underlying TagTreeItem object with index.data(Qt.ItemDataRole.UserRole). Any action objects yielded by this method will be added to the context menu.

shutting_down()[zdroj]

Called once per plugin when the main GUI is in the process of shutting down. Release any used resources, but try not to block the shutdown for long periods of time.

class calibre.customize.InterfaceActionBase(*args, **kwargs)[zdroj]

Bases: Plugin

supported_platforms = ['windows', 'osx', 'linux']

List of platforms this plugin works on.
For example: ['windows', 'osx', 'linux']

author = 'Kovid Goyal'

Autor doplňku

type = 'Akce uživatelského rozhraní'

The type of this plugin. Used for categorizing plugins in the
GUI

can_be_disabled = False

If False, the user will not be able to disable this plugin. Use with care.

load_actual_plugin(gui)[zdroj]

This method must return the actual interface action plugin object.

Moduly předvoleb
class calibre.customize.PreferencesPlugin(plugin_path)[zdroj]

Bases: Plugin

A plugin representing a widget displayed in the Preferences dialog.

This plugin has only one important method create_widget(). The various fields of the plugin control how it is categorized in the UI.

supported_platforms = ['windows', 'osx', 'linux']

List of platforms this plugin works on.
For example: ['windows', 'osx', 'linux']

author = 'Kovid Goyal'

Autor doplňku

type = 'Předvolby'

The type of this plugin. Used for categorizing plugins in the
GUI

can_be_disabled = False

If False, the user will not be able to disable this plugin. Use with care.

config_widget = None

Import path to module that contains a class named ConfigWidget which implements the ConfigWidgetInterface. Used by create_widget().

category_order = 100

Where in the list of categories the category of this plugin should be.

name_order = 100

Where in the list of names in a category, the gui_name of this plugin should be

category = None

The category this plugin should be in

gui_category = None

The category name displayed to the user for this plugin

gui_name = None

The name displayed to the user for this plugin

icon = None

The icon for this plugin, should be an absolute path

description = None

The description used for tooltips and the like

create_widget(parent=None)[zdroj]

Create and return the actual Qt widget used for setting this group of preferences. The widget must implement the calibre.gui2.preferences.ConfigWidgetInterface.

The default implementation uses config_widget to instantiate the widget.

class calibre.gui2.preferences.ConfigWidgetInterface[zdroj]

This class defines the interface that all widgets displayed in the
Preferences dialog must implement. See ConfigWidgetBase for a base class that implements this interface and defines various convenience methods as well.

changed_signal = None

This signal must be emitted whenever the user changes a value in this widget

supports_restoring_to_defaults = True

Set to True iff the restore_to_defaults() method is implemented.

restore_defaults_desc = 'Obnovit nastavení na výchozí hodnoty. Musíte kliknout na Použít pro skutečné uložení výchozího nastavení.'

The tooltip for the „Restore defaults“ button

restart_critical = False

If True the Preferences dialog will not allow the user to set any more preferences. Only has effect if commit() returns True.

genesis(gui)[zdroj]

Called once before the widget is displayed, should perform any necessary setup.

Parametry:

gui – The main calibre graphical user interface

initialize()[zdroj]

Should set all config values to their initial values (the values stored in the config files). A „return“ statement is optional. Return
False if the dialog is not to be shown.

restore_defaults()[zdroj]

Should set all config values to their defaults.

commit()[zdroj]

Save any changed settings. Return True if the changes require a restart, False otherwise. Raise an AbortCommit exception to indicate that an error occurred. You are responsible for giving the user feedback about what the error is and how to correct it.

refresh_gui(gui)[zdroj]

Called once after this widget is committed. Responsible for causing the gui to reread any changed settings. Note that by default the GUI re-initializes various elements anyway, so most widgets won’t need to use this method.

initial_tab_changed()[zdroj]

Called if the initially displayed tab is changed before the widget is shown, but after it is initialized.

class calibre.gui2.preferences.ConfigWidgetBase(parent=None)[zdroj]

Base class that contains code to easily add standard config widgets like checkboxes, combo boxes, text fields and so on. See the register() method.

This class automatically handles change notification, resetting to default, translation between gui objects and config objects, etc. for registered settings.

If your config widget inherits from this class but includes setting that are not registered, you should override the ConfigWidgetInterface methods and call the base class methods inside the overrides.

changed_signal

This signal must be emitted whenever the user changes a value in this widget

supports_restoring_to_defaults = True

Set to True iff the restore_to_defaults() method is implemented.

restart_critical = False

If True the Preferences dialog will not allow the user to set any more preferences. Only has effect if commit() returns True.

register(name, config_obj, gui_name=None, choices=None, restart_required=False, empty_string_is_None=True, setting=<class 'calibre.gui2.preferences.Setting'>)[zdroj]

Register a setting.

Parametry:

- name – The setting name

- config_obj – The config object that reads/writes the setting

- gui_name – The name of the GUI object that presents an interface to change the setting. By default it is assumed to be 'opt_' + name.

- choices – If this setting is a multiple choice (combobox) based setting, the list of choices. The list is a list of two element tuples of the form: [(gui name, value), ...]

- setting – The class responsible for managing this setting. The default class handles almost all cases, so this param is rarely used.

initialize()[zdroj]

Should set all config values to their initial values (the values stored in the config files). A „return“ statement is optional. Return
False if the dialog is not to be shown.

commit(*args)[zdroj]

Save any changed settings. Return True if the changes require a restart, False otherwise. Raise an AbortCommit exception to indicate that an error occurred. You are responsible for giving the user feedback about what the error is and how to correct it.

restore_defaults(*args)[zdroj]

Should set all config values to their defaults.

© Copyright Kovid Goyal.
Naposledy aktualizováno kvě 03, 2024.

Navigace

- moduly
- další |
- předchozí |
- Spustit »
- Rozhraní příkazového řádku

Rozhraní příkazového řádku

Poznámka

On macOS, the command line tools are inside the calibre bundle, for example, if you installed calibre in /Applications the command line tools are in /Applications/calibre.app/Contents/MacOS/. So, for example, to run ebook-convert you would use: /Applications/calibre.app/Contents/MacOS/ebook-convert.

Dokumentované příkazy

- calibre
- calibre-customize
- calibre-debug
- calibre-server
- calibre-smtp
- calibredb
- ebook-convert
- ebook-edit
- ebook-meta
- ebook-polish
- ebook-viewer
- fetch-ebook-metadata
- lrf2lrs
- lrfviewer
- lrs2lrf
- web2disk

Nedokumentované příkazy

- ebook-device

- markdown-calibre

Použití pro nedokumentované příkazy můžete vidět jejich provedením bez parametrů v terminálu.

© Copyright Kovid Goyal.
Naposledy aktualizováno kvě 03, 2024.

Navigace

- moduly
- další |
- předchozí |
- Spustit »
- Rozhraní příkazového řádku »
- calibre

calibre

calibre [volby] [cesta_k_eknize nebo url calibre...]

Spustí hlavní grafické uživatelské rozhraní Calibre a volitelně přidejte e-knihu v cestě_k_eknize do databáze. Můžete také zadat adresy URL Calibre pro provádění jednotlivých různých akcí, než jen přidávání knih. Například:

calibre://view-book/testovací_knihovna/1842/epub

Ro otevře knihu s ID 1842 ve formátu EPUB z knihovny „testovací_knihovna“ v Prohlížeči e-knih Calibre. Názvy knihoven jsou názvy složek knihoven s mezerami nahrazenými podtržítky. Úplný popis různých akcí založených na URL naleznete v Uživatelské příručce.

Kdykoliv předáváte parametry pro calibre, který mají mezery, uzavřete parametry uvozovkami. Například: „/some path/with spaces

[volby]

--detach

Odpojit od řídícího terminálu, pokud nějaký je (pouze pro Linux)

--help, -h

zobrazit tuto zprávu nápovědy a ukončit

--ignore-plugins

Ignorovat vlastní moduly. Užitečné, pokud jste nainstalovali modul, který brání Calibre ve spuštění.

--no-update-check

Nekontrolovat aktualizace

--shutdown-running-calibre, -s

Způsobí vypnutí spuštěných instancí Calibre. Pamatujte, že pokud máte spuštěné úlohy, budou bez upozornění přerušeny, takže to používejte opatrně.

--start-in-tray

Spustit minimalizované do hlavního panelu systému.

--verbose, -v

Ignorováno, nepoužívejte. Přítomno pouze z historických důvodů

--version

zobrazit číslo verze programu a ukončit

--with-library

Použít knihovnu umístěnou v zadané cestě.

© Copyright Kovid Goyal.
Naposledy aktualizováno kvě 03, 2024.

Navigace

- moduly
- další |
- předchozí |
- Spustit »
- Rozhraní příkazového řádku »
- calibre-customize

calibre-customize

calibre-customize options

Přizpůsobit Calibre načtením externích modulů.

Kdykoliv předáváte parametry pro calibre-customize, který mají mezery, uzavřete parametry uvozovkami. Například: „/some path/with spaces

[volby]

--add-plugin, -a

Přidat modul zadáním cesty k souboru ZIP, který ho obsahuje.

--build-plugin, -b

Pro vývojáře doplňků: Cesta ke složce, ve které vyvíjíte doplněk. Tento příkaz automaticky zabalí doplněk a aktualizuje ho v Calibre.

--customize-plugin

Customize plugin. Specify name of plugin and customization string separated by a comma. The customization string is the same as you would enter when customizing the plugin in the main calibre GUI.

--disable-plugin

Zakázat uvedený modul

--enable-plugin

Povolit uvedený modul

--help, -h

zobrazit tuto zprávu nápovědy a ukončit

--list-plugins, -l

Vypsat všechny nainstalované moduly

--remove-plugin, -r

Odebrat vlastní modul podle jména. Nemá žádný vliv na vestavěné moduly.

--version

zobrazit číslo verze programu a ukončit

© Copyright Kovid Goyal.
Naposledy aktualizováno kvě 03, 2024.

Navigace

- moduly
- další |
- předchozí |
- Spustit »
- Rozhraní příkazového řádku »
- calibre-debug

calibre-debug

calibre-debug [options]

Různá rozhraní příkazového řádku užitečná pro ladění Calibre. Bez voleb spouští tento příkaz vložený překladač Pythonu. Můžete také spustit hlavní grafické rozhraní Calibre, Prohlížeč e-knih Calibre a editor Calibre v režimu ladění.

Také obsahuje rozhraní pro různé kusy Calibre, které neobsahují vyhrazené nástroje příkazového řádku, jako jsou částečné vkládání písma, nástroj Rozdílů e-knih a tak dále.

Můžete také použít calibre-debug ke spuštění samostatných skriptů. K tomu ho použijte takto:

calibre-debug -e myscript.py -- --option1 --option2 file1 file2 …

Všechno za -- je předáno skriptu. Ladění Calibre můžete také použít jako shebang ve skriptech, například takto:

#!/usr/bin/env -S calibre-debug -e -- –

Kdykoliv předáváte parametry pro calibre-debug, který mají mezery, uzavřete parametry uvozovkami. Například: „/some path/with spaces

[volby]

--add-simple-plugin

Přidat jednoduchý doplněk (např. doplněk obsahující pouze soubor .py) zadáním cesty k souboru py obsahujícímu kód doplňku.

--command, -c

Spustit kód Pythonu.

--debug-device-driver, -d

Ladit rozpoznávání zařízení

--default-programs

(Od)registrovat Calibre ve výchozích programech Windows. --default-programs = (register|unregister)

--diff

Spustit rozdílový nástroj Calibre. Například: calibre-debug --diff soubor1 soubor2

--edit-book

Spustit nástroj calibre Upravit knihu v režimu ladění.

--exec-file, -e

Spustit kód Pythonu v souboru.

--explode-book, -x

Rozložit knihu do zadané složky. Použití: -x soubor.epub vystupni_adresar Exportuje knihu jako kolekci souborů HTML a metadat, které můžete upravovat pomocí standardních nástrojů pro úpravu HTML. Funguje se soubory EPUB, AZW3, HTMLZ a DOCX.

--export-all-calibre-data

Export všech dat Calibre (knihy, nastavení, moduly). Obvykle budete vyzváni k zadání složky pro export a knihoven, které chcete exportovat. Můžete je také zadat jako parametry příkazového řádku a tyto otázky přeskočit. Pro složku pro export a knihovny použijte absolutní cesty. Pro export všech knihoven lze použít speciální klíčové slovo "all". Příklady: calibre-debug --export-all-calibre-data # pro interaktivní použití calibre-debug --export-all-calibre-data /cesta/k/prazdne/slozce/pro/export /cesta/ke/slozce/knihovny1 /cesta/ke/slozce/knihovny2 calibre-debug --export-all-calibre-data /slozks/pro/export all # export všech známých knihoven

--fix-multiprocessing

Pro interní použití

--gui, -g

Spustit grafické rozhraní s povoleným laděním. Výstup ladění je vytištěn do stdout a stderr.

--gui-debug

Spustit grafické rozhraní s konzolí ladění, protokolování do zadané cesty. Pouze pro vnitřní použití, pro spuštění grafického rozhraní v režimu ladění použijte volbu -g

--help, -h

zobrazit tuto zprávu nápovědy a ukončit

--implode-book, -i

Složit dříve rozloženou knihu. Použití: -i vystupni_adresar soubor.epub Importuje knihu ze souborů ve vystupni_adresar, který musí být vytvořen předchozím vyvoláním --explode-book. Ujistěte se, že zadáváte stejný typ souboru, jaký byl použit při rozložení.

--import-calibre-data

Importovat dříve exportovaná data Calibre

--inspect-mobi, -m

Zkontrolovat soubory MOBI v zadaných cestách

--paths

Výstup cest potřebných pro nastavení prostředí Calibre

--run-plugin, -r

Spustit modul poskytující rozhraní příkazového řádku. Například: calibre-debug -r „Název modulu“ -- soubor1 --volba1 Všechno za -- bude předáno modulu jako parametry.

--run-test, -t

Spusťte pojmenované testy. Pro spuštění všech testů použijte speciální hodnotu "all". Pokud název testu začíná tečkou, je považován za název modulu. Pokud název testu začíná @, je považován za název kategorie.

--run-without-debug

Nespouštějte s nastaveným příznakem DEBUG

--shutdown-running-calibre, -s

Způsobí vypnutí spuštěných instancí Calibre. Pamatujte, že pokud máte spuštěné úlohy, budou bez upozornění přerušeny, takže to používejte opatrně.

--subset-font, -f

Částečně vložit zadané písmo. Použijte -- po této volbě pro předání volby programu pro částečné vkládání písem.

--test-build

Testovat binární moduly v sestavení

--version

zobrazit číslo verze programu a ukončit

--viewer, -w

Spustit Prohlížeč e-knih v režimu ladění

© Copyright Kovid Goyal.
Naposledy aktualizováno kvě 03, 2024.

Navigace

- moduly
- další |
- předchozí |
- Spustit »
- Rozhraní příkazového řádku »
- calibre-server

calibre-server

calibre-server [options] [path to library folder ...]

Spustit Server s obsahem Calibre. Server s obsahem Calibre vystaví vaše knihovny Calibre na internet. Můžete zadat cestu ke složkám knihoven jako parametry do calibre-server. Pokud nezadáte žádné cesty, budou použity všechny knihovny, o kterých hlavní program Calibre ví.

Kdykoliv předáváte parametry pro calibre-server, který mají mezery, uzavřete parametry uvozovkami. Například: „/some path/with spaces

[volby]

--access-log

Cesta k souboru protokolů přístupu. Tento protokol obsahuje informace o připojení klientů k serveru a vytváření požadavků. Ve výchozím nastavení není prováděno žádné protokolování přístupu.

--ajax-timeout

Čas (v sekundách) čekání na odezvu ze serveru při dotazování.

--auth-mode

Zvolte typ použitého ověřování. Nastavte režim ověřování HTTP používaný serverem. Nastavte na "základní", pokud je tento server za SSL proxy. Jinak ponechte jako "automaticky", což použije "základní", pokud je nakonfigurováno SSL, jinak použije "digest".

--auto-reload

Automaticky znovu načíst server, když se změní zdrojový kód. Užitečné pro vývoj. Měli byste také zadat malou hodnotu pro časový limit vypnutí.

--ban-after

Počet chybných přihlášení pro zakázání. Počet chybných přihlášení, po kterých je IP adresa zakázána

--ban-for

Zakázat IP adresy, které mají opakovaná chybná přihlášení. Dočasně zakáže přístup pro IP adresy, které mají opakovaná chybná přihlášení, na zadaný počet minut. Užitečné zabránění pokusům o odhadnutí hesel. Pokud je nastaveno na nulu, je zakazování vypnuto.

--book-list-mode

Zvolte výchozí režim seznamu knih. Nastavte výchozí režim seznamu knih, který bude použit pro nové uživatele. Jednotliví uživatelé mohou přepsat výchozí nastavení ve svém vlastním nastavení. Výchozí je použití mřížky obálek.

--compress-min-size

Minimální velikost, pro kterou odpovědi použijí kompresi dat (v bajtech).

--custom-list-template

Cesta k souboru JSON obsahujícímu šablonu pro režim vlastního seznamu knih. Nejsnadnější způsob vytvoření takového souboru šablony je přejít v Calibre do Předvolby->Sdílení po síti->Šablona seznamu knih, vytvořit šablonu a exportovat ji.

--daemonize

Spustit proces na pozadí jako démon (pouze Linux).

--displayed-fields

Omezit zobrazená uživatelem definovaná pole. Čárkami oddělený seznam uživatelem definovaných polí metadat, která budou zobrazena Serverem s obsahem v zobrazeních /opds a /mobilní. Pokud zadáte tuto volbu, nebudou zobrazena žádná pole, která nejsou v tomto seznamu. Například: my_rating,my_tags

--enable-allow-socket-preallocation, --disable-allow-socket-preallocation

Předpřidělení soketu, například při aktivaci soketu systemd. Ve výchozím nastavení je tato volba povolena.

--enable-auth, --disable-auth

Ověřování na základě hesla pro přístup k serveru. Obyčejně je server neomezený, umožňuje přístup každému. Pomocí této volby můžete omezit přístup na předdefinované uživatele. Ve výchozím nastavení je tato volba zakázána.

--enable-fallback-to-detected-interface, --disable-fallback-to-detected-interface

Záloha pro automatické rozpoznání rozhraní. Pokud se server není z nějakého důvodu schopen připojit k rozhraní zadanému ve volbě listen_on, pak se bude snažit rozpoznat rozhraní, které se připojuje k vnějšímu světu a připojit se k němu. Ve výchozím nastavení je tato volba povolena.

--enable-local-write, --disable-local-write

Povolit neověřeným místním připojením provádět změny. Obyčejně, pokud nezapnete ověřování, server pracuje v režimu pouze pro čtení, aby neumožnil anonymním uživatelům provádět změny ve vašich knihovnách Calibre. Tato volba umožňuje provádět změny komukoliv připojenému ze stejného počítače, na kterém je spuštěn server. To je užitečné, pokud chcete spustit server bez ověřování, ale stále používáte calibredb k provádění změn ve vašich knihovnách Calibre. Pamatujte, že zapnutí této volby znamená, že jakýkoliv program spuštěný v počítači může provádět změny ve vašich knihovnách Calibre. Ve výchozím nastavení je tato volba zakázána.

--enable-log-not-found, --disable-log-not-found

Protokolovat požadavky HTTP 404 (nenalezeno). Obyčejně server zaznamenává všechny požadavky HTTP na zdroje, které nejsou nalezeny. To může generovat velké množství protokolového spamu, pokud je váš server cílem botů. Použijte tuto volbu pro vypnutí. Ve výchozím nastavení je tato volba povolena.

--enable-use-bonjour, --disable-use-bonjour

Inzerovat informační kanály OPDS prostřednictvím BonJour. Inzerovat informační kanály OPDS prostřednictvím služby BonJour, aby aplikace založené na čtení OPDS mohly automaticky rozpoznat a připojit k serveru. Ve výchozím nastavení je tato volba povolena.

--enable-use-sendfile, --disable-use-sendfile

Přenosy nulových kopií souborů pro zvýšení výkonu. Toto použije přenosy nulových kopií v jádru při odesílání souborů přes síť, zvýšení výkonu. Může to však způsobit poškozené přenosy souborů u některých poškozených systémů souborů. Pokud narazíte na poškozené přenosy souborů, vypněte to. Ve výchozím nastavení je tato volba povolena.

--help, -h

zobrazit tuto zprávu nápovědy a ukončit

--ignored-fields

Ignorovaná uživatelem definovaná pole metadat. Čárkami oddělený seznam uživatelem definovaných polí metadat, která nebudou zobrazena Serverem s obsahem v zobrazeních /opds a /mobilní. Například: my_rating,my_tags

--listen-on

Rozhraní, na kterém naslouchat pro připojení. The default is to listen on all available IPv6 and IPv4 interfaces. You can change this to, for example, "127.0.0.1" to only listen for IPv4 connections from the local machine, or to "0.0.0.0" to listen to all incoming IPv4 connections.

--log

Cesta k souboru protokolu pro protokol serveru. Tento protokol obsahuje informace a chyby, ne protokoly přístupu. Ve výchozím nastavení je zapsán do stdout.

--manage-users

Spravovat databázi uživatelů, kteří mají povoleno připojovat se k tomuto serveru. V automatizovaném režimu to můžete použít přidáním –. Podrobnosti naleznete na calibre-server --manage-users -- help. Podívejte se také na volbu --userdb.

--max-header-line-size

Max. velikost jednoduchého záhlaví HTTP (v KB).

--max-job-time

Maximální čas pro pracovní procesy. Maximální množství času, který mají pracovní procesy povoleno být spuštěny (v minutách). Nastavte na nulu pro žádné omezení.

--max-jobs

Maximální počet pracovních procesů. Pracovní procesy jsou spouštěny podle potřeby a použity pro velké úlohy, jako jsou příprava knihy pro zobrazení, přidávání knih, převod atd. Obyčejně je max. počet takových procesů založen na počtu jader procesoru. Můžete to ovládat tímto nastavením.

--max-log-size

Max. velikost souboru protokolu (v MB). Maximální velikost souborů protokolu generovaných serverem. Když bude protokol větší než tato velikost, je automaticky vyměněn. Nastavte na nulu pro zakázání výměny protokolu.

--max-opds-items

Maximální počet knih v informačních kanálech OPDS. Maximální počet knih, které server vrátí v jednom získaném informačním kanálu OPDS.

--max-opds-ungrouped-items

Maximální počet neseskupených položek v informačních kanálech OPDS. Seskupit položky do kategorií jako jsou autor nebo štítek podle prvního písmene, když je více než tento počet položek. Nastavte na nula pro zakázání.

--max-request-body-size

Max. povolená velikost pro soubory odesílané na server (v MB).

--num-per-page

Počet zobrazených knih na jedné stránce. Počet zobrazených knih na jedné stránce v prohlížeči:

--pidfile

Zapsat kód PID procesu do zadaného souboru

--port

Port, na kterém naslouchat pro připojení.

--search-the-net-urls

Cesta k souboru JSON obsahujícímu adresy URL pro funkci „Vyhledat na internetu“. Nejsnadnější způsob vytvoření takového souboru je přejít v Calibre do Předvolby->Sdílení po síti->Vyhledat na internetu, vytvořit adresy URL a exportovat je.

--shutdown-timeout

Celkový čas v sekundách, který čekat na čisté vypnutí.

--ssl-certfile

Cesta k souboru certifikátu SSL.

--ssl-keyfile

Cesta k souboru soukromého klíče SSL.

--timeout

Čas (v sekundách), po kterém je nečinné připojení uzavřeno.

--trusted-ips

Povolit neověřeným připojením z konkrétní adresy IP provádět změny. Obyčejně, pokud nezapnete ověřování, server pracuje v režimu pouze pro čtení, aby neumožnil anonymním uživatelům provádět změny ve vašich knihovnách Calibre. Tato volba umožňuje provádět změny komukoliv připojenému z konkrétní adresy IP. Musí to být čárkami oddělený seznam adres nebo specifikací sítě. To je užitečné, pokud chcete spustit server bez ověřování, ale stále používáte calibredb k provádění změn ve vašich knihovnách Calibre. Pamatujte, že zapnutí této volby znamená, že kdokoliv připojený ze zadaných adres IP může provádět změny ve vašich knihovnách Calibre.

--url-prefix

Předpona, kterou předřadit všem adresám URL. Užitečné, pokud si přejete spustit tento server za reverzní proxy. Například použijte /calibre jako předponu URL.

--userdb

Cesta k uživatelské databázi použité pro ověřování. Databáze je soubor SQLite. Pro jeho vytvoření použijte --manage-users. Více o správě uživatelů si můžete přečíst v: https://manual.calibre-ebook.com/cs/server.html#managing-user-accounts-from-the-command-line-only

--version

zobrazit číslo verze programu a ukončit

--worker-count

Počet pracovních vláken použitých pro zpracování požadavků.

© Copyright Kovid Goyal.
Naposledy aktualizováno kvě 03, 2024.

Navigace

- moduly
- další |
- předchozí |
- Spustit »
- Rozhraní příkazového řádku »
- calibre-smtp

calibre-smtp

calibre-smtp [options] [from to text]

Odeslat e-mail pomocí protokolu SMTP. calibre-smtp má dva režimy. V režimu psaní zadáte from to a text, a ty jsou použity k sestavení a odeslání e-mailové zprávy. V režimu filtrování čte calibre-smtp kompletní e-mailovou zprávu ze vstupu STDIN a odešle ji.

text je tělo e-mailové zprávy.
Pokud text není zadán, kompletní e-mailová zpráva je čtena ze vstupu STDIN.
from je e-mailová adresa odesílatele a to je e-mailová adresa příjemce.
Když je kompletní e-mail čten ze vstupu STDIN, from a to jsou použity pouze při připojení vstupu STDIN, hlavičky zpráv nejsou měněny.

Kdykoliv předáváte parametry pro calibre-smtp, který mají mezery, uzavřete parametry uvozovkami. Například: „/some path/with spaces

[volby]

--fork, -f

Rozvětvit a doručit zprávu na pozadí. Pokud použijete tuto volbu, měli byste také použít --outbox pro vyřešení selhání doručení.

--help, -h

zobrazit tuto zprávu nápovědy a ukončit

--localhost, -l

Název hostitele localhost. Použito při připojování k serveru SMTP.

--outbox, -o

Cesta ke složce maildir pro e-mailové zprávy, které se nepodařilo odeslat.

--timeout, -t

Časový limit pro připojení

--verbose, -v

Více podrobností

--version

zobrazit číslo verze programu a ukončit

COMPOSE MAIL

Volby pro napsání e-mailu. Ignorovat, pokud text není zadán

--attachment, -a

Soubor připojený k e-mailu

--subject, -s

Předmět e-mailu

SMTP RELAY

Volby pro použití serveru přenosu SMTP pro odesílání pošty. Calibre se pokusí odeslat e-mail přímo, pokud není zadáno –relay.

--cafile

Cesta ke souboru zřetězených certifikátů CA ve formátu PEM používaných při ověřování certifikátu serveru při použití TLS. Ve výchozím nastavení se používají certifikáty systému CA.

--dont-verify-server-certificate

Neověřovat certifikát serveru při připojení pomocí TLS. Toto bylo výchozí chování ve verzích Calibre před 3.27. Pokud používáte přenos s certifikátem s vlastním podpisem nebo jinak neplatným certifikátem, můžete použít tuto volbu k obnovení chování před 3.27

--encryption-method, -e

Metoda šifrování použitá při připojování k přenosu. Možnosti jsou TLS, SSL a Žádné. Výchozí je TLS. VAROVÁNÍ: Výběr volby Žádné je vysoce nezabezpečený.

--password, -p

Heslo pro přenos

--port

Port pro připojení k serveru přenosu. Výchozí je použít 465, pokud je metoda šifrování SSL, jinak 25.

--relay, -r

Server přenosu SMTP použitý pro odeslání e-mailu.

--username, -u

Uživatelské jméno pro přenos

© Copyright Kovid Goyal.
Naposledy aktualizováno kvě 03, 2024.

Navigace

- moduly
- další |
- předchozí |
- Spustit »
- Rozhraní příkazového řádku »
- calibredb

calibredb

calibredb command [options] [arguments]

calibredb je rozhraní příkazového řádku k databázi Calibre.
Má několik níže zdokumentovaných podpříkazů.

calibredb calibredb` lze použít buď pro zacházení s databází
Calibre zadanou cestou nebo se Serverem s obsahem Calibre běžícím buď na místním počítači nebo přes internet. Server s obsahem Calibre můžete spustit buď pomocí programu calibre-server nebo v hlavním programu Calibre klikněte na Připojit nebo sdílet  →  Spustit
Server s obsahem. Protože calibredb může ve vašich knihovnách
Calibre provádět změny, musíte nejprve na serveru nastavit ověřování. Jsou dva způsoby, jak toho dosáhnout:

- Pokud se plánujete připojovat pouze k serveru běžícímu na

stejném počítači, můžete jednoduše použít volbu Serveru s obsahem --enable-local-write, aby umožnil jakémukoliv programu, včetně calibredb, běžícímu na místním počítači provádět změny v datech Calibre. Při spuštění serveru z hlavního programu Calibru je tato možnost v Předvolby →
Sdílení po síti → Rozšířené.

- Pokud chcete povolit přístup přes internet, měli byste na serveru nastavit

uživatelské účty a použít volby --username a --password pro calibredb, aby jim udělil přístup. Ověřování uživatele pro calibre-server můžete nastavit pomocí volby --enable-auth a pomocí --manage-users vytvořit uživatelské účty. Pokud spouštíte server z hlavního programu Calibre, použijte Předvolby → Sdílení po síti → Vyžadovat uživatelské jméno a heslo.

Pro připojení k běžícímu serveru s obsahem předejte adresu URL serveru volbě --with-library, viz dokumentace této volby pro podrobnosti a příklady.

- GLOBÁLNÍ VOLBY

- list

- add

- PŘIDÁVÁNÍ ZE SLOŽEK

- remove

- add_format

- remove_format

- show_metadata

- set_metadata

- export

- catalog

- VOLBY EPUB

- saved_searches

- add_custom_column

- custom_columns

- remove_custom_column

- set_custom

- restore_database

- check_library

- list_categories

- backup_metadata

- clone

- embed_metadata

- search

- fts_index

- fts_search

GLOBÁLNÍ VOLBY

--help, -h

zobrazit tuto zprávu nápovědy a ukončit

--library-path, --with-library

Cesta ke knihovně Calibre. Výchozí je použít cestu uloženou v nastaveních. Pro provádění akce na vzdálených knihovnách se můžete také připojit k Serveru s obsahem Calibre. Abyste to udělali, použijte URL ve formě: http://hostitel:port/#id_knihovny, například http://localhost:8080/#mojeknihovna. id_knihovny je identifikátor knihovny, ke které se chcete připojit na Serveru s obsahem. Pomocí speciální hodnoty id_knihovny - můžete získat seznam identifikátorů knihoven dostupných na serveru. Pro podrobnosti o nastavení přístupu prostřednictvím Serveru s obsahem navštivte https://manual.calibre-ebook.com/cs/generated/cs/calibredb.html.

--password

Heslo pro připojení k Serveru s obsahem Calibre. Pro přečtení hesla ze standardního vstupu použijte speciální hodnotu: <stdin>. Pro přečtení hesla ze souboru použijte: <f:/path/to/file> (tj. <f: následované úplnou cestou k souboru a koncovou >). Výše uvedené lomené závorky jsou vyžadovány, nezapomeňte je uvodit nebo použijte uvozovky pro váš shell.

--timeout

Časový limit v sekundách u připojení ke knihovně Calibre přes síť. Výchozí je dvě minuty.

--username

Uživatelské jméno pro připojení k Serveru s obsahem Calibre

--version

zobrazit číslo verze programu a ukončit

list

calibredb list [options]

Vypsat knihy dostupné v databázi Calibre.

Kdykoliv předáváte parametry pro calibredb, který mají mezery, uzavřete parametry uvozovkami. Například: „/some path/with spaces

--ascending

Seřadit výsledky ve vzestupném pořadí

--fields, -f

Zobrazená pole při výpisu knih v databázi. Měl by to být čárkami oddělený seznam polí. Dostupná pole: author_sort, authors, comments, cover, formats, identifiers, isbn, languages, last_modified, pubdate, publisher, rating, series, series_index, size, tags, template, timestamp, title, uuid Výchozí: title,authors. Pro výběr všech polí může být použito speciální pole „all“. Kromě výše uvedených polí jsou také dostupná vlastní pole, jako například *field_name, pro vlastní pole #rating použijte název *rating

--for-machine

Generovat výstup ve formátu JSON, který je vhodnější pro strojní analýzu. Způsobí, že šířka řádku a volby oddělovače budou ignorovány.

--limit

Maximální počet výsledků k zobrazení. Výchozí: vše

--line-width, -w

Maximální šířka jednoho řádku ve výstupu. Výchozí nastavení pro rozpoznání velikosti obrazovky.

--prefix

Předpona pro všechny cesty k souborům. Výchozí je absolutní cesta do složky knihovny.

--search, -s

Filtrovat výsledky podle vyhledávacího dotazu. Pro formát vyhledávacího dotazu se podívejte do dokumentace související s hledáním v uživatelské příručce. Výchozí je neprovádět žádné filtrování.

--separator

Řetězec použitý pro oddělení polí. Výchozí je mezera.

--sort-by

Pole, podle kterého řadit výsledky. Více polí můžete zadat jejich oddělením čárkami. Dostupná pole: author_sort, authors, comments, cover, formats, identifiers, isbn, languages, last_modified, pubdate, publisher, rating, series, series_index, size, tags, template, timestamp, title, uuid Výchozí: id

--template

Šablona, kterou spustit, pokud je „template“ v seznamu polí. Výchozí: Žádná

--template_file, -t

Cesta k souboru obsahujícímu šablonu, kterou spustit, pokud je „template“ v seznamu polí. Výchozí: Žádná

--template_heading

Záhlaví sloupce šablony. Výchozí: %dvýchozí. Tato volba je ignorována, pokud je nastavena volba --for-machine

add

calibredb add [options] file1 file2 file3...

Přidat zadané soubory jako knihy do databáze. Můžete také zadat složky, podívejte se na volby související s následující složkou.

Kdykoliv předáváte parametry pro calibredb, který mají mezery, uzavřete parametry uvozovkami. Například: „/some path/with spaces

--authors, -a

Nastavit autory přidaných knih

--automerge, -m

Pokud jsou nalezeny knihy s podobnými názvy a autory, automaticky sloučit příchozí formáty (soubory) do existujících záznamů knih. Hodnota „ignore“ znamená, že duplicitní formáty jsou zahozeny. Hodnota „overwrite“ znamená, že duplicitní formáty v knihovně jsou přepsány nově přidanými soubory. Hodnota „new_record“ znamená, že duplicitní formáty jsou umístěny do nového záznamu knihy.

--cover, -c

Nastavit použitou obálku pro přidanou knihu

--duplicates, -d

Přidat knihy do databáze, i když již existují. Srovnání je prováděno na základě názvů knih. Pamatujte, že volby --automerge mají přednost.

--empty, -e

Přidat prázdnou knihu (kniha bez formátů)

--identifier, -I

Nastavit identifikátory pro tuto knihu, např. -I asin:XXX -I isbn:YYY

--isbn, -i

Nastavit ISBN přidaných knih

--languages, -l

Čárkami oddělený seznam jazyků (nejlepší je použít kódy jazyka ISO639, i když mohou být rozpoznány také některé názvy jazyků)

--series, -s

Nastavit série přidaných knih

--series-index, -S

Nastavit číslo série přidaných knih

--tags, -T

Nastavit štítky přidaných knih

--title, -t

Nastavit název přidaných knih

PŘIDÁVÁNÍ ZE SLOŽEK

Volby pro ovládání přidávání knih ze složek. Ve výchozím nastavení jsou přidávány pouze soubory, které mají příponu známých typů souborů e-knih.

--add

Vzor názvu souboru (glob), soubory odpovídající tomuto vzoru budou přidány při prohledávání souborů ve složkách, dokonce i když nejsou známého typu souboru e-knihy. Může být zadáno vícekrát pro více vzorů.

--ignore

Vzor názvu souboru (glob), soubory odpovídající tomuto vzoru budou ignorovány při prohledávání souborů ve složkách. Může být zadáno vícekrát pro více vzorů: Například: *.pdf bude ignorovat všechny soubory PDF

--one-book-per-directory, -1

Předpokládat, že každá složka má pouze jednu logickou knihu, a že všechny soubory v ní jsou různé formáty e-knih této knihy

--recurse, -r

Zpracuj složky rekurzivně

remove

calibredb remove ids

Odebrat knihy identifikované identifikátory z databáze. Identifikátory by měly být čárkami oddělený seznam identifikačních čísel (identifikační čísla můžete získat pomocí příkazu search). Například 23,34,57-85 (když zadáváte rozsah, poslední číslo rozsahu není zahrnuto).

Kdykoliv předáváte parametry pro calibredb, který mají mezery, uzavřete parametry uvozovkami. Například: „/some path/with spaces

--permanent

Nepoužívat Koš

add_format

calibredb add_format [options] id ebook_file

Přidat e-knihu v ebook_file do dostupných formátů pro logickou knihu identifikovanou identifikátorem. Identifikátor můžete získat pomocí příkazu search. Pokud formát již existuje, je nahrazen, pokud není zadána volba nenahrazovat.

Kdykoliv předáváte parametry pro calibredb, který mají mezery, uzavřete parametry uvozovkami. Například: „/some path/with spaces

--as-extra-data-file

Přidat soubor jako dodatečný datový soubor do knihy, ne jako formát e-knihy

--dont-replace

Nenahrazovat formát, pokud již existuje

remove_format

calibredb remove_format [options] id fmt

Odebrat formát fmt z logické knihy identifikované identifikátorem. Identifikátor můžete získat pomocí příkazu search. fmt by měla být přípona souboru, jako je LRF, TXT nebo EPUB. Pokud logická kniha nemá dostupné fmt, nedělat nic.

Kdykoliv předáváte parametry pro calibredb, který mají mezery, uzavřete parametry uvozovkami. Například: „/some path/with spaces

show_metadata

calibredb show_metadata [options] id

Zobrazit metadata uložená v databázi Calibre pro knihu identifikovanou identifikátorem.
Identifikátor je identifikační číslo z příkazu search.

Kdykoliv předáváte parametry pro calibredb, který mají mezery, uzavřete parametry uvozovkami. Například: „/some path/with spaces

--as-opf

Vypsat metadata v podobě OPF (XML)

set_metadata

calibredb set_metadata [options] book_id [/path/to/metadata.opf]

Nastavit metadata uložená v databázi Calibre pro knihu identifikovanou pomocí book_id ze souboru OPF metadata.opf. book_id je identifikační číslo knihy z příkazu search. Můžete získat rychlé chování pro formát OPF pomocí přepínače –as-opf příkazu show_metadata. Můžete také nastavit metadata jednotlivých polí pomocí volby –field. Pokud použijete volbu –field, není potřeba zadávat soubor OPF.

Kdykoliv předáváte parametry pro calibredb, který mají mezery, uzavřete parametry uvozovkami. Například: „/some path/with spaces

--field, -f

Pole k nastavení. Formát je field_name:value, například : --field tags:tag1,tag2. Použijte --list-fields pro získání seznamu všech názvů polí. Tuto volbu můžete zadat vícekrát pro nastavení více polí. Poznámka: Pro jazyky musíte použít kódy jazyka ISO639 (např. cs pro češtinu, en pro angličtinu, fr pro francouzštinu atd.). Pro identifikátory je syntaxe --field identifiers:isbn:XXXX,doi:YYYYY. Pro pole logických hodnot (ano/ne) použijte pravda a nepravda nebo ano a ne.

--list-fields, -l

Vypsat názvy polí metadat, která mohou být použita s volbou --field

export

calibredb export [options] ids

Exportovat knihy zadané identifikátory (čárkami oddělený seznam) do systému souborů. Operace exportu ukládá všechny formáty knihy, její obálku a metadata
(v souboru OPF). Jakákoliv dodatečná data přidružená ke knize jsou také uložena
Identifikační čísla můžete získat z příkazu search.

Kdykoliv předáváte parametry pro calibredb, který mají mezery, uzavřete parametry uvozovkami. Například: „/some path/with spaces

--all

Exportovat všechny knihy v databázi, ignorovat seznam identifikátorů.

--dont-asciiize

Nechat Calibre převést všechny neanglické znaky na anglické ekvivalenty pro názvy souborů. To je užitečné, pokud ukládáte do starší verze systému souborů bez plné podpory názvů souborů Unicode. Zadání tohoto přepínače vypne toto chování.

--dont-save-cover

Obyčejně Calibre uloží obálku do samostatného souboru společně se skutečnými soubory e-knih. Zadání tohoto přepínače vypne toto chování.

--dont-save-extra-files

Při ukládání knihy uložit všechny datové soubory přidružené ke knize Zadání tohoto přepínače vypne toto chování.

--dont-update-metadata

Obyčejně Calibre aktualizuje metadata v uložených souborech z toho, co je v knihovně Calibre. Zpomalí ukládání na disk. Zadání tohoto přepínače vypne toto chování.

--dont-write-opf

Obyčejně Calibre zapíše metadata do samostatného souboru OPF společně se skutečnými soubory e-knih. Zadání tohoto přepínače vypne toto chování.

--formats

Čárkami oddělený seznam formátů pro uložení pro každou knihu. Ve výchozím nastavení jsou ukládány všechny dostupné formáty.

--progress

Průběh hlášení

--replace-whitespace

Nahradit prázdné znaky podtržítky.

--single-dir

Exportovat všechny knihy do jedné složky

--template

Šablona pro ovládání názvu souboru a struktury složekukládaných souborů. Výchozí je "{author_sort}/{title}/{title} - {authors}", což uloží knihy do podsložky podle autora s názvy souborů obsahujícími název a autora. Dostupné ovládací prvky jsou: {author_sort, authors, id, isbn, languages, last_modified, pubdate, publisher, rating, series, series_index, tags, timestamp, title}

--timefmt

Formát, ve kterém zobrazovat data. %d - den, %b - měsíc, %m - číslo měsíce, %Y - rok. Výchozí je: %b, %Y

--to-dir

Exportovat knihy do zadané složky. Výchozí je .

--to-lowercase

Převést cesty na malá písmena.

catalog

calibredb catalog /path/to/destination.(csv|epub|mobi|xml...) [options]

Export a catalog in format specified by path/to/destination extension.
Options control how entries are displayed in the generated catalog output.
Note that different catalog formats support different sets of options. To see the different options, specify the name of the output file and then the
–help option.

Kdykoliv předáváte parametry pro calibredb, který mají mezery, uzavřete parametry uvozovkami. Například: „/some path/with spaces

--ids, -i

Čárkami oddělený seznam identifikátorů databáze pro katalog. Pokud je deklarováno, --search je ignorováno. Výchozí: vše

--search, -s

Filtrovat výsledky podle vyhledávacího dotazu. Pro formát vyhledávacího dotazu se podívejte do dokumentace související s hledáním v uživatelské příručce. Výchozí: žádné filtrování

--verbose, -v

Zobrazit podrobné výstupní informace. Užitečné pro ladění

VOLBY EPUB

--catalog-title

Název generovaného katalogu použitý jako název v metadatech. Výchozí: 'My Books' Použije se na: výstupní formáty AZW3, EPUB, MOBI

--cross-reference-authors

Vytvořit křížové odkazy v oddílu Autoři pro knihy s více autory. Výchozí: 'False' Použije se na: výstupní formáty AZW3, EPUB, MOBI

--debug-pipeline

Uložit výstup z různých stavů převodního řetězce do zadané složky. Užitečné, pokud si nejste jisti, ve kterém stavu procesu převodu se objevuje chyba. Výchozí: 'None' Použije se na: výstupní formáty AZW3, EPUB, MOBI

--exclude-genre

Regulární výraz popisující štítky, které vyloučit jako žánry. Výchozí: '[.+]|^+$' vylučuje štítky v hranatých závorkách, např. '[Project Gutenberg]' a '+', výchozí štítek pro přečtené knihy. Použije se na: výstupní formáty AZW3, EPUB, MOBI

--exclusion-rules

Určuje pravidla použitá pro vyloučení knih z generovaného katalogu. Model pro pravidlo vyloučení je buď ('<název pravidla>','Tags','<čárkami oddělený seznam štítků>') nebo ('<název pravidla>','<vlastní sloupec>','<vzor>'). Například: (('Archivované knihy','#stav','Archivováno'),) vyloučí knihu s hodnotou 'Archivováno' ve vlastním sloupci 'stav'. Pokud je definováno více pravidel, budou použita všechna pravidla. Výchozí: "(('Catalogs','Tags','Catalog'),)" Použije se na výstupní formáty AZW3, EPUB, MOBI

--generate-authors

Zahrnout oddíl 'Autoři' do katalogu. Výchozí: 'False' Použije se na: výstupní formáty AZW3, EPUB, MOBI

--generate-descriptions

Zahrnout oddíl 'Popisy' do katalogu. Výchozí: 'False' Použije se na: výstupní formáty AZW3, EPUB, MOBI

--generate-genres

Zahrnout oddíl 'Žánry' do katalogu. Výchozí: 'False' Použije se na: výstupní formáty AZW3, EPUB, MOBI

--generate-recently-added

Zahrnout oddíl 'Nedávno přidané' do katalogu. Výchozí: 'False' Použije se na: výstupní formáty AZW3, EPUB, MOBI

--generate-series

Zahrnout oddíl 'Série' do katalogu. Výchozí: 'False' Použije se na: výstupní formáty AZW3, EPUB, MOBI

--generate-titles

Zahrnout oddíl 'Názvy' do katalogu. Výchozí: 'False' Použije se na: výstupní formáty AZW3, EPUB, MOBI

--genre-source-field

Zdrojové pole pro oddíl ‚Žánry. Výchozí: 'Štítky' Použije se na: výstupní formáty AZW3, EPUB, MOBI

--header-note-source-field

Vlastní pole obsahující text poznámky k vložení do záhlaví Popisu. Výchozí: '' Použije se na: výstupní formáty AZW3, EPUB, MOBI

--merge-comments-rule

#<custom field>:[before|after]:[True|False] určující: <custom field>Vlastní pole obsahující poznámky ke sloučení s komentáři [before|after] Umístění poznámek vzhledem ke kometářům [True|False] - Mezi poznámky a komentáře je vložena vodorovná čára Výchozí: '::' Použije se na: výstupní formáty AZW3, EPUB, MOBI

--output-profile

Určuje výstupní profil. V některých případech je vyžadován výstupní profil pro optimalizaci katalogu pro zařízení. Například 'kindle' nebo 'kindle_dx' vytvoří strukturovaný obsah s Oddíly a Články. Výchozí: 'None' Použije se na: výstupní formáty AZW3, EPUB, MOBI

--prefix-rules

Určuje pravidla použitá pro zahrnutí předpon označujících přečtené knihy, seznam přání a jiné předpony zadané uživatelem. Model pro pravidlo předpony je ('<název pravidla>','<zdrojové pole>','<vzor>','<předpona>'). Pokud je definováno více pravidel, bude použito první odpovídající pravidlo. Výchozí: '(('Read books','tags','+','✓'),('Wishlist item','tags','Wishlist','×'))' Použije se na výstupní formáty AZW3, EPUB, MOBI

--preset

Použít pojmenovanou předvolbu vytvořenou Nástrojem pro sestavení katalogu v grafickém rozhraní. Předvolba určuje všechna nastavení pro sestavení katalogu. Výchozí: 'None' Použije se na výstupní formáty AZW3, EPUB, MOBI

--thumb-width

Nápověda velikosti (v palcích) pro obálky knih v katalogu. Rozsah: 1,0 - 2,0 Výchozí: '1.0' Použije se na výstupní formáty AZW3, EPUB, MOBI

--use-existing-cover

Nahradit existující obálku při generování katalogu. Výchozí: 'False' Použije se na: výstupní formáty AZW3, EPUB, MOBI

saved_searches

calibredb saved_searches [options] (list|add|remove)

Spravovat uložená hledání umístěná v této databázi.
Pokud se pokusíte přidat dotaz s názvem, který již existuje, bude nahrazen.

Syntaxe pro přidání:

calibredb saved_searches add search_name search_expression

Syntaxe pro odebrání:

calibredb saved_searches remove search_name

Kdykoliv předáváte parametry pro calibredb, který mají mezery, uzavřete parametry uvozovkami. Například: „/some path/with spaces

add_custom_column

calibredb add_custom_column [options] label name datatype

Vytvořit vlastní sloupec. label je název sloupce vhodný pro stroje. Neměl by obsahovat mezery nebo dvojtečky. name je název sloupce vhodný pro lidi.
datatype je jeden z: bool, comments, composite, datetime, enumeration, float, int, rating, series, text

Kdykoliv předáváte parametry pro calibredb, který mají mezery, uzavřete parametry uvozovkami. Například: „/some path/with spaces

--display

Slovník voleb pro přizpůsobení, jak budou data v tomto sloupci interpretována. Toto je řetězec JSON. Pro sloupce výčtu použijte --display"{\ "enum_values\ ":[\ "val1\ ", \ "val2\ "]}" Je mnoho voleb, které mohou přijít do proměnné display. Volby podle typu sloupce jsou: složené: composite_template, composite_sort, make_category,contains_html, use_decorations datum a čas: date_format výčet: enum_values, enum_colors, use_decorations celá část, plovoucí: number_format text: is_names, use_decorations Nejlepší způsob jak najít zákonité kombinace, je vytvořit v grafickém rozhraní vlastní sloupec vhodného typu, pak vyhledat v záložním OPF knihu (ujistěte se, že byl vytvořen nový OPF od přidání sloupce). Uvidíte JSON pro "display" pro nový sloupec v OPF.

--is-multiple

Tento sloupec ukládá štítky jako data (tj. více čárkami oddělených hodnot). Použije se, pouze pokud je datový typ text.

custom_columns

calibredb custom_columns [options]

Vypsat dostupné vlastní sloupce. Zobrazí popisky sloupců a identifikátory.

Kdykoliv předáváte parametry pro calibredb, který mají mezery, uzavřete parametry uvozovkami. Například: „/some path/with spaces

--details, -d

Zobrazit podrobnosti pro každý sloupec.

remove_custom_column

calibredb remove_custom_column [options] label

Odebrat vlastní sloupec identifikovaný popiskem. Dostupné sloupce můžete zobrazit příkazem custom_columns.

Kdykoliv předáváte parametry pro calibredb, který mají mezery, uzavřete parametry uvozovkami. Například: „/some path/with spaces

--force, -f

Nepožadovat potvrzení

set_custom

calibredb set_custom [options] column id value

Nastavit hodnotu vlastního sloupce pro knihu identifikovanou identifikátorem.
Seznam identifikátorů můžete získat pomocí příkazu search.
Seznam názvů vlastních sloupců můžete získat pomocí příkazu custom_columns.

Kdykoliv předáváte parametry pro calibredb, který mají mezery, uzavřete parametry uvozovkami. Například: „/some path/with spaces

--append, -a

Pokud sloupec ukládá více hodnot, přidat zadané hodnoty k existujícím, namísto jejich nahrazení.

restore_database

calibredb restore_database [options]

Obnovit tuto databázi z metadat uložených v souborech OPF v každé složce knihovny Calibre. To je užitečné, pokud byl váš soubor meatadata.db poškozen.

VAROVÁNÍ: Tento příkaz znovu úplně vygeneruje vaši databázi. Ztratíte všechna uložená hledání, uživatelské katalogy, zásuvné panely, uložená nastavení převodu pro knihu a vlastní předpisy. Obnovená metadata budou pouze tak přesná, jako to, co je nalezeno v souborech OPF.

Kdykoliv předáváte parametry pro calibredb, který mají mezery, uzavřete parametry uvozovkami. Například: „/some path/with spaces

--really-do-it, -r

Opravdu provést obnovení. Příkaz nebude spuštěn, pokud není zadána tato volba.

check_library

calibredb check_library [options]

Provést některé kontroly souborového systému představujícího knihovnu.
Hlášení jsou invalid_titles, extra_titles, invalid_authors, extra_authors, missing_formats, extra_formats, extra_files, missing_covers, extra_covers, failed_folders

Kdykoliv předáváte parametry pro calibredb, který mají mezery, uzavřete parametry uvozovkami. Například: „/some path/with spaces

--csv, -c

Výstup v CSV

--ignore_extensions, -e

Čárkami oddělený seznam ignorovaných přípon. Výchozí: vše

--ignore_names, -n

Čárkami oddělený seznam ignorovaných názvů. Výchozí: vše

--report, -r

Čárkami oddělený seznam hlášení. Výchozí: vše

--vacuum-fts-db

Vyčistit databázi fulltextového vyhledávání. V závislosti na velikosti databáze to může být velmi pomalé a náročné na paměť.

list_categories

calibredb list_categories [options]

Vytvořit hlášení informací o kategoriích v databázi. Informace je ekvivalentem toho, co je zobrazeno v Prohlížeči štítků.

Kdykoliv předáváte parametry pro calibredb, který mají mezery, uzavřete parametry uvozovkami. Například: „/some path/with spaces

--categories, -r

Čárkami oddělený seznam kategorií názvů vyhledávání. Výchozí: vše

--csv, -c

Výstup v CSV

--dialect

Typ souboru CSV, který vytvářet. Možnosti: excel, excel-tab, unix

--item_count, -i

Výstupem je pouze počet položek v kategorii namísto počtů na položku v kategorii.

--width, -w

Maximální šířka jednoho řádku ve výstupu. Výchozí nastavení pro rozpoznání velikosti obrazovky.

backup_metadata

calibredb backup_metadata [options]

Zálohovat metadata uložená v databázi do jednotlivých souborů OPF v každé složce knih. Toto se normálně děje automaticky, ale můžete tento příkaz spustit s volbou –all, abyste vynutili opětovné vygenerování souborů OPF.

Pamatujte, že normálně není vůbec potřeba toto dělat, protože soubory OPF jsou zálohovány automaticky pokaždé, když se změní metadata.

Kdykoliv předáváte parametry pro calibredb, který mají mezery, uzavřete parametry uvozovkami. Například: „/some path/with spaces

--all

Normálně tento příkaz pracuje pouze nad knihami, které mají zastaralé soubory OPF. Pomocí této volby pracuje nad všemi knihami.

clone

calibredb clone path/to/new/library

Vytvořit klon aktuální knihovny. Toto vytvoří novou, prázdnou knihovnu, která má stejné vlastní sloupce, Virtuální knihovny a jiná nastavení jako aktuální knihovna.

Klonovaná knihovna nebude obsahovat žádné knihy. Pokud chcete vytvořit úplný duplikát včetně všech knih, pak jednoduše použijte nástroje vašeho systému souborů ke zkopírování složky knihovny.

Kdykoliv předáváte parametry pro calibredb, který mají mezery, uzavřete parametry uvozovkami. Například: „/some path/with spaces

embed_metadata

calibredb embed_metadata [options] book_id

Aktualizovat metadata ve skutečných souborech knih uložená v knihovně Calibre z metadat v databázi Calibre. Normálně jsou metadata aktualizována pouze při exportování souborů z Calibre, tento příkaz je užitečný, pokud chcete, aby byla metadata aktualizována na místě. Pamatujte, že různé formáty souborů podporují různá množství metadat. K aktualizaci metadat ve všech knihách můžete pro book_id použít speciální hodnotu ‚all‘. Můžete také zadat mnoho identifikátorů knih oddělených mezerami a rozsahů identifikátorů oddělených spojovníky. Například: calibredb embed_metadata 1 2 10-15 23

Kdykoliv předáváte parametry pro calibredb, který mají mezery, uzavřete parametry uvozovkami. Například: „/some path/with spaces

--only-formats, -f

Aktualizovat metadata pouze v souborech zadaného formátu. Zadejte vícekrát pro více formátů. Ve výchozím nastavení jsou aktualizovány všechny formáty.

search

calibredb search [options] search expression

Vyhledat v knihovně zadaný hledaný výraz vracející čárkami oddělený seznam identifikátorů knih odpovídající hledanému výrazu.Výstupní formát je užitečný pro zásobení jiných příkazů, které přijímají seznam identifikátorů jako vstup.

Hledaný výraz může být cokoliv z výkonného jazyka vyhledávacích dotazů Calibre, například: calibredb search author:asimov ‚title:“i robot“‘

Kdykoliv předáváte parametry pro calibredb, který mají mezery, uzavřete parametry uvozovkami. Například: „/some path/with spaces

--limit, -l

Maximální počet výsledků k navrácení. Výchozí je všechny výsledky.

fts_index

calibredb fts_index [volby] enable/disable/status/reindex

Ovládání procesu indexování fulltextového vyhledávání.

enable

Zapne indexování FTV pro tuto knihovnu.

disable

Vypne indexování FTV pro tuto knihovnu.

status

Zobrazí aktuální stav indexování

reindex

Může být použito ke znovu indexování buď konkrétních knih, nebo celé knihovny. Pro znovu indexování konkrétních knih zadejte identifikátory knih jako další argumenty za příkazem reindex. Pokud nejsou zadány žádné identifikátory knih, bude znovu indexována celá knihovna.

Kdykoliv předáváte parametry pro calibredb, který mají mezery, uzavřete parametry uvozovkami. Například: „/some path/with spaces

--indexing-speed

Rychlost indexování. Použijte rychlé pro rychlé indexování využívající všechny zdroje vašeho počítače a pomalé pro indexování méně náročné na zdroje. Pamatujte, že rychlost se po každém vyvolání obnoví na pomalou.

--wait-for-completion

Čekat, dokud nejsou všechny knihy indexovány, pravidelně zobrazovat průběh indexování

fts_search

calibredb fts_search [volby] vyhledávací výraz

Provede fulltextové vyhledávání v celé knihovně nebo její podmnožině.

Kdykoliv předáváte parametry pro calibredb, který mají mezery, uzavřete parametry uvozovkami. Například: „/some path/with spaces

--do-not-match-on-related-words

Porovnávat pouze přesná slova, nikoliv související slova. Takže oprava se nebude shodovat s opravou.

--include-snippets

Zahrnout úryvky textu obklopující každou shodu. Pamatujte, že to výrazně zpomaluje vyhledávání.

--indexing-threshold

Kolik v procentech z knihovny musí být indexováno, než je povoleno vyhledávání. Výchozí nastavení je 90

--match-end-marker

Značka použitá k označení konce shodného slova uvnitř úryvku

--match-start-marker

Značka použitá k označení začátku shodného slova uvnitř úryvku

--output-format

Formát, ve kterém vypisovat výsledky vyhledávání. Buď „text“ pro prostý text, nebo „json“ pro výstup JSON.

--restrict-to

Omezit vyhledávané knihy buď pomocí vyhledávacího výrazu, nebo identifikátorů. Například: ids:1,2,3 pro omezení podle identifikátorů nebo search:tag:foo pro omezení na knihy se značkou foo.

© Copyright Kovid Goyal.
Naposledy aktualizováno kvě 03, 2024.

Navigace

- moduly
- další |
- předchozí |
- Spustit »
- Rozhraní příkazového řádku »
- ebook-convert

ebook-convert

ebook-convert input_file output_file [options]

Převeďte e-knihu z jednoho formátu do jiného.

input_file je vstup a output_file je výstup. Oba musí být zadány jako první dva parametry příkazu.

Výstupní formát e-knihy je odhadnut z příponou output_file. output_file může být také speciálního formátu .EXT, kde EXT je přípona výstupního souboru. V tomto případě je název výstupního souboru odvozen od názvu vstupního souboru. Pamatujte, že názvy souborů nesmí začínat pomlčkou. A konečně, pokud output_file nemá žádnou příponu, pak je s ním zacházeno jako se složkou a do této složky je zapsána „otevřená e-kniha“ (OEB) sestávající ze souborů HTML. Tyto soubory jsou soubory, které by obyčejně byly předány výstupnímu modulu.

Po zadání vstupního a výstupního souboru můžete přizpůsobit převod zadáním různých voleb. Dostupné volby závisí na typech vstupních a výstupních souborů. Pro získání nápovědy k nim zadejte vstupní a výstupní soubor a použijte volbu -h.

Úplnou dokumentaci systému převodu najdete na Převod e-knihy

Kdykoliv předáváte parametry pro ebook-convert, který mají mezery, uzavřete parametry uvozovkami. Například: „/some path/with spaces

Volby a výchozí hodnoty pro změnu voleb závisí na vstupním i výstupním formátu, takže byste je měli vždy zkontrolovat pomocí:

ebook-convert myfile.input_format myfile.output_format -h

Níže jsou volby, které jsou společné pro všechny převody, následované volbami konkrétními pro každý vstupní a výstupní formát.

- VOLBY VSTUPU

- VOLBY VÝSTUPU

- VZHLED

- HEURISTICKÉ ZPRACOVÁNÍ

- NAJÍT A NAHRADIT

- ROZPOZNÁVÁNÍ STRUKTURY

- OBSAH

- METADATA

- LADĚNÍ

--help, -h

zobrazit tuto zprávu nápovědy a ukončit

--input-profile

Zadejte vstupní profil. Vstupní profil dává převodnímu systému informaci, jak interpretovat různé informace ve vstupním dokumentu. Například délky závislé na rozlišení (t.j. délky v pixelech). Možnosti jsou: cybookg3, cybook_opus, default, hanlinv3, hanlinv5, illiad, irexdr1000, irexdr800, kindle, msreader, mobipocket, nook, sony, sony300, sony900

--list-recipes

Seznam vestavěných názvů předpisů. Z vestavěného předpisu můžete vytvořit e-knihu takto: ebook-convert "Nazev Predpisu.recipe" output.epub

--output-profile

Zadejte výstupní profil. Výstupní profil říká převodnímu systému, jak optimalizovat vytvořený dokument pro zadané zařízení. V některých případech může být výstupní profil použit pro optimalizaci výstupu pro konkrétní zařízení, ale je to zřídka potřeba. Možnosti jsou:cybookg3, cybook_opus, default, hanlinv3, hanlinv5, illiad, ipad, ipad3, irexdr1000, irexdr800, jetbook5, kindle, kindle_dx, kindle_fire, kindle_oasis, kindle_pw, kindle_pw3, kindle_scribe, kindle_voyage, kobo, msreader, mobipocket, nook, nook_color, nook_hd_plus, pocketbook_inkpad3, pocketbook_lux, pocketbook_hd, pocketbook_900, pocketbook_pro_912, galaxy, sony, sony300, sony900, sony-landscape, sonyt3, tablet, generic_eink, generic_eink_hd, generic_eink_large

--version

zobrazit číslo verze programu a ukončit

VOLBY VSTUPU

Volby pro ovládání zpracování vstupního souboru mobi

--input-encoding

Zadejte kódování znaků vstupního dokumentu. Pokud je tato volba nastavena, přepíše jakékoliv kódování deklarované samotným dokumentem. Zvláště užitečné pro dokumenty, které nedeklarují kódování nebo mají nesprávně deklarované kódování.

VOLBY VÝSTUPU

Volby pro ovládání zpracování výstupního souboru epub

--dont-split-on-page-breaks

Vypnout rozdělování na koncích stránek. Vstupní soubory jsou obyčejně automaticky rozděleny na každém konci stránky na dva soubory. Tak vznikne e-kniha, která může být analyzována rychleji a za použití méně prostředků. Rozdělování je však pomalé, a pokud váš zdrojový soubor obsahuje velmi velké množství konců stránek, měli byste rozdělování na koncích stránek vypnout.

--epub-flatten

Tato volba je nutná, pouze pokud máte v úmyslu použít EPUB v FBReaderJ. Urovná to souborový systém uvnitř EPUB, všechny soubory dá na nejvyšší úroveň.

--epub-inline-toc

Vložit vložený obsah, který se objeví jako část hlavního obsahu knihy.

--epub-max-image-size

Maximální velikost obrázku (šířka x výška). Hodnota none znamená použití velikosti obrazovky z výstupního profilu. Hodnota profile znamená, že není zadána žádná maximální velikost. Například hodnota 100x200 způsobí, že se velikost všech obrázků změní tak, aby jejich šířka nebyla větší než 100 pixelů a jejich výška nebyla větší než 200 pixelů. Pamatujte, že to ovlivňuje pouze velikost samotných souborů obrázků. Každý obrázek může být vykreslen v jiné velikosti v závislosti na stylování, které je na něj v dokumentu použito.

--epub-toc-at-end

Dát vložený obsah na konec knihy místo na začátek.

--epub-version

Verze souboru EPUB, který chcete vygenerovat. EPUB 2 je nejkompatibilnější, EPUB 3 použijte pouze tehdy, pokud víte, že ho skutečně potřebujete.

--extract-to

Rozbalit obsah vygenerovaného souboru EPUB do zadané složky. Obsah složky je nejdříve odstraněn, takže buďte opatrní.

--flow-size

Rozdělit všechny soubory HTML větší než tato velikost (v KB). Je to nezbytné, protože většina čteček EPUB neumí zpracovat velké soubory. Výchozí hodnota 260KB je velikost požadovaná pro Adobe Digital Editions. Nastavte na 0 pro zakázání rozdělování na základě velikosti.

--no-default-epub-cover

Pokud vstupní soubor nemá žádnou obálku a vy jste žádnou nezadali, je obyčejně vygenerována výchozí obálka s názvem, autorem atd. Tato volba zakáže generování této obálky.

--no-svg-cover

Nepoužívat SVG pro obálku knihy. Tuto volbu použijte, pokud vaše soubory EPUB budou používány v zařízení, které nepodporuje SVG, jako je iPhone nebo JetBook Lite. Bez této volby budou taková zařízení zobrazovat obálku jako prázdnou stránku.

--preserve-cover-aspect-ratio

Při použití obálky SVG tato volba způsobí změnu velikosti obálky, aby pokryla dostupné oblasti obrazovky, ale přesto zachová její poměr stran (poměr šířky k výšce). To znamená, že mohou být vidět bílé okraje po stranách nebo v horní a dolní části obrázku, ale obrázek nebude nikdy zdeformovaný. Bez této volby může být obrázek mírně deformovaný, ale nebudou vidět žádné okraje.

--pretty-print

Pokud je zadáno, výstupní modul se pokusí vytvořit výstup, který je co nejvíce čitelný pro člověka. Nemusí to mít žádný vliv na některé výstupní moduly.

--toc-title

Název pro vygenerovaný vložený obsah.

VZHLED

Volby pro ovládání vzheldu a chování výstupu

--asciiize

Překódovat znaky Unicode do ASCII. Používejte opatrně, protože to nahradí znaky Unicode za ASCII. Například to nahradí "Pelé" za "Pele". Také pamatujte, že v případech, kdy je více zastoupení znaku (například znaky společné pro čínštinu a japonštinu), bude použito zastoupení podle aktuálního jazyku rozhraní Calibre.

--base-font-size

Základní velikost písma v bodech. Všechny velikosti písma ve vytvořené knize budou mít změněnou velikost na základě této hodnoty. Výběrem větší velikosti můžete písmo ve výstupu zvětšit a naopak. Ve výchozím nastavení, když je hodnota nula, je základní velikost písma zvolena na základě vámi zvoleného výstupního profilu.

--change-justification

Změnit zarovnání textu. Hodnota "doleva" převede všechen text zarovnaný do bloku ve zdroji na text zarovnaný vlevo (tj. nezarovnaný). Hodnota "do bloku" převede všechen nezarovnaný text na zarovnaný. Hodnota "původní" (výchozí) nezmění zarovnání ve zdrojovém souboru. Pamatujte, že pouze některé výstupní formáty podporují zarovnání.

--disable-font-rescaling

Zakázat všechny úpravy měřítka velikostí písma.

--embed-all-fonts

Vložit každé písmo, na které se odkazuje ve vstupním dokumentu, ale ještě není vloženo. Vyhledá písma ve vašem systému, a pokud je nalezen, budou vložena. Vkládání bude fungovat, pouze pokud formát, do kterého převádíte, podporuje vložená písma, jako jsou EPUB, AZW3, DOCX nebo PDF. Ujistěte se, že máte správnou licenci pro vkládání písem použitých v tomto dokumentu.

--embed-font-family

Vložit zadanou rodinu písem do knihy. To určuje "základní" písmo použité pro knihu. Pokud vstupní dokument určuje svá vlastní písma, mohou přepsat toto základní písmo. Můžete použít volbu filtrování stylu informací pro odebrání písem ze vstupního dokumentu. Pamatujte, že vkládání písem funguje pouze u některých výstupních formátů, hlavně EPUB a AZW3 a DOCX.

--expand-css

Ve výchozím nastavení použije calibre zjednodušený formulář pro různé vlastnosti CSS, jako jsou okraje, odsazení, rámeček atd. Tato volba způsobí, že se místo toho použije rozšířený formulář. Pamatujte, že CSS je vždy rozšířené při generování souborů EPUB s výstupním profilem nastaveným na jeden z profilů Nook, protože Nook neovládá zjednodušené CSS.

--extra-css

Buď cesta k šabloně stylů CSS nebo nezpracované CSS. Toto CSS bude připojeno k pravidlům stylu ze zdrojového souboru, takže může být použito k přepsání těchto pravidel.

--filter-css

Čárkami oddělený seznam vlastností CSS, které budou odebrány ze všech pravidel stylů CSS. To je užitečné, pokud přítomnost některých informací o stylu zabraňuje přepsání ve vašem zařízení. Například: font-family,color,margin-left,margin-right

--font-size-mapping

Mapování z názvů písem CSS na velikosti písem v bodech. Příklad nastavení je 12,12,14,16,18,20,22,24. Toto jsou mapování pro velikosti od xx-small do xx-large, s poslední velikostí pro velice velká písma. Algoritmus úpravy měřítka písma používá tyto velikosti pro chytrou úpravu měřítka písem. Výchozí je použití mapování na základě vámi zvoleného výstupního profilu.

--insert-blank-line

Vložit prázdný řádek mezi odstavce. Nebude fungovat, pokud zdrojový soubor nepoužívá odstavce (značky <p> nebo <div>).

--insert-blank-line-size

Nastavit výšku vložených prázdných řádků (v em). Výška řádků mezi odstavci bude dvojnásobek zde nastavené hodnoty.

--keep-ligatures

Zachovat ligatury přítomné ve vstupním dokumentu. Ligatura je speciální vykreslení páru znaků, jako ff, fi, fl atd. Většina čteček nemá podporu pro ligatury ve svých základních písmech, takže je nepravděpodobné, že je správně vykreslí. Ve výchozím nastavení Calibre změní ligaturu na odpovídající pár normálních znaků. Tato volba je namísto toho ponechá.

--line-height

Výška řádku v bodech. Určuje mezery mezi za sebou následujícími řádky textu. Platí pouze pro prvky, které nedefinují svou vlastní výšku řádku. Ve většině případů je užitečnější volba minimální výška řádku. Ve výchozím nastavení se neprovádí žádné zpracování výšky řádku.

--linearize-tables

Některé chybně navržené dokumenty používají tabulky pro ovládání rozložení textu na stránce. Při převodu mají tyto dokumenty často text, který vybíhá mimo stránku a jiné artefakty. Tato volba extrahuje obsah z tabulek a zobrazí ho v linární tvaru.

--margin-bottom

Nastavit dolní okraj v bodech. Výchozí je 5.0. Nastavení na hodnotu menší než nula způsobí, že se nenastaví žádné okraje (bude zachováno nastavení okrajů v původním dokumentu). Poznámka: Formáty orientované na stránky, jako je PDF a DOCX, mají vlastní nastavení okrajů, které má přednost.

--margin-left

Nastavit levý okraj v bodech. Výchozí je 5.0. Nastavení na hodnotu menší než nula způsobí, že se nenastaví žádné okraje (bude zachováno nastavení okrajů v původním dokumentu). Poznámka: Formáty orientované na stránky, jako je PDF a DOCX, mají vlastní nastavení okrajů, které má přednost.

--margin-right

Nastavit pravý okraj v bodech. Výchozí je 5.0. Nastavení na hodnotu menší než nula způsobí, že se nenastaví žádné okraje (bude zachováno nastavení okrajů v původním dokumentu). Poznámka: Formáty orientované na stránky, jako je PDF a DOCX, mají vlastní nastavení okrajů, které má přednost.

--margin-top

Nastavit horní okraj v bodech. Výchozí je 5.0. Nastavení na hodnotu menší než nula způsobí, že se nenastaví žádné okraje (bude zachováno nastavení okrajů v původním dokumentu). Poznámka: Formáty orientované na stránky, jako je PDF a DOCX, mají vlastní nastavení okrajů, které má přednost.

--minimum-line-height

Minimální výška řádku jako procento vypočítané velikosti písma prvku. Calibre zajistí, že každý prvek bude mít výšku řádku alespoň podle tohoto nastavení bez ohledu na to, co určuje vstupní dokument. Nastavte na nulu pro zakázání. Výchozí je 120 %. Použijte toto nastavení přednostně pro přímé určení výšky řádku, pokud nevíte, co děláte. Například můžete dosáhnout textu s dvojitým řádkováním nastavením na 240.

--remove-paragraph-spacing

Odebrat mezery mezi odstavci. Také nastaví odsazení odstavce na 1,5 em. Odebrání mezer nebude fungovat, pokud zdrojový soubor nepoužívá odstavce (značky <p> nebo <div>).

--remove-paragraph-spacing-indent-size

Když Calibre odebírá prázdné řádky mezi odstavci, automaticky nastaví odsazení odstavce, aby bylo zajištěno snadné rozlišení odstavců. Tato volba určuje šířku tohoto odsazení (v em). Pokud nastavíte zápornou hodnotu, pak bude použito odsazení zadané ve vstupním dokumentu, to znamená, že Calibre nezmění odsazení.

--smarten-punctuation

Převést jednoduché uvozovky, pomlčky a trojtečky na jejich typograficky správnéh ekvivalenty. Pro podrobnosti přejděte na https://daringfireball.net/projects/smartypants.

--subset-embedded-fonts

Podmnožina všech vložených písem. Každé vložené písmo je zmenšeno, aby obsahovalo pouze glyfy použité v tomto dokumentu. To zmenšuje velikost souborů písem. Užitečné, pokud vkládáte obzvlášť velké písmo se spoustou nevyužitých glyfů.

--transform-css-rules

Cesta k souboru obsahujícímu pravidla pro převod stylů CSS v této knize. Nejsnadnější způsob, jak vytvořit takový soubor, je použít průvodce pro vytváření pravidel v grafickém rozhraní Calibre. Přístup k němu získáte v oddílu "Vzhled a chování->Převod stylů" v dialogovém okně převodu. Jakmile vytvoříte pravidla, můžete použít tlačítko Exportovat pro jejich uložení do souboru.

--transform-html-rules

Cesta k souboru obsahujícímu pravidla pro převod HTML v této knize. Nejsnadnější způsob, jak vytvořit takový soubor, je použít průvodce pro vytváření pravidel v grafickém rozhraní Calibre. Přístup k němu získáte v oddílu „Vzhled a chování->Převod HTML“ v dialogovém okně převodu. Jakmile vytvoříte pravidla, můžete použít tlačítko Exportovat pro jejich uložení do souboru.

--unsmarten-punctuation

Převést ozdobné uvozovky, pomlčky a tři tečky na jejich prosté ekvivalenty.

HEURISTICKÉ ZPRACOVÁNÍ

Změnit text a strukturu dokumentu pomocí běžných vzorů. Ve výchozím nastavení zakázáno. Použijte –enable-heuristics pro povolení. Jednotlivé akce mohou být zakázány pomocí voleb –disable-*.

--disable-dehyphenate

Analyzovat rozdělená slova v celém dokumentu. Samotný dokument je použitý jako slovník pro určení, zda by měly být pomlčky zachovány nebo odebrány.

--disable-delete-blank-paragraphs

Odebrat z dokumentu prázdné odstavce, pokud se vyskytují mezi všemi ostatními odstavci

--disable-fix-indents

Změnit odsazení vytvořená z více entit pevné mezery na odsazení CSS.

--disable-format-scene-breaks

Vlevo zarovnané značky přerušení příběhu jsou zarovnané na střed. Nahradit měkká přerušení příběhu, která používají více prázdných řádků, vodorovnými čárami.

--disable-italicize-common-cases

Hledat běžná slova a vzory, které označují kurzívu a převést je na kurzívu.

--disable-markup-chapter-headings

Rozpoznat neformátované nadpisy a podnadpisy kapitol. Změnit je na značky h2 a h3. Toto nastavení nevytvoří obsah, ale může být použito společně s rozpoznáním struktury pro jeho vytvoření.

--disable-renumber-headings

Hledá výskyty po sobě následujících značek <h1> nebo <h2>. Značky jsou přečíslovány, aby se zabránilo rozdělení uprostřed nadpisů kapitol.

--disable-unwrap-lines

Zrušit zalomení řádků pomocí interpunkce a jiných vodítek formátování.

--enable-heuristics

Povolit heuristické zpracování. Tato volba musí být nastavena, aby se provedlo jakékoliv heuristické zpracování.

--html-unwrap-factor

Měřítko použité k určení délky, při které by mělo být řádku zrušeno zalomení. Platné hodnoty jsou desetinná čísla mezi 0 a 1. Výchozí je 0,4, těsně pod mediánem délky řádku. Pokud by pouze pár řádků v dokumentu vyžadovalo zrušení zalomení, měla by být tato hodnota zmenšena.

--replace-scene-breaks

Nahradit přerušení příběhu zadaným textem. Ve výchozím nastavení je použit text ze vstupního dokumentu.

NAJÍT A NAHRADIT

Změnit text a strukturu dokumentu pomocí vzorů definovaných uživatelem.

--search-replace

Cesta k souboru obsahující regulární výrazy hledávání a nahrazování. Soubor musí obsahovat střídavě řádky regulárního výrazu následované vzorem nahrazení (což může být prázdný řádek). Regulární výraz musí mít syntaxi regulárního výrazu Pythonu a soubor musí mít kódování UTF-8.

--sr1-replace

Nahrazení, které má nahradit text nalezený pomocí sr1-search.

--sr1-search

Hledaný vzor (regulární výraz), který má být nahrazen pomocí sr1-replace.

--sr2-replace

Nahrazení, které má nahradit text nalezený pomocí sr2-search.

--sr2-search

Hledaný vzor (regulární výraz), který má být nahrazen pomocí sr2-replace.

--sr3-replace

Nahrazení, které má nahradit text nalezený pomocí sr2-search.

--sr3-search

Hledaný vzor (regulární výraz), který má být nahrazen pomocí sr3-replace.

ROZPOZNÁVÁNÍ STRUKTURY

Nastavení automatického rozpoznávání struktury dokumentu.

--chapter

Výraz XPath pro rozpoznání nadpisů kapitol. Výchozí nastavení je považovat tagy <h1> nebo <h2> obsahující slova "chapter" (kapitola), "book" (kniha), "section" (oddíl), "prologue" (prolog), "epilogue" (epilog) nebo "part" (část) za nadpisy kapitol, stejně jako jakékoliv tagy s class="chapter" (třída kapitola). Použitý výraz musí vyhodnotit seznam prvků. Pro zakázání rozpoznávání kapitol použijte výraz "/". Viz XPath Tutorial v ráži uživatelské příručce pro další pomoc pro použití této funkce.

--chapter-mark

Zadejte, jak označit rozpoznané kapitoly. Hodnota "konecstránky" vloží před kapitoly konce stránek. Hodnota "čára" vloží před kapitoly čáru. Hodnota "žádné" zakáže označování kapitol a hodnota "obojí" použije pro označení kapitol jak konce stránek, tak čáry.

--disable-remove-fake-margins

Některé dokumenty zadávají okraje stránek zadáním levého a pravého okraje každému jednotlivému odstavci. Calibre se pokusí rozpoznat a odebrat tyto okraje. Někdy to může způsobit odebírání okrajů, které neměly být odebrány. V tomto případě můžete zakázat odebírání.

--insert-metadata

Vložit metadata knihy na její začátek. Užitečné v případě, že vaše čtečka e-knih nepodporuje zobrazení nebo vyhledávání metadat přímo.

--page-breaks-before

Výraz XPath. Konce stránek jsou vloženy před zadané prvky. Pro zakázání použijte výraz: /

--prefer-metadata-cover

Použít obálku rozpoznanou ve zdrojovém souboru přednostně před zadanou obálkou.

--remove-first-image

Odebrat první obrázek ze vstupní e-knihy. Užitečné, pokud má vstupní dokument obrázek obálky, který není identifikován jako obálka. Pokud v tomto případě nastavíte obálku v Calibre, výstupní dokument bude mít dva obrázky obálky, pokud nezadáte tuto volbu.

--start-reading-at

Výraz XPath pro rozpoznání místa v dokumentu, na kterém začít číst. Některé programy pro čtení e-knih (hlavně Kindle) používají toto místo jako pozici, na které se otevře kniha. Další nápovědu k používání této funkce naleznete v kurzu XPath v uživatelské příručce Calibre.

OBSAH

Nastavení automatického generování obsahu. Pokud má zdrojový soubor obsah, bude ve výchozím nastavení použit přednostně před automaticky generovaným.

--duplicate-links-in-toc

Při vytváření obsah z odkazů ve vstupním dokumentu povolit duplicitní položky, tj. povolit více než jednu položku se stejným textem, za předpokladu že směřují na rozdílná umístění.

--level1-toc

Výraz XPath, který určuje všechny značky, které by měly být přidány do obsahu na první úroveň. Pokud je toto zadáno, má to přednost před jinými formami automatického rozpoznávání. Příklady naleznete v kurzu XPath v uživatelské příručce Calibre.

--level2-toc

Výraz XPath, který určuje všechny značky, které by měly být přidány do obsahu na druhou úroveň. Každá položka je přidána pod předchozí položku první úrovně. Příklady naleznete v kurzu XPath v uživatelské příručce Calibre.

--level3-toc

Výraz XPath, který určuje všechny značky, které by měly být přidány do obsahu na třetí úroveň. Každá položka je přidána pod předchozí položku druhé úrovně. Příklady naleznete v kurzu XPath v uživatelské příručce Calibre.

--max-toc-links

Maximální počet odkazů vložených do obsahu. Nastavte 0 pro zakázání. Výchozí je: 50. Odkazy jsou přidány do obsahu, pouze pokud bylo rozpoznáno méně kapitol, než je mezní hodnota.

--no-chapters-in-toc

Nepřidávat automaticky rozpoznané kapitoly do obsahu.

--toc-filter

Odebrat z obsahu položky, jejichž názvy odpovídají zadanému regulárnímu výrazu. Odpovídající položky a všechny jejich podřízené položky budou odebrány.

--toc-threshold

Pokud je rozpoznáno méně než tento počet kapitol, budou odkazy přidány do obsahu. Výchozí: 6

--use-auto-toc

Pokud má již zdrojový soubor obsah, je obyčejně použit přednostně před automaticky generovaným. Pomocí této volby je vždy použit ten automaticky generovaný.

METADATA

Volby pro nastavení metadat ve výstupu

--author-sort

Řetězec použitý při řazení podle autora.

--authors

Nastavit autory. Více autorů by mělo být odděleno znaky "&".

--book-producer

Nastavit nakladatele knihy.

--comments

Nastavit popis e-knihy.

--cover

Nastavit obálku na zadaný soubor nebo URL

--isbn

Nastavit ISBN knihy.

--language

Nastavit jazyk.

--pubdate

Nastavit datum vydání (předpokládá se, že je v místním časovém pásmu, pokud není časové pásmo výslovně zadáno)

--publisher

Nastavit vydavatele e-knihy.

--rating

Nastavit hodnocení. Mělo by to být číslo mezi 1 a 5.

--read-metadata-from-opf, --from-opf, -m

Číst metadata ze zadaného souboru OPF. Metadata načtená z tohoto souboru přepíší jakákoliv metadata ve zdrojovém souboru.

--series

Nastavit sérii, do které tato e-kniha patří.

--series-index

Nastavit pořadí knihy v této sérii.

--tags

Nastavit štítky pro knihu. Měl by to být seznam oddělený čárkami.

--timestamp

Nastavit časové razítko knihy (nikde se již nepoužívá)

--title

Nastavit název.

--title-sort

Verze názvu použitá při řazení.

LADĚNÍ

Volby pro pomoc s laděním převodu

--debug-pipeline, -d

Uložit výstup z rozdílných fází převodního řetězce do zadané složky. Užitečné, pokud si nejste jisti, v jaké fázi zpracování převodu se objevuje chyba.

--verbose, -v

Úroveň podrobnosti. Zadejte vícekrát pro větší podrobnost. Zadání dvakrát bude mít za následek úplnou podrobnost, jednou střední podrobnost a nulakrát nejmenší podrobnost.

© Copyright Kovid Goyal.
Naposledy aktualizováno kvě 03, 2024.

Navigace

- moduly
- další |
- předchozí |
- Spustit »
- Rozhraní příkazového řádku »
- ebook-edit

ebook-edit

ebook-edit [opts] [path_to_ebook] [name_of_file_inside_book ...]

Spustit nástroj Calibre Upravit knihu. Volitelně můžete také zadat názvy souborů uvnitř knihy, které budou automaticky otevřeny pro úpravu.

Kdykoliv předáváte parametry pro ebook-edit, který mají mezery, uzavřete parametry uvozovkami. Například: „/some path/with spaces

[volby]

--detach

Odpojit od řídícího terminálu, pokud nějaký je (pouze pro Linux)

--help, -h

zobrazit tuto zprávu nápovědy a ukončit

--select-text

Text, který v knize vybrat při otevírání pro úpravu

--version

zobrazit číslo verze programu a ukončit

© Copyright Kovid Goyal.
Naposledy aktualizováno kvě 03, 2024.

Navigace

- moduly
- další |
- předchozí |
- Spustit »
- Rozhraní příkazového řádku »
- ebook-meta

ebook-meta

ebook-meta ebook_file [options]

Číst/Zapisovat metadata z/do souborů e-knih.

Podporované formáty pro čtení metadat: azw, azw1, azw3, azw4, cb7, cbc, cbr, cbz, chm, docx, epub, fb2, fbz, html, htmlz, imp, lit, lrf, lrx, mobi, odt, oebzip, opf, pdb, pdf, pml, pmlz, pobi, prc, rar, rb, rtf, snb, tpz, txt, txtz, updb, zip

Podporované formáty pro zápis metadat: azw, azw1, azw3, azw4, docx, epub, fb2, fbz, htmlz, lrf, mobi, odt, pdb, pdf, prc, rtf, tpz, txtz

Různé typy souborů podporují různé druhy metadat. Pokud se pokusíte nastavit nějaká metadata k typu souboru, který je nepodporuje, metadata budou bez upozornění ignorována.

Kdykoliv předáváte parametry pro ebook-meta, který mají mezery, uzavřete parametry uvozovkami. Například: „/some path/with spaces

[volby]

--author-sort

Řetězec použitý při řazení podle autora. Pokud není zadán a je zadán autor, bude automaticky generován z autora.

--authors, -a

Nastavit autory. Více autorů by mělo být odděleno znakem &. Jména autorů by měla být v pořadí křestní jméno, příjmení.

--book-producer, -k

Nastavit nakladatele knihy.

--category

Nastavit kategorii knihy.

--comments, -c

Nastavit popis e-knihy.

--cover

Nastavit obálku zadaného souboru.

--date, -d

Nastavit datum vydání.

--from-opf

Načíst metadata z vybraného souboru OPF a použít je k nastavení metadat v e-knize. Metadata uvedená v příkazové řádce přepíšou metadata načtená ze souboru OPF.

--get-cover

Získat obálku z e-knihy a uložit ji jako zadaný soubor.

--help, -h

zobrazit tuto zprávu nápovědy a ukončit

--identifier

Nastavit identifikátory pro knihu, může být zadáno vícekrát. Například: --identifier uri:https://acme.com --identifier isbn:12345. Pro odebrání identifikátoru zadejte prázdnou hodnotu, --identifier isbn:. Pamatujte, že identifikátor označený jako identifikátor balíčku nemůže být u souborů EPUB odebrán.

--index, -i

Nastavit pořadí knihy v této sérii.

--isbn

Nastavit ISBN knihy.

--language, -l

Nastavit jazyk.

--lrf-bookid

Nastavit BookID v souborech LRF

--publisher, -p

Nastavit vydavatele e-knihy.

--rating, -r

Nastavit hodnocení. Mělo by to být číslo mezi 1 a 5.

--series, -s

Nastavit sérii, do které tato e-kniha patří.

--tags

Nastavit štítky pro knihu. Měl by to být seznam oddělený čárkami.

--title, -t

Nastavit název.

--title-sort

Verze názvu použitá pro řazení. Pokud není zadán a je zadán název, bude automaticky generován z názvu.

--to-opf

Zadejte název souboru OPF. Metadata budou zapsána do souboru OPF.

--version

zobrazit číslo verze programu a ukončit

© Copyright Kovid Goyal.
Naposledy aktualizováno kvě 03, 2024.

Navigace

- moduly
- další |
- předchozí |
- Spustit »
- Rozhraní příkazového řádku »
- ebook-polish

ebook-polish

ebook-polish [options] input_file [output_file]

Vylepšení knih je celé o dotažení vašich pečlivě vytvářených e-knih k dokonalosti.

Vylepšení se pokouší minimalizovat změny vnitřního kódu vaší e-knihy.
Na rozdíl od převodu nedělá následující: neuhlazuje CSS, nepřejmenovává soubory, nemění velikosti písma, neupravuje okraje atd. Každá akce provádí pouze minimální sadu změn potřebnou pro požadovaný efekt.

Tento nástroj byste měli použít jako poslední krok při vytváření e-knihy.

Pamatujte, že vylepšení funguje pouze u souborů ve formátech AZW3 nebo EPUB.

Kdykoliv předáváte parametry pro ebook-polish, který mají mezery, uzavřete parametry uvozovkami. Například: „/some path/with spaces

[volby]

--add-soft-hyphens, -H

Přidat pohyblivé spojovníky do všech slov v knize. To umožní knihu lépe vykreslit, když je text zarovnán do bloku, ve čtečkách, které nepodporují dělení slov.

--compress-images, -i

Bezztrátově komprimovat obrázky v knize, aby se zmenšila velikost souboru bez ovlivnění kvality obrázku.

--cover, -c

Cesta k obrázku obálky. Mění obálku zadanou v e-knize. Pokud není přítomna žádná obálka, nebo pokud není obálka správně identifikována, vloží novou obálku.

--embed-fonts, -e

Vložit všechna písma, na která je odkazováno v dokumentu a nejsou již vložena. To vyhledá písma ve vašem počítači, a pokud jsou nalezena, budou vložena do dokumentu. Ujistěte se, že máte řádnou licenci pro vkládání písem použitých v tomto dokumentu.

--help, -h

zobrazit tuto zprávu nápovědy a ukončit

--jacket, -j

Vložit na začátek knihy stránku "přebalu knihy", která obsahuje všechna metadata knihy, jako jsou název, štítky, autoři, série, komentáře atd. Jakýkoliv předchozí přebal knihy bude nahrazen.

--opf, -o

Cesta k souboru OPF. Metadata v knize jsou aktualizována ze souboru OPF.

--remove-jacket

Odebrat dříve vloženou stránku přebalu knihy.

--remove-soft-hyphens

Odebrat pohyblivé spojovníky z celého textu v knize.

--remove-unused-css, -u

Odebrat všechna nepoužívaná pravidla CSS ze šablon stylů a značek <style>. Některé knihy vytvořené pomocí výrobních šablon mohou mít velký počet dodatečných pravidel CSS, která neodpovídají skutečnému obsahu. Tato dodatečná pravidla mohou zpomalovat čtečky, které je musí všechny analyzovat.

--smarten-punctuation, -p

Převést pomlčky, tři tečky, uvozovky, vícenásobné pomlčky atd. v prostém textu na jejich typograficky správné ekvivalenty. Pamatujte, že algoritmus může někdy generovat nesprávné výsledky, obzvláště pokud se to týká jednoduchých uvozovek na začátku zkrácených tvarů.

--subset-fonts, -f

Částečné vkládání písem znamená zmenšení vloženého písma, aby obsahovalo pouze znaky použité z tohoto písma v knize. To značně zmenšuje velikost souborů písem (poloviční velikosti souborů písem jsou běžné). Například pokud kniha používá konkrétní písmo pro nadpisy, pak částečné vkládání zmenší toto písmo, aby obsahovalo pouze znaky přítomné v nadpisech v knize. Nebo pokud kniha vloží verze písma pro tučné písmo a kurzívu, ale texty tučným písmem a kurzívou jsou poměrně vzácné nebo úplně chybí, pak může být tučné písmo a kurzíva buď zmenšeno, nebo úplně odebráno. Jediná nevýhoda částečného vkládání písem je ta, že pokud se později rozhodnete přidat další text do svých knih, nově přidaný text nemusí být pokryt podmnožinou písma.

--upgrade-book, -U

Upgrade vnitřní struktury knihy, pokud je to možné. Například upgraduje knihy EPUB 2 na knihy EPUB 3.

--verbose

Vytváří podrobnější výstup, užitečné pro ladění.

--version

zobrazit číslo verze programu a ukončit

© Copyright Kovid Goyal.
Naposledy aktualizováno kvě 03, 2024.

Navigace

- moduly
- další |
- předchozí |
- Spustit »
- Rozhraní příkazového řádku »
- ebook-viewer

ebook-viewer

ebook-viewer [options] file

Zobrazit e-knihu.

Kdykoliv předáváte parametry pro ebook-viewer, který mají mezery, uzavřete parametry uvozovkami. Například: „/some path/with spaces

[volby]

--continue

Pokračovat ve čtení naposledy otevřené knihy

--detach

Odpojit od řídícího terminálu, pokud nějaký je (pouze pro Linux)

--force-reload

Vynutit nové načtení všech otevřených knih

--full-screen, --fullscreen, -f

Pokud je zadáno, okno Prohlížeče e-knih se pokusí při spuštění otevřít na celou obrazovku.

--help, -h

zobrazit tuto zprávu nápovědy a ukončit

--new-instance

Otevřít nové okno prohlížeče, i když je nastavena možnost použít pouze jedno okno prohlížeče.

--open-at

Pozice, na které otevřít zadanou knihu. Pozice je umístění nebo pozice, které můžete získat pomocí akce Přejít na->Umístění v ovládacích prvcích prohlížeče. Případně můžete použít formu toc:něco, což se otevře na umístění první položky obsahu, která obsahuje řetězec "něco". Forma toc-href:něco bude odpovídat href (interní cíl odkazu) uzlům obsahu. Shoda je přesná. Pokud chcete porovnat podřetězec, použijte formu toc-href-contains:něco. Forma ref:něco použije odkazy Režimu odkazu. Forma regex:něco bude po otevření knihy hledat regulární výraz něco.

--raise-window

Pokud je zadáno, okno Prohlížeče e-knih se pokusí při spuštění zobrazit v popředí.

--version

zobrazit číslo verze programu a ukončit

© Copyright Kovid Goyal.
Naposledy aktualizováno kvě 03, 2024.

Navigace

- moduly
- další |
- předchozí |
- Spustit »
- Rozhraní příkazového řádku »
- fetch-ebook-metadata

fetch-ebook-metadata

fetch-ebook-metadata [options]

Načíst metadata knihy z online zdrojů. Musíte zadat alespoň název, autory nebo ISBN.

Kdykoliv předáváte parametry pro fetch-ebook-metadata, který mají mezery, uzavřete parametry uvozovkami. Například: „/some path/with spaces

[volby]

--allowed-plugin, -p

Zadejte název modulu pro stahování metadat, který chcete použít. Ve výchozím nastavení budou použity všechny moduly metadat. Může být zadáno vícekrát pro více modulů. Všechny názvy modulů: Google, Google Images, Amazon.com, Edelweiss, Open Library, Big Book Search

--authors, -a

Autoři knihy

--cover, -c

Zadejte název souboru. Pokud existuje obálka, bude do něj uložena. Bez této volby nebude stažena žádná obálka.

--help, -h

zobrazit tuto zprávu nápovědy a ukončit

--identifier, -I

Identifikátory, jako jsou ASIN nebo ID Goodreads atd. Mohou být zadány vícekrát pro více identifikátorů. Například:--identifier asin:B0082BAJA0

--isbn, -i

ISBN knihy

--opf, -o

Výstup metadat ve formátu OPF namísto textu čitelného pro člověka.

--timeout, -d

Časový limit v sekundách. Výchozí je 30

--title, -t

Název knihy

--verbose, -v

Vypsat protokol do konzole (stderr)

--version

zobrazit číslo verze programu a ukončit

© Copyright Kovid Goyal.
Naposledy aktualizováno kvě 03, 2024.

Navigace

- moduly
- další |
- předchozí |
- Spustit »
- Rozhraní příkazového řádku »
- lrf2lrs

lrf2lrs

lrf2lrs book.lrf

Převést soubor LRF na soubor LRS (XML s kódováním UTF-8)

Kdykoliv předáváte parametry pro lrf2lrs, který mají mezery, uzavřete parametry uvozovkami. Například: „/some path/with spaces

[volby]

--dont-output-resources

Neukládat vložené soubory obrázků a písem na disk

--help, -h

zobrazit tuto zprávu nápovědy a ukončit

--output, -o

Výstupní soubor LRS

--verbose

Více podrobností

--version

zobrazit číslo verze programu a ukončit

© Copyright Kovid Goyal.
Naposledy aktualizováno kvě 03, 2024.

Navigace

- moduly
- další |
- předchozí |
- Spustit »
- Rozhraní příkazového řádku »
- lrfviewer

lrfviewer

lrfviewer [options] book.lrf

Číst e-knihu LRF book.lrf

Kdykoliv předáváte parametry pro lrfviewer, který mají mezery, uzavřete parametry uvozovkami. Například: „/some path/with spaces

[volby]

--disable-hyphenation

Zakázat dělení slov. Mělo by to výrazně zrychlit vykreslování.

--help, -h

zobrazit tuto zprávu nápovědy a ukončit

--profile

Profilovat nástroj pro vykreslování LRF

--verbose

Vypsat více informací o procesu vykreslování

--version

zobrazit číslo verze programu a ukončit

--visual-debug

Zapnout vizuální pomůcky pro ladění modulu vykreslování

--white-background

Ve výchozím nastavení je pozadí skoro bílé, protože je to snadnější pro oči. Použijte tuto volbu, aby bylo pozadí čistě bílé.

© Copyright Kovid Goyal.
Naposledy aktualizováno kvě 03, 2024.

Navigace

- moduly
- další |
- předchozí |
- Spustit »
- Rozhraní příkazového řádku »
- lrs2lrf

lrs2lrf

lrs2lrf [options] file.lrs

Kompilovat soubor LRS na soubor LRF.

Kdykoliv předáváte parametry pro lrs2lrf, který mají mezery, uzavřete parametry uvozovkami. Například: „/some path/with spaces

[volby]

--help, -h

zobrazit tuto zprávu nápovědy a ukončit

--lrs

Převést LRS na LRS, užitečné pro ladění.

--output, -o

Cesta k výstupnímu souboru

--verbose

Podrobné zpracování

--version

zobrazit číslo verze programu a ukončit

© Copyright Kovid Goyal.
Naposledy aktualizováno kvě 03, 2024.

Navigace

- moduly
- další |
- předchozí |
- Spustit »
- Rozhraní příkazového řádku »
- web2disk

web2disk

web2disk URL

Kde URL je například https://google.com

Kdykoliv předáváte parametry pro web2disk, který mají mezery, uzavřete parametry uvozovkami. Například: „/some path/with spaces

[volby]

--base-dir, -d

Základní složka, do které je uložena URL. Výchozí je .

--delay

Minimální interval mezi za sebou následujícími načteními v sekundách. Výchozí je 0 s

--dont-download-stylesheets

Nestahovat šablony stylů CSS.

--encoding

Kódování znaků webových stránek, které se pokoušíte stáhnout. Výchozí je pokusit se odhadnout kódování.

--filter-regexp

Jakýkoliv odkaz, který odpovídá tomuto regulárnímu výrazu, bude ignorován. Tato volba může být zadána vícekrát, v tom případě dokud kterýkoliv regulární výraz odpovídá odkazu, bude ignorován. Pokud je zadáno filtrování regulárních výrazů a porovnání regulárních výrazů, pak filtrování regulárních výrazů je použito první.

--help, -h

zobrazit tuto zprávu nápovědy a ukončit

--match-regexp

Pouze odkazy, které odpovídají tomuto regulárnímu výrazu, budou následovány. Tato volba může být zadána vícekrát, v tom případě bude odkaz následován, dokud odpovídá kterémukoliv regulárnímu výrazu. Ve výchozím nastavení jsou následovány všechny odkazy.

--max-files, -n

Maximální počet stahovaných souborů. Toto se použije pouze na soubory v <a href>štítcích. Výchozí je 9223372036854775807

--max-recursions, -r

Maximální počet úrovní rekurze, tj. hloubka sledovaných odkazů. Výchozí 1

--timeout, -t

Časový limit čekání na odezvu ze serveru v sekundách. Výchozí: 10.0 s

--verbose

Zobrazit podrobné výstupní informace. Užitečné pro ladění

--version

zobrazit číslo verze programu a ukončit

© Copyright Kovid Goyal.
Naposledy aktualizováno kvě 03, 2024.

Navigace

- moduly
- další |
- předchozí |
- Spustit »
- Nastavení vývojového prostředí Calibre

Nastavení vývojového prostředí Calibre

Calibre je celé open source pod licencí GNU GPL v3 [https://www.gnu.org/licenses/gpl.html]. To znamená, že můžete volně stáhnout a upravit program podle své libosti. V této části se naučíte, jak nastavit vývojové prostředí Calibre na vámi zvoleném operačním systému. Calibre je napsáno převážně v Pythonu [https://www.python.org] s nějakým kódem C/C++ kvůli rychlosti a systémovému rozhraní. Pamatujte, že Calibre vyžaduje alespoň Python 3.8.

Obsah

- Filozofie návrhu

- Rozložení kódu

- Získání kódu

- Odesílání vašich změn, které mají být zahrnuty

- Vývojové prostředí Windows

- Vývojové prostředí macOS

- Vývojové prostředí Linux

- Mít samostatnou „normální“ a „vývojovou“ instalaci Calibre na stejném počítači

- Tipy k ladění

- Používání příkazů tisku

- Používání interaktivního překladače Pythonu

- Používání ladicího programu Pythonu jako vzdáleného ladicího programu

- Používání ladicího programu ve vašem oblíbeném IDE Pythonu

- Provádění libovolných skriptů uvnitř prostření Pythonu Calibre

- Používání Calibre ve vašich projektech

- Binární instalace Calibre

- Zdrojová instalace na Linuxu

- Dokumentace API pro různé části Calibre

Filozofie návrhu

Calibre má své kořeny ve světě Unixu, což znamená, že jeho design je vysoce modulární. Moduly se navzájem ovlivňují prostřednictvím dobře definovaných rozhraní. To umožňuje velice snadné přidávání nových funkcí a opravování chyb v Calibre, což má za následek zběsilé tempo vývoje. Vzhledem ke svým kořenům má Calibre komplexní rozhraní příkazového řádku pro všechny své funkce, zdokumentované v Rozhraní příkazového řádku.

Modulární návrh Calibre je vyjádřen prostřednictvím Modulů. K dispozici je kurz psaní modulů Calibre . Například přidání podpory pro nové zařízení do Calibre typicky zahrnuje napsání méně než 100 řádků kódu v podobě modulu ovladače zařízení. Vestavěné ovladače [https://github.com/kovidgoyal/calibre/tree/master/src/calibre/devices] můžete procházet. Podobně přidání podpory pro nové formáty převodu zahrnuje napsání modulu vstupně-výstupního formátu. Dalším příkladem modulárního návrhu je systém předpisů pro načítání zpráv. Pro více příkladů modulů navržených pro přidání funkcí do Calibre se podívejte na Rejstřík modulů [https://www.mobileread.com/forums/showthread.php?p=1362767#post1362767]..

Rozložení kódu

Všechen kód Pythonu Calibre je v balíčku calibre. Tento balíček obsahuje následující hlavní podbalíčky

- zařízení – Všechny ovladače zařízení. Prohlédněte si některé z vestavěných ovladačů pro získání představy o tom, jak fungují.

- Pro podrobnosti se podívejte na devices.interface, které definuje rozhraní podporované ovladači zařízení, a devices.usbms, které definuje obecný ovladač, který se připojuje k zařízení USBMS. Všechny ovladače v Calibre založené na USBMS se z něj dědí.

- e-knihy – Všechen kód převodu a metadat e-knih. Vhodným výchozím bodem je balíček calibre.ebooks.conversion.cli, což je modul poháněný příkazem ebook-convert. Zpracování převodu je řízeno prostřednictvím conversion.plumber. Kód nezávislý na formátu je všechen ebooks.oeb a kód závislý na formátu je v ebooks.format_name.

- Čtení, zápis a stahování jsou v ebooks.metadata

- Převod probíhá v řetězci, pro strukturu řetězce se podívejte na Úvod. Řetězec se skládá ze vstupního modulu, různých transformací a výstupního modulu. Kód, který vytvoří a řídí řetězec se nachází v plumber.py. Řetězec funguje na základě zobrazení e-knihy, které je jako rozbalené EPUB, s manifestem, páteří, obsahem, průvodcem, obsahem html atd. Třída, která spravuje toto zobrazení je OEBBook v ``ebooks.oeb.base`. Různé transformace, které jsou použity na knihu během převodu jsou v oeb/transforms/*.py. A vstupní a výstupní moduly jsou v conversion/plugins/*.py.

- Úprava e-knihy se odehrává pomocí různých objektů kontejneru. To je zdokumentováno v Dokumentace API pro nástroje pro úpravu e-knih.

- db – Serverová část databáze. Pro rozhraní do knihovny Calibre se podívejte na Dokumentace API pro rozhraní databáze.

- Server s obsahem: srv je Server s obsahem Calibre.

- gui2 – Grafické uživatelské rozhraní. Inicializace grafického rozhraní se odehrává v gui2.main a gui2.ui. Prohlížeč e-knih je v gui2.viewer. Editor e-knih je v ``gui2.tweak_book`.

Pokud chcete najít vstupní body pro všechny jednotlivé spustitelné soubory Calibre, podívejte se na strukturu entry_points v linux.py [https://github.com/kovidgoyal/calibre/blob/master/src/calibre/linux.py].

Pokud potřebujete pomoct pochopit kód, odešlete příspěvek do vývojářského fóra [https://www.mobileread.com/forums/forumdisplay.php?f=240] a s největší pravděpodobností získáte pomoc od jednoho z mnoha vývojářů Calibre.

Získání kódu

Zdrojový kód Calibre můžete získat dvěma způsoby, pomocí systému řízení verzí nebo přímo stažením tarball [https://calibre-ebook.com/dist/src].

Calibre používá Git [https://www.git-scm.com/], distribuovaný systém řízení verzí. Git je dostupný na všech platformách, které Calibre podporuje. Po nainstalování Gitu, můžete získat zdrojový kód Calibre pomocí příkazu:

git clone https://github.com/kovidgoyal/calibre.git

Na Windows budete potřebovat úplný název cesty, který bude něco jako C:\Program Files\Git\git.exe.

Calibre je velice rozsáhlý projekt s velmi dlouhou historií řízení zdroje, takže výše uvedené může nějakou dobu trvat (10 minut až hodinu v závislosti na vaší rychlosti internetu).

Pokud chcete získat kód rychleji, zdrojový kód pro nejnovější vydanou verzi je vždy dostupný jako archive [https://calibre-ebook.com/dist/src].

Pro aktualizaci větve na nejnovější kód použijte příkaz:

git pull --no-edit

Také můžete procházet kód na GitHub [https://github.com/kovidgoyal/calibre].

Odesílání vašich změn, které mají být zahrnuty

Pokud plánujete pouze provést několik malých změn, můžete provést změny a vytvořit „direktivu sloučení“, kterou pak můžete připojit k oznámení v přehledu chyb [https://bugs.launchpad.net/calibre] Calibre. Abyste to udělali, proveďte změny, pak spusťte:

git commit -am "Comment describing your changes"
git format-patch origin/master --stdout > my-changes

Tím se vytvoří soubor my-changes v aktuální složce, jednoduše ho připojte k oznámení na přehledu chyb [https://bugs.launchpad.net/calibre] Calibre. Pamatujte, že to bude zahrnovat všechna potvrzení změn, které jste provedli. Pokud chcete odeslat pouze některá potvrzení, musíte změnit výše uvedený origin/master. Pro odeslání pouze posledního potvrzení použijte:

git format-patch HEAD~1 --stdout > my-changes

Pro odeslání posledních n potvrzení nahraďte 1 pomocí n, například pro poslední 3 potvrzení:

git format-patch HEAD~3 --stdout > my-changes

Buďte opatrní, abyste nezahrnuli sloučení, když použijete HEAD~n.

Pokud máte v plánu udělat hodně vyvíjet Calibre, pak je nejlepší metodou vytvořit si účet na GitHub [https://github.com]. Níže je základní průvodce pro nastavení vaší lastní větve Calibre takovým způsobem, který vám umožní předkládat požadavky o zahrnutí do hlavního úložiště Calibre:

- Nastavte git ve svém počítači, jak je popsáno v tomto článku: Set Up Git [https://help.github.com/articles/set-up-git]

- Nastavte klíče ssh pro ověřování na GitHub, jak je popsáno zde: Generating an SSH key [https://help.github.com/articles/generating-ssh-keys]

- Přejděte na https://github.com/kovidgoyal/calibre a klikněte na tlačítko Fork.

- V terminálu zadejte:

git clone git@github.com:<username>/calibre.git
git remote add upstream https://github.com/kovidgoyal/calibre.git

Nahraďte výše uvedené <username> svým uživatelským jménem na GitHub. Tím místně odsouhlasíte svůj fork.

- Můžete provádět změny a potvrzovat je, kdykoliv budete chtít. Až budete připraveni sloučit svou práci, zadejte:

git push

a přejděte na https://github.com/<username>/calibre a klikněte na tlačítko Pull Request pro vygenerování požadavku, který může být sloučen.

- Svou lokální kopii můžete kdykoliv aktualizovat kódem z hlavního úložiště zadáním:

git pull upstream

Také byste měli dávat pozor na vývojové fórum [https://www.mobileread.com/forums/forumdisplay.php?f=240] Calibre. Před provedením zásadních změn byste měli o nich diskutovat na fóru nebo se přímo obrátit na Kovida (jeho e-mailová adresa je v celém zdrojovém kódu).

Vývojové prostředí Windows

Poznámka

Musíte také získat zdrojový kód Calibre samostatně, jak je popsáno výše.

Nainstalujte normálně Calibre pomocí instalátoru Windows [https://calibre-ebook.com/download_windows]. Pak otevřete příkazový řádek a přejděte do dříve odsouhlasené složky s kódem Calibre. Například:

cd C:\Users\kovid\work\calibre

calibre je složka, která obsahuje podsložky src a resources.

Dalším krokem je nastavení proměnné prostředí CALIBRE_DEVELOP_FROM na absolutní cestu složky src. Takže v návaznosti na výše uvedený příklad by to bylo C:\Users\kovid\work\calibre\src. Zde je krátký průvodce [https://docs.python.org/using/windows.html#excursus-setting-environment-variables] k nastavení proměnných prostředí v systému Windows.

Jakmile nastavíte proměnnou prostředí, otevřete nový příkazový řádek a zkontrolujte, že je správně nastavena pomocí příkazu:

echo %CALIBRE_DEVELOP_FROM%

Nastavení této proměnné prostředí znamená, že Calibre bude nyní načítat všechen svůj kód Pythonu ze zadaného umístění.

A je to! Nyní jste připraveni upravovat kód Calibre. Například otevřete soubor src\calibre\__init__.py ve svém oblíbeném editoru a přidejte řádek:

print("Hello, world!")

na začátek souboru. Nyní spusťte příkaz calibredb. Úplně první řádek výstupu by měl být Hello, world!.

Pokud chcete, můžete také nastavit vývojové prostředí Calibre uvnitř bezplatného programu Microsoft Visual Studio podle pokynů zde [https://www.mobileread.com/forums/showthread.php?t=251201].

Vývojové prostředí macOS

Poznámka

Musíte také získat zdrojový kód Calibre samostatně, jak je popsáno výše.

Nainstalujte normálně Calibre pomocí poskytnutého .dmg [https://calibre-ebook.com/download_osx]. Pak otevřete terminál a přejděte do dříve odsouhlasené složky s kódem Calibre, například:

cd /Users/kovid/work/calibre

calibre je složka, která obsahuje podsložky src a resources. Nástroje příkazového řádku Calibre se nacházejí uvnitř balíčku aplikací Calibre, v /Applications/calibre.app/Contents/MacOS byste měli tuto složku přidat do proměnné prostředí PATH, pokud chcete snadno spouštět nástroje příkazového řádku.

Dalším krokem je vytvoření bashový skript, který nastaví proměnnou prostředí CALIBRE_DEVELOP_FROM na absolutní cestu složky src při spuštění Calibre v režimu ladění.

Vytvořte textový soubor:

#!/bin/sh
export CALIBRE_DEVELOP_FROM="/Users/kovid/work/calibre/src"
calibre-debug -g

Uložte tento soubor jako /usr/bin/calibre-develop, pak nastavte jeho oprávnění tak, aby mohl být spuštěn:

chmod +x /usr/local/bin/calibre-develop

Jakmile toto uděláte, spusťte:

calibre-develop

Měli byste vidět nějaké diagnostické informace v okně terminálu při spuštění Calibre a měli byste vidět hvězdičku za číslem verze v okně grafického rozhraní, která znamená, že spouštíte ze zdrojového kódu.

Vývojové prostředí Linux

Poznámka

Musíte také získat zdrojový kód Calibre samostatně, jak je popsáno výše.

Calibre je vyvíjeno především na Linuxu. Při nastavování vývojového prostředí máte dvě možnosti. Můžete nainstalovat binární kód Calibre jako normálně a použít ho jako běhové prostředí pro váš vývoj. Tento přístup je podobný tomu použitému ve Windows a macOS. Případně můžete nainstalovat Calibre ze zdrojového kódu. Pokyny pro nastavení vývojového prostředí ze zdrojového kódu jsou v souboru INSTALL ve stromu zdrojového kódu. Zde budeme adresovat pomocí binárního kódu jako běhového prostředí, což je doporučená metoda.

Nainstalujte Calibre pomocí instalátoru binárního kódu [https://calibre-ebook.com/download_linux]. Pak otevřete terminál a přejděte do dříve odsouhlasené složky s kódem Calibre, například:

cd /home/kovid/work/calibre

calibre je složka, která obsahuje podsložky src a resources.

Dalším krokem je nastavení proměnné prostředí CALIBRE_DEVELOP_FROM na absolutní cestu složky src. Takže v návaznosti na výše uvedený příklad by to bylo /home/kovid/work/calibre/src. Jak nastavit proměnné prostředí závisí na vaší distribuci Linuxu a jaké prostředí používáte.

Poznámka

Je doporučeno použít binární instalační program poskytovaný z upstreamu. Pokud trváte na použití balíčku poskytovaného vaší distribucí, použijte místo toho proměnné CALIBRE_PYTHON_PATH a CALIBRE_RESOURCES_PATH.

Jakmile nastavíte proměnnou prostředí, otevřete nový terminál a zkontrolujte, že je správně nastavena pomocí příkazu:

echo $CALIBRE_DEVELOP_FROM

Nastavení této proměnné prostředí znamená, že Calibre bude nyní načítat všechen svůj kód Pythonu ze zadaného umístění.

A je to! Nyní jste připraveni upravovat kód Calibre. Například otevřete soubor src/calibre/__init__.py ve svém oblíbeném editoru a přidejte řádek:

print("Hello, world!")

na začátek souboru. Nyní spusťte příkaz calibredb. Úplně první řádek výstupu by měl být Hello, world!.

Mít samostatnou „normální“ a „vývojovou“ instalaci Calibre na stejném počítači

Zdrojový strom Calibre je velice stabilní a zřídka kdy spadne, ale pokud cítíte potřebu spouštět ze zdroje na samostatné zkušební knihovně a spouštět vydanou verzi Calibre se svou každodenní knihovnou, můžete toho snadno dosáhnout pomocí souborů .bat nebo skriptů prostředí pro spuštění Calibre. Níže uvedený příklad ukazuje, jak to provést v systému Windows pomocí souborů .bat (pokyny pro ostatní platformy jsou stejné, stačí použít skript prostředí namísto souboru .bat)

Pro spuštění vydané verze Calibre s vaší každodenní knihovnou:

calibre-normal.bat:

calibre.exe "--with-library=C:\path\to\everyday\library folder"

calibre-dev.bat:

set CALIBRE_DEVELOP_FROM=C:\path\to\calibre\checkout\src
calibre.exe "--with-library=C:\path\to\test\library folder"

Tipy k ladění

Python je dynamicky napsaný jazyk s vynikajícím vybavením pro introspekci. Kovid napsal kód jádra Calibre, aniž by jedinkrát použil ladicí program. Existuje mnoho strategií ladění kódu Calibre:

Používání příkazů tisku

Toto je Kovidův oblíbený způsob ladění. Jednoduše vložte příkazy tisku do nejdůležitějších bodů a spusťte program v terminálu. Grafické rozhraní můžete spustit z terminálu například takto:

calibre-debug -g

Podobně můžete spustit Prohlížeč e-knih takto:

calibre-debug -w /path/to/file/to/be/viewed

Editor e-knih může být spuštěn takto:

calibre-debug --edit-book /path/to/be/edited

Používání interaktivního překladače Pythonu

Můžete vložit následující dva řádky kódu pro spuštění interaktivní relace Pythonu v tomto bodě:

from calibre import ipython
ipython(locals())

Při spuštění z příkazového řádku toto spustí interaktivní překladač Pythonu s přístupem ke všem místně definovaným proměnným (proměnné v místním rozsahu). Interaktivní výzva má dokonce dokončení Tab pro vlastnosti objektů a můžete použít různé vybavení Pythonu pro introspekci, například dir(), type(), repr(), atd.

Používání ladicího programu Pythonu jako vzdáleného ladicího programu

Můžete použít vestavěný ladicí program Pythonu (pdb) jako vzdálený ladicí program z příkazového řádku. Nejdříve takto spusťte vzdálený ladicí program v místě v kódu Calibre, které vás zajímá:

from calibre.rpdb import set_trace
set_trace()

Pak spusťte Calibre, a to buď jako normálně, nebo pomocí jednoho z ladicích příkazů Calibre popsaných v předchozím oddílu. Jakmile je dosažen výše uvedený bod v kódu, Calibre se zablokuje a počká na připojení ladicího programu.

Nyní otevřete terminálu nebo příkazový řádek a použijte následující příkaz pro spuštění relace ladění:

calibre-debug -c "from calibre.rpdb import cli; cli()"

Jak používat ladicí program Pythonu, si můžete přečíst v dokumentaci stdlib Pythonu pro modul pdb [https://docs.python.org/library/pdb.html#debugger-commands].

Poznámka

Ve výchozím nastavení se vzdálený ladicí program pokusí připojit na portu 4444. Můžete jej změnit předáním parametru portu výše uvedeným funkcím set_trace() a cli() takto : set_trace(port=1234) a cli(port=1234).

Poznámka

Ladicí program Pythonu nemůže zpracovat více vláken, takže musíte volat set_trace pro každé vlákno, pokaždé s jiným číslem portu.

Používání ladicího programu ve vašem oblíbeném IDE Pythonu

Je možné použít vestavěný ladicí program ve vašem oblíbeném IDE Pythonu, pokud podporuje vzdálené ladění. Prvním krokem je přidání rezervační složky src Calibre do PYTHONPATH ve vašem IDE. Jinými slovy, složka nastavená výše jako CALIBRE_DEVELOP_FROM, musí být také v PYTHONPATH vašeho IDE.

Pak umístěte modul vzdáleného ladicího programu IDE do podsložky src rezervační složky zdrojového kódu Calibre. Přidejte jakýkoliv potřebný kód pro spuštění vzdáleného ladicího programu do Calibre v důležitém místě, například do hlavní funkce. Pak spusťte Calibre jako normálně. Váš IDE by nyní měl být schopný se připojit ke vzdálenému ladicímu programu běžícímu uvnitř Calibre.

Provádění libovolných skriptů uvnitř prostření Pythonu Calibre

Příkaz calibre-debug poskytuje několik šikovných přepínačů pro provádění vašeho kódu s přístupem k modulům Calibre:

calibre-debug -c "some Python code"

je skvělý pro testování malého fragmentu kódu na příkazovém řádku. Funguje to stejným způsobem jako přepínač -c pro překladač Pythonu:

calibre-debug myscript.py

může být použit ke spuštění vašeho skriptu Python. Funguje to stejným způsobem jako předání skriptu překladači Pythonu, kromě toho, že prostředí Calibre je plně inicializované, takže můžete ve svém skriptu použít celý kód Calibre. Pro použití parametrů příkazového řádku s vaším skriptem použijte formulář:

calibre-debug myscript.py -- --option1 arg1

-- způsobí, že všechny následující parametry budou předány do vašeho skriptu.

Používání Calibre ve vašich projektech

Je možné přímo použít funkce nebo kód Calibre ve vašem projektu v Pythonu. Existují dva způsoby, jak to udělat:

Binární instalace Calibre

Pokud máte binární instalaci Calibre, můžete použít překladač Pythonu nainstalovaný s Calibre takto:

calibre-debug /path/to/your/python/script.py -- arguments to your script

Zdrojová instalace na Linuxu

Kromě použití výše uvedené techniky, pokud provedete zdrojovou instalaci na Linuxu, můžete také přímo importovat Calibre následujícím způsobem:

import init_calibre
import calibre

print(calibre.__version__)

Je nezbytné, abyste importovali modul init_calibre před jakýmikoliv jinými moduly nebo balíčky Calibre, protože nastavuje překladač ke spuštění kódu Calibre.

Dokumentace API pro různé části Calibre

- Dokumentace API pro předpisy
- Dokumentace API pro moduly
- Dokumentace API pro rozhraní databáze

- Dokumentace API pro nástroje pro úpravu e-knih

© Copyright Kovid Goyal.
Naposledy aktualizováno kvě 03, 2024.

Navigace

- moduly
- další |
- předchozí |
- Spustit »
- Nastavení vývojového prostředí Calibre »
- Dokumentace API pro rozhraní databáze

Dokumentace API pro rozhraní databáze

Toto rozhraní API je bezpečné z hlediska podprocesů (používá schéma vícenásobné čtečky, zamykání jednoduchého zapisovače). K tomuto rozhraní API můžete přistupovat takto:

from calibre.library import db
db = db('Path to calibre library folder').new_api

Pokud jste v modulu Calibre, který je součástí hlavního grafického rozhraní Calibre, získáte k němu namísto toho přístup takto:

db = self.gui.current_db.new_api

class calibre.db.cache.Cache(backend, library_database_instance=None)[zdroj]

An in-memory cache of the metadata.db file from a calibre library.
This class also serves as a threadsafe API for accessing the database.
The in-memory cache is maintained in normal form for maximum performance.

SQLITE is simply used as a way to read and write from metadata.db robustly.
All table reading/sorting/searching/caching logic is re-implemented. This was necessary for maximum performance and flexibility.

class EventType(value, names=<not given>, *values, module=None, qualname=None, type=None, start=1, boundary=None)

book_created = 4

When a new book record is created in the database, with the book id as the only argument

book_edited = 8

When a book format is edited, with arguments: (book_id, fmt)

books_removed = 5

When books are removed from the database with the list of book ids as the only argument

format_added = 2

When a format is added to a book, with arguments:
(book_id, format)

formats_removed = 3

When formats are removed from a book, with arguments:
(mapping of book id to set of formats removed from the book)

indexing_progress_changed = 9

When the indexing progress changes

items_removed = 7

When items such as tags or authors are removed from some books.
Arguments: (field_name, affected book ids, ids of removed items)

items_renamed = 6

When items such as tags or authors are renamed in some or all books.
Arguments: (field_name, affected book ids, map of old item id to new item id)

metadata_changed = 1

When some metadata is changed for some books, with arguments: (name of changed field, set of affected book ids)

add_books(books, add_duplicates=True, apply_import_tags=True, preserve_uuid=False, run_hooks=True, dbapi=None)[zdroj]

Add the specified books to the library. Books should be an iterable of
2-tuples, each 2-tuple of the form (mi, format_map) where mi is a
Metadata object and format_map is a dictionary of the form {fmt: path_or_stream}, for example: {'EPUB': '/path/to/file.epub'}.

Returns a pair of lists: ids, duplicates. ids contains the book ids for all newly created books in the database. duplicates contains the (mi, format_map) for all books that already exist in the database as per the simple duplicate detection heuristic used by has_book().

add_custom_book_data(name, val_map, delete_first=False)[zdroj]

Add data for name where val_map is a map of book_ids to values. If delete_first is True, all previously stored data for name will be removed.

add_extra_files(book_id, map_of_relpath_to_stream_or_path, replace=True, auto_rename=False)[zdroj]

Add extra data files

add_format(book_id, fmt, stream_or_path, replace=True, run_hooks=True, dbapi=None)[zdroj]

Add a format to the specified book. Return True if the format was added successfully.

Parametry:

- replace – If True replace existing format, otherwise if the format already exists, return False.

- run_hooks – If True, file type plugins are run on the format before and after being added.

- dbapi – Pouze interní použití.

add_listener(event_callback_function, check_already_added=False)[zdroj]

Register a callback function that will be called after certain actions are taken on this database. The function must take three arguments:
(EventType, library_id, event_type_specific_data)

add_notes_resource(path_or_stream_or_data, name: str, mtime: float = None) → int[zdroj]

Add the specified resource so it can be referenced by notes and return its content hash

all_book_ids(type=<class 'frozenset'>)[zdroj]

Frozen set of all known book ids.

all_field_for(field, book_ids, default_value=None)[zdroj]

Same as field_for, except that it operates on multiple books at once

all_field_ids(name)[zdroj]

Frozen set of ids for all values in the field name.

all_field_names(field)[zdroj]

Frozen set of all fields names (should only be used for many-one and many-many fields)

author_data(author_ids=None)[zdroj]

Return author data as a dictionary with keys: name, sort, link

If no authors with the specified ids are found an empty dictionary is returned. If author_ids is None, data for all authors is returned.

author_sort_from_authors(authors, key_func=<function make_change_case_func.<locals>.change_case>)[zdroj]

Given a list of authors, return the author_sort string for the authors, preferring the author sort associated with the author over the computed string.

books_for_field(name, item_id)[zdroj]

Return all the books associated with the item identified by item_id, where the item belongs to the field name.

Returned value is a set of book ids, or the empty set if the item or the field does not exist.

books_in_virtual_library(vl, search_restriction=None, virtual_fields=None)[zdroj]

Return the set of books in the specified virtual library

compress_covers(book_ids, jpeg_quality=100, progress_callback=None)[zdroj]

Compress the cover images for the specified books. A compression quality of 100 will perform lossless compression, otherwise lossy compression.

The progress callback will be called with the book_id and the old and new sizes for each book that has been processed. If an error occurs, the new size will be a string with the error details.

copy_cover_to(book_id, dest, use_hardlink=False, report_file_size=None)[zdroj]

Copy the cover to the file like object dest. Returns False if no cover exists or dest is the same file as the current cover.
dest can also be a path in which case the cover is copied to it if and only if the path is different from the current path (taking case sensitivity into account).

copy_format_to(book_id, fmt, dest, use_hardlink=False, report_file_size=None)[zdroj]

Copy the format fmt to the file like object dest. If the specified format does not exist, raises NoSuchFormat error.
dest can also be a path (to a file), in which case the format is copied to it, iff the path is different from the current path (taking case sensitivity into account).

cover(book_id, as_file=False, as_image=False, as_path=False, as_pixmap=False)[zdroj]

Return the cover image or None. By default, returns the cover as a bytestring.

WARNING: Using as_path will copy the cover to a temp file and return the path to the temp file. You should delete the temp file when you are done with it.

Parametry:

- as_file – If True return the image as an open file object (a SpooledTemporaryFile)

- as_image – If True return the image as a QImage object

- as_pixmap – If True return the image as a QPixmap object

- as_path – If True return the image as a path pointing to a temporary file

data_for_find_identical_books()[zdroj]

Return data that can be used to implement find_identical_books() in a worker process without access to the db. See db.utils for an implementation.

data_for_has_book()[zdroj]

Return data suitable for use in has_book(). This can be used for an implementation of has_book() in a worker process without access to the db.

delete_custom_book_data(name, book_ids=())[zdroj]

Delete data for name. By default deletes all data, if you only want to delete data for some book ids, pass in a list of book ids.

delete_trash_entry(book_id, category)[zdroj]

Delete an entry from the trash. Here category is ‚b‘ for books and ‚f‘ for formats.

embed_metadata(book_ids, only_fmts=None, report_error=None, report_progress=None)[zdroj]

Update metadata in all formats of the specified book_ids to current metadata in the database.

expire_old_trash()[zdroj]

Expire entries from the trash that are too old

export_note(field, item_id) → str[zdroj]

Export the note as a single HTML document with embedded images as data: URLs

fast_field_for(field_obj, book_id, default_value=None)[zdroj]

Same as field_for, except that it avoids the extra lookup to get the field object

field_for(name, book_id, default_value=None)[zdroj]

Return the value of the field name for the book identified by book_id. If no such book exists or it has no defined value for the field name or no such field exists, then default_value is returned.

default_value is not used for title, title_sort, authors, author_sort and series_index. This is because these always have values in the db. default_value is used for all custom columns.

The returned value for is_multiple fields are always tuples, even when no values are found (in other words, default_value is ignored). The exception is identifiers for which the returned value is always a dictionary.
The returned tuples are always in link order, that is, the order in which they were created.

field_ids_for(name, book_id)[zdroj]

Return the ids (as a tuple) for the values that the field name has on the book identified by book_id. If there are no values, or no such book, or no such field, an empty tuple is returned.

field_supports_notes(field=None) → bool[zdroj]

Return True iff the specified field supports notes. If field is None return frozenset of all fields that support notes.

find_identical_books(mi, search_restriction='', book_ids=None)[zdroj]

Finds books that have a superset of the authors in mi and the same title (title is fuzzy matched). See also data_for_find_identical_books().

format(book_id, fmt, as_file=False, as_path=False, preserve_filename=False)[zdroj]

Return the e-book format as a bytestring or None if the format doesn’t exist, or we don’t have permission to write to the e-book file.

Parametry:

- as_file – If True the e-book format is returned as a file object. Note that the file object is a SpooledTemporaryFile, so if what you want to do is copy the format to another file, use copy_format_to() instead for performance.

- as_path – Copies the format file to a temp file and returns the path to the temp file

- preserve_filename – If True and returning a path the filename is the same as that used in the library. Note that using this means that repeated calls yield the same temp file (which is re-created each time)

format_abspath(book_id, fmt)[zdroj]

Return absolute path to the e-book file of format format. You should almost never use this, as it breaks the threadsafe promise of this API.
Instead use, copy_format_to().

Currently used only in calibredb list, the viewer, edit book, compare_format to original format, open with, bulk metadata edit and the catalogs (via get_data_as_dict()).

Apart from the viewer, open with and edit book, I don’t believe any of the others do any file write I/O with the results of this call.

format_hash(book_id, fmt)[zdroj]

Return the hash of the specified format for the specified book. The kind of hash is backend dependent, but is usually SHA-256.

format_metadata(book_id, fmt, allow_cache=True, update_db=False)[zdroj]

Return the path, size and mtime for the specified format for the specified book.
You should not use path unless you absolutely have to, since accessing it directly breaks the threadsafe guarantees of this API. Instead use the copy_format_to() method.

Parametry:

- allow_cache – If True cached values are used, otherwise a slow filesystem access is done. The cache values could be out of date if access was performed to the filesystem outside of this API.

- update_db – If True The max_size field of the database is updated for this book.

formats(book_id, verify_formats=True)[zdroj]

Return tuple of all formats for the specified book. If verify_formats is True, verifies that the files exist on disk.

get_all_items_that_have_notes(field_name=None) → set[int] | dict[str, set[int]][zdroj]

Return all item_ids for items that have notes in the specified field or all fields if field_name is None

get_all_link_maps_for_book(book_id)[zdroj]

Returns all links for all fields referenced by book identified by book_id.
If book_id doesn’t exist then the method returns {}.

Example: Assume author A has link X, author B has link Y, tag S has link
F, and tag T has link G. If book 1 has author A and tag T, this method returns {‚authors‘:{‚A‘:‘X‘}, ‚tags‘:{‚T‘, ‚G‘}}.
If book 2’s author is neither A nor B and has no tags, this method returns {}.

Parametry:

book_id – the book id in question.

Vrací:

{field: {field_value, link_value}, … for all fields with a field_value having a non-empty link value for that book

get_categories(sort='name', book_ids=None, already_fixed=None, first_letter_sort=False)[zdroj]

Used internally to implement the Tag Browser

get_custom_book_data(name, book_ids=(), default=None)[zdroj]

Get data for name. By default returns data for all book_ids, pass in a list of book ids if you only want some data. Returns a map of book_id to values. If a particular value could not be decoded, uses default for it.

get_id_map(field)[zdroj]

Return a mapping of id numbers to values for the specified field.
The field must be a many-one or many-many field, otherwise a ValueError is raised.

get_ids_for_custom_book_data(name)[zdroj]

Return the set of book ids for which name has data.

get_item_id(field, item_name)[zdroj]

Return the item id for item_name (case-insensitive) or None if not found

get_item_ids(field, item_names)[zdroj]

Return the item id for item_name (case-insensitive)

get_item_name(field, item_id)[zdroj]

Return the item name for the item specified by item_id in the specified field. See also get_id_map().

get_item_name_map(field, normalize_func=None)[zdroj]

Return mapping of item values to ids

get_link_map(for_field)[zdroj]

Return a dictionary of links for the supplied field.

Parametry:

for_field – the lookup name of the field for which the link map is desired

Vrací:

{field_value:link_value, …} for non-empty links

get_metadata(book_id, get_cover=False, get_user_categories=True, cover_as_data=False)[zdroj]

Return metadata for the book identified by book_id as a calibre.ebooks.metadata.book.base.Metadata object.
Note that the list of formats is not verified. If get_cover is True, the cover is returned, either a path to temp file as mi.cover or if cover_as_data is True then as mi.cover_data.

get_next_series_num_for(series, field='series', current_indices=False)[zdroj]

Return the next series index for the specified series, taking into account the various preferences that control next series number generation.

Parametry:

- field – The series-like field (defaults to the builtin series column)

- current_indices – If True, returns a mapping of book_id to current series_index value instead.

get_notes_resource(resource_hash) → dict | None[zdroj]

Return a dict containing the resource data and name or None if no resource with the specified hash is found

get_proxy_metadata(book_id)[zdroj]

Like get_metadata() except that it returns a ProxyMetadata object that only reads values from the database on demand. This is much faster than get_metadata when only a small number of fields need to be accessed from the returned metadata object.

get_usage_count_by_id(field)[zdroj]

Return a mapping of id to usage count for all values of the specified field, which must be a many-one or many-many field.

has_book(mi)[zdroj]

Return True iff the database contains an entry with the same title as the passed in Metadata object. The comparison is case-insensitive.
See also data_for_has_book().

has_format(book_id, fmt)[zdroj]

Return True iff the format exists on disk

has_id(book_id)[zdroj]

Return True iff the specified book_id exists in the db

import_note(field, item_id, path_to_html_file, path_is_data=False)[zdroj]

Import a previously exported note or an arbitrary HTML file as the note for the specified item

init()[zdroj]

Initialize this cache with data from the backend.

items_with_notes_in_book(book_id: int) → dict[str, dict[int, str]][zdroj]

Return a dict of field to items that have associated notes for that field for the specified book

link_for(field, item_id)[zdroj]

Return the link, if any, for the specified item or None if no link is found

list_extra_files(book_id, use_cache=False, pattern='') → Tuple[ExtraFile, ...][zdroj]

Get information about extra files in the book’s directory.

Parametry:

- book_id – the database book id for the book

- pattern – the pattern of filenames to search for. Empty pattern matches all extra files. Patterns must use / as separator.
Use the DATA_FILE_PATTERN constant to match files inside the data directory.

Vrací:

A tuple of all extra files matching the specified pattern. Each element of the tuple is
ExtraFile(relpath, file_path, stat_result). Where relpath is the relative path of the file to the book directory using / as a separator.
stat_result is the result of calling os.stat() on the file.

merge_extra_files(dest_id, src_ids, replace=False)[zdroj]

Merge the extra files from src_ids into dest_id. Conflicting files are auto-renamed unless replace=True in which case they are replaced.

move_book_from_trash(book_id)[zdroj]

Undelete a book from the trash directory

move_format_from_trash(book_id, fmt)[zdroj]

Undelete a format from the trash directory

multisort(fields, ids_to_sort=None, virtual_fields=None)[zdroj]

Return a list of sorted book ids. If ids_to_sort is None, all book ids are returned.

fields must be a list of 2-tuples of the form (field_name, ascending=True or False). The most significant field is the first
2-tuple.

notes_data_for(field, item_id) → str[zdroj]

Return all notes data as a dict or None if note does not exist

notes_for(field, item_id) → str[zdroj]

Return the notes document or an empty string if not found

notes_resources_used_by(field, item_id)[zdroj]

Return the set of resource hashes of all resources used by the note for the specified item

pref(name, default=None, namespace=None)[zdroj]

Return the value for the specified preference or the value specified as default if the preference is not set.

read_backup(book_id)[zdroj]

Return the OPF metadata backup for the book as a bytestring or None if no such backup exists.

remove_books(book_ids, permanent=False)[zdroj]

Remove the books specified by the book_ids from the database and delete their format files. If permanent is False, then the format files are placed in the per-library trash directory.

remove_formats(formats_map, db_only=False)[zdroj]

Remove the specified formats from the specified books.

Parametry:

- formats_map – A mapping of book_id to a list of formats to be removed from the book.

- db_only – If True, only remove the record for the format from the db, do not delete the actual format file from the filesystem.

Vrací:

A map of book id to set of formats actually deleted from the filesystem for that book

remove_items(field, item_ids, restrict_to_book_ids=None)[zdroj]

Delete all items in the specified field with the specified ids.
Returns the set of affected book ids. restrict_to_book_ids is an optional set of books ids. If specified the items will only be removed from those books.

rename_extra_files(book_id, map_of_relpath_to_new_relpath, replace=False)[zdroj]

Rename extra data files

rename_items(field, item_id_to_new_name_map, change_index=True, restrict_to_book_ids=None)[zdroj]

Rename items from a many-one or many-many field such as tags or series.

Parametry:

- change_index – When renaming in a series-like field also change the series_index values.

- restrict_to_book_ids – An optional set of book ids for which the rename is to be performed, defaults to all books.

restore_book(book_id, mi, last_modified, path, formats, annotations=())[zdroj]

Restore the book entry in the database for a book that already exists on the filesystem

restore_original_format(book_id, original_fmt)[zdroj]

Restore the specified format from the previously saved
ORIGINAL_FORMAT, if any. Return True on success. The ORIGINAL_FORMAT is deleted after a successful restore.

property safe_read_lock

A safe read lock is a lock that does nothing if the thread already has a write lock, otherwise it acquires a read lock. This is necessary to prevent DowngradeLockErrors, which can happen when updating the search cache in the presence of composite columns. Updating the search cache holds an exclusive lock, but searching a composite column involves reading field values via ProxyMetadata which tries to get a shared lock. There may be other scenarios that trigger this as well.

This property returns a new lock object on every access. This lock object is not recursive (for performance) and must only be used in a with statement as with cache.safe_read_lock: otherwise bad things will happen.

save_original_format(book_id, fmt)[zdroj]

Save a copy of the specified format as ORIGINAL_FORMAT, overwriting any existing ORIGINAL_FORMAT.

search(query, restriction='', virtual_fields=None, book_ids=None)[zdroj]

Search the database for the specified query, returning a set of matched book ids.

Parametry:

- restriction – A restriction that is ANDed to the specified query. Note that restrictions are cached, therefore the search for a AND b will be slower than a with restriction b.

- virtual_fields – Used internally (virtual fields such as on_device to search over).

- book_ids – If not None, a set of book ids for which books will be searched instead of searching all books.

search_notes(fts_engine_query='', use_stemming=True, highlight_start=None, highlight_end=None, snippet_size=None, restrict_to_fields=(), return_text=True, result_type=<class 'tuple'>, process_each_result=None, limit=None)[zdroj]

Search the text of notes using an FTS index. If the query is empty return all notes.

set_conversion_options(options, fmt='PIPE')[zdroj]

options must be a map of the form {book_id:conversion_options}

set_cover(book_id_data_map)[zdroj]

Set the cover for this book. The data can be either a QImage,
QPixmap, file object or bytestring. It can also be None, in which case any existing cover is removed.

set_field(name, book_id_to_val_map, allow_case_change=True, do_path_update=True)[zdroj]

Set the values of the field specified by name. Returns the set of all book ids that were affected by the change.

Parametry:

- book_id_to_val_map – Mapping of book_ids to values that should be applied.

- allow_case_change – If True, the case of many-one or many-many fields will be changed.
For example, if a book has the tag tag1 and you set the tag for another book to Tag1 then the both books will have the tag Tag1 if allow_case_change is True, otherwise they will both have the tag tag1.

- do_path_update – Used internally, you should never change it.

set_link_map(field, value_to_link_map, only_set_if_no_existing_link=False)[zdroj]

Sets links for item values in field.
Note: this method doesn’t change values not in the value_to_link_map

Parametry:

- field – the lookup name

- value_to_link_map – dict(field_value:link, …). Note that these are values, not field ids.

Vrací:

books changed by setting the link

set_metadata(book_id, mi, ignore_errors=False, force_changes=False, set_title=True, set_authors=True, allow_case_change=False)[zdroj]

Set metadata for the book id from the Metadata object mi

Setting force_changes=True will force set_metadata to update fields even if mi contains empty values. In this case, ‚None‘ is distinguished from
‚empty‘. If mi.XXX is None, the XXX is not replaced, otherwise it is.
The tags, identifiers, and cover attributes are special cases. Tags and identifiers cannot be set to None so they will always be replaced if force_changes is true. You must ensure that mi contains the values you want the book to have. Covers are always changed if a new cover is provided, but are never deleted. Also note that force_changes has no effect on setting title or authors.

set_notes_for(field, item_id, doc: str, searchable_text: str = '', resource_hashes=(), remove_unused_resources=False) → int[zdroj]

Set the notes document. If the searchable text is different from the document, specify it as searchable_text. If the document references resources their hashes must be present in resource_hashes. Set remove_unused_resources to True to cleanup unused resources, note that updating a note automatically cleans up resources pertaining to that note anyway.

set_pref(name, val, namespace=None)[zdroj]

Set the specified preference to the specified value. See also pref().

split_if_is_multiple_composite(f, val)[zdroj]

If f is a composite column lookup key and the column is is_multiple then split v into unique non-empty values. The comparison is case sensitive.
Order is not preserved. Return a list() for compatibility with proxy metadata field getters, for example tags.

tags_older_than(tag, delta=None, must_have_tag=None, must_have_authors=None)[zdroj]

Return the ids of all books having the tag tag that are older than the specified time. tag comparison is case insensitive.

Parametry:

- delta – A timedelta object or None. If None, then all ids with the tag are returned.

- must_have_tag – If not None the list of matches will be restricted to books that have this tag

- must_have_authors – A list of authors. If not None the list of matches will be restricted to books that have these authors (case insensitive).

unretire_note_for(field, item_id) → int[zdroj]

Unretire a previously retired note for the specified item. Notes are retired when an item is removed from the database

user_categories_for_books(book_ids, proxy_metadata_map=None)[zdroj]

Return the user categories for the specified books.
proxy_metadata_map is optional and is useful for a performance boost, in contexts where a ProxyMetadata object for the books already exists.
It should be a mapping of book_ids to their corresponding ProxyMetadata objects.

© Copyright Kovid Goyal.
Naposledy aktualizováno kvě 03, 2024.

Navigace

- moduly
- další |
- předchozí |
- Spustit »
- Nastavení vývojového prostředí Calibre »

- Dokumentace API pro nástroje pro úpravu e-knih

Dokumentace API pro nástroje pro úpravu e-knih

Nástroje pro úpravu e-knih se skládají z objektu calibre.ebooks.oeb.polish.container.Container, který představuje knihu jako kolekci souborů HTML a zdrojů a různých nástrojů, které lze použít k provádění operací na kontejneru. Všechny nástroje jsou ve formě funkcí na úrovni modulů v různých modulech calibre.ebooks.oeb.polish.*.

Objekt kontejneru pro knihu v cestě získáte takto:

from calibre.ebooks.oeb.polish.container import get_container
container = get_container('Path to book file', tweak_mode=True)

Pokud píšete modul pro Editor e-knih, získáte aktuální kontejner pro upravovanou knihu takto:

from calibre.gui2.tweak_book import current_container
container = current_container()
if container is None:
report_error # No book has been opened yet

Objekt kontejneru
class calibre.ebooks.oeb.polish.container.Container(rootpath, opfpath, log, clone_data=None)[zdroj]

A container represents an open e-book as a folder full of files and an
OPF file. There are two important concepts:

- The root folder. This is the base of the e-book. All the e-books files are inside this folder or in its sub-folders.

- Names: These are paths to the books‘ files relative to the root folder. They always contain POSIX separators and are unquoted. They can be thought of as canonical identifiers for files in the book.
Most methods on the container object work with names. Names are always in the NFC Unicode normal form.

- Clones: the container object supports efficient on-disk cloning, which is used to implement checkpoints in the e-book editor. In order to make this work, you should never access files on the filesystem directly. Instead, use raw_data() or open() to read/write to component files in the book.

When converting between hrefs and names use the methods provided by this class, they assume all hrefs are quoted.

abspath_to_name(fullpath, root=None)[zdroj]

Convert an absolute path to a canonical name relative to root

Parametry:

root – The base folder. By default the root for this container object is used.

add_file(name, data, media_type=None, spine_index=None, modify_name_if_needed=False, process_manifest_item=None)[zdroj]

Add a file to this container. Entries for the file are automatically created in the OPF manifest and spine
(if the file is a text document)

add_name_to_manifest(name, process_manifest_item=None)[zdroj]

Add an entry to the manifest for a file with the specified name. Returns the manifest id.

add_properties(name, *properties)[zdroj]

Add the specified properties to the manifest item identified by name.

apply_unique_properties(name, *properties)[zdroj]

Ensure that the specified properties are set on only the manifest item identified by name. You can pass None as the name to remove the property from all items.

book_type = 'oeb'

Typ knihy (epub pro soubory EPUB a azw3 pro soubory AZW3)

commit(outpath=None, keep_parsed=False)[zdroj]

Commit all dirtied parsed objects to the filesystem and write out the e-book file at outpath.

Parametry:

- output – The path to write the saved e-book file to. If None, the path of the original book file is used.

- keep_parsed – If True the parsed representations of committed items are kept in the cache.

commit_item(name, keep_parsed=False)[zdroj]

Commit a parsed object to disk (it is serialized and written to the underlying file). If keep_parsed is True the parsed representation is retained in the cache. See also: parsed()

dirty(name)[zdroj]

Mark the parsed object corresponding to name as dirty. See also: parsed().

exists(name)[zdroj]

True iff a file/folder corresponding to the canonical name exists. Note that this function suffers from the limitations of the underlying OS filesystem, in particular case (in)sensitivity. So on a case insensitive filesystem this will return True even if the case of name is different from the case of the underlying filesystem file. See also has_name()

filesize(name)[zdroj]

Return the size in bytes of the file represented by the specified canonical name. Automatically handles dirtied parsed objects. See also: parsed()

generate_item(name, id_prefix=None, media_type=None, unique_href=True)[zdroj]

Add an item to the manifest with href derived from the given name. Ensures uniqueness of href and id automatically. Returns generated item.

get_file_path_for_processing(name, allow_modification=True)[zdroj]

Similar to open() except that it returns a file path, instead of an open file object.

property guide_type_map

Mapping of guide type to canonical name

has_name(name)[zdroj]

Return True iff a file with the same canonical name as that specified exists. Unlike exists() this method is always case-sensitive.

href_to_name(href, base=None)[zdroj]

Convert an href (relative to base) to a name. base must be a name or
None, in which case self.root is used.

insert_into_xml(parent, item, index=None)[zdroj]

Insert item into parent (or append if index is None), fixing indentation. Only works with self closing items.

is_dir = False

Pokud tento kontejner představuje rozbalenou knihu (adresář)

iterlinks(name, get_line_numbers=True)[zdroj]

Iterate over all links in name. If get_line_numbers is True the yields results of the form (link, line_number, offset). Where line_number is the line_number at which the link occurs and offset is the number of characters from the start of the line. Note that offset could actually encompass several lines if not zero.

make_name_unique(name)[zdroj]

Ensure that name does not already exist in this book. If it does, return a modified version that does not exist.

manifest_has_name(name)[zdroj]

Return True if the manifest has an entry corresponding to name

property manifest_id_map

Mapping of manifest id to canonical names

manifest_items_of_type(predicate)[zdroj]

The names of all manifest items whose media-type matches predicate. predicate can be a set, a list, a string or a function taking a single argument, which will be called with the media-type.

manifest_items_with_property(property_name)[zdroj]

All manifest items that have the specified property

property manifest_type_map

Mapping of manifest media-type to list of canonical names of that media-type

property mi

The metadata of this book as a Metadata object. Note that this object is constructed on the fly every time this property is requested, so use it sparingly.

name_to_abspath(name)[zdroj]

Convert a canonical name to an absolute OS dependent path

name_to_href(name, base=None)[zdroj]

Convert a name to a href relative to base, which must be a name or
None in which case self.root is used as the base

property names_that_must_not_be_changed

Set of names that must never be renamed. Depends on the e-book file format.

property names_that_must_not_be_removed

Set of names that must never be deleted from the container. Depends on the e-book file format.

property names_that_need_not_be_manifested

Set of names that are allowed to be missing from the manifest. Depends on the e-book file format.

open(name, mode='rb')[zdroj]

Open the file pointed to by name for direct read/write. Note that this will commit the file if it is dirtied and remove it from the parse cache. You must finish with this file before accessing the parsed version of it again, or bad things will happen.

property opf

The parsed OPF file

opf_get_or_create(name)[zdroj]

Convenience method to either return the first XML element with the specified name or create it under the opf:package element and then return it, if it does not already exist.

property opf_version

The version set on the OPF’s <package> element

property opf_version_parsed

The version set on the OPF’s <package> element as a tuple of integers

opf_xpath(expr)[zdroj]

Convenience method to evaluate an XPath expression on the OPF file, has the opf: and dc: namespace prefixes pre-defined.

parsed(name)[zdroj]

Return a parsed representation of the file specified by name. For
HTML and XML files an lxml tree is returned. For CSS files a css_parser stylesheet is returned. Note that parsed objects are cached for performance. If you make any changes to the parsed object, you must call dirty() so that the container knows to update the cache. See also replace().

raw_data(name, decode=True, normalize_to_nfc=True)[zdroj]

Return the raw data corresponding to the file specified by name

Parametry:

- decode – If True and the file has a text based MIME type, decode it and return a unicode object instead of raw bytes.

- normalize_to_nfc – If True the returned unicode object is normalized to the NFC normal form as is required for the EPUB and AZW3 file formats.

relpath(path, base=None)[zdroj]

Convert an absolute path (with os separators) to a path relative to base (defaults to self.root). The relative path is not a name. Use abspath_to_name() for that.

remove_from_spine(spine_items, remove_if_no_longer_in_spine=True)[zdroj]

Remove the specified items (by canonical name) from the spine. If remove_if_no_longer_in_spine is True, the items are also deleted from the book, not just from the spine.

remove_from_xml(item)[zdroj]

Removes item from parent, fixing indentation (works only with self closing items)

remove_item(name, remove_from_guide=True)[zdroj]

Remove the item identified by name from this container. This removes all references to the item in the OPF manifest, guide and spine as well as from any internal caches.

rename(current_name, new_name)[zdroj]

Renames a file from current_name to new_name. It automatically rebases all links inside the file if the folder the file is in changes. Note however, that links are not updated in the other files that could reference this file. This is for performance, such updates should be done once, in bulk.

replace(name, obj)[zdroj]

Replace the parsed object corresponding to name with obj, which must be a similar object, i.e. an lxml tree for HTML/XML or a css_parser stylesheet for a CSS file.

replace_links(name, replace_func)[zdroj]

Replace all links in name using replace_func, which must be a callable that accepts a URL and returns the replaced URL. It must also have a ‚replaced‘ attribute that is set to True if any actual replacement is done. Convenient ways of creating such callables are using the LinkReplacer and LinkRebaser classes.

serialize_item(name)[zdroj]

Convert a parsed object (identified by canonical name) into a bytestring. See parsed().

set_spine(spine_items)[zdroj]

Set the spine to be spine_items where spine_items is an iterable of the form (name, linear). Will raise an error if one of the names is not present in the manifest.

property spine_items

An iterator yielding the path for every item in the books‘ spine. See also: spine_iter and spine_items.

property spine_iter

An iterator that yields item, name is_linear for every item in the books‘ spine. item is the lxml element, name is the canonical file name and is_linear is True if the item is linear. See also: spine_names and spine_items.

property spine_names

An iterator yielding name and is_linear for every item in the books‘ spine. See also: spine_iter and spine_items.

Správa souborů komponent v kontejneru

calibre.ebooks.oeb.polish.replace.replace_links(container, link_map, frag_map=<function <lambda>>, replace_in_opf=False)[zdroj]

Replace links to files in the container. Will iterate over all files in the container and change the specified links in them.

Parametry:

- link_map – A mapping of old canonical name to new canonical name. For example: {'images/old.png': 'images/new.png'}

- frag_map – A callable that takes two arguments (name, anchor) and returns a new anchor. This is useful if you need to change the anchors in
HTML files. By default, it does nothing.

- replace_in_opf – If False, links are not replaced in the OPF file.

calibre.ebooks.oeb.polish.replace.rename_files(container, file_map)[zdroj]

Rename files in the container, automatically updating all links to them.

Parametry:

file_map – A mapping of old canonical name to new canonical name, for example: {'text/chapter1.html': 'chapter1.html'}.

calibre.ebooks.oeb.polish.replace.get_recommended_folders(container, names)[zdroj]

Return the folders that are recommended for the given filenames. The recommendation is based on where the majority of files of the same type are located in the container. If no files of a particular type are present, the recommended folder is assumed to be the folder containing the OPF file.

Pěkný tisk a automatická analýza opravy chyb

calibre.ebooks.oeb.polish.pretty.fix_html(container, raw)[zdroj]

Fix any parsing errors in the HTML represented as a string in raw. Fixing is done using the HTML5 parsing algorithm.

calibre.ebooks.oeb.polish.pretty.fix_all_html(container)[zdroj]

Fix any parsing errors in all HTML files in the container. Fixing is done using the HTML5 parsing algorithm.

calibre.ebooks.oeb.polish.pretty.pretty_html(container, name, raw)[zdroj]

Pretty print the HTML represented as a string in raw

calibre.ebooks.oeb.polish.pretty.pretty_css(container, name, raw)[zdroj]

Pretty print the CSS represented as a string in raw

calibre.ebooks.oeb.polish.pretty.pretty_xml(container, name, raw)[zdroj]

Pretty print the XML represented as a string in raw. If name is the name of the OPF, extra OPF-specific prettying is performed.

calibre.ebooks.oeb.polish.pretty.pretty_all(container)[zdroj]

Pretty print all HTML/CSS/XML files in the container

Správa přebalů knih

calibre.ebooks.oeb.polish.jacket.remove_jacket(container)[zdroj]

Remove an existing jacket, if any. Returns False if no existing jacket was found.

calibre.ebooks.oeb.polish.jacket.add_or_replace_jacket(container)[zdroj]

Either create a new jacket from the book’s metadata or replace an existing jacket. Returns True if an existing jacket was replaced.

Rozdělování a slučování souborů

calibre.ebooks.oeb.polish.split.split(container, name, loc_or_xpath, before=True, totals=None)[zdroj]

Split the file specified by name at the position specified by loc_or_xpath.
Splitting automatically migrates all links and references to the affected files.

Parametry:

- loc_or_xpath – Should be an XPath expression such as
//h:div[@id=“split_here“]. Can also be a loc which is used internally to implement splitting in the preview panel.

- before – If True the split occurs before the identified element otherwise after it.

- totals – Used internally

calibre.ebooks.oeb.polish.split.multisplit(container, name, xpath, before=True)[zdroj]

Split the specified file at multiple locations (all tags that match the specified XPath expression). See also: split().
Splitting automatically migrates all links and references to the affected files.

Parametry:

before – If True the splits occur before the identified element otherwise after it.

calibre.ebooks.oeb.polish.split.merge(container, category, names, master)[zdroj]

Merge the specified files into a single file, automatically migrating all links and references to the affected files. The file must all either be HTML or CSS files.

Parametry:

- category – Must be either 'text' for HTML files or 'styles' for CSS files

- names – The list of files to be merged

- master – Which of the merged files is the master file, that is, the file that will remain after merging.

Správa obálek

calibre.ebooks.oeb.polish.cover.set_cover(container, cover_path, report=None, options=None)[zdroj]

Set the cover of the book to the image pointed to by cover_path.

Parametry:

- cover_path – Either the absolute path to an image file or the canonical name of an image in the book. When using an image in the book, you must also set options, see below.

- report – An optional callable that takes a single argument. It will be called with information about the tasks being processed.

- options – None or a dictionary that controls how the cover is set. The dictionary can have entries: keep_aspect: True or False (Preserve aspect ratio of covers in EPUB) no_svg: True or False (Use an SVG cover wrapper in the EPUB titlepage) existing: True or False (cover_path refers to an existing image in the book)

calibre.ebooks.oeb.polish.cover.mark_as_cover(container, name)[zdroj]

Mark the specified image as the cover image.

calibre.ebooks.oeb.polish.cover.mark_as_titlepage(container, name, move_to_start=True)[zdroj]

Mark the specified HTML file as the titlepage of the EPUB.

Parametry:

move_to_start – If True the HTML file is moved to the start of the spine

Práce s CSS

calibre.ebooks.oeb.polish.fonts.change_font(container, old_name, new_name=None)[zdroj]

Change a font family from old_name to new_name. Changes all occurrences of the font family in stylesheets, style tags and style attributes.
If the old_name refers to an embedded font, it is removed. You can set new_name to None to remove the font family instead of changing it.

calibre.ebooks.oeb.polish.css.remove_unused_css(container, report=None, remove_unused_classes=False, merge_rules=False, merge_rules_with_identical_properties=False, remove_unreferenced_sheets=False)[zdroj]

Remove all unused CSS rules from the book. An unused CSS rule is one that does not match any actual content.

Parametry:

- report – An optional callable that takes a single argument. It is called with information about the operations being performed.

- remove_unused_classes – If True, class attributes in the HTML that do not match any CSS rules are also removed.

- merge_rules – If True, rules with identical selectors are merged.

- merge_rules_with_identical_properties – If True, rules with identical properties are merged.

- remove_unreferenced_sheets – If True, stylesheets that are not referenced by any content are removed

calibre.ebooks.oeb.polish.css.filter_css(container, properties, names=())[zdroj]

Remove the specified CSS properties from all CSS rules in the book.

Parametry:

- properties – Set of properties to remove. For example: {'font-family', 'color'}.

- names – The files from which to remove the properties. Defaults to all HTML and CSS files in the book.

Práce s obsahem

calibre.ebooks.oeb.polish.toc.from_xpaths(container, xpaths, prefer_title=False)[zdroj]

Generate a Table of Contents from a list of XPath expressions. Each expression in the list corresponds to a level of the generate ToC. For example: ['//h:h1', '//h:h2', '//h:h3'] will generate a three level
Table of Contents from the <h1>, <h2> and <h3> tags.

calibre.ebooks.oeb.polish.toc.from_links(container)[zdroj]

Generate a Table of Contents from links in the book.

calibre.ebooks.oeb.polish.toc.from_files(container)[zdroj]

Generate a Table of Contents from files in the book.

calibre.ebooks.oeb.polish.toc.create_inline_toc(container, title=None)[zdroj]

Create an inline (HTML) Table of Contents from an existing NCX Table of Contents.

Parametry:

title – The title for this table of contents.

Nástroj na úpravu knih
class calibre.gui2.tweak_book.plugin.Tool[zdroj]

Bases: object

The base class for individual tools in an Edit Book plugin. Useful members include:

- self.plugin: A reference to the calibre.customize.Plugin object to which this tool belongs.

- self. boss

- self. gui

Methods that must be overridden in sub classes:

- create_action()

- register_shortcut()

name = None

Set this to a unique name it will be used as a key

allowed_in_toolbar = True

If True the user can choose to place this tool in the plugins toolbar

allowed_in_menu = True

If True the user can choose to place this tool in the plugins menu

toolbar_button_popup_mode = 'delayed'

The popup mode for the menu (if any) of the toolbar button. Possible values are ‚delayed‘, ‚instant‘, ‚button‘

property boss

The calibre.gui2.tweak_book.boss.Boss object. Used to control the user interface.

property gui

The main window of the user interface

property current_container

Return the current calibre.ebooks.oeb.polish.container.Container object that represents the book being edited.

register_shortcut(qaction, unique_name, default_keys=(), short_text=None, description=None, **extra_data)[zdroj]

Register a keyboard shortcut that will trigger the specified qaction. This keyboard shortcut will become automatically customizable by the user in the Keyboard shortcuts section of the editor preferences.

Parametry:

- qaction – A QAction object, it will be triggered when the configured key combination is pressed by the user.

- unique_name – A unique name for this shortcut/action. It will be used internally, it must not be shared by any other actions in this plugin.

- default_keys – A list of the default keyboard shortcuts. If not specified no default shortcuts will be set. If the shortcuts specified here conflict with either builtin shortcuts or shortcuts from user configuration/other plugins, they will be ignored. In that case, users will have to configure the shortcuts manually via Preferences. For example: default_keys=('Ctrl+J', 'F9').

- short_text – An optional short description of this action. If not specified the text from the QAction will be used.

- description – An optional longer description of this action, it will be used in the preferences entry for this shortcut.

create_action(for_toolbar=True)[zdroj]

Create a QAction that will be added to either the plugins toolbar or the plugins menu depending on for_toolbar. For example:

def create_action(self, for_toolbar=True):
ac = QAction(get_icons('myicon.png'), 'Do something')
if for_toolbar:
# We want the toolbar button to have a popup menu
menu = QMenu()
ac.setMenu(menu)
menu.addAction('Do something else')
subaction = menu.addAction('And another')

# Register a keyboard shortcut for this toolbar action be
# careful to do this for only one of the toolbar action or
# the menu action, not both.
self.register_shortcut(ac, 'some-unique-name', default_keys=('Ctrl+K',))
return ac

Viz také

Method register_shortcut().

Ovládání uživatelského rozhraní editoru

Uživatelské rozhraní editoru e-knih je ovládáno jediným globálním Hlavním objektem. To má mnoho užitečných metod, které lze použít v kódu modulu k provádění běžných úkolů.

class calibre.gui2.tweak_book.boss.Boss(parent, notify=None)[zdroj]

add_savepoint(msg)[zdroj]

Create a restore checkpoint with the name specified as msg

apply_container_update_to_gui(mark_as_modified=True)[zdroj]

Update all the components of the user interface to reflect the latest data in the current book container.

Parametry:

mark_as_modified – If True, the book will be marked as modified, so the user will be prompted to save it when quitting.

close_editor(name)[zdroj]

Close the editor that is editing the file specified by name

commit_all_editors_to_container()[zdroj]

Commit any changes that the user has made to files open in editors to the container. You should call this method before performing any actions on the current container

property currently_editing

Return the name of the file being edited currently or None if no file is being edited

edit_file(name, syntax=None, use_template=None)[zdroj]

Open the file specified by name in an editor

Parametry:

- syntax – The media type of the file, for example, 'text/html'. If not specified it is guessed from the file extension.

- use_template – A template to initialize the opened editor with

open_book(path=None, edit_file=None, clear_notify_data=True, open_folder=False, search_text=None)[zdroj]

Open the e-book at path for editing. Will show an error if the e-book is not in a supported format or the current book has unsaved changes.

Parametry:

edit_file – The name of a file inside the newly opened book to start editing. Can also be a list of names.

rewind_savepoint()[zdroj]

Undo the previous creation of a restore checkpoint, useful if you create a checkpoint, then abort the operation with no changes

save_book()[zdroj]

Save the book. Saving is performed in the background

set_modified()[zdroj]

Mark the book as having been modified

show_current_diff(allow_revert=True, to_container=None)[zdroj]

Show the changes to the book from its last checkpointed state

Parametry:

- allow_revert – If True the diff dialog will have a button to allow the user to revert all changes

- to_container – A container object to compare the current container to. If None, the previously checkpointed container is used

show_editor(name)[zdroj]

Show the editor that is editing the file specified by name

sync_preview_to_editor()[zdroj]

Sync the position of the preview panel to the current cursor position in the current editor

© Copyright Kovid Goyal.
Naposledy aktualizováno kvě 03, 2024.

Navigace

- moduly
- další |
- předchozí |
- Spustit »
- Správa digitálních práv (DRM)

Správa digitálních práv (DRM)

Správa digitálních práv (DRM) je všeobecný pojem pro technologie řízení přístupu, které mohou výrobci hardwaru, vydavatelé, držitelé autorských práv a jednotlivci použít k pokusu o vynucení omezení na používání digitálního obsahu a zařízení. Někdy je také pohrdavě popsán jako Správa digitálních omezení. Pojem se používá k popisu jakékoliv technologie, která brání (právního nebo jiného) použití digitálního obsahu, který poskytovatel obsahu nepožadoval ani nepředvídal. Pojem se obecně nevztahuje na jiné formy ochrany proti kopírování, které lze obejít bez úpravy souboru nebo zařízení, jako jsou sériová čísla nebo soubory klíčů. Může také odkazovat na omezení spojená s konkrétními případy digitálních děl nebo zařízení. Technologie DRM se snaží kontrolovat použití digitálních médií tím, že koncovým uživatelům brání v přístupu, kopírování nebo převodu do jiných formátů. Viz Wikipedia [https://en.wikipedia.org/wiki/Digital_rights_management].

Co z DRM vyplývá pro mě osobně?

Když si koupíte e-knihu s DRM, ve skutečnosti ji nevlastníte, ale zakoupili jste povolení k jejímu používání způsobem, který vám diktuje prodejce. DRM omezuje to, co můžete dělat s e-knihami, které jste „koupili“. Lidé, kteří nakupují knihy s DRM, často nevědí o rozsahu těchto omezení. Tato omezení vám brání v přeformátování e-knihy podle vašich představ, včetně provedení stylistických změn, jako je úprava velikosti písma, i když existuje software, který vám umožňuje dělat takové věci pro knihy bez DRM. Lidé jsou často překvapeni, že e-knihu, kterou zakoupili v určitém formátu, nelze převést na jiný formát, pokud má e-kniha DRM. Takže pokud máte Amazon Kindle a koupíte si knihu od Barnes a Nobles, měli byste vědět, že pokud tato e-kniha má DRM, nebudete ji moci číst na svém Kindle. Všimněte si, že mluvím o knize, kterou kupujete, ne kradete nebo pirátíte, ale KUPUJETE.

Co přináší DRM autorům?

Vydavatelé e-knih chráněných DRM tvrdí, že DRM je v zájmu autorů a chrání jejich uměleckou integritu a zabraňuje pirátství. DRM však NEZABRAŇUJE pirátství. Lidé, kteří chtějí pirátit obsah nebo používají pirátský obsah, to stále dělají a jsou v tom úspěšní. Tři hlavní schémata DRM pro e-knihy dnes provozují Amazon, Adobe a Barnes and Noble a všechna tři schémata DRM byla prolomena. Jediné, co DRM dělá, je to, že přináší nepohodlí oprávněným uživatelům. Lze tvrdit, že to vlastně poškozuje autory, protože lidé, kteří by knihu zakoupili, se rozhodnou najít pirátskou verzi, protože nejsou ochotni se smířit s DRM. Ti, kteří by pirátili i bez DRM, tak činí také v jeho přítomnosti. Znovu opakuji, klíčovým bodem je, že DRM *nezabraňuje pirátství *. DRM tedy není jen zbytečné a škodlivé pro kupující e-knihy, ale také plýtvání penězi.

DRM a svoboda

Přestože digitální obsah může být použit pro snadné zpřístupnění informací a také tvůrčích děl všem a posílení lidskosti, není to v zájmu některých vydavatelů, kteří chtějí lidi odvrátit od této možnosti svobody, jen aby si udrželi svůj význam ve světě, který se vyvíjí tak rychle, že se jim nedaří držet krok.

Proč Calibre nepodporuje DRM?

Calibre je software s otevřeným zdrojovým kódem, zatímco DRM je ze své podstaty uzavřené. Pokud by mělo Calibre podporovat otevírání nebo zobrazování souborů DRM, mohlo by být snadno upraveno pro použití jako nástroje pro odebírání DRM, což je podle dnešních zákonů nezákonné. Software s otevřeným zdrojovým kódem a DRM jsou střetem principů. Zatímco DRM je jen o ovládání uživatele, software s otevřeným zdrojovým kódem je o posílení postavení uživatele. Oba jednoduše nemohou spolu existovat.

Jak Calibre nahlíží na poskytovatele obsahu?

Pevně věříme, že autoři a ostatní poskytovatelé obsahu by měli být za své úsilí odměněni, ale DRM není cestou, jak toho dosáhnout. Tuto databázi e-knih bez DRM vyvíjíme z různých zdrojů, abychom vám pomohli najít alternativy bez DRM a pomohli nezávislým autorům a vydavatelům e-knih bez DRM propagovat jejich obsah. Doufáme, že to shledáte užitečným, a žádáme vás, abyste nepirátili obsah, který je vám zde k dispozici.

Jak mohu pomoci v boji proti DRM?

Jako někdo, kdo čte a kupuje e-knihy, můžete pomoci v boji proti DRM. Nekupujte e-knihy s DRM. Někteří vydavatelé vydávají e-knihy bez DRM. Snažte se zjistit, zda nabízejí e-knihu, kterou hledáte. Pokud máte rádi knihy od některých nezávislých autorů, kteří prodávají e-knihy bez DRM, a můžete si to dovolit, věnujte jim finanční dar. Jsou to dobře vynaložené peníze, protože jejich e-knihy bývají levnější (mohou existovat výjimky), než ty, které byste si koupili od vydavatelů knih chráněných DRM, a pravděpodobně budou v budoucnu fungovat na všech zařízeních, která vlastníte, čímž vám ušetří náklady na nové zakoupení e-knihy. Neodstrašujte vydavatele a autory e-knih bez DRM pirátěním jejich obsahu. Poskytovatelé obsahu si za své úsilí zaslouží odměnu. Netrestejte je za to, že se snaží zlepšit váš zážitek ze čtení zpřístupněním e-knih bez DRM. Z dlouhodobého hlediska je to pro vás škodlivé. Pokud jste si zakoupili knihy od prodejců, kteří nabízejí knihy jak s DRM, tak i bez DRM, aniž byste věděli, zda mají DRM nebo ne, bylo by vhodné zanechat komentář nebo recenzi na webových stránkách, abyste informovali budoucí kupující o stavu DRM. Mnoho prodejců si nemyslí, že je důležité jasně označit svým kupujícím, zda e-kniha obsahuje DRM nebo ne. Zde <https://www.defectivebydesign.org/guide/ebooks> najdete průvodce životem bez DRM.

© Copyright Kovid Goyal.
Naposledy aktualizováno kvě 03, 2024.

Navigace

- moduly
- předchozí |
- Spustit »
- Slovník pojmů

Slovník pojmů

RSS

RSS (Really Simple Syndication) je formát webového informačního kanálu, který se používá k publikování často aktualizovaného obsahu, jako jsou novinové články, příspěvky blogu atd. Je to formát, který je obzvlášť vhodný pro čtení počítači, a je proto upřednostňovaným způsob získávání obsahu z webu do e-knihy. Existuje mnoho dalších formátů informačních kanálů používaných na internetu a calibre rozumí většině z nich. Zejména má dobrou podporu pro formát ATOM, který se obvykle používá pro blogy.

předpis

Předpis je sada instrukcí, která učí calibre, jak převést na e-knihu online zdroje zpráv, jako jsou časopisy nebo blogy. Předpis je v podstatě kód Pythonu [https://www.python.org]. Jako takový je schopný převést na e-knihy libovolně složitý zdroj zpráv. Na nejjednodušší úrovni je to pouze sada proměnných, jako jsou URL, které dávají calibre dostatek informací pro přejití na internet a stažení zpráv.

HTML

HTML (značkovací jazyk pro hypertext = HyperText Markup Language), podmnožina SGML (univerzální značkovací metajazyk = Standard Generalized Markup Language) pro elektronické publikování, je konkrétní standard použití pro web.

CSS

CSS (šablony stylů = Cascading Style Sheets) je jazyk použitý pro popsání, jak by měl být vykreslen dokument HTML (vizuální stylování).

API

API (rozhraní pro programování aplikací = Application Programming Interface) je rozhraní zdrojového kódu, které knihovna poskytuje pro podporu požadavků na služby, které na něj mají být provedeny počítačovými programy.

LRF

LRF Formát e-knihy, který je čten čtečkami e-knih SONY.

URL

URL (jednotná adresa zdroje = Uniform Resource Locator) například: http://example.com

regexp

Regulární výrazy poskytují stručný a pružný prostředek pro identifikaci řetězců zkoumaného textu, jako jsou určité znaky, slova nebo vzory znaků. Úvod do regulárních výrazů naleznete na the tutorial.

© Copyright Kovid Goyal.
Naposledy aktualizováno kvě 03, 2024.

Navigace

- moduly
- Spustit »
- Rejstřík modulů Pythonu

Rejstřík modulů Pythonu
c

| c |
| calibre |
| calibre.customize | Defines various abstract base classes that can be subclassed to create plugins.
| calibre.customize.conversion |
| calibre.db.cache | The API accessing and manipulating a calibre library.
| calibre.devices.interface |
| calibre.ebooks.metadata.book.base |
| calibre.ebooks.metadata.sources.base |
| calibre.ebooks.oeb.polish.container | The container object used to represent a book as a collection of its constituent HTML files.
| calibre.ebooks.oeb.polish.cover |
| calibre.ebooks.oeb.polish.css |
| calibre.ebooks.oeb.polish.jacket |
| calibre.ebooks.oeb.polish.pretty |
| calibre.ebooks.oeb.polish.replace |
| calibre.ebooks.oeb.polish.split |
| calibre.ebooks.oeb.polish.toc |
| calibre.gui2.tweak_book.boss |
| calibre.utils.formatter_functions |
| calibre.web.feeds.news | The API for writing recipes is defined by the :class:`BasicNewsRecipe`

© Copyright Kovid Goyal.
Naposledy aktualizováno kvě 03, 2024.

Navigace

- moduly
- Spustit »
- Přehled: kód modulu

Všechny moduly s dostupným kódem

- PyQt6.QtCore
- PyQt6.QtWidgets
- calibre.customize

- calibre.customize.conversion

- calibre.db.cache
- calibre.devices.interface
- calibre.devices.usbms.cli
- calibre.devices.usbms.device
- calibre.devices.usbms.driver
- calibre.ebooks.metadata.book.base
- calibre.ebooks.metadata.sources.base
- calibre.ebooks.oeb.polish.container
- calibre.ebooks.oeb.polish.cover
- calibre.ebooks.oeb.polish.css
- calibre.ebooks.oeb.polish.fonts
- calibre.ebooks.oeb.polish.jacket
- calibre.ebooks.oeb.polish.pretty
- calibre.ebooks.oeb.polish.replace
- calibre.ebooks.oeb.polish.split
- calibre.ebooks.oeb.polish.toc
- calibre.gui2.actions
- calibre.gui2.preferences
- calibre.gui2.tweak_book.boss
- calibre.gui2.tweak_book.plugin
- calibre.utils.formatter_functions
- calibre.web.feeds.news

© Copyright Kovid Goyal.
Naposledy aktualizováno kvě 03, 2024.

Navigace

- moduly
- Spustit »
- Kód modulu »
- calibre.customize

Zdrojový kód pro calibre.customize

__license__ = 'GPL v3'
__copyright__ = '2008, Kovid Goyal <kovid at kovidgoyal.net>'

import enum import importlib import os import sys import zipfile from calibre.constants import ismacos, iswindows, numeric_version from calibre.ptempfile import PersistentTemporaryFile if iswindows:
platform = 'windows'
elif ismacos:
platform = 'osx'
else:
platform = 'linux'

class PluginNotFound(ValueError):
pass class InvalidPlugin(ValueError):
pass class PluginInstallationType(enum.IntEnum):
EXTERNAL = 1
SYSTEM = 2
BUILTIN = 3

[dokumentace]
class Plugin: # {{{
'''
A calibre plugin. Useful members include:

* ``self.installation_type``: Stores how the plugin was installed.
* ``self.plugin_path``: Stores path to the ZIP file that contains this plugin or None if it is a builtin plugin
* ``self.site_customization``: Stores a customization string entered by the user.

Methods that should be overridden in sub classes:

* :meth:`initialize`
* :meth:`customization_help`

Useful methods:

* :meth:`temporary_file`
* :meth:`__enter__`
* :meth:`load_resources`

'''
#: List of platforms this plugin works on.
#: For example: ``['windows', 'osx', 'linux']`` supported_platforms = []

#: The name of this plugin. You must set it something other
#: than Trivial Plugin for it to work.
name = 'Trivial Plugin'

#: The version of this plugin as a 3-tuple (major, minor, revision)
version = (1, 0, 0)

#: A short string describing what this plugin does description = _('Does absolutely nothing')

#: The author of this plugin author = _('Unknown')

#: When more than one plugin exists for a filetype,
#: the plugins are run in order of decreasing priority.
#: Plugins with higher priority will be run first.
#: The highest possible priority is ``sys.maxsize``.
#: Default priority is 1.
priority = 1

#: The earliest version of calibre this plugin requires minimum_calibre_version = (0, 4, 118)

#: The way this plugin is installed installation_type = None

#: If False, the user will not be able to disable this plugin. Use with
#: care.
can_be_disabled = True

#: The type of this plugin. Used for categorizing plugins in the
#: GUI type = _('Base')

def __init__(self, plugin_path):
self.plugin_path = plugin_path self.site_customization = None

[dokumentace]
def initialize(self):
'''
Called once when calibre plugins are initialized. Plugins are re-initialized every time a new plugin is added. Also note that if the plugin is run in a worker process, such as for adding books, then the plugin will be initialized for every new worker process.

Perform any plugin specific initialization here, such as extracting resources from the plugin ZIP file. The path to the ZIP file is available as ``self.plugin_path``.

Note that ``self.site_customization`` is **not** available at this point.
'''
pass

[dokumentace]
def config_widget(self):
'''
Implement this method and :meth:`save_settings` in your plugin to use a custom configuration dialog, rather then relying on the simple string based default customization.

This method, if implemented, must return a QWidget. The widget can have an optional method validate() that takes no arguments and is called immediately after the user clicks OK. Changes are applied if and only if the method returns True.

If for some reason you cannot perform the configuration at this time, return a tuple of two strings (message, details), these will be displayed as a warning dialog to the user and the process will be aborted.
'''
raise NotImplementedError()

[dokumentace]
def save_settings(self, config_widget):
'''
Save the settings specified by the user with config_widget.

:param config_widget: The widget returned by :meth:`config_widget`.

'''
raise NotImplementedError()

[dokumentace]
def do_user_config(self, parent=None):
'''
This method shows a configuration dialog for this plugin. It returns
True if the user clicks OK, False otherwise. The changes are automatically applied.
'''
from qt.core import QApplication, QDialog, QDialogButtonBox, QLabel, QLineEdit, QScrollArea, QSize, Qt, QVBoxLayout from calibre.gui2 import gprefs prefname = 'plugin config dialog:'+self.type + ':' + self.name config_dialog = QDialog(parent)
button_box = QDialogButtonBox(QDialogButtonBox.StandardButton.Ok | QDialogButtonBox.StandardButton.Cancel)
v = QVBoxLayout(config_dialog)
button_box.accepted.connect(config_dialog.accept)
button_box.rejected.connect(config_dialog.reject)
config_dialog.setWindowTitle(_('Customize') + ' ' + self.name)
try:
config_widget = self.config_widget()
except NotImplementedError:
config_widget = None if isinstance(config_widget, tuple):
from calibre.gui2 import warning_dialog warning_dialog(parent, _('Cannot configure'), config_widget[0], det_msg=config_widget[1], show=True)
return False if config_widget is not None:
class SA(QScrollArea):
def sizeHint(self):
sz = self.widget().sizeHint()
fw = 2 * self.frameWidth()
return QSize(sz.width() + self.verticalScrollBar().sizeHint().width() + fw, sz.height() + fw)
sa = SA(config_dialog)
sa.setWidget(config_widget)
sa.setWidgetResizable(True)
v.addWidget(sa)
v.addWidget(button_box)
if not config_dialog.restore_geometry(gprefs, prefname):
QApplication.instance().ensure_window_on_screen(config_dialog)
config_dialog.exec()

if config_dialog.result() == QDialog.DialogCode.Accepted:
if hasattr(config_widget, 'validate'):
if config_widget.validate():
self.save_settings(config_widget)
else:
self.save_settings(config_widget)
else:
from calibre.customize.ui import customize_plugin, plugin_customization help_text = self.customization_help(gui=True)
help_text = QLabel(help_text, config_dialog)
help_text.setWordWrap(True)
help_text.setTextInteractionFlags(Qt.TextInteractionFlag.LinksAccessibleByMouse | Qt.TextInteractionFlag.LinksAccessibleByKeyboard)
help_text.setOpenExternalLinks(True)
v.addWidget(help_text)
sc = plugin_customization(self)
if not sc:
sc = ''
sc = sc.strip()
sc = QLineEdit(sc, config_dialog)
v.addWidget(sc)
v.addWidget(button_box)
config_dialog.restore_geometry(gprefs, prefname)
config_dialog.exec()

if config_dialog.result() == QDialog.DialogCode.Accepted:
sc = str(sc.text()).strip()
customize_plugin(self, sc)

config_dialog.save_geometry(gprefs, prefname)
return config_dialog.result()

[dokumentace]
def load_resources(self, names):
'''
If this plugin comes in a ZIP file (user added plugin), this method will allow you to load resources from the ZIP file.

For example to load an image::

pixmap = QPixmap()
pixmap.loadFromData(self.load_resources(['images/icon.png'])['images/icon.png'])
icon = QIcon(pixmap)

:param names: List of paths to resources in the ZIP file using / as separator

:return: A dictionary of the form ``{name: file_contents}``. Any names that were not found in the ZIP file will not be present in the dictionary.

'''
if self.plugin_path is None:
raise ValueError('This plugin was not loaded from a ZIP file')
ans = {}
with zipfile.ZipFile(self.plugin_path, 'r') as zf:
for candidate in zf.namelist():
if candidate in names:
ans[candidate] = zf.read(candidate)
return ans

[dokumentace]
def customization_help(self, gui=False):
'''
Return a string giving help on how to customize this plugin.
By default raise a :class:`NotImplementedError`, which indicates that the plugin does not require customization.

If you re-implement this method in your subclass, the user will be asked to enter a string as customization for this plugin.
The customization string will be available as
``self.site_customization``.

Site customization could be anything, for example, the path to a needed binary on the user's computer.

:param gui: If True return HTML help, otherwise return plain text help.

'''
raise NotImplementedError()

[dokumentace]
def temporary_file(self, suffix):
'''
Return a file-like object that is a temporary file on the file system.
This file will remain available even after being closed and will only be removed on interpreter shutdown. Use the ``name`` member of the returned object to access the full path to the created temporary file.

:param suffix: The suffix that the temporary file will have.
'''
return PersistentTemporaryFile(suffix)

def is_customizable(self):
try:
self.customization_help()
return True except NotImplementedError:
return False def __enter__(self, *args):
'''
Add this plugin to the python path so that it's contents become directly importable.
Useful when bundling large python libraries into the plugin. Use it like this::
with plugin:
import something
'''
if self.plugin_path is not None:
from importlib.machinery import EXTENSION_SUFFIXES from calibre.utils.zipfile import ZipFile with ZipFile(self.plugin_path) as zf:
extensions = {x.lower() for x in EXTENSION_SUFFIXES}
zip_safe = True for name in zf.namelist():
for q in extensions:
if name.endswith(q):
zip_safe = False break if not zip_safe:
break if zip_safe:
sys.path.append(self.plugin_path)
self.sys_insertion_path = self.plugin_path else:
from calibre.ptempfile import TemporaryDirectory self._sys_insertion_tdir = TemporaryDirectory('plugin_unzip')
self.sys_insertion_path = self._sys_insertion_tdir.__enter__(*args)
zf.extractall(self.sys_insertion_path)
sys.path.append(self.sys_insertion_path)

def __exit__(self, *args):
ip, it = getattr(self, 'sys_insertion_path', None), getattr(self,
'_sys_insertion_tdir', None)
if ip in sys.path:
sys.path.remove(ip)
if hasattr(it, '__exit__'):
it.__exit__(*args)

[dokumentace]
def cli_main(self, args):
'''
This method is the main entry point for your plugins command line interface. It is called when the user does: calibre-debug -r "Plugin
Name". Any arguments passed are present in the args variable.
'''
raise NotImplementedError('The %s plugin has no command line interface'
%self.name)

# }}}

[dokumentace]
class FileTypePlugin(Plugin): # {{{
'''
A plugin that is associated with a particular set of file types.
'''

#: Set of file types for which this plugin should be run.
#: Use '*' for all file types.
#: For example: ``{'lit', 'mobi', 'prc'}`` file_types = set()

#: If True, this plugin is run when books are added
#: to the database on_import = False

#: If True, this plugin is run after books are added
#: to the database. In this case the postimport and postadd
#: methods of the plugin are called.
on_postimport = False

#: If True, this plugin is run after a book is converted.
#: In this case the postconvert method of the plugin is called.
on_postconvert = False

#: If True, this plugin is run after a book file is deleted
#: from the database. In this case the postdelete method of
#: the plugin is called.
on_postdelete = False

#: If True, this plugin is run just before a conversion on_preprocess = False

#: If True, this plugin is run after conversion
#: on the final file produced by the conversion output plugin.
on_postprocess = False type = _('File type')

[dokumentace]
def run(self, path_to_ebook):
'''
Run the plugin. Must be implemented in subclasses.
It should perform whatever modifications are required on the e-book and return the absolute path to the modified e-book. If no modifications are needed, it should return the path to the original e-book. If an error is encountered it should raise an Exception. The default implementation simply return the path to the original e-book. Note that the path to the original file (before any file type plugins are run, is available as self.original_path_to_file).

The modified e-book file should be created with the
:meth:`temporary_file` method.

:param path_to_ebook: Absolute path to the e-book.

:return: Absolute path to the modified e-book.
'''
# Default implementation does nothing return path_to_ebook

[dokumentace]
def postimport(self, book_id, book_format, db):
'''
Called post import, i.e., after the book file has been added to the database. Note that this is different from :meth:`postadd` which is called when the book record is created for the first time. This method is called whenever a new file is added to a book record. It is useful for modifying the book record based on the contents of the newly added file.

:param book_id: Database id of the added book.
:param book_format: The file type of the book that was added.
:param db: Library database.
'''
pass # Default implementation does nothing

[dokumentace]
def postconvert(self, book_id, book_format, db):
'''
Called post conversion, i.e., after the conversion output book file has been added to the database.
Note that it is run after a conversion only, not after a book is added. It is useful for modifying the book record based on the contents of the newly added file.

:param book_id: Database id of the added book.
:param book_format: The file type of the book that was added.
:param db: Library database.
'''
pass # Default implementation does nothing

[dokumentace]
def postdelete(self, book_id, book_format, db):
'''
Called post deletion, i.e., after the book file has been deleted from the database. Note that it is not run when a book record is deleted, only when one or more formats from the book are deleted. It is useful for modifying the book record based on the format of the deleted file.

:param book_id: Database id of the added book.
:param book_format: The file type of the book that was added.
:param db: Library database.
'''
pass # Default implementation does nothing

[dokumentace]
def postadd(self, book_id, fmt_map, db):
'''
Called post add, i.e. after a book has been added to the db. Note that this is different from :meth:`postimport`, which is called after a single book file has been added to a book. postadd() is called only when an entire book record with possibly more than one book file has been created for the first time.
This is useful if you wish to modify the book record in the database when the book is first added to calibre.

:param book_id: Database id of the added book.
:param fmt_map: Map of file format to path from which the file format was added. Note that this might or might not point to an actual existing file, as sometimes files are added as streams. In which case it might be a dummy value or a non-existent path.
:param db: Library database
'''
pass # Default implementation does nothing

# }}}

[dokumentace]
class MetadataReaderPlugin(Plugin): # {{{
'''
A plugin that implements reading metadata from a set of file types.
'''
#: Set of file types for which this plugin should be run.
#: For example: ``set(['lit', 'mobi', 'prc'])`` file_types = set()

supported_platforms = ['windows', 'osx', 'linux']
version = numeric_version author = 'Kovid Goyal'

type = _('Metadata reader')

def __init__(self, *args, **kwargs):
Plugin.__init__(self, *args, **kwargs)
self.quick = False

[dokumentace]
def get_metadata(self, stream, type):
'''
Return metadata for the file represented by stream (a file like object that supports reading). Raise an exception when there is an error with the input data.

:param type: The type of file. Guaranteed to be one of the entries in :attr:`file_types`.
:return: A :class:`calibre.ebooks.metadata.book.Metadata` object
'''
return None

# }}}

[dokumentace]
class MetadataWriterPlugin(Plugin): # {{{
'''
A plugin that implements reading metadata from a set of file types.
'''
#: Set of file types for which this plugin should be run.
#: For example: ``set(['lit', 'mobi', 'prc'])`` file_types = set()

supported_platforms = ['windows', 'osx', 'linux']
version = numeric_version author = 'Kovid Goyal'

type = _('Metadata writer')

def __init__(self, *args, **kwargs):
Plugin.__init__(self, *args, **kwargs)
self.apply_null = False

[dokumentace]
def set_metadata(self, stream, mi, type):
'''
Set metadata for the file represented by stream (a file like object that supports reading). Raise an exception when there is an error with the input data.

:param type: The type of file. Guaranteed to be one of the entries in :attr:`file_types`.
:param mi: A :class:`calibre.ebooks.metadata.book.Metadata` object
'''
pass

# }}}

[dokumentace]
class CatalogPlugin(Plugin): # {{{
'''
A plugin that implements a catalog generator.
'''

resources_path = None

#: Output file type for which this plugin should be run.
#: For example: 'epub' or 'xml'
file_types = set()

type = _('Catalog generator')

#: CLI parser options specific to this plugin, declared as `namedtuple` `Option`:
#:
#: from collections import namedtuple
#: Option = namedtuple('Option', 'option, default, dest, help')
#: cli_options = [Option('--catalog-title', default = 'My Catalog',
#: dest = 'catalog_title', help = (_('Title of generated catalog. \nDefault:') + " '" + '%default' + "'"))]
#: cli_options parsed in calibre.db.cli.cmd_catalog:option_parser()
#:
cli_options = []

def _field_sorter(self, key):
'''
Custom fields sort after standard fields
'''
if key.startswith('#'):
return '~%s' % key[1:]
else:
return key def search_sort_db(self, db, opts):

db.search(opts.search_text)

if getattr(opts, 'sort_by', None):
# 2nd arg = ascending db.sort(opts.sort_by, True)
return db.get_data_as_dict(ids=opts.ids)

def get_output_fields(self, db, opts):
# Return a list of requested fields all_std_fields = {'author_sort','authors','comments','cover','formats',
'id','isbn','library_name','ondevice','pubdate','publisher',
'rating','series_index','series','size','tags','timestamp',
'title_sort','title','uuid','languages','identifiers'}
all_custom_fields = set(db.custom_field_keys())
for field in list(all_custom_fields):
fm = db.field_metadata[field]
if fm['datatype'] == 'series':
all_custom_fields.add(field+'_index')
all_fields = all_std_fields.union(all_custom_fields)

if getattr(opts, 'fields', 'all') != 'all':
# Make a list from opts.fields of = [x.strip() for x in opts.fields.split(',')]
requested_fields = set(of)

# Validate requested_fields if requested_fields - all_fields:
from calibre.library import current_library_name invalid_fields = sorted(list(requested_fields - all_fields))
print("invalid --fields specified: %s" % ', '.join(invalid_fields))
print("available fields in '%s': %s" %
(current_library_name(), ', '.join(sorted(list(all_fields)))))
raise ValueError("unable to generate catalog with specified fields")

fields = [x for x in of if x in all_fields]
else:
fields = sorted(all_fields, key=self._field_sorter)

if not opts.connected_device['is_device_connected'] and 'ondevice' in fields:
fields.pop(int(fields.index('ondevice')))

return fields

[dokumentace]
def initialize(self):
'''
If plugin is not a built-in, copy the plugin's .ui and .py files from the ZIP file to $TMPDIR.
Tab will be dynamically generated and added to the Catalog Options dialog in calibre.gui2.dialogs.catalog.py:Catalog
'''
from calibre.customize.builtins import plugins as builtin_plugins from calibre.customize.ui import config from calibre.ptempfile import PersistentTemporaryDirectory if type(self) not in builtin_plugins and self.name not in config['disabled_plugins']:
files_to_copy = [f"{self.name.lower()}.{ext}" for ext in ["ui","py"]]
resources = zipfile.ZipFile(self.plugin_path,'r')

if self.resources_path is None:
self.resources_path = PersistentTemporaryDirectory('_plugin_resources', prefix='')

for file in files_to_copy:
try:
resources.extract(file, self.resources_path)
except:
print(f" customize:__init__.initialize(): {file} not found in {os.path.basename(self.plugin_path)}")
continue resources.close()

[dokumentace]
def run(self, path_to_output, opts, db, ids, notification=None):
'''
Run the plugin. Must be implemented in subclasses.
It should generate the catalog in the format specified in file_types, returning the absolute path to the generated catalog file. If an error is encountered it should raise an Exception.

The generated catalog file should be created with the
:meth:`temporary_file` method.

:param path_to_output: Absolute path to the generated catalog file.
:param opts: A dictionary of keyword arguments
:param db: A LibraryDatabase2 object
'''
# Default implementation does nothing raise NotImplementedError('CatalogPlugin.generate_catalog() default '
'method, should be overridden in subclass')

# }}}

[dokumentace]
class InterfaceActionBase(Plugin): # {{{ supported_platforms = ['windows', 'osx', 'linux']
author = 'Kovid Goyal'
type = _('User interface action')
can_be_disabled = False actual_plugin = None def __init__(self, *args, **kwargs):
Plugin.__init__(self, *args, **kwargs)
self.actual_plugin_ = None

[dokumentace]
def load_actual_plugin(self, gui):
'''
This method must return the actual interface action plugin object.
'''
ac = self.actual_plugin_ if ac is None:
mod, cls = self.actual_plugin.split(':')
ac = getattr(importlib.import_module(mod), cls)(gui, self.site_customization)
self.actual_plugin_ = ac return ac

# }}}

[dokumentace]
class PreferencesPlugin(Plugin): # {{{

'''
A plugin representing a widget displayed in the Preferences dialog.

This plugin has only one important method :meth:`create_widget`. The various fields of the plugin control how it is categorized in the UI.
'''

supported_platforms = ['windows', 'osx', 'linux']
author = 'Kovid Goyal'
type = _('Preferences')
can_be_disabled = False

#: Import path to module that contains a class named ConfigWidget
#: which implements the ConfigWidgetInterface. Used by
#: :meth:`create_widget`.
config_widget = None

#: Where in the list of categories the :attr:`category` of this plugin should be.
category_order = 100

#: Where in the list of names in a category, the :attr:`gui_name` of this
#: plugin should be name_order = 100

#: The category this plugin should be in category = None

#: The category name displayed to the user for this plugin gui_category = None

#: The name displayed to the user for this plugin gui_name = None

#: The icon for this plugin, should be an absolute path icon = None

#: The description used for tooltips and the like description = None

[dokumentace]
def create_widget(self, parent=None):
'''
Create and return the actual Qt widget used for setting this group of preferences. The widget must implement the
:class:`calibre.gui2.preferences.ConfigWidgetInterface`.

The default implementation uses :attr:`config_widget` to instantiate the widget.
'''
base, _, wc = self.config_widget.partition(':')
if not wc:
wc = 'ConfigWidget'
base = importlib.import_module(base)
widget = getattr(base, wc)
return widget(parent)

# }}}

class StoreBase(Plugin): # {{{ supported_platforms = ['windows', 'osx', 'linux']
author = 'John Schember'
type = _('Store')
# Information about the store. Should be in the primary language
# of the store. This should not be translatable when set by
# a subclass.
description = _('An e-book store.')
minimum_calibre_version = (0, 8, 0)
version = (1, 0, 1)

actual_plugin = None

# Does the store only distribute e-books without DRM.
drm_free_only = False
# This is the 2 letter country code for the corporate
# headquarters of the store.
headquarters = ''
# All formats the store distributes e-books in.
formats = []
# Is this store on an affiliate program?
affiliate = False def load_actual_plugin(self, gui):
'''
This method must return the actual interface action plugin object.
'''
mod, cls = self.actual_plugin.split(':')
self.actual_plugin_object = getattr(importlib.import_module(mod), cls)(gui, self.name)
return self.actual_plugin_object def customization_help(self, gui=False):
if getattr(self, 'actual_plugin_object', None) is not None:
return self.actual_plugin_object.customization_help(gui)
raise NotImplementedError()

def config_widget(self):
if getattr(self, 'actual_plugin_object', None) is not None:
return self.actual_plugin_object.config_widget()
raise NotImplementedError()

def save_settings(self, config_widget):
if getattr(self, 'actual_plugin_object', None) is not None:
return self.actual_plugin_object.save_settings(config_widget)
raise NotImplementedError()

# }}}

class EditBookToolPlugin(Plugin): # {{{ type = _('Edit book tool')
minimum_calibre_version = (1, 46, 0)

# }}}

class LibraryClosedPlugin(Plugin): # {{{
'''
LibraryClosedPlugins are run when a library is closed, either at shutdown, when the library is changed, or when a library is used in some other way.
At the moment these plugins won't be called by the CLI functions.
'''
type = _('Library closed')

# minimum version 2.54 because that is when support was added minimum_calibre_version = (2, 54, 0)

def run(self, db):
'''
The db will be a reference to the new_api (db.cache.py).

The plugin must run to completion. It must not use the GUI, threads, or any signals.
'''
raise NotImplementedError('LibraryClosedPlugin '
'run method must be overridden in subclass')
# }}}

© Copyright Kovid Goyal.
Naposledy aktualizováno kvě 03, 2024.

Navigace

- moduly
- Spustit »
- Kód modulu »
- calibre.customize »
- calibre.customize.conversion

Zdrojový kód pro calibre.customize.conversion

'''
Defines the plugin system for conversions.
'''
import numbers import os import re import shutil from calibre import CurrentDir from calibre.customize import Plugin class ConversionOption:

'''
Class representing conversion options
'''

def __init__(self, name=None, help=None, long_switch=None, short_switch=None, choices=None):
self.name = name self.help = help self.long_switch = long_switch self.short_switch = short_switch self.choices = choices if self.long_switch is None:
self.long_switch = self.name.replace('_', '-')

self.validate_parameters()

def validate_parameters(self):
'''
Validate the parameters passed to :meth:`__init__`.
'''
if re.match(r'[a-zA-Z_]([a-zA-Z0-9_])*', self.name) is None:
raise ValueError(self.name + ' is not a valid Python identifier')
if not self.help:
raise ValueError('You must set the help text')

def __hash__(self):
return hash(self.name)

def __eq__(self, other):
return self.name == getattr(other, 'name', other)

def clone(self):
return ConversionOption(name=self.name, help=self.help, long_switch=self.long_switch, short_switch=self.short_switch, choices=self.choices)

class OptionRecommendation:
LOW = 1
MED = 2
HIGH = 3 def __init__(self, recommended_value=None, level=LOW, **kwargs):
'''
An option recommendation. That is, an option as well as its recommended value and the level of the recommendation.
'''
self.level = level self.recommended_value = recommended_value self.option = kwargs.pop('option', None)
if self.option is None:
self.option = ConversionOption(**kwargs)

self.validate_parameters()

@property def help(self):
return self.option.help def clone(self):
return OptionRecommendation(recommended_value=self.recommended_value, level=self.level, option=self.option.clone())

def validate_parameters(self):
if self.option.choices and self.recommended_value not in \ self.option.choices:
raise ValueError('OpRec: %s: Recommended value not in choices'% self.option.name)
if not (isinstance(self.recommended_value, (numbers.Number, bytes, str)) or self.recommended_value is None):
raise ValueError('OpRec: %s:'%self.option.name + repr( self.recommended_value) + ' is not a string or a number')

class DummyReporter:

def __init__(self):
self.cancel_requested = False def __call__(self, percent, msg=''):
pass def gui_configuration_widget(name, parent, get_option_by_name, get_option_help, db, book_id, for_output=True):
import importlib def widget_factory(cls):
return cls(parent, get_option_by_name, get_option_help, db, book_id)

if for_output:
try:
output_widget = importlib.import_module(
'calibre.gui2.convert.'+name)
pw = output_widget.PluginWidget pw.ICON = 'back.png'
pw.HELP = _('Options specific to the output format.')
return widget_factory(pw)
except ImportError:
pass else:
try:
input_widget = importlib.import_module(
'calibre.gui2.convert.'+name)
pw = input_widget.PluginWidget pw.ICON = 'forward.png'
pw.HELP = _('Options specific to the input format.')
return widget_factory(pw)
except ImportError:
pass return None

[dokumentace]
class InputFormatPlugin(Plugin):

'''
InputFormatPlugins are responsible for converting a document into
HTML+OPF+CSS+etc.
The results of the conversion *must* be encoded in UTF-8.
The main action happens in :meth:`convert`.
'''

type = _('Conversion input')
can_be_disabled = False supported_platforms = ['windows', 'osx', 'linux']
commit_name = None # unique name under which options for this plugin are saved ui_data = None

#: Set of file types for which this plugin should be run
#: For example: ``set(['azw', 'mobi', 'prc'])`` file_types = set()

#: If True, this input plugin generates a collection of images,
#: one per HTML file. This can be set dynamically, in the convert method
#: if the input files can be both image collections and non-image collections.
#: If you set this to True, you must implement the get_images() method that returns
#: a list of images.
is_image_collection = False

#: Number of CPU cores used by this plugin.
#: A value of -1 means that it uses all available cores core_usage = 1

#: If set to True, the input plugin will perform special processing
#: to make its output suitable for viewing for_viewer = False

#: The encoding that this input plugin creates files in. A value of
#: None means that the encoding is undefined and must be
#: detected individually output_encoding = 'utf-8'

#: Options shared by all Input format plugins. Do not override
#: in sub-classes. Use :attr:`options` instead. Every option must be an
#: instance of :class:`OptionRecommendation`.
common_options = {
OptionRecommendation(name='input_encoding', recommended_value=None, level=OptionRecommendation.LOW, help=_('Specify the character encoding of the input document. If '
'set this option will override any encoding declared by the '
'document itself. Particularly useful for documents that '
'do not declare an encoding or that have erroneous '
'encoding declarations.')
)}

#: Options to customize the behavior of this plugin. Every option must be an
#: instance of :class:`OptionRecommendation`.
options = set()

#: A set of 3-tuples of the form
#: (option_name, recommended_value, recommendation_level)
recommendations = set()

def __init__(self, *args):
Plugin.__init__(self, *args)
self.report_progress = DummyReporter()

[dokumentace]
def get_images(self):
'''
Return a list of absolute paths to the images, if this input plugin represents an image collection. The list of images is in the same order as the spine and the TOC.
'''
raise NotImplementedError()

[dokumentace]
def convert(self, stream, options, file_ext, log, accelerators):
'''
This method must be implemented in sub-classes. It must return the path to the created OPF file or an :class:`OEBBook` instance.
All output should be contained in the current folder.
If this plugin creates files outside the current folder they must be deleted/marked for deletion before this method returns.

:param stream: A file like object that contains the input file.
:param options: Options to customize the conversion process.
Guaranteed to have attributes corresponding to all the options declared by this plugin. In addition, it will have a verbose attribute that takes integral values from zero upwards. Higher numbers mean be more verbose. Another useful attribute is
``input_profile`` that is an instance of
:class:`calibre.customize.profiles.InputProfile`.
:param file_ext: The extension (without the .) of the input file. It is guaranteed to be one of the `file_types` supported by this plugin.
:param log: A :class:`calibre.utils.logging.Log` object. All output should use this object.
:param accelarators: A dictionary of various information that the input plugin can get easily that would speed up the subsequent stages of the conversion.

'''
raise NotImplementedError()

def __call__(self, stream, options, file_ext, log, accelerators, output_dir):
try:
log('InputFormatPlugin: %s running'%self.name)
if hasattr(stream, 'name'):
log('on', stream.name)
except:
# In case stdout is broken pass with CurrentDir(output_dir):
for x in os.listdir('.'):
shutil.rmtree(x) if os.path.isdir(x) else os.remove(x)

ret = self.convert(stream, options, file_ext, log, accelerators)

return ret

[dokumentace]
def postprocess_book(self, oeb, opts, log):
'''
Called to allow the input plugin to perform postprocessing after the book has been parsed.
'''
pass

[dokumentace]
def specialize(self, oeb, opts, log, output_fmt):
'''
Called to allow the input plugin to specialize the parsed book for a particular output format. Called after postprocess_book and before any transforms are performed on the parsed book.
'''
pass

[dokumentace]
def gui_configuration_widget(self, parent, get_option_by_name, get_option_help, db, book_id=None):
'''
Called to create the widget used for configuring this plugin in the calibre GUI. The widget must be an instance of the PluginWidget class.
See the builtin input plugins for examples.
'''
name = self.name.lower().replace(' ', '_')
return gui_configuration_widget(name, parent, get_option_by_name, get_option_help, db, book_id, for_output=False)

[dokumentace]
class OutputFormatPlugin(Plugin):

'''
OutputFormatPlugins are responsible for converting an OEB document
(OPF+HTML) into an output e-book.

The OEB document can be assumed to be encoded in UTF-8.
The main action happens in :meth:`convert`.
'''

type = _('Conversion output')
can_be_disabled = False supported_platforms = ['windows', 'osx', 'linux']
commit_name = None # unique name under which options for this plugin are saved ui_data = None

#: The file type (extension without leading period) that this
#: plugin outputs file_type = None

#: Options shared by all Input format plugins. Do not override
#: in sub-classes. Use :attr:`options` instead. Every option must be an
#: instance of :class:`OptionRecommendation`.
common_options = {
OptionRecommendation(name='pretty_print', recommended_value=False, level=OptionRecommendation.LOW, help=_('If specified, the output plugin will try to create output '
'that is as human readable as possible. May not have any effect '
'for some output plugins.')
)}

#: Options to customize the behavior of this plugin. Every option must be an
#: instance of :class:`OptionRecommendation`.
options = set()

#: A set of 3-tuples of the form
#: (option_name, recommended_value, recommendation_level)
recommendations = set()

@property def description(self):
return _('Convert e-books to the %s format')%self.file_type.upper()

def __init__(self, *args):
Plugin.__init__(self, *args)
self.report_progress = DummyReporter()

[dokumentace]
def convert(self, oeb_book, output, input_plugin, opts, log):
'''
Render the contents of `oeb_book` (which is an instance of
:class:`calibre.ebooks.oeb.OEBBook`) to the file specified by output.

:param output: Either a file like object or a string. If it is a string it is the path to a folder that may or may not exist. The output plugin should write its output into that folder. If it is a file like object, the output plugin should write its output into the file.
:param input_plugin: The input plugin that was used at the beginning of the conversion pipeline.
:param opts: Conversion options. Guaranteed to have attributes corresponding to the OptionRecommendations of this plugin.
:param log: The logger. Print debug/info messages etc. using this.

'''
raise NotImplementedError()

@property def is_periodical(self):
return self.oeb.metadata.publication_type and \ str(self.oeb.metadata.publication_type[0]).startswith('periodical:')

[dokumentace]
def specialize_options(self, log, opts, input_fmt):
'''
Can be used to change the values of conversion options, as used by the conversion pipeline.
'''
pass

[dokumentace]
def specialize_css_for_output(self, log, opts, item, stylizer):
'''
Can be used to make changes to the CSS during the CSS flattening process.

:param item: The item (HTML file) being processed
:param stylizer: A Stylizer object containing the flattened styles for item. You can get the style for any element by stylizer.style(element).

'''
pass

[dokumentace]
def gui_configuration_widget(self, parent, get_option_by_name, get_option_help, db, book_id=None):
'''
Called to create the widget used for configuring this plugin in the calibre GUI. The widget must be an instance of the PluginWidget class.
See the builtin output plugins for examples.
'''
name = self.name.lower().replace(' ', '_')
return gui_configuration_widget(name, parent, get_option_by_name, get_option_help, db, book_id, for_output=True)

© Copyright Kovid Goyal.
Naposledy aktualizováno kvě 03, 2024.

Navigace

- moduly
- Spustit »
- Kód modulu »
- calibre.db.cache

Zdrojový kód pro calibre.db.cache

#!/usr/bin/env python

__license__ = 'GPL v3'
__copyright__ = '2011, Kovid Goyal <kovid@kovidgoyal.net>'
__docformat__ = 'restructuredtext en'

import hashlib import operator import os import random import shutil import sys import traceback import weakref from collections import defaultdict from collections.abc import MutableSet, Set from functools import partial, wraps from io import DEFAULT_BUFFER_SIZE, BytesIO from queue import Queue from threading import Lock from time import mktime, monotonic, sleep, time from typing import NamedTuple, Optional, Tuple from calibre import as_unicode, detect_ncpus, isbytestring from calibre.constants import iswindows, preferred_encoding from calibre.customize.ui import run_plugins_on_import, run_plugins_on_postadd, run_plugins_on_postdelete, run_plugins_on_postimport from calibre.db import SPOOL_SIZE, _get_next_series_num_for_list from calibre.db.annotations import merge_annotations from calibre.db.categories import get_categories from calibre.db.constants import NOTES_DIR_NAME from calibre.db.errors import NoSuchBook, NoSuchFormat from calibre.db.fields import IDENTITY, InvalidLinkTable, create_field from calibre.db.lazy import FormatMetadata, FormatsList, ProxyMetadata from calibre.db.listeners import EventDispatcher, EventType from calibre.db.locking import DowngradeLockError, LockingError, SafeReadLock, create_locks, try_lock from calibre.db.notes.connect import copy_marked_up_text from calibre.db.search import Search from calibre.db.tables import VirtualTable from calibre.db.utils import type_safe_sort_key_function from calibre.db.write import get_series_values, uniq from calibre.ebooks import check_ebook_format from calibre.ebooks.metadata import author_to_author_sort, string_to_authors from calibre.ebooks.metadata.book.base import Metadata from calibre.ebooks.metadata.opf2 import metadata_to_opf from calibre.ptempfile import PersistentTemporaryFile, SpooledTemporaryFile, base_dir from calibre.utils.config import prefs, tweaks from calibre.utils.date import UNDEFINED_DATE, timestampfromdt, utcnow from calibre.utils.date import now as nowf from calibre.utils.filenames import make_long_path_useable from calibre.utils.icu import lower as icu_lower from calibre.utils.icu import sort_key from calibre.utils.localization import canonicalize_lang from polyglot.builtins import cmp, iteritems, itervalues, string_or_bytes class ExtraFile(NamedTuple):
relpath: str file_path: str stat_result: os.stat_result def api(f):
f.is_cache_api = True return f def read_api(f):
f = api(f)
f.is_read_api = True return f def write_api(f):
f = api(f)
f.is_read_api = False return f def wrap_simple(lock, func):
@wraps(func)
def call_func_with_lock(*args, **kwargs):
try:
with lock:
return func(*args, **kwargs)
except DowngradeLockError:
# We already have an exclusive lock, no need to acquire a shared
# lock. See the safe_read_lock properties' documentation for why
# this is necessary.
return func(*args, **kwargs)
return call_func_with_lock def run_import_plugins(path_or_stream, fmt):
fmt = fmt.lower()
if hasattr(path_or_stream, 'seek'):
path_or_stream.seek(0)
pt = PersistentTemporaryFile('_import_plugin.'+fmt)
shutil.copyfileobj(path_or_stream, pt, 1024**2)
pt.close()
path = pt.name else:
path = path_or_stream return run_plugins_on_import(path, fmt)

def _add_newbook_tag(mi):
tags = prefs['new_book_tags']
if tags:
for tag in [t.strip() for t in tags]:
if tag:
if not mi.tags:
mi.tags = [tag]
elif tag not in mi.tags:
mi.tags.append(tag)

def _add_default_custom_column_values(mi, fm):
cols = fm.custom_field_metadata(include_composites=False)
for cc,col in iteritems(cols):
dv = col['display'].get('default_value', None)
try:
if dv is not None:
if not mi.get_user_metadata(cc, make_copy=False):
mi.set_user_metadata(cc, col)
dt = col['datatype']
if dt == 'datetime' and icu_lower(dv) == 'now':
dv = nowf()
mi.set(cc, dv)
except:
traceback.print_exc()

dynamic_category_preferences = frozenset({'grouped_search_make_user_categories', 'grouped_search_terms', 'user_categories'})

[dokumentace]
class Cache:

'''
An in-memory cache of the metadata.db file from a calibre library.
This class also serves as a threadsafe API for accessing the database.
The in-memory cache is maintained in normal form for maximum performance.

SQLITE is simply used as a way to read and write from metadata.db robustly.
All table reading/sorting/searching/caching logic is re-implemented. This was necessary for maximum performance and flexibility.
'''
EventType = EventType fts_indexing_sleep_time = 4 # seconds def __init__(self, backend, library_database_instance=None):
self.shutting_down = False self.is_doing_rebuild_or_vacuum = False self.backend = backend self.library_database_instance = (None if library_database_instance is None else weakref.ref(library_database_instance))
self.event_dispatcher = EventDispatcher()
self.fields = {}
self.composites = {}
self.read_lock, self.write_lock = create_locks()
self.format_metadata_cache = defaultdict(dict)
self.formatter_template_cache = {}
self.dirtied_cache = {}
self.link_maps_cache = {}
self.extra_files_cache = {}
self.vls_for_books_cache = None self.vls_for_books_lib_in_process = None self.vls_cache_lock = Lock()
self.dirtied_sequence = 0 self.cover_caches = set()
self.clear_search_cache_count = 0

# Implement locking for all simple read/write API methods
# An unlocked version of the method is stored with the name starting
# with a leading underscore. Use the unlocked versions when the lock
# has already been acquired.
for name in dir(self):
func = getattr(self, name)
ira = getattr(func, 'is_read_api', None)
if ira is not None:
# Save original function setattr(self, '_'+name, func)
# Wrap it in a lock lock = self.read_lock if ira else self.write_lock setattr(self, name, wrap_simple(lock, func))

self._search_api = Search(self, 'saved_searches', self.field_metadata.get_search_terms())
self.initialize_dynamic()
self.initialize_fts()

@property def new_api(self):
return self

@property def library_id(self):
return self.backend.library_id

@property def dbpath(self):
return self.backend.dbpath

@property def is_fat_filesystem(self):
return self.backend.is_fat_filesystem

@property def safe_read_lock(self):
''' A safe read lock is a lock that does nothing if the thread already has a write lock, otherwise it acquires a read lock. This is necessary to prevent DowngradeLockErrors, which can happen when updating the search cache in the presence of composite columns. Updating the search cache holds an exclusive lock, but searching a composite column involves reading field values via ProxyMetadata which tries to get a shared lock. There may be other scenarios that trigger this as well.

This property returns a new lock object on every access. This lock object is not recursive (for performance) and must only be used in a with statement as ``with cache.safe_read_lock:`` otherwise bad things will happen.'''
return SafeReadLock(self.read_lock)

@write_api def ensure_has_search_category(self, fail_on_existing=True):
if len(self._search_api.saved_searches.names()) > 0:
self.field_metadata.add_search_category(label='search', name=_('Saved searches'), fail_on_existing=fail_on_existing)

def _initialize_dynamic_categories(self):
# Reconstruct the user categories, putting them into field_metadata fm = self.field_metadata fm.remove_dynamic_categories()
for user_cat in sorted(self._pref('user_categories', {}), key=sort_key):
cat_name = '@' + user_cat # add the '@' to avoid name collision while cat_name:
try:
fm.add_user_category(label=cat_name, name=user_cat)
except ValueError:
break # Can happen since we are removing dots and adding parent categories ourselves cat_name = cat_name.rpartition('.')[0]

# add grouped search term user categories muc = frozenset(self._pref('grouped_search_make_user_categories', []))
for cat in sorted(self._pref('grouped_search_terms', {}), key=sort_key):
if cat in muc:
# There is a chance that these can be duplicates of an existing
# user category. Print the exception and continue.
try:
self.field_metadata.add_user_category(label='@' + cat, name=cat)
except ValueError:
traceback.print_exc()
self._ensure_has_search_category()

self.field_metadata.add_grouped_search_terms( self._pref('grouped_search_terms', {}))
self._refresh_search_locations()

@write_api def initialize_dynamic(self):
self.backend.dirty_books_with_dirtied_annotations()
self.dirtied_cache = {x:i for i, x in enumerate(self.backend.dirtied_books())}
if self.dirtied_cache:
self.dirtied_sequence = max(itervalues(self.dirtied_cache))+1 self._initialize_dynamic_categories()

@write_api def initialize_template_cache(self):
self.formatter_template_cache = {}

@write_api def set_user_template_functions(self, user_template_functions):
self.backend.set_user_template_functions(user_template_functions)

@write_api def clear_composite_caches(self, book_ids=None):
for field in itervalues(self.composites):
field.clear_caches(book_ids=book_ids)

@write_api def clear_search_caches(self, book_ids=None):
self.clear_search_cache_count += 1 self._search_api.update_or_clear(self, book_ids)
self.vls_for_books_cache = None self.vls_for_books_lib_in_process = None

@write_api def clear_extra_files_cache(self, book_id=None):
if book_id is None:
self.extra_files_cache = {}
else:
self.extra_files_cache.pop(book_id, None)

@read_api def last_modified(self):
return self.backend.last_modified()

@write_api def clear_caches(self, book_ids=None, template_cache=True, search_cache=True):
if template_cache:
self._initialize_template_cache() # Clear the formatter template cache for field in itervalues(self.fields):
if hasattr(field, 'clear_caches'):
field.clear_caches(book_ids=book_ids) # Clear the composite cache and ondevice caches if book_ids:
for book_id in book_ids:
self.format_metadata_cache.pop(book_id, None)
else:
self.format_metadata_cache.clear()
if search_cache:
self._clear_search_caches(book_ids)
self._clear_link_map_cache(book_ids)

@write_api def clear_link_map_cache(self, book_ids=None):
if book_ids is None:
self.link_maps_cache = {}
else:
for book in book_ids:
self.link_maps_cache.pop(book, None)

@write_api def reload_from_db(self, clear_caches=True):
if clear_caches:
self._clear_caches()
with self.backend.conn: # Prevent other processes, such as calibredb from interrupting the reload by locking the db self.backend.prefs.load_from_db()
self._search_api.saved_searches.load_from_db()
for field in itervalues(self.fields):
if hasattr(field, 'table'):
field.table.read(self.backend) # Reread data from metadata.db

@property def field_metadata(self):
return self.backend.field_metadata def _get_metadata(self, book_id, get_user_categories=True): # {{{ mi = Metadata(None, template_cache=self.formatter_template_cache)

mi._proxy_metadata = ProxyMetadata(self, book_id, formatter=mi.formatter)

author_ids = self._field_ids_for('authors', book_id)
adata = self._author_data(author_ids)
aut_list = [adata[i] for i in author_ids]
aum = []
aus = {}
for rec in aut_list:
aut = rec['name']
aum.append(aut)
aus[aut] = rec['sort']
mi.title = self._field_for('title', book_id, default_value=_('Unknown'))
mi.authors = aum mi.author_sort = self._field_for('author_sort', book_id, default_value=_('Unknown'))
mi.author_sort_map = aus mi.comments = self._field_for('comments', book_id)
mi.publisher = self._field_for('publisher', book_id)
n = utcnow()
mi.timestamp = self._field_for('timestamp', book_id, default_value=n)
mi.pubdate = self._field_for('pubdate', book_id, default_value=n)
mi.uuid = self._field_for('uuid', book_id, default_value='dummy')
mi.title_sort = self._field_for('sort', book_id, default_value=_('Unknown'))
mi.last_modified = self._field_for('last_modified', book_id, default_value=n)
formats = self._field_for('formats', book_id)
mi.format_metadata = {}
mi.languages = list(self._field_for('languages', book_id))
if not formats:
good_formats = None else:
mi.format_metadata = FormatMetadata(self, book_id, formats)
good_formats = FormatsList(sorted(formats), mi.format_metadata)
# These three attributes are returned by the db2 get_metadata(),
# however, we dont actually use them anywhere other than templates, so
# they have been removed, to avoid unnecessary overhead. The templates
# all use _proxy_metadata.
# mi.book_size = self._field_for('size', book_id, default_value=0)
# mi.ondevice_col = self._field_for('ondevice', book_id, default_value='')
# mi.db_approx_formats = formats mi.formats = good_formats mi.has_cover = _('Yes') if self._field_for('cover', book_id, default_value=False) else ''
mi.tags = list(self._field_for('tags', book_id, default_value=()))
mi.series = self._field_for('series', book_id)
if mi.series:
mi.series_index = self._field_for('series_index', book_id, default_value=1.0)
mi.rating = self._field_for('rating', book_id)
mi.set_identifiers(self._field_for('identifiers', book_id, default_value={}))
mi.application_id = book_id mi.id = book_id for key, meta in self.field_metadata.custom_iteritems():
mi.set_user_metadata(key, meta)
if meta['datatype'] != 'composite':
# composites are evaluated on demand in metadata.book.base
# because their value is None val = self._field_for(key, book_id)
if isinstance(val, tuple):
val = list(val)
extra = self._field_for(key+'_index', book_id)
mi.set(key, val=val, extra=extra)

mi.link_maps = self._get_all_link_maps_for_book(book_id)

user_cat_vals = {}
if get_user_categories:
user_cats = self._pref('user_categories', {})
for ucat in user_cats:
res = []
for name,cat,ign in user_cats[ucat]:
v = mi.get(cat, None)
if isinstance(v, list):
if name in v:
res.append([name,cat])
elif name == v:
res.append([name,cat])
user_cat_vals[ucat] = res mi.user_categories = user_cat_vals return mi
# }}}

[dokumentace]
@api def init(self):
'''
Initialize this cache with data from the backend.
'''
with self.write_lock:
self.backend.read_tables()
bools_are_tristate = self.backend.prefs['bools_are_tristate']

for field, table in iteritems(self.backend.tables):
self.fields[field] = create_field(field, table, bools_are_tristate, self.backend.get_template_functions)
if table.metadata['datatype'] == 'composite':
self.composites[field] = self.fields[field]

self.fields['ondevice'] = create_field('ondevice',
VirtualTable('ondevice'), bools_are_tristate, self.backend.get_template_functions)

for name, field in iteritems(self.fields):
if name[0] == '#' and name.endswith('_index'):
field.series_field = self.fields[name[:-len('_index')]]
self.fields[name[:-len('_index')]].index_field = field elif name == 'series_index':
field.series_field = self.fields['series']
self.fields['series'].index_field = field elif name == 'authors':
field.author_sort_field = self.fields['author_sort']
elif name == 'title':
field.title_sort_field = self.fields['sort']
if self.backend.prefs['update_all_last_mod_dates_on_start']:
self.update_last_modified(self.all_book_ids())
self.backend.prefs.set('update_all_last_mod_dates_on_start', False)# FTS API {{{ def initialize_fts(self):
self.fts_queue_thread = None self.fts_measuring_rate = None self.fts_num_done_since_start = 0 self.fts_job_queue = Queue()
self.fts_indexing_left = self.fts_indexing_total = 0 fts = self.backend.initialize_fts(weakref.ref(self))
if self.is_fts_enabled():
self.start_fts_pool()
return fts def start_fts_pool(self):
from threading import Event, Thread self.fts_dispatch_stop_event = Event()
self.fts_queue_thread = Thread(name='FTSQueue', target=Cache.dispatch_fts_jobs, args=( self.fts_job_queue, self.fts_dispatch_stop_event, weakref.ref(self)), daemon=True)
self.fts_queue_thread.start()
self.backend.fts.pool.initialize()
self.backend.fts.pool.initialized.wait()
self.queue_next_fts_job()

@read_api def is_fts_enabled(self):
return self.backend.fts_enabled

@write_api def fts_start_measuring_rate(self, measure=True):
self.fts_measuring_rate = monotonic() if measure else None self.fts_num_done_since_start = 0 def _update_fts_indexing_numbers(self, job_time=None):
# this is called when new formats are added and when a format is
# indexed, but NOT when books or formats are deleted, so total may not
# be up to date.
nl = self.backend.fts.number_dirtied()
nt = self.backend.get('SELECT COUNT(*) FROM main.data')[0][0] or 0 if not nl:
self._fts_start_measuring_rate(measure=False)
if job_time is not None and self.fts_measuring_rate is not None:
self.fts_num_done_since_start += 1 if (self.fts_indexing_left, self.fts_indexing_total) != (nl, nt) or job_time is not None:
self.fts_indexing_left = nl self.fts_indexing_total = nt self.event_dispatcher(EventType.indexing_progress_changed, *self._fts_indexing_progress())

@read_api def fts_indexing_progress(self):
rate = None if self.fts_measuring_rate is not None and self.fts_num_done_since_start > 4:
rate = self.fts_num_done_since_start / (monotonic() - self.fts_measuring_rate)
return self.fts_indexing_left, self.fts_indexing_total, rate

@write_api def enable_fts(self, enabled=True, start_pool=True):
fts = self.backend.enable_fts(weakref.ref(self) if enabled else None)
if fts and start_pool: # used in the tests self.start_fts_pool()
if not fts and self.fts_queue_thread:
self.fts_job_queue.put(None)
self.fts_queue_thread = None self.fts_job_queue = Queue()
if fts:
self._update_fts_indexing_numbers()
return fts

@write_api def fts_unindex(self, book_id, fmt=None):
self.backend.fts_unindex(book_id, fmt=fmt)

@staticmethod def dispatch_fts_jobs(queue, stop_dispatch, dbref):
from .fts.text import is_fmt_ok def do_one():
self = dbref()
if self is None:
return False start_time = monotonic()
with self.read_lock:
if not self.backend.fts_enabled:
return False book_id, fmt = self.backend.get_next_fts_job()
if book_id is None:
return False path = self._format_abspath(book_id, fmt)
if not path or not is_fmt_ok(fmt):
with self.write_lock:
self.backend.remove_dirty_fts(book_id, fmt)
self._update_fts_indexing_numbers()
return True with self.read_lock, open(path, 'rb') as src, PersistentTemporaryFile(suffix=f'.{fmt.lower()}') as pt:
sz = 0 h = hashlib.sha1()
while True:
chunk = src.read(DEFAULT_BUFFER_SIZE)
if not chunk:
break sz += len(chunk)
h.update(chunk)
pt.write(chunk)
with self.write_lock:
queued = self.backend.queue_fts_job(book_id, fmt, pt.name, sz, h.hexdigest(), start_time)
if not queued: # means a dirtied book was removed from the dirty list because the text has not changed self._update_fts_indexing_numbers(monotonic() - start_time)
return self.backend.fts_has_idle_workers def loop_while_more_available():
self = dbref()
if not self or not self.backend.fts_enabled:
return has_more = True while has_more and not self.shutting_down and self.backend.fts_enabled and not stop_dispatch.is_set():
try:
has_more = do_one()
except Exception:
if self.backend.fts_enabled:
traceback.print_exc()
sleep(self.fts_indexing_sleep_time)

while not getattr(dbref(), 'shutting_down', True):
x = queue.get()
if x is None:
break loop_while_more_available()

@write_api def queue_next_fts_job(self):
if not self.backend.fts_enabled:
return self.fts_job_queue.put(True)
self._update_fts_indexing_numbers()

@write_api def commit_fts_result(self, book_id, fmt, fmt_size, fmt_hash, text, err_msg, start_time):
ans = self.backend.commit_fts_result(book_id, fmt, fmt_size, fmt_hash, text, err_msg)
self._update_fts_indexing_numbers(monotonic() - start_time)
return ans

@write_api def reindex_fts_book(self, book_id, *fmts):
if not self.is_fts_enabled():
return if not fmts:
fmts = self._formats(book_id)
self.backend.reindex_fts_book(book_id, *fmts)
self._queue_next_fts_job()

@api def reindex_fts(self):
if not self.is_fts_enabled():
return with self.write_lock:
self._shutdown_fts()
self._shutdown_fts(stage=2)
with self.write_lock:
self.backend.reindex_fts()
fts = self.initialize_fts()
fts.initialize(self.backend.conn) # ensure fts is pre-initialized needed for the tests self._queue_next_fts_job()
return fts

@write_api def set_fts_num_of_workers(self, num):
existing = self.backend.fts_num_of_workers if num != existing:
self.backend.fts_num_of_workers = num if num > existing:
self._queue_next_fts_job()
return True return False

@write_api def set_fts_speed(self, slow=True):
orig = self.fts_indexing_sleep_time if slow:
self.fts_indexing_sleep_time = Cache.fts_indexing_sleep_time changed = self._set_fts_num_of_workers(1)
else:
self.fts_indexing_sleep_time = 0.1 changed = self._set_fts_num_of_workers(max(1, detect_ncpus()))
changed = changed or orig != self.fts_indexing_sleep_time if changed and self.fts_measuring_rate is not None:
self._fts_start_measuring_rate()
return changed

@write_api # we need to use write locking as SQLITE gives a locked table error if multiple FTS queries are made at the same time def fts_search( self, fts_engine_query, use_stemming=True, highlight_start=None, highlight_end=None, snippet_size=None, restrict_to_book_ids=None, return_text=True, result_type=tuple, process_each_result=None,
):
return result_type(self.backend.fts_search( fts_engine_query, use_stemming=use_stemming, highlight_start=highlight_start, highlight_end=highlight_end, snippet_size=snippet_size, return_text=return_text, restrict_to_book_ids=restrict_to_book_ids, process_each_result=process_each_result,
))

# }}}

# Notes API {{{

[dokumentace]
@read_api def notes_for(self, field, item_id) -> str:
' Return the notes document or an empty string if not found '
return self.backend.notes_for(field, item_id)

[dokumentace]
@read_api def notes_data_for(self, field, item_id) -> str:
' Return all notes data as a dict or None if note does not exist '
return self.backend.notes_data_for(field, item_id)

[dokumentace]
@read_api def get_all_items_that_have_notes(self, field_name=None) -> set[int] | dict[str, set[int]]:
' Return all item_ids for items that have notes in the specified field or all fields if field_name is None '
return self.backend.get_all_items_that_have_notes(field_name)

[dokumentace]
@read_api def field_supports_notes(self, field=None) -> bool:
' Return True iff the specified field supports notes. If field is None return frozenset of all fields that support notes. '
if field is None:
return self.backend.notes.allowed_fields return field in self.backend.notes.allowed_fields

[dokumentace]
@read_api def items_with_notes_in_book(self, book_id: int) -> dict[str, dict[int, str]]:
' Return a dict of field to items that have associated notes for that field for the specified book '
ans = {}
for k in self.backend.notes.allowed_fields:
try:
field = self.fields[k]
except KeyError:
continue v = {}
for item_id in field.ids_for_book(book_id):
if self.backend.notes_for(k, item_id):
v[item_id] = field.table.id_map[item_id]
if v:
ans[k] = v return ans

[dokumentace]
@write_api def set_notes_for(self, field, item_id, doc: str, searchable_text: str = copy_marked_up_text, resource_hashes=(), remove_unused_resources=False) -> int:
'''
Set the notes document. If the searchable text is different from the document, specify it as searchable_text. If the document references resources their hashes must be present in resource_hashes. Set remove_unused_resources to True to cleanup unused resources, note that updating a note automatically cleans up resources pertaining to that note anyway.
'''
return self.backend.set_notes_for(field, item_id, doc, searchable_text, resource_hashes, remove_unused_resources)

[dokumentace]
@write_api def add_notes_resource(self, path_or_stream_or_data, name: str, mtime: float = None) -> int:
' Add the specified resource so it can be referenced by notes and return its content hash '
return self.backend.add_notes_resource(path_or_stream_or_data, name, mtime)

[dokumentace]
@read_api def get_notes_resource(self, resource_hash) -> Optional[dict]:
' Return a dict containing the resource data and name or None if no resource with the specified hash is found '
return self.backend.get_notes_resource(resource_hash)

[dokumentace]
@read_api def notes_resources_used_by(self, field, item_id):
' Return the set of resource hashes of all resources used by the note for the specified item '
return frozenset(self.backend.notes_resources_used_by(field, item_id))

[dokumentace]
@write_api def unretire_note_for(self, field, item_id) -> int:
' Unretire a previously retired note for the specified item. Notes are retired when an item is removed from the database '
return self.backend.unretire_note_for(field, item_id)

[dokumentace]
@read_api def export_note(self, field, item_id) -> str:
' Export the note as a single HTML document with embedded images as data: URLs '
return self.backend.export_note(field, item_id)

[dokumentace]
@write_api def import_note(self, field, item_id, path_to_html_file, path_is_data=False):
' Import a previously exported note or an arbitrary HTML file as the note for the specified item '
if path_is_data:
html = path_to_html_file ctime = mtime = time()
basedir = base_dir()
else:
with open(path_to_html_file, 'rb') as f:
html = f.read()
st = os.stat(f.fileno())
ctime, mtime = st.st_ctime, st.st_mtime basedir = os.path.dirname(os.path.abspath(path_to_html_file))
return self.backend.import_note(field, item_id, html, basedir, ctime, mtime)

[dokumentace]
@write_api # we need to use write locking as SQLITE gives a locked table error if multiple FTS queries are made at the same time def search_notes( self, fts_engine_query='', use_stemming=True, highlight_start=None, highlight_end=None, snippet_size=None, restrict_to_fields=(), return_text=True, result_type=tuple, process_each_result=None, limit=None,
):
' Search the text of notes using an FTS index. If the query is empty return all notes. '
return result_type(self.backend.search_notes( fts_engine_query, use_stemming=use_stemming, highlight_start=highlight_start, highlight_end=highlight_end, snippet_size=snippet_size, return_text=return_text, restrict_to_fields=restrict_to_fields, process_each_result=process_each_result, limit=limit,
))

# }}}

# Cache Layer API {{{

[dokumentace]
@write_api def add_listener(self, event_callback_function, check_already_added=False):
'''
Register a callback function that will be called after certain actions are taken on this database. The function must take three arguments:
(:class:`EventType`, library_id, event_type_specific_data)
'''
self.event_dispatcher.library_id = getattr(self, 'server_library_id', self.library_id)
if check_already_added and event_callback_function in self.event_dispatcher:
return False self.event_dispatcher.add_listener(event_callback_function)
return True

@write_api def remove_listener(self, event_callback_function):
self.event_dispatcher.remove_listener(event_callback_function)

[dokumentace]
@read_api def field_for(self, name, book_id, default_value=None):
'''
Return the value of the field ``name`` for the book identified by ``book_id``. If no such book exists or it has no defined value for the field ``name`` or no such field exists, then
``default_value`` is returned.

``default_value`` is not used for title, title_sort, authors, author_sort and series_index. This is because these always have values in the db.
``default_value`` is used for all custom columns.

The returned value for is_multiple fields are always tuples, even when no values are found (in other words, default_value is ignored). The exception is identifiers for which the returned value is always a dictionary.
The returned tuples are always in link order, that is, the order in which they were created.
'''
if self.composites and name in self.composites:
return self.composite_for(name, book_id, default_value=default_value)
try:
field = self.fields[name]
except KeyError:
return default_value if field.is_multiple:
default_value = field.default_value try:
return field.for_book(book_id, default_value=default_value)
except (KeyError, IndexError):
return default_value

[dokumentace]
@read_api def fast_field_for(self, field_obj, book_id, default_value=None):
' Same as field_for, except that it avoids the extra lookup to get the field object '
if field_obj.is_composite:
return field_obj.get_value_with_cache(book_id, self._get_proxy_metadata)
if field_obj.is_multiple:
default_value = field_obj.default_value try:
return field_obj.for_book(book_id, default_value=default_value)
except (KeyError, IndexError):
return default_value

[dokumentace]
@read_api def all_field_for(self, field, book_ids, default_value=None):
' Same as field_for, except that it operates on multiple books at once '
field_obj = self.fields[field]
return {book_id:self._fast_field_for(field_obj, book_id, default_value=default_value) for book_id in book_ids}

@read_api def composite_for(self, name, book_id, mi=None, default_value=''):
try:
f = self.fields[name]
except KeyError:
return default_value if mi is None:
return f.get_value_with_cache(book_id, self._get_proxy_metadata)
else:
return f._render_composite_with_cache(book_id, mi, mi.formatter, mi.template_cache)

[dokumentace]
@read_api def field_ids_for(self, name, book_id):
'''
Return the ids (as a tuple) for the values that the field ``name`` has on the book identified by ``book_id``. If there are no values, or no such book, or no such field, an empty tuple is returned.
'''
try:
return self.fields[name].ids_for_book(book_id)
except (KeyError, IndexError):
return ()

[dokumentace]
@read_api def books_for_field(self, name, item_id):
'''
Return all the books associated with the item identified by
``item_id``, where the item belongs to the field ``name``.

Returned value is a set of book ids, or the empty set if the item or the field does not exist.
'''
try:
return self.fields[name].books_for(item_id)
except (KeyError, IndexError):
return set()

[dokumentace]
@read_api def all_book_ids(self, type=frozenset):
'''
Frozen set of all known book ids.
'''
return type(self.fields['uuid'].table.book_col_map)

[dokumentace]
@read_api def all_field_ids(self, name):
'''
Frozen set of ids for all values in the field ``name``.
'''
return frozenset(iter(self.fields[name]))

[dokumentace]
@read_api def all_field_names(self, field):
''' Frozen set of all fields names (should only be used for many-one and many-many fields) '''
if field == 'formats':
return frozenset(self.fields[field].table.col_book_map)

try:
return frozenset(self.fields[field].table.id_map.values())
except AttributeError:
raise ValueError('%s is not a many-one or many-many field' % field)

[dokumentace]
@read_api def get_usage_count_by_id(self, field):
''' Return a mapping of id to usage count for all values of the specified field, which must be a many-one or many-many field. '''
try:
return {k:len(v) for k, v in iteritems(self.fields[field].table.col_book_map)}
except AttributeError:
raise ValueError('%s is not a many-one or many-many field' % field)

[dokumentace]
@read_api def get_id_map(self, field):
''' Return a mapping of id numbers to values for the specified field.
The field must be a many-one or many-many field, otherwise a ValueError is raised. '''
try:
return self.fields[field].table.id_map.copy()
except AttributeError:
if field == 'title':
return self.fields[field].table.book_col_map.copy()
raise ValueError('%s is not a many-one or many-many field' % field)

[dokumentace]
@read_api def get_item_name(self, field, item_id):
''' Return the item name for the item specified by item_id in the specified field. See also :meth:`get_id_map`.'''
return self.fields[field].table.id_map[item_id]

[dokumentace]
@read_api def get_item_id(self, field, item_name):
' Return the item id for item_name (case-insensitive) or None if not found '
q = icu_lower(item_name)
try:
for item_id, item_val in self.fields[field].table.id_map.items():
if icu_lower(item_val) == q:
return item_id except KeyError:
return None

[dokumentace]
@read_api def get_item_ids(self, field, item_names):
' Return the item id for item_name (case-insensitive) '
rmap = {icu_lower(v) if isinstance(v, str) else v:k for k, v in iteritems(self.fields[field].table.id_map)}
return {name:rmap.get(icu_lower(name) if isinstance(name, str) else name, None) for name in item_names}

[dokumentace]
@read_api def get_item_name_map(self, field, normalize_func=None):
' Return mapping of item values to ids '
if normalize_func is None:
return {v:k for k, v in self.fields[field].table.id_map.items()}
return {normalize_func(v):k for k, v in self.fields[field].table.id_map.items()}

[dokumentace]
@read_api def author_data(self, author_ids=None):
'''
Return author data as a dictionary with keys: name, sort, link

If no authors with the specified ids are found an empty dictionary is returned. If author_ids is None, data for all authors is returned.
'''
af = self.fields['authors']
if author_ids is None:
return {aid:af.author_data(aid) for aid in af.table.id_map}
return {aid:af.author_data(aid) for aid in author_ids if aid in af.table.id_map}

[dokumentace]
@read_api def format_hash(self, book_id, fmt):
''' Return the hash of the specified format for the specified book. The kind of hash is backend dependent, but is usually SHA-256. '''
try:
name = self.fields['formats'].format_fname(book_id, fmt)
path = self._field_for('path', book_id).replace('/', os.sep)
except:
raise NoSuchFormat('Record %d has no fmt: %s'%(book_id, fmt))
return self.backend.format_hash(book_id, fmt, name, path)

[dokumentace]
@api def format_metadata(self, book_id, fmt, allow_cache=True, update_db=False):
'''
Return the path, size and mtime for the specified format for the specified book.
You should not use path unless you absolutely have to, since accessing it directly breaks the threadsafe guarantees of this API. Instead use the :meth:`copy_format_to` method.

:param allow_cache: If ``True`` cached values are used, otherwise a slow filesystem access is done. The cache values could be out of date if access was performed to the filesystem outside of this API.

:param update_db: If ``True`` The max_size field of the database is updated for this book.
'''
if not fmt:
return {}
fmt = fmt.upper()
# allow_cache and update_db are mutually exclusive. Give priority to update_db if allow_cache and not update_db:
x = self.format_metadata_cache[book_id].get(fmt, None)
if x is not None:
return x with self.safe_read_lock:
try:
name = self.fields['formats'].format_fname(book_id, fmt)
path = self._field_for('path', book_id).replace('/', os.sep)
except:
return {}

ans = {}
if path and name:
ans = self.backend.format_metadata(book_id, fmt, name, path)
self.format_metadata_cache[book_id][fmt] = ans if update_db and 'size' in ans:
with self.write_lock:
max_size = self.fields['formats'].table.update_fmt(book_id, fmt, name, ans['size'], self.backend)
self.fields['size'].table.update_sizes({book_id: max_size})

return ans@read_api def format_files(self, book_id):
field = self.fields['formats']
fmts = field.table.book_col_map.get(book_id, ())
return {fmt:field.format_fname(book_id, fmt) for fmt in fmts}

@read_api def format_db_size(self, book_id, fmt):
field = self.fields['formats']
return field.format_size(book_id, fmt)

[dokumentace]
@read_api def pref(self, name, default=None, namespace=None):
' Return the value for the specified preference or the value specified as ``default`` if the preference is not set. '
if namespace is not None:
return self.backend.prefs.get_namespaced(namespace, name, default)
return self.backend.prefs.get(name, default)

[dokumentace]
@write_api def set_pref(self, name, val, namespace=None):
' Set the specified preference to the specified value. See also :meth:`pref`. '
if namespace is not None:
self.backend.prefs.set_namespaced(namespace, name, val)
return self.backend.prefs.set(name, val)
if name in ('grouped_search_terms', 'virtual_libraries'):
self._clear_search_caches()
if name in dynamic_category_preferences:
self._initialize_dynamic_categories()

[dokumentace]
@api def get_metadata(self, book_id, get_cover=False, get_user_categories=True, cover_as_data=False):
'''
Return metadata for the book identified by book_id as a :class:`calibre.ebooks.metadata.book.base.Metadata` object.
Note that the list of formats is not verified. If get_cover is True, the cover is returned, either a path to temp file as mi.cover or if cover_as_data is True then as mi.cover_data.
'''

# Check if virtual_libraries_for_books rebuilt its cache. If it did then
# we must clear the composite caches so the new data can be taken into
# account. Clearing the caches requires getting a write lock, so it must
# be done outside of the closure of _get_metadata().
composite_cache_needs_to_be_cleared = False with self.safe_read_lock:
vl_cache_was_none = self.vls_for_books_cache is None mi = self._get_metadata(book_id, get_user_categories=get_user_categories)
if vl_cache_was_none and self.vls_for_books_cache is not None:
composite_cache_needs_to_be_cleared = True if composite_cache_needs_to_be_cleared:
try:
self.clear_composite_caches()
except LockingError:
# We can't clear the composite caches because a read lock is set.
# As a consequence the value of a composite column that calls
# virtual_libraries() might be wrong. Oh well. Log and keep running.
print('Couldn\'t get write lock after vls_for_books_cache was loaded', file=sys.stderr)
traceback.print_exc()

if get_cover:
if cover_as_data:
cdata = self.cover(book_id)
if cdata:
mi.cover_data = ('jpeg', cdata)
else:
mi.cover = self.cover(book_id, as_path=True)

return mi

[dokumentace]
@read_api def get_proxy_metadata(self, book_id):
''' Like :meth:`get_metadata` except that it returns a ProxyMetadata object that only reads values from the database on demand. This is much faster than get_metadata when only a small number of fields need to be accessed from the returned metadata object. '''
return ProxyMetadata(self, book_id)

[dokumentace]
@api def cover(self, book_id, as_file=False, as_image=False, as_path=False, as_pixmap=False):
'''
Return the cover image or None. By default, returns the cover as a bytestring.

WARNING: Using as_path will copy the cover to a temp file and return the path to the temp file. You should delete the temp file when you are done with it.

:param as_file: If True return the image as an open file object (a SpooledTemporaryFile)
:param as_image: If True return the image as a QImage object
:param as_pixmap: If True return the image as a QPixmap object
:param as_path: If True return the image as a path pointing to a temporary file
'''
if as_file:
ret = SpooledTemporaryFile(SPOOL_SIZE)
if not self.copy_cover_to(book_id, ret):
ret.close()
return ret.seek(0)
elif as_path:
pt = PersistentTemporaryFile('_dbcover.jpg')
with pt:
if not self.copy_cover_to(book_id, pt):
return ret = pt.name elif as_pixmap or as_image:
from qt.core import QImage, QPixmap ret = QImage() if as_image else QPixmap()
with self.safe_read_lock:
path = self._format_abspath(book_id, '__COVER_INTERNAL__')
if path:
ret.load(path)
else:
buf = BytesIO()
if not self.copy_cover_to(book_id, buf):
return ret = buf.getvalue()
return ret

@read_api def cover_or_cache(self, book_id, timestamp, as_what='bytes'):
try:
path = self._field_for('path', book_id).replace('/', os.sep)
except AttributeError:
return False, None, None return self.backend.cover_or_cache(path, timestamp, as_what)

@read_api def cover_last_modified(self, book_id):
try:
path = self._field_for('path', book_id).replace('/', os.sep)
except AttributeError:
return return self.backend.cover_last_modified(path)

[dokumentace]
@read_api def copy_cover_to(self, book_id, dest, use_hardlink=False, report_file_size=None):
'''
Copy the cover to the file like object ``dest``. Returns False if no cover exists or dest is the same file as the current cover.
dest can also be a path in which case the cover is copied to it if and only if the path is different from the current path (taking case sensitivity into account).
'''
try:
path = self._field_for('path', book_id).replace('/', os.sep)
except AttributeError:
return False return self.backend.copy_cover_to(path, dest, use_hardlink=use_hardlink, report_file_size=report_file_size)

[dokumentace]
@write_api def compress_covers(self, book_ids, jpeg_quality=100, progress_callback=None):
'''
Compress the cover images for the specified books. A compression quality of 100 will perform lossless compression, otherwise lossy compression.

The progress callback will be called with the book_id and the old and new sizes for each book that has been processed. If an error occurs, the new size will be a string with the error details.
'''
jpeg_quality = max(10, min(jpeg_quality, 100))
path_map = {}
for book_id in book_ids:
try:
path_map[book_id] = self._field_for('path', book_id).replace('/', os.sep)
except AttributeError:
continue self.backend.compress_covers(path_map, jpeg_quality, progress_callback)

[dokumentace]
@read_api def copy_format_to(self, book_id, fmt, dest, use_hardlink=False, report_file_size=None):
'''
Copy the format ``fmt`` to the file like object ``dest``. If the specified format does not exist, raises :class:`NoSuchFormat` error.
dest can also be a path (to a file), in which case the format is copied to it, iff the path is different from the current path (taking case sensitivity into account).
'''
fmt = (fmt or '').upper()
try:
name = self.fields['formats'].format_fname(book_id, fmt)
path = self._field_for('path', book_id).replace('/', os.sep)
except (KeyError, AttributeError):
raise NoSuchFormat('Record %d has no %s file'%(book_id, fmt))

return self.backend.copy_format_to(book_id, fmt, name, path, dest, use_hardlink=use_hardlink, report_file_size=report_file_size)

[dokumentace]
@read_api def format_abspath(self, book_id, fmt):
'''
Return absolute path to the e-book file of format `format`. You should almost never use this, as it breaks the threadsafe promise of this API.
Instead use, :meth:`copy_format_to`.

Currently used only in calibredb list, the viewer, edit book, compare_format to original format, open with, bulk metadata edit and the catalogs (via get_data_as_dict()).

Apart from the viewer, open with and edit book, I don't believe any of the others do any file write I/O with the results of this call.
'''
fmt = (fmt or '').upper()
try:
path = self._field_for('path', book_id).replace('/', os.sep)
except:
return None if path:
if fmt == '__COVER_INTERNAL__':
return self.backend.cover_abspath(book_id, path)
else:
try:
name = self.fields['formats'].format_fname(book_id, fmt)
except:
return None if name:
return self.backend.format_abspath(book_id, fmt, name, path)

[dokumentace]
@read_api def has_format(self, book_id, fmt):
'Return True iff the format exists on disk'
fmt = (fmt or '').upper()
try:
name = self.fields['formats'].format_fname(book_id, fmt)
path = self._field_for('path', book_id).replace('/', os.sep)
except:
return False return self.backend.has_format(book_id, fmt, name, path)

[dokumentace]
@api def save_original_format(self, book_id, fmt):
' Save a copy of the specified format as ORIGINAL_FORMAT, overwriting any existing ORIGINAL_FORMAT. '
fmt = fmt.upper()
if 'ORIGINAL' in fmt:
raise ValueError('Cannot save original of an original fmt')
fmtfile = self.format(book_id, fmt, as_file=True)
if fmtfile is None:
return False with fmtfile:
nfmt = 'ORIGINAL_'+fmt return self.add_format(book_id, nfmt, fmtfile, run_hooks=False)

[dokumentace]
@write_api def restore_original_format(self, book_id, original_fmt):
''' Restore the specified format from the previously saved
ORIGINAL_FORMAT, if any. Return True on success. The ORIGINAL_FORMAT is deleted after a successful restore. '''
original_fmt = original_fmt.upper()
fmt = original_fmt.partition('_')[2]
try:
ofmt_name = self.fields['formats'].format_fname(book_id, original_fmt)
path = self._field_for('path', book_id).replace('/', os.sep)
except Exception:
return False if self.backend.is_format_accessible(book_id, original_fmt, ofmt_name, path):
self.add_format(book_id, fmt, BytesIO(), run_hooks=False)
fmt_name = self.fields['formats'].format_fname(book_id, fmt)
file_size = self.backend.rename_format_file(book_id, ofmt_name, original_fmt, fmt_name, fmt, path)
self.fields['formats'].table.update_fmt(book_id, fmt, fmt_name, file_size, self.backend)
self._remove_formats({book_id:(original_fmt,)})
return True return False

[dokumentace]
@read_api def formats(self, book_id, verify_formats=True):
'''
Return tuple of all formats for the specified book. If verify_formats is True, verifies that the files exist on disk.
'''
ans = self.field_for('formats', book_id)
if verify_formats and ans:
try:
path = self._field_for('path', book_id).replace('/', os.sep)
except:
return ()

def verify(fmt):
try:
name = self.fields['formats'].format_fname(book_id, fmt)
except:
return False return self.backend.has_format(book_id, fmt, name, path)

ans = tuple(x for x in ans if verify(x))
return ans

[dokumentace]
@api def format(self, book_id, fmt, as_file=False, as_path=False, preserve_filename=False):
'''
Return the e-book format as a bytestring or `None` if the format doesn't exist, or we don't have permission to write to the e-book file.

:param as_file: If True the e-book format is returned as a file object. Note that the file object is a SpooledTemporaryFile, so if what you want to do is copy the format to another file, use :meth:`copy_format_to` instead for performance.
:param as_path: Copies the format file to a temp file and returns the path to the temp file
:param preserve_filename: If True and returning a path the filename is the same as that used in the library. Note that using this means that repeated calls yield the same temp file (which is re-created each time)
'''
fmt = (fmt or '').upper()
ext = ('.'+fmt.lower()) if fmt else ''
if as_path:
if preserve_filename:
with self.safe_read_lock:
try:
fname = self.fields['formats'].format_fname(book_id, fmt)
except:
return None fname += ext bd = base_dir()
d = os.path.join(bd, 'format_abspath')
try:
os.makedirs(d)
except:
pass ret = os.path.join(d, fname)
try:
self.copy_format_to(book_id, fmt, ret)
except NoSuchFormat:
return None else:
with PersistentTemporaryFile(ext) as pt:
try:
self.copy_format_to(book_id, fmt, pt)
except NoSuchFormat:
return None ret = pt.name elif as_file:
with self.safe_read_lock:
try:
fname = self.fields['formats'].format_fname(book_id, fmt)
except:
return None fname += ext ret = SpooledTemporaryFile(SPOOL_SIZE)
try:
self.copy_format_to(book_id, fmt, ret)
except NoSuchFormat:
ret.close()
return None ret.seek(0)
# Various bits of code try to use the name as the default
# title when reading metadata, so set it ret.name = fname else:
buf = BytesIO()
try:
self.copy_format_to(book_id, fmt, buf)
except NoSuchFormat:
return None ret = buf.getvalue()

return ret@read_api def newly_added_book_ids(self, count=5, book_ids=None) -> list[int]:
ids_to_sort = self._all_book_ids(list) if book_ids is None else list(book_ids)
ids_to_sort.sort(reverse=True)
return ids_to_sort[:count]

@read_api def size_stats(self) -> dict[str, int]:
return self.backend.size_stats()

[dokumentace]
@read_api def multisort(self, fields, ids_to_sort=None, virtual_fields=None):
'''
Return a list of sorted book ids. If ids_to_sort is None, all book ids are returned.

fields must be a list of 2-tuples of the form (field_name, ascending=True or False). The most significant field is the first
2-tuple.
'''
ids_to_sort = self._all_book_ids() if ids_to_sort is None else ids_to_sort get_metadata = self._get_proxy_metadata lang_map = self.fields['languages'].book_value_map virtual_fields = virtual_fields or {}

fm = {'title':'sort', 'authors':'author_sort'}

def sort_key_func(field):
'Handle series type fields, virtual fields and the id field'
idx = field + '_index'
is_series = idx in self.fields try:
func = self.fields[fm.get(field, field)].sort_keys_for_books(get_metadata, lang_map)
except KeyError:
if field == 'id':
return IDENTITY else:
return virtual_fields[fm.get(field, field)].sort_keys_for_books(get_metadata, lang_map)
if is_series:
idx_func = self.fields[idx].sort_keys_for_books(get_metadata, lang_map)

def skf(book_id):
return (func(book_id), idx_func(book_id))
return skf return func

# Sort only once on any given field fields = uniq(fields, operator.itemgetter(0))

if len(fields) == 1:
keyfunc = sort_key_func(fields[0][0])
reverse = not fields[0][1]
try:
return sorted(ids_to_sort, key=keyfunc, reverse=reverse)
except Exception as err:
print('Failed to sort database on field:', fields[0][0], 'with error:', err, file=sys.stderr)
try:
return sorted(ids_to_sort, key=type_safe_sort_key_function(keyfunc), reverse=reverse)
except Exception as err:
print('Failed to type-safe sort database on field:', fields[0][0], 'with error:', err, file=sys.stderr)
return sorted(ids_to_sort, reverse=reverse)
sort_key_funcs = tuple(sort_key_func(field) for field, order in fields)
orders = tuple(1 if order else -1 for _, order in fields)
Lazy = object() # Lazy load the sort keys for sub-sort fields class SortKey:

__slots__ = 'book_id', 'sort_key'

def __init__(self, book_id):
self.book_id = book_id
# Calculate only the first sub-sort key since that will always be used self.sort_key = [key(book_id) if i == 0 else Lazy for i, key in enumerate(sort_key_funcs)]

def compare_to_other(self, other):
for i, (order, self_key, other_key) in enumerate(zip(orders, self.sort_key, other.sort_key)):
if self_key is Lazy:
self_key = self.sort_key[i] = sort_key_funcs[i](self.book_id)
if other_key is Lazy:
other_key = other.sort_key[i] = sort_key_funcs[i](other.book_id)
ans = cmp(self_key, other_key)
if ans != 0:
return ans * order return 0 def __eq__(self, other):
return self.compare_to_other(other) == 0 def __ne__(self, other):
return self.compare_to_other(other) != 0 def __lt__(self, other):
return self.compare_to_other(other) < 0 def __le__(self, other):
return self.compare_to_other(other) <= 0 def __gt__(self, other):
return self.compare_to_other(other) > 0 def __ge__(self, other):
return self.compare_to_other(other) >= 0 return sorted(ids_to_sort, key=SortKey)

[dokumentace]
@read_api def search(self, query, restriction='', virtual_fields=None, book_ids=None):
'''
Search the database for the specified query, returning a set of matched book ids.

:param restriction: A restriction that is ANDed to the specified query. Note that restrictions are cached, therefore the search for a AND b will be slower than a with restriction b.

:param virtual_fields: Used internally (virtual fields such as on_device to search over).

:param book_ids: If not None, a set of book ids for which books will be searched instead of searching all books.
'''
return self._search_api(self, query, restriction, virtual_fields=virtual_fields, book_ids=book_ids)

[dokumentace]
@read_api def books_in_virtual_library(self, vl, search_restriction=None, virtual_fields=None):
' Return the set of books in the specified virtual library '
vl = self._pref('virtual_libraries', {}).get(vl) if vl else None if not vl and not search_restriction:
return self.all_book_ids()
# We utilize the search restriction cache to speed this up srch = partial(self._search, virtual_fields=virtual_fields)
if vl:
if search_restriction:
return frozenset(srch('', vl) & srch('', search_restriction))
return frozenset(srch('', vl))
return frozenset(srch('', search_restriction))

@read_api def number_of_books_in_virtual_library(self, vl=None, search_restriction=None):
if not vl and not search_restriction:
return len(self.fields['uuid'].table.book_col_map)
return len(self.books_in_virtual_library(vl, search_restriction))

[dokumentace]
@api def get_categories(self, sort='name', book_ids=None, already_fixed=None, first_letter_sort=False):
' Used internally to implement the Tag Browser '
try:
with self.safe_read_lock:
return get_categories(self, sort=sort, book_ids=book_ids, first_letter_sort=first_letter_sort)
except InvalidLinkTable as err:
bad_field = err.field_name if bad_field == already_fixed:
raise with self.write_lock:
self.fields[bad_field].table.fix_link_table(self.backend)
return self.get_categories(sort=sort, book_ids=book_ids, already_fixed=bad_field)

@write_api def update_last_modified(self, book_ids, now=None):
if book_ids:
if now is None:
now = nowf()
f = self.fields['last_modified']
f.writer.set_books({book_id:now for book_id in book_ids}, self.backend)
if self.composites:
self._clear_composite_caches(book_ids)
self._clear_search_caches(book_ids)

@write_api def mark_as_dirty(self, book_ids):
self._update_last_modified(book_ids)
already_dirtied = set(self.dirtied_cache).intersection(book_ids)
new_dirtied = book_ids - already_dirtied already_dirtied = {book_id:self.dirtied_sequence+i for i, book_id in enumerate(already_dirtied)}
if already_dirtied:
self.dirtied_sequence = max(itervalues(already_dirtied)) + 1 self.dirtied_cache.update(already_dirtied)
if new_dirtied:
self.backend.dirty_books(new_dirtied)
new_dirtied = {book_id:self.dirtied_sequence+i for i, book_id in enumerate(new_dirtied)}
self.dirtied_sequence = max(itervalues(new_dirtied)) + 1 self.dirtied_cache.update(new_dirtied)

@write_api def commit_dirty_cache(self):
if self.dirtied_cache:
self.backend.dirty_books(self.dirtied_cache)

@write_api def check_dirtied_annotations(self):
if not self.backend.dirty_books_with_dirtied_annotations():
return book_ids = set(self.backend.dirtied_books())
new_dirtied = book_ids - set(self.dirtied_cache)
if new_dirtied:
new_dirtied = {book_id:self.dirtied_sequence+i for i, book_id in enumerate(new_dirtied)}
self.dirtied_sequence = max(itervalues(new_dirtied)) + 1 self.dirtied_cache.update(new_dirtied)

[dokumentace]
@write_api def set_field(self, name, book_id_to_val_map, allow_case_change=True, do_path_update=True):
'''
Set the values of the field specified by ``name``. Returns the set of all book ids that were affected by the change.

:param book_id_to_val_map: Mapping of book_ids to values that should be applied.
:param allow_case_change: If True, the case of many-one or many-many fields will be changed.
For example, if a book has the tag ``tag1`` and you set the tag for another book to ``Tag1`` then the both books will have the tag ``Tag1`` if allow_case_change is True, otherwise they will both have the tag ``tag1``.
:param do_path_update: Used internally, you should never change it.
'''
f = self.fields[name]
is_series = f.metadata['datatype'] == 'series'
update_path = name in {'title', 'authors'}
if update_path and iswindows:
paths = (x for x in (self._field_for('path', book_id) for book_id in book_id_to_val_map) if x)
self.backend.windows_check_if_files_in_use(paths)

if is_series:
bimap, simap = {}, {}
sfield = self.fields[name + '_index']
for k, v in iteritems(book_id_to_val_map):
if isinstance(v, string_or_bytes):
v, sid = get_series_values(v)
else:
v = sid = None if sid is None and name.startswith('#'):
sid = self._fast_field_for(sfield, k)
sid = 1.0 if sid is None else sid # The value to be set the db link table bimap[k] = v if sid is not None:
simap[k] = sid book_id_to_val_map = bimap dirtied = f.writer.set_books( book_id_to_val_map, self.backend, allow_case_change=allow_case_change)

if is_series and simap:
sf = self.fields[f.name+'_index']
dirtied |= sf.writer.set_books(simap, self.backend, allow_case_change=False)

if dirtied:
if update_path and do_path_update:
self._update_path(dirtied, mark_as_dirtied=False)
self._mark_as_dirty(dirtied)
self._clear_link_map_cache(dirtied)
self.event_dispatcher(EventType.metadata_changed, name, dirtied)
return dirtied@write_api def update_path(self, book_ids, mark_as_dirtied=True):
for book_id in book_ids:
title = self._field_for('title', book_id, default_value=_('Unknown'))
try:
author = self._field_for('authors', book_id, default_value=(_('Unknown'),))[0]
except IndexError:
author = _('Unknown')
self.backend.update_path(book_id, title, author, self.fields['path'], self.fields['formats'])
self.format_metadata_cache.pop(book_id, None)
if mark_as_dirtied:
self._mark_as_dirty(book_ids)
self._clear_link_map_cache(book_ids)

@read_api def get_a_dirtied_book(self):
if self.dirtied_cache:
return random.choice(tuple(self.dirtied_cache))
return None def _metadata_as_object_for_dump(self, book_id):
mi = self._get_metadata(book_id)
# Always set cover to cover.jpg. Even if cover doesn't exist,
# no harm done. This way no need to call dirtied when
# cover is set/removed mi.cover = 'cover.jpg'
mi.all_annotations = self._all_annotations_for_book(book_id)
return mi

@read_api def get_metadata_for_dump(self, book_id):
mi = None
# get the current sequence number for this book to pass back to the
# backup thread. This will avoid double calls in the case where the
# thread has not done the work between the put and the get_metadata sequence = self.dirtied_cache.get(book_id, None)
if sequence is not None:
try:
# While a book is being created, the path is empty. Don't bother to
# try to write the opf, because it will go to the wrong folder.
if self._field_for('path', book_id):
mi = self._metadata_as_object_for_dump(book_id)
except:
# This almost certainly means that the book has been deleted while
# the backup operation sat in the queue.
traceback.print_exc()
return mi, sequence

@write_api def clear_dirtied(self, book_id, sequence):
# Clear the dirtied indicator for the books. This is used when fetching
# metadata, creating an OPF, and writing a file are separated into steps.
# The last step is clearing the indicator dc_sequence = self.dirtied_cache.get(book_id, None)
if dc_sequence is None or sequence is None or dc_sequence == sequence:
self.backend.mark_book_as_clean(book_id)
self.dirtied_cache.pop(book_id, None)

@write_api def write_backup(self, book_id, raw):
try:
path = self._field_for('path', book_id).replace('/', os.sep)
except:
return self.backend.write_backup(path, raw)

@read_api def dirty_queue_length(self):
return len(self.dirtied_cache)

[dokumentace]
@read_api def read_backup(self, book_id):
''' Return the OPF metadata backup for the book as a bytestring or None if no such backup exists. '''
try:
path = self._field_for('path', book_id).replace('/', os.sep)
except:
return try:
return self.backend.read_backup(path)
except OSError:
return None

@write_api def dump_metadata(self, book_ids=None, remove_from_dirtied=True, callback=None):
# Write metadata for each record to an individual OPF file. If callback
# is not None, it is called once at the start with the number of book_ids
# being processed. And once for every book_id, with arguments (book_id,
# mi, ok).
if book_ids is None:
book_ids = set(self.dirtied_cache)

if callback is not None:
callback(len(book_ids), True, False)

for book_id in book_ids:
if self._field_for('path', book_id) is None:
if callback is not None:
callback(book_id, None, False)
continue mi, sequence = self._get_metadata_for_dump(book_id)
if mi is None:
if callback is not None:
callback(book_id, mi, False)
continue try:
raw = metadata_to_opf(mi)
self._write_backup(book_id, raw)
if remove_from_dirtied:
self._clear_dirtied(book_id, sequence)
except:
pass if callback is not None:
callback(book_id, mi, True)

[dokumentace]
@write_api def set_cover(self, book_id_data_map):
''' Set the cover for this book. The data can be either a QImage,
QPixmap, file object or bytestring. It can also be None, in which case any existing cover is removed. '''

for book_id, data in iteritems(book_id_data_map):
try:
path = self._field_for('path', book_id).replace('/', os.sep)
except AttributeError:
self._update_path((book_id,))
path = self._field_for('path', book_id).replace('/', os.sep)

self.backend.set_cover(book_id, path, data)
for cc in self.cover_caches:
cc.invalidate(book_id_data_map)
return self._set_field('cover', { book_id:(0 if data is None else 1) for book_id, data in iteritems(book_id_data_map)})

@write_api def add_cover_cache(self, cover_cache):
if not callable(cover_cache.invalidate):
raise ValueError('Cover caches must have an invalidate method')
self.cover_caches.add(cover_cache)

@write_api def remove_cover_cache(self, cover_cache):
self.cover_caches.discard(cover_cache)

[dokumentace]
@write_api def set_metadata(self, book_id, mi, ignore_errors=False, force_changes=False, set_title=True, set_authors=True, allow_case_change=False):
'''
Set metadata for the book `id` from the `Metadata` object `mi`

Setting force_changes=True will force set_metadata to update fields even if mi contains empty values. In this case, 'None' is distinguished from
'empty'. If mi.XXX is None, the XXX is not replaced, otherwise it is.
The tags, identifiers, and cover attributes are special cases. Tags and identifiers cannot be set to None so they will always be replaced if force_changes is true. You must ensure that mi contains the values you want the book to have. Covers are always changed if a new cover is provided, but are never deleted. Also note that force_changes has no effect on setting title or authors.
'''
dirtied = set()

try:
# Handle code passing in an OPF object instead of a Metadata object mi = mi.to_book_metadata()
except (AttributeError, TypeError):
pass def set_field(name, val):
dirtied.update(self._set_field(name, {book_id:val}, do_path_update=False, allow_case_change=allow_case_change))

path_changed = False if set_title and mi.title:
path_changed = True set_field('title', mi.title)
authors_changed = False if set_authors:
path_changed = True if not mi.authors:
mi.authors = [_('Unknown')]
authors = []
for a in mi.authors:
authors += string_to_authors(a)
set_field('authors', authors)
authors_changed = True if path_changed:
self._update_path({book_id})

def protected_set_field(name, val):
try:
set_field(name, val)
except:
if ignore_errors:
traceback.print_exc()
else:
raise

# force_changes has no effect on cover manipulation try:
cdata = mi.cover_data[1]
if cdata is None and isinstance(mi.cover, string_or_bytes) and mi.cover and os.access(mi.cover, os.R_OK):
with open(mi.cover, 'rb') as f:
cdata = f.read() or None if cdata is not None:
self._set_cover({book_id: cdata})
except:
if ignore_errors:
traceback.print_exc()
else:
raise try:
with self.backend.conn: # Speed up set_metadata by not operating in autocommit mode for field in ('rating', 'series_index', 'timestamp'):
val = getattr(mi, field)
if val is not None:
protected_set_field(field, val)

val = mi.get('author_sort', None)
if authors_changed and (not val or mi.is_null('author_sort')):
val = self._author_sort_from_authors(mi.authors)
if authors_changed or (force_changes and val is not None) or not mi.is_null('author_sort'):
protected_set_field('author_sort', val)

for field in ('publisher', 'series', 'tags', 'comments',
'languages', 'pubdate'):
val = mi.get(field, None)
if (force_changes and val is not None) or not mi.is_null(field):
protected_set_field(field, val)

val = mi.get('title_sort', None)
if (force_changes and val is not None) or not mi.is_null('title_sort'):
protected_set_field('sort', val)

# identifiers will always be replaced if force_changes is True mi_idents = mi.get_identifiers()
if force_changes:
protected_set_field('identifiers', mi_idents)
elif mi_idents:
identifiers = self._field_for('identifiers', book_id, default_value={})
for key, val in iteritems(mi_idents):
if val and val.strip(): # Don't delete an existing identifier identifiers[icu_lower(key)] = val protected_set_field('identifiers', identifiers)

user_mi = mi.get_all_user_metadata(make_copy=False)
fm = self.field_metadata for key in user_mi:
if (key in fm and user_mi[key]['datatype'] == fm[key]['datatype'] and ( user_mi[key]['datatype'] != 'text' or ( user_mi[key]['is_multiple'] == fm[key]['is_multiple']))):
val = mi.get(key, None)
if force_changes or val is not None:
protected_set_field(key, val)
idx = key + '_index'
if idx in self.fields:
extra = mi.get_extra(key)
if extra is not None or force_changes:
protected_set_field(idx, extra)
except:
# sqlite will rollback the entire transaction, thanks to the with
# statement, so we have to re-read everything form the db to ensure
# the db and Cache are in sync self._reload_from_db()
raise return dirtieddef _do_add_format(self, book_id, fmt, stream, name=None, mtime=None):
path = self._field_for('path', book_id)
if path is None:
# Theoretically, this should never happen, but apparently it
# does: https://www.mobileread.com/forums/showthread.php?t=233353 self._update_path({book_id}, mark_as_dirtied=False)
path = self._field_for('path', book_id)

path = path.replace('/', os.sep)
title = self._field_for('title', book_id, default_value=_('Unknown'))
try:
author = self._field_for('authors', book_id, default_value=(_('Unknown'),))[0]
except IndexError:
author = _('Unknown')

size, fname = self.backend.add_format(book_id, fmt, stream, title, author, path, name, mtime=mtime)
return size, fname

[dokumentace]
@api def add_format(self, book_id, fmt, stream_or_path, replace=True, run_hooks=True, dbapi=None):
'''
Add a format to the specified book. Return True if the format was added successfully.

:param replace: If True replace existing format, otherwise if the format already exists, return False.
:param run_hooks: If True, file type plugins are run on the format before and after being added.
:param dbapi: Internal use only.
'''
needs_close = False if run_hooks:
# Run import plugins, the write lock is not held to cater for
# broken plugins that might spin the event loop by popping up a
# message in the GUI during the processing.
npath = run_import_plugins(stream_or_path, fmt)
fmt = os.path.splitext(npath)[-1].lower().replace('.', '').upper()
stream_or_path = open(make_long_path_useable(npath), 'rb')
needs_close = True fmt = check_ebook_format(stream_or_path, fmt)

with self.write_lock:
if not self._has_id(book_id):
raise NoSuchBook(book_id)
fmt = (fmt or '').upper()
self.format_metadata_cache[book_id].pop(fmt, None)
try:
name = self.fields['formats'].format_fname(book_id, fmt)
except Exception:
name = None if name and not replace:
if needs_close:
stream_or_path.close()
return False if hasattr(stream_or_path, 'read'):
stream = stream_or_path else:
stream = open(make_long_path_useable(stream_or_path), 'rb')
needs_close = True try:
size, fname = self._do_add_format(book_id, fmt, stream, name)
finally:
if needs_close:
stream.close()
del stream max_size = self.fields['formats'].table.update_fmt(book_id, fmt, fname, size, self.backend)
self.fields['size'].table.update_sizes({book_id: max_size})
self._update_last_modified((book_id,))
self.event_dispatcher(EventType.format_added, book_id, fmt)

if run_hooks:
# Run post import plugins, the write lock is released so the plugin
# can call api without a locking violation.
run_plugins_on_postimport(dbapi or self, book_id, fmt)
stream_or_path.close()

self.queue_next_fts_job()
return True[dokumentace]
@write_api def remove_formats(self, formats_map, db_only=False):
'''
Remove the specified formats from the specified books.

:param formats_map: A mapping of book_id to a list of formats to be removed from the book.
:param db_only: If True, only remove the record for the format from the db, do not delete the actual format file from the filesystem.
:return: A map of book id to set of formats actually deleted from the filesystem for that book
'''
table = self.fields['formats'].table formats_map = {book_id:frozenset((f or '').upper() for f in fmts) for book_id, fmts in iteritems(formats_map)}
removed_map = {}

for book_id, fmts in iteritems(formats_map):
for fmt in fmts:
self.format_metadata_cache[book_id].pop(fmt, None)

if not db_only:
removes = defaultdict(set)
metadata_map = {}
for book_id, fmts in iteritems(formats_map):
try:
path = self._field_for('path', book_id).replace('/', os.sep)
except:
continue for fmt in fmts:
try:
name = self.fields['formats'].format_fname(book_id, fmt)
except:
continue if name and path:
removes[book_id].add((fmt, name, path))
if removes[book_id]:
metadata_map[book_id] = {'title': self._field_for('title', book_id), 'authors': self._field_for('authors', book_id)}
if removes:
removed_map = self.backend.remove_formats(removes, metadata_map)

size_map = table.remove_formats(formats_map, self.backend)
self.fields['size'].table.update_sizes(size_map)

for book_id, fmts in iteritems(formats_map):
for fmt in fmts:
run_plugins_on_postdelete(self, book_id, fmt)

self._update_last_modified(tuple(formats_map))
self.event_dispatcher(EventType.formats_removed, formats_map)
return removed_map

[dokumentace]
@read_api def get_next_series_num_for(self, series, field='series', current_indices=False):
'''
Return the next series index for the specified series, taking into account the various preferences that control next series number generation.

:param field: The series-like field (defaults to the builtin series column)
:param current_indices: If True, returns a mapping of book_id to current series_index value instead.
'''
books = ()
sf = self.fields[field]
if series:
q = icu_lower(series)
for val, book_ids in sf.iter_searchable_values(self._get_proxy_metadata, frozenset(self._all_book_ids())):
if q == icu_lower(val):
books = book_ids break idf = sf.index_field index_map = {book_id:self._fast_field_for(idf, book_id, default_value=1.0) for book_id in books}
if current_indices:
return index_map series_indices = sorted(index_map.values(), key=lambda s: s or 0)
return _get_next_series_num_for_list(tuple(series_indices), unwrap=False)

[dokumentace]
@read_api def author_sort_from_authors(self, authors, key_func=icu_lower):
'''Given a list of authors, return the author_sort string for the authors, preferring the author sort associated with the author over the computed string. '''
table = self.fields['authors'].table result = []
rmap = {key_func(v):k for k, v in iteritems(table.id_map)}
for aut in authors:
aid = rmap.get(key_func(aut), None)
result.append(author_to_author_sort(aut) if aid is None else table.asort_map[aid])
return ' & '.join(_f for _f in result if _f)

[dokumentace]
@read_api def data_for_has_book(self):
''' Return data suitable for use in :meth:`has_book`. This can be used for an implementation of :meth:`has_book` in a worker process without access to the db. '''
try:
return {icu_lower(title) for title in itervalues(self.fields['title'].table.book_col_map)}
except TypeError:
# Some non-unicode titles in the db return {icu_lower(as_unicode(title)) for title in itervalues(self.fields['title'].table.book_col_map)}

[dokumentace]
@read_api def has_book(self, mi):
''' Return True iff the database contains an entry with the same title as the passed in Metadata object. The comparison is case-insensitive.
See also :meth:`data_for_has_book`. '''
title = mi.title if title:
if isbytestring(title):
title = title.decode(preferred_encoding, 'replace')
q = icu_lower(title).strip()
for title in itervalues(self.fields['title'].table.book_col_map):
if q == icu_lower(title):
return True return False

[dokumentace]
@read_api def has_id(self, book_id):
' Return True iff the specified book_id exists in the db '''
return book_id in self.fields['title'].table.book_col_map

@write_api def create_book_entry(self, mi, cover=None, add_duplicates=True, force_id=None, apply_import_tags=True, preserve_uuid=False):
if mi.tags:
mi.tags = list(mi.tags)
if apply_import_tags:
_add_newbook_tag(mi)
_add_default_custom_column_values(mi, self.field_metadata)
if not add_duplicates and self._has_book(mi):
return series_index = (self._get_next_series_num_for(mi.series) if mi.series_index is None else mi.series_index)
try:
series_index = float(series_index)
except Exception:
try:
series_index = float(self._get_next_series_num_for(mi.series))
except Exception:
series_index = 1.0 if not mi.authors:
mi.authors = (_('Unknown'),)
aus = mi.author_sort if not mi.is_null('author_sort') else self._author_sort_from_authors(mi.authors)
mi.title = mi.title or _('Unknown')
if isbytestring(aus):
aus = aus.decode(preferred_encoding, 'replace')
if isbytestring(mi.title):
mi.title = mi.title.decode(preferred_encoding, 'replace')
if force_id is None:
self.backend.execute('INSERT INTO books(title, series_index, author_sort) VALUES (?, ?, ?)',
(mi.title, series_index, aus))
else:
self.backend.execute('INSERT INTO books(id, title, series_index, author_sort) VALUES (?, ?, ?, ?)',
(force_id, mi.title, series_index, aus))
book_id = self.backend.last_insert_rowid()
self.event_dispatcher(EventType.book_created, book_id)

mi.timestamp = utcnow() if mi.timestamp is None else mi.timestamp mi.pubdate = UNDEFINED_DATE if mi.pubdate is None else mi.pubdate if cover is not None:
mi.cover, mi.cover_data = None, (None, cover)
self._set_metadata(book_id, mi, ignore_errors=True)
lm = getattr(mi, 'link_maps', None)
if lm:
for field, link_map in lm.items():
if self._has_link_map(field):
self._set_link_map(field, link_map, only_set_if_no_existing_link=True)
if preserve_uuid and mi.uuid:
self._set_field('uuid', {book_id:mi.uuid})
# Update the caches for fields from the books table self.fields['size'].table.book_col_map[book_id] = 0 row = next(self.backend.execute('SELECT sort, series_index, author_sort, uuid, has_cover FROM books WHERE id=?', (book_id,)))
for field, val in zip(('sort', 'series_index', 'author_sort', 'uuid', 'cover'), row):
if field == 'cover':
val = bool(val)
elif field == 'uuid':
self.fields[field].table.uuid_to_id_map[val] = book_id self.fields[field].table.book_col_map[book_id] = val return book_id

[dokumentace]
@api def add_books(self, books, add_duplicates=True, apply_import_tags=True, preserve_uuid=False, run_hooks=True, dbapi=None):
'''
Add the specified books to the library. Books should be an iterable of
2-tuples, each 2-tuple of the form :code:`(mi, format_map)` where mi is a
Metadata object and format_map is a dictionary of the form :code:`{fmt: path_or_stream}`, for example: :code:`{'EPUB': '/path/to/file.epub'}`.

Returns a pair of lists: :code:`ids, duplicates`. ``ids`` contains the book ids for all newly created books in the database. ``duplicates`` contains the :code:`(mi, format_map)` for all books that already exist in the database as per the simple duplicate detection heuristic used by :meth:`has_book`.
'''
duplicates, ids = [], []
for mi, format_map in books:
book_id = self.create_book_entry(mi, add_duplicates=add_duplicates, apply_import_tags=apply_import_tags, preserve_uuid=preserve_uuid)
if book_id is None:
duplicates.append((mi, format_map))
else:
fmt_map = {}
ids.append(book_id)
for fmt, stream_or_path in format_map.items():
if self.add_format(book_id, fmt, stream_or_path, dbapi=dbapi, run_hooks=run_hooks):
fmt_map[fmt.lower()] = getattr(stream_or_path, 'name', stream_or_path) or '<stream>'
run_plugins_on_postadd(dbapi or self, book_id, fmt_map)
return ids, duplicates

[dokumentace]
@write_api def remove_books(self, book_ids, permanent=False):
''' Remove the books specified by the book_ids from the database and delete their format files. If ``permanent`` is False, then the format files are placed in the per-library trash directory. '''
path_map = {}
for book_id in book_ids:
try:
path = self._field_for('path', book_id).replace('/', os.sep)
except Exception:
path = None path_map[book_id] = path if not permanent and path:
# ensure metadata.opf is written and up-to-date so we can restore the book try:
mi = self._metadata_as_object_for_dump(book_id)
raw = metadata_to_opf(mi)
self.backend.write_backup(path, raw)
except Exception:
traceback.print_exc()
self.backend.remove_books(path_map, permanent=permanent)
for field in itervalues(self.fields):
try:
table = field.table except AttributeError:
continue # Some fields like ondevice do not have tables else:
table.remove_books(book_ids, self.backend)
self._search_api.discard_books(book_ids)
self._clear_caches(book_ids=book_ids, template_cache=False, search_cache=False)
for cc in self.cover_caches:
cc.invalidate(book_ids)
self.event_dispatcher(EventType.books_removed, book_ids)@read_api def author_sort_strings_for_books(self, book_ids):
val_map = {}
for book_id in book_ids:
authors = self._field_ids_for('authors', book_id)
adata = self._author_data(authors)
val_map[book_id] = tuple(adata[aid]['sort'] for aid in authors)
return val_map

[dokumentace]
@write_api def rename_items(self, field, item_id_to_new_name_map, change_index=True, restrict_to_book_ids=None):
'''
Rename items from a many-one or many-many field such as tags or series.

:param change_index: When renaming in a series-like field also change the series_index values.
:param restrict_to_book_ids: An optional set of book ids for which the rename is to be performed, defaults to all books.
'''

f = self.fields[field]
affected_books = set()
try:
sv = f.metadata['is_multiple']['ui_to_list']
except (TypeError, KeyError, AttributeError):
sv = None if restrict_to_book_ids is not None:
# We have a VL. Only change the item name for those books if not isinstance(restrict_to_book_ids, (Set, MutableSet)):
restrict_to_book_ids = frozenset(restrict_to_book_ids)
id_map = {}
default_process_map = {}
for old_id, new_name in iteritems(item_id_to_new_name_map):
new_names = tuple(x.strip() for x in new_name.split(sv)) if sv else (new_name,)
# Get a list of books in the VL with the item books_with_id = f.books_for(old_id)
books_to_process = books_with_id & restrict_to_book_ids if len(books_with_id) == len(books_to_process):
# All the books with the ID are in the VL, so we can use
# the normal processing default_process_map[old_id] = new_name elif books_to_process:
affected_books.update(books_to_process)
newvals = {}
for book_id in books_to_process:
# Get the current values, remove the one being renamed, then add
# the new value(s) back.
vals = self._field_for(field, book_id)
# Check for is_multiple if isinstance(vals, tuple):
# We must preserve order.
vals = list(vals)
# Don't need to worry about case here because we
# are fetching its one-true spelling. But lets be
# careful anyway try:
dex = vals.index(self._get_item_name(field, old_id))
# This can put the name back with a different case vals[dex] = new_names[0]
# now add any other items if they aren't already there if len(new_names) > 1:
set_vals = {icu_lower(x) for x in vals}
for v in new_names[1:]:
lv = icu_lower(v)
if lv not in set_vals:
vals.append(v)
set_vals.add(lv)
newvals[book_id] = vals except Exception:
traceback.print_exc()
else:
newvals[book_id] = new_names[0]
# Allow case changes self._set_field(field, newvals)
id_map[old_id] = self._get_item_id(field, new_names[0])
if default_process_map:
ab, idm = self._rename_items(field, default_process_map, change_index=change_index)
affected_books.update(ab)
id_map.update(idm)
self.event_dispatcher(EventType.items_renamed, field, affected_books, id_map)
return affected_books, id_map try:
func = f.table.rename_item except AttributeError:
raise ValueError('Cannot rename items for one-one fields: %s' % field)
moved_books = set()
id_map = {}
for item_id, new_name in iteritems(item_id_to_new_name_map):
new_names = tuple(x.strip() for x in new_name.split(sv)) if sv else (new_name,)
books, new_id = func(item_id, new_names[0], self.backend)
affected_books.update(books)
id_map[item_id] = new_id if new_id != item_id:
moved_books.update(books)
if len(new_names) > 1:
# Add the extra items to the books extra = new_names[1:]
self._set_field(field, {book_id:self._fast_field_for(f, book_id) + extra for book_id in books})

if affected_books:
if field == 'authors':
self._set_field('author_sort',
{k:' & '.join(v) for k, v in iteritems(self._author_sort_strings_for_books(affected_books))})
self._update_path(affected_books, mark_as_dirtied=False)
elif change_index and hasattr(f, 'index_field') and tweaks['series_index_auto_increment'] != 'no_change':
for book_id in moved_books:
self._set_field(f.index_field.name, {book_id:self._get_next_series_num_for(self._fast_field_for(f, book_id), field=field)})
self._mark_as_dirty(affected_books)
self._clear_link_map_cache(affected_books)
self.event_dispatcher(EventType.items_renamed, field, affected_books, id_map)
return affected_books, id_map

[dokumentace]
@write_api def remove_items(self, field, item_ids, restrict_to_book_ids=None):
''' Delete all items in the specified field with the specified ids.
Returns the set of affected book ids. ``restrict_to_book_ids`` is an optional set of books ids. If specified the items will only be removed from those books. '''
field = self.fields[field]
if restrict_to_book_ids is not None and not isinstance(restrict_to_book_ids, (MutableSet, Set)):
restrict_to_book_ids = frozenset(restrict_to_book_ids)
affected_books = field.table.remove_items(item_ids, self.backend, restrict_to_book_ids=restrict_to_book_ids)
if affected_books:
if hasattr(field, 'index_field'):
self._set_field(field.index_field.name, {bid:1.0 for bid in affected_books})
else:
self._mark_as_dirty(affected_books)
self._clear_link_map_cache(affected_books)
self.event_dispatcher(EventType.items_removed, field, affected_books, item_ids)
return affected_books

[dokumentace]
@write_api def add_custom_book_data(self, name, val_map, delete_first=False):
''' Add data for name where val_map is a map of book_ids to values. If delete_first is True, all previously stored data for name will be removed. '''
missing = frozenset(val_map) - self._all_book_ids()
if missing:
raise ValueError('add_custom_book_data: no such book_ids: %d'%missing)
self.backend.add_custom_data(name, val_map, delete_first)

[dokumentace]
@read_api def get_custom_book_data(self, name, book_ids=(), default=None):
''' Get data for name. By default returns data for all book_ids, pass in a list of book ids if you only want some data. Returns a map of book_id to values. If a particular value could not be decoded, uses default for it. '''
return self.backend.get_custom_book_data(name, book_ids, default)

[dokumentace]
@write_api def delete_custom_book_data(self, name, book_ids=()):
''' Delete data for name. By default deletes all data, if you only want to delete data for some book ids, pass in a list of book ids. '''
self.backend.delete_custom_book_data(name, book_ids)

[dokumentace]
@read_api def get_ids_for_custom_book_data(self, name):
''' Return the set of book ids for which name has data. '''
return self.backend.get_ids_for_custom_book_data(name)

@read_api def conversion_options(self, book_id, fmt='PIPE'):
return self.backend.conversion_options(book_id, fmt)

@read_api def has_conversion_options(self, ids, fmt='PIPE'):
return self.backend.has_conversion_options(ids, fmt)

@write_api def delete_conversion_options(self, book_ids, fmt='PIPE'):
return self.backend.delete_conversion_options(book_ids, fmt)

[dokumentace]
@write_api def set_conversion_options(self, options, fmt='PIPE'):
''' options must be a map of the form {book_id:conversion_options} '''
return self.backend.set_conversion_options(options, fmt)

@write_api def refresh_format_cache(self):
self.fields['formats'].table.read(self.backend)
self.format_metadata_cache.clear()

@write_api def refresh_ondevice(self):
self.fields['ondevice'].clear_caches()
self.clear_search_caches()
self.clear_composite_caches()

@read_api def books_matching_device_book(self, lpath):
ans = set()
for book_id, (_, _, _, _, lpaths) in self.fields['ondevice'].cache.items():
if lpath in lpaths:
ans.add(book_id)
return ans

[dokumentace]
@read_api def tags_older_than(self, tag, delta=None, must_have_tag=None, must_have_authors=None):
'''
Return the ids of all books having the tag ``tag`` that are older than the specified time. tag comparison is case insensitive.

:param delta: A timedelta object or None. If None, then all ids with the tag are returned.

:param must_have_tag: If not None the list of matches will be restricted to books that have this tag

:param must_have_authors: A list of authors. If not None the list of matches will be restricted to books that have these authors (case insensitive).

'''
tag_map = {icu_lower(v):k for k, v in iteritems(self._get_id_map('tags'))}
tag = icu_lower(tag.strip())
mht = icu_lower(must_have_tag.strip()) if must_have_tag else None tag_id, mht_id = tag_map.get(tag, None), tag_map.get(mht, None)
ans = set()
if mht_id is None and mht:
return ans if tag_id is not None:
tagged_books = self._books_for_field('tags', tag_id)
if mht_id is not None and tagged_books:
tagged_books = tagged_books.intersection(self._books_for_field('tags', mht_id))
if tagged_books:
if must_have_authors is not None:
amap = {icu_lower(v):k for k, v in iteritems(self._get_id_map('authors'))}
books = None for author in must_have_authors:
abooks = self._books_for_field('authors', amap.get(icu_lower(author), None))
books = abooks if books is None else books.intersection(abooks)
if not books:
break tagged_books = tagged_books.intersection(books or set())
if delta is None:
ans = tagged_books else:
now = nowf()
for book_id in tagged_books:
ts = self._field_for('timestamp', book_id)
if (now - ts) > delta:
ans.add(book_id)
return ans@write_api def set_sort_for_authors(self, author_id_to_sort_map, update_books=True):
sort_map = self.fields['authors'].table.set_sort_names(author_id_to_sort_map, self.backend)
changed_books = set()
if update_books:
val_map = {}
for author_id in sort_map:
books = self._books_for_field('authors', author_id)
changed_books |= books for book_id in books:
authors = self._field_ids_for('authors', book_id)
adata = self._author_data(authors)
sorts = [adata[x]['sort'] for x in authors]
val_map[book_id] = ' & '.join(sorts)
if val_map:
self._set_field('author_sort', val_map)
if changed_books:
self._mark_as_dirty(changed_books)
self._clear_link_map_cache(changed_books)
return changed_books

@write_api def set_link_for_authors(self, author_id_to_link_map):
link_map = self.fields['authors'].table.set_links(author_id_to_link_map, self.backend)
changed_books = set()
for author_id in link_map:
changed_books |= self._books_for_field('authors', author_id)
if changed_books:
self._mark_as_dirty(changed_books)
self._clear_link_map_cache(changed_books)
return changed_books

@read_api def has_link_map(self, field):
return hasattr(getattr(self.fields.get(field), 'table', None), 'link_map')

[dokumentace]
@read_api def get_link_map(self, for_field):
'''
Return a dictionary of links for the supplied field.

:param for_field: the lookup name of the field for which the link map is desired

:return: {field_value:link_value, ...} for non-empty links
'''
if for_field not in self.fields:
raise ValueError(f'Lookup name {for_field} is not a valid name')
table = self.fields[for_field].table lm = getattr(table, 'link_map', None)
if lm is None:
raise ValueError(f"Lookup name {for_field} doesn't have a link map")
lm = table.link_map vm = table.id_map return {vm.get(fid):v for fid,v in lm.items() if v}

[dokumentace]
@read_api def link_for(self, field, item_id):
'''
Return the link, if any, for the specified item or None if no link is found
'''
f = self.fields.get(field)
if f is not None:
table = f.table lm = getattr(table, 'link_map', None)
if lm is not None:
return lm.get(item_id)

[dokumentace]
@read_api def get_all_link_maps_for_book(self, book_id):
'''
Returns all links for all fields referenced by book identified by book_id.
If book_id doesn't exist then the method returns {}.

Example: Assume author A has link X, author B has link Y, tag S has link
F, and tag T has link G. If book 1 has author A and tag T, this method returns {'authors':{'A':'X'}, 'tags':{'T', 'G'}}.
If book 2's author is neither A nor B and has no tags, this method returns {}.

:param book_id: the book id in question.

:return: {field: {field_value, link_value}, ... for all fields with a field_value having a non-empty link value for that book

'''
if not self._has_id(book_id):
# Works for book_id is None.
return {}
cached = self.link_maps_cache.get(book_id)
if cached is not None:
return cached links = {}
def add_links_for_field(f):
field_ids = self._field_ids_for(f, book_id)
if field_ids:
table = self.fields[f].table lm = table.link_map id_link_map = {fid:lm.get(fid) for fid in field_ids}
vm = table.id_map d = {vm.get(fid):v for fid, v in id_link_map.items() if v}
d.pop(None, None)
if d:
links[f] = d for field in ('authors', 'publisher', 'series', 'tags'):
add_links_for_field(field)
for field in self.field_metadata.custom_field_keys(include_composites=False):
if self._has_link_map(field):
add_links_for_field(field)
self.link_maps_cache[book_id] = links return links[dokumentace]
@write_api def set_link_map(self, field, value_to_link_map, only_set_if_no_existing_link=False):
'''
Sets links for item values in field.
Note: this method doesn't change values not in the value_to_link_map

:param field: the lookup name
:param value_to_link_map: dict(field_value:link, ...). Note that these are values, not field ids.

:return: books changed by setting the link

'''
if field not in self.fields:
raise ValueError(f'Lookup name {field} is not a valid name')
table = getattr(self.fields[field], 'table', None)
if table is None:
raise ValueError(f"Lookup name {field} doesn't have a link map")
# Clear the links for book cache as we don't know what will be affected self.link_maps_cache = {}

fids = self._get_item_ids(field, value_to_link_map)
if only_set_if_no_existing_link:
lm = table.link_map id_to_link_map = {fid:value_to_link_map[k] for k, fid in fids.items() if fid is not None and not lm.get(fid)}
else:
id_to_link_map = {fid:value_to_link_map[k] for k, fid in fids.items() if fid is not None}
result_map = table.set_links(id_to_link_map, self.backend)
changed_books = set()
for id_ in result_map:
changed_books |= self._books_for_field(field, id_)
if changed_books:
self._mark_as_dirty(changed_books)
self._clear_link_map_cache(changed_books)
return changed_books@read_api def lookup_by_uuid(self, uuid):
return self.fields['uuid'].table.lookup_by_uuid(uuid)

@write_api def delete_custom_column(self, label=None, num=None):
self.backend.delete_custom_column(label, num)

@write_api def create_custom_column(self, label, name, datatype, is_multiple, editable=True, display={}):
return self.backend.create_custom_column(label, name, datatype, is_multiple, editable=editable, display=display)

@write_api def set_custom_column_metadata(self, num, name=None, label=None, is_editable=None, display=None, update_last_modified=False):
changed = self.backend.set_custom_column_metadata(num, name=name, label=label, is_editable=is_editable, display=display)
if changed:
if update_last_modified:
self._update_last_modified(self._all_book_ids())
else:
self.backend.prefs.set('update_all_last_mod_dates_on_start', True)
return changed

@read_api def get_books_for_category(self, category, item_id_or_composite_value):
f = self.fields[category]
if hasattr(f, 'get_books_for_val'):
# Composite field return f.get_books_for_val(item_id_or_composite_value, self._get_proxy_metadata, self._all_book_ids())
return self._books_for_field(f.name, int(item_id_or_composite_value))

[dokumentace]
@read_api def split_if_is_multiple_composite(self, f, val):
'''
If f is a composite column lookup key and the column is is_multiple then split v into unique non-empty values. The comparison is case sensitive.
Order is not preserved. Return a list() for compatibility with proxy metadata field getters, for example tags.
'''
fm = self.field_metadata.get(f, None)
if fm and fm['datatype'] == 'composite' and fm['is_multiple']:
sep = fm['is_multiple'].get('cache_to_list', ',')
return list({v.strip() for v in val.split(sep) if v.strip()})
return val

[dokumentace]
@read_api def data_for_find_identical_books(self):
''' Return data that can be used to implement
:meth:`find_identical_books` in a worker process without access to the db. See db.utils for an implementation. '''
at = self.fields['authors'].table author_map = defaultdict(set)
for aid, author in iteritems(at.id_map):
author_map[icu_lower(author)].add(aid)
return (author_map, at.col_book_map.copy(), self.fields['title'].table.book_col_map.copy(), self.fields['languages'].book_value_map.copy())

@read_api def update_data_for_find_identical_books(self, book_id, data):
author_map, author_book_map, title_map, lang_map = data title_map[book_id] = self._field_for('title', book_id)
lang_map[book_id] = self._field_for('languages', book_id)
at = self.fields['authors'].table for aid in at.book_col_map.get(book_id, ()):
author_map[icu_lower(at.id_map[aid])].add(aid)
try:
author_book_map[aid].add(book_id)
except KeyError:
author_book_map[aid] = {book_id}

[dokumentace]
@read_api def find_identical_books(self, mi, search_restriction='', book_ids=None):
''' Finds books that have a superset of the authors in mi and the same title (title is fuzzy matched). See also :meth:`data_for_find_identical_books`. '''
from calibre.db.utils import fuzzy_title identical_book_ids = set()
langq = tuple(x for x in map(canonicalize_lang, mi.languages or ()) if x and x != 'und')
if mi.authors:
try:
quathors = mi.authors[:20] # Too many authors causes parsing of the search expression to fail query = ' and '.join('authors:"=%s"'%(a.replace('"', '')) for a in quathors)
qauthors = mi.authors[20:]
except ValueError:
return identical_book_ids try:
book_ids = self._search(query, restriction=search_restriction, book_ids=book_ids)
except:
traceback.print_exc()
return identical_book_ids if qauthors and book_ids:
matches = set()
qauthors = {icu_lower(x) for x in qauthors}
for book_id in book_ids:
aut = self._field_for('authors', book_id)
if aut:
aut = {icu_lower(x) for x in aut}
if aut.issuperset(qauthors):
matches.add(book_id)
book_ids = matches for book_id in book_ids:
fbook_title = self._field_for('title', book_id)
fbook_title = fuzzy_title(fbook_title)
mbook_title = fuzzy_title(mi.title)
if fbook_title == mbook_title:
bl = self._field_for('languages', book_id)
if not langq or not bl or bl == langq:
identical_book_ids.add(book_id)
return identical_book_ids@read_api def get_top_level_move_items(self):
all_paths = {self._field_for('path', book_id).partition('/')[0] for book_id in self._all_book_ids()}
return self.backend.get_top_level_move_items(all_paths)

@write_api def move_library_to(self, newloc, progress=None, abort=None):
def progress_callback(item_name, item_count, total):
try:
if progress is not None:
progress(item_name, item_count, total)
except Exception:
traceback.print_exc()

all_paths = {self._field_for('path', book_id).partition('/')[0] for book_id in self._all_book_ids()}
self.backend.move_library_to(all_paths, newloc, progress=progress_callback, abort=abort)

@read_api def saved_search_names(self):
return self._search_api.saved_searches.names()

@read_api def saved_search_lookup(self, name):
return self._search_api.saved_searches.lookup(name)

@write_api def saved_search_set_all(self, smap):
self._search_api.saved_searches.set_all(smap)
self._clear_search_caches()

@write_api def saved_search_delete(self, name):
self._search_api.saved_searches.delete(name)
self._clear_search_caches()

@write_api def saved_search_add(self, name, val):
self._search_api.saved_searches.add(name, val)

@write_api def saved_search_rename(self, old_name, new_name):
self._search_api.saved_searches.rename(old_name, new_name)
self._clear_search_caches()

@write_api def change_search_locations(self, newlocs):
self._search_api.change_locations(newlocs)

@write_api def refresh_search_locations(self):
self._search_api.change_locations(self.field_metadata.get_search_terms())

@write_api def dump_and_restore(self, callback=None, sql=None):
return self.backend.dump_and_restore(callback=callback, sql=sql)

@write_api def vacuum(self, include_fts_db=False, include_notes_db=True):
self.is_doing_rebuild_or_vacuum = True try:
self.backend.vacuum(include_fts_db, include_notes_db)
finally:
self.is_doing_rebuild_or_vacuum = False def __del__(self):
self.close()

def _shutdown_fts(self, stage=1):
if stage == 1:
self.backend.shutdown_fts()
if self.fts_queue_thread is not None:
self.fts_job_queue.put(None)
if hasattr(self, 'fts_dispatch_stop_event'):
self.fts_dispatch_stop_event.set()
return
# the fts supervisor thread could be in the middle of committing a
# result to the db, so holding a lock here will cause a deadlock if self.fts_queue_thread is not None:
self.fts_queue_thread.join()
self.fts_queue_thread = None self.backend.join_fts()

@api def close(self):
with self.write_lock:
if hasattr(self, 'close_called'):
return self.close_called = True self.shutting_down = True self.event_dispatcher.close()
self._shutdown_fts()
try:
from calibre.customize.ui import available_library_closed_plugins except ImportError:
pass # happens during interpreter shutdown else:
for plugin in available_library_closed_plugins():
try:
plugin.run(self)
except Exception:
traceback.print_exc()
self._shutdown_fts(stage=2)
with self.write_lock:
self.backend.close()

@property def is_closed(self):
return self.backend.is_closed

@write_api def clear_trash_bin(self):
self.backend.clear_trash_dir()

@read_api def list_trash_entries(self):
books, formats = self.backend.list_trash_entries()
ff = []
for e in formats:
if self._has_id(e.book_id):
ff.append(e)
e.cover_path = self.format_abspath(e.book_id, '__COVER_INTERNAL__')
return books, formats

@read_api def copy_format_from_trash(self, book_id, fmt, dest):
fmt = fmt.upper()
fpath = self.backend.path_for_trash_format(book_id, fmt)
if not fpath:
raise ValueError(f'No format {fmt} found in book {book_id}')
shutil.copyfile(fpath, dest)

[dokumentace]
@write_api def move_format_from_trash(self, book_id, fmt):
''' Undelete a format from the trash directory '''
if not self._has_id(book_id):
raise ValueError(f'A book with the id {book_id} does not exist')
fmt = fmt.upper()
try:
name = self.fields['formats'].format_fname(book_id, fmt)
except Exception:
name = None fpath = self.backend.path_for_trash_format(book_id, fmt)
if not fpath:
raise ValueError(f'No format {fmt} found in book {book_id}')
size, fname = self._do_add_format(book_id, fmt, fpath, name)
self.format_metadata_cache.pop(book_id, None)
max_size = self.fields['formats'].table.update_fmt(book_id, fmt, fname, size, self.backend)
self.fields['size'].table.update_sizes({book_id: max_size})
self.event_dispatcher(EventType.format_added, book_id, fmt)
self.backend.remove_trash_formats_dir_if_empty(book_id)

@read_api def copy_book_from_trash(self, book_id, dest: str):
self.backend.copy_book_from_trash(book_id, dest)

[dokumentace]
@write_api def move_book_from_trash(self, book_id):
''' Undelete a book from the trash directory '''
if self._has_id(book_id):
raise ValueError(f'A book with the id {book_id} already exists')
mi, annotations, formats = self.backend.get_metadata_for_trash_book(book_id)
mi.cover = None self._create_book_entry(mi, add_duplicates=True, force_id=book_id, apply_import_tags=False, preserve_uuid=True)
path = self._field_for('path', book_id).replace('/', os.sep)
self.backend.move_book_from_trash(book_id, path)
self.format_metadata_cache.pop(book_id, None)
f = self.fields['formats'].table max_size = 0 for (fmt, size, fname) in formats:
max_size = max(max_size, f.update_fmt(book_id, fmt, fname, size, self.backend))
self.fields['size'].table.update_sizes({book_id: max_size})
cover = self.backend.cover_abspath(book_id, path)
if cover and os.path.exists(cover):
self._set_field('cover', {book_id:1})
if annotations:
self._restore_annotations(book_id, annotations)

[dokumentace]
@write_api def delete_trash_entry(self, book_id, category):
" Delete an entry from the trash. Here category is 'b' for books and 'f' for formats. "
self.backend.delete_trash_entry(book_id, category)

[dokumentace]
@write_api def expire_old_trash(self):
' Expire entries from the trash that are too old '
self.backend.expire_old_trash()

[dokumentace]
@write_api def restore_book(self, book_id, mi, last_modified, path, formats, annotations=()):
''' Restore the book entry in the database for a book that already exists on the filesystem '''
cover, mi.cover = mi.cover, None self._create_book_entry(mi, add_duplicates=True, force_id=book_id, apply_import_tags=False, preserve_uuid=True)
self._update_last_modified((book_id,), last_modified)
if cover and os.path.exists(cover):
self._set_field('cover', {book_id:1})
f = self.fields['formats'].table for (fmt, size, fname) in formats:
f.update_fmt(book_id, fmt, fname, size, self.backend)
self.fields['path'].table.set_path(book_id, path, self.backend)
if annotations:
self._restore_annotations(book_id, annotations)

@read_api def virtual_libraries_for_books(self, book_ids, virtual_fields=None):
# use a primitive lock to ensure that only one thread is updating
# the cache and that recursive calls don't do the update. This
# method can recurse via self._search()
with try_lock(self.vls_cache_lock) as got_lock:
# Using a list is slightly faster than a set.
c = defaultdict(list)
if not got_lock:
# We get here if resolving the books in a VL triggers another VL
# cache calculation. This can be 'real' recursion, for example a
# VL expression using a template that calls virtual_libraries(),
# or a search using a location of 'all' that causes evaluation
# of a composite that uses virtual_libraries(). The first case
# is an error and the exception message should appear somewhere.
# However, the error can seem nondeterministic. It might not be
# raised if the use is via a composite and that composite is
# evaluated before it is used in the search. The second case is
# also an error but if the composite isn't used in a VL then the
# eventual answer will be correct because get_metadata() will
# clear the caches.
raise ValueError(_('Recursion detected while processing Virtual library "%s"')
% self.vls_for_books_lib_in_process)
if self.vls_for_books_cache is None:
libraries = self._pref('virtual_libraries', {})
for lib, expr in libraries.items():
book = None self.vls_for_books_lib_in_process = lib try:
for book in self._search(expr, virtual_fields=virtual_fields):
c[book].append(lib)
except Exception as e:
if book:
c[book].append(_('[Error in Virtual library {0}: {1}]').format(lib, str(e)))
self.vls_for_books_cache = {b:tuple(sorted(libs, key=sort_key)) for b, libs in c.items()}
if not book_ids:
book_ids = self._all_book_ids()
# book_ids is usually 1 long. The loop will be faster than a comprehension r = {}
default = ()
for b in book_ids:
r[b] = self.vls_for_books_cache.get(b, default)
return r

[dokumentace]
@read_api def user_categories_for_books(self, book_ids, proxy_metadata_map=None):
''' Return the user categories for the specified books.
proxy_metadata_map is optional and is useful for a performance boost, in contexts where a ProxyMetadata object for the books already exists.
It should be a mapping of book_ids to their corresponding ProxyMetadata objects.
'''
user_cats = self._pref('user_categories', {})
pmm = proxy_metadata_map or {}
ans = {}

for book_id in book_ids:
proxy_metadata = pmm.get(book_id) or self._get_proxy_metadata(book_id)
user_cat_vals = ans[book_id] = {}
for ucat, categories in iteritems(user_cats):
user_cat_vals[ucat] = res = []
for name, cat, ign in categories:
try:
field_obj = self.fields[cat]
except KeyError:
continue if field_obj.is_composite:
v = field_obj.get_value_with_cache(book_id, lambda x:proxy_metadata)
else:
v = self._fast_field_for(field_obj, book_id)

if isinstance(v, (list, tuple)):
if name in v:
res.append([name, cat])
elif name == v:
res.append([name, cat])
return ans

[dokumentace]
@write_api def embed_metadata(self, book_ids, only_fmts=None, report_error=None, report_progress=None):
''' Update metadata in all formats of the specified book_ids to current metadata in the database. '''
field = self.fields['formats']
from calibre.customize.ui import apply_null_metadata from calibre.ebooks.metadata.meta import set_metadata from calibre.ebooks.metadata.opf2 import pretty_print if only_fmts:
only_fmts = {f.lower() for f in only_fmts}

def doit(fmt, mi, stream):
with apply_null_metadata, pretty_print:
set_metadata(stream, mi, stream_type=fmt, report_error=report_error)
stream.seek(0, os.SEEK_END)
return stream.tell()

for i, book_id in enumerate(book_ids):
fmts = field.table.book_col_map.get(book_id, ())
if not fmts:
continue mi = self._get_metadata(book_id)
buf = BytesIO()
if not self._copy_cover_to(book_id, buf):
return cdata = buf.getvalue()
if cdata:
mi.cover_data = ('jpeg', cdata)
try:
path = self._field_for('path', book_id).replace('/', os.sep)
except:
continue for fmt in fmts:
if only_fmts is not None and fmt.lower() not in only_fmts:
continue try:
name = self.fields['formats'].format_fname(book_id, fmt)
except:
continue if name and path:
try:
new_size = self.backend.apply_to_format(book_id, path, name, fmt, partial(doit, fmt, mi))
except Exception as e:
if report_error is not None:
tb = traceback.format_exc()
if iswindows and isinstance(e, PermissionError) and e.filename and isinstance(e.filename, str):
from calibre_extensions import winutil try:
p = winutil.get_processes_using_files(e.filename)
except OSError:
pass else:
path_map = {x['path']: x for x in p}
tb = _('Could not open the file: "{}". It is already opened in the following programs:').format(e.filename)
for path, x in path_map.items():
tb += '\n' + f'{x["app_name"]}: {path}'
report_error(mi, fmt, tb)
new_size = None else:
raise if new_size is not None:
self.format_metadata_cache[book_id].get(fmt, {})['size'] = new_size max_size = self.fields['formats'].table.update_fmt(book_id, fmt, name, new_size, self.backend)
self.fields['size'].table.update_sizes({book_id: max_size})
if report_progress is not None:
report_progress(i+1, len(book_ids), mi)@read_api def get_last_read_positions(self, book_id, fmt, user):
fmt = fmt.upper()
ans = []
for device, cfi, epoch, pos_frac in self.backend.execute(
'SELECT device,cfi,epoch,pos_frac FROM last_read_positions WHERE book=? AND format=? AND user=?',
(book_id, fmt, user)):
ans.append({'device':device, 'cfi': cfi, 'epoch':epoch, 'pos_frac':pos_frac})
return ans

@write_api def set_last_read_position(self, book_id, fmt, user='_', device='_', cfi=None, epoch=None, pos_frac=0):
fmt = fmt.upper()
device = device or '_'
user = user or '_'
if not cfi:
self.backend.execute(
'DELETE FROM last_read_positions WHERE book=? AND format=? AND user=? AND device=?',
(book_id, fmt, user, device))
else:
self.backend.execute(
'INSERT OR REPLACE INTO last_read_positions(book,format,user,device,cfi,epoch,pos_frac) VALUES (?,?,?,?,?,?,?)',
(book_id, fmt, user, device, cfi, epoch or time(), pos_frac))

@read_api def export_library(self, library_key, exporter, progress=None, abort=None):
from polyglot.binary import as_hex_unicode key_prefix = as_hex_unicode(library_key)
book_ids = self._all_book_ids()
total = len(book_ids) + 2 has_fts = self.is_fts_enabled()
if has_fts:
total += 1 poff = 0 def report_progress(fname):
nonlocal poff if progress is not None:
progress(fname, poff, total)
poff += 1 report_progress('metadata.db')
pt = PersistentTemporaryFile('-export.db')
pt.close()
self.backend.backup_database(pt.name)
dbkey = key_prefix + ':::' + 'metadata.db'
with open(pt.name, 'rb') as f:
exporter.add_file(f, dbkey)
os.remove(pt.name)
if has_fts:
report_progress('full-text-search.db')
pt = PersistentTemporaryFile('-export.db')
pt.close()
self.backend.backup_fts_database(pt.name)
ftsdbkey = key_prefix + ':::full-text-search.db'
with open(pt.name, 'rb') as f:
exporter.add_file(f, ftsdbkey)
os.remove(pt.name)
notesdbkey = key_prefix + ':::notes.db'
with PersistentTemporaryFile('-export.db') as pt:
self.backend.export_notes_data(pt)
pt.flush()
pt.seek(0)
report_progress('notes.db')
exporter.add_file(pt, notesdbkey)

format_metadata = {}
extra_files = {}
metadata = {'format_data':format_metadata, 'metadata.db':dbkey, 'notes.db': notesdbkey, 'total':total, 'extra_files': extra_files}
if has_fts:
metadata['full-text-search.db'] = ftsdbkey for i, book_id in enumerate(book_ids):
if abort is not None and abort.is_set():
return if progress is not None:
report_progress(self._field_for('title', book_id))
format_metadata[book_id] = fm = {}
for fmt in self._formats(book_id):
mdata = self.format_metadata(book_id, fmt)
key = f'{key_prefix}:{book_id}:{fmt}'
fm[fmt] = key mtime = mdata.get('mtime')
if mtime is not None:
mtime = timestampfromdt(mtime)
with exporter.start_file(key, mtime=mtime) as dest:
self._copy_format_to(book_id, fmt, dest)
cover_key = '{}:{}:{}'.format(key_prefix, book_id, '.cover')
with exporter.start_file(cover_key) as dest:
if not self.copy_cover_to(book_id, dest):
dest.discard()
else:
fm['.cover'] = cover_key bp = self.field_for('path', book_id)
extra_files[book_id] = ef = {}
if bp:
for (relpath, fobj, stat_result) in self.backend.iter_extra_files(book_id, bp, self.fields['formats']):
key = f'{key_prefix}:{book_id}:.|{relpath}'
with exporter.start_file(key, mtime=stat_result.st_mtime) as dest:
shutil.copyfileobj(fobj, dest)
ef[relpath] = key exporter.set_metadata(library_key, metadata)
if progress is not None:
progress(_('Completed'), total, total)

@read_api def annotations_map_for_book(self, book_id, fmt, user_type='local', user='viewer'):
ans = {}
for annot in self.backend.annotations_for_book(book_id, fmt, user_type, user):
ans.setdefault(annot['type'], []).append(annot)
return ans

@read_api def all_annotations_for_book(self, book_id):
return tuple(self.backend.all_annotations_for_book(book_id))

@read_api def annotation_count_for_book(self, book_id):
return self.backend.annotation_count_for_book(book_id)

@read_api def all_annotation_users(self):
return tuple(self.backend.all_annotation_users())

@read_api def all_annotation_types(self):
return tuple(self.backend.all_annotation_types())

@read_api def all_annotations(self, restrict_to_user=None, limit=None, annotation_type=None, ignore_removed=False, restrict_to_book_ids=None):
return tuple(self.backend.all_annotations(restrict_to_user, limit, annotation_type, ignore_removed, restrict_to_book_ids))

@read_api def search_annotations( self, fts_engine_query, use_stemming=True, highlight_start=None, highlight_end=None, snippet_size=None, annotation_type=None, restrict_to_book_ids=None, restrict_to_user=None, ignore_removed=False
):
return tuple(self.backend.search_annotations( fts_engine_query, use_stemming, highlight_start, highlight_end, snippet_size, annotation_type, restrict_to_book_ids, restrict_to_user, ignore_removed
))

@write_api def delete_annotations(self, annot_ids):
self.backend.delete_annotations(annot_ids)

@write_api def update_annotations(self, annot_id_map):
self.backend.update_annotations(annot_id_map)

@write_api def restore_annotations(self, book_id, annotations):
from calibre.utils.date import EPOCH from calibre.utils.iso8601 import parse_iso8601 umap = defaultdict(list)
for adata in annotations:
key = adata['user_type'], adata['user'], adata['format']
a = adata['annotation']
ts = (parse_iso8601(a['timestamp']) - EPOCH).total_seconds()
umap[key].append((a, ts))
for (user_type, user, fmt), annots_list in iteritems(umap):
self._set_annotations_for_book(book_id, fmt, annots_list, user_type=user_type, user=user)

@write_api def set_annotations_for_book(self, book_id, fmt, annots_list, user_type='local', user='viewer'):
self.backend.set_annotations_for_book(book_id, fmt, annots_list, user_type, user)

@write_api def merge_annotations_for_book(self, book_id, fmt, annots_list, user_type='local', user='viewer'):
from calibre.utils.date import EPOCH from calibre.utils.iso8601 import parse_iso8601 amap = self._annotations_map_for_book(book_id, fmt, user_type=user_type, user=user)
merge_annotations(annots_list, amap)
alist = []
for val in itervalues(amap):
for annot in val:
ts = (parse_iso8601(annot['timestamp']) - EPOCH).total_seconds()
alist.append((annot, ts))
self._set_annotations_for_book(book_id, fmt, alist, user_type=user_type, user=user)

@write_api def reindex_annotations(self):
self.backend.reindex_annotations()

@read_api def are_paths_inside_book_dir(self, book_id, paths, sub_path=''):
try:
path = self._field_for('path', book_id).replace('/', os.sep)
except:
return set()
return {x for x in paths if self.backend.is_path_inside_book_dir(x, path, sub_path)}

[dokumentace]
@write_api def add_extra_files(self, book_id, map_of_relpath_to_stream_or_path, replace=True, auto_rename=False):
' Add extra data files '
path = self._field_for('path', book_id).replace('/', os.sep)
added = {}
for relpath, stream_or_path in map_of_relpath_to_stream_or_path.items():
added[relpath] = bool(self.backend.add_extra_file(relpath, stream_or_path, path, replace, auto_rename))
self._clear_extra_files_cache(book_id)
return added

[dokumentace]
@write_api def rename_extra_files(self, book_id, map_of_relpath_to_new_relpath, replace=False):
' Rename extra data files '
path = self._field_for('path', book_id).replace('/', os.sep)
renamed = set()
for relpath, newrelpath in map_of_relpath_to_new_relpath.items():
if self.backend.rename_extra_file(relpath, newrelpath, path, replace):
renamed.add(relpath)
self._clear_extra_files_cache(book_id)
return renamed

[dokumentace]
@write_api def merge_extra_files(self, dest_id, src_ids, replace=False):
' Merge the extra files from src_ids into dest_id. Conflicting files are auto-renamed unless replace=True in which case they are replaced. '
added = set()
path = self._field_for('path', dest_id)
if path:
path = path.replace('/', os.sep)
for src_id in src_ids:
book_path = self._field_for('path', src_id)
if book_path:
book_path = book_path.replace('/', os.sep)
for (relpath, file_path, stat_result) in self.backend.iter_extra_files( src_id, book_path, self.fields['formats'], yield_paths=True):
added.add(self.backend.add_extra_file(relpath, file_path, path, replace=replace, auto_rename=True))
self._clear_extra_files_cache(dest_id)
return added

[dokumentace]
@read_api def list_extra_files(self, book_id, use_cache=False, pattern='') -> Tuple[ExtraFile, ...]:
'''
Get information about extra files in the book's directory.

:param book_id: the database book id for the book
:param pattern: the pattern of filenames to search for. Empty pattern matches all extra files. Patterns must use / as separator.
Use the DATA_FILE_PATTERN constant to match files inside the data directory.

:return: A tuple of all extra files matching the specified pattern. Each element of the tuple is
ExtraFile(relpath, file_path, stat_result). Where relpath is the relative path of the file to the book directory using / as a separator.
stat_result is the result of calling os.stat() on the file.
'''
ans = self.extra_files_cache.setdefault(book_id, {}).get(pattern)
if ans is None or not use_cache:
ans = []
path = self._field_for('path', book_id)
if path:
for (relpath, file_path, stat_result) in self.backend.iter_extra_files( book_id, path, self.fields['formats'], yield_paths=True, pattern=pattern
):
ans.append(ExtraFile(relpath, file_path, stat_result))
self.extra_files_cache[book_id][pattern] = ans = tuple(ans)
return ans

@read_api def copy_extra_file_to(self, book_id, relpath, stream_or_path):
path = self._field_for('path', book_id).replace('/', os.sep)
self.backend.copy_extra_file_to(book_id, path, relpath, stream_or_path)

@write_api def merge_book_metadata(self, dest_id, src_ids, replace_cover=False):
dest_mi = self.get_metadata(dest_id)
merged_identifiers = self._field_for('identifiers', dest_id) or {}
orig_dest_comments = dest_mi.comments dest_cover = orig_dest_cover = self.cover(dest_id)
had_orig_cover = bool(dest_cover)
from calibre.utils.date import is_date_undefined def is_null_date(x):
return x is None or is_date_undefined(x)

for src_id in src_ids:
src_mi = self.get_metadata(src_id)

if src_mi.comments and orig_dest_comments != src_mi.comments:
if not dest_mi.comments:
dest_mi.comments = src_mi.comments else:
dest_mi.comments = str(dest_mi.comments) + '\n\n' + str(src_mi.comments)
if src_mi.title and dest_mi.is_null('title'):
dest_mi.title = src_mi.title dest_mi.title_sort = src_mi.title_sort if (src_mi.authors and src_mi.authors[0] != _('Unknown')) and (not dest_mi.authors or dest_mi.authors[0] == _('Unknown')):
dest_mi.authors = src_mi.authors dest_mi.author_sort = src_mi.author_sort if src_mi.tags:
if not dest_mi.tags:
dest_mi.tags = src_mi.tags else:
dest_mi.tags.extend(src_mi.tags)
if not dest_cover or replace_cover:
src_cover = self.cover(src_id)
if src_cover:
dest_cover = src_cover replace_cover = False if not dest_mi.publisher:
dest_mi.publisher = src_mi.publisher if not dest_mi.rating:
dest_mi.rating = src_mi.rating if not dest_mi.series:
dest_mi.series = src_mi.series dest_mi.series_index = src_mi.series_index if is_null_date(dest_mi.pubdate) and not is_null_date(src_mi.pubdate):
dest_mi.pubdate = src_mi.pubdate src_identifiers = self.field_for('identifier', src_id) or {}
src_identifiers.update(merged_identifiers)
merged_identifiers = src_identifiers.copy()

if merged_identifiers:
dest_mi.set_identifiers(merged_identifiers)
self._set_metadata(dest_id, dest_mi, ignore_errors=False)

if dest_cover and (not had_orig_cover or dest_cover is not orig_dest_cover):
self._set_cover({dest_id: dest_cover})

for key in self.field_metadata: # loop thru all defined fields fm = self.field_metadata[key]
if not fm['is_custom']:
continue dt = fm['datatype']
label = fm['label']
try:
field = self.field_metadata.label_to_key(label)
except ValueError:
continue
# Get orig_dest_comments before it gets changed if dt == 'comments':
orig_dest_value = self._field_for(field, dest_id)

for src_id in src_ids:
dest_value = self._field_for(field, dest_id)
src_value = self._field_for(field, src_id)
if (dt == 'comments' and src_value and src_value != orig_dest_value):
if not dest_value:
self._set_field(field, {dest_id: src_value})
else:
dest_value = str(dest_value) + '\n\n' + str(src_value)
self._set_field(field, {dest_id: dest_value})
if (dt in {'bool', 'int', 'float', 'rating', 'datetime'} and dest_value is None):
self._set_field(field, {dest_id: src_value})
if (dt == 'series' and not dest_value and src_value):
src_index = self._field_for(field + '_index', src_id)
self._set_field(field, {dest_id:src_value})
self._set_field(field + '_index', {dest_id:src_index})
if ((dt == 'enumeration' or (dt == 'text' and not fm['is_multiple'])) and not dest_value):
self._set_field(field, {dest_id:src_value})
if (dt == 'text' and fm['is_multiple'] and src_value):
if not dest_value:
dest_value = src_value else:
dest_value = list(dest_value)
dest_value.extend(src_value)
self._set_field(field, {dest_id: dest_value})def import_library(library_key, importer, library_path, progress=None, abort=None):
from calibre.db.backend import DB metadata = importer.metadata[library_key]
total = metadata['total']
poff = 0 def report_progress(fname):
nonlocal poff if progress is not None:
progress(fname, poff, total)
poff += 1 report_progress('metadata.db')
if abort is not None and abort.is_set():
return importer.save_file(metadata['metadata.db'], 'metadata.db for ' + library_path, os.path.join(library_path, 'metadata.db'))
if 'full-text-search.db' in metadata:
if progress is not None:
progress('full-text-search.db', 1, total)
if abort is not None and abort.is_set():
return poff += 1 importer.save_file(metadata['full-text-search.db'], 'full-text-search.db for ' + library_path, os.path.join(library_path, 'full-text-search.db'))
if abort is not None and abort.is_set():
return if 'notes.db' in metadata:
import zipfile notes_dir = os.path.join(library_path, NOTES_DIR_NAME)
os.makedirs(notes_dir, exist_ok=True)
with importer.start_file(metadata['notes.db'], 'notes.db for ' + library_path) as stream:
stream.check_hash = False with zipfile.ZipFile(stream) as zf:
for zi in zf.infolist():
tpath = zf._extract_member(zi, notes_dir, None)
date_time = mktime(zi.date_time + (0, 0, -1))
os.utime(tpath, (date_time, date_time))
if abort is not None and abort.is_set():
return if importer.corrupted_files:
raise ValueError('Corrupted files:\n' + '\n'.join(importer.corrupted_files))
cache = Cache(DB(library_path, load_user_formatter_functions=False))
cache.init()

format_data = {int(book_id):data for book_id, data in iteritems(metadata['format_data'])}
extra_files = {int(book_id):data for book_id, data in metadata.get('extra_files', {}).items()}
for i, (book_id, fmt_key_map) in enumerate(iteritems(format_data)):
if abort is not None and abort.is_set():
return title = cache._field_for('title', book_id)
if progress is not None:
progress(title, i + poff, total)
cache._update_path((book_id,), mark_as_dirtied=False)
for fmt, fmtkey in fmt_key_map.items():
if fmt == '.cover':
with importer.start_file(fmtkey, _('Cover for %s') % title) as stream:
path = cache._field_for('path', book_id).replace('/', os.sep)
cache.backend.set_cover(book_id, path, stream, no_processing=True)
else:
with importer.start_file(fmtkey, _('{0} format for {1}').format(fmt.upper(), title)) as stream:
size, fname = cache._do_add_format(book_id, fmt, stream, mtime=stream.mtime)
cache.fields['formats'].table.update_fmt(book_id, fmt, fname, size, cache.backend)
for relpath, efkey in extra_files.get(book_id, {}).items():
with importer.start_file(efkey, _('Extra file {0} for book {1}').format(relpath, title)) as stream:
path = cache._field_for('path', book_id).replace('/', os.sep)
cache.backend.add_extra_file(relpath, stream, path)
cache.dump_metadata({book_id})
if importer.corrupted_files:
raise ValueError('Corrupted files:\n' + '\n'.join(importer.corrupted_files))
if progress is not None:
progress(_('Completed'), total, total)
return cache
# }}}

© Copyright Kovid Goyal.
Naposledy aktualizováno kvě 03, 2024.

Navigace

- moduly
- Spustit »
- Kód modulu »
- calibre.devices.interface

Zdrojový kód pro calibre.devices.interface

__license__ = 'GPL v3'
__copyright__ = '2008, Kovid Goyal <kovid at kovidgoyal.net>'
import os from collections import namedtuple from calibre import prints from calibre.constants import iswindows from calibre.customize import Plugin class OpenPopupMessage:

def __init__(self, title='', message='', level='info', skip_dialog_skip_precheck=True):
self.title = title self.message = message self.level = level self.skip_dialog_skip_precheck = skip_dialog_skip_precheck

[dokumentace]
class DevicePlugin(Plugin):
"""
Defines the interface that should be implemented by backends that communicate with an e-book reader.
"""
type = _('Device interface')

#: Ordered list of supported formats
FORMATS = ["lrf", "rtf", "pdf", "txt"]
# If True, the config dialog will not show the formats box
HIDE_FORMATS_CONFIG_BOX = False

#: VENDOR_ID can be either an integer, a list of integers or a dictionary
#: If it is a dictionary, it must be a dictionary of dictionaries,
#: of the form::
#:
#: {
#: integer_vendor_id : { product_id : [list of BCDs], ... },
#: ...
#: }
#:
VENDOR_ID = 0x0000

#: An integer or a list of integers
PRODUCT_ID = 0x0000
#: BCD can be either None to not distinguish between devices based on BCD, or
#: it can be a list of the BCD numbers of all devices supported by this driver.
BCD = None

#: Height for thumbnails on the device
THUMBNAIL_HEIGHT = 68

#: Compression quality for thumbnails. Set this closer to 100 to have better
#: quality thumbnails with fewer compression artifacts. Of course, the
#: thumbnails get larger as well.
THUMBNAIL_COMPRESSION_QUALITY = 75

#: Set this to True if the device supports updating cover thumbnails during
#: sync_booklists. Setting it to true will ask device.py to refresh the
#: cover thumbnails during book matching
WANTS_UPDATED_THUMBNAILS = False

#: Whether the metadata on books can be set via the GUI.
CAN_SET_METADATA = ['title', 'authors', 'collections']

#: Whether the device can handle device_db metadata plugboards
CAN_DO_DEVICE_DB_PLUGBOARD = False

# Set this to None if the books on the device are files that the GUI can
# access in order to add the books from the device to the library
BACKLOADING_ERROR_MESSAGE = _('Cannot get files from this device')

#: Path separator for paths to books on device path_sep = os.sep

#: Icon for this device icon = 'reader.png'

# Encapsulates an annotation fetched from the device
UserAnnotation = namedtuple('Annotation','type, value')

#: GUI displays this as a message if not None in the status bar. Useful if opening can take a
#: long time
OPEN_FEEDBACK_MESSAGE = None

#: Set of extensions that are "virtual books" on the device
#: and therefore cannot be viewed/saved/added to library.
#: For example: ``frozenset(['kobo'])``
VIRTUAL_BOOK_EXTENSIONS = frozenset()

#: Message to display to user for virtual book extensions.
VIRTUAL_BOOK_EXTENSION_MESSAGE = None

#: Whether to nuke comments in the copy of the book sent to the device. If
#: not None this should be short string that the comments will be replaced
#: by.
NUKE_COMMENTS = None

#: If True indicates that this driver completely manages device detection,
#: ejecting and so forth. If you set this to True, you *must* implement the
#: detect_managed_devices and debug_managed_device_detection methods.
#: A driver with this set to true is responsible for detection of devices,
#: managing a blacklist of devices, a list of ejected devices and so forth.
#: calibre will periodically call the detect_managed_devices() method and
#: if it returns a detected device, calibre will call open(). open() will
#: be called every time a device is returned even if previous calls to open()
#: failed, therefore the driver must maintain its own blacklist of failed
#: devices. Similarly, when ejecting, calibre will call eject() and then
#: assuming the next call to detect_managed_devices() returns None, it will
#: call post_yank_cleanup().
MANAGES_DEVICE_PRESENCE = False

#: If set the True, calibre will call the :meth:`get_driveinfo()` method
#: after the books lists have been loaded to get the driveinfo.
SLOW_DRIVEINFO = False

#: If set to True, calibre will ask the user if they want to manage the
#: device with calibre, the first time it is detected. If you set this to
#: True you must implement :meth:`get_device_uid()` and
#: :meth:`ignore_connected_device()` and
#: :meth:`get_user_blacklisted_devices` and
#: :meth:`set_user_blacklisted_devices`
ASK_TO_ALLOW_CONNECT = False

#: Set this to a dictionary of the form {'title':title, 'msg':msg, 'det_msg':detailed_msg} to have calibre popup
#: a message to the user after some callbacks are run (currently only upload_books).
#: Be careful to not spam the user with too many messages. This variable is checked after *every* callback,
#: so only set it when you really need to.
user_feedback_after_callback = None

@classmethod def get_gui_name(cls):
if hasattr(cls, 'gui_name'):
return cls.gui_name if hasattr(cls, '__name__'):
return cls.__name__ return cls.name

[dokumentace]
@classmethod def get_open_popup_message(self):
' GUI displays this as a non-modal popup. Should be an instance of OpenPopupMessage '
return None

# Device detection {{{ def test_bcd(self, bcdDevice, bcd):
if bcd is None or len(bcd) == 0:
return True for c in bcd:
if c == bcdDevice:
return True return False

[dokumentace]
def is_usb_connected(self, devices_on_system, debug=False, only_presence=False):
'''
Return True, device_info if a device handled by this plugin is currently connected.

:param devices_on_system: List of devices currently connected

'''
vendors_on_system = {x[0] for x in devices_on_system}
vendors = set(self.VENDOR_ID) if hasattr(self.VENDOR_ID, '__len__') else {self.VENDOR_ID}
if hasattr(self.VENDOR_ID, 'keys'):
products = []
for ven in self.VENDOR_ID:
products.extend(self.VENDOR_ID[ven].keys())
else:
products = self.PRODUCT_ID if hasattr(self.PRODUCT_ID, '__len__') else [self.PRODUCT_ID]

ch = self.can_handle_windows if iswindows else self.can_handle for vid in vendors_on_system.intersection(vendors):
for dev in devices_on_system:
cvid, pid, bcd = dev[:3]
if cvid == vid:
if pid in products:
if hasattr(self.VENDOR_ID, 'keys'):
try:
cbcd = self.VENDOR_ID[vid][pid]
except KeyError:
# Vendor vid does not have product pid, pid
# exists for some other vendor in this
# device continue else:
cbcd = self.BCD if self.test_bcd(bcd, cbcd):
if debug:
prints(dev)
if ch(dev, debug=debug):
return True, dev return False, None

[dokumentace]
def detect_managed_devices(self, devices_on_system, force_refresh=False):
'''
Called only if MANAGES_DEVICE_PRESENCE is True.

Scan for devices that this driver can handle. Should return a device object if a device is found. This object will be passed to the open()
method as the connected_device. If no device is found, return None. The returned object can be anything, calibre does not use it, it is only passed to open().

This method is called periodically by the GUI, so make sure it is not too resource intensive. Use a cache to avoid repeatedly scanning the system.

:param devices_on_system: Set of USB devices found on the system.

:param force_refresh: If True and the driver uses a cache to prevent repeated scanning, the cache must be flushed.

'''
raise NotImplementedError()

[dokumentace]
def debug_managed_device_detection(self, devices_on_system, output):
'''
Called only if MANAGES_DEVICE_PRESENCE is True.

Should write information about the devices detected on the system to output, which is a file like object.

Should return True if a device was detected and successfully opened, otherwise False.
'''
raise NotImplementedError()

# }}}

[dokumentace]
def reset(self, key='-1', log_packets=False, report_progress=None, detected_device=None):
"""
:param key: The key to unlock the device
:param log_packets: If true the packet stream to/from the device is logged
:param report_progress: Function that is called with a % progress
(number between 0 and 100) for various tasks.
If it is called with -1 that means that the task does not have any progress information
:param detected_device: Device information from the device scanner

"""
raise NotImplementedError()

[dokumentace]
def can_handle_windows(self, usbdevice, debug=False):
'''
Optional method to perform further checks on a device to see if this driver is capable of handling it. If it is not it should return False. This method is only called after the vendor, product ids and the bcd have matched, so it can do some relatively time intensive checks. The default implementation returns True. This method is called only on Windows. See also
:meth:`can_handle`.

Note that for devices based on USBMS this method by default delegates to :meth:`can_handle`. So you only need to override :meth:`can_handle` in your subclass of USBMS.

:param usbdevice: A usbdevice as returned by :func:`calibre.devices.winusb.scan_usb_devices`
'''
return True

[dokumentace]
def can_handle(self, device_info, debug=False):
'''
Unix version of :meth:`can_handle_windows`.

:param device_info: Is a tuple of (vid, pid, bcd, manufacturer, product, serial number)

'''

return True
can_handle.is_base_class_implementation = True

[dokumentace]
def open(self, connected_device, library_uuid):
'''
Perform any device specific initialization. Called after the device is detected but before any other functions that communicate with the device.
For example: For devices that present themselves as USB Mass storage devices, this method would be responsible for mounting the device or if the device has been automounted, for finding out where it has been mounted. The method :meth:`calibre.devices.usbms.device.Device.open` has an implementation of this function that should serve as a good example for USB Mass storage devices.

This method can raise an OpenFeedback exception to display a message to the user.

:param connected_device: The device that we are trying to open. It is a tuple of (vendor id, product id, bcd, manufacturer name, product name, device serial number). However, some devices have no serial number and on Windows only the first three fields are present, the rest are None.

:param library_uuid: The UUID of the current calibre library. Can be
None if there is no library (for example when used from the command line).

'''
raise NotImplementedError()

[dokumentace]
def eject(self):
'''
Un-mount / eject the device from the OS. This does not check if there are pending GUI jobs that need to communicate with the device.

NOTE: That this method may not be called on the same thread as the rest of the device methods.
'''
raise NotImplementedError()

[dokumentace]
def post_yank_cleanup(self):
'''
Called if the user yanks the device without ejecting it first.
'''
raise NotImplementedError()

[dokumentace]
def set_progress_reporter(self, report_progress):
'''
Set a function to report progress information.

:param report_progress: Function that is called with a % progress
(number between 0 and 100) for various tasks.
If it is called with -1 that means that the task does not have any progress information

'''
raise NotImplementedError()

[dokumentace]
def get_device_information(self, end_session=True):
"""
Ask device for device information. See L{DeviceInfoQuery}.

:return: (device name, device version, software version on device, MIME type)
The tuple can optionally have a fifth element, which is a drive information dictionary. See usbms.driver for an example.

"""
raise NotImplementedError()

[dokumentace]
def get_driveinfo(self):
'''
Return the driveinfo dictionary. Usually called from get_device_information(), but if loading the driveinfo is slow for this driver, then it should set SLOW_DRIVEINFO. In this case, this method will be called by calibre after the book lists have been loaded. Note that it is not called on the device thread, so the driver should cache the drive info in the books() method and this function should return the cached data.
'''
return {}

[dokumentace]
def card_prefix(self, end_session=True):
'''
Return a 2 element list of the prefix to paths on the cards.
If no card is present None is set for the card's prefix.
E.G.
('/place', '/place2')
(None, 'place2')
('place', None)
(None, None)
'''
raise NotImplementedError()

[dokumentace]
def total_space(self, end_session=True):
"""
Get total space available on the mountpoints:
1. Main memory
2. Memory Card A
3. Memory Card B

:return: A 3 element list with total space in bytes of (1, 2, 3). If a particular device doesn't have any of these locations it should return 0.

"""
raise NotImplementedError()

[dokumentace]
def free_space(self, end_session=True):
"""
Get free space available on the mountpoints:
1. Main memory
2. Card A
3. Card B

:return: A 3 element list with free space in bytes of (1, 2, 3). If a particular device doesn't have any of these locations it should return -1.

"""
raise NotImplementedError()

[dokumentace]
def books(self, oncard=None, end_session=True):
"""
Return a list of e-books on the device.

:param oncard: If 'carda' or 'cardb' return a list of e-books on the specific storage card, otherwise return list of e-books in main memory of device. If a card is specified and no books are on the card return empty list.

:return: A BookList.

"""
raise NotImplementedError()

[dokumentace]
def upload_books(self, files, names, on_card=None, end_session=True, metadata=None):
'''
Upload a list of books to the device. If a file already exists on the device, it should be replaced.
This method should raise a :class:`FreeSpaceError` if there is not enough free space on the device. The text of the FreeSpaceError must contain the word "card" if ``on_card`` is not None otherwise it must contain the word "memory".

:param files: A list of paths
:param names: A list of file names that the books should have once uploaded to the device. len(names) == len(files)
:param metadata: If not None, it is a list of :class:`Metadata` objects.
The idea is to use the metadata to determine where on the device to put the book. len(metadata) == len(files). Apart from the regular cover (path to cover), there may also be a thumbnail attribute, which should be used in preference. The thumbnail attribute is of the form
(width, height, cover_data as jpeg).

:return: A list of 3-element tuples. The list is meant to be passed to :meth:`add_books_to_metadata`.
'''
raise NotImplementedError()

[dokumentace]
@classmethod def add_books_to_metadata(cls, locations, metadata, booklists):
'''
Add locations to the booklists. This function must not communicate with the device.

:param locations: Result of a call to L{upload_books}
:param metadata: List of :class:`Metadata` objects, same as for
:meth:`upload_books`.
:param booklists: A tuple containing the result of calls to
(:meth:`books(oncard=None)`,
:meth:`books(oncard='carda')`,
:meth`books(oncard='cardb')`).

'''
raise NotImplementedError()

[dokumentace]
def delete_books(self, paths, end_session=True):
'''
Delete books at paths on device.
'''
raise NotImplementedError()

[dokumentace]
@classmethod def remove_books_from_metadata(cls, paths, booklists):
'''
Remove books from the metadata list. This function must not communicate with the device.

:param paths: paths to books on the device.
:param booklists: A tuple containing the result of calls to
(:meth:`books(oncard=None)`,
:meth:`books(oncard='carda')`,
:meth`books(oncard='cardb')`).

'''
raise NotImplementedError()

[dokumentace]
def sync_booklists(self, booklists, end_session=True):
'''
Update metadata on device.

:param booklists: A tuple containing the result of calls to
(:meth:`books(oncard=None)`,
:meth:`books(oncard='carda')`,
:meth`books(oncard='cardb')`).

'''
raise NotImplementedError()

[dokumentace]
def get_file(self, path, outfile, end_session=True):
'''
Read the file at ``path`` on the device and write it to outfile.

:param outfile: file object like ``sys.stdout`` or the result of an
:func:`open` call.

'''
raise NotImplementedError()

[dokumentace]
@classmethod def config_widget(cls):
'''
Should return a QWidget. The QWidget contains the settings for the device interface
'''
raise NotImplementedError()

[dokumentace]
@classmethod def save_settings(cls, settings_widget):
'''
Should save settings to disk. Takes the widget created in
:meth:`config_widget` and saves all settings to disk.
'''
raise NotImplementedError()

[dokumentace]
@classmethod def settings(cls):
'''
Should return an opts object. The opts object should have at least one attribute `format_map` which is an ordered list of formats for the device.
'''
raise NotImplementedError()

[dokumentace]
def set_plugboards(self, plugboards, pb_func):
'''
provide the driver the current set of plugboards and a function to select a specific plugboard. This method is called immediately before add_books and sync_booklists.

pb_func is a callable with the following signature::
def pb_func(device_name, format, plugboards)

You give it the current device name (either the class name or
DEVICE_PLUGBOARD_NAME), the format you are interested in (a 'real'
format or 'device_db'), and the plugboards (you were given those by set_plugboards, the same place you got this method).

:return: None or a single plugboard instance.

'''
pass

[dokumentace]
def set_driveinfo_name(self, location_code, name):
'''
Set the device name in the driveinfo file to 'name'. This setting will persist until the file is re-created or the name is changed again.

Non-disk devices should implement this method based on the location codes returned by the get_device_information() method.
'''
pass

[dokumentace]
def prepare_addable_books(self, paths):
'''
Given a list of paths, returns another list of paths. These paths point to addable versions of the books.

If there is an error preparing a book, then instead of a path, the position in the returned list for that book should be a three tuple:
(original_path, the exception instance, traceback)
'''
return paths

[dokumentace]
def startup(self):
'''
Called when calibre is starting the device. Do any initialization required. Note that multiple instances of the class can be instantiated, and thus __init__ can be called multiple times, but only one instance will have this method called. This method is called on the device thread, not the GUI thread.
'''
pass

[dokumentace]
def shutdown(self):
'''
Called when calibre is shutting down, either for good or in preparation to restart. Do any cleanup required. This method is called on the device thread, not the GUI thread.
'''
pass

[dokumentace]
def get_device_uid(self):
'''
Must return a unique id for the currently connected device (this is called immediately after a successful call to open()). You must implement this method if you set ASK_TO_ALLOW_CONNECT = True
'''
raise NotImplementedError()

[dokumentace]
def ignore_connected_device(self, uid):
'''
Should ignore the device identified by uid (the result of a call to get_device_uid()) in the future. You must implement this method if you set ASK_TO_ALLOW_CONNECT = True. Note that this function is called immediately after open(), so if open() caches some state, the driver should reset that state.
'''
raise NotImplementedError()

[dokumentace]
def get_user_blacklisted_devices(self):
'''
Return map of device uid to friendly name for all devices that the user has asked to be ignored.
'''
return {}

[dokumentace]
def set_user_blacklisted_devices(self, devices):
'''
Set the list of device uids that should be ignored by this driver.
'''
pass

[dokumentace]
def specialize_global_preferences(self, device_prefs):
'''
Implement this method if your device wants to override a particular preference. You must ensure that all call sites that want a preference that can be overridden use device_prefs['something'] instead of prefs['something']. Your method should call device_prefs.set_overrides(pref=val, pref=val, ...).
Currently used for:
metadata management (prefs['manage_device_metadata'])
'''
device_prefs.set_overrides()

[dokumentace]
def set_library_info(self, library_name, library_uuid, field_metadata):
'''
Implement this method if you want information about the current calibre library. This method is called at startup and when the calibre library changes while connected.
'''
pass

# Dynamic control interface.
# The following methods are probably called on the GUI thread. Any driver
# that implements these methods must take pains to be thread safe, because
# the device_manager might be using the driver at the same time that one of
# these methods is called.

[dokumentace]
def is_dynamically_controllable(self):
'''
Called by the device manager when starting plugins. If this method returns a string, then a) it supports the device manager's dynamic control interface, and b) that name is to be used when talking to the plugin.

This method can be called on the GUI thread. A driver that implements this method must be thread safe.
'''
return None

[dokumentace]
def start_plugin(self):
'''
This method is called to start the plugin. The plugin should begin to accept device connections however it does that. If the plugin is already accepting connections, then do nothing.

This method can be called on the GUI thread. A driver that implements this method must be thread safe.
'''
pass

[dokumentace]
def stop_plugin(self):
'''
This method is called to stop the plugin. The plugin should no longer accept connections, and should cleanup behind itself. It is likely that this method should call shutdown. If the plugin is already not accepting connections, then do nothing.

This method can be called on the GUI thread. A driver that implements this method must be thread safe.
'''
pass

[dokumentace]
def get_option(self, opt_string, default=None):
'''
Return the value of the option indicated by opt_string. This method can be called when the plugin is not started. Return None if the option does not exist.

This method can be called on the GUI thread. A driver that implements this method must be thread safe.
'''
return default

[dokumentace]
def set_option(self, opt_string, opt_value):
'''
Set the value of the option indicated by opt_string. This method can be called when the plugin is not started.

This method can be called on the GUI thread. A driver that implements this method must be thread safe.
'''
pass

[dokumentace]
def is_running(self):
'''
Return True if the plugin is started, otherwise false

This method can be called on the GUI thread. A driver that implements this method must be thread safe.
'''
return False

[dokumentace]
def synchronize_with_db(self, db, book_id, book_metadata, first_call):
'''
Called during book matching when a book on the device is matched with a book in calibre's db. The method is responsible for synchronizing data from the device to calibre's db (if needed).

The method must return a two-value tuple. The first value is a set of calibre book ids changed if calibre's database was changed or None if the database was not changed. If the first value is an empty set then the metadata for the book on the device is updated with calibre's metadata and given back to the device, but no GUI refresh of that book is done.
This is useful when the calibre data is correct but must be sent to the device.

The second value is itself a 2-value tuple. The first value in the tuple specifies whether a book format should be sent to the device. The intent is to permit verifying that the book on the device is the same as the book in calibre. This value must be None if no book is to be sent, otherwise return the base file name on the device (a string like foobar.epub). Be sure to include the extension in the name. The device subsystem will construct a send_books job for all books with not- None returned values. Note: other than to later retrieve the extension, the name is ignored in cases where the device uses a template to generate the file name, which most do. The second value in the returned tuple indicated whether the format is future-dated. Return True if it is, otherwise return False. calibre will display a dialog to the user listing all future dated books.

Extremely important: this method is called on the GUI thread. It must be threadsafe with respect to the device manager's thread.

book_id: the calibre id for the book in the database.
book_metadata: the Metadata object for the book coming from the device.
first_call: True if this is the first call during a sync, False otherwise
'''
return (None, (None, False))

[dokumentace]
class BookList(list):
'''
A list of books. Each Book object must have the fields

#. title
#. authors
#. size (file size of the book)
#. datetime (a UTC time tuple)
#. path (path on the device to the book)
#. thumbnail (can be None) thumbnail is either a str/bytes object with the image data or it should have an attribute image_path that stores an absolute (platform native) path to the image
#. tags (a list of strings, can be empty).

'''

__getslice__ = None
__setslice__ = None def __init__(self, oncard, prefix, settings):
pass

[dokumentace]
def supports_collections(self):
''' Return True if the device supports collections for this book list. '''
raise NotImplementedError()

[dokumentace]
def add_book(self, book, replace_metadata):
'''
Add the book to the booklist. Intent is to maintain any device-internal metadata. Return True if booklists must be sync'ed
'''
raise NotImplementedError()

[dokumentace]
def remove_book(self, book):
'''
Remove a book from the booklist. Correct any device metadata at the same time
'''
raise NotImplementedError()

[dokumentace]
def get_collections(self, collection_attributes):
'''
Return a dictionary of collections created from collection_attributes.
Each entry in the dictionary is of the form collection name:[list of books]

The list of books is sorted by book title, except for collections created from series, in which case series_index is used.

:param collection_attributes: A list of attributes of the Book object

'''
raise NotImplementedError()

class CurrentlyConnectedDevice:

def __init__(self):
self._device = None

@property def device(self):
return self._device

# A device driver can check if a device is currently connected to calibre using
# the following code::
# from calibre.device.interface import currently_connected_device
# if currently_connected_device.device is None:
# # no device connected
# The device attribute will be either None or the device driver object
# (DevicePlugin instance) for the currently connected device.
currently_connected_device = CurrentlyConnectedDevice()

© Copyright Kovid Goyal.
Naposledy aktualizováno kvě 03, 2024.

Navigace

- moduly
- Spustit »
- Kód modulu »
- calibre.devices.usbms.cli

Zdrojový kód pro calibre.devices.usbms.cli

__license__ = 'GPL v3'
__copyright__ = '2009, John Schember <john@nachtimwald.com>'
__docformat__ = 'restructuredtext en'

import os import shutil import time from calibre import fsync from calibre.devices.errors import PathError from calibre.utils.filenames import case_preserving_open_file class File:

def __init__(self, path):
stats = os.stat(path)
self.is_dir = os.path.isdir(path)
self.is_readonly = not os.access(path, os.W_OK)
self.ctime = stats.st_ctime self.wtime = stats.st_mtime self.size = stats.st_size if path.endswith(os.sep):
path = path[:-1]
self.path = path self.name = os.path.basename(path)

def check_transfer(infile, dest):
infile.seek(0)
dest.seek(0)
return infile.read() == dest.read()

[dokumentace]
class CLI:

def get_file(self, path, outfile, end_session=True):
path = self.munge_path(path)
with open(path, 'rb') as src:
shutil.copyfileobj(src, outfile)

def put_file(self, infile, path, replace_file=False, end_session=True):
path = self.munge_path(path)
close = False if not hasattr(infile, 'read'):
infile, close = open(infile, 'rb'), True infile.seek(0)
if os.path.isdir(path):
path = os.path.join(path, infile.name)
if not replace_file and os.path.exists(path):
raise PathError('File already exists: ' + path)
dest, actual_path = case_preserving_open_file(path)
with dest:
try:
shutil.copyfileobj(infile, dest)
except OSError:
print('WARNING: First attempt to send file to device failed')
time.sleep(0.2)
infile.seek(0)
dest.seek(0)
dest.truncate()
shutil.copyfileobj(infile, dest)
fsync(dest)
# if not check_transfer(infile, dest): raise Exception('Transfer failed')
if close:
infile.close()
return actual_path def munge_path(self, path):
if path.startswith('/') and not (path.startswith(self._main_prefix) or
(self._card_a_prefix and path.startswith(self._card_a_prefix)) or
(self._card_b_prefix and path.startswith(self._card_b_prefix))):
path = self._main_prefix + path[1:]
elif path.startswith('carda:'):
path = path.replace('carda:', self._card_a_prefix[:-1])
elif path.startswith('cardb:'):
path = path.replace('cardb:', self._card_b_prefix[:-1])
return path def list(self, path, recurse=False, end_session=True, munge=True):
if munge:
path = self.munge_path(path)
if os.path.isfile(path):
return [(os.path.dirname(path), [File(path)])]
entries = [File(os.path.join(path, f)) for f in os.listdir(path)]
dirs = [(path, entries)]
for _file in entries:
if recurse and _file.is_dir:
dirs[len(dirs):] = self.list(_file.path, recurse=True, munge=False)
return dirs def mkdir(self, path, end_session=True):
if self.SUPPORTS_SUB_DIRS:
path = self.munge_path(path)
os.mkdir(path)

def rm(self, path, end_session=True):
path = self.munge_path(path)
self.delete_books([path])

def touch(self, path, end_session=True):
path = self.munge_path(path)
if not os.path.exists(path):
open(path, 'wb').close()
if not os.path.isdir(path):
os.utime(path, None)

© Copyright Kovid Goyal.
Naposledy aktualizováno kvě 03, 2024.

Navigace

- moduly
- Spustit »
- Kód modulu »
- calibre.devices.usbms.device

Zdrojový kód pro calibre.devices.usbms.device

__license__ = 'GPL v3'
__copyright__ = '2009, John Schember <john at nachtimwald.com> ' \ '2009, Kovid Goyal <kovid@kovidgoyal.net>'
__docformat__ = 'restructuredtext en'

'''
Generic device driver. This is not a complete stand alone driver. It is intended to be subclassed with the relevant parts implemented for a particular device. This class handles device detection.
'''

import glob import os import re import subprocess import sys import time from collections import namedtuple from itertools import repeat from calibre import prints from calibre.constants import is_debugging, isfreebsd, islinux, ismacos, iswindows from calibre.devices.errors import DeviceError from calibre.devices.interface import DevicePlugin from calibre.devices.usbms.deviceconfig import DeviceConfig from calibre.utils.filenames import ascii_filename as sanitize from polyglot.builtins import iteritems, string_or_bytes if ismacos:
osx_sanitize_name_pat = re.compile(r'[.-]')

if iswindows:
usb_info_cache = {}

def eject_exe():
base = sys.extensions_location if hasattr(sys, 'new_app_layout') else os.path.dirname(sys.executable)
return os.path.join(base, 'calibre-eject.exe')

class USBDevice:

def __init__(self, dev):
self.idVendor = dev[0]
self.idProduct = dev[1]
self.bcdDevice = dev[2]
if iswindows:
# Getting this information requires communicating with the device
# we only do that in the can_handle_windows() method, if needed.
self.manufacturer = self.serial = self.product = None else:
self.manufacturer = dev[3]
self.product = dev[4]
self.serial = dev[5]

def match_serial(self, serial):
return self.serial and self.serial == serial def match_numbers(self, vid, pid, bcd):
return self.idVendor == vid and self.idProduct == pid and self.bcdDevice == bcd def match_strings(self, vid, pid, bcd, man, prod):
if not self.match_numbers(vid, pid, bcd):
return False if man == self.manufacturer and prod == self.product:
return True
# As of macOS 10.11.4 Apple started mangling the names returned via the
# IOKit registry. See
# https://www.mobileread.com/forums/showthread.php?t=273213 m = osx_sanitize_name_pat.sub('_', (self.manufacturer or ''))
p = osx_sanitize_name_pat.sub('_', (self.product or ''))
return m == man and p == prod

[dokumentace]
class Device(DeviceConfig, DevicePlugin):

'''
This class provides logic common to all drivers for devices that export themselves as USB Mass Storage devices. Provides implementations for mounting/ejecting of USBMS devices on all platforms.
'''

VENDOR_ID = 0x0
PRODUCT_ID = 0x0
BCD = None

VENDOR_NAME = None

#: String identifying the main memory of the device in the Windows PnP id
#: strings
#: This can be None, string, list of strings or compiled regex
WINDOWS_MAIN_MEM = None

#: String identifying the first card of the device in the Windows PnP id
#: strings
#: This can be None, string, list of strings or compiled regex
WINDOWS_CARD_A_MEM = None

#: String identifying the second card of the device in the Windows PnP id
#: strings
#: This can be None, string, list of strings or compiled regex
WINDOWS_CARD_B_MEM = None

#: Used by the new driver detection to disambiguate main memory from
#: storage cards. Should be a regular expression that matches the
#: main memory mount point assigned by macOS
OSX_MAIN_MEM_VOL_PAT = None
OSX_EJECT_COMMAND = ['diskutil', 'eject']

MAIN_MEMORY_VOLUME_LABEL = ''
STORAGE_CARD_VOLUME_LABEL = ''
STORAGE_CARD2_VOLUME_LABEL = None

EBOOK_DIR_MAIN = ''
EBOOK_DIR_CARD_A = ''
EBOOK_DIR_CARD_B = ''
DELETE_EXTS = []

# USB disk-based devices can see the book files on the device, so can
# copy these back to the library
BACKLOADING_ERROR_MESSAGE = None

#: The maximum length of paths created on the device
MAX_PATH_LEN = 250

#: Put news in its own folder
NEWS_IN_FOLDER = True

[dokumentace]
def reset(self, key='-1', log_packets=False, report_progress=None, detected_device=None):
self._main_prefix = self._card_a_prefix = self._card_b_prefix = None self.detected_device = None if detected_device is None else USBDevice(detected_device)
self.set_progress_reporter(report_progress)

[dokumentace]
def set_progress_reporter(self, report_progress):
self.report_progress = report_progress self.report_progress = report_progress if self.report_progress is None:
self.report_progress = lambda x, y: x

[dokumentace]
def card_prefix(self, end_session=True):
return (self._card_a_prefix, self._card_b_prefix)

@classmethod def _windows_space(cls, prefix):
if not prefix:
return 0, 0 prefix = prefix[:-1]
from calibre_extensions import winutil try:
available_space, total_space, free_space = winutil.get_disk_free_space(prefix)
except OSError as err:
if err.winerror == winutil.ERROR_NOT_READY:
# Disk not ready time.sleep(3)
available_space, total_space, free_space = winutil.get_disk_free_space(prefix)
else:
raise return total_space, available_space

[dokumentace]
def total_space(self, end_session=True):
msz = casz = cbsz = 0 if not iswindows:
if self._main_prefix is not None:
stats = os.statvfs(self._main_prefix)
msz = stats.f_frsize * (stats.f_blocks + stats.f_bavail - stats.f_bfree)
if self._card_a_prefix is not None:
stats = os.statvfs(self._card_a_prefix)
casz = stats.f_frsize * (stats.f_blocks + stats.f_bavail - stats.f_bfree)
if self._card_b_prefix is not None:
stats = os.statvfs(self._card_b_prefix)
cbsz = stats.f_frsize * (stats.f_blocks + stats.f_bavail - stats.f_bfree)
else:
msz = self._windows_space(self._main_prefix)[0]
casz = self._windows_space(self._card_a_prefix)[0]
cbsz = self._windows_space(self._card_b_prefix)[0]

return (msz, casz, cbsz)

[dokumentace]
def free_space(self, end_session=True):
msz = casz = cbsz = 0 if not iswindows:
if self._main_prefix is not None:
stats = os.statvfs(self._main_prefix)
msz = stats.f_frsize * stats.f_bavail if self._card_a_prefix is not None:
stats = os.statvfs(self._card_a_prefix)
casz = stats.f_frsize * stats.f_bavail if self._card_b_prefix is not None:
stats = os.statvfs(self._card_b_prefix)
cbsz = stats.f_frsize * stats.f_bavail else:
msz = self._windows_space(self._main_prefix)[1]
casz = self._windows_space(self._card_a_prefix)[1]
cbsz = self._windows_space(self._card_b_prefix)[1]

return (msz, casz, cbsz)

def windows_filter_pnp_id(self, pnp_id):
return False

[dokumentace]
def windows_sort_drives(self, drives):
'''
Called to disambiguate main memory and storage card for devices that do not distinguish between them on the basis of `WINDOWS_CARD_NAME`.
For example: The EB600
'''
return drives

[dokumentace]
def can_handle_windows(self, usbdevice, debug=False):
if hasattr(self.can_handle, 'is_base_class_implementation'):
# No custom can_handle implementation return True
# Delegate to the unix can_handle function, creating a unix like
# USBDevice object from calibre.devices.winusb import get_usb_info dev = usb_info_cache.get(usbdevice)
if dev is None:
try:
data = get_usb_info(usbdevice, debug=debug)
except Exception:
time.sleep(0.1)
try:
data = get_usb_info(usbdevice, debug=debug)
except Exception:
data = {}
dev = usb_info_cache[usbdevice] = namedtuple(
'USBDevice', 'vendor_id product_id bcd manufacturer product serial')( usbdevice.vendor_id, usbdevice.product_id, usbdevice.bcd, data.get('manufacturer') or '', data.get('product') or '', data.get('serial_number') or '')
if debug:
prints(f'USB Info for device: {dev}')
return self.can_handle(dev, debug=debug)

def open_windows(self):
from calibre.devices.scanner import drive_is_ok from calibre.devices.winusb import get_drive_letters_for_device usbdev = self.device_being_opened debug = is_debugging() or getattr(self, 'do_device_debug', False)
try:
dlmap = get_drive_letters_for_device(usbdev, debug=debug)
except Exception:
dlmap = {}

if not dlmap.get('drive_letters'):
time.sleep(7)
dlmap = get_drive_letters_for_device(usbdev, debug=debug)

if debug:
from pprint import pformat prints(f'Drive letters for {usbdev}')
prints(pformat(dlmap))

filtered = set()
for dl in dlmap['drive_letters']:
pnp_id = dlmap['pnp_id_map'][dl].upper()
if dl in dlmap['readonly_drives']:
filtered.add(dl)
if debug:
prints('Ignoring the drive %s as it is readonly' % dl)
elif self.windows_filter_pnp_id(pnp_id):
filtered.add(dl)
if debug:
prints(f'Ignoring the drive {dl} because of a PNP filter on {pnp_id}')
elif not drive_is_ok(dl, debug=debug):
filtered.add(dl)
if debug:
prints('Ignoring the drive %s because failed to get free space for it' % dl)
dlmap['drive_letters'] = [dl for dl in dlmap['drive_letters'] if dl not in filtered]

if not dlmap['drive_letters']:
raise DeviceError(_('Unable to detect any disk drives for the device: %s. Try rebooting') % self.get_gui_name())

drives = {}

for drive_letter, which in zip(dlmap['drive_letters'], 'main carda cardb'.split()):
drives[which] = drive_letter + ':\\'

drives = self.windows_sort_drives(drives)
self._main_prefix = drives.get('main')
self._card_a_prefix = drives.get('carda', None)
self._card_b_prefix = drives.get('cardb', None)

@classmethod def run_ioreg(cls, raw=None):
if raw is not None:
return raw ioreg = '/usr/sbin/ioreg'
if not os.access(ioreg, os.X_OK):
ioreg = 'ioreg'
cmd = (ioreg+' -w 0 -S -c IOMedia').split()
for i in range(3):
try:
return subprocess.Popen(cmd, stdout=subprocess.PIPE).communicate()[0]
except OSError: # Probably an interrupted system call if i == 2:
raise time.sleep(2)

def osx_sort_names(self, names):
return names

@classmethod def osx_run_mount(cls):
for i in range(3):
try:
return subprocess.Popen('mount', stdout=subprocess.PIPE).communicate()[0]
except OSError: # Probably an interrupted system call if i == 2:
raise time.sleep(2)

@classmethod def osx_get_usb_drives(cls):
from calibre_extensions.usbobserver import get_usb_drives return get_usb_drives()

def _osx_bsd_names(self):
drives = self.osx_get_usb_drives()
matches = []
d = self.detected_device if d.serial:
for path, vid, pid, bcd, ven, prod, serial in drives:
if d.match_serial(serial):
matches.append(path)
if not matches and d.manufacturer and d.product:
for path, vid, pid, bcd, man, prod, serial in drives:
if d.match_strings(vid, pid, bcd, man, prod):
matches.append(path)
if not matches:
# Since Apple started mangling the names stored in the IOKit
# registry, we cannot trust match_strings() so fallback to matching
# on just numbers. See http://www.mobileread.com/forums/showthread.php?t=273213 for path, vid, pid, bcd, man, prod, serial in drives:
if d.match_numbers(vid, pid, bcd):
matches.append(path)
if not matches:
from pprint import pformat raise DeviceError( f'Could not detect BSD names for {self.name}. Try rebooting.\nOutput from osx_get_usb_drives():\n{pformat(drives)}')

pat = re.compile(r'(?P<m>\d+)([a-z]+(?P<p>\d+)){0,1}')

def nums(x):
'Return (disk num, partition number)'
m = pat.search(x)
if m is None:
return (10000, -1)
g = m.groupdict()
if g['p'] is None:
g['p'] = 0 return list(map(int, (g.get('m'), g.get('p'))))

def cmp_key(x):
'''
Sorting based on the following scheme:
- disks without partitions are first
- sub sorted based on disk number
- disks with partitions are sorted first on disk number, then on partition number
'''
x = x.rpartition('/')[-1]
disk_num, part_num = nums(x)
has_part = 1 if part_num > 0 else 0 return has_part, disk_num, part_num matches.sort(key=cmp_key)
drives = {'main':matches[0]}
if len(matches) > 1:
drives['carda'] = matches[1]
if len(matches) > 2:
drives['cardb'] = matches[2]

return drives def osx_bsd_names(self):
drives = {}
for i in range(3):
try:
drives = self._osx_bsd_names()
if len(drives) > 1: # wait for device to settle and SD card (if any) to become available return drives except Exception:
if i == 2:
raise time.sleep(3)
return drives def open_osx(self):
from calibre_extensions.usbobserver import get_mounted_filesystems bsd_drives = self.osx_bsd_names()
drives = self.osx_sort_names(bsd_drives.copy())
mount_map = get_mounted_filesystems()
# macOS 13 Ventura uses a weird scheme for mounted FAT devices of the
# form fat://basename_of_bsd_name/basename_of_mountpoint
# see https://www.mobileread.com/forums/showthread.php?t=347294 for dev_node in tuple(mount_map):
if ':' in dev_node and '//' in dev_node:
val = mount_map[dev_node]
dev_node = dev_node.split('/')[-2]
dev_node = f'/dev/{dev_node}'
if dev_node not in mount_map:
mount_map[dev_node] = val drives = {k: mount_map.get(v) for k, v in iteritems(drives)}
if is_debugging():
print()
from pprint import pprint pprint({'bsd_drives': bsd_drives, 'mount_map': mount_map, 'drives': drives})
if drives.get('carda') is None and drives.get('cardb') is not None:
drives['carda'] = drives.pop('cardb')
if drives.get('main') is None and drives.get('carda') is not None:
drives['main'] = drives.pop('carda')
if drives.get('carda') is None and drives.get('cardb') is not None:
drives['carda'] = drives.pop('cardb')
if drives.get('main') is None:
raise DeviceError(_('Unable to detect the %s mount point. Try rebooting.')%self.__class__.__name__)
pat = self.OSX_MAIN_MEM_VOL_PAT if pat is not None and len(drives) > 1 and 'main' in drives:
if pat.search(drives['main']) is None:
main = drives['main']
for x in ('carda', 'cardb'):
if x in drives and pat.search(drives[x]):
drives['main'] = drives.pop(x)
drives[x] = main break self._main_prefix = drives['main']+os.sep def get_card_prefix(c):
ans = drives.get(c, None)
if ans is not None:
ans += os.sep return ans self._card_a_prefix = get_card_prefix('carda')
self._card_b_prefix = get_card_prefix('cardb')

def find_device_nodes(self, detected_device=None):

def walk(base):
base = os.path.abspath(os.path.realpath(base))
for x in os.listdir(base):
p = os.path.join(base, x)
if os.path.islink(p) or not os.access(p, os.R_OK):
continue isfile = os.path.isfile(p)
yield p, isfile if not isfile:
yield from walk(p)

def raw2num(raw):
raw = raw.lower()
if not raw.startswith('0x'):
raw = '0x' + raw return int(raw, 16)

# Find device node based on vendor, product and bcd d, j = os.path.dirname, os.path.join usb_dir = None if detected_device is None:
detected_device = self.detected_device def test(val, attr):
q = getattr(detected_device, attr)
return q == val def getnum(usb_dir):
def rc(q):
with open(j(usb_dir, q), 'rb') as f:
return raw2num(f.read().decode('utf-8'))
return rc for x, isfile in walk('/sys/devices'):
if isfile and x.endswith('idVendor'):
usb_dir = d(x)
for y in ('idProduct', 'idVendor', 'bcdDevice'):
if not os.access(j(usb_dir, y), os.R_OK):
usb_dir = None break if usb_dir is None:
continue ven, prod, bcd = map(getnum(usb_dir), ('idVendor', 'idProduct', 'bcdDevice'))
if not (test(ven, 'idVendor') and test(prod, 'idProduct') and test(bcd, 'bcdDevice')):
usb_dir = None continue else:
break if usb_dir is None:
raise DeviceError(_('Unable to detect the %s disk drive.')
%self.__class__.__name__)

devnodes, ok = [], {}
for x, isfile in walk(usb_dir):
if not isfile and '/block/' in x:
parts = x.split('/')
idx = parts.index('block')
if idx == len(parts)-2:
sz = j(x, 'size')
node = parts[idx+1]
try:
with open(sz, 'rb') as szf:
exists = int(szf.read().decode('utf-8')) > 0 if exists:
node = self.find_largest_partition(x)
ok[node] = True else:
ok[node] = False except:
ok[node] = False if is_debugging() and not ok[node]:
print(f'\nIgnoring the node: {node} as could not read size from: {sz}')

devnodes.append(node)

devnodes += list(repeat(None, 3))
ans = ['/dev/'+x if ok.get(x, False) else None for x in devnodes]
ans.sort(key=lambda x: x[5:] if x else 'zzzzz')
return self.linux_swap_drives(ans[:3])

def linux_swap_drives(self, drives):
return drives def node_mountpoint(self, node):
from calibre.devices.udisks import node_mountpoint return node_mountpoint(node)

def find_largest_partition(self, path):
node = path.split('/')[-1]
nodes = []
for x in glob.glob(path+'/'+node+'*'):
sz = x + '/size'

if not os.access(sz, os.R_OK):
continue try:
with open(sz, 'rb') as szf:
sz = int(szf.read().decode('utf-8'))
except:
continue if sz > 0:
nodes.append((x.split('/')[-1], sz))

nodes.sort(key=lambda x: x[1])
if not nodes:
return node return nodes[-1][0]

def open_linux(self):

def mount(node, type):
mp = self.node_mountpoint(node)
if mp is not None:
return mp, 0 def do_mount(node):
try:
from calibre.devices.udisks import mount mount(node)
return 0 except:
print('Udisks mount call failed:')
import traceback traceback.print_exc()
return 1 ret = do_mount(node)
if ret != 0:
return None, ret return self.node_mountpoint(node)+'/', 0 main, carda, cardb = self.find_device_nodes()
if main is None:
raise DeviceError(_('Unable to detect the %s disk drive. Either '
'the device has already been ejected, or your '
'kernel is exporting a deprecated version of SYSFS.')
%self.__class__.__name__)
if is_debugging():
print('\nFound device nodes:', main, carda, cardb)

self._linux_mount_map = {}
mp, ret = mount(main, 'main')
if mp is None:
raise DeviceError(
_('Unable to mount main memory (Error code: %d)')%ret)
if not mp.endswith('/'):
mp += '/'
self._linux_mount_map[main] = mp self._main_prefix = mp self._linux_main_device_node = main cards = [(carda, '_card_a_prefix', 'carda'),
(cardb, '_card_b_prefix', 'cardb')]
for card, prefix, typ in cards:
if card is None:
continue mp, ret = mount(card, typ)
if mp is None:
print('Unable to mount card (Error code: %d)'%ret, file=sys.stderr)
else:
if not mp.endswith('/'):
mp += '/'
setattr(self, prefix, mp)
self._linux_mount_map[card] = mp self.filter_read_only_mount_points()

def filter_read_only_mount_points(self):

def is_readonly(mp):
if mp is None:
return True path = os.path.join(mp, 'calibre_readonly_test')
ro = True try:
with open(path, 'wb'):
ro = False except:
pass else:
try:
os.remove(path)
except:
pass if is_debugging() and ro:
print('\nThe mountpoint', mp, 'is readonly, ignoring it')
return ro for mp in ('_main_prefix', '_card_a_prefix', '_card_b_prefix'):
if is_readonly(getattr(self, mp, None)):
setattr(self, mp, None)

if self._main_prefix is None:
for p in ('_card_a_prefix', '_card_b_prefix'):
nmp = getattr(self, p, None)
if nmp is not None:
self._main_prefix = nmp setattr(self, p, None)
break if self._main_prefix is None:
raise DeviceError(_('The main memory of %s is read only. '
'This usually happens because of file system errors.')
%self.__class__.__name__)

if self._card_a_prefix is None and self._card_b_prefix is not None:
self._card_a_prefix = self._card_b_prefix self._card_b_prefix = None

# ------------------------------------------------------
#
# open for FreeBSD
# find the device node or nodes that match the S/N we already have from the scanner
# and attempt to mount each one
# 1. get list of devices in /dev with matching s/n etc.
# 2. get list of volumes associated with each
# 3. attempt to mount each one using Hal
# 4. when finished, we have a list of mount points and associated dbus nodes
# def open_freebsd(self):
# There should be some way to access the -v arg...
verbose = False

# this gives us access to the S/N, etc. of the reader that the scanner has found
# and the match routines for some of that data, like s/n, vendor ID, etc.
d=self.detected_device if not d.serial:
raise DeviceError("Device has no S/N. Can't continue")
from .hal import get_hal hal = get_hal()
vols = hal.get_volumes(d)
if verbose:
print("FBSD: ", vols)

ok, mv = hal.mount_volumes(vols)
if not ok:
raise DeviceError(_('Unable to mount the device'))
for k, v in mv.items():
setattr(self, k, v)

#
# ------------------------------------------------------
#
# this one is pretty simple:
# just umount each of the previously
# mounted filesystems, using the stored volume object
# def eject_freebsd(self):
from .hal import get_hal hal = get_hal()
if self._main_prefix:
hal.unmount(self._main_vol)
if self._card_a_prefix:
hal.unmount(self._card_a_vol)
if self._card_b_prefix:
hal.unmount(self._card_b_vol)

self._main_prefix = self._main_vol = None self._card_a_prefix = self._card_a_vol = None self._card_b_prefix = self._card_b_vol = None
# ------------------------------------------------------

[dokumentace]
def open(self, connected_device, library_uuid):
time.sleep(5)
self._main_prefix = self._card_a_prefix = self._card_b_prefix = None self.device_being_opened = connected_device try:
if islinux:
try:
self.open_linux()
except DeviceError:
time.sleep(7)
self.open_linux()
if isfreebsd:
self._main_vol = self._card_a_vol = self._card_b_vol = None try:
self.open_freebsd()
except DeviceError:
time.sleep(2)
self.open_freebsd()
if iswindows:
self.open_windows()
if ismacos:
try:
self.open_osx()
except DeviceError:
time.sleep(7)
self.open_osx()

self.current_library_uuid = library_uuid self.post_open_callback()
finally:
self.device_being_opened = None
def post_open_callback(self):
pass def eject_windows(self):
from threading import Thread drives = []
for x in ('_main_prefix', '_card_a_prefix', '_card_b_prefix'):
x = getattr(self, x, None)
if x is not None:
drives.append(x[0].upper())

def do_it(drives):
subprocess.Popen([eject_exe()] + drives, creationflags=subprocess.CREATE_NO_WINDOW).wait()

t = Thread(target=do_it, args=[drives])
t.daemon = True t.start()
self.__save_win_eject_thread = t def eject_osx(self):
for x in ('_main_prefix', '_card_a_prefix', '_card_b_prefix'):
x = getattr(self, x, None)
if x is not None:
try:
subprocess.Popen(self.OSX_EJECT_COMMAND + [x])
except:
pass def eject_linux(self):
from calibre.devices.udisks import eject, umount drives = [d for d in self.find_device_nodes() if d]
for d in drives:
try:
umount(d)
except:
pass for d in drives:
try:
eject(d)
except Exception as e:
print('Udisks eject call for:', d, 'failed:')
print('\t', e)

[dokumentace]
def eject(self):
if islinux:
try:
self.eject_linux()
except:
pass if isfreebsd:
try:
self.eject_freebsd()
except:
pass if iswindows:
try:
self.eject_windows()
except:
pass if ismacos:
try:
self.eject_osx()
except:
pass self._main_prefix = self._card_a_prefix = self._card_b_prefix = None
def linux_post_yank(self):
self._linux_mount_map = {}

[dokumentace]
def post_yank_cleanup(self):
if islinux:
try:
self.linux_post_yank()
except:
import traceback traceback.print_exc()
self._main_prefix = self._card_a_prefix = self._card_b_prefix = None
def get_main_ebook_dir(self, for_upload=False):
return self.EBOOK_DIR_MAIN def get_carda_ebook_dir(self, for_upload=False):
return self.EBOOK_DIR_CARD_A def get_cardb_ebook_dir(self, for_upload=False):
return self.EBOOK_DIR_CARD_B def _sanity_check(self, on_card, files):
from calibre.devices.utils import sanity_check sanity_check(on_card, files, self.card_prefix(), self.free_space())

def get_dest_dir(prefix, candidates):
if isinstance(candidates, string_or_bytes):
candidates = [candidates]
if not candidates:
candidates = ['']
candidates = [
((os.path.join(prefix, *(x.split('/')))) if x else prefix)
for x in candidates]
existing = [x for x in candidates if os.path.exists(x)]
if not existing:
existing = candidates return existing[0]

if on_card == 'carda':
candidates = self.get_carda_ebook_dir(for_upload=True)
path = get_dest_dir(self._card_a_prefix, candidates)
elif on_card == 'cardb':
candidates = self.get_cardb_ebook_dir(for_upload=True)
path = get_dest_dir(self._card_b_prefix, candidates)
else:
candidates = self.get_main_ebook_dir(for_upload=True)
path = get_dest_dir(self._main_prefix, candidates)

return path

[dokumentace]
def sanitize_callback(self, path):
'''
Callback to allow individual device drivers to override the path sanitization used by :meth:`create_upload_path`.
'''
return sanitize(path)

[dokumentace]
def filename_callback(self, default, mi):
'''
Callback to allow drivers to change the default file name set by :meth:`create_upload_path`.
'''
return default

[dokumentace]
def sanitize_path_components(self, components):
'''
Perform any device specific sanitization on the path components for files to be uploaded to the device
'''
return components

[dokumentace]
def get_annotations(self, path_map):
'''
Resolve path_map to annotation_map of files found on the device
'''
return {}

[dokumentace]
def add_annotation_to_library(self, db, db_id, annotation):
'''
Add an annotation to the calibre library
'''
pass
def create_upload_path(self, path, mdata, fname, create_dirs=True):
from calibre.devices.utils import create_upload_path settings = self.settings()
filepath = create_upload_path(mdata, fname, self.save_template(), self.sanitize_callback, prefix_path=os.path.abspath(path), maxlen=self.MAX_PATH_LEN, use_subdirs=self.SUPPORTS_SUB_DIRS and settings.use_subdirs, news_in_folder=self.NEWS_IN_FOLDER, filename_callback=self.filename_callback, sanitize_path_components=self.sanitize_path_components
)
filedir = os.path.dirname(filepath)

if create_dirs and not os.path.exists(filedir):
os.makedirs(filedir)

return filepath def create_annotations_path(self, mdata, device_path=None):
return self.create_upload_path(os.path.abspath('/<storage>'), mdata, 'x.bookmark', create_dirs=False)

© Copyright Kovid Goyal.
Naposledy aktualizováno kvě 03, 2024.

Navigace

- moduly
- Spustit »
- Kód modulu »
- calibre.devices.usbms.driver

Zdrojový kód pro calibre.devices.usbms.driver

__license__ = 'GPL v3'
__copyright__ = '2009, John Schember <john at nachtimwald.com>'
__docformat__ = 'restructuredtext en'

'''
Generic USB Mass storage device driver. This is not a complete stand alone driver. It is intended to be subclassed with the relevant parts implemented for a particular device.
'''

import json import os import shutil import time from itertools import cycle from calibre import fsync, isbytestring, prints from calibre.constants import filesystem_encoding, is_debugging, ismacos, numeric_version from calibre.devices.usbms.books import Book, BookList from calibre.devices.usbms.cli import CLI from calibre.devices.usbms.device import Device from calibre.ebooks.metadata.book.json_codec import JsonCodec from polyglot.builtins import itervalues, string_or_bytes def debug_print(*args, **kw):
base_time = getattr(debug_print, 'base_time', None)
if base_time is None:
debug_print.base_time = base_time = time.monotonic()
if is_debugging():
prints('DEBUG: %6.1f'%(time.monotonic()-base_time), *args, **kw)

def safe_walk(top, topdown=True, onerror=None, followlinks=False, maxdepth=128):
' A replacement for os.walk that does not die when it encounters undecodeable filenames in a linux filesystem'
if maxdepth < 0:
return islink, join, isdir = os.path.islink, os.path.join, os.path.isdir

# We may not have read permission for top, in which case we can't
# get a list of the files the directory contains. os.path.walk
# always suppressed the exception then, rather than blow up for a
# minor reason when (say) a thousand readable directories are still
# left to visit. That logic is copied here.
try:
names = os.listdir(top)
except OSError as err:
if onerror is not None:
onerror(err)
return dirs, nondirs = [], []
for name in names:
if isinstance(name, bytes):
try:
name = name.decode(filesystem_encoding)
except UnicodeDecodeError:
debug_print('Skipping undecodeable file: %r' % name)
continue if isdir(join(top, name)):
dirs.append(name)
else:
nondirs.append(name)

if topdown:
yield top, dirs, nondirs for name in dirs:
new_path = join(top, name)
if followlinks or not islink(new_path):
yield from safe_walk(new_path, topdown, onerror, followlinks, maxdepth-1)
if not topdown:
yield top, dirs, nondirs

# CLI must come before Device as it implements the CLI functions that
# are inherited from the device interface in Device.
[dokumentace]
class USBMS(CLI, Device):

'''
The base class for all USBMS devices. Implements the logic for sending/getting/updating metadata/caching metadata/etc.
'''

description = _('Communicate with an e-book reader.')
author = 'John Schember'
supported_platforms = ['windows', 'osx', 'linux']

# Store type instances of BookList and Book. We must do this because
# a) we need to override these classes in some device drivers, and
# b) the classmethods seem only to see real attributes declared in the
# class, not attributes stored in the class booklist_class = BookList book_class = Book

FORMATS = []
CAN_SET_METADATA = []
METADATA_CACHE = 'metadata.calibre'
DRIVEINFO = 'driveinfo.calibre'

SCAN_FROM_ROOT = False def _update_driveinfo_record(self, dinfo, prefix, location_code, name=None):
import uuid from calibre.utils.date import isoformat, now if not isinstance(dinfo, dict):
dinfo = {}
if dinfo.get('device_store_uuid', None) is None:
dinfo['device_store_uuid'] = str(uuid.uuid4())
if dinfo.get('device_name', None) is None:
dinfo['device_name'] = self.get_gui_name()
if name is not None:
dinfo['device_name'] = name dinfo['location_code'] = location_code dinfo['last_library_uuid'] = getattr(self, 'current_library_uuid', None)
dinfo['calibre_version'] = '.'.join([str(i) for i in numeric_version])
dinfo['date_last_connected'] = isoformat(now())
dinfo['prefix'] = prefix.replace('\\', '/')
return dinfo def _update_driveinfo_file(self, prefix, location_code, name=None):
from calibre.utils.config import from_json, to_json if os.path.exists(os.path.join(prefix, self.DRIVEINFO)):
with open(os.path.join(prefix, self.DRIVEINFO), 'rb') as f:
try:
driveinfo = json.loads(f.read(), object_hook=from_json)
except:
driveinfo = None driveinfo = self._update_driveinfo_record(driveinfo, prefix, location_code, name)
data = json.dumps(driveinfo, default=to_json)
if not isinstance(data, bytes):
data = data.encode('utf-8')
with open(os.path.join(prefix, self.DRIVEINFO), 'wb') as f:
f.write(data)
fsync(f)
else:
driveinfo = self._update_driveinfo_record({}, prefix, location_code, name)
data = json.dumps(driveinfo, default=to_json)
if not isinstance(data, bytes):
data = data.encode('utf-8')
with open(os.path.join(prefix, self.DRIVEINFO), 'wb') as f:
f.write(data)
fsync(f)
return driveinfo

[dokumentace]
def get_device_information(self, end_session=True):
self.report_progress(1.0, _('Get device information...'))
self.driveinfo = {}

def raise_os_error(e):
raise OSError(_('Failed to access files in the main memory of'
' your device. You should contact the device'
' manufacturer for support. Common fixes are:'
' try a different USB cable/USB port on your computer.'
' If you device has a "Reset to factory defaults" type'
' of setting somewhere, use it. Underlying error: %s')
% e) from e if self._main_prefix is not None:
try:
self.driveinfo['main'] = self._update_driveinfo_file(self._main_prefix, 'main')
except PermissionError as e:
if ismacos:
raise PermissionError(_(
'Permission was denied by macOS trying to access files in the main memory of'
' your device. You will need to grant permission explicitly by looking under'
' System Preferences > Security and Privacy > Privacy > Files and Folders.'
' Underlying error: %s'
) % e) from e raise_os_error(e)
except OSError as e:
raise_os_error(e)
try:
if self._card_a_prefix is not None:
self.driveinfo['A'] = self._update_driveinfo_file(self._card_a_prefix, 'A')
if self._card_b_prefix is not None:
self.driveinfo['B'] = self._update_driveinfo_file(self._card_b_prefix, 'B')
except OSError as e:
raise OSError(_('Failed to access files on the SD card in your'
' device. This can happen for many reasons. The SD card may be'
' corrupted, it may be too large for your device, it may be'
' write-protected, etc. Try a different SD card, or reformat'
' your SD card using the FAT32 filesystem. Also make sure'
' there are not too many files in the root of your SD card.'
' Underlying error: %s') % e)
return (self.get_gui_name(), '', '', '', self.driveinfo)

[dokumentace]
def set_driveinfo_name(self, location_code, name):
if location_code == 'main':
self._update_driveinfo_file(self._main_prefix, location_code, name)
elif location_code == 'A':
self._update_driveinfo_file(self._card_a_prefix, location_code, name)
elif location_code == 'B':
self._update_driveinfo_file(self._card_b_prefix, location_code, name)

def formats_to_scan_for(self):
return set(self.settings().format_map) | set(self.FORMATS)

def is_allowed_book_file(self, filename, path, prefix):
return True

[dokumentace]
def books(self, oncard=None, end_session=True):
from calibre.ebooks.metadata.meta import path_to_ext debug_print('USBMS: Fetching list of books from device. Device=', self.__class__.__name__,
'oncard=', oncard)

dummy_bl = self.booklist_class(None, None, None)

if oncard == 'carda' and not self._card_a_prefix:
self.report_progress(1.0, _('Getting list of books on device...'))
return dummy_bl elif oncard == 'cardb' and not self._card_b_prefix:
self.report_progress(1.0, _('Getting list of books on device...'))
return dummy_bl elif oncard and oncard != 'carda' and oncard != 'cardb':
self.report_progress(1.0, _('Getting list of books on device...'))
return dummy_bl prefix = self._card_a_prefix if oncard == 'carda' else \ self._card_b_prefix if oncard == 'cardb' \ else self._main_prefix ebook_dirs = self.get_carda_ebook_dir() if oncard == 'carda' else \ self.EBOOK_DIR_CARD_B if oncard == 'cardb' else \ self.get_main_ebook_dir()

debug_print('USBMS: dirs are:', prefix, ebook_dirs)

# get the metadata cache bl = self.booklist_class(oncard, prefix, self.settings)
need_sync = self.parse_metadata_cache(bl, prefix, self.METADATA_CACHE)

# make a dict cache of paths so the lookup in the loop below is faster.
bl_cache = {}
for idx, b in enumerate(bl):
bl_cache[b.lpath] = idx all_formats = self.formats_to_scan_for()

def update_booklist(filename, path, prefix):
changed = False
# Ignore AppleDouble files if filename.startswith("._"):
return False if path_to_ext(filename) in all_formats and self.is_allowed_book_file(filename, path, prefix):
try:
lpath = os.path.join(path, filename).partition(self.normalize_path(prefix))[2]
if lpath.startswith(os.sep):
lpath = lpath[len(os.sep):]
lpath = lpath.replace('\\', '/')
idx = bl_cache.get(lpath, None)
if idx is not None:
bl_cache[lpath] = None if self.update_metadata_item(bl[idx]):
# print 'update_metadata_item returned true'
changed = True else:
if bl.add_book(self.book_from_path(prefix, lpath), replace_metadata=False):
changed = True except: # Probably a filename encoding error import traceback traceback.print_exc()
return changed if isinstance(ebook_dirs, string_or_bytes):
ebook_dirs = [ebook_dirs]
for ebook_dir in ebook_dirs:
ebook_dir = self.path_to_unicode(ebook_dir)
if self.SCAN_FROM_ROOT:
ebook_dir = self.normalize_path(prefix)
else:
ebook_dir = self.normalize_path( os.path.join(prefix, *(ebook_dir.split('/')))
if ebook_dir else prefix)
debug_print('USBMS: scan from root', self.SCAN_FROM_ROOT, ebook_dir)
if not os.path.exists(ebook_dir):
continue
# Get all books in the ebook_dir directory if self.SUPPORTS_SUB_DIRS or self.SUPPORTS_SUB_DIRS_FOR_SCAN:
# build a list of files to check, so we can accurately report progress flist = []
for path, dirs, files in safe_walk(ebook_dir):
for filename in files:
if filename != self.METADATA_CACHE:
flist.append({'filename': self.path_to_unicode(filename),
'path':self.path_to_unicode(path)})
for i, f in enumerate(flist):
self.report_progress(i/float(len(flist)), _('Getting list of books on device...'))
changed = update_booklist(f['filename'], f['path'], prefix)
if changed:
need_sync = True else:
paths = os.listdir(ebook_dir)
for i, filename in enumerate(paths):
self.report_progress((i+1) / float(len(paths)), _('Getting list of books on device...'))
changed = update_booklist(self.path_to_unicode(filename), ebook_dir, prefix)
if changed:
need_sync = True

# Remove books that are no longer in the filesystem. Cache contains
# indices into the booklist if book not in filesystem, None otherwise
# Do the operation in reverse order so indices remain valid for idx in sorted(itervalues(bl_cache), reverse=True, key=lambda x: -1 if x is None else x):
if idx is not None:
need_sync = True del bl[idx]

debug_print('USBMS: count found in cache: %d, count of files in metadata: %d, need_sync: %s' %
(len(bl_cache), len(bl), need_sync))
if need_sync: # self.count_found_in_bl != len(bl) or need_sync:
if oncard == 'cardb':
self.sync_booklists((None, None, bl))
elif oncard == 'carda':
self.sync_booklists((None, bl, None))
else:
self.sync_booklists((bl, None, None))

self.report_progress(1.0, _('Getting list of books on device...'))
debug_print('USBMS: Finished fetching list of books from device. oncard=', oncard)
return bl[dokumentace]
def upload_books(self, files, names, on_card=None, end_session=True, metadata=None):
debug_print('USBMS: uploading %d books'%(len(files)))

path = self._sanity_check(on_card, files)

paths = []
names = iter(names)
metadata = iter(metadata)

for i, infile in enumerate(files):
mdata, fname = next(metadata), next(names)
filepath = self.normalize_path(self.create_upload_path(path, mdata, fname))
if not hasattr(infile, 'read'):
infile = self.normalize_path(infile)
filepath = self.put_file(infile, filepath, replace_file=True)
paths.append(filepath)
try:
self.upload_cover(os.path.dirname(filepath), os.path.splitext(os.path.basename(filepath))[0], mdata, filepath)
except: # Failure to upload cover is not catastrophic import traceback traceback.print_exc()

self.report_progress((i+1) / float(len(files)), _('Transferring books to device...'))

self.report_progress(1.0, _('Transferring books to device...'))
debug_print('USBMS: finished uploading %d books'%(len(files)))
return list(zip(paths, cycle([on_card])))

[dokumentace]
def upload_cover(self, path, filename, metadata, filepath):
'''
Upload book cover to the device. Default implementation does nothing.

:param path: The full path to the folder where the associated book is located.
:param filename: The name of the book file without the extension.
:param metadata: metadata belonging to the book. Use metadata.thumbnail for cover
:param filepath: The full path to the e-book file

'''
pass

[dokumentace]
def add_books_to_metadata(self, locations, metadata, booklists):
debug_print('USBMS: adding metadata for %d books'%(len(metadata)))

metadata = iter(metadata)
locations = tuple(locations)
for i, location in enumerate(locations):
self.report_progress((i+1) / float(len(locations)), _('Adding books to device metadata listing...'))
info = next(metadata)
blist = 2 if location[1] == 'cardb' else 1 if location[1] == 'carda' else 0

# Extract the correct prefix from the pathname. To do this correctly,
# we must ensure that both the prefix and the path are normalized
# so that the comparison will work. Book's __init__ will fix up
# lpath, so we don't need to worry about that here.
path = self.normalize_path(location[0])
if self._main_prefix:
prefix = self._main_prefix if \ path.startswith(self.normalize_path(self._main_prefix)) else None if not prefix and self._card_a_prefix:
prefix = self._card_a_prefix if \ path.startswith(self.normalize_path(self._card_a_prefix)) else None if not prefix and self._card_b_prefix:
prefix = self._card_b_prefix if \ path.startswith(self.normalize_path(self._card_b_prefix)) else None if prefix is None:
prints('in add_books_to_metadata. Prefix is None!', path, self._main_prefix)
continue lpath = path.partition(prefix)[2]
if lpath.startswith('/') or lpath.startswith('\\'):
lpath = lpath[1:]
book = self.book_class(prefix, lpath, other=info)
if book.size is None:
book.size = os.stat(self.normalize_path(path)).st_size b = booklists[blist].add_book(book, replace_metadata=True)
if b:
b._new_book = True self.report_progress(1.0, _('Adding books to device metadata listing...'))
debug_print('USBMS: finished adding metadata')def delete_single_book(self, path):
os.unlink(path)

def delete_extra_book_files(self, path):
filepath = os.path.splitext(path)[0]
for ext in self.DELETE_EXTS:
for x in (filepath, path):
x += ext if os.path.exists(x):
if os.path.isdir(x):
shutil.rmtree(x, ignore_errors=True)
else:
os.unlink(x)

if self.SUPPORTS_SUB_DIRS:
try:
os.removedirs(os.path.dirname(path))
except:
pass

[dokumentace]
def delete_books(self, paths, end_session=True):
debug_print('USBMS: deleting %d books'%(len(paths)))
for i, path in enumerate(paths):
self.report_progress((i+1) / float(len(paths)), _('Removing books from device...'))
path = self.normalize_path(path)
if os.path.exists(path):
# Delete the ebook self.delete_single_book(path)
self.delete_extra_book_files(path)

self.report_progress(1.0, _('Removing books from device...'))
debug_print('USBMS: finished deleting %d books'%(len(paths)))

[dokumentace]
def remove_books_from_metadata(self, paths, booklists):
debug_print('USBMS: removing metadata for %d books'%(len(paths)))

for i, path in enumerate(paths):
self.report_progress((i+1) / float(len(paths)), _('Removing books from device metadata listing...'))
for bl in booklists:
for book in bl:
if path.endswith(book.path):
bl.remove_book(book)
self.report_progress(1.0, _('Removing books from device metadata listing...'))
debug_print('USBMS: finished removing metadata for %d books'%(len(paths)))

# If you override this method and you use book._new_book, then you must
# complete the processing before you call this method. The flag is cleared
# at the end just before the return
[dokumentace]
def sync_booklists(self, booklists, end_session=True):
debug_print('USBMS: starting sync_booklists')
json_codec = JsonCodec()

if not os.path.exists(self.normalize_path(self._main_prefix)):
os.makedirs(self.normalize_path(self._main_prefix))

def write_prefix(prefix, listid):
if (prefix is not None and len(booklists) > listid and isinstance(booklists[listid], self.booklist_class)):
if not os.path.exists(prefix):
os.makedirs(self.normalize_path(prefix))
with open(self.normalize_path(os.path.join(prefix, self.METADATA_CACHE)), 'wb') as f:
json_codec.encode_to_file(f, booklists[listid])
fsync(f)
write_prefix(self._main_prefix, 0)
write_prefix(self._card_a_prefix, 1)
write_prefix(self._card_b_prefix, 2)

# Clear the _new_book indication, as we are supposed to be done with
# adding books at this point for blist in booklists:
if blist is not None:
for book in blist:
book._new_book = False self.report_progress(1.0, _('Sending metadata to device...'))
debug_print('USBMS: finished sync_booklists')@classmethod def build_template_regexp(cls):
from calibre.devices.utils import build_template_regexp return build_template_regexp(cls.save_template())

@classmethod def path_to_unicode(cls, path):
if isbytestring(path):
path = path.decode(filesystem_encoding)
return path

[dokumentace]
@classmethod def normalize_path(cls, path):
'Return path with platform native path separators'
if path is None:
return None if os.sep == '\\':
path = path.replace('/', '\\')
else:
path = path.replace('\\', '/')
return cls.path_to_unicode(path)

@classmethod def parse_metadata_cache(cls, bl, prefix, name):
json_codec = JsonCodec()
need_sync = False cache_file = cls.normalize_path(os.path.join(prefix, name))
if os.access(cache_file, os.R_OK):
try:
with open(cache_file, 'rb') as f:
json_codec.decode_from_file(f, bl, cls.book_class, prefix)
except:
import traceback traceback.print_exc()
bl = []
need_sync = True else:
need_sync = True return need_sync

@classmethod def update_metadata_item(cls, book):
changed = False size = os.stat(cls.normalize_path(book.path)).st_size if size != book.size:
changed = True mi = cls.metadata_from_path(book.path)
book.smart_update(mi)
book.size = size return changed

@classmethod def metadata_from_path(cls, path):
return cls.metadata_from_formats([path])

@classmethod def metadata_from_formats(cls, fmts):
from calibre.customize.ui import quick_metadata from calibre.ebooks.metadata.meta import metadata_from_formats with quick_metadata:
return metadata_from_formats(fmts, force_read_metadata=True, pattern=cls.build_template_regexp())

@classmethod def book_from_path(cls, prefix, lpath):
from calibre.ebooks.metadata.book.base import Metadata if cls.settings().read_metadata or cls.MUST_READ_METADATA:
mi = cls.metadata_from_path(cls.normalize_path(os.path.join(prefix, lpath)))
else:
from calibre.ebooks.metadata.meta import metadata_from_filename mi = metadata_from_filename(cls.normalize_path(os.path.basename(lpath)), cls.build_template_regexp())
if mi is None:
mi = Metadata(os.path.splitext(os.path.basename(lpath))[0],
[_('Unknown')])
size = os.stat(cls.normalize_path(os.path.join(prefix, lpath))).st_size book = cls.book_class(prefix, lpath, other=mi, size=size)
return book

© Copyright Kovid Goyal.
Naposledy aktualizováno kvě 03, 2024.

Navigace

- moduly
- Spustit »
- Kód modulu »
- calibre.ebooks.metadata.book.base

Zdrojový kód pro calibre.ebooks.metadata.book.base

#!/usr/bin/env python

__license__ = 'GPL v3'
__copyright__ = '2010, Kovid Goyal <kovid@kovidgoyal.net>'
__docformat__ = 'restructuredtext en'

import copy import traceback from calibre import prints from calibre.constants import DEBUG from calibre.ebooks.metadata.book import ALL_METADATA_FIELDS, SC_COPYABLE_FIELDS, SC_FIELDS_COPY_NOT_NULL, STANDARD_METADATA_FIELDS, TOP_LEVEL_IDENTIFIERS from calibre.library.field_metadata import FieldMetadata from calibre.utils.icu import lower as icu_lower from calibre.utils.icu import sort_key from calibre.utils.localization import ngettext from polyglot.builtins import iteritems, string_or_bytes

# Special sets used to optimize the performance of getting and setting
# attributes on Metadata objects
SIMPLE_GET = frozenset(STANDARD_METADATA_FIELDS - TOP_LEVEL_IDENTIFIERS)
SIMPLE_SET = frozenset(SIMPLE_GET - {'identifiers'})

def human_readable(size, precision=2):
""" Convert a size in bytes into megabytes """
ans = size/(1024*1024)
if ans < 0.1:
return '<0.1 MB'
return ('%.'+str(precision)+'f'+ ' MB') % ans

NULL_VALUES = {
'user_metadata': {},
'cover_data' : (None, None),
'tags' : [],
'identifiers' : {},
'languages' : [],
'device_collections': [],
'author_sort_map': {},
'authors' : [_('Unknown')],
'author_sort' : _('Unknown'),
'title' : _('Unknown'),
'user_categories' : {},
'link_maps' : {},
'language' : 'und'
}

field_metadata = FieldMetadata()

def reset_field_metadata():
global field_metadata field_metadata = FieldMetadata()

def ck(typ):
return icu_lower(typ).strip().replace(':', '').replace(',', '')

def cv(val):
return val.strip().replace(',', '|')

[dokumentace]
class Metadata:

'''
A class representing all the metadata for a book. The various standard metadata fields are available as attributes of this object. You can also stick arbitrary attributes onto this object.

Metadata from custom columns should be accessed via the get() method, passing in the lookup name for the column, for example: "#mytags".

Use the :meth:`is_null` method to test if a field is null.

This object also has functions to format fields into strings.

The list of standard metadata fields grows with time is in
:data:`STANDARD_METADATA_FIELDS`.

Please keep the method based API of this class to a minimum. Every method becomes a reserved field name.
'''
__calibre_serializable__ = True def __init__(self, title, authors=(_('Unknown'),), other=None, template_cache=None, formatter=None):
'''
@param title: title or ``_('Unknown')``
@param authors: List of strings or []
@param other: None or a metadata object
'''
_data = copy.deepcopy(NULL_VALUES)
_data.pop('language')
object.__setattr__(self, '_data', _data)
if other is not None:
self.smart_update(other)
else:
if title:
self.title = title if authors:
# List of strings or []
self.author = list(authors) if authors else [] # Needed for backward compatibility self.authors = list(authors) if authors else []
from calibre.ebooks.metadata.book.formatter import SafeFormat self.formatter = SafeFormat() if formatter is None else formatter self.template_cache = template_cache

[dokumentace]
def is_null(self, field):
'''
Return True if the value of field is null in this object.
'null' means it is unknown or evaluates to False. So a title of
_('Unknown') is null or a language of 'und' is null.

Be careful with numeric fields since this will return True for zero as well as None.

Also returns True if the field does not exist.
'''
try:
null_val = NULL_VALUES.get(field, None)
val = getattr(self, field, None)
return not val or val == null_val except:
return True
def set_null(self, field):
null_val = copy.copy(NULL_VALUES.get(field))
setattr(self, field, null_val)

def __getattribute__(self, field):
_data = object.__getattribute__(self, '_data')
if field in SIMPLE_GET:
return _data.get(field, None)
if field in TOP_LEVEL_IDENTIFIERS:
return _data.get('identifiers').get(field, None)
if field == 'language':
try:
return _data.get('languages', [])[0]
except:
return NULL_VALUES['language']
try:
return object.__getattribute__(self, field)
except AttributeError:
pass if field in _data['user_metadata']:
d = _data['user_metadata'][field]
val = d['#value#']
if val is None and d['datatype'] == 'composite':
d['#value#'] = 'RECURSIVE_COMPOSITE FIELD (Metadata) ' + field val = d['#value#'] = self.formatter.safe_format( d['display']['composite_template'], self,
_('TEMPLATE ERROR'), self, column_name=field, template_cache=self.template_cache).strip()
return val if field.startswith('#') and field.endswith('_index'):
try:
return self.get_extra(field[:-6])
except:
pass raise AttributeError(
'Metadata object has no attribute named: '+ repr(field))

def __setattr__(self, field, val, extra=None):
_data = object.__getattribute__(self, '_data')
if field in SIMPLE_SET:
if val is None:
val = copy.copy(NULL_VALUES.get(field, None))
_data[field] = val elif field in TOP_LEVEL_IDENTIFIERS:
field, val = self._clean_identifier(field, val)
identifiers = _data['identifiers']
identifiers.pop(field, None)
if val:
identifiers[field] = val elif field == 'identifiers':
if not val:
val = copy.copy(NULL_VALUES.get('identifiers', None))
self.set_identifiers(val)
elif field == 'language':
langs = []
if val and val.lower() != 'und':
langs = [val]
_data['languages'] = langs elif field in _data['user_metadata']:
d = _data['user_metadata'][field]
d['#value#'] = val d['#extra#'] = extra else:
# You are allowed to stick arbitrary attributes onto this object as
# long as they don't conflict with global or user metadata names
# Don't abuse this privilege self.__dict__[field] = val def __iter__(self):
return iter(object.__getattribute__(self, '_data'))

def has_key(self, key):
return key in object.__getattribute__(self, '_data')

def _evaluate_all_composites(self):
custom_fields = object.__getattribute__(self, '_data')['user_metadata']
for field in custom_fields:
self._evaluate_composite(field)

def _evaluate_composite(self, field):
f = object.__getattribute__(self, '_data')['user_metadata'].get(field, None)
if f is not None:
if f['datatype'] == 'composite' and f['#value#'] is None:
self.get(field)

[dokumentace]
def deepcopy(self, class_generator=lambda : Metadata(None)):
''' Do not use this method unless you know what you are doing, if you want to create a simple clone of this object, use :meth:`deepcopy_metadata` instead. Class_generator must be a function that returns an instance of Metadata or a subclass of it.'''
# We don't need to evaluate all the composites here because we
# are returning a "real" Metadata instance that has __get_attribute__.
m = class_generator()
if not isinstance(m, Metadata):
return None object.__setattr__(m, '__dict__', copy.deepcopy(self.__dict__))
return m
def deepcopy_metadata(self):
# We don't need to evaluate all the composites here because we
# are returning a "real" Metadata instance that has __get_attribute__.
m = Metadata(None)
object.__setattr__(m, '_data', copy.deepcopy(object.__getattribute__(self, '_data')))
return m def get(self, field, default=None):
try:
return self.__getattribute__(field)
except AttributeError:
return default def get_extra(self, field, default=None):
# Don't need to evaluate all composites because a composite can't have
# an extra value
_data = object.__getattribute__(self, '_data')
if field in _data['user_metadata']:
try:
return _data['user_metadata'][field]['#extra#']
except:
return default raise AttributeError(
'Metadata object has no attribute named: '+ repr(field))

def set(self, field, val, extra=None):
self.__setattr__(field, val, extra)

[dokumentace]
def get_identifiers(self):
'''
Return a copy of the identifiers dictionary.
The dict is small, and the penalty for using a reference where a copy is needed is large. Also, we don't want any manipulations of the returned dict to show up in the book.
'''
ans = object.__getattribute__(self, '_data')['identifiers']
if not ans:
ans = {}
return copy.deepcopy(ans)

def _clean_identifier(self, typ, val):
if typ:
typ = ck(typ)
if val:
val = cv(val)
return typ, val

[dokumentace]
def set_identifiers(self, identifiers):
'''
Set all identifiers. Note that if you previously set ISBN, calling this method will delete it.
'''
cleaned = {ck(k):cv(v) for k, v in iteritems(identifiers) if k and v}
object.__getattribute__(self, '_data')['identifiers'] = cleaned

[dokumentace]
def set_identifier(self, typ, val):
'If val is empty, deletes identifier of type typ'
typ, val = self._clean_identifier(typ, val)
if not typ:
return identifiers = object.__getattribute__(self, '_data')['identifiers']

identifiers.pop(typ, None)
if val:
identifiers[typ] = val
def has_identifier(self, typ):
identifiers = object.__getattribute__(self, '_data')['identifiers']
return typ in identifiers

# field-oriented interface. Intended to be the same as in LibraryDatabase

[dokumentace]
def standard_field_keys(self):
'''
return a list of all possible keys, even if this book doesn't have them
'''
return STANDARD_METADATA_FIELDS

[dokumentace]
def custom_field_keys(self):
'''
return a list of the custom fields in this book
'''
return iter(object.__getattribute__(self, '_data')['user_metadata'])

[dokumentace]
def all_field_keys(self):
'''
All field keys known by this instance, even if their value is None
'''
_data = object.__getattribute__(self, '_data')
return frozenset(ALL_METADATA_FIELDS.union(frozenset(_data['user_metadata'])))

[dokumentace]
def metadata_for_field(self, key):
'''
return metadata describing a standard or custom field.
'''
if key not in self.custom_field_keys():
return self.get_standard_metadata(key, make_copy=False)
return self.get_user_metadata(key, make_copy=False)

[dokumentace]
def all_non_none_fields(self):
'''
Return a dictionary containing all non-None metadata fields, including the custom ones.
'''
result = {}
_data = object.__getattribute__(self, '_data')
for attr in STANDARD_METADATA_FIELDS:
v = _data.get(attr, None)
if v is not None:
result[attr] = v
# separate these because it uses the self.get(), not _data.get()
for attr in TOP_LEVEL_IDENTIFIERS:
v = self.get(attr, None)
if v is not None:
result[attr] = v for attr in _data['user_metadata']:
v = self.get(attr, None)
if v is not None:
result[attr] = v if _data['user_metadata'][attr]['datatype'] == 'series':
result[attr+'_index'] = _data['user_metadata'][attr]['#extra#']
return result

# End of field-oriented interface

# Extended interfaces. These permit one to get copies of metadata dictionaries, and to
# get and set custom field metadata

[dokumentace]
def get_standard_metadata(self, field, make_copy):
'''
return field metadata from the field if it is there. Otherwise return
None. field is the key name, not the label. Return a copy if requested, just in case the user wants to change values in the dict.
'''
if field in field_metadata and field_metadata[field]['kind'] == 'field':
if make_copy:
return copy.deepcopy(field_metadata[field])
return field_metadata[field]
return None

[dokumentace]
def get_all_standard_metadata(self, make_copy):
'''
return a dict containing all the standard field metadata associated with the book.
'''
if not make_copy:
return field_metadata res = {}
for k in field_metadata:
if field_metadata[k]['kind'] == 'field':
res[k] = copy.deepcopy(field_metadata[k])
return res

[dokumentace]
def get_all_user_metadata(self, make_copy):
'''
return a dict containing all the custom field metadata associated with the book.
'''
# Must evaluate all composites because we are returning a dict, not a
# Metadata instance self._evaluate_all_composites()
_data = object.__getattribute__(self, '_data')
user_metadata = _data['user_metadata']
if not make_copy:
return user_metadata res = {}
for k in user_metadata:
res[k] = copy.deepcopy(user_metadata[k])
return res

[dokumentace]
def get_user_metadata(self, field, make_copy):
'''
return field metadata from the object if it is there. Otherwise return
None. field is the key name, not the label. Return a copy if requested, just in case the user wants to change values in the dict.
'''
_data = object.__getattribute__(self, '_data')['user_metadata']
if field in _data:
# Must evaluate the field because it might be a composite. It won't
# be evaluated on demand because we are returning its dict, not a
# Metadata instance self._evaluate_composite(field)
if make_copy:
return copy.deepcopy(_data[field])
return _data[field]
return None

[dokumentace]
def set_all_user_metadata(self, metadata):
'''
store custom field metadata into the object. Field is the key name not the label
'''
if metadata is None:
traceback.print_stack()
return um = {}
for key, meta in iteritems(metadata):
m = meta.copy()
if '#value#' not in m:
if m['datatype'] == 'text' and m['is_multiple']:
m['#value#'] = []
else:
m['#value#'] = None um[key] = m
_data = object.__getattribute__(self, '_data')
_data['user_metadata'] = um

[dokumentace]
def set_user_metadata(self, field, metadata):
'''
store custom field metadata for one column into the object. Field is the key name not the label
'''
if field is not None:
if not field.startswith('#'):
raise AttributeError(
'Custom field name %s must begin with \'#\''%repr(field))
if metadata is None:
traceback.print_stack()
return m = dict(metadata)
# Copying the elements should not be necessary. The objects referenced
# in the dict should not change. Of course, they can be replaced.
# for k,v in iteritems(metadata):
# m[k] = copy.copy(v)
if '#value#' not in m:
if m['datatype'] == 'text' and m['is_multiple']:
m['#value#'] = []
else:
m['#value#'] = None
_data = object.__getattribute__(self, '_data')
_data['user_metadata'][field] = m

[dokumentace]
def remove_stale_user_metadata(self, other_mi):
'''
Remove user metadata keys (custom column keys) if they don't exist in 'other_mi', which must be a metadata object
'''
me = self.get_all_user_metadata(make_copy=False)
other = set(other_mi.custom_field_keys())
new = {}
for k,v in me.items():
if k in other:
new[k] = v self.set_all_user_metadata(new)

[dokumentace]
def template_to_attribute(self, other, ops):
'''
Takes a list [(src,dest), (src,dest)], evaluates the template in the context of other, then copies the result to self[dest]. This is on a best-efforts basis. Some assignments can make no sense.
'''
if not ops:
return from calibre.ebooks.metadata.book.formatter import SafeFormat formatter = SafeFormat()
for op in ops:
try:
src = op[0]
dest = op[1]
val = formatter.safe_format(src, other, 'PLUGBOARD TEMPLATE ERROR', other)
if dest == 'tags':
self.set(dest, [f.strip() for f in val.split(',') if f.strip()])
elif dest == 'authors':
self.set(dest, [f.strip() for f in val.split('&') if f.strip()])
else:
self.set(dest, val)
except:
if DEBUG:
traceback.print_exc()

# Old Metadata API {{{ def print_all_attributes(self):
for x in STANDARD_METADATA_FIELDS:
prints('%s:'%x, getattr(self, x, 'None'))
for x in self.custom_field_keys():
meta = self.get_user_metadata(x, make_copy=False)
if meta is not None:
prints(x, meta)
prints('--------------')

[dokumentace]
def smart_update(self, other, replace_metadata=False):
'''
Merge the information in `other` into self. In case of conflicts, the information in `other` takes precedence, unless the information in `other` is NULL.
'''
def copy_not_none(dest, src, attr):
v = getattr(src, attr, None)
if v not in (None, NULL_VALUES.get(attr, None)):
setattr(dest, attr, copy.deepcopy(v))

unknown = _('Unknown')
if other.title and other.title != unknown:
self.title = other.title if hasattr(other, 'title_sort'):
self.title_sort = other.title_sort if other.authors and ( other.authors[0] != unknown or ( not self.authors or ( len(self.authors) == 1 and self.authors[0] == unknown and getattr(self, 'author_sort', None) == unknown
)
)
):
self.authors = list(other.authors)
if hasattr(other, 'author_sort_map'):
self.author_sort_map = dict(other.author_sort_map)
if hasattr(other, 'author_sort'):
self.author_sort = other.author_sort if replace_metadata:
# SPECIAL_FIELDS = frozenset(['lpath', 'size', 'comments', 'thumbnail'])
for attr in SC_COPYABLE_FIELDS:
setattr(self, attr, getattr(other, attr, 1.0 if attr == 'series_index' else None))
self.tags = other.tags self.cover_data = getattr(other, 'cover_data',
NULL_VALUES['cover_data'])
self.set_all_user_metadata(other.get_all_user_metadata(make_copy=True))
for x in SC_FIELDS_COPY_NOT_NULL:
copy_not_none(self, other, x)
if callable(getattr(other, 'get_identifiers', None)):
self.set_identifiers(other.get_identifiers())
# language is handled below else:
for attr in SC_COPYABLE_FIELDS:
copy_not_none(self, other, attr)
for x in SC_FIELDS_COPY_NOT_NULL:
copy_not_none(self, other, x)

if other.tags:
# Case-insensitive but case preserving merging lotags = [t.lower() for t in other.tags]
lstags = [t.lower() for t in self.tags]
ot, st = map(frozenset, (lotags, lstags))
for t in st.intersection(ot):
sidx = lstags.index(t)
oidx = lotags.index(t)
self.tags[sidx] = other.tags[oidx]
self.tags += [t for t in other.tags if t.lower() in ot-st]

if getattr(other, 'cover_data', False):
other_cover = other.cover_data[-1]
self_cover = self.cover_data[-1] if self.cover_data else b''
if not self_cover:
self_cover = b''
if not other_cover:
other_cover = b''
if len(other_cover) > len(self_cover):
self.cover_data = other.cover_data if callable(getattr(other, 'custom_field_keys', None)):
for x in other.custom_field_keys():
meta = other.get_user_metadata(x, make_copy=True)
if meta is not None:
self_tags = self.get(x, [])
if isinstance(self_tags, string_or_bytes):
self_tags = []
self.set_user_metadata(x, meta) # get... did the deepcopy other_tags = other.get(x, [])
if meta['datatype'] == 'text' and meta['is_multiple']:
# Case-insensitive but case preserving merging lotags = [t.lower() for t in other_tags]
try:
lstags = [t.lower() for t in self_tags]
except TypeError:
# Happens if x is not a text, is_multiple field
# on self lstags = []
self_tags = []
ot, st = map(frozenset, (lotags, lstags))
for t in st.intersection(ot):
sidx = lstags.index(t)
oidx = lotags.index(t)
self_tags[sidx] = other_tags[oidx]
self_tags += [t for t in other_tags if t.lower() in ot-st]
setattr(self, x, self_tags)

my_comments = getattr(self, 'comments', '')
other_comments = getattr(other, 'comments', '')
if not my_comments:
my_comments = ''
if not other_comments:
other_comments = ''
if len(other_comments.strip()) > len(my_comments.strip()):
self.comments = other_comments

# Copy all the non-none identifiers if callable(getattr(other, 'get_identifiers', None)):
d = self.get_identifiers()
s = other.get_identifiers()
d.update([v for v in iteritems(s) if v[1] is not None])
self.set_identifiers(d)
else:
# other structure not Metadata. Copy the top-level identifiers for attr in TOP_LEVEL_IDENTIFIERS:
copy_not_none(self, other, attr)

other_lang = getattr(other, 'languages', [])
if other_lang and other_lang != ['und']:
self.languages = list(other_lang)
if not getattr(self, 'series', None):
self.series_index = Nonedef format_series_index(self, val=None):
from calibre.ebooks.metadata import fmt_sidx v = self.series_index if val is None else val try:
x = float(v)
except Exception:
x = 1 return fmt_sidx(x)

def authors_from_string(self, raw):
from calibre.ebooks.metadata import string_to_authors self.authors = string_to_authors(raw)

def format_authors(self):
from calibre.ebooks.metadata import authors_to_string return authors_to_string(self.authors)

def format_tags(self):
return ', '.join([str(t) for t in sorted(self.tags, key=sort_key)])

def format_rating(self, v=None, divide_by=1):
if v is None:
if self.rating is not None:
return str(self.rating/divide_by)
return 'None'
return str(v/divide_by)

[dokumentace]
def format_field(self, key, series_with_index=True):
'''
Returns the tuple (display_name, formatted_value)
'''
name, val, ign, ign = self.format_field_extended(key, series_with_index)
return (name, val)

def format_field_extended(self, key, series_with_index=True):
from calibre.ebooks.metadata import authors_to_string
'''
returns the tuple (display_name, formatted_value, original_value, field_metadata)
'''
from calibre.utils.date import format_date

# Handle custom series index if key.startswith('#') and key.endswith('_index'):
tkey = key[:-6] # strip the _index cmeta = self.get_user_metadata(tkey, make_copy=False)
if cmeta and cmeta['datatype'] == 'series':
if self.get(tkey):
res = self.get_extra(tkey)
return (str(cmeta['name']+'_index'), self.format_series_index(res), res, cmeta)
else:
return (str(cmeta['name']+'_index'), '', '', cmeta)

if key in self.custom_field_keys():
res = self.get(key, None) # get evaluates all necessary composites cmeta = self.get_user_metadata(key, make_copy=False)
name = str(cmeta['name'])
if res is None or res == '': # can't check "not res" because of numeric fields return (name, res, None, None)
orig_res = res datatype = cmeta['datatype']
if datatype == 'text' and cmeta['is_multiple']:
res = cmeta['is_multiple']['list_to_ui'].join(res)
elif datatype == 'series' and series_with_index:
if self.get_extra(key) is not None:
res = res + \ ' [%s]'%self.format_series_index(val=self.get_extra(key))
elif datatype == 'datetime':
res = format_date(res, cmeta['display'].get('date_format','dd MMM yyyy'))
elif datatype == 'bool':
res = _('Yes') if res else _('No')
elif datatype == 'rating':
res = '%.2g'%(res/2)
elif datatype in ['int', 'float']:
try:
fmt = cmeta['display'].get('number_format', None)
res = fmt.format(res)
except:
pass return (name, str(res), orig_res, cmeta)

# convert top-level ids into their value if key in TOP_LEVEL_IDENTIFIERS:
fmeta = field_metadata['identifiers']
name = key res = self.get(key, None)
return (name, res, res, fmeta)

# Translate aliases into the standard field name fmkey = field_metadata.search_term_to_field_key(key)
if fmkey in field_metadata and field_metadata[fmkey]['kind'] == 'field':
res = self.get(key, None)
fmeta = field_metadata[fmkey]
name = str(fmeta['name'])
if res is None or res == '':
return (name, res, None, None)
orig_res = res name = str(fmeta['name'])
datatype = fmeta['datatype']
if key == 'authors':
res = authors_to_string(res)
elif key == 'series_index':
res = self.format_series_index(res)
elif datatype == 'text' and fmeta['is_multiple']:
if isinstance(res, dict):
res = [k + ':' + v for k,v in res.items()]
res = fmeta['is_multiple']['list_to_ui'].join(sorted(filter(None, res), key=sort_key))
elif datatype == 'series' and series_with_index:
res = res + ' [%s]'%self.format_series_index()
elif datatype == 'datetime':
res = format_date(res, fmeta['display'].get('date_format','dd MMM yyyy'))
elif datatype == 'rating':
res = '%.2g'%(res/2)
elif key == 'size':
res = human_readable(res)
return (name, str(res), orig_res, fmeta)

return (None, None, None, None)

def __unicode__representation__(self):
'''
A string representation of this object, suitable for printing to console
'''
from calibre.ebooks.metadata import authors_to_string from calibre.utils.date import isoformat ans = []

def fmt(x, y):
ans.append('%-20s: %s'%(str(x), str(y)))

fmt('Title', self.title)
if self.title_sort:
fmt('Title sort', self.title_sort)
if self.authors:
fmt('Author(s)', authors_to_string(self.authors) +
((' [' + self.author_sort + ']')
if self.author_sort and self.author_sort != _('Unknown') else ''))
if self.publisher:
fmt('Publisher', self.publisher)
if getattr(self, 'book_producer', False):
fmt('Book Producer', self.book_producer)
if self.tags:
fmt('Tags', ', '.join([str(t) for t in self.tags]))
if self.series:
fmt('Series', self.series + ' #%s'%self.format_series_index())
if not self.is_null('languages'):
fmt('Languages', ', '.join(self.languages))
if self.rating is not None:
fmt('Rating', ('%.2g'%(float(self.rating)/2)) if self.rating else '')
if self.timestamp is not None:
fmt('Timestamp', isoformat(self.timestamp))
if self.pubdate is not None:
fmt('Published', isoformat(self.pubdate))
if self.rights is not None:
fmt('Rights', str(self.rights))
if self.identifiers:
fmt('Identifiers', ', '.join(['%s:%s'%(k, v) for k, v in iteritems(self.identifiers)]))
if self.comments:
fmt('Comments', self.comments)

for key in self.custom_field_keys():
val = self.get(key, None)
if val:
(name, val) = self.format_field(key)
fmt(name, str(val))
return '\n'.join(ans)

[dokumentace]
def to_html(self):
'''
A HTML representation of this object.
'''
from calibre.ebooks.metadata import authors_to_string from calibre.utils.date import isoformat ans = [(_('Title'), str(self.title))]
ans += [(_('Author(s)'), (authors_to_string(self.authors) if self.authors else _('Unknown')))]
ans += [(_('Publisher'), str(self.publisher))]
ans += [(_('Producer'), str(self.book_producer))]
ans += [(_('Comments'), str(self.comments))]
ans += [('ISBN', str(self.isbn))]
ans += [(_('Tags'), ', '.join([str(t) for t in self.tags]))]
if self.series:
ans += [(ngettext('Series', 'Series', 1), str(self.series) + ' #%s'%self.format_series_index())]
ans += [(_('Languages'), ', '.join(self.languages))]
if self.timestamp is not None:
ans += [(_('Timestamp'), str(isoformat(self.timestamp, as_utc=False, sep=' ')))]
if self.pubdate is not None:
ans += [(_('Published'), str(isoformat(self.pubdate, as_utc=False, sep=' ')))]
if self.rights is not None:
ans += [(_('Rights'), str(self.rights))]
for key in self.custom_field_keys():
val = self.get(key, None)
if val:
(name, val) = self.format_field(key)
ans += [(name, val)]
for i, x in enumerate(ans):
ans[i] = '<tr><td><b>%s</b></td><td>%s</td></tr>'%x return '<table>%s</table>'%'\n'.join(ans)__str__ = __unicode__representation__ def __nonzero__(self):
return bool(self.title or self.author or self.comments or self.tags)
__bool__ = __nonzero__# }}}

def field_from_string(field, raw, field_metadata):
''' Parse the string raw to return an object that is suitable for calling set() on a Metadata object. '''
dt = field_metadata['datatype']
val = object if dt in {'int', 'float'}:
val = int(raw) if dt == 'int' else float(raw)
elif dt == 'rating':
val = float(raw) * 2 elif dt == 'datetime':
from calibre.utils.iso8601 import parse_iso8601 try:
val = parse_iso8601(raw, require_aware=True)
except Exception:
from calibre.utils.date import parse_only_date val = parse_only_date(raw)
elif dt == 'bool':
if raw.lower() in {'true', 'yes', 'y'}:
val = True elif raw.lower() in {'false', 'no', 'n'}:
val = False else:
raise ValueError('Unknown value for %s: %s'%(field, raw))
elif dt == 'text':
ism = field_metadata['is_multiple']
if ism:
val = [x.strip() for x in raw.split(ism['ui_to_list'])]
if field == 'identifiers':
val = {x.partition(':')[0]:x.partition(':')[-1] for x in val}
elif field == 'languages':
from calibre.utils.localization import canonicalize_lang val = [canonicalize_lang(x) for x in val]
val = [x for x in val if x]
if val is object:
val = raw return val

© Copyright Kovid Goyal.
Naposledy aktualizováno kvě 03, 2024.

Navigace

- moduly
- Spustit »
- Kód modulu »
- calibre.ebooks.metadata.sources.base

Zdrojový kód pro calibre.ebooks.metadata.sources.base

#!/usr/bin/env python
# vim:fileencoding=UTF-8:ts=4:sw=4:sta:et:sts=4:ai from __future__ import absolute_import, division, print_function, unicode_literals

__license__ = 'GPL v3'
__copyright__ = '2011, Kovid Goyal <kovid@kovidgoyal.net>'
__docformat__ = 'restructuredtext en'

import re import threading from functools import total_ordering from calibre import browser, random_user_agent from calibre.customize import Plugin from calibre.ebooks.metadata import check_isbn from calibre.ebooks.metadata.author_mapper import cap_author_token from calibre.utils.localization import canonicalize_lang, get_lang from polyglot.builtins import cmp, iteritems def create_log(ostream=None):
from calibre.utils.logging import FileStream, ThreadSafeLog log = ThreadSafeLog(level=ThreadSafeLog.DEBUG)
log.outputs = [FileStream(ostream)]
return log

# Comparing Metadata objects for relevance {{{ words = ("the", "a", "an", "of", "and")
prefix_pat = re.compile(r'^(%s)\s+'%("|".join(words)))
trailing_paren_pat = re.compile(r'\(.*\)$')
whitespace_pat = re.compile(r'\s+')

def cleanup_title(s):
if not s:
s = _('Unknown')
s = s.strip().lower()
s = prefix_pat.sub(' ', s)
s = trailing_paren_pat.sub('', s)
s = whitespace_pat.sub(' ', s)
return s.strip()

[dokumentace]
@total_ordering class InternalMetadataCompareKeyGen:

'''
Generate a sort key for comparison of the relevance of Metadata objects, given a search query. This is used only to compare results from the same metadata source, not across different sources.

The sort key ensures that an ascending order sort is a sort by order of decreasing relevance.

The algorithm is:

* Prefer results that have at least one identifier the same as for the query
* Prefer results with a cached cover URL
* Prefer results with all available fields filled in
* Prefer results with the same language as the current user interface language
* Prefer results that are an exact title match to the query
* Prefer results with longer comments (greater than 10% longer)
* Use the relevance of the result as reported by the metadata source's search engine
'''

def __init__(self, mi, source_plugin, title, authors, identifiers):
same_identifier = 2 idents = mi.get_identifiers()
for k, v in iteritems(identifiers):
if idents.get(k) == v:
same_identifier = 1 break all_fields = 1 if source_plugin.test_fields(mi) is None else 2 exact_title = 1 if title and \ cleanup_title(title) == cleanup_title(mi.title) else 2 language = 1 if mi.language:
mil = canonicalize_lang(mi.language)
if mil != 'und' and mil != canonicalize_lang(get_lang()):
language = 2 has_cover = 2 if (not source_plugin.cached_cover_url_is_reliable or source_plugin.get_cached_cover_url(mi.identifiers) is None) else 1 self.base = (same_identifier, has_cover, all_fields, language, exact_title)
self.comments_len = len((mi.comments or '').strip())
self.extra = getattr(mi, 'source_relevance', 0)

def compare_to_other(self, other):
a = cmp(self.base, other.base)
if a != 0:
return a cx, cy = self.comments_len, other.comments_len if cx and cy:
t = (cx + cy) / 20 delta = cy - cx if abs(delta) > t:
return -1 if delta < 0 else 1 return cmp(self.extra, other.extra)

def __eq__(self, other):
return self.compare_to_other(other) == 0 def __ne__(self, other):
return self.compare_to_other(other) != 0 def __lt__(self, other):
return self.compare_to_other(other) < 0 def __le__(self, other):
return self.compare_to_other(other) <= 0 def __gt__(self, other):
return self.compare_to_other(other) > 0 def __ge__(self, other):
return self.compare_to_other(other) >= 0# }}}

def get_cached_cover_urls(mi):
from calibre.customize.ui import metadata_plugins plugins = list(metadata_plugins(['identify']))
for p in plugins:
url = p.get_cached_cover_url(mi.identifiers)
if url:
yield (p, url)

def dump_caches():
from calibre.customize.ui import metadata_plugins return {p.name:p.dump_caches() for p in metadata_plugins(['identify'])}

def load_caches(dump):
from calibre.customize.ui import metadata_plugins plugins = list(metadata_plugins(['identify']))
for p in plugins:
cache = dump.get(p.name, None)
if cache:
p.load_caches(cache)

def fixauthors(authors):
if not authors:
return authors ans = []
for x in authors:
ans.append(' '.join(map(cap_author_token, x.split())))
return ans def fixcase(x):
if x:
from calibre.utils.titlecase import titlecase x = titlecase(x)
return x class Option:
__slots__ = ['type', 'default', 'label', 'desc', 'name', 'choices']

def __init__(self, name, type_, default, label, desc, choices=None):
'''
:param name: The name of this option. Must be a valid python identifier
:param type_: The type of this option, one of ('number', 'string',
'bool', 'choices')
:param default: The default value for this option
:param label: A short (few words) description of this option
:param desc: A longer description of this option
:param choices: A dict of possible values, used only if type='choices'.
dict is of the form {key:human readable label, ...}
'''
self.name, self.type, self.default, self.label, self.desc = (name, type_, default, label, desc)
if choices and not isinstance(choices, dict):
choices = dict([(x, x) for x in choices])
self.choices = choices

[dokumentace]
class Source(Plugin):

type = _('Metadata source')
author = 'Kovid Goyal'

supported_platforms = ['windows', 'osx', 'linux']

#: Set of capabilities supported by this plugin.
#: Useful capabilities are: 'identify', 'cover'
capabilities = frozenset()

#: List of metadata fields that can potentially be download by this plugin
#: during the identify phase touched_fields = frozenset()

#: Set this to True if your plugin returns HTML formatted comments has_html_comments = False

#: Setting this to True means that the browser object will indicate
#: that it supports gzip transfer encoding. This can speedup downloads
#: but make sure that the source actually supports gzip transfer encoding
#: correctly first supports_gzip_transfer_encoding = False

#: Set this to True to ignore HTTPS certificate errors when connecting
#: to this source.
ignore_ssl_errors = False

#: Cached cover URLs can sometimes be unreliable (i.e. the download could
#: fail or the returned image could be bogus). If that is often the case
#: with this source, set to False cached_cover_url_is_reliable = True

#: A list of :class:`Option` objects. They will be used to automatically
#: construct the configuration widget for this plugin options = ()

#: A string that is displayed at the top of the config widget for this
#: plugin config_help_message = None

#: If True this source can return multiple covers for a given query can_get_multiple_covers = False

#: If set to True covers downloaded by this plugin are automatically trimmed.
auto_trim_covers = False

#: If set to True, and this source returns multiple results for a query,
#: some of which have ISBNs and some of which do not, the results without
#: ISBNs will be ignored prefer_results_with_isbn = True def __init__(self, *args, **kwargs):
Plugin.__init__(self, *args, **kwargs)
self.running_a_test = False # Set to True when using identify_test()
self._isbn_to_identifier_cache = {}
self._identifier_to_cover_url_cache = {}
self.cache_lock = threading.RLock()
self._config_obj = None self._browser = None self.prefs.defaults['ignore_fields'] = []
for opt in self.options:
self.prefs.defaults[opt.name] = opt.default

# Configuration {{{

[dokumentace]
def is_configured(self):
'''
Return False if your plugin needs to be configured before it can be used. For example, it might need a username/password/API key.
'''
return True
def is_customizable(self):
return True

[dokumentace]
def customization_help(self):
return 'This plugin can only be customized using the GUI'

[dokumentace]
def config_widget(self):
from calibre.gui2.metadata.config import ConfigWidget return ConfigWidget(self)

[dokumentace]
def save_settings(self, config_widget):
config_widget.commit()

@property def prefs(self):
if self._config_obj is None:
from calibre.utils.config import JSONConfig self._config_obj = JSONConfig('metadata_sources/%s.json'%self.name)
return self._config_obj
# }}}

# Browser {{{

@property def user_agent(self):
# Pass in an index to random_user_agent() to test with a particular
# user agent return random_user_agent()

@property def browser(self):
if self._browser is None:
self._browser = browser(user_agent=self.user_agent, verify_ssl_certificates=not self.ignore_ssl_errors)
if self.supports_gzip_transfer_encoding:
self._browser.set_handle_gzip(True)
return self._browser.clone_browser()

# }}}

# Caching {{{ def get_related_isbns(self, id_):
with self.cache_lock:
for isbn, q in iteritems(self._isbn_to_identifier_cache):
if q == id_:
yield isbn def cache_isbn_to_identifier(self, isbn, identifier):
with self.cache_lock:
self._isbn_to_identifier_cache[isbn] = identifier def cached_isbn_to_identifier(self, isbn):
with self.cache_lock:
return self._isbn_to_identifier_cache.get(isbn, None)

def cache_identifier_to_cover_url(self, id_, url):
with self.cache_lock:
self._identifier_to_cover_url_cache[id_] = url def cached_identifier_to_cover_url(self, id_):
with self.cache_lock:
return self._identifier_to_cover_url_cache.get(id_, None)

def dump_caches(self):
with self.cache_lock:
return {'isbn_to_identifier':self._isbn_to_identifier_cache.copy(),
'identifier_to_cover':self._identifier_to_cover_url_cache.copy()}

def load_caches(self, dump):
with self.cache_lock:
self._isbn_to_identifier_cache.update(dump['isbn_to_identifier'])
self._identifier_to_cover_url_cache.update(dump['identifier_to_cover'])

# }}}

# Utility functions {{{

[dokumentace]
def get_author_tokens(self, authors, only_first_author=True):
'''
Take a list of authors and return a list of tokens useful for an
AND search query. This function tries to return tokens in first name middle names last name order, by assuming that if a comma is in the author name, the name is in lastname, other names form.
'''

if authors:
# Leave ' in there for Irish names remove_pat = re.compile(r'[!@#$%^&*()（）「」{}`~"\s\[\]/]')
replace_pat = re.compile(r'[-+.:;,，。；：]')
if only_first_author:
authors = authors[:1]
for au in authors:
has_comma = ',' in au au = replace_pat.sub(' ', au)
parts = au.split()
if has_comma:
# au probably in ln, fn form parts = parts[1:] + parts[:1]
for tok in parts:
tok = remove_pat.sub('', tok).strip()
if len(tok) > 2 and tok.lower() not in ('von', 'van',
_('Unknown').lower()):
yield tok

[dokumentace]
def get_title_tokens(self, title, strip_joiners=True, strip_subtitle=False):
'''
Take a title and return a list of tokens useful for an AND search query.
Excludes connectives(optionally) and punctuation.
'''
if title:
# strip sub-titles if strip_subtitle:
subtitle = re.compile(r'([\(\[\{].*?[\)\]\}]|[/:\\].*$)')
if len(subtitle.sub('', title)) > 1:
title = subtitle.sub('', title)

title_patterns = [(re.compile(pat, re.IGNORECASE), repl) for pat, repl in
[
# Remove things like: (2010) (Omnibus) etc.
(r'(?i)[({\[](\d{4}|omnibus|anthology|hardcover|audiobook|audio\scd|paperback|turtleback|mass\s*market|edition|ed\.)[\])}]', ''),
# Remove any strings that contain the substring edition inside
# parentheses
(r'(?i)[({\[].*?(edition|ed.).*?[\]})]', ''),
# Remove commas used a separators in numbers
(r'(\d+),(\d+)', r'\1\2'),
# Remove hyphens only if they have whitespace before them
(r'(\s-)', ' '),
# Replace other special chars with a space
(r'''[:,;!@$%^&*(){}.`~"\s\[\]/]《》「」“”''', ' '),
]]

for pat, repl in title_patterns:
title = pat.sub(repl, title)

tokens = title.split()
for token in tokens:
token = token.strip().strip('"').strip("'")
if token and (not strip_joiners or token.lower() not in ('a',
'and', 'the', '&')):
yield token

[dokumentace]
def split_jobs(self, jobs, num):
'Split a list of jobs into at most num groups, as evenly as possible'
groups = [[] for i in range(num)]
jobs = list(jobs)
while jobs:
for gr in groups:
try:
job = jobs.pop()
except IndexError:
break gr.append(job)
return [g for g in groups if g]

[dokumentace]
def test_fields(self, mi):
'''
Return the first field from self.touched_fields that is null on the mi object
'''
for key in self.touched_fields:
if key.startswith('identifier:'):
key = key.partition(':')[-1]
if not mi.has_identifier(key):
return 'identifier: ' + key elif mi.is_null(key):
return key

[dokumentace]
def clean_downloaded_metadata(self, mi):
'''
Call this method in your plugin's identify method to normalize metadata before putting the Metadata object into result_queue. You can of course, use a custom algorithm suited to your metadata source.
'''
docase = mi.language == 'eng' or mi.is_null('language')
if docase and mi.title:
mi.title = fixcase(mi.title)
mi.authors = fixauthors(mi.authors)
if mi.tags and docase:
mi.tags = list(map(fixcase, mi.tags))
mi.isbn = check_isbn(mi.isbn)

def download_multiple_covers(self, title, authors, urls, get_best_cover, timeout, result_queue, abort, log, prefs_name='max_covers'):
if not urls:
log('No images found for, title: %r and authors: %r'%(title, authors))
return import time from threading import Thread if prefs_name:
urls = urls[:self.prefs[prefs_name]]
if get_best_cover:
urls = urls[:1]
log('Downloading %d covers'%len(urls))
workers = [Thread(target=self.download_image, args=(u, timeout, log, result_queue)) for u in urls]
for w in workers:
w.daemon = True w.start()
alive = True start_time = time.time()
while alive and not abort.is_set() and time.time() - start_time < timeout:
alive = False for w in workers:
if w.is_alive():
alive = True break abort.wait(0.1)

def download_image(self, url, timeout, log, result_queue):
try:
ans = self.browser.open_novisit(url, timeout=timeout).read()
result_queue.put((self, ans))
log('Downloaded cover from: %s'%url)
except Exception:
self.log.exception('Failed to download cover from: %r'%url)

# }}}

# Metadata API {{{

[dokumentace]
def get_book_url(self, identifiers):
'''
Return a 3-tuple or None. The 3-tuple is of the form:
(identifier_type, identifier_value, URL).
The URL is the URL for the book identified by identifiers at this source. identifier_type, identifier_value specify the identifier corresponding to the URL.
This URL must be browsable to by a human using a browser. It is meant to provide a clickable link for the user to easily visit the books page at this source.
If no URL is found, return None. This method must be quick, and consistent, so only implement it if it is possible to construct the URL from a known scheme given identifiers.
'''
return None

[dokumentace]
def get_book_url_name(self, idtype, idval, url):
'''
Return a human readable name from the return value of get_book_url().
'''
return self.name

[dokumentace]
def get_book_urls(self, identifiers):
'''
Override this method if you would like to return multiple URLs for this book.
Return a list of 3-tuples. By default this method simply calls :func:`get_book_url`.
'''
data = self.get_book_url(identifiers)
if data is None:
return ()
return (data,)

[dokumentace]
def get_cached_cover_url(self, identifiers):
'''
Return cached cover URL for the book identified by the identifiers dictionary or None if no such URL exists.

Note that this method must only return validated URLs, i.e. not URLS that could result in a generic cover image or a not found error.
'''
return None

[dokumentace]
def id_from_url(self, url):
'''
Parse a URL and return a tuple of the form:
(identifier_type, identifier_value).
If the URL does not match the pattern for the metadata source, return None.
'''
return None

[dokumentace]
def identify_results_keygen(self, title=None, authors=None, identifiers={}):
'''
Return a function that is used to generate a key that can sort Metadata objects by their relevance given a search query (title, authors, identifiers).

These keys are used to sort the results of a call to :meth:`identify`.

For details on the default algorithm see
:class:`InternalMetadataCompareKeyGen`. Re-implement this function in your plugin if the default algorithm is not suitable.
'''
def keygen(mi):
return InternalMetadataCompareKeyGen(mi, self, title, authors, identifiers)
return keygen

[dokumentace]
def identify(self, log, result_queue, abort, title=None, authors=None, identifiers={}, timeout=30):
'''
Identify a book by its Title/Author/ISBN/etc.

If identifiers(s) are specified and no match is found and this metadata source does not store all related identifiers (for example, all ISBNs of a book), this method should retry with just the title and author
(assuming they were specified).

If this metadata source also provides covers, the URL to the cover should be cached so that a subsequent call to the get covers API with the same ISBN/special identifier does not need to get the cover URL again. Use the caching API for this.

Every Metadata object put into result_queue by this method must have a
`source_relevance` attribute that is an integer indicating the order in which the results were returned by the metadata source for this query.
This integer will be used by :meth:`compare_identify_results`. If the order is unimportant, set it to zero for every result.

Make sure that any cover/ISBN mapping information is cached before the
Metadata object is put into result_queue.

:param log: A log object, use it to output debugging information/errors
:param result_queue: A result Queue, results should be put into it.
Each result is a Metadata object
:param abort: If abort.is_set() returns True, abort further processing and return as soon as possible
:param title: The title of the book, can be None
:param authors: A list of authors of the book, can be None
:param identifiers: A dictionary of other identifiers, most commonly
{'isbn':'1234...'}
:param timeout: Timeout in seconds, no network request should hang for longer than timeout.
:return: None if no errors occurred, otherwise a unicode representation of the error suitable for showing to the user

'''
return None

[dokumentace]
def download_cover(self, log, result_queue, abort, title=None, authors=None, identifiers={}, timeout=30, get_best_cover=False):
'''
Download a cover and put it into result_queue. The parameters all have the same meaning as for :meth:`identify`. Put (self, cover_data) into result_queue.

This method should use cached cover URLs for efficiency whenever possible. When cached data is not present, most plugins simply call identify and use its results.

If the parameter get_best_cover is True and this plugin can get multiple covers, it should only get the "best" one.
'''
pass

# }}}

© Copyright Kovid Goyal.
Naposledy aktualizováno kvě 03, 2024.

Navigace

- moduly
- Spustit »
- Kód modulu »
- calibre.ebooks.oeb.polish.container

Zdrojový kód pro calibre.ebooks.oeb.polish.container

#!/usr/bin/env python
# License: GPLv3 Copyright: 2013, Kovid Goyal <kovid at kovidgoyal.net> import errno import hashlib import logging import os import re import shutil import sys import unicodedata import uuid from collections import defaultdict from io import BytesIO from itertools import count from css_parser import getUrls, replaceUrls from calibre import CurrentDir, walk from calibre.constants import iswindows from calibre.customize.ui import plugin_for_input_format, plugin_for_output_format from calibre.ebooks import escape_xpath_attr from calibre.ebooks.chardet import xml_to_unicode from calibre.ebooks.conversion.plugins.epub_input import ADOBE_OBFUSCATION, IDPF_OBFUSCATION, decrypt_font_data from calibre.ebooks.conversion.preprocess import CSSPreProcessor as cssp from calibre.ebooks.conversion.preprocess import HTMLPreProcessor from calibre.ebooks.metadata.opf3 import CALIBRE_PREFIX, ensure_prefix, items_with_property, read_prefixes from calibre.ebooks.metadata.utils import parse_opf_version from calibre.ebooks.mobi import MobiError from calibre.ebooks.mobi.reader.headers import MetadataHeader from calibre.ebooks.oeb.base import (
DC11_NS,
OEB_DOCS,
OEB_STYLES,
OPF,
OPF2_NS,
Manifest, itercsslinks, iterlinks, rewrite_links, serialize, urlquote, urlunquote,
)
from calibre.ebooks.oeb.parse_utils import NotHTML, parse_html from calibre.ebooks.oeb.polish.errors import DRMError, InvalidBook from calibre.ebooks.oeb.polish.parsing import parse as parse_html_tweak from calibre.ebooks.oeb.polish.utils import OEB_FONTS, CommentFinder, PositionFinder, adjust_mime_for_epub, guess_type, parse_css from calibre.ptempfile import PersistentTemporaryDirectory, PersistentTemporaryFile from calibre.utils.filenames import hardlink_file, nlinks_file, retry_on_fail from calibre.utils.ipc.simple_worker import WorkerError, fork_job from calibre.utils.logging import default_log from calibre.utils.xml_parse import safe_xml_fromstring from calibre.utils.zipfile import ZipFile from polyglot.builtins import iteritems from polyglot.urllib import urlparse exists, join, relpath = os.path.exists, os.path.join, os.path.relpath
OPF_NAMESPACES = {'opf':OPF2_NS, 'dc':DC11_NS}
null = object()
OEB_FONTS # for plugin compat class CSSPreProcessor(cssp):

def __call__(self, data):
return self.MS_PAT.sub(self.ms_sub, data)

def clone_dir(src, dest):
' Clone a folder using hard links for the files, dest must already exist '
for x in os.listdir(src):
dpath = os.path.join(dest, x)
spath = os.path.join(src, x)
if os.path.isdir(spath):
os.mkdir(dpath)
clone_dir(spath, dpath)
else:
try:
hardlink_file(spath, dpath)
except:
shutil.copy2(spath, dpath)

def clone_container(container, dest_dir):
' Efficiently clone a container using hard links '
dest_dir = os.path.abspath(os.path.realpath(dest_dir))
clone_data = container.clone_data(dest_dir)
cls = type(container)
if cls is Container:
return cls(None, None, container.log, clone_data=clone_data)
return cls(None, container.log, clone_data=clone_data)

def name_to_abspath(name, root):
return os.path.abspath(join(root, *name.split('/')))

def abspath_to_name(path, root):
return relpath(os.path.abspath(path), root).replace(os.sep, '/')

def name_to_href(name, root, base=None, quote=urlquote):
fullpath = name_to_abspath(name, root)
basepath = root if base is None else os.path.dirname(name_to_abspath(base, root))
path = relpath(fullpath, basepath).replace(os.sep, '/')
return quote(path)

def href_to_name(href, root, base=None):
base = root if base is None else os.path.dirname(name_to_abspath(base, root))
try:
purl = urlparse(href)
except ValueError:
return None if purl.scheme or not purl.path:
return None href = urlunquote(purl.path)
if iswindows and ':' in href:
# path manipulations on windows fail for paths with : in them, so we
# assume all such paths are invalid/absolute paths.
return None fullpath = os.path.join(base, *href.split('/'))
try:
return unicodedata.normalize('NFC', abspath_to_name(fullpath, root))
except ValueError:
return None class ContainerBase: # {{{
'''
A base class that implements just the parsing methods. Useful to create virtual containers for testing.
'''

#: The mode used to parse HTML and CSS (polishing uses tweak_mode=False and the editor uses tweak_mode=True)
tweak_mode = False def __init__(self, log):
self.log = log self.parsed_cache = {}
self.mime_map = {}
self.encoding_map = {}
self.html_preprocessor = HTMLPreProcessor()
self.css_preprocessor = CSSPreProcessor()

def guess_type(self, name):
' Return the expected mimetype for the specified file name based on its extension. '
return adjust_mime_for_epub(filename=name, opf_version=self.opf_version_parsed)

def decode(self, data, normalize_to_nfc=True):
"""
Automatically decode ``data`` into a ``unicode`` object.

:param normalize_to_nfc: Normalize returned unicode to the NFC normal form as is required by both the EPUB and AZW3 formats.
"""
def fix_data(d):
return d.replace('\r\n', '\n').replace('\r', '\n')
if isinstance(data, str):
return fix_data(data)
bom_enc = None if data[:4] in {b'\0\0\xfe\xff', b'\xff\xfe\0\0'}:
bom_enc = {b'\0\0\xfe\xff':'utf-32-be', b'\xff\xfe\0\0':'utf-32-le'}[data[:4]]
data = data[4:]
elif data[:2] in {b'\xff\xfe', b'\xfe\xff'}:
bom_enc = {b'\xff\xfe':'utf-16-le', b'\xfe\xff':'utf-16-be'}[data[:2]]
data = data[2:]
elif data[:3] == b'\xef\xbb\xbf':
bom_enc = 'utf-8'
data = data[3:]
if bom_enc is not None:
try:
self.used_encoding = bom_enc return fix_data(data.decode(bom_enc))
except UnicodeDecodeError:
pass try:
self.used_encoding = 'utf-8'
return fix_data(data.decode('utf-8'))
except UnicodeDecodeError:
pass data, self.used_encoding = xml_to_unicode(data)
if normalize_to_nfc:
data = unicodedata.normalize('NFC', data)
return fix_data(data)

def parse_xml(self, data):
data, self.used_encoding = xml_to_unicode( data, strip_encoding_pats=True, assume_utf8=True, resolve_entities=True)
data = unicodedata.normalize('NFC', data)
return safe_xml_fromstring(data)

def parse_xhtml(self, data, fname='<string>', force_html5_parse=False):
if self.tweak_mode:
return parse_html_tweak(data, log=self.log, decoder=self.decode, force_html5_parse=force_html5_parse)
else:
try:
return parse_html( data, log=self.log, decoder=self.decode, preprocessor=self.html_preprocessor, filename=fname, non_html_file_tags={'ncx'})
except NotHTML:
return self.parse_xml(data)

def parse_css(self, data, fname='<string>', is_declaration=False):
return parse_css(data, fname=fname, is_declaration=is_declaration, decode=self.decode, log_level=logging.WARNING, css_preprocessor=(None if self.tweak_mode else self.css_preprocessor))
# }}}

[dokumentace]
class Container(ContainerBase): # {{{

'''
A container represents an open e-book as a folder full of files and an
OPF file. There are two important concepts:

* The root folder. This is the base of the e-book. All the e-books files are inside this folder or in its sub-folders.

* Names: These are paths to the books' files relative to the root folder. They always contain POSIX separators and are unquoted. They can be thought of as canonical identifiers for files in the book.
Most methods on the container object work with names. Names are always in the NFC Unicode normal form.

* Clones: the container object supports efficient on-disk cloning, which is used to implement checkpoints in the e-book editor. In order to make this work, you should never access files on the filesystem directly. Instead, use :meth:`raw_data` or
:meth:`open` to read/write to component files in the book.

When converting between hrefs and names use the methods provided by this class, they assume all hrefs are quoted.
'''

#: The type of book (epub for EPUB files and azw3 for AZW3 files)
book_type = 'oeb'
#: If this container represents an unzipped book (a directory)
is_dir = False

SUPPORTS_TITLEPAGES = True
SUPPORTS_FILENAMES = True

@property def book_type_for_display(self):
return self.book_type.upper()

def __init__(self, rootpath, opfpath, log, clone_data=None):
ContainerBase.__init__(self, log)
self.root = clone_data['root'] if clone_data is not None else os.path.abspath(rootpath)

self.name_path_map = {}
self.dirtied = set()
self.pretty_print = set()
self.cloned = False self.cache_names = ('parsed_cache', 'mime_map', 'name_path_map', 'encoding_map', 'dirtied', 'pretty_print')
self.href_to_name_cache = {}

if clone_data is not None:
self.cloned = True for x in ('name_path_map', 'opf_name', 'mime_map', 'pretty_print', 'encoding_map', 'tweak_mode'):
setattr(self, x, clone_data[x])
self.opf_dir = os.path.dirname(self.name_path_map[self.opf_name])
return

# Map of relative paths with '/' separators from root of unzipped ePub
# to absolute paths on filesystem with os-specific separators opfpath = os.path.abspath(os.path.realpath(opfpath))
all_opf_files = []
for dirpath, _dirnames, filenames in os.walk(self.root):
for f in filenames:
path = join(dirpath, f)
name = self.abspath_to_name(path)
self.name_path_map[name] = path self.mime_map[name] = guess_type(path)
# Special case if we have stumbled onto the opf if path == opfpath:
self.opf_name = name self.opf_dir = os.path.dirname(path)
self.mime_map[name] = guess_type('a.opf')
if path.lower().endswith('.opf'):
all_opf_files.append((name, os.path.dirname(path)))

if not hasattr(self, 'opf_name') and all_opf_files:
self.opf_name, self.opf_dir = all_opf_files[0]
self.mime_map[self.opf_name] = guess_type('a.opf')

if not hasattr(self, 'opf_name'):
raise InvalidBook('Could not locate opf file: %r'%opfpath)

# Update mime map with data from the OPF self.refresh_mime_map()

def refresh_mime_map(self):
for item in self.opf_xpath('//opf:manifest/opf:item[@href and @media-type]'):
href = item.get('href')
try:
name = self.href_to_name(href, self.opf_name)
except ValueError:
continue # special filenames such as CON on windows cause relpath to fail mt = item.get('media-type')
if name in self.mime_map and name != self.opf_name and mt:
# some epubs include the opf in the manifest with an incorrect mime type self.mime_map[name] = mt def data_for_clone(self, dest_dir=None):
dest_dir = dest_dir or self.root return {
'root': dest_dir,
'opf_name': self.opf_name,
'mime_map': self.mime_map.copy(),
'pretty_print': set(self.pretty_print),
'encoding_map': self.encoding_map.copy(),
'tweak_mode': self.tweak_mode,
'name_path_map': { name:os.path.join(dest_dir, os.path.relpath(path, self.root))
for name, path in iteritems(self.name_path_map)}
}

def clone_data(self, dest_dir):
Container.commit(self, keep_parsed=False)
self.cloned = True clone_dir(self.root, dest_dir)
return self.data_for_clone(dest_dir)

[dokumentace]
def add_name_to_manifest(self, name, process_manifest_item=None):
' Add an entry to the manifest for a file with the specified name. Returns the manifest id. '
all_ids = {x.get('id') for x in self.opf_xpath('//*[@id]')}
c = 0 item_id = 'id'
while item_id in all_ids:
c += 1 item_id = 'id' + '%d'%c manifest = self.opf_xpath('//opf:manifest')[0]
href = self.name_to_href(name, self.opf_name)
item = manifest.makeelement(OPF('item'), id=item_id, href=href)
item.set('media-type', self.mime_map[name])
self.insert_into_xml(manifest, item)
if process_manifest_item is not None:
process_manifest_item(item)
self.dirty(self.opf_name)
return item_id

[dokumentace]
def manifest_has_name(self, name):
''' Return True if the manifest has an entry corresponding to name '''
all_names = {self.href_to_name(x.get('href'), self.opf_name) for x in self.opf_xpath('//opf:manifest/opf:item[@href]')}
return name in all_names

[dokumentace]
def make_name_unique(self, name):
''' Ensure that `name` does not already exist in this book. If it does, return a modified version that does not exist. '''
counter = count()
while self.has_name_case_insensitive(name) or self.manifest_has_name(name):
c = next(counter) + 1 base, ext = name.rpartition('.')[::2]
if c > 1:
base = base.rpartition('-')[0]
name = '%s-%d.%s' % (base, c, ext)
return name

[dokumentace]
def add_file(self, name, data, media_type=None, spine_index=None, modify_name_if_needed=False, process_manifest_item=None):
''' Add a file to this container. Entries for the file are automatically created in the OPF manifest and spine
(if the file is a text document) '''
if '..' in name:
raise ValueError('Names are not allowed to have .. in them')
href = self.name_to_href(name, self.opf_name)
if self.has_name_case_insensitive(name) or self.manifest_has_name(name):
if not modify_name_if_needed:
raise ValueError(('A file with the name %s already exists' % name) if self.has_name_case_insensitive(name) else
('An item with the href %s already exists in the manifest' % href))
name = self.make_name_unique(name)
href = self.name_to_href(name, self.opf_name)
path = self.name_to_abspath(name)
base = os.path.dirname(path)
if not os.path.exists(base):
os.makedirs(base)
with open(path, 'wb') as f:
if hasattr(data, 'read'):
shutil.copyfileobj(data, f)
else:
f.write(data)
mt = media_type or self.guess_type(name)
self.name_path_map[name] = path self.mime_map[name] = mt if self.ok_to_be_unmanifested(name):
return name item_id = self.add_name_to_manifest(name, process_manifest_item=process_manifest_item)
if mt in OEB_DOCS:
manifest = self.opf_xpath('//opf:manifest')[0]
spine = self.opf_xpath('//opf:spine')[0]
si = manifest.makeelement(OPF('itemref'), idref=item_id)
self.insert_into_xml(spine, si, index=spine_index)
return name[dokumentace]
def rename(self, current_name, new_name):
''' Renames a file from current_name to new_name. It automatically rebases all links inside the file if the folder the file is in changes. Note however, that links are not updated in the other files that could reference this file. This is for performance, such updates should be done once, in bulk. '''
if current_name in self.names_that_must_not_be_changed:
raise ValueError('Renaming of %s is not allowed' % current_name)
if self.exists(new_name) and (new_name == current_name or new_name.lower() != current_name.lower()):
# The destination exists and does not differ from the current name only by case raise ValueError(f'Cannot rename {current_name} to {new_name} as {new_name} already exists')
new_path = self.name_to_abspath(new_name)
base = os.path.dirname(new_path)
if os.path.isfile(base):
raise ValueError(f'Cannot rename {current_name} to {new_name} as {base} is a file')
if not os.path.exists(base):
os.makedirs(base)
old_path = parent_dir = self.name_to_abspath(current_name)
self.commit_item(current_name)
os.rename(old_path, new_path)
# Remove empty directories while parent_dir:
parent_dir = os.path.dirname(parent_dir)
try:
os.rmdir(parent_dir)
except OSError:
break for x in ('mime_map', 'encoding_map'):
x = getattr(self, x)
if current_name in x:
x[new_name] = x[current_name]
self.name_path_map[new_name] = new_path for x in self.cache_names:
x = getattr(self, x)
try:
x.pop(current_name, None)
except TypeError:
x.discard(current_name)
if current_name == self.opf_name:
self.opf_name = new_name if os.path.dirname(old_path) != os.path.dirname(new_path):
from calibre.ebooks.oeb.polish.replace import LinkRebaser repl = LinkRebaser(self, current_name, new_name)
self.replace_links(new_name, repl)
self.dirty(new_name)

[dokumentace]
def replace_links(self, name, replace_func):
''' Replace all links in name using replace_func, which must be a callable that accepts a URL and returns the replaced URL. It must also have a 'replaced' attribute that is set to True if any actual replacement is done. Convenient ways of creating such callables are using the :class:`LinkReplacer` and :class:`LinkRebaser` classes. '''
media_type = self.mime_map.get(name, guess_type(name))
if name == self.opf_name:
replace_func.file_type = 'opf'
for elem in self.opf_xpath('//*[@href]'):
elem.set('href', replace_func(elem.get('href')))
elif media_type.lower() in OEB_DOCS:
replace_func.file_type = 'text'
rewrite_links(self.parsed(name), replace_func)
elif media_type.lower() in OEB_STYLES:
replace_func.file_type = 'style'
replaceUrls(self.parsed(name), replace_func)
elif media_type.lower() == guess_type('toc.ncx'):
replace_func.file_type = 'ncx'
for elem in self.parsed(name).xpath('//*[@src]'):
elem.set('src', replace_func(elem.get('src')))

if replace_func.replaced:
self.dirty(name)
return replace_func.replaced

[dokumentace]
def iterlinks(self, name, get_line_numbers=True):
''' Iterate over all links in name. If get_line_numbers is True the yields results of the form (link, line_number, offset). Where line_number is the line_number at which the link occurs and offset is the number of characters from the start of the line. Note that offset could actually encompass several lines if not zero. '''
media_type = self.mime_map.get(name, guess_type(name))
if name == self.opf_name:
for elem in self.opf_xpath('//*[@href]'):
yield (elem.get('href'), elem.sourceline, 0) if get_line_numbers else elem.get('href')
elif media_type.lower() in OEB_DOCS:
for el, attr, link, pos in iterlinks(self.parsed(name)):
yield (link, el.sourceline, pos) if get_line_numbers else link elif media_type.lower() in OEB_STYLES:
if get_line_numbers:
with self.open(name, 'rb') as f:
raw = self.decode(f.read()).replace('\r\n', '\n').replace('\r', '\n')
position = PositionFinder(raw)
is_in_comment = CommentFinder(raw)
for link, offset in itercsslinks(raw):
if not is_in_comment(offset):
lnum, col = position(offset)
yield link, lnum, col else:
for link in getUrls(self.parsed(name)):
yield link elif media_type.lower() == guess_type('toc.ncx'):
for elem in self.parsed(name).xpath('//*[@src]'):
yield (elem.get('src'), elem.sourceline, 0) if get_line_numbers else elem.get('src')

[dokumentace]
def abspath_to_name(self, fullpath, root=None):
'''
Convert an absolute path to a canonical name relative to :attr:`root`

:param root: The base folder. By default the root for this container object is used.
'''
# OS X silently changes all file names to NFD form. The EPUB
# spec requires all text including filenames to be in NFC form.
# The proper fix is to implement a VFS that maps between
# canonical names and their file system representation, however,
# I dont have the time for that now. Note that the container
# ensures that all text files are normalized to NFC when
# decoding them anyway, so there should be no mismatch between
# names in the text and NFC canonical file names.
return unicodedata.normalize('NFC', abspath_to_name(fullpath, root or self.root))

[dokumentace]
def name_to_abspath(self, name):
' Convert a canonical name to an absolute OS dependent path '
return name_to_abspath(name, self.root)

[dokumentace]
def exists(self, name):
''' True iff a file/folder corresponding to the canonical name exists. Note that this function suffers from the limitations of the underlying OS filesystem, in particular case (in)sensitivity. So on a case insensitive filesystem this will return True even if the case of name is different from the case of the underlying filesystem file. See also :meth:`has_name`'''
return os.path.exists(self.name_to_abspath(name))

[dokumentace]
def href_to_name(self, href, base=None):
'''
Convert an href (relative to base) to a name. base must be a name or
None, in which case self.root is used.
'''
key = href, base ans = self.href_to_name_cache.get(key, null)
if ans is null:
ans = self.href_to_name_cache[key] = href_to_name(href, self.root, base=base)
return ans

[dokumentace]
def name_to_href(self, name, base=None):
'''Convert a name to a href relative to base, which must be a name or
None in which case self.root is used as the base'''
return name_to_href(name, self.root, base=base)

[dokumentace]
def opf_xpath(self, expr):
' Convenience method to evaluate an XPath expression on the OPF file, has the opf: and dc: namespace prefixes pre-defined. '
return self.opf.xpath(expr, namespaces=OPF_NAMESPACES)

[dokumentace]
def has_name(self, name):
''' Return True iff a file with the same canonical name as that specified exists. Unlike :meth:`exists` this method is always case-sensitive. '''
return name and name in self.name_path_map
def has_name_and_is_not_empty(self, name):
if not self.has_name(name):
return False try:
return os.path.getsize(self.name_path_map[name]) > 0 except OSError:
return False def has_name_case_insensitive(self, name):
if not name:
return False name = name.lower()
for q in self.name_path_map:
if q.lower() == name:
return True return False

[dokumentace]
def relpath(self, path, base=None):
'''Convert an absolute path (with os separators) to a path relative to base (defaults to self.root). The relative path is *not* a name. Use
:meth:`abspath_to_name` for that.'''
return relpath(path, base or self.root)

def ok_to_be_unmanifested(self, name):
return name in self.names_that_need_not_be_manifested

@property def names_that_need_not_be_manifested(self):
' Set of names that are allowed to be missing from the manifest. Depends on the e-book file format. '
return {self.opf_name}

@property def names_that_must_not_be_removed(self):
' Set of names that must never be deleted from the container. Depends on the e-book file format. '
return {self.opf_name}

@property def names_that_must_not_be_changed(self):
' Set of names that must never be renamed. Depends on the e-book file format. '
return set()

def parse(self, path, mime):
with open(path, 'rb') as src:
data = src.read()
if mime in OEB_DOCS:
data = self.parse_xhtml(data, self.relpath(path))
elif mime[-4:] in {'+xml', '/xml'}:
data = self.parse_xml(data)
elif mime in OEB_STYLES:
data = self.parse_css(data, self.relpath(path))
return data

[dokumentace]
def raw_data(self, name, decode=True, normalize_to_nfc=True):
'''
Return the raw data corresponding to the file specified by name

:param decode: If True and the file has a text based MIME type, decode it and return a unicode object instead of raw bytes.
:param normalize_to_nfc: If True the returned unicode object is normalized to the NFC normal form as is required for the EPUB and AZW3 file formats.
'''
with self.open(name) as nf:
ans = nf.read()
mime = self.mime_map.get(name, guess_type(name))
if decode and (mime in OEB_STYLES or mime in OEB_DOCS or mime == 'text/plain' or mime[-4:] in {'+xml', '/xml'}):
ans = self.decode(ans, normalize_to_nfc=normalize_to_nfc)
return ans

[dokumentace]
def parsed(self, name):
''' Return a parsed representation of the file specified by name. For
HTML and XML files an lxml tree is returned. For CSS files a css_parser stylesheet is returned. Note that parsed objects are cached for performance. If you make any changes to the parsed object, you must call :meth:`dirty` so that the container knows to update the cache. See also :meth:`replace`.'''
ans = self.parsed_cache.get(name, None)
if ans is None:
self.used_encoding = None mime = self.mime_map.get(name, guess_type(name))
ans = self.parse(self.name_path_map[name], mime)
self.parsed_cache[name] = ans self.encoding_map[name] = self.used_encoding return ans

[dokumentace]
def replace(self, name, obj):
'''
Replace the parsed object corresponding to name with obj, which must be a similar object, i.e. an lxml tree for HTML/XML or a css_parser stylesheet for a CSS file.
'''
self.parsed_cache[name] = obj self.dirty(name)

@property def opf(self):
' The parsed OPF file '
return self.parsed(self.opf_name)

@property def mi(self):
''' The metadata of this book as a Metadata object. Note that this object is constructed on the fly every time this property is requested, so use it sparingly. '''
from calibre.ebooks.metadata.opf2 import OPF as O mi = self.serialize_item(self.opf_name)
return O(BytesIO(mi), basedir=self.opf_dir, unquote_urls=False, populate_spine=False).to_book_metadata()

@property def opf_version(self):
' The version set on the OPF\'s <package> element '
try:
return self.opf_xpath('//opf:package/@version')[0]
except IndexError:
return ''

@property def opf_version_parsed(self):
' The version set on the OPF\'s <package> element as a tuple of integers '
return parse_opf_version(self.opf_version)

@property def manifest_id_map(self):
' Mapping of manifest id to canonical names '
return {item.get('id'):self.href_to_name(item.get('href'), self.opf_name)
for item in self.opf_xpath('//opf:manifest/opf:item[@href and @id]')}

@property def manifest_type_map(self):
' Mapping of manifest media-type to list of canonical names of that media-type '
ans = defaultdict(list)
for item in self.opf_xpath('//opf:manifest/opf:item[@href and @media-type]'):
ans[item.get('media-type').lower()].append(self.href_to_name( item.get('href'), self.opf_name))
return {mt:tuple(v) for mt, v in iteritems(ans)}

[dokumentace]
def manifest_items_with_property(self, property_name):
' All manifest items that have the specified property '
prefixes = read_prefixes(self.opf)
for item in items_with_property(self.opf, property_name, prefixes):
href = item.get('href')
if href:
yield self.href_to_name(item.get('href'), self.opf_name)

[dokumentace]
def manifest_items_of_type(self, predicate):
''' The names of all manifest items whose media-type matches predicate.
`predicate` can be a set, a list, a string or a function taking a single argument, which will be called with the media-type. '''
if isinstance(predicate, str):
predicate = predicate.__eq__ elif hasattr(predicate, '__contains__'):
predicate = predicate.__contains__ for mt, names in iteritems(self.manifest_type_map):
if predicate(mt):
yield from names

[dokumentace]
def apply_unique_properties(self, name, *properties):
''' Ensure that the specified properties are set on only the manifest item identified by name. You can pass None as the name to remove the property from all items. '''
properties = frozenset(properties)
removed_names, added_names = [], []
for p in properties:
if p.startswith('calibre:'):
ensure_prefix(self.opf, None, 'calibre', CALIBRE_PREFIX)
break for item in self.opf_xpath('//opf:manifest/opf:item'):
iname = self.href_to_name(item.get('href'), self.opf_name)
props = (item.get('properties') or '').split()
lprops = {p.lower() for p in props}
for prop in properties:
if prop.lower() in lprops:
if name != iname:
removed_names.append(iname)
props = [p for p in props if p.lower() != prop]
if props:
item.set('properties', ' '.join(props))
else:
del item.attrib['properties']
else:
if name == iname:
added_names.append(iname)
props.append(prop)
item.set('properties', ' '.join(props))
self.dirty(self.opf_name)
return removed_names, added_names

[dokumentace]
def add_properties(self, name, *properties):
''' Add the specified properties to the manifest item identified by name. '''
properties = frozenset(properties)
if not properties:
return True for p in properties:
if p.startswith('calibre:'):
ensure_prefix(self.opf, None, 'calibre', CALIBRE_PREFIX)
break for item in self.opf_xpath('//opf:manifest/opf:item'):
iname = self.href_to_name(item.get('href'), self.opf_name)
if name == iname:
props = frozenset((item.get('properties') or '').split()) | properties item.set('properties', ' '.join(props))
return True return False

@property def guide_type_map(self):
' Mapping of guide type to canonical name '
return {item.get('type', ''):self.href_to_name(item.get('href'), self.opf_name)
for item in self.opf_xpath('//opf:guide/opf:reference[@href and @type]')}

@property def spine_iter(self):
''' An iterator that yields item, name is_linear for every item in the books' spine. item is the lxml element, name is the canonical file name and is_linear is True if the item is linear. See also: :attr:`spine_names` and :attr:`spine_items`. '''
manifest_id_map = self.manifest_id_map non_linear = []
for item in self.opf_xpath('//opf:spine/opf:itemref[@idref]'):
idref = item.get('idref')
name = manifest_id_map.get(idref, None)
path = self.name_path_map.get(name, None)
if path:
if item.get('linear', 'yes') == 'yes':
yield item, name, True else:
non_linear.append((item, name))
for item, name in non_linear:
yield item, name, False def index_in_spine(self, name):
manifest_id_map = self.manifest_id_map for i, item in enumerate(self.opf_xpath('//opf:spine/opf:itemref[@idref]')):
idref = item.get('idref')
q = manifest_id_map.get(idref, None)
if q == name:
return i

@property def spine_names(self):
''' An iterator yielding name and is_linear for every item in the books' spine. See also: :attr:`spine_iter` and :attr:`spine_items`. '''
for item, name, linear in self.spine_iter:
yield name, linear

@property def spine_items(self):
''' An iterator yielding the path for every item in the books' spine. See also: :attr:`spine_iter` and :attr:`spine_items`. '''
for name, linear in self.spine_names:
yield self.name_path_map[name]

[dokumentace]
def remove_from_spine(self, spine_items, remove_if_no_longer_in_spine=True):
'''
Remove the specified items (by canonical name) from the spine. If ``remove_if_no_longer_in_spine`` is True, the items are also deleted from the book, not just from the spine.
'''
nixed = set()
for (name, remove), (item, xname, linear) in zip(spine_items, self.spine_iter):
if remove and name == xname:
self.remove_from_xml(item)
nixed.add(name)
if remove_if_no_longer_in_spine:
# Remove from the book if no longer in spine nixed -= {name for name, linear in self.spine_names}
for name in nixed:
self.remove_item(name)

[dokumentace]
def set_spine(self, spine_items):
''' Set the spine to be spine_items where spine_items is an iterable of the form (name, linear). Will raise an error if one of the names is not present in the manifest. '''
imap = self.manifest_id_map imap = {name:item_id for item_id, name in iteritems(imap)}
items = [item for item, name, linear in self.spine_iter]
tail, last_tail = (items[0].tail, items[-1].tail) if items else ('\n ', '\n ')
for i in items:
self.remove_from_xml(i)
spine = self.opf_xpath('//opf:spine')[0]
spine.text = tail for name, linear in spine_items:
i = spine.makeelement('{%s}itemref' % OPF_NAMESPACES['opf'], nsmap={'opf':OPF_NAMESPACES['opf']})
i.tail = tail i.set('idref', imap[name])
spine.append(i)
if not linear:
i.set('linear', 'no')
if len(spine) > 0:
spine[-1].tail = last_tail self.dirty(self.opf_name)

[dokumentace]
def remove_item(self, name, remove_from_guide=True):
'''
Remove the item identified by name from this container. This removes all references to the item in the OPF manifest, guide and spine as well as from any internal caches.
'''
removed = set()
for elem in self.opf_xpath('//opf:manifest/opf:item[@href]'):
if self.href_to_name(elem.get('href'), self.opf_name) == name:
id_ = elem.get('id', None)
if id_ is not None:
removed.add(id_)
self.remove_from_xml(elem)
self.dirty(self.opf_name)
if removed:
for spine in self.opf_xpath('//opf:spine'):
tocref = spine.attrib.get('toc', None)
if tocref and tocref in removed:
spine.attrib.pop('toc', None)
self.dirty(self.opf_name)

for item in self.opf_xpath('//opf:spine/opf:itemref[@idref]'):
idref = item.get('idref')
if idref in removed:
self.remove_from_xml(item)
self.dirty(self.opf_name)

for meta in self.opf_xpath('//opf:meta[@name="cover" and @content]'):
if meta.get('content') in removed:
self.remove_from_xml(meta)
self.dirty(self.opf_name)

if remove_from_guide:
for item in self.opf_xpath('//opf:guide/opf:reference[@href]'):
if self.href_to_name(item.get('href'), self.opf_name) == name:
self.remove_from_xml(item)
self.dirty(self.opf_name)

path = self.name_path_map.pop(name, None)
if path and os.path.exists(path):
os.remove(path)
self.mime_map.pop(name, None)
self.parsed_cache.pop(name, None)
self.dirtied.discard(name)

[dokumentace]
def dirty(self, name):
''' Mark the parsed object corresponding to name as dirty. See also: :meth:`parsed`. '''
self.dirtied.add(name)

[dokumentace]
def remove_from_xml(self, item):
'Removes item from parent, fixing indentation (works only with self closing items)'
parent = item.getparent()
idx = parent.index(item)
if idx == 0:
# We are removing the first item - only care about adjusting
# the tail if this was the only child if len(parent) == 1:
parent.text = item.tail else:
# Make sure the preceding item has this tail parent[idx-1].tail = item.tail parent.remove(item)
return item

[dokumentace]
def insert_into_xml(self, parent, item, index=None):
'''Insert item into parent (or append if index is None), fixing indentation. Only works with self closing items.'''
if index is None:
parent.append(item)
else:
parent.insert(index, item)
idx = parent.index(item)
if idx == 0:
item.tail = parent.text
# If this is the only child of this parent element, we need a
# little extra work as we have gone from a self-closing <foo />
# element to <foo><item /></foo> if len(parent) == 1:
sibling = parent.getprevious()
if sibling is None:
# Give up!
return parent.text = sibling.text item.tail = sibling.tail else:
item.tail = parent[idx-1].tail if idx == len(parent)-1:
parent[idx-1].tail = parent.text

[dokumentace]
def opf_get_or_create(self, name):
''' Convenience method to either return the first XML element with the specified name or create it under the opf:package element and then return it, if it does not already exist. '''
ans = self.opf_xpath('//opf:'+name)
if ans:
return ans[0]
self.dirty(self.opf_name)
package = self.opf_xpath('//opf:package')[0]
item = package.makeelement(OPF(name))
item.tail = '\n'
package.append(item)
return item

[dokumentace]
def generate_item(self, name, id_prefix=None, media_type=None, unique_href=True):
'''Add an item to the manifest with href derived from the given name. Ensures uniqueness of href and id automatically. Returns generated item.'''
id_prefix = id_prefix or 'id'
media_type = media_type or self.guess_type(name)
if unique_href:
name = self.make_name_unique(name)
href = self.name_to_href(name, self.opf_name)
base, ext = href.rpartition('.')[0::2]
all_ids = {x.get('id') for x in self.opf_xpath('//*[@id]')}
c = 0 item_id = id_prefix while item_id in all_ids:
c += 1 item_id = id_prefix + '%d'%c manifest = self.opf_xpath('//opf:manifest')[0]
item = manifest.makeelement(OPF('item'), id=item_id, href=href)
item.set('media-type', media_type)
self.insert_into_xml(manifest, item)
self.dirty(self.opf_name)
name = self.href_to_name(href, self.opf_name)
self.name_path_map[name] = path = self.name_to_abspath(name)
self.mime_map[name] = media_type
# Ensure that the file corresponding to the newly created item exists
# otherwise cloned containers will fail when they try to get the number
# of links to the file base = os.path.dirname(path)
if not os.path.exists(base):
os.makedirs(base)
open(path, 'wb').close()
return itemdef format_opf(self):
try:
mdata = self.opf_xpath('//opf:metadata')[0]
except IndexError:
pass else:
mdata.text = '\n '
remove = set()
for child in mdata:
child.tail = '\n '
try:
if (child.get('name', '').startswith('calibre:'
) and child.get('content', '').strip() in {'{}', ''}):
remove.add(child)
except AttributeError:
continue # Happens for XML comments for child in remove:
mdata.remove(child)
if len(mdata) > 0:
mdata[-1].tail = '\n '
# Ensure name comes before content, needed for Nooks for meta in self.opf_xpath('//opf:meta[@name="cover"]'):
if 'content' in meta.attrib:
meta.set('content', meta.attrib.pop('content'))

[dokumentace]
def serialize_item(self, name):
''' Convert a parsed object (identified by canonical name) into a bytestring. See :meth:`parsed`. '''
data = root = self.parsed(name)
if name == self.opf_name:
self.format_opf()
data = serialize(data, self.mime_map[name], pretty_print=name in self.pretty_print)
if name == self.opf_name and root.nsmap.get(None) == OPF2_NS:
# Needed as I can't get lxml to output opf:role and
# not output <opf:metadata> as well data = re.sub(br'(<[/]{0,1})opf:', br'\1', data)
return data

[dokumentace]
def commit_item(self, name, keep_parsed=False):
''' Commit a parsed object to disk (it is serialized and written to the underlying file). If ``keep_parsed`` is True the parsed representation is retained in the cache. See also: :meth:`parsed` '''
if name not in self.parsed_cache:
return data = self.serialize_item(name)
self.dirtied.discard(name)
if not keep_parsed:
self.parsed_cache.pop(name)
dest = self.name_path_map[name]
if self.cloned and nlinks_file(dest) > 1:
# Decouple this file from its links os.unlink(dest)
with open(dest, 'wb') as f:
f.write(data)

[dokumentace]
def filesize(self, name):
''' Return the size in bytes of the file represented by the specified canonical name. Automatically handles dirtied parsed objects. See also:
:meth:`parsed` '''
if name in self.dirtied:
self.commit_item(name, keep_parsed=True)
path = self.name_to_abspath(name)
return os.path.getsize(path)

[dokumentace]
def get_file_path_for_processing(self, name, allow_modification=True):
''' Similar to open() except that it returns a file path, instead of an open file object. '''
if name in self.dirtied:
self.commit_item(name)
self.parsed_cache.pop(name, False)
path = self.name_to_abspath(name)
base = os.path.dirname(path)
if not os.path.exists(base):
os.makedirs(base)
else:
if self.cloned and allow_modification and os.path.exists(path) and nlinks_file(path) > 1:
# Decouple this file from its links temp = path + 'xxx'
shutil.copyfile(path, temp)
if iswindows:
retry_on_fail(os.unlink, path)
else:
os.unlink(path)
os.rename(temp, path)
return path

[dokumentace]
def open(self, name, mode='rb'):
''' Open the file pointed to by name for direct read/write. Note that this will commit the file if it is dirtied and remove it from the parse cache. You must finish with this file before accessing the parsed version of it again, or bad things will happen. '''
return open(self.get_file_path_for_processing(name, mode not in {'r', 'rb'}), mode)

[dokumentace]
def commit(self, outpath=None, keep_parsed=False):
'''
Commit all dirtied parsed objects to the filesystem and write out the e-book file at outpath.

:param output: The path to write the saved e-book file to. If None, the path of the original book file is used.
:param keep_parsed: If True the parsed representations of committed items are kept in the cache.
'''
for name in tuple(self.dirtied):
self.commit_item(name, keep_parsed=keep_parsed)

def compare_to(self, other):
if set(self.name_path_map) != set(other.name_path_map):
return 'Set of files is not the same'
mismatches = []
for name, path in iteritems(self.name_path_map):
opath = other.name_path_map[name]
with open(path, 'rb') as f1, open(opath, 'rb') as f2:
if f1.read() != f2.read():
mismatches.append('The file %s is not the same'%name)
return '\n'.join(mismatches)# }}}

# EPUB {{{ class InvalidEpub(InvalidBook):
pass class ObfuscationKeyMissing(InvalidEpub):
pass

OCF_NS = 'urn:oasis:names:tc:opendocument:xmlns:container'
VCS_IGNORE_FILES = frozenset('.gitignore .hgignore .agignore .bzrignore'.split())
VCS_DIRS = frozenset(('.git', '.hg', '.svn', '.bzr'))

def walk_dir(basedir):
for dirpath, dirnames, filenames in os.walk(basedir):
for vcsdir in VCS_DIRS:
try:
dirnames.remove(vcsdir)
except Exception:
pass is_root = os.path.abspath(os.path.normcase(dirpath)) == os.path.abspath(os.path.normcase(basedir))
yield is_root, dirpath, None for fname in filenames:
if fname not in VCS_IGNORE_FILES:
yield is_root, dirpath, fname class EpubContainer(Container):

book_type = 'epub'

@property def book_type_for_display(self):
ans = self.book_type.upper()
try:
v = self.opf_version_parsed except Exception:
pass else:
try:
if v.major == 2:
ans += ' 2'
else:
if not v.minor:
ans += f' {v.major}'
else:
ans += f' {v.major}.{v.minor}'
except Exception:
pass return ans

META_INF = {
'container.xml': True,
'manifest.xml': False,
'encryption.xml': False,
'metadata.xml': False,
'signatures.xml': False,
'rights.xml': False,
}

def __init__(self, pathtoepub, log, clone_data=None, tdir=None):
if clone_data is not None:
super().__init__(None, None, log, clone_data=clone_data)
for x in ('pathtoepub', 'obfuscated_fonts', 'is_dir'):
setattr(self, x, clone_data[x])
return self.pathtoepub = pathtoepub if tdir is None:
tdir = PersistentTemporaryDirectory('_epub_container')
tdir = os.path.abspath(os.path.realpath(tdir))
self.root = tdir self.is_dir = os.path.isdir(pathtoepub)
if self.is_dir:
for is_root, dirpath, fname in walk_dir(self.pathtoepub):
if is_root:
base = tdir else:
base = os.path.join(tdir, os.path.relpath(dirpath, self.pathtoepub))
if fname is None:
os.mkdir(base)
if fname is not None:
shutil.copy(os.path.join(dirpath, fname), os.path.join(base, fname))
else:
with open(self.pathtoepub, 'rb') as stream:
try:
zf = ZipFile(stream)
zf.extractall(tdir)
except:
log.exception('EPUB appears to be invalid ZIP file, trying a'
' more forgiving ZIP parser')
from calibre.utils.localunzip import extractall stream.seek(0)
extractall(stream, path=tdir)
try:
os.remove(join(tdir, 'mimetype'))
except OSError:
pass
# Ensure all filenames are in NFC normalized form
# has no effect on HFS+ filesystems as they always store filenames
# in NFD form for filename in walk(self.root):
n = unicodedata.normalize('NFC', filename)
if n != filename:
s = filename + 'suff1x'
os.rename(filename, s)
os.rename(s, n)

container_path = join(self.root, 'META-INF', 'container.xml')
if not exists(container_path):
raise InvalidEpub('No META-INF/container.xml in epub')
with open(container_path, 'rb') as cf:
container = safe_xml_fromstring(cf.read())
opf_files = container.xpath(( r'child::ocf:rootfiles/ocf:rootfile'
'[@media-type="%s" and @full-path]'%guess_type('a.opf')
), namespaces={'ocf':OCF_NS}
)
if not opf_files:
raise InvalidEpub('META-INF/container.xml contains no link to OPF file')
opf_path = os.path.join(self.root, *(urlunquote(opf_files[0].get('full-path')).split('/')))
if not exists(opf_path):
raise InvalidEpub('OPF file does not exist at location pointed to'
' by META-INF/container.xml')

super().__init__(tdir, opf_path, log)

self.obfuscated_fonts = {}
if 'META-INF/encryption.xml' in self.name_path_map:
self.process_encryption()
self.parsed_cache['META-INF/container.xml'] = container def clone_data(self, dest_dir):
ans = super().clone_data(dest_dir)
ans['pathtoepub'] = self.pathtoepub ans['obfuscated_fonts'] = self.obfuscated_fonts.copy()
ans['is_dir'] = self.is_dir return ans def rename(self, old_name, new_name):
is_opf = old_name == self.opf_name super().rename(old_name, new_name)
if is_opf:
for elem in self.parsed('META-INF/container.xml').xpath(( r'child::ocf:rootfiles/ocf:rootfile'
'[@media-type="%s" and @full-path]'%guess_type('a.opf')
), namespaces={'ocf':OCF_NS}
):
# The asinine epubcheck cannot handle quoted filenames in
# container.xml elem.set('full-path', self.opf_name)
self.dirty('META-INF/container.xml')
if old_name in self.obfuscated_fonts:
self.obfuscated_fonts[new_name] = self.obfuscated_fonts.pop(old_name)
enc = self.parsed('META-INF/encryption.xml')
for cr in enc.xpath('//*[local-name()="CipherReference" and @URI]'):
if self.href_to_name(cr.get('URI')) == old_name:
cr.set('URI', self.name_to_href(new_name))
self.dirty('META-INF/encryption.xml')

@property def names_that_need_not_be_manifested(self):
return super().names_that_need_not_be_manifested | {'META-INF/' + x for x in self.META_INF}

def ok_to_be_unmanifested(self, name):
return name in self.names_that_need_not_be_manifested or name.startswith('META-INF/')

@property def names_that_must_not_be_removed(self):
return super().names_that_must_not_be_removed | {'META-INF/container.xml'}

@property def names_that_must_not_be_changed(self):
return super().names_that_must_not_be_changed | {'META-INF/' + x for x in self.META_INF}

def remove_item(self, name, remove_from_guide=True):
# Handle removal of obfuscated fonts if name == 'META-INF/encryption.xml':
self.obfuscated_fonts.clear()
if name in self.obfuscated_fonts:
self.obfuscated_fonts.pop(name, None)
enc = self.parsed('META-INF/encryption.xml')
for em in enc.xpath('//*[local-name()="EncryptionMethod" and @Algorithm]'):
alg = em.get('Algorithm')
if alg not in {ADOBE_OBFUSCATION, IDPF_OBFUSCATION}:
continue try:
cr = em.getparent().xpath('descendant::*[local-name()="CipherReference" and @URI]')[0]
except (IndexError, ValueError, KeyError):
continue if name == self.href_to_name(cr.get('URI')):
self.remove_from_xml(em.getparent())
self.dirty('META-INF/encryption.xml')
super().remove_item(name, remove_from_guide=remove_from_guide)

def read_raw_unique_identifier(self):
package_id = raw_unique_identifier = idpf_key = None for attrib, val in iteritems(self.opf.attrib):
if attrib.endswith('unique-identifier'):
package_id = val break if package_id is not None:
for elem in self.opf_xpath('//*[@id=%s]'%escape_xpath_attr(package_id)):
if elem.text:
raw_unique_identifier = elem.text break if raw_unique_identifier is not None:
idpf_key = raw_unique_identifier idpf_key = re.sub('[\u0020\u0009\u000d\u000a]', '', idpf_key)
idpf_key = hashlib.sha1(idpf_key.encode('utf-8')).digest()
return package_id, raw_unique_identifier, idpf_key def iter_encryption_entries(self):
if 'META-INF/encryption.xml' in self.name_path_map:
enc = self.parsed('META-INF/encryption.xml')
for em in enc.xpath('//*[local-name()="EncryptionMethod" and @Algorithm]'):
try:
cr = em.getparent().xpath('descendant::*[local-name()="CipherReference" and @URI]')[0]
except Exception:
cr = None yield em, cr def process_encryption(self):
fonts = {}
for em, cr in self.iter_encryption_entries():
alg = em.get('Algorithm')
if alg not in {ADOBE_OBFUSCATION, IDPF_OBFUSCATION}:
raise DRMError()
if cr is None:
continue name = self.href_to_name(cr.get('URI'))
path = self.name_path_map.get(name, None)
if path is not None:
fonts[name] = alg package_id, raw_unique_identifier, idpf_key = self.read_raw_unique_identifier()
key = None for item in self.opf_xpath('//*[local-name()="metadata"]/*'
'[local-name()="identifier"]'):
scheme = None for xkey in item.attrib.keys():
if xkey.endswith('scheme'):
scheme = item.get(xkey)
if (scheme and scheme.lower() == 'uuid') or \ (item.text and item.text.startswith('urn:uuid:')):
try:
key = item.text.rpartition(':')[-1]
key = uuid.UUID(key).bytes except Exception:
self.log.exception('Failed to parse obfuscation key')
key = None for font, alg in iteritems(fonts):
tkey = key if alg == ADOBE_OBFUSCATION else idpf_key if not tkey:
raise ObfuscationKeyMissing('Failed to find obfuscation key')
raw = self.raw_data(font, decode=False)
raw = decrypt_font_data(tkey, raw, alg)
with self.open(font, 'wb') as f:
f.write(raw)
self.obfuscated_fonts[font] = (alg, tkey)

def update_modified_timestamp(self):
from calibre.ebooks.metadata.opf3 import set_last_modified_in_opf set_last_modified_in_opf(self.opf)
self.dirty(self.opf_name)

def commit(self, outpath=None, keep_parsed=False):
if self.opf_version_parsed.major == 3:
self.update_modified_timestamp()
super().commit(keep_parsed=keep_parsed)
container_path = join(self.root, 'META-INF', 'container.xml')
if not exists(container_path):
raise InvalidEpub('No META-INF/container.xml in EPUB, this typically happens if the temporary files calibre'
' is using are deleted by some other program while calibre is running')
restore_fonts = {}
for name in self.obfuscated_fonts:
if name not in self.name_path_map:
continue alg, key = self.obfuscated_fonts[name]
# Decrypting and encrypting are the same operation (XOR with key)
restore_fonts[name] = data = self.raw_data(name, decode=False)
with self.open(name, 'wb') as f:
f.write(decrypt_font_data(key, data, alg))
if outpath is None:
outpath = self.pathtoepub if self.is_dir:
# First remove items from the source dir that do not exist any more for is_root, dirpath, fname in walk_dir(self.pathtoepub):
if fname is not None:
if is_root and fname == 'mimetype':
continue base = self.root if is_root else os.path.join(self.root, os.path.relpath(dirpath, self.pathtoepub))
fpath = os.path.join(base, fname)
if not os.path.exists(fpath):
os.remove(os.path.join(dirpath, fname))
try:
os.rmdir(dirpath)
except OSError as err:
if err.errno != errno.ENOTEMPTY:
raise
# Now copy over everything from root to source dir for dirpath, dirnames, filenames in os.walk(self.root):
is_root = os.path.abspath(os.path.normcase(dirpath)) == os.path.abspath(os.path.normcase(self.root))
base = self.pathtoepub if is_root else os.path.join(self.pathtoepub, os.path.relpath(dirpath, self.root))
try:
os.mkdir(base)
except OSError as err:
if err.errno != errno.EEXIST:
raise for fname in filenames:
with open(os.path.join(dirpath, fname), 'rb') as src, open(os.path.join(base, fname), 'wb') as dest:
shutil.copyfileobj(src, dest)

else:
from calibre.ebooks.tweak import zip_rebuilder with open(join(self.root, 'mimetype'), 'wb') as f:
et = guess_type('a.epub')
if not isinstance(et, bytes):
et = et.encode('ascii')
f.write(et)
zip_rebuilder(self.root, outpath)
for name, data in iteritems(restore_fonts):
with self.open(name, 'wb') as f:
f.write(data)

@property def path_to_ebook(self):
return self.pathtoepub

@path_to_ebook.setter def path_to_ebook(self, val):
self.pathtoepub = val

# }}}

# AZW3 {{{ class InvalidMobi(InvalidBook):
pass def do_explode(path, dest):
from calibre.ebooks.mobi.reader.mobi6 import MobiReader from calibre.ebooks.mobi.reader.mobi8 import Mobi8Reader with open(path, 'rb') as stream:
mr = MobiReader(stream, default_log, None, None)

with CurrentDir(dest):
mr = Mobi8Reader(mr, default_log, for_tweak=True)
opf = os.path.abspath(mr())
obfuscated_fonts = mr.encrypted_fonts return opf, obfuscated_fonts def opf_to_azw3(opf, outpath, container):
from calibre.ebooks.conversion.plumber import Plumber, create_oebbook from calibre.ebooks.mobi.tweak import set_cover class Item(Manifest.Item):

def _parse_css(self, data):
# The default CSS parser used by oeb.base inserts the h namespace
# and resolves all @import rules. We dont want that.
return container.parse_css(data)

def specialize(oeb):
oeb.manifest.Item = Item plumber = Plumber(opf, outpath, container.log)
plumber.setup_options()
inp = plugin_for_input_format('azw3')
outp = plugin_for_output_format('azw3')
plumber.opts.mobi_passthrough = True plumber.opts.keep_ligatures = True oeb = create_oebbook(container.log, opf, plumber.opts, specialize=specialize)
set_cover(oeb)
outp.convert(oeb, outpath, inp, plumber.opts, container.log)

def epub_to_azw3(epub, outpath=None):
container = get_container(epub, tweak_mode=True)
changed = False for item in container.opf_xpath('//opf:manifest/opf:item[@properties and @href]'):
p = item.get('properties').split()
if 'cover-image' in p:
href = item.get('href')
guides = container.opf_xpath('//opf:guide')
if not guides:
guides = (container.opf.makeelement(OPF('guide')),)
container.opf.append(guides[0])
for guide in guides:
for child in guide:
if child.get('type') == 'cover':
break else:
guide.append(guide.makeelement(OPF('reference'), type='cover', href=href))
changed = True break elif 'calibre:title-page' in p:
item.getparent().remove(item)
if changed:
container.dirty(container.opf_name)
container.commit_item(container.opf_name)
outpath = outpath or (epub.rpartition('.')[0] + '.azw3')
opf_to_azw3(container.name_to_abspath(container.opf_name), outpath, container)

class AZW3Container(Container):

book_type = 'azw3'
SUPPORTS_TITLEPAGES = False
SUPPORTS_FILENAMES = False def __init__(self, pathtoazw3, log, clone_data=None, tdir=None):
if clone_data is not None:
super().__init__(None, None, log, clone_data=clone_data)
for x in ('pathtoazw3', 'obfuscated_fonts'):
setattr(self, x, clone_data[x])
return self.pathtoazw3 = pathtoazw3 if tdir is None:
tdir = PersistentTemporaryDirectory('_azw3_container')
tdir = os.path.abspath(os.path.realpath(tdir))
self.root = tdir with open(pathtoazw3, 'rb') as stream:
raw = stream.read(3)
if raw == b'TPZ':
raise InvalidMobi(_('This is not a MOBI file. It is a Topaz file.'))

try:
header = MetadataHeader(stream, default_log)
except MobiError:
raise InvalidMobi(_('This is not a MOBI file.'))

if header.encryption_type != 0:
raise DRMError()

kf8_type = header.kf8_type if kf8_type is None:
raise InvalidMobi(_('This MOBI file does not contain a KF8 format '
'book. KF8 is the new format from Amazon. calibre can '
'only edit MOBI files that contain KF8 books. Older '
'MOBI files without KF8 are not editable.'))

if kf8_type == 'joint':
raise InvalidMobi(_('This MOBI file contains both KF8 and '
'older Mobi6 data. calibre can only edit MOBI files '
'that contain only KF8 data.'))

try:
opf_path, obfuscated_fonts = fork_job(
'calibre.ebooks.oeb.polish.container', 'do_explode', args=(pathtoazw3, tdir), no_output=True)['result']
except WorkerError as e:
log(e.orig_tb)
raise InvalidMobi('Failed to explode MOBI')
super().__init__(tdir, opf_path, log)
self.obfuscated_fonts = {x.replace(os.sep, '/') for x in obfuscated_fonts}

def clone_data(self, dest_dir):
ans = super().clone_data(dest_dir)
ans['pathtoazw3'] = self.pathtoazw3 ans['obfuscated_fonts'] = self.obfuscated_fonts.copy()
return ans def commit(self, outpath=None, keep_parsed=False):
super().commit(keep_parsed=keep_parsed)
if outpath is None:
outpath = self.pathtoazw3 opf_to_azw3(self.name_path_map[self.opf_name], outpath, self)

@property def path_to_ebook(self):
return self.pathtoazw3

@path_to_ebook.setter def path_to_ebook(self, val):
self.pathtoazw3 = val

@property def names_that_must_not_be_changed(self):
return set(self.name_path_map)
# }}}

def get_container(path, log=None, tdir=None, tweak_mode=False):
if log is None:
log = default_log try:
isdir = os.path.isdir(path)
except Exception:
isdir = False own_tdir = not tdir ebook_cls = (AZW3Container if path.rpartition('.')[-1].lower() in {'azw3', 'mobi', 'original_azw3', 'original_mobi'} and not isdir else EpubContainer)
if own_tdir:
tdir = PersistentTemporaryDirectory(f'_{ebook_cls.book_type}_container')
try:
ebook = ebook_cls(path, log, tdir=tdir)
ebook.tweak_mode = tweak_mode except BaseException:
if own_tdir:
shutil.rmtree(tdir, ignore_errors=True)
raise return ebook def test_roundtrip():
ebook = get_container(sys.argv[-1])
p = PersistentTemporaryFile(suffix='.'+sys.argv[-1].rpartition('.')[-1])
p.close()
ebook.commit(outpath=p.name)
ebook2 = get_container(p.name)
ebook3 = get_container(p.name)
diff = ebook3.compare_to(ebook2)
if diff is not None:
print(diff)

if __name__ == '__main__':
test_roundtrip()

© Copyright Kovid Goyal.
Naposledy aktualizováno kvě 03, 2024.

Navigace

- moduly
- Spustit »
- Kód modulu »
- calibre.ebooks.oeb.polish.cover

Zdrojový kód pro calibre.ebooks.oeb.polish.cover

__license__ = 'GPL v3'
__copyright__ = '2013, Kovid Goyal <kovid at kovidgoyal.net>'
__docformat__ = 'restructuredtext en'

import os import re import shutil from calibre.ebooks.oeb.base import OEB_DOCS, OPF, XLINK, XPath, xml2text from calibre.ebooks.oeb.polish.replace import get_recommended_folders, replace_links from calibre.utils.imghdr import identify from polyglot.builtins import iteritems def set_azw3_cover(container, cover_path, report, options=None):
existing_image = options is not None and options.get('existing_image', False)
name = None found = True for gi in container.opf_xpath('//opf:guide/opf:reference[@href and contains(@type, "cover")]'):
href = gi.get('href')
name = container.href_to_name(href, container.opf_name)
container.remove_from_xml(gi)
if existing_image:
name = cover_path found = False else:
if name is None or not container.has_name(name):
item = container.generate_item(name='cover.jpeg', id_prefix='cover')
name = container.href_to_name(item.get('href'), container.opf_name)
found = False href = container.name_to_href(name, container.opf_name)
guide = container.opf_xpath('//opf:guide')[0]
container.insert_into_xml(guide, guide.makeelement(
OPF('reference'), href=href, type='cover'))
if not existing_image:
with open(cover_path, 'rb') as src, container.open(name, 'wb') as dest:
shutil.copyfileobj(src, dest)
container.dirty(container.opf_name)
report(_('Cover updated') if found else _('Cover inserted'))

def get_azw3_raster_cover_name(container):
items = container.opf_xpath('//opf:guide/opf:reference[@href and contains(@type, "cover")]')
if items:
return container.href_to_name(items[0].get('href'))

def mark_as_cover_azw3(container, name):
href = container.name_to_href(name, container.opf_name)
found = False for item in container.opf_xpath('//opf:guide/opf:reference[@href and contains(@type, "cover")]'):
item.set('href', href)
found = True if not found:
for guide in container.opf_xpath('//opf:guide'):
container.insert_into_xml(guide, guide.makeelement(
OPF('reference'), href=href, type='cover'))
container.dirty(container.opf_name)

def get_raster_cover_name(container):
if container.book_type == 'azw3':
return get_azw3_raster_cover_name(container)
return find_cover_image(container, strict=True)

def get_cover_page_name(container):
if container.book_type == 'azw3':
return return find_cover_page(container)

[dokumentace]
def set_cover(container, cover_path, report=None, options=None):
'''
Set the cover of the book to the image pointed to by cover_path.

:param cover_path: Either the absolute path to an image file or the canonical name of an image in the book. When using an image in the book, you must also set options, see below.
:param report: An optional callable that takes a single argument. It will be called with information about the tasks being processed.
:param options: None or a dictionary that controls how the cover is set. The dictionary can have entries:
**keep_aspect**: True or False (Preserve aspect ratio of covers in EPUB)
**no_svg**: True or False (Use an SVG cover wrapper in the EPUB titlepage)
**existing**: True or False (``cover_path`` refers to an existing image in the book)
'''
report = report or (lambda x:x)
if container.book_type == 'azw3':
set_azw3_cover(container, cover_path, report, options=options)
else:
set_epub_cover(container, cover_path, report, options=options)

[dokumentace]
def mark_as_cover(container, name):
'''
Mark the specified image as the cover image.
'''
if name not in container.mime_map:
raise ValueError('Cannot mark %s as cover as it does not exist' % name)
mt = container.mime_map[name]
if not is_raster_image(mt):
raise ValueError('Cannot mark %s as the cover image as it is not a raster image' % name)
if container.book_type == 'azw3':
mark_as_cover_azw3(container, name)
else:
mark_as_cover_epub(container, name)

###############################################################################
# The delightful EPUB cover processing def is_raster_image(media_type):
return media_type and media_type.lower() in {
'image/png', 'image/jpeg', 'image/jpg', 'image/gif'}

COVER_TYPES = {
'coverimagestandard', 'other.ms-coverimage-standard',
'other.ms-titleimage-standard', 'other.ms-titleimage',
'other.ms-coverimage', 'other.ms-thumbimage-standard',
'other.ms-thumbimage', 'thumbimagestandard', 'cover'}

def find_cover_image2(container, strict=False):
manifest_id_map = container.manifest_id_map mm = container.mime_map for meta in container.opf_xpath('//opf:meta[@name="cover" and @content]'):
item_id = meta.get('content')
name = manifest_id_map.get(item_id, None)
media_type = mm.get(name, None)
if is_raster_image(media_type):
return name

# First look for a guide item with type == 'cover'
guide_type_map = container.guide_type_map for ref_type, name in iteritems(guide_type_map):
if ref_type.lower() == 'cover' and is_raster_image(mm.get(name, None)):
return name if strict:
return

# Find the largest image from all possible guide cover items largest_cover = (None, 0)
for ref_type, name in iteritems(guide_type_map):
if ref_type.lower() in COVER_TYPES and is_raster_image(mm.get(name, None)):
path = container.name_path_map.get(name, None)
if path:
sz = os.path.getsize(path)
if sz > largest_cover[1]:
largest_cover = (name, sz)

if largest_cover[0]:
return largest_cover[0]

def find_cover_image3(container):
for name in container.manifest_items_with_property('cover-image'):
return name manifest_id_map = container.manifest_id_map mm = container.mime_map for meta in container.opf_xpath('//opf:meta[@name="cover" and @content]'):
item_id = meta.get('content')
name = manifest_id_map.get(item_id, None)
media_type = mm.get(name, None)
if is_raster_image(media_type):
return name def find_cover_image(container, strict=False):
'Find a raster image marked as a cover in the OPF'
ver = container.opf_version_parsed if ver.major < 3:
return find_cover_image2(container, strict=strict)
else:
return find_cover_image3(container)

def get_guides(container):
guides = container.opf_xpath('//opf:guide')
if not guides:
container.insert_into_xml(container.opf, container.opf.makeelement(
OPF('guide')))
guides = container.opf_xpath('//opf:guide')
return guides def mark_as_cover_epub(container, name):
mmap = {v:k for k, v in iteritems(container.manifest_id_map)}
if name not in mmap:
raise ValueError('Cannot mark %s as cover as it is not in manifest' % name)
mid = mmap[name]
ver = container.opf_version_parsed

# Remove all entries from the opf that identify a raster image as cover for meta in container.opf_xpath('//opf:meta[@name="cover" and @content]'):
container.remove_from_xml(meta)
for ref in container.opf_xpath('//opf:guide/opf:reference[@href and @type]'):
if ref.get('type').lower() not in COVER_TYPES:
continue rname = container.href_to_name(ref.get('href'), container.opf_name)
mt = container.mime_map.get(rname, None)
if is_raster_image(mt):
container.remove_from_xml(ref)

if ver.major < 3:
# Add reference to image in <metadata> for metadata in container.opf_xpath('//opf:metadata'):
m = metadata.makeelement(OPF('meta'), name='cover', content=mid)
container.insert_into_xml(metadata, m)

# If no entry for cover exists in guide, insert one that points to this
# image if not container.opf_xpath('//opf:guide/opf:reference[@type="cover"]'):
for guide in get_guides(container):
container.insert_into_xml(guide, guide.makeelement(
OPF('reference'), type='cover', href=container.name_to_href(name, container.opf_name)))
else:
container.apply_unique_properties(name, 'cover-image')

container.dirty(container.opf_name)

[dokumentace]
def mark_as_titlepage(container, name, move_to_start=True):
'''
Mark the specified HTML file as the titlepage of the EPUB.

:param move_to_start: If True the HTML file is moved to the start of the spine
'''
ver = container.opf_version_parsed if move_to_start:
for item, q, linear in container.spine_iter:
if name == q:
break if not linear:
item.set('linear', 'yes')
if item.getparent().index(item) > 0:
container.insert_into_xml(item.getparent(), item, 0)
if ver.major < 3:
for ref in container.opf_xpath('//opf:guide/opf:reference[@type="cover"]'):
ref.getparent().remove(ref)

for guide in get_guides(container):
container.insert_into_xml(guide, guide.makeelement(
OPF('reference'), type='cover', href=container.name_to_href(name, container.opf_name)))
else:
container.apply_unique_properties(name, 'calibre:title-page')

container.dirty(container.opf_name)

def find_cover_page(container):
'Find a document marked as a cover in the OPF'
ver = container.opf_version_parsed mm = container.mime_map if ver.major < 3:
guide_type_map = container.guide_type_map for ref_type, name in iteritems(guide_type_map):
if ref_type.lower() == 'cover' and mm.get(name, '').lower() in OEB_DOCS:
return name else:
for name in container.manifest_items_with_property('calibre:title-page'):
return name from calibre.ebooks.oeb.polish.toc import get_landmarks for landmark in get_landmarks(container):
if landmark['type'] == 'cover' and mm.get(landmark['dest'], '').lower() in OEB_DOCS:
return landmark['dest']

def fix_conversion_titlepage_links_in_nav(container):
from calibre.ebooks.oeb.polish.toc import find_existing_nav_toc cover_page_name = find_cover_page(container)
if not cover_page_name:
return nav_page_name = find_existing_nav_toc(container)
if not nav_page_name:
return for elem in container.parsed(nav_page_name).xpath('//*[@data-calibre-removed-titlepage]'):
elem.attrib.pop('data-calibre-removed-titlepage')
elem.set('href', container.name_to_href(cover_page_name, nav_page_name))
container.dirty(nav_page_name)

def find_cover_image_in_page(container, cover_page):
root = container.parsed(cover_page)
body = XPath('//h:body')(root)
if len(body) != 1:
return body = body[0]
images = []
for img in XPath('descendant::h:img[@src]|descendant::svg:svg/descendant::svg:image')(body):
href = img.get('src') or img.get(XLINK('href'))
if href:
name = container.href_to_name(href, base=cover_page)
images.append(name)
text = re.sub(r'\s+', '', xml2text(body))
if text or len(images) > 1:
# Document has more content than a single image return if images:
return images[0]

def clean_opf(container):
'Remove all references to covers from the OPF'
manifest_id_map = container.manifest_id_map for meta in container.opf_xpath('//opf:meta[@name="cover" and @content]'):
name = manifest_id_map.get(meta.get('content', None), None)
container.remove_from_xml(meta)
if name and name in container.name_path_map:
yield name gtm = container.guide_type_map for ref in container.opf_xpath('//opf:guide/opf:reference[@type]'):
typ = ref.get('type', '')
if typ.lower() in COVER_TYPES:
container.remove_from_xml(ref)
name = gtm.get(typ, None)
if name and name in container.name_path_map:
yield name ver = container.opf_version_parsed if ver.major > 2:
removed_names = container.apply_unique_properties(None, 'cover-image', 'calibre:title-page')[0]
for name in removed_names:
yield name container.dirty(container.opf_name)

def create_epub_cover(container, cover_path, existing_image, options=None):
from calibre.ebooks.conversion.config import load_defaults from calibre.ebooks.oeb.transforms.cover import CoverManager try:
ext = cover_path.rpartition('.')[-1].lower()
except Exception:
ext = 'jpeg'
cname, tname = 'cover.' + ext, 'titlepage.xhtml'
recommended_folders = get_recommended_folders(container, (cname, tname))

if existing_image:
raster_cover = existing_image manifest_id = {v:k for k, v in iteritems(container.manifest_id_map)}[existing_image]
raster_cover_item = container.opf_xpath('//opf:manifest/*[@id="%s"]' % manifest_id)[0]
else:
folder = recommended_folders[cname]
if folder:
cname = folder + '/' + cname raster_cover_item = container.generate_item(cname, id_prefix='cover')
raster_cover = container.href_to_name(raster_cover_item.get('href'), container.opf_name)

with container.open(raster_cover, 'wb') as dest:
if callable(cover_path):
cover_path('write_image', dest)
else:
with open(cover_path, 'rb') as src:
shutil.copyfileobj(src, dest)
if options is None:
opts = load_defaults('epub_output')
keep_aspect = opts.get('preserve_cover_aspect_ratio', False)
no_svg = opts.get('no_svg_cover', False)
else:
keep_aspect = options.get('keep_aspect', False)
no_svg = options.get('no_svg', False)
if no_svg:
style = 'style="height: 100%%"'
templ = CoverManager.NONSVG_TEMPLATE.replace('__style__', style)
has_svg = False else:
if callable(cover_path):
templ = (options or {}).get('template', CoverManager.SVG_TEMPLATE)
has_svg = 'xlink:href' in templ else:
width, height = 600, 800 has_svg = True try:
if existing_image:
width, height = identify(container.raw_data(existing_image, decode=False))[1:]
else:
with open(cover_path, 'rb') as csrc:
width, height = identify(csrc)[1:]
except:
container.log.exception("Failed to get width and height of cover")
ar = 'xMidYMid meet' if keep_aspect else 'none'
templ = CoverManager.SVG_TEMPLATE.replace('__ar__', ar)
templ = templ.replace('__viewbox__', '0 0 %d %d'%(width, height))
templ = templ.replace('__width__', str(width))
templ = templ.replace('__height__', str(height))
folder = recommended_folders[tname]
if folder:
tname = folder + '/' + tname titlepage_item = container.generate_item(tname, id_prefix='titlepage')
titlepage = container.href_to_name(titlepage_item.get('href'), container.opf_name)
raw = templ % container.name_to_href(raster_cover, titlepage)
with container.open(titlepage, 'wb') as f:
if not isinstance(raw, bytes):
raw = raw.encode('utf-8')
f.write(raw)

# We have to make sure the raster cover item has id="cover" for the moron
# that wrote the Nook firmware if raster_cover_item.get('id') != 'cover':
from calibre.ebooks.oeb.base import uuid_id newid = uuid_id()
for item in container.opf_xpath('//*[@id="cover"]'):
item.set('id', newid)
for item in container.opf_xpath('//*[@idref="cover"]'):
item.set('idref', newid)
raster_cover_item.set('id', 'cover')

spine = container.opf_xpath('//opf:spine')[0]
ref = spine.makeelement(OPF('itemref'), idref=titlepage_item.get('id'))
container.insert_into_xml(spine, ref, index=0)
ver = container.opf_version_parsed if ver.major < 3:
guide = container.opf_get_or_create('guide')
container.insert_into_xml(guide, guide.makeelement(
OPF('reference'), type='cover', title=_('Cover'), href=container.name_to_href(titlepage, base=container.opf_name)))
metadata = container.opf_get_or_create('metadata')
meta = metadata.makeelement(OPF('meta'), name='cover')
meta.set('content', raster_cover_item.get('id'))
container.insert_into_xml(metadata, meta)
else:
container.apply_unique_properties(raster_cover, 'cover-image')
container.apply_unique_properties(titlepage, 'calibre:title-page')
if has_svg:
container.add_properties(titlepage, 'svg')

return raster_cover, titlepage def remove_cover_image_in_page(container, page, cover_images):
for img in container.parsed(page).xpath('//*[local-name()="img" and @src]'):
href = img.get('src')
name = container.href_to_name(href, page)
if name in cover_images:
img.getparent().remove(img)
break def has_epub_cover(container):
if find_cover_image(container):
return True if find_cover_page(container):
return True spine_items = tuple(container.spine_items)
if spine_items:
candidate = container.abspath_to_name(spine_items[0])
if find_cover_image_in_page(container, candidate) is not None:
return True return False def set_epub_cover(container, cover_path, report, options=None, image_callback=None):
existing_image = options is not None and options.get('existing_image', False)
if existing_image:
existing_image = cover_path cover_image = find_cover_image(container)
cover_page = find_cover_page(container)
wrapped_image = extra_cover_page = None updated = False log = container.log possible_removals = set(clean_opf(container))
possible_removals
# TODO: Handle possible_removals and also iterate over links in the removed
# pages and handle possibly removing stylesheets referred to by them.

image_callback_called = False spine_items = tuple(container.spine_items)
if cover_page is None and spine_items:
# Check if the first item in the spine is a simple cover wrapper candidate = container.abspath_to_name(spine_items[0])
if find_cover_image_in_page(container, candidate) is not None:
cover_page = candidate if cover_page is not None:
log('Found existing cover page')
wrapped_image = find_cover_image_in_page(container, cover_page)

if len(spine_items) > 1:
# Look for an extra cover page c = container.abspath_to_name(spine_items[1])
if c != cover_page:
candidate = find_cover_image_in_page(container, c)
if candidate and candidate in {wrapped_image, cover_image}:
log('Found an extra cover page that is a simple wrapper, removing it')
# This page has only a single image and that image is the
# cover image, remove it.
container.remove_item(c)
extra_cover_page = c spine_items = spine_items[:1] + spine_items[2:]
elif candidate is None:
# Remove the cover image if it is the first image in this
# page remove_cover_image_in_page(container, c, {wrapped_image, cover_image})

if wrapped_image is not None:
# The cover page is a simple wrapper around a single cover image,
# we can remove it safely.
log(f'Existing cover page {cover_page} is a simple wrapper, removing it')
container.remove_item(cover_page)
if wrapped_image != existing_image:
if image_callback is not None and not image_callback_called:
image_callback(cover_image, wrapped_image)
image_callback_called = True container.remove_item(wrapped_image)
updated = True if image_callback is not None and not image_callback_called:
image_callback_called = True image_callback(cover_image, wrapped_image)
if cover_image and cover_image != wrapped_image:
# Remove the old cover image if cover_image != existing_image:
container.remove_item(cover_image)

# Insert the new cover raster_cover, titlepage = create_epub_cover(container, cover_path, existing_image, options=options)

report(_('Cover updated') if updated else _('Cover inserted'))

# Replace links to the old cover image/cover page link_sub = {s:d for s, d in iteritems({ cover_page:titlepage, wrapped_image:raster_cover, cover_image:raster_cover, extra_cover_page:titlepage})
if s is not None and s != d}
if link_sub:
replace_links(container, link_sub, frag_map=lambda x, y:None)
return raster_cover, titlepage

© Copyright Kovid Goyal.
Naposledy aktualizováno kvě 03, 2024.

Navigace

- moduly
- Spustit »
- Kód modulu »
- calibre.ebooks.oeb.polish.css

Zdrojový kód pro calibre.ebooks.oeb.polish.css

#!/usr/bin/env python

__license__ = 'GPL v3'
__copyright__ = '2014, Kovid Goyal <kovid at kovidgoyal.net>'

import re from collections import defaultdict from functools import partial from operator import itemgetter from css_parser.css import CSSRule, CSSStyleDeclaration from css_selectors import Select, SelectorError, SelectorSyntaxError, parse from calibre import force_unicode from calibre.ebooks.oeb.base import OEB_DOCS, OEB_STYLES, XHTML, css_text from calibre.ebooks.oeb.normalize_css import normalize_filter_css, normalizers from calibre.ebooks.oeb.polish.pretty import pretty_script_or_style, pretty_xml_tree, serialize from calibre.utils.icu import lower as icu_lower from calibre.utils.icu import numeric_sort_key from calibre.utils.localization import ngettext from polyglot.builtins import iteritems, itervalues from polyglot.functools import lru_cache class SelectorStatus:
any_used: bool = False any_unused: bool = False def mark_used_selectors(rules, log, select):
ans = SelectorStatus()
for rule in rules:
for selector in rule.selectorList:
if getattr(selector, 'calibre_used', False):
ans.any_used = True continue try:
if select.has_matches(selector.selectorText):
selector.calibre_used = True ans.any_used = True else:
ans.any_unused = True selector.calibre_used = False except SelectorError:
# Cannot parse/execute this selector, be safe and assume it
# matches something selector.calibre_used = True ans.any_used = True return ans def get_imported_sheets(name, container, sheets, recursion_level=10, sheet=None):
ans = set()
sheet = sheet or sheets[name]
for rule in sheet.cssRules.rulesOfType(CSSRule.IMPORT_RULE):
if rule.href:
iname = container.href_to_name(rule.href, name)
if iname in sheets:
ans.add(iname)
if recursion_level > 0:
for imported_sheet in tuple(ans):
ans |= get_imported_sheets(imported_sheet, container, sheets, recursion_level=recursion_level-1)
ans.discard(name)
return ans def merge_declarations(first, second):
for prop in second.getProperties():
first.setProperty(prop)

def merge_identical_selectors(sheet):
' Merge rules that have identical selectors '
selector_map = defaultdict(list)
for rule in sheet.cssRules.rulesOfType(CSSRule.STYLE_RULE):
selector_map[rule.selectorText].append(rule)
remove = []
for rule_group in itervalues(selector_map):
if len(rule_group) > 1:
for i in range(1, len(rule_group)):
merge_declarations(rule_group[0].style, rule_group[i].style)
remove.append(rule_group[i])
for rule in remove:
sheet.cssRules.remove(rule)
return len(remove)

def merge_identical_properties(sheet):
' Merge rules having identical properties '
properties_map = defaultdict(list)

def declaration_key(declaration):
return tuple(sorted(
((prop.name, prop.propertyValue.value) for prop in declaration.getProperties()), key=itemgetter(0)
))

for idx, rule in enumerate(sheet.cssRules):
if rule.type == CSSRule.STYLE_RULE:
properties_map[declaration_key(rule.style)].append((idx, rule))

removals = []
num_merged = 0 for rule_group in properties_map.values():
if len(rule_group) < 2:
continue num_merged += len(rule_group)
selectors = rule_group[0][1].selectorList seen = {s.selectorText for s in selectors}
rules = iter(rule_group)
next(rules)
for idx, rule in rules:
removals.append(idx)
for s in rule.selectorList:
q = s.selectorText if q not in seen:
seen.add(q)
selectors.append(s)
for idx in sorted(removals, reverse=True):
sheet.cssRules.pop(idx)
return num_merged def remove_unused_selectors_and_rules(rules_container, rules, removal_stats):
ans = SelectorStatus()
for r in rules:
removals = []
for i, sel in enumerate(r.selectorList):
if getattr(sel, 'calibre_used', True):
ans.any_used = True else:
removals.append(i)
if removals:
ans.any_unused = True if len(removals) == len(r.selectorList):
rules_container.remove(r)
removal_stats['rules'] += 1 else:
removal_stats['selectors'] += len(removals)
for i in reversed(removals):
del r.selectorList[i]
return ans

[dokumentace]
def remove_unused_css( container, report=None, remove_unused_classes=False, merge_rules=False, merge_rules_with_identical_properties=False, remove_unreferenced_sheets=False,
):
'''
Remove all unused CSS rules from the book. An unused CSS rule is one that does not match any actual content.

:param report: An optional callable that takes a single argument. It is called with information about the operations being performed.
:param remove_unused_classes: If True, class attributes in the HTML that do not match any CSS rules are also removed.
:param merge_rules: If True, rules with identical selectors are merged.
:param merge_rules_with_identical_properties: If True, rules with identical properties are merged.
:param remove_unreferenced_sheets: If True, stylesheets that are not referenced by any content are removed
'''
report = report or (lambda x:x)

def safe_parse(name):
try:
return container.parsed(name)
except TypeError:
pass sheets = {name:safe_parse(name) for name, mt in iteritems(container.mime_map) if mt in OEB_STYLES}
sheets = {k:v for k, v in iteritems(sheets) if v is not None}
num_merged = num_rules_merged = 0 if merge_rules:
for name, sheet in iteritems(sheets):
num = merge_identical_selectors(sheet)
if num:
container.dirty(name)
num_merged += num if merge_rules_with_identical_properties:
for name, sheet in iteritems(sheets):
num = merge_identical_properties(sheet)
if num:
container.dirty(name)
num_rules_merged += num import_map = {name:get_imported_sheets(name, container, sheets) for name in sheets}
unreferenced_sheets = set(sheets)
if remove_unused_classes:
class_map = {name:{icu_lower(x) for x in classes_in_rule_list(sheet.cssRules)} for name, sheet in iteritems(sheets)}
style_rules = {name:tuple(sheet.cssRules.rulesOfType(CSSRule.STYLE_RULE)) for name, sheet in iteritems(sheets)}

removal_stats = {'rules': 0, 'selectors': 0}
num_of_removed_classes = 0 for name, mt in iteritems(container.mime_map):
if mt not in OEB_DOCS:
continue root = container.parsed(name)
select = Select(root, ignore_inappropriate_pseudo_classes=True)
used_classes = set()
for style in root.xpath('//*[local-name()="style"]'):
if style.get('type', 'text/css') == 'text/css' and style.text:
sheet = container.parse_css(style.text)
if merge_rules:
num = merge_identical_selectors(sheet)
if num:
num_merged += num container.dirty(name)
if merge_rules_with_identical_properties:
num = merge_identical_properties(sheet)
if num:
num_rules_merged += num container.dirty(name)
if remove_unused_classes:
used_classes |= {icu_lower(x) for x in classes_in_rule_list(sheet.cssRules)}
imports = get_imported_sheets(name, container, sheets, sheet=sheet)
for imported_sheet in imports:
unreferenced_sheets.discard(imported_sheet)
mark_used_selectors(style_rules[imported_sheet], container.log, select)
if remove_unused_classes:
used_classes |= class_map[imported_sheet]
rules = tuple(sheet.cssRules.rulesOfType(CSSRule.STYLE_RULE))
if mark_used_selectors(rules, container.log, select).any_unused:
remove_unused_selectors_and_rules(sheet.cssRules, rules, removal_stats)
style.text = force_unicode(sheet.cssText, 'utf-8')
pretty_script_or_style(container, style)
container.dirty(name)

for link in root.xpath('//*[local-name()="link" and @href]'):
sname = container.href_to_name(link.get('href'), name)
if sname not in sheets:
continue mark_used_selectors(style_rules[sname], container.log, select)
if remove_unused_classes:
used_classes |= class_map[sname]
unreferenced_sheets.discard(sname)

for iname in import_map[sname]:
unreferenced_sheets.discard(iname)
mark_used_selectors(style_rules[iname], container.log, select)
if remove_unused_classes:
used_classes |= class_map[iname]

if remove_unused_classes:
for elem in root.xpath('//*[@class]'):
original_classes, classes = elem.get('class', '').split(), []
for x in original_classes:
if icu_lower(x) in used_classes:
classes.append(x)
if len(classes) != len(original_classes):
if classes:
elem.set('class', ' '.join(classes))
else:
del elem.attrib['class']
num_of_removed_classes += len(original_classes) - len(classes)
container.dirty(name)

for name, sheet in iteritems(sheets):
if name in unreferenced_sheets:
continue q = remove_unused_selectors_and_rules(sheet.cssRules, style_rules[name], removal_stats)
if q.any_unused:
container.dirty(name)
num_sheets_removed = 0 if remove_unreferenced_sheets and len(unreferenced_sheets):
num_sheets_removed += len(unreferenced_sheets)
for uname in unreferenced_sheets:
container.remove_item(uname)

num_changes = num_merged + num_of_removed_classes + num_rules_merged + removal_stats['rules'] + removal_stats['selectors'] + num_sheets_removed if num_changes > 0:
if removal_stats['rules']:
report(ngettext('Removed one unused CSS style rule', 'Removed {} unused CSS style rules', removal_stats['rules']).format(removal_stats['rules']))
if removal_stats['selectors']:
report(ngettext('Removed one unused CSS selector', 'Removed {} unused CSS selectors', removal_stats['selectors']).format(removal_stats['selectors']))
if num_of_removed_classes > 0:
report(ngettext('Removed one unused class from the HTML', 'Removed {} unused classes from the HTML', num_of_removed_classes).format(num_of_removed_classes))
if num_merged > 0:
report(ngettext('Merged one CSS style rule with identical selectors', 'Merged {} CSS style rules with identical selectors', num_merged).format(num_merged))
if num_rules_merged > 0:
report(ngettext('Merged one CSS style rule with identical properties', 'Merged {} CSS style rules with identical properties', num_rules_merged).format(num_rules_merged))
if num_sheets_removed:
report(ngettext('Removed one unreferenced stylesheet', 'Removed {} unreferenced stylesheets', num_sheets_removed).format(num_sheets_removed))
if not removal_stats['rules']:
report(_('No unused CSS style rules found'))
if not removal_stats['selectors']:
report(_('No unused CSS selectors found'))
if remove_unused_classes and num_of_removed_classes == 0:
report(_('No unused class attributes found'))
if merge_rules and num_merged == 0:
report(_('No style rules that could be merged found'))
if remove_unreferenced_sheets and num_sheets_removed == 0:
report(_('No unused stylesheets found'))
return num_changes > 0def filter_declaration(style, properties=()):
changed = False for prop in properties:
if style.removeProperty(prop) != '':
changed = True all_props = set(style.keys())
for prop in style.getProperties():
n = normalizers.get(prop.name, None)
if n is not None:
normalized = n(prop.name, prop.propertyValue)
removed = properties.intersection(set(normalized))
if removed:
changed = True style.removeProperty(prop.name)
for prop in set(normalized) - removed - all_props:
style.setProperty(prop, normalized[prop])
return changed def filter_sheet(sheet, properties=()):
from css_parser.css import CSSRule changed = False remove = []
for rule in sheet.cssRules.rulesOfType(CSSRule.STYLE_RULE):
if filter_declaration(rule.style, properties):
changed = True if rule.style.length == 0:
remove.append(rule)
for rule in remove:
sheet.cssRules.remove(rule)
return changed def transform_inline_styles(container, name, transform_sheet, transform_style):
root = container.parsed(name)
changed = False for style in root.xpath('//*[local-name()="style"]'):
if style.text and (style.get('type') or 'text/css').lower() == 'text/css':
sheet = container.parse_css(style.text)
if transform_sheet(sheet):
changed = True style.text = force_unicode(sheet.cssText, 'utf-8')
pretty_script_or_style(container, style)
for elem in root.xpath('//*[@style]'):
text = elem.get('style', None)
if text:
style = container.parse_css(text, is_declaration=True)
if transform_style(style):
changed = True if style.length == 0:
del elem.attrib['style']
else:
elem.set('style', force_unicode(style.getCssText(separator=' '), 'utf-8'))
return changed def transform_css(container, transform_sheet=None, transform_style=None, names=()):
if not names:
types = OEB_STYLES | OEB_DOCS names = []
for name, mt in iteritems(container.mime_map):
if mt in types:
names.append(name)

doc_changed = False for name in names:
mt = container.mime_map[name]
if mt in OEB_STYLES:
sheet = container.parsed(name)
if transform_sheet(sheet):
container.dirty(name)
doc_changed = True elif mt in OEB_DOCS:
if transform_inline_styles(container, name, transform_sheet, transform_style):
container.dirty(name)
doc_changed = True return doc_changed

[dokumentace]
def filter_css(container, properties, names=()):
'''
Remove the specified CSS properties from all CSS rules in the book.

:param properties: Set of properties to remove. For example: :code:`{'font-family', 'color'}`.
:param names: The files from which to remove the properties. Defaults to all HTML and CSS files in the book.
'''
properties = normalize_filter_css(properties)
return transform_css(container, transform_sheet=partial(filter_sheet, properties=properties), transform_style=partial(filter_declaration, properties=properties), names=names)

def _classes_in_selector(selector, classes):
for attr in ('selector', 'subselector', 'parsed_tree'):
s = getattr(selector, attr, None)
if s is not None:
_classes_in_selector(s, classes)
cn = getattr(selector, 'class_name', None)
if cn is not None:
classes.add(cn)

@lru_cache(maxsize=4096)
def classes_in_selector(text):
classes = set()
try:
for selector in parse(text):
_classes_in_selector(selector, classes)
except SelectorSyntaxError:
pass return classes def classes_in_rule_list(css_rules):
classes = set()
for rule in css_rules:
if rule.type == rule.STYLE_RULE:
classes |= classes_in_selector(rule.selectorText)
elif hasattr(rule, 'cssRules'):
classes |= classes_in_rule_list(rule.cssRules)
return classes def iter_declarations(sheet_or_rule):
if hasattr(sheet_or_rule, 'cssRules'):
for rule in sheet_or_rule.cssRules:
yield from iter_declarations(rule)
elif hasattr(sheet_or_rule, 'style'):
yield sheet_or_rule.style elif isinstance(sheet_or_rule, CSSStyleDeclaration):
yield sheet_or_rule def remove_property_value(prop, predicate):
''' Remove the Values that match the predicate from this property. If all values of the property would be removed, the property is removed from its parent instead. Note that this means the property must have a parent (a
CSSStyleDeclaration). '''
removed_vals = list(filter(predicate, prop.propertyValue))
if len(removed_vals) == len(prop.propertyValue):
prop.parent.removeProperty(prop.name)
else:
x = css_text(prop.propertyValue)
for v in removed_vals:
x = x.replace(css_text(v), '').strip()
prop.propertyValue.cssText = x return bool(removed_vals)

RULE_PRIORITIES = {t:i for i, t in enumerate((CSSRule.COMMENT, CSSRule.CHARSET_RULE, CSSRule.IMPORT_RULE, CSSRule.NAMESPACE_RULE))}

def sort_sheet(container, sheet_or_text):
''' Sort the rules in a stylesheet. Note that in the general case this can change the effective styles, but for most common sheets, it should be safe.
'''
sheet = container.parse_css(sheet_or_text) if isinstance(sheet_or_text, str) else sheet_or_text def text_sort_key(x):
return numeric_sort_key(str(x or ''))

def selector_sort_key(x):
return (x.specificity, text_sort_key(x.selectorText))

def rule_sort_key(rule):
primary = RULE_PRIORITIES.get(rule.type, len(RULE_PRIORITIES))
secondary = text_sort_key(getattr(rule, 'atkeyword', '') or '')
tertiary = None if rule.type == CSSRule.STYLE_RULE:
primary += 1 selectors = sorted(rule.selectorList, key=selector_sort_key)
tertiary = selector_sort_key(selectors[0])
rule.selectorText = ', '.join(s.selectorText for s in selectors)
elif rule.type == CSSRule.FONT_FACE_RULE:
try:
tertiary = text_sort_key(rule.style.getPropertyValue('font-family'))
except Exception:
pass return primary, secondary, tertiary sheet.cssRules.sort(key=rule_sort_key)
return sheet def add_stylesheet_links(container, name, text):
root = container.parse_xhtml(text, name)
head = root.xpath('//*[local-name() = "head"]')
if not head:
return head = head[0]
sheets = tuple(container.manifest_items_of_type(lambda mt: mt in OEB_STYLES))
if not sheets:
return for sname in sheets:
link = head.makeelement(XHTML('link'), type='text/css', rel='stylesheet', href=container.name_to_href(sname, name))
head.append(link)
pretty_xml_tree(head)
return serialize(root, 'text/html')

def rename_class_in_rule_list(css_rules, old_name, new_name):
# this regex will not match class names inside attribute value selectors
# and it will match id selectors that contain .old_name but its the best
# that can be done without implementing a full parser for CSS selectors pat = re.compile(rf'(?<=\.){re.escape(old_name)}(?:\W|$)')

def repl(m):
return m.group().replace(old_name, new_name)

changed = False for rule in css_rules:
if rule.type == rule.STYLE_RULE:
old = rule.selectorText q = pat.sub(repl, old)
if q != old:
changed = True rule.selectorText = q elif hasattr(rule, 'cssRules'):
if rename_class_in_rule_list(rule.cssRules, old_name, new_name):
changed = True return changed def rename_class_in_doc(container, root, old_name, new_name):
changed = False pat = re.compile(rf'(?:^|\W){re.escape(old_name)}(?:\W|$)')

def repl(m):
return m.group().replace(old_name, new_name)

for elem in root.xpath('//*[@class]'):
old = elem.get('class')
if old:
new = pat.sub(repl, old)
if new != old:
changed = True elem.set('class', new)
for style in root.xpath('//*[local-name()="style"]'):
if style.get('type', 'text/css') == 'text/css' and style.text:
sheet = container.parse_css(style.text)
if rename_class_in_rule_list(sheet.cssRules, old_name, new_name):
changed = True style.text = force_unicode(sheet.cssText, 'utf-8')
return changed def rename_class(container, old_name, new_name):
changed = False if not old_name or old_name == new_name:
return changed for sheet_name in container.manifest_items_of_type(lambda mt: mt in OEB_STYLES):
sheet = container.parsed(sheet_name)
if rename_class_in_rule_list(sheet.cssRules, old_name, new_name):
container.dirty(sheet_name)
changed = True for doc_name in container.manifest_items_of_type(lambda mt: mt in OEB_DOCS):
doc = container.parsed(doc_name)
if rename_class_in_doc(container, doc, old_name, new_name):
container.dirty(doc_name)
changed = True return changed

© Copyright Kovid Goyal.
Naposledy aktualizováno kvě 03, 2024.

Navigace

- moduly
- Spustit »
- Kód modulu »
- calibre.ebooks.oeb.polish.fonts

Zdrojový kód pro calibre.ebooks.oeb.polish.fonts

#!/usr/bin/env python

__license__ = 'GPL v3'
__copyright__ = '2014, Kovid Goyal <kovid at kovidgoyal.net>'

from tinycss.fonts3 import parse_font, parse_font_family, serialize_font, serialize_font_family from calibre.ebooks.oeb.base import css_text from calibre.ebooks.oeb.normalize_css import normalize_font from calibre.ebooks.oeb.polish.container import OEB_DOCS, OEB_STYLES from polyglot.builtins import iteritems def unquote(x):
if x and len(x) > 1 and x[0] == x[-1] and x[0] in ('"', "'"):
x = x[1:-1]
return x def font_family_data_from_declaration(style, families):
font_families = []
f = style.getProperty('font')
if f is not None:
f = normalize_font(f.propertyValue, font_family_as_list=True).get('font-family', None)
if f is not None:
font_families = [unquote(x) for x in f]
f = style.getProperty('font-family')
if f is not None:
font_families = parse_font_family(css_text(f.propertyValue))

for f in font_families:
families[f] = families.get(f, False)

def font_family_data_from_sheet(sheet, families):
for rule in sheet.cssRules:
if rule.type == rule.STYLE_RULE:
font_family_data_from_declaration(rule.style, families)
elif rule.type == rule.FONT_FACE_RULE:
ff = rule.style.getProperty('font-family')
if ff is not None:
for f in parse_font_family(css_text(ff.propertyValue)):
families[f] = True def font_family_data(container):
families = {}
for name, mt in iteritems(container.mime_map):
if mt in OEB_STYLES:
sheet = container.parsed(name)
font_family_data_from_sheet(sheet, families)
elif mt in OEB_DOCS:
root = container.parsed(name)
for style in root.xpath('//*[local-name() = "style"]'):
if style.text and style.get('type', 'text/css').lower() == 'text/css':
sheet = container.parse_css(style.text)
font_family_data_from_sheet(sheet, families)
for style in root.xpath('//*/@style'):
if style:
style = container.parse_css(style, is_declaration=True)
font_family_data_from_declaration(style, families)
return families def change_font_in_declaration(style, old_name, new_name=None):
changed = False ff = style.getProperty('font-family')
if ff is not None:
fams = parse_font_family(css_text(ff.propertyValue))
nfams = list(filter(None, [new_name if x == old_name else x for x in fams]))
if fams != nfams:
if nfams:
ff.propertyValue.cssText = serialize_font_family(nfams)
else:
style.removeProperty(ff.name)
changed = True ff = style.getProperty('font')
if ff is not None:
props = parse_font(css_text(ff.propertyValue))
fams = props.get('font-family') or []
nfams = list(filter(None, [new_name if x == old_name else x for x in fams]))
if fams != nfams:
props['font-family'] = nfams if nfams:
ff.propertyValue.cssText = serialize_font(props)
else:
style.removeProperty(ff.name)
changed = True return changed def remove_embedded_font(container, sheet, rule, sheet_name):
src = getattr(rule.style.getProperty('src'), 'value', None)
if src is not None:
if src.startswith('url('):
src = src[4:-1]
sheet.cssRules.remove(rule)
if src:
src = unquote(src)
name = container.href_to_name(src, sheet_name)
if container.has_name(name):
container.remove_item(name)

def change_font_in_sheet(container, sheet, old_name, new_name, sheet_name):
changed = False removals = []
for rule in sheet.cssRules:
if rule.type == rule.STYLE_RULE:
changed |= change_font_in_declaration(rule.style, old_name, new_name)
elif rule.type == rule.FONT_FACE_RULE:
ff = rule.style.getProperty('font-family')
if ff is not None:
families = {x for x in parse_font_family(css_text(ff.propertyValue))}
if old_name in families:
changed = True removals.append(rule)
for rule in reversed(removals):
remove_embedded_font(container, sheet, rule, sheet_name)
return changed

[dokumentace]
def change_font(container, old_name, new_name=None):
'''
Change a font family from old_name to new_name. Changes all occurrences of the font family in stylesheets, style tags and style attributes.
If the old_name refers to an embedded font, it is removed. You can set new_name to None to remove the font family instead of changing it.
'''
changed = False for name, mt in tuple(iteritems(container.mime_map)):
if mt in OEB_STYLES:
sheet = container.parsed(name)
if change_font_in_sheet(container, sheet, old_name, new_name, name):
container.dirty(name)
changed = True elif mt in OEB_DOCS:
root = container.parsed(name)
for style in root.xpath('//*[local-name() = "style"]'):
if style.text and style.get('type', 'text/css').lower() == 'text/css':
sheet = container.parse_css(style.text)
if change_font_in_sheet(container, sheet, old_name, new_name, name):
container.dirty(name)
changed = True for elem in root.xpath('//*[@style]'):
style = elem.get('style', '')
if style:
style = container.parse_css(style, is_declaration=True)
if change_font_in_declaration(style, old_name, new_name):
style = css_text(style).strip().rstrip(';').strip()
if style:
elem.set('style', style)
else:
del elem.attrib['style']
container.dirty(name)
changed = True return changed

© Copyright Kovid Goyal.
Naposledy aktualizováno kvě 03, 2024.

Navigace

- moduly
- Spustit »
- Kód modulu »
- calibre.ebooks.oeb.polish.jacket

Zdrojový kód pro calibre.ebooks.oeb.polish.jacket

#!/usr/bin/env python

__license__ = 'GPL v3'
__copyright__ = '2013, Kovid Goyal <kovid at kovidgoyal.net>'
__docformat__ = 'restructuredtext en'

from calibre.customize.ui import output_profiles from calibre.ebooks.conversion.config import load_defaults from calibre.ebooks.oeb.base import OPF, XPath from calibre.ebooks.oeb.polish.cover import find_cover_page from calibre.ebooks.oeb.transforms.jacket import referenced_images from calibre.ebooks.oeb.transforms.jacket import render_jacket as render def render_jacket(container, jacket):
mi = container.mi ps = load_defaults('page_setup')
op = ps.get('output_profile', 'default')
opmap = {x.short_name:x for x in output_profiles()}
output_profile = opmap.get(op, opmap['default'])
root = render(mi, output_profile)
for img, path in referenced_images(root):
container.log('Embedding referenced image: %s into jacket' % path)
ext = path.rpartition('.')[-1]
jacket_item = container.generate_item('jacket_image.'+ext, id_prefix='jacket_img')
name = container.href_to_name(jacket_item.get('href'), container.opf_name)
with open(path, 'rb') as f:
container.parsed_cache[name] = f.read()
container.commit_item(name)
href = container.name_to_href(name, jacket)
img.set('src', href)
return root def is_legacy_jacket(root):
return len(root.xpath(
'//*[starts-with(@class,"calibrerescale") and (local-name()="h1" or local-name()="h2")]')) > 0 def is_current_jacket(root):
return len(XPath(
'//h:meta[@name="calibre-content" and @content="jacket"]')(root)) > 0 def find_existing_jacket(container):
for item in container.spine_items:
name = container.abspath_to_name(item)
if container.book_type == 'azw3':
root = container.parsed(name)
if is_current_jacket(root):
return name else:
if name.rpartition('/')[-1].startswith('jacket') and name.endswith('.xhtml'):
root = container.parsed(name)
if is_current_jacket(root) or is_legacy_jacket(root):
return name def replace_jacket(container, name):
root = render_jacket(container, name)
container.parsed_cache[name] = root container.dirty(name)

[dokumentace]
def remove_jacket(container):
' Remove an existing jacket, if any. Returns False if no existing jacket was found. '
name = find_existing_jacket(container)
if name is not None:
remove_jacket_images(container, name)
container.remove_item(name)
return True return False
def remove_jacket_images(container, name):
root = container.parsed_cache[name]
for img in root.xpath('//*[local-name() = "img" and @src]'):
iname = container.href_to_name(img.get('src'), name)
if container.has_name(iname):
container.remove_item(iname)

[dokumentace]
def add_or_replace_jacket(container):
''' Either create a new jacket from the book's metadata or replace an existing jacket. Returns True if an existing jacket was replaced. '''
name = find_existing_jacket(container)
found = True if name is None:
jacket_item = container.generate_item('jacket.xhtml', id_prefix='jacket')
name = container.href_to_name(jacket_item.get('href'), container.opf_name)
found = False if found:
remove_jacket_images(container, name)

replace_jacket(container, name)
if not found:
# Insert new jacket into spine index = 0 sp = container.abspath_to_name(next(container.spine_items))
if sp == find_cover_page(container):
index = 1 itemref = container.opf.makeelement(OPF('itemref'), idref=jacket_item.get('id'))
container.insert_into_xml(container.opf_xpath('//opf:spine')[0], itemref, index=index)
return found

© Copyright Kovid Goyal.
Naposledy aktualizováno kvě 03, 2024.

Navigace

- moduly
- Spustit »
- Kód modulu »
- calibre.ebooks.oeb.polish.pretty

Zdrojový kód pro calibre.ebooks.oeb.polish.pretty

#!/usr/bin/env python

__license__ = 'GPL v3'
__copyright__ = '2013, Kovid Goyal <kovid at kovidgoyal.net>'

import textwrap

# from lxml.etree import Element from calibre import force_unicode from calibre.ebooks.oeb.base import OEB_DOCS, OEB_STYLES, SVG, XHTML, XPNSMAP, barename, serialize from calibre.ebooks.oeb.polish.container import OPF_NAMESPACES from calibre.ebooks.oeb.polish.utils import guess_type from calibre.utils.icu import sort_key from polyglot.builtins import iteritems def isspace(x):
return not x.strip('\u0009\u000a\u000c\u000d\u0020')

def pretty_xml_tree(elem, level=0, indent=' '):
''' XML beautifier, assumes that elements that have children do not have textual content. Also assumes that there is no text immediately after closing tags. These are true for opf/ncx and container.xml files. If either of the assumptions are violated, there should be no data loss, but pretty printing won't produce optimal results.'''
if (not elem.text and len(elem) > 0) or (elem.text and isspace(elem.text)):
elem.text = '\n' + (indent * (level+1))
for i, child in enumerate(elem):
pretty_xml_tree(child, level=level+1, indent=indent)
if not child.tail or isspace(child.tail):
l = level + 1 if i == len(elem) - 1:
l -= 1 child.tail = '\n' + (indent * l)

def pretty_opf(root):
# Put all dc: tags first starting with title and author. Preserve order for
# the rest.
def dckey(x):
return {'title':0, 'creator':1}.get(barename(x.tag), 2)
for metadata in root.xpath('//opf:metadata', namespaces=OPF_NAMESPACES):
dc_tags = metadata.xpath('./*[namespace-uri()="%s"]' % OPF_NAMESPACES['dc'])
dc_tags.sort(key=dckey)
for x in reversed(dc_tags):
metadata.insert(0, x)

# Group items in the manifest spine_ids = root.xpath('//opf:spine/opf:itemref/@idref', namespaces=OPF_NAMESPACES)
spine_ids = {x:i for i, x in enumerate(spine_ids)}

def manifest_key(x):
mt = x.get('media-type', '')
href = x.get('href', '')
ext = href.rpartition('.')[-1].lower()
cat = 1000 if mt in OEB_DOCS:
cat = 0 elif mt == guess_type('a.ncx'):
cat = 1 elif mt in OEB_STYLES:
cat = 2 elif mt.startswith('image/'):
cat = 3 elif ext in {'otf', 'ttf', 'woff', 'woff2'}:
cat = 4 elif mt.startswith('audio/'):
cat = 5 elif mt.startswith('video/'):
cat = 6 if cat == 0:
i = spine_ids.get(x.get('id', None), 1000000000)
else:
i = sort_key(href)
return (cat, i)

for manifest in root.xpath('//opf:manifest', namespaces=OPF_NAMESPACES):
try:
children = sorted(manifest, key=manifest_key)
except AttributeError:
continue # There are comments so dont sort since that would mess up the comments for x in reversed(children):
manifest.insert(0, x)

SVG_TAG = SVG('svg')
BLOCK_TAGS = frozenset(map(XHTML, (
'address', 'article', 'aside', 'audio', 'blockquote', 'body', 'canvas', 'col', 'colgroup', 'dd',
'div', 'dl', 'dt', 'fieldset', 'figcaption', 'figure', 'footer', 'form',
'h1', 'h2', 'h3', 'h4', 'h5', 'h6', 'header', 'hgroup', 'hr', 'li',
'noscript', 'ol', 'output', 'p', 'pre', 'script', 'section', 'style', 'table', 'tbody', 'td',
'tfoot', 'th', 'thead', 'tr', 'ul', 'video', 'img'))) | {SVG_TAG}

def isblock(x):
if callable(x.tag) or not x.tag:
return True if x.tag in BLOCK_TAGS:
return True return False def has_only_blocks(x):
if hasattr(x.tag, 'split') and len(x) == 0:
# Tag with no children, return False if x.text and not isspace(x.text):
return False for child in x:
if not isblock(child) or (child.tail and not isspace(child.tail)):
return False return True def indent_for_tag(x):
prev = x.getprevious()
x = x.getparent().text if prev is None else prev.tail if not x:
return ''
s = x.rpartition('\n')[-1]
return s if isspace(s) else ''

def set_indent(elem, attr, indent):
x = getattr(elem, attr)
if not x:
x = indent else:
lines = x.splitlines()
if isspace(lines[-1]):
lines[-1] = indent else:
lines.append(indent)
x = '\n'.join(lines)
setattr(elem, attr, x)

def pretty_block(parent, level=1, indent=' '):
''' Surround block tags with blank lines and recurse into child block tags that contain only other block tags '''
if not parent.text or isspace(parent.text):
parent.text = ''
nn = '\n' if hasattr(parent.tag, 'strip') and barename(parent.tag) in {'tr', 'td', 'th'} else '\n\n'
parent.text = parent.text + nn + (indent * level)
for i, child in enumerate(parent):
if isblock(child) and has_only_blocks(child):
pretty_block(child, level=level+1, indent=indent)
elif child.tag == SVG_TAG:
pretty_xml_tree(child, level=level, indent=indent)
l = level if i == len(parent) - 1:
l -= 1 if not child.tail or isspace(child.tail):
child.tail = ''
child.tail = child.tail + nn + (indent * l)

def pretty_script_or_style(container, child):
if child.text:
indent = indent_for_tag(child)
if child.tag.endswith('style'):
child.text = force_unicode(pretty_css(container, '', child.text), 'utf-8')
child.text = textwrap.dedent(child.text)
child.text = '\n' + '\n'.join([(indent + x) if x else '' for x in child.text.splitlines()])
set_indent(child, 'text', indent)

def pretty_html_tree(container, root):
root.text = '\n\n'
for child in root:
child.tail = '\n\n'
if hasattr(child.tag, 'endswith') and child.tag.endswith('}head'):
pretty_xml_tree(child)
for body in root.findall('h:body', namespaces=XPNSMAP):
pretty_block(body)
# Special case the handling of a body that contains a single block tag
# with all content. In this case we prettify the containing block tag
# even if it has non block children.
if (len(body) == 1 and not callable(body[0].tag) and isblock(body[0]) and not has_only_blocks( body[0]) and barename(body[0].tag) not in (
'pre', 'p', 'h1', 'h2', 'h3', 'h4', 'h5', 'h6') and len(body[0]) > 0):
pretty_block(body[0], level=2)

if container is not None:
# Handle <script> and <style> tags for child in root.xpath('//*[local-name()="script" or local-name()="style"]'):
pretty_script_or_style(container, child)

[dokumentace]
def fix_html(container, raw):
' Fix any parsing errors in the HTML represented as a string in raw. Fixing is done using the HTML5 parsing algorithm. '
root = container.parse_xhtml(raw)
return serialize(root, 'text/html')

[dokumentace]
def pretty_html(container, name, raw):
' Pretty print the HTML represented as a string in raw '
root = container.parse_xhtml(raw)
pretty_html_tree(container, root)
return serialize(root, 'text/html')

[dokumentace]
def pretty_css(container, name, raw):
' Pretty print the CSS represented as a string in raw '
sheet = container.parse_css(raw)
return serialize(sheet, 'text/css')

[dokumentace]
def pretty_xml(container, name, raw):
' Pretty print the XML represented as a string in raw. If ``name`` is the name of the OPF, extra OPF-specific prettying is performed. '
root = container.parse_xml(raw)
if name == container.opf_name:
pretty_opf(root)
pretty_xml_tree(root)
return serialize(root, 'text/xml')

[dokumentace]
def fix_all_html(container):
' Fix any parsing errors in all HTML files in the container. Fixing is done using the HTML5 parsing algorithm. '
for name, mt in iteritems(container.mime_map):
if mt in OEB_DOCS:
container.parsed(name)
container.dirty(name)

[dokumentace]
def pretty_all(container):
' Pretty print all HTML/CSS/XML files in the container '
xml_types = {guess_type('a.ncx'), guess_type('a.xml'), guess_type('a.svg')}
for name, mt in iteritems(container.mime_map):
prettied = False if mt in OEB_DOCS:
pretty_html_tree(container, container.parsed(name))
prettied = True elif mt in OEB_STYLES:
container.parsed(name)
prettied = True elif name == container.opf_name:
root = container.parsed(name)
pretty_opf(root)
pretty_xml_tree(root)
prettied = True elif mt in xml_types:
pretty_xml_tree(container.parsed(name))
prettied = True if prettied:
container.dirty(name)

© Copyright Kovid Goyal.
Naposledy aktualizováno kvě 03, 2024.

Navigace

- moduly
- Spustit »
- Kód modulu »
- calibre.ebooks.oeb.polish.replace

Zdrojový kód pro calibre.ebooks.oeb.polish.replace

#!/usr/bin/env python

__license__ = 'GPL v3'
__copyright__ = '2013, Kovid Goyal <kovid at kovidgoyal.net>'
__docformat__ = 'restructuredtext en'

import codecs import os import posixpath import shutil from collections import Counter, defaultdict from functools import partial from calibre import sanitize_file_name from calibre.ebooks.chardet import strip_encoding_declarations from calibre.ebooks.oeb.base import css_text from calibre.ebooks.oeb.polish.css import iter_declarations, remove_property_value from calibre.ebooks.oeb.polish.utils import extract from polyglot.builtins import iteritems, itervalues from polyglot.urllib import urlparse, urlunparse class LinkReplacer:

def __init__(self, base, container, link_map, frag_map):
self.base = base self.frag_map = frag_map self.link_map = link_map self.container = container self.replaced = False def __call__(self, url):
if url and url.startswith('#'):
repl = self.frag_map(self.base, url[1:])
if not repl or repl == url[1:]:
return url self.replaced = True return '#' + repl name = self.container.href_to_name(url, self.base)
if not name:
return url nname = self.link_map.get(name, None)
if not nname:
return url purl = urlparse(url)
href = self.container.name_to_href(nname, self.base)
if purl.fragment:
nfrag = self.frag_map(name, purl.fragment)
if nfrag:
href += '#%s'%nfrag if href != url:
self.replaced = True return href class IdReplacer:

def __init__(self, base, container, id_map):
self.base, self.container, self.replaced = base, container, False self.id_map = id_map def __call__(self, url):
if url and url.startswith('#'):
repl = self.id_map.get(self.base, {}).get(url[1:])
if repl is None or repl == url[1:]:
return url self.replaced = True return '#' + repl name = self.container.href_to_name(url, self.base)
if not name:
return url id_map = self.id_map.get(name)
if id_map is None:
return url purl = urlparse(url)
nfrag = id_map.get(purl.fragment)
if nfrag is None:
return url purl = purl._replace(fragment=nfrag)
href = urlunparse(purl)
if href != url:
self.replaced = True return href class LinkRebaser:

def __init__(self, container, old_name, new_name):
self.old_name, self.new_name = old_name, new_name self.container = container self.replaced = False def __call__(self, url):
if url and url.startswith('#'):
return url purl = urlparse(url)
frag = purl.fragment name = self.container.href_to_name(url, self.old_name)
if not name:
return url if name == self.old_name:
name = self.new_name href = self.container.name_to_href(name, self.new_name)
if frag:
href += '#' + frag if href != url:
self.replaced = True return href

[dokumentace]
def replace_links(container, link_map, frag_map=lambda name, frag:frag, replace_in_opf=False):
'''
Replace links to files in the container. Will iterate over all files in the container and change the specified links in them.

:param link_map: A mapping of old canonical name to new canonical name. For example: :code:`{'images/old.png': 'images/new.png'}`
:param frag_map: A callable that takes two arguments ``(name, anchor)`` and returns a new anchor. This is useful if you need to change the anchors in
HTML files. By default, it does nothing.
:param replace_in_opf: If False, links are not replaced in the OPF file.

'''
for name, media_type in iteritems(container.mime_map):
if name == container.opf_name and not replace_in_opf:
continue repl = LinkReplacer(name, container, link_map, frag_map)
container.replace_links(name, repl)

def replace_ids(container, id_map):
'''
Replace all links in the container that pointed to the changed ids.

:param id_map: A mapping of {name:id_map} where each id_map is a mapping of {old_id:new_id}
:return: True iff at least one link was changed

'''
changed = False for name, media_type in iteritems(container.mime_map):
repl = IdReplacer(name, container, id_map)
container.replace_links(name, repl)
if name == container.opf_name:
imap = id_map.get(name, {})
for item in container.opf_xpath('//*[@idref]'):
old_id = item.get('idref')
if old_id is not None:
new_id = imap.get(old_id)
if new_id is not None:
item.set('idref', new_id)
if repl.replaced:
changed = True return changed def smarten_punctuation(container, report):
from calibre.ebooks.conversion.preprocess import smarten_punctuation smartened = False for path in container.spine_items:
name = container.abspath_to_name(path)
changed = False with container.open(name, 'r+b') as f:
html = container.decode(f.read())
newhtml = smarten_punctuation(html, container.log)
if newhtml != html:
changed = True report(_('Smartened punctuation in: %s')%name)
newhtml = strip_encoding_declarations(newhtml)
f.seek(0)
f.truncate()
f.write(codecs.BOM_UTF8 + newhtml.encode('utf-8'))
if changed:
# Add an encoding declaration (it will be added automatically when
# serialized)
root = container.parsed(name)
for m in root.xpath('descendant::*[local-name()="meta" and @http-equiv]'):
m.getparent().remove(m)
container.dirty(name)
smartened = True if not smartened:
report(_('No punctuation that could be smartened found'))
return smartened

[dokumentace]
def rename_files(container, file_map):
'''
Rename files in the container, automatically updating all links to them.

:param file_map: A mapping of old canonical name to new canonical name, for example: :code:`{'text/chapter1.html': 'chapter1.html'}`.
'''
overlap = set(file_map).intersection(set(itervalues(file_map)))
if overlap:
raise ValueError('Circular rename detected. The files %s are both rename targets and destinations' % ', '.join(overlap))
for name, dest in iteritems(file_map):
if container.exists(dest):
if name != dest and name.lower() == dest.lower():
# A case change on an OS with a case insensitive file-system.
continue raise ValueError('Cannot rename {0} to {1} as {1} already exists'.format(name, dest))
if len(tuple(itervalues(file_map))) != len(set(itervalues(file_map))):
raise ValueError('Cannot rename, the set of destination files contains duplicates')
link_map = {}
for current_name, new_name in iteritems(file_map):
container.rename(current_name, new_name)
if new_name != container.opf_name: # OPF is handled by the container link_map[current_name] = new_name replace_links(container, link_map, replace_in_opf=True)

def replace_file(container, name, path, basename, force_mt=None):
dirname, base = name.rpartition('/')[0::2]
nname = sanitize_file_name(basename)
if dirname:
nname = dirname + '/' + nname with open(path, 'rb') as src:
if name != nname:
count = 0 b, e = nname.rpartition('.')[0::2]
while container.exists(nname):
count += 1 nname = b + ('_%d.%s' % (count, e))
rename_files(container, {name:nname})
mt = force_mt or container.guess_type(nname)
container.mime_map[nname] = mt for itemid, q in iteritems(container.manifest_id_map):
if q == nname:
for item in container.opf_xpath('//opf:manifest/opf:item[@href and @id="%s"]' % itemid):
item.set('media-type', mt)
container.dirty(container.opf_name)
with container.open(nname, 'wb') as dest:
shutil.copyfileobj(src, dest)

def mt_to_category(container, mt):
from calibre.ebooks.oeb.base import OEB_DOCS, OEB_STYLES from calibre.ebooks.oeb.polish.utils import OEB_FONTS, guess_type if mt in OEB_DOCS:
category = 'text'
elif mt in OEB_STYLES:
category = 'style'
elif mt in OEB_FONTS:
category = 'font'
elif mt == guess_type('a.opf'):
category = 'opf'
elif mt == guess_type('a.ncx'):
category = 'toc'
else:
category = mt.partition('/')[0]
return category

[dokumentace]
def get_recommended_folders(container, names):
''' Return the folders that are recommended for the given filenames. The recommendation is based on where the majority of files of the same type are located in the container. If no files of a particular type are present, the recommended folder is assumed to be the folder containing the OPF file. '''
from calibre.ebooks.oeb.polish.utils import guess_type counts = defaultdict(Counter)
for name, mt in iteritems(container.mime_map):
folder = name.rpartition('/')[0] if '/' in name else ''
counts[mt_to_category(container, mt)][folder] += 1 try:
opf_folder = counts['opf'].most_common(1)[0][0]
except KeyError:
opf_folder = ''

recommendations = {category:counter.most_common(1)[0][0] for category, counter in iteritems(counts)}
return {n:recommendations.get(mt_to_category(container, guess_type(os.path.basename(n))), opf_folder) for n in names}

def normalize_case(container, val):

def safe_listdir(x):
try:
return os.listdir(x)
except OSError:
return ()

parts = val.split('/')
ans = []
for i in range(len(parts)):
q = '/'.join(parts[:i+1])
x = container.name_to_abspath(q)
xl = parts[i].lower()
candidates = [c for c in safe_listdir(os.path.dirname(x)) if c != parts[i] and c.lower() == xl]
ans.append(candidates[0] if candidates else parts[i])
return '/'.join(ans)

def rationalize_folders(container, folder_type_map):
all_names = set(container.mime_map)
new_names = set()
name_map = {}
for key in tuple(folder_type_map):
val = folder_type_map[key]
folder_type_map[key] = normalize_case(container, val)
for name in all_names:
if name.startswith('META-INF/'):
continue category = mt_to_category(container, container.mime_map[name])
folder = folder_type_map.get(category, None)
if folder is not None:
bn = posixpath.basename(name)
new_name = posixpath.join(folder, bn)
if new_name != name:
c = 0 while new_name in all_names or new_name in new_names:
c += 1 n, ext = bn.rpartition('.')[0::2]
new_name = posixpath.join(folder, '%s_%d.%s' % (n, c, ext))
name_map[name] = new_name new_names.add(new_name)
return name_map def remove_links_in_sheet(href_to_name, sheet, predicate):
import_rules_to_remove = []
changed = False for i, r in enumerate(sheet):
if r.type == r.IMPORT_RULE:
name = href_to_name(r.href)
if predicate(name, r.href, None):
import_rules_to_remove.append(i)
for i in sorted(import_rules_to_remove, reverse=True):
sheet.deleteRule(i)
changed = True for dec in iter_declarations(sheet):
changed = remove_links_in_declaration(href_to_name, dec, predicate) or changed return changed def remove_links_in_declaration(href_to_name, style, predicate):
def check_pval(v):
if v.type == v.URI:
name = href_to_name(v.uri)
return predicate(name, v.uri, None)
return False changed = False for p in tuple(style.getProperties(all=True)):
changed = remove_property_value(p, check_pval) or changed return changed def remove_links_to(container, predicate):
''' predicate must be a function that takes the arguments (name, href, fragment=None) and returns True iff the link should be removed '''
from calibre.ebooks.oeb.base import OEB_DOCS, OEB_STYLES, XHTML, XPath, iterlinks stylepath = XPath('//h:style')
styleattrpath = XPath('//*[@style]')
changed = set()
for name, mt in iteritems(container.mime_map):
removed = False if mt in OEB_DOCS:
root = container.parsed(name)
for el, attr, href, pos in iterlinks(root, find_links_in_css=False):
hname = container.href_to_name(href, name)
frag = href.partition('#')[-1]
if predicate(hname, href, frag):
if attr is None:
el.text = None else:
if el.tag == XHTML('link') or el.tag == XHTML('img'):
extract(el)
else:
del el.attrib[attr]
removed = True for tag in stylepath(root):
if tag.text and (tag.get('type') or 'text/css').lower() == 'text/css':
sheet = container.parse_css(tag.text)
if remove_links_in_sheet(partial(container.href_to_name, base=name), sheet, predicate):
tag.text = css_text(sheet)
removed = True for tag in styleattrpath(root):
style = tag.get('style')
if style:
style = container.parse_css(style, is_declaration=True)
if remove_links_in_declaration(partial(container.href_to_name, base=name), style, predicate):
removed = True tag.set('style', css_text(style))
elif mt in OEB_STYLES:
removed = remove_links_in_sheet(partial(container.href_to_name, base=name), container.parsed(name), predicate)
if removed:
changed.add(name)
for i in changed:
container.dirty(i)
return changed def get_spine_order_for_all_files(container):
linear_names, non_linear_names = [], []
for name, is_linear in container.spine_names:
(linear_names if is_linear else non_linear_names).append(name)
all_names = linear_names + non_linear_names spine_names = frozenset(all_names)
ans = {}
for spine_pos, name in enumerate(all_names):
ans.setdefault(name, (spine_pos, -1))
for i, href in enumerate(container.iterlinks(name, get_line_numbers=False)):
lname = container.href_to_name(href, name)
if lname not in spine_names:
ans.setdefault(lname, (spine_pos, i))
return ans

© Copyright Kovid Goyal.
Naposledy aktualizováno kvě 03, 2024.

Navigace

- moduly
- Spustit »
- Kód modulu »
- calibre.ebooks.oeb.polish.split

Zdrojový kód pro calibre.ebooks.oeb.polish.split

#!/usr/bin/env python

__license__ = 'GPL v3'
__copyright__ = '2013, Kovid Goyal <kovid at kovidgoyal.net>'

import copy import os import re from calibre.ebooks.oeb.base import OEB_DOCS, OPF, XHTML, XPNSMAP, XPath, barename from calibre.ebooks.oeb.polish.errors import MalformedMarkup from calibre.ebooks.oeb.polish.replace import LinkRebaser from calibre.ebooks.oeb.polish.toc import node_from_loc from polyglot.builtins import iteritems, string_or_bytes from polyglot.urllib import urlparse class AbortError(ValueError):
pass def in_table(node):
while node is not None:
if node.tag.endswith('}table'):
return True node = node.getparent()
return False def adjust_split_point(split_point, log):
'''
Move the split point up its ancestor chain if it has no content before it. This handles the common case:
<div id="chapter1"><h2>Chapter 1</h2>...</div> with a page break on the h2.
'''
sp = split_point while True:
parent = sp.getparent()
if ( parent is None or barename(parent.tag) in {'body', 'html'} or
(parent.text and parent.text.strip()) or parent.index(sp) > 0
):
break sp = parent if sp is not split_point:
log.debug('Adjusted split point to ancestor')

return sp def get_body(root):
return root.find('h:body', namespaces=XPNSMAP)

def do_split(split_point, log, before=True):
'''
Split tree into a *before* and an *after* tree at ``split_point``.

:param split_point: The Element at which to split
:param before: If True tree is split before split_point, otherwise after split_point
:return: before_tree, after_tree
'''
if before:
# We cannot adjust for after since moving an after split point to a
# parent will cause breakage if the parent contains any content
# after the original split point split_point = adjust_split_point(split_point, log)
tree = split_point.getroottree()
path = tree.getpath(split_point)

tree, tree2 = copy.deepcopy(tree), copy.deepcopy(tree)
root, root2 = tree.getroot(), tree2.getroot()
body, body2 = map(get_body, (root, root2))
split_point = root.xpath(path)[0]
split_point2 = root2.xpath(path)[0]

def nix_element(elem, top=True):
# Remove elem unless top is False in which case replace elem by its
# children parent = elem.getparent()
if top:
parent.remove(elem)
else:
index = parent.index(elem)
parent[index:index+1] = list(elem.iterchildren())

# Tree 1 hit_split_point = False keep_descendants = False split_point_descendants = frozenset(split_point.iterdescendants())
for elem in tuple(body.iterdescendants()):
if elem is split_point:
hit_split_point = True if before:
nix_element(elem)
else:
# We want to keep the descendants of the split point in
# Tree 1 keep_descendants = True
# We want the split point element, but not its tail elem.tail = '\n'

continue if hit_split_point:
if keep_descendants:
if elem in split_point_descendants:
# elem is a descendant keep it continue else:
# We are out of split_point, so prevent further set
# lookups of split_point_descendants keep_descendants = False nix_element(elem)

# Tree 2 ancestors = frozenset(XPath('ancestor::*')(split_point2))
for elem in tuple(body2.iterdescendants()):
if elem is split_point2:
if not before:
# Keep the split point element's tail, if it contains non-whitespace
# text tail = elem.tail if tail and not tail.isspace():
parent = elem.getparent()
idx = parent.index(elem)
if idx == 0:
parent.text = (parent.text or '') + tail else:
sib = parent[idx-1]
sib.tail = (sib.tail or '') + tail
# Remove the element itself nix_element(elem)
break if elem in ancestors:
# We have to preserve the ancestors as they could have CSS
# styles that are inherited/applicable, like font or
# width. So we only remove the text, if any.
elem.text = '\n'
else:
nix_element(elem, top=False)

body2.text = '\n'

return tree, tree2 class SplitLinkReplacer:

def __init__(self, base, bottom_anchors, top_name, bottom_name, container):
self.bottom_anchors, self.bottom_name = bottom_anchors, bottom_name self.container, self.top_name = container, top_name self.base = base self.replaced = False def __call__(self, url):
if url and url.startswith('#'):
return url name = self.container.href_to_name(url, self.base)
if name != self.top_name:
return url purl = urlparse(url)
if purl.fragment and purl.fragment in self.bottom_anchors:
url = self.container.name_to_href(self.bottom_name, self.base) + '#' + purl.fragment self.replaced = True return url

[dokumentace]
def split(container, name, loc_or_xpath, before=True, totals=None):
'''
Split the file specified by name at the position specified by loc_or_xpath.
Splitting automatically migrates all links and references to the affected files.

:param loc_or_xpath: Should be an XPath expression such as
//h:div[@id="split_here"]. Can also be a *loc* which is used internally to implement splitting in the preview panel.
:param before: If True the split occurs before the identified element otherwise after it.
:param totals: Used internally
'''

root = container.parsed(name)
if isinstance(loc_or_xpath, str):
split_point = root.xpath(loc_or_xpath)[0]
else:
try:
split_point = node_from_loc(root, loc_or_xpath, totals=totals)
except MalformedMarkup:
# The webkit HTML parser and the container parser have yielded
# different node counts, this can happen if the file is valid XML
# but contains constructs like nested <p> tags. So force parse it
# with the HTML 5 parser and try again.
raw = container.raw_data(name)
root = container.parse_xhtml(raw, fname=name, force_html5_parse=True)
try:
split_point = node_from_loc(root, loc_or_xpath, totals=totals)
except MalformedMarkup:
raise MalformedMarkup(_('The file %s has malformed markup. Try running the Fix HTML tool'
' before splitting') % name)
container.replace(name, root)
if in_table(split_point):
raise AbortError('Cannot split inside tables')
if split_point.tag.endswith('}body'):
raise AbortError('Cannot split on the <body> tag')
tree1, tree2 = do_split(split_point, container.log, before=before)
root1, root2 = tree1.getroot(), tree2.getroot()
anchors_in_top = frozenset(root1.xpath('//*/@id')) | frozenset(root1.xpath('//*/@name')) | {''}
anchors_in_bottom = frozenset(root2.xpath('//*/@id')) | frozenset(root2.xpath('//*/@name'))
base, ext = name.rpartition('.')[0::2]
base = re.sub(r'_split\d+$', '', base)
nname, s = None, 0 while not nname or container.exists(nname):
s += 1 nname = '%s_split%d.%s' % (base, s, ext)
manifest_item = container.generate_item(nname, media_type=container.mime_map[name])
bottom_name = container.href_to_name(manifest_item.get('href'), container.opf_name)

# Fix links in the split trees for r in (root1, root2):
for a in r.xpath('//*[@href]'):
url = a.get('href')
if url.startswith('#'):
fname = name else:
fname = container.href_to_name(url, name)
if fname == name:
purl = urlparse(url)
if purl.fragment in anchors_in_top:
if r is root2:
a.set('href', f'{container.name_to_href(name, bottom_name)}#{purl.fragment}')
else:
a.set('href', '#' + purl.fragment)
elif purl.fragment in anchors_in_bottom:
if r is root1:
a.set('href', f'{container.name_to_href(bottom_name, name)}#{purl.fragment}')
else:
a.set('href', '#' + purl.fragment)

# Fix all links in the container that point to anchors in the bottom tree for fname, media_type in iteritems(container.mime_map):
if fname not in {name, bottom_name}:
repl = SplitLinkReplacer(fname, anchors_in_bottom, name, bottom_name, container)
container.replace_links(fname, repl)

container.replace(name, root1)
container.replace(bottom_name, root2)

spine = container.opf_xpath('//opf:spine')[0]
for spine_item, spine_name, linear in container.spine_iter:
if spine_name == name:
break index = spine.index(spine_item) + 1 si = spine.makeelement(OPF('itemref'), idref=manifest_item.get('id'))
if not linear:
si.set('linear', 'no')
container.insert_into_xml(spine, si, index=index)
container.dirty(container.opf_name)
return bottom_name

[dokumentace]
def multisplit(container, name, xpath, before=True):
'''
Split the specified file at multiple locations (all tags that match the specified XPath expression). See also: :func:`split`.
Splitting automatically migrates all links and references to the affected files.

:param before: If True the splits occur before the identified element otherwise after it.
'''
root = container.parsed(name)
nodes = root.xpath(xpath, namespaces=XPNSMAP)
if not nodes:
raise AbortError(_('The expression %s did not match any nodes') % xpath)
for split_point in nodes:
if in_table(split_point):
raise AbortError('Cannot split inside tables')
if split_point.tag.endswith('}body'):
raise AbortError('Cannot split on the <body> tag')

for i, tag in enumerate(nodes):
tag.set('calibre-split-point', str(i))

current = name all_names = [name]
for i in range(len(nodes)):
current = split(container, current, '//*[@calibre-split-point="%d"]' % i, before=before)
all_names.append(current)

for x in all_names:
for tag in container.parsed(x).xpath('//*[@calibre-split-point]'):
tag.attrib.pop('calibre-split-point')
container.dirty(x)

return all_names[1:]

class MergeLinkReplacer:

def __init__(self, base, anchor_map, master, container):
self.container, self.anchor_map = container, anchor_map self.master = master self.base = base self.replaced = False def __call__(self, url):
if url and url.startswith('#'):
return url name = self.container.href_to_name(url, self.base)
amap = self.anchor_map.get(name, None)
if amap is None:
return url purl = urlparse(url)
frag = purl.fragment or ''
frag = amap.get(frag, frag)
url = self.container.name_to_href(self.master, self.base) + '#' + frag self.replaced = True return url def add_text(body, text):
if len(body) > 0:
body[-1].tail = (body[-1].tail or '') + text else:
body.text = (body.text or '') + text def all_anchors(root):
return set(root.xpath('//*/@id')) | set(root.xpath('//*/@name'))

def all_stylesheets(container, name):
for link in XPath('//h:head/h:link[@href]')(container.parsed(name)):
name = container.href_to_name(link.get('href'), name)
typ = link.get('type', 'text/css')
if typ == 'text/css':
yield name def unique_anchor(seen_anchors, current):
c = 0 ans = current while ans in seen_anchors:
c += 1 ans = '%s_%d' % (current, c)
return ans def remove_name_attributes(root):
# Remove all name attributes, replacing them with id attributes for elem in root.xpath('//*[@id and @name]'):
del elem.attrib['name']
for elem in root.xpath('//*[@name]'):
elem.set('id', elem.attrib.pop('name'))

def merge_html(container, names, master, insert_page_breaks=False):
p = container.parsed root = p(master)

# Ensure master has a <head> head = root.find('h:head', namespaces=XPNSMAP)
if head is None:
head = root.makeelement(XHTML('head'))
container.insert_into_xml(root, head, 0)

seen_anchors = all_anchors(root)
seen_stylesheets = set(all_stylesheets(container, master))
master_body = p(master).findall('h:body', namespaces=XPNSMAP)[-1]
master_base = os.path.dirname(master)
anchor_map = {n:{} for n in names if n != master}
first_anchor_map = {}

for name in names:
if name == master:
continue
# Insert new stylesheets into master for sheet in all_stylesheets(container, name):
if sheet not in seen_stylesheets:
seen_stylesheets.add(sheet)
link = head.makeelement(XHTML('link'), rel='stylesheet', type='text/css', href=container.name_to_href(sheet, master))
container.insert_into_xml(head, link)

# Rebase links if master is in a different directory if os.path.dirname(name) != master_base:
container.replace_links(name, LinkRebaser(container, name, master))

root = p(name)
children = []
for body in p(name).findall('h:body', namespaces=XPNSMAP):
children.append(body.text if body.text and body.text.strip() else '\n\n')
children.extend(body)

first_child = ''
for first_child in children:
if not isinstance(first_child, string_or_bytes):
break if isinstance(first_child, string_or_bytes):
# body contained only text, no tags first_child = body.makeelement(XHTML('p'))
first_child.text, children[0] = children[0], first_child amap = anchor_map[name]
remove_name_attributes(root)

for elem in root.xpath('//*[@id]'):
val = elem.get('id')
if not val:
continue if val in seen_anchors:
nval = unique_anchor(seen_anchors, val)
elem.set('id', nval)
amap[val] = nval else:
seen_anchors.add(val)

if 'id' not in first_child.attrib:
first_child.set('id', unique_anchor(seen_anchors, 'top'))
seen_anchors.add(first_child.get('id'))
first_anchor_map[name] = first_child.get('id')

if insert_page_breaks:
first_child.set('style', first_child.get('style', '') + '; page-break-before: always')

amap[''] = first_child.get('id')

# Fix links that point to local changed anchors for a in XPath('//h:a[starts-with(@href, "#")]')(root):
q = a.get('href')[1:]
if q in amap:
a.set('href', '#' + amap[q])

for child in children:
if isinstance(child, string_or_bytes):
add_text(master_body, child)
else:
master_body.append(copy.deepcopy(child))

container.remove_item(name, remove_from_guide=False)

# Fix all links in the container that point to merged files for fname, media_type in iteritems(container.mime_map):
repl = MergeLinkReplacer(fname, anchor_map, master, container)
container.replace_links(fname, repl)

return first_anchor_map def merge_css(container, names, master):
p = container.parsed msheet = p(master)
master_base = os.path.dirname(master)
merged = set()

for name in names:
if name == master:
continue
# Rebase links if master is in a different directory if os.path.dirname(name) != master_base:
container.replace_links(name, LinkRebaser(container, name, master))

sheet = p(name)

# Remove charset rules cr = [r for r in sheet.cssRules if r.type == r.CHARSET_RULE]
[sheet.deleteRule(sheet.cssRules.index(r)) for r in cr]
for rule in sheet.cssRules:
msheet.add(rule)

container.remove_item(name)
merged.add(name)

# Remove links to merged stylesheets in the html files, replacing with a
# link to the master sheet for name, mt in iteritems(container.mime_map):
if mt in OEB_DOCS:
removed = False root = p(name)
for link in XPath('//h:link[@href]')(root):
q = container.href_to_name(link.get('href'), name)
if q in merged:
container.remove_from_xml(link)
removed = True if removed:
container.dirty(name)
if removed and master not in set(all_stylesheets(container, name)):
head = root.find('h:head', namespaces=XPNSMAP)
if head is not None:
link = head.makeelement(XHTML('link'), type='text/css', rel='stylesheet', href=container.name_to_href(master, name))
container.insert_into_xml(head, link)

[dokumentace]
def merge(container, category, names, master):
'''
Merge the specified files into a single file, automatically migrating all links and references to the affected files. The file must all either be HTML or CSS files.

:param category: Must be either ``'text'`` for HTML files or ``'styles'`` for CSS files
:param names: The list of files to be merged
:param master: Which of the merged files is the *master* file, that is, the file that will remain after merging.
'''
if category not in {'text', 'styles'}:
raise AbortError('Cannot merge files of type: %s' % category)
if len(names) < 2:
raise AbortError('Must specify at least two files to be merged')
if master not in names:
raise AbortError('The master file (%s) must be one of the files being merged' % master)

if category == 'text':
merge_html(container, names, master)
elif category == 'styles':
merge_css(container, names, master)

container.dirty(master)

© Copyright Kovid Goyal.
Naposledy aktualizováno kvě 03, 2024.

Navigace

- moduly
- Spustit »
- Kód modulu »
- calibre.ebooks.oeb.polish.toc

Zdrojový kód pro calibre.ebooks.oeb.polish.toc

#!/usr/bin/env python

__license__ = 'GPL v3'
__copyright__ = '2013, Kovid Goyal <kovid at kovidgoyal.net>'
__docformat__ = 'restructuredtext en'

import re from collections import Counter, OrderedDict from functools import partial from operator import itemgetter from lxml import etree from lxml.builder import ElementMaker from calibre import __version__ from calibre.ebooks.oeb.base import EPUB_NS, NCX, NCX_NS, OEB_DOCS, XHTML, XHTML_NS, XML, XML_NS, XPath, serialize, uuid_id, xml2text from calibre.ebooks.oeb.polish.errors import MalformedMarkup from calibre.ebooks.oeb.polish.opf import get_book_language, set_guide_item from calibre.ebooks.oeb.polish.pretty import pretty_html_tree, pretty_xml_tree from calibre.ebooks.oeb.polish.utils import extract, guess_type from calibre.translations.dynamic import translate from calibre.utils.localization import canonicalize_lang, get_lang, lang_as_iso639_1 from calibre.utils.resources import get_path as P from polyglot.builtins import iteritems from polyglot.urllib import urlparse ns = etree.FunctionNamespace('calibre_xpath_extensions')
ns.prefix = 'calibre'
ns['lower-case'] = lambda c, x: x.lower() if hasattr(x, 'lower') else x class TOC:

toc_title = None def __init__(self, title=None, dest=None, frag=None):
self.title, self.dest, self.frag = title, dest, frag self.dest_exists = self.dest_error = None if self.title:
self.title = self.title.strip()
self.parent = None self.children = []
self.page_list = []

def add(self, title, dest, frag=None):
c = TOC(title, dest, frag)
self.children.append(c)
c.parent = self return c def remove(self, child):
self.children.remove(child)
child.parent = None def remove_from_parent(self):
if self.parent is None:
return idx = self.parent.children.index(self)
for child in reversed(self.children):
child.parent = self.parent self.parent.children.insert(idx, child)
self.parent.children.remove(self)
self.parent = None def __iter__(self):
yield from self.children def __len__(self):
return len(self.children)

def iterdescendants(self, level=None):
gc_level = None if level is None else level + 1 for child in self:
if level is None:
yield child else:
yield level, child yield from child.iterdescendants(level=gc_level)

def remove_duplicates(self, only_text=True):
seen = set()
remove = []
for child in self:
key = child.title if only_text else (child.title, child.dest, (child.frag or None))
if key in seen:
remove.append(child)
else:
seen.add(key)
child.remove_duplicates()
for child in remove:
self.remove(child)

@property def depth(self):
"""The maximum depth of the navigation tree rooted at this node."""
try:
return max(node.depth for node in self) + 1 except ValueError:
return 1

@property def last_child(self):
return self.children[-1] if self.children else None def get_lines(self, lvl=0):
frag = ('#'+self.frag) if self.frag else ''
ans = [('\t'*lvl) + 'TOC: %s --> %s%s'%(self.title, self.dest, frag)]
for child in self:
ans.extend(child.get_lines(lvl+1))
return ans def __str__(self):
return '\n'.join(self.get_lines())

def to_dict(self, node_counter=None):
ans = {
'title':self.title, 'dest':self.dest, 'frag':self.frag,
'children':[c.to_dict(node_counter) for c in self.children]
}
if self.dest_exists is not None:
ans['dest_exists'] = self.dest_exists if self.dest_error is not None:
ans['dest_error'] = self.dest_error if node_counter is not None:
ans['id'] = next(node_counter)
return ans

@property def as_dict(self):
return self.to_dict()

def child_xpath(tag, name):
return tag.xpath('./*[calibre:lower-case(local-name()) = "%s"]'%name)

def add_from_navpoint(container, navpoint, parent, ncx_name):
dest = frag = text = None nl = child_xpath(navpoint, 'navlabel')
if nl:
nl = nl[0]
text = ''
for txt in child_xpath(nl, 'text'):
text += etree.tostring(txt, method='text', encoding='unicode', with_tail=False)
content = child_xpath(navpoint, 'content')
if content:
content = content[0]
href = content.get('src', None)
if href:
dest = container.href_to_name(href, base=ncx_name)
frag = urlparse(href).fragment or None return parent.add(text or None, dest or None, frag or None)

def process_ncx_node(container, node, toc_parent, ncx_name):
for navpoint in node.xpath('./*[calibre:lower-case(local-name()) = "navpoint"]'):
child = add_from_navpoint(container, navpoint, toc_parent, ncx_name)
if child is not None:
process_ncx_node(container, navpoint, child, ncx_name)

def parse_ncx(container, ncx_name):
root = container.parsed(ncx_name)
toc_root = TOC()
navmaps = root.xpath('//*[calibre:lower-case(local-name()) = "navmap"]')
if navmaps:
process_ncx_node(container, navmaps[0], toc_root, ncx_name)
toc_root.lang = toc_root.uid = None for attr, val in iteritems(root.attrib):
if attr.endswith('lang'):
toc_root.lang = str(val)
break for uid in root.xpath('//*[calibre:lower-case(local-name()) = "meta" and @name="dtb:uid"]/@content'):
if uid:
toc_root.uid = str(uid)
break for pl in root.xpath('//*[calibre:lower-case(local-name()) = "pagelist"]'):
for pt in pl.xpath('descendant::*[calibre:lower-case(local-name()) = "pagetarget"]'):
pagenum = pt.get('value')
if pagenum:
href = pt.xpath('descendant::*[calibre:lower-case(local-name()) = "content"]/@src')
if href:
dest = container.href_to_name(href[0], base=ncx_name)
frag = urlparse(href[0]).fragment or None toc_root.page_list.append({'dest': dest, 'pagenum': pagenum, 'frag': frag})
return toc_root def add_from_li(container, li, parent, nav_name):
dest = frag = text = None for x in li.iterchildren(XHTML('a'), XHTML('span')):
text = etree.tostring(x, method='text', encoding='unicode', with_tail=False).strip() or ' '.join(x.xpath('descendant-or-self::*/@title')).strip()
href = x.get('href')
if href:
dest = nav_name if href.startswith('#') else container.href_to_name(href, base=nav_name)
frag = urlparse(href).fragment or None break return parent.add(text or None, dest or None, frag or None)

def first_child(parent, tagname):
try:
return next(parent.iterchildren(tagname))
except StopIteration:
return None def process_nav_node(container, node, toc_parent, nav_name):
for li in node.iterchildren(XHTML('li')):
child = add_from_li(container, li, toc_parent, nav_name)
ol = first_child(li, XHTML('ol'))
if child is not None and ol is not None:
process_nav_node(container, ol, child, nav_name)

def parse_nav(container, nav_name):
root = container.parsed(nav_name)
toc_root = TOC()
toc_root.lang = toc_root.uid = None et = '{%s}type' % EPUB_NS for nav in root.iterdescendants(XHTML('nav')):
if nav.get(et) == 'toc':
ol = first_child(nav, XHTML('ol'))
if ol is not None:
process_nav_node(container, ol, toc_root, nav_name)
for h in nav.iterchildren(*map(XHTML, 'h1 h2 h3 h4 h5 h6'.split())):
text = etree.tostring(h, method='text', encoding='unicode', with_tail=False) or h.get('title')
if text:
toc_root.toc_title = text break break return toc_root def verify_toc_destinations(container, toc):
anchor_map = {}
anchor_xpath = XPath('//*/@id|//h:a/@name')
for item in toc.iterdescendants():
name = item.dest if not name:
item.dest_exists = False item.dest_error = _('No file named %s exists')%name continue try:
root = container.parsed(name)
except KeyError:
item.dest_exists = False item.dest_error = _('No file named %s exists')%name continue if not hasattr(root, 'xpath'):
item.dest_exists = False item.dest_error = _('No HTML file named %s exists')%name continue if not item.frag:
item.dest_exists = True continue if name not in anchor_map:
anchor_map[name] = frozenset(anchor_xpath(root))
item.dest_exists = item.frag in anchor_map[name]
if not item.dest_exists:
item.dest_error = _(
'The anchor %(a)s does not exist in file %(f)s')%dict( a=item.frag, f=name)

def find_existing_ncx_toc(container):
toc = container.opf_xpath('//opf:spine/@toc')
if toc:
toc = container.manifest_id_map.get(toc[0], None)
if not toc:
ncx = guess_type('a.ncx')
toc = container.manifest_type_map.get(ncx, [None])[0]
return toc or None def find_existing_nav_toc(container):
for name in container.manifest_items_with_property('nav'):
return name def mark_as_nav(container, name):
if container.opf_version_parsed.major > 2:
container.apply_unique_properties(name, 'nav')

def get_x_toc(container, find_toc, parse_toc, verify_destinations=True):
def empty_toc():
ans = TOC()
ans.lang = ans.uid = None return ans toc = find_toc(container)
ans = empty_toc() if toc is None or not container.has_name(toc) else parse_toc(container, toc)
ans.toc_file_name = toc if toc and container.has_name(toc) else None if verify_destinations:
verify_toc_destinations(container, ans)
return ans def get_toc(container, verify_destinations=True):
ver = container.opf_version_parsed if ver.major < 3:
return get_x_toc(container, find_existing_ncx_toc, parse_ncx, verify_destinations=verify_destinations)
else:
ans = get_x_toc(container, find_existing_nav_toc, parse_nav, verify_destinations=verify_destinations)
if len(ans) == 0:
ans = get_x_toc(container, find_existing_ncx_toc, parse_ncx, verify_destinations=verify_destinations)
return ans def get_guide_landmarks(container):
for ref in container.opf_xpath('./opf:guide/opf:reference'):
href, title, rtype = ref.get('href'), ref.get('title'), ref.get('type')
href, frag = href.partition('#')[::2]
name = container.href_to_name(href, container.opf_name)
if container.has_name(name):
yield {'dest':name, 'frag':frag, 'title':title or '', 'type':rtype or ''}

def get_nav_landmarks(container):
nav = find_existing_nav_toc(container)
if nav and container.has_name(nav):
root = container.parsed(nav)
et = '{%s}type' % EPUB_NS for elem in root.iterdescendants(XHTML('nav')):
if elem.get(et) == 'landmarks':
for li in elem.iterdescendants(XHTML('li')):
for a in li.iterdescendants(XHTML('a')):
href, rtype = a.get('href'), a.get(et)
if href:
title = etree.tostring(a, method='text', encoding='unicode', with_tail=False).strip()
href, frag = href.partition('#')[::2]
name = container.href_to_name(href, nav)
if container.has_name(name):
yield {'dest':name, 'frag':frag, 'title':title or '', 'type':rtype or ''}
break def get_landmarks(container):
ver = container.opf_version_parsed if ver.major < 3:
return list(get_guide_landmarks(container))
ans = list(get_nav_landmarks(container))
if len(ans) == 0:
ans = list(get_guide_landmarks(container))
return ans def ensure_id(elem, all_ids):
elem_id = elem.get('id')
if elem_id:
return False, elem_id if elem.tag == XHTML('a'):
anchor = elem.get('name', None)
if anchor:
elem.set('id', anchor)
return False, anchor c = 0 while True:
c += 1 q = f'toc_{c}'
if q not in all_ids:
elem.set('id', q)
all_ids.add(q)
break return True, elem.get('id')

def elem_to_toc_text(elem, prefer_title=False):
text = xml2text(elem).strip()
if prefer_title:
text = elem.get('title', '').strip() or text if not text:
text = elem.get('title', '')
if not text:
text = elem.get('alt', '')
text = re.sub(r'\s+', ' ', text.strip())
text = text[:1000].strip()
if not text:
text = _('(Untitled)')
return text def item_at_top(elem):
try:
body = XPath('//h:body')(elem.getroottree().getroot())[0]
except (TypeError, IndexError, KeyError, AttributeError):
return False tree = body.getroottree()
path = tree.getpath(elem)
for el in body.iterdescendants(etree.Element):
epath = tree.getpath(el)
if epath == path:
break try:
if el.tag.endswith('}img') or (el.text and el.text.strip()):
return False except:
return False if not path.startswith(epath):
# Only check tail of non-parent elements if el.tail and el.tail.strip():
return False return True

[dokumentace]
def from_xpaths(container, xpaths, prefer_title=False):
'''
Generate a Table of Contents from a list of XPath expressions. Each expression in the list corresponds to a level of the generate ToC. For example: :code:`['//h:h1', '//h:h2', '//h:h3']` will generate a three level
Table of Contents from the ``<h1>``, ``<h2>`` and ``<h3>`` tags.
'''
tocroot = TOC()
xpaths = [XPath(xp) for xp in xpaths]

# Find those levels that have no elements in all spine items maps = OrderedDict()
empty_levels = {i+1 for i, xp in enumerate(xpaths)}
for spinepath in container.spine_items:
name = container.abspath_to_name(spinepath)
root = container.parsed(name)
level_item_map = maps[name] = {i+1:frozenset(xp(root)) for i, xp in enumerate(xpaths)}
for lvl, elems in iteritems(level_item_map):
if elems:
empty_levels.discard(lvl)
# Remove empty levels from all level_maps if empty_levels:
for name, lmap in tuple(iteritems(maps)):
lmap = {lvl:items for lvl, items in iteritems(lmap) if lvl not in empty_levels}
lmap = sorted(iteritems(lmap), key=itemgetter(0))
lmap = {i+1:items for i, (l, items) in enumerate(lmap)}
maps[name] = lmap node_level_map = {tocroot: 0}

def parent_for_level(child_level):
limit = child_level - 1 def process_node(node):
child = node.last_child if child is None:
return node lvl = node_level_map[child]
return node if lvl > limit else child if lvl == limit else process_node(child)

return process_node(tocroot)

for name, level_item_map in iteritems(maps):
root = container.parsed(name)
item_level_map = {e:i for i, elems in iteritems(level_item_map) for e in elems}
item_dirtied = False all_ids = set(root.xpath('//*/@id'))

for item in root.iterdescendants(etree.Element):
lvl = item_level_map.get(item, None)
if lvl is None:
continue text = elem_to_toc_text(item, prefer_title)
parent = parent_for_level(lvl)
if item_at_top(item):
dirtied, elem_id = False, None else:
dirtied, elem_id = ensure_id(item, all_ids)
item_dirtied = dirtied or item_dirtied toc = parent.add(text, name, elem_id)
node_level_map[toc] = lvl toc.dest_exists = True if item_dirtied:
container.commit_item(name, keep_parsed=True)

return tocroot[dokumentace]
def from_links(container):
'''
Generate a Table of Contents from links in the book.
'''
toc = TOC()
link_path = XPath('//h:a[@href]')
seen_titles, seen_dests = set(), set()
for name, is_linear in container.spine_names:
root = container.parsed(name)
for a in link_path(root):
href = a.get('href')
if not href or not href.strip():
continue frag = None if href.startswith('#'):
dest = name frag = href[1:]
else:
href, _, frag = href.partition('#')
dest = container.href_to_name(href, base=name)
frag = frag or None if (dest, frag) in seen_dests:
continue seen_dests.add((dest, frag))
text = elem_to_toc_text(a)
if text in seen_titles:
continue seen_titles.add(text)
toc.add(text, dest, frag=frag)
verify_toc_destinations(container, toc)
for child in toc:
if not child.dest_exists:
toc.remove(child)
return tocdef find_text(node):
LIMIT = 200 pat = re.compile(r'\s+')
for child in node:
if isinstance(child, etree._Element):
text = xml2text(child).strip()
text = pat.sub(' ', text)
if len(text) < 1:
continue if len(text) > LIMIT:
# Look for less text in a child of this node, recursively ntext = find_text(child)
return ntext or (text[:LIMIT] + '...')
else:
return text

[dokumentace]
def from_files(container):
'''
Generate a Table of Contents from files in the book.
'''
toc = TOC()
for i, spinepath in enumerate(container.spine_items):
name = container.abspath_to_name(spinepath)
root = container.parsed(name)
body = XPath('//h:body')(root)
if not body:
continue text = find_text(body[0])
if not text:
text = name.rpartition('/')[-1]
if i == 0 and text.rpartition('.')[0].lower() in {'titlepage', 'cover'}:
text = _('Cover')
toc.add(text, name)
return toc
def node_from_loc(root, locs, totals=None):
node = root.xpath('//*[local-name()="body"]')[0]
for i, loc in enumerate(locs):
children = tuple(node.iterchildren(etree.Element))
if totals is not None and totals[i] != len(children):
raise MalformedMarkup()
node = children[loc]
return node def add_id(container, name, loc, totals=None):
root = container.parsed(name)
try:
node = node_from_loc(root, loc, totals=totals)
except MalformedMarkup:
# The webkit HTML parser and the container parser have yielded
# different node counts, this can happen if the file is valid XML
# but contains constructs like nested <p> tags. So force parse it
# with the HTML 5 parser and try again.
raw = container.raw_data(name)
root = container.parse_xhtml(raw, fname=name, force_html5_parse=True)
try:
node = node_from_loc(root, loc, totals=totals)
except MalformedMarkup:
raise MalformedMarkup(_('The file %s has malformed markup. Try running the Fix HTML tool'
' before editing.') % name)
container.replace(name, root)

if not node.get('id'):
ensure_id(node, set(root.xpath('//*/@id')))
container.commit_item(name, keep_parsed=True)
return node.get('id')

def create_ncx(toc, to_href, btitle, lang, uid):
lang = lang.replace('_', '-')
ncx = etree.Element(NCX('ncx'), attrib={'version': '2005-1', XML('lang'): lang}, nsmap={None: NCX_NS})
head = etree.SubElement(ncx, NCX('head'))
etree.SubElement(head, NCX('meta'), name='dtb:uid', content=str(uid))
etree.SubElement(head, NCX('meta'), name='dtb:depth', content=str(toc.depth))
generator = ''.join(['calibre (', __version__, ')'])
etree.SubElement(head, NCX('meta'), name='dtb:generator', content=generator)
etree.SubElement(head, NCX('meta'), name='dtb:totalPageCount', content='0')
etree.SubElement(head, NCX('meta'), name='dtb:maxPageNumber', content='0')
title = etree.SubElement(ncx, NCX('docTitle'))
text = etree.SubElement(title, NCX('text'))
text.text = btitle navmap = etree.SubElement(ncx, NCX('navMap'))
spat = re.compile(r'\s+')

play_order = Counter()

def process_node(xml_parent, toc_parent):
for child in toc_parent:
play_order['c'] += 1 point = etree.SubElement(xml_parent, NCX('navPoint'), id='num_%d' % play_order['c'], playOrder=str(play_order['c']))
label = etree.SubElement(point, NCX('navLabel'))
title = child.title if title:
title = spat.sub(' ', title)
etree.SubElement(label, NCX('text')).text = title if child.dest:
href = to_href(child.dest)
if child.frag:
href += '#'+child.frag etree.SubElement(point, NCX('content'), src=href)
process_node(point, child)

process_node(navmap, toc)
return ncx def commit_ncx_toc(container, toc, lang=None, uid=None):
tocname = find_existing_ncx_toc(container)
if tocname is None:
item = container.generate_item('toc.ncx', id_prefix='toc')
tocname = container.href_to_name(item.get('href'), base=container.opf_name)
ncx_id = item.get('id')
[s.set('toc', ncx_id) for s in container.opf_xpath('//opf:spine')]
if not lang:
lang = get_lang()
for l in container.opf_xpath('//dc:language'):
l = canonicalize_lang(xml2text(l).strip())
if l:
lang = l lang = lang_as_iso639_1(l) or l break lang = lang_as_iso639_1(lang) or lang if not uid:
uid = uuid_id()
eid = container.opf.get('unique-identifier', None)
if eid:
m = container.opf_xpath('//*[@id="%s"]'%eid)
if m:
uid = xml2text(m[0])

title = _('Table of Contents')
m = container.opf_xpath('//dc:title')
if m:
x = xml2text(m[0]).strip()
title = x or title to_href = partial(container.name_to_href, base=tocname)
root = create_ncx(toc, to_href, title, lang, uid)
container.replace(tocname, root)
container.pretty_print.add(tocname)

def ensure_single_nav_of_type(root, ntype='toc'):
et = '{%s}type' % EPUB_NS navs = [n for n in root.iterdescendants(XHTML('nav')) if n.get(et) == ntype]
for x in navs[1:]:
extract(x)
if navs:
nav = navs[0]
tail = nav.tail attrib = dict(nav.attrib)
nav.clear()
nav.attrib.update(attrib)
nav.tail = tail else:
nav = root.makeelement(XHTML('nav'))
first_child(root, XHTML('body')).append(nav)
nav.set('{%s}type' % EPUB_NS, ntype)
return nav def ensure_container_has_nav(container, lang=None, previous_nav=None):
tocname = find_existing_nav_toc(container)
if previous_nav is not None:
nav_name = container.href_to_name(previous_nav[0])
if nav_name and container.exists(nav_name):
tocname = nav_name container.apply_unique_properties(tocname, 'nav')
if tocname is None:
item = container.generate_item('nav.xhtml', id_prefix='nav')
item.set('properties', 'nav')
tocname = container.href_to_name(item.get('href'), base=container.opf_name)
if previous_nav is not None:
root = previous_nav[1]
else:
root = container.parse_xhtml(P('templates/new_nav.html', data=True).decode('utf-8'))
container.replace(tocname, root)
else:
root = container.parsed(tocname)
if lang:
lang = lang_as_iso639_1(lang) or lang root.set('lang', lang)
root.set('{%s}lang' % XML_NS, lang)
return tocname, root def collapse_li(parent):
for li in parent.iterdescendants(XHTML('li')):
if len(li) == 1:
li.text = None li[0].tail = None def create_nav_li(container, ol, entry, tocname):
li = ol.makeelement(XHTML('li'))
ol.append(li)
a = li.makeelement(XHTML('a'))
li.append(a)
href = container.name_to_href(entry['dest'], tocname)
if entry['frag']:
href += '#' + entry['frag']
a.set('href', href)
return a def set_landmarks(container, root, tocname, landmarks):
nav = ensure_single_nav_of_type(root, 'landmarks')
nav.set('hidden', '')
ol = nav.makeelement(XHTML('ol'))
nav.append(ol)
for entry in landmarks:
if entry['type'] and container.has_name(entry['dest']) and container.mime_map[entry['dest']] in OEB_DOCS:
a = create_nav_li(container, ol, entry, tocname)
a.set('{%s}type' % EPUB_NS, entry['type'])
a.text = entry['title'] or None pretty_xml_tree(nav)
collapse_li(nav)

def commit_nav_toc(container, toc, lang=None, landmarks=None, previous_nav=None):
tocname, root = ensure_container_has_nav(container, lang=lang, previous_nav=previous_nav)
nav = ensure_single_nav_of_type(root, 'toc')
if toc.toc_title:
nav.append(nav.makeelement(XHTML('h1')))
nav[-1].text = toc.toc_title rnode = nav.makeelement(XHTML('ol'))
nav.append(rnode)
to_href = partial(container.name_to_href, base=tocname)
spat = re.compile(r'\s+')

def process_node(xml_parent, toc_parent):
for child in toc_parent:
li = xml_parent.makeelement(XHTML('li'))
xml_parent.append(li)
title = child.title or ''
title = spat.sub(' ', title).strip()
a = li.makeelement(XHTML('a' if child.dest else 'span'))
a.text = title li.append(a)
if child.dest:
href = to_href(child.dest)
if child.frag:
href += '#'+child.frag a.set('href', href)
if len(child):
ol = li.makeelement(XHTML('ol'))
li.append(ol)
process_node(ol, child)
process_node(rnode, toc)
pretty_xml_tree(nav)

collapse_li(nav)
nav.tail = '\n'

if toc.page_list:
nav = ensure_single_nav_of_type(root, 'page-list')
nav.set('hidden', '')
ol = nav.makeelement(XHTML('ol'))
nav.append(ol)
for entry in toc.page_list:
if container.has_name(entry['dest']) and container.mime_map[entry['dest']] in OEB_DOCS:
a = create_nav_li(container, ol, entry, tocname)
a.text = str(entry['pagenum'])
pretty_xml_tree(nav)
collapse_li(nav)
container.replace(tocname, root)

def commit_toc(container, toc, lang=None, uid=None):
commit_ncx_toc(container, toc, lang=lang, uid=uid)
if container.opf_version_parsed.major > 2:
commit_nav_toc(container, toc, lang=lang)

def remove_names_from_toc(container, names):
changed = []
names = frozenset(names)
for find_toc, parse_toc, commit_toc in (
(find_existing_ncx_toc, parse_ncx, commit_ncx_toc),
(find_existing_nav_toc, parse_nav, commit_nav_toc),
):
toc = get_x_toc(container, find_toc, parse_toc, verify_destinations=False)
if len(toc) > 0:
remove = []
for node in toc.iterdescendants():
if node.dest in names:
remove.append(node)
if remove:
for node in reversed(remove):
node.remove_from_parent()
commit_toc(container, toc)
changed.append(find_toc(container))
return changed def find_inline_toc(container):
for name, linear in container.spine_names:
if container.parsed(name).xpath('//*[local-name()="body" and @id="calibre_generated_inline_toc"]'):
return name def toc_to_html(toc, container, toc_name, title, lang=None):

def process_node(html_parent, toc, level=1, indent=' ', style_level=2):
li = html_parent.makeelement(XHTML('li'))
li.tail = '\n'+ (indent*level)
html_parent.append(li)
name, frag = toc.dest, toc.frag href = '#'
if name:
href = container.name_to_href(name, toc_name)
if frag:
href += '#' + frag a = li.makeelement(XHTML('a'), href=href)
a.text = toc.title li.append(a)
if len(toc) > 0:
parent = li.makeelement(XHTML('ul'))
parent.set('class', 'level%d' % (style_level))
li.append(parent)
a.tail = '\n\n' + (indent*(level+2))
parent.text = '\n'+(indent*(level+3))
parent.tail = '\n\n' + (indent*(level+1))
for child in toc:
process_node(parent, child, level+3, style_level=style_level + 1)
parent[-1].tail = '\n' + (indent*(level+2))

E = ElementMaker(namespace=XHTML_NS, nsmap={None:XHTML_NS})
html = E.html(
E.head(
E.title(title),
E.style(P('templates/inline_toc_styles.css', data=True).decode('utf-8'), type='text/css'),
),
E.body(
E.h2(title),
E.ul(), id="calibre_generated_inline_toc",
)
)

ul = html[1][1]
ul.set('class', 'level1')
for child in toc:
process_node(ul, child)
if lang:
html.set('lang', lang)
pretty_html_tree(container, html)
return html

[dokumentace]
def create_inline_toc(container, title=None):
'''
Create an inline (HTML) Table of Contents from an existing NCX Table of Contents.

:param title: The title for this table of contents.
'''
lang = get_book_language(container)
default_title = 'Table of Contents'
if lang:
lang = lang_as_iso639_1(lang) or lang default_title = translate(lang, default_title)
title = title or default_title toc = get_toc(container)
if len(toc) == 0:
return None toc_name = find_inline_toc(container)

name = toc_name html = toc_to_html(toc, container, name, title, lang)
raw = serialize(html, 'text/html')
if name is None:
name, c = 'toc.xhtml', 0 while container.has_name(name):
c += 1 name = 'toc%d.xhtml' % c container.add_file(name, raw, spine_index=0)
else:
with container.open(name, 'wb') as f:
f.write(raw)
set_guide_item(container, 'toc', title, name, frag='calibre_generated_inline_toc')
return name

© Copyright Kovid Goyal.
Naposledy aktualizováno kvě 03, 2024.

Navigace

- moduly
- Spustit »
- Kód modulu »
- calibre.gui2.actions

Zdrojový kód pro calibre.gui2.actions

#!/usr/bin/env python

__license__ = 'GPL v3'
__copyright__ = '2010, Kovid Goyal <kovid@kovidgoyal.net>'
__docformat__ = 'restructuredtext en'

from functools import partial from zipfile import ZipFile from qt.core import QAction, QIcon, QKeySequence, QMenu, QObject, QPoint, QTimer, QToolButton from calibre import prints from calibre.constants import ismacos from calibre.gui2 import Dispatcher from calibre.gui2.keyboard import NameConflict from polyglot.builtins import string_or_bytes def toolbar_widgets_for_action(gui, action):
# Search the toolbars for the widget associated with an action, passing
# them to the caller for further processing for x in gui.bars_manager.bars:
try:
w = x.widgetForAction(action)
# It seems that multiple copies of the action can exist, such as
# when the device-connected menu is changed while the device is
# connected. Use the one that has an actual position.
if w is None or w.pos().x() == 0:
continue
# The button might be hidden if not w.isVisible():
continue yield(w)
except Exception:
continue def show_menu_under_widget(gui, menu, action, name):
# First try the tool bar for w in toolbar_widgets_for_action(gui, action):
try:
# The w.height() assures that the menu opens below the button.
menu.exec(w.mapToGlobal(QPoint(0, w.height())))
return except Exception:
continue
# Now try the menu bar for x in gui.bars_manager.menu_bar.added_actions:
# This depends on no two menus with the same name.
# I don't know if this works on a Mac if x.text() == name:
try:
# The menu item might be hidden if not x.isVisible():
continue
# We can't use x.trigger() because it doesn't put the menu
# in the right place. Instead get the position of the menu
# widget on the menu bar p = x.parent().menu_bar r = p.actionGeometry(x)
# Make sure that the menu item is actually displayed in the menu
# and not the overflow if p.geometry().width() < (r.x() + r.width()):
continue
# Show the menu under the name in the menu bar menu.exec(p.mapToGlobal(QPoint(r.x()+2, r.height()-2)))
return except Exception:
continue
# No visible button found. Fall back to displaying in upper left corner
# of the library view.
menu.exec(gui.library_view.mapToGlobal(QPoint(10, 10)))

def menu_action_unique_name(plugin, unique_name):
return '%s : menu action : %s'%(plugin.unique_name, unique_name)

[dokumentace]
class InterfaceAction(QObject):

'''
A plugin representing an "action" that can be taken in the graphical user interface. All the items in the toolbar and context menus are implemented by these plugins.

Note that this class is the base class for these plugins, however, to integrate the plugin with calibre's plugin system, you have to make a wrapper class that references the actual plugin. See the
:mod:`calibre.customize.builtins` module for examples.

If two :class:`InterfaceAction` objects have the same name, the one with higher priority takes precedence.

Sub-classes should implement the :meth:`genesis`, :meth:`library_changed`,
:meth:`location_selected`, :meth:`shutting_down`,
:meth:`initialization_complete` and :meth:`tag_browser_context_action` methods.

Once initialized, this plugin has access to the main calibre GUI via the
:attr:`gui` member. You can access other plugins by name, for example::

self.gui.iactions['Save To Disk']

To access the actual plugin, use the :attr:`interface_action_base_plugin` attribute, this attribute only becomes available after the plugin has been initialized. Useful if you want to use methods from the plugin class like do_user_config().

The QAction specified by :attr:`action_spec` is automatically create and made available as ``self.qaction``.

'''

#: The plugin name. If two plugins with the same name are present, the one
#: with higher priority takes precedence.
name = 'Implement me'

#: The plugin priority. If two plugins with the same name are present, the one
#: with higher priority takes precedence.
priority = 1

#: The menu popup type for when this plugin is added to a toolbar popup_type = QToolButton.ToolButtonPopupMode.MenuButtonPopup

#: Whether this action should be auto repeated when its shortcut
#: key is held down.
auto_repeat = False

#: Of the form: (text, icon_path, tooltip, keyboard shortcut).
#: icon, tooltip and keyboard shortcut can be None.
#: keyboard shortcut must be either a string, None or tuple of shortcuts.
#: If None, a keyboard shortcut corresponding to the action is not
#: registered. If you pass an empty tuple, then the shortcut is registered
#: with no default key binding.
action_spec = ('text', 'icon', None, None)

#: If not None, used for the name displayed to the user when customizing
#: the keyboard shortcuts for the above action spec instead of action_spec[0]
action_shortcut_name = None

#: If True, a menu is automatically created and added to self.qaction action_add_menu = False

#: If True, a clone of self.qaction is added to the menu of self.qaction
#: If you want the text of this action to be different from that of
#: self.qaction, set this variable to the new text action_menu_clone_qaction = False

#: Set of locations to which this action must not be added.
#: See :attr:`all_locations` for a list of possible locations dont_add_to = frozenset()

#: Set of locations from which this action must not be removed.
#: See :attr:`all_locations` for a list of possible locations dont_remove_from = frozenset()

all_locations = frozenset(['toolbar', 'toolbar-device', 'context-menu',
'context-menu-device', 'toolbar-child', 'menubar', 'menubar-device',
'context-menu-cover-browser', 'context-menu-split', 'searchbar'])

#: Type of action
#: 'current' means acts on the current view
#: 'global' means an action that does not act on the current view, but rather
#: on calibre as a whole action_type = 'global'

#: If True, then this InterfaceAction will have the opportunity to interact
#: with drag and drop events. See the methods, :meth:`accept_enter_event`,
#: :meth`:accept_drag_move_event`, :meth:`drop_event` for details.
accepts_drops = False def __init__(self, parent, site_customization):
QObject.__init__(self, parent)
self.setObjectName(self.name)
self.gui = parent self.site_customization = site_customization self.interface_action_base_plugin = None

[dokumentace]
def accept_enter_event(self, event, mime_data):
''' This method should return True iff this interface action is capable of handling the drag event. Do not call accept/ignore on the event, that will be taken care of by the calibre UI.'''
return False

[dokumentace]
def accept_drag_move_event(self, event, mime_data):
''' This method should return True iff this interface action is capable of handling the drag event. Do not call accept/ignore on the event, that will be taken care of by the calibre UI.'''
return False

[dokumentace]
def drop_event(self, event, mime_data):
''' This method should perform some useful action and return True iff this interface action is capable of handling the drop event. Do not call accept/ignore on the event, that will be taken care of by the calibre UI. You should not perform blocking/long operations in this function. Instead emit a signal or use QTimer.singleShot and return quickly. See the builtin actions for examples.'''
return False
def do_genesis(self):
self.Dispatcher = partial(Dispatcher, parent=self)
self.create_action()
self.gui.addAction(self.qaction)
self.gui.addAction(self.menuless_qaction)
self.genesis()
self.location_selected('library')

@property def unique_name(self):
bn = self.__class__.__name__ if getattr(self.interface_action_base_plugin, 'name'):
bn = self.interface_action_base_plugin.name return 'Interface Action: %s (%s)'%(bn, self.name)

def create_action(self, spec=None, attr='qaction', shortcut_name=None, persist_shortcut=False):
if spec is None:
spec = self.action_spec text, icon, tooltip, shortcut = spec if icon is not None:
action = QAction(QIcon.ic(icon), text, self.gui)
else:
action = QAction(text, self.gui)
if attr == 'qaction':
if hasattr(self.action_menu_clone_qaction, 'rstrip'):
mt = str(self.action_menu_clone_qaction)
else:
mt = action.text()
self.menuless_qaction = ma = QAction(action.icon(), mt, self.gui)
ma.triggered.connect(action.trigger)
for a in ((action, ma) if attr == 'qaction' else (action,)):
a.setAutoRepeat(self.auto_repeat)
text = tooltip if tooltip else text a.setToolTip(text)
a.setStatusTip(text)
a.setWhatsThis(text)
shortcut_action = action desc = tooltip if tooltip else None if attr == 'qaction':
shortcut_action = ma if shortcut is not None:
keys = ((shortcut,) if isinstance(shortcut, string_or_bytes) else tuple(shortcut))
if shortcut_name is None:
if self.action_shortcut_name is not None:
shortcut_name = self.action_shortcut_name elif spec[0]:
shortcut_name = str(spec[0])
if shortcut_name and self.action_spec[0] and not ( attr == 'qaction' and self.popup_type == QToolButton.ToolButtonPopupMode.InstantPopup):
try:
self.gui.keyboard.register_shortcut(self.unique_name + ' - ' + attr, shortcut_name, default_keys=keys, action=shortcut_action, description=desc, group=self.action_spec[0], persist_shortcut=persist_shortcut)
except NameConflict as e:
try:
prints(str(e))
except:
pass shortcut_action.setShortcuts([QKeySequence(key,
QKeySequence.SequenceFormat.PortableText) for key in keys])
else:
self.shortcut_action_for_context_menu = shortcut_action if ismacos:
# In Qt 5 keyboard shortcuts dont work unless the
# action is explicitly added to the main window self.gui.addAction(shortcut_action)

if attr is not None:
setattr(self, attr, action)
if attr == 'qaction' and self.action_add_menu:
menu = QMenu()
action.setMenu(menu)
if self.action_menu_clone_qaction:
menu.addAction(self.menuless_qaction)
return action

[dokumentace]
def create_menu_action(self, menu, unique_name, text, icon=None, shortcut=None, description=None, triggered=None, shortcut_name=None, persist_shortcut=False):
'''
Convenience method to easily add actions to a QMenu.
Returns the created QAction. This action has one extra attribute calibre_shortcut_unique_name which if not None refers to the unique name under which this action is registered with the keyboard manager.

:param menu: The QMenu the newly created action will be added to
:param unique_name: A unique name for this action, this must be globally unique, so make it as descriptive as possible. If in doubt, add an UUID to it.
:param text: The text of the action.
:param icon: Either a QIcon or a file name. The file name is passed to the QIcon.ic() builtin, so you do not need to pass the full path to the images folder.
:param shortcut: A string, a list of strings, None or False. If False, no keyboard shortcut is registered for this action. If None, a keyboard shortcut with no default keybinding is registered. String and list of strings register a shortcut with default keybinding as specified.
:param description: A description for this action. Used to set tooltips.
:param triggered: A callable which is connected to the triggered signal of the created action.
:param shortcut_name: The text displayed to the user when customizing the keyboard shortcuts for this action. By default it is set to the value of ``text``.
:param persist_shortcut: Shortcuts for actions that don't always appear, or are library dependent, may disappear when other keyboard shortcuts are edited unless
```persist_shortcut``` is set True.

'''
if shortcut_name is None:
shortcut_name = str(text)
ac = menu.addAction(text)
if icon is not None:
if not isinstance(icon, QIcon):
icon = QIcon.ic(icon)
ac.setIcon(icon)
keys = ()
if shortcut is not None and shortcut is not False:
keys = ((shortcut,) if isinstance(shortcut, string_or_bytes) else tuple(shortcut))
unique_name = menu_action_unique_name(self, unique_name)
if description is not None:
ac.setToolTip(description)
ac.setStatusTip(description)
ac.setWhatsThis(description)

ac.calibre_shortcut_unique_name = unique_name if shortcut is not False:
self.gui.keyboard.register_shortcut(unique_name, shortcut_name, default_keys=keys, action=ac, description=description, group=self.action_spec[0], persist_shortcut=persist_shortcut)
# In Qt 5 keyboard shortcuts dont work unless the
# action is explicitly added to the main window and on OSX and
# Unity since the menu might be exported, the shortcuts won't work self.gui.addAction(ac)
if triggered is not None:
ac.triggered.connect(triggered)
return ac

[dokumentace]
def load_resources(self, names):
'''
If this plugin comes in a ZIP file (user added plugin), this method will allow you to load resources from the ZIP file.

For example to load an image::

pixmap = QPixmap()
pixmap.loadFromData(tuple(self.load_resources(['images/icon.png']).values())[0])
icon = QIcon(pixmap)

:param names: List of paths to resources in the ZIP file using / as separator

:return: A dictionary of the form ``{name : file_contents}``. Any names that were not found in the ZIP file will not be present in the dictionary.

'''
if self.plugin_path is None:
raise ValueError('This plugin was not loaded from a ZIP file')
ans = {}
with ZipFile(self.plugin_path, 'r') as zf:
for candidate in zf.namelist():
if candidate in names:
ans[candidate] = zf.read(candidate)
return ans

[dokumentace]
def genesis(self):
'''
Setup this plugin. Only called once during initialization. self.gui is available. The action specified by :attr:`action_spec` is available as
``self.qaction``.
'''
pass

[dokumentace]
def location_selected(self, loc):
'''
Called whenever the book list being displayed in calibre changes.
Currently values for loc are: ``library, main, card and cardb``.

This method should enable/disable this action and its sub actions as appropriate for the location.
'''
pass

[dokumentace]
def library_about_to_change(self, olddb, db):
'''
Called whenever the current library is changed.

:param olddb: The LibraryDatabase corresponding to the previous library.
:param db: The LibraryDatabase corresponding to the new library.

'''
pass

[dokumentace]
def library_changed(self, db):
'''
Called whenever the current library is changed.

:param db: The LibraryDatabase corresponding to the current library.

'''
pass

[dokumentace]
def gui_layout_complete(self):
'''
Called once per action when the layout of the main GUI is completed. If your action needs to make changes to the layout, they should be done here, rather than in :meth:`initialization_complete`.
'''
pass

[dokumentace]
def initialization_complete(self):
'''
Called once per action when the initialization of the main GUI is completed.
'''
pass

[dokumentace]
def tag_browser_context_action(self, index):
'''
Called when displaying the context menu in the Tag browser. ``index`` is the QModelIndex that points to the Tag browser item that was right clicked.
Test it for validity with index.valid() and get the underlying TagTreeItem object with index.data(Qt.ItemDataRole.UserRole). Any action objects yielded by this method will be added to the context menu.
'''
if False:
yield QAction()

[dokumentace]
def shutting_down(self):
'''
Called once per plugin when the main GUI is in the process of shutting down. Release any used resources, but try not to block the shutdown for long periods of time.
'''
pass
class InterfaceActionWithLibraryDrop(InterfaceAction):
'''
Subclass of InterfaceAction that implemente methods to execute the default action by drop some books from the library.

Inside the do_drop() method, the ids of the droped books are provided by the attribute self.dropped_ids
'''

accepts_drops = True mimetype_for_drop = 'application/calibre+from_library'

def accept_enter_event(self, event, mime_data):
if mime_data.hasFormat(self.mimetype_for_drop):
return True return False def accept_drag_move_event(self, event, mime_data):
if mime_data.hasFormat(self.mimetype_for_drop):
return True return False def drop_event(self, event, mime_data):
if mime_data.hasFormat(self.mimetype_for_drop):
self.dropped_ids = tuple(map(int, mime_data.data(self.mimetype_for_drop).data().split()))
QTimer.singleShot(1, self.do_drop)
return True return False def do_drop(self):
raise NotImplementedError()

© Copyright Kovid Goyal.
Naposledy aktualizováno kvě 03, 2024.

Navigace

- moduly
- Spustit »
- Kód modulu »
- calibre.gui2.preferences

Zdrojový kód pro calibre.gui2.preferences

#!/usr/bin/env python

__license__ = 'GPL v3'
__copyright__ = '2010, Kovid Goyal <kovid@kovidgoyal.net>'
__docformat__ = 'restructuredtext en'

import textwrap from qt.core import (
QAbstractSpinBox,
QApplication,
QCheckBox,
QComboBox,
QDialog,
QDialogButtonBox,
QEvent,
QIcon,
QLineEdit,
QListView,
QListWidget,
Qt,
QTableWidget,
QVBoxLayout,
QWidget, pyqtSignal,
)

from calibre.customize.ui import preferences_plugins from calibre.gui2.complete2 import EditWithComplete from calibre.gui2.widgets import HistoryLineEdit from calibre.utils.config import ConfigProxy from polyglot.builtins import string_or_bytes class AbortCommit(Exception):
pass class AbortInitialize(Exception):
pass

[dokumentace]
class ConfigWidgetInterface:

'''
This class defines the interface that all widgets displayed in the
Preferences dialog must implement. See :class:`ConfigWidgetBase` for a base class that implements this interface and defines various convenience methods as well.
'''

#: This signal must be emitted whenever the user changes a value in this
#: widget changed_signal = None

#: Set to True iff the :meth:`restore_to_defaults` method is implemented.
supports_restoring_to_defaults = True

#: The tooltip for the "Restore defaults" button restore_defaults_desc = _('Restore settings to default values. '
'You have to click Apply to actually save the default settings.')

#: If True the Preferences dialog will not allow the user to set any more
#: preferences. Only has effect if :meth:`commit` returns True.
restart_critical = False

[dokumentace]
def genesis(self, gui):
'''
Called once before the widget is displayed, should perform any necessary setup.

:param gui: The main calibre graphical user interface
'''
raise NotImplementedError()

[dokumentace]
def initialize(self):
'''
Should set all config values to their initial values (the values stored in the config files). A "return" statement is optional. Return
False if the dialog is not to be shown.
'''
raise NotImplementedError()

[dokumentace]
def restore_defaults(self):
'''
Should set all config values to their defaults.
'''
pass

[dokumentace]
def commit(self):
'''
Save any changed settings. Return True if the changes require a restart, False otherwise. Raise an :class:`AbortCommit` exception to indicate that an error occurred. You are responsible for giving the user feedback about what the error is and how to correct it.
'''
return False

[dokumentace]
def refresh_gui(self, gui):
'''
Called once after this widget is committed. Responsible for causing the gui to reread any changed settings. Note that by default the GUI re-initializes various elements anyway, so most widgets won't need to use this method.
'''
pass

[dokumentace]
def initial_tab_changed(self):
'''
Called if the initially displayed tab is changed before the widget is shown, but after it is initialized.
'''
pass

def set_help_tips(gui_obj, tt):
if tt:
if not str(gui_obj.whatsThis()):
gui_obj.setWhatsThis(tt)
if not str(gui_obj.statusTip()):
gui_obj.setStatusTip(tt)
tt = '\n'.join(textwrap.wrap(tt, 70))
gui_obj.setToolTip(tt)

class Setting:

CHOICES_SEARCH_FLAGS = Qt.MatchFlag.MatchExactly | Qt.MatchFlag.MatchCaseSensitive def __init__(self, name, config_obj, widget, gui_name=None, empty_string_is_None=True, choices=None, restart_required=False):
self.name, self.gui_name = name, gui_name self.empty_string_is_None = empty_string_is_None self.restart_required = restart_required self.choices = choices if gui_name is None:
self.gui_name = 'opt_'+name self.config_obj = config_obj self.gui_obj = getattr(widget, self.gui_name)
self.widget = widget if isinstance(self.gui_obj, QCheckBox):
self.datatype = 'bool'
self.gui_obj.stateChanged.connect(self.changed)
elif isinstance(self.gui_obj, QAbstractSpinBox):
self.datatype = 'number'
self.gui_obj.valueChanged.connect(self.changed)
elif isinstance(self.gui_obj, (QLineEdit, HistoryLineEdit)):
self.datatype = 'string'
self.gui_obj.textChanged.connect(self.changed)
if isinstance(self.gui_obj, HistoryLineEdit):
self.gui_obj.initialize('preferences_setting_' + self.name)
elif isinstance(self.gui_obj, QComboBox):
self.datatype = 'choice'
self.gui_obj.editTextChanged.connect(self.changed)
self.gui_obj.currentIndexChanged.connect(self.changed)
else:
raise ValueError('Unknown data type %s' % self.gui_obj.__class__)

if isinstance(self.config_obj, ConfigProxy) and \ not str(self.gui_obj.toolTip()):
h = self.config_obj.help(self.name)
if h:
self.gui_obj.setToolTip(h)
tt = str(self.gui_obj.toolTip())
set_help_tips(self.gui_obj, tt)

def changed(self, *args):
self.widget.changed_signal.emit()

def initialize(self):
self.gui_obj.blockSignals(True)
if self.datatype == 'choice':
choices = self.choices or []
if isinstance(self.gui_obj, EditWithComplete):
self.gui_obj.all_items = choices else:
self.gui_obj.clear()
for x in choices:
if isinstance(x, string_or_bytes):
x = (x, x)
self.gui_obj.addItem(x[0], (x[1]))
self.set_gui_val(self.get_config_val(default=False))
self.gui_obj.blockSignals(False)
self.initial_value = self.get_gui_val()

def commit(self):
val = self.get_gui_val()
oldval = self.get_config_val()
changed = val != oldval if changed:
self.set_config_val(self.get_gui_val())
return changed and self.restart_required def restore_defaults(self):
self.set_gui_val(self.get_config_val(default=True))

def get_config_val(self, default=False):
if default:
val = self.config_obj.defaults[self.name]
else:
val = self.config_obj[self.name]
return val def set_config_val(self, val):
self.config_obj[self.name] = val def set_gui_val(self, val):
if self.datatype == 'bool':
self.gui_obj.setChecked(bool(val))
elif self.datatype == 'number':
self.gui_obj.setValue(val)
elif self.datatype == 'string':
self.gui_obj.setText(val if val else '')
elif self.datatype == 'choice':
if isinstance(self.gui_obj, EditWithComplete):
self.gui_obj.setText(val)
else:
idx = self.gui_obj.findData((val), role=Qt.ItemDataRole.UserRole, flags=self.CHOICES_SEARCH_FLAGS)
if idx == -1:
idx = 0 self.gui_obj.setCurrentIndex(idx)

def get_gui_val(self):
if self.datatype == 'bool':
val = bool(self.gui_obj.isChecked())
elif self.datatype == 'number':
val = self.gui_obj.value()
elif self.datatype == 'string':
val = str(self.gui_obj.text()).strip()
if self.empty_string_is_None and not val:
val = None elif self.datatype == 'choice':
if isinstance(self.gui_obj, EditWithComplete):
val = str(self.gui_obj.text())
else:
idx = self.gui_obj.currentIndex()
if idx < 0:
idx = 0 val = str(self.gui_obj.itemData(idx) or '')
return val class CommaSeparatedList(Setting):

def set_gui_val(self, val):
x = ''
if val:
x = ', '.join(val)
self.gui_obj.setText(x)

def get_gui_val(self):
val = str(self.gui_obj.text()).strip()
ans = []
if val:
ans = [x.strip() for x in val.split(',')]
ans = [x for x in ans if x]
return ans

[dokumentace]
class ConfigWidgetBase(QWidget, ConfigWidgetInterface):

'''
Base class that contains code to easily add standard config widgets like checkboxes, combo boxes, text fields and so on. See the :meth:`register` method.

This class automatically handles change notification, resetting to default, translation between gui objects and config objects, etc. for registered settings.

If your config widget inherits from this class but includes setting that are not registered, you should override the :class:`ConfigWidgetInterface` methods and call the base class methods inside the overrides.
'''

changed_signal = pyqtSignal()
restart_now = pyqtSignal()
supports_restoring_to_defaults = True restart_critical = False def __init__(self, parent=None):
QWidget.__init__(self, parent)
if hasattr(self, 'setupUi'):
self.setupUi(self)
self.settings = {}

[dokumentace]
def register(self, name, config_obj, gui_name=None, choices=None, restart_required=False, empty_string_is_None=True, setting=Setting):
'''
Register a setting.

:param name: The setting name
:param config_obj: The config object that reads/writes the setting
:param gui_name: The name of the GUI object that presents an interface to change the setting. By default it is assumed to be
``'opt_' + name``.
:param choices: If this setting is a multiple choice (combobox) based setting, the list of choices. The list is a list of two element tuples of the form: ``[(gui name, value), ...]``
:param setting: The class responsible for managing this setting. The default class handles almost all cases, so this param is rarely used.
'''
setting = setting(name, config_obj, self, gui_name=gui_name, choices=choices, restart_required=restart_required, empty_string_is_None=empty_string_is_None)
return self.register_setting(setting)

def register_setting(self, setting):
self.settings[setting.name] = setting return setting

[dokumentace]
def initialize(self):
for setting in self.settings.values():
setting.initialize()

[dokumentace]
def commit(self, *args):
restart_required = False for setting in self.settings.values():
rr = setting.commit()
if rr:
restart_required = True return restart_required

[dokumentace]
def restore_defaults(self, *args):
for setting in self.settings.values():
setting.restore_defaults()

def get_plugin(category, name):
for plugin in preferences_plugins():
if plugin.category == category and plugin.name == name:
return plugin raise ValueError(
'No Preferences Plugin with category: %s and name: %s found' %
(category, name))

class ConfigDialog(QDialog):

def set_widget(self, w):
self.w = w def accept(self):
try:
self.restart_required = self.w.commit()
except AbortCommit:
return
QDialog.accept(self)

def init_gui():
from calibre.gui2.main import option_parser from calibre.gui2.ui import Main from calibre.library import db parser = option_parser()
opts, args = parser.parse_args([])
actions = tuple(Main.create_application_menubar())
db = db()
gui = Main(opts)
gui.initialize(db.library_path, db, actions, show_gui=False)
return gui def show_config_widget(category, name, gui=None, show_restart_msg=False, parent=None, never_shutdown=False):
'''
Show the preferences plugin identified by category and name

:param gui: gui instance, if None a hidden gui is created
:param show_restart_msg: If True and the preferences plugin indicates a restart is required, show a message box telling the user to restart
:param parent: The parent of the displayed dialog

:return: True iff a restart is required for the changes made by the user to take effect
'''
from calibre.gui2 import gprefs pl = get_plugin(category, name)
d = ConfigDialog(parent)
d.resize(750, 550)
conf_name = 'config_widget_dialog_geometry_%s_%s'%(category, name)
d.setWindowTitle(_('Configure ') + pl.gui_name)
d.setWindowIcon(QIcon.ic('config.png'))
bb = QDialogButtonBox(d)
bb.setStandardButtons(QDialogButtonBox.StandardButton.Apply|QDialogButtonBox.StandardButton.Cancel|QDialogButtonBox.StandardButton.RestoreDefaults)
bb.accepted.connect(d.accept)
bb.rejected.connect(d.reject)
w = pl.create_widget(d)
d.set_widget(w)
bb.button(QDialogButtonBox.StandardButton.RestoreDefaults).clicked.connect(w.restore_defaults)
bb.button(QDialogButtonBox.StandardButton.RestoreDefaults).setEnabled(w.supports_restoring_to_defaults)
bb.button(QDialogButtonBox.StandardButton.Apply).setEnabled(False)
bb.button(QDialogButtonBox.StandardButton.Apply).clicked.connect(d.accept)

def onchange():
b = bb.button(QDialogButtonBox.StandardButton.Apply)
b.setEnabled(True)
b.setDefault(True)
b.setAutoDefault(True)
w.changed_signal.connect(onchange)
bb.button(QDialogButtonBox.StandardButton.Cancel).setFocus(Qt.FocusReason.OtherFocusReason)
l = QVBoxLayout()
d.setLayout(l)
l.addWidget(w)
l.addWidget(bb)
mygui = gui is None if gui is None:
gui = init_gui()
mygui = True w.genesis(gui)
w.initialize()
d.restore_geometry(gprefs, conf_name)
d.exec()
d.save_geometry(gprefs, conf_name)
rr = getattr(d, 'restart_required', False)
if show_restart_msg and rr:
from calibre.gui2 import warning_dialog warning_dialog(gui, 'Restart required', 'Restart required', show=True)
if mygui and not never_shutdown:
gui.shutdown()
return rr class ListViewWithMoveByKeyPress(QListView):

def set_movement_functions(self, up_function, down_function):
self.up_function = up_function self.down_function = down_function def event(self, event):
if (event.type() == QEvent.KeyPress and
QApplication.keyboardModifiers() == Qt.KeyboardModifier.ControlModifier):
if event.key() == Qt.Key.Key_Up:
self.up_function()
elif event.key() == Qt.Key.Key_Down:
self.down_function()
return True return QListView.event(self, event)

class ListWidgetWithMoveByKeyPress(QListWidget):

def set_movement_functions(self, up_function, down_function):
self.up_function = up_function self.down_function = down_function def event(self, event):
if (event.type() == QEvent.KeyPress and
QApplication.keyboardModifiers() == Qt.KeyboardModifier.ControlModifier):
if event.key() == Qt.Key.Key_Up:
self.up_function()
elif event.key() == Qt.Key.Key_Down:
self.down_function()
return True return QListWidget.event(self, event)

class TableWidgetWithMoveByKeyPress(QTableWidget):

def set_movement_functions(self, up_function, down_function):
self.up_function = up_function self.down_function = down_function def event(self, event):
if (event.type() == QEvent.KeyPress and
QApplication.keyboardModifiers() == Qt.KeyboardModifier.ControlModifier):
if event.key() == Qt.Key.Key_Up:
self.up_function()
elif event.key() == Qt.Key.Key_Down:
self.down_function()
return True return QTableWidget.event(self, event)

# Testing {{{ def test_widget(category, name, gui=None):
show_config_widget(category, name, gui=gui, show_restart_msg=True)

def test_all():
from qt.core import QApplication app = QApplication([])
app gui = init_gui()
for plugin in preferences_plugins():
test_widget(plugin.category, plugin.name, gui=gui)
gui.shutdown()

if __name__ == '__main__':
test_all()
# }}}

© Copyright Kovid Goyal.
Naposledy aktualizováno kvě 03, 2024.

Navigace

- moduly
- Spustit »
- Kód modulu »
- calibre.gui2.tweak_book.boss

Zdrojový kód pro calibre.gui2.tweak_book.boss

#!/usr/bin/env python
# License: GPLv3 Copyright: 2013, Kovid Goyal <kovid at kovidgoyal.net> import errno import os import shutil import subprocess import sys import tempfile from functools import partial, wraps from qt.core import (
QApplication,
QCheckBox,
QDialog,
QDialogButtonBox,
QGridLayout,
QIcon,
QInputDialog,
QLabel,
QMimeData,
QObject,
QSize,
Qt,
QTimer,
QUrl,
QVBoxLayout, pyqtSignal,
)

from calibre import isbytestring, prints from calibre.constants import cache_dir, islinux, ismacos, iswindows from calibre.ebooks.oeb.base import urlnormalize from calibre.ebooks.oeb.polish.container import OEB_DOCS, OEB_STYLES, clone_container, guess_type from calibre.ebooks.oeb.polish.container import get_container as _gc from calibre.ebooks.oeb.polish.cover import mark_as_cover, mark_as_titlepage, set_cover from calibre.ebooks.oeb.polish.css import filter_css, rename_class from calibre.ebooks.oeb.polish.main import SUPPORTED, tweak_polish from calibre.ebooks.oeb.polish.pretty import fix_all_html, pretty_all from calibre.ebooks.oeb.polish.replace import get_recommended_folders, rationalize_folders, rename_files, replace_file from calibre.ebooks.oeb.polish.split import AbortError, merge, multisplit, split from calibre.ebooks.oeb.polish.toc import create_inline_toc, mark_as_nav, remove_names_from_toc from calibre.ebooks.oeb.polish.utils import link_stylesheets from calibre.ebooks.oeb.polish.utils import setup_css_parser_serialization as scs from calibre.gui2 import ( add_to_recent_docs, choose_dir, choose_files, choose_save_file, error_dialog, info_dialog, open_url, question_dialog, sanitize_env_vars, warning_dialog,
)
from calibre.gui2.dialogs.confirm_delete import confirm from calibre.gui2.tweak_book import actions, current_container, dictionaries, editor_name, editors, set_book_locale, set_current_container, tprefs from calibre.gui2.tweak_book.completion.worker import completion_worker from calibre.gui2.tweak_book.editor import editor_from_syntax, syntax_from_mime from calibre.gui2.tweak_book.editor.insert_resource import NewBook, get_resource_data from calibre.gui2.tweak_book.file_list import FILE_COPY_MIME, NewFileDialog from calibre.gui2.tweak_book.preferences import Preferences from calibre.gui2.tweak_book.preview import parse_worker from calibre.gui2.tweak_book.save import SaveManager, find_first_existing_ancestor, save_container from calibre.gui2.tweak_book.search import run_search, validate_search_request from calibre.gui2.tweak_book.spell import find_next as find_next_word from calibre.gui2.tweak_book.spell import find_next_error from calibre.gui2.tweak_book.toc import TOCEditor from calibre.gui2.tweak_book.undo import GlobalUndoHistory from calibre.gui2.tweak_book.widgets import (
AddCover,
FilterCSS,
ImportForeign,
InsertLink,
InsertSemantics,
InsertTag,
MultiSplit,
QuickOpen,
RationalizeFolders,
)
from calibre.gui2.widgets import BusyCursor from calibre.ptempfile import PersistentTemporaryDirectory, TemporaryDirectory from calibre.startup import connect_lambda from calibre.utils.config import JSONConfig from calibre.utils.icu import numeric_sort_key from calibre.utils.imghdr import identify from calibre.utils.ipc.launch import exe_path, macos_edit_book_bundle_path from calibre.utils.localization import ngettext from calibre.utils.tdir_in_cache import tdir_in_cache from polyglot.builtins import as_bytes, iteritems, itervalues, string_or_bytes from polyglot.urllib import urlparse

_diff_dialogs = []
last_used_transform_rules = []
last_used_html_transform_rules = []

def get_container(*args, **kwargs):
kwargs['tweak_mode'] = True container = _gc(*args, **kwargs)
return container def setup_css_parser_serialization():
scs(tprefs['editor_tab_stop_width'])

def in_thread_job(func):
@wraps(func)
def ans(*args, **kwargs):
with BusyCursor():
return func(*args, **kwargs)
return ans def get_boss():
return get_boss.boss

[dokumentace]
class Boss(QObject):

handle_completion_result_signal = pyqtSignal(object)

def __init__(self, parent, notify=None):
QObject.__init__(self, parent)
self.global_undo = GlobalUndoHistory()
self.file_was_readonly = False self.container_count = 0 self.tdir = None self.save_manager = SaveManager(parent, notify)
self.save_manager.report_error.connect(self.report_save_error)
self.save_manager.check_for_completion.connect(self.check_terminal_save)
self.doing_terminal_save = False self.ignore_preview_to_editor_sync = False setup_css_parser_serialization()
get_boss.boss = self self.gui = parent completion_worker().result_callback = self.handle_completion_result_signal.emit self.handle_completion_result_signal.connect(self.handle_completion_result, Qt.ConnectionType.QueuedConnection)
self.completion_request_count = 0 self.editor_cache = JSONConfig('editor-cache', base_path=cache_dir())
d = self.editor_cache.defaults d['edit_book_state'] = {}
d['edit_book_state_order'] = []

def __call__(self, gui):
self.gui = gui gui.message_popup.undo_requested.connect(self.do_global_undo)
fl = gui.file_list fl.delete_requested.connect(self.delete_requested)
fl.reorder_spine.connect(self.reorder_spine)
fl.rename_requested.connect(self.rename_requested)
fl.bulk_rename_requested.connect(self.bulk_rename_requested)
fl.edit_file.connect(self.edit_file_requested)
fl.merge_requested.connect(self.merge_requested)
fl.mark_requested.connect(self.mark_requested)
fl.export_requested.connect(self.export_requested)
fl.replace_requested.connect(self.replace_requested)
fl.link_stylesheets_requested.connect(self.link_stylesheets_requested)
fl.initiate_file_copy.connect(self.copy_files_to_clipboard)
fl.initiate_file_paste.connect(self.paste_files_from_clipboard)
fl.open_file_with.connect(self.open_file_with)
self.gui.central.current_editor_changed.connect(self.apply_current_editor_state)
self.gui.central.close_requested.connect(self.editor_close_requested)
self.gui.central.search_panel.search_triggered.connect(self.search)
self.gui.text_search.find_text.connect(self.find_text)
self.gui.preview.sync_requested.connect(self.sync_editor_to_preview)
self.gui.preview.split_start_requested.connect(self.split_start_requested)
self.gui.preview.split_requested.connect(self.split_requested)
self.gui.preview.link_clicked.connect(self.link_clicked)
self.gui.preview.render_process_restarted.connect(self.report_render_process_restart)
self.gui.preview.open_file_with.connect(self.open_file_with)
self.gui.preview.edit_file.connect(self.edit_file_requested)
self.gui.check_book.item_activated.connect(self.check_item_activated)
self.gui.check_book.check_requested.connect(self.check_requested)
self.gui.check_book.fix_requested.connect(self.fix_requested)
self.gui.toc_view.navigate_requested.connect(self.link_clicked)
self.gui.toc_view.refresh_requested.connect(self.commit_all_editors_to_container)
self.gui.image_browser.image_activated.connect(self.image_activated)
self.gui.checkpoints.revert_requested.connect(self.revert_requested)
self.gui.checkpoints.compare_requested.connect(self.compare_requested)
self.gui.saved_searches.run_saved_searches.connect(self.run_saved_searches)
self.gui.saved_searches.copy_search_to_search_panel.connect(self.gui.central.search_panel.paste_saved_search)
self.gui.central.search_panel.save_search.connect(self.save_search)
self.gui.central.search_panel.show_saved_searches.connect(self.show_saved_searches)
self.gui.spell_check.find_word.connect(self.find_word)
self.gui.spell_check.refresh_requested.connect(self.commit_all_editors_to_container)
self.gui.spell_check.word_replaced.connect(self.word_replaced)
self.gui.spell_check.word_ignored.connect(self.word_ignored)
self.gui.spell_check.change_requested.connect(self.word_change_requested)
self.gui.live_css.goto_declaration.connect(self.goto_style_declaration)
self.gui.manage_fonts.container_changed.connect(self.apply_container_update_to_gui)
self.gui.manage_fonts.embed_all_fonts.connect(self.manage_fonts_embed)
self.gui.manage_fonts.subset_all_fonts.connect(self.manage_fonts_subset)
self.gui.reports.edit_requested.connect(self.reports_edit_requested)
self.gui.reports.refresh_starting.connect(self.commit_all_editors_to_container)
self.gui.reports.delete_requested.connect(self.delete_requested)

def report_render_process_restart(self):
self.gui.show_status_message(_('The Qt WebEngine Render process crashed and has been restarted'))

@property def currently_editing(self):
' Return the name of the file being edited currently or None if no file is being edited '
return editor_name(self.gui.central.current_editor)

def preferences(self):
orig_spell = tprefs['inline_spell_check']
orig_size = tprefs['toolbar_icon_size']
p = Preferences(self.gui)
ret = p.exec()
if p.dictionaries_changed:
dictionaries.clear_caches()
dictionaries.initialize(force=True) # Reread user dictionaries if p.toolbars_changed:
self.gui.populate_toolbars()
for ed in itervalues(editors):
if hasattr(ed, 'populate_toolbars'):
ed.populate_toolbars()
if orig_size != tprefs['toolbar_icon_size']:
for ed in itervalues(editors):
if hasattr(ed, 'bars'):
for bar in ed.bars:
bar.setIconSize(QSize(tprefs['toolbar_icon_size'], tprefs['toolbar_icon_size']))

if ret == QDialog.DialogCode.Accepted:
setup_css_parser_serialization()
self.gui.apply_settings()
self.refresh_file_list()
self.gui.preview.start_refresh_timer()
if ret == QDialog.DialogCode.Accepted or p.dictionaries_changed:
for ed in itervalues(editors):
ed.apply_settings(dictionaries_changed=p.dictionaries_changed)
if orig_spell != tprefs['inline_spell_check']:
from calibre.gui2.tweak_book.editor.syntax.html import refresh_spell_check_status refresh_spell_check_status()
for ed in itervalues(editors):
try:
ed.editor.highlighter.rehighlight()
except AttributeError:
pass def mark_requested(self, name, action):
self.commit_dirty_opf()
c = current_container()
if action == 'cover':
mark_as_cover(current_container(), name)
elif action.startswith('titlepage:'):
action, move_to_start = action.partition(':')[0::2]
move_to_start = move_to_start == 'True'
mark_as_titlepage(current_container(), name, move_to_start=move_to_start)
elif action == 'nav':
mark_as_nav(current_container(), name)

if c.opf_name in editors:
editors[c.opf_name].replace_data(c.raw_data(c.opf_name))
self.gui.file_list.build(c)
self.set_modified()

def mkdtemp(self, prefix=''):
self.container_count += 1 return tempfile.mkdtemp(prefix='%s%05d-' % (prefix, self.container_count), dir=self.tdir)

def _check_before_open(self):
if self.gui.action_save.isEnabled():
if not question_dialog(self.gui, _('Unsaved changes'), _(
'The current book has unsaved changes. If you open a new book, they will be lost.'
' Are you sure you want to proceed?')):
return if self.save_manager.has_tasks:
return info_dialog(self.gui, _('Cannot open'),
_('The current book is being saved, you cannot open a new book until'
' the saving is completed'), show=True)
return True def new_book(self):
if not self._check_before_open():
return d = NewBook(self.gui)
if d.exec() == QDialog.DialogCode.Accepted:
fmt = d.fmt.lower()
path = choose_save_file(self.gui, 'edit-book-new-book', _('Choose file location'), filters=[(fmt.upper(), (fmt,))], all_files=False)
if path is not None:
if not path.lower().endswith('.' + fmt):
path = path + '.' + fmt from calibre.ebooks.oeb.polish.create import create_book create_book(d.mi, path, fmt=fmt)
self.open_book(path=path)

def import_book(self, path=None):
if not self._check_before_open():
return d = ImportForeign(self.gui)
if hasattr(path, 'rstrip'):
d.set_src(os.path.abspath(path))
if d.exec() == QDialog.DialogCode.Accepted:
for name in tuple(editors):
self.close_editor(name)
from calibre.ebooks.oeb.polish.import_book import import_book_as_epub src, dest = d.data self._clear_notify_data = True def func(src, dest, tdir):
import_book_as_epub(src, dest)
return get_container(dest, tdir=tdir)
self.gui.blocking_job('import_book', _('Importing book, please wait...'), self.book_opened, func, src, dest, tdir=self.mkdtemp())

[dokumentace]
def open_book(self, path=None, edit_file=None, clear_notify_data=True, open_folder=False, search_text=None):
'''
Open the e-book at ``path`` for editing. Will show an error if the e-book is not in a supported format or the current book has unsaved changes.

:param edit_file: The name of a file inside the newly opened book to start editing. Can also be a list of names.
'''
if isinstance(path, (list, tuple)) and path:
# Can happen from an file_event_hook on OS X when drag and dropping
# onto the icon in the dock or using open -a path = path[-1]
if not self._check_before_open():
return if not hasattr(path, 'rpartition'):
if open_folder:
path = choose_dir(self.gui, 'open-book-folder-for-tweaking', _('Choose book folder'))
if path:
path = [path]
else:
path = choose_files(self.gui, 'open-book-for-tweaking', _('Choose book'),
[(_('Books'), [x.lower() for x in SUPPORTED])], all_files=False, select_only_single_file=True)

if not path:
return path = path[0]

if not os.path.exists(path):
return error_dialog(self.gui, _('File not found'), _(
'The file %s does not exist.') % path, show=True)
isdir = os.path.isdir(path)
ext = path.rpartition('.')[-1].upper()
if ext not in SUPPORTED and not isdir:
from calibre.ebooks.oeb.polish.import_book import IMPORTABLE if ext.lower() in IMPORTABLE:
return self.import_book(path)
return error_dialog(self.gui, _('Unsupported format'),
_('Tweaking is only supported for books in the %s formats.'
' Convert your book to one of these formats first.') % _(' and ').join(sorted(SUPPORTED)), show=True)

self.file_was_readonly = not os.access(path, os.W_OK)
if self.file_was_readonly:
warning_dialog(self.gui, _('Read-only file'), _(
'The file {} is read-only. Saving changes to it will either fail or cause its permissions to be reset.').format(path), show=True)

for name in tuple(editors):
self.close_editor(name)
self.gui.preview.clear()
self.gui.live_css.clear()
self.container_count = -1 if self.tdir:
shutil.rmtree(self.tdir, ignore_errors=True)
# We use the cache dir rather than the temporary dir to try and prevent
# temp file cleaners from nuking ebooks. See https://bugs.launchpad.net/bugs/1740460 self.tdir = tdir_in_cache('ee')
self._edit_file_on_open = edit_file self._search_text_on_open = search_text self._clear_notify_data = clear_notify_data self.gui.blocking_job('open_book', _('Opening book, please wait...'), self.book_opened, get_container, path, tdir=self.mkdtemp())def book_opened(self, job):
ef = getattr(self, '_edit_file_on_open', None)
cn = getattr(self, '_clear_notify_data', True)
st = getattr(self, '_search_text_on_open', None)
self._edit_file_on_open = self._search_text_on_open = None if job.traceback is not None:
self.gui.update_status_bar_default_message()
if 'DRMError:' in job.traceback:
from calibre.gui2.dialogs.drm_error import DRMErrorMessage return DRMErrorMessage(self.gui).exec()
if 'ObfuscationKeyMissing:' in job.traceback:
return error_dialog(self.gui, _('Failed to open book'), _(
'Failed to open book, it has obfuscated fonts, but the obfuscation key is missing from the OPF.'
' Do an EPUB to EPUB conversion before trying to edit this book.'), show=True)

return error_dialog(self.gui, _('Failed to open book'),
_('Failed to open book, click "Show details" for more information.'), det_msg=job.traceback, show=True)
if cn:
self.save_manager.clear_notify_data()
self.gui.check_book.clear_at_startup()
self.gui.spell_check.clear_caches()
dictionaries.clear_ignored(), dictionaries.clear_caches()
parse_worker.clear()
container = job.result set_current_container(container)
completion_worker().clear_caches()
with BusyCursor():
self.current_metadata = self.gui.current_metadata = container.mi lang = container.opf_xpath('//dc:language/text()') or [self.current_metadata.language]
set_book_locale(lang[0])
self.global_undo.open_book(container)
self.gui.update_window_title()
self.gui.file_list.current_edited_name = None self.gui.file_list.build(container, preserve_state=False)
self.gui.action_save.setEnabled(False)
self.update_global_history_actions()
recent_books = list(tprefs.get('recent-books', []))
path = os.path.abspath(container.path_to_ebook)
if path in recent_books:
recent_books.remove(path)
self.gui.update_status_bar_default_message(path)
recent_books.insert(0, path)
tprefs['recent-books'] = recent_books[:10]
self.gui.update_recent_books()
if iswindows:
try:
add_to_recent_docs(path)
except Exception:
import traceback traceback.print_exc()
if ef:
if isinstance(ef, str):
ef = [ef]
for i in ef:
self.gui.file_list.request_edit(i)
else:
if tprefs['restore_book_state']:
self.restore_book_edit_state()
self.gui.toc_view.update_if_visible()
self.add_savepoint(_('Start of editing session'))
if st:
self.find_initial_text(st)

def update_editors_from_container(self, container=None, names=None):
c = container or current_container()
for name, ed in tuple(iteritems(editors)):
if c.has_name(name):
if names is None or name in names:
ed.replace_data(c.raw_data(name))
ed.is_synced_to_container = True else:
self.close_editor(name)

def refresh_file_list(self):
container = current_container()
self.gui.file_list.build(container)
completion_worker().clear_caches('names')

[dokumentace]
def apply_container_update_to_gui(self, mark_as_modified=True):
'''
Update all the components of the user interface to reflect the latest data in the current book container.

:param mark_as_modified: If True, the book will be marked as modified, so the user will be prompted to save it when quitting.
'''
self.refresh_file_list()
self.update_global_history_actions()
self.update_editors_from_container()
if mark_as_modified:
self.set_modified()
self.gui.toc_view.update_if_visible()
completion_worker().clear_caches()
self.gui.preview.start_refresh_timer()

@in_thread_job def delete_requested(self, spine_items, other_items):
self.add_savepoint(_('Before: Delete files'))
self.commit_dirty_opf()
c = current_container()
c.remove_from_spine(spine_items)
for name in other_items:
c.remove_item(name)
self.set_modified()
self.gui.file_list.delete_done(spine_items, other_items)
spine_names = [x for x, remove in spine_items if remove]
completion_worker().clear_caches('names')
items = spine_names + list(other_items)
for name in items:
if name in editors:
self.close_editor(name)
if not editors:
self.gui.preview.clear()
self.gui.live_css.clear()
changed = remove_names_from_toc(current_container(), spine_names + list(other_items))
if changed:
self.gui.toc_view.update_if_visible()
for toc in changed:
if toc and toc in editors:
editors[toc].replace_data(c.raw_data(toc))
if c.opf_name in editors:
editors[c.opf_name].replace_data(c.raw_data(c.opf_name))
self.gui.message_popup(ngettext(
'One file deleted', '{} files deleted', len(items)).format(len(items)))

def commit_dirty_opf(self):
c = current_container()
if c.opf_name in editors and not editors[c.opf_name].is_synced_to_container:
self.commit_editor_to_container(c.opf_name)
self.gui.update_window_title()

def reorder_spine(self, items):
if not self.ensure_book():
return self.add_savepoint(_('Before: Re-order text'))
c = current_container()
c.set_spine(items)
self.set_modified()
self.gui.file_list.build(current_container()) # needed as the linear flag may have changed on some items if c.opf_name in editors:
editors[c.opf_name].replace_data(c.raw_data(c.opf_name))
completion_worker().clear_caches('names')

def add_file(self):
if not self.ensure_book(_('You must first open a book to edit, before trying to create new files in it.')):
return self.commit_dirty_opf()
d = NewFileDialog(self.gui)
if d.exec() != QDialog.DialogCode.Accepted:
return added_name = self.do_add_file(d.file_name, d.file_data, using_template=d.using_template, edit_file=True)
if d.file_name.rpartition('.')[2].lower() in ('ttf', 'otf', 'woff'):
from calibre.gui2.tweak_book.manage_fonts import show_font_face_rule_for_font_file show_font_face_rule_for_font_file(d.file_data, added_name, self.gui)

def do_add_file(self, file_name, data, using_template=False, edit_file=False):
self.add_savepoint(_('Before: Add file %s') % self.gui.elided_text(file_name))
c = current_container()
adata = data.replace(b'%CURSOR%', b'') if using_template else data spine_index = c.index_in_spine(self.currently_editing or '')
if spine_index is not None:
spine_index += 1 try:
added_name = c.add_file(file_name, adata, spine_index=spine_index)
except:
self.rewind_savepoint()
raise self.gui.file_list.build(c)
self.gui.file_list.select_name(file_name)
if c.opf_name in editors:
editors[c.opf_name].replace_data(c.raw_data(c.opf_name))
mt = c.mime_map[file_name]
syntax = syntax_from_mime(file_name, mt)
if syntax and edit_file:
if using_template:
self.edit_file(file_name, syntax, use_template=data.decode('utf-8'))
else:
self.edit_file(file_name, syntax)
self.set_modified()
completion_worker().clear_caches('names')
return added_name def add_files(self):
if not self.ensure_book(_('You must first open a book to edit, before trying to create new files in it.')):
return files = choose_files(self.gui, 'tweak-book-bulk-import-files', _('Choose files'))
if files:
folder_map = get_recommended_folders(current_container(), files)
files = {x:('/'.join((folder, os.path.basename(x))) if folder else os.path.basename(x))
for x, folder in iteritems(folder_map)}
self.add_savepoint(_('Before Add files'))
c = current_container()
added_fonts = set()
for path in sorted(files, key=numeric_sort_key):
name = files[path]
i = 0 while c.exists(name) or c.manifest_has_name(name) or c.has_name_case_insensitive(name):
i += 1 name, ext = name.rpartition('.')[0::2]
name = '%s_%d.%s' % (name, i, ext)
try:
with open(path, 'rb') as f:
c.add_file(name, f.read())
except:
self.rewind_savepoint()
raise if name.rpartition('.')[2].lower() in ('ttf', 'otf', 'woff'):
added_fonts.add(name)
self.gui.file_list.build(c)
if c.opf_name in editors:
editors[c.opf_name].replace_data(c.raw_data(c.opf_name))
self.set_modified()
completion_worker().clear_caches('names')
if added_fonts:
from calibre.gui2.tweak_book.manage_fonts import show_font_face_rule_for_font_files show_font_face_rule_for_font_files(c, added_fonts, self.gui)

def add_cover(self):
if not self.ensure_book():
return d = AddCover(current_container(), self.gui)
d.import_requested.connect(self.do_add_file)
try:
if d.exec() == QDialog.DialogCode.Accepted and d.file_name is not None:
report = []
with BusyCursor():
self.add_savepoint(_('Before: Add cover'))
set_cover(current_container(), d.file_name, report.append, options={
'existing_image':True, 'keep_aspect':tprefs['add_cover_preserve_aspect_ratio']})
self.apply_container_update_to_gui()
finally:
d.import_requested.disconnect()

def ensure_book(self, msg=None):
msg = msg or _('No book is currently open. You must first open a book.')
if current_container() is None:
error_dialog(self.gui, _('No book open'), msg, show=True)
return False return True def edit_toc(self):
if not self.ensure_book(_('You must open a book before trying to edit the Table of Contents.')):
return self.add_savepoint(_('Before: Edit Table of Contents'))
d = TOCEditor(title=self.current_metadata.title, parent=self.gui)
if d.exec() != QDialog.DialogCode.Accepted:
self.rewind_savepoint()
return with BusyCursor():
self.set_modified()
self.update_editors_from_container()
self.gui.toc_view.update_if_visible()
self.gui.file_list.build(current_container())

def insert_inline_toc(self):
if not self.ensure_book():
return self.commit_all_editors_to_container()
self.add_savepoint(_('Before: Insert inline Table of Contents'))
name = create_inline_toc(current_container())
if name is None:
self.rewind_savepoint()
return error_dialog(self.gui, _('No Table of Contents'), _(
'Cannot create an inline Table of Contents as this book has no existing'
' Table of Contents. You must first create a Table of Contents using the'
' Edit Table of Contents tool.'), show=True)
self.apply_container_update_to_gui()
self.edit_file(name, 'html')

def polish(self, action, name, parent=None):
if not self.ensure_book():
return from calibre.gui2.tweak_book.polish import get_customization, show_report customization = get_customization(action, name, parent or self.gui)
if customization is None:
return with BusyCursor():
self.add_savepoint(_('Before: %s') % name)
try:
report, changed = tweak_polish(current_container(), {action:True}, customization=customization)
except:
self.rewind_savepoint()
raise if changed:
self.apply_container_update_to_gui()
self.gui.update_window_title()
if not changed:
self.rewind_savepoint()
show_report(changed, self.current_metadata.title, report, parent or self.gui, self.show_current_diff)

def transform_html(self):
global last_used_html_transform_rules if not self.ensure_book(_('You must first open a book in order to transform styles.')):
return from calibre.ebooks.html_transform_rules import transform_container from calibre.gui2.html_transform_rules import RulesDialog d = RulesDialog(self.gui)
d.rules = last_used_html_transform_rules d.transform_scope = tprefs['html_transform_scope']
ret = d.exec()
last_used_html_transform_rules = d.rules scope = d.transform_scope tprefs.set('html_transform_scope', scope)
if ret != QDialog.DialogCode.Accepted:
return mime_map = current_container().mime_map names = ()
if scope == 'current':
if not self.currently_editing or mime_map.get(self.currently_editing) not in OEB_DOCS:
return error_dialog(self.gui, _('No HTML file'), _('Not currently editing an HTML file'), show=True)
names = (self.currently_editing,)
elif scope == 'open':
names = tuple(name for name in editors if mime_map.get(name) in OEB_DOCS)
if not names:
return error_dialog(self.gui, _('No HTML files'), _('Not currently editing any HTML files'), show=True)
elif scope == 'selected':
names = tuple(name for name in self.gui.file_list.file_list.selected_names if mime_map.get(name) in OEB_DOCS)
if not names:
return error_dialog(self.gui, _('No HTML files'), _('No HTML files are currently selected in the File browser'), show=True)
with BusyCursor():
self.add_savepoint(_('Before HTML transformation'))
try:
changed = transform_container(current_container(), last_used_html_transform_rules, names)
except:
self.rewind_savepoint()
raise if changed:
self.apply_container_update_to_gui()
if not changed:
self.rewind_savepoint()
return info_dialog(self.gui, _('No changes'), _('No HTML was changed.'), show=True)
self.show_current_diff()

def transform_styles(self):
global last_used_transform_rules if not self.ensure_book(_('You must first open a book in order to transform styles.')):
return from calibre.ebooks.css_transform_rules import transform_container from calibre.gui2.css_transform_rules import RulesDialog d = RulesDialog(self.gui)
d.rules = last_used_transform_rules ret = d.exec()
last_used_transform_rules = d.rules if ret != QDialog.DialogCode.Accepted:
return with BusyCursor():
self.add_savepoint(_('Before style transformation'))
try:
changed = transform_container(current_container(), last_used_transform_rules)
except:
self.rewind_savepoint()
raise if changed:
self.apply_container_update_to_gui()
if not changed:
self.rewind_savepoint()
info_dialog(self.gui, _('No changes'), _(
'No styles were changed.'), show=True)
return self.show_current_diff()

def get_external_resources(self):
if not self.ensure_book(_('You must first open a book in order to transform styles.')):
return from calibre.gui2.tweak_book.download import DownloadResources with BusyCursor():
self.add_savepoint(_('Before: Get external resources'))
try:
d = DownloadResources(self.gui)
d.exec()
except Exception:
self.rewind_savepoint()
raise if d.resources_replaced:
self.apply_container_update_to_gui()
if d.show_diff:
self.show_current_diff()
else:
self.rewind_savepoint()

def manage_fonts(self):
if not self.ensure_book(_('No book is currently open. You must first open a book to manage fonts.')):
return self.commit_all_editors_to_container()
self.gui.manage_fonts.display()

def manage_fonts_embed(self):
self.polish('embed', _('Embed all fonts'), parent=self.gui.manage_fonts)
self.gui.manage_fonts.refresh()

def manage_fonts_subset(self):
self.polish('subset', _('Subset all fonts'), parent=self.gui.manage_fonts)

# Renaming {{{ def rationalize_folders(self):
if not self.ensure_book():
return c = current_container()
if not c.SUPPORTS_FILENAMES:
return error_dialog(self.gui, _('Not supported'),
_('The %s format does not support file and folder names internally, therefore'
' arranging files into folders is not allowed.') % c.book_type.upper(), show=True)
d = RationalizeFolders(self.gui)
if d.exec() != QDialog.DialogCode.Accepted:
return self.commit_all_editors_to_container()
name_map = rationalize_folders(c, d.folder_map)
if not name_map:
confirm(_(
'The files in this book are already arranged into folders'), 'already-arranged-into-folders', self.gui, pixmap='dialog_information.png', title=_('Nothing to do'), show_cancel_button=False, config_set=tprefs, confirm_msg=_('Show this message &again'))
return self.add_savepoint(_('Before: Arrange into folders'))
self.gui.blocking_job(
'rationalize_folders', _('Renaming and updating links...'), partial(self.rename_done, name_map), rename_files, current_container(), name_map)

def rename_requested(self, oldname, newname):
self.commit_all_editors_to_container()
if guess_type(oldname) != guess_type(newname):
args = os.path.splitext(oldname) + os.path.splitext(newname)
if not confirm(
_('You are changing the file type of {0}<b>{1}</b> to {2}<b>{3}</b>.'
' Doing so can cause problems, are you sure?').format(*args),
'confirm-filetype-change', parent=self.gui, title=_('Are you sure?'), config_set=tprefs):
return if urlnormalize(newname) != newname:
if not confirm(
_('The name you have chosen {0} contains special characters, internally'
' it will look like: {1}Try to use only the English alphabet [a-z], numbers [0-9],'
' hyphens and underscores for file names. Other characters can cause problems for '
' different e-book viewers. Are you sure you want to proceed?').format(
'<pre>%s</pre>'%newname, '<pre>%s</pre>' % urlnormalize(newname)),
'confirm-urlunsafe-change', parent=self.gui, title=_('Are you sure?'), config_set=tprefs):
return self.add_savepoint(_('Before: Rename %s') % oldname)
name_map = {oldname:newname}
self.gui.blocking_job(
'rename_file', _('Renaming and updating links...'), partial(self.rename_done, name_map, from_filelist=self.gui.file_list.current_name), rename_files, current_container(), name_map)

def bulk_rename_requested(self, name_map):
self.add_savepoint(_('Before: Bulk rename'))
self.gui.blocking_job(
'bulk_rename_files', _('Renaming and updating links...'), partial(self.rename_done, name_map, from_filelist=self.gui.file_list.current_name), rename_files, current_container(), name_map)

def rename_done(self, name_map, job, from_filelist=None):
if job.traceback is not None:
self.gui.file_list.restore_temp_names()
return error_dialog(self.gui, _('Failed to rename files'),
_('Failed to rename files, click "Show details" for more information.'), det_msg=job.traceback, show=True)
self.gui.file_list.build(current_container())
self.set_modified()
for oldname, newname in iteritems(name_map):
if oldname in editors:
editors[newname] = ed = editors.pop(oldname)
ed.change_document_name(newname)
self.gui.central.rename_editor(editors[newname], newname)
if self.gui.preview.current_name == oldname:
self.gui.preview.current_name = newname self.apply_container_update_to_gui()
if from_filelist:
self.gui.file_list.select_names(frozenset(itervalues(name_map)), current_name=name_map.get(from_filelist))
self.gui.file_list.file_list.setFocus(Qt.FocusReason.PopupFocusReason)

# }}}

# Global history {{{ def do_global_undo(self, *a):
container = self.global_undo.undo()
if container is not None:
set_current_container(container)
self.apply_container_update_to_gui()

def do_global_redo(self):
container = self.global_undo.redo()
if container is not None:
set_current_container(container)
self.apply_container_update_to_gui()

def update_global_history_actions(self):
gu = self.global_undo for x, text in (('undo', _('&Revert to')), ('redo', _('&Revert to'))):
ac = getattr(self.gui, 'action_global_%s' % x)
ac.setEnabled(getattr(gu, 'can_' + x))
ac.setText(text + ' "%s"'%(getattr(gu, x + '_msg') or '...'))

[dokumentace]
def add_savepoint(self, msg):
' Create a restore checkpoint with the name specified as ``msg`` '
self.commit_all_editors_to_container()
nc = clone_container(current_container(), self.mkdtemp())
self.global_undo.add_savepoint(nc, msg)
set_current_container(nc)
self.update_global_history_actions()

[dokumentace]
def rewind_savepoint(self):
' Undo the previous creation of a restore checkpoint, useful if you create a checkpoint, then abort the operation with no changes '
container = self.global_undo.rewind_savepoint()
if container is not None:
set_current_container(container)
self.update_global_history_actions()

def create_diff_dialog(self, revert_msg=_('&Revert changes'), show_open_in_editor=True):
global _diff_dialogs from calibre.gui2.tweak_book.diff.main import Diff def line_activated(name, lnum, right):
if right:
self.edit_file_requested(name, None, guess_type(name))
if name in editors:
editor = editors[name]
editor.go_to_line(lnum)
editor.setFocus(Qt.FocusReason.OtherFocusReason)
self.gui.raise_and_focus()
d = Diff(revert_button_msg=revert_msg, show_open_in_editor=show_open_in_editor)
[x.break_cycles() for x in _diff_dialogs if not x.isVisible()]
_diff_dialogs = [x for x in _diff_dialogs if x.isVisible()] + [d]
d.show(), d.raise_and_focus(), d.setFocus(Qt.FocusReason.OtherFocusReason), d.setWindowModality(Qt.WindowModality.NonModal)
if show_open_in_editor:
d.line_activated.connect(line_activated)
return d

[dokumentace]
def show_current_diff(self, allow_revert=True, to_container=None):
'''
Show the changes to the book from its last checkpointed state

:param allow_revert: If True the diff dialog will have a button to allow the user to revert all changes
:param to_container: A container object to compare the current container to. If None, the previously checkpointed container is used
'''
self.commit_all_editors_to_container()
k = {} if allow_revert else {'revert_msg': None}
d = self.create_diff_dialog(**k)
previous_container = self.global_undo.previous_container connect_lambda(d.revert_requested, self, lambda self: self.revert_requested(previous_container))
other = to_container or self.global_undo.previous_container d.container_diff(other, self.global_undo.current_container, names=(self.global_undo.label_for_container(other), self.global_undo.label_for_container(self.global_undo.current_container)))

def ask_to_show_current_diff(self, name, title, msg, allow_revert=True, to_container=None):
if tprefs.get('skip_ask_to_show_current_diff_for_' + name):
return d = QDialog(self.gui)
k = QVBoxLayout(d)
d.setWindowTitle(title)
k.addWidget(QLabel(msg))
k.confirm = cb = QCheckBox(_('Show this popup again'))
k.addWidget(cb)
cb.setChecked(True)
connect_lambda(cb.toggled, d, lambda d, checked: tprefs.set('skip_ask_to_show_current_diff_for_' + name, not checked))
d.bb = bb = QDialogButtonBox(QDialogButtonBox.StandardButton.Close, d)
k.addWidget(bb)
bb.accepted.connect(d.accept)
bb.rejected.connect(d.reject)
d.b = b = bb.addButton(_('See what &changed'), QDialogButtonBox.ButtonRole.AcceptRole)
b.setIcon(QIcon.ic('diff.png')), b.setAutoDefault(False)
bb.button(QDialogButtonBox.StandardButton.Close).setDefault(True)
if d.exec() == QDialog.DialogCode.Accepted:
self.show_current_diff(allow_revert=allow_revert, to_container=to_container)

def compare_book(self):
if not self.ensure_book():
return self.commit_all_editors_to_container()
c = current_container()
path = choose_files(self.gui, 'select-book-for-comparison', _('Choose book'), filters=[
(_('%s books') % c.book_type.upper(), (c.book_type,))], select_only_single_file=True, all_files=False)
if path and path[0]:
with TemporaryDirectory('_compare') as tdir:
other = _gc(path[0], tdir=tdir, tweak_mode=True)
d = self.create_diff_dialog(revert_msg=None)
d.container_diff(other, c, names=(_('Other book'), _('Current book')))

def revert_requested(self, container):
self.commit_all_editors_to_container()
nc = self.global_undo.revert_to(container)
set_current_container(nc)
self.apply_container_update_to_gui()

def compare_requested(self, container):
self.show_current_diff(to_container=container)

# }}}

[dokumentace]
def set_modified(self):
' Mark the book as having been modified '
self.gui.action_save.setEnabled(True)

def request_completion(self, name, completion_type, completion_data, query=None):
if completion_type is None:
completion_worker().clear_caches(completion_data)
return request_id = (self.completion_request_count, name)
self.completion_request_count += 1 completion_worker().queue_completion(request_id, completion_type, completion_data, query)
return request_id[0]

def handle_completion_result(self, result):
name = result.request_id[1]
editor = editors.get(name)
if editor is not None:
editor.handle_completion_result(result)

def fix_html(self, current):
if current:
ed = self.gui.central.current_editor if hasattr(ed, 'fix_html'):
ed.fix_html()
else:
with BusyCursor():
self.add_savepoint(_('Before: Fix HTML'))
fix_all_html(current_container())
self.update_editors_from_container()
self.set_modified()
self.ask_to_show_current_diff('html-fix', _('Fixing done'), _('All HTML files fixed'))

def pretty_print(self, current):
if current:
ed = self.gui.central.current_editor ed.pretty_print(editor_name(ed))
else:
with BusyCursor():
self.add_savepoint(_('Before: Beautify files'))
pretty_all(current_container())
self.update_editors_from_container()
self.set_modified()
QApplication.alert(self.gui)
self.ask_to_show_current_diff('beautify', _('Beautified'), _('All files beautified'))

def mark_selected_text(self):
ed = self.gui.central.current_editor if ed is not None:
ed.mark_selected_text()
if ed.has_marked_text:
self.gui.central.search_panel.set_where('selected-text')
else:
self.gui.central.search_panel.unset_marked()

def editor_action(self, action):
ed = self.gui.central.current_editor edname = editor_name(ed)
if hasattr(ed, 'action_triggered'):
if action and action[0] == 'insert_resource':
rtype = action[1]
if rtype == 'image' and ed.syntax not in {'css', 'html'}:
return error_dialog(self.gui, _('Not supported'), _(
'Inserting images is only supported for HTML and CSS files.'), show=True)
rdata = get_resource_data(rtype, self.gui)
if rdata is None:
return if rtype == 'image':
chosen_name, chosen_image_is_external, fullpage, preserve_ar = rdata if chosen_image_is_external:
with open(chosen_image_is_external[1], 'rb') as f:
current_container().add_file(chosen_image_is_external[0], f.read())
self.refresh_file_list()
chosen_name = chosen_image_is_external[0]
href = current_container().name_to_href(chosen_name, edname)
fmt, width, height = identify(current_container().raw_data(chosen_name, decode=False))
ed.insert_image(href, fullpage=fullpage, preserve_aspect_ratio=preserve_ar, width=width, height=height)
elif action[0] == 'insert_hyperlink':
self.commit_all_editors_to_container()
d = InsertLink(current_container(), edname, initial_text=ed.get_smart_selection(), parent=self.gui)
if d.exec() == QDialog.DialogCode.Accepted:
ed.insert_hyperlink(d.href, d.text, template=d.rendered_template)
elif action[0] == 'insert_tag':
d = InsertTag(parent=self.gui)
if d.exec() == QDialog.DialogCode.Accepted:
ed.insert_tag(d.tag)
else:
ed.action_triggered(action)

def rename_class(self, class_name):
self.commit_all_editors_to_container()
text, ok = QInputDialog.getText(self.gui, _('New class name'), _(
'Rename the class {} to?').format(class_name))
if ok:
self.add_savepoint(_('Before: Rename {}').format(class_name))
with BusyCursor():
changed = rename_class(current_container(), class_name, text.strip())
if changed:
self.apply_container_update_to_gui()
self.show_current_diff()
else:
self.rewind_savepoint()
return info_dialog(self.gui, _('No matches'), _(
'No class {} found to change').format(class_name), show=True)

def set_semantics(self):
if not self.ensure_book():
return self.commit_all_editors_to_container()
c = current_container()
if c.book_type == 'azw3':
return error_dialog(self.gui, _('Not supported'), _(
'Semantics are not supported for the AZW3 format.'), show=True)
d = InsertSemantics(c, parent=self.gui)
if d.exec() == QDialog.DialogCode.Accepted and d.changes:
self.add_savepoint(_('Before: Set Semantics'))
d.apply_changes(current_container())
self.apply_container_update_to_gui()

def filter_css(self):
self.commit_all_editors_to_container()
c = current_container()
ed = self.gui.central.current_editor current_name = editor_name(ed)
if current_name and c.mime_map[current_name] not in OEB_DOCS | OEB_STYLES:
current_name = None d = FilterCSS(current_name=current_name, parent=self.gui)
if d.exec() == QDialog.DialogCode.Accepted and d.filtered_properties:
self.add_savepoint(_('Before: Filter style information'))
with BusyCursor():
changed = filter_css(current_container(), d.filtered_properties, names=d.filter_names)
if changed:
self.apply_container_update_to_gui()
self.show_current_diff()
else:
self.rewind_savepoint()
return info_dialog(self.gui, _('No matches'), _(
'No matching style rules were found'), show=True)

def show_find(self):
self.gui.central.show_find()
ed = self.gui.central.current_editor if ed is not None and hasattr(ed, 'selected_text'):
text = ed.selected_text if text and text.strip():
self.gui.central.pre_fill_search(text)

def show_text_search(self):
self.gui.text_search_dock.show()
self.gui.text_search.find.setFocus(Qt.FocusReason.OtherFocusReason)

def search_action_triggered(self, action, overrides=None):
ss = self.gui.saved_searches.isVisible()
trigger_saved_search = ss and (not self.gui.central.search_panel.isVisible() or self.gui.saved_searches.has_focus())
if trigger_saved_search:
return self.gui.saved_searches.trigger_action(action, overrides=overrides)
self.search(action, overrides)

def run_saved_searches(self, searches, action):
ed = self.gui.central.current_editor name = editor_name(ed)
searchable_names = self.gui.file_list.searchable_names if not searches or not validate_search_request(name, searchable_names, getattr(ed, 'has_marked_text', False), searches[0], self.gui):
return ret = run_search(searches, action, ed, name, searchable_names, self.gui, self.show_editor, self.edit_file, self.show_current_diff, self.add_savepoint, self.rewind_savepoint, self.set_modified)
ed = ret is True and self.gui.central.current_editor if getattr(ed, 'has_line_numbers', False):
ed.editor.setFocus(Qt.FocusReason.OtherFocusReason)
else:
self.gui.saved_searches.setFocus(Qt.FocusReason.OtherFocusReason)

def search(self, action, overrides=None):
# Run a search/replace sp = self.gui.central.search_panel
# Ensure the search panel is visible sp.setVisible(True)
ed = self.gui.central.current_editor name = editor_name(ed)
state = sp.state if overrides:
state.update(overrides)
searchable_names = self.gui.file_list.searchable_names if not validate_search_request(name, searchable_names, getattr(ed, 'has_marked_text', False), state, self.gui):
return ret = run_search(state, action, ed, name, searchable_names, self.gui, self.show_editor, self.edit_file, self.show_current_diff, self.add_savepoint, self.rewind_savepoint, self.set_modified)
ed = ret is True and self.gui.central.current_editor if getattr(ed, 'has_line_numbers', False):
ed.editor.setFocus(Qt.FocusReason.OtherFocusReason)
else:
self.gui.saved_searches.setFocus(Qt.FocusReason.OtherFocusReason)

def find_text(self, state):
from calibre.gui2.tweak_book.text_search import run_text_search searchable_names = self.gui.file_list.searchable_names ed = self.gui.central.current_editor name = editor_name(ed)
if not validate_search_request(name, searchable_names, getattr(ed, 'has_marked_text', False), state, self.gui):
return ret = run_text_search(state, ed, name, searchable_names, self.gui, self.show_editor, self.edit_file)
ed = ret is True and self.gui.central.current_editor if getattr(ed, 'has_line_numbers', False):
ed.editor.setFocus(Qt.FocusReason.OtherFocusReason)

def find_initial_text(self, text):
from calibre.gui2.tweak_book.search import get_search_regex from calibre.gui2.tweak_book.text_search import file_matches_pattern search = {'find': text, 'mode': 'normal', 'case_sensitive': True, 'direction': 'down'}
pat = get_search_regex(search)
searchable_names = set(self.gui.file_list.searchable_names['text'])
for name, ed in iteritems(editors):
searchable_names.discard(name)
if ed.find_text(pat, complete=True):
self.show_editor(name)
return for name in searchable_names:
if file_matches_pattern(name, pat):
self.edit_file(name)
if editors[name].find_text(pat, complete=True):
return def find_word(self, word, locations):
# Go to a word from the spell check dialog ed = self.gui.central.current_editor name = editor_name(ed)
find_next_word(word, locations, ed, name, self.gui, self.show_editor, self.edit_file)

def next_spell_error(self):
# Go to the next spelling error ed = self.gui.central.current_editor name = editor_name(ed)
find_next_error(ed, name, self.gui, self.show_editor, self.edit_file, self.close_editor)

def word_change_requested(self, w, new_word):
if self.commit_all_editors_to_container():
self.gui.spell_check.change_word_after_update(w, new_word)
else:
self.gui.spell_check.do_change_word(w, new_word)

def word_replaced(self, changed_names):
self.set_modified()
self.update_editors_from_container(names=set(changed_names))

def word_ignored(self, word, locale):
if tprefs['inline_spell_check']:
for ed in itervalues(editors):
try:
ed.editor.recheck_word(word, locale)
except AttributeError:
pass def editor_link_clicked(self, url):
ed = self.gui.central.current_editor name = editor_name(ed)
if url.startswith('#'):
target = name else:
target = current_container().href_to_name(url, name)
frag = url.partition('#')[-1]
if current_container().has_name(target):
self.link_clicked(target, frag, show_anchor_not_found=True)
else:
try:
purl = urlparse(url)
except ValueError:
return if purl.scheme not in {'', 'file'}:
open_url(QUrl(url))
else:
error_dialog(self.gui, _('Not found'), _(
'No file with the name %s was found in the book') % target, show=True)

def editor_class_clicked(self, class_data):
from calibre.gui2.tweak_book.jump_to_class import NoMatchingRuleFound, NoMatchingTagFound, find_first_matching_rule ed = self.gui.central.current_editor name = editor_name(ed)
try:
res = find_first_matching_rule(current_container(), name, ed.get_raw_data(), class_data)
except (NoMatchingTagFound, NoMatchingRuleFound):
res = None if res is not None and res.file_name and res.rule_address:
editor = self.open_editor_for_name(res.file_name)
if editor:
editor.goto_css_rule(res.rule_address, sourceline_address=res.style_tag_address)
else:
error_dialog(self.gui, _('No matches found'), _('No style rules that match the class {} were found').format( class_data['class']), show=True)

def save_search(self):
state = self.gui.central.search_panel.state self.show_saved_searches()
self.gui.saved_searches.add_predefined_search(state)

def show_saved_searches(self):
self.gui.saved_searches_dock.show()
saved_searches = show_saved_searches def create_checkpoint(self):
text, ok = QInputDialog.getText(self.gui, _('Choose name'), _(
'Choose a name for the checkpoint.\nYou can later restore the book'
' to this checkpoint via the\n"Revert to..." entries in the Edit menu.'))
if ok:
self.add_savepoint(text)

def commit_editor_to_container(self, name, container=None):
container = container or current_container()
ed = editors[name]
with container.open(name, 'wb') as f:
f.write(ed.data)
if name == container.opf_name:
container.refresh_mime_map()
lang = container.opf_xpath('//dc:language/text()') or [self.current_metadata.language]
set_book_locale(lang[0])
if container is current_container():
ed.is_synced_to_container = True if name == container.opf_name:
self.gui.file_list.build(container)

[dokumentace]
def commit_all_editors_to_container(self):
''' Commit any changes that the user has made to files open in editors to the container. You should call this method before performing any actions on the current container '''
changed = False with BusyCursor():
for name, ed in iteritems(editors):
if not ed.is_synced_to_container:
self.commit_editor_to_container(name)
ed.is_synced_to_container = True changed = True return changed

[dokumentace]
def save_book(self):
' Save the book. Saving is performed in the background '
self.gui.update_window_title()
c = current_container()
for name, ed in iteritems(editors):
if ed.is_modified or not ed.is_synced_to_container:
self.commit_editor_to_container(name, c)
ed.is_modified = False path_to_ebook = os.path.abspath(c.path_to_ebook)
destdir = os.path.dirname(path_to_ebook)
if not c.is_dir and not os.path.exists(destdir):
info_dialog(self.gui, _('Path does not exist'), _(
'The file you are editing (%s) no longer exists. You have to choose a new save location.') % path_to_ebook, show_copy_button=False, show=True)
fmt = path_to_ebook.rpartition('.')[-1].lower()
start_dir = find_first_existing_ancestor(path_to_ebook)
path = choose_save_file( self.gui, 'choose-new-save-location', _('Choose file location'), initial_path=os.path.join(start_dir, os.path.basename(path_to_ebook)), filters=[(fmt.upper(), (fmt,))], all_files=False)
if path is not None:
if not path.lower().endswith('.' + fmt):
path = path + '.' + fmt path = os.path.abspath(path)
c.path_to_ebook = path self.global_undo.update_path_to_ebook(path)
else:
return if os.path.exists(c.path_to_ebook) and not os.access(c.path_to_ebook, os.W_OK):
if not question_dialog(self.gui, _('File is read-only'), _(
'The file at {} is read-only. The editor will try to reset its permissions before saving. Proceed with saving?'
).format(c.path_to_ebook), override_icon='dialog_warning.png', yes_text=_('&Save'), no_text=_('&Cancel'), yes_icon='save.png'):
return self.gui.action_save.setEnabled(False)
tdir = self.mkdtemp(prefix='save-')
container = clone_container(c, tdir)
self.save_manager.schedule(tdir, container)def _save_copy(self, post_action=None):
self.gui.update_window_title()
c = current_container()
if c.is_dir:
return error_dialog(self.gui, _('Cannot save a copy'), _(
'Saving a copy of a folder based book is not supported'), show=True)
ext = c.path_to_ebook.rpartition('.')[-1]
path = choose_save_file( self.gui, 'tweak_book_save_copy', _('Choose path'), initial_filename=self.current_metadata.title + '.' + ext, filters=[(_('Book (%s)') % ext.upper(), [ext.lower()])], all_files=False)
if not path:
return if '.' not in os.path.basename(path):
path += '.' + ext.lower()
tdir = self.mkdtemp(prefix='save-copy-')
container = clone_container(c, tdir)
for name, ed in iteritems(editors):
if ed.is_modified or not ed.is_synced_to_container:
self.commit_editor_to_container(name, container)

def do_save(c, path, tdir):
save_container(c, path)
shutil.rmtree(tdir, ignore_errors=True)
return path self.gui.blocking_job('save_copy', _('Saving copy, please wait...'), partial(self.copy_saved, post_action=post_action), do_save, container, path, tdir)

def save_copy(self):
self._save_copy()

def copy_saved(self, job, post_action=None):
if job.traceback is not None:
return error_dialog(self.gui, _('Failed to save copy'),
_('Failed to save copy, click "Show details" for more information.'), det_msg=job.traceback, show=True)
msg = _('Copy saved to %s') % job.result if post_action is None:
info_dialog(self.gui, _('Copy saved'), msg, show=True)
elif post_action == 'replace':
msg = _('Editing saved copy at: %s') % job.result self.open_book(job.result, edit_file=self.currently_editing)
elif post_action == 'edit':
if ismacos:
cmd = ['open', '-F', '-n', '-a', os.path.dirname(os.path.dirname(os.path.dirname(macos_edit_book_bundle_path()))), '--args']
else:
cmd = [exe_path('ebook-edit')]
if islinux:
cmd.append('--detach')
cmd.append(job.result)
ce = self.currently_editing if ce:
cmd.append(ce)
with sanitize_env_vars():
subprocess.Popen(cmd)
self.gui.show_status_message(msg, 5)

def report_save_error(self, tb):
if self.doing_terminal_save:
prints(tb, file=sys.stderr)
self.abort_terminal_save()
self.set_modified()
error_dialog(self.gui, _('Could not save'),
_('Saving of the book failed. Click "Show details"'
' for more information. You can try to save a copy'
' to a different location, via File->Save a copy'), det_msg=tb, show=True)

def go_to_line_number(self):
ed = self.gui.central.current_editor if ed is None or not ed.has_line_numbers:
return num, ok = QInputDialog.getInt(self.gui, _('Enter line number'), ('Line number:'), ed.current_line, 1, max(100000, ed.number_of_lines))
if ok:
ed.current_line = num def split_start_requested(self):
self.commit_all_editors_to_container()
self.gui.preview.do_start_split()

@in_thread_job def split_requested(self, name, loc, totals):
self.add_savepoint(_('Before: Split %s') % self.gui.elided_text(name))
try:
bottom_name = split(current_container(), name, loc, totals=totals)
except AbortError:
self.rewind_savepoint()
raise self.apply_container_update_to_gui()
self.edit_file(bottom_name, 'html')

def multisplit(self):
ed = self.gui.central.current_editor if ed.syntax != 'html':
return name = editor_name(ed)
if name is None:
return d = MultiSplit(self.gui)
if d.exec() == QDialog.DialogCode.Accepted:
with BusyCursor():
self.add_savepoint(_('Before: Split %s') % self.gui.elided_text(name))
try:
multisplit(current_container(), name, d.xpath)
except AbortError:
self.rewind_savepoint()
raise self.apply_container_update_to_gui()

def open_editor_for_name(self, name):
if name in editors:
editor = editors[name]
self.gui.central.show_editor(editor)
else:
try:
mt = current_container().mime_map[name]
except KeyError:
error_dialog(self.gui, _('Does not exist'), _(
'The file %s does not exist. If you were trying to click an item in'
' the Table of Contents, you may'
' need to refresh it by right-clicking and choosing "Refresh".') % name, show=True)
return None syntax = syntax_from_mime(name, mt)
if not syntax:
error_dialog( self.gui, _('Unsupported file format'),
_('Editing files of type %s is not supported') % mt, show=True)
return None editor = self.edit_file(name, syntax)
return editor def link_clicked(self, name, anchor, show_anchor_not_found=False):
if not name:
return editor = self.open_editor_for_name(name)
if anchor and hasattr(editor, 'go_to_anchor') :
if editor.go_to_anchor(anchor):
self.gui.preview.pending_go_to_anchor = anchor elif show_anchor_not_found:
error_dialog(self.gui, _('Not found'), _(
'The anchor %s was not found in this file') % anchor, show=True)

@in_thread_job def check_item_activated(self, item):
is_mult = item.has_multiple_locations and getattr(item, 'current_location_index', None) is not None name = item.all_locations[item.current_location_index][0] if is_mult else item.name editor = None if name in editors:
editor = editors[name]
self.gui.central.show_editor(editor)
else:
try:
editor = self.edit_file_requested(name, None, current_container().mime_map[name])
except KeyError:
error_dialog(self.gui, _('File deleted'), _(
'The file {} has already been deleted, re-run Check Book to update the results.').format(name), show=True)
if getattr(editor, 'has_line_numbers', False):
if is_mult:
editor.go_to_line(*(item.all_locations[item.current_location_index][1:3]))
else:
editor.go_to_line(item.line or 0, item.col or 0)
editor.set_focus()

@in_thread_job def check_requested(self, *args):
if current_container() is None:
return self.commit_all_editors_to_container()
c = self.gui.check_book c.parent().show()
c.parent().raise_and_focus()
c.run_checks(current_container())

def spell_check_requested(self):
if current_container() is None:
return self.commit_all_editors_to_container()
self.add_savepoint(_('Before: Spell Check'))
self.gui.spell_check.show()

@in_thread_job def fix_requested(self, errors):
self.add_savepoint(_('Before: Auto-fix errors'))
c = self.gui.check_book c.parent().show()
c.parent().raise_and_focus()
changed = c.fix_errors(current_container(), errors)
if changed:
self.apply_container_update_to_gui()
self.set_modified()
else:
self.rewind_savepoint()

@in_thread_job def merge_requested(self, category, names, master):
self.add_savepoint(_('Before: Merge files into %s') % self.gui.elided_text(master))
try:
merge(current_container(), category, names, master)
except AbortError:
self.rewind_savepoint()
raise self.apply_container_update_to_gui()
if master in editors:
self.show_editor(master)
self.gui.file_list.merge_completed(master)
self.gui.message_popup(_('{} files merged').format(len(names)))

@in_thread_job def link_stylesheets_requested(self, names, sheets, remove):
self.add_savepoint(_('Before: Link stylesheets'))
changed_names = link_stylesheets(current_container(), names, sheets, remove)
if changed_names:
self.update_editors_from_container(names=changed_names)
self.set_modified()

@in_thread_job def export_requested(self, name_or_names, path):
if isinstance(name_or_names, string_or_bytes):
return self.export_file(name_or_names, path)
for name in name_or_names:
dest = os.path.abspath(os.path.join(path, name))
if '/' in name or os.sep in name:
try:
os.makedirs(os.path.dirname(dest))
except OSError as err:
if err.errno != errno.EEXIST:
raise self.export_file(name, dest)

def open_file_with(self, file_name, fmt, entry):
if file_name in editors and not editors[file_name].is_synced_to_container:
self.commit_editor_to_container(file_name)
with current_container().open(file_name) as src:
tdir = PersistentTemporaryDirectory(suffix='-ee-ow')
with open(os.path.join(tdir, os.path.basename(file_name)), 'wb') as dest:
shutil.copyfileobj(src, dest)
from calibre.gui2.open_with import run_program run_program(entry, dest.name, self)
if question_dialog(self.gui, _('File opened'), _(
'When you are done editing {0} click "Import" to update'
' the file in the book or "Discard" to lose any changes.').format(file_name), yes_text=_('Import'), no_text=_('Discard')
):
self.add_savepoint(_('Before: Replace %s') % file_name)
with open(dest.name, 'rb') as src, current_container().open(file_name, 'wb') as cdest:
shutil.copyfileobj(src, cdest)
self.apply_container_update_to_gui()
try:
shutil.rmtree(tdir)
except Exception:
pass

@in_thread_job def copy_files_to_clipboard(self, names):
names = tuple(names)
for name in names:
if name in editors and not editors[name].is_synced_to_container:
self.commit_editor_to_container(name)
container = current_container()
md = QMimeData()
url_map = { name:container.get_file_path_for_processing(name, allow_modification=False)
for name in names
}
md.setUrls(list(map(QUrl.fromLocalFile, list(url_map.values()))))
import json md.setData(FILE_COPY_MIME, as_bytes(json.dumps({ name: (url_map[name], container.mime_map.get(name)) for name in names
})))
QApplication.instance().clipboard().setMimeData(md)

@in_thread_job def paste_files_from_clipboard(self):
md = QApplication.instance().clipboard().mimeData()
if md.hasUrls() and md.hasFormat(FILE_COPY_MIME):
import json self.commit_all_editors_to_container()
name_map = json.loads(bytes(md.data(FILE_COPY_MIME)))
container = current_container()
for name, (path, mt) in iteritems(name_map):
with open(path, 'rb') as f:
container.add_file(name, f.read(), media_type=mt, modify_name_if_needed=True)
self.apply_container_update_to_gui()

def export_file(self, name, path):
if name in editors and not editors[name].is_synced_to_container:
self.commit_editor_to_container(name)
with current_container().open(name, 'rb') as src, open(path, 'wb') as dest:
shutil.copyfileobj(src, dest)

@in_thread_job def replace_requested(self, name, path, basename, force_mt):
self.add_savepoint(_('Before: Replace %s') % name)
replace_file(current_container(), name, path, basename, force_mt)
self.apply_container_update_to_gui()

def browse_images(self):
self.gui.image_browser.refresh()
self.gui.image_browser.show()
self.gui.image_browser.raise_and_focus()

def show_reports(self):
if not self.ensure_book(_('You must first open a book in order to see the report.')):
return self.gui.reports.refresh()
self.gui.reports.show()
self.gui.reports.raise_and_focus()

def reports_edit_requested(self, name):
mt = current_container().mime_map.get(name, guess_type(name))
self.edit_file_requested(name, None, mt)

def image_activated(self, name):
mt = current_container().mime_map.get(name, guess_type(name))
self.edit_file_requested(name, None, mt)

def check_external_links(self):
if self.ensure_book(_('You must first open a book in order to check links.')):
self.commit_all_editors_to_container()
self.gui.check_external_links.show()

def compress_images(self):
if not self.ensure_book(_('You must first open a book in order to compress images.')):
return from calibre.gui2.tweak_book.polish import CompressImages, CompressImagesProgress, show_report d = CompressImages(self.gui)
if d.exec() == QDialog.DialogCode.Accepted:
with BusyCursor():
self.add_savepoint(_('Before: compress images'))
d = CompressImagesProgress(names=d.names, jpeg_quality=d.jpeg_quality, webp_quality=d.webp_quality, parent=self.gui)
if d.exec() != QDialog.DialogCode.Accepted:
self.rewind_savepoint()
return changed, report = d.result if changed is None and report:
self.rewind_savepoint()
return error_dialog(self.gui, _('Unexpected error'), _(
'Failed to compress images, click "Show details" for more information'), det_msg=report, show=True)
if changed:
self.apply_container_update_to_gui()
else:
self.rewind_savepoint()
show_report(changed, self.current_metadata.title, report, self.gui, self.show_current_diff)

def sync_editor_to_preview(self, name, sourceline_address):
editor = self.edit_file(name, 'html')
self.ignore_preview_to_editor_sync = True try:
editor.goto_sourceline(*sourceline_address)
finally:
self.ignore_preview_to_editor_sync = False def do_sync_preview_to_editor(self, wait_for_highlight_to_finish=False):
if self.ignore_preview_to_editor_sync:
return ed = self.gui.central.current_editor if ed is not None:
name = editor_name(ed)
if name is not None and getattr(ed, 'syntax', None) == 'html':
hl = getattr(ed, 'highlighter', None)
if wait_for_highlight_to_finish:
if getattr(hl, 'is_working', False):
QTimer.singleShot(75, self.sync_preview_to_editor_on_highlight_finish)
return ct = ed.current_tag()
self.gui.preview.sync_to_editor(name, ct)
if hl is not None and hl.is_working:
QTimer.singleShot(75, self.sync_preview_to_editor_on_highlight_finish)

[dokumentace]
def sync_preview_to_editor(self):
' Sync the position of the preview panel to the current cursor position in the current editor '
self.do_sync_preview_to_editor()

def sync_preview_to_editor_on_highlight_finish(self):
self.do_sync_preview_to_editor(wait_for_highlight_to_finish=True)

def show_partial_cfi_in_editor(self, name, cfi):
editor = self.edit_file(name, 'html')
if not editor or not editor.has_line_numbers:
return False from calibre.ebooks.epub.cfi.parse import decode_cfi from calibre.ebooks.oeb.polish.parsing import parse root = parse( editor.get_raw_data(), decoder=lambda x: x.decode('utf-8'), line_numbers=True, linenumber_attribute='data-lnum')
node = decode_cfi(root, cfi)

def barename(x):
return x.tag.partition('}')[-1]

if node is not None:
lnum = node.get('data-lnum')
if lnum:
tags_before = []
for tag in root.xpath('//*[@data-lnum="%s"]' % lnum):
tags_before.append(barename(tag))
if tag is node:
break else:
tags_before.append(barename(node))
lnum = int(lnum)
return editor.goto_sourceline(lnum, tags_before, attribute='id' if node.get('id') else None)
return False def goto_style_declaration(self, data):
name = data['name']
editor = self.edit_file(name, syntax=data['syntax'])
self.gui.live_css.navigate_to_declaration(data, editor)

def init_editor(self, name, editor, data=None, use_template=False):
editor.undo_redo_state_changed.connect(self.editor_undo_redo_state_changed)
editor.data_changed.connect(self.editor_data_changed)
editor.copy_available_state_changed.connect(self.editor_copy_available_state_changed)
editor.cursor_position_changed.connect(self.sync_preview_to_editor)
editor.cursor_position_changed.connect(self.update_cursor_position)
if hasattr(editor, 'word_ignored'):
editor.word_ignored.connect(self.word_ignored)
if hasattr(editor, 'link_clicked'):
editor.link_clicked.connect(self.editor_link_clicked)
if hasattr(editor, 'class_clicked'):
editor.class_clicked.connect(self.editor_class_clicked)
if hasattr(editor, 'rename_class'):
editor.rename_class.connect(self.rename_class)
if getattr(editor, 'syntax', None) == 'html':
editor.smart_highlighting_updated.connect(self.gui.live_css.sync_to_editor)
if hasattr(editor, 'set_request_completion'):
editor.set_request_completion(partial(self.request_completion, name), name)
if data is not None:
if use_template:
editor.init_from_template(data)
else:
editor.data = data editor.is_synced_to_container = True editor.modification_state_changed.connect(self.editor_modification_state_changed)
self.gui.central.add_editor(name, editor)

[dokumentace]
def edit_file(self, name, syntax=None, use_template=None):
''' Open the file specified by name in an editor

:param syntax: The media type of the file, for example, ``'text/html'``. If not specified it is guessed from the file extension.
:param use_template: A template to initialize the opened editor with
'''
editor = editors.get(name, None)
if editor is None:
syntax = syntax or syntax_from_mime(name, guess_type(name))
if use_template is None:
data = current_container().raw_data(name)
if isbytestring(data) and syntax in {'html', 'css', 'text', 'xml', 'javascript'}:
try:
data = data.decode('utf-8')
except UnicodeDecodeError:
return error_dialog(self.gui, _('Cannot decode'), _(
'Cannot edit %s as it appears to be in an unknown character encoding') % name, show=True)
else:
data = use_template editor = editors[name] = editor_from_syntax(syntax, self.gui.editor_tabs)
self.init_editor(name, editor, data, use_template=bool(use_template))
if tprefs['pretty_print_on_open']:
editor.pretty_print(name)
self.show_editor(name)
return editor

[dokumentace]
def show_editor(self, name):
' Show the editor that is editing the file specified by ``name`` '
self.gui.central.show_editor(editors[name])
editors[name].set_focus()

def edit_file_requested(self, name, syntax=None, mime=None):
if name in editors:
self.gui.central.show_editor(editors[name])
return editors[name]
mime = mime or current_container().mime_map.get(name, guess_type(name))
syntax = syntax or syntax_from_mime(name, mime)
if not syntax:
return error_dialog( self.gui, _('Unsupported file format'),
_('Editing files of type %s is not supported') % mime, show=True)
return self.edit_file(name, syntax)

def edit_next_file(self, backwards=False):
self.gui.file_list.edit_next_file(self.currently_editing, backwards)

def quick_open(self):
if not self.ensure_book(_('No book is currently open. You must first open a book to edit.')):
return c = current_container()
files = [name for name, mime in iteritems(c.mime_map) if c.exists(name) and syntax_from_mime(name, mime) is not None]
d = QuickOpen(files, parent=self.gui)
if d.exec() == QDialog.DialogCode.Accepted and d.selected_result is not None:
self.edit_file_requested(d.selected_result, None, c.mime_map[d.selected_result])

# Editor basic controls {{{ def do_editor_undo(self):
ed = self.gui.central.current_editor if ed is not None:
ed.undo()

def do_editor_redo(self):
ed = self.gui.central.current_editor if ed is not None:
ed.redo()

def do_editor_copy(self):
ed = self.gui.central.current_editor if ed is not None:
ed.copy()

def do_editor_cut(self):
ed = self.gui.central.current_editor if ed is not None:
ed.cut()

def do_editor_paste(self):
ed = self.gui.central.current_editor if ed is not None:
ed.paste()

def editor_data_changed(self, editor):
self.gui.preview.start_refresh_timer()
for name, ed in iteritems(editors):
if ed is editor:
self.gui.toc_view.start_refresh_timer(name)
break def editor_undo_redo_state_changed(self, *args):
self.apply_current_editor_state()

def editor_copy_available_state_changed(self, *args):
self.apply_current_editor_state()

def editor_modification_state_changed(self, is_modified):
self.apply_current_editor_state()
if is_modified:
self.set_modified()
# }}}

def apply_current_editor_state(self):
ed = self.gui.central.current_editor self.gui.cursor_position_widget.update_position()
if ed is not None:
actions['editor-undo'].setEnabled(ed.undo_available)
actions['editor-redo'].setEnabled(ed.redo_available)
actions['editor-copy'].setEnabled(ed.copy_available)
actions['editor-cut'].setEnabled(ed.cut_available)
actions['go-to-line-number'].setEnabled(ed.has_line_numbers)
actions['fix-html-current'].setEnabled(ed.syntax == 'html')
name = editor_name(ed)
mime = current_container().mime_map.get(name)
if name is not None and (getattr(ed, 'syntax', None) == 'html' or mime == 'image/svg+xml'):
if self.gui.preview.show(name):
# The file being displayed by the preview has changed.
# Set the preview's position to the current cursor
# position in the editor, in case the editors' cursor
# position has not changed, since the last time it was
# focused. This is not inefficient since multiple requests
# to sync are de-bounced with a 100 msec wait.
self.sync_preview_to_editor()
if name is not None:
self.gui.file_list.mark_name_as_current(name)
if ed.has_line_numbers:
self.gui.cursor_position_widget.update_position(*ed.cursor_position)
else:
actions['go-to-line-number'].setEnabled(False)
self.gui.file_list.clear_currently_edited_name()

def update_cursor_position(self):
ed = self.gui.central.current_editor if getattr(ed, 'has_line_numbers', False):
self.gui.cursor_position_widget.update_position(*ed.cursor_position)
else:
self.gui.cursor_position_widget.update_position()

def editor_close_requested(self, editor):
name = editor_name(editor)
if not name:
return if not editor.is_synced_to_container:
self.commit_editor_to_container(name)
self.close_editor(name)

[dokumentace]
def close_editor(self, name):
' Close the editor that is editing the file specified by ``name`` '
editor = editors.pop(name)
self.gui.central.close_editor(editor)
editor.break_cycles()
if not editors or getattr(self.gui.central.current_editor, 'syntax', None) != 'html':
self.gui.preview.clear()
self.gui.live_css.clear()

def insert_character(self):
self.gui.insert_char.show()

def manage_snippets(self):
from calibre.gui2.tweak_book.editor.snippets import UserSnippets
UserSnippets(self.gui).exec()

def merge_files(self):
self.gui.file_list.merge_files()

# Shutdown {{{ def quit(self):
if self.doing_terminal_save:
return False if self.save_manager.has_tasks:
if question_dialog( self.gui, _('Are you sure?'), _(
'The current book is being saved in the background. Quitting now will'
' <b>abort the save process</b>! Finish saving first?'), yes_text=_('Finish &saving first'), no_text=_('&Quit immediately')):
if self.save_manager.has_tasks:
self.start_terminal_save_indicator()
return False if not self.confirm_quit():
return False self.shutdown()
QApplication.instance().exit()
return True def confirm_quit(self):
if self.gui.action_save.isEnabled():
d = QDialog(self.gui)
d.l = QGridLayout(d)
d.setLayout(d.l)
d.setWindowTitle(_('Unsaved changes'))
d.i = QLabel('')
d.i.setMaximumSize(QSize(64, 64))
d.i.setPixmap(QIcon.ic('dialog_warning.png').pixmap(d.i.maximumSize()))
d.l.addWidget(d.i, 0, 0)
d.m = QLabel(_('There are unsaved changes, if you quit without saving, you will lose them.'))
d.m.setWordWrap(True)
d.l.addWidget(d.m, 0, 1)
d.bb = QDialogButtonBox(QDialogButtonBox.StandardButton.Cancel)
d.bb.rejected.connect(d.reject)
d.bb.accepted.connect(d.accept)
d.l.addWidget(d.bb, 1, 0, 1, 2)
d.do_save = None def endit(d, x):
d.do_save = x d.accept()
b = d.bb.addButton(_('&Save and Quit'), QDialogButtonBox.ButtonRole.ActionRole)
b.setIcon(QIcon.ic('save.png'))
connect_lambda(b.clicked, d, lambda d: endit(d, True))
b = d.bb.addButton(_('&Quit without saving'), QDialogButtonBox.ButtonRole.ActionRole)
connect_lambda(b.clicked, d, lambda d: endit(d, False))
d.resize(d.sizeHint())
if d.exec() != QDialog.DialogCode.Accepted or d.do_save is None:
return False if d.do_save:
self.gui.action_save.trigger()
self.start_terminal_save_indicator()
return False return True def start_terminal_save_indicator(self):
self.save_state()
self.gui.blocking_job.set_msg(_('Saving, please wait...'))
self.gui.blocking_job.start()
self.doing_terminal_save = True def abort_terminal_save(self):
self.doing_terminal_save = False self.gui.blocking_job.stop()

def check_terminal_save(self):
if self.doing_terminal_save and not self.save_manager.has_tasks: # terminal save could have been aborted self.shutdown()
QApplication.instance().exit()

def shutdown(self):
self.save_state()
completion_worker().shutdown()
self.save_manager.check_for_completion.disconnect()
self.gui.preview.stop_refresh_timer()
self.gui.live_css.stop_update_timer()
[x.reject() for x in _diff_dialogs]
del _diff_dialogs[:]
self.save_manager.shutdown()
parse_worker.shutdown()
self.save_manager.wait(0.1)

def save_state(self):
with self.editor_cache:
self.save_book_edit_state()
with tprefs:
self.gui.save_state()

def save_book_edit_state(self):
c = current_container()
if c and c.path_to_ebook:
tprefs = self.editor_cache mem = tprefs['edit_book_state']
order = tprefs['edit_book_state_order']
extra = len(order) - 99 if extra > 0:
order = [k for k in order[extra:] if k in mem]
mem = {k:mem[k] for k in order}
mem[c.path_to_ebook] = {
'editors':{name:ed.current_editing_state for name, ed in iteritems(editors)},
'currently_editing':self.currently_editing,
'tab_order':self.gui.central.tab_order,
}
try:
order.remove(c.path_to_ebook)
except ValueError:
pass order.append(c.path_to_ebook)
tprefs['edit_book_state'] = mem tprefs['edit_book_state_order'] = order def restore_book_edit_state(self):
c = current_container()
if c and c.path_to_ebook:
tprefs = self.editor_cache state = tprefs['edit_book_state'].get(c.path_to_ebook)
if state is not None:
opened = set()
eds = state.get('editors', {})
for name in state.get('tab_order', ()):
if c.has_name(name):
try:
editor = self.edit_file_requested(name)
if editor is not None:
opened.add(name)
es = eds.get(name)
if es is not None:
editor.current_editing_state = es except Exception:
import traceback traceback.print_exc()
ce = state.get('currently_editing')
if ce in opened:
self.show_editor(ce)# }}}

© Copyright Kovid Goyal.
Naposledy aktualizováno kvě 03, 2024.

Navigace

- moduly
- Spustit »
- Kód modulu »
- calibre.gui2.tweak_book.plugin

Zdrojový kód pro calibre.gui2.tweak_book.plugin

#!/usr/bin/env python

__license__ = 'GPL v3'
__copyright__ = '2014, Kovid Goyal <kovid at kovidgoyal.net>'

import importlib import sys from qt.core import QToolButton from calibre import prints from calibre.customize import PluginInstallationType from calibre.customize.ui import all_edit_book_tool_plugins from calibre.gui2.tweak_book import current_container, tprefs from calibre.gui2.tweak_book.boss import get_boss from polyglot.builtins import itervalues

[dokumentace]
class Tool:

'''
The base class for individual tools in an Edit Book plugin. Useful members include:

* ``self.plugin``: A reference to the :class:`calibre.customize.Plugin` object to which this tool belongs.
* self. :attr:`boss`
* self. :attr:`gui`

Methods that must be overridden in sub classes:

* :meth:`create_action`
* :meth:`register_shortcut`

'''

#: Set this to a unique name it will be used as a key name = None

#: If True the user can choose to place this tool in the plugins toolbar allowed_in_toolbar = True

#: If True the user can choose to place this tool in the plugins menu allowed_in_menu = True

#: The popup mode for the menu (if any) of the toolbar button. Possible values are 'delayed', 'instant', 'button'
toolbar_button_popup_mode = 'delayed'

@property def boss(self):
' The :class:`calibre.gui2.tweak_book.boss.Boss` object. Used to control the user interface. '
return get_boss()

@property def gui(self):
' The main window of the user interface '
return self.boss.gui

@property def current_container(self):
' Return the current :class:`calibre.ebooks.oeb.polish.container.Container` object that represents the book being edited. '
return current_container()

[dokumentace]
def register_shortcut(self, qaction, unique_name, default_keys=(), short_text=None, description=None, **extra_data):
'''
Register a keyboard shortcut that will trigger the specified ``qaction``. This keyboard shortcut will become automatically customizable by the user in the Keyboard shortcuts section of the editor preferences.

:param qaction: A QAction object, it will be triggered when the configured key combination is pressed by the user.
:param unique_name: A unique name for this shortcut/action. It will be used internally, it must not be shared by any other actions in this plugin.
:param default_keys: A list of the default keyboard shortcuts. If not specified no default shortcuts will be set. If the shortcuts specified here conflict with either builtin shortcuts or shortcuts from user configuration/other plugins, they will be ignored. In that case, users will have to configure the shortcuts manually via Preferences. For example:
``default_keys=('Ctrl+J', 'F9')``.
:param short_text: An optional short description of this action. If not specified the text from the QAction will be used.
:param description: An optional longer description of this action, it will be used in the preferences entry for this shortcut.
'''
short_text = short_text or str(qaction.text()).replace('&&', '\0').replace('&', '').replace('\0', '&')
self.gui.keyboard.register_shortcut( self.name + '_' + unique_name, short_text, default_keys=default_keys, action=qaction, description=description or '', group=_('Plugins'))

[dokumentace]
def create_action(self, for_toolbar=True):
'''
Create a QAction that will be added to either the plugins toolbar or the plugins menu depending on ``for_toolbar``. For example::

def create_action(self, for_toolbar=True):
ac = QAction(get_icons('myicon.png'), 'Do something')
if for_toolbar:
# We want the toolbar button to have a popup menu menu = QMenu()
ac.setMenu(menu)
menu.addAction('Do something else')
subaction = menu.addAction('And another')

# Register a keyboard shortcut for this toolbar action be
# careful to do this for only one of the toolbar action or
# the menu action, not both.
self.register_shortcut(ac, 'some-unique-name', default_keys=('Ctrl+K',))
return ac

.. seealso:: Method :meth:`register_shortcut`.
'''
raise NotImplementedError()

def load_plugin_tools(plugin):
try:
main = importlib.import_module(plugin.__class__.__module__+'.main')
except ImportError:
import traceback traceback.print_exc()
else:
for x in itervalues(vars(main)):
if isinstance(x, type) and x is not Tool and issubclass(x, Tool):
ans = x()
ans.plugin = plugin yield ans def plugin_action_sid(plugin, tool, for_toolbar=True):
return plugin.name + tool.name + ('toolbar' if for_toolbar else 'menu')

plugin_toolbar_actions = []

def create_plugin_action(plugin, tool, for_toolbar, actions=None, toolbar_actions=None, plugin_menu_actions=None):
try:
ac = tool.create_action(for_toolbar=for_toolbar)
if ac is None:
raise RuntimeError('create_action() failed to return an action')
except Exception:
prints('Failed to create action for tool:', tool.name)
import traceback traceback.print_exc()
return sid = plugin_action_sid(plugin, tool, for_toolbar)
if actions is not None and sid in actions:
prints(f'The {tool.name} tool from the {plugin.name} plugin has a non unique name, ignoring')
else:
if actions is not None:
actions[sid] = ac ac.sid = sid if for_toolbar:
if toolbar_actions is not None:
toolbar_actions[sid] = ac plugin_toolbar_actions.append(ac)
ac.popup_mode = {'instant':QToolButton.ToolButtonPopupMode.InstantPopup, 'button':QToolButton.ToolButtonPopupMode.MenuButtonPopup}.get( tool.toolbar_button_popup_mode, QToolButton.ToolButtonPopupMode.DelayedPopup)
else:
if plugin_menu_actions is not None:
plugin_menu_actions.append(ac)
return ac

_tool_memory = [] # Needed to prevent the tool object from being garbage collected def create_plugin_actions(actions, toolbar_actions, plugin_menu_actions):
del _tool_memory[:]
del plugin_toolbar_actions[:]

for plugin in all_edit_book_tool_plugins():
try:
tools = tuple(load_plugin_tools(plugin))
except Exception:
if plugin.installation_type is PluginInstallationType.BUILTIN:
raise print('Failed to load third-party plugin:', plugin.name, file=sys.stderr)
import traceback traceback.print_exc()
continue for tool in tools:
_tool_memory.append(tool)
if tool.allowed_in_toolbar:
create_plugin_action(plugin, tool, True, actions, toolbar_actions, plugin_menu_actions)
if tool.allowed_in_menu:
create_plugin_action(plugin, tool, False, actions, toolbar_actions, plugin_menu_actions)

def install_plugin(plugin):
for tool in load_plugin_tools(plugin):
if tool.allowed_in_toolbar:
sid = plugin_action_sid(plugin, tool, True)
if sid not in tprefs['global_plugins_toolbar']:
tprefs['global_plugins_toolbar'] = tprefs['global_plugins_toolbar'] + [sid]

© Copyright Kovid Goyal.
Naposledy aktualizováno kvě 03, 2024.

Navigace

- moduly
- Spustit »
- Kód modulu »
- calibre.utils.formatter_functions

Zdrojový kód pro calibre.utils.formatter_functions

#!/usr/bin/env python

'''
Created on 13 Jan 2011

@author: charles
'''

__license__ = 'GPL v3'
__copyright__ = '2010, Kovid Goyal <kovid@kovidgoyal.net>'
__docformat__ = 'restructuredtext en'

import inspect import numbers import posixpath import re import traceback from contextlib import suppress from datetime import datetime, timedelta from enum import Enum, auto from functools import partial from math import ceil, floor, modf, trunc from lxml import html from calibre import human_readable, prepare_string_for_xml, prints from calibre.constants import DEBUG from calibre.db.constants import DATA_DIR_NAME, DATA_FILE_PATTERN from calibre.db.notes.exim import expand_note_resources, parse_html from calibre.ebooks.metadata import title_sort from calibre.utils.config import tweaks from calibre.utils.date import UNDEFINED_DATE, format_date, now, parse_date from calibre.utils.icu import capitalize, sort_key, strcmp from calibre.utils.icu import lower as icu_lower from calibre.utils.localization import _, calibre_langcode_to_name, canonicalize_lang from calibre.utils.titlecase import titlecase from polyglot.builtins import iteritems, itervalues class StoredObjectType(Enum):
PythonFunction = auto()
StoredGPMTemplate = auto()
StoredPythonTemplate = auto()

class FormatterFunctions:

error_function_body = ('def evaluate(self, formatter, kwargs, mi, locals):\n'
'\treturn "' +
_('Duplicate user function name {0}. '
'Change the name or ensure that the functions are identical') + '"')

def __init__(self):
self._builtins = {}
self._functions = {}
self._functions_from_library = {}

def register_builtin(self, func_class):
if not isinstance(func_class, FormatterFunction):
raise ValueError('Class %s is not an instance of FormatterFunction'%( func_class.__class__.__name__))
name = func_class.name if name in self._functions:
raise ValueError('Name %s already used'%name)
self._builtins[name] = func_class self._functions[name] = func_class for a in func_class.aliases:
self._functions[a] = func_class def _register_function(self, func_class, replace=False):
if not isinstance(func_class, FormatterFunction):
raise ValueError('Class %s is not an instance of FormatterFunction'%( func_class.__class__.__name__))
name = func_class.name if not replace and name in self._functions:
raise ValueError('Name %s already used'%name)
self._functions[name] = func_class def register_functions(self, library_uuid, funcs):
self._functions_from_library[library_uuid] = funcs self._register_functions()

def _register_functions(self):
for compiled_funcs in itervalues(self._functions_from_library):
for cls in compiled_funcs:
f = self._functions.get(cls.name, None)
replace = False if f is not None:
existing_body = f.program_text new_body = cls.program_text if new_body != existing_body:
# Change the body of the template function to one that will
# return an error message. Also change the arg count to
# -1 (variable) to avoid template compilation errors if DEBUG:
print(f'attempt to replace formatter function {f.name} with a different body')
replace = True func = [cls.name, '', -1, self.error_function_body.format(cls.name)]
cls = compile_user_function(*func)
else:
continue formatter_functions()._register_function(cls, replace=replace)

def unregister_functions(self, library_uuid):
if library_uuid in self._functions_from_library:
for cls in self._functions_from_library[library_uuid]:
self._functions.pop(cls.name, None)
self._functions_from_library.pop(library_uuid)
self._register_functions()

def get_builtins(self):
return self._builtins def get_builtins_and_aliases(self):
res = {}
for f in itervalues(self._builtins):
res[f.name] = f for a in f.aliases:
res[a] = f return res def get_functions(self):
return self._functions def reset_to_builtins(self):
self._functions = {}
for n,c in self._builtins.items():
self._functions[n] = c for a in c.aliases:
self._functions[a] = c

_ff = FormatterFunctions()

def formatter_functions():
global _ff return _ff def only_in_gui_error(name):
raise ValueError(_('The function {} can be used only in the GUI').format(name))

def get_database(mi, name):
proxy = mi.get('_proxy_metadata', None)
if proxy is None:
if name is not None:
only_in_gui_error(name)
return None wr = proxy.get('_db', None)
if wr is None:
if name is not None:
raise ValueError(_('In function {}: The database has been closed').format(name))
return None cache = wr()
if cache is None:
if name is not None:
raise ValueError(_('In function {}: The database has been closed').format(name))
return None wr = getattr(cache, 'library_database_instance', None)
if wr is None:
if name is not None:
only_in_gui_error()
return None db = wr()
if db is None:
if name is not None:
raise ValueError(_('In function {}: The database has been closed').format(name))
return None return db class FormatterFunction:

doc = _('No documentation provided')
name = 'no name provided'
category = 'Unknown'
arg_count = 0 aliases = []
object_type = StoredObjectType.PythonFunction def evaluate(self, formatter, kwargs, mi, locals, *args):
raise NotImplementedError()

def eval_(self, formatter, kwargs, mi, locals, *args):
ret = self.evaluate(formatter, kwargs, mi, locals, *args)
if isinstance(ret, (bytes, str)):
return ret if isinstance(ret, list):
return ','.join(ret)
if isinstance(ret, (numbers.Number, bool)):
return str(ret)

def only_in_gui_error(self):
only_in_gui_error(self.name)

def get_database(self, mi):
return get_database(mi, self.name)

class BuiltinFormatterFunction(FormatterFunction):

def __init__(self):
formatter_functions().register_builtin(self)
eval_func = inspect.getmembers(self.__class__, lambda x: inspect.isfunction(x) and x.__name__ == 'evaluate')
try:
lines = [l[4:] for l in inspect.getsourcelines(eval_func[0][1])[0]]
except:
lines = []
self.program_text = ''.join(lines)

[dokumentace]
class BuiltinStrcmp(BuiltinFormatterFunction):
name = 'strcmp'
arg_count = 5 category = 'Relational'
__doc__ = doc = _('strcmp(x, y, lt, eq, gt) -- does a case-insensitive comparison of x '
'and y as strings. Returns lt if x < y. Returns eq if x == y. '
'Otherwise returns gt. In many cases the lexical comparison operators '
'(>, <, == etc) can replace this function.')

def evaluate(self, formatter, kwargs, mi, locals, x, y, lt, eq, gt):
v = strcmp(x, y)
if v < 0:
return lt if v == 0:
return eq return gt

[dokumentace]
class BuiltinStrcmpcase(BuiltinFormatterFunction):
name = 'strcmpcase'
arg_count = 5 category = 'Relational'
__doc__ = doc = _('strcmpcase(x, y, lt, eq, gt) -- does a case-sensitive comparison of x '
'and y as strings. Returns lt if x < y. Returns eq if x == y. '
'Otherwise returns gt.\n'
'Note: This is NOT the default behavior used by calibre, for example, in the '
'lexical comparison operators (==, >, <, etc.). This function could '
'cause unexpected results, preferably use strcmp() whenever possible.')

def evaluate(self, formatter, kwargs, mi, locals, x, y, lt, eq, gt):
from calibre.utils.icu import case_sensitive_strcmp as case_strcmp v = case_strcmp(x, y)
if v < 0:
return lt if v == 0:
return eq return gt

[dokumentace]
class BuiltinCmp(BuiltinFormatterFunction):
name = 'cmp'
category = 'Relational'
arg_count = 5
__doc__ = doc = _('cmp(x, y, lt, eq, gt) -- compares x and y after converting both to '
'numbers. Returns lt if x < y. Returns eq if x == y. Otherwise returns gt. '
'In many cases the numeric comparison operators '
'(>#, <#, ==# etc) can replace this function.')

def evaluate(self, formatter, kwargs, mi, locals, x, y, lt, eq, gt):
x = float(x if x and x != 'None' else 0)
y = float(y if y and y != 'None' else 0)
if x < y:
return lt if x == y:
return eq return gt

[dokumentace]
class BuiltinFirstMatchingCmp(BuiltinFormatterFunction):
name = 'first_matching_cmp'
category = 'Relational'
arg_count = -1
__doc__ = doc = _('first_matching_cmp(val, [cmp1, result1,]+, else_result) -- '
'compares "val < cmpN" in sequence, returning resultN for '
'the first comparison that succeeds. Returns else_result '
'if no comparison succeeds. Example: '
'first_matching_cmp(10,5,"small",10,"middle",15,"large","giant") '
'returns "large". The same example with a first value of 16 returns "giant".')

def evaluate(self, formatter, kwargs, mi, locals, *args):
if (len(args) % 2) != 0:
raise ValueError(_('first_matching_cmp requires an even number of arguments'))
val = float(args[0] if args[0] and args[0] != 'None' else 0)
for i in range(1, len(args) - 1, 2):
c = float(args[i] if args[i] and args[i] != 'None' else 0)
if val < c:
return args[i+1]
return args[len(args)-1]

[dokumentace]
class BuiltinStrcat(BuiltinFormatterFunction):
name = 'strcat'
arg_count = -1 category = 'String manipulation'
__doc__ = doc = _('strcat(a [, b]*) -- can take any number of arguments. Returns the '
'string formed by concatenating all the arguments')

def evaluate(self, formatter, kwargs, mi, locals, *args):
i = 0 res = ''
for i in range(0, len(args)):
res += args[i]
return res

[dokumentace]
class BuiltinStrlen(BuiltinFormatterFunction):
name = 'strlen'
arg_count = 1 category = 'String manipulation'
__doc__ = doc = _('strlen(a) -- Returns the length of the string passed as '
'the argument')

def evaluate(self, formatter, kwargs, mi, locals, a):
try:
return len(a)
except:
return -1

[dokumentace]
class BuiltinAdd(BuiltinFormatterFunction):
name = 'add'
arg_count = -1 category = 'Arithmetic'
__doc__ = doc = _('add(x [, y]*) -- returns the sum of its arguments. '
'Throws an exception if an argument is not a number. '
'This function can often be '
'replaced with the + operator.')

def evaluate(self, formatter, kwargs, mi, locals, *args):
res = 0 for v in args:
v = float(v if v and v != 'None' else 0)
res += v return str(res)

[dokumentace]
class BuiltinSubtract(BuiltinFormatterFunction):
name = 'subtract'
arg_count = 2 category = 'Arithmetic'
__doc__ = doc = _('subtract(x, y) -- returns x - y. Throws an exception if '
'either x or y are not numbers. This function can often be '
'replaced with the - operator.')

def evaluate(self, formatter, kwargs, mi, locals, x, y):
x = float(x if x and x != 'None' else 0)
y = float(y if y and y != 'None' else 0)
return str(x - y)

[dokumentace]
class BuiltinMultiply(BuiltinFormatterFunction):
name = 'multiply'
arg_count = -1 category = 'Arithmetic'
__doc__ = doc = _('multiply(x [, y]*) -- returns the product of its arguments. '
'Throws an exception if any argument is not a number. '
'This function can often be replaced with the * operator.')

def evaluate(self, formatter, kwargs, mi, locals, *args):
res = 1 for v in args:
v = float(v if v and v != 'None' else 0)
res *= v return str(res)

[dokumentace]
class BuiltinDivide(BuiltinFormatterFunction):
name = 'divide'
arg_count = 2 category = 'Arithmetic'
__doc__ = doc = _('divide(x, y) -- returns x / y. Throws an exception if '
'either x or y are not numbers.'
' This function can often be replaced with the / operator.')

def evaluate(self, formatter, kwargs, mi, locals, x, y):
x = float(x if x and x != 'None' else 0)
y = float(y if y and y != 'None' else 0)
return str(x / y)

[dokumentace]
class BuiltinCeiling(BuiltinFormatterFunction):
name = 'ceiling'
arg_count = 1 category = 'Arithmetic'
__doc__ = doc = _('ceiling(x) -- returns the smallest integer greater '
'than or equal to x. Throws an exception if x is '
'not a number.')

def evaluate(self, formatter, kwargs, mi, locals, x):
x = float(x if x and x != 'None' else 0)
return str(int(ceil(x)))

[dokumentace]
class BuiltinFloor(BuiltinFormatterFunction):
name = 'floor'
arg_count = 1 category = 'Arithmetic'
__doc__ = doc = _('floor(x) -- returns the largest integer less '
'than or equal to x. Throws an exception if x is '
'not a number.')

def evaluate(self, formatter, kwargs, mi, locals, x):
x = float(x if x and x != 'None' else 0)
return str(int(floor(x)))

[dokumentace]
class BuiltinRound(BuiltinFormatterFunction):
name = 'round'
arg_count = 1 category = 'Arithmetic'
__doc__ = doc = _('round(x) -- returns the nearest integer to x. '
'Throws an exception if x is not a number.')

def evaluate(self, formatter, kwargs, mi, locals, x):
x = float(x if x and x != 'None' else 0)
return str(int(round(x)))

[dokumentace]
class BuiltinMod(BuiltinFormatterFunction):
name = 'mod'
arg_count = 2 category = 'Arithmetic'
__doc__ = doc = _('mod(x) -- returns floor(remainder of x / y). '
'Throws an exception if either x or y is not a number.')

def evaluate(self, formatter, kwargs, mi, locals, x, y):
x = float(x if x and x != 'None' else 0)
y = float(y if y and y != 'None' else 0)
return str(int(x % y))

[dokumentace]
class BuiltinFractionalPart(BuiltinFormatterFunction):
name = 'fractional_part'
arg_count = 1 category = 'Arithmetic'
__doc__ = doc = _('fractional_part(x) -- returns the value after the decimal '
'point. For example, fractional_part(3.14) returns 0.14. '
'Throws an exception if x is not a number.')

def evaluate(self, formatter, kwargs, mi, locals, x):
x = float(x if x and x != 'None' else 0)
return str(modf(x)[0])

[dokumentace]
class BuiltinTemplate(BuiltinFormatterFunction):
name = 'template'
arg_count = 1 category = 'Recursion'

__doc__ = doc = _('template(x) -- evaluates x as a template. The evaluation is done '
'in its own context, meaning that variables are not shared between '
'the caller and the template evaluation. Because the { and } '
'characters are special, you must use [[ for the { character and '
']] for the } character; they are converted automatically. '
'For example, template(\'[[title_sort]]\') will evaluate the '
'template {title_sort} and return its value. Note also that '
'prefixes and suffixes (the `|prefix|suffix` syntax) cannot be '
'used in the argument to this function when using template program mode.')

def evaluate(self, formatter, kwargs, mi, locals, template):
template = template.replace('[[', '{').replace(']]', '}')
return formatter.__class__().safe_format(template, kwargs, 'TEMPLATE', mi)

[dokumentace]
class BuiltinEval(BuiltinFormatterFunction):
name = 'eval'
arg_count = 1 category = 'Recursion'
__doc__ = doc = _('eval(template) -- evaluates the template, passing the local '
'variables (those \'assign\'ed to) instead of the book metadata. '
' This permits using the template processor to construct complex '
'results from local variables. Because the { and } '
'characters are special, you must use [[ for the { character and '
']] for the } character; they are converted automatically. '
'Note also that prefixes and suffixes (the `|prefix|suffix` syntax) '
'cannot be used in the argument to this function when using '
'template program mode.')

def evaluate(self, formatter, kwargs, mi, locals, template):
from calibre.utils.formatter import EvalFormatter template = template.replace('[[', '{').replace(']]', '}')
return EvalFormatter().safe_format(template, locals, 'EVAL', None)

[dokumentace]
class BuiltinAssign(BuiltinFormatterFunction):
name = 'assign'
arg_count = 2 category = 'Other'
__doc__ = doc = _('assign(id, val) -- assigns val to id, then returns val. '
'id must be an identifier, not an expression. '
'This function can often be replaced with the = operator.')

def evaluate(self, formatter, kwargs, mi, locals, target, value):
locals[target] = value return value

[dokumentace]
class BuiltinListSplit(BuiltinFormatterFunction):
name = 'list_split'
arg_count = 3 category = 'List manipulation'
__doc__ = doc = _('list_split(list_val, sep, id_prefix) -- splits the list_val '
"into separate values using 'sep', then assigns the values "
"to variables named 'id_prefix_N' where N is the position "
"of the value in the list. The first item has position 0 (zero). "
"The function returns the last element in the list. "
"Example: split('one:two:foo', ':', 'var') is equivalent "
"to var_0 = 'one'; var_1 = 'two'; var_2 = 'foo'.")

def evaluate(self, formatter, kwargs, mi, locals, list_val, sep, id_prefix):
l = [v.strip() for v in list_val.split(sep)]
res = ''
for i,v in enumerate(l):
res = locals[id_prefix+'_'+str(i)] = v return res

[dokumentace]
class BuiltinPrint(BuiltinFormatterFunction):
name = 'print'
arg_count = -1 category = 'Other'
__doc__ = doc = _('print(a[, b]*) -- prints the arguments to standard output. '
'Unless you start calibre from the command line (calibre-debug -g), '
'the output will go to a black hole.')

def evaluate(self, formatter, kwargs, mi, locals, *args):
print(args)
return ''

[dokumentace]
class BuiltinField(BuiltinFormatterFunction):
name = 'field'
arg_count = 1 category = 'Get values from metadata'
__doc__ = doc = _('field(lookup_name) -- returns the metadata field named by lookup_name')

def evaluate(self, formatter, kwargs, mi, locals, name):
return formatter.get_value(name, [], kwargs)

[dokumentace]
class BuiltinRawField(BuiltinFormatterFunction):
name = 'raw_field'
arg_count = -1 category = 'Get values from metadata'
__doc__ = doc = _('raw_field(lookup_name [, optional_default]) -- returns the '
'metadata field named by lookup_name without applying any formatting. '
'It evaluates and returns the optional second argument '
"'default' if the field is undefined ('None').")

def evaluate(self, formatter, kwargs, mi, locals, name, default=None):
res = getattr(mi, name, None)
if res is None and default is not None:
return default if isinstance(res, list):
fm = mi.metadata_for_field(name)
if fm is None:
return ', '.join(res)
return fm['is_multiple']['list_to_ui'].join(res)
return str(res)

[dokumentace]
class BuiltinRawList(BuiltinFormatterFunction):
name = 'raw_list'
arg_count = 2 category = 'Get values from metadata'
__doc__ = doc = _('raw_list(lookup_name, separator) -- returns the metadata list '
'named by lookup_name without applying any formatting or sorting and '
'with items separated by separator.')

def evaluate(self, formatter, kwargs, mi, locals, name, separator):
res = getattr(mi, name, None)
if not isinstance(res, list):
return "%s is not a list" % name return separator.join(res)

[dokumentace]
class BuiltinSubstr(BuiltinFormatterFunction):
name = 'substr'
arg_count = 3 category = 'String manipulation'
__doc__ = doc = _('substr(str, start, end) -- returns the start\'th through the end\'th '
'characters of str. The first character in str is the zero\'th '
'character. If end is negative, then it indicates that many '
'characters counting from the right. If end is zero, then it '
'indicates the last character. For example, substr(\'12345\', 1, 0) '
'returns \'2345\', and substr(\'12345\', 1, -1) returns \'234\'.')

def evaluate(self, formatter, kwargs, mi, locals, str_, start_, end_):
return str_[int(start_): len(str_) if int(end_) == 0 else int(end_)]

[dokumentace]
class BuiltinLookup(BuiltinFormatterFunction):
name = 'lookup'
arg_count = -1 category = 'Iterating over values'
__doc__ = doc = _('lookup(val, [pattern, field,]+ else_field) -- '
'like switch, except the arguments are field (metadata) names, not '
'text. The value of the appropriate field will be fetched and used. '
'Note that because composite columns are fields, you can use this '
'function in one composite field to use the value of some other '
'composite field. This is extremely useful when constructing '
'variable save paths')

def evaluate(self, formatter, kwargs, mi, locals, val, *args):
if len(args) == 2: # here for backwards compatibility if val:
return formatter.vformat('{'+args[0].strip()+'}', [], kwargs)
else:
return formatter.vformat('{'+args[1].strip()+'}', [], kwargs)
if (len(args) % 2) != 1:
raise ValueError(_('lookup requires either 2 or an odd number of arguments'))
i = 0 while i < len(args):
if i + 1 >= len(args):
return formatter.vformat('{' + args[i].strip() + '}', [], kwargs)
if re.search(args[i], val, flags=re.I):
return formatter.vformat('{'+args[i+1].strip() + '}', [], kwargs)
i += 2

[dokumentace]
class BuiltinTest(BuiltinFormatterFunction):
name = 'test'
arg_count = 3 category = 'If-then-else'
__doc__ = doc = _('test(val, text if not empty, text if empty) -- return `text if not '
'empty` if val is not empty, otherwise return `text if empty`')

def evaluate(self, formatter, kwargs, mi, locals, val, value_if_set, value_not_set):
if val:
return value_if_set else:
return value_not_set

[dokumentace]
class BuiltinContains(BuiltinFormatterFunction):
name = 'contains'
arg_count = 4 category = 'If-then-else'
__doc__ = doc = _('contains(val, pattern, text if match, text if not match) -- checks '
'if val contains matches for the regular expression `pattern`. '
'Returns `text if match` if matches are found, otherwise it returns '
'`text if no match`')

def evaluate(self, formatter, kwargs, mi, locals, val, test, value_if_present, value_if_not):
if re.search(test, val, flags=re.I):
return value_if_present else:
return value_if_not

[dokumentace]
class BuiltinSwitch(BuiltinFormatterFunction):
name = 'switch'
arg_count = -1 category = 'Iterating over values'
__doc__ = doc = _('switch(val, [pattern, value,]+ else_value) -- '
'for each `pattern, value` pair, checks if `val` matches '
'the regular expression `pattern` and if so, returns that '
'`value`. If no pattern matches, then `else_value` is returned. '
'You can have as many `pattern, value` pairs as you want')

def evaluate(self, formatter, kwargs, mi, locals, val, *args):
if (len(args) % 2) != 1:
raise ValueError(_('switch requires an even number of arguments'))
i = 0 while i < len(args):
if i + 1 >= len(args):
return args[i]
if re.search(args[i], val, flags=re.I):
return args[i+1]
i += 2

[dokumentace]
class BuiltinSwitchIf(BuiltinFormatterFunction):
name = 'switch_if'
arg_count = -1 category = 'Iterating over values'
__doc__ = doc = _('switch_if([test_expression, value_expression,]+ else_expression) -- '
'for each "test_expression, value_expression" pair, checks if test_expression '
'is True (non-empty) and if so returns the result of value_expression. '
'If no test_expression is True then the result of else_expression is returned. '
'You can have as many "test_expression, value_expression" pairs as you want.')

def evaluate(self, formatter, kwargs, mi, locals, *args):
if (len(args) % 2) != 1:
raise ValueError(_('switch_if requires an odd number of arguments'))
# We shouldn't get here because the function is inlined. However, someone
# might call it directly.
i = 0 while i < len(args):
if i + 1 >= len(args):
return args[i]
if args[i]:
return args[i+1]
i += 2

[dokumentace]
class BuiltinStrcatMax(BuiltinFormatterFunction):
name = 'strcat_max'
arg_count = -1 category = 'String manipulation'
__doc__ = doc = _('strcat_max(max, string1 [, prefix2, string2]*) -- '
'Returns a string formed by concatenating the arguments. The '
'returned value is initialized to string1. `Prefix, string` '
'pairs are added to the end of the value as long as the '
'resulting string length is less than `max`. String1 is returned '
'even if string1 is longer than max. You can pass as many '
'`prefix, string` pairs as you wish.')

def evaluate(self, formatter, kwargs, mi, locals, *args):
if len(args) < 2:
raise ValueError(_('strcat_max requires 2 or more arguments'))
if (len(args) % 2) != 0:
raise ValueError(_('strcat_max requires an even number of arguments'))
try:
max = int(args[0])
except:
raise ValueError(_('first argument to strcat_max must be an integer'))

i = 2 result = args[1]
try:
while i < len(args):
if (len(result) + len(args[i]) + len(args[i+1])) > max:
break result = result + args[i] + args[i+1]
i += 2 except:
pass return result.strip()

[dokumentace]
class BuiltinInList(BuiltinFormatterFunction):
name = 'in_list'
arg_count = -1 category = 'List lookup'
__doc__ = doc = _('in_list(val, separator, [ pattern, found_val, ]+ not_found_val) -- '
'treating val as a list of items separated by separator, '
'if the pattern matches any of the list values then return found_val.'
'If the pattern matches no list value then return '
'not_found_val. The pattern and found_value pairs can be repeated as '
'many times as desired. The patterns are checked in order. The '
'found_val for the first match is returned. '
'Aliases: in_list(), list_contains()')
aliases = ['list_contains']

def evaluate(self, formatter, kwargs, mi, locals, val, sep, *args):
if (len(args) % 2) != 1:
raise ValueError(_('in_list requires an odd number of arguments'))
l = [v.strip() for v in val.split(sep) if v.strip()]
i = 0 while i < len(args):
if i + 1 >= len(args):
return args[i]
sf = args[i]
fv = args[i+1]
if l:
for v in l:
if re.search(sf, v, flags=re.I):
return fv i += 2

[dokumentace]
class BuiltinStrInList(BuiltinFormatterFunction):
name = 'str_in_list'
arg_count = -1 category = 'List lookup'
__doc__ = doc = _('str_in_list(val, separator, [string, found_val, ]+ not_found_val) -- '
'treating val as a list of items separated by separator, if the '
'string matches any of the list values then return found_val.'
'If the string matches no list value then return '
'not_found_val. The comparison is exact match (not contains) and is '
'case insensitive. The string and found_value pairs can be repeated as '
'many times as desired. The patterns are checked in order. The '
'found_val for the first match is returned.')

def evaluate(self, formatter, kwargs, mi, locals, val, sep, *args):
if (len(args) % 2) != 1:
raise ValueError(_('str_in_list requires an odd number of arguments'))
l = [v.strip() for v in val.split(sep) if v.strip()]
i = 0 while i < len(args):
if i + 1 >= len(args):
return args[i]
sf = args[i]
fv = args[i+1]
c = [v.strip() for v in sf.split(sep) if v.strip()]
if l:
for v in l:
for t in c:
if strcmp(t, v) == 0:
return fv i += 2

[dokumentace]
class BuiltinIdentifierInList(BuiltinFormatterFunction):
name = 'identifier_in_list'
arg_count = -1 category = 'List lookup'
__doc__ = doc = _('identifier_in_list(val, id_name [, found_val, not_found_val]) -- '
'treat val as a list of identifiers separated by commas. An identifier '
'has the format "id_name:value". The id_name parameter is the id_name '
'text to search for, either "id_name" or "id_name:regexp". The first case '
'matches if there is any identifier matching that id_name. The second '
'case matches if id_name matches an identifier and the regexp '
'matches the identifier\'s value. If found_val and not_found_val '
'are provided then if there is a match then return found_val, otherwise '
'return not_found_val. If found_val and not_found_val are not '
'provided then if there is a match then return the identifier:value '
'pair, otherwise the empty string.')

def evaluate(self, formatter, kwargs, mi, locals, val, ident, *args):
if len(args) == 0:
fv_is_id = True nfv = ''
elif len(args) == 2:
fv_is_id = False fv = args[0]
nfv = args[1]
else:
raise ValueError(_("{} requires 2 or 4 arguments").format(self.name))

l = [v.strip() for v in val.split(',') if v.strip()]
(id_, __, regexp) = ident.partition(':')
if not id_:
return nfv for candidate in l:
i, __, v = candidate.partition(':')
if v and i == id_:
if not regexp or re.search(regexp, v, flags=re.I):
return candidate if fv_is_id else fv return nfv

[dokumentace]
class BuiltinRe(BuiltinFormatterFunction):
name = 're'
arg_count = 3 category = 'String manipulation'
__doc__ = doc = _('re(val, pattern, replacement) -- return val after applying '
'the regular expression. All instances of `pattern` are replaced '
'with `replacement`. As in all of calibre, these are '
'Python-compatible regular expressions')

def evaluate(self, formatter, kwargs, mi, locals, val, pattern, replacement):
return re.sub(pattern, replacement, val, flags=re.I)

[dokumentace]
class BuiltinReGroup(BuiltinFormatterFunction):
name = 're_group'
arg_count = -1 category = 'String manipulation'
__doc__ = doc = _('re_group(val, pattern [, template_for_group]*) -- '
'return a string made by applying the regular expression pattern '
'to the val and replacing each matched instance with the string '
'computed by replacing each matched group by the value returned '
'by the corresponding template. The original matched value for the '
'group is available as $. In template program mode, like for '
'the template and the eval functions, you use [[ for { and ]] for }.'
' The following example in template program mode looks for series '
'with more than one word and uppercases the first word: '
"{series:'re_group($, \"(\\S* )(.*)\", \"[[$:uppercase()]]\", \"[[$]]\")'}")

def evaluate(self, formatter, kwargs, mi, locals, val, pattern, *args):
from calibre.utils.formatter import EvalFormatter def repl(mo):
res = ''
if mo and mo.lastindex:
for dex in range(0, mo.lastindex):
gv = mo.group(dex+1)
if gv is None:
continue if len(args) > dex:
template = args[dex].replace('[[', '{').replace(']]', '}')
res += EvalFormatter().safe_format(template, {'$': gv},
'EVAL', None, strip_results=False)
else:
res += gv return res return re.sub(pattern, repl, val, flags=re.I)

[dokumentace]
class BuiltinSwapAroundComma(BuiltinFormatterFunction):
name = 'swap_around_comma'
arg_count = 1 category = 'String manipulation'
__doc__ = doc = _('swap_around_comma(val) -- given a value of the form '
'"B, A", return "A B". This is most useful for converting names '
'in LN, FN format to FN LN. If there is no comma, the function '
'returns val unchanged')

def evaluate(self, formatter, kwargs, mi, locals, val):
return re.sub(r'^(.*?),\s*(.*$)', r'\2 \1', val, flags=re.I).strip()

[dokumentace]
class BuiltinIfempty(BuiltinFormatterFunction):
name = 'ifempty'
arg_count = 2 category = 'If-then-else'
__doc__ = doc = _('ifempty(val, text if empty) -- return val if val is not empty, '
'otherwise return `text if empty`')

def evaluate(self, formatter, kwargs, mi, locals, val, value_if_empty):
if val:
return val else:
return value_if_empty

[dokumentace]
class BuiltinShorten(BuiltinFormatterFunction):
name = 'shorten'
arg_count = 4 category = 'String manipulation'
__doc__ = doc = _('shorten(val, left chars, middle text, right chars) -- Return a '
'shortened version of val, consisting of `left chars` '
'characters from the beginning of val, followed by '
'`middle text`, followed by `right chars` characters from '
'the end of the string. `Left chars` and `right chars` must be '
'integers. For example, assume the title of the book is '
'`Ancient English Laws in the Times of Ivanhoe`, and you want '
'it to fit in a space of at most 15 characters. If you use '
'{title:shorten(9,-,5)}, the result will be `Ancient E-anhoe`. '
'If the field\'s length is less than left chars + right chars + '
'the length of `middle text`, then the field will be used '
'intact. For example, the title `The Dome` would not be changed.')

def evaluate(self, formatter, kwargs, mi, locals, val, leading, center_string, trailing):
l = max(0, int(leading))
t = max(0, int(trailing))
if len(val) > l + len(center_string) + t:
return val[0:l] + center_string + ('' if t == 0 else val[-t:])
else:
return val

[dokumentace]
class BuiltinCount(BuiltinFormatterFunction):
name = 'count'
arg_count = 2 category = 'List manipulation'
aliases = ['list_count']

__doc__ = doc = _('count(val, separator) -- interprets the value as a list of items '
'separated by `separator`, returning the number of items in the '
'list. Most lists use a comma as the separator, but authors '
'uses an ampersand. Examples: {tags:count(,)}, {authors:count(&)}. '
'Aliases: count(), list_count()')

def evaluate(self, formatter, kwargs, mi, locals, val, sep):
return str(len([v for v in val.split(sep) if v]))

[dokumentace]
class BuiltinListCountMatching(BuiltinFormatterFunction):
name = 'list_count_matching'
arg_count = 3 category = 'List manipulation'
aliases = ['count_matching']

__doc__ = doc = _('list_count_matching(list, pattern, separator) -- '
"interprets 'list' as a list of items separated by 'separator', "
'returning the number of items in the list that match the regular '
"expression 'pattern'. Aliases: list_count_matching(), count_matching()")

def evaluate(self, formatter, kwargs, mi, locals, list_, pattern, sep):
res = 0 for v in [x.strip() for x in list_.split(sep) if x.strip()]:
if re.search(pattern, v, flags=re.I):
res += 1 return str(res)

[dokumentace]
class BuiltinListitem(BuiltinFormatterFunction):
name = 'list_item'
arg_count = 3 category = 'List lookup'
__doc__ = doc = _('list_item(val, index, separator) -- interpret the value as a list of '
'items separated by `separator`, returning the `index`th item. '
'The first item is number zero. The last item can be returned '
'using `list_item(-1,separator)`. If the item is not in the list, '
'then the empty value is returned. The separator has the same '
'meaning as in the count function.')

def evaluate(self, formatter, kwargs, mi, locals, val, index, sep):
if not val:
return ''
index = int(index)
val = val.split(sep)
try:
return val[index].strip()
except:
return ''

[dokumentace]
class BuiltinSelect(BuiltinFormatterFunction):
name = 'select'
arg_count = 2 category = 'List lookup'
__doc__ = doc = _('select(val, key) -- interpret the value as a comma-separated list '
'of items, with the items being "id:value". Find the pair with the '
'id equal to key, and return the corresponding value. Returns the '
'empty string if no match is found.'
)

def evaluate(self, formatter, kwargs, mi, locals, val, key):
if not val:
return ''
vals = [v.strip() for v in val.split(',')]
tkey = key+':'
for v in vals:
if v.startswith(tkey):
return v[len(tkey):]
return ''

[dokumentace]
class BuiltinApproximateFormats(BuiltinFormatterFunction):
name = 'approximate_formats'
arg_count = 0 category = 'Get values from metadata'
__doc__ = doc = _('approximate_formats() -- return a comma-separated '
'list of formats that at one point were associated with the '
'book. There is no guarantee that this list is correct, '
'although it probably is. '
'This function can be called in template program mode using '
'the template "{:\'approximate_formats()\'}". '
'Note that format names are always uppercase, as in EPUB. '
'This function works only in the GUI. If you want to use these values '
'in save-to-disk or send-to-device templates then you '
'must make a custom "Column built from other columns", use '
'the function in that column\'s template, and use that '
'column\'s value in your save/send templates'
)

def evaluate(self, formatter, kwargs, mi, locals):
if hasattr(mi, '_proxy_metadata'):
fmt_data = mi._proxy_metadata.db_approx_formats if not fmt_data:
return ''
data = sorted(fmt_data)
return ','.join(v.upper() for v in data)
self.only_in_gui_error()

[dokumentace]
class BuiltinFormatsModtimes(BuiltinFormatterFunction):
name = 'formats_modtimes'
arg_count = 1 category = 'Get values from metadata'
__doc__ = doc = _('formats_modtimes(date_format) -- return a comma-separated '
'list of colon-separated items representing modification times '
'for the formats of a book. The date_format parameter '
'specifies how the date is to be formatted. See the '
'format_date function for details. You can use the select '
'function to get the mod time for a specific '
'format. Note that format names are always uppercase, '
'as in EPUB.'
)

def evaluate(self, formatter, kwargs, mi, locals, fmt):
fmt_data = mi.get('format_metadata', {})
try:
data = sorted(fmt_data.items(), key=lambda x:x[1]['mtime'], reverse=True)
return ','.join(k.upper()+':'+format_date(v['mtime'], fmt)
for k,v in data)
except:
return ''

[dokumentace]
class BuiltinFormatsSizes(BuiltinFormatterFunction):
name = 'formats_sizes'
arg_count = 0 category = 'Get values from metadata'
__doc__ = doc = _('formats_sizes() -- return a comma-separated list of '
'colon-separated items representing sizes in bytes '
'of the formats of a book. You can use the select '
'function to get the size for a specific '
'format. Note that format names are always uppercase, '
'as in EPUB.'
)

def evaluate(self, formatter, kwargs, mi, locals):
fmt_data = mi.get('format_metadata', {})
try:
return ','.join(k.upper()+':'+str(v['size']) for k,v in iteritems(fmt_data))
except:
return ''

[dokumentace]
class BuiltinFormatsPaths(BuiltinFormatterFunction):
name = 'formats_paths'
arg_count = 0 category = 'Get values from metadata'
__doc__ = doc = _('formats_paths() -- return a comma-separated list of '
'colon-separated items representing full path to '
'the formats of a book. You can use the select '
'function to get the path for a specific '
'format. Note that format names are always uppercase, '
'as in EPUB.')

def evaluate(self, formatter, kwargs, mi, locals):
fmt_data = mi.get('format_metadata', {})
try:
return ','.join(k.upper()+':'+str(v['path']) for k,v in iteritems(fmt_data))
except:
return ''

[dokumentace]
class BuiltinHumanReadable(BuiltinFormatterFunction):
name = 'human_readable'
arg_count = 1 category = 'Formatting values'
__doc__ = doc = _('human_readable(v) -- return a string '
'representing the number v in KB, MB, GB, etc.'
)

def evaluate(self, formatter, kwargs, mi, locals, val):
try:
return human_readable(round(float(val)))
except:
return ''

[dokumentace]
class BuiltinFormatNumber(BuiltinFormatterFunction):
name = 'format_number'
arg_count = 2 category = 'Formatting values'
__doc__ = doc = _('format_number(v, template) -- format the number v using '
'a Python formatting template such as "{0:5.2f}" or '
'"{0:,d}" or "${0:5,.2f}". The field_name part of the '
'template must be a 0 (zero) (the "{0:" in the above examples). '
'See the template language and Python documentation for more '
'examples. You can leave off the leading "{0:" and trailing '
'"}" if the template contains only a format. Returns the empty '
'string if formatting fails.'
)

def evaluate(self, formatter, kwargs, mi, locals, val, template):
if val == '' or val == 'None':
return ''
if '{' not in template:
template = '{0:' + template + '}'
try:
v1 = float(val)
except:
return ''
try: # Try formatting the value as a float return template.format(v1)
except:
pass try: # Try formatting the value as an int v2 = trunc(v1)
if v2 == v1:
return template.format(v2)
except:
pass return ''

[dokumentace]
class BuiltinSublist(BuiltinFormatterFunction):
name = 'sublist'
arg_count = 4 category = 'List manipulation'
__doc__ = doc = _('sublist(val, start_index, end_index, separator) -- interpret the '
'value as a list of items separated by `separator`, returning a '
'new list made from the `start_index` to the `end_index` item. '
'The first item is number zero. If an index is negative, then it '
'counts from the end of the list. As a special case, an end_index '
'of zero is assumed to be the length of the list. Examples using '
'basic template mode and assuming that the tags column (which is '
'comma-separated) contains "A, B, C": '
'{tags:sublist(0,1,\\\\,)} returns "A". '
'{tags:sublist(-1,0,\\\\,)} returns "C". '
'{tags:sublist(0,-1,\\\\,)} returns "A, B".'
)

def evaluate(self, formatter, kwargs, mi, locals, val, start_index, end_index, sep):
if not val:
return ''
si = int(start_index)
ei = int(end_index)
# allow empty list items so counts are what the user expects val = [v.strip() for v in val.split(sep)]

if sep == ',':
sep = ', '
try:
if ei == 0:
return sep.join(val[si:])
else:
return sep.join(val[si:ei])
except:
return ''

[dokumentace]
class BuiltinSubitems(BuiltinFormatterFunction):
name = 'subitems'
arg_count = 3 category = 'List manipulation'
__doc__ = doc = _('subitems(val, start_index, end_index) -- This function is used to '
'break apart lists of items such as genres. It interprets the value '
'as a comma-separated list of items, where each item is a period-'
'separated list. Returns a new list made by first finding all the '
'period-separated items, then for each such item extracting the '
'`start_index` to the `end_index` components, then combining '
'the results back together. The first component in a period-'
'separated list has an index of zero. If an index is negative, '
'then it counts from the end of the list. As a special case, an '
'end_index of zero is assumed to be the length of the list. '
'Example using basic template mode and assuming a #genre value of '
'"A.B.C": {#genre:subitems(0,1)} returns "A". {#genre:subitems(0,2)} '
'returns "A.B". {#genre:subitems(1,0)} returns "B.C". Assuming a #genre '
'value of "A.B.C, D.E.F", {#genre:subitems(0,1)} returns "A, D". '
'{#genre:subitems(0,2)} returns "A.B, D.E"')

period_pattern = re.compile(r'(?<=[^\.\s])\.(?=[^\.\s])', re.U)

def evaluate(self, formatter, kwargs, mi, locals, val, start_index, end_index):
if not val:
return ''
si = int(start_index)
ei = int(end_index)
has_periods = '.' in val items = [v.strip() for v in val.split(',') if v.strip()]
rv = set()
for item in items:
if has_periods and '.' in item:
components = self.period_pattern.split(item)
else:
components = [item]
try:
if ei == 0:
t = '.'.join(components[si:]).strip()
else:
t = '.'.join(components[si:ei]).strip()
if t:
rv.add(t)
except:
pass return ', '.join(sorted(rv, key=sort_key))

[dokumentace]
class BuiltinFormatDate(BuiltinFormatterFunction):
name = 'format_date'
arg_count = 2 category = 'Formatting values'
__doc__ = doc = _('format_date(val, format_string) -- format the value, '
'which must be a date, using the format_string, returning a string. '
'The formatting codes are: '
'd : the day as number without a leading zero (1 to 31) '
'dd : the day as number with a leading zero (01 to 31) '
'ddd : the abbreviated localized day name (e.g. "Mon" to "Sun"). '
'dddd : the long localized day name (e.g. "Monday" to "Sunday"). '
'M : the month as number without a leading zero (1 to 12). '
'MM : the month as number with a leading zero (01 to 12) '
'MMM : the abbreviated localized month name (e.g. "Jan" to "Dec"). '
'MMMM : the long localized month name (e.g. "January" to "December"). '
'yy : the year as two digit number (00 to 99). '
'yyyy : the year as four digit number. '
'h : the hours without a leading 0 (0 to 11 or 0 to 23, depending on am/pm) '
'hh : the hours with a leading 0 (00 to 11 or 00 to 23, depending on am/pm) '
'm : the minutes without a leading 0 (0 to 59) '
'mm : the minutes with a leading 0 (00 to 59) '
's : the seconds without a leading 0 (0 to 59) '
'ss : the seconds with a leading 0 (00 to 59) '
'ap : use a 12-hour clock instead of a 24-hour clock, with "ap" replaced by the localized string for am or pm '
'AP : use a 12-hour clock instead of a 24-hour clock, with "AP" replaced by the localized string for AM or PM '
'iso : the date with time and timezone. Must be the only format present '
'to_number: the date as a floating point number '
'from_number[:fmt]: format the timestamp using fmt if present otherwise iso')

def evaluate(self, formatter, kwargs, mi, locals, val, format_string):
if not val or val == 'None':
return ''
try:
if format_string == 'to_number':
s = parse_date(val).timestamp()
elif format_string.startswith('from_number'):
val = datetime.fromtimestamp(float(val))
f = format_string[12:]
s = format_date(val, f if f else 'iso')
else:
s = format_date(parse_date(val), format_string)
return s except:
s = 'BAD DATE'
return s

[dokumentace]
class BuiltinFormatDateField(BuiltinFormatterFunction):
name = 'format_date_field'
arg_count = 2 category = 'Formatting values'
__doc__ = doc = _("format_date_field(field_name, format_string) -- format "
"the value in the field 'field_name', which must be the lookup name "
"of date field, either standard or custom. See 'format_date' for "
"the formatting codes. This function is much faster than format_date "
"and should be used when you are formatting the value in a field "
"(column). It can't be used for computed dates or dates in string "
"variables. Example: format_date_field('pubdate', 'yyyy.MM.dd')")

def evaluate(self, formatter, kwargs, mi, locals, field, format_string):
try:
if field not in mi.all_field_keys():
return _('Unknown field %s passed to function %s')%(field, 'format_date_field')
val = mi.get(field, None)
if val is None:
s = ''
elif format_string == 'to_number':
s = val.timestamp()
elif format_string.startswith('from_number'):
val = datetime.fromtimestamp(float(val))
f = format_string[12:]
s = format_date(val, f if f else 'iso')
else:
s = format_date(val, format_string)
return s except:
traceback.print_exc()
s = 'BAD DATE'
return s

[dokumentace]
class BuiltinUppercase(BuiltinFormatterFunction):
name = 'uppercase'
arg_count = 1 category = 'String case changes'
__doc__ = doc = _('uppercase(val) -- return val in upper case')

def evaluate(self, formatter, kwargs, mi, locals, val):
return val.upper()

[dokumentace]
class BuiltinLowercase(BuiltinFormatterFunction):
name = 'lowercase'
arg_count = 1 category = 'String case changes'
__doc__ = doc = _('lowercase(val) -- return val in lower case')

def evaluate(self, formatter, kwargs, mi, locals, val):
return val.lower()

[dokumentace]
class BuiltinTitlecase(BuiltinFormatterFunction):
name = 'titlecase'
arg_count = 1 category = 'String case changes'
__doc__ = doc = _('titlecase(val) -- return val in title case')

def evaluate(self, formatter, kwargs, mi, locals, val):
return titlecase(val)

[dokumentace]
class BuiltinCapitalize(BuiltinFormatterFunction):
name = 'capitalize'
arg_count = 1 category = 'String case changes'
__doc__ = doc = _('capitalize(val) -- return val capitalized')

def evaluate(self, formatter, kwargs, mi, locals, val):
return capitalize(val)

[dokumentace]
class BuiltinBooksize(BuiltinFormatterFunction):
name = 'booksize'
arg_count = 0 category = 'Get values from metadata'
__doc__ = doc = _('booksize() -- return value of the size field. '
'This function works only in the GUI. If you want to use this value '
'in save-to-disk or send-to-device templates then you '
'must make a custom "Column built from other columns", use '
'the function in that column\'s template, and use that '
'column\'s value in your save/send templates')

def evaluate(self, formatter, kwargs, mi, locals):
if hasattr(mi, '_proxy_metadata'):
try:
v = mi._proxy_metadata.book_size if v is not None:
return str(mi._proxy_metadata.book_size)
return ''
except:
pass return ''
self.only_in_gui_error()

[dokumentace]
class BuiltinOndevice(BuiltinFormatterFunction):
name = 'ondevice'
arg_count = 0 category = 'Get values from metadata'
__doc__ = doc = _('ondevice() -- return Yes if ondevice is set, otherwise return '
'the empty string. This function works only in the GUI. If you want to '
'use this value in save-to-disk or send-to-device templates then you '
'must make a custom "Column built from other columns", use '
'the function in that column\'s template, and use that '
'column\'s value in your save/send templates')

def evaluate(self, formatter, kwargs, mi, locals):
if hasattr(mi, '_proxy_metadata'):
if mi._proxy_metadata.ondevice_col:
return _('Yes')
return ''
self.only_in_gui_error()

[dokumentace]
class BuiltinAnnotationCount(BuiltinFormatterFunction):
name = 'annotation_count'
arg_count = 0 category = 'Get values from metadata'
__doc__ = doc = _('annotation_count() -- return the total number of annotations '
'of all types attached to the current book. '
'This function works only in the GUI.')

def evaluate(self, formatter, kwargs, mi, locals):
c = self.get_database(mi).new_api.annotation_count_for_book(mi.id)
return '' if c == 0 else str(c)

[dokumentace]
class BuiltinIsMarked(BuiltinFormatterFunction):
name = 'is_marked'
arg_count = 0 category = 'Get values from metadata'
__doc__ = doc = _("is_marked() -- check whether the book is 'marked' in "
"calibre. If it is then return the value of the mark, "
"either 'true' or the comma-separated list of named "
"marks. Returns '' if the book is not marked.")

def evaluate(self, formatter, kwargs, mi, locals):
c = self.get_database(mi).data.get_marked(mi.id)
return c if c else ''

[dokumentace]
class BuiltinSeriesSort(BuiltinFormatterFunction):
name = 'series_sort'
arg_count = 0 category = 'Get values from metadata'
__doc__ = doc = _('series_sort() -- return the series sort value')

def evaluate(self, formatter, kwargs, mi, locals):
if mi.series:
langs = mi.languages lang = langs[0] if langs else None return title_sort(mi.series, lang=lang)
return ''

[dokumentace]
class BuiltinHasCover(BuiltinFormatterFunction):
name = 'has_cover'
arg_count = 0 category = 'Get values from metadata'
__doc__ = doc = _('has_cover() -- return Yes if the book has a cover, '
'otherwise return the empty string')

def evaluate(self, formatter, kwargs, mi, locals):
if mi.has_cover:
return _('Yes')
return ''

[dokumentace]
class BuiltinFirstNonEmpty(BuiltinFormatterFunction):
name = 'first_non_empty'
arg_count = -1 category = 'Iterating over values'
__doc__ = doc = _('first_non_empty(value [, value]*) -- '
'returns the first value that is not empty. If all values are '
'empty, then the empty string is returned. '
'You can have as many values as you want.')

def evaluate(self, formatter, kwargs, mi, locals, *args):
i = 0 while i < len(args):
if args[i]:
return args[i]
i += 1 return ''

[dokumentace]
class BuiltinAnd(BuiltinFormatterFunction):
name = 'and'
arg_count = -1 category = 'Boolean'
__doc__ = doc = _('and(value [, value]*) -- '
'returns the string "1" if all values are not empty, otherwise '
'returns the empty string. This function works well with test or '
'first_non_empty. You can have as many values as you want. In many '
'cases the && operator can replace this function.')

def evaluate(self, formatter, kwargs, mi, locals, *args):
i = 0 while i < len(args):
if not args[i]:
return ''
i += 1 return '1'

[dokumentace]
class BuiltinOr(BuiltinFormatterFunction):
name = 'or'
arg_count = -1 category = 'Boolean'
__doc__ = doc = _('or(value [, value]*) -- '
'returns the string "1" if any value is not empty, otherwise '
'returns the empty string. This function works well with test or '
'first_non_empty. You can have as many values as you want. In many '
'cases the || operator can replace this function.')

def evaluate(self, formatter, kwargs, mi, locals, *args):
i = 0 while i < len(args):
if args[i]:
return '1'
i += 1 return ''

[dokumentace]
class BuiltinNot(BuiltinFormatterFunction):
name = 'not'
arg_count = 1 category = 'Boolean'
__doc__ = doc = _('not(value) -- '
'returns the string "1" if the value is empty, otherwise '
'returns the empty string. This function works well with test or '
'first_non_empty. In many cases the ! operator can replace this '
'function.')

def evaluate(self, formatter, kwargs, mi, locals, val):
return '' if val else '1'

[dokumentace]
class BuiltinListJoin(BuiltinFormatterFunction):
name = 'list_join'
arg_count = -1 category = 'List manipulation'
__doc__ = doc = _("list_join(with_separator, list1, separator1 [, list2, separator2]*) -- "
"return a list made by joining the items in the source lists "
"(list1, etc) using with_separator between the items in the "
"result list. Items in each source list[123...] are separated "
"by the associated separator[123...]. A list can contain "
"zero values. It can be a field like publisher that is "
"single-valued, effectively a one-item list. Duplicates "
"are removed using a case-insensitive comparison. Items are "
"returned in the order they appear in the source lists. "
"If items on lists differ only in letter case then the last "
"is used. All separators can be more than one character.\n"
"Example:") + "\n\n" + (
" program:\n"
" list_join('#@#', $authors, '&', $tags, ',')\n\n") + _(
"You can use list_join on the results of previous "
"calls to list_join as follows:") + "\n" + (
" program:\n\n"
" a = list_join('#@#', $authors, '&', $tags, ',');\n"
" b = list_join('#@#', a, '#@#', $#genre, ',', $#people, '&')\n\n") + _(
"You can use expressions to generate a list. For example, "
"assume you want items for authors and #genre, but "
"with the genre changed to the word 'Genre: ' followed by "
"the first letter of the genre, i.e. the genre 'Fiction' "
"becomes 'Genre: F'. The following will do that:") + "\n" + (
" program:\n"
" list_join('#@#', $authors, '&', list_re($#genre, ',', '^(.).*$', 'Genre: \\1'), ',')")

def evaluate(self, formatter, kwargs, mi, locals, with_separator, *args):
if len(args) % 2 != 0:
raise ValueError(
_("Invalid 'List, separator' pairs. Every list must have one "
"associated separator"))

# Starting in python 3.7 dicts preserve order so we don't need OrderedDict result = dict()
i = 0 while i < len(args):
lst = [v.strip() for v in args[i].split(args[i+1]) if v.strip()]
result.update({item.lower():item for item in lst})
i += 2 return with_separator.join(result.values())

[dokumentace]
class BuiltinListUnion(BuiltinFormatterFunction):
name = 'list_union'
arg_count = 3 category = 'List manipulation'
__doc__ = doc = _('list_union(list1, list2, separator) -- '
'return a list made by merging the items in list1 and list2, '
'removing duplicate items using a case-insensitive comparison. If '
'items differ in case, the one in list1 is used. '
'The items in list1 and list2 are separated by separator, as are '
'the items in the returned list. Aliases: list_union(), merge_lists()')
aliases = ['merge_lists']

def evaluate(self, formatter, kwargs, mi, locals, list1, list2, separator):
res = {icu_lower(l.strip()): l.strip() for l in list2.split(separator) if l.strip()}
res.update({icu_lower(l.strip()): l.strip() for l in list1.split(separator) if l.strip()})
if separator == ',':
separator = ', '
return separator.join(res.values())

[dokumentace]
class BuiltinRange(BuiltinFormatterFunction):
name = 'range'
arg_count = -1 category = 'List manipulation'
__doc__ = doc = _("range(start, stop, step, limit) -- "
"returns a list of numbers generated by looping over the "
"range specified by the parameters start, stop, and step, "
"with a maximum length of limit. The first value produced "
"is 'start'. Subsequent values next_v are "
"current_v+step. The loop continues while "
"next_v < stop assuming step is positive, otherwise "
"while next_v > stop. An empty list is produced if "
"start fails the test: start>=stop if step "
"is positive. The limit sets the maximum length of "
"the list and has a default of 1000. The parameters "
"start, step, and limit are optional. "
"Calling range() with one argument specifies stop. "
"Two arguments specify start and stop. Three arguments "
"specify start, stop, and step. Four "
"arguments specify start, stop, step and limit. "
"Examples: range(5) -> '0,1,2,3,4'. range(0,5) -> '0,1,2,3,4'. "
"range(-1,5) -> '-1,0,1,2,3,4'. range(1,5) -> '1,2,3,4'. "
"range(1,5,2) -> '1,3'. range(1,5,2,5) -> '1,3'. "
"range(1,5,2,1) -> error(limit exceeded).")

def evaluate(self, formatter, kwargs, mi, locals, *args):
limit_val = 1000 start_val = 0 step_val = 1 if len(args) == 1:
stop_val = int(args[0] if args[0] and args[0] != 'None' else 0)
elif len(args) == 2:
start_val = int(args[0] if args[0] and args[0] != 'None' else 0)
stop_val = int(args[1] if args[1] and args[1] != 'None' else 0)
elif len(args) >= 3:
start_val = int(args[0] if args[0] and args[0] != 'None' else 0)
stop_val = int(args[1] if args[1] and args[1] != 'None' else 0)
step_val = int(args[2] if args[2] and args[2] != 'None' else 0)
if len(args) > 3:
limit_val = int(args[3] if args[3] and args[3] != 'None' else 0)
r = range(start_val, stop_val, step_val)
if len(r) > limit_val:
raise ValueError(
_("{0}: length ({1}) longer than limit ({2})").format(
'range', len(r), str(limit_val)))
return ', '.join([str(v) for v in r])

[dokumentace]
class BuiltinListRemoveDuplicates(BuiltinFormatterFunction):
name = 'list_remove_duplicates'
arg_count = 2 category = 'List manipulation'
__doc__ = doc = _('list_remove_duplicates(list, separator) -- '
'return a list made by removing duplicate items in the source list. '
'If items differ only in case, the last of them is returned. '
'The items in source list are separated by separator, as are '
'the items in the returned list.')

def evaluate(self, formatter, kwargs, mi, locals, list_, separator):
res = {icu_lower(l.strip()): l.strip() for l in list_.split(separator) if l.strip()}
if separator == ',':
separator = ', '
return separator.join(res.values())

[dokumentace]
class BuiltinListDifference(BuiltinFormatterFunction):
name = 'list_difference'
arg_count = 3 category = 'List manipulation'
__doc__ = doc = _('list_difference(list1, list2, separator) -- '
'return a list made by removing from list1 any item found in list2, '
'using a case-insensitive comparison. The items in list1 and list2 '
'are separated by separator, as are the items in the returned list.')

def evaluate(self, formatter, kwargs, mi, locals, list1, list2, separator):
l1 = [l.strip() for l in list1.split(separator) if l.strip()]
l2 = {icu_lower(l.strip()) for l in list2.split(separator) if l.strip()}

res = []
for i in l1:
if icu_lower(i) not in l2 and i not in res:
res.append(i)
if separator == ',':
return ', '.join(res)
return separator.join(res)

[dokumentace]
class BuiltinListIntersection(BuiltinFormatterFunction):
name = 'list_intersection'
arg_count = 3 category = 'List manipulation'
__doc__ = doc = _('list_intersection(list1, list2, separator) -- '
'return a list made by removing from list1 any item not found in list2, '
'using a case-insensitive comparison. The items in list1 and list2 '
'are separated by separator, as are the items in the returned list.')

def evaluate(self, formatter, kwargs, mi, locals, list1, list2, separator):
l1 = [l.strip() for l in list1.split(separator) if l.strip()]
l2 = {icu_lower(l.strip()) for l in list2.split(separator) if l.strip()}

res = []
for i in l1:
if icu_lower(i) in l2 and i not in res:
res.append(i)
if separator == ',':
return ', '.join(res)
return separator.join(res)

[dokumentace]
class BuiltinListSort(BuiltinFormatterFunction):
name = 'list_sort'
arg_count = 3 category = 'List manipulation'
__doc__ = doc = _('list_sort(list, direction, separator) -- '
'return list sorted using a case-insensitive sort. If direction is '
'zero, the list is sorted ascending, otherwise descending. The list items '
'are separated by separator, as are the items in the returned list.')

def evaluate(self, formatter, kwargs, mi, locals, list1, direction, separator):
res = [l.strip() for l in list1.split(separator) if l.strip()]
if separator == ',':
return ', '.join(sorted(res, key=sort_key, reverse=direction != "0"))
return separator.join(sorted(res, key=sort_key, reverse=direction != "0"))

[dokumentace]
class BuiltinListEquals(BuiltinFormatterFunction):
name = 'list_equals'
arg_count = 6 category = 'List manipulation'
__doc__ = doc = _('list_equals(list1, sep1, list2, sep2, yes_val, no_val) -- '
'return yes_val if list1 and list2 contain the same items, '
'otherwise return no_val. The items are determined by splitting '
'each list using the appropriate separator character (sep1 or '
'sep2). The order of items in the lists is not relevant. '
'The comparison is case insensitive.')

def evaluate(self, formatter, kwargs, mi, locals, list1, sep1, list2, sep2, yes_val, no_val):
s1 = {icu_lower(l.strip()) for l in list1.split(sep1) if l.strip()}
s2 = {icu_lower(l.strip()) for l in list2.split(sep2) if l.strip()}
if s1 == s2:
return yes_val return no_val

[dokumentace]
class BuiltinListRe(BuiltinFormatterFunction):
name = 'list_re'
arg_count = 4 category = 'List manipulation'
__doc__ = doc = _('list_re(src_list, separator, include_re, opt_replace) -- '
'Construct a list by first separating src_list into items using '
'the separator character. For each item in the list, check if it '
'matches include_re. If it does, then add it to the list to be '
'returned. If opt_replace is not the empty string, then apply the '
'replacement before adding the item to the returned list.')

def evaluate(self, formatter, kwargs, mi, locals, src_list, separator, include_re, opt_replace):
l = [l.strip() for l in src_list.split(separator) if l.strip()]
res = []
for item in l:
if re.search(include_re, item, flags=re.I) is not None:
if opt_replace:
item = re.sub(include_re, opt_replace, item)
for i in [t.strip() for t in item.split(separator) if t.strip()]:
if i not in res:
res.append(i)
if separator == ',':
return ', '.join(res)
return separator.join(res)

[dokumentace]
class BuiltinListReGroup(BuiltinFormatterFunction):
name = 'list_re_group'
arg_count = -1 category = 'List manipulation'
__doc__ = doc = _('list_re_group(src_list, separator, include_re, search_re [, group_template]+) -- '
'Like list_re except replacements are not optional. It '
'uses re_group(list_item, search_re, group_template, ...) when '
'doing the replacements on the resulting list.')

def evaluate(self, formatter, kwargs, mi, locals, src_list, separator, include_re, search_re, *args):
from calibre.utils.formatter import EvalFormatter l = [l.strip() for l in src_list.split(separator) if l.strip()]
res = []
for item in l:
def repl(mo):
newval = ''
if mo and mo.lastindex:
for dex in range(0, mo.lastindex):
gv = mo.group(dex+1)
if gv is None:
continue if len(args) > dex:
template = args[dex].replace('[[', '{').replace(']]', '}')
newval += EvalFormatter().safe_format(template, {'$': gv},
'EVAL', None, strip_results=False)
else:
newval += gv return newval if re.search(include_re, item, flags=re.I) is not None:
item = re.sub(search_re, repl, item, flags=re.I)
for i in [t.strip() for t in item.split(separator) if t.strip()]:
if i not in res:
res.append(i)
if separator == ',':
return ', '.join(res)
return separator.join(res)

[dokumentace]
class BuiltinToday(BuiltinFormatterFunction):
name = 'today'
arg_count = 0 category = 'Date functions'
__doc__ = doc = _('today() -- '
'return a date string for today. This value is designed for use in '
'format_date or days_between, but can be manipulated like any '
'other string. The date is in ISO format.')

def evaluate(self, formatter, kwargs, mi, locals):
return format_date(now(), 'iso')

[dokumentace]
class BuiltinDaysBetween(BuiltinFormatterFunction):
name = 'days_between'
arg_count = 2 category = 'Date functions'
__doc__ = doc = _('days_between(date1, date2) -- '
'return the number of days between date1 and date2. The number is '
'positive if date1 is greater than date2, otherwise negative. If '
'either date1 or date2 are not dates, the function returns the '
'empty string.')

def evaluate(self, formatter, kwargs, mi, locals, date1, date2):
try:
d1 = parse_date(date1)
if d1 == UNDEFINED_DATE:
return ''
d2 = parse_date(date2)
if d2 == UNDEFINED_DATE:
return ''
except:
return ''
i = d1 - d2 return '%.1f'%(i.days + (i.seconds/(24.0*60.0*60.0)))

[dokumentace]
class BuiltinDateArithmetic(BuiltinFormatterFunction):
name = 'date_arithmetic'
arg_count = -1 category = 'Date functions'
__doc__ = doc = _('date_arithmetic(date, calc_spec, fmt) -- '
"Calculate a new date from 'date' using 'calc_spec'. Return the "
"new date formatted according to optional 'fmt': if not supplied "
"then the result will be in iso format. The calc_spec is a string "
"formed by concatenating pairs of 'vW' (valueWhat) where 'v' is a "
"possibly-negative number and W is one of the following letters: "
"s: add 'v' seconds to 'date' "
"m: add 'v' minutes to 'date' "
"h: add 'v' hours to 'date' "
"d: add 'v' days to 'date' "
"w: add 'v' weeks to 'date' "
"y: add 'v' years to 'date', where a year is 365 days. "
"Example: '1s3d-1m' will add 1 second, add 3 days, and subtract 1 "
"minute from 'date'.")

calc_ops = {
's': lambda v: timedelta(seconds=v),
'm': lambda v: timedelta(minutes=v),
'h': lambda v: timedelta(hours=v),
'd': lambda v: timedelta(days=v),
'w': lambda v: timedelta(weeks=v),
'y': lambda v: timedelta(days=v * 365),
}

def evaluate(self, formatter, kwargs, mi, locals, date, calc_spec, fmt=None):
try:
d = parse_date(date)
if d == UNDEFINED_DATE:
return ''
while calc_spec:
mo = re.match(r'([-+\d]+)([smhdwy])', calc_spec)
if mo is None:
raise ValueError(
_("{0}: invalid calculation specifier '{1}'").format(
'date_arithmetic', calc_spec))
d += self.calc_ops[mo[2]](int(mo[1]))
calc_spec = calc_spec[len(mo[0]):]
return format_date(d, fmt if fmt else 'iso')
except ValueError as e:
raise e except Exception as e:
traceback.print_exc()
raise ValueError(_("{0}: error: {1}").format('date_arithmetic', str(e)))

[dokumentace]
class BuiltinLanguageStrings(BuiltinFormatterFunction):
name = 'language_strings'
arg_count = 2 category = 'Get values from metadata'
__doc__ = doc = _('language_strings(lang_codes, localize) -- '
'return the strings for the language codes passed in lang_codes. '
'If localize is zero, return the strings in English. If '
'localize is not zero, return the strings in the language of '
'the current locale. Lang_codes is a comma-separated list.')

def evaluate(self, formatter, kwargs, mi, locals, lang_codes, localize):
retval = []
for c in [c.strip() for c in lang_codes.split(',') if c.strip()]:
try:
n = calibre_langcode_to_name(c, localize != '0')
if n:
retval.append(n)
except:
pass return ', '.join(retval)

[dokumentace]
class BuiltinLanguageCodes(BuiltinFormatterFunction):
name = 'language_codes'
arg_count = 1 category = 'Get values from metadata'
__doc__ = doc = _('language_codes(lang_strings) -- '
'return the language codes for the strings passed in lang_strings. '
'The strings must be in the language of the current locale. '
'Lang_strings is a comma-separated list.')

def evaluate(self, formatter, kwargs, mi, locals, lang_strings):
retval = []
for c in [c.strip() for c in lang_strings.split(',') if c.strip()]:
try:
cv = canonicalize_lang(c)
if cv:
retval.append(canonicalize_lang(cv))
except:
pass return ', '.join(retval)

[dokumentace]
class BuiltinCurrentLibraryName(BuiltinFormatterFunction):
name = 'current_library_name'
arg_count = 0 category = 'Get values from metadata'
__doc__ = doc = _('current_library_name() -- '
'return the last name on the path to the current calibre library. '
'This function can be called in template program mode using the '
'template "{:\'current_library_name()\'}".')

def evaluate(self, formatter, kwargs, mi, locals):
from calibre.library import current_library_name return current_library_name()

[dokumentace]
class BuiltinCurrentLibraryPath(BuiltinFormatterFunction):
name = 'current_library_path'
arg_count = 0 category = 'Get values from metadata'
__doc__ = doc = _('current_library_path() -- '
'return the path to the current calibre library. This function can '
'be called in template program mode using the template '
'"{:\'current_library_path()\'}".')

def evaluate(self, formatter, kwargs, mi, locals):
from calibre.library import current_library_path return current_library_path()

[dokumentace]
class BuiltinFinishFormatting(BuiltinFormatterFunction):
name = 'finish_formatting'
arg_count = 4 category = 'Formatting values'
__doc__ = doc = _('finish_formatting(val, fmt, prefix, suffix) -- apply the '
'format, prefix, and suffix to a value in the same way as '
'done in a template like `{series_index:05.2f| - |- }`. For '
'example, the following program produces the same output '
'as the above template: '
'program: finish_formatting(field("series_index"), "05.2f", " - ", " - ")')

def evaluate(self, formatter, kwargs, mi, locals_, val, fmt, prefix, suffix):
if not val:
return val return prefix + formatter._do_format(val, fmt) + suffix

[dokumentace]
class BuiltinVirtualLibraries(BuiltinFormatterFunction):
name = 'virtual_libraries'
arg_count = 0 category = 'Get values from metadata'
__doc__ = doc = _('virtual_libraries() -- return a comma-separated list of '
'Virtual libraries that contain this book. This function '
'works only in the GUI. If you want to use these values '
'in save-to-disk or send-to-device templates then you '
'must make a custom "Column built from other columns", use '
'the function in that column\'s template, and use that '
'column\'s value in your save/send templates')

def evaluate(self, formatter, kwargs, mi, locals_):
db = self.get_database(mi)
try:
a = db.data.get_virtual_libraries_for_books((mi.id,))
return ', '.join(a[mi.id])
except ValueError as v:
return str(v)

[dokumentace]
class BuiltinCurrentVirtualLibraryName(BuiltinFormatterFunction):
name = 'current_virtual_library_name'
arg_count = 0 category = 'Get values from metadata'
__doc__ = doc = _('current_virtual_library_name() -- '
'return the name of the current virtual library if there is one, '
'otherwise the empty string. Library name case is preserved. '
'Example: "program: current_virtual_library_name()".')

def evaluate(self, formatter, kwargs, mi, locals):
return self.get_database(mi).data.get_base_restriction_name()

[dokumentace]
class BuiltinUserCategories(BuiltinFormatterFunction):
name = 'user_categories'
arg_count = 0 category = 'Get values from metadata'
__doc__ = doc = _('user_categories() -- return a comma-separated list of '
'the user categories that contain this book. This function '
'works only in the GUI. If you want to use these values '
'in save-to-disk or send-to-device templates then you '
'must make a custom "Column built from other columns", use '
'the function in that column\'s template, and use that '
'column\'s value in your save/send templates')

def evaluate(self, formatter, kwargs, mi, locals_):
if hasattr(mi, '_proxy_metadata'):
cats = {k for k, v in iteritems(mi._proxy_metadata.user_categories) if v}
cats = sorted(cats, key=sort_key)
return ', '.join(cats)
self.only_in_gui_error()

[dokumentace]
class BuiltinTransliterate(BuiltinFormatterFunction):
name = 'transliterate'
arg_count = 1 category = 'String manipulation'
__doc__ = doc = _('transliterate(a) -- Returns a string in a latin alphabet '
'formed by approximating the sound of the words in the '
'source string. For example, if the source is "{0}"'
' the function returns "{1}".').format(
"Фёдор Миха́йлович Достоевский", 'Fiodor Mikhailovich Dostoievskii')

def evaluate(self, formatter, kwargs, mi, locals, source):
from calibre.utils.filenames import ascii_text return ascii_text(source)

[dokumentace]
class BuiltinGetLink(BuiltinFormatterFunction):
name = 'get_link'
arg_count = 2 category = 'Template database functions'
__doc__ = doc = _("get_link(field_name, field_value) -- fetch the link for "
"field 'field_name' with value 'field_value'. If there is "
"no attached link, return ''. Example: "
"get_link('tags', 'Fiction') returns the link attached to "
"the tag 'Fiction'.")

def evaluate(self, formatter, kwargs, mi, locals, field_name, field_value):
db = self.get_database(mi).new_api try:
link = None item_id = db.get_item_id(field_name, field_value)
if item_id is not None:
link = db.link_for(field_name, item_id)
return link if link is not None else ''
except Exception as e:
traceback.print_exc()
raise ValueError(e)

[dokumentace]
class BuiltinAuthorLinks(BuiltinFormatterFunction):
name = 'author_links'
arg_count = 2 category = 'Get values from metadata'
__doc__ = doc = _('author_links(val_separator, pair_separator) -- returns '
'a string containing a list of authors and that author\'s '
'link values in the '
'form author1 val_separator author1link pair_separator '
'author2 val_separator author2link etc. An author is '
'separated from its link value by the val_separator string '
'with no added spaces. author:linkvalue pairs are separated '
'by the pair_separator string argument with no added spaces. '
'It is up to you to choose separator strings that do '
'not occur in author names or links. An author is '
'included even if the author link is empty.')

def evaluate(self, formatter, kwargs, mi, locals, val_sep, pair_sep):
if hasattr(mi, '_proxy_metadata'):
link_data = mi._proxy_metadata.link_maps if not link_data:
return ''
link_data = link_data.get('authors')
if not link_data:
return ''
names = sorted(link_data.keys(), key=sort_key)
return pair_sep.join(n + val_sep + link_data[n] for n in names)
self.only_in_gui_error()

[dokumentace]
class BuiltinAuthorSorts(BuiltinFormatterFunction):
name = 'author_sorts'
arg_count = 1 category = 'Get values from metadata'
__doc__ = doc = _('author_sorts(val_separator) -- returns a string '
'containing a list of author\'s sort values for the '
'authors of the book. The sort is the one in the author '
'metadata (different from the author_sort in books). The '
'returned list has the form author sort 1 val_separator '
'author sort 2 etc. The author sort values in this list '
'are in the same order as the authors of the book. If '
'you want spaces around val_separator then include them '
'in the separator string')

def evaluate(self, formatter, kwargs, mi, locals, val_sep):
sort_data = mi.author_sort_map if not sort_data:
return ''
names = [sort_data.get(n) for n in mi.authors if n.strip()]
return val_sep.join(n for n in names)

[dokumentace]
class BuiltinConnectedDeviceName(BuiltinFormatterFunction):
name = 'connected_device_name'
arg_count = 1 category = 'Get values from metadata'
__doc__ = doc = _("connected_device_name(storage_location) -- if a device is "
"connected then return the device name, otherwise return "
"the empty string. Each storage location on a device can "
"have a different name. The location names are 'main', "
"'carda' and 'cardb'. This function works only in the GUI.")

def evaluate(self, formatter, kwargs, mi, locals, storage_location):
# We can't use get_database() here because we need the device manager.
# In other words, the function really does need the GUI with suppress(Exception):
# Do the import here so that we don't entangle the GUI when using
# command line functions from calibre.gui2.ui import get_gui info = get_gui().device_manager.get_current_device_information()
if info is None:
return ''
try:
if storage_location not in {'main', 'carda', 'cardb'}:
raise ValueError(
_('connected_device_name: invalid storage location "{}"'
.format(storage_location)))
info = info['info'][4]
if storage_location not in info:
return ''
return info[storage_location]['device_name']
except Exception:
traceback.print_exc()
raise self.only_in_gui_error()

[dokumentace]
class BuiltinConnectedDeviceUUID(BuiltinFormatterFunction):
name = 'connected_device_uuid'
arg_count = 1 category = 'Get values from metadata'
__doc__ = doc = _("connected_device_uuid(storage_location) -- if a device is "
"connected then return the device uuid (unique id), "
"otherwise return the empty string. Each storage location "
"on a device has a different uuid. The location names are "
"'main', 'carda' and 'cardb'. This function works only in "
"the GUI.")

def evaluate(self, formatter, kwargs, mi, locals, storage_location):
# We can't use get_database() here because we need the device manager.
# In other words, the function really does need the GUI with suppress(Exception):
# Do the import here so that we don't entangle the GUI when using
# command line functions from calibre.gui2.ui import get_gui info = get_gui().device_manager.get_current_device_information()
if info is None:
return ''
try:
if storage_location not in {'main', 'carda', 'cardb'}:
raise ValueError(
_('connected_device_name: invalid storage location "{}"'
.format(storage_location)))
info = info['info'][4]
if storage_location not in info:
return ''
return info[storage_location]['device_store_uuid']
except Exception:
traceback.print_exc()
raise self.only_in_gui_error()

[dokumentace]
class BuiltinCheckYesNo(BuiltinFormatterFunction):
name = 'check_yes_no'
arg_count = 4 category = 'If-then-else'
__doc__ = doc = _('check_yes_no(field_name, is_undefined, is_false, is_true) '
'-- checks the value of the yes/no field named by the '
'lookup key field_name for a value specified by the '
'parameters, returning "yes" if a match is found, otherwise '
'returning an empty string. Set the parameter is_undefined, '
'is_false, or is_true to 1 (the number) to check that '
'condition, otherwise set it to 0. Example: '
'check_yes_no("#bool", 1, 0, 1) returns "yes" if the '
'yes/no field "#bool" is either undefined (neither True '
'nor False) or True. More than one of is_undefined, '
'is_false, or is_true can be set to 1. This function '
'is usually used by the test() or is_empty() functions.')

def evaluate(self, formatter, kwargs, mi, locals, field, is_undefined, is_false, is_true):
# 'field' is a lookup name, not a value if field not in self.get_database(mi).field_metadata:
raise ValueError(_("The column {} doesn't exist").format(field))
res = getattr(mi, field, None)
if res is None:
if is_undefined == '1':
return 'Yes'
return ""
if not isinstance(res, bool):
raise ValueError(_('check_yes_no requires the field be a Yes/No custom column'))
if is_false == '1' and not res:
return 'Yes'
if is_true == '1' and res:
return 'Yes'
return ""

[dokumentace]
class BuiltinRatingToStars(BuiltinFormatterFunction):
name = 'rating_to_stars'
arg_count = 2 category = 'Formatting values'
__doc__ = doc = _('rating_to_stars(value, use_half_stars) '
'-- Returns the rating as string of star characters. '
'The value is a number between 0 and 5. Set use_half_stars '
'to 1 if you want half star characters for custom ratings '
'columns that support non-integer ratings, for example 2.5.')

def evaluate(self, formatter, kwargs, mi, locals, value, use_half_stars):
if not value:
return ''
err_msg = _('The rating must be a number between 0 and 5')
try:
v = float(value) * 2 except:
raise ValueError(err_msg)
if v < 0 or v > 10:
raise ValueError(err_msg)
from calibre.ebooks.metadata import rating_to_stars return rating_to_stars(v, use_half_stars == '1')

[dokumentace]
class BuiltinSwapAroundArticles(BuiltinFormatterFunction):
name = 'swap_around_articles'
arg_count = 2 category = 'String manipulation'
__doc__ = doc = _('swap_around_articles(val, separator) '
'-- returns the val with articles moved to the end. '
'The value can be a list, in which case each member '
'of the list is processed. If the value is a list then '
'you must provide the list value separator. If no '
'separator is provided then the value is treated as '
'being a single value, not a list.')

def evaluate(self, formatter, kwargs, mi, locals, val, separator):
if not val:
return ''
if not separator:
return title_sort(val).replace(',', ';')
result = []
try:
for v in [x.strip() for x in val.split(separator)]:
result.append(title_sort(v).replace(',', ';'))
except:
traceback.print_exc()
return separator.join(sorted(result, key=sort_key))

[dokumentace]
class BuiltinArguments(BuiltinFormatterFunction):
name = 'arguments'
arg_count = -1 category = 'Other'
__doc__ = doc = _('arguments(id[=expression] [, id[=expression]]*) '
'-- Used in a stored template to retrieve the arguments '
'passed in the call. It both declares and initializes '
'local variables, effectively parameters. The variables '
'are positional; they get the value of the parameter given '
'in the call in the same position. If the corresponding '
'parameter is not provided in the call then arguments '
'assigns that variable the provided default value. If '
'there is no default value then the variable is set to '
'the empty string.')

def evaluate(self, formatter, kwargs, mi, locals, *args):
# The arguments function is implemented in-line in the formatter raise NotImplementedError()

[dokumentace]
class BuiltinGlobals(BuiltinFormatterFunction):
name = 'globals'
arg_count = -1 category = 'Other'
__doc__ = doc = _('globals(id[=expression] [, id[=expression]]*) '
'-- Retrieves "global variables" that can be passed into '
'the formatter. It both declares and initializes local '
'variables with the names of the global variables passed '
'in. If the corresponding variable is not provided in '
'the passed-in globals then it assigns that variable the '
'provided default value. If there is no default value '
'then the variable is set to the empty string.')

def evaluate(self, formatter, kwargs, mi, locals, *args):
# The globals function is implemented in-line in the formatter raise NotImplementedError()

[dokumentace]
class BuiltinSetGlobals(BuiltinFormatterFunction):
name = 'set_globals'
arg_count = -1 category = 'other'
__doc__ = doc = _('set_globals(id[=expression] [, id[=expression]]*) '
'-- Sets "global variables" that can be passed into '
'the formatter. The globals are given the name of the id '
'passed in. The value of the id is used unless an '
'expression is provided.')

def evaluate(self, formatter, kwargs, mi, locals, *args):
# The globals function is implemented in-line in the formatter raise NotImplementedError()

[dokumentace]
class BuiltinFieldExists(BuiltinFormatterFunction):
name = 'field_exists'
arg_count = 1 category = 'If-then-else'
__doc__ = doc = _('field_exists(field_name) -- checks if a field '
'(column) named field_name exists, returning '
"'1' if so and '' if not.")

def evaluate(self, formatter, kwargs, mi, locals, field_name):
if field_name.lower() in mi.all_field_keys():
return '1'
return ''

[dokumentace]
class BuiltinCharacter(BuiltinFormatterFunction):
name = 'character'
arg_count = 1 category = 'String manipulation'
__doc__ = doc = _('character(character_name) -- returns the '
'character named by character_name. For example, '
r"character('newline') returns a newline character ('\n'). "
"The supported character names are 'newline', 'return', "
"'tab', and 'backslash'.")

def evaluate(self, formatter, kwargs, mi, locals, character_name):
# The globals function is implemented in-line in the formatter raise NotImplementedError()

[dokumentace]
class BuiltinToHex(BuiltinFormatterFunction):
name = 'to_hex'
arg_count = 1 category = 'String manipulation'
__doc__ = doc = _('to_hex(val) -- returns the string encoded in hex. '
'This is useful when constructing calibre URLs.')

def evaluate(self, formatter, kwargs, mi, locals, val):
return val.encode().hex()

[dokumentace]
class BuiltinUrlsFromIdentifiers(BuiltinFormatterFunction):
name = 'urls_from_identifiers'
arg_count = 2 category = 'Formatting values'
__doc__ = doc = _('urls_from_identifiers(identifiers, sort_results) -- given '
'a comma-separated list of identifiers, where an identifier '
'is a colon-separated pair of values (name:id_value), returns a '
'comma-separated list of HTML URLs generated from the '
'identifiers. The list not sorted if sort_results is 0 '
'(character or number), otherwise it is sorted alphabetically '
'by the identifier name. The URLs are generated in the same way '
'as the built-in identifiers column when shown in Book details.')

def evaluate(self, formatter, kwargs, mi, locals, identifiers, sort_results):
from calibre.ebooks.metadata.sources.identify import urls_from_identifiers try:
v = {}
for id_ in identifiers.split(','):
if id_:
pair = id_.split(':', maxsplit=1)
if len(pair) == 2:
l = pair[0].strip()
r = pair[1].strip()
if l and r:
v[l] = r urls = urls_from_identifiers(v, sort_results=str(sort_results) != '0')
p = prepare_string_for_xml a = partial(prepare_string_for_xml, attribute=True)
links = [f'<a href="{a(url)}" title="{a(id_typ)}:{a(id_val)}">{p(name)}</a>'
for name, id_typ, id_val, url in urls]
return ', '.join(links)
except Exception as e:
return str(e)

[dokumentace]
class BuiltinBookCount(BuiltinFormatterFunction):
name = 'book_count'
arg_count = 2 category = 'Template database functions'
__doc__ = doc = _('book_count(query, use_vl) -- returns the count of '
'books found by searching for query. If use_vl is '
'0 (zero) then virtual libraries are ignored. This '
'function can be used only in the GUI.')

def evaluate(self, formatter, kwargs, mi, locals, query, use_vl):
from calibre.db.fields import rendering_composite_name if (not tweaks.get('allow_template_database_functions_in_composites', False) and formatter.global_vars.get(rendering_composite_name, None)):
raise ValueError(_('The book_count() function cannot be used in a composite column'))
db = self.get_database(mi)
try:
ids = db.search_getting_ids(query, None, use_virtual_library=use_vl != '0')
return len(ids)
except Exception:
traceback.print_exc()

[dokumentace]
class BuiltinBookValues(BuiltinFormatterFunction):
name = 'book_values'
arg_count = 4 category = 'Template database functions'
__doc__ = doc = _('book_values(column, query, sep, use_vl) -- returns a list '
'of the values contained in the column "column", separated '
'by "sep", in the books found by searching for "query". '
'If use_vl is 0 (zero) then virtual libraries are ignored. '
'This function can be used only in the GUI.')

def evaluate(self, formatter, kwargs, mi, locals, column, query, sep, use_vl):
from calibre.db.fields import rendering_composite_name if (not tweaks.get('allow_template_database_functions_in_composites', False) and formatter.global_vars.get(rendering_composite_name, None)):
raise ValueError(_('The book_values() function cannot be used in a composite column'))
db = self.get_database(mi)
if column not in db.field_metadata:
raise ValueError(_("The column {} doesn't exist").format(column))
try:
ids = db.search_getting_ids(query, None, use_virtual_library=use_vl != '0')
s = set()
for id_ in ids:
f = db.new_api.get_proxy_metadata(id_).get(column, None)
if isinstance(f, (tuple, list)):
s.update(f)
elif f is not None:
s.add(str(f))
return sep.join(s)
except Exception as e:
raise ValueError(e)[dokumentace]
class BuiltinHasExtraFiles(BuiltinFormatterFunction):
name = 'has_extra_files'
arg_count = -1 category = 'Template database functions'
__doc__ = doc = _("has_extra_files([pattern]) -- returns the count of extra "
"files, otherwise '' (the empty string). "
"If the optional parameter 'pattern' (a regular expression) "
"is supplied then the list is filtered to files that match "
"pattern before the files are counted. The pattern match is "
"case insensitive. "
'This function can be used only in the GUI.')

def evaluate(self, formatter, kwargs, mi, locals, *args):
if len(args) > 1:
raise ValueError(_('Incorrect number of arguments for function {0}').format('has_extra_files'))
pattern = args[0] if len(args) == 1 else None db = self.get_database(mi).new_api try:
files = tuple(f.relpath.partition('/')[-1] for f in db.list_extra_files(mi.id, use_cache=True, pattern=DATA_FILE_PATTERN))
if pattern:
r = re.compile(pattern, re.IGNORECASE)
files = tuple(filter(r.search, files))
return len(files) if len(files) > 0 else ''
except Exception as e:
traceback.print_exc()
raise ValueError(e)[dokumentace]
class BuiltinExtraFileNames(BuiltinFormatterFunction):
name = 'extra_file_names'
arg_count = -1 category = 'Template database functions'
__doc__ = doc = _("extra_file_names(sep [, pattern]) -- returns a sep-separated "
"list of extra files in the book's '{}/' folder. If the "
"optional parameter 'pattern', a regular expression, is "
"supplied then the list is filtered to files that match pattern. "
"The pattern match is case insensitive. "
'This function can be used only in the GUI.').format(DATA_DIR_NAME)

def evaluate(self, formatter, kwargs, mi, locals, sep, *args):
if len(args) > 1:
raise ValueError(_('Incorrect number of arguments for function {0}').format('has_extra_files'))
pattern = args[0] if len(args) == 1 else None db = self.get_database(mi).new_api try:
files = tuple(f.relpath.partition('/')[-1] for f in db.list_extra_files(mi.id, use_cache=True, pattern=DATA_FILE_PATTERN))
if pattern:
r = re.compile(pattern, re.IGNORECASE)
files = tuple(filter(r.search, files))
return sep.join(files)
except Exception as e:
traceback.print_exc()
raise ValueError(e)

[dokumentace]
class BuiltinExtraFileSize(BuiltinFormatterFunction):
name = 'extra_file_size'
arg_count = 1 category = 'Template database functions'
__doc__ = doc = _("extra_file_size(file_name) -- returns the size in bytes of "
"the extra file 'file_name' in the book's '{}/' folder if "
"it exists, otherwise -1."
'This function can be used only in the GUI.').format(DATA_DIR_NAME)

def evaluate(self, formatter, kwargs, mi, locals, file_name):
db = self.get_database(mi).new_api try:
q = posixpath.join(DATA_DIR_NAME, file_name)
for f in db.list_extra_files(mi.id, use_cache=True, pattern=DATA_FILE_PATTERN):
if f.relpath == q:
return str(f.stat_result.st_size)
return str(-1)
except Exception as e:
traceback.print_exc()
raise ValueError(e)

[dokumentace]
class BuiltinExtraFileModtime(BuiltinFormatterFunction):
name = 'extra_file_modtime'
arg_count = 2 category = 'Template database functions'
__doc__ = doc = _("extra_file_modtime(file_name, format_string) -- returns the "
"modification time of the extra file 'file_name' in the "
"book's '{}/' folder if it exists, otherwise -1.0. The "
"modtime is formatted according to 'format_string' "
"(see format_date()). If 'format_string' is empty, returns "
"the modtime as the floating point number of seconds since "
"the epoch. The epoch is OS dependent. "
"This function can be used only in the GUI.").format(DATA_DIR_NAME)

def evaluate(self, formatter, kwargs, mi, locals, file_name, format_string):
db = self.get_database(mi).new_api try:
q = posixpath.join(DATA_DIR_NAME, file_name)
for f in db.list_extra_files(mi.id, use_cache=True, pattern=DATA_FILE_PATTERN):
if f.relpath == q:
val = f.stat_result.st_mtime if format_string:
return format_date(datetime.fromtimestamp(val), format_string)
return str(val)
return str(1.0)
except Exception as e:
traceback.print_exc()
raise ValueError(e)

[dokumentace]
class BuiltinGetNote(BuiltinFormatterFunction):
name = 'get_note'
arg_count = 3 category = 'Template database functions'
__doc__ = doc = _("get_note(field_name, field_value, plain_text) -- fetch the "
"note for field 'field_name' with value 'field_value'. If "
"'plain_text' is empty, return the note's HTML. If 'plain_text' "
"is non-empty, return the note's plain text. If the note "
"doesn't exist, return '' in both cases. Example: "
"get_note('tags', 'Fiction', '') returns the HTML of the "
"note attached to the tag 'Fiction'.")

def evaluate(self, formatter, kwargs, mi, locals, field_name, field_value, plain_text):
db = self.get_database(mi).new_api try:
note = ''
item_id = db.get_item_id(field_name, field_value)
if item_id is not None:
note_data = db.notes_data_for(field_name, item_id)
if note_data is not None:
if plain_text == '1':
return note['searchable_text'].partition('\n')[2]
# Return the full HTML of the note, including all images as
# data: URLs. Reason: non-exported note html contains
# "calres://" URLs for images. These images won't render
# outside the context of the library where the note "lives".
# For example, they don't work in book jackets and book
# details from a different library. They also don't work in
# tooltips.

# This code depends on the note being wrapped in <body> tags
# by parse_html. The body is changed to a <div>. That means
# we often end up with <div><div> or some such, but that is
# OK root = parse_html(note_data['doc'])
# There should be only one <body> root = root.xpath('//body')[0]
# Change the body to a div root.tag = 'div'
# Expand all the resources in the note root = expand_note_resources(root, db.get_notes_resource)
note = html.tostring(root, encoding='unicode')
return note except Exception as e:
traceback.print_exc()
raise ValueError(e)

[dokumentace]
class BuiltinHasNote(BuiltinFormatterFunction):
name = 'has_note'
arg_count = 2 category = 'Template database functions'
__doc__ = doc = _("has_note(field_name, field_value) -- return '1' "
"if the value 'field_value' in the field 'field_name' "
"has an attached note, '' otherwise. Example: "
"has_note('tags', 'Fiction') returns '1' if the tag "
"'fiction' has an attached note, '' otherwise.")

def evaluate(self, formatter, kwargs, mi, locals, field_name, field_value):
db = self.get_database(mi).new_api note = None try:
item_id = db.get_item_id(field_name, field_value)
if item_id is not None:
note = db.notes_data_for(field_name, item_id)
except Exception as e:
traceback.print_exc()
raise ValueError(e)
return '1' if note is not None else ''

_formatter_builtins = [
BuiltinAdd(), BuiltinAnd(), BuiltinApproximateFormats(), BuiltinArguments(),
BuiltinAssign(),
BuiltinAuthorLinks(), BuiltinAuthorSorts(), BuiltinBookCount(),
BuiltinBookValues(), BuiltinBooksize(),
BuiltinCapitalize(), BuiltinCharacter(), BuiltinCheckYesNo(), BuiltinCeiling(),
BuiltinCmp(), BuiltinConnectedDeviceName(), BuiltinConnectedDeviceUUID(), BuiltinContains(),
BuiltinCount(), BuiltinCurrentLibraryName(), BuiltinCurrentLibraryPath(),
BuiltinCurrentVirtualLibraryName(), BuiltinDateArithmetic(),
BuiltinDaysBetween(), BuiltinDivide(), BuiltinEval(),
BuiltinExtraFileNames(), BuiltinExtraFileSize(), BuiltinExtraFileModtime(),
BuiltinFirstNonEmpty(), BuiltinField(), BuiltinFieldExists(),
BuiltinFinishFormatting(), BuiltinFirstMatchingCmp(), BuiltinFloor(),
BuiltinFormatDate(), BuiltinFormatDateField(), BuiltinFormatNumber(), BuiltinFormatsModtimes(),
BuiltinFormatsPaths(), BuiltinFormatsSizes(), BuiltinFractionalPart(),
BuiltinGetLink(),
BuiltinGetNote(), BuiltinGlobals(), BuiltinHasCover(), BuiltinHasExtraFiles(),
BuiltinHasNote(), BuiltinHumanReadable(), BuiltinIdentifierInList(),
BuiltinIfempty(), BuiltinLanguageCodes(), BuiltinLanguageStrings(),
BuiltinInList(), BuiltinIsMarked(), BuiltinListCountMatching(),
BuiltinListDifference(), BuiltinListEquals(), BuiltinListIntersection(),
BuiltinListitem(), BuiltinListJoin(), BuiltinListRe(),
BuiltinListReGroup(), BuiltinListRemoveDuplicates(), BuiltinListSort(),
BuiltinListSplit(), BuiltinListUnion(),BuiltinLookup(),
BuiltinLowercase(), BuiltinMod(), BuiltinMultiply(), BuiltinNot(), BuiltinOndevice(),
BuiltinOr(), BuiltinPrint(), BuiltinRatingToStars(), BuiltinRange(),
BuiltinRawField(), BuiltinRawList(),
BuiltinRe(), BuiltinReGroup(), BuiltinRound(), BuiltinSelect(), BuiltinSeriesSort(),
BuiltinSetGlobals(), BuiltinShorten(), BuiltinStrcat(), BuiltinStrcatMax(),
BuiltinStrcmp(), BuiltinStrcmpcase(), BuiltinStrInList(), BuiltinStrlen(), BuiltinSubitems(),
BuiltinSublist(),BuiltinSubstr(), BuiltinSubtract(), BuiltinSwapAroundArticles(),
BuiltinSwapAroundComma(), BuiltinSwitch(), BuiltinSwitchIf(),
BuiltinTemplate(), BuiltinTest(), BuiltinTitlecase(), BuiltinToday(),
BuiltinToHex(), BuiltinTransliterate(), BuiltinUppercase(), BuiltinUrlsFromIdentifiers(),
BuiltinUserCategories(), BuiltinVirtualLibraries(), BuiltinAnnotationCount()
]

class FormatterUserFunction(FormatterFunction):

def __init__(self, name, doc, arg_count, program_text, object_type):
self.object_type = object_type self.name = name self.doc = doc self.arg_count = arg_count self.program_text = program_text self.cached_compiled_text = None
# Keep this for external code compatibility. Set it to True if we have a
# python template function, otherwise false. This might break something
# if the code depends on stored templates being in GPM.
self.is_python = True if object_type is StoredObjectType.PythonFunction else False def to_pref(self):
return [self.name, self.doc, self.arg_count, self.program_text]

tabs = re.compile(r'^\t*')

def function_object_type(thing):
# 'thing' can be a preference instance, program text, or an already-compiled function if isinstance(thing, FormatterUserFunction):
return thing.object_type if isinstance(thing, list):
text = thing[3]
else:
text = thing if text.startswith('def'):
return StoredObjectType.PythonFunction if text.startswith('program'):
return StoredObjectType.StoredGPMTemplate if text.startswith('python'):
return StoredObjectType.StoredPythonTemplate raise ValueError('Unknown program type in formatter function pref')

def function_pref_name(pref):
return pref[0]

def compile_user_function(name, doc, arg_count, eval_func):
typ = function_object_type(eval_func)
if typ is not StoredObjectType.PythonFunction:
return FormatterUserFunction(name, doc, arg_count, eval_func, typ)

def replace_func(mo):
return mo.group().replace('\t', ' ')

func = ' ' + '\n '.join([tabs.sub(replace_func, line)
for line in eval_func.splitlines()])
prog = '''
from calibre.utils.formatter_functions import FormatterUserFunction from calibre.utils.formatter_functions import formatter_functions class UserFunction(FormatterUserFunction):
''' + func locals_ = {}
if DEBUG and tweaks.get('enable_template_debug_printing', False):
print(prog)
exec(prog, locals_)
cls = locals_['UserFunction'](name, doc, arg_count, eval_func, typ)
return cls def compile_user_template_functions(funcs):
compiled_funcs = {}
for func in funcs:
try:
# Force a name conflict to test the logic
# if func[0] == 'myFunc2':
# func[0] = 'myFunc3'

# Compile the function so that the tab processing is done on the
# source. This helps ensure that if the function already is defined
# then white space differences don't cause them to compare differently cls = compile_user_function(*func)
cls.object_type = function_object_type(func)
compiled_funcs[cls.name] = cls except Exception:
try:
func_name = func[0]
except Exception:
func_name = 'Unknown'
prints('**** Compilation errors in user template function "%s" ****' % func_name)
traceback.print_exc(limit=10)
prints('**** End compilation errors in %s "****"' % func_name)
return compiled_funcs def load_user_template_functions(library_uuid, funcs, precompiled_user_functions=None):
unload_user_template_functions(library_uuid)
if precompiled_user_functions:
compiled_funcs = precompiled_user_functions else:
compiled_funcs = compile_user_template_functions(funcs)
formatter_functions().register_functions(library_uuid, list(compiled_funcs.values()))

def unload_user_template_functions(library_uuid):
formatter_functions().unregister_functions(library_uuid)

© Copyright Kovid Goyal.
Naposledy aktualizováno kvě 03, 2024.

Navigace

- moduly
- Spustit »
- Kód modulu »
- calibre.web.feeds.news

Zdrojový kód pro calibre.web.feeds.news

__license__ = 'GPL v3'
__copyright__ = '2008, Kovid Goyal <kovid at kovidgoyal.net>'
'''
Defines various abstract base classes that can be subclassed to create powerful news fetching recipes.
'''
__docformat__ = "restructuredtext en"

import io import os import re import sys import time import traceback from collections import defaultdict from contextlib import closing from urllib.parse import urlparse, urlsplit from calibre import __appname__, as_unicode, browser, force_unicode, iswindows, preferred_encoding, random_user_agent, strftime from calibre.ebooks.BeautifulSoup import BeautifulSoup, CData, NavigableString, Tag from calibre.ebooks.metadata import MetaInformation from calibre.ebooks.metadata.opf2 import OPFCreator from calibre.ebooks.metadata.toc import TOC from calibre.ptempfile import PersistentTemporaryFile from calibre.utils.date import now as nowf from calibre.utils.icu import numeric_sort_key from calibre.utils.img import add_borders_to_image, image_to_data, save_cover_data_to from calibre.utils.localization import _, canonicalize_lang, ngettext from calibre.utils.logging import ThreadSafeWrapper from calibre.utils.threadpool import NoResultsPending, ThreadPool, WorkRequest from calibre.web import Recipe from calibre.web.feeds import Feed, feed_from_xml, feeds_from_index, templates from calibre.web.fetch.simple import AbortArticle, RecursiveFetcher from calibre.web.fetch.simple import option_parser as web2disk_option_parser from calibre.web.fetch.utils import prepare_masthead_image from polyglot.builtins import string_or_bytes def classes(classes):
q = frozenset(classes.split(' '))
return dict(attrs={
'class': lambda x: x and frozenset(x.split()).intersection(q)})

def prefixed_classes(classes):
q = frozenset(classes.split(' '))

def matcher(x):
if x:
for candidate in frozenset(x.split()):
for x in q:
if candidate.startswith(x):
return True return False return {'attrs': {'class': matcher}}

class LoginFailed(ValueError):
pass class DownloadDenied(ValueError):
pass

[dokumentace]
class BasicNewsRecipe(Recipe):
'''
Base class that contains logic needed in all recipes. By overriding progressively more of the functionality in this class, you can make progressively more customized/powerful recipes. For a tutorial introduction to creating recipes, see :doc:`news`.
'''

#: The title to use for the e-book title = _('Unknown News Source')

#: A couple of lines that describe the content this recipe downloads.
#: This will be used primarily in a GUI that presents a list of recipes.
description = ''

#: The author of this recipe
__author__ = __appname__

#: Minimum calibre version needed to use this recipe requires_version = (0, 6, 0)

#: The language that the news is in. Must be an ISO-639 code either
#: two or three characters long language = 'und'

#: Maximum number of articles to download from each feed. This is primarily
#: useful for feeds that don't have article dates. For most feeds, you should
#: use :attr:`BasicNewsRecipe.oldest_article` max_articles_per_feed = 100

#: Oldest article to download from this news source. In days.
oldest_article = 7.0

#: Number of levels of links to follow on article webpages recursions = 0

#: The default delay between consecutive downloads in seconds. The argument may be a
#: floating point number to indicate a more precise time. See :meth:`get_url_specific_delay`
#: to implement per URL delays.
delay = 0

#: Publication type
#: Set to newspaper, magazine or blog. If set to None, no publication type
#: metadata will be written to the opf file.
publication_type = 'unknown'

#: Number of simultaneous downloads. Set to 1 if the server is picky.
#: Automatically reduced to 1 if :attr:`BasicNewsRecipe.delay` > 0 simultaneous_downloads = 5

#: Timeout for fetching files from server in seconds timeout = 120.0

#: The format string for the date shown on the first page.
#: By default: Day_Name, Day_Number Month_Name Year timefmt = ' [%a, %d %b %Y]'

#: List of feeds to download.
#: Can be either ``[url1, url2, ...]`` or ``[('title1', url1), ('title2', url2),...]`` feeds = None

#: Max number of characters in the short description summary_length = 500

#: Convenient flag to disable loading of stylesheets for websites
#: that have overly complex stylesheets unsuitable for conversion
#: to e-book formats.
#: If True stylesheets are not downloaded and processed no_stylesheets = False

#: Convenient flag to strip all JavaScript tags from the downloaded HTML remove_javascript = True

#: If True the GUI will ask the user for a username and password
#: to use while downloading.
#: If set to "optional" the use of a username and password becomes optional needs_subscription = False

#: If True the navigation bar is center aligned, otherwise it is left aligned center_navbar = True

#: Specify an override encoding for sites that have an incorrect
#: charset specification. The most common being specifying ``latin1`` and
#: using ``cp1252``. If None, try to detect the encoding. If it is a
#: callable, the callable is called with two arguments: The recipe object
#: and the source to be decoded. It must return the decoded source.
encoding = None

#: Normally we try to guess if a feed has full articles embedded in it
#: based on the length of the embedded content. If `None`, then the
#: default guessing is used. If `True` then the we always assume the feeds has
#: embedded content and if `False` we always assume the feed does not have
#: embedded content.
use_embedded_content = None

#: Set to True and implement :meth:`get_obfuscated_article` to handle
#: websites that try to make it difficult to scrape content.
articles_are_obfuscated = False

#: Reverse the order of articles in each feed reverse_article_order = False

#: Automatically extract all the text from downloaded article pages. Uses
#: the algorithms from the readability project. Setting this to True, means
#: that you do not have to worry about cleaning up the downloaded HTML
#: manually (though manual cleanup will always be superior).
auto_cleanup = False

#: Specify elements that the auto cleanup algorithm should never remove.
#: The syntax is a XPath expression. For example::
#:
#: auto_cleanup_keep = '//div[@id="article-image"]' will keep all divs with
#: id="article-image"
#: auto_cleanup_keep = '//*[@class="important"]' will keep all elements
#: with class="important"
#: auto_cleanup_keep = '//div[@id="article-image"]|//span[@class="important"]'
#: will keep all divs with id="article-image" and spans
#: with class="important"
#:
auto_cleanup_keep = None

#: Specify any extra :term:`CSS` that should be added to downloaded :term:`HTML` files.
#: It will be inserted into `<style>` tags, just before the closing
#: `</head>` tag thereby overriding all :term:`CSS` except that which is
#: declared using the style attribute on individual :term:`HTML` tags.
#: Note that if you want to programmatically generate the extra_css override
#: the :meth:`get_extra_css()` method instead.
#: For example::
#:
#: extra_css = '.heading { font: serif x-large }'
#:
extra_css = None

#: If True empty feeds are removed from the output.
#: This option has no effect if parse_index is overridden in
#: the sub class. It is meant only for recipes that return a list
#: of feeds using `feeds` or :meth:`get_feeds`. It is also used if you use
#: the ignore_duplicate_articles option.
remove_empty_feeds = False

#: List of regular expressions that determines which links to follow.
#: If empty, it is ignored. Used only if is_link_wanted is
#: not implemented. For example::
#:
#: match_regexps = [r'page=[0-9]+']
#:
#: will match all URLs that have `page=some number` in them.
#:
#: Only one of :attr:`BasicNewsRecipe.match_regexps` or
#: :attr:`BasicNewsRecipe.filter_regexps` should be defined.
match_regexps = []

#: List of regular expressions that determines which links to ignore.
#: If empty it is ignored. Used only if is_link_wanted is not
#: implemented. For example::
#:
#: filter_regexps = [r'ads\.doubleclick\.net']
#:
#: will remove all URLs that have `ads.doubleclick.net` in them.
#:
#: Only one of :attr:`BasicNewsRecipe.match_regexps` or
#: :attr:`BasicNewsRecipe.filter_regexps` should be defined.
filter_regexps = []

#: Recipe specific options to control the conversion of the downloaded
#: content into an e-book. These will override any user or plugin specified
#: values, so only use if absolutely necessary. For example::
#:
#: conversion_options = {
#: 'base_font_size' : 16,
#: 'linearize_tables' : True,
#: }
#:
conversion_options = {}

#: List of tags to be removed. Specified tags are removed from downloaded HTML.
#: A tag is specified as a dictionary of the form::
#:
#: {
#: name : 'tag name', #e.g. 'div'
#: attrs : a dictionary, #e.g. {'class': 'advertisment'}
#: }
#:
#: All keys are optional. For a full explanation of the search criteria, see
#: `Beautiful Soup <https://www.crummy.com/software/BeautifulSoup/bs4/doc/#searching-the-tree>`__
#: A common example::
#:
#: remove_tags = [dict(name='div', class_='advert')]
#:
#: This will remove all `<div class="advert">` tags and all
#: their children from the downloaded :term:`HTML`.
remove_tags = []

#: Remove all tags that occur after the specified tag.
#: For the format for specifying a tag see :attr:`BasicNewsRecipe.remove_tags`.
#: For example::
#:
#: remove_tags_after = [dict(id='content')]
#:
#: will remove all
#: tags after the first element with `id="content"`.
remove_tags_after = None

#: Remove all tags that occur before the specified tag.
#: For the format for specifying a tag see :attr:`BasicNewsRecipe.remove_tags`.
#: For example::
#:
#: remove_tags_before = dict(id='content')
#:
#: will remove all
#: tags before the first element with `id="content"`.
remove_tags_before = None

#: List of attributes to remove from all tags.
#: For example::
#:
#: remove_attributes = ['style', 'font']
remove_attributes = []

#: Keep only the specified tags and their children.
#: For the format for specifying a tag see :attr:`BasicNewsRecipe.remove_tags`.
#: If this list is not empty, then the `<body>` tag will be emptied and re-filled with
#: the tags that match the entries in this list. For example::
#:
#: keep_only_tags = [dict(id=['content', 'heading'])]
#:
#: will keep only tags that have an `id` attribute of `"content"` or `"heading"`.
keep_only_tags = []

#: List of :term:`regexp` substitution rules to run on the downloaded :term:`HTML`.
#: Each element of the
#: list should be a two element tuple. The first element of the tuple should
#: be a compiled regular expression and the second a callable that takes
#: a single match object and returns a string to replace the match. For example::
#:
#: preprocess_regexps = [
#: (re.compile(r'<!--Article ends here-->.*</body>', re.DOTALL|re.IGNORECASE),
#: lambda match: '</body>'),
#: ]
#:
#: will remove everything from `<!--Article ends here-->` to `</body>`.
preprocess_regexps = []

#: The CSS that is used to style the templates, i.e., the navigation bars and
#: the Tables of Contents. Rather than overriding this variable, you should
#: use `extra_css` in your recipe to customize look and feel.
template_css = '''
.article_date { color: gray; font-family: monospace;
}

.article_description { text-indent: 0pt;
}

a.article { font-weight: bold; text-align:left;
}

a.feed { font-weight: bold;
}

.calibre_navbar { font-family:monospace;
}
'''

#: By default, calibre will use a default image for the masthead (Kindle only).
#: Override this in your recipe to provide a URL to use as a masthead.
masthead_url = None

#: By default, the cover image returned by get_cover_url() will be used as
#: the cover for the periodical. Overriding this in your recipe instructs
#: calibre to render the downloaded cover into a frame whose width and height
#: are expressed as a percentage of the downloaded cover.
#: cover_margins = (10, 15, '#ffffff') pads the cover with a white margin
#: 10px on the left and right, 15px on the top and bottom.
#: Color names are defined `here <https://www.imagemagick.org/script/color.php>`_.
#: Note that for some reason, white does not always work in Windows. Use
#: #ffffff instead cover_margins = (0, 0, '#ffffff')

#: Set to a non empty string to disable this recipe.
#: The string will be used as the disabled message recipe_disabled = None

#: Ignore duplicates of articles that are present in more than one section.
#: A duplicate article is an article that has the same title and/or URL.
#: To ignore articles with the same title, set this to::
#:
#: ignore_duplicate_articles = {'title'}
#:
#: To use URLs instead, set it to::
#:
#: ignore_duplicate_articles = {'url'}
#:
#: To match on title or URL, set it to::
#:
#: ignore_duplicate_articles = {'title', 'url'}
ignore_duplicate_articles = None

# The following parameters control how the recipe attempts to minimize
# JPEG image sizes

#: Set this to False to ignore all scaling and compression parameters and
#: pass images through unmodified. If True and the other compression
#: parameters are left at their default values, JPEG images will be scaled to fit
#: in the screen dimensions set by the output profile and compressed to size at
#: most (w * h)/16 where w x h are the scaled image dimensions.
compress_news_images = False

#: The factor used when auto compressing JPEG images. If set to None,
#: auto compression is disabled. Otherwise, the images will be reduced in size to
#: (w * h)/compress_news_images_auto_size bytes if possible by reducing
#: the quality level, where w x h are the image dimensions in pixels.
#: The minimum JPEG quality will be 5/100 so it is possible this constraint
#: will not be met. This parameter can be overridden by the parameter
#: compress_news_images_max_size which provides a fixed maximum size for images.
#: Note that if you enable scale_news_images_to_device then the image will
#: first be scaled and then its quality lowered until its size is less than
#: (w * h)/factor where w and h are now the *scaled* image dimensions. In
#: other words, this compression happens after scaling.
compress_news_images_auto_size = 16

#: Set JPEG quality so images do not exceed the size given (in KBytes).
#: If set, this parameter overrides auto compression via compress_news_images_auto_size.
#: The minimum JPEG quality will be 5/100 so it is possible this constraint
#: will not be met.
compress_news_images_max_size = None

#: Rescale images to fit in the device screen dimensions set by the output profile.
#: Ignored if no output profile is set.
scale_news_images_to_device = True

#: Maximum dimensions (w,h) to scale images to. If scale_news_images_to_device is True
#: this is set to the device screen dimensions set by the output profile unless
#: there is no profile set, in which case it is left at whatever value it has been
#: assigned (default None).
scale_news_images = None

#: If set to True then links in downloaded articles that point to other downloaded articles are
#: changed to point to the downloaded copy of the article rather than its original web URL. If you
#: set this to True, you might also need to implement :meth:`canonicalize_internal_url` to work
#: with the URL scheme of your particular website.
resolve_internal_links = False

#: Set to False if you do not want to use gzipped transfers. Note that some old servers flake out with gzip handle_gzip = True

# See the built-in recipes for examples of these settings.

def short_title(self):
return force_unicode(self.title, preferred_encoding)

[dokumentace]
def is_link_wanted(self, url, tag):
'''
Return True if the link should be followed or False otherwise. By default, raises NotImplementedError which causes the downloader to ignore it.

:param url: The URL to be followed
:param tag: The tag from which the URL was derived
'''
raise NotImplementedError()

[dokumentace]
def get_extra_css(self):
'''
By default returns `self.extra_css`. Override if you want to programmatically generate the extra_css.
'''
return self.extra_css

[dokumentace]
def get_cover_url(self):
'''
Return a :term:`URL` to the cover image for this issue or `None`.
By default it returns the value of the member `self.cover_url` which is normally `None`. If you want your recipe to download a cover for the e-book override this method in your subclass, or set the member variable `self.cover_url` before this method is called.
'''
return getattr(self, 'cover_url', None)

[dokumentace]
def get_masthead_url(self):
'''
Return a :term:`URL` to the masthead image for this issue or `None`.
By default it returns the value of the member `self.masthead_url` which is normally `None`. If you want your recipe to download a masthead for the e-book override this method in your subclass, or set the member variable `self.masthead_url` before this method is called.
Masthead images are used in Kindle MOBI files.
'''
return getattr(self, 'masthead_url', None)

[dokumentace]
def get_feeds(self):
'''
Return a list of :term:`RSS` feeds to fetch for this profile. Each element of the list must be a 2-element tuple of the form (title, url). If title is None or an empty string, the title from the feed is used. This method is useful if your recipe needs to do some processing to figure out the list of feeds to download. If so, override in your subclass.
'''
if not self.feeds:
raise NotImplementedError()
if self.test:
return self.feeds[:self.test[0]]
return self.feeds

[dokumentace]
def get_url_specific_delay(self, url):
'''
Return the delay in seconds before downloading this URL. If you want to programmatically determine the delay for the specified URL, override this method in your subclass, returning self.delay by default for URLs you do not want to affect.

:return: A floating point number, the delay in seconds.
'''
return self.delay

[dokumentace]
@classmethod def print_version(cls, url):
'''
Take a `url` pointing to the webpage with article content and return the
:term:`URL` pointing to the print version of the article. By default does nothing. For example::

def print_version(self, url):
return url + '?&pagewanted=print'

'''
raise NotImplementedError()

[dokumentace]
@classmethod def image_url_processor(cls, baseurl, url):
'''
Perform some processing on image urls (perhaps removing size restrictions for dynamically generated images, etc.) and return the precessed URL. Return None or an empty string to skip fetching the image.
'''
return url

[dokumentace]
def preprocess_image(self, img_data, image_url):
'''
Perform some processing on downloaded image data. This is called on the raw data before any resizing is done. Must return the processed raw data. Return
None to skip the image.
'''
return img_data

[dokumentace]
def get_browser(self, *args, **kwargs):
'''
Return a browser instance used to fetch documents from the web. By default it returns a `mechanize <https://mechanize.readthedocs.io/en/latest/>`_ browser instance that supports cookies, ignores robots.txt, handles refreshes and has a mozilla firefox user agent.

If your recipe requires that you login first, override this method in your subclass. For example, the following code is used in the New York
Times recipe to login for full access::

def get_browser(self):
br = BasicNewsRecipe.get_browser(self)
if self.username is not None and self.password is not None:
br.open('https://www.nytimes.com/auth/login')
br.select_form(name='login')
br['USERID'] = self.username br['PASSWORD'] = self.password br.submit()
return br

'''
if 'user_agent' not in kwargs:
# More and more news sites are serving JPEG XR images to IE ua = getattr(self, 'last_used_user_agent', None) or self.calibre_most_common_ua or random_user_agent(allow_ie=False)
kwargs['user_agent'] = self.last_used_user_agent = ua self.log('Using user agent:', kwargs['user_agent'])
br = browser(*args, **kwargs)
br.addheaders += [('Accept', '*/*')]
if self.handle_gzip:
br.set_handle_gzip(True)
return br

[dokumentace]
def clone_browser(self, br):
'''
Clone the browser br. Cloned browsers are used for multi-threaded downloads, since mechanize is not thread safe. The default cloning routines should capture most browser customization, but if you do something exotic in your recipe, you should override this method in your recipe and clone manually.

Cloned browser instances use the same, thread-safe CookieJar by default, unless you have customized cookie handling.
'''
if callable(getattr(br, 'clone_browser', None)):
return br.clone_browser()

# Uh-oh recipe using something exotic, call get_browser return self.get_browser()

@property def cloned_browser(self):
if hasattr(self.get_browser, 'is_base_class_implementation'):
# We are using the default get_browser, which means no need to
# clone br = BasicNewsRecipe.get_browser(self)
else:
br = self.clone_browser(self.browser)
return br

[dokumentace]
def get_article_url(self, article):
'''
Override in a subclass to customize extraction of the :term:`URL` that points to the content for each article. Return the article URL. It is called with `article`, an object representing a parsed article from a feed. See `feedparser <https://pythonhosted.org/feedparser/>`_.
By default it looks for the original link (for feeds syndicated via a service like FeedBurner or Pheedo) and if found, returns that or else returns
`article.link <https://pythonhosted.org/feedparser/reference-entry-link.html>`_.
'''
for key in article.keys():
if key.endswith('_origlink'):
url = article[key]
if url and (url.startswith('http://') or url.startswith('https://')):
return url ans = article.get('link', None)
if not ans and getattr(article, 'links', None):
for item in article.links:
if item.get('rel', 'alternate') == 'alternate':
ans = item['href']
break return ans

[dokumentace]
def skip_ad_pages(self, soup):
'''
This method is called with the source of each downloaded :term:`HTML` file, before any of the cleanup attributes like remove_tags, keep_only_tags are applied. Note that preprocess_regexps will have already been applied.
It is meant to allow the recipe to skip ad pages. If the soup represents an ad page, return the HTML of the real page. Otherwise return
None.

`soup`: A `BeautifulSoup <https://www.crummy.com/software/BeautifulSoup/bs4/doc/>`__ instance containing the downloaded :term:`HTML`.
'''
return None

[dokumentace]
def abort_article(self, msg=None):
''' Call this method inside any of the preprocess methods to abort the download for the current article. Useful to skip articles that contain inappropriate content, such as pure video articles. '''
raise AbortArticle(msg or _('Article download aborted'))

[dokumentace]
def preprocess_raw_html(self, raw_html, url):
'''
This method is called with the source of each downloaded :term:`HTML` file, before it is parsed into an object tree. raw_html is a unicode string representing the raw HTML downloaded from the web. url is the URL from which the HTML was downloaded.

Note that this method acts *before* preprocess_regexps.

This method must return the processed raw_html as a unicode object.
'''
return raw_html
def preprocess_raw_html_(self, raw_html, url):
raw_html = self.preprocess_raw_html(raw_html, url)
if self.auto_cleanup:
try:
raw_html = self.extract_readable_article(raw_html, url)
except:
self.log.exception('Auto cleanup of URL: %r failed'%url)

return raw_html

[dokumentace]
def preprocess_html(self, soup):
'''
This method is called with the source of each downloaded :term:`HTML` file, before it is parsed for links and images. It is called after the cleanup as specified by remove_tags etc.
It can be used to do arbitrarily powerful pre-processing on the :term:`HTML`.
It should return `soup` after processing it.

`soup`: A `BeautifulSoup <https://www.crummy.com/software/BeautifulSoup/bs4/doc/>`__ instance containing the downloaded :term:`HTML`.
'''
return soup

[dokumentace]
def postprocess_html(self, soup, first_fetch):
'''
This method is called with the source of each downloaded :term:`HTML` file, after it is parsed for links and images.
It can be used to do arbitrarily powerful post-processing on the :term:`HTML`.
It should return `soup` after processing it.

:param soup: A `BeautifulSoup <https://www.crummy.com/software/BeautifulSoup/bs4/doc/>`__ instance containing the downloaded :term:`HTML`.
:param first_fetch: True if this is the first page of an article.

'''
return soup

[dokumentace]
def cleanup(self):
'''
Called after all articles have been download. Use it to do any cleanup like logging out of subscription sites, etc.
'''
pass

[dokumentace]
def canonicalize_internal_url(self, url, is_link=True):
'''
Return a set of canonical representations of ``url``. The default implementation uses just the server hostname and path of the URL, ignoring any query parameters, fragments, etc. The canonical representations must be unique across all URLs for this news source. If they are not, then internal links may be resolved incorrectly.

:param is_link: Is True if the URL is coming from an internal link in an HTML file. False if the URL is the URL used to download an article.
'''
try:
parts = urlparse(url)
except Exception:
self.log.error('Failed to parse url: %r, ignoring' % url)
return frozenset()
nl = parts.netloc path = parts.path or ''
if isinstance(nl, bytes):
nl = nl.decode('utf-8', 'replace')
if isinstance(path, bytes):
path = path.decode('utf-8', 'replace')
return frozenset({(nl, path.rstrip('/'))})

[dokumentace]
def index_to_soup(self, url_or_raw, raw=False, as_tree=False, save_raw=None):
'''
Convenience method that takes an URL to the index page and returns a `BeautifulSoup <https://www.crummy.com/software/BeautifulSoup/bs4/doc>`__ of it.

`url_or_raw`: Either a URL or the downloaded index page as a string
'''
if re.match((br'\w+://' if isinstance(url_or_raw, bytes) else r'\w+://'), url_or_raw):
# We may be called in a thread (in the skip_ad_pages method), so
# clone the browser to be safe. We cannot use self.cloned_browser
# as it may or may not actually clone the browser, depending on if
# the recipe implements get_browser() or not br = self.clone_browser(self.browser)
open_func = getattr(br, 'open_novisit', br.open)
with closing(open_func(url_or_raw, timeout=self.timeout)) as f:
_raw = f.read()
if not _raw:
raise RuntimeError('Could not fetch index from %s'%url_or_raw)
else:
_raw = url_or_raw if raw:
return _raw if not isinstance(_raw, str) and self.encoding:
if callable(self.encoding):
_raw = self.encoding(_raw)
else:
_raw = _raw.decode(self.encoding, 'replace')
from calibre.ebooks.chardet import strip_encoding_declarations, xml_to_unicode from calibre.utils.cleantext import clean_xml_chars if isinstance(_raw, str):
_raw = strip_encoding_declarations(_raw)
else:
_raw = xml_to_unicode(_raw, strip_encoding_pats=True, resolve_entities=True)[0]
_raw = clean_xml_chars(_raw)
if save_raw:
with open(save_raw, 'wb') as f:
f.write(_raw.encode('utf-8'))
if as_tree:
from html5_parser import parse return parse(_raw)
return BeautifulSoup(_raw)[dokumentace]
def extract_readable_article(self, html, url):
'''
Extracts main article content from 'html', cleans up and returns as a (article_html, extracted_title) tuple.
Based on the original readability algorithm by Arc90.
'''
from lxml.html import document_fromstring, fragment_fromstring, tostring from calibre.ebooks.readability import readability doc = readability.Document(html, self.log, url=url, keep_elements=self.auto_cleanup_keep)
article_html = doc.summary()
extracted_title = doc.title()

try:
frag = fragment_fromstring(article_html)
except:
doc = document_fromstring(article_html)
frag = doc.xpath('//body')[-1]
if frag.tag == 'html':
root = frag elif frag.tag == 'body':
root = document_fromstring(
'<html><head><title>%s</title></head></html>' % extracted_title)
root.append(frag)
else:
root = document_fromstring(
'<html><head><title>%s</title></head><body/></html>' % extracted_title)
root.xpath('//body')[0].append(frag)

body = root.xpath('//body')[0]
has_title = False for x in body.iterdescendants():
if x.text == extracted_title:
has_title = True inline_titles = body.xpath('//h1|//h2')
if not has_title and not inline_titles:
heading = body.makeelement('h2')
heading.text = extracted_title body.insert(0, heading)

raw_html = tostring(root, encoding='unicode')

return raw_html

[dokumentace]
def sort_index_by(self, index, weights):
'''
Convenience method to sort the titles in `index` according to `weights`.
`index` is sorted in place. Returns `index`.

`index`: A list of titles.

`weights`: A dictionary that maps weights to titles. If any titles in index are not in weights, they are assumed to have a weight of 0.
'''
weights = defaultdict(int, weights)
index.sort(key=lambda x: weights[x])
return index

[dokumentace]
def parse_index(self):
'''
This method should be implemented in recipes that parse a website instead of feeds to generate a list of articles. Typical uses are for news sources that have a "Print Edition" webpage that lists all the articles in the current print edition. If this function is implemented, it will be used in preference to :meth:`BasicNewsRecipe.parse_feeds`.

It must return a list. Each element of the list must be a 2-element tuple of the form ``('feed title', list of articles)``.

Each list of articles must contain dictionaries of the form::

{
'title' : article title,
'url' : URL of print version,
'date' : The publication date of the article as a string,
'description' : A summary of the article
'content' : The full article (can be an empty string). Obsolete do not use, instead save the content to a temporary file and pass a file:///path/to/temp/file.html as the URL.
}

For an example, see the recipe for downloading `The Atlantic`.
In addition, you can add 'author' for the author of the article.

If you want to abort processing for some reason and have calibre show the user a simple message instead of an error, call
:meth:`abort_recipe_processing`.
'''
raise NotImplementedError()

[dokumentace]
def abort_recipe_processing(self, msg):
'''
Causes the recipe download system to abort the download of this recipe, displaying a simple feedback message to the user.
'''
from calibre.ebooks.conversion import ConversionUserFeedBack raise ConversionUserFeedBack(_('Failed to download %s')%self.title, msg)

[dokumentace]
def get_obfuscated_article(self, url):
'''
If you set `articles_are_obfuscated` this method is called with every article URL. It should return the path to a file on the filesystem that contains the article HTML. That file is processed by the recursive
HTML fetching engine, so it can contain links to pages/images on the web.
Alternately, you can return a dictionary of the form:
{'data': <HTML data>, 'url': <the resolved URL of the article>}. This avoids needing to create temporary files. The `url` key in the dictionary is useful if the effective URL of the article is different from the URL passed into this method, for example, because of redirects. It can be omitted if the URL is unchanged.

This method is typically useful for sites that try to make it difficult to access article content automatically.
'''
raise NotImplementedError()

[dokumentace]
def add_toc_thumbnail(self, article, src):
'''
Call this from populate_article_metadata with the src attribute of an
<img> tag from the article that is appropriate for use as the thumbnail representing the article in the Table of Contents. Whether the thumbnail is actually used is device dependent (currently only used by the Kindles). Note that the referenced image must be one that was successfully downloaded, otherwise it will be ignored.
'''
if not src or not hasattr(article, 'toc_thumbnail'):
return src = src.replace('\\', '/')
if re.search(r'feed_\d+/article_\d+/images/img', src, flags=re.I) is None:
self.log.warn('Ignoring invalid TOC thumbnail image: %r'%src)
return article.toc_thumbnail = re.sub(r'^.*?feed', 'feed', src, flags=re.IGNORECASE)

[dokumentace]
def populate_article_metadata(self, article, soup, first):
'''
Called when each HTML page belonging to article is downloaded.
Intended to be used to get article metadata like author/summary/etc.
from the parsed HTML (soup).

:param article: A object of class :class:`calibre.web.feeds.Article`.
If you change the summary, remember to also change the text_summary
:param soup: Parsed HTML belonging to this article
:param first: True iff the parsed HTML is the first page of the article.
'''
pass

[dokumentace]
def postprocess_book(self, oeb, opts, log):
'''
Run any needed post processing on the parsed downloaded e-book.

:param oeb: An OEBBook object
:param opts: Conversion options
'''
pass
def __init__(self, options, log, progress_reporter):
'''
Initialize the recipe.
:param options: Parsed commandline options
:param log: Logging object
:param progress_reporter: A Callable that takes two arguments: progress (a number between 0 and 1) and a string message. The message should be optional.
'''
self.log = ThreadSafeWrapper(log)
if not isinstance(self.title, str):
self.title = str(self.title, 'utf-8', 'replace')

self.debug = options.verbose > 1 self.output_dir = os.path.abspath(os.getcwd())
self.verbose = options.verbose self.test = options.test if self.test and not isinstance(self.test, tuple):
self.test = (2, 2)
self.username = options.username self.password = options.password self.lrf = options.lrf self.output_profile = options.output_profile self.touchscreen = getattr(self.output_profile, 'touchscreen', False)
if self.touchscreen:
self.template_css += self.output_profile.touchscreen_news_css if self.test:
self.max_articles_per_feed = self.test[1]
self.simultaneous_downloads = min(4, self.simultaneous_downloads)

if self.debug:
self.verbose = True self.report_progress = progress_reporter if self.needs_subscription and ( self.username is None or self.password is None or ( not self.username and not self.password)):
if self.needs_subscription != 'optional':
raise ValueError(_('The "%s" recipe needs a username and password.')%self.title)

self.browser = self.get_browser()
self.image_map, self.image_counter = {}, 1 self.css_map = {}

web2disk_cmdline = ['web2disk',
'--timeout', str(self.timeout),
'--max-recursions', str(self.recursions),
'--delay', str(self.delay),
]

if self.verbose:
web2disk_cmdline.append('--verbose')

if self.no_stylesheets:
web2disk_cmdline.append('--dont-download-stylesheets')

for reg in self.match_regexps:
web2disk_cmdline.extend(['--match-regexp', reg])

for reg in self.filter_regexps:
web2disk_cmdline.extend(['--filter-regexp', reg])

if options.output_profile.short_name in ('default', 'tablet'):
self.scale_news_images_to_device = False elif self.scale_news_images_to_device:
self.scale_news_images = options.output_profile.screen_size self.web2disk_options = web2disk_option_parser().parse_args(web2disk_cmdline)[0]
for extra in ('keep_only_tags', 'remove_tags', 'preprocess_regexps',
'skip_ad_pages', 'preprocess_html', 'remove_tags_after',
'remove_tags_before', 'is_link_wanted',
'compress_news_images', 'compress_news_images_max_size',
'compress_news_images_auto_size', 'scale_news_images'):
setattr(self.web2disk_options, extra, getattr(self, extra))

self.web2disk_options.postprocess_html = self._postprocess_html self.web2disk_options.preprocess_image = self.preprocess_image self.web2disk_options.encoding = self.encoding self.web2disk_options.preprocess_raw_html = self.preprocess_raw_html_ self.web2disk_options.get_delay = self.get_url_specific_delay if self.delay > 0:
self.simultaneous_downloads = 1 self.navbar = templates.TouchscreenNavBarTemplate() if self.touchscreen else \ templates.NavBarTemplate()
self.failed_downloads = []
self.partial_failures = []
self.aborted_articles = []

def _postprocess_html(self, soup, first_fetch, job_info):
if self.no_stylesheets:
for link in soup.findAll('link'):
if (link.get('type') or 'text/css').lower() == 'text/css' and 'stylesheet' in (link.get('rel') or ('stylesheet',)):
link.extract()
for style in soup.findAll('style'):
style.extract()
head = soup.find('head')
if not head:
head = soup.find('body')
if not head:
head = soup.find(True)
css = self.template_css + '\n\n' + (self.get_extra_css() or '')
style = soup.new_tag('style', type='text/css', title='override_css')
style.append(css)
head.append(style)
if first_fetch and job_info:
url, f, a, feed_len = job_info body = soup.find('body')
if body is not None:
templ = self.navbar.generate(False, f, a, feed_len, not self.has_single_feed, url, __appname__, center=self.center_navbar, extra_css=self.get_extra_css() or '')
elem = BeautifulSoup(templ.render(doctype='xhtml').decode('utf-8')).find('div')
body.insert(0, elem)
# This is needed because otherwise inserting elements into
# the soup breaks find()
soup = BeautifulSoup(soup.decode_contents())
if self.remove_javascript:
for script in list(soup.findAll('script')):
script.extract()
for o in soup.findAll(onload=True):
del o['onload']

for attr in self.remove_attributes:
for x in soup.findAll(attrs={attr:True}):
del x[attr]
for bad_tag in list(soup.findAll(['base', 'iframe', 'canvas', 'embed', 'button',
'command', 'datalist', 'video', 'audio', 'noscript', 'link', 'meta'])):
# link tags can be used for preloading causing network activity in
# calibre viewer. meta tags can do all sorts of crazy things,
# including http-equiv refresh, viewport shenanigans, etc.
bad_tag.extract()
# srcset causes some viewers, like calibre's to load images from the
# web, and it also possible causes iBooks on iOS to barf, see
# https://bugs.launchpad.net/bugs/1713986 for img in soup.findAll('img', srcset=True):
del img['srcset']

ans = self.postprocess_html(soup, first_fetch)

# Nuke HTML5 tags for x in ans.findAll(['article', 'aside', 'header', 'footer', 'nav',
'figcaption', 'figure', 'section']):
x.name = 'div'

if job_info:
url, f, a, feed_len = job_info try:
article = self.feed_objects[f].articles[a]
except:
self.log.exception('Failed to get article object for postprocessing')
pass else:
self.populate_article_metadata(article, ans, first_fetch)
return ans

[dokumentace]
def download(self):
'''
Download and pre-process all articles from the feeds in this recipe.
This method should be called only once on a particular Recipe instance.
Calling it more than once will lead to undefined behavior.
:return: Path to index.html
'''
try:
res = self.build_index()
self.report_progress(1, _('Download finished'))
if self.failed_downloads:
self.log.warning(_('Failed to download the following articles:'))
for feed, article, debug in self.failed_downloads:
self.log.warning(article.title, 'from', feed.title)
self.log.debug(article.url)
self.log.debug(debug)
if self.partial_failures:
self.log.warning(_('Failed to download parts of the following articles:'))
for feed, atitle, aurl, debug in self.partial_failures:
self.log.warning(atitle + _(' from ') + feed)
self.log.debug(aurl)
self.log.warning(_('\tFailed links:'))
for l, tb in debug:
self.log.warning(l)
self.log.debug(tb)
return res finally:
self.cleanup()

@property def lang_for_html(self):
try:
lang = self.language.replace('_', '-').partition('-')[0].lower()
if lang == 'und':
lang = None except:
lang = None return lang def feeds2index(self, feeds):
templ = (templates.TouchscreenIndexTemplate if self.touchscreen else templates.IndexTemplate)
templ = templ(lang=self.lang_for_html)
css = self.template_css + '\n\n' +(self.get_extra_css() or '')
timefmt = self.timefmt return templ.generate(self.title, "mastheadImage.jpg", timefmt, feeds, extra_css=css).render(doctype='xhtml')

@classmethod def description_limiter(cls, src):
if not src:
return ''
src = force_unicode(src, 'utf-8')
pos = cls.summary_length fuzz = 50 si = src.find(';', pos)
if si > 0 and si-pos > fuzz:
si = -1 gi = src.find('>', pos)
if gi > 0 and gi-pos > fuzz:
gi = -1 npos = max(si, gi)
if npos < 0:
npos = pos ans = src[:npos+1]
if len(ans) < len(src):
from calibre.utils.cleantext import clean_xml_chars

# Truncating the string could cause a dangling UTF-16 half-surrogate, which will cause lxml to barf, clean it ans = clean_xml_chars(ans) + '\u2026'
return ans def feed2index(self, f, feeds):
feed = feeds[f]
if feed.image_url is not None: # Download feed image imgdir = os.path.join(self.output_dir, 'images')
if not os.path.isdir(imgdir):
os.makedirs(imgdir)

if feed.image_url in self.image_map:
feed.image_url = self.image_map[feed.image_url]
else:
bn = urlsplit(feed.image_url).path if bn:
bn = bn.rpartition('/')[-1]
if bn:
img = os.path.join(imgdir, 'feed_image_%d%s'%(self.image_counter, os.path.splitext(bn)[-1]))
try:
with open(img, 'wb') as fi, closing(self.browser.open(feed.image_url, timeout=self.timeout)) as r:
fi.write(r.read())
self.image_counter += 1 feed.image_url = img self.image_map[feed.image_url] = img except:
pass if isinstance(feed.image_url, bytes):
feed.image_url = feed.image_url.decode(sys.getfilesystemencoding(), 'strict')

templ = (templates.TouchscreenFeedTemplate if self.touchscreen else templates.FeedTemplate)
templ = templ(lang=self.lang_for_html)
css = self.template_css + '\n\n' +(self.get_extra_css() or '')

return templ.generate(f, feeds, self.description_limiter, extra_css=css).render(doctype='xhtml')

def _fetch_article(self, url, dir_, f, a, num_of_feeds, preloaded=None):
br = self.browser if hasattr(self.get_browser, 'is_base_class_implementation'):
# We are using the default get_browser, which means no need to
# clone br = BasicNewsRecipe.get_browser(self)
else:
br = self.clone_browser(self.browser)
self.web2disk_options.browser = br fetcher = RecursiveFetcher(self.web2disk_options, self.log, self.image_map, self.css_map,
(url, f, a, num_of_feeds))
fetcher.browser = br fetcher.base_dir = dir_ fetcher.current_dir = dir_ fetcher.show_progress = False fetcher.image_url_processor = self.image_url_processor if preloaded is not None:
fetcher.preloaded_urls[url] = preloaded res, path, failures = fetcher.start_fetch(url), fetcher.downloaded_paths, fetcher.failed_links if not res or not os.path.exists(res):
msg = _('Could not fetch article.') + ' '
if self.debug:
msg += _('The debug traceback is available earlier in this log')
else:
msg += _('Run with -vv to see the reason')
raise Exception(msg)

return res, path, failures def fetch_article(self, url, dir, f, a, num_of_feeds):
return self._fetch_article(url, dir, f, a, num_of_feeds)

def fetch_obfuscated_article(self, url, dir, f, a, num_of_feeds):
x = self.get_obfuscated_article(url)
if isinstance(x, dict):
data = x['data']
if isinstance(data, str):
data = data.encode(self.encoding or 'utf-8')
url = x.get('url', url)
else:
with open(x, 'rb') as of:
data = of.read()
os.remove(x)
return self._fetch_article(url, dir, f, a, num_of_feeds, preloaded=data)

def fetch_embedded_article(self, article, dir, f, a, num_of_feeds):
templ = templates.EmbeddedContent()
raw = templ.generate(article).render('html')
with PersistentTemporaryFile('_feeds2disk.html') as pt:
pt.write(raw)
url = ('file:'+pt.name) if iswindows else ('file://'+pt.name)
return self._fetch_article(url, dir, f, a, num_of_feeds)

def remove_duplicate_articles(self, feeds):
seen_keys = defaultdict(set)
remove = []
for f in feeds:
for article in f:
for key in self.ignore_duplicate_articles:
val = getattr(article, key)
seen = seen_keys[key]
if val:
if val in seen:
remove.append((f, article))
else:
seen.add(val)

for feed, article in remove:
self.log.debug('Removing duplicate article: %s from section: %s'%( article.title, feed.title))
feed.remove_article(article)

if self.remove_empty_feeds:
feeds = [f for f in feeds if len(f) > 0]
return feeds def build_index(self):
self.report_progress(0, _('Fetching feeds...'))
feeds = None try:
feeds = feeds_from_index(self.parse_index(), oldest_article=self.oldest_article, max_articles_per_feed=self.max_articles_per_feed, log=self.log)
self.report_progress(0, _('Got feeds from index page'))
except NotImplementedError:
pass if feeds is None:
feeds = self.parse_feeds()

if not feeds:
raise ValueError('No articles found, aborting')

if self.ignore_duplicate_articles is not None:
feeds = self.remove_duplicate_articles(feeds)

self.report_progress(0, _('Trying to download cover...'))
self.download_cover()
self.report_progress(0, _('Generating masthead...'))
self.resolve_masthead()

if self.test:
feeds = feeds[:self.test[0]]
self.has_single_feed = len(feeds) == 1 index = os.path.join(self.output_dir, 'index.html')

html = self.feeds2index(feeds)
with open(index, 'wb') as fi:
fi.write(html)

self.jobs = []

if self.reverse_article_order:
for feed in feeds:
if hasattr(feed, 'reverse'):
feed.reverse()

self.feed_objects = feeds for f, feed in enumerate(feeds):
feed_dir = os.path.join(self.output_dir, 'feed_%d'%f)
if not os.path.isdir(feed_dir):
os.makedirs(feed_dir)

for a, article in enumerate(feed):
if a >= self.max_articles_per_feed:
break art_dir = os.path.join(feed_dir, 'article_%d'%a)
if not os.path.isdir(art_dir):
os.makedirs(art_dir)
try:
url = self.print_version(article.url)
except NotImplementedError:
url = article.url except:
self.log.exception('Failed to find print version for: '+article.url)
url = None if not url:
continue func, arg = (self.fetch_embedded_article, article) \ if self.use_embedded_content or (self.use_embedded_content is None and feed.has_embedded_content()) \ else \ ((self.fetch_obfuscated_article if self.articles_are_obfuscated else self.fetch_article), url)
req = WorkRequest(func, (arg, art_dir, f, a, len(feed)),
{}, (f, a), self.article_downloaded, self.error_in_article_download)
req.feed = feed req.article = article req.feed_dir = feed_dir self.jobs.append(req)

self.jobs_done = 0 tp = ThreadPool(self.simultaneous_downloads)
for req in self.jobs:
tp.putRequest(req, block=True, timeout=0)

self.report_progress(0, ngettext(
'Starting download in a single thread...',
'Starting download [{} threads]...', self.simultaneous_downloads).format(self.simultaneous_downloads))
while True:
try:
tp.poll()
time.sleep(0.1)
except NoResultsPending:
break for f, feed in enumerate(feeds):
html = self.feed2index(f,feeds)
feed_dir = os.path.join(self.output_dir, 'feed_%d'%f)
with open(os.path.join(feed_dir, 'index.html'), 'wb') as fi:
fi.write(html)
self.create_opf(feeds)
self.report_progress(1, _('Feeds downloaded to %s')%index)

return index def _download_cover(self):
self.cover_path = None try:
cu = self.get_cover_url()
except Exception as err:
self.log.error(_('Could not download cover: %s')%as_unicode(err))
self.log.debug(traceback.format_exc())
else:
if not cu:
return cdata = None if hasattr(cu, 'read'):
cdata = cu.read()
cu = getattr(cu, 'name', 'cover.jpg')
elif os.access(cu, os.R_OK):
with open(cu, 'rb') as f:
cdata = f.read()
else:
self.report_progress(1, _('Downloading cover from %s')%cu)
with closing(self.browser.open(cu, timeout=self.timeout)) as r:
cdata = r.read()
if not cdata:
return ext = cu.split('/')[-1].rpartition('.')[-1].lower().strip()
if ext == 'pdf':
from calibre.ebooks.metadata.pdf import get_metadata stream = io.BytesIO(cdata)
cdata = None mi = get_metadata(stream)
if mi.cover_data and mi.cover_data[1]:
cdata = mi.cover_data[1]
if not cdata:
return if self.cover_margins[0] or self.cover_margins[1]:
cdata = image_to_data(add_borders_to_image(cdata, left=self.cover_margins[0],right=self.cover_margins[0], top=self.cover_margins[1],bottom=self.cover_margins[1], border_color=self.cover_margins[2]))

cpath = os.path.join(self.output_dir, 'cover.jpg')
save_cover_data_to(cdata, cpath)
self.cover_path = cpath def download_cover(self):
self.cover_path = None try:
self._download_cover()
except:
self.log.exception('Failed to download cover')
self.cover_path = None def _download_masthead(self, mu):
if hasattr(mu, 'rpartition'):
ext = mu.rpartition('.')[-1]
if '?' in ext:
ext = ''
else:
ext = mu.name.rpartition('.')[-1]
ext = ext.lower() if ext else 'jpg'
mpath = os.path.join(self.output_dir, 'masthead_source.'+ext)
outfile = os.path.join(self.output_dir, 'mastheadImage.jpg')
if hasattr(mu, 'read'):
with open(mpath, 'wb') as mfile:
mfile.write(mu.read())
elif os.access(mu, os.R_OK):
with open(mpath, 'wb') as mfile:
mfile.write(open(mu, 'rb').read())
else:
with open(mpath, 'wb') as mfile, closing(self.browser.open(mu, timeout=self.timeout)) as r:
mfile.write(r.read())
self.report_progress(1, _('Masthead image downloaded'))
self.prepare_masthead_image(mpath, outfile)
self.masthead_path = outfile if os.path.exists(mpath):
os.remove(mpath)

def download_masthead(self, url):
try:
self._download_masthead(url)
except:
self.log.exception("Failed to download supplied masthead_url")

def resolve_masthead(self):
self.masthead_path = None try:
murl = self.get_masthead_url()
except:
self.log.exception('Failed to get masthead url')
murl = None if murl is not None:
# Try downloading the user-supplied masthead_url
# Failure sets self.masthead_path to None self.download_masthead(murl)
if self.masthead_path is None:
self.log.info("Synthesizing mastheadImage")
self.masthead_path = os.path.join(self.output_dir, 'mastheadImage.jpg')
try:
self.default_masthead_image(self.masthead_path)
except:
self.log.exception('Failed to generate default masthead image')
self.masthead_path = None

[dokumentace]
def default_cover(self, cover_file):
'''
Create a generic cover for recipes that don't have a cover
'''
try:
from calibre.ebooks.covers import create_cover title = self.title if isinstance(self.title, str) else \ self.title.decode(preferred_encoding, 'replace')
date = strftime(self.timefmt).replace('[', '').replace(']', '')
img_data = create_cover(title, [date])
cover_file.write(img_data)
cover_file.flush()
except:
self.log.exception('Failed to generate default cover')
return False return True

[dokumentace]
def get_masthead_title(self):
'Override in subclass to use something other than the recipe title'
return self.title

MI_WIDTH = 600
MI_HEIGHT = 60 def default_masthead_image(self, out_path):
from calibre.ebooks import generate_masthead generate_masthead(self.get_masthead_title(), output_path=out_path, width=self.MI_WIDTH, height=self.MI_HEIGHT)

def prepare_masthead_image(self, path_to_image, out_path):
prepare_masthead_image(path_to_image, out_path, self.MI_WIDTH, self.MI_HEIGHT)

[dokumentace]
def publication_date(self):
'''
Use this method to set the date when this issue was published.
Defaults to the moment of download. Must return a :class:`datetime.datetime` object.
'''
return nowf()

def create_opf(self, feeds, dir=None):
if dir is None:
dir = self.output_dir title = self.short_title()
pdate = self.publication_date()
if self.output_profile.periodical_date_in_title:
title += strftime(self.timefmt, pdate)
mi = MetaInformation(title, [__appname__])
mi.publisher = __appname__ mi.author_sort = __appname__ if self.publication_type:
mi.publication_type = 'periodical:'+self.publication_type+':'+self.short_title()
mi.timestamp = nowf()
article_titles, aseen = [], set()
for (af, aa) in self.aborted_articles:
aseen.add(aa.title)
for (ff, fa, tb) in self.failed_downloads:
aseen.add(fa.title)
for f in feeds:
for a in f:
if a.title and a.title not in aseen:
aseen.add(a.title)
article_titles.append(force_unicode(a.title, 'utf-8'))

desc = self.description if not isinstance(desc, str):
desc = desc.decode('utf-8', 'replace')
mi.comments = (_('Articles in this issue:'
) + '\n\n' + '\n\n'.join(article_titles)) + '\n\n' + desc language = canonicalize_lang(self.language)
if language is not None:
mi.language = language mi.pubdate = pdate opf_path = os.path.join(dir, 'index.opf')
ncx_path = os.path.join(dir, 'index.ncx')

opf = OPFCreator(dir, mi)
# Add mastheadImage entry to <guide> section mp = getattr(self, 'masthead_path', None)
if mp is not None and os.access(mp, os.R_OK):
from calibre.ebooks.metadata.opf2 import Guide ref = Guide.Reference(os.path.basename(self.masthead_path), os.getcwd())
ref.type = 'masthead'
ref.title = 'Masthead Image'
opf.guide.append(ref)

manifest = [os.path.join(dir, 'feed_%d'%i) for i in range(len(feeds))]
manifest.append(os.path.join(dir, 'index.html'))
manifest.append(os.path.join(dir, 'index.ncx'))

# Get cover cpath = getattr(self, 'cover_path', None)
if cpath is None:
pf = open(os.path.join(dir, 'cover.jpg'), 'wb')
if self.default_cover(pf):
cpath = pf.name if cpath is not None and os.access(cpath, os.R_OK):
opf.cover = cpath manifest.append(cpath)

# Get masthead mpath = getattr(self, 'masthead_path', None)
if mpath is not None and os.access(mpath, os.R_OK):
manifest.append(mpath)

opf.create_manifest_from_files_in(manifest)
for mani in opf.manifest:
if mani.path.endswith('.ncx'):
mani.id = 'ncx'
if mani.path.endswith('mastheadImage.jpg'):
mani.id = 'masthead-image'

entries = ['index.html']
toc = TOC(base_path=dir)
self.play_order_counter = 0 self.play_order_map = {}

self.article_url_map = aumap = defaultdict(set)

def feed_index(num, parent):
f = feeds[num]
for j, a in enumerate(f):
if getattr(a, 'downloaded', False):
adir = 'feed_%d/article_%d/'%(num, j)
auth = a.author if not auth:
auth = None desc = a.text_summary if not desc:
desc = None else:
desc = self.description_limiter(desc)
tt = a.toc_thumbnail if a.toc_thumbnail else None entries.append('%sindex.html'%adir)
po = self.play_order_map.get(entries[-1], None)
if po is None:
self.play_order_counter += 1 po = self.play_order_counter arelpath = '%sindex.html'%adir for curl in self.canonicalize_internal_url(a.orig_url, is_link=False):
aumap[curl].add(arelpath)
article_toc_entry = parent.add_item(arelpath, None, a.title if a.title else _('Untitled article'), play_order=po, author=auth, description=desc, toc_thumbnail=tt)
for entry in a.internal_toc_entries:
anchor = entry.get('anchor')
if anchor:
self.play_order_counter += 1 po += 1 article_toc_entry.add_item( arelpath, entry['anchor'], entry['title'] or _('Unknown section'), play_order=po
)
last = os.path.join(self.output_dir, ('%sindex.html'%adir).replace('/', os.sep))
for sp in a.sub_pages:
prefix = os.path.commonprefix([opf_path, sp])
relp = sp[len(prefix):]
entries.append(relp.replace(os.sep, '/'))
last = sp if os.path.exists(last):
with open(last, 'rb') as fi:
src = fi.read().decode('utf-8')
soup = BeautifulSoup(src)
body = soup.find('body')
if body is not None:
prefix = '/'.join('..'for i in range(2*len(re.findall(r'link\d+', last))))
templ = self.navbar.generate(True, num, j, len(f), not self.has_single_feed, a.orig_url, __appname__, prefix=prefix, center=self.center_navbar)
elem = BeautifulSoup(templ.render(doctype='xhtml').decode('utf-8')).find('div')
body.insert(len(body.contents), elem)
with open(last, 'wb') as fi:
fi.write(str(soup).encode('utf-8'))
if len(feeds) == 0:
raise Exception('All feeds are empty, aborting.')

if len(feeds) > 1:
for i, f in enumerate(feeds):
entries.append('feed_%d/index.html'%i)
po = self.play_order_map.get(entries[-1], None)
if po is None:
self.play_order_counter += 1 po = self.play_order_counter auth = getattr(f, 'author', None)
if not auth:
auth = None desc = getattr(f, 'description', None)
if not desc:
desc = None feed_index(i, toc.add_item('feed_%d/index.html'%i, None, f.title, play_order=po, description=desc, author=auth))

else:
entries.append('feed_%d/index.html'%0)
feed_index(0, toc)

for i, p in enumerate(entries):
entries[i] = os.path.join(dir, p.replace('/', os.sep))
opf.create_spine(entries)
opf.set_toc(toc)

with open(opf_path, 'wb') as opf_file, open(ncx_path, 'wb') as ncx_file:
opf.render(opf_file, ncx_file)

def article_downloaded(self, request, result):
index = os.path.join(os.path.dirname(result[0]), 'index.html')
if index != result[0]:
if os.path.exists(index):
os.remove(index)
os.rename(result[0], index)
a = request.requestID[1]

article = request.article self.log.debug('Downloaded article:', article.title, 'from', article.url)
article.orig_url = article.url article.url = 'article_%d/index.html'%a article.downloaded = True article.sub_pages = result[1][1:]
self.jobs_done += 1 self.report_progress(float(self.jobs_done)/len(self.jobs),
_('Article downloaded: %s')%force_unicode(article.title))
if result[2]:
self.partial_failures.append((request.feed.title, article.title, article.url, result[2]))

def error_in_article_download(self, request, traceback):
self.jobs_done += 1 if traceback and re.search('^AbortArticle:', traceback, flags=re.M) is not None:
self.log.warn('Aborted download of article:', request.article.title,
'from', request.article.url)
self.report_progress(float(self.jobs_done)/len(self.jobs),
_('Article download aborted: %s')%force_unicode(request.article.title))
self.aborted_articles.append((request.feed, request.article))
else:
self.log.error('Failed to download article:', request.article.title,
'from', request.article.url)
self.log.debug(traceback)
self.log.debug('\n')
self.report_progress(float(self.jobs_done)/len(self.jobs),
_('Article download failed: %s')%force_unicode(request.article.title))
self.failed_downloads.append((request.feed, request.article, traceback))

[dokumentace]
def parse_feeds(self):
'''
Create a list of articles from the list of feeds returned by :meth:`BasicNewsRecipe.get_feeds`.
Return a list of :class:`Feed` objects.
'''
feeds = self.get_feeds()
parsed_feeds = []
br = self.browser for obj in feeds:
if isinstance(obj, string_or_bytes):
title, url = None, obj else:
title, url = obj if isinstance(title, bytes):
title = title.decode('utf-8')
if isinstance(url, bytes):
url = url.decode('utf-8')
if url.startswith('feed://'):
url = 'http'+url[4:]
self.report_progress(0, _('Fetching feed')+' %s...'%(title if title else url))
try:
purl = urlparse(url, allow_fragments=False)
if purl.username or purl.password:
hostname = purl.hostname if purl.port:
hostname += f':{purl.port}'
url = purl._replace(netloc=hostname).geturl()
if purl.username and purl.password:
br.add_password(url, purl.username, purl.password)
with closing(br.open_novisit(url, timeout=self.timeout)) as f:
raw = f.read()
parsed_feeds.append(feed_from_xml( raw, title=title, log=self.log, oldest_article=self.oldest_article, max_articles_per_feed=self.max_articles_per_feed, get_article_url=self.get_article_url
))
except Exception as err:
feed = Feed()
msg = 'Failed feed: %s'%(title if title else url)
feed.populate_from_preparsed_feed(msg, [])
feed.description = as_unicode(err)
parsed_feeds.append(feed)
self.log.exception(msg)
delay = self.get_url_specific_delay(url)
if delay > 0:
time.sleep(delay)

remove = [fl for fl in parsed_feeds if len(fl) == 0 and self.remove_empty_feeds]
for f in remove:
parsed_feeds.remove(f)

return parsed_feeds[dokumentace]
@classmethod def tag_to_string(self, tag, use_alt=True, normalize_whitespace=True):
'''
Convenience method to take a
`BeautifulSoup <https://www.crummy.com/software/BeautifulSoup/bs4/doc/>`_
:code:`Tag` and extract the text from it recursively, including any CDATA sections and alt tag attributes. Return a possibly empty Unicode string.

`use_alt`: If `True` try to use the alt attribute for tags that don't have any textual content

`tag`: `BeautifulSoup <https://www.crummy.com/software/BeautifulSoup/bs4/doc/>`_
:code:`Tag`
'''
if tag is None:
return ''
if isinstance(tag, string_or_bytes):
return tag if callable(getattr(tag, 'xpath', None)) and not hasattr(tag, 'contents'): # a lxml tag from lxml.etree import tostring ans = tostring(tag, method='text', encoding='unicode', with_tail=False)
else:
strings = []
for item in tag.contents:
if isinstance(item, (NavigableString, CData)):
strings.append(item.string)
elif isinstance(item, Tag):
res = self.tag_to_string(item)
if res:
strings.append(res)
elif use_alt:
try:
strings.append(item['alt'])
except KeyError:
pass ans = ''.join(strings)
if normalize_whitespace:
ans = re.sub(r'\s+', ' ', ans)
return ans@classmethod def soup(cls, raw):
return BeautifulSoup(raw)

[dokumentace]
@classmethod def adeify_images(cls, soup):
'''
If your recipe when converted to EPUB has problems with images when viewed in Adobe Digital Editions, call this method from within
:meth:`postprocess_html`.
'''
for item in soup.findAll('img'):
for attrib in ['height','width','border','align','style']:
try:
del item[attrib]
except KeyError:
pass oldParent = item.parent myIndex = oldParent.contents.index(item)
item.extract()
divtag = soup.new_tag('div')
brtag = soup.new_tag('br')
oldParent.insert(myIndex,divtag)
divtag.append(item)
divtag.append(brtag)
return soup
def internal_postprocess_book(self, oeb, opts, log):
if self.resolve_internal_links and self.article_url_map:
seen = set()
for item in oeb.spine:
for a in item.data.xpath('//*[local-name()="a" and @href]'):
if a.get('rel') == 'calibre-downloaded-from':
continue url = a.get('href')
for curl in self.canonicalize_internal_url(url):
articles = self.article_url_map.get(curl)
if articles:
arelpath = sorted(articles, key=numeric_sort_key)[0]
a.set('href', item.relhref(arelpath))
if url not in seen:
log.debug(f'Resolved internal URL: {url} -> {arelpath}')
seen.add(url)class CustomIndexRecipe(BasicNewsRecipe):

def custom_index(self):
'''
Return the filesystem path to a custom HTML document that will serve as the index for this recipe. The index document will typically contain many `<a href="...">` tags that point to resources on the internet that should be downloaded.
'''
raise NotImplementedError def create_opf(self):
mi = MetaInformation(self.title + strftime(self.timefmt), [__appname__])
mi.publisher = __appname__ mi.author_sort = __appname__ mi = OPFCreator(self.output_dir, mi)
mi.create_manifest_from_files_in([self.output_dir])
mi.create_spine([os.path.join(self.output_dir, 'index.html')])
with open(os.path.join(self.output_dir, 'index.opf'), 'wb') as opf_file:
mi.render(opf_file)

def download(self):
index = os.path.abspath(self.custom_index())
url = 'file:'+index if iswindows else 'file://'+index self.web2disk_options.browser = self.clone_browser(self.browser)
fetcher = RecursiveFetcher(self.web2disk_options, self.log)
fetcher.base_dir = self.output_dir fetcher.current_dir = self.output_dir fetcher.show_progress = False res = fetcher.start_fetch(url)
self.create_opf()
return res class AutomaticNewsRecipe(BasicNewsRecipe):

auto_cleanup = True class CalibrePeriodical(BasicNewsRecipe):

#: Set this to the slug for the calibre periodical calibre_periodicals_slug = None

LOG_IN = 'https://news.calibre-ebook.com/accounts/login'
needs_subscription = True
__author__ = 'calibre Periodicals'

def get_browser(self):
br = BasicNewsRecipe.get_browser(self)
br.open(self.LOG_IN)
br.select_form(name='login')
br['username'] = self.username br['password'] = self.password raw = br.submit().read()
if 'href="/my-account"' not in raw:
raise LoginFailed(
_('Failed to log in, check your username and password for'
' the calibre Periodicals service.'))

return br get_browser.is_base_class_implementation = True def download(self):
self.log('Fetching downloaded recipe')
try:
raw = self.browser.open_novisit(
'https://news.calibre-ebook.com/subscribed_files/%s/0/temp.downloaded_recipe'
% self.calibre_periodicals_slug
).read()
except Exception as e:
if hasattr(e, 'getcode') and e.getcode() == 403:
raise DownloadDenied(
_('You do not have permission to download this issue.'
' Either your subscription has expired or you have'
' exceeded the maximum allowed downloads for today.'))
raise f = io.BytesIO(raw)
from calibre.utils.zipfile import ZipFile zf = ZipFile(f)
zf.extractall()
zf.close()
from glob import glob from calibre.web.feeds.recipes import compile_recipe try:
recipe = compile_recipe(open(glob('*.recipe')[0],
'rb').read())
self.conversion_options = recipe.conversion_options except:
self.log.exception('Failed to compile downloaded recipe')
return os.path.abspath('index.html')

© Copyright Kovid Goyal.
Naposledy aktualizováno kvě 03, 2024.

Table of Contents

Uživatelská příručka Calibre

Grafické uživatelské rozhraní

1. Akce

1. Přidat knihy
2. Upravit metadata
3. Převést knihy
4. Zobrazit
5. Odeslat do zařízení
6. Načíst zprávy
7. Knihovna
8. Zařízení
9. Uložit na disk
10. Připojit nebo sdílet
11. Odebrat knihy

2. Předvolby
3. Katalogy
4. Hledání a řazení
5. Rozhraní hledání
6. Ukládání hledání
7. Searching the full text of all books
8. Virtuální knihovny
9. Dočasné označování knih
10. Odhadování metadat z názvů souborů
11. Podrobnosti o knize
12. Prohlížeč štítků
13. Mřížka obálek
14. Prohlížeč obálek
15. Adding notes for authors, series, etc.
16. Rychlé zobrazení
17. Úlohy
18. Klávesové zkratky

Přidávání oblíbených webových stránek se zprávami

1. Úplně automatické načítání

1. Blog Calibre

2. bbc.co.uk

2. Přizpůsobení procesu načítání

1. Použití tiskové verze bbc.co.uk
2. Nahrazování stylů článku
3. Krájení a sekání
4. Příklad ze skutečného života

3. Tipy pro vývoj nových předpisů

4. Další čtení

5. Dokumentace API

1. Dokumentace API pro předpisy

1. BasicNewsRecipe

Prohlížeč e-knih

1. Spuštění Prohlížeče e-knih

2. Navigace v e-knize

1. Záložky
2. Obsah
3. Navigace podle místa
4. Režim odkazu

3. Text zvýraznění
4. Číst nahlas
5. Vyhledávání textu
6. Následování odkazů pouze pomocí klávesnice

7. Přizpůsobení vzhledu a chování vašeho zážitku ze čtení

8. Vyhledávání ve slovníku
9. Kopírování textu a obrázků
10. Přibližování obrázků
11. Klávesové zkratky
12. Neplovoucí obsah

13. Navrhování knihy tak, aby dobře fungovala s prohlížečem Calibre

Převod e-knihy

1. Úvod

2. Vzhled a chování

1. Písma
2. Text
3. Rozložení
4. Stylování
5. Převod stylů
6. Převod HTML

3. Nastavení stránky
4. Heuristické zpracování
5. Hledat a nahradit

6. Rozpoznávání struktury

1. Konce kapitol a stránek

2. Různé

7. Obsah

8. Požití obrázku jako názvu kapitol pří konverzi HTML vstupních dokumentů

9. Používání atributů značek pro dodání textu pro položky v obsahu

10. Jak jsou nastaveny nebo uloženy volby pro převod

11. Tipy pro konkrétní formáty

1. Převod dokumentů Microsoft Word

1. Starší soubory .doc

2. Převod dokumentů TXT
3. Převod dokumentů PDF
4. Sbírky komiksů
5. Ukázka rozšířeného formátování EPUB
6. Převod dokumentů ODT

7. Převádění do PDF

1. Záhlaví a zápatí

2. Tisknutelný obsah

3. Vlastní okraje stránky pro jednotlivé soubory HTML

Úprava e-knih

Základní pracovní postup

Prohlížeč souborů

1. Přejmenovávání souborů
2. Slučování souborů
3. Změna pořadí textových souborů
4. Označování obálky
5. Odstraňování souborů
6. Exportování souborů

7. Přidávání nových obrázků, písem atd. nebo vytváření nových prázdných souborů

8. Nahrazování souborů

9. Efektivní propojování šablon stylů se soubory HTML

Hledat a nahradit

1. Uložená hledání
2. Režim funkcí
3. Hledat a ignorovat značky HTML

Automatizované nástroje

1. Upravování obsahu
2. Kontrolování knihy
3. Přidávání obálky
4. Vkládání odkazovaných písem
5. Částečné vkládání písem
6. Vylepšení interpunkce
7. Transformace vlastností CSS
8. Odebírání nepoužívaných pravidel CSS
9. Opravování HTML
10. Zkrášlování souborů
11. Vkládání vloženého obsahu
12. Nastavování sémantiky
13. Filtrování informací stylu
14. Upgradování vnitřních prvků knihy

Kontrolní body

Panel Živý náhled

1. Rozdělování souborů HTML

Panel Živé CSS

Různé nástroje

1. Zobrazení obsahu

2. Kontrola pravopisu slov v knize

1. Přidávání nových slovníků

3. Vkládání speciálních znaků
4. Zobrazení kontroly kódu
5. Kontrola externích odkazů
6. Stahování externích zdrojů
7. Uspořádávání souborů do složek podle typu

8. Importování souborů v jiných formátech e-knih než EPUB

9. Nástroj Hlášení

Speciální funkce v editoru kódu

1. Zvýraznění syntaxe
2. Nápověda závislá na kontextu
3. Automatické dokončování
4. Fragmenty

Server s obsahem Calibre

1. Přístup k Serveru s obsahem z jiných zařízení

1. Přístup k serveru ze zařízení ve vaší domácí síti

1. Odstraňování problémů s připojením v domácí síti

2. Přístup k serveru odkudkoliv z internetu

2. Rozhraní serveru

1. Seznam knih

2. Čtečka knih

3. Podpora prohlížečů

4. Povolení offline podpory

5. Správa uživatelských účtů pouze z příkazového řádku

6. Integrace Serveru s obsahem Calibre do jiných serverů

1. Použití plně virtuálního hostitele

2. Používání předpony adresy URL

7. Vytváření služby pro server Calibre na moderním systému Linux

Porovnání e-knih

1. Pochopení zobrazení porovnání

2. Spuštění nástroje porovnání

1. Porovnání dvou souborů e-knih

2. Porovnání ORIGINAL_FMT s FMT

3. Porovnání kontrolního bodu s aktuálním stavem knihy během úpravy

Úprava metadat e-knihy

1. Úprava metadat jedné knihy najednou

1. Stahování metadat
2. Správa formátů knihy
3. Vše o obálkách

2. Úprava metadat mnoha knih najednou

1. Hledat a nahradit

2. Hromadné stahování metadat

3. Přidání dodatečných souborů dat do knihy

Nejčastější dotazy

Převod formátu e-knihy

1. Ze kterých a do kterých formátů podporuje Calibre převod?

2. Jaké jsou nejlepší zdrojové formáty pro převod?

3. Převedl jsem soubor PDF, ale výsledek má různé problémy?

4. Jak převedu můj soubor obsahující neanglické znaky nebo chytré uvozovky?

5. O co jde s obsahem v souborech MOBI?

6. Jak převedu kolekci souborů HTML v konkrétním pořadí?

7. EPUB vytvořený pomocí Calibre není platný?

8. Jak použiji některé pokročilé funkce nástrojů převodu?

Integrace zařízení

1. Která zařízení Calibre podporuje?

2. Jak mohu pomoci, aby Calibre podporovalo mé zařízení?

3. Moje zařízení není v Calibre rozpoznáno?

4. Mé zařízení je nestandardní nebo neobvyklé. Co můžu udělat pro jeho připojení?

5. Jak použiji Calibre s mým zařízením iPad, iPhone nebo iPod touch?

1. Používání Serveru s obsahem

6. How do I use calibre with my Android phone/tablet or Kindle Fire?

1. Pomocí kabelu USB

2. Vzduchem

7. Can I access my calibre books using the web browser in my Kindle or other reading device?

8. I cannot send emails using calibre?

9. My device is getting mounted read-only in Linux, so calibre cannot connect to it?

10. Why does calibre not support collections on the Kindle or shelves on the Nook?

11. I am getting an error when I try to use calibre with my Kobo Touch/Glo/etc.?

12. Covers for books I send to my e-ink Kindle show up momentarily and then are replaced by a generic cover?

13. Obálky pro mé soubory MOBI se přestaly zobrazovat v Kindle pro PC nebo Kindle pro Android, iPad apod.

14. I transferred some books to my Kindle using calibre and they did not show up?

Library management

1. Where are the book files stored?

2. How does calibre manage author names and sorting?

3. Why doesn’t calibre let me store books in my own folder structure?

4. Why doesn’t calibre have a column for foo?

5. Can I have a column showing the formats or the ISBN?

6. How do I move my calibre data from one computer to another?

7. The list of books in calibre is blank!

8. I am getting errors with my calibre library on a networked drive/NAS?

Různé

1. Amazon is stopping email delivery of MOBI files?

2. I want calibre to download news from my favorite news website.

3. Why the name calibre?

4. Why does calibre show only some of my fonts on macOS?

5. calibre is not starting on Windows?

6. calibre freezes/crashes occasionally?

7. The calibre E-book viewer and Edit book tools do not work on Windows?

8. Using the viewer or doing any conversions results in a permission denied error on Windows

9. calibre is not starting/crashing on macOS?

10. I get only a black or white screen when running the calibre E-book viewer?

11. Stáhnul jsem instalační soubor, ale nefunguje?

12. My antivirus program claims calibre is a virus/trojan?

13. How do I backup calibre?

14. How do I use purchased EPUB books with calibre (or what do I do with .acsm files)?

15. I am getting a „Permission Denied“ error?

16. Can I have the comment metadata show up on my reader?

17. How do I get calibre to use my HTTP proxy?

18. I want some feature added to calibre. What can I do?

19. Why doesn’t calibre have an automatic update?

20. How is calibre licensed?

21. How do I run calibre from my USB stick?

22. How do I run parts of calibre like news download and the Content server on my own Linux server?

Kurzy

Přidávání oblíbených webových stránek se zprávami

1. Úplně automatické načítání

1. Blog Calibre

2. bbc.co.uk

2. Přizpůsobení procesu načítání

1. Použití tiskové verze bbc.co.uk
2. Nahrazování stylů článku
3. Krájení a sekání
4. Příklad ze skutečného života

3. Tipy pro vývoj nových předpisů

4. Další čtení

5. Dokumentace API

1. Dokumentace API pro předpisy

Správa podskupin knih, například „žánr

1. Nastavení
2. Hledání
3. Omezení
4. Užitečné funkce šablon

Kurz XPath

1. Výběr podle názvu značky
2. Výběr podle atributů
3. Výběr podle obsahu značky
4. Ukázková e-kniha
5. Vestavěné funkce XPath

Jazyk šablon v programu Calibre

1. Basic templates
2. Advanced formatting
3. Using templates to define custom columns
4. Templates and plugboards

5. Using functions in templates - Single Function Mode

6. General Program Mode

7. More complex programs in template expressions - Template Program Mode

8. Python Template Mode

9. Uložené šablony

10. Providing additional information to templates

11. Notes on the difference between modes
12. User-defined Python template functions
13. Special notes for save/send templates
14. Tips

15. Function reference

1. Reference for all built-in template language functions

Vše o používání regulárních výrazů v Calibre

1. Nejdříve malé varování a malé povzbuzení

2. Kde v Calibre můžete použít regulární výrazy?

3. Co to u všech všudy je regulární příkaz?

4. Zajímá vás vysvětlení?

5. To nezní tak špatně. Co dál?

6. Parádní! Začíná to dávat smysl!

7. No, tyto speciální znaky jsou velice elegantní a tak, ale co když chci porovnat tečku nebo otazník?

8. Takže, jaké jsou nejužitečnější sady?

9. Ale když budu mít několik různých řetězců, které chci porovnat, zkomplikuje se to?

10. Zapomněl jsi…

11. Na začátku jsi říkal, že existuje způsob, aby regulární výraz nerozlišoval malá a velká písmena?

12. Myslím, že teď už začínám ty regulární výrazy chápat… Jak je použiju v Calibre?

1. Převody
2. Přidávání knih
3. Hromadná úprava metadat

13. Rychlý odkaz

1. Základy vyhledávání prostřednictvím regulárních výrazů

14. Poděkování

Psaní vlastních modulů pro rozšíření funkcí Calibre

1. Anatomie modulu Calibre

2. Modul uživatelského rozhraní

1. __init__.py
2. ui.py
3. main.py
4. Získání zdrojů ze souboru ZIP modulu

5. Povolení uživatelské konfigurace vašeho modulu

3. Moduly pro úpravu knihy

1. main.py

4. Přidávání překladů do vašeho modulu
5. Rozhraní API modulu
6. Ladění modulů
7. Další příklady modulů
8. Sdílení vašeho modulu s ostatními

Sazba matematiky v e-knihách

1. Zjednodušený soubor HTML s matematikou

2. Více informací

Vytváření katalogů AZW3 • EPUB • MOBI

1. Výběr knih do katalogu
2. Zahrnuté oddíly
3. Předpony
4. Vyloučené knihy
5. Vyloučené žánry
6. Další volby
7. Vlastní obálky katalogu
8. Dodatečné zdroje nápovědy

Virtuální knihovny

1. Vytváření Virtuálních knihoven

1. Příklady užitečných Virtuálních knihoven

2. Práce s Virtuálními knihovnami
3. Používání Virtuálních knihoven v hledáních
4. Používání dodatečných omezení

calibre:// URL schéma

1. Přepnout na konkrétní knihovnu

2. Zobrazit určitou knihu v calibre

3. Otevřít konkrétní knihu v Prohlížeči e-knih na konkrétní pozici

4. Hledání knih

5. Otevřít okno s podrobnostmi o knize na knize v některé knihovně

6. Otevřít poznámky přidružené k autorovi, sérii atd.

7. Hex kódování URL parametrů

Přizpůsobení Calibre

1. Proměnné prostředí
2. Vylepšení
3. Přepsání ikon, šablon atd.

4. Vytvoření vlastního motivu ikon pro Calibre

5. Přizpůsobení Calibre pomocí modulů

Rozhraní příkazového řádku
Dokumentované příkazy
calibre

1. [volby]

calibre-customize

1. [volby]

calibre-debug

1. [volby]

calibre-server

1. [volby]

calibre-smtp

1. [volby]

calibredb

1. GLOBÁLNÍ VOLBY
2. list
3. add
4. remove
5. add_format
6. remove_format
7. show_metadata
8. set_metadata
9. export
10. catalog
11. saved_searches
12. add_custom_column
13. custom_columns
14. remove_custom_column
15. set_custom
16. restore_database
17. check_library
18. list_categories
19. backup_metadata
20. clone
21. embed_metadata
22. search
23. fts_index
24. fts_search

ebook-convert

1. VOLBY VSTUPU
2. VOLBY VÝSTUPU
3. VZHLED
4. HEURISTICKÉ ZPRACOVÁNÍ
5. NAJÍT A NAHRADIT
6. ROZPOZNÁVÁNÍ STRUKTURY
7. OBSAH
8. METADATA
9. LADĚNÍ

ebook-edit

1. [volby]

ebook-meta

1. [volby]

ebook-polish

1. [volby]

ebook-viewer

1. [volby]

fetch-ebook-metadata

1. [volby]

lrf2lrs

1. [volby]

lrfviewer

1. [volby]

lrs2lrf

1. [volby]

web2disk

1. [volby]

Nedokumentované příkazy

Nastavení vývojového prostředí Calibre

1. Filozofie návrhu

1. Rozložení kódu

2. Získání kódu

1. Odesílání vašich změn, které mají být zahrnuty

3. Vývojové prostředí Windows
4. Vývojové prostředí macOS
5. Vývojové prostředí Linux

6. Mít samostatnou „normální“ a „vývojovou“ instalaci Calibre na stejném počítači

7. Tipy k ladění

1. Používání příkazů tisku

2. Používání interaktivního překladače Pythonu

3. Používání ladicího programu Pythonu jako vzdáleného ladicího programu

4. Používání ladicího programu ve vašem oblíbeném IDE Pythonu

5. Provádění libovolných skriptů uvnitř prostření Pythonu Calibre

8. Používání Calibre ve vašich projektech

1. Binární instalace Calibre

2. Zdrojová instalace na Linuxu

9. Dokumentace API pro různé části Calibre

1. Dokumentace API pro předpisy

1. BasicNewsRecipe

2. Dokumentace API pro moduly

1. Modul
2. ModulTypuSouboru
3. Moduly metadat
4. Moduly katalogu
5. Moduly stahování metadat
6. Moduly převodu
7. Ovladače zařízení
8. User interface actions
9. Moduly předvoleb

3. Dokumentace API pro rozhraní databáze

1. Cache

4. Dokumentace API pro nástroje pro úpravu e-knih

1. Objekt kontejneru

2. Správa souborů komponent v kontejneru

3. Pěkný tisk a automatická analýza opravy chyb

4. Správa přebalů knih
5. Rozdělování a slučování souborů
6. Správa obálek
7. Práce s CSS
8. Práce s obsahem
9. Nástroj na úpravu knih
10. Ovládání uživatelského rozhraní editoru

Správa digitálních práv (DRM)

1. Co z DRM vyplývá pro mě osobně?

2. Co přináší DRM autorům?

3. DRM a svoboda

4. Proč Calibre nepodporuje DRM?

5. Jak Calibre nahlíží na poskytovatele obsahu?

6. Jak mohu pomoci v boji proti DRM?

Slovník pojmů